From 99f5fea001bc8693570a15ef7267cdf7297bd16a Mon Sep 17 00:00:00 2001 From: JzoNg Date: Sat, 12 Oct 2024 14:39:53 +0800 Subject: [PATCH] plugin detail panel header --- .../assets/vender/plugin/box-sparkle-fill.svg | 9 +++ .../src/vender/plugin/BoxSparkleFill.json | 66 +++++++++++++++++++ .../src/vender/plugin/BoxSparkleFill.tsx | 16 +++++ .../base/icons/src/vender/plugin/index.ts | 1 + .../plugins/plugin-detail-panel/index.tsx | 54 +++++++++------ .../operation-dropdown.tsx | 61 +++++++++++++++++ web/i18n/en-US/plugin.ts | 9 +++ web/i18n/zh-Hans/plugin.ts | 9 +++ 8 files changed, 206 insertions(+), 19 deletions(-) create mode 100644 web/app/components/base/icons/assets/vender/plugin/box-sparkle-fill.svg create mode 100644 web/app/components/base/icons/src/vender/plugin/BoxSparkleFill.json create mode 100644 web/app/components/base/icons/src/vender/plugin/BoxSparkleFill.tsx create mode 100644 web/app/components/plugins/plugin-detail-panel/operation-dropdown.tsx diff --git a/web/app/components/base/icons/assets/vender/plugin/box-sparkle-fill.svg b/web/app/components/base/icons/assets/vender/plugin/box-sparkle-fill.svg new file mode 100644 index 0000000000..3ec651fd94 --- /dev/null +++ b/web/app/components/base/icons/assets/vender/plugin/box-sparkle-fill.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/web/app/components/base/icons/src/vender/plugin/BoxSparkleFill.json b/web/app/components/base/icons/src/vender/plugin/BoxSparkleFill.json new file mode 100644 index 0000000000..3733f98afd --- /dev/null +++ b/web/app/components/base/icons/src/vender/plugin/BoxSparkleFill.json @@ -0,0 +1,66 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "14", + "height": "14", + "viewBox": "0 0 14 14", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "g", + "attributes": { + "id": "Icon" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "id": "Vector", + "fill-rule": "evenodd", + "clip-rule": "evenodd", + "d": "M11.3891 2.41987C11.6635 2.58871 11.749 2.94802 11.5802 3.22239L10.3324 5.25H11.0833C11.4055 5.25 11.6667 5.51117 11.6667 5.83334V11.6667C11.6667 12.311 11.1444 12.8333 10.5 12.8333H3.50001C2.85568 12.8333 2.33334 12.311 2.33334 11.6667V5.83334C2.33334 5.51117 2.59451 5.25 2.91668 5.25H8.96252L10.5865 2.61094C10.7554 2.33657 11.1147 2.25102 11.3891 2.41987ZM5.83334 7.58334C5.51118 7.58334 5.25001 7.84449 5.25001 8.16667C5.25001 8.48884 5.51118 8.75 5.83334 8.75H8.16668C8.48885 8.75 8.75001 8.48884 8.75001 8.16667C8.75001 7.84449 8.48885 7.58334 8.16668 7.58334H5.83334Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "g", + "attributes": { + "id": "Vector_2", + "opacity": "0.5" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "d": "M6.91257 1.79347C6.96898 1.76525 7.01477 1.71948 7.043 1.66303L7.32195 1.10508C7.42946 0.890105 7.73623 0.890105 7.84374 1.10508L8.12269 1.66303C8.15093 1.71948 8.19672 1.76525 8.25313 1.79347L8.81108 2.07245C9.0261 2.17994 9.0261 2.48672 8.81108 2.5942L8.25313 2.87318C8.19672 2.9014 8.15093 2.94717 8.12269 3.00362L7.84374 3.56158C7.73623 3.77655 7.42946 3.77655 7.32195 3.56158L7.043 3.00362C7.01477 2.94717 6.96898 2.9014 6.91257 2.87318L6.35461 2.5942C6.13965 2.48672 6.13965 2.17994 6.35461 2.07245L6.91257 1.79347Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M3.80145 2.7657C3.85789 2.73748 3.90366 2.69171 3.93189 2.63526L4.11364 2.27174C4.22113 2.05677 4.5279 2.05677 4.63539 2.27174L4.81715 2.63526C4.84537 2.6917 4.89114 2.73748 4.94759 2.7657L5.3111 2.94745C5.52607 3.05494 5.52607 3.36172 5.3111 3.4692L4.94759 3.65096C4.89114 3.67919 4.84537 3.72495 4.81715 3.7814L4.63539 4.14491C4.5279 4.35988 4.22113 4.35988 4.11364 4.14491L3.93189 3.7814C3.90366 3.72495 3.85789 3.67919 3.80145 3.65096L3.43793 3.4692C3.22296 3.36172 3.22296 3.05494 3.43793 2.94745L3.80145 2.7657Z", + "fill": "currentColor" + }, + "children": [] + } + ] + } + ] + } + ] + }, + "name": "BoxSparkleFill" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/vender/plugin/BoxSparkleFill.tsx b/web/app/components/base/icons/src/vender/plugin/BoxSparkleFill.tsx new file mode 100644 index 0000000000..5b60827fe9 --- /dev/null +++ b/web/app/components/base/icons/src/vender/plugin/BoxSparkleFill.tsx @@ -0,0 +1,16 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './BoxSparkleFill.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase' + +const Icon = React.forwardRef, Omit>(( + props, + ref, +) => ) + +Icon.displayName = 'BoxSparkleFill' + +export default Icon diff --git a/web/app/components/base/icons/src/vender/plugin/index.ts b/web/app/components/base/icons/src/vender/plugin/index.ts index 6d219fce21..943c764116 100644 --- a/web/app/components/base/icons/src/vender/plugin/index.ts +++ b/web/app/components/base/icons/src/vender/plugin/index.ts @@ -1 +1,2 @@ +export { default as BoxSparkleFill } from './BoxSparkleFill' export { default as LeftCorner } from './LeftCorner' diff --git a/web/app/components/plugins/plugin-detail-panel/index.tsx b/web/app/components/plugins/plugin-detail-panel/index.tsx index 14c47f3b01..84d2afdd43 100644 --- a/web/app/components/plugins/plugin-detail-panel/index.tsx +++ b/web/app/components/plugins/plugin-detail-panel/index.tsx @@ -1,16 +1,20 @@ 'use client' -import React, { useEffect, useState } from 'react' +import React, { useEffect, useMemo, useState } from 'react' import type { FC } from 'react' +import { useTranslation } from 'react-i18next' import { usePathname, useRouter, useSearchParams } from 'next/navigation' -import { RiVerifiedBadgeLine } from '@remixicon/react' -import Badge from '../../base/badge' +import { RiCloseLine, RiVerifiedBadgeLine } from '@remixicon/react' import type { Plugin } from '../types' +import Badge from '../../base/badge' import Description from '../card/base/description' import Icon from '../card/base/card-icon' import Title from '../card/base/title' -import DownloadCount from '../card/base/download-count' +import OperationDropdown from './operation-dropdown' import type { Locale } from '@/i18n' import { fetchPluginDetail } from '@/app/(commonLayout)/plugins/test/card/actions' +import { BoxSparkleFill } from '@/app/components/base/icons/src/vender/plugin' +import Button from '@/app/components/base/button' +import ActionButton from '@/app/components/base/action-button' import Drawer from '@/app/components/base/drawer' import Loading from '@/app/components/base/loading' import cn from '@/utils/classnames' @@ -27,6 +31,7 @@ type Props = { const PluginDetailPanel: FC = ({ locale, }) => { + const { t } = useTranslation() const searchParams = useSearchParams() const pluginID = searchParams.get('pluginID') const router = useRouter() @@ -34,6 +39,12 @@ const PluginDetailPanel: FC = ({ const [loading, setLoading] = useState(true) const [pluginDetail, setPluginDetail] = useState() + const hasNewVersion = useMemo(() => { + if (!pluginDetail) + return false + return pluginDetail.latest_version !== pluginDetail.version + }, [pluginDetail]) + const getPluginDetail = async (pluginID: string) => { setLoading(true) const detail = await fetchPluginDetail(pluginID) @@ -49,6 +60,8 @@ const PluginDetailPanel: FC = ({ router.replace(pathname) } + const handleUpdate = () => {} + useEffect(() => { if (pluginID) getPluginDetail(pluginID) @@ -65,40 +78,43 @@ const PluginDetailPanel: FC = ({ footer={null} mask={false} positionCenter={false} - panelClassname={cn('mt-[65px] !w-[405px] !max-w-[405px]')} + panelClassname={cn('mt-[64px] mr-2 mb-2 !w-[420px] !max-w-[420px] !p-0 !bg-components-panel-bg rounded-2xl border-[0.5px] border-components-panel-border shadow-xl')} > {loading && } {!loading && pluginDetail && ( -
-
- {/* Header */} +
+
<RiVerifiedBadgeLine className="shrink-0 ml-0.5 w-4 h-4 text-text-accent" /> + <Badge + className='mx-1' + text={pluginDetail.version} + hasRedCornerMark={hasNewVersion} + /> + {hasNewVersion && ( + <Button variant='secondary-accent' size='small' className='!h-5' onClick={handleUpdate}>{t('plugin.detailPanel.operation.update')}</Button> + )} </div> <div className='mb-1 flex justify-between items-center h-4'> <div className='flex items-center'> <div className='text-text-tertiary system-xs-regular'>{pluginDetail.org}</div> <div className='mx-2 text-text-quaternary system-xs-regular'>·</div> - <DownloadCount downloadCount={pluginDetail.install_count || 0} /> + <BoxSparkleFill className='w-3.5 h-3.5 text-text-tertiary' /> </div> </div> </div> + <div className='flex gap-1'> + <OperationDropdown /> + <ActionButton onClick={handleClose}> + <RiCloseLine className='w-4 h-4' /> + </ActionButton> + </div> </div> <Description className='mt-3' text={pluginDetail.brief[locale]} descriptionLineRows={2}></Description> - <div className='mt-3 flex space-x-0.5'> - {['LLM', 'text embedding', 'speech2text'].map(tag => ( - <Badge key={tag} text={tag} /> - ))} - </div> </div> </div> )} diff --git a/web/app/components/plugins/plugin-detail-panel/operation-dropdown.tsx b/web/app/components/plugins/plugin-detail-panel/operation-dropdown.tsx new file mode 100644 index 0000000000..c9be924a65 --- /dev/null +++ b/web/app/components/plugins/plugin-detail-panel/operation-dropdown.tsx @@ -0,0 +1,61 @@ +'use client' +import type { FC } from 'react' +import React, { useCallback, useRef, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { RiArrowRightUpLine, RiMoreFill } from '@remixicon/react' +import ActionButton from '@/app/components/base/action-button' +// import Button from '@/app/components/base/button' +import { + PortalToFollowElem, + PortalToFollowElemContent, + PortalToFollowElemTrigger, +} from '@/app/components/base/portal-to-follow-elem' +import cn from '@/utils/classnames' + +type Props = { +} + +const OperationDropdown: FC<Props> = () => { + const { t } = useTranslation() + const [open, doSetOpen] = useState(false) + const openRef = useRef(open) + const setOpen = useCallback((v: boolean) => { + doSetOpen(v) + openRef.current = v + }, [doSetOpen]) + + const handleTrigger = useCallback(() => { + setOpen(!openRef.current) + }, [setOpen]) + + return ( + <PortalToFollowElem + open={open} + onOpenChange={setOpen} + placement='bottom-end' + offset={{ + mainAxis: -12, + crossAxis: 36, + }} + > + <PortalToFollowElemTrigger onClick={handleTrigger}> + <ActionButton className={cn(open && 'bg-state-base-hover')}> + <RiMoreFill className='w-4 h-4' /> + </ActionButton> + </PortalToFollowElemTrigger> + <PortalToFollowElemContent className='z-50'> + <div className='w-[160px] p-1 bg-components-panel-bg-blur rounded-xl border-[0.5px] border-components-panel-border shadow-lg'> + <div className='px-3 py-1.5 rounded-lg text-text-secondary system-md-regular cursor-pointer hover:bg-state-base-hover'>{t('plugin.detailPanel.operation.info')}</div> + <div className='px-3 py-1.5 rounded-lg text-text-secondary system-md-regular cursor-pointer hover:bg-state-base-hover'>{t('plugin.detailPanel.operation.checkUpdate')}</div> + <div className='flex items-center px-3 py-1.5 rounded-lg text-text-secondary system-md-regular cursor-pointer hover:bg-state-base-hover'> + <div className='grow'>{t('plugin.detailPanel.operation.viewDetail')}</div> + <RiArrowRightUpLine className='shrink-0 w-3.5 h-3.5 text-text-tertiary' /> + </div> + <div className='my-1 h-px bg-divider-subtle'></div> + <div className='px-3 py-1.5 rounded-lg text-text-secondary system-md-regular cursor-pointer hover:bg-state-base-hover'>{t('plugin.detailPanel.operation.remove')}</div> + </div> + </PortalToFollowElemContent> + </PortalToFollowElem> + ) +} +export default React.memo(OperationDropdown) diff --git a/web/i18n/en-US/plugin.ts b/web/i18n/en-US/plugin.ts index 55acb354ab..239c1fc322 100644 --- a/web/i18n/en-US/plugin.ts +++ b/web/i18n/en-US/plugin.ts @@ -1,6 +1,15 @@ const translation = { from: 'From', endpointsEnabled: '{{num}} sets of endpoints enabled', + detailPanel: { + operation: { + update: 'Update', + info: 'Plugin Info', + checkUpdate: 'Check Update', + viewDetail: 'View Detail', + remove: 'Remove', + }, + }, } export default translation diff --git a/web/i18n/zh-Hans/plugin.ts b/web/i18n/zh-Hans/plugin.ts index e540d590f6..e89c5bddef 100644 --- a/web/i18n/zh-Hans/plugin.ts +++ b/web/i18n/zh-Hans/plugin.ts @@ -1,6 +1,15 @@ const translation = { from: '来自', endpointsEnabled: '{{num}} 组端点已启用', + detailPanel: { + operation: { + update: '更新', + info: '插件信息', + checkUpdate: '检查更新', + viewDetail: '查看详情', + remove: '移除', + }, + }, } export default translation