mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-22 19:53:48 +08:00
feat: new workspace switch dropdown design (#3700)
Co-authored-by: Alex Yang <himself65@outlook.com>
This commit is contained in:
@@ -1,11 +1,4 @@
|
|||||||
import {
|
import { Menu, MenuItem, Tooltip } from '@affine/component';
|
||||||
Menu,
|
|
||||||
MenuItem,
|
|
||||||
Modal,
|
|
||||||
ModalCloseButton,
|
|
||||||
ModalWrapper,
|
|
||||||
Tooltip,
|
|
||||||
} from '@affine/component';
|
|
||||||
import { ScrollableContainer } from '@affine/component';
|
import { ScrollableContainer } from '@affine/component';
|
||||||
import { WorkspaceList } from '@affine/component/workspace-list';
|
import { WorkspaceList } from '@affine/component/workspace-list';
|
||||||
import type {
|
import type {
|
||||||
@@ -15,28 +8,38 @@ import type {
|
|||||||
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 { HelpIcon, ImportIcon, PlusIcon } from '@blocksuite/icons';
|
|
||||||
import type { DragEndEvent } from '@dnd-kit/core';
|
|
||||||
import { useCallback, useRef } from 'react';
|
|
||||||
|
|
||||||
import type { AllWorkspace } from '../../../shared';
|
|
||||||
import { Footer } from '../footer';
|
|
||||||
import {
|
import {
|
||||||
StyledCreateWorkspaceCard,
|
CloudWorkspaceIcon,
|
||||||
|
HelpIcon,
|
||||||
|
ImportIcon,
|
||||||
|
MoreHorizontalIcon,
|
||||||
|
PlusIcon,
|
||||||
|
} from '@blocksuite/icons';
|
||||||
|
import type { DragEndEvent } from '@dnd-kit/core';
|
||||||
|
import { Popover } from '@mui/material';
|
||||||
|
import { IconButton } from '@toeverything/components/button';
|
||||||
|
import { Divider } from '@toeverything/components/divider';
|
||||||
|
import { useSetAtom } from 'jotai';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
import { openDisableCloudAlertModalAtom } from '../../../atoms';
|
||||||
|
import type { AllWorkspace } from '../../../shared';
|
||||||
|
import {
|
||||||
StyledCreateWorkspaceCardPill,
|
StyledCreateWorkspaceCardPill,
|
||||||
StyledCreateWorkspaceCardPillContainer,
|
|
||||||
StyledCreateWorkspaceCardPillContent,
|
StyledCreateWorkspaceCardPillContent,
|
||||||
StyledCreateWorkspaceCardPillIcon,
|
StyledCreateWorkspaceCardPillIcon,
|
||||||
StyledCreateWorkspaceCardPillTextSecondary,
|
|
||||||
StyledHelperContainer,
|
StyledHelperContainer,
|
||||||
|
StyledImportWorkspaceCardPill,
|
||||||
|
StyledModalBody,
|
||||||
StyledModalContent,
|
StyledModalContent,
|
||||||
StyledModalHeader,
|
StyledModalHeader,
|
||||||
StyledModalHeaderLeft,
|
StyledModalHeaderLeft,
|
||||||
StyledModalTitle,
|
StyledModalTitle,
|
||||||
StyledOperationWrapper,
|
StyledOperationWrapper,
|
||||||
StyleWorkspaceAdd,
|
StyledSignInCardPill,
|
||||||
StyleWorkspaceInfo,
|
StyledSignInCardPillTextCotainer,
|
||||||
StyleWorkspaceTitle,
|
StyledSignInCardPillTextPrimary,
|
||||||
|
StyledSignInCardPillTextSecondary,
|
||||||
} from './styles';
|
} from './styles';
|
||||||
|
|
||||||
interface WorkspaceModalProps {
|
interface WorkspaceModalProps {
|
||||||
@@ -52,105 +55,94 @@ interface WorkspaceModalProps {
|
|||||||
onMoveWorkspace: (activeId: string, overId: string) => void;
|
onMoveWorkspace: (activeId: string, overId: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CreateWorkspaceCard = ({
|
const AccountMenu = () => {
|
||||||
onNewWorkspace,
|
|
||||||
onAddWorkspace,
|
|
||||||
}: {
|
|
||||||
onNewWorkspace: () => void;
|
|
||||||
onAddWorkspace: () => void;
|
|
||||||
}) => {
|
|
||||||
const t = useAFFiNEI18N();
|
const t = useAFFiNEI18N();
|
||||||
const anchorEL = useRef<HTMLDivElement>(null);
|
return (
|
||||||
|
<div>
|
||||||
|
<div>Unlimted</div>
|
||||||
|
<Divider></Divider>
|
||||||
|
<MenuItem icon={<ImportIcon />} data-testid="editor-option-menu-import">
|
||||||
|
{t['com.affine.workspace.cloud.join']()}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem icon={<ImportIcon />} data-testid="editor-option-menu-import">
|
||||||
|
{t['com.affine.workspace.cloud.account.settings']()}
|
||||||
|
</MenuItem>
|
||||||
|
<Divider></Divider>
|
||||||
|
<MenuItem icon={<ImportIcon />} data-testid="editor-option-menu-import">
|
||||||
|
{t['com.affine.workspace.cloud.account.logout']()}
|
||||||
|
</MenuItem>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
if (runtimeConfig.enableSQLiteProvider && environment.isDesktop) {
|
const CloudWorkSpaceList = ({
|
||||||
return (
|
disabled,
|
||||||
<Menu
|
workspaces,
|
||||||
placement="auto"
|
onClickWorkspace,
|
||||||
trigger={['click']}
|
onClickWorkspaceSetting,
|
||||||
zIndex={1000}
|
currentWorkspaceId,
|
||||||
content={
|
onMoveWorkspace,
|
||||||
<StyledCreateWorkspaceCardPillContainer>
|
}: WorkspaceModalProps) => {
|
||||||
<StyledCreateWorkspaceCardPill>
|
const t = useAFFiNEI18N();
|
||||||
<MenuItem
|
|
||||||
style={{
|
|
||||||
height: 'auto',
|
|
||||||
padding: '8px 12px',
|
|
||||||
}}
|
|
||||||
onClick={onNewWorkspace}
|
|
||||||
data-testid="new-workspace"
|
|
||||||
>
|
|
||||||
<StyledCreateWorkspaceCardPillContent>
|
|
||||||
<div>
|
|
||||||
<p>{t['New Workspace']()}</p>
|
|
||||||
<StyledCreateWorkspaceCardPillTextSecondary>
|
|
||||||
<p>{t['Create your own workspace']()}</p>
|
|
||||||
</StyledCreateWorkspaceCardPillTextSecondary>
|
|
||||||
</div>
|
|
||||||
<StyledCreateWorkspaceCardPillIcon>
|
|
||||||
<PlusIcon />
|
|
||||||
</StyledCreateWorkspaceCardPillIcon>
|
|
||||||
</StyledCreateWorkspaceCardPillContent>
|
|
||||||
</MenuItem>
|
|
||||||
</StyledCreateWorkspaceCardPill>
|
|
||||||
<StyledCreateWorkspaceCardPill>
|
|
||||||
<MenuItem
|
|
||||||
onClick={onAddWorkspace}
|
|
||||||
data-testid="add-workspace"
|
|
||||||
style={{
|
|
||||||
height: 'auto',
|
|
||||||
padding: '8px 12px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<StyledCreateWorkspaceCardPillContent>
|
|
||||||
<div>
|
|
||||||
<p>{t['Add Workspace']()}</p>
|
|
||||||
<StyledCreateWorkspaceCardPillTextSecondary>
|
|
||||||
<p>{t['Add Workspace Hint']()}</p>
|
|
||||||
</StyledCreateWorkspaceCardPillTextSecondary>
|
|
||||||
</div>
|
|
||||||
<StyledCreateWorkspaceCardPillIcon>
|
|
||||||
<ImportIcon />
|
|
||||||
</StyledCreateWorkspaceCardPillIcon>
|
|
||||||
</StyledCreateWorkspaceCardPillContent>
|
|
||||||
</MenuItem>
|
|
||||||
</StyledCreateWorkspaceCardPill>
|
|
||||||
</StyledCreateWorkspaceCardPillContainer>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<StyledCreateWorkspaceCard
|
|
||||||
ref={anchorEL}
|
|
||||||
data-testid="add-or-new-workspace"
|
|
||||||
>
|
|
||||||
<StyleWorkspaceAdd className="add-icon">
|
|
||||||
<PlusIcon />
|
|
||||||
</StyleWorkspaceAdd>
|
|
||||||
|
|
||||||
<StyleWorkspaceInfo>
|
return (
|
||||||
<StyleWorkspaceTitle>{t['New Workspace']()}</StyleWorkspaceTitle>
|
<>
|
||||||
<p>{t['Create Or Import']()}</p>
|
<StyledModalHeader>
|
||||||
</StyleWorkspaceInfo>
|
<StyledModalHeaderLeft>
|
||||||
</StyledCreateWorkspaceCard>
|
<StyledModalTitle>
|
||||||
</Menu>
|
{t['com.affine.workspace.cloud.sync']()}
|
||||||
);
|
</StyledModalTitle>
|
||||||
} else {
|
<Tooltip
|
||||||
return (
|
content={t['Workspace description']()}
|
||||||
<div>
|
placement="top-start"
|
||||||
<StyledCreateWorkspaceCard
|
disablePortal={true}
|
||||||
onClick={onNewWorkspace}
|
>
|
||||||
data-testid="new-workspace"
|
<StyledHelperContainer>
|
||||||
>
|
<HelpIcon />
|
||||||
<StyleWorkspaceAdd className="add-icon">
|
</StyledHelperContainer>
|
||||||
<PlusIcon />
|
</Tooltip>
|
||||||
</StyleWorkspaceAdd>
|
</StyledModalHeaderLeft>
|
||||||
|
|
||||||
<StyleWorkspaceInfo>
|
<StyledOperationWrapper>
|
||||||
<StyleWorkspaceTitle>{t['New Workspace']()}</StyleWorkspaceTitle>
|
<Menu
|
||||||
<p>{t['Create Or Import']()}</p>
|
placement="bottom-end"
|
||||||
</StyleWorkspaceInfo>
|
trigger={['click']}
|
||||||
</StyledCreateWorkspaceCard>
|
content={<AccountMenu />}
|
||||||
</div>
|
zIndex={1000}
|
||||||
);
|
>
|
||||||
}
|
<IconButton
|
||||||
|
data-testid="previous-image-button"
|
||||||
|
icon={<MoreHorizontalIcon />}
|
||||||
|
type="plain"
|
||||||
|
/>
|
||||||
|
</Menu>
|
||||||
|
</StyledOperationWrapper>
|
||||||
|
</StyledModalHeader>
|
||||||
|
<StyledModalContent>
|
||||||
|
<WorkspaceList
|
||||||
|
disabled={disabled}
|
||||||
|
items={
|
||||||
|
workspaces.filter(
|
||||||
|
({ flavour }) => flavour !== WorkspaceFlavour.PUBLIC
|
||||||
|
) as (AffineCloudWorkspace | LocalWorkspace)[]
|
||||||
|
}
|
||||||
|
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]
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Divider />
|
||||||
|
</StyledModalContent>
|
||||||
|
</>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WorkspaceListModal = ({
|
export const WorkspaceListModal = ({
|
||||||
@@ -166,19 +158,77 @@ export const WorkspaceListModal = ({
|
|||||||
onMoveWorkspace,
|
onMoveWorkspace,
|
||||||
}: WorkspaceModalProps) => {
|
}: WorkspaceModalProps) => {
|
||||||
const t = useAFFiNEI18N();
|
const t = useAFFiNEI18N();
|
||||||
|
const setOpen = useSetAtom(openDisableCloudAlertModalAtom);
|
||||||
|
// TODO: AFFiNE Cloud support
|
||||||
|
const isLoggedIn = false;
|
||||||
|
const anchorEl = document.getElementById('current-workspace');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal open={open} onClose={onClose}>
|
<Popover
|
||||||
<ModalWrapper
|
sx={{
|
||||||
width={720}
|
color: 'success.main',
|
||||||
height={690}
|
zIndex: 999,
|
||||||
style={{
|
'& .MuiPopover-paper': {
|
||||||
display: 'flex',
|
borderRadius: '8px',
|
||||||
flexDirection: 'column',
|
boxShadow: 'var(--affine-shadow-1)',
|
||||||
}}
|
},
|
||||||
>
|
}}
|
||||||
<StyledModalHeader>
|
open={open}
|
||||||
<StyledModalHeaderLeft>
|
anchorEl={anchorEl}
|
||||||
<StyledModalTitle>{t['My Workspaces']()}</StyledModalTitle>
|
onClose={onClose}
|
||||||
|
>
|
||||||
|
<StyledModalBody>
|
||||||
|
<ScrollableContainer>
|
||||||
|
{isLoggedIn ? (
|
||||||
|
<CloudWorkSpaceList
|
||||||
|
disabled={disabled}
|
||||||
|
open={open}
|
||||||
|
onClose={onClose}
|
||||||
|
workspaces={workspaces}
|
||||||
|
onClickWorkspace={onClickWorkspace}
|
||||||
|
onClickWorkspaceSetting={onClickWorkspaceSetting}
|
||||||
|
onNewWorkspace={onNewWorkspace}
|
||||||
|
onAddWorkspace={onAddWorkspace}
|
||||||
|
currentWorkspaceId={currentWorkspaceId}
|
||||||
|
onMoveWorkspace={onMoveWorkspace}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<StyledModalContent>
|
||||||
|
<StyledSignInCardPill>
|
||||||
|
<MenuItem
|
||||||
|
style={{
|
||||||
|
height: 'auto',
|
||||||
|
padding: '8px 12px',
|
||||||
|
}}
|
||||||
|
onClick={async () => {
|
||||||
|
if (!runtimeConfig.enableCloud) {
|
||||||
|
setOpen(true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
data-testid="cloud-signin-button"
|
||||||
|
>
|
||||||
|
<StyledCreateWorkspaceCardPillContent>
|
||||||
|
<StyledCreateWorkspaceCardPillIcon>
|
||||||
|
<CloudWorkspaceIcon />
|
||||||
|
</StyledCreateWorkspaceCardPillIcon>
|
||||||
|
<StyledSignInCardPillTextCotainer>
|
||||||
|
<StyledSignInCardPillTextPrimary>
|
||||||
|
{t['com.affine.workspace.cloud.auth']()}
|
||||||
|
</StyledSignInCardPillTextPrimary>
|
||||||
|
<StyledSignInCardPillTextSecondary>
|
||||||
|
Sync with AFFiNE Cloud
|
||||||
|
</StyledSignInCardPillTextSecondary>
|
||||||
|
</StyledSignInCardPillTextCotainer>
|
||||||
|
</StyledCreateWorkspaceCardPillContent>
|
||||||
|
</MenuItem>
|
||||||
|
</StyledSignInCardPill>
|
||||||
|
<Divider />
|
||||||
|
</StyledModalContent>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<StyledModalHeader>
|
||||||
|
<StyledModalTitle>{t['Local Workspace']()}</StyledModalTitle>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
content={t['Workspace description']()}
|
content={t['Workspace description']()}
|
||||||
placement="top-start"
|
placement="top-start"
|
||||||
@@ -188,19 +238,7 @@ export const WorkspaceListModal = ({
|
|||||||
<HelpIcon />
|
<HelpIcon />
|
||||||
</StyledHelperContainer>
|
</StyledHelperContainer>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</StyledModalHeaderLeft>
|
</StyledModalHeader>
|
||||||
|
|
||||||
<StyledOperationWrapper>
|
|
||||||
<ModalCloseButton
|
|
||||||
data-testid="close-workspace-modal"
|
|
||||||
onClick={() => {
|
|
||||||
onClose();
|
|
||||||
}}
|
|
||||||
absolute={false}
|
|
||||||
/>
|
|
||||||
</StyledOperationWrapper>
|
|
||||||
</StyledModalHeader>
|
|
||||||
<ScrollableContainer>
|
|
||||||
<StyledModalContent>
|
<StyledModalContent>
|
||||||
<WorkspaceList
|
<WorkspaceList
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
@@ -222,14 +260,51 @@ export const WorkspaceListModal = ({
|
|||||||
[onMoveWorkspace]
|
[onMoveWorkspace]
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<CreateWorkspaceCard
|
</StyledModalContent>
|
||||||
onNewWorkspace={onNewWorkspace}
|
<StyledModalContent>
|
||||||
onAddWorkspace={onAddWorkspace}
|
{runtimeConfig.enableSQLiteProvider && environment.isDesktop ? (
|
||||||
/>
|
<StyledImportWorkspaceCardPill>
|
||||||
|
<MenuItem
|
||||||
|
onClick={onAddWorkspace}
|
||||||
|
data-testid="add-workspace"
|
||||||
|
style={{
|
||||||
|
height: 'auto',
|
||||||
|
padding: '8px 12px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<StyledCreateWorkspaceCardPillContent>
|
||||||
|
<StyledCreateWorkspaceCardPillIcon>
|
||||||
|
<ImportIcon />
|
||||||
|
</StyledCreateWorkspaceCardPillIcon>
|
||||||
|
<div>
|
||||||
|
<p>{t['com.affine.workspace.local.import']()}</p>
|
||||||
|
</div>
|
||||||
|
</StyledCreateWorkspaceCardPillContent>
|
||||||
|
</MenuItem>
|
||||||
|
</StyledImportWorkspaceCardPill>
|
||||||
|
) : null}
|
||||||
|
<StyledCreateWorkspaceCardPill>
|
||||||
|
<MenuItem
|
||||||
|
style={{
|
||||||
|
height: 'auto',
|
||||||
|
padding: '8px 12px',
|
||||||
|
}}
|
||||||
|
onClick={onNewWorkspace}
|
||||||
|
data-testid="new-workspace"
|
||||||
|
>
|
||||||
|
<StyledCreateWorkspaceCardPillContent>
|
||||||
|
<StyledCreateWorkspaceCardPillIcon>
|
||||||
|
<PlusIcon />
|
||||||
|
</StyledCreateWorkspaceCardPillIcon>
|
||||||
|
<div>
|
||||||
|
<p>{t['New Workspace']()}</p>
|
||||||
|
</div>
|
||||||
|
</StyledCreateWorkspaceCardPillContent>
|
||||||
|
</MenuItem>
|
||||||
|
</StyledCreateWorkspaceCardPill>
|
||||||
</StyledModalContent>
|
</StyledModalContent>
|
||||||
</ScrollableContainer>
|
</ScrollableContainer>
|
||||||
<Footer />
|
</StyledModalBody>
|
||||||
</ModalWrapper>
|
</Popover>
|
||||||
</Modal>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -81,11 +81,33 @@ export const StyledCreateWorkspaceCardPillContainer = styled('div')(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const StyledCreateWorkspaceCardPill = styled('div')(() => {
|
export const StyledCreateWorkspaceCardPill = styled('div')(() => {
|
||||||
|
return {
|
||||||
|
borderRadius: '8px',
|
||||||
|
display: 'flex',
|
||||||
|
width: '100%',
|
||||||
|
height: '58px',
|
||||||
|
background: 'var(--affine-background-primary-color)',
|
||||||
|
border: `1px solid var(--affine-border-color)`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export const StyledSignInCardPill = styled('div')(() => {
|
||||||
|
return {
|
||||||
|
borderRadius: '8px',
|
||||||
|
display: 'flex',
|
||||||
|
width: '100%',
|
||||||
|
height: '58px',
|
||||||
|
marginBottom: '6px',
|
||||||
|
background: 'var(--affine-background-primary-color)',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export const StyledImportWorkspaceCardPill = styled('div')(() => {
|
||||||
return {
|
return {
|
||||||
borderRadius: '5px',
|
borderRadius: '5px',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
boxShadow: '0px 0px 6px 0px rgba(0, 0, 0, 0.1)',
|
width: '100%',
|
||||||
background: 'var(--affine-background-primary-color)',
|
marginBottom: '12px',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -94,7 +116,6 @@ export const StyledCreateWorkspaceCardPillContent = styled('div')(() => {
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
gap: '12px',
|
gap: '12px',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'space-between',
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -106,13 +127,30 @@ export const StyledCreateWorkspaceCardPillIcon = styled('div')(() => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
export const StyledCreateWorkspaceCardPillTextSecondary = styled('div')(() => {
|
export const StyledSignInCardPillTextCotainer = styled('div')(() => {
|
||||||
|
return {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export const StyledSignInCardPillTextSecondary = styled('div')(() => {
|
||||||
return {
|
return {
|
||||||
fontSize: '12px',
|
fontSize: '12px',
|
||||||
color: 'var(--affine-text-secondary-color)',
|
color: 'var(--affine-text-secondary-color)',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const StyledSignInCardPillTextPrimary = styled('div')(() => {
|
||||||
|
return {
|
||||||
|
fontSize: 'var(--affine-font-base)',
|
||||||
|
fontWeight: 600,
|
||||||
|
lineHeight: '24px',
|
||||||
|
maxWidth: '200px',
|
||||||
|
...textEllipsis(1),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
export const StyledModalHeaderLeft = styled('div')(() => {
|
export const StyledModalHeaderLeft = styled('div')(() => {
|
||||||
return { ...displayFlex('flex-start', 'center') };
|
return { ...displayFlex('flex-start', 'center') };
|
||||||
});
|
});
|
||||||
@@ -120,6 +158,7 @@ export const StyledModalTitle = styled('div')(() => {
|
|||||||
return {
|
return {
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
fontSize: 'var(--affine-font-h6)',
|
fontSize: 'var(--affine-font-h6)',
|
||||||
|
color: 'var(--affine-text-primary-color)',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -134,10 +173,9 @@ export const StyledHelperContainer = styled('div')(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const StyledModalContent = styled('div')({
|
export const StyledModalContent = styled('div')({
|
||||||
height: '540px',
|
|
||||||
padding: '8px 40px',
|
|
||||||
...displayFlex('space-between', 'flex-start', 'flex-start'),
|
...displayFlex('space-between', 'flex-start', 'flex-start'),
|
||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
|
flexDirection: 'column',
|
||||||
});
|
});
|
||||||
export const StyledOperationWrapper = styled('div')(() => {
|
export const StyledOperationWrapper = styled('div')(() => {
|
||||||
return {
|
return {
|
||||||
@@ -162,11 +200,22 @@ export const StyleWorkspaceAdd = styled('div')(() => {
|
|||||||
export const StyledModalHeader = styled('div')(() => {
|
export const StyledModalHeader = styled('div')(() => {
|
||||||
return {
|
return {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
marginTop: '10px',
|
|
||||||
left: 0,
|
left: 0,
|
||||||
top: 0,
|
top: 0,
|
||||||
borderRadius: '24px 24px 0 0',
|
borderRadius: '24px 24px 0 0',
|
||||||
padding: '10px 40px',
|
padding: '12px 14px',
|
||||||
...displayFlex('space-between', 'center'),
|
...displayFlex('space-between', 'center'),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const StyledModalBody = styled('div')(() => {
|
||||||
|
return {
|
||||||
|
borderRadius: '8px',
|
||||||
|
padding: '12px',
|
||||||
|
display: 'inline-flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
gap: '12px',
|
||||||
|
background: 'var(--affine-white)',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|||||||
+2
@@ -32,6 +32,7 @@ export const WorkspaceSelector = ({
|
|||||||
|
|
||||||
// Open dialog when `Enter` or `Space` pressed
|
// Open dialog when `Enter` or `Space` pressed
|
||||||
// TODO-Doma Refactor with `@radix-ui/react-dialog` or other libraries that handle these out of the box and be accessible by default
|
// TODO-Doma Refactor with `@radix-ui/react-dialog` or other libraries that handle these out of the box and be accessible by default
|
||||||
|
// TODO: Delete this?
|
||||||
const handleKeyDown = useCallback(
|
const handleKeyDown = useCallback(
|
||||||
(e: React.KeyboardEvent) => {
|
(e: React.KeyboardEvent) => {
|
||||||
if (e.key === 'Enter' || e.key === ' ') {
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
@@ -50,6 +51,7 @@ export const WorkspaceSelector = ({
|
|||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
data-testid="current-workspace"
|
data-testid="current-workspace"
|
||||||
|
id="current-workspace"
|
||||||
>
|
>
|
||||||
<WorkspaceAvatar
|
<WorkspaceAvatar
|
||||||
data-testid="workspace-avatar"
|
data-testid="workspace-avatar"
|
||||||
|
|||||||
@@ -126,6 +126,7 @@ export const AllWorkspaceModals = (): ReactElement => {
|
|||||||
);
|
);
|
||||||
const setCurrentPageId = useSetAtom(currentPageIdAtom);
|
const setCurrentPageId = useSetAtom(currentPageIdAtom);
|
||||||
const [isPending, startTransition] = useTransition();
|
const [isPending, startTransition] = useTransition();
|
||||||
|
const [, startCloseTransition] = useTransition();
|
||||||
const [, setOpenSettingModalAtom] = useAtom(openSettingModalAtom);
|
const [, setOpenSettingModalAtom] = useAtom(openSettingModalAtom);
|
||||||
|
|
||||||
const handleOpenSettingModal = useCallback(
|
const handleOpenSettingModal = useCallback(
|
||||||
@@ -153,7 +154,9 @@ export const AllWorkspaceModals = (): ReactElement => {
|
|||||||
isOpenCreateWorkspaceModal === false
|
isOpenCreateWorkspaceModal === false
|
||||||
}
|
}
|
||||||
onClose={useCallback(() => {
|
onClose={useCallback(() => {
|
||||||
setOpenWorkspacesModal(false);
|
startCloseTransition(() => {
|
||||||
|
setOpenWorkspacesModal(false);
|
||||||
|
});
|
||||||
}, [setOpenWorkspacesModal])}
|
}, [setOpenWorkspacesModal])}
|
||||||
onMoveWorkspace={useCallback(
|
onMoveWorkspace={useCallback(
|
||||||
(activeId, overId) => {
|
(activeId, overId) => {
|
||||||
@@ -169,10 +172,12 @@ export const AllWorkspaceModals = (): ReactElement => {
|
|||||||
)}
|
)}
|
||||||
onClickWorkspace={useCallback(
|
onClickWorkspace={useCallback(
|
||||||
workspaceId => {
|
workspaceId => {
|
||||||
setOpenWorkspacesModal(false);
|
startCloseTransition(() => {
|
||||||
setCurrentWorkspaceId(workspaceId);
|
setOpenWorkspacesModal(false);
|
||||||
setCurrentPageId(null);
|
setCurrentWorkspaceId(workspaceId);
|
||||||
jumpToSubPath(workspaceId, WorkspaceSubPath.ALL);
|
setCurrentPageId(null);
|
||||||
|
jumpToSubPath(workspaceId, WorkspaceSubPath.ALL);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
jumpToSubPath,
|
jumpToSubPath,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { pluginRegisterPromise } from '@affine/core/bootstrap/register-plugins';
|
import { pluginRegisterPromise } from '@affine/core/bootstrap/register-plugins';
|
||||||
import { routes } from '@affine/core/router';
|
import { routes } from '@affine/core/router';
|
||||||
import { assertExists } from '@blocksuite/global/utils';
|
import { assertExists } from '@blocksuite/global/utils';
|
||||||
import type { StoryContext, StoryFn } from '@storybook/react';
|
import type { Decorator, StoryFn } from '@storybook/react';
|
||||||
import { userEvent, waitFor } from '@storybook/testing-library';
|
import { userEvent, waitFor } from '@storybook/testing-library';
|
||||||
import { use } from 'foxact/use';
|
import { use } from 'foxact/use';
|
||||||
import { Outlet, useLocation } from 'react-router-dom';
|
import { Outlet, useLocation } from 'react-router-dom';
|
||||||
@@ -11,9 +11,9 @@ import {
|
|||||||
withRouter,
|
withRouter,
|
||||||
} from 'storybook-addon-react-router-v6';
|
} from 'storybook-addon-react-router-v6';
|
||||||
|
|
||||||
const withCleanLocalStorage = (Story: StoryFn, context: StoryContext) => {
|
const withCleanLocalStorage: Decorator = (Story, context) => {
|
||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
return <Story {...context.args} />;
|
return <Story {...context} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
const FakeApp = () => {
|
const FakeApp = () => {
|
||||||
@@ -78,3 +78,33 @@ NotFoundPage.parameters = {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const WorkspaceList: StoryFn = () => {
|
||||||
|
return <FakeApp />;
|
||||||
|
};
|
||||||
|
WorkspaceList.play = async ({ canvasElement }) => {
|
||||||
|
// click current-workspace
|
||||||
|
await waitFor(
|
||||||
|
() => {
|
||||||
|
assertExists(
|
||||||
|
canvasElement.querySelector('[data-testid="current-workspace"]')
|
||||||
|
);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timeout: 5000,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const currentWorkspace = canvasElement.querySelector(
|
||||||
|
'[data-testid="current-workspace"]'
|
||||||
|
) as Element;
|
||||||
|
await userEvent.click(currentWorkspace);
|
||||||
|
};
|
||||||
|
WorkspaceList.decorators = [withRouter, withCleanLocalStorage];
|
||||||
|
WorkspaceList.parameters = {
|
||||||
|
reactRouter: reactRouterParameters({
|
||||||
|
routing: reactRouterOutlets(routes),
|
||||||
|
location: {
|
||||||
|
path: '/',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { SettingsIcon } from '@blocksuite/icons';
|
|||||||
import {
|
import {
|
||||||
CloudWorkspaceIcon as DefaultCloudWorkspaceIcon,
|
CloudWorkspaceIcon as DefaultCloudWorkspaceIcon,
|
||||||
CollaborationIcon as DefaultJoinedWorkspaceIcon,
|
CollaborationIcon as DefaultJoinedWorkspaceIcon,
|
||||||
LocalDataIcon as DefaultLocalDataIcon,
|
|
||||||
LocalWorkspaceIcon as DefaultLocalWorkspaceIcon,
|
LocalWorkspaceIcon as DefaultLocalWorkspaceIcon,
|
||||||
} from '@blocksuite/icons';
|
} from '@blocksuite/icons';
|
||||||
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
|
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
|
||||||
@@ -16,8 +15,8 @@ import { WorkspaceAvatar } from '../../workspace-avatar';
|
|||||||
import {
|
import {
|
||||||
StyledCard,
|
StyledCard,
|
||||||
StyledSettingLink,
|
StyledSettingLink,
|
||||||
StyleWorkspaceInfo,
|
StyledWorkspaceInfo,
|
||||||
StyleWorkspaceTitle,
|
StyledWorkspaceTitle,
|
||||||
} from './styles';
|
} from './styles';
|
||||||
|
|
||||||
const JoinedWorkspaceIcon = () => {
|
const JoinedWorkspaceIcon = () => {
|
||||||
@@ -31,9 +30,9 @@ const CloudWorkspaceIcon = () => {
|
|||||||
return <DefaultCloudWorkspaceIcon style={{ color: '#60A5FA' }} />;
|
return <DefaultCloudWorkspaceIcon style={{ color: '#60A5FA' }} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
const LocalDataIcon = () => {
|
// const LocalDataIcon = () => {
|
||||||
return <DefaultLocalDataIcon style={{ color: '#62CD80' }} />;
|
// return <DefaultLocalDataIcon style={{ color: '#62CD80' }} />;
|
||||||
};
|
// };
|
||||||
|
|
||||||
export interface WorkspaceTypeProps {
|
export interface WorkspaceTypeProps {
|
||||||
flavour: WorkspaceFlavour;
|
flavour: WorkspaceFlavour;
|
||||||
@@ -46,7 +45,7 @@ const WorkspaceType = ({ flavour }: WorkspaceTypeProps) => {
|
|||||||
|
|
||||||
if (flavour === WorkspaceFlavour.LOCAL) {
|
if (flavour === WorkspaceFlavour.LOCAL) {
|
||||||
return (
|
return (
|
||||||
<p title={t['Local Workspace']()}>
|
<p style={{ fontSize: '10px' }} title={t['Local Workspace']()}>
|
||||||
<LocalWorkspaceIcon />
|
<LocalWorkspaceIcon />
|
||||||
<span>{t['Local Workspace']()}</span>
|
<span>{t['Local Workspace']()}</span>
|
||||||
</p>
|
</p>
|
||||||
@@ -54,12 +53,12 @@ const WorkspaceType = ({ flavour }: WorkspaceTypeProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return isOwner ? (
|
return isOwner ? (
|
||||||
<p title={t['Cloud Workspace']()}>
|
<p style={{ fontSize: '10px' }} title={t['Cloud Workspace']()}>
|
||||||
<CloudWorkspaceIcon />
|
<CloudWorkspaceIcon />
|
||||||
<span>{t['Cloud Workspace']()}</span>
|
<span>{t['Cloud Workspace']()}</span>
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<p title={t['Joined Workspace']()}>
|
<p style={{ fontSize: '10px' }} title={t['Joined Workspace']()}>
|
||||||
<JoinedWorkspaceIcon />
|
<JoinedWorkspaceIcon />
|
||||||
<span>{t['Joined Workspace']()}</span>
|
<span>{t['Joined Workspace']()}</span>
|
||||||
</p>
|
</p>
|
||||||
@@ -79,7 +78,7 @@ export const WorkspaceCard = ({
|
|||||||
currentWorkspaceId,
|
currentWorkspaceId,
|
||||||
meta,
|
meta,
|
||||||
}: WorkspaceCardProps) => {
|
}: WorkspaceCardProps) => {
|
||||||
const t = useAFFiNEI18N();
|
// const t = useAFFiNEI18N();
|
||||||
const workspace = useStaticBlockSuiteWorkspace(meta.id);
|
const workspace = useStaticBlockSuiteWorkspace(meta.id);
|
||||||
const [name] = useBlockSuiteWorkspaceName(workspace);
|
const [name] = useBlockSuiteWorkspaceName(workspace);
|
||||||
|
|
||||||
@@ -91,18 +90,18 @@ export const WorkspaceCard = ({
|
|||||||
}, [onClick, meta.id])}
|
}, [onClick, meta.id])}
|
||||||
active={workspace.id === currentWorkspaceId}
|
active={workspace.id === currentWorkspaceId}
|
||||||
>
|
>
|
||||||
<WorkspaceAvatar size={58} workspace={workspace} />
|
<WorkspaceAvatar size={28} workspace={workspace} />
|
||||||
|
|
||||||
<StyleWorkspaceInfo>
|
<StyledWorkspaceInfo>
|
||||||
<StyleWorkspaceTitle>{name}</StyleWorkspaceTitle>
|
<StyledWorkspaceTitle>{name}</StyledWorkspaceTitle>
|
||||||
<WorkspaceType flavour={meta.flavour} />
|
<WorkspaceType flavour={meta.flavour} />
|
||||||
{meta.flavour === WorkspaceFlavour.LOCAL && (
|
{/* {meta.flavour === WorkspaceFlavour.LOCAL && (
|
||||||
<p title={t['Available Offline']()}>
|
<p title={t['Available Offline']()}>
|
||||||
<LocalDataIcon />
|
<LocalDataIcon />
|
||||||
<span>{t['Available Offline']()}</span>
|
<span>{t['Available Offline']()}</span>
|
||||||
</p>
|
</p>
|
||||||
)}
|
)} */}
|
||||||
</StyleWorkspaceInfo>
|
</StyledWorkspaceInfo>
|
||||||
<StyledSettingLink
|
<StyledSettingLink
|
||||||
className="setting-entry"
|
className="setting-entry"
|
||||||
onClick={e => {
|
onClick={e => {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { IconButton } from '@toeverything/components/button';
|
|||||||
|
|
||||||
import { displayFlex, styled, textEllipsis } from '../../../styles';
|
import { displayFlex, styled, textEllipsis } from '../../../styles';
|
||||||
|
|
||||||
export const StyleWorkspaceInfo = styled('div')(() => {
|
export const StyledWorkspaceInfo = styled('div')(() => {
|
||||||
return {
|
return {
|
||||||
marginLeft: '15px',
|
marginLeft: '15px',
|
||||||
width: '202px',
|
width: '202px',
|
||||||
@@ -23,13 +23,13 @@ export const StyleWorkspaceInfo = styled('div')(() => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
export const StyleWorkspaceTitle = styled('div')(() => {
|
export const StyledWorkspaceTitle = styled('div')(() => {
|
||||||
return {
|
return {
|
||||||
fontSize: 'var(--affine-font-base)',
|
fontSize: 'var(--affine-font-base)',
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
lineHeight: '24px',
|
lineHeight: '24px',
|
||||||
marginBottom: '10px',
|
|
||||||
maxWidth: '200px',
|
maxWidth: '200px',
|
||||||
|
color: 'var(--affine-text-primary-color)',
|
||||||
...textEllipsis(1),
|
...textEllipsis(1),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -38,19 +38,21 @@ 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';
|
||||||
return {
|
return {
|
||||||
width: '310px',
|
width: '280px',
|
||||||
height: '124px',
|
height: '58px',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
padding: '16px',
|
padding: '16px',
|
||||||
boxShadow: 'var(--affine-shadow-1)',
|
|
||||||
borderRadius: '12px',
|
borderRadius: '12px',
|
||||||
border: `1px solid ${borderColor}`,
|
border: `1px solid ${borderColor}`,
|
||||||
...displayFlex('flex-start', 'flex-start'),
|
...displayFlex('flex-start', 'flex-start'),
|
||||||
marginBottom: '24px',
|
marginBottom: '12px',
|
||||||
transition: 'background .2s',
|
transition: 'background .2s',
|
||||||
background: 'var(--affine-white-80)',
|
alignItems: 'center',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
|
color: 'var(--affine-text-secondary-color)',
|
||||||
|
background: backgroundColor,
|
||||||
':hover': {
|
':hover': {
|
||||||
background: 'var(--affine-hover-color)',
|
background: 'var(--affine-hover-color)',
|
||||||
'.add-icon': {
|
'.add-icon': {
|
||||||
@@ -96,3 +98,9 @@ export const StyledSettingLink = styled(IconButton)(() => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const StyledWorkspaceType = styled('p')(() => {
|
||||||
|
return {
|
||||||
|
fontSize: 10,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|||||||
@@ -470,6 +470,12 @@
|
|||||||
"com.affine.auth.create.count": "Create Account",
|
"com.affine.auth.create.count": "Create Account",
|
||||||
"com.affine.expired.page.title": "This link has expired...",
|
"com.affine.expired.page.title": "This link has expired...",
|
||||||
"com.affine.expired.page.subtitle": "Please request a new reset password link.",
|
"com.affine.expired.page.subtitle": "Please request a new reset password link.",
|
||||||
|
"com.affine.workspace.cloud.join": "Join Workspace",
|
||||||
|
"com.affine.workspace.cloud.account.settings": "Account Settings",
|
||||||
|
"com.affine.workspace.cloud.account.logout": "Log Out",
|
||||||
|
"com.affine.workspace.cloud.sync": "Cloud sync",
|
||||||
|
"com.affine.workspace.cloud.auth": "Sign up/ Sign in",
|
||||||
|
"com.affine.workspace.local.import": "Import Workspace",
|
||||||
"core": "core",
|
"core": "core",
|
||||||
"dark": "Dark",
|
"dark": "Dark",
|
||||||
"emptyAllPages": "Click on the <1>$t(New Page)</1> button to create your first page.",
|
"emptyAllPages": "Click on the <1>$t(New Page)</1> button to create your first page.",
|
||||||
|
|||||||
@@ -625,7 +625,7 @@ test('tooltips for all buttons should be visible when hovering', async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
await locator.getByTestId('download-button').hover();
|
await locator.getByTestId('download-button').hover();
|
||||||
await page.waitForTimeout(2000);
|
await page.waitForTimeout(1000);
|
||||||
{
|
{
|
||||||
const element = await page.getByRole('tooltip');
|
const element = await page.getByRole('tooltip');
|
||||||
const downloadTooltip = await element.getByText('Download').count();
|
const downloadTooltip = await element.getByText('Download').count();
|
||||||
|
|||||||
@@ -45,8 +45,7 @@ test('create one workspace in the workspace list', async ({
|
|||||||
expect(workspaceCards.length).toBe(2);
|
expect(workspaceCards.length).toBe(2);
|
||||||
|
|
||||||
//check page list length
|
//check page list length
|
||||||
const closeWorkspaceModal = page.getByTestId('close-workspace-modal');
|
await page.keyboard.press('Escape');
|
||||||
await closeWorkspaceModal.click();
|
|
||||||
await clickSideBarAllPageButton(page);
|
await clickSideBarAllPageButton(page);
|
||||||
await page.waitForTimeout(1000);
|
await page.waitForTimeout(1000);
|
||||||
const pageList = page.locator('[data-testid=page-list-item]');
|
const pageList = page.locator('[data-testid=page-list-item]');
|
||||||
@@ -91,6 +90,7 @@ test('create multi workspace in the workspace list', async ({
|
|||||||
expect(currentWorkspace.flavour).toContain('local');
|
expect(currentWorkspace.flavour).toContain('local');
|
||||||
|
|
||||||
await openWorkspaceListModal(page);
|
await openWorkspaceListModal(page);
|
||||||
|
await page.waitForTimeout(1000);
|
||||||
const sourceElement = page.getByTestId('draggable-item').nth(2);
|
const sourceElement = page.getByTestId('draggable-item').nth(2);
|
||||||
const targetElement = page.getByTestId('draggable-item').nth(1);
|
const targetElement = page.getByTestId('draggable-item').nth(1);
|
||||||
|
|
||||||
|
|||||||
@@ -16,12 +16,14 @@ export async function createWorkspace(
|
|||||||
await openWorkspaceListModal(page);
|
await openWorkspaceListModal(page);
|
||||||
|
|
||||||
// open create workspace modal
|
// open create workspace modal
|
||||||
await page.locator('.add-icon').click();
|
await page.getByTestId('new-workspace').click();
|
||||||
|
|
||||||
// input workspace name
|
// input workspace name
|
||||||
await page.getByPlaceholder('Set a Workspace name').click();
|
await page.getByPlaceholder('Set a Workspace name').click();
|
||||||
await page.getByPlaceholder('Set a Workspace name').fill(params.name);
|
await page.getByPlaceholder('Set a Workspace name').fill(params.name);
|
||||||
|
|
||||||
// click create button
|
// click create button
|
||||||
return page.getByRole('button', { name: 'Create' }).click();
|
return page.getByRole('button', { name: 'Create' }).click({
|
||||||
|
delay: 500,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user