feat(core): sidebar local workspace enable cloud directly (#6366)

- Add a new hook `useEnableClould`, remove `<EnableAffineClouldModal />`
- Sidebar local workspace list item support enable AFFiNE Cloud
This commit is contained in:
CatsJuice
2024-03-29 02:08:09 +00:00
parent 1a873ecf3c
commit 35715ab1d8
11 changed files with 273 additions and 211 deletions

View File

@@ -1,10 +1,12 @@
import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant'; import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant';
import type { WorkspaceFlavour } from '@affine/env/workspace'; import { WorkspaceFlavour } from '@affine/env/workspace';
import { CollaborationIcon, SettingsIcon } from '@blocksuite/icons'; import { CollaborationIcon, SettingsIcon } from '@blocksuite/icons';
import type { WorkspaceMetadata } from '@toeverything/infra'; import type { WorkspaceMetadata } from '@toeverything/infra';
import { useCallback } from 'react'; import clsx from 'clsx';
import { type MouseEvent, useCallback } from 'react';
import { Avatar, type AvatarProps } from '../../../ui/avatar'; import { Avatar, type AvatarProps } from '../../../ui/avatar';
import { Button } from '../../../ui/button';
import { Skeleton } from '../../../ui/skeleton'; import { Skeleton } from '../../../ui/skeleton';
import * as styles from './styles.css'; import * as styles from './styles.css';
@@ -18,7 +20,10 @@ export interface WorkspaceCardProps {
meta: WorkspaceMetadata; meta: WorkspaceMetadata;
onClick: (metadata: WorkspaceMetadata) => void; onClick: (metadata: WorkspaceMetadata) => void;
onSettingClick: (metadata: WorkspaceMetadata) => void; onSettingClick: (metadata: WorkspaceMetadata) => void;
onEnableCloudClick?: (meta: WorkspaceMetadata) => void;
isOwner?: boolean; isOwner?: boolean;
openingId?: string | null;
enableCloudText?: string;
avatar?: string; avatar?: string;
name?: string; name?: string;
} }
@@ -45,13 +50,25 @@ const avatarImageProps = {
export const WorkspaceCard = ({ export const WorkspaceCard = ({
onClick, onClick,
onSettingClick, onSettingClick,
onEnableCloudClick,
openingId,
currentWorkspaceId, currentWorkspaceId,
meta, meta,
isOwner = true, isOwner = true,
enableCloudText = 'Enable Cloud',
name, name,
avatar, avatar,
}: WorkspaceCardProps) => { }: WorkspaceCardProps) => {
const isLocal = meta.flavour === WorkspaceFlavour.LOCAL;
const displayName = name ?? UNTITLED_WORKSPACE_NAME; const displayName = name ?? UNTITLED_WORKSPACE_NAME;
const onEnableCloud = useCallback(
(e: MouseEvent) => {
e.stopPropagation();
onEnableCloudClick?.(meta);
},
[meta, onEnableCloudClick]
);
return ( return (
<div <div
className={styles.card} className={styles.card}
@@ -73,6 +90,17 @@ export const WorkspaceCard = ({
<div className={styles.workspaceTitle}>{displayName}</div> <div className={styles.workspaceTitle}>{displayName}</div>
<div className={styles.actionButtons}> <div className={styles.actionButtons}>
{isLocal ? (
<Button
loading={!!openingId && openingId === meta.id}
disabled={!!openingId}
type="default"
className={clsx(styles.enableCloudButton, styles.showOnCardHover)}
onClick={onEnableCloud}
>
{enableCloudText}
</Button>
) : null}
{isOwner ? null : <CollaborationIcon />} {isOwner ? null : <CollaborationIcon />}
<div <div
className={styles.settingButton} className={styles.settingButton}

View File

@@ -84,3 +84,16 @@ export const settingButton = style({
// }, // },
}, },
}); });
export const enableCloudButton = style({
background: 'transparent',
});
export const showOnCardHover = style({
display: 'none',
selectors: {
[`.${card}:hover &`]: {
display: 'block',
},
},
});

View File

@@ -19,8 +19,10 @@ export interface WorkspaceListProps {
disabled?: boolean; disabled?: boolean;
currentWorkspaceId?: string | null; currentWorkspaceId?: string | null;
items: WorkspaceMetadata[]; items: WorkspaceMetadata[];
openingId?: string | null;
onClick: (workspace: WorkspaceMetadata) => void; onClick: (workspace: WorkspaceMetadata) => void;
onSettingClick: (workspace: WorkspaceMetadata) => void; onSettingClick: (workspace: WorkspaceMetadata) => void;
onEnableCloudClick?: (meta: WorkspaceMetadata) => void;
onDragEnd: (event: DragEndEvent) => void; onDragEnd: (event: DragEndEvent) => void;
useIsWorkspaceOwner: (workspaceMetadata: WorkspaceMetadata) => boolean; useIsWorkspaceOwner: (workspaceMetadata: WorkspaceMetadata) => boolean;
useWorkspaceAvatar: ( useWorkspaceAvatar: (
@@ -38,12 +40,14 @@ interface SortableWorkspaceItemProps extends Omit<WorkspaceListProps, 'items'> {
const SortableWorkspaceItem = ({ const SortableWorkspaceItem = ({
disabled, disabled,
item, item,
openingId,
useIsWorkspaceOwner, useIsWorkspaceOwner,
useWorkspaceAvatar, useWorkspaceAvatar,
useWorkspaceName, useWorkspaceName,
currentWorkspaceId, currentWorkspaceId,
onClick, onClick,
onSettingClick, onSettingClick,
onEnableCloudClick,
}: SortableWorkspaceItemProps) => { }: SortableWorkspaceItemProps) => {
const { setNodeRef, attributes, listeners, transform, transition } = const { setNodeRef, attributes, listeners, transform, transition } =
useSortable({ useSortable({
@@ -77,6 +81,8 @@ const SortableWorkspaceItem = ({
meta={item} meta={item}
onClick={onClick} onClick={onClick}
onSettingClick={onSettingClick} onSettingClick={onSettingClick}
onEnableCloudClick={onEnableCloudClick}
openingId={openingId}
isOwner={isOwner} isOwner={isOwner}
name={name} name={name}
avatar={avatar} avatar={avatar}

View File

@@ -56,9 +56,16 @@ export const ConfirmModal = ({
); );
}; };
interface OpenConfirmModalOptions {
autoClose?: boolean;
onSuccess?: () => void;
}
interface ConfirmModalContextProps { interface ConfirmModalContextProps {
modalProps: ConfirmModalProps; modalProps: ConfirmModalProps;
openConfirmModal: (props?: ConfirmModalProps) => void; openConfirmModal: (
props?: ConfirmModalProps,
options?: OpenConfirmModalOptions
) => void;
closeConfirmModal: () => void; closeConfirmModal: () => void;
} }
const ConfirmModalContext = createContext<ConfirmModalContextProps>({ const ConfirmModalContext = createContext<ConfirmModalContextProps>({
@@ -86,7 +93,8 @@ export const ConfirmModalProvider = ({ children }: PropsWithChildren) => {
}, []); }, []);
const openConfirmModal = useCallback( const openConfirmModal = useCallback(
(props?: ConfirmModalProps) => { (props?: ConfirmModalProps, options?: OpenConfirmModalOptions) => {
const { autoClose = true, onSuccess } = options ?? {};
if (!props) { if (!props) {
setModalProps({ open: true }); setModalProps({ open: true });
return; return;
@@ -97,17 +105,22 @@ export const ConfirmModalProvider = ({ children }: PropsWithChildren) => {
const onConfirm = () => { const onConfirm = () => {
setLoading(true); setLoading(true);
_onConfirm?.() _onConfirm?.()
?.catch(console.error) ?.then(() => onSuccess?.())
?.finally(() => closeConfirmModal()); .catch(console.error)
.finally(() => autoClose && closeConfirmModal());
}; };
setModalProps({ ...otherProps, onConfirm, open: true }); setModalProps({ ...otherProps, onConfirm, open: true });
}, },
[closeConfirmModal, setLoading] [closeConfirmModal, setLoading]
); );
const onOpenChange = useCallback((open: boolean) => { const onOpenChange = useCallback(
setModalProps(props => ({ ...props, open })); (open: boolean) => {
}, []); modalProps.onOpenChange?.(open);
setModalProps(props => ({ ...props, open }));
},
[modalProps]
);
return ( return (
<ConfirmModalContext.Provider <ConfirmModalContext.Provider
@@ -115,7 +128,7 @@ export const ConfirmModalProvider = ({ children }: PropsWithChildren) => {
> >
{children} {children}
{/* TODO: multi-instance support(unnecessary for now) */} {/* TODO: multi-instance support(unnecessary for now) */}
<ConfirmModal onOpenChange={onOpenChange} {...modalProps} /> <ConfirmModal {...modalProps} onOpenChange={onOpenChange} />
</ConfirmModalContext.Provider> </ConfirmModalContext.Provider>
); );
}; };

View File

@@ -1,54 +0,0 @@
import type { ConfirmModalProps } from '@affine/component/ui/modal';
import { ConfirmModal } from '@affine/component/ui/modal';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { useSetAtom } from 'jotai';
import { useCallback } from 'react';
import { authAtom } from '../../../atoms';
import { setOnceSignedInEventAtom } from '../../../atoms/event';
import { useCurrentLoginStatus } from '../../../hooks/affine/use-current-login-status';
export const EnableAffineCloudModal = ({
onConfirm: propsOnConfirm,
...props
}: Omit<ConfirmModalProps, 'onConfirm'> & { onConfirm: () => void }) => {
const t = useAFFiNEI18N();
const loginStatus = useCurrentLoginStatus();
const setAuthAtom = useSetAtom(authAtom);
const setOnceSignedInEvent = useSetAtom(setOnceSignedInEventAtom);
const confirm = useCallback(() => {
return propsOnConfirm?.();
}, [propsOnConfirm]);
const onConfirm = useCallback(() => {
if (loginStatus === 'unauthenticated') {
setAuthAtom(prev => ({
...prev,
openModal: true,
}));
setOnceSignedInEvent(confirm);
}
if (loginStatus === 'authenticated') {
return propsOnConfirm?.();
}
}, [confirm, loginStatus, propsOnConfirm, setAuthAtom, setOnceSignedInEvent]);
return (
<ConfirmModal
title={t['Enable AFFiNE Cloud']()}
description={t['Enable AFFiNE Cloud Description']()}
cancelText={t['com.affine.enableAffineCloudModal.button.cancel']()}
onConfirm={onConfirm}
confirmButtonOptions={{
type: 'primary',
['data-testid' as string]: 'confirm-enable-affine-cloud-button',
children:
loginStatus === 'authenticated'
? t['Enable']()
: t['Sign in and Enable'](),
}}
{...props}
/>
);
};

View File

@@ -3,64 +3,43 @@ import {
openDisableCloudAlertModalAtom, openDisableCloudAlertModalAtom,
openHistoryTipsModalAtom, openHistoryTipsModalAtom,
} from '@affine/core/atoms'; } from '@affine/core/atoms';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; import { useEnableCloud } from '@affine/core/hooks/affine/use-enable-cloud';
import { useNavigateHelper } from '@affine/core/hooks/use-navigate-helper';
import { WorkspaceSubPath } from '@affine/core/shared';
import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { useService, Workspace, WorkspaceManager } from '@toeverything/infra'; import { useService, Workspace } from '@toeverything/infra';
import { useAtom, useSetAtom } from 'jotai'; import { useAtom, useSetAtom } from 'jotai';
import { useCallback, useState } from 'react'; import { useCallback } from 'react';
import { EnableAffineCloudModal } from '../enable-affine-cloud-modal';
import TopSvg from './top-svg'; import TopSvg from './top-svg';
export const HistoryTipsModal = () => { export const HistoryTipsModal = () => {
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
const { openPage } = useNavigateHelper();
const workspaceManager = useService(WorkspaceManager);
const currentWorkspace = useService(Workspace); const currentWorkspace = useService(Workspace);
const [open, setOpen] = useAtom(openHistoryTipsModalAtom); const [open, setOpen] = useAtom(openHistoryTipsModalAtom);
const setTempDisableCloudOpen = useSetAtom(openDisableCloudAlertModalAtom); const setTempDisableCloudOpen = useSetAtom(openDisableCloudAlertModalAtom);
const [openEnableCloudModal, setOpenEnableCloudModal] = useState(false); const confirmEnableCloud = useEnableCloud();
const handleConfirm = useCallback(() => { const handleConfirm = useCallback(() => {
setOpen(false); setOpen(false);
if (runtimeConfig.enableCloud) { if (runtimeConfig.enableCloud) {
return setOpenEnableCloudModal(true); confirmEnableCloud(currentWorkspace);
}
return setTempDisableCloudOpen(true);
}, [setOpen, setTempDisableCloudOpen]);
const handEnableCloud = useAsyncCallback(async () => {
if (!currentWorkspace) {
return; return;
} }
const { id: newId } = return setTempDisableCloudOpen(true);
await workspaceManager.transformLocalToCloud(currentWorkspace); }, [confirmEnableCloud, currentWorkspace, setOpen, setTempDisableCloudOpen]);
openPage(newId, WorkspaceSubPath.ALL);
setOpenEnableCloudModal(false);
}, [openPage, currentWorkspace, workspaceManager]);
return ( return (
<> <OverlayModal
<OverlayModal open={open}
open={open} topImage={<TopSvg />}
topImage={<TopSvg />} title={t['com.affine.history-vision.tips-modal.title']()}
title={t['com.affine.history-vision.tips-modal.title']()} onOpenChange={setOpen}
onOpenChange={setOpen} description={t['com.affine.history-vision.tips-modal.description']()}
description={t['com.affine.history-vision.tips-modal.description']()} cancelText={t['com.affine.history-vision.tips-modal.cancel']()}
cancelText={t['com.affine.history-vision.tips-modal.cancel']()} confirmButtonOptions={{
confirmButtonOptions={{ type: 'primary',
type: 'primary', }}
}} onConfirm={handleConfirm}
onConfirm={handleConfirm} confirmText={t['com.affine.history-vision.tips-modal.confirm']()}
confirmText={t['com.affine.history-vision.tips-modal.confirm']()} />
/>
<EnableAffineCloudModal
open={openEnableCloudModal}
onOpenChange={setOpenEnableCloudModal}
onConfirm={handEnableCloud}
/>
</>
); );
}; };

View File

@@ -1,19 +1,15 @@
import { SettingRow } from '@affine/component/setting-components'; import { SettingRow } from '@affine/component/setting-components';
import { Button } from '@affine/component/ui/button'; import { Button } from '@affine/component/ui/button';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; import { useEnableCloud } from '@affine/core/hooks/affine/use-enable-cloud';
import { useWorkspaceInfo } from '@affine/core/hooks/use-workspace-info'; import { useWorkspaceInfo } from '@affine/core/hooks/use-workspace-info';
import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant'; import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant';
import { WorkspaceFlavour } from '@affine/env/workspace'; import { WorkspaceFlavour } from '@affine/env/workspace';
import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { useAFFiNEI18N } from '@affine/i18n/hooks';
import type { Workspace } from '@toeverything/infra'; import type { Workspace } from '@toeverything/infra';
import { useService, WorkspaceManager } from '@toeverything/infra';
import { useSetAtom } from 'jotai'; import { useSetAtom } from 'jotai';
import { useState } from 'react'; import { useCallback, useState } from 'react';
import { openSettingModalAtom } from '../../../../../atoms'; import { openSettingModalAtom } from '../../../../../atoms';
import { useNavigateHelper } from '../../../../../hooks/use-navigate-helper';
import { WorkspaceSubPath } from '../../../../../shared';
import { EnableAffineCloudModal } from '../../../enable-affine-cloud-modal';
import { TmpDisableAffineCloudModal } from '../../../tmp-disable-affine-cloud-modal'; import { TmpDisableAffineCloudModal } from '../../../tmp-disable-affine-cloud-modal';
import type { WorkspaceSettingDetailProps } from './types'; import type { WorkspaceSettingDetailProps } from './types';
@@ -26,28 +22,21 @@ export const EnableCloudPanel = ({
workspace, workspace,
}: PublishPanelProps) => { }: PublishPanelProps) => {
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
const confirmEnableCloud = useEnableCloud();
const { openPage } = useNavigateHelper();
const workspaceManager = useService(WorkspaceManager);
const workspaceInfo = useWorkspaceInfo(workspaceMetadata); const workspaceInfo = useWorkspaceInfo(workspaceMetadata);
const setSettingModal = useSetAtom(openSettingModalAtom); const setSettingModal = useSetAtom(openSettingModalAtom);
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const handleEnableCloud = useAsyncCallback(async () => { const confirmEnableCloudAndClose = useCallback(() => {
if (!workspace) { if (!workspace) return;
return; confirmEnableCloud(workspace, {
} onSuccess: () => {
const { id: newId } = setSettingModal(settings => ({ ...settings, open: false }));
await workspaceManager.transformLocalToCloud(workspace); },
openPage(newId, WorkspaceSubPath.ALL); });
setOpen(false); }, [confirmEnableCloud, setSettingModal, workspace]);
setSettingModal(settings => ({
...settings,
open: false,
}));
}, [openPage, setSettingModal, workspace, workspaceManager]);
if (workspaceMetadata.flavour !== WorkspaceFlavour.LOCAL) { if (workspaceMetadata.flavour !== WorkspaceFlavour.LOCAL) {
return null; return null;
@@ -69,21 +58,13 @@ export const EnableCloudPanel = ({
<Button <Button
data-testid="publish-enable-affine-cloud-button" data-testid="publish-enable-affine-cloud-button"
type="primary" type="primary"
onClick={() => { onClick={confirmEnableCloudAndClose}
setOpen(true);
}}
style={{ marginTop: '12px' }} style={{ marginTop: '12px' }}
> >
{t['Enable AFFiNE Cloud']()} {t['Enable AFFiNE Cloud']()}
</Button> </Button>
</SettingRow> </SettingRow>
{runtimeConfig.enableCloud ? ( {runtimeConfig.enableCloud ? null : (
<EnableAffineCloudModal
open={open}
onOpenChange={setOpen}
onConfirm={handleEnableCloud}
/>
) : (
<TmpDisableAffineCloudModal open={open} onOpenChange={setOpen} /> <TmpDisableAffineCloudModal open={open} onOpenChange={setOpen} />
)} )}
</> </>

View File

@@ -1,12 +1,7 @@
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; import { useEnableCloud } from '@affine/core/hooks/affine/use-enable-cloud';
import { WorkspaceFlavour } from '@affine/env/workspace';
import type { Doc } from '@blocksuite/store'; import type { Doc } from '@blocksuite/store';
import type { Workspace } from '@toeverything/infra'; import type { Workspace } from '@toeverything/infra';
import { useService, WorkspaceManager } from '@toeverything/infra';
import { useState } from 'react';
import { useNavigateHelper } from '../../../hooks/use-navigate-helper';
import { EnableAffineCloudModal } from '../enable-affine-cloud-modal';
import { ShareMenu } from './share-menu'; import { ShareMenu } from './share-menu';
type SharePageModalProps = { type SharePageModalProps = {
@@ -20,37 +15,18 @@ export const SharePageButton = ({
page, page,
isJournal, isJournal,
}: SharePageModalProps) => { }: SharePageModalProps) => {
const [open, setOpen] = useState(false); const confirmEnableCloud = useEnableCloud();
const { openPage } = useNavigateHelper();
const workspaceManager = useService(WorkspaceManager);
const handleConfirm = useAsyncCallback(async () => {
if (workspace.flavour !== WorkspaceFlavour.LOCAL) {
return;
}
const { id: newId } =
await workspaceManager.transformLocalToCloud(workspace);
openPage(newId, page.id);
setOpen(false);
}, [openPage, page.id, workspace, workspaceManager]);
return ( return (
<> <ShareMenu
<ShareMenu isJournal={isJournal}
isJournal={isJournal} workspaceMetadata={workspace.meta}
workspaceMetadata={workspace.meta} currentPage={page}
currentPage={page} onEnableAffineCloud={() =>
onEnableAffineCloud={() => setOpen(true)} confirmEnableCloud(workspace, {
/> openPageId: page.id,
{workspace.flavour === WorkspaceFlavour.LOCAL ? ( })
<EnableAffineCloudModal }
open={open} />
onOpenChange={setOpen}
onConfirm={handleConfirm}
/>
) : null}
</>
); );
}; };

View File

@@ -2,6 +2,7 @@ import { ScrollableContainer } from '@affine/component';
import { Divider } from '@affine/component/ui/divider'; import { Divider } from '@affine/component/ui/divider';
import { WorkspaceList } from '@affine/component/workspace-list'; import { WorkspaceList } from '@affine/component/workspace-list';
import { useSession } from '@affine/core/hooks/affine/use-current-user'; import { useSession } from '@affine/core/hooks/affine/use-current-user';
import { useEnableCloud } from '@affine/core/hooks/affine/use-enable-cloud';
import { import {
useWorkspaceAvatar, useWorkspaceAvatar,
useWorkspaceName, useWorkspaceName,
@@ -13,7 +14,7 @@ import type { DragEndEvent } from '@dnd-kit/core';
import type { WorkspaceMetadata } from '@toeverything/infra'; import type { WorkspaceMetadata } from '@toeverything/infra';
import { useLiveData, useService, WorkspaceManager } from '@toeverything/infra'; import { useLiveData, useService, WorkspaceManager } from '@toeverything/infra';
import { useSetAtom } from 'jotai'; import { useSetAtom } from 'jotai';
import { useCallback, useMemo } from 'react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { import {
openCreateWorkspaceModalAtom, openCreateWorkspaceModalAtom,
@@ -28,8 +29,10 @@ interface WorkspaceModalProps {
disabled?: boolean; disabled?: boolean;
workspaces: WorkspaceMetadata[]; workspaces: WorkspaceMetadata[];
currentWorkspaceId?: string | null; currentWorkspaceId?: string | null;
openingId?: string | null;
onClickWorkspace: (workspaceMetadata: WorkspaceMetadata) => void; onClickWorkspace: (workspaceMetadata: WorkspaceMetadata) => void;
onClickWorkspaceSetting: (workspaceMetadata: WorkspaceMetadata) => void; onClickWorkspaceSetting: (workspaceMetadata: WorkspaceMetadata) => void;
onClickEnableCloud?: (meta: WorkspaceMetadata) => void;
onNewWorkspace: () => void; onNewWorkspace: () => void;
onAddWorkspace: () => void; onAddWorkspace: () => void;
onDragEnd: (event: DragEndEvent) => void; onDragEnd: (event: DragEndEvent) => void;
@@ -77,6 +80,8 @@ const LocalWorkspaces = ({
workspaces, workspaces,
onClickWorkspace, onClickWorkspace,
onClickWorkspaceSetting, onClickWorkspaceSetting,
onClickEnableCloud,
openingId,
currentWorkspaceId, currentWorkspaceId,
onDragEnd, onDragEnd,
}: WorkspaceModalProps) => { }: WorkspaceModalProps) => {
@@ -95,11 +100,13 @@ const LocalWorkspaces = ({
{t['com.affine.workspaceList.workspaceListType.local']()} {t['com.affine.workspaceList.workspaceListType.local']()}
</div> </div>
<WorkspaceList <WorkspaceList
openingId={openingId}
disabled={disabled} disabled={disabled}
items={workspaces} items={workspaces}
currentWorkspaceId={currentWorkspaceId} currentWorkspaceId={currentWorkspaceId}
onClick={onClickWorkspace} onClick={onClickWorkspace}
onSettingClick={onClickWorkspaceSetting} onSettingClick={onClickWorkspaceSetting}
onEnableCloudClick={onClickEnableCloud}
onDragEnd={onDragEnd} onDragEnd={onDragEnd}
useIsWorkspaceOwner={useIsWorkspaceOwner} useIsWorkspaceOwner={useIsWorkspaceOwner}
useWorkspaceName={useWorkspaceName} useWorkspaceName={useWorkspaceName}
@@ -114,11 +121,13 @@ export const AFFiNEWorkspaceList = ({
}: { }: {
onEventEnd?: () => void; onEventEnd?: () => void;
}) => { }) => {
const workspaces = useLiveData( const openWsRef = useRef<ReturnType<typeof workspaceManager.open>>();
useService(WorkspaceManager).list.workspaceList$ const workspaceManager = useService(WorkspaceManager);
); const workspaces = useLiveData(workspaceManager.list.workspaceList$);
const setOpenCreateWorkspaceModal = useSetAtom(openCreateWorkspaceModalAtom); const setOpenCreateWorkspaceModal = useSetAtom(openCreateWorkspaceModalAtom);
const [openingId, setOpeningId] = useState<string | null>(null);
const confirmEnableCloud = useEnableCloud();
const { jumpToSubPath } = useNavigateHelper(); const { jumpToSubPath } = useNavigateHelper();
@@ -160,6 +169,28 @@ export const AFFiNEWorkspaceList = ({
[onEventEnd, setOpenSettingModalAtom] [onEventEnd, setOpenSettingModalAtom]
); );
const onClickEnableCloud = useCallback(
(meta: WorkspaceMetadata) => {
openWsRef.current?.release();
openWsRef.current = workspaceManager.open(meta);
confirmEnableCloud(openWsRef.current.workspace, {
onFinished: () => {
openWsRef.current?.release();
openWsRef.current = undefined;
setOpeningId(null);
},
});
setOpeningId(meta.id);
},
[confirmEnableCloud, workspaceManager]
);
useEffect(() => {
return () => {
openWsRef.current?.release();
};
}, []);
const onMoveWorkspace = useCallback((_activeId: string, _overId: string) => { const onMoveWorkspace = useCallback((_activeId: string, _overId: string) => {
// TODO: order // TODO: order
// const oldIndex = workspaces.findIndex(w => w.id === activeId); // const oldIndex = workspaces.findIndex(w => w.id === activeId);
@@ -219,9 +250,11 @@ export const AFFiNEWorkspaceList = ({
</div> </div>
) : null} ) : null}
<LocalWorkspaces <LocalWorkspaces
openingId={openingId}
workspaces={localWorkspaces} workspaces={localWorkspaces}
onClickWorkspace={onClickWorkspace} onClickWorkspace={onClickWorkspace}
onClickWorkspaceSetting={onClickWorkspaceSetting} onClickWorkspaceSetting={onClickWorkspaceSetting}
onClickEnableCloud={onClickEnableCloud}
onNewWorkspace={onNewWorkspace} onNewWorkspace={onNewWorkspace}
onAddWorkspace={onAddWorkspace} onAddWorkspace={onAddWorkspace}
currentWorkspaceId={currentWorkspace?.id} currentWorkspaceId={currentWorkspace?.id}

View File

@@ -1,18 +1,14 @@
import { BrowserWarning, LocalDemoTips } from '@affine/component/affine-banner'; import { BrowserWarning, LocalDemoTips } from '@affine/component/affine-banner';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { WorkspaceFlavour } from '@affine/env/workspace'; import { WorkspaceFlavour } from '@affine/env/workspace';
import { Trans } from '@affine/i18n'; import { Trans } from '@affine/i18n';
import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { useAFFiNEI18N } from '@affine/i18n/hooks';
import type { Workspace } from '@toeverything/infra'; import type { Workspace } from '@toeverything/infra';
import { useService, WorkspaceManager } from '@toeverything/infra';
import { useSetAtom } from 'jotai'; import { useSetAtom } from 'jotai';
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
import { authAtom } from '../atoms'; import { authAtom } from '../atoms';
import { useCurrentLoginStatus } from '../hooks/affine/use-current-login-status'; import { useCurrentLoginStatus } from '../hooks/affine/use-current-login-status';
import { useNavigateHelper } from '../hooks/use-navigate-helper'; import { useEnableCloud } from '../hooks/affine/use-enable-cloud';
import { WorkspaceSubPath } from '../shared';
import { EnableAffineCloudModal } from './affine/enable-affine-cloud-modal';
const minimumChromeVersion = 106; const minimumChromeVersion = 106;
@@ -68,47 +64,29 @@ export const TopTip = ({
const [showWarning, setShowWarning] = useState(shouldShowWarning); const [showWarning, setShowWarning] = useState(shouldShowWarning);
const [showLocalDemoTips, setShowLocalDemoTips] = useState(true); const [showLocalDemoTips, setShowLocalDemoTips] = useState(true);
const [open, setOpen] = useState(false); const confirmEnableCloud = useEnableCloud();
const setAuthModal = useSetAtom(authAtom); const setAuthModal = useSetAtom(authAtom);
const onLogin = useCallback(() => { const onLogin = useCallback(() => {
setAuthModal({ openModal: true, state: 'signIn' }); setAuthModal({ openModal: true, state: 'signIn' });
}, [setAuthModal]); }, [setAuthModal]);
const { openPage } = useNavigateHelper();
const workspaceManager = useService(WorkspaceManager);
const handleConfirm = useAsyncCallback(async () => {
if (workspace.flavour !== WorkspaceFlavour.LOCAL) {
return;
}
// TODO: we need to transform local to cloud
const { id: newId } =
await workspaceManager.transformLocalToCloud(workspace);
openPage(newId, pageId || WorkspaceSubPath.ALL);
setOpen(false);
}, [openPage, pageId, workspace, workspaceManager]);
if ( if (
showLocalDemoTips && showLocalDemoTips &&
!environment.isDesktop && !environment.isDesktop &&
workspace.flavour === WorkspaceFlavour.LOCAL workspace.flavour === WorkspaceFlavour.LOCAL
) { ) {
return ( return (
<> <LocalDemoTips
<LocalDemoTips isLoggedIn={isLoggedIn}
isLoggedIn={isLoggedIn} onLogin={onLogin}
onLogin={onLogin} onEnableCloud={() =>
onEnableCloud={() => setOpen(true)} confirmEnableCloud(workspace, { openPageId: pageId })
onClose={() => { }
setShowLocalDemoTips(false); onClose={() => {
}} setShowLocalDemoTips(false);
/> }}
<EnableAffineCloudModal />
open={open}
onOpenChange={setOpen}
onConfirm={handleConfirm}
/>
</>
); );
} }

View File

@@ -0,0 +1,109 @@
import { useConfirmModal } from '@affine/component';
import { authAtom } from '@affine/core/atoms';
import { setOnceSignedInEventAtom } from '@affine/core/atoms/event';
import { WorkspaceSubPath } from '@affine/core/shared';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import type { Workspace } from '@toeverything/infra';
import { useService, WorkspaceManager } from '@toeverything/infra';
import { useSetAtom } from 'jotai';
import { useCallback } from 'react';
import { useNavigateHelper } from '../use-navigate-helper';
import { useCurrentLoginStatus } from './use-current-login-status';
interface ConfirmEnableCloudOptions {
/**
* Fired when the workspace is successfully enabled
*/
onSuccess?: () => void;
/**
* Fired when workspace is successfully enabled or user cancels the operation
*/
onFinished?: () => void;
openPageId?: string;
}
type ConfirmEnableArgs = [Workspace, ConfirmEnableCloudOptions | undefined];
export const useEnableCloud = () => {
const t = useAFFiNEI18N();
const loginStatus = useCurrentLoginStatus();
const setAuthAtom = useSetAtom(authAtom);
const setOnceSignedInEvent = useSetAtom(setOnceSignedInEventAtom);
const { openConfirmModal, closeConfirmModal } = useConfirmModal();
const workspaceManager = useService(WorkspaceManager);
const { openPage } = useNavigateHelper();
const enableCloud = useCallback(
async (ws: Workspace | null, options?: ConfirmEnableCloudOptions) => {
if (!ws) return;
const { id: newId } = await workspaceManager.transformLocalToCloud(ws);
openPage(newId, options?.openPageId || WorkspaceSubPath.ALL);
options?.onSuccess?.();
},
[openPage, workspaceManager]
);
const openSignIn = useCallback(
(...args: ConfirmEnableArgs) => {
setAuthAtom(prev => ({ ...prev, openModal: true }));
setOnceSignedInEvent(() => {
enableCloud(...args).catch(console.error);
});
},
[enableCloud, setAuthAtom, setOnceSignedInEvent]
);
const signInOrEnableCloud = useCallback(
async (...args: ConfirmEnableArgs) => {
// not logged in, open login modal
if (loginStatus === 'unauthenticated') {
openSignIn(...args);
}
if (loginStatus === 'authenticated') {
await enableCloud(...args);
}
},
[enableCloud, loginStatus, openSignIn]
);
const confirmEnableCloud = useCallback(
(ws: Workspace, options?: ConfirmEnableCloudOptions) => {
const { onSuccess, onFinished } = options ?? {};
if (!runtimeConfig.enableCloud) return;
const closeOnSuccess = () => {
closeConfirmModal();
onSuccess?.();
};
openConfirmModal(
{
title: t['Enable AFFiNE Cloud'](),
description: t['Enable AFFiNE Cloud Description'](),
cancelText: t['com.affine.enableAffineCloudModal.button.cancel'](),
confirmButtonOptions: {
type: 'primary',
['data-testid' as string]: 'confirm-enable-affine-cloud-button',
children:
loginStatus === 'authenticated'
? t['Enable']()
: t['Sign in and Enable'](),
},
onConfirm: async () =>
await signInOrEnableCloud(ws, {
...options,
onSuccess: closeOnSuccess,
}),
onOpenChange: open => {
if (!open) onFinished?.();
},
},
{ autoClose: false }
);
},
[closeConfirmModal, loginStatus, openConfirmModal, signInOrEnableCloud, t]
);
return confirmEnableCloud;
};