From a6cc25e5d03ffed16bfaa32e48e9cc2eaff7deaf Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 7 Nov 2023 13:48:26 -0500 Subject: Changed how selection works to avoid invalidations. Fixed Cast problem with ProxyFields that caused renameEmbedding to infinite loop.. Changed brushing for the same reason. Cleaned up a few things with filter code. --- src/Utils.ts | 24 +++--- src/client/documents/Documents.ts | 2 +- src/client/util/CurrentUserUtils.ts | 2 +- src/client/util/SelectionManager.ts | 59 ++++---------- src/client/views/DocComponent.tsx | 8 +- src/client/views/StyleProvider.tsx | 6 +- src/client/views/collections/CollectionMenu.tsx | 19 ----- src/client/views/collections/CollectionSubView.tsx | 2 +- src/client/views/collections/TabDocView.tsx | 4 +- .../CollectionFreeFormLinkView.tsx | 20 ++--- .../collectionSchema/CollectionSchemaView.tsx | 12 ++- src/client/views/nodes/DocumentView.tsx | 18 +++- src/client/views/nodes/MapBox/MapBox.tsx | 4 +- src/client/views/nodes/MapBox/MapBox2.tsx | 4 +- src/client/views/nodes/WebBox.tsx | 4 +- src/client/views/pdf/Annotation.tsx | 2 +- src/client/views/pdf/PDFViewer.tsx | 12 +-- src/fields/Doc.ts | 95 +++++++--------------- src/fields/DocSymbols.ts | 2 + src/fields/Types.ts | 3 + 20 files changed, 119 insertions(+), 183 deletions(-) (limited to 'src') diff --git a/src/Utils.ts b/src/Utils.ts index 330ca59f9..9499aaf2f 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -157,23 +157,19 @@ export namespace Utils { const isTransparentFunctionHack = 'isTransparent(__value__)'; export const noRecursionHack = '__noRecursion'; - export const noDragsDocFilter = 'noDragDocs::any::check'; + + // special case filters + export const noDragDocsFilter = 'noDragDocs::any::check'; + export const TransparentBackgroundFilter = `backgroundColor::${isTransparentFunctionHack},${noRecursionHack}::check`; // bcz: hack. noRecursion should probably be either another ':' delimited field, or it should be a modifier to the comparision (eg., check, x, etc) field + export const OpaqueBackgroundFilter = `backgroundColor::${isTransparentFunctionHack},${noRecursionHack}::x`; // bcz: hack. noRecursion should probably be either another ':' delimited field, or it should be a modifier to the comparision (eg., check, x, etc) field + export function IsRecursiveFilter(val: string) { return !val.includes(noRecursionHack); } - export function HasTransparencyFilter(val: string) { - return val.includes(isTransparentFunctionHack); - } - export function IsTransparentFilter() { - // bcz: isTransparent(__value__) is a hack. it would be nice to have acual functions be parsed, but now Doc.matchFieldValue is hardwired to recognize just this one - return `backgroundColor::${isTransparentFunctionHack},${noRecursionHack}::check`; // bcz: hack. noRecursion should probably be either another ':' delimited field, or it should be a modifier to the comparision (eg., check, x, etc) field - } - export function IsOpaqueFilter() { - // bcz: isTransparent(__value__) is a hack. it would be nice to have acual functions be parsed, but now Doc.matchFieldValue is hardwired to recognize just this one - return `backgroundColor::${isTransparentFunctionHack},${noRecursionHack}::x`; // bcz: hack. noRecursion should probably be either another ':' delimited field, or it should be a modifier to the comparision (eg., check, x, etc) field - } - export function IsPropUnsetFilter(prop: string) { - return `${prop}::any,${noRecursionHack}::unset`; + export function HasFunctionFilter(val: string) { + if (val.includes(isTransparentFunctionHack)) return (color: string) => color !== '' && DashColor(color).alpha() !== 1; + // add other function filters here... + return undefined; } export function toRGBAstr(col: { r: number; g: number; b: number; a?: number }) { diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 4086ede20..c5a42aadc 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1284,7 +1284,7 @@ export namespace DocUtils { if (d.cookies && (!filterFacets.cookies || !Object.keys(filterFacets.cookies).some(key => d.cookies === key))) { return false; } - for (const facetKey of Object.keys(filterFacets).filter(fkey => fkey !== 'cookies' && fkey !== Utils.noDragsDocFilter.split(Doc.FilterSep)[0])) { + for (const facetKey of Object.keys(filterFacets).filter(fkey => fkey !== 'cookies' && fkey !== Utils.noDragDocsFilter.split(Doc.FilterSep)[0])) { const facet = filterFacets[facetKey]; // facets that match some value in the field of the document (e.g. some text field) diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 87ee1b252..ba3c26b42 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -339,7 +339,7 @@ export class CurrentUserUtils { /// returns descriptions needed to buttons for the left sidebar to open up panes displaying different collections of documents static leftSidebarMenuBtnDescriptions(doc: Doc):{title:string, target:Doc, icon:string, toolTip: string, scripts:{[key:string]:any}, funcs?:{[key:string]:any}, hidden?: boolean}[] { - const badgeValue = "((len) => len && len !== '0' ? len: undefined)(docList(self.target.data).filter(doc => !docList(self.target.viewed).includes(doc)).length.toString())"; + const badgeValue = "((len) => len && len !== '0' ? len: undefined)(docList(self.target?.data).filter(doc => !docList(self.target.viewed).includes(doc)).length.toString())"; const getActiveDashTrails = "Doc.ActiveDashboard?.myTrails"; return [ { title: "Dashboards", toolTip: "Dashboards", target: this.setupDashboards(doc, "myDashboards"), ignoreClick: true, icon: "desktop", funcs: {hidden: "IsNoviceMode()"} }, diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index d0f66d124..fcf705ac0 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -1,6 +1,6 @@ -import { action, observable, ObservableMap } from 'mobx'; -import { computedFn } from 'mobx-utils'; +import { action, observable } from 'mobx'; import { Doc, Opt } from '../../fields/Doc'; +import { DocViews } from '../../fields/DocSymbols'; import { List } from '../../fields/List'; import { listSpec } from '../../fields/Schema'; import { Cast, DocCast } from '../../fields/Types'; @@ -12,9 +12,8 @@ import { UndoManager } from './UndoManager'; export namespace SelectionManager { class Manager { - @observable IsDragging: boolean = false; - SelectedViewsMap: ObservableMap = new ObservableMap(); @observable SelectedViews: DocumentView[] = []; + @observable IsDragging: boolean = false; @observable SelectedSchemaDocument: Doc | undefined; @action @@ -24,27 +23,21 @@ export namespace SelectionManager { @action SelectView(docView: DocumentView, ctrlPressed: boolean): void { // if doc is not in SelectedDocuments, add it - if (!manager.SelectedViewsMap.get(docView)) { - if (!ctrlPressed) { - this.DeselectAll(); - } - + if (!docView.SELECTED) { + if (!ctrlPressed) this.DeselectAll(); manager.SelectedViews.push(docView); - manager.SelectedViewsMap.set(docView, docView.rootDoc); - docView.props.whenChildContentsActiveChanged(true); - } else if (!ctrlPressed && (Array.from(manager.SelectedViewsMap.entries()).length > 1 || manager.SelectedSchemaDocument)) { - Array.from(manager.SelectedViewsMap.keys()).map(dv => dv !== docView && dv.props.whenChildContentsActiveChanged(false)); + } else if (!ctrlPressed && (manager.SelectedViews.length > 1 || manager.SelectedSchemaDocument)) { + manager.SelectedViews.filter(dv => dv !== docView).forEach(dv => dv.props.whenChildContentsActiveChanged(false)); manager.SelectedSchemaDocument = undefined; manager.SelectedViews.length = 0; - manager.SelectedViewsMap.clear(); - manager.SelectedViews.push(docView); - manager.SelectedViewsMap.set(docView, docView.rootDoc); } + docView.SELECTED = true; + docView.props.whenChildContentsActiveChanged(true); } @action DeselectView(docView?: DocumentView): void { - if (docView && manager.SelectedViewsMap.get(docView)) { - manager.SelectedViewsMap.delete(docView); + if (docView && manager.SelectedViews.includes(docView)) { + docView.SELECTED = false; manager.SelectedViews.splice(manager.SelectedViews.indexOf(docView), 1); docView.props.whenChildContentsActiveChanged(false); } @@ -54,8 +47,10 @@ export namespace SelectionManager { LinkManager.currentLink = undefined; LinkManager.currentLinkAnchor = undefined; manager.SelectedSchemaDocument = undefined; - Array.from(manager.SelectedViewsMap.keys()).forEach(dv => dv.props.whenChildContentsActiveChanged(false)); - manager.SelectedViewsMap.clear(); + manager.SelectedViews.forEach(dv => { + dv.SELECTED = false; + dv.props.whenChildContentsActiveChanged(false); + }); manager.SelectedViews.length = 0; } } @@ -74,45 +69,27 @@ export namespace SelectionManager { manager.SelectSchemaViewDoc(document); } - const IsSelectedCache = computedFn(function isSelected(doc: DocumentView) { - // wrapping get() in a computedFn only generates mobx() invalidations when the return value of the function for the specific get parameters has changed - return manager.SelectedViewsMap.get(doc) ? true : false; - }); // computed functions, such as used in IsSelected generate errors if they're called outside of a // reaction context. Specifying the context with 'outsideReaction' allows an efficiency feature // to avoid unnecessary mobx invalidations when running inside a reaction. - export function IsSelected(doc: DocumentView | undefined, outsideReaction?: boolean): boolean { - return !doc - ? false - : outsideReaction - ? manager.SelectedViewsMap.get(doc) - ? true - : false // get() accesses a hashtable -- setting anything in the hashtable generates a mobx invalidation for every get() - : IsSelectedCache(doc); + export function IsSelected(dv?: DocumentView | Doc): boolean { + return (dv instanceof Doc ? Array.from(dv[DocViews]) : dv ? [dv] : []).some(dv => dv?.SELECTED); } export function DeselectAll(except?: Doc): void { - let found: DocumentView | undefined = undefined; - if (except) { - for (const view of Array.from(manager.SelectedViewsMap.keys())) { - if (view.props.Document === except) found = view; - } - } - + const found = manager.SelectedViews.find(dv => dv.Document === except); manager.DeselectAll(); if (found) manager.SelectView(found, false); } export function Views(): Array { return manager.SelectedViews; - // Array.from(manager.SelectedViewsMap.keys()); //.filter(dv => manager.SelectedViews.get(dv)?._type_collection !== CollectionViewType.Docking); } export function SelectedSchemaDoc(): Doc | undefined { return manager.SelectedSchemaDocument; } export function Docs(): Doc[] { return manager.SelectedViews.map(dv => dv.rootDoc).filter(doc => doc?._type_collection !== CollectionViewType.Docking); - // Array.from(manager.SelectedViewsMap.values()).filter(doc => doc?._type_collection !== CollectionViewType.Docking); } } ScriptingGlobals.add(function SelectionManager_selectedDocType(type: string, expertMode: boolean, checkContext?: boolean) { diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 483b92957..57cea77c9 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -1,10 +1,10 @@ import { action, computed, observable } from 'mobx'; import { DateField } from '../../fields/DateField'; -import { Doc, DocListCast, HierarchyMapping, Opt, ReverseHierarchyMap } from '../../fields/Doc'; -import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, DocAcl, DocData } from '../../fields/DocSymbols'; +import { Doc, DocListCast, Opt } from '../../fields/Doc'; +import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, DocData } from '../../fields/DocSymbols'; import { List } from '../../fields/List'; -import { Cast, DocCast, StrCast } from '../../fields/Types'; -import { distributeAcls, GetEffectiveAcl, inheritParentAcls, SharingPermissions } from '../../fields/util'; +import { Cast } from '../../fields/Types'; +import { GetEffectiveAcl, inheritParentAcls } from '../../fields/util'; import { returnFalse } from '../../Utils'; import { DocUtils } from '../documents/Documents'; import { DocumentType } from '../documents/DocumentTypes'; diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index c6d3efd0c..72d7cd1c5 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -7,13 +7,13 @@ import { extname } from 'path'; import { BsArrowDown, BsArrowDownUp, BsArrowUp } from 'react-icons/bs'; import { FaFilter } from 'react-icons/fa'; import { Doc, Opt, StrListCast } from '../../fields/Doc'; +import { DocViews } from '../../fields/DocSymbols'; import { BoolCast, Cast, DocCast, ImageCast, NumCast, StrCast } from '../../fields/Types'; import { DashColor, lightOrDark, Utils } from '../../Utils'; import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; import { DocFocusOrOpen, DocumentManager } from '../util/DocumentManager'; import { IsFollowLinkScript } from '../util/LinkFollower'; import { LinkManager } from '../util/LinkManager'; -import { SelectionManager } from '../util/SelectionManager'; import { SettingsManager } from '../util/SettingsManager'; import { undoBatch, UndoManager } from '../util/UndoManager'; import { TreeSort } from './collections/TreeSort'; @@ -116,7 +116,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt dv.rootDoc === doc); + const selected = Array.from(doc?.[DocViews]??[]).filter(dv => dv.SELECTED).length; const highlightIndex = Doc.isBrushedHighlightedDegree(doc) || (selected ? Doc.DocBrushStatus.selfBrushed : 0); const highlightColor = ['transparent', 'rgb(68, 118, 247)', selected ? "black" : 'rgb(68, 118, 247)', 'orange', 'lightBlue'][highlightIndex]; const highlightStyle = ['solid', 'dashed', 'solid', 'solid', 'solid'][highlightIndex]; @@ -282,7 +282,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt Utils.IsRecursiveFilter(f) && f !== Utils.noDragsDocFilter).length || docProps?.childFiltersByRanges().length + : docProps?.childFilters?.().filter(f => Utils.IsRecursiveFilter(f) && f !== Utils.noDragDocsFilter).length || docProps?.childFiltersByRanges().length ? 'orange' //'inheritsFilter' : undefined; return !showFilterIcon ? null : ( diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 52cf40635..d0eadd9aa 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -557,25 +557,6 @@ export class CollectionViewBaseChrome extends React.Component{Doc.isDocPinned(targetDoc) ? 'Unpin from presentation' : 'Pin to presentation'}} placement="top"> - - - ); - } @undoBatch @action diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 8a1ba0df1..e9192ebbe 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -125,7 +125,7 @@ export function CollectionSubView(moreProps?: X) { const docsforFilter: Doc[] = []; childDocs.forEach(d => { // dragging facets - const dragged = this.props.childFilters?.().some(f => f.includes(Utils.noDragsDocFilter)); + const dragged = this.props.childFilters?.().some(f => f.includes(Utils.noDragDocsFilter)); if (dragged && SnappingManager.GetCanEmbed() && DragManager.docsBeingDragged.includes(d)) return false; let notFiltered = d.z || Doc.IsSystem(d) || DocUtils.FilterDocs([d], this.unrecursiveDocFilters(), childFiltersByRanges, this.props.Document).length > 0; if (notFiltered) { diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 26aa5a121..41f3b2603 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -53,7 +53,7 @@ export class TabDocView extends React.Component { @observable _isActive: boolean = false; @observable _isAnyChildContentActive = false; @computed get _isUserActivated() { - return SelectionManager.Views().some(view => view.rootDoc === this._document) || this._isAnyChildContentActive; + return SelectionManager.IsSelected(this._document) || this._isAnyChildContentActive; } @computed get _isContentActive() { return this._isUserActivated || this._hovering; @@ -203,7 +203,7 @@ export class TabDocView extends React.Component { } }); tab._disposers.selectionDisposer = reaction( - () => SelectionManager.Views().some(view => view.rootDoc === this._document), + () => SelectionManager.IsSelected(this._document), action(selected => { if (selected) this._activated = true; const toggle = tab.element[0].children[2].children[0] as HTMLInputElement; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index 24a758d8c..aca6df3c9 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -1,7 +1,7 @@ import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import { Doc, Field } from '../../../../fields/Doc'; -import { DocCss } from '../../../../fields/DocSymbols'; +import { Brushed, DocCss } from '../../../../fields/DocSymbols'; import { Id } from '../../../../fields/FieldSymbols'; import { List } from '../../../../fields/List'; import { Cast, NumCast, StrCast } from '../../../../fields/Types'; @@ -223,8 +223,8 @@ export class CollectionFreeFormLinkView extends React.Component - - + + diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index f73c037f4..ce63a2cf2 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -86,7 +86,17 @@ export class CollectionSchemaView extends CollectionSubView() { } @computed get _selectedDocs() { - return SelectionManager.Docs().filter(doc => Doc.AreProtosEqual(DocCast(doc.embedContainer), this.rootDoc)); + const selected = SelectionManager.Docs().filter(doc => Doc.AreProtosEqual(DocCast(doc.embedContainer), this.rootDoc)); + if (!selected.length) { + for (const sel of SelectionManager.Docs()) { + const contextPath = DocumentManager.GetContextPath(sel, true); + if (contextPath.includes(this.rootDoc)) { + const parentInd = contextPath.indexOf(this.rootDoc); + return parentInd < contextPath.length - 1 ? [contextPath[parentInd + 1]] : []; + } + } + } + return selected; } @computed get documentKeys() { diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 98b13f90f..d87efb7b1 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -5,7 +5,7 @@ import { observer } from 'mobx-react'; import { computedFn } from 'mobx-utils'; import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal'; import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../fields/Doc'; -import { AclPrivate, Animation, AudioPlay, DocData, Width } from '../../../fields/DocSymbols'; +import { AclPrivate, Animation, AudioPlay, DocData, DocViews, Width } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; import { List } from '../../../fields/List'; @@ -900,7 +900,7 @@ export class DocumentViewInternal extends DocComponent this._rootSelected; + rootSelected = () => this._rootSelected; panelHeight = () => this.props.PanelHeight() - this.headerMargin; screenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -this.headerMargin); onClickFunc: any = () => (this.disableClickScriptFunc ? undefined : this.onClickHandler); @@ -1345,6 +1345,13 @@ export class DocumentViewInternal extends DocComponent { public static ROOT_DIV = 'documentView-effectsWrapper'; + @observable _selected = false; + public get SELECTED() { + return this._selected; + } + public set SELECTED(val) { + this._selected = val; + } @observable public static Interacting = false; @observable public static LongPress = false; @observable public static ExploreMode = false; @@ -1579,7 +1586,7 @@ export class DocumentView extends React.Component { scaleToScreenSpace = () => (1 / (this.props.NativeDimScaling?.() || 1)) * this.screenToLocalTransform().Scale; docViewPathFunc = () => this.docViewPath; - isSelected = (outsideReaction?: boolean) => SelectionManager.IsSelected(this, outsideReaction); + isSelected = () => SelectionManager.IsSelected(this); select = (extendSelection: boolean, focusSelection?: boolean) => { SelectionManager.SelectView(this, extendSelection); if (focusSelection) { @@ -1602,7 +1609,10 @@ export class DocumentView extends React.Component { .ScreenToLocalTransform() .translate(-this.centeringX, -this.centeringY) .scale(this.trueNativeWidth() ? 1 / this.nativeScaling : 1); + + @action componentDidMount() { + this.rootDoc[DocViews].add(this); this._disposers.updateContentsScript = reaction( () => ScriptCast(this.rootDoc.updateContentsScript)?.script?.run({ this: this.props.Document, self: Cast(this.rootDoc, Doc, null) || this.props.Document }).result, output => output @@ -1617,7 +1627,9 @@ export class DocumentView extends React.Component { ); !BoolCast(this.props.Document.dontRegisterView, this.props.dontRegisterView) && DocumentManager.Instance.AddView(this); } + @action componentWillUnmount() { + this.rootDoc[DocViews].delete(this); Object.values(this._disposers).forEach(disposer => disposer?.()); !BoolCast(this.props.Document.dontRegisterView, this.props.dontRegisterView) && DocumentManager.Instance.RemoveView(this); } diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx index 50b070e7f..08dda2e1f 100644 --- a/src/client/views/nodes/MapBox/MapBox.tsx +++ b/src/client/views/nodes/MapBox/MapBox.tsx @@ -307,8 +307,8 @@ export class MapBox extends ViewBoxAnnotatableComponent this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - this.sidebarWidth(); panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop)); - transparentFilter = () => [...this.props.childFilters(), Utils.IsTransparentFilter()]; - opaqueFilter = () => [...this.props.childFilters(), Utils.IsOpaqueFilter()]; + transparentFilter = () => [...this.props.childFilters(), Utils.TransparentBackgroundFilter]; + opaqueFilter = () => [...this.props.childFilters(), Utils.OpaqueBackgroundFilter]; infoWidth = () => this.props.PanelWidth() / 5; infoHeight = () => this.props.PanelHeight() / 5; anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick; diff --git a/src/client/views/nodes/MapBox/MapBox2.tsx b/src/client/views/nodes/MapBox/MapBox2.tsx index 407a91dd0..d38857d90 100644 --- a/src/client/views/nodes/MapBox/MapBox2.tsx +++ b/src/client/views/nodes/MapBox/MapBox2.tsx @@ -547,8 +547,8 @@ export class MapBox2 extends ViewBoxAnnotatableComponent this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - this.sidebarWidth(); panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop)); - transparentFilter = () => [...this.props.childFilters(), Utils.IsTransparentFilter()]; - opaqueFilter = () => [...this.props.childFilters(), Utils.IsOpaqueFilter()]; + transparentFilter = () => [...this.props.childFilters(), Utils.TransparentBackgroundFilter]; + opaqueFilter = () => [...this.props.childFilters(), Utils.OpaqueBackgroundFilter]; infoWidth = () => this.props.PanelWidth() / 5; infoHeight = () => this.props.PanelHeight() / 5; anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick; diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 2aca314da..5c526fe38 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -1053,8 +1053,8 @@ export class WebBox extends ViewBoxAnnotatableComponent this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop)); anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick; - transparentFilter = () => [...this.props.childFilters(), Utils.IsTransparentFilter()]; - opaqueFilter = () => [...this.props.childFilters(), Utils.noDragsDocFilter, ...(SnappingManager.GetCanEmbed() ? [] : [Utils.IsOpaqueFilter()])]; + transparentFilter = () => [...this.props.childFilters(), Utils.TransparentBackgroundFilter]; + opaqueFilter = () => [...this.props.childFilters(), Utils.noDragDocsFilter, ...(SnappingManager.GetCanEmbed() ? [] : [Utils.OpaqueBackgroundFilter])]; childStyleProvider = (doc: Doc | undefined, props: Opt, property: string): any => { if (doc instanceof Doc && property === StyleProp.PointerEvents) { if (this.inlineTextAnnotations.includes(doc)) return 'none'; diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index 52904b852..17a8048e9 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -90,7 +90,7 @@ class RegionAnnotation extends React.Component { @computed get linkHighlighted() { for (const link of LinkManager.Instance.getAllDirectLinks(this.props.document)) { const a1 = LinkManager.getOppositeAnchor(link, this.props.document); - if (a1 && Doc.IsBrushedDegreeUnmemoized(DocCast(a1.annotationOn, this.props.document))) return true; + if (a1 && Doc.IsBrushedDegree(DocCast(a1.annotationOn, this.props.document))) return true; } } diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 43b662f0f..c1b2749fb 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -110,13 +110,7 @@ export class PDFViewer extends React.Component { this._disposers.selected = reaction( () => this.props.isSelected(), - selected => { - // if (!selected) { - // Array.from(this._savedAnnotations.values()).forEach(v => v.forEach(a => a.remove())); - // Array.from(this._savedAnnotations.keys()).forEach(k => this._savedAnnotations.set(k, [])); - // } - SelectionManager.Views().length === 1 && this.setupPdfJsViewer(); - }, + selected => SelectionManager.Views().length === 1 && this.setupPdfJsViewer(), { fireImmediately: true } ); this._disposers.curPage = reaction( @@ -509,8 +503,8 @@ export class PDFViewer extends React.Component { overlayTransform = () => this.scrollXf().scale(1 / NumCast(this.props.layoutDoc._freeform_scale, 1)); panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1); panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); - transparentFilter = () => [...this.props.childFilters(), Utils.IsTransparentFilter()]; - opaqueFilter = () => [...this.props.childFilters(), Utils.noDragsDocFilter, ...(SnappingManager.GetCanEmbed() && this.props.isContentActive() ? [] : [Utils.IsOpaqueFilter()])]; + transparentFilter = () => [...this.props.childFilters(), Utils.TransparentBackgroundFilter]; + opaqueFilter = () => [...this.props.childFilters(), Utils.noDragDocsFilter, ...(SnappingManager.GetCanEmbed() && this.props.isContentActive() ? [] : [Utils.OpaqueBackgroundFilter])]; childStyleProvider = (doc: Doc | undefined, props: Opt, property: string): any => { if (doc instanceof Doc && property === StyleProp.PointerEvents) { if (this.inlineTextAnnotations.includes(doc) || this.props.isContentActive() === false) return 'none'; diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index feacdc9c5..2f9eea492 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -11,7 +11,7 @@ import { afterDocDeserialize, autoObject, Deserializable, SerializationHelper } import { undoable } from '../client/util/UndoManager'; import { decycle } from '../decycler/decycler'; import * as JSZipUtils from '../JSZipUtils'; -import { DashColor, incrementTitleCopy, intersectRect, Utils } from '../Utils'; +import { incrementTitleCopy, intersectRect, Utils } from '../Utils'; import { DateField } from './DateField'; import { AclAdmin, @@ -21,6 +21,7 @@ import { AclReadonly, Animation, AudioPlay, + Brushed, CachedUpdates, DirectLinks, DocAcl, @@ -28,6 +29,7 @@ import { DocData, DocFields, DocLayout, + DocViews, FieldKeys, FieldTuples, ForceServerWrite, @@ -52,6 +54,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, normalizeEmail, setter, SharingPermissions, TraceMobx } from './util'; import JSZip = require('jszip'); +import { DocumentView } from '../client/views/nodes/DocumentView'; export const LinkedTo = '-linkedTo'; export namespace Field { export function toKeyValueString(doc: Doc, key: string): string { @@ -354,6 +357,8 @@ export class Doc extends RefField { @observable public [AudioPlay]: any; // meant to store sound object from Howl @observable public [Animation]: Opt; @observable public [Highlight]: boolean = false; + @observable public [Brushed]: boolean = false; + @observable public [DocViews] = new ObservableSet(); static __Anim(Doc: Doc) { // for debugging to print AnimationSym field easily. return Doc[Animation]; @@ -808,9 +813,7 @@ export namespace Doc { const docAtKey = DocCast(clone[key]); if (docAtKey && !Doc.IsSystem(docAtKey)) { if (!Array.from(cloneMap.values()).includes(docAtKey)) { - if (cloneMap.has(docAtKey[Id])) { - clone[key] = cloneMap.get(docAtKey[Id]); - } else clone[key] = undefined; + clone[key] = cloneMap.get(docAtKey[Id]); } else { repairClone(docAtKey, cloneMap, visited); } @@ -1020,7 +1023,7 @@ export namespace Doc { references.add(doc); return; } - const excludeLists = ['My Recently Closed', 'My Header Bar', 'My Dashboards'].includes(StrCast(doc.title)); + const excludeLists = [Doc.MyRecentlyClosed, Doc.MyHeaderBar, Doc.MyDashboards].includes(doc); if (system !== undefined && ((system && !Doc.IsSystem(doc)) || (!system && Doc.IsSystem(doc)))) return; references.add(doc); Object.keys(doc).forEach(key => { @@ -1202,9 +1205,6 @@ export namespace Doc { // change it to render the target metadata field instead of what it was rendering before and assign it to the template field layout document. Doc.Layout(templateField).layout = templateFieldLayoutString.replace(/fieldKey={'[^']*'}/, `fieldKey={'${metadataFieldKey}'}`); - // assign the template field doc a delegate of any extension document that was previously used to render the template field (since extension doc's carry rendering informatino) - Doc.Layout(templateField)[metadataFieldKey + '_ext'] = Doc.MakeDelegate(templateField[templateFieldLayoutString?.split("'")[1] + '_ext'] as Doc); - return true; } @@ -1234,13 +1234,13 @@ export namespace Doc { export function isBrushedHighlightedDegree(doc: Doc) { return Doc.IsHighlighted(doc) ? DocBrushStatus.highlighted : Doc.IsBrushedDegree(doc); } - export function isBrushedHighlightedDegreeUnmemoized(doc: Doc) { - return Doc.IsHighlighted(doc) ? DocBrushStatus.highlighted : Doc.IsBrushedDegreeUnmemoized(doc); - } - export class DocBrush { - BrushedDoc: ObservableMap = new ObservableMap(); + BrushedDoc = new Set(); SearchMatchDoc: ObservableMap = new ObservableMap(); + brushDoc = action((doc: Doc, unbrush: boolean) => { + unbrush ? this.BrushedDoc.delete(doc) : this.BrushedDoc.add(doc); + doc[Brushed] = !unbrush; + }); } export const brushManager = new DocBrush(); @@ -1330,45 +1330,25 @@ export namespace Doc { brushManager.SearchMatchDoc.clear(); } - const isBrushedCache = computedFn(function IsBrushed(doc: Doc) { - return brushManager.BrushedDoc.has(doc) || brushManager.BrushedDoc.has(Doc.GetProto(doc)); - }); - export function IsBrushed(doc: Doc) { - return isBrushedCache(doc); - } - export enum DocBrushStatus { unbrushed = 0, protoBrushed = 1, selfBrushed = 2, highlighted = 3, } - // don't bother memoizing (caching) the result if called from a non-reactive context. (plus this avoids a warning message) - export function IsBrushedDegreeUnmemoized(doc: Doc) { - if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate || doc.opacity === 0) return DocBrushStatus.unbrushed; - const status = brushManager.BrushedDoc.has(doc) ? DocBrushStatus.selfBrushed : brushManager.BrushedDoc.has(Doc.GetProto(doc)) ? DocBrushStatus.protoBrushed : DocBrushStatus.unbrushed; - return status; - } + // returns 'how' a Doc has been brushed over - whether the document itself was brushed, it's prototype, or neither export function IsBrushedDegree(doc: Doc) { - return computedFn(function IsBrushDegree(doc: Doc) { - return Doc.IsBrushedDegreeUnmemoized(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; } - export function BrushDoc(doc: Doc) { + export function BrushDoc(doc: Doc, unbrush = false) { if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc; - runInAction(() => { - brushManager.BrushedDoc.set(doc, true); - brushManager.BrushedDoc.set(Doc.GetProto(doc), true); - }); + brushManager.brushDoc(doc, unbrush); + brushManager.brushDoc(Doc.GetProto(doc), unbrush); return doc; } export function UnBrushDoc(doc: Doc) { - if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc; - runInAction(() => { - brushManager.BrushedDoc.delete(doc); - brushManager.BrushedDoc.delete(Doc.GetProto(doc)); - }); - return doc; + return BrushDoc(doc, true); } export function LinkEndpoint(linkDoc: Doc, anchorDoc: Doc) { @@ -1436,7 +1416,7 @@ export namespace Doc { }); } export function UnBrushAllDocs() { - runInAction(() => brushManager.BrushedDoc.clear()); + Array.from(brushManager.BrushedDoc).forEach(action(doc => (doc[Brushed] = false))); } export function getDocTemplate(doc?: Doc) { @@ -1454,9 +1434,9 @@ export namespace Doc { } export function matchFieldValue(doc: Doc, key: string, value: any): boolean { - if (Utils.HasTransparencyFilter(value)) { - const isTransparent = (color: string) => color !== '' && DashColor(color).alpha() !== 1; - return isTransparent(StrCast(doc[key])); + const hasFunctionFilter = Utils.HasFunctionFilter(value); + if (hasFunctionFilter) { + return hasFunctionFilter(StrCast(doc[key])); } if (key === LinkedTo) { // links are not a field value, so handled here. value is an expression of form ([field=]idToDoc("...")) @@ -1481,11 +1461,9 @@ export namespace Doc { (value === Doc.FilterNone && fieldVal === undefined)) { return true; } - if (Cast(fieldVal, listSpec('string'), []).length) { - const vals = StrListCast(fieldVal); - const docs = vals.some(v => (v as any) instanceof Doc); - if (docs) return value === Field.toString(fieldVal as Field); - return vals.some(v => v.includes(value)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring + const vals = StrListCast(fieldVal); // list typing is very imperfect. casting to a string list doesn't mean that the entries will actually be strings + if (vals.length) { + return vals.some(v => typeof v === 'string' && v.includes(value)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring } return Field.toString(fieldVal as Field).includes(value); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring } @@ -1501,34 +1479,27 @@ export namespace Doc { doc.layout_fieldKey = deiconify || 'layout'; } export function setDocRangeFilter(container: Opt, key: string, range?: readonly number[], modifiers?: 'remove') { - //, modifiers: 'remove' | 'set' if (!container) return; const childFiltersByRanges = Cast(container._childFiltersByRanges, listSpec('string'), []); for (let i = 0; i < childFiltersByRanges.length; i += 3) { if (childFiltersByRanges[i] === key) { - console.log('this is key inside childfilters by range ' + key); childFiltersByRanges.splice(i, 3); - console.log('this is child filters by range ' + childFiltersByRanges); break; } } if (range !== undefined) { - console.log('in doc.ts in set range filter'); childFiltersByRanges.push(key); childFiltersByRanges.push(range[0].toString()); childFiltersByRanges.push(range[1].toString()); container._childFiltersByRanges = new List(childFiltersByRanges); - console.log('this is child filters by range ' + childFiltersByRanges[0] + ',' + childFiltersByRanges[1] + ',' + childFiltersByRanges[2]); - console.log('this is new list ' + container._childFiltersByRange); } if (modifiers) { childFiltersByRanges.splice(0, 3); container._childFiltersByRanges = new List(childFiltersByRanges); } - console.log('this is child filters by range END' + childFiltersByRanges[0] + ',' + childFiltersByRanges[1] + ',' + childFiltersByRanges[2]); } export const FilterSep = '::'; @@ -1599,12 +1570,6 @@ export namespace Doc { }); } - export function isDocPinned(doc: Doc) { - //add this new doc to props.Document - const curPres = Doc.ActivePresentation; - return !curPres ? false : DocListCast(curPres.data).findIndex(val => Doc.AreProtosEqual(val, doc)) !== -1; - } - export function styleFromLayoutString(rootDoc: Doc, layoutDoc: Doc, props: any, scale: number) { const style: { [key: string]: any } = {}; const divKeys = ['width', 'height', 'fontSize', 'transform', 'left', 'backgroundColor', 'left', 'right', 'top', 'bottom', 'pointerEvents', 'position']; @@ -1628,7 +1593,7 @@ export namespace Doc { if (ptx !== undefined && pty !== undefined && newPoint !== undefined) { const firstx = list.length ? NumCast(list[0].x) + ptx - newPoint[0] : 0; const firsty = list.length ? NumCast(list[0].y) + pty - newPoint[1] : 0; - docs.map(doc => { + docs.forEach(doc => { doc.x = NumCast(doc.x) - firstx; doc.y = NumCast(doc.y) - firsty; }); @@ -1871,10 +1836,6 @@ ScriptingGlobals.add(function setInPlace(doc: any, field: any, value: any) { ScriptingGlobals.add(function sameDocs(doc1: any, doc2: any) { return Doc.AreProtosEqual(doc1, doc2); }); -ScriptingGlobals.add(function DOC(id: string) { - console.log("Can't parse a document id in a script"); - return 'invalid'; -}); ScriptingGlobals.add(function assignDoc(doc: Doc, field: string, id: string) { return Doc.assignDocToField(doc, field, id); }); diff --git a/src/fields/DocSymbols.ts b/src/fields/DocSymbols.ts index df74cc9fe..87f186a23 100644 --- a/src/fields/DocSymbols.ts +++ b/src/fields/DocSymbols.ts @@ -8,6 +8,8 @@ export const Width = Symbol('DocWidth'); export const Height = Symbol('DocHeight'); export const Animation = Symbol('DocAnimation'); export const Highlight = Symbol('DocHighlight'); +export const DocViews = Symbol('DocViews'); +export const Brushed = Symbol('DocBrushed'); export const DocData = Symbol('DocData'); export const DocLayout = Symbol('DocLayout'); export const DocFields = Symbol('DocFields'); diff --git a/src/fields/Types.ts b/src/fields/Types.ts index 69dbe9756..337e8ca21 100644 --- a/src/fields/Types.ts +++ b/src/fields/Types.ts @@ -1,6 +1,7 @@ import { DateField } from './DateField'; import { Doc, Field, FieldResult, Opt } from './Doc'; import { List } from './List'; +import { ProxyField } from './Proxy'; import { RefField } from './RefField'; import { RichTextField } from './RichTextField'; import { ScriptField } from './ScriptField'; @@ -72,6 +73,8 @@ export function Cast(field: FieldResult, ctor: T, defaultVal } } else if (field instanceof (ctor as any)) { return field as ToType; + } else if (field instanceof ProxyField && field.value instanceof (ctor as any)) { + return field.value as ToType; } } return defaultVal === null ? undefined : defaultVal; -- cgit v1.2.3-70-g09d2