diff options
20 files changed, 253 insertions, 164 deletions
diff --git a/src/ClientUtils.ts b/src/ClientUtils.ts index 57e0373c4..402f6613a 100644 --- a/src/ClientUtils.ts +++ b/src/ClientUtils.ts @@ -3,7 +3,7 @@ import * as React from 'react'; import { ColorResult } from 'react-color'; import * as rp from 'request-promise'; import { numberRange, decimalToHexString } from './Utils'; -import { DocumentType } from './client/documents/DocumentTypes'; +import { CollectionViewType, DocumentType } from './client/documents/DocumentTypes'; import { Colors } from './client/views/global/globalEnums'; export function DashColor(color: string) { @@ -100,11 +100,22 @@ export namespace ClientUtils { return Date.now() - downTime < ClientUtils.CLICK_TIME && Math.abs(x - downX) < ClientUtils.DRAG_THRESHOLD && Math.abs(y - downY) < ClientUtils.DRAG_THRESHOLD; } - export function cleanDocumentType(type: DocumentType) { + export function cleanDocumentTypeExt(type: DocumentType) { switch (type) { + case DocumentType.PDF: return 'PDF'; + case DocumentType.IMG: return 'Img'; + case DocumentType.AUDIO: return 'Aud'; + case DocumentType.COL: return 'Col'; + case DocumentType.RTF: return 'Rtf'; + default: return type.charAt(0).toUpperCase() + type.substring(1,3); + } // prettier-ignore + } + export function cleanDocumentType(type: DocumentType, colType: CollectionViewType) { + switch (type) { + case DocumentType.PDF: return 'PDF'; case DocumentType.IMG: return 'Image'; case DocumentType.AUDIO: return 'Audio'; - case DocumentType.COL: return 'Collection'; + case DocumentType.COL: return 'Collection:'+colType; case DocumentType.RTF: return 'Text'; default: return type.charAt(0).toUpperCase() + type.slice(1); } // prettier-ignore diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index cca92816f..ac3721436 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -310,8 +310,8 @@ export class DocumentManager { focused = focused || nextFocus !== undefined; // keep track of whether focusing on a view needed to actually change anything // eslint-disable-next-line no-await-in-loop const { childDocView, viewSpec } = await iterator(docView); - if (!childDocView) return { viewSpec: options.anchorDoc ?? viewSpec ?? docView.Document, docView, contextView, focused }; - contextView = options.anchorDoc?.layout_unrendered && !childDocView.Document.layout_unrendered ? childDocView : docView; + if (!childDocView) return { viewSpec: viewSpec ?? docView.Document, docView, contextView, focused }; + contextView = !childDocView.Document.layout_unrendered ? childDocView : docView; docView = childDocView; } return undefined; diff --git a/src/client/util/SerializationHelper.ts b/src/client/util/SerializationHelper.ts index fa1a911f7..d9d22437c 100644 --- a/src/client/util/SerializationHelper.ts +++ b/src/client/util/SerializationHelper.ts @@ -65,7 +65,7 @@ export namespace SerializationHelper { } } -export function Deserializable(className: string, afterDeserialize?: (obj: any) => void | Promise<any>, constructorArgs?: [string]): (constructor: { new (...args: any[]): any }) => void { +export function Deserializable(classNameForSerializer: string, afterDeserialize?: (obj: any) => void | Promise<any>, constructorArgs?: [string]): (constructor: { new (...args: any[]): any }) => void { function addToMap(className: string, Ctor: { new (...args: any[]): any }) { const schema = getDefaultModelSchema(Ctor) as any; if (schema.targetClass !== Ctor || constructorArgs) { @@ -78,7 +78,7 @@ export function Deserializable(className: string, afterDeserialize?: (obj: any) throw new Error(`Name ${className} has already been registered as deserializable`); } } - return (ctor: { new (...args: any[]): any }) => addToMap(className, ctor); + return (ctor: { new (...args: any[]): any }) => addToMap(classNameForSerializer, ctor); } export function autoObject(): PropSchema { diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index 0301632ec..c4c00e0c3 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -95,6 +95,7 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP presentation_zoomText: true, title: '>' + this.props.Document.title, }); + const textRegionAnnoProto = textRegionAnno[DocData]; let minX = Number.MAX_VALUE; let maxX = -Number.MAX_VALUE; let minY = Number.MAX_VALUE; @@ -107,7 +108,7 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP textRegion.y = parseInt(anno.style.top ?? '0'); textRegion._height = parseInt(anno.style.height ?? '0'); textRegion._width = parseInt(anno.style.width ?? '0'); - textRegion.annoTextRegion = textRegionAnno; + textRegion.embedContainer = textRegionAnnoProto; textRegion.backgroundColor = color; annoDocs.push(textRegion); anno.remove(); @@ -118,7 +119,6 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP }) ); - const textRegionAnnoProto = textRegionAnno[DocData]; textRegionAnnoProto.y = Math.max(minY, 0); textRegionAnnoProto.x = Math.max(minX, 0); textRegionAnnoProto.height = Math.max(maxY, 0) - Math.max(minY, 0); @@ -202,10 +202,10 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP return target; }; DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docView(), sourceAnchorCreator, targetCreator), e.pageX, e.pageY, { - dragComplete: e => { - if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) { - e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.props.Document; - e.annoDragData.linkSourceDoc.followLinkZoom = false; + dragComplete: dragEv => { + if (!dragEv.aborted && dragEv.annoDragData && dragEv.annoDragData.linkSourceDoc && dragEv.annoDragData.dropDocument && dragEv.linkDocument) { + dragEv.annoDragData.linkSourceDoc.followLinkToggle = dragEv.annoDragData.dropDocument.annotationOn === this.props.Document; + dragEv.annoDragData.linkSourceDoc.followLinkZoom = false; } }, }); diff --git a/src/client/views/PropertiesSection.tsx b/src/client/views/PropertiesSection.tsx index f083f03b9..b9a587719 100644 --- a/src/client/views/PropertiesSection.tsx +++ b/src/client/views/PropertiesSection.tsx @@ -13,7 +13,6 @@ export interface PropertiesSectionProps { children?: JSX.Element | string | null; isOpen: boolean; setIsOpen: (bool: boolean) => any; - setInSection?: (bool: boolean) => any; onDoubleClick?: () => void; } @@ -30,7 +29,7 @@ export class PropertiesSection extends React.Component<PropertiesSectionProps> { render() { if (this.props.children === undefined || this.props.children === null) return null; return ( - <div className="propertiesView-section" onPointerEnter={action(() => this.props.setInSection && this.props.setInSection(true))} onPointerLeave={action(() => this.props.setInSection && this.props.setInSection(false))}> + <div className="propertiesView-section"> <div className="propertiesView-sectionTitle" onDoubleClick={action(() => { diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 8ac0f7d7b..5e1c80f0c 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -114,7 +114,6 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps @observable openAddSlide: boolean = false; @observable openSlideOptions: boolean = false; - @observable inOptions: boolean = false; @observable _controlButton: boolean = false; private _disposers: { [name: string]: IReactionDisposer } = {}; @@ -602,9 +601,9 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps } @computed get currentType() { - const documentType = StrCast(this.selectedDoc?.type); - const currentType: string = documentType; - const capitalizedDocType = ClientUtils.cleanDocumentType(currentType as DocumentType); + const docType = StrCast(this.selectedDoc?.type) as DocumentType; + const colType = StrCast(this.selectedDoc?.type_collection) as CollectionViewType; + const capitalizedDocType = ClientUtils.cleanDocumentType(docType, colType); return ( <div> @@ -1080,7 +1079,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps @computed get optionsSubMenu() { return ( // prettier-ignore - <PropertiesSection title="Options" inSection={this.inOptions} isOpen={this.openOptions} setInSection={bool => { this.inOptions = bool; }} setIsOpen={bool => { this.openOptions = bool; }} onDoubleClick={this.CloseAll}> + <PropertiesSection title="Options" isOpen={this.openOptions} setIsOpen={bool => { this.openOptions = bool; }} onDoubleClick={this.CloseAll}> <PropertiesButtons /> </PropertiesSection> ); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx index 707f6c198..90977d955 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx @@ -11,7 +11,7 @@ export interface CollectionFreeFormPannableContentsProps { Document: Doc; viewDefDivClick?: ScriptField; children?: React.ReactNode | undefined; - transition?: string; + transition: () => string; isAnnotationOverlay: boolean | undefined; showPresPaths: () => boolean; transform: () => string; @@ -53,7 +53,7 @@ export class CollectionFreeFormPannableContents extends ObservableReactComponent }} style={{ transform: this._props.transform(), - transition: this._props.transition, + transition: this._props.transition(), width: this._props.isAnnotationOverlay ? undefined : 0, // if not an overlay, then this will be the size of the collection, but panning and zooming will move it outside the visible border of the collection and make it selectable. This problem shows up after zooming/panning on a background collection -- you can drag the collection by clicking on apparently empty space outside the collection }}> {this.props.children} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index d9c988d54..12a299ce6 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -246,6 +246,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection onChildDoubleClickHandler = () => this._props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); elementFunc = () => this._layoutElements; viewTransition = () => (this._panZoomTransition ? '' + this._panZoomTransition : undefined); + panZoomTransition = () => (this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this.Document._viewTransition, 'string', null))); fitContentOnce = () => { const vals = this.fitToContentVals; this.layoutDoc._freeform_panX = vals.bounds.cx; @@ -1854,7 +1855,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection isAnnotationOverlay={this.isAnnotationOverlay} transform={this.PanZoomCenterXf} showPresPaths={this.showPresPaths} - transition={this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this.Document._viewTransition, 'string', null))} + transition={this.panZoomTransition} viewDefDivClick={this._props.viewDefDivClick}> {this.props.children ?? null} {/* most likely case of children is document content that's being annoated: eg., an image */} {this.contentViews} diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 5c91d5aca..3f453eb93 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -29,7 +29,6 @@ export interface FocusViewOptions { openLocation?: OpenWhere; // where to open a missing document zoomTextSelections?: boolean; // whether to display a zoomed overlay of anchor text selections toggleTarget?: boolean; // whether to toggle target on and off - anchorDoc?: Doc; // doc containing anchor info to apply at end of focus to target doc easeFunc?: 'linear' | 'ease'; // transition method for scrolling } export type FocusFuncType = (doc: Doc, options: FocusViewOptions) => Opt<number>; diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx index 79738c452..1b2aefbe2 100644 --- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx @@ -178,7 +178,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() { if (selected.length > 1) { text = selected.length + ' selected'; } else { - text = ClientUtils.cleanDocumentType(StrCast(selected.lastElement().type) as DocumentType); + text = ClientUtils.cleanDocumentType(StrCast(selected.lastElement().type) as DocumentType, '' as CollectionViewType); icon = Doc.toIcon(selected.lastElement()); } return ( diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss index 0f5e25a0c..7bca1230f 100644 --- a/src/client/views/nodes/PDFBox.scss +++ b/src/client/views/nodes/PDFBox.scss @@ -236,7 +236,7 @@ //pointer-events: none; .pdfViewerDash-text { .textLayer { - display: none; + // display: none; // this makes search highlights not show up span { user-select: none; } diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index cc897aaef..b03b90418 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -28,6 +28,7 @@ import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { PinProps, ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent'; import { Colors } from '../global/globalEnums'; +// eslint-disable-next-line import/extensions import { CreateImage } from './WebBoxRenderer'; import { PDFViewer } from '../pdf/PDFViewer'; import { SidebarAnnos } from '../SidebarAnnos'; @@ -225,12 +226,13 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem ); } - sidebarAddDocTab = (doc: Doc, where: OpenWhere) => { - if (DocListCast(this.Document[this._props.fieldKey + '_sidebar']).includes(doc) && !this.SidebarShown) { + sidebarAddDocTab = (docIn: Doc | Doc[], where: OpenWhere) => { + const docs = docIn instanceof Doc ? [docIn] : docIn; + if (docs.some(doc => 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(docs, where); }; focus = (anchor: Doc, options: FocusViewOptions) => { this._initialScrollTarget = anchor; @@ -344,7 +346,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem setupMoveUpEvents( this, e, - (e, down, delta) => { + (moveEv, down, delta) => { const localDelta = this._props .ScreenToLocalTransform() .scale(this._props.NativeDimScaling?.() || 1) @@ -359,7 +361,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem } return false; }, - (e, movement, isClick) => !isClick && batch.end(), + (clickEv, movement, isClick) => !isClick && batch.end(), () => { onButton && this.toggleSidebar(); batch.end(); diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index a3b1a419b..0a4efbfe4 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -104,7 +104,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { return DocCast(this.childDocs[NumCast(this.Document._itemIndex)]); } @computed get targetDoc() { - return Cast(this.activeItem?.presentation_targetDoc, Doc, null); + return DocCast(this.activeItem?.presentation_targetDoc); } public static targetRenderedDoc = (doc: Doc) => { const targetDoc = Cast(doc?.presentation_targetDoc, Doc, null); @@ -774,7 +774,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { effect: activeItem, noSelect: true, openLocation: targetDoc.type === DocumentType.PRES ? ((OpenWhere.replace + ':' + PresBox.PanelName) as OpenWhere) : OpenWhere.addLeft, - anchorDoc: activeItem, easeFunc: StrCast(activeItem.presEaseFunc, 'ease') as any, zoomTextSelections: BoolCast(activeItem.presentation_zoomText), playAudio: BoolCast(activeItem.presPlayAudio), diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index 053c88e17..38578837a 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -1,18 +1,20 @@ +/* eslint-disable react/jsx-props-no-spreading */ import { action, computed, makeObservable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, DocListCast, Opt } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; -import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../fields/Types'; +import { BoolCast, DocCast, NumCast, StrCast } from '../../../fields/Types'; import { LinkFollower } from '../../util/LinkFollower'; import { LinkManager } from '../../util/LinkManager'; -import { undoBatch } from '../../util/UndoManager'; +import { undoable } from '../../util/UndoManager'; +import { ObservableReactComponent } from '../ObservableReactComponent'; import { OpenWhere } from '../nodes/DocumentView'; import { FieldViewProps } from '../nodes/FieldView'; import { AnchorMenu } from './AnchorMenu'; import './Annotation.scss'; -import { ObservableReactComponent } from '../ObservableReactComponent'; +import { Highlight } from '../../../fields/DocSymbols'; interface IAnnotationProps extends FieldViewProps { anno: Doc; @@ -27,11 +29,41 @@ export class Annotation extends ObservableReactComponent<IAnnotationProps> { super(props); makeObservable(this); } + + @computed get linkHighlighted() { + const found = LinkManager.Instance.getAllDirectLinks(this._props.anno).find(link => { + const a1 = LinkManager.getOppositeAnchor(link, this._props.anno); + return a1 && Doc.GetBrushStatus(DocCast(a1.annotationOn, a1)); + }); + return found; + } + linkHighlightedFunc = () => this.linkHighlighted; + highlightedFunc = () => this._props.anno[Highlight]; + + deleteAnnotation = undoable(() => { + const docAnnotations = DocListCast(this._props.dataDoc[this._props.fieldKey]); + this._props.dataDoc[this._props.fieldKey] = new List<Doc>(docAnnotations.filter(a => a !== this._props.anno)); + AnchorMenu.Instance.fadeOut(true); + this._props.select(false); + }, 'delete annotation'); + + pinToPres = undoable(() => this._props.pinToPres(this._props.anno, {}), 'pin to pres'); + render() { return ( <div style={{ display: this._props.anno.textCopied && !Doc.GetBrushHighlightStatus(this._props.anno) ? 'none' : undefined }}> {DocListCast(this._props.anno.text_inlineAnnotations).map(a => ( - <RegionAnnotation pointerEvents={this._props.pointerEvents} {...this._props} document={a} key={a[Id]} /> + // eslint-disable-next-line no-use-before-define + <RegionAnnotation + pointerEvents={this._props.pointerEvents} + {...this._props} + highlighted={this.highlightedFunc} + linkHighlighted={this.linkHighlightedFunc} + pinToPres={this.pinToPres} + deleteAnnotation={this.deleteAnnotation} + document={a} + key={a[Id]} + /> ))} </div> ); @@ -40,49 +72,41 @@ export class Annotation extends ObservableReactComponent<IAnnotationProps> { interface IRegionAnnotationProps extends IAnnotationProps { document: Doc; + linkHighlighted: () => Doc | undefined; + highlighted: () => any; + deleteAnnotation: () => void; + pinToPres: (...args: any[]) => void; pointerEvents?: () => Opt<string>; } @observer class RegionAnnotation extends ObservableReactComponent<IRegionAnnotationProps> { private _mainCont: React.RefObject<HTMLDivElement> = React.createRef(); - @computed get annoTextRegion() { - return Cast(this._props.document.annoTextRegion, Doc, null) || this._props.document; + @computed get regionDoc() { + return DocCast(this._props.document.embedContainer, this._props.document); } - @undoBatch - deleteAnnotation = () => { - const docAnnotations = DocListCast(this._props.dataDoc[this._props.fieldKey]); - this._props.dataDoc[this._props.fieldKey] = new List<Doc>(docAnnotations.filter(a => a !== this.annoTextRegion)); - AnchorMenu.Instance.fadeOut(true); - this._props.select(false); - }; - - @undoBatch - pinToPres = () => this._props.pinToPres(this.annoTextRegion, {}); + makeTargetToggle = undoable(() => { this.regionDoc.followLinkToggle = !this.regionDoc.followLinkToggle }, "set link toggle"); // prettier-ignore - @undoBatch - makeTargetToggle = () => { this.annoTextRegion.followLinkToggle = !this.annoTextRegion.followLinkToggle }; // prettier-ignore + isTargetToggler = () => BoolCast(this.regionDoc.followLinkToggle); - isTargetToggler = () => BoolCast(this.annoTextRegion.followLinkToggle); - @undoBatch - showTargetTrail = (anchor: Doc) => { + showTargetTrail = undoable((anchor: Doc) => { const trail = DocCast(anchor.presentationTrail); if (trail) { Doc.ActivePresentation = trail; this._props.addDocTab(trail, OpenWhere.replaceRight); } - }; + }, 'show target trail'); @action onContextMenu = (e: React.MouseEvent) => { AnchorMenu.Instance.Status = 'annotation'; - AnchorMenu.Instance.Delete = this.deleteAnnotation.bind(this); + AnchorMenu.Instance.Delete = this._props.deleteAnnotation; AnchorMenu.Instance.Pinned = false; - AnchorMenu.Instance.PinToPres = this.pinToPres; + AnchorMenu.Instance.PinToPres = this._props.pinToPres; AnchorMenu.Instance.MakeTargetToggle = this.makeTargetToggle; AnchorMenu.Instance.IsTargetToggler = this.isTargetToggler; - AnchorMenu.Instance.ShowTargetTrail = () => this.showTargetTrail(this.annoTextRegion); + AnchorMenu.Instance.ShowTargetTrail = () => this.showTargetTrail(this.regionDoc); AnchorMenu.Instance.jumpTo(e.clientX, e.clientY, true); e.stopPropagation(); e.preventDefault(); @@ -94,19 +118,12 @@ class RegionAnnotation extends ObservableReactComponent<IRegionAnnotationProps> e.preventDefault(); } else if (e.button === 0) { e.stopPropagation(); - LinkFollower.FollowLink(undefined, this.annoTextRegion, false); + LinkFollower.FollowLink(undefined, this.regionDoc, false); } }; - @computed get linkHighlighted() { - for (const link of LinkManager.Instance.getAllDirectLinks(this._props.document)) { - const a1 = LinkManager.getOppositeAnchor(link, this._props.document); - if (a1 && Doc.GetBrushStatus(DocCast(a1.annotationOn, this._props.document))) return true; - } - } - render() { - const brushed = this.annoTextRegion && Doc.GetBrushHighlightStatus(this.annoTextRegion); + const brushed = this.regionDoc && Doc.GetBrushHighlightStatus(this.regionDoc); return ( <div className="htmlAnnotation" @@ -128,8 +145,8 @@ class RegionAnnotation extends ObservableReactComponent<IRegionAnnotationProps> height: NumCast(this._props.document._height), opacity: brushed === Doc.DocBrushStatus.highlighted ? 0.5 : undefined, pointerEvents: this._props.pointerEvents?.() as any, - outline: brushed === Doc.DocBrushStatus.unbrushed && this.linkHighlighted ? 'solid 1px lightBlue' : undefined, - backgroundColor: brushed === Doc.DocBrushStatus.highlighted ? 'orange' : StrCast(this._props.document.backgroundColor), + outline: this._props.linkHighlighted() ? 'solid 1px lightBlue' : undefined, + backgroundColor: this._props.highlighted() ? 'orange' : StrCast(this._props.document.backgroundColor), }} /> ); diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 0ab952e84..a3fd192f7 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -1,3 +1,5 @@ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as Pdfjs from 'pdfjs-dist'; @@ -10,7 +12,7 @@ import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; import { Cast, NumCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; -import { emptyFunction, Utils } from '../../../Utils'; +import { emptyFunction } from '../../../Utils'; import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, returnAll, returnFalse, returnNone, returnZero, smoothScroll } from '../../../ClientUtils'; import { DocUtils } from '../../documents/Documents'; import { SelectionManager } from '../../util/SelectionManager'; @@ -27,9 +29,8 @@ import { AnchorMenu } from './AnchorMenu'; import { Annotation } from './Annotation'; import { GPTPopup } from './GPTPopup/GPTPopup'; import './PDFViewer.scss'; -const _global = (window /* browser */ || global) /* node */ as any; -//pdfjsLib.GlobalWorkerOptions.workerSrc = `/assets/pdf.worker.js`; +// pdfjsLib.GlobalWorkerOptions.workerSrc = `/assets/pdf.worker.js`; // The workerSrc property shall be specified. Pdfjs.GlobalWorkerOptions.workerSrc = 'https://unpkg.com/pdfjs-dist@4.1.392/build/pdf.worker.mjs'; @@ -43,6 +44,7 @@ interface IViewerProps extends FieldViewProps { url: string; sidebarAddDoc: (doc: Doc | Doc[], sidebarKey?: string | undefined) => boolean; loaded?: (nw: number, nh: number, np: number) => void; + // eslint-disable-next-line no-use-before-define setPdfViewer: (view: PDFViewer) => void; anchorMenuClick?: () => undefined | ((anchor: Doc) => void); crop: (region: Doc | undefined, addCrop?: boolean) => Doc | undefined; @@ -109,8 +111,8 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { this._disposers.layout_autoHeight = reaction( () => this._props.layoutDoc._layout_autoHeight, - layout_autoHeight => { - if (layout_autoHeight) { + layoutAutoHeight => { + if (layoutAutoHeight) { this._props.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)); } @@ -119,7 +121,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { this._disposers.selected = reaction( () => this._props.isSelected(), - selected => SelectionManager.Views.length === 1 && this.setupPdfJsViewer(), + () => SelectionManager.Views.length === 1 && this.setupPdfJsViewer(), { fireImmediately: true } ); this._disposers.curPage = reaction( @@ -169,7 +171,9 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { ) ); } - runInAction(() => (this._scrollHeight = (this._pageSizes.reduce((size, page) => size + page.height, 0) * 96) / 72)); + runInAction(() => { + this._scrollHeight = (this._pageSizes.reduce((size, page) => size + page.height, 0) * 96) / 72; + }); }; _scrollStopper: undefined | (() => void); @@ -207,14 +211,18 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { pagesinit = () => { if (this._pdfViewer._setDocumentViewerElement?.offsetParent) { - runInAction(() => (this._pdfViewer.currentScaleValue = this._props.layoutDoc._freeform_scale = 1)); + runInAction(() => { + this._pdfViewer.currentScaleValue = this._props.layoutDoc._freeform_scale = 1; + }); this.gotoPage(NumCast(this._props.Document._layout_curPage, 1)); } document.removeEventListener('pagesinit', this.pagesinit); - var quickScroll: { loc?: string; easeFunc?: 'ease' | 'linear' } | undefined = { loc: this._initialScroll ? this._initialScroll.loc?.toString() : '', easeFunc: this._initialScroll ? this._initialScroll.easeFunc : undefined }; + let quickScroll: { loc?: string; easeFunc?: 'ease' | 'linear' } | undefined = { loc: this._initialScroll ? this._initialScroll.loc?.toString() : '', easeFunc: this._initialScroll ? this._initialScroll.easeFunc : undefined }; this._disposers.scale = reaction( () => NumCast(this._props.layoutDoc._freeform_scale, 1), - scale => (this._pdfViewer.currentScaleValue = scale), + scale => { + this._pdfViewer.currentScaleValue = scale; + }, { fireImmediately: true } ); this._disposers.scroll = reaction( @@ -231,7 +239,9 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { setTimeout( () => { this._mainCont.current && (this._scrollStopper = smoothScroll(duration, this._mainCont.current, pos, this._initialScroll?.easeFunc ?? 'ease', this._scrollStopper)); - setTimeout(() => (this._forcedScroll = false), duration); + setTimeout(() => { + this._forcedScroll = false; + }, duration); }, this._mainCont.current ? 0 : 250 ); // wait for mainCont and try again to scroll @@ -267,7 +277,9 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { eventBus._on('pagesinit', this.pagesinit); eventBus._on( 'pagerendered', - action(() => (this._showWaiting = false)) + action(() => { + this._showWaiting = false; + }) ); const pdfLinkService = new PDFJSViewer.PDFLinkService({ eventBus }); const pdfFindController = new PDFJSViewer.PDFFindController({ linkService: pdfLinkService, eventBus }); @@ -310,7 +322,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { @observable private _scrollTimer: any = undefined; - onScroll = (e: React.UIEvent<HTMLElement>) => { + onScroll = () => { 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 (!LinkInfo.Instance?.LinkInfo) { @@ -345,7 +357,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { query: searchString, }; if (clear) { - this._pdfViewer?.eventBus.dispatch('reset', {}); + this._pdfViewer?.eventBus.dispatch('findbarclose', {}); } else if (!searchString) { bwd ? this.prevAnnotation() : this.nextAnnotation(); } else if (this._pdfViewer?.pageViewsReady) { @@ -392,7 +404,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { }; @action - finishMarquee = (x?: number, y?: number) => { + finishMarquee = (/* x?: number, y?: number */) => { this._getAnchor = AnchorMenu.Instance?.GetAnchor; this.isAnnotating = false; this._marqueeref.current?.onTerminateSelection(); @@ -467,7 +479,9 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { // e.stopPropagation(); // bcz: not sure why this was here. We need to allow the DocumentView to get clicks to process doubleClicks }; - setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => (this._setPreviewCursor = func); + setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => { + this._setPreviewCursor = func; + }; @action onZoomWheel = (e: React.WheelEvent) => { @@ -490,6 +504,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { return ( <div className="pdfViewerDash-annotationLayer" style={{ height: Doc.NativeHeight(this._props.Document), transform: `scale(${NumCast(this._props.layoutDoc._freeform_scale, 1)})` }} ref={this._annotationLayer}> {inlineAnnos.map(anno => ( + // eslint-disable-next-line react/jsx-props-no-spreading <Annotation {...this._props} fieldKey={this._props.fieldKey + '_annotations'} pointerEvents={this.pointerEvents} dataDoc={this._props.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} /> ))} </div> @@ -522,6 +537,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { pointerEvents: Doc.ActiveTool !== InkTool.None ? 'all' : undefined, }}> <CollectionFreeFormView + // eslint-disable-next-line react/jsx-props-no-spreading {...this._props} NativeWidth={returnZero} NativeHeight={returnZero} @@ -529,7 +545,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { pointerEvents={this._props.isContentActive() && (SnappingManager.IsDragging || Doc.ActiveTool !== InkTool.None) ? returnAll : returnNone} // freeform view doesn't get events unless something is being dragged onto it. childPointerEvents={this.childPointerEvents} // but freeform children need to get events to allow text editing, etc renderDepth={this._props.renderDepth + 1} - isAnnotationOverlay={true} + isAnnotationOverlay fieldKey={this._props.fieldKey + '_annotations'} getScrollHeight={this.getScrollHeight} setPreviewCursor={this.setPreviewCursor} @@ -582,7 +598,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { {this.pdfViewerDiv} {this.annotationLayer} {this.overlayLayer} - {this._showWaiting ? <img className="pdfViewerDash-waiting" src={'/assets/loading.gif'} /> : null} + {this._showWaiting ? <img alt="" className="pdfViewerDash-waiting" src="/assets/loading.gif" /> : null} {!this._mainCont.current || !this._annotationLayer.current ? null : ( <MarqueeAnnotator ref={this._marqueeref} @@ -590,7 +606,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { getPageFromScroll={this.getPageFromScroll} anchorMenuClick={this._props.anchorMenuClick} scrollTop={0} - isNativeScaled={true} + isNativeScaled annotationLayerScrollTop={NumCast(this._props.Document._layout_scrollTop)} addDocument={this.addDocumentWrapper} docView={this._props.pdfBox.DocumentView!} diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index 0c1a419aa..384e6d654 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -6,7 +6,6 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, DocListCastAsync, Field, FieldType } from '../../../fields/Doc'; import { DirectLinks, DocData } from '../../../fields/DocSymbols'; -import { Id } from '../../../fields/FieldSymbols'; import { DocCast, StrCast } from '../../../fields/Types'; import { DocumentType } from '../../documents/DocumentTypes'; import { DocUtils } from '../../documents/Documents'; @@ -16,16 +15,111 @@ import { SearchUtil } from '../../util/SearchUtil'; import { SettingsManager } from '../../util/SettingsManager'; import { undoBatch } from '../../util/UndoManager'; import { ViewBoxBaseComponent } from '../DocComponent'; +import { ObservableReactComponent } from '../ObservableReactComponent'; import { CollectionDockingView } from '../collections/CollectionDockingView'; import { IRecommendation, Recommendation } from '../newlightbox/components'; import { fetchRecommendations } from '../newlightbox/utils'; import { FieldView, FieldViewProps } from '../nodes/FieldView'; import './SearchBox.scss'; +import { Id } from '../../../fields/FieldSymbols'; +import { ClientUtils } from '../../../ClientUtils'; const DAMPENING_FACTOR = 0.9; const MAX_ITERATIONS = 25; const ERROR = 0.03; +export interface SearchBoxItemProps { + Document: Doc; + searchString: string; + isLinkSearch: boolean; + matchedKeys: string[]; + className: string; + linkFrom: Doc | undefined; + selectItem: (doc: Doc) => void; + linkCreateAnchor?: () => Doc | undefined; + linkCreated?: (link: Doc) => void; +} +@observer +export class SearchBoxItem extends ObservableReactComponent<SearchBoxItemProps> { + constructor(props: SearchBoxItemProps) { + super(props); + makeObservable(this); + } + + /** + * @param {Doc} doc - doc to be selected + * + * This method selects a doc by either jumping to it (centering/zooming in on it) + * or opening it in a new tab. + */ + selectElement = async (doc: Doc, finishFunc: () => void) => { + await DocumentManager.Instance.showDocument(doc, { willZoomCentered: true }, finishFunc); + }; + + /** + * @param {Doc} doc - doc of the search result that has been clicked on + * + * This method is called when the user clicks on a search result. The _selectedResult is + * updated accordingly and the doc is highlighted with the selectElement method. + */ + onResultClick = action(async (doc: Doc) => { + this._props.selectItem(doc); + this.selectElement(doc, () => DocumentManager.Instance.getFirstDocumentView(doc)?.ComponentView?.search?.(this._props.searchString, undefined, false)); + }); + + componentWillUnmount(): void { + const doc = this._props.Document; + DocumentManager.Instance.getFirstDocumentView(doc)?.ComponentView?.search?.('', undefined, true); + } + + @undoBatch + makeLink = action((linkTo: Doc) => { + const linkFrom = this._props.linkCreateAnchor?.(); + if (linkFrom) { + const link = DocUtils.MakeLink(linkFrom, linkTo, {}); + link && this._props.linkCreated?.(link); + } + }); + + render() { + // eslint-disable-next-line no-use-before-define + const formattedType = SearchBox.formatType(StrCast(this._props.Document.type), StrCast(this._props.Document.type_collection)); + const { title } = this._props.Document; + + return ( + <Tooltip placement="right" title={<div className="dash-tooltip">{title as string}</div>}> + <div + onClick={ + this._props.isLinkSearch + ? () => this.makeLink(this._props.Document) + : e => { + this.onResultClick(this._props.Document); + e.stopPropagation(); + } + } + style={{ + fontWeight: LinkManager.Links(this._props.linkFrom).find( + link => + Doc.AreProtosEqual(LinkManager.getOppositeAnchor(link, this._props.linkFrom!), this._props.Document) || + Doc.AreProtosEqual(DocCast(LinkManager.getOppositeAnchor(link, this._props.linkFrom!)?.annotationOn), this._props.Document) + ) + ? 'bold' + : '', + }} + className={this._props.className}> + <div className="searchBox-result-title">{title as string}</div> + <div className="searchBox-result-type" style={{ color: SettingsManager.userVariantColor }}> + {formattedType} + </div> + <div className="searchBox-result-keys" style={{ color: SettingsManager.userVariantColor }}> + {this._props.matchedKeys.join(', ')} + </div> + </div> + </Tooltip> + ); + } +} + export interface SearchBoxProps extends FieldViewProps { linkSearch: boolean; linkFrom?: (() => Doc | undefined) | undefined; @@ -112,26 +206,6 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() { }); /** - * @param {Doc} doc - doc of the search result that has been clicked on - * - * This method is called when the user clicks on a search result. The _selectedResult is - * updated accordingly and the doc is highlighted with the selectElement method. - */ - onResultClick = action(async (doc: Doc) => { - this._selectedResult = doc; - this.selectElement(doc, () => DocumentManager.Instance.getFirstDocumentView(doc)?.ComponentView?.search?.(this._searchString, undefined, false)); - }); - - @undoBatch - makeLink = action((linkTo: Doc) => { - const linkFrom = this._props.linkCreateAnchor?.(); - if (linkFrom) { - const link = DocUtils.MakeLink(linkFrom, linkTo, {}); - link && this._props.linkCreated?.(link); - } - }); - - /** * @param {Doc[]} docs - docs to be searched through recursively * @param {number, Doc => void} func - function to be called on each doc * @@ -217,7 +291,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() { if (doc[DocData][DirectLinks].size === 0) { this._linkedDocsOut.set(doc, new Set(this._results.keys())); - this._results.forEach((_, linkedDoc) => { + this._results.forEach((__, linkedDoc) => { this._linkedDocsIn.get(linkedDoc)?.add(doc); }); } else { @@ -251,7 +325,6 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() { pageRankIteration(): boolean { let converged = true; const pageRankFromAll = (1 - DAMPENING_FACTOR) / this._results.size; - const nextPageRanks = new Map<Doc, number>(); this._results.forEach((_, doc) => { @@ -346,16 +419,6 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() { }); /** - * @param {Doc} doc - doc to be selected - * - * This method selects a doc by either jumping to it (centering/zooming in on it) - * or opening it in a new tab. - */ - selectElement = async (doc: Doc, finishFunc: () => void) => { - await DocumentManager.Instance.showDocument(doc, { willZoomCentered: true }, finishFunc); - }; - - /** * This method returns a JSX list of the options in the select drop-down menu, which * is used to filter the types of documents that appear in the search results. */ @@ -365,7 +428,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() { return selectValues.map(value => ( <option key={value} value={value}> - {SearchBox.formatType(value, '')} + {ClientUtils.cleanDocumentTypeExt(value as DocumentType)} </option> )); } @@ -374,56 +437,36 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() { * This method renders the search input box, select drop-down menu, and search results. */ render() { - let validResults = 0; - const isLinkSearch: boolean = this._props.linkSearch; - const sortedResults = Array.from(this._results.entries()).sort((a, b) => (this._pageRanks.get(b[0]) ?? 0) - (this._pageRanks.get(a[0]) ?? 0)); // sorted by page rank - const resultsJSX = [] as any[]; + const linkFrom = this._props.linkFrom?.(); - const fromDoc = this._props.linkFrom?.(); - - sortedResults.forEach(result => { + let validResults = 0; + sortedResults.forEach(([Document, matchedKeys]) => { let className = 'searchBox-results-scroll-view-result'; - if (this._selectedResult === result[0]) { + if (this._selectedResult === Document) { className += ' searchBox-results-scroll-view-result-selected'; } - const formattedType = SearchBox.formatType(StrCast(result[0].type), StrCast(result[0].type_collection)); - const { title } = result[0]; - - if (this._docTypeString === 'keys' || this._docTypeString === 'all' || this._docTypeString === result[0].type) { + if (this._docTypeString === 'keys' || this._docTypeString === 'all' || this._docTypeString === Document.type) { validResults++; resultsJSX.push( - <Tooltip key={result[0][Id]} placement="right" title={<div className="dash-tooltip">{title as string}</div>}> - <div - onClick={ - isLinkSearch - ? () => this.makeLink(result[0]) - : e => { - this.onResultClick(result[0]); - e.stopPropagation(); - } - } - style={{ - fontWeight: LinkManager.Links(fromDoc).find( - link => Doc.AreProtosEqual(LinkManager.getOppositeAnchor(link, fromDoc!), result[0] as Doc) || Doc.AreProtosEqual(DocCast(LinkManager.getOppositeAnchor(link, fromDoc!)?.annotationOn), result[0] as Doc) - ) - ? 'bold' - : '', - }} - className={className}> - <div className="searchBox-result-title">{title as string}</div> - <div className="searchBox-result-type" style={{ color: SettingsManager.userVariantColor }}> - {formattedType} - </div> - <div className="searchBox-result-keys" style={{ color: SettingsManager.userVariantColor }}> - {result[1].join(', ')} - </div> - </div> - </Tooltip> + <SearchBoxItem + key={Document[Id]} + Document={Document} + selectItem={action((doc: Doc) => { + this._selectedResult = doc; + })} + isLinkSearch={isLinkSearch} + searchString={this._searchString} + matchedKeys={matchedKeys} + linkFrom={linkFrom} + className={className} + linkCreateAnchor={this._props.linkCreateAnchor} + linkCreated={this._props.linkCreated} + /> ); } }); diff --git a/src/client/views/selectedDoc/SelectedDocView.tsx b/src/client/views/selectedDoc/SelectedDocView.tsx index c9c01189e..7ad7b2927 100644 --- a/src/client/views/selectedDoc/SelectedDocView.tsx +++ b/src/client/views/selectedDoc/SelectedDocView.tsx @@ -35,7 +35,7 @@ export class SelectedDocView extends React.Component<SelectedDocViewProps> { val: StrCast(doc._id), color: SettingsManager.userColor, background: SettingsManager.userBackgroundColor, - icon: <FontAwesomeIcon size={'1x'} icon={Doc.toIcon(doc)} />, + icon: <FontAwesomeIcon size="1x" icon={Doc.toIcon(doc)} />, onClick: () => DocumentManager.Instance.showDocument(doc, options, emptyFunction), }; })} diff --git a/src/client/views/selectedDoc/index.ts b/src/client/views/selectedDoc/index.ts index 1f1db91f6..968e9c2d4 100644 --- a/src/client/views/selectedDoc/index.ts +++ b/src/client/views/selectedDoc/index.ts @@ -1 +1 @@ -export * from './SelectedDocView'
\ No newline at end of file +export * from './SelectedDocView'; diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 5028e1f8f..7b5e21ab7 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1235,6 +1235,8 @@ export namespace Doc { // if doc is a layout template then we don't want to highlight the proto since that will be the entire template, not just the specific layout field highlightedDocs.add(doc[DocData]); doc[DocData][Highlight] = true; + // want to highlight the targets of presentation docs explicitly since following a pres target does not highlight PDf <Annotations> which are not DocumentViews + if (doc.presentation_targetDoc) DocCast(doc.presentation_targetDoc)[Highlight] = true; } }); } @@ -1246,6 +1248,7 @@ export namespace Doc { highlightedDocs.delete(doc[DocData]); doc[Highlight] = doc[DocData][Highlight] = false; doc[Animation] = undefined; + if (doc.presentation_targetDoc) DocCast(doc.presentation_targetDoc)[Highlight] = false; }); }); } diff --git a/src/fields/List.ts b/src/fields/List.ts index 852b05bc6..38c47d546 100644 --- a/src/fields/List.ts +++ b/src/fields/List.ts @@ -308,7 +308,7 @@ class ListImpl<T extends FieldType> extends ObjectField { // eslint-disable-next-line no-use-before-define private [SelfProxy]: List<FieldType>; // also used in utils.ts even though it won't be found using find all references - [ToScriptString]() { return `new List(${this[ToJavascriptString]})`; } // prettier-ignore + [ToScriptString]() { return `new List(${this[ToJavascriptString]()})`; } // prettier-ignore [ToJavascriptString]() { return `[${(this as any).map((field: any) => Field.toScriptString(field))}]`; } // prettier-ignore [ToString]() { return `[${(this as any).map((field: any) => Field.toString(field))}]`; } // prettier-ignore } |