diff options
Diffstat (limited to 'src/client/views')
118 files changed, 3069 insertions, 2796 deletions
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx index ca877b93e..18b433a77 100644 --- a/src/client/views/ContextMenu.tsx +++ b/src/client/views/ContextMenu.tsx @@ -1,16 +1,16 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx'; +import { action, computed, IReactionDisposer, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { StrCast } from '../../fields/Types'; -import { SettingsManager } from '../util/SettingsManager'; +import { DivHeight, DivWidth } from '../../ClientUtils'; +import { SnappingManager } from '../util/SnappingManager'; import './ContextMenu.scss'; import { ContextMenuItem, ContextMenuProps, OriginalMenuProps } from './ContextMenuItem'; import { ObservableReactComponent } from './ObservableReactComponent'; -import { DivHeight, DivWidth } from '../../Utils'; @observer export class ContextMenu extends ObservableReactComponent<{}> { + // eslint-disable-next-line no-use-before-define static Instance: ContextMenu; private _ignoreUp = false; @@ -124,8 +124,8 @@ export class ContextMenu extends ObservableReactComponent<{}> { @action displayMenu = (x: number, y: number, initSearch = '', showSearch = false, onDisplay?: () => void) => { - //maxX and maxY will change if the UI/font size changes, but will work for any amount - //of items added to the menu + // maxX and maxY will change if the UI/font size changes, but will work for any amount + // of items added to the menu this._showSearch = showSearch; this._pageX = x; @@ -192,7 +192,7 @@ export class ContextMenu extends ObservableReactComponent<{}> { key={index + value.join(' -> ')} className="contextMenu-group" style={{ - background: StrCast(SettingsManager.userVariantColor), + background: SnappingManager.userVariantColor, }}> <div className="contextMenu-description">{value.join(' -> ')}</div> </div> @@ -224,11 +224,11 @@ export class ContextMenu extends ObservableReactComponent<{}> { display: this._display ? '' : 'none', left: this.pageX, ...(this._yRelativeToTop ? { top: Math.max(0, this.pageY) } : { bottom: this.pageY }), - background: SettingsManager.userBackgroundColor, - color: SettingsManager.userColor, + background: SnappingManager.userBackgroundColor, + color: SnappingManager.userColor, }}> {!this.itemsNeedSearch ? null : ( - <span className={'search-icon'}> + <span className="search-icon"> <span className="icon-background"> <FontAwesomeIcon icon="search" size="lg" /> </span> @@ -267,7 +267,7 @@ export class ContextMenu extends ObservableReactComponent<{}> { if (item) { item.event({ x: this.pageX, y: this.pageY }); } else { - //if (this._searchString.startsWith(this._defaultPrefix)) { + // if (this._searchString.startsWith(this._defaultPrefix)) { this._defaultItem?.(this._searchString.substring(this._defaultPrefix.length)); } this.closeMenu(); @@ -281,12 +281,10 @@ export class ContextMenu extends ObservableReactComponent<{}> { this._searchString = e.target.value; if (!this._searchString) { this._selectedIndex = -1; + } else if (this._selectedIndex === -1) { + this._selectedIndex = 0; } else { - if (this._selectedIndex === -1) { - this._selectedIndex = 0; - } else { - this._selectedIndex = Math.min(this.flatItems.length - 1, this._selectedIndex); - } + this._selectedIndex = Math.min(this.flatItems.length - 1, this._selectedIndex); } }; } diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx index d15ab749c..5760872fb 100644 --- a/src/client/views/ContextMenuItem.tsx +++ b/src/client/views/ContextMenuItem.tsx @@ -1,17 +1,17 @@ -import * as React from 'react'; -import { observable, action, runInAction, makeObservable } from 'mobx'; -import { observer } from 'mobx-react'; import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { action, makeObservable, observable, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { SnappingManager } from '../util/SnappingManager'; import { UndoManager } from '../util/UndoManager'; -import { SettingsManager } from '../util/SettingsManager'; import { ObservableReactComponent } from './ObservableReactComponent'; export interface OriginalMenuProps { description: string; event: (stuff?: any) => void; undoable?: boolean; - icon: IconProp | JSX.Element; //maybe should be optional (icon?) + icon: IconProp | JSX.Element; // maybe should be optional (icon?) closeMenu?: () => void; } @@ -20,7 +20,7 @@ export interface SubmenuProps { subitems: ContextMenuProps[]; noexpand?: boolean; addDivider?: boolean; - icon: IconProp; //maybe should be optional (icon?) + icon: IconProp; // maybe should be optional (icon?) closeMenu?: () => void; } @@ -67,7 +67,9 @@ export class ContextMenuItem extends ObservableReactComponent<ContextMenuProps & this._overPosY = e.clientY; this._overPosX = e.clientX; this.currentTimeout = setTimeout( - action(() => (this.overItem = true)), + action(() => { + this.overItem = true; + }), ContextMenuItem.timeout ); }; @@ -97,14 +99,15 @@ export class ContextMenuItem extends ObservableReactComponent<ContextMenuProps & {this._props.icon ? <span className="contextMenu-item-icon-background">{this.isJSXElement(this._props.icon) ? this._props.icon : <FontAwesomeIcon icon={this._props.icon} size="sm" />}</span> : null} <div className="contextMenu-description">{this._props.description.replace(':', '')}</div> <div - className={`contextMenu-item-background`} + className="contextMenu-item-background" style={{ - background: SettingsManager.userColor, + background: SnappingManager.userColor, }} /> </div> ); - } else if ('subitems' in this._props) { + } + if ('subitems' in this._props) { const where = !this.overItem ? '' : this._overPosY < window.innerHeight / 3 ? 'flex-start' : this._overPosY > (window.innerHeight * 2) / 3 ? 'flex-end' : 'center'; const marginTop = !this.overItem ? '' : this._overPosY < window.innerHeight / 3 ? '20px' : this._overPosY > (window.innerHeight * 2) / 3 ? '-20px' : ''; @@ -115,7 +118,7 @@ export class ContextMenuItem extends ObservableReactComponent<ContextMenuProps & style={{ marginLeft: window.innerWidth - this._overPosX - 50 > 0 ? '90%' : '20%', marginTop, - background: SettingsManager.userBackgroundColor, + background: SnappingManager.userBackgroundColor, }}> {this._items.map(prop => ( <ContextMenuItem {...prop} key={prop.description} closeMenu={this._props.closeMenu} /> @@ -149,7 +152,7 @@ export class ContextMenuItem extends ObservableReactComponent<ContextMenuProps & <div className={`contextMenu-item-background`} style={{ - background: SettingsManager.userColor, + background: SnappingManager.userColor, }} /> {submenu} diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx index a4f598d1a..1d286c987 100644 --- a/src/client/views/DashboardView.tsx +++ b/src/client/views/DashboardView.tsx @@ -4,6 +4,7 @@ import { action, computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { FaPlus } from 'react-icons/fa'; +import { ClientUtils } from '../../ClientUtils'; import { Doc, DocListCast } from '../../fields/Doc'; import { AclPrivate, DocAcl, DocData } from '../../fields/DocSymbols'; import { Id } from '../../fields/FieldSymbols'; @@ -14,21 +15,19 @@ import { ScriptField } from '../../fields/ScriptField'; import { Cast, ImageCast, StrCast } from '../../fields/Types'; import { normalizeEmail } from '../../fields/util'; import { DocServer } from '../DocServer'; -import { Docs, DocumentOptions, DocUtils } from '../documents/Documents'; +import { DocUtils, Docs, DocumentOptions } from '../documents/Documents'; +import { dropActionType } from '../util/DropActionTypes'; import { HistoryUtil } from '../util/History'; import { ScriptingGlobals } from '../util/ScriptingGlobals'; -import { SettingsManager } from '../util/SettingsManager'; import { SharingManager } from '../util/SharingManager'; -import { undoable, undoBatch, UndoManager } from '../util/UndoManager'; -import { CollectionDockingView } from './collections/CollectionDockingView'; -import { CollectionView } from './collections/CollectionView'; +import { SnappingManager } from '../util/SnappingManager'; +import { undoBatch, undoable } from '../util/UndoManager'; import { ContextMenu } from './ContextMenu'; import './DashboardView.scss'; -import { Colors } from './global/globalEnums'; import { MainViewModal } from './MainViewModal'; -import { ButtonType } from './nodes/FontIconBox/FontIconBox'; import { ObservableReactComponent } from './ObservableReactComponent'; -import { dropActionType } from '../util/DragManager'; +import { Colors } from './global/globalEnums'; +import { ButtonType } from './nodes/FontIconBox/FontIconBox'; enum DashboardGroup { MyDashboards, @@ -65,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 === Doc.CurrentUserEmail); + return DocListCast(Doc.MyDashboards.data).filter(dashboard => dashboard[DocData].author === ClientUtils.CurrentUserEmail); } return DocListCast(Doc.MySharedDocs.data_dashboards).filter(doc => doc.dockingConfig); }; @@ -84,11 +83,11 @@ export class DashboardView extends ObservableReactComponent<{}> { <div className="new-dashboard" style={{ - background: SettingsManager.userBackgroundColor, - color: SettingsManager.userColor, + background: SnappingManager.userBackgroundColor, + color: SnappingManager.userColor, }}> <div className="header">Create New Dashboard</div> - <EditableText formLabel="Title" placeholder={this.newDashboardName} type={Type.SEC} color={SettingsManager.userColor} setVal={val => this.setNewDashboardName(val as string)} fillWidth /> + <EditableText formLabel="Title" placeholder={this.newDashboardName} type={Type.SEC} color={SnappingManager.userColor} setVal={val => this.setNewDashboardName(val as string)} fillWidth /> <ColorPicker formLabel="Background" // colorPickerType="github" @@ -98,8 +97,8 @@ export class DashboardView extends ObservableReactComponent<{}> { setSelectedColor={this.setNewDashboardColor} /> <div className="button-bar"> - <Button text="Cancel" color={SettingsManager.userColor} onClick={this.abortCreateNewDashboard} /> - <Button text="Create" color={SettingsManager.userVariantColor} type={Type.TERT} onClick={() => this.createNewDashboard(this.newDashboardName, this.newDashboardColor)} /> + <Button text="Cancel" color={SnappingManager.userColor} onClick={this.abortCreateNewDashboard} /> + <Button text="Create" color={SnappingManager.userVariantColor} type={Type.TERT} onClick={() => this.createNewDashboard(this.newDashboardName, this.newDashboardColor)} /> </div> </div> ); @@ -133,8 +132,8 @@ export class DashboardView extends ObservableReactComponent<{}> { }; render() { - const color = SettingsManager.userColor; - const variant = SettingsManager.userVariantColor; + const color = SnappingManager.userColor; + const variant = SnappingManager.userVariantColor; return ( <> <div className="dashboard-view"> @@ -156,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(Doc.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 ( <div @@ -187,7 +186,7 @@ export class DashboardView extends ObservableReactComponent<{}> { <div className="background" style={{ - background: SettingsManager.userColor, + background: SnappingManager.userColor, filter: 'opacity(0.2)', }} /> @@ -201,7 +200,7 @@ export class DashboardView extends ObservableReactComponent<{}> { <div className="background" style={{ - background: SettingsManager.userColor, + background: SnappingManager.userColor, filter: 'opacity(0.2)', }} /> @@ -217,9 +216,6 @@ export class DashboardView extends ObservableReactComponent<{}> { public static closeActiveDashboard() { Doc.ActiveDashboard = undefined; } - public static snapshotDashboard() { - return CollectionDockingView.TakeSnapshot(Doc.ActiveDashboard); - } public static openSharedDashboard = (dashboard: Doc) => { Doc.AddDocToList(Doc.MySharedDocs, 'viewed', dashboard); @@ -250,16 +246,16 @@ export class DashboardView extends ObservableReactComponent<{}> { }); if (state.readonly === true || state.readonly === null) { DocServer.Control.makeReadOnly(); - } else if (state.safe) { - if (!state.nro) { - DocServer.Control.makeReadOnly(); - } - CollectionView.SetSafeMode(true); + // } else if (state.safe) { + // if (!state.nro) { + // DocServer.Control.makeReadOnly(); + // } + // CollectionView.SetSafeMode(true); } else if (state.nro || state.nro === null || state.readonly === false) { } else if (doc.readOnly) { DocServer.Control.makeReadOnly(); } else { - Doc.CurrentUserEmail !== 'guest' && DocServer.Control.makeEditable(); + ClientUtils.CurrentUserEmail !== 'guest' && DocServer.Control.makeEditable(); } } @@ -473,23 +469,23 @@ export class DashboardView extends ObservableReactComponent<{}> { } } +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function createNewDashboard() { return DashboardView.createNewDashboard(); }, 'creates a new dashboard when called'); +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function shareDashboard(dashboard: Doc) { SharingManager.Instance.open(undefined, dashboard); }, 'opens sharing dialog for Dashboard'); +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function removeDashboard(dashboard: Doc) { DashboardView.removeDashboard(dashboard); }, 'Remove Dashboard from Dashboards'); +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function resetDashboard(dashboard: Doc) { DashboardView.resetDashboard(dashboard); }, 'move all dashboard tabs to single stack'); +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function addToDashboards(dashboard: Doc) { DashboardView.openDashboard(Doc.MakeEmbedding(dashboard)); }, 'adds Dashboard to set of Dashboards'); -ScriptingGlobals.add(async function snapshotDashboard() { - const batch = UndoManager.StartBatch('snapshot'); - await DashboardView.snapshotDashboard(); - batch.end(); -}, 'creates a snapshot copy of a dashboard'); diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index de4df1830..348b18129 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -1,22 +1,52 @@ import { action, computed, makeObservable, observable } from 'mobx'; import * as React from 'react'; -import { returnFalse } from '../../Utils'; +import { returnFalse } from '../../ClientUtils'; import { DateField } from '../../fields/DateField'; -import { Doc, DocListCast, Field, Opt } from '../../fields/Doc'; -import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, DocData } from '../../fields/DocSymbols'; +import { Doc, DocListCast, FieldType, Opt } from '../../fields/Doc'; +import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, DocData, DocViews } from '../../fields/DocSymbols'; import { List } from '../../fields/List'; import { RefField } from '../../fields/RefField'; import { GetEffectiveAcl, inheritParentAcls } from '../../fields/util'; import { DocumentType } from '../documents/DocumentTypes'; -import { DocUtils } from '../documents/Documents'; -import { DocumentManager } from '../util/DocumentManager'; import { DragManager } from '../util/DragManager'; import { ObservableReactComponent } from './ObservableReactComponent'; -import { CollectionFreeFormView } from './collections/collectionFreeForm'; import { DocumentView, OpenWhere } from './nodes/DocumentView'; import { FieldViewProps, FocusViewOptions } from './nodes/FieldView'; -import { PinProps } from './nodes/trails'; +// import { DocUtils } from '../documents/Documents'; +export interface pinDataTypes { + scrollable?: boolean; + dataviz?: number[]; + pannable?: boolean; + type_collection?: boolean; + inkable?: boolean; + filters?: boolean; + pivot?: boolean; + temporal?: boolean; + clippable?: boolean; + datarange?: boolean; + dataview?: boolean; + poslayoutview?: boolean; + dataannos?: boolean; + map?: boolean; +} + +export interface MarqueeViewBounds { + left: number; + top: number; + width: number; + height: number; +} +export interface PinProps { + audioRange?: boolean; + activeFrame?: number; + currentFrame?: number; + hidePresBox?: boolean; + pinViewport?: MarqueeViewBounds; // pin a specific viewport on a freeform view (use MarqueeView.CurViewBounds to compute if no region has been selected) + pinDocLayout?: boolean; // pin layout info (width/height/x/y) + pinAudioPlay?: boolean; // pin audio annotation + pinData?: pinDataTypes; +} /** * Shared interface among all viewBox'es (ie, react classes that render the contents of a Doc) * Many of these methods only make sense for specific viewBox'es, but they should be written to @@ -25,6 +55,7 @@ import { PinProps } from './nodes/trails'; export interface ViewBoxInterface { fieldKey?: string; annotationKey?: string; + promoteCollection?: () => void; // moves contents of collection to parent updateIcon?: () => void; // updates the icon representation of the document getAnchor?: (addAsAnnotation: boolean, pinData?: PinProps) => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box) restoreView?: (viewSpec: Doc) => boolean; @@ -46,7 +77,7 @@ export interface ViewBoxInterface { IsPlaying?: () => boolean; // is a media document playing TogglePause?: (keep?: boolean) => void; // toggle media document playing state setFocus?: () => void; // sets input focus to the componentView - setData?: (data: Field | Promise<RefField | undefined>) => boolean; + setData?: (data: FieldType | Promise<RefField | undefined>) => boolean; componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element | null; dragStarting?: (snapToDraggedDoc: boolean, showGroupDragTarget: boolean, visited: Set<Doc>) => void; dragConfig?: (dragData: DragManager.DocumentDragData) => void; // function to setup dragData in custom way (see TreeViews which add a tree view flag) @@ -228,7 +259,7 @@ export function ViewBoxAnnotatableComponent<P extends FieldViewProps>() { if (toRemove.length !== 0) { const recentlyClosed = this.Document !== Doc.MyRecentlyClosed ? Doc.MyRecentlyClosed : undefined; toRemove.forEach(doc => { - leavePushpin && DocUtils.LeavePushpin(doc, annotationKey ?? this.annotationKey); + // leavePushpin && DocUtils.LeavePushpin(doc, annotationKey ?? this.annotationKey); Doc.RemoveDocFromList(targetDataDoc, annotationKey ?? this.annotationKey, doc, true); doc.embedContainer = undefined; if (recentlyClosed && !dontAddToRemoved && doc.type !== DocumentType.LOADING) { @@ -237,7 +268,7 @@ export function ViewBoxAnnotatableComponent<P extends FieldViewProps>() { } }); if (targetDataDoc.isGroup && DocListCast(targetDataDoc[annotationKey ?? this.annotationKey]).length < 2) { - (DocumentManager.Instance.getFirstDocumentView(targetDataDoc)?.ComponentView as CollectionFreeFormView)?.promoteCollection(); + Array.from(targetDataDoc[DocViews])[0]?.ComponentView?.promoteCollection?.(); } else { this.isAnyChildContentActive() && this._props.select(false); } diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 15ce4c15f..16586d922 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -1,32 +1,34 @@ import { IconLookup, IconProp } from '@fortawesome/fontawesome-svg-core'; +import { faCalendarDays } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@mui/material'; -import { action, computed, makeObservable, observable, runInAction } from 'mobx'; +import { Popup } from 'browndash-components'; +import { action, computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { emptyFunction, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick } from '../../Utils'; +import { FaEdit } from 'react-icons/fa'; +import { returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick } from '../../ClientUtils'; +import { emptyFunction } from '../../Utils'; import { Doc } from '../../fields/Doc'; import { Cast, DocCast } from '../../fields/Types'; import { DocUtils } from '../documents/Documents'; import { CalendarManager } from '../util/CalendarManager'; -import { DragManager, dropActionType } from '../util/DragManager'; +import { DragManager } from '../util/DragManager'; +import { dropActionType } from '../util/DropActionTypes'; import { IsFollowLinkScript } from '../util/LinkFollower'; import { SelectionManager } from '../util/SelectionManager'; import { SharingManager } from '../util/SharingManager'; import { UndoManager, undoBatch } from '../util/UndoManager'; +import { PinProps } from './DocComponent'; import './DocumentButtonBar.scss'; import { ObservableReactComponent } from './ObservableReactComponent'; +import { TemplateMenu } from './TemplateMenu'; import { TabDocView } from './collections/TabDocView'; import { Colors } from './global/globalEnums'; import { LinkPopup } from './linking/LinkPopup'; import { DocumentLinksButton } from './nodes/DocumentLinksButton'; import { DocumentView, DocumentViewInternal, OpenWhere } from './nodes/DocumentView'; import { DashFieldView } from './nodes/formattedText/DashFieldView'; -import { PinProps } from './nodes/trails'; -import { faCalendarDays } from '@fortawesome/free-solid-svg-icons'; -import { Popup } from 'browndash-components'; -import { TemplateMenu } from './TemplateMenu'; -import { FaEdit } from 'react-icons/fa'; @observer export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (DocumentView | undefined)[]; stack?: any }> { diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 2a44a9739..009097b3b 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -5,9 +5,10 @@ import { action, computed, makeObservable, observable, runInAction } from 'mobx' import { observer } from 'mobx-react'; import * as React from 'react'; import { FaUndo } from 'react-icons/fa'; -import { Utils, emptyFunction, lightOrDark, numberValue, returnFalse, setupMoveUpEvents } from '../../Utils'; +import { lightOrDark, returnFalse, setupMoveUpEvents } from '../../ClientUtils'; +import { Utils, numberValue, emptyFunction } from '../../Utils'; import { DateField } from '../../fields/DateField'; -import { Doc, DocListCast, Field, HierarchyMapping, ReverseHierarchyMap } from '../../fields/Doc'; +import { Doc, DocListCast, Field, FieldType, HierarchyMapping, ReverseHierarchyMap } from '../../fields/Doc'; import { AclAdmin, AclAugment, AclEdit, DocData } from '../../fields/DocSymbols'; import { InkField } from '../../fields/InkField'; import { ScriptField } from '../../fields/ScriptField'; @@ -34,7 +35,7 @@ import { DocumentView, OpenWhereMod } from './nodes/DocumentView'; import { ImageBox } from './nodes/ImageBox'; import { KeyValueBox } from './nodes/KeyValueBox'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; -import { identity } from 'lodash'; +import { Id } from '../../fields/FieldSymbols'; interface DocumentDecorationsProps { PanelWidth: number; @@ -44,6 +45,7 @@ interface DocumentDecorationsProps { } @observer export class DocumentDecorations extends ObservableReactComponent<DocumentDecorationsProps> { + // eslint-disable-next-line no-use-before-define static Instance: DocumentDecorations; private _resizeHdlId = ''; private _keyinput = React.createRef<HTMLInputElement>(); @@ -123,7 +125,9 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora this._titleControlString = this._accumulatedTitle; } else if (this._titleControlString.startsWith('$')) { if (this._accumulatedTitle.startsWith('-->#')) { - SelectionManager.Docs.forEach(doc => (doc[DocData].onViewMounted = ScriptField.MakeScript(`updateTagsCollection(this)`))); + SelectionManager.Docs.forEach(doc => { + doc[DocData].onViewMounted = ScriptField.MakeScript(`updateTagsCollection(this)`); + }); } const titleFieldKey = this._titleControlString.substring(1); UndoManager.RunInBatch( @@ -149,7 +153,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora onContainerDown = (e: React.PointerEvent) => { const effectiveLayoutAcl = GetEffectiveAcl(SelectionManager.Views[0].Document); - if (effectiveLayoutAcl == AclAdmin || effectiveLayoutAcl == AclEdit || effectiveLayoutAcl == AclAugment) { + if (effectiveLayoutAcl === AclAdmin || effectiveLayoutAcl === AclEdit || effectiveLayoutAcl === AclAugment) { setupMoveUpEvents(this, e, e => this.onBackgroundMove(true, e), emptyFunction, emptyFunction); e.stopPropagation(); } @@ -157,13 +161,13 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora onTitleDown = (e: React.PointerEvent) => { const effectiveLayoutAcl = GetEffectiveAcl(SelectionManager.Views[0].Document); - if (effectiveLayoutAcl == AclAdmin || effectiveLayoutAcl == AclEdit || effectiveLayoutAcl == AclAugment) { + if (effectiveLayoutAcl === AclAdmin || effectiveLayoutAcl === AclEdit || effectiveLayoutAcl === AclAugment) { setupMoveUpEvents( this, e, e => this.onBackgroundMove(true, e), emptyFunction, - action(e => { + action(() => { const selected = SelectionManager.Views.length === 1 ? SelectionManager.Docs[0] : undefined; !this._editingTitle && (this._accumulatedTitle = this._titleControlString.startsWith('$') ? (selected && Field.toKeyValueString(selected, this._titleControlString.substring(1))) || '-unset-' : this._titleControlString); this._editingTitle = true; @@ -182,7 +186,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora onBackgroundMove = (dragTitle: boolean, e: PointerEvent): boolean => { const dragDocView = SelectionManager.Views[0]; const effectiveLayoutAcl = GetEffectiveAcl(dragDocView.Document); - if (effectiveLayoutAcl != AclAdmin && effectiveLayoutAcl != AclEdit && effectiveLayoutAcl != AclAugment) { + if (effectiveLayoutAcl !== AclAdmin && effectiveLayoutAcl !== AclEdit && effectiveLayoutAcl !== AclAugment) { return false; } const containers = new Set<Doc | undefined>(); @@ -205,7 +209,9 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora e.x, e.y, { - dragComplete: action(e => (this._hidden = false)), + dragComplete: action(() => { + this._hidden = false; + }), hideSource: true, } ); @@ -217,15 +223,16 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora onCloseClick = (forceDeleteOrIconify: boolean | undefined) => { const views = SelectionManager.Views.filter(v => v && v._props.renderDepth > 0); if (forceDeleteOrIconify === false && this._iconifyBatch) return; - this._deleteAfterIconify = forceDeleteOrIconify || this._iconifyBatch ? true : false; - var iconifyingCount = views.length; + this._deleteAfterIconify = !!(forceDeleteOrIconify || this._iconifyBatch); + let iconifyingCount = views.length; const finished = action((force?: boolean) => { if ((force || --iconifyingCount === 0) && this._iconifyBatch) { if (this._deleteAfterIconify) { views.forEach(iconView => { - Doc.setNativeView(iconView.Document); - if (iconView.Document.activeFrame) { - iconView.Document.opacity = 0; // bcz: hacky ... allows inkMasks and other documents to be "turned off" without removing them from the animated collection which allows them to function properly in a presenation. + const iconViewDoc = iconView.Document; + Doc.setNativeView(iconViewDoc); + if (iconViewDoc.activeFrame) { + iconViewDoc.opacity = 0; // bcz: hacky ... allows inkMasks and other documents to be "turned off" without removing them from the animated collection which allows them to function properly in a presenation. } else { iconView._props.removeDocument?.(iconView.Document); } @@ -240,6 +247,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora (document.activeElement as any).blur?.(); this._iconifyBatch = UndoManager.StartBatch(forceDeleteOrIconify ? 'delete selected docs' : 'iconifying'); } else { + // eslint-disable-next-line no-param-reassign forceDeleteOrIconify = false; // can't force immediate close in the middle of iconifying -- have to wait until iconifying completes } @@ -268,7 +276,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora // open same document in new tab CollectionDockingView.ToggleSplit(selectedDocs[0].Document, OpenWhereMod.right); } else { - var openDoc = selectedDocs[0].Document; + let openDoc = selectedDocs[0].Document; if (openDoc.layout_fieldKey === 'layout_icon') { openDoc = Doc.GetEmbeddings(openDoc).find(embedding => !embedding.embedContainer) ?? Doc.MakeEmbedding(openDoc); Doc.deiconifyView(openDoc); @@ -294,7 +302,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora */ @action onRadiusDown = (e: React.PointerEvent): void => { - SnappingManager.SetIsResizing(SelectionManager.Docs.lastElement()); + SnappingManager.SetIsResizing(SelectionManager.Docs.lastElement()?.[Id]); this._isRounding = true; this._resizeUndo = UndoManager.StartBatch('DocDecs set radius'); setupMoveUpEvents( @@ -304,14 +312,14 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora const [x, y] = [this.Bounds.x + 3, this.Bounds.y + 3]; const maxDist = Math.min((this.Bounds.r - this.Bounds.x) / 2, (this.Bounds.b - this.Bounds.y) / 2); const dist = e.clientX < x && e.clientY < y ? 0 : Math.sqrt((e.clientX - x) * (e.clientX - x) + (e.clientY - y) * (e.clientY - y)); - SelectionManager.Docs.map(doc => { + SelectionManager.Docs.forEach(doc => { const docMax = Math.min(NumCast(doc.width) / 2, NumCast(doc.height) / 2); const radius = Math.min(1, dist / maxDist) * docMax; // set radius based on ratio of drag distance to half diagonal distance of bounding box doc._layout_borderRounding = `${radius}px`; }); return false; }, - action(e => { + action(() => { SnappingManager.SetIsResizing(undefined); this._isRounding = false; this._resizeUndo?.end(); @@ -329,17 +337,18 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora e, returnFalse, // don't care about move or up event, emptyFunction, // just care about whether we get a click event - e => UndoManager.RunInBatch(() => SelectionManager.Docs.forEach(doc => Doc.toggleLockedPosition(doc)), 'toggleBackground') + () => UndoManager.RunInBatch(() => SelectionManager.Docs.forEach(doc => Doc.toggleLockedPosition(doc)), 'toggleBackground') ); e.stopPropagation(); }; setRotateCenter = (seldocview: DocumentView, rotCenter: number[]) => { + const selDoc = seldocview.Document; const newloccentern = seldocview.screenToContentsTransform().transformPoint(rotCenter[0], rotCenter[1]); const newlocenter = [newloccentern[0] - NumCast(seldocview.layoutDoc._width) / 2, newloccentern[1] - NumCast(seldocview.layoutDoc._height) / 2]; const final = Utils.rotPt(newlocenter[0], newlocenter[1], -(NumCast(seldocview.Document._rotation) / 180) * Math.PI); - seldocview.Document._rotation_centerX = final.x / NumCast(seldocview.layoutDoc._width); - seldocview.Document._rotation_centerY = final.y / NumCast(seldocview.layoutDoc._height); + selDoc._rotation_centerX = final.x / NumCast(seldocview.layoutDoc._width); + selDoc._rotation_centerY = final.y / NumCast(seldocview.layoutDoc._height); }; @action @@ -351,8 +360,8 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora e, (e: PointerEvent, down: number[], delta: number[]) => // return false to keep getting events this.setRotateCenter(seldocview, [this.rotCenter[0] + delta[0], this.rotCenter[1] + delta[1]]) as any as boolean, - action(e => (this._isRotating = false)), // upEvent - action(e => (seldocview.Document._rotation_centerX = seldocview.Document._rotation_centerY = 0)), + action(() => { this._isRotating = false; }), // upEvent + action(() => { seldocview.Document._rotation_centerX = seldocview.Document._rotation_centerY = 0; }), true ); // prettier-ignore e.stopPropagation(); @@ -418,13 +427,15 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora this._isRotating = false; rotateUndo?.end(); }), // upEvent - action(e => (this._showRotCenter = !this._showRotCenter)) // clickEvent + action(() => { + this._showRotCenter = !this._showRotCenter; + }) // clickEvent ); }; @action onPointerDown = (e: React.PointerEvent): void => { - SnappingManager.SetIsResizing(SelectionManager.Docs.lastElement()); // turns off pointer events on things like youtube videos and web pages so that dragging doesn't get "stuck" when cursor moves over them + SnappingManager.SetIsResizing(SelectionManager.Docs.lastElement()?.[Id]); // turns off pointer events on things like youtube videos and web pages so that dragging doesn't get "stuck" when cursor moves over them setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction); e.stopPropagation(); const id = (this._resizeHdlId = e.currentTarget.className); @@ -453,12 +464,12 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora const tl = docView.screenToContentsTransform().inverse().transformPoint(0, 0); return project([e.clientX + this._offset.x, e.clientY + this._offset.y], tl, [tl[0] + fixedAspect, tl[1] + 1]); }; - onPointerMove = (e: PointerEvent, down: number[], move: number[]): boolean => { + onPointerMove = (e: PointerEvent): boolean => { const first = SelectionManager.Views[0]; const effectiveAcl = GetEffectiveAcl(first.Document); - if (!(effectiveAcl == AclAdmin || effectiveAcl == AclEdit || effectiveAcl == AclAugment)) return false; + if (!(effectiveAcl === AclAdmin || effectiveAcl === AclEdit || effectiveAcl === AclAugment)) return false; if (!first) return false; - var fixedAspect = Doc.NativeAspect(first.layoutDoc); + const fixedAspect = Doc.NativeAspect(first.layoutDoc); const dragHdl = this._resizeHdlId.split(' ')[0].replace('documentDecorations-', '').replace('Resizer', ''); const thisPt = // do snapping of drag point @@ -476,7 +487,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora const scaleAspect = {x:scale.x === 1 && fixedAspect ? scale.y : scale.x, y: scale.x !== 1 && fixedAspect ? scale.x : scale.y}; SelectionManager.Views.forEach(docView => this.resizeView(docView, refPt, scaleAspect, { dragHdl, ctrlKey:e.ctrlKey })); // prettier-ignore - await new Promise<any>(res => setTimeout(() => res(this._interactionLock = undefined))); + await new Promise<any>(res => { setTimeout(() => { res(this._interactionLock = undefined)})}); }); // prettier-ignore return false; @@ -553,10 +564,11 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora doc._layout_modificationDate = new DateField(); if (scale.y !== 1) { - docView.layoutDoc._layout_autoHeight = undefined; + const docLayout = docView.layoutDoc; + docLayout._layout_autoHeight = undefined; if (docView.layoutDoc._layout_autoHeight) { // if autoHeight is still on because of a prototype - docView.layoutDoc._layout_autoHeight = false; // then don't inherit, but explicitly set it to false + docLayout._layout_autoHeight = false; // then don't inherit, but explicitly set it to false } } } @@ -586,15 +598,17 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora }; @action - onPointerUp = (e: PointerEvent): void => { + onPointerUp = (): void => { SnappingManager.SetIsResizing(undefined); SnappingManager.clearSnapLines(); this._resizeHdlId = ''; this._resizeUndo?.end(); // detect layout_autoHeight gesture and apply - SelectionManager.Views.forEach(view => NumCast(view.Document._height) < 20 && (view.layoutDoc._layout_autoHeight = true)); - //need to change points for resize, or else rotation/control points will fail. + SelectionManager.Views.forEach(view => { + NumCast(view.Document._height) < 20 && (view.layoutDoc._layout_autoHeight = true); + }); + // need to change points for resize, or else rotation/control points will fail. this._inkDragDocs .map(oldbds => ({ oldbds, inkPts: Cast(oldbds.doc.data, InkField)?.inkData || [] })) .forEach(({ oldbds: { doc, x, y, width, height }, inkPts }) => { @@ -613,7 +627,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora if (SelectionManager.Views.length === 1) { const selected = SelectionManager.Views[0]; if (this._titleControlString.startsWith('$')) { - return Field.toJavascriptString(selected.Document[this._titleControlString.substring(1)] as Field) || '-unset-'; + return Field.toJavascriptString(selected.Document[this._titleControlString.substring(1)] as FieldType) || '-unset-'; } return this._accumulatedTitle; } @@ -648,7 +662,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora const acl = GetEffectiveAcl(!this._showLayoutAcl ? Doc.GetProto(seldocview.Document) : seldocview.Document); const docShareMode = HierarchyMapping.get(acl)!.name; const shareMode = StrCast(docShareMode); - var shareSymbolIcon = ReverseHierarchyMap.get(shareMode)?.image; + const shareSymbolIcon = ReverseHierarchyMap.get(shareMode)?.image; // hide the decorations if the parent chooses to hide it or if the document itself hides it const hideDecorations = SnappingManager.IsResizing || seldocview._props.hideDecorations || seldocview.Document.layout_hideDecorations; @@ -687,7 +701,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora const bounds = this.ClippedBounds; const useLock = bounds.r - bounds.x > 135; const useRotation = !hideResizers && seldocview.Document.type !== DocumentType.EQUATION && seldocview.CollectionFreeFormDocumentView; // when do we want an object to not rotate? - const rotation = SelectionManager.Views.length == 1 ? seldocview.screenToContentsTransform().inverse().RotateDeg : 0; + const rotation = SelectionManager.Views.length === 1 ? seldocview.screenToContentsTransform().inverse().RotateDeg : 0; // Radius constants const useRounding = seldocview.ComponentView instanceof ImageBox || seldocview.ComponentView instanceof FormattedTextBox || seldocview.ComponentView instanceof CollectionFreeFormView; @@ -727,11 +741,13 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora name="dynbox" autoComplete="on" value={hideTitle ? '' : this._accumulatedTitle} - onBlur={action((e: React.FocusEvent) => { + onBlur={action(() => { this._editingTitle = false; this.titleBlur(); })} - onChange={action(e => !hideTitle && (this._accumulatedTitle = e.target.value))} + onChange={action(e => { + !hideTitle && (this._accumulatedTitle = e.target.value); + })} onKeyDown={hideTitle ? emptyFunction : this.titleEntered} onPointerDown={e => e.stopPropagation()} /> @@ -789,8 +805,8 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora display: hideDeleteButton && hideTitle && hideOpenButton ? 'none' : undefined, }} onPointerDown={this.onContainerDown}> - {hideDeleteButton ? null : topBtn('close', 'times', undefined, e => this.onCloseClick(true), 'Close')} - {hideResizers || hideDeleteButton ? null : topBtn('minimize', 'window-maximize', undefined, e => this.onCloseClick(undefined), 'Minimize')} + {hideDeleteButton ? null : topBtn('close', 'times', undefined, () => this.onCloseClick(true), 'Close')} + {hideResizers || hideDeleteButton ? null : topBtn('minimize', 'window-maximize', undefined, () => this.onCloseClick(undefined), 'Minimize')} {titleArea} {hideOpenButton ? <div /> : topBtn('open', 'external-link-alt', this.onMaximizeDown, undefined, 'Open in Lightbox (ctrl: as alias, shift: in new collection)')} </div> diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx index 0521c4a4b..d3cebd1c3 100644 --- a/src/client/views/FilterPanel.tsx +++ b/src/client/views/FilterPanel.tsx @@ -1,10 +1,12 @@ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ import { action, computed, makeObservable, observable, ObservableMap } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Handles, Rail, Slider, Ticks, Tracks } from 'react-compound-slider'; import { AiOutlineMinusSquare, AiOutlinePlusSquare } from 'react-icons/ai'; import { CiCircleRemove } from 'react-icons/ci'; -import { Doc, DocListCast, Field, LinkedTo, StrListCast } from '../../fields/Doc'; +import { Doc, DocListCast, Field, FieldType, LinkedTo, StrListCast } from '../../fields/Doc'; import { Id } from '../../fields/FieldSymbols'; import { List } from '../../fields/List'; import { RichTextField } from '../../fields/RichTextField'; @@ -58,7 +60,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> { @computed get mapActiveFiltersToFacets() { const filters = new Map<string, string>(); - //this.targetDoc.docFilters + // this.targetDoc.docFilters this.activeFilters.map(filter => filters.set(filter.split(Doc.FilterSep)[1], filter.split(Doc.FilterSep)[0])); return filters; } @@ -73,7 +75,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> { // ["#tags", "width", "height"] // @computed get activeFacetHeaders() { - const activeHeaders = new Array(); + const activeHeaders = [] as string[]; this.activeFilters.map(filter => activeHeaders.push(filter.split(Doc.FilterSep)[0])); return activeHeaders; @@ -83,19 +85,20 @@ export class FilterPanel extends ObservableReactComponent<filterProps> { const valueSet = new Set<string>(childFilters.map(filter => filter.split(Doc.FilterSep)[1])); let rtFields = 0; let subDocs = childDocs; - let gatheredDocs = [] as Doc[]; + const gatheredDocs = [] as Doc[]; if (subDocs.length > 0) { let newarray: Doc[] = []; while (subDocs.length > 0) { newarray = []; + // eslint-disable-next-line no-loop-func subDocs.forEach(t => { gatheredDocs.push(t); const facetVal = t[facetKey]; if (facetVal instanceof RichTextField || typeof facetVal === 'string') rtFields++; - facetVal !== undefined && valueSet.add(Field.toString(facetVal as Field)); - (facetVal === true || facetVal == false) && valueSet.add(Field.toString(!facetVal)); + facetVal !== undefined && valueSet.add(Field.toString(facetVal as FieldType)); + (facetVal === true || facetVal === false) && valueSet.add(Field.toString(!facetVal)); const fieldKey = Doc.LayoutFieldKey(t); - const annos = !Field.toString(Doc.LayoutField(t) as Field).includes('CollectionView'); + const annos = !Field.toString(Doc.LayoutField(t) as FieldType).includes('CollectionView'); DocListCast(t[annos ? fieldKey + '_annotations' : fieldKey]).forEach(newdoc => newarray.push(newdoc)); annos && DocListCast(t[fieldKey + '_sidebar']).forEach(newdoc => newarray.push(newdoc)); }); @@ -115,7 +118,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> { // @observable _chosenFacets = new ObservableMap<string, 'text' | 'checkbox' | 'slider' | 'range'>(); @observable _chosenFacetsCollapse = new ObservableMap<string, boolean>(); - @observable _collapseReturnKeys = new Array(); + @observable _collapseReturnKeys = [] as string[]; // this computed function gets the active filters and maps them to their headers // @@ -130,11 +133,11 @@ export class FilterPanel extends ObservableReactComponent<filterProps> { const facetValues = FilterPanel.gatherFieldValues(this.targetDocChildren, facetHeader, StrListCast(this.Document.childFilters)); let nonNumbers = 0; - let minVal = Number.MAX_VALUE, - maxVal = -Number.MAX_VALUE; - facetValues.strings.map(val => { + let minVal = Number.MAX_VALUE; + let maxVal = -Number.MAX_VALUE; + facetValues.strings.forEach(val => { const num = val ? Number(val) : Number.NaN; - if (Number.isNaN(num)) { + if (isNaN(num)) { val && nonNumbers++; } else { minVal = Math.min(num, minVal); @@ -144,14 +147,14 @@ export class FilterPanel extends ObservableReactComponent<filterProps> { if (facetHeader === 'text') { return { facetHeader, renderType: 'text' }; - } else if (facetHeader !== 'tags' && nonNumbers / facetValues.strings.length < 0.1) { + } + if (facetHeader !== 'tags' && nonNumbers / facetValues.strings.length < 0.1) { const extendedMinVal = minVal - Math.min(1, Math.floor(Math.abs(maxVal - minVal) * 0.1)); const extendedMaxVal = Math.max(minVal + 1, maxVal + Math.min(1, Math.ceil(Math.abs(maxVal - minVal) * 0.05))); - const ranged = Doc.readDocRangeFilter(this.Document, facetHeader); // not the filter range, but the zooomed in range on the filter - return { facetHeader, renderType: 'range', domain: [extendedMinVal, extendedMaxVal], range: ranged ? ranged : [extendedMinVal, extendedMaxVal] }; - } else { - return { facetHeader, renderType: 'checkbox' }; + const ranged: number[] | undefined = Doc.readDocRangeFilter(this.Document, facetHeader); // not the filter range, but the zooomed in range on the filter + return { facetHeader, renderType: 'range', domain: [extendedMinVal, extendedMaxVal], range: ranged || [extendedMinVal, extendedMaxVal] }; } + return { facetHeader, renderType: 'checkbox' }; }) ); } @@ -171,17 +174,17 @@ export class FilterPanel extends ObservableReactComponent<filterProps> { sortingCurrentFacetValues = (facetHeader: string) => { this._collapseReturnKeys.splice(0); - Array.from(this.activeRenderedFacetInfos.keys()).map(renderInfo => { + Array.from(this.activeRenderedFacetInfos.keys()).forEach(renderInfo => { if (renderInfo.renderType === 'range' && renderInfo.facetHeader === facetHeader && renderInfo.range) { - this._collapseReturnKeys.push(renderInfo.range.map(number => number.toFixed(2))); + this._collapseReturnKeys.push(...renderInfo.range.map(number => number.toFixed(2))); } }); - for (var key of this.facetValues(facetHeader)) { + this.facetValues(facetHeader).forEach(key => { if (this.mapActiveFiltersToFacets.get(key)) { this._collapseReturnKeys.push(key); } - } + }); return <div className=" filterbox-collpasedAndActive">{this._collapseReturnKeys.join(', ')}</div>; }; @@ -198,7 +201,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> { ); else allCollectionDocs.forEach(child => { - const fieldVal = child[facetHeader] as Field; + const fieldVal = child[facetHeader] as FieldType; if (!(fieldVal instanceof List)) { // currently we have no good way of filtering based on a field that is a list set.add(Field.toString(fieldVal)); @@ -209,7 +212,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> { let nonNumbers = 0; - facetValues.map(val => Number.isNaN(Number(val)) && nonNumbers++); + facetValues.map(val => isNaN(Number(val)) && nonNumbers++); return nonNumbers / facetValues.length > 0.1 ? facetValues.sort() : facetValues.sort((n1: string, n2: string) => Number(n1) - Number(n2)); }; @@ -218,7 +221,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> { <div className="filterBox-treeView"> <div className="filterBox-select"> <div style={{ width: '100%' }}> - <FieldsDropdown Document={this.Document} selectFunc={this.facetClick} showPlaceholder={true} placeholder="add a filter" addedFields={['acl-Guest', LinkedTo]} /> + <FieldsDropdown Document={this.Document} selectFunc={this.facetClick} showPlaceholder placeholder="add a filter" addedFields={['acl-Guest', LinkedTo]} /> </div> {/* THE FOLLOWING CODE SHOULD BE DEVELOPER FOR BOOLEAN EXPRESSION (AND / OR) */} {/* <div className="filterBox-select-bool"> @@ -234,34 +237,31 @@ export class FilterPanel extends ObservableReactComponent<filterProps> { <div className="filterBox-tree" key="tree"> {Array.from(this.activeRenderedFacetInfos.keys()).map( - ( - renderInfo // iterato over activeFacetRenderInfos ==> renderInfo which you can renderInfo.facetHeader - ) => ( + // iterate over activeFacetRenderInfos ==> renderInfo which you can renderInfo.facetHeader + renderInfo => ( <div> <div className="filterBox-facetHeader"> <div className="filterBox-facetHeader-Header"> </div> {renderInfo.facetHeader.charAt(0).toUpperCase() + renderInfo.facetHeader.slice(1)} - <div className="filterBox-facetHeader-collapse" - onClick={action(e => { + onClick={action(() => { const collapseBoolValue = this._chosenFacetsCollapse.get(renderInfo.facetHeader); this._chosenFacetsCollapse.set(renderInfo.facetHeader, !collapseBoolValue); })}> {this._chosenFacetsCollapse.get(renderInfo.facetHeader) ? <AiOutlinePlusSquare /> : <AiOutlineMinusSquare />} </div> - <div className="filterBox-facetHeader-remove" - onClick={action(e => { + onClick={action(() => { if (renderInfo.facetHeader === 'text') { Doc.setDocFilter(this.Document, renderInfo.facetHeader, 'match', 'remove'); } else { - for (var key of this.facetValues(renderInfo.facetHeader)) { + this.facetValues(renderInfo.facetHeader).forEach((key: string) => { if (this.mapActiveFiltersToFacets.get(key)) { Doc.setDocFilter(this.Document, renderInfo.facetHeader, key, 'remove'); } - } + }); } this._selectedFacetHeaders.delete(renderInfo.facetHeader); this._chosenFacetsCollapse.delete(renderInfo.facetHeader); @@ -292,7 +292,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> { return ( <input key={this.Document[Id]} - placeholder={'enter text to match'} + placeholder="enter text to match" defaultValue={ StrListCast(this.Document._childFilters) .find(filter => filter.split(Doc.FilterSep)[0] === facetHeader) @@ -312,7 +312,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> { style={{ width: 20, marginLeft: 20 }} checked={['check', 'exists'].includes( StrListCast(this.Document._childFilters) - .find(filter => filter.split(Doc.FilterSep)[0] === facetHeader && filter.split(Doc.FilterSep)[1] == facetValue) + .find(filter => filter.split(Doc.FilterSep)[0] === facetHeader && filter.split(Doc.FilterSep)[1] === facetValue) ?.split(Doc.FilterSep)[2] ?? '' )} type={type} @@ -324,55 +324,56 @@ export class FilterPanel extends ObservableReactComponent<filterProps> { }); case 'range': - const domain = renderInfoDomain; - const range = renderInfoRange; - if (domain) { - return ( - <div className="sliderBox-outerDiv" style={{ width: '95%', height: 45, float: 'right' }}> - <Slider - mode={2} - step={Math.min(1, 0.1 * (domain[1] - domain[0]))} - domain={[domain[0], domain[1]]} // -1000, 1000 - rootStyle={{ position: 'relative', width: '100%' }} - onChange={values => Doc.setDocRangeFilter(this.Document, facetHeader, values)} - values={renderInfoRange!}> - <Rail>{railProps => <TooltipRail {...railProps} />}</Rail> - <Handles> - {({ handles, activeHandleID, getHandleProps }) => ( - <div className="slider-handles"> - {handles.map((handle, i) => { - // const value = i === 0 ? defaultValues[0] : defaultValues[1]; - return ( + { + const domain = renderInfoDomain; + if (domain) { + return ( + <div className="sliderBox-outerDiv" style={{ width: '95%', height: 45, float: 'right' }}> + <Slider + mode={2} + step={Math.min(1, 0.1 * (domain[1] - domain[0]))} + domain={[domain[0], domain[1]]} // -1000, 1000 + rootStyle={{ position: 'relative', width: '100%' }} + onChange={values => Doc.setDocRangeFilter(this.Document, facetHeader, values)} + values={renderInfoRange!}> + <Rail>{railProps => <TooltipRail {...railProps} />}</Rail> + <Handles> + {({ handles, activeHandleID, getHandleProps }) => ( + <div className="slider-handles"> + {handles.map(handle => ( + // const value = i === 0 ? defaultValues[0] : defaultValues[1]; <div> <Handle key={handle.id} handle={handle} domain={domain} isActive={handle.id === activeHandleID} getHandleProps={getHandleProps} /> </div> - ); - })} - </div> - )} - </Handles> - <Tracks left={false} right={false}> - {({ tracks, getTrackProps }) => ( - <div className="slider-tracks"> - {tracks.map(({ id, source, target }) => ( - <Track key={id} source={source} target={target} disabled={false} getTrackProps={getTrackProps} /> - ))} - </div> - )} - </Tracks> - <Ticks count={5}> - {({ ticks }) => ( - <div className="slider-ticks"> - {ticks.map(tick => ( - <Tick key={tick.id} tick={tick} count={ticks.length} format={(val: number) => val.toString()} /> - ))} - </div> - )} - </Ticks> - </Slider> - </div> - ); + ))} + </div> + )} + </Handles> + <Tracks left={false} right={false}> + {({ tracks, getTrackProps }) => ( + <div className="slider-tracks"> + {tracks.map(({ id, source, target }) => ( + <Track key={id} source={source} target={target} disabled={false} getTrackProps={getTrackProps} /> + ))} + </div> + )} + </Tracks> + <Ticks count={5}> + {({ ticks }) => ( + <div className="slider-ticks"> + {ticks.map(tick => ( + <Tick key={tick.id} tick={tick} count={ticks.length} format={(val: number) => val.toString()} /> + ))} + </div> + )} + </Ticks> + </Slider> + </div> + ); + } } + break; + default: // case 'range' // return <Slider ... @@ -386,5 +387,6 @@ export class FilterPanel extends ObservableReactComponent<filterProps> { // <dimain changing handles > // <?div } + return undefined; } } diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 86b9f5e40..46fa04d72 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -2,11 +2,13 @@ import * as fitCurve from 'fit-curve'; import { action, computed, makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, setupMoveUpEvents } from '../../Utils'; +import { returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, setupMoveUpEvents } from '../../ClientUtils'; +import { emptyFunction } from '../../Utils'; import { Doc, Opt } from '../../fields/Doc'; import { InkData, InkTool } from '../../fields/InkField'; import { BoolCast, NumCast } from '../../fields/Types'; import MobileInkOverlay from '../../mobile/MobileInkOverlay'; +import { Gestures } from '../../pen-gestures/GestureTypes'; import { GestureUtils } from '../../pen-gestures/GestureUtils'; import { MobileInkOverlayContent } from '../../server/Message'; import { InteractionUtils } from '../util/InteractionUtils'; @@ -29,15 +31,22 @@ import { SetActiveInkWidth, } from './InkingStroke'; import { ObservableReactComponent } from './ObservableReactComponent'; -import { checkInksToGroup } from './global/globalScripts'; import { DocumentView } from './nodes/DocumentView'; +export enum ToolglassTools { + InkToText = 'inktotext', + IgnoreGesture = 'ignoregesture', + RadialMenu = 'radialmenu', + None = 'none', +} interface GestureOverlayProps { isActive: boolean; } @observer export class GestureOverlay extends ObservableReactComponent<React.PropsWithChildren<GestureOverlayProps>> { + // eslint-disable-next-line no-use-before-define static Instance: GestureOverlay; + // eslint-disable-next-line no-use-before-define static Instances: GestureOverlay[] = []; public static set RecognizeGestures(active) { @@ -47,7 +56,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil return BoolCast(Doc.UserDoc().recognizeGestures); } - @observable public InkShape: Opt<GestureUtils.Gestures> = undefined; + @observable public InkShape: Opt<Gestures> = undefined; @observable public SavedColor?: string = undefined; @observable public SavedWidth?: number = undefined; @observable public Tool: ToolglassTools = ToolglassTools.None; @@ -55,9 +64,6 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil @observable private _thumbX?: number = undefined; @observable private _thumbY?: number = undefined; - @observable private _selectedIndex: number = -1; - @observable private _menuX: number = -300; - @observable private _menuY: number = -300; @observable private _pointerY?: number = undefined; @observable private _points: { X: number; Y: number }[] = []; @observable private _strokes: InkData[] = []; @@ -117,9 +123,9 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil const yInGlass = initialPoint.Y > (this._thumbY ?? Number.MAX_SAFE_INTEGER) - this.height && initialPoint.Y < (this._thumbY ?? Number.MAX_SAFE_INTEGER); if (this.Tool !== ToolglassTools.None && xInGlass && yInGlass) { switch (this.Tool) { - case ToolglassTools.RadialMenu: - return true; - } + case ToolglassTools.RadialMenu: return true; + default: + } // prettier-ignore } } return false; @@ -128,8 +134,8 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil @action primCreated() { if (!this.KeepPrimitiveMode) { this.InkShape = undefined; - //get out of ink mode after each stroke= - //if (Doc.ActiveTool === InkTool.Highlighter && GestureOverlay.Instance.SavedColor) SetActiveInkColor(GestureOverlay.Instance.SavedColor); + // get out of ink mode after each stroke= + // if (Doc.ActiveTool === InkTool.Highlighter && GestureOverlay.Instance.SavedColor) SetActiveInkColor(GestureOverlay.Instance.SavedColor); Doc.ActiveTool = InkTool.None; // SetActiveArrowStart('none'); // SetActiveArrowEnd('none'); @@ -142,7 +148,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil const B = this.svgBounds; const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top })); - //if any of the shape is activated in the CollectionFreeFormViewChrome + // if any of the shape is activated in the CollectionFreeFormViewChrome if (this.InkShape) { this.makeBezierPolygon(this.InkShape, false); this.dispatchGesture(this.InkShape); @@ -155,16 +161,17 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil let actionPerformed = false; if (GestureOverlay.RecognizeGestures && result && result.Score > 0.7) { switch (result.Name) { - case GestureUtils.Gestures.Line: - case GestureUtils.Gestures.Triangle: - case GestureUtils.Gestures.Rectangle: - case GestureUtils.Gestures.Circle: + case Gestures.Line: + case Gestures.Triangle: + case Gestures.Rectangle: + case Gestures.Circle: this.makeBezierPolygon(result.Name, true); actionPerformed = this.dispatchGesture(result.Name); break; - case GestureUtils.Gestures.Scribble: + case Gestures.Scribble: console.log('scribble'); break; + default: } } @@ -178,21 +185,20 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil const controlPoints: { X: number; Y: number }[] = []; const bezierCurves = (fitCurve as any)(newPoints, 10); - for (const curve of bezierCurves) { + Array.from(bezierCurves).forEach((curve: any) => { controlPoints.push({ X: curve[0][0], Y: curve[0][1] }); controlPoints.push({ X: curve[1][0], Y: curve[1][1] }); controlPoints.push({ X: curve[2][0], Y: curve[2][1] }); controlPoints.push({ X: curve[3][0], Y: curve[3][1] }); - } + }); const dist = Math.sqrt( (controlPoints[0].X - controlPoints.lastElement().X) * (controlPoints[0].X - controlPoints.lastElement().X) + (controlPoints[0].Y - controlPoints.lastElement().Y) * (controlPoints[0].Y - controlPoints.lastElement().Y) ); + // eslint-disable-next-line prefer-destructuring if (controlPoints.length > 4 && dist < 10) controlPoints[controlPoints.length - 1] = controlPoints[0]; this._points.length = 0; this._points.push(...controlPoints); - this.dispatchGesture(GestureUtils.Gestures.Stroke); - // TODO: nda - check inks to group here - checkInksToGroup(); + this.dispatchGesture(Gestures.Stroke); } } } @@ -202,34 +208,34 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil makeBezierPolygon = (shape: string, gesture: boolean) => { const xs = this._points.map(p => p.X); const ys = this._points.map(p => p.Y); - var right = Math.max(...xs); - var left = Math.min(...xs); - var bottom = Math.max(...ys); - var top = Math.min(...ys); + let right = Math.max(...xs); + let left = Math.min(...xs); + let bottom = Math.max(...ys); + let top = Math.min(...ys); const firstx = this._points[0].X; const firsty = this._points[0].Y; - var lastx = this._points[this._points.length - 2].X; - var lasty = this._points[this._points.length - 2].Y; - var fourth = (lastx - firstx) / 4; + let lastx = this._points[this._points.length - 2].X; + let lasty = this._points[this._points.length - 2].Y; + let fourth = (lastx - firstx) / 4; if (isNaN(fourth) || fourth === 0) { fourth = 0.01; } - var m = (lasty - firsty) / (lastx - firstx); + let m = (lasty - firsty) / (lastx - firstx); if (isNaN(m) || m === 0) { m = 0.01; } - const b = firsty - m * firstx; + // const b = firsty - m * firstx; if (shape === 'noRec') { return false; } if (!gesture) { - //if shape options is activated in inkOptionMenu - //take second to last point because _point[length-1] is _points[0] + // if shape options is activated in inkOptionMenu + // take second to last point because _point[length-1] is _points[0] right = this._points[this._points.length - 2].X; left = this._points[0].X; bottom = this._points[this._points.length - 2].Y; top = this._points[0].Y; - if (shape !== GestureUtils.Gestures.Arrow && shape !== GestureUtils.Gestures.Line && shape !== GestureUtils.Gestures.Circle) { + if (shape !== Gestures.Arrow && shape !== Gestures.Line && shape !== Gestures.Circle) { if (left > right) { const temp = right; right = left; @@ -244,7 +250,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil } this._points.length = 0; switch (shape) { - case GestureUtils.Gestures.Rectangle: + case Gestures.Rectangle: this._points.push({ X: left, Y: top }); this._points.push({ X: left, Y: top }); this._points.push({ X: right, Y: top }); @@ -267,7 +273,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil break; - case GestureUtils.Gestures.Triangle: + case Gestures.Triangle: this._points.push({ X: left, Y: bottom }); this._points.push({ X: left, Y: bottom }); @@ -285,39 +291,40 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil this._points.push({ X: left, Y: bottom }); break; - case GestureUtils.Gestures.Circle: - // Approximation of a circle using 4 Bézier curves in which the constant "c" reduces the maximum radial drift to 0.019608%, - // making the curves indistinguishable from a circle. - // Source: https://spencermortensen.com/articles/bezier-circle/ - const c = 0.551915024494; - const centerX = (Math.max(left, right) + Math.min(left, right)) / 2; - const centerY = (Math.max(top, bottom) + Math.min(top, bottom)) / 2; - const radius = Math.max(centerX - Math.min(left, right), centerY - Math.min(top, bottom)); - - // Dividing the circle into four equal sections, and fitting each section to a cubic Bézier curve. - this._points.push({ X: centerX, Y: centerY + radius }); - this._points.push({ X: centerX + c * radius, Y: centerY + radius }); - this._points.push({ X: centerX + radius, Y: centerY + c * radius }); - this._points.push({ X: centerX + radius, Y: centerY }); - - this._points.push({ X: centerX + radius, Y: centerY }); - this._points.push({ X: centerX + radius, Y: centerY - c * radius }); - this._points.push({ X: centerX + c * radius, Y: centerY - radius }); - this._points.push({ X: centerX, Y: centerY - radius }); - - this._points.push({ X: centerX, Y: centerY - radius }); - this._points.push({ X: centerX - c * radius, Y: centerY - radius }); - this._points.push({ X: centerX - radius, Y: centerY - c * radius }); - this._points.push({ X: centerX - radius, Y: centerY }); - - this._points.push({ X: centerX - radius, Y: centerY }); - this._points.push({ X: centerX - radius, Y: centerY + c * radius }); - this._points.push({ X: centerX - c * radius, Y: centerY + radius }); - this._points.push({ X: centerX, Y: centerY + radius }); - + case Gestures.Circle: + { + // Approximation of a circle using 4 Bézier curves in which the constant "c" reduces the maximum radial drift to 0.019608%, + // making the curves indistinguishable from a circle. + // Source: https://spencermortensen.com/articles/bezier-circle/ + const c = 0.551915024494; + const centerX = (Math.max(left, right) + Math.min(left, right)) / 2; + const centerY = (Math.max(top, bottom) + Math.min(top, bottom)) / 2; + const radius = Math.max(centerX - Math.min(left, right), centerY - Math.min(top, bottom)); + + // Dividing the circle into four equal sections, and fitting each section to a cubic Bézier curve. + this._points.push({ X: centerX, Y: centerY + radius }); + this._points.push({ X: centerX + c * radius, Y: centerY + radius }); + this._points.push({ X: centerX + radius, Y: centerY + c * radius }); + this._points.push({ X: centerX + radius, Y: centerY }); + + this._points.push({ X: centerX + radius, Y: centerY }); + this._points.push({ X: centerX + radius, Y: centerY - c * radius }); + this._points.push({ X: centerX + c * radius, Y: centerY - radius }); + this._points.push({ X: centerX, Y: centerY - radius }); + + this._points.push({ X: centerX, Y: centerY - radius }); + this._points.push({ X: centerX - c * radius, Y: centerY - radius }); + this._points.push({ X: centerX - radius, Y: centerY - c * radius }); + this._points.push({ X: centerX - radius, Y: centerY }); + + this._points.push({ X: centerX - radius, Y: centerY }); + this._points.push({ X: centerX - radius, Y: centerY + c * radius }); + this._points.push({ X: centerX - c * radius, Y: centerY + radius }); + this._points.push({ X: centerX, Y: centerY + radius }); + } break; - case GestureUtils.Gestures.Line: + case Gestures.Line: if (Math.abs(firstx - lastx) < 10 && Math.abs(firsty - lasty) > 10) { lastx = firstx; } @@ -330,28 +337,32 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil this._points.push({ X: lastx, Y: lasty }); this._points.push({ X: lastx, Y: lasty }); break; - case GestureUtils.Gestures.Arrow: - const x1 = left; - const y1 = top; - const x2 = right; - const y2 = bottom; - const L1 = Math.sqrt(Math.pow(Math.abs(x1 - x2), 2) + Math.pow(Math.abs(y1 - y2), 2)); - const L2 = L1 / 5; - const angle = 0.785398; - const x3 = x2 + (L2 / L1) * ((x1 - x2) * Math.cos(angle) + (y1 - y2) * Math.sin(angle)); - const y3 = y2 + (L2 / L1) * ((y1 - y2) * Math.cos(angle) - (x1 - x2) * Math.sin(angle)); - const x4 = x2 + (L2 / L1) * ((x1 - x2) * Math.cos(angle) - (y1 - y2) * Math.sin(angle)); - const y4 = y2 + (L2 / L1) * ((y1 - y2) * Math.cos(angle) + (x1 - x2) * Math.sin(angle)); - this._points.push({ X: x1, Y: y1 }); - this._points.push({ X: x2, Y: y2 }); - this._points.push({ X: x3, Y: y3 }); - this._points.push({ X: x4, Y: y4 }); - this._points.push({ X: x2, Y: y2 }); + case Gestures.Arrow: + { + const x1 = left; + const y1 = top; + const x2 = right; + const y2 = bottom; + const L1 = Math.sqrt(Math.abs(x1 - x2) ** 2 + Math.abs(y1 - y2) ** 2); + const L2 = L1 / 5; + const angle = 0.785398; + const x3 = x2 + (L2 / L1) * ((x1 - x2) * Math.cos(angle) + (y1 - y2) * Math.sin(angle)); + const y3 = y2 + (L2 / L1) * ((y1 - y2) * Math.cos(angle) - (x1 - x2) * Math.sin(angle)); + const x4 = x2 + (L2 / L1) * ((x1 - x2) * Math.cos(angle) - (y1 - y2) * Math.sin(angle)); + const y4 = y2 + (L2 / L1) * ((y1 - y2) * Math.cos(angle) + (x1 - x2) * Math.sin(angle)); + this._points.push({ X: x1, Y: y1 }); + this._points.push({ X: x2, Y: y2 }); + this._points.push({ X: x3, Y: y3 }); + this._points.push({ X: x4, Y: y4 }); + this._points.push({ X: x2, Y: y2 }); + } + break; + default: } return false; }; - dispatchGesture = (gesture: GestureUtils.Gestures, stroke?: InkData, text?: any) => { + dispatchGesture = (gesture: Gestures, stroke?: InkData, text?: any) => { const points = (stroke ?? this._points).slice(); return ( document.elementFromPoint(points[0].X, points[0].Y)?.dispatchEvent( @@ -387,11 +398,11 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil const selView = GestureOverlay.DownDocView; const width = Number(ActiveInkWidth()) * NumCast(selView?.Document._freeform_scale, 1); // * (selView?.screenToViewTransform().Scale || 1); const rect = this._overlayRef.current?.getBoundingClientRect(); - const B = { left: -20000, right: 20000, top: -20000, bottom: 20000, width: 40000, height: 40000 }; //this.getBounds(this._points, true); - B.left = B.left - width / 2; - B.right = B.right + width / 2; + const B = { left: -20000, right: 20000, top: -20000, bottom: 20000, width: 40000, height: 40000 }; // this.getBounds(this._points, true); + B.left -= width / 2; + B.right += width / 2; B.top = B.top - width / 2 - (rect?.y || 0); - B.bottom = B.bottom + width / 2; + B.bottom += width / 2; B.width += width; B.height += width; const fillColor = ActiveFillColor(); @@ -401,7 +412,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil this._palette, [ this._strokes.map((l, i) => { - const b = { left: -20000, right: 20000, top: -20000, bottom: 20000, width: 40000, height: 40000 }; //this.getBounds(l, true); + const b = { left: -20000, right: 20000, top: -20000, bottom: 20000, width: 40000, height: 40000 }; // this.getBounds(l, true); return ( <svg key={i} width={b.width} height={b.height} style={{ top: 0, left: 0, transform: `translate(${b.left}px, ${b.top}px)`, pointerEvents: 'none', position: 'absolute', zIndex: 30000, overflow: 'visible' }}> {InteractionUtils.CreatePolyline( @@ -414,7 +425,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil 'miter', 'round', ActiveInkBezierApprox(), - 'none' /*ActiveFillColor()*/, + 'none' /* ActiveFillColor() */, ActiveArrowStart(), ActiveArrowEnd(), ActiveArrowScale(), @@ -441,7 +452,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil 'miter', 'round', '', - 'none' /*ActiveFillColor()*/, + 'none' /* ActiveFillColor() */, ActiveArrowStart(), ActiveArrowEnd(), ActiveArrowScale(), @@ -529,19 +540,14 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil } } -// export class - -export enum ToolglassTools { - InkToText = 'inktotext', - IgnoreGesture = 'ignoregesture', - RadialMenu = 'radialmenu', - None = 'none', -} - ScriptingGlobals.add('GestureOverlay', GestureOverlay); +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function setToolglass(tool: any) { - runInAction(() => (GestureOverlay.Instance.Tool = tool)); + runInAction(() => { + GestureOverlay.Instance.Tool = tool; + }); }); +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function setPen(width: any, color: any, fill: any, arrowStart: any, arrowEnd: any, dash: any) { runInAction(() => { GestureOverlay.Instance.SavedColor = ActiveInkColor(); @@ -554,6 +560,7 @@ ScriptingGlobals.add(function setPen(width: any, color: any, fill: any, arrowSta SetActiveDash(dash); }); }); +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function resetPen() { runInAction(() => { SetActiveInkColor(GestureOverlay.Instance.SavedColor ?? 'rgb(0, 0, 0)'); @@ -561,8 +568,9 @@ ScriptingGlobals.add(function resetPen() { }); }, 'resets the pen tool'); ScriptingGlobals.add( + // eslint-disable-next-line prefer-arrow-callback function createText(text: any, x: any, y: any) { - GestureOverlay.Instance.dispatchGesture(GestureUtils.Gestures.Text, [{ X: x, Y: y }], text); + GestureOverlay.Instance.dispatchGesture(Gestures.Text, [{ X: x, Y: y }], text); }, 'creates a text document with inputted text and coordinates', '(text: any, x: any, y: any)' diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 2f64ea28c..5c923d301 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -67,7 +67,6 @@ export class KeyManager { // accumulate buffer of characters to insert in a new text note. once the note is created, it will stop keyboard events from reaching this function. if (FormattedTextBox.SelectOnLoadChar) FormattedTextBox.SelectOnLoadChar = FormattedTextBox.SelectOnLoadChar + (e.key === 'Enter' ? '\n' : e.key); e.key === 'Control' && (CtrlKey = true); - //if (!Doc.noviceMode && e.key.toLocaleLowerCase() === "shift") DocServer.UPDATE_SERVER_CACHE(true); const keyname = e.key && e.key.toLowerCase(); this.handleGreedy(keyname); @@ -89,10 +88,7 @@ export class KeyManager { control.preventDefault && e.preventDefault(); }); - private handleGreedy = action((keyname: string) => { - switch (keyname) { - } - }); + private handleGreedy = action((/* keyname: string */) => {}); nudge = (x: number, y: number, label: string) => { const nudgeable = SelectionManager.Views.some(dv => dv.CollectionFreeFormDocumentView?.nudge); @@ -105,7 +101,7 @@ export class KeyManager { case 'u': if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') { const ungroupings = SelectionManager.Views; - UndoManager.RunInBatch(() => ungroupings.map(dv => (dv.layoutDoc.group = undefined)), 'ungroup'); + UndoManager.RunInBatch(() => ungroupings.forEach(dv => { dv.layoutDoc.group = undefined; }), 'ungroup'); SelectionManager.DeselectAll(); } break; @@ -124,41 +120,41 @@ export class KeyManager { case ' ': // MarqueeView.DragMarquee = !MarqueeView.DragMarquee; // bcz: this needs a better disclosure UI break; - case 'escape': - DocumentLinksButton.StartLink = undefined; - DocumentLinksButton.StartLinkView = undefined; - InkStrokeProperties.Instance._controlButton = false; - Doc.ActiveTool = InkTool.None; - DragManager.CompleteWindowDrag?.(true); - var doDeselect = true; - if (SnappingManager.IsDragging) { - DragManager.AbortDrag(); - } - if (CollectionDockingView.Instance?.HasFullScreen) { - CollectionDockingView.Instance?.CloseFullScreen(); - } - if (CollectionStackedTimeline.SelectingRegions.size) { - CollectionStackedTimeline.StopSelecting(); - doDeselect = false; - } else { - doDeselect = !ContextMenu.Instance.closeMenu(); - } - if (doDeselect) { - SelectionManager.DeselectAll(); - LightboxView.Instance.SetLightboxDoc(undefined); + case 'escape': { + DocumentLinksButton.StartLink = undefined; + DocumentLinksButton.StartLinkView = undefined; + InkStrokeProperties.Instance._controlButton = false; + Doc.ActiveTool = InkTool.None; + DragManager.CompleteWindowDrag?.(true); + let doDeselect = true; + if (SnappingManager.IsDragging) { + DragManager.AbortDrag(); + } + if (CollectionDockingView.Instance?.HasFullScreen) { + CollectionDockingView.Instance?.CloseFullScreen(); + } + if (CollectionStackedTimeline.SelectingRegions.size) { + CollectionStackedTimeline.StopSelecting(); + doDeselect = false; + } else { + doDeselect = !ContextMenu.Instance.closeMenu(); + } + if (doDeselect) { + SelectionManager.DeselectAll(); + LightboxView.Instance.SetLightboxDoc(undefined); + } + // DictationManager.Controls.stop(); + GoogleAuthenticationManager.Instance.cancel(); + SharingManager.Instance.close(); + if (!GroupManager.Instance.isOpen) SettingsManager.Instance.close(); + GroupManager.Instance.close(); + window.getSelection()?.empty(); + document.body.focus(); } - // DictationManager.Controls.stop(); - GoogleAuthenticationManager.Instance.cancel(); - SharingManager.Instance.close(); - if (!GroupManager.Instance.isOpen) SettingsManager.Instance.close(); - GroupManager.Instance.close(); - window.getSelection()?.empty(); - document.body.focus(); break; - case 'enter': { - //DocumentDecorations.Instance.onCloseClick(false); + case 'enter': + // DocumentDecorations.Instance.onCloseClick(false); break; - } case 'delete': case 'backspace': if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') { @@ -173,6 +169,7 @@ export class KeyManager { case 'arrowright': return this.nudge(1,0, 'nudge right'); case 'arrowup': return this.nudge(0, -1, 'nudge up'); case 'arrowdown': return this.nudge(0, 1, 'nudge down'); + default: } // prettier-ignore return { diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx index edc6b404b..0358344b9 100644 --- a/src/client/views/InkControlPtHandles.tsx +++ b/src/client/views/InkControlPtHandles.tsx @@ -6,7 +6,7 @@ import { ControlPoint, InkData, PointData } from '../../fields/InkField'; import { List } from '../../fields/List'; import { listSpec } from '../../fields/Schema'; import { Cast } from '../../fields/Types'; -import { returnFalse, setupMoveUpEvents } from '../../Utils'; +import { returnFalse, setupMoveUpEvents } from '../../ClientUtils'; import { SelectionManager } from '../util/SelectionManager'; import { UndoManager } from '../util/UndoManager'; import { Colors } from './global/globalEnums'; diff --git a/src/client/views/InkTangentHandles.tsx b/src/client/views/InkTangentHandles.tsx index c20399698..f3a757f19 100644 --- a/src/client/views/InkTangentHandles.tsx +++ b/src/client/views/InkTangentHandles.tsx @@ -6,7 +6,8 @@ import { HandleLine, HandlePoint, InkData } from '../../fields/InkField'; import { List } from '../../fields/List'; import { listSpec } from '../../fields/Schema'; import { Cast } from '../../fields/Types'; -import { emptyFunction, setupMoveUpEvents } from '../../Utils'; +import { emptyFunction } from '../../Utils'; +import { setupMoveUpEvents } from '../../ClientUtils'; import { Transform } from '../util/Transform'; import { UndoManager } from '../util/UndoManager'; import { Colors } from './global/globalEnums'; diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 51baaa23e..9f82fdb18 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -23,18 +23,18 @@ import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; +import { DashColor, returnFalse, setupMoveUpEvents } from '../../ClientUtils'; import { Doc } from '../../fields/Doc'; import { InkData, InkField } from '../../fields/InkField'; import { BoolCast, Cast, NumCast, RTFCast, StrCast } from '../../fields/Types'; import { TraceMobx } from '../../fields/util'; -import { DashColor, returnFalse, setupMoveUpEvents } from '../../Utils'; import { CognitiveServices } from '../cognitive_services/CognitiveServices'; import { Docs } from '../documents/Documents'; import { InteractionUtils } from '../util/InteractionUtils'; import { SnappingManager } from '../util/SnappingManager'; import { UndoManager } from '../util/UndoManager'; import { ContextMenu } from './ContextMenu'; -import { ViewBoxAnnotatableComponent, ViewBoxBaseComponent, ViewBoxInterface } from './DocComponent'; +import { PinProps, ViewBoxAnnotatableComponent, ViewBoxInterface } from './DocComponent'; import { Colors } from './global/globalEnums'; import { InkControlPtHandles, InkEndPtHandles } from './InkControlPtHandles'; import './InkStroke.scss'; @@ -42,8 +42,9 @@ import { InkStrokeProperties } from './InkStrokeProperties'; import { InkTangentHandles } from './InkTangentHandles'; import { FieldView, FieldViewProps } from './nodes/FieldView'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; -import { PinProps, PresBox } from './nodes/trails'; +import { PresBox } from './nodes/trails'; import { StyleProp } from './StyleProvider'; + const { INK_MASK_SIZE } = require('./global/globalCssVariables.module.scss'); // prettier-ignore @observer export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface { @@ -387,8 +388,7 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>() 1.0, false, undefined, - undefined, - color === 'transparent' ? highlightColor : undefined + undefined ); const higlightMargin = Math.min(12, Math.max(2, 0.3 * inkStrokeWidth)); // Invisible polygonal line that enables the ink to be selected by the user. @@ -415,8 +415,7 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>() 0.0, false, downHdlr, - mask, - highlightColor + mask ); // bootsrap 3 style sheet sets line height to be 20px for default 14 point font size. // this attempts to figure out the lineHeight ratio by inquiring the body's lineHeight and dividing by the fontsize which should yield 1.428571429 @@ -438,7 +437,7 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>() <svg className="inkStroke" style={{ - transform: isInkMask ? `rotate(-${NumCast(this._props.CollectionFreeFormDocumentView?.()._props.rotation ?? 0)}deg) translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined, + transform: isInkMask ? `rotate(-${NumCast(this._props.LocalRotation?.() ?? 0)}deg) translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined, // mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? 'multiply' : 'unset', cursor: this._props.isSelected() ? 'default' : undefined, }} diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index ef4b5b4ca..c34554b56 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -4,7 +4,8 @@ import { Toggle, ToggleType, Type } from 'browndash-components'; import { action, computed, makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnTrue } from '../../Utils'; +import { ClientUtils, returnEmptyDoclist, returnEmptyFilter, returnTrue } from '../../ClientUtils'; +import { emptyFunction } from '../../Utils'; import { Doc, DocListCast, FieldResult, Opt } from '../../fields/Doc'; import { Id } from '../../fields/FieldSymbols'; import { InkTool } from '../../fields/InkField'; @@ -127,7 +128,9 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> { DocumentManager.Instance.showDocument(target, { willZoomCentered: true, zoomScale: 0.9 }); if (this._history.lastElement().target !== target) this._history.push({ doc: lightDoc, target }); } else if (!target && this._path.length) { - savedKeys.forEach(key => (lightDoc[key] = this._savedState[key])); + savedKeys.forEach(key => { + lightDoc[key] = this._savedState[key]; + }); this._path.pop(); } else { this.SetLightboxDoc(target); @@ -239,7 +242,7 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> { downx = e.clientX; downy = e.clientY; }} - onClick={e => Utils.isClick(e.clientX, e.clientY, downx, downy, Date.now()) && this.SetLightboxDoc(undefined)}> + onClick={e => ClientUtils.isClick(e.clientX, e.clientY, downx, downy, Date.now()) && this.SetLightboxDoc(undefined)}> <div className="lightboxView-contents" style={{ diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 58b8d255a..8a060d0e0 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -7,7 +7,8 @@ import { action, computed, configure, makeObservable, observable, reaction, runI import { observer } from 'mobx-react'; import * as React from 'react'; import '../../../node_modules/browndash-components/dist/styles/global.min.css'; -import { Utils, emptyFunction, lightOrDark, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents } from '../../Utils'; +import { ClientUtils, lightOrDark, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents } from '../../ClientUtils'; +import { emptyFunction } from '../../Utils'; import { Doc, DocListCast, Opt } from '../../fields/Doc'; import { DocData } from '../../fields/DocSymbols'; import { DocCast, StrCast } from '../../fields/Types'; @@ -18,10 +19,12 @@ import { Docs } from '../documents/Documents'; import { CalendarManager } from '../util/CalendarManager'; import { CaptureManager } from '../util/CaptureManager'; import { DocumentManager } from '../util/DocumentManager'; -import { DragManager, dropActionType } from '../util/DragManager'; +import { DragManager } from '../util/DragManager'; +import { dropActionType } from '../util/DropActionTypes'; import { GroupManager } from '../util/GroupManager'; import { HistoryUtil } from '../util/History'; import { Hypothesis } from '../util/HypothesisUtils'; +import { UPDATE_SERVER_CACHE } from '../util/LinkManager'; import { RTFMarkup } from '../util/RTFMarkup'; import { ScriptingGlobals } from '../util/ScriptingGlobals'; import { SelectionManager } from '../util/SelectionManager'; @@ -57,7 +60,7 @@ import { AudioBox } from './nodes/AudioBox'; import { SchemaCSVPopUp } from './nodes/DataVizBox/SchemaCSVPopUp'; import { DocButtonState } from './nodes/DocumentLinksButton'; import { DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod, returnEmptyDocViewList } from './nodes/DocumentView'; -import { ImageBox, ImageEditorData as ImageEditor } from './nodes/ImageBox'; +import { ImageEditorData as ImageEditor } from './nodes/ImageBox'; import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup'; import { LinkDocPreview, LinkInfo } from './nodes/LinkDocPreview'; import { DirectionsAnchorMenu } from './nodes/MapBox/DirectionsAnchorMenu'; @@ -71,11 +74,13 @@ import { PresBox } from './nodes/trails'; import { AnchorMenu } from './pdf/AnchorMenu'; import { GPTPopup } from './pdf/GPTPopup/GPTPopup'; import { TopBar } from './topbar/TopBar'; -const { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } = require('./global/globalCssVariables.module.scss'); // prettier-ignore + const _global = (window /* browser */ || global) /* node */ as any; +const { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } = require('./global/globalCssVariables.module.scss'); // prettier-ignore @observer export class MainView extends ObservableReactComponent<{}> { + // eslint-disable-next-line no-use-before-define public static Instance: MainView; public static Live: boolean = false; private _docBtnRef = React.createRef<HTMLDivElement>(); @@ -117,8 +122,12 @@ export class MainView extends ObservableReactComponent<{}> { } @observable mainDoc: Opt<Doc> = undefined; @computed private get mainContainer() { - if (window.location.pathname.startsWith('/doc/') && Doc.CurrentUserEmail === 'guest') { - DocServer.GetRefField(window.location.pathname.substring('/doc/'.length)).then(main => runInAction(() => (this.mainDoc = main as Doc))); + 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; + }) + ); return this.mainDoc; } return this.userDoc ? Doc.ActiveDashboard : Doc.GuestDashboard; @@ -165,7 +174,11 @@ export class MainView extends ObservableReactComponent<{}> { scriptTag.async = true; scriptTag.defer = true; document.body.appendChild(scriptTag); - document.getElementById('root')?.addEventListener('scroll', e => (ele => (ele.scrollLeft = ele.scrollTop = 0))(document.getElementById('root')!)); + document.getElementById('root')?.addEventListener('scroll', () => + (ele => { + ele.scrollLeft = ele.scrollTop = 0; + })(document.getElementById('root')!) + ); const ele = document.getElementById('loader'); const prog = document.getElementById('dash-progress'); if (ele && prog) { @@ -174,7 +187,9 @@ export class MainView extends ObservableReactComponent<{}> { prog.style.transition = '1s'; prog.style.width = '100%'; }, 0); - setTimeout(() => (ele.outerHTML = ''), 1000); + setTimeout(() => { + ele.outerHTML = ''; + }, 1000); } this._sidebarContent.proto = undefined; if (!MainView.Live) { @@ -202,7 +217,7 @@ export class MainView extends ObservableReactComponent<{}> { 'text_scrollHeight', 'text_height', 'hidden', - //'type_collection', + // 'type_collection', 'chromeHidden', 'currentFrame', ]); // can play with these fields on someone else's @@ -530,7 +545,7 @@ export class MainView extends ObservableReactComponent<{}> { } private longPressTimer: NodeJS.Timeout | undefined; - globalPointerClick = action((e: any) => { + globalPointerClick = action(() => { this.longPressTimer && clearTimeout(this.longPressTimer); DocumentView.LongPress = false; }); @@ -540,7 +555,9 @@ export class MainView extends ObservableReactComponent<{}> { globalPointerDown = action((e: PointerEvent) => { DocumentView.LongPress = false; this.longPressTimer = setTimeout( - action(() => (DocumentView.LongPress = true)), + action(() => { + DocumentView.LongPress = true; + }), 1000 ); DocumentManager.removeOverlayViews(); @@ -559,7 +576,7 @@ export class MainView extends ObservableReactComponent<{}> { }); initEventListeners = () => { - window.addEventListener('beforeunload', DocServer.UPDATE_SERVER_CACHE); + window.addEventListener('beforeunload', UPDATE_SERVER_CACHE); window.addEventListener('drop', e => e.preventDefault(), false); // prevent default behavior of navigating to a new web page window.addEventListener('dragover', e => e.preventDefault(), false); document.addEventListener('pointerdown', this.globalPointerDown, true); @@ -625,10 +642,10 @@ export class MainView extends ObservableReactComponent<{}> { isDocumentActive={returnTrue} // headerBar is always documentActive (ie, the docView gets pointer events) isContentActive={returnTrue} // headerBar is awlays contentActive which means its items are always documentActive ScreenToLocalTransform={this.headerBarScreenXf} - childHideResizeHandles={true} + childHideResizeHandles childDragAction={dropActionType.move} - dontRegisterView={true} - hideResizeHandles={true} + dontRegisterView + hideResizeHandles PanelWidth={this.headerBarDocWidth} PanelHeight={this.headerBarDocHeight} renderDepth={0} @@ -664,7 +681,7 @@ export class MainView extends ObservableReactComponent<{}> { childFilters={returnEmptyFilter} childFiltersByRanges={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} - suppressSetHeight={true} + suppressSetHeight renderDepth={this._hideUI ? 0 : -1} /> </> @@ -673,7 +690,7 @@ export class MainView extends ObservableReactComponent<{}> { @computed get dockingContent() { return ( - <GestureOverlay isActive={LightboxView.LightboxDoc ? false : true}> + <GestureOverlay isActive={!LightboxView.LightboxDoc}> <div key="docking" className={`mainView-dockingContent${this._leftMenuFlyoutWidth ? '-flyout' : ''}`} @@ -697,9 +714,16 @@ export class MainView extends ObservableReactComponent<{}> { setupMoveUpEvents( this, e, - action(e => ((SettingsManager.Instance.propertiesWidth = Math.max(0, this._dashUIWidth - e.clientX)) ? false : false)), - action(() => SettingsManager.Instance.propertiesWidth < 5 && (SettingsManager.Instance.propertiesWidth = 0)), - action(() => (SettingsManager.Instance.propertiesWidth = this.propertiesWidth() < 15 ? Math.min(this._dashUIWidth - 50, 250) : 0)), + action(() => { + SettingsManager.Instance.propertiesWidth = Math.max(0, this._dashUIWidth - e.clientX); + return !SettingsManager.Instance.propertiesWidth; + }), + action(() => { + SettingsManager.Instance.propertiesWidth < 5 && (SettingsManager.Instance.propertiesWidth = 0); + }), + action(() => { + SettingsManager.Instance.propertiesWidth = this.propertiesWidth() < 15 ? Math.min(this._dashUIWidth - 50, 250) : 0; + }), false ); }; @@ -709,7 +733,10 @@ export class MainView extends ObservableReactComponent<{}> { setupMoveUpEvents( this, e, - action(e => ((this._leftMenuFlyoutWidth = Math.max(e.clientX - 58, 0)) ? false : false)), + action(e => { + this._leftMenuFlyoutWidth = Math.max(e.clientX - 58, 0); + return false; + }), () => this._leftMenuFlyoutWidth < 5 && this.closeFlyout(), this.closeFlyout ); @@ -734,7 +761,7 @@ export class MainView extends ObservableReactComponent<{}> { @computed get flyout() { return !this._leftMenuFlyoutWidth ? ( - <div key="flyout" className={`mainView-libraryFlyout-out`}> + <div key="flyout" className="mainView-libraryFlyout-out"> {this.docButtons} </div> ) : ( @@ -837,11 +864,9 @@ export class MainView extends ObservableReactComponent<{}> { </div> )} <div className="properties-container" style={{ width: this.propertiesWidth(), color: SettingsManager.userColor }}> - { - <div style={{ display: this.propertiesWidth() < 10 ? 'none' : undefined }}> - <PropertiesView styleProvider={DefaultStyleProvider} addDocTab={DocumentViewInternal.addDocTabFunc} width={this.propertiesWidth()} height={this.propertiesHeight()} /> - </div> - } + <div style={{ display: this.propertiesWidth() < 10 ? 'none' : undefined }}> + <PropertiesView styleProvider={DefaultStyleProvider} addDocTab={DocumentViewInternal.addDocTabFunc} width={this.propertiesWidth()} height={this.propertiesHeight()} /> + </div> </div> </div> </div> @@ -878,7 +903,7 @@ export class MainView extends ObservableReactComponent<{}> { // generate the wrong value from getClientRectangle() -- specifically they return an 'x' that is the flyout's width greater than it should be. // interactively adjusting the flyout fixes the problem. So does programmatically changing the value after a timeout to something *fractionally* different (ie, 1.5, not 1);) this._leftMenuFlyoutWidth = this._leftMenuFlyoutWidth || 250; - //setTimeout(action(() => (this._leftMenuFlyoutWidth += 0.5))); + // setTimeout(action(() => (this._leftMenuFlyoutWidth += 0.5))); this._sidebarContent.proto = DocCast(button.target); SettingsManager.Instance.SetLastPressedBtn(button); @@ -897,7 +922,7 @@ export class MainView extends ObservableReactComponent<{}> { 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); }; @@ -940,9 +965,11 @@ export class MainView extends ObservableReactComponent<{}> { <div className="mainView-snapLines"> <svg style={{ width: '100%', height: '100%' }}> {SnappingManager.HorizSnapLines.map((l, i) => ( + // eslint-disable-next-line react/no-array-index-key <line key={i} x1="0" y1={l} x2="2000" y2={l} stroke={lightOrDark(dragPar.layoutDoc.backgroundColor ?? 'gray')} opacity={0.3} strokeWidth={1} strokeDasharray={'2 2'} /> ))} {SnappingManager.VertSnapLines.map((l, i) => ( + // eslint-disable-next-line react/no-array-index-key <line key={i} y1={this.topOfMainDocContent.toString()} x1={l} y2="2000" x2={l} stroke={lightOrDark(dragPar.layoutDoc.backgroundColor ?? 'gray')} opacity={0.3} strokeWidth={1} strokeDasharray={'2 2'} /> ))} </svg> @@ -961,13 +988,14 @@ export class MainView extends ObservableReactComponent<{}> { values="1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 1 0"></feColorMatrix> - <feGaussianBlur in="color" stdDeviation="4" result="blur"></feGaussianBlur> - <feOffset in="blur" dx="0" dy="0" result="offset"></feOffset> + 0 0 0 1 0" + /> + <feGaussianBlur in="color" stdDeviation="4" result="blur" /> + <feOffset in="blur" dx="0" dy="0" result="offset" /> <feMerge> - <feMergeNode in="bg"></feMergeNode> - <feMergeNode in="offset"></feMergeNode> - <feMergeNode in="SourceGraphic"></feMergeNode> + <feMergeNode in="bg" /> + <feMergeNode in="offset" /> + <feMergeNode in="SourceGraphic" /> </feMerge> </filter> </defs> @@ -984,7 +1012,11 @@ export class MainView extends ObservableReactComponent<{}> { color: SettingsManager.userColor, background: SettingsManager.userBackgroundColor, }} - onScroll={() => (ele => (ele.scrollTop = ele.scrollLeft = 0))(document.getElementById('root')!)} + onScroll={() => + (ele => { + ele.scrollTop = ele.scrollLeft = 0; + })(document.getElementById('root')!) + } ref={r => { r && new _global.ResizeObserver( @@ -1009,23 +1041,31 @@ export class MainView extends ObservableReactComponent<{}> { <ComponentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfMainDocContent} /> {this._hideUI ? null : <TopBar />} <LinkDescriptionPopup /> - {DocButtonState.Instance.LinkEditorDocView ? <LinkMenu clearLinkEditor={action(() => (DocButtonState.Instance.LinkEditorDocView = undefined))} docView={DocButtonState.Instance.LinkEditorDocView} /> : null} - {LinkInfo.Instance?.LinkInfo ? <LinkDocPreview {...LinkInfo.Instance.LinkInfo} /> : null} - + {DocButtonState.Instance.LinkEditorDocView ? ( + <LinkMenu + clearLinkEditor={action(() => { + DocButtonState.Instance.LinkEditorDocView = undefined; + })} + docView={DocButtonState.Instance.LinkEditorDocView} + /> + ) : null} + {LinkInfo.Instance?.LinkInfo ? ( + // eslint-disable-next-line react/jsx-props-no-spreading + <LinkDocPreview {...LinkInfo.Instance.LinkInfo} /> + ) : null} {((page: string) => { // prettier-ignore switch (page) { - default: - case 'dashboard': return (<> + case 'home': return <DashboardView />; + case 'dashboard': + default: return (<> <div key="dashdiv" style={{ position: 'relative', display: this._hideUI || LightboxView.LightboxDoc ? 'none' : undefined, zIndex: 2001 }}> <CollectionMenu panelWidth={this.topMenuWidth} panelHeight={this.topMenuHeight} toggleTopBar={this.toggleTopBar} topBarHeight={this.headerBarHeightFunc}/> </div> {this.mainDashboardArea} </> ); - case 'home': return <DashboardView />; } })(Doc.ActivePage)} - <PreviewCursor /> <TaskCompletionBox /> <ContextMenu /> @@ -1049,15 +1089,19 @@ export class MainView extends ObservableReactComponent<{}> { } } -ScriptingGlobals.add(function selectMainMenu(doc: Doc, title: string) { +// eslint-disable-next-line prefer-arrow-callback +ScriptingGlobals.add(function selectMainMenu(doc: Doc) { MainView.Instance.selectMenu(doc); }); +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function createNewPresentation() { return MainView.Instance.createNewPresentation(); }, 'creates a new presentation when called'); +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function openPresentation(pres: Doc) { return MainView.Instance.openPresentation(pres); }, 'creates a new presentation when called'); +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function createNewFolder() { return MainView.Instance.createNewFolder(); }, 'creates a new folder in myFiles when called'); diff --git a/src/client/views/MainViewModal.tsx b/src/client/views/MainViewModal.tsx index af7f38937..e52562625 100644 --- a/src/client/views/MainViewModal.tsx +++ b/src/client/views/MainViewModal.tsx @@ -1,7 +1,7 @@ import { isDark } from 'browndash-components'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { SettingsManager } from '../util/SettingsManager'; +import { SnappingManager } from '../util/SnappingManager'; import './MainViewModal.scss'; export interface MainViewOverlayProps { @@ -43,7 +43,7 @@ export class MainViewModal extends React.Component<MainViewOverlayProps> { className="overlay" onClick={this.props?.closeOnExternalClick} style={{ - backgroundColor: isDark(SettingsManager.userColor) ? '#DFDFDF30' : '#32323230', + backgroundColor: isDark(SnappingManager.userColor) ? '#DFDFDF30' : '#32323230', ...(p.overlayStyle || {}), }} /> diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index 9fa20f642..f92bd68a3 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -14,8 +14,8 @@ import { undoable, undoBatch, UndoManager } from '../util/UndoManager'; import './MarqueeAnnotator.scss'; import { DocumentView } from './nodes/DocumentView'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; -import { AnchorMenu } from './pdf/AnchorMenu'; import { ObservableReactComponent } from './ObservableReactComponent'; +import { AnchorMenu } from './pdf/AnchorMenu'; const _global = (window /* browser */ || global) /* node */ as any; export interface MarqueeAnnotatorProps { diff --git a/src/client/views/MetadataEntryMenu.tsx b/src/client/views/MetadataEntryMenu.tsx index 89c3c41f8..9626f1653 100644 --- a/src/client/views/MetadataEntryMenu.tsx +++ b/src/client/views/MetadataEntryMenu.tsx @@ -3,7 +3,7 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import * as Autosuggest from 'react-autosuggest'; import { emptyFunction, emptyPath } from '../../Utils'; -import { Doc, DocListCast, Field } from '../../fields/Doc'; +import { Doc, DocListCast, Field, FieldType } from '../../fields/Doc'; import { undoBatch } from '../util/UndoManager'; import './MetadataEntryMenu.scss'; import { KeyValueBox } from './nodes/KeyValueBox'; @@ -12,7 +12,6 @@ export type DocLike = Doc | Doc[] | Promise<Doc> | Promise<Doc[]>; export interface MetadataEntryProps { docs: Doc[]; onError?: () => boolean; - suggestWithFunction?: boolean; } @observer @@ -35,10 +34,10 @@ export class MetadataEntryMenu extends React.Component<MetadataEntryProps> { }; previewValue = async () => { - let field: Field | undefined | null = null; + let field: FieldType | undefined | null = null; let onProto: boolean = false; - let value: string | undefined = undefined; - const docs = this.props.docs; + let value: string | undefined; + const { docs } = this.props; for (const doc of docs) { const v = await doc[this._currentKey]; onProto = onProto || !Object.keys(doc).includes(this._currentKey); diff --git a/src/client/views/ObservableReactComponent.tsx b/src/client/views/ObservableReactComponent.tsx index 394d1eae2..266411770 100644 --- a/src/client/views/ObservableReactComponent.tsx +++ b/src/client/views/ObservableReactComponent.tsx @@ -14,7 +14,10 @@ export abstract class ObservableReactComponent<T> extends React.Component<T, {}> makeObservable(this); } componentDidUpdate(prevProps: Readonly<T>): void { - Object.keys(prevProps).filter(pkey => (prevProps as any)[pkey] !== (this.props as any)[pkey]).forEach(action(pkey => - ((this._props as any)[pkey] = (this.props as any)[pkey]))); // prettier-ignore + Object.keys(prevProps) + .filter(pkey => (prevProps as any)[pkey] !== (this.props as any)[pkey]) + .forEach(action(pkey => { + (this._props as any)[pkey] = (this.props as any)[pkey]; + })); // prettier-ignore } } diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx index 15b1f0275..6539c0834 100644 --- a/src/client/views/OverlayView.tsx +++ b/src/client/views/OverlayView.tsx @@ -3,13 +3,15 @@ import { observer } from 'mobx-react'; import { computedFn } from 'mobx-utils'; import * as React from 'react'; import ReactLoading from 'react-loading'; -import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnTrue, setupMoveUpEvents } from '../../Utils'; +import { returnEmptyDoclist, returnEmptyFilter, returnTrue, setupMoveUpEvents } from '../../ClientUtils'; +import { Utils, emptyFunction } from '../../Utils'; import { Doc } from '../../fields/Doc'; import { Height, Width } from '../../fields/DocSymbols'; import { Id } from '../../fields/FieldSymbols'; import { NumCast } from '../../fields/Types'; import { DocumentType } from '../documents/DocumentTypes'; -import { DragManager, dropActionType } from '../util/DragManager'; +import { DragManager } from '../util/DragManager'; +import { dropActionType } from '../util/DropActionTypes'; import { Transform } from '../util/Transform'; import { LightboxView } from './LightboxView'; import { ObservableReactComponent } from './ObservableReactComponent'; @@ -50,14 +52,14 @@ export class OverlayWindow extends ObservableReactComponent<OverlayWindowProps> this.height = opts.height || 200; } - onPointerDown = (_: React.PointerEvent) => { + onPointerDown = () => { document.removeEventListener('pointermove', this.onPointerMove); document.removeEventListener('pointerup', this.onPointerUp); document.addEventListener('pointermove', this.onPointerMove); document.addEventListener('pointerup', this.onPointerUp); }; - onResizerPointerDown = (_: React.PointerEvent) => { + onResizerPointerDown = () => { document.removeEventListener('pointermove', this.onResizerPointerMove); document.removeEventListener('pointerup', this.onResizerPointerUp); document.addEventListener('pointermove', this.onResizerPointerMove); @@ -95,12 +97,12 @@ export class OverlayWindow extends ObservableReactComponent<OverlayWindowProps> <div className="overlayWindow-outerDiv" style={{ transform: `translate(${this.x}px, ${this.y}px)`, width: this.width, height: this.height }}> <div className="overlayWindow-titleBar" onPointerDown={this.onPointerDown}> {this._props.overlayOptions.title || 'Untitled'} - <button onClick={this._props.onClick} className="overlayWindow-closeButton"> + <button type="button" onClick={this._props.onClick} className="overlayWindow-closeButton"> X </button> </div> <div className="overlayWindow-content">{this.props.children}</div> - <div className="overlayWindow-resizeDragger" onPointerDown={this.onResizerPointerDown}></div> + <div className="overlayWindow-resizeDragger" onPointerDown={this.onResizerPointerDown} /> </div> ); } @@ -108,6 +110,7 @@ export class OverlayWindow extends ObservableReactComponent<OverlayWindowProps> @observer export class OverlayView extends ObservableReactComponent<{}> { + // eslint-disable-next-line no-use-before-define public static Instance: OverlayView; @observable.shallow _elements: JSX.Element[] = []; @@ -118,8 +121,9 @@ export class OverlayView extends ObservableReactComponent<{}> { OverlayView.Instance = this; new _global.ResizeObserver( action((entries: any) => { - for (const entry of entries) { - Doc.MyOverlayDocs.forEach(doc => { + Array.from(entries).forEach((entry: any) => { + Doc.MyOverlayDocs.forEach(docIn => { + const doc = docIn; if (NumCast(doc.overlayX) > entry.contentRect.width - 10) { doc.overlayX = entry.contentRect.width - 10; } @@ -127,7 +131,7 @@ export class OverlayView extends ObservableReactComponent<{}> { doc.overlayY = entry.contentRect.height - 10; } }); - } + }); }) ).observe(window.document.body); } @@ -162,7 +166,7 @@ export class OverlayView extends ObservableReactComponent<{}> { const index = this._elements.indexOf(contents); if (index !== -1) this._elements.splice(index, 1); }); - contents = ( + const wincontents = ( <OverlayWindow onClick={remove} key={Utils.GenerateGuid()} overlayOptions={options}> {contents} </OverlayWindow> @@ -177,15 +181,16 @@ export class OverlayView extends ObservableReactComponent<{}> { }; docScreenToLocalXf = computedFn( + // eslint-disable-next-line prefer-arrow-callback function docScreenToLocalXf(this: any, doc: Doc) { return () => new Transform(-NumCast(doc.overlayX), -NumCast(doc.overlayY), 1); - }.bind(this) + } ); @computed get overlayDocs() { return Doc.MyOverlayDocs.filter(d => !LightboxView.LightboxDoc || d.type === DocumentType.PRES).map(d => { - let offsetx = 0, - offsety = 0; + let offsetx = 0; + let offsety = 0; const dref = React.createRef<HTMLDivElement>(); const onPointerMove = action((e: PointerEvent, down: number[]) => { if (e.cancelBubble) return false; // if the overlay doc processed the move event (e.g., to pan its contents), then the event should be marked as canceled since propagation can't be stopped @@ -198,9 +203,7 @@ export class OverlayView extends ObservableReactComponent<{}> { dragData.offset = [-offsetx, -offsety]; dragData.dropAction = dropActionType.move; dragData.removeDocument = this.removeOverlayDoc; - dragData.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => { - return dragData.removeDocument!(doc) ? addDocument(doc) : false; - }; + dragData.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => (dragData.removeDocument?.(doc) ? addDocument(doc) : false); DragManager.StartDocumentDrag([dref.current!], dragData, down[0], down[1]); return true; } @@ -227,7 +230,7 @@ export class OverlayView extends ObservableReactComponent<{}> { PanelHeight={d[Height]} ScreenToLocalTransform={this.docScreenToLocalXf(d)} renderDepth={1} - hideDecorations={true} + hideDecorations isDocumentActive={returnTrue} isContentActive={returnTrue} whenChildContentsActiveChanged={emptyFunction} diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx index 4b7771f27..6d03034ec 100644 --- a/src/client/views/PreviewCursor.tsx +++ b/src/client/views/PreviewCursor.tsx @@ -1,7 +1,7 @@ import { action, makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { lightOrDark, returnFalse } from '../../Utils'; +import { lightOrDark, returnFalse } from '../../ClientUtils'; import { Doc, Opt } from '../../fields/Doc'; import { DocUtils, Docs, DocumentOptions } from '../documents/Documents'; import { ImageUtils } from '../util/Import & Export/ImageUtils'; diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index 517a80d63..c7720d834 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -46,7 +46,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { propertyToggleBtn = (label: (on?: any) => string, property: string, tooltip: (on?: any) => string, icon: (on?: any) => any, onClick?: (dv: Opt<DocumentView>, doc: Doc, property: string) => void, useUserDoc?: boolean) => { const targetDoc = useUserDoc ? Doc.UserDoc() : this.selectedLayoutDoc; - const onPropToggle = (dv: Opt<DocumentView>, doc: Doc, prop: string) => ((dv?.layoutDoc || doc)[prop] = (dv?.layoutDoc || doc)[prop] ? false : true); + const onPropToggle = (dv: Opt<DocumentView>, doc: Doc, prop: string) => ((dv?.layoutDoc || doc)[prop] = !(dv?.layoutDoc || doc)[prop]); return !targetDoc ? null : ( <Toggle toggleStatus={BoolCast(targetDoc[property])} @@ -103,7 +103,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { (dv, doc) => { const tdoc = dv?.Document || doc; const newtitle = !tdoc._layout_showTitle ? 'title' : tdoc._layout_showTitle === 'title' ? 'title:hover' : ''; - tdoc._layout_showTitle = newtitle ? newtitle : undefined; + tdoc._layout_showTitle = newtitle || undefined; } ); } @@ -181,9 +181,9 @@ export class PropertiesButtons extends React.Component<{}, {}> { on => (on ? 'DISABLE FLASHCARD' : 'ENABLE FLASHCARD'), 'layout_textPainted', on => `${on ? 'Flashcard enabled' : 'Flashcard disabled'} `, - on => <MdTouchApp />, + () => <MdTouchApp />, (dv, doc) => { - const on = doc.onPaint ? true : false; + const on = !!doc.onPaint; doc[DocData].onPaint = on ? undefined : ScriptField.MakeScript(`toggleDetail(documentView, "textPainted")`, { documentView: 'any' }); doc[DocData].layout_textPainted = on ? undefined : `<ComparisonBox {...props} fieldKey={'${dv?.LayoutFieldKey ?? 'text'}'}/>`; } diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index ae29382c1..77e68866e 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -1,3 +1,6 @@ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable prettier/prettier */ import { IconLookup } from '@fortawesome/fontawesome-svg-core'; import { faAnchor, faArrowRight, faWindowMaximize } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; @@ -9,8 +12,9 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { ColorResult, SketchPicker } from 'react-color'; import * as Icons from 'react-icons/bs'; //{BsCollectionFill, BsFillFileEarmarkImageFill} from "react-icons/bs" -import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from '../../Utils'; -import { Doc, Field, FieldResult, HierarchyMapping, NumListCast, Opt, ReverseHierarchyMap, StrListCast } from '../../fields/Doc'; +import { ClientUtils, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from '../../ClientUtils'; +import { emptyFunction } from '../../Utils'; +import { Doc, Field, FieldType, FieldResult, HierarchyMapping, NumListCast, Opt, ReverseHierarchyMap, StrListCast } from '../../fields/Doc'; import { AclAdmin, DocAcl, DocData } from '../../fields/DocSymbols'; import { Id } from '../../fields/FieldSymbols'; import { InkField } from '../../fields/InkField'; @@ -54,6 +58,7 @@ interface PropertiesViewProps { export class PropertiesView extends ObservableReactComponent<PropertiesViewProps> { private _widthUndo?: UndoManager.Batch; + // eslint-disable-next-line no-use-before-define public static Instance: PropertiesView | undefined; constructor(props: any) { super(props); @@ -101,7 +106,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps @observable openTransform: boolean = true; @observable openFilters: boolean = false; - //Pres Trails booleans: + // Pres Trails booleans: @observable openPresTransitions: boolean = true; @observable openPresProgressivize: boolean = false; @observable openPresVisibilityAndDuration: boolean = false; @@ -183,35 +188,37 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps .forEach(key => doc[key] !== ComputedField.undefined && key && ids.add(key)) ); - // prettier-ignore - Array.from(ids).sort().map(key => { - const multiple = Array.from(docs.reduce((set,doc) => set.add(doc[key]), new Set<FieldResult>()).keys()).length > 1; - const editableContents = multiple ? '-multiple-' : Field.toKeyValueString(docs[0], key); - const displayContents = multiple ? '-multiple-' : Field.toString(docs[0][key] as Field); - const contentElement = ( - <EditableView - key="editableView" - contents={displayContents} - height={13} - fontSize={10} - GetValue={() => editableContents} - SetValue={(value: string) => { - value !== '-multiple-' && docs.map(doc => KeyValueBox.SetField(doc, key, value, true)); - return true; - }} - />); - rows.push( - <div style={{ display: 'flex', overflowY: 'visible', marginBottom: '-1px' }} key={key}> - <span style={{ fontWeight: 'bold', whiteSpace: 'nowrap' }}>{key + ':'}</span> - - {contentElement} - </div> - ); - }); + Array.from(ids) + .sort() + .forEach(key => { + const multiple = Array.from(docs.reduce((set, doc) => set.add(doc[key]), new Set<FieldResult>()).keys()).length > 1; + const editableContents = multiple ? '-multiple-' : Field.toKeyValueString(docs[0], key); + const displayContents = multiple ? '-multiple-' : Field.toString(docs[0][key] as FieldType); + const contentElement = ( + <EditableView + key="editableView" + contents={displayContents} + height={13} + fontSize={10} + GetValue={() => editableContents} + SetValue={(value: string) => { + value !== '-multiple-' && docs.map(doc => KeyValueBox.SetField(doc, key, value, true)); + return true; + }} + /> + ); + rows.push( + <div style={{ display: 'flex', overflowY: 'visible', marginBottom: '-1px' }} key={key}> + <span style={{ fontWeight: 'bold', whiteSpace: 'nowrap' }}>{key + ':'}</span> + + {contentElement} + </div> + ); + }); rows.push( <div className="propertiesView-field" key="newKeyValue" style={{ marginTop: '3px', backgroundColor: SettingsManager.userBackgroundColor, textAlign: 'center' }}> - <EditableView key="editableView" oneLine contents={'add key:value or #tags'} height={13} fontSize={10} GetValue={() => ''} SetValue={this.setKeyValue} /> + <EditableView key="editableView" oneLine contents="add key:value or #tags" height={13} fontSize={10} GetValue={() => ''} SetValue={this.setKeyValue} /> </div> ); } @@ -240,7 +247,8 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps KeyValueBox.SetField(doc, 'tags', `"${tags.replace(splits[0] + ':', '')}"`, true); } return true; - } else if (value[0] === '#') { + } + if (value[0] === '#') { const tags = StrListCast(doc.tags); if (!tags.includes(value)) { tags.push(value); @@ -248,6 +256,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps } return true; } + return undefined; }); return false; }; @@ -256,7 +265,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps getTransform = () => this.transform; propertiesDocViewRef = (ref: HTMLDivElement) => { const observer = new _global.ResizeObserver( - action((entries: any) => { + action(() => { const cliRect = ref.getBoundingClientRect(); this.transform = new Transform(-cliRect.x, -cliRect.y, 1); }) @@ -265,28 +274,27 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps }; @computed get contexts() { - return !this.selectedDoc ? null : <PropertiesDocContextSelector DocView={this.selectedDocumentView} hideTitle={true} addDocTab={this._props.addDocTab} />; + return !this.selectedDoc ? null : <PropertiesDocContextSelector DocView={this.selectedDocumentView} hideTitle addDocTab={this._props.addDocTab} />; } @computed get contextCount() { if (this.selectedDocumentView) { const target = this.selectedDocumentView.Document; return Doc.GetEmbeddings(target).length - 1; - } else { - return 0; } + return 0; } @computed get links() { const selAnchor = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.Instance.currentLinkAnchor ?? this.selectedDoc; - return !selAnchor ? null : <PropertiesDocBacklinksSelector Document={selAnchor} hideTitle={true} addDocTab={this._props.addDocTab} />; + return !selAnchor ? null : <PropertiesDocBacklinksSelector Document={selAnchor} hideTitle addDocTab={this._props.addDocTab} />; } @computed get linkCount() { const selAnchor = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.Instance.currentLinkAnchor ?? this.selectedDoc; - var counter = 0; + let counter = 0; - LinkManager.Links(selAnchor).forEach((l, i) => counter++); + LinkManager.Links(selAnchor).forEach(() => counter++); return counter; } @@ -308,7 +316,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps fitContentsToBox={returnTrue} styleProvider={DefaultStyleProvider} containerViewPath={returnEmptyDoclist} - dontCenter={'y'} + dontCenter="y" isDocumentActive={returnFalse} isContentActive={emptyFunction} NativeWidth={layoutDoc.type === DocumentType.RTF ? this.rtfWidth : undefined} @@ -326,13 +334,12 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps whenChildContentsActiveChanged={emptyFunction} addDocTab={returnFalse} pinToPres={emptyFunction} - dontRegisterView={true} + dontRegisterView /> </div> ); - } else { - return null; } + return null; } /** @@ -381,7 +388,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps return ( <div className="expansion-button"> <IconButton - icon={<FontAwesomeIcon icon={'ellipsis-h'} />} + icon={<FontAwesomeIcon icon="ellipsis-h" />} size={Size.XSMALL} color={SettingsManager.userColor} onClick={action(() => { @@ -398,7 +405,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps * @returns a row of the permissions panel */ sharingItem(name: string, admin: boolean, permission: string, showExpansionIcon?: boolean) { - if (name == Doc.CurrentUserEmail) { + if (name === ClientUtils.CurrentUserEmail) { name = 'Me'; } return ( @@ -415,7 +422,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps {/* {name !== "Me" ? this.notifyIcon : null} */} <div className="propertiesView-sharingTable-item-permission"> {this.colorACLDropDown(name, admin, permission, false)} - {(permission === 'Owner' && name == 'Me') || showExpansionIcon ? this.expansionIcon : null} + {(permission === 'Owner' && name === 'Me') || showExpansionIcon ? this.expansionIcon : null} </div> </div> ); @@ -425,10 +432,10 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps * @returns a colored dropdown bar reflective of the permission */ colorACLDropDown(name: string, admin: boolean, permission: string, showGuestOptions: boolean) { - var shareImage = ReverseHierarchyMap.get(permission)?.image; + const shareImage = ReverseHierarchyMap.get(permission)?.image; return ( <div> - <div className={'propertiesView-shareDropDown'}> + <div className="propertiesView-shareDropDown"> <div className={`propertiesView-shareDropDown${permission}`}> <div>{admin && permission !== 'Owner' ? this.getPermissionsSelect(name, permission, showGuestOptions) : concat(shareImage, ' ', permission)}</div> </div> @@ -440,9 +447,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps /** * Sorting algorithm to sort users. */ - sortUsers = (u1: String, u2: String) => { - return u1 > u2 ? -1 : u1 === u2 ? 0 : 1; - }; + sortUsers = (u1: String, u2: String) => (u1 > u2 ? -1 : u1 === u2 ? 0 : 1); /** * Sorting algorithm to sort groups. @@ -468,35 +473,35 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps const seldoc = this.layoutDocAcls ? this.selectedLayoutDoc : this.selectedDoc?.[DocData]; // adds each user to usersAdded SharingManager.Instance.users.forEach(eachUser => { - var userOnDoc = true; + let userOnDoc = true; if (seldoc) { if (Doc.GetT(seldoc, 'acl-' + normalizeEmail(eachUser.user.email), 'string', true) === '' || Doc.GetT(seldoc, 'acl-' + normalizeEmail(eachUser.user.email), 'string', true) === undefined) { userOnDoc = false; } } - if (userOnDoc && !usersAdded.includes(eachUser.user.email) && eachUser.user.email !== 'guest' && eachUser.user.email != target.author) { + if (userOnDoc && !usersAdded.includes(eachUser.user.email) && eachUser.user.email !== 'guest' && eachUser.user.email !== target.author) { usersAdded.push(eachUser.user.email); } }); // sorts and then adds each user to the table usersAdded.sort(this.sortUsers); - usersAdded.map(userEmail => { + usersAdded.forEach(userEmail => { const userKey = `acl-${normalizeEmail(userEmail)}`; - var aclField = Doc.GetT(this.layoutDocAcls ? target : Doc.GetProto(target), userKey, 'string', true); - var permission = StrCast(aclField); + const aclField = Doc.GetT(this.layoutDocAcls ? target : Doc.GetProto(target), userKey, 'string', true); + const permission = StrCast(aclField); individualTableEntries.unshift(this.sharingItem(userEmail, showAdmin, permission!, false)); // adds each user }); // adds current user - var userEmail = Doc.CurrentUserEmail; - if (userEmail == 'guest') userEmail = 'Guest'; + let userEmail = ClientUtils.CurrentUserEmail; + if (userEmail === 'guest') userEmail = 'Guest'; const userKey = `acl-${normalizeEmail(userEmail)}`; - if (!usersAdded.includes(userEmail) && userEmail !== 'Guest' && userEmail != target.author) { - var permission; + if (!usersAdded.includes(userEmail) && userEmail !== 'Guest' && userEmail !== target.author) { + let permission; if (this.layoutDocAcls) { if (target[DocAcl][userKey]) permission = HierarchyMapping.get(target[DocAcl][userKey])?.name; - else if (target['embedContainer']) permission = StrCast(Doc.GetProto(DocCast(target['embedContainer']))[userKey]); + else if (target.embedContainer) permission = StrCast(Doc.GetProto(DocCast(target.embedContainer))[userKey]); else permission = StrCast(Doc.GetProto(target)?.[userKey]); } else permission = StrCast(target[userKey]); individualTableEntries.unshift(this.sharingItem(userEmail, showAdmin, permission!, false)); // adds each user @@ -509,15 +514,15 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps const groupTableEntries: JSX.Element[] = []; const groupList = GroupManager.Instance?.allGroups || []; groupList.sort(this.sortGroups); - groupList.map(group => { - if (group.title != 'Guest' && this.selectedDoc) { + groupList.forEach(group => { + if (group.title !== 'Guest' && this.selectedDoc) { const groupKey = 'acl-' + normalizeEmail(StrCast(group.title)); - if (this.selectedDoc[groupKey] != '' && this.selectedDoc[groupKey] != undefined) { - var permission; + if (this.selectedDoc[groupKey] !== '' && this.selectedDoc[groupKey] !== undefined) { + let permission; if (this.layoutDocAcls) { if (target[DocAcl][groupKey]) { permission = HierarchyMapping.get(target[DocAcl][groupKey])?.name; - } else if (target['embedContainer']) permission = StrCast(Doc.GetProto(DocCast(target['embedContainer']))[groupKey]); + } else if (target.embedContainer) permission = StrCast(Doc.GetProto(DocCast(target.embedContainer))[groupKey]); else permission = StrCast(Doc.GetProto(target)?.[groupKey]); } else permission = StrCast(target[groupKey]); groupTableEntries.unshift(this.sharingItem(StrCast(group.title), showAdmin, permission!, false)); @@ -531,22 +536,22 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps return ( <div> <div> - <br></br> Individuals with Access to this Document + <br /> Individuals with Access to this Document </div> <div className="propertiesView-sharingTable" style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}> - {<div> {individualTableEntries}</div>} + <div> {individualTableEntries}</div> </div> {groupTableEntries.length > 0 ? ( <div> <div> - <br></br> Groups with Access to this Document + <br /> Groups with Access to this Document </div> <div className="propertiesView-sharingTable" style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}> - {<div> {groupTableEntries}</div>} + <div> {groupTableEntries}</div> </div> </div> ) : null} - <br></br> Guest + <br /> Guest <div>{this.colorACLDropDown('Guest', showAdmin, guestPermission!, true)}</div> </div> ); @@ -558,7 +563,9 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps } @action - toggleCheckbox = () => (this.layoutFields = !this.layoutFields); + toggleCheckbox = () => { + this.layoutFields = !this.layoutFields; + }; @computed get color() { return SettingsManager.userColor; @@ -578,21 +585,17 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps const title = Array.from(titles.keys()).length > 1 ? '--multiple selected--' : StrCast(this.selectedDoc?.title); return ( <div> - <EditableText val={title} setVal={this.setTitle} color={this.color} type={Type.SEC} formLabel={'Title'} fillWidth /> + <EditableText val={title} setVal={this.setTitle} color={this.color} type={Type.SEC} formLabel="Title" fillWidth /> {LinkManager.Instance.currentLinkAnchor ? ( <p className="propertiesView-titleExtender"> - <> - <b>Anchor:</b> - {LinkManager.Instance.currentLinkAnchor.title} - </> + <b>Anchor:</b> + {StrCast(LinkManager.Instance.currentLinkAnchor.title)} </p> ) : null} {this.selectedLink?.title ? ( <p className="propertiesView-titleExtender"> - <> - <b>Link:</b> - {this.selectedLink.title} - </> + <b>Link:</b> + {StrCast(this.selectedLink.title)} </p> ) : null} </div> @@ -601,8 +604,8 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps @computed get currentType() { const documentType = StrCast(this.selectedDoc?.type); - var currentType: string = documentType; - var capitalizedDocType = Utils.cleanDocumentType(currentType as DocumentType); + const currentType: string = documentType; + const capitalizedDocType = ClientUtils.cleanDocumentType(currentType as DocumentType); return ( <div> @@ -618,7 +621,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps } @computed get currentComponent() { - var iconName = StrCast(this.selectedDoc?.systemIcon); + const iconName = StrCast(this.selectedDoc?.systemIcon); if (iconName) { const Icon = Icons[iconName as keyof typeof Icons]; @@ -650,13 +653,11 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps const ys = ink.map(p => p.Y); const left = Math.min(...xs); const top = Math.min(...ys); - const right = Math.max(...xs); - const bottom = Math.max(...ys); _centerPoints.push({ X: left, Y: top }); } } - var index = 0; + let index = 0; if (doc.type === DocumentType.INK && doc.x && doc.y && layout._width && layout._height && doc.data) { layout.rotation = NumCast(layout.rotation) + angle; const inks = Cast(doc.stroke, InkField)?.inkData; @@ -687,11 +688,13 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps get controlPointsButton() { return ( <div className="inking-button"> - <Tooltip title={<div className="dash-tooltip">{'Edit points'}</div>}> + <Tooltip title={<div className="dash-tooltip">Edit points</div>}> <div className="inking-button-points" style={{ backgroundColor: InkStrokeProperties.Instance._controlButton ? 'black' : '' }} - onPointerDown={action(() => (InkStrokeProperties.Instance._controlButton = !InkStrokeProperties.Instance._controlButton))}> + onPointerDown={action(() => { + InkStrokeProperties.Instance._controlButton = !InkStrokeProperties.Instance._controlButton; + })}> <FontAwesomeIcon icon="bezier-curve" size="lg" /> </div> </Tooltip> @@ -699,152 +702,129 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps ); } - inputBox = (key: string, value: any, setter: (val: string) => {}, title: string) => { - return ( - <div - className="inputBox" - style={{ - marginRight: title === 'X:' ? '19px' : '', - marginLeft: title === '∠:' ? '39px' : '', - }}> - <div className="inputBox-title"> {title} </div> - <input - className="inputBox-input" - type="text" - value={value} - style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }} - onChange={e => setter(e.target.value)} - onKeyDown={e => e.stopPropagation()} - /> - <div className="inputBox-button"> - <div className="inputBox-button-up" key="up2" onPointerDown={undoBatch(action(() => this.upDownButtons('up', key)))}> - <FontAwesomeIcon icon="caret-up" size="sm" /> - </div> - <div className="inputbox-Button-down" key="down2" onPointerDown={undoBatch(action(() => this.upDownButtons('down', key)))}> - <FontAwesomeIcon icon="caret-down" size="sm" /> - </div> + inputBox = (key: string, value: any, setter: (val: string) => {}, title: string) => ( + <div + className="inputBox" + style={{ + marginRight: title === 'X:' ? '19px' : '', + marginLeft: title === '∠:' ? '39px' : '', + }}> + <div className="inputBox-title"> {title} </div> + <input className="inputBox-input" type="text" value={value} style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }} onChange={e => setter(e.target.value)} onKeyDown={e => e.stopPropagation()} /> + <div className="inputBox-button"> + <div className="inputBox-button-up" key="up2" onPointerDown={undoBatch(action(() => this.upDownButtons('up', key)))}> + <FontAwesomeIcon icon="caret-up" size="sm" /> + </div> + <div className="inputbox-Button-down" key="down2" onPointerDown={undoBatch(action(() => this.upDownButtons('down', key)))}> + <FontAwesomeIcon icon="caret-down" size="sm" /> </div> </div> - ); - }; + </div> + ); - inputBoxDuo = (key: string, value: any, setter: (val: string) => {}, title1: string, key2: string, value2: any, setter2: (val: string) => {}, title2: string) => { - return ( - <div className="inputBox-duo"> - {this.inputBox(key, value, setter, title1)} - {title2 === '' ? null : this.inputBox(key2, value2, setter2, title2)} - </div> - ); - }; + inputBoxDuo = (key: string, value: any, setter: (val: string) => {}, title1: string, key2: string, value2: any, setter2: (val: string) => {}, title2: string) => ( + <div className="inputBox-duo"> + {this.inputBox(key, value, setter, title1)} + {title2 === '' ? null : this.inputBox(key2, value2, setter2, title2)} + </div> + ); @action upDownButtons = (dirs: string, field: string) => { const selDoc = this.selectedDoc; if (!selDoc) return; - //prettier-ignore + // prettier-ignore switch (field) { case 'Xps': selDoc.x = NumCast(this.selectedDoc?.x) + (dirs === 'up' ? 10 : -10); break; case 'Yps': selDoc.y = NumCast(this.selectedDoc?.y) + (dirs === 'up' ? 10 : -10); break; case 'stk': selDoc.stroke_width = NumCast(this.selectedDoc?.[DocData].stroke_width) + (dirs === 'up' ? 0.1 : -0.1); break; - case 'wid': - const oldWidth = NumCast(selDoc._width); - const oldHeight = NumCast(selDoc._height); - const oldX = NumCast(selDoc.x); - const oldY = NumCast(selDoc.y); - selDoc._width = oldWidth + (dirs === 'up' ? 10 : -10); - if (selDoc.type === DocumentType.INK && selDoc.x && selDoc.y && selDoc._height && selDoc._width) { - const ink = Cast(selDoc.data, InkField)?.inkData; - if (ink) { - const newPoints: { X: number; Y: number }[] = []; - for (var j = 0; j < ink.length; j++) { - // (new x — oldx) + (oldxpoint * newWidt)/oldWidth - const newX = NumCast(selDoc.x) - oldX + (ink[j].X * NumCast(selDoc._width)) / oldWidth; - const newY = NumCast(selDoc.y) - oldY + (ink[j].Y * NumCast(selDoc._height)) / oldHeight; - newPoints.push({ X: newX, Y: newY }); + case 'wid': { + const oldWidth = NumCast(selDoc._width); + const oldHeight = NumCast(selDoc._height); + const oldX = NumCast(selDoc.x); + const oldY = NumCast(selDoc.y); + selDoc._width = oldWidth + (dirs === 'up' ? 10 : -10); + if (selDoc.type === DocumentType.INK && selDoc.x && selDoc.y && selDoc._height && selDoc._width) { + const ink = Cast(selDoc.data, InkField)?.inkData; + if (ink) { + const newPoints: { X: number; Y: number }[] = []; + for (let j = 0; j < ink.length; j++) { + // (new x — oldx) + (oldxpoint * newWidt)/oldWidth + const newX = NumCast(selDoc.x) - oldX + (ink[j].X * NumCast(selDoc._width)) / oldWidth; + const newY = NumCast(selDoc.y) - oldY + (ink[j].Y * NumCast(selDoc._height)) / oldHeight; + newPoints.push({ X: newX, Y: newY }); + } + selDoc.data = new InkField(newPoints); } - selDoc.data = new InkField(newPoints); } } break; - case 'hgt': - const oWidth = NumCast(selDoc._width); - const oHeight = NumCast(selDoc._height); - const oX = NumCast(selDoc.x); - const oY = NumCast(selDoc.y); - selDoc._height = oHeight + (dirs === 'up' ? 10 : -10); - if (selDoc.type === DocumentType.INK && selDoc.x && selDoc.y && selDoc._height && selDoc._width) { - const ink = Cast(selDoc.data, InkField)?.inkData; - if (ink) { - const newPoints: { X: number; Y: number }[] = []; - for (var j = 0; j < ink.length; j++) { - // (new x — oldx) + (oldxpoint * newWidt)/oldWidth - const newX = NumCast(selDoc.x) - oX + (ink[j].X * NumCast(selDoc._width)) / oWidth; - const newY = NumCast(selDoc.y) - oY + (ink[j].Y * NumCast(selDoc._height)) / oHeight; - newPoints.push({ X: newX, Y: newY }); + case 'hgt': { + const oWidth = NumCast(selDoc._width); + const oHeight = NumCast(selDoc._height); + const oX = NumCast(selDoc.x); + const oY = NumCast(selDoc.y); + selDoc._height = oHeight + (dirs === 'up' ? 10 : -10); + if (selDoc.type === DocumentType.INK && selDoc.x && selDoc.y && selDoc._height && selDoc._width) { + const ink = Cast(selDoc.data, InkField)?.inkData; + if (ink) { + const newPoints: { X: number; Y: number }[] = []; + for (let j = 0; j < ink.length; j++) { + // (new x — oldx) + (oldxpoint * newWidt)/oldWidth + const newX = NumCast(selDoc.x) - oX + (ink[j].X * NumCast(selDoc._width)) / oWidth; + const newY = NumCast(selDoc.y) - oY + (ink[j].Y * NumCast(selDoc._height)) / oHeight; + newPoints.push({ X: newX, Y: newY }); + } + selDoc.data = new InkField(newPoints); } - selDoc.data = new InkField(newPoints); } } break; + default: { /* empty */ } } }; getField(key: string) { - return Field.toString(this.selectedDoc?.[DocData][key] as Field); + return Field.toString(this.selectedDoc?.[DocData][key] as FieldType); } - @computed get shapeXps() { - return NumCast(this.selectedDoc?.x); - } - @computed get shapeYps() { - return NumCast(this.selectedDoc?.y); - } - @computed get shapeHgt() { - return NumCast(this.selectedDoc?._height); - } - @computed get shapeWid() { - return NumCast(this.selectedDoc?._width); - } - @computed get strokeThk() { - return NumCast(this.selectedDoc?.[DocData].stroke_width); - } - set shapeXps(value) { - this.selectedDoc && (this.selectedDoc.x = Math.round(value * 100) / 100); - } - set shapeYps(value) { - this.selectedDoc && (this.selectedDoc.y = Math.round(value * 100) / 100); - } - set shapeWid(value) { - this.selectedDoc && (this.selectedDoc._width = Math.round(value * 100) / 100); - } - set shapeHgt(value) { - this.selectedDoc && (this.selectedDoc._height = Math.round(value * 100) / 100); - } - set strokeThk(value) { - this.selectedDoc && (this.selectedDoc[DocData].stroke_width = Math.round(value * 100) / 100); - } + @computed get shapeXps() { return NumCast(this.selectedDoc?.x); } // prettier-ignore + set shapeXps(value) { this.selectedDoc && (this.selectedDoc.x = Math.round(value * 100) / 100); } // prettier-ignore + @computed get shapeYps() { return NumCast(this.selectedDoc?.y); } // prettier-ignore + set shapeYps(value) { this.selectedDoc && (this.selectedDoc.y = Math.round(value * 100) / 100); } // prettier-ignore + @computed get shapeWid() { return NumCast(this.selectedDoc?._width); } // prettier-ignore + set shapeWid(value) { this.selectedDoc && (this.selectedDoc._width = Math.round(value * 100) / 100); } // prettier-ignore + @computed get shapeHgt() { return NumCast(this.selectedDoc?._height); } // prettier-ignore + set shapeHgt(value) { this.selectedDoc && (this.selectedDoc._height = Math.round(value * 100) / 100); } // prettier-ignore + @computed get strokeThk(){ return NumCast(this.selectedDoc?.[DocData].stroke_width); } // prettier-ignore + set strokeThk(value) { this.selectedDoc && (this.selectedDoc[DocData].stroke_width = Math.round(value * 100) / 100); } // prettier-ignore @computed get hgtInput() { return this.inputBoxDuo( 'hgt', this.shapeHgt, - undoable((val: string) => !isNaN(Number(val)) && (this.shapeHgt = +val), 'set height'), + undoable((val: string) => { + !Number(val) && (this.shapeHgt = +val); + }, 'set height'), 'H:', 'wid', this.shapeWid, - undoable((val: string) => !isNaN(Number(val)) && (this.shapeWid = +val), 'set width'), + undoable((val: string) => { + !isNaN(Number(val)) && (this.shapeWid = +val); + }, 'set width'), 'W:' ); } @computed get XpsInput() { + // prettier-ignore return this.inputBoxDuo( 'Xps', this.shapeXps, - undoable((val: string) => val !== '0' && !isNaN(Number(val)) && (this.shapeXps = +val), 'set x coord'), + undoable((val: string) => { val !== '0' && !isNaN(Number(val)) && (this.shapeXps = +val); }, 'set x coord'), 'X:', 'Yps', this.shapeYps, - undoable((val: string) => val !== '0' && !isNaN(Number(val)) && (this.shapeYps = +val), 'set y coord'), + undoable((val: string) => { val !== '0' && !isNaN(Number(val)) && (this.shapeYps = +val); }, 'set y coord'), 'Y:' ); } @@ -854,18 +834,10 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps private _lastDash: any = '2'; - @computed get colorFil() { - return StrCast(this.selectedDoc?.[DocData].fillColor); - } - @computed get colorStk() { - return StrCast(this.selectedDoc?.[DocData].color); - } - set colorFil(value) { - this.selectedDoc && (this.selectedDoc[DocData].fillColor = value ? value : undefined); - } - set colorStk(value) { - this.selectedDoc && (this.selectedDoc[DocData].color = value ? value : undefined); - } + @computed get colorFil() { return StrCast(this.selectedDoc?.[DocData].fillColor); } // prettier-ignore + set colorFil(value) { this.selectedDoc && (this.selectedDoc[DocData].fillColor = value || undefined); } // prettier-ignore + @computed get colorStk() { return StrCast(this.selectedDoc?.[DocData].color); } // prettier-ignore + set colorStk(value) { this.selectedDoc && (this.selectedDoc[DocData].color = value || undefined); } // prettier-ignore colorButton(value: string, type: string, setter: () => void) { return ( @@ -886,11 +858,9 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps colorPicker(color: string, setter: (color: string) => void) { return ( + // prettier-ignore <SketchPicker - onChange={undoable( - action((color: ColorResult) => setter(color.hex)), - 'set stroke color property' - )} + onChange={undoable( action((color: ColorResult) => setter(color.hex)), 'set stroke color property' )} presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']} color={color} /> @@ -911,10 +881,10 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps } @computed get fillPicker() { - return this.colorPicker(this.colorFil, (color: string) => (this.colorFil = color)); + return this.colorPicker(this.colorFil, (color: string) => { this.colorFil = color; }); // prettier-ignore } @computed get linePicker() { - return this.colorPicker(this.colorStk, (color: string) => (this.colorStk = color)); + return this.colorPicker(this.colorStk, (color: string) => { this.colorStk = color; }); // prettier-ignore } @computed get strokeAndFill() { @@ -936,60 +906,48 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps ); } - @computed get dashdStk() { - return this.selectedDoc?.stroke_dash || ''; - } - @computed get widthStk() { - return this.getField('stroke_width') || '1'; - } - @computed get markScal() { - return Number(this.getField('stroke_markerScale') || '1'); - } - @computed get markHead() { - return this.getField('stroke_startMarker') || ''; - } - @computed get markTail() { - return this.getField('stroke_endMarker') || ''; - } + @computed get dashdStk() { return this.selectedDoc?.stroke_dash || ''; } // prettier-ignore set dashdStk(value) { value && (this._lastDash = value); this.selectedDoc && (this.selectedDoc[DocData].stroke_dash = value ? this._lastDash : undefined); } - set markScal(value) { - this.selectedDoc && (this.selectedDoc[DocData].stroke_markerScale = Number(value)); - } + @computed get widthStk() { return this.getField('stroke_width') || '1'; } // prettier-ignore set widthStk(value) { this.selectedDoc && (this.selectedDoc[DocData].stroke_width = Number(value)); } + @computed get markScal() { return Number(this.getField('stroke_markerScale') || '1'); } // prettier-ignore + set markScal(value) { + this.selectedDoc && (this.selectedDoc[DocData].stroke_markerScale = Number(value)); + } + @computed get markHead() { return this.getField('stroke_startMarker') || ''; } // prettier-ignore set markHead(value) { this.selectedDoc && (this.selectedDoc[DocData].stroke_startMarker = value); } + @computed get markTail() { return this.getField('stroke_endMarker') || ''; } // prettier-ignore set markTail(value) { this.selectedDoc && (this.selectedDoc[DocData].stroke_endMarker = value); } @computed get stkInput() { - return this.regInput('stk', this.widthStk, (val: string) => (this.widthStk = val)); + return this.regInput('stk', this.widthStk, (val: string) => { this.widthStk = val; }); // prettier-ignore } @computed get markScaleInput() { - return this.regInput('scale', this.markScal.toString(), (val: string) => (this.markScal = Number(val))); + return this.regInput('scale', this.markScal.toString(), (val: string) => { this.markScal = Number(val); }); // prettier-ignore } - regInput = (key: string, value: any, setter: (val: string) => {}) => { - return ( - <div className="inputBox"> - <input className="inputBox-input" type="text" value={value} style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }} onChange={e => setter(e.target.value)} /> - <div className="inputBox-button"> - <div className="inputBox-button-up" key="up2" onPointerDown={undoBatch(action(() => this.upDownButtons('up', key)))}> - <FontAwesomeIcon icon="caret-up" size="sm" /> - </div> - <div className="inputbox-Button-down" key="down2" onPointerDown={undoBatch(action(() => this.upDownButtons('down', key)))}> - <FontAwesomeIcon icon="caret-down" size="sm" /> - </div> + regInput = (key: string, value: any, setter: (val: string) => {}) => ( + <div className="inputBox"> + <input className="inputBox-input" type="text" value={value} style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }} onChange={e => setter(e.target.value)} /> + <div className="inputBox-button"> + <div className="inputBox-button-up" key="up2" onPointerDown={undoBatch(action(() => this.upDownButtons('up', key)))}> + <FontAwesomeIcon icon="caret-up" size="sm" /> + </div> + <div className="inputbox-Button-down" key="down2" onPointerDown={undoBatch(action(() => this.upDownButtons('down', key)))}> + <FontAwesomeIcon icon="caret-down" size="sm" /> </div> </div> - ); - }; + </div> + ); @action CloseAll = () => { @@ -1005,18 +963,55 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps @computed get widthAndDash() { return ( + // prettier-ignore <div className="widthAndDash"> - <div className="width">{this.getNumber('Thickness', '', 0, Math.max(50, this.strokeThk), this.strokeThk, (val: number) => !isNaN(val) && (this.strokeThk = val), 50, 1)}</div> - <div className="width">{this.getNumber('Arrow Scale', '', 0, Math.max(10, this.markScal), this.markScal, (val: number) => !isNaN(val) && (this.markScal = val), 10, 1)}</div> + <div className="width"> + {this.getNumber( + 'Thickness', + '', + 0, + Math.max(50, this.strokeThk), + this.strokeThk, + (val: number) => { !isNaN(val) && (this.strokeThk = val); }, + 50, + 1 + )} + </div> + <div className="width"> + {this.getNumber( + 'Arrow Scale', + '', + 0, + Math.max(10, this.markScal), + this.markScal, + (val: number) => { !isNaN(val) && (this.markScal = val); }, + 10, + 1 + )} + </div> <div className="arrows"> <div className="arrows-head"> <div className="arrows-head-title">Arrow Head: </div> - <input key="markHead" className="arrows-head-input" type="checkbox" checked={this.markHead !== ''} onChange={undoBatch(action(() => (this.markHead = this.markHead ? '' : 'arrow')))} /> + <input + key="markHead" + className="arrows-head-input" + type="checkbox" + checked={this.markHead !== ''} + onChange={undoBatch(action(() => { this.markHead = this.markHead ? '' : 'arrow'; }))} + /> </div> <div className="arrows-tail"> <div className="arrows-tail-title">Arrow End: </div> - <input key="markTail" className="arrows-tail-input" type="checkbox" checked={this.markTail !== ''} onChange={undoBatch(action(() => (this.markTail = this.markTail ? '' : 'arrow')))} /> + <input + key="markTail" + className="arrows-tail-input" + type="checkbox" + checked={this.markTail !== ''} + onChange={undoBatch( + action(() => { this.markTail = this.markTail ? '' : 'arrow'; }) + )} + /> </div> </div> <div className="dashed"> @@ -1045,50 +1040,53 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps setFinalNumber = () => { this._sliderBatch?.end(); }; - getNumber = (label: string, unit: string, min: number, max: number, number: number, setNumber: any, autorange?: number, autorangeMinVal?: number) => { - return ( - <div key={label + this.selectedDoc?.title}> - <NumberInput formLabel={label} formLabelPlacement={'left'} type={Type.SEC} unit={unit} fillWidth color={this.color} number={number} setNumber={setNumber} min={min} max={max} /> - <Slider - key={label} - onPointerDown={e => (this._sliderBatch = UndoManager.StartBatch('slider ' + label))} - multithumb={false} - color={this.color} - size={Size.XSMALL} - min={min} - max={max} - autorangeMinVal={autorangeMinVal} - autorange={autorange} - number={number} - unit={unit} - decimals={1} - setFinalNumber={this.setFinalNumber} - setNumber={setNumber} - fillWidth - /> - </div> - ); - }; + getNumber = (label: string, unit: string, min: number, max: number, number: number, setNumber: any, autorange?: number, autorangeMinVal?: number) => ( + <div key={label + (this.selectedDoc?.title ?? '')}> + <NumberInput formLabel={label} formLabelPlacement="left" type={Type.SEC} unit={unit} fillWidth color={this.color} number={number} setNumber={setNumber} min={min} max={max} /> + <Slider + key={label} + onPointerDown={() => { + this._sliderBatch = UndoManager.StartBatch('slider ' + label); + }} + multithumb={false} + color={this.color} + size={Size.XSMALL} + min={min} + max={max} + autorangeMinVal={autorangeMinVal} + autorange={autorange} + number={number} + unit={unit} + decimals={1} + setFinalNumber={this.setFinalNumber} + setNumber={setNumber} + fillWidth + /> + </div> + ); + setVal = (func: (doc: Doc, val: number) => void) => (val: number) => this.selectedDoc && !isNaN(val) && func(this.selectedDoc, val); @computed get transformEditor() { return ( + // prettier-ignore <div className="transform-editor"> - {!this.isStack ? null : this.getNumber('Gap', ' px', 0, 200, NumCast(this.selectedDoc!.gridGap), (val: number) => !isNaN(val) && (this.selectedDoc!.gridGap = val))} - {!this.isStack ? null : this.getNumber('xMargin', ' px', 0, 500, NumCast(this.selectedDoc!.xMargin), (val: number) => !isNaN(val) && (this.selectedDoc!.xMargin = val))} - {!this.isStack ? null : this.getNumber('yMargin', ' px', 0, 500, NumCast(this.selectedDoc!.yMargin), (val: number) => !isNaN(val) && (this.selectedDoc!.yMargin = val))} - {!this.isGroup ? null : this.getNumber('Padding', ' px', 0, 500, NumCast(this.selectedDoc!.xPadding), (val: number) => !isNaN(val) && (this.selectedDoc!.xPadding = this.selectedDoc!.yPadding = val))} + {!this.isStack ? null : this.getNumber('Gap', ' px', 0, 200, NumCast(this.selectedDoc!.gridGap), this.setVal((doc: Doc, val: number) => { doc.gridGap = val; })) } + {!this.isStack ? null : this.getNumber('xMargin', ' px', 0, 500, NumCast(this.selectedDoc!.xMargin), this.setVal((doc: Doc, val: number) => { doc.xMargin = val; })) } + {!this.isStack ? null : this.getNumber('yMargin', ' px', 0, 500, NumCast(this.selectedDoc!.yMargin), this.setVal((doc: Doc, val: number) => { doc.yMargin = val; })) } + {!this.isGroup ? null : this.getNumber('Padding', ' px', 0, 500, NumCast(this.selectedDoc!.xPadding), this.setVal((doc: Doc, val: number) => { doc.xPadding = doc.yPadding = val; })) } {this.isInk ? this.controlPointsButton : null} - {this.getNumber('Width', ' px', 0, Math.max(1000, this.shapeWid), this.shapeWid, (val: number) => !isNaN(val) && (this.shapeWid = val), 1000, 1)} - {this.getNumber('Height', ' px', 0, Math.max(1000, this.shapeHgt), this.shapeHgt, (val: number) => !isNaN(val) && (this.shapeHgt = val), 1000, 1)} - {this.getNumber('X', ' px', this.shapeXps - 500, this.shapeXps + 500, this.shapeXps, (val: number) => !isNaN(val) && (this.shapeXps = val), 1000)} - {this.getNumber('Y', ' px', this.shapeYps - 500, this.shapeYps + 500, this.shapeYps, (val: number) => !isNaN(val) && (this.shapeYps = val), 1000)} + {this.getNumber('Width', ' px', 0, Math.max(1000, this.shapeWid), this.shapeWid, this.setVal((doc: Doc, val:number) => {this.shapeWid = val}), 1000, 1)} + {this.getNumber('Height', ' px', 0, Math.max(1000, this.shapeHgt), this.shapeHgt, this.setVal((doc: Doc, val:number) => {this.shapeHgt = val}), 1000, 1)} + {this.getNumber('X', ' px', this.shapeXps - 500, this.shapeXps + 500, this.shapeXps, this.setVal((doc: Doc, val:number) => {this.shapeXps = val}), 1000)} + {this.getNumber('Y', ' px', this.shapeYps - 500, this.shapeYps + 500, this.shapeYps, this.setVal((doc: Doc, val:number) => {this.shapeYps = val}), 1000)} </div> ); } @computed get optionsSubMenu() { return ( - <PropertiesSection title="Options" inSection={this.inOptions} isOpen={this.openOptions} setInSection={bool => (this.inOptions = bool)} setIsOpen={bool => (this.openOptions = bool)} onDoubleClick={this.CloseAll}> + // prettier-ignore + <PropertiesSection title="Options" inSection={this.inOptions} isOpen={this.openOptions} setInSection={bool => { this.inOptions = bool; }} setIsOpen={bool => { this.openOptions = bool; }} onDoubleClick={this.CloseAll}> <PropertiesButtons /> </PropertiesSection> ); @@ -1096,12 +1094,23 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps @computed get sharingSubMenu() { return ( - <PropertiesSection title="Sharing and Permissions" isOpen={this.openSharing} setIsOpen={bool => (this.openSharing = bool)} onDoubleClick={() => this.CloseAll()}> + // prettier-ignore + <PropertiesSection + title="Sharing and Permissions" + isOpen={this.openSharing} + setIsOpen={bool => { this.openSharing = bool; }} + onDoubleClick={this.CloseAll}> <> {/* <div className="propertiesView-buttonContainer"> */} <div className="propertiesView-acls-checkbox"> Layout Permissions - <Checkbox color="primary" onChange={action(() => (this.layoutDocAcls = !this.layoutDocAcls))} checked={this.layoutDocAcls} /> + <Checkbox + color="primary" + onChange={action(() => { + this.layoutDocAcls = !this.layoutDocAcls; + })} + checked={this.layoutDocAcls} + /> </div> {/* <Tooltip title={<><div className="dash-tooltip">{"Re-distribute sharing settings"}</div></>}> <button onPointerDown={() => SharingManager.Instance.distributeOverCollection(this.selectedDoc!)}> @@ -1141,7 +1150,8 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps @computed get filtersSubMenu() { return ( - <PropertiesSection title="Filters" isOpen={this.openFilters} setIsOpen={bool => (this.openFilters = bool)} onDoubleClick={() => this.CloseAll()}> + // prettier-ignore + <PropertiesSection title="Filters" isOpen={this.openFilters} setIsOpen={bool => { this.openFilters = bool; }} onDoubleClick={this.CloseAll}> <div className="propertiesView-content filters" style={{ position: 'relative', height: 'auto' }}> <FilterPanel Document={this.selectedDoc ?? Doc.ActiveDashboard!} /> </div> @@ -1151,11 +1161,12 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps @computed get inkSubMenu() { return ( + // prettier-ignore <> - <PropertiesSection title="Appearance" isOpen={this.openAppearance} setIsOpen={bool => (this.openAppearance = bool)} onDoubleClick={() => this.CloseAll()}> + <PropertiesSection title="Appearance" isOpen={this.openAppearance} setIsOpen={bool => { this.openAppearance = bool; }} onDoubleClick={this.CloseAll}> {this.selectedLayoutDoc?.layout_isSvg ? this.appearanceEditor : null} </PropertiesSection> - <PropertiesSection title="Transform" isOpen={this.openTransform} setIsOpen={bool => (this.openTransform = bool)} onDoubleClick={() => this.CloseAll()}> + <PropertiesSection title="Transform" isOpen={this.openTransform} setIsOpen={bool => { this.openTransform = bool; }} onDoubleClick={this.CloseAll}> {this.transformEditor} </PropertiesSection> </> @@ -1164,7 +1175,13 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps @computed get fieldsSubMenu() { return ( - <PropertiesSection title="Fields & Tags" isOpen={this.openFields} setIsOpen={bool => (this.openFields = bool)} onDoubleClick={() => this.CloseAll()}> + <PropertiesSection + title="Fields & Tags" + isOpen={this.openFields} + setIsOpen={bool => { + this.openFields = bool; + }} + onDoubleClick={this.CloseAll}> <div className="propertiesView-content fields">{Doc.noviceMode ? this.noviceFields : this.expandedField}</div> </PropertiesSection> ); @@ -1172,7 +1189,13 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps @computed get contextsSubMenu() { return ( - <PropertiesSection title="Other Contexts" isOpen={this.openContexts} setIsOpen={bool => (this.openContexts = bool)} onDoubleClick={() => this.CloseAll()}> + <PropertiesSection + title="Other Contexts" + isOpen={this.openContexts} + setIsOpen={bool => { + this.openContexts = bool; + }} + onDoubleClick={this.CloseAll}> {this.contextCount > 0 ? this.contexts : 'There are no other contexts.'} </PropertiesSection> ); @@ -1180,7 +1203,13 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps @computed get linksSubMenu() { return ( - <PropertiesSection title="Linked To" isOpen={this.openLinks} setIsOpen={bool => (this.openLinks = bool)} onDoubleClick={this.CloseAll}> + <PropertiesSection + title="Linked To" + isOpen={this.openLinks} + setIsOpen={bool => { + this.openLinks = bool; + }} + onDoubleClick={this.CloseAll}> {this.linkCount > 0 ? this.links : 'There are no current links.'} </PropertiesSection> ); @@ -1188,14 +1217,20 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps @computed get layoutSubMenu() { return ( - <PropertiesSection title="Layout" isOpen={this.openLayout} setIsOpen={bool => (this.openLayout = bool)} onDoubleClick={this.CloseAll}> + <PropertiesSection + title="Layout" + isOpen={this.openLayout} + setIsOpen={bool => { + this.openLayout = bool; + }} + onDoubleClick={this.CloseAll}> {this.layoutPreview} </PropertiesSection> ); } @computed get description() { - return Field.toString(this.selectedLink?.link_description as any as Field); + return Field.toString(this.selectedLink?.link_description as any as FieldType); } @computed get relationship() { return StrCast(this.selectedLink?.link_relationship); @@ -1250,12 +1285,12 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps // if the relationship is already in the list AND the new rel is different from the prev rel, update the rel sizes } else if (linkRelationshipList && value !== prevRelationship) { const index = linkRelationshipList.indexOf(value); - //increment size of new relationship size + // increment size of new relationship size if (index !== -1 && index < linkRelationshipSizes.length) { const pvalue = linkRelationshipSizes[index]; linkRelationshipSizes[index] = pvalue === undefined || !Number.isFinite(pvalue) ? 1 : pvalue + 1; } - //decrement the size of the previous relationship if it already exists (i.e. not default 'link' relationship upon link creation) + // decrement the size of the previous relationship if it already exists (i.e. not default 'link' relationship upon link creation) if (linkRelationshipList.includes(prevRelationship)) { const pindex = linkRelationshipList.indexOf(prevRelationship); if (pindex !== -1 && pindex < linkRelationshipSizes.length) { @@ -1265,21 +1300,25 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps } } this.relationshipButtonColor = 'rgb(62, 133, 55)'; - setTimeout( - action(() => (this.relationshipButtonColor = '')), - 750 - ); + setTimeout(action(() => { this.relationshipButtonColor = ''; }), 750); // prettier-ignore return true; } + return undefined; }); - changeFollowBehavior = undoable((loc: Opt<string>) => this.sourceAnchor && (this.sourceAnchor.followLinkLocation = loc), 'change follow behavior'); + changeFollowBehavior = undoable((loc: Opt<string>) => { + this.sourceAnchor && (this.sourceAnchor.followLinkLocation = loc); + }, 'change follow behavior'); @undoBatch - changeAnimationBehavior = action((behavior: string) => this.sourceAnchor && (this.sourceAnchor.followLinkAnimEffect = behavior)); + changeAnimationBehavior = action((behavior: string) => { + this.sourceAnchor && (this.sourceAnchor.followLinkAnimEffect = behavior); + }); @undoBatch - changeEffectDirection = action((effect: PresEffectDirection) => this.sourceAnchor && (this.sourceAnchor.followLinkAnimDirection = effect)); + changeEffectDirection = action((effect: PresEffectDirection) => { + this.sourceAnchor && (this.sourceAnchor.followLinkAnimDirection = effect); + }); animationDirection = (direction: PresEffectDirection, icon: string, gridColumn: number, gridRow: number, opts: object) => { const lanch = this.sourceAnchor; @@ -1300,7 +1339,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps document.getElementById('link_description_input')?.blur(); }; - onDescriptionKey = (e: React.KeyboardEvent<HTMLTextAreaElement>) => { + onDescriptionKey = () => { // if (e.key === 'Enter') { // this.setDescripValue(this.description); // document.getElementById('link_description_input')?.blur(); @@ -1320,7 +1359,13 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps }; toggleLinkProp = (e: React.PointerEvent, prop: string) => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedLink && (this.selectedLink[prop] = !this.selectedLink[prop])))); + setupMoveUpEvents( + this, + e, + returnFalse, + emptyFunction, + undoBatch(action(() => { this.selectedLink && (this.selectedLink[prop] = !this.selectedLink[prop]); })) // prettier-ignore + ); }; @computed get destinationAnchor() { @@ -1343,12 +1388,10 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps e, returnFalse, emptyFunction, - undoBatch( - action(() => { - anchor[prop] = anchor[prop] === value ? ovalue : value; - this.selectedDoc && cb(anchor[prop]); - }) - ) + undoBatch(action(() => { + anchor[prop] = anchor[prop] === value ? ovalue : value; + this.selectedDoc && cb(anchor[prop]); + })) // prettier-ignore ); }; @@ -1357,7 +1400,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps return ( <input style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }} - autoComplete={'off'} + autoComplete="off" id="link_relationship_input" value={StrCast(this.selectedLink?.link_relationship)} onKeyDown={this.onRelationshipKey} @@ -1459,11 +1502,14 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps '10', NumCast(this.sourceAnchor?.followLinkTransitionTime) / 1000, true, - (val: string) => PresBox.SetTransitionTime(val, (timeInMS: number) => this.sourceAnchor && (this.sourceAnchor.followLinkTransitionTime = timeInMS)), + (val: string) => + PresBox.SetTransitionTime(val, (timeInMS: number) => { + this.sourceAnchor && (this.sourceAnchor.followLinkTransitionTime = timeInMS); + }), indent )}{' '} <div - className={'slider-headers'} + className="slider-headers" style={{ display: 'grid', justifyContent: 'space-between', @@ -1478,86 +1524,118 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps </div>{' '} <div className="propertiesView-input inline"> <p>Play Target Audio</p> - <button - style={{ background: !this.sourceAnchor?.followLinkAudio ? '' : '#4476f7', borderRadius: 3 }} - onPointerDown={e => this.toggleAnchorProp(e, 'followLinkAudio', this.sourceAnchor)} - onClick={e => e.stopPropagation()} - className="propertiesButton"> - <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> - </button> + { + // eslint-disable-next-line jsx-a11y/control-has-associated-label + <button + type="button" + style={{ background: !this.sourceAnchor?.followLinkAudio ? '' : '#4476f7', borderRadius: 3 }} + onPointerDown={e => this.toggleAnchorProp(e, 'followLinkAudio', this.sourceAnchor)} + onClick={e => e.stopPropagation()} + className="propertiesButton"> + <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> + </button> + } </div> <div className="propertiesView-input inline"> <p>Play Target Video</p> - <button - style={{ background: !this.sourceAnchor?.followLinkVideo ? '' : '#4476f7', borderRadius: 3 }} - onPointerDown={e => this.toggleAnchorProp(e, 'followLinkVideo', this.sourceAnchor)} - onClick={e => e.stopPropagation()} - className="propertiesButton"> - <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> - </button> + { + // eslint-disable-next-line jsx-a11y/control-has-associated-label + <button + type="button" + style={{ background: !this.sourceAnchor?.followLinkVideo ? '' : '#4476f7', borderRadius: 3 }} + onPointerDown={e => this.toggleAnchorProp(e, 'followLinkVideo', this.sourceAnchor)} + onClick={e => e.stopPropagation()} + className="propertiesButton"> + <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> + </button> + } </div> <div className="propertiesView-input inline"> <p>Zoom Text Selections</p> - <button - style={{ background: !this.sourceAnchor?.followLinkZoomText ? '' : '#4476f7', borderRadius: 3 }} - onPointerDown={e => this.toggleAnchorProp(e, 'followLinkZoomText', this.sourceAnchor)} - onClick={e => e.stopPropagation()} - className="propertiesButton"> - <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> - </button> + { + // eslint-disable-next-line jsx-a11y/control-has-associated-label + <button + type="button" + style={{ background: !this.sourceAnchor?.followLinkZoomText ? '' : '#4476f7', borderRadius: 3 }} + onPointerDown={e => this.toggleAnchorProp(e, 'followLinkZoomText', this.sourceAnchor)} + onClick={e => e.stopPropagation()} + className="propertiesButton"> + <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> + </button> + } </div> <div className="propertiesView-input inline"> <p>Toggle Follow to Outer Context</p> - <button - style={{ background: !this.sourceAnchor?.followLinkToOuterContext ? '' : '#4476f7', borderRadius: 3 }} - onPointerDown={e => this.toggleAnchorProp(e, 'followLinkToOuterContext', this.sourceAnchor)} - onClick={e => e.stopPropagation()} - className="propertiesButton"> - <FontAwesomeIcon className="fa-icon" icon={faWindowMaximize as IconLookup} size="lg" /> - </button> + { + // eslint-disable-next-line jsx-a11y/control-has-associated-label + <button + type="button" + style={{ background: !this.sourceAnchor?.followLinkToOuterContext ? '' : '#4476f7', borderRadius: 3 }} + onPointerDown={e => this.toggleAnchorProp(e, 'followLinkToOuterContext', this.sourceAnchor)} + onClick={e => e.stopPropagation()} + className="propertiesButton"> + <FontAwesomeIcon className="fa-icon" icon={faWindowMaximize as IconLookup} size="lg" /> + </button> + } </div> <div className="propertiesView-input inline"> <p>Toggle Target (Show/Hide)</p> - <button - style={{ background: !this.sourceAnchor?.followLinkToggle ? '' : '#4476f7', borderRadius: 3 }} - onPointerDown={e => this.toggleAnchorProp(e, 'followLinkToggle', this.sourceAnchor)} - onClick={e => e.stopPropagation()} - className="propertiesButton"> - <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> - </button> + { + // eslint-disable-next-line jsx-a11y/control-has-associated-label + <button + type="button" + style={{ background: !this.sourceAnchor?.followLinkToggle ? '' : '#4476f7', borderRadius: 3 }} + onPointerDown={e => this.toggleAnchorProp(e, 'followLinkToggle', this.sourceAnchor)} + onClick={e => e.stopPropagation()} + className="propertiesButton"> + <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> + </button> + } </div> <div className="propertiesView-input inline"> <p>Ease Transitions</p> - <button - style={{ background: this.sourceAnchor?.followLinkEase === 'linear' ? '' : '#4476f7', borderRadius: 3 }} - onPointerDown={e => this.toggleAnchorProp(e, 'followLinkEase', this.sourceAnchor, 'ease', 'linear')} - onClick={e => e.stopPropagation()} - className="propertiesButton"> - <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> - </button> + { + // eslint-disable-next-line jsx-a11y/control-has-associated-label + <button + type="button" + style={{ background: this.sourceAnchor?.followLinkEase === 'linear' ? '' : '#4476f7', borderRadius: 3 }} + onPointerDown={e => this.toggleAnchorProp(e, 'followLinkEase', this.sourceAnchor, 'ease', 'linear')} + onClick={e => e.stopPropagation()} + className="propertiesButton"> + <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> + </button> + } </div> <div className="propertiesView-input inline"> <p>Capture Offset to Target</p> - <button - style={{ background: this.sourceAnchor?.followLinkXoffset === undefined ? '' : '#4476f7', borderRadius: 3 }} - onPointerDown={e => { - this.toggleAnchorProp(e, 'followLinkXoffset', this.sourceAnchor, NumCast(this.destinationAnchor?.x) - NumCast(this.sourceAnchor?.x), undefined); - this.toggleAnchorProp(e, 'followLinkYoffset', this.sourceAnchor, NumCast(this.destinationAnchor?.y) - NumCast(this.sourceAnchor?.y), undefined); - }} - onClick={e => e.stopPropagation()} - className="propertiesButton"> - <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> - </button> + { + // eslint-disable-next-line jsx-a11y/control-has-associated-label + <button + type="button" + style={{ background: this.sourceAnchor?.followLinkXoffset === undefined ? '' : '#4476f7', borderRadius: 3 }} + onPointerDown={e => { + this.toggleAnchorProp(e, 'followLinkXoffset', this.sourceAnchor, NumCast(this.destinationAnchor?.x) - NumCast(this.sourceAnchor?.x), undefined); + this.toggleAnchorProp(e, 'followLinkYoffset', this.sourceAnchor, NumCast(this.destinationAnchor?.y) - NumCast(this.sourceAnchor?.y), undefined); + }} + onClick={e => e.stopPropagation()} + className="propertiesButton"> + <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> + </button> + } </div> <div className="propertiesView-input inline"> <p>Center Target (no zoom)</p> - <button - style={{ background: this.sourceAnchor?.followLinkZoom ? '' : '#4476f7', borderRadius: 3 }} - onPointerDown={e => this.toggleAnchorProp(e, 'followLinkZoom', this.sourceAnchor)} - onClick={e => e.stopPropagation()} - className="propertiesButton"> - <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> - </button> + { + // eslint-disable-next-line jsx-a11y/control-has-associated-label + <button + type="button" + style={{ background: this.sourceAnchor?.followLinkZoom ? '' : '#4476f7', borderRadius: 3 }} + onPointerDown={e => this.toggleAnchorProp(e, 'followLinkZoom', this.sourceAnchor)} + onClick={e => e.stopPropagation()} + className="propertiesButton"> + <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> + </button> + } </div> <div className="propertiesView-input inline" style={{ display: 'grid', gridTemplateColumns: '78px calc(100% - 108px) 50px' }}> <p>Zoom %</p> @@ -1565,24 +1643,28 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps <input className="presBox-input" style={{ width: '100%', color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }} readOnly={true} type="number" value={zoom} /> <div className="ribbon-propertyUpDown" style={{ display: 'flex', flexDirection: 'column' }}> <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setZoom(String(zoom), 0.1))}> - <FontAwesomeIcon icon={'caret-up'} /> + <FontAwesomeIcon icon="caret-up" /> </div> <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setZoom(String(zoom), -0.1))}> - <FontAwesomeIcon icon={'caret-down'} /> + <FontAwesomeIcon icon="caret-down" /> </div> </div> </div> - <button - style={{ background: !targZoom || this.sourceAnchor?.followLinkZoomScale === 0 ? '' : '#4476f7', borderRadius: 3, gridColumn: 3 }} - onPointerDown={e => this.toggleAnchorProp(e, 'followLinkZoom', this.sourceAnchor)} - onClick={e => e.stopPropagation()} - className="propertiesButton"> - <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" /> - </button> + { + // eslint-disable-next-line jsx-a11y/control-has-associated-label + <button + type="button" + style={{ background: !targZoom || this.sourceAnchor?.followLinkZoomScale === 0 ? '' : '#4476f7', borderRadius: 3, gridColumn: 3 }} + onPointerDown={e => this.toggleAnchorProp(e, 'followLinkZoom', this.sourceAnchor)} + onClick={e => e.stopPropagation()} + className="propertiesButton"> + <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" /> + </button> + } </div> {!targZoom ? null : PresBox.inputter('0', '1', '100', zoom, true, this.setZoom, 30)} <div - className={'slider-headers'} + className="slider-headers" style={{ display: !targZoom ? 'none' : 'grid', justifyContent: 'space-between', @@ -1594,7 +1676,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps }}> <div className="slider-text">0%</div> <div className="slider-text">100%</div> - </div>{' '} + </div> </div> )} </> @@ -1622,128 +1704,136 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps </div> </div> ); - } else { - if (this.selectedDoc && !this.isPres) { - return ( - <div - className="propertiesView" - style={{ - background: SettingsManager.userBackgroundColor, - color: SettingsManager.userColor, - width: this._props.width, - minWidth: this._props.width, - }}> - <div className="propertiesView-propAndInfoGrouping"> - <div className="propertiesView-sectionTitle" style={{ width: this._props.width }}> - Properties - <div className="propertiesView-info" onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/properties')}> - <IconButton icon={<FontAwesomeIcon icon="info-circle" />} color={SettingsManager.userColor} /> - </div> + } + if (this.selectedDoc && !this.isPres) { + return ( + <div + className="propertiesView" + style={{ + background: SettingsManager.userBackgroundColor, + color: SettingsManager.userColor, + width: this._props.width, + minWidth: this._props.width, + }}> + <div className="propertiesView-propAndInfoGrouping"> + <div className="propertiesView-sectionTitle" style={{ width: this._props.width }}> + Properties + <div className="propertiesView-info" onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/properties')}> + <IconButton icon={<FontAwesomeIcon icon="info-circle" />} color={SettingsManager.userColor} /> </div> </div> + </div> - <div className="propertiesView-name">{this.editableTitle}</div> - <div className="propertiesView-type"> {this.currentType} </div> - {this.fieldsSubMenu} - {this.optionsSubMenu} - {this.linksSubMenu} - {!this.selectedLink || !this.openLinks ? null : this.linkProperties} - {this.inkSubMenu} - {this.contextsSubMenu} - {isNovice ? null : this.sharingSubMenu} - {this.filtersSubMenu} - {isNovice ? null : this.layoutSubMenu} + <div className="propertiesView-name">{this.editableTitle}</div> + <div className="propertiesView-type"> {this.currentType} </div> + {this.fieldsSubMenu} + {this.optionsSubMenu} + {this.linksSubMenu} + {!this.selectedLink || !this.openLinks ? null : this.linkProperties} + {this.inkSubMenu} + {this.contextsSubMenu} + {isNovice ? null : this.sharingSubMenu} + {this.filtersSubMenu} + {isNovice ? null : this.layoutSubMenu} + </div> + ); + } + if (this.isPres && PresBox.Instance) { + const selectedItem: boolean = PresBox.Instance.selectedArray.size > 0; + const type = [DocumentType.AUDIO, DocumentType.VID].includes(DocCast(PresBox.Instance.activeItem?.annotationOn)?.type as any as DocumentType) + ? (DocCast(PresBox.Instance.activeItem?.annotationOn)?.type as any as DocumentType) + : PresBox.targetRenderedDoc(PresBox.Instance.activeItem)?.type; + return ( + <div className="propertiesView" style={{ width: this._props.width }}> + <div className="propertiesView-sectionTitle" style={{ width: this._props.width }}> + Presentation </div> - ); - } - if (this.isPres && PresBox.Instance) { - const selectedItem: boolean = PresBox.Instance.selectedArray.size > 0; - const type = [DocumentType.AUDIO, DocumentType.VID].includes(DocCast(PresBox.Instance.activeItem?.annotationOn)?.type as any as DocumentType) - ? (DocCast(PresBox.Instance.activeItem?.annotationOn)?.type as any as DocumentType) - : PresBox.targetRenderedDoc(PresBox.Instance.activeItem)?.type; - return ( - <div className="propertiesView" style={{ width: this._props.width }}> - <div className="propertiesView-sectionTitle" style={{ width: this._props.width }}> - Presentation - </div> - <div className="propertiesView-name" style={{ borderBottom: 0 }}> - {this.editableTitle} - <div className="propertiesView-presSelected"> - <div className="propertiesView-selectedCount">{PresBox.Instance.selectedArray.size} selected</div> - <div className="propertiesView-selectedList">{PresBox.Instance.listOfSelected}</div> - </div> + <div className="propertiesView-name" style={{ borderBottom: 0 }}> + {this.editableTitle} + <div className="propertiesView-presSelected"> + <div className="propertiesView-selectedCount">{PresBox.Instance.selectedArray.size} selected</div> + <div className="propertiesView-selectedList">{PresBox.Instance.listOfSelected}</div> </div> - {!selectedItem ? null : ( - <div className="propertiesView-presentationTrails"> - <div - className="propertiesView-presentationTrails-title" - onPointerDown={action(() => (this.openPresTransitions = !this.openPresTransitions))} - style={{ - color: SettingsManager.userColor, - backgroundColor: this.openPresTransitions ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor, - }}> - <FontAwesomeIcon style={{ alignSelf: 'center' }} icon={'rocket'} /> Transitions - <div className="propertiesView-presentationTrails-title-icon"> - <FontAwesomeIcon icon={this.openPresTransitions ? 'caret-down' : 'caret-right'} size="lg" /> - </div> + </div> + {!selectedItem ? null : ( + <div className="propertiesView-presentationTrails"> + <div + className="propertiesView-presentationTrails-title" + onPointerDown={action(() => { + this.openPresTransitions = !this.openPresTransitions; + })} + style={{ + color: SettingsManager.userColor, + backgroundColor: this.openPresTransitions ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor, + }}> + <FontAwesomeIcon style={{ alignSelf: 'center' }} icon={'rocket'} /> Transitions + <div className="propertiesView-presentationTrails-title-icon"> + <FontAwesomeIcon icon={this.openPresTransitions ? 'caret-down' : 'caret-right'} size="lg" /> </div> - {this.openPresTransitions ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.transitionDropdown}</div> : null} </div> - )} - {!selectedItem ? null : ( - <div className="propertiesView-presentationTrails"> - <div - className="propertiesView-presentationTrails-title" - onPointerDown={action(() => (this.openPresVisibilityAndDuration = !this.openPresVisibilityAndDuration))} - style={{ - color: SettingsManager.userColor, - backgroundColor: this.openPresVisibilityAndDuration ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor, - }}> - <FontAwesomeIcon style={{ alignSelf: 'center' }} icon={'rocket'} /> Visibility - <div className="propertiesView-presentationTrails-title-icon"> - <FontAwesomeIcon icon={this.openPresVisibilityAndDuration ? 'caret-down' : 'caret-right'} size="lg" /> - </div> + {this.openPresTransitions ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.transitionDropdown}</div> : null} + </div> + )} + {!selectedItem ? null : ( + <div className="propertiesView-presentationTrails"> + <div + className="propertiesView-presentationTrails-title" + onPointerDown={action(() => { + this.openPresVisibilityAndDuration = !this.openPresVisibilityAndDuration; + })} + style={{ + color: SettingsManager.userColor, + backgroundColor: this.openPresVisibilityAndDuration ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor, + }}> + <FontAwesomeIcon style={{ alignSelf: 'center' }} icon="rocket" /> Visibility + <div className="propertiesView-presentationTrails-title-icon"> + <FontAwesomeIcon icon={this.openPresVisibilityAndDuration ? 'caret-down' : 'caret-right'} size="lg" /> </div> - {this.openPresVisibilityAndDuration ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.visibilityDurationDropdown}</div> : null} </div> - )} - {!selectedItem ? null : ( - <div className="propertiesView-presentationTrails"> - <div - className="propertiesView-presentationTrails-title" - onPointerDown={action(() => (this.openPresProgressivize = !this.openPresProgressivize))} - style={{ - color: SettingsManager.userColor, - backgroundColor: this.openPresProgressivize ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor, - }}> - <FontAwesomeIcon style={{ alignSelf: 'center' }} icon={'rocket'} /> Progressivize - <div className="propertiesView-presentationTrails-title-icon"> - <FontAwesomeIcon icon={this.openPresProgressivize ? 'caret-down' : 'caret-right'} size="lg" /> - </div> + {this.openPresVisibilityAndDuration ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.visibilityDurationDropdown}</div> : null} + </div> + )} + {!selectedItem ? null : ( + <div className="propertiesView-presentationTrails"> + <div + className="propertiesView-presentationTrails-title" + onPointerDown={action(() => { + this.openPresProgressivize = !this.openPresProgressivize; + })} + style={{ + color: SettingsManager.userColor, + backgroundColor: this.openPresProgressivize ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor, + }}> + <FontAwesomeIcon style={{ alignSelf: 'center' }} icon="rocket" /> Progressivize + <div className="propertiesView-presentationTrails-title-icon"> + <FontAwesomeIcon icon={this.openPresProgressivize ? 'caret-down' : 'caret-right'} size="lg" /> </div> - {this.openPresProgressivize ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.progressivizeDropdown}</div> : null} </div> - )} - {!selectedItem || (type !== DocumentType.VID && type !== DocumentType.AUDIO) ? null : ( - <div className="propertiesView-presentationTrails"> - <div - className="propertiesView-presentationTrails-title" - onPointerDown={action(() => (this.openSlideOptions = !this.openSlideOptions))} - style={{ - color: SettingsManager.userColor, - backgroundColor: this.openSlideOptions ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor, - }}> - <FontAwesomeIcon style={{ alignSelf: 'center' }} icon={type === DocumentType.AUDIO ? 'file-audio' : 'file-video'} /> {type === DocumentType.AUDIO ? 'Audio Options' : 'Video Options'} - <div className="propertiesView-presentationTrails-title-icon"> - <FontAwesomeIcon icon={this.openSlideOptions ? 'caret-down' : 'caret-right'} size="lg" /> - </div> + {this.openPresProgressivize ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.progressivizeDropdown}</div> : null} + </div> + )} + {!selectedItem || (type !== DocumentType.VID && type !== DocumentType.AUDIO) ? null : ( + <div className="propertiesView-presentationTrails"> + <div + className="propertiesView-presentationTrails-title" + onPointerDown={action(() => { + this.openSlideOptions = !this.openSlideOptions; + })} + style={{ + color: SettingsManager.userColor, + backgroundColor: this.openSlideOptions ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor, + }}> + <FontAwesomeIcon style={{ alignSelf: 'center' }} icon={type === DocumentType.AUDIO ? 'file-audio' : 'file-video'} /> {type === DocumentType.AUDIO ? 'Audio Options' : 'Video Options'} + <div className="propertiesView-presentationTrails-title-icon"> + <FontAwesomeIcon icon={this.openSlideOptions ? 'caret-down' : 'caret-right'} size="lg" /> </div> - {this.openSlideOptions ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.mediaOptionsDropdown}</div> : null} </div> - )} - </div> - ); - } + {this.openSlideOptions ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.mediaOptionsDropdown}</div> : null} + </div> + )} + </div> + ); } + return null; } } diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx index 623201ed1..365980f33 100644 --- a/src/client/views/ScriptBox.tsx +++ b/src/client/views/ScriptBox.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import { Doc, Opt } from '../../fields/Doc'; import { ScriptField } from '../../fields/ScriptField'; import { ScriptCast } from '../../fields/Types'; -import { emptyFunction } from '../../Utils'; +import { emptyFunction } from '../../ClientUtils'; import { DragManager } from '../util/DragManager'; import { CompileScript } from '../util/Scripting'; import { EditableView } from './EditableView'; diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx index 3ad3c92da..bcd82887c 100644 --- a/src/client/views/SidebarAnnos.tsx +++ b/src/client/views/SidebarAnnos.tsx @@ -1,8 +1,10 @@ import { computed, makeObservable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { emptyFunction, returnAll, returnFalse, returnOne, returnZero } from '../../Utils'; -import { Doc, DocListCast, Field, FieldResult, StrListCast } from '../../fields/Doc'; +import { ClientUtils, returnAll, returnFalse, returnOne, returnZero } from '../../ClientUtils'; +import { emptyFunction } from '../../Utils'; +import { Doc, DocListCast, Field, FieldType, FieldResult, StrListCast } from '../../fields/Doc'; +import { DocData } from '../../fields/DocSymbols'; import { Id } from '../../fields/FieldSymbols'; import { List } from '../../fields/List'; import { RichTextField } from '../../fields/RichTextField'; @@ -18,7 +20,6 @@ import { StyleProp } from './StyleProvider'; import { CollectionStackingView } from './collections/CollectionStackingView'; import { FieldViewProps } from './nodes/FieldView'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; -import { DocData } from '../../fields/DocSymbols'; interface ExtraProps { fieldKey: string; @@ -44,7 +45,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr _stackRef = React.createRef<CollectionStackingView>(); @computed get allMetadata() { - const keys = new Map<string, FieldResult<Field>>(); + const keys = new Map<string, FieldResult<FieldType>>(); DocListCast(this._props.Document[this.sidebarKey]).forEach(doc => SearchUtil.documentKeys(doc) .filter(key => key[0] && key[0] !== '_' && key[0] === key[0].toUpperCase()) @@ -95,7 +96,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr return { type: 'dashField', attrs: { fieldKey: key, docId: '', hideKey: false, hideValue: false, editable: true }, - marks: [{ type: 'pFontSize', attrs: { fontSize: '12px' } }, { type: 'strong' }, { type: 'user_mark', attrs: { userid: Doc.CurrentUserEmail, modified: 0 } }], + marks: [{ type: 'pFontSize', attrs: { fontSize: '12px' } }, { type: 'strong' }, { type: 'user_mark', attrs: { userid: ClientUtils.CurrentUserEmail, modified: 0 } }], }; }); @@ -196,7 +197,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr </div> ); }; - const renderMeta = (tag: string, dflt: FieldResult<Field>) => { + const renderMeta = (tag: string, dflt: FieldResult<FieldType>) => { const active = this.childFilters().includes(`${tag}${Doc.FilterSep}${Doc.FilterAny}${Doc.FilterSep}exists`); return ( <div key={tag} className={`sidebarAnnos-filterTag${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this._props.Document, tag, Doc.FilterAny, 'exists', true, undefined, e.shiftKey)}> diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index dcec2fe3d..15e309ca0 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -7,10 +7,10 @@ import { extname } from 'path'; import * as React from 'react'; import { BsArrowDown, BsArrowDownUp, BsArrowUp } from 'react-icons/bs'; import { FaFilter } from 'react-icons/fa'; +import { ClientUtils, DashColor, lightOrDark } from '../../ClientUtils'; import { Doc, Opt, StrListCast } from '../../fields/Doc'; import { DocViews } from '../../fields/DocSymbols'; import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from '../../fields/Types'; -import { DashColor, lightOrDark, Utils } from '../../Utils'; import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; import { DocFocusOrOpen, DocumentManager } from '../util/DocumentManager'; import { IsFollowLinkScript } from '../util/LinkFollower'; @@ -26,6 +26,7 @@ import { FieldViewProps } from './nodes/FieldView'; import { KeyValueBox } from './nodes/KeyValueBox'; import { PropertiesView } from './PropertiesView'; import './StyleProvider.scss'; +import { ScriptField } from '../../fields/ScriptField'; export enum StyleProp { TreeViewIcon = 'treeView_Icon', @@ -70,6 +71,20 @@ function togglePaintView(e: React.MouseEvent, doc: Opt<Doc>, props: Opt<FieldVie ScriptCast(doc?.onPaint)?.script.run(scriptProps); } +export function styleFromLayoutString(doc: Doc, props: FieldViewProps, scale: number) { + const style: { [key: string]: any } = {}; + const divKeys = ['width', 'height', 'fontSize', 'transform', 'left', 'backgroundColor', 'left', 'right', 'top', 'bottom', 'pointerEvents', 'position']; + const replacer = (match: any, expr: string, offset: any, string: any) => { + // bcz: this executes a script to convert a property expression string: { script } into a value + return ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: 'number' })?.script.run({ this: doc, self: doc, scale }).result?.toString() ?? ''; + }; + divKeys.map((prop: string) => { + const p = (props as any)[prop]; + typeof p === 'string' && (style[prop] = p?.replace(/{([^.'][^}']+)}/g, replacer)); + }); + return style; +} + export function wavyBorderPath(pw: number, ph: number, inset: number = 0.05) { return `M ${pw * 0.5} ${ph * inset} C ${pw * 0.6} ${ph * inset} ${pw * (1 - 2 * inset)} 0 ${pw * (1 - inset)} ${ph * inset} C ${pw} ${ph * (2 * inset)} ${pw * (1 - inset)} ${ph * 0.25} ${pw * (1 - inset)} ${ph * 0.3} C ${ pw * (1 - inset) @@ -156,7 +171,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps & doc._layout_showTitle, props?.layout_showTitle?.() || (!Doc.IsSystem(doc) && [DocumentType.COL, DocumentType.FUNCPLOT, DocumentType.LABEL, DocumentType.RTF, DocumentType.IMG, DocumentType.VID].includes(doc.type as any) - ? doc.author === Doc.CurrentUserEmail + ? doc.author === ClientUtils.CurrentUserEmail ? StrCast(Doc.UserDoc().layout_showTitle) : remoteDocHeader : '') @@ -300,7 +315,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps & const showFilterIcon = StrListCast(doc?._childFilters).length || StrListCast(doc?._childFiltersByRanges).length ? 'green' // #18c718bd' //'hasFilter' - : props?.childFilters?.().filter(f => Utils.IsRecursiveFilter(f) && f !== Utils.noDragDocsFilter).length || props?.childFiltersByRanges().length + : props?.childFilters?.().filter(f => ClientUtils.IsRecursiveFilter(f) && f !== ClientUtils.noDragDocsFilter).length || props?.childFiltersByRanges().length ? 'orange' //'inheritsFilter' : undefined; return !showFilterIcon ? null : ( @@ -308,7 +323,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps & <Dropdown type={Type.TERT} dropdownType={DropdownType.CLICK} - fillWidth + fillWidth iconProvider={(active:boolean) => <div className='styleProvider-filterShift'><FaFilter/></div>} closeOnSelect={true} setSelectedVal={ diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index 5df0bea1a..1d02568dd 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -1,12 +1,13 @@ -import { computed, observable, ObservableSet, runInAction } from 'mobx'; +import { computed, ObservableSet, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; +import { returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../ClientUtils'; import { Doc, DocListCast } from '../../fields/Doc'; import { DocData } from '../../fields/DocSymbols'; import { ScriptField } from '../../fields/ScriptField'; import { Cast, DocCast, StrCast } from '../../fields/Types'; import { TraceMobx } from '../../fields/util'; -import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../Utils'; +import { emptyFunction } from '../../Utils'; import { Docs, DocUtils } from '../documents/Documents'; import { ScriptingGlobals } from '../util/ScriptingGlobals'; import { Transform } from '../util/Transform'; @@ -16,21 +17,6 @@ import { DefaultStyleProvider } from './StyleProvider'; import './TemplateMenu.scss'; @observer -class TemplateToggle extends React.Component<{ template: string; checked: boolean; toggle: (event: React.ChangeEvent<HTMLInputElement>, template: string) => void }> { - render() { - if (this.props.template) { - return ( - <li className="templateToggle"> - <input type="checkbox" checked={this.props.checked} onChange={event => this.props.toggle(event, this.props.template)} /> - {this.props.template} - </li> - ); - } else { - return null; - } - } -} -@observer class OtherToggle extends React.Component<{ checked: boolean; name: string; toggle: (event: React.ChangeEvent<HTMLInputElement>) => void }> { render() { return ( @@ -50,12 +36,25 @@ export interface TemplateMenuProps { export class TemplateMenu extends React.Component<TemplateMenuProps> { _addedKeys = new ObservableSet(); _customRef = React.createRef<HTMLInputElement>(); - @observable private _hidden: boolean = true; + + componentDidMount() { + !this._addedKeys && (this._addedKeys = new ObservableSet()); + [...Array.from(Object.keys(this.props.docViews[0].Document[DocData])), ...Array.from(Object.keys(this.props.docViews[0].Document))] + .filter(key => key.startsWith('layout_') && ( + StrCast(this.props.docViews[0].Document[key]).startsWith("<") || + DocCast(this.props.docViews[0].Document[key])?.isTemplateDoc + )) + .forEach(key => runInAction(() => this._addedKeys.add(key.replace('layout_', '')))); // prettier-ignore + } + @computed get scriptField() { + const script = ScriptField.MakeScript('docs.map(d => switchView(d, this))', { this: Doc.name }, { docs: this.props.docViews.map(dv => dv.Document) as any }); + return script ? () => script : undefined; + } toggleLayout = (e: React.ChangeEvent<HTMLInputElement>, layout: string): void => { this.props.docViews.map(dv => dv.switchViews(e.target.checked, layout, undefined, true)); }; - toggleDefault = (e: React.ChangeEvent<HTMLInputElement>): void => { + toggleDefault = (): void => { this.props.docViews.map(dv => dv.switchViews(false, 'layout')); }; @@ -65,21 +64,8 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> { runInAction(() => this._addedKeys.add(this._customRef.current!.value)); } }; - componentDidMount() { - !this._addedKeys && (this._addedKeys = new ObservableSet()); - [...Array.from(Object.keys(this.props.docViews[0].Document[DocData])), ...Array.from(Object.keys(this.props.docViews[0].Document))] - .filter(key => key.startsWith('layout_') && ( - StrCast(this.props.docViews[0].Document[key]).startsWith("<") || - DocCast(this.props.docViews[0].Document[key])?.isTemplateDoc - )) - .map(key => runInAction(() => this._addedKeys.add(key.replace('layout_', '')))); // prettier-ignore - } return100 = () => 300; - @computed get scriptField() { - const script = ScriptField.MakeScript('docs.map(d => switchView(d, this))', { this: Doc.name }, { docs: this.props.docViews.map(dv => dv.Document) as any }); - return script ? () => script : undefined; - } templateIsUsed = (selDoc: Doc, templateDoc: Doc) => { const template = StrCast(templateDoc.dragFactory ? Cast(templateDoc.dragFactory, Doc, null)?.title : templateDoc.title); return StrCast(selDoc.layout_fieldKey) === 'layout_' + template ? 'check' : 'unchecked'; @@ -88,10 +74,11 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> { TraceMobx(); const firstDoc = this.props.docViews[0].Document; const templateName = StrCast(firstDoc.layout_fieldKey, 'layout').replace('layout_', ''); - const noteTypes = DocListCast(Cast(Doc.UserDoc()['template_notes'], Doc, null)?.data); - const addedTypes = DocListCast(Cast(Doc.UserDoc()['template_clickFuncs'], Doc, null)?.data); + const noteTypes = DocListCast(Cast(Doc.UserDoc().template_notes, Doc, null)?.data); + const addedTypes = DocListCast(Cast(Doc.UserDoc().template_clickFuncs, Doc, null)?.data); const templateMenu: Array<JSX.Element> = []; templateMenu.push(<OtherToggle key="default" name={firstDoc.layout instanceof Doc ? StrCast(firstDoc.layout.title) : 'Default'} checked={templateName === 'layout'} toggle={this.toggleDefault} />); + // eslint-disable-next-line no-return-assign addedTypes.concat(noteTypes).map(template => (template.treeView_Checked = this.templateIsUsed(firstDoc, template))); this._addedKeys && Array.from(this._addedKeys) @@ -122,10 +109,10 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> { addDocTab={returnFalse} PanelWidth={this.return100} PanelHeight={this.return100} - treeViewHideHeaderFields={true} - treeViewHideTitle={true} - dontRegisterView={true} - fieldKey={'data'} + treeViewHideHeaderFields + treeViewHideTitle + dontRegisterView + fieldKey="data" moveDocument={returnFalse} removeDocument={returnFalse} addDocument={returnFalse} @@ -135,10 +122,9 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> { } } -ScriptingGlobals.add(function switchView(doc: Doc, template: Doc | undefined) { - if (template?.dragFactory) { - template = Cast(template.dragFactory, Doc, null); - } +// eslint-disable-next-line prefer-arrow-callback +ScriptingGlobals.add(function switchView(doc: Doc, templateIn: Doc | undefined) { + const template = templateIn?.dragFactory ? Cast(templateIn.dragFactory, Doc, null) : templateIn; const templateTitle = StrCast(template?.title); return templateTitle && DocUtils.makeCustomViewClicked(doc, Docs.Create.FreeformDocument, templateTitle, template); }); diff --git a/src/client/views/Touchable.tsx b/src/client/views/Touchable.tsx deleted file mode 100644 index 436cb688f..000000000 --- a/src/client/views/Touchable.tsx +++ /dev/null @@ -1,213 +0,0 @@ -import * as React from 'react'; -import { action } from 'mobx'; -import { InteractionUtils } from '../util/InteractionUtils'; - -const HOLD_DURATION = 1000; - -export abstract class Touchable<T = {}> extends React.Component<React.PropsWithChildren<T>> { - //private holdTimer: NodeJS.Timeout | undefined; - private moveDisposer?: InteractionUtils.MultiTouchEventDisposer; - private endDisposer?: InteractionUtils.MultiTouchEventDisposer; - private holdMoveDisposer?: InteractionUtils.MultiTouchEventDisposer; - private holdEndDisposer?: InteractionUtils.MultiTouchEventDisposer; - - protected abstract _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; - protected _touchDrag: boolean = false; - protected prevPoints: Map<number, React.Touch> = new Map<number, React.Touch>(); - - public FirstX: number = 0; - public FirstY: number = 0; - public SecondX: number = 0; - public SecondY: number = 0; - - /** - * When a touch even starts, we keep track of each touch that is associated with that event - */ - @action - protected onTouchStart = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): void => { - const actualPts: React.Touch[] = []; - const te = me.touchEvent; - // loop through all touches on screen - for (const pt of me.touches) { - actualPts.push(pt); - if (this.prevPoints.has(pt.identifier)) { - this.prevPoints.set(pt.identifier, pt); - } - // only add the ones that are targeted on "this" element, but with the identifier that the screen touch gives - for (const tPt of me.changedTouches) { - if (pt.clientX === tPt.clientX && pt.clientY === tPt.clientY) { - // pen is also a touch, but with a radius of 0.5 (at least with the surface pens) - // and this seems to be the only way of differentiating pen and touch on touch events - if ((pt as any).radiusX > 1 && (pt as any).radiusY > 1) { - this.prevPoints.set(pt.identifier, pt); - } - } - } - } - - const ptsToDelete: number[] = []; - this.prevPoints.forEach(pt => { - if (!actualPts.includes(pt)) { - ptsToDelete.push(pt.identifier); - } - }); - - ptsToDelete.forEach(pt => this.prevPoints.delete(pt)); - - if (this.prevPoints.size) { - switch (this.prevPoints.size) { - case 1: - this.handle1PointerDown(te, me); - te.persist(); - // -- code for radial menu -- - // if (this.holdTimer) { - // clearTimeout(this.holdTimer) - // this.holdTimer = undefined; - // } - break; - case 2: - this.handle2PointersDown(te, me); - break; - } - } - }; - - /** - * Handle touch move event - */ - @action - protected onTouch = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => { - const te = me.touchEvent; - const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true); - - // if we're not actually moving a lot, don't consider it as dragging yet - if (!InteractionUtils.IsDragging(this.prevPoints, myTouches, 5) && !this._touchDrag) return; - this._touchDrag = true; - switch (myTouches.length) { - case 1: - this.handle1PointerMove(te, me); - break; - case 2: - this.handle2PointersMove(te, me); - break; - } - - for (const pt of me.touches) { - if (pt && this.prevPoints.has(pt.identifier)) { - this.prevPoints.set(pt.identifier, pt); - } - } - }; - - @action - protected onTouchEnd = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => { - // remove all the touches associated with the event - const te = me.touchEvent; - for (const pt of me.changedTouches) { - if (pt) { - if (this.prevPoints.has(pt.identifier)) { - this.prevPoints.delete(pt.identifier); - } - } - } - this._touchDrag = false; - te.stopPropagation(); - - // if (e.targetTouches.length === 0) { - // this.prevPoints.clear(); - // } - - if (this.prevPoints.size === 0) { - this.cleanUpInteractions(); - } - e.stopPropagation(); - }; - - cleanUpInteractions = (): void => { - this.removeMoveListeners(); - this.removeEndListeners(); - }; - - handle1PointerMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>): any => { - e.stopPropagation(); - e.preventDefault(); - }; - - handle2PointersMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>): any => { - e.stopPropagation(); - e.preventDefault(); - }; - - handle1PointerDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => { - this.removeMoveListeners(); - this.addMoveListeners(); - this.removeEndListeners(); - this.addEndListeners(); - }; - - handle2PointersDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => { - this.removeMoveListeners(); - this.addMoveListeners(); - this.removeEndListeners(); - this.addEndListeners(); - }; - - handle1PointerHoldStart = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => { - e.stopPropagation(); - me.touchEvent.stopPropagation(); - this.removeMoveListeners(); - this.removeEndListeners(); - this.removeHoldMoveListeners(); - this.removeHoldEndListeners(); - this.addHoldMoveListeners(); - this.addHoldEndListeners(); - }; - - addMoveListeners = () => { - const handler = (e: Event) => this.onTouch(e, (e as CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>).detail); - document.addEventListener('dashOnTouchMove', handler); - this.moveDisposer = () => document.removeEventListener('dashOnTouchMove', handler); - }; - addEndListeners = () => { - const handler = (e: Event) => this.onTouchEnd(e, (e as CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>).detail); - document.addEventListener('dashOnTouchEnd', handler); - this.endDisposer = () => document.removeEventListener('dashOnTouchEnd', handler); - }; - - addHoldMoveListeners = () => { - const handler = (e: Event) => this.handle1PointerHoldMove(e, (e as CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>).detail); - document.addEventListener('dashOnTouchHoldMove', handler); - this.holdMoveDisposer = () => document.removeEventListener('dashOnTouchHoldMove', handler); - }; - - addHoldEndListeners = () => { - const handler = (e: Event) => this.handle1PointerHoldEnd(e, (e as CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>).detail); - document.addEventListener('dashOnTouchHoldEnd', handler); - this.holdEndDisposer = () => document.removeEventListener('dashOnTouchHoldEnd', handler); - }; - - removeMoveListeners = () => this.moveDisposer?.(); - removeEndListeners = () => this.endDisposer?.(); - removeHoldMoveListeners = () => this.holdMoveDisposer?.(); - removeHoldEndListeners = () => this.holdEndDisposer?.(); - - handle1PointerHoldMove = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => { - // e.stopPropagation(); - // me.touchEvent.stopPropagation(); - }; - - handle1PointerHoldEnd = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => { - e.stopPropagation(); - me.touchEvent.stopPropagation(); - this.removeHoldMoveListeners(); - this.removeHoldEndListeners(); - - me.touchEvent.stopPropagation(); - me.touchEvent.preventDefault(); - }; - - handleHandDown = (e: React.TouchEvent) => { - // e.stopPropagation(); - // e.preventDefault(); - }; -} diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx index cc4da1694..c3e513154 100644 --- a/src/client/views/animationtimeline/Timeline.tsx +++ b/src/client/views/animationtimeline/Timeline.tsx @@ -4,7 +4,8 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { Utils, emptyFunction, setupMoveUpEvents } from '../../../Utils'; +import { setupMoveUpEvents } from '../../../ClientUtils'; +import { Utils, emptyFunction } from '../../../Utils'; import { Doc, DocListCast } from '../../../fields/Doc'; import { BoolCast, NumCast, StrCast } from '../../../fields/Types'; import { DocumentType } from '../../documents/DocumentTypes'; diff --git a/src/client/views/animationtimeline/TimelineMenu.tsx b/src/client/views/animationtimeline/TimelineMenu.tsx index 97a571dc4..20da21cb9 100644 --- a/src/client/views/animationtimeline/TimelineMenu.tsx +++ b/src/client/views/animationtimeline/TimelineMenu.tsx @@ -1,3 +1,4 @@ +/* eslint-disable jsx-a11y/click-events-have-key-events */ import { IconLookup } from '@fortawesome/fontawesome-svg-core'; import { faChartLine, faClipboard } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; @@ -9,6 +10,7 @@ import './TimelineMenu.scss'; @observer export class TimelineMenu extends React.Component { + // eslint-disable-next-line no-use-before-define public static Instance: TimelineMenu; @observable private _opacity = 0; @@ -67,16 +69,19 @@ export class TimelineMenu extends React.Component { this._currentMenu.push( <div key={Utils.GenerateGuid()} className="timeline-menu-item"> <FontAwesomeIcon icon={faChartLine as IconLookup} size="lg" /> - <p - className="timeline-menu-desc" - onClick={e => { - e.preventDefault(); - e.stopPropagation(); - event(e); - this.closeMenu(); - }}> - {title} - </p> + { + // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions + <p + className="timeline-menu-desc" + onClick={e => { + e.preventDefault(); + e.stopPropagation(); + event(e); + this.closeMenu(); + }}> + {title} + </p> + } </div> ); } 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<number> => { 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 ( - <DocumentView - {...this._props} - Document={childPair.layout} - TemplateDataDocument={childPair.data} - //suppressSetHeight={true} - NativeWidth={returnZero} - NativeHeight={returnZero} - layout_fitWidth={undefined} - onDoubleClickScript={this.onChildDoubleClick} - renderDepth={this._props.renderDepth + 1} - LayoutTemplate={this._props.childLayoutTemplate} - LayoutTemplateString={this._props.childLayoutString} - focus={this.focus} - ScreenToLocalTransform={this.childScreenToLocal} - isContentActive={this.isChildContentActive} - isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive} - PanelWidth={this.panelWidth} - PanelHeight={this.panelHeight} - /> - ); - }; - - return this.carouselItems.map((childPair, index) => { - return ( - <div key={childPair.layout[Id]} className={`collectionCarousel3DView-item${index === currentIndex ? '-active' : ''} ${index}`} style={{ width: this.panelWidth() }}> - {displayDoc(childPair)} - </div> - ); - }); + const displayDoc = (childPair: { layout: Doc; data: Doc }) => ( + <DocumentView + // eslint-disable-next-line react/jsx-props-no-spreading + {...this._props} + Document={childPair.layout} + TemplateDataDocument={childPair.data} + // suppressSetHeight={true} + NativeWidth={returnZero} + NativeHeight={returnZero} + layout_fitWidth={undefined} + onDoubleClickScript={this.onChildDoubleClick} + renderDepth={this._props.renderDepth + 1} + LayoutTemplate={this._props.childLayoutTemplate} + LayoutTemplateString={this._props.childLayoutString} + focus={this.focus} + ScreenToLocalTransform={this.childScreenToLocal} + isContentActive={this.isChildContentActive} + isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive} + PanelWidth={this.panelWidth} + PanelHeight={this.panelHeight} + /> + ); + + return this.carouselItems.map((childPair, index) => ( + <div key={childPair.layout[Id]} className={`collectionCarousel3DView-item${index === currentIndex ? '-active' : ''} ${index}`} style={{ width: this.panelWidth() }}> + {displayDoc(childPair)} + </div> + )); } 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 ( <div className="arrow-buttons"> - <div title="click to go back" key="back" className="carousel3DView-back" onClick={e => this.onArrowClick(-1)} /> - <div title="click to advance" key="fwd" className="carousel3DView-fwd" onClick={e => this.onArrowClick(1)} /> + <div title="click to go back" key="back" className="carousel3DView-back" onClick={() => this.onArrowClick(-1)} /> + <div title="click to advance" key="fwd" className="carousel3DView-fwd" onClick={() => this.onArrowClick(1)} /> {/* {this.autoScrollButton} */} </div> ); @@ -149,17 +150,25 @@ export class CollectionCarousel3DView extends CollectionSubView() { return ( <> <div className={`carousel3DView-back-scroll${whichButton === 'back' ? '' : '-hidden'}`} style={{ background: `${StrCast(this.Document.backgroundColor)}` }} onClick={() => this.toggleAutoScroll(-1)}> - {this.layoutDoc.autoScrollOn ? <FontAwesomeIcon icon={'pause'} size={'1x'} /> : <FontAwesomeIcon icon={'angle-double-left'} size={'1x'} />} + {this.layoutDoc.autoScrollOn ? <FontAwesomeIcon icon="pause" size="1x" /> : <FontAwesomeIcon icon="angle-double-left" size="1x" />} </div> <div className={`carousel3DView-fwd-scroll${whichButton === 'fwd' ? '' : '-hidden'}`} style={{ background: `${StrCast(this.Document.backgroundColor)}` }} onClick={() => this.toggleAutoScroll(1)}> - {this.layoutDoc.autoScrollOn ? <FontAwesomeIcon icon={'pause'} size={'1x'} /> : <FontAwesomeIcon icon={'angle-double-right'} size={'1x'} />} + {this.layoutDoc.autoScrollOn ? <FontAwesomeIcon icon="pause" size="1x" /> : <FontAwesomeIcon icon="angle-double-right" size="1x" />} </div> </> ); } @computed get dots() { - return this.carouselItems.map((_child, index) => <div key={Utils.GenerateGuid()} className={`dot${index === NumCast(this.layoutDoc._carousel_index) ? '-active' : ''}`} onClick={() => (this.layoutDoc._carousel_index = index)} />); + return this.carouselItems.map((_child, index) => ( + <div + key={Utils.GenerateGuid()} + className={`dot${index === NumCast(this.layoutDoc._carousel_index) ? '-active' : ''}`} + onClick={() => { + 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() { <div> {href ? ( <img + alt="thumbnail of nested dashboard" style={{ background: 'white', top: 0, position: 'absolute' }} src={href} // + '?d=' + (new Date()).getTime()} width={this._props.PanelWidth()} height={this._props.PanelHeight()} /> ) : ( - <p>nested dashboards has no thumbnail</p> + <p>nested dashboard has no thumbnail</p> )} </div> ) : ( @@ -625,6 +635,7 @@ export class CollectionDockingView extends CollectionSubView() { } ScriptingGlobals.add( + // eslint-disable-next-line prefer-arrow-callback function openInLightbox(doc: any) { LightboxView.Instance.AddDocTab(doc, OpenWhere.lightbox); }, @@ -632,26 +643,37 @@ ScriptingGlobals.add( '(doc: any)' ); ScriptingGlobals.add( + // eslint-disable-next-line prefer-arrow-callback function openDoc(doc: any, where: OpenWhere) { switch (where) { case OpenWhere.addRight: return CollectionDockingView.AddSplit(doc, OpenWhereMod.right); case OpenWhere.overlay: + default: // prettier-ignore switch (doc) { case '<ScriptingRepl />': return OverlayView.Instance.addWindow(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200, title: 'Scripting REPL' }); case "<UndoStack />": return OverlayView.Instance.addWindow(<UndoStack />, { 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<CollectionMenuProps> { 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<CollectionMenuProps> { const propTitle = SettingsManager.Instance.propertiesWidth > 0 ? 'Close Properties' : 'Open Properties'; const hardCodedButtons = ( - <div className={`hardCodedButtons`}> + <div className="hardCodedButtons"> <Toggle toggleType={ToggleType.BUTTON} type={Type.PRIM} @@ -145,7 +147,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> { </div> ); - //dash col linear view buttons + // dash col linear view buttons const contMenuButtons = ( <div className="collectionMenu-container" @@ -172,7 +174,7 @@ const stopPropagation = (e: React.SyntheticEvent) => e.stopPropagation(); @observer export class CollectionViewBaseChrome extends React.Component<CollectionViewMenuProps> { - //(!)?\(\(\(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<CollectionViewMenu params: ['target', 'source'], title: 'child click view', script: 'this.target.childClickedOpenTemplateView = getDocTemplate(this.source?.[0])', - immediate: undoBatch((source: Doc[]) => 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<Doc>(source))), + immediate: undoBatch((source: Doc[]) => { + this.target[DocData].data = new List<Doc>(source); + }), initialize: emptyFunction, }; _onClickCommand = { @@ -224,14 +230,14 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu getProto(this.proxy[0]).target = this.target.target; getProto(this.proxy[0]).source = copyField(this.target.source); }}`, - immediate: undoBatch((source: Doc[]) => {}), + 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<CollectionViewMenu params: ['target'], title: 'fit content', script: 'this.target._freeform_fitContentsToBox = !this.target._freeform_fitContentsToBox;', - immediate: undoBatch((source: Doc[]) => (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<CollectionViewMenu title: 'save filter', script: `this.target._childFilters = compareLists(this['target-childFilters'],this.target._childFilters) ? undefined : copyField(this['target-childFilters']); this.target._searchFilterDocs = compareLists(this['target-searchFilterDocs'],this.target._searchFilterDocs) ? undefined: copyField(this['target-searchFilterDocs']);`, - immediate: undoBatch((source: Doc[]) => { + immediate: undoBatch(() => { this.target._childFilters = undefined; this.target._searchFilterDocs = undefined; }), @@ -332,12 +342,10 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu @undoBatch viewChanged = (e: React.ChangeEvent) => { 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<Partial<collection // TODO: plj - these are the children children = (docs: Doc[]) => { - //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<Partial<collection }; // is sections that all collections inherit? I think this is how we show the masonry/columns - //TODO: this seems important + // TODO: this seems important get Sections() { // appears that pivot field IS actually for sorting if (!this.pivotField || this.colHeaderData instanceof Promise) return new Map<SchemaHeaderField, Doc[]>(); if (this.colHeaderData === undefined) { - setTimeout(() => (this.dataDoc['_' + this.fieldKey + '_columnHeaders'] = new List<SchemaHeaderField>()), 0); + setTimeout(() => { + this.dataDoc['_' + this.fieldKey + '_columnHeaders'] = new List<SchemaHeaderField>(); + }); return new Map<SchemaHeaderField, Doc[]>(); } const colHeaderData = Array.from(this.colHeaderData); const fields = new Map<SchemaHeaderField, Doc[]>(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<Partial<collection if (this.layoutDoc._columnsHideIfEmpty) { Array.from(fields.keys()) .filter(key => !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<Partial<collection // reset section headers when a new filter is inputted this._disposers.pivotField = reaction( () => 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<Partial<collection isAnyChildContentActive = () => 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<Partial<collection const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName('documentView-node')).find((node: any) => 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<Partial<collection onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => { 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<Doc>([]) : 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<Partial<collection getDocTransform(doc: Doc) { const dref = this.docRefs.get(doc); this._scroll; // must be referenced for document decorations to update when the text box container is scrolled - const { translateX, translateY } = Utils.GetScreenTransform(dref?.ContentDiv); + const { translateX, translateY } = ClientUtils.GetScreenTransform(dref?.ContentDiv); // 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); } @@ -399,7 +410,9 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection // This following three functions must be from the view Mehek showed columnDividerDown = (e: React.PointerEvent) => { - 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<Partial<collection onPointerDown={this.columnDividerDown} ref={this._draggerRef} style={{ cursor: this._cursor, color: SettingsManager.userColor, left: `${this.columnWidth + this.xMargin}px`, top: `${Math.max(0, this.yMargin - 9)}px` }}> - <FontAwesomeIcon icon={'arrows-alt-h'} /> + <FontAwesomeIcon icon="arrows-alt-h" /> </div> ); } @@ -439,7 +452,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection let dropAfter = 0; if (de.complete.docDragData) { // going to re-add the docs to the _docXFs based on position of where we just dropped - this._docXfs.map((cd, i) => { + this._docXfs.forEach((cd, i) => { const pos = cd .stackedDocTransform() .inverse() @@ -496,7 +509,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection onExternalDrop = async (e: React.DragEvent): Promise<void> => { 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<Partial<collection // what a section looks like if we're in stacking view sectionStacking = (heading: SchemaHeaderField | undefined, docList: Doc[]) => { 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<Partial<collection // what a section looks like if we're in masonry. Shouldn't actually need to use this. sectionMasonry = (heading: SchemaHeaderField | undefined, docList: Doc[], first: boolean) => { 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<Partial<collection const cm = ContextMenu.Instance; const options = cm.findByDescription('Options...'); const optionItems: ContextMenuProps[] = options && 'subitems' in options ? options.subitems : []; - optionItems.push({ description: `${this.layoutDoc._columnsFill ? 'Variable Size' : 'Autosize'} Column`, event: () => (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<Partial<collection className={this.isStackingView ? 'collectionStackingView' : 'collectionMasonryView'} ref={ele => { 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<Partial<collection background: this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor), pointerEvents: (this._props.pointerEvents?.() as any) ?? this.backgroundEvents, }} - onScroll={action(e => (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<HTMLDivElement>) => 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 ( - <div className="collectionStackingView-optionPicker"> - <div className="optionOptions"> - <div className={'optionPicker' + (true ? ' active' : '')} onClick={action(() => {})}> - Add options here - </div> + renderMenu = () => ( + <div className="collectionStackingView-optionPicker"> + <div className="optionOptions"> + <div className={'optionPicker' + (true ? ' active' : '')} onClick={action(() => {})}> + Add options here </div> </div> - ); - }; + </div> + ); @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 ? ( <div key={heading} @@ -324,14 +333,19 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< onPointerDown={this.headerDown} title={evContents === noValueHeader ? `Documents that don't have a ${key} value will go here. This column cannot be removed.` : ''} style={{ background: evContents !== noValueHeader ? this._color : 'inherit' }}> - <EditableView GetValue={() => evContents} SetValue={this.headingChanged} contents={evContents} oneLine={true} /> + <EditableView GetValue={() => evContents} SetValue={this.headingChanged} contents={evContents} oneLine /> <div className="collectionStackingView-sectionColor" style={{ display: evContents === noValueHeader ? 'none' : undefined }}> - <button className="collectionStackingView-sectionColorButton" onClick={action(e => (this._paletteOn = !this._paletteOn))}> + <button + type="button" + className="collectionStackingView-sectionColorButton" + onClick={action(() => { + this._paletteOn = !this._paletteOn; + })}> <FontAwesomeIcon icon="palette" size="lg" /> </button> {this._paletteOn ? this.renderColorPicker() : null} </div> - <button className="collectionStackingView-sectionDelete" onClick={this.deleteColumn}> + <button type="button" className="collectionStackingView-sectionDelete" onClick={this.deleteColumn}> <FontAwesomeIcon icon="trash" size="lg" /> </button> {/* {evContents === noValueHeader ? null : ( @@ -352,7 +366,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< </div> ) : 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! <div key={`${heading}-add-document`} onKeyDown={e => e.stopPropagation()} @@ -390,7 +404,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< SetValue={this.addNewTextDoc} textCallback={this.typedNote} placeholder={"Type ':' for commands"} - contents={<FontAwesomeIcon icon={'plus'} />} + contents={<FontAwesomeIcon icon="plus" />} menuCallback={this.menuCallback} /> </div> 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<FieldViewProps> { + 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<Doc>) => 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<X>(moreProps?: X) { } }; protected CreateDropTarget(ele: HTMLDivElement) { - //used in schema view + // used in schema view this.createDashEventsTarget(ele); } @@ -83,10 +114,11 @@ export function CollectionSubView<X>(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<X>(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<X>(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<X>(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<X>(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<X>(moreProps?: X) { if (!cursors) { proto.cursors = cursors = new List<CursorField>(); } + // 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<X>(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<X>(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<X>(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<X>(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<X>(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<string>(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<string>(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<X>(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<X>(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<X>(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<FieldViewProps> { - 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<Doc>) => 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<CollectionViewProps>() implements ViewBoxInterface { public static LayoutString(fieldStr: string) { @@ -89,7 +59,9 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr // this a reaction, downstream invalidations only occur when the reaction value actually changes. this.reactionDisposer = reaction( () => (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<CollectionViewPr get collectionViewType(): CollectionViewType | undefined { const viewField = StrCast(this.layoutDoc._type_collection); if (CollectionView._safeMode) { - switch (viewField) { + switch (viewField) { case CollectionViewType.Freeform: - case CollectionViewType.Schema: - return CollectionViewType.Tree; - case CollectionViewType.Invalid: - return CollectionViewType.Freeform; - } + case CollectionViewType.Schema: return CollectionViewType.Tree; + case CollectionViewType.Invalid: return CollectionViewType.Freeform; + default: + } // prettier-ignore } return viewField as any as CollectionViewType; } @@ -117,8 +88,6 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr TraceMobx(); if (type === undefined) return null; switch (type) { - default: - case CollectionViewType.Freeform: return <CollectionFreeFormView key="collview" {...props} />; case CollectionViewType.Schema: return <CollectionSchemaView key="collview" {...props} />; case CollectionViewType.Calendar: return <CollectionCalendarView key="collview" {...props} />; case CollectionViewType.Docking: return <CollectionDockingView key="collview" {...props} />; @@ -134,6 +103,8 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr case CollectionViewType.Masonry: return <CollectionStackingView key="collview" {...props} />; case CollectionViewType.Time: return <CollectionTimeView key="collview" {...props} />; case CollectionViewType.Grid: return <CollectionGridView key="collview" {...props} />; + case CollectionViewType.Freeform: + default: return <CollectionFreeFormView key="collview" {...props} />; } }; @@ -144,9 +115,9 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr { description: 'Freeform', event: () => 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<CollectionViewPr const options = cm.findByDescription('Options...'); const optionItems = options && 'subitems' in options ? options.subitems : []; - !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; + !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<CollectionViewPr onClicks.push({ description: `Edit ${func.name} script`, icon: 'edit', - event: (obj: any) => { + 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<CollectionViewPr onClicks.push({ description: `Set child ${childClick.title}`, icon: 'edit', - event: () => (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<CollectionViewPr bodyPanelWidth = () => 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<TabDocViewProps> { 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<TabDocViewProps> { }; const docIcon = <FontAwesomeIcon onPointerDown={dragBtnDown} icon={iconType} />; - const closeIcon = <FontAwesomeIcon icon={'eye'} />; + const closeIcon = <FontAwesomeIcon icon="eye" />; 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<TabDocViewProps> { 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<TabDocViewProps> { @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<TabDocViewProps> { 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<TabDocViewProps> { 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<TabDocViewProps> { 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<TabMiniThumbProps> { render() { - return <div className="miniThumb" style={{ width: `${this.props.miniWidth()}% `, height: `${this.props.miniHeight()}% `, left: `${this.props.miniLeft()}% `, top: `${this.props.miniTop()}% ` }} />; + const { miniWidth, miniHeight, miniLeft, miniTop } = this.props; + return <div className="miniThumb" style={{ width: `${miniWidth()}%`, height: `${miniHeight()}%`, left: `${miniLeft()}%`, top: `${miniTop()}%` }} />; } } @observer @@ -532,14 +535,10 @@ export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps static miniStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, 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<TabMinimapViewProps case DocumentType.RTF: return 'yellow'; case DocumentType.COL: return undefined; default: return 'gray'; - } - })(doc.type as DocumentType); + } // prettier-ignore + })(); return !background ? undefined : <div style={{ width: NumCast(doc._width), height: NumCast(doc._height), position: 'absolute', display: 'block', background }} />; - } + } + default: return DefaultStyleProvider(doc, props, property); + } // prettier-ignore } }; @@ -639,7 +640,7 @@ export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps render() { return this._props.document.layout !== CollectionView.LayoutString(Doc.LayoutFieldKey(this._props.document)) || this._props.document?._type_collection !== CollectionViewType.Freeform ? null : ( <div className="miniMap-hidden"> - <Popup icon={<FontAwesomeIcon icon="globe-asia" size="lg" />} color={SettingsManager.userVariantColor} type={Type.TERT} onPointerDown={e => e.stopPropagation()} placement="top-end" popup={this.popup} /> + <Popup icon={<FontAwesomeIcon icon="globe-asia" size="lg" />} color={SnappingManager.userVariantColor} type={Type.TERT} onPointerDown={e => e.stopPropagation()} placement="top-end" popup={this.popup} /> </div> ); } 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<TreeViewProps> { + // eslint-disable-next-line no-use-before-define static _editTitleOnLoad: Opt<{ id: string; parent: TreeView | CollectionTreeView | undefined }>; static _openTitleScript: Opt<ScriptField | undefined>; static _openLevelScript: Opt<ScriptField | undefined>; @@ -101,6 +109,9 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { 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<TreeViewProps> { @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<TreeViewProps> { 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<TreeViewProps> { 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<TreeViewProps> { 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<TreeViewProps> { 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<TreeViewProps> { 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<TreeViewProps> { 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<TreeViewProps> { 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<TreeViewProps> { 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<TreeViewProps> { 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<TreeViewProps> { contentElement = ( <EditableView key="editableView" - contents={contents !== undefined ? Field.toString(contents as Field) : 'null'} + contents={contents !== undefined ? Field.toString(contents as FieldType) : 'null'} height={13} fontSize={12} GetValue={() => Field.toKeyValueString(doc, key)} @@ -572,15 +585,15 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { ); } rows.push( - <div style={{ display: 'flex', overflow: 'auto' }} key={'newKeyValue'}> + <div style={{ display: 'flex', overflow: 'auto' }} key="newKeyValue"> <EditableView key="editableView" - contents={'+key=value'} + contents="+key=value" height={13} fontSize={12} GetValue={returnEmptyString} SetValue={input => { - 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<TreeViewProps> { 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<TreeViewProps> { }; 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<TreeViewProps> { 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<TreeViewProps> { </ul> </div> ); - } else if (this.treeViewExpandedView === 'fields') { + } + if (this.treeViewExpandedView === 'fields') { return ( <ul key={this.Document[Id] + this.Document.title} style={{ cursor: 'inherit' }}> <div>{this.expandedField}</div> @@ -891,14 +907,15 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { titleStyleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, 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<TreeViewProps> { {StrCast(doc?.title)} </div> ); + } + default: } return treeView._props.styleProvider?.(doc, props, property); }; @@ -924,7 +943,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { 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<TreeViewProps> { e.stopPropagation?.(); e.preventDefault?.(); return UndoManager.RunInBatch(this.makeTextCollection, 'bullet'); + default: } } return false; @@ -959,22 +979,24 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { const view = this._editTitle ? ( <EditableView key="_editTitle" - oneLine={true} - display={'inline-block'} + oneLine + display="inline-block" editing={this._editTitle} - background={'#7089bb'} + background="#7089bb" contents={StrCast(this.Document.title)} height={12} - sizeToContent={true} + sizeToContent fontSize={12} - isEditingCallback={action(e => (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<TreeViewProps> { <DocumentView key="title" ref={action((r: any) => { - 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<TreeViewProps> { 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<TreeViewProps> { 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<TreeViewProps> { }}> {view} </div> - <div className="treeView-rightButtons" ref={action((r: any) => r && (this.headerEleWidth = r.getBoundingClientRect().width))}> + <div + className="treeView-rightButtons" + ref={action((r: any) => { + r && (this.headerEleWidth = r.getBoundingClientRect().width); + })}> {this.titleButtons} </div> </> ); } - renderBulletHeader = (contents: JSX.Element, editing: boolean) => { - return ( - <> + renderBulletHeader = (contents: JSX.Element, editing: boolean) => ( + <> + <div + className={`treeView-header` + (editing ? '-editing' : '')} + key="titleheader" + ref={this._header} + onClick={this.ignoreEvent} + onPointerDown={e => { + 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}> <div - className={`treeView-header` + (editing ? '-editing' : '')} - key="titleheader" - ref={this._header} - onClick={this.ignoreEvent} - onPointerDown={e => { - 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}> - <div - className="treeView-background" - style={{ - background: SettingsManager.userColor, - }} - /> - {contents} - </div> - {this.renderBorder} - </> - ); - }; - - fitWidthFilter = (doc: Doc) => (doc.type === DocumentType.IMG ? false : undefined); - renderEmbeddedDocument = (asText: boolean, isActive: () => boolean | undefined) => { - return ( - <div style={{ height: this.embeddedPanelHeight(), width: this.embeddedPanelWidth() }}> - <DocumentView - key={this.Document[Id]} - ref={action((r: DocumentView | null) => (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} </div> - ); - }; + {this.renderBorder} + </> + ); + + fitWidthFilter = (doc: Doc) => (doc.type === DocumentType.IMG ? false : undefined); + renderEmbeddedDocument = (asText: boolean, isActive: () => boolean | undefined) => ( + <div style={{ height: this.embeddedPanelHeight(), width: this.embeddedPanelWidth() }}> + <DocumentView + key={this.Document[Id]} + ref={action((r: DocumentView | null) => { + 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} + /> + </div> + ); // 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 ? ( - <div></div> + <div /> ) : ( <> {this.renderBullet} @@ -1147,14 +1171,12 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { } // 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<TreeViewProps> { 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} > <li className="collection-child"> @@ -1209,11 +1231,10 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { 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<TreeViewProps> { 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<TreeViewProps> { 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<TreeViewProps> { 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<Doc, TreeView | undefined>(); + const treeViewRefs = new Map<Doc, TreeView | undefined>(); 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<TreeViewProps> { 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<TreeViewProps> { return ( <TreeView key={child[Id]} - ref={r => 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<TreeViewProps> { 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<TreeViewProps> { 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<Collectio @observable _currState: infoState | undefined = undefined; get currState() { return this._currState; } // prettier-ignore - set currState(val) { runInAction(() => (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<Collectio }; setupStates = () => { - 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<Field>; - let docY: FieldResult<Field>; + let docX: FieldResult<FieldType>; + let docY: FieldResult<FieldType>; const docNewX = () => firstDoc()?.x; const docNewY = () => firstDoc()?.y; @@ -244,12 +245,12 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio // editPresentation: [() => 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<Field>) { +function toLabel(target: FieldResult<FieldType>) { 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<string, PoolData>, pivotDoc export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) { const docMap = new Map<string, PoolData>(); const fieldKey = 'data'; - const pivotColumnGroups = new Map<FieldResult<Field>, PivotColumn>(); + const pivotColumnGroups = new Map<FieldResult<FieldType>, PivotColumn>(); let nonNumbers = 0; const pivotFieldKey = toLabel(engineProps?.pivotField ?? pivotDoc._pivotField) || 'author'; @@ -136,10 +136,10 @@ export function computePivotLayout(poolData: Map<string, PoolData>, 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<string, PoolData>, pivotDoc: Do return normalizeResults(panelDim, max_text, docMap, poolData, viewDefsToJSX, groupNames, 0, []); } -function toNumber(val: FieldResult<Field>) { +function toNumber(val: FieldResult<FieldType>) { return val === undefined ? undefined : NumCast(val, Number(StrCast(val))); } @@ -274,7 +274,7 @@ export function computeTimelineLayout(poolData: Map<string, PoolData>, pivotDoc: const pivotDateGroups = new Map<number, Doc[]>(); const docMap = new Map<string, PoolData>(); 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<string, PoolData>, 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<CollectionFreeFormPannableContentsProps> { +export class CollectionFreeFormPannableContents extends ObservableReactComponent<CollectionFreeFormPannableContentsProps> { 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<Collecti render() { return ( <div - className={'collectionfreeformview' + (this.props.viewDefDivClick ? '-viewDef' : '-none')} + className={'collectionfreeformview' + (this._props.viewDefDivClick ? '-viewDef' : '-none')} onScroll={e => { const target = e.target as any; if (getComputedStyle(target)?.overflow === 'visible') { @@ -50,13 +52,13 @@ export class CollectionFreeFormPannableContents extends React.Component<Collecti } }} style={{ - transform: this.props.transform(), - transition: this.props.transition, - width: this.props.isAnnotationOverlay ? undefined : 0, // if not an overlay, then this will be the size of the collection, but panning and zooming will move it outside the visible border of the collection and make it selectable. This problem shows up after zooming/panning on a background collection -- you can drag the collection by clicking on apparently empty space outside the collection + transform: this._props.transform(), + transition: this._props.transition, + width: this._props.isAnnotationOverlay ? undefined : 0, // if not an overlay, then this will be the size of the collection, but panning and zooming will move it outside the visible border of the collection and make it selectable. This problem shows up after zooming/panning on a background collection -- you can drag the collection by clicking on apparently empty space outside the collection }}> {this.props.children} {this.presPaths} - {this.showViewport(this.props.brushedView())} + {this.showViewport(this._props.brushedView())} </div> ); } 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<CollectionViewProps> { @computed protected get cursors(): CursorField[] { - const doc = this.props.Document; + const { Document } = this.props; let cursors: FieldResult<List<CursorField>>; 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<Partial<collection @observable _paintedId = 'id' + Utils.GenerateGuid().replace(/-/g, ''); @computed get paintFunc() { const field = this.dataDoc[this.fieldKey]; - const paintFunc = StrCast(Field.toJavascriptString(Cast(field, RichTextField, null)?.Text as Field)).trim(); + const paintFunc = StrCast(Field.toJavascriptString(Cast(field, RichTextField, null)?.Text as FieldType)).trim(); return !paintFunc ? '' : paintFunc.includes('dashDiv') @@ -635,11 +640,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection onGesture = (e: Event, ge: GestureUtils.GestureEvent) => { 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<Partial<collection this.addDocument(inkDoc); e.stopPropagation(); break; - case GestureUtils.Gestures.Rectangle: + case Gestures.Rectangle: const strokes = this.getActiveDocuments() .filter(doc => doc.type === DocumentType.INK) .map(i => { @@ -672,7 +677,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection CognitiveServices.Inking.Appliers.InterpretStrokes(strokes).then(results => {}); 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<Partial<collection @action onClick = (e: React.MouseEvent) => { 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<Partial<collection segments.forEach(segment => 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<Partial<collection }); return false; }; - forceStrokeGesture = (e: PointerEvent, gesture: GestureUtils.Gestures, points: InkData, text?: any) => { + 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<Partial<collection switch ( !e.ctrlKey && !e.shiftKey && !e.metaKey && !e.altKey ?// Doc.UserDoc().freeformScrollMode : // no modifiers, do assigned mode - e.ctrlKey && !CtrlKey? // otherwise, if ctrl key (pinch gesture) try to zoom else pan + e.ctrlKey && !SnappingManager.CtrlKey? // otherwise, if ctrl key (pinch gesture) try to zoom else pan freeformScrollMode.Zoom : freeformScrollMode.Pan // prettier-ignore ) { case freeformScrollMode.Pan: @@ -1030,9 +1035,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection (!this._props.getScrollHeight?.() ? fitYscroll : 0); // when not zoomed, scrolling is handled via a scrollbar, not panning let newPanY = Math.max(minPanY, Math.min(maxPanY, panY)); if (false && NumCast(this.layoutDoc.layout_scrollTop) && NumCast(this.layoutDoc._freeform_scale, minScale) !== minScale) { - const relTop = NumCast(this.layoutDoc.layout_scrollTop) / maxScrollTop; - this.layoutDoc.layout_scrollTop = undefined; - newPanY = minPanY + relTop * (maxPanY - minPanY); } else if (fitYscroll > 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<Partial<collection : NumCast(this.layoutDoc._rotation_jitter) * random(-1, 1, NumCast(x), NumCast(y)) ); const childProps = { ...this._props, fieldKey: '', styleProvider: this.clusterStyleProvider }; return { - x: Number.isNaN(NumCast(x)) ? 0 : NumCast(x), - y: Number.isNaN(NumCast(y)) ? 0 : NumCast(y), + x: isNaN(NumCast(x)) ? 0 : NumCast(x), + y: isNaN(NumCast(y)) ? 0 : NumCast(y), z: Cast(z, 'number'), autoDim, rotation, @@ -1424,8 +1426,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return anchor; }; - @action closeInfo = () => (Doc.IsInfoUIDisabled = true); - infoUI = () => (Doc.IsInfoUIDisabled || this.Document.annotationOn || this._props.renderDepth ? null : <CollectionFreeFormInfoUI Document={this.Document} Freeform={this} close={this.closeInfo} />); + childDocsFunc = () => this.childDocs; + @action closeInfo = () => { Doc.IsInfoUIDisabled = true }; // prettier-ignore + infoUI = () => (Doc.IsInfoUIDisabled || this.Document.annotationOn || this._props.renderDepth ? null : <CollectionFreeFormInfoUI Document={this.Document} LayoutDoc={this.layoutDoc} childDocs={this.childDocsFunc} close={this.closeInfo} />); componentDidMount() { this._props.setContentViewBox?.(this); @@ -1483,6 +1486,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection if (!code.includes('dashDiv')) { const script = CompileScript(code, { params: { docView: 'any' }, typecheck: false, editable: true }); if (script.compiled) script.run({ this: this.DocumentView?.() }); + // eslint-disable-next-line no-eval } else code && !first && eval?.(code); }, { fireImmediately: true } @@ -1491,7 +1495,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection this._disposers.layoutElements = reaction( // layoutElements can't be a computed value because doLayoutComputation() is an action that has side effect of updating clusters () => 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<Partial<collection const canvas = oldDiv; const img = document.createElement('img'); // create a Image Element try { - img.src = canvas.toDataURL(); //image source + img.src = canvas.toDataURL(); // image source } catch (e) { console.log(e); } @@ -1567,14 +1573,12 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const htmlString = new XMLSerializer().serializeToString(newDiv); const nativeWidth = width; const nativeHeight = height; - return CreateImage(Utils.prepend(''), document.styleSheets, htmlString, nativeWidth, (nativeWidth * panelHeight) / panelWidth, (scrollTop * panelHeight) / realNativeHeight) - .then(async (data_url: any) => { - 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<Partial<collection } @action - onCursorMove = (e: React.PointerEvent) => { + 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<Partial<collection const width = Math.max(...docs.map(doc => 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<Partial<collection const snappableDocs = activeDocs.filter(doc => 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<Partial<collection incrementalRender = action(() => { 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<Partial<collection ); } + showPresPaths = () => 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<Partial<collection brushedView={this.brushedView} isAnnotationOverlay={this.isAnnotationOverlay} transform={this.PanZoomCenterXf} + showPresPaths={this.showPresPaths} transition={this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this.Document._viewTransition, 'string', null))} viewDefDivClick={this._props.viewDefDivClick}> {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<Partial<collection this._brushtimer1 = setTimeout( action(() => { 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<Partial<collection @observer class CollectionFreeFormOverlayView extends React.Component<{ elements: () => 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<string>(['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<void>; } -export interface MarqueeViewBounds { - left: number; - top: number; - width: number; - height: number; -} - @observer export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps & MarqueeViewProps> { public static CurViewBounds(pinDoc: Doc, panelWidth: number, panelHeight: number) { @@ -102,7 +99,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps @action onKeyDown = (e: KeyboardEvent) => { - //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<SubCollectionViewProps } } let ypos = y; - ns.map(line => { + 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<SubCollectionViewProps pasteImageBitmap((data: any, error: any) => { 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<SubCollectionViewProps TreeView._editTitleOnLoad = { id: slide[Id], parent: undefined }; this._props.addDocument?.(slide); e.stopPropagation(); - }*/ else if (e.key === 'p' && e.ctrlKey) { + } */ else if (e.key === 'p' && e.ctrlKey) { e.preventDefault(); (async () => { const text: string = await navigator.clipboard.readText(); @@ -184,7 +181,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps e.stopPropagation(); } }; - //heuristically converts pasted text into a table. + // heuristically converts pasted text into a table. // assumes each entry is separated by a tab // skips all rows until it gets to a row with more than one entry // assumes that 1st row has header entry for each column @@ -192,7 +189,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps // any row that has only one column is a section header-- this header is then added as a column to subsequent rows until the next header // assumes each cell is a string or a number pasteTable(ns: string[], x: number, y: number) { - let csvRows = []; + const csvRows = []; const headers = ns[0].split('\t'); csvRows.push(headers.join(',')); ns[0] = ''; @@ -200,7 +197,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps let eachRow = []; for (let i = 1; i < eachCell.length; i++) { eachRow.push(eachCell[i].replace(/\,/g, '')); - if (i % headers.length == 0) { + if (i % headers.length === 0) { csvRows.push(eachRow); eachRow = []; } @@ -233,7 +230,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps this._lastY = e.pageY; this._lassoPts.push([e.clientX, e.clientY]); if (!e.cancelBubble) { - if (!Utils.isClick(this._lastX, this._lastY, this._downX, this._downY, Date.now())) { + if (!ClientUtils.isClick(this._lastX, this._lastY, this._downX, this._downY, Date.now())) { if (!this._commandExecuted) { this.showMarquee(); } @@ -320,7 +317,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps @action onClick = (e: React.MouseEvent): void => { 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<SubCollectionViewProps }; @action - showMarquee = () => (this._visible = true); + showMarquee = () => { + this._visible = true; + }; @action - hideMarquee = () => (this._visible = false); + hideMarquee = () => { + this._visible = false; + }; @undoBatch delete = action((e?: React.PointerEvent<Element> | 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<SubCollectionViewProps }; touchesLine(r1: { left: number; top: number; width: number; height: number }) { + // eslint-disable-next-line no-restricted-syntax for (const lassoPt of this._lassoPts) { const topLeft = this.Transform.transformPoint(lassoPt[0], lassoPt[1]); if (r1.left < topLeft[0] && topLeft[0] < r1.left + r1.width && r1.top < topLeft[1] && topLeft[1] < r1.top + r1.height) { @@ -560,6 +564,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps let hasLeft = false; let hasBottom = false; let hasRight = false; + // eslint-disable-next-line no-restricted-syntax for (const lassoPt of this._lassoPts) { const truePoint = this.Transform.transformPoint(lassoPt[0], lassoPt[1]); hasLeft = hasLeft || (truePoint[0] > 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<SubCollectionViewProps }; render() { return ( + // eslint-disable-next-line jsx-a11y/click-events-have-key-events <div className="marqueeView" ref={r => { @@ -673,7 +679,9 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps cursor: [InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool) || this._visible ? 'crosshair' : 'pointer', }} onDragOver={e => 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<HTMLDivElement>) => { 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<HTMLDivElement>) => 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 : ( <span className="bottomPopup-background" style={{ pointerEvents: 'all' }} onPointerDown={e => e.stopPropagation()}> <span className="bottomPopup-text"> Creating link from:{' '} @@ -108,7 +113,7 @@ export class CollectionLinearView extends CollectionSubView() { </b> </span> - <Tooltip title={<div className="dash-tooltip">{'Toggle description pop-up'} </div>} placement="top"> + <Tooltip title={<div className="dash-tooltip">Toggle description pop-up </div>} placement="top"> <span className="bottomPopup-descriptions" onClick={this.changeDescriptionSetting}> Labels: {LinkDescriptionPopup.Instance.showDescriptions ? LinkDescriptionPopup.Instance.showDescriptions : 'ON'} </span> @@ -121,9 +126,8 @@ export class CollectionLinearView extends CollectionSubView() { </Tooltip> </span> ); - }; - getCurrentlyPlayingUI = () => { - return !CollectionStackedTimeline.CurrentlyPlaying?.length ? null : ( + getCurrentlyPlayingUI = () => + !CollectionStackedTimeline.CurrentlyPlaying?.length ? null : ( <span className="bottomPopup-background"> <span className="bottomPopup-text"> Currently playing: @@ -139,7 +143,6 @@ export class CollectionLinearView extends CollectionSubView() { </span> </span> ); - }; 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 '<CurrentlyPlayingUI>': return this.getCurrentlyPlayingUI(); case '<UndoStack>': return <UndoStack key={doc[Id]}/>; case '<Branching>': return Doc.UserDoc().isBranchingMode ? <BranchingTrailManager key={doc[Id]} /> : null; + default: } const nested = doc._type_collection === CollectionViewType.Linear; @@ -161,7 +165,9 @@ export class CollectionLinearView extends CollectionSubView() { <div className={preview ? 'preview' : `collectionLinearView-docBtn`} key={doc[Id]} - ref={r => (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 /> </div> ); @@ -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 ( <div className={`collectionLinearView-outer ${this.layoutDoc.linearView_SubMenu}`} style={{ backgroundColor: this.layoutDoc.linearView_IsOpen ? undefined : 'transparent' }}> <div className="collectionLinearView" ref={this.createDashEventsTarget} onContextMenu={this.myContextMenu} style={{ minHeight: this.dimension(), pointerEvents: 'all' }}> - { - <> - {!this.layoutDoc.linearView_Expandable ? null : menuOpener} - {!this.layoutDoc.linearView_IsOpen ? null : ( - <div - className="collectionLinearView-content" - style={{ - height: this.dimension(), - flexDirection: flexDir as any, - gap: flexGap, - }}> - {this.childLayoutPairs.map(pair => this.getDisplayDoc(pair.layout))} - </div> - )} - </> - } + {!this.layoutDoc.linearView_Expandable ? null : menuOpener} + {!this.layoutDoc.linearView_IsOpen ? null : ( + <div + className="collectionLinearView-content" + style={{ + height: this.dimension(), + flexDirection: flexDir as any, + gap: flexGap, + }}> + {this.childLayoutPairs.map(pair => this.getDisplayDoc(pair.layout))} + </div> + )} </div> </div> ); 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<SchemaTableCellPro choosePath(url: URL) { if (url.protocol === 'data') return url.href; // if the url ises the data protocol, just return the href - if (url.href.indexOf(window.location.origin) === -1) return Utils.CorsProxy(url.href); // otherwise, put it through the cors proxy erver + if (url.href.indexOf(window.location.origin) === -1) return ClientUtils.CorsProxy(url.href); // otherwise, put it through the cors proxy erver if (!/\.(png|jpg|jpeg|gif|webp)$/.test(url.href.toLowerCase())) return url.href; //Why is this here — good question const ext = extname(url.href); @@ -220,7 +222,7 @@ export class SchemaImageCell extends ObservableReactComponent<SchemaTableCellPro .map(url => 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]; } diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index 2a5732708..50cf26cdb 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -2,10 +2,11 @@ import { Colors } from 'browndash-components'; import { action, runInAction } from 'mobx'; import { aggregateBounds } from '../../../Utils'; import { Doc, Opt } from '../../../fields/Doc'; +import { DocData } from '../../../fields/DocSymbols'; import { InkTool } from '../../../fields/InkField'; import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types'; import { WebField } from '../../../fields/URLField'; -import { GestureUtils } from '../../../pen-gestures/GestureUtils'; +import { Gestures } from '../../../pen-gestures/GestureTypes'; import { DocumentType } from '../../documents/DocumentTypes'; import { LinkManager } from '../../util/LinkManager'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; @@ -14,26 +15,28 @@ import { UndoManager, undoable } from '../../util/UndoManager'; import { GestureOverlay } from '../GestureOverlay'; import { ActiveFillColor, ActiveInkColor, ActiveInkHideTextLabels, ActiveInkWidth, ActiveIsInkMask, InkingStroke, SetActiveFillColor, SetActiveInkColor, SetActiveInkHideTextLabels, SetActiveInkWidth, SetActiveIsInkMask } from '../InkingStroke'; import { CollectionFreeFormView } from '../collections/collectionFreeForm'; -// import { InkTranscription } from '../InkTranscription'; -import { DocData } from '../../../fields/DocSymbols'; import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView'; import { DocumentView } from '../nodes/DocumentView'; +import { ImageBox } from '../nodes/ImageBox'; import { VideoBox } from '../nodes/VideoBox'; import { WebBox } from '../nodes/WebBox'; import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; -import { ImageBox } from '../nodes/ImageBox'; +// import { InkTranscription } from '../InkTranscription'; +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function IsNoneSelected() { return SelectionManager.Views.length <= 0; }, 'are no document selected'); // toggle: Set overlay status of selected document +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function setView(view: string) { const selected = SelectionManager.Docs.lastElement(); selected ? (selected._type_collection = view) : console.log('[FontIconBox.tsx] changeView failed'); }); // toggle: Set overlay status of selected document +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: boolean) { const selectedViews = SelectionManager.Views; if (Doc.ActiveTool !== InkTool.None) { @@ -70,15 +73,18 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b return selected.lastElement()?._backgroundColor ?? 'transparent'; } SetActiveFillColor(color ?? 'transparent'); - selected.forEach(doc => (doc[DocData].backgroundColor = color)); + selected.forEach(doc => { doc[DocData].backgroundColor = color; }); // prettier-ignore } + return ''; }); // toggle: Set overlay status of selected document +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function setDefaultTemplate(checkResult?: boolean) { return DocumentView.setDefaultTemplate(checkResult); }); // toggle: Set overlay status of selected document +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function setHeaderColor(color?: string, checkResult?: boolean) { if (checkResult) { return SelectionManager.Views.length ? StrCast(SelectionManager.Docs.lastElement().layout_headingColor) : Doc.SharingDoc().headingColor; @@ -93,9 +99,11 @@ ScriptingGlobals.add(function setHeaderColor(color?: string, checkResult?: boole Doc.GetProto(Doc.SharingDoc()).headingColor = color === 'transparent' ? undefined : color; Doc.UserDoc().layout_showTitle = color === 'transparent' ? undefined : StrCast(Doc.UserDoc().layout_showTitle, 'title'); } + return undefined; }); // toggle: Set overlay status of selected document +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) { const selected = SelectionManager.Views.length ? SelectionManager.Views[0] : undefined; if (checkResult) { @@ -103,19 +111,21 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) { return false; } selected ? selected.CollectionFreeFormDocumentView?.float() : console.log('[FontIconBox.tsx] toggleOverlay failed'); + return undefined; }); +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines' | 'clusters' | 'viewAll' | 'fitOnce', checkResult?: boolean, persist?: boolean) { const selected = SelectionManager.Docs.lastElement(); // prettier-ignore const map: Map<'center' |'grid' | 'snaplines' | 'clusters' | 'viewAll' | 'fitOnce', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc, dv:DocumentView) => void;}> = new Map([ ['grid', { checkResult: (doc:Doc) => BoolCast(doc?._freeform_backgroundGrid, false), - setDoc: (doc:Doc,dv:DocumentView) => doc._freeform_backgroundGrid = !doc._freeform_backgroundGrid, + setDoc: (doc:Doc) => { doc._freeform_backgroundGrid = !doc._freeform_backgroundGrid; }, }], ['snaplines', { checkResult: (doc:Doc) => BoolCast(doc?._freeform_snapLines, false), - setDoc: (doc:Doc, dv:DocumentView) => doc._freeform_snapLines = !doc._freeform_snapLines, + setDoc: (doc:Doc) => { doc._freeform_snapLines = !doc._freeform_snapLines; }, }], ['viewAll', { checkResult: (doc:Doc) => BoolCast(doc?._freeform_fitContentsToBox, false), @@ -127,12 +137,12 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines' }], ['center', { checkResult: (doc:Doc) => BoolCast(doc?._stacking_alignCenter, false), - setDoc: (doc:Doc,dv:DocumentView) => doc._stacking_alignCenter = !doc._stacking_alignCenter, + setDoc: (doc:Doc) => { doc._stacking_alignCenter = !doc._stacking_alignCenter; }, }], ['clusters', { waitForRender: true, // flags that undo batch should terminate after a re-render giving the script the chance to fire checkResult: (doc:Doc) => BoolCast(doc?._freeform_useClusters, false), - setDoc: (doc:Doc,dv:DocumentView) => doc._freeform_useClusters = !doc._freeform_useClusters, + setDoc: (doc:Doc) => { doc._freeform_useClusters = !doc._freeform_useClusters; }, }], ]); @@ -142,11 +152,12 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines' const batch = map.get(attr)?.waitForRender ? UndoManager.StartBatch('set freeform attribute') : { end: () => {} }; SelectionManager.Views.map(dv => map.get(attr)?.setDoc(dv.layoutDoc, dv)); setTimeout(() => batch.end(), 100); + return undefined; }); +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highlight' | 'fontSize' | 'alignment', value: any, checkResult?: boolean) { const editorView = RichTextMenu.Instance?.TextView?.EditorView; - const selected = SelectionManager.Docs.lastElement(); // prettier-ignore const map: Map<'font'|'fontColor'|'highlight'|'fontSize'|'alignment', { checkResult: () => any; setDoc: () => void;}> = new Map([ ['font', { @@ -163,14 +174,15 @@ ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highligh }], ['alignment', { checkResult: () => RichTextMenu.Instance?.textAlign, - setDoc: () => value && editorView?.state ? RichTextMenu.Instance?.align(editorView, editorView.dispatch, value):(Doc.UserDoc().textAlign = value), + setDoc: () => { value && editorView?.state ? RichTextMenu.Instance?.align(editorView, editorView.dispatch, value):(Doc.UserDoc().textAlign = value); }, }], ['fontSize', { checkResult: () => RichTextMenu.Instance?.fontSize.replace('px', ''), setDoc: () => { - if (typeof value === 'number') value = value.toString(); - if (value && Number(value).toString() === value) value += 'px'; - RichTextMenu.Instance?.setFontSize(value); + let fsize = value; + if (typeof fsize === 'number') fsize = fsize.toString(); + if (fsize && Number(fsize).toString() === fsize) fsize += 'px'; + RichTextMenu.Instance?.setFontSize(fsize); }, }], ]); @@ -179,63 +191,55 @@ ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highligh return map.get(attr)?.checkResult(); } map.get(attr)?.setDoc?.(); + return undefined; }); type attrname = 'noAutoLink' | 'dictation' | 'bold' | 'italics' | 'elide' | 'underline' | 'left' | 'center' | 'right' | 'vcent' | 'bullet' | 'decimal'; type attrfuncs = [attrname, { checkResult: () => boolean; toggle?: () => any }]; +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?: boolean) { const textView = RichTextMenu.Instance?.TextView; const editorView = textView?.EditorView; // prettier-ignore const alignments:attrfuncs[] = (['left','right','center','vcent'] as ("left"|"center"|"right"|"vcent")[]).map((where) => - [ where, { checkResult: () =>(editorView ? (where === 'vcent' ? RichTextMenu.Instance?.textVcenter ?? false: - (RichTextMenu.Instance?.textAlign === where)): - where === 'vcent' ? BoolCast(Doc.UserDoc()._layout_centered): - (Doc.UserDoc().textAlign ===where) ? true:false), - toggle: () => (editorView?.state ? (where === 'vcent' ? RichTextMenu.Instance?.vcenterToggle(editorView, editorView.dispatch): - RichTextMenu.Instance?.align(editorView, editorView.dispatch, where)): + [ where, { checkResult: () => editorView ? (where === 'vcent' ? RichTextMenu.Instance?.textVcenter ?? false: + (RichTextMenu.Instance?.textAlign === where)): + where === 'vcent' ? BoolCast(Doc.UserDoc()._layout_centered): + (Doc.UserDoc().textAlign === where), + toggle: () => { editorView?.state ? (where === 'vcent' ? RichTextMenu.Instance?.vcenterToggle(editorView, editorView.dispatch): + RichTextMenu.Instance?.align(editorView, editorView.dispatch, where)): where === 'vcent' ? Doc.UserDoc()._layout_centered = !Doc.UserDoc()._layout_centered: - (Doc.UserDoc().textAlign = where))}]); // prettier-ignore + (Doc.UserDoc().textAlign = where); } + }]); // prettier-ignore // prettier-ignore const listings:attrfuncs[] = (['bullet','decimal'] as attrname[]).map(list => [ list, { checkResult: () => (editorView ? RichTextMenu.Instance?.listStyle === list:false), toggle: () => editorView?.state && RichTextMenu.Instance?.changeListType(list) }]); // prettier-ignore const attrs:attrfuncs[] = [ - ['dictation', { checkResult: () => textView?._recordingDictation ? true:false, - toggle: () => textView && runInAction(() => (textView._recordingDictation = !textView._recordingDictation)) }], + ['dictation', { checkResult: () => !!textView?._recordingDictation, + toggle: () => textView && runInAction(() => { textView._recordingDictation = !textView._recordingDictation;} ) }], ['elide', { checkResult: () => false, toggle: () => editorView ? RichTextMenu.Instance?.elideSelection(): 0}], ['noAutoLink',{ checkResult: () => ((editorView && RichTextMenu.Instance?.noAutoLink) ?? false), toggle: () => editorView && RichTextMenu.Instance?.toggleNoAutoLinkAnchor()}], - ['bold', { checkResult: () => (editorView ? RichTextMenu.Instance?.bold??false : (Doc.UserDoc().fontWeight === 'bold') ? true:false), - toggle: editorView ? RichTextMenu.Instance?.toggleBold : () => (Doc.UserDoc().fontWeight = Doc.UserDoc().fontWeight === 'bold' ? undefined : 'bold')}], - ['italics', { checkResult: () => (editorView ? RichTextMenu.Instance?.italics ?? false : (Doc.UserDoc().fontStyle === 'italics') ? true:false), - toggle: editorView ? RichTextMenu.Instance?.toggleItalics : () => (Doc.UserDoc().fontStyle = Doc.UserDoc().fontStyle === 'italics' ? undefined : 'italics')}], - ['underline', { checkResult: () => (editorView ? RichTextMenu.Instance?.underline ?? false: (Doc.UserDoc().textDecoration === 'underline') ? true:false), - toggle: editorView ? RichTextMenu.Instance?.toggleUnderline : () => (Doc.UserDoc().textDecoration = Doc.UserDoc().textDecoration === 'underline' ? undefined : 'underline') }]] + ['bold', { checkResult: () => (editorView ? RichTextMenu.Instance?.bold??false : (Doc.UserDoc().fontWeight === 'bold')), + toggle: editorView ? RichTextMenu.Instance?.toggleBold : () => { Doc.UserDoc().fontWeight = Doc.UserDoc().fontWeight === 'bold' ? undefined : 'bold'; }}], + ['italics', { checkResult: () => (editorView ? RichTextMenu.Instance?.italics ?? false : (Doc.UserDoc().fontStyle === 'italics')), + toggle: editorView ? RichTextMenu.Instance?.toggleItalics : () => { Doc.UserDoc().fontStyle = Doc.UserDoc().fontStyle === 'italics' ? undefined : 'italics'; }}], + ['underline', { checkResult: () => (editorView ? RichTextMenu.Instance?.underline ?? false: (Doc.UserDoc().textDecoration === 'underline')), + toggle: editorView ? RichTextMenu.Instance?.toggleUnderline : () => { Doc.UserDoc().textDecoration = Doc.UserDoc().textDecoration === 'underline' ? undefined : 'underline'; } }]] const map = new Map(attrs.concat(alignments).concat(listings)); if (checkResult) { return map.get(charStyle)?.checkResult(); } undoable(() => map.get(charStyle)?.toggle?.(), 'toggle ' + charStyle)(); + return undefined; }); -export function checkInksToGroup() { - if (Doc.ActiveTool === InkTool.Write) { - CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => { - // TODO: nda - will probably want to go through ffView unprocessed docs and then see if any of the inksToGroup docs are in it and only use those - // find all inkDocs in ffView.unprocessedDocs that are within 200 pixels of each other - const inksToGroup = ffView.unprocessedDocs.filter(inkDoc => { - // console.log(inkDoc.x, inkDoc.y); - }); - }); - } -} - -export function createInkGroup(inksToGroup?: Doc[], isSubGroup?: boolean) { +export function createInkGroup(/* inksToGroup?: Doc[], isSubGroup?: boolean */) { // TODO nda - if document being added to is a inkGrouping then we can just add to that group if (Doc.ActiveTool === InkTool.Write) { CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => { @@ -301,26 +305,24 @@ export function createInkGroup(inksToGroup?: Doc[], isSubGroup?: boolean) { CollectionFreeFormView.collectionsWithUnprocessedInk.clear(); } -function setActiveTool(tool: InkTool | GestureUtils.Gestures, keepPrim: boolean, checkResult?: boolean) { +function setActiveTool(tool: InkTool | Gestures, keepPrim: boolean, checkResult?: boolean) { // InkTranscription.Instance?.createInkGroup(); if (checkResult) { return (Doc.ActiveTool === tool && !GestureOverlay.Instance?.InkShape) || GestureOverlay.Instance?.InkShape === tool - ? GestureOverlay.Instance?.KeepPrimitiveMode || ![GestureUtils.Gestures.Circle, GestureUtils.Gestures.Line, GestureUtils.Gestures.Rectangle].includes(tool as GestureUtils.Gestures) - ? true - : true + ? GestureOverlay.Instance?.KeepPrimitiveMode || ![Gestures.Circle, Gestures.Line, Gestures.Rectangle].includes(tool as Gestures) : false; } runInAction(() => { if (GestureOverlay.Instance) { GestureOverlay.Instance.KeepPrimitiveMode = keepPrim; } - if (Object.values(GestureUtils.Gestures).includes(tool as any)) { + if (Object.values(Gestures).includes(tool as any)) { if (GestureOverlay.Instance.InkShape === tool && !keepPrim) { Doc.ActiveTool = InkTool.None; GestureOverlay.Instance.InkShape = undefined; } else { Doc.ActiveTool = InkTool.Pen; - GestureOverlay.Instance.InkShape = tool as GestureUtils.Gestures; + GestureOverlay.Instance.InkShape = tool as Gestures; } } else if (tool) { // pen or eraser @@ -334,38 +336,40 @@ function setActiveTool(tool: InkTool | GestureUtils.Gestures, keepPrim: boolean, Doc.ActiveTool = InkTool.None; } }); + return undefined; } ScriptingGlobals.add(setActiveTool, 'sets the active ink tool mode'); // toggle: Set overlay status of selected document +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'labels' | 'fillColor' | 'strokeWidth' | 'strokeColor', value: any, checkResult?: boolean) { const selected = SelectionManager.Docs.lastElement() ?? Doc.UserDoc(); // prettier-ignore const map: Map<'inkMask' | 'labels' | 'fillColor' | 'strokeWidth' | 'strokeColor', { checkResult: () => any; setInk: (doc: Doc) => void; setMode: () => void }> = new Map([ ['inkMask', { checkResult: () => ((selected?._layout_isSvg ? BoolCast(selected[DocData].stroke_isInkMask) : ActiveIsInkMask())), - setInk: (doc: Doc) => (doc[DocData].stroke_isInkMask = !doc.stroke_isInkMask), + setInk: (doc: Doc) => { doc[DocData].stroke_isInkMask = !doc.stroke_isInkMask; }, setMode: () => selected?.type !== DocumentType.INK && SetActiveIsInkMask(!ActiveIsInkMask()), }], ['labels', { checkResult: () => ((selected?._stroke_showLabel ? BoolCast(selected[DocData].stroke_showLabel) : ActiveInkHideTextLabels())), - setInk: (doc: Doc) => (doc[DocData].stroke_showLabel = !doc.stroke_showLabel), + setInk: (doc: Doc) => { doc[DocData].stroke_showLabel = !doc.stroke_showLabel; }, setMode: () => selected?.type !== DocumentType.INK && SetActiveInkHideTextLabels(!ActiveInkHideTextLabels()), }], ['fillColor', { checkResult: () => (selected?._layout_isSvg ? StrCast(selected[DocData].fillColor) : ActiveFillColor() ?? "transparent"), - setInk: (doc: Doc) => (doc[DocData].fillColor = StrCast(value)), + setInk: (doc: Doc) => { doc[DocData].fillColor = StrCast(value); }, setMode: () => SetActiveFillColor(StrCast(value)), }], [ 'strokeWidth', { checkResult: () => (selected?._layout_isSvg ? NumCast(selected[DocData].stroke_width) : ActiveInkWidth()), - setInk: (doc: Doc) => (doc[DocData].stroke_width = NumCast(value)), + setInk: (doc: Doc) => { doc[DocData].stroke_width = NumCast(value); }, setMode: () => { SetActiveInkWidth(value.toString()); selected?.type === DocumentType.INK && setActiveTool( GestureOverlay.Instance.InkShape ?? InkTool.Pen, true, false);}, }], ['strokeColor', { checkResult: () => (selected?._layout_isSvg? StrCast(selected[DocData].color) : ActiveInkColor()), - setInk: (doc: Doc) => (doc[DocData].color = String(value)), + setInk: (doc: Doc) => { doc[DocData].color = String(value); }, setMode: () => { SetActiveInkColor(StrCast(value)); selected?.type === DocumentType.INK && setActiveTool(GestureOverlay.Instance.InkShape ?? InkTool.Pen, true, false);}, }], ]); @@ -375,11 +379,13 @@ ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'labels' | 'fil } map.get(option)?.setMode(); SelectionManager.Docs.filter(doc => doc._layout_isSvg).map(doc => map.get(option)?.setInk(doc)); + return undefined; }); /** WEB * webSetURL - **/ + * */ +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function webSetURL(url: string, checkResult?: boolean) { const selected = SelectionManager.Views.lastElement(); if (selected?.Document.type === DocumentType.WEB) { @@ -387,30 +393,36 @@ ScriptingGlobals.add(function webSetURL(url: string, checkResult?: boolean) { return StrCast(selected.Document.data, Cast(selected.Document.data, WebField, null)?.url?.href); } selected.ComponentView?.setData?.(url); - //selected.Document.data = new WebField(url); } + return ''; }); +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function webForward(checkResult?: boolean) { const selected = SelectionManager.Views.lastElement()?.ComponentView as WebBox; if (checkResult) { return selected?.forward(checkResult) ? undefined : 'lightGray'; } selected?.forward(); + return undefined; }); +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function webBack() { const selected = SelectionManager.Views.lastElement()?.ComponentView as WebBox; selected?.back(); }); +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function videoSnapshot() { const selected = SelectionManager.Views.lastElement()?.ComponentView as VideoBox; selected?.Snapshot(); }); +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function imageSetPixelSize() { const selected = SelectionManager.Views.lastElement()?.ComponentView as ImageBox; selected?.setNativeSize(); }); +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function imageRotate90() { const selected = SelectionManager.Views.lastElement()?.ComponentView as ImageBox; selected?.rotate(); @@ -418,21 +430,25 @@ ScriptingGlobals.add(function imageRotate90() { /** Schema * toggleSchemaPreview - **/ + * */ +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function toggleSchemaPreview(checkResult?: boolean) { const selected = SelectionManager.Docs.lastElement(); if (checkResult && selected) { const result: boolean = NumCast(selected.schema_previewWidth) > 0; if (result) return Colors.MEDIUM_BLUE; - else return 'transparent'; - } else if (selected) { + return 'transparent'; + } + if (selected) { if (NumCast(selected.schema_previewWidth) > 0) { selected.schema_previewWidth = 0; } else { selected.schema_previewWidth = 200; } } + return ''; }); +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function toggleSingleLineSchema(checkResult?: boolean) { const selected = SelectionManager.Docs.lastElement(); if (checkResult && selected) { @@ -441,17 +457,20 @@ ScriptingGlobals.add(function toggleSingleLineSchema(checkResult?: boolean) { if (selected) { selected._schema_singleLine = !selected._schema_singleLine; } + return undefined; }); /** STACK * groupBy */ +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function setGroupBy(key: string, checkResult?: boolean) { - SelectionManager.Docs.map(doc => (doc._text_fontFamily = key)); + SelectionManager.Docs.forEach(doc => { doc._text_fontFamily = key; }); // prettier-ignore const editorView = RichTextMenu.Instance?.TextView?.EditorView; if (checkResult) { return StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc())?.fontFamily); } if (editorView) RichTextMenu.Instance?.setFontFamily(key); else Doc.UserDoc().fontFamily = key; + return undefined; }); diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index a2c9d10b6..38ef08ef9 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -4,13 +4,15 @@ import { Tooltip } from '@mui/material'; import { action, computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../Utils'; +import { returnFalse, setupMoveUpEvents } from '../../../ClientUtils'; +import { emptyFunction } from '../../../Utils'; import { Doc } from '../../../fields/Doc'; import { Cast, DocCast, StrCast } from '../../../fields/Types'; import { WebField } from '../../../fields/URLField'; import { 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 { LinkFollower } from '../../util/LinkFollower'; import { LinkManager } from '../../util/LinkManager'; import { SelectionManager } from '../../util/SelectionManager'; diff --git a/src/client/views/linking/LinkPopup.tsx b/src/client/views/linking/LinkPopup.tsx index c9e3c203d..f2681bf2e 100644 --- a/src/client/views/linking/LinkPopup.tsx +++ b/src/client/views/linking/LinkPopup.tsx @@ -2,7 +2,8 @@ import { action, observable } from 'mobx'; import { observer } from 'mobx-react'; import { EditorView } from 'prosemirror-view'; import * as React from 'react'; -import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../Utils'; +import { emptyFunction } from '../../../Utils'; +import { returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../ClientUtils'; import { Doc } from '../../../fields/Doc'; import { Transform } from '../../util/Transform'; import { undoBatch } from '../../util/UndoManager'; diff --git a/src/client/views/newlightbox/NewLightboxView.tsx b/src/client/views/newlightbox/NewLightboxView.tsx index 12b9870ca..dcbc9fc50 100644 --- a/src/client/views/newlightbox/NewLightboxView.tsx +++ b/src/client/views/newlightbox/NewLightboxView.tsx @@ -2,7 +2,8 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnTrue } from '../../../Utils'; +import { returnEmptyDoclist, returnEmptyFilter, returnTrue } from '../../../ClientUtils'; +import { emptyFunction } from '../../../Utils'; import { Doc, DocListCast, Opt } from '../../../fields/Doc'; import { InkTool } from '../../../fields/InkField'; import { Cast, NumCast, StrCast } from '../../../fields/Types'; diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index c685ec66f..c37466914 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -1,15 +1,18 @@ +/* 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 { action, computed, IReactionDisposer, makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; +import { returnFalse, setupMoveUpEvents } from '../../../ClientUtils'; import { DateField } from '../../../fields/DateField'; import { Doc } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { ComputedField } from '../../../fields/ScriptField'; import { Cast, DateCast, NumCast } from '../../../fields/Types'; import { AudioField, nullAudio } from '../../../fields/URLField'; -import { emptyFunction, formatTime, returnFalse, setupMoveUpEvents } from '../../../Utils'; +import { formatTime } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { Networking } from '../../Network'; import { DragManager } from '../../util/DragManager'; @@ -18,11 +21,11 @@ import { undoBatch } from '../../util/UndoManager'; import { CollectionStackedTimeline, TrimScope } from '../collections/CollectionStackedTimeline'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; -import { ViewBoxAnnotatableComponent } from '../DocComponent'; +import { PinProps, ViewBoxAnnotatableComponent } from '../DocComponent'; import './AudioBox.scss'; -import { FocusViewOptions, FieldView, FieldViewProps } from './FieldView'; -import { PinProps, PresBox } from './trails'; import { OpenWhere } from './DocumentView'; +import { FieldView, FieldViewProps } from './FieldView'; +import { PresBox } from './trails'; /** * AudioBox @@ -42,7 +45,7 @@ declare class MediaRecorder { constructor(e: any); // whatever MediaRecorder has } -export enum media_state { +export enum mediaState { PendingRecording = 'pendingRecording', Recording = 'recording', Paused = 'paused', @@ -97,16 +100,16 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { return LinkManager.Links(this.dataDoc); } @computed get mediaState() { - return this.dataDoc.mediaState as media_state; + return this.dataDoc.mediaState as mediaState; + } + set mediaState(value) { + this.dataDoc.mediaState = value; } @computed get path() { // returns the path of the audio file const path = Cast(this.Document[this.fieldKey], AudioField, null)?.url.href || ''; return path === nullAudio ? '' : path; } - set mediaState(value) { - this.dataDoc.mediaState = value; - } @computed get timeline() { return this._stackedTimeline; @@ -117,17 +120,17 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { this._dropDisposer?.(); Object.values(this._disposers).forEach(disposer => disposer?.()); - this.mediaState === media_state.Recording && this.stopRecording(); + this.mediaState === mediaState.Recording && this.stopRecording(); } @action componentDidMount() { this._props.setContentViewBox?.(this); if (this.path) { - this.mediaState = media_state.Paused; + this.mediaState = mediaState.Paused; this.setPlayheadTime(NumCast(this.layoutDoc.clipStart)); } else { - this.mediaState = undefined as any as media_state; + this.mediaState = undefined as any as mediaState; } } @@ -149,7 +152,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { this.Document, this.dataDoc, this.annotationKey, - this._ele?.currentTime || Cast(this.Document._layout_currentTimecode, 'number', null) || (this.mediaState === media_state.Recording ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined), + this._ele?.currentTime || Cast(this.Document._layout_currentTimecode, 'number', null) || (this.mediaState === mediaState.Recording ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined), undefined, undefined, addAsAnnotation @@ -163,10 +166,10 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { // updates timecode and shows it in timeline, follows links at time @action timecodeChanged = () => { - if (this.mediaState !== media_state.Recording && this._ele) { + if (this.mediaState !== mediaState.Recording && this._ele) { this.links .map(l => this.getLinkData(l)) - .forEach(({ la1, la2, linkTime }) => { + .forEach(({ la1, linkTime }) => { if (linkTime > NumCast(this.layoutDoc._layout_currentTimecode) && linkTime < this._ele!.currentTime) { Doc.linkFollowHighlight(la1); } @@ -180,7 +183,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { @action playFrom = (seekTimeInSeconds: number, endTime?: number, fullPlay: boolean = false) => { clearTimeout(this._play); // abort any previous clip ending - if (Number.isNaN(this._ele?.duration)) { + if (isNaN(this._ele?.duration ?? Number.NaN)) { // audio element isn't loaded yet... wait 1/2 second and try again setTimeout(() => this.playFrom(seekTimeInSeconds, endTime), 500); } else if (this.timeline && this._ele && AudioBox.Enabled) { @@ -191,7 +194,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { if (seekTimeInSeconds >= 0 && this.timeline.trimStart <= end && seekTimeInSeconds <= this.timeline.trimEnd) { this._ele.currentTime = start; this._ele.play(); - this.mediaState = media_state.Playing; + this.mediaState = mediaState.Playing; this.addCurrentlyPlaying(); this._play = setTimeout( () => { @@ -233,7 +236,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { // update the recording time updateRecordTime = () => { - if (this.mediaState === media_state.Recording) { + if (this.mediaState === mediaState.Recording) { setTimeout(this.updateRecordTime, 30); if (!this._paused) { this.layoutDoc._layout_currentTimecode = (new Date().getTime() - this._recordStart - this._pausedTime) / 1000; @@ -254,7 +257,9 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { } }; this._recordStart = new Date().getTime(); - runInAction(() => (this.mediaState = media_state.Recording)); + runInAction(() => { + this.mediaState = mediaState.Recording; + }); setTimeout(this.updateRecordTime); this._recorder.start(); setTimeout(this.stopRecording, 60 * 60 * 1000); // stop after an hour @@ -269,7 +274,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { const now = new Date().getTime(); this._paused && (this._pausedTime += now - this._pauseStart); this.dataDoc[this.fieldKey + '_duration'] = (now - this._recordStart - this._pausedTime) / 1000; - this.mediaState = media_state.Paused; + this.mediaState = mediaState.Paused; this._stream?.getAudioTracks()[0].stop(); const ind = DocUtils.ActiveRecordings.indexOf(this); ind !== -1 && DocUtils.ActiveRecordings.splice(ind, 1); @@ -277,26 +282,26 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { }; // context menu - specificContextMenu = (e: React.MouseEvent): void => { + specificContextMenu = (): void => { const funcs: ContextMenuProps[] = []; funcs.push({ description: (this.layoutDoc.hideAnchors ? "Don't hide" : 'Hide') + ' anchors', - event: e => (this.layoutDoc.hideAnchors = !this.layoutDoc.hideAnchors), + event: () => { this.layoutDoc.hideAnchors = !this.layoutDoc.hideAnchors; }, // prettier-ignore icon: 'expand-arrows-alt', }); funcs.push({ description: (this.layoutDoc.dontAutoFollowLinks ? '' : "Don't") + ' follow links when encountered', - event: e => (this.layoutDoc.dontAutoFollowLinks = !this.layoutDoc.dontAutoFollowLinks), + event: () => { this.layoutDoc.dontAutoFollowLinks = !this.layoutDoc.dontAutoFollowLinks}, // prettier-ignore icon: 'expand-arrows-alt', }); funcs.push({ description: (this.layoutDoc.dontAutoPlayFollowedLinks ? '' : "Don't") + ' play when link is selected', - event: e => (this.layoutDoc.dontAutoPlayFollowedLinks = !this.layoutDoc.dontAutoPlayFollowedLinks), + event: () => { this.layoutDoc.dontAutoPlayFollowedLinks = !this.layoutDoc.dontAutoPlayFollowedLinks; }, // prettier-ignore icon: 'expand-arrows-alt', }); funcs.push({ description: (this.layoutDoc.autoPlayAnchors ? "Don't auto" : 'Auto') + ' play anchors onClick', - event: e => (this.layoutDoc.autoPlayAnchors = !this.layoutDoc.autoPlayAnchors), + event: () => { this.layoutDoc.autoPlayAnchors = !this.layoutDoc.autoPlayAnchors; }, // prettier-ignore icon: 'expand-arrows-alt', }); ContextMenu.Instance?.addItem({ @@ -342,9 +347,9 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { } }; - IsPlaying = () => this.mediaState === media_state.Playing; + IsPlaying = () => this.mediaState === mediaState.Playing; TogglePause = () => { - if (this.mediaState === media_state.Paused) this.Play(); + if (this.mediaState === mediaState.Paused) this.Play(); else this.pause(); }; // pause playback without removing from the playback list to allow user to play it again. @@ -352,7 +357,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { pause = () => { if (this._ele) { this._ele.pause(); - this.mediaState = media_state.Paused; + this.mediaState = mediaState.Paused; // if paused in the middle of playback, prevents restart on next play if (!this._finished) clearTimeout(this._play); @@ -434,7 +439,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { }; // plays link - playLink = (link: Doc, options: FocusViewOptions) => { + playLink = (link: Doc /* , options: FocusViewOptions */) => { if (link.annotationOn === this.Document) { if (!this.layoutDoc.dontAutoPlayFollowedLinks) { this.playFrom(this.timeline?.anchorStart(link) || 0, this.timeline?.anchorEnd(link)); @@ -460,13 +465,17 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { }; @action - timelineWhenChildContentsActiveChanged = (isActive: boolean) => this._props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive)); + timelineWhenChildContentsActiveChanged = (isActive: boolean) => { + this._props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive)); + }; timelineScreenToLocal = () => this.ScreenToLocalBoxXf().translate(0, -AudioBox.topControlsHeight); - setPlayheadTime = (time: number) => (this._ele!.currentTime /*= this.layoutDoc._layout_currentTimecode*/ = time); + setPlayheadTime = (time: number) => { + this._ele!.currentTime /* = this.layoutDoc._layout_currentTimecode */ = time; + }; - playing = () => this.mediaState === media_state.Playing; + playing = () => this.mediaState === mediaState.Playing; isActiveChild = () => this._isAnyChildContentActive; @@ -497,7 +506,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { e, returnFalse, returnFalse, - action(e => { + action(() => { if (this.timeline?.IsTrimming !== TrimScope.None) { this.timeline?.CancelTrimming(); } else { @@ -566,7 +575,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { this._dropDisposer = DragManager.MakeDropTarget( r, (e, de) => { - const [xp, yp] = this.ScreenToLocalBoxXf().transformPoint(de.x, de.y); + const [xp] = this.ScreenToLocalBoxXf().transformPoint(de.x, de.y); de.complete.docDragData && this.timeline?.internalDocDrop(e, de, de.complete.docDragData, xp); }, this.layoutDoc @@ -581,7 +590,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { <div className="audiobox-dictation" onPointerDown={this.onFile}> <FontAwesomeIcon size="2x" icon="file-alt" /> </div> - {[media_state.Recording, media_state.Playing].includes(this.mediaState) ? ( + {[mediaState.Recording, mediaState.Playing].includes(this.mediaState) ? ( <div className="recording-controls" onClick={e => e.stopPropagation()}> <div className="record-button" onPointerDown={this.Record}> <FontAwesomeIcon size="2x" icon="stop" /> @@ -614,31 +623,29 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { <div className="controls-left"> <div className="audiobox-button" - title={this.mediaState === media_state.Paused ? 'play' : 'pause'} + title={this.mediaState === mediaState.Paused ? 'play' : 'pause'} onPointerDown={ - this.mediaState === media_state.Paused + this.mediaState === mediaState.Paused ? this.Play : e => { e.stopPropagation(); this.Pause(); } }> - <FontAwesomeIcon icon={this.mediaState === media_state.Paused ? 'play' : 'pause'} size={'1x'} /> + <FontAwesomeIcon icon={this.mediaState === mediaState.Paused ? 'play' : 'pause'} size="1x" /> </div> {!this.miniPlayer && ( <> <Tooltip title={<>trim audio</>}> <div className="audiobox-button" onPointerDown={this.onClipPointerDown}> - <FontAwesomeIcon icon={this.timeline?.IsTrimming !== TrimScope.None ? 'check' : 'cut'} size={'1x'} /> + <FontAwesomeIcon icon={this.timeline?.IsTrimming !== TrimScope.None ? 'check' : 'cut'} size="1x" /> </div> </Tooltip> - {this.timeline?.IsTrimming == TrimScope.None && !NumCast(this.layoutDoc.clipStart) && NumCast(this.layoutDoc.clipEnd) === this.rawDuration ? ( - <></> - ) : ( - <Tooltip title={<>{this.timeline?.IsTrimming !== TrimScope.None ? 'Cancel trimming' : 'Edit original timeline'}</>}> + {this.timeline?.IsTrimming === TrimScope.None && !NumCast(this.layoutDoc.clipStart) && NumCast(this.layoutDoc.clipEnd) === this.rawDuration ? null : ( + <Tooltip title={this.timeline?.IsTrimming !== TrimScope.None ? 'Cancel trimming' : 'Edit original timeline'}> <div className="audiobox-button" onPointerDown={this.onResetPointerDown}> - <FontAwesomeIcon icon={this.timeline?.IsTrimming !== TrimScope.None ? 'cancel' : 'arrows-left-right'} size={'1x'} /> + <FontAwesomeIcon icon={this.timeline?.IsTrimming !== TrimScope.None ? 'cancel' : 'arrows-left-right'} size="1x" /> </div> </Tooltip> )} @@ -705,9 +712,11 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { @computed get renderTimeline() { return ( <CollectionStackedTimeline - ref={action((r: CollectionStackedTimeline | null) => (this._stackedTimeline = r))} + ref={action((r: CollectionStackedTimeline | null) => { + this._stackedTimeline = r; + })} + // eslint-disable-next-line react/jsx-props-no-spreading {...this._props} - CollectionFreeFormDocumentView={undefined} dataFieldKey={this.fieldKey} fieldKey={this.annotationKey} dictationKey={this.fieldKey + '_dictation'} @@ -738,10 +747,13 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { // returns the html audio element @computed get audio() { return ( + // eslint-disable-next-line jsx-a11y/media-has-caption <audio ref={this.setRef} className={`audiobox-control${this._props.isContentActive() ? '-interactive' : ''}`} - onLoadedData={action(e => this._ele?.duration && this._ele?.duration !== Infinity && (this.dataDoc[this.fieldKey + '_duration'] = this._ele.duration))}> + onLoadedData={action(() => { + this._ele?.duration && this._ele?.duration !== Infinity && (this.dataDoc[this.fieldKey + '_duration'] = this._ele.duration); + })}> <source src={this.path} type="audio/mpeg" /> Not supported. </audio> diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 0d0a7c623..958d63267 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -1,8 +1,10 @@ -import { action, makeObservable, observable, trace } from 'mobx'; +import { action, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { OmitKeys, numberRange } from '../../../Utils'; +import { OmitKeys } from '../../../ClientUtils'; +import { numberRange } from '../../../Utils'; import { Doc, DocListCast, Opt } from '../../../fields/Doc'; +import { TransitionTimer } from '../../../fields/DocSymbols'; import { List } from '../../../fields/List'; import { listSpec } from '../../../fields/Schema'; import { ComputedField } from '../../../fields/ScriptField'; @@ -17,7 +19,6 @@ import { CollectionFreeFormView } from '../collections/collectionFreeForm/Collec import './CollectionFreeFormDocumentView.scss'; import { DocumentView, DocumentViewProps, OpenWhere } from './DocumentView'; import { FieldViewProps } from './FieldView'; -import { TransitionTimer } from '../../../fields/DocSymbols'; /// Ugh, typescript has no run-time way of iterating through the keys of an interface. so we need /// manaully keep this list of keys in synch wih the fields of the freeFormProps interface @@ -239,6 +240,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF const isGroup = this.dataDoc.isGroup && (!backColor || backColor === 'transparent'); return isGroup ? (this._props.isDocumentActive?.() ? 'group' : this._props.isGroupActive?.() ? 'child' : 'inactive') : this._props.isGroupActive?.() ? 'child' : undefined; }; + localRotation = () => this._props.rotation; render() { TraceMobx(); @@ -259,6 +261,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF <DocumentView {...OmitKeys(this._props,this.WrapperKeys.map(val => val.lower)).omit} // prettier-ignore DataTransition={this.DataTransition} + LocalRotation={this.localRotation} CollectionFreeFormDocumentView={this.returnThis} styleProvider={this.styleProvider} ScreenToLocalTransform={this.screenToLocalTransform} diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index 9ffdc350d..99f9c03bf 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -1,22 +1,24 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, makeObservable, observable } from 'mobx'; +import { action, computed, makeObservable, observable, trace } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { emptyFunction, returnFalse, returnNone, returnZero, setupMoveUpEvents } from '../../../Utils'; +import { returnFalse, returnNone, returnZero, setupMoveUpEvents } from '../../../ClientUtils'; +import { emptyFunction } from '../../../Utils'; import { Doc, Opt } from '../../../fields/Doc'; import { RichTextField } from '../../../fields/RichTextField'; import { DocCast, NumCast, RTFCast, StrCast } from '../../../fields/Types'; import { DocUtils, Docs } from '../../documents/Documents'; -import { DragManager, dropActionType } from '../../util/DragManager'; +import { DragManager } from '../../util/DragManager'; +import { dropActionType } from '../../util/DropActionTypes'; import { undoBatch } from '../../util/UndoManager'; -import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent'; +import { PinProps, ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent'; import { StyleProp } from '../StyleProvider'; import './ComparisonBox.scss'; import { DocumentView } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; import { KeyValueBox } from './KeyValueBox'; import { FormattedTextBox } from './formattedText/FormattedTextBox'; -import { PinProps, PresBox } from './trails'; +import { PresBox } from './trails'; @observer export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface { @@ -164,7 +166,8 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() * @returns a JSX layout string if a text field is found, othwerise undefined */ testForTextFields = (whichSlot: string) => { - const slotHasText = Doc.Get(this.dataDoc, whichSlot, true) instanceof RichTextField || typeof Doc.Get(this.dataDoc, whichSlot, true) === 'string'; + const slotData = Doc.Get(this.dataDoc, whichSlot, true); + const slotHasText = slotData instanceof RichTextField || typeof slotData === 'string'; const subjectText = RTFCast(this.Document[this.fieldKey])?.Text.trim(); const altText = RTFCast(this.Document[this.fieldKey + '_alternate'])?.Text.trim(); const layoutTemplateString = @@ -180,8 +183,8 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() // where (this) is replaced by the text in the fieldKey slot abd this.excludeWords is repalced by the conetnts of the excludeWords field // The GPT call will put the "answer" in the second slot of the comparison (eg., text_2) if (whichSlot.endsWith('2') && !layoutTemplateString?.includes(whichSlot)) { - var queryText = altText?.replace('(this)', subjectText); // TODO: this should be done in KeyValueBox.setField but it doesn't know about the fieldKey ... - if (queryText && queryText.match(/\(\(.*\)\)/)) { + const queryText = altText?.replace('(this)', subjectText); // TODO: this should be done in KeyValueBox.setField but it doesn't know about the fieldKey ... + if (queryText?.match(/\(\(.*\)\)/)) { KeyValueBox.SetField(this.Document, whichSlot, ':=' + queryText, false); // make the second slot be a computed field on the data doc that calls ChatGpt } } @@ -190,17 +193,16 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() _closeRef = React.createRef<HTMLDivElement>(); render() { - const clearButton = (which: string) => { - return ( - <div - ref={this._closeRef} - className={`clear-button ${which}`} - onPointerDown={e => this.closeDown(e, which)} // prevent triggering slider movement in registerSliding - > - <FontAwesomeIcon className={`clear-button ${which}`} icon="times" size="sm" /> - </div> - ); - }; + trace(); + const clearButton = (which: string) => ( + <div + ref={this._closeRef} + className={`clear-button ${which}`} + onPointerDown={e => this.closeDown(e, which)} // prevent triggering slider movement in registerSliding + > + <FontAwesomeIcon className={`clear-button ${which}`} icon="times" size="sm" /> + </div> + ); /** * Display the Docs in the before/after fields of the comparison. This also supports a GPT flash card use case @@ -229,7 +231,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() isDocumentActive={returnFalse} whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} styleProvider={this._isAnyChildContentActive ? this._props.styleProvider : this.docStyleProvider} - hideLinkButton={true} + hideLinkButton pointerEvents={this._isAnyChildContentActive ? undefined : returnNone} /> {layoutTemplateString ? null : clearButton(whichSlot)} @@ -240,13 +242,11 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() </div> ); }; - const displayBox = (which: string, index: number, cover: number) => { - return ( - <div className={`${index === 0 ? 'before' : 'after'}Box-cont`} key={which} style={{ width: this._props.PanelWidth() }} onPointerDown={e => this.registerSliding(e, cover)} ref={ele => this.createDropTarget(ele, which, index)}> - {displayDoc(which)} - </div> - ); - }; + const displayBox = (which: string, index: number, cover: number) => ( + <div className={`${index === 0 ? 'before' : 'after'}Box-cont`} key={which} style={{ width: this._props.PanelWidth() }} onPointerDown={e => this.registerSliding(e, cover)} ref={ele => this.createDropTarget(ele, which, index)}> + {displayDoc(which)} + </div> + ); return ( <div className={`comparisonBox${this._props.isContentActive() ? '-interactive' : ''}` /* change className to easily disable/enable pointer events in CSS */}> diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index 22f1f7b79..2a0bc42ee 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -1,9 +1,11 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Checkbox } from '@mui/material'; import { Colors, Toggle, ToggleType, Type } from 'browndash-components'; import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { emptyFunction, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents } from '../../../../Utils'; +import { returnEmptyString, returnFalse, returnOne, setupMoveUpEvents } from '../../../../ClientUtils'; +import { emptyFunction } from '../../../../Utils'; import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../../fields/Doc'; import { InkTool } from '../../../../fields/InkField'; import { List } from '../../../../fields/List'; @@ -14,20 +16,18 @@ import { TraceMobx } from '../../../../fields/util'; import { DocUtils, Docs } from '../../../documents/Documents'; import { DocumentManager } from '../../../util/DocumentManager'; import { UndoManager, undoable } from '../../../util/UndoManager'; -import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent'; +import { PinProps, ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent'; import { MarqueeAnnotator } from '../../MarqueeAnnotator'; import { SidebarAnnos } from '../../SidebarAnnos'; import { AnchorMenu } from '../../pdf/AnchorMenu'; import { GPTPopup } from '../../pdf/GPTPopup/GPTPopup'; import { DocumentView } from '../DocumentView'; -import { FocusViewOptions, FieldView, FieldViewProps } from '../FieldView'; -import { PinProps } from '../trails'; +import { FieldView, FieldViewProps, FocusViewOptions } from '../FieldView'; import './DataVizBox.scss'; import { Histogram } from './components/Histogram'; import { LineChart } from './components/LineChart'; import { PieChart } from './components/PieChart'; import { TableBox } from './components/TableBox'; -import { Checkbox } from '@mui/material'; export enum DataVizView { TABLE = 'table', diff --git a/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx b/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx index 24023077f..0084d7394 100644 --- a/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx +++ b/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx @@ -3,7 +3,8 @@ import { action, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { CgClose } from 'react-icons/cg'; -import { Utils, emptyFunction, setupMoveUpEvents } from '../../../../Utils'; +import { ClientUtils, setupMoveUpEvents } from '../../../../ClientUtils'; +import { emptyFunction } from '../../../../Utils'; import { Doc } from '../../../../fields/Doc'; import { StrCast } from '../../../../fields/Types'; import { DragManager } from '../../../util/DragManager'; @@ -84,7 +85,7 @@ export class SchemaCSVPopUp extends React.Component<SchemaCSVPopUpProps> { const embedding = Doc.MakeEmbedding(this.dataVizDoc!); return embedding; }; - if (this.view && sourceAnchorCreator && !Utils.isClick(e.clientX, e.clientY, downX, downY, Date.now())) { + if (this.view && sourceAnchorCreator && !ClientUtils.isClick(e.clientX, e.clientY, downX, downY, Date.now())) { DragManager.StartAnchorAnnoDrag(e.target instanceof HTMLElement ? [e.target] : [], new DragManager.AnchorAnnoDragData(this.view, sourceAnchorCreator, targetCreator), downX, downY, { dragComplete: e => { this.setVisible(false); diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index 6672603f3..58cacef76 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -11,8 +11,9 @@ import { listSpec } from '../../../../../fields/Schema'; import { Cast, DocCast, StrCast } from '../../../../../fields/Types'; import { Docs } from '../../../../documents/Documents'; import { undoable } from '../../../../util/UndoManager'; +import { PinProps } from '../../../DocComponent'; import { ObservableReactComponent } from '../../../ObservableReactComponent'; -import { PinProps, PresBox } from '../../trails'; +import { PresBox } from '../../trails'; import { scaleCreatorNumerical, yAxisCreator } from '../utils/D3Utils'; import './Chart.scss'; @@ -64,7 +65,7 @@ export class Histogram extends ObservableReactComponent<HistogramProps> { if (this._props.axes.length < 1) return []; if (this._props.axes.length < 2) { var ax0 = this._props.axes[0]; - if (!/[A-Za-z-:]/.test(this._props.records[0][ax0])){ + if (!/[A-Za-z-:]/.test(this._props.records[0][ax0])) { this.numericalXData = true; } return this._tableData.map(record => ({ [ax0]: record[this._props.axes[0]] })); @@ -132,7 +133,7 @@ export class Histogram extends ObservableReactComponent<HistogramProps> { // cleans data by converting numerical data to numbers and taking out empty cells data = (dataSet: any) => { - var validData = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || Number.isNaN(d[key]))); + var validData = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || isNaN(d[key]))); const field = dataSet[0] ? Object.keys(dataSet[0])[0] : undefined; return !field ? [] @@ -191,7 +192,7 @@ export class Histogram extends ObservableReactComponent<HistogramProps> { const endingPoint = this.numericalXData ? this.rangeVals.xMax! : numBins; // converts data into Objects - var histDataSet = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || Number.isNaN(d[key]))); + var histDataSet = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || isNaN(d[key]))); if (!this.numericalXData) { var histStringDataSet: { [x: string]: unknown }[] = []; if (this.numericalYData) { @@ -452,17 +453,16 @@ export class Histogram extends ObservableReactComponent<HistogramProps> { : '' ); selected = selected.substring(0, selected.length - 2) + ' }'; - if (this._props.titleCol!="" && (!this._currSelected["frequency"] || this._currSelected["frequency"]<10)){ - selected+= "\n" + this._props.titleCol + ": " + if (this._props.titleCol != '' && (!this._currSelected['frequency'] || this._currSelected['frequency'] < 10)) { + selected += '\n' + this._props.titleCol + ': '; this._tableData.forEach(each => { - if (this._currSelected[this._props.axes[0]]==each[this._props.axes[0]]) { - if (this._props.axes[1]){ - if (this._currSelected[this._props.axes[1]]==each[this._props.axes[1]]) selected+= each[this._props.titleCol] + ", "; - } - else selected+= each[this._props.titleCol] + ", "; + if (this._currSelected[this._props.axes[0]] == each[this._props.axes[0]]) { + if (this._props.axes[1]) { + if (this._currSelected[this._props.axes[1]] == each[this._props.axes[1]]) selected += each[this._props.titleCol] + ', '; + } else selected += each[this._props.titleCol] + ', '; } - }) - selected = selected.slice(0,-1).slice(0,-1); + }); + selected = selected.slice(0, -1).slice(0, -1); } } var selectedBarColor; diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx index e093ec648..c667a15de 100644 --- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx @@ -11,10 +11,11 @@ import { Docs } from '../../../../documents/Documents'; import { DocumentManager } from '../../../../util/DocumentManager'; import { undoable } from '../../../../util/UndoManager'; import { ObservableReactComponent } from '../../../ObservableReactComponent'; -import { PinProps, PresBox } from '../../trails'; +import { PresBox } from '../../trails'; import { DataVizBox } from '../DataVizBox'; import { createLineGenerator, drawLine, minMaxRange, scaleCreatorNumerical, xAxisCreator, xGrid, yAxisCreator, yGrid } from '../utils/D3Utils'; import './Chart.scss'; +import { PinProps } from '../../../DocComponent'; export interface DataPoint { x: number; @@ -258,17 +259,18 @@ export class LineChart extends ObservableReactComponent<LineChartProps> { .attr('transform', `translate(${margin.left}, ${margin.top})`)); var validSecondData; - if (this._props.axes.length>2){ // for when there are 2 lines on the chart + if (this._props.axes.length > 2) { + // for when there are 2 lines on the chart var next = this._tableData.map(record => ({ x: Number(record[this._props.axes[0]]), y: Number(record[this._props.axes[2]]) })).sort((a, b) => (a.x < b.x ? -1 : 1)); validSecondData = next.filter(d => { - if (!d.x || Number.isNaN(d.x) || !d.y || Number.isNaN(d.y)) return false; + if (!d.x || isNaN(d.x) || !d.y || isNaN(d.y)) return false; return true; }); var secondDataRange = minMaxRange([validSecondData]); - if (secondDataRange.xMax!>xMax) xMax = secondDataRange.xMax; - if (secondDataRange.yMax!>yMax) yMax = secondDataRange.yMax; - if (secondDataRange.xMin!<xMin) xMin = secondDataRange.xMin; - if (secondDataRange.yMin!<yMin) yMin = secondDataRange.yMin; + if (secondDataRange.xMax! > xMax) xMax = secondDataRange.xMax; + if (secondDataRange.yMax! > yMax) yMax = secondDataRange.yMax; + if (secondDataRange.xMin! < xMin) xMin = secondDataRange.xMin; + if (secondDataRange.yMin! < yMin) yMin = secondDataRange.yMin; } // creating the x and y scales @@ -285,37 +287,45 @@ export class LineChart extends ObservableReactComponent<LineChartProps> { if (validSecondData) { drawLine(svg.append('path'), validSecondData, lineGen, true); this.drawDataPoints(validSecondData, 0, xScale, yScale); - svg.append('path').attr("stroke", "red"); + svg.append('path').attr('stroke', 'red'); // legend - var color = d3.scaleOrdinal() - .range(["black", "blue"]) - .domain([this._props.axes[1], this._props.axes[2]]) - svg.selectAll("mydots") + var color = d3.scaleOrdinal().range(['black', 'blue']).domain([this._props.axes[1], this._props.axes[2]]); + svg.selectAll('mydots') .data([this._props.axes[1], this._props.axes[2]]) .enter() - .append("circle") - .attr("cx", 5) - .attr("cy", function(d,i){ return -30 + i*15}) - .attr("r", 7) - .style("fill", function(d){ return color(d)}) - svg.selectAll("mylabels") + .append('circle') + .attr('cx', 5) + .attr('cy', function (d, i) { + return -30 + i * 15; + }) + .attr('r', 7) + .style('fill', function (d) { + return color(d); + }); + svg.selectAll('mylabels') .data([this._props.axes[1], this._props.axes[2]]) .enter() - .append("text") - .attr("x", 25) - .attr("y", function(d,i){ return -30 + i*15}) - .style("fill", function(d){ return color(d)}) - .text(function(d){ return d}) - .attr("text-anchor", "left") - .style("alignment-baseline", "middle") + .append('text') + .attr('x', 25) + .attr('y', function (d, i) { + return -30 + i * 15; + }) + .style('fill', function (d) { + return color(d); + }) + .text(function (d) { + return d; + }) + .attr('text-anchor', 'left') + .style('alignment-baseline', 'middle'); } // get valid data points const data = dataSet[0]; var validData = data.filter(d => { Object.keys(data[0]).map(key => { - if (!d[key] || Number.isNaN(d[key])) return false; + if (!d[key] || isNaN(d[key])) return false; }); return true; }); @@ -399,16 +409,16 @@ export class LineChart extends ObservableReactComponent<LineChartProps> { else if (this._props.axes.length > 0) titleAccessor = titleAccessor + this._props.axes[0]; if (!this._props.layoutDoc[titleAccessor]) this._props.layoutDoc[titleAccessor] = this.defaultGraphTitle; const selectedPt = this._currSelected ? `{ ${this._props.axes[0]}: ${this._currSelected.x} ${this._props.axes[1]}: ${this._currSelected.y} }` : 'none'; - var selectedTitle = ""; - if (this._currSelected && this._props.titleCol){ - selectedTitle+= "\n" + this._props.titleCol + ": " + var selectedTitle = ''; + if (this._currSelected && this._props.titleCol) { + selectedTitle += '\n' + this._props.titleCol + ': '; this._tableData.forEach(each => { - var mapThisEntry = false; - if (this._currSelected.x==each[this._props.axes[0]] && this._currSelected.y==each[this._props.axes[1]]) mapThisEntry = true; - else if (this._currSelected.y==each[this._props.axes[0]] && this._currSelected.x==each[this._props.axes[1]]) mapThisEntry = true; - if (mapThisEntry) selectedTitle += each[this._props.titleCol] + ", "; - }) - selectedTitle = selectedTitle.slice(0,-1).slice(0,-1); + var mapThisEntry = false; + if (this._currSelected.x == each[this._props.axes[0]] && this._currSelected.y == each[this._props.axes[1]]) mapThisEntry = true; + else if (this._currSelected.y == each[this._props.axes[0]] && this._currSelected.x == each[this._props.axes[1]]) mapThisEntry = true; + if (mapThisEntry) selectedTitle += each[this._props.titleCol] + ', '; + }); + selectedTitle = selectedTitle.slice(0, -1).slice(0, -1); } if (this._lineChartData.length > 0 || !this.parentViz || this.parentViz.length == 0) { return this._props.axes.length >= 2 && /\d/.test(this._props.records[0][this._props.axes[0]]) && /\d/.test(this._props.records[0][this._props.axes[1]]) ? ( diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx index fc23f47de..2735a40d5 100644 --- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx @@ -12,8 +12,9 @@ import { Cast, DocCast, StrCast } from '../../../../../fields/Types'; import { Docs } from '../../../../documents/Documents'; import { undoable } from '../../../../util/UndoManager'; import { ObservableReactComponent } from '../../../ObservableReactComponent'; -import { PinProps, PresBox } from '../../trails'; +import { PresBox } from '../../trails'; import './Chart.scss'; +import { PinProps } from '../../../DocComponent'; export interface PieChartProps { Document: Doc; @@ -122,7 +123,7 @@ export class PieChart extends ObservableReactComponent<PieChartProps> { // cleans data by converting numerical data to numbers and taking out empty cells data = (dataSet: any) => { - const validData = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || Number.isNaN(d[key]))); + const validData = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || isNaN(d[key]))); const field = dataSet[0] ? Object.keys(dataSet[0])[0] : undefined; return !field ? undefined @@ -192,7 +193,7 @@ export class PieChart extends ObservableReactComponent<PieChartProps> { // converts data into Objects var data = this.data(dataSet); - var pieDataSet = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || Number.isNaN(d[key]))); + var pieDataSet = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || isNaN(d[key]))); if (this.byCategory) { let uniqueCategories = [...new Set(data)]; var pieStringDataSet: { frequency: number }[] = []; @@ -348,17 +349,16 @@ export class PieChart extends ObservableReactComponent<PieChartProps> { }); selected = selected.substring(0, selected.length - 2); selected += ' }'; - if (this._props.titleCol!="" && (!this._currSelected["frequency"] || this._currSelected["frequency"]<10)){ - selected+= "\n" + this._props.titleCol + ": " + if (this._props.titleCol != '' && (!this._currSelected['frequency'] || this._currSelected['frequency'] < 10)) { + selected += '\n' + this._props.titleCol + ': '; this._tableData.forEach(each => { - if (this._currSelected[this._props.axes[0]]==each[this._props.axes[0]]) { - if (this._props.axes[1]){ - if (this._currSelected[this._props.axes[1]]==each[this._props.axes[1]]) selected+= each[this._props.titleCol] + ", "; - } - else selected+= each[this._props.titleCol] + ", "; + if (this._currSelected[this._props.axes[0]] == each[this._props.axes[0]]) { + if (this._props.axes[1]) { + if (this._currSelected[this._props.axes[1]] == each[this._props.axes[1]]) selected += each[this._props.titleCol] + ', '; + } else selected += each[this._props.titleCol] + ', '; } - }) - selected = selected.slice(0,-1).slice(0,-1); + }); + selected = selected.slice(0, -1).slice(0, -1); } } else selected = 'none'; var selectedSliceColor; diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx index 53d1869d9..15959c61d 100644 --- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx +++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx @@ -2,7 +2,8 @@ import { Button, Type } 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, setupMoveUpEvents } from '../../../../../Utils'; +import { ClientUtils, setupMoveUpEvents } from '../../../../../ClientUtils'; +import { emptyFunction } from '../../../../../Utils'; import { Doc, Field, NumListCast } from '../../../../../fields/Doc'; import { List } from '../../../../../fields/List'; import { listSpec } from '../../../../../fields/Schema'; @@ -137,7 +138,7 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> { embedding.pieSliceColors = Field.Copy(this._props.layoutDoc.pieSliceColors); return embedding; }; - if (this._props.docView?.() && !Utils.isClick(e.clientX, e.clientY, downX, downY, Date.now())) { + if (this._props.docView?.() && !ClientUtils.isClick(e.clientX, e.clientY, downX, downY, Date.now())) { DragManager.StartAnchorAnnoDrag(e.target instanceof HTMLElement ? [e.target] : [], new DragManager.AnchorAnnoDragData(this._props.docView()!, sourceAnchorCreator, targetCreator), downX, downY, { dragComplete: e => { if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) { diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index e729e2fa2..518158a7f 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -3,7 +3,8 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import JsxParser from 'react-jsx-parser'; import * as XRegExp from 'xregexp'; -import { OmitKeys, Without, emptyPath } from '../../../Utils'; +import { OmitKeys } from '../../../ClientUtils'; +import { Without, emptyPath } from '../../../Utils'; import { Doc, Opt } from '../../../fields/Doc'; import { AclPrivate, DocData } from '../../../fields/DocSymbols'; import { ScriptField } from '../../../fields/ScriptField'; @@ -20,7 +21,6 @@ import { SchemaRowBox } from '../collections/collectionSchema/SchemaRowBox'; import { PresElementBox } from '../nodes/trails/PresElementBox'; import { SearchBox } from '../search/SearchBox'; import { DashWebRTCVideo } from '../webcam/DashWebRTCVideo'; -import { YoutubeBox } from './../../apis/youtube/YoutubeBox'; import { AudioBox } from './AudioBox'; import { ComparisonBox } from './ComparisonBox'; import { DataVizBox } from './DataVizBox/DataVizBox'; @@ -250,7 +250,6 @@ export class DocumentContentsView extends ObservableReactComponent<DocumentConte AudioBox, RecordingBox, PresBox, - YoutubeBox, PresElementBox, SearchBox, FunctionPlotBox, diff --git a/src/client/views/nodes/DocumentIcon.tsx b/src/client/views/nodes/DocumentIcon.tsx index 4a22766cc..364406197 100644 --- a/src/client/views/nodes/DocumentIcon.tsx +++ b/src/client/views/nodes/DocumentIcon.tsx @@ -3,11 +3,12 @@ import { action, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { factory } from 'typescript'; -import { Field } from '../../../fields/Doc'; +import { FieldType } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; +import { StrCast } from '../../../fields/Types'; import { DocumentManager } from '../../util/DocumentManager'; import { Transformer, ts } from '../../util/Scripting'; -import { SettingsManager } from '../../util/SettingsManager'; +import { SnappingManager } from '../../util/SnappingManager'; import { LightboxView } from '../LightboxView'; import { ObservableReactComponent } from '../ObservableReactComponent'; import { DocumentView } from './DocumentView'; @@ -28,22 +29,22 @@ export class DocumentIcon extends ObservableReactComponent<DocumentIconProps> { return LightboxView.LightboxDoc ? DocumentManager.Instance.DocumentViews.filter(v => LightboxView.Contains(v)) : DocumentManager.Instance.DocumentViews; } render() { - const view = this._props.view; - const { left, top, right, bottom } = view.getBounds || { left: 0, top: 0, right: 0, bottom: 0 }; + const { view } = this._props; + const { left, top, right } = view.getBounds || { left: 0, top: 0, right: 0, bottom: 0 }; return ( <div className="documentIcon-outerDiv" - onPointerEnter={action(e => (this._hovered = true))} - onPointerLeave={action(e => (this._hovered = false))} + onPointerEnter={action(() => { this._hovered = true; })} // prettier-ignore + onPointerLeave={action(() => { this._hovered = false; })} // prettier-ignore style={{ pointerEvents: 'all', opacity: this._hovered ? 0.3 : 1, position: 'absolute', - background: SettingsManager.userBackgroundColor, + background: SnappingManager.userBackgroundColor, transform: `translate(${(left + right) / 2}px, ${top}px)`, }}> - <Tooltip title={<>{this._props.view.Document.title}</>}> + <Tooltip title={<div>{StrCast(this._props.view.Document?.title)}</div>}> <p>d{this._props.index}</p> </Tooltip> </div> @@ -56,40 +57,40 @@ export class DocumentIconContainer extends React.Component { public static getTransformer(): Transformer { const usedDocuments = new Set<number>(); return { - transformer: context => { - return root => { - function visit(node: ts.Node) { - node = ts.visitEachChild(node, visit, context); + transformer: context => root => { + function visit(nodeIn: ts.Node) { + const node = ts.visitEachChild(nodeIn, visit, context); - if (ts.isIdentifier(node)) { - const isntPropAccess = !ts.isPropertyAccessExpression(node.parent) || node.parent.expression === node; - const isntPropAssign = !ts.isPropertyAssignment(node.parent) || node.parent.name !== node; - const isntParameter = !ts.isParameter(node.parent); - if (isntPropAccess && isntPropAssign && isntParameter && !(node.text in globalThis)) { - const match = node.text.match(/d([0-9]+)/); - if (match) { - const m = parseInt(match[1]); - const doc = DocumentIcon.DocViews[m].Document; - usedDocuments.add(m); - return factory.createIdentifier(`idToDoc("${doc[Id]}")`); - } + if (ts.isIdentifier(node)) { + const isntPropAccess = !ts.isPropertyAccessExpression(node.parent) || node.parent.expression === node; + const isntPropAssign = !ts.isPropertyAssignment(node.parent) || node.parent.name !== node; + const isntParameter = !ts.isParameter(node.parent); + if (isntPropAccess && isntPropAssign && isntParameter && !(node.text in globalThis)) { + const match = node.text.match(/d([0-9]+)/); + if (match) { + const m = parseInt(match[1]); + const doc = DocumentIcon.DocViews[m].Document; + usedDocuments.add(m); + return factory.createIdentifier(`idToDoc("${doc[Id]}")`); } } - - return node; } - return ts.visitNode(root, visit); - }; + + return node; + } + return ts.visitNode(root, visit); }, getVars() { const docs = DocumentIcon.DocViews; - const capturedVariables: { [name: string]: Field } = {}; - usedDocuments.forEach(index => (capturedVariables[`d${index}`] = docs.length > index ? docs[index].Document : `d${index}`)); + const capturedVariables: { [name: string]: FieldType } = {}; + usedDocuments.forEach(index => { + capturedVariables[`d${index}`] = docs.length > index ? docs[index].Document : `d${index}`; + }); return capturedVariables; }, }; } render() { - return DocumentIcon.DocViews.map((dv, i) => <DocumentIcon key={i} index={i} view={dv} />); + return DocumentIcon.DocViews.map((dv, i) => <DocumentIcon key={dv.DocUniqueId} index={i} view={dv} />); } } diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index 2a68d2bf6..d378082f8 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -3,21 +3,22 @@ import { Tooltip } from '@mui/material'; import { action, computed, makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { StopEvent, emptyFunction, returnFalse, setupMoveUpEvents } from '../../../Utils'; +import { StopEvent, returnFalse, setupMoveUpEvents } from '../../../ClientUtils'; +import { emptyFunction } from '../../../Utils'; import { Doc } from '../../../fields/Doc'; +import { DocData } from '../../../fields/DocSymbols'; import { StrCast } from '../../../fields/Types'; import { DocUtils } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; import { Hypothesis } from '../../util/HypothesisUtils'; import { LinkManager } from '../../util/LinkManager'; import { UndoManager, undoBatch } from '../../util/UndoManager'; +import { PinProps } from '../DocComponent'; import { ObservableReactComponent } from '../ObservableReactComponent'; import './DocumentLinksButton.scss'; import { DocumentView } from './DocumentView'; import { LinkDescriptionPopup } from './LinkDescriptionPopup'; import { TaskCompletionBox } from './TaskCompletedBox'; -import { PinProps } from './trails'; -import { DocData } from '../../../fields/DocSymbols'; interface DocumentLinksButtonProps { View: DocumentView; @@ -151,7 +152,7 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB DocumentLinksButton.StartLinkView = undefined; DocumentLinksButton.AnnotationId = undefined; DocumentLinksButton.AnnotationUri = undefined; - //!this._props.StartLink + // !this._props.StartLink } else if (startLink !== endLink) { endLink = endLinkView?.ComponentView?.getAnchor?.(true, pinProps) || endLink; startLink = DocumentLinksButton.StartLinkView?.ComponentView?.getAnchor?.(true) || startLink; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index ee7bbbdba..dbd2ebe0d 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,11 +1,15 @@ +/* eslint-disable no-use-before-define */ +/* eslint-disable react/jsx-props-no-spreading */ +/* eslint-disable jsx-a11y/no-static-element-interactions */ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { Howl } from 'howler'; import { IReactionDisposer, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Bounce, Fade, Flip, JackInTheBox, Roll, Rotate, Zoom } from 'react-awesome-reveal'; -import { DivWidth, Utils, emptyFunction, isTargetChildOf as isParentOf, lightOrDark, returnEmptyString, returnFalse, returnTrue, returnVal, simulateMouseClick } from '../../../Utils'; -import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../fields/Doc'; +import { ClientUtils, DivWidth, isTargetChildOf as isParentOf, lightOrDark, returnEmptyString, returnFalse, returnTrue, returnVal, simulateMouseClick } from '../../../ClientUtils'; +import { Utils, emptyFunction, emptyPath } from '../../../Utils'; +import { Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../fields/Doc'; import { AclPrivate, Animation, AudioPlay, DocData, DocViews } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; @@ -23,14 +27,14 @@ import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes' import { DocUtils, Docs } from '../../documents/Documents'; import { DictationManager } from '../../util/DictationManager'; import { DocumentManager } from '../../util/DocumentManager'; -import { DragManager, dropActionType } from '../../util/DragManager'; +import { DragManager } from '../../util/DragManager'; +import { dropActionType } from '../../util/DropActionTypes'; import { MakeTemplate, makeUserTemplateButton } from '../../util/DropConverter'; import { FollowLinkScript } from '../../util/LinkFollower'; -import { LinkManager } from '../../util/LinkManager'; +import { LinkManager, UPDATE_SERVER_CACHE } from '../../util/LinkManager'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SearchUtil } from '../../util/SearchUtil'; import { SelectionManager } from '../../util/SelectionManager'; -import { SettingsManager } from '../../util/SettingsManager'; import { SharingManager } from '../../util/SharingManager'; import { SnappingManager } from '../../util/SnappingManager'; import { UndoManager, undoBatch, undoable } from '../../util/UndoManager'; @@ -42,6 +46,7 @@ import { FieldsDropdown } from '../FieldsDropdown'; import { GestureOverlay } from '../GestureOverlay'; import { LightboxView } from '../LightboxView'; import { AudioAnnoState, StyleProp } from '../StyleProvider'; +import { CollectionFreeFormDocumentView } from './CollectionFreeFormDocumentView'; import { DocumentContentsView, ObserverJsxParser } from './DocumentContentsView'; import { DocumentLinksButton } from './DocumentLinksButton'; import './DocumentView.scss'; @@ -50,12 +55,6 @@ import { KeyValueBox } from './KeyValueBox'; import { LinkAnchorBox } from './LinkAnchorBox'; import { FormattedTextBox } from './formattedText/FormattedTextBox'; import { PresEffect, PresEffectDirection } from './trails'; -interface Window { - MediaRecorder: MediaRecorder; -} -declare class MediaRecorder { - constructor(e: any); // whatever MediaRecorder has -} export enum OpenWhereMod { none = '', @@ -83,9 +82,6 @@ export enum OpenWhere { addRightKeyvalue = 'add:right:keyValue', } -export function returnEmptyDocViewList() { - return [] as DocumentView[]; -} export interface DocumentViewProps extends FieldViewSharedProps { hideDecorations?: boolean; // whether to suppress all DocumentDecorations when doc is selected hideResizeHandles?: boolean; // whether to suppress resized handles on doc decorations when this document is selected @@ -183,12 +179,12 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document @computed get disableClickScriptFunc() { const onScriptDisable = this._props.onClickScriptDisable ?? this._componentView?.onClickScriptDisable?.() ?? this.layoutDoc.onClickScriptDisable; - // prettier-ignore return ( + // eslint-disable-next-line no-use-before-define DocumentView.LongPress || onScriptDisable === 'always' || (onScriptDisable !== 'never' && (this.rootSelected() || this._componentView?.isAnyChildContentActive?.())) - ); + ); // prettier-ignore } @computed get _rootSelected() { return this._props.isSelected() || BoolCast(this._props.TemplateDataDocument && this._props.rootSelected?.()); @@ -237,7 +233,9 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document } componentDidMount() { - runInAction(() => (this._mounted = true)); + runInAction(() => { + this._mounted = true; + }); this.setupHandlers(); this._disposers.contentActive = reaction( () => @@ -249,19 +247,23 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document : Doc.ActiveTool !== InkTool.None || SnappingManager.CanEmbed || this.rootSelected() || this.Document.forceActive || this._componentView?.isAnyChildContentActive?.() || this._props.isContentActive() ? true : undefined, - active => (this._isContentActive = active), + active => { + this._isContentActive = active; + }, { fireImmediately: true } ); this._disposers.pointerevents = reaction( () => this.style(this.Document, StyleProp.PointerEvents), - pointerevents => (this._pointerEvents = pointerevents), + pointerevents => { + this._pointerEvents = pointerevents; + }, { fireImmediately: true } ); } preDrop = (e: Event, de: DragManager.DropEvent, dropAction: dropActionType) => { const dragData = de.complete.docDragData; if (dragData && this.isContentActive() && !this.props.dontRegisterView) { - dragData.dropAction = dropAction ? dropAction : dragData.dropAction; + dragData.dropAction = dropAction || dragData.dropAction; e.stopPropagation(); } }; @@ -291,7 +293,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document dragData.removeDocument = this._props.removeDocument; dragData.moveDocument = this._props.moveDocument; dragData.draggedViews = [docView]; - dragData.canEmbed = this.Document.dragAction ?? this._props.dragAction ? true : false; + dragData.canEmbed = !!(this.Document.dragAction ?? this._props.dragAction); (this._props.dragConfig ?? this._componentView?.dragConfig)?.(dragData); DragManager.StartDocumentDrag( selected.map(dv => dv.ContentDiv!), @@ -311,7 +313,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document onClick = action((e: React.MouseEvent | React.PointerEvent) => { if (this._props.isGroupActive?.() === 'child' && !this._props.isDocumentActive?.()) return; const documentView = this._docView; - if (documentView && !this.Document.ignoreClick && this._props.renderDepth >= 0 && Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) { + if (documentView && !this.Document.ignoreClick && this._props.renderDepth >= 0 && ClientUtils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) { let stopPropagate = true; let preventDefault = true; !this.layoutDoc._keepZWhenDragged && this._props.bringToFront?.(this.Document); @@ -368,6 +370,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document if ((clickFunc && waitFordblclick !== 'never') || waitFordblclick === 'always') { this._doubleClickTimeout && clearTimeout(this._doubleClickTimeout); this._doubleClickTimeout = setTimeout(this._singleClickFunc, 300); + // eslint-disable-next-line no-use-before-define } else if (!DocumentView.LongPress) { this._singleClickFunc(); this._singleClickFunc = undefined; @@ -380,6 +383,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document onPointerDown = (e: React.PointerEvent): void => { if (this._props.isGroupActive?.() === 'child' && !this._props.isDocumentActive?.()) return; + // eslint-disable-next-line no-use-before-define this._longPressSelector = setTimeout(() => DocumentView.LongPress && this._props.select(false), 1000); if (!GestureOverlay.DownDocView) GestureOverlay.DownDocView = this._docView; @@ -412,7 +416,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document onPointerMove = (e: PointerEvent): void => { if (e.buttons !== 1 || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) 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())) { this.cleanupPointerEvents(); this._longPressSelector && clearTimeout(this._longPressSelector); this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && dropActionType.embed) || ((this.Document.dragAction || this._props.dragAction || undefined) as dropActionType)); @@ -430,14 +434,15 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document if (this.onPointerUpHandler?.script) { this.onPointerUpHandler.script.run({ this: this.Document }, console.log); - } else if (e.button === 0 && Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) { - this._doubleTap = (this.onDoubleClickHandler?.script || this.Document.defaultDoubleClick !== 'ignore') && Date.now() - this._lastTap < Utils.CLICK_TIME; + } else if (e.button === 0 && ClientUtils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) { + this._doubleTap = (this.onDoubleClickHandler?.script || this.Document.defaultDoubleClick !== 'ignore') && Date.now() - this._lastTap < ClientUtils.CLICK_TIME; if (!this.isContentActive()) this._lastTap = Date.now(); // don't want to process the start of a double tap if the doucment is selected } + // eslint-disable-next-line no-use-before-define if (DocumentView.LongPress) e.preventDefault(); }; - toggleFollowLink = undoable((zoom?: boolean, setTargetToggle?: boolean): void => { + toggleFollowLink = undoable((): void => { const hadOnClick = this.Document.onClick; this.noOnClick(); this.Document.onClick = hadOnClick ? undefined : FollowLinkScript(); @@ -458,16 +463,14 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document }, 'default on click'); deleteClicked = undoable(() => this._props.removeDocument?.(this.Document), 'delete doc'); - setToggleDetail = undoable( - (scriptFieldKey: 'onClick') => - (this.Document[scriptFieldKey] = ScriptField.MakeScript( - `toggleDetail(documentView, "${StrCast(this.Document.layout_fieldKey) - .replace('layout_', '') - .replace(/^layout$/, 'detail')}")`, - { documentView: 'any' } - )), - 'set toggle detail' - ); + setToggleDetail = undoable((scriptFieldKey: 'onClick') => { + this.Document[scriptFieldKey] = ScriptField.MakeScript( + `toggleDetail(documentView, "${StrCast(this.Document.layout_fieldKey) + .replace('layout_', '') + .replace(/^layout$/, 'detail')}")`, + { documentView: 'any' } + ); + }, 'set toggle detail'); drop = undoable((e: Event, de: DragManager.DropEvent) => { if (this._props.dontRegisterView || this._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return false; @@ -505,7 +508,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document const input = document.createElement('input'); input.type = 'file'; input.accept = '.zip'; - input.onchange = _e => { + input.onchange = () => { if (input.files) { const batch = UndoManager.StartBatch('importing'); Doc.importDocument(input.files[0]).then(doc => { @@ -523,7 +526,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document if (e && this.layoutDoc.layout_hideContextMenu && Doc.noviceMode) { e.preventDefault(); e.stopPropagation(); - //!this._props.isSelected(true) && SelectionManager.SelectView(this.DocumentView(), false); + // !this._props.isSelected(true) && SelectionManager.SelectView(this.DocumentView(), false); } // the touch onContextMenu is button 0, the pointer onContextMenu is button 2 if (e) { @@ -535,7 +538,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document e.stopPropagation(); e.persist(); - if (!navigator.userAgent.includes('Mozilla') && (Math.abs(this._downX - e?.clientX) > 3 || Math.abs(this._downY - e?.clientY) > 3)) { + if (!navigator.userAgent.includes('Mozilla') && (Math.abs(this._downX - (e?.clientX ?? 0)) > 3 || Math.abs(this._downY - (e?.clientY ?? 0)) > 3)) { return; } } @@ -587,7 +590,11 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document zorderItems.push({ description: 'Send to Back', event: () => SelectionManager.Views.forEach(dv => dv._props.bringToFront?.(dv.Document, true)), icon: 'arrow-down' }); zorderItems.push({ description: !this.layoutDoc._keepZDragged ? 'Keep ZIndex when dragged' : 'Allow ZIndex to change when dragged', - event: undoBatch(action(() => (this.layoutDoc._keepZWhenDragged = !this.layoutDoc._keepZWhenDragged))), + event: undoBatch( + action(() => { + this.layoutDoc._keepZWhenDragged = !this.layoutDoc._keepZWhenDragged; + }) + ), icon: 'hand-point-up', }); !zorders && cm.addItem({ description: 'Z Order...', addDivider: true, noexpand: true, subitems: zorderItems, icon: 'layer-group' }); @@ -597,7 +604,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document const existingOnClick = cm.findByDescription('OnClick...'); const onClicks: ContextMenuProps[] = existingOnClick && 'subitems' in existingOnClick ? existingOnClick.subitems : []; - onClicks.push({ description: 'Enter Portal', event: undoable(e => DocUtils.makeIntoPortal(this.Document, this.layoutDoc, this._allLinks), 'make into portal'), icon: 'window-restore' }); + onClicks.push({ description: 'Enter Portal', event: undoable(() => DocUtils.makeIntoPortal(this.Document, this.layoutDoc, this._allLinks), 'make into portal'), icon: 'window-restore' }); !Doc.noviceMode && onClicks.push({ description: 'Toggle Detail', event: this.setToggleDetail, icon: 'concierge-bell' }); if (!this.Document.annotationOn) { @@ -613,9 +620,9 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document const funcs: ContextMenuProps[] = []; if (!Doc.noviceMode && this.layoutDoc.onDragStart) { - funcs.push({ description: 'Drag an Embedding', icon: 'edit', event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getEmbedding(this.dragFactory)')) }); - funcs.push({ description: 'Drag a Copy', icon: 'edit', event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)')) }); - funcs.push({ description: 'Drag Document', icon: 'edit', event: () => (this.layoutDoc.onDragStart = undefined) }); + funcs.push({ description: 'Drag an Embedding', icon: 'edit', event: () => { this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getEmbedding(this.dragFactory)')); } }); // prettier-ignore + funcs.push({ description: 'Drag a Copy', icon: 'edit', event: () => { this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)')); } }); // prettier-ignore + funcs.push({ description: 'Drag Document', icon: 'edit', event: () => { this.layoutDoc.onDragStart = undefined; } }); // prettier-ignore cm.addItem({ description: 'OnDrag...', noexpand: true, subitems: funcs, icon: 'asterisk' }); } @@ -624,14 +631,14 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document if (!Doc.IsSystem(this.Document)) { if (!Doc.noviceMode) { moreItems.push({ description: 'Make View of Metadata Field', event: () => Doc.MakeMetadataFieldTemplate(this.Document, this._props.TemplateDataDocument), icon: 'concierge-bell' }); - moreItems.push({ description: `${this.Document._chromeHidden ? 'Show' : 'Hide'} Chrome`, event: () => (this.Document._chromeHidden = !this.Document._chromeHidden), icon: 'project-diagram' }); + moreItems.push({ description: `${this.Document._chromeHidden ? 'Show' : 'Hide'} Chrome`, event: () => { this.Document._chromeHidden = !this.Document._chromeHidden; }, icon: 'project-diagram' }); // prettier-ignore if (Cast(Doc.GetProto(this.Document).data, listSpec(Doc))) { moreItems.push({ description: 'Export to Google Photos Album', event: () => GooglePhotos.Export.CollectionToAlbum({ collection: this.Document }).then(console.log), icon: 'caret-square-right' }); moreItems.push({ description: 'Tag Child Images via Google Photos', event: () => GooglePhotos.Query.TagChildImages(this.Document), icon: 'caret-square-right' }); moreItems.push({ description: 'Write Back Link to Album', event: () => GooglePhotos.Transactions.AddTextEnrichment(this.Document), icon: 'caret-square-right' }); } - moreItems.push({ description: 'Copy ID', event: () => Utils.CopyText(Doc.globalServerPath(this.Document)), icon: 'fingerprint' }); + moreItems.push({ description: 'Copy ID', event: () => ClientUtils.CopyText(Doc.globalServerPath(this.Document)), icon: 'fingerprint' }); } } @@ -639,7 +646,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document } const constantItems: ContextMenuProps[] = []; if (!Doc.IsSystem(this.Document) && this.Document._type_collection !== CollectionViewType.Docking) { - constantItems.push({ description: 'Zip Export', icon: 'download', event: async () => Doc.Zip(this.Document) }); + constantItems.push({ description: 'Zip Export', icon: 'download', event: async () => DocUtils.Zip(this.Document) }); (this.Document._type_collection !== CollectionViewType.Docking || !Doc.noviceMode) && constantItems.push({ description: 'Share', event: () => SharingManager.Instance.open(this._docView), icon: 'users' }); if (this._props.removeDocument && Doc.ActiveDashboard !== this.Document) { // need option to gray out menu items ... preferably with a '?' that explains why they're grayed out (eg., no permissions) @@ -655,8 +662,8 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document !Doc.noviceMode && helpItems.push({ description: 'Print Document in Console', event: () => console.log(this.Document), icon: 'hand-point-right' }); !Doc.noviceMode && helpItems.push({ description: 'Print DataDoc in Console', event: () => console.log(this.dataDoc), icon: 'hand-point-right' }); - let documentationDescription: string | undefined = undefined; - let documentationLink: string | undefined = undefined; + let documentationDescription: string | undefined; + let documentationLink: string | undefined; switch (this.Document.type) { case DocumentType.COL: documentationDescription = 'See collection documentation'; @@ -690,6 +697,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document documentationDescription = 'See DataViz node documentation'; documentationLink = 'https://brown-dash.github.io/Dash-Documentation/documents/dataViz/'; break; + default: } // Add link to help documentation (unless the doc contents have been overriden in which case the documentation isn't relevant) if (!this.docContents && documentationDescription && documentationLink) { @@ -710,8 +718,8 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document panelHeight = () => this._props.PanelHeight() - this.headerMargin; screenToLocalContent = () => this._props.ScreenToLocalTransform().translate(0, -this.headerMargin); onClickFunc = this.disableClickScriptFunc ? undefined : () => this.onClickHandler; - setHeight = (height: number) => !this._props.suppressSetHeight && (this.layoutDoc._height = Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), height)); - setContentView = action((view: ViewBoxInterface) => (this._componentView = view)); + setHeight = (height: number) => { !this._props.suppressSetHeight && (this.layoutDoc._height = Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), height)); } // prettier-ignore + setContentView = action((view: ViewBoxInterface) => { this._componentView = view; }); // prettier-ignore isContentActive = (): boolean | undefined => this._isContentActive; childFilters = () => [...this._props.childFilters(), ...StrListCast(this.layoutDoc.childFilters)]; @@ -729,11 +737,13 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document const filtered = DocUtils.FilterDocs(this.directLinks, this._props.childFilters?.() ?? [], []).filter(d => d.link_displayLine || Doc.UserDoc().showLinkLines); return filtered.some(link => link._link_displayArrow) ? 0 : undefined; } + default: } return this._props.styleProvider?.(doc, props, property); }; - removeLinkByHiding = (link: Doc) => () => (link.link_displayLine = false); + // eslint-disable-next-line no-return-assign + removeLinkByHiding = (link: Doc) => () => link.link_displayLine = false; // prettier-ignore @computed get allLinkEndpoints() { // the small blue dots that mark the endpoints of links if (this._componentView instanceof KeyValueBox || this._props.hideLinkAnchors || this.layoutDoc.layout_hideLinkAnchors || this._props.dontRegisterView || this.layoutDoc.layout_unrendered) return null; @@ -748,8 +758,8 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document PanelHeight={this.anchorPanelHeight} dontRegisterView={false} layout_showTitle={returnEmptyString} - hideCaptions={true} - hideLinkAnchors={true} + hideCaptions + hideLinkAnchors layout_fitWidth={returnTrue} removeDocument={this.removeLinkByHiding(link)} styleProvider={this.anchorStyleProvider} @@ -792,32 +802,30 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document } captionStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => this._props?.styleProvider?.(doc, props, property + ':caption'); - fieldsDropdown = (placeholder: string) => { - return ( - <div - ref={action((r: any) => r && (this._titleDropDownInnerWidth = DivWidth(r)))} - onPointerDown={action(e => (this._changingTitleField = true))} - style={{ width: 'max-content', background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor, transformOrigin: 'left', transform: `scale(${this.titleHeight / 30 /* height of Dropdown */})` }}> - <FieldsDropdown - Document={this.Document} - placeholder={placeholder} - selectFunc={action((field: string | number) => { - if (this.layoutDoc.layout_showTitle) { - this.layoutDoc._layout_showTitle = field; - } else if (!this._props.layout_showTitle) { - Doc.UserDoc().layout_showTitle = field; - } - this._changingTitleField = false; - })} - menuClose={action(() => (this._changingTitleField = false))} - /> - </div> - ); - }; + fieldsDropdown = (placeholder: string) => ( + <div + ref={action((r: any) => { r && (this._titleDropDownInnerWidth = DivWidth(r));} )} // prettier-ignore + onPointerDown={action(() => { this._changingTitleField = true; })} // prettier-ignore + style={{ width: 'max-content', background: SnappingManager.userBackgroundColor, color: SnappingManager.userColor, transformOrigin: 'left', transform: `scale(${this.titleHeight / 30 /* height of Dropdown */})` }}> + <FieldsDropdown + Document={this.Document} + placeholder={placeholder} + selectFunc={action((field: string | number) => { + if (this.layoutDoc.layout_showTitle) { + this.layoutDoc._layout_showTitle = field; + } else if (!this._props.layout_showTitle) { + Doc.UserDoc().layout_showTitle = field; + } + this._changingTitleField = false; + })} + menuClose={action(() => { this._changingTitleField = false; })} // prettier-ignore + /> + </div> + ); /** * displays a 'title' at the top of a document. The title contents default to the 'title' field, but can be changed to one or more fields by * setting layout_showTitle using the format: field1[:hover] - **/ + * */ @computed get titleView() { const showTitle = this.layout_showTitle?.split(':')[0]; const showTitleHover = this.layout_showTitle?.includes(':hover'); @@ -825,7 +833,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document const targetDoc = showTitle?.startsWith('_') ? this.layoutDoc : this.Document; const background = StrCast( this.layoutDoc.layout_headingColor, - StrCast(SharingManager.Instance.users.find(u => u.user.email === this.dataDoc.author)?.sharingDoc.headingColor, StrCast(Doc.SharingDoc().headingColor, SettingsManager.userBackgroundColor)) + StrCast(SharingManager.Instance.users.find(u => u.user.email === this.dataDoc.author)?.sharingDoc.headingColor, StrCast(Doc.SharingDoc().headingColor, SnappingManager.userBackgroundColor)) ); const dropdownWidth = this._titleRef.current?._editing || this._changingTitleField ? Math.max(10, (this._titleDropDownInnerWidth * this.titleHeight) / 30) : 0; const sidebarWidthPercent = +StrCast(this.layoutDoc.layout_sidebarWidthPercent).replace('%', ''); @@ -839,7 +847,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document position: this.headerMargin ? 'relative' : 'absolute', height: this.titleHeight, width: 100 - sidebarWidthPercent + '%', - color: background === 'transparent' ? SettingsManager.userColor : lightOrDark(background), + color: background === 'transparent' ? SnappingManager.userColor : lightOrDark(background), background, pointerEvents: (!this.disableClickScriptFunc && this.onClickHandler) || this.Document.ignoreClick ? 'none' : this.isContentActive() || this._props.isDocumentActive?.() ? 'all' : undefined, }}> @@ -860,11 +868,11 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document contents={ showTitle .split(';') - .map(field => Field.toJavascriptString(this.Document[field] as Field)) + .map(field => Field.toJavascriptString(this.Document[field] as FieldType)) .join(' \\ ') || '-unset-' } display="block" - oneLine={true} + oneLine fontSize={(this.titleHeight / 15) * 10} GetValue={() => showTitle @@ -905,10 +913,10 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document xPadding={10} fieldKey={this.layout_showCaption} styleProvider={this.captionStyleProvider} - dontRegisterView={true} + dontRegisterView rootSelected={this.rootSelected} - noSidebar={true} - dontScale={true} + noSidebar + dontScale renderDepth={this._props.renderDepth} isContentActive={this.isContentActive} /> @@ -952,8 +960,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document render() { TraceMobx(); - const highlighting = this.highlighting; - const borderPath = this.borderPath; + const { highlighting, borderPath } = this; const boxShadow = !highlighting ? this.boxShadow : highlighting && this.borderRounding && highlighting.highlightStyle !== 'dashed' @@ -968,23 +975,22 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document }); return ( + // eslint-disable-next-line jsx-a11y/click-events-have-key-events <div className={`${DocumentView.ROOT_DIV} docView-hack`} ref={this._mainCont} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick} - onPointerEnter={e => (!SnappingManager.IsDragging || SnappingManager.CanEmbed) && Doc.BrushDoc(this.Document)} - onPointerOver={e => (!SnappingManager.IsDragging || SnappingManager.CanEmbed) && Doc.BrushDoc(this.Document)} + onPointerEnter={() => (!SnappingManager.IsDragging || SnappingManager.CanEmbed) && Doc.BrushDoc(this.Document)} + onPointerOver={() => (!SnappingManager.IsDragging || SnappingManager.CanEmbed) && Doc.BrushDoc(this.Document)} onPointerLeave={e => !isParentOf(this._contentDiv, document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y)) && Doc.UnBrushDoc(this.Document)} style={{ borderRadius: this.borderRounding, pointerEvents: this._pointerEvents === 'visiblePainted' ? 'none' : this._pointerEvents, // visible painted means that the underlying doc contents are irregular and will process their own pointer events (otherwise, the contents are expected to fill the entire doc view box so we can handle pointer events here) }}> - <> - {this._componentView instanceof KeyValueBox ? renderDoc : DocumentViewInternal.AnimationEffect(renderDoc, this.Document[Animation], this.Document)} - {borderPath?.jsx} - </> + {this._componentView instanceof KeyValueBox ? renderDoc : DocumentViewInternal.AnimationEffect(renderDoc, this.Document[Animation])} + {borderPath?.jsx} </div> ); } @@ -994,7 +1000,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document * @param presEffectDoc presentation effects document that specifies the animation effect parameters * @returns a function that will wrap a JSX animation element wrapping any JSX element */ - public static AnimationEffect(renderDoc: JSX.Element, presEffectDoc: Opt<Doc>, root: Doc) { + public static AnimationEffect(renderDoc: JSX.Element, presEffectDoc: Opt<Doc> /* , root: Doc */) { const dir = presEffectDoc?.presentation_effectDirection ?? presEffectDoc?.followLinkAnimDirection; const effectProps = { left: dir === PresEffectDirection.Left, @@ -1005,10 +1011,8 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document delay: 0, duration: Cast(presEffectDoc?.presentation_transition, 'number', Cast(presEffectDoc?.followLinkTransitionTime, 'number', null)), }; - //prettier-ignore + // prettier-ignore switch (StrCast(presEffectDoc?.presentation_effect, StrCast(presEffectDoc?.followLinkAnimEffect))) { - default: - case PresEffect.None: return renderDoc; case PresEffect.Zoom: return <Zoom {...effectProps}>{renderDoc}</Zoom>; case PresEffect.Fade: return <Fade {...effectProps}>{renderDoc}</Fade>; case PresEffect.Flip: return <Flip {...effectProps}>{renderDoc}</Flip>; @@ -1016,17 +1020,19 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document case PresEffect.Bounce: return <Bounce {...effectProps}>{renderDoc}</Bounce>; case PresEffect.Roll: return <Roll {...effectProps}>{renderDoc}</Roll>; case PresEffect.Lightspeed: return <JackInTheBox {...effectProps}>{renderDoc}</JackInTheBox>; + case PresEffect.None: + default: return renderDoc; } } public static recordAudioAnnotation(dataDoc: Doc, field: string, onRecording?: (stop: () => void) => void, onEnd?: () => void) { let gumStream: any; let recorder: any; - navigator.mediaDevices.getUserMedia({ audio: true }).then(function (stream) { + navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => { let audioTextAnnos = Cast(dataDoc[field + '_audioAnnotations_text'], listSpec('string'), null); if (audioTextAnnos) audioTextAnnos.push(''); else audioTextAnnos = dataDoc[field + '_audioAnnotations_text'] = new List<string>(['']); DictationManager.Controls.listen({ - interimHandler: value => (audioTextAnnos[audioTextAnnos.length - 1] = value), + interimHandler: value => { audioTextAnnos[audioTextAnnos.length - 1] = value; }, // prettier-ignore continuous: { indefinite: false }, }).then(results => { if (results && [DictationManager.Controls.Infringed].includes(results)) { @@ -1060,9 +1066,9 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document } @observer -export class DocumentView extends DocComponent<DocumentViewProps>() { +export class DocumentView extends DocComponent<DocumentViewProps & { CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView }>() { public static ROOT_DIV = 'documentView-effectsWrapper'; - public get displayName() { return 'DocumentView(' + this.Document?.title + ')'; } // prettier-ignore + public get displayName() { return 'DocumentView(' + (this.Document?.title??"") + ')'; } // prettier-ignore public ContentRef = React.createRef<HTMLDivElement>(); private _htmlOverlayEffect: Opt<Doc>; private _disposers: { [name: string]: IReactionDisposer } = {}; @@ -1084,7 +1090,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { return () => (SnappingManager.ExploreMode ? ScriptField.MakeScript('CollectionBrowseClick(documentView, clientX, clientY)', { documentView: 'any', clientX: 'number', clientY: 'number' })! : undefined); } - constructor(props: DocumentViewProps) { + constructor(props: DocumentViewProps & { CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView }) { super(props); makeObservable(this); } @@ -1161,7 +1167,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { !BoolCast(this.Document.dontRegisterView, this._props.dontRegisterView) && DocumentManager.Instance.RemoveView(this); } - public set IsSelected(val) { runInAction(() => (this._selected = val)); } // prettier-ignore + public set IsSelected(val) { runInAction(() => { this._selected = val; }); } // prettier-ignore public get IsSelected() { return this._selected; } // prettier-ignore public get topMost() { return this._props.renderDepth === 0; } // prettier-ignore public get ContentDiv() { return this._docViewInternal?._contentDiv; } // prettier-ignore @@ -1176,7 +1182,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { return this._props.layout_fitWidth?.(this.layoutDoc) ?? this.layoutDoc?.layout_fitWidth; } @computed get anchorViewDoc() { - return this._props.LayoutTemplateString?.includes('link_anchor_2') ? DocCast(this.Document['link_anchor_2']) : this._props.LayoutTemplateString?.includes('link_anchor_1') ? DocCast(this.Document['link_anchor_1']) : undefined; + return this._props.LayoutTemplateString?.includes('link_anchor_2') ? DocCast(this.Document.link_anchor_2) : this._props.LayoutTemplateString?.includes('link_anchor_1') ? DocCast(this.Document.link_anchor_1) : undefined; } @computed get getBounds(): Opt<{ left: number; top: number; right: number; bottom: number; transition?: string }> { @@ -1213,6 +1219,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { public get containerViewPath() { return this._props.containerViewPath; } // prettier-ignore public get CollectionFreeFormView() { return this.CollectionFreeFormDocumentView?.CollectionFreeFormView; } // prettier-ignore public get CollectionFreeFormDocumentView() { return this._props.CollectionFreeFormDocumentView?.(); } // prettier-ignore + public get LocalRotation() { return this._props.LocalRotation?.(); } // prettier-ignore public clearViewTransition = () => { this._viewTimer && clearTimeout(this._viewTimer); @@ -1231,18 +1238,18 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { public iconify(finished?: () => void, animateTime?: number) { this.ComponentView?.updateIcon?.(); const animTime = this._docViewInternal?.animateScaleTime(); - runInAction(() => this._docViewInternal && animateTime !== undefined && (this._docViewInternal._animateScaleTime = animateTime)); + runInAction(() => { this._docViewInternal && animateTime !== undefined && (this._docViewInternal._animateScaleTime = animateTime); }); // prettier-ignore const finalFinished = action(() => { finished?.(); this._docViewInternal && (this._docViewInternal._animateScaleTime = animTime); }); - const layout_fieldKey = Cast(this.Document.layout_fieldKey, 'string', null); - if (layout_fieldKey !== 'layout_icon') { + const layoutFieldKey = Cast(this.Document.layout_fieldKey, 'string', null); + if (layoutFieldKey !== 'layout_icon') { this.switchViews(true, 'icon', finalFinished); - if (layout_fieldKey && layout_fieldKey !== 'layout' && layout_fieldKey !== 'layout_icon') this.Document.deiconifyLayout = layout_fieldKey.replace('layout_', ''); + if (layoutFieldKey && layoutFieldKey !== 'layout' && layoutFieldKey !== 'layout_icon') this.Document.deiconifyLayout = layoutFieldKey.replace('layout_', ''); } else { const deiconifyLayout = Cast(this.Document.deiconifyLayout, 'string', null); - this.switchViews(deiconifyLayout ? true : false, deiconifyLayout, finalFinished, true); + this.switchViews(!!deiconifyLayout, deiconifyLayout, finalFinished, true); this.Document.deiconifyLayout = undefined; this._props.bringToFront?.(this.Document); } @@ -1262,7 +1269,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { autoplay: true, loop: false, volume: 0.5, - onend: action(() => (self.dataDoc.audioAnnoState = AudioAnnoState.stopped)), + onend: action(() => { self.dataDoc.audioAnnoState = AudioAnnoState.stopped; }), // prettier-ignore }); this.dataDoc.audioAnnoState = AudioAnnoState.playing; break; @@ -1270,6 +1277,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { this.dataDoc[AudioPlay]?.stop(); this.dataDoc.audioAnnoState = AudioAnnoState.stopped; break; + default: } } }; @@ -1284,10 +1292,10 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { this._docViewInternal._animateScaleTime = time; } }); - public setAnimEffect = (presEffect: Doc, timeInMs: number, afterTrans?: () => void) => { + public setAnimEffect = (presEffect: Doc, timeInMs: number /* , afterTrans?: () => void */) => { this._animEffectTimer && clearTimeout(this._animEffectTimer); this.Document[Animation] = presEffect; - this._animEffectTimer = setTimeout(() => (this.Document[Animation] = undefined), timeInMs); + this._animEffectTimer = setTimeout(() => { this.Document[Animation] = undefined; }, timeInMs); // prettier-ignore }; public setViewTransition = (transProp: string, timeInMs: number, afterTrans?: () => void, dataTrans = false) => { this._viewTimer = DocumentView.SetViewTransition([this.layoutDoc], transProp, timeInMs, this._viewTimer, afterTrans, dataTrans); @@ -1304,7 +1312,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { } const view = SelectionManager.Views[0]?._props.renderDepth > 0 ? SelectionManager.Views[0] : undefined; undoable(() => { - var tempDoc: Opt<Doc>; + let tempDoc: Opt<Doc>; if (view) { if (!view.layoutDoc.isTemplateDoc) { tempDoc = view.Document; @@ -1322,6 +1330,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { } Doc.UserDoc().defaultTextLayout = tempDoc ? new PrefetchProxy(tempDoc) : undefined; }, 'set default template')(); + return undefined; } /** @@ -1335,12 +1344,13 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { const curLayout = StrCast(this.Document.layout_fieldKey).replace('layout_', '').replace('layout', ''); if (!this.Document.layout_default && curLayout !== detailLayoutKeySuffix) this.Document.layout_default = curLayout; const defaultLayout = StrCast(this.Document.layout_default); - if (this.Document.layout_fieldKey === 'layout_' + detailLayoutKeySuffix) this.switchViews(defaultLayout ? true : false, defaultLayout, undefined, true); + if (this.Document.layout_fieldKey === 'layout_' + detailLayoutKeySuffix) this.switchViews(!!defaultLayout, defaultLayout, undefined, true); else this.switchViews(true, detailLayoutKeySuffix, undefined, true); }; public switchViews = (custom: boolean, view: string, finished?: () => void, useExistingLayout = false) => { const batch = UndoManager.StartBatch('switchView:' + view); - runInAction(() => this._docViewInternal && (this._docViewInternal._animateScalingTo = 0.1)); // shrink doc + // shrink doc first.. + runInAction(() => { this._docViewInternal && (this._docViewInternal._animateScalingTo = 0.1); }); // prettier-ignore setTimeout( action(() => { if (useExistingLayout && custom && this.Document['layout_' + view]) { @@ -1348,7 +1358,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { } else { this.setCustomView(custom, view); } - this._docViewInternal && (this._docViewInternal._animateScalingTo = 1); // expand it + this._docViewInternal && (this._docViewInternal._animateScalingTo = 1); // now expand it setTimeout( action(() => { this._docViewInternal && (this._docViewInternal._animateScalingTo = 0); @@ -1366,7 +1376,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { */ public docViewPath = () => (this.containerViewPath ? [...this.containerViewPath(), this] : [this]); - layout_fitWidthFunc = (doc: Doc) => BoolCast(this.layout_fitWidth); + layout_fitWidthFunc = (/* doc: Doc */) => BoolCast(this.layout_fitWidth); screenToLocalScale = () => this._props.ScreenToLocalTransform().Scale; isSelected = () => this.IsSelected; select = (extendSelection: boolean, focusSelection?: boolean) => { @@ -1390,7 +1400,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { PanelWidth = () => this.panelWidth; PanelHeight = () => this.panelHeight; NativeDimScaling = () => this.nativeScaling; - hideLinkCount = () => (this.hideLinkButton ? true : false); + hideLinkCount = () => !!this.hideLinkButton; selfView = () => this; /** * @returns Transform to the document view (in the coordinate system of whatever contains the DocumentView) @@ -1413,17 +1423,16 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { ref={r => { const val = r?.style.display !== 'none'; // if the outer overlay has been displayed, trigger the innner div to start it's opacity fade in transition if (r && val !== this._enableHtmlOverlayTransitions) { - setTimeout(action(() => (this._enableHtmlOverlayTransitions = val))); + setTimeout(action(() => { this._enableHtmlOverlayTransitions = val; })); // prettier-ignore } }} style={{ display: !this._htmlOverlayText ? 'none' : undefined }}> <div className="documentView-htmlOverlayInner" style={{ transition: `all 500ms`, opacity: this._enableHtmlOverlayTransitions ? 0.9 : 0 }}> {DocumentViewInternal.AnimationEffect( <div className="webBox-textHighlight"> - <ObserverJsxParser autoCloseVoidElements={true} key={42} onError={(e: any) => console.log('PARSE error', e)} renderInWrapper={false} jsx={StrCast(this._htmlOverlayText)} /> + <ObserverJsxParser autoCloseVoidElements key={42} onError={(e: any) => console.log('PARSE error', e)} renderInWrapper={false} jsx={StrCast(this._htmlOverlayText)} /> </div>, - { ...(this._htmlOverlayEffect ?? {}), presentation_effect: effect ?? PresEffect.Zoom } as any as Doc, - this.Document + { ...(this._htmlOverlayEffect ?? {}), presentation_effect: effect ?? PresEffect.Zoom } as any as Doc )} </div> </div> @@ -1436,7 +1445,15 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { const yshift = Math.abs(this.Yshift) <= 0.001 ? this._props.PanelHeight() : undefined; return ( - <div id={this.ViewGuid} className="contentFittingDocumentView" onPointerEnter={action(() => (this._isHovering = true))} onPointerLeave={action(() => (this._isHovering = false))}> + <div + id={this.ViewGuid} + className="contentFittingDocumentView" + onPointerEnter={action(() => { + this._isHovering = true; + })} + onPointerLeave={action(() => { + this._isHovering = false; + })}> {!this.Document || !this._props.PanelWidth() ? null : ( <div className="contentFittingDocumentView-previewDoc" @@ -1462,14 +1479,16 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { layout_fitWidth={this.layout_fitWidthFunc} ScreenToLocalTransform={this.screenToContentsTransform} focus={this._props.focus || emptyFunction} - ref={action((r: DocumentViewInternal | null) => r && (this._docViewInternal = r))} + ref={action((r: DocumentViewInternal | null) => { + r && (this._docViewInternal = r); + })} /> {this.htmlOverlay()} {this.ComponentView?.infoUI?.()} </div> )} {/* display link count button */} - <DocumentLinksButton hideCount={this.hideLinkCount} View={this} scaling={this.screenToLocalScale} OnHover={true} Bottom={this.topMost} ShowCount={true} /> + <DocumentLinksButton hideCount={this.hideLinkCount} View={this} scaling={this.screenToLocalScale} OnHover Bottom={this.topMost} ShowCount /> </div> ); } @@ -1493,7 +1512,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { // shows a stacking view collection (by default, but the user can change) of all documents linked to the source public static showBackLinks(linkAnchor: Doc) { - const docId = Doc.CurrentUserEmail + Doc.GetProto(linkAnchor)[Id] + '-pivotish'; + const docId = ClientUtils.CurrentUserEmail + Doc.GetProto(linkAnchor)[Id] + '-pivotish'; // prettier-ignore DocServer.GetRefField(docId).then(docx => LightboxView.Instance.SetLightboxDoc( @@ -1504,19 +1523,27 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { } } +export function returnEmptyDocViewList() { + return emptyPath; +} + +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function deiconifyView(documentView: DocumentView) { documentView.iconify(); documentView.select(false); }); +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function deiconifyViewToLightbox(documentView: DocumentView) { - LightboxView.Instance.AddDocTab(documentView.Document, OpenWhere.lightbox, 'layout'); //, 0); + LightboxView.Instance.AddDocTab(documentView.Document, OpenWhere.lightbox, 'layout'); // , 0); }); +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuffix: string) { dv.toggleDetail(detailLayoutKeySuffix); }); +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc, linkSource: Doc) { const collectedLinks = DocListCast(linkCollection[DocData].data); let wid = NumCast(linkSource._width); @@ -1534,9 +1561,10 @@ ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc, linkSour Doc.AddDocToList(Doc.GetProto(linkCollection), 'data', embedding); } }); - embedding && DocServer.UPDATE_SERVER_CACHE(); // if a new embedding was made, update the client's server cache so that it will not come back as a promise + embedding && UPDATE_SERVER_CACHE(); // if a new embedding was made, update the client's server cache so that it will not come back as a promise return links; }); +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function updateTagsCollection(collection: Doc) { const tag = StrCast(collection.title).split('-->')[1]; const matchedTags = Array.from(SearchUtil.SearchCollection(Doc.MyFilesystem, tag, false, ['tags']).keys()); diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx index a557cff4f..9be66ba4a 100644 --- a/src/client/views/nodes/EquationBox.tsx +++ b/src/client/views/nodes/EquationBox.tsx @@ -1,7 +1,7 @@ import { action, makeObservable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { DivHeight, DivWidth } from '../../../Utils'; +import { DivHeight, DivWidth } from '../../../ClientUtils'; import { Id } from '../../../fields/FieldSymbols'; import { NumCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 771856788..14454ff61 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -1,3 +1,4 @@ +import { computed } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { DateField } from '../../../fields/DateField'; @@ -5,13 +6,10 @@ import { Doc, Field, Opt } from '../../../fields/Doc'; import { List } from '../../../fields/List'; import { ScriptField } from '../../../fields/ScriptField'; import { WebField } from '../../../fields/URLField'; -import { dropActionType } from '../../util/DragManager'; +import { dropActionType } from '../../util/DropActionTypes'; import { Transform } from '../../util/Transform'; -import { ViewBoxInterface } from '../DocComponent'; -import { CollectionFreeFormDocumentView } from './CollectionFreeFormDocumentView'; +import { PinProps, ViewBoxInterface } from '../DocComponent'; import { DocumentView, OpenWhere } from './DocumentView'; -import { PinProps } from './trails'; -import { computed } from 'mobx'; export interface FocusViewOptions { willPan?: boolean; // determines whether to pan to target document @@ -56,7 +54,7 @@ export interface FieldViewSharedProps { disableBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over. hideClickBehaviors?: boolean; // whether to suppress menu item options for changing click behaviors ignoreUsePath?: boolean; // ignore the usePath field for selecting the fieldKey (eg., on text docs) - CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView; + LocalRotation?: () => number | undefined; // amount of rotation applied to freeformdocumentview containing document view containerViewPath?: () => DocumentView[]; fitContentsToBox?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _freeform_fitContentsToBox property on a Document isGroupActive?: () => string | undefined; // is this document part of a group that is active diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx index 57ae92359..70fc63115 100644 --- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx @@ -6,7 +6,8 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, DocListCast, StrListCast } from '../../../../fields/Doc'; import { BoolCast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; -import { emptyFunction, returnTrue, setupMoveUpEvents, Utils } from '../../../../Utils'; +import { emptyFunction, Utils } from '../../../../Utils'; +import { ClientUtils, returnTrue, setupMoveUpEvents } from '../../../../ClientUtils'; import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; import { SelectionManager } from '../../../util/SelectionManager'; import { SettingsManager } from '../../../util/SettingsManager'; @@ -183,7 +184,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() { if (selected.length > 1) { text = selected.length + ' selected'; } else { - text = Utils.cleanDocumentType(StrCast(selected.lastElement().type) as DocumentType); + text = ClientUtils.cleanDocumentType(StrCast(selected.lastElement().type) as DocumentType); icon = Doc.toIcon(selected.lastElement()); } return ( diff --git a/src/client/views/nodes/FunctionPlotBox.tsx b/src/client/views/nodes/FunctionPlotBox.tsx index a86bdbd79..180c651fb 100644 --- a/src/client/views/nodes/FunctionPlotBox.tsx +++ b/src/client/views/nodes/FunctionPlotBox.tsx @@ -2,18 +2,18 @@ import functionPlot from 'function-plot'; import { computed, makeObservable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { Doc, DocListCast } from '../../../fields/Doc'; +import { DocListCast } from '../../../fields/Doc'; import { List } from '../../../fields/List'; import { listSpec } from '../../../fields/Schema'; import { Cast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; import { DocUtils, Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; +import { LinkManager } from '../../util/LinkManager'; import { undoBatch } from '../../util/UndoManager'; -import { ViewBoxAnnotatableComponent } from '../DocComponent'; +import { PinProps, ViewBoxAnnotatableComponent } from '../DocComponent'; import { FieldView, FieldViewProps } from './FieldView'; -import { PinProps, PresBox } from './trails'; -import { LinkManager } from '../../util/LinkManager'; +import { PresBox } from './trails'; @observer export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { @@ -89,7 +89,7 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps> drop = (e: Event, de: DragManager.DropEvent) => { if (de.complete.docDragData?.droppedDocuments.length) { const added = de.complete.docDragData.droppedDocuments.reduce((res, doc) => { - ///const ret = res && Doc.AddDocToList(this.dataDoc, this._props.fieldKey, doc); + // const ret = res && Doc.AddDocToList(this.dataDoc, this._props.fieldKey, doc); if (res) { const link = DocUtils.MakeLink(doc, this.Document, { link_relationship: 'function', link_description: 'input' }); link && this._props.addDocument?.(link); diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index bb1f70f97..231300a65 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -1,10 +1,11 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@mui/material'; import { Colors } from 'browndash-components'; -import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx'; +import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction } from 'mobx'; import { observer } from 'mobx-react'; import { extname } from 'path'; import * as React from 'react'; +import { ClientUtils, DashColor, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents } from '../../../ClientUtils'; import { Doc, Opt } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; @@ -14,7 +15,7 @@ import { ObjectField } from '../../../fields/ObjectField'; import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; -import { DashColor, emptyFunction, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils'; +import { emptyFunction } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; import { Networking } from '../../Network'; @@ -24,7 +25,7 @@ import { undoBatch } from '../../util/UndoManager'; import { ContextMenu } from '../../views/ContextMenu'; import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; import { ContextMenuProps } from '../ContextMenuItem'; -import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent'; +import { PinProps, ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent'; import { MarqueeAnnotator } from '../MarqueeAnnotator'; import { OverlayView } from '../OverlayView'; import { AnchorMenu } from '../pdf/AnchorMenu'; @@ -32,26 +33,29 @@ import { StyleProp } from '../StyleProvider'; import { OpenWhere } from './DocumentView'; import { FieldView, FieldViewProps, FocusViewOptions } from './FieldView'; import './ImageBox.scss'; -import { PinProps, PresBox } from './trails'; +import { PresBox } from './trails'; export class ImageEditorData { + // eslint-disable-next-line no-use-before-define private static _instance: ImageEditorData; private static get imageData() { return (ImageEditorData._instance ?? new ImageEditorData()).imageData; } // prettier-ignore @observable imageData: { rootDoc: Doc | undefined; open: boolean; source: string; addDoc: Opt<(doc: Doc | Doc[], annotationKey?: string) => boolean> } = observable({ rootDoc: undefined, open: false, source: '', addDoc: undefined }); - @action private static set = (open: boolean, rootDoc: Doc | undefined, source: string, addDoc: Opt<(doc: Doc | Doc[], annotationKey?: string) => boolean>) => (this._instance.imageData = { open, rootDoc, source, addDoc }); + @action private static set = (open: boolean, rootDoc: Doc | undefined, source: string, addDoc: Opt<(doc: Doc | Doc[], annotationKey?: string) => boolean>) => { + this._instance.imageData = { open, rootDoc, source, addDoc }; + }; constructor() { makeObservable(this); ImageEditorData._instance = this; } - public static get Open() { return ImageEditorData.imageData.open; } // prettier-ignore - public static get Source() { return ImageEditorData.imageData.source; } // prettier-ignore - public static get RootDoc() { return ImageEditorData.imageData.rootDoc; } // prettier-ignore - public static get AddDoc() { return ImageEditorData.imageData.addDoc; } // prettier-ignore + public static get Open() { return ImageEditorData.imageData.open; } // prettier-ignore public static set Open(open: boolean) { ImageEditorData.set(open, this.imageData.rootDoc, this.imageData.source, this.imageData.addDoc); } // prettier-ignore + public static get Source() { return ImageEditorData.imageData.source; } // prettier-ignore public static set Source(source: string) { ImageEditorData.set(this.imageData.open, this.imageData.rootDoc, source, this.imageData.addDoc); } // prettier-ignore + public static get RootDoc() { return ImageEditorData.imageData.rootDoc; } // prettier-ignore public static set RootDoc(rootDoc: Opt<Doc>) { ImageEditorData.set(this.imageData.open, rootDoc, this.imageData.source, this.imageData.addDoc); } // prettier-ignore + public static get AddDoc() { return ImageEditorData.imageData.addDoc; } // prettier-ignore public static set AddDoc(addDoc: Opt<(doc: Doc | Doc[], annotationKey?: string) => boolean>) { ImageEditorData.set(this.imageData.open, this.imageData.rootDoc, this.imageData.source, addDoc); } // prettier-ignore } @observer @@ -93,7 +97,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl if (anchor) { if (!addAsAnnotation) anchor.backgroundColor = 'transparent'; addAsAnnotation && this.addDocument(anchor); - PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: visibleAnchor ? false : true } }, this.Document); + PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: !visibleAnchor } }, this.Document); return anchor; } return this.Document; @@ -106,10 +110,12 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl scrSize: (this.ScreenToLocalBoxXf().inverse().transformDirection(this.nativeSize.nativeWidth, this.nativeSize.nativeHeight)[0] / this.nativeSize.nativeWidth) * NumCast(this.layoutDoc._freeform_scale, 1), selected: this._props.isSelected(), }), - ({ forceFull, scrSize, selected }) => (this._curSuffix = selected ? '_o' : this.fieldKey === 'icon' ? '_m' : forceFull ? '_o' : scrSize < 0.25 ? '_s' : scrSize < 0.5 ? '_m' : scrSize < 0.8 ? '_l' : '_o'), + ({ forceFull, scrSize, selected }) => { + this._curSuffix = selected ? '_o' : this.fieldKey === 'icon' ? '_m' : forceFull ? '_o' : scrSize < 0.25 ? '_s' : scrSize < 0.5 ? '_m' : scrSize < 0.8 ? '_l' : '_o'; + }, { fireImmediately: true, delay: 1000 } ); - const layoutDoc = this.layoutDoc; + const { layoutDoc } = this; this._disposers.path = reaction( () => ({ nativeSize: this.nativeSize, width: NumCast(this.layoutDoc._width) }), ({ nativeSize, width }) => { @@ -121,10 +127,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl ); this._disposers.scroll = reaction( () => this.layoutDoc.layout_scrollTop, - s_top => { + sTop => { this._forcedScroll = true; - !this._ignoreScroll && this._mainCont.current && (this._mainCont.current.scrollTop = NumCast(s_top)); - this._mainCont.current?.scrollTo({ top: NumCast(s_top) }); + !this._ignoreScroll && this._mainCont.current && (this._mainCont.current.scrollTop = NumCast(sTop)); + this._mainCont.current?.scrollTo({ top: NumCast(sTop) }); this._forcedScroll = false; }, { fireImmediately: true } @@ -138,7 +144,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl @undoBatch drop = (e: Event, de: DragManager.DropEvent) => { if (de.complete.docDragData) { - let added: boolean | undefined = undefined; + let added: boolean | undefined; const targetIsBullseye = (ele: HTMLElement): boolean => { if (!ele) return false; if (ele === this._overlayIconRef.current) return true; @@ -168,7 +174,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl }; @undoBatch - resolution = () => (this.layoutDoc._showFullRes = !this.layoutDoc._showFullRes); + resolution = () => { + this.layoutDoc._showFullRes = !this.layoutDoc._showFullRes; + }; @undoBatch setNativeSize = action(() => { @@ -189,7 +197,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl const nh = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight']); const w = this.layoutDoc._width; const h = this.layoutDoc._height; - this.dataDoc[this.fieldKey + '-rotation'] = (NumCast(this.dataDoc[this.fieldKey + '-rotation']) + 90) % 360; + this.dataDoc[this.fieldKey + '_rotation'] = (NumCast(this.dataDoc[this.fieldKey + '_rotation']) + 90) % 360; this.dataDoc[this.fieldKey + '_nativeWidth'] = nh; this.dataDoc[this.fieldKey + '_nativeHeight'] = nw; this.layoutDoc._width = h; @@ -197,7 +205,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl }); crop = (region: Doc | undefined, addCrop?: boolean) => { - if (!region) return; + if (!region) return undefined; const cropping = Doc.MakeCopy(region, true); const regionData = region[DocData]; regionData.lockedPosition = true; @@ -223,8 +231,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl croppingProto.type = DocumentType.IMG; croppingProto.layout = ImageBox.LayoutString('data'); croppingProto.data = ObjectField.MakeCopy(this.dataDoc[this.fieldKey] as ObjectField); - croppingProto['data_nativeWidth'] = anchw; - croppingProto['data_nativeHeight'] = anchh; + croppingProto.data_nativeWidth = anchw; + croppingProto.data_nativeHeight = anchh; croppingProto.freeform_scale = viewScale; croppingProto.freeform_scale_min = viewScale; croppingProto.freeform_panX = anchx / viewScale; @@ -244,14 +252,14 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl return cropping; }; - specificContextMenu = (e: React.MouseEvent): void => { + specificContextMenu = (): void => { const field = Cast(this.dataDoc[this.fieldKey], ImageField); if (field) { const funcs: ContextMenuProps[] = []; funcs.push({ description: 'Rotate Clockwise 90', event: this.rotate, icon: 'redo-alt' }); funcs.push({ description: `Show ${this.layoutDoc._showFullRes ? 'Dynamic Res' : 'Full Res'}`, event: this.resolution, icon: 'expand' }); funcs.push({ description: 'Set Native Pixel Size', event: this.setNativeSize, icon: 'expand-arrows-alt' }); - funcs.push({ description: 'Copy path', event: () => Utils.CopyText(this.choosePath(field.url)), icon: 'copy' }); + funcs.push({ description: 'Copy path', event: () => ClientUtils.CopyText(this.choosePath(field.url)), icon: 'copy' }); funcs.push({ description: 'Open Image Editor', event: action(() => { @@ -270,7 +278,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl if (!url?.href) return ''; const lower = url.href.toLowerCase(); if (url.protocol === 'data') return url.href; - if (url.href.indexOf(window.location.origin) === -1 && url.href.indexOf('dashblobstore') === -1) return Utils.CorsProxy(url.href); + if (url.href.indexOf(window.location.origin) === -1 && url.href.indexOf('dashblobstore') === -1) return ClientUtils.CorsProxy(url.href); if (!/\.(png|jpg|jpeg|gif|webp)$/.test(lower) || lower.endsWith('/assets/unknown-file-icon-hi.png')) return `/assets/unknown-file-icon-hi.png`; const ext = extname(url.href); @@ -282,7 +290,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl TraceMobx(); const nativeWidth = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth'], NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth'], 500)); const nativeHeight = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight'], NumCast(this.layoutDoc[this.fieldKey + '_nativeHeight'], 500)); - const nativeOrientation = NumCast(this.dataDoc[this.fieldKey + '-nativeOrientation'], 1); + const nativeOrientation = NumCast(this.dataDoc[this.fieldKey + '_nativeOrientation'], 1); return { nativeWidth, nativeHeight, nativeOrientation }; } @computed get overlayImageIcon() { @@ -307,7 +315,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl <div className="imageBox-alternateDropTarget" ref={this._overlayIconRef} - onPointerDown={e => setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, e => (this.layoutDoc[`_${this.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined))} + onPointerDown={e => + setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, () => { + this.layoutDoc[`_${this.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined; + }) + } style={{ display: (this._props.isContentActive() !== false && DragManager.DocDragData?.canEmbed) || this.dataDoc[this.fieldKey + '_alternates'] ? 'block' : 'none', width: 'min(10%, 25px)', @@ -324,7 +336,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl @computed get paths() { const field = Cast(this.dataDoc[this.fieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc const alts = this.dataDoc[this.fieldKey + '_alternates'] as any as List<Doc>; // retrieve alternate documents that may be rendered as alternate images - const defaultUrl = new URL(Utils.prepend('/assets/unknown-file-icon-hi.png')); + const defaultUrl = new URL(ClientUtils.prepend('/assets/unknown-file-icon-hi.png')); const altpaths = alts ?.map(doc => (doc instanceof Doc ? ImageCast(doc[Doc.LayoutFieldKey(doc)])?.url ?? defaultUrl : defaultUrl)) @@ -344,8 +356,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl const backAlpha = backColor.red() === 0 && backColor.green() === 0 && backColor.blue() === 0 ? backColor.alpha() : 1; const srcpath = this.layoutDoc.hideImage ? '' : this.paths[0]; const fadepath = this.layoutDoc.hideImage ? '' : this.paths.lastElement(); - const { nativeWidth, nativeHeight, nativeOrientation } = this.nativeSize; - const rotation = NumCast(this.dataDoc[this.fieldKey + '-rotation']); + const { nativeWidth, nativeHeight /* , nativeOrientation */ } = this.nativeSize; + const rotation = NumCast(this.dataDoc[this.fieldKey + '_rotation']); const aspect = rotation % 180 ? nativeHeight / nativeWidth : 1; let transformOrigin = 'center center'; let transform = `translate(0%, 0%) rotate(${rotation}deg) scale(${aspect})`; @@ -361,12 +373,32 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl const usePath = this.layoutDoc[`_${this.fieldKey}_usePath`]; return ( - <div className="imageBox-cont" onPointerEnter={action(() => (this._isHovering = true))} onPointerLeave={action(() => (this._isHovering = false))} key={this.layoutDoc[Id]} ref={this.createDropTarget} onPointerDown={this.marqueeDown}> + <div + className="imageBox-cont" + onPointerEnter={action(() => { + this._isHovering = true; + })} + onPointerLeave={action(() => { + this._isHovering = false; + })} + key={this.layoutDoc[Id]} + ref={this.createDropTarget} + onPointerDown={this.marqueeDown}> <div className="imageBox-fader" style={{ opacity: backAlpha }}> - <img key="paths" src={srcpath} style={{ transform, transformOrigin }} onError={action(e => (this._error = e.toString()))} draggable={false} width={nativeWidth} /> + <img + alt="" + key="paths" + src={srcpath} + style={{ transform, transformOrigin }} + onError={action(e => { + this._error = e.toString(); + })} + draggable={false} + width={nativeWidth} + /> {fadepath === srcpath ? null : ( <div className={`imageBox-fadeBlocker${(this._isHovering && usePath === 'alternate:hover') || usePath === 'alternate' ? '-hover' : ''}`} style={{ transition: StrCast(this.layoutDoc.viewTransition, 'opacity 1000ms') }}> - <img className="imageBox-fadeaway" key="fadeaway" src={fadepath} style={{ transform, transformOrigin }} draggable={false} width={nativeWidth} /> + <img alt="" className="imageBox-fadeaway" key="fadeaway" src={fadepath} style={{ transform, transformOrigin }} draggable={false} width={nativeWidth} /> </div> )} </div> @@ -384,8 +416,15 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl } screenToLocalTransform = () => this.ScreenToLocalBoxXf().translate(0, NumCast(this.layoutDoc._layout_scrollTop) * this.ScreenToLocalBoxXf().Scale); marqueeDown = (e: React.PointerEvent) => { - if (!this.dataDoc[this.fieldKey]) return this.chooseImage(); - if (!e.altKey && e.button === 0 && NumCast(this.layoutDoc._freeform_scale, 1) <= NumCast(this.dataDoc.freeform_scaleMin, 1) && this._props.isContentActive() && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) { + if (!this.dataDoc[this.fieldKey]) { + this.chooseImage(); + } else if ( + !e.altKey && + e.button === 0 && + NumCast(this.layoutDoc._freeform_scale, 1) <= NumCast(this.dataDoc.freeform_scaleMin, 1) && + this._props.isContentActive() && + ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool) + ) { setupMoveUpEvents( this, e, @@ -419,7 +458,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl className="imageBox" onContextMenu={this.specificContextMenu} ref={this._mainCont} - onScroll={action(e => { + onScroll={action(() => { if (!this._forcedScroll) { if (this.layoutDoc._layout_scrollTop || this._mainCont.current?.scrollTop) { this._ignoreScroll = true; @@ -444,8 +483,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl renderDepth={this._props.renderDepth + 1} fieldKey={this.annotationKey} styleProvider={this._props.styleProvider} - isAnnotationOverlay={true} - annotationLayerHostsContent={true} + isAnnotationOverlay + annotationLayerHostsContent PanelWidth={this._props.PanelWidth} PanelHeight={this._props.PanelHeight} ScreenToLocalTransform={this.screenToLocalTransform} @@ -476,7 +515,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl selectionText={returnEmptyString} annotationLayer={this._annotationLayer.current} marqueeContainer={this._mainCont.current} - highlightDragSrcColor={''} + highlightDragSrcColor="" anchorMenuCrop={this.crop} /> )} @@ -489,7 +528,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl input.type = 'file'; input.multiple = true; input.accept = 'image/*'; - input.onchange = async _e => { + input.onchange = async () => { const file = input.files?.[0]; if (file) { const disposer = OverlayView.ShowSpinner(); diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index 31a2367fc..74773b244 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -1,8 +1,8 @@ import { action, computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { returnAlways, returnTrue } from '../../../Utils'; -import { Doc, Field, FieldResult } from '../../../fields/Doc'; +import { returnAlways, returnTrue } from '../../../ClientUtils'; +import { Doc, Field, FieldType, FieldResult } from '../../../fields/Doc'; import { List } from '../../../fields/List'; import { RichTextField } from '../../../fields/RichTextField'; import { ComputedField, ScriptField } from '../../../fields/ScriptField'; @@ -96,15 +96,15 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> { public static ApplyKVPScript(doc: Doc, key: string, kvpScript: KVPScript, forceOnDelegate?: boolean, setResult?: (value: FieldResult) => void) { const { script, type, onDelegate } = kvpScript; - //const target = onDelegate ? Doc.Layout(doc.layout) : Doc.GetProto(doc); // bcz: TODO need to be able to set fields on layout templates + // const target = onDelegate ? Doc.Layout(doc.layout) : Doc.GetProto(doc); // bcz: TODO need to be able to set fields on layout templates const target = forceOnDelegate || onDelegate || key.startsWith('_') ? doc : DocCast(doc.proto, doc); - let field: Field | undefined; + let field: FieldType | undefined; switch (type) { case 'computed': field = new ComputedField(script); break; // prettier-ignore case 'script': field = new ScriptField(script); break; // prettier-ignore default: { const _setCacheResult_ = (value: FieldResult) => { - field = value as Field; + field = value as FieldType; if (setResult) setResult?.(value); else target[key] = field; }; diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index f9e8ce4f3..c3afc198d 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -2,7 +2,8 @@ import { Tooltip } from '@mui/material'; import { action, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../Utils'; +import { returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../ClientUtils'; +import { emptyFunction } from '../../../Utils'; import { Doc, Field } from '../../../fields/Doc'; import { DocCast } from '../../../fields/Types'; import { DocumentOptions, FInfo } from '../../documents/Documents'; diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index 74e78c671..d1c8c62ed 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -1,21 +1,21 @@ import { action, computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { Doc, DocListCast, Field } from '../../../fields/Doc'; +import { Doc, DocListCast, Field, FieldType } from '../../../fields/Doc'; import { List } from '../../../fields/List'; import { listSpec } from '../../../fields/Schema'; import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types'; +import { Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; import { undoBatch } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; -import { ViewBoxBaseComponent } from '../DocComponent'; +import { PinProps, ViewBoxBaseComponent } from '../DocComponent'; import { StyleProp } from '../StyleProvider'; import { FieldView, FieldViewProps } from './FieldView'; import BigText from './LabelBigText'; import './LabelBox.scss'; -import { PinProps, PresBox } from './trails'; -import { Docs } from '../../documents/Documents'; +import { PresBox } from './trails'; @observer export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() { @@ -41,7 +41,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() { } @computed get Title() { - return Field.toString(this.dataDoc[this.fieldKey] as Field) || StrCast(this.Document.title); + return Field.toString(this.dataDoc[this.fieldKey] as FieldType) || StrCast(this.Document.title); } protected createDropTarget = (ele: HTMLDivElement) => { diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx index ff1e62885..0155defb7 100644 --- a/src/client/views/nodes/LinkAnchorBox.tsx +++ b/src/client/views/nodes/LinkAnchorBox.tsx @@ -1,11 +1,13 @@ import { action, computed, makeObservable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { Utils, emptyFunction, setupMoveUpEvents } from '../../../Utils'; +import { setupMoveUpEvents } from '../../../ClientUtils'; +import { Utils, emptyFunction } from '../../../Utils'; import { Doc } from '../../../fields/Doc'; import { NumCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; -import { DragManager, dropActionType } from '../../util/DragManager'; +import { DragManager } from '../../util/DragManager'; +import { dropActionType } from '../../util/DropActionTypes'; import { LinkFollower } from '../../util/LinkFollower'; import { SelectionManager } from '../../util/SelectionManager'; import { ViewBoxBaseComponent } from '../DocComponent'; @@ -39,7 +41,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() { } onPointerDown = (e: React.PointerEvent) => { - const linkSource = this.linkSource; + const { linkSource } = this; linkSource && setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, (e, doubleTap) => { if (doubleTap) LinkFollower.FollowLink(this.Document, linkSource, false); @@ -58,11 +60,10 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() { dragData.dropPropertiesToRemove = ['link_anchor_1_x', 'link_anchor_1_y', 'link_anchor_2_x', 'link_anchor_2_y', 'onClick']; DragManager.StartDocumentDrag([this._ref.current!], dragData, pt[0], pt[1]); return true; - } else { - this.layoutDoc[this.fieldKey + '_x'] = ((pt[0] - bounds.left) / bounds.width) * 100; - this.layoutDoc[this.fieldKey + '_y'] = ((pt[1] - bounds.top) / bounds.height) * 100; - this.layoutDoc.link_autoMoveAnchors = false; } + this.layoutDoc[this.fieldKey + '_x'] = ((pt[0] - bounds.left) / bounds.width) * 100; + this.layoutDoc[this.fieldKey + '_y'] = ((pt[1] - bounds.top) / bounds.height) * 100; + this.layoutDoc.link_autoMoveAnchors = false; } return false; }); diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx index 3a2509c3d..2593491cc 100644 --- a/src/client/views/nodes/LinkBox.tsx +++ b/src/client/views/nodes/LinkBox.tsx @@ -2,12 +2,13 @@ import { action, computed, IReactionDisposer, makeObservable, observable, reacti import { observer } from 'mobx-react'; import * as React from 'react'; import Xarrow from 'react-xarrows'; +import { DashColor, lightOrDark, returnFalse } from '../../../ClientUtils'; import { FieldResult } from '../../../fields/Doc'; import { DocCss, DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { DocCast, NumCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; -import { DashColor, emptyFunction, lightOrDark, returnFalse } from '../../../Utils'; +import { emptyFunction } from '../../../Utils'; import { DocumentManager } from '../../util/DocumentManager'; import { SnappingManager } from '../../util/SnappingManager'; import { ViewBoxBaseComponent } from '../DocComponent'; diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index c9c8f9260..539daf0bd 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -4,7 +4,8 @@ import { action, computed, makeObservable, observable, runInAction } from 'mobx' import { observer } from 'mobx-react'; import * as React from 'react'; import wiki from 'wikijs'; -import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnNone, setupMoveUpEvents } from '../../../Utils'; +import { returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnNone, setupMoveUpEvents } from '../../../ClientUtils'; +import { emptyFunction } from '../../../Utils'; import { Doc, Opt } from '../../../fields/Doc'; import { Cast, DocCast, NumCast, PromiseValue, StrCast } from '../../../fields/Types'; import { DocServer } from '../../DocServer'; diff --git a/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx b/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx index 7e99795b5..fe7f8d8b3 100644 --- a/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx +++ b/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx @@ -4,7 +4,8 @@ import { IconButton } from 'browndash-components'; import { IReactionDisposer, ObservableMap, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { returnFalse, unimplementedFunction } from '../../../../Utils'; +import { returnFalse } from '../../../../ClientUtils'; +import { unimplementedFunction } from '../../../../Utils'; import { Doc, Opt } from '../../../../fields/Doc'; import { NumCast, StrCast } from '../../../../fields/Types'; import { SelectionManager } from '../../../util/SelectionManager'; diff --git a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx index 08bea5d9d..d17c4298c 100644 --- a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx +++ b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx @@ -7,7 +7,8 @@ import { IReactionDisposer, ObservableMap, action, makeObservable, observable, r import { observer } from 'mobx-react'; import * as React from 'react'; import { CirclePicker, ColorResult } from 'react-color'; -import { returnFalse, setupMoveUpEvents, unimplementedFunction } from '../../../../Utils'; +import { returnFalse, setupMoveUpEvents } from '../../../../ClientUtils'; +import { unimplementedFunction } from '../../../../Utils'; import { Doc, Opt } from '../../../../fields/Doc'; import { NumCast, StrCast } from '../../../../fields/Types'; import { CalendarManager } from '../../../util/CalendarManager'; diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx index b73898f59..7855f8fe8 100644 --- a/src/client/views/nodes/MapBox/MapBox.tsx +++ b/src/client/views/nodes/MapBox/MapBox.tsx @@ -12,7 +12,8 @@ import * as React from 'react'; import { CirclePicker, ColorResult } from 'react-color'; import { Layer, MapProvider, MapRef, Map as MapboxMap, Marker, Source, ViewState, ViewStateChangeEvent } from 'react-map-gl'; import { MarkerEvent } from 'react-map-gl/dist/esm/types'; -import { Utils, emptyFunction, setupMoveUpEvents } from '../../../../Utils'; +import { ClientUtils, setupMoveUpEvents } from '../../../../ClientUtils'; +import { emptyFunction } from '../../../../Utils'; import { Doc, DocListCast, Field, LinkedTo, Opt } from '../../../../fields/Doc'; import { DocCast, NumCast, StrCast } from '../../../../fields/Types'; import { DocumentType } from '../../../documents/DocumentTypes'; @@ -22,14 +23,14 @@ import { DragManager } from '../../../util/DragManager'; import { LinkManager } from '../../../util/LinkManager'; import { SnappingManager } from '../../../util/SnappingManager'; import { UndoManager, undoable } from '../../../util/UndoManager'; -import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent'; +import { PinProps, ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent'; import { SidebarAnnos } from '../../SidebarAnnos'; import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm'; import { Colors } from '../../global/globalEnums'; import { DocumentView } from '../DocumentView'; import { FieldView, FieldViewProps, FocusViewOptions } from '../FieldView'; import { FormattedTextBox } from '../formattedText/FormattedTextBox'; -import { PinProps, PresBox } from '../trails'; +import { PresBox } from '../trails'; import { fastSpeedIcon, mediumSpeedIcon, slowSpeedIcon } from './AnimationSpeedIcons'; import { AnimationSpeed, AnimationStatus, AnimationUtility } from './AnimationUtility'; import { MapAnchorMenu } from './MapAnchorMenu'; @@ -401,8 +402,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem panelWidth = () => this._props.PanelWidth() / (this._props.NativeDimScaling?.() || 1) - this.sidebarWidth(); panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1); scrollXf = () => this.ScreenToLocalBoxXf().translate(0, NumCast(this.layoutDoc._layout_scrollTop)); - transparentFilter = () => [...this._props.childFilters(), Utils.TransparentBackgroundFilter]; - opaqueFilter = () => [...this._props.childFilters(), Utils.OpaqueBackgroundFilter]; + transparentFilter = () => [...this._props.childFilters(), ClientUtils.TransparentBackgroundFilter]; + opaqueFilter = () => [...this._props.childFilters(), ClientUtils.OpaqueBackgroundFilter]; infoWidth = () => this._props.PanelWidth() / 5; infoHeight = () => this._props.PanelHeight() / 5; anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick; diff --git a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx index 3eb051dbf..e857ef722 100644 --- a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx +++ b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx @@ -4,9 +4,11 @@ import { IReactionDisposer, ObservableMap, action, computed, makeObservable, obs import { observer } from 'mobx-react'; import * as React from 'react'; import { MapProvider, Map as MapboxMap } from 'react-map-gl'; -import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, setupMoveUpEvents } from '../../../../Utils'; -import { Doc, DocListCast, Field, LinkedTo, Opt } from '../../../../fields/Doc'; +import { ClientUtils, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, setupMoveUpEvents } from '../../../../ClientUtils'; +import { emptyFunction } from '../../../../Utils'; +import { Doc, DocListCast, LinkedTo, Opt } from '../../../../fields/Doc'; import { DocCss, Highlight } from '../../../../fields/DocSymbols'; +import { Id } from '../../../../fields/FieldSymbols'; import { DocCast, NumCast, StrCast } from '../../../../fields/Types'; import { DocumentType } from '../../../documents/DocumentTypes'; import { DocUtils, Docs } from '../../../documents/Documents'; @@ -16,15 +18,15 @@ import { LinkManager } from '../../../util/LinkManager'; import { SnappingManager } from '../../../util/SnappingManager'; import { Transform } from '../../../util/Transform'; import { UndoManager, undoable } from '../../../util/UndoManager'; -import { ViewBoxAnnotatableComponent } from '../../DocComponent'; +import { PinProps, ViewBoxAnnotatableComponent } from '../../DocComponent'; import { SidebarAnnos } from '../../SidebarAnnos'; import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm'; import { Colors } from '../../global/globalEnums'; import { DocumentView } from '../DocumentView'; -import { FocusViewOptions, FieldView, FieldViewProps } from '../FieldView'; +import { FieldView, FieldViewProps, FocusViewOptions } from '../FieldView'; import { MapAnchorMenu } from '../MapBox/MapAnchorMenu'; import { FormattedTextBox } from '../formattedText/FormattedTextBox'; -import { PinProps, PresBox } from '../trails'; +import { PresBox } from '../trails'; import './MapBox.scss'; /** @@ -268,7 +270,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps> sidebarDown = (e: React.PointerEvent) => { setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), true); }; - sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => { + sidebarMove = (e: PointerEvent) => { const bounds = this._ref.current!.getBoundingClientRect(); this.layoutDoc._layout_sidebarWidthPercent = '' + 100 * Math.max(0, 1 - (e.clientX - bounds.left) / bounds.width) + '%'; this.layoutDoc._layout_showSidebar = this.layoutDoc._layout_sidebarWidthPercent !== '0%'; @@ -276,7 +278,9 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps> return false; }; - setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => (this._setPreviewCursor = func); + setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => { + this._setPreviewCursor = func; + }; addDocumentWrapper = (doc: Doc | Doc[], annotationKey?: string) => this.addDocument(doc, annotationKey); @@ -285,8 +289,8 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps> panelWidth = () => this._props.PanelWidth() / (this._props.NativeDimScaling?.() || 1) - this.sidebarWidth(); panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1); scrollXf = () => this.ScreenToLocalBoxXf().translate(0, NumCast(this.layoutDoc._layout_scrollTop)); - transparentFilter = () => [...this._props.childFilters(), Utils.TransparentBackgroundFilter]; - opaqueFilter = () => [...this._props.childFilters(), Utils.OpaqueBackgroundFilter]; + transparentFilter = () => [...this._props.childFilters(), ClientUtils.TransparentBackgroundFilter]; + opaqueFilter = () => [...this._props.childFilters(), ClientUtils.OpaqueBackgroundFilter]; infoWidth = () => this._props.PanelWidth() / 5; infoHeight = () => this._props.PanelHeight() / 5; anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick; @@ -306,11 +310,11 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps> // center: new this.MicrosoftMaps.Location(loc.latitude, loc.longitude), // }); // - bingGeocode = (map: any, query: string) => { - return new Promise<{ latitude: number; longitude: number }>((res, reject) => { - //If search manager is not defined, load the search module. + bingGeocode = (map: any, query: string) => + new Promise<{ latitude: number; longitude: number }>((res, reject) => { + // If search manager is not defined, load the search module. if (!this._bingSearchManager) { - //Create an instance of the search manager and call the geocodeQuery function again. + // Create an instance of the search manager and call the geocodeQuery function again. this.MicrosoftMaps.loadModule('Microsoft.Maps.Search', () => { this._bingSearchManager = new this.MicrosoftMaps.Search.SearchManager(map.current); res(this.bingGeocode(map, query)); @@ -319,11 +323,10 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps> this._bingSearchManager.geocode({ where: query, callback: action((r: any) => res(r.results[0].location)), - errorCallback: (e: any) => reject(), + errorCallback: () => reject(), }); } }); - }; @observable bingSearchBarContents: any = this.Document.map; // For Bing Maps: The contents of the Bing search bar (string) @@ -368,7 +371,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps> this._bingMap.current.entities.remove(this.map_docToPinMap.get(temp)); } const newpin = new this.MicrosoftMaps.Pushpin(new this.MicrosoftMaps.Location(temp.latitude, temp.longitude)); - this.MicrosoftMaps.Events.addHandler(newpin, 'click', (e: any) => this.pushpinClicked(temp as Doc)); + this.MicrosoftMaps.Events.addHandler(newpin, 'click', () => this.pushpinClicked(temp as Doc)); if (!this._unmounting) { this._bingMap.current.entities.push(newpin); } @@ -383,7 +386,9 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps> this.toggleSidebar(); options.didMove = true; } - return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv))); + return new Promise<Opt<DocumentView>>(res => { + DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)); + }); }; /* * Pushpin onclick @@ -418,7 +423,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps> * Map OnClick */ @action - mapOnClick = (e: { location: { latitude: any; longitude: any } }) => { + mapOnClick = (/* e: { location: { latitude: any; longitude: any } } */) => { this._props.select(false); this.deselectPin(); }; @@ -442,22 +447,23 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps> * Updates maptype */ @action - updateMapType = () => (this.dataDoc.map_type = this._bingMap.current.getMapTypeId()); + updateMapType = () => { + this.dataDoc.map_type = this._bingMap.current.getMapTypeId(); + }; /* * For Bing Maps * Called by search button's onClick * Finds the geocode of the searched contents and sets location to that location - **/ + * */ @action - bingSearch = () => { - return this.bingGeocode(this._bingMap, this.bingSearchBarContents).then(location => { + bingSearch = () => + this.bingGeocode(this._bingMap, this.bingSearchBarContents).then(location => { this.dataDoc.latitude = location.latitude; this.dataDoc.longitude = location.longitude; this.dataDoc.map_zoom = this._bingMap.current.getZoom(); this.dataDoc.map = this.bingSearchBarContents; }); - }; /* * Returns doc w/ relevant info @@ -502,7 +508,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps> this._bingMap.current.entities.push(pushPin); - this.MicrosoftMaps.Events.addHandler(pushPin, 'click', (e: any) => this.pushpinClicked(pin)); + this.MicrosoftMaps.Events.addHandler(pushPin, 'click', () => this.pushpinClicked(pin)); // this.MicrosoftMaps.Events.addHandler(pushPin, 'dblclick', (e: any) => this.pushpinDblClicked(pushPin, pin)); this.map_docToPinMap.set(pin, pushPin); }; @@ -591,7 +597,9 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps> }; @action - searchbarOnEdit = (newText: string) => (this.bingSearchBarContents = newText); + searchbarOnEdit = (newText: string) => { + this.bingSearchBarContents = newText; + }; recolorPin = (pin: Doc, color?: string) => { this._bingMap.current.entities.remove(this.map_docToPinMap.get(pin)); @@ -620,7 +628,9 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps> this._disposers.mapLocation = reaction( () => this.Document.map, - mapLoc => (this.bingSearchBarContents = mapLoc), + mapLoc => { + this.bingSearchBarContents = mapLoc; + }, { fireImmediately: true } ); this._disposers.highlight = reaction( @@ -720,6 +730,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps> MapBoxContainer._rerenderDelay = 0; } this._rerenderTimeout = undefined; + // eslint-disable-next-line operator-assignment this.Document[DocCss] = this.Document[DocCss] + 1; }), MapBoxContainer._rerenderDelay); return null; @@ -752,18 +763,19 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps> <svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="magnifying-glass" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" color="#DFDFDF"> <path fill="currentColor" - d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352a144 144 0 1 0 0-288 144 144 0 1 0 0 288z"></path> + d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352a144 144 0 1 0 0-288 144 144 0 1 0 0 288z" + /> </svg> } onClick={this.bingSearch} type={Type.TERT} /> <div style={{ width: 30, height: 30 }} ref={this._dragRef} onPointerDown={this.dragToggle}> - <Button tooltip="drag to place a pushpin" icon={<FontAwesomeIcon size={'lg'} icon={'bullseye'} />} /> + <Button tooltip="drag to place a pushpin" icon={<FontAwesomeIcon size="lg" icon="bullseye" />} /> </div> </div> <MapProvider> - <MapboxMap id="mabox-map" mapStyle={`mapbox://styles/mapbox/streets-v9`} mapboxAccessToken={mapboxApiKey} /> + <MapboxMap id="mabox-map" mapStyle="mapbox://styles/mapbox/streets-v9" mapboxAccessToken={mapboxApiKey} /> </MapProvider> {/* @@ -782,7 +794,8 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps> .filter(anno => !anno.layout_unrendered) .map((pushpin, i) => ( <DocumentView - key={i} + key={pushpin[Id]} + // eslint-disable-next-line react/jsx-props-no-spreading {...this._props} renderDepth={this._props.renderDepth + 1} Document={pushpin} @@ -821,12 +834,13 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps> <div className="mapBox-sidebar" style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}> <SidebarAnnos ref={this._sidebarRef} + // eslint-disable-next-line react/jsx-props-no-spreading {...this._props} fieldKey={this.fieldKey} Document={this.Document} layoutDoc={this.layoutDoc} dataDoc={this.dataDoc} - usePanelWidth={true} + usePanelWidth showSidebar={this.SidebarShown} nativeWidth={NumCast(this.layoutDoc._nativeWidth)} whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 1274220b6..8140c0ca7 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -4,6 +4,7 @@ import { observer } from 'mobx-react'; import * as Pdfjs from 'pdfjs-dist'; import 'pdfjs-dist/web/pdf_viewer.css'; import * as React from 'react'; +import { ClientUtils, returnFalse, setupMoveUpEvents } from '../../../ClientUtils'; import { Doc, DocListCast, Opt } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; @@ -12,7 +13,7 @@ import { ComputedField } from '../../../fields/ScriptField'; import { Cast, FieldValue, ImageCast, NumCast, StrCast } from '../../../fields/Types'; import { ImageField, PdfField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; -import { emptyFunction, returnFalse, setupMoveUpEvents, Utils } from '../../../Utils'; +import { emptyFunction } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; import { DocumentManager } from '../../util/DocumentManager'; @@ -23,16 +24,16 @@ import { CollectionFreeFormView } from '../collections/collectionFreeForm'; import { CollectionStackingView } from '../collections/CollectionStackingView'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; -import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent'; +import { PinProps, ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent'; import { Colors } from '../global/globalEnums'; import { CreateImage } from '../nodes/WebBoxRenderer'; import { PDFViewer } from '../pdf/PDFViewer'; import { SidebarAnnos } from '../SidebarAnnos'; import { DocumentView, OpenWhere } from './DocumentView'; -import { FocusViewOptions, FieldView, FieldViewProps } from './FieldView'; +import { FieldView, FieldViewProps, FocusViewOptions } from './FieldView'; import { ImageBox } from './ImageBox'; import './PDFBox.scss'; -import { PinProps, PresBox } from './trails'; +import { PresBox } from './trails'; @observer export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface { @@ -128,9 +129,9 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem croppingProto.proto = Cast(this.Document.proto, Doc, null)?.proto; // set proto of cropping's data doc to be IMAGE_PROTO croppingProto.type = DocumentType.IMG; croppingProto.layout = ImageBox.LayoutString('data'); - croppingProto.data = new ImageField(Utils.CorsProxy('http://www.cs.brown.edu/~bcz/noImage.png')); - croppingProto['data_nativeWidth'] = anchw; - croppingProto['data_nativeHeight'] = anchh; + croppingProto.data = new ImageField(ClientUtils.CorsProxy('http://www.cs.brown.edu/~bcz/noImage.png')); + croppingProto.data_nativeWidth = anchw; + croppingProto.data_nativeHeight = anchh; if (addCrop) { DocUtils.MakeLink(region, cropping, { link_relationship: 'cropped image' }); } @@ -146,8 +147,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem (NumCast(region.x) * this._props.PanelWidth()) / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']), 4 ) - .then((data_url: any) => { - Utils.convertDataUri(data_url, region[Id]).then(returnedfilename => + .then((dataUrl: any) => { + ClientUtils.convertDataUri(dataUrl, region[Id]).then(returnedfilename => setTimeout( action(() => { croppingProto.data = new ImageField(returnedfilename); @@ -182,8 +183,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem (iconFile: string, nativeWidth: number, nativeHeight: number) => { setTimeout(() => { this.dataDoc.icon = new ImageField(iconFile); - this.dataDoc['icon_nativeWidth'] = nativeWidth; - this.dataDoc['icon_nativeHeight'] = nativeHeight; + this.dataDoc.icon_nativeWidth = nativeWidth; + this.dataDoc.icon_nativeHeight = nativeHeight; }, 500); } ); @@ -231,11 +232,13 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem options.didMove = true; this.toggleSidebar(false); } - return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv))); + return new Promise<Opt<DocumentView>>(res => { + DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)); + }); }; getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { - let ele: Opt<HTMLDivElement> = undefined; + let ele: Opt<HTMLDivElement>; if (this._pdfViewer?.selectionContent()) { ele = document.createElement('div'); ele.append(this._pdfViewer.selectionContent()!); @@ -451,7 +454,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem !options && ContextMenu.Instance.addItem({ description: 'Options...', subitems: optionItems, icon: 'asterisk' }); const help = cm.findByDescription('Help...'); const helpItems: ContextMenuProps[] = help && 'subitems' in help ? help.subitems : []; - helpItems.push({ description: 'Copy path', event: () => this.pdfUrl && Utils.CopyText(Utils.prepend('') + this.pdfUrl.url.pathname), icon: 'expand-arrows-alt' }); + helpItems.push({ description: 'Copy path', event: () => this.pdfUrl && ClientUtils.CopyText(ClientUtils.prepend('') + this.pdfUrl.url.pathname), icon: 'expand-arrows-alt' }); !help && ContextMenu.Instance.addItem({ description: 'Help...', noexpand: true, subitems: helpItems, icon: 'asterisk' }); }; diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx index 1f976f926..40199cce1 100644 --- a/src/client/views/nodes/RecordingBox/RecordingBox.tsx +++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx @@ -11,17 +11,18 @@ import { Upload } from '../../../../server/SharedMediaTypes'; import { 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 { ScriptingGlobals } from '../../../util/ScriptingGlobals'; import { Presentation } from '../../../util/TrackMovements'; import { undoBatch } from '../../../util/UndoManager'; import { ViewBoxBaseComponent } from '../../DocComponent'; import { CollectionFreeFormView } from '../../collections/collectionFreeForm/CollectionFreeFormView'; -import { media_state } from '../AudioBox'; +import { mediaState } from '../AudioBox'; import { FieldView, FieldViewProps } from '../FieldView'; import { VideoBox } from '../VideoBox'; import { RecordingView } from './RecordingView'; import { DocData } from '../../../../fields/DocSymbols'; +import { dropActionType } from '../../../util/DropActionTypes'; @observer export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() { @@ -78,7 +79,7 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() { }, 100); //could break if recording takes too long to turn into videobox. If so, either increase time on setTimeout below or find diff place to do this setTimeout(() => Doc.RemFromMyOverlay(remDoc), 1000); - Doc.UserDoc().workspaceRecordingState = media_state.Paused; + Doc.UserDoc().workspaceRecordingState = mediaState.Paused; Doc.AddDocToList(Doc.UserDoc(), 'workspaceRecordings', remDoc); } } @@ -110,7 +111,7 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() { RecordingBox.screengrabber = docView.ComponentView as RecordingBox; RecordingBox.screengrabber.Record?.(); }); - Doc.UserDoc().workspaceRecordingState = media_state.Recording; + Doc.UserDoc().workspaceRecordingState = mediaState.Recording; } /** @@ -150,7 +151,7 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() { if (docView?.ComponentView instanceof VideoBox) { docView.ComponentView.Play(); } - Doc.UserDoc().workspaceReplayingState = media_state.Playing; + Doc.UserDoc().workspaceReplayingState = mediaState.Playing; } public static pauseWorkspaceReplaying(doc: Doc) { @@ -159,7 +160,7 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() { if (videoBox) { videoBox.Pause(); } - Doc.UserDoc().workspaceReplayingState = media_state.Paused; + Doc.UserDoc().workspaceReplayingState = mediaState.Paused; } public static stopWorkspaceReplaying(value: Doc) { @@ -224,7 +225,7 @@ ScriptingGlobals.add(function getWorkspaceRecordings() { return new List<any>(['Record Workspace', `Record Webcam`, ...DocListCast(Doc.UserDoc().workspaceRecordings)]); }); ScriptingGlobals.add(function isWorkspaceRecording() { - return Doc.UserDoc().workspaceRecordingState === media_state.Recording; + return Doc.UserDoc().workspaceRecordingState === mediaState.Recording; }); ScriptingGlobals.add(function isWorkspaceReplaying() { return Doc.UserDoc().workspaceReplayingState; diff --git a/src/client/views/nodes/RecordingBox/RecordingView.tsx b/src/client/views/nodes/RecordingBox/RecordingView.tsx index f7ed82643..9c05a3e94 100644 --- a/src/client/views/nodes/RecordingBox/RecordingView.tsx +++ b/src/client/views/nodes/RecordingBox/RecordingView.tsx @@ -4,7 +4,7 @@ import { IconContext } from 'react-icons'; import { FaCheckCircle } from 'react-icons/fa'; import { MdBackspace } from 'react-icons/md'; import { Upload } from '../../../../server/SharedMediaTypes'; -import { returnFalse, returnTrue, setupMoveUpEvents } from '../../../../Utils'; +import { returnFalse, returnTrue, setupMoveUpEvents } from '../../../../ClientUtils'; import { Networking } from '../../../Network'; import { Presentation, TrackMovements } from '../../../util/TrackMovements'; import { ProgressBar } from './ProgressBar'; diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx index 1e3933ac3..e29e47514 100644 --- a/src/client/views/nodes/ScreenshotBox.tsx +++ b/src/client/views/nodes/ScreenshotBox.tsx @@ -4,30 +4,31 @@ import * as React from 'react'; import { computed, makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; // import { BufferAttribute, Camera, Vector2, Vector3 } from 'three'; +import { returnFalse, returnOne, returnZero } from '../../../ClientUtils'; +import { emptyFunction } from '../../../Utils'; import { DateField } from '../../../fields/DateField'; import { Doc } from '../../../fields/Doc'; +import { DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { ComputedField } from '../../../fields/ScriptField'; import { Cast, DocCast, NumCast } from '../../../fields/Types'; import { AudioField, VideoField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; -import { emptyFunction, returnFalse, returnOne, returnZero } from '../../../Utils'; -import { DocUtils } from '../../documents/Documents'; -import { DocumentType } from '../../documents/DocumentTypes'; import { Networking } from '../../Network'; +import { DocumentType } from '../../documents/DocumentTypes'; +import { DocUtils } from '../../documents/Documents'; import { CaptureManager } from '../../util/CaptureManager'; import { SettingsManager } from '../../util/SettingsManager'; import { TrackMovements } from '../../util/TrackMovements'; -import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; -import { CollectionStackedTimeline } from '../collections/CollectionStackedTimeline'; import { ContextMenu } from '../ContextMenu'; import { ViewBoxAnnotatableComponent } from '../DocComponent'; -import { media_state } from './AudioBox'; +import { CollectionStackedTimeline } from '../collections/CollectionStackedTimeline'; +import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; +import { mediaState } from './AudioBox'; import { FieldView, FieldViewProps } from './FieldView'; import { FormattedTextBox } from './formattedText/FormattedTextBox'; import './ScreenshotBox.scss'; import { VideoBox } from './VideoBox'; -import { DocData } from '../../../fields/DocSymbols'; declare class MediaRecorder { constructor(e: any, options?: any); // whatever MediaRecorder has @@ -175,7 +176,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>() ref={r => { this._videoRef = r; setTimeout(() => { - if (this.layoutDoc.mediaState === media_state.PendingRecording && this._videoRef) { + if (this.layoutDoc.mediaState === mediaState.PendingRecording && this._videoRef) { this.toggleRecording(); } }, 100); diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx index 8c65fd34e..60d7e4b00 100644 --- a/src/client/views/nodes/ScriptingBox.tsx +++ b/src/client/views/nodes/ScriptingBox.tsx @@ -2,7 +2,7 @@ let ReactTextareaAutocomplete = require('@webscopeio/react-textarea-autocomplete import { action, computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { returnAlways, returnEmptyString } from '../../../Utils'; +import { returnAlways, returnEmptyString } from '../../../ClientUtils'; import { Doc } from '../../../fields/Doc'; import { List } from '../../../fields/List'; import { listSpec } from '../../../fields/Schema'; diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 4773a21c9..60141b2a6 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -3,6 +3,7 @@ import { action, computed, IReactionDisposer, makeObservable, observable, Observ import { observer } from 'mobx-react'; import { basename } from 'path'; import * as React from 'react'; +import { ClientUtils, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents } from '../../../ClientUtils'; import { Doc, Opt, StrListCast } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { InkTool } from '../../../fields/InkField'; @@ -10,11 +11,11 @@ import { List } from '../../../fields/List'; import { ObjectField } from '../../../fields/ObjectField'; import { Cast, NumCast, StrCast } from '../../../fields/Types'; import { AudioField, ImageField, VideoField } from '../../../fields/URLField'; -import { emptyFunction, formatTime, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils'; +import { emptyFunction, formatTime } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; import { DocumentManager } from '../../util/DocumentManager'; -import { dropActionType } from '../../util/DragManager'; +import { dropActionType } from '../../util/DropActionTypes'; import { FollowLinkScript } from '../../util/LinkFollower'; import { LinkManager } from '../../util/LinkManager'; import { ReplayMovements } from '../../util/ReplayMovements'; @@ -23,14 +24,14 @@ import { CollectionFreeFormView } from '../collections/collectionFreeForm/Collec import { CollectionStackedTimeline, TrimScope } from '../collections/CollectionStackedTimeline'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; -import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent'; +import { PinProps, ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent'; import { MarqueeAnnotator } from '../MarqueeAnnotator'; import { AnchorMenu } from '../pdf/AnchorMenu'; import { StyleProp } from '../StyleProvider'; import { DocumentView } from './DocumentView'; import { FieldView, FieldViewProps, FocusViewOptions } from './FieldView'; import { RecordingBox } from './RecordingBox'; -import { PinProps, PresBox } from './trails'; +import { PresBox } from './trails'; import './VideoBox.scss'; /** @@ -303,7 +304,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl const retitled = StrCast(this.Document.title).replace(/[ -\.:]/g, ''); const encodedFilename = encodeURIComponent(('snapshot' + retitled + '_' + (this.layoutDoc._layout_currentTimecode || 0).toString()).replace(/[\.\/\?\=]/g, '_')); const filename = basename(encodedFilename); - Utils.convertDataUri(dataUrl, filename).then((returnedFilename: string) => returnedFilename && (cb ?? this.createSnapshotLink)(returnedFilename, downX, downY)); + ClientUtils.convertDataUri(dataUrl, filename).then((returnedFilename: string) => returnedFilename && (cb ?? this.createSnapshotLink)(returnedFilename, downX, downY)); } }; @@ -318,7 +319,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl // creates link for snapshot createSnapshotLink = (imagePath: string, downX?: number, downY?: number) => { - const url = !imagePath.startsWith('/') ? Utils.CorsProxy(imagePath) : imagePath; + const url = !imagePath.startsWith('/') ? ClientUtils.CorsProxy(imagePath) : imagePath; const width = NumCast(this.layoutDoc._width) || 1; const height = NumCast(this.layoutDoc._height); const imageSnapshot = Docs.Create.ImageDocument(url, { @@ -399,7 +400,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl canvas.getContext('2d')?.drawImage(video, 0, 0, video.videoWidth, video.videoHeight, 0, 0, 100, 100); const retitled = StrCast(this.Document.title).replace(/[ -\.:]/g, ''); const encodedFilename = encodeURIComponent('thumbnail' + retitled + '_' + video.currentTime.toString().replace(/\./, '_')); - thumbnailPromises?.push(Utils.convertDataUri(canvas.toDataURL(), basename(encodedFilename), true)); + thumbnailPromises?.push(ClientUtils.convertDataUri(canvas.toDataURL(), basename(encodedFilename), true)); const newTime = video.currentTime + video.duration / (VideoBox.numThumbnails - 1); if (newTime < video.duration) { video.currentTime = newTime; @@ -480,7 +481,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl subitems.push({ description: 'Copy path', event: () => { - Utils.CopyText(url); + ClientUtils.CopyText(url); }, icon: 'expand-arrows-alt', }); @@ -644,7 +645,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl playFrom = (seekTimeInSeconds: number, endTime?: number, fullPlay: boolean = false) => { clearTimeout(this._playRegionTimer); this._playRegionTimer = undefined; - if (Number.isNaN(this.player?.duration)) { + if (isNaN(this.player?.duration)) { setTimeout(() => this.playFrom(seekTimeInSeconds, endTime), 500); } else if (this.player) { // trimBounds override requested playback bounds @@ -806,7 +807,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl marqueeOffset = () => [((this.panelWidth() / 2) * (1 - this.heightPercent / 100)) / (this.heightPercent / 100), 0]; - timelineDocFilter = () => [`_isTimelineLabel:true,${Utils.noRecursionHack}:x`]; + timelineDocFilter = () => [`_isTimelineLabel:true,${ClientUtils.noRecursionHack}:x`]; // renders video controls componentUI = (boundsLeft: number, boundsTop: number) => { diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 033b01d24..446e83dd3 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -4,7 +4,8 @@ import { action, computed, IReactionDisposer, makeObservable, observable, Observ import { observer } from 'mobx-react'; import * as React from 'react'; import * as WebRequest from 'web-request'; -import { Doc, DocListCast, Field, Opt } from '../../../fields/Doc'; +import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, DivHeight, getWordAtPoint, lightOrDark, returnFalse, returnOne, returnZero, setupMoveUpEvents, smoothScroll } from '../../../ClientUtils'; +import { Doc, DocListCast, Field, FieldType, Opt } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { HtmlField } from '../../../fields/HtmlField'; import { InkTool } from '../../../fields/InkField'; @@ -14,7 +15,7 @@ import { listSpec } from '../../../fields/Schema'; import { Cast, NumCast, StrCast, WebCast } from '../../../fields/Types'; import { ImageField, WebField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; -import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, DivHeight, emptyFunction, getWordAtPoint, lightOrDark, returnFalse, returnOne, returnZero, setupMoveUpEvents, smoothScroll, stringHash, Utils } from '../../../Utils'; +import { emptyFunction, stringHash, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentManager } from '../../util/DocumentManager'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; @@ -24,7 +25,7 @@ import { MarqueeOptionsMenu } from '../collections/collectionFreeForm'; import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; -import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent'; +import { PinProps, ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent'; import { Colors } from '../global/globalEnums'; import { LightboxView } from '../LightboxView'; import { MarqueeAnnotator } from '../MarqueeAnnotator'; @@ -36,7 +37,7 @@ import { StyleProp } from '../StyleProvider'; import { DocumentView, OpenWhere } from './DocumentView'; import { FieldView, FieldViewProps, FocusViewOptions } from './FieldView'; import { LinkInfo } from './LinkDocPreview'; -import { PinProps, PresBox } from './trails'; +import { PresBox } from './trails'; import './WebBox.scss'; const { CreateImage } = require('./WebBoxRenderer'); const _global = (window /* browser */ || global) /* node */ as any; @@ -143,7 +144,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem const nativeHeight = (nativeWidth * this._props.PanelHeight()) / this._props.PanelWidth(); var htmlString = this._iframe.contentDocument && new XMLSerializer().serializeToString(this._iframe.contentDocument); if (!htmlString) { - htmlString = await (await fetch(Utils.CorsProxy(this.webField!.href))).text(); + htmlString = await (await fetch(ClientUtils.CorsProxy(this.webField!.href))).text(); } this.layoutDoc.thumb = undefined; this.Document.thumbLockout = true; // lock to prevent multiple thumb updates. @@ -219,7 +220,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem } } // else it's an HTMLfield } else if (this.webField && !this.dataDoc.text) { - WebRequest.get(Utils.CorsProxy(this.webField.href)) // + WebRequest.get(ClientUtils.CorsProxy(this.webField.href)) // .then(result => result && (this.dataDoc.text = htmlToText(result.content))); } @@ -254,7 +255,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem const clientRects = selRange.getClientRects(); for (let i = 0; i < clientRects.length; i++) { const rect = clientRects.item(i); - const mainrect = this._url ? { translateX: 0, translateY: 0, scale: 1 } : Utils.GetScreenTransform(this._mainCont.current); + const mainrect = this._url ? { translateX: 0, translateY: 0, scale: 1 } : ClientUtils.GetScreenTransform(this._mainCont.current); if (rect && rect.width !== this._mainCont.current.clientWidth) { const annoBox = document.createElement('div'); annoBox.className = 'marqueeAnnotator-annotationBox'; @@ -283,7 +284,14 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem focus = (anchor: Doc, options: FocusViewOptions) => { if (anchor !== this.Document && this._outerRef.current) { const windowHeight = this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1); - const scrollTo = Utils.scrollIntoView(NumCast(anchor.y), NumCast(anchor._height), NumCast(this.layoutDoc._layout_scrollTop), windowHeight, windowHeight * 0.1, Math.max(NumCast(anchor.y) + NumCast(anchor._height), this._scrollHeight)); + const scrollTo = ClientUtils.scrollIntoView( + NumCast(anchor.y), + NumCast(anchor._height), + NumCast(this.layoutDoc._layout_scrollTop), + windowHeight, + windowHeight * 0.1, + Math.max(NumCast(anchor.y) + NumCast(anchor._height), this._scrollHeight) + ); if (scrollTo !== undefined) { if (this._initialScroll === undefined) { const focusTime = options.zoomTime ?? 500; @@ -356,7 +364,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem this._textAnnotationCreator = undefined; this.DocumentView?.()?.cleanupPointerEvents(); // pointerup events aren't generated on containing document view, so we have to invoke it here. if (this._iframe?.contentWindow && this._iframe.contentDocument && !this._iframe.contentWindow.getSelection()?.isCollapsed) { - const mainContBounds = Utils.GetScreenTransform(this._mainCont.current!); + const mainContBounds = ClientUtils.GetScreenTransform(this._mainCont.current!); const scale = (this._props.NativeDimScaling?.() || 1) * mainContBounds.scale; const sel = this._iframe.contentWindow.getSelection(); if (sel) { @@ -491,7 +499,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem runInAction(() => this._warning++); href = undefined; } - let requrlraw = decodeURIComponent(href?.replace(Utils.prepend('') + '/corsProxy/', '') ?? this._url.toString()); + let requrlraw = decodeURIComponent(href?.replace(ClientUtils.prepend('') + '/corsProxy/', '') ?? this._url.toString()); if (requrlraw !== this._url.toString()) { if (requrlraw.match(/q=.*&/)?.length && this._url.toString().match(/q=.*&/)?.length) { const matches = requrlraw.match(/[^a-zA-z]q=[^&]*/g); @@ -553,7 +561,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem const batch = UndoManager.StartBatch('webclick'); e.stopPropagation(); setTimeout(() => { - this.setData(href.replace(Utils.prepend(''), origin)); + this.setData(href.replace(ClientUtils.prepend(''), origin)); batch.end(); }); if (this._outerRef.current) { @@ -698,7 +706,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem }; @action - setData = (data: Field | Promise<RefField | undefined>) => { + setData = (data: FieldType | Promise<RefField | undefined>) => { if (!(typeof data === 'string') && !(data instanceof WebField)) return false; if (Field.toString(data) === this._url) return false; this._scrollHeight = 0; @@ -816,7 +824,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem ); } if (field instanceof WebField) { - const url = this.layoutDoc[this.fieldKey + '_useCors'] ? Utils.CorsProxy(this._webUrl) : this._webUrl; + const url = this.layoutDoc[this.fieldKey + '_useCors'] ? ClientUtils.CorsProxy(this._webUrl) : this._webUrl; const scripts = this.dataDoc[this.fieldKey + '_allowScripts'] || this._webUrl.includes('wikipedia.org') || this._webUrl.includes('google.com') || this._webUrl.startsWith('https://bing'); //if (!scripts) console.log('No scripts for: ' + url); return ( @@ -1081,8 +1089,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1); scrollXf = () => this.ScreenToLocalBoxXf().translate(0, NumCast(this.layoutDoc._layout_scrollTop)); anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick; - transparentFilter = () => [...this._props.childFilters(), Utils.TransparentBackgroundFilter]; - opaqueFilter = () => [...this._props.childFilters(), Utils.noDragDocsFilter, ...(SnappingManager.CanEmbed ? [] : [Utils.OpaqueBackgroundFilter])]; + transparentFilter = () => [...this._props.childFilters(), ClientUtils.TransparentBackgroundFilter]; + opaqueFilter = () => [...this._props.childFilters(), ClientUtils.noDragDocsFilter, ...(SnappingManager.CanEmbed ? [] : [ClientUtils.OpaqueBackgroundFilter])]; childStyleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string): any => { if (doc instanceof Doc && property === StyleProp.PointerEvents) { if (this.inlineTextAnnotations.includes(doc)) return 'none'; diff --git a/src/client/views/nodes/calendarBox/CalendarBox.tsx b/src/client/views/nodes/calendarBox/CalendarBox.tsx index 748c3322e..8577510e3 100644 --- a/src/client/views/nodes/calendarBox/CalendarBox.tsx +++ b/src/client/views/nodes/calendarBox/CalendarBox.tsx @@ -4,7 +4,7 @@ import multiMonthPlugin from '@fullcalendar/multimonth'; import { makeObservable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { dateRangeStrToDates } from '../../../../Utils'; +import { dateRangeStrToDates } from '../../../../ClientUtils'; import { Doc } from '../../../../fields/Doc'; import { StrCast } from '../../../../fields/Types'; import { ViewBoxBaseComponent } from '../../DocComponent'; diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx index 7335c9286..dee7d70bb 100644 --- a/src/client/views/nodes/formattedText/DashDocView.tsx +++ b/src/client/views/nodes/formattedText/DashDocView.tsx @@ -3,10 +3,11 @@ import { observer } from 'mobx-react'; import { NodeSelection } from 'prosemirror-state'; import * as React from 'react'; import * as ReactDOM from 'react-dom/client'; +import { Utils } from '../../../../Utils'; +import { returnFalse } from '../../../../ClientUtils'; import { Doc } from '../../../../fields/Doc'; import { Height, Width } from '../../../../fields/DocSymbols'; import { NumCast } from '../../../../fields/Types'; -import { emptyFunction, returnFalse, Utils } from '../../../../Utils'; import { DocServer } from '../../../DocServer'; import { Docs, DocUtils } from '../../../documents/Documents'; import { Transform } from '../../../util/Transform'; diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index 439d4785e..eaa8fffaa 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -1,15 +1,17 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@mui/material'; -import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction, trace } from 'mobx'; +import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; +import { NodeSelection } from 'prosemirror-state'; import * as React from 'react'; import * as ReactDOM from 'react-dom/client'; +import { returnFalse, returnZero, setupMoveUpEvents } from '../../../../ClientUtils'; import { Doc, DocListCast, Field } from '../../../../fields/Doc'; import { List } from '../../../../fields/List'; import { listSpec } from '../../../../fields/Schema'; import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField'; import { Cast, DocCast } from '../../../../fields/Types'; -import { emptyFunction, returnFalse, returnZero, setupMoveUpEvents } from '../../../../Utils'; +import { emptyFunction } from '../../../../Utils'; import { DocServer } from '../../../DocServer'; import { CollectionViewType } from '../../../documents/DocumentTypes'; import { Transform } from '../../../util/Transform'; @@ -21,8 +23,6 @@ import { ObservableReactComponent } from '../../ObservableReactComponent'; import { OpenWhere } from '../DocumentView'; import './DashFieldView.scss'; import { FormattedTextBox } from './FormattedTextBox'; -import { DocData } from '../../../../fields/DocSymbols'; -import { NodeSelection } from 'prosemirror-state'; export class DashFieldView { dom: HTMLDivElement; // container for label and value diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index a2db2a1cc..31252e0ab 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1,3 +1,4 @@ +/* eslint-disable jsx-a11y/no-static-element-interactions */ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@mui/material'; @@ -12,8 +13,9 @@ import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from ' import { EditorView } from 'prosemirror-view'; import * as React from 'react'; import { BsMarkdownFill } from 'react-icons/bs'; +import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, DivWidth, returnFalse, returnZero, setupMoveUpEvents, smoothScroll } from '../../../../ClientUtils'; import { DateField } from '../../../../fields/DateField'; -import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../../fields/Doc'; +import { Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../../fields/Doc'; import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, DocData, ForceServerWrite, UpdatingFromServer } from '../../../../fields/DocSymbols'; import { Id } from '../../../../fields/FieldSymbols'; import { InkTool } from '../../../../fields/InkField'; @@ -23,14 +25,15 @@ import { RichTextField } from '../../../../fields/RichTextField'; import { ComputedField } from '../../../../fields/ScriptField'; import { BoolCast, Cast, DateCast, DocCast, FieldValue, NumCast, RTFCast, ScriptCast, StrCast } from '../../../../fields/Types'; import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util'; -import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, DivWidth, emptyFunction, numberRange, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils'; +import { emptyFunction, numberRange, unimplementedFunction, Utils } from '../../../../Utils'; import { gptAPICall, GPTCallType } from '../../../apis/gpt/GPT'; import { DocServer } from '../../../DocServer'; import { Docs, DocUtils } from '../../../documents/Documents'; import { CollectionViewType } from '../../../documents/DocumentTypes'; import { DictationManager } from '../../../util/DictationManager'; import { DocumentManager } from '../../../util/DocumentManager'; -import { DragManager, dropActionType } from '../../../util/DragManager'; +import { DragManager } from '../../../util/DragManager'; +import { dropActionType } from '../../../util/DropActionTypes'; import { MakeTemplate } from '../../../util/DropConverter'; import { LinkManager } from '../../../util/LinkManager'; import { RTFMarkup } from '../../../util/RTFMarkup'; @@ -42,18 +45,18 @@ import { CollectionStackingView } from '../../collections/CollectionStackingView import { CollectionTreeView } from '../../collections/CollectionTreeView'; import { ContextMenu } from '../../ContextMenu'; import { ContextMenuProps } from '../../ContextMenuItem'; -import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent'; +import { PinProps, ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent'; import { Colors } from '../../global/globalEnums'; import { LightboxView } from '../../LightboxView'; import { AnchorMenu } from '../../pdf/AnchorMenu'; import { GPTPopup } from '../../pdf/GPTPopup/GPTPopup'; import { SidebarAnnos } from '../../SidebarAnnos'; -import { StyleProp } from '../../StyleProvider'; -import { media_state } from '../AudioBox'; +import { styleFromLayoutString, StyleProp } from '../../StyleProvider'; +import { mediaState } from '../AudioBox'; import { DocumentView, DocumentViewInternal, OpenWhere } from '../DocumentView'; import { FieldView, FieldViewProps, FocusViewOptions } from '../FieldView'; import { LinkInfo } from '../LinkDocPreview'; -import { PinProps, PresBox } from '../trails'; +import { PresBox } from '../trails'; import { DashDocCommentView } from './DashDocCommentView'; import { DashDocView } from './DashDocView'; import { DashFieldView } from './DashFieldView'; @@ -152,10 +155,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB return this.titleHeight + NumCast(this.layoutDoc._layout_autoHeightMargins); } @computed get _recordingDictation() { - return this.dataDoc?.mediaState === media_state.Recording; + return this.dataDoc?.mediaState === mediaState.Recording; } set _recordingDictation(value) { - !this.dataDoc[`${this.fieldKey}_recordingSource`] && (this.dataDoc.mediaState = value ? media_state.Recording : undefined); + !this.dataDoc[`${this.fieldKey}_recordingSource`] && (this.dataDoc.mediaState = value ? mediaState.Recording : undefined); } @computed get config() { this._keymap = buildKeymap(schema, this._props); @@ -275,7 +278,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB anchor.followLinkAudio = true; let stopFunc: any; const targetData = target[DocData]; - targetData.mediaState = media_state.Recording; + targetData.mediaState = mediaState.Recording; DocumentViewInternal.recordAudioAnnotation(targetData, Doc.LayoutFieldKey(target), stop => (stopFunc = stop)); const reactionDisposer = reaction( () => target.mediaState, @@ -313,7 +316,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB }); const coordsB = this._editorView!.coordsAtPos(this._editorView!.state.selection.to); this._props.rootSelected?.() && AnchorMenu.Instance.jumpTo(coordsB.left, coordsB.bottom); - let ele: Opt<HTMLDivElement> = undefined; + let ele: Opt<HTMLDivElement>; try { const contents = window.getSelection()?.getRangeAt(0).cloneContents(); if (contents) { @@ -330,7 +333,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB const fieldKey = StrCast(node.attrs.fieldKey); return ( (node.attrs.hideKey ? '' : fieldKey + ':') + // - (node.attrs.hideValue ? '' : Field.toJavascriptString(refDoc[fieldKey] as Field)) + (node.attrs.hideValue ? '' : Field.toJavascriptString(refDoc[fieldKey] as FieldType)) ); } return ''; @@ -345,8 +348,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB tryUpdateDoc = (force: boolean) => { if (this._editorView && (this._editorView as any).docView) { - const state = this._editorView.state; - const dataDoc = this.dataDoc; + const { state } = this._editorView; + const { dataDoc } = this; const newText = state.doc.textBetween(0, state.doc.content.size, ' \n', this.leafText); const newJson = JSON.stringify(state.toJSON()); const prevData = Cast(this.layoutDoc[this.fieldKey], RichTextField, null); // the actual text in the text box @@ -386,7 +389,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB } else { // if we've deleted all the text in a note driven by a template, then restore the template data dataDoc[this.fieldKey] = undefined; - this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse(((layoutData !== prevData ? layoutData : undefined) ?? protoData).Data))); + this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse(((layoutData !== prevData ? layoutData : undefined) ?? protoData)?.Data))); ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, text: newText }); unchanged = false; } @@ -417,7 +420,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB let link; LinkManager.Links(this.dataDoc).forEach((l, i) => { const anchor = (l.link_anchor_1 as Doc).annotationOn ? (l.link_anchor_1 as Doc) : (l.link_anchor_2 as Doc).annotationOn ? (l.link_anchor_2 as Doc) : undefined; - if (anchor && (anchor.annotationOn as Doc).mediaState === media_state.Recording) { + if (anchor && (anchor.annotationOn as Doc).mediaState === mediaState.Recording) { linkTime = NumCast(anchor._timecodeToShow /* audioStart */); linkAnchor = anchor; link = l; @@ -426,7 +429,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB if (this._editorView && linkTime) { const state = this._editorView.state; const now = Date.now(); - let mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(now / 1000) }); + let mark = schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail, modified: Math.floor(now / 1000) }); if (!this._break && state.selection.to !== state.selection.from) { for (let i = state.selection.from; i <= state.selection.to; i++) { const pos = state.doc.resolve(i); @@ -507,7 +510,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB hyperlinkTerm = (tr: any, target: Doc, newAutoLinks: Set<Doc>) => { const editorView = this._editorView; if (editorView && (editorView as any).docView && !Doc.AreProtosEqual(target, this.Document)) { - const autoLinkTerm = Field.toString(target.title as Field).replace(/^@/, ''); + const autoLinkTerm = Field.toString(target.title as FieldType).replace(/^@/, ''); var alink: Doc | undefined; this.findInNode(editorView, editorView.state.doc, autoLinkTerm).forEach(sel => { if ( @@ -587,7 +590,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB }; adoptAnnotation = (start: number, end: number, mark: Mark) => { const view = this._editorView!; - const nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: Doc.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) => { @@ -726,7 +729,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-remote', { background: 'yellow' }); } if (highlights.includes('My Text')) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + Doc.CurrentUserEmail.replace(/\./g, '').replace(/@/g, ''), { background: 'moccasin' }); + addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + ClientUtils.CurrentUserEmail.replace(/\./g, '').replace(/@/g, ''), { background: 'moccasin' }); } if (highlights.includes('Todo Items')) { addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-todo', { outline: 'black solid 1px' }); @@ -745,12 +748,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-ignore', { 'font-size': '1' }); } if (highlights.includes('By Recent Minute')) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + Doc.CurrentUserEmail.replace('.', '').replace('@', ''), { opacity: '0.1' }); + addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + ClientUtils.CurrentUserEmail.replace('.', '').replace('@', ''), { opacity: '0.1' }); const min = Math.round(Date.now() / 1000 / 60); numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-min-' + (min - i), { opacity: ((10 - i - 1) / 10).toString() })); } if (highlights.includes('By Recent Hour')) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + Doc.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() })); } @@ -1214,18 +1217,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB width => this.tryUpdateScrollHeight() ); this._disposers.scrollHeight = reaction( - () => ({ scrollHeight: this.scrollHeight, layout_autoHeight: this.layout_autoHeight, width: NumCast(this.layoutDoc._width) }), - ({ width, scrollHeight, layout_autoHeight }) => width && layout_autoHeight && this.resetNativeHeight(scrollHeight), + () => ({ scrollHeight: this.scrollHeight, layoutAutoHeight: this.layout_autoHeight, width: NumCast(this.layoutDoc._width) }), + ({ width, scrollHeight, layoutAutoHeight }) => width && layoutAutoHeight && this.resetNativeHeight(scrollHeight), { fireImmediately: true } ); this._disposers.componentHeights = reaction( // set the document height when one of the component heights changes and layout_autoHeight is on - () => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, layout_autoHeight: this.layout_autoHeight, marginsHeight: this.layout_autoHeightMargins }), - ({ sidebarHeight, textHeight, layout_autoHeight, marginsHeight }) => { + () => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, layoutAutoHeight: this.layout_autoHeight, marginsHeight: this.layout_autoHeightMargins }), + ({ sidebarHeight, textHeight, layoutAutoHeight, marginsHeight }) => { const newHeight = this.contentScaling * (marginsHeight + Math.max(sidebarHeight, textHeight)); if ( (!Array.from(FormattedTextBox._globalHighlights).includes('Bold Text') || this._props.isSelected()) && // - layout_autoHeight && + layoutAutoHeight && newHeight && newHeight !== this.layoutDoc.height && !this._props.dontRegisterView @@ -1273,14 +1276,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB this._disposers.search = reaction( () => Doc.IsSearchMatch(this.Document), search => (search ? this.highlightSearchTerms([Doc.SearchQuery()], search.searchMatch < 0) : this.unhighlightSearchTerms()), - { fireImmediately: Doc.IsSearchMatchUnmemoized(this.Document) ? true : false } + { fireImmediately: !!Doc.IsSearchMatchUnmemoized(this.Document) } ); this._disposers.selected = reaction( () => this._props.rootSelected?.(), action(selected => { - //selected && setTimeout(() => this.prepareForTyping()); + // selected && setTimeout(() => this.prepareForTyping()); if (FormattedTextBox._globalHighlights.has('Bold Text')) { + // eslint-disable-next-line operator-assignment this.layoutDoc[DocCss] = this.layoutDoc[DocCss] + 1; // css change happens outside of mobx/react, so this will notify anyone interested in the layout that it has changed } if (RichTextMenu.Instance?.view === this._editorView && !selected) { @@ -1321,10 +1325,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB } clipboardTextSerializer = (slice: Slice): string => { - let text = '', - separated = true; - const from = 0, - to = slice.content.size; + let text = ''; + let separated = true; + const from = 0; + const to = slice.content.size; slice.content.nodesBetween( from, to, @@ -1346,7 +1350,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB handlePaste = (view: EditorView, event: Event, slice: Slice): boolean => { const pdfAnchorId = (event as ClipboardEvent).clipboardData?.getData('dash/pdfAnchor'); - return pdfAnchorId && this.addPdfReference(pdfAnchorId) ? true : false; + return !!(pdfAnchorId && this.addPdfReference(pdfAnchorId)); }; addPdfReference = (pdfAnchorId: string) => { @@ -1389,7 +1393,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB const self = this; return new Plugin({ view(newView) { - runInAction(() => self._props.rootSelected?.() && RichTextMenu.Instance && (RichTextMenu.Instance.view = newView)); + runInAction(() => { + self._props.rootSelected?.() && RichTextMenu.Instance && (RichTextMenu.Instance.view = newView); + }); return new RichTextMenuPlugin({ editorProps: this._props }); }, }); @@ -1414,7 +1420,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB const shift = Math.min(topOff ?? Number.MAX_VALUE, botOff ?? Number.MAX_VALUE); const scrollPos = scrollRef.scrollTop + shift * self.ScreenToLocalBoxXf().Scale; if (this._focusSpeed !== undefined) { - setTimeout(() => scrollPos && (this._scrollStopper = smoothScroll(this._focusSpeed || 0, scrollRef, scrollPos, 'ease', this._scrollStopper))); + setTimeout(() => { + scrollPos && (this._scrollStopper = smoothScroll(this._focusSpeed || 0, scrollRef, scrollPos, 'ease', this._scrollStopper)); + }); } else { scrollRef.scrollTo({ top: scrollPos }); } @@ -1424,25 +1432,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB }, dispatchTransaction: this.dispatchTransaction, nodeViews: { - dashComment(node: any, view: any, getPos: any) { - return new DashDocCommentView(node, view, getPos); - }, - dashDoc(node: any, view: any, getPos: any) { - return new DashDocView(node, view, getPos, self); - }, - dashField(node: any, view: any, getPos: any) { - return new DashFieldView(node, view, getPos, self); - }, - equation(node: any, view: any, getPos: any) { - return new EquationView(node, view, getPos, self); - }, - summary(node: any, view: any, getPos: any) { - return new SummaryView(node, view, getPos); - }, - //ordered_list(node: any, view: any, getPos: any) { return new OrderedListView(); }, - footnote(node: any, view: any, getPos: any) { - return new FootnoteView(node, view, getPos); - }, + dashComment(node: any, view: any, getPos: any) { return new DashDocCommentView(node, view, getPos); }, // prettier-ignore + dashDoc(node: any, view: any, getPos: any) { return new DashDocView(node, view, getPos, self); }, // prettier-ignore + dashField(node: any, view: any, getPos: any) { return new DashFieldView(node, view, getPos, self); }, // prettier-ignore + equation(node: any, view: any, getPos: any) { return new EquationView(node, view, getPos, self); }, // prettier-ignore + summary(node: any, view: any, getPos: any) { return new SummaryView(node, view, getPos); }, // prettier-ignore + footnote(node: any, view: any, getPos: any) { return new FootnoteView(node, view, getPos); }, // prettier-ignore }, clipboardTextSerializer: this.clipboardTextSerializer, handlePaste: this.handlePaste, @@ -1450,7 +1445,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB const { state, dispatch } = this._editorView; if (!rtfField) { const dataDoc = Doc.IsDelegateField(DocCast(this.layoutDoc.proto), this.fieldKey) ? DocCast(this.layoutDoc.proto) : this.dataDoc; - const startupText = Field.toString(dataDoc[fieldKey] as Field); + const startupText = Field.toString(dataDoc[fieldKey] as FieldType); if (startupText) { dispatch(state.tr.insertText(startupText)); } @@ -1474,7 +1469,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB this._props.select(false); if (selLoadChar) { const $from = this._editorView.state.selection.anchor ? this._editorView.state.doc.resolve(this._editorView.state.selection.anchor - 1) : undefined; - const mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }); + const mark = schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }); const curMarks = this._editorView.state.storedMarks ?? $from?.marksAcross(this._editorView.state.selection.$head) ?? []; const storedMarks = [...curMarks.filter(m => m.type !== mark.type), mark]; const tr1 = this._editorView.state.tr.setStoredMarks(storedMarks); @@ -1510,7 +1505,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB ...(Doc.UserDoc().fontFamily ? [schema.mark(schema.marks.pFontFamily, { family: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontFamily) })] : []), ...(Doc.UserDoc().fontSize ? [schema.mark(schema.marks.pFontSize, { fontSize: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontSize) })] : []), ...(Doc.UserDoc().fontWeight === 'bold' ? [schema.mark(schema.marks.strong)] : []), - ...[schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })], + ...[schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })], ]; this._editorView?.dispatch(this._editorView?.state.tr.setStoredMarks(docDefaultMarks)); }; @@ -1761,7 +1756,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB let stopPropagation = true; for (var i = state.selection.from; i <= state.selection.to; i++) { const node = state.doc.resolve(i); - if (state.doc.content.size - 1 > i && node?.marks?.().some(mark => mark.type === schema.marks.user_mark && mark.attrs.userid !== Doc.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(); } } @@ -1781,11 +1776,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB this._editorView?.state.selection.empty && (stopPropagation = false); break; default: - if (this._lastTimedMark?.attrs.userid === Doc.CurrentUserEmail) break; + if (this._lastTimedMark?.attrs.userid === ClientUtils.CurrentUserEmail) break; case ' ': if (e.code !== 'Space' && e.code !== 'Backspace') { [AclEdit, AclAugment, AclAdmin].includes(GetEffectiveAcl(this.Document)) && - this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark).addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }))); + this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark).addStoredMark(schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }))); } break; } @@ -1892,7 +1887,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB Document={this.Document} layoutDoc={this.layoutDoc} dataDoc={this.dataDoc} - usePanelWidth={true} + usePanelWidth nativeWidth={NumCast(this.layoutDoc._nativeWidth)} showSidebar={this.SidebarShown} whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} @@ -2000,11 +1995,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB // if scrollTop is 0, then don't let wheel trigger scroll on any container (which it would since onScroll won't be triggered on this) if (this._props.isContentActive()) { const scale = this._props.NativeDimScaling?.() || 1; - const styleFromLayoutString = Doc.styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._header_height}px' > - const height = Number(styleFromLayoutString.height?.replace('px', '')); + const styleFromLayout = styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._header_height}px' > + const height = Number(styleFromLayout.height?.replace('px', '')); // prevent default if selected || child is active but this doc isn't scrollable if ( - !Number.isNaN(height) && + !isNaN(height) && (this._scrollRef?.scrollHeight ?? 0) <= Math.ceil((height ? height : this._props.PanelHeight()) / scale) && // (this._props.rootSelected?.() || this.isAnyChildContentActive()) ) { @@ -2033,15 +2028,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB setTimeout(() => !this._props.isContentActive() && FormattedTextBoxComment.textBox === this && FormattedTextBoxComment.Hide); const paddingX = NumCast(this.layoutDoc._xMargin, this._props.xPadding || 0); const paddingY = NumCast(this.layoutDoc._yMargin, this._props.yPadding || 0); - const styleFromLayoutString = Doc.styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._header_height}px' > - return styleFromLayoutString?.height === '0px' ? null : ( + const styleFromLayout = styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._header_height}px' > + return styleFromLayout?.height === '0px' ? null : ( <div className="formattedTextBox" onPointerEnter={action(() => { this._isHovering = true; this.layoutDoc[`_${this._props.fieldKey}_usePath`] && (this.Document.isHovering = true); })} - onPointerLeave={action(() => (this.Document.isHovering = this._isHovering = false))} + onPointerLeave={action(() => { this.Document.isHovering = this._isHovering = false; })} // prettier-ignore ref={r => { this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel); this._oldWheel = r; @@ -2061,7 +2056,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB fontSize: this.fontSize, fontFamily: this.fontFamily, fontWeight: this.fontWeight, - ...styleFromLayoutString, + ...styleFromLayout, }}> <div className="formattedTextBox-cont" @@ -2083,7 +2078,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB onDoubleClick={this.onDoubleClick}> <div className="formattedTextBox-outer" - ref={r => (this._scrollRef = r)} + ref={r => { + this._scrollRef = r; + }} style={{ width: this.noSidebar ? '100%' : `calc(100% - ${this.layout_sidebarWidthPercent})`, overflow: this.layoutDoc._createDocOnCR ? 'hidden' : this.layoutDoc._layout_autoHeight ? 'visible' : undefined, diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx index ce17af6ca..c0cb60c6d 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx @@ -1,15 +1,16 @@ import { Mark, ResolvedPos } from 'prosemirror-model'; -import { EditorState, NodeSelection } from 'prosemirror-state'; +import { EditorState } from 'prosemirror-state'; import { EditorView } from 'prosemirror-view'; +import { ClientUtils } from '../../../../ClientUtils'; import { Doc } from '../../../../fields/Doc'; import { DocServer } from '../../../DocServer'; -import { LinkDocPreview, LinkInfo } from '../LinkDocPreview'; +import { LinkInfo } from '../LinkDocPreview'; import { FormattedTextBox } from './FormattedTextBox'; 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 !== Doc.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 03c902580..e9ed2549e 100644 --- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts +++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts @@ -1,28 +1,28 @@ import { chainCommands, deleteSelection, exitCode, joinBackward, joinDown, joinUp, lift, newlineInCode, selectNodeBackward, setBlockType, splitBlockKeepMarks, toggleMark, wrapIn } from 'prosemirror-commands'; import { redo, undo } from 'prosemirror-history'; import { Schema } from 'prosemirror-model'; -import { splitListItem, wrapInList, sinkListItem, liftListItem } from 'prosemirror-schema-list'; +import { liftListItem, sinkListItem, splitListItem, wrapInList } from 'prosemirror-schema-list'; import { EditorState, NodeSelection, TextSelection, Transaction } from 'prosemirror-state'; import { liftTarget } from 'prosemirror-transform'; +import { EditorView } from 'prosemirror-view'; +import { ClientUtils } from '../../../../ClientUtils'; +import { Utils } from '../../../../Utils'; import { AclAdmin, AclAugment, AclEdit } from '../../../../fields/DocSymbols'; import { GetEffectiveAcl } from '../../../../fields/util'; -import { Utils } from '../../../../Utils'; import { Docs } from '../../../documents/Documents'; import { RTFMarkup } from '../../../util/RTFMarkup'; import { SelectionManager } from '../../../util/SelectionManager'; import { OpenWhere } from '../DocumentView'; -import { Doc } from '../../../../fields/Doc'; -import { EditorView } from 'prosemirror-view'; const mac = typeof navigator !== 'undefined' ? /Mac/.test(navigator.platform) : false; export type KeyMap = { [key: string]: any }; -export let updateBullets = (tx2: Transaction, schema: Schema, assignedMapStyle?: string, from?: number, to?: number) => { +export const updateBullets = (tx2: Transaction, schema: Schema, assignedMapStyle?: string, from?: number, to?: number) => { let mapStyle = assignedMapStyle; - tx2.doc.descendants((node: any, offset: any, index: any) => { + tx2.doc.descendants((node: any, offset: any /* , index: any */) => { if ((from === undefined || to === undefined || (from <= offset + node.nodeSize && to >= offset)) && (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item)) { - const path = (tx2.doc.resolve(offset) as any).path; + const { path } = tx2.doc.resolve(offset) as any; let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty('type') && c.type === schema.nodes.ordered_list ? 1 : 0), 0); if (node.type === schema.nodes.ordered_list) { if (depth === 0 && !assignedMapStyle) mapStyle = node.attrs.mapStyle; @@ -49,23 +49,27 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey const canEdit = (state: any) => { switch (GetEffectiveAcl(props.TemplateDataDocument)) { case AclAugment: - const prevNode = state.selection.$cursor.nodeBefore; - const prevUser = !prevNode ? Doc.CurrentUserEmail : prevNode.marks[prevNode.marks.length - 1].attrs.userid; - if (prevUser != Doc.CurrentUserEmail) { - return false; + { + const prevNode = state.selection.$cursor.nodeBefore; + const prevUser = !prevNode ? ClientUtils.CurrentUserEmail : prevNode.marks[prevNode.marks.length - 1].attrs.userid; + if (prevUser !== ClientUtils.CurrentUserEmail) { + return false; + } } + break; + default: } return true; }; const toggleEditableMark = (mark: any) => (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && toggleMark(mark)(state, dispatch); - //History commands + // History commands bind('Mod-z', undo); bind('Shift-Mod-z', redo); !mac && bind('Mod-y', redo); - //Commands to modify Mark + // Commands to modify Mark bind('Mod-b', toggleEditableMark(schema.marks.strong)); bind('Mod-B', toggleEditableMark(schema.marks.strong)); @@ -77,7 +81,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey bind('Mod-u', toggleEditableMark(schema.marks.underline)); bind('Mod-U', toggleEditableMark(schema.marks.underline)); - //Commands for lists + // Commands for lists bind('Ctrl-i', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && wrapInList(schema.nodes.ordered_list)(state as any, dispatch as any)); bind('Ctrl-Tab', () => (props.onKey?.(event, props) ? true : true)); @@ -103,8 +107,8 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey if ( !wrapInList(schema.nodes.ordered_list)(newstate.state as any, (tx2: Transaction) => { const tx25 = updateBullets(tx2, schema); - const ol_node = tx25.doc.nodeAt(range!.start)!; - const tx3 = tx25.setNodeMarkup(range!.start, ol_node.type, ol_node.attrs, marks); + const olNode = tx25.doc.nodeAt(range!.start)!; + const tx3 = tx25.setNodeMarkup(range!.start, olNode.type, olNode.attrs, marks); // when promoting to a list, assume list will format things so don't copy the stored marks. marks && tx3.ensureMarks([...marks]); marks && tx3.setStoredMarks([...marks]); @@ -134,13 +138,13 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey } }); - //Command to create a new Tab with a PDF of all the command shortcuts + // Command to create a new Tab with a PDF of all the command shortcuts bind('Mod-/', (state: EditorState, dispatch: (tx: Transaction) => void) => { - const newDoc = Docs.Create.PdfDocument(Utils.prepend('/assets/cheat-sheet.pdf'), { _width: 300, _height: 300 }); + const newDoc = Docs.Create.PdfDocument(ClientUtils.prepend('/assets/cheat-sheet.pdf'), { _width: 300, _height: 300 }); props.addDocTab(newDoc, OpenWhere.addRight); }); - //Commands to modify BlockType + // Commands to modify BlockType bind('Ctrl->', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state && wrapIn(schema.nodes.blockquote)(state as any, dispatch as any))); bind('Alt-\\', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.paragraph)(state as any, dispatch as any)); bind('Shift-Ctrl-\\', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.code_block)(state as any, dispatch as any)); @@ -156,11 +160,11 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey bind('Shift-Ctrl-' + i, (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.heading, { level: i })(state as any, dispatch as any)); } - //Command to create a horizontal break line + // Command to create a horizontal break line const hr = schema.nodes.horizontal_rule; bind('Mod-_', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView())); - //Command to unselect all + // Command to unselect all bind('Escape', (state: EditorState, dispatch: (tx: Transaction) => void) => { dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from))); (document.activeElement as any).blur?.(); @@ -189,7 +193,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey }); bind('Cmd-]', (state: EditorState, dispatch: (tx: Transaction) => void) => { const resolved = state.doc.resolve(state.selection.from) as any; - const tr = state.tr; + const { tr } = state; if (resolved?.parent.type.name === 'paragraph') { tr.setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'right' }, resolved.parent.marks); } else { @@ -204,7 +208,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey }); bind('Cmd-\\', (state: EditorState, dispatch: (tx: Transaction) => void) => { const resolved = state.doc.resolve(state.selection.from) as any; - const tr = state.tr; + const { tr } = state; if (resolved?.parent.type.name === 'paragraph') { tr.setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'center' }, resolved.parent.marks); } else { @@ -219,7 +223,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey }); bind('Cmd-[', (state: EditorState, dispatch: (tx: Transaction) => void) => { const resolved = state.doc.resolve(state.selection.from) as any; - const tr = state.tr; + const { tr } = state; if (resolved?.parent.type.name === 'paragraph') { tr.setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'left' }, resolved.parent.marks); } else { @@ -236,7 +240,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey bind('Cmd-f', (state: EditorState, dispatch: (tx: Transaction) => void) => { const content = state.tr.selection.empty ? undefined : state.tr.selection.content().content.textBetween(0, state.tr.selection.content().size + 1); const newNode = schema.nodes.footnote.create({}, content ? state.schema.text(content) : undefined); - const tr = state.tr; + const { tr } = state; tr.replaceSelectionWith(newNode); // replace insertion with a footnote. dispatch( tr.setSelection( @@ -288,8 +292,8 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey }; bind('Backspace', backspace); - //newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock - //command to break line + // newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock + // command to break line const enter = (state: EditorState, dispatch: (tx: Transaction) => void, view: EditorView, once = true) => { if (props.onKey?.(event, props)) return true; @@ -356,7 +360,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey }; bind('Enter', enter); - //Command to create a blank space + // Command to create a blank space bind('Space', (state: EditorState, dispatch: (tx: Transaction) => void) => { if (props.TemplateDataDocument && GetEffectiveAcl(props.TemplateDataDocument) != AclEdit && GetEffectiveAcl(props.TemplateDataDocument) != AclAugment && GetEffectiveAcl(props.TemplateDataDocument) != AclAdmin) return true; return false; diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index cecf106a3..ec9c1a15d 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -3,7 +3,7 @@ import { Tooltip } from '@mui/material'; import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import { lift, wrapIn } from 'prosemirror-commands'; -import { Mark, MarkType, Node as ProsNode, ResolvedPos } from 'prosemirror-model'; +import { Mark, MarkType } from 'prosemirror-model'; import { wrapInList } from 'prosemirror-schema-list'; import { EditorState, NodeSelection, TextSelection } from 'prosemirror-state'; import { EditorView } from 'prosemirror-view'; diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index 42665830f..78ea99592 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -1,5 +1,6 @@ import { ellipsis, emDash, InputRule, smartQuotes, textblockTypeInputRule } from 'prosemirror-inputrules'; import { NodeSelection, TextSelection } from 'prosemirror-state'; +import { ClientUtils } from '../../../../ClientUtils'; import { Doc, DocListCast, FieldResult, StrListCast } from '../../../../fields/Doc'; import { DocData } from '../../../../fields/DocSymbols'; import { Id } from '../../../../fields/FieldSymbols'; @@ -48,13 +49,9 @@ export class RichTextRules { /^A\.\s$/, schema.nodes.ordered_list, // match => { - () => { - return { mapStyle: 'multi', bulletStyle: 1 }; - // return ({ order: +match[1] }) - }, - (match: any, node: any) => { - return node.childCount + node.attrs.order === +match[1]; - }, + () => ({ mapStyle: 'multi', bulletStyle: 1 }), + // return ({ order: +match[1] }) + (match: any, node: any) => node.childCount + node.attrs.order === +match[1], ((type: any) => ({ type: type, attrs: { mapStyle: 'multi', bulletStyle: 1 } })) as any ), @@ -70,7 +67,7 @@ export class RichTextRules { // ``` create code block new InputRule(/^```$/, (state, match, start, end) => { - let $start = state.doc.resolve(start); + const $start = state.doc.resolve(start); if (!$start.node(-1).canReplaceWith($start.index(-1), $start.indexAfter(-1), schema.nodes.code_block)) return null; // this enables text with code blocks to be used as a 'paint' function via a styleprovider button that is added to Docs that have an onPaint script @@ -86,13 +83,13 @@ export class RichTextRules { }), // %<font-size> set the font size - new InputRule(new RegExp(/%([0-9]+)\s$/), (state, match, start, end) => { + new InputRule(/%([0-9]+)\s$/, (state, match, start, end) => { const size = Number(match[1]); return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: size })); }), - //Create annotation to a field on the text document - new InputRule(new RegExp(/>::$/), (state, match, start, end) => { + // Create annotation to a field on the text document + new InputRule(/>::$/, (state, match, start, end) => { const creator = (doc: Doc) => { const textDoc = this.Document[DocData]; const numInlines = NumCast(textDoc.inlineTextCount); @@ -107,7 +104,7 @@ export class RichTextRules { .insert(start, newNode) .replaceRangeWith(start + 1, end + 2, dashDoc) .insertText(' ', start + 2) - .setStoredMarks([...node.marks, ...(sm ? sm : [])]) + .setStoredMarks([...node.marks, ...(sm || [])]) : this.TextBox.EditorView.state.tr ); }; @@ -117,8 +114,8 @@ export class RichTextRules { return null; }), - //Create annotation to a field on the text document - new InputRule(new RegExp(/>>$/), (state, match, start, end) => { + // Create annotation to a field on the text document + new InputRule(/>>$/, (state, match, start, end) => { const textDoc = this.Document[DocData]; const numInlines = NumCast(textDoc.inlineTextCount); textDoc.inlineTextCount = numInlines + 1; @@ -150,13 +147,13 @@ export class RichTextRules { .insert(start, newNode) .replaceRangeWith(start + 1, end + 1, dashDoc) .insertText(' ', start + 2) - .setStoredMarks([...node.marks, ...(sm ? sm : [])]) + .setStoredMarks([...node.marks, ...(sm || [])]) : state.tr; return replaced; }), // set the First-line indent node type for the selection's paragraph (assumes % was used to initiate an EnteringStyle mode) - new InputRule(new RegExp(/(%d|d)$/), (state, match, start, end) => { + new InputRule(/(%d|d)$/, (state, match, start, end) => { if (!match[0].startsWith('%') && !this.EnteringStyle) return null; const pos = state.doc.resolve(start) as any; for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) { @@ -171,7 +168,7 @@ export class RichTextRules { }), // set the Hanging indent node type for the current selection's paragraph (assumes % was used to initiate an EnteringStyle mode) - new InputRule(new RegExp(/(%h|h)$/), (state, match, start, end) => { + new InputRule(/(%h|h)$/, (state, match, start, end) => { if (!match[0].startsWith('%') && !this.EnteringStyle) return null; const pos = state.doc.resolve(start) as any; for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) { @@ -186,11 +183,11 @@ export class RichTextRules { }), // set the Quoted indent node type for the current selection's paragraph (assumes % was used to initiate an EnteringStyle mode) - new InputRule(new RegExp(/(%q|q)$/), (state, match, start, end) => { + new InputRule(/(%q|q)$/, (state, match, start, end) => { if (!match[0].startsWith('%') && !this.EnteringStyle) return null; const pos = state.doc.resolve(start) as any; if (state.selection instanceof NodeSelection && state.selection.node.type === schema.nodes.ordered_list) { - const node = state.selection.node; + const { node } = state.selection; return state.tr.setNodeMarkup(pos.pos, node.type, { ...node.attrs, indent: node.attrs.indent === 30 ? undefined : 30 }); } for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) { @@ -205,46 +202,43 @@ export class RichTextRules { }), // center justify text - new InputRule(new RegExp(/%\^/), (state, match, start, end) => { + new InputRule(/%\^/, (state, match, start, end) => { const resolved = state.doc.resolve(start) as any; if (resolved?.parent.type.name === 'paragraph') { return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'center' }, resolved.parent.marks); - } else { - const node = resolved.nodeAfter; - const sm = state.storedMarks || undefined; - const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'center' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; - return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); } + const node = resolved.nodeAfter; + const sm = state.storedMarks || undefined; + const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'center' })).setStoredMarks([...node.marks, ...(sm || [])]) : state.tr; + return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); }), // left justify text - new InputRule(new RegExp(/%\[/), (state, match, start, end) => { + new InputRule(/%\[/, (state, match, start, end) => { const resolved = state.doc.resolve(start) as any; if (resolved?.parent.type.name === 'paragraph') { return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'left' }, resolved.parent.marks); - } else { - const node = resolved.nodeAfter; - const sm = state.storedMarks || undefined; - const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'left' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; - return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); } + const node = resolved.nodeAfter; + const sm = state.storedMarks || undefined; + const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'left' })).setStoredMarks([...node.marks, ...(sm || [])]) : state.tr; + return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); }), // right justify text - new InputRule(new RegExp(/%\]/), (state, match, start, end) => { + new InputRule(/%\]/, (state, match, start, end) => { const resolved = state.doc.resolve(start) as any; if (resolved?.parent.type.name === 'paragraph') { return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'right' }, resolved.parent.marks); - } else { - const node = resolved.nodeAfter; - const sm = state.storedMarks || undefined; - const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'right' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; - return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); } + const node = resolved.nodeAfter; + const sm = state.storedMarks || undefined; + const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'right' })).setStoredMarks([...node.marks, ...(sm || [])]) : state.tr; + return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); }), // activate a style by name using prefix '%<color name>' - new InputRule(new RegExp(/%[a-zA-Z_]+$/), (state, match, start, end) => { + new InputRule(/%[a-zA-Z_]+$/, (state, match, start, end) => { const color = match[0].substring(1, match[0].length); const marks = RichTextMenu.Instance?._brushMap.get(color); @@ -276,13 +270,13 @@ export class RichTextRules { }), // toggle alternate text UI %/ - new InputRule(new RegExp(/%\//), (state, match, start, end) => { + new InputRule(/%\//, (state, match, start, end) => { setTimeout(() => this.TextBox.cycleAlternateText(true)); return state.tr.deleteRange(start, end); }), // stop using active style - new InputRule(new RegExp(/%%$/), (state, match, start, end) => { + new InputRule(/%%$/, (state, match, start, end) => { const tr = state.tr.deleteRange(start, end); const marks = state.tr.selection.$anchor.nodeBefore?.marks; @@ -295,7 +289,7 @@ export class RichTextRules { // create a hyperlink to a titled document // @(<doctitle>) - new InputRule(new RegExp(/@\(([a-zA-Z_@\.\? \-0-9]+)\)/), (state, match, start, end) => { + new InputRule(/@\(([a-zA-Z_@.? \-0-9]+)\)/, (state, match, start, end) => { const docTitle = match[1]; const prefixLength = '@('.length; if (docTitle) { @@ -335,7 +329,7 @@ export class RichTextRules { // [@{this,doctitle,}.fieldKey{:,=,:=,=:=}value] // [@{this,doctitle,}.fieldKey] new InputRule( - new RegExp(/\[(@|@this\.|@[a-zA-Z_\? \-0-9]+\.)([a-zA-Z_\?\-0-9]+)((:|=|:=|=:=)([a-zA-Z,_\(\)\.@\?\+\-\*\/\ 0-9\(\)]*))?\]/), + /\[(@|@this\.|@[a-zA-Z_? \-0-9]+\.)([a-zA-Z_?\-0-9]+)((:|=|:=|=:=)([a-zA-Z,_().@?+\-*/ 0-9()]*))?\]/, (state, match, start, end) => { const docTitle = match[1].substring(1).replace(/\.$/, ''); const fieldKey = match[2]; @@ -349,8 +343,17 @@ export class RichTextRules { const strs = values.some(v => !v.match(/^[-]?[0-9.]$/)); this.Document[DocData][fieldKey] = strs ? new List<string>(values) : new List<number>(values.map(v => Number(v))); } else if (value) { - KeyValueBox.SetField(this.Document, fieldKey, assign + value, Doc.IsDataProto(this.Document) ? true : undefined, assign.includes(":=") ? undefined: - (gptval: FieldResult) => (dataDoc ? this.Document[DocData]:this.Document)[fieldKey] = gptval as string ); // prettier-ignore + KeyValueBox.SetField( + this.Document, + fieldKey, + assign + value, + Doc.IsDataProto(this.Document) ? true : undefined, + assign.includes(':=') + ? undefined + : (gptval: FieldResult) => { + (dataDoc ? this.Document[DocData] : this.Document)[fieldKey] = gptval as string; + } + ); if (fieldKey === this.TextBox.fieldKey) return this.TextBox.EditorView!.state.tr; } const target = docTitle ? getTitledDoc(docTitle) : undefined; @@ -361,8 +364,8 @@ export class RichTextRules { ), // pass the contents between '((' and '))' to chatGPT and append the result - new InputRule(new RegExp(/(^|[^=])(\(\(.*\)\))$/), (state, match, start, end) => { - var count = 0; // ignore first return value which will be the notation that chat is pending a result + new InputRule(/(^|[^=])(\(\(.*\)\))$/, (state, match, start, end) => { + let count = 0; // ignore first return value which will be the notation that chat is pending a result KeyValueBox.SetField(this.Document, '', match[2], false, (gptval: FieldResult) => { if (count) { const tr = this.TextBox.EditorView?.state.tr.insertText(' ' + (gptval as string)); @@ -376,7 +379,7 @@ export class RichTextRules { // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document // @(wiki:title) - new InputRule(new RegExp(/@\(wiki:([a-zA-Z_@:\.\?\-0-9 ]+)\)$/), (state, match, start, end) => { + new InputRule(/@\(wiki:([a-zA-Z_@:.?\-0-9 ]+)\)$/, (state, match, start, end) => { const title = match[1].trim().replace(/ /g, '_'); this.TextBox.EditorView?.dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end)))); @@ -392,7 +395,7 @@ export class RichTextRules { // create an inline equation node // %eq - new InputRule(new RegExp(/%eq/), (state, match, start, end) => { + new InputRule(/%eq/, (state, match, start, end) => { const fieldKey = 'math' + Utils.GenerateGuid(); this.TextBox.dataDoc[fieldKey] = 'y='; const tr = state.tr.setSelection(new TextSelection(state.tr.doc.resolve(end - 3), state.tr.doc.resolve(end))).replaceSelectionWith(schema.nodes.equation.create({ fieldKey })); @@ -400,10 +403,10 @@ export class RichTextRules { }), // create an inline view of a tag stored under the '#' field - new InputRule(new RegExp(/#([a-zA-Z_\-]+[a-zA-Z_\-0-9]*)\s$/), (state, match, start, end) => { + new InputRule(/#([a-zA-Z_-]+[a-zA-Z_\-0-9]*)\s$/, (state, match, start, end) => { const tag = match[1]; if (!tag) return state.tr; - //this.Document[DocData]['#' + tag] = '#' + tag; + // this.Document[DocData]['#' + tag] = '#' + tag; const tags = StrListCast(this.Document[DocData].tags); if (!tags.includes(tag)) { tags.push(tag); @@ -417,29 +420,25 @@ export class RichTextRules { }), // # heading - textblockTypeInputRule(new RegExp(/^(#{1,6})\s$/), schema.nodes.heading, match => { - return { level: match[1].length }; - }), + textblockTypeInputRule(/^(#{1,6})\s$/, schema.nodes.heading, match => ({ level: match[1].length })), // set the Todo user-tag on the current selection (assumes % was used to initiate an EnteringStyle mode) - new InputRule(new RegExp(/[ti!x]$/), (state, match, start, end) => { + new InputRule(/[ti!x]$/, (state, match, start, end) => { if (state.selection.to === state.selection.from || !this.EnteringStyle) return null; const tag = match[0] === 't' ? 'todo' : match[0] === 'i' ? 'ignore' : match[0] === 'x' ? 'disagree' : match[0] === '!' ? 'important' : '??'; const node = (state.doc.resolve(start) as any).nodeAfter; if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag); - if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_mark) !== -1) { - } return node ? state.tr .removeMark(start, end, schema.marks.user_mark) - .addMark(start, end, schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })) - .addMark(start, end, schema.marks.user_tag.create({ userid: Doc.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; }), - new InputRule(new RegExp(/%\(/), (state, match, start, end) => { + new InputRule(/%\(/, (state, match, start, end) => { const node = (state.doc.resolve(start) as any).nodeAfter; const sm = state.storedMarks?.slice() || []; const mark = state.schema.marks.summarizeInclusive.create(); @@ -452,9 +451,7 @@ export class RichTextRules { return replaced.setSelection(new TextSelection(replaced.doc.resolve(end))).setStoredMarks([...node.marks, ...sm]); }), - new InputRule(new RegExp(/%\)/), (state, match, start, end) => { - return state.tr.deleteRange(start, end).removeStoredMark(state.schema.marks.summarizeInclusive.create()); - }), + new InputRule(/%\)/, (state, match, start, end) => state.tr.deleteRange(start, end).removeStoredMark(state.schema.marks.summarizeInclusive.create())), ], }; } diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts index ccf7de4a1..8f716ad7a 100644 --- a/src/client/views/nodes/formattedText/marks_rts.ts +++ b/src/client/views/nodes/formattedText/marks_rts.ts @@ -1,6 +1,5 @@ -import * as React from 'react'; -import { DOMOutputSpec, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from 'prosemirror-model'; -import { Doc } from '../../../../fields/Doc'; +import { DOMOutputSpec, MarkSpec } from 'prosemirror-model'; +import { ClientUtils } from '../../../../ClientUtils'; import { Utils } from '../../../../Utils'; const emDOM: DOMOutputSpec = ['em', 0]; @@ -336,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 !== Doc.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/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts index 62b8b03d6..70b6604ab 100644 --- a/src/client/views/nodes/formattedText/nodes_rts.ts +++ b/src/client/views/nodes/formattedText/nodes_rts.ts @@ -2,7 +2,7 @@ import { DOMOutputSpec, Node, NodeSpec } from 'prosemirror-model'; import { listItem, orderedList } from 'prosemirror-schema-list'; import { ParagraphNodeSpec, toParagraphDOM, getParagraphNodeAttrs } from './ParagraphNodeSpec'; import { DocServer } from '../../../DocServer'; -import { Doc, Field } from '../../../../fields/Doc'; +import { Doc, Field, FieldType } from '../../../../fields/Doc'; const blockquoteDOM: DOMOutputSpec = ['blockquote', 0], hrDOM: DOMOutputSpec = ['hr'], @@ -266,7 +266,7 @@ export const nodes: { [index: string]: NodeSpec } = { hideValue: { default: false }, editable: { default: true }, }, - leafText: node => Field.toString((DocServer.GetCachedRefField(node.attrs.docId as string) as Doc)?.[node.attrs.fieldKey as string] as Field), + leafText: node => Field.toString((DocServer.GetCachedRefField(node.attrs.docId as string) as Doc)?.[node.attrs.fieldKey as string] as FieldType), group: 'inline', draggable: false, toDOM(node) { diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx index a485ea4c3..95eb86720 100644 --- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx +++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx @@ -1,27 +1,31 @@ +/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */ +/* eslint-disable jsx-a11y/img-redundant-alt */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +/* eslint-disable react/function-component-definition */ import { Checkbox, FormControlLabel, Slider, TextField } from '@mui/material'; import { IconButton } from 'browndash-components'; +import * as React from 'react'; import { useEffect, useRef, useState } from 'react'; import { CgClose } from 'react-icons/cg'; import { IoMdRedo, IoMdUndo } from 'react-icons/io'; +import { ClientUtils } from '../../../../ClientUtils'; import { Doc, DocListCast } from '../../../../fields/Doc'; import { List } from '../../../../fields/List'; import { NumCast } from '../../../../fields/Types'; -import { Utils } from '../../../../Utils'; -import { Docs, DocUtils } from '../../../documents/Documents'; import { Networking } from '../../../Network'; +import { DocUtils, Docs } from '../../../documents/Documents'; import { DocumentManager } from '../../../util/DocumentManager'; import { CollectionDockingView } from '../../collections/CollectionDockingView'; import { CollectionFreeFormView } from '../../collections/collectionFreeForm'; import { OpenWhereMod } from '../DocumentView'; -import { ImageBox, ImageEditorData } from '../ImageBox'; +import { ImageEditorData } from '../ImageBox'; import './GenerativeFill.scss'; import Buttons from './GenerativeFillButtons'; import { BrushHandler } from './generativeFillUtils/BrushHandler'; -import { activeColor, canvasSize, eraserColor, freeformRenderSize, newCollectionSize, offsetDistanceY, offsetX } from './generativeFillUtils/generativeFillConstants'; -import { CursorData, ImageDimensions, Point } from './generativeFillUtils/generativeFillInterfaces'; import { APISuccess, ImageUtility } from './generativeFillUtils/ImageHandler'; import { PointerHandler } from './generativeFillUtils/PointerHandler'; -import * as React from 'react'; +import { activeColor, canvasSize, eraserColor, freeformRenderSize, newCollectionSize, offsetDistanceY, offsetX } from './generativeFillUtils/generativeFillConstants'; +import { CursorData, ImageDimensions, Point } from './generativeFillUtils/generativeFillInterfaces'; enum BrushStyle { ADD, @@ -332,7 +336,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD const startY = NumCast(parentDoc.current.y); const children = DocListCast(parentDoc.current.gen_fill_children); const len = children.length; - let initialYPositions: number[] = []; + const initialYPositions: number[] = []; for (let i = 0; i < len; i++) { initialYPositions.push(startY + i * offsetDistanceY); } @@ -348,9 +352,9 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD // creates a new image document and returns its reference const createNewImgDoc = async (img: HTMLImageElement, firstDoc: boolean): Promise<Doc | undefined> => { if (!imageRootDoc) return; - const src = img.src; + const { src } = img; const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [src] }); - const source = Utils.prepend(result.accessPaths.agnostic.client); + const source = ClientUtils.prepend(result.accessPaths.agnostic.client); if (firstDoc) { const x = 0; @@ -370,51 +374,51 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD } parentDoc.current = newImg; return newImg; - } else { - if (!parentDoc.current) return; - const x = NumCast(parentDoc.current.x) + freeformRenderSize + offsetX; - const initialY = 0; - - const newImg = Docs.Create.ImageDocument(source, { - x: x, - y: initialY, - _height: freeformRenderSize, - _width: freeformRenderSize, - data_nativeWidth: result.nativeWidth, - data_nativeHeight: result.nativeHeight, - }); + } + if (!parentDoc.current) return; + const x = NumCast(parentDoc.current.x) + freeformRenderSize + offsetX; + const initialY = 0; + + const newImg = Docs.Create.ImageDocument(source, { + x: x, + y: initialY, + _height: freeformRenderSize, + _width: freeformRenderSize, + data_nativeWidth: result.nativeWidth, + data_nativeHeight: result.nativeHeight, + }); - const parentList = DocListCast(parentDoc.current.gen_fill_children); - if (parentList.length > 0) { - parentList.push(newImg); - parentDoc.current.gen_fill_children = new List<Doc>(parentList); - } else { - parentDoc.current.gen_fill_children = new List<Doc>([newImg]); - } + const parentList = DocListCast(parentDoc.current.gen_fill_children); + if (parentList.length > 0) { + parentList.push(newImg); + parentDoc.current.gen_fill_children = new List<Doc>(parentList); + } else { + parentDoc.current.gen_fill_children = new List<Doc>([newImg]); + } - DocUtils.MakeLink(parentDoc.current, newImg, { link_relationship: `Image edit; Prompt: ${input}`, link_displayLine: true }); - adjustImgPositions(); + DocUtils.MakeLink(parentDoc.current, newImg, { link_relationship: `Image edit; Prompt: ${input}`, link_displayLine: true }); + adjustImgPositions(); - if (isNewCollection && newCollectionRef.current) { - Doc.AddDocToList(newCollectionRef.current, undefined, newImg); - } else { - addDoc?.(newImg); - } - return newImg; + if (isNewCollection && newCollectionRef.current) { + Doc.AddDocToList(newCollectionRef.current, undefined, newImg); + } else { + addDoc?.(newImg); } + return newImg; }; // Saves an image to the collection const onSave = async (src: string) => { const img = new Image(); img.src = src; - if (!currImg.current || !originalImg.current || !imageRootDoc) return; + if (!currImg.current || !originalImg.current || !imageRootDoc) return undefined; try { const res = await createNewImgDoc(img, false); return res; } catch (err) { console.log(err); } + return undefined; }; // Closes the editor view @@ -443,12 +447,12 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD }} /> } - label={'Create New Collection'} + label="Create New Collection" labelPlacement="end" sx={{ whiteSpace: 'nowrap' }} /> <Buttons getEdit={getEdit} loading={loading} onReset={handleReset} /> - <IconButton color={activeColor} tooltip="close" icon={<CgClose size={'16px'} />} onClick={handleViewClose} /> + <IconButton color={activeColor} tooltip="close" icon={<CgClose size="16px" />} onClick={handleViewClose} /> </div> </div> {/* Main canvas for editing */} @@ -469,7 +473,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD width: cursorData.width, height: cursorData.width, }}> - <div className="innerPointer"></div> + <div className="innerPointer" /> </div> {/* Icons */} <div className="iconContainer"> @@ -519,11 +523,13 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD /> </div> </div> - {/* Edits thumbnails*/} + {/* Edits thumbnails */} <div className="editsBox"> {edits.map((edit, i) => ( <img + // eslint-disable-next-line react/no-array-index-key key={i} + alt="image edits" width={75} src={edit[0] as string} style={{ cursor: 'pointer' }} @@ -552,6 +558,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD Original </label> <img + alt="image stuff" width={75} src={originalImg.current?.src} style={{ cursor: 'pointer' }} diff --git a/src/client/views/nodes/importBox/ImportElementBox.tsx b/src/client/views/nodes/importBox/ImportElementBox.tsx index 6e7c3e612..dec9a5019 100644 --- a/src/client/views/nodes/importBox/ImportElementBox.tsx +++ b/src/client/views/nodes/importBox/ImportElementBox.tsx @@ -1,7 +1,7 @@ import { computed, makeObservable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { returnFalse } from '../../../../Utils'; +import { returnFalse } from '../../../../ClientUtils'; import { Doc } from '../../../../fields/Doc'; import { ViewBoxBaseComponent } from '../../DocComponent'; import { DocumentView } from '../DocumentView'; diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 91fdb90fc..518bf66cd 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -3,7 +3,8 @@ import { Tooltip } from '@mui/material'; import { action, computed, IReactionDisposer, makeObservable, observable, ObservableSet, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { Doc, DocListCast, Field, FieldResult, NumListCast, Opt, StrListCast } from '../../../../fields/Doc'; +import { lightOrDark, returnFalse, returnOne, setupMoveUpEvents, StopEvent } from '../../../../ClientUtils'; +import { Doc, DocListCast, Field, FieldType, FieldResult, NumListCast, Opt, StrListCast } from '../../../../fields/Doc'; import { Animation, DocData, TransitionTimer } from '../../../../fields/DocSymbols'; import { Copy, Id } from '../../../../fields/FieldSymbols'; import { InkField } from '../../../../fields/InkField'; @@ -13,23 +14,23 @@ import { listSpec } from '../../../../fields/Schema'; import { ComputedField, ScriptField } from '../../../../fields/ScriptField'; import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types'; import { AudioField } from '../../../../fields/URLField'; -import { emptyFunction, emptyPath, lightOrDark, returnFalse, returnOne, setupMoveUpEvents, StopEvent, stringHash } from '../../../../Utils'; +import { emptyFunction, emptyPath, stringHash } from '../../../../Utils'; import { DocServer } from '../../../DocServer'; import { Docs } from '../../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; import { DocumentManager } from '../../../util/DocumentManager'; -import { dropActionType } from '../../../util/DragManager'; +import { dropActionType } from '../../../util/DropActionTypes'; import { ScriptingGlobals } from '../../../util/ScriptingGlobals'; import { SelectionManager } from '../../../util/SelectionManager'; import { SerializationHelper } from '../../../util/SerializationHelper'; -import { SettingsManager } from '../../../util/SettingsManager'; +import { SnappingManager } from '../../../util/SnappingManager'; import { undoBatch, UndoManager } from '../../../util/UndoManager'; import { CollectionDockingView } from '../../collections/CollectionDockingView'; -import { CollectionFreeFormView, MarqueeViewBounds } from '../../collections/collectionFreeForm'; +import { CollectionFreeFormView } from '../../collections/collectionFreeForm'; import { CollectionStackedTimeline } from '../../collections/CollectionStackedTimeline'; import { CollectionView } from '../../collections/CollectionView'; import { TreeView } from '../../collections/TreeView'; -import { ViewBoxBaseComponent } from '../../DocComponent'; +import { pinDataTypes, PinProps, ViewBoxBaseComponent } from '../../DocComponent'; import { Colors } from '../../global/globalEnums'; import { LightboxView } from '../../LightboxView'; import { DocumentView, OpenWhere, OpenWhereMod } from '../DocumentView'; @@ -37,32 +38,6 @@ import { FieldView, FieldViewProps, FocusViewOptions } from '../FieldView'; import { ScriptingBox } from '../ScriptingBox'; import './PresBox.scss'; import { PresEffect, PresEffectDirection, PresMovement, PresStatus } from './PresEnums'; -export interface pinDataTypes { - scrollable?: boolean; - dataviz?: number[]; - pannable?: boolean; - type_collection?: boolean; - inkable?: boolean; - filters?: boolean; - pivot?: boolean; - temporal?: boolean; - clippable?: boolean; - datarange?: boolean; - dataview?: boolean; - poslayoutview?: boolean; - dataannos?: boolean; - map?: boolean; -} -export interface PinProps { - audioRange?: boolean; - activeFrame?: number; - currentFrame?: number; - hidePresBox?: boolean; - pinViewport?: MarqueeViewBounds; // pin a specific viewport on a freeform view (use MarqueeView.CurViewBounds to compute if no region has been selected) - pinDocLayout?: boolean; // pin layout info (width/height/x/y) - pinAudioPlay?: boolean; // pin audio annotation - pinData?: pinDataTypes; -} @observer export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { @@ -443,7 +418,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { else { const bestTargetData = bestTarget[DocData]; const current = bestTargetData[fkey]; - const hash = bestTargetData[fkey] ? stringHash(Field.toString(bestTargetData[fkey] as Field)) : undefined; + const hash = bestTargetData[fkey] ? stringHash(Field.toString(bestTargetData[fkey] as FieldType)) : undefined; if (hash) bestTargetData[fkey + '_' + hash] = current instanceof ObjectField ? current[Copy]() : current; bestTargetData[fkey] = activeItem.config_data instanceof ObjectField ? activeItem.config_data[Copy]() : activeItem.config_data; } @@ -623,7 +598,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { /// reserved fields on the pinDoc so that those values can be restored to the /// target doc when navigating to it. @action - static pinDocView(pinDoc: Doc, pinProps: PinProps, targetDoc: Doc) { + static pinDocView(pinDocIn: Doc, pinProps: PinProps, targetDoc: Doc) { + const pinDoc = pinDocIn; pinDoc.presentation = true; pinDoc.config = ''; if (pinProps.pinDocLayout) { @@ -1479,7 +1455,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { max={max} value={value} readOnly={true} - style={{ marginLeft: hmargin, marginRight: hmargin, width: `calc(100% - ${2 * (hmargin ?? 0)}px)`, background: SettingsManager.userColor, color: SettingsManager.userVariantColor }} + style={{ marginLeft: hmargin, marginRight: hmargin, width: `calc(100% - ${2 * (hmargin ?? 0)}px)`, background: SnappingManager.userColor, color: SnappingManager.userVariantColor }} className={`toolbar-slider ${active ? '' : 'none'}`} onPointerDown={e => { PresBox._sliderBatch = UndoManager.StartBatch('pres slider'); @@ -1521,7 +1497,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { <Tooltip title={<div className="dash-tooltip">Hide before presented</div>}> <div className={`ribbon-toggle ${activeItem.presentation_hideBefore ? 'active' : ''}`} - style={{ border: `solid 1px ${SettingsManager.userColor}`, color: SettingsManager.userColor, background: activeItem.presentation_hideBefore ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor }} + style={{ border: `solid 1px ${SnappingManager.userColor}`, color: SnappingManager.userColor, background: activeItem.presentation_hideBefore ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor }} onClick={() => this.updateHideBefore(activeItem)}> Hide before </div> @@ -1529,7 +1505,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { <Tooltip title={<div className="dash-tooltip">{'Hide while presented'}</div>}> <div className={`ribbon-toggle ${activeItem.presentation_hide ? 'active' : ''}`} - style={{ border: `solid 1px ${SettingsManager.userColor}`, color: SettingsManager.userColor, background: activeItem.presentation_hide ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor }} + style={{ border: `solid 1px ${SnappingManager.userColor}`, color: SnappingManager.userColor, background: activeItem.presentation_hide ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor }} onClick={() => this.updateHide(activeItem)}> Hide </div> @@ -1538,7 +1514,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { <Tooltip title={<div className="dash-tooltip">{'Hide after presented'}</div>}> <div className={`ribbon-toggle ${activeItem.presentation_hideAfter ? 'active' : ''}`} - style={{ border: `solid 1px ${SettingsManager.userColor}`, color: SettingsManager.userColor, background: activeItem.presentation_hideAfter ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor }} + style={{ border: `solid 1px ${SnappingManager.userColor}`, color: SnappingManager.userColor, background: activeItem.presentation_hideAfter ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor }} onClick={() => this.updateHideAfter(activeItem)}> Hide after </div> @@ -1548,9 +1524,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { <div className="ribbon-toggle" style={{ - border: `solid 1px ${SettingsManager.userColor}`, - color: SettingsManager.userColor, - background: activeItem.presentation_openInLightbox ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor, + border: `solid 1px ${SnappingManager.userColor}`, + color: SnappingManager.userColor, + background: activeItem.presentation_openInLightbox ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor, }} onClick={() => this.updateOpenDoc(activeItem)}> Lightbox @@ -1559,7 +1535,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { <Tooltip title={<div className="dash-tooltip">Transition movement style</div>}> <div className="ribbon-toggle" - style={{ border: `solid 1px ${SettingsManager.userColor}`, color: SettingsManager.userColor, background: activeItem.presEaseFunc === 'ease' ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor }} + style={{ border: `solid 1px ${SnappingManager.userColor}`, color: SnappingManager.userColor, background: activeItem.presEaseFunc === 'ease' ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor }} onClick={() => this.updateEaseFunc(activeItem)}> {`${StrCast(activeItem.presEaseFunc, 'ease')}`} </div> @@ -1569,10 +1545,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { <> <div className="ribbon-doubleButton"> <div className="presBox-subheading">Slide Duration</div> - <div className="ribbon-property" style={{ border: `solid 1px ${SettingsManager.userColor}` }}> - <input className="presBox-input" type="number" readOnly={true} value={duration} onKeyDown={e => e.stopPropagation()} onChange={e => this.updateDurationTime(e.target.value)} /> s + <div className="ribbon-property" style={{ border: `solid 1px ${SnappingManager.userColor}` }}> + <input className="presBox-input" type="number" readOnly value={duration} onKeyDown={e => e.stopPropagation()} onChange={e => this.updateDurationTime(e.target.value)} /> s </div> - <div className="ribbon-propertyUpDown" style={{ color: SettingsManager.userBackgroundColor, background: SettingsManager.userColor }}> + <div className="ribbon-propertyUpDown" style={{ color: SnappingManager.userBackgroundColor, background: SnappingManager.userColor }}> <div className="ribbon-propertyUpDownItem" onClick={() => this.updateDurationTime(String(duration), 1000)}> <FontAwesomeIcon icon={'caret-up'} /> </div> @@ -1611,7 +1587,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { <div className="presBox-subheading">Progressivize Collection</div> <input className="presBox-checkbox" - style={{ margin: 10, border: `solid 1px ${SettingsManager.userColor}` }} + style={{ margin: 10, border: `solid 1px ${SnappingManager.userColor}` }} type="checkbox" onChange={() => { activeItem.presentation_indexed = activeItem.presentation_indexed === undefined ? 0 : undefined; @@ -1634,7 +1610,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { <div className="presBox-subheading">Progressivize First Bullet</div> <input className="presBox-checkbox" - style={{ margin: 10, border: `solid 1px ${SettingsManager.userColor}` }} + style={{ margin: 10, border: `solid 1px ${SnappingManager.userColor}` }} type="checkbox" onChange={() => (activeItem.presentation_indexedStart = activeItem.presentation_indexedStart ? 0 : 1)} checked={!NumCast(activeItem.presentation_indexedStart)} @@ -1644,7 +1620,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { <div className="presBox-subheading">Expand Current Bullet</div> <input className="presBox-checkbox" - style={{ margin: 10, border: `solid 1px ${SettingsManager.userColor}` }} + style={{ margin: 10, border: `solid 1px ${SnappingManager.userColor}` }} type="checkbox" onChange={() => (activeItem.presBulletExpand = !activeItem.presBulletExpand)} checked={BoolCast(activeItem.presBulletExpand)} @@ -1660,16 +1636,16 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { this._openBulletEffectDropdown = !this._openBulletEffectDropdown; })} style={{ - color: SettingsManager.userColor, - background: SettingsManager.userVariantColor, + color: SnappingManager.userColor, + background: SnappingManager.userVariantColor, borderBottomLeftRadius: this._openBulletEffectDropdown ? 0 : 5, - border: this._openBulletEffectDropdown ? `solid 2px ${SettingsManager.userVariantColor}` : `solid 1px ${SettingsManager.userColor}`, + border: this._openBulletEffectDropdown ? `solid 2px ${SnappingManager.userVariantColor}` : `solid 1px ${SnappingManager.userColor}`, }}> {effect?.toString()} <FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this._openBulletEffectDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={'angle-down'} /> <div className={'presBox-dropdownOptions'} - style={{ display: this._openBulletEffectDropdown ? 'grid' : 'none', color: SettingsManager.userColor, background: SettingsManager.userBackgroundColor }} + style={{ display: this._openBulletEffectDropdown ? 'grid' : 'none', color: SnappingManager.userColor, background: SnappingManager.userBackgroundColor }} onPointerDown={e => e.stopPropagation()}> {Object.values(PresEffect) .filter(v => isNaN(Number(v))) @@ -1698,7 +1674,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { </div> ); const presDirection = (direction: PresEffectDirection, icon: string, gridColumn: number, gridRow: number, opts: object) => { - const color = activeItem.presentation_effectDirection === direction || (direction === PresEffectDirection.Center && !activeItem.presentation_effectDirection) ? SettingsManager.userVariantColor : SettingsManager.userColor; + const color = activeItem.presentation_effectDirection === direction || (direction === PresEffectDirection.Center && !activeItem.presentation_effectDirection) ? SnappingManager.userVariantColor : SnappingManager.userColor; return ( <Tooltip title={<div className="dash-tooltip">{direction}</div>}> <div @@ -1733,10 +1709,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { this._openMovementDropdown = !this._openMovementDropdown; })} style={{ - color: SettingsManager.userColor, - background: SettingsManager.userVariantColor, + color: SnappingManager.userColor, + background: SnappingManager.userVariantColor, borderBottomLeftRadius: this._openMovementDropdown ? 0 : 5, - border: this._openMovementDropdown ? `solid 2px ${SettingsManager.userVariantColor}` : `solid 1px ${SettingsManager.userColor}`, + border: this._openMovementDropdown ? `solid 2px ${SnappingManager.userVariantColor}` : `solid 1px ${SnappingManager.userColor}`, }}> {this.movementName(activeItem)} <FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this._openMovementDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={'angle-down'} /> @@ -1745,8 +1721,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { id={'presBoxMovementDropdown'} onPointerDown={StopEvent} style={{ - color: SettingsManager.userColor, - background: SettingsManager.userBackgroundColor, + color: SnappingManager.userColor, + background: SnappingManager.userBackgroundColor, display: this._openMovementDropdown ? 'grid' : 'none', }}> {presMovement(PresMovement.None)} @@ -1758,10 +1734,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { </div> <div className="ribbon-doubleButton" style={{ display: activeItem.presentation_movement === PresMovement.Zoom ? 'inline-flex' : 'none' }}> <div className="presBox-subheading">Zoom (% screen filled)</div> - <div className="ribbon-property" style={{ border: `solid 1px ${SettingsManager.userColor}` }}> - <input className="presBox-input" type="number" readOnly={true} value={zoom} onChange={e => this.updateZoom(e.target.value)} />% + <div className="ribbon-property" style={{ border: `solid 1px ${SnappingManager.userColor}` }}> + <input className="presBox-input" type="number" readOnly value={zoom} onChange={e => this.updateZoom(e.target.value)} />% </div> - <div className="ribbon-propertyUpDown" style={{ color: SettingsManager.userBackgroundColor, background: SettingsManager.userColor }}> + <div className="ribbon-propertyUpDown" style={{ color: SnappingManager.userBackgroundColor, background: SnappingManager.userColor }}> <div className="ribbon-propertyUpDownItem" onClick={() => this.updateZoom(String(zoom), 0.1)}> <FontAwesomeIcon icon={'caret-up'} /> </div> @@ -1773,10 +1749,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { {PresBox.inputter('0', '1', '100', zoom, activeItem.presentation_movement === PresMovement.Zoom, this.updateZoom)} <div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}> <div className="presBox-subheading">Transition Time</div> - <div className="ribbon-property" style={{ border: `solid 1px ${SettingsManager.userColor}` }}> + <div className="ribbon-property" style={{ border: `solid 1px ${SnappingManager.userColor}` }}> <input className="presBox-input" type="number" readOnly={true} value={transitionSpeed} onKeyDown={e => e.stopPropagation()} onChange={action(e => this.updateTransitionTime(e.target.value))} /> s </div> - <div className="ribbon-propertyUpDown" style={{ color: SettingsManager.userBackgroundColor, background: SettingsManager.userColor }}> + <div className="ribbon-propertyUpDown" style={{ color: SnappingManager.userBackgroundColor, background: SnappingManager.userColor }}> <div className="ribbon-propertyUpDownItem" onClick={() => this.updateTransitionTime(String(transitionSpeed), 1000)}> <FontAwesomeIcon icon={'caret-up'} /> </div> @@ -1798,7 +1774,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { <div className="presBox-subheading">Play Audio Annotation</div> <input className="presBox-checkbox" - style={{ margin: 10, border: `solid 1px ${SettingsManager.userColor}` }} + style={{ margin: 10, border: `solid 1px ${SnappingManager.userColor}` }} type="checkbox" onChange={() => (activeItem.presPlayAudio = !BoolCast(activeItem.presPlayAudio))} checked={BoolCast(activeItem.presPlayAudio)} @@ -1808,7 +1784,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { <div className="presBox-subheading">Zoom Text Selections</div> <input className="presBox-checkbox" - style={{ margin: 10, border: `solid 1px ${SettingsManager.userColor}` }} + style={{ margin: 10, border: `solid 1px ${SnappingManager.userColor}` }} type="checkbox" onChange={() => (activeItem.presentation_zoomText = !BoolCast(activeItem.presentation_zoomText))} checked={BoolCast(activeItem.presentation_zoomText)} @@ -1821,10 +1797,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { this._openEffectDropdown = !this._openEffectDropdown; })} style={{ - color: SettingsManager.userColor, - background: SettingsManager.userVariantColor, + color: SnappingManager.userColor, + background: SnappingManager.userVariantColor, borderBottomLeftRadius: this._openEffectDropdown ? 0 : 5, - border: this._openEffectDropdown ? `solid 2px ${SettingsManager.userVariantColor}` : `solid 1px ${SettingsManager.userColor}`, + border: this._openEffectDropdown ? `solid 2px ${SettingsSnappingManagerManager.userVariantColor}` : `solid 1px ${SnappingManager.userColor}`, }}> {effect?.toString()} <FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this._openEffectDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={'angle-down'} /> @@ -1832,8 +1808,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { className="presBox-dropdownOptions" id={'presBoxMovementDropdown'} style={{ - color: SettingsManager.userColor, - background: SettingsManager.userBackgroundColor, + color: SnappingManager.userColor, + background: SnappingManager.userBackgroundColor, display: this._openEffectDropdown ? 'grid' : 'none', }} onPointerDown={e => e.stopPropagation()}> @@ -1844,7 +1820,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { </div> <div className="ribbon-doubleButton" style={{ display: effect === PresEffectDirection.None ? 'none' : 'inline-flex' }}> <div className="presBox-subheading">Effect direction</div> - <div className="ribbon-property" style={{ border: `solid 1px ${SettingsManager.userColor}` }}> + <div className="ribbon-property" style={{ border: `solid 1px ${SnappingManager.userColor}` }}> {StrCast(this.activeItem.presentation_effectDirection)} </div> </div> @@ -1882,7 +1858,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { <div className="slider-text" style={{ fontWeight: 500 }}> Start time (s) </div> - <div id="startTime" className="slider-number" style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }}> + <div id="startTime" className="slider-number" style={{ color: SnappingManager.userColor, backgroundColor: SnappingManager.userBackgroundColor }}> <input className="presBox-input" style={{ textAlign: 'center', width: '100%', height: 15, fontSize: 10 }} @@ -1898,7 +1874,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { <div className="slider-text" style={{ fontWeight: 500 }}> Duration (s) </div> - <div className="slider-number" style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }}> + <div className="slider-number" style={{ color: SnappingManager.userColor, backgroundColor: SnappingManager.userBackgroundColor }}> {Math.round((config_clipEnd - NumCast(activeItem.config_clipStart)) * 10) / 10} </div> </div> @@ -1906,7 +1882,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { <div className="slider-text" style={{ fontWeight: 500 }}> End time (s) </div> - <div id="endTime" className="slider-number" style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }}> + <div id="endTime" className="slider-number" style={{ color: SnappingManager.userColor, backgroundColor: SnappingManager.userBackgroundColor }}> <input className="presBox-input" onKeyDown={e => e.stopPropagation()} @@ -1926,14 +1902,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { min={clipStart} max={clipEnd} value={config_clipEnd} - style={{ gridColumn: 1, gridRow: 1, background: SettingsManager.userColor, color: SettingsManager.userVariantColor }} + style={{ gridColumn: 1, gridRow: 1, background: SnappingManager.userColor, color: SnappingManager.userVariantColor }} className={`toolbar-slider ${'end'}`} id="toolbar-slider" onPointerDown={e => { this._batch = UndoManager.StartBatch('config_clipEnd'); const endBlock = document.getElementById('endTime'); if (endBlock) { - endBlock.style.backgroundColor = SettingsManager.userVariantColor; + endBlock.style.backgroundColor = SnappingManager.userVariantColor; } e.stopPropagation(); }} @@ -1941,7 +1917,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { this._batch?.end(); const endBlock = document.getElementById('endTime'); if (endBlock) { - endBlock.style.backgroundColor = SettingsManager.userBackgroundColor; + endBlock.style.backgroundColor = SnappingManager.userBackgroundColor; } }} onChange={(e: React.ChangeEvent<HTMLInputElement>) => { @@ -1962,7 +1938,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { this._batch = UndoManager.StartBatch('config_clipStart'); const startBlock = document.getElementById('startTime'); if (startBlock) { - startBlock.style.backgroundColor = SettingsManager.userVariantColor; + startBlock.style.backgroundColor = SnappingManager.userVariantColor; } e.stopPropagation(); }} @@ -1970,7 +1946,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { this._batch?.end(); const startBlock = document.getElementById('startTime'); if (startBlock) { - startBlock.style.backgroundColor = SettingsManager.userBackgroundColor; + startBlock.style.backgroundColor = SnappingManager.userBackgroundColor; } }} onChange={(e: React.ChangeEvent<HTMLInputElement>) => { @@ -1993,7 +1969,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { <input className="presBox-checkbox" type="checkbox" - style={{ border: `solid 1px ${SettingsManager.userColor}` }} + style={{ border: `solid 1px ${SnappingManager.userColor}` }} onChange={() => (activeItem.presentation_mediaStart = 'manual')} checked={activeItem.presentation_mediaStart === 'manual'} /> @@ -2002,7 +1978,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { <div className="checkbox-container"> <input className="presBox-checkbox" - style={{ border: `solid 1px ${SettingsManager.userColor}` }} + style={{ border: `solid 1px ${SnappingManager.userColor}` }} type="checkbox" onChange={() => (activeItem.presentation_mediaStart = 'auto')} checked={activeItem.presentation_mediaStart === 'auto'} @@ -2016,7 +1992,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { <input className="presBox-checkbox" type="checkbox" - style={{ border: `solid 1px ${SettingsManager.userColor}` }} + style={{ border: `solid 1px ${SnappingManager.userColor}` }} onChange={() => (activeItem.presentation_mediaStop = 'manual')} checked={activeItem.presentation_mediaStop === 'manual'} /> @@ -2026,7 +2002,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { <input className="presBox-checkbox" type="checkbox" - style={{ border: `solid 1px ${SettingsManager.userColor}` }} + style={{ border: `solid 1px ${SnappingManager.userColor}` }} onChange={() => (activeItem.presentation_mediaStop = 'auto')} checked={activeItem.presentation_mediaStop === 'auto'} /> @@ -2270,15 +2246,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { } @action - toggleProperties = () => (SettingsManager.Instance.propertiesWidth = SettingsManager.Instance.propertiesWidth > 0 ? 0 : 250); + toggleProperties = () => (SnappingManager.Instance.propertiesWidth = SnappingManager.Instance.propertiesWidth > 0 ? 0 : 250); @computed get toolbar() { - const propIcon = SettingsManager.Instance.propertiesWidth > 0 ? 'angle-double-right' : 'angle-double-left'; - const propTitle = SettingsManager.Instance.propertiesWidth > 0 ? 'Close Presentation Panel' : 'Open Presentation Panel'; + const propIcon = SnappingManager.Instance.propertiesWidth > 0 ? 'angle-double-right' : 'angle-double-left'; + const propTitle = SnappingManager.Instance.propertiesWidth > 0 ? 'Close Presentation Panel' : 'Open Presentation Panel'; const mode = StrCast(this.Document._type_collection) as CollectionViewType; const isMini: boolean = this.toolbarWidth <= 100; - const activeColor = SettingsManager.userVariantColor; - const inactiveColor = lightOrDark(SettingsManager.userBackgroundColor) === Colors.WHITE ? Colors.WHITE : SettingsManager.userBackgroundColor; + const activeColor = SnappingManager.userVariantColor; + const inactiveColor = lightOrDark(SnappingManager.userBackgroundColor) === Colors.WHITE ? Colors.WHITE : SnappingManager.userBackgroundColor; return mode === CollectionViewType.Carousel3D || Doc.IsInMyOverlay(this.Document) ? null : ( <div id="toolbarContainer" className={'presBox-toolbar'}> {/* <Tooltip title={<><div className="dash-tooltip">{"Add new slide"}</div></>}><div className={`toolbar-button ${this.newDocumentTools ? "active" : ""}`} onClick={action(() => this.newDocumentTools = !this.newDocumentTools)}> @@ -2303,7 +2279,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { </Tooltip> <Tooltip title={<div className="dash-tooltip">{propTitle}</div>}> <div className="toolbar-button" style={{ position: 'absolute', right: 4, fontSize: 16 }} onClick={this.toggleProperties}> - <FontAwesomeIcon className={'toolbar-thumbtack'} icon={propIcon} style={{ color: SettingsManager.Instance.propertiesWidth > 0 ? activeColor : inactiveColor }} /> + <FontAwesomeIcon className={'toolbar-thumbtack'} icon={propIcon} style={{ color: SnappingManager.Instance.propertiesWidth > 0 ? activeColor : inactiveColor }} /> </div> </Tooltip> </> @@ -2378,12 +2354,24 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { // Case 1: There are still other frames and should go through all frames before going to next slide return ( <div className="presPanelOverlay" style={{ display: this.layoutDoc.presentation_status !== 'edit' ? 'inline-flex' : 'none' }}> - <Tooltip title={<div className="dash-tooltip">{'Loop'}</div>}> + <Tooltip title={<div className="dash-tooltip">Loop</div>}> <div className="presPanel-button" style={{ color: this.layoutDoc.presLoop ? Colors.MEDIUM_BLUE : 'white' }} - onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => (this.layoutDoc.presLoop = !this.layoutDoc.presLoop), false, false)}> - <FontAwesomeIcon icon={'redo-alt'} /> + onPointerDown={e => + setupMoveUpEvents( + this, + e, + returnFalse, + emptyFunction, + () => { + this.layoutDoc.presLoop = !this.layoutDoc.presLoop; + }, + false, + false + ) + }> + <FontAwesomeIcon icon="redo-alt" /> </div> </Tooltip> <div className="presPanel-divider" /> @@ -2617,12 +2605,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { childXPadding={Doc.IsComicStyle(this.Document) ? 20 : undefined} filterAddDocument={this.addDocumentFilter} removeDocument={returnFalse} - dontRegisterView={true} + dontRegisterView focus={this.focusElement} ScreenToLocalTransform={this.getTransform} AddToMap={this.AddToMap} RemFromMap={this.RemFromMap} - hierarchyIndex={emptyPath as any as number[]} + hierarchyIndex={emptyPath} /> ) : null} </div> diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index 28139eb14..fca5a2770 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -7,7 +7,8 @@ import { Doc, DocListCast, Opt } from '../../../../fields/Doc'; import { Id } from '../../../../fields/FieldSymbols'; import { List } from '../../../../fields/List'; import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types'; -import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from '../../../../Utils'; +import { emptyFunction } from '../../../../Utils'; +import { returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from '../../../../ClientUtils'; import { Docs } from '../../../documents/Documents'; import { CollectionViewType } from '../../../documents/DocumentTypes'; import { DocumentManager } from '../../../util/DocumentManager'; diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index 59f191af0..9c4080154 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -4,7 +4,8 @@ import { IReactionDisposer, ObservableMap, action, computed, makeObservable, obs import { observer } from 'mobx-react'; import * as React from 'react'; import { ColorResult } from 'react-color'; -import { Utils, returnFalse, setupMoveUpEvents, unimplementedFunction } from '../../../Utils'; +import { ClientUtils, returnFalse, setupMoveUpEvents } from '../../../ClientUtils'; +import { unimplementedFunction } from '../../../Utils'; import { Doc, Opt } from '../../../fields/Doc'; import { GPTCallType, gptAPICall } from '../../apis/gpt/GPT'; import { DocumentType } from '../../documents/DocumentTypes'; @@ -17,6 +18,7 @@ import { GPTPopup, GPTPopupMode } from './GPTPopup/GPTPopup'; @observer export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { + // eslint-disable-next-line no-use-before-define static Instance: AnchorMenu; private _disposer: IReactionDisposer | undefined; @@ -37,7 +39,9 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { // GPT additions @observable private selectedText: string = ''; @action - public setSelectedText = (txt: string) => (this.selectedText = txt); + public setSelectedText = (txt: string) => { + this.selectedText = txt; + }; public onMakeAnchor: () => Opt<Doc> = () => undefined; // Method to get anchor from text search @@ -64,7 +68,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { componentDidMount() { this._disposer = reaction( () => SelectionManager.Views.slice(), - sel => AnchorMenu.Instance.fadeOut(true) + () => AnchorMenu.Instance.fadeOut(true) ); } @@ -72,7 +76,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { * Invokes the API with the selected text and stores it in the summarized text. * @param e pointer down event */ - gptSummarize = async (e: React.PointerEvent) => { + gptSummarize = async () => { // move this logic to gptpopup, need to implement generate again GPTPopup.Instance.setVisible(true); GPTPopup.Instance.setMode(GPTPopupMode.SUMMARY); @@ -128,7 +132,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { <Group> <IconButton icon={<FontAwesomeIcon icon="highlighter" style={{ transition: 'transform 0.1s', transform: 'rotate(-45deg)' }} />} - tooltip={'Click to Highlight'} + tooltip="Click to Highlight" onClick={this.highlightClicked} colorPicker={this.highlightColor} color={SettingsManager.userColor} @@ -144,7 +148,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { hsl: { a: 0, h: 0, s: 0, l: 0 }, rgb: { a: 0, r: 0, b: 0, g: 0 }, }; - this.highlightColor = Utils.colorString(col); + this.highlightColor = ClientUtils.colorString(col); }; /** @@ -167,7 +171,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { color={SettingsManager.userColor} /> </div> - {/* GPT Summarize icon only shows up when text is highlighted, not on marquee selection*/} + {/* GPT Summarize icon only shows up when text is highlighted, not on marquee selection */} {AnchorMenu.Instance.StartCropDrag === unimplementedFunction && this.canSummarize() && ( <IconButton tooltip="Summarize with AI" // @@ -187,7 +191,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { <Popup tooltip="Find document to link to selected text" // type={Type.PRIM} - icon={<FontAwesomeIcon icon={'search'} />} + icon={<FontAwesomeIcon icon="search" />} popup={<LinkPopup key="popup" linkCreateAnchor={this.onMakeAnchor} />} color={SettingsManager.userColor} /> @@ -230,7 +234,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { )} {this.IsTargetToggler !== returnFalse && ( <Toggle - tooltip={'Make target visibility toggle on click'} + tooltip="Make target visibility toggle on click" type={Type.PRIM} toggleType={ToggleType.BUTTON} toggleStatus={this.IsTargetToggler()} diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx index da8a88803..cd13d4cbc 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx +++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx @@ -6,7 +6,7 @@ import * as React from 'react'; import { CgClose } from 'react-icons/cg'; import ReactLoading from 'react-loading'; import { TypeAnimation } from 'react-type-animation'; -import { Utils } from '../../../../Utils'; +import { ClientUtils } from '../../../../ClientUtils'; import { Doc } from '../../../../fields/Doc'; import { NumCast, StrCast } from '../../../../fields/Types'; import { Networking } from '../../../Network'; @@ -26,6 +26,7 @@ interface GPTPopupProps {} @observer export class GPTPopup extends ObservableReactComponent<GPTPopupProps> { + // eslint-disable-next-line no-use-before-define static Instance: GPTPopup; @observable @@ -71,8 +72,6 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> { @observable public highlightRange: number[] = []; @action callSummaryApi = () => {}; - @action callEditApi = () => {}; - @action replaceText = (replacement: string) => {}; @observable private done: boolean = false; @@ -110,24 +109,25 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> { * Generates a Dalle image and uploads it to the server. */ generateImage = async () => { - if (this.imgDesc === '') return; + if (this.imgDesc === '') return undefined; this.setImgUrls([]); this.setMode(GPTPopupMode.IMAGE); this.setVisible(true); this.setLoading(true); try { - let image_urls = await gptImageCall(this.imgDesc); - if (image_urls && image_urls[0]) { - const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [image_urls[0]] }); - const source = Utils.prepend(result.accessPaths.agnostic.client); - this.setImgUrls([[image_urls[0], source]]); + const imageUrls = await gptImageCall(this.imgDesc); + if (imageUrls && imageUrls[0]) { + const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [imageUrls[0]] }); + const source = ClientUtils.prepend(result.accessPaths.agnostic.client); + this.setImgUrls([[imageUrls[0], source]]); } } catch (err) { console.log(err); return ''; } this.setLoading(false); + return undefined; }; /** @@ -188,55 +188,43 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> { } }; - imageBox = () => { - return ( - <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}> - {this.heading('GENERATED IMAGE')} - <div className="image-content-wrapper"> - {this.imgUrls.map(rawSrc => ( - <div className="img-wrapper"> - <div className="img-container"> - <img key={rawSrc[0]} src={rawSrc[0]} width={150} height={150} alt="dalle generation" /> - </div> - <div className="btn-container"> - <Button text="Save Image" onClick={() => this.transferToImage(rawSrc[1])} color={StrCast(Doc.UserDoc().userColor)} type={Type.TERT} /> - </div> + imageBox = () => ( + <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}> + {this.heading('GENERATED IMAGE')} + <div className="image-content-wrapper"> + {this.imgUrls.map(rawSrc => ( + <div className="img-wrapper"> + <div className="img-container"> + <img key={rawSrc[0]} src={rawSrc[0]} width={150} height={150} alt="dalle generation" /> </div> - ))} - </div> - {!this.loading && ( - <> - <IconButton tooltip="Generate Again" onClick={this.generateImage} icon={<FontAwesomeIcon icon="redo-alt" size="lg" />} color={StrCast(Doc.UserDoc().userVariantColor)} /> - </> - )} + <div className="btn-container"> + <Button text="Save Image" onClick={() => this.transferToImage(rawSrc[1])} color={StrCast(Doc.UserDoc().userColor)} type={Type.TERT} /> + </div> + </div> + ))} </div> - ); - }; + {!this.loading && <IconButton tooltip="Generate Again" onClick={this.generateImage} icon={<FontAwesomeIcon icon="redo-alt" size="lg" />} color={StrCast(Doc.UserDoc().userVariantColor)} />} + </div> + ); - data = () => { - return ( - <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}> - {this.heading('GENERATED IMAGE')} - <div className="image-content-wrapper"> - {this.imgUrls.map(rawSrc => ( - <div className="img-wrapper"> - <div className="img-container"> - <img key={rawSrc[0]} src={rawSrc[0]} width={150} height={150} alt="dalle generation" /> - </div> - <div className="btn-container"> - <Button text="Save Image" onClick={() => this.transferToImage(rawSrc[1])} color={StrCast(Doc.UserDoc().userColor)} type={Type.TERT} /> - </div> + data = () => ( + <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}> + {this.heading('GENERATED IMAGE')} + <div className="image-content-wrapper"> + {this.imgUrls.map(rawSrc => ( + <div className="img-wrapper"> + <div className="img-container"> + <img key={rawSrc[0]} src={rawSrc[0]} width={150} height={150} alt="dalle generation" /> </div> - ))} - </div> - {!this.loading && ( - <> - <IconButton tooltip="Generate Again" onClick={this.generateImage} icon={<FontAwesomeIcon icon="redo-alt" size="lg" />} color={StrCast(Doc.UserDoc().userVariantColor)} /> - </> - )} + <div className="btn-container"> + <Button text="Save Image" onClick={() => this.transferToImage(rawSrc[1])} color={StrCast(Doc.UserDoc().userColor)} type={Type.TERT} /> + </div> + </div> + ))} </div> - ); - }; + {!this.loading && <IconButton tooltip="Generate Again" onClick={this.generateImage} icon={<FontAwesomeIcon icon="redo-alt" size="lg" />} color={StrCast(Doc.UserDoc().userVariantColor)} />} + </div> + ); summaryBox = () => ( <> @@ -255,7 +243,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> { }, 500); }, ]} - //cursor={{ hideWhenDone: true }} + // cursor={{ hideWhenDone: true }} /> ) : ( this.text @@ -294,9 +282,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> { <FontAwesomeIcon icon="exclamation-circle" size="sm" style={{ paddingRight: '5px' }} /> AI generated responses can contain inaccurate or misleading content. </div> - ) : ( - <></> - ); + ) : null; heading = (headingText: string) => ( <div className="summary-heading"> diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index aaff2a342..0ab952e84 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -10,7 +10,8 @@ import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; import { Cast, NumCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; -import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, returnAll, returnFalse, returnNone, returnZero, smoothScroll, Utils } from '../../../Utils'; +import { emptyFunction, Utils } from '../../../Utils'; +import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, returnAll, returnFalse, returnNone, returnZero, smoothScroll } from '../../../ClientUtils'; import { DocUtils } from '../../documents/Documents'; import { SelectionManager } from '../../util/SelectionManager'; import { SnappingManager } from '../../util/SnappingManager'; @@ -98,9 +99,13 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { } componentDidMount() { - runInAction(() => (this._showWaiting = true)); + runInAction(() => { + this._showWaiting = true; + }); this.setupPdfJsViewer(); - this._mainCont.current?.addEventListener('scroll', e => ((e.target as any).scrollLeft = 0)); + this._mainCont.current?.addEventListener('scroll', e => { + (e.target as any).scrollLeft = 0; + }); this._disposers.layout_autoHeight = reaction( () => this._props.layoutDoc._layout_autoHeight, @@ -176,7 +181,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { let focusSpeed: Opt<number>; if (doc !== this._props.Document && mainCont) { const windowHeight = this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1); - const scrollTo = Utils.scrollIntoView(scrollTop, doc[Height](), NumCast(this._props.layoutDoc._layout_scrollTop), windowHeight, windowHeight * 0.1, this._scrollHeight); + const scrollTo = ClientUtils.scrollIntoView(scrollTop, doc[Height](), NumCast(this._props.layoutDoc._layout_scrollTop), windowHeight, windowHeight * 0.1, this._scrollHeight); if (scrollTo !== undefined && scrollTo !== this._props.layoutDoc._layout_scrollTop) { if (!this._pdfViewer) this._initialScroll = { loc: scrollTo, easeFunc: options.easeFunc }; else if (!options.instant) this._scrollStopper = smoothScroll((focusSpeed = options.zoomTime ?? 500), mainCont, scrollTo, options.easeFunc, this._scrollStopper); @@ -456,7 +461,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { onClick = (e: React.MouseEvent) => { this._scrollStopper?.(); - if (this._setPreviewCursor && e.button === 0 && Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) { + if (this._setPreviewCursor && e.button === 0 && Math.abs(e.clientX - this._downX) < ClientUtils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < ClientUtils.DRAG_THRESHOLD) { this._setPreviewCursor(e.clientX, e.clientY, false, false, this._props.Document); } // e.stopPropagation(); // bcz: not sure why this was here. We need to allow the DocumentView to get clicks to process doubleClicks @@ -496,8 +501,8 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { overlayTransform = () => this.scrollXf().scale(1 / NumCast(this._props.layoutDoc._freeform_scale, 1)); panelWidth = () => this._props.PanelWidth() / (this._props.NativeDimScaling?.() || 1); panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1); - transparentFilter = () => [...this._props.childFilters(), Utils.TransparentBackgroundFilter]; - opaqueFilter = () => [...this._props.childFilters(), Utils.noDragDocsFilter, ...(SnappingManager.CanEmbed && this._props.isContentActive() ? [] : [Utils.OpaqueBackgroundFilter])]; + transparentFilter = () => [...this._props.childFilters(), ClientUtils.TransparentBackgroundFilter]; + opaqueFilter = () => [...this._props.childFilters(), ClientUtils.noDragDocsFilter, ...(SnappingManager.CanEmbed && this._props.isContentActive() ? [] : [ClientUtils.OpaqueBackgroundFilter])]; childStyleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string): any => { if (doc instanceof Doc && property === StyleProp.PointerEvents) { if (this.inlineTextAnnotations.includes(doc) || this._props.isContentActive() === false) return 'none'; @@ -532,7 +537,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { PanelWidth={this.panelWidth} ScreenToLocalTransform={this.overlayTransform} isAnyChildContentActive={returnFalse} - isAnnotationOverlayScrollable={true} + isAnnotationOverlayScrollable childFilters={childFilters} select={emptyFunction} styleProvider={this.childStyleProvider} diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index 9f153e86d..af9f05a14 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -2,7 +2,7 @@ import { Tooltip } from '@mui/material'; import { action, computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { Doc, DocListCastAsync, Field } from '../../../fields/Doc'; +import { Doc, DocListCastAsync, Field, FieldType } from '../../../fields/Doc'; import { DirectLinks, DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { DocCast, StrCast } from '../../../fields/Types'; @@ -145,7 +145,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() { .filter(d => d) .map(async d => { const fieldKey = Doc.LayoutFieldKey(d); - const annos = !Field.toString(Doc.LayoutField(d) as Field).includes('CollectionView'); + const annos = !Field.toString(Doc.LayoutField(d) as FieldType).includes('CollectionView'); const data = d[annos ? fieldKey + '_annotations' : fieldKey]; const docs = await DocListCastAsync(data); docs && newarray.push(...docs); diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx index eab33114e..b87e5cdde 100644 --- a/src/client/views/topbar/TopBar.tsx +++ b/src/client/views/topbar/TopBar.tsx @@ -5,14 +5,14 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { Flip } from 'react-awesome-reveal'; import { FaBug } from 'react-icons/fa'; +import { returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../ClientUtils'; import { Doc, DocListCast } from '../../../fields/Doc'; import { AclAdmin, DashVersion } from '../../../fields/DocSymbols'; import { StrCast } from '../../../fields/Types'; import { GetEffectiveAcl } from '../../../fields/util'; -import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../Utils'; +import { emptyFunction } from '../../../Utils'; import { CurrentUserUtils } from '../../util/CurrentUserUtils'; import { DocumentManager } from '../../util/DocumentManager'; -import { dropActionType } from '../../util/DragManager'; import { PingManager } from '../../util/PingManager'; import { ReportManager } from '../../util/reportManager/ReportManager'; import { ServerStats } from '../../util/ServerStats'; @@ -28,6 +28,7 @@ import { DocumentViewInternal, returnEmptyDocViewList } from '../nodes/DocumentV import { ObservableReactComponent } from '../ObservableReactComponent'; import { DefaultStyleProvider } from '../StyleProvider'; import './TopBar.scss'; +import { dropActionType } from '../../util/DropActionTypes'; /** * ABOUT: This is the topbar in Dash, which included the current Dashboard as well as access to information on the user |
