fix: app icon & category icon (#529)

This commit is contained in:
BiggerRain
2025-05-19 17:24:51 +08:00
committed by GitHub
parent 71ce23ef21
commit f9c1be8517
11 changed files with 129 additions and 86 deletions

View File

@@ -7,17 +7,7 @@ title: "Release Notes"
Information about release notes of Coco Server is provided here.
## Latest (In development)
### ❌ Breaking changes
### 🚀 Features
### 🐛 Bug fix
### ✈️ Improvements
## 0.5.0 (2025-05-17)
## 0.5.0 (In development)
### ❌ Breaking changes
@@ -62,6 +52,7 @@ Information about release notes of Coco Server is provided here.
- refactor: optimizing list styles in markdown content #520
- feat: add a component for text reading aloud #522
- style: history component styles #528
- fix: app icon & category icon #529
## 0.4.0 (2025-04-27)

View File

@@ -1,16 +1,17 @@
import { useState } from "react";
import { isEmpty } from "lodash-es";
import { useAsyncEffect } from "ahooks";
import { Box } from "lucide-react";
import platformAdapter from "@/utils/platformAdapter";
import UniversalIcon from "./UniversalIcon";
import UniversalIcon, { getIconType } from "./UniversalIcon";
import { useFindConnectorIcon } from "@/hooks/useFindConnectorIcon";
interface CommonIconProps {
renderOrder: string[];
item: any;
itemIcon?: string;
defaultIcon?: React.FC;
defaultIcon?: React.FC | string;
className?: string;
onClick?: (e: React.MouseEvent) => void;
}
@@ -26,6 +27,9 @@ function CommonIcon({
const connectorSource = useFindConnectorIcon(item);
const [isAbsolute, setIsAbsolute] = useState<boolean>();
const [defaultIconState, setDefaultIconState] = useState<
React.FC | string | undefined
>(defaultIcon);
useAsyncEffect(async () => {
if (isEmpty(item)) return;
@@ -35,40 +39,40 @@ function CommonIcon({
omitSize: true,
});
setIsAbsolute(Boolean(isAbsolute));
setDefaultIconState(defaultIcon || Box);
} catch (error) {
setIsAbsolute(false);
}
}, [item]);
// Handle special icon types
const renderSpecialIcon = () => {
if (item.id === "Calculator") {
return (
<UniversalIcon
icon="/assets/calculator.png"
className={className}
onClick={onClick}
/>
);
}
if (isAbsolute) {
return (
<UniversalIcon
icon={platformAdapter.convertFileSrc(item?.icon)}
className={className}
onClick={onClick}
/>
);
}
return null;
};
// Handle regular icon types
const renderIconByType = (renderType: string) => {
switch (renderType) {
case "special_icon": {
if (item.id === "Calculator") {
return (
<UniversalIcon
icon="/assets/calculator.png"
className={className}
onClick={onClick}
/>
);
}
if (isAbsolute) {
return (
<UniversalIcon
icon={item?.icon}
appIcon={true}
className={className}
onClick={onClick}
/>
);
}
return null;
}
case "item_icon":
if (getIconType(itemIcon) === "default") return null;
return (
<UniversalIcon
icon={itemIcon}
@@ -78,7 +82,8 @@ function CommonIcon({
);
case "connector_icon": {
const icons = connectorSource?.assets?.icons || {};
const selectedIcon = itemIcon && icons[itemIcon];
const selectedIcon = (itemIcon && icons[itemIcon]) || itemIcon;
if (!selectedIcon) return null;
return (
<UniversalIcon
icon={selectedIcon}
@@ -90,7 +95,7 @@ function CommonIcon({
case "default_icon":
return (
<UniversalIcon
defaultIcon={defaultIcon}
defaultIcon={defaultIconState}
className={className}
onClick={onClick}
/>
@@ -100,13 +105,10 @@ function CommonIcon({
}
};
// Render logic
const specialIcon = renderSpecialIcon();
if (specialIcon) return specialIcon;
for (const renderType of renderOrder) {
const icon = renderIconByType(renderType);
if (icon) return icon;
if (!icon) continue;
return icon;
}
return null;

View File

@@ -1,41 +1,52 @@
import React from "react";
import { Box } from "lucide-react";
import { File } from "lucide-react";
import IconWrapper from "./IconWrapper";
import ThemedIcon from "./ThemedIcon";
import FontIcon from "./FontIcon";
import { useAppStore } from "@/stores/appStore";
import platformAdapter from "@/utils/platformAdapter";
interface UniversalIconProps {
icon?: string; // Icon source
defaultIcon?: React.FC; // Default icon component
className?: string; // Style class name
icon?: string; // Icon source
defaultIcon?: React.FC | string; // Default icon component
appIcon?: boolean; // Whether the icon is local
className?: string; // Style class name
onClick?: React.MouseEventHandler<HTMLDivElement>;
wrapWithIconWrapper?: boolean; // Whether to wrap with IconWrapper
}
type IconType = 'url' | 'base64' | 'font' | 'local' | 'splice' | 'default';
type IconType =
| "url"
| "base64"
| "font"
| "local"
| "app"
| "splice"
| "default";
// Determine icon type
export const getIconType = (icon?: string, appIcon?: boolean): IconType => {
if (!icon) return "default";
if (appIcon) return "app";
if (icon.startsWith("http://") || icon.startsWith("https://")) return "url";
if (icon.startsWith("data:image/")) return "base64";
if (icon.startsWith("font_")) return "font";
if (icon.startsWith("/assets")) return "local";
if (icon.startsWith("/")) return "splice";
return "default";
};
function UniversalIcon({
icon,
defaultIcon = Box,
defaultIcon = File,
appIcon = false,
className = "w-5 h-5 flex-shrink-0",
onClick = () => {},
wrapWithIconWrapper = true,
}: UniversalIconProps) {
const endpoint_http = useAppStore((state) => state.endpoint_http);
// Determine icon type
const getIconType = (icon?: string): IconType => {
if (!icon) return 'default';
if (icon.startsWith('http://') || icon.startsWith('https://')) return 'url';
if (icon.startsWith('data:image/')) return 'base64';
if (icon.startsWith('font_')) return 'font';
if (icon.startsWith('/assets')) return 'local';
if (icon.startsWith('/')) return 'splice';
return 'default';
};
// Render image type icon
const renderImageIcon = (src: string) => {
const img = <img className={className} src={src} alt="icon" />;
@@ -43,34 +54,63 @@ function UniversalIcon({
<IconWrapper className={className} onClick={onClick}>
{img}
</IconWrapper>
) : img;
) : (
img
);
};
// Render app type icon
const renderAppIcon = (src: string) => {
const img = (
<img
className={className}
src={platformAdapter.convertFileSrc(src)}
alt="icon"
/>
);
return wrapWithIconWrapper ? (
<IconWrapper className={className} onClick={onClick}>
{img}
</IconWrapper>
) : (
img
);
};
// Render default icon
const renderDefaultIcon = () => {
const defaultComponent = <ThemedIcon component={defaultIcon} className={className} />;
if (typeof defaultIcon === "string") {
return renderImageIcon(defaultIcon);
}
const defaultComponent = (
<ThemedIcon component={defaultIcon} className={className} />
);
return wrapWithIconWrapper ? (
<IconWrapper className={className} onClick={onClick}>
{defaultComponent}
</IconWrapper>
) : defaultComponent;
) : (
defaultComponent
);
};
// Render component based on icon type
const iconType = getIconType(icon);
const iconType = getIconType(icon, appIcon);
switch (iconType) {
case 'url':
case 'base64':
case 'local':
return renderImageIcon(icon!);
case 'splice':
const url = `${endpoint_http}${icon!}`
case "url":
case "base64":
case "local":
return renderImageIcon(icon!);
case "app":
return renderAppIcon(icon!);
case "splice":
const url = `${endpoint_http}${icon!}`;
return renderImageIcon(url);
case 'font':
case "font":
return <FontIcon name={icon!} className={className} />;
default:
return renderDefaultIcon();
}
}
export default UniversalIcon;
export default UniversalIcon;

View File

@@ -13,6 +13,9 @@ import { useUpdateStore } from "@/stores/updateStore";
import VisibleKey from "../VisibleKey";
import { useShortcutsStore } from "@/stores/shortcutsStore";
import { formatKey } from "@/utils/keyboardUtils";
import source_default_img from "@/assets/images/source_default.png";
import source_default_dark_img from "@/assets/images/source_default_dark.png";
import { useThemeStore } from "@/stores/themeStore";
interface FooterProps {
isTauri: boolean;
@@ -27,6 +30,7 @@ export default function Footer({
}: FooterProps) {
const { t } = useTranslation();
const sourceData = useSearchStore((state) => state.sourceData);
const isDark = useThemeStore((state) => state.isDark);
const isPinned = useAppStore((state) => state.isPinned);
const setIsPinned = useAppStore((state) => state.setIsPinned);
@@ -59,8 +63,9 @@ export default function Footer({
{sourceData?.source?.name ? (
<CommonIcon
item={sourceData}
renderOrder={["item_icon", "connector_icon"]}
renderOrder={["connector_icon", "default_icon"]}
itemIcon={sourceData?.source?.icon}
defaultIcon={isDark ? source_default_dark_img : source_default_img}
className="w-4 h-4"
/>
) : (

View File

@@ -66,7 +66,7 @@ export const DocumentDetail: React.FC<DocumentDetailProps> = ({ document }) => {
/>
) : (
<CommonIcon
renderOrder={["item_icon", "connector_icon", "default_icon"]}
renderOrder={["special_icon", "item_icon", "connector_icon", "default_icon"]}
item={document}
itemIcon={document?.icon}
defaultIcon={File}
@@ -102,7 +102,7 @@ export const DocumentDetail: React.FC<DocumentDetailProps> = ({ document }) => {
icon={
<CommonIcon
item={document}
renderOrder={["item_icon", "connector_icon"]}
renderOrder={["connector_icon", "default_icon"]}
itemIcon={document?.source?.icon}
className="w-4 h-4 mr-1"
/>

View File

@@ -10,6 +10,7 @@ import { ArrowBigRight } from "lucide-react";
import { isNil } from "lodash-es";
import { useDebounceFn, useUnmount } from "ahooks";
import { useTranslation } from "react-i18next";
import clsx from "clsx";
import { useSearchStore } from "@/stores/searchStore";
import ThemedIcon from "@/components/Common/Icons/ThemedIcon";
@@ -23,7 +24,9 @@ import Calculator from "./Calculator";
import { useShortcutsStore } from "@/stores/shortcutsStore";
import ErrorSearch from "@/components/Common/ErrorNotification/ErrorSearch";
// import AiSummary from "./AiSummary";
import clsx from "clsx";
import source_default_img from "@/assets/images/source_default.png";
import source_default_dark_img from "@/assets/images/source_default_dark.png";
import { useThemeStore } from "@/stores/themeStore";
type ISearchData = Record<string, any[]>;
@@ -47,6 +50,7 @@ function DropdownList({
const globalItemIndexMap: any[] = [];
const setSourceData = useSearchStore((state) => state.setSourceData);
const isDark = useThemeStore((state) => state.isDark);
const [selectedItem, setSelectedItem] = useState<number | null>(null);
const [selectedName, setSelectedName] = useState<string>("");
@@ -235,8 +239,9 @@ function DropdownList({
<div className="p-2 text-xs text-[#999] dark:text-[#666] flex items-center gap-2.5 relative">
<CommonIcon
item={items[0]?.document}
renderOrder={["item_icon", "connector_icon"]}
renderOrder={["connector_icon", "default_icon"]}
itemIcon={items[0]?.document?.source?.icon}
defaultIcon={isDark ? source_default_dark_img : source_default_img}
className="w-4 h-4"
/>
{sourceName} - {items[0]?.source.name}

View File

@@ -28,7 +28,7 @@ export function RichCategories({
<div className="flex items-center justify-end max-w-[calc(100%-20px)] whitespace-nowrap">
<CommonIcon
item={item}
renderOrder={["item_icon", "connector_icon"]}
renderOrder={["connector_icon", "default_icon"]}
itemIcon={item?.rich_categories?.[0]?.icon}
className={`w-4 h-4 mr-2 cursor-pointer`}
onClick={(e) => {
@@ -72,7 +72,7 @@ export function RichCategories({
>
<CommonIcon
item={item}
renderOrder={["item_icon", "connector_icon"]}
renderOrder={["connector_icon", "default_icon"]}
itemIcon={item?.source?.icon}
className="w-4 h-4 cursor-pointer"
onClick={(e) => {

View File

@@ -287,7 +287,7 @@ export default function MCPPopover({
) : (
<CommonIcon
item={item}
renderOrder={["item_icon", "connector_icon"]}
renderOrder={["item_icon", "connector_icon", "default_icon"]}
itemIcon={item.icon}
className="size-4"
/>

View File

@@ -59,7 +59,7 @@ const SearchListItem: React.FC<SearchListItemProps> = React.memo(
} min-w-0 flex gap-2 items-center justify-start `}
>
<CommonIcon
renderOrder={["item_icon", "connector_icon", "default_icon"]}
renderOrder={["special_icon", "item_icon", "connector_icon", "default_icon"]}
item={item}
itemIcon={item?.icon}
defaultIcon={File}

View File

@@ -292,7 +292,7 @@ export default function SearchPopover({
) : (
<CommonIcon
item={item}
renderOrder={["item_icon", "connector_icon"]}
renderOrder={["item_icon", "connector_icon", "default_icon"]}
itemIcon={item.icon}
className="size-4"
/>

View File

@@ -4,10 +4,10 @@ import { useAppStore } from "@/stores/appStore";
export function useFindConnectorIcon(item: any) {
const connector_data = useConnectStore((state) => state.connector_data);
const datasourceData = useConnectStore((state) => state.datasourceData);
const currentService = useConnectStore((state) => state.currentService);
const isTauri = useAppStore((state) => state.isTauri);
let currentServiceId = currentService?.id;
let currentServiceId = item?.querySource?.id;
if (!isTauri) {
currentServiceId = "web"
}