mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-06-16 21:03:58 +08:00
feat(plugins): enhance plugin logo handling with fallback and improve sidebar layout
This commit is contained in:
@@ -35,6 +35,7 @@ import {
|
||||
} from '@/stores';
|
||||
import {
|
||||
collectPluginResourceEntries,
|
||||
resolvePluginAssetURL,
|
||||
type PluginResourceEntry,
|
||||
} from '@/features/plugins/pluginResources';
|
||||
import { triggerHeaderRefresh } from '@/hooks/useHeaderRefresh';
|
||||
@@ -84,6 +85,17 @@ interface SidebarNavGroup {
|
||||
const flattenNavItems = (items: SidebarNavItem[]): SidebarNavLinkItem[] =>
|
||||
items.flatMap((item) => item.kind === 'drawer' ? item.children : [item]);
|
||||
|
||||
function PluginSidebarIcon({ src }: { src: string }) {
|
||||
const [failed, setFailed] = useState(false);
|
||||
const showImage = Boolean(src) && !failed;
|
||||
|
||||
return showImage ? (
|
||||
<img src={src} alt="" onError={() => setFailed(true)} />
|
||||
) : (
|
||||
<IconSidebarPlugins size={18} />
|
||||
);
|
||||
}
|
||||
|
||||
// Header action icons - smaller size for header buttons
|
||||
const headerIconProps: SVGProps<SVGSVGElement> = {
|
||||
width: 16,
|
||||
@@ -471,23 +483,25 @@ export function MainLayout() {
|
||||
(group): SidebarNavItem[] => {
|
||||
if (group.entries.length === 1) {
|
||||
const resource = group.entries[0];
|
||||
const pluginLogo = resolvePluginAssetURL(resource.pluginLogo, apiBase);
|
||||
return [
|
||||
{
|
||||
path: resource.route,
|
||||
label: resource.label,
|
||||
meta: resource.description,
|
||||
icon: sidebarIcons.plugins,
|
||||
icon: <PluginSidebarIcon src={pluginLogo} />,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
const pluginLogo = resolvePluginAssetURL(group.entries[0]?.pluginLogo ?? '', apiBase);
|
||||
return [
|
||||
{
|
||||
kind: 'drawer',
|
||||
id: `plugin-pages-${group.pluginID}`,
|
||||
label: group.pluginTitle,
|
||||
meta: t('plugin_resource.page_count', { count: group.entries.length }),
|
||||
icon: sidebarIcons.plugins,
|
||||
icon: <PluginSidebarIcon src={pluginLogo} />,
|
||||
children: group.entries.map((resource) => ({
|
||||
path: resource.route,
|
||||
label: resource.label,
|
||||
@@ -554,15 +568,6 @@ export function MainLayout() {
|
||||
},
|
||||
],
|
||||
},
|
||||
...(pluginPageNavItems.length > 0
|
||||
? [
|
||||
{
|
||||
id: 'plugin-pages',
|
||||
labelKey: 'nav_groups.plugin_pages',
|
||||
items: pluginPageNavItems,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
id: 'control',
|
||||
labelKey: 'nav_groups.control',
|
||||
@@ -587,6 +592,15 @@ export function MainLayout() {
|
||||
},
|
||||
],
|
||||
},
|
||||
...(pluginPageNavItems.length > 0
|
||||
? [
|
||||
{
|
||||
id: 'plugin-pages',
|
||||
labelKey: 'nav_groups.plugin_pages',
|
||||
items: pluginPageNavItems,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
];
|
||||
const navItems = navGroups.flatMap((group) => flattenNavItems(group.items));
|
||||
const navOrder = navItems.map((item) => item.path);
|
||||
@@ -928,7 +942,10 @@ export function MainLayout() {
|
||||
|
||||
<div className="nav-section">
|
||||
{navGroups.map((group, idx) => (
|
||||
<div className="nav-group" key={group.id}>
|
||||
<div
|
||||
className={`nav-group ${group.id === 'plugin-pages' ? 'nav-group-bottom' : ''}`}
|
||||
key={group.id}
|
||||
>
|
||||
{showSidebarLabels
|
||||
? <div className="nav-group-label">{t(group.labelKey)}</div>
|
||||
: idx > 0 && <div className="nav-group-divider" aria-hidden="true" />}
|
||||
|
||||
@@ -31,6 +31,17 @@ interface PluginConfigDraft {
|
||||
errors: Record<string, string>;
|
||||
}
|
||||
|
||||
function PluginCardLogo({ src }: { src: string }) {
|
||||
const [failed, setFailed] = useState(false);
|
||||
const showImage = Boolean(src) && !failed;
|
||||
|
||||
return showImage ? (
|
||||
<img src={src} alt="" onError={() => setFailed(true)} />
|
||||
) : (
|
||||
<IconPlug size={18} />
|
||||
);
|
||||
}
|
||||
|
||||
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||
value !== null && typeof value === 'object' && !Array.isArray(value);
|
||||
|
||||
@@ -701,7 +712,7 @@ export function PluginsPage() {
|
||||
<article key={plugin.id} className={styles.pluginRow}>
|
||||
{/* Logo */}
|
||||
<div className={styles.logoBox} aria-hidden="true">
|
||||
{logo ? <img src={logo} alt="" /> : <IconPlug size={18} />}
|
||||
<PluginCardLogo src={logo} />
|
||||
</div>
|
||||
|
||||
{/* Info */}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { normalizeApiBase } from '@/utils/connection';
|
||||
export interface PluginResourceEntry {
|
||||
pluginID: string;
|
||||
pluginTitle: string;
|
||||
pluginLogo: string;
|
||||
menuIndex: number;
|
||||
menu: PluginMenu;
|
||||
label: string;
|
||||
@@ -31,6 +32,7 @@ export const collectPluginResourceEntries = (
|
||||
): PluginResourceEntry[] =>
|
||||
plugins.flatMap((plugin) => {
|
||||
const pluginTitle = getPluginTitle(plugin);
|
||||
const pluginLogo = plugin.logo || plugin.metadata?.logo || '';
|
||||
|
||||
return plugin.menus
|
||||
.map((menu, menuIndex): PluginResourceEntry | null => {
|
||||
@@ -41,6 +43,7 @@ export const collectPluginResourceEntries = (
|
||||
return {
|
||||
pluginID: plugin.id,
|
||||
pluginTitle,
|
||||
pluginLogo,
|
||||
menuIndex,
|
||||
menu: { ...menu, path },
|
||||
label: menuLabel || pluginTitle,
|
||||
|
||||
@@ -498,6 +498,11 @@
|
||||
gap: $spacing-xs;
|
||||
}
|
||||
|
||||
.nav-group-bottom {
|
||||
margin-top: auto;
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
.nav-group-label {
|
||||
padding: 0 12px 4px;
|
||||
font-size: 10.5px;
|
||||
@@ -590,6 +595,14 @@
|
||||
height: 18px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
img {
|
||||
display: block;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 5px;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-text {
|
||||
|
||||
Reference in New Issue
Block a user