mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-12 15:46:29 +08:00
fix(electron): sync settings from localStorage -> atom -> electron (#5020)
- moved `appSettingAtom` to infra since we now have different packages that depends on it. There is no better place to fit in for now - use atomEffect to sync setting changes to updater related configs to Electron side - refactored how Electron reacts to updater config changes.
This commit is contained in:
Vendored
+1
-2
@@ -21,8 +21,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@affine/templates": "workspace:*",
|
||||
"@blocksuite/global": "0.0.0-20230409084303-221991d4-nightly",
|
||||
"@toeverything/infra": "workspace:*"
|
||||
"@blocksuite/global": "0.0.0-20230409084303-221991d4-nightly"
|
||||
},
|
||||
"dependencies": {
|
||||
"lit": "^3.0.2"
|
||||
|
||||
Vendored
-3
@@ -7,9 +7,6 @@
|
||||
"outDir": "lib"
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"path": "../infra"
|
||||
},
|
||||
{
|
||||
"path": "../../../tests/fixtures"
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"dev": "vite build --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@affine/env": "workspace:*",
|
||||
"@affine/sdk": "workspace:*",
|
||||
"@blocksuite/blocks": "0.11.0-nightly-202312070955-2b5bb47",
|
||||
"@blocksuite/global": "0.11.0-nightly-202312070955-2b5bb47",
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { atom } from 'jotai';
|
||||
|
||||
export const loadedPluginNameAtom = atom<string[]>([]);
|
||||
|
||||
export * from './layout';
|
||||
export * from './root-store';
|
||||
export * from './settings';
|
||||
export * from './workspace';
|
||||
@@ -1,34 +1,5 @@
|
||||
import type { ExpectedLayout } from '@affine/sdk/entry';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import type { Workspace } from '@blocksuite/store';
|
||||
import { atom, createStore } from 'jotai/vanilla';
|
||||
|
||||
import { getBlockSuiteWorkspaceAtom } from './__internal__/workspace';
|
||||
|
||||
// global store
|
||||
let rootStore = createStore();
|
||||
|
||||
export function getCurrentStore() {
|
||||
return rootStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal do not use this function unless you know what you are doing
|
||||
*/
|
||||
export function _setCurrentStore(store: ReturnType<typeof createStore>) {
|
||||
rootStore = store;
|
||||
}
|
||||
|
||||
export const loadedPluginNameAtom = atom<string[]>([]);
|
||||
|
||||
export const currentWorkspaceIdAtom = atom<string | null>(null);
|
||||
export const currentPageIdAtom = atom<string | null>(null);
|
||||
export const currentWorkspaceAtom = atom<Promise<Workspace>>(async get => {
|
||||
const workspaceId = get(currentWorkspaceIdAtom);
|
||||
assertExists(workspaceId);
|
||||
const [currentWorkspaceAtom] = getBlockSuiteWorkspaceAtom(workspaceId);
|
||||
return get(currentWorkspaceAtom);
|
||||
});
|
||||
import { atom } from 'jotai';
|
||||
|
||||
const contentLayoutBaseAtom = atom<ExpectedLayout>('editor');
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { createStore } from 'jotai';
|
||||
|
||||
// global store
|
||||
let rootStore = createStore();
|
||||
|
||||
export function getCurrentStore() {
|
||||
return rootStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal do not use this function unless you know what you are doing
|
||||
*/
|
||||
export function _setCurrentStore(store: ReturnType<typeof createStore>) {
|
||||
rootStore = store;
|
||||
}
|
||||
+29
-5
@@ -1,5 +1,9 @@
|
||||
import { setupGlobal } from '@affine/env/global';
|
||||
import { atom } from 'jotai';
|
||||
import { atomWithStorage } from 'jotai/utils';
|
||||
import { atomEffect } from 'jotai-effect';
|
||||
|
||||
setupGlobal();
|
||||
|
||||
export type DateFormats =
|
||||
| 'MM/dd/YYYY'
|
||||
@@ -63,15 +67,35 @@ const appSettingBaseAtom = atomWithStorage<AppSetting>('affine-settings', {
|
||||
|
||||
type SetStateAction<Value> = Value | ((prev: Value) => Value);
|
||||
|
||||
const appSettingEffect = atomEffect(get => {
|
||||
const settings = get(appSettingBaseAtom);
|
||||
// some values in settings should be synced into electron side
|
||||
if (environment.isDesktop) {
|
||||
console.log('set config', settings);
|
||||
window.apis?.updater
|
||||
.setConfig({
|
||||
autoCheckUpdate: settings.autoCheckUpdate,
|
||||
autoDownloadUpdate: settings.autoDownloadUpdate,
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export const appSettingAtom = atom<
|
||||
AppSetting,
|
||||
[SetStateAction<Partial<AppSetting>>],
|
||||
void
|
||||
>(
|
||||
get => get(appSettingBaseAtom),
|
||||
(get, set, apply) => {
|
||||
const prev = get(appSettingBaseAtom);
|
||||
const next = typeof apply === 'function' ? apply(prev) : apply;
|
||||
set(appSettingBaseAtom, { ...prev, ...next });
|
||||
get => {
|
||||
get(appSettingEffect);
|
||||
return get(appSettingBaseAtom);
|
||||
},
|
||||
(_get, set, apply) => {
|
||||
set(appSettingBaseAtom, prev => {
|
||||
const next = typeof apply === 'function' ? apply(prev) : apply;
|
||||
return { ...prev, ...next };
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,14 @@
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import type { Workspace } from '@blocksuite/store';
|
||||
import { atom } from 'jotai';
|
||||
|
||||
import { getBlockSuiteWorkspaceAtom } from '../__internal__/workspace';
|
||||
|
||||
export const currentWorkspaceIdAtom = atom<string | null>(null);
|
||||
export const currentPageIdAtom = atom<string | null>(null);
|
||||
export const currentWorkspaceAtom = atom<Promise<Workspace>>(async get => {
|
||||
const workspaceId = get(currentWorkspaceIdAtom);
|
||||
assertExists(workspaceId);
|
||||
const [currentWorkspaceAtom] = getBlockSuiteWorkspaceAtom(workspaceId);
|
||||
return get(currentWorkspaceAtom);
|
||||
});
|
||||
@@ -201,7 +201,7 @@ export type UpdaterHandlers = {
|
||||
downloadUpdate: () => Promise<void>;
|
||||
getConfig: () => Promise<UpdaterConfig>;
|
||||
setConfig: (newConfig: Partial<UpdaterConfig>) => Promise<void>;
|
||||
checkForUpdatesAndNotify: () => Promise<{ version: string } | null>;
|
||||
checkForUpdates: () => Promise<{ version: string } | null>;
|
||||
};
|
||||
|
||||
export type WorkspaceHandlers = {
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
{
|
||||
"path": "../sdk"
|
||||
},
|
||||
{
|
||||
"path": "../env"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ export default defineConfig({
|
||||
entry: {
|
||||
blocksuite: resolve(root, 'src/blocksuite/index.ts'),
|
||||
index: resolve(root, 'src/index.ts'),
|
||||
atom: resolve(root, 'src/atom.ts'),
|
||||
atom: resolve(root, 'src/atom/index.ts'),
|
||||
command: resolve(root, 'src/command/index.ts'),
|
||||
type: resolve(root, 'src/type.ts'),
|
||||
'core/event-emitter': resolve(root, 'src/core/event-emitter.ts'),
|
||||
|
||||
+2
-1
@@ -85,12 +85,12 @@ export const installLabel = style({
|
||||
flex: 1,
|
||||
fontSize: 'var(--affine-font-sm)',
|
||||
whiteSpace: 'nowrap',
|
||||
justifyContent: 'space-between',
|
||||
});
|
||||
|
||||
export const installLabelNormal = style([
|
||||
installLabel,
|
||||
{
|
||||
justifyContent: 'space-between',
|
||||
selectors: {
|
||||
[`${root}:hover &, ${root}[data-updating=true] &`]: {
|
||||
display: 'none',
|
||||
@@ -103,6 +103,7 @@ export const installLabelHover = style([
|
||||
installLabel,
|
||||
{
|
||||
display: 'none',
|
||||
justifyContent: 'flex-start',
|
||||
selectors: {
|
||||
[`${root}:hover &, ${root}[data-updating=true] &`]: {
|
||||
display: 'flex',
|
||||
|
||||
+206
-135
@@ -1,18 +1,9 @@
|
||||
import { Unreachable } from '@affine/env/constant';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { CloseIcon, NewIcon, ResetIcon } from '@blocksuite/icons';
|
||||
import {
|
||||
changelogCheckedAtom,
|
||||
currentChangelogUnreadAtom,
|
||||
currentVersionAtom,
|
||||
downloadProgressAtom,
|
||||
updateAvailableAtom,
|
||||
updateReadyAtom,
|
||||
useAppUpdater,
|
||||
} from '@toeverything/hooks/use-app-updater';
|
||||
import { useAppUpdater } from '@toeverything/hooks/use-app-updater';
|
||||
import clsx from 'clsx';
|
||||
import { useAtomValue, useSetAtom } from 'jotai';
|
||||
import { startTransition, useCallback } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { Tooltip } from '../../../ui/tooltip';
|
||||
import * as styles from './index.css';
|
||||
@@ -26,36 +17,178 @@ export interface AddPageButtonPureProps {
|
||||
version: string;
|
||||
allowAutoUpdate: boolean;
|
||||
} | null;
|
||||
autoDownload: boolean;
|
||||
downloadProgress: number | null;
|
||||
appQuitting: boolean;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
interface ButtonContentProps {
|
||||
updateReady: boolean;
|
||||
updateAvailable: {
|
||||
version: string;
|
||||
allowAutoUpdate: boolean;
|
||||
} | null;
|
||||
autoDownload: boolean;
|
||||
downloadProgress: number | null;
|
||||
appQuitting: boolean;
|
||||
currentChangelogUnread: boolean;
|
||||
onDismissCurrentChangelog: () => void;
|
||||
}
|
||||
|
||||
function DownloadUpdate({ updateAvailable }: ButtonContentProps) {
|
||||
const t = useAFFiNEI18N();
|
||||
return (
|
||||
<div className={styles.installLabel}>
|
||||
<span className={styles.ellipsisTextOverflow}>
|
||||
{t['com.affine.appUpdater.downloadUpdate']()}
|
||||
</span>
|
||||
<span className={styles.versionLabel}>{updateAvailable?.version}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function UpdateReady({ updateAvailable, appQuitting }: ButtonContentProps) {
|
||||
const t = useAFFiNEI18N();
|
||||
return (
|
||||
<div className={styles.updateAvailableWrapper}>
|
||||
<div className={styles.installLabelNormal}>
|
||||
<span className={styles.ellipsisTextOverflow}>
|
||||
{t['com.affine.appUpdater.updateAvailable']()}
|
||||
</span>
|
||||
<span className={styles.versionLabel}>{updateAvailable?.version}</span>
|
||||
</div>
|
||||
|
||||
<div className={styles.installLabelHover}>
|
||||
<ResetIcon className={styles.icon} />
|
||||
<span className={styles.ellipsisTextOverflow}>
|
||||
{t[appQuitting ? 'Loading' : 'com.affine.appUpdater.installUpdate']()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function DownloadingUpdate({
|
||||
updateAvailable,
|
||||
downloadProgress,
|
||||
}: ButtonContentProps) {
|
||||
const t = useAFFiNEI18N();
|
||||
return (
|
||||
<div className={clsx([styles.updateAvailableWrapper])}>
|
||||
<div className={clsx([styles.installLabelNormal])}>
|
||||
<span className={styles.ellipsisTextOverflow}>
|
||||
{t['com.affine.appUpdater.downloading']()}
|
||||
</span>
|
||||
<span className={styles.versionLabel}>{updateAvailable?.version}</span>
|
||||
</div>
|
||||
|
||||
<div className={styles.progress}>
|
||||
<div
|
||||
className={styles.progressInner}
|
||||
style={{ width: `${downloadProgress}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function OpenDownloadPage({ updateAvailable }: ButtonContentProps) {
|
||||
const t = useAFFiNEI18N();
|
||||
return (
|
||||
<>
|
||||
<div className={styles.installLabelNormal}>
|
||||
<span className={styles.ellipsisTextOverflow}>
|
||||
{t['com.affine.appUpdater.updateAvailable']()}
|
||||
</span>
|
||||
<span className={styles.versionLabel}>{updateAvailable?.version}</span>
|
||||
</div>
|
||||
|
||||
<div className={styles.installLabelHover}>
|
||||
<span className={styles.ellipsisTextOverflow}>
|
||||
{t['com.affine.appUpdater.openDownloadPage']()}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function WhatsNew({ onDismissCurrentChangelog }: ButtonContentProps) {
|
||||
const t = useAFFiNEI18N();
|
||||
const onClickClose: React.MouseEventHandler = useCallback(
|
||||
e => {
|
||||
onDismissCurrentChangelog();
|
||||
e.stopPropagation();
|
||||
},
|
||||
[onDismissCurrentChangelog]
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<div className={clsx([styles.whatsNewLabel])}>
|
||||
<NewIcon className={styles.icon} />
|
||||
<span className={styles.ellipsisTextOverflow}>
|
||||
{t['com.affine.appUpdater.whatsNew']()}
|
||||
</span>
|
||||
</div>
|
||||
<div className={styles.closeIcon} onClick={onClickClose}>
|
||||
<CloseIcon />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const getButtonContentRenderer = (props: ButtonContentProps) => {
|
||||
if (props.updateReady) {
|
||||
return UpdateReady;
|
||||
} else if (props.updateAvailable?.allowAutoUpdate) {
|
||||
if (props.autoDownload && props.updateAvailable.allowAutoUpdate) {
|
||||
return DownloadingUpdate;
|
||||
} else {
|
||||
return DownloadUpdate;
|
||||
}
|
||||
} else if (props.updateAvailable && !props.updateAvailable?.allowAutoUpdate) {
|
||||
return OpenDownloadPage;
|
||||
} else if (props.currentChangelogUnread) {
|
||||
return WhatsNew;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export function AppUpdaterButtonPure({
|
||||
updateReady,
|
||||
onClickUpdate,
|
||||
onDismissCurrentChangelog,
|
||||
currentChangelogUnread,
|
||||
updateAvailable,
|
||||
autoDownload,
|
||||
downloadProgress,
|
||||
appQuitting,
|
||||
className,
|
||||
style,
|
||||
}: AddPageButtonPureProps) {
|
||||
const t = useAFFiNEI18N();
|
||||
const contentProps = useMemo(
|
||||
() => ({
|
||||
updateReady,
|
||||
updateAvailable,
|
||||
currentChangelogUnread,
|
||||
autoDownload,
|
||||
downloadProgress,
|
||||
appQuitting,
|
||||
onDismissCurrentChangelog,
|
||||
}),
|
||||
[
|
||||
updateReady,
|
||||
updateAvailable,
|
||||
currentChangelogUnread,
|
||||
autoDownload,
|
||||
downloadProgress,
|
||||
appQuitting,
|
||||
onDismissCurrentChangelog,
|
||||
]
|
||||
);
|
||||
|
||||
if (!updateAvailable && !currentChangelogUnread) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const updateAvailableNode = updateAvailable
|
||||
? updateAvailable.allowAutoUpdate
|
||||
? renderUpdateAvailableAllowAutoUpdate()
|
||||
: renderUpdateAvailableNotAllowAutoUpdate()
|
||||
: null;
|
||||
const whatsNew =
|
||||
!updateAvailable && currentChangelogUnread ? renderWhatsNew() : null;
|
||||
const ContentComponent = getButtonContentRenderer(contentProps);
|
||||
|
||||
const wrapWithTooltip = (
|
||||
node: React.ReactElement,
|
||||
@@ -72,102 +205,38 @@ export function AppUpdaterButtonPure({
|
||||
);
|
||||
};
|
||||
|
||||
const disabled = useMemo(() => {
|
||||
if (appQuitting) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (updateAvailable?.allowAutoUpdate) {
|
||||
return !updateReady && autoDownload;
|
||||
}
|
||||
|
||||
return false;
|
||||
}, [
|
||||
appQuitting,
|
||||
autoDownload,
|
||||
updateAvailable?.allowAutoUpdate,
|
||||
updateReady,
|
||||
]);
|
||||
|
||||
return wrapWithTooltip(
|
||||
<button
|
||||
style={style}
|
||||
className={clsx([styles.root, className])}
|
||||
data-has-update={!!updateAvailable}
|
||||
data-updating={appQuitting}
|
||||
data-disabled={
|
||||
(updateAvailable?.allowAutoUpdate && !updateReady) || appQuitting
|
||||
}
|
||||
data-disabled={disabled}
|
||||
onClick={onClickUpdate}
|
||||
>
|
||||
{updateAvailableNode}
|
||||
{whatsNew}
|
||||
{ContentComponent ? <ContentComponent {...contentProps} /> : null}
|
||||
<div className={styles.particles} aria-hidden="true"></div>
|
||||
<span className={styles.halo} aria-hidden="true"></span>
|
||||
</button>,
|
||||
updateAvailable?.version
|
||||
);
|
||||
|
||||
function renderUpdateAvailableAllowAutoUpdate() {
|
||||
return (
|
||||
<div className={clsx([styles.updateAvailableWrapper])}>
|
||||
<div className={clsx([styles.installLabelNormal])}>
|
||||
<span className={styles.ellipsisTextOverflow}>
|
||||
{!updateReady
|
||||
? t['com.affine.appUpdater.downloading']()
|
||||
: t['com.affine.appUpdater.updateAvailable']()}
|
||||
</span>
|
||||
<span className={styles.versionLabel}>
|
||||
{updateAvailable?.version}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{updateReady ? (
|
||||
<div className={clsx([styles.installLabelHover])}>
|
||||
<ResetIcon className={styles.icon} />
|
||||
<span className={styles.ellipsisTextOverflow}>
|
||||
{t[
|
||||
appQuitting ? 'Loading' : 'com.affine.appUpdater.installUpdate'
|
||||
]()}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className={styles.progress}>
|
||||
<div
|
||||
className={styles.progressInner}
|
||||
style={{ width: `${downloadProgress}%` }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function renderUpdateAvailableNotAllowAutoUpdate() {
|
||||
return (
|
||||
<>
|
||||
<div className={clsx([styles.installLabelNormal])}>
|
||||
<span className={styles.ellipsisTextOverflow}>
|
||||
{t['com.affine.appUpdater.updateAvailable']()}
|
||||
</span>
|
||||
<span className={styles.versionLabel}>
|
||||
{updateAvailable?.version}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className={clsx([styles.installLabelHover])}>
|
||||
<span className={styles.ellipsisTextOverflow}>
|
||||
{t['com.affine.appUpdater.openDownloadPage']()}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function renderWhatsNew() {
|
||||
return (
|
||||
<>
|
||||
<div className={clsx([styles.whatsNewLabel])}>
|
||||
<NewIcon className={styles.icon} />
|
||||
<span className={styles.ellipsisTextOverflow}>
|
||||
{t['com.affine.appUpdater.whatsNew']()}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className={styles.closeIcon}
|
||||
onClick={e => {
|
||||
onDismissCurrentChangelog();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<CloseIcon />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Although it is called an input, it is actually a button.
|
||||
@@ -178,62 +247,64 @@ export function AppUpdaterButton({
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
}) {
|
||||
const currentChangelogUnread = useAtomValue(currentChangelogUnreadAtom);
|
||||
const updateReady = useAtomValue(updateReadyAtom);
|
||||
const updateAvailable = useAtomValue(updateAvailableAtom);
|
||||
const downloadProgress = useAtomValue(downloadProgressAtom);
|
||||
const currentVersion = useAtomValue(currentVersionAtom);
|
||||
const { quitAndInstall, appQuitting } = useAppUpdater();
|
||||
const setChangelogCheckAtom = useSetAtom(changelogCheckedAtom);
|
||||
|
||||
const dismissCurrentChangelog = useCallback(() => {
|
||||
if (!currentVersion) {
|
||||
return;
|
||||
}
|
||||
startTransition(() =>
|
||||
setChangelogCheckAtom(mapping => {
|
||||
return {
|
||||
...mapping,
|
||||
[currentVersion]: true,
|
||||
};
|
||||
})
|
||||
);
|
||||
}, [currentVersion, setChangelogCheckAtom]);
|
||||
const {
|
||||
quitAndInstall,
|
||||
appQuitting,
|
||||
autoDownload,
|
||||
downloadUpdate,
|
||||
readChangelog,
|
||||
changelogUnread,
|
||||
updateReady,
|
||||
updateAvailable,
|
||||
downloadProgress,
|
||||
currentVersion,
|
||||
} = useAppUpdater();
|
||||
|
||||
const handleClickUpdate = useCallback(() => {
|
||||
if (updateReady) {
|
||||
quitAndInstall();
|
||||
} else if (updateAvailable) {
|
||||
if (updateAvailable.allowAutoUpdate) {
|
||||
// wait for download to finish
|
||||
if (autoDownload) {
|
||||
// wait for download to finish
|
||||
} else {
|
||||
downloadUpdate();
|
||||
}
|
||||
} else {
|
||||
window.open(
|
||||
`https://github.com/toeverything/AFFiNE/releases/tag/v${currentVersion}`,
|
||||
'_blank'
|
||||
);
|
||||
}
|
||||
} else if (currentChangelogUnread) {
|
||||
} else if (changelogUnread) {
|
||||
window.open(runtimeConfig.changelogUrl, '_blank');
|
||||
dismissCurrentChangelog();
|
||||
readChangelog();
|
||||
} else {
|
||||
throw new Unreachable();
|
||||
}
|
||||
}, [
|
||||
updateReady,
|
||||
quitAndInstall,
|
||||
updateAvailable,
|
||||
currentChangelogUnread,
|
||||
dismissCurrentChangelog,
|
||||
changelogUnread,
|
||||
quitAndInstall,
|
||||
autoDownload,
|
||||
downloadUpdate,
|
||||
currentVersion,
|
||||
readChangelog,
|
||||
]);
|
||||
|
||||
if (!updateAvailable && !changelogUnread) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<AppUpdaterButtonPure
|
||||
appQuitting={appQuitting}
|
||||
autoDownload={autoDownload}
|
||||
updateReady={!!updateReady}
|
||||
onClickUpdate={handleClickUpdate}
|
||||
onDismissCurrentChangelog={dismissCurrentChangelog}
|
||||
currentChangelogUnread={currentChangelogUnread}
|
||||
onDismissCurrentChangelog={readChangelog}
|
||||
currentChangelogUnread={changelogUnread}
|
||||
updateAvailable={updateAvailable}
|
||||
downloadProgress={downloadProgress}
|
||||
className={className}
|
||||
|
||||
@@ -62,6 +62,7 @@
|
||||
"intl-segmenter-polyfill-rs": "^0.1.6",
|
||||
"jotai": "^2.5.1",
|
||||
"jotai-devtools": "^0.7.0",
|
||||
"jotai-effect": "^0.2.3",
|
||||
"lit": "^3.0.2",
|
||||
"lottie-web": "^5.12.2",
|
||||
"mini-css-extract-plugin": "^2.7.6",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { SettingsIcon } from '@blocksuite/icons';
|
||||
import { appSettingAtom } from '@toeverything/infra/atom';
|
||||
import {
|
||||
PreconditionStrategy,
|
||||
registerAffineCommand,
|
||||
@@ -8,7 +9,6 @@ import { type createStore } from 'jotai';
|
||||
import type { useTheme } from 'next-themes';
|
||||
|
||||
import { openQuickSearchModalAtom } from '../atoms';
|
||||
import { appSettingAtom } from '../atoms/settings';
|
||||
import type { useLanguageHelper } from '../hooks/affine/use-language-helper';
|
||||
|
||||
export function registerAffineSettingsCommands({
|
||||
|
||||
+21
-26
@@ -3,15 +3,8 @@ import { SettingRow } from '@affine/component/setting-components';
|
||||
import { Button } from '@affine/component/ui/button';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
|
||||
import {
|
||||
downloadProgressAtom,
|
||||
isCheckingForUpdatesAtom,
|
||||
updateAvailableAtom,
|
||||
updateReadyAtom,
|
||||
useAppUpdater,
|
||||
} from '@toeverything/hooks/use-app-updater';
|
||||
import { useAppUpdater } from '@toeverything/hooks/use-app-updater';
|
||||
import clsx from 'clsx';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import * as styles from './style.css';
|
||||
@@ -25,18 +18,16 @@ enum CheckUpdateStatus {
|
||||
|
||||
const useUpdateStatusLabels = (checkUpdateStatus: CheckUpdateStatus) => {
|
||||
const t = useAFFiNEI18N();
|
||||
const isCheckingForUpdates = useAtomValue(isCheckingForUpdatesAtom);
|
||||
const updateAvailable = useAtomValue(updateAvailableAtom);
|
||||
const updateReady = useAtomValue(updateReadyAtom);
|
||||
const downloadProgress = useAtomValue(downloadProgressAtom);
|
||||
const { updateAvailable, downloadProgress, updateReady, checkingForUpdates } =
|
||||
useAppUpdater();
|
||||
|
||||
const buttonLabel = useMemo(() => {
|
||||
if (updateAvailable && downloadProgress === null) {
|
||||
return t['com.affine.aboutAFFiNE.checkUpdate.button.download']();
|
||||
}
|
||||
if (updateReady) {
|
||||
return t['com.affine.aboutAFFiNE.checkUpdate.button.restart']();
|
||||
}
|
||||
if (updateAvailable && downloadProgress === null) {
|
||||
return t['com.affine.aboutAFFiNE.checkUpdate.button.download']();
|
||||
}
|
||||
if (
|
||||
checkUpdateStatus === CheckUpdateStatus.LATEST ||
|
||||
checkUpdateStatus === CheckUpdateStatus.ERROR
|
||||
@@ -47,16 +38,16 @@ const useUpdateStatusLabels = (checkUpdateStatus: CheckUpdateStatus) => {
|
||||
}, [checkUpdateStatus, downloadProgress, t, updateAvailable, updateReady]);
|
||||
|
||||
const subtitleLabel = useMemo(() => {
|
||||
if (updateAvailable && downloadProgress === null) {
|
||||
if (updateReady) {
|
||||
return t['com.affine.aboutAFFiNE.checkUpdate.subtitle.restart']();
|
||||
} else if (updateAvailable && downloadProgress === null) {
|
||||
return t['com.affine.aboutAFFiNE.checkUpdate.subtitle.update-available']({
|
||||
version: updateAvailable.version,
|
||||
});
|
||||
} else if (isCheckingForUpdates) {
|
||||
} else if (checkingForUpdates) {
|
||||
return t['com.affine.aboutAFFiNE.checkUpdate.subtitle.checking']();
|
||||
} else if (updateAvailable && downloadProgress !== null) {
|
||||
return t['com.affine.aboutAFFiNE.checkUpdate.subtitle.downloading']();
|
||||
} else if (updateReady) {
|
||||
return t['com.affine.aboutAFFiNE.checkUpdate.subtitle.restart']();
|
||||
} else if (checkUpdateStatus === CheckUpdateStatus.ERROR) {
|
||||
return t['com.affine.aboutAFFiNE.checkUpdate.subtitle.error']();
|
||||
} else if (checkUpdateStatus === CheckUpdateStatus.LATEST) {
|
||||
@@ -66,7 +57,7 @@ const useUpdateStatusLabels = (checkUpdateStatus: CheckUpdateStatus) => {
|
||||
}, [
|
||||
checkUpdateStatus,
|
||||
downloadProgress,
|
||||
isCheckingForUpdates,
|
||||
checkingForUpdates,
|
||||
t,
|
||||
updateAvailable,
|
||||
updateReady,
|
||||
@@ -83,14 +74,14 @@ const useUpdateStatusLabels = (checkUpdateStatus: CheckUpdateStatus) => {
|
||||
error: checkUpdateStatus === CheckUpdateStatus.ERROR,
|
||||
})}
|
||||
>
|
||||
{isCheckingForUpdates ? <Loading size={14} /> : null}
|
||||
{checkingForUpdates ? <Loading size={14} /> : null}
|
||||
{subtitleLabel}
|
||||
</span>
|
||||
);
|
||||
}, [
|
||||
checkUpdateStatus,
|
||||
downloadProgress,
|
||||
isCheckingForUpdates,
|
||||
checkingForUpdates,
|
||||
subtitleLabel,
|
||||
updateAvailable,
|
||||
updateReady,
|
||||
@@ -101,10 +92,14 @@ const useUpdateStatusLabels = (checkUpdateStatus: CheckUpdateStatus) => {
|
||||
|
||||
export const UpdateCheckSection = () => {
|
||||
const t = useAFFiNEI18N();
|
||||
const { checkForUpdates, downloadUpdate, quitAndInstall } = useAppUpdater();
|
||||
const updateAvailable = useAtomValue(updateAvailableAtom);
|
||||
const updateReady = useAtomValue(updateReadyAtom);
|
||||
const downloadProgress = useAtomValue(downloadProgressAtom);
|
||||
const {
|
||||
checkForUpdates,
|
||||
downloadUpdate,
|
||||
quitAndInstall,
|
||||
updateAvailable,
|
||||
downloadProgress,
|
||||
updateReady,
|
||||
} = useAppUpdater();
|
||||
const [checkUpdateStatus, setCheckUpdateStatus] = useState<CheckUpdateStatus>(
|
||||
CheckUpdateStatus.UNCHECK
|
||||
);
|
||||
|
||||
+1
-4
@@ -1,11 +1,8 @@
|
||||
import { Menu, MenuItem, MenuTrigger } from '@affine/component/ui/menu';
|
||||
import { dateFormatOptions, type DateFormats } from '@toeverything/infra/atom';
|
||||
import dayjs from 'dayjs';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import {
|
||||
dateFormatOptions,
|
||||
type DateFormats,
|
||||
} from '../../../../../atoms/settings';
|
||||
import { useAppSettingHelper } from '../../../../../hooks/affine/use-app-setting-helper';
|
||||
|
||||
interface DateFormatMenuContentProps {
|
||||
|
||||
+4
-4
@@ -3,14 +3,14 @@ import { SettingHeader } from '@affine/component/setting-components';
|
||||
import { SettingRow } from '@affine/component/setting-components';
|
||||
import { SettingWrapper } from '@affine/component/setting-components';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { useTheme } from 'next-themes';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import {
|
||||
type AppSetting,
|
||||
fontStyleOptions,
|
||||
windowFrameStyleOptions,
|
||||
} from '../../../../../atoms/settings';
|
||||
} from '@toeverything/infra/atom';
|
||||
import { useTheme } from 'next-themes';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { useAppSettingHelper } from '../../../../../hooks/affine/use-app-setting-helper';
|
||||
import { LanguageMenu } from '../../../language-menu';
|
||||
import { DateFormatSetting } from './date-format-setting';
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-
|
||||
import { useBlockSuiteWorkspacePage } from '@toeverything/hooks/use-block-suite-workspace-page';
|
||||
import { pluginEditorAtom } from '@toeverything/infra/__internal__/plugin';
|
||||
import { getCurrentStore } from '@toeverything/infra/atom';
|
||||
import { fontStyleOptions } from '@toeverything/infra/atom';
|
||||
import clsx from 'clsx';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import type { CSSProperties } from 'react';
|
||||
@@ -15,7 +16,6 @@ import { memo, Suspense, useCallback, useMemo, useState } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import { type PageMode, pageSettingFamily } from '../atoms';
|
||||
import { fontStyleOptions } from '../atoms/settings';
|
||||
import { useAppSettingHelper } from '../hooks/affine/use-app-setting-helper';
|
||||
import { useBlockSuiteMetaHelper } from '../hooks/affine/use-block-suite-meta-helper';
|
||||
import { BlockSuiteEditor as Editor } from './blocksuite/block-suite-editor';
|
||||
|
||||
@@ -20,11 +20,6 @@ import { FolderIcon, SettingsIcon } from '@blocksuite/icons';
|
||||
import type { Page } from '@blocksuite/store';
|
||||
import { useDroppable } from '@dnd-kit/core';
|
||||
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
|
||||
import {
|
||||
isAutoCheckUpdateAtom,
|
||||
isAutoDownloadUpdateAtom,
|
||||
useAppUpdater,
|
||||
} from '@toeverything/hooks/use-app-updater';
|
||||
import { useAtom, useAtomValue } from 'jotai';
|
||||
import type { HTMLAttributes, ReactElement } from 'react';
|
||||
import { forwardRef, useCallback, useEffect, useMemo } from 'react';
|
||||
@@ -107,10 +102,6 @@ export const RootAppSidebar = ({
|
||||
}: RootAppSidebarProps): ReactElement => {
|
||||
const currentWorkspaceId = currentWorkspace.id;
|
||||
const { appSettings } = useAppSettingHelper();
|
||||
const { toggleAutoCheck, toggleAutoDownload } = useAppUpdater();
|
||||
const { autoCheckUpdate, autoDownloadUpdate } = appSettings;
|
||||
const isAutoDownload = useAtomValue(isAutoDownloadUpdateAtom);
|
||||
const isAutoCheck = useAtomValue(isAutoCheckUpdateAtom);
|
||||
const blockSuiteWorkspace = currentWorkspace.blockSuiteWorkspace;
|
||||
const t = useAFFiNEI18N();
|
||||
const [openUserWorkspaceList, setOpenUserWorkspaceList] = useAtom(
|
||||
@@ -159,26 +150,6 @@ export const RootAppSidebar = ({
|
||||
}
|
||||
}, [sidebarOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!environment.isDesktop) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isAutoCheck !== autoCheckUpdate) {
|
||||
toggleAutoCheck(autoCheckUpdate);
|
||||
}
|
||||
if (isAutoDownload !== autoDownloadUpdate) {
|
||||
toggleAutoDownload(autoDownloadUpdate);
|
||||
}
|
||||
}, [
|
||||
autoCheckUpdate,
|
||||
autoDownloadUpdate,
|
||||
isAutoCheck,
|
||||
isAutoDownload,
|
||||
toggleAutoCheck,
|
||||
toggleAutoDownload,
|
||||
]);
|
||||
|
||||
const [history, setHistory] = useHistoryAtom();
|
||||
const router = useMemo(() => {
|
||||
return {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { type AppSetting, appSettingAtom } from '@toeverything/infra/atom';
|
||||
import { useAtom } from 'jotai';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { type AppSetting, appSettingAtom } from '../../atoms/settings';
|
||||
|
||||
export function useAppSettingHelper() {
|
||||
const [appSettings, setAppSettings] = useAtom(appSettingAtom);
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import type { EditorContainer } from '@blocksuite/presets';
|
||||
import type { Page, Workspace } from '@blocksuite/store';
|
||||
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
|
||||
import {
|
||||
appSettingAtom,
|
||||
currentPageIdAtom,
|
||||
currentWorkspaceIdAtom,
|
||||
} from '@toeverything/infra/atom';
|
||||
@@ -36,7 +37,6 @@ import type { Map as YMap } from 'yjs';
|
||||
import { setPageModeAtom } from '../../../atoms';
|
||||
import { collectionsCRUDAtom } from '../../../atoms/collections';
|
||||
import { currentModeAtom } from '../../../atoms/mode';
|
||||
import { appSettingAtom } from '../../../atoms/settings';
|
||||
import { AffineErrorBoundary } from '../../../components/affine/affine-error-boundary';
|
||||
import { HubIsland } from '../../../components/affine/hub-island';
|
||||
import { GlobalPageHistoryModal } from '../../../components/affine/page-history-modal';
|
||||
|
||||
@@ -125,7 +125,7 @@ export function createApplicationMenu() {
|
||||
{
|
||||
label: 'Check for Updates',
|
||||
click: async () => {
|
||||
await checkForUpdates(true);
|
||||
await checkForUpdates();
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -17,9 +17,9 @@ export const quitAndInstall = async () => {
|
||||
autoUpdater.quitAndInstall();
|
||||
};
|
||||
|
||||
let lastCheckTime = 0;
|
||||
|
||||
let downloading = false;
|
||||
let configured = false;
|
||||
let checkingUpdate = false;
|
||||
|
||||
export type UpdaterConfig = {
|
||||
autoCheckUpdate: boolean;
|
||||
@@ -36,29 +36,39 @@ export const getConfig = (): UpdaterConfig => {
|
||||
};
|
||||
|
||||
export const setConfig = (newConfig: Partial<UpdaterConfig> = {}): void => {
|
||||
configured = true;
|
||||
|
||||
Object.assign(config, newConfig);
|
||||
|
||||
logger.info('Updater configured!', config);
|
||||
|
||||
// if config.autoCheckUpdate is true, trigger a check
|
||||
if (config.autoCheckUpdate) {
|
||||
checkForUpdates().catch(err => {
|
||||
logger.error('Error checking for updates', err);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const checkForUpdates = async (force = false) => {
|
||||
if (disabled) {
|
||||
export const checkForUpdates = async () => {
|
||||
if (disabled || checkingUpdate) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
force ||
|
||||
(config.autoCheckUpdate && lastCheckTime + 1000 * 1800 < Date.now())
|
||||
) {
|
||||
lastCheckTime = Date.now();
|
||||
return await autoUpdater.checkForUpdates();
|
||||
checkingUpdate = true;
|
||||
try {
|
||||
const info = await autoUpdater.checkForUpdates();
|
||||
return info;
|
||||
} finally {
|
||||
checkingUpdate = false;
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
export const downloadUpdate = async () => {
|
||||
if (disabled) {
|
||||
if (disabled || downloading) {
|
||||
return;
|
||||
}
|
||||
downloading = true;
|
||||
updaterSubjects.downloadProgress.next(0);
|
||||
autoUpdater.downloadUpdate().catch(e => {
|
||||
downloading = false;
|
||||
logger.error('Failed to download update', e);
|
||||
@@ -96,19 +106,16 @@ export const registerUpdater = async () => {
|
||||
|
||||
autoUpdater.setFeedURL(feedUrl);
|
||||
|
||||
// register events for checkForUpdatesAndNotify
|
||||
// register events for checkForUpdates
|
||||
autoUpdater.on('checking-for-update', () => {
|
||||
logger.info('Checking for update');
|
||||
});
|
||||
autoUpdater.on('update-available', info => {
|
||||
logger.info('Update available', info);
|
||||
if (config.autoDownloadUpdate && allowAutoUpdate && !downloading) {
|
||||
downloading = true;
|
||||
autoUpdater?.downloadUpdate().catch(e => {
|
||||
downloading = false;
|
||||
logger.error('Failed to download update', e);
|
||||
if (config.autoDownloadUpdate && allowAutoUpdate) {
|
||||
downloadUpdate().catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
logger.info('Update available, downloading...', info);
|
||||
}
|
||||
updaterSubjects.updateAvailable.next({
|
||||
version: info.version,
|
||||
@@ -137,9 +144,20 @@ export const registerUpdater = async () => {
|
||||
});
|
||||
autoUpdater.forceDevUpdateConfig = isDev;
|
||||
|
||||
// check update whenever the window is activated
|
||||
let lastCheckTime = 0;
|
||||
app.on('activate', () => {
|
||||
checkForUpdates(false).catch(err => {
|
||||
console.error(err);
|
||||
(async () => {
|
||||
if (
|
||||
configured &&
|
||||
config.autoCheckUpdate &&
|
||||
lastCheckTime + 1000 * 1800 < Date.now()
|
||||
) {
|
||||
lastCheckTime = Date.now();
|
||||
await checkForUpdates();
|
||||
}
|
||||
})().catch(err => {
|
||||
logger.error('Error checking for updates', err);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -29,8 +29,8 @@ export const updaterHandlers = {
|
||||
): Promise<void> => {
|
||||
return setConfig(newConfig);
|
||||
},
|
||||
checkForUpdatesAndNotify: async () => {
|
||||
const res = await checkForUpdates(true);
|
||||
checkForUpdates: async () => {
|
||||
const res = await checkForUpdates();
|
||||
if (res) {
|
||||
const { updateInfo } = res;
|
||||
return {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"foxact": "^0.2.20",
|
||||
"image-blob-reduce": "^4.1.0",
|
||||
"jotai": "^2.5.1",
|
||||
"jotai-effect": "^0.2.3",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"p-queue": "^7.4.1",
|
||||
"react": "18.2.0",
|
||||
@@ -26,6 +27,7 @@
|
||||
"@blocksuite/presets": "0.11.0-nightly-202312070955-2b5bb47",
|
||||
"@blocksuite/store": "0.11.0-nightly-202312070955-2b5bb47",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@toeverything/infra": "workspace:*",
|
||||
"@types/image-blob-reduce": "^4.1.3",
|
||||
"@types/lodash.debounce": "^4.0.7",
|
||||
"fake-indexeddb": "^5.0.0",
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { isBrowser } from '@affine/env/constant';
|
||||
import { appSettingAtom } from '@toeverything/infra/atom';
|
||||
import type { UpdateMeta } from '@toeverything/infra/type';
|
||||
import { atom, useAtomValue, useSetAtom } from 'jotai';
|
||||
import { atom, useAtom, useAtomValue } from 'jotai';
|
||||
import { atomWithObservable, atomWithStorage } from 'jotai/utils';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { useAsyncCallback } from './affine-async-hooks';
|
||||
|
||||
function rpcToObservable<
|
||||
T,
|
||||
H extends () => Promise<T>,
|
||||
@@ -41,25 +44,21 @@ function rpcToObservable<
|
||||
});
|
||||
}
|
||||
|
||||
// download complete, ready to install
|
||||
export const updateReadyAtom = atomWithObservable(() => {
|
||||
return rpcToObservable(null as UpdateMeta | null, {
|
||||
event: window.events?.updater.onUpdateReady,
|
||||
});
|
||||
});
|
||||
|
||||
export const updateAvailableStateAtom = atom<UpdateMeta | null>(null);
|
||||
|
||||
export const updateAvailableAtom = atomWithObservable(get => {
|
||||
return rpcToObservable(get(updateAvailableStateAtom), {
|
||||
// update available, but not downloaded yet
|
||||
export const updateAvailableAtom = atomWithObservable(() => {
|
||||
return rpcToObservable(null as UpdateMeta | null, {
|
||||
event: window.events?.updater.onUpdateAvailable,
|
||||
onSubscribe: () => {
|
||||
window.apis?.updater.checkForUpdatesAndNotify().catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
// downloading new update
|
||||
export const downloadProgressAtom = atomWithObservable(() => {
|
||||
return rpcToObservable(null as number | null, {
|
||||
event: window.events?.updater.onDownloadProgress,
|
||||
@@ -71,6 +70,8 @@ export const changelogCheckedAtom = atomWithStorage<Record<string, boolean>>(
|
||||
{}
|
||||
);
|
||||
|
||||
export const checkingForUpdatesAtom = atom(false);
|
||||
|
||||
export const currentVersionAtom = atom(async () => {
|
||||
if (!isBrowser) {
|
||||
return null;
|
||||
@@ -79,29 +80,43 @@ export const currentVersionAtom = atom(async () => {
|
||||
return currentVersion;
|
||||
});
|
||||
|
||||
export const currentChangelogUnreadAtom = atom(async get => {
|
||||
if (!isBrowser) {
|
||||
const currentChangelogUnreadAtom = atom(
|
||||
async get => {
|
||||
if (!isBrowser) {
|
||||
return false;
|
||||
}
|
||||
const mapping = get(changelogCheckedAtom);
|
||||
const currentVersion = await get(currentVersionAtom);
|
||||
if (currentVersion) {
|
||||
return !mapping[currentVersion];
|
||||
}
|
||||
return false;
|
||||
},
|
||||
async (get, set, v: boolean) => {
|
||||
const currentVersion = await get(currentVersionAtom);
|
||||
if (currentVersion) {
|
||||
set(changelogCheckedAtom, mapping => {
|
||||
return {
|
||||
...mapping,
|
||||
[currentVersion]: v,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
const mapping = get(changelogCheckedAtom);
|
||||
const currentVersion = await get(currentVersionAtom);
|
||||
if (currentVersion) {
|
||||
return !mapping[currentVersion];
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
export const isCheckingForUpdatesAtom = atom(false);
|
||||
export const isAutoDownloadUpdateAtom = atom(true);
|
||||
export const isAutoCheckUpdateAtom = atom(true);
|
||||
);
|
||||
|
||||
export const useAppUpdater = () => {
|
||||
const [appQuitting, setAppQuitting] = useState(false);
|
||||
const updateReady = useAtomValue(updateReadyAtom);
|
||||
const setUpdateAvailableState = useSetAtom(updateAvailableStateAtom);
|
||||
const setIsCheckingForUpdates = useSetAtom(isCheckingForUpdatesAtom);
|
||||
const setIsAutoCheckUpdate = useSetAtom(isAutoCheckUpdateAtom);
|
||||
const setIsAutoDownloadUpdate = useSetAtom(isAutoDownloadUpdateAtom);
|
||||
const [setting, setSetting] = useAtom(appSettingAtom);
|
||||
const downloadProgress = useAtomValue(downloadProgressAtom);
|
||||
const [changelogUnread, setChangelogUnread] = useAtom(
|
||||
currentChangelogUnreadAtom
|
||||
);
|
||||
|
||||
const [checkingForUpdates, setCheckingForUpdates] = useAtom(
|
||||
checkingForUpdatesAtom
|
||||
);
|
||||
|
||||
const quitAndInstall = useCallback(() => {
|
||||
if (updateReady) {
|
||||
@@ -114,73 +129,64 @@ export const useAppUpdater = () => {
|
||||
}, [updateReady]);
|
||||
|
||||
const checkForUpdates = useCallback(async () => {
|
||||
setIsCheckingForUpdates(true);
|
||||
if (checkingForUpdates) {
|
||||
return;
|
||||
}
|
||||
setCheckingForUpdates(true);
|
||||
try {
|
||||
const updateInfo = await window.apis?.updater.checkForUpdatesAndNotify();
|
||||
setIsCheckingForUpdates(false);
|
||||
if (updateInfo) {
|
||||
const updateMeta: UpdateMeta = {
|
||||
version: updateInfo.version,
|
||||
allowAutoUpdate: false,
|
||||
};
|
||||
setUpdateAvailableState(updateMeta);
|
||||
return updateInfo.version;
|
||||
}
|
||||
return false;
|
||||
const updateInfo = await window.apis?.updater.checkForUpdates();
|
||||
return updateInfo?.version ?? false;
|
||||
} catch (err) {
|
||||
setIsCheckingForUpdates(false);
|
||||
console.error('Error checking for updates:', err);
|
||||
return null;
|
||||
} finally {
|
||||
setCheckingForUpdates(false);
|
||||
}
|
||||
}, [setIsCheckingForUpdates, setUpdateAvailableState]);
|
||||
}, [checkingForUpdates, setCheckingForUpdates]);
|
||||
|
||||
const downloadUpdate = useCallback(() => {
|
||||
window.apis?.updater
|
||||
.downloadUpdate()
|
||||
.then(() => {})
|
||||
.catch(err => {
|
||||
console.error('Error downloading update:', err);
|
||||
});
|
||||
window.apis?.updater.downloadUpdate().catch(err => {
|
||||
console.error('Error downloading update:', err);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const toggleAutoDownload = useCallback(
|
||||
(enable: boolean) => {
|
||||
window.apis?.updater
|
||||
.setConfig({
|
||||
autoDownloadUpdate: enable,
|
||||
})
|
||||
.then(() => {
|
||||
setIsAutoDownloadUpdate(enable);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Error setting auto download:', err);
|
||||
});
|
||||
setSetting({
|
||||
autoDownloadUpdate: enable,
|
||||
});
|
||||
},
|
||||
[setIsAutoDownloadUpdate]
|
||||
[setSetting]
|
||||
);
|
||||
|
||||
const toggleAutoCheck = useCallback(
|
||||
(enable: boolean) => {
|
||||
window.apis?.updater
|
||||
.setConfig({
|
||||
autoCheckUpdate: enable,
|
||||
})
|
||||
.then(() => {
|
||||
setIsAutoCheckUpdate(enable);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Error setting auto check:', err);
|
||||
});
|
||||
setSetting({
|
||||
autoCheckUpdate: enable,
|
||||
});
|
||||
},
|
||||
[setIsAutoCheckUpdate]
|
||||
[setSetting]
|
||||
);
|
||||
|
||||
const readChangelog = useAsyncCallback(async () => {
|
||||
await setChangelogUnread(true);
|
||||
}, [setChangelogUnread]);
|
||||
|
||||
return {
|
||||
quitAndInstall,
|
||||
appQuitting,
|
||||
checkForUpdates,
|
||||
downloadUpdate,
|
||||
toggleAutoDownload,
|
||||
toggleAutoCheck,
|
||||
appQuitting,
|
||||
checkingForUpdates,
|
||||
autoCheck: setting.autoCheckUpdate,
|
||||
autoDownload: setting.autoDownloadUpdate,
|
||||
changelogUnread,
|
||||
readChangelog,
|
||||
updateReady,
|
||||
updateAvailable: useAtomValue(updateAvailableAtom),
|
||||
downloadProgress,
|
||||
currentVersion: useAtomValue(currentVersionAtom),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"references": [
|
||||
{ "path": "../../common/env" },
|
||||
{ "path": "../../common/y-indexeddb" },
|
||||
{ "path": "../../common/debug" }
|
||||
{ "path": "../../common/debug" },
|
||||
{ "path": "../../common/infra" }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -373,6 +373,7 @@
|
||||
"com.affine.appUpdater.downloading": "Downloading",
|
||||
"com.affine.appUpdater.installUpdate": "Restart to install update",
|
||||
"com.affine.appUpdater.openDownloadPage": "Open download page",
|
||||
"com.affine.appUpdater.downloadUpdate": "Download update",
|
||||
"com.affine.appUpdater.updateAvailable": "Update available",
|
||||
"com.affine.appUpdater.whatsNew": "Discover what's new!",
|
||||
"com.affine.appearanceSettings.clientBorder.description": "Customise the appearance of the client.",
|
||||
|
||||
@@ -402,6 +402,7 @@ __metadata:
|
||||
intl-segmenter-polyfill-rs: "npm:^0.1.6"
|
||||
jotai: "npm:^2.5.1"
|
||||
jotai-devtools: "npm:^0.7.0"
|
||||
jotai-effect: "npm:^0.2.3"
|
||||
lit: "npm:^3.0.2"
|
||||
lodash-es: "npm:^4.17.21"
|
||||
lottie-web: "npm:^5.12.2"
|
||||
@@ -524,7 +525,6 @@ __metadata:
|
||||
peerDependencies:
|
||||
"@affine/templates": "workspace:*"
|
||||
"@blocksuite/global": 0.0.0-20230409084303-221991d4-nightly
|
||||
"@toeverything/infra": "workspace:*"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
@@ -13665,12 +13665,14 @@ __metadata:
|
||||
"@blocksuite/presets": "npm:0.11.0-nightly-202312070955-2b5bb47"
|
||||
"@blocksuite/store": "npm:0.11.0-nightly-202312070955-2b5bb47"
|
||||
"@testing-library/react": "npm:^14.0.0"
|
||||
"@toeverything/infra": "workspace:*"
|
||||
"@types/image-blob-reduce": "npm:^4.1.3"
|
||||
"@types/lodash.debounce": "npm:^4.0.7"
|
||||
fake-indexeddb: "npm:^5.0.0"
|
||||
foxact: "npm:^0.2.20"
|
||||
image-blob-reduce: "npm:^4.1.0"
|
||||
jotai: "npm:^2.5.1"
|
||||
jotai-effect: "npm:^0.2.3"
|
||||
lodash.debounce: "npm:^4.0.8"
|
||||
p-queue: "npm:^7.4.1"
|
||||
react: "npm:18.2.0"
|
||||
@@ -13709,6 +13711,7 @@ __metadata:
|
||||
resolution: "@toeverything/infra@workspace:packages/common/infra"
|
||||
dependencies:
|
||||
"@affine-test/fixtures": "workspace:*"
|
||||
"@affine/env": "workspace:*"
|
||||
"@affine/sdk": "workspace:*"
|
||||
"@affine/templates": "workspace:*"
|
||||
"@blocksuite/blocks": "npm:0.11.0-nightly-202312070955-2b5bb47"
|
||||
|
||||
Reference in New Issue
Block a user