mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-11 20:08:37 +00: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:
@@ -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',
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user