mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2026-01-08 05:26:19 +00:00
add selection widgets to modal
This commit is contained in:
parent
79c3398b51
commit
ccc230b5a3
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2025 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 { Col, Row } from 'antd';
|
||||
import React, { forwardRef } from 'react';
|
||||
import { Document } from '../../../../generated/entity/docStore/document';
|
||||
import WidgetCard from '../WidgetCard/WidgetCard';
|
||||
import './all-widgets-content.less';
|
||||
|
||||
interface AllWidgetsContentProps {
|
||||
widgets: Document[];
|
||||
selectedWidgets: string[];
|
||||
onSelectWidget: (id: string) => void;
|
||||
}
|
||||
|
||||
const AllWidgetsContent = forwardRef<HTMLDivElement, AllWidgetsContentProps>(
|
||||
({ widgets, selectedWidgets, onSelectWidget }, ref) => {
|
||||
return (
|
||||
<Row className="all-widgets-grid" gutter={[20, 20]} ref={ref}>
|
||||
{widgets.map((widget) => (
|
||||
<Col
|
||||
data-widget-key={widget.fullyQualifiedName}
|
||||
key={widget.id}
|
||||
lg={8}
|
||||
md={12}
|
||||
sm={24}>
|
||||
<WidgetCard
|
||||
isSelected={selectedWidgets.includes(widget.id ?? '')}
|
||||
widget={widget}
|
||||
onSelect={() => onSelectWidget(widget.id ?? '')}
|
||||
/>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default AllWidgetsContent;
|
||||
@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2025 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.
|
||||
*/
|
||||
.all-widgets-grid {
|
||||
max-height: calc(100vh - 220px); // Adjust as needed
|
||||
overflow-y: auto;
|
||||
padding-right: 8px;
|
||||
}
|
||||
@ -12,11 +12,18 @@
|
||||
*/
|
||||
import Icon from '@ant-design/icons';
|
||||
import { Button, Col, Divider, Modal, Row, Typography } from 'antd';
|
||||
import React, { useState } from 'react';
|
||||
import { AxiosError } from 'axios';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ReactComponent as AddIcon } from '../../../../assets/svg/add-square.svg';
|
||||
import { PAGE_SIZE_MEDIUM } from '../../../../constants/constants';
|
||||
import { DEFAULT_HEADER_BG_COLOR } from '../../../../constants/Mydata.constants';
|
||||
import { LandingPageWidgetKeys } from '../../../../enums/CustomizablePage.enum';
|
||||
import { Document } from '../../../../generated/entity/docStore/document';
|
||||
import { getAllKnowledgePanels } from '../../../../rest/DocStoreAPI';
|
||||
import { showErrorToast } from '../../../../utils/ToastUtils';
|
||||
import HeaderTheme from '../../HeaderTheme/HeaderTheme';
|
||||
import AllWidgetsContent from '../AllWidgetsContent/AllWidgetsContent';
|
||||
import './customise-home-modal.less';
|
||||
|
||||
interface CustomiseHomeModalProps {
|
||||
@ -37,6 +44,51 @@ const CustomiseHomeModal = ({
|
||||
currentBackgroundColor ?? DEFAULT_HEADER_BG_COLOR
|
||||
);
|
||||
|
||||
const [widgets, setWidgets] = useState<Document[]>([]);
|
||||
const [selectedWidgets, setSelectedWidgets] = useState<string[]>([]);
|
||||
const [selectedKey, setSelectedKey] = useState('header-theme');
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const fetchWidgets = async () => {
|
||||
try {
|
||||
const { data } = await getAllKnowledgePanels({
|
||||
fqnPrefix: 'KnowledgePanel',
|
||||
limit: PAGE_SIZE_MEDIUM,
|
||||
});
|
||||
setWidgets(
|
||||
data.filter(
|
||||
(widget) =>
|
||||
widget.fullyQualifiedName !== LandingPageWidgetKeys.ANNOUNCEMENTS
|
||||
)
|
||||
);
|
||||
} catch (error) {
|
||||
showErrorToast(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchWidgets();
|
||||
}, []);
|
||||
|
||||
const handleSelectWidget = (id: string) => {
|
||||
setSelectedWidgets((prev) =>
|
||||
prev.includes(id) ? prev.filter((w) => w !== id) : [...prev, id]
|
||||
);
|
||||
};
|
||||
|
||||
const handleSidebarClick = (key: string) => {
|
||||
if (key === 'header-theme' || key === 'all-widgets') {
|
||||
setSelectedKey(key);
|
||||
} else {
|
||||
const target = contentRef.current?.querySelector(
|
||||
`[data-widget-key="${key}"]`
|
||||
);
|
||||
if (target) {
|
||||
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const customiseOptions = [
|
||||
{
|
||||
key: 'header-theme',
|
||||
@ -51,15 +103,57 @@ const CustomiseHomeModal = ({
|
||||
{
|
||||
key: 'all-widgets',
|
||||
label: t('label.all-widgets'),
|
||||
component: <div>{t('label.all-widgets')}</div>,
|
||||
component: (
|
||||
<AllWidgetsContent
|
||||
ref={contentRef}
|
||||
selectedWidgets={selectedWidgets}
|
||||
widgets={widgets}
|
||||
onSelectWidget={handleSelectWidget}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const [selectedKey, setSelectedKey] = useState(customiseOptions[0].key);
|
||||
const sidebarItems = [
|
||||
...customiseOptions.map(({ key, label }) => ({ key, label })),
|
||||
...widgets.map((widget) => ({
|
||||
key: widget.fullyQualifiedName,
|
||||
label: widget.name,
|
||||
})),
|
||||
];
|
||||
|
||||
const selectedComponent = customiseOptions.find(
|
||||
(item) => item.key === selectedKey
|
||||
)?.component;
|
||||
const selectedComponent = useMemo(() => {
|
||||
return customiseOptions.find((item) => item.key === selectedKey)?.component;
|
||||
}, [customiseOptions, selectedKey]);
|
||||
|
||||
const sidebarOptions = useMemo(() => {
|
||||
return (
|
||||
<>
|
||||
{sidebarItems.map((item) => {
|
||||
const isWidgetItem =
|
||||
item.key !== 'header-theme' && item.key !== 'all-widgets';
|
||||
|
||||
const isAllWidgets = item.key === 'all-widgets';
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`sidebar-option text-md font-semibold border-radius-xs cursor-pointer d-flex flex-wrap items-center
|
||||
${isWidgetItem ? 'sidebar-widget-item' : ''}
|
||||
${selectedKey === item.key ? 'active' : ''}`}
|
||||
key={item.key}
|
||||
onClick={() => handleSidebarClick(item.key)}>
|
||||
<span>{item.label}</span>
|
||||
{isAllWidgets && (
|
||||
<span className="widget-count text-xs border-radius-md m-l-sm">
|
||||
{widgets.length}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}, [sidebarItems, selectedKey, handleSidebarClick]);
|
||||
|
||||
const handleApply = () => {
|
||||
if (onBackgroundColorUpdate) {
|
||||
@ -88,17 +182,7 @@ const CustomiseHomeModal = ({
|
||||
onCancel={onClose}>
|
||||
<Row className="customise-home-modal-body d-flex gap-1">
|
||||
<Col className="sidebar p-box sticky top-0 self-start">
|
||||
{customiseOptions.map((item) => (
|
||||
<div
|
||||
className={`sidebar-option text-md font-semibold border-radius-xs cursor-pointer ${
|
||||
selectedKey === item.key ? 'active' : ''
|
||||
}`}
|
||||
data-testid={`sidebar-option-${item.key}`}
|
||||
key={item.key}
|
||||
onClick={() => setSelectedKey(item.key)}>
|
||||
{item.label}
|
||||
</div>
|
||||
))}
|
||||
{sidebarOptions}
|
||||
</Col>
|
||||
<Divider
|
||||
className="customise-home-modal-divider h-auto self-stretch"
|
||||
|
||||
@ -57,6 +57,11 @@
|
||||
color: @blue-9;
|
||||
}
|
||||
}
|
||||
.sidebar-widget-item {
|
||||
color: @grey-600;
|
||||
font-weight: @font-regular;
|
||||
padding: @padding-xs @padding-md @padding-xs @padding-lg;
|
||||
}
|
||||
}
|
||||
|
||||
.customise-home-modal-divider {
|
||||
@ -94,3 +99,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.widget-count {
|
||||
color: @blue-9;
|
||||
background-color: @alert-info-icon-bg-1;
|
||||
padding: 2px @padding-xs;
|
||||
}
|
||||
|
||||
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2025 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 { CheckOutlined } from '@ant-design/icons';
|
||||
import { Card, Typography } from 'antd';
|
||||
import React from 'react';
|
||||
import { Document } from '../../../../generated/entity/docStore/document';
|
||||
import './widget-card.less';
|
||||
|
||||
interface WidgetCardProps {
|
||||
widget: Document;
|
||||
isSelected: boolean;
|
||||
onSelect: () => void;
|
||||
}
|
||||
|
||||
const WidgetCard = ({ widget, isSelected, onSelect }: WidgetCardProps) => {
|
||||
return (
|
||||
<Card
|
||||
hoverable
|
||||
className={`widget-card ${isSelected ? 'selected' : ''}`}
|
||||
onClick={onSelect}>
|
||||
<div className="widget-card-content">
|
||||
<Typography.Text strong>{widget.name}</Typography.Text>
|
||||
{isSelected && <CheckOutlined className="check-icon" />}
|
||||
</div>
|
||||
<Typography.Paragraph className="widget-desc">
|
||||
{widget.description || 'No description available.'}
|
||||
</Typography.Paragraph>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default WidgetCard;
|
||||
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2025 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.
|
||||
*/
|
||||
.widget-card {
|
||||
border: 1px solid #e5e5e5;
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
transition: border 0.3s ease;
|
||||
position: relative;
|
||||
|
||||
&.selected {
|
||||
border: 2px solid #1890ff;
|
||||
}
|
||||
|
||||
.widget-card-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.check-icon {
|
||||
color: #1890ff;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.widget-desc {
|
||||
margin-top: 8px;
|
||||
color: #888;
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user