Skip to content

Commit f9c1be8

Browse files
authored
fix: app icon & category icon (#529)
1 parent 71ce23e commit f9c1be8

11 files changed

Lines changed: 129 additions & 86 deletions

File tree

docs/content.en/docs/release-notes/_index.md

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,7 @@ title: "Release Notes"
77

88
Information about release notes of Coco Server is provided here.
99

10-
## Latest (In development)
11-
12-
### ❌ Breaking changes
13-
14-
### 🚀 Features
15-
16-
### 🐛 Bug fix
17-
18-
### ✈️ Improvements
19-
20-
## 0.5.0 (2025-05-17)
10+
## 0.5.0 (In development)
2111

2212
### ❌ Breaking changes
2313

@@ -62,6 +52,7 @@ Information about release notes of Coco Server is provided here.
6252
- refactor: optimizing list styles in markdown content #520
6353
- feat: add a component for text reading aloud #522
6454
- style: history component styles #528
55+
- fix: app icon & category icon #529
6556

6657
## 0.4.0 (2025-04-27)
6758

src/components/Common/Icons/CommonIcon.tsx

Lines changed: 36 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
import { useState } from "react";
22
import { isEmpty } from "lodash-es";
33
import { useAsyncEffect } from "ahooks";
4+
import { Box } from "lucide-react";
45

56
import platformAdapter from "@/utils/platformAdapter";
6-
import UniversalIcon from "./UniversalIcon";
7+
import UniversalIcon, { getIconType } from "./UniversalIcon";
78
import { useFindConnectorIcon } from "@/hooks/useFindConnectorIcon";
89

910
interface CommonIconProps {
1011
renderOrder: string[];
1112
item: any;
1213
itemIcon?: string;
13-
defaultIcon?: React.FC;
14+
defaultIcon?: React.FC | string;
1415
className?: string;
1516
onClick?: (e: React.MouseEvent) => void;
1617
}
@@ -26,6 +27,9 @@ function CommonIcon({
2627
const connectorSource = useFindConnectorIcon(item);
2728

2829
const [isAbsolute, setIsAbsolute] = useState<boolean>();
30+
const [defaultIconState, setDefaultIconState] = useState<
31+
React.FC | string | undefined
32+
>(defaultIcon);
2933

3034
useAsyncEffect(async () => {
3135
if (isEmpty(item)) return;
@@ -35,40 +39,40 @@ function CommonIcon({
3539
omitSize: true,
3640
});
3741
setIsAbsolute(Boolean(isAbsolute));
42+
setDefaultIconState(defaultIcon || Box);
3843
} catch (error) {
3944
setIsAbsolute(false);
4045
}
4146
}, [item]);
4247

43-
// Handle special icon types
44-
const renderSpecialIcon = () => {
45-
if (item.id === "Calculator") {
46-
return (
47-
<UniversalIcon
48-
icon="/assets/calculator.png"
49-
className={className}
50-
onClick={onClick}
51-
/>
52-
);
53-
}
54-
55-
if (isAbsolute) {
56-
return (
57-
<UniversalIcon
58-
icon={platformAdapter.convertFileSrc(item?.icon)}
59-
className={className}
60-
onClick={onClick}
61-
/>
62-
);
63-
}
64-
65-
return null;
66-
};
67-
6848
// Handle regular icon types
6949
const renderIconByType = (renderType: string) => {
7050
switch (renderType) {
51+
case "special_icon": {
52+
if (item.id === "Calculator") {
53+
return (
54+
<UniversalIcon
55+
icon="/assets/calculator.png"
56+
className={className}
57+
onClick={onClick}
58+
/>
59+
);
60+
}
61+
62+
if (isAbsolute) {
63+
return (
64+
<UniversalIcon
65+
icon={item?.icon}
66+
appIcon={true}
67+
className={className}
68+
onClick={onClick}
69+
/>
70+
);
71+
}
72+
return null;
73+
}
7174
case "item_icon":
75+
if (getIconType(itemIcon) === "default") return null;
7276
return (
7377
<UniversalIcon
7478
icon={itemIcon}
@@ -78,7 +82,8 @@ function CommonIcon({
7882
);
7983
case "connector_icon": {
8084
const icons = connectorSource?.assets?.icons || {};
81-
const selectedIcon = itemIcon && icons[itemIcon];
85+
const selectedIcon = (itemIcon && icons[itemIcon]) || itemIcon;
86+
if (!selectedIcon) return null;
8287
return (
8388
<UniversalIcon
8489
icon={selectedIcon}
@@ -90,7 +95,7 @@ function CommonIcon({
9095
case "default_icon":
9196
return (
9297
<UniversalIcon
93-
defaultIcon={defaultIcon}
98+
defaultIcon={defaultIconState}
9499
className={className}
95100
onClick={onClick}
96101
/>
@@ -100,13 +105,10 @@ function CommonIcon({
100105
}
101106
};
102107

103-
// Render logic
104-
const specialIcon = renderSpecialIcon();
105-
if (specialIcon) return specialIcon;
106-
107108
for (const renderType of renderOrder) {
108109
const icon = renderIconByType(renderType);
109-
if (icon) return icon;
110+
if (!icon) continue;
111+
return icon;
110112
}
111113

112114
return null;
Lines changed: 69 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,116 @@
11
import React from "react";
2-
import { Box } from "lucide-react";
2+
import { File } from "lucide-react";
33

44
import IconWrapper from "./IconWrapper";
55
import ThemedIcon from "./ThemedIcon";
66
import FontIcon from "./FontIcon";
77
import { useAppStore } from "@/stores/appStore";
8+
import platformAdapter from "@/utils/platformAdapter";
89

910
interface UniversalIconProps {
10-
icon?: string; // Icon source
11-
defaultIcon?: React.FC; // Default icon component
12-
className?: string; // Style class name
11+
icon?: string; // Icon source
12+
defaultIcon?: React.FC | string; // Default icon component
13+
appIcon?: boolean; // Whether the icon is local
14+
className?: string; // Style class name
1315
onClick?: React.MouseEventHandler<HTMLDivElement>;
1416
wrapWithIconWrapper?: boolean; // Whether to wrap with IconWrapper
1517
}
1618

17-
type IconType = 'url' | 'base64' | 'font' | 'local' | 'splice' | 'default';
19+
type IconType =
20+
| "url"
21+
| "base64"
22+
| "font"
23+
| "local"
24+
| "app"
25+
| "splice"
26+
| "default";
27+
28+
// Determine icon type
29+
export const getIconType = (icon?: string, appIcon?: boolean): IconType => {
30+
if (!icon) return "default";
31+
if (appIcon) return "app";
32+
if (icon.startsWith("http://") || icon.startsWith("https://")) return "url";
33+
if (icon.startsWith("data:image/")) return "base64";
34+
if (icon.startsWith("font_")) return "font";
35+
if (icon.startsWith("/assets")) return "local";
36+
if (icon.startsWith("/")) return "splice";
37+
return "default";
38+
};
1839

1940
function UniversalIcon({
2041
icon,
21-
defaultIcon = Box,
42+
defaultIcon = File,
43+
appIcon = false,
2244
className = "w-5 h-5 flex-shrink-0",
2345
onClick = () => {},
2446
wrapWithIconWrapper = true,
2547
}: UniversalIconProps) {
2648
const endpoint_http = useAppStore((state) => state.endpoint_http);
2749

28-
// Determine icon type
29-
const getIconType = (icon?: string): IconType => {
30-
if (!icon) return 'default';
31-
if (icon.startsWith('http://') || icon.startsWith('https://')) return 'url';
32-
if (icon.startsWith('data:image/')) return 'base64';
33-
if (icon.startsWith('font_')) return 'font';
34-
if (icon.startsWith('/assets')) return 'local';
35-
if (icon.startsWith('/')) return 'splice';
36-
return 'default';
37-
};
38-
3950
// Render image type icon
4051
const renderImageIcon = (src: string) => {
4152
const img = <img className={className} src={src} alt="icon" />;
4253
return wrapWithIconWrapper ? (
4354
<IconWrapper className={className} onClick={onClick}>
4455
{img}
4556
</IconWrapper>
46-
) : img;
57+
) : (
58+
img
59+
);
60+
};
61+
62+
// Render app type icon
63+
const renderAppIcon = (src: string) => {
64+
const img = (
65+
<img
66+
className={className}
67+
src={platformAdapter.convertFileSrc(src)}
68+
alt="icon"
69+
/>
70+
);
71+
return wrapWithIconWrapper ? (
72+
<IconWrapper className={className} onClick={onClick}>
73+
{img}
74+
</IconWrapper>
75+
) : (
76+
img
77+
);
4778
};
4879

4980
// Render default icon
5081
const renderDefaultIcon = () => {
51-
const defaultComponent = <ThemedIcon component={defaultIcon} className={className} />;
82+
if (typeof defaultIcon === "string") {
83+
return renderImageIcon(defaultIcon);
84+
}
85+
const defaultComponent = (
86+
<ThemedIcon component={defaultIcon} className={className} />
87+
);
5288
return wrapWithIconWrapper ? (
5389
<IconWrapper className={className} onClick={onClick}>
5490
{defaultComponent}
5591
</IconWrapper>
56-
) : defaultComponent;
92+
) : (
93+
defaultComponent
94+
);
5795
};
5896

5997
// Render component based on icon type
60-
const iconType = getIconType(icon);
98+
const iconType = getIconType(icon, appIcon);
6199
switch (iconType) {
62-
case 'url':
63-
case 'base64':
64-
case 'local':
65-
return renderImageIcon(icon!);
66-
case 'splice':
67-
const url = `${endpoint_http}${icon!}`
100+
case "url":
101+
case "base64":
102+
case "local":
103+
return renderImageIcon(icon!);
104+
case "app":
105+
return renderAppIcon(icon!);
106+
case "splice":
107+
const url = `${endpoint_http}${icon!}`;
68108
return renderImageIcon(url);
69-
case 'font':
109+
case "font":
70110
return <FontIcon name={icon!} className={className} />;
71111
default:
72112
return renderDefaultIcon();
73113
}
74114
}
75115

76-
export default UniversalIcon;
116+
export default UniversalIcon;

src/components/Common/UI/Footer.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ import { useUpdateStore } from "@/stores/updateStore";
1313
import VisibleKey from "../VisibleKey";
1414
import { useShortcutsStore } from "@/stores/shortcutsStore";
1515
import { formatKey } from "@/utils/keyboardUtils";
16+
import source_default_img from "@/assets/images/source_default.png";
17+
import source_default_dark_img from "@/assets/images/source_default_dark.png";
18+
import { useThemeStore } from "@/stores/themeStore";
1619

1720
interface FooterProps {
1821
isTauri: boolean;
@@ -27,6 +30,7 @@ export default function Footer({
2730
}: FooterProps) {
2831
const { t } = useTranslation();
2932
const sourceData = useSearchStore((state) => state.sourceData);
33+
const isDark = useThemeStore((state) => state.isDark);
3034

3135
const isPinned = useAppStore((state) => state.isPinned);
3236
const setIsPinned = useAppStore((state) => state.setIsPinned);
@@ -59,8 +63,9 @@ export default function Footer({
5963
{sourceData?.source?.name ? (
6064
<CommonIcon
6165
item={sourceData}
62-
renderOrder={["item_icon", "connector_icon"]}
66+
renderOrder={["connector_icon", "default_icon"]}
6367
itemIcon={sourceData?.source?.icon}
68+
defaultIcon={isDark ? source_default_dark_img : source_default_img}
6469
className="w-4 h-4"
6570
/>
6671
) : (

src/components/Search/DocumentDetail.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export const DocumentDetail: React.FC<DocumentDetailProps> = ({ document }) => {
6666
/>
6767
) : (
6868
<CommonIcon
69-
renderOrder={["item_icon", "connector_icon", "default_icon"]}
69+
renderOrder={["special_icon", "item_icon", "connector_icon", "default_icon"]}
7070
item={document}
7171
itemIcon={document?.icon}
7272
defaultIcon={File}
@@ -102,7 +102,7 @@ export const DocumentDetail: React.FC<DocumentDetailProps> = ({ document }) => {
102102
icon={
103103
<CommonIcon
104104
item={document}
105-
renderOrder={["item_icon", "connector_icon"]}
105+
renderOrder={["connector_icon", "default_icon"]}
106106
itemIcon={document?.source?.icon}
107107
className="w-4 h-4 mr-1"
108108
/>

src/components/Search/DropdownList.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { ArrowBigRight } from "lucide-react";
1010
import { isNil } from "lodash-es";
1111
import { useDebounceFn, useUnmount } from "ahooks";
1212
import { useTranslation } from "react-i18next";
13+
import clsx from "clsx";
1314

1415
import { useSearchStore } from "@/stores/searchStore";
1516
import ThemedIcon from "@/components/Common/Icons/ThemedIcon";
@@ -23,7 +24,9 @@ import Calculator from "./Calculator";
2324
import { useShortcutsStore } from "@/stores/shortcutsStore";
2425
import ErrorSearch from "@/components/Common/ErrorNotification/ErrorSearch";
2526
// import AiSummary from "./AiSummary";
26-
import clsx from "clsx";
27+
import source_default_img from "@/assets/images/source_default.png";
28+
import source_default_dark_img from "@/assets/images/source_default_dark.png";
29+
import { useThemeStore } from "@/stores/themeStore";
2730

2831
type ISearchData = Record<string, any[]>;
2932

@@ -47,6 +50,7 @@ function DropdownList({
4750
const globalItemIndexMap: any[] = [];
4851

4952
const setSourceData = useSearchStore((state) => state.setSourceData);
53+
const isDark = useThemeStore((state) => state.isDark);
5054

5155
const [selectedItem, setSelectedItem] = useState<number | null>(null);
5256
const [selectedName, setSelectedName] = useState<string>("");
@@ -235,8 +239,9 @@ function DropdownList({
235239
<div className="p-2 text-xs text-[#999] dark:text-[#666] flex items-center gap-2.5 relative">
236240
<CommonIcon
237241
item={items[0]?.document}
238-
renderOrder={["item_icon", "connector_icon"]}
242+
renderOrder={["connector_icon", "default_icon"]}
239243
itemIcon={items[0]?.document?.source?.icon}
244+
defaultIcon={isDark ? source_default_dark_img : source_default_img}
240245
className="w-4 h-4"
241246
/>
242247
{sourceName} - {items[0]?.source.name}

0 commit comments

Comments
 (0)