diff --git a/packages/frontend/component/src/components/card/workspace-card/index.tsx b/packages/frontend/component/src/components/card/workspace-card/index.tsx
index bd1c11216e..cce605e305 100644
--- a/packages/frontend/component/src/components/card/workspace-card/index.tsx
+++ b/packages/frontend/component/src/components/card/workspace-card/index.tsx
@@ -1,69 +1,18 @@
import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant';
-import { WorkspaceFlavour } from '@affine/env/workspace';
-import { useAFFiNEI18N } from '@affine/i18n/hooks';
+import type { WorkspaceFlavour } from '@affine/env/workspace';
import { CollaborationIcon, SettingsIcon } from '@blocksuite/icons';
import type { WorkspaceMetadata } from '@toeverything/infra';
import { useCallback } from 'react';
-import { Avatar } from '../../../ui/avatar';
-import { Divider } from '../../../ui/divider';
+import { Avatar, type AvatarProps } from '../../../ui/avatar';
import { Skeleton } from '../../../ui/skeleton';
-import { Tooltip } from '../../../ui/tooltip';
-import {
- StyledCard,
- StyledIconContainer,
- StyledSettingLink,
- StyledWorkspaceInfo,
- StyledWorkspaceTitle,
- StyledWorkspaceTitleArea,
- StyledWorkspaceType,
- StyledWorkspaceTypeEllipse,
- StyledWorkspaceTypeText,
-} from './styles';
+import * as styles from './styles.css';
export interface WorkspaceTypeProps {
flavour: WorkspaceFlavour;
isOwner: boolean;
}
-const WorkspaceType = ({ flavour, isOwner }: WorkspaceTypeProps) => {
- const t = useAFFiNEI18N();
- if (flavour === WorkspaceFlavour.LOCAL) {
- return (
-
-
- {t['Local']()}
-
- );
- }
-
- return isOwner ? (
-
-
-
- {t['com.affine.brand.affineCloud']()}
-
-
- ) : (
-
-
-
- {t['com.affine.brand.affineCloud']()}
-
-
-
-
-
-
-
-
- );
-};
-
export interface WorkspaceCardProps {
currentWorkspaceId?: string | null;
meta: WorkspaceMetadata;
@@ -77,7 +26,7 @@ export interface WorkspaceCardProps {
export const WorkspaceCardSkeleton = () => {
return (
-
+
{
width={220}
style={{ marginLeft: '12px' }}
/>
-
+
);
};
+const avatarImageProps = {
+ style: { borderRadius: 3, overflow: 'hidden' },
+} satisfies AvatarProps['imageProps'];
export const WorkspaceCard = ({
onClick,
onSettingClick,
@@ -101,32 +53,38 @@ export const WorkspaceCard = ({
}: WorkspaceCardProps) => {
const displayName = name ?? UNTITLED_WORKSPACE_NAME;
return (
- {
onClick(meta);
}, [onClick, meta])}
- active={meta.id === currentWorkspaceId}
>
-
-
-
- {displayName}
+
+
+
{displayName}
-
+ {isOwner ? null : }
+ {
e.stopPropagation();
onSettingClick(meta);
}}
- withoutHoverStyle={true}
>
-
-
-
-
-
-
+
+
+
+
+
);
};
diff --git a/packages/frontend/component/src/components/card/workspace-card/styles.css.ts b/packages/frontend/component/src/components/card/workspace-card/styles.css.ts
new file mode 100644
index 0000000000..20778bedbb
--- /dev/null
+++ b/packages/frontend/component/src/components/card/workspace-card/styles.css.ts
@@ -0,0 +1,86 @@
+import { cssVar } from '@toeverything/theme';
+import { style } from '@vanilla-extract/css';
+
+import { displayFlex, textEllipsis } from '../../../styles';
+
+export const card = style({
+ width: '100%',
+ cursor: 'pointer',
+ padding: '8px 12px',
+ borderRadius: 4,
+ // border: `1px solid ${borderColor}`,
+ boxShadow: 'inset 0 0 0 1px transparent',
+ ...displayFlex('flex-start', 'flex-start'),
+ transition: 'background .2s',
+ position: 'relative',
+ color: cssVar('textSecondaryColor'),
+ background: 'transparent',
+ display: 'flex',
+ alignItems: 'center',
+ gap: 12,
+
+ selectors: {
+ '&:hover': {
+ background: cssVar('hoverColor'),
+ },
+ '&[data-active="true"]': {
+ boxShadow: 'inset 0 0 0 1px ' + cssVar('brandColor'),
+ },
+ },
+});
+
+export const workspaceInfo = style({
+ width: 0,
+ flex: 1,
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ gap: 2,
+});
+
+export const workspaceTitle = style({
+ width: 0,
+ flex: 1,
+ fontSize: cssVar('fontSm'),
+ fontWeight: 500,
+ lineHeight: '22px',
+ maxWidth: '190px',
+ color: cssVar('textPrimaryColor'),
+ ...textEllipsis(1),
+});
+
+export const actionButtons = style({
+ display: 'flex',
+ alignItems: 'center',
+});
+
+export const settingButtonWrapper = style({});
+
+export const settingButton = style({
+ transition: 'all 0.13s ease',
+ width: 0,
+ height: 20,
+ overflow: 'hidden',
+ marginLeft: 0,
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ placeItems: 'center',
+
+ borderRadius: 4,
+ boxShadow: 'none',
+ background: 'transparent',
+ cursor: 'pointer',
+
+ selectors: {
+ [`.${card}:hover &`]: {
+ width: 20,
+ marginLeft: 8,
+ boxShadow: cssVar('shadow1'),
+ background: cssVar('white80'),
+ },
+ // [`.${card}:hover &:hover`]: {
+ // background: cssVar('hoverColor'),
+ // },
+ },
+});
diff --git a/packages/frontend/component/src/components/card/workspace-card/styles.ts b/packages/frontend/component/src/components/card/workspace-card/styles.ts
deleted file mode 100644
index 46d48c2de2..0000000000
--- a/packages/frontend/component/src/components/card/workspace-card/styles.ts
+++ /dev/null
@@ -1,136 +0,0 @@
-import { displayFlex, styled, textEllipsis } from '../../../styles';
-import { IconButton } from '../../../ui/button';
-
-export const StyledWorkspaceInfo = styled('div')(() => {
- return {
- marginLeft: '12px',
- width: '100%',
- };
-});
-
-export const StyledWorkspaceTitle = styled('div')(() => {
- return {
- fontSize: 'var(--affine-font-sm)',
- fontWeight: 700,
- lineHeight: '22px',
- maxWidth: '190px',
- color: 'var(--affine-text-primary-color)',
- ...textEllipsis(1),
- };
-});
-
-export const StyledCard = styled('div')<{
- active?: boolean;
-}>(({ active }) => {
- const borderColor = active ? 'var(--affine-primary-color)' : 'transparent';
- const backgroundColor = active ? 'var(--affine-white-30)' : 'transparent';
- return {
- width: '100%',
- cursor: 'pointer',
- padding: '12px',
- borderRadius: '8px',
- border: `1px solid ${borderColor}`,
- ...displayFlex('flex-start', 'flex-start'),
- transition: 'background .2s',
- alignItems: 'center',
- position: 'relative',
- color: 'var(--affine-text-secondary-color)',
- background: backgroundColor,
- ':hover': {
- background: 'var(--affine-hover-color)',
- '.add-icon': {
- borderColor: 'var(--affine-primary-color)',
- color: 'var(--affine-primary-color)',
- },
- '.setting-entry': {
- opacity: 1,
- pointerEvents: 'auto',
- backgroundColor: 'var(--affine-white-30)',
- boxShadow: 'var(--affine-shadow-1)',
- ':hover': {
- background:
- 'linear-gradient(0deg, var(--affine-hover-color) 0%, var(--affine-hover-color) 100%), var(--affine-white-30)',
- },
- },
- },
- '@media (max-width: 720px)': {
- width: '100%',
- },
- };
-});
-
-export const StyledModalHeader = styled('div')(() => {
- return {
- width: '100%',
- height: '72px',
- position: 'absolute',
- left: 0,
- top: 0,
- borderRadius: '24px 24px 0 0',
- padding: '0 40px',
- ...displayFlex('space-between', 'center'),
- };
-});
-
-export const StyledSettingLink = styled(IconButton)(() => {
- return {
- position: 'absolute',
- right: '10px',
- top: '10px',
- opacity: 0,
- borderRadius: '4px',
- color: 'var(--affine-primary-color)',
- pointerEvents: 'none',
- transition: 'all .15s',
- ':hover': {
- background: 'var(--affine-hover-color)',
- },
- };
-});
-
-export const StyledWorkspaceType = styled('div')(() => {
- return {
- ...displayFlex('flex-start', 'center'),
- width: '100%',
- height: '20px',
- };
-});
-
-export const StyledWorkspaceTitleArea = styled('div')(() => {
- return {
- display: 'flex',
- 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)',
- };
-});
diff --git a/packages/frontend/component/src/ui/avatar/style.css.ts b/packages/frontend/component/src/ui/avatar/style.css.ts
index e7df958785..c34b6243e9 100644
--- a/packages/frontend/component/src/ui/avatar/style.css.ts
+++ b/packages/frontend/component/src/ui/avatar/style.css.ts
@@ -79,7 +79,6 @@ export const DefaultAvatarContainerStyle = style({
width: '100%',
height: '100%',
position: 'relative',
- borderRadius: '50%',
overflow: 'hidden',
});
export const DefaultAvatarMiddleItemStyle = style({
@@ -155,6 +154,7 @@ export const avatarFallback = style({
width: '100%',
height: '100%',
borderRadius: '50%',
+ overflow: 'hidden',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
diff --git a/packages/frontend/core/public/imgs/unknown-user.svg b/packages/frontend/core/public/imgs/unknown-user.svg
new file mode 100644
index 0000000000..25b33b446b
--- /dev/null
+++ b/packages/frontend/core/public/imgs/unknown-user.svg
@@ -0,0 +1,17 @@
+
+
\ No newline at end of file
diff --git a/packages/frontend/core/src/components/app-sidebar/fallback.css.ts b/packages/frontend/core/src/components/app-sidebar/fallback.css.ts
index 1250b8b585..046413fa68 100644
--- a/packages/frontend/core/src/components/app-sidebar/fallback.css.ts
+++ b/packages/frontend/core/src/components/app-sidebar/fallback.css.ts
@@ -1,10 +1,9 @@
import { style } from '@vanilla-extract/css';
export const fallbackStyle = style({
- margin: '5px 16px',
+ margin: '4px 16px',
height: '100%',
});
export const fallbackHeaderStyle = style({
- height: '56px',
width: '100%',
display: 'flex',
alignItems: 'center',
diff --git a/packages/frontend/core/src/components/pure/workspace-slider-bar/user-with-workspace-list/index.tsx b/packages/frontend/core/src/components/pure/workspace-slider-bar/user-with-workspace-list/index.tsx
index 4daa4c70e7..e77248ef9f 100644
--- a/packages/frontend/core/src/components/pure/workspace-slider-bar/user-with-workspace-list/index.tsx
+++ b/packages/frontend/core/src/components/pure/workspace-slider-bar/user-with-workspace-list/index.tsx
@@ -19,7 +19,7 @@ import * as styles from './index.css';
import { UserAccountItem } from './user-account';
import { AFFiNEWorkspaceList } from './workspace-list';
-const SignInItem = () => {
+export const SignInItem = () => {
const setDisableCloudOpen = useSetAtom(openDisableCloudAlertModalAtom);
const setOpen = useSetAtom(authAtom);
diff --git a/packages/frontend/core/src/components/pure/workspace-slider-bar/user-with-workspace-list/user-account/index.tsx b/packages/frontend/core/src/components/pure/workspace-slider-bar/user-with-workspace-list/user-account/index.tsx
index bb67ce51ae..94616d703c 100644
--- a/packages/frontend/core/src/components/pure/workspace-slider-bar/user-with-workspace-list/user-account/index.tsx
+++ b/packages/frontend/core/src/components/pure/workspace-slider-bar/user-with-workspace-list/user-account/index.tsx
@@ -1,73 +1,8 @@
-import { IconButton } from '@affine/component/ui/button';
-import { Divider } from '@affine/component/ui/divider';
-import { Menu, MenuIcon, MenuItem } from '@affine/component/ui/menu';
-import { useAFFiNEI18N } from '@affine/i18n/hooks';
-import {
- AccountIcon,
- MoreHorizontalIcon,
- SignOutIcon,
-} from '@blocksuite/icons';
-import { useSetAtom } from 'jotai';
-import { useCallback } from 'react';
-
-import {
- openSettingModalAtom,
- openSignOutModalAtom,
-} from '../../../../../atoms';
import { UserPlanButton } from '../../../../affine/auth/user-plan-button';
import * as styles from './index.css';
-const AccountMenu = ({ onEventEnd }: { onEventEnd?: () => void }) => {
- const setSettingModalAtom = useSetAtom(openSettingModalAtom);
- const setOpenSignOutModalAtom = useSetAtom(openSignOutModalAtom);
-
- const onOpenAccountSetting = useCallback(() => {
- setSettingModalAtom(prev => ({
- ...prev,
- open: true,
- activeTab: 'account',
- }));
- }, [setSettingModalAtom]);
-
- const onOpenSignOutModal = useCallback(() => {
- onEventEnd?.();
- setOpenSignOutModalAtom(true);
- }, [onEventEnd, setOpenSignOutModalAtom]);
-
- const t = useAFFiNEI18N();
-
- return (
-
-
-
-
-
- );
-};
-
export const UserAccountItem = ({
email,
- onEventEnd,
}: {
email: string;
onEventEnd?: () => void;
@@ -76,21 +11,8 @@ export const UserAccountItem = ({
-
}
- contentOptions={{
- side: 'right',
- sideOffset: 12,
- }}
- >
-
}
- type="plain"
- />
-
+
);
};
diff --git a/packages/frontend/core/src/components/pure/workspace-slider-bar/user-with-workspace-list/workspace-list/index.css.ts b/packages/frontend/core/src/components/pure/workspace-slider-bar/user-with-workspace-list/workspace-list/index.css.ts
index 13fac8256b..3bd7664102 100644
--- a/packages/frontend/core/src/components/pure/workspace-slider-bar/user-with-workspace-list/workspace-list/index.css.ts
+++ b/packages/frontend/core/src/components/pure/workspace-slider-bar/user-with-workspace-list/workspace-list/index.css.ts
@@ -10,17 +10,21 @@ export const workspaceListWrapper = style({
display: 'flex',
width: '100%',
flexDirection: 'column',
- gap: '4px',
+ gap: 2,
});
export const workspaceType = style({
display: 'flex',
alignItems: 'center',
- justifyContent: 'space-between',
+ gap: 4,
padding: '0px 12px',
+ fontWeight: 500,
fontSize: cssVar('fontXs'),
lineHeight: '20px',
color: cssVar('textSecondaryColor'),
});
+export const workspaceTypeIcon = style({
+ color: cssVar('iconSecondary'),
+});
export const scrollbar = style({
transform: 'translateX(8px)',
width: '4px',
diff --git a/packages/frontend/core/src/components/pure/workspace-slider-bar/user-with-workspace-list/workspace-list/index.tsx b/packages/frontend/core/src/components/pure/workspace-slider-bar/user-with-workspace-list/workspace-list/index.tsx
index 0b9940d435..4e72fce242 100644
--- a/packages/frontend/core/src/components/pure/workspace-slider-bar/user-with-workspace-list/workspace-list/index.tsx
+++ b/packages/frontend/core/src/components/pure/workspace-slider-bar/user-with-workspace-list/workspace-list/index.tsx
@@ -8,6 +8,7 @@ import {
} from '@affine/core/hooks/use-workspace-info';
import { WorkspaceFlavour } from '@affine/env/workspace';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
+import { CloudWorkspaceIcon, LocalWorkspaceIcon } from '@blocksuite/icons';
import type { DragEndEvent } from '@dnd-kit/core';
import type { WorkspaceMetadata } from '@toeverything/infra';
import { useLiveData, useService, WorkspaceManager } from '@toeverything/infra';
@@ -49,6 +50,11 @@ const CloudWorkSpaceList = ({
return (
+
{t['com.affine.workspaceList.workspaceListType.cloud']()}
+
{t['com.affine.workspaceList.workspaceListType.local']()}
{
return (
<>
- AFFiNE Cloud
+ Cloud
>
);
};
@@ -196,21 +190,49 @@ const useSyncEngineSyncProgress = () => {
) : (
),
+ active:
+ currentWorkspace.flavour === WorkspaceFlavour.AFFINE_CLOUD &&
+ (syncing || retrying || isOverCapacity),
};
};
-const WorkspaceStatus = () => {
- const { message, icon } = useSyncEngineSyncProgress();
+const WorkspaceInfo = ({ name }: { name: string }) => {
+ const { message, icon, active } = useSyncEngineSyncProgress();
+ const currentWorkspace = useService(Workspace);
+ const isCloud = currentWorkspace.flavour === WorkspaceFlavour.AFFINE_CLOUD;
+
+ // to make sure that animation will play first time
+ const [delayActive, setDelayActive] = useState(false);
+ useEffect(() => {
+ setDelayActive(active);
+ }, [active]);
return (
-
-
- {icon}
-
+
+
+
+
+ {name}
+
+
+ {isCloud ? : }
+
+
+
+ {/* when syncing/offline/... */}
+
+
);
};
+const avatarImageProps = {
+ style: { borderRadius: 3 },
+} satisfies AvatarProps['imageProps'];
export const WorkspaceCard = forwardRef<
HTMLDivElement,
HTMLAttributes
@@ -227,7 +249,8 @@ export const WorkspaceCard = forwardRef<
const name = information?.name ?? UNTITLED_WORKSPACE_NAME;
return (
-
-
-
- {name}
-
-
-
-
+
+
);
});
diff --git a/packages/frontend/core/src/components/pure/workspace-slider-bar/workspace-card/styles.css.ts b/packages/frontend/core/src/components/pure/workspace-slider-bar/workspace-card/styles.css.ts
new file mode 100644
index 0000000000..528cabf5e7
--- /dev/null
+++ b/packages/frontend/core/src/components/pure/workspace-slider-bar/workspace-card/styles.css.ts
@@ -0,0 +1,99 @@
+import { cssVar } from '@toeverything/theme';
+import { globalStyle, style } from '@vanilla-extract/css';
+
+const wsSlideAnim = {
+ ease: 'cubic-bezier(.45,.21,0,1)',
+ duration: '0.5s',
+ delay: '0.23s',
+};
+
+export const container = style({
+ height: '50px',
+ display: 'flex',
+ alignItems: 'center',
+ gap: 8,
+ padding: '0 6px',
+ borderRadius: 4,
+ outline: 'none',
+ width: '100%',
+ maxWidth: 500,
+ color: cssVar('textPrimaryColor'),
+ ':hover': {
+ cursor: 'pointer',
+ background: cssVar('hoverColor'),
+ },
+});
+
+export const workspaceInfoSlider = style({
+ height: 42,
+ overflow: 'hidden',
+});
+export const workspaceInfoSlide = style({
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'flex-start',
+ transform: 'translateY(0)',
+ transition: `transform ${wsSlideAnim.duration} ${wsSlideAnim.ease} ${wsSlideAnim.delay}`,
+ selectors: {
+ [`.${workspaceInfoSlider}[data-active="true"] &`]: {
+ transform: 'translateY(-42px)',
+ },
+ },
+});
+export const workspaceInfo = style({
+ width: '100%',
+ flexGrow: 1,
+ overflow: 'hidden',
+ height: 42,
+ display: 'flex',
+ flexDirection: 'column',
+ justifyContent: 'center',
+ transition: `opacity ${wsSlideAnim.duration} ${wsSlideAnim.ease} ${wsSlideAnim.delay}`,
+
+ selectors: {
+ [`.${workspaceInfoSlider}[data-active="true"] &[data-type="normal"]`]: {
+ opacity: 0,
+ },
+ [`.${workspaceInfoSlider}[data-active="false"] &[data-type="events"]`]: {
+ opacity: 0,
+ },
+ },
+});
+
+export const workspaceName = style({
+ fontSize: cssVar('fontSm'),
+ lineHeight: '22px',
+ fontWeight: 500,
+ userSelect: 'none',
+ overflow: 'hidden',
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+});
+
+export const workspaceStatus = style({
+ display: 'flex',
+ gap: 2,
+ alignItems: 'center',
+ fontSize: cssVar('fontXs'),
+ lineHeight: '20px',
+ fontWeight: 400,
+ color: cssVar('black50'),
+});
+globalStyle(`.${workspaceStatus} svg`, {
+ width: 16,
+ height: 16,
+ color: cssVar('iconSecondary'),
+});
+
+export const workspaceActiveStatus = style({
+ display: 'flex',
+ gap: 2,
+ alignItems: 'center',
+ fontSize: cssVar('fontSm'),
+ lineHeight: '22px',
+ color: cssVar('textSecondaryColor'),
+});
+globalStyle(`.${workspaceActiveStatus} svg`, {
+ width: 16,
+ height: 16,
+});
diff --git a/packages/frontend/core/src/components/pure/workspace-slider-bar/workspace-card/styles.ts b/packages/frontend/core/src/components/pure/workspace-slider-bar/workspace-card/styles.ts
index ea8f62c6de..55d0fb3ec6 100644
--- a/packages/frontend/core/src/components/pure/workspace-slider-bar/workspace-card/styles.ts
+++ b/packages/frontend/core/src/components/pure/workspace-slider-bar/workspace-card/styles.ts
@@ -1,16 +1,18 @@
import { displayFlex, styled, textEllipsis } from '@affine/component';
+import { cssVar } from '@toeverything/theme';
export const StyledSelectorContainer = styled('div')({
- height: '58px',
+ height: '50px',
display: 'flex',
alignItems: 'center',
padding: '0 6px',
borderRadius: '8px',
outline: 'none',
- width: '100%',
- color: 'var(--affine-text-primary-color)',
+ width: 'fit-content',
+ maxWidth: '100%',
+ color: cssVar('textPrimaryColor'),
':hover': {
cursor: 'pointer',
- background: 'var(--affine-hover-color)',
+ background: cssVar('hoverColor'),
},
});
@@ -23,8 +25,9 @@ export const StyledSelectorWrapper = styled('div')(() => {
});
export const StyledWorkspaceName = styled('div')(() => {
return {
- lineHeight: '24px',
- fontWeight: 600,
+ fontSize: cssVar('fontSm'),
+ lineHeight: '22px',
+ fontWeight: 500,
userSelect: 'none',
...textEllipsis(1),
marginLeft: '4px',
@@ -35,17 +38,16 @@ export const StyledWorkspaceStatus = styled('div')(() => {
return {
height: '22px',
...displayFlex('flex-start', 'center'),
- fontSize: 'var(--affine-font-sm)',
- color: 'var(--affine-text-secondary-color)',
+ fontSize: cssVar('fontXs'),
+ color: cssVar('black50'),
userSelect: 'none',
padding: '0 4px',
gap: '4px',
zIndex: '1',
svg: {
- color: 'var(--affine-icon-color)',
- fontSize: 'var(--affine-font-base)',
+ color: cssVar('iconSecondary'),
'&[data-warning-color="true"]': {
- color: 'var(--affine-error-color)',
+ color: cssVar('errorColor'),
},
},
};
diff --git a/packages/frontend/core/src/components/root-app-sidebar/index.css.ts b/packages/frontend/core/src/components/root-app-sidebar/index.css.ts
new file mode 100644
index 0000000000..1b4d57eee5
--- /dev/null
+++ b/packages/frontend/core/src/components/root-app-sidebar/index.css.ts
@@ -0,0 +1,19 @@
+import { style } from '@vanilla-extract/css';
+
+export const workspaceAndUserWrapper = style({
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ gap: 8,
+});
+
+export const workspaceWrapper = style({
+ width: 0,
+ flex: 1,
+});
+
+export const userInfoWrapper = style({
+ flexShrink: 0,
+ width: 28,
+ height: 28,
+});
diff --git a/packages/frontend/core/src/components/root-app-sidebar/index.tsx b/packages/frontend/core/src/components/root-app-sidebar/index.tsx
index 194ed8eeb4..e6dca11b8d 100644
--- a/packages/frontend/core/src/components/root-app-sidebar/index.tsx
+++ b/packages/frontend/core/src/components/root-app-sidebar/index.tsx
@@ -42,8 +42,10 @@ import { AddFavouriteButton } from '../pure/workspace-slider-bar/favorite/add-fa
import FavoriteList from '../pure/workspace-slider-bar/favorite/favorite-list';
import { WorkspaceSelector } from '../workspace-selector';
import ImportPage from './import-page';
+import { workspaceAndUserWrapper, workspaceWrapper } from './index.css';
import { AppSidebarJournalButton } from './journal-button';
import { UpdaterButton } from './updater-button';
+import { UserInfo } from './user-info';
export type RootAppSidebarProps = {
isPublicWorkspace: boolean;
@@ -179,7 +181,12 @@ export const RootAppSidebar = ({
titles={deletePageTitles}
/>
-
+
{
+ const { status } = useSession();
+ const isAuthenticated = status === 'authenticated';
+ return isAuthenticated ? : ;
+};
+
+const AuthorizedUserInfo = () => {
+ const user = useCurrentUser();
+ return (
+ }>
+
+
+ );
+};
+
+const UnauthorizedUserInfo = () => {
+ const setDisableCloudOpen = useSetAtom(openDisableCloudAlertModalAtom);
+ const setOpen = useSetAtom(authAtom);
+
+ const openSignInModal = useCallback(() => {
+ if (!runtimeConfig.enableCloud) setDisableCloudOpen(true);
+ else setOpen(state => ({ ...state, openModal: true }));
+ }, [setDisableCloudOpen, setOpen]);
+
+ return (
+
+ );
+};
+
+const AccountMenu = () => {
+ const setSettingModalAtom = useSetAtom(openSettingModalAtom);
+ const setOpenSignOutModalAtom = useSetAtom(openSignOutModalAtom);
+
+ const onOpenAccountSetting = useCallback(() => {
+ setSettingModalAtom(prev => ({
+ ...prev,
+ open: true,
+ activeTab: 'account',
+ }));
+ }, [setSettingModalAtom]);
+
+ const onOpenSignOutModal = useCallback(() => {
+ setOpenSignOutModalAtom(true);
+ }, [setOpenSignOutModalAtom]);
+
+ const t = useAFFiNEI18N();
+
+ return (
+ <>
+
+
+
+ >
+ );
+};
+
+const OperationMenu = () => {
+ // TODO: display usage progress bar
+ const StorageUsage = null;
+
+ return (
+ <>
+ {StorageUsage}
+
+ >
+ );
+};
diff --git a/packages/frontend/core/src/modules/navigation/view/navigation-buttons.css.ts b/packages/frontend/core/src/modules/navigation/view/navigation-buttons.css.ts
index 7b91edb5d6..941dd3c25d 100644
--- a/packages/frontend/core/src/modules/navigation/view/navigation-buttons.css.ts
+++ b/packages/frontend/core/src/modules/navigation/view/navigation-buttons.css.ts
@@ -3,7 +3,7 @@ import { style } from '@vanilla-extract/css';
export const container = style({
display: 'flex',
alignItems: 'center',
- columnGap: '32px',
+ columnGap: '8px',
});
export const button = style({
diff --git a/tests/affine-cloud/e2e/login.spec.ts b/tests/affine-cloud/e2e/login.spec.ts
index 6ede51db77..9ea7e65f02 100644
--- a/tests/affine-cloud/e2e/login.spec.ts
+++ b/tests/affine-cloud/e2e/login.spec.ts
@@ -11,6 +11,7 @@ import {
clickSideBarAllPageButton,
clickSideBarCurrentWorkspaceBanner,
clickSideBarSettingButton,
+ clickSideBarUseAvatar,
} from '@affine-test/kit/utils/sidebar';
import { createLocalWorkspace } from '@affine-test/kit/utils/workspace';
import { expect } from '@playwright/test';
@@ -69,14 +70,13 @@ test.describe('login first', () => {
);
await clickSideBarAllPageButton(page);
const currentUrl = page.url();
- await clickSideBarCurrentWorkspaceBanner(page);
- await page.getByTestId('workspace-modal-account-option').click();
+ await clickSideBarUseAvatar(page);
await page.getByTestId('workspace-modal-sign-out-option').click();
await page.getByTestId('confirm-sign-out-button').click();
await page.reload();
- await clickSideBarCurrentWorkspaceBanner(page);
- const signInButton = page.getByTestId('cloud-signin-button');
- await expect(signInButton).toBeVisible();
+ await clickSideBarUseAvatar(page);
+ const authModal = page.getByTestId('auth-modal');
+ await expect(authModal).toBeVisible();
expect(page.url()).toBe(currentUrl);
});
diff --git a/tests/affine-local/e2e/image-preview.spec.ts b/tests/affine-local/e2e/image-preview.spec.ts
index 4ed72ce702..5845869ba9 100644
--- a/tests/affine-local/e2e/image-preview.spec.ts
+++ b/tests/affine-local/e2e/image-preview.spec.ts
@@ -33,7 +33,8 @@ async function importImage(page: Page, url: string) {
},
[url]
);
- await page.waitForTimeout(500);
+ // TODO: wait for image to be loaded more reliably
+ await page.waitForTimeout(1000);
}
async function closeImagePreviewModal(page: Page) {
@@ -53,7 +54,7 @@ test('image preview should be shown', async ({ page }) => {
await title.click();
await page.keyboard.press('Enter');
await importImage(page, 'http://localhost:8081/large-image.png');
- await page.locator('img').first().dblclick();
+ await page.locator('affine-page-image').first().dblclick();
const locator = page.getByTestId('image-preview-modal');
await expect(locator).toBeVisible();
await closeImagePreviewModal(page);
@@ -70,11 +71,12 @@ test('image go left and right', async ({ page }) => {
await title.click();
await page.keyboard.press('Enter');
await importImage(page, 'http://localhost:8081/large-image.png');
- await page.locator('img').first().dblclick();
+ await page.locator('affine-page-image').first().dblclick();
await page.waitForTimeout(500);
blobId = (await page
+ .getByTestId('image-preview-modal')
.locator('img')
- .nth(1)
+ .first()
.getAttribute('data-blob-id')) as string;
expect(blobId).toBeTruthy();
await closeImagePreviewModal(page);
@@ -87,11 +89,12 @@ test('image go left and right', async ({ page }) => {
}
const locator = page.getByTestId('image-preview-modal');
await expect(locator).toBeHidden();
- await page.locator('img').first().dblclick();
+ await page.locator('affine-page-image').first().dblclick();
await page.waitForTimeout(1000);
{
- const newBlobId = (await locator
- .locator('img[data-blob-id]')
+ const newBlobId = (await page
+ .getByTestId('image-preview-modal')
+ .locator('img')
.first()
.getAttribute('data-blob-id')) as string;
expect(newBlobId).not.toBe(blobId);
@@ -99,8 +102,9 @@ test('image go left and right', async ({ page }) => {
await page.keyboard.press('ArrowRight');
await page.waitForTimeout(1000);
{
- const newBlobId = (await locator
- .locator('img[data-blob-id]')
+ const newBlobId = (await page
+ .getByTestId('image-preview-modal')
+ .locator('img')
.first()
.getAttribute('data-blob-id')) as string;
expect(newBlobId).toBe(blobId);
@@ -117,11 +121,12 @@ test('image able to zoom in and out with mouse scroll', async ({ page }) => {
await title.click();
await page.keyboard.press('Enter');
await importImage(page, 'http://localhost:8081/large-image.png');
- await page.locator('img').first().dblclick();
+ await page.locator('affine-page-image').first().dblclick();
await page.waitForTimeout(500);
blobId = (await page
+ .getByTestId('image-preview-modal')
.locator('img')
- .nth(1)
+ .first()
.getAttribute('data-blob-id')) as string;
expect(blobId).toBeTruthy();
}
@@ -170,11 +175,12 @@ test('image able to zoom in and out with button click', async ({ page }) => {
await title.click();
await page.keyboard.press('Enter');
await importImage(page, 'http://localhost:8081/large-image.png');
- await page.locator('img').first().dblclick();
+ await page.locator('affine-page-image').first().dblclick();
await page.waitForTimeout(500);
blobId = (await page
+ .getByTestId('image-preview-modal')
.locator('img')
- .nth(1)
+ .first()
.getAttribute('data-blob-id')) as string;
expect(blobId).toBeTruthy();
}
@@ -216,11 +222,12 @@ test('image should able to go left and right by buttons', async ({ page }) => {
await title.click();
await page.keyboard.press('Enter');
await importImage(page, 'http://localhost:8081/large-image.png');
- await page.locator('img').first().dblclick();
+ await page.locator('affine-page-image').first().dblclick();
await page.waitForTimeout(500);
blobId = (await page
+ .getByTestId('image-preview-modal')
.locator('img')
- .nth(1)
+ .first()
.getAttribute('data-blob-id')) as string;
expect(blobId).toBeTruthy();
await closeImagePreviewModal(page);
@@ -232,7 +239,7 @@ test('image should able to go left and right by buttons', async ({ page }) => {
await importImage(page, 'http://localhost:8081/affine-preview.png');
}
const locator = page.getByTestId('image-preview-modal');
- await page.locator('img').first().dblclick();
+ await page.locator('affine-page-image').first().dblclick();
await expect(locator).toBeVisible();
{
const newBlobId = (await locator
@@ -268,11 +275,12 @@ test('image able to fit to screen by button', async ({ page }) => {
await title.click();
await page.keyboard.press('Enter');
await importImage(page, 'http://localhost:8081/large-image.png');
- await page.locator('img').first().dblclick();
+ await page.locator('affine-page-image').first().dblclick();
await page.waitForTimeout(500);
blobId = (await page
+ .getByTestId('image-preview-modal')
.locator('img')
- .nth(1)
+ .first()
.getAttribute('data-blob-id')) as string;
expect(blobId).toBeTruthy();
}
@@ -325,11 +333,12 @@ test('image able to reset zoom to 100%', async ({ page }) => {
await title.click();
await page.keyboard.press('Enter');
await importImage(page, 'http://localhost:8081/large-image.png');
- await page.locator('img').first().dblclick();
+ await page.locator('affine-page-image').first().dblclick();
await page.waitForTimeout(500);
blobId = (await page
+ .getByTestId('image-preview-modal')
.locator('img')
- .nth(1)
+ .first()
.getAttribute('data-blob-id')) as string;
expect(blobId).toBeTruthy();
}
@@ -378,11 +387,12 @@ test('image able to copy to clipboard', async ({ page }) => {
await title.click();
await page.keyboard.press('Enter');
await importImage(page, 'http://localhost:8081/large-image.png');
- await page.locator('img').first().dblclick();
+ await page.locator('affine-page-image').first().dblclick();
await page.waitForTimeout(500);
blobId = (await page
+ .getByTestId('image-preview-modal')
.locator('img')
- .nth(1)
+ .first()
.getAttribute('data-blob-id')) as string;
expect(blobId).toBeTruthy();
}
@@ -407,11 +417,12 @@ test('image able to download', async ({ page }) => {
await title.click();
await page.keyboard.press('Enter');
await importImage(page, 'http://localhost:8081/large-image.png');
- await page.locator('img').first().dblclick();
+ await page.locator('affine-page-image').first().dblclick();
await page.waitForTimeout(500);
blobId = (await page
+ .getByTestId('image-preview-modal')
.locator('img')
- .nth(1)
+ .first()
.getAttribute('data-blob-id')) as string;
expect(blobId).toBeTruthy();
}
@@ -437,11 +448,12 @@ test('image should only able to move when image is larger than viewport', async
await title.click();
await page.keyboard.press('Enter');
await importImage(page, 'http://localhost:8081/large-image.png');
- await page.locator('img').first().dblclick();
+ await page.locator('affine-page-image').first().dblclick();
await page.waitForTimeout(500);
blobId = (await page
+ .getByTestId('image-preview-modal')
.locator('img')
- .nth(1)
+ .first()
.getAttribute('data-blob-id')) as string;
expect(blobId).toBeTruthy();
}
@@ -494,11 +506,12 @@ test('image should able to delete and when delete, it will move to previous/next
await title.click();
await page.keyboard.press('Enter');
await importImage(page, 'http://localhost:8081/large-image.png');
- await page.locator('img').first().dblclick();
+ await page.locator('affine-page-image').first().dblclick();
await page.waitForTimeout(500);
blobId = (await page
+ .getByTestId('image-preview-modal')
.locator('img')
- .nth(1)
+ .first()
.getAttribute('data-blob-id')) as string;
expect(blobId).toBeTruthy();
await closeImagePreviewModal(page);
@@ -510,7 +523,7 @@ test('image should able to delete and when delete, it will move to previous/next
await importImage(page, 'http://localhost:8081/affine-preview.png');
}
const locator = page.getByTestId('image-preview-modal');
- await page.locator('img').first().dblclick();
+ await page.locator('affine-page-image').first().dblclick();
await expect(locator).toBeVisible();
// ensure the new image was imported
await page.waitForTimeout(1000);
@@ -533,7 +546,7 @@ test('image should able to delete and when delete, it will move to previous/next
await page.keyboard.press('Enter');
await importImage(page, 'http://localhost:8081/affine-preview.png');
}
- await page.locator('img').first().dblclick();
+ await page.locator('affine-page-image').first().dblclick();
await locator.getByTestId('next-image-button').click();
await page.waitForTimeout(1000);
{
@@ -569,11 +582,12 @@ test('tooltips for all buttons should be visible when hovering', async ({
await title.click();
await page.keyboard.press('Enter');
await importImage(page, 'http://localhost:8081/large-image.png');
- await page.locator('img').first().dblclick();
+ await page.locator('affine-page-image').first().dblclick();
await page.waitForTimeout(500);
blobId = (await page
+ .getByTestId('image-preview-modal')
.locator('img')
- .nth(1)
+ .first()
.getAttribute('data-blob-id')) as string;
expect(blobId).toBeTruthy();
}
@@ -662,7 +676,7 @@ test('keypress esc should close the modal', async ({ page }) => {
await title.click();
await page.keyboard.press('Enter');
await importImage(page, 'http://localhost:8081/large-image.png');
- await page.locator('img').first().dblclick();
+ await page.locator('affine-page-image').first().dblclick();
const locator = page.getByTestId('image-preview-modal');
await expect(locator).toBeVisible();
await page.keyboard.press('Escape');
@@ -680,7 +694,7 @@ test('when mouse moves outside, the modal should be closed', async ({
await title.click();
await page.keyboard.press('Enter');
await importImage(page, 'http://localhost:8081/large-image.png');
- await page.locator('img').first().dblclick();
+ await page.locator('affine-page-image').first().dblclick();
const locator = page.getByTestId('image-preview-modal');
await expect(locator).toBeVisible();
// animation delay
@@ -701,14 +715,14 @@ test('caption should be visible and different styles were applied if image zoome
await title.click();
await page.keyboard.press('Enter');
await importImage(page, 'http://localhost:8081/large-image.png');
- await page.locator('img').first().hover();
+ await page.locator('affine-page-image').first().hover();
await page
.locator('.embed-editing-state')
.locator('icon-button')
.nth(1)
.click();
await page.getByPlaceholder('Write a caption').fill(sampleCaption);
- await page.locator('img').first().dblclick();
+ await page.locator('affine-page-image').first().dblclick();
const locator = page.getByTestId('image-preview-modal');
await expect(locator).toBeVisible();
await page.waitForTimeout(1000);
diff --git a/tests/kit/utils/sidebar.ts b/tests/kit/utils/sidebar.ts
index b9e685b0a5..7ac33a9a9d 100644
--- a/tests/kit/utils/sidebar.ts
+++ b/tests/kit/utils/sidebar.ts
@@ -12,6 +12,10 @@ export async function clickSideBarCurrentWorkspaceBanner(page: Page) {
return page.getByTestId('current-workspace').click();
}
+export async function clickSideBarUseAvatar(page: Page) {
+ return page.getByTestId('sidebar-user-avatar').click();
+}
+
export async function clickNewPageButton(page: Page) {
return page.getByTestId('sidebar-new-page-button').click();
}