дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 19:25:54 +02:00
perf(settings): open the Settings window shell before its startup chain (#1175)
The Settings window ran its whole startup chain (importing every settings page, then the project and settings IPC, one after another) before opening the dialog, so it showed an empty title bar for a while on every open. The dialog now opens at once with a loading state while the pages and the settings load in parallel, and re-bases the open dialog when the settings arrive; the search field is focused once the pages are in. Co-authored-by: ppxb <[email protected]>
Цей коміт міститься в:
1 джерело
85c03d33af
коміт
8687f6b1e3
6 files changed
+55
-15
No files matched your search
@@ -38,7 +38,10 @@ describe("the native Settings window", () => {
|
||||
it("opens even when a page fails to register, and logs why", async () => {
|
||||
vi.spyOn(console, "error").mockImplementation(() => {});
|
||||
const dispose = activateSettingsWindow("main");
|
||||
expect(useSettingsDialog.getState().open).toBe(true);
|
||||
expect(useSettingsDialog.getState().pagesLoading).toBe(true);
|
||||
await vi.waitFor(() => expect(useSettingsDialog.getState().open).toBe(true));
|
||||
await vi.waitFor(() => expect(useSettingsDialog.getState().pagesLoading).toBe(false));
|
||||
expect(logFrontend).toHaveBeenCalledWith(
|
||||
"error",
|
||||
expect.stringContaining("cannot register settings pages: Error: page failed"),
|
||||
|
||||
@@ -63,6 +63,15 @@
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
.loading {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 0;
|
||||
color: var(--fg-subtle);
|
||||
font-size: var(--fs-small);
|
||||
}
|
||||
.pageHeader {
|
||||
flex: none;
|
||||
display: flex;
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
Icon,
|
||||
IconButton,
|
||||
SearchField,
|
||||
Spinner,
|
||||
Tree,
|
||||
type DialogButton,
|
||||
type TreeHandle,
|
||||
@@ -220,7 +221,7 @@ function SettingsDialogInner() {
|
||||
/** Page tree + current page; reads the shared settings staging store. */
|
||||
export function SettingsLayout() {
|
||||
const pages = useSettingsPages((s) => s.pages);
|
||||
const { pageId, query, staged, stagedSecrets, stagedExternal, saving } = useSettingsDialog();
|
||||
const { pageId, query, staged, stagedSecrets, stagedExternal, saving, pagesLoading } = useSettingsDialog();
|
||||
const treeRef = useRef<TreeHandle>(null);
|
||||
|
||||
const tree = useMemo(() => buildPageTree(pages.values()), [pages]);
|
||||
@@ -265,6 +266,16 @@ export function SettingsLayout() {
|
||||
}
|
||||
};
|
||||
|
||||
if (pagesLoading) {
|
||||
return (
|
||||
<div className={styles.layout}>
|
||||
<div className={styles.loading}>
|
||||
<Spinner size={16} /> <T>Loading…</T>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.layout}>
|
||||
<div className={styles.sidebar}>
|
||||
|
||||
@@ -24,6 +24,7 @@ import { LazySettingsDialog, loadBuiltinPages, unloadBuiltinPages } from "./dial
|
||||
import {
|
||||
closeSettings,
|
||||
openSettings,
|
||||
rebaseSettings,
|
||||
selectPage,
|
||||
setNewProjects,
|
||||
setQuery,
|
||||
@@ -208,20 +209,24 @@ export function activateSettingsWindow(parent: string): () => void {
|
||||
}),
|
||||
];
|
||||
void (async () => {
|
||||
let launch: SettingsLaunch | null = null;
|
||||
useSettingsDialog.setState({ pagesLoading: true });
|
||||
openSettings();
|
||||
try {
|
||||
// A page that fails to register is reported; the window still opens with the others.
|
||||
await loadBuiltinPages().catch((e) => logError("cannot register settings pages", e));
|
||||
await restoreRemote();
|
||||
const project = await commands.projectCurrent().catch(() => null);
|
||||
if (project) useProject.setState({ project, settings: project.settings });
|
||||
else await loadSettings().catch((e) => console.warn("cannot load settings", e));
|
||||
// `listenLaunch` may have taken the launch first and applied it already.
|
||||
launch = await takeLaunch();
|
||||
const launch = await takeLaunch();
|
||||
relaunch(launch);
|
||||
await Promise.all([
|
||||
loadBuiltinPages().catch((e) => logError("cannot register settings pages", e)),
|
||||
(async () => {
|
||||
await restoreRemote();
|
||||
const project = await commands.projectCurrent().catch(() => null);
|
||||
if (project) useProject.setState({ project, settings: project.settings });
|
||||
else await loadSettings().catch((e) => console.warn("cannot load settings", e));
|
||||
rebaseSettings();
|
||||
})(),
|
||||
]);
|
||||
} finally {
|
||||
// Without this the window would stay an empty title bar.
|
||||
openSettings(launch?.page, { newProjects: launch?.newProjects ?? useSettingsDialog.getState().newProjects });
|
||||
useSettingsDialog.setState({ pagesLoading: false });
|
||||
}
|
||||
})();
|
||||
})().catch((e) => logError("settings window startup failed", e));
|
||||
return () => disposers.reverse().forEach((d) => d());
|
||||
}
|
||||
@@ -46,6 +46,7 @@ export interface SettingsDialogState {
|
||||
scopes: Record<string, Scope>;
|
||||
saving: boolean;
|
||||
error: string | null;
|
||||
pagesLoading: boolean;
|
||||
}
|
||||
|
||||
export const DEFAULT_PAGE = "appearance";
|
||||
@@ -66,6 +67,7 @@ export const useSettingsDialog = create<SettingsDialogState>(() => ({
|
||||
scopes: {},
|
||||
saving: false,
|
||||
error: null,
|
||||
pagesLoading: false,
|
||||
}));
|
||||
|
||||
const stageKey = (scope: Scope, key: string) => `${scope}|${key}`;
|
||||
@@ -109,6 +111,12 @@ async function loadGlobalBase() {
|
||||
}
|
||||
}
|
||||
|
||||
export function rebaseSettings() {
|
||||
const base = validateSettings(useProject.getState().settings);
|
||||
useSettingsDialog.setState((s) => (s.open ? { base, globalBase: base } : {}));
|
||||
void loadGlobalBase();
|
||||
}
|
||||
|
||||
/** IDEA's File › New Projects Setup › Settings for New Projects. */
|
||||
export const settingsTitle = (newProjects: boolean) => (newProjects ? "Settings for New Projects" : "Settings");
|
||||
|
||||
@@ -129,6 +137,7 @@ export function closeSettings() {
|
||||
externalApplied: {},
|
||||
error: null,
|
||||
saving: false,
|
||||
pagesLoading: false,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -14,12 +14,15 @@ export function SettingsWindow() {
|
||||
const os = usePlatform((s) => s.os);
|
||||
const ownTitleBar = usePlatform((s) => s.overlayTitleBar || s.framelessTitleBar);
|
||||
const open = useSettingsDialog((s) => s.open);
|
||||
const pagesLoading = useSettingsDialog((s) => s.pagesLoading);
|
||||
const title = useSettingsTitle();
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) ref.current?.querySelector<HTMLElement>("[data-autofocus]")?.focus({ preventScroll: true });
|
||||
}, [open]);
|
||||
if (open && !pagesLoading) {
|
||||
ref.current?.querySelector<HTMLElement>("[data-autofocus]")?.focus({ preventScroll: true });
|
||||
}
|
||||
}, [open, pagesLoading]);
|
||||
|
||||
return (
|
||||
<div ref={ref} className={clsx(styles.window, os === "linux" && styles.windowEdge)}>
|
||||
|
||||
Reference in new issue
Заблокувати користувача