mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-14 05:14:54 +00:00
feat(core): support creating cloud workspaces to different servers (#9006)
This commit is contained in:
@@ -23,6 +23,8 @@ import * as styles from './dialog.css';
|
||||
|
||||
interface NameWorkspaceContentProps extends ConfirmModalProps {
|
||||
loading: boolean;
|
||||
forcedCloud?: boolean;
|
||||
serverId?: string;
|
||||
onConfirmName: (
|
||||
name: string,
|
||||
workspaceFlavour: string,
|
||||
@@ -33,15 +35,14 @@ interface NameWorkspaceContentProps extends ConfirmModalProps {
|
||||
const NameWorkspaceContent = ({
|
||||
loading,
|
||||
onConfirmName,
|
||||
forcedCloud,
|
||||
serverId,
|
||||
...props
|
||||
}: NameWorkspaceContentProps) => {
|
||||
const t = useI18n();
|
||||
const [workspaceName, setWorkspaceName] = useState('');
|
||||
const featureFlagService = useService(FeatureFlagService);
|
||||
const enableLocalWorkspace = useLiveData(
|
||||
featureFlagService.flags.enable_local_workspace.$
|
||||
);
|
||||
const [enable, setEnable] = useState(!enableLocalWorkspace);
|
||||
|
||||
const [enable, setEnable] = useState(!!forcedCloud);
|
||||
const session = useService(AuthService).session;
|
||||
const loginStatus = useLiveData(session.status$);
|
||||
|
||||
@@ -62,8 +63,8 @@ const NameWorkspaceContent = ({
|
||||
);
|
||||
|
||||
const handleCreateWorkspace = useCallback(() => {
|
||||
onConfirmName(workspaceName, enable ? 'affine-cloud' : 'local');
|
||||
}, [enable, onConfirmName, workspaceName]);
|
||||
onConfirmName(workspaceName, enable ? serverId || 'affine-cloud' : 'local');
|
||||
}, [enable, onConfirmName, serverId, workspaceName]);
|
||||
|
||||
const onEnter = useCallback(() => {
|
||||
if (workspaceName) {
|
||||
@@ -120,7 +121,7 @@ const NameWorkspaceContent = ({
|
||||
<Switch
|
||||
checked={enable}
|
||||
onChange={onSwitchChange}
|
||||
disabled={!enableLocalWorkspace}
|
||||
disabled={forcedCloud}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.cardDescription}>
|
||||
@@ -131,7 +132,7 @@ const NameWorkspaceContent = ({
|
||||
<CloudSvg />
|
||||
</div>
|
||||
</div>
|
||||
{!enableLocalWorkspace ? (
|
||||
{forcedCloud ? (
|
||||
<a
|
||||
className={styles.cloudTips}
|
||||
href={BUILD_CONFIG.downloadUrl}
|
||||
@@ -147,9 +148,15 @@ const NameWorkspaceContent = ({
|
||||
};
|
||||
|
||||
export const CreateWorkspaceDialog = ({
|
||||
forcedCloud,
|
||||
serverId,
|
||||
close,
|
||||
}: DialogComponentProps<GLOBAL_DIALOG_SCHEMA['create-workspace']>) => {
|
||||
const workspacesService = useService(WorkspacesService);
|
||||
const featureFlagService = useService(FeatureFlagService);
|
||||
const enableLocalWorkspace = useLiveData(
|
||||
featureFlagService.flags.enable_local_workspace.$
|
||||
);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const onConfirmName = useAsyncCallback(
|
||||
@@ -185,6 +192,8 @@ export const CreateWorkspaceDialog = ({
|
||||
<NameWorkspaceContent
|
||||
loading={loading}
|
||||
open
|
||||
serverId={serverId}
|
||||
forcedCloud={forcedCloud || !enableLocalWorkspace}
|
||||
onOpenChange={onOpenChange}
|
||||
onConfirmName={onConfirmName}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
import { cssVar } from '@toeverything/theme';
|
||||
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
export const dialogContainer = style({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
color: cssVarV2('text/primary'),
|
||||
padding: '16px',
|
||||
});
|
||||
|
||||
export const mainIcon = style({
|
||||
width: 36,
|
||||
height: 36,
|
||||
color: cssVarV2('icon/primary'),
|
||||
});
|
||||
|
||||
export const mainTitle = style({
|
||||
fontSize: '18px',
|
||||
lineHeight: '26px',
|
||||
textAlign: 'center',
|
||||
marginTop: '16px',
|
||||
fontWeight: 600,
|
||||
});
|
||||
|
||||
export const desc = style({
|
||||
textAlign: 'center',
|
||||
color: cssVarV2('text/secondary'),
|
||||
marginBottom: '20px',
|
||||
});
|
||||
|
||||
export const mainButton = style({
|
||||
width: '100%',
|
||||
fontSize: '14px',
|
||||
height: '42px',
|
||||
});
|
||||
|
||||
export const modal = style({
|
||||
maxWidth: '352px',
|
||||
});
|
||||
|
||||
export const workspaceSelector = style({
|
||||
margin: '0 -16px',
|
||||
width: 'calc(100% + 32px)',
|
||||
border: `1px solid ${cssVarV2('layer/insideBorder/border')}`,
|
||||
padding: '0 16px',
|
||||
});
|
||||
|
||||
export const root = style({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
gap: '20px',
|
||||
});
|
||||
|
||||
export const textContainer = style({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
textAlign: 'center',
|
||||
});
|
||||
|
||||
export const title = style({
|
||||
fontSize: cssVar('fontH6'),
|
||||
fontWeight: 600,
|
||||
lineHeight: '26px',
|
||||
});
|
||||
|
||||
export const description = style({
|
||||
fontSize: cssVar('fontBase'),
|
||||
fontWeight: 400,
|
||||
lineHeight: '24px',
|
||||
color: cssVar('textSecondaryColor'),
|
||||
});
|
||||
|
||||
export const serverSelector = style({
|
||||
width: '100%',
|
||||
});
|
||||
|
||||
export const button = style({
|
||||
width: '100%',
|
||||
marginTop: '20px',
|
||||
});
|
||||
@@ -0,0 +1,169 @@
|
||||
import { Button, Modal, notify } from '@affine/component';
|
||||
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
||||
import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-helper';
|
||||
import { ServerSelector } from '@affine/core/components/server-selector';
|
||||
import {
|
||||
AuthService,
|
||||
type Server,
|
||||
ServersService,
|
||||
} from '@affine/core/modules/cloud';
|
||||
import {
|
||||
type DialogComponentProps,
|
||||
type GLOBAL_DIALOG_SCHEMA,
|
||||
GlobalDialogService,
|
||||
} from '@affine/core/modules/dialogs';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { CloudWorkspaceIcon } from '@blocksuite/icons/rc';
|
||||
import {
|
||||
FrameworkScope,
|
||||
useLiveData,
|
||||
useService,
|
||||
WorkspacesService,
|
||||
} from '@toeverything/infra';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
import * as styles from './dialog.css';
|
||||
|
||||
const Dialog = ({
|
||||
workspaceId,
|
||||
close,
|
||||
selectedServer,
|
||||
setSelectedServer,
|
||||
serverList,
|
||||
openPageId,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
serverList: Server[];
|
||||
selectedServer: Server;
|
||||
setSelectedServer: (server: Server) => void;
|
||||
openPageId?: string;
|
||||
serverId?: string;
|
||||
close?: () => void;
|
||||
}) => {
|
||||
const t = useI18n();
|
||||
const authService = useService(AuthService);
|
||||
const account = useLiveData(authService.session.account$);
|
||||
const loginStatus = useLiveData(useService(AuthService).session.status$);
|
||||
const globalDialogService = useService(GlobalDialogService);
|
||||
const workspacesService = useService(WorkspacesService);
|
||||
const workspaceMeta = useLiveData(
|
||||
workspacesService.list.workspace$(workspaceId)
|
||||
);
|
||||
const { workspace } = workspaceMeta
|
||||
? workspacesService.open({ metadata: workspaceMeta })
|
||||
: { workspace: undefined };
|
||||
|
||||
const { jumpToPage } = useNavigateHelper();
|
||||
|
||||
const enableCloud = useCallback(async () => {
|
||||
try {
|
||||
if (!workspace) return;
|
||||
if (!account) return;
|
||||
|
||||
const { id: newId } = await workspacesService.transformLocalToCloud(
|
||||
workspace,
|
||||
account.id,
|
||||
selectedServer.id
|
||||
);
|
||||
jumpToPage(newId, openPageId || 'all');
|
||||
close?.();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
notify.error({
|
||||
title: t['com.affine.workspace.enable-cloud.failed'](),
|
||||
});
|
||||
}
|
||||
}, [
|
||||
workspace,
|
||||
account,
|
||||
workspacesService,
|
||||
selectedServer.id,
|
||||
jumpToPage,
|
||||
openPageId,
|
||||
close,
|
||||
t,
|
||||
]);
|
||||
|
||||
const openSignIn = useCallback(() => {
|
||||
globalDialogService.open('sign-in', {
|
||||
server: selectedServer.baseUrl,
|
||||
});
|
||||
}, [globalDialogService, selectedServer.baseUrl]);
|
||||
|
||||
const signInOrEnableCloud = useAsyncCallback(async () => {
|
||||
// not logged in, open login modal
|
||||
if (loginStatus === 'unauthenticated') {
|
||||
openSignIn();
|
||||
}
|
||||
|
||||
if (loginStatus === 'authenticated') {
|
||||
await enableCloud();
|
||||
}
|
||||
}, [enableCloud, loginStatus, openSignIn]);
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
<CloudWorkspaceIcon width={'36px'} height={'36px'} />
|
||||
<div className={styles.textContainer}>
|
||||
<div className={styles.title}>
|
||||
{t['com.affine.enableAffineCloudModal.custom-server.title']({
|
||||
workspaceName: workspace?.name$.value || 'untitled',
|
||||
})}
|
||||
</div>
|
||||
<div className={styles.description}>
|
||||
{t['com.affine.enableAffineCloudModal.custom-server.description']()}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.serverSelector}>
|
||||
<ServerSelector
|
||||
servers={serverList}
|
||||
selectedSeverName={`${selectedServer.config$.value.serverName} (${selectedServer.baseUrl})`}
|
||||
onSelect={setSelectedServer}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
className={styles.button}
|
||||
onClick={signInOrEnableCloud}
|
||||
size="extraLarge"
|
||||
variant="primary"
|
||||
>
|
||||
{t['com.affine.enableAffineCloudModal.custom-server.enable']()}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const EnableCloudDialog = ({
|
||||
workspaceId,
|
||||
openPageId,
|
||||
serverId,
|
||||
close,
|
||||
}: DialogComponentProps<GLOBAL_DIALOG_SCHEMA['enable-cloud']>) => {
|
||||
const serversService = useService(ServersService);
|
||||
const serverList = useLiveData(serversService.servers$);
|
||||
const [selectedServer, setSelectedServer] = useState<Server>(serverList[0]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open
|
||||
modal={true}
|
||||
persistent
|
||||
contentOptions={{
|
||||
className: styles.modal,
|
||||
}}
|
||||
onOpenChange={() => close()}
|
||||
>
|
||||
<FrameworkScope key={selectedServer.id} scope={selectedServer.scope}>
|
||||
<Dialog
|
||||
workspaceId={workspaceId}
|
||||
openPageId={openPageId}
|
||||
serverId={serverId}
|
||||
close={close}
|
||||
serverList={serverList}
|
||||
selectedServer={selectedServer}
|
||||
setSelectedServer={setSelectedServer}
|
||||
/>
|
||||
</FrameworkScope>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -11,6 +11,7 @@ import { ChangePasswordDialog } from './change-password';
|
||||
import { CollectionEditorDialog } from './collection-editor';
|
||||
import { CreateWorkspaceDialog } from './create-workspace';
|
||||
import { DocInfoDialog } from './doc-info';
|
||||
import { EnableCloudDialog } from './enable-cloud';
|
||||
import { ImportDialog } from './import';
|
||||
import { ImportTemplateDialog } from './import-template';
|
||||
import { ImportWorkspaceDialog } from './import-workspace';
|
||||
@@ -30,6 +31,7 @@ const GLOBAL_DIALOGS = {
|
||||
'sign-in': SignInDialog,
|
||||
'change-password': ChangePasswordDialog,
|
||||
'verify-email': VerifyEmailDialog,
|
||||
'enable-cloud': EnableCloudDialog,
|
||||
} satisfies {
|
||||
[key in keyof GLOBAL_DIALOG_SCHEMA]?: React.FC<
|
||||
DialogComponentProps<GLOBAL_DIALOG_SCHEMA[key]>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Modal } from '@affine/component';
|
||||
import { SignInPanel } from '@affine/core/components/sign-in';
|
||||
import { SignInPanel, type SignInStep } from '@affine/core/components/sign-in';
|
||||
import type {
|
||||
DialogComponentProps,
|
||||
GLOBAL_DIALOG_SCHEMA,
|
||||
@@ -7,6 +7,7 @@ import type {
|
||||
export const SignInDialog = ({
|
||||
close,
|
||||
server: initialServerBaseUrl,
|
||||
step,
|
||||
}: DialogComponentProps<GLOBAL_DIALOG_SCHEMA['sign-in']>) => {
|
||||
return (
|
||||
<Modal
|
||||
@@ -19,7 +20,11 @@ export const SignInDialog = ({
|
||||
style: { padding: '44px 40px 20px' },
|
||||
}}
|
||||
>
|
||||
<SignInPanel onClose={close} server={initialServerBaseUrl} />
|
||||
<SignInPanel
|
||||
onClose={close}
|
||||
server={initialServerBaseUrl}
|
||||
initStep={step as SignInStep}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user