mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-29 06:35:24 +00:00
fix: home tab, layout switch and missing layout fallbacks for looks
This commit is contained in:
1 parent
4c235bb23f
commit
ec3cc9cf18
14 files changed
+761
-11
No files matched your search
@@ -13,6 +13,7 @@ import {
|
||||
LAYOUT_SETTING,
|
||||
LOOK_ID_PATTERN,
|
||||
THEME_SETTING,
|
||||
installedLayoutIds,
|
||||
resolveDefaultLook,
|
||||
safeLayoutForced,
|
||||
unavailableLooks,
|
||||
@@ -35,7 +36,7 @@ async function adminDefaults() {
|
||||
* /user-preferences/look:
|
||||
* get:
|
||||
* summary: Get the layout and theme to show the current user
|
||||
* description: The user's own choice, the admin defaults, the default look of the bundled plugins, whether TERMIX_SAFE_LAYOUT forces the repair view, and installed layout or theme plugins that could not load. The browser resolves the final look from these.
|
||||
* description: The user's own choice, the admin defaults (a layout no installed plugin brings reads as null), the default look of the bundled plugins, whether TERMIX_SAFE_LAYOUT forces the repair view, and installed layout or theme plugins that could not load. The browser resolves the final look from these.
|
||||
* tags:
|
||||
* - User Preferences
|
||||
* responses:
|
||||
@@ -101,10 +102,14 @@ router.get("/", authenticateJWT, async (req: Request, res: Response) => {
|
||||
const preferences =
|
||||
await createCurrentUserPreferenceRepository().findByUserId(userId);
|
||||
const { loader } = getPluginRuntime();
|
||||
const installed = installedLayoutIds(loader);
|
||||
const known = (id: string | null | undefined) =>
|
||||
id && installed.has(id) ? id : null;
|
||||
const defaults = await adminDefaults();
|
||||
res.json({
|
||||
layoutId: preferences?.layoutId ?? null,
|
||||
layoutId: known(preferences?.layoutId),
|
||||
themeId: preferences?.themeId ?? null,
|
||||
defaults: await adminDefaults(),
|
||||
defaults: { ...defaults, layout: known(defaults.layout) },
|
||||
defaultLook: resolveDefaultLook(loader),
|
||||
safeLayout: safeLayoutForced(),
|
||||
unavailable: unavailableLooks(loader),
|
||||
|
||||
@@ -14,6 +14,7 @@ export interface DefaultLook {
|
||||
|
||||
interface LookLoader {
|
||||
get(pluginId: string): Pick<LoadedPlugin, "manifest"> | undefined;
|
||||
list(): Array<Pick<LoadedPlugin, "manifest">>;
|
||||
skipped(): SkippedPlugin[];
|
||||
}
|
||||
|
||||
@@ -46,6 +47,24 @@ export function unavailableLooks(loader: LookLoader): SkippedPlugin[] {
|
||||
.filter((plugin) => plugin.layouts.length > 0 || plugin.themes.length > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Layout ids some installed plugin brings, whether it runs, is turned off or
|
||||
* failed to load. A choice outside this set was uninstalled, so the next
|
||||
* choice down applies instead of the repair view.
|
||||
*/
|
||||
export function installedLayoutIds(loader: LookLoader): Set<string> {
|
||||
const ids = new Set<string>();
|
||||
for (const plugin of loader.list()) {
|
||||
for (const layout of plugin.manifest.contributes?.layouts ?? []) {
|
||||
ids.add(layout.id);
|
||||
}
|
||||
}
|
||||
for (const plugin of loader.skipped()) {
|
||||
for (const id of plugin.layouts) ids.add(id);
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
/** TERMIX_SAFE_LAYOUT=1 starts everyone in the layout repair view. */
|
||||
export function safeLayoutForced(): boolean {
|
||||
const value = process.env.TERMIX_SAFE_LAYOUT?.trim().toLowerCase();
|
||||
|
||||
@@ -11,6 +11,7 @@ const state = vi.hoisted(() => ({
|
||||
audits: [] as unknown[],
|
||||
skipped: [] as unknown[],
|
||||
defaultLookIds: [] as string[],
|
||||
installed: [] as string[],
|
||||
}));
|
||||
|
||||
vi.mock("../../../utils/logger.js", () => ({
|
||||
@@ -69,6 +70,15 @@ vi.mock("../../../plugins/index.js", () => ({
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
list: () => [
|
||||
{
|
||||
manifest: {
|
||||
contributes: {
|
||||
layouts: state.installed.map((id) => ({ id, label: id })),
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
skipped: () => state.skipped,
|
||||
},
|
||||
}),
|
||||
@@ -90,6 +100,7 @@ beforeEach(async () => {
|
||||
state.audits = [];
|
||||
state.skipped = [];
|
||||
state.defaultLookIds = [];
|
||||
state.installed = [];
|
||||
delete process.env.TERMIX_SAFE_LAYOUT;
|
||||
|
||||
const app = express();
|
||||
@@ -133,6 +144,7 @@ describe("GET /user-preferences/look", () => {
|
||||
|
||||
it("returns the user's pick, the admin defaults and the default look", async () => {
|
||||
state.prefs = { layoutId: "mine", themeId: "nord" };
|
||||
state.installed = ["mine", "admin-layout"];
|
||||
state.settings.set("ui.layout", "admin-layout");
|
||||
state.settings.set("ui.theme", "gruvbox");
|
||||
state.defaultLookIds = ["shipped-look"];
|
||||
@@ -162,6 +174,31 @@ describe("GET /user-preferences/look", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("GET /user-preferences/look with an uninstalled layout", () => {
|
||||
it("drops a choice no installed plugin brings, so the next one applies", async () => {
|
||||
state.prefs = { layoutId: "deleted", themeId: null };
|
||||
state.settings.set("ui.layout", "also-deleted");
|
||||
state.installed = ["kept"];
|
||||
const { body } = await getLook();
|
||||
expect(body.layoutId).toBeNull();
|
||||
expect(body.defaults.layout).toBeNull();
|
||||
});
|
||||
|
||||
it("keeps a choice whose plugin failed to load, for the repair view", async () => {
|
||||
state.prefs = { layoutId: "b", themeId: null };
|
||||
state.skipped = [
|
||||
{
|
||||
id: "broken",
|
||||
version: "1.0.0",
|
||||
layouts: ["b"],
|
||||
themes: [],
|
||||
error: "x",
|
||||
},
|
||||
];
|
||||
expect((await getLook()).body.layoutId).toBe("b");
|
||||
});
|
||||
});
|
||||
|
||||
describe("PUT /user-preferences/look/defaults", () => {
|
||||
it("is for admins only", async () => {
|
||||
const { status } = await putDefaults({ layout: "x" });
|
||||
|
||||
@@ -6,6 +6,7 @@ vi.mock("../../plugins/bundled-index.js", () => ({
|
||||
}));
|
||||
|
||||
import {
|
||||
installedLayoutIds,
|
||||
resolveDefaultLook,
|
||||
safeLayoutForced,
|
||||
unavailableLooks,
|
||||
@@ -23,6 +24,10 @@ function loaderWith(
|
||||
plugins[id]
|
||||
? ({ manifest: { contributes: plugins[id] } } as never)
|
||||
: undefined,
|
||||
list: () =>
|
||||
Object.values(plugins).map(
|
||||
(contributes) => ({ manifest: { contributes } }) as never,
|
||||
),
|
||||
skipped: () => skipped,
|
||||
};
|
||||
}
|
||||
@@ -103,3 +108,14 @@ describe("safeLayoutForced", () => {
|
||||
expect(safeLayoutForced()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("installedLayoutIds", () => {
|
||||
it("lists layouts of loaded and skipped plugins", () => {
|
||||
const ids = installedLayoutIds(
|
||||
loaderWith({ a: { layouts: [{ id: "one" }], themes: [{ id: "t" }] } }, [
|
||||
{ id: "b", version: null, layouts: ["two"], themes: [], error: "x" },
|
||||
]),
|
||||
);
|
||||
expect([...ids].sort()).toEqual(["one", "two"]);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user