diff --git a/apps/electron/layers/main/src/application-menu.ts b/apps/electron/layers/main/src/application-menu.ts index 935f34c791..e0ad8dada8 100644 --- a/apps/electron/layers/main/src/application-menu.ts +++ b/apps/electron/layers/main/src/application-menu.ts @@ -2,6 +2,7 @@ import { app, Menu } from 'electron'; import { isMacOS } from '../../utils'; import { subjects } from './events'; +import { checkForUpdatesAndNotify } from './handlers/updater'; // Unique id for menuitems const MENUITEM_NEW_PAGE = 'affine:new-page'; @@ -114,6 +115,12 @@ export function createApplicationMenu() { await shell.openExternal('https://affine.pro/'); }, }, + { + label: 'Check for Updates', + click: async () => { + await checkForUpdatesAndNotify(true); + }, + }, ], }, ]; diff --git a/apps/electron/layers/main/src/events/updater.ts b/apps/electron/layers/main/src/events/updater.ts index 87233cdc7e..b526067309 100644 --- a/apps/electron/layers/main/src/events/updater.ts +++ b/apps/electron/layers/main/src/events/updater.ts @@ -1,19 +1,34 @@ -import { Subject } from 'rxjs'; +import { BehaviorSubject, Subject } from 'rxjs'; import type { MainEventListener } from './type'; interface UpdateMeta { version: string; + allowAutoUpdate: boolean; } export const updaterSubjects = { // means it is ready for restart and install the new version - clientUpdateReady: new Subject(), + updateAvailable: new Subject(), + updateReady: new Subject(), + downloadProgress: new BehaviorSubject(0), }; export const updaterEvents = { - onClientUpdateReady: (fn: (versionMeta: UpdateMeta) => void) => { - const sub = updaterSubjects.clientUpdateReady.subscribe(fn); + onUpdateAvailable: (fn: (versionMeta: UpdateMeta) => void) => { + const sub = updaterSubjects.updateAvailable.subscribe(fn); + return () => { + sub.unsubscribe(); + }; + }, + onUpdateReady: (fn: (versionMeta: UpdateMeta) => void) => { + const sub = updaterSubjects.updateReady.subscribe(fn); + return () => { + sub.unsubscribe(); + }; + }, + onDownloadProgress: (fn: (progress: number) => void) => { + const sub = updaterSubjects.downloadProgress.subscribe(fn); return () => { sub.unsubscribe(); }; diff --git a/apps/electron/layers/main/src/handlers/updater/index.ts b/apps/electron/layers/main/src/handlers/updater/index.ts index 505cbd52da..2690810481 100644 --- a/apps/electron/layers/main/src/handlers/updater/index.ts +++ b/apps/electron/layers/main/src/handlers/updater/index.ts @@ -1,9 +1,17 @@ +import { app } from 'electron'; + import type { NamespaceHandlers } from '../type'; +import { checkForUpdatesAndNotify, quitAndInstall } from './updater'; export const updaterHandlers = { - updateClient: async () => { - const { updateClient } = await import('./updater'); - return updateClient(); + currentVersion: async () => { + return app.getVersion(); + }, + quitAndInstall: async () => { + return quitAndInstall(); + }, + checkForUpdatesAndNotify: async () => { + return checkForUpdatesAndNotify(true); }, } satisfies NamespaceHandlers; diff --git a/apps/electron/layers/main/src/handlers/updater/updater.ts b/apps/electron/layers/main/src/handlers/updater/updater.ts index 059e22a334..954ede28ce 100644 --- a/apps/electron/layers/main/src/handlers/updater/updater.ts +++ b/apps/electron/layers/main/src/handlers/updater/updater.ts @@ -1,3 +1,4 @@ +import { app } from 'electron'; import type { AppUpdater } from 'electron-updater'; import { z } from 'zod'; @@ -21,10 +22,22 @@ const isDev = mode === 'development'; let _autoUpdater: AppUpdater | null = null; -export const updateClient = async () => { +export const quitAndInstall = async () => { _autoUpdater?.quitAndInstall(); }; +let lastCheckTime = 0; +export const checkForUpdatesAndNotify = async (force = true) => { + if (!_autoUpdater) { + return; // ? + } + // check every 30 minutes (1800 seconds) at most + if (force || lastCheckTime + 1000 * 1800 < Date.now()) { + lastCheckTime = Date.now(); + return _autoUpdater.checkForUpdatesAndNotify(); + } +}; + export const registerUpdater = async () => { // require it will cause some side effects and will break generate-main-exposed-meta, // so we wrap it in a function @@ -37,6 +50,9 @@ export const registerUpdater = async () => { return; } + // TODO: support auto update on windows and linux + const allowAutoUpdate = isMacOS(); + _autoUpdater.autoDownload = false; _autoUpdater.allowPrerelease = buildType !== 'stable'; _autoUpdater.autoInstallOnAppQuit = false; @@ -49,24 +65,36 @@ export const registerUpdater = async () => { releaseType: buildType === 'stable' ? 'release' : 'prerelease', }); - if (isMacOS()) { - _autoUpdater.on('update-available', () => { + // register events for checkForUpdatesAndNotify + _autoUpdater.on('update-available', info => { + if (allowAutoUpdate) { _autoUpdater!.downloadUpdate(); - logger.info('Update available, downloading...'); + logger.info('Update available, downloading...', info); + } + updaterSubjects.updateAvailable.next({ + version: info.version, + allowAutoUpdate, }); - _autoUpdater.on('download-progress', e => { - logger.info(`Download progress: ${e.percent}`); + }); + _autoUpdater.on('download-progress', e => { + logger.info(`Download progress: ${e.percent}`); + updaterSubjects.downloadProgress.next(e.percent); + }); + _autoUpdater.on('update-downloaded', e => { + updaterSubjects.updateReady.next({ + version: e.version, + allowAutoUpdate, }); - _autoUpdater.on('update-downloaded', e => { - updaterSubjects.clientUpdateReady.next({ - version: e.version, - }); - logger.info('Update downloaded, ready to install'); - }); - _autoUpdater.on('error', e => { - logger.error('Error while updating client', e); - }); - _autoUpdater.forceDevUpdateConfig = isDev; - await _autoUpdater.checkForUpdatesAndNotify(); - } + // I guess we can skip it? + // updaterSubjects.clientDownloadProgress.next(100); + logger.info('Update downloaded, ready to install'); + }); + _autoUpdater.on('error', e => { + logger.error('Error while updating client', e); + }); + _autoUpdater.forceDevUpdateConfig = isDev; + + app.on('activate', async () => { + await checkForUpdatesAndNotify(false); + }); }; diff --git a/apps/electron/layers/preload/preload.d.ts b/apps/electron/layers/preload/preload.d.ts index 064be96d9f..f96bb8445f 100644 --- a/apps/electron/layers/preload/preload.d.ts +++ b/apps/electron/layers/preload/preload.d.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/consistent-type-imports */ interface Window { - apis?: typeof import('./src/affine-apis').apis; - events?: typeof import('./src/affine-apis').events; - appInfo?: typeof import('./src/affine-apis').appInfo; + apis: typeof import('./src/affine-apis').apis; + events: typeof import('./src/affine-apis').events; + appInfo: typeof import('./src/affine-apis').appInfo; } diff --git a/apps/web/src/components/root-app-sidebar/index.tsx b/apps/web/src/components/root-app-sidebar/index.tsx index 6e99b88fd4..28dea48176 100644 --- a/apps/web/src/components/root-app-sidebar/index.tsx +++ b/apps/web/src/components/root-app-sidebar/index.tsx @@ -8,7 +8,6 @@ import { QuickSearchInput, SidebarContainer, SidebarScrollableContainer, - updateAvailableAtom, } from '@affine/component/app-sidebar'; import { config } from '@affine/env'; import { useAFFiNEI18N } from '@affine/i18n/hooks'; @@ -20,7 +19,7 @@ import { ShareIcon, } from '@blocksuite/icons'; import type { Page } from '@blocksuite/store'; -import { useAtom, useAtomValue } from 'jotai'; +import { useAtom } from 'jotai'; import type { ReactElement } from 'react'; import type React from 'react'; import { useCallback, useEffect, useMemo } from 'react'; @@ -114,7 +113,6 @@ export const RootAppSidebar = ({ document.removeEventListener('keydown', keydown, { capture: true }); }, [sidebarOpen, setSidebarOpen]); - const clientUpdateAvailable = useAtomValue(updateAvailableAtom); const [history, setHistory] = useHistoryAtom(); const router = useMemo(() => { return { @@ -192,7 +190,8 @@ export const RootAppSidebar = ({ - {clientUpdateAvailable && } + {environment.isDesktop && } +
diff --git a/packages/component/src/components/app-sidebar/app-updater-button/index.css.ts b/packages/component/src/components/app-sidebar/app-updater-button/index.css.ts index a206b8b2ff..311cea5d83 100644 --- a/packages/component/src/components/app-sidebar/app-updater-button/index.css.ts +++ b/packages/component/src/components/app-sidebar/app-updater-button/index.css.ts @@ -13,11 +13,12 @@ export const root = style({ cursor: 'pointer', padding: '0 12px', position: 'relative', + transition: 'all 0.3s ease', selectors: { '&:hover': { - background: 'var(--affine-hover-color)', + background: 'var(--affine-white-60)', }, - '&:before': { + '&[data-has-update="true"]:before': { content: "''", position: 'absolute', top: '-3px', @@ -30,6 +31,9 @@ export const root = style({ opacity: 1, transition: '0.3s ease', }, + '&[data-disabled="true"]': { + pointerEvents: 'none', + }, }, vars: { '--svg-dot-animation': `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 122 116'%3E%3Cpath id='b' stroke='%23fff' stroke-linecap='round' stroke-width='0' d='M17.9256 115C17.434 111.774 13.1701 104.086 13.4282 95.6465C13.6862 87.207 18.6628 76.0721 17.9256 64.3628C17.1883 52.6535 8.7772 35.9512 9.00452 25.3907C9.23185 14.8302 16.2114 5.06512 17.9256 1'/%3E%3Cpath id='d' stroke='%23fff' stroke-linecap='round' stroke-width='0' d='M84.1628 115C85.2376 112.055 94.5618 98.8394 93.9975 91.1338C93.4332 83.4281 82.5505 73.2615 84.1628 62.5704C85.775 51.8793 96.4803 35.4248 95.9832 25.7826C95.4861 16.1404 87.9113 4.71163 84.1628 1'/%3E%3Cpath id='f' stroke='%23fff' stroke-linecap='round' stroke-width='0' d='M37.0913 115C37.9604 111.921 44.4347 99.4545 45.3816 92.9773C48.9305 68.7011 35.7877 73.9552 37.0913 62.7781C38.3949 51.6011 47.3889 36.9895 46.9869 26.9091C46.585 16.8286 40.1222 4.88034 37.0913 1'/%3E%3Cpath id='h' stroke='%23fff' stroke-linecap='round' stroke-width='0' d='M112.443 115C111.698 112.235 108.25 106.542 107.715 93.7582C107.241 82.4286 107.229 83.9543 112.443 66.1429C116.085 44.0408 100.661 42.5908 101.006 33.539C101.35 24.4871 109.843 4.48439 112.443 1'/%3E%3Cg%3E%3Ccircle r='1.5' fill='rgba(96, 70, 254, 0.3)'%3E%3CanimateMotion dur='10s' repeatCount='indefinite'%3E%3Cmpath href='%23b' /%3E%3C/animateMotion%3E%3C/circle%3E%3C/g%3E%3Cg%3E%3Ccircle r='1' fill='rgba(96, 70, 254, 0.3)' fill-opacity='1' shape-rendering='crispEdges'%3E%3CanimateMotion dur='8s' repeatCount='indefinite'%3E%3Cmpath href='%23d' /%3E%3C/animateMotion%3E%3C/circle%3E%3C/g%3E%3Cg%3E%3Ccircle r='.5' fill='rgba(96, 70, 254, 0.3)' fill-opacity='1' shape-rendering='crispEdges'%3E%3CanimateMotion dur='4s' repeatCount='indefinite'%3E%3Cmpath href='%23f' /%3E%3C/animateMotion%3E%3C/circle%3E%3C/g%3E%3Cg%3E%3Ccircle r='.8' fill='rgba(96, 70, 254, 0.3)' fill-opacity='1' shape-rendering='crispEdges'%3E%3CanimateMotion dur='6s' repeatCount='indefinite'%3E%3Cmpath href='%23h' /%3E%3C/animateMotion%3E%3C/circle%3E%3C/g%3E%3C/svg%3E")`, @@ -42,39 +46,35 @@ export const icon = style({ fontSize: '24px', }); -export const particles = style({ - background: `var(--svg-dot-animation), var(--svg-dot-animation)`, - backgroundRepeat: 'no-repeat, repeat', - backgroundPosition: 'center, center top 100%', - backgroundSize: '100%, 130%', - WebkitMaskImage: - 'linear-gradient(to top, transparent, black, black, transparent)', - width: '100%', - height: '100%', +export const closeIcon = style({ position: 'absolute', - left: 0, -}); - -export const particlesBefore = style({ - content: '""', - display: 'block', - position: 'absolute', - width: '100%', - height: '100%', - background: `var(--svg-dot-animation), var(--svg-dot-animation), var(--svg-dot-animation)`, - backgroundRepeat: 'no-repeat, repeat, repeat', - backgroundPosition: 'center, center top 100%, center center', - backgroundSize: '100% 120%, 150%, 120%', - filter: 'blur(1px)', - willChange: 'filter', + top: '4px', + right: '4px', + height: '14px', + width: '14px', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + boxShadow: 'var(--affine-shadow-1)', + color: 'var(--affine-text-secondary-color)', + backgroundColor: 'var(--affine-background-primary-color)', + fontSize: '14px', + cursor: 'pointer', + transition: '0.1s', + borderRadius: '50%', + zIndex: 1, + selectors: { + '&:hover': { + transform: 'scale(1.1)', + }, + }, }); export const installLabel = style({ display: 'flex', - justifyContent: 'flex-start', alignItems: 'center', width: '100%', - height: '100%', + flex: 1, fontSize: 'var(--affine-font-sm)', whiteSpace: 'nowrap', }); @@ -82,6 +82,7 @@ export const installLabel = style({ export const installLabelNormal = style([ installLabel, { + justifyContent: 'space-between', selectors: { [`${root}:hover &`]: { display: 'none', @@ -102,31 +103,111 @@ export const installLabelHover = style([ }, ]); -export const halo = style({ - overflow: 'hidden', +export const updateAvailableWrapper = style({ + display: 'flex', + flexDirection: 'column', + position: 'relative', width: '100%', height: '100%', + padding: '8px 0', +}); + +export const versionLabel = style({ + padding: '0 6px', + color: 'var(--affine-text-secondary-color)', + background: 'var(--affine-background-primary-color)', + fontSize: '10px', + lineHeight: '18px', + borderRadius: '4px', +}); + +export const whatsNewLabel = style({ + display: 'flex', + alignItems: 'center', + width: '100%', + height: '100%', + fontSize: 'var(--affine-font-sm)', + whiteSpace: 'nowrap', +}); + +export const progress = style({ + position: 'relative', + width: '100%', + height: '4px', + borderRadius: '12px', + background: 'var(--affine-black-10)', +}); + +export const progressInner = style({ position: 'absolute', top: 0, left: 0, + height: '100%', + borderRadius: '12px', + background: 'var(--affine-primary-color)', + transition: '0.1s', +}); + +export const particles = style({ + background: `var(--svg-dot-animation), var(--svg-dot-animation)`, + backgroundRepeat: 'no-repeat, repeat', + backgroundPosition: 'center, center top 100%', + backgroundSize: '100%, 130%', + WebkitMaskImage: + 'linear-gradient(to top, transparent, black, black, transparent)', + width: '100%', + height: '100%', + position: 'absolute', + left: 0, + pointerEvents: 'none', +}); + +export const particlesBefore = style({ + content: '""', + display: 'block', + position: 'absolute', + width: '100%', + height: '100%', + background: `var(--svg-dot-animation), var(--svg-dot-animation), var(--svg-dot-animation)`, + backgroundRepeat: 'no-repeat, repeat, repeat', + backgroundPosition: 'center, center top 100%, center center', + backgroundSize: '100% 120%, 150%, 120%', + filter: 'blur(1px)', + willChange: 'filter', + pointerEvents: 'none', +}); + +export const halo = style({ + overflow: 'hidden', + position: 'absolute', + inset: 0, ':before': { content: '""', display: 'block', - width: '60%', - height: '40%', + inset: 0, position: 'absolute', - top: '80%', - left: '50%', - background: - 'linear-gradient(180deg, rgba(50, 26, 206, 0.1) 10%, rgba(50, 26, 206, 0.35) 30%, rgba(84, 56, 255, 1) 50%)', filter: 'blur(10px) saturate(1.2)', - transform: 'translateX(-50%) translateY(calc(0 * 1%)) scale(0)', transition: '0.3s ease', - willChange: 'filter', + willChange: 'filter, transform', + transform: 'translateY(100%) scale(0.6)', + background: + 'radial-gradient(ellipse 60% 80% at bottom, rgba(50, 26, 206, 0.35), transparent)', + }, + ':after': { + content: '""', + display: 'block', + inset: 0, + position: 'absolute', + filter: 'blur(10px) saturate(1.2)', + transition: '0.1s ease', + willChange: 'filter, transform', + transform: 'translateY(100%) scale(0.6)', + background: + 'radial-gradient(ellipse 30% 45% at bottom, rgba(50, 26, 206, 0.6), transparent)', }, selectors: { - '&:hover:before': { - transform: 'translateX(-50%) translateY(calc(-70 * 1%)) scale(1)', + '&:hover:before, &:hover:after': { + transform: 'translateY(0) scale(1)', }, }, }); diff --git a/packages/component/src/components/app-sidebar/app-updater-button/index.jotai.ts b/packages/component/src/components/app-sidebar/app-updater-button/index.jotai.ts new file mode 100644 index 0000000000..64702247a5 --- /dev/null +++ b/packages/component/src/components/app-sidebar/app-updater-button/index.jotai.ts @@ -0,0 +1,71 @@ +import { getEnvironment } from '@affine/env/config'; +import { atomWithObservable, atomWithStorage } from 'jotai/utils'; +import { Observable } from 'rxjs'; + +// todo: move to utils? +function rpcToObservable< + T, + H extends () => Promise, + E extends (callback: (t: T) => void) => () => void +>( + initialValue: T, + { + event, + handler, + onSubscribe, + }: { + event?: E; + handler?: H; + onSubscribe?: () => void; + } +) { + return new Observable(subscriber => { + subscriber.next(initialValue); + const environment = getEnvironment(); + onSubscribe?.(); + if (typeof window === 'undefined' || !environment.isDesktop || !event) { + subscriber.complete(); + return () => {}; + } + handler?.().then(t => { + subscriber.next(t); + }); + return event(t => { + subscriber.next(t); + }); + }); +} + +type InferTFromEvent = E extends ( + callback: (t: infer T) => void +) => () => void + ? T + : never; + +type UpdateMeta = InferTFromEvent; + +export const updateReadyAtom = atomWithObservable(() => { + return rpcToObservable(null as UpdateMeta | null, { + event: window.events?.updater.onUpdateReady, + }); +}); + +export const updateAvailableAtom = atomWithObservable(() => { + return rpcToObservable(null as UpdateMeta | null, { + event: window.events?.updater.onUpdateAvailable, + onSubscribe: () => { + window.apis?.updater.checkForUpdatesAndNotify(); + }, + }); +}); + +export const downloadProgressAtom = atomWithObservable(() => { + return rpcToObservable(0, { + event: window.events?.updater.onDownloadProgress, + }); +}); + +export const changelogCheckedAtom = atomWithStorage>( + 'affine:client-changelog-checked', + {} +); diff --git a/packages/component/src/components/app-sidebar/app-updater-button/index.stories.tsx b/packages/component/src/components/app-sidebar/app-updater-button/index.stories.tsx deleted file mode 100644 index 110293e588..0000000000 --- a/packages/component/src/components/app-sidebar/app-updater-button/index.stories.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import type { Meta, StoryFn } from '@storybook/react'; - -import { AppUpdaterButton } from '.'; - -export default { - title: 'Components/AppSidebar/AppUpdaterButton', - component: AppUpdaterButton, -} satisfies Meta; - -export const Default: StoryFn = () => { - return ( -
- -
- ); -}; diff --git a/packages/component/src/components/app-sidebar/app-updater-button/index.tsx b/packages/component/src/components/app-sidebar/app-updater-button/index.tsx index 571bb92234..c16a416507 100644 --- a/packages/component/src/components/app-sidebar/app-updater-button/index.tsx +++ b/packages/component/src/components/app-sidebar/app-updater-button/index.tsx @@ -1,35 +1,167 @@ import { useAFFiNEI18N } from '@affine/i18n/hooks'; -import { ResetIcon } from '@blocksuite/icons'; +import { CloseIcon, NewIcon, ResetIcon } from '@blocksuite/icons'; import clsx from 'clsx'; +import { atom, useAtomValue, useSetAtom } from 'jotai'; +import { startTransition } from 'react'; import * as styles from './index.css'; +import { + changelogCheckedAtom, + downloadProgressAtom, + updateAvailableAtom, + updateReadyAtom, +} from './index.jotai'; interface AddPageButtonProps { className?: string; style?: React.CSSProperties; } +const currentVersionAtom = atom(async () => { + if (typeof window === 'undefined') { + return null; + } + const currentVersion = await window.apis?.updater.currentVersion(); + return currentVersion; +}); + +const currentChangelogUnreadAtom = atom(async get => { + if (typeof window === 'undefined') { + return false; + } + const mapping = get(changelogCheckedAtom); + const currentVersion = await get(currentVersionAtom); + if (currentVersion) { + return !mapping[currentVersion]; + } + return false; +}); + // Although it is called an input, it is actually a button. export function AppUpdaterButton({ className, style }: AddPageButtonProps) { const t = useAFFiNEI18N(); + const currentChangelogUnread = useAtomValue(currentChangelogUnreadAtom); + const updateReady = useAtomValue(updateReadyAtom); + const updateAvailable = useAtomValue(updateAvailableAtom); + const currentVersion = useAtomValue(currentVersionAtom); + const downloadProgress = useAtomValue(downloadProgressAtom); + const onReadOrDismissChangelog = useSetAtom(changelogCheckedAtom); + + const onReadOrDismissCurrentChangelog = (visit: boolean) => { + if (visit) { + window.open( + `https://github.com/toeverything/AFFiNE/releases/tag/v${currentVersion}`, + '_blank' + ); + } + + startTransition(() => + onReadOrDismissChangelog(mapping => { + return { + ...mapping, + [currentVersion!]: true, + }; + }) + ); + }; + + if (!updateAvailable && !currentChangelogUnread) { + return null; + } + return ( ); + + function renderUpdateAvailableAllowAutoUpdate() { + return ( +
+
+ + {!updateReady + ? t['com.affine.updater.downloading']() + : t['com.affine.updater.update-available']()} + + + {updateAvailable?.version} + +
+ + {updateReady ? ( +
+ + {t['com.affine.updater.restart-to-update']()} +
+ ) : ( +
+
+
+ )} +
+ ); + } + + function renderUpdateAvailableNotAllowAutoUpdate() { + return ( + <> +
+ {t['com.affine.updater.update-available']()} + + {updateAvailable?.version} + +
+ +
+ {t['com.affine.updater.open-download-page']()} +
+ + ); + } + + function renderWhatsNew() { + return ( + <> +
+ + {t[`Discover what's new!`]()} +
+
{ + onReadOrDismissCurrentChangelog(false); + e.stopPropagation(); + }} + > + +
+ + ); + } } diff --git a/packages/component/src/components/app-sidebar/index.jotai.ts b/packages/component/src/components/app-sidebar/index.jotai.ts index 0bd3cef72e..8d3ac03268 100644 --- a/packages/component/src/components/app-sidebar/index.jotai.ts +++ b/packages/component/src/components/app-sidebar/index.jotai.ts @@ -1,7 +1,5 @@ import { atom } from 'jotai'; -import { atomWithObservable } from 'jotai/utils'; import { atomWithStorage } from 'jotai/utils'; -import { Observable } from 'rxjs'; export const APP_SIDEBAR_OPEN = 'app-sidebar-open'; export const appSidebarOpenAtom = atomWithStorage( @@ -15,20 +13,3 @@ export const appSidebarWidthAtom = atomWithStorage( 'app-sidebar-width', 256 /* px */ ); - -export const updateAvailableAtom = atomWithObservable(() => { - return new Observable(subscriber => { - subscriber.next(false); - if (typeof window !== 'undefined') { - const isMacosDesktop = environment.isDesktop && environment.isMacOs; - if (isMacosDesktop) { - const dispose = window.events?.updater.onClientUpdateReady(() => { - subscriber.next(true); - }); - return () => { - dispose?.(); - }; - } - } - }); -}); diff --git a/packages/component/src/components/app-sidebar/index.tsx b/packages/component/src/components/app-sidebar/index.tsx index d089cc55ed..b362e27927 100644 --- a/packages/component/src/components/app-sidebar/index.tsx +++ b/packages/component/src/components/app-sidebar/index.tsx @@ -18,7 +18,6 @@ import { appSidebarOpenAtom, appSidebarResizingAtom, appSidebarWidthAtom, - updateAvailableAtom, } from './index.jotai'; import { ResizeIndicator } from './resize-indicator'; import type { SidebarHeaderProps } from './sidebar-header'; @@ -122,9 +121,4 @@ export { AppSidebarFallback } from './fallback'; export * from './menu-item'; export * from './quick-search-input'; export * from './sidebar-containers'; -export { - appSidebarFloatingAtom, - appSidebarOpenAtom, - appSidebarResizingAtom, - updateAvailableAtom, -}; +export { appSidebarFloatingAtom, appSidebarOpenAtom, appSidebarResizingAtom }; diff --git a/packages/i18n/src/resources/en.json b/packages/i18n/src/resources/en.json index 0218ffeeab..3102b61608 100644 --- a/packages/i18n/src/resources/en.json +++ b/packages/i18n/src/resources/en.json @@ -231,7 +231,6 @@ "Synced with AFFiNE Cloud": "Synced with AFFiNE Cloud", "Recent": "Recent", "Successfully deleted": "Successfully deleted", - "Restart Install Client Update": "Restart to install update", "Add Workspace": "Add Workspace", "Add Workspace Hint": "Select where you already have", "Export success": "Export success", @@ -271,7 +270,10 @@ "com.affine.onboarding.videoDescription2": "Create structured documents with ease, using a modular interface to drag and drop blocks of text, images, and other content.", "FILE_ALREADY_EXISTS": "File already exists", "others": "Others", - "Update Available": "Update available", + "com.affine.updater.update-available": "Update available", + "com.affine.updater.downloading": "Downloading", + "com.affine.updater.restart-to-update": "Restart to install update", + "com.affine.updater.open-download-page": "Open download page", "dark": "Dark", "system": "System", "light": "Light",