diff options
Diffstat (limited to 'src')
21 files changed, 302 insertions, 259 deletions
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx index 249e2c3a3..831925054 100644 --- a/src/client/views/ContextMenu.tsx +++ b/src/client/views/ContextMenu.tsx @@ -204,7 +204,6 @@ export class ContextMenu extends React.Component { } render() { - console.log('DISPLAY = ' + this._display); return ( <div className="contextMenu-cont" diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx index 04fb1fe80..35c0d617b 100644 --- a/src/client/views/DashboardView.tsx +++ b/src/client/views/DashboardView.tsx @@ -1,6 +1,6 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Button, ColorPicker, EditableText, Size, Type } from 'browndash-components'; -import { action, computed, observable } from 'mobx'; +import { action, computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { FaPlus } from 'react-icons/fa'; @@ -39,6 +39,11 @@ enum DashboardGroup { export class DashboardView extends React.Component { public static _urlState: HistoryUtil.DocUrl; + constructor(props: any) { + super(props); + makeObservable(this); + } + @observable private openModal = false; @observable private selectedDashboardGroup = DashboardGroup.MyDashboards; @observable private newDashboardName = ''; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index dae2490c9..a8f4020ee 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -56,7 +56,7 @@ import { RichTextMenu } from './nodes/formattedText/RichTextMenu'; import GenerativeFill from './nodes/generativeFill/GenerativeFill'; import { ImageBox } from './nodes/ImageBox'; import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup'; -import { LinkDocPreview } from './nodes/LinkDocPreview'; +import { LinkDocPreview, LinkInfo } from './nodes/LinkDocPreview'; import { MapAnchorMenu } from './nodes/MapBox/MapAnchorMenu'; import { MapBox } from './nodes/MapBox/MapBox'; import { RadialMenu } from './nodes/RadialMenu'; @@ -968,9 +968,6 @@ export class MainView extends React.Component { ); } - @computed get linkDocPreview() { - return LinkDocPreview.LinkInfo ? <LinkDocPreview {...LinkDocPreview.LinkInfo} /> : null; - } @observable mapBoxHackBool = false; @computed get mapBoxHack() { return this.mapBoxHackBool ? null : ( @@ -1039,7 +1036,7 @@ export class MainView extends React.Component { {this._hideUI ? null : <TopBar />} {LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null} {DocButtonState.Instance.LinkEditorDocView ? <LinkMenu clearLinkEditor={action(() => (DocButtonState.Instance.LinkEditorDocView = undefined))} docView={DocButtonState.Instance.LinkEditorDocView} /> : null} - {this.linkDocPreview} + {LinkInfo.Instance?.LinkInfo ? <LinkDocPreview {...LinkInfo.Instance.LinkInfo} /> : null} {((page: string) => { // prettier-ignore diff --git a/src/client/views/PropertiesDocContextSelector.tsx b/src/client/views/PropertiesDocContextSelector.tsx index 196250167..5bde9d3c4 100644 --- a/src/client/views/PropertiesDocContextSelector.tsx +++ b/src/client/views/PropertiesDocContextSelector.tsx @@ -1,4 +1,4 @@ -import { computed } from 'mobx'; +import { computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, DocListCast } from '../../fields/Doc'; @@ -8,6 +8,7 @@ import { DocFocusOrOpen } from '../util/DocumentManager'; import { CollectionDockingView } from './collections/CollectionDockingView'; import { DocumentView, OpenWhere } from './nodes/DocumentView'; import './PropertiesDocContextSelector.scss'; +import { copyProps } from '../../Utils'; type PropertiesDocContextSelectorProps = { DocView?: DocumentView; @@ -18,10 +19,20 @@ type PropertiesDocContextSelectorProps = { @observer export class PropertiesDocContextSelector extends React.Component<PropertiesDocContextSelectorProps> { + _prevProps: PropertiesDocContextSelectorProps; + @observable _props: PropertiesDocContextSelectorProps; + constructor(props: PropertiesDocContextSelectorProps) { + super(props); + this._props = this._prevProps = props; + makeObservable(this); + } + componentDidUpdate() { + copyProps(this); + } @computed get _docs() { - if (!this.props.DocView) return []; - const target = this.props.DocView.props.Document; - const targetContext = this.props.DocView.props.docViewPath().lastElement()?.Document; + if (!this._props.DocView) return []; + const target = this._props.DocView._props.Document; + const targetContext = this._props.DocView._props.docViewPath().lastElement()?.Document; const embeddings = DocListCast(target.proto_embeddings); const containerProtos = embeddings.filter(embedding => embedding.embedContainer && embedding.embedContainer instanceof Doc).reduce((set, embedding) => set.add(Cast(embedding.embedContainer, Doc, null)), new Set<Doc>()); const containerSets = Array.from(containerProtos.keys()).map(container => DocListCast(container.proto_embeddings)); @@ -40,23 +51,23 @@ export class PropertiesDocContextSelector extends React.Component<PropertiesDocC ); return doclayouts - .filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance?.props.Document)) + .filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance?.Document)) .filter(doc => !Doc.IsSystem(doc)) .filter(doc => doc !== targetContext) .map(doc => ({ col: doc, target })); } getOnClick = (col: Doc, target: Doc) => { - if (!this.props.DocView) return; + if (!this._props.DocView) return; col = Doc.IsDataProto(col) ? Doc.MakeDelegate(col) : col; - DocFocusOrOpen(Doc.GetProto(this.props.DocView.props.Document), undefined, col); + DocFocusOrOpen(Doc.GetProto(this._props.DocView.Document), undefined, col); }; render() { if (this._docs.length < 1) return undefined; return ( <div> - {this.props.hideTitle ? null : <p key="contexts">Contexts:</p>} + {this._props.hideTitle ? null : <p key="contexts">Contexts:</p>} {this._docs.map(doc => ( <p key={doc.col[Id] + doc.target[Id]}> <a onClick={() => this.getOnClick(doc.col, doc.target)}>{StrCast(doc.col.title)}</a> diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx index 2a52d86c2..19473de2b 100644 --- a/src/client/views/SidebarAnnos.tsx +++ b/src/client/views/SidebarAnnos.tsx @@ -1,11 +1,11 @@ -import { computed } from 'mobx'; +import { computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import { Doc, DocListCast, Field, FieldResult, StrListCast } from '../../fields/Doc'; import { Id } from '../../fields/FieldSymbols'; import { List } from '../../fields/List'; import { RichTextField } from '../../fields/RichTextField'; import { DocCast, NumCast, StrCast } from '../../fields/Types'; -import { emptyFunction, returnAll, returnFalse, returnOne, returnTrue, returnZero } from '../../Utils'; +import { copyProps, emptyFunction, returnAll, returnFalse, returnOne, returnTrue, returnZero } from '../../Utils'; import { Docs, DocUtils } from '../documents/Documents'; import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; import { LinkManager } from '../util/LinkManager'; @@ -35,14 +35,21 @@ interface ExtraProps { } @observer export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> { - constructor(props: Readonly<FieldViewProps & ExtraProps>) { + _prevProps: FieldViewProps & ExtraProps; + @observable _props: FieldViewProps & ExtraProps; + constructor(props: FieldViewProps & ExtraProps) { super(props); - // this.props.dataDoc[this.sidebarKey] = new List<Doc>(); // bcz: can't do this here. it blows away existing things and isn't a robust solution for making sure the field exists -- instead this should happen when the document is created and/or shared + this._props = this._prevProps = props; + makeObservable(this); + // this._props.dataDoc[this.sidebarKey] = new List<Doc>(); // bcz: can't do this here. it blows away existing things and isn't a robust solution for making sure the field exists -- instead this should happen when the document is created and/or shared + } + componentDidUpdate() { + copyProps(this); } _stackRef = React.createRef<CollectionStackingView>(); @computed get allMetadata() { const keys = new Map<string, FieldResult<Field>>(); - DocListCast(this.props.Document[this.sidebarKey]).forEach(doc => + DocListCast(this._props.Document[this.sidebarKey]).forEach(doc => SearchUtil.documentKeys(doc) .filter(key => key[0] && key[0] !== '_' && key[0] === key[0].toUpperCase()) .map(key => keys.set(key, doc[key])) @@ -51,7 +58,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> { } @computed get allHashtags() { const keys = new Set<string>(); - DocListCast(this.props.Document[this.sidebarKey]).forEach(doc => StrListCast(doc.tags).forEach(tag => keys.add(tag))); + DocListCast(this._props.Document[this.sidebarKey]).forEach(doc => StrListCast(doc.tags).forEach(tag => keys.add(tag))); return Array.from(keys.keys()) .filter(key => key[0]) .filter(key => !key.startsWith('_') && (key[0] === '#' || key[0] === key[0].toUpperCase())) @@ -59,7 +66,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> { } @computed get allUsers() { const keys = new Set<string>(); - DocListCast(this.props.Document[this.sidebarKey]).forEach(doc => keys.add(StrCast(doc.author))); + DocListCast(this._props.Document[this.sidebarKey]).forEach(doc => keys.add(StrCast(doc.author))); return Array.from(keys.keys()).sort(); } @@ -69,7 +76,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> { .join(' '); const target = Docs.Create.TextDocument(startup, { title: '-note-', - annotationOn: this.props.Document, + annotationOn: this._props.Document, _width: 200, _height: 50, _layout_fitWidth: true, @@ -147,10 +154,10 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> { return target; }; makeDocUnfiltered = (doc: Doc) => { - if (DocListCast(this.props.Document[this.sidebarKey]).find(anno => Doc.AreProtosEqual(doc.layout_unrendered ? DocCast(doc.annotationOn) : doc, anno))) { + if (DocListCast(this._props.Document[this.sidebarKey]).find(anno => Doc.AreProtosEqual(doc.layout_unrendered ? DocCast(doc.annotationOn) : doc, anno))) { if (this.childFilters()) { // if any child filters exist, get rid of them - this.props.layoutDoc._childFilters = new List<string>(); + this._props.layoutDoc._childFilters = new List<string>(); } return true; } @@ -158,27 +165,27 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> { }; get sidebarKey() { - return this.props.fieldKey + '_sidebar'; + return this._props.fieldKey + '_sidebar'; } filtersHeight = () => 38; screenToLocalTransform = () => - this.props + this._props .ScreenToLocalTransform() - .translate(Doc.NativeWidth(this.props.dataDoc), 0) - .scale(this.props.NativeDimScaling?.() || 1); + .translate(Doc.NativeWidth(this._props.dataDoc), 0) + .scale(this._props.NativeDimScaling?.() || 1); panelWidth = () => - !this.props.showSidebar + !this._props.showSidebar ? 0 - : this.props.usePanelWidth // [DocumentType.RTF, DocumentType.MAP].includes(this.props.layoutDoc.type as any) - ? this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - : ((NumCast(this.props.nativeWidth) - Doc.NativeWidth(this.props.dataDoc)) * this.props.PanelWidth()) / NumCast(this.props.nativeWidth); - panelHeight = () => this.props.PanelHeight() - this.filtersHeight(); - addDocument = (doc: Doc | Doc[]) => this.props.sidebarAddDocument(doc, this.sidebarKey); - moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.props.moveDocument(doc, targetCollection, addDocument, this.sidebarKey); - removeDocument = (doc: Doc | Doc[]) => this.props.removeDocument(doc, this.sidebarKey); - childFilters = () => StrListCast(this.props.layoutDoc._childFilters); + : this._props.usePanelWidth // [DocumentType.RTF, DocumentType.MAP].includes(this._props.layoutDoc.type as any) + ? this._props.PanelWidth() / (this._props.NativeDimScaling?.() || 1) + : ((NumCast(this._props.nativeWidth) - Doc.NativeWidth(this._props.dataDoc)) * this._props.PanelWidth()) / NumCast(this._props.nativeWidth); + panelHeight = () => this._props.PanelHeight() - this.filtersHeight(); + addDocument = (doc: Doc | Doc[]) => this._props.sidebarAddDocument(doc, this.sidebarKey); + moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this._props.moveDocument(doc, targetCollection, addDocument, this.sidebarKey); + removeDocument = (doc: Doc | Doc[]) => this._props.removeDocument(doc, this.sidebarKey); + childFilters = () => StrListCast(this._props.layoutDoc._childFilters); layout_showTitle = () => 'title'; - setHeightCallback = (height: number) => this.props.setHeight?.(height + this.filtersHeight()); + setHeightCallback = (height: number) => this._props.setHeight?.(height + this.filtersHeight()); sortByLinkAnchorY = (a: Doc, b: Doc) => { const ay = LinkManager.Links(a).length && DocCast(LinkManager.Links(a)[0].link_anchor_1).y; const by = LinkManager.Links(b).length && DocCast(LinkManager.Links(b)[0].link_anchor_1).y; @@ -188,7 +195,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> { const renderTag = (tag: string) => { const active = this.childFilters().includes(`tags${Doc.FilterSep}${tag}${Doc.FilterSep}check`); return ( - <div key={tag} className={`sidebarAnnos-filterTag${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this.props.Document, 'tags', tag, 'check', true, undefined, e.shiftKey)}> + <div key={tag} className={`sidebarAnnos-filterTag${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this._props.Document, 'tags', tag, 'check', true, undefined, e.shiftKey)}> {tag} </div> ); @@ -196,7 +203,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> { const renderMeta = (tag: string, dflt: FieldResult<Field>) => { const active = this.childFilters().includes(`${tag}${Doc.FilterSep}${Doc.FilterAny}${Doc.FilterSep}exists`); return ( - <div key={tag} className={`sidebarAnnos-filterTag${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this.props.Document, tag, Doc.FilterAny, 'exists', true, undefined, e.shiftKey)}> + <div key={tag} className={`sidebarAnnos-filterTag${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this._props.Document, tag, Doc.FilterAny, 'exists', true, undefined, e.shiftKey)}> {tag} </div> ); @@ -204,20 +211,20 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> { const renderUsers = (user: string) => { const active = this.childFilters().includes(`author:${user}:check`); return ( - <div key={user} className={`sidebarAnnos-filterUser${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this.props.Document, 'author', user, 'check', true, undefined, e.shiftKey)}> + <div key={user} className={`sidebarAnnos-filterUser${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this._props.Document, 'author', user, 'check', true, undefined, e.shiftKey)}> {user} </div> ); }; // TODO: Calculation of the topbar is hardcoded and different for text nodes - it should all be the same and all be part of SidebarAnnos - return !this.props.showSidebar ? null : ( + return !this._props.showSidebar ? null : ( <div style={{ position: 'absolute', - pointerEvents: this.props.isContentActive() ? 'all' : undefined, - top: this.props.Document.type !== DocumentType.RTF && StrCast(this.props.Document._layout_showTitle) === 'title' ? 15 : 0, + pointerEvents: this._props.isContentActive() ? 'all' : undefined, + top: this._props.Document.type !== DocumentType.RTF && StrCast(this._props.Document._layout_showTitle) === 'title' ? 15 : 0, right: 0, - background: this.props.styleProvider?.(this.props.Document, this.props, StyleProp.WidgetColor), + background: this._props.styleProvider?.(this._props.Document, this._props, StyleProp.WidgetColor), width: `100%`, height: '100%', }}> @@ -231,7 +238,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> { <div style={{ width: '100%', height: `calc(100% - 38px)`, position: 'relative' }}> <CollectionStackingView - {...this.props} + {...this._props} setContentView={emptyFunction} NativeWidth={returnZero} NativeHeight={returnZero} @@ -246,14 +253,14 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> { NativeDimScaling={returnOne} //childlayout_showTitle={this.layout_showTitle} isAnyChildContentActive={returnFalse} - childDocumentsActive={this.props.isContentActive} - whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} + childDocumentsActive={this._props.isContentActive} + whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged} childHideDecorationTitle={true} removeDocument={this.removeDocument} moveDocument={this.moveDocument} addDocument={this.addDocument} ScreenToLocalTransform={this.screenToLocalTransform} - renderDepth={this.props.renderDepth + 1} + renderDepth={this._props.renderDepth + 1} type_collection={CollectionViewType.Stacking} fieldKey={this.sidebarKey} pointerEvents={returnAll} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 154c914ad..d16599bab 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1441,7 +1441,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return anchor; }; - infoUI = () => <CollectionFreeFormInfoUI Document={this.Document} Freeform={this} />; + infoUI = () => (this.Document.annotationOn ? null : <CollectionFreeFormInfoUI Document={this.Document} Freeform={this} />); componentDidMount() { this._props.setContentView?.(this); diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx index af0fe73c3..bcfe2c232 100644 --- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx +++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx @@ -121,7 +121,8 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> { <EditableView oneLine={this._props.oneLine} allowCRs={this._props.allowCRs} - contents={<FieldView {...fieldProps} />} + contents={undefined} + fieldContents={fieldProps} editing={this.selected ? undefined : false} GetValue={() => Field.toKeyValueString(this._props.Document, this._props.fieldKey)} SetValue={undoable((value: string, shiftDown?: boolean, enterKey?: boolean) => { @@ -333,7 +334,8 @@ export class SchemaBoolCell extends React.Component<SchemaTableCellProps> { })} /> <EditableView - contents={<FieldView {...fieldProps} />} + contents={undefined} + fieldContents={fieldProps} editing={this.selected ? undefined : false} GetValue={() => Field.toKeyValueString(this._props.Document, this._props.fieldKey)} SetValue={undoBatch((value: string, shiftDown?: boolean, enterKey?: boolean) => { diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx index 91d812f35..af22f41e1 100644 --- a/src/client/views/linking/LinkMenu.tsx +++ b/src/client/views/linking/LinkMenu.tsx @@ -3,7 +3,7 @@ import { observer } from 'mobx-react'; import { Doc } from '../../../fields/Doc'; import { LinkManager } from '../../util/LinkManager'; import { DocumentView } from '../nodes/DocumentView'; -import { LinkDocPreview } from '../nodes/LinkDocPreview'; +import { LinkInfo } from '../nodes/LinkDocPreview'; import './LinkMenu.scss'; import { LinkMenuGroup } from './LinkMenuGroup'; import * as React from 'react'; @@ -38,7 +38,7 @@ export class LinkMenu extends React.Component<Props> { } onPointerDown = action((e: PointerEvent) => { - LinkDocPreview.Clear(); + LinkInfo.Clear(); if (!this._linkMenuRef.current?.contains(e.target as any) && !this._editorRef.current?.contains(e.target as any)) { this.clear(); } diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index 858cc2e5e..d77525e04 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -15,7 +15,7 @@ import { SelectionManager } from '../../util/SelectionManager'; import { SettingsManager } from '../../util/SettingsManager'; import { undoBatch } from '../../util/UndoManager'; import { DocumentView, DocumentViewInternal, OpenWhere } from '../nodes/DocumentView'; -import { LinkDocPreview } from '../nodes/LinkDocPreview'; +import { LinkDocPreview, LinkInfo } from '../nodes/LinkDocPreview'; import './LinkMenuItem.scss'; import * as React from 'react'; @@ -180,11 +180,11 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> { </div> <div className="linkMenu-text" - onPointerLeave={LinkDocPreview.Clear} + onPointerLeave={LinkInfo.Clear} onPointerEnter={e => this._props.linkDoc && this._props.clearLinkEditor && - LinkDocPreview.SetLinkInfo({ + LinkInfo.SetLinkInfo({ docProps: this._props.docView._props, linkSrc: this._props.sourceDoc, linkDoc: this._props.linkDoc, diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index b3acb08e2..abe59feb5 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -136,7 +136,7 @@ export interface DocComponentView { componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element | null; dragStarting?: (snapToDraggedDoc: boolean, showGroupDragTarget: boolean, visited: Set<Doc>) => void; incrementalRendering?: () => void; - infoUI?: () => JSX.Element; + infoUI?: () => JSX.Element | null; getCenter?: (xf: Transform) => { X: number; Y: number }; screenBounds?: () => Opt<{ left: number; top: number; right: number; bottom: number; center?: { X: number; Y: number } }>; ptToScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number }; diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx index 1b5161e47..33c9b2e08 100644 --- a/src/client/views/nodes/LinkAnchorBox.tsx +++ b/src/client/views/nodes/LinkAnchorBox.tsx @@ -11,7 +11,7 @@ import { ViewBoxBaseComponent } from '../DocComponent'; import { StyleProp } from '../StyleProvider'; import { FieldView, FieldViewProps } from './FieldView'; import './LinkAnchorBox.scss'; -import { LinkDocPreview } from './LinkDocPreview'; +import { LinkDocPreview, LinkInfo } from './LinkDocPreview'; import * as React from 'react'; // import globalCssVariables = require('../global/globalCssVariables.scss'); const MEDIUM_GRAY = 'lightGray'; @@ -85,7 +85,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() { title={targetTitle} className={`linkAnchorBox-cont${small ? '-small' : ''}`} onPointerEnter={e => - LinkDocPreview.SetLinkInfo({ + LinkInfo.SetLinkInfo({ docProps: this.props, linkSrc: this.linkSource, linkDoc: this.Document, diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index dd102edef..108930ad9 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -1,11 +1,11 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@mui/material'; -import { action, computed, observable } from 'mobx'; +import { action, computed, makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import wiki from 'wikijs'; import { Doc, Opt } from '../../../fields/Doc'; import { Cast, DocCast, NumCast, PromiseValue, StrCast } from '../../../fields/Types'; -import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnNone, setupMoveUpEvents } from '../../../Utils'; +import { copyProps, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnNone, setupMoveUpEvents } from '../../../Utils'; import { DocServer } from '../../DocServer'; import { Docs } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; @@ -20,6 +20,25 @@ import { DocumentView, DocumentViewSharedProps, OpenWhere } from './DocumentView import './LinkDocPreview.scss'; import * as React from 'react'; +export class LinkInfo { + private static _instance: Opt<LinkInfo>; + constructor() { + LinkInfo._instance = this; + makeObservable(this); + } + @observable public LinkInfo: Opt<LinkDocPreviewProps> = undefined; + + public static get Instance() { + return LinkInfo._instance ?? new LinkInfo(); + } + public static Clear() { + runInAction(() => LinkInfo.Instance && (LinkInfo.Instance.LinkInfo = undefined)); + } + public static SetLinkInfo(info?: LinkDocPreviewProps) { + runInAction(() => LinkInfo.Instance && (LinkInfo.Instance.LinkInfo = info)); + } +} + interface LinkDocPreviewProps { linkDoc?: Doc; linkSrc?: Doc; @@ -31,35 +50,28 @@ interface LinkDocPreviewProps { } @observer export class LinkDocPreview extends React.Component<LinkDocPreviewProps> { - @action public static Clear() { - LinkDocPreview.LinkInfo = undefined; - } - @action public static SetLinkInfo(info?: LinkDocPreviewProps) { - LinkDocPreview.LinkInfo !== info && (LinkDocPreview.LinkInfo = info); + _prevProps: LinkDocPreviewProps; + @observable _props: LinkDocPreviewProps; + constructor(props: LinkDocPreviewProps) { + super(props); + this._props = this._prevProps = props; + makeObservable(this); } - static _instance: Opt<LinkDocPreview>; - _infoRef = React.createRef<HTMLDivElement>(); _linkDocRef = React.createRef<HTMLDivElement>(); - @observable public static LinkInfo: Opt<LinkDocPreviewProps>; - @observable _targetDoc: Opt<Doc>; - @observable _markerTargetDoc: Opt<Doc>; - @observable _linkDoc: Opt<Doc>; - @observable _linkSrc: Opt<Doc>; + @observable _targetDoc: Opt<Doc> = undefined; + @observable _markerTargetDoc: Opt<Doc> = undefined; + @observable _linkDoc: Opt<Doc> = undefined; + @observable _linkSrc: Opt<Doc> = undefined; @observable _toolTipText = ''; @observable _hrefInd = 0; - constructor(props: any) { - super(props); - LinkDocPreview._instance = this; - } - @action init() { - var linkTarget = this.props.linkDoc; - this._linkSrc = this.props.linkSrc; - this._linkDoc = this.props.linkDoc; + var linkTarget = this._props.linkDoc; + this._linkSrc = this._props.linkSrc; + this._linkDoc = this._props.linkDoc; const link_anchor_1 = DocCast(this._linkDoc?.link_anchor_1); const link_anchor_2 = DocCast(this._linkDoc?.link_anchor_2); if (link_anchor_1 && link_anchor_2) { @@ -73,27 +85,27 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> { this.updateHref(); } componentDidUpdate(props: any) { - if (props.linkSrc !== this.props.linkSrc || props.linkDoc !== this.props.linkDoc || props.hrefs !== this.props.hrefs) this.init(); + copyProps(this); + if (props.linkSrc !== this._props.linkSrc || props.linkDoc !== this._props.linkDoc || props.hrefs !== this._props.hrefs) this.init(); } componentDidMount() { this.init(); document.addEventListener('pointerdown', this.onPointerDown, true); } - @action componentWillUnmount() { - LinkDocPreview.SetLinkInfo(undefined); + LinkInfo.Clear(); document.removeEventListener('pointerdown', this.onPointerDown, true); } onPointerDown = (e: PointerEvent) => { - !this._linkDocRef.current?.contains(e.target as any) && LinkDocPreview.Clear(); // close preview when not clicking anywhere other than the info bar of the preview + !this._linkDocRef.current?.contains(e.target as any) && LinkInfo.Clear(); // close preview when not clicking anywhere other than the info bar of the preview }; @action updateHref() { - if (this.props.hrefs?.length) { - const href = this.props.hrefs[this._hrefInd]; + if (this._props.hrefs?.length) { + const href = this._props.hrefs[this._hrefInd]; if (href.indexOf(Doc.localServerPath()) !== 0) { // link to a web page URL -- try to show a preview if (href.startsWith('https://en.wikipedia.org/wiki/')) { @@ -123,7 +135,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> { this._markerTargetDoc = linkTarget; this._targetDoc = /*linkTarget?.type === DocumentType.MARKER &&*/ linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget; } - if (LinkDocPreview.LinkInfo?.noPreview || this._linkSrc?.followLinkToggle || this._markerTargetDoc?.type === DocumentType.PRES) this.followLink(); + if (LinkInfo.Instance?.LinkInfo?.noPreview || this._linkSrc?.followLinkToggle || this._markerTargetDoc?.type === DocumentType.PRES) this.followLink(); } }) ); @@ -143,7 +155,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> { action(() => { LinkManager.currentLink = this._linkDoc; LinkManager.currentLinkAnchor = this._linkSrc; - this.props.docProps.DocumentView?.().select(false); + this._props.docProps.DocumentView?.().select(false); if ((SettingsManager.Instance.propertiesWidth ?? 0) < 100) { SettingsManager.Instance.propertiesWidth = 250; } @@ -157,7 +169,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> { returnFalse, emptyFunction, action(() => { - const nextHrefInd = (this._hrefInd + 1) % (this.props.hrefs?.length || 1); + const nextHrefInd = (this._hrefInd + 1) % (this._props.hrefs?.length || 1); if (nextHrefInd !== this._hrefInd) { this._linkDoc = undefined; this._hrefInd = nextHrefInd; @@ -168,19 +180,19 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> { }; followLink = () => { - LinkDocPreview.Clear(); + LinkInfo.Clear(); if (this._linkDoc && this._linkSrc) { LinkFollower.FollowLink(this._linkDoc, this._linkSrc, false); - } else if (this.props.hrefs?.length) { + } else if (this._props.hrefs?.length) { const webDoc = - Array.from(SearchUtil.SearchCollection(Doc.MyFilesystem, this.props.hrefs[0]).keys()).lastElement() ?? - Docs.Create.WebDocument(this.props.hrefs[0], { title: this.props.hrefs[0], _nativeWidth: 850, _width: 200, _height: 400, data_useCors: true }); + Array.from(SearchUtil.SearchCollection(Doc.MyFilesystem, this._props.hrefs[0]).keys()).lastElement() ?? + Docs.Create.WebDocument(this._props.hrefs[0], { title: this._props.hrefs[0], _nativeWidth: 850, _width: 200, _height: 400, data_useCors: true }); DocumentManager.Instance.showDocument(webDoc, { openLocation: OpenWhere.lightbox, willPan: true, zoomTime: 500, }); - //this.props.docProps?.addDocTab(webDoc, OpenWhere.lightbox); + //this._props.docProps?.addDocTab(webDoc, OpenWhere.lightbox); } }; @@ -216,7 +228,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> { </div> <div className="linkDocPreview-buttonBar" style={{ float: 'right' }}> <Tooltip title={<div className="dash-tooltip">Next Link</div>} placement="top"> - <div className="linkDocPreview-button" style={{ background: (this.props.hrefs?.length || 0) <= 1 ? 'gray' : 'green' }} onPointerDown={this.nextHref}> + <div className="linkDocPreview-button" style={{ background: (this._props.hrefs?.length || 0) <= 1 ? 'gray' : 'green' }} onPointerDown={this.nextHref}> <FontAwesomeIcon className="linkDocPreview-fa-icon" icon="chevron-right" color="white" size="sm" /> </div> </Tooltip> @@ -228,7 +240,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> { @computed get docPreview() { return (!this._linkDoc || !this._targetDoc || !this._linkSrc) && !this._toolTipText ? null : ( <div className="linkDocPreview-inner"> - {!this.props.showHeader ? null : this.previewHeader} + {!this._props.showHeader ? null : this.previewHeader} <div className="linkDocPreview-preview-wrapper" onPointerDown={e => @@ -238,7 +250,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> { (e, down, delta) => { if (Math.abs(e.clientX - down[0]) + Math.abs(e.clientY - down[1]) > 100) { DragManager.StartDocumentDrag([this._infoRef.current!], new DragManager.DocumentDragData([this._targetDoc!]), e.pageX, e.pageY); - LinkDocPreview.Clear(); + LinkInfo.Clear(); return true; } return false; @@ -256,11 +268,11 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> { <DocumentView ref={r => { const targetanchor = this._linkDoc && this._linkSrc && LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc); - targetanchor && this._targetDoc !== targetanchor && r?.props.focus?.(targetanchor, {}); + targetanchor && this._targetDoc !== targetanchor && r?._props.focus?.(targetanchor, {}); }} Document={this._targetDoc!} moveDocument={returnFalse} - styleProvider={this.props.docProps?.styleProvider} + styleProvider={this._props.docProps?.styleProvider} docViewPath={returnEmptyDoclist} ScreenToLocalTransform={Transform.Identity} isDocumentActive={returnFalse} @@ -299,7 +311,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> { className="linkDocPreview" ref={this._linkDocRef} onPointerDown={this.followLinkPointerDown} - style={{ borderColor: SettingsManager.userColor, left: this.props.location[0], top: this.props.location[1], width: this.width() + borders, height: this.height() + borders + (this.props.showHeader ? 37 : 0) }}> + style={{ borderColor: SettingsManager.userColor, left: this._props.location[0], top: this._props.location[1], width: this.width() + borders, height: this.height() + borders + (this._props.showHeader ? 37 : 0) }}> {this.docPreview} </div> ); diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 2a884cef8..a453210eb 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -611,8 +611,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps static pdfpromise = new Map<string, Promise<Pdfjs.PDFDocumentProxy>>(); render() { TraceMobx(); - const pdfView = this.renderPdfView; - + const pdfView = !this._pdf ? null : this.renderPdfView; const href = this.pdfUrl?.url.href; if (!pdfView && href) { if (PDFBox.pdfcache.get(href)) setTimeout(action(() => (this._pdf = PDFBox.pdfcache.get(href)))); diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index c722399c1..d74af9b27 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -1,6 +1,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, trace } from 'mobx'; +import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, override, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; +import * as React from 'react'; import * as WebRequest from 'web-request'; import { Doc, DocListCast, Field, Opt } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; @@ -12,7 +13,7 @@ import { listSpec } from '../../../fields/Schema'; import { Cast, NumCast, StrCast, WebCast } from '../../../fields/Types'; import { ImageField, WebField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; -import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, getWordAtPoint, lightOrDark, returnFalse, returnOne, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils'; +import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, copyProps, emptyFunction, getWordAtPoint, lightOrDark, returnFalse, returnOne, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentManager } from '../../util/DocumentManager'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; @@ -33,10 +34,9 @@ import { SidebarAnnos } from '../SidebarAnnos'; import { StyleProp } from '../StyleProvider'; import { DocComponentView, DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; -import { LinkDocPreview } from './LinkDocPreview'; +import { LinkInfo } from './LinkDocPreview'; import { PinProps, PresBox } from './trails'; import './WebBox.scss'; -import * as React from 'react'; const { CreateImage } = require('./WebBoxRenderer'); const _global = (window /* browser */ || global) /* node */ as any; const htmlToText = require('html-to-text'); @@ -95,12 +95,20 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps return this.allAnnotations.filter(a => a.text_inlineAnnotations); } @computed get webField() { - return Cast(this.Document[this.props.fieldKey], WebField)?.url; + return Cast(this.Document[this._props.fieldKey], WebField)?.url; } - constructor(props: any) { + _prevProps: ViewBoxAnnotatableProps & FieldViewProps; + @override _props: ViewBoxAnnotatableProps & FieldViewProps; + constructor(props: ViewBoxAnnotatableProps & FieldViewProps) { super(props); - runInAction(() => (this._webUrl = this._url)); // setting the weburl will change the src parameter of the embedded iframe and force a navigation to it. + this._props = this._prevProps = props; + makeObservable(this); + this._webUrl = this._url; // setting the weburl will change the src parameter of the embedded iframe and force a navigation to it. + } + + componentDidUpdate() { + copyProps(this); } @action @@ -139,7 +147,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps if (!this._iframe) return; const scrollTop = NumCast(this.layoutDoc._layout_scrollTop); const nativeWidth = NumCast(this.layoutDoc.nativeWidth); - const nativeHeight = (nativeWidth * this.props.PanelHeight()) / this.props.PanelWidth(); + const nativeHeight = (nativeWidth * this._props.PanelHeight()) / this._props.PanelWidth(); var htmlString = this._iframe.contentDocument && new XMLSerializer().serializeToString(this._iframe.contentDocument); if (!htmlString) { htmlString = await (await fetch(Utils.CorsProxy(this.webField!.href))).text(); @@ -170,8 +178,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps }); }; - async componentDidMount() { - this.props.setContentView?.(this); // this tells the DocumentView that this WebBox is the "content" of the document. this allows the DocumentView to call WebBox relevant methods to configure the UI (eg, show back/forward buttons) + componentDidMount() { + this._props.setContentView?.(this); // this tells the DocumentView that this WebBox is the "content" of the document. this allows the DocumentView to call WebBox relevant methods to configure the UI (eg, show back/forward buttons) runInAction(() => { this._annotationKeySuffix = () => (this._urlHash ? this._urlHash + '_' : '') + 'annotations'; @@ -200,8 +208,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps () => this.layoutDoc._layout_autoHeight, layout_autoHeight => { if (layout_autoHeight) { - this.layoutDoc._nativeHeight = NumCast(this.props.Document[this.props.fieldKey + '_nativeHeight']); - this.props.setHeight?.(NumCast(this.props.Document[this.props.fieldKey + '_nativeHeight']) * (this.props.NativeDimScaling?.() || 1)); + this.layoutDoc._nativeHeight = NumCast(this.Document[this._props.fieldKey + '_nativeHeight']); + this._props.setHeight?.(NumCast(this.Document[this._props.fieldKey + '_nativeHeight']) * (this._props.NativeDimScaling?.() || 1)); } } ); @@ -218,10 +226,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps } } // else it's an HTMLfield } else if (this.webField && !this.dataDoc.text) { - const result = await WebRequest.get(Utils.CorsProxy(this.webField.href)); - if (result) { - this.dataDoc.text = htmlToText.fromString(result.content); - } + WebRequest.get(Utils.CorsProxy(this.webField.href)) // + .then(result => result && (this.dataDoc.text = htmlToText.convert(result.content))); } this._disposers.scrollReaction = reaction( @@ -236,7 +242,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps { fireImmediately: true } ); } - @action componentWillUnmount() { + componentWillUnmount() { this._iframetimeout && clearTimeout(this._iframetimeout); this._iframetimeout = undefined; Object.values(this._disposers).forEach(disposer => disposer?.()); @@ -251,7 +257,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps @action createTextAnnotation = (sel: Selection, selRange: Range | undefined) => { if (this._mainCont.current && selRange) { - if (this.dataDoc[this.props.fieldKey] instanceof HtmlField) this._mainCont.current.style.transform = `rotate(${NumCast(this.props.DocumentView!().screenToLocalTransform().RotateDeg)}deg)`; + if (this.dataDoc[this._props.fieldKey] instanceof HtmlField) this._mainCont.current.style.transform = `rotate(${NumCast(this._props.DocumentView!().screenToLocalTransform().RotateDeg)}deg)`; const clientRects = selRange.getClientRects(); for (let i = 0; i < clientRects.length; i++) { const rect = clientRects.item(i); @@ -259,7 +265,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps if (rect && rect.width !== this._mainCont.current.clientWidth) { const annoBox = document.createElement('div'); annoBox.className = 'marqueeAnnotator-annotationBox'; - const scale = this._url ? 1 : this.props.ScreenToLocalTransform().Scale; + const scale = this._url ? 1 : this._props.ScreenToLocalTransform().Scale; // transforms the positions from screen onto the pdf div annoBox.style.top = ((rect.top - mainrect.translateY) * scale + (this._url ? this._mainCont.current.scrollTop : NumCast(this.layoutDoc.layout_scrollTop))).toString(); annoBox.style.left = ((rect.left - mainrect.translateX) * scale).toString(); @@ -282,7 +288,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps focus = (anchor: Doc, options: DocFocusOptions) => { if (anchor !== this.Document && this._outerRef.current) { - const windowHeight = this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); + const windowHeight = this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1); const scrollTo = Utils.scrollIntoView(NumCast(anchor.y), NumCast(anchor._height), NumCast(this.layoutDoc._layout_scrollTop), windowHeight, windowHeight * 0.1, Math.max(NumCast(anchor.y) + NumCast(anchor._height), this._scrollHeight)); if (scrollTo !== undefined) { if (this._initialScroll === undefined) { @@ -298,8 +304,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps @action getView = (doc: Doc) => { - if (Doc.AreProtosEqual(doc, this.Document)) return new Promise<Opt<DocumentView>>(res => res(this.props.DocumentView?.())); - if (this.Document.layout_fieldKey === 'layout_icon') this.props.DocumentView?.().iconify(); + if (Doc.AreProtosEqual(doc, this.Document)) return new Promise<Opt<DocumentView>>(res => res(this._props.DocumentView?.())); + if (this.Document.layout_fieldKey === 'layout_icon') this._props.DocumentView?.().iconify(); const webUrl = WebCast(doc.config_data)?.url; if (this._url && webUrl && webUrl.href !== this._url) this.setData(webUrl.href); if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) this.toggleSidebar(false); @@ -307,11 +313,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps }; sidebarAddDocTab = (doc: Doc, where: OpenWhere) => { - if (DocListCast(this.props.Document[this.props.fieldKey + '_sidebar']).includes(doc) && !this.SidebarShown) { + if (DocListCast(this.Document[this._props.fieldKey + '_sidebar']).includes(doc) && !this.SidebarShown) { this.toggleSidebar(false); return true; } - return this.props.addDocTab(doc, where); + return this._props.addDocTab(doc, where); }; getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { let ele: Opt<HTMLDivElement> = undefined; @@ -344,10 +350,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps iframeUp = (e: PointerEvent) => { this._getAnchor = AnchorMenu.Instance?.GetAnchor; // need to save AnchorMenu's getAnchor since a subsequent selection on another doc will overwrite this value this._textAnnotationCreator = undefined; - this.props.docViewPath().lastElement()?.docView?.cleanupPointerEvents(); // pointerup events aren't generated on containing document view, so we have to invoke it here. + this._props.docViewPath().lastElement()?.docView?.cleanupPointerEvents(); // pointerup events aren't generated on containing document view, so we have to invoke it here. if (this._iframe?.contentWindow && this._iframe.contentDocument && !this._iframe.contentWindow.getSelection()?.isCollapsed) { const mainContBounds = Utils.GetScreenTransform(this._mainCont.current!); - const scale = (this.props.NativeDimScaling?.() || 1) * mainContBounds.scale; + const scale = (this._props.NativeDimScaling?.() || 1) * mainContBounds.scale; const sel = this._iframe.contentWindow.getSelection(); if (sel) { this._selectionText = sel.toString(); @@ -355,7 +361,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps this._textAnnotationCreator = () => this.createTextAnnotation(sel, !sel.isCollapsed ? sel.getRangeAt(0) : undefined); AnchorMenu.Instance.jumpTo(e.clientX * scale + mainContBounds.translateX, e.clientY * scale + mainContBounds.translateY - NumCast(this.layoutDoc._layout_scrollTop) * scale); // Changing which document to add the annotation to (the currently selected WebBox) - GPTPopup.Instance.setSidebarId(`${this.props.fieldKey}_${this._urlHash ? this._urlHash + '_' : ''}sidebar`); + GPTPopup.Instance.setSidebarId(`${this._props.fieldKey}_${this._urlHash ? this._urlHash + '_' : ''}sidebar`); GPTPopup.Instance.addDoc = this.sidebarAddDocument; } } @@ -393,23 +399,23 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps this._textAnnotationCreator = () => this.createTextAnnotation(sel, selRange); (!sel.isCollapsed || this.marqueeing) && AnchorMenu.Instance.jumpTo(e.clientX, e.clientY); // Changing which document to add the annotation to (the currently selected WebBox) - GPTPopup.Instance.setSidebarId(`${this.props.fieldKey}_${this._urlHash ? this._urlHash + '_' : ''}sidebar`); + GPTPopup.Instance.setSidebarId(`${this._props.fieldKey}_${this._urlHash ? this._urlHash + '_' : ''}sidebar`); GPTPopup.Instance.addDoc = this.sidebarAddDocument; } }; @action iframeDown = (e: PointerEvent) => { - this.props.select(false); + this._props.select(false); const locpt = { - x: (e.clientX / NumCast(this.Document.nativeWidth)) * this.props.PanelWidth(), - y: ((e.clientY - NumCast(this.layoutDoc.layout_scrollTop))/ NumCast(this.Document.nativeHeight)) * this.props.PanelHeight() }; // prettier-ignore - const scrclick = this.props.DocumentView?.()._props.ScreenToLocalTransform().inverse().transformPoint(locpt.x, locpt.y)!; - const scrcent = this.props + x: (e.clientX / NumCast(this.Document.nativeWidth)) * this._props.PanelWidth(), + y: ((e.clientY - NumCast(this.layoutDoc.layout_scrollTop))/ NumCast(this.Document.nativeHeight)) * this._props.PanelHeight() }; // prettier-ignore + const scrclick = this._props.DocumentView?.()._props.ScreenToLocalTransform().inverse().transformPoint(locpt.x, locpt.y)!; + const scrcent = this._props .DocumentView?.() - .props.ScreenToLocalTransform() + ._props.ScreenToLocalTransform() .inverse() .transformPoint(NumCast(this.Document.width) / 2, NumCast(this.Document.height) / 2)!; - const theclickoff = Utils.rotPt(scrclick[0] - scrcent[0], scrclick[1] - scrcent[1], -this.props.ScreenToLocalTransform().Rotate); + const theclickoff = Utils.rotPt(scrclick[0] - scrcent[0], scrclick[1] - scrcent[1], -this._props.ScreenToLocalTransform().Rotate); const theclick = [theclickoff.x + scrcent[0], theclickoff.y + scrcent[1]]; MarqueeAnnotator.clearAnnotations(this._savedAnnotations); const word = getWordAtPoint(e.target, e.clientX, e.clientY); @@ -489,7 +495,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps this._scrollHeight = Math.max(this._scrollHeight, iframeContent.body.scrollHeight || 0); if (this._scrollHeight) { this.Document.nativeHeight = Math.min(NumCast(this.Document.nativeHeight), this._scrollHeight); - this.layoutDoc.height = Math.min(NumCast(this.layoutDoc._height), (NumCast(this.layoutDoc._width) * NumCast(this.dataDoc.nativeHeight)) / NumCast(this.dataDoc.nativeWidth)); + this.layoutDoc.height = Math.min(NumCast(this.layoutDoc._height), (NumCast(this.layoutDoc._width) * NumCast(this.Document.nativeHeight)) / NumCast(this.Document.nativeWidth)); } }; const swidth = Math.max(NumCast(this.layoutDoc.nativeWidth), iframeContent.body.scrollWidth || 0); @@ -564,7 +570,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps clearStyleSheetRules(WebBox.webStyleSheet); this._scrollTimer = undefined; const newScrollTop = scrollTop > iframeHeight ? iframeHeight : scrollTop; - if (!LinkDocPreview.LinkInfo && this._outerRef.current && newScrollTop !== this.layoutDoc.thumbScrollTop && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath()))) { + if (!LinkInfo.Instance?.LinkInfo && this._outerRef.current && newScrollTop !== this.layoutDoc.thumbScrollTop && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this._props.docViewPath()))) { this.layoutDoc.thumb = undefined; this.layoutDoc.thumbScrollTop = undefined; this.layoutDoc.thumbNativeWidth = undefined; @@ -709,7 +715,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps funcs.push({ description: (!this.layoutDoc.layout_reflowHorizontal ? 'Force' : 'Prevent') + ' Reflow', event: () => { - const nw = !this.layoutDoc.layout_reflowHorizontal ? undefined : Doc.NativeWidth(this.layoutDoc) - this.sidebarWidth() / (this.props.NativeDimScaling?.() || 1); + const nw = !this.layoutDoc.layout_reflowHorizontal ? undefined : Doc.NativeWidth(this.layoutDoc) - this.sidebarWidth() / (this._props.NativeDimScaling?.() || 1); this.layoutDoc.layout_reflowHorizontal = !nw; if (nw) { Doc.SetInPlace(this.layoutDoc, this.fieldKey + '_nativeWidth', nw, true); @@ -734,7 +740,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps if (sel?.empty) sel.empty(); // Chrome else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox this.marqueeing = [e.clientX, e.clientY]; - if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) { + if (!e.altKey && e.button === 0 && this._props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) { setupMoveUpEvents( this, e, @@ -770,7 +776,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps setTimeout(() => { // if menu comes up right away, the down event can still be active causing a menu item to be selected this.specificContextMenu(undefined as any); - this.props.docViewPath().lastElement().docView?.onContextMenu(undefined, x, y); + this._props.docViewPath().lastElement().docView?.onContextMenu(undefined, x, y); }); } } @@ -779,7 +785,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps @observable lighttext = false; @computed get urlContent() { - if (this.props.ScreenToLocalTransform().Scale > 25) return <div></div>; + if (this._props.ScreenToLocalTransform().Scale > 25) return <div></div>; setTimeout( action(() => { if (this._initialScroll === undefined && !this._webPageHasBeenRendered) { @@ -788,7 +794,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps this._webPageHasBeenRendered = true; }) ); - const field = this.dataDoc[this.props.fieldKey]; + const field = this.dataDoc[this._props.fieldKey]; if (field instanceof HtmlField) { return ( <span @@ -845,14 +851,14 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps e, action((e, down, delta) => { this._draggingSidebar = true; - const localDelta = this.props + const localDelta = this._props .ScreenToLocalTransform() - .scale(this.props.NativeDimScaling?.() || 1) + .scale(this._props.NativeDimScaling?.() || 1) .transformDirection(delta[0], delta[1]); const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth']); const nativeHeight = NumCast(this.layoutDoc[this.fieldKey + '_nativeHeight']); const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth); - const ratio = (curNativeWidth + ((onButton ? 1 : -1) * localDelta[0]) / (this.props.NativeDimScaling?.() || 1)) / nativeWidth; + const ratio = (curNativeWidth + ((onButton ? 1 : -1) * localDelta[0]) / (this._props.NativeDimScaling?.() || 1)) / nativeWidth; if (ratio >= 1) { this.layoutDoc.nativeWidth = nativeWidth * ratio; this.layoutDoc.nativeHeight = nativeHeight * (1 + ratio); @@ -916,7 +922,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps }); @action onZoomWheel = (e: React.WheelEvent) => { - if (this.props.isContentActive(true)) { + if (this._props.isContentActive(true)) { e.stopPropagation(); } }; @@ -924,14 +930,14 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps if (!this.SidebarShown) return 0; if (this._previewWidth) return WebBox.sidebarResizerWidth + WebBox.openSidebarWidth; // return default sidebar if previewing (as in viewing a link target) const nativeDiff = NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc); - return WebBox.sidebarResizerWidth + nativeDiff * (this.props.NativeDimScaling?.() || 1); + return WebBox.sidebarResizerWidth + nativeDiff * (this._props.NativeDimScaling?.() || 1); }; _innerCollectionView: CollectionFreeFormView | undefined; zoomScaling = () => this._innerCollectionView?.zoomScaling() ?? 1; setInnerContent = (component: DocComponentView) => (this._innerCollectionView = component as CollectionFreeFormView); @computed get content() { - const interactive = this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && Doc.ActiveTool === InkTool.None; + const interactive = this._props.isContentActive() && this._props.pointerEvents?.() !== 'none' && Doc.ActiveTool === InkTool.None; return ( <div className={'webBox-cont' + (interactive ? '-interactive' : '')} @@ -959,7 +965,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps {this.inlineTextAnnotations .sort((a, b) => NumCast(a.y) - NumCast(b.y)) .map(anno => ( - <Annotation {...this.props} fieldKey={this.annotationKey} pointerEvents={this.pointerEvents} dataDoc={this.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} /> + <Annotation {...this._props} fieldKey={this.annotationKey} pointerEvents={this.pointerEvents} dataDoc={this.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} /> ))} </div> ); @@ -969,13 +975,13 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps } renderAnnotations = (childFilters: () => string[]) => ( <CollectionFreeFormView - {...this.props} + {...this._props} setContentView={this.setInnerContent} NativeWidth={returnZero} NativeHeight={returnZero} originTopLeft={false} isAnnotationOverlayScrollable={true} - renderDepth={this.props.renderDepth + 1} + renderDepth={this._props.renderDepth + 1} isAnnotationOverlay={true} fieldKey={this.annotationKey} setPreviewCursor={this.setPreviewCursor} @@ -1004,11 +1010,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps @computed get renderTransparentAnnotations() { return this.renderAnnotations(this.transparentFilter); } - childPointerEvents = () => (this.props.isContentActive() ? 'all' : undefined); + childPointerEvents = () => (this._props.isContentActive() ? 'all' : undefined); @computed get webpage() { const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1; - const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this.props.pointerEvents?.() as any); - const scale = previewScale * (this.props.NativeDimScaling?.() || 1); + const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this._props.pointerEvents?.() as any); + const scale = previewScale * (this._props.NativeDimScaling?.() || 1); return ( <div className="webBox-outerContent" @@ -1021,7 +1027,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps onWheel={this.onZoomWheel} onScroll={e => this.setDashScrollTop(this._outerRef.current?.scrollTop || 0)} onPointerDown={this.onMarqueeDown}> - <div className="webBox-innerContent" style={{ height: (this._webPageHasBeenRendered && this._scrollHeight > this.props.PanelHeight() && this._scrollHeight) || '100%', pointerEvents }}> + <div className="webBox-innerContent" style={{ height: (this._webPageHasBeenRendered && this._scrollHeight > this._props.PanelHeight() && this._scrollHeight) || '100%', pointerEvents }}> {this.content} <div style={{ display: SnappingManager.GetCanEmbed() ? 'none' : undefined, mixBlendMode: 'multiply' }}>{this.renderTransparentAnnotations}</div> {this.renderOpaqueAnnotations} @@ -1033,7 +1039,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps @computed get searchUI() { return ( - <div className="webBox-ui" onPointerDown={e => e.stopPropagation()} style={{ display: this.props.isContentActive() ? 'flex' : 'none' }}> + <div className="webBox-ui" onPointerDown={e => e.stopPropagation()} style={{ display: this._props.isContentActive() ? 'flex' : 'none' }}> <div className="webBox-overlayCont" onPointerDown={e => e.stopPropagation()} style={{ left: `${this._searching ? 0 : 100}%` }}> <button className="webBox-overlayButton" title={'search'} /> <input @@ -1067,27 +1073,27 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps } searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => (this._searchString = e.currentTarget.value); setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => (this._setPreviewCursor = func); - panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - this.sidebarWidth() + WebBox.sidebarResizerWidth; - panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); - scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop)); + panelWidth = () => this._props.PanelWidth() / (this._props.NativeDimScaling?.() || 1) - this.sidebarWidth() + WebBox.sidebarResizerWidth; + panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1); + scrollXf = () => this._props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop)); anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick; - transparentFilter = () => [...this.props.childFilters(), Utils.TransparentBackgroundFilter]; - opaqueFilter = () => [...this.props.childFilters(), Utils.noDragDocsFilter, ...(SnappingManager.GetCanEmbed() ? [] : [Utils.OpaqueBackgroundFilter])]; + transparentFilter = () => [...this._props.childFilters(), Utils.TransparentBackgroundFilter]; + opaqueFilter = () => [...this._props.childFilters(), Utils.noDragDocsFilter, ...(SnappingManager.GetCanEmbed() ? [] : [Utils.OpaqueBackgroundFilter])]; childStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => { if (doc instanceof Doc && property === StyleProp.PointerEvents) { if (this.inlineTextAnnotations.includes(doc)) return 'none'; } - return this.props.styleProvider?.(doc, props, property); + return this._props.styleProvider?.(doc, props, property); }; pointerEvents = () => - !this._draggingSidebar && this.props.isContentActive() && !MarqueeOptionsMenu.Instance?.isShown() + !this._draggingSidebar && this._props.isContentActive() && !MarqueeOptionsMenu.Instance?.isShown() ? 'all' // : 'none'; - annotationPointerEvents = () => (this.props.isContentActive() && (SnappingManager.GetIsDragging() || Doc.ActiveTool !== InkTool.None) ? 'all' : 'none'); + annotationPointerEvents = () => (this._props.isContentActive() && (SnappingManager.GetIsDragging() || Doc.ActiveTool !== InkTool.None) ? 'all' : 'none'); render() { const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1; - const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this.props.pointerEvents?.() as any); - const scale = previewScale * (this.props.NativeDimScaling?.() || 1); + const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this._props.pointerEvents?.() as any); + const scale = previewScale * (this._props.NativeDimScaling?.() || 1); return ( <div className="webBox" @@ -1096,7 +1102,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps pointerEvents: this.pointerEvents(), // position: SnappingManager.GetIsDragging() ? 'absolute' : undefined, }}> - <div className="webBox-background" style={{ backgroundColor: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor) }} /> + <div className="webBox-background" style={{ backgroundColor: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) }} /> <div className="webBox-container" style={{ @@ -1115,9 +1121,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps anchorMenuClick={this.anchorMenuClick} scrollTop={NumCast(this.layoutDoc.layout_scrollTop)} annotationLayerScrollTop={0} - scaling={this.props.NativeDimScaling} + scaling={this._props.NativeDimScaling} addDocument={this.addDocumentWrapper} - docView={this.props.DocumentView!} + docView={this._props.DocumentView!} finishMarquee={this.finishMarquee} savedAnnotations={this.savedAnnotationsCreator} selectionText={this.selectionText} @@ -1135,25 +1141,25 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps }} onPointerDown={e => this.sidebarBtnDown(e, false)} /> - <div style={{ position: 'absolute', height: '100%', right: 0, top: 0, width: `calc(100 * ${this.sidebarWidth() / this.props.PanelWidth()}%` }}> + <div style={{ position: 'absolute', height: '100%', right: 0, top: 0, width: `calc(100 * ${this.sidebarWidth() / this._props.PanelWidth()}%` }}> <SidebarAnnos ref={this._sidebarRef} - {...this.props} + {...this._props} whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} fieldKey={this.fieldKey + '_' + this._urlHash} Document={this.Document} layoutDoc={this.layoutDoc} dataDoc={this.dataDoc} setHeight={emptyFunction} - nativeWidth={this._previewNativeWidth ?? NumCast(this.layoutDoc._nativeWidth) - WebBox.sidebarResizerWidth / (this.props.NativeDimScaling?.() || 1)} + nativeWidth={this._previewNativeWidth ?? NumCast(this.layoutDoc._nativeWidth) - WebBox.sidebarResizerWidth / (this._props.NativeDimScaling?.() || 1)} showSidebar={this.SidebarShown} sidebarAddDocument={this.sidebarAddDocument} moveDocument={this.moveDocument} removeDocument={this.removeDocument} /> </div> - {!this.props.isContentActive() || SnappingManager.GetIsDragging() ? null : this.sidebarHandle} - {!this.props.isContentActive() || SnappingManager.GetIsDragging() ? null : this.searchUI} + {!this._props.isContentActive() || SnappingManager.GetIsDragging() ? null : this.sidebarHandle} + {!this._props.isContentActive() || SnappingManager.GetIsDragging() ? null : this.searchUI} </div> ); } diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index 19e14d5a7..e2cceb906 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -1,6 +1,6 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@mui/material'; -import { action, computed, IReactionDisposer, observable } from 'mobx'; +import { action, computed, IReactionDisposer, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as ReactDOM from 'react-dom/client'; import { Doc } from '../../../../fields/Doc'; @@ -8,7 +8,7 @@ import { List } from '../../../../fields/List'; import { listSpec } from '../../../../fields/Schema'; import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField'; import { Cast, StrCast } from '../../../../fields/Types'; -import { emptyFunction, returnFalse, returnZero, setupMoveUpEvents } from '../../../../Utils'; +import { copyProps, emptyFunction, returnFalse, returnZero, setupMoveUpEvents } from '../../../../Utils'; import { DocServer } from '../../../DocServer'; import { CollectionViewType } from '../../../documents/DocumentTypes'; import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu'; @@ -70,10 +70,10 @@ export class DashFieldView { } catch {} }); } - @action deselectNode() { + deselectNode() { this.dom.classList.remove('ProseMirror-selectednode'); } - @action selectNode() { + selectNode() { this.dom.classList.add('ProseMirror-selectednode'); } } @@ -100,17 +100,24 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna @observable _dashDoc: Doc | undefined = undefined; @observable _expanded = false; + _prevProps: IDashFieldViewInternal; + @observable _props: IDashFieldViewInternal; constructor(props: IDashFieldViewInternal) { super(props); - this._fieldKey = this.props.fieldKey; - this._textBoxDoc = this.props.tbox.Document; + this._props = this._prevProps = props; + makeObservable(this); + this._fieldKey = this._props.fieldKey; + this._textBoxDoc = this._props.tbox.Document; - if (this.props.docId) { - DocServer.GetRefField(this.props.docId).then(action(dashDoc => dashDoc instanceof Doc && (this._dashDoc = dashDoc))); + if (this._props.docId) { + DocServer.GetRefField(this._props.docId).then(action(dashDoc => dashDoc instanceof Doc && (this._dashDoc = dashDoc))); } else { - this._dashDoc = this.props.tbox.Document; + this._dashDoc = this._props.tbox.Document; } } + componentDidUpdate(prevProps: Readonly<IDashFieldViewInternal>, prevState: Readonly<{}>, snapshot?: any): void { + copyProps(this); + } componentWillUnmount() { this._reactionDisposer?.(); } @@ -119,18 +126,18 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna // set the display of the field's value (checkbox for booleans, span of text for strings) @computed get fieldValueContent() { return !this._dashDoc ? null : ( - <div onClick={action(e => (this._expanded = !this.props.editable ? !this._expanded : true))} style={{ fontSize: 'smaller', width: this.props.hideKey ? this.props.tbox.props.PanelWidth() - 20 : undefined }}> + <div onClick={action(e => (this._expanded = !this._props.editable ? !this._expanded : true))} style={{ fontSize: 'smaller', width: this._props.hideKey ? this._props.tbox._props.PanelWidth() - 20 : undefined }}> <SchemaTableCell Document={this._dashDoc} col={0} deselectCell={emptyFunction} selectCell={emptyFunction} - maxWidth={this.props.hideKey ? undefined : this.return100} - columnWidth={this.props.hideKey ? () => this.props.tbox._props.PanelWidth() - 20 : returnZero} + maxWidth={this._props.hideKey ? undefined : this.return100} + columnWidth={this._props.hideKey ? () => this._props.tbox._props.PanelWidth() - 20 : returnZero} selectedCell={() => [this._dashDoc!, 0]} fieldKey={this._fieldKey} rowHeight={returnZero} - isRowActive={() => this._expanded && this.props.editable} + isRowActive={() => this._expanded && this._props.editable} padding={0} getFinfo={emptyFunction} setColumnValues={returnFalse} @@ -145,7 +152,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna } createPivotForField = (e: React.MouseEvent) => { - let container = this.props.tbox._props.DocumentView?.()._props.docViewPath().lastElement(); + let container = this._props.tbox._props.DocumentView?.()._props.docViewPath().lastElement(); if (container) { const embedding = Doc.MakeEmbedding(container.Document); embedding._type_collection = CollectionViewType.Time; @@ -157,7 +164,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna list.map(c => c.heading).indexOf(this._fieldKey) === -1 && list.push(new SchemaHeaderField(this._fieldKey, '#f1efeb')); list.map(c => c.heading).indexOf('text') === -1 && list.push(new SchemaHeaderField('text', '#f1efeb')); embedding._pivotField = this._fieldKey.startsWith('#') ? 'tags' : this._fieldKey; - this.props.tbox._props.addDocTab(embedding, OpenWhere.addRight); + this._props.tbox._props.addDocTab(embedding, OpenWhere.addRight); } }; @@ -175,17 +182,17 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna <div className="dashFieldView" style={{ - width: this.props.width, - height: this.props.height, - pointerEvents: this.props.tbox._props.isSelected() || this.props.tbox.isAnyChildContentActive?.() ? undefined : 'none', + width: this._props.width, + height: this._props.height, + pointerEvents: this._props.tbox._props.isSelected() || this._props.tbox.isAnyChildContentActive?.() ? undefined : 'none', }}> - {this.props.hideKey ? null : ( + {this._props.hideKey ? null : ( <span className="dashFieldView-labelSpan" title="click to see related tags" onPointerDown={this.onPointerDownLabelSpan}> {this._fieldKey} </span> )} - {this.props.fieldKey.startsWith('#') ? null : this.fieldValueContent} + {this._props.fieldKey.startsWith('#') ? null : this.fieldValueContent} </div> ); } @@ -198,7 +205,7 @@ export class DashFieldViewMenu extends AntimodeMenu<AntimodeMenuProps> { super(props); DashFieldViewMenu.Instance = this; } - @action + showFields = (e: React.MouseEvent) => { DashFieldViewMenu.createFieldView(e); DashFieldViewMenu.Instance.fadeOut(true); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 244de7849..6b1df4560 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -55,7 +55,7 @@ import { StyleProp } from '../../StyleProvider'; import { media_state } from '../AudioBox'; import { DocFocusOptions, DocumentView, DocumentViewInternal, OpenWhere } from '../DocumentView'; import { FieldView, FieldViewProps } from '../FieldView'; -import { LinkDocPreview } from '../LinkDocPreview'; +import { LinkDocPreview, LinkInfo } from '../LinkDocPreview'; import { PinProps, PresBox } from '../trails'; import { DashDocCommentView } from './DashDocCommentView'; import { DashDocView } from './DashDocView'; @@ -1855,7 +1855,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps e.stopPropagation(); // drag n drop of text within text note will generate a new note if not caughst, as will dragging in from outside of Dash. }; onScroll = (e: React.UIEvent) => { - if (!LinkDocPreview.LinkInfo && this._scrollRef.current) { + if (!LinkInfo.Instance?.LinkInfo && this._scrollRef.current) { if (!this._props.dontSelectOnLoad) { this._ignoreScroll = true; this.layoutDoc._layout_scrollTop = this._scrollRef.current.scrollTop; diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx index 4212d46eb..be8736525 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx @@ -3,7 +3,7 @@ import { EditorState, NodeSelection } from 'prosemirror-state'; import { EditorView } from 'prosemirror-view'; import { Doc } from '../../../../fields/Doc'; import { DocServer } from '../../../DocServer'; -import { LinkDocPreview } from '../LinkDocPreview'; +import { LinkDocPreview, LinkInfo } from '../LinkDocPreview'; import { FormattedTextBox } from './FormattedTextBox'; import './FormattedTextBoxComment.scss'; import { schema } from './schema_rts'; @@ -133,7 +133,7 @@ export class FormattedTextBoxComment { const naft = findEndOfMark(state.selection.$from, view, findLinkMark) || nbef; //nbef && naft && - LinkDocPreview.SetLinkInfo({ + LinkInfo.SetLinkInfo({ docProps: textBox.props, linkSrc: textBox.Document, linkDoc: linkDoc ? (DocServer.GetCachedRefField(linkDoc) as Doc) : undefined, diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 62df77c0a..db815eae3 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -1,6 +1,5 @@ import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; -import * as Pdfjs from 'pdfjs-dist'; import 'pdfjs-dist/web/pdf_viewer.css'; import { Doc, DocListCast, Opt } from '../../../fields/Doc'; import { Height } from '../../../fields/DocSymbols'; @@ -17,20 +16,20 @@ import { CollectionFreeFormView } from '../collections/collectionFreeForm/Collec import { MarqueeAnnotator } from '../MarqueeAnnotator'; import { DocFocusOptions, DocumentViewProps } from '../nodes/DocumentView'; import { FieldViewProps } from '../nodes/FieldView'; -import { LinkDocPreview } from '../nodes/LinkDocPreview'; +import { LinkDocPreview, LinkInfo } from '../nodes/LinkDocPreview'; import { StyleProp } from '../StyleProvider'; import { AnchorMenu } from './AnchorMenu'; import { Annotation } from './Annotation'; import { GPTPopup } from './GPTPopup/GPTPopup'; import './PDFViewer.scss'; import * as React from 'react'; -const PDFJSViewer = require('pdfjs-dist/web/pdf_viewer'); -const pdfjsLib = require('pdfjs-dist'); +import * as Pdfjs from 'pdfjs-dist'; +import * as PDFJSViewer from 'pdfjs-dist/web/pdf_viewer.mjs'; const _global = (window /* browser */ || global) /* node */ as any; //pdfjsLib.GlobalWorkerOptions.workerSrc = `/assets/pdf.worker.js`; // The workerSrc property shall be specified. -pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://unpkg.com/pdfjs-dist@2.16.105/build/pdf.worker.js'; +Pdfjs.GlobalWorkerOptions.workerSrc = 'https://unpkg.com/pdfjs-dist@4.0.269/build/pdf.worker.mjs'; interface IViewerProps extends FieldViewProps { Document: Doc; @@ -52,11 +51,6 @@ interface IViewerProps extends FieldViewProps { @observer export class PDFViewer extends React.Component<IViewerProps> { static _annotationStyle: any = addStyleSheet(); - @observable private _pageSizes: { width: number; height: number }[] = []; - @observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>(); - @observable private _textSelecting = true; - @observable private _showWaiting = true; - @observable private Index: number = -1; _prevProps: IViewerProps; @observable _props: IViewerProps; @@ -65,6 +59,13 @@ export class PDFViewer extends React.Component<IViewerProps> { this._prevProps = this._props = props; makeObservable(this); } + + @observable _pageSizes: { width: number; height: number }[] = []; + @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>(); + @observable _textSelecting = true; + @observable _showWaiting = true; + @observable Index: number = -1; + componentDidUpdate() { copyProps(this); } @@ -101,7 +102,7 @@ export class PDFViewer extends React.Component<IViewerProps> { return this.allAnnotations.filter(a => a.text_inlineAnnotations); } - componentDidMount = async () => { + componentDidMount() { runInAction(() => (this._showWaiting = true)); this.setupPdfJsViewer(); this._mainCont.current?.addEventListener('scroll', e => ((e.target as any).scrollLeft = 0)); @@ -126,7 +127,7 @@ export class PDFViewer extends React.Component<IViewerProps> { page => page !== undefined && page !== this._pdfViewer?.currentPageNumber && this.gotoPage(page), { fireImmediately: true } ); - }; + } componentWillUnmount = () => { Object.values(this._disposers).forEach(disposer => disposer?.()); @@ -262,7 +263,7 @@ export class PDFViewer extends React.Component<IViewerProps> { } document.removeEventListener('copy', this.copy); document.addEventListener('copy', this.copy); - const eventBus = new PDFJSViewer.EventBus(true); + const eventBus = new PDFJSViewer.EventBus(); eventBus._on('pagesinit', this.pagesinit); eventBus._on( 'pagerendered', @@ -272,10 +273,9 @@ export class PDFViewer extends React.Component<IViewerProps> { const pdfFindController = new PDFJSViewer.PDFFindController({ linkService: pdfLinkService, eventBus }); this._pdfViewer = new PDFJSViewer.PDFViewer({ container: this._mainCont.current, - viewer: this._viewer.current, + viewer: this._viewer.current || undefined, linkService: pdfLinkService, findController: pdfFindController, - renderer: 'canvas', eventBus, }); pdfLinkService.setViewer(this._pdfViewer); @@ -313,7 +313,7 @@ export class PDFViewer extends React.Component<IViewerProps> { onScroll = (e: React.UIEvent<HTMLElement>) => { if (this._mainCont.current && !this._forcedScroll) { this._ignoreScroll = true; // the pdf scrolled, so we need to tell the Doc to scroll but we don't want the doc to then try to set the PDF scroll pos (which would interfere with the smooth scroll animation) - if (!LinkDocPreview.LinkInfo) { + if (!LinkInfo.Instance?.LinkInfo) { this._props.layoutDoc._layout_scrollTop = this._mainCont.current.scrollTop; } this._ignoreScroll = false; diff --git a/src/server/ApiManagers/PDFManager.ts b/src/server/ApiManagers/PDFManager.ts index e419d3ac4..d634a241d 100644 --- a/src/server/ApiManagers/PDFManager.ts +++ b/src/server/ApiManagers/PDFManager.ts @@ -1,31 +1,26 @@ -import ApiManager, { Registration } from "./ApiManager"; -import { Method } from "../RouteManager"; -import RouteSubscriber from "../RouteSubscriber"; -import { existsSync, createReadStream, createWriteStream } from "fs"; -import * as Pdfjs from 'pdfjs-dist/legacy/build/pdf'; -import { createCanvas } from "canvas"; -const imageSize = require("probe-image-size"); -import * as express from "express"; -import * as path from "path"; -import { Directory, serverPathToFile, clientPathToFile, pathToDirectory } from "./UploadManager"; -import { red } from "colors"; -import { resolve } from "path"; +import ApiManager, { Registration } from './ApiManager'; +import { Method } from '../RouteManager'; +import RouteSubscriber from '../RouteSubscriber'; +import { existsSync, createReadStream, createWriteStream } from 'fs'; +//import * as Pdfjs from 'pdfjs-dist'; +import { createCanvas } from 'canvas'; +const imageSize = require('probe-image-size'); +import * as express from 'express'; +import { Directory, serverPathToFile, clientPathToFile, pathToDirectory } from './UploadManager'; +import { red } from 'colors'; +import { resolve } from 'path'; export default class PDFManager extends ApiManager { - protected initialize(register: Registration): void { - register({ method: Method.POST, - subscription: new RouteSubscriber("thumbnail"), + subscription: new RouteSubscriber('thumbnail'), secureHandler: async ({ req, res }) => { const { coreFilename, pageNum, subtree } = req.body; return getOrCreateThumbnail(coreFilename, pageNum, res, subtree); - } + }, }); - } - } async function getOrCreateThumbnail(coreFilename: string, pageNum: number, res: express.Response, subtree?: string): Promise<void> { @@ -51,7 +46,7 @@ async function getOrCreateThumbnail(coreFilename: string, pageNum: number, res: } async function CreateThumbnail(coreFilename: string, pageNum: number, res: express.Response, subtree?: string) { - const part1 = subtree ?? ""; + const part1 = subtree ?? ''; const filename = `${part1}${coreFilename}.pdf`; const sourcePath = resolve(pathToDirectory(Directory.pdfs), filename); const documentProxy = await Pdfjs.getDocument(sourcePath).promise; @@ -62,7 +57,7 @@ async function CreateThumbnail(coreFilename: string, pageNum: number, res: expre const renderContext = { canvasContext: context, canvasFactory: factory, - viewport + viewport, }; await page.render(renderContext).promise; const pngStream = canvas.createPNGStream(); @@ -71,11 +66,11 @@ async function CreateThumbnail(coreFilename: string, pageNum: number, res: expre const out = createWriteStream(pngFile); pngStream.pipe(out); return new Promise<void>((resolve, reject) => { - out.on("finish", () => { + out.on('finish', () => { dispatchThumbnail(res, viewport, resolved); resolve(); }); - out.on("error", error => { + out.on('error', error => { console.log(red(`In PDF thumbnail creation, encountered the following error when piping ${pngFile}:`)); console.log(error); reject(); @@ -87,30 +82,29 @@ function dispatchThumbnail(res: express.Response, { width, height }: Pdfjs.PageV res.send({ path: clientPathToFile(Directory.pdf_thumbnails, thumbnailName), width, - height + height, }); } class NodeCanvasFactory { - create = (width: number, height: number) => { const canvas = createCanvas(width, height); const context = canvas.getContext('2d'); return { canvas, - context + context, }; - } + }; reset = (canvasAndContext: any, width: number, height: number) => { canvasAndContext.canvas.width = width; canvasAndContext.canvas.height = height; - } + }; destroy = (canvasAndContext: any) => { canvasAndContext.canvas.width = 0; canvasAndContext.canvas.height = 0; canvasAndContext.canvas = null; canvasAndContext.context = null; - } + }; } diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts index dd2a857d9..46f521bc9 100644 --- a/src/server/DashUploadUtils.ts +++ b/src/server/DashUploadUtils.ts @@ -607,19 +607,22 @@ export namespace DashUploadUtils { )); // prettier-ignore const fileIn = fs.readFileSync(sourcePath); - return autorotate.rotate(fileIn, { quality: 30 }).then(({ buffer }: { buffer: any }) => - Jimp.read(buffer) - .then(async img => { - await Promise.all( sizes.filter(({ width }) => width) .map(({ width, suffix }) => + let buffer: any; + try { + const { buffer2 } = await autorotate.rotate(fileIn, { quality: 30 }); + buffer = buffer2; + } catch (e) {} + return Jimp.read(buffer ?? fileIn) + .then(async img => { + await Promise.all( sizes.filter(({ width }) => width) .map(({ width, suffix }) => img = img.resize(width, Jimp.AUTO).write(outputPath(suffix)) )); // prettier-ignore - return writtenFiles; - }) - .catch(e => { - console.log('ERROR' + e); - return writtenFiles; - }) - ); + return writtenFiles; + }) + .catch(e => { + console.log('ERROR' + e); + return writtenFiles; + }); } /** diff --git a/src/typings/index.d.ts b/src/typings/index.d.ts index 284aae338..710b0211e 100644 --- a/src/typings/index.d.ts +++ b/src/typings/index.d.ts @@ -8,6 +8,7 @@ declare module 'bezier-curve'; declare module 'fit-curve'; declare module 'react-audio-waveform'; declare module 'iink-js'; +declare module 'pdfjs-dist/web/pdf_viewer'; declare module 'reveal'; declare module 'react-reveal'; |