mirror of
https://github.com/toeverything/AFFiNE.git
synced 2024-12-23 12:52:20 +03:00
refactor: workspace list (#4432)
This commit is contained in:
parent
092e2e0a3d
commit
edd7d00104
@ -0,0 +1,24 @@
|
|||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
export const ItemContainer = style({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'flex-start',
|
||||||
|
padding: '8px 14px',
|
||||||
|
gap: '14px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
borderRadius: '8px',
|
||||||
|
transition: 'background-color 0.2s',
|
||||||
|
fontSize: '24px',
|
||||||
|
color: 'var(--affine-icon-secondary)',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ItemText = style({
|
||||||
|
fontSize: 'var(--affine-font-sm)',
|
||||||
|
lineHeight: '22px',
|
||||||
|
color: 'var(--affine-text-secondary-color)',
|
||||||
|
fontWeight: 400,
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
});
|
@ -0,0 +1,44 @@
|
|||||||
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
|
import { ImportIcon, PlusIcon } from '@blocksuite/icons';
|
||||||
|
import { MenuItem } from '@toeverything/components/menu';
|
||||||
|
|
||||||
|
import * as styles from './index.css';
|
||||||
|
|
||||||
|
export const AddWorkspace = ({
|
||||||
|
onAddWorkspace,
|
||||||
|
onNewWorkspace,
|
||||||
|
}: {
|
||||||
|
onAddWorkspace?: () => void;
|
||||||
|
onNewWorkspace?: () => void;
|
||||||
|
}) => {
|
||||||
|
const t = useAFFiNEI18N();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{runtimeConfig.enableSQLiteProvider && environment.isDesktop ? (
|
||||||
|
<MenuItem
|
||||||
|
block={true}
|
||||||
|
preFix={<ImportIcon />}
|
||||||
|
onClick={onAddWorkspace}
|
||||||
|
data-testid="add-workspace"
|
||||||
|
className={styles.ItemContainer}
|
||||||
|
>
|
||||||
|
<div className={styles.ItemText}>
|
||||||
|
{t['com.affine.workspace.local.import']()}
|
||||||
|
</div>
|
||||||
|
</MenuItem>
|
||||||
|
) : null}
|
||||||
|
<MenuItem
|
||||||
|
block={true}
|
||||||
|
preFix={<PlusIcon />}
|
||||||
|
onClick={onNewWorkspace}
|
||||||
|
data-testid="new-workspace"
|
||||||
|
className={styles.ItemContainer}
|
||||||
|
>
|
||||||
|
<div className={styles.ItemText}>
|
||||||
|
{t['com.affine.workspaceList.addWorkspace.create']()}
|
||||||
|
</div>
|
||||||
|
</MenuItem>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,57 @@
|
|||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
export const workspaceListWrapper = style({
|
||||||
|
display: 'flex',
|
||||||
|
width: '100%',
|
||||||
|
flexDirection: 'column',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const signInWrapper = style({
|
||||||
|
display: 'flex',
|
||||||
|
width: '100%',
|
||||||
|
gap: '12px',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'flex-start',
|
||||||
|
borderRadius: '8px',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const iconContainer = style({
|
||||||
|
width: '28px',
|
||||||
|
padding: '2px 4px 4px',
|
||||||
|
borderRadius: '14px',
|
||||||
|
background: 'var(--affine-white)',
|
||||||
|
display: 'flex',
|
||||||
|
border: '1px solid var(--affine-icon-secondary)',
|
||||||
|
color: 'var(--affine-icon-secondary)',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
fontSize: '20px',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const signInTextContainer = style({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const signInTextPrimary = style({
|
||||||
|
fontSize: 'var(--affine-font-sm)',
|
||||||
|
fontWeight: 600,
|
||||||
|
lineHeight: '22px',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const signInTextSecondary = style({
|
||||||
|
fontSize: 'var(--affine-font-xs)',
|
||||||
|
fontWeight: 400,
|
||||||
|
lineHeight: '20px',
|
||||||
|
color: 'var(--affine-text-secondary-color)',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const menuItem = style({
|
||||||
|
borderRadius: '8px',
|
||||||
|
});
|
@ -1,155 +1,62 @@
|
|||||||
import { WorkspaceList } from '@affine/component/workspace-list';
|
|
||||||
import type {
|
|
||||||
AffineCloudWorkspace,
|
|
||||||
LocalWorkspace,
|
|
||||||
} from '@affine/env/workspace';
|
|
||||||
import { WorkspaceFlavour, WorkspaceSubPath } from '@affine/env/workspace';
|
|
||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import type { RootWorkspaceMetadata } from '@affine/workspace/atom';
|
|
||||||
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
||||||
import {
|
import { Logo1Icon } from '@blocksuite/icons';
|
||||||
AccountIcon,
|
|
||||||
ImportIcon,
|
|
||||||
Logo1Icon,
|
|
||||||
MoreHorizontalIcon,
|
|
||||||
PlusIcon,
|
|
||||||
SignOutIcon,
|
|
||||||
} from '@blocksuite/icons';
|
|
||||||
import type { DragEndEvent } from '@dnd-kit/core';
|
|
||||||
import { arrayMove } from '@dnd-kit/sortable';
|
|
||||||
import { IconButton } from '@toeverything/components/button';
|
|
||||||
import { Divider } from '@toeverything/components/divider';
|
import { Divider } from '@toeverything/components/divider';
|
||||||
import { Menu, MenuIcon, MenuItem } from '@toeverything/components/menu';
|
import { MenuItem } from '@toeverything/components/menu';
|
||||||
import {
|
import { useAtomValue, useSetAtom } from 'jotai';
|
||||||
currentPageIdAtom,
|
|
||||||
currentWorkspaceIdAtom,
|
|
||||||
} from '@toeverything/infra/atom';
|
|
||||||
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
||||||
import { useSession } from 'next-auth/react';
|
import { useSession } from 'next-auth/react';
|
||||||
import { startTransition, useCallback, useMemo, useTransition } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
authAtom,
|
authAtom,
|
||||||
openCreateWorkspaceModalAtom,
|
openCreateWorkspaceModalAtom,
|
||||||
openDisableCloudAlertModalAtom,
|
openDisableCloudAlertModalAtom,
|
||||||
openSettingModalAtom,
|
|
||||||
} from '../../../../atoms';
|
} from '../../../../atoms';
|
||||||
import type { AllWorkspace } from '../../../../shared';
|
import { AddWorkspace } from './add-workspace';
|
||||||
import { signOutCloud } from '../../../../utils/cloud-utils';
|
import * as styles from './index.css';
|
||||||
import { useNavigateHelper } from '../.././../../hooks/use-navigate-helper';
|
import { UserAccountItem } from './user-account';
|
||||||
import {
|
import { AFFiNEWorkspaceList } from './workspace-list';
|
||||||
StyledCreateWorkspaceCardPill,
|
|
||||||
StyledCreateWorkspaceCardPillContent,
|
|
||||||
StyledCreateWorkspaceCardPillIcon,
|
|
||||||
StyledImportWorkspaceCardPill,
|
|
||||||
StyledItem,
|
|
||||||
StyledModalBody,
|
|
||||||
StyledModalContent,
|
|
||||||
StyledModalFooterContent,
|
|
||||||
StyledModalHeader,
|
|
||||||
StyledModalHeaderContent,
|
|
||||||
StyledModalHeaderLeft,
|
|
||||||
StyledModalTitle,
|
|
||||||
StyledOperationWrapper,
|
|
||||||
StyledSignInCardPill,
|
|
||||||
StyledSignInCardPillTextCotainer,
|
|
||||||
StyledSignInCardPillTextPrimary,
|
|
||||||
StyledSignInCardPillTextSecondary,
|
|
||||||
StyledWorkspaceFlavourTitle,
|
|
||||||
} from './styles';
|
|
||||||
|
|
||||||
interface WorkspaceModalProps {
|
const SignInItem = () => {
|
||||||
disabled?: boolean;
|
const setDisableCloudOpen = useSetAtom(openDisableCloudAlertModalAtom);
|
||||||
workspaces: RootWorkspaceMetadata[];
|
|
||||||
currentWorkspaceId: AllWorkspace['id'] | null;
|
const setOpen = useSetAtom(authAtom);
|
||||||
onClickWorkspace: (workspace: RootWorkspaceMetadata['id']) => void;
|
|
||||||
onClickWorkspaceSetting: (workspace: RootWorkspaceMetadata['id']) => void;
|
|
||||||
onNewWorkspace: () => void;
|
|
||||||
onAddWorkspace: () => void;
|
|
||||||
onMoveWorkspace: (activeId: string, overId: string) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const AccountMenu = ({
|
|
||||||
onOpenAccountSetting,
|
|
||||||
onSignOut,
|
|
||||||
}: {
|
|
||||||
onOpenAccountSetting: () => void;
|
|
||||||
onSignOut: () => void;
|
|
||||||
}) => {
|
|
||||||
const t = useAFFiNEI18N();
|
const t = useAFFiNEI18N();
|
||||||
|
|
||||||
return (
|
const onClickSignIn = useCallback(async () => {
|
||||||
<div>
|
if (!runtimeConfig.enableCloud) {
|
||||||
<MenuItem
|
setDisableCloudOpen(true);
|
||||||
preFix={
|
} else {
|
||||||
<MenuIcon>
|
setOpen(state => ({
|
||||||
<AccountIcon />
|
...state,
|
||||||
</MenuIcon>
|
openModal: true,
|
||||||
}
|
}));
|
||||||
data-testid="editor-option-menu-import"
|
}
|
||||||
onClick={onOpenAccountSetting}
|
}, [setOpen, setDisableCloudOpen]);
|
||||||
>
|
|
||||||
{t['com.affine.workspace.cloud.account.settings']()}
|
|
||||||
</MenuItem>
|
|
||||||
<Divider />
|
|
||||||
<MenuItem
|
|
||||||
preFix={
|
|
||||||
<MenuIcon>
|
|
||||||
<SignOutIcon />
|
|
||||||
</MenuIcon>
|
|
||||||
}
|
|
||||||
data-testid="editor-option-menu-import"
|
|
||||||
onClick={onSignOut}
|
|
||||||
>
|
|
||||||
{t['com.affine.workspace.cloud.account.logout']()}
|
|
||||||
</MenuItem>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const CloudWorkSpaceList = ({
|
|
||||||
disabled,
|
|
||||||
workspaces,
|
|
||||||
onClickWorkspace,
|
|
||||||
onClickWorkspaceSetting,
|
|
||||||
currentWorkspaceId,
|
|
||||||
onMoveWorkspace,
|
|
||||||
}: WorkspaceModalProps) => {
|
|
||||||
const t = useAFFiNEI18N();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<MenuItem
|
||||||
<StyledModalHeader>
|
className={styles.menuItem}
|
||||||
<StyledModalHeaderLeft>
|
onClick={onClickSignIn}
|
||||||
<StyledWorkspaceFlavourTitle>
|
data-testid="cloud-signin-button"
|
||||||
{t['com.affine.workspace.cloud']()}
|
>
|
||||||
</StyledWorkspaceFlavourTitle>
|
<div className={styles.signInWrapper}>
|
||||||
</StyledModalHeaderLeft>
|
<div className={styles.iconContainer}>
|
||||||
</StyledModalHeader>
|
<Logo1Icon />
|
||||||
<StyledModalContent>
|
</div>
|
||||||
<WorkspaceList
|
|
||||||
disabled={disabled}
|
<div className={styles.signInTextContainer}>
|
||||||
items={
|
<div className={styles.signInTextPrimary}>
|
||||||
workspaces.filter(
|
{t['com.affine.workspace.cloud.auth']()}
|
||||||
({ flavour }) => flavour === WorkspaceFlavour.AFFINE_CLOUD
|
</div>
|
||||||
) as (AffineCloudWorkspace | LocalWorkspace)[]
|
<div className={styles.signInTextSecondary}>
|
||||||
}
|
{t['com.affine.workspace.cloud.description']()}
|
||||||
currentWorkspaceId={currentWorkspaceId}
|
</div>
|
||||||
onClick={onClickWorkspace}
|
</div>
|
||||||
onSettingClick={onClickWorkspaceSetting}
|
</div>
|
||||||
onDragEnd={useCallback(
|
</MenuItem>
|
||||||
(event: DragEndEvent) => {
|
|
||||||
const { active, over } = event;
|
|
||||||
if (active.id !== over?.id) {
|
|
||||||
onMoveWorkspace(active.id as string, over?.id as string);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[onMoveWorkspace]
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</StyledModalContent>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -158,240 +65,43 @@ export const UserWithWorkspaceList = ({
|
|||||||
}: {
|
}: {
|
||||||
onEventEnd?: () => void;
|
onEventEnd?: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
|
const { data: session, status } = useSession();
|
||||||
|
|
||||||
|
const isAuthenticated = useMemo(() => status === 'authenticated', [status]);
|
||||||
|
|
||||||
const setOpenCreateWorkspaceModal = useSetAtom(openCreateWorkspaceModalAtom);
|
const setOpenCreateWorkspaceModal = useSetAtom(openCreateWorkspaceModalAtom);
|
||||||
|
|
||||||
const { jumpToSubPath, jumpToIndex } = useNavigateHelper();
|
|
||||||
const workspaces = useAtomValue(rootWorkspacesMetadataAtom, {
|
|
||||||
delay: 0,
|
|
||||||
});
|
|
||||||
const setWorkspaces = useSetAtom(rootWorkspacesMetadataAtom);
|
|
||||||
const [currentWorkspaceId, setCurrentWorkspaceId] = useAtom(
|
|
||||||
currentWorkspaceIdAtom
|
|
||||||
);
|
|
||||||
const setCurrentPageId = useSetAtom(currentPageIdAtom);
|
|
||||||
const [, startCloseTransition] = useTransition();
|
|
||||||
const setOpenSettingModalAtom = useSetAtom(openSettingModalAtom);
|
|
||||||
const setSettingModalAtom = useSetAtom(openSettingModalAtom);
|
|
||||||
|
|
||||||
const t = useAFFiNEI18N();
|
|
||||||
const setOpen = useSetAtom(authAtom);
|
|
||||||
const setDisableCloudOpen = useSetAtom(openDisableCloudAlertModalAtom);
|
|
||||||
// TODO: AFFiNE Cloud support
|
|
||||||
const { data: session, status } = useSession();
|
|
||||||
const isLoggedIn = useMemo(() => status === 'authenticated', [status]);
|
|
||||||
const cloudWorkspaces = useMemo(
|
|
||||||
() =>
|
|
||||||
workspaces.filter(
|
|
||||||
({ flavour }) => flavour === WorkspaceFlavour.AFFINE_CLOUD
|
|
||||||
) as (AffineCloudWorkspace | LocalWorkspace)[],
|
|
||||||
[workspaces]
|
|
||||||
);
|
|
||||||
const localWorkspaces = useMemo(
|
|
||||||
() =>
|
|
||||||
workspaces.filter(
|
|
||||||
({ flavour }) => flavour === WorkspaceFlavour.LOCAL
|
|
||||||
) as (AffineCloudWorkspace | LocalWorkspace)[],
|
|
||||||
[workspaces]
|
|
||||||
);
|
|
||||||
|
|
||||||
const onClickWorkspaceSetting = useCallback(
|
|
||||||
(workspaceId: string) => {
|
|
||||||
setOpenSettingModalAtom({
|
|
||||||
open: true,
|
|
||||||
activeTab: 'workspace',
|
|
||||||
workspaceId,
|
|
||||||
});
|
|
||||||
onEventEnd?.();
|
|
||||||
},
|
|
||||||
[onEventEnd, setOpenSettingModalAtom]
|
|
||||||
);
|
|
||||||
|
|
||||||
const onMoveWorkspace = useCallback(
|
|
||||||
(activeId: string, overId: string) => {
|
|
||||||
const oldIndex = workspaces.findIndex(w => w.id === activeId);
|
|
||||||
const newIndex = workspaces.findIndex(w => w.id === overId);
|
|
||||||
startTransition(() => {
|
|
||||||
setWorkspaces(workspaces => arrayMove(workspaces, oldIndex, newIndex));
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[setWorkspaces, workspaces]
|
|
||||||
);
|
|
||||||
const onClickWorkspace = useCallback(
|
|
||||||
(workspaceId: string) => {
|
|
||||||
startCloseTransition(() => {
|
|
||||||
setCurrentWorkspaceId(workspaceId);
|
|
||||||
setCurrentPageId(null);
|
|
||||||
jumpToSubPath(workspaceId, WorkspaceSubPath.ALL);
|
|
||||||
});
|
|
||||||
onEventEnd?.();
|
|
||||||
},
|
|
||||||
[jumpToSubPath, onEventEnd, setCurrentPageId, setCurrentWorkspaceId]
|
|
||||||
);
|
|
||||||
const onNewWorkspace = useCallback(() => {
|
const onNewWorkspace = useCallback(() => {
|
||||||
setOpenCreateWorkspaceModal('new');
|
setOpenCreateWorkspaceModal('new');
|
||||||
onEventEnd?.();
|
onEventEnd?.();
|
||||||
}, [onEventEnd, setOpenCreateWorkspaceModal]);
|
}, [onEventEnd, setOpenCreateWorkspaceModal]);
|
||||||
|
|
||||||
const onAddWorkspace = useCallback(async () => {
|
const onAddWorkspace = useCallback(async () => {
|
||||||
setOpenCreateWorkspaceModal('add');
|
setOpenCreateWorkspaceModal('add');
|
||||||
onEventEnd?.();
|
onEventEnd?.();
|
||||||
}, [onEventEnd, setOpenCreateWorkspaceModal]);
|
}, [onEventEnd, setOpenCreateWorkspaceModal]);
|
||||||
|
|
||||||
const onOpenAccountSetting = useCallback(() => {
|
const workspaces = useAtomValue(rootWorkspacesMetadataAtom, {
|
||||||
setSettingModalAtom(prev => ({
|
delay: 0,
|
||||||
...prev,
|
});
|
||||||
open: true,
|
|
||||||
activeTab: 'account',
|
|
||||||
}));
|
|
||||||
onEventEnd?.();
|
|
||||||
}, [onEventEnd, setSettingModalAtom]);
|
|
||||||
const onSignOut = useCallback(async () => {
|
|
||||||
signOutCloud()
|
|
||||||
.then(() => {
|
|
||||||
jumpToIndex();
|
|
||||||
})
|
|
||||||
.catch(console.error);
|
|
||||||
onEventEnd?.();
|
|
||||||
}, [onEventEnd, jumpToIndex]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className={styles.workspaceListWrapper}>
|
||||||
{!isLoggedIn ? (
|
{isAuthenticated ? (
|
||||||
<StyledModalHeaderContent>
|
<UserAccountItem
|
||||||
<StyledSignInCardPill>
|
email={session?.user.email ?? 'Unknown User'}
|
||||||
<StyledItem
|
onEventEnd={onEventEnd}
|
||||||
onClick={async () => {
|
/>
|
||||||
if (!runtimeConfig.enableCloud) {
|
|
||||||
setDisableCloudOpen(true);
|
|
||||||
} else {
|
|
||||||
setOpen(state => ({
|
|
||||||
...state,
|
|
||||||
openModal: true,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
data-testid="cloud-signin-button"
|
|
||||||
>
|
|
||||||
<StyledCreateWorkspaceCardPillContent>
|
|
||||||
<StyledCreateWorkspaceCardPillIcon>
|
|
||||||
<Logo1Icon />
|
|
||||||
</StyledCreateWorkspaceCardPillIcon>
|
|
||||||
<StyledSignInCardPillTextCotainer>
|
|
||||||
<StyledSignInCardPillTextPrimary>
|
|
||||||
{t['com.affine.workspace.cloud.auth']()}
|
|
||||||
</StyledSignInCardPillTextPrimary>
|
|
||||||
<StyledSignInCardPillTextSecondary>
|
|
||||||
{t['com.affine.workspace.cloud.description']()}
|
|
||||||
</StyledSignInCardPillTextSecondary>
|
|
||||||
</StyledSignInCardPillTextCotainer>
|
|
||||||
</StyledCreateWorkspaceCardPillContent>
|
|
||||||
</StyledItem>
|
|
||||||
</StyledSignInCardPill>
|
|
||||||
<Divider
|
|
||||||
style={{
|
|
||||||
margin: '12px 0px',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</StyledModalHeaderContent>
|
|
||||||
) : (
|
) : (
|
||||||
<StyledModalHeaderContent>
|
<SignInItem />
|
||||||
<StyledModalHeader>
|
|
||||||
<StyledModalTitle>{session?.user.email}</StyledModalTitle>
|
|
||||||
<StyledOperationWrapper>
|
|
||||||
<Menu
|
|
||||||
items={
|
|
||||||
<AccountMenu
|
|
||||||
onOpenAccountSetting={onOpenAccountSetting}
|
|
||||||
onSignOut={onSignOut}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
contentOptions={{
|
|
||||||
side: 'right',
|
|
||||||
sideOffset: 30,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<IconButton
|
|
||||||
data-testid="more-button"
|
|
||||||
icon={<MoreHorizontalIcon />}
|
|
||||||
type="plain"
|
|
||||||
/>
|
|
||||||
</Menu>
|
|
||||||
</StyledOperationWrapper>
|
|
||||||
</StyledModalHeader>
|
|
||||||
<Divider style={{ margin: '12px 0px' }} />
|
|
||||||
</StyledModalHeaderContent>
|
|
||||||
)}
|
)}
|
||||||
<StyledModalBody>
|
<Divider size="thinner" />
|
||||||
{isLoggedIn && cloudWorkspaces.length !== 0 ? (
|
<AFFiNEWorkspaceList workspaces={workspaces} onEventEnd={onEventEnd} />
|
||||||
<>
|
{workspaces.length > 0 ? <Divider size="thinner" /> : null}
|
||||||
<CloudWorkSpaceList
|
<AddWorkspace
|
||||||
workspaces={workspaces}
|
onAddWorkspace={onAddWorkspace}
|
||||||
onClickWorkspace={onClickWorkspace}
|
onNewWorkspace={onNewWorkspace}
|
||||||
onClickWorkspaceSetting={onClickWorkspaceSetting}
|
/>
|
||||||
onNewWorkspace={onNewWorkspace}
|
</div>
|
||||||
onAddWorkspace={onAddWorkspace}
|
|
||||||
currentWorkspaceId={currentWorkspaceId}
|
|
||||||
onMoveWorkspace={onMoveWorkspace}
|
|
||||||
/>
|
|
||||||
<Divider
|
|
||||||
style={{
|
|
||||||
margin: '12px 0px',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
<StyledModalHeader>
|
|
||||||
<StyledWorkspaceFlavourTitle>
|
|
||||||
{t['com.affine.workspace.local']()}
|
|
||||||
</StyledWorkspaceFlavourTitle>
|
|
||||||
</StyledModalHeader>
|
|
||||||
<StyledModalContent>
|
|
||||||
<WorkspaceList
|
|
||||||
items={localWorkspaces}
|
|
||||||
currentWorkspaceId={currentWorkspaceId}
|
|
||||||
onClick={onClickWorkspace}
|
|
||||||
onSettingClick={onClickWorkspaceSetting}
|
|
||||||
onDragEnd={useCallback(
|
|
||||||
(event: DragEndEvent) => {
|
|
||||||
const { active, over } = event;
|
|
||||||
if (active.id !== over?.id) {
|
|
||||||
onMoveWorkspace(active.id as string, over?.id as string);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[onMoveWorkspace]
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</StyledModalContent>
|
|
||||||
{runtimeConfig.enableSQLiteProvider && environment.isDesktop ? (
|
|
||||||
<StyledImportWorkspaceCardPill>
|
|
||||||
<StyledItem onClick={onAddWorkspace} data-testid="add-workspace">
|
|
||||||
<StyledCreateWorkspaceCardPillContent
|
|
||||||
style={{ gap: '14px', paddingLeft: '2px' }}
|
|
||||||
>
|
|
||||||
<StyledCreateWorkspaceCardPillIcon style={{ fontSize: '24px' }}>
|
|
||||||
<ImportIcon />
|
|
||||||
</StyledCreateWorkspaceCardPillIcon>
|
|
||||||
<div>
|
|
||||||
<p>{t['com.affine.workspace.local.import']()}</p>
|
|
||||||
</div>
|
|
||||||
</StyledCreateWorkspaceCardPillContent>
|
|
||||||
</StyledItem>
|
|
||||||
</StyledImportWorkspaceCardPill>
|
|
||||||
) : null}
|
|
||||||
</StyledModalBody>
|
|
||||||
<StyledModalFooterContent>
|
|
||||||
<StyledCreateWorkspaceCardPill>
|
|
||||||
<StyledItem onClick={onNewWorkspace} data-testid="new-workspace">
|
|
||||||
<StyledCreateWorkspaceCardPillContent>
|
|
||||||
<StyledCreateWorkspaceCardPillIcon>
|
|
||||||
<PlusIcon />
|
|
||||||
</StyledCreateWorkspaceCardPillIcon>
|
|
||||||
<div>
|
|
||||||
<p>{t['New Workspace']()}</p>
|
|
||||||
</div>
|
|
||||||
</StyledCreateWorkspaceCardPillContent>
|
|
||||||
</StyledItem>
|
|
||||||
</StyledCreateWorkspaceCardPill>
|
|
||||||
</StyledModalFooterContent>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,273 +0,0 @@
|
|||||||
import { displayFlex, styled, textEllipsis } from '@affine/component';
|
|
||||||
|
|
||||||
export const StyledSplitLine = styled('div')(() => {
|
|
||||||
return {
|
|
||||||
width: '1px',
|
|
||||||
height: '20px',
|
|
||||||
background: 'var(--affine-border-color)',
|
|
||||||
marginRight: '12px',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export const StyleWorkspaceInfo = styled('div')(() => {
|
|
||||||
return {
|
|
||||||
marginLeft: '15px',
|
|
||||||
width: '202px',
|
|
||||||
p: {
|
|
||||||
height: '20px',
|
|
||||||
fontSize: 'var(--affine-font-sm)',
|
|
||||||
...displayFlex('flex-start', 'center'),
|
|
||||||
},
|
|
||||||
svg: {
|
|
||||||
marginRight: '10px',
|
|
||||||
fontSize: '16px',
|
|
||||||
flexShrink: 0,
|
|
||||||
},
|
|
||||||
span: {
|
|
||||||
flexGrow: 1,
|
|
||||||
...textEllipsis(1),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export const StyleWorkspaceTitle = styled('div')(() => {
|
|
||||||
return {
|
|
||||||
fontSize: 'var(--affine-font-base)',
|
|
||||||
fontWeight: 600,
|
|
||||||
lineHeight: '24px',
|
|
||||||
marginBottom: '10px',
|
|
||||||
maxWidth: '200px',
|
|
||||||
...textEllipsis(1),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export const StyledCreateWorkspaceCard = styled('div')(() => {
|
|
||||||
return {
|
|
||||||
width: '310px',
|
|
||||||
height: '124px',
|
|
||||||
marginBottom: '24px',
|
|
||||||
cursor: 'pointer',
|
|
||||||
padding: '16px',
|
|
||||||
boxShadow: 'var(--affine-shadow-1)',
|
|
||||||
borderRadius: '12px',
|
|
||||||
transition: 'all .1s',
|
|
||||||
background: 'var(--affine-white-80)',
|
|
||||||
...displayFlex('flex-start', 'flex-start'),
|
|
||||||
color: 'var(--affine-text-secondary-color)',
|
|
||||||
|
|
||||||
':hover': {
|
|
||||||
background: 'var(--affine-hover-color)',
|
|
||||||
color: 'var(--affine-text-primary-color)',
|
|
||||||
'.add-icon': {
|
|
||||||
borderColor: 'var(--affine-white)',
|
|
||||||
color: 'var(--affine-primary-color)',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'@media (max-width: 720px)': {
|
|
||||||
width: '100%',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
export const StyledCreateWorkspaceCardPillContainer = styled('div')(() => {
|
|
||||||
return {
|
|
||||||
borderRadius: '10px',
|
|
||||||
display: 'flex',
|
|
||||||
margin: '-8px -4px',
|
|
||||||
flexFlow: 'column',
|
|
||||||
gap: '12px',
|
|
||||||
background: 'var(--affine-background-overlay-panel-color)',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export const StyledCreateWorkspaceCardPill = styled('div')(() => {
|
|
||||||
return {
|
|
||||||
borderRadius: '8px',
|
|
||||||
display: 'flex',
|
|
||||||
width: '100%',
|
|
||||||
height: '58px',
|
|
||||||
border: `1px solid var(--affine-border-color)`,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export const StyledSignInCardPill = styled('div')(() => {
|
|
||||||
return {
|
|
||||||
borderRadius: '8px',
|
|
||||||
display: 'flex',
|
|
||||||
width: '100%',
|
|
||||||
height: '58px',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export const StyledImportWorkspaceCardPill = styled('div')(() => {
|
|
||||||
return {
|
|
||||||
borderRadius: '5px',
|
|
||||||
display: 'flex',
|
|
||||||
width: '100%',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export const StyledCreateWorkspaceCardPillContent = styled('div')(() => {
|
|
||||||
return {
|
|
||||||
display: 'flex',
|
|
||||||
gap: '12px',
|
|
||||||
alignItems: 'center',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export const StyledCreateWorkspaceCardPillIcon = styled('div')(() => {
|
|
||||||
return {
|
|
||||||
fontSize: '28px',
|
|
||||||
width: '1em',
|
|
||||||
height: '1em',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export const StyledSignInCardPillTextCotainer = styled('div')(() => {
|
|
||||||
return {
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export const StyledSignInCardPillTextSecondary = styled('div')(() => {
|
|
||||||
return {
|
|
||||||
fontSize: '12px',
|
|
||||||
color: 'var(--affine-text-secondary-color)',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export const StyledSignInCardPillTextPrimary = styled('div')(() => {
|
|
||||||
return {
|
|
||||||
fontSize: 'var(--affine-font-base)',
|
|
||||||
fontWeight: 600,
|
|
||||||
lineHeight: '24px',
|
|
||||||
maxWidth: '200px',
|
|
||||||
textAlign: 'left',
|
|
||||||
...textEllipsis(1),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export const StyledModalHeaderLeft = styled('div')(() => {
|
|
||||||
return { ...displayFlex('flex-start', 'center') };
|
|
||||||
});
|
|
||||||
export const StyledModalTitle = styled('div')(() => {
|
|
||||||
return {
|
|
||||||
fontWeight: 600,
|
|
||||||
fontSize: 'var(--affine-font-h6)',
|
|
||||||
color: 'var(--affine-text-primary-color)',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export const StyledHelperContainer = styled('div')(() => {
|
|
||||||
return {
|
|
||||||
color: 'var(--affine-icon-color)',
|
|
||||||
marginLeft: '15px',
|
|
||||||
fontWeight: 400,
|
|
||||||
fontSize: 'var(--affine-font-h6)',
|
|
||||||
...displayFlex('center', 'center'),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export const StyledModalContent = styled('div')({
|
|
||||||
...displayFlex('space-between', 'flex-start', 'flex-start'),
|
|
||||||
flexWrap: 'wrap',
|
|
||||||
flexDirection: 'column',
|
|
||||||
width: '100%',
|
|
||||||
gap: '4px',
|
|
||||||
});
|
|
||||||
|
|
||||||
export const StyledModalFooterContent = styled('div')({
|
|
||||||
...displayFlex('space-between', 'flex-start', 'flex-start'),
|
|
||||||
flexWrap: 'wrap',
|
|
||||||
flexDirection: 'column',
|
|
||||||
width: '100%',
|
|
||||||
marginTop: '12px',
|
|
||||||
backgroundColor: 'var(--affine-background-overlay-panel-color)',
|
|
||||||
});
|
|
||||||
|
|
||||||
export const StyledModalHeaderContent = styled('div')({
|
|
||||||
...displayFlex('space-between', 'flex-start', 'flex-start'),
|
|
||||||
flexWrap: 'wrap',
|
|
||||||
flexDirection: 'column',
|
|
||||||
width: '100%',
|
|
||||||
backgroundColor: 'var(--affine-background-overlay-panel-color)',
|
|
||||||
});
|
|
||||||
|
|
||||||
export const StyledOperationWrapper = styled('div')(() => {
|
|
||||||
return {
|
|
||||||
...displayFlex('flex-end', 'center'),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export const StyleWorkspaceAdd = styled('div')(() => {
|
|
||||||
return {
|
|
||||||
width: '58px',
|
|
||||||
height: '58px',
|
|
||||||
borderRadius: '100%',
|
|
||||||
background: 'var(--affine-background-overlay-panel-color)',
|
|
||||||
border: '1.5px dashed #f4f5fa',
|
|
||||||
transition: 'background .2s',
|
|
||||||
fontSize: '24px',
|
|
||||||
...displayFlex('center', 'center'),
|
|
||||||
borderColor: 'var(--affine-white)',
|
|
||||||
color: 'var(--affine-background-overlay-panel-color)',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
export const StyledModalHeader = styled('div')(() => {
|
|
||||||
return {
|
|
||||||
width: '100%',
|
|
||||||
left: 0,
|
|
||||||
top: 0,
|
|
||||||
borderRadius: '24px 24px 0 0',
|
|
||||||
padding: '0px 14px',
|
|
||||||
...displayFlex('space-between', 'center'),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export const StyledModalBody = styled('div')(() => {
|
|
||||||
return {
|
|
||||||
display: 'inline-flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
alignItems: 'flex-start',
|
|
||||||
gap: '4px',
|
|
||||||
flex: 1,
|
|
||||||
overflowY: 'auto',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export const StyledWorkspaceFlavourTitle = styled('div')(() => {
|
|
||||||
return {
|
|
||||||
fontSize: 'var(--affine-font-xs)',
|
|
||||||
color: 'var(--affine-text-secondary-color)',
|
|
||||||
marginBottom: '4px',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
export const StyledItem = styled('button')<{
|
|
||||||
active?: boolean;
|
|
||||||
}>(({ active = false }) => {
|
|
||||||
return {
|
|
||||||
height: 'auto',
|
|
||||||
padding: '8px 12px',
|
|
||||||
width: '100%',
|
|
||||||
borderRadius: '5px',
|
|
||||||
fontSize: 'var(--affine-font-sm)',
|
|
||||||
...displayFlex('flex-start', 'center'),
|
|
||||||
cursor: 'pointer',
|
|
||||||
position: 'relative',
|
|
||||||
backgroundColor: 'transparent',
|
|
||||||
color: 'var(--affine-text-primary-color)',
|
|
||||||
svg: {
|
|
||||||
color: 'var(--affine-icon-color)',
|
|
||||||
},
|
|
||||||
|
|
||||||
':hover': {
|
|
||||||
backgroundColor: 'var(--affine-hover-color)',
|
|
||||||
},
|
|
||||||
|
|
||||||
...(active
|
|
||||||
? {
|
|
||||||
backgroundColor: 'var(--affine-hover-color)',
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
};
|
|
||||||
});
|
|
@ -0,0 +1,18 @@
|
|||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
export const userAccountContainer = style({
|
||||||
|
display: 'flex',
|
||||||
|
padding: '4px 0px 4px 12px',
|
||||||
|
gap: '12px',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
});
|
||||||
|
export const userEmail = style({
|
||||||
|
fontSize: 'var(--affine-font-sm)',
|
||||||
|
fontWeight: 400,
|
||||||
|
lineHeight: '22px',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
overflow: 'hidden',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
maxWidth: 'calc(100% - 36px)',
|
||||||
|
});
|
@ -0,0 +1,96 @@
|
|||||||
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
|
import {
|
||||||
|
AccountIcon,
|
||||||
|
MoreHorizontalIcon,
|
||||||
|
SignOutIcon,
|
||||||
|
} from '@blocksuite/icons';
|
||||||
|
import { IconButton } from '@toeverything/components/button';
|
||||||
|
import { Divider } from '@toeverything/components/divider';
|
||||||
|
import { Menu, MenuIcon, MenuItem } from '@toeverything/components/menu';
|
||||||
|
import { useSetAtom } from 'jotai';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
import { openSettingModalAtom } from '../../../../../atoms';
|
||||||
|
import { signOutCloud } from '../../../../../utils/cloud-utils';
|
||||||
|
import { useNavigateHelper } from '../.././../../../hooks/use-navigate-helper';
|
||||||
|
import * as styles from './index.css';
|
||||||
|
|
||||||
|
const AccountMenu = ({ onEventEnd }: { onEventEnd?: () => void }) => {
|
||||||
|
const setSettingModalAtom = useSetAtom(openSettingModalAtom);
|
||||||
|
|
||||||
|
const { jumpToIndex } = useNavigateHelper();
|
||||||
|
|
||||||
|
const onOpenAccountSetting = useCallback(() => {
|
||||||
|
setSettingModalAtom(prev => ({
|
||||||
|
...prev,
|
||||||
|
open: true,
|
||||||
|
activeTab: 'account',
|
||||||
|
}));
|
||||||
|
}, [setSettingModalAtom]);
|
||||||
|
|
||||||
|
const onSignOut = useCallback(async () => {
|
||||||
|
signOutCloud()
|
||||||
|
.then(() => {
|
||||||
|
jumpToIndex();
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
|
onEventEnd?.();
|
||||||
|
}, [onEventEnd, jumpToIndex]);
|
||||||
|
|
||||||
|
const t = useAFFiNEI18N();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<MenuItem
|
||||||
|
preFix={
|
||||||
|
<MenuIcon>
|
||||||
|
<AccountIcon />
|
||||||
|
</MenuIcon>
|
||||||
|
}
|
||||||
|
data-testid="editor-option-menu-import"
|
||||||
|
onClick={onOpenAccountSetting}
|
||||||
|
>
|
||||||
|
{t['com.affine.workspace.cloud.account.settings']()}
|
||||||
|
</MenuItem>
|
||||||
|
<Divider />
|
||||||
|
<MenuItem
|
||||||
|
preFix={
|
||||||
|
<MenuIcon>
|
||||||
|
<SignOutIcon />
|
||||||
|
</MenuIcon>
|
||||||
|
}
|
||||||
|
data-testid="editor-option-menu-import"
|
||||||
|
onClick={onSignOut}
|
||||||
|
>
|
||||||
|
{t['com.affine.workspace.cloud.account.logout']()}
|
||||||
|
</MenuItem>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const UserAccountItem = ({
|
||||||
|
email,
|
||||||
|
onEventEnd,
|
||||||
|
}: {
|
||||||
|
email: string;
|
||||||
|
onEventEnd?: () => void;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className={styles.userAccountContainer}>
|
||||||
|
<div className={styles.userEmail}>{email}</div>
|
||||||
|
<Menu
|
||||||
|
items={<AccountMenu onEventEnd={onEventEnd} />}
|
||||||
|
contentOptions={{
|
||||||
|
side: 'right',
|
||||||
|
sideOffset: 12,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
data-testid="more-button"
|
||||||
|
icon={<MoreHorizontalIcon />}
|
||||||
|
type="plain"
|
||||||
|
/>
|
||||||
|
</Menu>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,29 @@
|
|||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
export const workspaceListsWrapper = style({
|
||||||
|
display: 'flex',
|
||||||
|
width: '100%',
|
||||||
|
flexDirection: 'column',
|
||||||
|
maxHeight: 'calc(100vh - 300px)',
|
||||||
|
});
|
||||||
|
export const workspaceListWrapper = style({
|
||||||
|
display: 'flex',
|
||||||
|
width: '100%',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: '4px',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const workspaceType = style({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
padding: '0px 12px',
|
||||||
|
fontSize: 'var(--affine-font-xs)',
|
||||||
|
lineHeight: '20px',
|
||||||
|
color: 'var(--affine-text-secondary-color)',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const scrollbar = style({
|
||||||
|
transform: 'translateX(10px)',
|
||||||
|
width: '4px',
|
||||||
|
});
|
@ -0,0 +1,233 @@
|
|||||||
|
import { ScrollableContainer } from '@affine/component';
|
||||||
|
import { WorkspaceList } from '@affine/component/workspace-list';
|
||||||
|
import type {
|
||||||
|
AffineCloudWorkspace,
|
||||||
|
LocalWorkspace,
|
||||||
|
} from '@affine/env/workspace';
|
||||||
|
import { WorkspaceFlavour, WorkspaceSubPath } from '@affine/env/workspace';
|
||||||
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
|
import type { RootWorkspaceMetadata } from '@affine/workspace/atom';
|
||||||
|
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
||||||
|
import type { DragEndEvent } from '@dnd-kit/core';
|
||||||
|
import { arrayMove } from '@dnd-kit/sortable';
|
||||||
|
import { Divider } from '@toeverything/components/divider';
|
||||||
|
import {
|
||||||
|
currentPageIdAtom,
|
||||||
|
currentWorkspaceIdAtom,
|
||||||
|
} from '@toeverything/infra/atom';
|
||||||
|
import { useAtom, useSetAtom } from 'jotai';
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
||||||
|
import { useSession } from 'next-auth/react';
|
||||||
|
import { startTransition, useCallback, useMemo, useTransition } from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
openCreateWorkspaceModalAtom,
|
||||||
|
openSettingModalAtom,
|
||||||
|
} from '../../../../../atoms';
|
||||||
|
import type { AllWorkspace } from '../../../../../shared';
|
||||||
|
import { useIsWorkspaceOwner } from '../.././../../../hooks/affine/use-is-workspace-owner';
|
||||||
|
import { useNavigateHelper } from '../.././../../../hooks/use-navigate-helper';
|
||||||
|
import * as styles from './index.css';
|
||||||
|
interface WorkspaceModalProps {
|
||||||
|
disabled?: boolean;
|
||||||
|
workspaces: (AffineCloudWorkspace | LocalWorkspace)[];
|
||||||
|
currentWorkspaceId: AllWorkspace['id'] | null;
|
||||||
|
onClickWorkspace: (workspace: RootWorkspaceMetadata['id']) => void;
|
||||||
|
onClickWorkspaceSetting: (workspace: RootWorkspaceMetadata['id']) => void;
|
||||||
|
onNewWorkspace: () => void;
|
||||||
|
onAddWorkspace: () => void;
|
||||||
|
onDragEnd: (event: DragEndEvent) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CloudWorkSpaceList = ({
|
||||||
|
disabled,
|
||||||
|
workspaces,
|
||||||
|
onClickWorkspace,
|
||||||
|
onClickWorkspaceSetting,
|
||||||
|
currentWorkspaceId,
|
||||||
|
onDragEnd,
|
||||||
|
}: WorkspaceModalProps) => {
|
||||||
|
const t = useAFFiNEI18N();
|
||||||
|
if (workspaces.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className={styles.workspaceListWrapper}>
|
||||||
|
<div className={styles.workspaceType}>
|
||||||
|
{t['com.affine.workspaceList.workspaceListType.cloud']()}
|
||||||
|
</div>
|
||||||
|
<WorkspaceList
|
||||||
|
disabled={disabled}
|
||||||
|
items={workspaces}
|
||||||
|
currentWorkspaceId={currentWorkspaceId}
|
||||||
|
onClick={onClickWorkspace}
|
||||||
|
onSettingClick={onClickWorkspaceSetting}
|
||||||
|
onDragEnd={onDragEnd}
|
||||||
|
useIsWorkspaceOwner={useIsWorkspaceOwner}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const LocalWorkspaces = ({
|
||||||
|
disabled,
|
||||||
|
workspaces,
|
||||||
|
onClickWorkspace,
|
||||||
|
onClickWorkspaceSetting,
|
||||||
|
currentWorkspaceId,
|
||||||
|
onDragEnd,
|
||||||
|
}: WorkspaceModalProps) => {
|
||||||
|
const t = useAFFiNEI18N();
|
||||||
|
if (workspaces.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className={styles.workspaceListWrapper}>
|
||||||
|
<div className={styles.workspaceType}>
|
||||||
|
{t['com.affine.workspaceList.workspaceListType.local']()}
|
||||||
|
</div>
|
||||||
|
<WorkspaceList
|
||||||
|
disabled={disabled}
|
||||||
|
items={workspaces}
|
||||||
|
currentWorkspaceId={currentWorkspaceId}
|
||||||
|
onClick={onClickWorkspace}
|
||||||
|
onSettingClick={onClickWorkspaceSetting}
|
||||||
|
onDragEnd={onDragEnd}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AFFiNEWorkspaceList = ({
|
||||||
|
workspaces,
|
||||||
|
onEventEnd,
|
||||||
|
}: {
|
||||||
|
workspaces: RootWorkspaceMetadata[];
|
||||||
|
onEventEnd?: () => void;
|
||||||
|
}) => {
|
||||||
|
const setOpenCreateWorkspaceModal = useSetAtom(openCreateWorkspaceModalAtom);
|
||||||
|
|
||||||
|
const { jumpToSubPath } = useNavigateHelper();
|
||||||
|
|
||||||
|
const setWorkspaces = useSetAtom(rootWorkspacesMetadataAtom);
|
||||||
|
|
||||||
|
const [currentWorkspaceId, setCurrentWorkspaceId] = useAtom(
|
||||||
|
currentWorkspaceIdAtom
|
||||||
|
);
|
||||||
|
|
||||||
|
const setCurrentPageId = useSetAtom(currentPageIdAtom);
|
||||||
|
|
||||||
|
const [, startCloseTransition] = useTransition();
|
||||||
|
|
||||||
|
const setOpenSettingModalAtom = useSetAtom(openSettingModalAtom);
|
||||||
|
|
||||||
|
// TODO: AFFiNE Cloud support
|
||||||
|
const { status } = useSession();
|
||||||
|
|
||||||
|
const isAuthenticated = useMemo(() => status === 'authenticated', [status]);
|
||||||
|
|
||||||
|
const cloudWorkspaces = useMemo(
|
||||||
|
() =>
|
||||||
|
workspaces.filter(
|
||||||
|
({ flavour }) => flavour === WorkspaceFlavour.AFFINE_CLOUD
|
||||||
|
) as (AffineCloudWorkspace | LocalWorkspace)[],
|
||||||
|
[workspaces]
|
||||||
|
);
|
||||||
|
|
||||||
|
const localWorkspaces = useMemo(
|
||||||
|
() =>
|
||||||
|
workspaces.filter(
|
||||||
|
({ flavour }) => flavour === WorkspaceFlavour.LOCAL
|
||||||
|
) as (AffineCloudWorkspace | LocalWorkspace)[],
|
||||||
|
[workspaces]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onClickWorkspaceSetting = useCallback(
|
||||||
|
(workspaceId: string) => {
|
||||||
|
setOpenSettingModalAtom({
|
||||||
|
open: true,
|
||||||
|
activeTab: 'workspace',
|
||||||
|
workspaceId,
|
||||||
|
});
|
||||||
|
onEventEnd?.();
|
||||||
|
},
|
||||||
|
[onEventEnd, setOpenSettingModalAtom]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onMoveWorkspace = useCallback(
|
||||||
|
(activeId: string, overId: string) => {
|
||||||
|
const oldIndex = workspaces.findIndex(w => w.id === activeId);
|
||||||
|
|
||||||
|
const newIndex = workspaces.findIndex(w => w.id === overId);
|
||||||
|
startTransition(() => {
|
||||||
|
setWorkspaces(workspaces => arrayMove(workspaces, oldIndex, newIndex));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[setWorkspaces, workspaces]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onClickWorkspace = useCallback(
|
||||||
|
(workspaceId: string) => {
|
||||||
|
startCloseTransition(() => {
|
||||||
|
setCurrentWorkspaceId(workspaceId);
|
||||||
|
setCurrentPageId(null);
|
||||||
|
jumpToSubPath(workspaceId, WorkspaceSubPath.ALL);
|
||||||
|
});
|
||||||
|
onEventEnd?.();
|
||||||
|
},
|
||||||
|
[jumpToSubPath, onEventEnd, setCurrentPageId, setCurrentWorkspaceId]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onDragEnd = useCallback(
|
||||||
|
(event: DragEndEvent) => {
|
||||||
|
const { active, over } = event;
|
||||||
|
if (active.id !== over?.id) {
|
||||||
|
onMoveWorkspace(active.id as string, over?.id as string);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[onMoveWorkspace]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onNewWorkspace = useCallback(() => {
|
||||||
|
setOpenCreateWorkspaceModal('new');
|
||||||
|
onEventEnd?.();
|
||||||
|
}, [onEventEnd, setOpenCreateWorkspaceModal]);
|
||||||
|
|
||||||
|
const onAddWorkspace = useCallback(async () => {
|
||||||
|
setOpenCreateWorkspaceModal('add');
|
||||||
|
onEventEnd?.();
|
||||||
|
}, [onEventEnd, setOpenCreateWorkspaceModal]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ScrollableContainer
|
||||||
|
className={styles.workspaceListsWrapper}
|
||||||
|
scrollBarClassName={styles.scrollbar}
|
||||||
|
>
|
||||||
|
{isAuthenticated ? (
|
||||||
|
<div>
|
||||||
|
<CloudWorkSpaceList
|
||||||
|
workspaces={cloudWorkspaces}
|
||||||
|
onClickWorkspace={onClickWorkspace}
|
||||||
|
onClickWorkspaceSetting={onClickWorkspaceSetting}
|
||||||
|
onNewWorkspace={onNewWorkspace}
|
||||||
|
onAddWorkspace={onAddWorkspace}
|
||||||
|
currentWorkspaceId={currentWorkspaceId}
|
||||||
|
onDragEnd={onDragEnd}
|
||||||
|
/>
|
||||||
|
{localWorkspaces.length > 0 && cloudWorkspaces.length > 0 ? (
|
||||||
|
<Divider size="thinner" />
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
<LocalWorkspaces
|
||||||
|
workspaces={localWorkspaces}
|
||||||
|
onClickWorkspace={onClickWorkspace}
|
||||||
|
onClickWorkspaceSetting={onClickWorkspaceSetting}
|
||||||
|
onNewWorkspace={onNewWorkspace}
|
||||||
|
onAddWorkspace={onAddWorkspace}
|
||||||
|
currentWorkspaceId={currentWorkspaceId}
|
||||||
|
onDragEnd={onDragEnd}
|
||||||
|
/>
|
||||||
|
</ScrollableContainer>
|
||||||
|
);
|
||||||
|
};
|
@ -30,6 +30,7 @@ import {
|
|||||||
} from './styles';
|
} from './styles';
|
||||||
|
|
||||||
const hoverAtom = atom(false);
|
const hoverAtom = atom(false);
|
||||||
|
|
||||||
// FIXME:
|
// FIXME:
|
||||||
// 1. Remove mui style
|
// 1. Remove mui style
|
||||||
// 2. Refactor the code to improve readability
|
// 2. Refactor the code to improve readability
|
||||||
@ -41,6 +42,7 @@ const CloudWorkspaceStatus = () => {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const SyncingWorkspaceStatus = () => {
|
const SyncingWorkspaceStatus = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -49,6 +51,7 @@ const SyncingWorkspaceStatus = () => {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const UnSyncWorkspaceStatus = () => {
|
const UnSyncWorkspaceStatus = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -82,11 +85,14 @@ const WorkspaceStatus = ({
|
|||||||
currentWorkspace: AllWorkspace;
|
currentWorkspace: AllWorkspace;
|
||||||
}) => {
|
}) => {
|
||||||
const isOnline = useSystemOnline();
|
const isOnline = useSystemOnline();
|
||||||
|
|
||||||
// todo: finish display sync status
|
// todo: finish display sync status
|
||||||
const [forceSyncStatus, startForceSync] = useDatasourceSync(
|
const [forceSyncStatus, startForceSync] = useDatasourceSync(
|
||||||
currentWorkspace.blockSuiteWorkspace
|
currentWorkspace.blockSuiteWorkspace
|
||||||
);
|
);
|
||||||
|
|
||||||
const setIsHovered = useSetAtom(hoverAtom);
|
const setIsHovered = useSetAtom(hoverAtom);
|
||||||
|
|
||||||
const content = useMemo(() => {
|
const content = useMemo(() => {
|
||||||
if (currentWorkspace.flavour === WorkspaceFlavour.LOCAL) {
|
if (currentWorkspace.flavour === WorkspaceFlavour.LOCAL) {
|
||||||
return 'Saved locally';
|
return 'Saved locally';
|
||||||
@ -103,6 +109,7 @@ const WorkspaceStatus = ({
|
|||||||
return 'Sync with AFFiNE Cloud';
|
return 'Sync with AFFiNE Cloud';
|
||||||
}
|
}
|
||||||
}, [currentWorkspace.flavour, forceSyncStatus.type, isOnline]);
|
}, [currentWorkspace.flavour, forceSyncStatus.type, isOnline]);
|
||||||
|
|
||||||
const CloudWorkspaceSyncStatus = useCallback(() => {
|
const CloudWorkspaceSyncStatus = useCallback(() => {
|
||||||
if (forceSyncStatus.type === 'syncing') {
|
if (forceSyncStatus.type === 'syncing') {
|
||||||
return SyncingWorkspaceStatus();
|
return SyncingWorkspaceStatus();
|
||||||
@ -160,6 +167,7 @@ export const WorkspaceCard = forwardRef<
|
|||||||
const [name] = useBlockSuiteWorkspaceName(
|
const [name] = useBlockSuiteWorkspaceName(
|
||||||
currentWorkspace.blockSuiteWorkspace
|
currentWorkspace.blockSuiteWorkspace
|
||||||
);
|
);
|
||||||
|
|
||||||
const [workspaceAvatar] = useBlockSuiteWorkspaceAvatarUrl(
|
const [workspaceAvatar] = useBlockSuiteWorkspaceAvatarUrl(
|
||||||
currentWorkspace.blockSuiteWorkspace
|
currentWorkspace.blockSuiteWorkspace
|
||||||
);
|
);
|
||||||
|
@ -6,6 +6,7 @@ export const StyledSelectorContainer = styled('div')({
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
padding: '0 6px',
|
padding: '0 6px',
|
||||||
borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
|
outline: 'none',
|
||||||
color: 'var(--affine-text-primary-color)',
|
color: 'var(--affine-text-primary-color)',
|
||||||
':hover': {
|
':hover': {
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
|
@ -19,17 +19,10 @@ import {
|
|||||||
} from '@blocksuite/icons';
|
} from '@blocksuite/icons';
|
||||||
import type { Page } from '@blocksuite/store';
|
import type { Page } from '@blocksuite/store';
|
||||||
import { useDroppable } from '@dnd-kit/core';
|
import { useDroppable } from '@dnd-kit/core';
|
||||||
import { Popover } from '@toeverything/components/popover';
|
import { Menu } from '@toeverything/components/menu';
|
||||||
import { useAtom } from 'jotai';
|
import { useAtom } from 'jotai';
|
||||||
import type { HTMLAttributes, ReactElement } from 'react';
|
import type { HTMLAttributes, ReactElement } from 'react';
|
||||||
import {
|
import { forwardRef, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
forwardRef,
|
|
||||||
Suspense,
|
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useMemo,
|
|
||||||
useState,
|
|
||||||
} from 'react';
|
|
||||||
|
|
||||||
import { useHistoryAtom } from '../../atoms/history';
|
import { useHistoryAtom } from '../../atoms/history';
|
||||||
import { useAppSetting } from '../../atoms/settings';
|
import { useAppSetting } from '../../atoms/settings';
|
||||||
@ -175,18 +168,21 @@ export const RootAppSidebar = ({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<SidebarContainer>
|
<SidebarContainer>
|
||||||
<Popover
|
<Menu
|
||||||
open={openUserWorkspaceList}
|
rootOptions={{
|
||||||
content={
|
open: openUserWorkspaceList,
|
||||||
<Suspense>
|
}}
|
||||||
<UserWithWorkspaceList onEventEnd={closeUserWorkspaceList} />
|
items={
|
||||||
</Suspense>
|
<UserWithWorkspaceList onEventEnd={closeUserWorkspaceList} />
|
||||||
}
|
}
|
||||||
contentOptions={{
|
contentOptions={{
|
||||||
// hide trigger
|
// hide trigger
|
||||||
sideOffset: -58,
|
sideOffset: -58,
|
||||||
onInteractOutside: closeUserWorkspaceList,
|
onInteractOutside: closeUserWorkspaceList,
|
||||||
onEscapeKeyDown: closeUserWorkspaceList,
|
onEscapeKeyDown: closeUserWorkspaceList,
|
||||||
|
style: {
|
||||||
|
width: '300px',
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<WorkspaceCard
|
<WorkspaceCard
|
||||||
@ -195,7 +191,7 @@ export const RootAppSidebar = ({
|
|||||||
setOpenUserWorkspaceList(true);
|
setOpenUserWorkspaceList(true);
|
||||||
}, [])}
|
}, [])}
|
||||||
/>
|
/>
|
||||||
</Popover>
|
</Menu>
|
||||||
<QuickSearchInput
|
<QuickSearchInput
|
||||||
data-testid="slider-bar-quick-search-button"
|
data-testid="slider-bar-quick-search-button"
|
||||||
onClick={onOpenQuickSearchModal}
|
onClick={onOpenQuickSearchModal}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { DebugLogger } from '@affine/debug';
|
import { DebugLogger } from '@affine/debug';
|
||||||
import { DEFAULT_HELLO_WORLD_PAGE_ID_SUFFIX } from '@affine/env/constant';
|
import { DEFAULT_HELLO_WORLD_PAGE_ID_SUFFIX } from '@affine/env/constant';
|
||||||
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
||||||
|
import { Menu } from '@toeverything/components/menu';
|
||||||
import { getWorkspace } from '@toeverything/infra/__internal__/workspace';
|
import { getWorkspace } from '@toeverything/infra/__internal__/workspace';
|
||||||
import { getCurrentStore } from '@toeverything/infra/atom';
|
import { getCurrentStore } from '@toeverything/infra/atom';
|
||||||
import { lazy } from 'react';
|
import { lazy } from 'react';
|
||||||
@ -61,15 +62,29 @@ export const Component = () => {
|
|||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
width: 300,
|
position: 'fixed',
|
||||||
margin: '80px auto',
|
left: '50%',
|
||||||
borderRadius: '8px',
|
top: '50%',
|
||||||
boxShadow: 'var(--affine-shadow-2)',
|
|
||||||
backgroundColor: 'var(--affine-background-overlay-panel-color)',
|
|
||||||
padding: '16px 12px',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<UserWithWorkspaceList />
|
<Menu
|
||||||
|
rootOptions={{
|
||||||
|
open: true,
|
||||||
|
}}
|
||||||
|
items={<UserWithWorkspaceList />}
|
||||||
|
contentOptions={{
|
||||||
|
style: {
|
||||||
|
width: 300,
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
borderRadius: '8px',
|
||||||
|
boxShadow: 'var(--affine-shadow-2)',
|
||||||
|
backgroundColor: 'var(--affine-background-overlay-panel-color)',
|
||||||
|
padding: '16px 12px',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div></div>
|
||||||
|
</Menu>
|
||||||
</div>
|
</div>
|
||||||
<AllWorkspaceModals />
|
<AllWorkspaceModals />
|
||||||
</>
|
</>
|
||||||
|
@ -37,6 +37,7 @@ export const AffineWorkspaceCard = () => {
|
|||||||
onClick={() => {}}
|
onClick={() => {}}
|
||||||
onSettingClick={() => {}}
|
onSettingClick={() => {}}
|
||||||
currentWorkspaceId={null}
|
currentWorkspaceId={null}
|
||||||
|
isOwner={true}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
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 { RootWorkspaceMetadata } from '@affine/workspace/atom';
|
import type { RootWorkspaceMetadata } from '@affine/workspace/atom';
|
||||||
import { SettingsIcon } from '@blocksuite/icons';
|
import { CollaborationIcon, SettingsIcon } from '@blocksuite/icons';
|
||||||
|
import { Skeleton } from '@mui/material';
|
||||||
import { Avatar } from '@toeverything/components/avatar';
|
import { Avatar } from '@toeverything/components/avatar';
|
||||||
|
import { Divider } from '@toeverything/components/divider';
|
||||||
|
import { Tooltip } from '@toeverything/components/tooltip';
|
||||||
import { useBlockSuiteWorkspaceAvatarUrl } from '@toeverything/hooks/use-block-suite-workspace-avatar-url';
|
import { useBlockSuiteWorkspaceAvatarUrl } from '@toeverything/hooks/use-block-suite-workspace-avatar-url';
|
||||||
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
|
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
|
||||||
import { useStaticBlockSuiteWorkspace } from '@toeverything/infra/__internal__/react';
|
import { useStaticBlockSuiteWorkspace } from '@toeverything/infra/__internal__/react';
|
||||||
@ -10,46 +13,56 @@ import { useCallback } from 'react';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
StyledCard,
|
StyledCard,
|
||||||
|
StyledIconContainer,
|
||||||
StyledSettingLink,
|
StyledSettingLink,
|
||||||
StyledWorkspaceInfo,
|
StyledWorkspaceInfo,
|
||||||
StyledWorkspaceTitle,
|
StyledWorkspaceTitle,
|
||||||
StyledWorkspaceTitleArea,
|
StyledWorkspaceTitleArea,
|
||||||
|
StyledWorkspaceType,
|
||||||
|
StyledWorkspaceTypeEllipse,
|
||||||
|
StyledWorkspaceTypeText,
|
||||||
} from './styles';
|
} from './styles';
|
||||||
|
|
||||||
export interface WorkspaceTypeProps {
|
export interface WorkspaceTypeProps {
|
||||||
flavour: WorkspaceFlavour;
|
flavour: WorkspaceFlavour;
|
||||||
|
isOwner: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const WorkspaceType = ({ flavour }: WorkspaceTypeProps) => {
|
const WorkspaceType = ({ flavour, isOwner }: WorkspaceTypeProps) => {
|
||||||
const t = useAFFiNEI18N();
|
const t = useAFFiNEI18N();
|
||||||
// fixme: cloud regression
|
|
||||||
const isOwner = true;
|
|
||||||
|
|
||||||
if (flavour === WorkspaceFlavour.LOCAL) {
|
if (flavour === WorkspaceFlavour.LOCAL) {
|
||||||
return (
|
return (
|
||||||
<p
|
<StyledWorkspaceType>
|
||||||
style={{ fontSize: '10px' }}
|
<StyledWorkspaceTypeEllipse />
|
||||||
title={t['com.affine.workspaceType.local']()}
|
<StyledWorkspaceTypeText>{t['Local']()}</StyledWorkspaceTypeText>
|
||||||
>
|
</StyledWorkspaceType>
|
||||||
<span>{t['com.affine.workspaceType.local']()}</span>
|
|
||||||
</p>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return isOwner ? (
|
return isOwner ? (
|
||||||
<p
|
<StyledWorkspaceType>
|
||||||
style={{ fontSize: '10px' }}
|
<StyledWorkspaceTypeEllipse cloud={true} />
|
||||||
title={t['com.affine.workspaceType.cloud']()}
|
<StyledWorkspaceTypeText>
|
||||||
>
|
{t['com.affine.brand.affineCloud']()}
|
||||||
<span>{t['com.affine.workspaceType.cloud']()}</span>
|
</StyledWorkspaceTypeText>
|
||||||
</p>
|
</StyledWorkspaceType>
|
||||||
) : (
|
) : (
|
||||||
<p
|
<StyledWorkspaceType>
|
||||||
style={{ fontSize: '10px' }}
|
<StyledWorkspaceTypeEllipse cloud={true} />
|
||||||
title={t['com.affine.workspaceType.joined']()}
|
<StyledWorkspaceTypeText>
|
||||||
>
|
{t['com.affine.brand.affineCloud']()}
|
||||||
<span>{t['com.affine.workspaceType.joined']()}</span>
|
</StyledWorkspaceTypeText>
|
||||||
</p>
|
<Divider
|
||||||
|
orientation="vertical"
|
||||||
|
size="thinner"
|
||||||
|
style={{ margin: '0px 8px', height: '7px' }}
|
||||||
|
/>
|
||||||
|
<Tooltip content={t['com.affine.workspaceType.joined']()}>
|
||||||
|
<StyledIconContainer>
|
||||||
|
<CollaborationIcon />
|
||||||
|
</StyledIconContainer>
|
||||||
|
</Tooltip>
|
||||||
|
</StyledWorkspaceType>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -58,19 +71,35 @@ export interface WorkspaceCardProps {
|
|||||||
meta: RootWorkspaceMetadata;
|
meta: RootWorkspaceMetadata;
|
||||||
onClick: (workspaceId: string) => void;
|
onClick: (workspaceId: string) => void;
|
||||||
onSettingClick: (workspaceId: string) => void;
|
onSettingClick: (workspaceId: string) => void;
|
||||||
|
isOwner?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const WorkspaceCardSkeleton = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<StyledCard data-testid="workspace-card">
|
||||||
|
<Skeleton variant="circular" width={28} height={28} />
|
||||||
|
<Skeleton
|
||||||
|
variant="rectangular"
|
||||||
|
height={43}
|
||||||
|
width={220}
|
||||||
|
style={{ marginLeft: '12px' }}
|
||||||
|
/>
|
||||||
|
</StyledCard>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const WorkspaceCard = ({
|
export const WorkspaceCard = ({
|
||||||
onClick,
|
onClick,
|
||||||
onSettingClick,
|
onSettingClick,
|
||||||
currentWorkspaceId,
|
currentWorkspaceId,
|
||||||
meta,
|
meta,
|
||||||
|
isOwner = true,
|
||||||
}: WorkspaceCardProps) => {
|
}: WorkspaceCardProps) => {
|
||||||
// const t = useAFFiNEI18N();
|
|
||||||
const workspace = useStaticBlockSuiteWorkspace(meta.id);
|
const workspace = useStaticBlockSuiteWorkspace(meta.id);
|
||||||
const [name] = useBlockSuiteWorkspaceName(workspace);
|
const [name] = useBlockSuiteWorkspaceName(workspace);
|
||||||
const [workspaceAvatar] = useBlockSuiteWorkspaceAvatarUrl(workspace);
|
const [workspaceAvatar] = useBlockSuiteWorkspaceAvatarUrl(workspace);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledCard
|
<StyledCard
|
||||||
data-testid="workspace-card"
|
data-testid="workspace-card"
|
||||||
@ -85,6 +114,7 @@ export const WorkspaceCard = ({
|
|||||||
<StyledWorkspaceTitle>{name}</StyledWorkspaceTitle>
|
<StyledWorkspaceTitle>{name}</StyledWorkspaceTitle>
|
||||||
|
|
||||||
<StyledSettingLink
|
<StyledSettingLink
|
||||||
|
size="small"
|
||||||
className="setting-entry"
|
className="setting-entry"
|
||||||
onClick={e => {
|
onClick={e => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@ -92,17 +122,10 @@ export const WorkspaceCard = ({
|
|||||||
}}
|
}}
|
||||||
withoutHoverStyle={true}
|
withoutHoverStyle={true}
|
||||||
>
|
>
|
||||||
<SettingsIcon style={{ margin: '0px' }} />
|
<SettingsIcon />
|
||||||
</StyledSettingLink>
|
</StyledSettingLink>
|
||||||
</StyledWorkspaceTitleArea>
|
</StyledWorkspaceTitleArea>
|
||||||
{/* {meta.flavour === WorkspaceFlavour.LOCAL && (
|
<WorkspaceType isOwner={isOwner} flavour={meta.flavour} />
|
||||||
<p title={t['com.affine.workspaceType.offline']()}>
|
|
||||||
<LocalDataIcon />
|
|
||||||
<WorkspaceType flavour={meta.flavour} />
|
|
||||||
</p>
|
|
||||||
|
|
||||||
)} */}
|
|
||||||
<WorkspaceType flavour={meta.flavour} />
|
|
||||||
</StyledWorkspaceInfo>
|
</StyledWorkspaceInfo>
|
||||||
</StyledCard>
|
</StyledCard>
|
||||||
);
|
);
|
||||||
|
@ -5,30 +5,16 @@ import { displayFlex, styled, textEllipsis } from '../../../styles';
|
|||||||
export const StyledWorkspaceInfo = styled('div')(() => {
|
export const StyledWorkspaceInfo = styled('div')(() => {
|
||||||
return {
|
return {
|
||||||
marginLeft: '12px',
|
marginLeft: '12px',
|
||||||
width: '202px',
|
width: '100%',
|
||||||
p: {
|
|
||||||
height: '20px',
|
|
||||||
fontSize: 'var(--affine-font-sm)',
|
|
||||||
...displayFlex('flex-start', 'center'),
|
|
||||||
},
|
|
||||||
svg: {
|
|
||||||
marginRight: '10px',
|
|
||||||
fontSize: '16px',
|
|
||||||
flexShrink: 0,
|
|
||||||
},
|
|
||||||
span: {
|
|
||||||
flexGrow: 1,
|
|
||||||
...textEllipsis(1),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
export const StyledWorkspaceTitle = styled('div')(() => {
|
export const StyledWorkspaceTitle = styled('div')(() => {
|
||||||
return {
|
return {
|
||||||
fontSize: 'var(--affine-font-base)',
|
fontSize: 'var(--affine-font-sm)',
|
||||||
fontWeight: 600,
|
fontWeight: 700,
|
||||||
lineHeight: '24px',
|
lineHeight: '22px',
|
||||||
maxWidth: '200px',
|
maxWidth: '190px',
|
||||||
color: 'var(--affine-text-primary-color)',
|
color: 'var(--affine-text-primary-color)',
|
||||||
...textEllipsis(1),
|
...textEllipsis(1),
|
||||||
};
|
};
|
||||||
@ -38,13 +24,12 @@ export const StyledCard = styled('div')<{
|
|||||||
active?: boolean;
|
active?: boolean;
|
||||||
}>(({ active }) => {
|
}>(({ active }) => {
|
||||||
const borderColor = active ? 'var(--affine-primary-color)' : 'transparent';
|
const borderColor = active ? 'var(--affine-primary-color)' : 'transparent';
|
||||||
const backgroundColor = active ? 'var(--affine-white)' : 'transparent';
|
const backgroundColor = active ? 'var(--affine-white-30)' : 'transparent';
|
||||||
return {
|
return {
|
||||||
width: '280px',
|
width: '100%',
|
||||||
height: '58px',
|
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
padding: '12px',
|
padding: '12px',
|
||||||
borderRadius: '12px',
|
borderRadius: '8px',
|
||||||
border: `1px solid ${borderColor}`,
|
border: `1px solid ${borderColor}`,
|
||||||
...displayFlex('flex-start', 'flex-start'),
|
...displayFlex('flex-start', 'flex-start'),
|
||||||
transition: 'background .2s',
|
transition: 'background .2s',
|
||||||
@ -91,8 +76,8 @@ export const StyledModalHeader = styled('div')(() => {
|
|||||||
export const StyledSettingLink = styled(IconButton)(() => {
|
export const StyledSettingLink = styled(IconButton)(() => {
|
||||||
return {
|
return {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
right: '6px',
|
right: '10px',
|
||||||
bottom: '6px',
|
top: '10px',
|
||||||
opacity: 0,
|
opacity: 0,
|
||||||
borderRadius: '4px',
|
borderRadius: '4px',
|
||||||
color: 'var(--affine-primary-color)',
|
color: 'var(--affine-primary-color)',
|
||||||
@ -104,9 +89,11 @@ export const StyledSettingLink = styled(IconButton)(() => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
export const StyledWorkspaceType = styled('p')(() => {
|
export const StyledWorkspaceType = styled('div')(() => {
|
||||||
return {
|
return {
|
||||||
fontSize: 10,
|
...displayFlex('flex-start', 'center'),
|
||||||
|
width: '100%',
|
||||||
|
height: '20px',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -116,3 +103,35 @@ export const StyledWorkspaceTitleArea = styled('div')(() => {
|
|||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const StyledWorkspaceTypeEllipse = styled('div')<{
|
||||||
|
cloud?: boolean;
|
||||||
|
}>(({ cloud }) => {
|
||||||
|
return {
|
||||||
|
width: '5px',
|
||||||
|
height: '5px',
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: cloud
|
||||||
|
? 'var(--affine-palette-shape-blue)'
|
||||||
|
: 'var(--affine-palette-shape-green)',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export const StyledWorkspaceTypeText = styled('div')(() => {
|
||||||
|
return {
|
||||||
|
fontSize: '12px',
|
||||||
|
fontWeight: 500,
|
||||||
|
lineHeight: '20px',
|
||||||
|
marginLeft: '4px',
|
||||||
|
color: 'var(--affine-text-secondary-color)',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export const StyledIconContainer = styled('div')(() => {
|
||||||
|
return {
|
||||||
|
...displayFlex('flex-start', 'center'),
|
||||||
|
fontSize: '14px',
|
||||||
|
gap: '8px',
|
||||||
|
color: 'var(--affine-icon-secondary)',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
@ -16,9 +16,12 @@ import {
|
|||||||
} from '@dnd-kit/modifiers';
|
} from '@dnd-kit/modifiers';
|
||||||
import { arrayMove, SortableContext, useSortable } from '@dnd-kit/sortable';
|
import { arrayMove, SortableContext, useSortable } from '@dnd-kit/sortable';
|
||||||
import type { CSSProperties } from 'react';
|
import type { CSSProperties } from 'react';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { Suspense, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { WorkspaceCard } from '../../components/card/workspace-card';
|
import {
|
||||||
|
WorkspaceCard,
|
||||||
|
WorkspaceCardSkeleton,
|
||||||
|
} from '../../components/card/workspace-card';
|
||||||
import { workspaceItemStyle } from './index.css';
|
import { workspaceItemStyle } from './index.css';
|
||||||
|
|
||||||
export interface WorkspaceListProps {
|
export interface WorkspaceListProps {
|
||||||
@ -28,16 +31,25 @@ export interface WorkspaceListProps {
|
|||||||
onClick: (workspaceId: string) => void;
|
onClick: (workspaceId: string) => void;
|
||||||
onSettingClick: (workspaceId: string) => void;
|
onSettingClick: (workspaceId: string) => void;
|
||||||
onDragEnd: (event: DragEndEvent) => void;
|
onDragEnd: (event: DragEndEvent) => void;
|
||||||
|
useIsWorkspaceOwner?: (workspaceId: string) => boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SortableWorkspaceItemProps extends Omit<WorkspaceListProps, 'items'> {
|
interface SortableWorkspaceItemProps extends Omit<WorkspaceListProps, 'items'> {
|
||||||
item: RootWorkspaceMetadata;
|
item: RootWorkspaceMetadata;
|
||||||
|
useIsWorkspaceOwner?: (workspaceId: string) => boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SortableWorkspaceItem = (props: SortableWorkspaceItemProps) => {
|
const SortableWorkspaceItem = ({
|
||||||
|
disabled,
|
||||||
|
item,
|
||||||
|
useIsWorkspaceOwner,
|
||||||
|
currentWorkspaceId,
|
||||||
|
onClick,
|
||||||
|
onSettingClick,
|
||||||
|
}: SortableWorkspaceItemProps) => {
|
||||||
const { setNodeRef, attributes, listeners, transform, transition } =
|
const { setNodeRef, attributes, listeners, transform, transition } =
|
||||||
useSortable({
|
useSortable({
|
||||||
id: props.item.id,
|
id: item.id,
|
||||||
});
|
});
|
||||||
const style: CSSProperties = useMemo(
|
const style: CSSProperties = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
@ -45,11 +57,12 @@ const SortableWorkspaceItem = (props: SortableWorkspaceItemProps) => {
|
|||||||
? `translate3d(${transform.x}px, ${transform.y}px, 0)`
|
? `translate3d(${transform.x}px, ${transform.y}px, 0)`
|
||||||
: undefined,
|
: undefined,
|
||||||
transition,
|
transition,
|
||||||
pointerEvents: props.disabled ? 'none' : undefined,
|
pointerEvents: disabled ? 'none' : undefined,
|
||||||
opacity: props.disabled ? 0.6 : undefined,
|
opacity: disabled ? 0.6 : undefined,
|
||||||
}),
|
}),
|
||||||
[props.disabled, transform, transition]
|
[disabled, transform, transition]
|
||||||
);
|
);
|
||||||
|
const isOwner = useIsWorkspaceOwner?.(item.id);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={workspaceItemStyle}
|
className={workspaceItemStyle}
|
||||||
@ -60,10 +73,11 @@ const SortableWorkspaceItem = (props: SortableWorkspaceItemProps) => {
|
|||||||
{...listeners}
|
{...listeners}
|
||||||
>
|
>
|
||||||
<WorkspaceCard
|
<WorkspaceCard
|
||||||
currentWorkspaceId={props.currentWorkspaceId}
|
currentWorkspaceId={currentWorkspaceId}
|
||||||
meta={props.item}
|
meta={item}
|
||||||
onClick={props.onClick}
|
onClick={onClick}
|
||||||
onSettingClick={props.onSettingClick}
|
onSettingClick={onSettingClick}
|
||||||
|
isOwner={isOwner}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -106,7 +120,9 @@ export const WorkspaceList = (props: WorkspaceListProps) => {
|
|||||||
<DndContext sensors={sensors} onDragEnd={onDragEnd} modifiers={modifiers}>
|
<DndContext sensors={sensors} onDragEnd={onDragEnd} modifiers={modifiers}>
|
||||||
<SortableContext items={optimisticList}>
|
<SortableContext items={optimisticList}>
|
||||||
{optimisticList.map(item => (
|
{optimisticList.map(item => (
|
||||||
<SortableWorkspaceItem {...props} item={item} key={item.id} />
|
<Suspense fallback={<WorkspaceCardSkeleton />} key={item.id}>
|
||||||
|
<SortableWorkspaceItem {...props} item={item} key={item.id} />
|
||||||
|
</Suspense>
|
||||||
))}
|
))}
|
||||||
</SortableContext>
|
</SortableContext>
|
||||||
</DndContext>
|
</DndContext>
|
||||||
|
@ -11,6 +11,7 @@ export type ScrollableContainerProps = {
|
|||||||
className?: string;
|
className?: string;
|
||||||
viewPortClassName?: string;
|
viewPortClassName?: string;
|
||||||
styles?: React.CSSProperties;
|
styles?: React.CSSProperties;
|
||||||
|
scrollBarClassName?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ScrollableContainer = ({
|
export const ScrollableContainer = ({
|
||||||
@ -20,6 +21,7 @@ export const ScrollableContainer = ({
|
|||||||
className,
|
className,
|
||||||
styles: _styles,
|
styles: _styles,
|
||||||
viewPortClassName,
|
viewPortClassName,
|
||||||
|
scrollBarClassName,
|
||||||
}: PropsWithChildren<ScrollableContainerProps>) => {
|
}: PropsWithChildren<ScrollableContainerProps>) => {
|
||||||
const [hasScrollTop, ref] = useHasScrollTop();
|
const [hasScrollTop, ref] = useHasScrollTop();
|
||||||
return (
|
return (
|
||||||
@ -39,7 +41,7 @@ export const ScrollableContainer = ({
|
|||||||
</ScrollArea.Viewport>
|
</ScrollArea.Viewport>
|
||||||
<ScrollArea.Scrollbar
|
<ScrollArea.Scrollbar
|
||||||
orientation="vertical"
|
orientation="vertical"
|
||||||
className={clsx(styles.scrollbar, {
|
className={clsx(styles.scrollbar, scrollBarClassName, {
|
||||||
[styles.TableScrollbar]: inTableView,
|
[styles.TableScrollbar]: inTableView,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
|
@ -580,5 +580,9 @@
|
|||||||
"Successfully enabled AFFiNE Cloud": "Successfully enabled AFFiNE Cloud",
|
"Successfully enabled AFFiNE Cloud": "Successfully enabled AFFiNE Cloud",
|
||||||
"404.hint": "Sorry, you do not have access or this content does not exist...",
|
"404.hint": "Sorry, you do not have access or this content does not exist...",
|
||||||
"404.back": "Back to My Content",
|
"404.back": "Back to My Content",
|
||||||
"404.signOut": "Sign in to another account"
|
"404.signOut": "Sign in to another account",
|
||||||
|
"com.affine.workspaceList.addWorkspace.create": "Create Workspace",
|
||||||
|
"com.affine.workspaceList.workspaceListType.local": "Local Storage",
|
||||||
|
"com.affine.workspaceList.workspaceListType.cloud": "Cloud Sync",
|
||||||
|
"Local": "Local"
|
||||||
}
|
}
|
||||||
|
@ -74,8 +74,6 @@ test('Show collections items in sidebar', async ({ page }) => {
|
|||||||
skipInitialPage: true,
|
skipInitialPage: true,
|
||||||
});
|
});
|
||||||
expect(await items.count()).toBe(1);
|
expect(await items.count()).toBe(1);
|
||||||
|
|
||||||
await clickSideBarCurrentWorkspaceBanner(page);
|
|
||||||
await createLocalWorkspace(
|
await createLocalWorkspace(
|
||||||
{
|
{
|
||||||
name: 'Test 1',
|
name: 'Test 1',
|
||||||
|
Loading…
Reference in New Issue
Block a user