feat(core): remove workspace selectors in settings (#9705)

fix AF-2119
This commit is contained in:
pengx17
2025-01-15 07:37:24 +00:00
parent c31a0d28cf
commit 0bce5c6730
33 changed files with 223 additions and 349 deletions

View File

@@ -38,7 +38,6 @@ export function registerAffineHelpCommands({
track.$.cmdk.help.contactUs();
globalDialogService.open('setting', {
activeTab: 'about',
workspaceMetadata: null,
});
},
})

View File

@@ -86,9 +86,8 @@ export const AFFiNESharePage = (props: ShareMenuProps) => {
const onOpenWorkspaceSettings = useCallback(() => {
globalDialogService.open('setting', {
activeTab: 'workspace:preference',
workspaceMetadata: props.workspaceMetadata,
});
}, [globalDialogService, props.workspaceMetadata]);
}, [globalDialogService]);
const onClickAnyoneReadOnlyShare = useAsyncCallback(async () => {
if (isSharedPage) {

View File

@@ -0,0 +1,22 @@
import { useWorkspace } from '@affine/core/components/hooks/use-workspace';
import { GlobalContextService } from '@affine/core/modules/global-context';
import { WorkspacesService } from '@affine/core/modules/workspace';
import { FrameworkScope, useLiveData, useService } from '@toeverything/infra';
export const CurrentWorkspaceScopeProvider = ({
children,
}: {
children: React.ReactNode;
}) => {
const globalContext = useService(GlobalContextService).globalContext;
const workspacesService = useService(WorkspacesService);
const workspaceMeta = useLiveData(workspacesService.list.workspaces$).find(
workspace => workspace.id === globalContext.workspaceId.get()
);
const workspace = useWorkspace(workspaceMeta);
if (!workspace) {
// todo(@pengx17): render a loading/error component here if not found?
return null;
}
return <FrameworkScope scope={workspace.scope}>{children}</FrameworkScope>;
};

View File

@@ -148,11 +148,7 @@ export const RootAppSidebar = memo((): ReactElement => {
<SidebarContainer>
<div className={workspaceAndUserWrapper}>
<div className={workspaceWrapper}>
<WorkspaceNavigator
showEnableCloudButton
showSettingsButton
showSyncStatus
/>
<WorkspaceNavigator showEnableCloudButton showSyncStatus />
</div>
<UserInfo />
</div>

View File

@@ -25,7 +25,6 @@ interface WorkspaceSelectorProps {
metadata: WorkspaceMetadata;
defaultDocId?: string;
}) => void;
showSettingsButton?: boolean;
showEnableCloudButton?: boolean;
showArrowDownIcon?: boolean;
showSyncStatus?: boolean;
@@ -38,7 +37,6 @@ export const WorkspaceSelector = ({
workspaceMetadata: outerWorkspaceMetadata,
onSelectWorkspace,
onCreatedWorkspace,
showSettingsButton,
showArrowDownIcon,
disable,
open: outerOpen,
@@ -89,7 +87,6 @@ export const WorkspaceSelector = ({
onClickWorkspace={onSelectWorkspace}
onCreatedWorkspace={onCreatedWorkspace}
showEnableCloudButton={showEnableCloudButton}
showSettingsButton={showSettingsButton}
/>
}
contentOptions={{

View File

@@ -55,7 +55,6 @@ interface UserWithWorkspaceListProps {
metadata: WorkspaceMetadata;
defaultDocId?: string;
}) => void;
showSettingsButton?: boolean;
showEnableCloudButton?: boolean;
}
@@ -63,7 +62,6 @@ const UserWithWorkspaceListInner = ({
onEventEnd,
onClickWorkspace,
onCreatedWorkspace,
showSettingsButton,
showEnableCloudButton,
}: UserWithWorkspaceListProps) => {
const globalDialogService = useService(GlobalDialogService);
@@ -121,7 +119,6 @@ const UserWithWorkspaceListInner = ({
onEventEnd={onEventEnd}
onClickWorkspace={onClickWorkspace}
showEnableCloudButton={showEnableCloudButton}
showSettingsButton={showSettingsButton}
/>
<AddWorkspace
onAddWorkspace={onAddWorkspace}

View File

@@ -50,13 +50,11 @@ const CloudWorkSpaceList = ({
server,
workspaces,
onClickWorkspace,
onClickWorkspaceSetting,
onClickEnableCloud,
}: {
server: Server;
workspaces: WorkspaceMetadata[];
onClickWorkspace: (workspaceMetadata: WorkspaceMetadata) => void;
onClickWorkspaceSetting?: (workspaceMetadata: WorkspaceMetadata) => void;
onClickEnableCloud?: (meta: WorkspaceMetadata) => void;
}) => {
const t = useI18n();
@@ -161,7 +159,6 @@ const CloudWorkSpaceList = ({
<WorkspaceList
items={workspaces}
onClick={onClickWorkspace}
onSettingClick={onClickWorkspaceSetting}
onEnableCloudClick={onClickEnableCloud}
/>
<MenuItem
@@ -215,16 +212,13 @@ export const AFFiNEWorkspaceList = ({
onEventEnd,
onClickWorkspace,
showEnableCloudButton,
showSettingsButton,
}: {
onClickWorkspace?: (workspaceMetadata: WorkspaceMetadata) => void;
onEventEnd?: () => void;
showSettingsButton?: boolean;
showEnableCloudButton?: boolean;
}) => {
const workspacesService = useService(WorkspacesService);
const workspaces = useLiveData(workspacesService.list.workspaces$);
const globalDialogService = useService(GlobalDialogService);
const confirmEnableCloud = useEnableCloud();
@@ -247,17 +241,6 @@ export const AFFiNEWorkspaceList = ({
[workspaces]
);
const onClickWorkspaceSetting = useCallback(
(workspaceMetadata: WorkspaceMetadata) => {
globalDialogService.open('setting', {
activeTab: 'workspace:preference',
workspaceMetadata,
});
onEventEnd?.();
},
[globalDialogService, onEventEnd]
);
const onClickEnableCloud = useCallback(
(meta: WorkspaceMetadata) => {
const { workspace, dispose } = workspacesService.open({ metadata: meta });
@@ -292,9 +275,6 @@ export const AFFiNEWorkspaceList = ({
({ flavour }) => flavour === server.id
)}
onClickWorkspace={handleClickWorkspace}
onClickWorkspaceSetting={
showSettingsButton ? onClickWorkspaceSetting : undefined
}
/>
<Divider size="thinner" />
</FrameworkScope>
@@ -303,9 +283,6 @@ export const AFFiNEWorkspaceList = ({
<LocalWorkspaces
workspaces={localWorkspaces}
onClickWorkspace={handleClickWorkspace}
onClickWorkspaceSetting={
showSettingsButton ? onClickWorkspaceSetting : undefined
}
onClickEnableCloud={
showEnableCloudButton ? onClickEnableCloud : undefined
}

View File

@@ -10,7 +10,7 @@ import {
PenIcon,
} from '@blocksuite/icons/rc';
import { useLiveData, useServices } from '@toeverything/infra';
import type { ReactElement, SVGProps } from 'react';
import type { ReactNode } from 'react';
import { useEffect } from 'react';
import { AuthService, ServerService } from '../../../../modules/cloud';
@@ -27,7 +27,7 @@ import { Shortcuts } from './shortcuts';
interface GeneralSettingListItem {
key: SettingTab;
title: string;
icon: (props: SVGProps<SVGSVGElement>) => ReactElement;
icon: ReactNode;
testId: string;
}
@@ -58,19 +58,19 @@ export const useGeneralSettingList = (): GeneralSettingList => {
{
key: 'appearance',
title: t['com.affine.settings.appearance'](),
icon: AppearanceIcon,
icon: <AppearanceIcon />,
testId: 'appearance-panel-trigger',
},
{
key: 'shortcuts',
title: t['com.affine.keyboardShortcuts.title'](),
icon: KeyboardIcon,
icon: <KeyboardIcon />,
testId: 'shortcuts-panel-trigger',
},
{
key: 'about',
title: t['com.affine.aboutAFFiNE.title'](),
icon: InformationIcon,
icon: <InformationIcon />,
testId: 'about-panel-trigger',
},
];
@@ -79,7 +79,7 @@ export const useGeneralSettingList = (): GeneralSettingList => {
settings.splice(1, 0, {
key: 'editor',
title: t['com.affine.settings.editorSettings'](),
icon: PenIcon,
icon: <PenIcon />,
testId: 'editor-panel-trigger',
});
}
@@ -88,14 +88,14 @@ export const useGeneralSettingList = (): GeneralSettingList => {
settings.splice(3, 0, {
key: 'plans',
title: t['com.affine.payment.title'](),
icon: UpgradeIcon,
icon: <UpgradeIcon />,
testId: 'plans-panel-trigger',
});
if (status === 'authenticated') {
settings.splice(3, 0, {
key: 'billing',
title: t['com.affine.payment.billing-setting.title'](),
icon: PaymentIcon,
icon: <PaymentIcon />,
testId: 'billing-panel-trigger',
});
}
@@ -104,7 +104,7 @@ export const useGeneralSettingList = (): GeneralSettingList => {
settings.push({
key: 'experimental-features',
title: t['com.affine.settings.workspace.experimental-features'](),
icon: ExperimentIcon,
icon: <ExperimentIcon />,
testId: 'experimental-features-trigger',
});

View File

@@ -13,7 +13,6 @@ import type {
} from '@affine/core/modules/dialogs';
import type { SettingTab } from '@affine/core/modules/dialogs/constant';
import { GlobalContextService } from '@affine/core/modules/global-context';
import type { WorkspaceMetadata } from '@affine/core/modules/workspace';
import { Trans } from '@affine/i18n';
import { ContactWithUsIcon } from '@blocksuite/icons/rc';
import { FrameworkScope, useLiveData, useService } from '@toeverything/infra';
@@ -37,7 +36,6 @@ import { WorkspaceSetting } from './workspace-setting';
interface SettingProps extends ModalProps {
activeTab?: SettingTab;
workspaceMetadata?: WorkspaceMetadata | null;
onCloseSetting: () => void;
}
@@ -54,12 +52,10 @@ const CenteredLoading = () => {
const SettingModalInner = ({
activeTab: initialActiveTab = 'appearance',
workspaceMetadata: initialWorkspaceMetadata = null,
onCloseSetting,
}: SettingProps) => {
const [settingState, setSettingState] = useState<SettingState>({
activeTab: initialActiveTab,
activeWorkspaceMetadata: initialWorkspaceMetadata,
scrollAnchor: undefined,
});
const globalContextService = useService(GlobalContextService);
@@ -122,8 +118,8 @@ const SettingModalInner = ({
}, []);
const onTabChange = useCallback(
(key: SettingTab, meta: WorkspaceMetadata | null) => {
setSettingState({ activeTab: key, activeWorkspaceMetadata: meta });
(key: SettingTab) => {
setSettingState({ activeTab: key });
},
[setSettingState]
);
@@ -143,7 +139,6 @@ const SettingModalInner = ({
<SettingSidebar
activeTab={settingState.activeTab}
onTabChange={onTabChange}
selectedWorkspaceId={settingState.activeWorkspaceMetadata?.id ?? null}
/>
<Scrollable.Root>
<Scrollable.Viewport
@@ -158,11 +153,9 @@ const SettingModalInner = ({
{settingState.activeTab === 'account' &&
loginStatus === 'authenticated' ? (
<AccountSetting onChangeSettingState={setSettingState} />
) : isWorkspaceSetting(settingState.activeTab) &&
settingState.activeWorkspaceMetadata ? (
) : isWorkspaceSetting(settingState.activeTab) ? (
<WorkspaceSetting
activeTab={settingState.activeTab}
workspaceMetadata={settingState.activeWorkspaceMetadata}
onCloseSetting={onCloseSetting}
onChangeSettingState={setSettingState}
/>
@@ -214,7 +207,6 @@ const SettingModalInner = ({
export const SettingDialog = ({
close,
activeTab,
workspaceMetadata,
}: DialogComponentProps<GLOBAL_DIALOG_SCHEMA['setting']>) => {
return (
<Modal
@@ -234,11 +226,7 @@ export const SettingDialog = ({
onOpenChange={() => close()}
>
<Suspense fallback={<CenteredLoading />}>
<SettingModalInner
activeTab={activeTab}
workspaceMetadata={workspaceMetadata}
onCloseSetting={close}
/>
<SettingModalInner activeTab={activeTab} onCloseSetting={close} />
</Suspense>
</Modal>
);

View File

@@ -1,30 +1,31 @@
import {
WorkspaceListItemSkeleton,
WorkspaceListSkeleton,
} from '@affine/component/setting-components';
import { WorkspaceListSkeleton } from '@affine/component/setting-components';
import { Avatar } from '@affine/component/ui/avatar';
import { Tooltip } from '@affine/component/ui/tooltip';
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 { WorkspaceAvatar } from '@affine/core/components/workspace-avatar';
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 { GlobalContextService } from '@affine/core/modules/global-context';
import {
type WorkspaceMetadata,
WorkspacesService,
WorkspaceService,
} from '@affine/core/modules/workspace';
import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant';
import { useI18n } from '@affine/i18n';
import { track } from '@affine/track';
import { Logo1Icon } from '@blocksuite/icons/rc';
import {
Logo1Icon,
PaymentIcon,
PropertyIcon,
SettingsIcon,
} from '@blocksuite/icons/rc';
import { useLiveData, useService, useServices } from '@toeverything/infra';
import clsx from 'clsx';
import {
type HTMLAttributes,
type MouseEvent,
type ReactNode,
Suspense,
useCallback,
useEffect,
@@ -117,17 +118,36 @@ export const SignInButton = () => {
);
};
const SettingSidebarItem = ({
isActive,
icon,
label,
...props
}: {
isActive: boolean;
label: string;
icon: ReactNode;
} & HTMLAttributes<HTMLDivElement>) => {
return (
<div
{...props}
title={label}
className={clsx(style.sidebarSelectItem, {
active: isActive,
})}
>
<div className={style.sidebarSelectItemIcon}>{icon}</div>
<div className={style.sidebarSelectItemName}>{label}</div>
</div>
);
};
export const SettingSidebar = ({
activeTab,
onTabChange,
selectedWorkspaceId,
}: {
activeTab: SettingTab;
onTabChange: (
key: SettingTab,
workspaceMetadata: WorkspaceMetadata | null
) => void;
selectedWorkspaceId: string | null;
onTabChange: (key: SettingTab) => void;
}) => {
const t = useI18n();
const loginStatus = useLiveData(useService(AuthService).session.status$);
@@ -137,21 +157,21 @@ export const SettingSidebar = ({
const tab = e.currentTarget.dataset.eventArg;
if (!tab) return;
track.$.settingsPanel.menu.openSettings({ to: tab });
onTabChange(tab as SettingTab, null);
onTabChange(tab as SettingTab);
},
[onTabChange]
);
const onAccountSettingClick = useCallback(() => {
track.$.settingsPanel.menu.openSettings({ to: 'account' });
onTabChange('account', null);
onTabChange('account');
}, [onTabChange]);
const onWorkspaceSettingClick = useCallback(
(tab: SettingTab, workspaceMetadata: WorkspaceMetadata) => {
(tab: SettingTab) => {
track.$.settingsPanel.menu.openSettings({
to: 'workspace',
control: tab,
});
onTabChange(tab, workspaceMetadata);
onTabChange(tab);
},
[onTabChange]
);
@@ -161,195 +181,129 @@ export const SettingSidebar = ({
<div className={style.sidebarTitle}>
{t['com.affine.settingSidebar.title']()}
</div>
<div className={style.sidebarSubtitle}>
{t['com.affine.settingSidebar.settings.general']()}
</div>
<div className={style.sidebarItemsWrapper}>
{generalList.map(({ title, icon, key, testId }) => {
return (
<div
className={clsx(style.sidebarSelectItem, {
active: key === activeTab,
})}
key={key}
title={title}
data-event-arg={key}
onClick={gotoTab}
data-testid={testId}
>
{icon({ className: 'icon' })}
<span className="setting-name">{title}</span>
</div>
);
})}
</div>
<div className={style.sidebarSubtitle}>
{t['com.affine.settingSidebar.settings.workspace']()}
</div>
<div className={clsx(style.sidebarItemsWrapper, 'scroll')}>
<Suspense fallback={<WorkspaceListSkeleton />}>
<WorkspaceList
onWorkspaceSettingClick={onWorkspaceSettingClick}
selectedWorkspaceId={selectedWorkspaceId}
activeTab={activeTab}
{loginStatus === 'unauthenticated' ? <SignInButton /> : null}
{loginStatus === 'authenticated' ? (
<Suspense>
<UserInfo
onAccountSettingClick={onAccountSettingClick}
active={activeTab === 'account'}
onTabChange={onTabChange}
/>
</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.sidebarFooter}>
{loginStatus === 'unauthenticated' ? <SignInButton /> : null}
{loginStatus === 'authenticated' ? (
<Suspense>
<UserInfo
onAccountSettingClick={onAccountSettingClick}
active={activeTab === 'account'}
onTabChange={onTabChange}
/>
<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>
) : null}
</div>
</div>
</div>
);
};
export const WorkspaceList = ({
const WorkspaceSettingItems = ({
onWorkspaceSettingClick,
selectedWorkspaceId,
activeTab,
}: {
onWorkspaceSettingClick: (
activeTab: SettingTab,
workspaceMetadata: WorkspaceMetadata
) => void;
selectedWorkspaceId: string | null;
onWorkspaceSettingClick: (activeTab: SettingTab) => void;
activeTab: SettingTab;
}) => {
const workspaces = useLiveData(
useService(WorkspacesService).list.workspaces$
);
return (
<>
{workspaces.map(workspace => {
return (
<Suspense key={workspace.id} fallback={<WorkspaceListItemSkeleton />}>
<WorkspaceListItem
meta={workspace}
onClick={subTab => {
onWorkspaceSettingClick(subTab, workspace);
}}
activeTab={
workspace.id === selectedWorkspaceId ? activeTab : undefined
}
/>
</Suspense>
);
})}
</>
);
};
const WorkspaceListItem = ({
activeTab,
meta,
onClick,
}: {
meta: WorkspaceMetadata;
activeTab?: SettingTab;
onClick: (activeTab: SettingTab) => void;
}) => {
const { globalContextService, userFeatureService } = useServices({
GlobalContextService,
const { userFeatureService } = useServices({
UserFeatureService,
});
const information = useWorkspaceInfo(meta);
const name = information?.name ?? UNTITLED_WORKSPACE_NAME;
const currentWorkspaceId = useLiveData(
globalContextService.globalContext.workspaceId.$
);
const isCurrent = currentWorkspaceId === meta.id;
const workspaceService = useService(WorkspaceService);
const information = useWorkspaceInfo(workspaceService.workspace);
const t = useI18n();
useEffect(() => {
userFeatureService.userFeature.revalidate();
}, [userFeatureService]);
const onClickPreference = useCallback(() => {
onClick('workspace:preference');
}, [onClick]);
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 }) => {
return subTabConfigs.map(({ key, title, icon }) => {
return (
<div
<SettingSidebarItem
isActive={activeTab === key}
label={t[title]()}
icon={icon}
data-testid={`workspace-list-item-${key}`}
onClick={() => {
onClick(key);
onWorkspaceSettingClick(key);
}}
className={clsx(style.sidebarSelectSubItem, {
className={clsx(style.sidebarSelectItem, {
active: activeTab === key,
})}
key={key}
>
{t[title]()}
</div>
/>
);
});
}, [activeTab, onClick, showBilling, t]);
}, [activeTab, onWorkspaceSettingClick, showBilling, t]);
return (
<>
<div
className={clsx(style.sidebarSelectItem, { active: !!activeTab })}
title={name}
onClick={onClickPreference}
data-testid="workspace-list-item"
>
<WorkspaceAvatar
key={meta.id}
meta={meta}
size={16}
name={name}
colorfulFallback
style={{
marginRight: '10px',
}}
rounded={2}
/>
<span className="setting-name">{name}</span>
{isCurrent ? (
<Tooltip content="Current" side="top">
<div
className={style.currentWorkspaceLabel}
data-testid="current-workspace-label"
></div>
</Tooltip>
) : null}
</div>
{activeTab && subTabs.length > 1 ? subTabs : null}
</>
<div className={style.sidebarItemsWrapper}>
{/* TODO: remove the suspense? */}
<Suspense fallback={<WorkspaceListSkeleton />}>{subTabs}</Suspense>
</div>
);
};

View File

@@ -1,28 +1,29 @@
import { cssVar } from '@toeverything/theme';
import { cssVarV2 } from '@toeverything/theme/v2';
import { globalStyle, style } from '@vanilla-extract/css';
export const settingSlideBar = style({
width: '25%',
maxWidth: '242px',
background: cssVar('backgroundSecondaryColor'),
padding: '20px 0px',
padding: '20px 12px',
height: '100%',
flexShrink: 0,
display: 'flex',
flexDirection: 'column',
gap: '16px',
overflowY: 'auto',
});
export const sidebarTitle = style({
fontSize: cssVar('fontH6'),
fontWeight: '600',
lineHeight: cssVar('lineHeight'),
padding: '0px 16px 0px 24px',
padding: '0 8px',
});
export const sidebarSubtitle = style({
fontSize: cssVar('fontSm'),
lineHeight: cssVar('lineHeight'),
color: cssVar('textSecondaryColor'),
padding: '0px 16px 0px 24px',
marginTop: '20px',
marginBottom: '4px',
padding: '4px 8px',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
@@ -31,18 +32,11 @@ export const sidebarItemsWrapper = style({
display: 'flex',
flexDirection: 'column',
gap: 4,
selectors: {
'&.scroll': {
flexGrow: 1,
overflowY: 'auto',
},
},
});
export const sidebarSelectItem = style({
display: 'flex',
alignItems: 'center',
margin: '0px 16px',
padding: '0px 8px',
padding: '4px 8px',
height: '30px',
flexShrink: 0,
fontSize: cssVar('fontSm'),
@@ -76,19 +70,23 @@ export const sidebarSelectSubItem = style({
},
},
});
globalStyle(`${settingSlideBar} .icon`, {
export const sidebarSelectItemIcon = style({
width: '16px',
height: '16px',
marginRight: '10px',
flexShrink: 0,
color: cssVarV2('icon/primary'),
});
globalStyle(`${settingSlideBar} .setting-name`, {
export const sidebarSelectItemName = style({
minWidth: 0,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
flexGrow: 1,
});
export const currentWorkspaceLabel = style({
width: '20px',
height: '20px',
@@ -105,9 +103,13 @@ export const currentWorkspaceLabel = style({
},
},
});
export const sidebarFooter = style({
padding: '0 16px',
export const sidebarGroup = style({
display: 'flex',
flexDirection: 'column',
gap: '4px',
});
export const accountButton = style({
padding: '4px 8px',
borderRadius: '8px',

View File

@@ -1,8 +1,6 @@
import type { SettingTab } from '@affine/core/modules/dialogs/constant';
import type { WorkspaceMetadata } from '@affine/core/modules/workspace';
export interface SettingState {
activeTab: SettingTab;
activeWorkspaceMetadata?: WorkspaceMetadata | null;
scrollAnchor?: string;
}

View File

@@ -8,7 +8,6 @@ import {
import { getUpgradeQuestionnaireLink } from '@affine/core/components/hooks/affine/use-subscription-notify';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { useMutation } from '@affine/core/components/hooks/use-mutation';
import { useWorkspace } from '@affine/core/components/hooks/use-workspace';
import {
AuthService,
SubscriptionService,
@@ -17,7 +16,7 @@ import {
} from '@affine/core/modules/cloud';
import { WorkspaceQuotaService } from '@affine/core/modules/quota';
import { UrlService } from '@affine/core/modules/url';
import type { WorkspaceMetadata } from '@affine/core/modules/workspace';
import { WorkspaceService } from '@affine/core/modules/workspace';
import {
createCustomerPortalMutation,
type InvoicesQuery,
@@ -27,7 +26,7 @@ import {
UserFriendlyError,
} from '@affine/graphql';
import { useI18n } from '@affine/i18n';
import { FrameworkScope, useLiveData, useService } from '@toeverything/infra';
import { useLiveData, useService } from '@toeverything/infra';
import { cssVar } from '@toeverything/theme';
import { useCallback, useEffect, useMemo, useState } from 'react';
@@ -37,14 +36,8 @@ import {
} from '../../general-setting/plans/actions';
import * as styles from './styles.css';
export const WorkspaceSettingBilling = ({
workspaceMetadata,
}: {
workspaceMetadata: WorkspaceMetadata;
}) => {
// useWorkspace hook is a vary heavy operation here, but we need syncing name and avatar changes here,
// we don't have a better way to do this now
const workspace = useWorkspace(workspaceMetadata);
export const WorkspaceSettingBilling = () => {
const workspace = useService(WorkspaceService).workspace;
const t = useI18n();
@@ -68,7 +61,7 @@ export const WorkspaceSettingBilling = ({
}
return (
<FrameworkScope scope={workspace.scope}>
<>
<SettingHeader
title={t['com.affine.payment.billing-setting.title']()}
subtitle={t['com.affine.payment.billing-setting.subtitle']()}
@@ -87,7 +80,7 @@ export const WorkspaceSettingBilling = ({
<SettingWrapper title={t['com.affine.payment.billing-setting.history']()}>
<BillingHistory />
</SettingWrapper>
</FrameworkScope>
</>
);
};

View File

@@ -1,5 +1,6 @@
import { CurrentWorkspaceScopeProvider } from '@affine/core/components/providers/current-workspace-scope';
import type { SettingTab } from '@affine/core/modules/dialogs/constant';
import type { WorkspaceMetadata } from '@affine/core/modules/workspace';
import { useMemo } from 'react';
import type { SettingState } from '../types';
import { WorkspaceSettingBilling } from './billing';
@@ -7,31 +8,32 @@ import { WorkspaceSettingDetail } from './new-workspace-setting-detail';
import { WorkspaceSettingProperties } from './properties';
export const WorkspaceSetting = ({
workspaceMetadata,
activeTab,
onCloseSetting,
onChangeSettingState,
}: {
workspaceMetadata: WorkspaceMetadata;
activeTab: SettingTab;
onCloseSetting: () => void;
onChangeSettingState: (settingState: SettingState) => void;
}) => {
switch (activeTab) {
case 'workspace:preference':
return (
<WorkspaceSettingDetail
onCloseSetting={onCloseSetting}
onChangeSettingState={onChangeSettingState}
workspaceMetadata={workspaceMetadata}
/>
);
case 'workspace:properties':
return (
<WorkspaceSettingProperties workspaceMetadata={workspaceMetadata} />
);
case 'workspace:billing':
return <WorkspaceSettingBilling workspaceMetadata={workspaceMetadata} />;
}
return null;
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>
);
};

View File

@@ -5,25 +5,18 @@ import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hoo
import { useSystemOnline } from '@affine/core/components/hooks/use-system-online';
import { DesktopApiService } from '@affine/core/modules/desktop-api';
import { WorkspacePermissionService } from '@affine/core/modules/permissions';
import type {
Workspace,
WorkspaceMetadata,
} from '@affine/core/modules/workspace';
import type { Workspace } from '@affine/core/modules/workspace';
import { useI18n } from '@affine/i18n';
import track from '@affine/track';
import { useLiveData, useService } from '@toeverything/infra';
import { useState } from 'react';
interface ExportPanelProps {
workspaceMetadata: WorkspaceMetadata;
workspace: Workspace | null;
workspace: Workspace;
}
export const DesktopExportPanel = ({
workspaceMetadata,
workspace,
}: ExportPanelProps) => {
const workspaceId = workspaceMetadata.id;
export const DesktopExportPanel = ({ workspace }: ExportPanelProps) => {
const workspaceId = workspace.id;
const workspacePermissionService = useService(
WorkspacePermissionService
).permission;

View File

@@ -3,13 +3,13 @@ import {
SettingRow,
SettingWrapper,
} from '@affine/component/setting-components';
import { useWorkspace } from '@affine/core/components/hooks/use-workspace';
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 { ArrowRightSmallIcon } from '@blocksuite/icons/rc';
import { FrameworkScope } from '@toeverything/infra';
import { FrameworkScope, useService } from '@toeverything/infra';
import { useCallback } from 'react';
import { DeleteLeaveWorkspace } from './delete-leave-workspace';
@@ -24,18 +24,15 @@ import type { WorkspaceSettingDetailProps } from './types';
import { WorkspaceQuotaPanel } from './workspace-quota';
export const WorkspaceSettingDetail = ({
workspaceMetadata,
onCloseSetting,
onChangeSettingState,
}: WorkspaceSettingDetailProps) => {
const t = useI18n();
// useWorkspace hook is a vary heavy operation here, but we need syncing name and avatar changes here,
// we don't have a better way to do this now
const workspace = useWorkspace(workspaceMetadata);
const workspace = useService(WorkspaceService).workspace;
const server = workspace?.scope.get(WorkspaceServerService).server;
const workspaceInfo = useWorkspaceInfo(workspaceMetadata);
const workspaceInfo = useWorkspaceInfo(workspace);
const handleResetSyncStatus = useCallback(() => {
workspace?.engine.doc
@@ -80,10 +77,7 @@ export const WorkspaceSettingDetail = ({
<SharingPanel />
{BUILD_CONFIG.isElectron && (
<SettingWrapper title={t['Storage and Export']()}>
<DesktopExportPanel
workspace={workspace}
workspaceMetadata={workspaceMetadata}
/>
<DesktopExportPanel workspace={workspace} />
</SettingWrapper>
)}
<SettingWrapper>

View File

@@ -1,9 +1,6 @@
import type { WorkspaceMetadata } from '@affine/core/modules/workspace';
import type { SettingState } from '../../types';
export interface WorkspaceSettingDetailProps {
workspaceMetadata: WorkspaceMetadata;
onCloseSetting: () => void;
onChangeSettingState: (settingState: SettingState) => void;
}

View File

@@ -4,13 +4,12 @@ import { DocPropertyManager } from '@affine/core/components/doc-properties/manag
import { CreatePropertyMenuItems } from '@affine/core/components/doc-properties/menu/create-doc-property';
import { useWorkspaceInfo } from '@affine/core/components/hooks/use-workspace-info';
import type { DocCustomPropertyInfo } from '@affine/core/modules/db';
import type { WorkspaceMetadata } from '@affine/core/modules/workspace';
import { WorkspaceService } from '@affine/core/modules/workspace';
import { Trans, useI18n } from '@affine/i18n';
import track from '@affine/track';
import { FrameworkScope } from '@toeverything/infra';
import { FrameworkScope, useService } from '@toeverything/infra';
import { useCallback } from 'react';
import { useWorkspace } from '../../../../../components/hooks/use-workspace';
import * as styles from './styles.css';
const WorkspaceSettingPropertiesMain = () => {
@@ -47,14 +46,10 @@ const WorkspaceSettingPropertiesMain = () => {
);
};
export const WorkspaceSettingProperties = ({
workspaceMetadata,
}: {
workspaceMetadata: WorkspaceMetadata;
}) => {
export const WorkspaceSettingProperties = () => {
const t = useI18n();
const workspace = useWorkspace(workspaceMetadata);
const workspaceInfo = useWorkspaceInfo(workspaceMetadata);
const workspace = useService(WorkspaceService).workspace;
const workspaceInfo = useWorkspaceInfo(workspace);
const title = workspaceInfo?.name || 'untitled';
if (workspace === null) {

View File

@@ -265,11 +265,7 @@ export function FallbackHeaderWithWorkspaceNavigator() {
return (
<div className={styles.fallbackHeader}>
{currentWorkspace && navigate ? (
<WorkspaceNavigator
showSettingsButton
showSyncStatus
showEnableCloudButton
/>
<WorkspaceNavigator showSyncStatus showEnableCloudButton />
) : (
<FallbackHeaderSkeleton />
)}

View File

@@ -35,7 +35,8 @@ export type { ServerConfig } from './types';
import { type Framework } from '@toeverything/infra';
import { DocScope, DocService } from '../doc';
import { DocScope } from '../doc/scopes/doc';
import { DocService } from '../doc/services/doc';
import { GlobalCache, GlobalState, GlobalStateService } from '../storage';
import { UrlService } from '../url';
import { WorkspaceScope, WorkspaceService } from '../workspace';

View File

@@ -1,6 +1,6 @@
import type { Framework } from '@toeverything/infra';
import { WorkspaceServerService } from '../cloud';
import { WorkspaceServerService } from '../cloud/services/workspace-server';
import { WorkspaceScope, WorkspaceService } from '../workspace';
import { WorkspaceDB } from './entities/db';
import { WorkspaceDBTable } from './entities/table';

View File

@@ -26,11 +26,7 @@ export type GLOBAL_DIALOG_SCHEMA = {
templateMode: DocMode;
snapshotUrl: string;
}) => void;
setting: (props: {
activeTab?: SettingTab;
workspaceMetadata?: WorkspaceMetadata | null;
scrollAnchor?: 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;

View File

@@ -8,7 +8,7 @@ export { DocsService } from './services/docs';
import type { Framework } from '@toeverything/infra';
import { WorkspaceDBService } from '../db';
import { WorkspaceDBService } from '../db/services/db';
import { WorkspaceScope, WorkspaceService } from '../workspace';
import { Doc } from './entities/doc';
import { DocPropertyList } from './entities/property-list';