mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-22 03:33:06 +08:00
feat: add worksapce type label (#4045)
Co-authored-by: Alex Yang <himself65@outlook.com>
This commit is contained in:
@@ -14,6 +14,7 @@ import { useMemo } from 'react';
|
|||||||
import { useWorkspace } from '../../../hooks/use-workspace';
|
import { useWorkspace } from '../../../hooks/use-workspace';
|
||||||
import { DeleteLeaveWorkspace } from './delete-leave-workspace';
|
import { DeleteLeaveWorkspace } from './delete-leave-workspace';
|
||||||
import { ExportPanel } from './export';
|
import { ExportPanel } from './export';
|
||||||
|
import { LabelsPanel } from './labels';
|
||||||
import { MembersPanel } from './members';
|
import { MembersPanel } from './members';
|
||||||
import { ProfilePanel } from './profile';
|
import { ProfilePanel } from './profile';
|
||||||
import { PublishPanel } from './publish';
|
import { PublishPanel } from './publish';
|
||||||
@@ -69,6 +70,7 @@ export const WorkspaceSettingDetail = (props: WorkspaceSettingDetailProps) => {
|
|||||||
spreadCol={false}
|
spreadCol={false}
|
||||||
>
|
>
|
||||||
<ProfilePanel workspace={workspace} {...props} />
|
<ProfilePanel workspace={workspace} {...props} />
|
||||||
|
<LabelsPanel workspace={workspace} {...props} />
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
</SettingWrapper>
|
</SettingWrapper>
|
||||||
<SettingWrapper title={t['AFFiNE Cloud']()}>
|
<SettingWrapper title={t['AFFiNE Cloud']()}>
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
import type { AffineOfficialWorkspace } from '@affine/env/workspace';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
import { type WorkspaceSettingDetailProps } from './index';
|
||||||
|
import * as style from './style.css';
|
||||||
|
|
||||||
|
export interface LabelsPanelProps extends WorkspaceSettingDetailProps {
|
||||||
|
workspace: AffineOfficialWorkspace;
|
||||||
|
}
|
||||||
|
|
||||||
|
type WorkspaceStatus =
|
||||||
|
| 'local'
|
||||||
|
| 'syncCloud'
|
||||||
|
| 'syncDocker'
|
||||||
|
| 'selfHosted'
|
||||||
|
| 'joinedWorkspace'
|
||||||
|
| 'availableOffline'
|
||||||
|
| 'publishedToWeb';
|
||||||
|
|
||||||
|
type LabelProps = {
|
||||||
|
value: string;
|
||||||
|
background: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type LabelMap = {
|
||||||
|
[key in WorkspaceStatus]: LabelProps;
|
||||||
|
};
|
||||||
|
type labelConditionsProps = {
|
||||||
|
condition: boolean;
|
||||||
|
label: WorkspaceStatus;
|
||||||
|
};
|
||||||
|
const Label = ({ value, background }: LabelProps) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className={style.workspaceLabel} style={{ background: background }}>
|
||||||
|
{value}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export const LabelsPanel = ({ workspace, isOwner }: LabelsPanelProps) => {
|
||||||
|
const labelMap: LabelMap = useMemo(
|
||||||
|
() => ({
|
||||||
|
local: {
|
||||||
|
value: 'Local',
|
||||||
|
background: 'var(--affine-tag-orange)',
|
||||||
|
},
|
||||||
|
syncCloud: {
|
||||||
|
value: 'Sync with AFFiNE Cloud',
|
||||||
|
background: 'var(--affine-tag-blue)',
|
||||||
|
},
|
||||||
|
syncDocker: {
|
||||||
|
value: 'Sync with AFFiNE Docker',
|
||||||
|
background: 'var(--affine-tag-green)',
|
||||||
|
},
|
||||||
|
selfHosted: {
|
||||||
|
value: 'Self-Hosted Server',
|
||||||
|
background: 'var(--affine-tag-purple)',
|
||||||
|
},
|
||||||
|
joinedWorkspace: {
|
||||||
|
value: 'Joined Workspace',
|
||||||
|
background: 'var(--affine-tag-yellow)',
|
||||||
|
},
|
||||||
|
availableOffline: {
|
||||||
|
value: 'Available Offline',
|
||||||
|
background: 'var(--affine-tag-green)',
|
||||||
|
},
|
||||||
|
publishedToWeb: {
|
||||||
|
value: 'Published to Web',
|
||||||
|
background: 'var(--affine-tag-blue)',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const labelConditions: labelConditionsProps[] = [
|
||||||
|
{ condition: !isOwner, label: 'joinedWorkspace' },
|
||||||
|
{ condition: workspace.flavour === 'local', label: 'local' },
|
||||||
|
{ condition: workspace.flavour === 'affine-cloud', label: 'syncCloud' },
|
||||||
|
{
|
||||||
|
condition: workspace.flavour === 'affine-public',
|
||||||
|
label: 'publishedToWeb',
|
||||||
|
},
|
||||||
|
//TODO: add these labels
|
||||||
|
// { status==="synced", label: 'availableOffline' }
|
||||||
|
// { workspace.flavour === 'affine-Docker', label: 'syncDocker' }
|
||||||
|
// { workspace.flavour === 'self-hosted', label: 'selfHosted' }
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={style.labelWrapper}>
|
||||||
|
{labelConditions.map(
|
||||||
|
({ condition, label }) =>
|
||||||
|
condition && (
|
||||||
|
<Label
|
||||||
|
key={label}
|
||||||
|
value={labelMap[label].value}
|
||||||
|
background={labelMap[label].background}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -12,6 +12,15 @@ export const profileHandlerWrapper = style({
|
|||||||
marginLeft: '20px',
|
marginLeft: '20px',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const labelWrapper = style({
|
||||||
|
width: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginTop: '24px',
|
||||||
|
gap: '10px',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
});
|
||||||
|
|
||||||
export const avatarWrapper = style({
|
export const avatarWrapper = style({
|
||||||
width: '56px',
|
width: '56px',
|
||||||
height: '56px',
|
height: '56px',
|
||||||
@@ -146,3 +155,17 @@ export const label = style({
|
|||||||
color: 'var(--affine-text-secondary-color)',
|
color: 'var(--affine-text-secondary-color)',
|
||||||
marginBottom: '5px',
|
marginBottom: '5px',
|
||||||
});
|
});
|
||||||
|
export const workspaceLabel = style({
|
||||||
|
width: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
borderRadius: '6px',
|
||||||
|
padding: '2px 10px',
|
||||||
|
border: '1px solid var(--affine-white-30)',
|
||||||
|
fontSize: 'var(--affine-font-xs)',
|
||||||
|
color: 'var(--affine-text-primary-color)',
|
||||||
|
lineHeight: '20px',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
});
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export const header = style({
|
|||||||
position: 'relative',
|
position: 'relative',
|
||||||
padding: '0 16px',
|
padding: '0 16px',
|
||||||
minHeight: '52px',
|
minHeight: '52px',
|
||||||
|
background: 'var(--affine-background-primary-color)',
|
||||||
borderBottom: '1px solid var(--affine-border-color)',
|
borderBottom: '1px solid var(--affine-border-color)',
|
||||||
zIndex: 2,
|
zIndex: 2,
|
||||||
selectors: {
|
selectors: {
|
||||||
|
|||||||
@@ -110,9 +110,9 @@ const CloudWorkSpaceList = ({
|
|||||||
<>
|
<>
|
||||||
<StyledModalHeader>
|
<StyledModalHeader>
|
||||||
<StyledModalHeaderLeft>
|
<StyledModalHeaderLeft>
|
||||||
<StyledModalTitle>
|
<StyledWorkspaceFlavourTitle>
|
||||||
{t['com.affine.workspace.cloud']()}
|
{t['com.affine.workspace.cloud']()}
|
||||||
</StyledModalTitle>
|
</StyledWorkspaceFlavourTitle>
|
||||||
</StyledModalHeaderLeft>
|
</StyledModalHeaderLeft>
|
||||||
</StyledModalHeader>
|
</StyledModalHeader>
|
||||||
<StyledModalContent>
|
<StyledModalContent>
|
||||||
|
|||||||
@@ -236,9 +236,8 @@ export const StyledModalBody = styled('div')(() => {
|
|||||||
|
|
||||||
export const StyledWorkspaceFlavourTitle = styled('div')(() => {
|
export const StyledWorkspaceFlavourTitle = styled('div')(() => {
|
||||||
return {
|
return {
|
||||||
fontSize: '12px',
|
fontSize: 'var(--affine-font-xs)',
|
||||||
fontWeight: 600,
|
|
||||||
color: 'var(--affine-text-secondary-color)',
|
color: 'var(--affine-text-secondary-color)',
|
||||||
lineHeight: '20px',
|
marginBottom: '4px',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { displayFlex, textEllipsis } from '@affine/component';
|
import { displayFlex, textEllipsis } from '@affine/component';
|
||||||
import { styled } from '@affine/component';
|
import { styled } from '@affine/component';
|
||||||
export const StyledSelectorContainer = styled('div')(() => {
|
export const StyledSelectorContainer = styled('div')(({
|
||||||
|
disableHoverBackground,
|
||||||
|
}: {
|
||||||
|
disableHoverBackground: boolean;
|
||||||
|
}) => {
|
||||||
return {
|
return {
|
||||||
height: '58px',
|
height: '58px',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -10,7 +14,7 @@ export const StyledSelectorContainer = styled('div')(() => {
|
|||||||
color: 'var(--affine-text-primary-color)',
|
color: 'var(--affine-text-primary-color)',
|
||||||
':hover': {
|
':hover': {
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
background: 'var(--affine-hover-color)',
|
background: disableHoverBackground ? '' : 'var(--affine-hover-color)',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -38,10 +42,17 @@ export const StyledWorkspaceStatus = styled('div')(() => {
|
|||||||
fontSize: 'var(--affine-font-sm)',
|
fontSize: 'var(--affine-font-sm)',
|
||||||
color: 'var(--affine-text-secondary-color)',
|
color: 'var(--affine-text-secondary-color)',
|
||||||
userSelect: 'none',
|
userSelect: 'none',
|
||||||
|
padding: '0 4px',
|
||||||
|
gap: '4px',
|
||||||
|
zIndex: '1',
|
||||||
svg: {
|
svg: {
|
||||||
color: 'var(--affine-icon-color)',
|
color: 'var(--affine-icon-color)',
|
||||||
fontSize: 'var(--affine-font-base)',
|
fontSize: 'var(--affine-font-base)',
|
||||||
marginRight: '4px',
|
},
|
||||||
|
':hover': {
|
||||||
|
cursor: 'pointer',
|
||||||
|
borderRadius: '4px',
|
||||||
|
background: 'var(--affine-hover-color)',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
+65
-11
@@ -1,9 +1,16 @@
|
|||||||
import { WorkspaceAvatar } from '@affine/component/workspace-avatar';
|
import { WorkspaceAvatar } from '@affine/component/workspace-avatar';
|
||||||
import { CloudWorkspaceIcon, LocalWorkspaceIcon } from '@blocksuite/icons';
|
import {
|
||||||
|
CloudWorkspaceIcon,
|
||||||
|
LocalWorkspaceIcon,
|
||||||
|
NoNetworkIcon,
|
||||||
|
} from '@blocksuite/icons';
|
||||||
|
import { Tooltip } from '@toeverything/components/tooltip';
|
||||||
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
|
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
|
||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
import { useCallback } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
import { useCurrentLoginStatus } from '../../../../hooks/affine/use-current-login-status';
|
||||||
|
import { useSystemOnline } from '../../../../hooks/use-system-online';
|
||||||
import type { AllWorkspace } from '../../../../shared';
|
import type { AllWorkspace } from '../../../../shared';
|
||||||
import { workspaceAvatarStyle } from './index.css';
|
import { workspaceAvatarStyle } from './index.css';
|
||||||
import {
|
import {
|
||||||
@@ -29,7 +36,8 @@ export const WorkspaceSelector = ({
|
|||||||
const [name] = useBlockSuiteWorkspaceName(
|
const [name] = useBlockSuiteWorkspaceName(
|
||||||
currentWorkspace.blockSuiteWorkspace
|
currentWorkspace.blockSuiteWorkspace
|
||||||
);
|
);
|
||||||
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
|
const [container, setContainer] = useState<HTMLDivElement | null>(null);
|
||||||
// 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?
|
// TODO: Delete this?
|
||||||
@@ -43,13 +51,48 @@ export const WorkspaceSelector = ({
|
|||||||
},
|
},
|
||||||
[onClick]
|
[onClick]
|
||||||
);
|
);
|
||||||
|
const loginStatus = useCurrentLoginStatus();
|
||||||
|
const isOnline = useSystemOnline();
|
||||||
|
const content = useMemo(() => {
|
||||||
|
if (!isOnline) {
|
||||||
|
return 'Disconnected, please check your network connection';
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
loginStatus === 'authenticated' &&
|
||||||
|
currentWorkspace.flavour !== 'local'
|
||||||
|
) {
|
||||||
|
return 'Sync with AFFiNE Cloud';
|
||||||
|
}
|
||||||
|
return 'Saved locally';
|
||||||
|
}, [currentWorkspace.flavour, isOnline, loginStatus]);
|
||||||
|
|
||||||
|
const WorkspaceStatus = () => {
|
||||||
|
if (!isOnline) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<NoNetworkIcon />
|
||||||
|
Offline
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{currentWorkspace.flavour === 'local' ? (
|
||||||
|
<LocalWorkspaceIcon />
|
||||||
|
) : (
|
||||||
|
<CloudWorkspaceIcon />
|
||||||
|
)}
|
||||||
|
{currentWorkspace.flavour === 'local' ? 'Local' : 'AFFiNE Cloud'}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<StyledSelectorContainer
|
<StyledSelectorContainer
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
|
disableHoverBackground={isHovered}
|
||||||
data-testid="current-workspace"
|
data-testid="current-workspace"
|
||||||
id="current-workspace"
|
id="current-workspace"
|
||||||
>
|
>
|
||||||
@@ -63,14 +106,25 @@ export const WorkspaceSelector = ({
|
|||||||
<StyledWorkspaceName data-testid="workspace-name">
|
<StyledWorkspaceName data-testid="workspace-name">
|
||||||
{name}
|
{name}
|
||||||
</StyledWorkspaceName>
|
</StyledWorkspaceName>
|
||||||
<StyledWorkspaceStatus>
|
<div style={{ display: 'flex' }}>
|
||||||
{currentWorkspace.flavour === 'local' ? (
|
<Tooltip
|
||||||
<LocalWorkspaceIcon />
|
content={content}
|
||||||
) : (
|
portalOptions={{
|
||||||
<CloudWorkspaceIcon />
|
container,
|
||||||
)}
|
}}
|
||||||
{currentWorkspace.flavour === 'local' ? 'Local' : 'AFFiNE Cloud'}
|
>
|
||||||
</StyledWorkspaceStatus>
|
<StyledWorkspaceStatus
|
||||||
|
onMouseEnter={() => {
|
||||||
|
setIsHovered(true);
|
||||||
|
}}
|
||||||
|
ref={setContainer}
|
||||||
|
onMouseLeave={() => setIsHovered(false)}
|
||||||
|
onClick={e => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<WorkspaceStatus />
|
||||||
|
</StyledWorkspaceStatus>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
</StyledSelectorWrapper>
|
</StyledSelectorWrapper>
|
||||||
</StyledSelectorContainer>
|
</StyledSelectorContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export const navWrapperStyle = style({
|
|||||||
width: navWidthVar,
|
width: navWidthVar,
|
||||||
minWidth: navWidthVar,
|
minWidth: navWidthVar,
|
||||||
height: '100%',
|
height: '100%',
|
||||||
zIndex: 1,
|
zIndex: 3,
|
||||||
paddingBottom: '8px',
|
paddingBottom: '8px',
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
'@media': {
|
'@media': {
|
||||||
|
|||||||
Reference in New Issue
Block a user