From 9b424c94d7a89950e9cf3f72e684bd15a61e87ae Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 2 May 2024 11:19:37 -0400 Subject: another push to remove cycles by pushing things onto Doc and DocumentView --- src/client/DocServer.ts | 16 +++----- src/client/util/SharingManager.tsx | 1 + src/client/views/DashboardView.tsx | 6 +-- src/client/views/DocComponent.tsx | 20 ++++------ src/client/views/DocumentDecorations.tsx | 3 +- src/client/views/InkingStroke.tsx | 10 ++--- src/client/views/Main.tsx | 5 ++- src/client/views/PropertiesView.tsx | 9 ++--- .../views/collections/CollectionDockingView.tsx | 14 ++++--- src/client/views/collections/TreeView.tsx | 5 +-- .../collectionSchema/CollectionSchemaView.tsx | 8 ++-- .../collectionSchema/SchemaTableCell.tsx | 9 ++--- src/client/views/nodes/ComparisonBox.tsx | 7 ++-- src/client/views/nodes/DocumentView.tsx | 26 ++++++------ src/client/views/nodes/FieldView.tsx | 2 +- src/client/views/nodes/KeyValueBox.tsx | 46 +++++++++++----------- src/client/views/nodes/KeyValuePair.tsx | 3 +- src/client/views/nodes/PDFBox.tsx | 6 +-- src/client/views/nodes/WebBox.tsx | 6 +-- .../views/nodes/formattedText/FormattedTextBox.tsx | 6 +-- .../views/nodes/formattedText/RichTextRules.ts | 14 +++---- src/fields/Doc.ts | 18 +++++++-- 22 files changed, 122 insertions(+), 118 deletions(-) (limited to 'src') diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index cf7a61d24..ac865382d 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -26,7 +26,10 @@ import { SerializationHelper } from './util/SerializationHelper'; */ export namespace DocServer { // eslint-disable-next-line import/no-mutable-exports - export let _cache: { [id: string]: RefField | Promise> } = {}; + let _cache: { [id: string]: RefField | Promise> } = {}; + export function Cache() { + return _cache; + } function errorFunc(): never { throw new Error("Can't use DocServer without calling init first"); @@ -54,15 +57,6 @@ export namespace DocServer { } } - export function FindDocByTitle(title: string) { - const foundDocId = - title && - Array.from(Object.keys(_cache)) - .filter(key => _cache[key] instanceof Doc) - .find(key => (_cache[key] as Doc).title === title); - - return foundDocId ? (_cache[foundDocId] as Doc) : undefined; - } let _socket: Socket; // this client's distinct GUID created at initialization let USER_ID: string; @@ -228,7 +222,7 @@ export namespace DocServer { const deserializeField = getSerializedField.then(async fieldJson => { // deserialize const field = await SerializationHelper.Deserialize(fieldJson); - if (force && field instanceof Doc && cached instanceof Doc) { + if (force && field && cached instanceof Doc) { cached[UpdatingFromServer] = true; Array.from(Object.keys(field)).forEach(key => { const fieldval = field[key]; diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index 31a99896f..c2a52cae9 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -94,6 +94,7 @@ export class SharingManager extends React.Component<{}> { super(props); makeObservable(this); SharingManager.Instance = this; + DocumentView.ShareOpen = this.open; } /** diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx index d92a73b63..fc6a330f7 100644 --- a/src/client/views/DashboardView.tsx +++ b/src/client/views/DashboardView.tsx @@ -22,7 +22,6 @@ import { Docs, DocumentOptions } from '../documents/Documents'; import { dropActionType } from '../util/DropActionTypes'; import { HistoryUtil } from '../util/History'; import { ScriptingGlobals } from '../util/ScriptingGlobals'; -import { SharingManager } from '../util/SharingManager'; import { SnappingManager } from '../util/SnappingManager'; import { undoBatch, undoable } from '../util/UndoManager'; import { ContextMenu } from './ContextMenu'; @@ -30,6 +29,7 @@ import './DashboardView.scss'; import { MainViewModal } from './MainViewModal'; import { ObservableReactComponent } from './ObservableReactComponent'; import { Colors } from './global/globalEnums'; +import { DocumentView } from './nodes/DocumentView'; import { ButtonType } from './nodes/FontIconBox/FontIconBox'; enum DashboardGroup { @@ -170,7 +170,7 @@ export class DashboardView extends ObservableReactComponent<{}> { ContextMenu.Instance.addItem({ description: `Share Dashboard`, - event: () => SharingManager.Instance.open(undefined, dashboard), + event: () => DocumentView.ShareOpen(undefined, dashboard), icon: 'edit', }); ContextMenu.Instance.addItem({ @@ -535,7 +535,7 @@ ScriptingGlobals.add(function createNewDashboard() { }, 'creates a new dashboard when called'); // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function shareDashboard(dashboard: Doc) { - SharingManager.Instance.open(undefined, dashboard); + DocumentView.ShareOpen(undefined, dashboard); }, 'opens sharing dialog for Dashboard'); // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function removeDashboard(dashboard: Doc) { diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 97ff346e4..70b20aec7 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -23,9 +23,7 @@ import { OpenWhere } from './nodes/OpenWhere'; * Many of these methods only make sense for specific viewBox'es, but they should be written to * be as general as possible */ -export interface ViewBoxInterface { - fieldKey?: string; - annotationKey?: string; +export class ViewBoxInterface

extends ObservableReactComponent> { promoteCollection?: () => void; // moves contents of collection to parent updateIcon?: () => void; // updates the icon representation of the document getAnchor?: (addAsAnnotation: boolean, pinData?: PinProps) => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box) @@ -61,7 +59,7 @@ export interface ViewBoxInterface { snapPt?: (pt: { X: number; Y: number }, excludeSegs?: number[]) => { nearestPt: { X: number; Y: number }; distance: number }; search?: (str: string, bwd?: boolean, clear?: boolean) => boolean; dontRegisterView?: () => boolean; // KeyValueBox's don't want to register their views - isUnstyledView?: () => boolean; // SchemaView and KeyValue are untyled -- not tiles, no opacity + isUnstyledView?: () => boolean; // SchemaView and KeyValue are unstyled -- not titles, no opacity, no animations } /** * DocComponent returns a React base class used by Doc views with accessors for unpacking the Document,layoutDoc, and dataDoc's @@ -113,7 +111,7 @@ export function DocComponent

() { * Example views include: InkingStroke, FontIconBox, EquationBox, etc */ export function ViewBoxBaseComponent

() { - class Component extends ObservableReactComponent> { + class Component extends ViewBoxInterface

{ constructor(props: P) { super(props); makeObservable(this); @@ -168,7 +166,7 @@ export function ViewBoxBaseComponent

() { * Example views include: PDFBox, ImageBox, MapBox, etc */ export function ViewBoxAnnotatableComponent

() { - class Component extends ObservableReactComponent> { + class Component extends ViewBoxInterface

{ @observable _annotationKeySuffix = () => 'annotations'; @observable _isAnyChildContentActive = false; @@ -219,8 +217,7 @@ export function ViewBoxAnnotatableComponent

() { return this.fieldKey + (this._annotationKeySuffix() ? '_' + this._annotationKeySuffix() : ''); } - @action.bound - removeDocument(docIn: Doc | Doc[], annotationKey?: string, leavePushpin?: boolean, dontAddToRemoved?: boolean): boolean { + override removeDocument = (docIn: Doc | Doc[], annotationKey?: string, leavePushpin?: boolean, dontAddToRemoved?: boolean): boolean => { const effectiveAcl = GetEffectiveAcl(this.dataDoc); const docs = toList(docIn).filter(fdoc => [AclEdit, AclAdmin].includes(effectiveAcl) || GetEffectiveAcl(fdoc) === AclAdmin); @@ -249,12 +246,11 @@ export function ViewBoxAnnotatableComponent

() { } return false; - } + }; // this is called with the document that was dragged and the collection to move it into. // if the target collection is the same as this collection, then the move will be allowed. // otherwise, the document being moved must be able to be removed from its container before // moving it into the target. - @action.bound moveDocument = (docs: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean, annotationKey?: string): boolean => { if (Doc.AreProtosEqual(this._props.Document, targetCollection)) { return true; @@ -265,8 +261,8 @@ export function ViewBoxAnnotatableComponent

() { } return false; }; - @action.bound - addDocument = (docIn: Doc | Doc[], annotationKey?: string): boolean => { + + override addDocument = (docIn: Doc | Doc[], annotationKey?: string): boolean => { const docs = toList(docIn); if (this._props.filterAddDocument?.(docs) === false || docs.find(fdoc => Doc.AreProtosEqual(fdoc, this.Document) && Doc.LayoutField(fdoc) === Doc.LayoutField(this.Document))) { return false; diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 432b02782..4262c2d57 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -33,7 +33,6 @@ import { Colors } from './global/globalEnums'; import { CollectionFreeFormDocumentView } from './nodes/CollectionFreeFormDocumentView'; import { DocumentView } from './nodes/DocumentView'; import { ImageBox } from './nodes/ImageBox'; -import { KeyValueBox } from './nodes/KeyValueBox'; import { OpenWhereMod } from './nodes/OpenWhere'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; @@ -137,7 +136,7 @@ export class DocumentDecorations extends ObservableReactComponent() implements ViewBoxInterface { +export class InkingStroke extends ViewBoxAnnotatableComponent() { static readonly MaskDim = INK_MASK_SIZE; // choose a really big number to make sure mask fits over container (which in theory can be arbitrarily big) public static LayoutString(fieldStr: string) { return FieldView.LayoutString(InkingStroke, fieldStr); @@ -337,9 +337,9 @@ export class InkingStroke extends ViewBoxAnnotatableComponent() ); }; - _subContentView: ViewBoxInterface | undefined; - setSubContentView = (doc: ViewBoxInterface) => { - this._subContentView = doc; + _subContentView: ViewBoxInterface | undefined; + setSubContentView = (box: ViewBoxInterface) => { + this._subContentView = box; }; @computed get fillColor(): string { const isInkMask = BoolCast(this.layoutDoc.stroke_isInkMask); diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index b6dfed687..259ffbbc5 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -7,9 +7,11 @@ import * as dotenv from 'dotenv'; // see https://github.com/motdotla/dotenv#how- import * as React from 'react'; import * as ReactDOM from 'react-dom/client'; import { AssignAllExtensions } from '../../extensions/Extensions'; +import { Doc } from '../../fields/Doc'; import { FieldLoader } from '../../fields/FieldLoader'; import { BranchingTrailManager } from '../util/BranchingTrailManager'; import { CurrentUserUtils } from '../util/CurrentUserUtils'; +import { LinkFollower } from '../util/LinkFollower'; import { PingManager } from '../util/PingManager'; import { ReplayMovements } from '../util/ReplayMovements'; import { TrackMovements } from '../util/TrackMovements'; @@ -18,7 +20,7 @@ import { MainView } from './MainView'; import { CollectionView } from './collections/CollectionView'; import { CollectionFreeFormInfoUI } from './collections/collectionFreeForm/CollectionFreeFormInfoUI'; import './global/globalScripts'; -import { LinkFollower } from '../util/LinkFollower'; +import { KeyValueBox } from './nodes/KeyValueBox'; dotenv.config(); @@ -66,6 +68,7 @@ FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0, message: 'cache' }; // iniitialize plugin apis CollectionFreeFormInfoUI.Init(); LinkFollower.Init(); + KeyValueBox.Init(); root.render(); }, 0); })(); diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 7c16e0ddb..b03c1a64e 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -41,7 +41,6 @@ import './PropertiesView.scss'; import { DefaultStyleProvider, SetFilterOpener as SetPropertiesFilterOpener } from './StyleProvider'; import { DocumentView } from './nodes/DocumentView'; import { StyleProviderFuncType } from './nodes/FieldView'; -import { KeyValueBox } from './nodes/KeyValueBox'; import { OpenWhere } from './nodes/OpenWhere'; import { PresBox, PresEffect, PresEffectDirection } from './nodes/trails'; @@ -208,7 +207,7 @@ export class PropertiesView extends ObservableReactComponent editableContents} SetValue={(value: string) => { - value !== '-multiple-' && docs.map(doc => KeyValueBox.SetField(doc, key, value, true)); + value !== '-multiple-' && docs.map(doc => Doc.SetField(doc, key, value, true)); return true; }} /> @@ -252,10 +251,10 @@ export class PropertiesView extends ObservableReactComponent Doc.SetInPlace(dv.Document, 'title', value, true)); } else if (this.dataDoc) { if (this.selectedDoc) Doc.SetInPlace(this.selectedDoc, 'title', value, true); - else KeyValueBox.SetField(this.dataDoc, 'title', value as string, true); + else Doc.SetField(this.dataDoc, 'title', value as string, true); } }; diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index fc9e2e39b..0ee3575f3 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -2,7 +2,7 @@ import { action, IReactionDisposer, makeObservable, observable, reaction } from import { observer } from 'mobx-react'; import * as React from 'react'; import * as ReactDOM from 'react-dom/client'; -import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, DivHeight, DivWidth, incrementTitleCopy, UpdateIcon } from '../../../ClientUtils'; +import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, DivHeight, DivWidth, incrementTitleCopy, returnTrue, UpdateIcon } from '../../../ClientUtils'; import { Doc, DocListCast, Field, Opt } from '../../../fields/Doc'; import { AclAdmin, AclEdit, DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; @@ -49,7 +49,8 @@ export class CollectionDockingView extends CollectionSubView() { private _reactionDisposer?: IReactionDisposer; private _lightboxReactionDisposer?: IReactionDisposer; private _containerRef = React.createRef(); - public _flush: UndoManager.Batch | undefined; + private _flush: UndoManager.Batch | undefined; + private _unmounting = false; private _ignoreStateChange = ''; public tabMap: Set = new Set(); public get HasFullScreen() { @@ -330,6 +331,7 @@ export class CollectionDockingView extends CollectionSubView() { }; componentDidMount: () => void = async () => { + this._props.setContentViewBox?.(this); this._unmounting = false; SetPropSetterCb('title', this.titleChanged); // this overrides any previously assigned callback for the property if (this._containerRef.current) { @@ -366,7 +368,6 @@ export class CollectionDockingView extends CollectionSubView() { } }; - _unmounting = false; componentWillUnmount: () => void = () => { this._unmounting = true; try { @@ -383,6 +384,9 @@ export class CollectionDockingView extends CollectionSubView() { this._lightboxReactionDisposer?.(); }; + // ViewBoxInterface overrides + override isUnstyledView = returnTrue; + @action onResize = () => { const cur = this._containerRef.current; @@ -437,8 +441,8 @@ export class CollectionDockingView extends CollectionSubView() { } else { const tabTarget = (e.target as HTMLElement)?.parentElement?.className.includes('lm_tab') ? (e.target as HTMLElement).parentElement : (e.target as HTMLElement); const map = Array.from(this.tabMap).find(tab => tab.element[0] === tabTarget); - if (map?.DashDoc && DocumentView.getFirstDocumentView(map.DashDoc)) { - DocumentView.SelectView(DocumentView.getFirstDocumentView(map.DashDoc), false); + if (map?.DashDoc && DocumentView.getDocumentView(map.DashDoc, this.DocumentView?.())) { + DocumentView.SelectView(DocumentView.getDocumentView(map.DashDoc, this.DocumentView?.()), false); } } } diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 969b98d91..6ea6bbfbd 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -32,7 +32,6 @@ import { ObservableReactComponent } from '../ObservableReactComponent'; import { StyleProp } from '../StyleProp'; import { DocumentView, DocumentViewInternal } from '../nodes/DocumentView'; import { FieldViewProps, StyleProviderFuncType } from '../nodes/FieldView'; -import { KeyValueBox } from '../nodes/KeyValueBox'; import { OpenWhere } from '../nodes/OpenWhere'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; @@ -568,7 +567,7 @@ export class TreeView extends ObservableReactComponent { height={13} fontSize={12} GetValue={() => Field.toKeyValueString(doc, key)} - SetValue={(value: string) => KeyValueBox.SetField(doc, key, value, true)} + SetValue={(value: string) => Doc.SetField(doc, key, value, true)} /> ); } @@ -600,7 +599,7 @@ export class TreeView extends ObservableReactComponent { const key = match[1]; const assign = match[2]; const val = match[3]; - KeyValueBox.SetField(doc, key, assign + val, false); + Doc.SetField(doc, key, assign + val, false); return true; } return false; diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index b30954ffd..77247e675 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -28,7 +28,6 @@ import { Colors } from '../../global/globalEnums'; import { DocumentView } from '../../nodes/DocumentView'; import { FieldViewProps } from '../../nodes/FieldView'; import { FocusViewOptions } from '../../nodes/FocusViewOptions'; -import { KeyValueBox } from '../../nodes/KeyValueBox'; import { CollectionSubView } from '../CollectionSubView'; import './CollectionSchemaView.scss'; import { SchemaColumnHeader } from './SchemaColumnHeader'; @@ -174,7 +173,9 @@ export class CollectionSchemaView extends CollectionSubView() { document.removeEventListener('keydown', this.onKeyDown); } - isUnstyledView = returnTrue; // used by style provide via ViewBoxInterface + // ViewBoxInterface overrides + override isUnstyledView = returnTrue; // used by style provider : turns off opacity, animation effects, scaling + rowIndex = (doc: Doc) => this.sortedDocs.docs.indexOf(doc); @action @@ -606,7 +607,7 @@ export class CollectionSchemaView extends CollectionSubView() { }; setColumnValues = (key: string, value: string) => { - this.childDocs.forEach(doc => KeyValueBox.SetField(doc, key, value)); + this.childDocs.forEach(doc => Doc.SetField(doc, key, value)); return true; }; @@ -804,7 +805,6 @@ export class CollectionSchemaView extends CollectionSubView() { ); } get renderKeysMenu() { - console.log('RNDERMENUT:' + this._columnMenuIndex); return (

e.stopPropagation()} /> diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx index ee6987e89..3df7ecdbe 100644 --- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx +++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx @@ -29,7 +29,6 @@ import { DefaultStyleProvider } from '../../StyleProvider'; import { Colors } from '../../global/globalEnums'; import { DocFocusOrOpen, returnEmptyDocViewList } from '../../nodes/DocumentView'; import { FieldViewProps } from '../../nodes/FieldView'; -import { KeyValueBox } from '../../nodes/KeyValueBox'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; import { FInfotoColType } from './CollectionSchemaView'; import './CollectionSchemaView.scss'; @@ -144,7 +143,7 @@ export class SchemaTableCell extends ObservableReactComponent | undefined) => { if ((value?.nativeEvent as any).shiftKey) { this._props.setColumnValues(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() ?? '')); + } else Doc.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + (value?.target?.checked.toString() ?? '')); })} /> KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), `"${val?.value ?? ''}"`)} + onChange={val => Doc.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), `"${val?.value ?? ''}"`)} />
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index 6ae6bb228..474d54119 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -19,11 +19,10 @@ import { StyleProp } from '../StyleProp'; import './ComparisonBox.scss'; import { DocumentView } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; -import { KeyValueBox } from './KeyValueBox'; import { FormattedTextBox } from './formattedText/FormattedTextBox'; @observer -export class ComparisonBox extends ViewBoxAnnotatableComponent() implements ViewBoxInterface { +export class ComparisonBox extends ViewBoxAnnotatableComponent() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ComparisonBox, fieldKey); } @@ -188,9 +187,9 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() // where (this) is replaced by the text in the fieldKey slot abd this.excludeWords is repalced by the conetnts of the excludeWords field // The GPT call will put the "answer" in the second slot of the comparison (eg., text_2) if (whichSlot.endsWith('2') && !layoutTemplateString?.includes(whichSlot)) { - const queryText = altText?.replace('(this)', subjectText); // TODO: this should be done in KeyValueBox.setField but it doesn't know about the fieldKey ... + const queryText = altText?.replace('(this)', subjectText); // TODO: this should be done in Doc.setField but it doesn't know about the fieldKey ... if (queryText?.match(/\(\(.*\)\)/)) { - KeyValueBox.SetField(this.Document, whichSlot, ':=' + queryText, false); // make the second slot be a computed field on the data doc that calls ChatGpt + Doc.SetField(this.Document, whichSlot, ':=' + queryText, false); // make the second slot be a computed field on the data doc that calls ChatGpt } } return layoutTemplateString; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index f8ce50c98..4a5c32b4c 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -31,7 +31,6 @@ import { MakeTemplate, makeUserTemplateButton } from '../../util/DropConverter'; import { UPDATE_SERVER_CACHE } from '../../util/LinkManager'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SearchUtil } from '../../util/SearchUtil'; -import { SharingManager } from '../../util/SharingManager'; import { SnappingManager } from '../../util/SnappingManager'; import { UndoManager, undoBatch, undoable } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; @@ -47,7 +46,6 @@ import { DocumentLinksButton } from './DocumentLinksButton'; import './DocumentView.scss'; import { FieldViewProps, FieldViewSharedProps } from './FieldView'; import { FocusViewOptions } from './FocusViewOptions'; -import { KeyValueBox } from './KeyValueBox'; import { OpenWhere } from './OpenWhere'; import { FormattedTextBox } from './formattedText/FormattedTextBox'; import { PresEffect, PresEffectDirection } from './trails'; @@ -115,7 +113,7 @@ export class DocumentViewInternal extends DocComponent = undefined; // needs to be accessed from DocumentView wrapper class + @observable _componentView: Opt> = undefined; // needs to be accessed from DocumentView wrapper class @observable _animateScaleTime: Opt = undefined; // milliseconds for animating between views. defaults to 300 if not uset @observable _animateScalingTo = 0; @@ -627,7 +625,7 @@ export class DocumentViewInternal extends DocComponent DocUtils.Zip(this.Document) }); - (this.Document._type_collection !== CollectionViewType.Docking || !Doc.noviceMode) && constantItems.push({ description: 'Share', event: () => SharingManager.Instance.open(this._docView), icon: 'users' }); + constantItems.push({ description: 'Share', event: () => DocumentView.ShareOpen(this._docView), icon: 'users' }); if (this._props.removeDocument && Doc.ActiveDashboard !== this.Document) { // need option to gray out menu items ... preferably with a '?' that explains why they're grayed out (eg., no permissions) constantItems.push({ description: 'Close', event: this.deleteClicked, icon: 'times' }); @@ -699,7 +697,7 @@ export class DocumentViewInternal extends DocComponent this._props.ScreenToLocalTransform().translate(0, -this.headerMargin); onClickFunc = this.disableClickScriptFunc ? undefined : () => this.onClickHandler; setHeight = (height: number) => { !this._props.suppressSetHeight && (this.layoutDoc._height = Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), height)); } // prettier-ignore - setContentView = action((view: ViewBoxInterface) => { this._componentView = view; }); // prettier-ignore + setContentView = action((view: ViewBoxInterface) => { this._componentView = view; }); // prettier-ignore isContentActive = (): boolean | undefined => this._isContentActive; childFilters = () => [...this._props.childFilters(), ...StrListCast(this.layoutDoc.childFilters)]; @@ -725,7 +723,7 @@ export class DocumentViewInternal extends DocComponent u.user.email === this.dataDoc.author)?.sharingDoc.headingColor, StrCast(Doc.SharingDoc().headingColor, SnappingManager.userBackgroundColor)) + // StrCast(SharingManager.Instance.users.find(u => u.user.email === this.dataDoc.author)?.sharingDoc.headingColor, + StrCast(Doc.SharingDoc().headingColor, SnappingManager.userBackgroundColor) + // ) ); const dropdownWidth = this._titleRef.current?._editing || this._changingTitleField ? Math.max(10, (this._titleDropDownInnerWidth * this.titleHeight) / 30) : 0; const sidebarWidthPercent = +StrCast(this.layoutDoc.layout_sidebarWidthPercent).replace('%', ''); @@ -839,7 +839,7 @@ export class DocumentViewInternal extends DocComponent - {this._componentView instanceof KeyValueBox ? renderDoc : DocumentViewInternal.AnimationEffect(renderDoc, this.Document[Animation])} + {this._componentView?.isUnstyledView?.() ? renderDoc : DocumentViewInternal.AnimationEffect(renderDoc, this.Document[Animation])} {borderPath?.jsx} ); @@ -980,6 +980,8 @@ export class DocumentViewInternal extends DocComponent() { public static ROOT_DIV = 'documentView-effectsWrapper'; + // Sharing Manager + public static ShareOpen: (target?: DocumentView, targetDoc?: Doc) => void; // LinkFollower public static FollowLink: (linkDoc: Opt, sourceDoc: Doc, altKey: boolean) => boolean; // selection funcs @@ -1049,7 +1051,7 @@ export class DocumentView extends DocComponent() { @observable public static LongPress = false; @computed private get shouldNotScale() { - return (this.layout_fitWidth && !this.nativeWidth) || this._props.LayoutTemplateString?.includes(KeyValueBox.name) || [CollectionViewType.Docking].includes(this.Document._type_collection as any); + return (this.layout_fitWidth && !this.nativeWidth) || this.ComponentView?.isUnstyledView?.(); } @computed private get effectiveNativeWidth() { return this.shouldNotScale ? 0 : this.nativeWidth || NumCast(this.layoutDoc.width); @@ -1144,10 +1146,10 @@ export class DocumentView extends DocComponent() { } @computed get nativeWidth() { - return this._props.LayoutTemplateString?.includes(KeyValueBox.name) ? 0 : returnVal(this._props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this._props.TemplateDataDocument, !this.layout_fitWidth)); + return returnVal(this._props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this._props.TemplateDataDocument, !this.layout_fitWidth)); } @computed get nativeHeight() { - return this._props.LayoutTemplateString?.includes(KeyValueBox.name) ? 0 : returnVal(this._props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this._props.TemplateDataDocument, !this.layout_fitWidth)); + return returnVal(this._props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this._props.TemplateDataDocument, !this.layout_fitWidth)); } @computed public get centeringX() { return this._props.dontCenter?.includes('x') ? 0 : this.Xshift; } // prettier-ignore @computed public get centeringY() { return this._props.dontCenter?.includes('y') ? 0 : this.Yshift; } // prettier-ignore diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index c6c77d8d2..8a37000f7 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -45,7 +45,7 @@ export interface FieldViewSharedProps { containerViewPath?: () => DocumentView[]; fitContentsToBox?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _freeform_fitContentsToBox property on a Document isGroupActive?: () => string | undefined; // is this document part of a group that is active - setContentViewBox?: (view: ViewBoxInterface) => any; // called by rendered field's viewBox so that DocumentView can make direct calls to the viewBox + setContentViewBox?: (view: ViewBoxInterface) => any; // called by rendered field's viewBox so that DocumentView can make direct calls to the viewBox PanelWidth: () => number; PanelHeight: () => number; isDocumentActive?: () => boolean | undefined; // whether a document should handle pointer events diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index cd591fa42..66e210c03 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -13,10 +13,10 @@ import { DocumentType } from '../../documents/DocumentTypes'; import { Docs } from '../../documents/Documents'; import { SetupDrag } from '../../util/DragManager'; import { CompiledScript } from '../../util/Scripting'; -import { undoBatch } from '../../util/UndoManager'; +import { undoable } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; -import { ObservableReactComponent } from '../ObservableReactComponent'; +import { ViewBoxBaseComponent } from '../DocComponent'; import { DocumentIconContainer } from './DocumentIcon'; import { FieldView, FieldViewProps } from './FieldView'; import { ImageBox } from './ImageBox'; @@ -31,7 +31,7 @@ export type KVPScript = { onDelegate: boolean; }; @observer -export class KeyValueBox extends ObservableReactComponent { +export class KeyValueBox extends ViewBoxBaseComponent() { public static LayoutString() { return FieldView.LayoutString(KeyValueBox, 'data'); } @@ -48,19 +48,17 @@ export class KeyValueBox extends ObservableReactComponent { componentDidMount() { this._props.setContentViewBox?.(this); } - isKeyValueBox = returnTrue; - able = returnAlways; - layout_fitWidth = returnTrue; - onClickScriptDisable = returnAlways; + // ViewBoxInterface overrides + override isUnstyledView = returnTrue; // used by style provider via ViewBoxInterface - ignore opacity, anim effects, titles + override dontRegisterView = returnTrue; // don't want to follow links to this view + override onClickScriptDisable = returnAlways; @observable private rows: KeyValuePair[] = []; @observable _splitPercentage = 50; get fieldDocToLayout() { - return DocCast(this._props.Document); + return DocCast(this.Document); } - isUnstyledView = returnTrue; // used by style provider via ViewBoxInterface - dontRegisterView = returnTrue; // used by ViewBoxInterface @action onEnterKey = (e: React.KeyboardEvent): void => { @@ -83,7 +81,7 @@ export class KeyValueBox extends ObservableReactComponent { * @param value * @returns */ - public static CompileKVPScript(rawvalueIn: string): KVPScript | undefined { + public static CompileKVPScript = (rawvalueIn: string): KVPScript | undefined => { let rawvalue = rawvalueIn; const onDelegate = rawvalue.startsWith('='); rawvalue = onDelegate ? rawvalue.substring(1) : rawvalue; @@ -97,9 +95,9 @@ export class KeyValueBox extends ObservableReactComponent { script = ScriptField.CompileScript(value, {}, true, undefined, DocumentIconContainer.getTransformer()); } return !script.compiled ? undefined : { script, type, onDelegate }; - } + }; - public static ApplyKVPScript(doc: Doc, key: string, kvpScript: KVPScript, forceOnDelegate?: boolean, setResult?: (value: FieldResult) => void) { + public static ApplyKVPScript = (doc: Doc, key: string, kvpScript: KVPScript, forceOnDelegate?: boolean, setResult?: (value: FieldResult) => void) => { const { script, type, onDelegate } = kvpScript; // const target = onDelegate ? Doc.Layout(doc.layout) : Doc.GetProto(doc); // bcz: TODO need to be able to set fields on layout templates const target = forceOnDelegate || onDelegate || key.startsWith('_') ? doc : DocCast(doc.proto, doc); @@ -127,14 +125,13 @@ export class KeyValueBox extends ObservableReactComponent { return true; } return false; - } + }; - @undoBatch - public static SetField(doc: Doc, key: string, value: string, forceOnDelegate?: boolean, setResult?: (value: FieldResult) => void) { - const script = this.CompileKVPScript(value); + public static SetField = undoable((doc: Doc, key: string, value: string, forceOnDelegate?: boolean, setResult?: (value: FieldResult) => void) => { + const script = KeyValueBox.CompileKVPScript(value); if (!script) return false; - return this.ApplyKVPScript(doc, key, script, forceOnDelegate, setResult); - } + return KeyValueBox.ApplyKVPScript(doc, key, script, forceOnDelegate, setResult); + }, 'Set Doc Field'); onPointerDown = (e: React.PointerEvent): void => { if (e.buttons === 1 && this._props.isSelected()) { @@ -249,15 +246,15 @@ export class KeyValueBox extends ObservableReactComponent { getFieldView = () => { const rows = this.rows.filter(row => row.isChecked); if (rows.length > 1) { - const parent = Docs.Create.StackingDocument([], { _layout_autoHeight: true, _width: 300, title: `field views for ${DocCast(this._props.Document).title}`, _chromeHidden: true }); + const parent = Docs.Create.StackingDocument([], { _layout_autoHeight: true, _width: 300, title: `field views for ${DocCast(this.Document).title}`, _chromeHidden: true }); rows.forEach(row => { - const field = this.createFieldView(DocCast(this._props.Document), row); + const field = this.createFieldView(DocCast(this.Document), row); field && Doc.AddDocToList(parent, 'data', field); row.uncheck(); }); return parent; } - return rows.length ? this.createFieldView(DocCast(this._props.Document), rows.lastElement()) : undefined; + return rows.length ? this.createFieldView(DocCast(this.Document), rows.lastElement()) : undefined; }; createFieldView = (templateDoc: Doc, row: KeyValuePair) => { @@ -305,7 +302,7 @@ export class KeyValueBox extends ObservableReactComponent { openItems.push({ description: 'Default Perspective', event: () => { - this._props.addDocTab(this._props.Document, OpenWhere.close); + this._props.addDocTab(this.Document, OpenWhere.close); this._props.addDocTab(this.fieldDocToLayout, OpenWhere.addRight); }, icon: 'image', @@ -341,6 +338,9 @@ export class KeyValueBox extends ObservableReactComponent { ); } + public static Init() { + Doc.SetField = KeyValueBox.SetField; + } } Docs.Prototypes.TemplateMap.set(DocumentType.KVP, { diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index 878b0e54c..397cc15ed 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -15,7 +15,6 @@ import { EditableView } from '../EditableView'; import { ObservableReactComponent } from '../ObservableReactComponent'; import { DefaultStyleProvider } from '../StyleProvider'; import { returnEmptyDocViewList } from './DocumentView'; -import { KeyValueBox } from './KeyValueBox'; import './KeyValueBox.scss'; import './KeyValuePair.scss'; import { OpenWhere } from './OpenWhere'; @@ -136,7 +135,7 @@ export class KeyValuePair extends ObservableReactComponent { pinToPres: returnZero, }} GetValue={() => Field.toKeyValueString(this._props.doc, this._props.keyName)} - SetValue={(value: string) => KeyValueBox.SetField(this._props.doc, this._props.keyName, value)} + SetValue={(value: string) => Doc.SetField(this._props.doc, this._props.keyName, value)} /> diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 9038ed27a..b8b9f63a9 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -25,7 +25,7 @@ import { CollectionFreeFormView } from '../collections/collectionFreeForm'; import { CollectionStackingView } from '../collections/CollectionStackingView'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; -import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent'; +import { ViewBoxAnnotatableComponent } from '../DocComponent'; import { Colors } from '../global/globalEnums'; import { PDFViewer } from '../pdf/PDFViewer'; import { PinDocView, PinProps } from '../PinFuncs'; @@ -39,7 +39,7 @@ import './PDFBox.scss'; import { CreateImage } from './WebBoxRenderer'; @observer -export class PDFBox extends ViewBoxAnnotatableComponent() implements ViewBoxInterface { +export class PDFBox extends ViewBoxAnnotatableComponent() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PDFBox, fieldKey); } @@ -283,7 +283,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent() implem !this.Document._layout_fitWidth && (this.Document._height = NumCast(this.Document._width) * (nh / nw)); }; - public search = action((searchString: string, bwd?: boolean, clear: boolean = false) => { + override search = action((searchString: string, bwd?: boolean, clear: boolean = false) => { if (!this._searching && !clear) { this._searching = true; setTimeout(() => { diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 1003bc473..198652310 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -48,7 +48,7 @@ import './WebBox.scss'; const { CreateImage } = require('./WebBoxRenderer'); @observer -export class WebBox extends ViewBoxAnnotatableComponent() implements ViewBoxInterface { +export class WebBox extends ViewBoxAnnotatableComponent() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(WebBox, fieldKey); } @@ -112,7 +112,7 @@ export class WebBox extends ViewBoxAnnotatableComponent() implem } @action - search = (searchString: string, bwd?: boolean, clear: boolean = false) => { + override search = (searchString: string, bwd?: boolean, clear: boolean = false) => { if (!this._searching && !clear) { this._searching = true; setTimeout(() => { @@ -1000,7 +1000,7 @@ export class WebBox extends ViewBoxAnnotatableComponent() implem }; _innerCollectionView: CollectionFreeFormView | undefined; zoomScaling = () => this._innerCollectionView?.zoomScaling() ?? 1; - setInnerContent = (component: ViewBoxInterface) => { + setInnerContent = (component: ViewBoxInterface) => { this._innerCollectionView = component as CollectionFreeFormView; }; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 8a4a7718a..321fdbb91 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -43,7 +43,7 @@ import { CollectionStackingView } from '../../collections/CollectionStackingView import { CollectionTreeView } from '../../collections/CollectionTreeView'; import { ContextMenu } from '../../ContextMenu'; import { ContextMenuProps } from '../../ContextMenuItem'; -import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent'; +import { ViewBoxAnnotatableComponent } from '../../DocComponent'; import { Colors } from '../../global/globalEnums'; import { LightboxView } from '../../LightboxView'; import { AnchorMenu } from '../../pdf/AnchorMenu'; @@ -73,12 +73,12 @@ import { schema } from './schema_rts'; import { SummaryView } from './SummaryView'; // import * as applyDevTools from 'prosemirror-dev-tools'; -interface FormattedTextBoxProps extends FieldViewProps { +export interface FormattedTextBoxProps extends FieldViewProps { onBlur?: () => void; // callback when text loses focus autoFocus?: boolean; // whether text should get input focus when created } @observer -export class FormattedTextBox extends ViewBoxAnnotatableComponent() implements ViewBoxInterface { +export class FormattedTextBox extends ViewBoxAnnotatableComponent() { public static LayoutString(fieldStr: string) { return FieldView.LayoutString(FormattedTextBox, fieldStr); } diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index 1ff862859..bf11dfe62 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -7,13 +7,11 @@ import { Id } from '../../../../fields/FieldSymbols'; import { List } from '../../../../fields/List'; import { NumCast, StrCast } from '../../../../fields/Types'; import { Utils } from '../../../../Utils'; -import { DocServer } from '../../../DocServer'; import { Docs } from '../../../documents/Documents'; -import { DocUtils } from '../../../documents/DocUtils'; import { CollectionViewType } from '../../../documents/DocumentTypes'; +import { DocUtils } from '../../../documents/DocUtils'; import { CollectionView } from '../../collections/CollectionView'; import { ContextMenu } from '../../ContextMenu'; -import { KeyValueBox } from '../KeyValueBox'; import { FormattedTextBox } from './FormattedTextBox'; import { wrappingInputRule } from './prosemirrorPatches'; import { RichTextMenu } from './RichTextMenu'; @@ -311,10 +309,10 @@ export class RichTextRules { } }; const getTitledDoc = (title: string) => { - if (!DocServer.FindDocByTitle(title)) { + if (!Doc.FindDocByTitle(title)) { Docs.Create.TextDocument('', { title: title, _width: 400, _layout_fitWidth: true, _layout_autoHeight: true }); } - const titledDoc = DocServer.FindDocByTitle(title); + const titledDoc = Doc.FindDocByTitle(title); return titledDoc ? Doc.BestEmbedding(titledDoc) : titledDoc; }; const target = getTitledDoc(docTitle); @@ -337,14 +335,14 @@ export class RichTextRules { const assign = match[4] === ':' ? (match[4] = '') : match[4]; const value = match[5]; const dataDoc = value === undefined ? !fieldKey.startsWith('_') : !assign?.startsWith('='); - const getTitledDoc = (title: string) => DocServer.FindDocByTitle(title); + const getTitledDoc = (title: string) => Doc.FindDocByTitle(title); // if the value has commas assume its an array (unless it's part of a chat gpt call indicated by '((' ) if (value?.includes(',') && !value.startsWith('((')) { const values = value.split(','); const strs = values.some(v => !v.match(/^[-]?[0-9.]$/)); this.Document[DocData][fieldKey] = strs ? new List(values) : new List(values.map(v => Number(v))); } else if (value) { - KeyValueBox.SetField( + Doc.SetField( this.Document, fieldKey, assign + value, @@ -367,7 +365,7 @@ export class RichTextRules { // pass the contents between '((' and '))' to chatGPT and append the result new InputRule(/(^|[^=])(\(\(.*\)\))$/, (state, match, start, end) => { let count = 0; // ignore first return value which will be the notation that chat is pending a result - KeyValueBox.SetField(this.Document, '', match[2], false, (gptval: FieldResult) => { + Doc.SetField(this.Document, '', match[2], false, (gptval: FieldResult) => { if (count) { const tr = this.TextBox.EditorView?.state.tr.insertText(' ' + (gptval as string)); tr && this.TextBox.EditorView?.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(end + 2), tr.doc.resolve(end + 2 + (gptval as string).length)))); diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index bb6995398..725221a66 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -225,7 +225,17 @@ export class Doc extends RefField { @observable public static GuestTarget: Doc | undefined = undefined; @observable public static GuestMobile: Doc | undefined = undefined; @observable.shallow public static CurrentlyLoading: Doc[] = observable([]); - // removes from currently loading display + // DocServer api + public static FindDocByTitle(title: string) { + const foundDocId = + title && + Array.from(Object.keys(DocServer.Cache())) + .filter(key => DocServer.Cache()[key] instanceof Doc) + .find(key => (DocServer.Cache()[key] as Doc).title === title); + + return foundDocId ? (DocServer.Cache()[foundDocId] as Doc) : undefined; + } + // removes from currently loading doc set public static removeCurrentlyLoading(doc: Doc) { if (Doc.CurrentlyLoading) { const index = Doc.CurrentlyLoading.indexOf(doc); @@ -238,12 +248,14 @@ export class Doc extends RefField { runInAction(() => Doc.CurrentlyLoading.push(doc)); } } - + // LinkManager api public static AddLink: (link: Doc, checkExists?: boolean) => void; public static DeleteLink: (link: Doc) => void; public static Links: (link: Doc | undefined) => Doc[]; public static getOppositeAnchor: (linkDoc: Doc, anchor: Doc) => Doc | undefined; - + // KeyValue SetField + public static SetField: (doc: Doc, key: string, value: string, forceOnDelegate?: boolean, setResult?: (value: FieldResult) => void) => boolean; + // UserDoc "API" public static get MySharedDocs() { return DocCast(Doc.UserDoc().mySharedDocs); } // prettier-ignore public static get MyUserDocView() { return DocCast(Doc.UserDoc().myUserDocView); } // prettier-ignore public static get MyDockedBtns() { return DocCast(Doc.UserDoc().myDockedBtns); } // prettier-ignore -- cgit v1.2.3-70-g09d2