From 9e0f2fc211f4e12cfe6c6cdb3b113de5af2dbfe6 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 25 Mar 2025 22:58:35 -0400 Subject: fixed sizing and background of collection filter button. fixed exception in getDescription for text labels. --- .../views/collections/FlashcardPracticeUI.tsx | 46 ++++++++++++++-------- 1 file changed, 29 insertions(+), 17 deletions(-) (limited to 'src/client/views/collections/FlashcardPracticeUI.tsx') diff --git a/src/client/views/collections/FlashcardPracticeUI.tsx b/src/client/views/collections/FlashcardPracticeUI.tsx index c071c5fb8..3bcdd843e 100644 --- a/src/client/views/collections/FlashcardPracticeUI.tsx +++ b/src/client/views/collections/FlashcardPracticeUI.tsx @@ -7,13 +7,15 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { returnFalse, returnZero, setupMoveUpEvents } from '../../../ClientUtils'; import { emptyFunction } from '../../../Utils'; -import { Doc, DocListCast } from '../../../fields/Doc'; +import { Doc, DocListCast, Opt } from '../../../fields/Doc'; import { BoolCast, NumCast, StrCast } from '../../../fields/Types'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { ObservableReactComponent } from '../ObservableReactComponent'; import { DocumentView, DocumentViewProps } from '../nodes/DocumentView'; import './FlashcardPracticeUI.scss'; +import { StyleProp } from '../StyleProp'; +import { FieldViewProps } from '../nodes/FieldView'; export enum practiceMode { PRACTICE = 'practice', @@ -131,6 +133,7 @@ export class FlashcardPracticeUI extends ObservableReactComponent= 1 ? undefined : `translateY(${this.btnHeight() / this._props.ScreenToLocalBoxXf().Scale - this.btnHeight()}px)`, }}> ); } + childStyleProvider = (doc: Doc | undefined, props: Opt, property: string) => { + if (doc instanceof Doc && property === StyleProp.BackgroundColor) { + return SnappingManager.userVariantColor; + } + return this._props.docViewProps().styleProvider?.(doc, props, property); + }; tryFilterOut = (doc: Doc) => (this.practiceMode && doc?._layout_flashcardType && doc[this.practiceField] === practiceVal.CORRECT ? true : false); // show only cards that aren't marked as correct render() { return ( @@ -188,22 +197,25 @@ export class FlashcardPracticeUI extends ObservableReactComponent {!this.filterDoc ? null : ( - +
+ +
)} {this.practiceModesMenu} -- cgit v1.2.3-70-g09d2 From a3d6fae1482c61ca725d0a103f13b621aa32b3e3 Mon Sep 17 00:00:00 2001 From: bobzel Date: Sat, 5 Apr 2025 14:29:03 -0400 Subject: fixed multitoggle behavior to only toggle submenu closed. switched to hard-light for masking ink. fixed to make menu toggle take effect immediately. --- packages/components/src/components/Group/Group.tsx | 68 +++++------ .../components/MultiToggle/MultiToggle.stories.tsx | 125 ++++++++++----------- .../src/components/MultiToggle/MultiToggle.tsx | 36 +++--- packages/components/src/components/Popup/Popup.tsx | 22 ++-- src/client/documents/Documents.ts | 37 +++--- src/client/views/collections/CollectionSubView.tsx | 7 +- .../views/collections/FlashcardPracticeUI.tsx | 5 +- .../collectionFreeForm/CollectionFreeFormView.scss | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 5 +- src/client/views/nodes/FontIconBox/FontIconBox.tsx | 1 - 10 files changed, 139 insertions(+), 169 deletions(-) (limited to 'src/client/views/collections/FlashcardPracticeUI.tsx') diff --git a/packages/components/src/components/Group/Group.tsx b/packages/components/src/components/Group/Group.tsx index 7abe4a1c7..6275a09dd 100644 --- a/packages/components/src/components/Group/Group.tsx +++ b/packages/components/src/components/Group/Group.tsx @@ -1,49 +1,33 @@ -import React from 'react' -import './Group.scss' -import { Colors, IGlobalProps, getFontSize, isDark , getFormLabelSize } from '../../global'; +import React from 'react'; +import './Group.scss'; +import { Colors, IGlobalProps, getFontSize, isDark, getFormLabelSize } from '../../global'; export interface IGroupProps extends IGlobalProps { - children: any - rowGap?: number; - columnGap?: number; - padding?: number | string; + children: any; + rowGap?: number; + columnGap?: number; + padding?: number | string; } export const Group = (props: IGroupProps) => { - const { - children, - width = '100%', - rowGap = 5, - columnGap = 5, - padding = 0, - formLabel, - formLabelPlacement, - size, - style, - color, - fillWidth - } = props + const { children, width = '100%', rowGap = 5, columnGap = 5, padding = 0, formLabel, formLabelPlacement, size, style, fillWidth } = props; - const group: JSX.Element = - ( -
-
{children}
-
- ) + const group: JSX.Element = ( +
+
+ {children} +
+
+ ); - return ( - formLabel ? -
-
{formLabel}
- {group} -
- : - group - ) -} + return formLabel ? ( +
+
+ {formLabel} +
+ {group} +
+ ) : ( + group + ); +}; diff --git a/packages/components/src/components/MultiToggle/MultiToggle.stories.tsx b/packages/components/src/components/MultiToggle/MultiToggle.stories.tsx index e71423d7a..b9ba45f72 100644 --- a/packages/components/src/components/MultiToggle/MultiToggle.stories.tsx +++ b/packages/components/src/components/MultiToggle/MultiToggle.stories.tsx @@ -1,69 +1,68 @@ -import { Meta, Story } from '@storybook/react' -import React from 'react' -import { IMultiToggleProps, MultiToggle } from './MultiToggle' -import { FaAlignLeft, FaAlignCenter, FaAlignJustify, FaAlignRight } from 'react-icons/fa' +import { Meta, Story } from '@storybook/react'; +import React from 'react'; +import { IMultiToggleProps, MultiToggle } from './MultiToggle'; +import { FaAlignLeft, FaAlignCenter, FaAlignJustify, FaAlignRight } from 'react-icons/fa'; export default { - title: 'Dash/MultiToggle', - component: MultiToggle, - argTypes: {}, -} as Meta + title: 'Dash/MultiToggle', + component: MultiToggle, + argTypes: {}, +} as Meta; -const MultiToggleStory: Story = (args) => -export const MultiToggleOne = MultiToggleStory.bind({}) +const MultiToggleStory: Story = args => ; +export const MultiToggleOne = MultiToggleStory.bind({}); MultiToggleOne.args = { - tooltip: "Text alignment", - label: "Alignment", - defaultSelectedItems: "center", - toggleStatus: true, - isToggle: false, - items: [ - { - icon: , - tooltip: 'Align left', - val: "left" - }, - { - icon: , - tooltip: 'Align center', - val: "center" - }, - { - icon: , - tooltip: 'Align right', - val: "right" - }, - { - icon: , - tooltip: 'Justify', - val: "justify" - }, - ] -} + tooltip: 'Text alignment', + label: 'Alignment', + defaultSelectedItems: 'center', + toggleStatus: true, + items: [ + { + icon: , + tooltip: 'Align left', + val: 'left', + }, + { + icon: , + tooltip: 'Align center', + val: 'center', + }, + { + icon: , + tooltip: 'Align right', + val: 'right', + }, + { + icon: , + tooltip: 'Justify', + val: 'justify', + }, + ], +}; -export const MultiToggleTwo = MultiToggleStory.bind({}) +export const MultiToggleTwo = MultiToggleStory.bind({}); MultiToggleTwo.args = { - tooltip: "Text Tags", - label: "Tags", - defaultSelectedItems : ["left"], - background: "green", - color: 'white', - multiSelect: true, - items: [ - { - icon: , - tooltip: 'Like', - val: "left" - }, - { - icon: , - tooltip: 'Todo', - val: "center" - }, - { - icon: , - tooltip: 'Idea', - val: "right" - }, - ] -} + tooltip: 'Text Tags', + label: 'Tags', + defaultSelectedItems: ['left'], + background: 'green', + color: 'white', + multiSelect: true, + items: [ + { + icon: , + tooltip: 'Like', + val: 'left', + }, + { + icon: , + tooltip: 'Todo', + val: 'center', + }, + { + icon: , + tooltip: 'Idea', + val: 'right', + }, + ], +}; diff --git a/packages/components/src/components/MultiToggle/MultiToggle.tsx b/packages/components/src/components/MultiToggle/MultiToggle.tsx index 0f659c5ca..0a6fb81c9 100644 --- a/packages/components/src/components/MultiToggle/MultiToggle.tsx +++ b/packages/components/src/components/MultiToggle/MultiToggle.tsx @@ -16,7 +16,6 @@ export interface IMultiToggleProps extends IGlobalProps { defaultSelectedItems?: (string | number) | (string | number)[]; selectedItems?: (string | number) | (string | number)[]; onSelectionChange?: (val: (string | number) | (string | number)[], added: boolean) => unknown; - isToggle?: boolean; toggleStatus?: boolean; } @@ -37,33 +36,30 @@ export const MultiToggle = (props: IMultiToggleProps) => { const itemsMap = new Map(); items.forEach(item => itemsMap.set(item.val, item)); return ( -
+
- - {promoteToArray(selectedItems).length < 2 ? null :
+
} -
- ) +
+ + {promoteToArray(selectedItems).length < 2 ? null :
+
} +
} - isToggle={props.isToggle} + isToggle={true} toggleFunc={() => { const selItem = items.find(item => promoteToArray(selectedItems).includes(item.val)); selItem && setSelectedItemsLocal([selItem.val]); }} type={props.type} - label={props.isToggle ? props.label : undefined} - toggleStatus={props.isToggle ? props.toggleStatus : undefined} + label={undefined} color={color} background={background} popup={ diff --git a/packages/components/src/components/Popup/Popup.tsx b/packages/components/src/components/Popup/Popup.tsx index 82e60f343..9e72ece87 100644 --- a/packages/components/src/components/Popup/Popup.tsx +++ b/packages/components/src/components/Popup/Popup.tsx @@ -20,11 +20,10 @@ export interface IPopupProps extends IGlobalProps { toggle?: JSX.Element; popup: JSX.Element | string | (() => JSX.Element); trigger?: PopupTrigger; - toggleStatus?: boolean; isOpen?: boolean; setOpen?: (b: boolean) => void; background?: string; - isToggle?: boolean; + isToggle?: boolean; // whether popup stays open when background is clicked. muyst click toggle button tp close it. toggleFunc?: () => void; popupContainsPt?: (x: number, y: number) => boolean; } @@ -71,6 +70,7 @@ export const Popup = (props: IPopupProps) => { const rect = popperRef.current?.getBoundingClientRect(); const rect2 = toggleRef.current?.getBoundingClientRect(); if ( + !props.isToggle && (!rect2 || !(rect2.left < e.clientX && rect2.top < e.clientY && rect2.right > e.clientX && rect2.bottom > e.clientY)) && rect && !(rect.left < e.clientX && rect.top < e.clientY && rect.right > e.clientX && rect.bottom > e.clientY) && @@ -109,8 +109,8 @@ export const Popup = (props: IPopupProps) => { timeout = setTimeout(() => setOpen(false), 1000); } }}> - {toggle ?? ( -
+
+ {toggle ?? ( { iconPlacement={iconPlacement} text={text} label={props.label} - toggleStatus={isOpen || props.toggleStatus} + toggleStatus={isOpen} onClick={() => { if (trigger === PopupTrigger.CLICK) { - if (!props.isToggle || props.toggleStatus) { - setOpen(!isOpen); - } + setOpen(!isOpen); props.toggleFunc?.(); } }} fillWidth={fillWidth} /> -
- )} + )} +
{ ref={popperRef} style={{ width, height, background }} tabIndex={-1} - onPointerDown={e => { - e.stopPropagation(); - }} + onPointerDown={e => e.stopPropagation()} onPointerEnter={() => { if (trigger === PopupTrigger.HOVER || trigger === PopupTrigger.HOVER_DELAY) { clearTimeout(timeout); diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 8458699fa..f1655e3cf 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -4,7 +4,6 @@ import { basename } from 'path'; import { ClientUtils, OmitKeys } from '../../ClientUtils'; import { DateField } from '../../fields/DateField'; import { CreateLinkToActiveAudio, Doc, FieldType, Opt, updateCachedAcls } from '../../fields/Doc'; -import { Initializing } from '../../fields/DocSymbols'; import { Id } from '../../fields/FieldSymbols'; import { HtmlField } from '../../fields/HtmlField'; import { InkField } from '../../fields/InkField'; @@ -983,26 +982,22 @@ export namespace Docs { export function InkDocument(points: PointData[], options: DocumentOptions = {}, strokeWidth: number, color: string, strokeBezier: string, fillColor: string, arrowStart: string, arrowEnd: string, dash: string, isInkMask: boolean) { const ink = InstanceFromProto(Prototypes.get(DocumentType.INK), '', { title: 'ink', ...options }); - const I = Doc.GetProto(ink); - // I.layout_hideOpenButton = true; // don't show open full screen button when selected - I.color = color; - I.fillColor = fillColor && fillColor !== 'transparent' ? fillColor : undefined; - I.stroke = new InkField(points); - I.stroke_width = strokeWidth; - I.stroke_bezier = strokeBezier; - I.stroke_startMarker = arrowStart; - I.stroke_endMarker = arrowEnd; - I.stroke_dash = dash; - I.stroke_isInkMask = isInkMask; - I.text_align = 'center'; - I.rotation = 0; - I.width_min = 1; - I.height_min = 1; - I.defaultDoubleClick = 'ignore'; - I.author_date = new DateField(); - I.acl_Guest = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View; - // I.acl_Override = SharingPermissions.Unset; - I[Initializing] = false; + ink.$color = color; + ink.$fillColor = fillColor && fillColor !== 'transparent' ? fillColor : undefined; + ink.$stroke = new InkField(points); + ink.$stroke_width = strokeWidth; + ink.$stroke_bezier = strokeBezier; + ink.$stroke_startMarker = arrowStart; + ink.$stroke_endMarker = arrowEnd; + ink.$stroke_dash = dash; + ink.$stroke_isInkMask = isInkMask; + ink.$text_align = 'center'; + ink.$rotation = 0; + ink.$width_min = 1; + ink.$height_min = 1; + ink.$defaultDoubleClick = 'ignore'; + ink.$author_date = new DateField(); + ink.$acl_Guest = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View; return ink; } diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index cfd52f9ee..a88707c6f 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -13,6 +13,7 @@ import { BoolCast, Cast, DateCast, NumCast, ScriptCast, StrCast, toList } from ' import { WebField } from '../../../fields/URLField'; import { GetEffectiveAcl, TraceMobx } from '../../../fields/util'; import { GestureUtils } from '../../../pen-gestures/GestureUtils'; +import { Upload } from '../../../server/SharedMediaTypes'; import { DocServer } from '../../DocServer'; import { Networking } from '../../Network'; import { DocUtils } from '../../documents/DocUtils'; @@ -24,11 +25,11 @@ import { ImageUtils } from '../../util/Import & Export/ImageUtils'; import { SnappingManager } from '../../util/SnappingManager'; import { UndoManager } from '../../util/UndoManager'; import { ViewBoxBaseComponent } from '../DocComponent'; +import { DocumentViewProps } from '../nodes/DocumentContentsView'; +import { DocumentView } from '../nodes/DocumentView'; import { FieldViewProps } from '../nodes/FieldView'; -import { DocumentView, DocumentViewProps } from '../nodes/DocumentView'; -import { FlashcardPracticeUI } from './FlashcardPracticeUI'; import { OpenWhere, OpenWhereMod } from '../nodes/OpenWhere'; -import { Upload } from '../../../server/SharedMediaTypes'; +import { FlashcardPracticeUI } from './FlashcardPracticeUI'; export enum docSortings { Time = 'time', diff --git a/src/client/views/collections/FlashcardPracticeUI.tsx b/src/client/views/collections/FlashcardPracticeUI.tsx index 3bcdd843e..8cd9c5452 100644 --- a/src/client/views/collections/FlashcardPracticeUI.tsx +++ b/src/client/views/collections/FlashcardPracticeUI.tsx @@ -12,10 +12,11 @@ import { BoolCast, NumCast, StrCast } from '../../../fields/Types'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { ObservableReactComponent } from '../ObservableReactComponent'; -import { DocumentView, DocumentViewProps } from '../nodes/DocumentView'; +import { DocumentView } from '../nodes/DocumentView'; import './FlashcardPracticeUI.scss'; import { StyleProp } from '../StyleProp'; import { FieldViewProps } from '../nodes/FieldView'; +import { DocumentViewProps } from '../nodes/DocumentContentsView'; export enum practiceMode { PRACTICE = 'practice', @@ -142,7 +143,6 @@ export class FlashcardPracticeUI extends ObservableReactComponent() { background={background} multiSelect={true} onPointerDown={e => script && !toggleStatus && setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => script.run({ this: this.Document, value: undefined, _readOnly_: false }))} - isToggle={false} toggleStatus={toggleStatus} label={selectedItems.length === 1 ? selectedItems[0] : this.label} items={items.map(item => ({ -- cgit v1.2.3-70-g09d2 From 40b8e5c30e7251db6258cac5b4ed5fb4a6a2418d Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 7 Apr 2025 15:20:36 -0400 Subject: added multiToggle option to allow popup to stay up until explicitly untoggled. fixed multitoggle to honor toggleStatus. fixed popup to follow target. fixed setting text highlight background fro popup menu. fixed flashcardui buttons to have background sized properly. --- package-lock.json | 24 ++++++++++++ package.json | 1 + .../src/components/MultiToggle/MultiToggle.tsx | 16 +++++++- packages/components/src/components/Popup/Popup.tsx | 43 +++++++++++++++------- src/client/util/CurrentUserUtils.ts | 5 ++- .../views/collections/CollectionCardDeckView.tsx | 3 +- .../views/collections/FlashcardPracticeUI.tsx | 7 ++-- src/client/views/nodes/FontIconBox/FontIconBox.tsx | 1 + .../views/nodes/formattedText/FormattedTextBox.tsx | 2 +- .../views/nodes/formattedText/RichTextMenu.tsx | 3 +- 10 files changed, 81 insertions(+), 24 deletions(-) (limited to 'src/client/views/collections/FlashcardPracticeUI.tsx') diff --git a/package-lock.json b/package-lock.json index 3b11eda7c..9acca90ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,6 +40,7 @@ "@pinecone-database/pinecone": "^2.2.2", "@react-google-maps/api": "^2.19.2", "@react-spring/web": "^9.7.5", + "@thednp/position-observer": "^1.0.7", "@turf/turf": "^7.1.0", "@types/bezier-js": "^4.1.3", "@types/brotli": "^1.3.4", @@ -12081,6 +12082,29 @@ "@testing-library/dom": ">=7.21.4" } }, + "node_modules/@thednp/position-observer": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@thednp/position-observer/-/position-observer-1.0.7.tgz", + "integrity": "sha512-MkUAMMgqZPxy71hpcrKr9ZtedMk+oIFbFs5B8uKD857iuYKRJxgJtC1Itus14EEM4qMyeN0x47AUZJmZJQyXbQ==", + "license": "MIT", + "dependencies": { + "@thednp/shorty": "^2.0.10" + }, + "engines": { + "node": ">=16", + "pnpm": ">=8.6.0" + } + }, + "node_modules/@thednp/shorty": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@thednp/shorty/-/shorty-2.0.10.tgz", + "integrity": "sha512-H+hs1lw3Yc1NfwG0b7F7YmVjxQZ31NO2+6zx+I+9XabHxdwPKjvYJnkKKXr7bSItgm2AFrfOn5+3veB6W4iauw==", + "license": "MIT", + "engines": { + "node": ">=16", + "pnpm": ">=8.6.0" + } + }, "node_modules/@tokenizer/token": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", diff --git a/package.json b/package.json index fe76189bf..1038c470e 100644 --- a/package.json +++ b/package.json @@ -123,6 +123,7 @@ "@pinecone-database/pinecone": "^2.2.2", "@react-google-maps/api": "^2.19.2", "@react-spring/web": "^9.7.5", + "@thednp/position-observer": "^1.0.7", "@turf/turf": "^7.1.0", "@types/bezier-js": "^4.1.3", "@types/brotli": "^1.3.4", diff --git a/packages/components/src/components/MultiToggle/MultiToggle.tsx b/packages/components/src/components/MultiToggle/MultiToggle.tsx index 0a6fb81c9..c1d610c34 100644 --- a/packages/components/src/components/MultiToggle/MultiToggle.tsx +++ b/packages/components/src/components/MultiToggle/MultiToggle.tsx @@ -17,6 +17,7 @@ export interface IMultiToggleProps extends IGlobalProps { selectedItems?: (string | number) | (string | number)[]; onSelectionChange?: (val: (string | number) | (string | number)[], added: boolean) => unknown; toggleStatus?: boolean; + showUntilToggle?: boolean; // whether popup stays open when background is clicked. muyst click toggle button tp close it. } function promoteToArrayOrUndefined(d: (string | number)[] | (string | number) | undefined) { @@ -32,12 +33,23 @@ export const MultiToggle = (props: IMultiToggleProps) => { init = false; const [selectedItemsLocal, setSelectedItemsLocal] = useState(initVal as (string | number) | (string | number)[]); - const { items, selectedItems = selectedItemsLocal, tooltip, tooltipPlacement = 'top', onSelectionChange, color, background } = props; + const { + items, // + selectedItems = selectedItemsLocal, + tooltip, + toggleStatus, + tooltipPlacement = 'top', + onSelectionChange, + color, + background, + } = props; const itemsMap = new Map(); items.forEach(item => itemsMap.set(item.val, item)); return (
{ {promoteToArray(selectedItems).length < 2 ? null :
+
}
} - isToggle={true} + showUntilToggle={props.showUntilToggle ?? true} toggleFunc={() => { const selItem = items.find(item => promoteToArray(selectedItems).includes(item.val)); selItem && setSelectedItemsLocal([selItem.val]); diff --git a/packages/components/src/components/Popup/Popup.tsx b/packages/components/src/components/Popup/Popup.tsx index 9e72ece87..9e91a75cf 100644 --- a/packages/components/src/components/Popup/Popup.tsx +++ b/packages/components/src/components/Popup/Popup.tsx @@ -3,6 +3,7 @@ import { IGlobalProps, Placement, Size } from '../../global'; import { Toggle, ToggleType } from '../Toggle'; import './Popup.scss'; import { Popper } from '@mui/material'; +import PositionObserver from '@thednp/position-observer'; export enum PopupTrigger { CLICK = 'click', @@ -23,9 +24,10 @@ export interface IPopupProps extends IGlobalProps { isOpen?: boolean; setOpen?: (b: boolean) => void; background?: string; - isToggle?: boolean; // whether popup stays open when background is clicked. muyst click toggle button tp close it. + showUntilToggle?: boolean; // whether popup stays open when background is clicked. muyst click toggle button tp close it. toggleFunc?: () => void; popupContainsPt?: (x: number, y: number) => boolean; + multitoggle?: boolean; } /** @@ -57,20 +59,21 @@ export const Popup = (props: IPopupProps) => { fillWidth, iconPlacement = 'left', background, + multitoggle, popupContainsPt, } = props; const triggerRef = useRef(null); const popperRef = useRef(null); - const toggleRef = useRef(null); + const [toggleRef, setToggleRef] = useState(null); let timeout = setTimeout(() => {}); const handlePointerAwayDown = (e: PointerEvent) => { const rect = popperRef.current?.getBoundingClientRect(); - const rect2 = toggleRef.current?.getBoundingClientRect(); + const rect2 = toggleRef?.getBoundingClientRect(); if ( - !props.isToggle && + !props.showUntilToggle && (!rect2 || !(rect2.left < e.clientX && rect2.top < e.clientY && rect2.right > e.clientX && rect2.bottom > e.clientY)) && rect && !(rect.left < e.clientX && rect.top < e.clientY && rect.right > e.clientX && rect.bottom > e.clientY) && @@ -82,22 +85,36 @@ export const Popup = (props: IPopupProps) => { } }; + let observer: PositionObserver | undefined = undefined; + const [previousPosition, setPreviousPosition] = useState(toggleRef?.getBoundingClientRect()); + useEffect(() => { if (isOpen) { window.removeEventListener('pointerdown', handlePointerAwayDown, { capture: true }); window.addEventListener('pointerdown', handlePointerAwayDown, { capture: true }); - return () => window.removeEventListener('pointerdown', handlePointerAwayDown, { capture: true }); - } - }, [isOpen, popupContainsPt]); - + if (toggleRef && multitoggle) { + (observer = new PositionObserver(entries => { + entries.forEach(entry => { + const currentPosition = entry.boundingClientRect; + if (Math.floor(currentPosition.top) !== Math.floor(previousPosition?.top ?? 0) || Math.floor(currentPosition.left) !== Math.floor(previousPosition?.left ?? 0)) { + // Perform actions when position changes + setPreviousPosition(currentPosition); // Update previous position + } + }); + })).observe(toggleRef); + } + return () => { + window.removeEventListener('pointerdown', handlePointerAwayDown, { capture: true }); + observer?.disconnect(); + }; + } else observer?.disconnect(); + }, [isOpen, toggleRef, popupContainsPt]); return (
{ - if (trigger === PopupTrigger.CLICK) setOpen(!isOpen); - }} + className={`trigger-container ${fillWidth && 'fillWidth'}`} + onClick={() => trigger === PopupTrigger.CLICK && setOpen(!isOpen)} onPointerEnter={() => { if (trigger === PopupTrigger.HOVER || trigger === PopupTrigger.HOVER_DELAY) { clearTimeout(timeout); @@ -109,7 +126,7 @@ export const Popup = (props: IPopupProps) => { timeout = setTimeout(() => setOpen(false), 1000); } }}> -
+
setToggleRef(R)}> {toggle ?? ( ; ignoreClick?: boolean; + showUntilToggle?: boolean; // whether the popup should stay open when the background is clicked. buttonText?: string; backgroundColor?: string; waitForDoubleClickToClick?: boolean; @@ -778,7 +779,7 @@ pie title Minerals in my tap water { title: "Circle", toolTip: "Circle (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "circle", toolType: Gestures.Circle, scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} }, { title: "Square", toolTip: "Square (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "square", toolType: Gestures.Rectangle, scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} }, { title: "Line", toolTip: "Line (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "minus", toolType: Gestures.Line, scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} }, - { title: "Ink", toolTip: "Ink", btnType: ButtonType.MultiToggleButton, toolType: InkTool.Ink, scripts: {onClick:'{ return setActiveTool(this.toolType, true, _readOnly_);}' }, + { title: "Ink", toolTip: "Ink", btnType: ButtonType.MultiToggleButton, toolType: InkTool.Ink, showUntilToggle: true, scripts: {onClick:'{ return setActiveTool(this.toolType, true, _readOnly_);}' }, subMenu: [ { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen-nib", toolType: InkInkTool.Pen, ignoreClick: true, scripts: {onClick:'{ return setActiveTool(this.toolType, true, _readOnly_);}' }}, { title: "Highlight",toolTip: "Highlight (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", toolType: InkInkTool.Highlight, ignoreClick: true, scripts: {onClick:'{ return setActiveTool(this.toolType, true, _readOnly_);}' }}, @@ -786,7 +787,7 @@ pie title Minerals in my tap water ]}, { title: "Width", toolTip: "Stroke width", btnType: ButtonType.NumberSliderButton, toolType: InkProperty.StrokeWidth,ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"!activeInkTool()"}, numBtnMin: 1, linearBtnWidth:40}, { title: "Color", toolTip: "Stroke color", btnType: ButtonType.ColorButton, icon: "pen", toolType: InkProperty.StrokeColor,ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"!activeInkTool()"}}, - { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.MultiToggleButton, toolType: InkTool.Eraser, scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' }, + { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.MultiToggleButton, toolType: InkTool.Eraser, showUntilToggle: true, scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' }, subMenu: [ { title: "Stroke", toolTip: "Eraser complete strokes",btnType: ButtonType.ToggleButton, icon: "eraser", toolType:InkEraserTool.Stroke, ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'}}, { title: "Segment", toolTip: "Erase between intersections",btnType:ButtonType.ToggleButton,icon:"xmark", toolType:InkEraserTool.Segment, ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'}}, diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx index 756b37f99..50de7c601 100644 --- a/src/client/views/collections/CollectionCardDeckView.tsx +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -23,7 +23,8 @@ import { undoable, UndoManager } from '../../util/UndoManager'; import { PinDocView, PinProps } from '../PinFuncs'; import { StyleProp } from '../StyleProp'; import { TagItem } from '../TagsView'; -import { DocumentView, DocumentViewProps } from '../nodes/DocumentView'; +import { DocumentViewProps } from '../nodes/DocumentContentsView'; +import { DocumentView } from '../nodes/DocumentView'; import { FocusViewOptions } from '../nodes/FocusViewOptions'; import './CollectionCardDeckView.scss'; import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; diff --git a/src/client/views/collections/FlashcardPracticeUI.tsx b/src/client/views/collections/FlashcardPracticeUI.tsx index 8cd9c5452..f24a8acb7 100644 --- a/src/client/views/collections/FlashcardPracticeUI.tsx +++ b/src/client/views/collections/FlashcardPracticeUI.tsx @@ -63,8 +63,8 @@ export class FlashcardPracticeUI extends ObservableReactComponent doc.title === 'Filter'); } // prettier-ignore @computed get practiceMode() { return this._props.allChildDocs().some(doc => doc._layout_flashcardType) ? StrCast(this._props.layoutDoc.practiceMode) : ''; } // prettier-ignore - btnHeight = () => NumCast(this.filterDoc?.height) * Math.min(1, this._props.ScreenToLocalBoxXf().Scale); - btnWidth = () => (!this.filterDoc ? 1 : (this.btnHeight() * NumCast(this.filterDoc._width)) / NumCast(this.filterDoc._height)); + btnHeight = () => NumCast(this.filterDoc?.height); + btnWidth = () => (!this.filterDoc ? 1 : NumCast(this.filterDoc._width)); /** * Sets the practice mode answer style for flashcards @@ -135,7 +135,6 @@ export class FlashcardPracticeUI extends ObservableReactComponent= 1 ? undefined : `translateY(${this.btnHeight() / this._props.ScreenToLocalBoxXf().Scale - this.btnHeight()}px)`, }}> +
{!this.filterDoc ? null : (
() { multiSelect={true} onPointerDown={e => script && !toggleStatus && setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => script.run({ this: this.Document, value: undefined, _readOnly_: false }))} toggleStatus={toggleStatus} + showUntilToggle={BoolCast(this.Document.showUntilToggle)} // allow the toggle to be clickable unless ignoreClick is set on the Document label={selectedItems.length === 1 ? selectedItems[0] : this.label} items={items.map(item => ({ icon: , diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index d90be007f..341340363 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1761,7 +1761,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { for (let newFocusEle = target instanceof HTMLElement ? target : null; newFocusEle; newFocusEle = newFocusEle?.parentElement) { // test if parent of new focused element is a UI button (should be more specific than testing className) - if (newFocusEle?.className === 'fonticonbox' || newFocusEle?.className === 'popup-container') { + if (['fonticonbox', 'antimodeMenu-cont', 'popup-container'].includes(newFocusEle?.className ?? '')) { return this.EditorView?.focus(); // keep focus on text box } } diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index 758b4035e..10c95f1e1 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -367,7 +367,8 @@ export class RichTextMenu extends AntimodeMenu { setFontField = (value: string, fontField: 'fitBox' | 'fontSize' | 'fontFamily' | 'fontColor' | 'fontHighlight') => { if (this.TextView && this.view && fontField !== 'fitBox') { - if (this.view.hasFocus()) { + const anchorNode = window.getSelection()?.anchorNode; + if (this.view.hasFocus() || (anchorNode && this.TextView.ProseRef?.contains(anchorNode))) { const attrs: { [key: string]: string } = {}; attrs[fontField] = value; const fmark = this.view.state.schema.marks['pF' + fontField.substring(1)].create(attrs); -- cgit v1.2.3-70-g09d2 From 78ac87b8acf63079071e5e8805692ed8c30042ce Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 23 Apr 2025 22:02:36 -0400 Subject: lots of typechecking fixes. --- .../apis/google_docs/GooglePhotosClientUtils.ts | 2 +- src/client/util/CalendarManager.tsx | 2 +- src/client/util/DocumentManager.ts | 8 +++--- src/client/util/LinkManager.ts | 3 +- src/client/util/ReplayMovements.ts | 6 ++-- src/client/util/ScriptManager.ts | 9 ++---- src/client/util/SearchUtil.ts | 5 ++-- .../util/reportManager/ReportManagerComponents.tsx | 4 +-- src/client/views/DashboardView.tsx | 28 +++++++++---------- src/client/views/InkStrokeProperties.ts | 8 +++--- src/client/views/InkingStroke.tsx | 12 ++++---- src/client/views/PropertiesButtons.tsx | 23 ++++++++-------- .../views/PropertiesDocBacklinksSelector.tsx | 8 ++---- src/client/views/PropertiesDocContextSelector.tsx | 4 +-- src/client/views/UndoStack.tsx | 5 +--- src/client/views/animationtimeline/Region.tsx | 32 ++++++++++------------ src/client/views/animationtimeline/Track.tsx | 24 ++++++++-------- src/client/views/collections/CollectionMenu.tsx | 4 +-- .../collections/CollectionNoteTakingViewColumn.tsx | 2 +- .../views/collections/CollectionPileView.tsx | 1 - .../collections/CollectionStackedTimeline.tsx | 2 +- .../views/collections/CollectionTreeView.tsx | 8 +++--- .../views/collections/FlashcardPracticeUI.tsx | 2 +- src/client/views/collections/TreeView.tsx | 4 +-- .../CollectionFreeFormLayoutEngines.tsx | 2 +- .../collectionFreeForm/FaceCollectionBox.tsx | 13 +++++---- .../collectionSchema/SchemaColumnHeader.tsx | 2 +- .../collectionSchema/SchemaTableCell.tsx | 18 ++++++------ src/client/views/linking/LinkMenuGroup.tsx | 5 ++-- src/client/views/linking/LinkMenuItem.tsx | 4 +-- .../views/newlightbox/ExploreView/ExploreView.tsx | 4 +-- src/client/views/newlightbox/NewLightboxView.tsx | 7 ++--- src/client/views/nodes/DataVizBox/DataVizBox.tsx | 16 +++++------ .../views/nodes/DataVizBox/SchemaCSVPopUp.tsx | 6 ++-- .../nodes/DataVizBox/components/Histogram.tsx | 17 ++++++------ .../nodes/DataVizBox/components/LineChart.tsx | 25 +++++++++++------ .../views/nodes/DataVizBox/components/TableBox.tsx | 2 +- src/client/views/nodes/EquationBox.tsx | 1 - src/client/views/nodes/FieldView.tsx | 7 +++-- src/client/views/nodes/FunctionPlotBox.tsx | 11 ++++---- src/client/views/nodes/ImageBox.tsx | 2 +- .../views/nodes/MapBox/DirectionsAnchorMenu.tsx | 2 +- src/client/views/nodes/calendarBox/CalendarBox.tsx | 3 +- .../nodes/chatbot/chatboxcomponents/ChatBox.tsx | 20 ++++++++------ .../views/nodes/chatbot/tools/GetDocsTool.ts | 5 +++- .../views/nodes/chatbot/vectorstore/Vectorstore.ts | 15 +++++----- .../views/nodes/formattedText/DailyJournal.tsx | 2 +- src/client/views/nodes/scrapbook/ScrapbookBox.tsx | 2 +- src/client/views/pdf/Annotation.tsx | 7 +++-- src/client/views/search/FaceRecognitionHandler.tsx | 6 ++-- src/fields/Doc.ts | 2 +- src/fields/Types.ts | 2 ++ 52 files changed, 207 insertions(+), 207 deletions(-) (limited to 'src/client/views/collections/FlashcardPracticeUI.tsx') diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts index 4b86a8341..15fd6313a 100644 --- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts +++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts @@ -311,7 +311,7 @@ export namespace GooglePhotos { sources .filter(source => ImageCast(Doc.GetProto(source).data)) .forEach(async source => { - const data = ImageCast(Doc.GetProto(source).data); + const data = ImageCast(Doc.GetProto(source).data)!; const url = data.url.href; const target = Doc.MakeEmbedding(source); const description = parseDescription(target, descriptionKey); diff --git a/src/client/util/CalendarManager.tsx b/src/client/util/CalendarManager.tsx index 4e321a893..b50e39c02 100644 --- a/src/client/util/CalendarManager.tsx +++ b/src/client/util/CalendarManager.tsx @@ -162,7 +162,7 @@ export class CalendarManager extends ObservableReactComponent { console.log('my calendars: ', Doc.MyCalendars); if (this.creationType === 'new-calendar') { - Doc.AddDocToList(Doc.MyCalendars, 'data', calendar); // add to new calendar to dashboard calendars + Doc.MyCalendars && Doc.AddDocToList(Doc.MyCalendars, 'data', calendar); // add to new calendar to dashboard calendars } } }; diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index ad57c2a62..3bae2881e 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -173,10 +173,10 @@ export class DocumentManager { while ( containerDocContext.length && DocCast(containerDocContext[0]?.embedContainer) && - DocCast(containerDocContext[0].embedContainer)?._type_collection !== CollectionViewType.Docking && + DocCast(containerDocContext[0].embedContainer)!._type_collection !== CollectionViewType.Docking && (includeExistingViews || !DocumentManager.Instance.getDocumentView(containerDocContext[0])) ) { - containerDocContext = [Cast(containerDocContext[0].embedContainer, Doc, null), ...containerDocContext]; + containerDocContext = [DocCast(containerDocContext[0].embedContainer)!, ...containerDocContext]; } return containerDocContext; } @@ -248,7 +248,7 @@ export class DocumentManager { finished?: (changed: boolean) => void // func called after focusing on target with flag indicating whether anything needed to be done. ) => { const options = optionsIn; - Doc.RemoveDocFromList(Doc.MyRecentlyClosed, undefined, targetDoc); + Doc.MyRecentlyClosed && Doc.RemoveDocFromList(Doc.MyRecentlyClosed, undefined, targetDoc); const docContextPath = DocumentManager.GetContextPath(targetDoc, true); if (docContextPath.some(doc => doc.hidden)) options.toggleTarget = false; let activatedTab = false; @@ -272,7 +272,7 @@ export class DocumentManager { })); if (options.openLocation?.includes(OpenWhere.lightbox)) { // even if we found the document view, if the target is a lightbox, we try to open it in the lightbox to preserve lightbox semantics (eg, there's only one active doc in the lightbox) - const target = DocCast(targetDoc.annotationOn, targetDoc); + const target = DocCast(targetDoc.annotationOn, targetDoc)!; const compView = this.getDocumentView(DocCast(target.embedContainer))?.ComponentView; if ((compView?.addDocTab ?? compView?._props.addDocTab)?.(target, options.openLocation)) { await new Promise(waitres => { diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 344e2e4c0..d8e0c4cbe 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -217,7 +217,8 @@ export class LinkManager { } // finds the opposite anchor of a given anchor in a link - public static getOppositeAnchor(linkDoc: Doc, anchor: Doc): Doc | undefined { + public static getOppositeAnchor(linkDoc: Doc | undefined, anchor: Doc | undefined): Doc | undefined { + if (!linkDoc || !anchor) return undefined; const id = LinkManager.anchorIndex(linkDoc, anchor); const a1 = DocCast(linkDoc.link_anchor_1); const a2 = DocCast(linkDoc.link_anchor_2); diff --git a/src/client/util/ReplayMovements.ts b/src/client/util/ReplayMovements.ts index 62a09a8bc..4f0423342 100644 --- a/src/client/util/ReplayMovements.ts +++ b/src/client/util/ReplayMovements.ts @@ -108,9 +108,11 @@ export class ReplayMovements { movements.forEach((movement, i) => { if (typeof movement.doc === 'string') { - movements[i].doc = IdToDoc(movement.doc); - if (!movements[i].doc) { + const doc = IdToDoc(movement.doc); + if (!doc) { console.log('ERROR: tracked doc not found'); + } else { + movements[i].doc = doc; } } }); diff --git a/src/client/util/ScriptManager.ts b/src/client/util/ScriptManager.ts index 9158f6c0b..8c7f88bf6 100644 --- a/src/client/util/ScriptManager.ts +++ b/src/client/util/ScriptManager.ts @@ -1,7 +1,6 @@ -import { Doc, DocListCast } from '../../fields/Doc'; +import { Doc, DocListCast, StrListCast } from '../../fields/Doc'; import { List } from '../../fields/List'; -import { listSpec } from '../../fields/Schema'; -import { Cast, StrCast } from '../../fields/Types'; +import { StrCast } from '../../fields/Types'; import { Docs } from '../documents/Documents'; import { ScriptingGlobals } from './ScriptingGlobals'; @@ -10,7 +9,6 @@ export class ScriptManager { // eslint-disable-next-line no-use-before-define private static _instance: ScriptManager; public static get Instance(): ScriptManager { - // eslint-disable-next-line no-return-assign return this._instance || (this._instance = new this()); } private constructor() { @@ -58,7 +56,7 @@ export class ScriptManager { public static addScriptToGlobals(scriptDoc: Doc): void { ScriptingGlobals.removeGlobal(StrCast(scriptDoc.name)); - const params = Cast(scriptDoc['data-params'], listSpec('string'), []); + const params = StrListCast(scriptDoc['data-params']); const paramNames = params.reduce((o: string, p: string) => { let out = o; if (params.indexOf(p) === params.length - 1) { @@ -69,7 +67,6 @@ export class ScriptManager { return out; }, '' as string); - // eslint-disable-next-line no-new-func const f = new Function(paramNames, StrCast(scriptDoc.script)); Object.defineProperty(f, 'name', { value: StrCast(scriptDoc.name), writable: false }); diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts index e4adcaa7e..fc3bb99ab 100644 --- a/src/client/util/SearchUtil.ts +++ b/src/client/util/SearchUtil.ts @@ -13,7 +13,7 @@ export namespace SearchUtil { const blockedKeys = matchKeyNames ? [] : Object.entries(DocOptions) - .filter(([, info]: [string, FInfo]) => !info?.searchable()) + .filter(([, info]: [string, FieldType | FInfo | undefined]) => (info instanceof FInfo ? !info.searchable() : true)) .map(([key]) => key); const exact = queryIn.startsWith('='); @@ -22,8 +22,7 @@ export namespace SearchUtil { const results = new ObservableMap(); if (collectionDoc) { const docs = DocListCast(collectionDoc[Doc.LayoutDataKey(collectionDoc)]); - // eslint-disable-next-line @typescript-eslint/ban-types - const docIDs: String[] = []; + const docIDs: string[] = []; SearchUtil.foreachRecursiveDoc(docs, (depth: number, doc: Doc) => { const dtype = StrCast(doc.type) as DocumentType; if (dtype && !blockedTypes.includes(dtype) && !docIDs.includes(doc[Id]) && depth >= 0) { diff --git a/src/client/util/reportManager/ReportManagerComponents.tsx b/src/client/util/reportManager/ReportManagerComponents.tsx index 92f877859..80653779e 100644 --- a/src/client/util/reportManager/ReportManagerComponents.tsx +++ b/src/client/util/reportManager/ReportManagerComponents.tsx @@ -1,5 +1,3 @@ -/* eslint-disable react/require-default-props */ -/* eslint-disable prefer-destructuring */ /* eslint-disable no-use-before-define */ import * as React from 'react'; import ReactMarkdown from 'react-markdown'; @@ -299,7 +297,7 @@ export function IssueView({ issue }: IssueViewProps) { )} - + {issueBody} diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx index f61f6db18..3ceb23ffd 100644 --- a/src/client/views/DashboardView.tsx +++ b/src/client/views/DashboardView.tsx @@ -113,12 +113,12 @@ export class DashboardView extends ObservableReactComponent { getDashboards = (whichGroup: DashboardGroup) => { if (whichGroup === DashboardGroup.MyDashboards) { - return DocListCast(Doc.MyDashboards.data).filter(dashboard => dashboard.$author === ClientUtils.CurrentUserEmail()); + return DocListCast(Doc.MyDashboards?.data).filter(dashboard => dashboard.$author === ClientUtils.CurrentUserEmail()); } - return DocListCast(Doc.MySharedDocs.data_dashboards).filter(doc => doc.dockingConfig); + return DocListCast(Doc.MySharedDocs?.data_dashboards).filter(doc => doc.dockingConfig); }; - isUnviewedSharedDashboard = (dashboard: Doc) => !DocListCast(Doc.MySharedDocs.viewed).includes(dashboard); + isUnviewedSharedDashboard = (dashboard: Doc) => !DocListCast(Doc.MySharedDocs?.viewed).includes(dashboard); @undoBatch createNewDashboard = (name: string, background?: string) => { @@ -155,7 +155,7 @@ export class DashboardView extends ObservableReactComponent { @action openNewDashboardModal = () => { this.openModal = true; - this.setNewDashboardName(`Dashboard ${DocListCast(Doc.MyDashboards.data).length + 1}`); + this.setNewDashboardName(`Dashboard ${DocListCast(Doc.MyDashboards?.data).length + 1}`); }; _downX: number = 0; @@ -191,7 +191,7 @@ export class DashboardView extends ObservableReactComponent {