finalize new media settings ui

This commit is contained in:
dswbx
2025-02-06 13:58:29 +01:00
parent 46cf310ad6
commit 9a4c2bd530
11 changed files with 58 additions and 103 deletions

View File

@@ -1,27 +0,0 @@
import { describe, expect, test } from "bun:test";
import * as utils from "../../../src/core/utils/objects";
describe("object utils", () => {
test("flattenObject", () => {
const obj = {
a: {
b: {
c: 1,
a: ["a", "b", "c"]
}
},
d: [1, 2, { e: 3 }]
};
console.log("flat", utils.flattenObject2(obj));
expect(utils.flattenObject2(obj)).toEqual({
"a.b.c": 1,
"a.b.a[0]": "a",
"a.b.a[1]": "b",
"a.b.a[2]": "c",
"d[0]": 1,
"d[1]": 2,
"d[2].e": 3
});
});
});

View File

@@ -3,7 +3,7 @@
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,
"bin": "./dist/cli/index.js", "bin": "./dist/cli/index.js",
"version": "0.7.0-rc.4", "version": "0.7.0-rc.5",
"description": "Lightweight Firebase/Supabase alternative built to run anywhere — incl. Next.js, Remix, Astro, Cloudflare, Bun, Node, AWS Lambda & more.", "description": "Lightweight Firebase/Supabase alternative built to run anywhere — incl. Next.js, Remix, Astro, Cloudflare, Bun, Node, AWS Lambda & more.",
"homepage": "https://bknd.io", "homepage": "https://bknd.io",
"repository": { "repository": {

View File

@@ -166,29 +166,6 @@ export function flattenObject(obj: any, parentKey = "", result: any = {}): any {
return result; return result;
} }
export function flattenObject2(obj: any, parentKey = "", result: any = {}): any {
for (const key in obj) {
if (key in obj) {
const newKey = parentKey ? `${parentKey}.${key}` : key;
if (typeof obj[key] === "object" && obj[key] !== null && !Array.isArray(obj[key])) {
flattenObject2(obj[key], newKey, result);
} else if (Array.isArray(obj[key])) {
obj[key].forEach((item, index) => {
const arrayKey = `${newKey}[${index}]`;
if (typeof item === "object" && item !== null) {
flattenObject2(item, arrayKey, result);
} else {
result[arrayKey] = item;
}
});
} else {
result[newKey] = obj[key];
}
}
}
return result;
}
export function objectDepth(object: object): number { export function objectDepth(object: object): number {
let level = 1; let level = 1;
for (const key in object) { for (const key in object) {

View File

@@ -1,10 +1,7 @@
import { IconAlertHexagon } from "@tabler/icons-react";
import type { ModuleConfigs, ModuleSchemas } from "modules"; import type { ModuleConfigs, ModuleSchemas } from "modules";
import { getDefaultConfig, getDefaultSchema } from "modules/ModuleManager"; import { getDefaultConfig, getDefaultSchema } from "modules/ModuleManager";
import { createContext, startTransition, useContext, useEffect, useRef, useState } from "react"; import { createContext, startTransition, useContext, useEffect, useRef, useState } from "react";
import { useApi } from "ui/client"; import { useApi } from "ui/client";
import { Button } from "ui/components/buttons/Button";
import { Alert } from "ui/components/display/Alert";
import { type TSchemaActions, getSchemaActions } from "./schema/actions"; import { type TSchemaActions, getSchemaActions } from "./schema/actions";
import { AppReduced } from "./utils/AppReduced"; import { AppReduced } from "./utils/AppReduced";
@@ -14,7 +11,6 @@ type BkndContext = {
config: ModuleConfigs; config: ModuleConfigs;
permissions: string[]; permissions: string[];
hasSecrets: boolean; hasSecrets: boolean;
fetched: boolean;
requireSecrets: () => Promise<void>; requireSecrets: () => Promise<void>;
actions: ReturnType<typeof getSchemaActions>; actions: ReturnType<typeof getSchemaActions>;
app: AppReduced; app: AppReduced;
@@ -104,7 +100,7 @@ export function BkndProvider({
return ( return (
<BkndContext.Provider <BkndContext.Provider
value={{ ...schema, fetched, actions, requireSecrets, app, adminOverride, hasSecrets }} value={{ ...schema, actions, requireSecrets, app, adminOverride, hasSecrets }}
key={local_version} key={local_version}
> >
{/*{error && ( {/*{error && (

View File

@@ -22,7 +22,7 @@ export type AnyOfFieldContext = {
select: (index: number | null) => void; select: (index: number | null) => void;
options: string[]; options: string[];
errors: JsonError[]; errors: JsonError[];
selectSchema: any; selectSchema: JSONSchema;
}; };
const AnyOfContext = createContext<AnyOfFieldContext>(undefined!); const AnyOfContext = createContext<AnyOfFieldContext>(undefined!);
@@ -39,8 +39,9 @@ const Root = ({ path = "", schema: _schema, children }: AnyOfFieldRootProps) =>
const [selected, setSelected] = useState<number | null>(matchedIndex > -1 ? matchedIndex : null); const [selected, setSelected] = useState<number | null>(matchedIndex > -1 ? matchedIndex : null);
const options = schemas.map((s, i) => s.title ?? `Option ${i + 1}`); const options = schemas.map((s, i) => s.title ?? `Option ${i + 1}`);
const selectSchema = { const selectSchema = {
type: "string",
enum: options enum: options
}; } satisfies JSONSchema;
//console.log("AnyOf:root", { value, matchedIndex, selected, schema }); //console.log("AnyOf:root", { value, matchedIndex, selected, schema });
const selectedSchema = const selectedSchema =

View File

@@ -35,36 +35,36 @@ export const ArrayField = ({
}; };
} }
const Wrapper = ({ children }) => ( // if unique items with enum
<FieldWrapper pointer={path} schema={schema} wrapper="fieldset">
{children}
</FieldWrapper>
);
if (schema.uniqueItems && typeof schema.items === "object" && "enum" in schema.items) { if (schema.uniqueItems && typeof schema.items === "object" && "enum" in schema.items) {
return ( return (
<Wrapper> <FieldWrapper pointer={path} schema={schema} wrapper="fieldset">
<Formy.Select <FieldComponent
required required
options={schema.items.enum} schema={schema.items}
multiple multiple
value={value} value={value}
className="h-auto" className="h-auto"
onChange={(e) => { onChange={(e: any) => {
// @ts-ignore
const selected = Array.from(e.target.selectedOptions).map((o) => o.value); const selected = Array.from(e.target.selectedOptions).map((o) => o.value);
console.log("selected", selected);
setValue(pointer, selected); setValue(pointer, selected);
}} }}
/> />
</Wrapper> </FieldWrapper>
); );
} }
return ( return (
<Wrapper> <FieldWrapper pointer={path} schema={schema} wrapper="fieldset">
{value?.map((v, index: number) => { {value?.map((v, index: number) => {
const pointer = `${path}/${index}`.replace(/\/+/g, "/"); const pointer = `${path}/${index}`.replace(/\/+/g, "/");
const [, , subschema] = getMultiSchemaMatched(schema.items, v); let subschema = schema.items;
if (itemsMultiSchema) {
const [, , _subschema] = getMultiSchemaMatched(schema.items, v);
subschema = _subschema;
}
return ( return (
<div key={pointer} className="flex flex-row gap-2"> <div key={pointer} className="flex flex-row gap-2">
<FieldComponent <FieldComponent
@@ -80,22 +80,24 @@ export const ArrayField = ({
</div> </div>
); );
})} })}
{itemsMultiSchema ? ( <div className="flex flex-row">
<Dropdown {itemsMultiSchema ? (
dropdownWrapperProps={{ <Dropdown
className: "min-w-0" dropdownWrapperProps={{
}} className: "min-w-0"
items={itemsMultiSchema.map((s, i) => ({ }}
label: s!.title ?? `Option ${i + 1}`, items={itemsMultiSchema.map((s, i) => ({
onClick: () => handleAdd(ctx.lib.getTemplate(undefined, s!)) label: s!.title ?? `Option ${i + 1}`,
}))} onClick: () => handleAdd(ctx.lib.getTemplate(undefined, s!))
onClickItem={console.log} }))}
> onClickItem={console.log}
<Button IconLeft={IconLibraryPlus}>Add</Button> >
</Dropdown> <Button IconLeft={IconLibraryPlus}>Add</Button>
) : ( </Dropdown>
<Button onClick={() => handleAdd()}>Add</Button> ) : (
)} <Button onClick={() => handleAdd()}>Add</Button>
</Wrapper> )}
</div>
</FieldWrapper>
); );
}; };

View File

@@ -5,7 +5,7 @@ import { ArrayField } from "./ArrayField";
import { FieldWrapper } from "./FieldWrapper"; import { FieldWrapper } from "./FieldWrapper";
import { useFieldContext } from "./Form"; import { useFieldContext } from "./Form";
import { ObjectField } from "./ObjectField"; import { ObjectField } from "./ObjectField";
import { coerce, isType, isTypeSchema } from "./utils"; import { coerce, enumToOptions, isType, isTypeSchema } from "./utils";
export type FieldProps = { export type FieldProps = {
name: string; name: string;
@@ -77,17 +77,12 @@ export const FieldComponent = ({
schema, schema,
...props ...props
}: { schema: JSONSchema } & ComponentPropsWithoutRef<"input">) => { }: { schema: JSONSchema } & ComponentPropsWithoutRef<"input">) => {
if (!schema || typeof schema === "boolean") return null; if (!isTypeSchema(schema)) return null;
//console.log("field", props.name, props.disabled);
if (schema.enum) { if (schema.enum) {
if (!Array.isArray(schema.enum)) return null; return (
let options = schema.enum; <Formy.Select id={props.name} {...(props as any)} options={enumToOptions(schema.enum)} />
if (schema.enum.every((v) => typeof v === "string")) { );
options = schema.enum.map((v, i) => ({ value: i, label: v }));
}
return <Formy.Select id={props.name} {...(props as any)} options={options} />;
} }
if (isType(schema.type, ["number", "integer"])) { if (isType(schema.type, ["number", "integer"])) {

View File

@@ -45,6 +45,7 @@ export function FieldWrapper({
> >
{debug && ( {debug && (
<div className="absolute right-0 top-0"> <div className="absolute right-0 top-0">
{/* @todo: use radix */}
<Popover> <Popover>
<Popover.Target> <Popover.Target>
<IconButton Icon={IconBug} size="xs" className="opacity-30" /> <IconButton Icon={IconBug} size="xs" className="opacity-30" />

View File

@@ -223,3 +223,10 @@ export function omitSchema<Given extends JSONSchema>(_schema: Given, keys: strin
export function isTypeSchema(schema?: JSONSchema): schema is Exclude<JSONSchema, boolean> { export function isTypeSchema(schema?: JSONSchema): schema is Exclude<JSONSchema, boolean> {
return typeof schema === "object" && "type" in schema && !isType(schema.type, "error"); return typeof schema === "object" && "type" in schema && !isType(schema.type, "error");
} }
export function enumToOptions(_enum: any) {
if (!Array.isArray(_enum)) return [];
return _enum.map((v, i) =>
typeof v === "string" ? { value: v, label: v } : { value: i, label: v }
);
}

View File

@@ -5,6 +5,7 @@ import {
Field, Field,
Form, Form,
FormContextOverride, FormContextOverride,
FormDebug,
ObjectField ObjectField
} from "ui/components/form/json-schema-form"; } from "ui/components/form/json-schema-form";
import { Scrollable } from "ui/layouts/AppShell/AppShell"; import { Scrollable } from "ui/layouts/AppShell/AppShell";
@@ -89,7 +90,7 @@ export default function JsonSchemaForm3() {
> >
<AutoForm /> <AutoForm />
</Form>*/} </Form>*/}
{/*<Form <Form
schema={{ schema={{
type: "object", type: "object",
properties: { properties: {
@@ -113,9 +114,11 @@ export default function JsonSchemaForm3() {
} }
}} }}
initialValues={{ tags: [0, 1] }} initialValues={{ tags: [0, 1] }}
options={{ debug: true }}
> >
<AutoForm /> <Field name="" />
</Form>*/} <FormDebug />
</Form>
{/*<Form {/*<Form
schema={{ schema={{
@@ -139,7 +142,7 @@ export default function JsonSchemaForm3() {
/>*/} />*/}
{/*<CustomMediaForm />*/} {/*<CustomMediaForm />*/}
<Form schema={schema.media} initialValues={config.media} /> {/*<Form schema={schema.media} initialValues={config.media} />*/}
{/*<Form {/*<Form
schema={removeKeyRecursively(schema.media, "pattern") as any} schema={removeKeyRecursively(schema.media, "pattern") as any}

BIN
bun.lockb

Binary file not shown.