feat: add plugin components displaying in marketplace page

This commit is contained in:
Junyan Qin
2025-11-20 18:50:00 +08:00
parent 763c1a885c
commit da323817f7
9 changed files with 149 additions and 43 deletions

View File

@@ -234,7 +234,19 @@ export default function PipelineExtension({
</div>
<div className="flex gap-1 mt-1">
<PluginComponentList
components={plugin.components}
components={(() => {
const componentKindCount: Record<string, number> =
{};
for (const component of plugin.components) {
const kind = component.manifest.manifest.kind;
if (componentKindCount[kind]) {
componentKindCount[kind]++;
} else {
componentKindCount[kind] = 1;
}
}
return componentKindCount;
})()}
showComponentName={true}
showTitle={false}
useBadge={true}
@@ -401,7 +413,19 @@ export default function PipelineExtension({
</div>
<div className="flex gap-1 mt-1">
<PluginComponentList
components={plugin.components}
components={(() => {
const componentKindCount: Record<string, number> =
{};
for (const component of plugin.components) {
const kind = component.manifest.manifest.kind;
if (componentKindCount[kind]) {
componentKindCount[kind]++;
} else {
componentKindCount[kind] = 1;
}
}
return componentKindCount;
})()}
showComponentName={true}
showTitle={false}
useBadge={true}

View File

@@ -1,4 +1,3 @@
import { PluginComponent } from '@/app/infra/entities/plugin';
import { TFunction } from 'i18next';
import { Wrench, AudioWaveform, Hash } from 'lucide-react';
import { Badge } from '@/components/ui/badge';
@@ -9,31 +8,22 @@ export default function PluginComponentList({
showTitle,
useBadge,
t,
responsive = false,
}: {
components: PluginComponent[];
components: Record<string, number>;
showComponentName: boolean;
showTitle: boolean;
useBadge: boolean;
t: TFunction;
responsive?: boolean;
}) {
const componentKindCount: Record<string, number> = {};
for (const component of components) {
const kind = component.manifest.manifest.kind;
if (componentKindCount[kind]) {
componentKindCount[kind]++;
} else {
componentKindCount[kind] = 1;
}
}
const kindIconMap: Record<string, React.ReactNode> = {
Tool: <Wrench className="w-5 h-5" />,
EventListener: <AudioWaveform className="w-5 h-5" />,
Command: <Hash className="w-5 h-5" />,
};
const componentKindList = Object.keys(componentKindCount);
const componentKindList = Object.keys(components || {});
return (
<>
@@ -44,11 +34,21 @@ export default function PluginComponentList({
return (
<>
{useBadge && (
<Badge variant="outline">
<Badge
key={kind}
variant="outline"
className="flex items-center gap-1"
>
{kindIconMap[kind]}
{showComponentName &&
t('plugins.componentName.' + kind) + ' '}
{componentKindCount[kind]}
{/* 响应式显示组件名称:在中等屏幕以上显示 */}
{responsive ? (
<span className="hidden md:inline">
{t('plugins.componentName.' + kind)}
</span>
) : (
showComponentName && t('plugins.componentName.' + kind)
)}
<span className="ml-1">{components[kind]}</span>
</Badge>
)}
@@ -58,9 +58,15 @@ export default function PluginComponentList({
className="flex flex-row items-center justify-start gap-[0.2rem]"
>
{kindIconMap[kind]}
{showComponentName &&
t('plugins.componentName.' + kind) + ' '}
{componentKindCount[kind]}
{/* 响应式显示组件名称:在中等屏幕以上显示 */}
{responsive ? (
<span className="hidden md:inline">
{t('plugins.componentName.' + kind)}
</span>
) : (
showComponentName && t('plugins.componentName.' + kind)
)}
<span className="ml-1">{components[kind]}</span>
</div>
)}
</>

View File

@@ -128,7 +128,18 @@ export default function PluginCardComponent({
<div className="w-full flex flex-row items-start justify-start gap-[0.6rem]">
<PluginComponentList
components={cardVO.components}
components={(() => {
const componentKindCount: Record<string, number> = {};
for (const component of cardVO.components) {
const kind = component.manifest.manifest.kind;
if (componentKindCount[kind]) {
componentKindCount[kind]++;
} else {
componentKindCount[kind] = 1;
}
}
return componentKindCount;
})()}
showComponentName={false}
showTitle={true}
useBadge={false}

View File

@@ -160,7 +160,18 @@ export default function PluginForm({
<div className="mb-4 flex flex-row items-center justify-start gap-[0.4rem]">
<PluginComponentList
components={pluginInfo.components}
components={(() => {
const componentKindCount: Record<string, number> = {};
for (const component of pluginInfo.components) {
const kind = component.manifest.manifest.kind;
if (componentKindCount[kind]) {
componentKindCount[kind]++;
} else {
componentKindCount[kind] = 1;
}
}
return componentKindCount;
})()}
showComponentName={true}
showTitle={false}
useBadge={true}

View File

@@ -111,6 +111,7 @@ function MarketPageContent({
),
githubURL: plugin.repository,
version: plugin.latest_version,
components: plugin.components,
});
}, []);

View File

@@ -12,6 +12,7 @@ import { toast } from 'sonner';
import { PluginV4 } from '@/app/infra/entities/plugin';
import { getCloudServiceClientSync } from '@/app/infra/http';
import { extractI18nObject } from '@/i18n/I18nProvider';
import PluginComponentList from '@/app/home/plugins/components/plugin-installed/PluginComponentList';
interface PluginDetailDialogProps {
open: boolean;
@@ -104,6 +105,15 @@ export default function PluginDetailDialog({
<Download className="w-4 h-4" />
{plugin!.install_count.toLocaleString()} {t('market.downloads')}
</Badge>
{plugin!.components && Object.keys(plugin!.components).length > 0 && (
<PluginComponentList
components={plugin!.components}
showComponentName={true}
showTitle={false}
useBadge={true}
t={t}
/>
)}
{plugin!.repository && (
<button
onClick={(e) => {

View File

@@ -1,4 +1,7 @@
import { PluginMarketCardVO } from './PluginMarketCardVO';
import { useTranslation } from 'react-i18next';
import { Badge } from '@/components/ui/badge';
import { Wrench, AudioWaveform, Hash } from 'lucide-react';
export default function PluginMarketCardComponent({
cardVO,
@@ -7,18 +10,32 @@ export default function PluginMarketCardComponent({
cardVO: PluginMarketCardVO;
onPluginClick?: (author: string, pluginName: string) => void;
}) {
const { t } = useTranslation();
function handleCardClick() {
if (onPluginClick) {
onPluginClick(cardVO.author, cardVO.pluginName);
}
}
const kindIconMap: Record<string, React.ReactNode> = {
Tool: <Wrench className="w-4 h-4" />,
EventListener: <AudioWaveform className="w-4 h-4" />,
Command: <Hash className="w-4 h-4" />,
};
const componentKindNameMap: Record<string, string> = {
Tool: t('plugins.componentName.Tool'),
EventListener: t('plugins.componentName.EventListener'),
Command: t('plugins.componentName.Command'),
};
return (
<div
className="w-[100%] h-auto min-h-[8rem] sm:h-[9rem] bg-white rounded-[10px] shadow-[0px_0px_4px_0_rgba(0,0,0,0.2)] p-3 sm:p-[1rem] cursor-pointer hover:shadow-[0px_2px_8px_0_rgba(0,0,0,0.15)] transition-shadow duration-200 dark:bg-[#1f1f22]"
className="w-[100%] h-auto min-h-[8rem] sm:min-h-[9rem] bg-white rounded-[10px] shadow-[0px_0px_4px_0_rgba(0,0,0,0.2)] p-3 sm:p-[1rem] cursor-pointer hover:shadow-[0px_2px_8px_0_rgba(0,0,0,0.15)] transition-shadow duration-200 dark:bg-[#1f1f22]"
onClick={handleCardClick}
>
<div className="w-full h-full flex flex-col justify-between gap-2">
<div className="w-full h-full flex flex-col justify-between gap-3">
{/* 上部分:插件信息 */}
<div className="flex flex-row items-start justify-start gap-2 sm:gap-[1.2rem] min-h-0">
<img
@@ -60,8 +77,9 @@ export default function PluginMarketCardComponent({
</div>
</div>
{/* 下部分:下载量 */}
<div className="w-full flex flex-row items-center justify-start gap-[0.3rem] sm:gap-[0.4rem] px-0 sm:px-[0.4rem] flex-shrink-0">
{/* 下部分:下载量和组件列表 */}
<div className="w-full flex flex-row items-center justify-between gap-[0.3rem] sm:gap-[0.4rem] px-0 sm:px-[0.4rem] flex-shrink-0">
<div className="flex flex-row items-center justify-start gap-[0.3rem] sm:gap-[0.4rem]">
<svg
className="w-4 h-4 sm:w-[1.2rem] sm:h-[1.2rem] text-[#2563eb] flex-shrink-0"
xmlns="http://www.w3.org/2000/svg"
@@ -78,6 +96,27 @@ export default function PluginMarketCardComponent({
{cardVO.installCount.toLocaleString()}
</div>
</div>
{/* 组件列表 */}
{cardVO.components && Object.keys(cardVO.components).length > 0 && (
<div className="flex flex-row items-center gap-1">
{Object.entries(cardVO.components).map(([kind, count]) => (
<Badge
key={kind}
variant="outline"
className="flex items-center gap-1"
>
{kindIconMap[kind]}
{/* 响应式显示组件名称:在中等屏幕以上显示 */}
<span className="hidden md:inline">
{componentKindNameMap[kind]}
</span>
<span className="ml-1">{count}</span>
</Badge>
))}
</div>
)}
</div>
</div>
</div>
);

View File

@@ -8,6 +8,7 @@ export interface IPluginMarketCardVO {
iconURL: string;
githubURL: string;
version: string;
components?: Record<string, number>;
}
export class PluginMarketCardVO implements IPluginMarketCardVO {
@@ -20,6 +21,7 @@ export class PluginMarketCardVO implements IPluginMarketCardVO {
githubURL: string;
installCount: number;
version: string;
components?: Record<string, number>;
constructor(prop: IPluginMarketCardVO) {
this.description = prop.description;
@@ -31,5 +33,6 @@ export class PluginMarketCardVO implements IPluginMarketCardVO {
this.installCount = prop.installCount;
this.pluginId = prop.pluginId;
this.version = prop.version;
this.components = prop.components;
}
}

View File

@@ -40,6 +40,7 @@ export interface PluginV4 {
tags: string[];
install_count: number;
latest_version: string;
components: Record<string, number>;
status: PluginV4Status;
created_at: string;
updated_at: string;