From 2a313f28fcb8675223708b0657de7517a3281095 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 17 Apr 2024 12:27:21 -0400 Subject: restoring eslint - updates not complete yet --- .../views/collections/CollectionCalendarView.tsx | 3 +- .../views/collections/CollectionCarousel3DView.tsx | 97 ++++--- .../views/collections/CollectionCarouselView.tsx | 5 +- .../views/collections/CollectionDockingView.tsx | 156 ++++++----- .../collections/CollectionMasonryViewFieldRow.tsx | 3 +- src/client/views/collections/CollectionMenu.tsx | 38 ++- .../views/collections/CollectionNoteTakingView.tsx | 15 +- .../collections/CollectionNoteTakingViewColumn.tsx | 2 +- .../CollectionNoteTakingViewDivider.tsx | 3 +- .../views/collections/CollectionPileView.tsx | 5 +- .../collections/CollectionStackedTimeline.tsx | 3 +- .../views/collections/CollectionStackingView.tsx | 79 +++--- .../CollectionStackingViewFieldColumn.tsx | 80 +++--- src/client/views/collections/CollectionSubView.tsx | 173 +++++++----- .../views/collections/CollectionTimeView.tsx | 5 +- .../views/collections/CollectionTreeView.tsx | 6 +- src/client/views/collections/CollectionView.tsx | 75 ++--- src/client/views/collections/TabDocView.tsx | 69 ++--- src/client/views/collections/TreeView.tsx | 307 +++++++++++---------- .../CollectionFreeFormInfoUI.tsx | 31 ++- .../CollectionFreeFormLayoutEngines.tsx | 18 +- .../CollectionFreeFormPannableContents.tsx | 18 +- .../CollectionFreeFormRemoteCursors.tsx | 6 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 122 ++++---- .../collections/collectionFreeForm/MarqueeView.tsx | 50 ++-- .../collectionGrid/CollectionGridView.tsx | 3 +- .../collectionLinear/CollectionLinearView.tsx | 66 ++--- .../collectionSchema/CollectionSchemaView.tsx | 24 +- .../collectionSchema/SchemaColumnHeader.tsx | 3 +- .../collections/collectionSchema/SchemaRowBox.tsx | 3 +- .../collectionSchema/SchemaTableCell.tsx | 12 +- 31 files changed, 810 insertions(+), 670 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/CollectionCalendarView.tsx b/src/client/views/collections/CollectionCalendarView.tsx index cbcc980a9..578ce77a5 100644 --- a/src/client/views/collections/CollectionCalendarView.tsx +++ b/src/client/views/collections/CollectionCalendarView.tsx @@ -1,7 +1,8 @@ import { computed, makeObservable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { dateRangeStrToDates, emptyFunction, returnTrue } from '../../../Utils'; +import { emptyFunction } from '../../../Utils'; +import { dateRangeStrToDates, returnTrue } from '../../../ClientUtils'; import { Doc, DocListCast } from '../../../fields/Doc'; import { StrCast } from '../../../fields/Types'; import { CollectionStackingView } from './CollectionStackingView'; diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx index 4e4bd43bf..8d8f41126 100644 --- a/src/client/views/collections/CollectionCarousel3DView.tsx +++ b/src/client/views/collections/CollectionCarousel3DView.tsx @@ -1,9 +1,12 @@ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { computed, makeObservable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { Utils, emptyFunction, returnFalse, returnZero } from '../../../Utils'; -import { Doc, DocListCast } from '../../../fields/Doc'; +import { returnZero } from '../../../ClientUtils'; +import { Utils } from '../../../Utils'; +import { Doc, DocListCast, Opt } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { DocumentType } from '../../documents/DocumentTypes'; @@ -14,12 +17,13 @@ import { DocumentView } from '../nodes/DocumentView'; import { FocusViewOptions } from '../nodes/FieldView'; import './CollectionCarousel3DView.scss'; import { CollectionSubView } from './CollectionSubView'; + const { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } = require('../global/globalCssVariables.module.scss'); @observer export class CollectionCarousel3DView extends CollectionSubView() { @computed get scrollSpeed() { - return this.layoutDoc._autoScrollSpeed ? NumCast(this.layoutDoc._autoScrollSpeed) : 1000; //default scroll speed + return this.layoutDoc._autoScrollSpeed ? NumCast(this.layoutDoc._autoScrollSpeed) : 1000; // default scroll speed } constructor(props: any) { super(props); @@ -48,16 +52,16 @@ export class CollectionCarousel3DView extends CollectionSubView() { panelHeight = () => this._props.PanelHeight() * Number(CAROUSEL3D_SIDE_SCALE); onChildDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick); isContentActive = () => this._props.isSelected() || this._props.isContentActive() || this._props.isAnyChildContentActive(); - isChildContentActive = () => (this.isContentActive() ? true : false); + isChildContentActive = () => !!this.isContentActive(); childScreenToLocal = () => this._props // document's left is the panel shifted by the doc's index * panelWidth/#docs. But it scales by centerScale around its center, so it's left moves left by the distance of the left from the center (panelwidth/2) * the scale delta (centerScale-1) .ScreenToLocalTransform() // the top behaves the same way ecept it's shifted by the 'top' amount specified for the panel in css and then by the scale factor. .translate(-this.panelWidth() + ((this.centerScale - 1) * this.panelWidth()) / 2, -((Number(CAROUSEL3D_TOP) / 100) * this._props.PanelHeight()) + ((this.centerScale - 1) * this.panelHeight()) / 2) .scale(1 / this.centerScale); - focus = (anchor: Doc, options: FocusViewOptions) => { + focus = (anchor: Doc, options: FocusViewOptions): Opt => { const docs = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]); - if (anchor.type !== DocumentType.CONFIG && !docs.includes(anchor)) return; + if (anchor.type !== DocumentType.CONFIG && !docs.includes(anchor)) return undefined; options.didMove = true; const target = DocCast(anchor.annotationOn) ?? anchor; const index = docs.indexOf(target); @@ -66,37 +70,34 @@ export class CollectionCarousel3DView extends CollectionSubView() { }; @computed get content() { const currentIndex = NumCast(this.layoutDoc._carousel_index); - const displayDoc = (childPair: { layout: Doc; data: Doc }) => { - return ( - - ); - }; - - return this.carouselItems.map((childPair, index) => { - return ( -
- {displayDoc(childPair)} -
- ); - }); + const displayDoc = (childPair: { layout: Doc; data: Doc }) => ( + + ); + + return this.carouselItems.map((childPair, index) => ( +
+ {displayDoc(childPair)} +
+ )); } changeSlide = (direction: number) => { @@ -124,21 +125,21 @@ export class CollectionCarousel3DView extends CollectionSubView() { }; toggleAutoScroll = (direction: number) => { - this.layoutDoc.autoScrollOn = this.layoutDoc.autoScrollOn ? false : true; + this.layoutDoc.autoScrollOn = !this.layoutDoc.autoScrollOn; this.layoutDoc.autoScrollOn ? this.startAutoScroll(direction) : this.stopAutoScroll(); }; fadeScrollButton = () => { window.setTimeout(() => { - !this.layoutDoc.autoScrollOn && (this.layoutDoc.showScrollButton = 'none'); //fade away after 1.5s if it's not clicked. + !this.layoutDoc.autoScrollOn && (this.layoutDoc.showScrollButton = 'none'); // fade away after 1.5s if it's not clicked. }, 1500); }; @computed get buttons() { return (
-
this.onArrowClick(-1)} /> -
this.onArrowClick(1)} /> +
this.onArrowClick(-1)} /> +
this.onArrowClick(1)} /> {/* {this.autoScrollButton} */}
); @@ -149,17 +150,25 @@ export class CollectionCarousel3DView extends CollectionSubView() { return ( <>
this.toggleAutoScroll(-1)}> - {this.layoutDoc.autoScrollOn ? : } + {this.layoutDoc.autoScrollOn ? : }
this.toggleAutoScroll(1)}> - {this.layoutDoc.autoScrollOn ? : } + {this.layoutDoc.autoScrollOn ? : }
); } @computed get dots() { - return this.carouselItems.map((_child, index) =>
(this.layoutDoc._carousel_index = index)} />); + return this.carouselItems.map((_child, index) => ( +
{ + this.layoutDoc._carousel_index = index; + }} + /> + )); } @computed get translateX() { const index = NumCast(this.layoutDoc._carousel_index); diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index 9c370bfbb..6dda6e545 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -2,9 +2,11 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { computed, makeObservable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { StopEvent, emptyFunction, returnFalse, returnOne, returnZero } from '../../../Utils'; +import { emptyFunction } from '../../../Utils'; +import { StopEvent, returnFalse, returnOne, returnZero } from '../../../ClientUtils'; import { Doc, Opt } from '../../../fields/Doc'; import { DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; +import { DocumentType } from '../../documents/DocumentTypes'; import { DragManager } from '../../util/DragManager'; import { StyleProp } from '../StyleProvider'; import { DocumentView } from '../nodes/DocumentView'; @@ -12,7 +14,6 @@ import { FieldViewProps } from '../nodes/FieldView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import './CollectionCarouselView.scss'; import { CollectionSubView } from './CollectionSubView'; -import { DocumentType } from '../../documents/DocumentTypes'; @observer export class CollectionCarouselView extends CollectionSubView() { diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index b2897a9b7..9a1781b93 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -2,25 +2,26 @@ import { action, IReactionDisposer, makeObservable, observable, reaction } from import { observer } from 'mobx-react'; import * as React from 'react'; import * as ReactDOM from 'react-dom/client'; -import * as GoldenLayout from '../../../client/goldenLayout'; +import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, DivHeight, DivWidth, incrementTitleCopy } from '../../../ClientUtils'; import { Doc, DocListCast, Field, Opt } from '../../../fields/Doc'; import { AclAdmin, AclEdit, DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; import { List } from '../../../fields/List'; -import { FieldValue, ImageCast, NumCast, StrCast } from '../../../fields/Types'; +import { ImageCast, NumCast, StrCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; import { GetEffectiveAcl, inheritParentAcls, SetPropSetterCb } from '../../../fields/util'; -import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, DivHeight, DivWidth, emptyFunction, incrementTitleCopy } from '../../../Utils'; +import { emptyFunction } from '../../../Utils'; import { DocServer } from '../../DocServer'; import { Docs } from '../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; +import * as GoldenLayout from '../../goldenLayout'; import { DocumentManager } from '../../util/DocumentManager'; import { DragManager } from '../../util/DragManager'; import { InteractionUtils } from '../../util/InteractionUtils'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SelectionManager } from '../../util/SelectionManager'; -import { SettingsManager } from '../../util/SettingsManager'; +import { SnappingManager } from '../../util/SnappingManager'; import { undoable, undoBatch, UndoManager } from '../../util/UndoManager'; import { DashboardView } from '../DashboardView'; import { LightboxView } from '../LightboxView'; @@ -32,11 +33,12 @@ import './CollectionDockingView.scss'; import { CollectionFreeFormView } from './collectionFreeForm'; import { CollectionSubView } from './CollectionSubView'; import { TabDocView } from './TabDocView'; -import { ComputedField } from '../../../fields/ScriptField'; + const _global = (window /* browser */ || global) /* node */ as any; @observer export class CollectionDockingView extends CollectionSubView() { + // eslint-disable-next-line no-use-before-define @observable public static Instance: CollectionDockingView | undefined = undefined; public static makeDocumentConfig(document: Doc, panelName?: string, width?: number, keyValue?: boolean) { return { @@ -68,7 +70,7 @@ export class CollectionDockingView extends CollectionSubView() { super(props); makeObservable(this); if (this._props.renderDepth < 0) CollectionDockingView.Instance = this; - //Why is this here? + // Why is this here? (window as any).React = React; (window as any).ReactDOM = ReactDOM; DragManager.StartWindowDrag = this.StartOtherDrag; @@ -201,6 +203,7 @@ export class CollectionDockingView extends CollectionSubView() { } else if (instance._goldenLayout.root.contentItems[0].isRow) { // if row switch (pullSide) { + // eslint-disable-next-line default-case-last default: case OpenWhereMod.none: case OpenWhereMod.right: @@ -210,7 +213,7 @@ export class CollectionDockingView extends CollectionSubView() { glayRoot.contentItems[0].addChild(newContentItem(), 0); break; case OpenWhereMod.top: - case OpenWhereMod.bottom: + case OpenWhereMod.bottom: { // if not going in a row layout, must add already existing content into column const rowlayout = glayRoot.contentItems[0]; const newColumn = rowlayout.layoutManager.createContentItem({ type: 'column' }, instance._goldenLayout); @@ -229,6 +232,7 @@ export class CollectionDockingView extends CollectionSubView() { rowlayout.config.height = 50; newItem.config.height = 50; + } } } else { // if (instance._goldenLayout.root.contentItems[0].isColumn) { // if column @@ -241,7 +245,7 @@ export class CollectionDockingView extends CollectionSubView() { break; case 'left': case 'right': - default: + default: { // if not going in a row layout, must add already existing content into column const collayout = glayRoot.contentItems[0]; const newRow = collayout.layoutManager.createContentItem({ type: 'row' }, instance._goldenLayout); @@ -260,6 +264,7 @@ export class CollectionDockingView extends CollectionSubView() { collayout.config.width = 50; newItem.config.width = 50; + } } } instance._ignoreStateChange = JSON.stringify(instance._goldenLayout.toConfig()); @@ -278,10 +283,10 @@ export class CollectionDockingView extends CollectionSubView() { } setupGoldenLayout = async () => { if (this._unmounting) return; - //const config = StrCast(this.Document.dockingConfig, JSON.stringify(DashboardView.resetDashboard(this.Document))); + // 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 matches = config.match(/"documentId":"[a-z0-9-]+"/g); const docids = matches?.map(m => m.replace('"documentId":"', '').replace('"', '')) ?? []; await Promise.all(docids.map(id => DocServer.GetRefField(id))); @@ -289,12 +294,13 @@ export class CollectionDockingView extends CollectionSubView() { if (this._goldenLayout) { if (config === JSON.stringify(this._goldenLayout.toConfig())) { return; - } else { - try { - this._goldenLayout.unbind('tabCreated', this.tabCreated); - this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed); - this._goldenLayout.unbind('stackCreated', this.stackCreated); - } catch (e) {} + } + try { + this._goldenLayout.unbind('tabCreated', this.tabCreated); + this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed); + this._goldenLayout.unbind('stackCreated', this.stackCreated); + } catch (e) { + /* empty */ } this.tabMap.clear(); this._goldenLayout.destroy(); @@ -323,7 +329,7 @@ export class CollectionDockingView extends CollectionSubView() { */ titleChanged = (target: any, value: any) => { const title = Field.toString(value); - if (title.startsWith('@') && !title.substring(1).match(/[\(\)\[\]@]/) && title.length > 1) { + if (title.startsWith('@') && !title.substring(1).match(/[()[\]@]/) && title.length > 1) { const embedding = DocListCast(target.proto_embeddings).lastElement(); embedding && Doc.AddToMyPublished(embedding); } else if (!title.startsWith('@')) { @@ -337,7 +343,7 @@ export class CollectionDockingView extends CollectionSubView() { if (this._containerRef.current) { this._lightboxReactionDisposer = reaction( () => LightboxView.LightboxDoc, - doc => setTimeout(() => !doc && this.onResize(undefined)) + doc => setTimeout(() => !doc && this.onResize()) ); new _global.ResizeObserver(this.onResize).observe(this._containerRef.current); this._reactionDisposer = reaction( @@ -356,12 +362,12 @@ export class CollectionDockingView extends CollectionSubView() { { fireImmediately: true } ); reaction( - () => [SettingsManager.userBackgroundColor, SettingsManager.userBackgroundColor], + () => [SnappingManager.userBackgroundColor, SnappingManager.userBackgroundColor], () => { clearStyleSheetRules(CollectionDockingView._highlightStyleSheet); - addStyleSheetRule(CollectionDockingView._highlightStyleSheet, 'lm_controls', { background: `${SettingsManager.userBackgroundColor} !important` }); - addStyleSheetRule(CollectionDockingView._highlightStyleSheet, 'lm_controls', { color: `${SettingsManager.userColor} !important` }); - addStyleSheetRule(SettingsManager._settingsStyle, 'lm_header', { background: `${SettingsManager.userBackgroundColor} !important` }); + addStyleSheetRule(CollectionDockingView._highlightStyleSheet, 'lm_controls', { background: `${SnappingManager.userBackgroundColor} !important` }); + addStyleSheetRule(CollectionDockingView._highlightStyleSheet, 'lm_controls', { color: `${SnappingManager.userColor} !important` }); + addStyleSheetRule(SnappingManager.SettingsStyle, 'lm_header', { background: `${SnappingManager.userBackgroundColor} !important` }); }, { fireImmediately: true } ); @@ -374,7 +380,9 @@ export class CollectionDockingView extends CollectionSubView() { try { this._goldenLayout.unbind('stackCreated', this.stackCreated); this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed); - } catch (e) {} + } catch (e) { + /* empty */ + } this._goldenLayout?.destroy(); window.removeEventListener('resize', this.onResize); window.removeEventListener('mouseup', this.onPointerUp); @@ -384,7 +392,7 @@ export class CollectionDockingView extends CollectionSubView() { }; @action - onResize = (event: any) => { + onResize = () => { const cur = this._containerRef.current; // bcz: since GoldenLayout isn't a React component itself, we need to notify it to resize when its document container's size has changed !LightboxView.LightboxDoc && cur && this._goldenLayout?.updateSize(cur.getBoundingClientRect().width, cur.getBoundingClientRect().height); @@ -392,7 +400,7 @@ export class CollectionDockingView extends CollectionSubView() { endUndoBatch = () => { const json = JSON.stringify(this._goldenLayout.toConfig()); - const matches = json.match(/\"documentId\":\"[a-z0-9-]+\"/g); + const matches = json.match(/"documentId":"[a-z0-9-]+"/g); const docids = matches?.map(m => m.replace('"documentId":"', '').replace('"', '')); const docs = !docids ? [] @@ -415,7 +423,7 @@ export class CollectionDockingView extends CollectionSubView() { }; @action - onPointerUp = (e: MouseEvent): void => { + onPointerUp = (): void => { window.removeEventListener('pointerup', this.onPointerUp); DragManager.CompleteWindowDrag = undefined; setTimeout(this.endUndoBatch, 100); @@ -453,24 +461,27 @@ export class CollectionDockingView extends CollectionSubView() { if (content) { const _width = DivWidth(content); const _height = DivHeight(content); - return CollectionFreeFormView.UpdateIcon(this.layoutDoc[Id] + '-icon' + new Date().getTime(), content, _width, _height, _width, _height, 0, 1, true, this.layoutDoc[Id] + '-icon', (iconFile, _nativeWidth, _nativeHeight) => { + return CollectionFreeFormView.UpdateIcon(this.layoutDoc[Id] + '-icon' + new Date().getTime(), content, _width, _height, _width, _height, 0, 1, true, this.layoutDoc[Id] + '-icon', iconFile => { const proto = this.dataDoc; // Cast(img.proto, Doc, null)!; - proto['thumb_nativeWidth'] = _width; - proto['thumb_nativeHeight'] = _height; + proto.thumb_nativeWidth = _width; + proto.thumb_nativeHeight = _height; proto.thumb = new ImageField(iconFile); }); } + return undefined; } public static async TakeSnapshot(doc: Doc | undefined, clone = false) { if (!doc) return undefined; let json = StrCast(doc.dockingConfig); if (clone) { const cloned = await Doc.MakeClone(doc); - Array.from(cloned.map.entries()).map(entry => (json = json.replace(entry[0], entry[1][Id]))); + Array.from(cloned.map.entries()).forEach(entry => { + json = json.replace(entry[0], entry[1][Id]); + }); cloned.clone[DocData].dockingConfig = json; return DashboardView.openDashboard(cloned.clone); } - const matches = json.match(/\"documentId\":\"[a-z0-9-]+\"/g); + const matches = json.match(/"documentId":"[a-z0-9-]+"/g); const origtabids = matches?.map(m => m.replace('"documentId":"', '').replace('"', '')) || []; const origtabs = origtabids .map(id => DocServer.GetCachedRefField(id)) @@ -508,19 +519,20 @@ export class CollectionDockingView extends CollectionSubView() { tabDestroyed = (tab: any) => { this._flush = this._flush ?? UndoManager.StartBatch('tab movement'); - if (tab.DashDoc && ![DocumentType.PRES].includes(tab.DashDoc?.type) && !tab.contentItem.config.props.keyValue) { - Doc.AddDocToList(Doc.MyHeaderBar, 'data', tab.DashDoc, undefined, undefined, true); + const dashDoc = tab.DashDoc; + if (dashDoc && ![DocumentType.PRES].includes(dashDoc.type) && !tab.contentItem.config.props.keyValue) { + Doc.AddDocToList(Doc.MyHeaderBar, 'data', dashDoc, undefined, undefined, true); // 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.RemoveEmbedding(tab.DashDoc, tab.DashDoc); + if (dashDoc.embedContainer === this.Document) dashDoc.embedContainer = undefined; + if (!dashDoc.embedContainer) { + Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', dashDoc, undefined, true, true); + Doc.RemoveEmbedding(dashDoc, dashDoc); } } if (CollectionDockingView.Instance) { const dview = CollectionDockingView.Instance.Document; - const fieldKey = CollectionDockingView.Instance.props.fieldKey; - Doc.RemoveDocFromList(dview, fieldKey, tab.DashDoc); + const { fieldKey } = CollectionDockingView.Instance.props; + Doc.RemoveDocFromList(dview, fieldKey, dashDoc); this.tabMap.delete(tab); tab._disposers && Object.values(tab._disposers).forEach((disposer: any) => disposer?.()); this.stateChanged(); @@ -531,8 +543,8 @@ export class CollectionDockingView extends CollectionSubView() { tab.contentItem.element[0]?.firstChild?.firstChild?.InitTab?.(tab); // have to explicitly initialize tabs that reuse contents from previous tabs (ie, when dragging a tab around a new tab is created for the old content) }; - stackCreated = (stack: any) => { - stack = stack.header ? stack : stack.origin; + stackCreated = (stackIn: any) => { + const stack = stackIn.header ? stackIn : stackIn.origin; stack.header?.element.on('mousedown', (e: any) => { const dashboard = Doc.ActiveDashboard; if (dashboard && e.target === stack.header?.element[0] && e.button === 2) { @@ -550,32 +562,29 @@ export class CollectionDockingView extends CollectionSubView() { } }); - let addNewDoc = undoable( - action(() => { - const dashboard = Doc.ActiveDashboard; - if (dashboard) { - dashboard.pane_count = NumCast(dashboard.pane_count) + 1; - const docToAdd = Docs.Create.FreeformDocument([], { - _width: this._props.PanelWidth(), - _height: this._props.PanelHeight(), - _layout_fitWidth: true, - _freeform_backgroundGrid: true, - title: `Untitled Tab ${NumCast(dashboard.pane_count)}`, - }); - Doc.AddDocToList(Doc.MyHeaderBar, 'data', docToAdd, undefined, undefined, true); - inheritParentAcls(this.dataDoc, docToAdd, false); - CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack); - } - }), - 'add new tab' - ); + const addNewDoc = undoable(() => { + const dashboard = Doc.ActiveDashboard; + if (dashboard) { + dashboard.pane_count = NumCast(dashboard.pane_count) + 1; + const docToAdd = Docs.Create.FreeformDocument([], { + _width: this._props.PanelWidth(), + _height: this._props.PanelHeight(), + _layout_fitWidth: true, + _freeform_backgroundGrid: true, + title: `Untitled Tab ${NumCast(dashboard.pane_count)}`, + }); + Doc.AddDocToList(Doc.MyHeaderBar, 'data', docToAdd, undefined, undefined, true); + inheritParentAcls(this.dataDoc, docToAdd, false); + CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack); + } + }, 'add new tab'); stack.header?.controlsContainer - .find('.lm_close') //get the close icon - .off('click') //unbind the current click handler + .find('.lm_close') // get the close icon + .off('click') // unbind the current click handler .click( action(() => { - //if (confirm('really close this?')) { + // if (confirm('really close this?')) { if ((!stack.parent.isRoot && !stack.parent.parent.isRoot) || stack.parent.contentItems.length > 1) { const batch = UndoManager.StartBatch('close stack'); stack.remove(); @@ -595,11 +604,11 @@ export class CollectionDockingView extends CollectionSubView() { } }); stack.header?.controlsContainer - .find('.lm_maximise') //get the close icon + .find('.lm_maximise') // get the close icon .click(() => setTimeout(this.stateChanged)); stack.header?.controlsContainer - .find('.lm_popout') //get the popout icon - .off('click') //unbind the current click handler + .find('.lm_popout') // get the popout icon + .off('click') // unbind the current click handler .click(addNewDoc); }; @@ -609,13 +618,14 @@ export class CollectionDockingView extends CollectionSubView() {
{href ? ( thumbnail of nested dashboard': return OverlayView.Instance.addWindow(, { x: 300, y: 100, width: 200, height: 200, title: 'Scripting REPL' }); case "": return OverlayView.Instance.addWindow(, { x: 300, y: 100, width: 200, height: 200, title: 'Undo stack' }); + default: } Doc.AddToMyOverlay(doc); + return true; } }, 'opens up document in location specified', '(doc: any)' ); ScriptingGlobals.add( + // eslint-disable-next-line prefer-arrow-callback function openRepl() { return 'openRepl'; }, 'opens up document in screen overlay layer', '(doc: any)' ); +// eslint-disable-next-line prefer-arrow-callback +ScriptingGlobals.add(async function snapshotDashboard() { + const batch = UndoManager.StartBatch('snapshot'); + await CollectionDockingView.TakeSnapshot(Doc.ActiveDashboard); + batch.end(); +}, 'creates a snapshot copy of a dashboard'); diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx index 7dcfd32bd..2d906dfe7 100644 --- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx +++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx @@ -2,7 +2,8 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { emptyFunction, numberRange, returnEmptyString, returnFalse, setupMoveUpEvents } from '../../../Utils'; +import { returnEmptyString, returnFalse, setupMoveUpEvents } from '../../../ClientUtils'; +import { emptyFunction, numberRange } from '../../../Utils'; import { Doc, DocListCast } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { PastelSchemaPalette, SchemaHeaderField } from '../../../fields/SchemaHeaderField'; diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 4f25f69ef..a23725348 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -4,7 +4,8 @@ import { Toggle, ToggleType, Type } from 'browndash-components'; import { Lambda, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from '../../../Utils'; +import { ClientUtils, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from '../../../ClientUtils'; +import { emptyFunction } from '../../../Utils'; import { Doc, DocListCast, Opt } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { List } from '../../../fields/List'; @@ -12,7 +13,8 @@ import { ObjectField } from '../../../fields/ObjectField'; import { RichTextField } from '../../../fields/RichTextField'; import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../fields/Types'; import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; -import { DragManager, dropActionType } from '../../util/DragManager'; +import { DragManager } from '../../util/DragManager'; +import { dropActionType } from '../../util/DropActionTypes'; import { SelectionManager } from '../../util/SelectionManager'; import { SettingsManager } from '../../util/SettingsManager'; import { Transform } from '../../util/Transform'; @@ -79,7 +81,7 @@ export class CollectionMenu extends AntimodeMenu { buttonBarXf = () => { if (!this._docBtnRef.current) return Transform.Identity(); - const { scale, translateX, translateY } = Utils.GetScreenTransform(this._docBtnRef.current); + const { scale, translateX, translateY } = ClientUtils.GetScreenTransform(this._docBtnRef.current); return new Transform(-translateX, -translateY, 1 / scale); }; @@ -123,7 +125,7 @@ export class CollectionMenu extends AntimodeMenu { const propTitle = SettingsManager.Instance.propertiesWidth > 0 ? 'Close Properties' : 'Open Properties'; const hardCodedButtons = ( -
+
{
); - //dash col linear view buttons + // dash col linear view buttons const contMenuButtons = (
e.stopPropagation(); @observer export class CollectionViewBaseChrome extends React.Component { - //(!)?\(\(\(doc.(\w+) && \(doc.\w+ as \w+\).includes\(\"(\w+)\"\) + // (!)?\(\(\(doc.(\w+) && \(doc.\w+ as \w+\).includes\(\"(\w+)\"\) get document() { return this.props.docView?.Document; @@ -206,14 +208,18 @@ export class CollectionViewBaseChrome extends React.Component source.length && (this.target.childClickedOpenTemplateView = Doc.getDocTemplate(source?.[0]))), + immediate: undoBatch((source: Doc[]) => { + source.length && (this.target.childClickedOpenTemplateView = Doc.getDocTemplate(source?.[0])); + }), initialize: emptyFunction, }; _contentCommand = { params: ['target', 'source'], title: 'set content', script: 'getProto(this.target).data = copyField(this.source);', - immediate: undoBatch((source: Doc[]) => (this.target[DocData].data = new List(source))), + immediate: undoBatch((source: Doc[]) => { + this.target[DocData].data = new List(source); + }), initialize: emptyFunction, }; _onClickCommand = { @@ -224,14 +230,14 @@ export class CollectionViewBaseChrome extends React.Component {}), + immediate: undoBatch(() => {}), initialize: emptyFunction, }; _viewCommand = { params: ['target'], title: 'bookmark view', script: "this.target._freeform_panX = self['target-freeform_panX']; this.target._freeform_panY = this['target-freeform_panY']; this.target._freeform_scale = this['target_freeform_scale']; gotoFrame(this.target, this['target-currentFrame']);", - immediate: undoBatch((source: Doc[]) => { + immediate: undoBatch(() => { this.target._freeform_panX = 0; this.target._freeform_panY = 0; this.target._freeform_scale = 1; @@ -248,14 +254,18 @@ export class CollectionViewBaseChrome extends React.Component (this.target._freeform_fitContentsToBox = !this.target._freeform_fitContentsToBox)), + immediate: undoBatch(() => { + this.target._freeform_fitContentsToBox = !this.target._freeform_fitContentsToBox; + }), initialize: emptyFunction, }; _fitContentCommand = { params: ['target'], title: 'toggle clusters', script: 'this.target._freeform_useClusters = !this.target._freeform_useClusters;', - immediate: undoBatch((source: Doc[]) => (this.target._freeform_useClusters = !this.target._freeform_useClusters)), + immediate: undoBatch(() => { + this.target._freeform_useClusters = !this.target._freeform_useClusters; + }), initialize: emptyFunction, }; _saveFilterCommand = { @@ -263,7 +273,7 @@ export class CollectionViewBaseChrome extends React.Component { + immediate: undoBatch(() => { this.target._childFilters = undefined; this.target._searchFilterDocs = undefined; }), @@ -332,12 +342,10 @@ export class CollectionViewBaseChrome extends React.Component { const target = this.document !== Doc.MyLeftSidebarPanel ? this.document : DocCast(this.document.proto); - //@ts-ignore target._type_collection = e.target.selectedOptions[0].value; }; commandChanged = (e: React.ChangeEvent) => { - //@ts-ignore runInAction(() => (this._currentKey = e.target.selectedOptions[0].value)); }; diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx index d8a0aebb1..4ceeb631d 100644 --- a/src/client/views/collections/CollectionNoteTakingView.tsx +++ b/src/client/views/collections/CollectionNoteTakingView.tsx @@ -1,7 +1,8 @@ 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 { ClientUtils, DivHeight, lightOrDark, returnZero, smoothScroll } from '../../../ClientUtils'; +import { Doc, Field, FieldType, Opt } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { Copy, Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; @@ -9,14 +10,16 @@ import { listSpec } from '../../../fields/Schema'; import { SchemaHeaderField } from '../../../fields/SchemaHeaderField'; import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; -import { DivHeight, emptyFunction, lightOrDark, returnZero, smoothScroll, Utils } from '../../../Utils'; +import { emptyFunction } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; -import { DragManager, dropActionType } from '../../util/DragManager'; +import { DragManager } from '../../util/DragManager'; +import { dropActionType } from '../../util/DropActionTypes'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { undoable, undoBatch } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; +import { FieldsDropdown } from '../FieldsDropdown'; import { Colors } from '../global/globalEnums'; import { LightboxView } from '../LightboxView'; import { DocumentView } from '../nodes/DocumentView'; @@ -27,7 +30,7 @@ import './CollectionNoteTakingView.scss'; import { CollectionNoteTakingViewColumn } from './CollectionNoteTakingViewColumn'; import { CollectionNoteTakingViewDivider } from './CollectionNoteTakingViewDivider'; import { CollectionSubView } from './CollectionSubView'; -import { FieldsDropdown } from '../FieldsDropdown'; + const _global = (window /* browser */ || global) /* node */ as any; /** @@ -300,7 +303,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { // getDocTransform is used to get the coordinates of a document when we go from a view like freeform to columns getDocTransform(doc: Doc, dref?: DocumentView) { const y = this._scroll; // required for document decorations to update when the text box container is scrolled - const { translateX, translateY } = Utils.GetScreenTransform(dref?.ContentDiv || undefined); + const { translateX, translateY } = ClientUtils.GetScreenTransform(dref?.ContentDiv || undefined); // the document view may center its contents and if so, will prepend that onto the screenToLocalTansform. so we have to subtract that off return new Transform(-translateX + (dref?.centeringX || 0), -translateY + (dref?.centeringY || 0), 1).scale(this.ScreenToLocalBoxXf().Scale); } @@ -308,7 +311,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { // how to get the width of a document. Currently returns the width of the column (minus margins) // if a note doc. Otherwise, returns the normal width (for graphs, images, etc...) getDocWidth(d: Doc) { - const heading = !d[this.notetakingCategoryField] ? 'unset' : Field.toString(d[this.notetakingCategoryField] as Field); + const heading = !d[this.notetakingCategoryField] ? 'unset' : Field.toString(d[this.notetakingCategoryField] as FieldType); const existingHeader = this.colHeaderData.find(sh => sh.heading === heading); const existingWidth = this.layoutDoc._notetaking_columns_autoSize ? 1 / (this.colHeaderData.length ?? 1) : existingHeader?.width ? existingHeader.width : 0; const maxWidth = existingWidth > 0 ? existingWidth * this.availableWidth : this.maxColWidth; diff --git a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx index 448b11b05..e00fdb50c 100644 --- a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx +++ b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx @@ -2,7 +2,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { lightOrDark, returnEmptyString } from '../../../Utils'; +import { lightOrDark, returnEmptyString } from '../../../ClientUtils'; import { Doc, DocListCast, Opt } from '../../../fields/Doc'; import { RichTextField } from '../../../fields/RichTextField'; import { listSpec } from '../../../fields/Schema'; diff --git a/src/client/views/collections/CollectionNoteTakingViewDivider.tsx b/src/client/views/collections/CollectionNoteTakingViewDivider.tsx index 50a97b978..67070b24d 100644 --- a/src/client/views/collections/CollectionNoteTakingViewDivider.tsx +++ b/src/client/views/collections/CollectionNoteTakingViewDivider.tsx @@ -1,7 +1,8 @@ import { action, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { emptyFunction, setupMoveUpEvents } from '../../../Utils'; +import { emptyFunction } from '../../../Utils'; +import { setupMoveUpEvents } from '../../../ClientUtils'; import { UndoManager } from '../../util/UndoManager'; import { ObservableReactComponent } from '../ObservableReactComponent'; diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx index 9d68c621b..e39f1c700 100644 --- a/src/client/views/collections/CollectionPileView.tsx +++ b/src/client/views/collections/CollectionPileView.tsx @@ -1,12 +1,13 @@ import { action, computed, IReactionDisposer, makeObservable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; +import { returnFalse, setupMoveUpEvents } from '../../../ClientUtils'; import { Doc, DocListCast } from '../../../fields/Doc'; import { ScriptField } from '../../../fields/ScriptField'; import { NumCast, StrCast } from '../../../fields/Types'; -import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../Utils'; +import { emptyFunction } from '../../../Utils'; import { DocUtils } from '../../documents/Documents'; -import { dropActionType } from '../../util/DragManager'; +import { dropActionType } from '../../util/DropActionTypes'; import { SelectionManager } from '../../util/SelectionManager'; import { undoBatch, UndoManager } from '../../util/UndoManager'; import { OpenWhere } from '../nodes/DocumentView'; diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 656f850b3..3f6638b25 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -2,6 +2,7 @@ import { action, computed, IReactionDisposer, makeObservable, observable, reacti import { observer } from 'mobx-react'; import { computedFn } from 'mobx-utils'; import * as React from 'react'; +import { returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents, smoothScrollHorizontal, StopEvent } from '../../../ClientUtils'; import { Doc, Opt } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; @@ -10,7 +11,7 @@ import { listSpec } from '../../../fields/Schema'; import { ComputedField, ScriptField } from '../../../fields/ScriptField'; import { Cast, NumCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; -import { emptyFunction, formatTime, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents, smoothScrollHorizontal, StopEvent } from '../../../Utils'; +import { emptyFunction, formatTime } from '../../../Utils'; import { Docs } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; import { DocumentManager } from '../../util/DocumentManager'; diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index bf0393883..b79d660c6 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -1,8 +1,10 @@ +/* eslint-disable react/jsx-props-no-spreading */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import * as CSS from 'csstype'; import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; +import { ClientUtils, DivHeight, returnEmptyDoclist, returnNone, returnZero, setupMoveUpEvents, smoothScroll } from '../../../ClientUtils'; import { Doc, Opt } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; @@ -11,10 +13,11 @@ import { listSpec } from '../../../fields/Schema'; import { SchemaHeaderField } from '../../../fields/SchemaHeaderField'; import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; -import { DivHeight, emptyFunction, returnEmptyDoclist, returnNone, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils'; +import { emptyFunction } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { CollectionViewType } from '../../documents/DocumentTypes'; -import { DragManager, dropActionType } from '../../util/DragManager'; +import { DragManager } from '../../util/DragManager'; +import { dropActionType } from '../../util/DropActionTypes'; import { SettingsManager } from '../../util/SettingsManager'; import { Transform } from '../../util/Transform'; import { undoBatch, UndoManager } from '../../util/UndoManager'; @@ -31,6 +34,7 @@ import { CollectionMasonryViewFieldRow } from './CollectionMasonryViewFieldRow'; import './CollectionStackingView.scss'; import { CollectionStackingViewFieldColumn } from './CollectionStackingViewFieldColumn'; import { CollectionSubView } from './CollectionSubView'; + const _global = (window /* browser */ || global) /* node */ as any; export type collectionStackingViewProps = { @@ -125,11 +129,16 @@ export class CollectionStackingView extends CollectionSubView { - //TODO: can somebody explain me to what exactly TraceMobX is? + // TODO: can somebody explain me to what exactly TraceMobX is? TraceMobx(); // appears that we are going to reset the _docXfs. TODO: what is Xfs? this._docXfs.length = 0; - this._renderCount < docs.length && setTimeout(action(() => (this._renderCount = Math.min(docs.length, this._renderCount + 5)))); + this._renderCount < docs.length && + setTimeout( + action(() => { + this._renderCount = Math.min(docs.length, this._renderCount + 5); + }) + ); return docs.map((d, i) => { const height = () => this.getDocHeight(d); const width = () => this.getDocWidth(d); @@ -152,19 +161,21 @@ export class CollectionStackingView extends CollectionSubView(); if (this.colHeaderData === undefined) { - setTimeout(() => (this.dataDoc['_' + this.fieldKey + '_columnHeaders'] = new List()), 0); + setTimeout(() => { + this.dataDoc['_' + this.fieldKey + '_columnHeaders'] = new List(); + }); return new Map(); } const colHeaderData = Array.from(this.colHeaderData); const fields = new Map(colHeaderData.map(sh => [sh, []] as [SchemaHeaderField, []])); let changed = false; - this.filteredChildren.map(d => { + this.filteredChildren.forEach(d => { const sectionValue = (d[this.pivotField] ? d[this.pivotField] : `NO ${this.pivotField.toUpperCase()} VALUE`) as object; // the next five lines ensures that floating point rounding errors don't create more than one section -syip const parsed = parseInt(sectionValue.toString()); @@ -186,7 +197,7 @@ export class CollectionStackingView extends CollectionSubView !fields.get(key)!.length) - .map(header => { + .forEach(header => { fields.delete(header); colHeaderData.splice(colHeaderData.indexOf(header), 1); changed = true; @@ -207,11 +218,13 @@ export class CollectionStackingView extends CollectionSubView this.pivotField, - () => (this.dataDoc['_' + this.fieldKey + '_columnHeaders'] = new List()) + () => { + this.dataDoc['_' + this.fieldKey + '_columnHeaders'] = new List(); + } ); this._disposers.autoHeight = reaction( () => this.layoutDoc._layout_autoHeight, - layout_autoHeight => layout_autoHeight && this._props.setHeight?.(this.headerMargin + (this.isStackingView ? Math.max(...this._refList.map(DivHeight)) : this._refList.reduce((p, r) => p + DivHeight(r), 0))) + layoutAutoHeight => layoutAutoHeight && this._props.setHeight?.(this.headerMargin + (this.isStackingView ? Math.max(...this._refList.map(DivHeight)) : this._refList.reduce((p, r) => p + DivHeight(r), 0))) ); this._disposers.refList = reaction( () => ({ refList: this._refList.slice(), autoHeight: this.layoutDoc._layout_autoHeight && !LightboxView.Contains(this.DocumentView?.()) }), @@ -231,9 +244,7 @@ export class CollectionStackingView extends CollectionSubView this._props.isAnyChildContentActive(); - moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => { - return this._props.removeDocument?.(doc) && addDocument?.(doc) ? true : false; - }; + moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => !!(this._props.removeDocument?.(doc) && addDocument?.(doc)); onChildClickHandler = () => this._props.childClickScript || ScriptCast(this.Document.onChildClick); @computed get onChildDoubleClickHandler() { @@ -250,10 +261,10 @@ export class CollectionStackingView extends CollectionSubView node.id === doc[Id]); if (found) { - const top = found.getBoundingClientRect().top; + const { top } = found.getBoundingClientRect(); const localTop = this.ScreenToLocalBoxXf().transformPoint(0, top); if (Math.floor(localTop[1]) !== 0) { - let focusSpeed = options.zoomTime ?? 500; + const focusSpeed = options.zoomTime ?? 500; smoothScroll(focusSpeed, this._mainCont!, localTop[1] + this._mainCont!.scrollTop, options.easeFunc); return focusSpeed; } @@ -276,18 +287,18 @@ export class CollectionStackingView extends CollectionSubView { if (['Enter'].includes(e.key) && e.ctrlKey) { e.stopPropagation?.(); - const below = !e.altKey && e.key !== 'Tab'; - const layout_fieldKey = StrCast(fieldProps.fieldKey); + const layoutFieldKey = 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' && fieldProps.Document[layout_fieldKey] instanceof Doc) { - newDoc[layout_fieldKey] = fieldProps.Document[layout_fieldKey]; + if (layoutFieldKey !== 'layout' && fieldProps.Document[layoutFieldKey] instanceof Doc) { + newDoc[layoutFieldKey] = fieldProps.Document[layoutFieldKey]; } newDoc[DocData].text = undefined; FormattedTextBox.SetSelectOnLoad(newDoc); return this.addDocument?.(newDoc); } + return false; }; isContentActive = () => (this._props.isContentActive() ? true : this._props.isSelected() === false || this._props.isContentActive() === false ? false : undefined); @@ -363,7 +374,7 @@ export class CollectionStackingView extends CollectionSubView { - runInAction(() => (this._cursor = 'grabbing')); + runInAction(() => { + this._cursor = 'grabbing'; + }); const batch = UndoManager.StartBatch('stacking width'); setupMoveUpEvents( this, @@ -425,7 +438,7 @@ export class CollectionStackingView extends CollectionSubView - +
); } @@ -439,7 +452,7 @@ export class CollectionStackingView extends CollectionSubView { + this._docXfs.forEach((cd, i) => { const pos = cd .stackedDocTransform() .inverse() @@ -496,7 +509,7 @@ export class CollectionStackingView extends CollectionSubView => { const where = [e.clientX, e.clientY]; let targInd = -1; - this._docXfs.map((cd, i) => { + this._docXfs.forEach((cd, i) => { const pos = cd .stackedDocTransform() .inverse() @@ -521,10 +534,11 @@ export class CollectionStackingView extends CollectionSubView { const key = this.pivotField; - let type: 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function' | undefined = undefined; + let type: 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function' | undefined; if (this.pivotField) { const types = docList.length ? docList.map(d => typeof d[key]) : this.filteredChildren.map(d => typeof d[key]); if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) { + // eslint-disable-next-line prefer-destructuring type = types[0]; } } @@ -557,9 +571,10 @@ export class CollectionStackingView extends CollectionSubView { const key = this.pivotField; - let type: 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function' | undefined = undefined; + let type: 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function' | undefined; const types = docList.length ? docList.map(d => typeof d[key]) : this.filteredChildren.map(d => typeof d[key]); if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) { + // eslint-disable-next-line prefer-destructuring type = types[0]; } const rows = () => (!this.isStackingView ? 1 : Math.max(1, Math.min(docList.length, Math.floor((this._props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap))))); @@ -609,9 +624,9 @@ export class CollectionStackingView extends CollectionSubView (this.layoutDoc._columnsFill = !this.layoutDoc._columnsFill), icon: 'plus' }); - optionItems.push({ description: `${this.layoutDoc._layout_autoHeight ? 'Variable Height' : 'Auto Height'}`, event: () => (this.layoutDoc._layout_autoHeight = !this.layoutDoc._layout_autoHeight), icon: 'plus' }); - optionItems.push({ description: 'Clear All', event: () => (this.dataDoc[this.fieldKey ?? 'data'] = new List([])), icon: 'times' }); + optionItems.push({ description: `${this.layoutDoc._columnsFill ? 'Variable Size' : 'Autosize'} Column`, event: () => { this.layoutDoc._columnsFill = !this.layoutDoc._columnsFill; }, icon: 'plus' }); // prettier-ignore + optionItems.push({ description: `${this.layoutDoc._layout_autoHeight ? 'Variable Height' : 'Auto Height'}`, event: () => { this.layoutDoc._layout_autoHeight = !this.layoutDoc._layout_autoHeight; }, icon: 'plus' }); // prettier-ignore + optionItems.push({ description: 'Clear All', event: () => { this.dataDoc[this.fieldKey ?? 'data'] = new List([]); } , icon: 'times' }); // prettier-ignore !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'compass' }); } }; @@ -700,7 +715,7 @@ export class CollectionStackingView extends CollectionSubView { this._masonryGridRef = ele; - this.createDashEventsTarget(ele); //so the whole grid is the drop target? + this.createDashEventsTarget(ele); // so the whole grid is the drop target? this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel); this._oldWheel = ele; // prevent wheel events from passively propagating up through containers and prevents containers from preventDefault which would block scrolling @@ -711,7 +726,9 @@ export class CollectionStackingView extends CollectionSubView (this._scroll = e.currentTarget.scrollTop))} + onScroll={action(e => { + this._scroll = e.currentTarget.scrollTop; + })} onDrop={this.onExternalDrop.bind(this)} onContextMenu={this.onContextMenu} onWheel={e => this.isContentActive() && e.stopPropagation()}> diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index c5292f880..35f330b44 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -1,7 +1,11 @@ +/* eslint-disable jsx-a11y/control-has-associated-label */ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; +import { DivHeight, DivWidth, returnEmptyString, setupMoveUpEvents } from '../../../ClientUtils'; import { Doc, DocListCast, Opt } from '../../../fields/Doc'; import { RichTextField } from '../../../fields/RichTextField'; import { PastelSchemaPalette, SchemaHeaderField } from '../../../fields/SchemaHeaderField'; @@ -9,10 +13,11 @@ import { ScriptField } from '../../../fields/ScriptField'; import { BoolCast, NumCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; -import { DivHeight, DivWidth, emptyFunction, returnEmptyString, setupMoveUpEvents } from '../../../Utils'; +import { emptyFunction } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; -import { DragManager, dropActionType } from '../../util/DragManager'; +import { DragManager } from '../../util/DragManager'; +import { dropActionType } from '../../util/DropActionTypes'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { undoBatch } from '../../util/UndoManager'; @@ -95,7 +100,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< this._ele && this.props.refList.push(this._ele); this._disposers.collapser = reaction( () => this._props.headingObject?.collapsed, - collapsed => (this.collapsed = collapsed !== undefined ? BoolCast(collapsed) : false), + collapsed => { this.collapsed = collapsed !== undefined ? BoolCast(collapsed) : false; }, // prettier-ignore { fireImmediately: true } ); } @@ -105,7 +110,6 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< this._ele = null; } - //TODO: what is scripting? I found it in SetInPlace def but don't know what that is @undoBatch columnDrop = action((e: Event, de: DragManager.DropEvent) => { const drop = { docs: de.complete.docDragData?.droppedDocuments, val: this.getValue(this._heading) }; @@ -121,13 +125,13 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< }; @action - headingChanged = (value: string, shiftDown?: boolean) => { + headingChanged = (value: string /* , shiftDown?: boolean */) => { const castedValue = this.getValue(value); if (castedValue) { if (this._props.colHeaderData?.map(i => i.heading).indexOf(castedValue.toString()) !== -1) { return false; } - this._props.pivotField && this._props.docList.forEach(d => (d[this._props.pivotField] = castedValue)); + this._props.pivotField && this._props.docList.forEach(d => { d[this._props.pivotField] = castedValue; }) // prettier-ignore if (this._props.headingObject) { this._props.headingObject.setHeading(castedValue.toString()); this._heading = this._props.headingObject.heading; @@ -143,9 +147,9 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< this._color = color; }; - @action pointerEntered = () => SnappingManager.IsDragging && (this._background = '#b4b4b4'); - @action pointerLeave = () => (this._background = 'inherit'); - @undoBatch typedNote = (char: string) => this.addNewTextDoc('-typed text-', false, true); + @action pointerEntered = () => { SnappingManager.IsDragging && (this._background = '#b4b4b4'); } // prettier-ignore + @action pointerLeave = () => { this._background = 'inherit'}; // prettier-ignore + @undoBatch typedNote = () => this.addNewTextDoc('-typed text-', false, true); @action addNewTextDoc = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => { @@ -163,7 +167,10 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< @action deleteColumn = () => { - this._props.pivotField && this._props.docList.forEach(d => (d[this._props.pivotField] = undefined)); + this._props.pivotField && + this._props.docList.forEach(d => { + d[this._props.pivotField] = undefined; + }); if (this._props.colHeaderData && this._props.headingObject) { const index = this._props.colHeaderData.indexOf(this._props.headingObject); this._props.colHeaderData.splice(index, 1); @@ -178,8 +185,8 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< headerDown = (e: React.PointerEvent) => setupMoveUpEvents(this, e, this.startDrag, emptyFunction, emptyFunction); - //TODO: I think this is where I'm supposed to edit stuff - startDrag = (e: PointerEvent, down: number[], delta: number[]) => { + // TODO: I think this is where I'm supposed to edit stuff + startDrag = (e: PointerEvent) => { // is MakeEmbedding a way to make a copy of a doc without rendering it? const embedding = Doc.MakeEmbedding(this._props.Document); embedding._width = this._props.columnWidth / (this._props.colHeaderData?.length || 1); @@ -210,23 +217,23 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< ); }; - renderMenu = () => { - return ( -
-
-
{})}> - Add options here -
+ renderMenu = () => ( +
+
+
{})}> + Add options here
- ); - }; +
+ ); @observable private collapsed: boolean = false; - private toggleVisibility = action(() => (this.collapsed = !this.collapsed)); + private toggleVisibility = action(() => { + this.collapsed = !this.collapsed; + }); - menuCallback = (x: number, y: number) => { + menuCallback = () => { ContextMenu.Instance.clearItems(); const layoutItems: ContextMenuProps[] = []; const docItems: ContextMenuProps[] = []; @@ -257,6 +264,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< } return this._props.addDocument?.(created); } + return false; }, icon: 'compress-arrows-alt', }) @@ -276,6 +284,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< } return this._props.addDocument?.(created) || false; } + return false; }, icon: 'compress-arrows-alt', }) @@ -307,7 +316,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< const columnYMargin = this._props.headingObject ? 0 : this._props.yMargin; const uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx); const noValueHeader = `NO ${key.toUpperCase()} VALUE`; - const evContents = heading ? heading : this._props?.type === 'number' ? '0' : noValueHeader; + const evContents = heading || (this._props?.type === 'number' ? '0' : noValueHeader); const headingView = this._props.headingObject ? (
- evContents} SetValue={this.headingChanged} contents={evContents} oneLine={true} /> + evContents} SetValue={this.headingChanged} contents={evContents} oneLine />
- {this._paletteOn ? this.renderColorPicker() : null}
- {/* {evContents === noValueHeader ? null : ( @@ -352,7 +366,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
) : null; const templatecols = `${this._props.columnWidth / this._props.numGroupColumns}px `; - const type = this._props.Document.type; + const { type } = this._props.Document; return ( <> {this._props.Document._columnsHideIfEmpty ? null : headingView} @@ -364,7 +378,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< style={{ padding: `${columnYMargin}px ${0}px ${this._props.yMargin}px ${0}px`, margin: 'auto', - width: 'max-content', //singleColumn ? undefined : `${cols * (style.columnWidth + style.gridGap) + 2 * style.xMargin - style.gridGap}px`, + width: 'max-content', // singleColumn ? undefined : `${cols * (style.columnWidth + style.gridGap) + 2 * style.xMargin - style.gridGap}px`, height: 'max-content', position: 'relative', gridGap: this._props.gridGap, @@ -376,10 +390,10 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< {!this._props.chromeHidden && type !== DocumentType.PRES ? ( // TODO: this is the "new" button: see what you can work with here // change cursor to pointer for this, and update dragging cursor - //TODO: there is a bug that occurs when adding a freeform document and trying to move it around - //TODO: would be great if there was additional space beyond the frame, so that you can actually see your + // TODO: there is a bug that occurs when adding a freeform document and trying to move it around + // TODO: would be great if there was additional space beyond the frame, so that you can actually see your // bottom note - //TODO: ok, so we are using a single column, and this is it! + // TODO: ok, so we are using a single column, and this is it!
e.stopPropagation()} @@ -390,7 +404,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< SetValue={this.addNewTextDoc} textCallback={this.typedNote} placeholder={"Type ':' for commands"} - contents={} + contents={} menuCallback={this.menuCallback} />
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 32198e3a2..cd401058f 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -1,10 +1,10 @@ import { action, computed, makeObservable, observable } from 'mobx'; import * as React from 'react'; import * as rp from 'request-promise'; -import { Utils, returnFalse } from '../../../Utils'; +import { ClientUtils, returnFalse } from '../../../ClientUtils'; import CursorField from '../../../fields/CursorField'; -import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../fields/Doc'; -import { AclPrivate, DocData } from '../../../fields/DocSymbols'; +import { Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../fields/Doc'; +import { AclPrivate } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { listSpec } from '../../../fields/Schema'; @@ -15,16 +15,47 @@ import { GestureUtils } from '../../../pen-gestures/GestureUtils'; import { DocServer } from '../../DocServer'; import { Networking } from '../../Network'; import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; -import { ViewBoxBaseComponent } from '../DocComponent'; import { DocUtils, Docs, DocumentOptions } from '../../documents/Documents'; -import { DragManager, dropActionType } from '../../util/DragManager'; +import { DragManager } from '../../util/DragManager'; +import { dropActionType } from '../../util/DropActionTypes'; 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 } from '../DocComponent'; +import { FieldViewProps } from '../nodes/FieldView'; import { LoadingBox } from '../nodes/LoadingBox'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; -import { CollectionView, CollectionViewProps } from './CollectionView'; + +export interface CollectionViewProps extends React.PropsWithChildren { + 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; + setPreviewCursor?: (func: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt) => void) => void; + ignoreUnrendered?: boolean; + + // property overrides for child documents + childDocuments?: Doc[]; // used to override the documents shown by the sub collection to an explicit list (see LinkBox) + childDocumentsActive?: () => boolean | undefined; // whether child documents can be dragged if collection can be dragged (eg., in a when a Pile document is in startburst mode) + childContentsActive?: () => boolean | undefined; + childLayoutFitWidth?: (child: Doc) => boolean; + childlayout_showTitle?: () => string; + childOpacity?: () => number; + childContextMenuItems?: () => { script: ScriptField; label: string }[]; + childLayoutTemplate?: () => Doc | undefined; // specify a layout Doc template to use for children of the collection + childHideDecorationTitle?: boolean; + childHideResizeHandles?: boolean; + childDragAction?: dropActionType; + childXPadding?: number; + childYPadding?: number; + childLayoutString?: string; + childIgnoreNativeSize?: boolean; + childClickScript?: ScriptField; + childDoubleClickScript?: ScriptField; + AddToMap?: (treeViewDoc: Doc, index: number[]) => void; + 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 SubCollectionViewProps extends CollectionViewProps { isAnyChildContentActive: () => boolean; @@ -53,7 +84,7 @@ export function CollectionSubView(moreProps?: X) { } }; protected CreateDropTarget(ele: HTMLDivElement) { - //used in schema view + // used in schema view this.createDashEventsTarget(ele); } @@ -83,10 +114,11 @@ export function CollectionSubView(moreProps?: X) { const { Document, TemplateDataDocument } = this._props; const validPairs = this.childDocs .map(doc => Doc.GetLayoutDataDocPair(Document, !this._props.isAnnotationOverlay ? TemplateDataDocument : undefined, doc)) - .filter(pair => { - // filter out any documents that have a proto that we don't have permissions to - return !pair.layout?.hidden && pair.layout && (!pair.layout.proto || (pair.layout.proto instanceof Doc && GetEffectiveAcl(pair.layout.proto) !== AclPrivate)); - }); + .filter( + pair => + // filter out any documents that have a proto that we don't have permissions to + !pair.layout?.hidden && pair.layout && (!pair.layout.proto || (pair.layout.proto instanceof Doc && GetEffectiveAcl(pair.layout.proto) !== AclPrivate)) + ); return validPairs.map(({ data, layout }) => ({ data: data as Doc, layout: layout! })); // this mapping is a bit of a hack to coerce types } @computed get childDocList() { @@ -95,9 +127,9 @@ export function CollectionSubView(moreProps?: X) { 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()]; + childDocFilters = () => [...(this._props.childFilters?.().filter(f => ClientUtils.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)) || [])]; + unrecursiveDocFilters = () => [...(this._props.childFilters?.().filter(f => !ClientUtils.IsRecursiveFilter(f)) || [])]; childDocRangeFilters = () => [...(this._props.childFiltersByRanges?.() || []), ...this.collectionRangeDocFilters()]; searchFilterDocs = () => this._props.searchFilterDocs?.() ?? DocListCast(this.Document._searchFilterDocs); @computed.struct get childDocs() { @@ -129,13 +161,13 @@ export function CollectionSubView(moreProps?: X) { const docsforFilter: Doc[] = []; childDocs.forEach(d => { // dragging facets - const dragged = this._props.childFilters?.().some(f => f.includes(Utils.noDragDocsFilter)); - if (dragged && SnappingManager.CanEmbed && DragManager.docsBeingDragged.includes(d)) return false; + const dragged = this._props.childFilters?.().some(f => f.includes(ClientUtils.noDragDocsFilter)); + if (dragged && SnappingManager.CanEmbed && DragManager.docsBeingDragged.includes(d)) return; 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.Document).length > 0; const fieldKey = Doc.LayoutFieldKey(d); - const annos = !Field.toString(Doc.LayoutField(d) as Field).includes(CollectionView.name); + const annos = !Field.toString(Doc.LayoutField(d) as FieldType).includes(CollectionView.name); const data = d[annos ? fieldKey + '_annotations' : fieldKey]; const side = annos && d[fieldKey + '_sidebar']; if (data !== undefined || side !== undefined) { @@ -147,7 +179,7 @@ export function CollectionSubView(moreProps?: X) { newarray = []; subDocs.forEach(t => { const fieldKey = Doc.LayoutFieldKey(t); - const annos = !Field.toString(Doc.LayoutField(t) as Field).includes(CollectionView.name); + const annos = !Field.toString(Doc.LayoutField(t) as FieldType).includes(CollectionView.name); notFiltered = notFiltered || ((!searchDocs.length || searchDocs.includes(t)) && ((!childDocFilters.length && !childFiltersByRanges.length) || DocUtils.FilterDocs([t], childDocFilters, childFiltersByRanges, d).length)); DocListCast(t[annos ? fieldKey + '_annotations' : fieldKey]).forEach(newdoc => newarray.push(newdoc)); @@ -168,7 +200,7 @@ export function CollectionSubView(moreProps?: X) { let ind; const doc = this.Document; const id = Doc.UserDoc()[Id]; - const email = Doc.CurrentUserEmail; + const email = ClientUtils.CurrentUserEmail; const pos = { x: position[0], y: position[1] }; if (id && email) { const proto = Doc.GetProto(doc); @@ -184,6 +216,7 @@ export function CollectionSubView(moreProps?: X) { if (!cursors) { proto.cursors = cursors = new List(); } + // eslint-disable-next-line no-cond-assign if (cursors.length > 0 && (ind = cursors.findIndex(entry => entry.data.metadata.id === id)) > -1) { cursors[ind].setPosition(pos); } else { @@ -215,9 +248,9 @@ export function CollectionSubView(moreProps?: X) { moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean, annotationKey?: string) => this._props.moveDocument?.(doc, targetCollection, addDocument) || false; protected onInternalDrop(e: Event, de: DragManager.DropEvent): boolean { - const docDragData = de.complete.docDragData; + const { docDragData } = de.complete; if (docDragData) { - let added = undefined; + let added; const dropAction = docDragData.dropAction || docDragData.userDropAction; const targetDocments = DocListCast(this.dataDoc[this._props.fieldKey]); const someMoved = !dropAction && docDragData.draggedDocuments.some(drag => targetDocments.includes(drag)); @@ -245,8 +278,9 @@ export function CollectionSubView(moreProps?: X) { } added === false && !this._props.isAnnotationOverlay && e.preventDefault(); added === true && e.stopPropagation(); - return added ? true : false; - } else if (de.complete.annoDragData) { + return !!added; + } + if (de.complete.annoDragData) { const dropCreator = de.complete.annoDragData.dropDocCreator; de.complete.annoDragData.dropDocCreator = () => { const dropped = dropCreator(this._props.isAnnotationOverlay ? this.Document : undefined); @@ -316,7 +350,7 @@ export function CollectionSubView(moreProps?: X) { const result = (await Networking.PostToServer('/uploadRemoteImage', { sources: [imgSrc] })).lastElement(); const newImgSrc = result.accessPaths.agnostic.client.indexOf('dashblobstore') === -1 // - ? Utils.prepend(result.accessPaths.agnostic.client) + ? ClientUtils.prepend(result.accessPaths.agnostic.client) : result.accessPaths.agnostic.client; addDocument(ImageUtils.AssignImgInfo(Docs.Create.ImageDocument(newImgSrc, imgOpts), result)); @@ -325,39 +359,39 @@ export function CollectionSubView(moreProps?: X) { addDocument(ImageUtils.AssignImgInfo(doc, await ImageUtils.ExtractImgInfo(doc))); } return; + } + const path = window.location.origin + '/doc/'; + if (text.startsWith(path)) { + const docId = text.replace(Doc.globalServerPath(), '').split('?')[0]; + DocServer.GetRefField(docId).then(f => { + const fDoc = f; + if (fDoc instanceof Doc) { + if (options.x || options.y) { + fDoc.x = options.x as number; + fDoc.y = options.y as number; + } // should be in CollectionFreeFormView + addDocument(fDoc); + } + }); } else { - const path = window.location.origin + '/doc/'; - if (text.startsWith(path)) { - const docId = text.replace(Doc.globalServerPath(), '').split('?')[0]; - DocServer.GetRefField(docId).then(f => { - if (f instanceof Doc) { - if (options.x || options.y) { - f.x = options.x as number; - f.y = options.y as number; - } // should be in CollectionFreeFormView - f instanceof Doc && addDocument(f); - } - }); - } else { - const srcWeb = SelectionManager.Views.lastElement(); - const srcUrl = (srcWeb?.Document.data as WebField)?.url?.href?.match(/https?:\/\/[^/]*/)?.[0]; - const reg = new RegExp(Utils.prepend(''), 'g'); - const modHtml = srcUrl ? html.replace(reg, srcUrl) : html; - const backgroundColor = tags.map(tag => tag.match(/.*(background-color: ?[^;]*)/)?.[1]?.replace(/background-color: ?(.*)/, '$1')).filter(t => t)?.[0]; - const htmlDoc = Docs.Create.HtmlDocument(modHtml, { ...options, title: srcUrl ? 'from:' + srcUrl : '-web clip-', _width: 300, _height: 300, backgroundColor }); - Doc.GetProto(htmlDoc)['data-text'] = Doc.GetProto(htmlDoc).text = text; - addDocument(htmlDoc); - if (srcWeb) { - const iframe = SelectionManager.Views[0].ContentDiv?.getElementsByTagName('iframe')?.[0]; - const focusNode = iframe?.contentDocument?.getSelection()?.focusNode as any; - if (focusNode) { - const anchor = srcWeb?.ComponentView?.getAnchor?.(true); - anchor && DocUtils.MakeLink(htmlDoc, anchor, {}); - } + const srcWeb = SelectionManager.Views.lastElement(); + const srcUrl = (srcWeb?.Document.data as WebField)?.url?.href?.match(/https?:\/\/[^/]*/)?.[0]; + const reg = new RegExp(ClientUtils.prepend(''), 'g'); + const modHtml = srcUrl ? html.replace(reg, srcUrl) : html; + const backgroundColor = tags.map(tag => tag.match(/.*(background-color: ?[^;]*)/)?.[1]?.replace(/background-color: ?(.*)/, '$1')).filter(t => t)?.[0]; + const htmlDoc = Docs.Create.HtmlDocument(modHtml, { ...options, title: srcUrl ? 'from:' + srcUrl : '-web clip-', _width: 300, _height: 300, backgroundColor }); + Doc.GetProto(htmlDoc)['data-text'] = Doc.GetProto(htmlDoc).text = text; + addDocument(htmlDoc); + if (srcWeb) { + const iframe = SelectionManager.Views[0].ContentDiv?.getElementsByTagName('iframe')?.[0]; + const focusNode = iframe?.contentDocument?.getSelection()?.focusNode as any; + if (focusNode) { + const anchor = srcWeb?.ComponentView?.getAnchor?.(true); + anchor && DocUtils.MakeLink(htmlDoc, anchor, {}); } } - return; } + return; } } @@ -414,10 +448,15 @@ export function CollectionSubView(moreProps?: X) { for (let i = 0; i < length; i++) { const item = e.dataTransfer.items[i]; if (item.kind === 'string' && item.type.includes('uri')) { - const stringContents = await new Promise(resolve => item.getAsString(resolve)); - const type = (await rp.head(Utils.CorsProxy(stringContents)))['content-type']; + // eslint-disable-next-line no-await-in-loop + const stringContents = await new Promise(resolve => { + item.getAsString(resolve); + }); + // eslint-disable-next-line no-await-in-loop + const type = (await rp.head(ClientUtils.CorsProxy(stringContents)))['content-type']; if (type) { - const doc = await DocUtils.DocumentFromType(type, Utils.CorsProxy(stringContents), options); + // eslint-disable-next-line no-await-in-loop + const doc = await DocUtils.DocumentFromType(type, ClientUtils.CorsProxy(stringContents), options); doc && generatedDocuments.push(doc); } } @@ -426,7 +465,7 @@ export function CollectionSubView(moreProps?: X) { file?.type && files.push(file); file?.type === 'application/json' && - Utils.readUploadedFileAsText(file).then(result => { + ClientUtils.readUploadedFileAsText(file).then(result => { const json = JSON.parse(result as string); addDocument( Docs.Create.TreeDocument( @@ -450,7 +489,7 @@ export function CollectionSubView(moreProps?: X) { // create placeholder docs // inside placeholder docs have some func that - let pileUpDoc = undefined; + let pileUpDoc; if (typeof files === 'string') { const loading = Docs.Create.LoadingDocument(files, options); generatedDocuments.push(loading); @@ -478,20 +517,16 @@ export function CollectionSubView(moreProps?: X) { }) : []; if (completed) completed(set); - else { - if (isFreeformView && generatedDocuments.length > 1) { - pileUpDoc = DocUtils.pileup(generatedDocuments, options.x as number, options.y as number)!; - addDocument(pileUpDoc); - } else { - generatedDocuments.forEach(addDocument); - } - } - } else { - if (text && !text.includes('https://')) { - addDocument(Docs.Create.TextDocument(text, { ...options, title: text.substring(0, 20), _width: 400, _height: 315 })); + else if (isFreeformView && generatedDocuments.length > 1) { + pileUpDoc = DocUtils.pileup(generatedDocuments, options.x as number, options.y as number)!; + addDocument(pileUpDoc); } else { - alert('Document upload failed - possibly an unsupported file type.'); + generatedDocuments.forEach(addDocument); } + } else if (text && !text.includes('https://')) { + addDocument(Docs.Create.TextDocument(text, { ...options, title: text.substring(0, 20), _width: 400, _height: 315 })); + } else { + alert('Document upload failed - possibly an unsupported file type.'); } }; } diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx index 37452ddfb..7f234ffe9 100644 --- a/src/client/views/collections/CollectionTimeView.tsx +++ b/src/client/views/collections/CollectionTimeView.tsx @@ -1,7 +1,8 @@ import { action, computed, makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { emptyFunction, returnFalse, returnTrue, setupMoveUpEvents } from '../../../Utils'; +import { emptyFunction } from '../../../Utils'; +import { returnFalse, returnTrue, setupMoveUpEvents } from '../../../ClientUtils'; import { Doc, Opt, StrListCast } from '../../../fields/Doc'; import { List } from '../../../fields/List'; import { ObjectField } from '../../../fields/ObjectField'; @@ -195,7 +196,7 @@ export class CollectionTimeView extends CollectionSubView() { let nonNumbers = 0; this.childDocs.map(doc => { const num = NumCast(doc[this.pivotField], Number(StrCast(doc[this.pivotField]))); - if (Number.isNaN(num)) { + if (isNaN(num)) { nonNumbers++; } }); diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 5741fc29b..32473f20b 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -1,6 +1,7 @@ import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; +import { DivHeight, returnAll, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnOne, returnTrue, returnZero } from '../../../ClientUtils'; import { Doc, DocListCast, Opt, StrListCast } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; @@ -8,10 +9,11 @@ import { listSpec } from '../../../fields/Schema'; import { ScriptField } from '../../../fields/ScriptField'; import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; -import { DivHeight, emptyFunction, returnAll, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnOne, returnTrue, returnZero, Utils } from '../../../Utils'; +import { emptyFunction, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentManager } from '../../util/DocumentManager'; -import { DragManager, dropActionType } from '../../util/DragManager'; +import { DragManager } from '../../util/DragManager'; +import { dropActionType } from '../../util/DropActionTypes'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SelectionManager } from '../../util/SelectionManager'; import { SnappingManager } from '../../util/SnappingManager'; diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 18eb4dd1f..a0d84ab28 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -1,21 +1,20 @@ +/* eslint-disable react/jsx-props-no-spreading */ import { IReactionDisposer, makeObservable, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { returnEmptyString } from '../../../Utils'; -import { Doc, DocListCast, Opt } from '../../../fields/Doc'; +import { returnEmptyString } from '../../../ClientUtils'; +import { Doc, DocListCast } from '../../../fields/Doc'; import { ObjectField } from '../../../fields/ObjectField'; -import { ScriptField } from '../../../fields/ScriptField'; import { BoolCast, Cast, ScriptCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; import { CollectionViewType } from '../../documents/DocumentTypes'; import { DocUtils } from '../../documents/Documents'; -import { dropActionType } from '../../util/DragManager'; import { ImageUtils } from '../../util/Import & Export/ImageUtils'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent'; import { OpenWhere } from '../nodes/DocumentView'; -import { FieldView, FieldViewProps } from '../nodes/FieldView'; +import { FieldView } from '../nodes/FieldView'; import { CollectionCalendarView } from './CollectionCalendarView'; import { CollectionCarousel3DView } from './CollectionCarousel3DView'; import { CollectionCarouselView } from './CollectionCarouselView'; @@ -23,7 +22,7 @@ import { CollectionDockingView } from './CollectionDockingView'; import { CollectionNoteTakingView } from './CollectionNoteTakingView'; import { CollectionPileView } from './CollectionPileView'; import { CollectionStackingView } from './CollectionStackingView'; -import { SubCollectionViewProps } from './CollectionSubView'; +import { CollectionViewProps, SubCollectionViewProps } from './CollectionSubView'; import { CollectionTimeView } from './CollectionTimeView'; import { CollectionTreeView } from './CollectionTreeView'; import './CollectionView.scss'; @@ -33,36 +32,7 @@ import { CollectionLinearView } from './collectionLinear'; import { CollectionMulticolumnView } from './collectionMulticolumn/CollectionMulticolumnView'; import { CollectionMultirowView } from './collectionMulticolumn/CollectionMultirowView'; import { CollectionSchemaView } from './collectionSchema/CollectionSchemaView'; -export interface CollectionViewProps extends React.PropsWithChildren { - 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; - setPreviewCursor?: (func: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt) => void) => void; - ignoreUnrendered?: boolean; - // property overrides for child documents - childDocuments?: Doc[]; // used to override the documents shown by the sub collection to an explicit list (see LinkBox) - childDocumentsActive?: () => boolean | undefined; // whether child documents can be dragged if collection can be dragged (eg., in a when a Pile document is in startburst mode) - childContentsActive?: () => boolean | undefined; - childLayoutFitWidth?: (child: Doc) => boolean; - childlayout_showTitle?: () => string; - childOpacity?: () => number; - childContextMenuItems?: () => { script: ScriptField; label: string }[]; - childLayoutTemplate?: () => Doc | undefined; // specify a layout Doc template to use for children of the collection - childHideDecorationTitle?: boolean; - childHideResizeHandles?: boolean; - childDragAction?: dropActionType; - childXPadding?: number; - childYPadding?: number; - childLayoutString?: string; - childIgnoreNativeSize?: boolean; - childClickScript?: ScriptField; - childDoubleClickScript?: ScriptField; - //TODO: [AL] add these fields - AddToMap?: (treeViewDoc: Doc, index: number[]) => void; - RemFromMap?: (treeViewDoc: Doc, index: number[]) => void; - hierarchyIndex?: number[]; // hierarchical index of a document up to the rendering root (primarily used for tree views) -} @observer export class CollectionView extends ViewBoxAnnotatableComponent() implements ViewBoxInterface { public static LayoutString(fieldStr: string) { @@ -89,7 +59,9 @@ export class CollectionView extends ViewBoxAnnotatableComponent (this.isAnyChildContentActive() ? true : this._props.isContentActive()), - active => (this._isContentActive = active), + active => { + this._isContentActive = active; + }, { fireImmediately: true } ); } @@ -100,13 +72,12 @@ export class CollectionView extends ViewBoxAnnotatableComponent; case CollectionViewType.Schema: return ; case CollectionViewType.Calendar: return ; case CollectionViewType.Docking: return ; @@ -134,6 +103,8 @@ export class CollectionView extends ViewBoxAnnotatableComponent; case CollectionViewType.Time: return ; case CollectionViewType.Grid: return ; + case CollectionViewType.Freeform: + default: return ; } }; @@ -144,9 +115,9 @@ export class CollectionView extends ViewBoxAnnotatableComponent func(CollectionViewType.Freeform), icon: 'signature' }, { description: 'Schema', event: () => func(CollectionViewType.Schema), icon: 'th-list' }, { description: 'Tree', event: () => func(CollectionViewType.Tree), icon: 'tree' }, - { description: 'Stacking', event: () => (func(CollectionViewType.Stacking)._layout_autoHeight = true), icon: 'ellipsis-v' }, + { description: 'Stacking', event: () => {func(CollectionViewType.Stacking)._layout_autoHeight = true}, icon: 'ellipsis-v' }, { description: 'Calendar', event: () => func(CollectionViewType.Calendar), icon: 'columns'}, - { description: 'Notetaking', event: () => (func(CollectionViewType.NoteTaking)._layout_autoHeight = true), icon: 'ellipsis-v' }, + { description: 'Notetaking', event: () => {func(CollectionViewType.NoteTaking)._layout_autoHeight = true}, icon: 'ellipsis-v' }, { description: 'Multicolumn', event: () => func(CollectionViewType.Multicolumn), icon: 'columns' }, { description: 'Multirow', event: () => func(CollectionViewType.Multirow), icon: 'columns' }, { description: 'Masonry', event: () => func(CollectionViewType.Masonry), icon: 'columns' }, @@ -178,14 +149,14 @@ export class CollectionView extends ViewBoxAnnotatableComponent (this.Document.forceActive = !this.Document.forceActive), icon: 'project-diagram' }) : null; + !Doc.noviceMode ? optionItems.splice(0, 0, { description: `${this.Document.forceActive ? 'Select' : 'Force'} Contents Active`, event: () => {this.Document.forceActive = !this.Document.forceActive}, icon: 'project-diagram' }) : null; // prettier-ignore if (this.Document.childLayout instanceof Doc) { optionItems.push({ description: 'View Child Layout', event: () => this._props.addDocTab(this.Document.childLayout as Doc, OpenWhere.addRight), icon: 'project-diagram' }); } if (this.Document.childClickedOpenTemplateView instanceof Doc) { optionItems.push({ description: 'View Child Detailed Layout', event: () => this._props.addDocTab(this.Document.childClickedOpenTemplateView as Doc, OpenWhere.addRight), icon: 'project-diagram' }); } - !Doc.noviceMode && optionItems.push({ description: `${this.layoutDoc._isLightbox ? 'Unset' : 'Set'} is Lightbox`, event: () => (this.layoutDoc._isLightbox = !this.layoutDoc._isLightbox), icon: 'project-diagram' }); + !Doc.noviceMode && optionItems.push({ description: `${this.layoutDoc._isLightbox ? 'Unset' : 'Set'} is Lightbox`, event: () => { this.layoutDoc._isLightbox = !this.layoutDoc._isLightbox; }, icon: 'project-diagram' }); // prettier-ignore !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'hand-point-right' }); @@ -200,7 +171,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent { + event: () => { const embedding = Doc.MakeEmbedding(this.Document); DocUtils.makeCustomViewClicked(embedding, undefined, func.key); this._props.addDocTab(embedding, OpenWhere.addRight); @@ -211,7 +182,9 @@ export class CollectionView extends ViewBoxAnnotatableComponent (this.dataDoc[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data))), + event: () => { + this.dataDoc[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data)); + }, }) ); !Doc.IsSystem(this.Document) && !existingOnClick && cm.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' }); @@ -229,7 +202,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent this._props.PanelWidth(); childLayoutTemplate = () => this._props.childLayoutTemplate?.() || Cast(this.Document.childLayoutTemplate, Doc, null); - isContentActive = (outsideReaction?: boolean) => this._isContentActive; + isContentActive = () => this._isContentActive; pointerEvents = () => this.layoutDoc._lockedPosition && // diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 5cb7b149b..8a2e83ed9 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -6,7 +6,8 @@ import { IReactionDisposer, ObservableSet, action, computed, makeObservable, obs import { observer } from 'mobx-react'; import * as React from 'react'; import * as ReactDOM from 'react-dom/client'; -import { DashColor, Utils, emptyFunction, lightOrDark, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick } from '../../../Utils'; +import { ClientUtils, DashColor, lightOrDark, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick } from '../../../ClientUtils'; +import { emptyFunction } from '../../../Utils'; import { Doc, Opt } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; @@ -18,13 +19,14 @@ import { DocServer } from '../../DocServer'; import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; import { Docs } from '../../documents/Documents'; import { DocumentManager } from '../../util/DocumentManager'; -import { DragManager, dropActionType } from '../../util/DragManager'; +import { DragManager } from '../../util/DragManager'; +import { dropActionType } from '../../util/DropActionTypes'; import { SelectionManager } from '../../util/SelectionManager'; -import { SettingsManager } from '../../util/SettingsManager'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { UndoManager, undoable } from '../../util/UndoManager'; import { DashboardView } from '../DashboardView'; +import { PinProps } from '../DocComponent'; import { LightboxView } from '../LightboxView'; import { ObservableReactComponent } from '../ObservableReactComponent'; import { DefaultStyleProvider, StyleProp } from '../StyleProvider'; @@ -32,12 +34,12 @@ import { Colors } from '../global/globalEnums'; import { DocumentView, OpenWhere, OpenWhereMod, returnEmptyDocViewList } from '../nodes/DocumentView'; import { FieldViewProps, FocusViewOptions } from '../nodes/FieldView'; import { KeyValueBox } from '../nodes/KeyValueBox'; -import { DashFieldView } from '../nodes/formattedText/DashFieldView'; -import { PinProps, PresBox, PresMovement } from '../nodes/trails'; +import { PresBox, PresMovement } from '../nodes/trails'; import { CollectionDockingView } from './CollectionDockingView'; import { CollectionView } from './CollectionView'; import './TabDocView.scss'; import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; + const _global = (window /* browser */ || global) /* node */ as any; interface TabDocViewProps { @@ -105,7 +107,6 @@ export class TabDocView extends ObservableReactComponent { while (child?.children.length) { const next = Array.from(child.children).find(c => c.className?.toString().includes('SVGAnimatedString') || typeof c.className === 'string'); if (next?.className?.toString().includes(DocumentView.ROOT_DIV)) break; - if (next?.className?.toString().includes(DashFieldView.name)) break; if (next) child = next; else break; } @@ -149,17 +150,17 @@ export class TabDocView extends ObservableReactComponent { }; const docIcon = ; - const closeIcon = ; + const closeIcon = ; ReactDOM.createRoot(iconWrap).render(docIcon); ReactDOM.createRoot(closeWrap).render(closeIcon); tab.reactComponents = [iconWrap, closeWrap]; tab.element[0].prepend(iconWrap); tab._disposers.color = reaction( - () => ({ variant: SettingsManager.userVariantColor, degree: Doc.GetBrushStatus(doc), highlight: DefaultStyleProvider(this._document, undefined, StyleProp.Highlighting) }), + () => ({ variant: SnappingManager.userVariantColor, degree: Doc.GetBrushStatus(doc), highlight: DefaultStyleProvider(this._document, undefined, StyleProp.Highlighting) }), ({ variant, degree, highlight }) => { const color = highlight?.highlightIndex === Doc.DocBrushStatus.highlighted ? highlight.highlightColor : degree ? ['transparent', variant, variant, 'orange'][degree] : variant; - const textColor = color === variant ? SettingsManager.userColor : lightOrDark(color); + const textColor = color === variant ? SnappingManager.userColor : lightOrDark(color); titleEle.style.color = textColor; iconWrap.style.color = textColor; closeWrap.style.color = textColor; @@ -407,9 +408,7 @@ export class TabDocView extends ObservableReactComponent { return false; }; - getCurrentFrame = () => { - return NumCast(Cast(PresBox.Instance.activeItem.presentation_targetDoc, Doc, null)._currentFrame); - }; + getCurrentFrame = () => NumCast(Cast(PresBox.Instance.activeItem.presentation_targetDoc, Doc, null)._currentFrame); static Activate = (tabDoc: Doc) => { const tab = Array.from(CollectionDockingView.Instance?.tabMap!).find(tab => tab.DashDoc === tabDoc && !tab.contentItem.config.props.keyValue); tab?.header.parent.setActiveContentItem(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) @@ -426,7 +425,7 @@ export class TabDocView extends ObservableReactComponent { @observable _forceInvalidateScreenToLocal = 0; ScreenToLocalTransform = () => { this._forceInvalidateScreenToLocal; - const { translateX, translateY } = Utils.GetScreenTransform(this._mainCont?.children?.[0] as HTMLElement); + const { translateX, translateY } = ClientUtils.GetScreenTransform(this._mainCont?.children?.[0] as HTMLElement); return CollectionDockingView.Instance?.ScreenToLocalBoxXf().translate(-translateX, -translateY) ?? Transform.Identity(); }; PanelWidth = () => this._panelWidth; @@ -434,7 +433,9 @@ export class TabDocView extends ObservableReactComponent { miniMapColor = () => Colors.MEDIUM_GRAY; tabView = () => this._view; disableMinimap = () => !this._document; - whenChildContentActiveChanges = (isActive: boolean) => (this._isAnyChildContentActive = isActive); + whenChildContentActiveChanges = (isActive: boolean) => { + this._isAnyChildContentActive = isActive; + }; isContentActive = () => this._isContentActive; waitForDoubleClick = () => (SnappingManager.ExploreMode ? 'never' : undefined); @computed get docView() { @@ -465,9 +466,9 @@ export class TabDocView extends ObservableReactComponent { addDocument={undefined} removeDocument={this.remDocTab} addDocTab={this.addDocTab} - suppressSetHeight={this._document._layout_fitWidth ? true : false} + suppressSetHeight={!!this._document._layout_fitWidth} ScreenToLocalTransform={this.ScreenToLocalTransform} - dontCenter={'y'} + dontCenter="y" whenChildContentsActiveChanged={this.whenChildContentActiveChanges} focus={this.focusFunc} containerViewPath={returnEmptyDoclist} @@ -485,12 +486,13 @@ export class TabDocView extends ObservableReactComponent { style={{ fontFamily: Doc.UserDoc().renderStyle === 'comic' ? 'Comic Sans MS' : undefined, }} - onPointerOver={action(() => (this._hovering = true))} - onPointerLeave={action(() => (this._hovering = false))} - onDragOver={action(() => (this._hovering = true))} - onDragLeave={action(() => (this._hovering = false))} + onPointerOver={action(() => { this._hovering = true; })} // prettier-ignore + onPointerLeave={action(() => { this._hovering = false; })} // prettier-ignore + onDragOver={action(() => { this._hovering = true; })} // prettier-ignore + onDragLeave={action(() => { this._hovering = false; })} // prettier-ignore ref={ref => { - if ((this._mainCont = ref)) { + this._mainCont = ref; + if (this._mainCont) { if (this._lastTab) { this._view && DocumentManager.Instance.RemoveView(this._view); } @@ -524,7 +526,8 @@ interface TabMiniThumbProps { @observer class TabMiniThumb extends React.Component { render() { - return
; + const { miniWidth, miniHeight, miniLeft, miniTop } = this.props; + return
; } } @observer @@ -532,14 +535,10 @@ export class TabMinimapView extends ObservableReactComponent, props: Opt, property: string): any => { if (doc) { switch (property.split(':')[0]) { - default: - return DefaultStyleProvider(doc, props, property); - case StyleProp.PointerEvents: - return 'none'; - case StyleProp.DocContents: - const background = ((type: DocumentType) => { - // prettier-ignore - switch (type) { + case StyleProp.PointerEvents: return 'none'; + case StyleProp.DocContents: { + const background = (() => { + switch (doc.type as DocumentType) { case DocumentType.PDF: return 'pink'; case DocumentType.AUDIO: return 'lightgreen'; case DocumentType.WEB: return 'brown'; @@ -549,10 +548,12 @@ export class TabMinimapView extends ObservableReactComponent; - } + } + default: return DefaultStyleProvider(doc, props, property); + } // prettier-ignore } }; @@ -639,7 +640,7 @@ export class TabMinimapView extends ObservableReactComponent - } color={SettingsManager.userVariantColor} type={Type.TERT} onPointerDown={e => e.stopPropagation()} placement="top-end" popup={this.popup} /> + } color={SnappingManager.userVariantColor} type={Type.TERT} onPointerDown={e => e.stopPropagation()} placement="top-end" popup={this.popup} />
); } diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 4fd49f8fe..e266ccd14 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -1,11 +1,15 @@ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { IconButton, Size } from 'browndash-components'; import { IReactionDisposer, action, computed, makeObservable, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { Utils, emptyFunction, lightOrDark, return18, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, returnZero, setupMoveUpEvents, simulateMouseClick } from '../../../Utils'; -import { Doc, DocListCast, Field, FieldResult, Opt, StrListCast } from '../../../fields/Doc'; +import { ClientUtils, lightOrDark, return18, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, returnZero, setupMoveUpEvents, simulateMouseClick } from '../../../ClientUtils'; +import { emptyFunction } from '../../../Utils'; +import { Doc, DocListCast, Field, FieldType, FieldResult, Opt, StrListCast } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; @@ -17,7 +21,8 @@ import { TraceMobx } from '../../../fields/util'; import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; import { DocUtils, Docs } from '../../documents/Documents'; import { DocumentManager } from '../../util/DocumentManager'; -import { DragManager, dropActionType } from '../../util/DragManager'; +import { DragManager } from '../../util/DragManager'; +import { dropActionType } from '../../util/DropActionTypes'; import { LinkManager } from '../../util/LinkManager'; import { SettingsManager } from '../../util/SettingsManager'; import { SnappingManager } from '../../util/SnappingManager'; @@ -35,10 +40,12 @@ import { CollectionTreeView, TreeViewType } from './CollectionTreeView'; import { CollectionView } from './CollectionView'; import { TreeSort } from './TreeSort'; import './TreeView.scss'; + const { TREE_BULLET_WIDTH } = require('../global/globalCssVariables.module.scss'); // prettier-ignore export interface TreeViewProps { treeView: CollectionTreeView; + // eslint-disable-next-line no-use-before-define parentTreeView: TreeView | CollectionTreeView | undefined; observeHeight: (ref: any) => void; unobserveHeight: (ref: any) => void; @@ -87,6 +94,7 @@ const treeBulletWidth = function () { */ @observer export class TreeView extends ObservableReactComponent { + // eslint-disable-next-line no-use-before-define static _editTitleOnLoad: Opt<{ id: string; parent: TreeView | CollectionTreeView | undefined }>; static _openTitleScript: Opt; static _openLevelScript: Opt; @@ -101,6 +109,9 @@ export class TreeView extends ObservableReactComponent { get treeViewOpenIsTransient() { return this.treeView.Document.treeView_OpenIsTransient || Doc.IsDataProto(this.Document); } + @computed get treeViewOpen() { + return (!this.treeViewOpenIsTransient && Doc.GetT(this.Document, 'treeView_Open', 'boolean', true)) || this._transientOpenState; + } set treeViewOpen(c: boolean) { if (this.treeViewOpenIsTransient) this._transientOpenState = c; else { @@ -137,9 +148,6 @@ export class TreeView extends ObservableReactComponent { @computed get Document() { return this._props.Document; } - @computed get treeViewOpen() { - return (!this.treeViewOpenIsTransient && Doc.GetT(this.Document, 'treeView_Open', 'boolean', true)) || this._transientOpenState; - } @computed get treeViewExpandedView() { return this.validExpandViewTypes.includes(StrCast(this.Document.treeView_ExpandedView)) ? StrCast(this.Document.treeView_ExpandedView) : this.defaultExpandedView; } @@ -221,7 +229,7 @@ export class TreeView extends ObservableReactComponent { this.treeViewOpen = !this.treeViewOpen; } else { // choose an appropriate embedding or make one. --- choose the first embedding that (1) user owns, (2) has no context field ... otherwise make a new embedding - const bestEmbedding = docView.Document.author === Doc.CurrentUserEmail && !Doc.IsDataProto(docView.Document) ? docView.Document : Doc.BestEmbedding(docView.Document); + const bestEmbedding = docView.Document.author === ClientUtils.CurrentUserEmail && !Doc.IsDataProto(docView.Document) ? docView.Document : Doc.BestEmbedding(docView.Document); this._props.addDocTab(bestEmbedding, OpenWhere.lightbox); } }; @@ -230,7 +238,7 @@ export class TreeView extends ObservableReactComponent { recurToggle = (childList: Doc[]) => { if (childList.length > 0) { childList.forEach(child => { - child.runProcess = !!!child.runProcess; + child.runProcess = !child.runProcess; TreeView.ToggleChildrenRun.get(child)?.(); }); } @@ -273,9 +281,7 @@ export class TreeView extends ObservableReactComponent { this.recurToggle(this.childDocs); }); - TreeView.GetRunningChildren.set(this.Document, () => { - return this.getRunningChildren(this.childDocs); - }); + TreeView.GetRunningChildren.set(this.Document, () => this.getRunningChildren(this.childDocs)); } _treeEle: any; @@ -301,7 +307,9 @@ export class TreeView extends ObservableReactComponent { super.componentDidUpdate(prevProps); this._disposers.opening = reaction( () => this.treeViewOpen, - open => !open && (this._renderCount = 20) + open => { + !open && (this._renderCount = 20); + } ); this._props.hierarchyIndex !== undefined && this._props.AddToMap?.(this.Document, this._props.hierarchyIndex); } @@ -324,7 +332,7 @@ export class TreeView extends ObservableReactComponent { document.addEventListener('pointerup', this.onDragUp, true); } }; - onPointerLeave = (e: React.PointerEvent): void => { + onPointerLeave = (): void => { Doc.UnBrushDoc(this.dataDoc); if (this._header.current?.className !== 'treeView-header-editing') { this._header.current!.className = 'treeView-header'; @@ -385,7 +393,7 @@ export class TreeView extends ObservableReactComponent { return this.localAdd(folder); }; - preTreeDrop = (e: Event, de: DragManager.DropEvent, docDropAction: dropActionType) => { + preTreeDrop = () => { // fall through and let the CollectionTreeView handle this since treeView items have no special properties of their own }; @@ -403,7 +411,7 @@ export class TreeView extends ObservableReactComponent { e.stopPropagation(); return true; } - const docDragData = de.complete.docDragData; + const { docDragData } = de.complete; if (docDragData && pt[0] < rect.left + rect.width) { if (docDragData.draggedDocuments[0] === this.Document) return true; const added = this.dropDocuments( @@ -462,8 +470,8 @@ export class TreeView extends ObservableReactComponent { refTransform = (ref: HTMLDivElement | undefined | null) => { if (!ref) return this.ScreenToLocalTransform(); - const { scale, translateX, translateY } = Utils.GetScreenTransform(ref); - const outerXf = Utils.GetScreenTransform(this.treeView.MainEle()); + const { translateX, translateY } = ClientUtils.GetScreenTransform(ref); + const outerXf = ClientUtils.GetScreenTransform(this.treeView.MainEle()); const offset = this.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY); return this.ScreenToLocalTransform().translate(offset[0], offset[1]); }; @@ -490,14 +498,19 @@ export class TreeView extends ObservableReactComponent { const ids: { [key: string]: string } = {}; const rows: JSX.Element[] = []; const doc = this.Document; - doc && Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key)); + doc && + Object.keys(doc).forEach(key => { + !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key); + }); + // eslint-disable-next-line no-restricted-syntax for (const key of Object.keys(ids).slice().sort()) { + // eslint-disable-next-line no-continue if (this._props.skipFields?.includes(key) || key === 'title' || key === 'treeView_Open') continue; const contents = doc[key]; let contentElement: (JSX.Element | null)[] | JSX.Element = []; - let leftOffset = observable({ width: 0 }); + const leftOffset = observable({ width: 0 }); const expandedWidth = () => this._props.panelWidth() - leftOffset.width; if (contents instanceof Doc || (Cast(contents, listSpec(Doc)) && Cast(contents, listSpec(Doc))!.length && Cast(contents, listSpec(Doc))![0] instanceof Doc)) { const remDoc = (doc: Doc | Doc[]) => this.remove(doc, key); @@ -549,7 +562,7 @@ export class TreeView extends ObservableReactComponent { contentElement = ( Field.toKeyValueString(doc, key)} @@ -572,15 +585,15 @@ export class TreeView extends ObservableReactComponent { ); } rows.push( -
+
{ - const match = input.match(/([a-zA-Z0-9_-]+)(=|=:=)([a-zA-Z,_@\?\+\-\*\/\ 0-9\(\)]+)/); + const match = input.match(/([a-zA-Z0-9_-]+)(=|=:=)([a-zA-Z,_@?+\-*/ 0-9()]+)/); if (match) { const key = match[1]; const assign = match[2]; @@ -620,7 +633,9 @@ export class TreeView extends ObservableReactComponent { const docs = TreeView.sortDocs(this.childDocs || ([] as Doc[]), ordering); doc.zIndex = addBefore ? NumCast(addBefore.zIndex) + (before ? -0.5 : 0.5) : 1000; docs.push(doc); - docs.sort((a, b) => (NumCast(a.zIndex) > NumCast(b.zIndex) ? 1 : -1)).forEach((d, i) => (d.zIndex = i)); + docs.sort((a, b) => (NumCast(a.zIndex) > NumCast(b.zIndex) ? 1 : -1)).forEach((d, i) => { + d.zIndex = i; + }); } const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[key])) instanceof ComputedField; const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false); @@ -630,8 +645,8 @@ export class TreeView extends ObservableReactComponent { }; const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && localAdd(doc, addBefore, before), true); const docs = expandKey === 'embeddings' ? this.childEmbeddings : expandKey === 'links' ? this.childLinks : expandKey === 'annotations' ? this.childAnnos : this.childDocs; - let downX = 0, - downY = 0; + let downX = 0; + let downY = 0; if (docs?.length && this._renderCount < docs?.length) { this._renderTimer && clearTimeout(this._renderTimer); this._renderTimer = setTimeout( @@ -667,7 +682,7 @@ export class TreeView extends ObservableReactComponent { style={{ cursor: 'inherit' }} key={expandKey + 'more'} title={`Sorted by : ${this.Document.treeView_SortCriterion}. click to cycle`} - className="" //this.doc.treeView_HideTitle ? 'no-indent' : ''} + className="" // this.doc.treeView_HideTitle ? 'no-indent' : ''} onPointerDown={e => { downX = e.clientX; downY = e.clientY; @@ -719,7 +734,8 @@ export class TreeView extends ObservableReactComponent {
); - } else if (this.treeViewExpandedView === 'fields') { + } + if (this.treeViewExpandedView === 'fields') { return (
    {this.expandedField}
    @@ -891,14 +907,15 @@ export class TreeView extends ObservableReactComponent { titleStyleProvider = (doc: Doc | undefined, props: Opt, property: string): any => { if (!doc || doc !== this.Document) return this._props?.treeView?._props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView - const treeView = this.treeView; + const { treeView } = this; // prettier-ignore switch (property.split(':')[0]) { case StyleProp.Opacity: return this.treeView.outlineMode ? undefined : 1; case StyleProp.BackgroundColor: return this.selected ? '#7089bb' : undefined;//StrCast(doc._backgroundColor, StrCast(doc.backgroundColor)); case StyleProp.Highlighting: if (this.treeView.outlineMode) return undefined; + break; case StyleProp.BoxShadow: return undefined; - case StyleProp.DocContents: + case StyleProp.DocContents: { const highlightIndex = this.treeView.outlineMode ? Doc.DocBrushStatus.unbrushed : Doc.GetBrushHighlightStatus(doc); const highlightColor = ['transparent', 'rgb(68, 118, 247)', 'rgb(68, 118, 247)', 'orange', 'lightBlue'][highlightIndex]; return treeView.outlineMode ? null : ( @@ -917,6 +934,8 @@ export class TreeView extends ObservableReactComponent { {StrCast(doc?.title)}
); + } + default: } return treeView._props.styleProvider?.(doc, props, property); }; @@ -924,7 +943,7 @@ export class TreeView extends ObservableReactComponent { if (property.startsWith(StyleProp.Decorations)) return null; return this._props?.treeView?._props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView }; - onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => { + onKeyDown = (e: React.KeyboardEvent) => { if (this.Document.treeView_HideHeader || (this.Document.treeView_HideHeaderIfTemplate && this.treeView._props.childLayoutTemplate?.()) || this.treeView.outlineMode) { switch (e.key) { case 'Tab': @@ -944,6 +963,7 @@ export class TreeView extends ObservableReactComponent { e.stopPropagation?.(); e.preventDefault?.(); return UndoManager.RunInBatch(this.makeTextCollection, 'bullet'); + default: } } return false; @@ -959,22 +979,24 @@ export class TreeView extends ObservableReactComponent { const view = this._editTitle ? ( (this._editTitle = e))} + isEditingCallback={action(e => { + this._editTitle = e; + })} GetValue={() => StrCast(this.Document.title)} OnTab={undoBatch((shift?: boolean) => { if (!shift) this._props.indentDocument?.(true); else this._props.outdentDocument?.(true); })} OnEmpty={undoBatch(() => this.treeView.outlineMode && this._props.removeDoc?.(this.Document))} - OnFillDown={val => this.treeView.fileSysMode && this.makeFolder()} + OnFillDown={() => this.treeView.fileSysMode && this.makeFolder()} SetValue={undoBatch((value: string, shiftKey: boolean, enterKey: boolean) => { Doc.SetInPlace(this.Document, 'title', value, false); this.treeView.outlineMode && enterKey && this.makeTextCollection(); @@ -984,7 +1006,7 @@ export class TreeView extends ObservableReactComponent { { - this._docRef = r ? r : undefined; + this._docRef = r || undefined; if (this._docRef && TreeView._editTitleOnLoad?.id === this.Document[Id] && TreeView._editTitleOnLoad.parent === this._props.parentTreeView) { this._docRef.select(false); this.setEditTitle(this._docRef); @@ -994,8 +1016,8 @@ export class TreeView extends ObservableReactComponent { Document={this.Document} layout_fitWidth={returnTrue} scriptContext={this} - hideDecorations={true} - hideClickBehaviors={true} + hideDecorations + hideClickBehaviors styleProvider={this.titleStyleProvider} onClickScriptDisable="never" // tree docViews have a script to show fields, etc. containerViewPath={this.treeView.childContainerViewPath} @@ -1015,7 +1037,7 @@ export class TreeView extends ObservableReactComponent { PanelHeight={return18} contextMenuItems={this.contextMenuItems} renderDepth={1} - isContentActive={emptyFunction} //this._props.isContentActive} + isContentActive={emptyFunction} // this._props.isContentActive} isDocumentActive={this._props.isContentActive} focus={this.refocus} whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged} @@ -1045,99 +1067,101 @@ export class TreeView extends ObservableReactComponent { }}> {view}
-
r && (this.headerEleWidth = r.getBoundingClientRect().width))}> +
{ + r && (this.headerEleWidth = r.getBoundingClientRect().width); + })}> {this.titleButtons}
); } - renderBulletHeader = (contents: JSX.Element, editing: boolean) => { - return ( - <> + renderBulletHeader = (contents: JSX.Element, editing: boolean) => ( + <> +
{ + this.treeView.isContentActive() && + setupMoveUpEvents( + this, + e, + () => { + (this._dref ?? this._docRef)?.startDragging(e.clientX, e.clientY, '' as any); + return true; + }, + returnFalse, + emptyFunction + ); + }} + onPointerEnter={this.onPointerEnter} + onPointerLeave={this.onPointerLeave}>
{ - this.treeView.isContentActive() && - setupMoveUpEvents( - this, - e, - () => { - (this._dref ?? this._docRef)?.startDragging(e.clientX, e.clientY, '' as any); - return true; - }, - returnFalse, - emptyFunction - ); + className="treeView-background" + style={{ + background: SettingsManager.userColor, }} - onPointerEnter={this.onPointerEnter} - onPointerLeave={this.onPointerLeave}> -
- {contents} -
- {this.renderBorder} - - ); - }; - - fitWidthFilter = (doc: Doc) => (doc.type === DocumentType.IMG ? false : undefined); - renderEmbeddedDocument = (asText: boolean, isActive: () => boolean | undefined) => { - return ( -
- (this._dref = r))} - Document={this.Document} - layout_fitWidth={this.fitWidthFilter} - PanelWidth={this.embeddedPanelWidth} - PanelHeight={this.embeddedPanelHeight} - LayoutTemplateString={asText ? FormattedTextBox.LayoutString('text') : undefined} - LayoutTemplate={this.treeView._props.childLayoutTemplate} - isContentActive={isActive} - isDocumentActive={isActive} - styleProvider={asText ? this.titleStyleProvider : this.embeddedStyleProvider} - fitContentsToBox={returnTrue} - hideTitle={asText} - hideDecorations={true} - hideClickBehaviors={true} - hideLinkButton={BoolCast(this.treeView.Document.childHideLinkButton)} - dontRegisterView={BoolCast(this.treeView.Document.childDontRegisterViews, this._props.dontRegisterView)} - ScreenToLocalTransform={this.docTransform} - renderDepth={this._props.renderDepth + 1} - onClickScript={this.onChildClick} - onKey={this.onKeyDown} - containerViewPath={this.treeView.childContainerViewPath} - childFilters={returnEmptyFilter} - childFiltersByRanges={returnEmptyFilter} - searchFilterDocs={returnEmptyDoclist} - focus={this.refocus} - addDocument={this._props.addDocument} - moveDocument={this.move} - removeDocument={this._props.removeDoc} - whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged} - xPadding={NumCast(this.treeView.Document.childXPadding, this.treeView._props.childXPadding)} - yPadding={NumCast(this.treeView.Document.childYPadding, this.treeView._props.childYPadding)} - addDocTab={this._props.addDocTab} - pinToPres={this.treeView._props.pinToPres} - disableBrushing={this.treeView._props.disableBrushing} - scriptContext={this} /> + {contents}
- ); - }; + {this.renderBorder} + + ); + + fitWidthFilter = (doc: Doc) => (doc.type === DocumentType.IMG ? false : undefined); + renderEmbeddedDocument = (asText: boolean, isActive: () => boolean | undefined) => ( +
+ { + this._dref = r; + })} + Document={this.Document} + layout_fitWidth={this.fitWidthFilter} + PanelWidth={this.embeddedPanelWidth} + PanelHeight={this.embeddedPanelHeight} + LayoutTemplateString={asText ? FormattedTextBox.LayoutString('text') : undefined} + LayoutTemplate={this.treeView._props.childLayoutTemplate} + isContentActive={isActive} + isDocumentActive={isActive} + styleProvider={asText ? this.titleStyleProvider : this.embeddedStyleProvider} + fitContentsToBox={returnTrue} + hideTitle={asText} + hideDecorations + hideClickBehaviors + hideLinkButton={BoolCast(this.treeView.Document.childHideLinkButton)} + dontRegisterView={BoolCast(this.treeView.Document.childDontRegisterViews, this._props.dontRegisterView)} + ScreenToLocalTransform={this.docTransform} + renderDepth={this._props.renderDepth + 1} + onClickScript={this.onChildClick} + onKey={this.onKeyDown} + containerViewPath={this.treeView.childContainerViewPath} + childFilters={returnEmptyFilter} + childFiltersByRanges={returnEmptyFilter} + searchFilterDocs={returnEmptyDoclist} + focus={this.refocus} + addDocument={this._props.addDocument} + moveDocument={this.move} + removeDocument={this._props.removeDoc} + whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged} + xPadding={NumCast(this.treeView.Document.childXPadding, this.treeView._props.childXPadding)} + yPadding={NumCast(this.treeView.Document.childYPadding, this.treeView._props.childYPadding)} + addDocTab={this._props.addDocTab} + pinToPres={this.treeView._props.pinToPres} + disableBrushing={this.treeView._props.disableBrushing} + scriptContext={this} + /> +
+ ); // renders the text version of a document as the header. This is used in the file system mode and in other vanilla tree views. @computed get renderTitleAsHeader() { return this.treeView.Document.treeView_HideUnrendered && this.Document.layout_unrendered && !this.Document.treeView_FieldKey ? ( -
+
) : ( <> {this.renderBullet} @@ -1147,14 +1171,12 @@ export class TreeView extends ObservableReactComponent { } // renders the document in the header field instead of a text proxy. - renderDocumentAsHeader = (asText: boolean) => { - return ( - <> - {this.renderBullet} - {this.renderEmbeddedDocument(asText, this._props.isContentActive)} - - ); - }; + renderDocumentAsHeader = (asText: boolean) => ( + <> + {this.renderBullet} + {this.renderEmbeddedDocument(asText, this._props.isContentActive)} + + ); @computed get renderBorder() { const sorting = StrCast(this.Document.treeView_SortCriterion, TreeSort.WhenAdded); @@ -1185,7 +1207,7 @@ export class TreeView extends ObservableReactComponent { className={`treeView-container${this._props.isContentActive() ? '-active' : ''}`} ref={this.createTreeDropTarget} onDrop={this.onTreeDrop} - //onPointerDown={e => this._props.isContentActive(true) && SelectionManager.DeselectAll()} // bcz: this breaks entering a text filter in a filterBox since it deselects the filter's target document + // onPointerDown={e => this._props.isContentActive(true) && SelectionManager.DeselectAll()} // bcz: this breaks entering a text filter in a filterBox since it deselects the filter's target document // onKeyDown={this.onKeyDown} >
  • @@ -1209,11 +1231,10 @@ export class TreeView extends ObservableReactComponent { const aN = parseInt(a.match(reN)![0], 10); const bN = parseInt(b.match(reN)![0], 10); return aN === bN ? 0 : aN > bN ? 1 : -1; - } else { - return aA > bA ? 1 : -1; } + return aA > bA ? 1 : -1; }; - docs.sort(function (d1, d2): 0 | 1 | -1 { + docs.sort((d1, d2): 0 | 1 | -1 => { const a = criterion === TreeSort.AlphaUp ? d2 : d1; const b = criterion === TreeSort.AlphaUp ? d1 : d2; const first = a[criterion === TreeSort.Zindex ? 'zIndex' : 'title']; @@ -1230,7 +1251,7 @@ export class TreeView extends ObservableReactComponent { childDocs: Doc[], treeView: CollectionTreeView, parentTreeView: CollectionTreeView | TreeView | undefined, - treeView_Parent: Doc, + treeViewParent: Doc, dataDoc: Doc | undefined, parentCollectionDoc: Doc | undefined, containerPrevSibling: Doc | undefined, @@ -1244,7 +1265,7 @@ export class TreeView extends ObservableReactComponent { isContentActive: (outsideReaction?: boolean) => boolean, panelWidth: () => number, renderDepth: number, - treeView_HideHeaderFields: () => boolean, + treeViewHideHeaderFields: () => boolean, renderedIds: string[], onCheckedClick: undefined | (() => ScriptField), onChildClick: undefined | (() => ScriptField), @@ -1261,19 +1282,19 @@ export class TreeView extends ObservableReactComponent { hierarchyIndex?: number[], renderCount?: number ) { - const viewSpecScript = Cast(treeView_Parent.viewSpecScript, ScriptField); + const viewSpecScript = Cast(treeViewParent.viewSpecScript, ScriptField); if (viewSpecScript) { childDocs = childDocs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result); } - const docs = TreeView.sortDocs(childDocs, StrCast(treeView_Parent.treeView_SortCriterion, TreeSort.WhenAdded)); + const docs = TreeView.sortDocs(childDocs, StrCast(treeViewParent.treeView_SortCriterion, TreeSort.WhenAdded)); const rowWidth = () => panelWidth() - treeBulletWidth() * (treeView._props.NativeDimScaling?.() || 1); - const treeView_Refs = new Map(); + const treeViewRefs = new Map(); return docs .filter(child => child instanceof Doc) .map((child, i) => { if (renderCount && i > renderCount) return null; - const pair = Doc.GetLayoutDataDocPair(treeView_Parent, dataDoc, child); + const pair = Doc.GetLayoutDataDocPair(treeViewParent, dataDoc, child); if (!pair.layout || pair.data instanceof Promise) { return null; } @@ -1290,7 +1311,7 @@ export class TreeView extends ObservableReactComponent { Doc.SetContainer(child, treeView.Document); } }; - const indent = i === 0 ? undefined : (editTitle: boolean) => dentDoc(editTitle, docs[i - 1], undefined, treeView_Refs.get(docs[i - 1])); + const indent = i === 0 ? undefined : (editTitle: boolean) => dentDoc(editTitle, docs[i - 1], undefined, treeViewRefs.get(docs[i - 1])); const outdent = !parentCollectionDoc ? undefined : (editTitle: boolean) => dentDoc(editTitle, parentCollectionDoc, containerPrevSibling, parentTreeView instanceof TreeView ? parentTreeView._props.parentTreeView : undefined); const addDocument = (doc: Doc | Doc[], annotationKey?: string, relativeTo?: Doc, before?: boolean) => add(doc, relativeTo ?? docs[i], before !== undefined ? before : false); const childLayout = Doc.Layout(pair.layout); @@ -1301,10 +1322,10 @@ export class TreeView extends ObservableReactComponent { return ( treeView_Refs.set(child, r ? r : undefined)} + ref={r => treeViewRefs.set(child, r || undefined)} Document={pair.layout} dataDoc={pair.data} - treeViewParent={treeView_Parent} + treeViewParent={treeViewParent} prevSibling={docs[i]} // TODO: [AL] add these hierarchyIndex={hierarchyIndex ? [...hierarchyIndex, i + 1] : undefined} @@ -1316,7 +1337,7 @@ export class TreeView extends ObservableReactComponent { onCheckedClick={onCheckedClick} onChildClick={onChildClick} renderDepth={renderDepth} - removeDoc={StrCast(treeView_Parent.treeView_FreezeChildren).includes('remove') ? undefined : remove} + removeDoc={StrCast(treeViewParent.treeView_FreezeChildren).includes('remove') ? undefined : remove} addDocument={addDocument} styleProvider={styleProvider} panelWidth={rowWidth} @@ -1327,7 +1348,7 @@ export class TreeView extends ObservableReactComponent { addDocTab={addDocTab} ScreenToLocalTransform={screenToLocalXf} isContentActive={isContentActive} - treeViewHideHeaderFields={treeView_HideHeaderFields} + treeViewHideHeaderFields={treeViewHideHeaderFields} renderedIds={renderedIds} skipFields={skipFields} firstLevel={firstLevel} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx index cc729decc..29d835b28 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx @@ -1,7 +1,7 @@ import { makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { Doc, DocListCast, Field, FieldResult } from '../../../../fields/Doc'; +import { Doc, DocListCast, FieldType, FieldResult } from '../../../../fields/Doc'; import { InkTool } from '../../../../fields/InkField'; import { StrCast } from '../../../../fields/Types'; import { DocumentManager } from '../../../util/DocumentManager'; @@ -10,13 +10,14 @@ import { ObservableReactComponent } from '../../ObservableReactComponent'; import { DocButtonState, DocumentLinksButton } from '../../nodes/DocumentLinksButton'; import { TopBar } from '../../topbar/TopBar'; import { CollectionFreeFormInfoState, InfoState, StateEntryFunc, infoState } from './CollectionFreeFormInfoState'; -import { CollectionFreeFormView } from './CollectionFreeFormView'; import './CollectionFreeFormView.scss'; +import { DocData } from '../../../../fields/DocSymbols'; export interface CollectionFreeFormInfoUIProps { Document: Doc; - Freeform: CollectionFreeFormView; - close: () => boolean; + LayoutDoc: Doc; + childDocs: () => Doc[]; + close: () => void; } @observer @@ -32,10 +33,10 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent (this._currState = val)); } // prettier-ignore + set currState(val) { runInAction(() => {this._currState = val;}); } // prettier-ignore componentWillUnmount(): void { - this._props.Freeform.dataDoc.backgroundColor = this._originalbackground; + this._props.Document[DocData].backgroundColor = this._originalbackground; } setCurrState = (state: infoState) => { @@ -46,16 +47,16 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent { - this._originalbackground = StrCast(this._props.Freeform.dataDoc.backgroundColor); + this._originalbackground = StrCast(this._props.Document[DocData].backgroundColor); // state entry functions - const setBackground = (colour: string) => () => (this._props.Freeform.dataDoc.backgroundColor = colour); - const setOpacity = (opacity: number) => () => (this._props.Freeform.layoutDoc.opacity = opacity); + const setBackground = (colour: string) => () => {this._props.Document[DocData].backgroundColor = colour;} // prettier-ignore + const setOpacity = (opacity: number) => () => {this._props.LayoutDoc.opacity = opacity;} // prettier-ignore // arc transition trigger conditions - const firstDoc = () => (this._props.Freeform.childDocs.length ? this._props.Freeform.childDocs[0] : undefined); - const numDocs = () => this._props.Freeform.childDocs.length; + const firstDoc = () => (this._props.childDocs().length ? this._props.childDocs()[0] : undefined); + const numDocs = () => this._props.childDocs().length; - let docX: FieldResult; - let docY: FieldResult; + let docX: FieldResult; + let docY: FieldResult; const docNewX = () => firstDoc()?.x; const docNewY = () => firstDoc()?.y; @@ -244,12 +245,12 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent presentationMode() === 'edit', () => editPresentationMode], manualPresentation: [() => presentationMode() === 'manual', () => manualPresentationMode], docRemoved: [() => numDocs() < 3, () => viewedLink], - docCreated: [() => numDocs() == 4, () => completed], + docCreated: [() => numDocs() === 4, () => completed], }); const completed = InfoState( 'Eager to learn more? Click the ? icon in the top right corner to read our full documentation.', - { docRemoved: [() => numDocs() == 1, () => oneDoc] }, + { docRemoved: [() => numDocs() === 1, () => oneDoc] }, 'documentation.png', () => TopBar.Instance.FlipDocumentationIcon() ); // prettier-ignore diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index c83c26509..a0b96c75a 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -1,4 +1,4 @@ -import { Doc, Field, FieldResult } from '../../../../fields/Doc'; +import { Doc, Field, FieldType, FieldResult } from '../../../../fields/Doc'; import { Id, ToString } from '../../../../fields/FieldSymbols'; import { ObjectField } from '../../../../fields/ObjectField'; import { RefField } from '../../../../fields/RefField'; @@ -50,7 +50,7 @@ export interface ViewDefResult { bounds?: ViewDefBounds; inkMask?: number; //sort elements into either the mask layer (which has a mixedBlendMode appropriate for transparent masks), or the regular documents layer; -1 = no mask, 0 = mask layer but stroke is transparent (hidden, as in during a presentation when you want to smoothly animate it into being a mask), >0 = mask layer and not hidden } -function toLabel(target: FieldResult) { +function toLabel(target: FieldResult) { if (typeof target === 'number' || Number(target)) { const truncated = Number(Number(target).toFixed(0)); const precise = Number(Number(target).toFixed(2)); @@ -128,7 +128,7 @@ export function computeStarburstLayout(poolData: Map, pivotDoc export function computePivotLayout(poolData: Map, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) { const docMap = new Map(); const fieldKey = 'data'; - const pivotColumnGroups = new Map, PivotColumn>(); + const pivotColumnGroups = new Map, PivotColumn>(); let nonNumbers = 0; const pivotFieldKey = toLabel(engineProps?.pivotField ?? pivotDoc._pivotField) || 'author'; @@ -136,10 +136,10 @@ export function computePivotLayout(poolData: Map, pivotDoc: Do const listValue = Cast(pair.layout[pivotFieldKey], listSpec('string'), null); const num = toNumber(pair.layout[pivotFieldKey]); - if (num === undefined || Number.isNaN(num)) { + if (num === undefined || isNaN(num)) { nonNumbers++; } - const val = Field.toString(pair.layout[pivotFieldKey] as Field); + const val = Field.toString(pair.layout[pivotFieldKey] as FieldType); if (listValue) { listValue.forEach((val, i) => { !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val], replicas: [] }); @@ -265,7 +265,7 @@ export function computePivotLayout(poolData: Map, pivotDoc: Do return normalizeResults(panelDim, max_text, docMap, poolData, viewDefsToJSX, groupNames, 0, []); } -function toNumber(val: FieldResult) { +function toNumber(val: FieldResult) { return val === undefined ? undefined : NumCast(val, Number(StrCast(val))); } @@ -274,7 +274,7 @@ export function computeTimelineLayout(poolData: Map, pivotDoc: const pivotDateGroups = new Map(); const docMap = new Map(); const groupNames: ViewDefBounds[] = []; - const timelineFieldKey = Field.toString(pivotDoc._pivotField as Field); + const timelineFieldKey = Field.toString(pivotDoc._pivotField as FieldType); const curTime = toNumber(pivotDoc[fieldKey + '-timelineCur']); const curTimeSpan = Cast(pivotDoc[fieldKey + '-timelineSpan'], 'number', null); const minTimeReq = curTimeSpan === undefined ? Cast(pivotDoc[fieldKey + '-timelineMinReq'], 'number', null) : curTime && curTime - curTimeSpan; @@ -290,7 +290,7 @@ export function computeTimelineLayout(poolData: Map, pivotDoc: let maxTime = maxTimeReq === undefined ? -Number.MAX_VALUE : maxTimeReq; childPairs.forEach(pair => { const num = NumCast(pair.layout[timelineFieldKey], Number(StrCast(pair.layout[timelineFieldKey]))); - if (!Number.isNaN(num) && (!minTimeReq || num >= minTimeReq) && (!maxTimeReq || num <= maxTimeReq)) { + if (!isNaN(num) && (!minTimeReq || num >= minTimeReq) && (!maxTimeReq || num <= maxTimeReq)) { !pivotDateGroups.get(num) && pivotDateGroups.set(num, []); pivotDateGroups.get(num)!.push(pair.layout); minTime = Math.min(num, minTime); @@ -400,7 +400,7 @@ function normalizeResults( const height = aggBounds.b - aggBounds.y === 0 ? 1 : aggBounds.b - aggBounds.y; const wscale = panelDim[0] / width; let scale = wscale * height > panelDim[1] ? panelDim[1] / height : wscale; - if (Number.isNaN(scale)) scale = 1; + if (isNaN(scale)) scale = 1; Array.from(docMap.entries()) .filter(ele => ele[1].pair) diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx index 69cbae86f..707f6c198 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx @@ -4,26 +4,28 @@ import * as React from 'react'; import { Doc } from '../../../../fields/Doc'; import { ScriptField } from '../../../../fields/ScriptField'; import { PresBox } from '../../nodes/trails/PresBox'; -import { CollectionFreeFormView } from './CollectionFreeFormView'; import './CollectionFreeFormView.scss'; +import { ObservableReactComponent } from '../../ObservableReactComponent'; + export interface CollectionFreeFormPannableContentsProps { Document: Doc; viewDefDivClick?: ScriptField; children?: React.ReactNode | undefined; transition?: string; isAnnotationOverlay: boolean | undefined; + showPresPaths: () => boolean; transform: () => string; brushedView: () => { panX: number; panY: number; width: number; height: number } | undefined; } @observer -export class CollectionFreeFormPannableContents extends React.Component { +export class CollectionFreeFormPannableContents extends ObservableReactComponent { constructor(props: CollectionFreeFormPannableContentsProps) { super(props); makeObservable(this); } @computed get presPaths() { - return CollectionFreeFormView.ShowPresPaths ? PresBox.Instance.pathLines(this.props.Document) : null; + return this._props.showPresPaths() ? PresBox.Instance.pathLines(this._props.Document) : null; } // rectangle highlight used when following trail/link to a region of a collection that isn't a document showViewport = (viewport: { panX: number; panY: number; width: number; height: number } | undefined) => @@ -42,7 +44,7 @@ export class CollectionFreeFormPannableContents extends React.Component { const target = e.target as any; if (getComputedStyle(target)?.overflow === 'visible') { @@ -50,13 +52,13 @@ export class CollectionFreeFormPannableContents extends React.Component {this.props.children} {this.presPaths} - {this.showViewport(this.props.brushedView())} + {this.showViewport(this._props.brushedView())}
  • ); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx index fa8218bdd..35394016d 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx @@ -9,17 +9,17 @@ import { Id } from '../../../../fields/FieldSymbols'; import { List } from '../../../../fields/List'; import { listSpec } from '../../../../fields/Schema'; import { Cast } from '../../../../fields/Types'; -import { CollectionViewProps } from '../CollectionView'; +import { CollectionViewProps } from '../CollectionSubView'; import './CollectionFreeFormView.scss'; @observer export class CollectionFreeFormRemoteCursors extends React.Component { @computed protected get cursors(): CursorField[] { - const doc = this.props.Document; + const { Document } = this.props; let cursors: FieldResult>; const id = Doc.UserDoc()[Id]; - if (!id || !(cursors = Cast(doc.cursors, listSpec(CursorField)))) { + if (!id || !(cursors = Cast(Document.cursors, listSpec(CursorField)))) { return []; } const now = mobxUtils.now(); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 3fab00968..a70713429 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,14 +1,17 @@ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +/* eslint-disable jsx-a11y/no-static-element-interactions */ import { Bezier } from 'bezier-js'; import { Colors } from 'browndash-components'; import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { computedFn } from 'mobx-utils'; import * as React from 'react'; +import { ClientUtils, DashColor, lightOrDark, OmitKeys, returnFalse, returnZero, setupMoveUpEvents } from '../../../../ClientUtils'; import { DateField } from '../../../../fields/DateField'; -import { Doc, DocListCast, Field, Opt } from '../../../../fields/Doc'; +import { Doc, DocListCast, Field, FieldType, Opt } from '../../../../fields/Doc'; import { DocData, Height, Width } from '../../../../fields/DocSymbols'; import { Id } from '../../../../fields/FieldSymbols'; -import { InkData, InkField, InkTool, PointData, Segment } from '../../../../fields/InkField'; +import { InkData, InkField, InkTool, Segment } from '../../../../fields/InkField'; import { List } from '../../../../fields/List'; import { RichTextField } from '../../../../fields/RichTextField'; import { listSpec } from '../../../../fields/Schema'; @@ -16,13 +19,15 @@ import { ScriptField } from '../../../../fields/ScriptField'; import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; import { ImageField } from '../../../../fields/URLField'; import { TraceMobx } from '../../../../fields/util'; +import { Gestures, PointData } from '../../../../pen-gestures/GestureTypes'; import { GestureUtils } from '../../../../pen-gestures/GestureUtils'; -import { aggregateBounds, DashColor, emptyFunction, intersectRect, lightOrDark, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils'; +import { aggregateBounds, emptyFunction, intersectRect, Utils } from '../../../../Utils'; import { CognitiveServices } from '../../../cognitive_services/CognitiveServices'; import { Docs, DocUtils } from '../../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; import { DocumentManager } from '../../../util/DocumentManager'; -import { DragManager, dropActionType } from '../../../util/DragManager'; +import { DragManager } from '../../../util/DragManager'; +import { dropActionType } from '../../../util/DropActionTypes'; import { ReplayMovements } from '../../../util/ReplayMovements'; import { CompileScript } from '../../../util/Scripting'; import { ScriptingGlobals } from '../../../util/ScriptingGlobals'; @@ -33,8 +38,8 @@ import { Transform } from '../../../util/Transform'; import { undoable, undoBatch, UndoManager } from '../../../util/UndoManager'; import { Timeline } from '../../animationtimeline/Timeline'; import { ContextMenu } from '../../ContextMenu'; +import { PinProps } from '../../DocComponent'; import { GestureOverlay } from '../../GestureOverlay'; -import { CtrlKey } from '../../GlobalKeyHandler'; import { ActiveInkWidth, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke'; import { LightboxView } from '../../LightboxView'; import { CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView'; @@ -42,7 +47,7 @@ import { SchemaCSVPopUp } from '../../nodes/DataVizBox/SchemaCSVPopUp'; import { DocumentView, OpenWhere } from '../../nodes/DocumentView'; import { FieldViewProps, FocusViewOptions } from '../../nodes/FieldView'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; -import { PinProps, PresBox } from '../../nodes/trails/PresBox'; +import { PresBox } from '../../nodes/trails/PresBox'; import { CreateImage } from '../../nodes/WebBoxRenderer'; import { StyleProp } from '../../StyleProvider'; import { CollectionSubView } from '../CollectionSubView'; @@ -77,7 +82,7 @@ export class CollectionFreeFormView extends CollectionSubView { switch (ge.gesture) { default: - case GestureUtils.Gestures.Line: - case GestureUtils.Gestures.Circle: - case GestureUtils.Gestures.Rectangle: - case GestureUtils.Gestures.Triangle: - case GestureUtils.Gestures.Stroke: + case Gestures.Line: + case Gestures.Circle: + case Gestures.Rectangle: + case Gestures.Triangle: + case Gestures.Stroke: const points = ge.points; const B = this.screenToFreeformContentsXf.transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height); const inkWidth = ActiveInkWidth() * this.ScreenToLocalBoxXf().Scale; @@ -660,7 +665,7 @@ export class CollectionFreeFormView extends CollectionSubView doc.type === DocumentType.INK) .map(i => { @@ -672,7 +677,7 @@ export class CollectionFreeFormView extends CollectionSubView {}); break; - case GestureUtils.Gestures.Text: + case Gestures.Text: if (ge.text) { const B = this.screenToFreeformContentsXf.transformPoint(ge.points[0].X, ge.points[0].Y); this.addDocument(Docs.Create.TextDocument(ge.text, { title: ge.text, x: B[0], y: B[1] })); @@ -690,7 +695,7 @@ export class CollectionFreeFormView extends CollectionSubView { if (this._lightboxDoc) this._lightboxDoc = undefined; - if (Utils.isClick(e.pageX, e.pageY, this._downX, this._downY, this._downTime)) { + if (ClientUtils.isClick(e.pageX, e.pageY, this._downX, this._downY, this._downTime)) { if (this.onBrowseClickHandler()) { this.onBrowseClickHandler().script.run({ documentView: this.DocumentView?.(), clientX: e.clientX, clientY: e.clientY }); e.stopPropagation(); @@ -746,7 +751,7 @@ export class CollectionFreeFormView extends CollectionSubView this.forceStrokeGesture( e, - GestureUtils.Gestures.Stroke, + Gestures.Stroke, segment.reduce((data, curve) => [...data, ...curve.points.map(p => intersect.inkView.ComponentView?.ptToScreen?.({ X: p.x, Y: p.y }) ?? { X: 0, Y: 0 })], [] as PointData[]) ) ); @@ -759,7 +764,7 @@ export class CollectionFreeFormView extends CollectionSubView { + forceStrokeGesture = (e: PointerEvent, gesture: Gestures, points: InkData, text?: any) => { this.onGesture(e, new GestureUtils.GestureEvent(gesture, points, GestureOverlay.getBounds(points), text)); }; @@ -956,7 +961,7 @@ export class CollectionFreeFormView extends CollectionSubView 2 && this.layoutDoc.layout_scrollTop === undefined && NumCast(this.layoutDoc._freeform_scale, minScale) === minScale) { const maxPanY = minPanY + fitYscroll; const relTop = (panY - minPanY) / (maxPanY - minPanY); @@ -1289,8 +1291,8 @@ export class CollectionFreeFormView extends CollectionSubView (Doc.IsInfoUIDisabled = true); - infoUI = () => (Doc.IsInfoUIDisabled || this.Document.annotationOn || this._props.renderDepth ? null : ); + childDocsFunc = () => this.childDocs; + @action closeInfo = () => { Doc.IsInfoUIDisabled = true }; // prettier-ignore + infoUI = () => (Doc.IsInfoUIDisabled || this.Document.annotationOn || this._props.renderDepth ? null : ); componentDidMount() { this._props.setContentViewBox?.(this); @@ -1483,6 +1486,7 @@ export class CollectionFreeFormView extends CollectionSubView this.doInternalLayoutComputation, - computation => (this._layoutElements = this.doLayoutComputation(computation.newPool, computation.computedElementData)), + computation => { + this._layoutElements = this.doLayoutComputation(computation.newPool, computation.computedElementData); + }, { fireImmediately: true } ); } @@ -1512,7 +1518,7 @@ export class CollectionFreeFormView extends CollectionSubView { - const returnedFilename = await Utils.convertDataUri(data_url, filename, noSuffix, replaceRootFilename); + return CreateImage(ClientUtils.prepend(''), document.styleSheets, htmlString, nativeWidth, (nativeWidth * panelHeight) / panelWidth, (scrollTop * panelHeight) / realNativeHeight) + .then(async (dataUrl: any) => { + const returnedFilename = await ClientUtils.convertDataUri(dataUrl, filename, noSuffix, replaceRootFilename); cb(returnedFilename as string, nativeWidth, nativeHeight); }) - .catch(function (error: any) { - console.error('oops, something went wrong!', error); - }); + .catch((error: any) => console.error('oops, something went wrong!', error)); } componentWillUnmount() { @@ -1583,14 +1587,15 @@ export class CollectionFreeFormView extends CollectionSubView { + onCursorMove = () => { // super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY)); }; @undoBatch promoteCollection = () => { const childDocs = this.childDocs.slice(); - childDocs.forEach(doc => { + childDocs.forEach(docIn => { + const doc = docIn; const scr = this.screenToFreeformContentsXf.inverse().transformPoint(NumCast(doc.x), NumCast(doc.y)); doc.x = scr?.[0]; doc.y = scr?.[1]; @@ -1604,7 +1609,8 @@ export class CollectionFreeFormView extends CollectionSubView NumCast(doc._width))) + 20; const height = Math.max(...docs.map(doc => NumCast(doc._height))) + 20; const dim = Math.ceil(Math.sqrt(docs.length)); - docs.forEach((doc, i) => { + docs.forEach((docIn, i) => { + const doc = docIn; doc.x = NumCast(this.Document[this.panXFieldKey]) + (i % dim) * width - (width * dim) / 2; doc.y = NumCast(this.Document[this.panYFieldKey]) + Math.floor(i / dim) * height - (height * dim) / 2; }); @@ -1704,14 +1710,14 @@ export class CollectionFreeFormView extends CollectionSubView doc.z === undefined && isDocInView(doc, selRect)); // first see if there are any foreground docs to snap to activeDocs - .filter(doc => doc.isGroup && SnappingManager.IsResizing !== doc && !DragManager.docsBeingDragged.includes(doc)) + .filter(doc => doc.isGroup && SnappingManager.IsResizing !== doc[Id] && !DragManager.docsBeingDragged.includes(doc)) .forEach(doc => DocumentManager.Instance.getDocumentView(doc)?.ComponentView?.dragStarting?.(snapToDraggedDoc, false, visited)); const horizLines: number[] = []; const vertLines: number[] = []; const invXf = this.screenToFreeformContentsXf.inverse(); snappableDocs - .filter(doc => !doc.isGroup && (snapToDraggedDoc || (SnappingManager.IsResizing !== doc && !DragManager.docsBeingDragged.includes(doc)))) + .filter(doc => !doc.isGroup && (snapToDraggedDoc || (SnappingManager.IsResizing !== doc[Id] && !DragManager.docsBeingDragged.includes(doc)))) .forEach(doc => { const { left, top, width, height } = docDims(doc); const topLeftInScreen = invXf.transformPoint(left, top); @@ -1727,10 +1733,10 @@ export class CollectionFreeFormView extends CollectionSubView { if (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView?.())) { - const layout_unrendered = this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id])); + const layoutUnrendered = this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id])); const loadIncrement = this.Document.isTemplateDoc ? Number.MAX_VALUE : 5; - for (var i = 0; i < Math.min(layout_unrendered.length, loadIncrement); i++) { - this._renderCutoffData.set(layout_unrendered[i][Id] + '', true); + for (let i = 0; i < Math.min(layoutUnrendered.length, loadIncrement); i++) { + this._renderCutoffData.set(layoutUnrendered[i][Id] + '', true); } } this.childDocs.some(doc => !this._renderCutoffData.get(doc[Id])) && setTimeout(this.incrementalRender, 1); @@ -1744,6 +1750,7 @@ export class CollectionFreeFormView extends CollectionSubView CollectionFreeFormView.ShowPresPaths; brushedView = () => this._brushedView; gridColor = () => DashColor(lightOrDark(this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor))) @@ -1776,6 +1783,7 @@ export class CollectionFreeFormView extends CollectionSubView {this.props.children ?? null} {/* most likely case of children is document content that's being annoated: eg., an image */} @@ -1828,7 +1836,7 @@ export class CollectionFreeFormView extends CollectionSubView { this._brushedView = { ...viewport, panX: viewport.panX - viewport.width / 2, panY: viewport.panY - viewport.height / 2 }; - this._brushtimer = setTimeout(action(() => (this._brushedView = undefined)), holdTime); // prettier-ignore + this._brushtimer = setTimeout(action(() => { this._brushedView = undefined; }), holdTime); // prettier-ignore }), transTime + 1 ); @@ -1912,6 +1920,7 @@ export class CollectionFreeFormView extends CollectionSubView ViewDefResult[] }> { render() { + // eslint-disable-next-line react/destructuring-assignment return this.props.elements().filter(ele => ele.bounds?.z).map(ele => ele.ele); // prettier-ignore } } @@ -1923,11 +1932,12 @@ export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY DocumentManager.Instance.showDocument(dv.Document, { zoomScale: 0.8, willZoomCentered: true }, (focused: boolean) => { if (!focused) { const selfFfview = !dv.Document.isGroup && dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined; - let containers = dv.containerViewPath?.() ?? []; + const containers = dv.containerViewPath?.() ?? []; let parFfview = dv.CollectionFreeFormView; - for (var cont of containers) { + containers.forEach(cont => { parFfview = parFfview ?? cont.CollectionFreeFormView; - } + }); + while (parFfview?.Document.isGroup) parFfview = parFfview.DocumentView?.().CollectionFreeFormView; const ffview = selfFfview && selfFfview.layoutDoc[selfFfview.scaleFieldKey] !== 0.5 ? selfFfview : parFfview; // if focus doc is a freeform that is not at it's default 0.5 scale, then zoom out on it. Otherwise, zoom out on the parent ffview ffview?.zoomSmoothlyAboutPt(ffview.screenToFreeformContentsXf.transformPoint(clientX, clientY), ffview?.isAnnotationOverlay ? 1 : 0.5, browseTransitionTime); @@ -1936,17 +1946,21 @@ export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY }); } ScriptingGlobals.add(CollectionBrowseClick); +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) { !readOnly && (SelectionManager.Views[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame(); }); +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function prevKeyFrame(readOnly: boolean) { !readOnly && (SelectionManager.Views[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame(true); }); +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function curKeyFrame(readOnly: boolean) { const selView = SelectionManager.Views; if (readOnly) return selView[0].ComponentView?.getKeyFrameEditing?.() ? Colors.MEDIUM_BLUE : 'transparent'; runInAction(() => selView[0].ComponentView?.setKeyFrameEditing?.(!selView[0].ComponentView?.getKeyFrameEditing?.())); }); +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function pinWithView(pinContent: boolean) { SelectionManager.Views.forEach(view => view._props.pinToPres(view.Document, { @@ -1959,15 +1973,19 @@ ScriptingGlobals.add(function pinWithView(pinContent: boolean) { }) ); }); +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function bringToFront() { SelectionManager.Views.forEach(view => view.CollectionFreeFormView?.bringToFront(view.Document)); }); -ScriptingGlobals.add(function sendToBack(doc: Doc) { +// eslint-disable-next-line prefer-arrow-callback +ScriptingGlobals.add(function sendToBack() { SelectionManager.Views.forEach(view => view.CollectionFreeFormView?.bringToFront(view.Document, true)); }); -ScriptingGlobals.add(function datavizFromSchema(doc: Doc) { +// eslint-disable-next-line prefer-arrow-callback +ScriptingGlobals.add(function datavizFromSchema() { // creating a dataviz doc to represent the schema table - SelectionManager.Views.forEach(view => { + SelectionManager.Views.forEach(viewIn => { + const view = viewIn; if (!view.layoutDoc.schema_columnKeys) { view.layoutDoc.schema_columnKeys = new List(['title', 'type', 'author', 'author_date']); } @@ -1975,13 +1993,13 @@ ScriptingGlobals.add(function datavizFromSchema(doc: Doc) { if (!keys) return; const children = DocListCast(view.Document[Doc.LayoutFieldKey(view.Document)]); - let csvRows = []; + const csvRows = []; csvRows.push(keys.join(',')); for (let i = 0; i < children.length; i++) { - let eachRow = []; + const eachRow = []; for (let j = 0; j < keys.length; j++) { - var cell = children[i][keys[j]]?.toString(); - if (cell) cell = cell.toString().replace(/\,/g, ''); + let cell = children[i][keys[j]]?.toString(); + if (cell) cell = cell.toString().replace(/,/g, ''); eachRow.push(cell); } csvRows.push(eachRow); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 6eca91e9d..b03e435ce 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -1,7 +1,9 @@ +/* eslint-disable jsx-a11y/no-static-element-interactions */ import { action, computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { Utils, intersectRect, lightOrDark, returnFalse } from '../../../../Utils'; +import { ClientUtils, lightOrDark, returnFalse } from '../../../../ClientUtils'; +import { intersectRect } from '../../../../Utils'; import { Doc, Opt } from '../../../../fields/Doc'; import { AclAdmin, AclAugment, AclEdit, DocData } from '../../../../fields/DocSymbols'; import { Id } from '../../../../fields/FieldSymbols'; @@ -20,6 +22,7 @@ import { SnappingManager } from '../../../util/SnappingManager'; import { Transform } from '../../../util/Transform'; import { UndoManager, undoBatch } from '../../../util/UndoManager'; import { ContextMenu } from '../../ContextMenu'; +import { MarqueeViewBounds } from '../../DocComponent'; import { ObservableReactComponent } from '../../ObservableReactComponent'; import { PreviewCursor } from '../../PreviewCursor'; import { OpenWhere } from '../../nodes/DocumentView'; @@ -28,6 +31,7 @@ import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; import { SubCollectionViewProps } from '../CollectionSubView'; import { MarqueeOptionsMenu } from './MarqueeOptionsMenu'; import './MarqueeView.scss'; + interface MarqueeViewProps { getContainerTransform: () => Transform; getTransform: () => Transform; @@ -44,13 +48,6 @@ interface MarqueeViewProps { slowLoadDocuments: (files: File[] | string, options: DocumentOptions, generatedDocuments: Doc[], text: string, completed: ((doc: Doc[]) => void) | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => Promise; } -export interface MarqueeViewBounds { - left: number; - top: number; - width: number; - height: number; -} - @observer export class MarqueeView extends ObservableReactComponent { public static CurViewBounds(pinDoc: Doc, panelWidth: number, panelHeight: number) { @@ -102,7 +99,7 @@ export class MarqueeView extends ObservableReactComponent { - //make textbox and add it to this collection + // make textbox and add it to this collection // tslint:disable-next-line:prefer-const const cm = ContextMenu.Instance; const [x, y] = this.Transform.transformPoint(this._downX, this._downY); @@ -141,7 +138,7 @@ export class MarqueeView extends ObservableReactComponent { + ns.forEach(line => { const indent = line.search(/\S|$/); const newBox = Docs.Create.TextDocument(line, { _width: 200, _height: 35, x: x + (indent / 3) * 10, y: ypos, title: line }); this._props.addDocument?.(newBox); @@ -155,7 +152,7 @@ export class MarqueeView extends ObservableReactComponent { error && console.log(error); data && - Utils.convertDataUri(data, this._props.Document[Id] + '-thumb-frozen').then(returnedfilename => { + ClientUtils.convertDataUri(data, this._props.Document[Id] + '-thumb-frozen').then(returnedfilename => { this._props.Document['thumb-frozen'] = new ImageField(returnedfilename); }); }) @@ -169,7 +166,7 @@ export class MarqueeView extends ObservableReactComponent { const text: string = await navigator.clipboard.readText(); @@ -184,7 +181,7 @@ export class MarqueeView extends ObservableReactComponent { if (this._props.pointerEvents?.() === 'none') return; - if (Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, Date.now())) { + if (ClientUtils.isClick(e.clientX, e.clientY, this._downX, this._downY, Date.now())) { if (Doc.ActiveTool === InkTool.None) { if (!this._props.trySelectCluster(e.shiftKey)) { !SnappingManager.ExploreMode && this.setPreviewCursor(e.clientX, e.clientY, false, false, this._props.Document); @@ -335,15 +332,21 @@ export class MarqueeView extends ObservableReactComponent (this._visible = true); + showMarquee = () => { + this._visible = true; + }; @action - hideMarquee = () => (this._visible = false); + hideMarquee = () => { + this._visible = false; + }; @undoBatch delete = action((e?: React.PointerEvent | KeyboardEvent | undefined, hide?: boolean) => { const selected = this.marqueeSelect(false); SelectionManager.DeselectAll(); - selected.forEach(doc => (hide ? (doc.hidden = true) : this._props.removeDocument?.(doc))); + selected.forEach(doc => { + hide ? (doc.hidden = true) : this._props.removeDocument?.(doc); + }); this.cleanupInteractions(false); MarqueeOptionsMenu.Instance.fadeOut(true); @@ -540,6 +543,7 @@ export class MarqueeView extends ObservableReactComponent tl[0] && truePoint[0] < r1.left && truePoint[1] > r1.top && truePoint[1] < r1.top + r1.height); @@ -662,6 +667,7 @@ export class MarqueeView extends ObservableReactComponent { @@ -673,7 +679,9 @@ export class MarqueeView extends ObservableReactComponent e.preventDefault()} - onScroll={e => (e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0)} + onScroll={e => { + e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0; + }} onClick={this.onClick} onPointerDown={this.onPointerDown}> {this._visible ? this.marqueeDiv : null} diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx index f25872c2b..ab6788e6f 100644 --- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx +++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx @@ -4,7 +4,8 @@ import * as React from 'react'; import { Doc, Opt } from '../../../../fields/Doc'; import { Id } from '../../../../fields/FieldSymbols'; import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; -import { emptyFunction, returnFalse, returnZero, setupMoveUpEvents } from '../../../../Utils'; +import { emptyFunction } from '../../../../Utils'; +import { returnFalse, returnZero, setupMoveUpEvents } from '../../../../ClientUtils'; import { Docs } from '../../../documents/Documents'; import { DragManager } from '../../../util/DragManager'; import { Transform } from '../../../util/Transform'; diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx index 228af78aa..6635ab222 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx @@ -1,10 +1,13 @@ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@mui/material'; import { Toggle, ToggleType, Type } from 'browndash-components'; import { IReactionDisposer, action, makeObservable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { Utils, emptyFunction, returnEmptyDoclist, returnTrue } from '../../../../Utils'; +import { ClientUtils, returnTrue } from '../../../../ClientUtils'; +import { emptyFunction } from '../../../../Utils'; import { Doc, Opt } from '../../../../fields/Doc'; import { Height, Width } from '../../../../fields/DocSymbols'; import { Id } from '../../../../fields/FieldSymbols'; @@ -46,13 +49,15 @@ export class CollectionLinearView extends CollectionSubView() { this._dropDisposer?.(); this._widthDisposer?.(); this._selectedDisposer?.(); - this.childLayoutPairs.map((pair, ind) => ScriptCast(DocCast(pair.layout.proto)?.onPointerUp)?.script.run({ this: pair.layout.proto }, console.log)); + this.childLayoutPairs.map(pair => ScriptCast(DocCast(pair.layout.proto)?.onPointerUp)?.script.run({ this: pair.layout.proto }, console.log)); } componentDidMount() { this._widthDisposer = reaction( () => 5 + NumCast(this.dataDoc.linearBtnWidth, this.dimension()) + (this.layoutDoc.linearView_IsOpen ? this.childDocs.filter(doc => !doc.hidden).reduce((tot, doc) => (NumCast(doc._width) || this.dimension()) + tot + 4, 0) : 0), - width => this.childDocs.length && (this.layoutDoc._width = width), + width => { + this.childDocs.length && (this.layoutDoc._width = width); + }, { fireImmediately: true } ); } @@ -64,14 +69,14 @@ export class CollectionLinearView extends CollectionSubView() { dimension = () => NumCast(this.layoutDoc._height); getTransform = (ele: Opt) => { if (!ele) return Transform.Identity(); - const { scale, translateX, translateY } = Utils.GetScreenTransform(ele); + const { translateX, translateY } = ClientUtils.GetScreenTransform(ele); return new Transform(-translateX, -translateY, 1); }; @action exitLongLinks = () => { if (DocumentLinksButton.StartLink?.Document) { - action((e: React.PointerEvent) => Doc.UnBrushDoc(DocumentLinksButton.StartLink?.Document as Doc)); + action(() => Doc.UnBrushDoc(DocumentLinksButton.StartLink?.Document as Doc)); } DocumentLinksButton.StartLink = undefined; DocumentLinksButton.StartLinkView = undefined; @@ -97,8 +102,8 @@ export class CollectionLinearView extends CollectionSubView() { e.preventDefault(); }; - getLinkUI = () => { - return !DocumentLinksButton.StartLink ? null : ( + getLinkUI = () => + !DocumentLinksButton.StartLink ? null : ( e.stopPropagation()}> Creating link from:{' '} @@ -108,7 +113,7 @@ export class CollectionLinearView extends CollectionSubView() { - {'Toggle description pop-up'}
    } placement="top"> + Toggle description pop-up
    } placement="top"> Labels: {LinkDescriptionPopup.Instance.showDescriptions ? LinkDescriptionPopup.Instance.showDescriptions : 'ON'} @@ -121,9 +126,8 @@ export class CollectionLinearView extends CollectionSubView() { ); - }; - getCurrentlyPlayingUI = () => { - return !CollectionStackedTimeline.CurrentlyPlaying?.length ? null : ( + getCurrentlyPlayingUI = () => + !CollectionStackedTimeline.CurrentlyPlaying?.length ? null : ( Currently playing: @@ -139,7 +143,6 @@ export class CollectionLinearView extends CollectionSubView() { ); - }; getDisplayDoc = (doc: Doc, preview: boolean = false) => { // hack to avoid overhead of making UndoStack,etc into DocumentView style Boxes. If the UndoStack is ever intended to become part of the persisten state of the dashboard, then this would have to change. @@ -149,6 +152,7 @@ export class CollectionLinearView extends CollectionSubView() { case '': return this.getCurrentlyPlayingUI(); case '': return ; case '': return Doc.UserDoc().isBranchingMode ? : null; + default: } const nested = doc._type_collection === CollectionViewType.Linear; @@ -161,7 +165,9 @@ export class CollectionLinearView extends CollectionSubView() {
    (dref = r || undefined)} + ref={r => { + dref = r || undefined; + }} style={{ pointerEvents: 'all', width: NumCast(doc._width), @@ -194,7 +200,7 @@ export class CollectionLinearView extends CollectionSubView() { childFilters={this._props.childFilters} childFiltersByRanges={this._props.childFiltersByRanges} searchFilterDocs={this._props.searchFilterDocs} - hideResizeHandles={true} + hideResizeHandles />
    ); @@ -220,30 +226,26 @@ export class CollectionLinearView extends CollectionSubView() { ScriptCast(this.Document.onClick)?.script.run({ this: this.Document }, console.log); }} tooltip={isExpanded ? 'Close' : 'Open'} - fillWidth={true} - align={'center'} + fillWidth + align="center" /> ); return (
    - { - <> - {!this.layoutDoc.linearView_Expandable ? null : menuOpener} - {!this.layoutDoc.linearView_IsOpen ? null : ( -
    - {this.childLayoutPairs.map(pair => this.getDisplayDoc(pair.layout))} -
    - )} - - } + {!this.layoutDoc.linearView_Expandable ? null : menuOpener} + {!this.layoutDoc.linearView_IsOpen ? null : ( +
    + {this.childLayoutPairs.map(pair => this.getDisplayDoc(pair.layout))} +
    + )}
    ); diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index 6a956f2ac..46bf56dc8 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -3,16 +3,19 @@ import { Popup, PopupTrigger, Type } from 'browndash-components'; import { ObservableMap, action, computed, makeObservable, observable, observe } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { emptyFunction, returnEmptyDoclist, returnEmptyString, returnFalse, returnIgnore, returnNever, returnTrue, setupMoveUpEvents, smoothScroll } from '../../../../Utils'; -import { Doc, DocListCast, Field, NumListCast, Opt, StrListCast } from '../../../../fields/Doc'; +import { returnEmptyDoclist, returnEmptyString, returnFalse, returnIgnore, returnNever, returnTrue, setupMoveUpEvents, smoothScroll } from '../../../../ClientUtils'; +import { emptyFunction } from '../../../../Utils'; +import { Doc, DocListCast, Field, FieldType, NumListCast, Opt, StrListCast } from '../../../../fields/Doc'; import { DocData } from '../../../../fields/DocSymbols'; import { Id } from '../../../../fields/FieldSymbols'; import { List } from '../../../../fields/List'; import { listSpec } from '../../../../fields/Schema'; +import { ColumnType } from '../../../../fields/SchemaHeaderField'; import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types'; import { DocUtils, Docs, DocumentOptions, FInfo } from '../../../documents/Documents'; import { DocumentManager } from '../../../util/DocumentManager'; -import { DragManager, dropActionType } from '../../../util/DragManager'; +import { DragManager } from '../../../util/DragManager'; +import { dropActionType } from '../../../util/DropActionTypes'; import { SelectionManager } from '../../../util/SelectionManager'; import { SettingsManager } from '../../../util/SettingsManager'; import { undoBatch, undoable } from '../../../util/UndoManager'; @@ -30,17 +33,6 @@ import { SchemaColumnHeader } from './SchemaColumnHeader'; import { SchemaRowBox } from './SchemaRowBox'; const { SCHEMA_NEW_NODE_HEIGHT } = require('../../global/globalCssVariables.module.scss'); // prettier-ignore -export enum ColumnType { - Number, - String, - Boolean, - Date, - Image, - RTF, - Enumeration, - Any, -} - export const FInfotoColType: { [key: string]: ColumnType } = { string: ColumnType.String, number: ColumnType.Number, @@ -823,8 +815,8 @@ export class CollectionSchemaView extends CollectionSubView() { const docs = !field ? this.childDocs : [...this.childDocs].sort((docA, docB) => { - const aStr = Field.toString(docA[field] as Field); - const bStr = Field.toString(docB[field] as Field); + const aStr = Field.toString(docA[field] as FieldType); + const bStr = Field.toString(docB[field] as FieldType); var out = 0; if (aStr < bStr) out = -1; if (aStr > bStr) out = 1; diff --git a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx index 5f8b412be..2823e1936 100644 --- a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx +++ b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx @@ -2,7 +2,8 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { emptyFunction, setupMoveUpEvents } from '../../../../Utils'; +import { setupMoveUpEvents } from '../../../../ClientUtils'; +import { emptyFunction } from '../../../../Utils'; import { Colors } from '../../global/globalEnums'; import './CollectionSchemaView.scss'; diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx index 39fea2d2e..27a4493cb 100644 --- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx +++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx @@ -5,7 +5,8 @@ import { computedFn } from 'mobx-utils'; import * as React from 'react'; import { CgClose, CgLock, CgLockUnlock } from 'react-icons/cg'; import { FaExternalLinkAlt } from 'react-icons/fa'; -import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../../Utils'; +import { emptyFunction } from '../../../../Utils'; +import { returnFalse, setupMoveUpEvents } from '../../../../ClientUtils'; import { Doc } from '../../../../fields/Doc'; import { BoolCast } from '../../../../fields/Types'; import { DragManager } from '../../../util/DragManager'; diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx index bf36b2668..08eb35586 100644 --- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx +++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx @@ -7,15 +7,17 @@ import * as React from 'react'; import DatePicker from 'react-datepicker'; import 'react-datepicker/dist/react-datepicker.css'; import Select from 'react-select'; -import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../../Utils'; +import { ClientUtils, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../../ClientUtils'; +import { emptyFunction } from '../../../../Utils'; import { DateField } from '../../../../fields/DateField'; import { Doc, DocListCast, Field } from '../../../../fields/Doc'; import { RichTextField } from '../../../../fields/RichTextField'; +import { ColumnType } from '../../../../fields/SchemaHeaderField'; import { BoolCast, Cast, DateCast, DocCast, FieldValue, StrCast } from '../../../../fields/Types'; import { ImageField } from '../../../../fields/URLField'; import { FInfo, FInfoFieldType } from '../../../documents/Documents'; import { DocFocusOrOpen } from '../../../util/DocumentManager'; -import { dropActionType } from '../../../util/DragManager'; +import { dropActionType } from '../../../util/DropActionTypes'; import { SettingsManager } from '../../../util/SettingsManager'; import { SnappingManager } from '../../../util/SnappingManager'; import { Transform } from '../../../util/Transform'; @@ -28,7 +30,7 @@ import { OpenWhere, returnEmptyDocViewList } from '../../nodes/DocumentView'; import { FieldViewProps } from '../../nodes/FieldView'; import { KeyValueBox } from '../../nodes/KeyValueBox'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; -import { ColumnType, FInfotoColType } from './CollectionSchemaView'; +import { FInfotoColType } from './CollectionSchemaView'; import './CollectionSchemaView.scss'; export interface SchemaTableCellProps { @@ -204,7 +206,7 @@ export class SchemaImageCell extends ObservableReactComponent this.choosePath(url)); // access the primary layout data of the alternate documents const paths = field ? [this.choosePath(field.url), ...altpaths] : altpaths; // If there is a path, follow it; otherwise, follow a link to a default image icon - const url = paths.length ? paths : [Utils.CorsProxy('http://www.cs.brown.edu/~bcz/noImage.png')]; + const url = paths.length ? paths : [ClientUtils.CorsProxy('http://www.cs.brown.edu/~bcz/noImage.png')]; return url[0]; } -- cgit v1.2.3-70-g09d2 From 89e5b4e224d77c7a029ec7d9c9027095665508ac Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 19 Apr 2024 11:32:46 -0400 Subject: lint fixes. --- .gitignore | 1 - src/.DS_Store | Bin 10244 -> 10244 bytes src/ClientUtils.ts | 649 +++++++++++++++++++++ src/client/DocServer.ts | 10 +- src/client/documents/Documents.ts | 10 +- src/client/util/CurrentUserUtils.ts | 14 +- src/client/util/DocumentManager.ts | 2 +- src/client/util/GroupManager.tsx | 6 +- src/client/util/HypothesisUtils.ts | 2 +- src/client/util/SettingsManager.tsx | 4 +- src/client/util/SharingManager.tsx | 10 +- src/client/util/reportManager/ReportManager.tsx | 2 +- src/client/views/DashboardView.tsx | 6 +- src/client/views/MainView.tsx | 2 +- src/client/views/PropertiesView.tsx | 4 +- src/client/views/SidebarAnnos.tsx | 2 +- src/client/views/StyleProvider.tsx | 2 +- src/client/views/collections/CollectionSubView.tsx | 2 +- src/client/views/collections/TreeView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 2 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 18 +- .../formattedText/FormattedTextBoxComment.tsx | 2 +- .../formattedText/ProsemirrorExampleTransfer.ts | 4 +- .../views/nodes/formattedText/RichTextRules.ts | 4 +- src/client/views/nodes/formattedText/marks_rts.ts | 2 +- src/fields/Doc.ts | 75 ++- src/fields/util.ts | 10 +- 27 files changed, 744 insertions(+), 103 deletions(-) create mode 100644 src/ClientUtils.ts (limited to 'src/client/views/collections') diff --git a/.gitignore b/.gitignore index f841c1a86..6a1963b4e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ node_modules dist/ .DS_Store .env -ClientUtils.ts solr-8.3.1/server/logs/ solr-8.3.1/server/solr/dash/data/tlog/* solr-8.3.1/server/solr/dash/data/index/* diff --git a/src/.DS_Store b/src/.DS_Store index f8d745dbf..75cff7b55 100644 Binary files a/src/.DS_Store and b/src/.DS_Store differ diff --git a/src/ClientUtils.ts b/src/ClientUtils.ts new file mode 100644 index 000000000..57e0373c4 --- /dev/null +++ b/src/ClientUtils.ts @@ -0,0 +1,649 @@ +import * as Color from 'color'; +import * as React from 'react'; +import { ColorResult } from 'react-color'; +import * as rp from 'request-promise'; +import { numberRange, decimalToHexString } from './Utils'; +import { DocumentType } from './client/documents/DocumentTypes'; +import { Colors } from './client/views/global/globalEnums'; + +export function DashColor(color: string) { + try { + return color ? Color(color.toLowerCase()) : Color('transparent'); + } catch (e) { + if (color.includes('gradient')) console.log("using color 'white' in place of :" + color); + else console.log('COLOR error:', e); + return Color('white'); + } +} + +export function lightOrDark(color: any) { + if (color === 'transparent' || !color) return Colors.BLACK; + if (color.startsWith?.('linear')) return Colors.BLACK; + if (DashColor(color).isLight()) return Colors.BLACK; + return Colors.WHITE; +} + +export function returnTransparent() { + return 'transparent'; +} + +export function returnTrue() { + return true; +} + +export function returnIgnore(): 'ignore' { + return 'ignore'; +} +export function returnAlways(): 'always' { + return 'always'; +} +export function returnNever(): 'never' { + return 'never'; +} + +export function returnDefault(): 'default' { + return 'default'; +} + +export function return18() { + return 18; +} + +export function returnFalse() { + return false; +} + +export function returnAll(): 'all' { + return 'all'; +} + +export function returnNone(): 'none' { + return 'none'; +} + +export function returnVal(val1?: number, val2?: number) { + return val1 || (val2 !== undefined ? val2 : 0); +} + +export function returnOne() { + return 1; +} + +export function returnZero() { + return 0; +} + +export function returnEmptyString() { + return ''; +} + +export function returnEmptyFilter() { + return [] as string[]; +} + +export function returnEmptyDoclist() { + return [] as any[]; +} + +export namespace ClientUtils { + export const CLICK_TIME = 300; + export const DRAG_THRESHOLD = 4; + export const SNAP_THRESHOLD = 10; + let _currentUserEmail: string = ''; + export function CurrentUserEmail() { + return _currentUserEmail; + } + export function SetCurrentUserEmail(email: string) { + _currentUserEmail = email; + } + export function isClick(x: number, y: number, downX: number, downY: number, downTime: number) { + return Date.now() - downTime < ClientUtils.CLICK_TIME && Math.abs(x - downX) < ClientUtils.DRAG_THRESHOLD && Math.abs(y - downY) < ClientUtils.DRAG_THRESHOLD; + } + + export function cleanDocumentType(type: DocumentType) { + switch (type) { + case DocumentType.IMG: return 'Image'; + case DocumentType.AUDIO: return 'Audio'; + case DocumentType.COL: return 'Collection'; + case DocumentType.RTF: return 'Text'; + default: return type.charAt(0).toUpperCase() + type.slice(1); + } // prettier-ignore + } + + export function readUploadedFileAsText(inputFile: File) { + // eslint-disable-next-line no-undef + const temporaryFileReader = new FileReader(); + + return new Promise((resolve, reject) => { + temporaryFileReader.onerror = () => { + temporaryFileReader.abort(); + reject(new DOMException('Problem parsing input file.')); + }; + + temporaryFileReader.onload = () => { + resolve(temporaryFileReader.result); + }; + temporaryFileReader.readAsText(inputFile); + }); + } + + /** + * Uploads an image buffer to the server and stores with specified filename. by default the image + * is stored at multiple resolutions each retrieved by using the filename appended with _o, _s, _m, _l (indicating original, small, medium, or large) + * @param imageUri the bytes of the image + * @param returnedFilename the base filename to store the image on the server + * @param nosuffix optionally suppress creating multiple resolution images + */ + export async function convertDataUri(imageUri: string, returnedFilename: string, nosuffix = false, replaceRootFilename: string | undefined = undefined) { + try { + const posting = ClientUtils.prepend('/uploadURI'); + const returnedUri = await rp.post(posting, { + body: { + uri: imageUri, + name: returnedFilename, + nosuffix, + replaceRootFilename, + }, + json: true, + }); + return returnedUri; + } catch (e) { + console.log('ConvertDataURI :' + e); + } + return undefined; + } + + export function GetScreenTransform(ele?: HTMLElement | null): { scale: number; translateX: number; translateY: number } { + if (!ele) { + return { scale: 1, translateX: 1, translateY: 1 }; + } + const rect = ele.getBoundingClientRect(); + const scale = ele.offsetWidth === 0 && rect.width === 0 ? 1 : rect.width / ele.offsetWidth; + const translateX = rect.left; + const translateY = rect.top; + + return { scale, translateX, translateY }; + } + + /** + * A convenience method. Prepends the full path (i.e. http://localhost:) to the + * requested extension + * @param extension the specified sub-path to append to the window origin + */ + export function prepend(extension: string): string { + return window.location.origin + extension; + } + export function fileUrl(filename: string): string { + return prepend(`/files/${filename}`); + } + + export function shareUrl(documentId: string): string { + return prepend(`/doc/${documentId}?sharing=true`); + } + + export function CorsProxy(url: string): string { + return prepend('/corsProxy/') + encodeURIComponent(url); + } + + export function CopyText(text: string) { + navigator.clipboard.writeText(text); + } + + export function colorString(color: ColorResult) { + return color.hex.startsWith('#') && color.hex.length < 8 ? color.hex + (color.rgb.a ? decimalToHexString(Math.round(color.rgb.a * 255)) : 'ff') : color.hex; + } + + export function fromRGBAstr(rgba: string) { + const rm = rgba.match(/rgb[a]?\(([ 0-9]+)/); + const r = rm ? Number(rm[1]) : 0; + const gm = rgba.match(/rgb[a]?\([ 0-9]+,([ 0-9]+)/); + const g = gm ? Number(gm[1]) : 0; + const bm = rgba.match(/rgb[a]?\([ 0-9]+,[ 0-9]+,([ 0-9]+)/); + const b = bm ? Number(bm[1]) : 0; + const am = rgba.match(/rgba?\([ 0-9]+,[ 0-9]+,[ 0-9]+,([ .0-9]+)/); + const a = am ? Number(am[1]) : 1; + return { r: r, g: g, b: b, a: a }; + } + + const isTransparentFunctionHack = 'isTransparent(__value__)'; + export const noRecursionHack = '__noRecursion'; + + // 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 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 }) { + return 'rgba(' + col.r + ',' + col.g + ',' + col.b + (col.a !== undefined ? ',' + col.a : '') + ')'; + } + + export function HSLtoRGB(h: number, s: number, l: number) { + // Must be fractions of 1 + // s /= 100; + // l /= 100; + + const c = (1 - Math.abs(2 * l - 1)) * s; + const x = c * (1 - Math.abs(((h / 60) % 2) - 1)); + const m = l - c / 2; + let r = 0; + let g = 0; + let b = 0; + if (h >= 0 && h < 60) { + r = c; + g = x; + b = 0; + } else if (h >= 60 && h < 120) { + r = x; + g = c; + b = 0; + } else if (h >= 120 && h < 180) { + r = 0; + g = c; + b = x; + } else if (h >= 180 && h < 240) { + r = 0; + g = x; + b = c; + } else if (h >= 240 && h < 300) { + r = x; + g = 0; + b = c; + } else if (h >= 300 && h < 360) { + r = c; + g = 0; + b = x; + } + r = Math.round((r + m) * 255); + g = Math.round((g + m) * 255); + b = Math.round((b + m) * 255); + return { r: r, g: g, b: b }; + } + + export function RGBToHSL(red: number, green: number, blue: number) { + // Make r, g, and b fractions of 1 + const r = red / 255; + const g = green / 255; + const b = blue / 255; + + // Find greatest and smallest channel values + const cmin = Math.min(r, g, b); + const cmax = Math.max(r, g, b); + const delta = cmax - cmin; + let h = 0; + let s = 0; + let l = 0; + // Calculate hue + + // No difference + if (delta === 0) h = 0; + // Red is max + else if (cmax === r) h = ((g - b) / delta) % 6; + // Green is max + else if (cmax === g) h = (b - r) / delta + 2; + // Blue is max + else h = (r - g) / delta + 4; + + h = Math.round(h * 60); + + // Make negative hues positive behind 360° + if (h < 0) h += 360; // Calculate lightness + + l = (cmax + cmin) / 2; + + // Calculate saturation + s = delta === 0 ? 0 : delta / (1 - Math.abs(2 * l - 1)); + + // Multiply l and s by 100 + // s = +(s * 100).toFixed(1); + // l = +(l * 100).toFixed(1); + + return { h: h, s: s, l: l }; + } + + export function scrollIntoView(targetY: number, targetHgt: number, scrollTop: number, contextHgt: number, minSpacing: number, scrollHeight: number) { + if (!targetHgt) return targetY; // if there's no height, then assume that + if (scrollTop + contextHgt < Math.min(scrollHeight, targetY + minSpacing + targetHgt)) { + return Math.ceil(targetY + minSpacing + targetHgt - contextHgt); + } + if (scrollTop >= Math.max(0, targetY - minSpacing)) { + return Math.max(0, Math.floor(targetY - minSpacing)); + } + return undefined; + } + + export function GetClipboardText(): string { + const textArea = document.createElement('textarea'); + document.body.appendChild(textArea); + textArea.focus(); + textArea.select(); + + try { + document.execCommand('paste'); + } catch (err) { + /* empty */ + } + + const val = textArea.value; + document.body.removeChild(textArea); + return val; + } +} + +export function OmitKeys(obj: any, keys: string[], pattern?: string, addKeyFunc?: (dup: any) => void): { omit: any; extract: any } { + const omit: any = { ...obj }; + const extract: any = {}; + keys.forEach(key => { + extract[key] = omit[key]; + delete omit[key]; + }); + pattern && + Array.from(Object.keys(omit)) + .filter(key => key.match(pattern)) + .forEach(key => { + extract[key] = omit[key]; + delete omit[key]; + }); + addKeyFunc?.(omit); + return { omit, extract }; +} + +export function WithKeys(obj: any, keys: string[], addKeyFunc?: (dup: any) => void) { + const dup: any = {}; + keys.forEach(key => { + dup[key] = obj[key]; + }); + addKeyFunc && addKeyFunc(dup); + return dup; +} + +export function incrementTitleCopy(title: string) { + const numstr = title.match(/.*(\{([0-9]*)\})+/); + const copyNumStr = `{${1 + (numstr ? +numstr[2] : 0)}}`; + return (numstr ? title.replace(numstr[1], '') : title) + copyNumStr; +} + +const easeFunc = (transition: 'ease' | 'linear' | undefined, currentTime: number, start: number, change: number, duration: number) => { + if (transition === 'linear') { + const newCurrentTime = currentTime / duration; // currentTime / (duration / 2); + return start + newCurrentTime * change; + } + + let newCurrentTime = currentTime / (duration / 2); + if (newCurrentTime < 1) { + return (change / 2) * newCurrentTime * newCurrentTime + start; + } + + newCurrentTime -= 1; + return (-change / 2) * (newCurrentTime * (newCurrentTime - 2) - 1) + start; +}; + +export function smoothScroll(duration: number, element: HTMLElement | HTMLElement[], to: number, transition: 'ease' | 'linear' | undefined, stopper?: () => void) { + stopper?.(); + const elements = element instanceof HTMLElement ? [element] : element; + const starts = elements.map(element => element.scrollTop); + const startDate = new Date().getTime(); + let _stop = false; + const stop = () => { + _stop = true; + }; + const animateScroll = () => { + const currentDate = new Date().getTime(); + const currentTime = currentDate - startDate; + const setScrollTop = (element: HTMLElement, value: number) => { + element.scrollTop = value; + }; + if (!_stop) { + if (currentTime < duration) { + elements.forEach((element, i) => currentTime && setScrollTop(element, easeFunc(transition, Math.min(currentTime, duration), starts[i], to - starts[i], duration))); + requestAnimationFrame(animateScroll); + } else { + elements.forEach(element => setScrollTop(element, to)); + } + } + }; + animateScroll(); + return stop; +} + +export function smoothScrollHorizontal(duration: number, element: HTMLElement | HTMLElement[], to: number) { + const elements = element instanceof HTMLElement ? [element] : element; + const starts = elements.map(element => element.scrollLeft); + const startDate = new Date().getTime(); + + const animateScroll = () => { + const currentDate = new Date().getTime(); + const currentTime = currentDate - startDate; + elements.forEach((element, i) => { + element.scrollLeft = easeFunc('ease', currentTime, starts[i], to - starts[i], duration); + }); + + if (currentTime < duration) { + requestAnimationFrame(animateScroll); + } else { + elements.forEach(element => { + element.scrollLeft = to; + }); + } + }; + animateScroll(); +} + +export function addStyleSheet(styleType: string = 'text/css') { + const style = document.createElement('style'); + style.type = styleType; + const sheets = document.head.appendChild(style); + return (sheets as any).sheet; +} +export function addStyleSheetRule(sheet: any, selector: any, css: any, selectorPrefix = '.') { + const propText = + typeof css === 'string' + ? css + : Object.keys(css) + .map(p => p + ':' + (p === 'content' ? "'" + css[p] + "'" : css[p])) + .join(';'); + return sheet.insertRule(selectorPrefix + selector + '{' + propText + '}', sheet.cssRules.length); +} +export function removeStyleSheetRule(sheet: any, rule: number) { + if (sheet.rules.length) { + sheet.removeRule(rule); + return true; + } + return false; +} +export function clearStyleSheetRules(sheet: any) { + if (sheet.rules.length) { + numberRange(sheet.rules.length).map(() => sheet.removeRule(0)); + return true; + } + return false; +} + +export function simulateMouseClick(element: Element | null | undefined, x: number, y: number, sx: number, sy: number, rightClick = true) { + if (!element) return; + ['pointerdown', 'pointerup'].forEach(event => { + const me = new PointerEvent(event, { + view: window, + bubbles: true, + cancelable: true, + button: 2, + pointerType: 'mouse', + clientX: x, + clientY: y, + screenX: sx, + screenY: sy, + }); + (me as any).dash = true; + element.dispatchEvent(me); + }); + + if (rightClick) { + const me = new MouseEvent('contextmenu', { + view: window, + bubbles: true, + cancelable: true, + button: 2, + clientX: x, + clientY: y, + movementX: 0, + movementY: 0, + screenX: sx, + screenY: sy, + }); + (me as any).dash = true; + element.dispatchEvent(me); + } +} + +export function getWordAtPoint(elem: any, x: number, y: number): string | undefined { + if (elem.tagName === 'INPUT') return 'input'; + if (elem.tagName === 'TEXTAREA') return 'textarea'; + if (elem.nodeType === elem.TEXT_NODE) { + const range = elem.ownerDocument.createRange(); + range.selectNodeContents(elem); + let currentPos = 0; + const endPos = range.endOffset; + while (currentPos + 1 <= endPos) { + range.setStart(elem, currentPos); + range.setEnd(elem, currentPos + 1); + const rangeRect = range.getBoundingClientRect(); + if (rangeRect.left <= x && rangeRect.right >= x && rangeRect.top <= y && rangeRect.bottom >= y) { + range.expand?.('word'); // doesn't exist in firefox + const ret = range.toString(); + range.detach(); + return ret; + } + currentPos += 1; + } + } else { + Array.from(elem.childNodes).forEach((childNode: any) => { + const range = childNode.ownerDocument.createRange(); + range.selectNodeContents(childNode); + const rangeRect = range.getBoundingClientRect(); + if (rangeRect.left <= x && rangeRect.right >= x && rangeRect.top <= y && rangeRect.bottom >= y) { + range.detach(); + const word = getWordAtPoint(childNode, x, y); + if (word) return word; + } else { + range.detach(); + } + return undefined; + }); + } + return undefined; +} + +export function isTargetChildOf(ele: HTMLDivElement | null, target: Element | null) { + let entered = false; + for (let child = target; !entered && child; child = child.parentElement) { + entered = child === ele; + } + return entered; +} + +export function StopEvent(e: React.PointerEvent | React.MouseEvent | React.KeyboardEvent) { + e.stopPropagation(); + e.preventDefault(); +} + +export function setupMoveUpEvents( + target: object, + e: React.PointerEvent, + moveEvent: (e: PointerEvent, down: number[], delta: number[]) => boolean, + upEvent: (e: PointerEvent, movement: number[], isClick: boolean) => any, + clickEvent: (e: PointerEvent, doubleTap?: boolean) => any, + // eslint-disable-next-line default-param-last + stopPropagation: boolean = true, + // eslint-disable-next-line default-param-last + stopMovePropagation: boolean = true, + noDoubleTapTimeout?: () => void +) { + const targetAny = target as any; + const doubleTapTimeout = 300; + targetAny._doubleTap = Date.now() - (target as any)._lastTap < doubleTapTimeout; + targetAny._lastTap = Date.now(); + targetAny._downX = targetAny._lastX = e.clientX; + targetAny._downY = targetAny._lastY = e.clientY; + targetAny._noClick = false; + let moving = false; + + const _moveEvent = (e: PointerEvent): void => { + if (moving || Math.abs(e.clientX - (target as any)._downX) > ClientUtils.DRAG_THRESHOLD || Math.abs(e.clientY - (target as any)._downY) > ClientUtils.DRAG_THRESHOLD) { + moving = true; + if ((target as any)._doubleTime) { + clearTimeout((target as any)._doubleTime); + targetAny._doubleTime = undefined; + } + if (moveEvent(e, [(target as any)._downX, (target as any)._downY], [e.clientX - (target as any)._lastX, e.clientY - (target as any)._lastY])) { + document.removeEventListener('pointermove', _moveEvent); + // eslint-disable-next-line no-use-before-define + document.removeEventListener('pointerup', _upEvent); + } + } + targetAny._lastX = e.clientX; + targetAny._lastY = e.clientY; + stopMovePropagation && e.stopPropagation(); + }; + const _upEvent = (e: PointerEvent): void => { + const isClick = Math.abs(e.clientX - targetAny._downX) < 4 && Math.abs(e.clientY - targetAny._downY) < 4; + upEvent(e, [e.clientX - targetAny._downX, e.clientY - targetAny._downY], isClick); + if (isClick) { + if (!targetAny._doubleTime && noDoubleTapTimeout) { + targetAny._doubleTime = setTimeout(() => { + noDoubleTapTimeout?.(); + targetAny._doubleTime = undefined; + }, doubleTapTimeout); + } + if (targetAny._doubleTime && targetAny._doubleTap) { + clearTimeout(targetAny._doubleTime); + targetAny._doubleTime = undefined; + } + targetAny._noClick = clickEvent(e, targetAny._doubleTap); + } + document.removeEventListener('pointermove', _moveEvent); + document.removeEventListener('pointerup', _upEvent, true); + }; + const _clickEvent = (e: MouseEvent): void => { + if ((target as any)._noClick) e.stopPropagation(); + document.removeEventListener('click', _clickEvent, true); + }; + if (stopPropagation) { + e.stopPropagation(); + e.preventDefault(); + } + document.addEventListener('pointermove', _moveEvent); + document.addEventListener('pointerup', _upEvent, true); + document.addEventListener('click', _clickEvent, true); +} + +export function DivHeight(ele: HTMLElement): number { + return Number(getComputedStyle(ele).height.replace('px', '')); +} +export function DivWidth(ele: HTMLElement): number { + return Number(getComputedStyle(ele).width.replace('px', '')); +} + +export function dateRangeStrToDates(dateStr: string) { + // dateStr in yyyy-mm-dd format + const dateRangeParts = dateStr.split('|'); // splits into from and to date + const fromParts = dateRangeParts[0].split('-'); + const toParts = dateRangeParts[1].split('-'); + + const fromYear = parseInt(fromParts[0]); + const fromMonth = parseInt(fromParts[1]) - 1; + const fromDay = parseInt(fromParts[2]); + + const toYear = parseInt(toParts[0]); + const toMonth = parseInt(toParts[1]) - 1; + const toDay = parseInt(toParts[2]); + + return [new Date(fromYear, fromMonth, fromDay), new Date(toYear, toMonth, toDay)]; +} diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index 804e6d1d7..b833d3287 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -102,7 +102,7 @@ export namespace DocServer { } export function getFieldWriteMode(field: string) { - return ClientUtils.CurrentUserEmail === 'guest' ? WriteMode.LivePlayground : fieldWriteModes[field] || WriteMode.Default; + return ClientUtils.CurrentUserEmail() === 'guest' ? WriteMode.LivePlayground : fieldWriteModes[field] || WriteMode.Default; } export function registerDocWithCachedUpdate(doc: Doc, field: string, oldValue: any) { @@ -463,7 +463,7 @@ export namespace DocServer { function _CreateFieldImpl(field: RefField) { _cache[field[Id]] = field; const initialState = SerializationHelper.Serialize(field); - ClientUtils.CurrentUserEmail !== 'guest' && DocServer.Emit(_socket, MessageStore.CreateField, initialState); + ClientUtils.CurrentUserEmail() !== 'guest' && DocServer.Emit(_socket, MessageStore.CreateField, initialState); } // NOTIFY THE SERVER OF AN UPDATE TO A DOC'S STATE @@ -481,7 +481,7 @@ export namespace DocServer { } function _UpdateFieldImpl(id: string, diff: any) { - !DocServer.Control.isReadOnly() && ClientUtils.CurrentUserEmail !== 'guest' && DocServer.Emit(_socket, MessageStore.UpdateField, { id, diff }); + !DocServer.Control.isReadOnly() && ClientUtils.CurrentUserEmail() !== 'guest' && DocServer.Emit(_socket, MessageStore.UpdateField, { id, diff }); } function _respondToUpdateImpl(diff: any) { @@ -516,11 +516,11 @@ export namespace DocServer { } export function DeleteDocument(id: string) { - ClientUtils.CurrentUserEmail !== 'guest' && DocServer.Emit(_socket, MessageStore.DeleteField, id); + ClientUtils.CurrentUserEmail() !== 'guest' && DocServer.Emit(_socket, MessageStore.DeleteField, id); } export function DeleteDocuments(ids: string[]) { - ClientUtils.CurrentUserEmail !== 'guest' && DocServer.Emit(_socket, MessageStore.DeleteFields, ids); + ClientUtils.CurrentUserEmail() !== 'guest' && DocServer.Emit(_socket, MessageStore.DeleteFields, ids); } function _respondToDeleteImpl(ids: string | string[]) { diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index d41a96db2..f827ea81c 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -970,7 +970,7 @@ export namespace Docs { dataProps['acl-Guest'] = options['acl-Guest'] ?? (Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View); dataProps.isSystem = viewProps.isSystem; dataProps.isDataDoc = true; - dataProps.author = ClientUtils.CurrentUserEmail; + dataProps.author = ClientUtils.CurrentUserEmail(); dataProps.author_date = new DateField(); if (fieldKey) { dataProps[`${fieldKey}_modificationDate`] = new DateField(); @@ -990,7 +990,7 @@ export namespace Docs { } if (!noView) { - const viewFirstProps: { [id: string]: any } = { author: ClientUtils.CurrentUserEmail }; + const viewFirstProps: { [id: string]: any } = { author: ClientUtils.CurrentUserEmail() }; viewFirstProps['acl-Guest'] = options['_acl-Guest'] ?? (Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View); let viewDoc: Doc; // determines whether viewDoc should be created using placeholder Doc or default @@ -1330,7 +1330,7 @@ export namespace Docs { const doc = DockDocument( configs.map(c => c.doc), JSON.stringify(layoutConfig), - ClientUtils.CurrentUserEmail === 'guest' ? options : { 'acl-Guest': SharingPermissions.View, ...options }, + ClientUtils.CurrentUserEmail() === 'guest' ? options : { 'acl-Guest': SharingPermissions.View, ...options }, id ); configs.forEach(c => { @@ -1722,7 +1722,7 @@ export namespace DocUtils { event: undoable(() => { const newDoc = DocUtils.copyDragFactory(dragDoc); if (newDoc) { - newDoc.author = ClientUtils.CurrentUserEmail; + newDoc.author = ClientUtils.CurrentUserEmail(); newDoc.x = x; newDoc.y = y; EquationBox.SelectOnLoad = newDoc[Id]; @@ -1773,7 +1773,7 @@ export namespace DocUtils { event: undoable(() => { const newDoc = DocUtils.delegateDragFactory(dragDoc); if (newDoc) { - newDoc.author = ClientUtils.CurrentUserEmail; + newDoc.author = ClientUtils.CurrentUserEmail(); newDoc.x = x; newDoc.y = y; EquationBox.SelectOnLoad = newDoc[Id]; diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 535f7cf9d..27ae5c9a0 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -586,7 +586,7 @@ pie title Minerals in my tap water /// initializes the left sidebar panel view of the UserDoc static setupUserDocView(doc: Doc, field:string) { const reqdOpts:DocumentOptions = { - _lockedPosition: true, _gridGap: 5, _forceActive: true, title: ClientUtils.CurrentUserEmail +"-view", + _lockedPosition: true, _gridGap: 5, _forceActive: true, title: ClientUtils.CurrentUserEmail() +"-view", layout_boxShadow: "0 0", childDontRegisterViews: true, dropAction: dropActionType.same, ignoreClick: true, isSystem: true, treeView_HideTitle: true, treeView_TruncateTitleWidth: 350 }; @@ -832,8 +832,8 @@ pie title Minerals in my tap water static setupLinkDocs(doc: Doc, linkDatabaseId: string) { if (!(CurrentUserUtils.newAccount ? undefined : DocCast(doc.myLinkDatabase))) { const linkDocs = new Doc(linkDatabaseId, true); - linkDocs.title = "LINK DATABASE: " + ClientUtils.CurrentUserEmail; - linkDocs.author = ClientUtils.CurrentUserEmail; + linkDocs.title = "LINK DATABASE: " + ClientUtils.CurrentUserEmail(); + linkDocs.author = ClientUtils.CurrentUserEmail(); linkDocs.isSystem = true; linkDocs.data = new List([]); linkDocs["acl-Guest"] = SharingPermissions.Augment; @@ -893,11 +893,11 @@ pie title Minerals in my tap water reaction(() => DateCast(DocCast(doc.globalGroupDatabase).data_modificationDate), async () => { const groups = await DocListCastAsync(DocCast(doc.globalGroupDatabase).data); - const mygroups = groups?.filter(group => JSON.parse(StrCast(group.members)).includes(ClientUtils.CurrentUserEmail)) || []; + const mygroups = groups?.filter(group => JSON.parse(StrCast(group.members)).includes(ClientUtils.CurrentUserEmail())) || []; SetCachedGroups(["Guest", ...(mygroups?.map(g => StrCast(g.title))??[])]); }, { fireImmediately: true }); doc.isSystem ?? (doc.isSystem = true); - doc.title ?? (doc.title = ClientUtils.CurrentUserEmail); + doc.title ?? (doc.title = ClientUtils.CurrentUserEmail()); Doc.noviceMode ?? (Doc.noviceMode = true); doc._showLabel ?? (doc._showLabel = true); doc.textAlign ?? (doc.textAlign = "left"); @@ -975,7 +975,7 @@ pie title Minerals in my tap water if (response) { const result: { version: string, userDocumentId: string, sharingDocumentId: string, linkDatabaseId: string, email: string, cacheDocumentIds: string, resolvedPorts: string } = JSON.parse(response); runInAction(() => { CurrentUserUtils.ServerVersion = result.version; }); - ClientUtils.CurrentUserEmail = result.email; + ClientUtils.SetCurrentUserEmail(result.email); resolvedPorts = result.resolvedPorts as any; DocServer.init(window.location.protocol, window.location.hostname, resolvedPorts?.socket, result.email); if (result.cacheDocumentIds) @@ -1004,7 +1004,7 @@ pie title Minerals in my tap water const userDoc = CurrentUserUtils.newAccount ? new Doc(info.userDocumentId, true) : field as Doc; this.updateUserDocument(Doc.SetUserDoc(userDoc), info.sharingDocumentId, info.linkDatabaseId); if (CurrentUserUtils.newAccount) { - if (ClientUtils.CurrentUserEmail === "guest") { + if (ClientUtils.CurrentUserEmail() === "guest") { DashboardView.createNewDashboard(undefined, "guest dashboard"); } else { userDoc.activePage = "home"; diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 67a61f17e..9a7786125 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -321,7 +321,7 @@ export class DocumentManager { if (options.toggleTarget && (!options.didMove || docView.Document.hidden)) docView.Document.hidden = !docView.Document.hidden; if (options.effect) docView.Document[Animation] = options.effect; - if (options.zoomTextSelections && Doc.UnhighlightTimer && contextView && targetDoc.text_html) { + if (options.zoomTextSelections && Doc.IsUnhighlightTimerSet() && contextView && targetDoc.text_html) { // if the docView is a text anchor, the contextView is the PDF/Web/Text doc contextView.setTextHtmlOverlay(StrCast(targetDoc.text_html), options.effect); DocumentManager._overlayViews.add(contextView); diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx index 6b20b885b..c261c0f1e 100644 --- a/src/client/util/GroupManager.tsx +++ b/src/client/util/GroupManager.tsx @@ -136,7 +136,7 @@ export class GroupManager extends ObservableReactComponent<{}> { hasEditAccess(groupDoc: Doc): boolean { if (!groupDoc) return false; const accessList: string[] = JSON.parse(StrCast(groupDoc.owners)); - return accessList.includes(ClientUtils.CurrentUserEmail) || this.adminGroupMembers?.includes(ClientUtils.CurrentUserEmail); + return accessList.includes(ClientUtils.CurrentUserEmail()) || this.adminGroupMembers?.includes(ClientUtils.CurrentUserEmail()); } /** @@ -148,7 +148,7 @@ export class GroupManager extends ObservableReactComponent<{}> { const name = groupName.toLowerCase() === 'admin' ? 'Admin' : groupName; const groupDoc = new Doc('GROUP:' + name, true); groupDoc.title = name; - groupDoc.owners = JSON.stringify([ClientUtils.CurrentUserEmail]); + groupDoc.owners = JSON.stringify([ClientUtils.CurrentUserEmail()]); groupDoc.members = JSON.stringify(memberEmails); this.addGroup(groupDoc); } @@ -177,7 +177,7 @@ export class GroupManager extends ObservableReactComponent<{}> { Doc.RemoveDocFromList(this.GroupManagerDoc, 'data', group); SharingManager.Instance.removeGroup(group); const members = JSON.parse(StrCast(group.members)); - if (members.includes(ClientUtils.CurrentUserEmail)) { + if (members.includes(ClientUtils.CurrentUserEmail())) { const index = DocListCast(this.GroupManagerDoc.data).findIndex(grp => grp === group); index !== -1 && Cast(this.GroupManagerDoc.data, listSpec(Doc), [])?.splice(index, 1); } diff --git a/src/client/util/HypothesisUtils.ts b/src/client/util/HypothesisUtils.ts index d1600468a..48d3ac046 100644 --- a/src/client/util/HypothesisUtils.ts +++ b/src/client/util/HypothesisUtils.ts @@ -33,7 +33,7 @@ export namespace Hypothesis { // await SearchUtil.Search('web', true).then( // action(async (res: SearchUtil.DocSearchResult) => { // const docs = res.docs; - // const filteredDocs = docs.filter(doc => doc.author === ClientUtils.CurrentUserEmail && doc.type === DocumentType.WEB && doc.data); + // const filteredDocs = docs.filter(doc => doc.author === ClientUtils.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? // }); diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx index e2971895a..f983c29b7 100644 --- a/src/client/util/SettingsManager.tsx +++ b/src/client/util/SettingsManager.tsx @@ -128,7 +128,7 @@ export class SettingsManager extends React.Component<{}> { if (this.playgroundMode) { DocServer.Control.makeReadOnly(); addStyleSheetRule(SettingsManager._settingsStyle, 'topbar-inner-container', { background: 'red !important' }); - } else ClientUtils.CurrentUserEmail !== 'guest' && DocServer.Control.makeEditable(); + } else ClientUtils.CurrentUserEmail() !== 'guest' && DocServer.Control.makeEditable(); }); @undoBatch @@ -537,7 +537,7 @@ export class SettingsManager extends React.Component<{}> {
    {DashVersion}
    - {ClientUtils.CurrentUserEmail} + {ClientUtils.CurrentUserEmail()}
    diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index 03f7e9b71..ade4cc218 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -137,7 +137,7 @@ export class SharingManager extends React.Component<{}> { if (!this.populating && Doc.UserDoc()[Id] !== Utils.GuestID()) { this.populating = true; const userList = await RequestPromise.get(ClientUtils.prepend('/getUsers')); - const raw = (JSON.parse(userList) as User[]).filter(user => user.email !== 'guest' && user.email !== ClientUtils.CurrentUserEmail); + const raw = (JSON.parse(userList) as User[]).filter(user => user.email !== 'guest' && user.email !== ClientUtils.CurrentUserEmail()); runInAction(() => { FieldLoader.ServerLoadStatus.message = 'users'; }); @@ -234,7 +234,7 @@ export class SharingManager extends React.Component<{}> { shareFromPropertiesSidebar = undoable((shareWith: string, permission: SharingPermissions, docs: Doc[], layout: boolean) => { if (layout) this.layoutDocAcls = true; if (shareWith !== 'Guest') { - const user = this.users.find(({ user: { email } }) => email === (shareWith === 'Me' ? ClientUtils.CurrentUserEmail : shareWith)); + const user = this.users.find(({ user: { email } }) => email === (shareWith === 'Me' ? ClientUtils.CurrentUserEmail() : shareWith)); docs.forEach(doc => { if (user) this.setInternalSharing(user, permission, doc); else this.setInternalGroupSharing(GroupManager.Instance.getGroup(shareWith)!, permission, doc, undefined, true); @@ -499,19 +499,19 @@ export class SharingManager extends React.Component<{}> { const sameAuthor = docs.every(doc => doc?.author === docs[0]?.author); // the owner of the doc and the current user are placed at the top of the user list. - const userKey = `acl-${normalizeEmail(ClientUtils.CurrentUserEmail)}`; + const userKey = `acl-${normalizeEmail(ClientUtils.CurrentUserEmail())}`; const curUserPermission = StrCast(targetDoc[userKey]); // const curUserPermission = HierarchyMapping.get(effectiveAcls[0])!.name userListContents.unshift( sameAuthor ? (
    - {targetDoc?.author === ClientUtils.CurrentUserEmail ? 'Me' : StrCast(targetDoc?.author)} + {targetDoc?.author === ClientUtils.CurrentUserEmail() ? 'Me' : StrCast(targetDoc?.author)}
    Owner
    ) : null, - sameAuthor && targetDoc?.author !== ClientUtils.CurrentUserEmail ? ( + sameAuthor && targetDoc?.author !== ClientUtils.CurrentUserEmail() ? (
    Me
    diff --git a/src/client/util/reportManager/ReportManager.tsx b/src/client/util/reportManager/ReportManager.tsx index d6fd339a9..02b3ee32c 100644 --- a/src/client/util/reportManager/ReportManager.tsx +++ b/src/client/util/reportManager/ReportManager.tsx @@ -134,7 +134,7 @@ export class ReportManager extends React.Component<{}> { const req = await this.octokit.request('POST /repos/{owner}/{repo}/issues', { owner: 'brown-dash', repo: 'Dash-Web', - title: formatTitle(this.formData.title, ClientUtils.CurrentUserEmail), + title: formatTitle(this.formData.title, ClientUtils.CurrentUserEmail()), body: `${this.formData.description} ${formattedLinks.length > 0 ? `\n\nFiles:\n${formattedLinks.join('\n')}` : ''}`, labels: ['from-dash-app', this.formData.type, this.formData.priority], }); diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx index 1d286c987..14abd5f89 100644 --- a/src/client/views/DashboardView.tsx +++ b/src/client/views/DashboardView.tsx @@ -64,7 +64,7 @@ export class DashboardView extends ObservableReactComponent<{}> { getDashboards = (whichGroup: DashboardGroup) => { if (whichGroup === DashboardGroup.MyDashboards) { - return DocListCast(Doc.MyDashboards.data).filter(dashboard => dashboard[DocData].author === ClientUtils.CurrentUserEmail); + return DocListCast(Doc.MyDashboards.data).filter(dashboard => dashboard[DocData].author === ClientUtils.CurrentUserEmail()); } return DocListCast(Doc.MySharedDocs.data_dashboards).filter(doc => doc.dockingConfig); }; @@ -155,7 +155,7 @@ export class DashboardView extends ObservableReactComponent<{}> { : this.getDashboards(this.selectedDashboardGroup).map(dashboard => { const href = ImageCast(dashboard.thumb)?.url?.href; const shared = Object.keys(dashboard[DocAcl]) - .filter(key => key !== `acl-${normalizeEmail(ClientUtils.CurrentUserEmail)}` && !['acl-Me', 'acl-Guest'].includes(key)) + .filter(key => key !== `acl-${normalizeEmail(ClientUtils.CurrentUserEmail())}` && !['acl-Me', 'acl-Guest'].includes(key)) .some(key => dashboard[DocAcl][key] !== AclPrivate); return (
    { } else if (doc.readOnly) { DocServer.Control.makeReadOnly(); } else { - ClientUtils.CurrentUserEmail !== 'guest' && DocServer.Control.makeEditable(); + ClientUtils.CurrentUserEmail() !== 'guest' && DocServer.Control.makeEditable(); } } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 8a060d0e0..13945cacf 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -122,7 +122,7 @@ export class MainView extends ObservableReactComponent<{}> { } @observable mainDoc: Opt = undefined; @computed private get mainContainer() { - if (window.location.pathname.startsWith('/doc/') && ClientUtils.CurrentUserEmail === 'guest') { + if (window.location.pathname.startsWith('/doc/') && ClientUtils.CurrentUserEmail() === 'guest') { DocServer.GetRefField(window.location.pathname.substring('/doc/'.length)).then(main => runInAction(() => { this.mainDoc = main as Doc; diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 77e68866e..2a847e51a 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -405,7 +405,7 @@ export class PropertiesView extends ObservableReactComponent, props: Opt(moreProps?: X) { let ind; const doc = this.Document; const id = Doc.UserDoc()[Id]; - const email = ClientUtils.CurrentUserEmail; + const email = ClientUtils.CurrentUserEmail(); const pos = { x: position[0], y: position[1] }; if (id && email) { const proto = Doc.GetProto(doc); diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index e266ccd14..20323a521 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -229,7 +229,7 @@ export class TreeView extends ObservableReactComponent { this.treeViewOpen = !this.treeViewOpen; } else { // choose an appropriate embedding or make one. --- choose the first embedding that (1) user owns, (2) has no context field ... otherwise make a new embedding - const bestEmbedding = docView.Document.author === ClientUtils.CurrentUserEmail && !Doc.IsDataProto(docView.Document) ? docView.Document : Doc.BestEmbedding(docView.Document); + const bestEmbedding = docView.Document.author === ClientUtils.CurrentUserEmail() && !Doc.IsDataProto(docView.Document) ? docView.Document : Doc.BestEmbedding(docView.Document); this._props.addDocTab(bestEmbedding, OpenWhere.lightbox); } }; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index dbd2ebe0d..62a3e2467 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1512,7 +1512,7 @@ export class DocumentView extends DocComponent LightboxView.Instance.SetLightboxDoc( diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index c7543560d..a82f025f9 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -580,7 +580,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { const view = this._editorView!; - const nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: ClientUtils.CurrentUserEmail }); + const nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: ClientUtils.CurrentUserEmail() }); view.dispatch(view.state.tr.removeMark(start, end, nmark).addMark(start, end, nmark)); }; protected createDropTarget = (ele: HTMLDivElement) => { @@ -722,7 +722,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-min-' + (min - i), { opacity: ((10 - i - 1) / 10).toString() })); } if (highlights.includes('By Recent Hour')) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + ClientUtils.CurrentUserEmail.replace('.', '').replace('@', ''), { opacity: '0.1' }); + addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + ClientUtils.CurrentUserEmail().replace('.', '').replace('@', ''), { opacity: '0.1' }); const hr = Math.round(Date.now() / 1000 / 60 / 60); numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-hr-' + (hr - i), { opacity: ((10 - i - 1) / 10).toString() })); } @@ -1489,7 +1489,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent m.type !== mark.type), mark]; const tr1 = this._editorView.state.tr.setStoredMarks(storedMarks); @@ -1503,7 +1503,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent m.type !== mark.type), mark]; const { tr } = this._editorView.state; @@ -1534,7 +1534,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent i && node?.marks?.().some(mark => mark.type === schema.marks.user_mark && mark.attrs.userid !== ClientUtils.CurrentUserEmail) && [AclAugment, AclSelfEdit].includes(GetEffectiveAcl(this.Document))) { + if (state.doc.content.size - 1 > i && node?.marks?.().some(mark => mark.type === schema.marks.user_mark && mark.attrs.userid !== ClientUtils.CurrentUserEmail()) && [AclAugment, AclSelfEdit].includes(GetEffectiveAcl(this.Document))) { e.preventDefault(); } } @@ -1799,7 +1799,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent m.type === schema.marks.user_mark && m.attrs.modified === modified); - _editorView.dispatch(state.tr.removeStoredMark(schema.marks.user_mark).addStoredMark(mark ?? schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail, modified }))); + _editorView.dispatch(state.tr.removeStoredMark(schema.marks.user_mark).addStoredMark(mark ?? schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified }))); } break; } diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx index 533fefa09..52d93ec38 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx @@ -10,7 +10,7 @@ import './FormattedTextBoxComment.scss'; import { schema } from './schema_rts'; export function findOtherUserMark(marks: readonly Mark[]): Mark | undefined { - return marks.find(m => m.attrs.userid && m.attrs.userid !== ClientUtils.CurrentUserEmail); + return marks.find(m => m.attrs.userid && m.attrs.userid !== ClientUtils.CurrentUserEmail()); } export function findUserMark(marks: readonly Mark[]): Mark | undefined { return marks.find(m => m.attrs.userid); diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts index e9ed2549e..c3a5a2c86 100644 --- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts +++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts @@ -51,8 +51,8 @@ export function buildKeymap>(schema: S, props: any, mapKey case AclAugment: { const prevNode = state.selection.$cursor.nodeBefore; - const prevUser = !prevNode ? ClientUtils.CurrentUserEmail : prevNode.marks[prevNode.marks.length - 1].attrs.userid; - if (prevUser !== ClientUtils.CurrentUserEmail) { + const prevUser = !prevNode ? ClientUtils.CurrentUserEmail() : prevNode.marks[prevNode.marks.length - 1].attrs.userid; + if (prevUser !== ClientUtils.CurrentUserEmail()) { return false; } } diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index 78ea99592..5b5617484 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -433,8 +433,8 @@ export class RichTextRules { return node ? state.tr .removeMark(start, end, schema.marks.user_mark) - .addMark(start, end, schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })) - .addMark(start, end, schema.marks.user_tag.create({ userid: ClientUtils.CurrentUserEmail, tag: tag, modified: Math.round(Date.now() / 1000 / 60) })) + .addMark(start, end, schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) })) + .addMark(start, end, schema.marks.user_tag.create({ userid: ClientUtils.CurrentUserEmail(), tag: tag, modified: Math.round(Date.now() / 1000 / 60) })) : state.tr; }), diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts index 0f3306bef..d1c7b72a5 100644 --- a/src/client/views/nodes/formattedText/marks_rts.ts +++ b/src/client/views/nodes/formattedText/marks_rts.ts @@ -335,7 +335,7 @@ export const marks: { [index: string]: MarkSpec } = { const min = Math.round(node.attrs.modified / 60); const hr = Math.round(min / 60); const day = Math.round(hr / 60 / 24); - const remote = node.attrs.userid !== ClientUtils.CurrentUserEmail ? ' UM-remote' : ''; + const remote = node.attrs.userid !== ClientUtils.CurrentUserEmail() ? ' UM-remote' : ''; return ['span', { class: 'UM-' + uid + remote + ' UM-min-' + min + ' UM-hr-' + hr + ' UM-day-' + day }, 0]; }, }, diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 3fb914423..f8351c238 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -152,7 +152,7 @@ export const ReverseHierarchyMap: Map { key.startsWith('acl') && (permissions[key] = ReverseHierarchyMap.get(StrCast(target[key]))!.acl); }); @@ -301,15 +301,16 @@ export class Doc extends RefField { private set __fieldTuples(value) { // called by deserializer to set all fields in one shot this[FieldTuples] = value; - // eslint-disable-next-line no-restricted-syntax - for (const key in value) { - const field = value[key]; - field !== undefined && (this[FieldKeys][key] = true); - if (field instanceof ObjectField) { - field[Parent] = this[Self]; - field[FieldChanged] = containedFieldChangedHandler(this[SelfProxy], key, field); + Object.keys(value).forEach(key => { + if (Object.prototype.hasOwnProperty.call(value, key)) { + const field = value[key]; + field !== undefined && (this[FieldKeys][key] = true); + if (field instanceof ObjectField) { + field[Parent] = this[Self]; + field[FieldChanged] = containedFieldChangedHandler(this[SelfProxy], key, field); + } } - } + }); } @observable private [FieldTuples]: any = {}; @@ -365,14 +366,11 @@ export class Doc extends RefField { public async [HandleUpdate](diff: any) { const set = diff.$set; - const sameAuthor = this.author === ClientUtils.CurrentUserEmail; - if (set) { - for (const key in set) { - const fprefix = 'fields.'; - if (!key.startsWith(fprefix)) { - // eslint-disable-next-line no-continue - continue; - } + const sameAuthor = this.author === ClientUtils.CurrentUserEmail(); + const fprefix = 'fields.'; + Object.keys(set ?? {}) + .filter(key => Object.prototype.hasOwnProperty.call(set, key) && key.startsWith(fprefix)) + .forEach(async key => { const fKey = key.substring(fprefix.length); const fn = async () => { const value = await SerializationHelper.Deserialize(set[key]); @@ -395,15 +393,11 @@ export class Doc extends RefField { } else { this[CachedUpdates][fKey] = fn; } - } - } + }); const unset = diff.$unset; - if (unset) { - for (const key in unset) { - if (!key.startsWith('fields.')) { - // eslint-disable-next-line no-continue - continue; - } + Object.keys(unset ?? {}) + .filter(key => Object.prototype.hasOwnProperty.call(unset, key) && key.startsWith(fprefix)) + .forEach(async key => { const fKey = key.substring(7); const fn = () => { this[UpdatingFromServer] = true; @@ -412,13 +406,11 @@ export class Doc extends RefField { }; if (sameAuthor || DocServer.getFieldWriteMode(fKey) !== DocServer.WriteMode.Playground) { delete this[CachedUpdates][fKey]; - // eslint-disable-next-line no-await-in-loop await fn(); } else { this[CachedUpdates][fKey] = fn; } - } - } + }); } } @@ -519,15 +511,15 @@ export namespace Doc { */ export function assign(doc: Doc, fields: Partial>>, skipUndefineds: boolean = false, isInitializing = false) { isInitializing && (doc[Initializing] = true); - for (const key in fields) { - if (fields.hasOwnProperty(key)) { - const value = fields[key]; + Object.keys(fields).forEach(key => { + if (Object.prototype.hasOwnProperty.call(fields.hasOwnProperty, key)) { + const value = (fields as any)[key]; if (!skipUndefineds || value !== undefined) { // Do we want to filter out undefineds? doc[key] = value; } } - } + }); isInitializing && (doc[Initializing] = false); return doc; } @@ -640,7 +632,7 @@ export namespace Doc { embedding.createdFrom = doc; embedding.proto_embeddingId = doc[DocData].proto_embeddingId = Doc.GetEmbeddings(doc).length - 1; !Doc.GetT(embedding, 'title', 'string', true) && (embedding.title = ComputedField.MakeFunction(`renameEmbedding(this)`)); - embedding.author = ClientUtils.CurrentUserEmail; + embedding.author = ClientUtils.CurrentUserEmail(); return embedding; } @@ -648,7 +640,7 @@ export namespace Doc { export function BestEmbedding(doc: Doc) { const dataDoc = doc[DocData]; const availableEmbeddings = Doc.GetEmbeddings(dataDoc); - const bestEmbedding = [...(dataDoc !== doc ? [doc] : []), ...availableEmbeddings].find(doc => !doc.embedContainer && doc.author === ClientUtils.CurrentUserEmail); + const bestEmbedding = [...(dataDoc !== doc ? [doc] : []), ...availableEmbeddings].find(doc => !doc.embedContainer && doc.author === ClientUtils.CurrentUserEmail()); bestEmbedding && Doc.AddEmbedding(doc, doc); return bestEmbedding ?? Doc.MakeEmbedding(doc); } @@ -708,7 +700,7 @@ export namespace Doc { }; const docAtKey = doc[key]; if (key === 'author') { - assignKey(ClientUtils.CurrentUserEmail); + assignKey(ClientUtils.CurrentUserEmail()); } else if (docAtKey instanceof Doc) { if (pruneDocs.includes(docAtKey)) { // prune doc and do nothing @@ -717,7 +709,7 @@ export namespace Doc { (key.includes('layout[') || key.startsWith('layout') || // ['embedContainer', 'annotationOn', 'proto'].includes(key) || - (['link_anchor_1', 'link_anchor_2'].includes(key) && doc.author === ClientUtils.CurrentUserEmail)) + (['link_anchor_1', 'link_anchor_2'].includes(key) && doc.author === ClientUtils.CurrentUserEmail())) ) { assignKey(await Doc.makeClone(docAtKey, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks, cloneTemplates)); } else { @@ -886,7 +878,7 @@ export namespace Doc { } } else { const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key])); - const field = key === 'author' ? ClientUtils.CurrentUserEmail : ProxyField.WithoutProxy(() => doc[key]); + const field = key === 'author' ? ClientUtils.CurrentUserEmail() : ProxyField.WithoutProxy(() => doc[key]); if (field instanceof RefField) { if (field instanceof Doc) { if (key === 'myLinkDatabase') { @@ -934,7 +926,7 @@ export namespace Doc { } } else { const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key])); - const field = key === 'author' ? ClientUtils.CurrentUserEmail : ProxyField.WithoutProxy(() => doc[key]); + const field = key === 'author' ? ClientUtils.CurrentUserEmail() : ProxyField.WithoutProxy(() => doc[key]); if (field instanceof RefField) { copy[key] = field; } else if (cfield instanceof ComputedField) { @@ -973,7 +965,7 @@ export namespace Doc { delegate[Initializing] = true; updateCachedAcls(delegate); delegate.proto = doc; - delegate.author = ClientUtils.CurrentUserEmail; + delegate.author = ClientUtils.CurrentUserEmail(); Object.keys(doc) .filter(key => key.startsWith('acl')) .forEach(key => { @@ -1004,7 +996,7 @@ export namespace Doc { export function ApplyTemplate(templateDoc: Doc) { if (templateDoc) { const proto = new Doc(); - proto.author = ClientUtils.CurrentUserEmail; + proto.author = ClientUtils.CurrentUserEmail(); const target = Doc.MakeDelegate(proto); const targetKey = StrCast(templateDoc.layout_fieldKey, 'layout'); const applied = ApplyTemplateTo(templateDoc, target, targetKey, templateDoc.title + '(...' + _applyCount++ + ')'); @@ -1199,7 +1191,8 @@ export namespace Doc { } const UnhighlightWatchers: (() => void)[] = []; - export let UnhighlightTimer: any; + let UnhighlightTimer: any; + export function IsUnhighlightTimerSet() { return UnhighlightTimer; } // prettier-ignore export function AddUnHighlightWatcher(watcher: () => void) { if (UnhighlightTimer) { UnhighlightWatchers.push(watcher); diff --git a/src/fields/util.ts b/src/fields/util.ts index 5ed10cf05..d7268f31a 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -84,7 +84,7 @@ const _setterImpl = action((target: any, prop: string | symbol | number, valueIn const writeMode = DocServer.getFieldWriteMode(prop as string); const fromServer = target[UpdatingFromServer]; - const sameAuthor = fromServer || receiver.author === ClientUtils.CurrentUserEmail; + const sameAuthor = fromServer || receiver.author === ClientUtils.CurrentUserEmail(); const writeToDoc = sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || writeMode === DocServer.WriteMode.Playground || writeMode === DocServer.WriteMode.LivePlayground || (effectiveAcl === AclAugment && value instanceof RichTextField); const writeToServer = @@ -163,7 +163,7 @@ const getEffectiveAclCache = computedFn((target: any, user?: string) => getEffec */ export function GetEffectiveAcl(target: any, user?: string): symbol { if (!target) return AclPrivate; - if (target[UpdatingFromServer] || ClientUtils.CurrentUserEmail === 'guest') return AclAdmin; + if (target[UpdatingFromServer] || ClientUtils.CurrentUserEmail() === 'guest') return AclAdmin; return getEffectiveAclCache(target, user); // all changes received from the server must be processed as Admin. return this directly so that the acls aren't cached (UpdatingFromServer is not observable) } @@ -186,7 +186,7 @@ function getEffectiveAcl(target: any, user?: string): symbol { const targetAcls = target[DocAcl]; if (targetAcls?.['acl-Me'] === AclAdmin || GetCachedGroupByName('Admin')) return AclAdmin; - const userChecked = user || ClientUtils.CurrentUserEmail; // if the current user is the author of the document / the current user is a member of the admin group + const userChecked = user || ClientUtils.CurrentUserEmail(); // if the current user is the author of the document / the current user is a member of the admin group if (targetAcls && Object.keys(targetAcls).length) { let effectiveAcl = AclPrivate; Object.entries(targetAcls).forEach(([key, value]) => { @@ -219,7 +219,7 @@ function getEffectiveAcl(target: any, user?: string): symbol { */ // eslint-disable-next-line default-param-last export function distributeAcls(key: string, acl: SharingPermissions, target: Doc, visited: Doc[] = [], allowUpgrade?: boolean, layoutOnly = false) { - const selfKey = `acl-${normalizeEmail(ClientUtils.CurrentUserEmail)}`; + const selfKey = `acl-${normalizeEmail(ClientUtils.CurrentUserEmail())}`; if (!target || visited.includes(target) || key === selfKey) return; visited.push(target); @@ -268,7 +268,7 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc * Copies parent's acl fields to the child */ export function inheritParentAcls(parent: Doc, child: Doc, layoutOnly: boolean) { - [...Object.keys(parent), ...(ClientUtils.CurrentUserEmail !== parent.author ? ['acl-Owner'] : [])] + [...Object.keys(parent), ...(ClientUtils.CurrentUserEmail() !== parent.author ? ['acl-Owner'] : [])] .filter(key => key.startsWith('acl')) .forEach(key => { // if the default acl mode is private, then don't inherit the acl-guest permission, but set it to private. -- cgit v1.2.3-70-g09d2 From 939e18624af4252551f38c43335ee8ef0acd144c Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 21 Apr 2024 19:03:49 -0400 Subject: more lint cleanup --- .eslintrc.json | 1 + package-lock.json | 3 +- package.json | 2 +- .../apis/google_docs/GooglePhotosClientUtils.ts | 82 ++- src/client/util/BranchingTrailManager.tsx | 41 +- src/client/util/CalendarManager.tsx | 39 +- src/client/util/CurrentUserUtils.ts | 2 +- src/client/util/DragManager.ts | 18 +- src/client/util/GroupManager.tsx | 85 ++- src/client/util/HypothesisUtils.ts | 2 - src/client/util/Import & Export/ImageUtils.ts | 2 +- src/client/util/LinkFollower.ts | 7 +- src/client/util/LinkManager.ts | 28 +- src/client/util/ScriptingGlobals.ts | 1 + src/client/util/SearchUtil.ts | 20 +- src/client/util/SettingsManager.tsx | 323 +++++---- src/client/util/SharingManager.tsx | 753 +++++++++++---------- src/client/util/SnappingManager.ts | 3 + src/client/util/UndoManager.ts | 90 +-- src/client/util/reportManager/ReportManager.tsx | 38 +- src/client/views/ContextMenuItem.tsx | 15 +- src/client/views/DashboardView.tsx | 41 +- src/client/views/DictationOverlay.tsx | 55 +- src/client/views/DocComponent.tsx | 5 +- src/client/views/DocumentButtonBar.tsx | 114 ++-- src/client/views/EditableView.tsx | 23 +- src/client/views/FieldsDropdown.tsx | 16 +- src/client/views/FilterPanel.tsx | 1 + src/client/views/GlobalKeyHandler.ts | 58 +- src/client/views/InkingStroke.tsx | 109 +-- src/client/views/MainView.tsx | 42 +- src/client/views/MetadataEntryMenu.tsx | 196 ------ src/client/views/OverlayView.tsx | 7 +- src/client/views/PropertiesButtons.tsx | 182 +++-- src/client/views/PropertiesView.tsx | 44 +- src/client/views/SidebarAnnos.tsx | 13 +- src/client/views/StyleProvider.tsx | 75 +- src/client/views/UndoStack.tsx | 19 +- src/client/views/animationtimeline/Timeline.tsx | 1 + src/client/views/animationtimeline/Track.tsx | 2 +- .../collections/CollectionMasonryViewFieldRow.tsx | 96 +-- src/client/views/collections/CollectionMenu.tsx | 19 +- .../views/collections/CollectionNoteTakingView.tsx | 85 ++- .../views/collections/CollectionPileView.tsx | 13 +- .../collections/CollectionStackedTimeline.tsx | 152 +++-- src/client/views/collections/CollectionSubView.tsx | 31 +- .../views/collections/CollectionTimeView.tsx | 32 +- .../views/collections/CollectionTreeView.tsx | 79 ++- src/client/views/collections/TabDocView.tsx | 347 +++++----- src/client/views/collections/TreeView.tsx | 46 +- .../CollectionFreeFormInfoUI.tsx | 33 +- .../CollectionFreeFormLayoutEngines.tsx | 38 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 295 +++++--- .../collections/collectionFreeForm/MarqueeView.tsx | 10 +- .../collectionSchema/SchemaTableCell.tsx | 33 +- src/client/views/global/globalScripts.ts | 13 +- src/client/views/linking/LinkMenuItem.tsx | 32 +- src/client/views/linking/LinkPopup.tsx | 21 +- .../views/nodes/CollectionFreeFormDocumentView.tsx | 44 +- src/client/views/nodes/ComparisonBox.tsx | 17 +- src/client/views/nodes/DataVizBox/DataVizBox.tsx | 115 ++-- .../nodes/DataVizBox/components/Histogram.tsx | 230 +++---- .../nodes/DataVizBox/components/LineChart.tsx | 107 ++- .../views/nodes/DataVizBox/components/TableBox.tsx | 80 ++- src/client/views/nodes/DocumentContentsView.tsx | 33 +- src/client/views/nodes/DocumentLinksButton.tsx | 34 +- src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/FieldView.tsx | 10 +- src/client/views/nodes/FontIconBox/FontIconBox.tsx | 60 +- src/client/views/nodes/ImageBox.tsx | 7 +- src/client/views/nodes/KeyValuePair.tsx | 13 +- src/client/views/nodes/LabelBox.tsx | 25 +- src/client/views/nodes/LinkAnchorBox.tsx | 6 +- src/client/views/nodes/MapBox/MapBox.tsx | 25 +- .../views/nodes/MapboxMapBox/MapboxContainer.tsx | 31 +- src/client/views/nodes/PDFBox.tsx | 100 ++- .../views/nodes/RecordingBox/RecordingBox.tsx | 37 +- src/client/views/nodes/ScreenshotBox.tsx | 29 +- src/client/views/nodes/VideoBox.tsx | 103 ++- src/client/views/nodes/WebBox.tsx | 153 +++-- .../views/nodes/formattedText/DashFieldView.tsx | 310 +++++---- .../nodes/formattedText/FormattedTextBox.scss | 3 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 2 +- .../formattedText/ProsemirrorExampleTransfer.ts | 51 +- .../views/nodes/formattedText/RichTextMenu.tsx | 226 +++---- .../views/nodes/formattedText/RichTextRules.ts | 2 +- src/client/views/nodes/formattedText/marks_rts.ts | 30 +- src/client/views/nodes/formattedText/nodes_rts.ts | 14 +- src/client/views/nodes/trails/PresBox.tsx | 635 ++++++++++------- src/client/views/nodes/trails/PresElementBox.tsx | 96 +-- src/client/views/pdf/Annotation.tsx | 4 +- src/client/views/pdf/GPTPopup/GPTPopup.tsx | 3 +- src/client/views/search/SearchBox.tsx | 68 +- src/client/views/topbar/TopBar.tsx | 25 +- src/fields/Doc.ts | 56 +- src/fields/List.ts | 34 +- src/fields/ObjectField.ts | 1 + src/fields/Proxy.ts | 17 +- src/fields/Schema.ts | 2 +- src/fields/Types.ts | 15 +- src/fields/documentSchemas.ts | 3 +- src/mobile/ImageUpload.tsx | 77 +-- src/mobile/MobileInterface.tsx | 2 +- src/server/ApiManagers/UploadManager.ts | 4 +- 104 files changed, 3740 insertions(+), 3134 deletions(-) delete mode 100644 src/client/views/MetadataEntryMenu.tsx (limited to 'src/client/views/collections') diff --git a/.eslintrc.json b/.eslintrc.json index 0c4e375a9..780626412 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -53,6 +53,7 @@ "react/destructuring-assignment": 0, "no-restricted-globals": ["error", "event"], "no-param-reassign": ["error", { "props": false }], + "import/no-cycle": 0, "no-alert": 0, "radix": "off" }, diff --git a/package-lock.json b/package-lock.json index cc1a0ea64..c616bb6ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -183,6 +183,7 @@ "react-measure": "^2.5.2", "react-resizable": "^3.0.5", "react-select": "^5.8.0", + "react-type-animation": "^3.2.0", "react-xarrows": "^2.0.2", "readline": "^1.3.0", "recharts": "^2.10.3", @@ -287,7 +288,6 @@ "jsdom": "^24.0.0", "mocha": "^10.2.0", "prettier": "^3.1.0", - "react-type-animation": "^3.2.0", "scss-loader": "0.0.1", "style-loader": "^4.0.0", "ts-loader": "^9.5.1", @@ -30110,7 +30110,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/react-type-animation/-/react-type-animation-3.2.0.tgz", "integrity": "sha512-WXTe0i3rRNKjmggPvT5ntye1QBt0ATGbijeW6V3cQe2W0jaMABXXlPPEdtofnS9tM7wSRHchEvI9SUw+0kUohw==", - "dev": true, "peerDependencies": { "prop-types": "^15.5.4", "react": ">= 15.0.0", diff --git a/package.json b/package.json index 3f2f5a70f..a3ba6bdd9 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,6 @@ "jsdom": "^24.0.0", "mocha": "^10.2.0", "prettier": "^3.1.0", - "react-type-animation": "^3.2.0", "scss-loader": "0.0.1", "style-loader": "^4.0.0", "ts-loader": "^9.5.1", @@ -267,6 +266,7 @@ "react-measure": "^2.5.2", "react-resizable": "^3.0.5", "react-select": "^5.8.0", + "react-type-animation": "^3.2.0", "react-xarrows": "^2.0.2", "readline": "^1.3.0", "recharts": "^2.10.3", diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts index 757031fec..07a2708ec 100644 --- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts +++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts @@ -1,3 +1,5 @@ +/* eslint-disable no-use-before-define */ +import Photos = require('googlephotos'); import { AssertionError } from 'assert'; import { EditorState } from 'prosemirror-state'; import { ClientUtils } from '../../../ClientUtils'; @@ -5,14 +7,12 @@ import { Doc, DocListCastAsync, Opt } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { RichTextField } from '../../../fields/RichTextField'; import { RichTextUtils } from '../../../fields/RichTextUtils'; -import { Cast, StrCast } from '../../../fields/Types'; -import { ImageField } from '../../../fields/URLField'; +import { Cast, ImageCast, StrCast } from '../../../fields/Types'; import { MediaItem, NewMediaItemResult } from '../../../server/apis/google/SharedTypes'; import { Networking } from '../../Network'; import { DocUtils, Docs, DocumentOptions } from '../../documents/Documents'; import { FormattedTextBox } from '../../views/nodes/formattedText/FormattedTextBox'; import { GoogleAuthenticationManager } from '../GoogleAuthenticationManager'; -import Photos = require('googlephotos'); export namespace GooglePhotos { const endpoint = async () => new Photos(await GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken()); @@ -76,17 +76,16 @@ export namespace GooglePhotos { export const CollectionToAlbum = async (options: AlbumCreationOptions): Promise> => { const { collection, title, descriptionKey, tag } = options; const dataDocument = Doc.GetProto(collection); - const images = ((await DocListCastAsync(dataDocument.data)) || []).filter(doc => Cast(doc.data, ImageField)); + const images = ((await DocListCastAsync(dataDocument.data)) || []).filter(doc => ImageCast(doc.data)); if (!images || !images.length) { return undefined; } - const resolved = title ? title : StrCast(collection.title) || `Dash Collection (${collection[Id]}`; + const resolved = title || StrCast(collection.title) || `Dash Collection (${collection[Id]}`; const { id, productUrl } = await Create.Album(resolved); const response = await Transactions.UploadImages(images, { id }, descriptionKey); if (response) { const { results, failed } = response; - let index: Opt; - while ((index = failed.pop()) !== undefined) { + for (let index = failed.pop(); index !== undefined; index = failed.pop()) { Doc.RemoveDocFromList(dataDocument, 'data', images.splice(index, 1)[0]); } const mediaItems: MediaItem[] = results.map(item => item.mediaItem); @@ -97,13 +96,12 @@ export namespace GooglePhotos { for (let i = 0; i < images.length; i++) { const image = Doc.GetProto(images[i]); const mediaItem = mediaItems[i]; - if (!mediaItem) { - continue; + if (mediaItem) { + image.googlePhotosId = mediaItem.id; + image.googlePhotosAlbumUrl = productUrl; + image.googlePhotosUrl = mediaItem.productUrl || mediaItem.baseUrl; + idMapping[mediaItem.id] = image; } - image.googlePhotosId = mediaItem.id; - image.googlePhotosAlbumUrl = productUrl; - image.googlePhotosUrl = mediaItem.productUrl || mediaItem.baseUrl; - idMapping[mediaItem.id] = image; } collection.googlePhotosAlbumUrl = productUrl; collection.googlePhotosIdMapping = idMapping; @@ -114,6 +112,7 @@ export namespace GooglePhotos { Transactions.AddTextEnrichment(collection, `Find me at ${ClientUtils.prepend(`/doc/${collection[Id]}?sharing=true`)}`); return { albumId: id, mediaItems }; } + return undefined; }; } @@ -124,7 +123,7 @@ export namespace GooglePhotos { await GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(); const response = await Query.ContentSearch(requested); const uploads = await Transactions.WriteMediaItemsToServer(response); - const children = uploads.map((upload: Transactions.UploadInformation) => Docs.Create.ImageDocument(ClientUtils.fileUrl(upload.fileNames.clean) /*, {"data_contentSize":upload.contentSize}*/)); + const children = uploads.map((upload: Transactions.UploadInformation) => Docs.Create.ImageDocument(ClientUtils.fileUrl(upload.fileNames.clean) /* , {"data_contentSize":upload.contentSize} */)); const options = { _width: 500, _height: 500 }; return constructor(children, options); }; @@ -144,7 +143,7 @@ export namespace GooglePhotos { const images = (await DocListCastAsync(collection.data))!.map(Doc.GetProto); images?.forEach(image => tagMapping.set(image[Id], ContentCategories.NONE)); const values = Object.values(ContentCategories).filter(value => value !== ContentCategories.NONE); - for (const value of values) { + values.forEach(async value => { const searched = (await ContentSearch({ included: [value] }))?.mediaItems?.map(({ id }) => id); searched?.forEach(async id => { const image = await Cast(idMapping[id], Doc); @@ -154,7 +153,7 @@ export namespace GooglePhotos { !tags?.includes(value) && tagMapping.set(key, tags + delimiter + value); } }); - } + }); images?.forEach(image => { const concatenated = tagMapping.get(image[Id])!; const tags = concatenated.split(delimiter); @@ -200,9 +199,10 @@ export namespace GooglePhotos { export const AlbumSearch = async (albumId: string, pageSize = 100): Promise => { const photos = await endpoint(); const mediaItems: MediaItem[] = []; - let nextPageTokenStored: Opt = undefined; + let nextPageTokenStored: Opt; const found = 0; do { + // eslint-disable-next-line no-await-in-loop const response: any = await photos.mediaItems.search(albumId, pageSize, nextPageTokenStored); mediaItems.push(...response.mediaItems); nextPageTokenStored = response.nextPageToken; @@ -222,7 +222,7 @@ export namespace GooglePhotos { excluded.length && excluded.forEach(category => contentFilter.addExcludedContentCategories(category)); filters.setContentFilter(contentFilter); - const date = options.date; + const { date } = options; if (date) { const dateFilter = new photos.DateFilter(); if (date instanceof Date) { @@ -240,15 +240,11 @@ export namespace GooglePhotos { }); }; - export const GetImage = async (mediaItemId: string): Promise => { - return (await endpoint()).mediaItems.get(mediaItemId); - }; + export const GetImage = async (mediaItemId: string): Promise => (await endpoint()).mediaItems.get(mediaItemId); } namespace Create { - export const Album = async (title: string) => { - return (await endpoint()).albums.create(title); - }; + export const Album = async (title: string) => (await endpoint()).albums.create(title); } export namespace Transactions { @@ -278,6 +274,7 @@ export namespace GooglePhotos { return enrichmentItem.id; } } + return undefined; }; export const WriteMediaItemsToServer = async (body: { mediaItems: any[] }): Promise => { @@ -291,9 +288,12 @@ export namespace GooglePhotos { return undefined; } const baseUrls: string[] = await Promise.all( - response.results.map(item => { - return new Promise(resolve => Query.GetImage(item.mediaItem.id).then(item => resolve(item.baseUrl))); - }) + response.results.map( + item => + new Promise(resolve => { + Query.GetImage(item.mediaItem.id).then(item => resolve(item.baseUrl)); + }) + ) ); return baseUrls; }; @@ -303,27 +303,25 @@ export namespace GooglePhotos { failed: number[]; } - export const UploadImages = async (sources: Doc[], album?: AlbumReference, descriptionKey = 'caption'): Promise> => { + export const UploadImages = async (sources: Doc[], albumIn?: AlbumReference, descriptionKey = 'caption'): Promise> => { await GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(); - if (album && 'title' in album) { - album = await Create.Album(album.title); - } + const album = albumIn && 'title' in albumIn ? await Create.Album(albumIn.title) : albumIn; const media: MediaInput[] = []; - for (const source of sources) { - const data = Cast(Doc.GetProto(source).data, ImageField); - if (!data) { - return; - } - const url = data.url.href; - const target = Doc.MakeEmbedding(source); - const description = parseDescription(target, descriptionKey); - await DocUtils.makeCustomViewClicked(target, Docs.Create.FreeformDocument); - media.push({ url, description }); - } + sources + .filter(source => ImageCast(Doc.GetProto(source).data)) + .forEach(async source => { + const data = ImageCast(Doc.GetProto(source).data); + const url = data.url.href; + const target = Doc.MakeEmbedding(source); + const description = parseDescription(target, descriptionKey); + await DocUtils.makeCustomViewClicked(target, Docs.Create.FreeformDocument); + media.push({ url, description }); + }); if (media.length) { const results = await Networking.PostToServer('/googlePhotosMediaPost', { media, album }); return results; } + return undefined; }; const parseDescription = (document: Doc, descriptionKey: string) => { diff --git a/src/client/util/BranchingTrailManager.tsx b/src/client/util/BranchingTrailManager.tsx index 02879e3c4..28c00644f 100644 --- a/src/client/util/BranchingTrailManager.tsx +++ b/src/client/util/BranchingTrailManager.tsx @@ -1,18 +1,31 @@ +/* eslint-disable react/no-unused-class-component-methods */ +/* eslint-disable react/no-array-index-key */ import { action, computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc } from '../../fields/Doc'; import { Id } from '../../fields/FieldSymbols'; -import { PresBox } from '../views/nodes/trails'; import { OverlayView } from '../views/OverlayView'; +import { PresBox } from '../views/nodes/trails'; import { DocumentManager } from './DocumentManager'; -import { Docs } from '../documents/Documents'; -import { nullAudio } from '../../fields/URLField'; @observer export class BranchingTrailManager extends React.Component { + // eslint-disable-next-line no-use-before-define public static Instance: BranchingTrailManager; + // stack of the history + @observable private slideHistoryStack: String[] = []; + @observable private containsSet: Set = new Set(); + // docId to Doc map + @observable private docIdToDocMap: Map = new Map(); + + // prev pres to copmare with + @observable private prevPresId: String | null = null; + @action setPrevPres = action((newId: String | null) => { + this.prevPresId = newId; + }); + constructor(props: any) { super(props); makeObservable(this); @@ -22,7 +35,7 @@ export class BranchingTrailManager extends React.Component { } setupUi = () => { - OverlayView.Instance.addWindow(, { x: 100, y: 150, width: 1000, title: 'Branching Trail' }); + OverlayView.Instance.addWindow(, { x: 100, y: 150, width: 1000, title: 'Branching Trail' }); // OverlayView.Instance.forceUpdate(); console.log(OverlayView.Instance); // let hi = Docs.Create.TextDocument("beee", { @@ -36,23 +49,11 @@ export class BranchingTrailManager extends React.Component { console.log(DocumentManager._overlayViews); }; - // stack of the history - @observable private slideHistoryStack: String[] = []; @action setSlideHistoryStack = action((newArr: String[]) => { this.slideHistoryStack = newArr; }); - @observable private containsSet: Set = new Set(); - - // prev pres to copmare with - @observable private prevPresId: String | null = null; - @action setPrevPres = action((newId: String | null) => { - this.prevPresId = newId; - }); - - // docId to Doc map - @observable private docIdToDocMap: Map = new Map(); - + // eslint-disable-next-line react/sort-comp observeDocumentChange = (targetDoc: Doc, pres: PresBox) => { const presId = pres.Document[Id]; if (this.prevPresId === presId) { @@ -106,7 +107,7 @@ export class BranchingTrailManager extends React.Component { if (this.slideHistoryStack.length === 0) { Doc.UserDoc().isBranchingMode = false; } - //PresBox.NavigateToTarget(targetDoc, targetDoc); + // PresBox.NavigateToTarget(targetDoc, targetDoc); }; @computed get trailBreadcrumbs() { @@ -116,11 +117,11 @@ export class BranchingTrailManager extends React.Component { const [presId, targetDocId] = info.split(','); const doc = this.docIdToDocMap.get(targetDocId); if (!doc) { - return <>; + return null; } return ( - -{'>'} diff --git a/src/client/util/CalendarManager.tsx b/src/client/util/CalendarManager.tsx index 6e9094b3a..46aa4d238 100644 --- a/src/client/util/CalendarManager.tsx +++ b/src/client/util/CalendarManager.tsx @@ -1,4 +1,10 @@ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +import { DateRangePicker, Provider, defaultTheme } from '@adobe/react-spectrum'; +import { IconLookup, faPlus } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { TextField } from '@mui/material'; +import { Button } from 'browndash-components'; import { action, computed, makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; @@ -6,21 +12,16 @@ import Select from 'react-select'; import { Doc, DocListCast } from '../../fields/Doc'; import { DocData } from '../../fields/DocSymbols'; import { StrCast } from '../../fields/Types'; +import { Docs } from '../documents/Documents'; import { DictationOverlay } from '../views/DictationOverlay'; import { MainViewModal } from '../views/MainViewModal'; +import { ObservableReactComponent } from '../views/ObservableReactComponent'; import { DocumentView } from '../views/nodes/DocumentView'; import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox'; import './CalendarManager.scss'; import { DocumentManager } from './DocumentManager'; import { SelectionManager } from './SelectionManager'; import { SettingsManager } from './SettingsManager'; -// import { DateRange, Range, RangeKeyDict } from 'react-date-range'; -import { DateRangePicker, Provider, defaultTheme } from '@adobe/react-spectrum'; -import { IconLookup, faPlus } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Button } from 'browndash-components'; -import { Docs } from '../documents/Documents'; -import { ObservableReactComponent } from '../views/ObservableReactComponent'; // import 'react-date-range/dist/styles.css'; // import 'react-date-range/dist/theme/default.css'; @@ -47,6 +48,7 @@ const formatCalendarDateToString = (calendarDate: any) => { @observer export class CalendarManager extends ObservableReactComponent<{}> { + // eslint-disable-next-line no-use-before-define public static Instance: CalendarManager; @observable private isOpen = false; @observable private targetDoc: Doc | undefined = undefined; // the target document @@ -83,10 +85,10 @@ export class CalendarManager extends ObservableReactComponent<{}> { this.creationType = type; }; - public open = (target?: DocumentView, target_doc?: Doc) => { + public open = (target?: DocumentView, targetDoc?: Doc) => { console.log('hi'); runInAction(() => { - this.targetDoc = target_doc || target?.Document; + this.targetDoc = targetDoc || target?.Document; this.targetDocView = target; DictationOverlay.Instance.hasActiveModal = true; this.isOpen = this.targetDoc !== undefined; @@ -117,7 +119,7 @@ export class CalendarManager extends ObservableReactComponent<{}> { @action handleSelectChange = (option: any) => { if (option) { - let selectOpt = option as CalendarSelectOptions; + const selectOpt = option as CalendarSelectOptions; this.selectedExistingCalendarOption = selectOpt; this.calendarName = selectOpt.value; // or label } @@ -136,7 +138,7 @@ export class CalendarManager extends ObservableReactComponent<{}> { // TODO: Make undoable private addToCalendar = () => { - let docs = SelectionManager.Views.length < 2 ? [this.targetDoc] : SelectionManager.Views.map(docView => docView.Document); + const docs = SelectionManager.Views.length < 2 ? [this.targetDoc] : SelectionManager.Views.map(docView => docView.Document); const targetDoc = this.layoutDocAcls ? docs[0] : docs[0]?.[DocData]; // doc to add to calendar console.log(targetDoc); @@ -159,7 +161,7 @@ export class CalendarManager extends ObservableReactComponent<{}> { } } else { // find existing calendar based on selected name (should technically always find one) - const existingCalendar = this.existingCalendars.find(calendar => StrCast(calendar.title) === this.calendarName); + const existingCalendar = this.existingCalendars.find(findCal => StrCast(findCal.title) === this.calendarName); if (existingCalendar) calendar = existingCalendar; else { this.errorMessage = 'Must select an existing calendar'; @@ -252,11 +254,9 @@ export class CalendarManager extends ObservableReactComponent<{}> { @computed get calendarInterface() { - let docs = SelectionManager.Views.length < 2 ? [this.targetDoc] : SelectionManager.Views.map(docView => docView.Document); + const docs = SelectionManager.Views.length < 2 ? [this.targetDoc] : SelectionManager.Views.map(docView => docView.Document); const targetDoc = this.layoutDocAcls ? docs[0] : docs[0]?.[DocData]; - const currentDate = new Date(); - return (
    { {this.focusOn(docs.length < 2 ? StrCast(targetDoc?.title, 'this document') : '-multiple-')}

    -
    this.setInterationType('new-calendar')}> +
    this.setInterationType('new-calendar')}> Add to New Calendar
    -
    this.setInterationType('existing-calendar')}> +
    this.setInterationType('existing-calendar')}> Add to Existing calendar
    @@ -317,7 +317,8 @@ export class CalendarManager extends ObservableReactComponent<{}> { color: StrCast(Doc.UserDoc().userColor), width: '100%', }), - }}> + }} + /> )}
    @@ -351,6 +352,6 @@ export class CalendarManager extends ObservableReactComponent<{}> { } render() { - return ; + return ; } } diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 27ae5c9a0..6dba8027d 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -709,7 +709,7 @@ pie title Minerals in my tap water return [ { title: "Back", toolTip: "Go back", btnType: ButtonType.ClickButton, icon: "arrow-left", scripts: { onClick: '{ return webBack(); }' }}, { title: "Forward", toolTip: "Go forward", btnType: ButtonType.ClickButton, icon: "arrow-right", scripts: { onClick: '{ return webForward(); }'}}, - { title: "URL", toolTip: "URL", width: 250, btnType: ButtonType.EditableText, icon: "lock", ignoreClick: true, scripts: { script: '{ return webSetURL(value, _readOnly_); }'} }, + { title: "URL", toolTip: "URL", width: 250, btnType: ButtonType.EditText, icon: "lock", ignoreClick: true, scripts: { script: '{ return webSetURL(value, _readOnly_); }'} }, ]; } static videoTools() { diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 62f055f1a..3890b7845 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -495,18 +495,20 @@ export namespace DragManager { .filter(pb => pb.width && pb.height) .map((pb, i) => pb.getContext('2d')!.drawImage(pdfBoxSrc[i], 0, 0)); } - [dragElement, ...Array.from(dragElement.getElementsByTagName('*'))].forEach(ele => { - (ele as any).style && ((ele as any).style.pointerEvents = 'none'); - }); + [dragElement, ...Array.from(dragElement.getElementsByTagName('*'))] + .map(dele => (dele as any).style) + .forEach(style => { + style && (style.pointerEvents = 'none'); + }); dragDiv.appendChild(dragElement); if (dragElement !== ele) { - const children = [Array.from(ele.children), Array.from(dragElement.children)]; - while (children[0].length) { - const childs = [children[0].pop(), children[1].pop()]; + const dragChildren = [Array.from(ele.children), Array.from(dragElement.children)]; + while (dragChildren[0].length) { + const childs = [dragChildren[0].pop(), dragChildren[1].pop()]; if (childs[0]?.children) { - children[0].push(...Array.from(childs[0].children)); - children[1].push(...Array.from(childs[1]!.children)); + dragChildren[0].push(...Array.from(childs[0].children)); + dragChildren[1].push(...Array.from(childs[1]!.children)); } if (childs[0]?.scrollTop) childs[1]!.scrollTop = childs[0].scrollTop; } diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx index c261c0f1e..8d84dbad8 100644 --- a/src/client/util/GroupManager.tsx +++ b/src/client/util/GroupManager.tsx @@ -1,3 +1,5 @@ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Button, IconButton, Size, Type } from 'browndash-components'; import { action, computed, makeObservable, observable } from 'mobx'; @@ -30,6 +32,7 @@ export interface UserOptions { @observer export class GroupManager extends ObservableReactComponent<{}> { + // eslint-disable-next-line no-use-before-define static Instance: GroupManager; @observable isOpen: boolean = false; // whether the GroupManager is to be displayed or not. @observable private users: string[] = []; // list of users populated from the database. @@ -160,7 +163,7 @@ export class GroupManager extends ObservableReactComponent<{}> { addGroup(groupDoc: Doc): boolean { if (this.GroupManagerDoc) { Doc.AddDocToList(this.GroupManagerDoc, 'data', groupDoc); - this.GroupManagerDoc['data_modificationDate'] = new DateField(); + this.GroupManagerDoc.data_modificationDate = new DateField(); return true; } return false; @@ -202,7 +205,7 @@ export class GroupManager extends ObservableReactComponent<{}> { !memberList.includes(email) && memberList.push(email); groupDoc.members = JSON.stringify(memberList); SharingManager.Instance.shareWithAddedMember(groupDoc, email); - this.GroupManagerDoc && (this.GroupManagerDoc['data_modificationDate'] = new DateField()); + this.GroupManagerDoc && (this.GroupManagerDoc.data_modificationDate = new DateField()); } } @@ -216,10 +219,9 @@ export class GroupManager extends ObservableReactComponent<{}> { const memberList = JSON.parse(StrCast(groupDoc.members)); const index = memberList.indexOf(email); if (index !== -1) { - const user = memberList.splice(index, 1)[0]; groupDoc.members = JSON.stringify(memberList); SharingManager.Instance.removeMember(groupDoc, email); - this.GroupManagerDoc && (this.GroupManagerDoc['data_modificationDate'] = new DateField()); + this.GroupManagerDoc && (this.GroupManagerDoc.data_modificationDate = new DateField()); } } } @@ -275,7 +277,9 @@ export class GroupManager extends ObservableReactComponent<{}> { TaskCompletionBox.textDisplayed = 'Group created!'; TaskCompletionBox.taskCompleted = true; setTimeout( - action(() => (TaskCompletionBox.taskCompleted = false)), + action(() => { + TaskCompletionBox.taskCompleted = false; + }), 2000 ); }; @@ -292,7 +296,7 @@ export class GroupManager extends ObservableReactComponent<{}> {

    - (this.buttonColour = this.inputRef.current?.value ? 'black' : '#979797'))} /> + { + this.buttonColour = this.inputRef.current?.value ? 'black' : '#979797'; + })} + />
    this.setInternalSharing({ user, linkDatabase, sharingDoc, userColor }, e.currentTarget.value, undefined)}> + {this.sharingOptions(uniform)} + + ) : ( +
    + {concat(ReverseHierarchyMap.get(permissions)?.image, ' ', permissions)} +   +
    + )} +
    +
    + ); }); - const docs = await DocServer.GetRefFields(raw.reduce((list, user) => [...list, user.sharingDocumentId, user.linkDatabaseId], [] as string[])); - raw.map( - action((newUser: User) => { - const sharingDoc = docs[newUser.sharingDocumentId]; - const linkDatabase = docs[newUser.linkDatabaseId]; - if (sharingDoc instanceof Doc && linkDatabase instanceof Doc) { - if (!this.users.find(user => user.user.email === newUser.email)) { - this.users.push({ user: newUser, sharingDoc, linkDatabase, userColor: StrCast(sharingDoc.userColor) }); - //LinkManager.addLinkDB(linkDatabase); - } - } - }) + + // checks if every doc has the same author + const sameAuthor = docs.every(doc => doc?.author === docs[0]?.author); + + // the owner of the doc and the current user are placed at the top of the user list. + const userKey = `acl-${normalizeEmail(ClientUtils.CurrentUserEmail())}`; + const curUserPermission = StrCast(targetDoc[userKey]); + // const curUserPermission = HierarchyMapping.get(effectiveAcls[0])!.name + userListContents.unshift( + sameAuthor ? ( +
    + {targetDoc?.author === ClientUtils.CurrentUserEmail() ? 'Me' : StrCast(targetDoc?.author)} +
    +
    Owner
    +
    +
    + ) : null, + sameAuthor && targetDoc?.author !== ClientUtils.CurrentUserEmail() ? ( +
    + Me +
    +
    + {effectiveAcls.every(acl => acl === effectiveAcls[0]) ? concat(ReverseHierarchyMap.get(curUserPermission!)?.image, ' ', curUserPermission) : '-multiple-'} +   +
    +
    +
    + ) : null + ); + + // the list of groups shared with + const groupListMap: (Doc | { title: string })[] = groups.filter(({ title }) => (docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(StrCast(title))}`) : true)); + groupListMap.unshift({ title: 'Guest' }); // , { title: "ALL" }); + const groupListContents = groupListMap.map(group => { + const groupKey = `acl-${StrCast(group.title)}`; + const uniform = docs.every(doc => doc?.[DocAcl]?.[groupKey] === docs[0]?.[DocAcl]?.[groupKey]); + const permissions = uniform ? StrCast(targetDoc?.[groupKey]) : '-multiple-'; + + return !permissions ? null : ( +
    +
    {StrCast(group.title)}
    +   + {group instanceof Doc ? ( + } + size={Size.XSMALL} + color={SettingsManager.userColor} + onClick={action(() => { + GroupManager.Instance.currentGroup = group; + })} + /> + ) : null} +
    + {admin || this.myDocAcls ? ( + + ) : ( +
    + {concat(ReverseHierarchyMap.get(permissions)?.image, ' ', permissions)} +   +
    + )} +
    +
    ); - this.populating = false; - } - }; + }); + return ( +
    + {GroupManager.Instance?.currentGroup ? ( + { + GroupManager.Instance.currentGroup = undefined; + })} + /> + ) : null} +
    +

    +

    window.open('https://brown-dash.github.io/Dash-Documentation/features/collaboration/', '_blank')}> + window.open('https://brown-dash.github.io/Dash-Documentation/features/collaboration/', '_blank')} /> +
    + Share + {this.focusOn(docs.length < 2 ? StrCast(targetDoc?.title, 'this document') : '-multiple-')} +

    +
    +
    +
    +
    + {admin ? ( +
    +
    + + {this.sharingOptions(true)} + +
    +
    +
    +
    +
    + { + this.showUserOptions = !this.showUserOptions; + })} + />{' '} + + { + this.showGroupOptions = !this.showGroupOptions; + })} + />{' '} + +
    + +
    + {Doc.noviceMode ? null : ( +
    + { + this.upgradeNested = !this.upgradeNested; + })} + checked={this.upgradeNested} + />{' '} + + { + this.layoutDocAcls = !this.layoutDocAcls; + })} + checked={this.layoutDocAcls} + />{' '} + +
    + )} +
    +
    + ) : ( +
    +
    +
    + { + this.layoutDocAcls = !this.layoutDocAcls; + })} + checked={this.layoutDocAcls} + />{' '} + +
    +
    +
    + )} +
    +
    +
    { + this.individualSort = this.individualSort === 'ascending' ? 'descending' : this.individualSort === 'descending' ? 'none' : 'ascending'; + })}> +
    + Individuals + } + size={Size.XSMALL} + color={StrCast(Doc.UserDoc().userColor)} + /> +
    +
    +
    {userListContents}
    +
    +
    +
    { + this.groupSort = this.groupSort === 'ascending' ? 'descending' : this.groupSort === 'descending' ? 'none' : 'ascending'; + })}> +
    + Groups + } size={Size.XSMALL} color={StrCast(Doc.UserDoc().userColor)} onClick={action(() => GroupManager.Instance.open())} /> + } + size={Size.XSMALL} + color={StrCast(Doc.UserDoc().userColor)} + /> +
    +
    +
    {groupListContents}
    +
    +
    +
    +
    + ); + } /** * Shares the document with a user. @@ -200,12 +479,69 @@ export class SharingManager extends React.Component<{}> { } }); }, 'set group permissions'); + /** + * Populates the list of validated users (this.users) by adding registered users which have a sharingDocument. + */ + populateUsers = async () => { + if (!this.populating && Doc.UserDoc()[Id] !== Utils.GuestID()) { + this.populating = true; + const userList = await RequestPromise.get(ClientUtils.prepend('/getUsers')); + const raw = (JSON.parse(userList) as User[]).filter(user => user.email !== 'guest' && user.email !== ClientUtils.CurrentUserEmail()); + runInAction(() => { + FieldLoader.ServerLoadStatus.message = 'users'; + }); + const docs = await DocServer.GetRefFields(raw.reduce((list, user) => [...list, user.sharingDocumentId, user.linkDatabaseId], [] as string[])); + raw.map( + action((newUser: User) => { + const sharingDoc = docs[newUser.sharingDocumentId]; + const linkDatabase = docs[newUser.linkDatabaseId]; + if (sharingDoc instanceof Doc && linkDatabase instanceof Doc) { + if (!this.users.find(user => user.user.email === newUser.email)) { + this.users.push({ user: newUser, sharingDoc, linkDatabase, userColor: StrCast(sharingDoc.userColor) }); + // LinkManager.addLinkDB(linkDatabase); + } + } + }) + ); + this.populating = false; + } + }; + + // eslint-disable-next-line react/sort-comp + public close = action(() => { + this.isOpen = false; + this.selectedUsers = null; // resets the list of users and selected users (in the react-select component) + TaskCompletionBox.taskCompleted = false; + setTimeout( + action(() => { + // this.copied = false; + DictationOverlay.Instance.hasActiveModal = false; + this.targetDoc = undefined; + }), + 500 + ); + this.layoutDocAcls = false; + }); + + // eslint-disable-next-line react/no-unused-class-component-methods + public open = (target?: DocumentView, targetDoc?: Doc) => { + this.populateUsers(); + runInAction(() => { + this.targetDocView = target; + this.targetDoc = targetDoc || target?.Document; + DictationOverlay.Instance.hasActiveModal = true; + this.isOpen = this.targetDoc !== undefined; + this.permissions = SharingPermissions.Augment; + this.upgradeNested = true; + }); + }; /** * Shares the documents shared with a group with a new user who has been added to that group. * @param group * @param emailId */ + // eslint-disable-next-line react/no-unused-class-component-methods shareWithAddedMember = (group: Doc, emailId: string, retry: boolean = true) => { const user = this.users.find(({ user: { email } }) => email === emailId)!; const self = this; @@ -231,6 +567,7 @@ export class SharingManager extends React.Component<{}> { /** * Called from the properties sidebar to change permissions of a user. */ + // eslint-disable-next-line react/no-unused-class-component-methods shareFromPropertiesSidebar = undoable((shareWith: string, permission: SharingPermissions, docs: Doc[], layout: boolean) => { if (layout) this.layoutDocAcls = true; if (shareWith !== 'Guest') { @@ -254,6 +591,7 @@ export class SharingManager extends React.Component<{}> { * @param group * @param emailId */ + // eslint-disable-next-line react/no-unused-class-component-methods removeMember = (group: Doc, emailId: string) => { const user: ValidatedUser = this.users.find(({ user: { email } }) => email === emailId)!; @@ -277,6 +615,7 @@ export class SharingManager extends React.Component<{}> { * Removes a group's permissions from documents that have been shared with it. * @param group */ + // eslint-disable-next-line react/no-unused-class-component-methods removeGroup = (group: Doc) => { if (group.docsShared) { DocListCast(group.docsShared).forEach(doc => { @@ -299,25 +638,12 @@ export class SharingManager extends React.Component<{}> { // targetDoc["acl-" + PublicKey] = permission; // }s - /** - * Copies the Public sharing url to the user's clipboard. - */ - private copyURL = (e: any) => { - ClientUtils.CopyText(ClientUtils.shareUrl(this.targetDoc![Id])); - }; - - /** - * Returns the SharingPermissions (Admin, Can Edit etc) access that's used to share - */ - private sharingOptions(uniform: boolean, showGuestOptions?: boolean) { - const dropdownValues: string[] = showGuestOptions ? [SharingPermissions.None, SharingPermissions.View] : Object.values(SharingPermissions); - if (!uniform) dropdownValues.unshift('-multiple-'); - return dropdownValues.map(permission => ( - - )); - } + /** + * Copies the Public sharing url to the user's clipboard. + */ + private copyURL = () => { + ClientUtils.CopyText(ClientUtils.shareUrl(this.targetDoc![Id])); + }; private focusOn = (contents: string) => { const title = this.targetDoc ? StrCast(this.targetDoc.title) : ''; @@ -350,24 +676,6 @@ export class SharingManager extends React.Component<{}> { ); }; - /** - * Handles changes in the users selected in react-select - */ - @action - handleUsersChange = (selectedOptions: any) => { - this.selectedUsers = selectedOptions as UserOptions[]; - }; - - /** - * Handles changes in the permission chosen to share with someone with - */ - handlePermissionsChange = undoable( - action((event: React.ChangeEvent) => { - this.permissions = event.currentTarget.value as SharingPermissions; - }), - 'permission change' - ); - /** * Calls the relevant method for sharing, displays the popup, and resets the relevant variables. */ @@ -389,7 +697,9 @@ export class SharingManager extends React.Component<{}> { TaskCompletionBox.textDisplayed = 'Document shared!'; TaskCompletionBox.taskCompleted = true; setTimeout( - action(() => (TaskCompletionBox.taskCompleted = false)), + action(() => { + TaskCompletionBox.taskCompleted = false; + }), 2000 ); } @@ -418,263 +728,20 @@ export class SharingManager extends React.Component<{}> { const g2 = StrCast(group2.title); return g1 < g2 ? -1 : g1 === g2 ? 0 : 1; }; - /** - * @returns the main interface of the SharingManager. + * Returns the SharingPermissions (Admin, Can Edit etc) access that's used to share */ - @computed get sharingInterface() { - if (!this.targetDoc) return null; - TraceMobx(); - const groupList = GroupManager.Instance?.allGroups || []; - - const sortedUsers = this.users - .slice() - .sort(this.sortUsers) - .map(({ user: { email } }) => ({ label: email, value: indType + email })); - const sortedGroups = groupList - .slice() - .sort(this.sortGroups) - .map(({ title }) => ({ label: StrCast(title), value: groupType + StrCast(title) })); - - // the next block handles the users shown (individuals/groups/both) - const options: GroupedOptions[] = []; - if (GroupManager.Instance) { - if ((this.showUserOptions && this.showGroupOptions) || (!this.showUserOptions && !this.showGroupOptions)) { - options.push({ label: 'Individuals', options: sortedUsers }, { label: 'Groups', options: sortedGroups }); - } else if (this.showUserOptions) options.push({ label: 'Individuals', options: sortedUsers }); - else options.push({ label: 'Groups', options: sortedGroups }); - } - - const users = this.individualSort === 'ascending' ? this.users.slice().sort(this.sortUsers) : this.individualSort === 'descending' ? this.users.slice().sort(this.sortUsers).reverse() : this.users; - const groups = this.groupSort === 'ascending' ? groupList.slice().sort(this.sortGroups) : this.groupSort === 'descending' ? groupList.slice().sort(this.sortGroups).reverse() : groupList; - - let docs = SelectionManager.Views.length < 2 ? [this.targetDoc] : SelectionManager.Views.map(docView => docView.Document); - - if (this.myDocAcls) { - const newDocs: Doc[] = []; - SearchUtil.foreachRecursiveDoc(docs, (depth, doc) => newDocs.push(doc)); - docs = newDocs.filter(doc => GetEffectiveAcl(doc) === AclAdmin); - } - - const targetDoc = this.layoutDocAcls ? docs[0] : docs[0]?.[DocData]; - - // tslint:disable-next-line: no-unnecessary-callback-wrapper - const effectiveAcls = docs.map(doc => GetEffectiveAcl(doc)); - const admin = this.myDocAcls ? Boolean(docs.length) : effectiveAcls.every(acl => acl === AclAdmin); - - // users in common between all docs - const commonKeys = intersection(docs).reduce((list, doc) => (doc?.[DocAcl] ? [...list, ...Object.keys(doc[DocAcl])] : list), [] as string[]); - - // the list of users shared with - const userListContents = users - // .filter(({ user }) => (docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(user.email)}`) : docs[0]?.author !== user.email)) - .filter(({ user }) => docs[0]?.author !== user.email) - .map(({ user, linkDatabase, sharingDoc, userColor }) => { - const userKey = `acl-${normalizeEmail(user.email)}`; - const uniform = docs.every(doc => doc?.[DocAcl]?.[userKey] === docs[0]?.[DocAcl]?.[userKey]); - // const permissions = uniform ? StrCast(targetDoc?.[userKey]) : '-multiple-'; - let permissions = targetDoc[DocAcl][userKey] ? HierarchyMapping.get(targetDoc[DocAcl][userKey])?.name : StrCast(targetDoc[userKey]); - permissions = uniform ? StrCast(targetDoc?.[userKey]) : '-multiple-'; - - return !permissions ? null : ( -
    - {user.email} -
    - {admin || this.myDocAcls ? ( - - ) : ( -
    - {concat(ReverseHierarchyMap.get(permissions)?.image, ' ', permissions)} -   -
    - )} -
    -
    - ); - }); - - // checks if every doc has the same author - const sameAuthor = docs.every(doc => doc?.author === docs[0]?.author); - - // the owner of the doc and the current user are placed at the top of the user list. - const userKey = `acl-${normalizeEmail(ClientUtils.CurrentUserEmail())}`; - const curUserPermission = StrCast(targetDoc[userKey]); - // const curUserPermission = HierarchyMapping.get(effectiveAcls[0])!.name - userListContents.unshift( - sameAuthor ? ( -
    - {targetDoc?.author === ClientUtils.CurrentUserEmail() ? 'Me' : StrCast(targetDoc?.author)} -
    -
    Owner
    -
    -
    - ) : null, - sameAuthor && targetDoc?.author !== ClientUtils.CurrentUserEmail() ? ( -
    - Me -
    -
    - {effectiveAcls.every(acl => acl === effectiveAcls[0]) ? concat(ReverseHierarchyMap.get(curUserPermission!)?.image, ' ', curUserPermission) : '-multiple-'} -   -
    -
    -
    - ) : null - ); - - // the list of groups shared with - const groupListMap: (Doc | { title: string })[] = groups.filter(({ title }) => (docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(StrCast(title))}`) : true)); - groupListMap.unshift({ title: 'Guest' }); //, { title: "ALL" }); - const groupListContents = groupListMap.map(group => { - let groupKey = `acl-${StrCast(group.title)}`; - const uniform = docs.every(doc => doc?.[DocAcl]?.[groupKey] === docs[0]?.[DocAcl]?.[groupKey]); - const permissions = uniform ? StrCast(targetDoc?.[groupKey]) : '-multiple-'; - - return !permissions ? null : ( -
    -
    {StrCast(group.title)}
    -   - {group instanceof Doc ? } size={Size.XSMALL} color={SettingsManager.userColor} onClick={action(() => (GroupManager.Instance.currentGroup = group))} /> : null} -
    - {admin || this.myDocAcls ? ( - - ) : ( -
    - {concat(ReverseHierarchyMap.get(permissions)?.image, ' ', permissions)} -   -
    - )} -
    -
    - ); - }); - return ( -
    - {GroupManager.Instance?.currentGroup ? (GroupManager.Instance.currentGroup = undefined))} /> : null} -
    -

    -

    window.open('https://brown-dash.github.io/Dash-Documentation/features/collaboration/', '_blank')}> - window.open('https://brown-dash.github.io/Dash-Documentation/features/collaboration/', '_blank')} /> -
    - Share - {this.focusOn(docs.length < 2 ? StrCast(targetDoc?.title, 'this document') : '-multiple-')} -

    -
    -
    -
    -
    - {admin ? ( -
    -
    - - {this.sharingOptions(true)} - -
    -
    -
    -
    -
    - (this.showUserOptions = !this.showUserOptions))} /> - (this.showGroupOptions = !this.showGroupOptions))} /> -
    - -
    - {Doc.noviceMode ? null : ( -
    - (this.upgradeNested = !this.upgradeNested))} checked={this.upgradeNested} /> - (this.layoutDocAcls = !this.layoutDocAcls))} checked={this.layoutDocAcls} /> -
    - )} -
    -
    - ) : ( -
    -
    -
    - (this.layoutDocAcls = !this.layoutDocAcls))} checked={this.layoutDocAcls} /> -
    -
    -
    - )} -
    -
    -
    (this.individualSort = this.individualSort === 'ascending' ? 'descending' : this.individualSort === 'descending' ? 'none' : 'ascending'))}> -
    - Individuals - } - size={Size.XSMALL} - color={StrCast(Doc.UserDoc().userColor)} - /> -
    -
    -
    {userListContents}
    -
    -
    -
    (this.groupSort = this.groupSort === 'ascending' ? 'descending' : this.groupSort === 'descending' ? 'none' : 'ascending'))}> -
    - Groups - } size={Size.XSMALL} color={StrCast(Doc.UserDoc().userColor)} onClick={action(() => GroupManager.Instance.open())} /> - } - size={Size.XSMALL} - color={StrCast(Doc.UserDoc().userColor)} - /> -
    -
    -
    {groupListContents}
    -
    -
    -
    -
    - ); + private sharingOptions(uniform: boolean, showGuestOptions?: boolean) { + const dropdownValues: string[] = showGuestOptions ? [SharingPermissions.None, SharingPermissions.View] : Object.values(SharingPermissions); + if (!uniform) dropdownValues.unshift('-multiple-'); + return dropdownValues.map(permission => ( + + )); } render() { - return ; + return ; } } diff --git a/src/client/util/SnappingManager.ts b/src/client/util/SnappingManager.ts index eb47bbe88..3da85191f 100644 --- a/src/client/util/SnappingManager.ts +++ b/src/client/util/SnappingManager.ts @@ -10,6 +10,7 @@ export class SnappingManager { @observable _shiftKey = false; @observable _ctrlKey = false; @observable _metaKey = false; + @observable _showPresPaths = false; @observable _isLinkFollowing = false; @observable _isDragging: boolean = false; @observable _isResizing: string | undefined = undefined; // the string is the Id of the document being resized @@ -36,6 +37,7 @@ export class SnappingManager { public static get ShiftKey() { return this.Instance._shiftKey; } // prettier-ignore public static get CtrlKey() { return this.Instance._ctrlKey; } // prettier-ignore public static get MetaKey() { return this.Instance._metaKey; } // prettier-ignore + public static get ShowPresPaths() { return this.Instance._showPresPaths; } // prettier-ignore public static get IsLinkFollowing(){ return this.Instance._isLinkFollowing; } // prettier-ignore public static get IsDragging() { return this.Instance._isDragging; } // prettier-ignore public static get IsResizing() { return this.Instance._isResizing; } // prettier-ignore @@ -44,6 +46,7 @@ export class SnappingManager { public static SetShiftKey = (down: boolean) => runInAction(() => {this.Instance._shiftKey = down}); // prettier-ignore public static SetCtrlKey = (down: boolean) => runInAction(() => {this.Instance._ctrlKey = down}); // prettier-ignore public static SetMetaKey = (down: boolean) => runInAction(() => {this.Instance._metaKey = down}); // prettier-ignore + public static SetShowPresPaths = (paths:boolean) => runInAction(() => {this.Instance._showPresPaths = paths}); // prettier-ignore public static SetIsLinkFollowing= (follow:boolean)=> runInAction(() => {this.Instance._isLinkFollowing = follow}); // prettier-ignore public static SetIsDragging = (drag: boolean) => runInAction(() => {this.Instance._isDragging = drag}); // prettier-ignore public static SetIsResizing = (docid?:string) => runInAction(() => {this.Instance._isResizing = docid}); // prettier-ignore diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts index 4e941508d..956c0e674 100644 --- a/src/client/util/UndoManager.ts +++ b/src/client/util/UndoManager.ts @@ -1,8 +1,11 @@ +/* eslint-disable prefer-spread */ +/* eslint-disable no-use-before-define */ import { action, observable, runInAction } from 'mobx'; import { Without } from '../../Utils'; import { RichTextField } from '../../fields/RichTextField'; -export let printToConsole = false; // Doc.MyDockedBtns.linearView_IsOpen +// eslint-disable-next-line prefer-const +let printToConsole = false; // Doc.MyDockedBtns.linearView_IsOpen function getBatchName(target: any, key: string | symbol): string { const keyName = key.toString(); @@ -38,10 +41,11 @@ function propertyDecorator(target: any, key: string | symbol) { } export function undoable(fn: (...args: any[]) => any, batchName: string): (...args: any[]) => any { - return function () { + return function (...fargs) { const batch = UndoManager.StartBatch(batchName); try { - return fn.apply(undefined, arguments as any); + // eslint-disable-next-line prefer-rest-params + return fn.apply(undefined, fargs); } finally { batch.end(); } @@ -49,13 +53,15 @@ export function undoable(fn: (...args: any[]) => any, batchName: string): (...ar } export function undoBatch(target: any, key: string | symbol, descriptor?: TypedPropertyDescriptor): any; +// eslint-disable-next-line no-redeclare export function undoBatch(fn: (...args: any[]) => any): (...args: any[]) => any; +// eslint-disable-next-line no-redeclare export function undoBatch(target: any, key?: string | symbol, descriptor?: TypedPropertyDescriptor): any { if (!key) { - return function () { + return function (...fargs: any[]) { const batch = UndoManager.StartBatch(''); try { - return target.apply(undefined, arguments); + return target.apply(undefined, fargs); } finally { batch.end(); } @@ -63,7 +69,7 @@ export function undoBatch(target: any, key?: string | symbol, descriptor?: Typed } if (!descriptor) { propertyDecorator(target, key); - return; + return undefined; } const oldFunction = descriptor.value; @@ -87,14 +93,18 @@ export namespace UndoManager { } type UndoBatch = UndoEvent[]; - export let undoStackNames: string[] = observable([]); - export let redoStackNames: string[] = observable([]); - export let undoStack: UndoBatch[] = observable([]); - export let redoStack: UndoBatch[] = observable([]); let currentBatch: UndoBatch | undefined; - export let batchCounter = observable.box(0); let undoing = false; - export let tempEvents: UndoEvent[] | undefined = undefined; + let tempEvents: UndoEvent[] | undefined; + export const undoStackNames: string[] = observable([]); + export const redoStackNames: string[] = observable([]); + export const undoStack: UndoBatch[] = observable([]); + export const redoStack: UndoBatch[] = observable([]); + export const batchCounter = observable.box(0); + let _fieldPrinter: (val: any) => string = val => val?.toString(); + export function SetFieldPrinter(printer: (val: any) => string) { + _fieldPrinter = printer; + } export function AddEvent(event: UndoEvent, value?: any): void { if (currentBatch && batchCounter.get() && !undoing) { @@ -103,8 +113,8 @@ export namespace UndoManager { ' '.slice(0, batchCounter.get()) + 'UndoEvent : ' + event.prop + - ' = ' + - (value instanceof RichTextField ? value.Text : value instanceof Array ? value.map(val => Field.toJavascriptString(val)).join(',') : Field.toJavascriptString(value)) + ' = ' + // prettier-ignore + (value instanceof RichTextField ? value.Text : value instanceof Array ? value.map(_fieldPrinter).join(',') : _fieldPrinter(value)) ); currentBatch.push(event); tempEvents?.push(event); @@ -130,21 +140,22 @@ export namespace UndoManager { } export function FilterBatches(fieldTypes: string[]) { const fieldCounts: { [key: string]: number } = {}; - const lastStack = UndoManager.undoStack.slice(-1)[0]; //.lastElement(); + const lastStack = UndoManager.undoStack.slice(-1)[0]; // .lastElement(); if (lastStack) { - lastStack.forEach(ev => fieldTypes.includes(ev.prop) && (fieldCounts[ev.prop] = (fieldCounts[ev.prop] || 0) + 1)); + lastStack.forEach(ev => { + fieldTypes.includes(ev.prop) && (fieldCounts[ev.prop] = (fieldCounts[ev.prop] || 0) + 1); + }); const fieldCount2: { [key: string]: number } = {}; - runInAction( - () => - (UndoManager.undoStack[UndoManager.undoStack.length - 1] = lastStack.filter(ev => { - if (fieldTypes.includes(ev.prop)) { - fieldCount2[ev.prop] = (fieldCount2[ev.prop] || 0) + 1; - if (fieldCount2[ev.prop] === 1 || fieldCount2[ev.prop] === fieldCounts[ev.prop]) return true; - return false; - } - return true; - })) - ); + runInAction(() => { + UndoManager.undoStack[UndoManager.undoStack.length - 1] = lastStack.filter(ev => { + if (fieldTypes.includes(ev.prop)) { + fieldCount2[ev.prop] = (fieldCount2[ev.prop] || 0) + 1; + if (fieldCount2[ev.prop] === 1 || fieldCount2[ev.prop] === fieldCounts[ev.prop]) return true; + return false; + } + return true; + }); + }); } } export function TraceOpenBatches() { @@ -161,11 +172,10 @@ export namespace UndoManager { if (this.disposed) { console.log('WARNING: undo batch already disposed'); return false; - } else { - this.disposed = true; - openBatches.splice(openBatches.indexOf(this)); - return EndBatch(this.batchName, cancel); } + this.disposed = true; + openBatches.splice(openBatches.indexOf(this)); + return EndBatch(this.batchName, cancel); }; end = () => this.dispose(false); @@ -183,7 +193,7 @@ export namespace UndoManager { const EndBatch = action((batchName: string, cancel: boolean = false) => { runInAction(() => batchCounter.set(batchCounter.get() - 1)); - printToConsole && console.log(' '.slice(0, batchCounter.get()) + 'End ' + batchName + ' (' + currentBatch?.length + ')'); + printToConsole && console.log(' '.slice(0, batchCounter.get()) + 'End ' + batchName + ' (' + (currentBatch?.length ?? 0) + ')'); if (batchCounter.get() === 0 && currentBatch?.length) { if (!cancel) { undoStack.push(currentBatch); @@ -200,10 +210,10 @@ export namespace UndoManager { export function StartTempBatch() { tempEvents = []; } - export function EndTempBatch(success: boolean) { + export function EndTempBatch(success: boolean) { UndoManager.UndoTempBatch(success); } - //TODO Make this return the return value + // TODO Make this return the return value export function RunInBatch(fn: () => T, batchName: string) { const batch = StartBatch(batchName); try { @@ -235,9 +245,11 @@ export namespace UndoManager { } undoing = true; - for (let i = commands.length - 1; i >= 0; i--) { - commands[i].undo(); - } + // eslint-disable-next-line prettier/prettier + commands + .slice() + .reverse() + .forEach(command => command.undo()); undoing = false; redoStackNames.push(names ?? '???'); @@ -256,9 +268,7 @@ export namespace UndoManager { } undoing = true; - for (const command of commands) { - command.redo(); - } + commands.forEach(command => command.redo()); undoing = false; undoStackNames.push(names ?? '???'); diff --git a/src/client/util/reportManager/ReportManager.tsx b/src/client/util/reportManager/ReportManager.tsx index 02b3ee32c..2224e642d 100644 --- a/src/client/util/reportManager/ReportManager.tsx +++ b/src/client/util/reportManager/ReportManager.tsx @@ -1,3 +1,6 @@ +/* eslint-disable jsx-a11y/label-has-associated-control */ +/* eslint-disable jsx-a11y/media-has-caption */ +/* eslint-disable react/no-unused-class-component-methods */ import { Octokit } from '@octokit/core'; import { Button, Dropdown, DropdownType, IconButton, Type } from 'browndash-components'; import { action, makeObservable, observable } from 'mobx'; @@ -13,7 +16,7 @@ import { ClientUtils } from '../../../ClientUtils'; import { Doc } from '../../../fields/Doc'; import { StrCast } from '../../../fields/Types'; import { MainViewModal } from '../../views/MainViewModal'; -import '.././SettingsManager.scss'; +import '../SettingsManager.scss'; import { SettingsManager } from '../SettingsManager'; import './ReportManager.scss'; import { Filter, FormInput, FormTextArea, IssueCard, IssueView } from './ReportManagerComponents'; @@ -25,10 +28,12 @@ import { BugType, FileData, Priority, ReportForm, ViewState, bugDropdownItems, d */ @observer export class ReportManager extends React.Component<{}> { + // eslint-disable-next-line no-use-before-define public static Instance: ReportManager; @observable private isOpen = false; @observable private query = ''; + // eslint-disable-next-line react/sort-comp @action private setQuery = (q: string) => { this.query = q; }; @@ -83,7 +88,9 @@ export class ReportManager extends React.Component<{}> { this.formData = newData; }); - public close = action(() => (this.isOpen = false)); + public close = action(() => { + this.isOpen = false; + }); public open = action(async () => { this.isOpen = true; if (this.shownIssues.length === 0) { @@ -165,7 +172,7 @@ export class ReportManager extends React.Component<{}> { * @returns JSX element of a piece of media (image, video, audio) */ private getMediaPreview = (fileData: FileData): JSX.Element => { - const file = fileData.file; + const { file } = fileData; const mimeType = file.type; const preview = URL.createObjectURL(file); @@ -180,7 +187,8 @@ export class ReportManager extends React.Component<{}> {
    ); - } else if (mimeType.startsWith('video/')) { + } + if (mimeType.startsWith('video/')) { return (
    @@ -194,7 +202,8 @@ export class ReportManager extends React.Component<{}> {
    ); - } else if (mimeType.startsWith('audio/')) { + } + if (mimeType.startsWith('audio/')) { return (
    ); } - return <>; + return
    ; }; /** @@ -307,8 +316,8 @@ export class ReportManager extends React.Component<{}> {
    { @@ -320,8 +329,8 @@ export class ReportManager extends React.Component<{}> { /> { @@ -347,7 +356,7 @@ export class ReportManager extends React.Component<{}> { text="Submit" type={Type.TERT} color={StrCast(Doc.UserDoc().userVariantColor)} - icon={} + icon={} iconPlacement="right" onClick={() => { this.reportIssue(); @@ -364,7 +373,7 @@ export class ReportManager extends React.Component<{}> { /> )}
    - } onClick={this.close} /> + } onClick={this.close} />
    ); @@ -376,9 +385,8 @@ export class ReportManager extends React.Component<{}> { private reportComponent = () => { if (this.viewState === ViewState.VIEW) { return this.viewIssuesComponent(); - } else { - return this.reportIssueComponent(); } + return this.reportIssueComponent(); }; render() { @@ -386,7 +394,7 @@ export class ReportManager extends React.Component<{}> { diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx index 5760872fb..eb1030eec 100644 --- a/src/client/views/ContextMenuItem.tsx +++ b/src/client/views/ContextMenuItem.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react/jsx-props-no-spreading */ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, makeObservable, observable, runInAction } from 'mobx'; @@ -17,6 +18,7 @@ export interface OriginalMenuProps { export interface SubmenuProps { description: string; + // eslint-disable-next-line no-use-before-define subitems: ContextMenuProps[]; noexpand?: boolean; addDivider?: boolean; @@ -37,7 +39,9 @@ export class ContextMenuItem extends ObservableReactComponent (this._items.length = 0)); + runInAction(() => { + this._items.length = 0; + }); if ((this._props as SubmenuProps)?.subitems) { (this._props as SubmenuProps).subitems?.forEach(i => runInAction(() => this._items.push(i))); } @@ -83,7 +87,9 @@ export class ContextMenuItem extends ObservableReactComponent (this.overItem = false)), + action(() => { + this.overItem = false; + }), ContextMenuItem.timeout ); }; @@ -147,10 +153,10 @@ export class ContextMenuItem extends ObservableReactComponent {this._props.description} - +
    ); } + return null; } } diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx index 14abd5f89..25415a4f0 100644 --- a/src/client/views/DashboardView.tsx +++ b/src/client/views/DashboardView.tsx @@ -1,3 +1,5 @@ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Button, ColorPicker, EditableText, Size, Type } from 'browndash-components'; import { action, computed, makeObservable, observable } from 'mobx'; @@ -48,10 +50,18 @@ export class DashboardView extends ObservableReactComponent<{}> { @observable private selectedDashboardGroup = DashboardGroup.MyDashboards; @observable private newDashboardName = ''; @observable private newDashboardColor = '#AFAFAF'; - @action abortCreateNewDashboard = () => (this.openModal = false); - @action setNewDashboardName = (name: string) => (this.newDashboardName = name); - @action setNewDashboardColor = (color: string) => (this.newDashboardColor = color); - @action selectDashboardGroup = (group: DashboardGroup) => (this.selectedDashboardGroup = group); + @action abortCreateNewDashboard = () => { + this.openModal = false; + }; + @action setNewDashboardName = (name: string) => { + this.newDashboardName = name; + }; + @action setNewDashboardColor = (color: string) => { + this.newDashboardColor = color; + }; + @action selectDashboardGroup = (group: DashboardGroup) => { + this.selectedDashboardGroup = group; + }; clickDashboard = (e: React.MouseEvent, dashboard: Doc) => { if (this.selectedDashboardGroup === DashboardGroup.SharedDashboards) { @@ -138,9 +148,9 @@ export class DashboardView extends ObservableReactComponent<{}> { <>
    -
    open linked trail
    }>
    -
    - +
    ); @@ -134,8 +144,12 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( key={icon.toString()} size="sm" icon={icon} - onPointerEnter={action(e => (this.subEndLink = (pinLayout ? 'Layout' : '') + (pinLayout && pinContent ? ' &' : '') + (pinContent ? ' Content' : '')))} - onPointerLeave={action(e => (this.subEndLink = ''))} + onPointerEnter={action(() => { + this.subEndLink = (pinLayout ? 'Layout' : '') + (pinLayout && pinContent ? ' &' : '') + (pinContent ? ' Content' : ''); + })} + onPointerLeave={action(() => { + this.subEndLink = ''; + })} onClick={e => { this.view0 && DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this.view0.Document, true, this.view0, { @@ -157,7 +171,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( {linkBtn(false, true, 'address-card')} {linkBtn(true, true, 'id-card')}
    - +
    ); } @@ -177,15 +191,16 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( key={icon.toString()} size="sm" icon={icon} - onPointerEnter={action( - e => - (this.subPin = - (pinLayoutView ? 'Layout' : '') + - (pinLayoutView && pinContentView ? ' &' : '') + - (pinContentView ? ' Content View' : '') + - (pinLayoutView && pinContentView ? '(shift+alt)' : pinLayoutView ? '(shift)' : pinContentView ? '(alt)' : '')) - )} - onPointerLeave={action(e => (this.subPin = ''))} + onPointerEnter={action(() => { + this.subPin = + (pinLayoutView ? 'Layout' : '') + + (pinLayoutView && pinContentView ? ' &' : '') + + (pinContentView ? ' Content View' : '') + + (pinLayoutView && pinContentView ? '(shift+alt)' : pinLayoutView ? '(shift)' : pinContentView ? '(alt)' : ''); + })} + onPointerLeave={action(() => { + this.subPin = ''; + })} onClick={e => { const docs = this._props .views() @@ -232,8 +247,8 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( get shareButton() { const targetDoc = this.view0?.Document; return !targetDoc ? null : ( - {'Open Sharing Manager'}
    }> -
    SharingManager.Instance.open(this.view0, targetDoc)}> + Open Sharing Manager
    }> +
    SharingManager.Instance.open(this.view0, targetDoc)}>
    @@ -244,7 +259,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( get menuButton() { const targetDoc = this.view0?.Document; return !targetDoc ? null : ( - {`Open Context Menu`}
    }> + Open Context Menu
    }>
    setupMoveUpEvents(this, e, returnFalse, emptyFunction, e => this.openContextMenu(e))}>
    @@ -260,8 +275,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
    { - console.log('hi: ', CalendarManager.Instance); + onClick={() => { CalendarManager.Instance.open(this.view0, targetDoc); }}> @@ -282,7 +296,18 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( style={{ backgroundColor: this._isRecording ? Colors.ERROR_RED : Colors.DARK_GRAY, color: Colors.WHITE }} onPointerDown={action((e: React.PointerEvent) => { this._isRecording = true; - this._props.views().map(view => view && DocumentViewInternal.recordAudioAnnotation(view.dataDoc, view.LayoutFieldKey, stopFunc => (this._stopFunc = stopFunc), emptyFunction)); + this._props.views().map( + view => + view && + DocumentViewInternal.recordAudioAnnotation( + view.dataDoc, + view.LayoutFieldKey, + stopFunc => { + this._stopFunc = stopFunc; + }, + emptyFunction + ) + ); const b = UndoManager.StartBatch('Recording'); setupMoveUpEvents( this, @@ -310,10 +335,10 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( if (this._dragRef.current) { const dragDocView = this.view0!; const dragData = new DragManager.DocumentDragData([dragDocView.Document]); - const [left, top] = dragDocView.screenToContentsTransform().inverse().transformPoint(0, 0); + const origin = dragDocView.screenToContentsTransform().inverse().transformPoint(0, 0); dragData.defaultDropAction = dropActionType.embed; dragData.canEmbed = true; - DragManager.StartDocumentDrag([dragDocView.ContentDiv!], dragData, left, top, { hideSource: false }); + DragManager.StartDocumentDrag([dragDocView.ContentDiv!], dragData, origin[0], origin[1], { hideSource: false }); return true; } return false; @@ -336,8 +361,19 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( @computed get templateButton() { return !this.view0 ? null : ( - Tap to Customize Layout. Drag an embedding
    } open={this._tooltipOpen} onClose={action(() => (this._tooltipOpen = false))} placement="bottom"> -
    !this._ref.current?.getBoundingClientRect().width && (this._tooltipOpen = true))}> + Tap to Customize Layout. Drag an embedding
    } + open={this._tooltipOpen} + onClose={action(() => { + this._tooltipOpen = false; + })} + placement="bottom"> +
    { + !this._ref.current?.getBoundingClientRect().width && (this._tooltipOpen = true); + })}> } popup={this.templateMenu} popupContainsPt={returnTrue} />
    @@ -365,17 +401,17 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( @observable _captureEndLinkLayout = false; @action - captureEndLinkLayout = (e: React.PointerEvent) => { + captureEndLinkLayout = () => { this._captureEndLinkLayout = !this._captureEndLinkLayout; }; @observable _captureEndLinkContent = false; @action - captureEndLinkContent = (e: React.PointerEvent) => { + captureEndLinkContent = () => { this._captureEndLinkContent = !this._captureEndLinkContent; }; @action - captureEndLinkState = (e: React.PointerEvent) => { + captureEndLinkState = () => { this._captureEndLinkContent = this._captureEndLinkLayout = !this._captureEndLinkLayout; }; @@ -402,13 +438,15 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( return (
    - +
    {this._showLinkPopup ? (
    (link.link_displayLine = !IsFollowLinkScript(this._props.views().lastElement()?.Document.onClick))} + linkCreated={link => { + link.link_displayLine = !IsFollowLinkScript(this._props.views().lastElement()?.Document.onClick); + }} linkCreateAnchor={() => this._props.views().lastElement()?.ComponentView?.getAnchor?.(true)} linkFrom={() => this._props.views().lastElement()?.Document} /> @@ -423,7 +461,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
    {this.pinButton}
    {this.recordButton}
    {this.calendarButton}
    - {!Doc.UserDoc()['documentLinksButton-fullMenu'] ? null :
    {this.shareButton}
    } + {!Doc.UserDoc().documentLinksButton_fullMenu ? null :
    {this.shareButton}
    }
    {this.menuButton}
    ); diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index 85e893e19..684b948af 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -1,3 +1,5 @@ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ import { action, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; @@ -70,7 +72,7 @@ export class EditableView extends ObservableReactComponent { constructor(props: EditableProps) { super(props); makeObservable(this); - this._editing = this._props.editing ? true : false; + this._editing = !!this._props.editing; } componentDidMount(): void { @@ -166,7 +168,7 @@ export class EditableView extends ObservableReactComponent { this._props.menuCallback(e.currentTarget.getBoundingClientRect().x, e.currentTarget.getBoundingClientRect().y); break; } - + // eslint-disable-next-line no-fallthrough default: if (this._props.textCallback?.(e.key)) { e.stopPropagation(); @@ -186,7 +188,6 @@ export class EditableView extends ObservableReactComponent { this._editing = true; this._props.isEditingCallback?.(true); } - // e.stopPropagation(); } }; @@ -223,6 +224,7 @@ export class EditableView extends ObservableReactComponent { renderEditor() { return this._props.autosuggestProps ? ( { ) : this._props.oneLine !== false && this._props.GetValue()?.toString().indexOf('\n') === -1 ? ( (this._inputref = r)} + ref={r => { this._inputref = r; }} // prettier-ignore style={{ display: this._props.display, overflow: 'auto', fontSize: this._props.fontSize, minWidth: 20, background: this._props.background }} placeholder={this._props.placeholder} onBlur={e => this.finalizeEdit(e.currentTarget.value, false, true, false)} defaultValue={this._props.GetValue()} - autoFocus={true} + // eslint-disable-next-line jsx-a11y/no-autofocus + autoFocus onChange={this.onChange} onKeyDown={this.onKeyDown} onPointerDown={this.stopPropagation} @@ -256,12 +259,13 @@ export class EditableView extends ObservableReactComponent { ) : (