mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-27 07:54:19 +00:00
fix: lint code to build success
This commit is contained in:
@@ -1,14 +1,22 @@
|
||||
'use client';
|
||||
import PluginInstalledComponent, { PluginInstalledComponentRef } from '@/app/home/plugins/plugin-installed/PluginInstalledComponent';
|
||||
import PluginInstalledComponent, {
|
||||
PluginInstalledComponentRef,
|
||||
} from '@/app/home/plugins/plugin-installed/PluginInstalledComponent';
|
||||
import PluginMarketComponent from '@/app/home/plugins/plugin-market/PluginMarketComponent';
|
||||
import styles from './plugins.module.css';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { GithubIcon } from "lucide-react";
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { PlusIcon } from 'lucide-react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogFooter,
|
||||
} from '@/components/ui/dialog';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { GithubIcon } from 'lucide-react';
|
||||
import { useState, useRef } from 'react';
|
||||
import { httpClient } from '@/app/infra/http/HttpClient';
|
||||
|
||||
enum PluginInstallStatus {
|
||||
@@ -18,9 +26,9 @@ enum PluginInstallStatus {
|
||||
}
|
||||
|
||||
export default function PluginConfigPage() {
|
||||
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [pluginInstallStatus, setPluginInstallStatus] = useState<PluginInstallStatus>(PluginInstallStatus.WAIT_INPUT);
|
||||
const [pluginInstallStatus, setPluginInstallStatus] =
|
||||
useState<PluginInstallStatus>(PluginInstallStatus.WAIT_INPUT);
|
||||
const [installError, setInstallError] = useState<string | null>(null);
|
||||
const [githubURL, setGithubURL] = useState('');
|
||||
const pluginInstalledRef = useRef<PluginInstalledComponentRef>(null);
|
||||
@@ -44,7 +52,8 @@ export default function PluginConfigPage() {
|
||||
if (resp.runtime.exception) {
|
||||
setInstallError(resp.runtime.exception);
|
||||
setPluginInstallStatus(PluginInstallStatus.ERROR);
|
||||
} else { // success
|
||||
} else {
|
||||
// success
|
||||
setGithubURL('');
|
||||
setModalOpen(false);
|
||||
pluginInstalledRef.current?.refreshPluginList();
|
||||
@@ -52,7 +61,6 @@ export default function PluginConfigPage() {
|
||||
}
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log('error when install plugin:', err);
|
||||
@@ -64,20 +72,27 @@ export default function PluginConfigPage() {
|
||||
return (
|
||||
<div className={styles.pageContainer}>
|
||||
<Tabs defaultValue="installed" className="w-full">
|
||||
<div className='flex flex-row justify-between items-center'>
|
||||
<TabsList className='shadow-md py-5 bg-[#f0f0f0]'>
|
||||
<TabsTrigger value="installed" className="px-6 py-4 cursor-pointer">已安装</TabsTrigger>
|
||||
<TabsTrigger value="market" className="px-6 py-4 cursor-pointer">插件市场</TabsTrigger>
|
||||
|
||||
<div className="flex flex-row justify-between items-center">
|
||||
<TabsList className="shadow-md py-5 bg-[#f0f0f0]">
|
||||
<TabsTrigger value="installed" className="px-6 py-4 cursor-pointer">
|
||||
已安装
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="market" className="px-6 py-4 cursor-pointer">
|
||||
插件市场
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<div className='flex flex-row justify-end items-center'>
|
||||
<Button variant="default" className='px-6 py-4 cursor-pointer' onClick={() => {
|
||||
setModalOpen(true);
|
||||
setPluginInstallStatus(PluginInstallStatus.WAIT_INPUT);
|
||||
setInstallError(null);
|
||||
}}>
|
||||
<PlusIcon className='w-4 h-4' />
|
||||
<div className="flex flex-row justify-end items-center">
|
||||
<Button
|
||||
variant="default"
|
||||
className="px-6 py-4 cursor-pointer"
|
||||
onClick={() => {
|
||||
setModalOpen(true);
|
||||
setPluginInstallStatus(PluginInstallStatus.WAIT_INPUT);
|
||||
setInstallError(null);
|
||||
}}
|
||||
>
|
||||
<PlusIcon className="w-4 h-4" />
|
||||
安装
|
||||
</Button>
|
||||
</div>
|
||||
@@ -86,16 +101,17 @@ export default function PluginConfigPage() {
|
||||
<PluginInstalledComponent ref={pluginInstalledRef} />
|
||||
</TabsContent>
|
||||
<TabsContent value="market">
|
||||
<PluginMarketComponent askInstallPlugin={(githubURL) => {
|
||||
setGithubURL(githubURL);
|
||||
setModalOpen(true);
|
||||
setPluginInstallStatus(PluginInstallStatus.WAIT_INPUT);
|
||||
setInstallError(null);
|
||||
}} />
|
||||
<PluginMarketComponent
|
||||
askInstallPlugin={(githubURL) => {
|
||||
setGithubURL(githubURL);
|
||||
setModalOpen(true);
|
||||
setPluginInstallStatus(PluginInstallStatus.WAIT_INPUT);
|
||||
setInstallError(null);
|
||||
}}
|
||||
/>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
|
||||
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
|
||||
<DialogContent className="w-[500px] p-6">
|
||||
<DialogHeader>
|
||||
@@ -105,14 +121,14 @@ export default function PluginConfigPage() {
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
{pluginInstallStatus === PluginInstallStatus.WAIT_INPUT && (
|
||||
<div className="mt-4">
|
||||
<p className="mb-2">目前仅支持从 GitHub 安装</p>
|
||||
<Input
|
||||
placeholder="请输入插件的Github链接"
|
||||
value={githubURL}
|
||||
onChange={(e) => setGithubURL(e.target.value)}
|
||||
className="mb-4"
|
||||
/>
|
||||
<div className="mt-4">
|
||||
<p className="mb-2">目前仅支持从 GitHub 安装</p>
|
||||
<Input
|
||||
placeholder="请输入插件的Github链接"
|
||||
value={githubURL}
|
||||
onChange={(e) => setGithubURL(e.target.value)}
|
||||
className="mb-4"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{pluginInstallStatus === PluginInstallStatus.INSTALLING && (
|
||||
@@ -129,12 +145,16 @@ export default function PluginConfigPage() {
|
||||
<DialogFooter>
|
||||
{pluginInstallStatus === PluginInstallStatus.WAIT_INPUT && (
|
||||
<>
|
||||
<Button variant="outline" onClick={() => setModalOpen(false)}>取消</Button>
|
||||
<Button variant="outline" onClick={() => setModalOpen(false)}>
|
||||
取消
|
||||
</Button>
|
||||
<Button onClick={handleModalConfirm}>确认</Button>
|
||||
</>
|
||||
)}
|
||||
{pluginInstallStatus === PluginInstallStatus.ERROR && (
|
||||
<Button variant="default" onClick={() => setModalOpen(false)}>关闭</Button>
|
||||
<Button variant="default" onClick={() => setModalOpen(false)}>
|
||||
关闭
|
||||
</Button>
|
||||
)}
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
@@ -1,105 +1,108 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect, forwardRef, useImperativeHandle } from 'react';
|
||||
import CreateCardComponent from '@/app/infra/basic-component/create-card-component/CreateCardComponent';
|
||||
import { PluginCardVO } from '@/app/home/plugins/plugin-installed/PluginCardVO';
|
||||
import PluginCardComponent from '@/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent';
|
||||
import PluginForm from '@/app/home/plugins/plugin-installed/plugin-form/PluginForm';
|
||||
import styles from '@/app/home/plugins/plugins.module.css';
|
||||
import { GithubIcon } from 'lucide-react';
|
||||
import { httpClient } from '@/app/infra/http/HttpClient';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogFooter,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
} from '@/components/ui/dialog';
|
||||
|
||||
export interface PluginInstalledComponentRef {
|
||||
refreshPluginList: () => void;
|
||||
}
|
||||
|
||||
const PluginInstalledComponent = forwardRef<PluginInstalledComponentRef>((props, ref) => {
|
||||
const [pluginList, setPluginList] = useState<PluginCardVO[]>([]);
|
||||
const [modalOpen, setModalOpen] = useState<boolean>(false);
|
||||
const [selectedPlugin, setSelectedPlugin] = useState<PluginCardVO | null>(null);
|
||||
// eslint-disable-next-line react/display-name
|
||||
const PluginInstalledComponent = forwardRef<PluginInstalledComponentRef>(
|
||||
(props, ref) => {
|
||||
const [pluginList, setPluginList] = useState<PluginCardVO[]>([]);
|
||||
const [modalOpen, setModalOpen] = useState<boolean>(false);
|
||||
const [selectedPlugin, setSelectedPlugin] = useState<PluginCardVO | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
initData();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
initData();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
function initData() {
|
||||
getPluginList();
|
||||
}
|
||||
function initData() {
|
||||
getPluginList();
|
||||
}
|
||||
|
||||
function getPluginList() {
|
||||
httpClient.getPlugins().then((value) => {
|
||||
setPluginList(
|
||||
value.plugins.map((plugin) => {
|
||||
return new PluginCardVO({
|
||||
author: plugin.author,
|
||||
description: plugin.description.zh_CN,
|
||||
enabled: plugin.enabled,
|
||||
name: plugin.name,
|
||||
version: plugin.version,
|
||||
status: plugin.status,
|
||||
tools: plugin.tools,
|
||||
event_handlers: plugin.event_handlers,
|
||||
repository: plugin.repository,
|
||||
priority: plugin.priority,
|
||||
});
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
refreshPluginList: getPluginList
|
||||
}));
|
||||
|
||||
function handlePluginClick(plugin: PluginCardVO) {
|
||||
setSelectedPlugin(plugin);
|
||||
setModalOpen(true);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`${styles.pluginListContainer}`}>
|
||||
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
|
||||
<DialogContent className="w-[700px] max-h-[80vh] p-0 flex flex-col">
|
||||
<DialogHeader className="px-6 pt-6 pb-2">
|
||||
<DialogTitle>插件配置</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="flex-1 overflow-y-auto px-6">
|
||||
{selectedPlugin && (
|
||||
<PluginForm
|
||||
pluginAuthor={selectedPlugin.author}
|
||||
pluginName={selectedPlugin.name}
|
||||
onFormSubmit={() => {
|
||||
setModalOpen(false);
|
||||
getPluginList();
|
||||
}}
|
||||
onFormCancel={() => {
|
||||
setModalOpen(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{pluginList.map((vo, index) => {
|
||||
return (
|
||||
<div key={index}>
|
||||
<PluginCardComponent cardVO={vo} onCardClick={() => handlePluginClick(vo)} />
|
||||
</div>
|
||||
function getPluginList() {
|
||||
httpClient.getPlugins().then((value) => {
|
||||
setPluginList(
|
||||
value.plugins.map((plugin) => {
|
||||
return new PluginCardVO({
|
||||
author: plugin.author,
|
||||
description: plugin.description.zh_CN,
|
||||
enabled: plugin.enabled,
|
||||
name: plugin.name,
|
||||
version: plugin.version,
|
||||
status: plugin.status,
|
||||
tools: plugin.tools,
|
||||
event_handlers: plugin.event_handlers,
|
||||
repository: plugin.repository,
|
||||
priority: plugin.priority,
|
||||
});
|
||||
}),
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
refreshPluginList: getPluginList,
|
||||
}));
|
||||
|
||||
function handlePluginClick(plugin: PluginCardVO) {
|
||||
setSelectedPlugin(plugin);
|
||||
setModalOpen(true);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`${styles.pluginListContainer}`}>
|
||||
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
|
||||
<DialogContent className="w-[700px] max-h-[80vh] p-0 flex flex-col">
|
||||
<DialogHeader className="px-6 pt-6 pb-2">
|
||||
<DialogTitle>插件配置</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="flex-1 overflow-y-auto px-6">
|
||||
{selectedPlugin && (
|
||||
<PluginForm
|
||||
pluginAuthor={selectedPlugin.author}
|
||||
pluginName={selectedPlugin.name}
|
||||
onFormSubmit={() => {
|
||||
setModalOpen(false);
|
||||
getPluginList();
|
||||
}}
|
||||
onFormCancel={() => {
|
||||
setModalOpen(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{pluginList.map((vo, index) => {
|
||||
return (
|
||||
<div key={index}>
|
||||
<PluginCardComponent
|
||||
cardVO={vo}
|
||||
onCardClick={() => handlePluginClick(vo)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export default PluginInstalledComponent;
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { PluginCardVO } from '@/app/home/plugins/plugin-installed/PluginCardVO';
|
||||
import { useState } from 'react';
|
||||
import { httpClient } from '@/app/infra/http/HttpClient';
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Switch } from "@/components/ui/switch"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { toast } from "sonner"
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
export default function PluginCardComponent({
|
||||
cardVO,
|
||||
@@ -25,39 +24,73 @@ export default function PluginCardComponent({
|
||||
setEnabled(!enabled);
|
||||
})
|
||||
.catch((err) => {
|
||||
toast.error("修改失败:" + err.message);
|
||||
toast.error('修改失败:' + err.message);
|
||||
})
|
||||
.finally(() => {
|
||||
setSwitchEnable(true);
|
||||
});
|
||||
}
|
||||
return (
|
||||
<div className="w-[26rem] h-[10rem] bg-white rounded-[10px] shadow-[0px_2px_2px_0_rgba(0,0,0,0.2)] p-[1.2rem] cursor-pointer" onClick={onCardClick}>
|
||||
<div
|
||||
className="w-[26rem] h-[10rem] bg-white rounded-[10px] shadow-[0px_2px_2px_0_rgba(0,0,0,0.2)] p-[1.2rem] cursor-pointer"
|
||||
onClick={onCardClick}
|
||||
>
|
||||
<div className="w-full h-full flex flex-row items-start justify-start gap-[1.2rem]">
|
||||
<svg className="w-16 h-16 text-[#2288ee]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M8 4C8 2.34315 9.34315 1 11 1C12.6569 1 14 2.34315 14 4C14 4.35064 13.9398 4.68722 13.8293 5H18C18.5523 5 19 5.44772 19 6V10.1707C19.3128 10.0602 19.6494 10 20 10C21.6569 10 23 11.3431 23 13C23 14.6569 21.6569 16 20 16C19.6494 16 19.3128 15.9398 19 15.8293V20C19 20.5523 18.5523 21 18 21H4C3.44772 21 3 20.5523 3 20V6C3 5.44772 3.44772 5 4 5H8.17071C8.06015 4.68722 8 4.35064 8 4Z"></path></svg>
|
||||
<svg
|
||||
className="w-16 h-16 text-[#2288ee]"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path d="M8 4C8 2.34315 9.34315 1 11 1C12.6569 1 14 2.34315 14 4C14 4.35064 13.9398 4.68722 13.8293 5H18C18.5523 5 19 5.44772 19 6V10.1707C19.3128 10.0602 19.6494 10 20 10C21.6569 10 23 11.3431 23 13C23 14.6569 21.6569 16 20 16C19.6494 16 19.3128 15.9398 19 15.8293V20C19 20.5523 18.5523 21 18 21H4C3.44772 21 3 20.5523 3 20V6C3 5.44772 3.44772 5 4 5H8.17071C8.06015 4.68722 8 4.35064 8 4Z"></path>
|
||||
</svg>
|
||||
|
||||
<div className="w-full h-full flex flex-col items-start justify-between gap-[0.6rem]">
|
||||
<div className="flex flex-col items-start justify-start">
|
||||
<div className="flex flex-col items-start justify-start">
|
||||
<div className="text-[0.7rem] text-[#666]">{cardVO.author} / </div>
|
||||
<div className="text-[0.7rem] text-[#666]">
|
||||
{cardVO.author} /{' '}
|
||||
</div>
|
||||
<div className="flex flex-row items-center justify-start gap-[0.4rem]">
|
||||
<div className="text-[1.2rem] text-black">{cardVO.name}</div>
|
||||
<Badge variant="outline" className="text-[0.7rem]">v{cardVO.version}</Badge>
|
||||
<Badge variant="outline" className="text-[0.7rem]">
|
||||
v{cardVO.version}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-[0.8rem] text-[#666] line-clamp-2">{cardVO.description}</div>
|
||||
<div className="text-[0.8rem] text-[#666] line-clamp-2">
|
||||
{cardVO.description}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full flex flex-row items-start justify-start gap-[0.6rem]">
|
||||
<div className="flex h-full flex-row items-center justify-center gap-[0.4rem]">
|
||||
<svg className="w-[1.2rem] h-[1.2rem] text-black" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M24 12L18.3431 17.6569L16.9289 16.2426L21.1716 12L16.9289 7.75736L18.3431 6.34315L24 12ZM2.82843 12L7.07107 16.2426L5.65685 17.6569L0 12L5.65685 6.34315L7.07107 7.75736L2.82843 12ZM9.78845 21H7.66009L14.2116 3H16.3399L9.78845 21Z"></path></svg>
|
||||
<div className="text-base text-black font-medium">事件 {Object.keys(cardVO.event_handlers).length}</div>
|
||||
<svg
|
||||
className="w-[1.2rem] h-[1.2rem] text-black"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path d="M24 12L18.3431 17.6569L16.9289 16.2426L21.1716 12L16.9289 7.75736L18.3431 6.34315L24 12ZM2.82843 12L7.07107 16.2426L5.65685 17.6569L0 12L5.65685 6.34315L7.07107 7.75736L2.82843 12ZM9.78845 21H7.66009L14.2116 3H16.3399L9.78845 21Z"></path>
|
||||
</svg>
|
||||
<div className="text-base text-black font-medium">
|
||||
事件 {Object.keys(cardVO.event_handlers).length}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex h-full flex-row items-center justify-center gap-[0.4rem]">
|
||||
<svg className="w-[1.2rem] h-[1.2rem] text-black" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M5.32943 3.27158C6.56252 2.8332 7.9923 3.10749 8.97927 4.09446C10.1002 5.21537 10.3019 6.90741 9.5843 8.23385L20.293 18.9437L18.8788 20.3579L8.16982 9.64875C6.84325 10.3669 5.15069 10.1654 4.02952 9.04421C3.04227 8.05696 2.7681 6.62665 3.20701 5.39332L5.44373 7.63C6.02952 8.21578 6.97927 8.21578 7.56505 7.63C8.15084 7.04421 8.15084 6.09446 7.56505 5.50868L5.32943 3.27158ZM15.6968 5.15512L18.8788 3.38736L20.293 4.80157L18.5252 7.98355L16.7574 8.3371L14.6361 10.4584L13.2219 9.04421L15.3432 6.92289L15.6968 5.15512ZM8.97927 13.2868L10.3935 14.7011L5.09018 20.0044C4.69966 20.3949 4.06649 20.3949 3.67597 20.0044C3.31334 19.6417 3.28744 19.0699 3.59826 18.6774L3.67597 18.5902L8.97927 13.2868Z"></path></svg>
|
||||
<div className="text-base text-black font-medium">工具 {cardVO.tools.length}</div>
|
||||
<svg
|
||||
className="w-[1.2rem] h-[1.2rem] text-black"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path d="M5.32943 3.27158C6.56252 2.8332 7.9923 3.10749 8.97927 4.09446C10.1002 5.21537 10.3019 6.90741 9.5843 8.23385L20.293 18.9437L18.8788 20.3579L8.16982 9.64875C6.84325 10.3669 5.15069 10.1654 4.02952 9.04421C3.04227 8.05696 2.7681 6.62665 3.20701 5.39332L5.44373 7.63C6.02952 8.21578 6.97927 8.21578 7.56505 7.63C8.15084 7.04421 8.15084 6.09446 7.56505 5.50868L5.32943 3.27158ZM15.6968 5.15512L18.8788 3.38736L20.293 4.80157L18.5252 7.98355L16.7574 8.3371L14.6361 10.4584L13.2219 9.04421L15.3432 6.92289L15.6968 5.15512ZM8.97927 13.2868L10.3935 14.7011L5.09018 20.0044C4.69966 20.3949 4.06649 20.3949 3.67597 20.0044C3.31334 19.6417 3.28744 19.0699 3.59826 18.6774L3.67597 18.5902L8.97927 13.2868Z"></path>
|
||||
</svg>
|
||||
<div className="text-base text-black font-medium">
|
||||
工具 {cardVO.tools.length}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { ApiRespPlugin, ApiRespPluginConfig, Plugin } from '@/app/infra/entities/api';
|
||||
import { ApiRespPluginConfig, Plugin } from '@/app/infra/entities/api';
|
||||
import { httpClient } from '@/app/infra/http/HttpClient';
|
||||
import DynamicFormComponent from '@/app/home/components/dynamic-form/DynamicFormComponent';
|
||||
import { IDynamicFormItemSchema } from '@/app/infra/entities/form/dynamic';
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -11,8 +10,8 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogFooter,
|
||||
} from "@/components/ui/dialog";
|
||||
import { toast } from "sonner";
|
||||
} from '@/components/ui/dialog';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
enum PluginRemoveStatus {
|
||||
WAIT_INPUT = 'WAIT_INPUT',
|
||||
@@ -36,8 +35,11 @@ export default function PluginForm({
|
||||
const [isSaving, setIsLoading] = useState(false);
|
||||
|
||||
const [showDeleteConfirmModal, setShowDeleteConfirmModal] = useState(false);
|
||||
const [pluginRemoveStatus, setPluginRemoveStatus] = useState<PluginRemoveStatus>(PluginRemoveStatus.WAIT_INPUT);
|
||||
const [pluginRemoveError, setPluginRemoveError] = useState<string | null>(null);
|
||||
const [pluginRemoveStatus, setPluginRemoveStatus] =
|
||||
useState<PluginRemoveStatus>(PluginRemoveStatus.WAIT_INPUT);
|
||||
const [pluginRemoveError, setPluginRemoveError] = useState<string | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// 获取插件信息
|
||||
@@ -52,12 +54,16 @@ export default function PluginForm({
|
||||
|
||||
const handleSubmit = async (values: object) => {
|
||||
setIsLoading(true);
|
||||
httpClient.updatePluginConfig(pluginAuthor, pluginName, values).then(() => {
|
||||
httpClient
|
||||
.updatePluginConfig(pluginAuthor, pluginName, values)
|
||||
.then(() => {
|
||||
onFormSubmit();
|
||||
toast.success("保存成功");
|
||||
}).catch((error) => {
|
||||
toast.error("保存失败:" + error.message);
|
||||
}).finally(() => {
|
||||
toast.success('保存成功');
|
||||
})
|
||||
.catch((error) => {
|
||||
toast.error('保存失败:' + error.message);
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
});
|
||||
};
|
||||
@@ -68,36 +74,39 @@ export default function PluginForm({
|
||||
|
||||
function deletePlugin() {
|
||||
setPluginRemoveStatus(PluginRemoveStatus.REMOVING);
|
||||
httpClient.removePlugin(pluginAuthor, pluginName).then((res) => {
|
||||
|
||||
const taskId = res.task_id;
|
||||
|
||||
const interval = setInterval(() => {
|
||||
httpClient.getAsyncTask(taskId).then((res) => {
|
||||
if (res.runtime.done) {
|
||||
clearInterval(interval);
|
||||
if (res.runtime.exception) {
|
||||
setPluginRemoveError(res.runtime.exception);
|
||||
setPluginRemoveStatus(PluginRemoveStatus.ERROR);
|
||||
} else {
|
||||
setPluginRemoveStatus(PluginRemoveStatus.WAIT_INPUT);
|
||||
setShowDeleteConfirmModal(false);
|
||||
onFormSubmit();
|
||||
}
|
||||
}
|
||||
});
|
||||
}, 1000);
|
||||
httpClient
|
||||
.removePlugin(pluginAuthor, pluginName)
|
||||
.then((res) => {
|
||||
const taskId = res.task_id;
|
||||
|
||||
}).catch((error) => {
|
||||
setPluginRemoveError(error.message);
|
||||
setPluginRemoveStatus(PluginRemoveStatus.ERROR);
|
||||
})
|
||||
const interval = setInterval(() => {
|
||||
httpClient.getAsyncTask(taskId).then((res) => {
|
||||
if (res.runtime.done) {
|
||||
clearInterval(interval);
|
||||
if (res.runtime.exception) {
|
||||
setPluginRemoveError(res.runtime.exception);
|
||||
setPluginRemoveStatus(PluginRemoveStatus.ERROR);
|
||||
} else {
|
||||
setPluginRemoveStatus(PluginRemoveStatus.WAIT_INPUT);
|
||||
setShowDeleteConfirmModal(false);
|
||||
onFormSubmit();
|
||||
}
|
||||
}
|
||||
});
|
||||
}, 1000);
|
||||
})
|
||||
.catch((error) => {
|
||||
setPluginRemoveError(error.message);
|
||||
setPluginRemoveStatus(PluginRemoveStatus.ERROR);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
||||
<Dialog open={showDeleteConfirmModal} onOpenChange={setShowDeleteConfirmModal}>
|
||||
<Dialog
|
||||
open={showDeleteConfirmModal}
|
||||
onOpenChange={setShowDeleteConfirmModal}
|
||||
>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>删除确认</DialogTitle>
|
||||
@@ -120,17 +129,23 @@ export default function PluginForm({
|
||||
</DialogDescription>
|
||||
<DialogFooter>
|
||||
{pluginRemoveStatus === PluginRemoveStatus.WAIT_INPUT && (
|
||||
<Button variant="outline" onClick={() => {
|
||||
setShowDeleteConfirmModal(false);
|
||||
setPluginRemoveStatus(PluginRemoveStatus.WAIT_INPUT);
|
||||
}}>
|
||||
取消
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setShowDeleteConfirmModal(false);
|
||||
setPluginRemoveStatus(PluginRemoveStatus.WAIT_INPUT);
|
||||
}}
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
)}
|
||||
{pluginRemoveStatus === PluginRemoveStatus.WAIT_INPUT && (
|
||||
<Button variant="destructive" onClick={() => {
|
||||
deletePlugin();
|
||||
}}>
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={() => {
|
||||
deletePlugin();
|
||||
}}
|
||||
>
|
||||
确认删除
|
||||
</Button>
|
||||
)}
|
||||
@@ -140,10 +155,13 @@ export default function PluginForm({
|
||||
</Button>
|
||||
)}
|
||||
{pluginRemoveStatus === PluginRemoveStatus.ERROR && (
|
||||
<Button variant="default" onClick={() => {
|
||||
setShowDeleteConfirmModal(false);
|
||||
// setPluginRemoveStatus(PluginRemoveStatus.WAIT_INPUT);
|
||||
}}>
|
||||
<Button
|
||||
variant="default"
|
||||
onClick={() => {
|
||||
setShowDeleteConfirmModal(false);
|
||||
// setPluginRemoveStatus(PluginRemoveStatus.WAIT_INPUT);
|
||||
}}
|
||||
>
|
||||
关闭
|
||||
</Button>
|
||||
)}
|
||||
@@ -151,7 +169,6 @@ export default function PluginForm({
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="text-lg font-medium">{pluginInfo.name}</div>
|
||||
<div className="text-sm text-gray-500 pb-2">
|
||||
@@ -160,7 +177,7 @@ export default function PluginForm({
|
||||
{pluginInfo.config_schema.length > 0 && (
|
||||
<DynamicFormComponent
|
||||
itemConfigList={pluginInfo.config_schema}
|
||||
initialValues={pluginConfig.config}
|
||||
initialValues={pluginConfig.config as Record<string, object>}
|
||||
onSubmit={(values) => {
|
||||
let config = pluginConfig.config;
|
||||
config = {
|
||||
@@ -174,15 +191,12 @@ export default function PluginForm({
|
||||
/>
|
||||
)}
|
||||
{pluginInfo.config_schema.length === 0 && (
|
||||
<div className="text-sm text-gray-500">
|
||||
该插件没有配置项。
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">该插件没有配置项。</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="sticky bottom-0 left-0 right-0 bg-background border-t p-4 mt-4">
|
||||
<div className="flex justify-end gap-2">
|
||||
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={() => {
|
||||
@@ -191,7 +205,9 @@ export default function PluginForm({
|
||||
}}
|
||||
disabled={pluginRemoveStatus === PluginRemoveStatus.REMOVING}
|
||||
>
|
||||
{pluginRemoveStatus === PluginRemoveStatus.REMOVING ? '删除中...' : '删除插件'}
|
||||
{pluginRemoveStatus === PluginRemoveStatus.REMOVING
|
||||
? '删除中...'
|
||||
: '删除插件'}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
@@ -208,4 +224,4 @@ export default function PluginForm({
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,18 +9,23 @@ import { Input } from '@/components/ui/input';
|
||||
import {
|
||||
Pagination,
|
||||
PaginationContent,
|
||||
PaginationEllipsis,
|
||||
PaginationItem,
|
||||
PaginationLink,
|
||||
PaginationNext,
|
||||
PaginationPrevious,
|
||||
} from "@/components/ui/pagination"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
} from '@/components/ui/pagination';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
|
||||
export default function PluginMarketComponent({
|
||||
askInstallPlugin,
|
||||
}: {
|
||||
askInstallPlugin: (githubURL: string) => void,
|
||||
askInstallPlugin: (githubURL: string) => void;
|
||||
}) {
|
||||
const [marketPluginList, setMarketPluginList] = useState<
|
||||
PluginMarketCardVO[]
|
||||
@@ -45,7 +50,7 @@ export default function PluginMarketComponent({
|
||||
|
||||
function onInputSearchKeyword(keyword: string) {
|
||||
setSearchKeyword(keyword);
|
||||
|
||||
|
||||
// 清除之前的定时器
|
||||
if (searchTimeout.current) {
|
||||
clearTimeout(searchTimeout.current);
|
||||
@@ -69,33 +74,31 @@ export default function PluginMarketComponent({
|
||||
.getMarketPlugins(page, pageSize, keyword, sortBy, sortOrder)
|
||||
.then((res) => {
|
||||
setMarketPluginList(
|
||||
res.plugins.map(
|
||||
(marketPlugin) => {
|
||||
let repository = marketPlugin.repository;
|
||||
if (repository.startsWith('https://github.com/')) {
|
||||
repository = repository.replace('https://github.com/', '');
|
||||
}
|
||||
res.plugins.map((marketPlugin) => {
|
||||
let repository = marketPlugin.repository;
|
||||
if (repository.startsWith('https://github.com/')) {
|
||||
repository = repository.replace('https://github.com/', '');
|
||||
}
|
||||
|
||||
if (repository.startsWith('github.com/')) {
|
||||
repository = repository.replace('github.com/', '');
|
||||
}
|
||||
if (repository.startsWith('github.com/')) {
|
||||
repository = repository.replace('github.com/', '');
|
||||
}
|
||||
|
||||
const author = repository.split('/')[0];
|
||||
const name = repository.split('/')[1];
|
||||
return new PluginMarketCardVO({
|
||||
author: author,
|
||||
description: marketPlugin.description,
|
||||
githubURL: `https://github.com/${repository}`,
|
||||
name: name,
|
||||
pluginId: String(marketPlugin.ID),
|
||||
starCount: marketPlugin.stars,
|
||||
version:
|
||||
'version' in marketPlugin
|
||||
? String(marketPlugin.version)
|
||||
: '1.0.0', // Default version if not provided
|
||||
});
|
||||
},
|
||||
),
|
||||
const author = repository.split('/')[0];
|
||||
const name = repository.split('/')[1];
|
||||
return new PluginMarketCardVO({
|
||||
author: author,
|
||||
description: marketPlugin.description,
|
||||
githubURL: `https://github.com/${repository}`,
|
||||
name: name,
|
||||
pluginId: String(marketPlugin.ID),
|
||||
starCount: marketPlugin.stars,
|
||||
version:
|
||||
'version' in marketPlugin
|
||||
? String(marketPlugin.version)
|
||||
: '1.0.0', // Default version if not provided
|
||||
});
|
||||
}),
|
||||
);
|
||||
setTotalCount(res.total);
|
||||
setLoading(false);
|
||||
@@ -113,7 +116,7 @@ export default function PluginMarketComponent({
|
||||
}
|
||||
|
||||
function handleSortChange(value: string) {
|
||||
const [newSortBy, newSortOrder] = value.split(',').map(s => s.trim());
|
||||
const [newSortBy, newSortOrder] = value.split(',').map((s) => s.trim());
|
||||
setSortByValue(newSortBy);
|
||||
setSortOrderValue(newSortOrder);
|
||||
setNowPage(1);
|
||||
@@ -132,7 +135,10 @@ export default function PluginMarketComponent({
|
||||
onChange={(e) => onInputSearchKeyword(e.target.value)}
|
||||
/>
|
||||
|
||||
<Select value={`${sortByValue},${sortOrderValue}`} onValueChange={handleSortChange}>
|
||||
<Select
|
||||
value={`${sortByValue},${sortOrderValue}`}
|
||||
onValueChange={handleSortChange}
|
||||
>
|
||||
<SelectTrigger className="w-[180px] ml-2 cursor-pointer">
|
||||
<SelectValue placeholder="排序方式" />
|
||||
</SelectTrigger>
|
||||
@@ -147,10 +153,12 @@ export default function PluginMarketComponent({
|
||||
{totalCount > 0 && (
|
||||
<Pagination>
|
||||
<PaginationContent>
|
||||
<PaginationItem className='cursor-pointer'>
|
||||
<PaginationItem className="cursor-pointer">
|
||||
<PaginationPrevious
|
||||
onClick={() => handlePageChange(nowPage - 1)}
|
||||
className={nowPage <= 1 ? 'pointer-events-none opacity-50' : ''}
|
||||
className={
|
||||
nowPage <= 1 ? 'pointer-events-none opacity-50' : ''
|
||||
}
|
||||
/>
|
||||
</PaginationItem>
|
||||
|
||||
@@ -158,35 +166,50 @@ export default function PluginMarketComponent({
|
||||
{(() => {
|
||||
const totalPages = Math.ceil(totalCount / pageSize);
|
||||
const maxVisiblePages = 5;
|
||||
let startPage = Math.max(1, nowPage - Math.floor(maxVisiblePages / 2));
|
||||
let endPage = Math.min(totalPages, startPage + maxVisiblePages - 1);
|
||||
let startPage = Math.max(
|
||||
1,
|
||||
nowPage - Math.floor(maxVisiblePages / 2),
|
||||
);
|
||||
const endPage = Math.min(
|
||||
totalPages,
|
||||
startPage + maxVisiblePages - 1,
|
||||
);
|
||||
|
||||
if (endPage - startPage + 1 < maxVisiblePages) {
|
||||
startPage = Math.max(1, endPage - maxVisiblePages + 1);
|
||||
}
|
||||
|
||||
return Array.from({ length: endPage - startPage + 1 }, (_, i) => {
|
||||
const pageNum = startPage + i;
|
||||
return (
|
||||
<PaginationItem key={pageNum} className='cursor-pointer'>
|
||||
<PaginationLink
|
||||
isActive={pageNum === nowPage}
|
||||
onClick={() => handlePageChange(pageNum)}
|
||||
return Array.from(
|
||||
{ length: endPage - startPage + 1 },
|
||||
(_, i) => {
|
||||
const pageNum = startPage + i;
|
||||
return (
|
||||
<PaginationItem
|
||||
key={pageNum}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<span className="text-black select-none">
|
||||
{pageNum}
|
||||
</span>
|
||||
</PaginationLink>
|
||||
</PaginationItem>
|
||||
);
|
||||
});
|
||||
<PaginationLink
|
||||
isActive={pageNum === nowPage}
|
||||
onClick={() => handlePageChange(pageNum)}
|
||||
>
|
||||
<span className="text-black select-none">
|
||||
{pageNum}
|
||||
</span>
|
||||
</PaginationLink>
|
||||
</PaginationItem>
|
||||
);
|
||||
},
|
||||
);
|
||||
})()}
|
||||
|
||||
|
||||
<PaginationItem className='cursor-pointer'>
|
||||
<PaginationItem className="cursor-pointer">
|
||||
<PaginationNext
|
||||
onClick={() => handlePageChange(nowPage + 1)}
|
||||
className={nowPage >= Math.ceil(totalCount / pageSize) ? 'pointer-events-none opacity-50' : ''}
|
||||
className={
|
||||
nowPage >= Math.ceil(totalCount / pageSize)
|
||||
? 'pointer-events-none opacity-50'
|
||||
: ''
|
||||
}
|
||||
/>
|
||||
</PaginationItem>
|
||||
</PaginationContent>
|
||||
@@ -207,9 +230,12 @@ export default function PluginMarketComponent({
|
||||
) : (
|
||||
marketPluginList.map((vo, index) => (
|
||||
<div key={`${vo.pluginId}-${index}`}>
|
||||
<PluginMarketCardComponent cardVO={vo} installPlugin={(githubURL) => {
|
||||
askInstallPlugin(githubURL);
|
||||
}} />
|
||||
<PluginMarketCardComponent
|
||||
cardVO={vo}
|
||||
installPlugin={(githubURL) => {
|
||||
askInstallPlugin(githubURL);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
|
||||
+38
-12
@@ -15,40 +15,66 @@ export default function PluginMarketCardComponent({
|
||||
return (
|
||||
<div className="w-[26rem] h-[10rem] bg-white rounded-[10px] shadow-[0px_2px_2px_0_rgba(0,0,0,0.2)] p-[1.2rem]">
|
||||
<div className="w-full h-full flex flex-row items-start justify-start gap-[1.2rem]">
|
||||
<svg className="w-16 h-16 text-[#2288ee]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M8 4C8 2.34315 9.34315 1 11 1C12.6569 1 14 2.34315 14 4C14 4.35064 13.9398 4.68722 13.8293 5H18C18.5523 5 19 5.44772 19 6V10.1707C19.3128 10.0602 19.6494 10 20 10C21.6569 10 23 11.3431 23 13C23 14.6569 21.6569 16 20 16C19.6494 16 19.3128 15.9398 19 15.8293V20C19 20.5523 18.5523 21 18 21H4C3.44772 21 3 20.5523 3 20V6C3 5.44772 3.44772 5 4 5H8.17071C8.06015 4.68722 8 4.35064 8 4Z"></path></svg>
|
||||
<svg
|
||||
className="w-16 h-16 text-[#2288ee]"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path d="M8 4C8 2.34315 9.34315 1 11 1C12.6569 1 14 2.34315 14 4C14 4.35064 13.9398 4.68722 13.8293 5H18C18.5523 5 19 5.44772 19 6V10.1707C19.3128 10.0602 19.6494 10 20 10C21.6569 10 23 11.3431 23 13C23 14.6569 21.6569 16 20 16C19.6494 16 19.3128 15.9398 19 15.8293V20C19 20.5523 18.5523 21 18 21H4C3.44772 21 3 20.5523 3 20V6C3 5.44772 3.44772 5 4 5H8.17071C8.06015 4.68722 8 4.35064 8 4Z"></path>
|
||||
</svg>
|
||||
|
||||
<div className="w-full h-full flex flex-col items-start justify-between gap-[0.6rem]">
|
||||
<div className="flex flex-col items-start justify-start">
|
||||
<div className="flex flex-col items-start justify-start">
|
||||
<div className="text-[0.7rem] text-[#666]">{cardVO.author} / </div>
|
||||
<div className="text-[0.7rem] text-[#666]">
|
||||
{cardVO.author} /{' '}
|
||||
</div>
|
||||
<div className="flex flex-row items-center justify-start gap-[0.4rem]">
|
||||
<div className="text-[1.2rem] text-black">{cardVO.name}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-[0.8rem] text-[#666] line-clamp-2">{cardVO.description}</div>
|
||||
<div className="text-[0.8rem] text-[#666] line-clamp-2">
|
||||
{cardVO.description}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full flex flex-row items-start justify-between gap-[0.6rem]">
|
||||
<div className="flex h-full flex-row items-center justify-center gap-[0.4rem]">
|
||||
<svg className="w-[1.2rem] h-[1.2rem] text-[#ffcd27]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12.0006 18.26L4.94715 22.2082L6.52248 14.2799L0.587891 8.7918L8.61493 7.84006L12.0006 0.5L15.3862 7.84006L23.4132 8.7918L17.4787 14.2799L19.054 22.2082L12.0006 18.26Z"></path></svg>
|
||||
<div className="text-base text-[#ffcd27] font-medium">星标 {cardVO.starCount}</div>
|
||||
<svg
|
||||
className="w-[1.2rem] h-[1.2rem] text-[#ffcd27]"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path d="M12.0006 18.26L4.94715 22.2082L6.52248 14.2799L0.587891 8.7918L8.61493 7.84006L12.0006 0.5L15.3862 7.84006L23.4132 8.7918L17.4787 14.2799L19.054 22.2082L12.0006 18.26Z"></path>
|
||||
</svg>
|
||||
<div className="text-base text-[#ffcd27] font-medium">
|
||||
星标 {cardVO.starCount}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex h-full flex-row items-center justify-center gap-[0.4rem]">
|
||||
<svg
|
||||
className="w-[1.4rem] h-[1.4rem] text-black cursor-pointer"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
<svg
|
||||
className="w-[1.4rem] h-[1.4rem] text-black cursor-pointer"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
onClick={() => window.open(cardVO.githubURL, '_blank')}
|
||||
><path d="M12.001 2C6.47598 2 2.00098 6.475 2.00098 12C2.00098 16.425 4.86348 20.1625 8.83848 21.4875C9.33848 21.575 9.52598 21.275 9.52598 21.0125C9.52598 20.775 9.51348 19.9875 9.51348 19.15C7.00098 19.6125 6.35098 18.5375 6.15098 17.975C6.03848 17.6875 5.55098 16.8 5.12598 16.5625C4.77598 16.375 4.27598 15.9125 5.11348 15.9C5.90098 15.8875 6.46348 16.625 6.65098 16.925C7.55098 18.4375 8.98848 18.0125 9.56348 17.75C9.65098 17.1 9.91348 16.6625 10.201 16.4125C7.97598 16.1625 5.65098 15.3 5.65098 11.475C5.65098 10.3875 6.03848 9.4875 6.67598 8.7875C6.57598 8.5375 6.22598 7.5125 6.77598 6.1375C6.77598 6.1375 7.61348 5.875 9.52598 7.1625C10.326 6.9375 11.176 6.825 12.026 6.825C12.876 6.825 13.726 6.9375 14.526 7.1625C16.4385 5.8625 17.276 6.1375 17.276 6.1375C17.826 7.5125 17.476 8.5375 17.376 8.7875C18.0135 9.4875 18.401 10.375 18.401 11.475C18.401 15.3125 16.0635 16.1625 13.8385 16.4125C14.201 16.725 14.5135 17.325 14.5135 18.2625C14.5135 19.6 14.501 20.675 14.501 21.0125C14.501 21.275 14.6885 21.5875 15.1885 21.4875C19.259 20.1133 21.9999 16.2963 22.001 12C22.001 6.475 17.526 2 12.001 2Z"></path></svg>
|
||||
<Button variant="default" size="sm"
|
||||
>
|
||||
<path d="M12.001 2C6.47598 2 2.00098 6.475 2.00098 12C2.00098 16.425 4.86348 20.1625 8.83848 21.4875C9.33848 21.575 9.52598 21.275 9.52598 21.0125C9.52598 20.775 9.51348 19.9875 9.51348 19.15C7.00098 19.6125 6.35098 18.5375 6.15098 17.975C6.03848 17.6875 5.55098 16.8 5.12598 16.5625C4.77598 16.375 4.27598 15.9125 5.11348 15.9C5.90098 15.8875 6.46348 16.625 6.65098 16.925C7.55098 18.4375 8.98848 18.0125 9.56348 17.75C9.65098 17.1 9.91348 16.6625 10.201 16.4125C7.97598 16.1625 5.65098 15.3 5.65098 11.475C5.65098 10.3875 6.03848 9.4875 6.67598 8.7875C6.57598 8.5375 6.22598 7.5125 6.77598 6.1375C6.77598 6.1375 7.61348 5.875 9.52598 7.1625C10.326 6.9375 11.176 6.825 12.026 6.825C12.876 6.825 13.726 6.9375 14.526 7.1625C16.4385 5.8625 17.276 6.1375 17.276 6.1375C17.826 7.5125 17.476 8.5375 17.376 8.7875C18.0135 9.4875 18.401 10.375 18.401 11.475C18.401 15.3125 16.0635 16.1625 13.8385 16.4125C14.201 16.725 14.5135 17.325 14.5135 18.2625C14.5135 19.6 14.501 20.675 14.501 21.0125C14.501 21.275 14.6885 21.5875 15.1885 21.4875C19.259 20.1133 21.9999 16.2963 22.001 12C22.001 6.475 17.526 2 12.001 2Z"></path>
|
||||
</svg>
|
||||
<Button
|
||||
variant="default"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
handleInstallClick(cardVO.githubURL);
|
||||
}}
|
||||
className="cursor-pointer"
|
||||
>安装</Button>
|
||||
>
|
||||
安装
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user