refactor: workspace list (#4432)

This commit is contained in:
JimmFly
2023-09-22 15:02:31 +08:00
committed by GitHub
parent 092e2e0a3d
commit edd7d00104
20 changed files with 749 additions and 728 deletions

View File

@@ -1,8 +1,11 @@
import { WorkspaceFlavour } from '@affine/env/workspace';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
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 { Divider } from '@toeverything/components/divider';
import { Tooltip } from '@toeverything/components/tooltip';
import { useBlockSuiteWorkspaceAvatarUrl } from '@toeverything/hooks/use-block-suite-workspace-avatar-url';
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
import { useStaticBlockSuiteWorkspace } from '@toeverything/infra/__internal__/react';
@@ -10,46 +13,56 @@ import { useCallback } from 'react';
import {
StyledCard,
StyledIconContainer,
StyledSettingLink,
StyledWorkspaceInfo,
StyledWorkspaceTitle,
StyledWorkspaceTitleArea,
StyledWorkspaceType,
StyledWorkspaceTypeEllipse,
StyledWorkspaceTypeText,
} from './styles';
export interface WorkspaceTypeProps {
flavour: WorkspaceFlavour;
isOwner: boolean;
}
const WorkspaceType = ({ flavour }: WorkspaceTypeProps) => {
const WorkspaceType = ({ flavour, isOwner }: WorkspaceTypeProps) => {
const t = useAFFiNEI18N();
// fixme: cloud regression
const isOwner = true;
if (flavour === WorkspaceFlavour.LOCAL) {
return (
<p
style={{ fontSize: '10px' }}
title={t['com.affine.workspaceType.local']()}
>
<span>{t['com.affine.workspaceType.local']()}</span>
</p>
<StyledWorkspaceType>
<StyledWorkspaceTypeEllipse />
<StyledWorkspaceTypeText>{t['Local']()}</StyledWorkspaceTypeText>
</StyledWorkspaceType>
);
}
return isOwner ? (
<p
style={{ fontSize: '10px' }}
title={t['com.affine.workspaceType.cloud']()}
>
<span>{t['com.affine.workspaceType.cloud']()}</span>
</p>
<StyledWorkspaceType>
<StyledWorkspaceTypeEllipse cloud={true} />
<StyledWorkspaceTypeText>
{t['com.affine.brand.affineCloud']()}
</StyledWorkspaceTypeText>
</StyledWorkspaceType>
) : (
<p
style={{ fontSize: '10px' }}
title={t['com.affine.workspaceType.joined']()}
>
<span>{t['com.affine.workspaceType.joined']()}</span>
</p>
<StyledWorkspaceType>
<StyledWorkspaceTypeEllipse cloud={true} />
<StyledWorkspaceTypeText>
{t['com.affine.brand.affineCloud']()}
</StyledWorkspaceTypeText>
<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;
onClick: (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 = ({
onClick,
onSettingClick,
currentWorkspaceId,
meta,
isOwner = true,
}: WorkspaceCardProps) => {
// const t = useAFFiNEI18N();
const workspace = useStaticBlockSuiteWorkspace(meta.id);
const [name] = useBlockSuiteWorkspaceName(workspace);
const [workspaceAvatar] = useBlockSuiteWorkspaceAvatarUrl(workspace);
return (
<StyledCard
data-testid="workspace-card"
@@ -85,6 +114,7 @@ export const WorkspaceCard = ({
<StyledWorkspaceTitle>{name}</StyledWorkspaceTitle>
<StyledSettingLink
size="small"
className="setting-entry"
onClick={e => {
e.stopPropagation();
@@ -92,17 +122,10 @@ export const WorkspaceCard = ({
}}
withoutHoverStyle={true}
>
<SettingsIcon style={{ margin: '0px' }} />
<SettingsIcon />
</StyledSettingLink>
</StyledWorkspaceTitleArea>
{/* {meta.flavour === WorkspaceFlavour.LOCAL && (
<p title={t['com.affine.workspaceType.offline']()}>
<LocalDataIcon />
<WorkspaceType flavour={meta.flavour} />
</p>
)} */}
<WorkspaceType flavour={meta.flavour} />
<WorkspaceType isOwner={isOwner} flavour={meta.flavour} />
</StyledWorkspaceInfo>
</StyledCard>
);

View File

@@ -5,30 +5,16 @@ import { displayFlex, styled, textEllipsis } from '../../../styles';
export const StyledWorkspaceInfo = styled('div')(() => {
return {
marginLeft: '12px',
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),
},
width: '100%',
};
});
export const StyledWorkspaceTitle = styled('div')(() => {
return {
fontSize: 'var(--affine-font-base)',
fontWeight: 600,
lineHeight: '24px',
maxWidth: '200px',
fontSize: 'var(--affine-font-sm)',
fontWeight: 700,
lineHeight: '22px',
maxWidth: '190px',
color: 'var(--affine-text-primary-color)',
...textEllipsis(1),
};
@@ -38,13 +24,12 @@ export const StyledCard = styled('div')<{
active?: boolean;
}>(({ active }) => {
const borderColor = active ? 'var(--affine-primary-color)' : 'transparent';
const backgroundColor = active ? 'var(--affine-white)' : 'transparent';
const backgroundColor = active ? 'var(--affine-white-30)' : 'transparent';
return {
width: '280px',
height: '58px',
width: '100%',
cursor: 'pointer',
padding: '12px',
borderRadius: '12px',
borderRadius: '8px',
border: `1px solid ${borderColor}`,
...displayFlex('flex-start', 'flex-start'),
transition: 'background .2s',
@@ -91,8 +76,8 @@ export const StyledModalHeader = styled('div')(() => {
export const StyledSettingLink = styled(IconButton)(() => {
return {
position: 'absolute',
right: '6px',
bottom: '6px',
right: '10px',
top: '10px',
opacity: 0,
borderRadius: '4px',
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 {
fontSize: 10,
...displayFlex('flex-start', 'center'),
width: '100%',
height: '20px',
};
});
@@ -116,3 +103,35 @@ export const StyledWorkspaceTitleArea = styled('div')(() => {
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)',
};
});

View File

@@ -16,9 +16,12 @@ import {
} from '@dnd-kit/modifiers';
import { arrayMove, SortableContext, useSortable } from '@dnd-kit/sortable';
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';
export interface WorkspaceListProps {
@@ -28,16 +31,25 @@ export interface WorkspaceListProps {
onClick: (workspaceId: string) => void;
onSettingClick: (workspaceId: string) => void;
onDragEnd: (event: DragEndEvent) => void;
useIsWorkspaceOwner?: (workspaceId: string) => boolean;
}
interface SortableWorkspaceItemProps extends Omit<WorkspaceListProps, 'items'> {
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 } =
useSortable({
id: props.item.id,
id: item.id,
});
const style: CSSProperties = useMemo(
() => ({
@@ -45,11 +57,12 @@ const SortableWorkspaceItem = (props: SortableWorkspaceItemProps) => {
? `translate3d(${transform.x}px, ${transform.y}px, 0)`
: undefined,
transition,
pointerEvents: props.disabled ? 'none' : undefined,
opacity: props.disabled ? 0.6 : undefined,
pointerEvents: disabled ? 'none' : undefined,
opacity: disabled ? 0.6 : undefined,
}),
[props.disabled, transform, transition]
[disabled, transform, transition]
);
const isOwner = useIsWorkspaceOwner?.(item.id);
return (
<div
className={workspaceItemStyle}
@@ -60,10 +73,11 @@ const SortableWorkspaceItem = (props: SortableWorkspaceItemProps) => {
{...listeners}
>
<WorkspaceCard
currentWorkspaceId={props.currentWorkspaceId}
meta={props.item}
onClick={props.onClick}
onSettingClick={props.onSettingClick}
currentWorkspaceId={currentWorkspaceId}
meta={item}
onClick={onClick}
onSettingClick={onSettingClick}
isOwner={isOwner}
/>
</div>
);
@@ -106,7 +120,9 @@ export const WorkspaceList = (props: WorkspaceListProps) => {
<DndContext sensors={sensors} onDragEnd={onDragEnd} modifiers={modifiers}>
<SortableContext items={optimisticList}>
{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>
</DndContext>

View File

@@ -11,6 +11,7 @@ export type ScrollableContainerProps = {
className?: string;
viewPortClassName?: string;
styles?: React.CSSProperties;
scrollBarClassName?: string;
};
export const ScrollableContainer = ({
@@ -20,6 +21,7 @@ export const ScrollableContainer = ({
className,
styles: _styles,
viewPortClassName,
scrollBarClassName,
}: PropsWithChildren<ScrollableContainerProps>) => {
const [hasScrollTop, ref] = useHasScrollTop();
return (
@@ -39,7 +41,7 @@ export const ScrollableContainer = ({
</ScrollArea.Viewport>
<ScrollArea.Scrollbar
orientation="vertical"
className={clsx(styles.scrollbar, {
className={clsx(styles.scrollbar, scrollBarClassName, {
[styles.TableScrollbar]: inTableView,
})}
>