mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-17 12:56:05 +00:00
Merge remote-tracking branch 'origin/main' into fork/cameronapak/cp/216-fix-users-link
# Conflicts: # bun.lock
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { Api, type ApiOptions, type AuthState } from "Api";
|
||||
import { isDebug } from "core";
|
||||
import { isDebug } from "core/env";
|
||||
import { createContext, type ReactNode, useContext, useMemo, useState } from "react";
|
||||
import type { AdminBkndWindowContext } from "modules/server/AdminController";
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { DB, PrimaryFieldType } from "core";
|
||||
import { objectTransform } from "core/utils/objects";
|
||||
import { encodeSearch } from "core/utils/reqres";
|
||||
import type { EntityData, RepoQueryIn, RepositoryResult } from "data";
|
||||
import type { DB, PrimaryFieldType, EntityData, RepoQueryIn } from "bknd";
|
||||
import { objectTransform, encodeSearch } from "bknd/utils";
|
||||
import type { RepositoryResult } from "data/entities";
|
||||
import type { Insertable, Selectable, Updateable } from "kysely";
|
||||
import type { FetchPromise, ModuleApi, ResponseObject } from "modules/ModuleApi";
|
||||
import useSWR, { type SWRConfiguration, type SWRResponse, mutate } from "swr";
|
||||
|
||||
@@ -11,4 +11,4 @@ export * from "./api/use-entity";
|
||||
export { useAuth } from "./schema/auth/use-auth";
|
||||
export { Api, type TApiUser, type AuthState, type ApiOptions } from "../../Api";
|
||||
export { FetchPromise } from "modules/ModuleApi";
|
||||
export type { RepoQueryIn } from "data";
|
||||
export type { RepoQueryIn } from "bknd";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { AuthState } from "Api";
|
||||
import type { AuthResponse } from "auth";
|
||||
import type { AuthResponse } from "bknd";
|
||||
import { useApi, useInvalidate } from "ui/client";
|
||||
import { useClientContext } from "ui/client/ClientProvider";
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { TypeInvalidError, parse, transformObject } from "core/utils";
|
||||
import { constructEntity } from "data";
|
||||
import { constructEntity } from "data/schema/constructor";
|
||||
import {
|
||||
type TAppDataEntity,
|
||||
type TAppDataEntityFields,
|
||||
@@ -13,8 +12,7 @@ import {
|
||||
import { useBknd } from "ui/client/bknd";
|
||||
import type { TSchemaActions } from "ui/client/schema/actions";
|
||||
import { bkndModals } from "ui/modals";
|
||||
import * as tb from "@sinclair/typebox";
|
||||
const { Type } = tb;
|
||||
import { s, parse, InvalidSchemaError, transformObject } from "bknd/utils";
|
||||
|
||||
export function useBkndData() {
|
||||
const { config, app, schema, actions: bkndActions } = useBknd();
|
||||
@@ -27,12 +25,10 @@ export function useBkndData() {
|
||||
const actions = {
|
||||
entity: {
|
||||
add: async (name: string, data: TAppDataEntity) => {
|
||||
console.log("create entity", { data });
|
||||
const validated = parse(entitiesSchema, data, {
|
||||
skipMark: true,
|
||||
forceParse: true,
|
||||
});
|
||||
console.log("validated", validated);
|
||||
// @todo: check for existing?
|
||||
return await bkndActions.add("data", `entities.${name}`, validated);
|
||||
},
|
||||
@@ -44,7 +40,6 @@ export function useBkndData() {
|
||||
|
||||
return {
|
||||
config: async (partial: Partial<TAppDataEntity["config"]>): Promise<boolean> => {
|
||||
console.log("patch config", entityName, partial);
|
||||
return await bkndActions.overwrite(
|
||||
"data",
|
||||
`entities.${entityName}.config`,
|
||||
@@ -57,13 +52,11 @@ export function useBkndData() {
|
||||
},
|
||||
relations: {
|
||||
add: async (relation: TAppDataRelation) => {
|
||||
console.log("create relation", { relation });
|
||||
const name = crypto.randomUUID();
|
||||
const validated = parse(Type.Union(relationsSchema), relation, {
|
||||
const validated = parse(s.anyOf(relationsSchema), relation, {
|
||||
skipMark: true,
|
||||
forceParse: true,
|
||||
});
|
||||
console.log("validated", validated);
|
||||
return await bkndActions.add("data", `relations.${name}`, validated);
|
||||
},
|
||||
},
|
||||
@@ -120,17 +113,14 @@ const modals = {
|
||||
function entityFieldActions(bkndActions: TSchemaActions, entityName: string) {
|
||||
return {
|
||||
add: async (name: string, field: TAppDataField) => {
|
||||
console.log("create field", { name, field });
|
||||
const validated = parse(fieldsSchema, field, {
|
||||
skipMark: true,
|
||||
forceParse: true,
|
||||
});
|
||||
console.log("validated", validated);
|
||||
return await bkndActions.add("data", `entities.${entityName}.fields.${name}`, validated);
|
||||
},
|
||||
patch: () => null,
|
||||
set: async (fields: TAppDataEntityFields) => {
|
||||
console.log("set fields", entityName, fields);
|
||||
try {
|
||||
const validated = parse(entityFields, fields, {
|
||||
skipMark: true,
|
||||
@@ -141,11 +131,9 @@ function entityFieldActions(bkndActions: TSchemaActions, entityName: string) {
|
||||
`entities.${entityName}.fields`,
|
||||
validated,
|
||||
);
|
||||
console.log("res", res);
|
||||
//bkndActions.set("data", "entities", fields);
|
||||
} catch (e) {
|
||||
console.error("error", e);
|
||||
if (e instanceof TypeInvalidError) {
|
||||
if (e instanceof InvalidSchemaError) {
|
||||
alert("Error updating fields: " + e.firstToString());
|
||||
} else {
|
||||
alert("An error occured, check console. There will be nice error handling soon.");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type Static, parse } from "core/utils";
|
||||
import { parse } from "bknd/utils";
|
||||
import { type TAppFlowSchema, flowSchema } from "flows/flows-schema";
|
||||
import { useBknd } from "../../BkndProvider";
|
||||
|
||||
@@ -8,11 +8,8 @@ export function useFlows() {
|
||||
const actions = {
|
||||
flow: {
|
||||
create: async (name: string, data: TAppFlowSchema) => {
|
||||
console.log("would create", name, data);
|
||||
const parsed = parse(flowSchema, data, { skipMark: true, forceParse: true });
|
||||
console.log("parsed", parsed);
|
||||
const res = await bkndActions.add("flows", `flows.${name}`, parsed);
|
||||
console.log("res", res);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { App } from "App";
|
||||
import { type Entity, type EntityRelation, constructEntity, constructRelation } from "data";
|
||||
import type { Entity } from "data/entities";
|
||||
import type { EntityRelation } from "data/relations";
|
||||
import { constructEntity, constructRelation } from "data/schema/constructor";
|
||||
import { RelationAccessor } from "data/relations/RelationAccessor";
|
||||
import { Flow, TaskMap } from "flows";
|
||||
import type { BkndAdminOptions } from "ui/client/BkndProvider";
|
||||
|
||||
@@ -34,11 +34,13 @@ class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
||||
|
||||
private renderFallback() {
|
||||
if (this.props.fallback) {
|
||||
return typeof this.props.fallback === "function"
|
||||
? this.props.fallback({ error: this.state.error!, resetError: this.resetError })
|
||||
: this.props.fallback;
|
||||
return typeof this.props.fallback === "function" ? (
|
||||
this.props.fallback({ error: this.state.error!, resetError: this.resetError })
|
||||
) : (
|
||||
<BaseError>{this.props.fallback}</BaseError>
|
||||
);
|
||||
}
|
||||
return <BaseError>Error</BaseError>;
|
||||
return <BaseError>Error1</BaseError>;
|
||||
}
|
||||
|
||||
override render() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import clsx from "clsx";
|
||||
import { getBrowser } from "core/utils";
|
||||
import type { Field } from "data";
|
||||
import type { Field } from "data/fields";
|
||||
import { Switch as RadixSwitch } from "radix-ui";
|
||||
import {
|
||||
type ComponentPropsWithoutRef,
|
||||
|
||||
@@ -64,7 +64,7 @@ const FieldImpl = ({
|
||||
const id = `${name}-${useId()}`;
|
||||
const required = typeof _required === "boolean" ? _required : ctx.required;
|
||||
|
||||
if (!isTypeSchema(schema))
|
||||
if (!schema)
|
||||
return (
|
||||
<Pre>
|
||||
[Field] {path} has no schema ({JSON.stringify(schema)})
|
||||
|
||||
@@ -4,10 +4,13 @@ import type { JSONSchema } from "json-schema-to-ts";
|
||||
import type { JSONSchemaType } from "json-schema-to-ts/lib/types/definitions/jsonSchema";
|
||||
|
||||
export { isEqual, getPath } from "core/utils/objects";
|
||||
//export { isEqual } from "lodash-es";
|
||||
|
||||
export function isNotDefined(value: any) {
|
||||
return value === null || value === undefined || value === "";
|
||||
}
|
||||
|
||||
export function coerce(value: any, schema: JsonSchema, opts?: { required?: boolean }) {
|
||||
if (!value && typeof opts?.required === "boolean" && !opts.required) {
|
||||
if (isNotDefined(value) && typeof opts?.required === "boolean" && !opts.required) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,10 +5,10 @@ import { cloneDeep } from "lodash-es";
|
||||
import { forwardRef, useId, useImperativeHandle, useRef, useState } from "react";
|
||||
import { fields as Fields } from "./fields";
|
||||
import { templates as Templates } from "./templates";
|
||||
import { RJSFTypeboxValidator } from "./typebox/RJSFTypeboxValidator";
|
||||
import { widgets as Widgets } from "./widgets";
|
||||
import { JsonvTsValidator } from "./JsonvTsValidator";
|
||||
|
||||
const validator = new RJSFTypeboxValidator();
|
||||
const validator = new JsonvTsValidator();
|
||||
|
||||
// @todo: don't import FormProps, instead, copy it here instead of "any"
|
||||
export type JsonSchemaFormProps = any & {
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
import { type OutputUnit, Validator } from "@cfworker/json-schema";
|
||||
import type {
|
||||
CustomValidator,
|
||||
ErrorSchema,
|
||||
ErrorTransformer,
|
||||
FormContextType,
|
||||
RJSFSchema,
|
||||
RJSFValidationError,
|
||||
StrictRJSFSchema,
|
||||
UiSchema,
|
||||
ValidationData,
|
||||
ValidatorType,
|
||||
} from "@rjsf/utils";
|
||||
import { toErrorSchema } from "@rjsf/utils";
|
||||
import { get } from "lodash-es";
|
||||
|
||||
function removeUndefinedKeys(obj: any): any {
|
||||
if (!obj) return obj;
|
||||
|
||||
if (typeof obj === "object") {
|
||||
Object.keys(obj).forEach((key) => {
|
||||
if (obj[key] === undefined) {
|
||||
delete obj[key];
|
||||
} else if (typeof obj[key] === "object") {
|
||||
removeUndefinedKeys(obj[key]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.filter((item) => item !== undefined);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
function onlyKeepMostSpecific(errors: OutputUnit[]) {
|
||||
const mostSpecific = errors.filter((error) => {
|
||||
return !errors.some((other) => {
|
||||
return error !== other && other.instanceLocation.startsWith(error.instanceLocation);
|
||||
});
|
||||
});
|
||||
return mostSpecific;
|
||||
}
|
||||
|
||||
const debug = true;
|
||||
const validate = true;
|
||||
|
||||
export class JsonSchemaValidator<
|
||||
T = any,
|
||||
S extends StrictRJSFSchema = RJSFSchema,
|
||||
F extends FormContextType = any,
|
||||
> implements ValidatorType
|
||||
{
|
||||
// @ts-ignore
|
||||
rawValidation<Result extends OutputUnit = OutputUnit>(schema: S, formData?: T) {
|
||||
if (!validate) return { errors: [], validationError: null };
|
||||
|
||||
debug && console.log("JsonSchemaValidator.rawValidation", schema, formData);
|
||||
const validator = new Validator(schema as any);
|
||||
const validation = validator.validate(removeUndefinedKeys(formData));
|
||||
const specificErrors = onlyKeepMostSpecific(validation.errors);
|
||||
|
||||
return { errors: specificErrors, validationError: null as any };
|
||||
}
|
||||
|
||||
validateFormData(
|
||||
formData: T | undefined,
|
||||
schema: S,
|
||||
customValidate?: CustomValidator,
|
||||
transformErrors?: ErrorTransformer,
|
||||
uiSchema?: UiSchema,
|
||||
): ValidationData<T> {
|
||||
if (!validate) return { errors: [], errorSchema: {} as any };
|
||||
|
||||
debug &&
|
||||
console.log(
|
||||
"JsonSchemaValidator.validateFormData",
|
||||
formData,
|
||||
schema,
|
||||
customValidate,
|
||||
transformErrors,
|
||||
uiSchema,
|
||||
);
|
||||
const { errors } = this.rawValidation(schema, formData);
|
||||
debug && console.log("errors", { errors });
|
||||
|
||||
const transformedErrors = errors
|
||||
//.filter((error) => error.keyword !== "properties")
|
||||
.map((error) => {
|
||||
const schemaLocation = error.keywordLocation.replace(/^#\/?/, "").split("/").join(".");
|
||||
const propertyError = get(schema, schemaLocation);
|
||||
const errorText = `${error.error.replace(/\.$/, "")}${propertyError ? ` "${propertyError}"` : ""}`;
|
||||
//console.log(error, schemaLocation, get(schema, schemaLocation));
|
||||
return {
|
||||
name: error.keyword,
|
||||
message: errorText,
|
||||
property: "." + error.instanceLocation.replace(/^#\/?/, "").split("/").join("."),
|
||||
schemaPath: error.keywordLocation,
|
||||
stack: error.error,
|
||||
};
|
||||
});
|
||||
debug && console.log("transformed", transformedErrors);
|
||||
|
||||
return {
|
||||
errors: transformedErrors,
|
||||
errorSchema: toErrorSchema(transformedErrors),
|
||||
} as any;
|
||||
}
|
||||
|
||||
toErrorList(errorSchema?: ErrorSchema<T>, fieldPath?: string[]): RJSFValidationError[] {
|
||||
debug && console.log("JsonSchemaValidator.toErrorList", errorSchema, fieldPath);
|
||||
return [];
|
||||
}
|
||||
|
||||
isValid(schema: S, formData: T | undefined, rootSchema: S): boolean {
|
||||
if (!validate) return true;
|
||||
debug && console.log("JsonSchemaValidator.isValid", schema, formData, rootSchema);
|
||||
return this.rawValidation(schema, formData).errors.length === 0;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Check, Errors } from "core/utils";
|
||||
import { FromSchema } from "./from-schema";
|
||||
import * as s from "jsonv-ts";
|
||||
|
||||
import type {
|
||||
CustomValidator,
|
||||
@@ -15,7 +14,7 @@ import { toErrorSchema } from "@rjsf/utils";
|
||||
|
||||
const validate = true;
|
||||
|
||||
export class RJSFTypeboxValidator<T = any, S extends StrictRJSFSchema = RJSFSchema>
|
||||
export class JsonvTsValidator<T = any, S extends StrictRJSFSchema = RJSFSchema>
|
||||
implements ValidatorType
|
||||
{
|
||||
// @ts-ignore
|
||||
@@ -23,16 +22,16 @@ export class RJSFTypeboxValidator<T = any, S extends StrictRJSFSchema = RJSFSche
|
||||
if (!validate) {
|
||||
return { errors: [], validationError: null as any };
|
||||
}
|
||||
const tbSchema = FromSchema(schema as unknown);
|
||||
|
||||
//console.log("--validation", tbSchema, formData);
|
||||
const jsSchema = s.fromSchema(JSON.parse(JSON.stringify(schema)) as any);
|
||||
const result = jsSchema.validate(formData);
|
||||
|
||||
if (Check(tbSchema, formData)) {
|
||||
if (result.valid) {
|
||||
return { errors: [], validationError: null as any };
|
||||
}
|
||||
|
||||
return {
|
||||
errors: [...Errors(tbSchema, formData)],
|
||||
errors: result.errors,
|
||||
validationError: null as any,
|
||||
};
|
||||
}
|
||||
@@ -47,14 +46,12 @@ export class RJSFTypeboxValidator<T = any, S extends StrictRJSFSchema = RJSFSche
|
||||
const { errors } = this.rawValidation(schema, formData);
|
||||
|
||||
const transformedErrors = errors.map((error) => {
|
||||
const schemaLocation = error.path.substring(1).split("/").join(".");
|
||||
|
||||
return {
|
||||
name: "any",
|
||||
message: error.message,
|
||||
property: "." + schemaLocation,
|
||||
schemaPath: error.path,
|
||||
stack: error.message,
|
||||
message: error.error,
|
||||
property: "." + error.instanceLocation.substring(1).split("/").join("."),
|
||||
schemaPath: error.instanceLocation,
|
||||
stack: error.error,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,299 +0,0 @@
|
||||
/*--------------------------------------------------------------------------
|
||||
|
||||
@sinclair/typebox/prototypes
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017-2024 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
import * as Type from "@sinclair/typebox";
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Schematics
|
||||
// ------------------------------------------------------------------
|
||||
const IsExact = (value: unknown, expect: unknown) => value === expect;
|
||||
const IsSValue = (value: unknown): value is SValue =>
|
||||
Type.ValueGuard.IsString(value) ||
|
||||
Type.ValueGuard.IsNumber(value) ||
|
||||
Type.ValueGuard.IsBoolean(value);
|
||||
const IsSEnum = (value: unknown): value is SEnum =>
|
||||
Type.ValueGuard.IsObject(value) &&
|
||||
Type.ValueGuard.IsArray(value.enum) &&
|
||||
value.enum.every((value) => IsSValue(value));
|
||||
const IsSAllOf = (value: unknown): value is SAllOf =>
|
||||
Type.ValueGuard.IsObject(value) && Type.ValueGuard.IsArray(value.allOf);
|
||||
const IsSAnyOf = (value: unknown): value is SAnyOf =>
|
||||
Type.ValueGuard.IsObject(value) && Type.ValueGuard.IsArray(value.anyOf);
|
||||
const IsSOneOf = (value: unknown): value is SOneOf =>
|
||||
Type.ValueGuard.IsObject(value) && Type.ValueGuard.IsArray(value.oneOf);
|
||||
const IsSTuple = (value: unknown): value is STuple =>
|
||||
Type.ValueGuard.IsObject(value) &&
|
||||
IsExact(value.type, "array") &&
|
||||
Type.ValueGuard.IsArray(value.items);
|
||||
const IsSArray = (value: unknown): value is SArray =>
|
||||
Type.ValueGuard.IsObject(value) &&
|
||||
IsExact(value.type, "array") &&
|
||||
!Type.ValueGuard.IsArray(value.items) &&
|
||||
Type.ValueGuard.IsObject(value.items);
|
||||
const IsSConst = (value: unknown): value is SConst =>
|
||||
// biome-ignore lint: reason
|
||||
Type.ValueGuard.IsObject(value) && Type.ValueGuard.IsObject(value["const"]);
|
||||
const IsSString = (value: unknown): value is SString =>
|
||||
Type.ValueGuard.IsObject(value) && IsExact(value.type, "string");
|
||||
const IsSNumber = (value: unknown): value is SNumber =>
|
||||
Type.ValueGuard.IsObject(value) && IsExact(value.type, "number");
|
||||
const IsSInteger = (value: unknown): value is SInteger =>
|
||||
Type.ValueGuard.IsObject(value) && IsExact(value.type, "integer");
|
||||
const IsSBoolean = (value: unknown): value is SBoolean =>
|
||||
Type.ValueGuard.IsObject(value) && IsExact(value.type, "boolean");
|
||||
const IsSNull = (value: unknown): value is SBoolean =>
|
||||
Type.ValueGuard.IsObject(value) && IsExact(value.type, "null");
|
||||
const IsSProperties = (value: unknown): value is SProperties => Type.ValueGuard.IsObject(value);
|
||||
// prettier-ignore
|
||||
// biome-ignore format:
|
||||
const IsSObject = (value: unknown): value is SObject => Type.ValueGuard.IsObject(value) && IsExact(value.type, 'object') && IsSProperties(value.properties) && (value.required === undefined || Type.ValueGuard.IsArray(value.required) && value.required.every((value: unknown) => Type.ValueGuard.IsString(value)))
|
||||
type SValue = string | number | boolean;
|
||||
type SEnum = Readonly<{ enum: readonly SValue[] }>;
|
||||
type SAllOf = Readonly<{ allOf: readonly unknown[] }>;
|
||||
type SAnyOf = Readonly<{ anyOf: readonly unknown[] }>;
|
||||
type SOneOf = Readonly<{ oneOf: readonly unknown[] }>;
|
||||
type SProperties = Record<PropertyKey, unknown>;
|
||||
type SObject = Readonly<{
|
||||
type: "object";
|
||||
properties: SProperties;
|
||||
required?: readonly string[];
|
||||
}>;
|
||||
type STuple = Readonly<{ type: "array"; items: readonly unknown[] }>;
|
||||
type SArray = Readonly<{ type: "array"; items: unknown }>;
|
||||
type SConst = Readonly<{ const: SValue }>;
|
||||
type SString = Readonly<{ type: "string" }>;
|
||||
type SNumber = Readonly<{ type: "number" }>;
|
||||
type SInteger = Readonly<{ type: "integer" }>;
|
||||
type SBoolean = Readonly<{ type: "boolean" }>;
|
||||
type SNull = Readonly<{ type: "null" }>;
|
||||
// ------------------------------------------------------------------
|
||||
// FromRest
|
||||
// ------------------------------------------------------------------
|
||||
// prettier-ignore
|
||||
// biome-ignore format:
|
||||
type TFromRest<T extends readonly unknown[], Acc extends Type.TSchema[] = []> = (
|
||||
// biome-ignore lint: reason
|
||||
T extends readonly [infer L extends unknown, ...infer R extends unknown[]]
|
||||
? TFromSchema<L> extends infer S extends Type.TSchema
|
||||
? TFromRest<R, [...Acc, S]>
|
||||
: TFromRest<R, [...Acc]>
|
||||
: Acc
|
||||
)
|
||||
function FromRest<T extends readonly unknown[]>(T: T): TFromRest<T> {
|
||||
return T.map((L) => FromSchema(L)) as never;
|
||||
}
|
||||
// ------------------------------------------------------------------
|
||||
// FromEnumRest
|
||||
// ------------------------------------------------------------------
|
||||
// prettier-ignore
|
||||
// biome-ignore format:
|
||||
type TFromEnumRest<T extends readonly SValue[], Acc extends Type.TSchema[] = []> = (
|
||||
T extends readonly [infer L extends SValue, ...infer R extends SValue[]]
|
||||
? TFromEnumRest<R, [...Acc, Type.TLiteral<L>]>
|
||||
: Acc
|
||||
)
|
||||
function FromEnumRest<T extends readonly SValue[]>(T: T): TFromEnumRest<T> {
|
||||
return T.map((L) => Type.Literal(L)) as never;
|
||||
}
|
||||
// ------------------------------------------------------------------
|
||||
// AllOf
|
||||
// ------------------------------------------------------------------
|
||||
// prettier-ignore
|
||||
// biome-ignore format:
|
||||
type TFromAllOf<T extends SAllOf> = (
|
||||
TFromRest<T['allOf']> extends infer Rest extends Type.TSchema[]
|
||||
? Type.TIntersectEvaluated<Rest>
|
||||
: Type.TNever
|
||||
)
|
||||
function FromAllOf<T extends SAllOf>(T: T): TFromAllOf<T> {
|
||||
return Type.IntersectEvaluated(FromRest(T.allOf), T);
|
||||
}
|
||||
// ------------------------------------------------------------------
|
||||
// AnyOf
|
||||
// ------------------------------------------------------------------
|
||||
// prettier-ignore
|
||||
// biome-ignore format:
|
||||
type TFromAnyOf<T extends SAnyOf> = (
|
||||
TFromRest<T['anyOf']> extends infer Rest extends Type.TSchema[]
|
||||
? Type.TUnionEvaluated<Rest>
|
||||
: Type.TNever
|
||||
)
|
||||
function FromAnyOf<T extends SAnyOf>(T: T): TFromAnyOf<T> {
|
||||
return Type.UnionEvaluated(FromRest(T.anyOf), T);
|
||||
}
|
||||
// ------------------------------------------------------------------
|
||||
// OneOf
|
||||
// ------------------------------------------------------------------
|
||||
// prettier-ignore
|
||||
// biome-ignore format:
|
||||
type TFromOneOf<T extends SOneOf> = (
|
||||
TFromRest<T['oneOf']> extends infer Rest extends Type.TSchema[]
|
||||
? Type.TUnionEvaluated<Rest>
|
||||
: Type.TNever
|
||||
)
|
||||
function FromOneOf<T extends SOneOf>(T: T): TFromOneOf<T> {
|
||||
return Type.UnionEvaluated(FromRest(T.oneOf), T);
|
||||
}
|
||||
// ------------------------------------------------------------------
|
||||
// Enum
|
||||
// ------------------------------------------------------------------
|
||||
// prettier-ignore
|
||||
// biome-ignore format:
|
||||
type TFromEnum<T extends SEnum> = (
|
||||
TFromEnumRest<T['enum']> extends infer Elements extends Type.TSchema[]
|
||||
? Type.TUnionEvaluated<Elements>
|
||||
: Type.TNever
|
||||
)
|
||||
function FromEnum<T extends SEnum>(T: T): TFromEnum<T> {
|
||||
return Type.UnionEvaluated(FromEnumRest(T.enum));
|
||||
}
|
||||
// ------------------------------------------------------------------
|
||||
// Tuple
|
||||
// ------------------------------------------------------------------
|
||||
// prettier-ignore
|
||||
// biome-ignore format:
|
||||
type TFromTuple<T extends STuple> = (
|
||||
TFromRest<T['items']> extends infer Elements extends Type.TSchema[]
|
||||
? Type.TTuple<Elements>
|
||||
: Type.TTuple<[]>
|
||||
)
|
||||
// prettier-ignore
|
||||
// biome-ignore format:
|
||||
function FromTuple<T extends STuple>(T: T): TFromTuple<T> {
|
||||
return Type.Tuple(FromRest(T.items), T) as never
|
||||
}
|
||||
// ------------------------------------------------------------------
|
||||
// Array
|
||||
// ------------------------------------------------------------------
|
||||
// prettier-ignore
|
||||
// biome-ignore format:
|
||||
type TFromArray<T extends SArray> = (
|
||||
TFromSchema<T['items']> extends infer Items extends Type.TSchema
|
||||
? Type.TArray<Items>
|
||||
: Type.TArray<Type.TUnknown>
|
||||
)
|
||||
// prettier-ignore
|
||||
// biome-ignore format:
|
||||
function FromArray<T extends SArray>(T: T): TFromArray<T> {
|
||||
return Type.Array(FromSchema(T.items), T) as never
|
||||
}
|
||||
// ------------------------------------------------------------------
|
||||
// Const
|
||||
// ------------------------------------------------------------------
|
||||
// prettier-ignore
|
||||
// biome-ignore format:
|
||||
type TFromConst<T extends SConst> = (
|
||||
Type.Ensure<Type.TLiteral<T['const']>>
|
||||
)
|
||||
function FromConst<T extends SConst>(T: T) {
|
||||
return Type.Literal(T.const, T);
|
||||
}
|
||||
// ------------------------------------------------------------------
|
||||
// Object
|
||||
// ------------------------------------------------------------------
|
||||
type TFromPropertiesIsOptional<
|
||||
K extends PropertyKey,
|
||||
R extends string | unknown,
|
||||
> = unknown extends R ? true : K extends R ? false : true;
|
||||
// prettier-ignore
|
||||
// biome-ignore format:
|
||||
type TFromProperties<T extends SProperties, R extends string | unknown> = Type.Evaluate<{
|
||||
-readonly [K in keyof T]: TFromPropertiesIsOptional<K, R> extends true
|
||||
? Type.TOptional<TFromSchema<T[K]>>
|
||||
: TFromSchema<T[K]>
|
||||
}>
|
||||
// prettier-ignore
|
||||
// biome-ignore format:
|
||||
type TFromObject<T extends SObject> = (
|
||||
TFromProperties<T['properties'], Exclude<T['required'], undefined>[number]> extends infer Properties extends Type.TProperties
|
||||
? Type.TObject<Properties>
|
||||
: Type.TObject<{}>
|
||||
)
|
||||
function FromObject<T extends SObject>(T: T): TFromObject<T> {
|
||||
const properties = globalThis.Object.getOwnPropertyNames(T.properties).reduce((Acc, K) => {
|
||||
return {
|
||||
// biome-ignore lint:
|
||||
...Acc,
|
||||
[K]:
|
||||
// biome-ignore lint: reason
|
||||
T.required && T.required.includes(K)
|
||||
? FromSchema(T.properties[K])
|
||||
: Type.Optional(FromSchema(T.properties[K])),
|
||||
};
|
||||
}, {} as Type.TProperties);
|
||||
|
||||
if ("additionalProperties" in T) {
|
||||
return Type.Object(properties, {
|
||||
additionalProperties: FromSchema(T.additionalProperties),
|
||||
}) as never;
|
||||
}
|
||||
|
||||
return Type.Object(properties, T) as never;
|
||||
}
|
||||
// ------------------------------------------------------------------
|
||||
// FromSchema
|
||||
// ------------------------------------------------------------------
|
||||
// prettier-ignore
|
||||
// biome-ignore format:
|
||||
export type TFromSchema<T> = (
|
||||
T extends SAllOf ? TFromAllOf<T> :
|
||||
T extends SAnyOf ? TFromAnyOf<T> :
|
||||
T extends SOneOf ? TFromOneOf<T> :
|
||||
T extends SEnum ? TFromEnum<T> :
|
||||
T extends SObject ? TFromObject<T> :
|
||||
T extends STuple ? TFromTuple<T> :
|
||||
T extends SArray ? TFromArray<T> :
|
||||
T extends SConst ? TFromConst<T> :
|
||||
T extends SString ? Type.TString :
|
||||
T extends SNumber ? Type.TNumber :
|
||||
T extends SInteger ? Type.TInteger :
|
||||
T extends SBoolean ? Type.TBoolean :
|
||||
T extends SNull ? Type.TNull :
|
||||
Type.TUnknown
|
||||
)
|
||||
/** Parses a TypeBox type from raw JsonSchema */
|
||||
export function FromSchema<T>(T: T): TFromSchema<T> {
|
||||
// prettier-ignore
|
||||
// biome-ignore format:
|
||||
return (
|
||||
IsSAllOf(T) ? FromAllOf(T) :
|
||||
IsSAnyOf(T) ? FromAnyOf(T) :
|
||||
IsSOneOf(T) ? FromOneOf(T) :
|
||||
IsSEnum(T) ? FromEnum(T) :
|
||||
IsSObject(T) ? FromObject(T) :
|
||||
IsSTuple(T) ? FromTuple(T) :
|
||||
IsSArray(T) ? FromArray(T) :
|
||||
IsSConst(T) ? FromConst(T) :
|
||||
IsSString(T) ? Type.String(T) :
|
||||
IsSNumber(T) ? Type.Number(T) :
|
||||
IsSInteger(T) ? Type.Integer(T) :
|
||||
IsSBoolean(T) ? Type.Boolean(T) :
|
||||
IsSNull(T) ? Type.Null(T) :
|
||||
Type.Unknown(T || {})
|
||||
) as never
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { useEvent } from "ui/hooks/use-event";
|
||||
import {
|
||||
type CleanOptions,
|
||||
type InputElement,
|
||||
|
||||
@@ -1,22 +1,11 @@
|
||||
import type { AppAuthOAuthStrategy, AppAuthSchema } from "auth/auth-schema";
|
||||
import clsx from "clsx";
|
||||
import { Form } from "json-schema-form-react";
|
||||
import { NativeForm } from "ui/components/form/native-form/NativeForm";
|
||||
import { transform } from "lodash-es";
|
||||
import type { ComponentPropsWithoutRef } from "react";
|
||||
import { Button } from "ui/components/buttons/Button";
|
||||
import { Group, Input, Password, Label } from "ui/components/form/Formy/components";
|
||||
import { SocialLink } from "./SocialLink";
|
||||
import type { ValueError } from "@sinclair/typebox/value";
|
||||
import { type TSchema, Value } from "core/utils";
|
||||
import type { Validator } from "json-schema-form-react";
|
||||
import * as tbbox from "@sinclair/typebox";
|
||||
const { Type } = tbbox;
|
||||
|
||||
class TypeboxValidator implements Validator<ValueError> {
|
||||
async validate(schema: TSchema, data: any) {
|
||||
return Value.Check(schema, data) ? [] : [...Value.Errors(schema, data)];
|
||||
}
|
||||
}
|
||||
|
||||
export type LoginFormProps = Omit<ComponentPropsWithoutRef<"form">, "onSubmit" | "action"> & {
|
||||
className?: string;
|
||||
@@ -27,16 +16,6 @@ export type LoginFormProps = Omit<ComponentPropsWithoutRef<"form">, "onSubmit" |
|
||||
buttonLabel?: string;
|
||||
};
|
||||
|
||||
const validator = new TypeboxValidator();
|
||||
const schema = Type.Object({
|
||||
email: Type.String({
|
||||
pattern: "^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$",
|
||||
}),
|
||||
password: Type.String({
|
||||
minLength: 8, // @todo: this should be configurable
|
||||
}),
|
||||
});
|
||||
|
||||
export function AuthForm({
|
||||
formData,
|
||||
className,
|
||||
@@ -81,38 +60,31 @@ export function AuthForm({
|
||||
<Or />
|
||||
</>
|
||||
)}
|
||||
<Form
|
||||
<NativeForm
|
||||
method={method}
|
||||
action={password.action}
|
||||
{...(props as any)}
|
||||
schema={schema}
|
||||
validator={validator}
|
||||
validationMode="change"
|
||||
validateOn="change"
|
||||
className={clsx("flex flex-col gap-3 w-full", className)}
|
||||
>
|
||||
{({ errors, submitting }) => (
|
||||
<>
|
||||
<Group>
|
||||
<Label htmlFor="email">Email address</Label>
|
||||
<Input type="email" name="email" />
|
||||
</Group>
|
||||
<Group>
|
||||
<Label htmlFor="password">Password</Label>
|
||||
<Password name="password" />
|
||||
</Group>
|
||||
<Group>
|
||||
<Label htmlFor="email">Email address</Label>
|
||||
<Input type="email" name="email" required />
|
||||
</Group>
|
||||
<Group>
|
||||
<Label htmlFor="password">Password</Label>
|
||||
<Password name="password" required minLength={8} />
|
||||
</Group>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
variant="primary"
|
||||
size="large"
|
||||
className="w-full mt-2 justify-center"
|
||||
disabled={errors.length > 0 || submitting}
|
||||
>
|
||||
{buttonLabel}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</Form>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="primary"
|
||||
size="large"
|
||||
className="w-full mt-2 justify-center"
|
||||
>
|
||||
{buttonLabel}
|
||||
</Button>
|
||||
</NativeForm>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { DB } from "core";
|
||||
import type { DB } from "bknd";
|
||||
import {
|
||||
type ComponentPropsWithRef,
|
||||
createContext,
|
||||
@@ -37,6 +37,7 @@ export type DropzoneRenderProps = {
|
||||
uploadFile: (file: { path: string }) => Promise<void>;
|
||||
deleteFile: (file: { path: string }) => Promise<void>;
|
||||
openFileInput: () => void;
|
||||
addFiles: (files: (File | FileWithPath)[]) => void;
|
||||
};
|
||||
showPlaceholder: boolean;
|
||||
onClick?: (file: { path: string }) => void;
|
||||
@@ -55,7 +56,8 @@ export type DropzoneProps = {
|
||||
autoUpload?: boolean;
|
||||
onRejected?: (files: FileWithPath[]) => void;
|
||||
onDeleted?: (file: { path: string }) => void;
|
||||
onUploaded?: (files: FileStateWithData[]) => void;
|
||||
onUploadedAll?: (files: FileStateWithData[]) => void;
|
||||
onUploaded?: (file: FileStateWithData) => void;
|
||||
onClick?: (file: FileState) => void;
|
||||
placeholder?: {
|
||||
show?: boolean;
|
||||
@@ -86,6 +88,7 @@ export function Dropzone({
|
||||
placeholder,
|
||||
onRejected,
|
||||
onDeleted,
|
||||
onUploadedAll,
|
||||
onUploaded,
|
||||
children,
|
||||
onClick,
|
||||
@@ -123,8 +126,8 @@ export function Dropzone({
|
||||
});
|
||||
}
|
||||
|
||||
const { handleFileInputChange, ref } = useDropzone({
|
||||
onDropped: (newFiles: FileWithPath[]) => {
|
||||
const addFiles = useCallback(
|
||||
(newFiles: (File | FileWithPath)[]) => {
|
||||
console.log("onDropped", newFiles);
|
||||
if (!isAllowed(newFiles)) return;
|
||||
|
||||
@@ -162,10 +165,10 @@ export function Dropzone({
|
||||
// prep new files
|
||||
const currentPaths = _prev.map((f) => f.path);
|
||||
const filteredFiles: FileState[] = newFiles
|
||||
.filter((f) => f.path && !currentPaths.includes(f.path))
|
||||
.filter((f) => !("path" in f) || (f.path && !currentPaths.includes(f.path)))
|
||||
.map((f) => ({
|
||||
body: f,
|
||||
path: f.path!,
|
||||
path: "path" in f ? f.path! : f.name,
|
||||
name: f.name,
|
||||
size: f.size,
|
||||
type: f.type,
|
||||
@@ -184,6 +187,14 @@ export function Dropzone({
|
||||
return updatedFiles;
|
||||
});
|
||||
},
|
||||
[autoUpload, flow, maxItems, overwrite],
|
||||
);
|
||||
|
||||
const { handleFileInputChange, ref } = useDropzone({
|
||||
onDropped: (newFiles: FileWithPath[]) => {
|
||||
console.log("onDropped", newFiles);
|
||||
addFiles(newFiles);
|
||||
},
|
||||
onOver: (items) => {
|
||||
if (!isAllowed(items)) {
|
||||
setIsOver(true, false);
|
||||
@@ -220,13 +231,15 @@ export function Dropzone({
|
||||
const uploaded: FileStateWithData[] = [];
|
||||
for (const file of pendingFiles) {
|
||||
try {
|
||||
uploaded.push(await uploadFileProgress(file));
|
||||
const progress = await uploadFileProgress(file);
|
||||
uploaded.push(progress);
|
||||
onUploaded?.(progress);
|
||||
} catch (e) {
|
||||
handleUploadError(e);
|
||||
}
|
||||
}
|
||||
setUploading(false);
|
||||
onUploaded?.(uploaded);
|
||||
onUploadedAll?.(uploaded);
|
||||
}
|
||||
})();
|
||||
}
|
||||
@@ -342,7 +355,8 @@ export function Dropzone({
|
||||
|
||||
const uploadFile = useCallback(async (file: FileState) => {
|
||||
const result = await uploadFileProgress(file);
|
||||
onUploaded?.([result]);
|
||||
onUploadedAll?.([result]);
|
||||
onUploaded?.(result);
|
||||
}, []);
|
||||
|
||||
const openFileInput = useCallback(() => inputRef.current?.click(), [inputRef]);
|
||||
@@ -367,6 +381,7 @@ export function Dropzone({
|
||||
uploadFile,
|
||||
deleteFile,
|
||||
openFileInput,
|
||||
addFiles,
|
||||
},
|
||||
dropzoneProps: {
|
||||
maxItems,
|
||||
@@ -406,11 +421,13 @@ export const useDropzoneState = () => {
|
||||
const files = useStore(store, (state) => state.files);
|
||||
const isOver = useStore(store, (state) => state.isOver);
|
||||
const isOverAccepted = useStore(store, (state) => state.isOverAccepted);
|
||||
const uploading = useStore(store, (state) => state.uploading);
|
||||
|
||||
return {
|
||||
files,
|
||||
isOver,
|
||||
isOverAccepted,
|
||||
uploading,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { Api } from "bknd/client";
|
||||
import type { PrimaryFieldType } from "core";
|
||||
import type { RepoQueryIn } from "data";
|
||||
import type { PrimaryFieldType, RepoQueryIn } from "bknd";
|
||||
import type { MediaFieldSchema } from "media/AppMedia";
|
||||
import type { TAppMediaConfig } from "media/media-schema";
|
||||
import { useId, useEffect, useRef, useState } from "react";
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
// .current at the right timing."
|
||||
// So we will have to make do with this "close enough" approach for now.
|
||||
import { useLayoutEffect, useRef } from "react";
|
||||
import { isDebug } from "core";
|
||||
import { isDebug } from "core/env";
|
||||
|
||||
export const useEvent = <Fn>(fn: Fn): Fn => {
|
||||
if (isDebug()) {
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import { decodeSearch, encodeSearch, mergeObject } from "core/utils";
|
||||
import { decodeSearch, encodeSearch, mergeObject, type s, parse } from "bknd/utils";
|
||||
import { isEqual, transform } from "lodash-es";
|
||||
import { useLocation, useSearch as useWouterSearch } from "wouter";
|
||||
import { type s, parse } from "core/object/schema";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
|
||||
export type UseSearchOptions<Schema extends s.TAnySchema = s.TAnySchema> = {
|
||||
export type UseSearchOptions<Schema extends s.Schema = s.Schema> = {
|
||||
defaultValue?: Partial<s.StaticCoerced<Schema>>;
|
||||
beforeEncode?: (search: Partial<s.StaticCoerced<Schema>>) => object;
|
||||
};
|
||||
|
||||
export function useSearch<Schema extends s.TAnySchema = s.TAnySchema>(
|
||||
export function useSearch<Schema extends s.Schema = s.Schema>(
|
||||
schema: Schema,
|
||||
options?: UseSearchOptions<Schema>,
|
||||
) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { PrimaryFieldType } from "core";
|
||||
import { encodeSearch } from "core/utils";
|
||||
import type { PrimaryFieldType } from "bknd";
|
||||
import { encodeSearch } from "bknd/utils";
|
||||
import { useLocation, useRouter } from "wouter";
|
||||
import { useBknd } from "../client/BkndProvider";
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ export function SchemaFormModal({
|
||||
<JsonSchemaForm
|
||||
tagName="form"
|
||||
ref={formRef}
|
||||
schema={schema}
|
||||
schema={JSON.parse(JSON.stringify(schema))}
|
||||
uiSchema={uiSchema}
|
||||
className="legacy hide-required-mark fieldset-alternative mute-root"
|
||||
onChange={handleChange}
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
import type { PrimaryFieldType, Entity, EntityData, Field } from "bknd";
|
||||
import type { FieldApi, ReactFormExtendedApi } from "@tanstack/react-form";
|
||||
import type { JSX } from "react";
|
||||
import {
|
||||
type Entity,
|
||||
type EntityData,
|
||||
EnumField,
|
||||
type Field,
|
||||
JsonField,
|
||||
JsonSchemaField,
|
||||
RelationField,
|
||||
} from "data";
|
||||
import { useStore } from "@tanstack/react-store";
|
||||
import { MediaField } from "media/MediaField";
|
||||
import { type ComponentProps, Suspense } from "react";
|
||||
@@ -22,7 +14,8 @@ import { EntityRelationalFormField } from "./fields/EntityRelationalFormField";
|
||||
import ErrorBoundary from "ui/components/display/ErrorBoundary";
|
||||
import { Alert } from "ui/components/display/Alert";
|
||||
import { bkndModals } from "ui/modals";
|
||||
import type { PrimaryFieldType } from "core";
|
||||
import type { EnumField, JsonField, JsonSchemaField } from "data/fields";
|
||||
import type { RelationField } from "data/relations";
|
||||
|
||||
// simplify react form types 🤦
|
||||
export type FormApi = ReactFormExtendedApi<any, any, any, any, any, any, any, any, any, any>;
|
||||
@@ -162,11 +155,11 @@ function EntityFormField({ fieldApi, field, action, data, ...props }: EntityForm
|
||||
//const required = field.isRequired();
|
||||
//const customFieldProps = { ...props, action, required };
|
||||
|
||||
if (field instanceof RelationField) {
|
||||
if (field.type === "relation") {
|
||||
return (
|
||||
<EntityRelationalFormField
|
||||
fieldApi={fieldApi}
|
||||
field={field}
|
||||
field={field as RelationField}
|
||||
data={data}
|
||||
disabled={props.disabled}
|
||||
tabIndex={props.tabIndex}
|
||||
@@ -174,15 +167,15 @@ function EntityFormField({ fieldApi, field, action, data, ...props }: EntityForm
|
||||
);
|
||||
}
|
||||
|
||||
if (field instanceof JsonField) {
|
||||
return <EntityJsonFormField fieldApi={fieldApi} field={field} {...props} />;
|
||||
if (field.type === "json") {
|
||||
return <EntityJsonFormField fieldApi={fieldApi} field={field as JsonField} {...props} />;
|
||||
}
|
||||
|
||||
if (field instanceof JsonSchemaField) {
|
||||
if (field.type === "jsonschema") {
|
||||
return (
|
||||
<EntityJsonSchemaFormField
|
||||
fieldApi={fieldApi}
|
||||
field={field}
|
||||
field={field as JsonSchemaField}
|
||||
data={data}
|
||||
disabled={props.disabled}
|
||||
tabIndex={props.tabIndex}
|
||||
@@ -191,8 +184,8 @@ function EntityFormField({ fieldApi, field, action, data, ...props }: EntityForm
|
||||
);
|
||||
}
|
||||
|
||||
if (field instanceof EnumField) {
|
||||
return <EntityEnumFormField fieldApi={fieldApi} field={field} {...props} />;
|
||||
if (field.type === "enum") {
|
||||
return <EntityEnumFormField fieldApi={fieldApi} field={field as EnumField} {...props} />;
|
||||
}
|
||||
|
||||
const fieldElement = field.getHtmlConfig().element;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useToggle } from "@mantine/hooks";
|
||||
import type { Entity, EntityData } from "data";
|
||||
import type { Entity, EntityData } from "bknd";
|
||||
import {
|
||||
TbArrowDown,
|
||||
TbArrowUp,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Entity, EntityData } from "data";
|
||||
import type { Entity, EntityData } from "bknd";
|
||||
import { CellValue, DataTable, type DataTableProps } from "ui/components/table/DataTable";
|
||||
import ErrorBoundary from "ui/components/display/ErrorBoundary";
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { EntityData, JsonSchemaField } from "data";
|
||||
import type { EntityData } from "bknd";
|
||||
import type { JsonSchemaField } from "data/fields";
|
||||
import * as Formy from "ui/components/form/Formy";
|
||||
import { FieldLabel } from "ui/components/form/Formy";
|
||||
import { JsonSchemaForm } from "ui/components/form/json-schema";
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { getHotkeyHandler, useHotkeys } from "@mantine/hooks";
|
||||
import { ucFirst } from "core/utils";
|
||||
import type { EntityData, RelationField } from "data";
|
||||
import type { EntityData } from "bknd";
|
||||
import type { RelationField } from "data/relations";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { TbEye } from "react-icons/tb";
|
||||
import { useEntityQuery } from "ui/client";
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import type { ModalProps } from "@mantine/core";
|
||||
import type { ContextModalProps } from "@mantine/modals";
|
||||
import { type Static, StringEnum, StringIdentifier } from "core/utils";
|
||||
import { entitiesSchema, fieldsSchema, relationsSchema } from "data/data-schema";
|
||||
import { useState } from "react";
|
||||
import { type Modal2Ref, ModalBody, ModalFooter, ModalTitle } from "ui/components/modal/Modal2";
|
||||
import { Step, Steps, useStepContext } from "ui/components/steps/Steps";
|
||||
@@ -11,66 +9,16 @@ import { StepEntityFields } from "./step.entity.fields";
|
||||
import { StepRelation } from "./step.relation";
|
||||
import { StepSelect } from "./step.select";
|
||||
import Templates from "./templates/register";
|
||||
import * as tbbox from "@sinclair/typebox";
|
||||
const { Type } = tbbox;
|
||||
import type { TCreateModalSchema } from "./schema";
|
||||
|
||||
export type CreateModalRef = Modal2Ref;
|
||||
|
||||
export const ModalActions = ["entity", "relation", "media"] as const;
|
||||
|
||||
export const entitySchema = Type.Composite([
|
||||
Type.Object({
|
||||
name: StringIdentifier,
|
||||
}),
|
||||
entitiesSchema,
|
||||
]);
|
||||
|
||||
const schemaAction = Type.Union([
|
||||
StringEnum(["entity", "relation", "media"]),
|
||||
Type.String({ pattern: "^template-" }),
|
||||
]);
|
||||
export type TSchemaAction = Static<typeof schemaAction>;
|
||||
|
||||
const createFieldSchema = Type.Object({
|
||||
entity: StringIdentifier,
|
||||
name: StringIdentifier,
|
||||
field: Type.Array(fieldsSchema),
|
||||
});
|
||||
export type TFieldCreate = Static<typeof createFieldSchema>;
|
||||
|
||||
const createModalSchema = Type.Object(
|
||||
{
|
||||
action: schemaAction,
|
||||
initial: Type.Optional(Type.Any()),
|
||||
entities: Type.Optional(
|
||||
Type.Object({
|
||||
create: Type.Optional(Type.Array(entitySchema)),
|
||||
}),
|
||||
),
|
||||
relations: Type.Optional(
|
||||
Type.Object({
|
||||
create: Type.Optional(Type.Array(Type.Union(relationsSchema))),
|
||||
}),
|
||||
),
|
||||
fields: Type.Optional(
|
||||
Type.Object({
|
||||
create: Type.Optional(Type.Array(createFieldSchema)),
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
additionalProperties: false,
|
||||
},
|
||||
);
|
||||
export type TCreateModalSchema = Static<typeof createModalSchema>;
|
||||
|
||||
export function CreateModal({
|
||||
context,
|
||||
id,
|
||||
innerProps: { initialPath = [], initialState },
|
||||
}: ContextModalProps<{ initialPath?: string[]; initialState?: TCreateModalSchema }>) {
|
||||
const [path, setPath] = useState<string[]>(initialPath);
|
||||
console.log("...", initialPath, initialState);
|
||||
|
||||
function close() {
|
||||
context.closeModal(id);
|
||||
@@ -116,4 +64,4 @@ CreateModal.modalProps = {
|
||||
padding: 0,
|
||||
} satisfies Partial<ModalProps>;
|
||||
|
||||
export { ModalBody, ModalFooter, ModalTitle, useStepContext, relationsSchema };
|
||||
export { ModalBody, ModalFooter, ModalTitle, useStepContext };
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { s } from "bknd/utils";
|
||||
import { entitiesSchema, fieldsSchema, relationsSchema } from "data/data-schema";
|
||||
|
||||
export const ModalActions = ["entity", "relation", "media"] as const;
|
||||
|
||||
export const entitySchema = s.object({
|
||||
...entitiesSchema.properties,
|
||||
name: s.string(),
|
||||
});
|
||||
|
||||
// @todo: this union is not fully working, just "string"
|
||||
const schemaAction = s.anyOf([
|
||||
s.string({ enum: ["entity", "relation", "media"] }),
|
||||
s.string({ pattern: "^template-" }),
|
||||
]);
|
||||
export type TSchemaAction = s.Static<typeof schemaAction>;
|
||||
|
||||
const createFieldSchema = s.object({
|
||||
entity: s.string(),
|
||||
name: s.string(),
|
||||
field: s.array(fieldsSchema),
|
||||
});
|
||||
export type TFieldCreate = s.Static<typeof createFieldSchema>;
|
||||
|
||||
const createModalSchema = s.strictObject({
|
||||
action: schemaAction,
|
||||
initial: s.any().optional(),
|
||||
entities: s
|
||||
.object({
|
||||
create: s.array(entitySchema).optional(),
|
||||
})
|
||||
.optional(),
|
||||
relations: s
|
||||
.object({
|
||||
create: s.array(s.anyOf(relationsSchema)).optional(),
|
||||
})
|
||||
.optional(),
|
||||
fields: s
|
||||
.object({
|
||||
create: s.array(createFieldSchema).optional(),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
export type TCreateModalSchema = s.Static<typeof createModalSchema>;
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useDisclosure } from "@mantine/hooks";
|
||||
import {
|
||||
IconAlignJustified,
|
||||
IconAugmentedReality,
|
||||
IconBox,
|
||||
IconCirclesRelation,
|
||||
IconInfoCircle,
|
||||
@@ -15,7 +14,7 @@ import { IconButton, type IconType } from "ui/components/buttons/IconButton";
|
||||
import { JsonViewer } from "ui/components/code/JsonViewer";
|
||||
import { ModalBody, ModalFooter } from "ui/components/modal/Modal2";
|
||||
import { useStepContext } from "ui/components/steps/Steps";
|
||||
import type { TCreateModalSchema } from "ui/modules/data/components/schema/create-modal/CreateModal";
|
||||
import type { TCreateModalSchema } from "ui/modules/data/components/schema/create-modal/schema";
|
||||
|
||||
type ActionItem = SummaryItemProps & {
|
||||
run: () => Promise<boolean>;
|
||||
@@ -35,9 +34,9 @@ export function StepCreate() {
|
||||
action: "add",
|
||||
Icon: IconBox,
|
||||
type: "Entity",
|
||||
name: entity.name,
|
||||
name: entity.name!,
|
||||
json: entity,
|
||||
run: async () => await $data.actions.entity.add(entity.name, entity),
|
||||
run: async () => await $data.actions.entity.add(entity.name!, entity),
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { typeboxResolver } from "@hookform/resolvers/typebox";
|
||||
import { type Static, objectCleanEmpty } from "core/utils";
|
||||
import { objectCleanEmpty, type s } from "bknd/utils";
|
||||
import { type TAppDataEntityFields, entitiesSchema } from "data/data-schema";
|
||||
import { mergeWith } from "lodash-es";
|
||||
import { useRef } from "react";
|
||||
@@ -10,11 +9,13 @@ import {
|
||||
EntityFieldsForm,
|
||||
type EntityFieldsFormRef,
|
||||
} from "ui/routes/data/forms/entity.fields.form";
|
||||
import { ModalBody, ModalFooter, type TCreateModalSchema, useStepContext } from "./CreateModal";
|
||||
import { ModalBody, ModalFooter, useStepContext } from "./CreateModal";
|
||||
import { useBkndData } from "ui/client/schema/data/use-bknd-data";
|
||||
import { standardSchemaResolver } from "@hookform/resolvers/standard-schema";
|
||||
import { entitySchema, type TCreateModalSchema } from "./schema";
|
||||
|
||||
const schema = entitiesSchema;
|
||||
type Schema = Static<typeof schema>;
|
||||
const schema = entitySchema;
|
||||
type Schema = s.Static<typeof schema>;
|
||||
|
||||
export function StepEntityFields() {
|
||||
const { nextStep, stepBack, state, setState } = useStepContext<TCreateModalSchema>();
|
||||
@@ -40,7 +41,7 @@ export function StepEntityFields() {
|
||||
setValue,
|
||||
} = useForm({
|
||||
mode: "onTouched",
|
||||
resolver: typeboxResolver(schema),
|
||||
resolver: standardSchemaResolver(schema),
|
||||
defaultValues: initial as NonNullable<Schema>,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,33 +1,30 @@
|
||||
import { typeboxResolver } from "@hookform/resolvers/typebox";
|
||||
|
||||
import { standardSchemaResolver } from "@hookform/resolvers/standard-schema";
|
||||
import { TextInput, Textarea } from "@mantine/core";
|
||||
import { useFocusTrap } from "@mantine/hooks";
|
||||
import { useForm } from "react-hook-form";
|
||||
import {
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
type TCreateModalSchema,
|
||||
entitySchema,
|
||||
useStepContext,
|
||||
} from "./CreateModal";
|
||||
import { MantineSelect } from "ui/components/form/hook-form-mantine/MantineSelect";
|
||||
import { ModalBody, ModalFooter, useStepContext } from "./CreateModal";
|
||||
import { entitySchema, type TCreateModalSchema } from "./schema";
|
||||
import { s } from "bknd/utils";
|
||||
import { cloneSchema } from "core/utils/schema";
|
||||
|
||||
const schema = s.object({
|
||||
name: entitySchema.properties.name,
|
||||
config: entitySchema.properties.config.partial().optional(),
|
||||
});
|
||||
type Schema = s.Static<typeof schema>;
|
||||
|
||||
export function StepEntity() {
|
||||
const focusTrapRef = useFocusTrap();
|
||||
|
||||
const { nextStep, stepBack, state, setState } = useStepContext<TCreateModalSchema>();
|
||||
const { register, handleSubmit, formState, watch, control } = useForm({
|
||||
mode: "onTouched",
|
||||
resolver: typeboxResolver(entitySchema),
|
||||
defaultValues: state.entities?.create?.[0] ?? {},
|
||||
mode: "onChange",
|
||||
resolver: standardSchemaResolver(cloneSchema(schema)),
|
||||
defaultValues: (state.entities?.create?.[0] ?? {}) as Schema,
|
||||
});
|
||||
/*const data = watch();
|
||||
console.log("state", { isValid });
|
||||
console.log("schema", JSON.stringify(entitySchema));
|
||||
console.log("data", JSON.stringify(data));*/
|
||||
|
||||
function onSubmit(data: any) {
|
||||
console.log(data);
|
||||
console.log("onSubmit", data);
|
||||
setState((prev) => {
|
||||
const prevEntity = prev.entities?.create?.[0];
|
||||
if (prevEntity && prevEntity.name !== data.name) {
|
||||
@@ -47,6 +44,7 @@ export function StepEntity() {
|
||||
<>
|
||||
<form onSubmit={handleSubmit(onSubmit)} ref={focusTrapRef}>
|
||||
<ModalBody>
|
||||
<input type="hidden" {...register("type")} defaultValue="regular" />
|
||||
<TextInput
|
||||
data-autofocus
|
||||
required
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { typeboxResolver } from "@hookform/resolvers/typebox";
|
||||
import { Switch, TextInput } from "@mantine/core";
|
||||
import { TypeRegistry } from "@sinclair/typebox";
|
||||
import { IconDatabase } from "@tabler/icons-react";
|
||||
import { type Static, StringEnum, StringIdentifier, registerCustomTypeboxKinds } from "core/utils";
|
||||
import { ManyToOneRelation, type RelationType, RelationTypes } from "data";
|
||||
import { ManyToOneRelation, type RelationType, RelationTypes } from "data/relations";
|
||||
import type { ReactNode } from "react";
|
||||
import { type Control, type FieldValues, type UseFormRegister, useForm } from "react-hook-form";
|
||||
import { TbRefresh } from "react-icons/tb";
|
||||
@@ -13,12 +10,10 @@ import { MantineNumberInput } from "ui/components/form/hook-form-mantine/Mantine
|
||||
import { MantineSelect } from "ui/components/form/hook-form-mantine/MantineSelect";
|
||||
import { useStepContext } from "ui/components/steps/Steps";
|
||||
import { useEvent } from "ui/hooks/use-event";
|
||||
import { ModalBody, ModalFooter, type TCreateModalSchema } from "./CreateModal";
|
||||
import * as tbbox from "@sinclair/typebox";
|
||||
const { Type } = tbbox;
|
||||
|
||||
// @todo: check if this could become an issue
|
||||
registerCustomTypeboxKinds(TypeRegistry);
|
||||
import { ModalBody, ModalFooter } from "./CreateModal";
|
||||
import type { TCreateModalSchema } from "./schema";
|
||||
import { s, stringIdentifier } from "bknd/utils";
|
||||
import { standardSchemaResolver } from "@hookform/resolvers/standard-schema";
|
||||
|
||||
const Relations: {
|
||||
type: RelationType;
|
||||
@@ -47,11 +42,11 @@ const Relations: {
|
||||
},
|
||||
];
|
||||
|
||||
const schema = Type.Object({
|
||||
type: StringEnum(Relations.map((r) => r.type)),
|
||||
source: StringIdentifier,
|
||||
target: StringIdentifier,
|
||||
config: Type.Object({}),
|
||||
const schema = s.strictObject({
|
||||
type: s.string({ enum: Relations.map((r) => r.type) }),
|
||||
source: stringIdentifier,
|
||||
target: stringIdentifier,
|
||||
config: s.object({}),
|
||||
});
|
||||
|
||||
type ComponentCtx<T extends FieldValues = FieldValues> = {
|
||||
@@ -73,8 +68,8 @@ export function StepRelation() {
|
||||
watch,
|
||||
control,
|
||||
} = useForm({
|
||||
resolver: typeboxResolver(schema),
|
||||
defaultValues: (state.relations?.create?.[0] ?? {}) as Static<typeof schema>,
|
||||
resolver: standardSchemaResolver(schema),
|
||||
defaultValues: (state.relations?.create?.[0] ?? {}) as s.Static<typeof schema>,
|
||||
});
|
||||
const data = watch();
|
||||
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
import type { IconType } from "react-icons";
|
||||
import { TbBox, TbCirclesRelation, TbPhoto } from "react-icons/tb";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import {
|
||||
type ModalActions,
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
type TCreateModalSchema,
|
||||
type TSchemaAction,
|
||||
useStepContext,
|
||||
} from "./CreateModal";
|
||||
import { ModalBody, ModalFooter, useStepContext } from "./CreateModal";
|
||||
import Templates from "./templates/register";
|
||||
import type { TCreateModalSchema, TSchemaAction } from "./schema";
|
||||
|
||||
export function StepSelect() {
|
||||
const { nextStep, stepBack, state, path, setState } = useStepContext<TCreateModalSchema>();
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { typeboxResolver } from "@hookform/resolvers/typebox";
|
||||
import { Radio, TextInput } from "@mantine/core";
|
||||
import { Default, type Static, StringEnum, StringIdentifier, transformObject } from "core/utils";
|
||||
import { transformObject, s, stringIdentifier } from "bknd/utils";
|
||||
import type { MediaFieldConfig } from "media/MediaField";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
@@ -8,23 +7,17 @@ import { useBknd } from "ui/client/bknd";
|
||||
import { MantineNumberInput } from "ui/components/form/hook-form-mantine/MantineNumberInput";
|
||||
import { MantineRadio } from "ui/components/form/hook-form-mantine/MantineRadio";
|
||||
import { MantineSelect } from "ui/components/form/hook-form-mantine/MantineSelect";
|
||||
import {
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
type TCreateModalSchema,
|
||||
type TFieldCreate,
|
||||
useStepContext,
|
||||
} from "../../CreateModal";
|
||||
import * as tbbox from "@sinclair/typebox";
|
||||
const { Type } = tbbox;
|
||||
import { ModalBody, ModalFooter, useStepContext } from "../../CreateModal";
|
||||
import type { TCreateModalSchema, TFieldCreate } from "../../schema";
|
||||
import { standardSchemaResolver } from "@hookform/resolvers/standard-schema";
|
||||
|
||||
const schema = Type.Object({
|
||||
entity: StringIdentifier,
|
||||
cardinality_type: StringEnum(["single", "multiple"], { default: "multiple" }),
|
||||
cardinality: Type.Optional(Type.Number({ minimum: 1 })),
|
||||
name: StringIdentifier,
|
||||
const schema = s.object({
|
||||
entity: stringIdentifier,
|
||||
cardinality_type: s.string({ enum: ["single", "multiple"], default: "multiple" }),
|
||||
cardinality: s.number({ minimum: 1 }).optional(),
|
||||
name: stringIdentifier,
|
||||
});
|
||||
type TCreateModalMediaSchema = Static<typeof schema>;
|
||||
type TCreateModalMediaSchema = s.Static<typeof schema>;
|
||||
|
||||
export function TemplateMediaComponent() {
|
||||
const { stepBack, setState, state, path, nextStep } = useStepContext<TCreateModalSchema>();
|
||||
@@ -36,8 +29,9 @@ export function TemplateMediaComponent() {
|
||||
control,
|
||||
} = useForm({
|
||||
mode: "onChange",
|
||||
resolver: typeboxResolver(schema),
|
||||
defaultValues: Default(schema, state.initial ?? {}) as TCreateModalMediaSchema,
|
||||
resolver: standardSchemaResolver(schema),
|
||||
defaultValues: schema.template(state.initial ?? {}) as TCreateModalMediaSchema,
|
||||
//defaultValues: Default(schema, state.initial ?? {}) as TCreateModalMediaSchema,
|
||||
});
|
||||
const [forbidden, setForbidden] = useState<boolean>(false);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useForm } from "@tanstack/react-form";
|
||||
import type { Entity, EntityData } from "data";
|
||||
import type { Entity, EntityData } from "bknd";
|
||||
import { getChangeSet, getDefaultValues } from "data/helper";
|
||||
|
||||
type EntityFormProps = {
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { Handle, type Node, type NodeProps, Position } from "@xyflow/react";
|
||||
import { Const, transformObject } from "core/utils";
|
||||
import { transformObject } from "core/utils";
|
||||
import { type Trigger, TriggerMap } from "flows";
|
||||
import type { IconType } from "react-icons";
|
||||
import { TbCircleLetterT } from "react-icons/tb";
|
||||
import { JsonSchemaForm } from "ui/components/form/json-schema";
|
||||
import * as tbbox from "@sinclair/typebox";
|
||||
const { Type } = tbbox;
|
||||
import { s } from "bknd/utils";
|
||||
|
||||
export type TaskComponentProps = NodeProps<Node<{ trigger: Trigger }>> & {
|
||||
Icon?: IconType;
|
||||
@@ -14,9 +13,9 @@ export type TaskComponentProps = NodeProps<Node<{ trigger: Trigger }>> & {
|
||||
|
||||
const triggerSchemas = Object.values(
|
||||
transformObject(TriggerMap, (trigger, name) =>
|
||||
Type.Object(
|
||||
s.object(
|
||||
{
|
||||
type: Const(name),
|
||||
type: s.literal(name),
|
||||
config: trigger.cls.schema,
|
||||
},
|
||||
{ title: String(name), additionalProperties: false },
|
||||
@@ -47,7 +46,7 @@ export function TriggerComponent({
|
||||
<div className="flex flex-col gap-2 px-3 py-2">
|
||||
<JsonSchemaForm
|
||||
className="legacy"
|
||||
schema={Type.Union(triggerSchemas)}
|
||||
schema={s.anyOf(triggerSchemas)}
|
||||
onChange={console.log}
|
||||
formData={trigger}
|
||||
{...props}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { FieldProps, FormContextType, RJSFSchema, StrictRJSFSchema } from "@rjsf/utils";
|
||||
import { SimpleRenderer } from "core";
|
||||
import { ucFirst, ucFirstAll } from "core/utils";
|
||||
import { SimpleRenderer } from "core/template/SimpleRenderer";
|
||||
import { ucFirst, ucFirstAll } from "bknd/utils";
|
||||
import { useState } from "react";
|
||||
|
||||
const modes = ["field", "code"] as const;
|
||||
|
||||
@@ -1,30 +1,26 @@
|
||||
import { typeboxResolver } from "@hookform/resolvers/typebox";
|
||||
import { Input, NativeSelect, Select, TextInput } from "@mantine/core";
|
||||
import { Input, TextInput } from "@mantine/core";
|
||||
import { useToggle } from "@mantine/hooks";
|
||||
import { IconMinus, IconPlus, IconWorld } from "@tabler/icons-react";
|
||||
import type { Node, NodeProps } from "@xyflow/react";
|
||||
import type { Static } from "core/utils";
|
||||
import { s } from "bknd/utils";
|
||||
import { FetchTask } from "flows";
|
||||
import { useRef, useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { Button } from "ui/components/buttons/Button";
|
||||
import { JsonViewer } from "ui/components/code/JsonViewer";
|
||||
import { SegmentedControl } from "ui/components/form/SegmentedControl";
|
||||
import { MantineSelect } from "ui/components/form/hook-form-mantine/MantineSelect";
|
||||
import { type TFlowNodeData, useFlowSelector } from "../../../hooks/use-flow";
|
||||
import type { TFlowNodeData } from "../../../hooks/use-flow";
|
||||
import { KeyValueInput } from "../../form/KeyValueInput";
|
||||
import { BaseNode } from "../BaseNode";
|
||||
import * as tbbox from "@sinclair/typebox";
|
||||
const { Type } = tbbox;
|
||||
import { standardSchemaResolver } from "@hookform/resolvers/standard-schema";
|
||||
|
||||
const schema = Type.Composite([
|
||||
FetchTask.schema,
|
||||
Type.Object({
|
||||
query: Type.Optional(Type.Record(Type.String(), Type.String())),
|
||||
}),
|
||||
]);
|
||||
const schema = s.object({
|
||||
query: s.record(s.string()).optional(),
|
||||
...FetchTask.schema.properties,
|
||||
});
|
||||
|
||||
type TFetchTaskSchema = Static<typeof FetchTask.schema>;
|
||||
type TFetchTaskSchema = s.Static<typeof FetchTask.schema>;
|
||||
type FetchTaskFormProps = NodeProps<Node<TFlowNodeData>> & {
|
||||
params: TFetchTaskSchema;
|
||||
onChange: (params: any) => void;
|
||||
@@ -42,8 +38,8 @@ export function FetchTaskForm({ onChange, params, ...props }: FetchTaskFormProps
|
||||
watch,
|
||||
control,
|
||||
} = useForm({
|
||||
resolver: typeboxResolver(schema),
|
||||
defaultValues: params as Static<typeof schema>,
|
||||
resolver: standardSchemaResolver(schema),
|
||||
defaultValues: params as s.Static<typeof schema>,
|
||||
mode: "onChange",
|
||||
//defaultValues: (state.relations?.create?.[0] ?? {}) as Static<typeof schema>
|
||||
});
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
import { TypeRegistry } from "@sinclair/typebox";
|
||||
import { type Node, type NodeProps, Position } from "@xyflow/react";
|
||||
import { registerCustomTypeboxKinds } from "core/utils";
|
||||
import type { TAppFlowTaskSchema } from "flows/AppFlows";
|
||||
import { useFlowCanvas, useFlowSelector } from "../../../hooks/use-flow";
|
||||
import { Handle } from "../Handle";
|
||||
import { FetchTaskForm } from "./FetchTaskNode";
|
||||
import { RenderNode } from "./RenderNode";
|
||||
|
||||
registerCustomTypeboxKinds(TypeRegistry);
|
||||
|
||||
const TaskComponents = {
|
||||
fetch: FetchTaskForm,
|
||||
render: RenderNode,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { typeboxResolver } from "@hookform/resolvers/typebox";
|
||||
import { TextInput } from "@mantine/core";
|
||||
import type { Node, NodeProps } from "@xyflow/react";
|
||||
import { Const, type Static, registerCustomTypeboxKinds, transformObject } from "core/utils";
|
||||
import { transformObject } from "core/utils";
|
||||
import { TriggerMap } from "flows";
|
||||
import type { TAppFlowTriggerSchema } from "flows/AppFlows";
|
||||
import { useForm } from "react-hook-form";
|
||||
@@ -11,22 +10,19 @@ import { MantineSelect } from "ui/components/form/hook-form-mantine/MantineSelec
|
||||
import { useFlowCanvas, useFlowSelector } from "../../../hooks/use-flow";
|
||||
import { BaseNode } from "../BaseNode";
|
||||
import { Handle } from "../Handle";
|
||||
import * as tb from "@sinclair/typebox";
|
||||
const { Type, TypeRegistry } = tb;
|
||||
import { s } from "bknd/utils";
|
||||
import { standardSchemaResolver } from "@hookform/resolvers/standard-schema";
|
||||
|
||||
// @todo: check if this could become an issue
|
||||
registerCustomTypeboxKinds(TypeRegistry);
|
||||
|
||||
const schema = Type.Object({
|
||||
trigger: Type.Union(
|
||||
const schema = s.object({
|
||||
trigger: s.anyOf(
|
||||
Object.values(
|
||||
transformObject(TriggerMap, (trigger, name) =>
|
||||
Type.Object(
|
||||
s.strictObject(
|
||||
{
|
||||
type: Const(name),
|
||||
type: s.literal(name),
|
||||
config: trigger.cls.schema,
|
||||
},
|
||||
{ title: String(name), additionalProperties: false },
|
||||
{ title: String(name) },
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -50,13 +46,13 @@ export const TriggerNode = (props: NodeProps<Node<TAppFlowTriggerSchema & { labe
|
||||
watch,
|
||||
control,
|
||||
} = useForm({
|
||||
resolver: typeboxResolver(schema),
|
||||
defaultValues: { trigger: state } as Static<typeof schema>,
|
||||
resolver: standardSchemaResolver(schema),
|
||||
defaultValues: { trigger: state } as s.Static<typeof schema>,
|
||||
mode: "onChange",
|
||||
});
|
||||
const data = watch("trigger");
|
||||
|
||||
async function onSubmit(data: Static<typeof schema>) {
|
||||
async function onSubmit(data: s.Static<typeof schema>) {
|
||||
console.log("submit", data.trigger);
|
||||
// @ts-ignore
|
||||
await actions.trigger.update(data.trigger);
|
||||
|
||||
@@ -46,7 +46,7 @@ export const flowStateAtom = atom<TFlowState>({
|
||||
|
||||
const FlowCanvasContext = createContext<FlowContextType>(undefined!);
|
||||
|
||||
const DEFAULT_FLOW = { trigger: {}, tasks: {}, connections: {} };
|
||||
const DEFAULT_FLOW: TAppFlowSchema = { trigger: { type: "manual" }, tasks: {}, connections: {} };
|
||||
export function FlowCanvasProvider({ children, name }: { children: any; name?: string }) {
|
||||
//const [dirty, setDirty] = useState(false);
|
||||
const setFlowState = useSetAtom(flowStateAtom);
|
||||
@@ -71,7 +71,7 @@ export function FlowCanvasProvider({ children, name }: { children: any; name?: s
|
||||
update: async (trigger: TAppFlowTriggerSchema | any) => {
|
||||
console.log("update trigger", trigger);
|
||||
setFlowState((state) => {
|
||||
const flow = state.flow || DEFAULT_FLOW;
|
||||
const flow = state.flow || (DEFAULT_FLOW as any);
|
||||
return { ...state, dirty: true, flow: { ...flow, trigger } };
|
||||
});
|
||||
//return s.actions.patch("flows", `flows.flows.${name}`, { trigger });
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { StringIdentifier, transformObject, ucFirstAllSnakeToPascalWithSpaces } from "core/utils";
|
||||
import {
|
||||
transformObject,
|
||||
ucFirstAllSnakeToPascalWithSpaces,
|
||||
s,
|
||||
stringIdentifier,
|
||||
} from "bknd/utils";
|
||||
import { useBkndAuth } from "ui/client/schema/auth/use-bknd-auth";
|
||||
import { Alert } from "ui/components/display/Alert";
|
||||
import { bkndModals } from "ui/modals";
|
||||
@@ -28,13 +33,9 @@ export function AuthRolesList() {
|
||||
bkndModals.open(
|
||||
"form",
|
||||
{
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: StringIdentifier,
|
||||
},
|
||||
required: ["name"],
|
||||
},
|
||||
schema: s.strictObject({
|
||||
name: stringIdentifier,
|
||||
}),
|
||||
uiSchema: {
|
||||
name: {
|
||||
"ui:title": "Role name",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import clsx from "clsx";
|
||||
import { isDebug } from "core";
|
||||
import { isDebug } from "core/env";
|
||||
import { TbChevronDown, TbChevronUp } from "react-icons/tb";
|
||||
import { useBknd } from "ui/client/BkndProvider";
|
||||
import { useBkndAuth } from "ui/client/schema/auth/use-bknd-auth";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { isDebug } from "core";
|
||||
import { isDebug } from "core/env";
|
||||
import { autoFormatString } from "core/utils";
|
||||
import { type ChangeEvent, useState } from "react";
|
||||
import {
|
||||
@@ -64,8 +64,7 @@ function AuthStrategiesListInternal() {
|
||||
const config = $auth.config.strategies;
|
||||
const schema = $auth.schema.properties.strategies;
|
||||
const schemas = Object.fromEntries(
|
||||
// @ts-ignore
|
||||
$auth.schema.properties.strategies.additionalProperties.anyOf.map((s) => [
|
||||
$auth.schema.properties.strategies?.additionalProperties?.anyOf.map((s) => [
|
||||
s.properties.type.const,
|
||||
s,
|
||||
]),
|
||||
@@ -76,7 +75,12 @@ function AuthStrategiesListInternal() {
|
||||
}
|
||||
|
||||
return (
|
||||
<Form schema={schema} initialValues={config} onSubmit={handleSubmit} options={formOptions}>
|
||||
<Form
|
||||
schema={schema as any}
|
||||
initialValues={config}
|
||||
onSubmit={handleSubmit}
|
||||
options={formOptions}
|
||||
>
|
||||
<Subscribe
|
||||
selector={(state) => ({
|
||||
dirty: state.dirty,
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { typeboxResolver } from "@hookform/resolvers/typebox";
|
||||
import { Input, Switch, Tooltip } from "@mantine/core";
|
||||
import { guardRoleSchema } from "auth/auth-schema";
|
||||
import { type Static, ucFirst } from "core/utils";
|
||||
import { ucFirst, type s } from "bknd/utils";
|
||||
import { forwardRef, useImperativeHandle } from "react";
|
||||
import { type UseControllerProps, useController, useForm } from "react-hook-form";
|
||||
import { useBknd } from "ui/client/bknd";
|
||||
import { Button } from "ui/components/buttons/Button";
|
||||
import { MantineSwitch } from "ui/components/form/hook-form-mantine/MantineSwitch";
|
||||
import { standardSchemaResolver } from "@hookform/resolvers/standard-schema";
|
||||
|
||||
const schema = guardRoleSchema;
|
||||
type Role = Static<typeof guardRoleSchema>;
|
||||
type Role = s.Static<typeof guardRoleSchema>;
|
||||
|
||||
export type AuthRoleFormRef = {
|
||||
getData: () => Role;
|
||||
@@ -33,7 +33,7 @@ export const AuthRoleForm = forwardRef<
|
||||
reset,
|
||||
getValues,
|
||||
} = useForm({
|
||||
resolver: typeboxResolver(schema),
|
||||
resolver: standardSchemaResolver(schema),
|
||||
defaultValues: role,
|
||||
});
|
||||
|
||||
@@ -87,7 +87,7 @@ const Permissions = ({
|
||||
const {
|
||||
field: { value, onChange: fieldOnChange, ...field },
|
||||
fieldState,
|
||||
} = useController<Static<typeof schema>, "permissions">({
|
||||
} = useController<s.Static<typeof schema>, "permissions">({
|
||||
name: "permissions",
|
||||
control,
|
||||
});
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
IconSettings,
|
||||
IconSwitchHorizontal,
|
||||
} from "@tabler/icons-react";
|
||||
import type { Entity, TEntityType } from "data";
|
||||
import type { Entity, TEntityType } from "bknd";
|
||||
import { TbDatabasePlus } from "react-icons/tb";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { useBkndData } from "ui/client/schema/data/use-bknd-data";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { PrimaryFieldType } from "core";
|
||||
import { ucFirst } from "core/utils";
|
||||
import type { Entity, EntityData, EntityRelation } from "data";
|
||||
import type { PrimaryFieldType } from "bknd";
|
||||
import { ucFirst } from "bknd/utils";
|
||||
import type { Entity, EntityData, EntityRelation } from "bknd";
|
||||
import { Fragment, useState } from "react";
|
||||
import { TbDots } from "react-icons/tb";
|
||||
import { useApiQuery, useEntityQuery } from "ui/client";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { EntityData } from "data";
|
||||
import type { EntityData } from "bknd";
|
||||
import { useState } from "react";
|
||||
import { useEntityMutate } from "ui/client";
|
||||
import { useBkndData } from "ui/client/schema/data/use-bknd-data";
|
||||
@@ -11,7 +11,7 @@ import { Breadcrumbs2 } from "ui/layouts/AppShell/Breadcrumbs2";
|
||||
import { routes, useNavigate } from "ui/lib/routes";
|
||||
import { EntityForm } from "ui/modules/data/components/EntityForm";
|
||||
import { useEntityForm } from "ui/modules/data/hooks/useEntityForm";
|
||||
import { s } from "core/object/schema";
|
||||
import { s } from "bknd/utils";
|
||||
|
||||
export function DataEntityCreate({ params }) {
|
||||
const { $data } = useBkndData();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { type Entity, repoQuery } from "data";
|
||||
import type { Entity } from "bknd";
|
||||
import { repoQuery } from "data/server/query";
|
||||
import { Fragment } from "react";
|
||||
import { TbDots } from "react-icons/tb";
|
||||
import { useApiQuery } from "ui/client";
|
||||
@@ -14,7 +15,7 @@ import * as AppShell from "ui/layouts/AppShell/AppShell";
|
||||
import { routes, useNavigate } from "ui/lib/routes";
|
||||
import { useCreateUserModal } from "ui/modules/auth/hooks/use-create-user-modal";
|
||||
import { EntityTable2 } from "ui/modules/data/components/EntityTable2";
|
||||
import { s } from "core/object/schema";
|
||||
import { s } from "bknd/utils";
|
||||
import { pick } from "core/utils/objects";
|
||||
|
||||
const searchSchema = s.partialObject({
|
||||
|
||||
@@ -4,8 +4,8 @@ import {
|
||||
IconCirclesRelation,
|
||||
IconSettings,
|
||||
} from "@tabler/icons-react";
|
||||
import { isDebug } from "core";
|
||||
import type { Entity } from "data";
|
||||
import { isDebug } from "core/env";
|
||||
import type { Entity } from "bknd";
|
||||
import { cloneDeep } from "lodash-es";
|
||||
import { useRef, useState } from "react";
|
||||
import {
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { typeboxResolver } from "@hookform/resolvers/typebox";
|
||||
import { Tabs, TextInput, Textarea, Tooltip, Switch } from "@mantine/core";
|
||||
import { useDisclosure } from "@mantine/hooks";
|
||||
import {
|
||||
Default,
|
||||
type Static,
|
||||
StringIdentifier,
|
||||
objectCleanEmpty,
|
||||
omitKeys,
|
||||
ucFirstAllSnakeToPascalWithSpaces,
|
||||
} from "core/utils";
|
||||
s,
|
||||
stringIdentifier,
|
||||
} from "bknd/utils";
|
||||
import {
|
||||
type TAppDataEntityFields,
|
||||
fieldsSchemaObject as originalFieldsSchemaObject,
|
||||
@@ -26,31 +24,26 @@ import { type SortableItemProps, SortableList } from "ui/components/list/Sortabl
|
||||
import { Popover } from "ui/components/overlay/Popover";
|
||||
import { type TFieldSpec, fieldSpecs } from "ui/modules/data/components/fields-specs";
|
||||
import { dataFieldsUiSchema } from "../../settings/routes/data.settings";
|
||||
import * as tbbox from "@sinclair/typebox";
|
||||
import { useRoutePathState } from "ui/hooks/use-route-path-state";
|
||||
import { MantineSelect } from "ui/components/form/hook-form-mantine/MantineSelect";
|
||||
import type { TPrimaryFieldFormat } from "data/fields/PrimaryField";
|
||||
const { Type } = tbbox;
|
||||
import { standardSchemaResolver } from "@hookform/resolvers/standard-schema";
|
||||
import ErrorBoundary from "ui/components/display/ErrorBoundary";
|
||||
|
||||
const fieldsSchemaObject = originalFieldsSchemaObject;
|
||||
const fieldsSchema = Type.Union(Object.values(fieldsSchemaObject));
|
||||
const fieldsSchema = s.anyOf(Object.values(fieldsSchemaObject));
|
||||
|
||||
const fieldSchema = Type.Object(
|
||||
{
|
||||
name: StringIdentifier,
|
||||
new: Type.Optional(Type.Boolean({ const: true })),
|
||||
field: fieldsSchema,
|
||||
},
|
||||
{
|
||||
additionalProperties: false,
|
||||
},
|
||||
);
|
||||
type TFieldSchema = Static<typeof fieldSchema>;
|
||||
|
||||
const schema = Type.Object({
|
||||
fields: Type.Array(fieldSchema),
|
||||
const fieldSchema = s.strictObject({
|
||||
name: stringIdentifier,
|
||||
new: s.boolean({ const: true }).optional(),
|
||||
field: fieldsSchema,
|
||||
});
|
||||
type TFieldsFormSchema = Static<typeof schema>;
|
||||
type TFieldSchema = s.Static<typeof fieldSchema>;
|
||||
|
||||
const schema = s.strictObject({
|
||||
fields: s.array(fieldSchema),
|
||||
});
|
||||
type TFieldsFormSchema = s.Static<typeof schema>;
|
||||
|
||||
const fieldTypes = Object.keys(fieldsSchemaObject);
|
||||
const defaultType = fieldTypes[0];
|
||||
@@ -58,7 +51,9 @@ const commonProps = ["label", "description", "required", "fillable", "hidden", "
|
||||
|
||||
function specificFieldSchema(type: keyof typeof fieldsSchemaObject) {
|
||||
//console.log("specificFieldSchema", type);
|
||||
return Type.Omit(fieldsSchemaObject[type]?.properties.config, commonProps);
|
||||
return s.object(
|
||||
omitKeys(fieldsSchemaObject[type]?.properties.config.properties, commonProps as any),
|
||||
);
|
||||
}
|
||||
|
||||
export type EntityFieldsFormProps = {
|
||||
@@ -100,7 +95,7 @@ export const EntityFieldsForm = forwardRef<EntityFieldsFormRef, EntityFieldsForm
|
||||
reset,
|
||||
} = useForm({
|
||||
mode: "all",
|
||||
resolver: typeboxResolver(schema),
|
||||
resolver: standardSchemaResolver(schema),
|
||||
defaultValues: {
|
||||
fields: entityFields,
|
||||
} as TFieldsFormSchema,
|
||||
@@ -135,15 +130,14 @@ export const EntityFieldsForm = forwardRef<EntityFieldsFormRef, EntityFieldsForm
|
||||
}));
|
||||
|
||||
function handleAppend(_type: keyof typeof fieldsSchemaObject) {
|
||||
const newField = {
|
||||
append({
|
||||
name: "",
|
||||
new: true,
|
||||
field: {
|
||||
type: _type,
|
||||
config: Default(fieldsSchemaObject[_type]?.properties.config, {}) as any,
|
||||
config: fieldsSchemaObject[_type]?.properties.config.template() as any,
|
||||
},
|
||||
};
|
||||
append(newField);
|
||||
});
|
||||
}
|
||||
|
||||
const formProps = {
|
||||
@@ -465,19 +459,17 @@ function EntityField({
|
||||
</Tabs.Panel>
|
||||
<Tabs.Panel value="specific">
|
||||
<div className="flex flex-col gap-2 pt-3 pb-1">
|
||||
<JsonSchemaForm
|
||||
key={type}
|
||||
schema={specificFieldSchema(type as any)}
|
||||
formData={specificData}
|
||||
uiSchema={dataFieldsUiSchema.config}
|
||||
className="legacy hide-required-mark fieldset-alternative mute-root"
|
||||
onChange={(value) => {
|
||||
setValue(`${prefix}.config`, {
|
||||
...getValues([`fields.${index}.config`])[0],
|
||||
...value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<ErrorBoundary fallback={`Error rendering JSON Schema for ${type}`}>
|
||||
<SpecificForm
|
||||
field={field}
|
||||
onChange={(value) => {
|
||||
setValue(`${prefix}.config`, {
|
||||
...getValues([`fields.${index}.config`])[0],
|
||||
...value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
</Tabs.Panel>
|
||||
<Tabs.Panel value="code">
|
||||
@@ -502,3 +494,25 @@ function EntityField({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const SpecificForm = ({
|
||||
field,
|
||||
onChange,
|
||||
}: {
|
||||
field: FieldArrayWithId<TFieldsFormSchema, "fields", "id">;
|
||||
onChange: (value: any) => void;
|
||||
}) => {
|
||||
const type = field.field.type;
|
||||
const specificData = omit(field.field.config, commonProps);
|
||||
|
||||
return (
|
||||
<JsonSchemaForm
|
||||
key={type}
|
||||
schema={specificFieldSchema(type as any)?.toJSON()}
|
||||
formData={specificData}
|
||||
uiSchema={dataFieldsUiSchema.config}
|
||||
className="legacy hide-required-mark fieldset-alternative mute-root"
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { IconHierarchy2 } from "@tabler/icons-react";
|
||||
import { isDebug } from "core";
|
||||
import { isDebug } from "core/env";
|
||||
import { TbSettings } from "react-icons/tb";
|
||||
import { useBknd } from "../../client/BkndProvider";
|
||||
import { IconButton } from "../../components/buttons/IconButton";
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { typeboxResolver } from "@hookform/resolvers/typebox";
|
||||
import { TextInput } from "@mantine/core";
|
||||
import { useFocusTrap } from "@mantine/hooks";
|
||||
import { TypeRegistry } from "@sinclair/typebox";
|
||||
import { type Static, StringEnum, StringIdentifier, registerCustomTypeboxKinds } from "core/utils";
|
||||
import { TRIGGERS } from "flows/flows-schema";
|
||||
import { forwardRef, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
@@ -16,18 +13,16 @@ import {
|
||||
ModalTitle,
|
||||
} from "../../../components/modal/Modal2";
|
||||
import { Step, Steps, useStepContext } from "../../../components/steps/Steps";
|
||||
import * as tbbox from "@sinclair/typebox";
|
||||
const { Type } = tbbox;
|
||||
|
||||
registerCustomTypeboxKinds(TypeRegistry);
|
||||
import { s, stringIdentifier } from "bknd/utils";
|
||||
import { standardSchemaResolver } from "@hookform/resolvers/standard-schema";
|
||||
|
||||
export type TCreateFlowModalSchema = any;
|
||||
const triggerNames = Object.keys(TRIGGERS) as unknown as (keyof typeof TRIGGERS)[];
|
||||
|
||||
const schema = Type.Object({
|
||||
name: StringIdentifier,
|
||||
trigger: StringEnum(triggerNames),
|
||||
mode: StringEnum(["async", "sync"]),
|
||||
const schema = s.strictObject({
|
||||
name: stringIdentifier,
|
||||
trigger: s.string({ enum: triggerNames }),
|
||||
mode: s.string({ enum: ["async", "sync"] }),
|
||||
});
|
||||
|
||||
export const FlowCreateModal = forwardRef<Modal2Ref>(function FlowCreateModal(props, ref) {
|
||||
@@ -61,16 +56,16 @@ export function StepCreate() {
|
||||
register,
|
||||
formState: { isValid, errors },
|
||||
} = useForm({
|
||||
resolver: typeboxResolver(schema),
|
||||
resolver: standardSchemaResolver(schema),
|
||||
defaultValues: {
|
||||
name: "",
|
||||
trigger: "manual",
|
||||
mode: "async",
|
||||
} as Static<typeof schema>,
|
||||
} as s.Static<typeof schema>,
|
||||
mode: "onSubmit",
|
||||
});
|
||||
|
||||
async function onSubmit(data: Static<typeof schema>) {
|
||||
async function onSubmit(data: s.Static<typeof schema>) {
|
||||
console.log(data, isValid);
|
||||
actions.flow.create(data.name, {
|
||||
trigger: {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { IconBrandAws, IconBrandCloudflare, IconCloud, IconServer } from "@tabler/icons-react";
|
||||
import { isDebug } from "core";
|
||||
import { isDebug } from "core/env";
|
||||
import { autoFormatString } from "core/utils";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { useBknd } from "ui/client/BkndProvider";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useHotkeys } from "@mantine/hooks";
|
||||
import { type TObject, ucFirst } from "core/utils";
|
||||
import { ucFirst, type s } from "bknd/utils";
|
||||
import { omit } from "lodash-es";
|
||||
import { type ReactNode, useMemo, useRef, useState } from "react";
|
||||
import { TbSettings } from "react-icons/tb";
|
||||
@@ -20,8 +20,8 @@ import { SettingNewModal, type SettingsNewModalProps } from "./SettingNewModal";
|
||||
import { SettingSchemaModal, type SettingsSchemaModalRef } from "./SettingSchemaModal";
|
||||
|
||||
export type SettingProps<
|
||||
Schema extends TObject = TObject,
|
||||
Props = Schema extends TObject<infer TProperties> ? TProperties : any,
|
||||
Schema extends s.ObjectSchema = s.ObjectSchema,
|
||||
Props = Schema extends s.ObjectSchema<infer TProperties> ? TProperties : any,
|
||||
> = {
|
||||
schema: Schema;
|
||||
config: any;
|
||||
@@ -44,7 +44,7 @@ export type SettingProps<
|
||||
};
|
||||
};
|
||||
|
||||
export function Setting<Schema extends TObject = any>({
|
||||
export function Setting<Schema extends s.ObjectSchema = s.ObjectSchema>({
|
||||
schema,
|
||||
uiSchema,
|
||||
config,
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { useDisclosure, useFocusTrap } from "@mantine/hooks";
|
||||
import type { TObject } from "core/utils";
|
||||
import { omit } from "lodash-es";
|
||||
import { useRef, useState } from "react";
|
||||
import { TbCirclePlus, TbVariable } from "react-icons/tb";
|
||||
import { useBknd } from "ui/client/BkndProvider";
|
||||
import { Button } from "ui/components/buttons/Button";
|
||||
import * as Formy from "ui/components/form/Formy";
|
||||
@@ -10,9 +8,10 @@ import { JsonSchemaForm, type JsonSchemaFormRef } from "ui/components/form/json-
|
||||
import { Dropdown } from "ui/components/overlay/Dropdown";
|
||||
import { Modal } from "ui/components/overlay/Modal";
|
||||
import { useLocation } from "wouter";
|
||||
import type { s } from "bknd/utils";
|
||||
|
||||
export type SettingsNewModalProps = {
|
||||
schema: TObject;
|
||||
schema: s.ObjectSchema;
|
||||
uiSchema?: object;
|
||||
anyOfValues?: Record<string, { label: string; icon?: any }>;
|
||||
path: string[];
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import type { Static, TObject } from "core/utils";
|
||||
import type { JSONSchema7 } from "json-schema";
|
||||
import { cloneDeep, omit, pick } from "lodash-es";
|
||||
import { omitKeys, type s } from "bknd/utils";
|
||||
|
||||
export function extractSchema<
|
||||
Schema extends TObject,
|
||||
Schema extends s.ObjectSchema,
|
||||
Keys extends keyof Schema["properties"],
|
||||
Config extends Static<Schema>,
|
||||
Config extends s.Static<Schema>,
|
||||
>(
|
||||
schema: Schema,
|
||||
config: Config,
|
||||
@@ -22,13 +21,13 @@ export function extractSchema<
|
||||
},
|
||||
] {
|
||||
if (!schema.properties) {
|
||||
return [{ ...schema }, config, {} as any];
|
||||
return [{ ...schema.toJSON() }, config, {} as any];
|
||||
}
|
||||
|
||||
const newSchema = cloneDeep(schema);
|
||||
const newSchema = JSON.parse(JSON.stringify(schema));
|
||||
const updated = {
|
||||
...newSchema,
|
||||
properties: omit(newSchema.properties, keys),
|
||||
properties: omitKeys(newSchema.properties, keys),
|
||||
};
|
||||
if (updated.required) {
|
||||
updated.required = updated.required.filter((key) => !keys.includes(key as any));
|
||||
@@ -44,7 +43,7 @@ export function extractSchema<
|
||||
};
|
||||
}
|
||||
|
||||
const reducedConfig = omit(config, keys) as any;
|
||||
const reducedConfig = omitKeys(config, keys as string[]) as any;
|
||||
|
||||
return [updated, reducedConfig, extracted];
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import AppShellAccordionsTest from "ui/routes/test/tests/appshell-accordions-test";
|
||||
import JsonSchemaFormReactTest from "ui/routes/test/tests/json-schema-form-react-test";
|
||||
|
||||
import FormyTest from "ui/routes/test/tests/formy-test";
|
||||
import HtmlFormTest from "ui/routes/test/tests/html-form-test";
|
||||
@@ -49,7 +48,6 @@ const tests = {
|
||||
SWRAndAPI,
|
||||
SwrAndDataApi,
|
||||
DropzoneElementTest,
|
||||
JsonSchemaFormReactTest,
|
||||
JsonSchemaForm3,
|
||||
FormyTest,
|
||||
HtmlFormTest,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { parse } from "core/utils";
|
||||
import { AppFlows } from "flows/AppFlows";
|
||||
import { useState } from "react";
|
||||
import { JsonViewer } from "../../../components/code/JsonViewer";
|
||||
import { JsonSchemaForm } from "../../../components/form/json-schema";
|
||||
import { Scrollable } from "../../../layouts/AppShell/AppShell";
|
||||
import { parse } from "bknd/utils";
|
||||
|
||||
export default function FlowCreateSchemaTest() {
|
||||
//const schema = flowsConfigSchema;
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
import { Form, type Validator } from "json-schema-form-react";
|
||||
import { useState } from "react";
|
||||
|
||||
import { type TSchema, Type } from "@sinclair/typebox";
|
||||
import { Value, type ValueError } from "@sinclair/typebox/value";
|
||||
|
||||
class TypeboxValidator implements Validator<ValueError> {
|
||||
async validate(schema: TSchema, data: any) {
|
||||
return Value.Check(schema, data) ? [] : [...Value.Errors(schema, data)];
|
||||
}
|
||||
}
|
||||
const validator = new TypeboxValidator();
|
||||
|
||||
const schema = Type.Object({
|
||||
name: Type.String(),
|
||||
age: Type.Optional(Type.Number()),
|
||||
});
|
||||
|
||||
export default function JsonSchemaFormReactTest() {
|
||||
const [data, setData] = useState(null);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form
|
||||
schema={schema}
|
||||
/*onChange={setData}
|
||||
onSubmit={setData}*/
|
||||
validator={validator}
|
||||
validationMode="change"
|
||||
>
|
||||
{({ errors, dirty, reset }) => (
|
||||
<>
|
||||
<div>
|
||||
<b>
|
||||
Form {dirty ? "*" : ""} (valid: {errors.length === 0 ? "valid" : "invalid"})
|
||||
</b>
|
||||
</div>
|
||||
<div>
|
||||
<input type="text" name="name" />
|
||||
<input type="number" name="age" />
|
||||
</div>
|
||||
<div>
|
||||
<button type="submit">submit</button>
|
||||
<button type="button" onClick={reset}>
|
||||
reset
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Form>
|
||||
<pre>{JSON.stringify(data, null, 2)}</pre>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -73,7 +73,7 @@ export default function JsonSchemaForm3() {
|
||||
return (
|
||||
<Scrollable>
|
||||
<div className="flex flex-col p-3">
|
||||
<Form schema={_schema.auth} options={formOptions} />
|
||||
<Form schema={_schema.auth.toJSON()} options={formOptions} />
|
||||
|
||||
{/*<Form
|
||||
onChange={(data) => console.log("change", data)}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { typeboxResolver } from "@hookform/resolvers/typebox";
|
||||
import { standardSchemaResolver } from "@hookform/resolvers/standard-schema";
|
||||
import { TextInput } from "@mantine/core";
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { s } from "bknd/utils";
|
||||
|
||||
const schema = Type.Object({
|
||||
example: Type.Optional(Type.String()),
|
||||
exampleRequired: Type.String({ minLength: 2 }),
|
||||
const schema = s.object({
|
||||
example: s.string().optional(),
|
||||
exampleRequired: s.string({ minLength: 2 }),
|
||||
});
|
||||
|
||||
export default function ReactHookErrors() {
|
||||
@@ -15,8 +15,10 @@ export default function ReactHookErrors() {
|
||||
watch,
|
||||
formState: { errors },
|
||||
} = useForm({
|
||||
resolver: typeboxResolver(schema),
|
||||
resolver: standardSchemaResolver(schema),
|
||||
});
|
||||
const data = watch();
|
||||
|
||||
const onSubmit = (data) => console.log(data);
|
||||
|
||||
console.log(watch("example")); // watch input value by passing the name of it
|
||||
|
||||
Reference in New Issue
Block a user