mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-25 18:26:05 +08:00
@@ -6,6 +6,7 @@ import {
|
||||
SettingWrapper,
|
||||
} from '@affine/component/setting-components';
|
||||
import { LanguageMenu } from '@affine/core/components/affine/language-menu';
|
||||
import { TraySettingService } from '@affine/core/modules/editor-setting/services/tray-settings';
|
||||
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
@@ -58,6 +59,28 @@ export const ThemeSettings = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const MenubarSetting = () => {
|
||||
const t = useI18n();
|
||||
const traySettingService = useService(TraySettingService);
|
||||
const { enabled } = useLiveData(traySettingService.setting$);
|
||||
return (
|
||||
<SettingWrapper
|
||||
id="menubar"
|
||||
title={t['com.affine.appearanceSettings.menubar.title']()}
|
||||
>
|
||||
<SettingRow
|
||||
name={t['com.affine.appearanceSettings.menubar.toggle']()}
|
||||
desc={t['com.affine.appearanceSettings.menubar.description']()}
|
||||
>
|
||||
<Switch
|
||||
checked={enabled}
|
||||
onChange={checked => traySettingService.setEnabled(checked)}
|
||||
/>
|
||||
</SettingRow>
|
||||
</SettingWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export const AppearanceSettings = () => {
|
||||
const t = useI18n();
|
||||
|
||||
@@ -150,6 +173,8 @@ export const AppearanceSettings = () => {
|
||||
)}
|
||||
</SettingWrapper>
|
||||
) : null}
|
||||
|
||||
{BUILD_CONFIG.isElectron ? <MenubarSetting /> : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -143,13 +143,11 @@ export const useGeneralSettingList = (): GeneralSettingList => {
|
||||
|
||||
interface GeneralSettingProps {
|
||||
activeTab: SettingTab;
|
||||
scrollAnchor?: string;
|
||||
onChangeSettingState: (settingState: SettingState) => void;
|
||||
}
|
||||
|
||||
export const GeneralSetting = ({
|
||||
activeTab,
|
||||
scrollAnchor,
|
||||
onChangeSettingState,
|
||||
}: GeneralSettingProps) => {
|
||||
switch (activeTab) {
|
||||
@@ -166,7 +164,7 @@ export const GeneralSetting = ({
|
||||
case 'about':
|
||||
return <AboutAffine />;
|
||||
case 'plans':
|
||||
return <AFFiNEPricingPlans scrollAnchor={scrollAnchor} />;
|
||||
return <AFFiNEPricingPlans />;
|
||||
case 'billing':
|
||||
return <BillingSettings onChangeSettingState={onChangeSettingState} />;
|
||||
case 'experimental-features':
|
||||
|
||||
@@ -11,7 +11,7 @@ import { CloudPlanLayout, PlanLayout } from './layout';
|
||||
import { PlansSkeleton } from './skeleton';
|
||||
import * as styles from './style.css';
|
||||
|
||||
const Settings = ({ scrollAnchor }: { scrollAnchor?: string }) => {
|
||||
const Settings = () => {
|
||||
const subscriptionService = useService(SubscriptionService);
|
||||
const prices = useLiveData(subscriptionService.prices.prices$);
|
||||
|
||||
@@ -24,23 +24,13 @@ const Settings = ({ scrollAnchor }: { scrollAnchor?: string }) => {
|
||||
return <PlansSkeleton />;
|
||||
}
|
||||
|
||||
return (
|
||||
<PlanLayout
|
||||
cloud={<CloudPlans />}
|
||||
ai={<AIPlan />}
|
||||
scrollAnchor={scrollAnchor}
|
||||
/>
|
||||
);
|
||||
return <PlanLayout cloud={<CloudPlans />} ai={<AIPlan />} />;
|
||||
};
|
||||
|
||||
export const AFFiNEPricingPlans = ({
|
||||
scrollAnchor,
|
||||
}: {
|
||||
scrollAnchor?: string;
|
||||
}) => {
|
||||
export const AFFiNEPricingPlans = () => {
|
||||
return (
|
||||
<SWRErrorBoundary FallbackComponent={PlansErrorBoundary}>
|
||||
<Settings scrollAnchor={scrollAnchor} />
|
||||
<Settings />
|
||||
</SWRErrorBoundary>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -8,11 +8,9 @@ import {
|
||||
type HtmlHTMLAttributes,
|
||||
type ReactNode,
|
||||
useCallback,
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { flushSync } from 'react-dom';
|
||||
|
||||
import * as styles from './layout.css';
|
||||
|
||||
@@ -69,24 +67,12 @@ export const PricingCollapsible = ({
|
||||
export interface PlanLayoutProps {
|
||||
cloud?: ReactNode;
|
||||
ai?: ReactNode;
|
||||
scrollAnchor?: string;
|
||||
}
|
||||
|
||||
export const PlanLayout = ({ cloud, ai, scrollAnchor }: PlanLayoutProps) => {
|
||||
export const PlanLayout = ({ cloud, ai }: PlanLayoutProps) => {
|
||||
const t = useI18n();
|
||||
const plansRootRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// TODO(@catsjuice): Need a better solution to handle this situation
|
||||
useLayoutEffect(() => {
|
||||
if (!scrollAnchor) return;
|
||||
flushSync(() => {
|
||||
const target = plansRootRef.current?.querySelector(`#${scrollAnchor}`);
|
||||
if (target) {
|
||||
target.scrollIntoView();
|
||||
}
|
||||
});
|
||||
}, [scrollAnchor]);
|
||||
|
||||
return (
|
||||
<div className={styles.plansLayoutRoot} ref={plansRootRef}>
|
||||
{/* TODO(@catsjuice): SettingHeader component shouldn't have margin itself */}
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { flushSync } from 'react-dom';
|
||||
|
||||
import { AccountSetting } from './account-setting';
|
||||
import { GeneralSetting } from './general-setting';
|
||||
@@ -39,6 +40,7 @@ import { WorkspaceSetting } from './workspace-setting';
|
||||
interface SettingProps extends ModalProps {
|
||||
activeTab?: SettingTab;
|
||||
onCloseSetting: () => void;
|
||||
scrollAnchor?: string;
|
||||
}
|
||||
|
||||
const isWorkspaceSetting = (key: string): boolean =>
|
||||
@@ -55,10 +57,11 @@ const CenteredLoading = () => {
|
||||
const SettingModalInner = ({
|
||||
activeTab: initialActiveTab = 'appearance',
|
||||
onCloseSetting,
|
||||
scrollAnchor: initialScrollAnchor,
|
||||
}: SettingProps) => {
|
||||
const [settingState, setSettingState] = useState<SettingState>({
|
||||
activeTab: initialActiveTab,
|
||||
scrollAnchor: undefined,
|
||||
scrollAnchor: initialScrollAnchor,
|
||||
});
|
||||
const globalContextService = useService(GlobalContextService);
|
||||
|
||||
@@ -150,6 +153,18 @@ const SettingModalInner = ({
|
||||
}
|
||||
}, [isSelfhosted, settingState.activeTab]);
|
||||
|
||||
useEffect(() => {
|
||||
if (settingState.scrollAnchor) {
|
||||
flushSync(() => {
|
||||
const target = modalContentRef.current?.querySelector(
|
||||
`#${settingState.scrollAnchor}`
|
||||
);
|
||||
if (target) {
|
||||
target.scrollIntoView();
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [settingState]);
|
||||
return (
|
||||
<FrameworkScope scope={currentServer.scope}>
|
||||
<SettingSidebar
|
||||
@@ -165,7 +180,6 @@ const SettingModalInner = ({
|
||||
<div className={style.centerContainer}>
|
||||
<div ref={modalContentRef} className={style.content}>
|
||||
<Suspense fallback={<WorkspaceDetailSkeleton />}>
|
||||
{}
|
||||
{settingState.activeTab === 'account' &&
|
||||
loginStatus === 'authenticated' ? (
|
||||
<AccountSetting onChangeSettingState={setSettingState} />
|
||||
@@ -178,7 +192,6 @@ const SettingModalInner = ({
|
||||
) : !isWorkspaceSetting(settingState.activeTab) ? (
|
||||
<GeneralSetting
|
||||
activeTab={settingState.activeTab}
|
||||
scrollAnchor={settingState.scrollAnchor}
|
||||
onChangeSettingState={setSettingState}
|
||||
/>
|
||||
) : null}
|
||||
@@ -223,6 +236,7 @@ const SettingModalInner = ({
|
||||
export const SettingDialog = ({
|
||||
close,
|
||||
activeTab,
|
||||
scrollAnchor,
|
||||
}: DialogComponentProps<WORKSPACE_DIALOG_SCHEMA['setting']>) => {
|
||||
return (
|
||||
<Modal
|
||||
@@ -242,7 +256,11 @@ export const SettingDialog = ({
|
||||
onOpenChange={() => close()}
|
||||
>
|
||||
<Suspense fallback={<CenteredLoading />}>
|
||||
<SettingModalInner activeTab={activeTab} onCloseSetting={close} />
|
||||
<SettingModalInner
|
||||
activeTab={activeTab}
|
||||
onCloseSetting={close}
|
||||
scrollAnchor={scrollAnchor}
|
||||
/>
|
||||
</Suspense>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -38,4 +38,11 @@ export class WorkspaceDialogService extends Service {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
closeAll() {
|
||||
const dialogs = this.dialogs$.value;
|
||||
dialogs.forEach(dialog => {
|
||||
this.close(dialog.id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { CurrentUserDBEditorSettingProvider } from './impls/user-db';
|
||||
import { EditorSettingProvider } from './provider/editor-setting-provider';
|
||||
import { EditorSettingService } from './services/editor-setting';
|
||||
import { SpellCheckSettingService } from './services/spell-check-setting';
|
||||
import { TraySettingService } from './services/tray-settings';
|
||||
export type { FontFamily } from './schema';
|
||||
export { EditorSettingSchema, fontStyleOptions } from './schema';
|
||||
export { EditorSettingService } from './services/editor-setting';
|
||||
@@ -30,3 +31,7 @@ export function configureSpellCheckSettingModule(framework: Framework) {
|
||||
DesktopApiService,
|
||||
]);
|
||||
}
|
||||
|
||||
export function configureTraySettingModule(framework: Framework) {
|
||||
framework.service(TraySettingService, [GlobalStateService]);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import type {
|
||||
MenubarStateKey,
|
||||
MenubarStateSchema,
|
||||
} from '@affine/electron/main/shared-state-schema';
|
||||
import { LiveData, Service } from '@toeverything/infra';
|
||||
|
||||
import type { GlobalStateService } from '../../storage';
|
||||
|
||||
const MENUBAR_SETTING_KEY: typeof MenubarStateKey = 'menubarState';
|
||||
|
||||
export class TraySettingService extends Service {
|
||||
constructor(private readonly globalStateService: GlobalStateService) {
|
||||
super();
|
||||
}
|
||||
|
||||
setting$ = LiveData.from(
|
||||
this.globalStateService.globalState.watch<MenubarStateSchema>(
|
||||
MENUBAR_SETTING_KEY
|
||||
),
|
||||
null
|
||||
).map(v => v ?? { enabled: true });
|
||||
|
||||
setEnabled(enabled: boolean) {
|
||||
this.globalStateService.globalState.set(MENUBAR_SETTING_KEY, {
|
||||
enabled,
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user