fix: open settings goes to the plugin's page and hides when it has none

This commit is contained in:
LukeGus committed 2026-10-06 22:26:35 -05:00
1 parent d1ddcd3ff9
commit 60024bdd4e
6 files changed
+96 -5

No files matched your search

+4
View File
@@ -1686,6 +1686,10 @@ export function AppShell({
tabLabel, tabLabel,
); );
} }
// Asking for a page again still goes there after the user moved away
if (type === "settings" && data?.section && data.revealAt === undefined) {
data = { ...data, revealAt: Date.now() };
}
const id = type; const id = type;
const singletonLabels: Partial<Record<TabType, string>> = { const singletonLabels: Partial<Record<TabType, string>> = {
"host-manager": t("nav.manage"), "host-manager": t("nav.manage"),
+3 -3
View File
@@ -37,6 +37,7 @@ import {
type PluginEntry, type PluginEntry,
} from "./plugin-model"; } from "./plugin-model";
import { openPluginSettings } from "./open-plugins"; import { openPluginSettings } from "./open-plugins";
import { hasSettingsPage } from "@/settings/settings-fields-util";
import { PluginReleaseNotes, PluginVideo } from "./PluginReleaseNotes"; import { PluginReleaseNotes, PluginVideo } from "./PluginReleaseNotes";
import type { PluginsManager } from "./use-plugins-manager"; import type { PluginsManager } from "./use-plugins-manager";
@@ -67,11 +68,10 @@ export function PluginDetail({
}, [plugin.version, plugin.latestVersion]); }, [plugin.version, plugin.latestVersion]);
const contributions = describeContributions(plugin.contributes); const contributions = describeContributions(plugin.contributes);
const settings = plugin.contributes?.settings as // Only people who can manage plugins get here, and they see admin settings
{ admin?: unknown[]; user?: unknown[] } | undefined;
const hasSettings = const hasSettings =
plugin.installed && plugin.installed &&
((settings?.admin?.length ?? 0) > 0 || (settings?.user?.length ?? 0) > 0); hasSettingsPage(plugin as Parameters<typeof hasSettingsPage>[0], true);
const issueUrl = reportIssueUrl( const issueUrl = reportIssueUrl(
plugin, plugin,
import.meta.env.VITE_APP_VERSION || undefined, import.meta.env.VITE_APP_VERSION || undefined,
+4 -1
View File
@@ -43,6 +43,8 @@ import {
export interface SettingsTabProps { export interface SettingsTabProps {
/** The page to open, from the tab's data. */ /** The page to open, from the tab's data. */
section?: string; section?: string;
/** Changes on every open request, so asking for the same page again still goes there. */
sectionAt?: string | number;
/** An element id on that page to scroll to, as "id@nonce". */ /** An element id on that page to scroll to, as "id@nonce". */
reveal?: string; reveal?: string;
username: string; username: string;
@@ -86,6 +88,7 @@ function revealText(container: HTMLElement, needle: string): boolean {
/** Everything that configures the app, one page at a time. */ /** Everything that configures the app, one page at a time. */
export function SettingsTab({ export function SettingsTab({
section, section,
sectionAt,
reveal, reveal,
username, username,
isAdmin, isAdmin,
@@ -141,7 +144,7 @@ export function SettingsTab({
const [current, setCurrent] = useState(section ?? "account"); const [current, setCurrent] = useState(section ?? "account");
useEffect(() => { useEffect(() => {
if (section) setCurrent(section); if (section) setCurrent(section);
}, [section]); }, [section, sectionAt]);
// The page renders lazily, so the target may take a moment to appear. // The page renders lazily, so the target may take a moment to appear.
useEffect(() => { useEffect(() => {
+21
View File
@@ -19,3 +19,24 @@ export function isFieldShown(
export function hasVisibleFields(fields: PluginSettingsField[]): boolean { export function hasVisibleFields(fields: PluginSettingsField[]): boolean {
return fields.some((field) => !field.hidden); return fields.some((field) => !field.hidden);
} }
/** Whether Settings lists a page for this plugin, by the same rule it uses. */
export function hasSettingsPage(
plugin: {
enabled: boolean;
contributes?: {
settings?: {
user?: PluginSettingsField[];
admin?: PluginSettingsField[];
};
} | null;
},
isAdmin: boolean,
): boolean {
const settings = plugin.contributes?.settings;
return (
plugin.enabled &&
(hasVisibleFields(settings?.user ?? []) ||
(isAdmin && hasVisibleFields(settings?.admin ?? [])))
);
}
+7 -1
View File
@@ -227,7 +227,7 @@ function noop() {}
export interface TabRenderContext { export interface TabRenderContext {
shell: TabShellCallbacks; shell: TabShellCallbacks;
/** What the Settings tab needs from the shell. */ /** What the Settings tab needs from the shell. */
settings?: Omit<SettingsTabProps, "section">; settings?: Omit<SettingsTabProps, "section" | "sectionAt">;
/** The terminal a panel shown as a tab sends commands to. */ /** The terminal a panel shown as a tab sends commands to. */
panelTargetTab?: Tab; panelTargetTab?: Tab;
isVisible?: boolean; isVisible?: boolean;
@@ -266,6 +266,12 @@ export function renderTabContent(tab: Tab, context: TabRenderContext) {
? tab.data.section ? tab.data.section
: undefined : undefined
} }
sectionAt={
typeof tab.data?.revealAt === "number" ||
typeof tab.data?.revealAt === "string"
? tab.data.revealAt
: undefined
}
reveal={ reveal={
typeof tab.data?.reveal === "string" typeof tab.data?.reveal === "string"
? `${tab.data.reveal}@${String(tab.data.revealAt ?? "")}` ? `${tab.data.reveal}@${String(tab.data.revealAt ?? "")}`
@@ -0,0 +1,57 @@
import { describe, expect, it } from "vitest";
import {
hasSettingsPage,
hasVisibleFields,
} from "@/settings/settings-fields-util";
import type { PluginSettingsField } from "@/api/plugins-api";
const field = (hidden = false) =>
({ key: "a", type: "string", hidden }) as unknown as PluginSettingsField;
describe("hasVisibleFields", () => {
it("ignores hidden fields", () => {
expect(hasVisibleFields([field(true)])).toBe(false);
expect(hasVisibleFields([field(true), field()])).toBe(true);
});
});
describe("hasSettingsPage", () => {
it("needs an enabled plugin", () => {
expect(
hasSettingsPage(
{ enabled: false, contributes: { settings: { user: [field()] } } },
true,
),
).toBe(false);
});
it("is false with no settings or only hidden ones", () => {
expect(hasSettingsPage({ enabled: true, contributes: null }, true)).toBe(
false,
);
expect(
hasSettingsPage(
{ enabled: true, contributes: { settings: { user: [field(true)] } } },
true,
),
).toBe(false);
});
it("counts admin settings only for admins", () => {
const plugin = {
enabled: true,
contributes: { settings: { admin: [field()] } },
};
expect(hasSettingsPage(plugin, true)).toBe(true);
expect(hasSettingsPage(plugin, false)).toBe(false);
});
it("counts user settings for anyone", () => {
expect(
hasSettingsPage(
{ enabled: true, contributes: { settings: { user: [field()] } } },
false,
),
).toBe(true);
});
});