From 3bc0f2e74a379f1961ef5a9a4ef7580f2e127ae4 Mon Sep 17 00:00:00 2001 From: zhujiruo Date: Fri, 4 Jul 2025 18:35:36 +0800 Subject: [PATCH] refactor(app-sidebar): Reconstruct application operation button layout - Move application operation buttons to a separate component AppOperations - Optimize the display logic of operation buttons, add more buttons to adapt to different screen widths - Use the PortalToFollowElem component to implement the pop-up menu for additional operations - Use useEffect to dynamically calculate visible operations and additional operations, improving responsive design --- web/app/components/app-sidebar/app-info.tsx | 127 +++++++-------- .../components/app-sidebar/app-operations.tsx | 145 ++++++++++++++++++ 2 files changed, 200 insertions(+), 72 deletions(-) create mode 100644 web/app/components/app-sidebar/app-operations.tsx diff --git a/web/app/components/app-sidebar/app-info.tsx b/web/app/components/app-sidebar/app-info.tsx index 1767b02cd8..c28cc20df5 100644 --- a/web/app/components/app-sidebar/app-info.tsx +++ b/web/app/components/app-sidebar/app-info.tsx @@ -34,6 +34,8 @@ import ContentDialog from '@/app/components/base/content-dialog' import Button from '@/app/components/base/button' import CardView from '@/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView' import Divider from '../base/divider' +import type { Operation } from './app-operations' +import AppOperations from './app-operations' export type IAppInfoProps = { expand: boolean @@ -187,6 +189,55 @@ const AppInfo = ({ expand, onlyShowDetail = false, openState = false, onDetailEx if (!appDetail) return null + const operations = [ + { + id: 'edit', + title: t('app.editApp'), + icon: , + onClick: () => { + setOpen(false) + onDetailExpand?.(false) + setShowEditModal(true) + }, + }, + { + id: 'duplicate', + title: t('app.duplicate'), + icon: , + onClick: () => { + setOpen(false) + onDetailExpand?.(false) + setShowDuplicateModal(true) + }, + }, + { + id: 'export', + title: t('app.export'), + icon: , + onClick: exportCheck, + }, + (appDetail.mode !== 'agent-chat' && (appDetail.mode === 'advanced-chat' || appDetail.mode === 'workflow')) ? { + id: 'import', + title: t('workflow.common.importDSL'), + icon: , + onClick: () => { + setOpen(false) + onDetailExpand?.(false) + setShowImportDSLModal(true) + }, + } : undefined, + (appDetail.mode !== 'agent-chat' && (appDetail.mode === 'completion' || appDetail.mode === 'chat')) ? { + id: 'switch', + title: t('app.switch'), + icon: , + onClick: () => { + setOpen(false) + onDetailExpand?.(false) + setShowSwitchModal(true) + }, + } : undefined, + ].filter((op): op is Operation => Boolean(op)) + return (
{!onlyShowDetail && ( @@ -252,78 +303,10 @@ const AppInfo = ({ expand, onlyShowDetail = false, openState = false, onDetailEx
{appDetail.description}
)} {/* operations */} -
- - - - {appDetail.mode !== 'agent-chat' - && <> - { - (appDetail.mode === 'advanced-chat' || appDetail.mode === 'workflow') - && - } - { - (appDetail.mode === 'completion' || appDetail.mode === 'chat') - && - } - - } -
+
void +} + +const AppOperations = ({ operations, gap }: { + operations: Operation[] + gap: number +}) => { + const { t } = useTranslation() + const [visibleOpreations, setVisibleOperations] = useState([]) + const [moreOperations, setMoreOperations] = useState([]) + const [showMore, setShowMore] = useState(false) + const navRef = useRef(null) + const handleTriggerMore = useCallback(() => { + setShowMore(true) + }, [setShowMore]) + + useEffect(() => { + const moreElement = document.getElementById('more') + const navElement = document.getElementById('nav') + let width = 0 + const containerWidth = navElement?.clientWidth ?? 0 + const moreWidth = moreElement?.clientWidth ?? 0 + + if (containerWidth === 0 || moreWidth === 0) return + + const updatedEntries: Record = operations.reduce((pre, cur) => { + pre[cur.id] = false + return pre + }, {} as Record) + const childrens = Array.from(navRef.current!.children).slice(0, -1) + for (let i = 0; i < childrens.length; i++) { + const child: any = childrens[i] + const id = child.dataset.targetid + if (!id) break + const childWidth = child.clientWidth + + if (width + gap + childWidth + moreWidth <= containerWidth) { + updatedEntries[id] = true + width += gap + childWidth + } + else { + if (i === childrens.length - 1 && width + childWidth <= containerWidth) + updatedEntries[id] = true + else + updatedEntries[id] = false + break + } + } + setVisibleOperations(operations.filter(item => updatedEntries[item.id])) + setMoreOperations(operations.filter(item => !updatedEntries[item.id])) + }, [operations, gap]) + + return ( + <> + {!visibleOpreations.length && } +
+ {visibleOpreations.map(operation => + , + )} + {visibleOpreations.length < operations.length && + + + + +
+ {moreOperations.map(item =>
+ {cloneElement(item.icon, { className: 'h-4 w-4 text-text-tertiary' })} + {item.title} +
)} +
+
+
} +
+ + ) +} + +export default AppOperations