feat: support opening logs from about page (#915)

* feat: support opening logs from about page

* docs: update changelog

* refactor: update

* refactor: update i18n
This commit is contained in:
ayangweb
2025-10-10 15:16:46 +08:00
committed by GitHub
parent 6523fef12b
commit a9a4b5319c
5 changed files with 145 additions and 40 deletions

View File

@@ -14,6 +14,7 @@ Information about release notes of Coco App is provided here.
### 🚀 Features
feat: support switching groups via keyboard shortcuts #911
feat: support opening logs from about page #915
### 🐛 Bug fix

View File

@@ -1,55 +1,135 @@
import { Globe, Github } from "lucide-react";
import {
Globe,
Github,
Rocket,
BookOpen,
MessageCircleReply,
ScrollText,
SquareArrowOutUpRight,
} from "lucide-react";
import { useTranslation } from "react-i18next";
import { OpenURLWithBrowser } from "@/utils";
import logoLight from "@/assets/images/logo-text-light.svg";
import logoDark from "@/assets/images/logo-text-dark.svg";
import lightLogo from "@/assets/images/logo-text-light.svg";
import darkLogo from "@/assets/images/logo-text-dark.svg";
import { useThemeStore } from "@/stores/themeStore";
import { cloneElement, ReactElement, useMemo } from "react";
import platformAdapter from "@/utils/platformAdapter";
interface Link {
icon: ReactElement;
label: string;
url?: string;
onPress?: () => void;
}
export default function AboutView() {
const { t } = useTranslation();
const isDark = useThemeStore((state) => state.isDark);
const { isDark } = useThemeStore();
const logo = isDark ? logoDark : logoLight;
const links = useMemo<Link[]>(() => {
return [
{
icon: <Rocket />,
label: t("settings.about.labels.changelog"),
url: "https://coco.rs/en/roadmap",
},
{
icon: <BookOpen />,
label: t("settings.about.labels.docs"),
url: "https://docs.infinilabs.com/coco-app/main",
},
{
icon: <Github />,
label: "GitHub",
url: "https://github.com/infinilabs/coco-app",
},
{
icon: <Globe />,
label: t("settings.about.labels.officialWebsite"),
url: "https://coco.rs",
},
{
icon: <MessageCircleReply />,
label: t("settings.about.labels.submitFeedback"),
url: "https://github.com/infinilabs/coco-app/issues",
},
{
icon: <ScrollText />,
label: t("settings.about.labels.runningLog"),
onPress: platformAdapter.openLogDir,
},
];
}, [t]);
const handleClick = (link: Link) => {
const { url, onPress } = link;
if (url) {
return OpenURLWithBrowser(url);
}
onPress?.();
};
return (
<div className="flex justify-center items-center flex-col h-[calc(100vh-170px)]">
<div>
<div className="flex h-[calc(100vh-170px)]">
<div className="flex flex-col items-center justify-center w-[70%] pr-10 text-[#999] text-sm">
<img
src={logo}
className="w-48 dark:text-white"
src={isDark ? darkLogo : lightLogo}
className="h-14"
alt={t("settings.about.logo")}
/>
<div className="mt-4 text-base font-medium text-[#333] dark:text-white/80">
{t("settings.about.slogan")}
</div>
<div className="mt-10">
{t("settings.about.version", {
version: process.env.VERSION || "N/A",
})}
</div>
<div className="mt-3">
{t("settings.about.copyright", { year: new Date().getFullYear() })}
</div>
</div>
<div className="mt-8 font-medium text-gray-900 dark:text-gray-100">
{t("settings.about.slogan")}
</div>
<div className="flex justify-center items-center mt-10">
<button
onClick={() => OpenURLWithBrowser("https://coco.rs")}
className="w-6 h-6 mr-2.5 flex justify-center rounded-[6px] border-[1px] gray-200 dark:border-gray-700"
aria-label={t("settings.about.website")}
>
<Globe className="w-3 text-blue-500" />
</button>
<button
onClick={() =>
OpenURLWithBrowser("https://github.com/infinilabs/coco-app")
}
className="w-6 h-6 flex justify-center rounded-[6px] border-[1px] gray-200 dark:border-gray-700"
aria-label={t("settings.about.github")}
>
<Github className="w-3 text-blue-500" />
</button>
</div>
<div className="mt-8 text-sm text-gray-500 dark:text-gray-400">
{t("settings.about.version", {
version: process.env.VERSION || "N/A",
<div className="flex-1 flex flex-col items-center justify-center gap-4 pl-10 border-l border-[#e5e5e5] dark:border-[#4e4e56]">
{links.map((item) => {
const { icon, label } = item;
return (
<div
key={label}
className="flex items-center justify-between w-full"
>
<div className="flex items-center gap-2">
{cloneElement(icon, {
className: "size-4 text-[#999]",
})}
<span
className="text-[#333] dark:text-white/80 cursor-pointer hover:text-[#027FFE] transition"
onClick={() => {
handleClick(item);
}}
>
{label}
</span>
</div>
<SquareArrowOutUpRight
className="text-[#027FFE] size-4 cursor-pointer"
onClick={() => {
handleClick(item);
}}
/>
</div>
);
})}
</div>
<div className="mt-4 text-sm text-gray-500 dark:text-gray-400">
{t("settings.about.copyright", { year: new Date().getFullYear() })}
</div>
</div>
);
}

View File

@@ -39,7 +39,14 @@
"website": "Visit Website",
"github": "Visit GitHub",
"version": "Version {{version}}",
"copyright": "©{{year}} INFINI Labs, All Rights Reserved."
"copyright": "©{{year}} INFINI Labs, All Rights Reserved.",
"labels": {
"changelog": "Whats New",
"docs": "Docs",
"officialWebsite": "Official Website",
"submitFeedback": "Submit Feedback",
"runningLog": "View App Logs"
}
},
"advanced": {
"startup": {

View File

@@ -39,7 +39,14 @@
"website": "访问官网",
"github": "访问 GitHub",
"version": "版本 {{version}}",
"copyright": "©{{year}} INFINI Labs保留所有权利。"
"copyright": "©{{year}} INFINI Labs保留所有权利。",
"labels": {
"changelog": "更新日志",
"docs": "帮助文档",
"officialWebsite": "官方网站",
"submitFeedback": "提交反馈",
"runningLog": "运行日志"
}
},
"advanced": {
"startup": {

View File

@@ -24,6 +24,7 @@ export interface TauriPlatformAdapter extends BasePlatformAdapter {
) => Promise<string | string[] | null>;
metadata: typeof metadata;
error: typeof error;
openLogDir: () => Promise<void>;
}
// Create Tauri adapter functions
@@ -277,14 +278,14 @@ export const createTauriAdapter = (): TauriPlatformAdapter => {
return textarea.dispatchEvent(event);
}
// View extension should be handled separately as it needs frontend to open
// View extension should be handled separately as it needs frontend to open
// a page
const onOpened = data?.on_opened;
if (onOpened?.Extension?.ty?.View) {
const { setViewExtensionOpened } = useSearchStore.getState();
const viewData = onOpened.Extension.ty.View;
const extensionPermission = onOpened.Extension.permission;
setViewExtensionOpened([viewData.page, extensionPermission]);
return;
}
@@ -352,5 +353,14 @@ export const createTauriAdapter = (): TauriPlatformAdapter => {
const window = await windowWrapper.getWebviewWindow();
return window.label;
},
async openLogDir() {
const { appLogDir } = await import("@tauri-apps/api/path");
const { revealItemInDir } = await import("@tauri-apps/plugin-opener");
const logDir = await appLogDir();
revealItemInDir(logDir);
},
};
};