chore(ui): refactored landing page layout (#13606)

This commit is contained in:
Aniket Katkar 2023-10-19 09:57:30 +05:30 committed by GitHub
parent 599d0e96ad
commit 52c316e644
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 421 additions and 782 deletions

View File

@ -50,7 +50,7 @@ import {
} from '../../../utils/EntityUtils';
import Loader from '../../Loader/Loader';
import { TaskTab } from '../../Task/TaskTab/TaskTab.component';
import '../../Widgets/FeedsWidget/feeds-widget.less';
import '../../Widgets/FeedsWidget/FeedsWidget.less';
import ActivityFeedEditor from '../ActivityFeedEditor/ActivityFeedEditor';
import ActivityFeedListV1 from '../ActivityFeedList/ActivityFeedListV1.component';
import FeedPanelBodyV1 from '../ActivityFeedPanel/FeedPanelBodyV1';

View File

@ -11,12 +11,11 @@
* limitations under the License.
*/
import { Button, Col, Modal, Row, Space, Typography } from 'antd';
import { Button, Col, Modal, Space, Typography } from 'antd';
import { AxiosError } from 'axios';
import classNames from 'classnames';
import { isEmpty, isNil, uniqBy } from 'lodash';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { Layout, Responsive, WidthProvider } from 'react-grid-layout';
import RGL, { Layout, WidthProvider } from 'react-grid-layout';
import { useTranslation } from 'react-i18next';
import { Link, useHistory, useLocation, useParams } from 'react-router-dom';
import AppState from '../../../AppState';
@ -40,7 +39,9 @@ import { Transi18next } from '../../../utils/CommonUtils';
import {
getAddWidgetHandler,
getLayoutUpdateHandler,
getLayoutWithEmptyWidgetPlaceholder,
getRemoveWidgetHandler,
getWidgetFromKey,
} from '../../../utils/CustomizableLandingPageUtils';
import customizePageClassBase from '../../../utils/CustomizePageClassBase';
import {
@ -50,12 +51,11 @@ import {
import { getDecodedFqn } from '../../../utils/StringsUtils';
import { showErrorToast } from '../../../utils/ToastUtils';
import ActivityFeedProvider from '../../ActivityFeed/ActivityFeedProvider/ActivityFeedProvider';
import RightSidebar from '../../MyData/RightSidebar/RightSidebar.component';
import PageLayoutV1 from '../../containers/PageLayoutV1';
import AddWidgetModal from '../AddWidgetModal/AddWidgetModal';
import EmptyWidgetPlaceholder from '../EmptyWidgetPlaceholder/EmptyWidgetPlaceholder';
import { CustomizeMyDataProps } from './CustomizeMyData.interface';
const ResponsiveGridLayout = WidthProvider(Responsive);
const ReactGridLayout = WidthProvider(RGL);
function CustomizeMyData({
initialPageData,
@ -67,20 +67,15 @@ function CustomizeMyData({
const history = useHistory();
const { fqn: personaFQN } = useParams<{ fqn: string; pageFqn: PageType }>();
const location = useLocation();
const [resetRightPanelLayout, setResetRightPanelLayout] =
useState<boolean>(false);
const [layout, setLayout] = useState<Array<WidgetConfig>>([
...(initialPageData.data?.page?.layout ??
customizePageClassBase.landingPageDefaultLayout),
{
h: 2,
i: LandingPageWidgetKeys.EMPTY_WIDGET_PLACEHOLDER,
w: 3,
x: 0,
y: 100,
isDraggable: false,
},
]);
const [layout, setLayout] = useState<Array<WidgetConfig>>(
getLayoutWithEmptyWidgetPlaceholder(
initialPageData.data?.page?.layout ??
customizePageClassBase.defaultLayout,
2,
4
)
);
const [placeholderWidgetKey, setPlaceholderWidgetKey] = useState<string>(
LandingPageWidgetKeys.EMPTY_WIDGET_PLACEHOLDER
);
@ -108,19 +103,11 @@ function CustomizeMyData({
setPlaceholderWidgetKey(value);
}, []);
const handleResetRightPanelLayout = useCallback((value: boolean) => {
setResetRightPanelLayout(value);
}, []);
const handleLayoutChange = useCallback((newLayout: Array<WidgetConfig>) => {
setLayout(newLayout);
}, []);
const handleRemoveWidget = useCallback((widgetKey: string) => {
setLayout(getRemoveWidgetHandler(widgetKey, 3, 3.5));
}, []);
const handleAddWidget = useCallback(
const handleMainPanelAddWidget = useCallback(
(
newWidgetData: Document,
placeholderWidgetKey: string,
@ -136,7 +123,7 @@ function CustomizeMyData({
);
setIsWidgetModalOpen(false);
},
[layout]
[]
);
const handleLayoutUpdate = useCallback(
@ -189,71 +176,6 @@ function CustomizeMyData({
}
};
const getWidgetFromKey = useCallback(
(widgetConfig: WidgetConfig) => {
if (widgetConfig.i.endsWith('.EmptyWidgetPlaceholder')) {
return (
<EmptyWidgetPlaceholder
handleOpenAddWidgetModal={handleOpenAddWidgetModal}
handlePlaceholderWidgetKey={handlePlaceholderWidgetKey}
handleRemoveWidget={handleRemoveWidget}
isEditable={widgetConfig.isDraggable}
widgetKey={widgetConfig.i}
/>
);
}
if (widgetConfig.i.startsWith(LandingPageWidgetKeys.RIGHT_PANEL)) {
return (
<div className="h-full border-left p-l-md">
<RightSidebar
isEditView
announcements={announcements}
followedData={followedData ?? []}
followedDataCount={followedDataCount}
handleResetLayout={handleResetRightPanelLayout}
handleSaveCurrentPageLayout={handleSaveCurrentPageLayout}
isAnnouncementLoading={isAnnouncementLoading}
isLoadingOwnedData={isLoadingOwnedData}
layoutConfigData={widgetConfig.data}
parentLayoutData={layout}
resetLayout={resetRightPanelLayout}
updateParentLayout={handleLayoutChange}
/>
</div>
);
}
const Widget = customizePageClassBase.getWidgetsFromKey(widgetConfig.i);
return (
<Widget
isEditView
announcements={announcements}
followedData={followedData ?? []}
followedDataCount={followedDataCount}
handleRemoveWidget={handleRemoveWidget}
isLoadingOwnedData={isLoadingOwnedData}
selectedGridSize={widgetConfig.w}
widgetKey={widgetConfig.i}
/>
);
},
[
handleOpenAddWidgetModal,
handleRemoveWidget,
followedData,
followedDataCount,
isLoadingOwnedData,
layout,
handleLayoutChange,
isAnnouncementLoading,
announcements,
resetRightPanelLayout,
handleResetRightPanelLayout,
handlePlaceholderWidgetKey,
]
);
const addedWidgetsList = useMemo(
() =>
layout
@ -265,16 +187,32 @@ function CustomizeMyData({
const widgets = useMemo(
() =>
layout.map((widget) => (
<div
className={classNames({
'mt--1': widget.i === LandingPageWidgetKeys.RIGHT_PANEL,
<div data-grid={widget} id={widget.i} key={widget.i}>
{getWidgetFromKey({
announcements: announcements,
followedData: followedData ?? [],
followedDataCount: followedDataCount,
isLoadingOwnedData: isLoadingOwnedData,
widgetConfig: widget,
handleOpenAddWidgetModal: handleOpenAddWidgetModal,
handlePlaceholderWidgetKey: handlePlaceholderWidgetKey,
handleRemoveWidget: handleRemoveWidget,
isEditView: true,
isAnnouncementLoading: isAnnouncementLoading,
})}
data-grid={widget}
key={widget.i}>
{getWidgetFromKey(widget)}
</div>
)),
[layout, getWidgetFromKey]
[
layout,
announcements,
followedData,
followedDataCount,
isLoadingOwnedData,
handleOpenAddWidgetModal,
handlePlaceholderWidgetKey,
handleRemoveWidget,
isAnnouncementLoading,
]
);
const fetchAnnouncements = useCallback(async () => {
@ -299,10 +237,11 @@ function CustomizeMyData({
...initialPageData,
data: {
page: {
...initialPageData.data.page,
layout: uniqBy(
layout.filter(
(widget) => !widget.i.endsWith('.EmptyWidgetPlaceholder')
(widget) =>
widget.i.startsWith('KnowledgePanel') &&
!widget.i.endsWith('.EmptyWidgetPlaceholder')
),
'i'
),
@ -321,18 +260,17 @@ function CustomizeMyData({
}, []);
const handleReset = useCallback(() => {
setLayout([
...customizePageClassBase.landingPageDefaultLayout,
{
h: 2,
i: LandingPageWidgetKeys.EMPTY_WIDGET_PLACEHOLDER,
w: 3,
x: 0,
y: 100,
isDraggable: false,
const newMainPanelLayout = customizePageClassBase.defaultLayout;
setLayout(newMainPanelLayout);
handlePageDataChange({
...initialPageData,
data: {
page: {
layout: uniqBy(newMainPanelLayout, 'i'),
},
},
]);
setResetRightPanelLayout(true);
});
handleSaveCurrentPageLayout(true);
setIsResetModalOpen(false);
}, []);
@ -347,83 +285,86 @@ function CustomizeMyData({
}, [AppState.userDetails, AppState.users, isAuthDisabled]);
return (
<Row>
<Col
className="bg-white d-flex justify-between border-bottom p-sm"
span={24}>
<div className="d-flex gap-2 items-center">
<Typography.Title className="m-0" level={5}>
<Transi18next
i18nKey="message.customize-landing-page-header"
renderElement={
<Link
style={{ color: '#1890ff', fontSize: '16px' }}
to={getPersonaDetailsPath(decodedPersonaFQN)}
<ActivityFeedProvider>
<PageLayoutV1
header={
<Col
className="bg-white d-flex justify-between border-bottom p-sm"
span={24}>
<div className="d-flex gap-2 items-center">
<Typography.Title className="m-0" level={5}>
<Transi18next
i18nKey="message.customize-landing-page-header"
renderElement={
<Link
style={{ color: '#1890ff', fontSize: '16px' }}
to={getPersonaDetailsPath(decodedPersonaFQN)}
/>
}
values={{
persona: decodedPersonaFQN,
}}
/>
}
values={{
persona: decodedPersonaFQN,
}}
/>
</Typography.Title>
</div>
<Space>
<Button size="small" onClick={handleCancel}>
{t('label.cancel')}
</Button>
<Button size="small" onClick={handleOpenResetModal}>
{t('label.reset')}
</Button>
<Button size="small" type="primary" onClick={onSaveLayout}>
{t('label.save')}
</Button>
</Space>
</Col>
<Col span={24}>
<ActivityFeedProvider>
<ResponsiveGridLayout
autoSize
breakpoints={{ lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }}
className="grid-container"
cols={{ lg: 4, md: 4, sm: 4, xs: 4, xxs: 4 }}
draggableHandle=".drag-widget-icon"
isResizable={false}
margin={[
customizePageClassBase.landingPageWidgetMargin,
customizePageClassBase.landingPageWidgetMargin,
]}
rowHeight={customizePageClassBase.landingPageRowHeight}
style={{
backgroundImage: `url(${gridBgImg})`,
}}
onLayoutChange={handleLayoutUpdate}>
{widgets}
</ResponsiveGridLayout>
{isWidgetModalOpen && (
<AddWidgetModal
addedWidgetsList={addedWidgetsList}
handleAddWidget={handleAddWidget}
handleCloseAddWidgetModal={handleCloseAddWidgetModal}
maxGridSizeSupport={customizePageClassBase.landingPageMaxGridSize}
open={isWidgetModalOpen}
placeholderWidgetKey={placeholderWidgetKey}
/>
)}
{isResetModalOpen && (
<Modal
centered
cancelText={t('label.no')}
okText={t('label.yes')}
open={isResetModalOpen}
title={t('label.reset-default-layout')}
onCancel={handleCloseResetModal}
onOk={handleReset}>
{t('message.reset-layout-confirmation')}
</Modal>
)}
</ActivityFeedProvider>
</Col>
</Row>
</Typography.Title>
</div>
<Space>
<Button size="small" onClick={handleCancel}>
{t('label.cancel')}
</Button>
<Button size="small" onClick={handleOpenResetModal}>
{t('label.reset')}
</Button>
<Button size="small" type="primary" onClick={onSaveLayout}>
{t('label.save')}
</Button>
</Space>
</Col>
}
headerClassName="m-0 p-0"
mainContainerClassName="p-t-0"
pageContainerStyle={{
backgroundImage: `url(${gridBgImg})`,
}}
pageTitle={t('label.customize-entity', {
entity: t('label.landing-page'),
})}>
<ReactGridLayout
className="grid-container"
cols={4}
draggableHandle=".drag-widget-icon"
isResizable={false}
margin={[
customizePageClassBase.landingPageWidgetMargin,
customizePageClassBase.landingPageWidgetMargin,
]}
rowHeight={customizePageClassBase.landingPageRowHeight}
onLayoutChange={handleLayoutUpdate}>
{widgets}
</ReactGridLayout>
</PageLayoutV1>
{isWidgetModalOpen && (
<AddWidgetModal
addedWidgetsList={addedWidgetsList}
handleAddWidget={handleMainPanelAddWidget}
handleCloseAddWidgetModal={handleCloseAddWidgetModal}
maxGridSizeSupport={customizePageClassBase.landingPageMaxGridSize}
open={isWidgetModalOpen}
placeholderWidgetKey={placeholderWidgetKey}
/>
)}
{isResetModalOpen && (
<Modal
centered
cancelText={t('label.no')}
okText={t('label.yes')}
open={isResetModalOpen}
title={t('label.reset-default-layout')}
onCancel={handleCloseResetModal}
onOk={handleReset}>
{t('message.reset-layout-confirmation')}
</Modal>
)}
</ActivityFeedProvider>
);
}

View File

@ -55,7 +55,7 @@ const MyDataWidgetInternal = ({
includeData.includes(data.type as AssetsType)
);
setData(includedOwnsData.slice(0, 9));
setData(includedOwnsData.slice(0, 8));
setTotalOwnedAssetsCount(includedOwnsData.length);
}
} catch (err) {

View File

@ -10,8 +10,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.announcement-container-list {
.feed-card-body {
padding: 0;
.announcement-container {
.ant-card-body {
height: 100%;
}
.announcement-container-list {
overflow-y: auto;
overflow-x: hidden;
max-height: 90%;
.feed-card-body {
padding: 0;
}
}
}

View File

@ -11,7 +11,7 @@
* limitations under the License.
*/
import { CloseOutlined, DragOutlined } from '@ant-design/icons';
import { Alert, Col, Row, Space, Typography } from 'antd';
import { Alert, Card, Col, Row, Space, Typography } from 'antd';
import { isEmpty, isUndefined } from 'lodash';
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
@ -20,8 +20,11 @@ import { Thread } from '../../../generated/entity/feed/thread';
import { WidgetCommonProps } from '../../../pages/CustomizablePage/CustomizablePage.interface';
import FeedCardBodyV1 from '../../ActivityFeed/ActivityFeedCard/FeedCardBody/FeedCardBodyV1';
import FeedCardHeaderV1 from '../../ActivityFeed/ActivityFeedCard/FeedCardHeader/FeedCardHeaderV1';
import Loader from '../../Loader/Loader';
import './AnnouncementsWidget.less';
export interface AnnouncementsWidgetProps extends WidgetCommonProps {
isAnnouncementLoading?: boolean;
announcements: Thread[];
}
@ -30,6 +33,7 @@ function AnnouncementsWidget({
isEditView,
handleRemoveWidget,
widgetKey,
isAnnouncementLoading = false,
}: Readonly<AnnouncementsWidgetProps>) {
const { t } = useTranslation();
@ -38,7 +42,7 @@ function AnnouncementsWidget({
}, [widgetKey]);
return (
<div className="bg-white h-full h-max-full overflow-y-scroll no-scrollbar">
<Card className="announcement-container card-widget h-full">
<Row justify="space-between">
<Col>
<Typography.Paragraph className="right-panel-label m-b-sm">
@ -57,52 +61,59 @@ function AnnouncementsWidget({
</Col>
)}
</Row>
<div className="announcement-container-list">
{isEmpty(announcements) && (
<Typography.Text className="text-xs">
{t('message.no-entity-data-available', {
entity: t('label.announcement-lowercase'),
{isAnnouncementLoading ? (
<Loader size="small" />
) : (
<div className="announcement-container-list">
{isEmpty(announcements) && (
<Typography.Text className="text-xs">
{t('message.no-entity-data-available', {
entity: t('label.announcement-lowercase'),
})}
</Typography.Text>
)}
<Row gutter={[8, 8]}>
{announcements.map((item) => {
return (
<Col key={item.id} span={24}>
<Alert
className="right-panel-announcement"
description={
<>
<FeedCardHeaderV1
about={item.about}
className="d-inline"
createdBy={item.createdBy}
showUserAvatar={false}
timeStamp={item.threadTs}
/>
<FeedCardBodyV1
isOpenInDrawer
announcement={item.announcement}
className="p-t-xs"
isEditPost={false}
message={item.message}
showSchedule={false}
/>
</>
}
message={
<div className="d-flex announcement-alert-heading">
<AnnouncementIcon width={20} />
<span className="text-sm p-l-xss">
{t('label.announcement')}
</span>
</div>
}
type="info"
/>
</Col>
);
})}
</Typography.Text>
)}
{announcements.map((item) => {
return (
<Alert
className="m-b-xs right-panel-announcement"
description={
<>
<FeedCardHeaderV1
about={item.about}
className="d-inline"
createdBy={item.createdBy}
showUserAvatar={false}
timeStamp={item.threadTs}
/>
<FeedCardBodyV1
isOpenInDrawer
announcement={item.announcement}
className="p-t-xs"
isEditPost={false}
message={item.message}
showSchedule={false}
/>
</>
}
key={item.id}
message={
<div className="d-flex announcement-alert-heading">
<AnnouncementIcon width={20} />
<span className="text-sm p-l-xss">
{t('label.announcement')}
</span>
</div>
}
type="info"
/>
);
})}
</div>
</div>
</Row>
</div>
)}
</Card>
);
}

View File

@ -11,7 +11,7 @@
* limitations under the License.
*/
import { CloseOutlined, DragOutlined } from '@ant-design/icons';
import { Space } from 'antd';
import { Card, Space } from 'antd';
import { isUndefined } from 'lodash';
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
@ -44,7 +44,7 @@ function FollowingWidget({
}, [widgetKey]);
return (
<div className="bg-white h-full" data-testid="following-data-container">
<Card className="card-widget h-full" data-testid="following-data-container">
<EntityListWithV1
entityList={followedData}
headerText={
@ -78,7 +78,7 @@ function FollowingWidget({
noDataPlaceholder={t('message.not-followed-anything')}
testIDText="following"
/>
</div>
</Card>
);
}

View File

@ -1,262 +0,0 @@
/*
* Copyright 2023 Collate.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { isEmpty, isUndefined, uniqBy } from 'lodash';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { Layout, Responsive, WidthProvider } from 'react-grid-layout';
import { SIZE } from '../../../enums/common.enum';
import { LandingPageWidgetKeys } from '../../../enums/CustomizablePage.enum';
import { Document } from '../../../generated/entity/docStore/document';
import { WidgetConfig } from '../../../pages/CustomizablePage/CustomizablePage.interface';
import {
getAddWidgetHandler,
getLayoutUpdateHandler,
getRemoveWidgetHandler,
} from '../../../utils/CustomizableLandingPageUtils';
import customizePageClassBase from '../../../utils/CustomizePageClassBase';
import AddWidgetModal from '../../CustomizableComponents/AddWidgetModal/AddWidgetModal';
import EmptyWidgetPlaceholder from '../../CustomizableComponents/EmptyWidgetPlaceholder/EmptyWidgetPlaceholder';
import './right-sidebar.less';
import { RightSidebarProps } from './RightSidebar.interface';
const ResponsiveGridLayout = WidthProvider(Responsive);
const RightSidebar = ({
announcements,
isAnnouncementLoading,
parentLayoutData,
isEditView = false,
followedData,
followedDataCount,
isLoadingOwnedData,
layoutConfigData,
updateParentLayout,
resetLayout = false,
handleResetLayout,
handleSaveCurrentPageLayout,
}: RightSidebarProps) => {
const [layout, setLayout] = useState<Array<WidgetConfig>>([
...(layoutConfigData?.page?.layout ?? []),
...(isEditView
? [
{
h: 2.3,
i: LandingPageWidgetKeys.EMPTY_WIDGET_PLACEHOLDER,
w: 1,
x: 0,
y: 100,
isDraggable: false,
},
]
: []),
]);
const [placeholderWidgetKey, setPlaceholderWidgetKey] = useState<string>(
LandingPageWidgetKeys.EMPTY_WIDGET_PLACEHOLDER
);
const [isWidgetModalOpen, setIsWidgetModalOpen] = useState<boolean>(false);
const handlePlaceholderWidgetKey = useCallback((value: string) => {
setPlaceholderWidgetKey(value);
}, []);
const handleOpenAddWidgetModal = useCallback(() => {
setIsWidgetModalOpen(true);
}, []);
const handleCloseAddWidgetModal = useCallback(() => {
setIsWidgetModalOpen(false);
}, []);
const handleRemoveWidget = useCallback((widgetKey: string) => {
setLayout(getRemoveWidgetHandler(widgetKey, 2.3, 2.5));
}, []);
const handleAddWidget = useCallback(
(
newWidgetData: Document,
placeholderWidgetKey: string,
widgetSize: number
) => {
setLayout(
getAddWidgetHandler(
newWidgetData,
placeholderWidgetKey,
widgetSize,
customizePageClassBase.landingPageRightContainerMaxGridSize
)
);
setIsWidgetModalOpen(false);
},
[layout]
);
const getWidgetFromKey = useCallback(
(widgetConfig: WidgetConfig) => {
if (widgetConfig.i.endsWith('.EmptyWidgetPlaceholder')) {
return (
<div className="h-full">
<EmptyWidgetPlaceholder
handleOpenAddWidgetModal={handleOpenAddWidgetModal}
handlePlaceholderWidgetKey={handlePlaceholderWidgetKey}
handleRemoveWidget={handleRemoveWidget}
iconHeight={SIZE.SMALL}
iconWidth={SIZE.SMALL}
isEditable={widgetConfig.isDraggable}
widgetKey={widgetConfig.i}
/>
</div>
);
}
const Widget = customizePageClassBase.getWidgetsFromKey(widgetConfig.i);
return (
<Widget
announcements={announcements}
followedData={followedData ?? []}
followedDataCount={followedDataCount}
handleRemoveWidget={handleRemoveWidget}
isEditView={isEditView}
isLoadingOwnedData={isLoadingOwnedData}
selectedGridSize={widgetConfig.w}
widgetKey={widgetConfig.i}
/>
);
},
[
announcements,
followedData,
followedDataCount,
isLoadingOwnedData,
isEditView,
handleRemoveWidget,
handleOpenAddWidgetModal,
handlePlaceholderWidgetKey,
]
);
const widgets = useMemo(
() =>
layout
.filter((widget: WidgetConfig) =>
!isAnnouncementLoading &&
widget.i.startsWith(LandingPageWidgetKeys.ANNOUNCEMENTS) &&
!isEditView
? !isEmpty(announcements)
: true
)
.map((widget: WidgetConfig) => (
<div data-grid={widget} key={widget.i}>
{getWidgetFromKey(widget)}
</div>
)),
[layout, announcements, getWidgetFromKey, isEditView, isAnnouncementLoading]
);
const handleLayoutUpdate = useCallback(
(updatedLayout: Layout[]) => {
if (!isEmpty(layout) && !isEmpty(updatedLayout)) {
setLayout(getLayoutUpdateHandler(updatedLayout));
}
},
[layout]
);
const addedWidgetsList = useMemo(
() =>
layout
.filter((widget) => widget.i.startsWith('KnowledgePanel'))
.map((widget) => widget.i),
[layout]
);
useEffect(() => {
if (isEditView && !isUndefined(updateParentLayout)) {
updateParentLayout(
(parentLayoutData ?? []).map((widget) => {
if (widget.i === LandingPageWidgetKeys.RIGHT_PANEL) {
return {
...widget,
data: {
page: {
layout: uniqBy(
layout.filter(
(widget) => !widget.i.endsWith('.EmptyWidgetPlaceholder')
),
'i'
),
},
},
};
} else {
return widget;
}
})
);
}
}, [layout]);
useEffect(() => {
if (resetLayout && handleResetLayout && handleSaveCurrentPageLayout) {
setLayout([
...customizePageClassBase.rightPanelDefaultLayout,
...(isEditView
? [
{
h: 2.3,
i: LandingPageWidgetKeys.EMPTY_WIDGET_PLACEHOLDER,
w: 1,
x: 0,
y: 100,
isDraggable: false,
},
]
: []),
]);
handleResetLayout(false);
handleSaveCurrentPageLayout(true);
}
}, [resetLayout]);
return (
<>
<ResponsiveGridLayout
breakpoints={{ lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }}
cols={{ lg: 1, md: 1, sm: 1, xs: 1, xxs: 1 }}
containerPadding={[0, customizePageClassBase.landingPageWidgetMargin]}
draggableHandle=".drag-widget-icon"
isResizable={false}
margin={[
customizePageClassBase.landingPageWidgetMargin,
customizePageClassBase.landingPageWidgetMargin,
]}
rowHeight={customizePageClassBase.landingPageRowHeight}
onLayoutChange={handleLayoutUpdate}>
{widgets}
</ResponsiveGridLayout>
{isWidgetModalOpen && (
<AddWidgetModal
addedWidgetsList={addedWidgetsList}
handleAddWidget={handleAddWidget}
handleCloseAddWidgetModal={handleCloseAddWidgetModal}
maxGridSizeSupport={
customizePageClassBase.landingPageRightContainerMaxGridSize
}
open={isWidgetModalOpen}
placeholderWidgetKey={placeholderWidgetKey}
/>
)}
</>
);
};
export default RightSidebar;

View File

@ -1,31 +0,0 @@
/*
* Copyright 2023 Collate.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Thread } from '../../../generated/entity/feed/thread';
import { EntityReference } from '../../../generated/entity/type';
import { WidgetConfig } from '../../../pages/CustomizablePage/CustomizablePage.interface';
export interface RightSidebarProps {
isEditView?: boolean;
resetLayout?: boolean;
handleResetLayout?: (value: boolean) => void;
announcements: Thread[];
isAnnouncementLoading: boolean;
followedDataCount: number;
followedData: Array<EntityReference>;
isLoadingOwnedData: boolean;
layoutConfigData?: WidgetConfig['data'];
parentLayoutData?: Array<WidgetConfig>;
updateParentLayout?: (newLayout: Array<WidgetConfig>) => void;
handleSaveCurrentPageLayout?: (value: boolean) => void;
}

View File

@ -33,7 +33,7 @@ import { getCountBadge, getEntityDetailLink } from '../../../utils/CommonUtils';
import { showErrorToast } from '../../../utils/ToastUtils';
import { useAuthContext } from '../../authentication/auth-provider/AuthProvider';
import FeedsFilterPopover from '../../common/FeedsFilterPopover/FeedsFilterPopover.component';
import './feeds-widget.less';
import './FeedsWidget.less';
const FeedsWidget = ({
isEditView = false,

View File

@ -11,7 +11,7 @@
* limitations under the License.
*/
@import url('../../styles/variables.less');
@import url('../../../styles/variables.less');
.right-panel-heading {
margin-bottom: 8px !important;

View File

@ -12,18 +12,21 @@
*/
import { CloseOutlined, DragOutlined } from '@ant-design/icons';
import { Button, Col, Row, Space, Typography } from 'antd';
import { Button, Card, Col, Row, Space, Typography } from 'antd';
import { isUndefined } from 'lodash';
import React, { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import EntityListSkeleton from '../../components/Skeleton/MyData/EntityListSkeleton/EntityListSkeleton.component';
import { EntityReference } from '../../generated/type/entityReference';
import { WidgetCommonProps } from '../../pages/CustomizablePage/CustomizablePage.interface';
import { getRecentlyViewedData, prepareLabel } from '../../utils/CommonUtils';
import { getEntityName } from '../../utils/EntityUtils';
import { getEntityIcon, getEntityLink } from '../../utils/TableUtils';
import './recently-viewed.less';
import { EntityReference } from '../../../generated/type/entityReference';
import { WidgetCommonProps } from '../../../pages/CustomizablePage/CustomizablePage.interface';
import {
getRecentlyViewedData,
prepareLabel,
} from '../../../utils/CommonUtils';
import { getEntityName } from '../../../utils/EntityUtils';
import { getEntityIcon, getEntityLink } from '../../../utils/TableUtils';
import EntityListSkeleton from '../../Skeleton/MyData/EntityListSkeleton/EntityListSkeleton.component';
import './RecentlyViewed.less';
const RecentlyViewed = ({
isEditView,
@ -63,7 +66,9 @@ const RecentlyViewed = ({
}, []);
return (
<div className="bg-white h-full" data-testid="recently-viewed-container">
<Card
className="card-widget h-full"
data-testid="recently-viewed-container">
<EntityListSkeleton
dataLength={data.length !== 0 ? data.length : 5}
loading={Boolean(isLoading)}>
@ -127,7 +132,7 @@ const RecentlyViewed = ({
</div>
</>
</EntityListSkeleton>
</div>
</Card>
);
};

View File

@ -30,6 +30,9 @@ interface PageLayoutProp extends HTMLAttributes<HTMLDivElement> {
rightPanel?: ReactNode;
center?: boolean;
pageTitle: string;
headerClassName?: string;
mainContainerClassName?: string;
pageContainerStyle?: React.CSSProperties;
rightPanelWidth?: number;
leftPanelWidth?: number;
}
@ -53,6 +56,9 @@ const PageLayoutV1: FC<PageLayoutProp> = ({
center = false,
leftPanelWidth = 230,
rightPanelWidth = 284,
headerClassName = '',
mainContainerClassName = '',
pageContainerStyle = {},
}: PageLayoutProp) => {
const contentWidth = useMemo(() => {
if (leftPanel && rightPanel) {
@ -71,17 +77,20 @@ const PageLayoutV1: FC<PageLayoutProp> = ({
<DocumentTitle title={pageTitle} />
{header && (
<div
className={classNames({
'header-center': center,
'm-t-md p-x-md': !center,
})}>
className={classNames(
{
'header-center': center,
'm-t-md p-x-md': !center,
},
headerClassName
)}>
{header}
</div>
)}
<Row
className={classNames(className, 'bg-white')}
data-testid="page-layout-v1"
style={pageContainerStyles}>
style={{ ...pageContainerStyles, ...pageContainerStyle }}>
{leftPanel && (
<Col
className="page-layout-leftpanel"
@ -95,7 +104,8 @@ const PageLayoutV1: FC<PageLayoutProp> = ({
'page-layout-v1-center p-t-sm page-layout-v1-vertical-scroll',
{
'flex justify-center': center,
}
},
mainContainerClassName
)}
flex={contentWidth}
offset={center ? 3 : 0}

View File

@ -165,7 +165,9 @@ export const PAGE_HEADERS = {
subHeader: i18n.t('message.tools-to-improve-workflow'),
},
CUSTOM_PAGE: {
header: i18n.t('label.customize-landing-page'),
header: i18n.t('label.customize-entity', {
entity: i18n.t('label.landing-page'),
}),
subHeader: i18n.t('message.page-sub-header-for-customize-landing-page'),
},
};

View File

@ -19,7 +19,6 @@ export enum WidgetWidths {
export enum LandingPageWidgetKeys {
ACTIVITY_FEED = 'KnowledgePanel.ActivityFeed',
RIGHT_PANEL = 'Container.RightSidebar',
ANNOUNCEMENTS = 'KnowledgePanel.Announcements',
FOLLOWING = 'KnowledgePanel.Following',
RECENTLY_VIEWED = 'KnowledgePanel.RecentlyViewed',

View File

@ -209,7 +209,7 @@
"custom-property-plural": "Benutzerdefinierte Eigenschaften",
"custom-range": "Benutzerdefinierter Bereich",
"customise": "Customise",
"customize-landing-page": "Customize Landing Page",
"customize-entity": "Customize {{entity}}",
"dag": "DAG",
"dag-view": "DAG-Ansicht",
"daily-active-users-on-the-platform": "Täglich aktive Benutzer auf der Plattform",
@ -536,6 +536,7 @@
"kpi-title": "Schlüsselindikatoren (KPI)",
"kpi-uppercase": "KPI",
"kpi-uppercase-plural": "KPIs",
"landing-page": "Landing Page",
"language": "Sprache",
"last": "Letzte",
"last-error": "Letzter Fehler",

View File

@ -209,7 +209,7 @@
"custom-property-plural": "Custom Properties",
"custom-range": "Custom Range",
"customise": "Customise",
"customize-landing-page": "Customize Landing Page",
"customize-entity": "Customize {{entity}}",
"dag": "Dag",
"dag-view": "DAG view",
"daily-active-users-on-the-platform": "Daily Active Users on the Platform",
@ -536,6 +536,7 @@
"kpi-title": "Key Performance Indicators (KPI)",
"kpi-uppercase": "KPI",
"kpi-uppercase-plural": "KPIs",
"landing-page": "Landing Page",
"language": "Language",
"last": "Last",
"last-error": "Last error",

View File

@ -209,7 +209,7 @@
"custom-property-plural": "Propiedades personalizadas",
"custom-range": "Custom Range",
"customise": "Customise",
"customize-landing-page": "Customize Landing Page",
"customize-entity": "Customize {{entity}}",
"dag": "DAG",
"dag-view": "Vista DAG",
"daily-active-users-on-the-platform": "Usuarios activos diarios en la plataforma",
@ -536,6 +536,7 @@
"kpi-title": "Indicadores clave de rendimiento (KPI)",
"kpi-uppercase": "KPI",
"kpi-uppercase-plural": "KPIs",
"landing-page": "Landing Page",
"language": "Idioma",
"last": "Último",
"last-error": "Último error",

View File

@ -209,7 +209,7 @@
"custom-property-plural": "Propriétés Personnalisées",
"custom-range": "Plage Personnalisée",
"customise": "Customise",
"customize-landing-page": "Customize Landing Page",
"customize-entity": "Customize {{entity}}",
"dag": "DAG",
"dag-view": "Vue DAG",
"daily-active-users-on-the-platform": "Utilisateurs Actifs Quotidiens sur la Plateforme",
@ -536,6 +536,7 @@
"kpi-title": "Indicateurs de Performance Clés (KPI)",
"kpi-uppercase": "KPI",
"kpi-uppercase-plural": "KPIs",
"landing-page": "Landing Page",
"language": "Langage",
"last": "Dernier·ère",
"last-error": "Dernière erreur",

View File

@ -209,7 +209,7 @@
"custom-property-plural": "カスタムプロパティ",
"custom-range": "Custom Range",
"customise": "Customise",
"customize-landing-page": "Customize Landing Page",
"customize-entity": "Customize {{entity}}",
"dag": "Dag",
"dag-view": "DAGビュー",
"daily-active-users-on-the-platform": "このプラットフォームのアクティブなユーザー",
@ -536,6 +536,7 @@
"kpi-title": "Key Performance Indicators (KPI)",
"kpi-uppercase": "KPI",
"kpi-uppercase-plural": "KPIs",
"landing-page": "Landing Page",
"language": "言語",
"last": "最新",
"last-error": "最新のエラー",

View File

@ -209,7 +209,7 @@
"custom-property-plural": "Propriedades customizadas",
"custom-range": "Custom Range",
"customise": "Customise",
"customize-landing-page": "Customize Landing Page",
"customize-entity": "Customize {{entity}}",
"dag": "DAG",
"dag-view": "Visão da DAG",
"daily-active-users-on-the-platform": "Usuários ativos diariamente na plataforma",
@ -536,6 +536,7 @@
"kpi-title": "Título do KPI",
"kpi-uppercase": "KPI",
"kpi-uppercase-plural": "KPIs",
"landing-page": "Landing Page",
"language": "Idioma",
"last": "Último",
"last-error": "Último erro",

View File

@ -209,7 +209,7 @@
"custom-property-plural": "Пользовательские свойства",
"custom-range": "Пользовательский диапазон",
"customise": "Customise",
"customize-landing-page": "Customize Landing Page",
"customize-entity": "Customize {{entity}}",
"dag": "Dag",
"dag-view": "Просмотр DAG",
"daily-active-users-on-the-platform": "Количество активных пользователей на платформе",
@ -536,6 +536,7 @@
"kpi-title": "Ключевой показатель эффективности (KPI)",
"kpi-uppercase": "KPI",
"kpi-uppercase-plural": "KPIs",
"landing-page": "Landing Page",
"language": "Язык",
"last": "Последний",
"last-error": "Последняя ошибка",

View File

@ -209,7 +209,7 @@
"custom-property-plural": "自定义属性",
"custom-range": "自定义范围",
"customise": "Customise",
"customize-landing-page": "Customize Landing Page",
"customize-entity": "Customize {{entity}}",
"dag": "DAG",
"dag-view": "DAG 视图",
"daily-active-users-on-the-platform": "平台上的每日活跃用户",
@ -536,6 +536,7 @@
"kpi-title": "关键绩效指标 (KPI)",
"kpi-uppercase": "KPI",
"kpi-uppercase-plural": "KPIs",
"landing-page": "Landing Page",
"language": "语言",
"last": "最近",
"last-error": "最近错误",

View File

@ -152,7 +152,9 @@ export const CustomPageSettings = () => {
size="small"
type="text"
onClick={() => handleCustomisePersona(persona)}>
{t('label.customize-landing-page')}
{t('label.customize-entity', {
entity: t('label.landing-page'),
})}
</Button>
}
title={getEntityName(persona)}>

View File

@ -26,7 +26,6 @@ import {
getDocumentByFQN,
updateDocument,
} from '../../rest/DocStoreAPI';
import { getFinalLandingPage } from '../../utils/CustomizableLandingPageUtils';
import customizePageClassBase from '../../utils/CustomizePageClassBase';
import { getDecodedFqn } from '../../utils/StringsUtils';
import { showErrorToast, showSuccessToast } from '../../utils/ToastUtils';
@ -59,27 +58,19 @@ export const CustomizablePage = () => {
try {
setIsLoading(true);
const pageData = await getDocumentByFQN(pageLayoutFQN);
const finalPageData = getFinalLandingPage(pageData, true);
setPage(finalPageData);
setEditedPage(finalPageData);
setPage(pageData);
setEditedPage(pageData);
} catch (error) {
if ((error as AxiosError).response?.status === ClientErrors.NOT_FOUND) {
setPage(
getFinalLandingPage(
{
name: `${decodedPersonaFQN}${decodedPageFQN}`,
fullyQualifiedName: getDecodedFqn(pageLayoutFQN),
entityType: EntityType.PAGE,
data: {
page: {
layout: customizePageClassBase.landingPageDefaultLayout,
},
},
},
true
)
);
setPage({
name: `${decodedPersonaFQN}${decodedPageFQN}`,
fullyQualifiedName: getDecodedFqn(pageLayoutFQN),
entityType: EntityType.PAGE,
data: {
page: { layout: customizePageClassBase.defaultLayout },
},
});
}
} finally {
setIsLoading(false);
@ -88,15 +79,14 @@ export const CustomizablePage = () => {
const handleSave = async () => {
try {
const finalPage = getFinalLandingPage(editedPage);
let response: Document;
if (page.id) {
const jsonPatch = compare(page, finalPage);
const jsonPatch = compare(page, editedPage);
response = await updateDocument(page.id ?? '', jsonPatch);
} else {
response = await createDocument(finalPage);
response = await createDocument(editedPage);
}
setPage(response);
setEditedPage(response);

View File

@ -12,7 +12,6 @@
*/
import { AxiosError } from 'axios';
import classNames from 'classnames';
import { isEmpty, isNil } from 'lodash';
import React, {
useCallback,
@ -22,16 +21,16 @@ import React, {
useState,
} from 'react';
import RGL, { WidthProvider } from 'react-grid-layout';
import { useTranslation } from 'react-i18next';
import { useLocation } from 'react-router-dom';
import AppState from '../../AppState';
import ActivityFeedProvider from '../../components/ActivityFeed/ActivityFeedProvider/ActivityFeedProvider';
import { useApplicationConfigContext } from '../../components/ApplicationConfigProvider/ApplicationConfigProvider';
import { useAuthContext } from '../../components/authentication/auth-provider/AuthProvider';
import PageLayoutV1 from '../../components/containers/PageLayoutV1';
import Loader from '../../components/Loader/Loader';
import RightSidebar from '../../components/MyData/RightSidebar/RightSidebar.component';
import WelcomeScreen from '../../components/WelcomeScreen/WelcomeScreen.component';
import { LOGGED_IN_USER_STORAGE_KEY } from '../../constants/constants';
import { LandingPageWidgetKeys } from '../../enums/CustomizablePage.enum';
import { AssetsType, EntityType } from '../../enums/entity.enum';
import { Thread } from '../../generated/entity/feed/thread';
import { PageType } from '../../generated/system/ui/page';
@ -40,6 +39,7 @@ import { useAuth } from '../../hooks/authHooks';
import { getDocumentByFQN } from '../../rest/DocStoreAPI';
import { getActiveAnnouncement } from '../../rest/feedsAPI';
import { getUserById } from '../../rest/userAPI';
import { getWidgetFromKey } from '../../utils/CustomizableLandingPageUtils';
import customizePageClassBase from '../../utils/CustomizePageClassBase';
import { showErrorToast } from '../../utils/ToastUtils';
import { WidgetConfig } from '../CustomizablePage/CustomizablePage.interface';
@ -49,6 +49,7 @@ const ReactGridLayout = WidthProvider(RGL);
const MyDataPageV1 = () => {
const location = useLocation();
const { t } = useTranslation();
const { isAuthDisabled } = useAuth(location.pathname);
const { currentUser } = useAuthContext();
const { selectedPersona } = useApplicationConfigContext();
@ -82,10 +83,10 @@ const MyDataPageV1 = () => {
const pageData = await getDocumentByFQN(pageFQN);
setLayout(pageData.data.page.layout);
} else {
setLayout(customizePageClassBase.landingPageDefaultLayout);
setLayout(customizePageClassBase.defaultLayout);
}
} catch {
setLayout(customizePageClassBase.landingPageDefaultLayout);
setLayout(customizePageClassBase.defaultLayout);
} finally {
setIsLoading(false);
}
@ -144,76 +145,33 @@ const MyDataPageV1 = () => {
!isEmpty(AppState.userDetails)) &&
isNil(followedData)
) {
fetchMyData().catch(() => {
// ignore since error is displayed in toast in the parent promise.
// Added block for sonar code smell
});
fetchMyData();
}
}, [AppState.userDetails, AppState.users, isAuthDisabled]);
const getWidgetFromKey = useCallback(
(widgetConfig: WidgetConfig) => {
if (widgetConfig.i.startsWith(LandingPageWidgetKeys.RIGHT_PANEL)) {
return (
<div className="h-full border-left p-l-md">
<RightSidebar
announcements={announcements}
followedData={followedData ?? []}
followedDataCount={followedDataCount}
isAnnouncementLoading={isAnnouncementLoading}
isLoadingOwnedData={isLoadingOwnedData}
layoutConfigData={widgetConfig.data}
parentLayoutData={layout}
/>
</div>
);
}
const Widget = customizePageClassBase.getWidgetsFromKey(widgetConfig.i);
return (
<Widget
announcements={announcements}
followedData={followedData ?? []}
followedDataCount={followedDataCount}
isLoadingOwnedData={isLoadingOwnedData}
selectedGridSize={widgetConfig.w}
widgetKey={widgetConfig.i}
/>
);
},
const widgets = useMemo(
() =>
layout.map((widget) => (
<div data-grid={widget} key={widget.i}>
{getWidgetFromKey({
announcements: announcements,
followedData: followedData ?? [],
followedDataCount: followedDataCount,
isLoadingOwnedData: isLoadingOwnedData,
widgetConfig: widget,
})}
</div>
)),
[
layout,
isAnnouncementLoading,
announcements,
followedData,
followedDataCount,
isLoadingOwnedData,
layout,
announcements,
isAnnouncementLoading,
]
);
const widgets = useMemo(
() =>
layout
.filter((widget: WidgetConfig) =>
!isAnnouncementLoading &&
widget.i.startsWith(LandingPageWidgetKeys.ANNOUNCEMENTS)
? !isEmpty(announcements)
: true
)
.map((widget) => (
<div
className={classNames({
'mt--1': widget.i === LandingPageWidgetKeys.RIGHT_PANEL,
})}
data-grid={widget}
key={widget.i}>
{getWidgetFromKey(widget)}
</div>
)),
[layout, getWidgetFromKey]
);
const fetchAnnouncements = useCallback(async () => {
try {
setIsAnnouncementLoading(true);
@ -240,8 +198,10 @@ const MyDataPageV1 = () => {
}
return (
<div className="bg-white h-full">
<ActivityFeedProvider>
<ActivityFeedProvider>
<PageLayoutV1
mainContainerClassName="p-t-0"
pageTitle={t('label.my-data')}>
{isLoading ? (
<Loader />
) : (
@ -258,8 +218,8 @@ const MyDataPageV1 = () => {
{widgets}
</ReactGridLayout>
)}
</ActivityFeedProvider>
</div>
</PageLayoutV1>
</ActivityFeedProvider>
);
};

View File

@ -14,7 +14,6 @@
@import url('../../styles/variables.less');
.page-layout-rightpanel {
background-color: @white;
border: 1px solid @border-color;
border-radius: 0;
padding-left: 0 !important;

View File

@ -333,7 +333,7 @@ export const addToRecentViewed = (eData: RecentlyViewedData): void => {
.sort(arraySorterByKey<RecentlyViewedData>('timestamp', true));
arrData.unshift(entityData);
if (arrData.length > 5) {
if (arrData.length > 8) {
arrData.pop();
}
recentlyViewed.data = arrData;

View File

@ -11,21 +11,18 @@
* limitations under the License.
*/
import {
floor,
isEmpty,
isUndefined,
max,
maxBy,
round,
uniqueId,
} from 'lodash';
import { isEmpty, isUndefined, max, uniqueId } from 'lodash';
import React from 'react';
import { Layout } from 'react-grid-layout';
import EmptyWidgetPlaceholder from '../components/CustomizableComponents/EmptyWidgetPlaceholder/EmptyWidgetPlaceholder';
import { SIZE } from '../enums/common.enum';
import {
LandingPageWidgetKeys,
WidgetWidths,
} from '../enums/CustomizablePage.enum';
import { Document } from '../generated/entity/docStore/document';
import { Thread } from '../generated/entity/feed/thread';
import { EntityReference } from '../generated/entity/type';
import { WidgetConfig } from '../pages/CustomizablePage/CustomizablePage.interface';
import customizePageClassBase from './CustomizePageClassBase';
@ -161,58 +158,81 @@ const getAllWidgetsArray = (layout: WidgetConfig[]) => {
return widgetsArray;
};
const getLayoutWithCalculatedRightPanelHeight = (
layout: WidgetConfig[],
increaseHeight?: boolean
) => {
const allWidgets = getAllWidgetsArray(layout);
const maxHeightsArray = allWidgets.map((widget) => {
const widgetHeightAndPos = widget.h + widget.y;
const floorHeightAndPosValue = floor(widgetHeightAndPos);
export const getWidgetFromKey = ({
widgetConfig,
handleOpenAddWidgetModal,
handlePlaceholderWidgetKey,
handleRemoveWidget,
announcements,
followedDataCount,
followedData,
isLoadingOwnedData,
iconHeight,
iconWidth,
isEditView,
isAnnouncementLoading,
}: {
widgetConfig: WidgetConfig;
handleOpenAddWidgetModal?: () => void;
handlePlaceholderWidgetKey?: (key: string) => void;
handleRemoveWidget?: (key: string) => void;
announcements: Thread[];
followedData?: EntityReference[];
followedDataCount: number;
isLoadingOwnedData: boolean;
iconHeight?: SIZE;
iconWidth?: SIZE;
isEditView?: boolean;
isAnnouncementLoading?: boolean;
}) => {
if (
widgetConfig.i.endsWith('.EmptyWidgetPlaceholder') &&
!isUndefined(handleOpenAddWidgetModal) &&
!isUndefined(handlePlaceholderWidgetKey) &&
!isUndefined(handleRemoveWidget)
) {
return (
<EmptyWidgetPlaceholder
handleOpenAddWidgetModal={handleOpenAddWidgetModal}
handlePlaceholderWidgetKey={handlePlaceholderWidgetKey}
handleRemoveWidget={handleRemoveWidget}
iconHeight={iconHeight}
iconWidth={iconWidth}
isEditable={widgetConfig.isDraggable}
widgetKey={widgetConfig.i}
/>
);
}
const heightOfWidget =
widgetHeightAndPos * customizePageClassBase.landingPageRowHeight +
(floorHeightAndPosValue + 1) *
customizePageClassBase.landingPageWidgetMargin;
const Widget = customizePageClassBase.getWidgetsFromKey(widgetConfig.i);
return {
h: round(
(heightOfWidget + customizePageClassBase.landingPageWidgetMargin) /
(customizePageClassBase.landingPageRowHeight +
customizePageClassBase.landingPageWidgetMargin),
2
),
height: heightOfWidget,
};
});
const maxHeight = maxBy(maxHeightsArray, 'height');
return layout.map((widget) =>
widget.i === LandingPageWidgetKeys.RIGHT_PANEL
? {
...widget,
h: increaseHeight
? customizePageClassBase.landingPageRightContainerEditHeight
: maxHeight?.h ?? widget.h,
}
: widget
return (
<Widget
announcements={announcements}
followedData={followedData ?? []}
followedDataCount={followedDataCount}
handleRemoveWidget={handleRemoveWidget}
isAnnouncementLoading={isAnnouncementLoading}
isEditView={isEditView}
isLoadingOwnedData={isLoadingOwnedData}
selectedGridSize={widgetConfig.w}
widgetKey={widgetConfig.i}
/>
);
};
export const getFinalLandingPage = (
page: Document,
increaseHeight?: boolean
): Document => {
return {
...page,
data: {
page: {
layout: getLayoutWithCalculatedRightPanelHeight(
page.data.page?.layout ?? [],
increaseHeight
),
},
},
};
};
export const getLayoutWithEmptyWidgetPlaceholder = (
layout: WidgetConfig[],
emptyWidgetHeight = 2,
emptyWidgetWidth = 1
) => [
...layout,
{
h: emptyWidgetHeight,
i: LandingPageWidgetKeys.EMPTY_WIDGET_PLACEHOLDER,
w: emptyWidgetWidth,
x: 0,
y: 100,
isDraggable: false,
},
];

View File

@ -29,9 +29,9 @@ import AnnouncementsWidget, {
import FollowingWidget, {
FollowingWidgetProps,
} from '../components/MyData/RightSidebar/FollowingWidget';
import RecentlyViewed from '../components/recently-viewed/RecentlyViewed';
import TotalDataAssetsWidget from '../components/TotalDataAssetsWidget/TotalDataAssetsWidget.component';
import FeedsWidget from '../components/Widgets/FeedsWidget/FeedsWidget.component';
import RecentlyViewed from '../components/Widgets/RecentlyViewed/RecentlyViewed';
import {
LandingPageWidgetKeys,
WidgetWidths,
@ -45,49 +45,19 @@ class CustomizePageClassBase {
defaultWidgetHeight = 3;
landingPageWidgetMargin = 16;
landingPageRowHeight = 100;
landingPageRightContainerEditHeight = 16;
landingPageMaxGridSize = 3;
landingPageRightContainerMaxGridSize = 1;
landingPageWidgetDefaultHeights: Record<string, number> = {
activityFeed: 5,
rightSidebar: 11.5,
announcements: 3.1,
following: 2.4,
recentlyViewed: 2.1,
myData: 3.1,
kpi: 3.1,
totalAssets: 3.1,
activityFeed: 6,
announcements: 3,
following: 3,
recentlyViewed: 3,
myData: 3,
kpi: 3,
totalAssets: 3,
};
rightPanelDefaultLayout: Array<WidgetConfig> = [
{
h: this.landingPageWidgetDefaultHeights.announcements,
i: LandingPageWidgetKeys.ANNOUNCEMENTS,
w: 1,
x: 0,
y: 0,
static: false,
},
{
h: this.landingPageWidgetDefaultHeights.following,
i: LandingPageWidgetKeys.FOLLOWING,
w: 1,
x: 0,
y: 1.5,
static: false,
},
{
h: this.landingPageWidgetDefaultHeights.recentlyViewed,
i: LandingPageWidgetKeys.RECENTLY_VIEWED,
w: 1,
x: 0,
y: 3,
static: false,
},
];
landingPageDefaultLayout: Array<WidgetConfig> = [
defaultLayout: Array<WidgetConfig> = [
{
h: this.landingPageWidgetDefaultHeights.activityFeed,
i: LandingPageWidgetKeys.ACTIVITY_FEED,
@ -96,19 +66,6 @@ class CustomizePageClassBase {
y: 0,
static: false,
},
{
h: this.landingPageWidgetDefaultHeights.rightSidebar,
i: LandingPageWidgetKeys.RIGHT_PANEL,
w: 1,
x: 3,
y: 0,
data: {
page: {
layout: this.rightPanelDefaultLayout,
},
},
static: true,
},
{
h: this.landingPageWidgetDefaultHeights.myData,
i: LandingPageWidgetKeys.MY_DATA,
@ -130,17 +87,37 @@ class CustomizePageClassBase {
i: LandingPageWidgetKeys.TOTAL_DATA_ASSETS,
w: 3,
x: 0,
y: 9.1,
y: 9,
static: false,
},
{
h: this.landingPageWidgetDefaultHeights.announcements,
i: LandingPageWidgetKeys.ANNOUNCEMENTS,
w: 1,
x: 3,
y: 0,
static: false,
},
{
h: this.landingPageWidgetDefaultHeights.following,
i: LandingPageWidgetKeys.FOLLOWING,
w: 1,
x: 3,
y: 1.5,
static: false,
},
{
h: this.landingPageWidgetDefaultHeights.recentlyViewed,
i: LandingPageWidgetKeys.RECENTLY_VIEWED,
w: 1,
x: 3,
y: 3,
static: false,
},
];
protected updateRightPanelDefaultLayout(layout: Array<WidgetConfig>) {
this.rightPanelDefaultLayout = layout;
}
protected updateLandingPageDefaultLayout(layout: Array<WidgetConfig>) {
this.landingPageDefaultLayout = layout;
protected updateDefaultLayoutLayout(layout: Array<WidgetConfig>) {
this.defaultLayout = layout;
}
protected updateLandingPageWidgetDefaultHeights(obj: Record<string, number>) {
@ -231,8 +208,6 @@ class CustomizePageClassBase {
switch (widgetName) {
case 'ActivityFeed':
return this.landingPageWidgetDefaultHeights.activityFeed;
case 'RightSidebar':
return this.landingPageWidgetDefaultHeights.rightSidebar;
case 'Announcements':
return this.landingPageWidgetDefaultHeights.announcements;
case 'Following':

View File

@ -254,7 +254,9 @@ export const getGlobalSettingsMenuWithPermission = (
key: 'openMetadata',
items: [
{
label: i18next.t('label.customize-landing-page'),
label: i18next.t('label.customize-entity', {
entity: i18next.t('label.landing-page'),
}),
isProtected: Boolean(isAdminUser),
key: 'openMetadata.customizeLandingPage',
icon: <CustomDashboardLogoIcon className="w-4 side-panel-icons" />,