feat(plugins): enhance plugin logo handling with fallback and improve sidebar layout

This commit is contained in:
LTbinglingfeng
2026-06-11 22:36:34 +08:00
Unverified
parent 7418fac6be
commit 4b3daa0010
4 changed files with 57 additions and 13 deletions
+29 -12
View File
@@ -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" />}
+12 -1
View File
@@ -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 */}
+3
View File
@@ -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,
+13
View File
@@ -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 {