mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 12:37:20 +00:00
196 lines
5.8 KiB
TypeScript
196 lines
5.8 KiB
TypeScript
import { useDisclosure } from "@mantine/hooks";
|
|
import {
|
|
IconAlignJustified,
|
|
IconAugmentedReality,
|
|
IconBox,
|
|
IconCirclesRelation,
|
|
IconInfoCircle
|
|
} from "@tabler/icons-react";
|
|
import { ucFirst } from "core/utils";
|
|
import { useEffect, useState } from "react";
|
|
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";
|
|
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";
|
|
|
|
type ActionItem = SummaryItemProps & {
|
|
run: () => Promise<boolean>;
|
|
};
|
|
|
|
export function StepCreate() {
|
|
const { stepBack, state, close } = useStepContext<TCreateModalSchema>();
|
|
const [states, setStates] = useState<(boolean | string)[]>([]);
|
|
const [submitting, setSubmitting] = useState(false);
|
|
const $data = useBkndData();
|
|
const b = useBknd();
|
|
|
|
const items: ActionItem[] = [];
|
|
if (state.entities?.create) {
|
|
items.push(
|
|
...state.entities.create.map((entity) => ({
|
|
action: "add",
|
|
Icon: IconBox,
|
|
type: "Entity",
|
|
name: entity.name,
|
|
json: entity,
|
|
run: async () => await $data.actions.entity.add(entity.name, entity)
|
|
}))
|
|
);
|
|
}
|
|
if (state.fields?.create) {
|
|
items.push(
|
|
...state.fields.create.map((field) => ({
|
|
action: "add",
|
|
Icon: IconAlignJustified,
|
|
type: "Field",
|
|
name: field.name,
|
|
json: field,
|
|
run: async () =>
|
|
await $data.actions.entity
|
|
.patch(field.entity)
|
|
.fields.add(field.name, field.field as any)
|
|
}))
|
|
);
|
|
}
|
|
if (state.relations?.create) {
|
|
items.push(
|
|
...state.relations.create.map((rel) => ({
|
|
action: "add",
|
|
Icon: IconCirclesRelation,
|
|
type: "Relation",
|
|
name: `${rel.source} -> ${rel.target} (${rel.type})`,
|
|
json: rel,
|
|
run: async () => await $data.actions.relations.add(rel)
|
|
}))
|
|
);
|
|
}
|
|
|
|
async function handleCreate() {
|
|
setSubmitting(true);
|
|
for (const item of items) {
|
|
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]);
|
|
}
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
console.log(
|
|
"states",
|
|
states,
|
|
items,
|
|
states.length,
|
|
items.length,
|
|
states.every((s) => s === true)
|
|
);
|
|
if (items.length === states.length && states.every((s) => s === true)) {
|
|
b.actions.reload().then(close);
|
|
//close();
|
|
} else {
|
|
setSubmitting(false);
|
|
}
|
|
}, [states]);
|
|
|
|
return (
|
|
<>
|
|
<ModalBody>
|
|
<div>This is what will be created. Please confirm by clicking "Next".</div>
|
|
<div className="flex flex-col gap-1">
|
|
{items.map((item, i) => (
|
|
<SummaryItem key={i} {...item} state={states[i]} />
|
|
))}
|
|
</div>
|
|
{/*<div>{submitting ? "submitting" : "idle"}</div>
|
|
<div>
|
|
{states.length}/{items.length}
|
|
</div>*/}
|
|
</ModalBody>
|
|
<ModalFooter
|
|
nextLabel="Create"
|
|
next={{
|
|
onClick: handleCreate,
|
|
disabled: submitting
|
|
}}
|
|
prev={{ onClick: stepBack, disabled: submitting }}
|
|
debug={{ state }}
|
|
/>
|
|
</>
|
|
);
|
|
}
|
|
|
|
type SummaryItemProps = {
|
|
Icon: IconType;
|
|
action: "add" | string;
|
|
type: string;
|
|
name: string;
|
|
json?: object;
|
|
state?: boolean | string;
|
|
initialExpanded?: boolean;
|
|
};
|
|
|
|
const SummaryItem: React.FC<SummaryItemProps> = ({
|
|
Icon,
|
|
type,
|
|
name,
|
|
json,
|
|
state,
|
|
action,
|
|
initialExpanded = false
|
|
}) => {
|
|
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",
|
|
done && "bg-green-500/20"
|
|
)}
|
|
>
|
|
<div className="flex flex-row gap-4 px-2 py-2 items-center">
|
|
<div className="flex flex-row items-center p-1 bg-primary/5 rounded">
|
|
<Icon className="w-6 h-6" />
|
|
</div>
|
|
<div className="flex flex-row flex-grow gap-5">
|
|
<Desc type="action" name={action} />
|
|
<Desc type="type" name={type} />
|
|
<Desc type="name" name={name} />
|
|
</div>
|
|
{json && (
|
|
<IconButton
|
|
Icon={IconInfoCircle}
|
|
variant={expanded ? "default" : "ghost"}
|
|
onClick={handlers.toggle}
|
|
/>
|
|
)}
|
|
</div>
|
|
{json && expanded && (
|
|
<div className="flex flex-col border-t border-t-muted">
|
|
<JsonViewer json={json} expand={8} className="text-sm" />
|
|
</div>
|
|
)}
|
|
{error && typeof state === "string" && <div className="text-sm text-red-500">{state}</div>}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const Desc = ({ type, name }) => (
|
|
<div className="flex flex-row text-sm font-mono gap-2">
|
|
<div className="opacity-50">{ucFirst(type)}</div>
|
|
<div className="font-semibold">{name}</div>
|
|
</div>
|
|
);
|