feat(core): implement parts of workspace upgrade design (#4850)

This commit is contained in:
Joooye_34 2023-11-07 01:02:25 +08:00 committed by GitHub
parent 8554d5d791
commit 7a4669a6aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 206 additions and 38 deletions

View File

@ -0,0 +1 @@
export { WorkspaceUpgrade } from './upgrade';

View File

@ -4,15 +4,18 @@ import type {
SQLiteProvider,
} from '@affine/env/workspace';
import { assertExists } from '@blocksuite/global/utils';
import { Button } from '@toeverything/components/button';
import { forceUpgradePages } from '@toeverything/infra/blocksuite';
import { useCallback, useMemo, useState } from 'react';
import { syncDataSourceFromDoc, syncDocFromDataSource } from 'y-provider';
import { useCurrentWorkspace } from '../hooks/current/use-current-workspace';
import { useCurrentWorkspace } from '../../hooks/current/use-current-workspace';
export type UpgradeState = 'pending' | 'upgrading' | 'done' | 'error';
export function useUpgradeWorkspace() {
const [state, setState] = useState<UpgradeState>('pending');
const [error, setError] = useState<Error | null>(null);
export const MigrationFallback = function MigrationFallback() {
const [done, setDone] = useState(false);
const [workspace] = useCurrentWorkspace();
const providers = workspace.blockSuiteWorkspace.providers;
const remoteProvider: AffineSocketIOProvider | undefined = useMemo(() => {
@ -33,46 +36,50 @@ export const MigrationFallback = function MigrationFallback() {
assertExists(provider, 'no local provider');
return provider;
}, [providers]);
const handleClick = useCallback(async () => {
setDone(false);
await syncDocFromDataSource(
workspace.blockSuiteWorkspace.doc,
localProvider.datasource
);
if (remoteProvider) {
const upgradeWorkspace = useCallback(() => {
setState('upgrading');
setError(null);
(async () => {
await syncDocFromDataSource(
workspace.blockSuiteWorkspace.doc,
remoteProvider.datasource
localProvider.datasource
);
}
if (remoteProvider) {
await syncDocFromDataSource(
workspace.blockSuiteWorkspace.doc,
remoteProvider.datasource
);
}
await forceUpgradePages({
getCurrentRootDoc: async () => workspace.blockSuiteWorkspace.doc,
getSchema: () => workspace.blockSuiteWorkspace.schema,
});
await syncDataSourceFromDoc(
workspace.blockSuiteWorkspace.doc,
localProvider.datasource
);
if (remoteProvider) {
await forceUpgradePages({
getCurrentRootDoc: async () => workspace.blockSuiteWorkspace.doc,
getSchema: () => workspace.blockSuiteWorkspace.schema,
});
await syncDataSourceFromDoc(
workspace.blockSuiteWorkspace.doc,
remoteProvider.datasource
localProvider.datasource
);
}
setDone(true);
if (remoteProvider) {
await syncDataSourceFromDoc(
workspace.blockSuiteWorkspace.doc,
remoteProvider.datasource
);
}
setState('done');
})().catch((e: any) => {
console.error(e);
setError(e);
setState('error');
});
}, [
localProvider.datasource,
remoteProvider,
workspace.blockSuiteWorkspace.doc,
workspace.blockSuiteWorkspace.schema,
]);
if (done) {
return <div>Done, please refresh the page.</div>;
}
return (
<Button data-testid="upgrade-workspace" onClick={handleClick}>
Upgrade Workspace
</Button>
);
};
return [state, error, upgradeWorkspace] as const;
}

View File

@ -0,0 +1,44 @@
import type React from 'react';
/**
* TODO: Define a place to manage all icons and svg images.
*/
export const ArrowCircleIcon = (props: React.SVGProps<SVGSVGElement>) => {
return (
<svg
width="20"
height="20"
viewBox="0 0 21 21"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
fill="currentColor"
d="M3.20837 10.7372C3.20837 6.71015 6.47296 3.44556 10.5 3.44556C12.3672 3.44556 14.0717 4.14815 15.3613 5.30239L15.3633 5.30417L16.5417 6.36801V4.07056C16.5417 3.72538 16.8215 3.44556 17.1667 3.44556C17.5119 3.44556 17.7917 3.72538 17.7917 4.07056V7.77426C17.7917 8.11944 17.5119 8.39926 17.1667 8.39926H13.463C13.1178 8.39926 12.838 8.11944 12.838 7.77426C12.838 7.42908 13.1178 7.14926 13.463 7.14926H15.5417L14.5277 6.23381C14.5273 6.23351 14.527 6.23321 14.5267 6.23291C13.4575 5.2764 12.0473 4.69556 10.5 4.69556C7.16332 4.69556 4.45837 7.4005 4.45837 10.7372C4.45837 14.0739 7.16332 16.7789 10.5 16.7789C13.3745 16.7789 15.7815 14.7708 16.392 12.0804C16.4684 11.7438 16.8032 11.5328 17.1398 11.6092C17.4764 11.6856 17.6874 12.0204 17.611 12.357C16.8742 15.6043 13.971 18.0289 10.5 18.0289C6.47296 18.0289 3.20837 14.7643 3.20837 10.7372Z"
/>
</svg>
);
};
export const HeartBreakIcon = (props: React.SVGProps<SVGSVGElement>) => {
return (
<svg
width="20"
height="20"
viewBox="0 0 20 21"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
fill="currentColor"
d="M9.99962 4.57817C8.29319 3.01965 5.71364 2.60583 3.71072 4.31175C1.58022 6.12634 1.27156 9.1804 2.95519 11.34C3.60283 12.1708 4.87307 13.4256 6.10152 14.5814C7.34198 15.7486 8.57984 16.8517 9.1904 17.3907L9.20124 17.4003C9.25961 17.4519 9.32753 17.5118 9.3919 17.5602C9.46767 17.617 9.57193 17.6842 9.70946 17.7251C9.89501 17.7803 10.0946 17.7803 10.2802 17.7251C10.4177 17.6842 10.522 17.617 10.5977 17.5602C10.6621 17.5118 10.73 17.4519 10.7884 17.4003L10.7992 17.3907C11.4098 16.8517 12.6477 15.7486 13.8881 14.5814C15.1166 13.4256 16.3868 12.1708 17.0344 11.34C18.7125 9.18752 18.4501 6.11052 16.2724 4.30628C14.2452 2.62668 11.7026 3.01918 9.99962 4.57817ZM10.8772 5.46815L12.1086 6.69955C12.2506 6.84161 12.3157 7.0433 12.2836 7.24161C12.2514 7.43991 12.1258 7.61066 11.9461 7.70051L9.38839 8.97938L11.2752 10.8662C11.5193 11.1103 11.5193 11.506 11.2752 11.7501C11.0312 11.9942 10.6354 11.9942 10.3913 11.7501L7.89135 9.2501C7.74929 9.10804 7.68418 8.90635 7.71636 8.70804C7.74854 8.50974 7.87409 8.33899 8.05378 8.24914L10.6115 6.97027L9.55801 5.91677C9.54678 5.90554 9.53598 5.89388 9.52564 5.88183C8.19717 4.33364 6.07983 3.93588 4.52123 5.26337C2.89416 6.64918 2.67381 8.94603 3.94102 10.5715C4.5201 11.3143 5.71945 12.5056 6.95809 13.6711C8.16952 14.8109 9.38104 15.8914 9.99482 16.4335C10.6086 15.8914 11.8201 14.8109 13.0315 13.6711C14.2702 12.5056 15.4695 11.3143 16.0486 10.5715C17.3214 8.93892 17.1194 6.63133 15.4749 5.26884C14.0321 4.07341 12.1948 4.27909 10.8772 5.46815Z"
/>
</svg>
);
};

View File

@ -0,0 +1,32 @@
import { keyframes, style } from '@vanilla-extract/css';
export const layout = style({
margin: '80px auto',
maxWidth: '536px',
});
export const upgradeBox = style({
padding: '48px 52px',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
});
export const upgradeTips = style({
margin: '20px 0',
color: 'var(--affine-text-secondary-color)',
fontSize: '12px',
fontStyle: 'normal',
fontWeight: '400',
lineHeight: '20px',
});
const rotate = keyframes({
'0%': { transform: 'rotate(0deg)' },
'50%': { transform: 'rotate(180deg)' },
'100%': { transform: 'rotate(360deg)' },
});
export const loadingIcon = style({
animation: `${rotate} 1.2s infinite linear`,
});

View File

@ -0,0 +1,77 @@
import { AffineShapeIcon } from '@affine/component/page-list'; // TODO: import from page-list temporarily, need to defined common svg icon/images management.
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { Button } from '@toeverything/components/button';
import { useCallback, useMemo } from 'react';
import * as styles from './upgrade.css';
import { type UpgradeState, useUpgradeWorkspace } from './upgrade-hooks';
import { ArrowCircleIcon, HeartBreakIcon } from './upgrade-icon';
const UPGRADE_TIPS_KEYS = {
pending: 'com.affine.upgrade.tips.normal',
upgrading: 'com.affine.upgrade.tips.normal',
done: 'com.affine.upgrade.tips.done',
error: 'com.affine.upgrade.tips.error',
} as const;
const BUTTON_TEXT_KEYS = {
pending: 'com.affine.upgrade.button-text.pending',
upgrading: 'com.affine.upgrade.button-text.upgrading',
done: 'com.affine.upgrade.button-text.done',
error: 'com.affine.upgrade.button-text.error',
} as const;
function UpgradeIcon({ upgradeState }: { upgradeState: UpgradeState }) {
if (upgradeState === 'error') {
return <HeartBreakIcon />;
}
return (
<ArrowCircleIcon
className={upgradeState === 'upgrading' ? styles.loadingIcon : undefined}
/>
);
}
/**
* TODO: Help info is not implemented yet.
*/
export const WorkspaceUpgrade = function MigrationFallback() {
const [upgradeState, , upgradeWorkspace] = useUpgradeWorkspace();
const t = useAFFiNEI18N();
const refreshPage = useCallback(() => {
window.location.reload();
}, []);
const onButtonClick = useMemo(() => {
if (upgradeState === 'done') {
return refreshPage;
}
if (upgradeState === 'pending') {
return upgradeWorkspace;
}
return undefined;
}, [upgradeState, upgradeWorkspace, refreshPage]);
return (
<div className={styles.layout}>
<div className={styles.upgradeBox}>
<AffineShapeIcon width={180} height={180} />
<p className={styles.upgradeTips}>
{t[UPGRADE_TIPS_KEYS[upgradeState]]()}
</p>
<Button
data-testid="upgrade-workspace-button"
onClick={onButtonClick}
size="extraLarge"
icon={<UpgradeIcon upgradeState={upgradeState} />}
type={upgradeState === 'error' ? 'error' : 'default'}
>
{t[BUTTON_TEXT_KEYS[upgradeState]]()}
</Button>
</div>
</div>
);
};

View File

@ -44,7 +44,6 @@ import { mainContainerAtom } from '../atoms/element';
import { AdapterProviderWrapper } from '../components/adapter-worksapce-wrapper';
import { AppContainer } from '../components/affine/app-container';
import { usePageHelper } from '../components/blocksuite/block-suite-page-list/utils';
import { MigrationFallback } from '../components/migration-fallback';
import type { IslandItemNames } from '../components/pure/help-island';
import { HelpIsland } from '../components/pure/help-island';
import { processCollectionsDrag } from '../components/pure/workspace-slider-bar/collections';
@ -52,6 +51,7 @@ import {
DROPPABLE_SIDEBAR_TRASH,
RootAppSidebar,
} from '../components/root-app-sidebar';
import { WorkspaceUpgrade } from '../components/workspace-upgrade';
import { useAppSettingHelper } from '../hooks/affine/use-app-setting-helper';
import { useBlockSuiteMetaHelper } from '../hooks/affine/use-block-suite-meta-helper';
import { useCurrentWorkspace } from '../hooks/current/use-current-workspace';
@ -297,7 +297,7 @@ export const WorkspaceLayoutInner = ({
padding={appSettings.clientBorder}
inTrashPage={inTrashPage}
>
{incompatible ? <MigrationFallback /> : children}
{incompatible ? <WorkspaceUpgrade /> : children}
<ToolContainer inTrashPage={inTrashPage}>
<BlockHubWrapper blockHubAtom={rootBlockHubAtom} />
<HelpIsland showList={pageId ? undefined : showList} />

View File

@ -775,5 +775,12 @@
"com.affine.payment.member.description": "Manage members here. {{planName}} Users can invite up to {{memberLimit}}",
"com.affine.cmdk.affine.switch-state.on": "ON",
"com.affine.cmdk.affine.switch-state.off": "OFF",
"com.affine.upgrade.button-text.pending": "Upgrade Workspace Data",
"com.affine.upgrade.button-text.upgrading": "Upgrading",
"com.affine.upgrade.button-text.done": "Refresh Current Page",
"com.affine.upgrade.button-text.error": "Data Upgrade Error",
"com.affine.upgrade.tips.normal": "To ensure compatibility with the updated AFFiNE client, please upgrade your data by clicking the \"Upgrade Workspace Data\" button below.",
"com.affine.upgrade.tips.done": "After upgrading the workspace data, please refresh the page to see the changes.",
"com.affine.upgrade.tips.error": "We encountered some errors while upgrading the workspace data.",
"com.affine.workspaceSubPath.trash.empty-description": "Deleted pages will appear here."
}

View File

@ -95,8 +95,8 @@ test.describe('basic', () => {
await page.reload();
await page.waitForTimeout(1000);
await page.goto(`${coreUrl}/workspace/${workspaceId}/all`);
await page.getByTestId('upgrade-workspace').click();
await expect(page.getByText('Done, please refresh the page.')).toBeVisible({
await page.getByTestId('upgrade-workspace-button').click();
await expect(page.getByText('Refresh Current Page')).toBeVisible({
timeout: 60000,
});
await page.goto(