From 205fdacf9807be2691e13e1d9d04d6b98620391a Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 15 Jan 2025 13:03:59 -0500 Subject: fixed so that users are sentt to dropbox to authorized uploads once, and then a refresh token is used afterwards. fixed up some collection views to work better with the tagsView. fixed grid view to show entire image. tweaked notetaking view to work better with tags, but it's incomplete. --- .../views/collections/collectionGrid/CollectionGridView.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'src/client/views/collections/collectionGrid') diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx index 5c41fee37..6dffb80f1 100644 --- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx +++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx @@ -186,21 +186,23 @@ export class CollectionGridView extends CollectionSubView() { getDisplayDoc(layout: Doc, dxf: () => Transform, width: () => number, height: () => number) { return ( ); } -- cgit v1.2.3-70-g09d2 From 436b3def89ea955e2ff30fe8c2e9d15092e8c9a9 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 5 Mar 2025 15:38:03 -0500 Subject: fixed gridView to have margins. fixed gridView rowslider. --- src/client/views/PropertiesView.tsx | 13 +- .../collectionGrid/CollectionGridView.scss | 13 +- .../collectionGrid/CollectionGridView.tsx | 135 +++++++++++---------- 3 files changed, 84 insertions(+), 77 deletions(-) (limited to 'src/client/views/collections/collectionGrid') diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 8e043baee..11adf7435 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -152,9 +152,16 @@ export class PropertiesView extends ObservableReactComponent (!this.selectedLayoutDoc ? 0 : Math.min(NumCast(this.selectedLayoutDoc?._width), this._props.width - 20)); diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.scss b/src/client/views/collections/collectionGrid/CollectionGridView.scss index 845b07c51..4edaf9745 100644 --- a/src/client/views/collections/collectionGrid/CollectionGridView.scss +++ b/src/client/views/collections/collectionGrid/CollectionGridView.scss @@ -21,11 +21,11 @@ width: 100%; } - .react-grid-item>.react-resizable-handle { + .react-grid-item > .react-resizable-handle { z-index: 4; // doesn't work on prezi otherwise } - .react-grid-item>.react-resizable-handle::after { + .react-grid-item > .react-resizable-handle::after { // grey so it can be seen on the audiobox border-right: 2px solid slategrey; border-bottom: 2px solid slategrey; @@ -41,7 +41,6 @@ position: absolute; height: 3; left: 5; - top: 30; transform-origin: left; transform: rotate(90deg); outline: none; @@ -103,7 +102,7 @@ span::before, span::after { - content: ""; + content: ''; width: 50%; position: relative; display: inline-block; @@ -128,10 +127,8 @@ } } } - } - /* Chrome, Safari, Edge, Opera */ input::-webkit-outer-spin-button, input::-webkit-inner-spin-button { @@ -140,6 +137,6 @@ input::-webkit-inner-spin-button { } /* Firefox */ -input[type=number] { +input[type='number'] { -moz-appearance: textfield; -} \ No newline at end of file +} diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx index 6dffb80f1..80bf4bd12 100644 --- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx +++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx @@ -9,7 +9,7 @@ import { emptyFunction } from '../../../../Utils'; import { Docs } from '../../../documents/Documents'; import { DragManager } from '../../../util/DragManager'; import { Transform } from '../../../util/Transform'; -import { undoable, undoBatch } from '../../../util/UndoManager'; +import { undoable } from '../../../util/UndoManager'; import { ContextMenu } from '../../ContextMenu'; import { ContextMenuProps } from '../../ContextMenuItem'; import { DocumentView } from '../../nodes/DocumentView'; @@ -22,9 +22,9 @@ export class CollectionGridView extends CollectionSubView() { private _containerRef: React.RefObject = React.createRef(); private _changeListenerDisposer: Opt; // listens for changes in this.childLayoutPairs private _resetListenerDisposer: Opt; // listens for when the reset button is clicked + private _dropLocation: object = {}; // sets the drop location for external drops @observable private _rowHeight: Opt = undefined; // temporary store of row height to make change undoable @observable private _scroll: number = 0; // required to make sure the decorations box container updates on scroll - private dropLocation: object = {}; // sets the drop location for external drops constructor(props: SubCollectionViewProps) { super(props); @@ -48,14 +48,20 @@ export class CollectionGridView extends CollectionSubView() { } @computed get colWidthPlusGap() { - return (this._props.PanelWidth() - this.margin) / this.numCols; + return (this._props.PanelWidth() - 2 * this.xMargin - this.gridGap) / this.numCols; } @computed get rowHeightPlusGap() { - return this.rowHeight + this.margin; + return this.rowHeight + this.gridGap; } - @computed get margin() { - return NumCast(this.Document.margin, 10); + @computed get xMargin() { + return NumCast(this.layoutDoc._xMargin, Math.max(3, 0.05 * this._props.PanelWidth())); + } + @computed get yMargin() { + return this._props.yPadding || NumCast(this.layoutDoc._yMargin, Math.min(5, 0.05 * this._props.PanelWidth())); + } + @computed get gridGap() { + return NumCast(this.Document._gridGap, 10); } // sets the margin between grid nodes @computed get flexGrid() { @@ -77,10 +83,10 @@ export class CollectionGridView extends CollectionSubView() { pairs.forEach((pair, i) => { const existing = oldLayouts.find(l => l.i === pair.layout[Id]); if (existing) newLayouts.push(existing); - else if (Object.keys(this.dropLocation).length) { + else if (Object.keys(this._dropLocation).length) { // external drop - this.addLayoutItem(newLayouts, this.makeLayoutItem(pair.layout, this.dropLocation as { x: number; y: number }, !this.flexGrid)); - this.dropLocation = {}; + this.addLayoutItem(newLayouts, this.makeLayoutItem(pair.layout, this._dropLocation as { x: number; y: number }, !this.flexGrid)); + this._dropLocation = {}; } else { // internal drop this.addLayoutItem(newLayouts, this.makeLayoutItem(pair.layout, this.unflexedPosition(i), !this.flexGrid)); @@ -115,30 +121,29 @@ export class CollectionGridView extends CollectionSubView() { * @returns the default location of the grid node (i.e. when the grid is static) * @param index */ - unflexedPosition(index: number): Omit { - return { - x: (index % (Math.floor(this.numCols / this.defaultW) || 1)) * this.defaultW, - y: Math.floor(index / (Math.floor(this.numCols / this.defaultH) || 1)) * this.defaultH, - w: this.defaultW, - h: this.defaultH, - static: true, - }; - } + unflexedPosition = (index: number): Omit => ({ + x: (index % (Math.floor(this.numCols / this.defaultW) || 1)) * this.defaultW, + y: Math.floor(index / (Math.floor(this.numCols / this.defaultH) || 1)) * this.defaultH, + w: this.defaultW, + h: this.defaultH, + static: true, + }); /** * Maps the x- and y- coordinates of the event to a grid cell. */ - screenToCell(sx: number, sy: number) { - const pt = this.ScreenToLocalBoxXf().transformPoint(sx, sy); - const x = Math.floor(pt[0] / this.colWidthPlusGap); - const y = Math.floor((pt[1] + this._scroll) / this.rowHeight); + screenToCell = (sx: number, sy: number) => { + const [ptx, pty] = this.ScreenToLocalBoxXf().transformPoint(sx, sy); + const x = Math.floor((ptx + this.xMargin) / this.colWidthPlusGap); + const y = Math.floor((pty + this.yMargin + this._scroll) / this.rowHeight); return { x, y }; - } + }; /** * Creates a layout object for a grid item */ - makeLayoutItem = (doc: Doc, pos: { x: number; y: number }, Static: boolean = false, w: number = this.defaultW, h: number = this.defaultH) => ({ i: doc[Id], w, h, x: pos.x, y: pos.y, static: Static }); + makeLayoutItem = (doc: Doc, pos: { x: number; y: number }, Static: boolean = false, w: number = this.defaultW, h: number = this.defaultH) => + ({ i: doc[Id], w, h, x: pos.x, y: pos.y, static: Static }); // prettier-ignore /** * Adds a layout to the list of layouts. @@ -152,9 +157,9 @@ export class CollectionGridView extends CollectionSubView() { /** * @returns the transform that will correctly place the document decorations box. */ - private lookupIndividualTransform = (layout: Layout) => { + lookupIndividualTransform = (layout: Layout) => { const xypos = this.flexGrid ? layout : this.unflexedPosition(this.renderedLayoutList.findIndex(l => l.i === layout.i)); - const pos = { x: xypos.x * this.colWidthPlusGap + this.margin, y: xypos.y * this.rowHeightPlusGap + this.margin - this._scroll }; + const pos = { x: xypos.x * this.colWidthPlusGap + this.gridGap + this.xMargin, y: xypos.y * this.rowHeightPlusGap + this.gridGap - this._scroll + this.yMargin }; return this.ScreenToLocalBoxXf().translate(-pos.x, -pos.y); }; @@ -169,9 +174,9 @@ export class CollectionGridView extends CollectionSubView() { /** * Stores the layout list on the Document as JSON */ - setLayoutList(layouts: Layout[]) { + setLayoutList = (layouts: Layout[]) => { this.Document.gridLayoutString = JSON.stringify(layouts); - } + }; isContentActive = () => this._props.isSelected() || this._props.isContentActive(); isChildContentActive = () => (this._props.isDocumentActive?.() && (this._props.childDocumentsActive?.() || BoolCast(this.Document.childDocumentsActive)) ? true : undefined); @@ -183,29 +188,27 @@ export class CollectionGridView extends CollectionSubView() { * @param height * @returns the `ContentFittingDocumentView` of the node */ - getDisplayDoc(layout: Doc, dxf: () => Transform, width: () => number, height: () => number) { - return ( - - ); - } + getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number) => ( + + ); /** * Saves the layouts received from the Grid to the Document. @@ -238,15 +241,14 @@ export class CollectionGridView extends CollectionSubView() { * @returns a list of `ContentFittingDocumentView`s inside wrapper divs. * The key of the wrapper div must be the same as the `i` value of the corresponding layout. */ - @computed - private get contents(): JSX.Element[] { + @computed get contents(): JSX.Element[] { const collector: JSX.Element[] = []; if (this.renderedLayoutList.length === this.childLayoutPairs.length) { this.renderedLayoutList.forEach(l => { const child = this.childLayoutPairs.find(c => c.layout[Id] === l.i); const dxf = () => this.lookupIndividualTransform(l); - const width = () => (this.flexGrid ? l.w : this.defaultW) * this.colWidthPlusGap - this.margin; - const height = () => (this.flexGrid ? l.h : this.defaultH) * this.rowHeightPlusGap - this.margin; + const width = () => (this.flexGrid ? l.w : this.defaultW) * this.colWidthPlusGap - this.gridGap; + const height = () => (this.flexGrid ? l.h : this.defaultH) * this.rowHeightPlusGap - this.gridGap; child && collector.push(
@@ -295,7 +297,7 @@ export class CollectionGridView extends CollectionSubView() { * Handles external drop of images/PDFs etc from outside Dash. */ onExternalDrop = async (e: React.DragEvent): Promise => { - this.dropLocation = this.screenToCell(e.clientX, e.clientY); + this._dropLocation = this.screenToCell(e.clientX, e.clientY); super.onExternalDrop(e, {}); }; @@ -316,12 +318,13 @@ export class CollectionGridView extends CollectionSubView() { this, e, returnFalse, - action(() => { - undoable(() => { + undoable( + action(() => { this.Document.gridRowHeight = this._rowHeight; - }, 'changing row height')(); - this._rowHeight = undefined; - }), + this._rowHeight = undefined; + }), + 'changing row height' + ), emptyFunction, false, false @@ -391,14 +394,14 @@ export class CollectionGridView extends CollectionSubView() {
e.stopPropagation()} onScroll={action(e => { if (!this._props.isSelected()) e.currentTarget.scrollTop = this._scroll; else this._scroll = e.currentTarget.scrollTop; })}> -- cgit v1.2.3-70-g09d2 From 1ab6f6c87b746e0c2898694216db50d5faadf7f5 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 6 Mar 2025 19:46:43 -0500 Subject: a bunch of changes to improve how docs are selected automatically when created. --- src/client/documents/DocUtils.ts | 5 +- src/client/views/GlobalKeyHandler.ts | 19 +++-- src/client/views/MarqueeAnnotator.tsx | 2 +- src/client/views/SidebarAnnos.tsx | 9 +-- .../collections/CollectionMasonryViewFieldRow.tsx | 7 +- .../views/collections/CollectionNoteTakingView.tsx | 2 +- .../collections/CollectionNoteTakingViewColumn.tsx | 21 ++---- .../views/collections/CollectionStackingView.tsx | 2 +- .../CollectionStackingViewFieldColumn.tsx | 26 ++----- .../views/collections/CollectionTreeView.tsx | 2 +- src/client/views/collections/TreeView.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 5 +- .../collections/collectionFreeForm/MarqueeView.tsx | 2 +- .../collectionGrid/CollectionGridView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 6 ++ src/client/views/nodes/EquationBox.tsx | 10 +-- src/client/views/nodes/LabelBox.tsx | 5 +- src/client/views/nodes/MapBox/MapBox.tsx | 2 +- src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx | 2 +- .../views/nodes/MapboxMapBox/MapboxContainer.tsx | 2 +- .../views/nodes/formattedText/DailyJournal.tsx | 25 +++---- .../views/nodes/formattedText/FormattedTextBox.tsx | 41 +++------- src/fields/Doc.ts | 4 - src/fields/RichTextField.ts | 87 +++++++++++++++++----- 24 files changed, 149 insertions(+), 141 deletions(-) (limited to 'src/client/views/collections/collectionGrid') diff --git a/src/client/documents/DocUtils.ts b/src/client/documents/DocUtils.ts index 7e0416447..9797a407b 100644 --- a/src/client/documents/DocUtils.ts +++ b/src/client/documents/DocUtils.ts @@ -385,7 +385,7 @@ export namespace DocUtils { newDoc.x = x; newDoc.y = y; newDoc[DocData].backgroundColor = Doc.UserDoc().textBackgroundColor; - Doc.SetSelectOnLoad(newDoc); + DocumentView.SetSelectOnLoad(newDoc); if (pivotField) { newDoc[pivotField] = pivotValue; } @@ -408,6 +408,7 @@ export namespace DocUtils { description: ':Daily Journal', event: undoable(() => { const newDoc = Docs.Create.DailyJournalDocument('', { x, y }); + DocumentView.SetSelectOnLoad(newDoc); docAdder?.(newDoc); }, 'Create Daily Journal'), icon: 'book', @@ -456,7 +457,7 @@ export namespace DocUtils { newDoc.author = ClientUtils.CurrentUserEmail(); newDoc.x = x; newDoc.y = y; - Doc.SetSelectOnLoad(newDoc); + DocumentView.SetSelectOnLoad(newDoc); if (pivotField) { newDoc[pivotField] = pivotValue; } diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index b200aff65..2d342d1b1 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -23,7 +23,6 @@ import { CollectionFreeFormDocumentView } from './nodes/CollectionFreeFormDocume import { DocumentLinksButton } from './nodes/DocumentLinksButton'; import { DocumentView } from './nodes/DocumentView'; import { OpenWhereMod } from './nodes/OpenWhere'; -import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; import { AnchorMenu } from './pdf/AnchorMenu'; const modifiers = ['control', 'meta', 'shift', 'alt']; @@ -75,7 +74,6 @@ export class KeyManager { public handle = action((e: KeyboardEvent) => { // accumulate buffer of characters to insert in a new text note. once the note is created, it will stop keyboard events from reaching this function. - if (FormattedTextBox.SelectOnLoadChar) FormattedTextBox.SelectOnLoadChar += e.key === 'Enter' ? '\n' : e.key; const keyname = e.key && e.key.toLowerCase(); this.handleGreedy(/* keyname */); @@ -106,6 +104,10 @@ export class KeyManager { }; private unmodified = action((keyname: string, e: KeyboardEvent) => { + const nothing = { + stopPropagation: false, + preventDefault: false, + }; switch (keyname) { case 'u': if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') { @@ -169,17 +171,14 @@ export class KeyManager { return { stopPropagation: true, preventDefault: true }; } break; - case 'arrowleft': return (e.target as any).type !== 'text' && this.nudge(-1, 0, 'nudge left') // if target is an input box, then we don't want to nudge any Docs since we're justing moving within the text itself. - case 'arrowright': return (e.target as any).type !== 'text' && this.nudge( 1, 0, 'nudge right'); - case 'arrowup': return (e.target as any).type !== 'text' && this.nudge(0, -1, 'nudge up'); - case 'arrowdown': return (e.target as any).type !== 'text' && this.nudge(0, 1, 'nudge down'); + case 'arrowleft': return (e.target as HTMLInputElement)?.type !== 'text' ? this.nudge(-1, 0, 'nudge left') : nothing; // if target is an input box, then we don't want to nudge any Docs since we're justing moving within the text itself. + case 'arrowright': return (e.target as HTMLInputElement)?.type !== 'text' ? this.nudge( 1, 0, 'nudge right') : nothing; + case 'arrowup': return (e.target as HTMLInputElement)?.type !== 'text' ? this.nudge(0, -1, 'nudge up') : nothing; + case 'arrowdown': return (e.target as HTMLInputElement | null)?.type !== 'text'? this.nudge(0, 1, 'nudge down'): nothing; default: } // prettier-ignore - return { - stopPropagation: false, - preventDefault: false, - }; + return nothing; }); private shift = action((keyname: string) => { diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index 02516264c..3f4200dce 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -199,7 +199,7 @@ export class MarqueeAnnotator extends ObservableReactComponent { const target = DocUtils.GetNewTextDoc('Note linked to ' + this.props.Document.title, 0, 0, 100, 100, annotationOn, 'yellow'); - Doc.SetSelectOnLoad(target); + DocumentView.SetSelectOnLoad(target); return target; }; DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docView(), sourceAnchorCreator, targetCreator), e.pageX, e.pageY, { diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx index 87076bf65..816fc8ed3 100644 --- a/src/client/views/SidebarAnnos.tsx +++ b/src/client/views/SidebarAnnos.tsx @@ -3,23 +3,23 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { ClientUtils, returnAll, returnFalse, returnOne, returnZero } from '../../ClientUtils'; import { emptyFunction } from '../../Utils'; -import { Doc, DocListCast, Field, FieldType, FieldResult, StrListCast } from '../../fields/Doc'; +import { Doc, DocListCast, Field, FieldResult, FieldType, StrListCast } from '../../fields/Doc'; import { DocData } from '../../fields/DocSymbols'; import { Id } from '../../fields/FieldSymbols'; import { List } from '../../fields/List'; import { RichTextField } from '../../fields/RichTextField'; import { DocCast, NumCast, StrCast } from '../../fields/Types'; +import { DocUtils } from '../documents/DocUtils'; import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; import { Docs } from '../documents/Documents'; -import { DocUtils } from '../documents/DocUtils'; import { SearchUtil } from '../util/SearchUtil'; import { Transform } from '../util/Transform'; import { ObservableReactComponent } from './ObservableReactComponent'; import './SidebarAnnos.scss'; import { StyleProp } from './StyleProp'; import { CollectionStackingView } from './collections/CollectionStackingView'; +import { DocumentView } from './nodes/DocumentView'; import { FieldViewProps } from './nodes/FieldView'; -import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; interface ExtraProps { fieldKey: string; @@ -81,8 +81,7 @@ export class SidebarAnnos extends ObservableReactComponent this.addNewTextDoc('-typed text-', false, true), 'add text note'); - // addNewTextDoc is called when a user starts typing in a column to create a new node - @action - addNewTextDoc = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => { - if (!value && !forceEmptyNote) return false; + addTextNote = undoable(() => { const key = this._props.pivotField; - const newDoc = Docs.Create.TextDocument(value, { _height: 18, _width: 200, _layout_fitWidth: true, title: value, _layout_autoHeight: true }); + const newDoc = Docs.Create.TextDocument('', { _height: 18, _width: 200, _layout_fitWidth: true, _layout_autoHeight: true }); const colValue = this.getValue(this._props.heading); newDoc[key] = colValue; - Doc.SetSelectOnLoad(newDoc); - FormattedTextBox.SelectOnLoadChar = forceEmptyNote ? '' : ' '; + DocumentView.SetSelectOnLoad(newDoc); return this._props.addDocument?.(newDoc) || false; - }; + }, 'add text note'); // deleteColumn is called when a user deletes a column using the 'trash' icon in the button area. // If the user deletes the first column, the documents get moved to the second column. Otherwise, @@ -177,7 +172,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent { const key = this._props.pivotField; doc[key] = this.getValue(this._props.heading); - Doc.SetSelectOnLoad(doc); + DocumentView.SetSelectOnLoad(doc); return this._props.addDocument?.(doc); }, this._props.addDocument, @@ -249,7 +244,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent
- +
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 96125f2c2..72e6bb8f2 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -321,7 +321,7 @@ export class CollectionStackingView extends CollectionSubView { SnappingManager.IsDragging && (this._background = '#b4b4b4'); } // prettier-ignore @action pointerLeave = () => { this._background = 'inherit'}; // prettier-ignore - @undoBatch typedNote = () => this.addNewTextDoc('-typed text-', false, true); - - @action - addNewTextDoc = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => { - if (!value && !forceEmptyNote) return false; + @undoBatch typedNote = () => { const key = this._props.pivotField; - const newDoc = Docs.Create.TextDocument(value, { _height: 18, _width: 200, _layout_fitWidth: true, title: value, _layout_autoHeight: true }); + const newDoc = Docs.Create.TextDocument('', { _height: 18, _width: 200, _layout_fitWidth: true, _layout_autoHeight: true }); key && (newDoc[key] = this.getValue(this._props.heading)); const maxHeading = this._props.docList.reduce((prevHeading, doc) => (NumCast(doc.heading) > prevHeading ? NumCast(doc.heading) : prevHeading), 0); const heading = maxHeading === 0 || this._props.docList.length === 0 ? 1 : maxHeading === 1 ? 2 : 3; newDoc.heading = heading; - Doc.SetSelectOnLoad(newDoc); - FormattedTextBox.SelectOnLoadChar = forceEmptyNote ? '' : ' '; + DocumentView.SetSelectOnLoad(newDoc); return this._props.addDocument?.(newDoc) || false; }; @@ -240,7 +235,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< const height = this._ele ? DivHeight(this._ele) : 0; DocUtils.addDocumentCreatorMenuItems( doc => { - Doc.SetSelectOnLoad(doc); + DocumentView.SetSelectOnLoad(doc); return this._props.addDocument?.(doc); }, this._props.addDocument, @@ -394,14 +389,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< onKeyDown={e => e.stopPropagation()} className="collectionStackingView-addDocumentButton" style={{ width: 'calc(100% - 25px)', maxWidth: this._props.columnWidth / this._props.numGroupColumns - 25, marginBottom: 10 }}> - } - menuCallback={this.menuCallback} - /> + } menuCallback={this.menuCallback} />
) : null}
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index a60cd98ac..e93724dd4 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -174,7 +174,7 @@ export class CollectionTreeView extends CollectionSubView 0 && prev) { - Doc.SetSelectOnLoad(prev); + DocumentView.SetSelectOnLoad(prev); DocumentView.getDocumentView(prev, this.DocumentView?.())?.select(false); } return true; diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index ab4d8b060..6208905fe 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -1300,7 +1300,7 @@ export class TreeView extends ObservableReactComponent { const fieldKey = Doc.LayoutFieldKey(newParent); if (remove && fieldKey && Cast(newParent[fieldKey], listSpec(Doc)) !== undefined) { remove(child); - Doc.SetSelectOnLoad(child); + DocumentView.SetSelectOnLoad(child); TreeView._editTitleOnLoad = editTitle ? { id: child[Id], parent } : undefined; Doc.AddDocToList(newParent, fieldKey, child, addAfter, false); newParent.treeView_Open = true; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 43addfc29..89aa53c35 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -293,7 +293,7 @@ export class CollectionFreeFormView extends CollectionSubView this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout); isAnyChildContentActive = () => this._props.isAnyChildContentActive(); addLiveTextBox = (newDoc: Doc) => { - Doc.SetSelectOnLoad(newDoc); // track the new text box so we can give it a prop that tells it to focus itself when it's displayed + DocumentView.SetSelectOnLoad(newDoc); // track the new text box so we can give it a prop that tells it to focus itself when it's displayed this.addDocument(newDoc); }; selectDocuments = (docs: Doc[]) => { @@ -1496,8 +1496,7 @@ export class CollectionFreeFormView extends CollectionSubView { const text = Docs.Create.TextDocument('', { _width: 150, _height: 50 }); - Doc.SetSelectOnLoad(text); // track the new text box so we can give it a prop that tells it to focus itself when it's displayed + DocumentView.SetSelectOnLoad(text); // track the new text box so we can give it a prop that tells it to focus itself when it's displayed Doc.AddDocToList(this.Document, this._props.fieldKey, text); this.setLayoutList(this.addLayoutItem(this.savedLayoutList, this.makeLayoutItem(text, this.screenToCell(clickEv.clientX, clickEv.clientY)))); }), diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 595abc7f8..a514ee04e 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1110,6 +1110,12 @@ export class DocumentView extends DocComponent() { public static DeselectAll: (except?: Doc) => void | undefined; public static DeselectView: (dv: DocumentView | undefined) => void | undefined; public static SelectView: (dv: DocumentView | undefined, extendSelection: boolean) => void | undefined; + + public static SelectOnLoad: Doc | undefined; + public static SetSelectOnLoad(doc?: Doc) { + DocumentView.SelectOnLoad = doc; + doc && DocumentView.addViewRenderedCb(doc, dv => dv.select(false)); + } /** * returns a list of all currently selected DocumentViews */ diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx index 576b5bbe0..dcc6e27ed 100644 --- a/src/client/views/nodes/EquationBox.tsx +++ b/src/client/views/nodes/EquationBox.tsx @@ -30,12 +30,12 @@ export class EquationBox extends ViewBoxBaseComponent() { componentDidMount() { this._props.setContentViewBox?.(this); - if (Doc.SelectOnLoad === this.Document && (!DocumentView.LightboxDoc() || DocumentView.LightboxContains(this.DocumentView?.()))) { + if (DocumentView.SelectOnLoad === this.Document && (!DocumentView.LightboxDoc() || DocumentView.LightboxContains(this.DocumentView?.()))) { this._props.select(false); - this._ref.current!.mathField.focus(); - this.dataDoc.text === 'x' && this._ref.current!.mathField.select(); - Doc.SetSelectOnLoad(undefined); + this._ref.current?.mathField.focus(); + this.dataDoc.text === 'x' && this._ref.current?.mathField.select(); + DocumentView.SetSelectOnLoad(undefined); } reaction( () => this._props.isSelected(), @@ -66,7 +66,7 @@ export class EquationBox extends ViewBoxBaseComponent() { color: StrCast(this.Document.color), fontSize: this.fontSize, }); - Doc.SetSelectOnLoad(nextEq); + DocumentView.SetSelectOnLoad(nextEq); this._props.addDocument?.(nextEq); e.stopPropagation(); } diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index dcf9e1fed..7fb83571f 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -17,6 +17,7 @@ import { FieldView, FieldViewProps } from './FieldView'; import './LabelBox.scss'; import { FormattedTextBox } from './formattedText/FormattedTextBox'; import { RichTextMenu } from './formattedText/RichTextMenu'; +import { DocumentView } from './DocumentView'; @observer export class LabelBox extends ViewBoxBaseComponent() { @@ -230,8 +231,8 @@ export class LabelBox extends ViewBoxBaseComponent() { if (this._divRef) { this._divRef.addEventListener('beforeinput', this.beforeInput); - if (Doc.SelectOnLoad === this.Document) { - Doc.SelectOnLoad = undefined; + if (DocumentView.SelectOnLoad === this.Document) { + DocumentView.SetSelectOnLoad(undefined); this._divRef.focus(); } this.fitTextToBox(this._divRef); diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx index 541b41bf7..792cb6b46 100644 --- a/src/client/views/nodes/MapBox/MapBox.tsx +++ b/src/client/views/nodes/MapBox/MapBox.tsx @@ -353,7 +353,7 @@ export class MapBox extends ViewBoxAnnotatableComponent() { const targetCreator = (annotationOn: Doc | undefined) => { const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, annotationOn, 'yellow'); - Doc.SetSelectOnLoad(target); + DocumentView.SetSelectOnLoad(target); return target; }; const docView = this.DocumentView?.(); diff --git a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx index eb0431b85..a27a8bda1 100644 --- a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx +++ b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx @@ -32,7 +32,7 @@ // addNoteClick = (e: React.PointerEvent) => { // setupMoveUpEvents(this, e, returnFalse, emptyFunction, e => { // const newDoc = Docs.Create.TextDocument('Note', { _layout_autoHeight: true }); -// Doc.SetSelectOnLoad(newDoc); // track the new text box so we can give it a prop that tells it to focus itself when it's displayed +// DocumentView.SetSelectOnLoad(newDoc); // track the new text box so we can give it a prop that tells it to focus itself when it's displayed // Doc.AddDocToList(this.props.place, 'data', newDoc); // this._stack?.scrollToBottom(); // e.stopPropagation(); diff --git a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx index 95f89a573..0627d382e 100644 --- a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx +++ b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx @@ -237,7 +237,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent const targetCreator = (annotationOn: Doc | undefined) => { const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, annotationOn, 'yellow'); - Doc.SetSelectOnLoad(target); + DocumentView.SetSelectOnLoad(target); return target; }; const docView = this.DocumentView?.(); diff --git a/src/client/views/nodes/formattedText/DailyJournal.tsx b/src/client/views/nodes/formattedText/DailyJournal.tsx index 9decbfaf0..7999357b0 100644 --- a/src/client/views/nodes/formattedText/DailyJournal.tsx +++ b/src/client/views/nodes/formattedText/DailyJournal.tsx @@ -51,22 +51,13 @@ export class DailyJournal extends ViewBoxAnnotatableComponent() @action setDailyText() { console.log('setDailyText() called...'); - const initialText = `Journal Entry - ${this.journalDate}\n\nStart writing here...`; + const placeholderText = 'Start writing here...'; + const initialText = `Journal Entry - ${this.journalDate}\n${placeholderText}`; console.log('Checking if dataDoc has text field...'); console.log('Setting new text field with:', initialText); - this.dataDoc[this.fieldKey] = new RichTextField( - JSON.stringify({ - doc: { - type: 'doc', - content: [{ type: 'paragraph', content: [{ type: 'text', text: initialText }] }], - }, - selection: { type: 'text', anchor: 1, head: 1 }, - storedMarks: [], - }), - initialText - ); + this.dataDoc[this.fieldKey] = RichTextField.textToRtf(initialText, undefined, placeholderText.length); console.log('Current text field:', this.dataDoc[this.fieldKey]); } @@ -85,10 +76,11 @@ export class DailyJournal extends ViewBoxAnnotatableComponent() } render() { - return
- - -
; + return ( +
+ +
+ ); } } @@ -99,6 +91,7 @@ Docs.Prototypes.TemplateMap.set(DocumentType.JOURNAL, { _height: 35, _xMargin: 10, _yMargin: 10, + _layout_autoHeight: true, _layout_nativeDimEditable: true, _layout_reflowVertical: true, _layout_reflowHorizontal: true, diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index c2a2caecf..83ee619d0 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -99,7 +99,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { [key: string]: NodeViewConstructor }) { FormattedTextBox._nodeViews = nodeViews; } // prettier-ignore public static PasteOnLoad: ClipboardEvent | undefined; - public static DontSelectInitialText = false; // whether initial text should be selected or not public static SelectOnLoadChar = ''; public static LiveTextUndo: UndoManager.Batch | undefined; // undo batch when typing a new text note into a collection @@ -286,7 +285,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, annotationOn); - Doc.SetSelectOnLoad(target); + DocumentView.SetSelectOnLoad(target); return target; }; @@ -1542,44 +1541,26 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent m.type !== mark.type), mark]; + let { tr } = this.EditorView.state; if (selLoadChar) { - const $from = this.EditorView.state.selection.anchor ? this.EditorView.state.doc.resolve(this.EditorView.state.selection.anchor - 1) : undefined; - const mark = schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) }); - const curMarks = this.EditorView.state.storedMarks ?? $from?.marksAcross(this.EditorView.state.selection.$head) ?? []; - const storedMarks = [...curMarks.filter(m => m.type !== mark.type), mark]; const tr1 = this.EditorView.state.tr.setStoredMarks(storedMarks); - const tr2 = selLoadChar === 'Enter' ? tr1.insert(this.EditorView.state.doc.content.size - 1, schema.nodes.paragraph.create()) : tr1.insertText(selLoadChar, this.EditorView.state.doc.content.size - 1); - const tr = tr2.setStoredMarks(storedMarks); - - this.EditorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size)))); - this.tryUpdateDoc(true); // calling select() above will make isContentActive() true only after a render .. which means the selectAll() above won't write to the Document and the incomingValue will overwrite the selection with the non-updated data - } else if (!FormattedTextBox.DontSelectInitialText) { - const mark = schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) }); - selectAll(this.EditorView.state, (tx: Transaction) => { - this.EditorView?.dispatch(tx.addStoredMark(mark)); - }); - this.EditorView?.dispatch(this.EditorView.state.tr.setSelection(new TextSelection(this.EditorView.state.doc.resolve(1)))); - this.tryUpdateDoc(true); // calling select() above will make isContentActive() true only after a render .. which means the selectAll() above won't write to the Document and the incomingValue will overwrite the selection with the non-updated data - } else { - const $from = this.EditorView.state.selection.anchor ? this.EditorView.state.doc.resolve(this.EditorView.state.selection.anchor - 1) : undefined; - const mark = schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) }); - const curMarks = this.EditorView.state.storedMarks ?? $from?.marksAcross(this.EditorView.state.selection.$head) ?? []; - const storedMarks = [...curMarks.filter(m => m.type !== mark.type), mark]; - const { tr } = this.EditorView.state; - this.EditorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size))).setStoredMarks(storedMarks)); - this.tryUpdateDoc(true); // calling select() above will make isContentActive() true only after a render .. which means the selectAll() above won't write to the Document and the incomingValue will overwrite the selection with the non-updated data + tr = selLoadChar === 'Enter' ? tr1.insert(this.EditorView.state.doc.content.size - 1, schema.nodes.paragraph.create()) : tr1.insertText(selLoadChar, this.EditorView.state.doc.content.size - 1); } + this.EditorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size))).setStoredMarks(storedMarks)); + this.tryUpdateDoc(true); // calling select() above will make isContentActive() true only after a render .. which means the selectAll() above won't write to the Document and the incomingValue will overwrite the selection with the non-updated data } if (selectOnLoad) { - FormattedTextBox.DontSelectInitialText = false; this.EditorView!.focus(); } if (this._props.isContentActive()) this.prepareForTyping(); diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index bdd41b0bb..805bb4526 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -468,10 +468,6 @@ export class Doc extends RefField { } } export namespace Doc { - export let SelectOnLoad: Doc | undefined; - export function SetSelectOnLoad(doc: Doc | undefined) { - SelectOnLoad = doc; - } export let DocDragDataName: string = ''; export function SetDocDragDataName(name: string) { DocDragDataName = name; diff --git a/src/fields/RichTextField.ts b/src/fields/RichTextField.ts index dc636031a..4b1403b88 100644 --- a/src/fields/RichTextField.ts +++ b/src/fields/RichTextField.ts @@ -48,26 +48,77 @@ export class RichTextField extends ObjectField { '' ); } + static Initialize = (initial?: string) => { + const content: object[] = []; + const state = { + doc: { + type: 'doc', + content, + }, + selection: { + type: 'text', + anchor: 0, + head: 0, + }, + }; + if (initial && initial.length) { + content.push({ + type: 'paragraph', + content: { + type: 'text', + text: initial, + }, + }); + state.selection.anchor = state.selection.head = initial.length + 1; + } + return JSON.stringify(state); + }; + + private static ToProsemirrorState = (plainText: string, selectBack?: number, delimeter = '\n') => { + // Remap the text, creating blocks split on newlines + const elements = plainText.split(delimeter); + + // Google Docs adds in an extra carriage return automatically, so this counteracts it + !elements[elements.length - 1].length && elements.pop(); + + // Preserve the current state, but re-write the content to be the blocks + const parsed: Record = JSON.parse(RichTextField.Initialize()); + (parsed.doc as Record).content = elements.map(text => { + const paragraph: object = { + type: 'paragraph', + content: text.length ? [{ type: 'text', marks: [], text }] : undefined, // An empty paragraph gets treated as a line break + }; + return paragraph; + }); - public static textToRtf(text: string, imgDocId?: string) { + // If the new content is shorter than the previous content and selection is unchanged, may throw an out of bounds exception, so we reset it + parsed.selection = { type: 'text', anchor: 2 + plainText.length - (selectBack ?? 0), head: 2 + plainText.length }; + + // Export the ProseMirror-compatible state object we've just built + return JSON.stringify(parsed); + }; + + public static textToRtf(text: string, imgDocId?: string, selectBack?: number) { return new RichTextField( - JSON.stringify({ - // this is a RichText json that has the question text placed above a related image - doc: { - type: 'doc', - content: [ - { - type: 'paragraph', - attrs: { align: 'center', color: null, id: null, indent: null, inset: null, lineSpacing: null, paddingBottom: null, paddingTop: null }, - content: [ - ...(text ? [{ type: 'text', text }] : []), // - ...(imgDocId ? [{ type: 'dashDoc', attrs: { width: '200px', height: '200px', title: 'dashDoc', float: 'unset', hidden: false, docId: imgDocId } }] : []), - ], - }, - ], - }, - selection: { type: 'text', anchor: 2, head: 2 }, - }), + !imgDocId + ? this.ToProsemirrorState(text, selectBack) + : JSON.stringify({ + // this is a RichText json that has the question text placed above a related image + doc: { + type: 'doc', + content: [ + { + type: 'paragraph', + attrs: { align: 'center', color: null, id: null, indent: null, inset: null, lineSpacing: null, paddingBottom: null, paddingTop: null }, + content: [ + ...(text ? [{ type: 'text', text }] : []), // + ...(imgDocId ? [{ type: 'dashDoc', attrs: { width: '200px', height: '200px', title: 'dashDoc', float: 'unset', hidden: false, docId: imgDocId } }] : []), + ], + }, + ], + }, + selection: { type: 'text', anchor: 2 + text.length, head: 2 + text.length }, + }), text ); } -- cgit v1.2.3-70-g09d2