From 4ea4a2d25f32ce69e4224e1a883f7c01c8fa2786 Mon Sep 17 00:00:00 2001 From: JimmFly <447268514@qq.com> Date: Fri, 23 Feb 2024 06:18:25 +0000 Subject: [PATCH] feat(core): support create cloud workspace (#5771) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit close TOV-475 has not logged: https://github.com/toeverything/AFFiNE/assets/102217452/b9aa2806-7dbd-4235-895d-5b27effb5831 has logged : https://github.com/toeverything/AFFiNE/assets/102217452/259a1c35-c6ab-4a52-9e03-4438ca64e620 client has not logged: https://github.com/toeverything/AFFiNE/assets/102217452/af6ef528-6fb8-4a27-842f-00c9669afcb2 --- packages/common/env/src/global.ts | 1 + .../component/src/ui/modal/styles.css.ts | 2 + .../component/src/ui/switch/index.css.ts | 4 + .../component/src/ui/switch/switch.tsx | 8 +- .../frontend/core/.webpack/runtime-config.ts | 9 +- .../create-workspace-modal/index.css.ts | 86 ++++++++---- .../affine/create-workspace-modal/index.tsx | 126 +++++++++++++++--- .../affine/share-page-modal/cloud-svg.tsx | 20 +++ .../share-page-modal/share-menu/index.css.ts | 1 - .../share-menu/share-page.tsx | 28 +--- .../add-workspace/index.tsx | 4 +- .../user-with-workspace-list/index.tsx | 28 +++- packages/frontend/i18n/src/resources/en.json | 5 + 13 files changed, 251 insertions(+), 71 deletions(-) create mode 100644 packages/frontend/core/src/components/affine/share-page-modal/cloud-svg.tsx diff --git a/packages/common/env/src/global.ts b/packages/common/env/src/global.ts index 3807a941c..3b829d017 100644 --- a/packages/common/env/src/global.ts +++ b/packages/common/env/src/global.ts @@ -32,6 +32,7 @@ export const runtimeFlagsSchema = z.object({ enableEnhanceShareMode: z.boolean(), enablePayment: z.boolean(), enablePageHistory: z.boolean(), + allowLocalWorkspace: z.boolean(), // this is for the electron app serverUrlPrefix: z.string(), enableMoveDatabase: z.boolean(), diff --git a/packages/frontend/component/src/ui/modal/styles.css.ts b/packages/frontend/component/src/ui/modal/styles.css.ts index 9c92e2324..e4fd9ad6f 100644 --- a/packages/frontend/component/src/ui/modal/styles.css.ts +++ b/packages/frontend/component/src/ui/modal/styles.css.ts @@ -69,6 +69,8 @@ export const modalFooter = style({ export const confirmModalContent = style({ marginTop: '12px', marginBottom: '20px', + height: '100%', + overflowY: 'auto', }); export const confirmModalContainer = style({ display: 'flex', diff --git a/packages/frontend/component/src/ui/switch/index.css.ts b/packages/frontend/component/src/ui/switch/index.css.ts index 7fefbd72f..5a6c8a48b 100644 --- a/packages/frontend/component/src/ui/switch/index.css.ts +++ b/packages/frontend/component/src/ui/switch/index.css.ts @@ -43,3 +43,7 @@ export const switchCheckedStyle = style({ }, }, }); +export const switchDisabledStyle = style({ + cursor: 'not-allowed', + opacity: 0.5, +}); diff --git a/packages/frontend/component/src/ui/switch/switch.tsx b/packages/frontend/component/src/ui/switch/switch.tsx index 739d47e47..8b3cb01e6 100644 --- a/packages/frontend/component/src/ui/switch/switch.tsx +++ b/packages/frontend/component/src/ui/switch/switch.tsx @@ -13,6 +13,7 @@ export type SwitchProps = Omit, 'onChange'> & { checked?: boolean; onChange?: (checked: boolean) => void; children?: ReactNode; + disabled?: boolean; }; export const Switch = ({ @@ -20,6 +21,7 @@ export const Switch = ({ onChange: onChangeProp, children, className, + disabled, ...otherProps }: SwitchProps) => { const [checkedState, setCheckedState] = useState(checkedProp); @@ -27,11 +29,14 @@ export const Switch = ({ const checked = onChangeProp ? checkedProp : checkedState; const onChange = useCallback( (event: React.ChangeEvent) => { + if (disabled) { + return; + } const newChecked = event.target.checked; if (onChangeProp) onChangeProp(newChecked); else setCheckedState(newChecked); }, - [onChangeProp] + [disabled, onChangeProp] ); return ( @@ -47,6 +52,7 @@ export const Switch = ({ diff --git a/packages/frontend/core/.webpack/runtime-config.ts b/packages/frontend/core/.webpack/runtime-config.ts index 825059cf6..a712f409d 100644 --- a/packages/frontend/core/.webpack/runtime-config.ts +++ b/packages/frontend/core/.webpack/runtime-config.ts @@ -33,6 +33,7 @@ export function getRuntimeConfig(buildFlags: BuildFlags): RuntimeConfig { enableEnhanceShareMode: false, enablePayment: true, enablePageHistory: true, + allowLocalWorkspace: false, serverUrlPrefix: 'https://app.affine.pro', editorFlags, appVersion: packageJson.version, @@ -75,6 +76,7 @@ export function getRuntimeConfig(buildFlags: BuildFlags): RuntimeConfig { enableEnhanceShareMode: false, enablePayment: true, enablePageHistory: true, + allowLocalWorkspace: false, serverUrlPrefix: 'https://affine.fail', editorFlags, appVersion: packageJson.version, @@ -138,6 +140,11 @@ export function getRuntimeConfig(buildFlags: BuildFlags): RuntimeConfig { : buildFlags.mode === 'development' ? true : currentBuildPreset.enablePageHistory, + allowLocalWorkspace: process.env.ALLOW_LOCAL_WORKSPACE + ? process.env.ALLOW_LOCAL_WORKSPACE === 'true' + : buildFlags.mode === 'development' + ? true + : currentBuildPreset.allowLocalWorkspace, }; if (buildFlags.mode === 'development') { @@ -149,6 +156,6 @@ export function getRuntimeConfig(buildFlags: BuildFlags): RuntimeConfig { // environment preset will overwrite current build preset // this environment variable is for debug proposes only // do not put them into CI - ...(process.env.CI ? {} : environmentPreset), + ...(process.env.CI ? { allowLocalWorkspace: true } : environmentPreset), }; } diff --git a/packages/frontend/core/src/components/affine/create-workspace-modal/index.css.ts b/packages/frontend/core/src/components/affine/create-workspace-modal/index.css.ts index a9955c7d1..2c2305527 100644 --- a/packages/frontend/core/src/components/affine/create-workspace-modal/index.css.ts +++ b/packages/frontend/core/src/components/affine/create-workspace-modal/index.css.ts @@ -1,36 +1,76 @@ -import { globalStyle, style } from '@vanilla-extract/css'; +import { cssVar } from '@toeverything/theme'; +import { style } from '@vanilla-extract/css'; + export const header = style({ position: 'relative', marginTop: '44px', }); -export const content = style({ - padding: '0 40px', - fontSize: '18px', - lineHeight: '26px', -}); -globalStyle(`${content} p`, { - marginTop: '12px', - marginBottom: '16px', -}); -export const contentTitle = style({ - fontSize: '20px', - lineHeight: '28px', + +export const subTitle = style({ + fontSize: cssVar('fontSm'), + color: cssVar('textPrimaryColor'), fontWeight: 600, - paddingBottom: '16px', }); -export const buttonGroup = style({ + +export const avatarWrapper = style({ display: 'flex', - justifyContent: 'flex-end', - gap: '20px', - margin: '24px 0', + margin: '10px 0', }); -export const radioGroup = style({ + +export const workspaceNameWrapper = style({ display: 'flex', flexDirection: 'column', gap: '8px', + padding: '12px 0', }); -export const radio = style({ - cursor: 'pointer', - appearance: 'auto', - marginRight: '12px', +export const affineCloudWrapper = style({ + display: 'flex', + flexDirection: 'column', + gap: '6px', + paddingTop: '10px', +}); + +export const card = style({ + padding: '12px', + display: 'flex', + alignItems: 'center', + borderRadius: '8px', + backgroundColor: cssVar('backgroundSecondaryColor'), + minHeight: '114px', + position: 'relative', +}); + +export const cardText = style({ + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + width: '100%', + gap: '12px', +}); + +export const cardTitle = style({ + fontSize: cssVar('fontBase'), + color: cssVar('textPrimaryColor'), + display: 'flex', + justifyContent: 'space-between', +}); +export const cardDescription = style({ + fontSize: cssVar('fontXs'), + color: cssVar('textSecondaryColor'), + maxWidth: '288px', +}); + +export const cloudTips = style({ + fontSize: cssVar('fontXs'), + color: cssVar('textSecondaryColor'), +}); + +export const cloudSvgContainer = style({ + width: '146px', + display: 'flex', + justifyContent: 'flex-end', + alignItems: 'center', + position: 'absolute', + bottom: '0', + right: '0', }); diff --git a/packages/frontend/core/src/components/affine/create-workspace-modal/index.tsx b/packages/frontend/core/src/components/affine/create-workspace-modal/index.tsx index 3f1cd8ada..a27b67001 100644 --- a/packages/frontend/core/src/components/affine/create-workspace-modal/index.tsx +++ b/packages/frontend/core/src/components/affine/create-workspace-modal/index.tsx @@ -1,9 +1,15 @@ -import { Input, toast } from '@affine/component'; +import { Avatar, Input, Switch, toast } from '@affine/component'; import { ConfirmModal, type ConfirmModalProps, Modal, } from '@affine/component/ui/modal'; +import { + authAtom, + openDisableCloudAlertModalAtom, + setPageModeAtom, +} from '@affine/core/atoms'; +import { useCurrentLoginStatus } from '@affine/core/hooks/affine/use-current-login-status'; import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; import { DebugLogger } from '@affine/debug'; import { apis } from '@affine/electron-api'; @@ -17,12 +23,13 @@ import { initEmptyPage, } from '@toeverything/infra/blocksuite'; import { useService } from '@toeverything/infra/di'; +import { useSetAtom } from 'jotai'; import type { KeyboardEvent } from 'react'; import { useLayoutEffect } from 'react'; import { useCallback, useState } from 'react'; -import { setPageModeAtom } from '../../../atoms'; -import * as style from './index.css'; +import { CloudSvg } from '../share-page-modal/cloud-svg'; +import * as styles from './index.css'; type CreateWorkspaceStep = | 'set-db-location' @@ -40,18 +47,55 @@ interface ModalProps { } interface NameWorkspaceContentProps extends ConfirmModalProps { - onConfirmName: (name: string) => void; + onConfirmName: ( + name: string, + workspaceFlavour: WorkspaceFlavour, + avatar?: File + ) => void; } +const shouldEnableCloud = + !runtimeConfig.allowLocalWorkspace && !environment.isDesktop; + const NameWorkspaceContent = ({ onConfirmName, ...props }: NameWorkspaceContentProps) => { + const t = useAFFiNEI18N(); const [workspaceName, setWorkspaceName] = useState(''); + const [enable, setEnable] = useState(shouldEnableCloud); + const loginStatus = useCurrentLoginStatus(); + const setDisableCloudOpen = useSetAtom(openDisableCloudAlertModalAtom); + + const setOpenSignIn = useSetAtom(authAtom); + + const openSignInModal = useCallback(() => { + if (!runtimeConfig.enableCloud) { + setDisableCloudOpen(true); + } else { + setOpenSignIn(state => ({ + ...state, + openModal: true, + })); + } + }, [setDisableCloudOpen, setOpenSignIn]); + + const onSwitchChange = useCallback( + (checked: boolean) => { + if (loginStatus !== 'authenticated') { + return openSignInModal(); + } + return setEnable(checked); + }, + [loginStatus, openSignInModal] + ); const handleCreateWorkspace = useCallback(() => { - onConfirmName(workspaceName); - }, [onConfirmName, workspaceName]); + onConfirmName( + workspaceName, + enable ? WorkspaceFlavour.AFFINE_CLOUD : WorkspaceFlavour.LOCAL + ); + }, [enable, onConfirmName, workspaceName]); const handleKeyDown = useCallback( (event: KeyboardEvent) => { @@ -61,7 +105,9 @@ const NameWorkspaceContent = ({ }, [handleCreateWorkspace, workspaceName] ); - const t = useAFFiNEI18N(); + // TODO: Support uploading avatars. + // Currently, when we create a new workspace and upload an avatar at the same time, + // an error occurs after the creation is successful: get blob 404 not found return ( - +
+ +
+ +
+
+ {t['com.affine.nameWorkspace.subtitle.workspace-name']()} +
+ +
+
+
{t['AFFiNE Cloud']()}
+
+
+
+ {t['com.affine.nameWorkspace.affine-cloud.title']()} + +
+
+ {t['com.affine.nameWorkspace.affine-cloud.description']()} +
+
+
+ +
+
+ {shouldEnableCloud ? ( + + {t['com.affine.nameWorkspace.affine-cloud.web-tips']()} + + ) : null} +
); }; @@ -145,11 +231,11 @@ export const CreateWorkspaceModal = ({ }, [mode, onClose, onCreate, t, workspaceManager]); const onConfirmName = useAsyncCallback( - async (name: string) => { + async (name: string, workspaceFlavour: WorkspaceFlavour) => { // this will be the last step for web for now // fix me later const { id } = await workspaceManager.createWorkspace( - WorkspaceFlavour.LOCAL, + workspaceFlavour, async workspace => { workspace.meta.setName(name); if (runtimeConfig.enablePreloading) { @@ -201,7 +287,7 @@ export const CreateWorkspaceModal = ({ style: { padding: '10px' }, }} > -
+
); }; diff --git a/packages/frontend/core/src/components/affine/share-page-modal/cloud-svg.tsx b/packages/frontend/core/src/components/affine/share-page-modal/cloud-svg.tsx new file mode 100644 index 000000000..98b3375ec --- /dev/null +++ b/packages/frontend/core/src/components/affine/share-page-modal/cloud-svg.tsx @@ -0,0 +1,20 @@ +import { cssVar } from '@toeverything/theme'; + +export const CloudSvg = () => ( + + + + + +); diff --git a/packages/frontend/core/src/components/affine/share-page-modal/share-menu/index.css.ts b/packages/frontend/core/src/components/affine/share-page-modal/share-menu/index.css.ts index 5d75d0042..b99511257 100644 --- a/packages/frontend/core/src/components/affine/share-page-modal/share-menu/index.css.ts +++ b/packages/frontend/core/src/components/affine/share-page-modal/share-menu/index.css.ts @@ -21,7 +21,6 @@ export const menuItemStyle = style({ }); export const descriptionStyle = style({ wordWrap: 'break-word', - // wordBreak: 'break-all', fontSize: cssVar('fontXs'), lineHeight: '20px', color: cssVar('textSecondaryColor'), diff --git a/packages/frontend/core/src/components/affine/share-page-modal/share-menu/share-page.tsx b/packages/frontend/core/src/components/affine/share-page-modal/share-menu/share-page.tsx index db286f45e..84d804bd7 100644 --- a/packages/frontend/core/src/components/affine/share-page-modal/share-menu/share-page.tsx +++ b/packages/frontend/core/src/components/affine/share-page-modal/share-menu/share-page.tsx @@ -8,6 +8,10 @@ import { import { PublicLinkDisableModal } from '@affine/component/disable-public-link'; import { Button } from '@affine/component/ui/button'; import { Menu, MenuItem, MenuTrigger } from '@affine/component/ui/menu'; +import type { PageMode } from '@affine/core/atoms'; +import { currentModeAtom } from '@affine/core/atoms/mode'; +import { useIsSharedPage } from '@affine/core/hooks/affine/use-is-shared-page'; +import { useServerBaseUrl } from '@affine/core/hooks/affine/use-server-config'; import { WorkspaceFlavour } from '@affine/env/workspace'; import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { ArrowRightSmallIcon } from '@blocksuite/icons'; @@ -15,33 +19,11 @@ import { useAtomValue } from 'jotai'; import { useMemo, useState } from 'react'; import { useCallback } from 'react'; -import type { PageMode } from '../../../../atoms'; -import { currentModeAtom } from '../../../../atoms/mode'; -import { useIsSharedPage } from '../../../../hooks/affine/use-is-shared-page'; -import { useServerBaseUrl } from '../../../../hooks/affine/use-server-config'; +import { CloudSvg } from '../cloud-svg'; import * as styles from './index.css'; import type { ShareMenuProps } from './share-menu'; import { useSharingUrl } from './use-share-url'; -const CloudSvg = () => ( - - - - - -); - export const LocalSharePage = (props: ShareMenuProps) => { const t = useAFFiNEI18N(); diff --git a/packages/frontend/core/src/components/pure/workspace-slider-bar/user-with-workspace-list/add-workspace/index.tsx b/packages/frontend/core/src/components/pure/workspace-slider-bar/user-with-workspace-list/add-workspace/index.tsx index 52388e8a4..b5ff95258 100644 --- a/packages/frontend/core/src/components/pure/workspace-slider-bar/user-with-workspace-list/add-workspace/index.tsx +++ b/packages/frontend/core/src/components/pure/workspace-slider-bar/user-with-workspace-list/add-workspace/index.tsx @@ -36,7 +36,9 @@ export const AddWorkspace = ({ className={styles.ItemContainer} >
- {t['com.affine.workspaceList.addWorkspace.create']()} + {runtimeConfig.enableSQLiteProvider && environment.isDesktop + ? t['com.affine.workspaceList.addWorkspace.create']() + : t['com.affine.workspaceList.addWorkspace.create-cloud']()}
diff --git a/packages/frontend/core/src/components/pure/workspace-slider-bar/user-with-workspace-list/index.tsx b/packages/frontend/core/src/components/pure/workspace-slider-bar/user-with-workspace-list/index.tsx index 835755658..289dc641a 100644 --- a/packages/frontend/core/src/components/pure/workspace-slider-bar/user-with-workspace-list/index.tsx +++ b/packages/frontend/core/src/components/pure/workspace-slider-bar/user-with-workspace-list/index.tsx @@ -73,11 +73,37 @@ export const UserWithWorkspaceList = ({ const isAuthenticated = useMemo(() => status === 'authenticated', [status]); const setOpenCreateWorkspaceModal = useSetAtom(openCreateWorkspaceModalAtom); + const setDisableCloudOpen = useSetAtom(openDisableCloudAlertModalAtom); + + const setOpenSignIn = useSetAtom(authAtom); + + const openSignInModal = useCallback(() => { + if (!runtimeConfig.enableCloud) { + setDisableCloudOpen(true); + } else { + setOpenSignIn(state => ({ + ...state, + openModal: true, + })); + } + }, [setDisableCloudOpen, setOpenSignIn]); const onNewWorkspace = useCallback(() => { + if ( + !isAuthenticated && + !environment.isDesktop && + !runtimeConfig.allowLocalWorkspace + ) { + return openSignInModal(); + } setOpenCreateWorkspaceModal('new'); onEventEnd?.(); - }, [onEventEnd, setOpenCreateWorkspaceModal]); + }, [ + isAuthenticated, + onEventEnd, + openSignInModal, + setOpenCreateWorkspaceModal, + ]); const onAddWorkspace = useCallback(() => { setOpenCreateWorkspaceModal('add'); diff --git a/packages/frontend/i18n/src/resources/en.json b/packages/frontend/i18n/src/resources/en.json index 5188f01df..deac7d1d8 100644 --- a/packages/frontend/i18n/src/resources/en.json +++ b/packages/frontend/i18n/src/resources/en.json @@ -698,6 +698,10 @@ "com.affine.nameWorkspace.description": "A workspace is your virtual space to capture, create and plan as just one person or together as a team.", "com.affine.nameWorkspace.placeholder": "Set a Workspace name", "com.affine.nameWorkspace.title": "Name Your Workspace", + "com.affine.nameWorkspace.subtitle.workspace-name": "Workspace Name", + "com.affine.nameWorkspace.affine-cloud.title": "Sync across devices with AFFiNE Cloud", + "com.affine.nameWorkspace.affine-cloud.description": "Enabling AFFiNE Cloud allows you to synchronise and backup data, as well as support multi-user collaboration and content publishing.", + "com.affine.nameWorkspace.affine-cloud.web-tips": "If you want the workspace to be stored locally, you can download the desktop client.", "com.affine.new_edgeless": "New Edgeless", "com.affine.new_import": "Import", "com.affine.notFoundPage.backButton": "Back Home", @@ -1013,6 +1017,7 @@ "com.affine.workspaceLeave.button.leave": "Leave", "com.affine.workspaceLeave.description": "After you leave, you will no longer be able to access the contents of this workspace.", "com.affine.workspaceList.addWorkspace.create": "Create Workspace", + "com.affine.workspaceList.addWorkspace.create-cloud": "Create Cloud Workspace", "com.affine.workspaceList.workspaceListType.cloud": "Cloud Sync", "com.affine.workspaceList.workspaceListType.local": "Local Storage", "com.affine.workspaceSubPath.all": "All docs",