refactor(core): move setting dialog to workspace scope (#9706)

This commit is contained in:
pengx17
2025-01-15 13:00:06 +00:00
parent 9df338274d
commit b1896746f9
32 changed files with 248 additions and 277 deletions

View File

@@ -2,18 +2,18 @@ import type { useI18n } from '@affine/i18n';
import { track } from '@affine/track';
import { ContactWithUsIcon, NewIcon } from '@blocksuite/icons/rc';
import type { GlobalDialogService } from '../modules/dialogs';
import type { WorkspaceDialogService } from '../modules/dialogs';
import type { UrlService } from '../modules/url';
import { registerAffineCommand } from './registry';
export function registerAffineHelpCommands({
t,
urlService,
globalDialogService,
workspaceDialogService,
}: {
t: ReturnType<typeof useI18n>;
urlService: UrlService;
globalDialogService: GlobalDialogService;
workspaceDialogService: WorkspaceDialogService;
}) {
const unsubs: Array<() => void> = [];
unsubs.push(
@@ -36,7 +36,7 @@ export function registerAffineHelpCommands({
label: t['com.affine.cmdk.affine.contact-us'](),
run() {
track.$.cmdk.help.contactUs();
globalDialogService.open('setting', {
workspaceDialogService.open('setting', {
activeTab: 'about',
});
},

View File

@@ -6,7 +6,7 @@ import type { createStore } from 'jotai';
import { openWorkspaceListModalAtom } from '../components/atoms';
import type { useNavigateHelper } from '../components/hooks/use-navigate-helper';
import type { GlobalDialogService } from '../modules/dialogs';
import type { WorkspaceDialogService } from '../modules/dialogs';
import { registerAffineCommand } from './registry';
export function registerAffineNavigationCommands({
@@ -14,13 +14,13 @@ export function registerAffineNavigationCommands({
store,
docCollection,
navigationHelper,
globalDialogService,
workspaceDialogService,
}: {
t: ReturnType<typeof useI18n>;
store: ReturnType<typeof createStore>;
navigationHelper: ReturnType<typeof useNavigateHelper>;
docCollection: Workspace;
globalDialogService: GlobalDialogService;
workspaceDialogService: WorkspaceDialogService;
}) {
const unsubs: Array<() => void> = [];
unsubs.push(
@@ -96,7 +96,7 @@ export function registerAffineNavigationCommands({
keyBinding: '$mod+,',
run() {
track.$.cmdk.settings.openSettings();
globalDialogService.open('setting', {
workspaceDialogService.open('setting', {
activeTab: 'appearance',
});
},
@@ -111,7 +111,7 @@ export function registerAffineNavigationCommands({
label: t['com.affine.cmdk.affine.navigation.open-account-settings'](),
run() {
track.$.cmdk.settings.openSettings({ to: 'account' });
globalDialogService.open('setting', {
workspaceDialogService.open('setting', {
activeTab: 'account',
});
},

View File

@@ -1,6 +1,6 @@
import { Button, FlexWrapper, notify } from '@affine/component';
import { SubscriptionService } from '@affine/core/modules/cloud';
import { GlobalDialogService } from '@affine/core/modules/dialogs';
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
import { EditorService } from '@affine/core/modules/editor';
import { useI18n } from '@affine/i18n';
import { track } from '@affine/track';
@@ -50,18 +50,18 @@ export const AIOnboardingEdgeless = () => {
const notifyId = useLiveData(edgelessNotifyId$);
const generalAIOnboardingOpened = useLiveData(showAIOnboardingGeneral$);
const aiSubscription = useLiveData(subscriptionService.subscription.ai$);
const globalDialogService = useService(GlobalDialogService);
const workspaceDialogService = useService(WorkspaceDialogService);
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const mode = useLiveData(editorService.editor.mode$);
const goToPricingPlans = useCallback(() => {
track.$.aiOnboarding.dialog.viewPlans();
globalDialogService.open('setting', {
workspaceDialogService.open('setting', {
activeTab: 'plans',
scrollAnchor: 'aiPricingPlan',
});
}, [globalDialogService]);
}, [workspaceDialogService]);
useEffect(() => {
if (generalAIOnboardingOpened) return;

View File

@@ -1,7 +1,7 @@
import { Button, IconButton, Modal } from '@affine/component';
import { useBlurRoot } from '@affine/core/components/hooks/use-blur-root';
import { AuthService, SubscriptionService } from '@affine/core/modules/cloud';
import { GlobalDialogService } from '@affine/core/modules/dialogs';
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
import { Trans, useI18n } from '@affine/i18n';
import { track } from '@affine/track';
import { ArrowLeftSmallIcon } from '@blocksuite/icons/rc';
@@ -95,7 +95,7 @@ export const AIOnboardingGeneral = () => {
const aiSubscription = useLiveData(subscriptionService.subscription.ai$);
const [index, setIndex] = useState(0);
const list = useMemo(() => getPlayList(t), [t]);
const globalDialogService = useService(GlobalDialogService);
const workspaceDialogService = useService(WorkspaceDialogService);
const readyToOpen = isLoggedIn;
useBlurRoot(open && readyToOpen);
@@ -110,13 +110,13 @@ export const AIOnboardingGeneral = () => {
toggleGeneralAIOnboarding(false);
}, []);
const goToPricingPlans = useCallback(() => {
globalDialogService.open('setting', {
workspaceDialogService.open('setting', {
activeTab: 'plans',
scrollAnchor: 'aiPricingPlan',
});
track.$.aiOnboarding.dialog.viewPlans();
closeAndDismiss();
}, [closeAndDismiss, globalDialogService]);
}, [closeAndDismiss, workspaceDialogService]);
const onPrev = useCallback(() => {
setIndex(i => Math.max(0, i - 1));
}, []);

View File

@@ -2,7 +2,7 @@ import { Loading, Scrollable } from '@affine/component';
import { EditorLoading } from '@affine/component/page-detail-skeleton';
import { Button, IconButton } from '@affine/component/ui/button';
import { Modal, useConfirmModal } from '@affine/component/ui/modal';
import { GlobalDialogService } from '@affine/core/modules/dialogs';
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta';
import { EditorService } from '@affine/core/modules/editor';
import { WorkspacePermissionService } from '@affine/core/modules/permissions';
@@ -187,18 +187,18 @@ const PlanPrompt = () => {
}, [permissionService]);
const [planPromptClosed, setPlanPromptClosed] = useAtom(planPromptClosedAtom);
const globalDialogService = useService(GlobalDialogService);
const workspaceDialogService = useService(WorkspaceDialogService);
const closeFreePlanPrompt = useCallback(() => {
setPlanPromptClosed(true);
}, [setPlanPromptClosed]);
const onClickUpgrade = useCallback(() => {
globalDialogService.open('setting', {
workspaceDialogService.open('setting', {
activeTab: 'plans',
scrollAnchor: 'cloudPricingPlan',
});
track.$.docHistory.$.viewPlans();
}, [globalDialogService]);
}, [workspaceDialogService]);
const t = useI18n();

View File

@@ -1,7 +1,7 @@
import { ConfirmModal } from '@affine/component/ui/modal';
import { openQuotaModalAtom } from '@affine/core/components/atoms';
import { UserQuotaService } from '@affine/core/modules/cloud';
import { GlobalDialogService } from '@affine/core/modules/dialogs';
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
import { WorkspacePermissionService } from '@affine/core/modules/permissions';
import { WorkspaceQuotaService } from '@affine/core/modules/quota';
import { WorkspaceService } from '@affine/core/modules/workspace';
@@ -42,16 +42,16 @@ export const CloudQuotaModal = () => {
)
);
const globalDialogService = useService(GlobalDialogService);
const workspaceDialogService = useService(WorkspaceDialogService);
const handleUpgradeConfirm = useCallback(() => {
globalDialogService.open('setting', {
workspaceDialogService.open('setting', {
activeTab: 'plans',
scrollAnchor: 'cloudPricingPlan',
});
track.$.paywall.storage.viewPlans();
setOpen(false);
}, [globalDialogService, setOpen]);
}, [workspaceDialogService, setOpen]);
const description = useMemo(() => {
if (userQuota && isOwner) {

View File

@@ -7,7 +7,7 @@ import {
} from '@affine/core/components/hooks/affine/use-share-url';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { ServerService } from '@affine/core/modules/cloud';
import { GlobalDialogService } from '@affine/core/modules/dialogs';
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
import { EditorService } from '@affine/core/modules/editor';
import { WorkspacePermissionService } from '@affine/core/modules/permissions';
import { ShareInfoService } from '@affine/core/modules/share-doc';
@@ -81,13 +81,13 @@ export const AFFiNESharePage = (props: ShareMenuProps) => {
const permissionService = useService(WorkspacePermissionService);
const isOwner = useLiveData(permissionService.permission.isOwner$);
const globalDialogService = useService(GlobalDialogService);
const workspaceDialogService = useService(WorkspaceDialogService);
const onOpenWorkspaceSettings = useCallback(() => {
globalDialogService.open('setting', {
workspaceDialogService.open('setting', {
activeTab: 'workspace:preference',
});
}, [globalDialogService]);
}, [workspaceDialogService]);
const onClickAnyoneReadOnlyShare = useAsyncCallback(async () => {
if (isSharedPage) {

View File

@@ -1,6 +1,9 @@
import { AppSidebarService } from '@affine/core/modules/app-sidebar';
import { DesktopApiService } from '@affine/core/modules/desktop-api';
import { GlobalDialogService } from '@affine/core/modules/dialogs';
import {
GlobalDialogService,
WorkspaceDialogService,
} from '@affine/core/modules/dialogs';
import { I18nService } from '@affine/core/modules/i18n';
import { UrlService } from '@affine/core/modules/url';
import { WorkspaceService } from '@affine/core/modules/workspace';
@@ -76,6 +79,7 @@ export function useRegisterWorkspaceCommands() {
const [editor] = useActiveBlocksuiteEditor();
const cmdkQuickSearchService = useService(CMDKQuickSearchService);
const editorSettingService = useService(EditorSettingService);
const workspaceDialogService = useService(WorkspaceDialogService);
const globalDialogService = useService(GlobalDialogService);
const appSidebarService = useService(AppSidebarService);
const i18n = useService(I18nService).i18n;
@@ -115,7 +119,7 @@ export function useRegisterWorkspaceCommands() {
t,
docCollection: currentWorkspace.docCollection,
navigationHelper,
globalDialogService,
workspaceDialogService,
});
return () => {
@@ -127,6 +131,7 @@ export function useRegisterWorkspaceCommands() {
currentWorkspace.docCollection,
navigationHelper,
globalDialogService,
workspaceDialogService,
]);
// register AffineSettingsCommands
@@ -182,11 +187,11 @@ export function useRegisterWorkspaceCommands() {
const unsub = registerAffineHelpCommands({
t,
urlService,
globalDialogService,
workspaceDialogService,
});
return () => {
unsub();
};
}, [t, globalDialogService, urlService]);
}, [t, globalDialogService, urlService, workspaceDialogService]);
}

View File

@@ -1,5 +1,5 @@
import { notify } from '@affine/component';
import { GlobalDialogService } from '@affine/core/modules/dialogs';
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
import { WorkspacePermissionService } from '@affine/core/modules/permissions';
import { WorkspaceService } from '@affine/core/modules/workspace';
import { useI18n } from '@affine/i18n';
@@ -20,13 +20,13 @@ export const OverCapacityNotification = () => {
permissionService.permission.revalidate();
}, [permissionService]);
const globalDialogService = useService(GlobalDialogService);
const workspaceDialogService = useService(WorkspaceDialogService);
const jumpToPricePlan = useCallback(() => {
globalDialogService.open('setting', {
workspaceDialogService.open('setting', {
activeTab: 'plans',
scrollAnchor: 'cloudPricingPlan',
});
}, [globalDialogService]);
}, [workspaceDialogService]);
// debounce sync engine status
useEffect(() => {

View File

@@ -14,7 +14,10 @@ import {
FetchService,
GraphQLService,
} from '@affine/core/modules/cloud';
import { GlobalDialogService } from '@affine/core/modules/dialogs';
import {
GlobalDialogService,
WorkspaceDialogService,
} from '@affine/core/modules/dialogs';
import { DocsService } from '@affine/core/modules/doc';
import { EditorSettingService } from '@affine/core/modules/editor-setting';
import { useRegisterNavigationCommands } from '@affine/core/modules/navigation/view/use-register-navigation-commands';
@@ -123,11 +126,12 @@ export const WorkspaceSideEffects = () => {
workbench,
]);
const workspaceDialogService = useService(WorkspaceDialogService);
const globalDialogService = useService(GlobalDialogService);
useEffect(() => {
const disposable = AIProvider.slots.requestUpgradePlan.on(() => {
globalDialogService.open('setting', {
workspaceDialogService.open('setting', {
activeTab: 'billing',
});
track.$.paywall.aiAction.viewPlans();
@@ -135,7 +139,7 @@ export const WorkspaceSideEffects = () => {
return () => {
disposable.dispose();
};
}, [globalDialogService]);
}, [workspaceDialogService]);
const graphqlService = useService(GraphQLService);
const eventSourceService = useService(EventSourceService);
@@ -158,9 +162,10 @@ export const WorkspaceSideEffects = () => {
}, [
eventSourceService,
fetchService,
globalDialogService,
workspaceDialogService,
graphqlService,
networkSearchService,
globalDialogService,
]);
useRegisterWorkspaceCommands();

View File

@@ -1,5 +1,5 @@
import { Tooltip } from '@affine/component/ui/tooltip';
import { GlobalDialogService } from '@affine/core/modules/dialogs';
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
import type { SettingTab } from '@affine/core/modules/dialogs/constant';
import { GlobalContextService } from '@affine/core/modules/global-context';
import { UrlService } from '@affine/core/modules/url';
@@ -36,18 +36,18 @@ export const HelpIsland = () => {
});
const docId = useLiveData(globalContextService.globalContext.docId.$);
const docMode = useLiveData(globalContextService.globalContext.docMode.$);
const globalDialogService = useService(GlobalDialogService);
const workspaceDialogService = useService(WorkspaceDialogService);
const [spread, setShowSpread] = useState(false);
const t = useI18n();
const openSettingModal = useCallback(
(tab: SettingTab) => {
setShowSpread(false);
globalDialogService.open('setting', {
workspaceDialogService.open('setting', {
activeTab: tab,
});
},
[globalDialogService]
[workspaceDialogService]
);
const openAbout = useCallback(
() => openSettingModal('about'),

View File

@@ -1,3 +1,4 @@
// Import is already correct, no changes needed
import {
AddPageButton,
AppDownloadButton,
@@ -10,10 +11,7 @@ import {
SidebarScrollableContainer,
} from '@affine/core/modules/app-sidebar/views';
import { ExternalMenuLinkItem } from '@affine/core/modules/app-sidebar/views/menu-item/external-menu-link-item';
import {
GlobalDialogService,
WorkspaceDialogService,
} from '@affine/core/modules/dialogs';
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
import {
ExplorerCollections,
ExplorerFavorites,
@@ -95,7 +93,6 @@ export const RootAppSidebar = memo((): ReactElement => {
CMDKQuickSearchService,
});
const t = useI18n();
const globalDialogService = useService(GlobalDialogService);
const workspaceDialogService = useService(WorkspaceDialogService);
const workbench = workbenchService.workbench;
const onOpenQuickSearchModal = useCallback(() => {
@@ -103,11 +100,11 @@ export const RootAppSidebar = memo((): ReactElement => {
}, [cMDKQuickSearchService]);
const onOpenSettingModal = useCallback(() => {
globalDialogService.open('setting', {
workspaceDialogService.open('setting', {
activeTab: 'appearance',
});
track.$.navigationPanel.$.openSettings();
}, [globalDialogService]);
}, [workspaceDialogService]);
const handleOpenDocs = useCallback(
(result: {

View File

@@ -8,7 +8,10 @@ import {
type MenuProps,
Skeleton,
} from '@affine/component';
import { GlobalDialogService } from '@affine/core/modules/dialogs';
import {
GlobalDialogService,
WorkspaceDialogService,
} from '@affine/core/modules/dialogs';
import { useI18n } from '@affine/i18n';
import { track } from '@affine/track';
import { AccountIcon, SignOutIcon } from '@blocksuite/icons/rc';
@@ -75,15 +78,15 @@ const UnauthorizedUserInfo = () => {
};
const AccountMenu = () => {
const globalDialogService = useService(GlobalDialogService);
const workspaceDialogService = useService(WorkspaceDialogService);
const openSignOutModal = useSignOut();
const onOpenAccountSetting = useCallback(() => {
track.$.navigationPanel.profileAndBadge.openSettings({ to: 'account' });
globalDialogService.open('setting', {
workspaceDialogService.open('setting', {
activeTab: 'account',
});
}, [globalDialogService]);
}, [workspaceDialogService]);
const t = useI18n();
@@ -112,13 +115,13 @@ const CloudUsage = () => {
const quota = useService(UserQuotaService).quota;
const quotaError = useLiveData(quota.error$);
const globalDialogService = useService(GlobalDialogService);
const workspaceDialogService = useService(WorkspaceDialogService);
const handleClick = useCatchEventCallback(() => {
globalDialogService.open('setting', {
workspaceDialogService.open('setting', {
activeTab: 'plans',
scrollAnchor: 'cloudPricingPlan',
});
}, [globalDialogService]);
}, [workspaceDialogService]);
useEffect(() => {
// revalidate quota to get the latest status
@@ -192,20 +195,20 @@ const AIUsage = () => {
const loading = copilotActionLimit === null || copilotActionUsed === null;
const loadError = useLiveData(copilotQuotaService.copilotQuota.error$);
const globalDialogService = useService(GlobalDialogService);
const workspaceDialogService = useService(WorkspaceDialogService);
const goToAIPlanPage = useCallback(() => {
globalDialogService.open('setting', {
workspaceDialogService.open('setting', {
activeTab: 'plans',
scrollAnchor: 'aiPricingPlan',
});
}, [globalDialogService]);
}, [workspaceDialogService]);
const goToAccountSetting = useCallback(() => {
globalDialogService.open('setting', {
workspaceDialogService.open('setting', {
activeTab: 'account',
});
}, [globalDialogService]);
}, [workspaceDialogService]);
if (loading) {
if (loadError) console.error(loadError);

View File

@@ -27,7 +27,6 @@ const GLOBAL_DIALOGS = {
'create-workspace': CreateWorkspaceDialog,
'import-workspace': ImportWorkspaceDialog,
'import-template': ImportTemplateDialog,
setting: SettingDialog,
'sign-in': SignInDialog,
'change-password': ChangePasswordDialog,
'verify-email': VerifyEmailDialog,
@@ -45,6 +44,7 @@ const WORKSPACE_DIALOGS = {
'doc-selector': DocSelectorDialog,
'collection-selector': CollectionSelectorDialog,
'date-selector': DateSelectorDialog,
setting: SettingDialog,
import: ImportDialog,
} satisfies {
[key in keyof WORKSPACE_DIALOG_SCHEMA]?: React.FC<

View File

@@ -10,11 +10,10 @@ import {
PenIcon,
} from '@blocksuite/icons/rc';
import { useLiveData, useServices } from '@toeverything/infra';
import type { ReactNode } from 'react';
import { useEffect } from 'react';
import { AuthService, ServerService } from '../../../../modules/cloud';
import type { SettingState } from '../types';
import type { SettingSidebarItem, SettingState } from '../types';
import { AboutAffine } from './about';
import { AppearanceSettings } from './appearance';
import { BillingSettings } from './billing';
@@ -24,14 +23,7 @@ import { PaymentIcon, UpgradeIcon } from './icons';
import { AFFiNEPricingPlans } from './plans';
import { Shortcuts } from './shortcuts';
interface GeneralSettingListItem {
key: SettingTab;
title: string;
icon: ReactNode;
testId: string;
}
export type GeneralSettingList = GeneralSettingListItem[];
export type GeneralSettingList = SettingSidebarItem[];
export const useGeneralSettingList = (): GeneralSettingList => {
const t = useI18n();
@@ -54,7 +46,7 @@ export const useGeneralSettingList = (): GeneralSettingList => {
userFeatureService.userFeature.revalidate();
}, [userFeatureService]);
const settings: GeneralSettingListItem[] = [
const settings: GeneralSettingList = [
{
key: 'appearance',
title: t['com.affine.settings.appearance'](),

View File

@@ -7,11 +7,11 @@ import {
DefaultServerService,
ServersService,
} from '@affine/core/modules/cloud';
import type { DialogComponentProps } from '@affine/core/modules/dialogs';
import type {
DialogComponentProps,
GLOBAL_DIALOG_SCHEMA,
} from '@affine/core/modules/dialogs';
import type { SettingTab } from '@affine/core/modules/dialogs/constant';
SettingTab,
WORKSPACE_DIALOG_SCHEMA,
} from '@affine/core/modules/dialogs/constant';
import { GlobalContextService } from '@affine/core/modules/global-context';
import { Trans } from '@affine/i18n';
import { ContactWithUsIcon } from '@blocksuite/icons/rc';
@@ -207,7 +207,7 @@ const SettingModalInner = ({
export const SettingDialog = ({
close,
activeTab,
}: DialogComponentProps<GLOBAL_DIALOG_SCHEMA['setting']>) => {
}: DialogComponentProps<WORKSPACE_DIALOG_SCHEMA['setting']>) => {
return (
<Modal
width={1280}

View File

@@ -1,38 +1,25 @@
import { WorkspaceListSkeleton } from '@affine/component/setting-components';
import { Avatar } from '@affine/component/ui/avatar';
import { UserPlanButton } from '@affine/core/components/affine/auth/user-plan-button';
import { useCatchEventCallback } from '@affine/core/components/hooks/use-catch-event-hook';
import { useWorkspaceInfo } from '@affine/core/components/hooks/use-workspace-info';
import { CurrentWorkspaceScopeProvider } from '@affine/core/components/providers/current-workspace-scope';
import { AuthService } from '@affine/core/modules/cloud';
import { UserFeatureService } from '@affine/core/modules/cloud/services/user-feature';
import { GlobalDialogService } from '@affine/core/modules/dialogs';
import type { SettingTab } from '@affine/core/modules/dialogs/constant';
import {
type WorkspaceMetadata,
WorkspaceService,
} from '@affine/core/modules/workspace';
import { type WorkspaceMetadata } from '@affine/core/modules/workspace';
import { useI18n } from '@affine/i18n';
import { track } from '@affine/track';
import {
Logo1Icon,
PaymentIcon,
PropertyIcon,
SettingsIcon,
} from '@blocksuite/icons/rc';
import { useLiveData, useService, useServices } from '@toeverything/infra';
import { Logo1Icon } from '@blocksuite/icons/rc';
import { useLiveData, useService } from '@toeverything/infra';
import clsx from 'clsx';
import {
type HTMLAttributes,
type MouseEvent,
type ReactNode,
Suspense,
useCallback,
useEffect,
useMemo,
} from 'react';
import { useGeneralSettingList } from '../general-setting';
import { useWorkspaceSettingList } from '../workspace-setting';
import * as style from './style.css';
export type UserInfoProps = {
@@ -118,26 +105,51 @@ export const SignInButton = () => {
);
};
type SettingSidebarItemProps = {
isActive: boolean;
icon: ReactNode;
title: string;
key: string;
testId?: string;
} & HTMLAttributes<HTMLDivElement>;
const SettingSidebarItem = ({
isActive,
icon,
label,
title,
testId,
...props
}: {
isActive: boolean;
label: string;
icon: ReactNode;
} & HTMLAttributes<HTMLDivElement>) => {
}: SettingSidebarItemProps) => {
return (
<div
{...props}
title={label}
title={title}
data-testid={testId}
className={clsx(style.sidebarSelectItem, {
active: isActive,
})}
>
<div className={style.sidebarSelectItemIcon}>{icon}</div>
<div className={style.sidebarSelectItemName}>{label}</div>
<div className={style.sidebarSelectItemName}>{title}</div>
</div>
);
};
const SettingSidebarGroup = ({
title,
items,
}: {
title: string;
items: SettingSidebarItemProps[];
}) => {
return (
<div className={style.sidebarGroup}>
<div className={style.sidebarSubtitle}>{title}</div>
<div className={style.sidebarItemsWrapper}>
{items.map(({ key, ...props }) => (
<SettingSidebarItem key={key} {...props} />
))}
</div>
</div>
);
};
@@ -152,12 +164,11 @@ export const SettingSidebar = ({
const t = useI18n();
const loginStatus = useLiveData(useService(AuthService).session.status$);
const generalList = useGeneralSettingList();
const workspaceSettingList = useWorkspaceSettingList();
const gotoTab = useCallback(
(e: MouseEvent<HTMLDivElement>) => {
const tab = e.currentTarget.dataset.eventArg;
if (!tab) return;
(tab: SettingTab) => {
track.$.settingsPanel.menu.openSettings({ to: tab });
onTabChange(tab as SettingTab);
onTabChange(tab);
},
[onTabChange]
);
@@ -165,16 +176,34 @@ export const SettingSidebar = ({
track.$.settingsPanel.menu.openSettings({ to: 'account' });
onTabChange('account');
}, [onTabChange]);
const onWorkspaceSettingClick = useCallback(
(tab: SettingTab) => {
track.$.settingsPanel.menu.openSettings({
to: 'workspace',
control: tab,
});
onTabChange(tab);
},
[onTabChange]
);
const groups = useMemo(() => {
const res = [
{
key: 'setting:general',
title: t['com.affine.settingSidebar.settings.general'](),
items: generalList,
},
{
key: 'setting:workspace',
title: t['com.affine.settingSidebar.settings.workspace'](),
items: workspaceSettingList,
},
].map(group => {
return {
...group,
items: group.items.map(item => {
return {
...item,
isActive: item.key === activeTab,
'data-event-arg': item.key,
onClick: () => gotoTab(item.key),
};
}),
};
});
return res;
}, [activeTab, generalList, gotoTab, t, workspaceSettingList]);
return (
<div className={style.settingSlideBar} data-testid="settings-sidebar">
@@ -193,117 +222,13 @@ export const SettingSidebar = ({
</Suspense>
) : null}
<div className={style.sidebarGroup}>
<div className={style.sidebarSubtitle}>
{t['com.affine.settingSidebar.settings.general']()}
</div>
<div className={style.sidebarItemsWrapper}>
{generalList.map(({ title, icon, key, testId }) => {
return (
<SettingSidebarItem
isActive={key === activeTab}
key={key}
label={title}
data-event-arg={key}
onClick={gotoTab}
data-testid={testId}
icon={icon}
/>
);
})}
</div>
</div>
<div className={style.sidebarGroup}>
<div className={style.sidebarSubtitle}>
{t['com.affine.settingSidebar.settings.workspace']()}
</div>
<div className={style.sidebarItemsWrapper}>
<Suspense fallback={<WorkspaceListSkeleton />}>
<CurrentWorkspaceScopeProvider>
<WorkspaceSettingItems
onWorkspaceSettingClick={onWorkspaceSettingClick}
activeTab={activeTab}
/>
</CurrentWorkspaceScopeProvider>
</Suspense>
</div>
</div>
</div>
);
};
const WorkspaceSettingItems = ({
onWorkspaceSettingClick,
activeTab,
}: {
onWorkspaceSettingClick: (activeTab: SettingTab) => void;
activeTab: SettingTab;
}) => {
const { userFeatureService } = useServices({
UserFeatureService,
});
const workspaceService = useService(WorkspaceService);
const information = useWorkspaceInfo(workspaceService.workspace);
const t = useI18n();
useEffect(() => {
userFeatureService.userFeature.revalidate();
}, [userFeatureService]);
const showBilling = information?.isTeam && information?.isOwner;
const subTabs = useMemo(() => {
const subTabConfigs = [
{
key: 'workspace:preference',
title: 'com.affine.settings.workspace.preferences',
icon: <SettingsIcon />,
},
{
key: 'workspace:properties',
title: 'com.affine.settings.workspace.properties',
icon: <PropertyIcon />,
},
...(showBilling
? [
{
key: 'workspace:billing' as SettingTab,
title: 'com.affine.settings.workspace.billing',
icon: <PaymentIcon />,
},
]
: []),
] satisfies {
key: SettingTab;
title: keyof ReturnType<typeof useI18n>;
icon: ReactNode;
}[];
return subTabConfigs.map(({ key, title, icon }) => {
return (
<SettingSidebarItem
isActive={activeTab === key}
label={t[title]()}
icon={icon}
data-testid={`workspace-list-item-${key}`}
onClick={() => {
onWorkspaceSettingClick(key);
}}
className={clsx(style.sidebarSelectItem, {
active: activeTab === key,
})}
key={key}
{groups.map(group => (
<SettingSidebarGroup
key={group.key}
title={group.title}
items={group.items}
/>
);
});
}, [activeTab, onWorkspaceSettingClick, showBilling, t]);
return (
<div className={style.sidebarItemsWrapper}>
{/* TODO: remove the suspense? */}
<Suspense fallback={<WorkspaceListSkeleton />}>{subTabs}</Suspense>
))}
</div>
);
};

View File

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

View File

@@ -1,8 +1,12 @@
import { CurrentWorkspaceScopeProvider } from '@affine/core/components/providers/current-workspace-scope';
import { useWorkspaceInfo } from '@affine/core/components/hooks/use-workspace-info';
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 { useService } from '@toeverything/infra';
import { useMemo } from 'react';
import type { SettingState } from '../types';
import type { SettingSidebarItem, SettingState } from '../types';
import { WorkspaceSettingBilling } from './billing';
import { WorkspaceSettingDetail } from './new-workspace-setting-detail';
import { WorkspaceSettingProperties } from './properties';
@@ -16,24 +20,56 @@ export const WorkspaceSetting = ({
onCloseSetting: () => void;
onChangeSettingState: (settingState: SettingState) => void;
}) => {
const element = useMemo(() => {
switch (activeTab) {
case 'workspace:preference':
return (
<WorkspaceSettingDetail
onCloseSetting={onCloseSetting}
onChangeSettingState={onChangeSettingState}
/>
);
case 'workspace:properties':
return <WorkspaceSettingProperties />;
case 'workspace:billing':
return <WorkspaceSettingBilling />;
default:
return null;
}
}, [activeTab, onCloseSetting, onChangeSettingState]);
return (
<CurrentWorkspaceScopeProvider>{element}</CurrentWorkspaceScopeProvider>
);
switch (activeTab) {
case 'workspace:preference':
return (
<WorkspaceSettingDetail
onCloseSetting={onCloseSetting}
onChangeSettingState={onChangeSettingState}
/>
);
case 'workspace:properties':
return <WorkspaceSettingProperties />;
case 'workspace:billing':
return <WorkspaceSettingBilling />;
default:
return null;
}
};
export const useWorkspaceSettingList = (): SettingSidebarItem[] => {
const workspaceService = useService(WorkspaceService);
const information = useWorkspaceInfo(workspaceService.workspace);
const t = useI18n();
const showBilling = information?.isTeam && information?.isOwner;
const items = useMemo<SettingSidebarItem[]>(() => {
return [
{
key: 'workspace:preference',
title: t['com.affine.settings.workspace.preferences'](),
icon: <SettingsIcon />,
testId: 'workspace-setting:preference',
},
{
key: 'workspace:properties',
title: t['com.affine.settings.workspace.properties'](),
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',
},
]
: []),
];
}, [showBilling, t]);
return items;
};

View File

@@ -18,7 +18,6 @@ const GLOBAL_DIALOGS = {
// 'create-workspace': CreateWorkspaceDialog,
// 'import-workspace': ImportWorkspaceDialog,
// 'import-template': ImportTemplateDialog,
setting: SettingDialog,
// import: ImportDialog,
'sign-in': SignInDialog,
} satisfies {
@@ -34,6 +33,7 @@ const WORKSPACE_DIALOGS = {
'doc-selector': DocSelectorDialog,
'collection-selector': CollectionSelectorDialog,
'date-selector': DateSelectorDialog,
setting: SettingDialog,
} satisfies {
[key in keyof WORKSPACE_DIALOG_SCHEMA]?: React.FC<
DialogComponentProps<WORKSPACE_DIALOG_SCHEMA[key]>

View File

@@ -1,7 +1,7 @@
import { AuthService } from '@affine/core/modules/cloud';
import type {
DialogComponentProps,
GLOBAL_DIALOG_SCHEMA,
WORKSPACE_DIALOG_SCHEMA,
} from '@affine/core/modules/dialogs';
import { useI18n } from '@affine/i18n';
import { useService } from '@toeverything/infra';
@@ -34,7 +34,7 @@ const MobileSetting = () => {
export const SettingDialog = ({
close,
}: DialogComponentProps<GLOBAL_DIALOG_SCHEMA['setting']>) => {
}: DialogComponentProps<WORKSPACE_DIALOG_SCHEMA['setting']>) => {
const t = useI18n();
return (

View File

@@ -3,7 +3,7 @@ import {
SafeArea,
startScopedViewTransition,
} from '@affine/component';
import { GlobalDialogService } from '@affine/core/modules/dialogs';
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
import { WorkbenchService } from '@affine/core/modules/workbench';
import { useI18n } from '@affine/i18n';
import { SettingsIcon } from '@blocksuite/icons/rc';
@@ -23,7 +23,7 @@ import * as styles from './styles.css';
* - hide Search
*/
export const HomeHeader = () => {
const globalDialogService = useService(GlobalDialogService);
const workspaceDialogService = useService(WorkspaceDialogService);
const workspaceCardRef = useRef<HTMLDivElement>(null);
const floatWorkspaceCardRef = useRef<HTMLDivElement>(null);
@@ -50,10 +50,10 @@ export const HomeHeader = () => {
);
const openSetting = useCallback(() => {
globalDialogService.open('setting', {
workspaceDialogService.open('setting', {
activeTab: 'appearance',
});
}, [globalDialogService]);
}, [workspaceDialogService]);
return (
<>

View File

@@ -26,7 +26,6 @@ export type GLOBAL_DIALOG_SCHEMA = {
templateMode: DocMode;
snapshotUrl: string;
}) => void;
setting: (props: { activeTab?: SettingTab; scrollAnchor?: string }) => void;
'sign-in': (props: { server?: string; step?: string }) => void;
'change-password': (props: { server?: string }) => void;
'verify-email': (props: { server?: string; changeEmail?: boolean }) => void;
@@ -38,6 +37,7 @@ export type GLOBAL_DIALOG_SCHEMA = {
};
export type WORKSPACE_DIALOG_SCHEMA = {
setting: (props: { activeTab?: SettingTab; scrollAnchor?: string }) => void;
'doc-info': (props: { docId: string }) => void;
'doc-selector': (props: {
init: string[];

View File

@@ -1,4 +1,5 @@
import { Button } from '@affine/component/ui/button';
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
import { appIconMap, appNames } from '@affine/core/utils/channel';
import { Trans, useI18n } from '@affine/i18n';
import { LocalWorkspaceIcon, Logo1Icon } from '@blocksuite/icons/rc';
@@ -6,7 +7,6 @@ import { useService } from '@toeverything/infra';
import type { MouseEvent } from 'react';
import { useCallback } from 'react';
import { GlobalDialogService } from '../../dialogs';
import { getOpenUrlInDesktopAppLink } from '../utils';
import * as styles from './open-in-app-page.css';
@@ -28,7 +28,7 @@ export const OpenInAppPage = ({
}: OpenAppProps) => {
// default to open the current page in desktop app
urlToOpen ??= getOpenUrlInDesktopAppLink(window.location.href, true);
const globalDialogService = useService(GlobalDialogService);
const workspaceDialogService = useService(WorkspaceDialogService);
const t = useI18n();
const openDownloadLink = useCallback(() => {
@@ -41,11 +41,11 @@ export const OpenInAppPage = ({
const goToAppearanceSetting = useCallback(
(e: MouseEvent) => {
openHereClicked?.(e);
globalDialogService.open('setting', {
workspaceDialogService.open('setting', {
activeTab: 'appearance',
});
},
[globalDialogService, openHereClicked]
[workspaceDialogService, openHereClicked]
);
if (urlToOpen && lastOpened !== urlToOpen) {

View File

@@ -1,5 +1,5 @@
import { useConfirmModal } from '@affine/component';
import { GlobalDialogService } from '@affine/core/modules/dialogs';
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
import { type I18nString, useI18n } from '@affine/i18n';
import { InformationFillDuotoneIcon } from '@blocksuite/icons/rc';
import { useLiveData, useService } from '@toeverything/infra';
@@ -36,18 +36,18 @@ export const QuotaCheck = ({
const profile = useLiveData(workspaceProfile.profile$);
const isOwner = profile?.isOwner;
const isTeam = profile?.isTeam;
const globalDialogService = useService(GlobalDialogService);
const workspaceDialogService = useService(WorkspaceDialogService);
const t = useI18n();
const onConfirm = useCallback(() => {
if (!isOwner) {
return;
}
globalDialogService.open('setting', {
workspaceDialogService.open('setting', {
activeTab: 'plans',
scrollAnchor: 'cloudPricingPlan',
});
}, [globalDialogService, isOwner]);
}, [workspaceDialogService, isOwner]);
useEffect(() => {
workspaceQuota?.revalidate();