mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 04:27:21 +00:00
Merge remote-tracking branch 'origin/release/0.6' into refactor/optimize-ui-bundle-size
# Conflicts: # app/build.ts # app/package.json
This commit is contained in:
@@ -34,7 +34,11 @@ export function EntityTable2({ entity, select, ...props }: EntityTableProps) {
|
||||
const field = getField(property)!;
|
||||
_value = field.getValue(value, "table");
|
||||
} catch (e) {
|
||||
console.warn("Couldn't render value", { value, property, entity, select, ...props }, e);
|
||||
console.warn(
|
||||
"Couldn't render value",
|
||||
{ value, property, entity, select, columns, ...props },
|
||||
e
|
||||
);
|
||||
}
|
||||
|
||||
return <CellValue value={_value} property={property} />;
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
TbToggleLeft
|
||||
} from "react-icons/tb";
|
||||
|
||||
type TFieldSpec = {
|
||||
export type TFieldSpec = {
|
||||
type: string;
|
||||
label: string;
|
||||
icon: any;
|
||||
|
||||
@@ -9,6 +9,7 @@ import { useBknd } from "ui/client/bknd";
|
||||
import { Button } from "ui/components/buttons/Button";
|
||||
import * as Formy from "ui/components/form/Formy";
|
||||
import { Popover } from "ui/components/overlay/Popover";
|
||||
import { Link } from "ui/components/wouter/Link";
|
||||
import { routes } from "ui/lib/routes";
|
||||
import { useLocation } from "wouter";
|
||||
import { EntityTable } from "../EntityTable";
|
||||
@@ -82,7 +83,9 @@ export function EntityRelationalFormField({
|
||||
|
||||
return (
|
||||
<Formy.Group>
|
||||
<Formy.Label htmlFor={fieldApi.name}>{field.getLabel()}</Formy.Label>
|
||||
<Formy.Label htmlFor={fieldApi.name}>
|
||||
{field.getLabel({ fallback: false }) ?? entity.label}
|
||||
</Formy.Label>
|
||||
<div
|
||||
data-disabled={fetching || disabled ? 1 : undefined}
|
||||
className="data-[disabled]:opacity-70 data-[disabled]:pointer-events-none"
|
||||
@@ -152,9 +155,11 @@ export function EntityRelationalFormField({
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<Button IconLeft={TbEye} onClick={handleViewItem} size="small">
|
||||
View
|
||||
</Button>
|
||||
<Link to={routes.data.entity.edit(entity.name, _value.id as any)}>
|
||||
<Button IconLeft={TbEye} size="small">
|
||||
View
|
||||
</Button>
|
||||
</Link>
|
||||
</>
|
||||
) : (
|
||||
<div className="pl-2">- Select -</div>
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
import { type Static, StringEnum, StringIdentifier, Type, transformObject } from "core/utils";
|
||||
import { FieldClassMap } from "data";
|
||||
import type { ModalProps } from "@mantine/core";
|
||||
import type { ContextModalProps } from "@mantine/modals";
|
||||
import { type Static, StringEnum, StringIdentifier, Type } from "core/utils";
|
||||
import { entitiesSchema, fieldsSchema, relationsSchema } from "data/data-schema";
|
||||
import { omit } from "lodash-es";
|
||||
import { forwardRef, useState } from "react";
|
||||
import {
|
||||
Modal2,
|
||||
type Modal2Ref,
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
ModalTitle
|
||||
} from "ui/components/modal/Modal2";
|
||||
import { useState } from "react";
|
||||
import { type Modal2Ref, ModalBody, ModalFooter, ModalTitle } from "ui/components/modal/Modal2";
|
||||
import { Step, Steps, useStepContext } from "ui/components/steps/Steps";
|
||||
import { StepCreate } from "ui/modules/data/components/schema/create-modal/step.create";
|
||||
import { StepEntity } from "./step.entity";
|
||||
@@ -45,6 +39,7 @@ 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))
|
||||
@@ -67,48 +62,59 @@ const createModalSchema = Type.Object(
|
||||
);
|
||||
export type TCreateModalSchema = Static<typeof createModalSchema>;
|
||||
|
||||
export const CreateModal = forwardRef<CreateModalRef>(function CreateModal(props, ref) {
|
||||
const [path, setPath] = useState<string[]>([]);
|
||||
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() {
|
||||
// @ts-ignore
|
||||
ref?.current?.close();
|
||||
context.closeModal(id);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal2 ref={ref}>
|
||||
<Steps path={path} lastBack={close}>
|
||||
<Step id="select">
|
||||
<ModalTitle path={["Create New"]} onClose={close} />
|
||||
<StepSelect />
|
||||
</Step>
|
||||
<Step id="entity" path={["action"]}>
|
||||
<ModalTitle path={["Create New", "Entity"]} onClose={close} />
|
||||
<StepEntity />
|
||||
</Step>
|
||||
<Step id="entity-fields" path={["action", "entity"]}>
|
||||
<ModalTitle path={["Create New", "Entity", "Fields"]} onClose={close} />
|
||||
<StepEntityFields />
|
||||
</Step>
|
||||
<Step id="relation" path={["action"]}>
|
||||
<ModalTitle path={["Create New", "Relation"]} onClose={close} />
|
||||
<StepRelation />
|
||||
</Step>
|
||||
<Step id="create" path={["action"]}>
|
||||
<ModalTitle path={["Create New", "Creation"]} onClose={close} />
|
||||
<StepCreate />
|
||||
</Step>
|
||||
<Steps path={path} lastBack={close} initialPath={initialPath} initialState={initialState}>
|
||||
<Step id="select">
|
||||
<ModalTitle path={["Create New"]} onClose={close} />
|
||||
<StepSelect />
|
||||
</Step>
|
||||
<Step id="entity" path={["action"]}>
|
||||
<ModalTitle path={["Create New", "Entity"]} onClose={close} />
|
||||
<StepEntity />
|
||||
</Step>
|
||||
<Step id="entity-fields" path={["action", "entity"]}>
|
||||
<ModalTitle path={["Create New", "Entity", "Fields"]} onClose={close} />
|
||||
<StepEntityFields />
|
||||
</Step>
|
||||
<Step id="relation" path={["action"]}>
|
||||
<ModalTitle path={["Create New", "Relation"]} onClose={close} />
|
||||
<StepRelation />
|
||||
</Step>
|
||||
<Step id="create" path={["action"]}>
|
||||
<ModalTitle path={["Create New", "Creation"]} onClose={close} />
|
||||
<StepCreate />
|
||||
</Step>
|
||||
|
||||
{/* Templates */}
|
||||
{Templates.map(([Component, meta]) => (
|
||||
<Step key={meta.id} id={meta.id} path={["action"]}>
|
||||
<ModalTitle path={["Create New", "Template", meta.title]} onClose={close} />
|
||||
<Component />
|
||||
</Step>
|
||||
))}
|
||||
</Steps>
|
||||
</Modal2>
|
||||
{/* Templates */}
|
||||
{Templates.map(([Component, meta]) => (
|
||||
<Step key={meta.id} id={meta.id} path={["action"]}>
|
||||
<ModalTitle path={["Create New", "Template", meta.title]} onClose={close} />
|
||||
<Component />
|
||||
</Step>
|
||||
))}
|
||||
</Steps>
|
||||
);
|
||||
});
|
||||
}
|
||||
CreateModal.defaultTitle = undefined;
|
||||
CreateModal.modalProps = {
|
||||
withCloseButton: false,
|
||||
size: "xl",
|
||||
padding: 0,
|
||||
classNames: {
|
||||
root: "bknd-admin"
|
||||
}
|
||||
} satisfies Partial<ModalProps>;
|
||||
|
||||
export { ModalBody, ModalFooter, ModalTitle, useStepContext, relationsSchema };
|
||||
|
||||
@@ -8,8 +8,8 @@ import {
|
||||
} from "@tabler/icons-react";
|
||||
import { ucFirst } from "core/utils";
|
||||
import { useEffect, useState } from "react";
|
||||
import { TbCirclesRelation, TbSettings } from "react-icons/tb";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { useBknd } from "ui/client/bknd";
|
||||
import { useBkndData } from "ui/client/schema/data/use-bknd-data";
|
||||
import { IconButton, type IconType } from "ui/components/buttons/IconButton";
|
||||
import { JsonViewer } from "ui/components/code/JsonViewer";
|
||||
@@ -26,6 +26,7 @@ export function StepCreate() {
|
||||
const [states, setStates] = useState<(boolean | string)[]>([]);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const $data = useBkndData();
|
||||
const b = useBknd();
|
||||
|
||||
const items: ActionItem[] = [];
|
||||
if (state.entities?.create) {
|
||||
@@ -74,6 +75,10 @@ export function StepCreate() {
|
||||
try {
|
||||
const res = await item.run();
|
||||
setStates((prev) => [...prev, res]);
|
||||
if (res !== true) {
|
||||
// make sure to break out
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
setStates((prev) => [...prev, (e as any).message]);
|
||||
}
|
||||
@@ -90,7 +95,8 @@ export function StepCreate() {
|
||||
states.every((s) => s === true)
|
||||
);
|
||||
if (items.length === states.length && states.every((s) => s === true)) {
|
||||
close();
|
||||
b.actions.reload().then(close);
|
||||
//close();
|
||||
} else {
|
||||
setSubmitting(false);
|
||||
}
|
||||
@@ -144,12 +150,14 @@ const SummaryItem: React.FC<SummaryItemProps> = ({
|
||||
}) => {
|
||||
const [expanded, handlers] = useDisclosure(initialExpanded);
|
||||
const error = typeof state !== "undefined" && state !== true;
|
||||
const done = state === true;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={twMerge(
|
||||
"flex flex-col border border-muted rounded bg-background mb-2",
|
||||
error && "bg-red-500/20"
|
||||
error && "bg-red-500/20",
|
||||
done && "bg-green-500/20"
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-row gap-4 px-2 py-2 items-center">
|
||||
|
||||
@@ -9,12 +9,15 @@ import {
|
||||
registerCustomTypeboxKinds
|
||||
} from "core/utils";
|
||||
import { ManyToOneRelation, type RelationType, RelationTypes } from "data";
|
||||
import { type ReactNode, useEffect } from "react";
|
||||
import { type ReactNode, startTransition, useEffect } from "react";
|
||||
import { type Control, type FieldValues, type UseFormRegister, useForm } from "react-hook-form";
|
||||
import { TbRefresh } from "react-icons/tb";
|
||||
import { useBknd } from "ui/client/bknd";
|
||||
import { Button } from "ui/components/buttons/Button";
|
||||
import { MantineNumberInput } from "ui/components/form/hook-form-mantine/MantineNumberInput";
|
||||
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";
|
||||
|
||||
// @todo: check if this could become an issue
|
||||
@@ -63,7 +66,7 @@ type ComponentCtx<T extends FieldValues = FieldValues> = {
|
||||
export function StepRelation() {
|
||||
const { config } = useBknd();
|
||||
const entities = config.data.entities;
|
||||
const { nextStep, stepBack, state, setState } = useStepContext<TCreateModalSchema>();
|
||||
const { nextStep, stepBack, state, path, setState } = useStepContext<TCreateModalSchema>();
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
@@ -93,6 +96,22 @@ export function StepRelation() {
|
||||
}
|
||||
}
|
||||
|
||||
const flip = useEvent(() => {
|
||||
const { source, target } = data;
|
||||
if (source && target) {
|
||||
setValue("source", target);
|
||||
setValue("target", source);
|
||||
} else {
|
||||
if (source) {
|
||||
setValue("target", source);
|
||||
setValue("source", null as any);
|
||||
} else {
|
||||
setValue("source", target);
|
||||
setValue("target", null as any);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<form onSubmit={handleSubmit(handleNext)}>
|
||||
@@ -109,14 +128,23 @@ export function StepRelation() {
|
||||
disabled: data.target === name
|
||||
}))}
|
||||
/>
|
||||
<MantineSelect
|
||||
control={control}
|
||||
name="type"
|
||||
onChange={() => setValue("config", {})}
|
||||
label="Relation Type"
|
||||
data={Relations.map((r) => ({ value: r.type, label: r.label }))}
|
||||
allowDeselect={false}
|
||||
/>
|
||||
<div className="flex flex-col gap-1">
|
||||
<MantineSelect
|
||||
control={control}
|
||||
name="type"
|
||||
onChange={() => setValue("config", {})}
|
||||
label="Relation Type"
|
||||
data={Relations.map((r) => ({ value: r.type, label: r.label }))}
|
||||
allowDeselect={false}
|
||||
/>
|
||||
{data.type && (
|
||||
<div className="flex justify-center mt-1">
|
||||
<Button size="small" IconLeft={TbRefresh} onClick={flip}>
|
||||
Flip entities
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<MantineSelect
|
||||
control={control}
|
||||
allowDeselect={false}
|
||||
@@ -146,7 +174,7 @@ export function StepRelation() {
|
||||
onClick: handleNext
|
||||
}}
|
||||
prev={{ onClick: stepBack }}
|
||||
debug={{ state, data }}
|
||||
debug={{ state, path, data }}
|
||||
/>
|
||||
</form>
|
||||
</>
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
import Templates from "./templates/register";
|
||||
|
||||
export function StepSelect() {
|
||||
const { nextStep, stepBack, state, setState } = useStepContext<TCreateModalSchema>();
|
||||
const { nextStep, stepBack, state, path, setState } = useStepContext<TCreateModalSchema>();
|
||||
const selected = state.action ?? null;
|
||||
|
||||
function handleSelect(action: TSchemaAction) {
|
||||
@@ -74,6 +74,7 @@ export function StepSelect() {
|
||||
}}
|
||||
prev={{ onClick: stepBack }}
|
||||
prevLabel="Cancel"
|
||||
debug={{ state, path }}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
transformObject
|
||||
} from "core/utils";
|
||||
import type { MediaFieldConfig } from "media/MediaField";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useBknd } from "ui/client/bknd";
|
||||
import { MantineNumberInput } from "ui/components/form/hook-form-mantine/MantineNumberInput";
|
||||
@@ -31,18 +32,19 @@ const schema = Type.Object({
|
||||
type TCreateModalMediaSchema = Static<typeof schema>;
|
||||
|
||||
export function TemplateMediaComponent() {
|
||||
const { stepBack, setState, state, nextStep } = useStepContext<TCreateModalSchema>();
|
||||
const { stepBack, setState, state, path, nextStep } = useStepContext<TCreateModalSchema>();
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { isValid },
|
||||
setValue,
|
||||
formState: { isValid, errors },
|
||||
watch,
|
||||
control
|
||||
} = useForm({
|
||||
mode: "onChange",
|
||||
resolver: typeboxResolver(schema),
|
||||
defaultValues: Default(schema, {}) as TCreateModalMediaSchema
|
||||
defaultValues: Default(schema, state.initial ?? {}) as TCreateModalMediaSchema
|
||||
});
|
||||
const [forbidden, setForbidden] = useState<boolean>(false);
|
||||
|
||||
const { config } = useBknd();
|
||||
const media_enabled = config.media.enabled ?? false;
|
||||
@@ -51,13 +53,16 @@ export function TemplateMediaComponent() {
|
||||
name !== media_entity ? entity : undefined
|
||||
);
|
||||
const data = watch();
|
||||
const forbidden_field_names = Object.keys(config.data.entities?.[data.entity]?.fields ?? {});
|
||||
|
||||
useEffect(() => {
|
||||
setForbidden(forbidden_field_names.includes(data.name));
|
||||
}, [forbidden_field_names, data.name]);
|
||||
|
||||
async function handleCreate() {
|
||||
if (isValid) {
|
||||
console.log("data", data);
|
||||
if (isValid && !forbidden) {
|
||||
const { field, relation } = convert(media_entity, data);
|
||||
|
||||
console.log("state", { field, relation });
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
fields: { create: [field] },
|
||||
@@ -120,6 +125,13 @@ export function TemplateMediaComponent() {
|
||||
data.entity ? data.entity : "the entity"
|
||||
}.`}
|
||||
{...register("name")}
|
||||
error={
|
||||
errors.name?.message
|
||||
? errors.name?.message
|
||||
: forbidden
|
||||
? `Property "${data.name}" already exists on entity ${data.entity}`
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{/*<p>step template media</p>
|
||||
@@ -129,12 +141,12 @@ export function TemplateMediaComponent() {
|
||||
<ModalFooter
|
||||
next={{
|
||||
type: "submit",
|
||||
disabled: !isValid || !media_enabled
|
||||
disabled: !isValid || !media_enabled || forbidden
|
||||
}}
|
||||
prev={{
|
||||
onClick: stepBack
|
||||
}}
|
||||
debug={{ state, data }}
|
||||
debug={{ state, path, data }}
|
||||
/>
|
||||
</form>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user