5 Commits

Author SHA1 Message Date
ayang
e7f8f7ef6e chore: add macos config for tauri 2025-07-28 16:31:54 +08:00
ayangweb
4709f8c660 feat: enhance ui for skipped version (#834) 2025-07-28 11:43:10 +08:00
SteveLauC
4696aa1759 test: test extract_build_number() (#835)
This commit adds a test for extract_build_number(), which I forgot to do
in commit 067fb7144f6[1].

[1]: 067fb7144f
2025-07-28 11:42:50 +08:00
ayangweb
924fc09516 fix: fix issue with update check failure (#833)
* fix: fix issue with update check failure

* docs: update changelog
2025-07-28 10:06:07 +08:00
SteveLauC
5a700662dd chore: release notes for 0.7.1 (#832) 2025-07-28 10:00:12 +08:00
7 changed files with 79 additions and 32 deletions

View File

@@ -5,7 +5,7 @@ title: "Release Notes"
# Release Notes # Release Notes
Information about release notes of Coco Server is provided here. Information about release notes of Coco App is provided here.
## Latest (In development) ## Latest (In development)
@@ -13,6 +13,20 @@ Information about release notes of Coco Server is provided here.
### 🚀 Features ### 🚀 Features
- feat: enhance ui for skipped version #834
### 🐛 Bug fix
- fix: fix issue with update check failure #833
### ✈️ Improvements
## 0.7.1 (2025-07-27)
### ❌ Breaking changes
### 🚀 Features
### 🐛 Bug fix ### 🐛 Bug fix
- fix: correct enter key behavior #828 - fix: correct enter key behavior #828
@@ -20,6 +34,7 @@ Information about release notes of Coco Server is provided here.
### ✈️ Improvements ### ✈️ Improvements
- chore: web component add notification component #825 - chore: web component add notification component #825
- refactor: collection behavior defaults to `MoveToActiveSpace`, and only use `CanJoinAllSpaces` when window is pinned #829
## 0.7.0 (2025-07-25) ## 0.7.0 (2025-07-25)

View File

@@ -5,7 +5,7 @@ use tauri_plugin_updater::RemoteRelease;
/// ///
/// If the version string is in the `x.y.z` format and does not include a build /// If the version string is in the `x.y.z` format and does not include a build
/// number, we assume a build number of 0. /// number, we assume a build number of 0.
fn extract_version_number(version: &Version) -> u32 { fn extract_build_number(version: &Version) -> u32 {
let pre = &version.pre; let pre = &version.pre;
if pre.is_empty() { if pre.is_empty() {
@@ -52,8 +52,8 @@ fn extract_version_number(version: &Version) -> u32 {
pub(crate) fn custom_version_comparator(local: Version, remote_release: RemoteRelease) -> bool { pub(crate) fn custom_version_comparator(local: Version, remote_release: RemoteRelease) -> bool {
let remote = remote_release.version; let remote = remote_release.version;
let local_build_number = extract_version_number(&local); let local_build_number = extract_build_number(&local);
let remote_build_number = extract_version_number(&remote); let remote_build_number = extract_build_number(&remote);
let should_update = remote_build_number > local_build_number; let should_update = remote_build_number > local_build_number;
log::debug!( log::debug!(
@@ -65,3 +65,23 @@ pub(crate) fn custom_version_comparator(local: Version, remote_release: RemoteRe
should_update should_update
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_extract_build_number() {
// 0.6.0 => 0
let version = Version::parse("0.6.0").unwrap();
assert_eq!(extract_build_number(&version), 0);
// 0.6.0-2371 => 2371
let version = Version::parse("0.6.0-2371").unwrap();
assert_eq!(extract_build_number(&version), 2371);
// 0.6.0-SNAPSHOT-2371 => 2371
let version = Version::parse("0.6.0-SNAPSHOT-2371").unwrap();
assert_eq!(extract_build_number(&version), 2371);
}
}

View File

@@ -1,4 +1,5 @@
{ {
"identifier": "rs.coco.app",
"bundle": { "bundle": {
"macOS": { "macOS": {
"entitlements": "./Entitlements.plist", "entitlements": "./Entitlements.plist",
@@ -7,4 +8,4 @@
} }
} }
} }
} }

View File

@@ -0,0 +1,8 @@
{
"identifier": "rs.coco.app",
"bundle": {
"macOS": {
"entitlements": "./Entitlements.plist"
}
}
}

View File

@@ -1,4 +1,4 @@
import { useCallback } from "react"; import { useCallback, useMemo } from "react";
import { ArrowDown01, CornerDownLeft } from "lucide-react"; import { ArrowDown01, CornerDownLeft } from "lucide-react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import clsx from "clsx"; import clsx from "clsx";
@@ -44,7 +44,7 @@ export default function Footer({ setIsPinnedWeb }: FooterProps) {
onPinChange: setIsPinnedWeb, onPinChange: setIsPinnedWeb,
}); });
const { setVisible, updateInfo } = useUpdateStore(); const { setVisible, updateInfo, skipVersions } = useUpdateStore();
const { fixedWindow, modifierKey } = useShortcutsStore(); const { fixedWindow, modifierKey } = useShortcutsStore();
@@ -52,6 +52,10 @@ export default function Footer({ setIsPinnedWeb }: FooterProps) {
return platformAdapter.emitEvent("open_settings", ""); return platformAdapter.emitEvent("open_settings", "");
}, []); }, []);
const hasUpdate = useMemo(() => {
return updateInfo && !skipVersions.includes(updateInfo.version);
}, [updateInfo, skipVersions]);
const renderLeft = () => { const renderLeft = () => {
if (sourceData?.source?.name) { if (sourceData?.source?.name) {
return ( return (
@@ -97,7 +101,7 @@ export default function Footer({ setIsPinnedWeb }: FooterProps) {
/> />
<div className="relative text-xs text-gray-500 dark:text-gray-400"> <div className="relative text-xs text-gray-500 dark:text-gray-400">
{updateInfo?.available ? ( {hasUpdate ? (
<div className="cursor-pointer" onClick={() => setVisible(true)}> <div className="cursor-pointer" onClick={() => setVisible(true)}>
<span>{t("search.footer.updateAvailable")}</span> <span>{t("search.footer.updateAvailable")}</span>
<span className="absolute top-0 -right-2 size-1.5 bg-[#FF3434] rounded-full"></span> <span className="absolute top-0 -right-2 size-1.5 bg-[#FF3434] rounded-full"></span>
@@ -127,7 +131,7 @@ export default function Footer({ setIsPinnedWeb }: FooterProps) {
onClick={togglePin} onClick={togglePin}
className={clsx({ className={clsx({
"text-blue-500": isPinned, "text-blue-500": isPinned,
"pl-2": updateInfo?.available, "pl-2": hasUpdate,
})} })}
> >
<VisibleKey shortcut={fixedWindow} onKeyPress={togglePin}> <VisibleKey shortcut={fixedWindow} onKeyPress={togglePin}>

View File

@@ -32,8 +32,8 @@ const UpdateApp = ({ isCheckPage }: UpdateAppProps) => {
const { const {
visible, visible,
setVisible, setVisible,
skipVersion, skipVersions,
setSkipVersion, setSkipVersions,
isOptional, isOptional,
updateInfo, updateInfo,
setUpdateInfo, setUpdateInfo,
@@ -50,11 +50,7 @@ const UpdateApp = ({ isCheckPage }: UpdateAppProps) => {
}, []); }, []);
useEffect(() => { useEffect(() => {
if (!snapshotUpdate) return; checkUpdateStatus();
checkUpdate().catch((error) => {
addError("Update failed:" + error, "error");
});
}, [snapshotUpdate]); }, [snapshotUpdate]);
useEffect(() => { useEffect(() => {
@@ -79,13 +75,13 @@ const UpdateApp = ({ isCheckPage }: UpdateAppProps) => {
const update = await checkUpdate(); const update = await checkUpdate();
if (update) { if (update) {
const { skipVersions } = useUpdateStore.getState();
setVisible(!skipVersions.includes(update.version));
setUpdateInfo(update); setUpdateInfo(update);
if (skipVersion === update.version) return;
setVisible(true);
} }
}, [skipVersion]); }, [skipVersions]);
const cursorClassName = useMemo(() => { const cursorClassName = useMemo(() => {
return state.loading ? "cursor-not-allowed" : "cursor-pointer"; return state.loading ? "cursor-not-allowed" : "cursor-pointer";
@@ -133,7 +129,9 @@ const UpdateApp = ({ isCheckPage }: UpdateAppProps) => {
const handleSkip = () => { const handleSkip = () => {
if (state.loading) return; if (state.loading) return;
setSkipVersion(updateInfo?.version); const { skipVersions, updateInfo } = useUpdateStore.getState();
setSkipVersions([...skipVersions, updateInfo.version]);
isCheckPage ? hide_check() : setVisible(false); isCheckPage ? hide_check() : setVisible(false);
}; };
@@ -182,7 +180,7 @@ const UpdateApp = ({ isCheckPage }: UpdateAppProps) => {
<img src={isDark ? darkIcon : lightIcon} className="h-6" /> <img src={isDark ? darkIcon : lightIcon} className="h-6" />
<div className="text-[#333] text-sm leading-5 py-2 dark:text-[#D8D8D8] text-center"> <div className="text-[#333] text-sm leading-5 py-2 dark:text-[#D8D8D8] text-center">
{updateInfo?.available ? ( {updateInfo ? (
isOptional ? ( isOptional ? (
t("update.optional_description") t("update.optional_description")
) : ( ) : (
@@ -196,7 +194,7 @@ const UpdateApp = ({ isCheckPage }: UpdateAppProps) => {
)} )}
</div> </div>
{updateInfo?.available ? ( {updateInfo ? (
<div <div
className="text-xs text-[#0072FF] cursor-pointer" className="text-xs text-[#0072FF] cursor-pointer"
onClick={() => onClick={() =>
@@ -223,21 +221,21 @@ const UpdateApp = ({ isCheckPage }: UpdateAppProps) => {
cursorClassName, cursorClassName,
state.loading && "opacity-50" state.loading && "opacity-50"
)} )}
onClick={updateInfo?.available ? handleDownload : handleSkip} onClick={updateInfo ? handleDownload : handleSkip}
> >
{state.loading ? ( {state.loading ? (
<div className="flex justify-center items-center gap-2"> <div className="flex justify-center items-center gap-2">
<LoaderCircle className="animate-spin size-5" /> <LoaderCircle className="animate-spin size-5" />
{percent}% {percent}%
</div> </div>
) : updateInfo?.available ? ( ) : updateInfo ? (
t("update.button.install") t("update.button.install")
) : ( ) : (
t("update.button.ok") t("update.button.ok")
)} )}
</Button> </Button>
{updateInfo?.available && isOptional && ( {!isCheckPage && updateInfo && isOptional && (
<div <div
className={clsx("text-xs text-[#999]", cursorClassName)} className={clsx("text-xs text-[#999]", cursorClassName)}
onClick={handleSkip} onClick={handleSkip}

View File

@@ -4,8 +4,8 @@ import { persist } from "zustand/middleware";
export type IUpdateStore = { export type IUpdateStore = {
visible: boolean; visible: boolean;
setVisible: (visible: boolean) => void; setVisible: (visible: boolean) => void;
skipVersion?: string; skipVersions: string[];
setSkipVersion: (skipVersion?: string) => void; setSkipVersions: (skipVersions: string[]) => void;
isOptional: boolean; isOptional: boolean;
setIsOptional: (isOptional: boolean) => void; setIsOptional: (isOptional: boolean) => void;
updateInfo?: any; updateInfo?: any;
@@ -19,8 +19,9 @@ export const useUpdateStore = create<IUpdateStore>()(
setVisible: (visible: boolean) => { setVisible: (visible: boolean) => {
return set({ visible }); return set({ visible });
}, },
setSkipVersion: (skipVersion?: string) => { skipVersions: [],
return set({ skipVersion }); setSkipVersions: (skipVersions: string[]) => {
return set({ skipVersions });
}, },
isOptional: true, isOptional: true,
setIsOptional: (isOptional: boolean) => { setIsOptional: (isOptional: boolean) => {
@@ -33,7 +34,7 @@ export const useUpdateStore = create<IUpdateStore>()(
{ {
name: "update-store", name: "update-store",
partialize: (state) => ({ partialize: (state) => ({
skipVersion: state.skipVersion, skipVersions: state.skipVersions,
}), }),
} }
) )