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:
@@ -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}
|
||||||
|
|||||||
@@ -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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -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}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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} />
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -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}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
109
packages/frontend/core/src/hooks/affine/use-enable-cloud.tsx
Normal file
109
packages/frontend/core/src/hooks/affine/use-enable-cloud.tsx
Normal 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;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user