mirror of
https://github.com/infinilabs/coco-app.git
synced 2026-08-29 10:09:30 +02:00
fix: app icon & category icon (#529)
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
) : (
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user