From 5b5730a7df073659cbb6c326f748f7fcbe6625e8 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 18 Jan 2024 15:05:53 -0500 Subject: lots of changes to try to simplify API for viewPaths and related --- src/client/views/nodes/CollectionFreeFormDocumentView.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src/client/views/nodes/CollectionFreeFormDocumentView.tsx') diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index ad5aabc21..83cabf355 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -15,7 +15,7 @@ import { DocComponent } from '../DocComponent'; import { StyleProp } from '../StyleProvider'; import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; import './CollectionFreeFormDocumentView.scss'; -import { DocumentView, DocumentViewInternalProps, DocumentViewProps, OpenWhere } from './DocumentView'; +import { DocumentView, DocumentViewProps, OpenWhere } from './DocumentView'; import { FieldViewProps } from './FieldView'; export interface CollectionFreeFormDocumentViewWrapperProps extends DocumentViewProps { @@ -149,7 +149,7 @@ export class CollectionFreeFormDocumentView extends DocComponent, property: string) => { + styleProvider = (doc: Doc | undefined, props: Opt, property: string) => { if (doc === this.layoutDoc) { switch (property) { case StyleProp.Opacity: return this._props.w_Opacity(); // only change the opacity for this specific document, not its children @@ -229,7 +229,7 @@ export class CollectionFreeFormDocumentView extends DocComponent { const topDoc = this.Document; - const containerDocView = this._props.docViewPath().lastElement(); + const containerDocView = this._props.containerViewPath?.().lastElement(); const screenXf = containerDocView?.screenToContentsTransform(); if (screenXf) { SelectionManager.DeselectAll(); @@ -252,7 +252,7 @@ export class CollectionFreeFormDocumentView extends DocComponent { - const [locX, locY] = this.ScreenToLocalBoxXf().transformDirection(x, y); + const [locX, locY] = this._props.ScreenToLocalTransform().transformDirection(x, y); this._props.Document.x = this._props.w_X() + locX; this._props.Document.y = this._props.w_Y() + locY; }; -- cgit v1.2.3-70-g09d2 From 001127c07f95173d7036db19d07dcfb1135f3caa Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 22 Jan 2024 21:45:28 -0500 Subject: fixed schema rows to render, fix for resizing docs from left side. all locking docs in non freeform view. fix for labelBox with multiple rows to keep top rows. cleaned up docViewPath/containerViewPath & --- src/client/util/CurrentUserUtils.ts | 2 +- src/client/views/DocComponent.tsx | 20 +------- src/client/views/DocumentDecorations.tsx | 9 ++-- src/client/views/MainView.tsx | 7 +-- src/client/views/MarqueeAnnotator.tsx | 6 +-- src/client/views/StyleProvider.tsx | 56 +++++++--------------- .../views/collections/CollectionNoteTakingView.tsx | 7 ++- .../views/collections/CollectionStackingView.tsx | 15 +++--- src/client/views/collections/CollectionSubView.tsx | 4 ++ src/client/views/collections/TabDocView.tsx | 4 +- src/client/views/collections/TreeView.tsx | 4 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 17 ++++--- .../collectionLinear/CollectionLinearView.tsx | 2 +- .../CollectionMulticolumnView.tsx | 2 +- .../CollectionMultirowView.tsx | 2 +- .../collectionSchema/CollectionSchemaView.scss | 3 +- .../collectionSchema/CollectionSchemaView.tsx | 2 +- .../collections/collectionSchema/SchemaRowBox.tsx | 18 ++++++- .../views/nodes/CollectionFreeFormDocumentView.tsx | 14 ++++-- src/client/views/nodes/ComparisonBox.tsx | 7 ++- src/client/views/nodes/DocumentContentsView.tsx | 12 ++--- src/client/views/nodes/DocumentView.tsx | 29 +++++------ src/client/views/nodes/LabelBox.scss | 5 +- src/client/views/nodes/LabelBox.tsx | 4 +- src/client/views/nodes/LinkAnchorBox.tsx | 11 +++-- src/client/views/nodes/LinkBox.tsx | 6 +-- src/client/views/nodes/PDFBox.tsx | 1 + .../views/nodes/formattedText/DashDocView.tsx | 7 ++- .../views/nodes/formattedText/DashFieldView.tsx | 2 +- src/client/views/nodes/trails/PresElementBox.tsx | 2 +- src/client/views/pdf/PDFViewer.tsx | 13 ++--- src/client/views/topbar/TopBar.tsx | 3 +- src/fields/Doc.ts | 5 ++ 33 files changed, 143 insertions(+), 158 deletions(-) (limited to 'src/client/views/nodes/CollectionFreeFormDocumentView.tsx') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 7d9cb36c5..0b15e3add 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -192,7 +192,7 @@ export class CurrentUserUtils { {onClick:"deiconifyView(documentView)", onDoubleClick: "deiconifyViewToLightbox(documentView)", }); }; const labelBox = (opts: DocumentOptions, data?:string) => Docs.Create.LabelDocument({ - textTransform: "unset", letterSpacing: "unset", _singleLine: false, _label_minFontSize: 14, _label_maxFontSize: 24, layout_borderRounding: "5px", _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, ...opts + textTransform: "unset", letterSpacing: "unset", _singleLine: false, _label_minFontSize: 14, _label_maxFontSize: 14, layout_borderRounding: "5px", _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, ...opts }); const imageBox = (opts: DocumentOptions, url?:string) => Docs.Create.ImageDocument(url ?? "http://www.cs.brown.edu/~bcz/noImage.png", { "icon_nativeWidth": 360 / 4, "icon_nativeHeight": 270 / 4, iconTemplate:DocumentType.IMG, _width: 360 / 4, _height: 270 / 4, _layout_showTitle: "title", ...opts }); const fontBox = (opts:DocumentOptions, data?:string) => Docs.Create.FontIconDocument({ _nativeHeight: 30, _nativeWidth: 30, _width: 30, _height: 30, ...opts }); diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 4400b3576..6aba4a042 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -1,6 +1,6 @@ import { action, computed, makeObservable, observable } from 'mobx'; import * as React from 'react'; -import { emptyPath, returnFalse } from '../../Utils'; +import { returnFalse } from '../../Utils'; import { DateField } from '../../fields/DateField'; import { Doc, DocListCast, Opt } from '../../fields/Doc'; import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, DocData } from '../../fields/DocSymbols'; @@ -41,7 +41,7 @@ export function DocComponent

() { } // This is the data part of a document -- ie, the data that is constant across all views of the document @computed get dataDoc() { - return this._props.Document[DocData] as Doc; + return this._props.Document[DocData]; } } return Component; @@ -55,16 +55,12 @@ export interface ViewBoxBaseProps { Document: Doc; TemplateDataDocument?: Doc; DocumentView?: () => DocumentView; - containerViewPath?: () => DocumentView[]; fieldKey: string; isSelected: () => boolean; isContentActive: () => boolean | undefined; ScreenToLocalTransform: () => Transform; renderDepth: number; } -function returnEmptyDocViewList() { - return [] as DocumentView[]; -} export function ViewBoxBaseComponent

() { class Component extends ObservableReactComponent> { ScreenToLocalBoxXf = () => this._props.ScreenToLocalTransform(); @@ -72,12 +68,6 @@ export function ViewBoxBaseComponent

() { get DocumentView() { return this._props.DocumentView; } - get docViewPath() { - return this.DocumentView?.().docViewPath ?? returnEmptyDocViewList; - } - get containerViewPath() { - return this._props.containerViewPath; - } //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then get Document() { return this._props.Document; @@ -132,12 +122,6 @@ export function ViewBoxAnnotatableComponent

() get DocumentView() { return this._props.DocumentView; } - get docViewPath() { - return this.DocumentView?.().docViewPath ?? returnEmptyDocViewList; - } - get containerViewPath() { - return this.DocumentView?.().containerViewPath ?? returnEmptyDocViewList; - } //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then @computed get Document() { return this._props.Document; diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index c96253405..8db972ef0 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -344,10 +344,7 @@ export class DocumentDecorations extends ObservableReactComponent UndoManager.RunInBatch( - () => SelectionManager.Docs.forEach(doc => - doc._pointerEvents = (doc._lockedPosition = !doc._lockedPosition)? 'none' : undefined ), - 'toggleBackground' ) // prettier-ignore + e => UndoManager.RunInBatch(() => SelectionManager.Docs.forEach(doc => Doc.toggleLockedPosition(doc)), 'toggleBackground') ); e.stopPropagation(); }; @@ -530,7 +527,7 @@ export class DocumentDecorations extends ObservableReactComponent 135 && seldocview.CollectionFreeFormDocumentView; + const useLock = bounds.r - bounds.x > 135; const useRotation = !hideResizers && seldocview.Document.type !== DocumentType.EQUATION && seldocview.CollectionFreeFormDocumentView; // when do we want an object to not rotate? const rotation = SelectionManager.Views.length == 1 ? seldocview.screenToContentsTransform().inverse().RotateDeg : 0; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index cd6aeea41..d700ea020 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -56,13 +56,11 @@ import { CollectionLinearView } from './collections/collectionLinear'; import { LinkMenu } from './linking/LinkMenu'; import { AudioBox } from './nodes/AudioBox'; import { DocButtonState } from './nodes/DocumentLinksButton'; -import { DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod } from './nodes/DocumentView'; +import { DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod, returnEmptyDocViewList } from './nodes/DocumentView'; import { ImageBox } from './nodes/ImageBox'; import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup'; import { LinkDocPreview, LinkInfo } from './nodes/LinkDocPreview'; import { MapAnchorMenu } from './nodes/MapBox/MapAnchorMenu'; -import { MapBox } from './nodes/MapBox/MapBox'; -import { RadialMenu } from './nodes/RadialMenu'; import { TaskCompletionBox } from './nodes/TaskCompletedBox'; import { DashFieldViewMenu } from './nodes/formattedText/DashFieldView'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; @@ -898,6 +896,7 @@ export class MainView extends ObservableReactComponent<{}> {

{ isAnyChildContentActive={returnFalse} isContentActive={emptyFunction} isSelected={returnFalse} - containerViewPath={returnEmptyDoclist} moveDocument={this.moveButtonDoc} addDocument={this.addButtonDoc} addDocTab={DocumentViewInternal.addDocTabFunc} @@ -1021,7 +1019,6 @@ export class MainView extends ObservableReactComponent<{}> { - diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index cfdc648b4..d0516336c 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -66,7 +66,7 @@ export class MarqueeAnnotator extends ObservableReactComponent { if (!e.aborted && e.linkDocument) { Doc.GetProto(e.linkDocument).link_relationship = 'cropped image'; - Doc.GetProto(e.linkDocument).title = 'crop: ' + this.props.docView().Document.title; + Doc.GetProto(e.linkDocument).title = 'crop: ' + this.props.Document.title; Doc.GetProto(e.linkDocument).link_displayLine = false; } }, @@ -256,7 +256,7 @@ export class MarqueeAnnotator extends ObservableReactComponent (copy.style[prop as any] = marqueeStyle[prop as any])); copy.className = 'marqueeAnnotator-annotationBox'; copy.style.top = parseInt(marqueeStyle.top.toString().replace('px', '')) / scale + this.props.scrollTop + 'px'; diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 491a72c08..1cb5ff249 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -20,7 +20,6 @@ import { SnappingManager } from '../util/SnappingManager'; import { undoBatch, UndoManager } from '../util/UndoManager'; import { TreeSort } from './collections/TreeSort'; import { Colors } from './global/globalEnums'; -import { DocumentViewProps } from './nodes/DocumentView'; import { FieldViewProps } from './nodes/FieldView'; import { KeyValueBox } from './nodes/KeyValueBox'; import { PropertiesView } from './PropertiesView'; @@ -51,21 +50,7 @@ export enum StyleProp { } function toggleLockedPosition(doc: Doc) { - UndoManager.RunInBatch( - () => - runInAction(() => { - doc._lockedPosition = !doc._lockedPosition; - doc._pointerEvents = doc._lockedPosition ? 'none' : undefined; - }), - 'toggleBackground' - ); -} - -export function testDocProps(toBeDetermined: any): toBeDetermined is DocumentViewProps { - return toBeDetermined?.isContentActive ? toBeDetermined : undefined; -} -export function testFieldProps(toBeDetermined: any): toBeDetermined is FieldViewProps { - return toBeDetermined?.isContentActive ? toBeDetermined : undefined; + UndoManager.RunInBatch(() => Doc.toggleLockedPosition(doc), 'toggleBackground'); } export function wavyBorderPath(pw: number, ph: number, inset: number = 0.05) { @@ -82,19 +67,17 @@ export function wavyBorderPath(pw: number, ph: number, inset: number = 0.05) { // export function DefaultStyleProvider(doc: Opt, props: Opt, property: string): any { const remoteDocHeader = 'author;author_date;noMargin'; - const docProps = testDocProps(props) ? props : undefined; - const fieldProps = testFieldProps(props) ? props : undefined; const isCaption = property.includes(':caption'); const isAnchor = property.includes(':anchor'); const isNonTransparent = property.includes(':nonTransparent'); const isNonTransparentLevel = isNonTransparent ? Number(property.replace(/.*:nonTransparent([0-9]+).*/, '$1')) : 0; // property.includes(':nonTransparent'); const isContent = property.includes(':content'); const isAnnotated = property.includes(':annotated'); - const isInk = () => doc?._layout_isSvg && !docProps?.LayoutTemplateString; + const isInk = () => doc?._layout_isSvg && !props?.LayoutTemplateString; const isOpen = property.includes(':open'); const isEmpty = property.includes(':empty'); const boxBackground = property.includes(':box'); - const fieldKey = fieldProps?.fieldKey ? fieldProps.fieldKey + '_' : isCaption ? 'caption_' : ''; + const fieldKey = props?.fieldKey ? props.fieldKey + '_' : isCaption ? 'caption_' : ''; const lockedPosition = () => doc && BoolCast(doc._lockedPosition); const titleHeight = () => props?.styleProvider?.(doc, props, StyleProp.TitleHeight); const backgroundCol = () => props?.styleProvider?.(doc, props, StyleProp.BackgroundColor + ':nonTransparent' + (isNonTransparentLevel + 1)); @@ -137,20 +120,19 @@ export function DefaultStyleProvider(doc: Opt, props: Opt, return undefined; case StyleProp.DocContents:return undefined; case StyleProp.WidgetColor:return isAnnotated ? Colors.LIGHT_BLUE : 'dimgrey'; - case StyleProp.Opacity: return docProps?.LayoutTemplateString?.includes(KeyValueBox.name) ? 1 : doc?.text_inlineAnnotations ? 0 : Cast(doc?._opacity, "number", Cast(doc?.opacity, 'number', null)); + case StyleProp.Opacity: return props?.LayoutTemplateString?.includes(KeyValueBox.name) ? 1 : doc?.text_inlineAnnotations ? 0 : Cast(doc?._opacity, "number", Cast(doc?.opacity, 'number', null)); case StyleProp.FontSize: return StrCast(doc?.[fieldKey + 'fontSize'], StrCast(doc?._text_fontSize, StrCast(Doc.UserDoc().fontSize))); case StyleProp.FontFamily: return StrCast(doc?.[fieldKey + 'fontFamily'], StrCast(doc?._text_fontFamily, StrCast(Doc.UserDoc().fontFamily))); case StyleProp.FontWeight: return StrCast(doc?.[fieldKey + 'fontWeight'], StrCast(doc?._text_fontWeight, StrCast(Doc.UserDoc().fontWeight))); case StyleProp.FillColor: return StrCast(doc?._fillColor, StrCast(doc?.fillColor, 'transparent')); case StyleProp.ShowCaption:return doc?._type_collection === CollectionViewType.Carousel ? undefined: StrCast(doc?._layout_showCaption); - case StyleProp.TitleHeight: - return (props?.DocumentView?.().screenToViewTransform().Scale ?? 1) * NumCast(Doc.UserDoc().headerHeight,30) + case StyleProp.TitleHeight:return (props?.ScreenToLocalTransform().Scale ?? 1) * NumCast(Doc.UserDoc().headerHeight,30); case StyleProp.ShowTitle: return ( (doc && - !docProps?.LayoutTemplateString && + !props?.LayoutTemplateString && !doc.presentation_targetDoc && - !docProps?.LayoutTemplateString?.includes(KeyValueBox.name) && + !props?.LayoutTemplateString?.includes(KeyValueBox.name) && props?.layout_showTitle?.() !== '' && StrCast( doc._layout_showTitle, @@ -169,7 +151,6 @@ export function DefaultStyleProvider(doc: Opt, props: Opt, if (doc?.type === DocumentType.FONTICON) return SettingsManager.userColor; const docColor: Opt = StrCast(doc?.[fieldKey + 'color'], StrCast(doc?._color)); if (docColor) return docColor; - const parView = props?.DocumentView?.(); const backColor = backgroundCol(); return backColor ? lightOrDark(backColor) : undefined; case StyleProp.BorderRounding: @@ -247,7 +228,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt, doc?.layout_boxShadow, doc?._type_collection === CollectionViewType.Pile ? '4px 4px 10px 2px' - : lockedPosition() || doc?.isGroup || docProps?.LayoutTemplateString + : lockedPosition() || doc?.isGroup || props?.LayoutTemplateString ? undefined // groups have no drop shadow -- they're supposed to be "invisible". LayoutString's imply collection is being rendered as something else (e.g., title of a Slide) : `${Colors.DARK_GRAY} ${StrCast(doc.layout_boxShadow, '0.2vw 0.2vw 0.8vw')}` ); @@ -257,10 +238,10 @@ export function DefaultStyleProvider(doc: Opt, props: Opt, default: return doc.z ? `#9c9396 ${StrCast(doc?.layout_boxShadow, '10px 10px 0.9vw')}` // if it's a floating doc, give it a big shadow - : props?.DocumentView?.().containerViewPath?.().lastElement()?.Document._freeform_useClusters - ? `${backgroundCol()} ${StrCast(doc.layout_boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (docProps?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent + : props?.containerViewPath?.().lastElement()?.Document._freeform_useClusters + ? `${backgroundCol()} ${StrCast(doc.layout_boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (props?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent : NumCast(doc.group, -1) !== -1 - ? `gray ${StrCast(doc.layout_boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (docProps?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent + ? `gray ${StrCast(doc.layout_boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (props?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent : lockedPosition() ? undefined // if it's a background & has a cluster color, make the shadow spread really big : StrCast(doc.layout_fieldKey).includes('_inline') // if doc is an inline document in a text box @@ -271,8 +252,8 @@ export function DefaultStyleProvider(doc: Opt, props: Opt, } } case StyleProp.PointerEvents: - if (StrCast(doc?.pointerEvents) && !docProps?.LayoutTemplateString?.includes(KeyValueBox.name)) return StrCast(doc!.pointerEvents); // honor pointerEvents field (set by lock button usually) if it's not a keyValue view of the Doc - if (docProps?.DocumentView?.()._props.LayoutTemplateString?.includes(KeyValueBox.name)) return 'all'; + if (StrCast(doc?.pointerEvents) && !props?.LayoutTemplateString?.includes(KeyValueBox.name)) return StrCast(doc!.pointerEvents); // honor pointerEvents field (set by lock button usually) if it's not a keyValue view of the Doc + if (props?.LayoutTemplateString?.includes(KeyValueBox.name)) return 'all'; if (SnappingManager.ExploreMode || doc?.layout_unrendered) return isInk() ? 'visiblePainted' : 'all'; if (props?.pointerEvents?.() === 'none') return 'none'; if (opacity() === 0) return 'none'; @@ -281,22 +262,17 @@ export function DefaultStyleProvider(doc: Opt, props: Opt, if (props?.isDocumentActive?.() && !props.treeViewDoc) return isInk() ? 'visiblePainted' : 'all'; return undefined; // fixes problem with tree view elements getting pointer events when the tree view is not active case StyleProp.Decorations: - const lock = () => { - if (props?.DocumentView?.().containerViewPath?.().lastElement()?.Document?._type_collection === CollectionViewType.Freeform) { - return doc?.pointerEvents !== 'none' ? null : ( + const lock = () => doc?.pointerEvents !== 'none' ? null : (
toggleLockedPosition(doc)}>
); - } - }; const filter = () => { - const docView = props?.DocumentView?.(); const dashView = untracked(() => DocumentManager.Instance.getDocumentView(Doc.ActiveDashboard)); const showFilterIcon = StrListCast(doc?._childFilters).length || StrListCast(doc?._childFiltersByRanges).length ? 'green' // #18c718bd' //'hasFilter' - : docProps?.childFilters?.().filter(f => Utils.IsRecursiveFilter(f) && f !== Utils.noDragDocsFilter).length || docProps?.childFiltersByRanges().length + : props?.childFilters?.().filter(f => Utils.IsRecursiveFilter(f) && f !== Utils.noDragDocsFilter).length || props?.childFiltersByRanges().length ? 'orange' //'inheritsFilter' : undefined; return !showFilterIcon ? null : ( @@ -327,7 +303,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt, "this view inherits filters from one of its parents"} color={SettingsManager.userColor} background={showFilterIcon} - items={[ ...(dashView ? [dashView]: []), ...(docView?.containerViewPath?.()??[]), ...(docView ? [docView]:[])] + items={[ ...(dashView ? [dashView]: []), ...(props?.docViewPath?.()??[])] .filter(dv => StrListCast(dv?.Document.childFilters).length || StrListCast(dv?.Document.childRangeFilters).length) .map(dv => ({ text: StrCast(dv?.Document.title), diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx index 92c8e1256..363db8850 100644 --- a/src/client/views/collections/CollectionNoteTakingView.tsx +++ b/src/client/views/collections/CollectionNoteTakingView.tsx @@ -238,7 +238,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { PanelWidth={width} PanelHeight={height} styleProvider={this.styleProvider} - containerViewPath={this.docViewPath} + containerViewPath={this.childContainerViewPath} layout_fitWidth={this._props.childLayoutFitWidth} isContentActive={emptyFunction} onKey={this.onKeyDown} @@ -407,10 +407,9 @@ export class CollectionNoteTakingView extends CollectionSubView() { @undoBatch onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => { - const docView = fieldProps.DocumentView?.(); - if (docView && (e.ctrlKey || docView.Document._createDocOnCR) && ['Enter'].includes(e.key)) { + if ((e.ctrlKey || fieldProps.Document._createDocOnCR) && ['Enter'].includes(e.key)) { e.stopPropagation?.(); - const newDoc = Doc.MakeCopy(docView.Document, true); + const newDoc = Doc.MakeCopy(fieldProps.Document, true); Doc.GetProto(newDoc).text = undefined; FormattedTextBox.SetSelectOnLoad(newDoc); return this.addDocument?.(newDoc); diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 0a0218124..9384b7088 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -278,16 +278,15 @@ export class CollectionStackingView extends CollectionSubView { - const docView = fieldProps.DocumentView?.(); - if (docView && ['Enter'].includes(e.key) && e.ctrlKey) { + if (['Enter'].includes(e.key) && e.ctrlKey) { e.stopPropagation?.(); const below = !e.altKey && e.key !== 'Tab'; - const layout_fieldKey = StrCast(docView.LayoutFieldKey); - const newDoc = Doc.MakeCopy(docView.Document, true); - const dataField = docView.Document[Doc.LayoutFieldKey(newDoc)]; + const layout_fieldKey = StrCast(fieldProps.fieldKey); + const newDoc = Doc.MakeCopy(fieldProps.Document, true); + const dataField = fieldProps.Document[Doc.LayoutFieldKey(newDoc)]; newDoc[DocData][Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List([]) : undefined; - if (layout_fieldKey !== 'layout' && docView.Document[layout_fieldKey] instanceof Doc) { - newDoc[layout_fieldKey] = docView.Document[layout_fieldKey]; + if (layout_fieldKey !== 'layout' && fieldProps.Document[layout_fieldKey] instanceof Doc) { + newDoc[layout_fieldKey] = fieldProps.Document[layout_fieldKey]; } Doc.GetProto(newDoc).text = undefined; FormattedTextBox.SetSelectOnLoad(newDoc); @@ -327,7 +326,7 @@ export class CollectionStackingView extends CollectionSubView boolean; @@ -69,6 +70,9 @@ export function CollectionSubView(moreProps?: X) { : Doc.GetProto(this._props.Document); // if the layout document has a resolvedDataDoc, then we don't want to get its parent which would be the unexpanded template } + get childContainerViewPath() { + return this.DocumentView?.().docViewPath ?? returnEmptyDocViewList; + } // this returns whether either the collection is selected, or the template that it is part of is selected rootSelected = () => this._props.isSelected() || BoolCast(this._props.TemplateDataDocument && this._props.rootSelected?.()); diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 2441c67e6..f00e42360 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -29,7 +29,7 @@ import { LightboxView } from '../LightboxView'; import { ObservableReactComponent } from '../ObservableReactComponent'; import { DefaultStyleProvider, StyleProp } from '../StyleProvider'; import { Colors } from '../global/globalEnums'; -import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere, OpenWhereMod } from '../nodes/DocumentView'; +import { DocFocusOptions, DocumentView, OpenWhere, OpenWhereMod, returnEmptyDocViewList } from '../nodes/DocumentView'; import { KeyValueBox } from '../nodes/KeyValueBox'; import { DashFieldView } from '../nodes/formattedText/DashFieldView'; import { PinProps, PresBox, PresMovement } from '../nodes/trails'; @@ -595,7 +595,7 @@ export class TabMinimapView extends ObservableReactComponent { hideResizeHandles={this.treeView.outlineMode} styleProvider={this.titleStyleProvider} onClickScriptDisable="never" // tree docViews have a script to show fields, etc. - containerViewPath={this.treeView.docViewPath} + containerViewPath={this.treeView.childContainerViewPath} treeViewDoc={this.treeView.Document} addDocument={undefined} addDocTab={this._props.addDocTab} @@ -1084,7 +1084,7 @@ export class TreeView extends ObservableReactComponent { ScreenToLocalTransform={this.docTransform} renderDepth={this._props.renderDepth + 1} treeViewDoc={this.treeView?.Document} - containerViewPath={this.treeView.docViewPath} + containerViewPath={this.treeView.childContainerViewPath} childFilters={returnEmptyFilter} childFiltersByRanges={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 3eaf1876c..c4358747b 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1170,18 +1170,17 @@ export class CollectionFreeFormView extends CollectionSubView { - const docView = fieldProps.DocumentView?.(); - if (docView && (e.metaKey || e.ctrlKey || e.altKey || docView.Document._createDocOnCR) && ['Tab', 'Enter'].includes(e.key)) { + if ((e.metaKey || e.ctrlKey || e.altKey || fieldProps.Document._createDocOnCR) && ['Tab', 'Enter'].includes(e.key)) { e.stopPropagation?.(); const below = !e.altKey && e.key !== 'Tab'; - const layout_fieldKey = StrCast(docView.LayoutFieldKey); - const newDoc = Doc.MakeCopy(docView.Document, true); - const dataField = docView.Document[Doc.LayoutFieldKey(newDoc)]; + const layout_fieldKey = StrCast(fieldProps.fieldKey); + const newDoc = Doc.MakeCopy(fieldProps.Document, true); + const dataField = fieldProps.Document[Doc.LayoutFieldKey(newDoc)]; newDoc[DocData][Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List([]) : undefined; - if (below) newDoc.y = NumCast(docView.Document.y) + NumCast(docView.Document._height) + 10; - else newDoc.x = NumCast(docView.Document.x) + NumCast(docView.Document._width) + 10; - if (layout_fieldKey !== 'layout' && docView.Document[layout_fieldKey] instanceof Doc) { - newDoc[layout_fieldKey] = docView.Document[layout_fieldKey]; + if (below) newDoc.y = NumCast(fieldProps.Document.y) + NumCast(fieldProps.Document._height) + 10; + else newDoc.x = NumCast(fieldProps.Document.x) + NumCast(fieldProps.Document._width) + 10; + if (layout_fieldKey !== 'layout' && fieldProps.Document[layout_fieldKey] instanceof Doc) { + newDoc[layout_fieldKey] = fieldProps.Document[layout_fieldKey]; } Doc.GetProto(newDoc).text = undefined; FormattedTextBox.SetSelectOnLoad(newDoc); diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx index 4f4c674ea..72bdb9b6b 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx @@ -189,7 +189,7 @@ export class CollectionLinearView extends CollectionSubView() { dontRegisterView={BoolCast(this.Document.childDontRegisterViews)} focus={emptyFunction} styleProvider={this._props.styleProvider} - containerViewPath={this.docViewPath} + containerViewPath={this.childContainerViewPath} whenChildContentsActiveChanged={emptyFunction} bringToFront={emptyFunction} childFilters={this._props.childFilters} diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx index 1fd514ccb..40b4151fe 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx @@ -259,7 +259,7 @@ export class CollectionMulticolumnView extends CollectionSubView() { Document={childLayout} TemplateDataDocument={childLayout.resolvedDataDoc as Doc} styleProvider={this._props.styleProvider} - containerViewPath={this.docViewPath} + containerViewPath={this.childContainerViewPath} LayoutTemplate={this._props.childLayoutTemplate} LayoutTemplateString={this._props.childLayoutString} renderDepth={this._props.renderDepth + 1} diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx index 98e39cd36..04a4042f1 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx @@ -254,7 +254,7 @@ export class CollectionMultirowView extends CollectionSubView() { Document={layout} TemplateDataDocument={layout.resolvedDataDoc as Doc} styleProvider={this._props.styleProvider} - containerViewPath={this.docViewPath} + containerViewPath={this.childContainerViewPath} LayoutTemplate={this._props.childLayoutTemplate} LayoutTemplateString={this._props.childLayoutString} renderDepth={this._props.renderDepth + 1} diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss index 02131ae22..29d121974 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss @@ -210,8 +210,9 @@ border: 1px solid $medium-gray; overflow-x: hidden; overflow-y: auto; - padding: 5px; display: inline-flex; + padding: 0; + align-items: center; } .schema-row { diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index e22a666c5..227274a53 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -971,7 +971,7 @@ class CollectionSchemaViewDoc extends ObservableReactComponent + : } + size={Size.XSMALL} + onPointerDown={e => + setupMoveUpEvents( + this, + e, + returnFalse, + emptyFunction, + undoable(e => { + e.stopPropagation(); + Doc.toggleLockedPosition(this.Document); + }, 'Delete Row') + ) + }> } diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 83cabf355..73709c17e 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -12,6 +12,7 @@ import { DocumentManager } from '../../util/DocumentManager'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SelectionManager } from '../../util/SelectionManager'; import { DocComponent } from '../DocComponent'; +import { ObservableReactComponent } from '../ObservableReactComponent'; import { StyleProp } from '../StyleProvider'; import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; import './CollectionFreeFormDocumentView.scss'; @@ -37,7 +38,7 @@ export interface CollectionFreeFormDocumentViewWrapperProps extends DocumentView CollectionFreeFormView: CollectionFreeFormView; } @observer -export class CollectionFreeFormDocumentViewWrapper extends DocComponent() implements CollectionFreeFormDocumentViewProps { +export class CollectionFreeFormDocumentViewWrapper extends ObservableReactComponent implements CollectionFreeFormDocumentViewProps { constructor(props: any) { super(props); makeObservable(this); @@ -59,6 +60,9 @@ export class CollectionFreeFormDocumentViewWrapper extends DocComponent key.startsWith('w_')).map(key => key.replace('w_', '')) .map(key => ({upper:key, lower:key[0].toLowerCase() + key.substring(1)})); // prettier-ignore @@ -227,7 +231,7 @@ export class CollectionFreeFormDocumentView extends DocComponent { + float = () => { const topDoc = this.Document; const containerDocView = this._props.containerViewPath?.().lastElement(); const screenXf = containerDocView?.screenToContentsTransform(); @@ -253,8 +257,8 @@ export class CollectionFreeFormDocumentView extends DocComponent { const [locX, locY] = this._props.ScreenToLocalTransform().transformDirection(x, y); - this._props.Document.x = this._props.w_X() + locX; - this._props.Document.y = this._props.w_Y() + locY; + this.Document.x = this._props.w_X() + locX; + this.Document.y = this._props.w_Y() + locY; }; screenToLocalTransform = () => this._props @@ -288,7 +292,7 @@ export class CollectionFreeFormDocumentView extends DocComponent - {this._props.RenderCutoffProvider(this._props.Document) ? ( + {this._props.RenderCutoffProvider(this.Document) ? (
) : ( diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index e707bb836..500eb52ac 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -11,7 +11,7 @@ import { undoBatch } from '../../util/UndoManager'; import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent'; import { StyleProp } from '../StyleProvider'; import './ComparisonBox.scss'; -import { DocumentView } from './DocumentView'; +import { DocumentView, returnEmptyDocViewList } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; import { PinProps, PresBox } from './trails'; @@ -152,6 +152,9 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent (doc instanceof Doc ? [doc] : doc).reduce((res, doc: Doc) => res && this.moveDoc(doc, addDoc, this.fieldKey + '_1'), true); moveDoc2 = (doc: Doc | Doc[], targetCol: Doc | undefined, addDoc: any) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc: Doc) => res && this.moveDoc(doc, addDoc, this.fieldKey + '_2'), true); remDoc1 = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_1'), true); @@ -178,7 +181,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent { @observer export class DocumentContentsView extends ObservableReactComponent< FieldViewProps & { - layout_fieldKey: string; - LayoutTemplateString?: string; onClick?: () => ScriptField; LayoutTemplate?: () => Opt; } @@ -132,8 +130,8 @@ export class DocumentContentsView extends ObservableReactComponent< TraceMobx(); if (this._props.LayoutTemplateString) return this._props.LayoutTemplateString; if (!this.layoutDoc) return '

awaiting layout

'; - if (this._props.layout_fieldKey === 'layout_keyValue') return StrCast(this._props.Document.layout_keyValue, KeyValueBox.LayoutString()); - const layout = Cast(this.layoutDoc[this.layoutDoc === this._props.Document && this._props.layout_fieldKey ? this._props.layout_fieldKey : StrCast(this.layoutDoc.layout_fieldKey, 'layout')], 'string'); + if (this._props.fieldKey === 'layout_keyValue') return StrCast(this._props.Document.layout_keyValue, KeyValueBox.LayoutString()); + const layout = Cast(this.layoutDoc[this.layoutDoc === this._props.Document && this._props.fieldKey ? this._props.fieldKey : StrCast(this.layoutDoc.layout_fieldKey, 'layout')], 'string'); if (layout === undefined) return this._props.Document.data ? "" : KeyValueBox.LayoutString(); if (typeof layout === 'string') return layout; return '

Loading layout

'; @@ -141,12 +139,12 @@ export class DocumentContentsView extends ObservableReactComponent< get layoutDoc() { // bcz: replaced this with below : is it correct? change was made to accommodate passing fieldKey's from a layout script - // const template: Doc = this._props.LayoutTemplate?.() || Doc.Layout(this._props.Document, this._props.layout_fieldKey ? Cast(this._props.Document[this._props.layout_fieldKey], Doc, null) : undefined); + // const template: Doc = this._props.LayoutTemplate?.() || Doc.Layout(this._props.Document, this._props.fieldKey ? Cast(this._props.Document[this._props.fieldKey], Doc, null) : undefined); const template: Doc = this._props.LayoutTemplate?.() || (this._props.LayoutTemplateString && this._props.Document) || - (this._props.layout_fieldKey && StrCast(this._props.Document[this._props.layout_fieldKey]) && this._props.Document) || - Doc.Layout(this._props.Document, this._props.layout_fieldKey ? Cast(this._props.Document[this._props.layout_fieldKey], Doc, null) : undefined); + (this._props.fieldKey && StrCast(this._props.Document[this._props.fieldKey]) && this._props.Document) || + Doc.Layout(this._props.Document, this._props.fieldKey ? Cast(this._props.Document[this._props.fieldKey], Doc, null) : undefined); return Doc.expandTemplateLayout(template, this._props.Document); } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 4e13b0ccb..444c300f3 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -89,6 +89,9 @@ export enum OpenWhere { addRightKeyvalue = 'add:right:keyValue', } +export function returnEmptyDocViewList() { + return [] as DocumentView[]; +} export interface DocFocusOptions { willPan?: boolean; // determines whether to pan to target document willZoomCentered?: boolean; // determines whether to zoom in on target document. if zoomScale is 0, this just centers the document @@ -148,6 +151,7 @@ export interface DocComponentView { * */ export interface DocumentViewSharedProps { Document: Doc; + LayoutTemplateString?: string; TemplateDataDocument?: Doc; renderDepth: number; scriptContext?: any; // can be assigned anything and will be passed as 'scriptContext' to any OnClick script that executes on this document @@ -161,6 +165,7 @@ export interface DocumentViewSharedProps { ignoreAutoHeight?: boolean; disableBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over. CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView; + containerViewPath?: () => DocumentView[]; fitContentsToBox?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _freeform_fitContentsToBox property on a Document isGroupActive?: () => string | undefined; // is this document part of a group that is active setContentView?: (view: DocComponentView) => any; @@ -206,7 +211,6 @@ export interface DocumentViewProps extends DocumentViewSharedProps { hideLinkButton?: boolean; hideCaptions?: boolean; contentPointerEvents?: 'none' | 'all' | undefined; // pointer events allowed for content of a document view. eg. set to "none" in menuSidebar for sharedDocs so that you can select a document, but not interact with its contents - LayoutTemplateString?: string; dontCenter?: 'x' | 'y' | 'xy'; childHideDecorationTitle?: boolean; childHideResizeHandles?: boolean; @@ -218,7 +222,6 @@ export interface DocumentViewProps extends DocumentViewSharedProps { NativeWidth?: () => number; NativeHeight?: () => number; LayoutTemplate?: () => Opt; - containerViewPath?: () => DocumentView[]; contextMenuItems?: () => { script: ScriptField; filter?: ScriptField; label: string; icon: string }[]; onClick?: () => ScriptField; onDoubleClick?: () => ScriptField; @@ -235,13 +238,13 @@ export interface DocumentViewProps extends DocumentViewSharedProps { * these props correspond to things that the DocumentView creates and thus doesn't need to receive as a prop */ export interface DocumentViewInternalSharedProps { - DocumentView: () => DocumentView; select: (ctrlPressed: boolean, shiftPress?: boolean) => void; isSelected: () => boolean; + docViewPath: () => DocumentView[]; NativeDimScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal } export interface DocumentViewInternalProps extends DocumentViewProps, DocumentViewInternalSharedProps { - docViewPath: () => DocumentView[]; + docViewPublic: () => DocumentView; } @observer @@ -276,7 +279,7 @@ export class DocumentViewInternal extends DocComponent { - const targetMatch = - Doc.AreProtosEqual(anchor, this.Document) || // anchor is this document, so anchor's properties apply to this document - (DocCast(anchor)?.layout_unrendered && Doc.AreProtosEqual(DocCast(anchor.annotationOn), this.Document)) // the anchor is an layout_unrendered annotation on this document, so anchor properties apply to this document - ? true - : false; - return targetMatch && PresBox.restoreTargetDocView(docView, anchor, focusSpeed) ? focusSpeed : undefined; - }; - // switches text input focus to the title bar of the document (and displays the title bar if it hadn't been) setTitleFocus = () => { if (!StrCast(this.layoutDoc._layout_showTitle)) this.layoutDoc._layout_showTitle = 'title'; @@ -923,8 +917,8 @@ export class DocumentViewInternal extends DocComponent {this.layoutDoc.layout_hideAllLinks ? null : this.allLinkEndpoints}
@@ -1699,7 +1692,7 @@ export class DocumentView extends ObservableReactComponent { }}> span { + max-height: 100%; // make sure top of text is in view, otherwise it would center on middle of large text span + } } .labelBox-params { @@ -29,4 +32,4 @@ width: 100%; background: lightgray; border: dimgray solid 1px; -} \ No newline at end of file +} diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index 934bce448..cc7c15a10 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -1,7 +1,7 @@ import { action, computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { Doc, DocListCast } from '../../../fields/Doc'; +import { Doc, DocListCast, Field } from '../../../fields/Doc'; import { List } from '../../../fields/List'; import { listSpec } from '../../../fields/Schema'; import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types'; @@ -43,7 +43,7 @@ export class LabelBox extends ViewBoxBaseComponent { diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx index 864c1955b..362a7def1 100644 --- a/src/client/views/nodes/LinkAnchorBox.tsx +++ b/src/client/views/nodes/LinkAnchorBox.tsx @@ -33,15 +33,16 @@ export class LinkAnchorBox extends ViewBoxBaseComponent { const linkSource = this.linkSource; - linkSource && setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, (e, doubleTap) => { - if (doubleTap) LinkFollower.FollowLink(this.Document, linkSource, false); - else this._props.select(false); - }); + linkSource && + setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, (e, doubleTap) => { + if (doubleTap) LinkFollower.FollowLink(this.Document, linkSource, false); + else this._props.select(false); + }); }; onPointerMove = action((e: PointerEvent, down: number[], delta: number[]) => { const cdiv = this._ref?.current?.parentElement; diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx index 4221f464d..ba34255dc 100644 --- a/src/client/views/nodes/LinkBox.tsx +++ b/src/client/views/nodes/LinkBox.tsx @@ -29,12 +29,12 @@ export class LinkBox extends ViewBoxBaseComponent() { @computed get anchor1() { const anchor1 = DocCast(this.dataDoc.link_anchor_1); const anchor_1 = anchor1?.layout_unrendered ? DocCast(anchor1.annotationOn) : anchor1; - return DocumentManager.Instance.getDocumentView(anchor_1, this.containerViewPath?.().lastElement()); + return DocumentManager.Instance.getDocumentView(anchor_1, this.DocumentView?.().containerViewPath?.().lastElement()); } @computed get anchor2() { const anchor2 = DocCast(this.dataDoc.link_anchor_2); const anchor_2 = anchor2?.layout_unrendered ? DocCast(anchor2.annotationOn) : anchor2; - return DocumentManager.Instance.getDocumentView(anchor_2, this.containerViewPath?.().lastElement()); + return DocumentManager.Instance.getDocumentView(anchor_2, this.DocumentView?.().containerViewPath?.().lastElement()); } screenBounds = () => { if (this.layoutDoc._layout_isSvg && this.anchor1 && this.anchor2 && this.anchor1.CollectionFreeFormView) { @@ -66,7 +66,7 @@ export class LinkBox extends ViewBoxBaseComponent() { const a = (this.anchor1 ?? this.anchor2)!; const b = (this.anchor2 ?? this.anchor1)!; - const parxf = this.containerViewPath?.().lastElement().ComponentView as CollectionFreeFormView; + const parxf = this.DocumentView?.().containerViewPath?.().lastElement().ComponentView as CollectionFreeFormView; const this_xf = parxf?.screenToFreeformContentsXf ?? Transform.Identity; //this.ScreenToLocalTransform(); const a_invXf = a.screenToViewTransform().inverse(); const b_invXf = b.screenToViewTransform().inverse(); diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 71c1b6a4c..7696a45a0 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -581,6 +581,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent Object.values(this._disposers).forEach(disposer => disposer?.()); isContentActive = () => this._props.tbox._props.isContentActive() || this._props.tbox.isAnyChildContentActive?.(); @@ -207,7 +210,7 @@ export class DashDocViewInternal extends ObservableReactComponent { - let container = this._props.tbox.containerViewPath?.().lastElement(); + let container = this._props.tbox.DocumentView?.().containerViewPath?.().lastElement(); if (container) { const embedding = Doc.MakeEmbedding(container.Document); embedding._type_collection = CollectionViewType.Time; diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index 8cf01b9de..4bc79176e 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -50,7 +50,7 @@ export class PresElementBox extends ViewBoxBaseComponent() { // the presentation view that renders this slide @computed get presBoxView() { - return this.containerViewPath?.().lastElement()?.ComponentView as PresBox; + return this.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as PresBox; } // the presentation view document that renders this slide diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 89e5944f2..01590749e 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -21,11 +21,12 @@ import { DocFocusOptions, DocumentViewProps } from '../nodes/DocumentView'; import { FieldViewProps } from '../nodes/FieldView'; import { LinkInfo } from '../nodes/LinkDocPreview'; import { ObservableReactComponent } from '../ObservableReactComponent'; -import { StyleProp, testDocProps } from '../StyleProvider'; +import { StyleProp } from '../StyleProvider'; import { AnchorMenu } from './AnchorMenu'; import { Annotation } from './Annotation'; import { GPTPopup } from './GPTPopup/GPTPopup'; import './PDFViewer.scss'; +import { PDFBox } from '../nodes/PDFBox'; const _global = (window /* browser */ || global) /* node */ as any; //pdfjsLib.GlobalWorkerOptions.workerSrc = `/assets/pdf.worker.js`; @@ -33,6 +34,7 @@ const _global = (window /* browser */ || global) /* node */ as any; Pdfjs.GlobalWorkerOptions.workerSrc = 'https://unpkg.com/pdfjs-dist@4.0.379/build/pdf.worker.mjs'; interface IViewerProps extends FieldViewProps { + pdfBox: PDFBox; Document: Doc; dataDoc: Doc; layoutDoc: Doc; @@ -313,7 +315,7 @@ export class PDFViewer extends ObservableReactComponent { this._ignoreScroll = false; if (this._scrollTimer) clearTimeout(this._scrollTimer); // wait until a scrolling pause, then create an anchor to audio this._scrollTimer = setTimeout(() => { - DocUtils.MakeLinkToActiveAudio(() => this._props.DocumentView?.().ComponentView?.getAnchor!(true)!, false); + DocUtils.MakeLinkToActiveAudio(() => this._props.pdfBox.getAnchor(true)!, false); this._scrollTimer = undefined; }, 200); } @@ -419,7 +421,7 @@ export class PDFViewer extends ObservableReactComponent { @action createTextAnnotation = (sel: Selection, selRange: Range) => { if (this._mainCont.current) { - this._mainCont.current.style.transform = `rotate(${NumCast(this._props.DocumentView!().screenToContentsTransform().RotateDeg)}deg)`; + this._mainCont.current.style.transform = `rotate(${NumCast(this._props.pdfBox.ScreenToLocalBoxXf().RotateDeg)}deg)`; const boundingRect = this._mainCont.current.getBoundingClientRect(); const clientRects = selRange.getClientRects(); for (let i = 0; i < clientRects.length; i++) { @@ -499,9 +501,8 @@ export class PDFViewer extends ObservableReactComponent { opaqueFilter = () => [...this._props.childFilters(), Utils.noDragDocsFilter, ...(SnappingManager.CanEmbed && this._props.isContentActive() ? [] : [Utils.OpaqueBackgroundFilter])]; childStyleProvider = (doc: Doc | undefined, props: Opt, property: string): any => { if (doc instanceof Doc && property === StyleProp.PointerEvents) { - const docProps = testDocProps(props) ? props : undefined; if (this.inlineTextAnnotations.includes(doc) || this._props.isContentActive() === false) return 'none'; - const isInk = doc.layout_isSvg && !docProps?.LayoutTemplateString; + const isInk = doc.layout_isSvg && !props?.LayoutTemplateString; return isInk ? 'visiblePainted' : 'all'; } return this._props.styleProvider?.(doc, props, property); @@ -589,7 +590,7 @@ export class PDFViewer extends ObservableReactComponent { isNativeScaled={true} annotationLayerScrollTop={NumCast(this._props.Document._layout_scrollTop)} addDocument={this.addDocumentWrapper} - docView={this._props.DocumentView!} + docView={this._props.pdfBox.DocumentView!} finishMarquee={this.finishMarquee} savedAnnotations={this.savedAnnotations} selectionText={this.selectionText} diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx index 188b2eba4..5828313c8 100644 --- a/src/client/views/topbar/TopBar.tsx +++ b/src/client/views/topbar/TopBar.tsx @@ -22,7 +22,7 @@ import { CollectionDockingView } from '../collections/CollectionDockingView'; import { CollectionLinearView } from '../collections/collectionLinear'; import { DashboardView } from '../DashboardView'; import { Colors } from '../global/globalEnums'; -import { DocumentViewInternal } from '../nodes/DocumentView'; +import { DocumentViewInternal, returnEmptyDocViewList } from '../nodes/DocumentView'; import { DefaultStyleProvider } from '../StyleProvider'; import './TopBar.scss'; @@ -100,6 +100,7 @@ export class TopBar extends React.Component {
Date: Tue, 23 Jan 2024 16:11:42 -0500 Subject: reorganization of DocumentView, DocumentViewInternal and FieldView methods and props. fix for selection bug after following a link. migrating to use [DocData] instad of GetProto() --- src/client/documents/Documents.ts | 15 +- src/client/util/CurrentUserUtils.ts | 2 +- src/client/util/DocumentManager.ts | 19 +- src/client/util/DragManager.ts | 7 +- src/client/util/DropConverter.ts | 9 +- src/client/util/HypothesisUtils.ts | 18 +- src/client/util/LinkFollower.ts | 9 +- src/client/util/LinkManager.ts | 12 +- src/client/util/SearchUtil.ts | 155 +-- src/client/util/SettingsManager.tsx | 5 +- src/client/views/DashboardView.tsx | 12 +- src/client/views/DocComponent.tsx | 125 +- src/client/views/DocumentDecorations.tsx | 16 +- src/client/views/InkingStroke.tsx | 14 +- src/client/views/LightboxView.tsx | 2 +- src/client/views/MainView.tsx | 29 +- src/client/views/MarqueeAnnotator.tsx | 9 +- src/client/views/PropertiesView.tsx | 5 +- src/client/views/ScriptBox.tsx | 5 +- src/client/views/SidebarAnnos.tsx | 9 +- src/client/views/StyleProvider.tsx | 12 +- src/client/views/TemplateMenu.tsx | 6 +- .../views/collections/CollectionCalendarView.tsx | 4 +- .../views/collections/CollectionCarousel3DView.tsx | 7 +- .../views/collections/CollectionCarouselView.tsx | 6 +- .../views/collections/CollectionDockingView.tsx | 18 +- src/client/views/collections/CollectionMenu.tsx | 25 +- .../views/collections/CollectionNoteTakingView.tsx | 20 +- .../collections/CollectionStackedTimeline.tsx | 27 +- .../views/collections/CollectionStackingView.tsx | 28 +- src/client/views/collections/CollectionSubView.tsx | 33 +- .../views/collections/CollectionTimeView.tsx | 7 +- .../views/collections/CollectionTreeView.tsx | 2 +- src/client/views/collections/CollectionView.tsx | 7 +- src/client/views/collections/TabDocView.tsx | 10 +- src/client/views/collections/TreeView.tsx | 19 +- .../CollectionFreeFormLinkView.tsx | 6 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 90 +- .../collections/collectionFreeForm/MarqueeView.tsx | 7 +- .../collectionGrid/CollectionGridView.tsx | 6 +- .../CollectionMulticolumnView.tsx | 4 +- .../CollectionMultirowView.tsx | 4 +- .../collectionMulticolumn/MulticolumnResizer.tsx | 6 +- .../collectionMulticolumn/MultirowResizer.tsx | 6 +- .../collectionSchema/CollectionSchemaView.tsx | 10 +- .../collections/collectionSchema/SchemaRowBox.tsx | 10 +- .../collectionSchema/SchemaTableCell.tsx | 5 +- src/client/views/linking/LinkMenu.tsx | 2 +- src/client/views/linking/LinkMenuGroup.tsx | 12 +- src/client/views/newlightbox/NewLightboxView.tsx | 2 +- src/client/views/nodes/AudioBox.tsx | 15 +- .../views/nodes/CollectionFreeFormDocumentView.tsx | 10 +- src/client/views/nodes/ComparisonBox.tsx | 15 +- src/client/views/nodes/DataVizBox/DataVizBox.tsx | 16 +- src/client/views/nodes/DocumentContentsView.tsx | 29 +- src/client/views/nodes/DocumentIcon.tsx | 2 +- src/client/views/nodes/DocumentLinksButton.tsx | 40 +- src/client/views/nodes/DocumentView.tsx | 1214 ++++++++------------ src/client/views/nodes/EquationBox.tsx | 4 +- src/client/views/nodes/FieldView.tsx | 109 +- src/client/views/nodes/FontIconBox/FontIconBox.tsx | 2 +- src/client/views/nodes/FunctionPlotBox.tsx | 2 +- src/client/views/nodes/ImageBox.tsx | 25 +- src/client/views/nodes/KeyValueBox.tsx | 2 +- src/client/views/nodes/KeyValuePair.tsx | 4 +- src/client/views/nodes/LabelBox.tsx | 23 +- src/client/views/nodes/LinkAnchorBox.tsx | 10 +- src/client/views/nodes/LinkBox.tsx | 7 +- src/client/views/nodes/LinkDescriptionPopup.tsx | 31 +- src/client/views/nodes/LinkDocPreview.tsx | 6 +- src/client/views/nodes/MapBox/MapBox.tsx | 16 +- src/client/views/nodes/MapBox/MapBox2.tsx | 12 +- src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx | 5 +- .../views/nodes/MapboxMapBox/MapboxContainer.tsx | 26 +- src/client/views/nodes/PDFBox.tsx | 28 +- .../views/nodes/RecordingBox/RecordingBox.tsx | 12 +- src/client/views/nodes/ScreenshotBox.tsx | 13 +- src/client/views/nodes/ScriptingBox.tsx | 8 +- src/client/views/nodes/VideoBox.tsx | 42 +- src/client/views/nodes/WebBox.tsx | 20 +- src/client/views/nodes/calendarBox/CalendarBox.tsx | 10 +- .../views/nodes/formattedText/DashDocView.tsx | 12 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 45 +- .../views/nodes/importBox/ImportElementBox.tsx | 6 +- src/client/views/nodes/trails/PresBox.tsx | 37 +- src/client/views/nodes/trails/PresElementBox.tsx | 2 +- src/client/views/pdf/PDFViewer.tsx | 9 +- src/client/views/search/SearchBox.tsx | 8 +- src/client/views/selectedDoc/SelectedDocView.tsx | 8 +- src/client/views/webcam/DashWebRTCVideo.tsx | 16 +- src/fields/Doc.ts | 52 +- 91 files changed, 1304 insertions(+), 1527 deletions(-) (limited to 'src/client/views/nodes/CollectionFreeFormDocumentView.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 40cffcf45..bcc016391 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -205,6 +205,7 @@ export class DocumentOptions { type_collection?: COLLt = new CTypeInfo('how collection is rendered'); // sub type of a collection _type_collection?: COLLt = new CTypeInfo('how collection is rendered'); // sub type of a collection title?: STRt = new StrInfo('title of document', true); + title_custom?: BOOLt = new BoolInfo('whether title is a default or has been intentionally set'); caption?: RichTextField; author?: string; // STRt = new StrInfo('creator of document'); // bcz: don't change this. Otherwise, the userDoc's field Infos will have a FieldInfo assigned to its author field which will render it unreadable author_date?: DATEt = new DateInfo('date the document was created', true); @@ -1454,17 +1455,17 @@ export namespace DocUtils { TaskCompletionBox.popupY = showPopup[1] - 33; TaskCompletionBox.taskCompleted = true; - LinkDescriptionPopup.popupX = showPopup[0]; - LinkDescriptionPopup.popupY = showPopup[1]; - LinkDescriptionPopup.descriptionPopup = true; + LinkDescriptionPopup.Instance.popupX = showPopup[0]; + LinkDescriptionPopup.Instance.popupY = showPopup[1]; + LinkDescriptionPopup.Instance.display = true; const rect = document.body.getBoundingClientRect(); - if (LinkDescriptionPopup.popupX + 200 > rect.width) { - LinkDescriptionPopup.popupX -= 190; + if (LinkDescriptionPopup.Instance.popupX + 200 > rect.width) { + LinkDescriptionPopup.Instance.popupX -= 190; TaskCompletionBox.popupX -= 40; } - if (LinkDescriptionPopup.popupY + 100 > rect.height) { - LinkDescriptionPopup.popupY -= 40; + if (LinkDescriptionPopup.Instance.popupY + 100 > rect.height) { + LinkDescriptionPopup.Instance.popupY -= 40; TaskCompletionBox.popupY -= 40; } diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 0b15e3add..c4af20b9b 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -275,7 +275,7 @@ export class CurrentUserUtils { {key: "Map", creator: opts => Docs.Create.MapDocument([], opts), opts: { _width: 800, _height: 600, _layout_fitWidth: true, }}, {key: "Screengrab", creator: Docs.Create.ScreenshotDocument, opts: { _width: 400, _height: 200 }}, {key: "WebCam", creator: opts => Docs.Create.WebCamDocument("", opts), opts: { _width: 400, _height: 200, recording:true, isSystem: true, cloneFieldFilter: new List(["isSystem"]) }}, - {key: "Button", creator: Docs.Create.ButtonDocument, opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, waitForDoubleClickToClick: 'never'}, scripts: {onClick: FollowLinkScript()?.script.originalScript ?? ""}}, + {key: "Button", creator: Docs.Create.ButtonDocument, opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, title_custom: true, waitForDoubleClickToClick: 'never'}, scripts: {onClick: FollowLinkScript()?.script.originalScript ?? ""}}, {key: "Script", creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }}, {key: "DataViz", creator: opts => Docs.Create.DataVizDocument("/users/rz/Downloads/addresses.csv", opts), opts: { _width: 300, _height: 300 }}, {key: "Header", creator: headerTemplate, opts: { _width: 300, _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _layout_autoHeight: true, treeView_HideUnrendered: true}}, diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index e81a99e40..f730d17fe 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -1,6 +1,7 @@ +import { Howl } from 'howler'; import { action, computed, makeObservable, observable, ObservableSet, observe } from 'mobx'; import { Doc, DocListCast, Opt } from '../../fields/Doc'; -import { AclAdmin, AclEdit, Animation } from '../../fields/DocSymbols'; +import { AclAdmin, AclEdit, Animation, DocData } from '../../fields/DocSymbols'; import { Id } from '../../fields/FieldSymbols'; import { listSpec } from '../../fields/Schema'; import { Cast, DocCast, NumCast, StrCast } from '../../fields/Types'; @@ -10,13 +11,13 @@ import { CollectionViewType } from '../documents/DocumentTypes'; import { CollectionDockingView } from '../views/collections/CollectionDockingView'; import { TabDocView } from '../views/collections/TabDocView'; import { LightboxView } from '../views/LightboxView'; -import { DocFocusOptions, DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod } from '../views/nodes/DocumentView'; +import { DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod } from '../views/nodes/DocumentView'; +import { FocusViewOptions } from '../views/nodes/FieldView'; import { KeyValueBox } from '../views/nodes/KeyValueBox'; import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox'; import { PresBox } from '../views/nodes/trails'; import { ScriptingGlobals } from './ScriptingGlobals'; import { SelectionManager } from './SelectionManager'; -import { Howl } from 'howler'; export class DocumentManager { private static _instance: DocumentManager; @@ -135,7 +136,7 @@ export class DocumentManager { }); if (toReturn.length === 0) { DocumentManager.Instance.DocumentViews.forEach(view => { - if (Doc.GetProto(view.Document)?.[Id] === id) { + if (view.Document[DocData]?.[Id] === id) { toReturn.push(view); } }); @@ -236,7 +237,7 @@ export class DocumentManager { // shows a documentView by: // traverses down through the viewPath of contexts to the view: // focusing on each context - public showDocumentView = async (targetDocView: DocumentView, options: DocFocusOptions) => { + public showDocumentView = async (targetDocView: DocumentView, options: FocusViewOptions) => { const docViewPath = [...(targetDocView.containerViewPath?.() ?? []), targetDocView]; let rootContextView = docViewPath.shift(); await (rootContextView && this.focusViewsInPath(rootContextView, options, async () => ({ childDocView: docViewPath.shift(), viewSpec: undefined, focused: false }))); @@ -252,7 +253,7 @@ export class DocumentManager { // and finally restoring the targetDoc to the viewSpec specified by the last document which may either be the targetDoc, or a viewSpec that describes the targetDoc configuration public showDocument = async ( targetDoc: Doc, // document to display - options: DocFocusOptions, // options for how to navigate to target + options: FocusViewOptions, // options for how to navigate to target finished?: (changed: boolean) => void // func called after focusing on target with flag indicating whether anything needed to be done. ) => { Doc.RemoveDocFromList(Doc.MyRecentlyClosed, undefined, targetDoc); @@ -293,7 +294,7 @@ export class DocumentManager { focusViewsInPath = async ( docView: DocumentView, // - options: DocFocusOptions, + options: FocusViewOptions, iterator: (docView: DocumentView) => Promise<{ viewSpec: Opt; childDocView: Opt; focused: boolean }> ) => { let contextView: DocumentView | undefined; // view containing context that contains target @@ -313,7 +314,7 @@ export class DocumentManager { }; @action - restoreDocView(viewSpec: Opt, docView: DocumentView, options: DocFocusOptions, contextView: Opt, targetDoc: Doc) { + restoreDocView(viewSpec: Opt, docView: DocumentView, options: FocusViewOptions, contextView: Opt, targetDoc: Doc) { if (viewSpec && docView) { //if (docView.ComponentView instanceof FormattedTextBox) //viewSpec !== docView.Document && @@ -337,7 +338,7 @@ export class DocumentManager { } } } -export function DocFocusOrOpen(doc: Doc, options: DocFocusOptions = { willZoomCentered: true, zoomScale: 0, openLocation: OpenWhere.toggleRight }, containingDoc?: Doc) { +export function DocFocusOrOpen(doc: Doc, options: FocusViewOptions = { willZoomCentered: true, zoomScale: 0, openLocation: OpenWhere.toggleRight }, containingDoc?: Doc) { const func = () => { const cv = DocumentManager.Instance.getDocumentView(containingDoc); const dv = DocumentManager.Instance.getDocumentView(doc, cv); diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 9ede18ed5..70c40c54e 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -13,6 +13,7 @@ import { ScriptingGlobals } from './ScriptingGlobals'; import { SelectionManager } from './SelectionManager'; import { SnappingManager } from './SnappingManager'; import { UndoManager } from './UndoManager'; +import { DocData } from '../../fields/DocSymbols'; const { default : { contextMenuZindex } } = require('../views/global/globalCssVariables.module.scss'); // prettier-ignore export type dropActionType = 'embed' | 'copy' | 'move' | 'add' | 'same' | 'inSame' | 'proto' | 'none' | undefined; // undefined = move, "same" = move but don't call dropPropertiesToRemove @@ -222,7 +223,7 @@ export namespace DragManager { : docDragData.dropAction === 'add' ? d : docDragData.dropAction === 'proto' - ? Doc.GetProto(d) + ? d[DocData] : docDragData.dropAction === 'copy' ? (await Doc.MakeClone(d)).clone : d @@ -250,9 +251,9 @@ export namespace DragManager { export function StartButtonDrag(eles: HTMLElement[], script: string, title: string, vars: { [name: string]: Field }, params: string[], initialize: (button: Doc) => void, downX: number, downY: number, options?: DragOptions) { const finishDrag = (e: DragCompleteEvent) => { const bd = Docs.Create.ButtonDocument({ toolTip: title, z: 1, _width: 150, _height: 50, title, onClick: ScriptField.MakeScript(script) }); - params.map(p => Object.keys(vars).indexOf(p) !== -1 && (Doc.GetProto(bd)[p] = new PrefetchProxy(vars[p] as Doc))); // copy all "captured" arguments into document parameterfields + params.map(p => Object.keys(vars).indexOf(p) !== -1 && (bd[DocData][p] = new PrefetchProxy(vars[p] as Doc))); // copy all "captured" arguments into document parameterfields initialize?.(bd); - Doc.GetProto(bd)['onClick-paramFieldKeys'] = new List(params); + bd[DocData]['onClick-paramFieldKeys'] = new List(params); e.docDragData && (e.docDragData.droppedDocuments = [bd]); return e; }; diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts index 2c371f28e..f62ec8f83 100644 --- a/src/client/util/DropConverter.ts +++ b/src/client/util/DropConverter.ts @@ -1,4 +1,5 @@ import { Doc, DocListCast, Opt } from '../../fields/Doc'; +import { DocData } from '../../fields/DocSymbols'; import { ObjectField } from '../../fields/ObjectField'; import { RichTextField } from '../../fields/RichTextField'; import { listSpec } from '../../fields/Schema'; @@ -12,7 +13,7 @@ import { DragManager } from './DragManager'; import { ScriptingGlobals } from './ScriptingGlobals'; export function MakeTemplate(doc: Doc, first: boolean = true, rename: Opt = undefined, templateField: string = '') { - if (templateField) Doc.GetProto(doc).title = templateField; /// the title determines which field is being templated + if (templateField) doc[DocData].title = templateField; /// the title determines which field is being templated doc.isTemplateDoc = makeTemplate(doc, first, rename); return doc; } @@ -33,7 +34,7 @@ function makeTemplate(doc: Doc, first: boolean = true, rename: Opt = und let any = false; docs.forEach(d => { if (!StrCast(d.title).startsWith('-')) { - any = Doc.MakeMetadataFieldTemplate(d, Doc.GetProto(layoutDoc)) || any; + any = Doc.MakeMetadataFieldTemplate(d, layoutDoc[DocData]) || any; } else if (d.type === DocumentType.COL || d.data instanceof RichTextField) { any = makeTemplate(d, false) || any; } @@ -41,12 +42,12 @@ function makeTemplate(doc: Doc, first: boolean = true, rename: Opt = und if (first) { if (!docs.length) { // bcz: feels hacky : if the root level document has items, it's not a field template - any = Doc.MakeMetadataFieldTemplate(doc, Doc.GetProto(layoutDoc)) || any; + any = Doc.MakeMetadataFieldTemplate(doc, layoutDoc[DocData]) || any; } } if (layoutDoc[fieldKey] instanceof RichTextField || layoutDoc[fieldKey] instanceof ImageField) { if (!StrCast(layoutDoc.title).startsWith('-')) { - any = Doc.MakeMetadataFieldTemplate(layoutDoc, Doc.GetProto(layoutDoc)); + any = Doc.MakeMetadataFieldTemplate(layoutDoc, layoutDoc[DocData]); } } rename && (doc.title = rename); diff --git a/src/client/util/HypothesisUtils.ts b/src/client/util/HypothesisUtils.ts index f46c2d431..c5f307f44 100644 --- a/src/client/util/HypothesisUtils.ts +++ b/src/client/util/HypothesisUtils.ts @@ -30,15 +30,15 @@ export namespace Hypothesis { if (currentDoc && Cast(currentDoc.data, WebField)?.url.href === uri) return currentDoc; // always check first whether the currently selected doc is the annotation's source, only use Search otherwise const results: Doc[] = []; - await SearchUtil.Search('web', true).then( - action(async (res: SearchUtil.DocSearchResult) => { - const docs = res.docs; - const filteredDocs = docs.filter(doc => doc.author === Doc.CurrentUserEmail && doc.type === DocumentType.WEB && doc.data); - filteredDocs.forEach(doc => { - uri === Cast(doc.data, WebField)?.url.href && results.push(doc); // TODO check visited sites history? - }); - }) - ); + // await SearchUtil.Search('web', true).then( + // action(async (res: SearchUtil.DocSearchResult) => { + // const docs = res.docs; + // const filteredDocs = docs.filter(doc => doc.author === Doc.CurrentUserEmail && doc.type === DocumentType.WEB && doc.data); + // filteredDocs.forEach(doc => { + // uri === Cast(doc.data, WebField)?.url.href && results.push(doc); // TODO check visited sites history? + // }); + // }) + // ); const onScreenResults = results.filter(doc => DocumentManager.Instance.getFirstDocumentView(doc)); return onScreenResults.length ? onScreenResults[0] : results.length ? results[0] : undefined; // prioritize results that are currently on the screen diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts index 2df4d1ca8..20261859c 100644 --- a/src/client/util/LinkFollower.ts +++ b/src/client/util/LinkFollower.ts @@ -1,16 +1,17 @@ -import { action, observable, runInAction } from 'mobx'; +import { action, runInAction } from 'mobx'; import { Doc, DocListCast, Field, FieldResult, Opt } from '../../fields/Doc'; import { ScriptField } from '../../fields/ScriptField'; import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../fields/Types'; import { DocumentType } from '../documents/DocumentTypes'; -import { DocFocusOptions, OpenWhere } from '../views/nodes/DocumentView'; +import { OpenWhere } from '../views/nodes/DocumentView'; +import { FocusViewOptions } from '../views/nodes/FieldView'; import { PresBox } from '../views/nodes/trails'; import { DocumentManager } from './DocumentManager'; import { LinkManager } from './LinkManager'; import { ScriptingGlobals } from './ScriptingGlobals'; import { SelectionManager } from './SelectionManager'; -import { UndoManager } from './UndoManager'; import { SnappingManager } from './SnappingManager'; +import { UndoManager } from './UndoManager'; /* * link doc: * - link_anchor_1: doc @@ -72,7 +73,7 @@ export class LinkFollower { if (target) { const doFollow = (canToggle?: boolean) => { const toggleTarget = canToggle && BoolCast(sourceDoc.followLinkToggle); - const options: DocFocusOptions = { + const options: FocusViewOptions = { playAudio: BoolCast(srcAnchor.followLinkAudio), playMedia: BoolCast(srcAnchor.followLinkVideo), toggleTarget, diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index ccb3c6b98..353f28a92 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -1,7 +1,7 @@ import { action, makeObservable, observable, observe, runInAction } from 'mobx'; import { computedFn } from 'mobx-utils'; import { Doc, DocListCast, DocListCastAsync, Field, Opt } from '../../fields/Doc'; -import { DirectLinks } from '../../fields/DocSymbols'; +import { DirectLinks, DocData } from '../../fields/DocSymbols'; import { FieldLoader } from '../../fields/FieldLoader'; import { List } from '../../fields/List'; import { ProxyField } from '../../fields/Proxy'; @@ -58,8 +58,8 @@ export class LinkManager { link && action(lAnchProtoProtos => { Doc.AddDocToList(Doc.UserDoc(), 'links', link); - lAnchs[0] && Doc.GetProto(lAnchs[0])[DirectLinks].add(link); - lAnchs[1] && Doc.GetProto(lAnchs[1])[DirectLinks].add(link); + lAnchs[0] && lAnchs[0][DocData][DirectLinks].add(link); + lAnchs[1] && lAnchs[1][DocData][DirectLinks].add(link); }) ) ) @@ -74,8 +74,8 @@ export class LinkManager { Promise.all(lAnchs.map(lAnch => PromiseValue(lAnch?.proto as Doc))).then((lAnchProtos: Opt[]) => Promise.all(lAnchProtos.map(lAnchProto => PromiseValue(lAnchProto?.proto as Doc))).then( action(lAnchProtoProtos => { - link && lAnchs[0] && Doc.GetProto(lAnchs[0])[DirectLinks].delete(link); - link && lAnchs[1] && Doc.GetProto(lAnchs[1])[DirectLinks].delete(link); + link && lAnchs[0] && lAnchs[0][DocData][DirectLinks].delete(link); + link && lAnchs[1] && lAnchs[1][DocData][DirectLinks].delete(link); }) ) ) @@ -162,7 +162,7 @@ export class LinkManager { return this.relatedLinker(anchor); } // finds all links that contain the given anchor public getAllDirectLinks(anchor?: Doc): Doc[] { - return anchor ? Array.from(Doc.GetProto(anchor)[DirectLinks]) : []; + return anchor ? Array.from(anchor[DirectLinks]) : []; } // finds all links that contain the given anchor relatedLinker = computedFn(function relatedLinker(this: any, anchor: Doc): Doc[] { diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts index e51770c25..2cc64f415 100644 --- a/src/client/util/SearchUtil.ts +++ b/src/client/util/SearchUtil.ts @@ -1,10 +1,7 @@ -import * as rp from 'request-promise'; -import { DocServer } from '../DocServer'; import { Doc, DocListCast, Field, Opt } from '../../fields/Doc'; import { Id } from '../../fields/FieldSymbols'; -import { Utils } from '../../Utils'; -import { DocumentType } from '../documents/DocumentTypes'; import { StrCast } from '../../fields/Types'; +import { DocumentType } from '../documents/DocumentTypes'; export namespace SearchUtil { export type HighlightingResult = { [id: string]: { [key: string]: string[] } }; @@ -110,154 +107,4 @@ export namespace SearchUtil { depth++; } } - export interface IdSearchResult { - ids: string[]; - lines: string[][]; - numFound: number; - highlighting: HighlightingResult | undefined; - } - - export interface DocSearchResult { - docs: Doc[]; - lines: string[][]; - numFound: number; - highlighting: HighlightingResult | undefined; - } - - export interface SearchParams { - hl?: string; - 'hl.fl'?: string; - start?: number; - rows?: number; - fq?: string; - sort?: string; - allowEmbeddings?: boolean; - onlyEmbeddings?: boolean; - facet?: string; - 'facet.field'?: string; - } - export function Search(query: string, returnDocs: true, options?: SearchParams): Promise; - export function Search(query: string, returnDocs: false, options?: SearchParams): Promise; - export async function Search(query: string, returnDocs: boolean, options: SearchParams = {}) { - query = query || '*'; //If we just have a filter query, search for * as the query - const rpquery = Utils.prepend('/dashsearch'); - let replacedQuery = query.replace(/type_t:([^ )])/g, (substring, arg) => `{!join from=id to=proto_i}*:* AND ${arg}`); - if (options.onlyEmbeddings) { - const header = query.match(/_[atnb]?:/) ? replacedQuery : 'DEFAULT:' + replacedQuery; - replacedQuery = `{!join from=id to=proto_i}* AND ${header}`; - } - //console.log("Q: " + replacedQuery + " fq: " + options.fq); - const gotten = await rp.get(rpquery, { qs: { ...options, q: replacedQuery } }); - const result: IdSearchResult = gotten.startsWith('<') ? { ids: [], docs: [], numFound: 0, lines: [] } : JSON.parse(gotten); - if (!returnDocs) { - return result; - } - - const { ids, highlighting } = result; - - const txtresult = - query !== '*' && - JSON.parse( - await rp.get(Utils.prepend('/textsearch'), { - qs: { ...options, q: query.replace(/^[ \+\?\*\|]*/, '') }, // a leading '+' leads to a server crash since findInFiles doesn't handle regex failures - }) - ); - - const fileids = txtresult ? txtresult.ids : []; - const newIds: string[] = []; - const newLines: string[][] = []; - // bcz: we stopped storing fileUpload id's, so this won't find anything - // if (fileids) { - // await Promise.all( - // fileids.map(async (tr: string, i: number) => { - // const docQuery = 'fileUpload_t:' + tr.substr(0, 7); //If we just have a filter query, search for * as the query - // const docResult = JSON.parse(await rp.get(Utils.prepend('/dashsearch'), { qs: { ...options, q: docQuery } })); - // newIds.push(...docResult.ids); - // newLines.push(...docResult.ids.map((dr: any) => txtresult.lines[i])); - // }) - // ); - // } - - const theDocs: Doc[] = []; - const theLines: string[][] = []; - const textDocMap = await DocServer.GetRefFields(newIds); - const textDocs = newIds.map((id: string) => textDocMap[id]).map(doc => doc as Doc); - for (let i = 0; i < textDocs.length; i++) { - const testDoc = textDocs[i]; - if (testDoc instanceof Doc && testDoc.type !== DocumentType.KVP && theDocs.findIndex(d => Doc.AreProtosEqual(d, testDoc)) === -1) { - theDocs.push(Doc.GetProto(testDoc)); - theLines.push(newLines[i].map(line => line.replace(query, query.toUpperCase()))); - } - } - - const docMap = await DocServer.GetRefFields(ids); - const docs = ids.map((id: string) => docMap[id]).map(doc => doc as Doc); - for (let i = 0; i < ids.length; i++) { - const testDoc = docs[i]; - if (testDoc instanceof Doc && testDoc.type !== DocumentType.KVP && (options.allowEmbeddings || testDoc.proto === undefined || theDocs.findIndex(d => Doc.AreProtosEqual(d, testDoc)) === -1)) { - theDocs.push(testDoc); - theLines.push([]); - } else { - result.numFound--; - } - } - - return { docs: theDocs, numFound: Math.max(0, result.numFound), highlighting, lines: theLines }; - } - - export async function GetEmbeddingsOfDocument(doc: Doc): Promise; - export async function GetEmbeddingsOfDocument(doc: Doc, returnDocs: false): Promise; - export async function GetEmbeddingsOfDocument(doc: Doc, returnDocs = true): Promise { - const proto = Doc.GetProto(doc); - const protoId = proto[Id]; - if (returnDocs) { - return (await Search('', returnDocs, { fq: `proto_i:"${protoId}"`, allowEmbeddings: true })).docs; - } else { - return (await Search('', returnDocs, { fq: `proto_i:"${protoId}"`, allowEmbeddings: true })).ids; - } - // return Search(`{!join from=id to=proto_i}id:${protoId}`, true); - } - - export async function GetViewsOfDocument(doc: Doc): Promise { - const results = await Search('', true, { fq: `proto_i:"${doc[Id]}"` }); - return results.docs; - } - - export async function GetContextsOfDocument(doc: Doc): Promise<{ contexts: Doc[]; embeddingContexts: Doc[] }> { - const docContexts = (await Search('', true, { fq: `data_l:"${doc[Id]}"` })).docs; - const embeddings = await GetEmbeddingsOfDocument(doc, false); - const embeddingContexts = await Promise.all(embeddings.map(doc => Search('', true, { fq: `data_l:"${doc}"` }))); - const contexts = { contexts: docContexts, embeddingContexts: [] as Doc[] }; - embeddingContexts.forEach(result => contexts.embeddingContexts.push(...result.docs)); - return contexts; - } - - export async function GetContextIdsOfDocument(doc: Doc): Promise<{ contexts: string[]; embeddingContexts: string[] }> { - const docContexts = (await Search('', false, { fq: `data_l:"${doc[Id]}"` })).ids; - const embeddings = await GetEmbeddingsOfDocument(doc, false); - const embeddingContexts = await Promise.all(embeddings.map(doc => Search('', false, { fq: `data_l:"${doc}"` }))); - const contexts = { contexts: docContexts, embeddingContexts: [] as string[] }; - embeddingContexts.forEach(result => contexts.embeddingContexts.push(...result.ids)); - return contexts; - } - - export async function GetAllDocs() { - const query = '*'; - const response = await rp.get(Utils.prepend('/dashsearch'), { - qs: { start: 0, rows: 10000, q: query }, - }); - const result: IdSearchResult = JSON.parse(response); - const { ids, numFound, highlighting } = result; - const docMap = await DocServer.GetRefFields(ids); - const docs: Doc[] = []; - for (const id of ids) { - const field = docMap[id]; - if (field instanceof Doc) { - docs.push(field); - } - } - return docs; - // const docs = ids.map((id: string) => docMap[id]).filter((doc: any) => doc instanceof Doc); - // return docs as Doc[]; - } } diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx index 0233c4051..5bf9e5b00 100644 --- a/src/client/util/SettingsManager.tsx +++ b/src/client/util/SettingsManager.tsx @@ -5,13 +5,13 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { BsGoogle } from 'react-icons/bs'; import { FaFillDrip, FaPalette } from 'react-icons/fa'; +import { Utils, addStyleSheet, addStyleSheetRule } from '../../Utils'; import { Doc, Opt } from '../../fields/Doc'; import { DashVersion } from '../../fields/DocSymbols'; import { BoolCast, Cast, NumCast, StrCast } from '../../fields/Types'; -import { addStyleSheet, addStyleSheetRule, Utils } from '../../Utils'; -import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager'; import { DocServer } from '../DocServer'; import { Networking } from '../Network'; +import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager'; import { GestureOverlay } from '../views/GestureOverlay'; import { MainViewModal } from '../views/MainViewModal'; import { FontIconBox } from '../views/nodes/FontIconBox/FontIconBox'; @@ -47,7 +47,6 @@ export class SettingsManager extends React.Component<{}> { @observable activeTab = 'Accounts'; @observable public propertiesWidth: number = 0; - @observable public headerBarHeight: number = 0; constructor(props: {}) { super(props); diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx index 523721b84..472d419fc 100644 --- a/src/client/views/DashboardView.tsx +++ b/src/client/views/DashboardView.tsx @@ -5,7 +5,7 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { FaPlus } from 'react-icons/fa'; import { Doc, DocListCast } from '../../fields/Doc'; -import { AclPrivate, DocAcl } from '../../fields/DocSymbols'; +import { AclPrivate, DocAcl, DocData } from '../../fields/DocSymbols'; import { Id } from '../../fields/FieldSymbols'; import { List } from '../../fields/List'; import { PrefetchProxy } from '../../fields/Proxy'; @@ -64,7 +64,7 @@ export class DashboardView extends ObservableReactComponent<{}> { getDashboards = (whichGroup: DashboardGroup) => { if (whichGroup === DashboardGroup.MyDashboards) { - return DocListCast(Doc.MyDashboards.data).filter(dashboard => Doc.GetProto(dashboard).author === Doc.CurrentUserEmail); + return DocListCast(Doc.MyDashboards.data).filter(dashboard => dashboard[DocData].author === Doc.CurrentUserEmail); } return DocListCast(Doc.MySharedDocs.data_dashboards).filter(doc => doc.dockingConfig); }; @@ -171,7 +171,7 @@ export class DashboardView extends ObservableReactComponent<{}> { } />
- (Doc.GetProto(dashboard).title = val)} /> + (dashboard[DocData].title = val)} /> {this.selectedDashboardGroup === DashboardGroup.SharedDashboards && this.isUnviewedSharedDashboard(dashboard) ?
unviewed
:
}
{ }, ], }; - if (dashboard.dockingConfig && dashboard.dockingConfig !== Doc.GetProto(dashboard).dockingConfig) dashboard.dockingConfig = JSON.stringify(reset); + if (dashboard.dockingConfig && dashboard.dockingConfig !== dashboard[DocData].dockingConfig) dashboard.dockingConfig = JSON.stringify(reset); else Doc.SetInPlace(dashboard, 'dockingConfig', JSON.stringify(reset), true); return reset; }; @@ -391,9 +391,9 @@ export class DashboardView extends ObservableReactComponent<{}> { Doc.ActivePresentation = undefined; }; - public static SetupDashboardCalendars(dashboardDoc: Doc){ + public static SetupDashboardCalendars(dashboardDoc: Doc) { // this section is creating the button document itself === myTrails = new Button - + // create a a list of calendars (as a CalendarCollectionDocument) and store it on the new dashboard const reqdOpts: DocumentOptions = { title: 'My Calendars', diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 6aba4a042..0e5a4f013 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -2,22 +2,62 @@ import { action, computed, makeObservable, observable } from 'mobx'; import * as React from 'react'; import { returnFalse } from '../../Utils'; import { DateField } from '../../fields/DateField'; -import { Doc, DocListCast, Opt } from '../../fields/Doc'; +import { Doc, DocListCast, Field, Opt } from '../../fields/Doc'; import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, DocData } from '../../fields/DocSymbols'; import { List } from '../../fields/List'; import { GetEffectiveAcl, inheritParentAcls } from '../../fields/util'; import { DocumentType } from '../documents/DocumentTypes'; import { DocUtils } from '../documents/Documents'; import { DocumentManager } from '../util/DocumentManager'; -import { Transform } from '../util/Transform'; import { ObservableReactComponent } from './ObservableReactComponent'; import { CollectionFreeFormView } from './collections/collectionFreeForm'; -import { DocumentView } from './nodes/DocumentView'; +import { FieldViewProps, FocusViewOptions } from './nodes/FieldView'; +import { DocumentView, OpenWhere } from './nodes/DocumentView'; +import { PinProps } from './nodes/trails'; +import { RefField } from '../../fields/RefField'; /** - * DocComponent returns a generic React base class used by Doc views (not the 'Box' views that render the contents of Doc views) - * (e.g.,CollectionFreeFormDocumentView, DocumentViewInternal) - * + * Shared interface among all viewBox'es (ie, react classes that render the contents of a Doc) + * Many of these methods only make sense for specific viewBox'es, but they should be written to + * be as general as possible + */ +export interface ViewBoxInterface { + fieldKey?: string; + annotationKey?: string; + updateIcon?: () => void; // updates the icon representation of the document + getAnchor?: (addAsAnnotation: boolean, pinData?: PinProps) => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box) + restoreView?: (viewSpec: Doc) => boolean; + scrollPreview?: (docView: DocumentView, doc: Doc, focusSpeed: number, options: FocusViewOptions) => Opt; // returns the duration of the focus + brushView?: (view: { width: number; height: number; panX: number; panY: number }, transTime: number, holdTime: number) => void; // highlight a region of a view (used by freeforms) + getView?: (doc: Doc, options: FocusViewOptions) => Promise>; // returns a nested DocumentView for the specified doc or undefined + addDocTab?: (doc: Doc, where: OpenWhere) => boolean; // determines how to add a document - used in following links to open the target ina local lightbox + addDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean; // add a document (used only by collections) + select?: (ctrlKey: boolean, shiftKey: boolean) => void; + focus?: (textAnchor: Doc, options: FocusViewOptions) => Opt; + isAnyChildContentActive?: () => boolean; // is any child content of the document active + onClickScriptDisable?: () => 'never' | 'always'; // disable click scripts : never, always, or undefined = only when selected + getKeyFrameEditing?: () => boolean; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown) + setKeyFrameEditing?: (set: boolean) => void; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown) + playFrom?: (time: number, endTime?: number) => void; + Pause?: () => void; // pause a media document (eg, audio/video) + IsPlaying?: () => boolean; // is a media document playing + TogglePause?: (keep?: boolean) => void; // toggle media document playing state + setFocus?: () => void; // sets input focus to the componentView + setData?: (data: Field | Promise) => boolean; + componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element | null; + dragStarting?: (snapToDraggedDoc: boolean, showGroupDragTarget: boolean, visited: Set) => void; + incrementalRendering?: () => void; + infoUI?: () => JSX.Element | null; + 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 }; + ptFromScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number }; + snapPt?: (pt: { X: number; Y: number }, excludeSegs?: number[]) => { nearestPt: { X: number; Y: number }; distance: number }; + search?: (str: string, bwd?: boolean, clear?: boolean) => boolean; +} +/** + * DocComponent returns a React base class used by Doc views with accessors for unpacking he Document,layoutDoc, and dataDoc's + * (note: this should not be used for the 'Box' views that render the contents of Doc views) + * Example derived views: CollectionFreeFormDocumentView, DocumentView, DocumentViewInternal) * */ export interface DocComponentProps { Document: Doc; @@ -26,7 +66,7 @@ export interface DocComponentProps { } export function DocComponent

() { class Component extends ObservableReactComponent> { - constructor(props: any) { + constructor(props: P) { super(props); makeObservable(this); } @@ -41,28 +81,25 @@ export function DocComponent

() { } // This is the data part of a document -- ie, the data that is constant across all views of the document @computed get dataDoc() { - return this._props.Document[DocData]; + return this.Document[DocData]; } } return Component; } /** - * ViewBoxBaseComponent - base class for non-annotatable views that render the interior contents of a DocumentView - * (e.g. InkingStroke, ColorBox) + * base class for non-annotatable views that render the interior contents of a DocumentView. + * this unpacks the Document/layout/data docs as well as the fieldKey being rendered, + * and provides accessors for DocumentView and ScreenToLocalBoxXf + * Example views include: InkingStroke, FontIconBox, EquationBox, etc */ -export interface ViewBoxBaseProps { - Document: Doc; - TemplateDataDocument?: Doc; - DocumentView?: () => DocumentView; - fieldKey: string; - isSelected: () => boolean; - isContentActive: () => boolean | undefined; - ScreenToLocalTransform: () => Transform; - renderDepth: number; -} -export function ViewBoxBaseComponent

() { +export function ViewBoxBaseComponent

() { class Component extends ObservableReactComponent> { + constructor(props: P) { + super(props); + makeObservable(this); + } + ScreenToLocalBoxXf = () => this._props.ScreenToLocalTransform(); get DocumentView() { @@ -89,36 +126,27 @@ export function ViewBoxBaseComponent

() { } /** - * DocAnnotatableComponent - base class for annotatable views that render the interior contents of a DocumentView - * (e.g., PdfBox, ImageBox) - * These views should be interactive (respond to pointerEvents) when their conatainer DocumentView is selected + * base class for annotatable views that render the interior contents of a DocumentView + * This does what ViewBoxBaseComponent does and additionally provides accessor for the + * field key where annotations are stored as well as add/move/remove methods for handing + * annotations. + * This also provides methods to determine when the contents should be interactive + * (respond to pointerEvents) such as when the DocumentView container is selected or a + * peer child of the container is selected + * Example views include: PDFBox, ImageBox, MapBox, etc */ -export interface ViewBoxAnnotatableProps { - Document: Doc; - TemplateDataDocument?: Doc; - DocumentView?: () => DocumentView; - fieldKey: string; - filterAddDocument?: (doc: Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example) - isContentActive: () => boolean | undefined; - select: (isCtrlPressed: boolean) => void; - whenChildContentsActiveChanged: (isActive: boolean) => void; - ScreenToLocalTransform: () => Transform; - isSelected: () => boolean; - renderDepth: number; - isAnnotationOverlay?: boolean; -} -export function ViewBoxAnnotatableComponent

() { +export function ViewBoxAnnotatableComponent

() { class Component extends ObservableReactComponent> { - constructor(props: any) { + @observable _annotationKeySuffix = () => 'annotations'; + @observable _isAnyChildContentActive = false; + + constructor(props: P) { super(props); makeObservable(this); } ScreenToLocalBoxXf = () => this._props.ScreenToLocalTransform(); - @observable _annotationKeySuffix = () => 'annotations'; - @observable _isAnyChildContentActive = false; - get DocumentView() { return this._props.DocumentView; } @@ -139,9 +167,6 @@ export function ViewBoxAnnotatableComponent

() @computed get fieldKey() { return this._props.fieldKey; } - - isAnyChildContentActive = () => this._isAnyChildContentActive; - @computed public get annotationKey() { return this.fieldKey + (this._annotationKeySuffix() ? '_' + this._annotationKeySuffix() : ''); } @@ -162,7 +187,7 @@ export function ViewBoxAnnotatableComponent

() toRemove.forEach(doc => { leavePushpin && DocUtils.LeavePushpin(doc, annotationKey ?? this.annotationKey); Doc.RemoveDocFromList(targetDataDoc, annotationKey ?? this.annotationKey, doc); - Doc.RemoveDocFromList(Doc.GetProto(doc), 'proto_embeddings', doc); + Doc.RemoveDocFromList(doc[DocData], 'proto_embeddings', doc); doc.embedContainer = undefined; if (recent) { doc.type !== DocumentType.LOADING && Doc.AddDocToList(recent, 'data', doc, undefined, true, true); @@ -210,8 +235,8 @@ export function ViewBoxAnnotatableComponent

() if ([AclAugment, AclEdit, AclAdmin].includes(effectiveAcl)) { added.forEach(doc => { doc._dragOnlyWithinContainer = undefined; - if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.Document; - else Doc.GetProto(doc).annotationOn = undefined; + if (annotationKey ?? this._annotationKeySuffix()) doc[DocData].annotationOn = this.Document; + else doc[DocData].annotationOn = undefined; Doc.SetContainer(doc, this.Document); inheritParentAcls(targetDataDoc, doc, true); }); @@ -225,6 +250,8 @@ export function ViewBoxAnnotatableComponent

() return true; }; + isAnyChildContentActive = () => this._isAnyChildContentActive; + whenChildContentsActiveChanged = action((isActive: boolean) => this._props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive))); } return Component; diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 8db972ef0..5ef62b2c5 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -8,7 +8,7 @@ import { FaUndo } from 'react-icons/fa'; import { Utils, emptyFunction, lightOrDark, numberValue, returnFalse, setupMoveUpEvents } from '../../Utils'; import { DateField } from '../../fields/DateField'; import { Doc, DocListCast, Field, HierarchyMapping, ReverseHierarchyMap } from '../../fields/Doc'; -import { AclAdmin, AclAugment, AclEdit } from '../../fields/DocSymbols'; +import { AclAdmin, AclAugment, AclEdit, DocData } from '../../fields/DocSymbols'; import { InkField } from '../../fields/InkField'; import { RichTextField } from '../../fields/RichTextField'; import { ScriptField } from '../../fields/ScriptField'; @@ -101,7 +101,7 @@ export class DocumentDecorations extends ObservableReactComponent dv._props.renderDepth > 0) - .map(dv => dv.getBounds()) + .map(dv => dv.getBounds) .reduce((bounds, rect) => !rect ? bounds : { x: Math.min(rect.left, bounds.x), y: Math.min(rect.top, bounds.y), @@ -203,7 +203,7 @@ export class DocumentDecorations extends ObservableReactComponent(); SelectionManager.Views.forEach(v => containers.add(DocCast(v.Document.embedContainer))); if (containers.size > 1) return false; - const { left, top } = dragDocView.getBounds() || { left: 0, top: 0 }; + const { left, top } = dragDocView.getBounds || { left: 0, top: 0 }; const dragData = new DragManager.DocumentDragData( SelectionManager.Views.map(dv => dv.Document), dragDocView._props.dropAction @@ -541,17 +541,17 @@ export class DocumentDecorations extends ObservableReactComponent ({ oldbds, inkPts: Cast(oldbds.doc.data, InkField)?.inkData || [] })) .forEach(({ oldbds: { doc, x, y, width, height }, inkPts }) => { - Doc.GetProto(doc).data = new InkField(inkPts.map( + doc[DocData].data = new InkField(inkPts.map( (ipt) => ({// (new x — oldx) + newWidth * (oldxpoint /oldWidth) X: NumCast(doc.x) - x + (NumCast(doc.width) * ipt.X) / width, Y: NumCast(doc.y) - y + (NumCast(doc.height) * ipt.Y) / height, diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 11b05352d..92644d3c5 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -34,21 +34,19 @@ import { InteractionUtils } from '../util/InteractionUtils'; import { SnappingManager } from '../util/SnappingManager'; import { UndoManager } from '../util/UndoManager'; import { ContextMenu } from './ContextMenu'; -import { ViewBoxBaseComponent, ViewBoxBaseProps } from './DocComponent'; +import { ViewBoxBaseComponent, ViewBoxInterface } from './DocComponent'; import { Colors } from './global/globalEnums'; import { InkControlPtHandles, InkEndPtHandles } from './InkControlPtHandles'; import './InkStroke.scss'; import { InkStrokeProperties } from './InkStrokeProperties'; import { InkTangentHandles } from './InkTangentHandles'; -import { DocComponentView } from './nodes/DocumentView'; import { FieldView, FieldViewProps } from './nodes/FieldView'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; import { PinProps, PresBox } from './nodes/trails'; import { StyleProp } from './StyleProvider'; -import { Transform } from '../util/Transform'; const { default: { INK_MASK_SIZE } } = require('./global/globalCssVariables.module.scss'); // prettier-ignore @observer -export class InkingStroke extends ViewBoxBaseComponent() { +export class InkingStroke extends ViewBoxBaseComponent() implements ViewBoxInterface { static readonly MaskDim = INK_MASK_SIZE; // choose a really big number to make sure mask fits over container (which in theory can be arbitrarily big) public static LayoutString(fieldStr: string) { return FieldView.LayoutString(InkingStroke, fieldStr); @@ -64,7 +62,7 @@ export class InkingStroke extends ViewBoxBaseComponent this._props.isSelected(), // react to stroke being deselected by turning off ink handles selected => !selected && (InkStrokeProperties.Instance._controlButton = false) @@ -332,8 +330,8 @@ export class InkingStroke extends ViewBoxBaseComponent (this._subContentView = doc); + _subContentView: ViewBoxInterface | undefined; + setSubContentView = (doc: ViewBoxInterface) => (this._subContentView = doc); @computed get fillColor() { const isInkMask = BoolCast(this.layoutDoc.stroke_isInkMask); return isInkMask ? DashColor(StrCast(this.layoutDoc.fillColor, 'transparent')).blacken(0).rgb().toString() : this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FillColor) ?? 'transparent'; @@ -462,7 +460,7 @@ export class InkingStroke extends ViewBoxBaseComponent { addDocTab={this.AddDocTab} pinToPres={TabDocView.PinDoc} bringToFront={emptyFunction} - onBrowseClick={DocumentView.exploreMode} + onBrowseClickScript={DocumentView.exploreMode} focus={emptyFunction} /> diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index d700ea020..9061e98d4 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -15,6 +15,7 @@ import { DocServer } from '../DocServer'; import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager'; import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; import { Docs } from '../documents/Documents'; +import { CalendarManager } from '../util/CalendarManager'; import { CaptureManager } from '../util/CaptureManager'; import { DocumentManager } from '../util/DocumentManager'; import { DragManager } from '../util/DragManager'; @@ -31,7 +32,6 @@ import { SnappingManager } from '../util/SnappingManager'; import { Transform } from '../util/Transform'; import { ReportManager } from '../util/reportManager/ReportManager'; import { ComponentDecorations } from './ComponentDecorations'; -import { SchemaCSVPopUp } from './nodes/DataVizBox/SchemaCSVPopUp'; import { ContextMenu } from './ContextMenu'; import { DashboardView } from './DashboardView'; import { DictationOverlay } from './DictationOverlay'; @@ -55,11 +55,13 @@ import { MarqueeOptionsMenu } from './collections/collectionFreeForm/MarqueeOpti import { CollectionLinearView } from './collections/collectionLinear'; import { LinkMenu } from './linking/LinkMenu'; import { AudioBox } from './nodes/AudioBox'; +import { SchemaCSVPopUp } from './nodes/DataVizBox/SchemaCSVPopUp'; import { DocButtonState } from './nodes/DocumentLinksButton'; import { DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod, returnEmptyDocViewList } from './nodes/DocumentView'; import { ImageBox } from './nodes/ImageBox'; import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup'; import { LinkDocPreview, LinkInfo } from './nodes/LinkDocPreview'; +import { DirectionsAnchorMenu } from './nodes/MapBox/DirectionsAnchorMenu'; import { MapAnchorMenu } from './nodes/MapBox/MapAnchorMenu'; import { TaskCompletionBox } from './nodes/TaskCompletedBox'; import { DashFieldViewMenu } from './nodes/formattedText/DashFieldView'; @@ -70,9 +72,8 @@ import { PresBox } from './nodes/trails'; import { AnchorMenu } from './pdf/AnchorMenu'; import { GPTPopup } from './pdf/GPTPopup/GPTPopup'; import { TopBar } from './topbar/TopBar'; +import { DocData } from '../../fields/DocSymbols'; const { default: { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } } = require('./global/globalCssVariables.module.scss'); // prettier-ignore -import { DirectionsAnchorMenu } from './nodes/MapBox/DirectionsAnchorMenu'; -import { CalendarManager } from '../util/CalendarManager'; const _global = (window /* browser */ || global) /* node */ as any; @observer @@ -130,9 +131,19 @@ export class MainView extends ObservableReactComponent<{}> { @computed public get mainFreeform(): Opt { return (docs => (docs?.length > 1 ? docs[1] : undefined))(DocListCast(this.mainContainer!.data)); } + @observable public headerBarHeight: number = 0; + headerBarHeightFunc = () => this.headerBarHeight; + @action + toggleTopBar = () => { + if (this.headerBarHeight > 0) { + this.headerBarHeight = 0; + } else { + this.headerBarHeight = 60; + } + }; headerBarDocWidth = () => this.mainDocViewWidth(); - headerBarDocHeight = () => (this._hideUI ? 0 : SettingsManager.Instance?.headerBarHeight ?? 0); + headerBarDocHeight = () => (this._hideUI ? 0 : this.headerBarHeight ?? 0); topMenuHeight = () => (this._hideUI ? 0 : 35); topMenuWidth = returnZero; // value is ignored ... leftMenuWidth = () => (this._hideUI ? 0 : Number(LEFT_MENU_WIDTH.replace('px', ''))); @@ -630,9 +641,11 @@ export class MainView extends ObservableReactComponent<{}> { ); } @computed get mainDocView() { + const headerBar = this._hideUI || !this.headerBarDocHeight?.() ? null : this.headerBarDocView; + console.log('Header = ' + this._hideUI + ' ' + this.headerBarDocHeight?.() + ' ' + headerBar); return ( <> - {this._hideUI || !this.headerBarDocHeight?.() ? null : this.headerBarDocView} + {headerBar} { @action selectMenu = (button: Doc) => { - const title = StrCast(Doc.GetProto(button).title); + const title = StrCast(button[DocData].title); const willOpen = !this._leftMenuFlyoutWidth || this._panelContent !== title; this.closeFlyout(); if (willOpen) { @@ -998,7 +1011,7 @@ export class MainView extends ObservableReactComponent<{}> { {this._hideUI ? null : } - {LinkDescriptionPopup.descriptionPopup ? : null} + {DocButtonState.Instance.LinkEditorDocView ? (DocButtonState.Instance.LinkEditorDocView = undefined))} docView={DocButtonState.Instance.LinkEditorDocView} /> : null} {LinkInfo.Instance?.LinkInfo ? : null} @@ -1008,7 +1021,7 @@ export class MainView extends ObservableReactComponent<{}> { default: case 'dashboard': return (<>

- +
{this.mainDashboardArea} ); diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index d0516336c..a4303c3aa 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -118,7 +118,7 @@ export class MarqueeAnnotator extends ObservableReactComponent { if (!e.aborted && e.linkDocument) { - Doc.GetProto(e.linkDocument).link_relationship = 'cropped image'; - Doc.GetProto(e.linkDocument).title = 'crop: ' + this.props.Document.title; - Doc.GetProto(e.linkDocument).link_displayLine = false; + const linkDocData = e.linkDocument[DocData]; + linkDocData.link_relationship = 'cropped image'; + linkDocData.title = 'crop: ' + this.props.Document.title; + linkDocData.link_displayLine = false; } }, }); diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 13057ffbf..952c8f0b4 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -37,7 +37,8 @@ import { PropertiesDocContextSelector } from './PropertiesDocContextSelector'; import { PropertiesSection } from './PropertiesSection'; import './PropertiesView.scss'; import { DefaultStyleProvider } from './StyleProvider'; -import { DocumentView, OpenWhere, StyleProviderFunc } from './nodes/DocumentView'; +import { DocumentView, OpenWhere } from './nodes/DocumentView'; +import { StyleProviderFuncType } from './nodes/FieldView'; import { KeyValueBox } from './nodes/KeyValueBox'; import { PresBox, PresEffect, PresEffectDirection } from './nodes/trails'; const _global = (window /* browser */ || global) /* node */ as any; @@ -45,7 +46,7 @@ const _global = (window /* browser */ || global) /* node */ as any; interface PropertiesViewProps { width: number; height: number; - styleProvider?: StyleProviderFunc; + styleProvider?: StyleProviderFuncType; addDocTab: (doc: Doc, where: OpenWhere) => boolean; } diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx index 086f40e96..623201ed1 100644 --- a/src/client/views/ScriptBox.tsx +++ b/src/client/views/ScriptBox.tsx @@ -11,6 +11,7 @@ import { EditableView } from './EditableView'; import { DocumentIconContainer } from './nodes/DocumentIcon'; import { OverlayView } from './OverlayView'; import './ScriptBox.scss'; +import { DocData } from '../../fields/DocSymbols'; export interface ScriptBoxProps { onSave: (text: string, onError: (error: string) => void) => void; @@ -102,7 +103,7 @@ export class ScriptBox extends React.Component { onCancel={overlayDisposer} onSave={(text, onError) => { if (!text) { - Doc.GetProto(doc)[fieldKey] = undefined; + doc[DocData][fieldKey] = undefined; } else { const script = CompileScript(text, { params: { this: Doc.name, ...contextParams }, @@ -125,7 +126,7 @@ export class ScriptBox extends React.Component { div.innerHTML = 'button'; params.length && DragManager.StartButtonDrag([div], text, doc.title + '-instance', {}, params, (button: Doc) => {}, clientX, clientY); - Doc.GetProto(doc)[fieldKey] = new ScriptField(script); + doc[DocData][fieldKey] = new ScriptField(script); overlayDisposer(); } }} diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx index 0f4a4260c..1e9272e93 100644 --- a/src/client/views/SidebarAnnos.tsx +++ b/src/client/views/SidebarAnnos.tsx @@ -18,6 +18,7 @@ import { StyleProp } from './StyleProvider'; import { CollectionStackingView } from './collections/CollectionStackingView'; import { FieldViewProps } from './nodes/FieldView'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; +import { DocData } from '../../fields/DocSymbols'; interface ExtraProps { fieldKey: string; @@ -90,7 +91,7 @@ export class SidebarAnnos extends ObservableReactComponent { const key = data.split(':')[0]; const val = Field.Copy(this.allMetadata.get(key)); - Doc.GetProto(target)[key] = val; + target[DocData][key] = val; return { type: 'dashField', attrs: { fieldKey: key, docId: '', hideKey: false, editable: true }, @@ -98,7 +99,7 @@ export class SidebarAnnos extends ObservableReactComponent , props: Opt, property: string): any { +export function DefaultStyleProvider(doc: Opt, props: Opt, property: string): any { const remoteDocHeader = 'author;author_date;noMargin'; const isCaption = property.includes(':caption'); const isAnchor = property.includes(':anchor'); const isNonTransparent = property.includes(':nonTransparent'); const isNonTransparentLevel = isNonTransparent ? Number(property.replace(/.*:nonTransparent([0-9]+).*/, '$1')) : 0; // property.includes(':nonTransparent'); - const isContent = property.includes(':content'); const isAnnotated = property.includes(':annotated'); const isInk = () => doc?._layout_isSvg && !props?.LayoutTemplateString; const isOpen = property.includes(':open'); @@ -125,7 +125,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt, case StyleProp.FontFamily: return StrCast(doc?.[fieldKey + 'fontFamily'], StrCast(doc?._text_fontFamily, StrCast(Doc.UserDoc().fontFamily))); case StyleProp.FontWeight: return StrCast(doc?.[fieldKey + 'fontWeight'], StrCast(doc?._text_fontWeight, StrCast(Doc.UserDoc().fontWeight))); case StyleProp.FillColor: return StrCast(doc?._fillColor, StrCast(doc?.fillColor, 'transparent')); - case StyleProp.ShowCaption:return doc?._type_collection === CollectionViewType.Carousel ? undefined: StrCast(doc?._layout_showCaption); + case StyleProp.ShowCaption:return props?.hideCaptions || doc?._type_collection === CollectionViewType.Carousel ? undefined: StrCast(doc?._layout_showCaption); case StyleProp.TitleHeight:return (props?.ScreenToLocalTransform().Scale ?? 1) * NumCast(Doc.UserDoc().headerHeight,30); case StyleProp.ShowTitle: return ( @@ -244,7 +244,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt, ? `gray ${StrCast(doc.layout_boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (props?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent : lockedPosition() ? undefined // if it's a background & has a cluster color, make the shadow spread really big - : StrCast(doc.layout_fieldKey).includes('_inline') // if doc is an inline document in a text box + : fieldKey.includes('_inline') // if doc is an inline document in a text box ? `${Colors.DARK_GRAY} ${StrCast(doc.layout_boxShadow, '0vw 0vw 0.1vw')}` : DocCast(doc.embedContainer)?.type=== DocumentType.RTF // if doc is embedded in a text document (but not an inline) ? `${Colors.DARK_GRAY} ${StrCast(doc.layout_boxShadow, '0.2vw 0.2vw 0.8vw')}` @@ -316,11 +316,11 @@ export function DefaultStyleProvider(doc: Opt, props: Opt, }; const audio = () => { const audioAnnoState = (doc: Doc) => StrCast(doc.audioAnnoState, 'stopped'); - const audioAnnosCount = (doc: Doc) => StrListCast(doc[Doc.LayoutFieldKey(doc) + '_audioAnnotations']).length; + const audioAnnosCount = (doc: Doc) => StrListCast(doc[fieldKey + 'audioAnnotations']).length; if (!doc || props?.renderDepth === -1 || !audioAnnosCount(doc)) return null; const audioIconColors: { [key: string]: string } = { recording: 'red', playing: 'green', stopped: 'blue' }; return ( - {StrListCast(doc[Doc.LayoutFieldKey(doc) + '_audioAnnotations_text']).lastElement()}
}> + {StrListCast(doc[fieldKey + 'audioAnnotations_text']).lastElement()}
}>
DocumentManager.Instance.getFirstDocumentView(doc)?.playAnnotation()}>
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index e5154efcb..ba7120b93 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -11,9 +11,10 @@ import { ScriptingGlobals } from '../util/ScriptingGlobals'; import { Transform } from '../util/Transform'; import { undoBatch } from '../util/UndoManager'; import { CollectionTreeView } from './collections/CollectionTreeView'; -import { DocumentView } from './nodes/DocumentView'; +import { DocumentView, returnEmptyDocViewList } from './nodes/DocumentView'; import { DefaultStyleProvider } from './StyleProvider'; import './TemplateMenu.scss'; +import { DocData } from '../../fields/DocSymbols'; @observer class TemplateToggle extends React.Component<{ template: string; checked: boolean; toggle: (event: React.ChangeEvent, template: string) => void }> { @@ -79,7 +80,7 @@ export class TemplateMenu extends React.Component { }; componentDidMount() { !this._addedKeys && (this._addedKeys = new ObservableSet()); - [...Array.from(Object.keys(Doc.GetProto(this.props.docViews[0].Document))), ...Array.from(Object.keys(this.props.docViews[0].Document))] + [...Array.from(Object.keys(this.props.docViews[0].Document[DocData])), ...Array.from(Object.keys(this.props.docViews[0].Document))] .filter(key => key.startsWith('layout_')) .map(key => runInAction(() => this._addedKeys.add(key.replace('layout_', '')))); } @@ -113,6 +114,7 @@ export class TemplateMenu extends React.Component { {templateMenu} this._props .ScreenToLocalTransform() - .translate(Doc.NativeWidth(this._props.Document), 0) + .translate(Doc.NativeWidth(this.Document), 0) .scale(this._props.NativeDimScaling?.() || 1); get calendarsKey() { @@ -74,7 +74,7 @@ export class CollectionCalendarView extends CollectionSubView() {
{ + focus = (anchor: Doc, options: FocusViewOptions) => { const docs = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]); if (anchor.type !== DocumentType.CONFIG && !docs.includes(anchor)) return; options.didMove = true; @@ -70,7 +71,7 @@ export class CollectionCarousel3DView extends CollectionSubView() { NativeWidth={returnZero} NativeHeight={returnZero} layout_fitWidth={undefined} - onDoubleClick={this.onChildDoubleClick} + onDoubleClickScript={this.onChildDoubleClick} renderDepth={this._props.renderDepth + 1} LayoutTemplate={this._props.childLayoutTemplate} LayoutTemplateString={this._props.childLayoutString} diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index 208fc45b5..79f5baabe 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -66,9 +66,9 @@ export class CollectionCarouselView extends CollectionSubView() { NativeWidth={returnZero} NativeHeight={returnZero} layout_fitWidth={undefined} - setContentView={undefined} - onDoubleClick={this.onContentDoubleClick} - onClick={this.onContentClick} + setContentViewBox={undefined} + onDoubleClickScript={this.onContentDoubleClick} + onClickScript={this.onContentClick} isDocumentActive={this._props.childDocumentsActive?.() ? this._props.isDocumentActive : this._props.isContentActive} isContentActive={this._props.childContentsActive ?? this._props.isContentActive() === false ? returnFalse : emptyFunction} hideCaptions={!!carouselShowsCaptions} // hide captions if the carousel is configured to show the captions diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 1f867fc44..87973fd81 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom/client'; import * as GoldenLayout from '../../../client/goldenLayout'; import { Doc, DocListCast, Opt } from '../../../fields/Doc'; -import { AclAdmin, AclEdit } from '../../../fields/DocSymbols'; +import { AclAdmin, AclEdit, DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; import { List } from '../../../fields/List'; @@ -277,8 +277,8 @@ export class CollectionDockingView extends CollectionSubView() { } setupGoldenLayout = async () => { if (this._unmounting) return; - //const config = StrCast(this._props.Document.dockingConfig, JSON.stringify(DashboardView.resetDashboard(this._props.Document))); - const config = StrCast(this._props.Document.dockingConfig); + //const config = StrCast(this.Document.dockingConfig, JSON.stringify(DashboardView.resetDashboard(this.Document))); + const config = StrCast(this.Document.dockingConfig); if (config) { const matches = config.match(/\"documentId\":\"[a-z0-9-]+\"/g); const docids = matches?.map(m => m.replace('"documentId":"', '').replace('"', '')) ?? []; @@ -322,7 +322,7 @@ export class CollectionDockingView extends CollectionSubView() { ); new _global.ResizeObserver(this.onResize).observe(this._containerRef.current); this._reactionDisposer = reaction( - () => StrCast(this._props.Document.dockingConfig), + () => StrCast(this.Document.dockingConfig), config => { if (!this._goldenLayout || this._ignoreStateChange !== config) { // bcz: TODO! really need to diff config with ignoreStateChange and modify the current goldenLayout instead of building a new one. @@ -381,7 +381,7 @@ export class CollectionDockingView extends CollectionSubView() { .map(id => DocServer.GetCachedRefField(id)) .filter(f => f) .map(f => f as Doc); - const changesMade = this._props.Document.dockingConfig !== json; + const changesMade = this.Document.dockingConfig !== json; if (changesMade) { if (![AclAdmin, AclEdit].includes(GetEffectiveAcl(this.dataDoc))) { this.layoutDoc.dockingConfig = json; @@ -449,7 +449,7 @@ export class CollectionDockingView extends CollectionSubView() { if (clone) { const cloned = await Doc.MakeClone(doc); Array.from(cloned.map.entries()).map(entry => (json = json.replace(entry[0], entry[1][Id]))); - Doc.GetProto(cloned.clone).dockingConfig = json; + cloned.clone[DocData].dockingConfig = json; return DashboardView.openDashboard(cloned.clone); } const matches = json.match(/\"documentId\":\"[a-z0-9-]+\"/g); @@ -463,7 +463,7 @@ export class CollectionDockingView extends CollectionSubView() { const newtab = origtabdocs.length ? Doc.MakeCopy(origtab, true, undefined, true) : Doc.MakeEmbedding(origtab); const newtabdocs = origtabdocs.map(origtabdoc => Doc.MakeEmbedding(origtabdoc)); if (newtabdocs.length) { - Doc.GetProto(newtab).data = new List(newtabdocs); + newtab[DocData].data = new List(newtabdocs); newtabdocs.forEach(ntab => Doc.SetContainer(ntab, newtab)); } json = json.replace(origtab[Id], newtab[Id]); @@ -479,7 +479,7 @@ export class CollectionDockingView extends CollectionSubView() { stateChanged = () => { this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig()); const json = JSON.stringify(this._goldenLayout.toConfig()); - const changesMade = this._props.Document.dockingConfig !== json; + const changesMade = this.Document.dockingConfig !== json; return changesMade; }; @@ -490,7 +490,7 @@ export class CollectionDockingView extends CollectionSubView() { // if you close a tab that is not embedded somewhere else (an embedded Doc can be opened simultaneously in a tab), then add the tab to recently closed if (tab.DashDoc.embedContainer === this.Document) tab.DashDoc.embedContainer = undefined; if (!tab.DashDoc.embedContainer) Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', tab.DashDoc, undefined, true, true); - Doc.RemoveDocFromList(Doc.GetProto(tab.DashDoc), 'proto_embeddings', tab.DashDoc); + Doc.RemoveDocFromList(tab.DashDoc[DocData], 'proto_embeddings', tab.DashDoc); } if (CollectionDockingView.Instance) { const dview = CollectionDockingView.Instance.Document; diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index a54a5ec81..2dad377e0 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -19,14 +19,17 @@ import { undoBatch } from '../../util/UndoManager'; import { AntimodeMenu } from '../AntimodeMenu'; import { EditableView } from '../EditableView'; import { MainView } from '../MainView'; -import { DocumentView, DocumentViewInternal } from '../nodes/DocumentView'; +import { DocumentView, DocumentViewInternal, returnEmptyDocViewList } from '../nodes/DocumentView'; import { DefaultStyleProvider } from '../StyleProvider'; import { CollectionLinearView } from './collectionLinear'; import './CollectionMenu.scss'; +import { DocData } from '../../../fields/DocSymbols'; interface CollectionMenuProps { panelHeight: () => number; panelWidth: () => number; + toggleTopBar: () => void; + topBarHeight: () => number; } @observer @@ -65,15 +68,6 @@ export class CollectionMenu extends AntimodeMenu { } }; - @action - toggleTopBar = () => { - if (SettingsManager.Instance.headerBarHeight > 0) { - SettingsManager.Instance.headerBarHeight = 0; - } else { - SettingsManager.Instance.headerBarHeight = 60; - } - }; - @action toggleProperties = () => { if (MainView.Instance.propertiesWidth() > 0) { @@ -95,6 +89,7 @@ export class CollectionMenu extends AntimodeMenu {
{ } render() { - const headerIcon = SettingsManager.Instance.headerBarHeight > 0 ? 'angle-double-up' : 'angle-double-down'; - const headerTitle = SettingsManager.Instance.headerBarHeight > 0 ? 'Close Header Bar' : 'Open Header Bar'; + const headerIcon = this.props.topBarHeight() > 0 ? 'angle-double-up' : 'angle-double-down'; + const headerTitle = this.props.topBarHeight() > 0 ? 'Close Header Bar' : 'Open Header Bar'; const propIcon = SettingsManager.Instance.propertiesWidth > 0 ? 'angle-double-right' : 'angle-double-left'; const propTitle = SettingsManager.Instance.propertiesWidth > 0 ? 'Close Properties' : 'Open Properties'; @@ -134,8 +129,8 @@ export class CollectionMenu extends AntimodeMenu { toggleType={ToggleType.BUTTON} type={Type.PRIM} color={SettingsManager.userColor} - onClick={this.toggleTopBar} - toggleStatus={SettingsManager.Instance.headerBarHeight > 0} + onClick={this.props.toggleTopBar} + toggleStatus={this.props.topBarHeight() > 0} icon={} tooltip={headerTitle} /> @@ -219,7 +214,7 @@ export class CollectionViewBaseChrome extends React.Component (Doc.GetProto(this.target).data = new List(source))), + immediate: undoBatch((source: Doc[]) => (this.target[DocData].data = new List(source))), initialize: emptyFunction, }; _onClickCommand = { diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx index 363db8850..e6ce5baab 100644 --- a/src/client/views/collections/CollectionNoteTakingView.tsx +++ b/src/client/views/collections/CollectionNoteTakingView.tsx @@ -1,6 +1,6 @@ -import * as React from 'react'; import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; +import * as React from 'react'; import { Doc, Field, Opt } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { Copy, Id } from '../../../fields/FieldSymbols'; @@ -18,8 +18,8 @@ import { undoBatch } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { LightboxView } from '../LightboxView'; -import { DocFocusOptions, DocumentView, DocumentViewProps } from '../nodes/DocumentView'; -import { FieldViewProps } from '../nodes/FieldView'; +import { DocumentView } from '../nodes/DocumentView'; +import { FocusViewOptions, FieldViewProps } from '../nodes/FieldView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { StyleProp } from '../StyleProvider'; import './CollectionNoteTakingView.scss'; @@ -51,7 +51,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { } @computed get chromeHidden() { - return BoolCast(this.layoutDoc.chromeHidden) || this._props.onBrowseClick?.() ? true : false; + return BoolCast(this.layoutDoc.chromeHidden) || this._props.onBrowseClickScript?.() ? true : false; } // columnHeaders returns the list of SchemaHeaderFields currently being used by the layout doc to render the columns @computed get colHeaderData() { @@ -189,7 +189,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { }; // let's dive in and get the actual document we want to drag/move around - focusDocument = (doc: Doc, options: DocFocusOptions) => { + focusDocument = (doc: Doc, options: FocusViewOptions) => { Doc.BrushDoc(doc); const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]); if (found) { @@ -254,9 +254,9 @@ export class CollectionNoteTakingView extends CollectionSubView() { rootSelected={this.rootSelected} layout_showTitle={this._props.childlayout_showTitle} dragAction={StrCast(this.layoutDoc.childDragAction) as dropActionType} - onClick={this.onChildClickHandler} - onBrowseClick={this._props.onBrowseClick} - onDoubleClick={this.onChildDoubleClickHandler} + onClickScript={this.onChildClickHandler} + onBrowseClickScript={this._props.onBrowseClickScript} + onDoubleClickScript={this.onChildDoubleClickHandler} ScreenToLocalTransform={noteTakingDocTransform} focus={this.focusDocument} childFilters={this.childDocFilters} @@ -410,7 +410,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { if ((e.ctrlKey || fieldProps.Document._createDocOnCR) && ['Enter'].includes(e.key)) { e.stopPropagation?.(); const newDoc = Doc.MakeCopy(fieldProps.Document, true); - Doc.GetProto(newDoc).text = undefined; + newDoc[DocData].text = undefined; FormattedTextBox.SetSelectOnLoad(newDoc); return this.addDocument?.(newDoc); } @@ -442,7 +442,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { } return true; } - } else if (de.complete.linkDragData?.dragDocument.embedContainer === this._props.Document && de.complete.linkDragData?.linkDragView?.CollectionFreeFormDocumentView) { + } else if (de.complete.linkDragData?.dragDocument.embedContainer === this.Document && de.complete.linkDragData?.linkDragView?.CollectionFreeFormDocumentView) { const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, _layout_fitWidth: true, title: 'dropped annotation' }); if (!this._props.addDocument?.(source)) e.preventDefault(); de.complete.linkDocument = DocUtils.MakeLink(source, de.complete.linkDragData.linkSourceGetAnchor(), { link_relationship: 'doc annotation' }); // TODODO this is where in text links get passed diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 1239a038a..1394e62ba 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -3,6 +3,7 @@ import { observer } from 'mobx-react'; import { computedFn } from 'mobx-utils'; import * as React from 'react'; import { Doc, Opt } from '../../../fields/Doc'; +import { DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { listSpec } from '../../../fields/Schema'; @@ -23,17 +24,17 @@ import { undoBatch, UndoManager } from '../../util/UndoManager'; import { CollectionSubView } from '../collections/CollectionSubView'; import { LightboxView } from '../LightboxView'; import { AudioWaveform } from '../nodes/audio/AudioWaveform'; -import { DocFocusFunc, DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere, StyleProviderFunc } from '../nodes/DocumentView'; +import { DocumentView, OpenWhere } from '../nodes/DocumentView'; +import { FocusFuncType, FocusViewOptions, StyleProviderFuncType } from '../nodes/FieldView'; import { LabelBox } from '../nodes/LabelBox'; import { VideoBox } from '../nodes/VideoBox'; import { ObservableReactComponent } from '../ObservableReactComponent'; import './CollectionStackedTimeline.scss'; -import { FieldViewProps } from '../nodes/FieldView'; export type CollectionStackedTimelineProps = { Play: () => void; Pause: () => void; - playLink: (linkDoc: Doc, options: DocFocusOptions) => void; + playLink: (linkDoc: Doc, options: FocusViewOptions) => void; playFrom: (seekTimeInSeconds: number, endTime?: number) => void; playing: () => boolean; thumbnails?: () => string[]; @@ -162,7 +163,7 @@ export class CollectionStackedTimeline extends CollectionSubView this.childDocList?.some(item => item === doc); - getView = async (doc: Doc, options: DocFocusOptions): Promise> => + getView = async (doc: Doc, options: FocusViewOptions): Promise> => new Promise>(res => { if (doc.hidden) options.didMove = !(doc.hidden = false); const findDoc = (finish: (dv: DocumentView) => void) => DocumentManager.Instance.AddViewRenderedCb(doc, dv => finish(dv)); @@ -427,8 +428,8 @@ export class CollectionStackedTimeline extends CollectionSubView number; - styleProvider?: StyleProviderFunc; - playLink: (linkDoc: Doc, options: DocFocusOptions) => void; + styleProvider?: StyleProviderFuncType; + playLink: (linkDoc: Doc, options: FocusViewOptions) => void; setTime: (time: number) => void; startTag: string; endTag: string; @@ -701,7 +702,7 @@ interface StackedTimelineAnchorProps { isDocumentActive?: () => boolean | undefined; ScreenToLocalTransform: () => Transform; _timeline: HTMLDivElement | null; - focus: DocFocusFunc; + focus: FocusFuncType; currentTimecode: () => number; isSelected: () => boolean; stackedTimeline: CollectionStackedTimeline; @@ -810,7 +811,7 @@ class StackedTimelineAnchor extends ObservableReactComponent ScriptField), doublescript: undefined | (() => ScriptField), screenXf: () => Transform, width: () => number, height: () => number) { const anchor = observable({ view: undefined as Opt | null }); - const focusFunc = (doc: Doc, options: DocFocusOptions): number | undefined => { + const focusFunc = (doc: Doc, options: FocusViewOptions): number | undefined => { this._props.playLink(mark, options); return undefined; }; @@ -842,8 +843,8 @@ class StackedTimelineAnchor extends ObservableReactComponent { + focusDocument = (doc: Doc, options: FocusViewOptions) => { Doc.BrushDoc(doc); const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]); @@ -288,7 +288,7 @@ export class CollectionStackingView extends CollectionSubView this.refList.splice(this.refList.indexOf(ref), 1)} @@ -670,7 +670,7 @@ export class CollectionStackingView extends CollectionSubView this.isContentActive() && e.stopPropagation()}> {this.renderedSections} {!this.showAddAGroup ? null : ( -
+
)} diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index d7c42a975..0aa74aaba 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -21,18 +21,17 @@ import { ImageUtils } from '../../util/Import & Export/ImageUtils'; import { SelectionManager } from '../../util/SelectionManager'; import { SnappingManager } from '../../util/SnappingManager'; import { UndoManager, undoBatch } from '../../util/UndoManager'; -import { ViewBoxBaseComponent, ViewBoxBaseProps } from '../DocComponent'; +import { ViewBoxBaseComponent } from '../DocComponent'; import { LoadingBox } from '../nodes/LoadingBox'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { CollectionView, CollectionViewProps } from './CollectionView'; -import { returnEmptyDocViewList } from '../nodes/DocumentView'; export interface SubCollectionViewProps extends CollectionViewProps { isAnyChildContentActive: () => boolean; } export function CollectionSubView(moreProps?: X) { - class CollectionSubView extends ViewBoxBaseComponent() { + class CollectionSubView extends ViewBoxBaseComponent() { private dropDisposer?: DragManager.DragDropDisposer; private gestureDisposer?: GestureUtils.GestureEventDisposer; protected _mainCont?: HTMLDivElement; @@ -63,20 +62,16 @@ export function CollectionSubView(moreProps?: X) { } get dataDoc() { - return this._props.TemplateDataDocument instanceof Doc && this._props.Document.isTemplateForField - ? Doc.GetProto(this._props.TemplateDataDocument) - : this._props.Document.resolvedDataDoc - ? this._props.Document - : Doc.GetProto(this._props.Document); // if the layout document has a resolvedDataDoc, then we don't want to get its parent which would be the unexpanded template + return this._props.TemplateDataDocument instanceof Doc && this.Document.isTemplateForField ? Doc.GetProto(this._props.TemplateDataDocument) : this.Document.resolvedDataDoc ? this.Document : Doc.GetProto(this.Document); // if the layout document has a resolvedDataDoc, then we don't want to get its parent which would be the unexpanded template } get childContainerViewPath() { - return this.DocumentView?.().docViewPath ?? returnEmptyDocViewList; + return this.DocumentView?.().docViewPath; } // this returns whether either the collection is selected, or the template that it is part of is selected rootSelected = () => this._props.isSelected() || BoolCast(this._props.TemplateDataDocument && this._props.rootSelected?.()); - // The data field for rendering this collection will be on the this._props.Document unless we're rendering a template in which case we try to use props.TemplateDataDocument. + // The data field for rendering this collection will be on the this.Document unless we're rendering a template in which case we try to use props.TemplateDataDocument. // When a document has a TemplateDataDoc but it's not a template, then it contains its own rendering data, but needs to pass the TemplateDataDoc through // to its children which may be templates. // If 'annotationField' is specified, then all children exist on that field of the extension document, otherwise, they exist directly on the data document under 'fieldKey' @@ -97,14 +92,14 @@ export function CollectionSubView(moreProps?: X) { @computed get childDocList() { return Cast(this.dataField, listSpec(Doc)); } - collectionFilters = () => this._focusFilters ?? StrListCast(this._props.Document._childFilters); - collectionRangeDocFilters = () => this._focusRangeFilters ?? Cast(this._props.Document._childFiltersByRanges, listSpec('string'), []); + collectionFilters = () => this._focusFilters ?? StrListCast(this.Document._childFilters); + collectionRangeDocFilters = () => this._focusRangeFilters ?? Cast(this.Document._childFiltersByRanges, listSpec('string'), []); // child filters apply to the descendants of the documents in this collection childDocFilters = () => [...(this._props.childFilters?.().filter(f => Utils.IsRecursiveFilter(f)) || []), ...this.collectionFilters()]; // unrecursive filters apply to the documents in the collection, but no their children. See Utils.noRecursionHack unrecursiveDocFilters = () => [...(this._props.childFilters?.().filter(f => !Utils.IsRecursiveFilter(f)) || [])]; childDocRangeFilters = () => [...(this._props.childFiltersByRanges?.() || []), ...this.collectionRangeDocFilters()]; - searchFilterDocs = () => this._props.searchFilterDocs?.() ?? DocListCast(this._props.Document._searchFilterDocs); + searchFilterDocs = () => this._props.searchFilterDocs?.() ?? DocListCast(this.Document._searchFilterDocs); @computed.struct get childDocs() { TraceMobx(); let rawdocs: (Doc | Promise)[] = []; @@ -127,7 +122,7 @@ export function CollectionSubView(moreProps?: X) { const childDocFilters = this.childDocFilters(); const childFiltersByRanges = this.childDocRangeFilters(); const searchDocs = this.searchFilterDocs(); - if (this._props.Document.dontRegisterView || (!childDocFilters.length && !this.unrecursiveDocFilters().length && !childFiltersByRanges.length && !searchDocs.length)) { + if (this.Document.dontRegisterView || (!childDocFilters.length && !this.unrecursiveDocFilters().length && !childFiltersByRanges.length && !searchDocs.length)) { return childDocs.filter(cd => !cd.cookies); // remove any documents that require a cookie if there are no filters to provide one } @@ -136,9 +131,9 @@ export function CollectionSubView(moreProps?: X) { // dragging facets const dragged = this._props.childFilters?.().some(f => f.includes(Utils.noDragDocsFilter)); if (dragged && SnappingManager.CanEmbed && DragManager.docsBeingDragged.includes(d)) return false; - let notFiltered = d.z || Doc.IsSystem(d) || DocUtils.FilterDocs([d], this.unrecursiveDocFilters(), childFiltersByRanges, this._props.Document).length > 0; + let notFiltered = d.z || Doc.IsSystem(d) || DocUtils.FilterDocs([d], this.unrecursiveDocFilters(), childFiltersByRanges, this.Document).length > 0; if (notFiltered) { - notFiltered = (!searchDocs.length || searchDocs.includes(d)) && DocUtils.FilterDocs([d], childDocFilters, childFiltersByRanges, this._props.Document).length > 0; + notFiltered = (!searchDocs.length || searchDocs.includes(d)) && DocUtils.FilterDocs([d], childDocFilters, childFiltersByRanges, this.Document).length > 0; const fieldKey = Doc.LayoutFieldKey(d); const annos = !Field.toString(Doc.LayoutField(d) as Field).includes(CollectionView.name); const data = d[annos ? fieldKey + '_annotations' : fieldKey]; @@ -171,7 +166,7 @@ export function CollectionSubView(moreProps?: X) { @action protected async setCursorPosition(position: [number, number]) { let ind; - const doc = this._props.Document; + const doc = this.Document; const id = Doc.UserDoc()[Id]; const email = Doc.CurrentUserEmail; const pos = { x: position[0], y: position[1] }; @@ -207,7 +202,7 @@ export function CollectionSubView(moreProps?: X) { const dropAction = this.layoutDoc.dropAction as dropActionType; // if the dropEvent's dragAction is, say 'embed', but we're just dragging within a collection, we may not actually want to make an embedding. // so we check if our collection has a dropAction set on it and if so, we use that instead. - if (dropAction && !de.complete.docDragData.draggedDocuments.some(d => d.embedContainer === this._props.Document && this.childDocs.includes(d))) { + if (dropAction && !de.complete.docDragData.draggedDocuments.some(d => d.embedContainer === this.Document && this.childDocs.includes(d))) { de.complete.docDragData.dropAction = dropAction; } e.stopPropagation(); @@ -471,7 +466,7 @@ export function CollectionSubView(moreProps?: X) { } if (generatedDocuments.length) { // Creating a dash document - const isFreeformView = this._props.Document._type_collection === CollectionViewType.Freeform; + const isFreeformView = this.Document._type_collection === CollectionViewType.Freeform; const set = !isFreeformView ? generatedDocuments : generatedDocuments.length > 1 diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx index 7036ec41c..ee5147428 100644 --- a/src/client/views/collections/CollectionTimeView.tsx +++ b/src/client/views/collections/CollectionTimeView.tsx @@ -16,7 +16,8 @@ import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { EditableView } from '../EditableView'; -import { DocFocusOptions, DocumentView } from '../nodes/DocumentView'; +import { DocumentView } from '../nodes/DocumentView'; +import { FocusViewOptions } from '../nodes/FieldView'; import { PresBox } from '../nodes/trails'; import { CollectionSubView } from './CollectionSubView'; import './CollectionTimeView.scss'; @@ -38,7 +39,7 @@ export class CollectionTimeView extends CollectionSubView() { } componentDidMount() { - this._props.setContentView?.(this); + this._props.setContentViewBox?.(this); runInAction(() => { this._childClickedScript = ScriptField.MakeScript('openInLightbox(this)', { this: Doc.name }); this._viewDefDivClick = ScriptField.MakeScript('pivotColumnClick(this,payload)', { payload: 'any' }); @@ -68,7 +69,7 @@ export class CollectionTimeView extends CollectionSubView() { }; @action - scrollPreview = (docView: DocumentView, anchor: Doc, focusSpeed: number, options: DocFocusOptions) => { + scrollPreview = (docView: DocumentView, anchor: Doc, focusSpeed: number, options: FocusViewOptions) => { // if in preview, then override document's fields with view spec this._focusFilters = StrListCast(anchor.config_docFilters); this._focusRangeFilters = StrListCast(anchor.config_docRangeFilters); diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 76b934802..4696d7948 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -447,7 +447,7 @@ export class CollectionTreeView extends CollectionSubView { isAnnotationOverlay?: boolean; // is the collection an annotation overlay (eg an overlay on an image/video/etc) isAnnotationOverlayScrollable?: boolean; // whether the annotation overlay can be vertically scrolled (just for tree views, currently) layoutEngine?: () => string; @@ -63,9 +63,8 @@ interface CollectionViewProps_ extends FieldViewProps { RemFromMap?: (treeViewDoc: Doc, index: number[]) => void; hierarchyIndex?: number[]; // hierarchical index of a document up to the rendering root (primarily used for tree views) } -export interface CollectionViewProps extends React.PropsWithChildren {} @observer -export class CollectionView extends ViewBoxAnnotatableComponent() { +export class CollectionView extends ViewBoxAnnotatableComponent() implements ViewBoxInterface { public static LayoutString(fieldStr: string) { return FieldView.LayoutString(CollectionView, fieldStr); } diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index f00e42360..da15f8dc5 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -29,7 +29,8 @@ import { LightboxView } from '../LightboxView'; import { ObservableReactComponent } from '../ObservableReactComponent'; import { DefaultStyleProvider, StyleProp } from '../StyleProvider'; import { Colors } from '../global/globalEnums'; -import { DocFocusOptions, DocumentView, OpenWhere, OpenWhereMod, returnEmptyDocViewList } from '../nodes/DocumentView'; +import { DocumentView, OpenWhere, OpenWhereMod, returnEmptyDocViewList } from '../nodes/DocumentView'; +import { FocusViewOptions, FieldViewProps } from '../nodes/FieldView'; import { KeyValueBox } from '../nodes/KeyValueBox'; import { DashFieldView } from '../nodes/formattedText/DashFieldView'; import { PinProps, PresBox, PresMovement } from '../nodes/trails'; @@ -37,7 +38,6 @@ import { CollectionDockingView } from './CollectionDockingView'; import { CollectionView } from './CollectionView'; import './TabDocView.scss'; import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; -import { FieldViewProps } from '../nodes/FieldView'; const _global = (window /* browser */ || global) /* node */ as any; interface TabDocViewProps { @@ -118,7 +118,7 @@ export class TabDocView extends ObservableReactComponent { titleEle.onchange = (e: any) => { undoable(() => { titleEle.size = e.currentTarget.value.length + 3; - Doc.GetProto(doc).title = e.currentTarget.value; + doc[DocData].title = e.currentTarget.value; }, 'edit tab title')(); }; @@ -416,7 +416,7 @@ export class TabDocView extends ObservableReactComponent { return tab !== undefined; }; @action - focusFunc = (doc: Doc, options: DocFocusOptions) => { + focusFunc = (doc: Doc, options: FocusViewOptions) => { if (!this.tab.header.parent._activeContentItem || this.tab.header.parent._activeContentItem !== this.tab.contentItem) { this.tab.header.parent.setActiveContentItem(this.tab.contentItem); // glr: Panning does not work when this is set - (this line is for trying to make a tab that is not topmost become topmost) } @@ -452,7 +452,7 @@ export class TabDocView extends ObservableReactComponent { hideTitle={this._props.keyValue} Document={this._document} TemplateDataDocument={!Doc.AreProtosEqual(this._document[DocData], this._document) ? this._document[DocData] : undefined} - onBrowseClick={DocumentView.exploreMode} + onBrowseClickScript={DocumentView.exploreMode} waitForDoubleClickToClick={this.waitForDoubleClick} isContentActive={this.isContentActive} isDocumentActive={returnFalse} diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 9f40edee1..38a2fe978 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -27,8 +27,8 @@ import { UndoManager, undoBatch, undoable } from '../../util/UndoManager'; import { EditableView } from '../EditableView'; import { ObservableReactComponent } from '../ObservableReactComponent'; import { StyleProp } from '../StyleProvider'; -import { DocumentView, DocumentViewInternal, OpenWhere, StyleProviderFunc } from '../nodes/DocumentView'; -import { FieldViewProps } from '../nodes/FieldView'; +import { DocumentView, DocumentViewInternal, OpenWhere } from '../nodes/DocumentView'; +import { FieldViewProps, StyleProviderFuncType } from '../nodes/FieldView'; import { KeyValueBox } from '../nodes/KeyValueBox'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; @@ -62,7 +62,7 @@ export interface TreeViewProps { ScreenToLocalTransform: () => Transform; contextMenuItems?: { script: ScriptField; filter: ScriptField; icon: string; label: string }[]; dontRegisterView?: boolean; - styleProvider?: StyleProviderFunc | undefined; + styleProvider?: StyleProviderFuncType | undefined; treeViewHideHeaderFields: () => boolean; renderedIds: string[]; // list of document ids rendered used to avoid unending expansion of items in a cycle onCheckedClick?: () => ScriptField; @@ -367,8 +367,9 @@ export class TreeView extends ObservableReactComponent { _width: 1000, _height: 10, }); - Doc.GetProto(bullet).title = ComputedField.MakeFunction('this.text?.Text'); - Doc.GetProto(bullet).data = new List([]); + const bulletData = bullet[DocData]; + bulletData.title = ComputedField.MakeFunction('this.text?.Text'); + bulletData.data = new List([]); DocumentManager.Instance.AddViewRenderedCb(bullet, dv => dv.ComponentView?.setFocus?.()); return bullet; @@ -982,8 +983,8 @@ export class TreeView extends ObservableReactComponent { addDocument={undefined} addDocTab={this._props.addDocTab} pinToPres={this.treeView._props.pinToPres} - onClick={this.onChildClick} - onDoubleClick={this.onChildDoubleClick} + onClickScript={this.onChildClick} + onDoubleClickScript={this.onChildDoubleClick} dragAction={this._props.dragAction} moveDocument={this.move} removeDocument={this._props.removeDoc} @@ -1076,7 +1077,7 @@ export class TreeView extends ObservableReactComponent { fitContentsToBox={returnTrue} hideDecorationTitle={this.treeView.outlineMode} hideResizeHandles={this.treeView.outlineMode} - onClick={this.onChildClick} + onClickScript={this.onChildClick} focus={this.refocus} onKey={this.onKeyDown} hideLinkButton={BoolCast(this.treeView.Document.childHideLinkButton)} @@ -1209,7 +1210,7 @@ export class TreeView extends ObservableReactComponent { move: DragManager.MoveFunction, dragAction: dropActionType, addDocTab: (doc: Doc, where: OpenWhere) => boolean, - styleProvider: undefined | StyleProviderFunc, + styleProvider: undefined | StyleProviderFuncType, screenToLocalXf: () => Transform, isContentActive: (outsideReaction?: boolean) => boolean, panelWidth: () => number, diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index 5204633ea..f0a31a8c6 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -130,7 +130,7 @@ export class CollectionFreeFormLinkView extends ObservableReactComponent { })} // />, { x: 300, y: 300 }); }) @@ -204,8 +204,8 @@ export class CollectionFreeFormLinkView extends ObservableReactComponent number; NativeHeight?: () => number; originTopLeft?: boolean; @@ -65,12 +65,12 @@ export type collectionFreeformViewProps = { noOverlay?: boolean; // used to suppress docs in the overlay (z) layer (ie, for minimap since overlay doesn't scale) engineProps?: any; getScrollHeight?: () => number | undefined; -}; +} @observer export class CollectionFreeFormView extends CollectionSubView>() { public get displayName() { - return 'CollectionFreeFormView(' + this._props.Document.title?.toString() + ')'; + return 'CollectionFreeFormView(' + this.Document.title?.toString() + ')'; } // this makes mobx trace() statements more descriptive constructor(props: any) { @@ -180,12 +180,10 @@ export class CollectionFreeFormView extends CollectionSubView { CollectionFreeFormDocumentView.animFields.forEach(val => { @@ -223,7 +221,7 @@ export class CollectionFreeFormView extends CollectionSubView (this._keyframeEditing = set); getKeyFrameEditing = () => this._keyframeEditing; - onBrowseClickHandler = () => this._props.onBrowseClick?.() || ScriptCast(this.layoutDoc.onBrowseClick); + onBrowseClickHandler = () => this._props.onBrowseClickScript?.() || ScriptCast(this.layoutDoc.onBrowseClick); onChildClickHandler = () => this._props.childClickScript || ScriptCast(this.Document.onChildClick); onChildDoubleClickHandler = () => this._props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); elementFunc = () => this._layoutElements; @@ -288,14 +286,14 @@ export class CollectionFreeFormView extends CollectionSubView= -1e-4 && curTime <= endTime); } - groupFocus = (anchor: Doc, options: DocFocusOptions) => { + groupFocus = (anchor: Doc, options: FocusViewOptions) => { options.docTransform = new Transform(-NumCast(this.layoutDoc[this.panXFieldKey]) + NumCast(anchor.x), -NumCast(this.layoutDoc[this.panYFieldKey]) + NumCast(anchor.y), 1); const res = this._props.focus(this.Document, options); options.docTransform = undefined; return res; }; - focus = (anchor: Doc, options: DocFocusOptions) => { + focus = (anchor: Doc, options: FocusViewOptions) => { if (this._lightboxDoc) return; if (anchor === this.Document) { if (options.willZoomCentered && options.zoomScale) { @@ -321,7 +319,7 @@ export class CollectionFreeFormView extends CollectionSubView> => + getView = async (doc: Doc, options: FocusViewOptions): Promise> => new Promise>(res => { if (doc.hidden && this._lightboxDoc !== doc) options.didMove = !(doc.hidden = false); const findDoc = (finish: (dv: DocumentView) => void) => DocumentManager.Instance.AddViewRenderedCb(doc, dv => finish(dv)); @@ -467,7 +465,7 @@ export class CollectionFreeFormView extends CollectionSubView pair.layout) .reduce((cluster, cd) => { - const grouping = this._props.Document._freeform_useClusters ? NumCast(cd.layout_cluster, -1) : NumCast(cd.group, -1); + const grouping = this.Document._freeform_useClusters ? NumCast(cd.layout_cluster, -1) : NumCast(cd.group, -1); if (grouping !== -1) { const layoutDoc = Doc.Layout(cd); const cx = NumCast(cd.x) - this._clusterDistance / 2; @@ -484,9 +482,9 @@ export class CollectionFreeFormView extends CollectionSubView pair.layout).filter(cd => (this._props.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === cluster); + const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === cluster); const clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this.DocumentView?.())!); - const { left, top } = clusterDocs[0].getBounds() || { left: 0, top: 0 }; + const { left, top } = clusterDocs[0].getBounds || { left: 0, top: 0 }; const de = new DragManager.DocumentDragData(eles, e.ctrlKey || e.altKey ? 'embed' : undefined); de.moveDocument = this._props.moveDocument; de.offset = this.screenToFreeformContentsXf.transformDirection(ptsParent.clientX - left, ptsParent.clientY - top); @@ -506,7 +504,7 @@ export class CollectionFreeFormView extends CollectionSubView pair.layout).map(c => this.updateCluster(c)); } @@ -514,7 +512,7 @@ export class CollectionFreeFormView extends CollectionSubView pair.layout); - if (this._props.Document._freeform_useClusters) { + if (this.Document._freeform_useClusters) { const docFirst = docs[0]; docs.map(doc => this._clusterSets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1))); const preferredInd = NumCast(docFirst.layout_cluster); @@ -557,7 +555,7 @@ export class CollectionFreeFormView extends CollectionSubView { const childLayouts = this.childLayoutPairs.map(pair => pair.layout); - if (this._props.Document._freeform_useClusters) { + if (this.Document._freeform_useClusters) { this._clusterSets.forEach(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1)); const preferredInd = NumCast(doc.layout_cluster); doc.layout_cluster = -1; @@ -616,7 +614,7 @@ export class CollectionFreeFormView extends CollectionSubView { if (this._hitCluster !== -1) { !addToSel && SelectionManager.DeselectAll(); - const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this._props.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === this._hitCluster); + const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === this._hitCluster); this.selectDocuments(eles); return true; } @@ -810,7 +808,7 @@ export class CollectionFreeFormView extends CollectionSubView DocumentManager.Instance.getDocumentView(doc, this.DocumentView?.())) .filter(inkView => inkView?.ComponentView instanceof InkingStroke) - .map(inkView => ({ inkViewBounds: inkView!.getBounds(), inkStroke: inkView!.ComponentView as InkingStroke, inkView: inkView! })) + .map(inkView => ({ inkViewBounds: inkView!.getBounds, inkStroke: inkView!.ComponentView as InkingStroke, inkView: inkView! })) .filter( ({ inkViewBounds }) => inkViewBounds && // bounding box of eraser segment and ink stroke overlap @@ -959,8 +957,8 @@ export class CollectionFreeFormView extends CollectionSubView= 0.05 || localTransform.Scale > this.zoomScaling()) { const safeScale = Math.min(Math.max(0.05, localTransform.Scale), 20); - this._props.Document[this.scaleFieldKey] = Math.abs(safeScale); - this.setPan(-localTransform.TranslateX / safeScale, (this._props.originTopLeft ? undefined : NumCast(this._props.Document.layout_scrollTop) * safeScale) || -localTransform.TranslateY / safeScale); + this.Document[this.scaleFieldKey] = Math.abs(safeScale); + this.setPan(-localTransform.TranslateX / safeScale, (this._props.originTopLeft ? undefined : NumCast(this.Document.layout_scrollTop) * safeScale) || -localTransform.TranslateY / safeScale); } }; @@ -968,7 +966,7 @@ export class CollectionFreeFormView extends CollectionSubView { if (this.Document.isGroup || !this.isContentActive()) return; // group style collections neither pan nor zoom PresBox.Instance?.pauseAutoPres(); - if (this.layoutDoc._Transform || this._props.Document.treeView_OutlineMode === TreeViewType.outline) return; + if (this.layoutDoc._Transform || this.Document.treeView_OutlineMode === TreeViewType.outline) return; e.stopPropagation(); const docHeight = NumCast(this.Document[Doc.LayoutFieldKey(this.Document) + '_nativeHeight'], this.nativeHeight); const scrollable = this.isAnnotationOverlay && NumCast(this.layoutDoc[this.scaleFieldKey], 1) === 1 && docHeight > this._props.PanelHeight() / this.nativeDimScaling + 1e-4; @@ -989,7 +987,7 @@ export class CollectionFreeFormView extends CollectionSubView { - (this._props.viewDefDivClick || ScriptCast(this._props.Document.onViewDefDivClick))?.script.run({ this: this._props.Document, payload }); + (this._props.viewDefDivClick || ScriptCast(this.Document.onViewDefDivClick))?.script.run({ this: this.Document, payload }); e.stopPropagation(); }; @@ -1377,7 +1375,7 @@ export class CollectionFreeFormView extends CollectionSubView, engine: (poolData: Map, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) => ViewDefResult[] ) { - return engine(poolData, this._props.Document, this.childLayoutPairs, [this._props.PanelWidth(), this._props.PanelHeight()], this.viewDefsToJSX, this._props.engineProps); + return engine(poolData, this.Document, this.childLayoutPairs, [this._props.PanelWidth(), this._props.PanelHeight()], this.viewDefsToJSX, this._props.engineProps); } doFreeformLayout(poolData: Map) { @@ -1437,7 +1435,7 @@ export class CollectionFreeFormView extends CollectionSubView (this.Document._hideInfo || this.Document.annotationOn || this._props.renderDepth ? null : ); componentDidMount() { - this._props.setContentView?.(this); + this._props.setContentViewBox?.(this); super.componentDidMount?.(); setTimeout( action(() => { @@ -1639,15 +1637,15 @@ export class CollectionFreeFormView extends CollectionSubView (this.layoutDoc._autoArrange = !this.layoutDoc._autoArrange), icon: 'compress-arrows-alt', }); - if (this._props.setContentView === emptyFunction) { + if (this._props.setContentViewBox === emptyFunction) { !appearance && ContextMenu.Instance.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' }); return; } @@ -1656,7 +1654,7 @@ export class CollectionFreeFormView extends CollectionSubView this.updateClusters(!this.Document._freeform_useClusters), icon: 'braille' }) : null; @@ -1680,8 +1678,8 @@ export class CollectionFreeFormView extends CollectionSubView { - if (this._props.Document.isGroup && this._props.Document.transcription) { - const text = StrCast(this._props.Document.transcription); + if (this.Document.isGroup && this.Document.transcription) { + const text = StrCast(this.Document.transcription); const lines = text.split('\n'); const height = 30 + 15 * lines.length; @@ -1742,7 +1740,7 @@ export class CollectionFreeFormView extends CollectionSubView - {this._props.Document.annotationOn ? '' : this._props.Document.title?.toString()} + {this.Document.annotationOn ? '' : this.Document.title?.toString()}
); } @@ -1882,10 +1880,10 @@ export class CollectionFreeFormView extends CollectionSubView { - Doc.GetProto(doc).data = new List(selected); - Doc.GetProto(doc).isGroup = makeGroup; - Doc.GetProto(doc).title = makeGroup ? 'grouping' : 'nested freeform'; + const docData = doc[DocData]; + docData.data = new List(selected); + docData.isGroup = makeGroup; + docData.title = makeGroup ? 'grouping' : 'nested freeform'; doc._freeform_panX = doc._freeform_panY = 0; return doc; })(Doc.MakeCopy(Doc.UserDoc().emptyCollection as Doc, true)); diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx index 113ffedb3..f25872c2b 100644 --- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx +++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx @@ -192,7 +192,7 @@ export class CollectionGridView extends CollectionSubView() { {...this._props} NativeWidth={returnZero} NativeHeight={returnZero} - setContentView={emptyFunction} + setContentViewBox={emptyFunction} Document={layout} TemplateDataDocument={layout.resolvedDataDoc as Doc} isContentActive={this.isChildContentActive} @@ -200,7 +200,7 @@ export class CollectionGridView extends CollectionSubView() { PanelHeight={height} ScreenToLocalTransform={dxf} whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged} - onClick={this.onChildClickHandler} + onClickScript={this.onChildClickHandler} renderDepth={this._props.renderDepth + 1} dontCenter={StrCast(this.layoutDoc.layout_dontCenter) as any} // 'y', 'x', 'xy' /> @@ -340,7 +340,7 @@ export class CollectionGridView extends CollectionSubView() { * Handles text document creation on double click. */ onPointerDown = (e: React.PointerEvent) => { - if (this._props.isContentActive(true)) { + if (this._props.isContentActive()) { setupMoveUpEvents( this, e, diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx index 40b4151fe..1fa106b45 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx @@ -267,8 +267,8 @@ export class CollectionMulticolumnView extends CollectionSubView() { PanelHeight={height} rootSelected={this.rootSelected} dragAction={StrCast(this.Document.childDragAction, this._props.childDragAction) as dropActionType} - onClick={this.onChildClickHandler} - onDoubleClick={this.onChildDoubleClickHandler} + onClickScript={this.onChildClickHandler} + onDoubleClickScript={this.onChildDoubleClickHandler} suppressSetHeight={true} ScreenToLocalTransform={dxf} isContentActive={this.isChildContentActive} diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx index 04a4042f1..97a444b8c 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx @@ -262,8 +262,8 @@ export class CollectionMultirowView extends CollectionSubView() { PanelHeight={height} rootSelected={this.rootSelected} dragAction={StrCast(this.Document.childDragAction, this._props.childDragAction) as dropActionType} - onClick={this.onChildClickHandler} - onDoubleClick={this.onChildDoubleClickHandler} + onClickScript={this.onChildClickHandler} + onDoubleClickScript={this.onChildDoubleClickHandler} ScreenToLocalTransform={dxf} isContentActive={this.isChildContentActive} isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive} diff --git a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx index c38c6dc4e..d580d9c52 100644 --- a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx +++ b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx @@ -1,16 +1,16 @@ -import { action, observable } from 'mobx'; +import { action } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc } from '../../../../fields/Doc'; import { NumCast, StrCast } from '../../../../fields/Types'; import { UndoManager } from '../../../util/UndoManager'; import { StyleProp } from '../../StyleProvider'; -import { StyleProviderFunc } from '../../nodes/DocumentView'; +import { StyleProviderFuncType } from '../../nodes/FieldView'; import { DimUnit } from './CollectionMulticolumnView'; interface ResizerProps { width: number; - styleProvider?: StyleProviderFunc; + styleProvider?: StyleProviderFuncType; isContentActive?: () => boolean | undefined; columnUnitLength(): number | undefined; toLeft?: Doc; diff --git a/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx index 6f1b3b425..73d08d5ef 100644 --- a/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx +++ b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx @@ -1,16 +1,16 @@ -import { action, observable } from 'mobx'; +import { action } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc } from '../../../../fields/Doc'; import { NumCast, StrCast } from '../../../../fields/Types'; import { UndoManager } from '../../../util/UndoManager'; import { StyleProp } from '../../StyleProvider'; -import { StyleProviderFunc } from '../../nodes/DocumentView'; +import { StyleProviderFuncType } from '../../nodes/FieldView'; import { DimUnit } from './CollectionMultirowView'; interface ResizerProps { height: number; - styleProvider?: StyleProviderFunc; + styleProvider?: StyleProviderFuncType; isContentActive?: () => boolean | undefined; columnUnitLength(): number | undefined; toTop?: Doc; diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index 227274a53..ec91b25f8 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -16,7 +16,8 @@ import { undoable, undoBatch } from '../../../util/UndoManager'; import { ContextMenu } from '../../ContextMenu'; import { EditableView } from '../../EditableView'; import { Colors } from '../../global/globalEnums'; -import { DocFocusOptions, DocumentView } from '../../nodes/DocumentView'; +import { DocumentView } from '../../nodes/DocumentView'; +import { FocusViewOptions, FieldViewProps } from '../../nodes/FieldView'; import { KeyValueBox } from '../../nodes/KeyValueBox'; import { ObservableReactComponent } from '../../ObservableReactComponent'; import { DefaultStyleProvider, StyleProp } from '../../StyleProvider'; @@ -24,7 +25,6 @@ import { CollectionSubView } from '../CollectionSubView'; import './CollectionSchemaView.scss'; import { SchemaColumnHeader } from './SchemaColumnHeader'; import { SchemaRowBox } from './SchemaRowBox'; -import { FieldViewProps } from '../../nodes/FieldView'; export enum ColumnType { Number, @@ -147,7 +147,7 @@ export class CollectionSchemaView extends CollectionSubView() { } componentDidMount() { - this._props.setContentView?.(this); + this._props.setContentViewBox?.(this); document.addEventListener('keydown', this.onKeyDown); Object.entries(this._documentOptions).forEach((pair: [string, FInfo]) => this.fieldInfos.set(pair[0], pair[1])); @@ -498,13 +498,13 @@ export class CollectionSchemaView extends CollectionSubView() { ContextMenu.Instance.displayMenu(x, y, undefined, true); }; - focusDocument = (doc: Doc, options: DocFocusOptions) => { + focusDocument = (doc: Doc, options: FocusViewOptions) => { Doc.BrushDoc(doc); this.scrollToDoc(doc, options); return undefined; }; - scrollToDoc = (doc: Doc, options: DocFocusOptions) => { + scrollToDoc = (doc: Doc, options: FocusViewOptions) => { const found = this._tableContentRef && Array.from(this._tableContentRef.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]); if (found) { const rect = found.getBoundingClientRect(); diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx index 3f94e7fc1..f2fe0dde7 100644 --- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx +++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx @@ -12,7 +12,7 @@ import { DragManager } from '../../../util/DragManager'; import { SnappingManager } from '../../../util/SnappingManager'; import { Transform } from '../../../util/Transform'; import { undoable } from '../../../util/UndoManager'; -import { ViewBoxBaseComponent, ViewBoxBaseProps } from '../../DocComponent'; +import { ViewBoxBaseComponent } from '../../DocComponent'; import { Colors } from '../../global/globalEnums'; import { OpenWhere } from '../../nodes/DocumentView'; import { FieldView, FieldViewProps } from '../../nodes/FieldView'; @@ -20,17 +20,17 @@ import { CollectionSchemaView } from './CollectionSchemaView'; import './CollectionSchemaView.scss'; import { SchemaTableCell } from './SchemaTableCell'; -interface SchemaRowBoxProps { +interface SchemaRowBoxProps extends FieldViewProps { rowIndex: number; } @observer -export class SchemaRowBox extends ViewBoxBaseComponent() { +export class SchemaRowBox extends ViewBoxBaseComponent() { public static LayoutString(fieldKey: string, rowIndex: number) { return FieldView.LayoutString(SchemaRowBox, fieldKey).replace('fieldKey', `rowIndex={${rowIndex}} fieldKey`); } private _ref: HTMLDivElement | null = null; - constructor(props: any) { + constructor(props: SchemaRowBoxProps) { super(props); makeObservable(this); } @@ -50,7 +50,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent { diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx index 422c4155d..fda5764dd 100644 --- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx +++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx @@ -18,7 +18,7 @@ import { EditableView } from '../../EditableView'; import { ObservableReactComponent } from '../../ObservableReactComponent'; import { DefaultStyleProvider } from '../../StyleProvider'; import { Colors } from '../../global/globalEnums'; -import { OpenWhere } from '../../nodes/DocumentView'; +import { OpenWhere, returnEmptyDocViewList } from '../../nodes/DocumentView'; import { FieldViewProps } from '../../nodes/FieldView'; import { KeyValueBox } from '../../nodes/KeyValueBox'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; @@ -49,7 +49,7 @@ export interface SchemaTableCellProps { @observer export class SchemaTableCell extends ObservableReactComponent { - constructor(props: any) { + constructor(props: SchemaTableCellProps) { super(props); makeObservable(this); } @@ -75,6 +75,7 @@ export class SchemaTableCell extends ObservableReactComponent { render() { const sourceDoc = this.props.docView.Document; const sourceAnchor = this.props.docView.anchorViewDoc ?? sourceDoc; - const style = this.props.style ?? (dv => ({ left: dv?.left || 0, top: this.props.docView.topMost ? undefined : (dv?.bottom || 0) + 15, bottom: this.props.docView.topMost ? 20 : undefined, maxWidth: 200 }))(this.props.docView.getBounds()); + const style = this.props.style ?? (dv => ({ left: dv?.left || 0, top: this.props.docView.topMost ? undefined : (dv?.bottom || 0) + 15, bottom: this.props.docView.topMost ? 20 : undefined, maxWidth: 200 }))(this.props.docView.getBounds); return (
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx index 91142b90b..028d3da53 100644 --- a/src/client/views/linking/LinkMenuGroup.tsx +++ b/src/client/views/linking/LinkMenuGroup.tsx @@ -47,18 +47,18 @@ export class LinkMenuGroup extends React.Component { const sourceDoc = this.props.docView.anchorViewDoc ?? (this.props.docView.Document.type === DocumentType.LINK // - ? this.props.docView.props.LayoutTemplateString?.includes('link_anchor_1') + ? this.props.docView._props.LayoutTemplateString?.includes('link_anchor_1') ? DocCast(linkDoc.link_anchor_1) : DocCast(linkDoc.link_anchor_2) : this.props.sourceDoc); const destDoc = !sourceDoc ? undefined : this.props.docView.Document.type === DocumentType.LINK - ? this.props.docView.props.LayoutTemplateString?.includes('link_anchor_1') - ? DocCast(linkDoc.link_anchor_2) - : DocCast(linkDoc.link_anchor_1) - : LinkManager.getOppositeAnchor(linkDoc, sourceDoc) || - LinkManager.getOppositeAnchor(linkDoc, Cast(linkDoc.link_anchor_2, Doc, null).annotationOn === sourceDoc ? Cast(linkDoc.link_anchor_2, Doc, null) : Cast(linkDoc.link_anchor_1, Doc, null)); + ? this.props.docView._props.LayoutTemplateString?.includes('link_anchor_1') + ? DocCast(linkDoc.link_anchor_2) + : DocCast(linkDoc.link_anchor_1) + : LinkManager.getOppositeAnchor(linkDoc, sourceDoc) || + LinkManager.getOppositeAnchor(linkDoc, Cast(linkDoc.link_anchor_2, Doc, null).annotationOn === sourceDoc ? Cast(linkDoc.link_anchor_2, Doc, null) : Cast(linkDoc.link_anchor_1, Doc, null)); return !destDoc || !sourceDoc ? null : ( { addDocTab={this.addDocTab} pinToPres={TabDocView.PinDoc} bringToFront={emptyFunction} - onBrowseClick={DocumentView.exploreMode} + onBrowseClickScript={DocumentView.exploreMode} focus={emptyFunction} /> diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 1729af191..2b71fd156 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -5,6 +5,7 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { DateField } from '../../../fields/DateField'; import { Doc } from '../../../fields/Doc'; +import { DocData } from '../../../fields/DocSymbols'; import { ComputedField } from '../../../fields/ScriptField'; import { Cast, DateCast, NumCast } from '../../../fields/Types'; import { AudioField, nullAudio } from '../../../fields/URLField'; @@ -19,8 +20,7 @@ import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { ViewBoxAnnotatableComponent } from '../DocComponent'; import './AudioBox.scss'; -import { DocFocusOptions } from './DocumentView'; -import { FieldView, FieldViewProps } from './FieldView'; +import { FocusViewOptions, FieldView, FieldViewProps } from './FieldView'; import { PinProps, PresBox } from './trails'; /** @@ -121,7 +121,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent() { @action componentDidMount() { - this._props.setContentView?.(this); + this._props.setContentViewBox?.(this); if (this.path) { this.mediaState = media_state.Paused; this.setPlayheadTime(NumCast(this.layoutDoc.clipStart)); @@ -374,9 +374,10 @@ export class AudioBox extends ViewBoxAnnotatableComponent() { action(() => { const newDoc = DocUtils.GetNewTextDoc('', NumCast(this.Document.x), NumCast(this.Document.y) + NumCast(this.layoutDoc._height) + 10, NumCast(this.layoutDoc._width), 2 * NumCast(this.layoutDoc._height)); const textField = Doc.LayoutFieldKey(newDoc); - Doc.GetProto(newDoc)[`${textField}_recordingSource`] = this.dataDoc; - Doc.GetProto(newDoc)[`${textField}_recordingStart`] = ComputedField.MakeFunction(`this.${textField}_recordingSource.${this.fieldKey}_recordingStart`); - Doc.GetProto(newDoc).mediaState = ComputedField.MakeFunction(`this.${textField}_recordingSource.mediaState`); + const newDocData = newDoc[DocData]; + newDocData[`${textField}_recordingSource`] = this.dataDoc; + newDocData[`${textField}_recordingStart`] = ComputedField.MakeFunction(`this.${textField}_recordingSource.${this.fieldKey}_recordingStart`); + newDocData.mediaState = ComputedField.MakeFunction(`this.${textField}_recordingSource.mediaState`); if (Doc.IsInMyOverlay(this.Document)) { newDoc.overlayX = this.Document.x; newDoc.overlayY = NumCast(this.Document.y) + NumCast(this.layoutDoc._height); @@ -432,7 +433,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent() { }; // plays link - playLink = (link: Doc, options: DocFocusOptions) => { + playLink = (link: Doc, options: FocusViewOptions) => { if (link.annotationOn === this.Document) { if (!this.layoutDoc.dontAutoPlayFollowedLinks) { this.playFrom(this.timeline?.anchorStart(link) || 0, this.timeline?.anchorEnd(link)); diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 73709c17e..0ae4ed62c 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -38,8 +38,8 @@ export interface CollectionFreeFormDocumentViewWrapperProps extends DocumentView CollectionFreeFormView: CollectionFreeFormView; } @observer -export class CollectionFreeFormDocumentViewWrapper extends ObservableReactComponent implements CollectionFreeFormDocumentViewProps { - constructor(props: any) { +export class CollectionFreeFormDocumentViewWrapper extends ObservableReactComponent { + constructor(props: CollectionFreeFormDocumentViewWrapperProps) { super(props); makeObservable(this); } @@ -104,7 +104,7 @@ export class CollectionFreeFormDocumentViewWrapper extends ObservableReactCompon ); } } -export interface CollectionFreeFormDocumentViewProps { +export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { w_X: () => number; w_Y: () => number; w_Z: () => number; @@ -125,8 +125,8 @@ export interface CollectionFreeFormDocumentViewProps { } @observer -export class CollectionFreeFormDocumentView extends DocComponent() { - constructor(props: any) { +export class CollectionFreeFormDocumentView extends DocComponent() { + constructor(props: CollectionFreeFormDocumentViewProps) { super(props); makeObservable(this); } diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index 500eb52ac..116dc48a6 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -8,20 +8,20 @@ import { DocCast, NumCast, StrCast } from '../../../fields/Types'; import { DocUtils, Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; import { undoBatch } from '../../util/UndoManager'; -import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent'; +import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent'; import { StyleProp } from '../StyleProvider'; import './ComparisonBox.scss'; -import { DocumentView, returnEmptyDocViewList } from './DocumentView'; +import { DocumentView } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; import { PinProps, PresBox } from './trails'; @observer -export class ComparisonBox extends ViewBoxAnnotatableComponent() { +export class ComparisonBox extends ViewBoxAnnotatableComponent() implements ViewBoxInterface { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ComparisonBox, fieldKey); } private _disposers: (DragManager.DragDropDisposer | undefined)[] = [undefined, undefined]; - constructor(props: any) { + constructor(props: FieldViewProps) { super(props); makeObservable(this); } @@ -35,7 +35,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent { this._disposers[disposerId]?.(); @@ -152,9 +152,6 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent (doc instanceof Doc ? [doc] : doc).reduce((res, doc: Doc) => res && this.moveDoc(doc, addDoc, this.fieldKey + '_1'), true); moveDoc2 = (doc: Doc | Doc[], targetCol: Doc | undefined, addDoc: any) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc: Doc) => res && this.moveDoc(doc, addDoc, this.fieldKey + '_2'), true); remDoc1 = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_1'), true); @@ -181,7 +178,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() { +export class DataVizBox extends ViewBoxAnnotatableComponent() implements ViewBoxInterface { private _mainCont: React.RefObject = React.createRef(); private _marqueeref = React.createRef(); private _annotationLayer: React.RefObject = React.createRef(); @@ -46,10 +46,10 @@ export class DataVizBox extends ViewBoxAnnotatableComponent(); - constructor(props: ViewBoxAnnotatableProps & FieldViewProps) { + constructor(props: FieldViewProps) { super(props); makeObservable(this); - this._props.setContentView?.(this); + this._props.setContentViewBox?.(this); } @computed get annotationLayer() { @@ -233,7 +233,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent UndoManager.RunInBatch(this.toggleSidebar, 'toggle sidebar') ); }; - getView = async (doc: Doc, options: DocFocusOptions) => { + getView = async (doc: Doc, options: FocusViewOptions) => { if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) { options.didMove = true; this.toggleSidebar(); @@ -254,7 +254,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent this.removeDocument(doc, sidebarKey); componentDidMount() { - this._props.setContentView?.(this); + this._props.setContentViewBox?.(this); if (!DataVizBox.dataset.has(CsvCast(this.dataDoc[this.fieldKey]).url.href)) this.fetchData(); } diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 55859b92b..9538cebb2 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -24,7 +24,6 @@ import { YoutubeBox } from './../../apis/youtube/YoutubeBox'; import { AudioBox } from './AudioBox'; import { ComparisonBox } from './ComparisonBox'; import { DataVizBox } from './DataVizBox/DataVizBox'; -import { DocumentViewProps } from './DocumentView'; import './DocumentView.scss'; import { EquationBox } from './EquationBox'; import { FieldView, FieldViewProps } from './FieldView'; @@ -114,13 +113,11 @@ export class HTMLtag extends React.Component { } } +export interface DocumentContentsViewProps extends FieldViewProps { + layoutFieldKey: string; +} @observer -export class DocumentContentsView extends ObservableReactComponent< - FieldViewProps & { - onClick?: () => ScriptField; - LayoutTemplate?: () => Opt; - } -> { +export class DocumentContentsView extends ObservableReactComponent { constructor(props: any) { super(props); makeObservable(this); @@ -130,8 +127,8 @@ export class DocumentContentsView extends ObservableReactComponent< TraceMobx(); if (this._props.LayoutTemplateString) return this._props.LayoutTemplateString; if (!this.layoutDoc) return '

awaiting layout

'; - if (this._props.fieldKey === 'layout_keyValue') return StrCast(this._props.Document.layout_keyValue, KeyValueBox.LayoutString()); - const layout = Cast(this.layoutDoc[this.layoutDoc === this._props.Document && this._props.fieldKey ? this._props.fieldKey : StrCast(this.layoutDoc.layout_fieldKey, 'layout')], 'string'); + if (this._props.layoutFieldKey === 'layout_keyValue') return StrCast(this._props.Document.layout_keyValue, KeyValueBox.LayoutString()); + const layout = Cast(this.layoutDoc[this.layoutDoc === this._props.Document && this._props.layoutFieldKey ? this._props.layoutFieldKey : StrCast(this.layoutDoc.layout_fieldKey, 'layout')], 'string'); if (layout === undefined) return this._props.Document.data ? "" : KeyValueBox.LayoutString(); if (typeof layout === 'string') return layout; return '

Loading layout

'; @@ -143,26 +140,26 @@ export class DocumentContentsView extends ObservableReactComponent< const template: Doc = this._props.LayoutTemplate?.() || (this._props.LayoutTemplateString && this._props.Document) || - (this._props.fieldKey && StrCast(this._props.Document[this._props.fieldKey]) && this._props.Document) || - Doc.Layout(this._props.Document, this._props.fieldKey ? Cast(this._props.Document[this._props.fieldKey], Doc, null) : undefined); + (this._props.layoutFieldKey && StrCast(this._props.Document[this._props.layoutFieldKey]) && this._props.Document) || + Doc.Layout(this._props.Document, this._props.layoutFieldKey ? Cast(this._props.Document[this._props.layoutFieldKey], Doc, null) : undefined); return Doc.expandTemplateLayout(template, this._props.Document); } CreateBindings(onClick: Opt, onInput: Opt): JsxBindings { const docOnlyProps = [ - // these are the properties in DocumentViewProps that need to be removed to pass on only DocumentSharedViewProps to the FieldViews + // these are the properties in DocumentViewProps that need to be removed to pass on only DocumentSharedViewProps to the FieldViews 'hideResizeHandles', 'hideTitle', 'contentPointerEvents', - 'radialMenu', 'LayoutTemplateString', 'LayoutTemplate', + 'layoutFieldKey', 'dontCenter', 'contextMenuItems', //'onClick', // don't need to omit this since it will be set - 'onDoubleClick', - 'onPointerDown', - 'onPointerUp', + 'onDoubleClickScript', + 'onPointerDownScript', + 'onPointerUpScript', ]; const templateDataDoc = this._props.TemplateDataDocument ?? (this.layoutDoc !== this._props.Document ? this._props.Document[DocData] : undefined); const list: BindingProps & React.DetailedHTMLProps, HTMLDivElement> = { diff --git a/src/client/views/nodes/DocumentIcon.tsx b/src/client/views/nodes/DocumentIcon.tsx index 5ce08cdc3..4a22766cc 100644 --- a/src/client/views/nodes/DocumentIcon.tsx +++ b/src/client/views/nodes/DocumentIcon.tsx @@ -29,7 +29,7 @@ export class DocumentIcon extends ObservableReactComponent { } render() { const view = this._props.view; - const { left, top, right, bottom } = view.getBounds() || { left: 0, top: 0, right: 0, bottom: 0 }; + const { left, top, right, bottom } = view.getBounds || { left: 0, top: 0, right: 0, bottom: 0 }; return (
rect.width) { - LinkDescriptionPopup.popupX -= 190; + if (LinkDescriptionPopup.Instance.popupX + 200 > rect.width) { + LinkDescriptionPopup.Instance.popupX -= 190; TaskCompletionBox.popupX -= 40; } - if (LinkDescriptionPopup.popupY + 100 > rect.height) { - LinkDescriptionPopup.popupY -= 40; + if (LinkDescriptionPopup.Instance.popupY + 100 > rect.height) { + LinkDescriptionPopup.Instance.popupY -= 40; TaskCompletionBox.popupY -= 40; } setTimeout( - action(() => { - TaskCompletionBox.taskCompleted = false; - }), + action(() => (TaskCompletionBox.taskCompleted = false)), 2500 ); } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 444c300f3..3e5e43b47 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -3,19 +3,17 @@ import { Dropdown, DropdownType, Type } from 'browndash-components'; import { Howl } from 'howler'; import { IReactionDisposer, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; -import { computedFn } from 'mobx-utils'; import * as React from 'react'; import { Bounce, Fade, Flip, JackInTheBox, Roll, Rotate, Zoom } from 'react-awesome-reveal'; import { Utils, emptyFunction, isTargetChildOf as isParentOf, lightOrDark, returnEmptyString, returnFalse, returnTrue, returnVal, simulateMouseClick } from '../../../Utils'; import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../fields/Doc'; -import { AclPrivate, Animation, AudioPlay, DocViews } from '../../../fields/DocSymbols'; +import { AclPrivate, Animation, AudioPlay, DocData, DocViews } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; import { List } from '../../../fields/List'; -import { RefField } from '../../../fields/RefField'; import { listSpec } from '../../../fields/Schema'; import { ScriptField } from '../../../fields/ScriptField'; -import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; +import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { AudioField } from '../../../fields/URLField'; import { GetEffectiveAcl, TraceMobx } from '../../../fields/util'; import { DocServer } from '../../DocServer'; @@ -33,34 +31,27 @@ import { SelectionManager } from '../../util/SelectionManager'; import { SettingsManager } from '../../util/SettingsManager'; import { SharingManager } from '../../util/SharingManager'; import { SnappingManager } from '../../util/SnappingManager'; -import { Transform } from '../../util/Transform'; -import { UndoManager, undoBatch } from '../../util/UndoManager'; +import { UndoManager, undoBatch, undoable } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; -import { DocComponent } from '../DocComponent'; +import { DocComponent, ViewBoxInterface } from '../DocComponent'; import { EditableView } from '../EditableView'; import { GestureOverlay } from '../GestureOverlay'; import { LightboxView } from '../LightboxView'; -import { ObservableReactComponent } from '../ObservableReactComponent'; import { StyleProp } from '../StyleProvider'; -import { CollectionFreeFormDocumentView } from './CollectionFreeFormDocumentView'; import { DocumentContentsView, ObserverJsxParser } from './DocumentContentsView'; import { DocumentLinksButton } from './DocumentLinksButton'; import './DocumentView.scss'; -import { FieldViewProps } from './FieldView'; +import { FieldViewProps, FieldViewSharedProps } from './FieldView'; import { KeyValueBox } from './KeyValueBox'; import { LinkAnchorBox } from './LinkAnchorBox'; import { FormattedTextBox } from './formattedText/FormattedTextBox'; import { PresEffect, PresEffectDirection } from './trails'; -import { PinProps, PresBox } from './trails/PresBox'; - interface Window { MediaRecorder: MediaRecorder; } - declare class MediaRecorder { - // whatever MediaRecorder has - constructor(e: any); + constructor(e: any); // whatever MediaRecorder has } export enum OpenWhereMod { @@ -92,114 +83,7 @@ export enum OpenWhere { export function returnEmptyDocViewList() { return [] as DocumentView[]; } -export interface DocFocusOptions { - willPan?: boolean; // determines whether to pan to target document - willZoomCentered?: boolean; // determines whether to zoom in on target document. if zoomScale is 0, this just centers the document - zoomScale?: number; // percent of containing frame to zoom into document - zoomTime?: number; - didMove?: boolean; // whether a document was changed during the showDocument process - docTransform?: Transform; // when a document can't be panned and zoomed within its own container (say a group), then we need to continue to move up the render hierarchy to find something that can pan and zoom. when this happens the docTransform must accumulate all the transforms of each level of the hierarchy - instant?: boolean; // whether focus should happen instantly (as opposed to smooth zoom) - preview?: boolean; // whether changes should be previewed by the componentView or written to the document - effect?: Doc; // animation effect for focus - noSelect?: boolean; // whether target should be selected after focusing - playAudio?: boolean; // whether to play audio annotation on focus - playMedia?: boolean; // whether to play start target videos - 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 DocFocusFunc = (doc: Doc, options: DocFocusOptions) => Opt; -export type StyleProviderFunc = (doc: Opt, props: Opt, property: string) => any; -export interface DocComponentView { - fieldKey?: string; - annotationKey?: string; - updateIcon?: () => void; // updates the icon representation of the document - getAnchor?: (addAsAnnotation: boolean, pinData?: PinProps) => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box) - restoreView?: (viewSpec: Doc) => boolean; - scrollPreview?: (docView: DocumentView, doc: Doc, focusSpeed: number, options: DocFocusOptions) => Opt; // returns the duration of the focus - brushView?: (view: { width: number; height: number; panX: number; panY: number }, transTime: number, holdTime: number) => void; // highlight a region of a view (used by freeforms) - getView?: (doc: Doc, options: DocFocusOptions) => Promise>; // returns a nested DocumentView for the specified doc or undefined - addDocTab?: (doc: Doc, where: OpenWhere) => boolean; // determines how to add a document - used in following links to open the target ina local lightbox - addDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean; // add a document (used only by collections) - select?: (ctrlKey: boolean, shiftKey: boolean) => void; - focus?: (textAnchor: Doc, options: DocFocusOptions) => Opt; - isAnyChildContentActive?: () => boolean; // is any child content of the document active - onClickScriptDisable?: () => 'never' | 'always'; // disable click scripts : never, always, or undefined = only when selected - getKeyFrameEditing?: () => boolean; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown) - setKeyFrameEditing?: (set: boolean) => void; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown) - playFrom?: (time: number, endTime?: number) => void; - Pause?: () => void; // pause a media document (eg, audio/video) - IsPlaying?: () => boolean; // is a media document playing - TogglePause?: (keep?: boolean) => void; // toggle media document playing state - setFocus?: () => void; // sets input focus to the componentView - setData?: (data: Field | Promise) => boolean; - componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element | null; - dragStarting?: (snapToDraggedDoc: boolean, showGroupDragTarget: boolean, visited: Set) => void; - incrementalRendering?: () => void; - infoUI?: () => JSX.Element | null; - 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 }; - ptFromScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number }; - snapPt?: (pt: { X: number; Y: number }, excludeSegs?: number[]) => { nearestPt: { X: number; Y: number }; distance: number }; - search?: (str: string, bwd?: boolean, clear?: boolean) => boolean; -} -/** - * props that DocumentViews, DocumentInternalViews and FieldViews all can use - * */ -export interface DocumentViewSharedProps { - Document: Doc; - LayoutTemplateString?: string; - TemplateDataDocument?: Doc; - renderDepth: number; - scriptContext?: any; // can be assigned anything and will be passed as 'scriptContext' to any OnClick script that executes on this document - treeViewDoc?: Doc; - xPadding?: number; - yPadding?: number; - dontRegisterView?: boolean; - dropAction?: dropActionType; - dragAction?: dropActionType; - forceAutoHeight?: boolean; - ignoreAutoHeight?: boolean; - disableBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over. - CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView; - containerViewPath?: () => DocumentView[]; - fitContentsToBox?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _freeform_fitContentsToBox property on a Document - isGroupActive?: () => string | undefined; // is this document part of a group that is active - setContentView?: (view: DocComponentView) => any; - PanelWidth: () => number; - PanelHeight: () => number; - isDocumentActive?: () => boolean | undefined; // whether a document should handle pointer events - isContentActive: () => boolean | undefined; // whether document contents should handle pointer events - childFilters: () => string[]; - childFiltersByRanges: () => string[]; - styleProvider: Opt; - setTitleFocus?: () => void; - focus: DocFocusFunc; - layout_fitWidth?: (doc: Doc) => boolean | undefined; - searchFilterDocs: () => Doc[]; - layout_showTitle?: () => string; - whenChildContentsActiveChanged: (isActive: boolean) => void; - rootSelected?: () => boolean; // whether the root of a template has been selected - addDocTab: (doc: Doc, where: OpenWhere) => boolean; - filterAddDocument?: (doc: Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example) - addDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean; - removeDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean; - moveDocument?: (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[], annotationKey?: string) => boolean) => boolean; - pinToPres: (document: Doc, pinProps: PinProps) => void; - ScreenToLocalTransform: () => Transform; - bringToFront: (doc: Doc, sendToBack?: boolean) => void; - waitForDoubleClickToClick?: () => 'never' | 'always' | undefined; - defaultDoubleClick?: () => 'default' | 'ignore' | undefined; - pointerEvents?: () => Opt; -} - -/** - * props that are used by DocumentViews and DocumentInternalViews but not by the contents of those views (FieldViews). - */ -export interface DocumentViewProps extends DocumentViewSharedProps { +export interface DocumentViewProps extends FieldViewSharedProps { hideDecorations?: boolean; // whether to suppress all DocumentDecorations when doc is selected hideResizeHandles?: boolean; // whether to suppress resized handles on doc decorations when this document is selected hideTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings @@ -221,35 +105,22 @@ export interface DocumentViewProps extends DocumentViewSharedProps { onClickScriptDisable?: 'never' | 'always'; // undefined = only when selected NativeWidth?: () => number; NativeHeight?: () => number; - LayoutTemplate?: () => Opt; contextMenuItems?: () => { script: ScriptField; filter?: ScriptField; label: string; icon: string }[]; - onClick?: () => ScriptField; - onDoubleClick?: () => ScriptField; - onPointerDown?: () => ScriptField; - onPointerUp?: () => ScriptField; - onBrowseClick?: () => ScriptField | undefined; - onKey?: (e: React.KeyboardEvent, fieldProps: FieldViewProps) => boolean | undefined; dragStarting?: () => void; dragEnding?: () => void; } - -/** - * props used by DocInternalViews and FieldViews but not DocumentViews - * these props correspond to things that the DocumentView creates and thus doesn't need to receive as a prop - */ -export interface DocumentViewInternalSharedProps { - select: (ctrlPressed: boolean, shiftPress?: boolean) => void; - isSelected: () => boolean; - docViewPath: () => DocumentView[]; - NativeDimScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal -} -export interface DocumentViewInternalProps extends DocumentViewProps, DocumentViewInternalSharedProps { - docViewPublic: () => DocumentView; -} - @observer -export class DocumentViewInternal extends DocComponent() { +export class DocumentViewInternal extends DocComponent() { + // this makes mobx trace() statements more descriptive + public get displayName() { return 'DocumentViewInternal(' + this.Document.title + ')'; } // prettier-ignore public static SelectAfterContextMenu = true; // whether a document should be selected after it's contextmenu is triggered. + + /** + * This function is filled in by MainView to allow non-viewBox views to add Docs as tabs without + * needing to know about/reference MainView + */ + public static addDocTabFunc: (doc: Doc, location: OpenWhere) => boolean = returnFalse; + private _disposers: { [name: string]: IReactionDisposer } = {}; private _doubleClickTimeout: NodeJS.Timeout | undefined; private _singleClickFunc: undefined | (() => any); @@ -262,79 +133,51 @@ export class DocumentViewInternal extends DocComponent(); private _titleRef = React.createRef(); private _dropDisposer?: DragManager.DragDropDisposer; - constructor(props: any) { + constructor(props: FieldViewProps & DocumentViewProps) { super(props); makeObservable(this); } - @observable _componentView: Opt = undefined; // needs to be accessed from DocumentView wrapper class + @observable _changingTitleField = false; + @observable _titleDropDownInnerWidth = 0; // width of menu dropdown when setting doc title + @observable _mounted = false; // turn off all pointer events if component isn't yet mounted (enables nested Docs in alternate UI textboxes that appear on hover which otherwise would grab focus from the text box, reverting to the original UI ) + @observable _isContentActive: boolean | undefined = undefined; + @observable _pointerEvents: 'none' | 'all' | 'visiblePainted' | undefined = undefined; + @observable _componentView: Opt = undefined; // needs to be accessed from DocumentView wrapper class @observable _animateScaleTime: Opt = undefined; // milliseconds for animating between views. defaults to 300 if not uset @observable _animateScalingTo = 0; - public get animateScaleTime() { - return this._animateScaleTime ?? 100; - } - public get displayName() { - return 'DocumentViewInternal(' + this.Document.title + ')'; - } // this makes mobx trace() statements more descriptive - - public get DocumentView() { - return this._props.docViewPublic; - } + get _contentDiv() { return this._mainCont.current; } // prettier-ignore + get _docView() { return this._props.DocumentView?.(); } // prettier-ignore + + animateScaleTime = () => this._animateScaleTime ?? 100; + style = (doc: Doc, sprop: StyleProp | string) => this._props.styleProvider?.(doc, this._props, sprop); + @computed get layout_showTitle() { return this.style(this.layoutDoc, StyleProp.ShowTitle) as Opt; } // prettier-ignore + @computed get opacity() { return this.style(this.layoutDoc, StyleProp.Opacity); } // prettier-ignore + @computed get boxShadow() { return this.style(this.layoutDoc, StyleProp.BoxShadow); } // prettier-ignore + @computed get borderRounding() { return this.style(this.layoutDoc, StyleProp.BorderRounding); } // prettier-ignore + @computed get widgetDecorations() { return this.style(this.layoutDoc, StyleProp.Decorations); } // prettier-ignore + @computed get backgroundBoxColor() { return this.style(this.layoutDoc, StyleProp.BackgroundColor + ':box'); } // prettier-ignore + @computed get headerMargin() { return this.style(this.layoutDoc, StyleProp.HeaderMargin) ?? 0; } // prettier-ignore + @computed get layout_showCaption() { return this.style(this.layoutDoc, StyleProp.ShowCaption) ?? 0; } // prettier-ignore + @computed get titleHeight() { return this.style(this.layoutDoc, StyleProp.TitleHeight) ?? 0; } // prettier-ignore + @computed get docContents() { return this.style(this.Document, StyleProp.DocContents); } // prettier-ignore + @computed get highlighting() { return this.style(this.Document, StyleProp.Highlighting); } // prettier-ignore + @computed get borderPath() { return this.style(this.Document, StyleProp.BorderPath); } // prettier-ignore - public get ContentDiv() { - return this._mainCont.current; - } - public get LayoutFieldKey() { - return Doc.LayoutFieldKey(this.layoutDoc); - } - @computed get styleProps(): FieldViewProps { - return { ...this._props, fieldKey: '' }; - } - @computed get layout_showTitle() { - return this._props.styleProvider?.(this.layoutDoc, this.styleProps, StyleProp.ShowTitle) as Opt; - } - @computed get NativeDimScaling() { - return this._props.NativeDimScaling?.() || 1; - } - @computed get thumb() { - return ImageCast(this.layoutDoc['thumb-frozen'], ImageCast(this.layoutDoc.thumb))?.url?.href.replace('.png', '_m.png'); - } - @computed get opacity() { - return this._props.styleProvider?.(this.layoutDoc, this.styleProps, StyleProp.Opacity); - } - @computed get boxShadow() { - return this._props.styleProvider?.(this.layoutDoc, this.styleProps, StyleProp.BoxShadow); - } - @computed get borderRounding() { - return this._props.styleProvider?.(this.layoutDoc, this.styleProps, StyleProp.BorderRounding); - } - @computed get widgetDecorations() { - TraceMobx(); - return this._props.styleProvider?.(this.layoutDoc, this.styleProps, StyleProp.Decorations); - } - @computed get backgroundBoxColor() { - return this._props.styleProvider?.(this.layoutDoc, this.styleProps, StyleProp.BackgroundColor + ':box'); - } - @computed get docContents() { - return this._props.styleProvider?.(this.Document, this.styleProps, StyleProp.DocContents); - } - @computed get headerMargin() { - return this._props?.styleProvider?.(this.layoutDoc, this.styleProps, StyleProp.HeaderMargin) || 0; - } - @computed get layout_showCaption() { - return this._props?.hideCaptions ? undefined : this._props?.styleProvider?.(this.layoutDoc, this.styleProps, StyleProp.ShowCaption) || 0; + @computed get onClickHandler() { + return this._props.onClickScript?.() ?? this._props.onBrowseClickScript?.() ?? ScriptCast(this.Document.onClick, ScriptCast(this.layoutDoc.onClick)); } - @computed get titleHeight() { - return this._props?.styleProvider?.(this.layoutDoc, this.styleProps, StyleProp.TitleHeight) || 0; + @computed get onDoubleClickHandler() { + return this._props.onDoubleClickScript?.() ?? ScriptCast(this.layoutDoc.onDoubleClick, ScriptCast(this.Document.onDoubleClick)); } - @observable _pointerEvents: 'none' | 'all' | 'visiblePainted' | undefined = undefined; - @computed get pointerEvents(): 'none' | 'all' | 'visiblePainted' | undefined { - return this._pointerEvents; + @computed get onPointerDownHandler() { + return this._props.onPointerDownScript?.() ?? ScriptCast(this.layoutDoc.onPointerDown, ScriptCast(this.Document.onPointerDown)); } - @computed get finalLayoutKey() { - return StrCast(this.Document.layout_fieldKey, 'layout'); + @computed get onPointerUpHandler() { + return this._props.onPointerUpScript?.() ?? ScriptCast(this.layoutDoc.onPointerUp, ScriptCast(this.Document.onPointerUp)); } + @computed get disableClickScriptFunc() { const onScriptDisable = this._props.onClickScriptDisable ?? this._componentView?.onClickScriptDisable?.() ?? this.layoutDoc.onClickScriptDisable; // prettier-ignore @@ -344,23 +187,50 @@ export class DocumentViewInternal extends DocComponent + (link.link_matchEmbeddings ? link.link_anchor_1 === this.Document : Doc.AreProtosEqual(link.link_anchor_1 as Doc, this.Document)) || + (link.link_matchEmbeddings ? link.link_anchor_2 === this.Document : Doc.AreProtosEqual(link.link_anchor_2 as Doc, this.Document)) || + ((link.link_anchor_1 as Doc)?.layout_unrendered && Doc.AreProtosEqual((link.link_anchor_1 as Doc)?.annotationOn as Doc, this.Document)) || + ((link.link_anchor_2 as Doc)?.layout_unrendered && Doc.AreProtosEqual((link.link_anchor_2 as Doc)?.annotationOn as Doc, this.Document)) + ); } - @computed get onPointerUpHandler() { - return this._props.onPointerUp?.() ?? ScriptCast(this.Document.onPointerUp); + @computed get _allLinks() { + TraceMobx(); + return LinkManager.Instance.getAllRelatedLinks(this.Document).filter(link => !link.link_matchEmbeddings || link.link_anchor_1 === this.Document || link.link_anchor_2 === this.Document); + } + + @computed get filteredLinks() { + return DocUtils.FilterDocs(this.directLinks, this._props.childFilters?.() ?? [], []).filter(d => d.link_displayLine || Doc.UserDoc().showLinkLines); } componentWillUnmount() { this.cleanupHandlers(true); } - @observable _mounted = false; // turn off all pointer events if component isn't yet mounted (enables nested Docs in alternate UI textboxes that appear on hover which otherwise would grab focus from the text box, reverting to the original UI ) componentDidMount() { runInAction(() => (this._mounted = true)); @@ -380,7 +250,7 @@ export class DocumentViewInternal extends DocComponent this._props.styleProvider?.(this.Document, this.styleProps, StyleProp.PointerEvents), + () => this.style(this.Document, StyleProp.PointerEvents), pointerevents => (this._pointerEvents = pointerevents), { fireImmediately: true } ); @@ -406,18 +276,19 @@ export class DocumentViewInternal extends DocComponent dv.ContentDiv); - const selected = views.length > 1 && views.some(dv => dv.Document === this.Document) ? views : [this.DocumentView()]; + const selected = views.length > 1 && views.some(dv => dv.Document === this.Document) ? views : [docView]; const dragData = new DragManager.DocumentDragData(selected.map(dv => dv.Document)); - const screenXf = this.DocumentView().screenToViewTransform(); + const screenXf = docView.screenToViewTransform(); const [left, top] = screenXf.inverse().transformPoint(0, 0); dragData.offset = screenXf.transformDirection(x - left, y - top); dragData.dropAction = dropAction; dragData.treeViewDoc = this._props.treeViewDoc; dragData.removeDocument = this._props.removeDocument; dragData.moveDocument = this._props.moveDocument; - dragData.draggedViews = [this.DocumentView()]; + dragData.draggedViews = [docView]; dragData.canEmbed = this.Document.dragAction ?? this._props.dragAction ? true : false; DragManager.StartDocumentDrag( selected.map(dv => dv.ContentDiv!), @@ -434,12 +305,10 @@ export class DocumentViewInternal extends DocComponent this._titleRef.current?.setIsFocused(true)); // use timeout in case title wasn't shown to allow re-render so that titleref will be defined }; - - public static addDocTabFunc: (doc: Doc, location: OpenWhere) => boolean = returnFalse; - onClick = action((e: React.MouseEvent | React.PointerEvent) => { if (this._props.isGroupActive?.() === 'child' && !this._props.isDocumentActive?.()) return; - if (!this.Document.ignoreClick && this._props.renderDepth >= 0 && Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) { + const documentView = this._docView; + if (documentView && !this.Document.ignoreClick && this._props.renderDepth >= 0 && Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) { let stopPropagate = true; let preventDefault = true; !this.layoutDoc._keepZWhenDragged && this._props.bringToFront(this.Document); @@ -451,7 +320,7 @@ export class DocumentViewInternal extends DocComponent this.onDoubleClickHandler.script.run( { this: this.Document, scriptContext: this._props.scriptContext, - documentView: this.DocumentView(), + documentView, clientX, clientY, altKey, shiftKey, ctrlKey, value: undefined, }, console.log ); @@ -482,7 +351,7 @@ export class DocumentViewInternal extends DocComponent (sendToBack ? this.DocumentView()._props.bringToFront(this.Document, true) : + clickFunc ?? (() => (sendToBack ? documentView._props.bringToFront(this.Document, true) : this._componentView?.select?.(e.ctrlKey || e.metaKey, e.shiftKey) ?? this._props.select(e.ctrlKey||e.shiftKey, e.metaKey))); const waitFordblclick = this._props.waitForDoubleClickToClick?.() ?? this.Document.waitForDoubleClickToClick; @@ -525,12 +394,8 @@ export class DocumentViewInternal extends DocComponent { if (this._props.isGroupActive?.() === 'child' && !this._props.isDocumentActive?.()) return; - this._longPressSelector = setTimeout(() => { - if (DocumentView.LongPress) { - this._props.select(false); - } - }, 1000); - if (!GestureOverlay.DownDocView) GestureOverlay.DownDocView = this.DocumentView(); + this._longPressSelector = setTimeout(() => DocumentView.LongPress && this._props.select(false), 1000); + if (!GestureOverlay.DownDocView) GestureOverlay.DownDocView = this._docView; this._downX = e.clientX; this._downY = e.clientY; @@ -541,7 +406,7 @@ export class DocumentViewInternal extends DocComponent { + toggleFollowLink = undoable((zoom?: boolean, setTargetToggle?: boolean): void => { const hadOnClick = this.Document.onClick; this.noOnClick(); this.Document.onClick = hadOnClick ? undefined : FollowLinkScript(); this.Document.waitForDoubleClickToClick = hadOnClick ? undefined : 'never'; - }; - @undoBatch - followLinkOnClick = () => { + }, 'toggle follow link'); + + followLinkOnClick = undoable(() => { this.Document.ignoreClick = false; this.Document.onClick = FollowLinkScript(); this.Document.followLinkToggle = false; this.Document.followLinkZoom = false; this.Document.followLinkLocation = undefined; - }; - @undoBatch - noOnClick = () => { - this.Document.ignoreClick = false; - this.Document.onClick = Doc.GetProto(this.Document).onClick = undefined; - }; - - @undoBatch deleteClicked = () => this._props.removeDocument?.(this.Document); - @undoBatch setToggleDetail = () => - (this.Document.onClick = ScriptField.MakeScript( - `toggleDetail(documentView, "${StrCast(this.Document.layout_fieldKey) - .replace('layout_', '') - .replace(/^layout$/, 'detail')}")`, - { documentView: 'any' } - )); + }, 'follow link on click'); - @undoBatch - drop = (e: Event, de: DragManager.DropEvent) => { + noOnClick = undoable(() => { + this.Document.ignoreClick = false; + this.Document.onClick = this.Document[DocData].onClick = undefined; + }, 'default on click'); + + deleteClicked = undoable(() => this._props.removeDocument?.(this.Document), 'delete doc'); + setToggleDetail = undoable( + () => + (this.Document.onClick = ScriptField.MakeScript( + `toggleDetail(documentView, "${StrCast(this.Document.layout_fieldKey) + .replace('layout_', '') + .replace(/^layout$/, 'detail')}")`, + { documentView: 'any' } + )), + 'set toggle detail' + ); + + drop = undoable((e: Event, de: DragManager.DropEvent) => { if (this._props.dontRegisterView || this._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return false; if (this.Document === Doc.ActiveDashboard) { e.stopPropagation(); @@ -642,7 +508,7 @@ export class DocumentViewInternal extends DocComponent { - const portalLink = this.allLinks.find(d => d.link_anchor_1 === this.Document && d.link_relationship === 'portal to:portal from'); + makeIntoPortal = undoable(() => { + const portalLink = this._allLinks.find(d => d.link_anchor_1 === this.Document && d.link_relationship === 'portal to:portal from'); if (!portalLink) { DocUtils.MakeLink( this.Document, @@ -670,7 +535,7 @@ export class DocumentViewInternal extends DocComponent { const input = document.createElement('input'); @@ -716,7 +581,7 @@ export class DocumentViewInternal extends DocComponent { - if (this.Document.type !== DocumentType.MAP) DocumentViewInternal.SelectAfterContextMenu && !this._props.isSelected() && SelectionManager.SelectView(this.DocumentView(), false); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up. by delaying the selection until hopefully after the pointer up, the context menu will appear. + if (this.Document.type !== DocumentType.MAP) DocumentViewInternal.SelectAfterContextMenu && this._props.select(false); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up. by delaying the selection until hopefully after the pointer up, the context menu will appear. setTimeout(() => simulateMouseClick(document.elementFromPoint(e.clientX, e.clientY), e.clientX, e.clientY, e.screenX, e.screenY)); }; if (navigator.userAgent.includes('Macintosh')) { @@ -811,7 +676,7 @@ export class DocumentViewInternal extends DocComponent Doc.Zip(this.Document) }); - (this.Document._type_collection !== CollectionViewType.Docking || !Doc.noviceMode) && constantItems.push({ description: 'Share', event: () => SharingManager.Instance.open(this.DocumentView()), icon: 'users' }); + (this.Document._type_collection !== CollectionViewType.Docking || !Doc.noviceMode) && constantItems.push({ description: 'Share', event: () => SharingManager.Instance.open(this._docView), icon: 'users' }); if (this._props.removeDocument && Doc.ActiveDashboard !== this.Document) { // need option to gray out menu items ... preferably with a '?' that explains why they're grayed out (eg., no permissions) constantItems.push({ description: 'Close', event: this.deleteClicked, icon: 'times' }); @@ -877,63 +742,16 @@ export class DocumentViewInternal extends DocComponent this._rootSelected; panelHeight = () => this._props.PanelHeight() - this.headerMargin; screenToLocalContent = () => this._props.ScreenToLocalTransform().translate(0, -this.headerMargin); - onClickFunc: any = () => (this.disableClickScriptFunc ? undefined : this.onClickHandler); + onClickFunc = this.disableClickScriptFunc ? undefined : () => this.onClickHandler; setHeight = (height: number) => !this._props.suppressSetHeight && (this.layoutDoc._height = height); - setContentView = action((view: { getAnchor?: (addAsAnnotation: boolean) => Doc; forward?: () => boolean; back?: () => boolean }) => (this._componentView = view)); - @observable _isContentActive: boolean | undefined = undefined; - + setContentView = action((view: ViewBoxInterface) => (this._componentView = view)); isContentActive = (): boolean | undefined => this._isContentActive; childFilters = () => [...this._props.childFilters(), ...StrListCast(this.layoutDoc.childFilters)]; - /// disable pointer events on content when there's an enabled onClick script (but not the browse script) and the contents aren't forced active, or if contents are marked inactive - @computed get _contentPointerEvents() { - TraceMobx(); - return this._props.contentPointerEvents ?? - ((!this.disableClickScriptFunc && // - this.onClickHandler && - !this._props.onBrowseClick?.() && - this.isContentActive() !== true) || - this.isContentActive() === false) - ? 'none' - : this.pointerEvents; - } contentPointerEvents = () => this._contentPointerEvents; - @computed get contents() { - TraceMobx(); - const isInk = this.layoutDoc._layout_isSvg && !this._props.LayoutTemplateString; - const noBackground = this.Document.isGroup && !this._props.LayoutTemplateString?.includes(KeyValueBox.name) && (!this.layoutDoc.backgroundColor || this.layoutDoc.backgroundColor === 'transparent'); - return ( -
- - {this.layoutDoc.layout_hideAllLinks ? null : this.allLinkEndpoints} -
- ); - } anchorPanelWidth = () => this._props.PanelWidth() || 1; anchorPanelHeight = () => this._props.PanelHeight() || 1; @@ -950,33 +768,12 @@ export class DocumentViewInternal extends DocComponent - (link.link_matchEmbeddings ? link.link_anchor_1 === this.Document : Doc.AreProtosEqual(link.link_anchor_1 as Doc, this.Document)) || - (link.link_matchEmbeddings ? link.link_anchor_2 === this.Document : Doc.AreProtosEqual(link.link_anchor_2 as Doc, this.Document)) || - ((link.link_anchor_1 as Doc)?.layout_unrendered && Doc.AreProtosEqual((link.link_anchor_1 as Doc)?.annotationOn as Doc, this.Document)) || - ((link.link_anchor_2 as Doc)?.layout_unrendered && Doc.AreProtosEqual((link.link_anchor_2 as Doc)?.annotationOn as Doc, this.Document)) - ); - } - @computed get allLinks() { - TraceMobx(); - return LinkManager.Instance.getAllRelatedLinks(this.Document); - } - hideLink = computedFn((link: Doc) => () => (link.link_displayLine = false)); - @computed get allLinkEndpoints() { + + removeLinkByHiding = (link: Doc) => () => (link.link_displayLine = false); + allLinkEndpoints = () => { // the small blue dots that mark the endpoints of links - TraceMobx(); if (this._componentView instanceof KeyValueBox || this._props.hideLinkAnchors || this.layoutDoc.layout_hideLinkAnchors || this._props.dontRegisterView || this.layoutDoc.layout_unrendered) return null; - const filtered = DocUtils.FilterDocs(this.directLinks, this._props.childFilters?.() ?? [], []).filter(d => d.link_displayLine || Doc.UserDoc().showLinkLines); - return filtered.map(link => ( + return this.filteredLinks.map(link => (
)); - } - - static recordAudioAnnotation(dataDoc: Doc, field: string, onRecording?: (stop: () => void) => void, onEnd?: () => void) { - let gumStream: any; - let recorder: any; - navigator.mediaDevices - .getUserMedia({ - audio: true, - }) - .then(function (stream) { - let audioTextAnnos = Cast(dataDoc[field + '_audioAnnotations_text'], listSpec('string'), null); - if (audioTextAnnos) audioTextAnnos.push(''); - else audioTextAnnos = dataDoc[field + '_audioAnnotations_text'] = new List(['']); - DictationManager.Controls.listen({ - interimHandler: value => (audioTextAnnos[audioTextAnnos.length - 1] = value), - continuous: { indefinite: false }, - }).then(results => { - if (results && [DictationManager.Controls.Infringed].includes(results)) { - DictationManager.Controls.stop(); - } - onEnd?.(); - }); + }; - gumStream = stream; - recorder = new MediaRecorder(stream); - recorder.ondataavailable = async (e: any) => { - const [{ result }] = await Networking.UploadFilesToServer({ file: e.data }); - if (!(result instanceof Error)) { - const audioField = new AudioField(result.accessPaths.agnostic.client); - const audioAnnos = Cast(dataDoc[field + '_audioAnnotations'], listSpec(AudioField), null); - if (audioAnnos === undefined) { - dataDoc[field + '_audioAnnotations'] = new List([audioField]); - } else { - audioAnnos.push(audioField); - } - } - }; - //runInAction(() => (dataDoc.audioAnnoState = 'recording')); - recorder.start(); - const stopFunc = () => { - recorder.stop(); - DictationManager.Controls.stop(false); - runInAction(() => (dataDoc.audioAnnoState = 'stopped')); - gumStream.getAudioTracks()[0].stop(); - }; - if (onRecording) onRecording(stopFunc); - else setTimeout(stopFunc, 5000); - }); - } - playAnnotation = () => { - const self = this; - const audioAnnoState = this.dataDoc.audioAnnoState ?? 'stopped'; - const audioAnnos = Cast(this.dataDoc[this.LayoutFieldKey + '_audioAnnotations'], listSpec(AudioField), null); - const anno = audioAnnos?.lastElement(); - if (anno instanceof AudioField) { - switch (audioAnnoState) { - case 'stopped': - this.dataDoc[AudioPlay] = new Howl({ - src: [anno.url.href], - format: ['mp3'], - autoplay: true, - loop: false, - volume: 0.5, - onend: action(() => (self.dataDoc.audioAnnoState = 'stopped')), - }); - this.dataDoc.audioAnnoState = 'playing'; - break; - case 'playing': - this.dataDoc[AudioPlay]?.stop(); - this.dataDoc.audioAnnoState = 'stopped'; - break; - } - } + viewBoxContents = () => { + TraceMobx(); + const isInk = this.layoutDoc._layout_isSvg && !this._props.LayoutTemplateString; + const noBackground = this.Document.isGroup && !this._props.LayoutTemplateString?.includes(KeyValueBox.name) && (!this.layoutDoc.backgroundColor || this.layoutDoc.backgroundColor === 'transparent'); + return ( +
+ + {this.layoutDoc.layout_hideAllLinks ? null : this.allLinkEndpoints()} +
+ ); }; captionStyleProvider = (doc: Opt, props: Opt, property: string) => this._props?.styleProvider?.(doc, props, property + ':caption'); - @observable _changingTitleField = false; - @observable _dropDownInnerWidth = 0; - fieldsDropdown = (inputOptions: string[], dropdownWidth: number, placeholder: string, onChange: (val: string | number) => void, onClose: () => void) => { - const filteredOptions = new Set(inputOptions); - const scaling = this.titleHeight / 30; /* height of Dropdown */ - Object.entries(DocOptions) - .filter(opts => opts[1].filterable) - .forEach((pair: [string, FInfo]) => filteredOptions.add(pair[0])); - filteredOptions.add(StrCast(this.layoutDoc.layout_showTitle)); - const options = Array.from(filteredOptions) - .filter(f => f) - .map(facet => ({ val: facet, text: facet })); + fieldsDropdown = (reqdFields: string[], dropdownWidth: number, placeholder: string, onChange: (val: string | number) => void, onClose: () => void) => { + const filteredFields = Object.entries(DocOptions).reduce((set, [field, opts]) => (opts.filterable ? set.add(field) : set), new Set(reqdFields)); return (
r && (this._dropDownInnerWidth = Number(getComputedStyle(r).width.replace('px', ''))))} + ref={action((r: any) => r && (this._titleDropDownInnerWidth = Number(getComputedStyle(r).width.replace('px', ''))))} onPointerDown={action(e => (this._changingTitleField = true))} - style={{ width: 'max-content', transformOrigin: 'left', transform: `scale(${scaling})` }}> + style={{ width: 'max-content', transformOrigin: 'left', transform: `scale(${this.titleHeight / 30 /* height of Dropdown */})` }}> !isOpen && (this._changingTitleField = false))} selectedVal={placeholder} @@ -1100,7 +844,7 @@ export class DocumentViewInternal extends DocComponent ({ val: facet, text: facet }))} width={100} fillWidth /> @@ -1108,43 +852,24 @@ export class DocumentViewInternal extends DocComponent ); }; - @computed get innards() { - TraceMobx(); + /** + * displays a 'title' at the top of a document. The title contents default to the 'title' field, but can be changed to one or more fields by + * setting layout_showTitle using the format: field1[;field2[...][:hover]] + * from the UI, this is done by clicking the title field and prefixin the format with '#'. eg., #field1[;field2;...][:hover] + **/ + titleView = () => { const showTitle = this.layout_showTitle?.split(':')[0]; const showTitleHover = this.layout_showTitle?.includes(':hover'); - const captionView = !this.layout_showCaption ? null : ( -
- -
- ); - const targetDoc = showTitle?.startsWith('_') ? this.layoutDoc : this.Document; - const background = StrCast( - this.layoutDoc.layout_headingColor, - StrCast(SharingManager.Instance.users.find(u => u.user.email === this.dataDoc.author)?.sharingDoc.headingColor, StrCast(Doc.SharingDoc().headingColor, SettingsManager.userBackgroundColor)) - ); - const dropdownWidth = this._titleRef.current?._editing || this._changingTitleField ? Math.max(10, (this._dropDownInnerWidth * this.titleHeight) / 30) : 0; - const sidebarWidthPercent = +StrCast(this.layoutDoc.layout_sidebarWidthPercent).replace('%', ''); - // displays a 'title' at the top of a document. The title contents default to the 'title' field, but can be changed to one or more fields by - // setting layout_showTitle using the format: field1[;field2[...][:hover]] - // from the UI, this is done by clicking the title field and prefixin the format with '#'. eg., #field1[;field2;...][:hover] - const titleView = !showTitle ? null : ( + + const targetDoc = showTitle?.startsWith('_') ? this.layoutDoc : this.Document; + const background = StrCast( + this.layoutDoc.layout_headingColor, + StrCast(SharingManager.Instance.users.find(u => u.user.email === this.dataDoc.author)?.sharingDoc.headingColor, StrCast(Doc.SharingDoc().headingColor, SettingsManager.userBackgroundColor)) + ); + const dropdownWidth = this._titleRef.current?._editing || this._changingTitleField ? Math.max(10, (this._titleDropDownInnerWidth * this.titleHeight) / 30) : 0; + const sidebarWidthPercent = +StrCast(this.layoutDoc.layout_sidebarWidthPercent).replace('%', ''); + + return !showTitle ? null : (
{ @@ -1206,19 +931,36 @@ export class DocumentViewInternal extends DocComponent
); - return this._props.hideTitle || (!showTitle && !this.layout_showCaption) ? ( - this.contents - ) : ( -
- {titleView} - {this.contents} - {captionView} + }; + + captionView = () => { + return !this.layout_showCaption ? null : ( +
+
); - } + }; renderDoc = (style: object) => { TraceMobx(); + const showTitle = this.layout_showTitle?.split(':')[0]; return !DocCast(this.Document) || GetEffectiveAcl(this.dataDoc) === AclPrivate ? null : this.docContents ?? ( @@ -1234,49 +976,22 @@ export class DocumentViewInternal extends DocComponent - {this.innards} + {this._props.hideTitle || (!showTitle && !this.layout_showCaption) ? ( + this.viewBoxContents() + ) : ( +
+ {this.titleView()} + {this.viewBoxContents()} + {this.captionView()} +
+ )} {this.widgetDecorations ?? null}
); }; - /** - * returns an entrance animation effect function to wrap a JSX element - * @param presEffectDoc presentation effects document that specifies the animation effect parameters - * @returns a function that will wrap a JSX animation element wrapping any JSX element - */ - public static AnimationEffect(renderDoc: JSX.Element, presEffectDoc: Opt, root: Doc) { - const dir = presEffectDoc?.presentation_effectDirection ?? presEffectDoc?.followLinkAnimDirection; - const effectProps = { - left: dir === PresEffectDirection.Left, - right: dir === PresEffectDirection.Right, - top: dir === PresEffectDirection.Top, - bottom: dir === PresEffectDirection.Bottom, - opposite: true, - delay: 0, - duration: Cast(presEffectDoc?.presentation_transition, 'number', Cast(presEffectDoc?.followLinkTransitionTime, 'number', null)), - }; - //prettier-ignore - switch (StrCast(presEffectDoc?.presentation_effect, StrCast(presEffectDoc?.followLinkAnimEffect))) { - default: - case PresEffect.None: return renderDoc; - case PresEffect.Zoom: return {renderDoc}; - case PresEffect.Fade: return {renderDoc}; - case PresEffect.Flip: return {renderDoc}; - case PresEffect.Rotate: return {renderDoc}; - case PresEffect.Bounce: return {renderDoc}; - case PresEffect.Roll: return {renderDoc}; - case PresEffect.Lightspeed: return {renderDoc}; - } - } - @computed get highlighting() { - return this._props.styleProvider?.(this.Document, { ...this._props, fieldKey: '' }, StyleProp.Highlighting); - } - @computed get borderPath() { - return this._props.styleProvider?.(this.Document, { ...this._props, fieldKey: '' }, StyleProp.BorderPath); - } render() { TraceMobx(); const highlighting = this.highlighting; @@ -1304,10 +1019,10 @@ export class DocumentViewInternal extends DocComponent (!SnappingManager.IsDragging || SnappingManager.CanEmbed) && Doc.BrushDoc(this.Document)} onPointerOver={e => (!SnappingManager.IsDragging || SnappingManager.CanEmbed) && Doc.BrushDoc(this.Document)} - onPointerLeave={e => !isParentOf(this.ContentDiv, document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y)) && Doc.UnBrushDoc(this.Document)} + onPointerLeave={e => !isParentOf(this._contentDiv, document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y)) && Doc.UnBrushDoc(this.Document)} style={{ borderRadius: this.borderRounding, - pointerEvents: this.pointerEvents === 'visiblePainted' ? 'none' : this.pointerEvents, // visible painted means that the underlying doc contents are irregular and will process their own pointer events (otherwise, the contents are expected to fill the entire doc view box so we can handle pointer events here) + pointerEvents: this._pointerEvents === 'visiblePainted' ? 'none' : this._pointerEvents, // visible painted means that the underlying doc contents are irregular and will process their own pointer events (otherwise, the contents are expected to fill the entire doc view box so we can handle pointer events here) }}> <> {this._componentView instanceof KeyValueBox ? renderDoc : DocumentViewInternal.AnimationEffect(renderDoc, this.Document[Animation], this.Document)} @@ -1316,176 +1031,124 @@ export class DocumentViewInternal extends DocComponent ); } -} -@observer -export class DocumentView extends ObservableReactComponent { - public static ROOT_DIV = 'documentView-effectsWrapper'; - - constructor(props: any) { - super(props); - makeObservable(this); + /** + * returns an entrance animation effect function to wrap a JSX element + * @param presEffectDoc presentation effects document that specifies the animation effect parameters + * @returns a function that will wrap a JSX animation element wrapping any JSX element + */ + public static AnimationEffect(renderDoc: JSX.Element, presEffectDoc: Opt, root: Doc) { + const dir = presEffectDoc?.presentation_effectDirection ?? presEffectDoc?.followLinkAnimDirection; + const effectProps = { + left: dir === PresEffectDirection.Left, + right: dir === PresEffectDirection.Right, + top: dir === PresEffectDirection.Top, + bottom: dir === PresEffectDirection.Bottom, + opposite: true, + delay: 0, + duration: Cast(presEffectDoc?.presentation_transition, 'number', Cast(presEffectDoc?.followLinkTransitionTime, 'number', null)), + }; + //prettier-ignore + switch (StrCast(presEffectDoc?.presentation_effect, StrCast(presEffectDoc?.followLinkAnimEffect))) { + default: + case PresEffect.None: return renderDoc; + case PresEffect.Zoom: return {renderDoc}; + case PresEffect.Fade: return {renderDoc}; + case PresEffect.Flip: return {renderDoc}; + case PresEffect.Rotate: return {renderDoc}; + case PresEffect.Bounce: return {renderDoc}; + case PresEffect.Roll: return {renderDoc}; + case PresEffect.Lightspeed: return {renderDoc}; + } } + public static recordAudioAnnotation(dataDoc: Doc, field: string, onRecording?: (stop: () => void) => void, onEnd?: () => void) { + let gumStream: any; + let recorder: any; + navigator.mediaDevices + .getUserMedia({ + audio: true, + }) + .then(function (stream) { + let audioTextAnnos = Cast(dataDoc[field + '_audioAnnotations_text'], listSpec('string'), null); + if (audioTextAnnos) audioTextAnnos.push(''); + else audioTextAnnos = dataDoc[field + '_audioAnnotations_text'] = new List(['']); + DictationManager.Controls.listen({ + interimHandler: value => (audioTextAnnos[audioTextAnnos.length - 1] = value), + continuous: { indefinite: false }, + }).then(results => { + if (results && [DictationManager.Controls.Infringed].includes(results)) { + DictationManager.Controls.stop(); + } + onEnd?.(); + }); - @observable _selected = false; - public get IsSelected() { - return this._selected; - } - public set IsSelected(val) { - runInAction(() => (this._selected = val)); - } - @observable public static LongPress = false; - @computed public static get exploreMode() { - return () => (SnappingManager.ExploreMode ? ScriptField.MakeScript('CollectionBrowseClick(documentView, clientX, clientY)', { documentView: 'any', clientX: 'number', clientY: 'number' })! : undefined); + gumStream = stream; + recorder = new MediaRecorder(stream); + recorder.ondataavailable = async (e: any) => { + const [{ result }] = await Networking.UploadFilesToServer({ file: e.data }); + if (!(result instanceof Error)) { + const audioField = new AudioField(result.accessPaths.agnostic.client); + const audioAnnos = Cast(dataDoc[field + '_audioAnnotations'], listSpec(AudioField), null); + if (audioAnnos === undefined) { + dataDoc[field + '_audioAnnotations'] = new List([audioField]); + } else { + audioAnnos.push(audioField); + } + } + }; + //runInAction(() => (dataDoc.audioAnnoState = 'recording')); + recorder.start(); + const stopFunc = () => { + recorder.stop(); + DictationManager.Controls.stop(false); + runInAction(() => (dataDoc.audioAnnoState = 'stopped')); + gumStream.getAudioTracks()[0].stop(); + }; + if (onRecording) onRecording(stopFunc); + else setTimeout(stopFunc, 5000); + }); } - @observable private _docViewInternal: DocumentViewInternal | undefined | null = undefined; - @observable private _htmlOverlayText: Opt = undefined; - @observable private _isHovering = false; - private _htmlOverlayEffect: Opt; +} - public get displayName() { - return 'DocumentView(' + this.Document?.title + ')'; - } // this makes mobx trace() statements more descriptive +@observer +export class DocumentView extends DocComponent() { + public static ROOT_DIV = 'documentView-effectsWrapper'; + public get displayName() { return 'DocumentView(' + this.Document?.title + ')'; } // prettier-ignore public ContentRef = React.createRef(); - public ViewTimer: NodeJS.Timeout | undefined; // timer for res - public AnimEffectTimer: NodeJS.Timeout | undefined; // timer for res + private _htmlOverlayEffect: Opt; private _disposers: { [name: string]: IReactionDisposer } = {}; - public clearViewTransition = () => { - this.ViewTimer && clearTimeout(this.ViewTimer); - this.layoutDoc._viewTransition = undefined; - }; - playAnnotation = () => this._docViewInternal?.playAnnotation(); - noOnClick = () => this._docViewInternal?.noOnClick(); - makeIntoPortal = () => this._docViewInternal?.makeIntoPortal(); - setToggleDetail = () => this._docViewInternal?.setToggleDetail(); - onContextMenu = (e?: React.MouseEvent, pageX?: number, pageY?: number) => this._docViewInternal?.onContextMenu?.(e, pageX, pageY); - cleanupPointerEvents = () => this._docViewInternal?.cleanupPointerEvents(); - public startDragging = (x: number, y: number, dropAction: dropActionType, hideSource = false) => this._docViewInternal?.startDragging(x, y, dropAction, hideSource); + private _viewTimer: NodeJS.Timeout | undefined; + private _animEffectTimer: NodeJS.Timeout | undefined; - public showContextMenu = (pageX: number, pageY: number) => this._docViewInternal?.onContextMenu(undefined, pageX, pageY); - - public setTextHtmlOverlay = action((text: string | undefined, effect?: Doc) => { - this._htmlOverlayText = text; - this._htmlOverlayEffect = effect; - }); - public setAnimateScaling = action((scale: number, time?: number) => { - if (this._docViewInternal) { - this._docViewInternal._animateScalingTo = scale; - this._docViewInternal._animateScaleTime = time; - } - }); - public setAnimEffect = (presEffect: Doc, timeInMs: number, afterTrans?: () => void) => { - this.AnimEffectTimer && clearTimeout(this.AnimEffectTimer); - this.Document[Animation] = presEffect; - this.AnimEffectTimer = setTimeout(() => (this.Document[Animation] = undefined), timeInMs); - }; - public setViewTransition = (transProp: string, timeInMs: number, afterTrans?: () => void, dataTrans = false) => { - this.layoutDoc._viewTransition = `${transProp} ${timeInMs}ms`; - if (dataTrans) this.Document._dataTransition = `${transProp} ${timeInMs}ms`; - this.ViewTimer && clearTimeout(this.ViewTimer); - return (this.ViewTimer = setTimeout(() => { - this.layoutDoc._viewTransition = undefined; - this.Document._dataTransition = 'inherit'; - afterTrans?.(); - }, timeInMs + 10)); - }; - public static SetViewTransition(docs: Doc[], transProp: string, timeInMs: number, afterTrans?: () => void, dataTrans = false) { - docs.forEach(doc => { - doc._viewTransition = `${transProp} ${timeInMs}ms`; - dataTrans && (doc.dataTransition = `${transProp} ${timeInMs}ms`); - }); - return setTimeout( - () => - docs.forEach(doc => { - doc._viewTransition = undefined; - dataTrans && (doc.dataTransition = 'inherit'); - afterTrans?.(); - }), - timeInMs + 10 - ); + @computed public static get exploreMode() { + return () => (SnappingManager.ExploreMode ? ScriptField.MakeScript('CollectionBrowseClick(documentView, clientX, clientY)', { documentView: 'any', clientX: 'number', clientY: 'number' })! : undefined); } - // shows a stacking view collection (by default, but the user can change) of all documents linked to the source - public static showBackLinks(linkAnchor: Doc) { - const docId = Doc.CurrentUserEmail + Doc.GetProto(linkAnchor)[Id] + '-pivotish'; - // prettier-ignore - DocServer.GetRefField(docId).then(docx => - LightboxView.Instance.SetLightboxDoc( - (docx as Doc) ?? // reuse existing pivot view of documents, or else create a new collection - Docs.Create.StackingDocument([], { title: linkAnchor.title + '-pivot', _width: 500, _height: 500, target: linkAnchor, onViewMounted: ScriptField.MakeScript('updateLinkCollection(this, this.target)') }, docId) - ) - ); + constructor(props: DocumentViewProps) { + super(props); + makeObservable(this); } - toggleFollowLink = (zoom?: boolean, setTargetToggle?: boolean): void => this._docViewInternal?.toggleFollowLink(zoom, setTargetToggle); - get Document() { - return this._props.Document; - } - get topMost() { - return this._props.renderDepth === 0; - } - get dataDoc() { - return this._docViewInternal?.dataDoc ?? this.Document; - } - get ContentDiv() { - return this._docViewInternal?.ContentDiv; - } - get ComponentView() { - return this._docViewInternal?._componentView; - } - get allLinks() { - return (this._docViewInternal?.allLinks || []).filter(link => !link.link_matchEmbeddings || link.link_anchor_1 === this.Document || link.link_anchor_2 === this.Document); - } - get LayoutFieldKey() { - return this._docViewInternal?.LayoutFieldKey || 'layout'; - } - @computed get layout_fitWidth() { - return this._props.layout_fitWidth?.(this.layoutDoc) ?? this.layoutDoc?.layout_fitWidth; - } - @computed get anchorViewDoc() { - return this._props.LayoutTemplateString?.includes('link_anchor_2') ? DocCast(this.Document['link_anchor_2']) : this._props.LayoutTemplateString?.includes('link_anchor_1') ? DocCast(this.Document['link_anchor_1']) : undefined; - } - @computed get hideLinkButton() { - return ( - this._props.hideLinkButton || - this._props.renderDepth === -1 || // - (this.IsSelected && this._props.renderDepth) || - !this._isHovering || - (!this.IsSelected && this.layoutDoc.layout_hideLinkButton) || - SnappingManager.IsDragging || - SnappingManager.IsResizing - ); - } - hideLinkCount = () => (this.hideLinkButton ? true : false); + // want the htmloverlay to be able to fade in but we also want it to be display 'none' until it is needed. + // unfortunately, CSS can't transition animate any properties for something that is display 'none'. + // so we need to first activate the div, then, after a render timeout, start the opacity transition. + @observable private _enableHtmlOverlayTransitions: boolean = false; + @observable private _docViewInternal: DocumentViewInternal | undefined | null = undefined; + @observable private _htmlOverlayText: Opt = undefined; + @observable private _isHovering = false; + @observable private _selected = false; + @observable public static LongPress = false; - @computed get linkCountView() { - return ; - } - /** - * path of DocumentViews hat contains this DocumentView (does not includes this DocumentView thouhg) - */ - @computed get containerViewPath() { - return this._props.containerViewPath; - } - @computed get layoutDoc() { - return Doc.Layout(this.Document, this._props.LayoutTemplate?.()); - } - @computed get nativeWidth() { - return this._props.LayoutTemplateString?.includes(KeyValueBox.name) ? 0 : returnVal(this._props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this._props.TemplateDataDocument, !this.layout_fitWidth)); - } - @computed get nativeHeight() { - return this._props.LayoutTemplateString?.includes(KeyValueBox.name) ? 0 : returnVal(this._props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this._props.TemplateDataDocument, !this.layout_fitWidth)); - } - @computed get shouldNotScale() { + @computed private get shouldNotScale() { return (this.layout_fitWidth && !this.nativeWidth) || this._props.LayoutTemplateString?.includes(KeyValueBox.name) || [CollectionViewType.Docking].includes(this.Document._type_collection as any); } - @computed get effectiveNativeWidth() { + @computed private get effectiveNativeWidth() { return this.shouldNotScale ? 0 : this.nativeWidth || NumCast(this.layoutDoc.width); } - @computed get effectiveNativeHeight() { + @computed private get effectiveNativeHeight() { return this.shouldNotScale ? 0 : this.nativeHeight || NumCast(this.layoutDoc.height); } - @computed get nativeScaling() { + @computed private get nativeScaling() { if (this.shouldNotScale) return 1; const minTextScale = this.Document.type === DocumentType.RTF ? 0.1 : 0; if (this.layout_fitWidth || this._props.PanelHeight() / (this.effectiveNativeHeight || 1) > this._props.PanelWidth() / (this.effectiveNativeWidth || 1)) { @@ -1493,19 +1156,19 @@ export class DocumentView extends ObservableReactComponent { } return Math.max(minTextScale, this._props.PanelHeight() / (this.effectiveNativeHeight || 1)); // height-limited or unscaled } - @computed get panelWidth() { + @computed private get panelWidth() { return this.effectiveNativeWidth ? this.effectiveNativeWidth * this.nativeScaling : this._props.PanelWidth(); } - @computed get panelHeight() { + @computed private get panelHeight() { if (this.effectiveNativeHeight && (!this.layout_fitWidth || !this.layoutDoc.layout_reflowVertical)) { return Math.min(this._props.PanelHeight(), this.effectiveNativeHeight * this.nativeScaling); } return this._props.PanelHeight(); } - @computed get Xshift() { + @computed private get Xshift() { return this.effectiveNativeWidth ? Math.max(0, (this._props.PanelWidth() - this.effectiveNativeWidth * this.nativeScaling) / 2) : 0; } - @computed get Yshift() { + @computed private get Yshift() { return this.effectiveNativeWidth && this.effectiveNativeHeight && Math.abs(this.Xshift) < 0.001 && @@ -1513,41 +1176,99 @@ export class DocumentView extends ObservableReactComponent { ? Math.max(0, (this._props.PanelHeight() - this.effectiveNativeHeight * this.nativeScaling) / 2) : 0; } - @computed get centeringX() { - return this._props.dontCenter?.includes('x') ? 0 : this.Xshift; + @computed private get hideLinkButton() { + return ( + this._props.hideLinkButton || + this._props.renderDepth === -1 || // + (this.IsSelected && this._props.renderDepth) || + !this._isHovering || + (!this.IsSelected && this.layoutDoc.layout_hideLinkButton) || + SnappingManager.IsDragging || + SnappingManager.IsResizing + ); + } + + componentDidMount() { + runInAction(() => this.Document[DocViews].add(this)); + this._disposers.onViewMounted = reaction(() => ScriptCast(this.Document.onViewMounted)?.script?.run({ this: this.Document }).result, emptyFunction); + !BoolCast(this.Document.dontRegisterView, this._props.dontRegisterView) && DocumentManager.Instance.AddView(this); + } + + componentWillUnmount() { + runInAction(() => this.Document[DocViews].delete(this)); + Object.values(this._disposers).forEach(disposer => disposer?.()); + !BoolCast(this.Document.dontRegisterView, this._props.dontRegisterView) && DocumentManager.Instance.RemoveView(this); } - @computed get centeringY() { - return this._props.dontCenter?.includes('y') ? 0 : this.Yshift; + + public set IsSelected(val) { runInAction(() => (this._selected = val)); } // prettier-ignore + public get IsSelected() { return this._selected; } // prettier-ignore + public get topMost() { return this._props.renderDepth === 0; } // prettier-ignore + public get ContentDiv() { return this._docViewInternal?._contentDiv; } // prettier-ignore + public get ComponentView() { return this._docViewInternal?._componentView; } // prettier-ignore + public get allLinks() { return this._docViewInternal?._allLinks ?? []; } // prettier-ignore + + get LayoutFieldKey() { + return Doc.LayoutFieldKey(this.Document, this._props.LayoutTemplateString); } - @computed get CollectionFreeFormView() { - return this.CollectionFreeFormDocumentView?.CollectionFreeFormView; + @computed get layout_fitWidth() { + return this._props.layout_fitWidth?.(this.layoutDoc) ?? this.layoutDoc?.layout_fitWidth; } - @computed get CollectionFreeFormDocumentView() { - return this._props.CollectionFreeFormDocumentView?.(); + @computed get anchorViewDoc() { + return this._props.LayoutTemplateString?.includes('link_anchor_2') ? DocCast(this.Document['link_anchor_2']) : this._props.LayoutTemplateString?.includes('link_anchor_1') ? DocCast(this.Document['link_anchor_1']) : undefined; } - public toggleNativeDimensions = () => this._docViewInternal && this.Document.type !== DocumentType.INK && Doc.toggleNativeDimensions(this.layoutDoc, this._docViewInternal.NativeDimScaling, this._props.PanelWidth(), this._props.PanelHeight()); - public getBounds = () => { - if (!this._docViewInternal?.ContentDiv || this._props.treeViewDoc || Doc.AreProtosEqual(this.Document, Doc.UserDoc())) { + @computed get getBounds() { + if (!this._docViewInternal?._contentDiv || this._props.treeViewDoc || Doc.AreProtosEqual(this.Document, Doc.UserDoc())) { return undefined; } if (this._docViewInternal._componentView?.screenBounds?.()) { return this._docViewInternal._componentView.screenBounds(); } - const xf = this._docViewInternal._props.ScreenToLocalTransform().scale(this.nativeScaling).inverse(); + const xf = this.screenToContentsTransform().scale(this.nativeScaling).inverse(); const [[left, top], [right, bottom]] = [xf.transformPoint(0, 0), xf.transformPoint(this.panelWidth, this.panelHeight)]; - if (this._docViewInternal._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) { - const docuBox = this._docViewInternal.ContentDiv.getElementsByClassName('linkAnchorBox-cont'); + if (this._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) { + const docuBox = this._docViewInternal._contentDiv.getElementsByClassName('linkAnchorBox-cont'); if (docuBox.length) return { ...docuBox[0].getBoundingClientRect(), center: undefined }; } return { left, top, right, bottom }; + } + + @computed get nativeWidth() { + return this._props.LayoutTemplateString?.includes(KeyValueBox.name) ? 0 : returnVal(this._props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this._props.TemplateDataDocument, !this.layout_fitWidth)); + } + @computed get nativeHeight() { + return this._props.LayoutTemplateString?.includes(KeyValueBox.name) ? 0 : returnVal(this._props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this._props.TemplateDataDocument, !this.layout_fitWidth)); + } + @computed public get centeringX() { return this._props.dontCenter?.includes('x') ? 0 : this.Xshift; } // prettier-ignore + @computed public get centeringY() { return this._props.dontCenter?.includes('y') ? 0 : this.Yshift; } // prettier-ignore + + /** + * path of DocumentViews hat contains this DocumentView (does not includes this DocumentView thouhg) + */ + public get containerViewPath() { return this._props.containerViewPath; } // prettier-ignore + public get CollectionFreeFormView() { return this.CollectionFreeFormDocumentView?.CollectionFreeFormView; } // prettier-ignore + public get CollectionFreeFormDocumentView() { return this._props.CollectionFreeFormDocumentView?.(); } // prettier-ignore + + public clearViewTransition = () => { + this._viewTimer && clearTimeout(this._viewTimer); + this.layoutDoc._viewTransition = undefined; }; + public noOnClick = () => this._docViewInternal?.noOnClick(); + public makeIntoPortal = () => this._docViewInternal?.makeIntoPortal(); + public toggleFollowLink = (zoom?: boolean, setTargetToggle?: boolean): void => this._docViewInternal?.toggleFollowLink(zoom, setTargetToggle); + public setToggleDetail = () => this._docViewInternal?.setToggleDetail(); + public onContextMenu = (e?: React.MouseEvent, pageX?: number, pageY?: number) => this._docViewInternal?.onContextMenu?.(e, pageX, pageY); + public cleanupPointerEvents = () => this._docViewInternal?.cleanupPointerEvents(); + public startDragging = (x: number, y: number, dropAction: dropActionType, hideSource = false) => this._docViewInternal?.startDragging(x, y, dropAction, hideSource); + public showContextMenu = (pageX: number, pageY: number) => this._docViewInternal?.onContextMenu(undefined, pageX, pageY); + + public toggleNativeDimensions = () => this._docViewInternal && this.Document.type !== DocumentType.INK && Doc.toggleNativeDimensions(this.layoutDoc, this.NativeDimScaling() ?? 1, this._props.PanelWidth(), this._props.PanelHeight()); public iconify(finished?: () => void, animateTime?: number) { this.ComponentView?.updateIcon?.(); - const animTime = this._docViewInternal?._animateScaleTime; + const animTime = this._docViewInternal?.animateScaleTime(); runInAction(() => this._docViewInternal && animateTime !== undefined && (this._docViewInternal._animateScaleTime = animateTime)); const finalFinished = action(() => { finished?.(); @@ -1564,13 +1285,58 @@ export class DocumentView extends ObservableReactComponent { this._props.bringToFront(this.Document); } } - @undoBatch - setCustomView = (custom: boolean, layout: string): void => { + + public playAnnotation = () => { + const self = this; + const audioAnnoState = this.dataDoc.audioAnnoState ?? 'stopped'; + const audioAnnos = Cast(this.dataDoc[this.LayoutFieldKey + '_audioAnnotations'], listSpec(AudioField), null); + const anno = audioAnnos?.lastElement(); + if (anno instanceof AudioField) { + switch (audioAnnoState) { + case 'stopped': + this.dataDoc[AudioPlay] = new Howl({ + src: [anno.url.href], + format: ['mp3'], + autoplay: true, + loop: false, + volume: 0.5, + onend: action(() => (self.dataDoc.audioAnnoState = 'stopped')), + }); + this.dataDoc.audioAnnoState = 'playing'; + break; + case 'playing': + this.dataDoc[AudioPlay]?.stop(); + this.dataDoc.audioAnnoState = 'stopped'; + break; + } + } + }; + + public setTextHtmlOverlay = action((text: string | undefined, effect?: Doc) => { + this._htmlOverlayText = text; + this._htmlOverlayEffect = effect; + }); + public setAnimateScaling = action((scale: number, time?: number) => { + if (this._docViewInternal) { + this._docViewInternal._animateScalingTo = scale; + this._docViewInternal._animateScaleTime = time; + } + }); + public setAnimEffect = (presEffect: Doc, timeInMs: number, afterTrans?: () => void) => { + this._animEffectTimer && clearTimeout(this._animEffectTimer); + this.Document[Animation] = presEffect; + this._animEffectTimer = setTimeout(() => (this.Document[Animation] = undefined), timeInMs); + }; + public setViewTransition = (transProp: string, timeInMs: number, afterTrans?: () => void, dataTrans = false) => { + this._viewTimer = DocumentView.SetViewTransition([this.layoutDoc], transProp, timeInMs, this._viewTimer, afterTrans, dataTrans); + }; + + public setCustomView = undoable((custom: boolean, layout: string): void => { Doc.setNativeView(this.Document); custom && DocUtils.makeCustomViewClicked(this.Document, Docs.Create.StackingDocument, layout, undefined); - }; + }, 'set custom view'); - switchViews = (custom: boolean, view: string, finished?: () => void, useExistingLayout = false) => { + public switchViews = (custom: boolean, view: string, finished?: () => void, useExistingLayout = false) => { runInAction(() => this._docViewInternal && (this._docViewInternal._animateScalingTo = 0.1)); // shrink doc setTimeout( action(() => { @@ -1585,16 +1351,19 @@ export class DocumentView extends ObservableReactComponent { this._docViewInternal && (this._docViewInternal._animateScalingTo = 0); finished?.(); }), - this._docViewInternal ? Math.max(0, this._docViewInternal.animateScaleTime - 10) : 0 + Math.max(0, (this._docViewInternal?.animateScaleTime() ?? 0) - 10) ); }), - this._docViewInternal ? Math.max(0, this._docViewInternal?.animateScaleTime - 10) : 0 + Math.max(0, (this._docViewInternal?.animateScaleTime() ?? 0) - 10) ); }; + /** + * @returns a hierarchy path through the nested DocumentViews that display this view. The last element of the path is this view. + */ + public docViewPath = () => (this.containerViewPath ? [...this.containerViewPath(), this] : [this]); layout_fitWidthFunc = (doc: Doc) => BoolCast(this.layout_fitWidth); screenToLocalScale = () => this._props.ScreenToLocalTransform().Scale; - docViewPath = () => (this.containerViewPath ? [...this.containerViewPath(), this] : [this]); isSelected = () => this.IsSelected; select = (extendSelection: boolean, focusSelection?: boolean) => { if (this.IsSelected && SelectionManager.Views.length > 1) SelectionManager.DeselectView(this); @@ -1615,6 +1384,7 @@ export class DocumentView extends ObservableReactComponent { PanelWidth = () => this.panelWidth; PanelHeight = () => this.panelHeight; NativeDimScaling = () => this.nativeScaling; + hideLinkCount = () => (this.hideLinkButton ? true : false); selfView = () => this; /** * @returns Transform to the document view (in the coordinate system of whatever contains the DocumentView) @@ -1629,34 +1399,19 @@ export class DocumentView extends ObservableReactComponent { .translate(-this.centeringX, -this.centeringY) .scale(1 / this.nativeScaling); - componentDidMount() { - runInAction(() => this.Document[DocViews].add(this)); - this._disposers.onViewMounted = reaction(() => ScriptCast(this.Document.onViewMounted)?.script?.run({ this: this.Document }).result, emptyFunction); - !BoolCast(this.Document.dontRegisterView, this._props.dontRegisterView) && DocumentManager.Instance.AddView(this); - } - - componentWillUnmount() { - runInAction(() => this.Document[DocViews].delete(this)); - Object.values(this._disposers).forEach(disposer => disposer?.()); - !BoolCast(this.Document.dontRegisterView, this._props.dontRegisterView) && DocumentManager.Instance.RemoveView(this); - } - // want the htmloverlay to be able to fade in but we also want it to be display 'none' until it is needed. - // unfortunately, CSS can't transition animate any properties for something that is display 'none'. - // so we need to first activate the div, then, after a render timeout, start the opacity transition. - @observable enableHtmlOverlayTransitions: boolean = false; - @computed get htmlOverlay() { + htmlOverlay = () => { const effect = StrCast(this._htmlOverlayEffect?.presentation_effect, StrCast(this._htmlOverlayEffect?.followLinkAnimEffect)); return (
{ const val = r?.style.display !== 'none'; // if the outer overlay has been displayed, trigger the innner div to start it's opacity fade in transition - if (r && val !== this.enableHtmlOverlayTransitions) { - setTimeout(action(() => (this.enableHtmlOverlayTransitions = val))); + if (r && val !== this._enableHtmlOverlayTransitions) { + setTimeout(action(() => (this._enableHtmlOverlayTransitions = val))); } }} style={{ display: !this._htmlOverlayText ? 'none' : undefined }}> -
+
{DocumentViewInternal.AnimationEffect(
console.log('PARSE error', e)} renderInWrapper={false} jsx={StrCast(this._htmlOverlayText)} /> @@ -1667,11 +1422,7 @@ export class DocumentView extends ObservableReactComponent {
); - } - - @computed get infoUI() { - return this.ComponentView?.infoUI?.(); - } + }; render() { TraceMobx(); @@ -1685,14 +1436,14 @@ export class DocumentView extends ObservableReactComponent { className="contentFittingDocumentView-previewDoc" ref={this.ContentRef} style={{ - transition: 'inherit', // this._props.dataTransition, transform: `translate(${this.centeringX}px, ${this.centeringY}px)`, width: xshift ?? `${this._props.PanelWidth() - this.Xshift * 2}px`, height: this._props.forceAutoHeight ? undefined : yshift ?? (this.layout_fitWidth ? `${this.panelHeight}px` : `${(this.effectiveNativeHeight / this.effectiveNativeWidth) * this._props.PanelWidth()}px`), }}> { focus={this._props.focus || emptyFunction} ref={action((r: DocumentViewInternal | null) => r && (this._docViewInternal = r))} /> - {this.htmlOverlay} - {this.infoUI} + {this.htmlOverlay()} + {this.ComponentView?.infoUI?.()}
)} - - {this.linkCountView} + {/* display link count button */} +
); } + + public static SetViewTransition(docs: Doc[], transProp: string, timeInMs: number, timer?: NodeJS.Timeout | undefined, afterTrans?: () => void, dataTrans = false) { + docs.forEach(doc => { + doc._viewTransition = `${transProp} ${timeInMs}ms`; + dataTrans && (doc.dataTransition = `${transProp} ${timeInMs}ms`); + }); + timer && clearTimeout(timer); + return setTimeout( + () => + docs.forEach(doc => { + doc._viewTransition = undefined; + dataTrans && (doc.dataTransition = 'inherit'); + afterTrans?.(); + }), + timeInMs + 10 + ); + } + + // shows a stacking view collection (by default, but the user can change) of all documents linked to the source + public static showBackLinks(linkAnchor: Doc) { + const docId = Doc.CurrentUserEmail + Doc.GetProto(linkAnchor)[Id] + '-pivotish'; + // prettier-ignore + DocServer.GetRefField(docId).then(docx => + LightboxView.Instance.SetLightboxDoc( + (docx as Doc) ?? // reuse existing pivot view of documents, or else create a new collection + Docs.Create.StackingDocument([], { title: linkAnchor.title + '-pivot', _width: 500, _height: 500, target: linkAnchor, onViewMounted: ScriptField.MakeScript('updateLinkCollection(this, this.target)') }, docId) + ) + ); + } } ScriptingGlobals.add(function deiconifyView(documentView: DocumentView) { diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx index cad76ddf7..2e03a766a 100644 --- a/src/client/views/nodes/EquationBox.tsx +++ b/src/client/views/nodes/EquationBox.tsx @@ -20,13 +20,13 @@ export class EquationBox extends ViewBoxBaseComponent() { public static SelectOnLoad: string = ''; _ref: React.RefObject = React.createRef(); - constructor(props: any) { + constructor(props: FieldViewProps) { super(props); makeObservable(this); } componentDidMount() { - this._props.setContentView?.(this); + this._props.setContentViewBox?.(this); if (EquationBox.SelectOnLoad === this.Document[Id] && (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView?.()))) { this._props.select(false); diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index b456807d7..5bb295565 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -1,26 +1,108 @@ -import { computed } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { DateField } from '../../../fields/DateField'; -import { Doc, Field, FieldResult, Opt } from '../../../fields/Doc'; +import { Doc, Field, Opt } from '../../../fields/Doc'; import { List } from '../../../fields/List'; import { ScriptField } from '../../../fields/ScriptField'; import { WebField } from '../../../fields/URLField'; -import { DocumentView, DocumentViewInternalSharedProps, DocumentViewSharedProps } from './DocumentView'; +import { dropActionType } from '../../util/DragManager'; +import { Transform } from '../../util/Transform'; +import { ViewBoxInterface } from '../DocComponent'; +import { CollectionFreeFormDocumentView } from './CollectionFreeFormDocumentView'; +import { DocumentView, OpenWhere } from './DocumentView'; +import { PinProps } from './trails'; +export interface FocusViewOptions { + willPan?: boolean; // determines whether to pan to target document + willZoomCentered?: boolean; // determines whether to zoom in on target document. if zoomScale is 0, this just centers the document + zoomScale?: number; // percent of containing frame to zoom into document + zoomTime?: number; + didMove?: boolean; // whether a document was changed during the showDocument process + docTransform?: Transform; // when a document can't be panned and zoomed within its own container (say a group), then we need to continue to move up the render hierarchy to find something that can pan and zoom. when this happens the docTransform must accumulate all the transforms of each level of the hierarchy + instant?: boolean; // whether focus should happen instantly (as opposed to smooth zoom) + preview?: boolean; // whether changes should be previewed by the componentView or written to the document + effect?: Doc; // animation effect for focus + noSelect?: boolean; // whether target should be selected after focusing + playAudio?: boolean; // whether to play audio annotation on focus + playMedia?: boolean; // whether to play start target videos + 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; +export type StyleProviderFuncType = (doc: Opt, props: Opt, property: string) => any; // // these properties get assigned through the render() method of the DocumentView when it creates this node. // However, that only happens because the properties are "defined" in the markup for the field view. // See the LayoutString method on each field view : ImageBox, FormattedTextBox, etc. // -export interface FieldViewProps extends DocumentViewSharedProps, DocumentViewInternalSharedProps { - // FieldView specific props that are not part of DocumentView props - fieldKey: string; - - setHeight?: (height: number) => void; - onBrowseClick?: () => ScriptField | undefined; +export interface FieldViewSharedProps { + Document: Doc; + LayoutTemplateString?: string; + LayoutTemplate?: () => Opt; + TemplateDataDocument?: Doc; + renderDepth: number; + scriptContext?: any; // can be assigned anything and will be passed as 'scriptContext' to any OnClick script that executes on this document + treeViewDoc?: Doc; + xPadding?: number; + yPadding?: number; + dontRegisterView?: boolean; + dropAction?: dropActionType; + dragAction?: dropActionType; + forceAutoHeight?: boolean; + ignoreAutoHeight?: boolean; + disableBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over. + CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView; + containerViewPath?: () => DocumentView[]; + fitContentsToBox?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _freeform_fitContentsToBox property on a Document + isGroupActive?: () => string | undefined; // is this document part of a group that is active + setContentViewBox?: (view: ViewBoxInterface) => any; // called by rendered field's viewBox so that DocumentView can make direct calls to the viewBox + PanelWidth: () => number; + PanelHeight: () => number; + isDocumentActive?: () => boolean | undefined; // whether a document should handle pointer events + isContentActive: () => boolean | undefined; // whether document contents should handle pointer events + childFilters: () => string[]; + childFiltersByRanges: () => string[]; + styleProvider: Opt; + setTitleFocus?: () => void; + focus: FocusFuncType; + onClickScript?: () => ScriptField; + onDoubleClickScript?: () => ScriptField; + onPointerDownScript?: () => ScriptField; + onPointerUpScript?: () => ScriptField; + onBrowseClickScript?: () => ScriptField | undefined; onKey?: (e: React.KeyboardEvent, fieldProps: FieldViewProps) => boolean | undefined; + layout_fitWidth?: (doc: Doc) => boolean | undefined; + searchFilterDocs: () => Doc[]; + layout_showTitle?: () => string; + whenChildContentsActiveChanged: (isActive: boolean) => void; + rootSelected?: () => boolean; // whether the root of a template has been selected + addDocTab: (doc: Doc, where: OpenWhere) => boolean; + filterAddDocument?: (doc: Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example) + addDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean; + removeDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean; + moveDocument?: (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[], annotationKey?: string) => boolean) => boolean; + pinToPres: (document: Doc, pinProps: PinProps) => void; + ScreenToLocalTransform: () => Transform; + bringToFront: (doc: Doc, sendToBack?: boolean) => void; + waitForDoubleClickToClick?: () => 'never' | 'always' | undefined; + defaultDoubleClick?: () => 'default' | 'ignore' | undefined; pointerEvents?: () => Opt; +} + +/** + * FieldView specific props that are not shared with DocumentView props + * */ +export interface FieldViewProps extends FieldViewSharedProps { + DocumentView?: () => DocumentView; + fieldKey: string; + isSelected: () => boolean; + select: (ctrlPressed: boolean, shiftPress?: boolean) => void; + docViewPath: () => DocumentView[]; + setHeight?: (height: number) => void; + NativeDimScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal // properties intended to be used from within layout strings (otherwise use the function equivalents that work more efficiently with React) // See currentUserUtils headerTemplate for examples of creating text boxes from html which set some of these fields @@ -30,9 +112,9 @@ export interface FieldViewProps extends DocumentViewSharedProps, DocumentViewInt color?: string; height?: number; width?: number; + dontSelectOnLoad?: boolean; // suppress selecting (e.g.,. text box) when loaded (and mark as not being associated with scrollTop document field) noSidebar?: boolean; dontScale?: boolean; - dontSelectOnLoad?: boolean; // suppress selecting (e.g.,. text box) when loaded (and mark as not being associated with scrollTop document field) } @observer @@ -41,13 +123,8 @@ export class FieldView extends React.Component { return `<${fieldType.name} {...props} fieldKey={'${fieldStr}'}/>`; //e.g., "" } - @computed - get field(): FieldResult { - const { Document, fieldKey: fieldKey } = this.props; - return Document[fieldKey]; - } render() { - const field = this.field; + const field = this.props.Document[this.props.fieldKey]; // prettier-ignore if (field instanceof Doc) return

{field.title?.toString()}

; if (field === undefined) return

{''}

; diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx index 5a8665aaf..cf07d98be 100644 --- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx @@ -46,7 +46,7 @@ export class FontIconBox extends ViewBoxBaseComponent() { return FieldView.LayoutString(FontIconBox, fieldKey); } - constructor(props: any) { + constructor(props: ButtonProps) { super(props); makeObservable(this); } diff --git a/src/client/views/nodes/FunctionPlotBox.tsx b/src/client/views/nodes/FunctionPlotBox.tsx index c26579e66..2e7a2120e 100644 --- a/src/client/views/nodes/FunctionPlotBox.tsx +++ b/src/client/views/nodes/FunctionPlotBox.tsx @@ -31,7 +31,7 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent } componentDidMount() { - this._props.setContentView?.(this); + this._props.setContentViewBox?.(this); reaction( () => [DocListCast(this.dataDoc[this.fieldKey]).map(doc => doc?.text), this.layoutDoc.width, this.layoutDoc.height, this.layoutDoc.xRange, this.layoutDoc.yRange], () => this.createGraph() diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 4483d12ce..2a10bd766 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -21,17 +21,17 @@ import { undoBatch } from '../../util/UndoManager'; import { ContextMenu } from '../../views/ContextMenu'; import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; import { ContextMenuProps } from '../ContextMenuItem'; -import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent'; +import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent'; import { MarqueeAnnotator } from '../MarqueeAnnotator'; import { AnchorMenu } from '../pdf/AnchorMenu'; import { StyleProp } from '../StyleProvider'; -import { DocFocusOptions, OpenWhere } from './DocumentView'; -import { FieldView, FieldViewProps } from './FieldView'; +import { OpenWhere } from './DocumentView'; +import { FocusViewOptions, FieldView, FieldViewProps } from './FieldView'; import './ImageBox.scss'; import { PinProps, PresBox } from './trails'; @observer -export class ImageBox extends ViewBoxAnnotatableComponent() { +export class ImageBox extends ViewBoxAnnotatableComponent() implements ViewBoxInterface { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ImageBox, fieldKey); } @@ -55,10 +55,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent(); @observable _curSuffix = ''; - constructor(props: any) { + constructor(props: FieldViewProps) { super(props); makeObservable(this); - this._props.setContentView?.(this); + this._props.setContentViewBox?.(this); } protected createDropTarget = (ele: HTMLDivElement) => { @@ -186,9 +186,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent { if (!region) return; const cropping = Doc.MakeCopy(region, true); - Doc.GetProto(region).lockedPosition = true; - Doc.GetProto(region).title = 'region:' + this.Document.title; - Doc.GetProto(region).followLinkToggle = true; + const regionData = region[DocData]; + regionData.lockedPosition = true; + regionData.title = 'region:' + this.Document.title; + regionData.followLinkToggle = true; this.addDocument(region); const anchx = NumCast(cropping.x); const anchy = NumCast(cropping.y); @@ -201,7 +202,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent (anchor.type === DocumentType.CONFIG ? undefined : this._ffref.current?.focus(anchor, options)); + focus = (anchor: Doc, options: FocusViewOptions) => (anchor.type === DocumentType.CONFIG ? undefined : this._ffref.current?.focus(anchor, options)); _ffref = React.createRef(); savedAnnotations = () => this._savedAnnotations; @@ -421,7 +422,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent { private _valInput = React.createRef(); componentDidMount() { - this._props.setContentView?.(this); + this._props.setContentViewBox?.(this); } isKeyValueBox = returnTrue; able = returnAlways; diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index b2aab2422..7c532f33f 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -12,8 +12,7 @@ import { ContextMenu } from '../ContextMenu'; import { EditableView } from '../EditableView'; import { ObservableReactComponent } from '../ObservableReactComponent'; import { DefaultStyleProvider } from '../StyleProvider'; -import { OpenWhere } from './DocumentView'; -import { FieldViewProps } from './FieldView'; +import { OpenWhere, returnEmptyDocViewList } from './DocumentView'; import { KeyValueBox } from './KeyValueBox'; import './KeyValueBox.scss'; import './KeyValuePair.scss'; @@ -110,6 +109,7 @@ export class KeyValuePair extends ObservableReactComponent { childFiltersByRanges: returnEmptyFilter, searchFilterDocs: returnEmptyDoclist, styleProvider: DefaultStyleProvider, + docViewPath: returnEmptyDocViewList, fieldKey: this._props.keyName, isSelected: returnFalse, setHeight: returnFalse, diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index cc7c15a10..10eeff08d 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -14,13 +14,15 @@ import { StyleProp } from '../StyleProvider'; import { FieldView, FieldViewProps } from './FieldView'; import BigText from './LabelBigText'; import './LabelBox.scss'; +import { PinProps, PresBox } from './trails'; +import { Docs } from '../../documents/Documents'; -export interface LabelBoxProps { +export interface LabelBoxProps extends FieldViewProps { label?: string; } @observer -export class LabelBox extends ViewBoxBaseComponent() { +export class LabelBox extends ViewBoxBaseComponent() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LabelBox, fieldKey); } @@ -30,13 +32,13 @@ export class LabelBox extends ViewBoxBaseComponent { + if (!pinProps) return this.Document; + const anchor = Docs.Create.ConfigDocument({ title: StrCast(this.Document.title), annotationOn: this.Document }); + + if (anchor) { + if (!addAsAnnotation) anchor.backgroundColor = 'transparent'; + // addAsAnnotation && this.addDocument(anchor); + PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}) } }, this.Document); + return anchor; + } + return anchor; + }; + fitTextToBox = ( r: any ): diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx index 362a7def1..00e1f04c5 100644 --- a/src/client/views/nodes/LinkAnchorBox.tsx +++ b/src/client/views/nodes/LinkAnchorBox.tsx @@ -1,4 +1,5 @@ import { action, computed, makeObservable } from 'mobx'; +import { observer } from 'mobx-react'; import * as React from 'react'; import { Utils, emptyFunction, setupMoveUpEvents } from '../../../Utils'; import { Doc } from '../../../fields/Doc'; @@ -7,13 +8,14 @@ import { TraceMobx } from '../../../fields/util'; import { DragManager } from '../../util/DragManager'; import { LinkFollower } from '../../util/LinkFollower'; import { SelectionManager } from '../../util/SelectionManager'; -import { ViewBoxBaseComponent, ViewBoxBaseProps } from '../DocComponent'; +import { ViewBoxBaseComponent } from '../DocComponent'; import { StyleProp } from '../StyleProvider'; import { FieldView, FieldViewProps } from './FieldView'; import './LinkAnchorBox.scss'; import { LinkInfo } from './LinkDocPreview'; const { default: { MEDIUM_GRAY }, } = require('../global/globalCssVariables.module.scss'); // prettier-ignore -export class LinkAnchorBox extends ViewBoxBaseComponent() { +@observer +export class LinkAnchorBox extends ViewBoxBaseComponent() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LinkAnchorBox, fieldKey); } @@ -23,13 +25,13 @@ export class LinkAnchorBox extends ViewBoxBaseComponent() { return FieldView.LayoutString(LinkBox, fieldKey); } - constructor(props: any) { + constructor(props: FieldViewProps) { super(props); makeObservable(this); } @@ -59,7 +59,7 @@ export class LinkBox extends ViewBoxBaseComponent() { }; disposer: IReactionDisposer | undefined; componentDidMount() { - this._props.setContentView?.(this); + this._props.setContentViewBox?.(this); this.disposer = reaction( () => { if (this.layoutDoc._layout_isSvg && (this.anchor1 || this.anchor2)?.CollectionFreeFormView) { @@ -100,6 +100,9 @@ export class LinkBox extends ViewBoxBaseComponent() { this.layoutDoc._width = params.rx - params?.lx; this.layoutDoc._height = params?.by - params?.ty; } + } else { + this.layoutDoc._width = Math.max(50, NumCast(this.layoutDoc._width)); + this.layoutDoc._height = Math.max(50, NumCast(this.layoutDoc._height)); } }, { fireImmediately: true } diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx index 8ad0b7dde..13f0ac4fc 100644 --- a/src/client/views/nodes/LinkDescriptionPopup.tsx +++ b/src/client/views/nodes/LinkDescriptionPopup.tsx @@ -1,20 +1,27 @@ -import { action, observable } from 'mobx'; +import { action, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { Doc } from '../../../fields/Doc'; +import { DocData } from '../../../fields/DocSymbols'; import { LinkManager } from '../../util/LinkManager'; import './LinkDescriptionPopup.scss'; import { TaskCompletionBox } from './TaskCompletedBox'; @observer export class LinkDescriptionPopup extends React.Component<{}> { - @observable public static descriptionPopup: boolean = false; - @observable public static showDescriptions: string = 'ON'; - @observable public static popupX: number = 700; - @observable public static popupY: number = 350; + public static Instance: LinkDescriptionPopup; + @observable public display: boolean = false; + @observable public showDescriptions: string = 'ON'; + @observable public popupX: number = 700; + @observable public popupY: number = 350; @observable description: string = ''; @observable popupRef = React.createRef(); + constructor(props: any) { + super(props); + makeObservable(this); + LinkDescriptionPopup.Instance = this; + } + @action descriptionChanged = (e: React.ChangeEvent) => { this.description = e.currentTarget.value; @@ -22,16 +29,16 @@ export class LinkDescriptionPopup extends React.Component<{}> { @action onDismiss = (add: boolean) => { - LinkDescriptionPopup.descriptionPopup = false; + this.display = false; if (add) { - LinkManager.currentLink && (Doc.GetProto(LinkManager.currentLink).link_description = this.description); + LinkManager.currentLink && (LinkManager.currentLink[DocData].link_description = this.description); } }; @action onClick = (e: PointerEvent) => { if (this.popupRef && !!!this.popupRef.current?.contains(e.target as any)) { - LinkDescriptionPopup.descriptionPopup = false; + this.display = false; TaskCompletionBox.taskCompleted = false; } }; @@ -46,13 +53,13 @@ export class LinkDescriptionPopup extends React.Component<{}> { } render() { - return ( + return !this.display ? null : (
; @@ -45,7 +45,7 @@ interface LinkDocPreviewProps { linkDoc?: Doc; linkSrc?: Doc; DocumentView?: () => DocumentView; - styleProvider?: StyleProviderFunc; + styleProvider?: StyleProviderFuncType; location: number[]; hrefs?: string[]; showHeader?: boolean; diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx index 56fb157da..c185c66fc 100644 --- a/src/client/views/nodes/MapBox/MapBox.tsx +++ b/src/client/views/nodes/MapBox/MapBox.tsx @@ -23,12 +23,12 @@ import { DragManager } from '../../../util/DragManager'; import { LinkManager } from '../../../util/LinkManager'; import { SnappingManager } from '../../../util/SnappingManager'; import { UndoManager, undoable } from '../../../util/UndoManager'; -import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocComponent'; +import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent'; import { SidebarAnnos } from '../../SidebarAnnos'; import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm'; import { Colors } from '../../global/globalEnums'; -import { DocFocusOptions, DocumentView } from '../DocumentView'; -import { FieldView, FieldViewProps } from '../FieldView'; +import { DocumentView } from '../DocumentView'; +import { FocusViewOptions, FieldView, FieldViewProps } from '../FieldView'; import { FormattedTextBox } from '../formattedText/FormattedTextBox'; import { PinProps, PresBox } from '../trails'; import { fastSpeedIcon, mediumSpeedIcon, slowSpeedIcon } from './AnimationSpeedIcons'; @@ -96,7 +96,7 @@ type MapMarker = { // }); @observer -export class MapBox extends ViewBoxAnnotatableComponent() { +export class MapBox extends ViewBoxAnnotatableComponent() implements ViewBoxInterface { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(MapBox, fieldKey); } @@ -107,7 +107,7 @@ export class MapBox extends ViewBoxAnnotatableComponent) => void); - constructor(props: any) { + constructor(props: FieldViewProps) { super(props); makeObservable(this); } @@ -248,7 +248,7 @@ export class MapBox extends ViewBoxAnnotatableComponent { 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.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.Document; e.annoDragData.linkSourceDoc.followLinkZoom = false; } }, @@ -504,7 +504,7 @@ export class MapBox extends ViewBoxAnnotatableComponent { + getView = async (doc: Doc, options: FocusViewOptions) => { if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) { this.toggleSidebar(); options.didMove = true; diff --git a/src/client/views/nodes/MapBox/MapBox2.tsx b/src/client/views/nodes/MapBox/MapBox2.tsx index 9734d9db1..9825824bd 100644 --- a/src/client/views/nodes/MapBox/MapBox2.tsx +++ b/src/client/views/nodes/MapBox/MapBox2.tsx @@ -12,7 +12,7 @@ // import { SnappingManager } from '../../../util/SnappingManager'; // import { UndoManager } from '../../../util/UndoManager'; // import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm'; -// import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocComponent'; +// import { ViewBoxAnnotatableComponent } from '../../DocComponent'; // import { Colors } from '../../global/globalEnums'; // import { AnchorMenu } from '../../pdf/AnchorMenu'; // import { Annotation } from '../../pdf/Annotation'; @@ -83,7 +83,7 @@ // } as google.maps.places.AutocompleteOptions; // @observer -// export class MapBox2 extends ViewBoxAnnotatableComponent>() { +// export class MapBox2 extends ViewBoxAnnotatableComponent>() { // private _dropDisposer?: DragManager.DragDropDisposer; // private _disposers: { [name: string]: IReactionDisposer } = {}; // private _annotationLayer: React.RefObject = React.createRef(); @@ -137,7 +137,7 @@ // // iterate allMarkers to size, center, and zoom map to contain all markers // private fitBounds = (map: google.maps.Map) => { -// const curBounds = map.getBounds() ?? new window.google.maps.LatLngBounds(); +// const curBounds = map.getBounds ?? new window.google.maps.LatLngBounds(); // const isFitting = this.allMapMarkers.reduce((fits, place) => fits && curBounds?.contains({ lat: NumCast(place.lat), lng: NumCast(place.lng) }), true as boolean); // !isFitting && map.fitBounds(this.allMapMarkers.reduce((bounds, place) => bounds.extend({ lat: NumCast(place.lat), lng: NumCast(place.lng) }), new window.google.maps.LatLngBounds())); // }; @@ -257,7 +257,7 @@ // map.setZoom(NumCast(this.dataDoc.map_zoom, 2.5)); // map.setCenter(new google.maps.LatLng(NumCast(this.dataDoc.mapLat), NumCast(this.dataDoc.mapLng))); // setTimeout(() => { -// if (this._loadPending && this._map.getBounds()) { +// if (this._loadPending && this._map.getBounds) { // this._loadPending = false; // this.layoutDoc.freeform_fitContentsToBox && this.fitBounds(this._map); // } @@ -272,7 +272,7 @@ // @action // centered = () => { -// if (this._loadPending && this._map.getBounds()) { +// if (this._loadPending && this._map.getBounds) { // this._loadPending = false; // this.layoutDoc.freeform_fitContentsToBox && this.fitBounds(this._map); // } @@ -282,7 +282,7 @@ // @action // zoomChanged = () => { -// if (this._loadPending && this._map.getBounds()) { +// if (this._loadPending && this._map.getBounds) { // this._loadPending = false; // this.layoutDoc.freeform_fitContentsToBox && this.fitBounds(this._map); // } diff --git a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx index a9c6ba22c..6ccbbbe1c 100644 --- a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx +++ b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx @@ -9,19 +9,18 @@ // import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; // import { CollectionNoteTakingView } from '../../collections/CollectionNoteTakingView'; // import { CollectionStackingView } from '../../collections/CollectionStackingView'; -// import { ViewBoxAnnotatableProps } from '../../DocComponent'; // import { FieldViewProps } from '../FieldView'; // import { FormattedTextBox } from '../formattedText/FormattedTextBox'; // import './MapBox.scss'; -// interface MapBoxInfoWindowProps { +// interface MapBoxInfoWindowProps extends FieldViewProps { // place: Doc; // renderDepth: number; // markerMap: { [id: string]: google.maps.Marker }; // isAnyChildContentActive: () => boolean; // } // @observer -// export class MapBoxInfoWindow extends React.Component { +// export class MapBoxInfoWindow extends React.Component { // @action // private handleInfoWindowClose = () => { // if (this.props.place.infoWindowOpen) { diff --git a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx index e079f7457..8a5bd7ce6 100644 --- a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx +++ b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx @@ -1,6 +1,6 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Button, EditableText, IconButton, Type } from 'browndash-components'; -import { IReactionDisposer, ObservableMap, action, computed, observable, reaction, runInAction } from 'mobx'; +import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { MapProvider, Map as MapboxMap } from 'react-map-gl'; @@ -16,18 +16,17 @@ import { LinkManager } from '../../../util/LinkManager'; import { SnappingManager } from '../../../util/SnappingManager'; import { Transform } from '../../../util/Transform'; import { UndoManager, undoable } from '../../../util/UndoManager'; -import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocComponent'; +import { ViewBoxAnnotatableComponent } from '../../DocComponent'; import { SidebarAnnos } from '../../SidebarAnnos'; import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm'; import { Colors } from '../../global/globalEnums'; -import { DocFocusOptions, DocumentView } from '../DocumentView'; -import { FieldView, FieldViewProps } from '../FieldView'; +import { DocumentView } from '../DocumentView'; +import { FocusViewOptions, FieldView, FieldViewProps } from '../FieldView'; import { MapAnchorMenu } from '../MapBox/MapAnchorMenu'; import { FormattedTextBox } from '../formattedText/FormattedTextBox'; import { PinProps, PresBox } from '../trails'; import './MapBox.scss'; -// amongus /** * MapBox architecture: * Main component: MapBox.tsx @@ -61,7 +60,7 @@ const bingApiKey = process.env.BING_MAPS; // if you're running local, get a Bing // }); @observer -export class MapBoxContainer extends ViewBoxAnnotatableComponent() { +export class MapBoxContainer extends ViewBoxAnnotatableComponent() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(MapBoxContainer, fieldKey); } @@ -71,6 +70,11 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent) => void); + constructor(props: FieldViewProps) { + super(props); + makeObservable(this); + } + @observable private _savedAnnotations = new ObservableMap(); @computed get allSidebarDocs() { return DocListCast(this.dataDoc[this.SidebarKey]); @@ -97,7 +101,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent { 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.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.Document; e.annoDragData.linkSourceDoc.followLinkZoom = false; } }, @@ -374,7 +378,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent { + getView = async (doc: Doc, options: FocusViewOptions) => { if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) { this.toggleSidebar(); options.didMove = true; @@ -787,8 +791,8 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent() { +export class PDFBox extends ViewBoxAnnotatableComponent() implements ViewBoxInterface { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PDFBox, fieldKey); } @@ -58,7 +59,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent { if (!region) return; const cropping = Doc.MakeCopy(region, true); - Doc.GetProto(region).lockedPosition = true; - Doc.GetProto(region).title = 'region:' + this.Document.title; - Doc.GetProto(region).followLinkToggle = true; + const regionData = region[DocData]; + regionData.lockedPosition = true; + regionData.title = 'region:' + this.Document.title; + regionData.followLinkToggle = true; this.addDocument(region); const docViewContent = this.DocumentView?.().ContentDiv!; @@ -120,7 +122,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent disposer?.()); } componentDidMount() { - this._props.setContentView?.(this); + this._props.setContentViewBox?.(this); this._disposers.select = reaction( () => this._props.isSelected(), () => { @@ -219,12 +221,12 @@ export class PDFBox extends ViewBoxAnnotatableComponent { + focus = (anchor: Doc, options: FocusViewOptions) => { this._initialScrollTarget = anchor; return this._pdfViewer?.scrollFocus(anchor, NumCast(anchor.y, NumCast(anchor.config_scrollTop)), options); }; - getView = async (doc: Doc, options: DocFocusOptions) => { + getView = async (doc: Doc, options: FocusViewOptions) => { if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) { options.didMove = true; this.toggleSidebar(false); @@ -528,7 +530,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.DocumentView?.()!, false), true)}> () { @@ -30,8 +31,13 @@ export class RecordingBox extends ViewBoxBaseComponent() { private _ref: React.RefObject = React.createRef(); + constructor(props: FieldViewProps) { + super(props); + makeObservable(this); + } + componentDidMount() { - this._props.setContentView?.(this); + this._props.setContentViewBox?.(this); Doc.SetNativeWidth(this.dataDoc, 1280); Doc.SetNativeHeight(this.dataDoc, 720); } @@ -100,7 +106,7 @@ export class RecordingBox extends ViewBoxBaseComponent() { }); screengrabber.overlayX = 70; //was -400 screengrabber.overlayY = 590; //was 0 - Doc.GetProto(screengrabber)[Doc.LayoutFieldKey(screengrabber) + '_trackScreen'] = true; + screengrabber[DocData][Doc.LayoutFieldKey(screengrabber) + '_trackScreen'] = true; Doc.AddToMyOverlay(screengrabber); //just adds doc to overlay DocumentManager.Instance.AddViewRenderedCb(screengrabber, docView => { RecordingBox.screengrabber = docView.ComponentView as RecordingBox; diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx index f74e6fb2b..1e3933ac3 100644 --- a/src/client/views/nodes/ScreenshotBox.tsx +++ b/src/client/views/nodes/ScreenshotBox.tsx @@ -21,12 +21,13 @@ import { TrackMovements } from '../../util/TrackMovements'; import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; import { CollectionStackedTimeline } from '../collections/CollectionStackedTimeline'; import { ContextMenu } from '../ContextMenu'; -import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent'; +import { ViewBoxAnnotatableComponent } from '../DocComponent'; import { media_state } from './AudioBox'; import { FieldView, FieldViewProps } from './FieldView'; import { FormattedTextBox } from './formattedText/FormattedTextBox'; import './ScreenshotBox.scss'; import { VideoBox } from './VideoBox'; +import { DocData } from '../../../fields/DocSymbols'; declare class MediaRecorder { constructor(e: any, options?: any); // whatever MediaRecorder has @@ -109,7 +110,7 @@ declare class MediaRecorder { // } @observer -export class ScreenshotBox extends ViewBoxAnnotatableComponent() { +export class ScreenshotBox extends ViewBoxAnnotatableComponent() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ScreenshotBox, fieldKey); } @@ -121,7 +122,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent ({ width: this._props.PanelWidth(), height: this._props.PanelHeight() }), // ({ width, height }) => { // if (this._camera) { @@ -281,7 +282,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent () { +export class ScriptingBox extends ViewBoxAnnotatableComponent() { private dropDisposer?: DragManager.DragDropDisposer; public static LayoutString(fieldStr: string) { return FieldView.LayoutString(ScriptingBox, fieldStr); @@ -56,7 +56,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent { diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 1913ab032..a6c524881 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -4,10 +4,11 @@ import { observer } from 'mobx-react'; import { basename } from 'path'; import * as React from 'react'; import { Doc, Opt, StrListCast } from '../../../fields/Doc'; +import { DocData } from '../../../fields/DocSymbols'; import { InkTool } from '../../../fields/InkField'; import { List } from '../../../fields/List'; import { ObjectField } from '../../../fields/ObjectField'; -import { Cast, NumCast, StrCast } from '../../../fields/Types'; +import { Cast, DocCast, NumCast, StrCast } from '../../../fields/Types'; import { AudioField, ImageField, VideoField } from '../../../fields/URLField'; import { emptyFunction, formatTime, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; @@ -21,12 +22,12 @@ import { CollectionFreeFormView } from '../collections/collectionFreeForm/Collec import { CollectionStackedTimeline, TrimScope } from '../collections/CollectionStackedTimeline'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; -import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent'; +import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent'; import { MarqueeAnnotator } from '../MarqueeAnnotator'; import { AnchorMenu } from '../pdf/AnchorMenu'; import { StyleProp } from '../StyleProvider'; -import { DocFocusOptions, DocumentView } from './DocumentView'; -import { FieldView, FieldViewProps } from './FieldView'; +import { DocumentView } from './DocumentView'; +import { FocusViewOptions, FieldView, FieldViewProps } from './FieldView'; import { RecordingBox } from './RecordingBox'; import { PinProps, PresBox } from './trails'; import './VideoBox.scss'; @@ -44,7 +45,7 @@ import './VideoBox.scss'; */ @observer -export class VideoBox extends ViewBoxAnnotatableComponent() { +export class VideoBox extends ViewBoxAnnotatableComponent() implements ViewBoxInterface { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(VideoBox, fieldKey); } @@ -61,10 +62,10 @@ export class VideoBox extends ViewBoxAnnotatableComponent downX !== undefined && downY !== undefined && DocumentManager.Instance.getFirstDocumentView(imageSnapshot)?.startDragging(downX, downY, 'move', true)); }; @@ -368,7 +369,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent { + getView = (doc: Doc, options: FocusViewOptions) => { if (this._stackedTimeline?.makeDocUnfiltered(doc)) { if (this.heightPercent === 100) { this.layoutDoc._layout_timelineHeightPercent = VideoBox.heightPercent; @@ -738,7 +739,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent this.timeline?.setZoom(zoom); // plays link - playLink = (doc: Doc, options: DocFocusOptions) => { + playLink = (doc: Doc, options: FocusViewOptions) => { const startTime = Math.max(0, NumCast(doc.config_clipStart, this._stackedTimeline?.anchorStart(doc) || 0)); const endTime = this.timeline?.anchorEnd(doc); if (startTime !== undefined) { @@ -885,10 +886,11 @@ export class VideoBox extends ViewBoxAnnotatableComponent { if (!region) return; const cropping = Doc.MakeCopy(region, true); - Doc.GetProto(region).backgroundColor = 'transparent'; - Doc.GetProto(region).lockedPosition = true; - Doc.GetProto(region).title = 'region:' + this.Document.title; - Doc.GetProto(region).followLinkToggle = true; + const regionData = region[DocData]; + regionData.backgroundColor = 'transparent'; + regionData.lockedPosition = true; + regionData.title = 'region:' + this.Document.title; + regionData.followLinkToggle = true; region._timecodeToHide = NumCast(region._timecodeToShow) + 0.0001; this.addDocument(region); const anchx = NumCast(cropping.x); @@ -904,7 +906,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent () { +export class WebBox extends ViewBoxAnnotatableComponent() implements ViewBoxInterface { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(WebBox, fieldKey); } @@ -98,7 +98,7 @@ export class WebBox extends ViewBoxAnnotatableComponent { this._annotationKeySuffix = () => (this._urlHash ? this._urlHash + '_' : '') + 'annotations'; @@ -280,7 +280,7 @@ export class WebBox extends ViewBoxAnnotatableComponent { + focus = (anchor: Doc, options: FocusViewOptions) => { if (anchor !== this.Document && this._outerRef.current) { 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)); @@ -297,7 +297,7 @@ export class WebBox extends ViewBoxAnnotatableComponent { + getView = (doc: Doc, options: FocusViewOptions) => { if (Doc.AreProtosEqual(doc, this.Document)) return new Promise>(res => res(this.DocumentView?.())); if (this.Document.layout_fieldKey === 'layout_icon') this.DocumentView?.().iconify(); const webUrl = WebCast(doc.config_data)?.url; @@ -924,7 +924,7 @@ export class WebBox extends ViewBoxAnnotatableComponent this._innerCollectionView?.zoomScaling() ?? 1; - setInnerContent = (component: DocComponentView) => (this._innerCollectionView = component as CollectionFreeFormView); + setInnerContent = (component: ViewBoxInterface) => (this._innerCollectionView = component as CollectionFreeFormView); @computed get content() { const interactive = this._props.isContentActive() && this._props.pointerEvents?.() !== 'none' && Doc.ActiveTool === InkTool.None; @@ -966,7 +966,7 @@ export class WebBox extends ViewBoxAnnotatableComponent string[]) => ( () { return FieldView.LayoutString(CalendarBox, fieldKey); } + constructor(props: FieldViewProps) { + super(props); + makeObservable(this); + } + componentDidMount(): void {} componentWillUnmount(): void {} @@ -105,11 +110,6 @@ export class CalendarBox extends ViewBoxBaseComponent() { }); } - constructor(props: any) { - super(props); - makeObservable(this); - } - render() { return (
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx index 4ae12505e..bc49ed23d 100644 --- a/src/client/views/nodes/formattedText/DashDocView.tsx +++ b/src/client/views/nodes/formattedText/DashDocView.tsx @@ -6,12 +6,13 @@ import * as ReactDOM from 'react-dom/client'; import { Doc } from '../../../../fields/Doc'; import { Height, Width } from '../../../../fields/DocSymbols'; import { NumCast } from '../../../../fields/Types'; -import { emptyFunction, returnEmptyDoclist, returnFalse, Utils } from '../../../../Utils'; +import { emptyFunction, returnFalse, Utils } from '../../../../Utils'; import { DocServer } from '../../../DocServer'; import { Docs, DocUtils } from '../../../documents/Documents'; import { Transform } from '../../../util/Transform'; import { ObservableReactComponent } from '../../ObservableReactComponent'; -import { DocFocusOptions, DocumentView, returnEmptyDocViewList } from '../DocumentView'; +import { DocumentView } from '../DocumentView'; +import { FocusViewOptions } from '../FieldView'; import { FormattedTextBox } from './FormattedTextBox'; var horizPadding = 3; // horizontal padding to container to allow cursor to show up on either side. @@ -159,7 +160,7 @@ export class DashDocViewInternal extends ObservableReactComponent this._textBox.focus(target, options); // ideally, this would scroll to show the focus target + outerFocus = (target: Doc, options: FocusViewOptions) => this._textBox.focus(target, options); // ideally, this would scroll to show the focus target onKeyDown = (e: any) => { e.stopPropagation(); @@ -178,9 +179,6 @@ export class DashDocViewInternal extends ObservableReactComponent Object.values(this._disposers).forEach(disposer => disposer?.()); isContentActive = () => this._props.tbox._props.isContentActive() || this._props.tbox.isAnyChildContentActive?.(); @@ -210,7 +208,7 @@ export class DashDocViewInternal extends ObservableReactComponent() { +export class FormattedTextBox extends ViewBoxAnnotatableComponent() implements ViewBoxInterface { public static LayoutString(fieldStr: string) { return FieldView.LayoutString(FormattedTextBox, fieldStr); } @@ -202,7 +200,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent (stopFunc = stop)); + const targetData = target[DocData]; + targetData.mediaState = media_state.Recording; + targetData.audioAnnoState = 'recording'; + DocumentViewInternal.recordAudioAnnotation(targetData, Doc.LayoutFieldKey(target), stop => (stopFunc = stop)); let reactionDisposer = reaction( () => target.mediaState, action(dictation => { if (!dictation) { - Doc.GetProto(target).audioAnnoState = 'stopped'; + targetData.audioAnnoState = 'stopped'; stopFunc(); reactionDisposer(); } @@ -433,7 +432,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { const newAutoLinks = new Set(); - const oldAutoLinks = LinkManager.Links(this._props.Document).filter(link => link.link_relationship === LinkManager.AutoKeywords); + const oldAutoLinks = LinkManager.Links(this.Document).filter(link => link.link_relationship === LinkManager.AutoKeywords); if (this._editorView?.state.doc.textContent) { const isNodeSel = this._editorView.state.selection instanceof NodeSelection; const f = this._editorView.state.selection.from; @@ -451,7 +450,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { const title = StrCast(this.dataDoc.title, Cast(this.dataDoc.title, RichTextField, null)?.Text); if ( - !this._props.dontRegisterView && // (this._props.Document.isTemplateForField === "text" || !this._props.Document.isTemplateForField) && // only update the title if the data document's data field is changing + !this._props.dontRegisterView && // (this.Document.isTemplateForField === "text" || !this.Document.isTemplateForField) && // only update the title if the data document's data field is changing (title.startsWith('-') || title.startsWith('@')) && this._editorView && !this.dataDoc.title_custom && @@ -494,7 +493,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent m.type.name === schema.marks.autoLinkAnchor.name)?.attrs.allAnchors ?? [])); const link = editorView.state.schema.marks.autoLinkAnchor.create({ allAnchors, title: 'auto term' }); tr = tr.addMark(pos, pos + node.nodeSize, link); @@ -578,7 +577,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { + getView = async (doc: Doc, options: FocusViewOptions) => { if (DocListCast(this.dataDoc[this.SidebarKey]).find(anno => Doc.AreProtosEqual(doc.layout_unrendered ? DocCast(doc.annotationOn) : doc, anno))) { if (!this.SidebarShown) { this.toggleSidebar(false); @@ -1066,7 +1065,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv))); }; - focus = (textAnchor: Doc, options: DocFocusOptions) => { + focus = (textAnchor: Doc, options: FocusViewOptions) => { const focusSpeed = options.zoomTime ?? 500; const textAnchorId = textAnchor[Id]; const findAnchorFrag = (frag: Fragment, editor: EditorView) => { @@ -1141,7 +1140,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent Doc.RecordingEvent, this.breakupDictation); this._disposers.layout_autoHeight = reaction( @@ -1777,7 +1776,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent BoolCast(this._props.Document._freeform_fitContentsToBox); + fitContentsToBox = () => BoolCast(this.Document._freeform_fitContentsToBox); sidebarContentScaling = () => (this._props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1); sidebarAddDocument = (doc: Doc | Doc[], sidebarKey: string = this.SidebarKey) => { if (!this.layoutDoc._layout_showSidebar) this.toggleSidebar(); @@ -2011,7 +2010,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ImportElementBox, fieldKey); } + constructor(props: FieldViewProps) { + super(props); + makeObservable(this); + } screenToLocalXf = () => this.ScreenToLocalBoxXf().scale(1 * (this._props.NativeDimScaling?.() || 1)); @computed get mainItem() { diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 645ac08e1..9e5ea9524 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -4,7 +4,7 @@ import { action, computed, IReactionDisposer, makeObservable, observable, Observ import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, DocListCast, FieldResult, NumListCast, Opt, StrListCast } from '../../../../fields/Doc'; -import { Animation } from '../../../../fields/DocSymbols'; +import { Animation, DocData } from '../../../../fields/DocSymbols'; import { Copy, Id } from '../../../../fields/FieldSymbols'; import { InkField } from '../../../../fields/InkField'; import { List } from '../../../../fields/List'; @@ -31,8 +31,8 @@ import { TreeView } from '../../collections/TreeView'; import { ViewBoxBaseComponent } from '../../DocComponent'; import { Colors } from '../../global/globalEnums'; import { LightboxView } from '../../LightboxView'; -import { DocFocusOptions, DocumentView, OpenWhere, OpenWhereMod } from '../DocumentView'; -import { FieldView, FieldViewProps } from '../FieldView'; +import { DocumentView, OpenWhere, OpenWhereMod } from '../DocumentView'; +import { FocusViewOptions, FieldView, FieldViewProps } from '../FieldView'; import { ScriptingBox } from '../ScriptingBox'; import './PresBox.scss'; import { PresEffect, PresEffectDirection, PresMovement, PresStatus } from './PresEnums'; @@ -70,7 +70,7 @@ export class PresBox extends ViewBoxBaseComponent() { } static navigateToDocScript: ScriptField; - constructor(props: any) { + constructor(props: FieldViewProps) { super(props); makeObservable(this); if (!PresBox.navigateToDocScript) { @@ -184,7 +184,7 @@ export class PresBox extends ViewBoxBaseComponent() { }, { fireImmediately: true } ); - this._props.setContentView?.(this); + this._props.setContentViewBox?.(this); this._unmounting = false; this.turnOffEdit(true); this._disposers.selection = reaction( @@ -438,9 +438,10 @@ export class PresBox extends ViewBoxBaseComponent() { const setData = bestTargetView?.ComponentView?.setData; if (setData) setData(activeItem.config_data); else { - const current = Doc.GetProto(bestTarget)[fkey]; - Doc.GetProto(bestTarget)[fkey + '_' + Date.now()] = current instanceof ObjectField ? current[Copy]() : current; - Doc.GetProto(bestTarget)[fkey] = activeItem.config_data instanceof ObjectField ? activeItem.config_data[Copy]() : activeItem.config_data; + const bestTargetData = bestTarget[DocData]; + const current = bestTargetData[fkey]; + bestTargetData[fkey + '_' + Date.now()] = current instanceof ObjectField ? current[Copy]() : current; + bestTargetData[fkey] = activeItem.config_data instanceof ObjectField ? activeItem.config_data[Copy]() : activeItem.config_data; } bestTarget[fkey + '_usePath'] = activeItem.config_usePath; setTimeout(() => (bestTarget._dataTransition = undefined), transTime + 10); @@ -498,11 +499,11 @@ export class PresBox extends ViewBoxBaseComponent() { } if (pinDataTypes?.inkable || (!pinDataTypes && (activeItem.config_fillColor !== undefined || activeItem.color !== undefined))) { if (bestTarget.fillColor !== activeItem.config_fillColor) { - Doc.GetProto(bestTarget).fillColor = StrCast(activeItem.config_fillColor, StrCast(bestTarget.fillColor)); + bestTarget[DocData].fillColor = StrCast(activeItem.config_fillColor, StrCast(bestTarget.fillColor)); changed = true; } if (bestTarget.color !== activeItem.config_color) { - Doc.GetProto(bestTarget).color = StrCast(activeItem.config_color, StrCast(bestTarget.color)); + bestTarget[DocData].color = StrCast(activeItem.config_color, StrCast(bestTarget.color)); changed = true; } if (bestTarget.width !== activeItem.width) { @@ -559,7 +560,7 @@ export class PresBox extends ViewBoxBaseComponent() { return doc; }); const newList = new List([...oldItems, ...hiddenItems, ...newItems]); - Doc.GetProto(bestTarget)[fkey + '_annotations'] = newList; + bestTarget[DocData][fkey + '_annotations'] = newList; } if (pinDataTypes?.poslayoutview || (!pinDataTypes && activeItem.config_pinLayoutData !== undefined)) { changed = true; @@ -580,9 +581,9 @@ export class PresBox extends ViewBoxBaseComponent() { data.fill && (doc._fillColor = data.fill); doc._width = data.w; doc._height = data.h; - data.data && (Doc.GetProto(doc).data = field); - data.text && (Doc.GetProto(doc).text = tfield); - Doc.AddDocToList(Doc.GetProto(bestTarget), layoutField, doc); + data.data && (doc[DocData].data = field); + data.text && (doc[DocData].text = tfield); + Doc.AddDocToList(bestTarget[DocData], layoutField, doc); } }); setTimeout(() => Array.from(transitioned).forEach(action(doc => (doc._dataTransition = undefined))), transTime + 10); @@ -648,7 +649,7 @@ export class PresBox extends ViewBoxBaseComponent() { } if (pinProps.pinData.dataannos) { const fkey = Doc.LayoutFieldKey(targetDoc); - pinDoc.config_annotations = new List(DocListCast(Doc.GetProto(targetDoc)[fkey + '_annotations']).filter(doc => !doc.layout_unrendered)); + pinDoc.config_annotations = new List(DocListCast(targetDoc[DocData][fkey + '_annotations']).filter(doc => !doc.layout_unrendered)); } if (pinProps.pinData.inkable) { pinDoc.config_fillColor = targetDoc.fillColor; @@ -763,7 +764,7 @@ export class PresBox extends ViewBoxBaseComponent() { } const effect = activeItem.presentation_effect && activeItem.presentation_effect !== PresEffect.None ? activeItem.presentation_effect : undefined; const presTime = NumCast(activeItem.presentation_transition, effect ? 750 : 500); - const options: DocFocusOptions = { + const options: FocusViewOptions = { willPan: activeItem.presentation_movement !== PresMovement.None, willZoomCentered: activeItem.presentation_movement === PresMovement.Zoom || activeItem.presentation_movement === PresMovement.Jump || activeItem.presentation_movement === PresMovement.Center, zoomScale: activeItem.presentation_movement === PresMovement.Center ? 0 : NumCast(activeItem.config_zoom, 1), @@ -1115,7 +1116,7 @@ export class PresBox extends ViewBoxBaseComponent() { presDocView && SelectionManager.SelectView(presDocView, false); }; - focusElement = (doc: Doc, options: DocFocusOptions) => { + focusElement = (doc: Doc, options: FocusViewOptions) => { this.selectElement(doc); return undefined; }; @@ -2605,7 +2606,7 @@ export class PresBox extends ViewBoxBaseComponent() { moveDocument={returnFalse} ignoreUnrendered={true} childDragAction="move" - setContentView={emptyFunction} + setContentViewBox={emptyFunction} //childLayoutFitWidth={returnTrue} childOpacity={returnOne} childClickScript={PresBox.navigateToDocScript} diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index 4bc79176e..7e68711fa 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -41,7 +41,7 @@ export class PresElementBox extends ViewBoxBaseComponent() { readonly expandViewHeight = 100; readonly collapsedHeight = 35; - constructor(props: any) { + constructor(props: FieldViewProps) { super(props); makeObservable(this); } diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 01590749e..ba7286111 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -17,16 +17,15 @@ import { SnappingManager } from '../../util/SnappingManager'; import { MarqueeOptionsMenu } from '../collections/collectionFreeForm'; import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; import { MarqueeAnnotator } from '../MarqueeAnnotator'; -import { DocFocusOptions, DocumentViewProps } from '../nodes/DocumentView'; -import { FieldViewProps } from '../nodes/FieldView'; +import { FocusViewOptions, FieldViewProps } from '../nodes/FieldView'; import { LinkInfo } from '../nodes/LinkDocPreview'; +import { PDFBox } from '../nodes/PDFBox'; import { ObservableReactComponent } from '../ObservableReactComponent'; import { StyleProp } from '../StyleProvider'; import { AnchorMenu } from './AnchorMenu'; import { Annotation } from './Annotation'; import { GPTPopup } from './GPTPopup/GPTPopup'; import './PDFViewer.scss'; -import { PDFBox } from '../nodes/PDFBox'; const _global = (window /* browser */ || global) /* node */ as any; //pdfjsLib.GlobalWorkerOptions.workerSrc = `/assets/pdf.worker.js`; @@ -172,7 +171,7 @@ export class PDFViewer extends ObservableReactComponent { // scrolls to focus on a nested annotation document. if this is part a link preview then it will jump to the scroll location, // otherwise it will scroll smoothly. - scrollFocus = (doc: Doc, scrollTop: number, options: DocFocusOptions) => { + scrollFocus = (doc: Doc, scrollTop: number, options: FocusViewOptions) => { const mainCont = this._mainCont.current; let focusSpeed: Opt; if (doc !== this._props.Document && mainCont) { @@ -521,7 +520,7 @@ export class PDFViewer extends ObservableReactComponent { {...this._props} NativeWidth={returnZero} NativeHeight={returnZero} - setContentView={emptyFunction} // override setContentView to do nothing + setContentViewBox={emptyFunction} // override setContentView to do nothing 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} diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index 4d29573d4..5dc4f5550 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -3,7 +3,7 @@ import { action, computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, DocListCastAsync, Field } from '../../../fields/Doc'; -import { DirectLinks } from '../../../fields/DocSymbols'; +import { DirectLinks, DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { DocCast, StrCast } from '../../../fields/Types'; import { DocumentType } from '../../documents/DocumentTypes'; @@ -58,7 +58,7 @@ export class SearchBox extends ViewBoxBaseComponent() { /** * This is the constructor for the SearchBox class. */ - constructor(props: any) { + constructor(props: SearchBoxProps) { super(props); makeObservable(this); SearchBox.Instance = this; @@ -207,7 +207,7 @@ export class SearchBox extends ViewBoxBaseComponent() { this._results.forEach((_, doc) => { this._pageRanks.set(doc, 1.0 / this._results.size); - if (Doc.GetProto(doc)[DirectLinks].size === 0) { + if (doc[DocData][DirectLinks].size === 0) { this._linkedDocsOut.set(doc, new Set(this._results.keys())); this._results.forEach((_, linkedDoc) => { @@ -216,7 +216,7 @@ export class SearchBox extends ViewBoxBaseComponent() { } else { const linkedDocSet = new Set(); - Doc.GetProto(doc)[DirectLinks].forEach(link => { + doc[DocData][DirectLinks].forEach(link => { const d1 = link?.link_anchor_1 as Doc; const d2 = link?.link_anchor_2 as Doc; if (doc === d1 && this._results.has(d2)) { diff --git a/src/client/views/selectedDoc/SelectedDocView.tsx b/src/client/views/selectedDoc/SelectedDocView.tsx index 39e778b76..c9c01189e 100644 --- a/src/client/views/selectedDoc/SelectedDocView.tsx +++ b/src/client/views/selectedDoc/SelectedDocView.tsx @@ -1,14 +1,14 @@ -import * as React from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { ListBox } from 'browndash-components'; import { computed } from 'mobx'; import { observer } from 'mobx-react'; +import * as React from 'react'; +import { emptyFunction } from '../../../Utils'; import { Doc } from '../../../fields/Doc'; import { StrCast } from '../../../fields/Types'; import { DocumentManager } from '../../util/DocumentManager'; -import { DocFocusOptions } from '../nodes/DocumentView'; -import { emptyFunction } from '../../../Utils'; import { SettingsManager } from '../../util/SettingsManager'; +import { FocusViewOptions } from '../nodes/FieldView'; export interface SelectedDocViewProps { selectedDocs: Doc[]; @@ -25,7 +25,7 @@ export class SelectedDocView extends React.Component {
{ - const options: DocFocusOptions = { + const options: FocusViewOptions = { playAudio: false, playMedia: false, willPan: true, diff --git a/src/client/views/webcam/DashWebRTCVideo.tsx b/src/client/views/webcam/DashWebRTCVideo.tsx index 94458563e..4e984f3d6 100644 --- a/src/client/views/webcam/DashWebRTCVideo.tsx +++ b/src/client/views/webcam/DashWebRTCVideo.tsx @@ -8,7 +8,6 @@ import { Doc } from '../../../fields/Doc'; import { InkTool } from '../../../fields/InkField'; import { SnappingManager } from '../../util/SnappingManager'; import '../../views/nodes/WebBox.scss'; -import { CollectionFreeFormDocumentViewProps } from '../nodes/CollectionFreeFormDocumentView'; import { FieldView, FieldViewProps } from '../nodes/FieldView'; import './DashWebRTCVideo.scss'; import { hangup, initialize, refreshVideos } from './WebCamLogic'; @@ -17,14 +16,12 @@ import { hangup, initialize, refreshVideos } from './WebCamLogic'; * This models the component that will be rendered, that can be used as a doc that will reflect the video cams. */ @observer -export class DashWebRTCVideo extends React.Component { +export class DashWebRTCVideo extends React.Component { private roomText: HTMLInputElement | undefined; @observable remoteVideoAdded: boolean = false; @action - changeUILook = () => { - this.remoteVideoAdded = true; - }; + changeUILook = () => (this.remoteVideoAdded = true); /** * Function that submits the title entered by user on enter press. @@ -42,14 +39,9 @@ export class DashWebRTCVideo extends React.Component { - refreshVideos(); - }; + onClickRefresh = () => refreshVideos(); - onClickHangUp = () => { - hangup(); - }; + onClickHangUp = () => hangup(); render() { const content = ( diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 9c2350bd0..ff416bbe7 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -31,6 +31,7 @@ import { BoolCast, Cast, DocCast, FieldValue, NumCast, StrCast, ToConstructor } import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField } from './URLField'; import { containedFieldChangedHandler, deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, setter, SharingPermissions } from './util'; import * as JSZip from 'jszip'; +import { FieldViewProps } from '../client/views/nodes/FieldView'; export const LinkedTo = '-linkedTo'; export namespace Field { export function toKeyValueString(doc: Doc, key: string): string { @@ -423,7 +424,7 @@ export namespace Doc { // export async function SetInPlace(doc: Doc, key: string, value: Field | undefined, defaultProto: boolean) { if (key.startsWith('_')) key = key.substring(1); - const hasProto = Doc.GetProto(doc) !== doc ? Doc.GetProto(doc) : undefined; + const hasProto = doc[DocData] !== doc ? doc[DocData] : undefined; const onDeleg = Object.getOwnPropertyNames(doc).indexOf(key) !== -1; const onProto = hasProto && Object.getOwnPropertyNames(hasProto).indexOf(key) !== -1; if (onDeleg || !hasProto || (!onProto && !defaultProto)) { @@ -510,7 +511,7 @@ export namespace Doc { export function RemoveDocFromList(listDoc: Doc, fieldKey: string | undefined, doc: Doc) { const key = fieldKey ? fieldKey : Doc.LayoutFieldKey(listDoc); if (listDoc[key] === undefined) { - Doc.GetProto(listDoc)[key] = new List(); + listDoc[DocData][key] = new List(); } const list = Cast(listDoc[key], listSpec(Doc)); if (list) { @@ -530,7 +531,7 @@ export namespace Doc { export function AddDocToList(listDoc: Doc, fieldKey: string | undefined, doc: Doc, relativeTo?: Doc, before?: boolean, first?: boolean, allowDuplicates?: boolean, reversed?: boolean) { const key = fieldKey ? fieldKey : Doc.LayoutFieldKey(listDoc); if (listDoc[key] === undefined) { - Doc.GetProto(listDoc)[key] = new List(); + listDoc[DocData][key] = new List(); } const list = Cast(listDoc[key], listSpec(Doc)); if (list) { @@ -564,7 +565,7 @@ export namespace Doc { Doc.SetLayout(embedding, Doc.MakeEmbedding(layout)); } embedding.createdFrom = doc; - embedding.proto_embeddingId = Doc.GetProto(doc).proto_embeddingId = DocListCast(Doc.GetProto(doc).proto_embeddings).length - 1; + embedding.proto_embeddingId = doc[DocData].proto_embeddingId = DocListCast(doc[DocData].proto_embeddings).length - 1; !Doc.GetT(embedding, 'title', 'string', true) && (embedding.title = ComputedField.MakeFunction(`renameEmbedding(this)`)); embedding.author = Doc.CurrentUserEmail; @@ -1084,8 +1085,8 @@ export namespace Doc { export function LayoutField(doc: Doc) { return doc[StrCast(doc.layout_fieldKey, 'layout')]; } - export function LayoutFieldKey(doc: Doc): string { - return StrCast(Doc.Layout(doc).layout).split("'")[1]; // bcz: TODO check on this . used to always reference 'layout', now it uses the layout speicfied by the current layout_fieldKey + export function LayoutFieldKey(doc: Doc, templateLayoutString?: string): string { + return StrCast(templateLayoutString || Doc.Layout(doc).layout).split("'")[1]; // bcz: TODO check on this . used to always reference 'layout', now it uses the layout speicfied by the current layout_fieldKey } export function NativeAspect(doc: Doc, dataDoc?: Doc, useDim?: boolean) { return Doc.NativeWidth(doc, dataDoc, useDim) / (Doc.NativeHeight(doc, dataDoc, useDim) || 1); @@ -1100,10 +1101,10 @@ export namespace Doc { return NumCast(doc._nativeHeight, nheight || dheight); } export function SetNativeWidth(doc: Doc, width: number | undefined, fieldKey?: string) { - doc[(fieldKey ?? Doc.LayoutFieldKey(doc)) + '_nativeWidth'] = width; + doc[(fieldKey || Doc.LayoutFieldKey(doc)) + '_nativeWidth'] = width; } export function SetNativeHeight(doc: Doc, height: number | undefined, fieldKey?: string) { - doc[(fieldKey ?? Doc.LayoutFieldKey(doc)) + '_nativeHeight'] = height; + doc[(fieldKey || Doc.LayoutFieldKey(doc)) + '_nativeHeight'] = height; } const manager = new UserDocData(); @@ -1127,22 +1128,22 @@ export namespace Doc { } const isSearchMatchCache = computedFn(function IsSearchMatch(doc: Doc) { - return brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) : brushManager.SearchMatchDoc.has(Doc.GetProto(doc)) ? brushManager.SearchMatchDoc.get(Doc.GetProto(doc)) : undefined; + return brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) : brushManager.SearchMatchDoc.has(doc[DocData]) ? brushManager.SearchMatchDoc.get(doc[DocData]) : undefined; }); export function IsSearchMatch(doc: Doc) { return isSearchMatchCache(doc); } export function IsSearchMatchUnmemoized(doc: Doc) { - return brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) : brushManager.SearchMatchDoc.has(Doc.GetProto(doc)) ? brushManager.SearchMatchDoc.get(Doc.GetProto(doc)) : undefined; + return brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) : brushManager.SearchMatchDoc.has(doc[DocData]) ? brushManager.SearchMatchDoc.get(doc[DocData]) : undefined; } export function SetSearchMatch(doc: Doc, results: { searchMatch: number }) { - if (doc && GetEffectiveAcl(doc) !== AclPrivate && GetEffectiveAcl(Doc.GetProto(doc)) !== AclPrivate) { + if (doc && GetEffectiveAcl(doc) !== AclPrivate && GetEffectiveAcl(doc[DocData]) !== AclPrivate) { brushManager.SearchMatchDoc.set(doc, results); } return doc; } export function SearchMatchNext(doc: Doc, backward: boolean) { - if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc; + if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(doc[DocData]) === AclPrivate) return doc; const result = brushManager.SearchMatchDoc.get(doc); const num = Math.abs(result?.searchMatch || 0) + 1; runInAction(() => result && brushManager.SearchMatchDoc.set(doc, { searchMatch: backward ? -num : num })); @@ -1160,13 +1161,13 @@ export namespace Doc { } // returns 'how' a Doc has been brushed over - whether the document itself was brushed, it's prototype, or neither export function GetBrushStatus(doc: Doc) { - if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate || doc.opacity === 0) return DocBrushStatus.unbrushed; - return doc[Brushed] ? DocBrushStatus.selfBrushed : Doc.GetProto(doc)[Brushed] ? DocBrushStatus.protoBrushed : DocBrushStatus.unbrushed; + if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(doc[DocData]) === AclPrivate || doc.opacity === 0) return DocBrushStatus.unbrushed; + return doc[Brushed] ? DocBrushStatus.selfBrushed : doc[DocData][Brushed] ? DocBrushStatus.protoBrushed : DocBrushStatus.unbrushed; } export function BrushDoc(doc: Doc, unbrush = false) { - if (doc && GetEffectiveAcl(doc) !== AclPrivate && GetEffectiveAcl(Doc.GetProto(doc)) !== AclPrivate) { + if (doc && GetEffectiveAcl(doc) !== AclPrivate && GetEffectiveAcl(doc[DocData]) !== AclPrivate) { brushManager.brushDoc(doc, unbrush); - brushManager.brushDoc(Doc.GetProto(doc), unbrush); + brushManager.brushDoc(doc[DocData], unbrush); } return doc; } @@ -1186,6 +1187,7 @@ export namespace Doc { } export function linkFollowUnhighlight() { clearTimeout(UnhighlightTimer); + UnhighlightTimer = 0; UnhighlightWatchers.forEach(watcher => watcher()); UnhighlightWatchers.length = 0; highlightedDocs.forEach(doc => Doc.UnHighlightDoc(doc)); @@ -1205,8 +1207,8 @@ export namespace Doc { export var highlightedDocs = new ObservableSet(); export function IsHighlighted(doc: Doc) { - if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate || doc.opacity === 0) return false; - return doc[Highlight] || Doc.GetProto(doc)[Highlight]; + if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(doc[DocData]) === AclPrivate || doc.opacity === 0) return false; + return doc[Highlight] || doc[DocData][Highlight]; } export function HighlightDoc(doc: Doc, dataAndDisplayDocs = true, presentation_effect?: Doc) { runInAction(() => { @@ -1214,8 +1216,8 @@ export namespace Doc { doc[Highlight] = true; doc[Animation] = presentation_effect; if (dataAndDisplayDocs) { - highlightedDocs.add(Doc.GetProto(doc)); - Doc.GetProto(doc)[Highlight] = true; + highlightedDocs.add(doc[DocData]); + doc[DocData][Highlight] = true; } }); } @@ -1224,8 +1226,8 @@ export namespace Doc { runInAction(() => { (doc ? [doc] : Array.from(highlightedDocs)).forEach(doc => { highlightedDocs.delete(doc); - highlightedDocs.delete(Doc.GetProto(doc)); - doc[Highlight] = Doc.GetProto(doc)[Highlight] = false; + highlightedDocs.delete(doc[DocData]); + doc[Highlight] = doc[DocData][Highlight] = false; doc[Animation] = undefined; }); }); @@ -1346,7 +1348,7 @@ export namespace Doc { }); } - export function styleFromLayoutString(doc: Doc, props: any, scale: number) { + export function styleFromLayoutString(doc: Doc, props: FieldViewProps, scale: number) { const style: { [key: string]: any } = {}; const divKeys = ['width', 'height', 'fontSize', 'transform', 'left', 'backgroundColor', 'left', 'right', 'top', 'bottom', 'pointerEvents', 'position']; const replacer = (match: any, expr: string, offset: any, string: any) => { @@ -1354,7 +1356,7 @@ export namespace Doc { return ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: 'number' })?.script.run({ this: doc, self: doc, scale }).result?.toString() ?? ''; }; divKeys.map((prop: string) => { - const p = props[prop]; + const p = (props as any)[prop]; typeof p === 'string' && (style[prop] = p?.replace(/{([^.'][^}']+)}/g, replacer)); }); return style; @@ -1593,7 +1595,7 @@ ScriptingGlobals.add(function idToDoc(id: string): any { return IdToDoc(id); }); ScriptingGlobals.add(function renameEmbedding(doc: any) { - return StrCast(Doc.GetProto(doc).title).replace(/\([0-9]*\)/, '') + `(${doc.proto_embeddingId})`; + return StrCast(doc[DocData].title).replace(/\([0-9]*\)/, '') + `(${doc.proto_embeddingId})`; }); ScriptingGlobals.add(function getProto(doc: any) { return Doc.GetProto(doc); -- cgit v1.2.3-70-g09d2