feat(core): reorg workspace settings (#9718)

fix AF-2118
This commit is contained in:
pengx17
2025-01-22 03:11:27 +00:00
parent 83ed215f4a
commit defb0de4dd
31 changed files with 209 additions and 128 deletions

View File

@@ -1,5 +1,5 @@
import type { SettingTab } from '@affine/core/modules/dialogs/constant';
import type { ReactNode } from 'react';
import type { ReactElement } from 'react';
export interface SettingState {
activeTab: SettingTab;
@@ -9,6 +9,6 @@ export interface SettingState {
export interface SettingSidebarItem {
key: SettingTab;
title: string;
icon: ReactNode;
icon: ReactElement;
testId: string;
}

View File

@@ -2,14 +2,22 @@ import { useWorkspaceInfo } from '@affine/core/components/hooks/use-workspace-in
import type { SettingTab } from '@affine/core/modules/dialogs/constant';
import { WorkspaceService } from '@affine/core/modules/workspace';
import { useI18n } from '@affine/i18n';
import { PaymentIcon, PropertyIcon, SettingsIcon } from '@blocksuite/icons/rc';
import {
CollaborationIcon,
PaymentIcon,
PropertyIcon,
SaveIcon,
SettingsIcon,
} from '@blocksuite/icons/rc';
import { useService } from '@toeverything/infra';
import { useMemo } from 'react';
import type { SettingSidebarItem, SettingState } from '../types';
import { WorkspaceSettingBilling } from './billing';
import { WorkspaceSettingDetail } from './new-workspace-setting-detail';
import { MembersPanel } from './members';
import { WorkspaceSettingDetail } from './preference';
import { WorkspaceSettingProperties } from './properties';
import { WorkspaceSettingStorage } from './storage';
export const WorkspaceSetting = ({
activeTab,
@@ -22,16 +30,20 @@ export const WorkspaceSetting = ({
}) => {
switch (activeTab) {
case 'workspace:preference':
return <WorkspaceSettingDetail onCloseSetting={onCloseSetting} />;
case 'workspace:properties':
return <WorkspaceSettingProperties />;
case 'workspace:members':
return (
<WorkspaceSettingDetail
<MembersPanel
onCloseSetting={onCloseSetting}
onChangeSettingState={onChangeSettingState}
/>
);
case 'workspace:properties':
return <WorkspaceSettingProperties />;
case 'workspace:billing':
return <WorkspaceSettingBilling />;
case 'workspace:storage':
return <WorkspaceSettingStorage />;
default:
return null;
}
@@ -58,17 +70,27 @@ export const useWorkspaceSettingList = (): SettingSidebarItem[] => {
icon: <PropertyIcon />,
testId: 'workspace-setting:properties',
},
...(showBilling
? [
{
key: 'workspace:billing' as SettingTab,
title: t['com.affine.settings.workspace.billing'](),
icon: <PaymentIcon />,
testId: 'workspace-setting:billing',
},
]
: []),
];
{
key: 'workspace:members',
title: t['Members'](),
icon: <CollaborationIcon />,
testId: 'workspace-setting:members',
},
{
key: 'workspace:storage',
title: t['Storage'](),
icon: <SaveIcon />,
testId: 'workspace-setting:storage',
},
showBilling && {
key: 'workspace:billing' as SettingTab,
title: t['com.affine.settings.workspace.billing'](),
icon: <PaymentIcon />,
testId: 'workspace-setting:billing',
},
// todo(@pengx17): add selfhost's team license
].filter((item): item is SettingSidebarItem => !!item);
}, [showBilling, t]);
return items;

View File

@@ -24,7 +24,7 @@ import { ExportIcon } from '@blocksuite/icons/rc';
import { useLiveData, useService } from '@toeverything/infra';
import { useCallback, useEffect, useMemo, useState } from 'react';
import type { SettingState } from '../../../types';
import type { SettingState } from '../../types';
import { MemberList } from './member-list';
import * as styles from './styles.css';

View File

@@ -7,19 +7,22 @@ import { useI18n } from '@affine/i18n';
import { useService } from '@toeverything/infra';
import type { ReactElement } from 'react';
import type { SettingState } from '../../../types';
import type { SettingState } from '../../types';
import { EnableCloudPanel } from '../preference/enable-cloud';
import { CloudWorkspaceMembersPanel } from './cloud-members-panel';
import * as styles from './styles.css';
export const MembersPanel = ({
onChangeSettingState,
onCloseSetting,
}: {
onChangeSettingState: (settingState: SettingState) => void;
onCloseSetting: () => void;
}): ReactElement | null => {
const workspace = useService(WorkspaceService).workspace;
const isTeam = useWorkspaceInfo(workspace.meta)?.isTeam;
if (workspace.flavour === 'local') {
return <MembersPanelLocal />;
return <MembersPanelLocal onCloseSetting={onCloseSetting} />;
}
return (
<AffineErrorBoundary>
@@ -31,15 +34,22 @@ export const MembersPanel = ({
);
};
const MembersPanelLocal = () => {
const MembersPanelLocal = ({
onCloseSetting,
}: {
onCloseSetting: () => void;
}) => {
const t = useI18n();
return (
<Tooltip content={t['com.affine.settings.member-tooltip']()}>
<div className={styles.fakeWrapper}>
<SettingRow name={`${t['Members']()} (0)`} desc={t['Members hint']()}>
<Button>{t['Invite Members']()}</Button>
</SettingRow>
</div>
</Tooltip>
<div className={styles.localMembersPanel}>
<Tooltip content={t['com.affine.settings.member-tooltip']()}>
<div className={styles.fakeWrapper}>
<SettingRow name={`${t['Members']()} (0)`} desc={t['Members hint']()}>
<Button>{t['Invite Members']()}</Button>
</SettingRow>
</div>
</Tooltip>
<EnableCloudPanel onCloseSetting={onCloseSetting} />
</div>
);
};

View File

@@ -29,6 +29,12 @@ export const membersPanel = style({
justifyContent: 'space-between',
});
export const localMembersPanel = style({
gap: '24px',
display: 'flex',
flexDirection: 'column',
});
export const goUpgradeWrapper = style({
display: 'inline-flex',
alignItems: 'center',

View File

@@ -1,78 +0,0 @@
import {
SettingHeader,
SettingRow,
SettingWrapper,
} from '@affine/component/setting-components';
import { useWorkspaceInfo } from '@affine/core/components/hooks/use-workspace-info';
import { WorkspaceServerService } from '@affine/core/modules/cloud';
import { WorkspaceService } from '@affine/core/modules/workspace';
import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant';
import { useI18n } from '@affine/i18n';
import { FrameworkScope, useService } from '@toeverything/infra';
import { DeleteLeaveWorkspace } from './delete-leave-workspace';
import { EnableCloudPanel } from './enable-cloud';
import { DesktopExportPanel } from './export';
import { LabelsPanel } from './labels';
import { MembersPanel } from './members';
import { ProfilePanel } from './profile';
import { SharingPanel } from './sharing';
import { TemplateDocSetting } from './template';
import type { WorkspaceSettingDetailProps } from './types';
import { WorkspaceQuotaPanel } from './workspace-quota';
export const WorkspaceSettingDetail = ({
onCloseSetting,
onChangeSettingState,
}: WorkspaceSettingDetailProps) => {
const t = useI18n();
const workspace = useService(WorkspaceService).workspace;
const server = workspace?.scope.get(WorkspaceServerService).server;
const workspaceInfo = useWorkspaceInfo(workspace);
if (!workspace) {
return null;
}
return (
<FrameworkScope scope={server?.scope}>
<FrameworkScope scope={workspace.scope}>
<SettingHeader
title={t[`Workspace Settings with name`]({
name: workspaceInfo?.name ?? UNTITLED_WORKSPACE_NAME,
})}
subtitle={t['com.affine.settings.workspace.description']()}
/>
<SettingWrapper title={t['Info']()}>
<SettingRow
name={t['Workspace Profile']()}
desc={t['com.affine.settings.workspace.not-owner']()}
spreadCol={false}
>
<ProfilePanel />
<LabelsPanel />
</SettingRow>
</SettingWrapper>
<TemplateDocSetting />
<SettingWrapper title={t['com.affine.brand.affineCloud']()}>
<EnableCloudPanel onCloseSetting={onCloseSetting} />
{workspace.flavour !== 'local' && <WorkspaceQuotaPanel />}
{workspace.flavour !== 'local' && (
<MembersPanel onChangeSettingState={onChangeSettingState} />
)}
</SettingWrapper>
<SharingPanel />
{BUILD_CONFIG.isElectron && (
<SettingWrapper title={t['Storage and Export']()}>
<DesktopExportPanel workspace={workspace} />
</SettingWrapper>
)}
<SettingWrapper>
<DeleteLeaveWorkspace onCloseSetting={onCloseSetting} />
</SettingWrapper>
</FrameworkScope>
</FrameworkScope>
);
};

View File

@@ -1,6 +0,0 @@
import type { SettingState } from '../../types';
export interface WorkspaceSettingDetailProps {
onCloseSetting: () => void;
onChangeSettingState: (settingState: SettingState) => void;
}

View File

@@ -0,0 +1,59 @@
import {
SettingHeader,
SettingRow,
SettingWrapper,
} from '@affine/component/setting-components';
import { useWorkspaceInfo } from '@affine/core/components/hooks/use-workspace-info';
import { WorkspaceServerService } from '@affine/core/modules/cloud';
import { WorkspaceService } from '@affine/core/modules/workspace';
import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant';
import { useI18n } from '@affine/i18n';
import { FrameworkScope, useService } from '@toeverything/infra';
import { DeleteLeaveWorkspace } from './delete-leave-workspace';
import { EnableCloudPanel } from './enable-cloud';
import { LabelsPanel } from './labels';
import { ProfilePanel } from './profile';
import { SharingPanel } from './sharing';
import { TemplateDocSetting } from './template';
import type { WorkspaceSettingDetailProps } from './types';
export const WorkspaceSettingDetail = ({
onCloseSetting,
}: WorkspaceSettingDetailProps) => {
const t = useI18n();
const workspace = useService(WorkspaceService).workspace;
const server = workspace?.scope.get(WorkspaceServerService).server;
const workspaceInfo = useWorkspaceInfo(workspace);
return (
<FrameworkScope scope={server?.scope}>
<SettingHeader
title={t[`Workspace Settings with name`]({
name: workspaceInfo?.name ?? UNTITLED_WORKSPACE_NAME,
})}
subtitle={t['com.affine.settings.workspace.description']()}
/>
<SettingWrapper title={t['Info']()}>
<SettingRow
name={t['Workspace Profile']()}
desc={t['com.affine.settings.workspace.not-owner']()}
spreadCol={false}
>
<ProfilePanel />
<LabelsPanel />
{workspace.flavour === 'local' && (
<EnableCloudPanel onCloseSetting={onCloseSetting} />
)}
</SettingRow>
</SettingWrapper>
<TemplateDocSetting />
<SharingPanel />
<SettingWrapper>
<DeleteLeaveWorkspace onCloseSetting={onCloseSetting} />
</SettingWrapper>
</FrameworkScope>
);
};

View File

@@ -0,0 +1,3 @@
export interface WorkspaceSettingDetailProps {
onCloseSetting: () => void;
}

View File

@@ -0,0 +1,33 @@
import {
SettingHeader,
SettingWrapper,
} from '@affine/component/setting-components';
import { WorkspaceService } from '@affine/core/modules/workspace';
import { useI18n } from '@affine/i18n';
import { useService } from '@toeverything/infra';
import { DesktopExportPanel } from './export';
import { WorkspaceQuotaPanel } from './workspace-quota';
export const WorkspaceSettingStorage = () => {
const t = useI18n();
const workspace = useService(WorkspaceService).workspace;
return (
<>
<SettingHeader
title={t['Storage']()}
subtitle={t['com.affine.settings.workspace.storage.subtitle']()}
/>
{workspace.flavour !== 'local' && (
<SettingWrapper>
<WorkspaceQuotaPanel />
</SettingWrapper>
)}
{BUILD_CONFIG.isElectron && (
<SettingWrapper>
<DesktopExportPanel workspace={workspace} />
</SettingWrapper>
)}
</>
);
};

View File

@@ -0,0 +1,30 @@
import { cssVar } from '@toeverything/theme';
import { cssVarV2 } from '@toeverything/theme/v2';
import { globalStyle, style } from '@vanilla-extract/css';
export const storageProgressContainer = style({
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
});
export const storageProgressWrapper = style({
flexGrow: 1,
});
globalStyle(`${storageProgressWrapper} .storage-progress-desc`, {
fontSize: cssVar('fontXs'),
color: cssVarV2('text/secondary'),
height: '20px',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 2,
});
globalStyle(`${storageProgressWrapper} .storage-progress-bar-wrapper`, {
height: '8px',
borderRadius: '4px',
backgroundColor: cssVarV2('layer/background/hoverOverlay'),
overflow: 'hidden',
});
export const storageProgressBar = style({
height: '100%',
});

View File

@@ -11,7 +11,7 @@ export type SettingTab =
| 'experimental-features'
| 'editor'
| 'account'
| `workspace:${'preference' | 'properties' | 'billing' | 'license'}`;
| `workspace:${'preference' | 'properties' | 'members' | 'storage' | 'billing' | 'license'}`;
export type GLOBAL_DIALOG_SCHEMA = {
'create-workspace': (props: { serverId?: string; forcedCloud?: boolean }) => {