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",
"sideEffects": false,
"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.",
"homepage": "https://bknd.io",
"repository": {

View File

@@ -166,29 +166,6 @@ export function flattenObject(obj: any, parentKey = "", result: any = {}): any {
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 {
let level = 1;
for (const key in object) {

View File

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

View File

@@ -22,7 +22,7 @@ export type AnyOfFieldContext = {
select: (index: number | null) => void;
options: string[];
errors: JsonError[];
selectSchema: any;
selectSchema: JSONSchema;
};
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 options = schemas.map((s, i) => s.title ?? `Option ${i + 1}`);
const selectSchema = {
type: "string",
enum: options
};
} satisfies JSONSchema;
//console.log("AnyOf:root", { value, matchedIndex, selected, schema });
const selectedSchema =

View File

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

View File

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

View File

@@ -45,6 +45,7 @@ export function FieldWrapper({
>
{debug && (
<div className="absolute right-0 top-0">
{/* @todo: use radix */}
<Popover>
<Popover.Target>
<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> {
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,
Form,
FormContextOverride,
FormDebug,
ObjectField
} from "ui/components/form/json-schema-form";
import { Scrollable } from "ui/layouts/AppShell/AppShell";
@@ -89,7 +90,7 @@ export default function JsonSchemaForm3() {
>
<AutoForm />
</Form>*/}
{/*<Form
<Form
schema={{
type: "object",
properties: {
@@ -113,9 +114,11 @@ export default function JsonSchemaForm3() {
}
}}
initialValues={{ tags: [0, 1] }}
options={{ debug: true }}
>
<AutoForm />
</Form>*/}
<Field name="" />
<FormDebug />
</Form>
{/*<Form
schema={{
@@ -139,7 +142,7 @@ export default function JsonSchemaForm3() {
/>*/}
{/*<CustomMediaForm />*/}
<Form schema={schema.media} initialValues={config.media} />
{/*<Form schema={schema.media} initialValues={config.media} />*/}
{/*<Form
schema={removeKeyRecursively(schema.media, "pattern") as any}