finalize new media settings

This commit is contained in:
dswbx
2025-02-05 17:54:48 +01:00
parent 8418231c43
commit 46cf310ad6
12 changed files with 102 additions and 86 deletions

View File

@@ -14,6 +14,7 @@ type BkndContext = {
config: ModuleConfigs;
permissions: string[];
hasSecrets: boolean;
fetched: boolean;
requireSecrets: () => Promise<void>;
actions: ReturnType<typeof getSchemaActions>;
app: AppReduced;
@@ -103,7 +104,7 @@ export function BkndProvider({
return (
<BkndContext.Provider
value={{ ...schema, actions, requireSecrets, app, adminOverride, hasSecrets }}
value={{ ...schema, fetched, actions, requireSecrets, app, adminOverride, hasSecrets }}
key={local_version}
>
{/*{error && (

View File

@@ -1,26 +1,15 @@
import type { TAppMediaConfig } from "media/media-schema";
import { useBknd } from "ui/client/BkndProvider";
export function useBkndMedia() {
const { config, schema, actions: bkndActions } = useBknd();
const actions = {
/*roles: {
add: async (name: string, data: any = {}) => {
console.log("add role", name, data);
return await bkndActions.add("auth", `roles.${name}`, data);
},
patch: async (name: string, data: any) => {
console.log("patch role", name, data);
return await bkndActions.patch("auth", `roles.${name}`, data);
},
delete: async (name: string) => {
console.log("delete role", name);
if (window.confirm(`Are you sure you want to delete the role "${name}"?`)) {
return await bkndActions.remove("auth", `roles.${name}`);
}
return false;
config: {
patch: async (data: Partial<TAppMediaConfig>) => {
return await bkndActions.set("media", data, true);
}
}*/
}
};
const $media = {};

View File

@@ -20,7 +20,7 @@ const styles = {
default: "bg-primary/5 hover:bg-primary/10 link text-primary/70",
primary: "bg-primary hover:bg-primary/80 link text-background",
ghost: "bg-transparent hover:bg-primary/5 link text-primary/70",
outline: "border border-primary/50 bg-transparent hover:bg-primary/5 link text-primary/80",
outline: "border border-primary/20 bg-transparent hover:bg-primary/5 link text-primary/80",
red: "dark:bg-red-950 dark:hover:bg-red-900 bg-red-100 hover:bg-red-200 link text-primary/70",
subtlered:
"dark:text-red-950 text-red-700 dark:hover:bg-red-900 bg-transparent hover:bg-red-50 link"
@@ -51,7 +51,7 @@ const Base = ({
}: BaseProps) => ({
...props,
className: twMerge(
"flex flex-row flex-nowrap items-center font-semibold disabled:opacity-50 cursor-pointer disabled:cursor-not-allowed",
"flex flex-row flex-nowrap items-center font-semibold disabled:opacity-50 cursor-pointer disabled:cursor-not-allowed transition-[opacity,background-color,color,border-color]",
sizes[size ?? "default"],
styles[variant ?? "default"],
props.className

View File

@@ -38,10 +38,11 @@ export const Field = ({ name, schema: _schema, onChange, label: _label, hidden }
setValue(pointer, value as any);*/
const value = coerce(e.target.value, schema as any, { required });
//console.log("handleChange", pointer, e.target.value, { value });
if (typeof value === "undefined" && !required) {
//console.log("handleChange", pointer, e.target.value, { value }, ctx.options);
if (typeof value === "undefined" && !required && ctx.options?.keepEmpty !== true) {
ctx.deleteValue(pointer);
} else {
//console.log("setValue", pointer, value);
setValue(pointer, value);
}
}

View File

@@ -2,7 +2,7 @@ import { Popover } from "@mantine/core";
import { IconBug } from "@tabler/icons-react";
import type { JsonError } from "json-schema-library";
import type { JSONSchema } from "json-schema-to-ts";
import { Children, type ReactElement, type ReactNode, cloneElement } from "react";
import { Children, type ReactElement, type ReactNode, cloneElement, isValidElement } from "react";
import { IconButton } from "ui/components/buttons/IconButton";
import { JsonViewer } from "ui/components/code/JsonViewer";
import * as Formy from "ui/components/form/Formy";
@@ -14,7 +14,7 @@ export type FieldwrapperProps = {
required?: boolean;
errors?: JsonError[];
schema?: Exclude<JSONSchema, boolean>;
debug?: object;
debug?: object | boolean;
wrapper?: "group" | "fieldset";
hidden?: boolean;
children: ReactElement | ReactNode;
@@ -26,7 +26,7 @@ export function FieldWrapper({
required,
errors = [],
schema,
debug = {},
debug,
wrapper,
hidden,
children
@@ -43,20 +43,28 @@ export function FieldWrapper({
as={wrapper === "fieldset" ? "fieldset" : "div"}
className={hidden ? "hidden" : "relative"}
>
{/*<div className="absolute right-0 top-0">
<Popover>
<Popover.Target>
<IconButton Icon={IconBug} size="xs" className="opacity-30" />
</Popover.Target>
<Popover.Dropdown>
<JsonViewer
json={{ ...debug, pointer, required, schema, errors }}
expand={6}
className="p-0"
/>
</Popover.Dropdown>
</Popover>
</div>*/}
{debug && (
<div className="absolute right-0 top-0">
<Popover>
<Popover.Target>
<IconButton Icon={IconBug} size="xs" className="opacity-30" />
</Popover.Target>
<Popover.Dropdown>
<JsonViewer
json={{
...(typeof debug === "object" ? debug : {}),
pointer,
required,
schema,
errors
}}
expand={6}
className="p-0"
/>
</Popover.Dropdown>
</Popover>
</div>
)}
{label && (
<Formy.Label as={wrapper === "fieldset" ? "legend" : "label"} htmlFor={pointer}>
@@ -65,9 +73,9 @@ export function FieldWrapper({
)}
<div className="flex flex-row gap-2">
<div className="flex flex-1 flex-col gap-3">
{children}
{/*{Children.count(children) === 1
{Children.count(children) === 1 && isValidElement(children)
? cloneElement(children, {
// @ts-ignore
list: examples.length > 0 ? examplesId : undefined
})
: children}
@@ -77,7 +85,7 @@ export function FieldWrapper({
<option key={i} value={e as any} />
))}
</datalist>
)}*/}
)}
</div>
</div>
{description && <Formy.Help>{description}</Formy.Help>}

View File

@@ -14,6 +14,7 @@ import {
useRef,
useState
} from "react";
import { JsonViewer } from "ui/components/code/JsonViewer";
import { Field } from "./Field";
import { isRequired, normalizePath, omitSchema, prefixPointer } from "./utils";
@@ -32,6 +33,10 @@ export type FormProps<
onSubmit?: (data: Partial<Data>) => void | Promise<void>;
onInvalidSubmit?: (errors: JsonError[], data: Partial<Data>) => void;
hiddenSubmit?: boolean;
options?: {
debug?: boolean;
keepEmpty?: boolean;
};
};
export type FormContext<Data> = {
@@ -44,6 +49,7 @@ export type FormContext<Data> = {
submitting: boolean;
schema: JSONSchema;
lib: Draft2019;
options: FormProps["options"];
};
const FormContext = createContext<FormContext<any>>(undefined!);
@@ -62,6 +68,7 @@ export function Form<
validateOn = "submit",
hiddenSubmit = true,
ignoreKeys = [],
options = {},
...props
}: FormProps<Schema, Data>) {
const [schema, initial] = omitSchema(_schema, ignoreKeys, _initialValues);
@@ -146,7 +153,8 @@ export function Form<
deleteValue,
errors,
schema,
lib
lib,
options
} as any;
//console.log("context", context);
@@ -229,3 +237,10 @@ export function Subscribe({ children }: { children: (ctx: FormContext<any>) => R
const ctx = useFormContext();
return children(ctx);
}
export function FormDebug() {
const { options, data, dirty, errors, submitting } = useFormContext();
if (options?.debug !== true) return null;
return <JsonViewer json={{ dirty, submitting, data, errors }} expand={99} />;
}

View File

@@ -1,33 +1,17 @@
import { IconPhoto } from "@tabler/icons-react";
import { IconAlertHexagon } from "@tabler/icons-react";
import { TbSettings } from "react-icons/tb";
import { useBknd } from "ui/client/BkndProvider";
import { IconButton } from "ui/components/buttons/IconButton";
import { Empty } from "ui/components/display/Empty";
import { Link } from "ui/components/wouter/Link";
import { Media } from "ui/elements";
import { useBrowserTitle } from "ui/hooks/use-browser-title";
import * as AppShell from "ui/layouts/AppShell/AppShell";
import { useLocation } from "wouter";
export function MediaRoot({ children }) {
const { app, config } = useBknd();
const [, navigate] = useLocation();
const mediaDisabled = !config.media.enabled;
useBrowserTitle(["Media"]);
if (!config.media.enabled) {
return (
<Empty
Icon={IconPhoto}
title="Media not enabled"
description="Please enable media in the settings to continue."
primary={{
children: "Manage Settings",
onClick: () => navigate(app.getSettingsPath(["media"]))
}}
/>
);
}
return (
<>
<AppShell.Sidebar>
@@ -42,12 +26,13 @@ export function MediaRoot({ children }) {
</AppShell.SectionHeader>
<AppShell.Scrollable initialOffset={96}>
<div className="flex flex-col flex-grow p-3 gap-3">
{/*<div>
<SearchInput placeholder="Search buckets" />
</div>*/}
<nav className="flex flex-col flex-1 gap-1">
<AppShell.SidebarLink as={Link} href={"/"}>
Main Bucket
<AppShell.SidebarLink
as={Link}
href={"/"}
className="flex flex-row justify-between"
>
Main Bucket {mediaDisabled && <IconAlertHexagon className="size-5" />}
</AppShell.SidebarLink>
<AppShell.SidebarLink as={Link} href={"/settings"}>
Settings

View File

@@ -1,10 +1,30 @@
import { IconPhoto } from "@tabler/icons-react";
import { useBknd } from "ui/client/BkndProvider";
import { Empty } from "ui/components/display/Empty";
import { Media } from "ui/elements";
import { useBrowserTitle } from "ui/hooks/use-browser-title";
import * as AppShell from "ui/layouts/AppShell/AppShell";
import { useLocation } from "wouter";
export function MediaIndex() {
const { app, config } = useBknd();
const [, navigate] = useLocation();
useBrowserTitle(["Media"]);
if (!config.media.enabled) {
return (
<Empty
Icon={IconPhoto}
title="Media not enabled"
description="Please enable media in the settings to continue."
primary={{
children: "Manage Settings",
onClick: () => navigate("/settings")
}}
/>
);
}
return (
<AppShell.Scrollable>
<div className="flex flex-1 p-3">

View File

@@ -1,4 +1,5 @@
import { IconBrandAws, IconCloud, IconServer } from "@tabler/icons-react";
import { isDebug } from "core";
import { autoFormatString } from "core/utils";
import { twMerge } from "tailwind-merge";
import { useBknd } from "ui/client/BkndProvider";
@@ -11,6 +12,7 @@ import {
Field,
Form,
FormContextOverride,
FormDebug,
ObjectField,
Subscribe
} from "ui/components/form/json-schema-form";
@@ -29,24 +31,23 @@ export function MediaSettings(props) {
return <MediaSettingsInternal {...props} />;
}
const ignore = ["entity_name", "basepath"];
const formConfig = {
ignoreKeys: ["entity_name", "basepath"],
options: { debug: isDebug(), keepEmpty: true }
};
function MediaSettingsInternal() {
const { config, schema } = useBkndMedia();
const { config, schema, actions } = useBkndMedia();
async function onSubmit(data: any) {
console.log("submit", data);
await new Promise((resolve) => setTimeout(resolve, 1000));
await actions.config.patch(data);
//await new Promise((resolve) => setTimeout(resolve, 1000));
}
return (
<>
<Form
schema={schema}
initialValues={config as any}
ignoreKeys={ignore}
onSubmit={onSubmit}
noValidate
>
<Form schema={schema} initialValues={config as any} onSubmit={onSubmit} {...formConfig}>
<Subscribe>
{({ dirty, errors, submitting }) => (
<AppShell.SectionHeader
@@ -79,11 +80,7 @@ function MediaSettingsInternal() {
<Adapters />
</AnyOf.Root>
</div>
{/*<Subscribe>
{({ data, errors }) => (
<JsonViewer json={JSON.parse(JSON.stringify({ data, errors }))} expand={999} />
)}
</Subscribe>*/}
<FormDebug />
</AppShell.Scrollable>
</Form>
</>
@@ -105,7 +102,7 @@ function Adapters() {
<Formy.Group>
<Formy.Label className="flex flex-row items-center gap-1">
<span className="font-bold">Media Adapter:</span>
{!ctx.selected && <span className="opacity-70"> (Choose one)</span>}
{ctx.selected === null && <span className="opacity-70"> (Choose one)</span>}
</Formy.Label>
<div className="flex flex-row gap-1 mb-2">
{ctx.schemas?.map((schema: any, i) => (

View File

@@ -23,8 +23,8 @@ export default function JsonSchemaFormReactTest() {
<>
<Form
schema={schema}
onChange={setData}
onSubmit={setData}
/*onChange={setData}
onSubmit={setData}*/
validator={validator}
validationMode="change"
>