removed admin config from server, theme is now client side, fixed module manager migrations

This commit is contained in:
dswbx
2025-03-11 17:41:33 +01:00
parent de89e26ffb
commit 96781f5d3d
33 changed files with 1572 additions and 319 deletions

View File

@@ -4,7 +4,13 @@ import { createContext, startTransition, useContext, useEffect, useRef, useState
import { useApi } from "ui/client";
import { type TSchemaActions, getSchemaActions } from "./schema/actions";
import { AppReduced } from "./utils/AppReduced";
import type { AppTheme } from "ui/client/use-theme";
export type BkndAdminOptions = {
logo_return_path?: string;
basepath?: string;
theme?: AppTheme;
};
type BkndContext = {
version: number;
schema: ModuleSchemas;
@@ -14,7 +20,7 @@ type BkndContext = {
requireSecrets: () => Promise<void>;
actions: ReturnType<typeof getSchemaActions>;
app: AppReduced;
adminOverride?: ModuleConfigs["server"]["admin"];
options: BkndAdminOptions;
fallback: boolean;
};
@@ -29,13 +35,15 @@ enum Fetching {
export function BkndProvider({
includeSecrets = false,
adminOverride,
options,
children,
fallback = null,
}: { includeSecrets?: boolean; children: any; fallback?: React.ReactNode } & Pick<
BkndContext,
"adminOverride"
>) {
}: {
includeSecrets?: boolean;
children: any;
fallback?: React.ReactNode;
options?: BkndAdminOptions;
}) {
const [withSecrets, setWithSecrets] = useState<boolean>(includeSecrets);
const [schema, setSchema] =
useState<Pick<BkndContext, "version" | "schema" | "config" | "permissions" | "fallback">>();
@@ -93,13 +101,6 @@ export function BkndProvider({
fallback: true,
} as any);
if (adminOverride) {
newSchema.config.server.admin = {
...newSchema.config.server.admin,
...adminOverride,
};
}
startTransition(() => {
document.startViewTransition(() => {
setSchema(newSchema);
@@ -122,13 +123,13 @@ export function BkndProvider({
}, []);
if (!fetched || !schema) return fallback;
const app = new AppReduced(schema?.config as any);
const app = new AppReduced(schema?.config as any, options);
const actions = getSchemaActions({ api, setSchema, reloadSchema });
const hasSecrets = withSecrets && !error;
return (
<BkndContext.Provider
value={{ ...schema, actions, requireSecrets, app, adminOverride, hasSecrets }}
value={{ ...schema, actions, requireSecrets, app, options: app.options, hasSecrets }}
key={local_version}
>
{/*{error && (
@@ -153,3 +154,12 @@ export function useBknd({ withSecrets }: { withSecrets?: boolean } = {}): BkndCo
return ctx;
}
export function useBkndOptions(): BkndAdminOptions {
const ctx = useContext(BkndContext);
return (
ctx.options ?? {
basepath: "/",
}
);
}

View File

@@ -1,6 +1,5 @@
import { Api, type ApiOptions, type TApiUser } from "Api";
import { isDebug } from "core";
import type { AppTheme } from "modules/server/AppServer";
import { createContext, useContext } from "react";
const ClientContext = createContext<{ baseUrl: string; api: Api }>({
@@ -62,7 +61,6 @@ export const useBaseUrl = () => {
type BkndWindowContext = {
user?: TApiUser;
logout_route: string;
color_scheme?: AppTheme;
};
export function useBkndWindowContext(): BkndWindowContext {
if (typeof window !== "undefined" && window.__BKND__) {

View File

@@ -1 +1 @@
export { BkndProvider, useBknd } from "./BkndProvider";
export { BkndProvider, type BkndAdminOptions, useBknd } from "./BkndProvider";

View File

@@ -29,17 +29,3 @@ export function useBkndSystem() {
actions,
};
}
export function useBkndSystemTheme() {
const $sys = useBkndSystem();
return {
theme: $sys.theme,
set: $sys.actions.theme.set,
toggle: async () => {
document.startViewTransition(async () => {
await $sys.actions.theme.toggle();
});
},
};
}

View File

@@ -1,29 +1,49 @@
import type { AppTheme } from "modules/server/AppServer";
import { useBkndWindowContext } from "ui/client/ClientProvider";
import { useBknd } from "ui/client/bknd";
import { create } from "zustand";
import { combine, persist } from "zustand/middleware";
const themes = ["dark", "light", "system"] as const;
export type AppTheme = (typeof themes)[number];
const themeStore = create(
persist(
combine({ theme: null as AppTheme | null }, (set) => ({
setTheme: (theme: AppTheme | any) => {
if (themes.includes(theme)) {
document.startViewTransition(() => {
set({ theme });
});
}
},
})),
{
name: "bknd-admin-theme",
},
),
);
export function useTheme(fallback: AppTheme = "system") {
const b = useBknd();
const winCtx = useBkndWindowContext();
const theme_state = themeStore((state) => state.theme);
const theme_set = themeStore((state) => state.setTheme);
// 1. override
// 2. config
// 3. winCtx
// 4. fallback
// 5. default
const override = b?.adminOverride?.color_scheme;
const config = b?.config.server.admin.color_scheme;
const win = winCtx.color_scheme;
// 2. local storage
// 3. fallback
// 4. default
const override = b?.options?.theme;
const prefersDark =
typeof window !== "undefined" && window.matchMedia("(prefers-color-scheme: dark)").matches;
const theme = override ?? config ?? win ?? fallback;
const theme = override ?? theme_state ?? fallback;
return {
theme: (theme === "system" ? (prefersDark ? "dark" : "light") : theme) as AppTheme,
value: theme,
themes,
setTheme: theme_set,
state: theme_state,
prefersDark,
override,
config,
win,
};
}

View File

@@ -2,6 +2,7 @@ import type { App } from "App";
import { type Entity, type EntityRelation, constructEntity, constructRelation } from "data";
import { RelationAccessor } from "data/relations/RelationAccessor";
import { Flow, TaskMap } from "flows";
import type { BkndAdminOptions } from "ui/client/BkndProvider";
export type AppType = ReturnType<App["toJSON"]>;
@@ -15,7 +16,10 @@ export class AppReduced {
private _relations: EntityRelation[] = [];
private _flows: Flow[] = [];
constructor(protected appJson: AppType) {
constructor(
protected appJson: AppType,
protected _options: BkndAdminOptions = {},
) {
//console.log("received appjson", appJson);
this._entities = Object.entries(this.appJson.data.entities ?? {}).map(([name, entity]) => {
@@ -62,18 +66,21 @@ export class AppReduced {
return this.appJson;
}
getAdminConfig() {
return this.appJson.server.admin;
get options() {
return {
basepath: "",
logo_return_path: "/",
...this._options,
};
}
getSettingsPath(path: string[] = []): string {
const { basepath } = this.getAdminConfig();
const base = `~/${basepath}/settings`.replace(/\/+/g, "/");
const base = `~/${this.options.basepath}/settings`.replace(/\/+/g, "/");
return [base, ...path].join("/");
}
getAbsolutePath(path?: string): string {
const { basepath } = this.getAdminConfig();
const { basepath } = this.options;
return (path ? `~/${basepath}/${path}` : `~/${basepath}`).replace(/\/+/g, "/");
}

View File

@@ -1,28 +0,0 @@
import { useState } from "react";
export type AppTheme = "light" | "dark" | string;
export function useSetTheme(initialTheme: AppTheme = "light") {
const [theme, _setTheme] = useState(initialTheme);
const $html = document.querySelector("#bknd-admin")!;
function setTheme(newTheme: AppTheme) {
$html?.classList.remove("dark", "light");
$html?.classList.add(newTheme);
_setTheme(newTheme);
// @todo: just a quick switcher config update test
fetch("/api/system/config/patch/server/admin", {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ color_scheme: newTheme }),
})
.then((res) => res.json())
.then((data) => {
console.log("theme updated", data);
});
}
return { theme, setTheme };
}