From 71194ef203859d5c10bc199381ced8abeaf6c60c Mon Sep 17 00:00:00 2001 From: Yury Kornilov Date: Wed, 29 Oct 2025 14:26:08 +0300 Subject: [PATCH 1/3] Add dash Body component to registry --- src/ui/registry/units/dash/components-map.tsx | 3 +++ src/ui/registry/units/dash/register.tsx | 2 ++ src/ui/units/dash/containers/Body/Body.tsx | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ui/registry/units/dash/components-map.tsx b/src/ui/registry/units/dash/components-map.tsx index ac4a42c2eb..af06c3c5ac 100644 --- a/src/ui/registry/units/dash/components-map.tsx +++ b/src/ui/registry/units/dash/components-map.tsx @@ -1,3 +1,5 @@ +import type {OwnProps as DashBodyProps} from 'ui/units/dash/containers/Body/Body'; + import type {DialogTextWidgetProps} from '../../../components/DialogTextWidget/DialogTextWidget'; import {makeDefaultEmpty} from '../../components/DefaultEmpty'; @@ -8,6 +10,7 @@ export const dashComponentsMap = { DashSelectState: makeDefaultEmpty(), DashSelectStateDialog: makeDefaultEmpty(), DialogTextWidget: makeDefaultEmpty(), + DashBody: makeDefaultEmpty(), DashBodyAdditionalControls: makeDefaultEmpty(), DialogDashOtherSettingsPrepend: makeDefaultEmpty(), DashActionPanelAdditionalButtons: makeDefaultEmpty(), diff --git a/src/ui/registry/units/dash/register.tsx b/src/ui/registry/units/dash/register.tsx index 8e87e827a8..bca604ca2d 100644 --- a/src/ui/registry/units/dash/register.tsx +++ b/src/ui/registry/units/dash/register.tsx @@ -3,6 +3,7 @@ import {EXAMPLE_FUNCTION} from 'ui/registry/units/common/constants/functions'; import {getMinAutoupdateInterval} from 'ui/units/dash/containers/Dialogs/Settings/utils'; import DialogTextWidget from '../../../components/DialogTextWidget/DialogTextWidget'; +import Body from '../../../units/dash/containers/Body/Body'; import {getCaptionText} from '../../../units/dash/containers/Dialogs/Tabs/PopupWidgetsOrder/helpers'; import {getExtendedItemData} from '../../../units/dash/store/actions/helpers'; import {getDashEntryUrl, getNewDashUrl} from '../../../units/dash/utils/url'; @@ -10,6 +11,7 @@ import {registry} from '../../index'; export const registerDashPlugins = () => { registry.dash.components.registerMany({ + DashBody: Body, DialogTextWidget, }); diff --git a/src/ui/units/dash/containers/Body/Body.tsx b/src/ui/units/dash/containers/Body/Body.tsx index d6d1dac2d4..0abce3f349 100644 --- a/src/ui/units/dash/containers/Body/Body.tsx +++ b/src/ui/units/dash/containers/Body/Body.tsx @@ -147,7 +147,7 @@ const isMobileFixedHeaderEnabled = isEnabledFeature(Feature.EnableMobileFixedHea type StateProps = ReturnType; type DispatchProps = ResolveThunks; -type OwnProps = { +export type OwnProps = { isPublicMode?: boolean; hideErrorDetails?: boolean; onRetry: () => void; From f05e23e52fd1108184112b67703e3fb3b822ac01 Mon Sep 17 00:00:00 2001 From: Yury Kornilov Date: Wed, 29 Oct 2025 16:49:58 +0300 Subject: [PATCH 2/3] Converted function renderBody to component Content --- src/ui/registry/units/dash/components-map.tsx | 4 +- src/ui/registry/units/dash/register.tsx | 4 +- src/ui/units/dash/containers/Body/Body.tsx | 147 +++------------ .../Body/components/Content/Content.tsx | 170 ++++++++++++++++++ 4 files changed, 196 insertions(+), 129 deletions(-) create mode 100644 src/ui/units/dash/containers/Body/components/Content/Content.tsx diff --git a/src/ui/registry/units/dash/components-map.tsx b/src/ui/registry/units/dash/components-map.tsx index af06c3c5ac..f0df2ce37a 100644 --- a/src/ui/registry/units/dash/components-map.tsx +++ b/src/ui/registry/units/dash/components-map.tsx @@ -1,4 +1,4 @@ -import type {OwnProps as DashBodyProps} from 'ui/units/dash/containers/Body/Body'; +import type {Props as DashBodyContentProps} from 'ui/units/dash/containers/Body/components/Content/Content'; import type {DialogTextWidgetProps} from '../../../components/DialogTextWidget/DialogTextWidget'; import {makeDefaultEmpty} from '../../components/DefaultEmpty'; @@ -10,7 +10,7 @@ export const dashComponentsMap = { DashSelectState: makeDefaultEmpty(), DashSelectStateDialog: makeDefaultEmpty(), DialogTextWidget: makeDefaultEmpty(), - DashBody: makeDefaultEmpty(), + DashBodyContent: makeDefaultEmpty(), DashBodyAdditionalControls: makeDefaultEmpty(), DialogDashOtherSettingsPrepend: makeDefaultEmpty(), DashActionPanelAdditionalButtons: makeDefaultEmpty(), diff --git a/src/ui/registry/units/dash/register.tsx b/src/ui/registry/units/dash/register.tsx index bca604ca2d..f76e8766c4 100644 --- a/src/ui/registry/units/dash/register.tsx +++ b/src/ui/registry/units/dash/register.tsx @@ -3,7 +3,7 @@ import {EXAMPLE_FUNCTION} from 'ui/registry/units/common/constants/functions'; import {getMinAutoupdateInterval} from 'ui/units/dash/containers/Dialogs/Settings/utils'; import DialogTextWidget from '../../../components/DialogTextWidget/DialogTextWidget'; -import Body from '../../../units/dash/containers/Body/Body'; +import {Content} from '../../../units/dash/containers/Body/components/Content/Content'; import {getCaptionText} from '../../../units/dash/containers/Dialogs/Tabs/PopupWidgetsOrder/helpers'; import {getExtendedItemData} from '../../../units/dash/store/actions/helpers'; import {getDashEntryUrl, getNewDashUrl} from '../../../units/dash/utils/url'; @@ -11,7 +11,7 @@ import {registry} from '../../index'; export const registerDashPlugins = () => { registry.dash.components.registerMany({ - DashBody: Body, + DashBodyContent: Content, DialogTextWidget, }); diff --git a/src/ui/units/dash/containers/Body/Body.tsx b/src/ui/units/dash/containers/Body/Body.tsx index 0abce3f349..c4c30e0921 100644 --- a/src/ui/units/dash/containers/Body/Body.tsx +++ b/src/ui/units/dash/containers/Body/Body.tsx @@ -11,11 +11,7 @@ import type { PreparedCopyItemOptions, ReactGridLayoutProps, } from '@gravity-ui/dashkit'; -import { - DashKitDnDWrapper, - ActionPanel as DashkitActionPanel, - DashKit as GravityDashkit, -} from '@gravity-ui/dashkit'; +import {DashKit as GravityDashkit} from '@gravity-ui/dashkit'; import {DEFAULT_GROUP, MenuItems} from '@gravity-ui/dashkit/helpers'; import { ChevronsDown, @@ -46,15 +42,11 @@ import type {DashTab, DashTabItem, DashTabLayout} from 'shared'; import { ControlQA, DASH_INFO_HEADER, - DashBodyQa, - DashEntryQa, DashKitOverlayMenuQa, DashTabItemType, - EntryScope, FOCUSED_WIDGET_PARAM_NAME, Feature, FixedHeaderQa, - LOADED_DASH_CLASS, SCROLL_TITLE_DEBOUNCE_TIME, UPDATE_STATE_DEBOUNCE_TIME, } from 'shared'; @@ -68,18 +60,13 @@ import {WidgetContextProvider} from 'ui/components/DashKit/context/WidgetContext import {getDashKitMenu} from 'ui/components/DashKit/helpers'; import {registry} from 'ui/registry'; import {showToast} from 'ui/store/actions/toaster'; -import {selectAsideHeaderIsCompact} from 'ui/store/selectors/asideHeader'; -import {selectUserSettings} from 'ui/store/selectors/user'; import {isEmbeddedMode} from 'ui/utils/embedded'; import {isEnabledFeature} from 'ui/utils/isEnabledFeature'; -import {getIsAsideHeaderEnabled} from '../../../../components/AsideHeaderAdapter'; import {getConfiguredDashKit} from '../../../../components/DashKit/DashKit'; import {DL} from '../../../../constants'; -import Utils from '../../../../utils'; -import {TYPES_TO_DIALOGS_MAP, getActionPanelItems} from '../../../../utils/getActionPanelItems'; +import {TYPES_TO_DIALOGS_MAP} from '../../../../utils/getActionPanelItems'; import {EmptyState} from '../../components/EmptyState/EmptyState'; -import Loader from '../../components/Loader/Loader'; import {Mode} from '../../modules/constants'; import type {CopiedConfigContext, CopiedConfigData} from '../../modules/helpers'; import { @@ -88,7 +75,6 @@ import { getLayoutParentId, getPastedWidgetData, getPreparedCopyItemOptions, - memoizedGetLocalTabs, } from '../../modules/helpers'; import type {TabsHashStates} from '../../store/actions/dashTyped'; import { @@ -108,28 +94,24 @@ import { selectCurrentTab, selectCurrentTabId, selectDashDescription, - selectDashError, selectDashShowOpenedDescription, selectDashWorkbookId, selectEntryId, selectLastModifiedItemId, selectSettings, - selectShowTableOfContent, selectSkipReload, selectTabHashState, selectTabs, } from '../../store/selectors/dashTypedSelectors'; import {getCustomizedProperties} from '../../utils/dashkitProps'; import {scrollIntoView} from '../../utils/scrollUtils'; -import {DashError} from '../DashError/DashError'; import { FixedHeaderContainer, FixedHeaderControls, FixedHeaderWrapper, } from '../FixedHeader/FixedHeader'; -import TableOfContent from '../TableOfContent/TableOfContent'; -import {Tabs} from '../Tabs/Tabs'; +import {Content} from './components/Content/Content'; import { FixedContainerWrapperWithContext, FixedControlsWrapperWithContext, @@ -147,7 +129,7 @@ const isMobileFixedHeaderEnabled = isEnabledFeature(Feature.EnableMobileFixedHea type StateProps = ReturnType; type DispatchProps = ResolveThunks; -export type OwnProps = { +type OwnProps = { isPublicMode?: boolean; hideErrorDetails?: boolean; onRetry: () => void; @@ -426,7 +408,24 @@ class Body extends React.PureComponent { return (
- {this.renderBody()} + @@ -1304,104 +1303,6 @@ class Body extends React.PureComponent { }); } }; - - private renderBody() { - const { - mode, - settings, - tabs, - showTableOfContent, - isSidebarOpened, - hideErrorDetails, - onRetry, - error, - disableHashNavigation, - } = this.props; - - const {loaded, hasCopyInBuffer} = this.state; - - switch (mode) { - case Mode.Loading: - case Mode.Updating: - return ; - case Mode.Error: - return ; - } - - const localTabs = memoizedGetLocalTabs(tabs); - - const hasTableOfContentForTab = !(localTabs.length === 1 && !localTabs[0].items.length); - - const showEditActionPanel = this.isEditMode(); - - const loadedMixin = loaded ? LOADED_DASH_CLASS : undefined; - - const hideDashTitle = - settings.hideDashTitle || (DL.IS_MOBILE && !isMobileFixedHeaderEnabled); - - const content = ( -
-
1, - })} - > - -
- {!hideDashTitle && this.props.entry?.key && ( -
- {Utils.getEntryNameFromKey(this.props.entry?.key)} -
- )} - {!settings.hideTabs && } - {this.renderDashkit()} - {!this.props.onlyView && ( - item.id === DashTabItemType.Image, - userSettings: this.props.userSettings, - scope: EntryScope.Dash, - })} - className={b('edit-panel', { - 'aside-opened': isSidebarOpened, - })} - /> - )} -
-
-
- ); - - return ( - - {content} - - ); - } } const mapStateToProps = (state: DatalensGlobalState) => ({ @@ -1409,7 +1310,6 @@ const mapStateToProps = (state: DatalensGlobalState) => ({ lastModifiedItem: selectLastModifiedItemId(state), entry: state.dash.entry, mode: state.dash.mode, - showTableOfContent: selectShowTableOfContent(state), hashStates: selectTabHashState(state), settings: selectSettings(state), tabData: selectCurrentTab(state), @@ -1417,11 +1317,8 @@ const mapStateToProps = (state: DatalensGlobalState) => ({ canEdit: canEdit(state), tabs: selectTabs(state), tabId: selectCurrentTabId(state), - isSidebarOpened: !selectAsideHeaderIsCompact(state), workbookId: selectDashWorkbookId(state), - error: selectDashError(state), skipReload: selectSkipReload(state), - userSettings: selectUserSettings(state), dashDescription: selectDashDescription(state), showOpenedDescription: selectDashShowOpenedDescription(state), hasTableOfContent: hasTableOfContent(state), diff --git a/src/ui/units/dash/containers/Body/components/Content/Content.tsx b/src/ui/units/dash/containers/Body/components/Content/Content.tsx new file mode 100644 index 0000000000..59d6cd228d --- /dev/null +++ b/src/ui/units/dash/containers/Body/components/Content/Content.tsx @@ -0,0 +1,170 @@ +import React from 'react'; + +import type {ConfigLayout} from '@gravity-ui/dashkit'; +import {DashKitDnDWrapper, ActionPanel as DashkitActionPanel} from '@gravity-ui/dashkit'; +import block from 'bem-cn-lite'; +import {useDispatch, useSelector} from 'react-redux'; +import { + DashBodyQa, + DashEntryQa, + DashTabItemType, + EntryScope, + Feature, + LOADED_DASH_CLASS, +} from 'shared'; +import {selectAsideHeaderIsCompact} from 'ui/store/selectors/asideHeader'; +import {selectUserSettings} from 'ui/store/selectors/user'; +import {isEnabledFeature} from 'ui/utils/isEnabledFeature'; + +import {getIsAsideHeaderEnabled} from '../../../../../../components/AsideHeaderAdapter'; +import {DL} from '../../../../../../constants'; +import Utils from '../../../../../../utils'; +import {getActionPanelItems} from '../../../../../../utils/getActionPanelItems'; +import Loader from '../../../../components/Loader/Loader'; +import {Mode} from '../../../../modules/constants'; +import type {CopiedConfigData} from '../../../../modules/helpers'; +import {memoizedGetLocalTabs} from '../../../../modules/helpers'; +import {openDialog} from '../../../../store/actions/dialogs/actions'; +import { + selectDashError, + selectSettings, + selectShowTableOfContent, + selectTabs, +} from '../../../../store/selectors/dashTypedSelectors'; +import {DashError} from '../../../DashError/DashError'; +import TableOfContent from '../../../TableOfContent/TableOfContent'; +import {Tabs} from '../../../Tabs/Tabs'; + +const b = block('dash-body'); + +const isMobileFixedHeaderEnabled = isEnabledFeature(Feature.EnableMobileFixedHeader); + +export type Props = { + copiedData: CopiedConfigData | null; + dashEntryKey?: string; + disableHashNavigation?: boolean; + hideErrorDetails?: boolean; + isCondensed: boolean; + loaded: boolean; + mode: Mode; + showEditActionPanel: boolean; + renderDashkit: () => void; + onDragEnd: () => void; + onDragStart: () => void; + onItemClick: (itemTitle: string) => void; + onRetry: () => void; +} & ( + | {onlyView: true} + | { + onlyView?: boolean; + onPasteItem: (data: CopiedConfigData, newLayout?: ConfigLayout[]) => void; + } +); + +export const Content = ({ + copiedData, + dashEntryKey, + disableHashNavigation, + hideErrorDetails, + isCondensed, + loaded, + mode, + showEditActionPanel, + renderDashkit, + onDragEnd, + onDragStart, + onItemClick, + onRetry, + ...restProps +}: Props) => { + const dispatch = useDispatch(); + + const error = useSelector(selectDashError); + const isSidebarOpened = !useSelector(selectAsideHeaderIsCompact); + const settings = useSelector(selectSettings); + const showTableOfContent = useSelector(selectShowTableOfContent); + const tabs = useSelector(selectTabs); + const userSettings = useSelector(selectUserSettings); + + const handleOpenDialog = React.useCallback<(...args: Parameters) => void>( + (dialogType, dragOperationProps) => { + dispatch(openDialog(dialogType, dragOperationProps)); + }, + [dispatch], + ); + + switch (mode) { + case Mode.Loading: + case Mode.Updating: + return ; + case Mode.Error: + return ; + } + + const localTabs = memoizedGetLocalTabs(tabs); + + const hasTableOfContentForTab = !(localTabs.length === 1 && !localTabs[0].items.length); + + const loadedMixin = loaded ? LOADED_DASH_CLASS : undefined; + + const hideDashTitle = settings.hideDashTitle || (DL.IS_MOBILE && !isMobileFixedHeaderEnabled); + + return ( + +
+
1, + })} + > + +
+ {!hideDashTitle && dashEntryKey && ( +
+ {Utils.getEntryNameFromKey(dashEntryKey)} +
+ )} + {!settings.hideTabs && } + {renderDashkit()} + {!restProps.onlyView && ( + item.id === DashTabItemType.Image, + userSettings, + scope: EntryScope.Dash, + })} + className={b('edit-panel', { + 'aside-opened': isSidebarOpened, + })} + /> + )} +
+
+
+
+ ); +}; From 3c53d431dd16b997a80819b49a6e916c7abe868d Mon Sep 17 00:00:00 2001 From: Yury Kornilov Date: Wed, 29 Oct 2025 18:12:10 +0300 Subject: [PATCH 3/3] Make Lazy Content --- src/ui/registry/units/dash/register.tsx | 4 ++-- src/ui/units/dash/containers/Body/Body.tsx | 2 +- .../units/dash/containers/Body/components/Content/Content.tsx | 4 +++- .../dash/containers/Body/components/Content/LazyContent.tsx | 3 +++ 4 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 src/ui/units/dash/containers/Body/components/Content/LazyContent.tsx diff --git a/src/ui/registry/units/dash/register.tsx b/src/ui/registry/units/dash/register.tsx index f76e8766c4..eb4fdef421 100644 --- a/src/ui/registry/units/dash/register.tsx +++ b/src/ui/registry/units/dash/register.tsx @@ -3,7 +3,7 @@ import {EXAMPLE_FUNCTION} from 'ui/registry/units/common/constants/functions'; import {getMinAutoupdateInterval} from 'ui/units/dash/containers/Dialogs/Settings/utils'; import DialogTextWidget from '../../../components/DialogTextWidget/DialogTextWidget'; -import {Content} from '../../../units/dash/containers/Body/components/Content/Content'; +import {LazyContent} from '../../../units/dash/containers/Body/components/Content/LazyContent'; import {getCaptionText} from '../../../units/dash/containers/Dialogs/Tabs/PopupWidgetsOrder/helpers'; import {getExtendedItemData} from '../../../units/dash/store/actions/helpers'; import {getDashEntryUrl, getNewDashUrl} from '../../../units/dash/utils/url'; @@ -11,7 +11,7 @@ import {registry} from '../../index'; export const registerDashPlugins = () => { registry.dash.components.registerMany({ - DashBodyContent: Content, + DashBodyContent: LazyContent, DialogTextWidget, }); diff --git a/src/ui/units/dash/containers/Body/Body.tsx b/src/ui/units/dash/containers/Body/Body.tsx index c4c30e0921..5c184a4005 100644 --- a/src/ui/units/dash/containers/Body/Body.tsx +++ b/src/ui/units/dash/containers/Body/Body.tsx @@ -111,7 +111,7 @@ import { FixedHeaderWrapper, } from '../FixedHeader/FixedHeader'; -import {Content} from './components/Content/Content'; +import Content from './components/Content/Content'; import { FixedContainerWrapperWithContext, FixedControlsWrapperWithContext, diff --git a/src/ui/units/dash/containers/Body/components/Content/Content.tsx b/src/ui/units/dash/containers/Body/components/Content/Content.tsx index 59d6cd228d..5808804861 100644 --- a/src/ui/units/dash/containers/Body/components/Content/Content.tsx +++ b/src/ui/units/dash/containers/Body/components/Content/Content.tsx @@ -61,7 +61,7 @@ export type Props = { } ); -export const Content = ({ +const Content = ({ copiedData, dashEntryKey, disableHashNavigation, @@ -168,3 +168,5 @@ export const Content = ({ ); }; + +export default Content; diff --git a/src/ui/units/dash/containers/Body/components/Content/LazyContent.tsx b/src/ui/units/dash/containers/Body/components/Content/LazyContent.tsx new file mode 100644 index 0000000000..e1380d2f9b --- /dev/null +++ b/src/ui/units/dash/containers/Body/components/Content/LazyContent.tsx @@ -0,0 +1,3 @@ +import React from 'react'; + +export const LazyContent = React.lazy(() => import('./Content'));