mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 04:27:21 +00:00
using path instead of pointer, replaced lodash usage
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
import { describe, expect, test } from "bun:test";
|
import { describe, expect, test } from "bun:test";
|
||||||
import { Perf } from "../../src/core/utils";
|
import { Perf } from "../../src/core/utils";
|
||||||
import * as reqres from "../../src/core/utils/reqres";
|
import * as utils from "../../src/core/utils";
|
||||||
import * as strings from "../../src/core/utils/strings";
|
|
||||||
|
|
||||||
async function wait(ms: number) {
|
async function wait(ms: number) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
@@ -13,7 +12,7 @@ describe("Core Utils", async () => {
|
|||||||
describe("[core] strings", async () => {
|
describe("[core] strings", async () => {
|
||||||
test("objectToKeyValueArray", async () => {
|
test("objectToKeyValueArray", async () => {
|
||||||
const obj = { a: 1, b: 2, c: 3 };
|
const obj = { a: 1, b: 2, c: 3 };
|
||||||
const result = strings.objectToKeyValueArray(obj);
|
const result = utils.objectToKeyValueArray(obj);
|
||||||
expect(result).toEqual([
|
expect(result).toEqual([
|
||||||
{ key: "a", value: 1 },
|
{ key: "a", value: 1 },
|
||||||
{ key: "b", value: 2 },
|
{ key: "b", value: 2 },
|
||||||
@@ -22,24 +21,24 @@ describe("Core Utils", async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("snakeToPascalWithSpaces", async () => {
|
test("snakeToPascalWithSpaces", async () => {
|
||||||
const result = strings.snakeToPascalWithSpaces("snake_to_pascal");
|
const result = utils.snakeToPascalWithSpaces("snake_to_pascal");
|
||||||
expect(result).toBe("Snake To Pascal");
|
expect(result).toBe("Snake To Pascal");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("randomString", async () => {
|
test("randomString", async () => {
|
||||||
const result = strings.randomString(10);
|
const result = utils.randomString(10);
|
||||||
expect(result).toHaveLength(10);
|
expect(result).toHaveLength(10);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("pascalToKebab", async () => {
|
test("pascalToKebab", async () => {
|
||||||
const result = strings.pascalToKebab("PascalCase");
|
const result = utils.pascalToKebab("PascalCase");
|
||||||
expect(result).toBe("pascal-case");
|
expect(result).toBe("pascal-case");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("replaceSimplePlaceholders", async () => {
|
test("replaceSimplePlaceholders", async () => {
|
||||||
const str = "Hello, {$name}!";
|
const str = "Hello, {$name}!";
|
||||||
const vars = { name: "John" };
|
const vars = { name: "John" };
|
||||||
const result = strings.replaceSimplePlaceholders(str, vars);
|
const result = utils.replaceSimplePlaceholders(str, vars);
|
||||||
expect(result).toBe("Hello, John!");
|
expect(result).toBe("Hello, John!");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -49,7 +48,7 @@ describe("Core Utils", async () => {
|
|||||||
const headers = new Headers();
|
const headers = new Headers();
|
||||||
headers.append("Content-Type", "application/json");
|
headers.append("Content-Type", "application/json");
|
||||||
headers.append("Authorization", "Bearer 123");
|
headers.append("Authorization", "Bearer 123");
|
||||||
const obj = reqres.headersToObject(headers);
|
const obj = utils.headersToObject(headers);
|
||||||
expect(obj).toEqual({
|
expect(obj).toEqual({
|
||||||
"content-type": "application/json",
|
"content-type": "application/json",
|
||||||
authorization: "Bearer 123"
|
authorization: "Bearer 123"
|
||||||
@@ -59,21 +58,21 @@ describe("Core Utils", async () => {
|
|||||||
test("replaceUrlParam", () => {
|
test("replaceUrlParam", () => {
|
||||||
const url = "/api/:id/:name";
|
const url = "/api/:id/:name";
|
||||||
const params = { id: "123", name: "test" };
|
const params = { id: "123", name: "test" };
|
||||||
const result = reqres.replaceUrlParam(url, params);
|
const result = utils.replaceUrlParam(url, params);
|
||||||
expect(result).toBe("/api/123/test");
|
expect(result).toBe("/api/123/test");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("encode", () => {
|
test("encode", () => {
|
||||||
const obj = { id: "123", name: "test" };
|
const obj = { id: "123", name: "test" };
|
||||||
const result = reqres.encodeSearch(obj);
|
const result = utils.encodeSearch(obj);
|
||||||
expect(result).toBe("id=123&name=test");
|
expect(result).toBe("id=123&name=test");
|
||||||
|
|
||||||
const obj2 = { id: "123", name: ["test1", "test2"] };
|
const obj2 = { id: "123", name: ["test1", "test2"] };
|
||||||
const result2 = reqres.encodeSearch(obj2);
|
const result2 = utils.encodeSearch(obj2);
|
||||||
expect(result2).toBe("id=123&name=test1&name=test2");
|
expect(result2).toBe("id=123&name=test1&name=test2");
|
||||||
|
|
||||||
const obj3 = { id: "123", name: { test: "test" } };
|
const obj3 = { id: "123", name: { test: "test" } };
|
||||||
const result3 = reqres.encodeSearch(obj3, { encode: true });
|
const result3 = utils.encodeSearch(obj3, { encode: true });
|
||||||
expect(result3).toBe("id=123&name=%7B%22test%22%3A%22test%22%7D");
|
expect(result3).toBe("id=123&name=%7B%22test%22%3A%22test%22%7D");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -108,4 +107,91 @@ describe("Core Utils", async () => {
|
|||||||
expect(count).toBe(2);
|
expect(count).toBe(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("objects", () => {
|
||||||
|
test("omitKeys", () => {
|
||||||
|
const objects = [
|
||||||
|
[{ a: 1, b: 2, c: 3 }, ["a"], { b: 2, c: 3 }],
|
||||||
|
[{ a: 1, b: 2, c: 3 }, ["b"], { a: 1, c: 3 }],
|
||||||
|
[{ a: 1, b: 2, c: 3 }, ["c"], { a: 1, b: 2 }],
|
||||||
|
[{ a: 1, b: 2, c: 3 }, ["a", "b"], { c: 3 }],
|
||||||
|
[{ a: 1, b: 2, c: 3 }, ["a", "b", "c"], {}]
|
||||||
|
] as [object, string[], object][];
|
||||||
|
|
||||||
|
for (const [obj, keys, expected] of objects) {
|
||||||
|
const result = utils.omitKeys(obj, keys as any);
|
||||||
|
expect(result).toEqual(expected);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("isEqual", () => {
|
||||||
|
const objects = [
|
||||||
|
[1, 1, true],
|
||||||
|
[1, "1", false],
|
||||||
|
[1, 2, false],
|
||||||
|
["1", "1", true],
|
||||||
|
["1", "2", false],
|
||||||
|
[true, true, true],
|
||||||
|
[true, false, false],
|
||||||
|
[false, false, true],
|
||||||
|
[1, NaN, false],
|
||||||
|
[NaN, NaN, true],
|
||||||
|
[null, null, true],
|
||||||
|
[null, undefined, false],
|
||||||
|
[undefined, undefined, true],
|
||||||
|
[new Map([["a", 1]]), new Map([["a", 1]]), true],
|
||||||
|
[new Map([["a", 1]]), new Map([["a", 2]]), false],
|
||||||
|
[new Map([["a", 1]]), new Map([["b", 1]]), false],
|
||||||
|
[
|
||||||
|
new Map([["a", 1]]),
|
||||||
|
new Map([
|
||||||
|
["a", 1],
|
||||||
|
["b", 2]
|
||||||
|
]),
|
||||||
|
false
|
||||||
|
],
|
||||||
|
[{ a: 1 }, { a: 1 }, true],
|
||||||
|
[{ a: 1 }, { a: 2 }, false],
|
||||||
|
[{ a: 1 }, { b: 1 }, false],
|
||||||
|
[{ a: "1" }, { a: "1" }, true],
|
||||||
|
[{ a: "1" }, { a: "2" }, false],
|
||||||
|
[{ a: "1" }, { b: "1" }, false],
|
||||||
|
[{ a: 1 }, { a: 1, b: 2 }, false],
|
||||||
|
[{ a: [1, 2, 3] }, { a: [1, 2, 3] }, true],
|
||||||
|
[{ a: [1, 2, 3] }, { a: [1, 2, 4] }, false],
|
||||||
|
[{ a: [1, 2, 3] }, { a: [1, 2, 3, 4] }, false],
|
||||||
|
[{ a: { b: 1 } }, { a: { b: 1 } }, true],
|
||||||
|
[{ a: { b: 1 } }, { a: { b: 2 } }, false],
|
||||||
|
[{ a: { b: 1 } }, { a: { c: 1 } }, false],
|
||||||
|
[{ a: { b: 1 } }, { a: { b: 1, c: 2 } }, false],
|
||||||
|
[[1, 2, 3], [1, 2, 3], true],
|
||||||
|
[[1, 2, 3], [1, 2, 4], false],
|
||||||
|
[[1, 2, 3], [1, 2, 3, 4], false],
|
||||||
|
[[{ a: 1 }], [{ a: 1 }], true],
|
||||||
|
[[{ a: 1 }], [{ a: 2 }], false],
|
||||||
|
[[{ a: 1 }], [{ b: 1 }], false]
|
||||||
|
] as [any, any, boolean][];
|
||||||
|
|
||||||
|
for (const [a, b, expected] of objects) {
|
||||||
|
const result = utils.isEqual(a, b);
|
||||||
|
expect(result).toEqual(expected);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getPath", () => {
|
||||||
|
const tests = [
|
||||||
|
[{ a: 1, b: 2, c: 3 }, "a", 1],
|
||||||
|
[{ a: 1, b: 2, c: 3 }, "b", 2],
|
||||||
|
[{ a: { b: 1 } }, "a.b", 1],
|
||||||
|
[{ a: { b: 1 } }, "a.b.c", null, null],
|
||||||
|
[{ a: { b: 1 } }, "a.b.c", 1, 1],
|
||||||
|
[[[1]], "0.0", 1]
|
||||||
|
] as [object, string, any, any][];
|
||||||
|
|
||||||
|
for (const [obj, path, expected, defaultValue] of tests) {
|
||||||
|
const result = utils.getPath(obj, path, defaultValue);
|
||||||
|
expect(result).toEqual(expected);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,23 +2,9 @@ import { describe, expect, test } from "bun:test";
|
|||||||
import { Draft2019 } from "json-schema-library";
|
import { Draft2019 } from "json-schema-library";
|
||||||
import type { JSONSchema } from "json-schema-to-ts";
|
import type { JSONSchema } from "json-schema-to-ts";
|
||||||
import * as utils from "../../src/ui/components/form/json-schema-form/utils";
|
import * as utils from "../../src/ui/components/form/json-schema-form/utils";
|
||||||
|
import type { IsTypeType } from "../../src/ui/components/form/json-schema-form/utils";
|
||||||
|
|
||||||
describe("json form", () => {
|
describe("json form", () => {
|
||||||
test("normalize path", () => {
|
|
||||||
const examples = [
|
|
||||||
["description", "#/description"],
|
|
||||||
["/description", "#/description"],
|
|
||||||
["nested/property", "#/nested/property"],
|
|
||||||
["nested.property", "#/nested/property"],
|
|
||||||
["nested.property[0]", "#/nested/property/0"],
|
|
||||||
["nested.property[0].name", "#/nested/property/0/name"]
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const [input, output] of examples) {
|
|
||||||
expect(utils.normalizePath(input)).toBe(output);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test("coerse", () => {
|
test("coerse", () => {
|
||||||
const examples = [
|
const examples = [
|
||||||
["test", { type: "string" }, "test"],
|
["test", { type: "string" }, "test"],
|
||||||
@@ -38,6 +24,25 @@ describe("json form", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("isType", () => {
|
||||||
|
const examples = [
|
||||||
|
["string", "string", true],
|
||||||
|
["integer", "number", false],
|
||||||
|
["number", "number", true],
|
||||||
|
["boolean", "boolean", true],
|
||||||
|
["null", "null", true],
|
||||||
|
["object", "object", true],
|
||||||
|
["array", "array", true],
|
||||||
|
["object", "array", false],
|
||||||
|
[["string", "number"], "number", true],
|
||||||
|
["number", ["string", "number"], true]
|
||||||
|
] satisfies [IsTypeType, IsTypeType, boolean][];
|
||||||
|
|
||||||
|
for (const [type, schemaType, output] of examples) {
|
||||||
|
expect(utils.isType(type, schemaType)).toBe(output);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test("getParentPointer", () => {
|
test("getParentPointer", () => {
|
||||||
const examples = [
|
const examples = [
|
||||||
["#/nested/property/0/name", "#/nested/property/0"],
|
["#/nested/property/0/name", "#/nested/property/0"],
|
||||||
@@ -97,49 +102,37 @@ describe("json form", () => {
|
|||||||
] satisfies [string, Exclude<JSONSchema, boolean>, boolean][];
|
] satisfies [string, Exclude<JSONSchema, boolean>, boolean][];
|
||||||
|
|
||||||
for (const [pointer, schema, output] of examples) {
|
for (const [pointer, schema, output] of examples) {
|
||||||
expect(utils.isRequired(pointer, schema)).toBe(output);
|
expect(utils.isRequired(new Draft2019(schema), pointer, schema)).toBe(output);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test("unflatten", () => {
|
test("prefixPath", () => {
|
||||||
const examples = [
|
const examples = [
|
||||||
[
|
["normal", "", "normal"],
|
||||||
{ "#/description": "test" },
|
["", "prefix", "prefix"],
|
||||||
{
|
["tags", "0", "0.tags"],
|
||||||
type: "object",
|
["tags", 0, "0.tags"],
|
||||||
properties: {
|
["nested.property", "prefix", "prefix.nested.property"],
|
||||||
description: { type: "string" }
|
["nested.property", "", "nested.property"]
|
||||||
}
|
] satisfies [string, any, string][];
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "test"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
] satisfies [Record<string, string>, Exclude<JSONSchema, boolean>, object][];
|
|
||||||
|
|
||||||
for (const [input, schema, output] of examples) {
|
for (const [path, prefix, output] of examples) {
|
||||||
expect(utils.unflatten(input, schema)).toEqual(output);
|
expect(utils.prefixPath(path, prefix)).toBe(output);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test("...", () => {
|
test("suffixPath", () => {
|
||||||
const schema = {
|
const examples = [
|
||||||
type: "object",
|
["normal", "", "normal"],
|
||||||
properties: {
|
["", "suffix", "suffix"],
|
||||||
name: { type: "string", maxLength: 2 },
|
["tags", "0", "tags.0"],
|
||||||
description: { type: "string", maxLength: 2 },
|
["tags", 0, "tags.0"],
|
||||||
age: { type: "number", description: "Age of you" },
|
["nested.property", "suffix", "nested.property.suffix"],
|
||||||
deep: {
|
["nested.property", "", "nested.property"]
|
||||||
type: "object",
|
] satisfies [string, any, string][];
|
||||||
properties: {
|
|
||||||
nested: { type: "string", maxLength: 2 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
required: ["description"]
|
|
||||||
};
|
|
||||||
|
|
||||||
//const lib = new Draft2019(schema);
|
for (const [path, suffix, output] of examples) {
|
||||||
//lib.eachSchema(console.log);
|
expect(utils.suffixPath(path, suffix)).toBe(output);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,6 +12,20 @@ export function isObject(value: unknown): value is Record<string, unknown> {
|
|||||||
return value !== null && typeof value === "object";
|
return value !== null && typeof value === "object";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function omitKeys<T extends object, K extends keyof T>(
|
||||||
|
obj: T,
|
||||||
|
keys_: readonly K[]
|
||||||
|
): Omit<T, Extract<K, keyof T>> {
|
||||||
|
const keys = new Set(keys_);
|
||||||
|
const result = {} as Omit<T, Extract<K, keyof T>>;
|
||||||
|
for (const [key, value] of Object.entries(obj) as [keyof T, T[keyof T]][]) {
|
||||||
|
if (!keys.has(key as K)) {
|
||||||
|
(result as any)[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
export function safelyParseObjectValues<T extends { [key: string]: any }>(obj: T): T {
|
export function safelyParseObjectValues<T extends { [key: string]: any }>(obj: T): T {
|
||||||
return Object.entries(obj).reduce((acc, [key, value]) => {
|
return Object.entries(obj).reduce((acc, [key, value]) => {
|
||||||
try {
|
try {
|
||||||
@@ -266,3 +280,82 @@ export function mergeObjectWith(object, source, customizer) {
|
|||||||
|
|
||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isEqual(value1: any, value2: any): boolean {
|
||||||
|
// Each type corresponds to a particular comparison algorithm
|
||||||
|
const getType = (value: any) => {
|
||||||
|
if (value !== Object(value)) return "primitive";
|
||||||
|
if (Array.isArray(value)) return "array";
|
||||||
|
if (value instanceof Map) return "map";
|
||||||
|
if (value != null && [null, Object.prototype].includes(Object.getPrototypeOf(value)))
|
||||||
|
return "plainObject";
|
||||||
|
if (value instanceof Function) return "function";
|
||||||
|
throw new Error(
|
||||||
|
`deeply comparing an instance of type ${value1.constructor?.name} is not supported.`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const type = getType(value1);
|
||||||
|
if (type !== getType(value2)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === "primitive") {
|
||||||
|
return value1 === value2 || (Number.isNaN(value1) && Number.isNaN(value2));
|
||||||
|
} else if (type === "array") {
|
||||||
|
return (
|
||||||
|
value1.length === value2.length &&
|
||||||
|
value1.every((iterValue: any, i: number) => isEqual(iterValue, value2[i]))
|
||||||
|
);
|
||||||
|
} else if (type === "map") {
|
||||||
|
// In this particular implementation, map keys are not
|
||||||
|
// being deeply compared, only map values.
|
||||||
|
return (
|
||||||
|
value1.size === value2.size &&
|
||||||
|
[...value1].every(([iterKey, iterValue]) => {
|
||||||
|
return value2.has(iterKey) && isEqual(iterValue, value2.get(iterKey));
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else if (type === "plainObject") {
|
||||||
|
const value1AsMap = new Map(Object.entries(value1));
|
||||||
|
const value2AsMap = new Map(Object.entries(value2));
|
||||||
|
return (
|
||||||
|
value1AsMap.size === value2AsMap.size &&
|
||||||
|
[...value1AsMap].every(([iterKey, iterValue]) => {
|
||||||
|
return value2AsMap.has(iterKey) && isEqual(iterValue, value2AsMap.get(iterKey));
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else if (type === "function") {
|
||||||
|
// just check signature
|
||||||
|
return value1.toString() === value2.toString();
|
||||||
|
} else {
|
||||||
|
throw new Error("Unreachable");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPath(
|
||||||
|
object: object,
|
||||||
|
_path: string | (string | number)[],
|
||||||
|
defaultValue = undefined
|
||||||
|
): any {
|
||||||
|
const path = typeof _path === "string" ? _path.split(/[.\[\]\"]+/).filter((x) => x) : _path;
|
||||||
|
|
||||||
|
if (path.length === 0) {
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [head, ...tail] = path;
|
||||||
|
if (!head || !(head in object)) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getPath(object[head], tail, defaultValue);
|
||||||
|
} catch (error) {
|
||||||
|
if (typeof defaultValue !== "undefined") {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Invalid path: ${path.join(".")}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ export function buildMediaSchema() {
|
|||||||
{
|
{
|
||||||
body_max_size: Type.Optional(
|
body_max_size: Type.Optional(
|
||||||
Type.Number({
|
Type.Number({
|
||||||
minimum: 0,
|
|
||||||
description: "Max size of the body in bytes. Leave blank for unlimited."
|
description: "Max size of the body in bytes. Leave blank for unlimited."
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { twMerge } from "tailwind-merge";
|
|||||||
import * as Formy from "ui/components/form/Formy";
|
import * as Formy from "ui/components/form/Formy";
|
||||||
import { useEvent } from "ui/hooks/use-event";
|
import { useEvent } from "ui/hooks/use-event";
|
||||||
import { FieldComponent, Field as FormField, type FieldProps as FormFieldProps } from "./Field";
|
import { FieldComponent, Field as FormField, type FieldProps as FormFieldProps } from "./Field";
|
||||||
import { FormContextOverride, useDerivedFieldContext } from "./Form";
|
import { FormContextOverride, useDerivedFieldContext, useFormError } from "./Form";
|
||||||
import { getLabel, getMultiSchemaMatched } from "./utils";
|
import { getLabel, getMultiSchemaMatched } from "./utils";
|
||||||
|
|
||||||
export type AnyOfFieldRootProps = {
|
export type AnyOfFieldRootProps = {
|
||||||
@@ -39,19 +39,19 @@ const Root = ({ path = "", schema: _schema, children }: AnyOfFieldRootProps) =>
|
|||||||
setValue,
|
setValue,
|
||||||
lib,
|
lib,
|
||||||
pointer,
|
pointer,
|
||||||
errors,
|
|
||||||
value: { matchedIndex, schemas },
|
value: { matchedIndex, schemas },
|
||||||
schema
|
schema
|
||||||
} = useDerivedFieldContext(path, _schema, (ctx) => {
|
} = useDerivedFieldContext(path, _schema, (ctx) => {
|
||||||
const [matchedIndex, schemas = []] = getMultiSchemaMatched(ctx.schema, ctx.value);
|
const [matchedIndex, schemas = []] = getMultiSchemaMatched(ctx.schema, ctx.value);
|
||||||
return { matchedIndex, schemas };
|
return { matchedIndex, schemas };
|
||||||
});
|
});
|
||||||
|
const errors = useFormError(path, { strict: true });
|
||||||
if (!schema) return `AnyOfField(${path}): no schema ${pointer}`;
|
if (!schema) return `AnyOfField(${path}): no schema ${pointer}`;
|
||||||
const [_selected, setSelected] = useAtom(selectedAtom);
|
const [_selected, setSelected] = useAtom(selectedAtom);
|
||||||
const selected = _selected !== null ? _selected : matchedIndex > -1 ? matchedIndex : null;
|
const selected = _selected !== null ? _selected : matchedIndex > -1 ? matchedIndex : null;
|
||||||
|
|
||||||
const select = useEvent((index: number | null) => {
|
const select = useEvent((index: number | null) => {
|
||||||
setValue(pointer, index !== null ? lib.getTemplate(undefined, schemas[index]) : undefined);
|
setValue(path, index !== null ? lib.getTemplate(undefined, schemas[index]) : undefined);
|
||||||
setSelected(index);
|
setSelected(index);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -92,20 +92,20 @@ const Root = ({ path = "", schema: _schema, children }: AnyOfFieldRootProps) =>
|
|||||||
const Select = () => {
|
const Select = () => {
|
||||||
const { selected, select, path, schema, options, selectSchema } = useAnyOfContext();
|
const { selected, select, path, schema, options, selectSchema } = useAnyOfContext();
|
||||||
|
|
||||||
function handleSelect(e: ChangeEvent<HTMLInputElement>) {
|
const handleSelect = useEvent((e: ChangeEvent<HTMLInputElement>) => {
|
||||||
const i = e.target.value ? Number(e.target.value) : null;
|
const i = e.target.value ? Number(e.target.value) : null;
|
||||||
select(i);
|
select(i);
|
||||||
}
|
});
|
||||||
|
|
||||||
|
const _options = useMemo(() => options.map((label, value) => ({ label, value })), []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Formy.Label>
|
<Formy.Label>{getLabel(path, schema)}</Formy.Label>
|
||||||
{getLabel(path, schema)} {selected}
|
|
||||||
</Formy.Label>
|
|
||||||
<FieldComponent
|
<FieldComponent
|
||||||
schema={selectSchema as any}
|
schema={selectSchema as any}
|
||||||
/* @ts-ignore */
|
/* @ts-ignore */
|
||||||
options={options.map((label, value) => ({ label, value }))}
|
options={_options}
|
||||||
onChange={handleSelect}
|
onChange={handleSelect}
|
||||||
value={selected ?? undefined}
|
value={selected ?? undefined}
|
||||||
className="h-8 py-1"
|
className="h-8 py-1"
|
||||||
@@ -114,6 +114,7 @@ const Select = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// @todo: add local validation for AnyOf fields
|
||||||
const Field = ({ name, label, schema, ...props }: Partial<FormFieldProps>) => {
|
const Field = ({ name, label, schema, ...props }: Partial<FormFieldProps>) => {
|
||||||
const { selected, selectedSchema, path, errors } = useAnyOfContext();
|
const { selected, selectedSchema, path, errors } = useAnyOfContext();
|
||||||
if (selected === null) return null;
|
if (selected === null) return null;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { IconLibraryPlus, IconTrash } from "@tabler/icons-react";
|
import { IconLibraryPlus, IconTrash } from "@tabler/icons-react";
|
||||||
import type { JsonSchema } from "json-schema-library";
|
import type { JsonSchema } from "json-schema-library";
|
||||||
import { isEqual } from "lodash-es";
|
|
||||||
import { memo, useMemo } from "react";
|
import { memo, useMemo } from "react";
|
||||||
import { Button } from "ui/components/buttons/Button";
|
import { Button } from "ui/components/buttons/Button";
|
||||||
import { IconButton } from "ui/components/buttons/IconButton";
|
import { IconButton } from "ui/components/buttons/IconButton";
|
||||||
@@ -8,8 +7,8 @@ import { Dropdown } from "ui/components/overlay/Dropdown";
|
|||||||
import { useEvent } from "ui/hooks/use-event";
|
import { useEvent } from "ui/hooks/use-event";
|
||||||
import { FieldComponent } from "./Field";
|
import { FieldComponent } from "./Field";
|
||||||
import { FieldWrapper } from "./FieldWrapper";
|
import { FieldWrapper } from "./FieldWrapper";
|
||||||
import { useDerivedFieldContext, useFormContext, useFormValue } from "./Form";
|
import { useDerivedFieldContext, useFormValue } from "./Form";
|
||||||
import { coerce, getMultiSchema, getMultiSchemaMatched } from "./utils";
|
import { coerce, getMultiSchema, getMultiSchemaMatched, isEqual, suffixPath } from "./utils";
|
||||||
|
|
||||||
export const ArrayField = ({
|
export const ArrayField = ({
|
||||||
path = "",
|
path = "",
|
||||||
@@ -59,7 +58,7 @@ const ArrayItem = memo(({ path, index, schema }: any) => {
|
|||||||
const { value, ...ctx } = useDerivedFieldContext(path, schema, (ctx) => {
|
const { value, ...ctx } = useDerivedFieldContext(path, schema, (ctx) => {
|
||||||
return ctx.value?.[index];
|
return ctx.value?.[index];
|
||||||
});
|
});
|
||||||
const pointer = [path, index].join(".");
|
const itemPath = suffixPath(path, index);
|
||||||
let subschema = schema.items;
|
let subschema = schema.items;
|
||||||
const itemsMultiSchema = getMultiSchema(schema.items);
|
const itemsMultiSchema = getMultiSchema(schema.items);
|
||||||
if (itemsMultiSchema) {
|
if (itemsMultiSchema) {
|
||||||
@@ -76,18 +75,18 @@ const ArrayItem = memo(({ path, index, schema }: any) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const DeleteButton = useMemo(
|
const DeleteButton = useMemo(
|
||||||
() => <IconButton Icon={IconTrash} onClick={() => handleDelete(pointer)} size="sm" />,
|
() => <IconButton Icon={IconTrash} onClick={() => handleDelete(itemPath)} size="sm" />,
|
||||||
[pointer]
|
[itemPath]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={pointer} className="flex flex-row gap-2">
|
<div key={itemPath} className="flex flex-row gap-2">
|
||||||
<FieldComponent
|
<FieldComponent
|
||||||
name={pointer}
|
name={itemPath}
|
||||||
schema={subschema!}
|
schema={subschema!}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
handleUpdate(pointer, coerce(e.target.value, subschema!));
|
handleUpdate(itemPath, coerce(e.target.value, subschema!));
|
||||||
}}
|
}}
|
||||||
className="w-full"
|
className="w-full"
|
||||||
/>
|
/>
|
||||||
@@ -114,8 +113,8 @@ const ArrayAdd = ({ schema, path }: { schema: JsonSchema; path: string }) => {
|
|||||||
const itemsMultiSchema = getMultiSchema(schema.items);
|
const itemsMultiSchema = getMultiSchema(schema.items);
|
||||||
|
|
||||||
function handleAdd(template?: any) {
|
function handleAdd(template?: any) {
|
||||||
const newPointer = `${path}/${currentIndex}`.replace(/\/+/g, "/");
|
const newPath = suffixPath(path, currentIndex);
|
||||||
setValue(newPointer, template ?? ctx.lib.getTemplate(undefined, schema!.items));
|
setValue(newPath, template ?? ctx.lib.getTemplate(undefined, schema!.items));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (itemsMultiSchema) {
|
if (itemsMultiSchema) {
|
||||||
|
|||||||
@@ -17,12 +17,12 @@ export type FieldProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const Field = ({ name, schema: _schema, onChange, label: _label, hidden }: FieldProps) => {
|
export const Field = ({ name, schema: _schema, onChange, label: _label, hidden }: FieldProps) => {
|
||||||
const { pointer, setValue, required, ...ctx } = useDerivedFieldContext(name, _schema);
|
const { path, setValue, required, ...ctx } = useDerivedFieldContext(name, _schema);
|
||||||
const schema = _schema ?? ctx.schema;
|
const schema = _schema ?? ctx.schema;
|
||||||
if (!isTypeSchema(schema))
|
if (!isTypeSchema(schema))
|
||||||
return (
|
return (
|
||||||
<Pre>
|
<Pre>
|
||||||
[Field] {pointer} has no schema ({JSON.stringify(schema)})
|
[Field] {path} has no schema ({JSON.stringify(schema)})
|
||||||
</Pre>
|
</Pre>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -39,9 +39,9 @@ export const Field = ({ name, schema: _schema, onChange, label: _label, hidden }
|
|||||||
const handleChange = useEvent((e: ChangeEvent<HTMLInputElement>) => {
|
const handleChange = useEvent((e: ChangeEvent<HTMLInputElement>) => {
|
||||||
const value = coerce(e.target.value, schema as any, { required });
|
const value = coerce(e.target.value, schema as any, { required });
|
||||||
if (typeof value === "undefined" && !required && ctx.options?.keepEmpty !== true) {
|
if (typeof value === "undefined" && !required && ctx.options?.keepEmpty !== true) {
|
||||||
ctx.deleteValue(pointer);
|
ctx.deleteValue(path);
|
||||||
} else {
|
} else {
|
||||||
setValue(pointer, value);
|
setValue(path, value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ export const FieldComponent = ({
|
|||||||
schema,
|
schema,
|
||||||
..._props
|
..._props
|
||||||
}: { schema: JsonSchema } & ComponentPropsWithoutRef<"input">) => {
|
}: { schema: JsonSchema } & ComponentPropsWithoutRef<"input">) => {
|
||||||
const { value } = useFormValue(_props.name!);
|
const { value } = useFormValue(_props.name!, { strict: true });
|
||||||
if (!isTypeSchema(schema)) return null;
|
if (!isTypeSchema(schema)) return null;
|
||||||
const props = {
|
const props = {
|
||||||
..._props,
|
..._props,
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import { selectAtom } from "jotai/utils";
|
|||||||
import { Draft2019, type JsonError, type JsonSchema as LibJsonSchema } from "json-schema-library";
|
import { Draft2019, type JsonError, type JsonSchema as LibJsonSchema } from "json-schema-library";
|
||||||
import type { TemplateOptions as LibTemplateOptions } from "json-schema-library/dist/lib/getTemplate";
|
import type { TemplateOptions as LibTemplateOptions } from "json-schema-library/dist/lib/getTemplate";
|
||||||
import type { JSONSchema as $JSONSchema, FromSchema } from "json-schema-to-ts";
|
import type { JSONSchema as $JSONSchema, FromSchema } from "json-schema-to-ts";
|
||||||
import { get, isEqual } from "lodash-es";
|
|
||||||
import * as immutable from "object-path-immutable";
|
import * as immutable from "object-path-immutable";
|
||||||
import {
|
import {
|
||||||
type ComponentPropsWithoutRef,
|
type ComponentPropsWithoutRef,
|
||||||
@@ -27,8 +26,9 @@ import { JsonViewer } from "ui/components/code/JsonViewer";
|
|||||||
import { useEvent } from "ui/hooks/use-event";
|
import { useEvent } from "ui/hooks/use-event";
|
||||||
import { Field } from "./Field";
|
import { Field } from "./Field";
|
||||||
import {
|
import {
|
||||||
|
getPath,
|
||||||
|
isEqual,
|
||||||
isRequired,
|
isRequired,
|
||||||
normalizePath,
|
|
||||||
omitSchema,
|
omitSchema,
|
||||||
pathToPointer,
|
pathToPointer,
|
||||||
prefixPath,
|
prefixPath,
|
||||||
@@ -139,25 +139,21 @@ export function Form<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const setValue = useEvent((pointer: string, value: any) => {
|
const setValue = useEvent((path: string, value: any) => {
|
||||||
const normalized = normalizePath(pointer);
|
|
||||||
const key = normalized.substring(2).replace(/\//g, ".");
|
|
||||||
setFormState((state) => {
|
setFormState((state) => {
|
||||||
const prev = state.data;
|
const prev = state.data;
|
||||||
const changed = immutable.set(prev, key, value);
|
const changed = immutable.set(prev, path, value);
|
||||||
onChange?.(changed, key, value);
|
onChange?.(changed, path, value);
|
||||||
return { ...state, data: changed };
|
return { ...state, data: changed };
|
||||||
});
|
});
|
||||||
check();
|
check();
|
||||||
});
|
});
|
||||||
|
|
||||||
const deleteValue = useEvent((pointer: string) => {
|
const deleteValue = useEvent((path: string) => {
|
||||||
const normalized = normalizePath(pointer);
|
|
||||||
const key = normalized.substring(2).replace(/\//g, ".");
|
|
||||||
setFormState((state) => {
|
setFormState((state) => {
|
||||||
const prev = state.data;
|
const prev = state.data;
|
||||||
const changed = immutable.del(prev, key);
|
const changed = immutable.del(prev, path);
|
||||||
onChange?.(changed, key, undefined);
|
onChange?.(changed, path, undefined);
|
||||||
return { ...state, data: changed };
|
return { ...state, data: changed };
|
||||||
});
|
});
|
||||||
check();
|
check();
|
||||||
@@ -191,7 +187,8 @@ export function Form<
|
|||||||
schema,
|
schema,
|
||||||
lib,
|
lib,
|
||||||
options,
|
options,
|
||||||
root: ""
|
root: "",
|
||||||
|
path: ""
|
||||||
}),
|
}),
|
||||||
[schema, initialValues]
|
[schema, initialValues]
|
||||||
) as any;
|
) as any;
|
||||||
@@ -245,8 +242,11 @@ export function FormContextOverride({
|
|||||||
return <FormContext.Provider value={context}>{children}</FormContext.Provider>;
|
return <FormContext.Provider value={context}>{children}</FormContext.Provider>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useFormValue(name: string) {
|
export function useFormValue(name: string, opts?: { strict?: boolean }) {
|
||||||
const { _formStateAtom, root } = useFormContext();
|
const { _formStateAtom, root } = useFormContext();
|
||||||
|
if ((typeof name !== "string" || name.length === 0) && opts?.strict === true)
|
||||||
|
return { value: undefined, errors: [] };
|
||||||
|
|
||||||
const selected = selectAtom(
|
const selected = selectAtom(
|
||||||
_formStateAtom,
|
_formStateAtom,
|
||||||
useCallback(
|
useCallback(
|
||||||
@@ -254,7 +254,7 @@ export function useFormValue(name: string) {
|
|||||||
const prefixedName = prefixPath(name, root);
|
const prefixedName = prefixPath(name, root);
|
||||||
const pointer = pathToPointer(prefixedName);
|
const pointer = pathToPointer(prefixedName);
|
||||||
return {
|
return {
|
||||||
value: get(state.data, prefixedName),
|
value: getPath(state.data, prefixedName),
|
||||||
errors: state.errors.filter((error) => error.data.pointer.startsWith(pointer))
|
errors: state.errors.filter((error) => error.data.pointer.startsWith(pointer))
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -265,7 +265,7 @@ export function useFormValue(name: string) {
|
|||||||
return useAtom(selected)[0];
|
return useAtom(selected)[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useFormError(name: string, opt?: { strict?: boolean }) {
|
export function useFormError(name: string, opt?: { strict?: boolean; debug?: boolean }) {
|
||||||
const { _formStateAtom, root } = useFormContext();
|
const { _formStateAtom, root } = useFormContext();
|
||||||
const selected = selectAtom(
|
const selected = selectAtom(
|
||||||
_formStateAtom,
|
_formStateAtom,
|
||||||
@@ -303,12 +303,17 @@ export function useDerivedFieldContext<Data = any, Reduced = undefined>(
|
|||||||
FormContext<Data> & {
|
FormContext<Data> & {
|
||||||
pointer: string;
|
pointer: string;
|
||||||
required: boolean;
|
required: boolean;
|
||||||
errors: JsonError[];
|
|
||||||
value: any;
|
value: any;
|
||||||
|
path: string;
|
||||||
},
|
},
|
||||||
Reduced
|
Reduced
|
||||||
>
|
>
|
||||||
): FormContext<Data> & { value: Reduced; pointer: string; required: boolean; errors: JsonError[] } {
|
): FormContext<Data> & {
|
||||||
|
value: Reduced;
|
||||||
|
pointer: string;
|
||||||
|
required: boolean;
|
||||||
|
path: string;
|
||||||
|
} {
|
||||||
const { _formStateAtom, root, lib, ...ctx } = useFormContext();
|
const { _formStateAtom, root, lib, ...ctx } = useFormContext();
|
||||||
const schema = _schema ?? ctx.schema;
|
const schema = _schema ?? ctx.schema;
|
||||||
const selected = selectAtom(
|
const selected = selectAtom(
|
||||||
@@ -318,23 +323,23 @@ export function useDerivedFieldContext<Data = any, Reduced = undefined>(
|
|||||||
const pointer = pathToPointer(path);
|
const pointer = pathToPointer(path);
|
||||||
const prefixedName = prefixPath(path, root);
|
const prefixedName = prefixPath(path, root);
|
||||||
const prefixedPointer = pathToPointer(prefixedName);
|
const prefixedPointer = pathToPointer(prefixedName);
|
||||||
const value = get(state.data, prefixedName);
|
const value = getPath(state.data, prefixedName);
|
||||||
const errors = state.errors.filter((error) =>
|
/*const errors = state.errors.filter((error) =>
|
||||||
error.data.pointer.startsWith(prefixedPointer)
|
error.data.pointer.startsWith(prefixedPointer)
|
||||||
);
|
);*/
|
||||||
const fieldSchema =
|
const fieldSchema =
|
||||||
pointer === "#/"
|
pointer === "#/"
|
||||||
? (schema as LibJsonSchema)
|
? (schema as LibJsonSchema)
|
||||||
: lib.getSchema({ pointer, data: value, schema });
|
: lib.getSchema({ pointer, data: value, schema });
|
||||||
const required = isRequired(prefixedPointer, schema, state.data);
|
const required = isRequired(lib, prefixedPointer, schema, state.data);
|
||||||
|
|
||||||
const context = {
|
const context = {
|
||||||
...ctx,
|
...ctx,
|
||||||
|
path: prefixedName,
|
||||||
root,
|
root,
|
||||||
schema: fieldSchema as LibJsonSchema,
|
schema: fieldSchema as LibJsonSchema,
|
||||||
pointer,
|
pointer,
|
||||||
required,
|
required
|
||||||
errors
|
|
||||||
};
|
};
|
||||||
const derived = deriveFn?.({ ...context, _formStateAtom, lib, value });
|
const derived = deriveFn?.({ ...context, _formStateAtom, lib, value });
|
||||||
|
|
||||||
|
|||||||
@@ -1,82 +1,10 @@
|
|||||||
import { autoFormatString } from "core/utils";
|
import { autoFormatString, omitKeys } from "core/utils";
|
||||||
import { Draft2019, type JsonSchema } from "json-schema-library";
|
import { type Draft, Draft2019, type JsonSchema } from "json-schema-library";
|
||||||
import type { JSONSchema } from "json-schema-to-ts";
|
import type { JSONSchema } from "json-schema-to-ts";
|
||||||
import type { JSONSchemaType } from "json-schema-to-ts/lib/types/definitions/jsonSchema";
|
import type { JSONSchemaType } from "json-schema-to-ts/lib/types/definitions/jsonSchema";
|
||||||
import { omit, set } from "lodash-es";
|
|
||||||
import type { FormEvent } from "react";
|
|
||||||
|
|
||||||
export function getFormTarget(e: FormEvent<HTMLFormElement>) {
|
export { isEqual, getPath } from "core/utils/objects";
|
||||||
const form = e.currentTarget;
|
//export { isEqual } from "lodash-es";
|
||||||
const target = e.target as HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | null;
|
|
||||||
|
|
||||||
// check if target has attribute "data-ignore" set
|
|
||||||
// also check if target is within a "data-ignore" element
|
|
||||||
|
|
||||||
if (
|
|
||||||
!target ||
|
|
||||||
!form.contains(target) ||
|
|
||||||
!target.name ||
|
|
||||||
target.hasAttribute("data-ignore") ||
|
|
||||||
target.closest("[data-ignore]")
|
|
||||||
) {
|
|
||||||
return; // Ignore events from outside the form
|
|
||||||
}
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function flatten(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])) {
|
|
||||||
flatten(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) {
|
|
||||||
flatten(item, arrayKey, result);
|
|
||||||
} else {
|
|
||||||
result[arrayKey] = item;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
result[newKey] = obj[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// @todo: make sure it's in the right order
|
|
||||||
export function unflatten(
|
|
||||||
obj: Record<string, string>,
|
|
||||||
schema: JsonSchema,
|
|
||||||
selections?: Record<string, number | undefined>
|
|
||||||
) {
|
|
||||||
const result = {};
|
|
||||||
const lib = new Draft2019(schema as any);
|
|
||||||
for (const pointer in obj) {
|
|
||||||
const required = isRequired(pointer, schema);
|
|
||||||
let subschema = lib.getSchema({ pointer });
|
|
||||||
//console.log("subschema", pointer, subschema, selections);
|
|
||||||
if (!subschema) {
|
|
||||||
throw new Error(`"${pointer}" not found in schema`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if subschema starts with "anyOf" or "oneOf"
|
|
||||||
if (subschema.anyOf || subschema.oneOf) {
|
|
||||||
const selected = selections?.[pointer];
|
|
||||||
if (selected !== undefined) {
|
|
||||||
subschema = subschema.anyOf ? subschema.anyOf[selected] : subschema.oneOf![selected];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const value = coerce(obj[pointer], subschema as any, { required });
|
|
||||||
|
|
||||||
set(result, pointer.substring(2).replace(/\//g, "."), value);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function coerce(value: any, schema: JsonSchema, opts?: { required?: boolean }) {
|
export function coerce(value: any, schema: JsonSchema, opts?: { required?: boolean }) {
|
||||||
if (!value && typeof opts?.required === "boolean" && !opts.required) {
|
if (!value && typeof opts?.required === "boolean" && !opts.required) {
|
||||||
@@ -98,25 +26,6 @@ export function coerce(value: any, schema: JsonSchema, opts?: { required?: boole
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* normalizes any path to a full json pointer
|
|
||||||
*
|
|
||||||
* examples: in -> out
|
|
||||||
* description -> #/description
|
|
||||||
* #/description -> #/description
|
|
||||||
* /description -> #/description
|
|
||||||
* nested/property -> #/nested/property
|
|
||||||
* nested.property -> #/nested/property
|
|
||||||
* nested.property[0] -> #/nested/property/0
|
|
||||||
* nested.property[0].name -> #/nested/property/0/name
|
|
||||||
* @param path
|
|
||||||
*/
|
|
||||||
export function normalizePath(path: string) {
|
|
||||||
return path.startsWith("#/")
|
|
||||||
? path
|
|
||||||
: `#/${path.replace(/#?\/?/, "").replace(/\./g, "/").replace(/\[/g, "/").replace(/\]/g, "")}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function pathToPointer(path: string) {
|
export function pathToPointer(path: string) {
|
||||||
return "#/" + (path.includes(".") ? path.split(".").join("/") : path);
|
return "#/" + (path.includes(".") ? path.split(".").join("/") : path);
|
||||||
}
|
}
|
||||||
@@ -125,22 +34,28 @@ export function prefixPointer(pointer: string, prefix: string) {
|
|||||||
return pointer.replace("#/", `#/${prefix.length > 0 ? prefix + "/" : ""}`).replace(/\/\//g, "/");
|
return pointer.replace("#/", `#/${prefix.length > 0 ? prefix + "/" : ""}`).replace(/\/\//g, "/");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function prefixPath(path: string = "", prefix: string = "") {
|
const PathFilter = (value: any) => typeof value !== "undefined" && value !== null && value !== "";
|
||||||
|
|
||||||
|
export function prefixPath(path: string = "", prefix: string | number = "") {
|
||||||
const p = path.includes(".") ? path.split(".") : [path];
|
const p = path.includes(".") ? path.split(".") : [path];
|
||||||
return [prefix, ...p].filter(Boolean).join(".");
|
return [prefix, ...p].filter(PathFilter).join(".");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function suffixPath(path: string = "", suffix: string | number = "") {
|
||||||
|
const p = path.includes(".") ? path.split(".") : [path];
|
||||||
|
return [...p, suffix].filter(PathFilter).join(".");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getParentPointer(pointer: string) {
|
export function getParentPointer(pointer: string) {
|
||||||
return pointer.substring(0, pointer.lastIndexOf("/"));
|
return pointer.substring(0, pointer.lastIndexOf("/"));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isRequired(pointer: string, schema: JsonSchema, data?: any) {
|
export function isRequired(lib: Draft, pointer: string, schema: JsonSchema, data?: any) {
|
||||||
if (pointer === "#/" || !schema) {
|
if (pointer === "#/" || !schema) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const lib = new Draft2019(schema as any);
|
|
||||||
|
|
||||||
const childSchema = lib.getSchema({ pointer, data });
|
const childSchema = lib.getSchema({ pointer, data, schema });
|
||||||
if (typeof childSchema === "object" && "const" in childSchema) {
|
if (typeof childSchema === "object" && "const" in childSchema) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -152,12 +67,17 @@ export function isRequired(pointer: string, schema: JsonSchema, data?: any) {
|
|||||||
return !!required;
|
return !!required;
|
||||||
}
|
}
|
||||||
|
|
||||||
type TType = JSONSchemaType | JSONSchemaType[] | readonly JSONSchemaType[] | string | undefined;
|
export type IsTypeType =
|
||||||
export function isType(_type: TType, _compare: TType) {
|
| JSONSchemaType
|
||||||
if (!_type || !_compare) return false;
|
| JSONSchemaType[]
|
||||||
const type = Array.isArray(_type) ? _type : [_type];
|
| readonly JSONSchemaType[]
|
||||||
const compare = Array.isArray(_compare) ? _compare : [_compare];
|
| string
|
||||||
return compare.some((t) => type.includes(t));
|
| undefined;
|
||||||
|
export function isType(type: IsTypeType, compare: IsTypeType) {
|
||||||
|
if (!type || !compare) return false;
|
||||||
|
const _type = Array.isArray(type) ? type : [type];
|
||||||
|
const _compare = Array.isArray(compare) ? compare : [compare];
|
||||||
|
return _compare.some((t) => _type.includes(t));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getLabel(name: string, schema: JsonSchema) {
|
export function getLabel(name: string, schema: JsonSchema) {
|
||||||
@@ -188,19 +108,6 @@ export function getMultiSchemaMatched(
|
|||||||
return [index, multiSchema, multiSchema[index]];
|
return [index, multiSchema, multiSchema[index]];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeKeyRecursively<Given extends object>(obj: Given, keyToRemove: string): Given {
|
|
||||||
if (Array.isArray(obj)) {
|
|
||||||
return obj.map((item) => removeKeyRecursively(item, keyToRemove)) as any;
|
|
||||||
} else if (typeof obj === "object" && obj !== null) {
|
|
||||||
return Object.fromEntries(
|
|
||||||
Object.entries(obj)
|
|
||||||
.filter(([key]) => key !== keyToRemove)
|
|
||||||
.map(([key, value]) => [key, removeKeyRecursively(value, keyToRemove)])
|
|
||||||
) as any;
|
|
||||||
}
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function omitSchema<Given extends JSONSchema>(_schema: Given, keys: string[], _data?: any) {
|
export function omitSchema<Given extends JSONSchema>(_schema: Given, keys: string[], _data?: any) {
|
||||||
if (typeof _schema !== "object" || !("properties" in _schema) || keys.length === 0)
|
if (typeof _schema !== "object" || !("properties" in _schema) || keys.length === 0)
|
||||||
return [_schema, _data];
|
return [_schema, _data];
|
||||||
@@ -209,13 +116,13 @@ export function omitSchema<Given extends JSONSchema>(_schema: Given, keys: strin
|
|||||||
|
|
||||||
const updated = {
|
const updated = {
|
||||||
...schema,
|
...schema,
|
||||||
properties: omit(schema.properties, keys)
|
properties: omitKeys(schema.properties, keys)
|
||||||
};
|
};
|
||||||
if (updated.required) {
|
if (updated.required) {
|
||||||
updated.required = updated.required.filter((key) => !keys.includes(key as any));
|
updated.required = updated.required.filter((key) => !keys.includes(key as any));
|
||||||
}
|
}
|
||||||
|
|
||||||
const reducedConfig = omit(data, keys) as any;
|
const reducedConfig = omitKeys(data, keys) as any;
|
||||||
|
|
||||||
return [updated, reducedConfig];
|
return [updated, reducedConfig];
|
||||||
}
|
}
|
||||||
@@ -223,10 +130,3 @@ export function omitSchema<Given extends JSONSchema>(_schema: Given, keys: strin
|
|||||||
export function isTypeSchema(schema?: JsonSchema): schema is JsonSchema {
|
export function isTypeSchema(schema?: JsonSchema): schema is JsonSchema {
|
||||||
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 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ function MediaSettingsInternal() {
|
|||||||
async function onSubmit(data: any) {
|
async function onSubmit(data: any) {
|
||||||
console.log("submit", data);
|
console.log("submit", data);
|
||||||
await actions.config.patch(data);
|
await actions.config.patch(data);
|
||||||
//await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ export default function JsonSchemaForm3() {
|
|||||||
|
|
||||||
config.media.storage.body_max_size = 1;
|
config.media.storage.body_max_size = 1;
|
||||||
schema.media.properties.storage.properties.body_max_size.minimum = 0;
|
schema.media.properties.storage.properties.body_max_size.minimum = 0;
|
||||||
|
//schema.media.properties.adapter.anyOf[2].properties.config.properties.path.minLength = 1;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Scrollable>
|
<Scrollable>
|
||||||
@@ -239,9 +240,14 @@ export default function JsonSchemaForm3() {
|
|||||||
</Form>*/}
|
</Form>*/}
|
||||||
|
|
||||||
{/*<CustomMediaForm />*/}
|
{/*<CustomMediaForm />*/}
|
||||||
{/*<Form schema={schema.media} initialValues={config.media} validateOn="change">
|
<Form
|
||||||
|
schema={schema.media}
|
||||||
|
initialValues={config.media as any}
|
||||||
|
/* validateOn="change"*/
|
||||||
|
onSubmit={console.log}
|
||||||
|
>
|
||||||
<Field name="" />
|
<Field name="" />
|
||||||
</Form>*/}
|
</Form>
|
||||||
|
|
||||||
{/*<Form
|
{/*<Form
|
||||||
schema={removeKeyRecursively(schema.media, "pattern") as any}
|
schema={removeKeyRecursively(schema.media, "pattern") as any}
|
||||||
@@ -257,20 +263,7 @@ export default function JsonSchemaForm3() {
|
|||||||
<AutoForm />
|
<AutoForm />
|
||||||
</Form>*/}
|
</Form>*/}
|
||||||
|
|
||||||
<Form
|
{/*<Form schema={ss} validateOn="change" />*/}
|
||||||
schema={ss}
|
|
||||||
initialValues={{
|
|
||||||
name: "Peter",
|
|
||||||
age: 20,
|
|
||||||
interested: true,
|
|
||||||
dinnerTime: "2023-12-31T23:59:59+02:00"
|
|
||||||
}}
|
|
||||||
ignoreKeys={["what"]}
|
|
||||||
onChange={(state) => console.log(state)}
|
|
||||||
onSubmit={(state) => console.log(state)}
|
|
||||||
validateOn="change"
|
|
||||||
options={{ debug: true }}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</Scrollable>
|
</Scrollable>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user