mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-15 20:17:22 +00:00
173 lines
6.0 KiB
TypeScript
173 lines
6.0 KiB
TypeScript
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 { useLocation } from "wouter";
|
|
import { useBknd } from "../../../client/BkndProvider";
|
|
import { Button } from "../../../components/buttons/Button";
|
|
import * as Formy from "../../../components/form/Formy";
|
|
import {
|
|
JsonSchemaForm,
|
|
type JsonSchemaFormRef
|
|
} from "../../../components/form/json-schema/JsonSchemaForm";
|
|
import { Dropdown } from "../../../components/overlay/Dropdown";
|
|
import { Modal } from "../../../components/overlay/Modal";
|
|
|
|
export type SettingsNewModalProps = {
|
|
schema: TObject;
|
|
uiSchema?: object;
|
|
anyOfValues?: Record<string, { label: string; icon?: any }>;
|
|
path: string[];
|
|
prefixPath: string;
|
|
generateKey?: string | ((formData: any) => string);
|
|
};
|
|
|
|
export const SettingNewModal = ({
|
|
schema,
|
|
uiSchema = {},
|
|
anyOfValues,
|
|
path,
|
|
prefixPath,
|
|
generateKey
|
|
}: SettingsNewModalProps) => {
|
|
const [location, navigate] = useLocation();
|
|
const [formSchema, setFormSchema] = useState(schema);
|
|
const [submitting, setSubmitting] = useState(false);
|
|
const { actions } = useBknd();
|
|
const [opened, { open, close }] = useDisclosure(false);
|
|
const isGeneratedKey = generateKey !== undefined;
|
|
const isStaticGeneratedKey = typeof generateKey === "string";
|
|
const [newKey, setNewKey] = useState(isStaticGeneratedKey ? generateKey : "");
|
|
const focusTrap = useFocusTrap(!isGeneratedKey);
|
|
const formRef = useRef<JsonSchemaFormRef>(null);
|
|
const isAnyOf = "anyOf" in schema;
|
|
|
|
function handleFormChange(data) {
|
|
if (generateKey && typeof generateKey === "function") {
|
|
handleKeyNameChange({
|
|
target: {
|
|
value: generateKey(data)
|
|
}
|
|
});
|
|
}
|
|
console.log("form change", data);
|
|
}
|
|
|
|
function handleKeyNameChange(e) {
|
|
const v = String(e.target.value);
|
|
if (v.length > 0 && !/^[a-zA-Z_][a-zA-Z0-9_ ]*$/.test(v)) {
|
|
console.log("no match", v);
|
|
return;
|
|
}
|
|
setNewKey(v.toLowerCase().replace(/ /g, "_").replace(/__+/g, "_"));
|
|
}
|
|
|
|
async function handleSave() {
|
|
if (formRef.current?.validateForm()) {
|
|
setSubmitting(true);
|
|
const data = formRef.current?.formData();
|
|
const [module, ...restOfPath] = path;
|
|
const addPath = [...restOfPath, newKey].join(".");
|
|
if (await actions.add(module as any, addPath, data)) {
|
|
setTimeout(() => {
|
|
navigate(prefixPath + newKey, {
|
|
replace: true
|
|
});
|
|
}, 500);
|
|
} else {
|
|
setSubmitting(false);
|
|
}
|
|
}
|
|
console.log("valid?", formRef.current?.validateForm());
|
|
console.log("data", newKey, formRef.current?.formData());
|
|
}
|
|
|
|
const anyOfItems = isAnyOf
|
|
? (schema.anyOf as any[])!.map((item) => {
|
|
const key = item.title;
|
|
const label = anyOfValues?.[key]?.label || key;
|
|
const icon = anyOfValues?.[key]?.icon;
|
|
|
|
return {
|
|
label,
|
|
icon,
|
|
onClick: () => {
|
|
setFormSchema(item);
|
|
open();
|
|
}
|
|
};
|
|
})
|
|
: [];
|
|
|
|
return (
|
|
<>
|
|
<div className="flex flex-row">
|
|
{isAnyOf ? (
|
|
<Dropdown position="top-start" items={anyOfItems} itemsClassName="gap-3">
|
|
<Button>Add new</Button>
|
|
</Dropdown>
|
|
) : (
|
|
<Button onClick={open}>Add new</Button>
|
|
)}
|
|
</div>
|
|
|
|
<Modal
|
|
open={opened}
|
|
allowBackdropClose={false}
|
|
onClose={close}
|
|
className="min-w-96 w-6/12 mt-[30px] mb-[20px]"
|
|
stickToTop
|
|
>
|
|
<div
|
|
className="border border-muted rounded-lg flex flex-col overflow-y-scroll font-normal"
|
|
style={{ maxHeight: "calc(100dvh - 100px)" }}
|
|
>
|
|
<div className="bg-muted py-2 px-4 text-primary/70 font-mono flex flex-row gap-2 items-center">
|
|
{[...path, newKey].join(".")}
|
|
</div>
|
|
<div className="flex flex-row gap-10 sticky top-0 bg-background z-10 p-4 border-b border-muted">
|
|
<div className="flex flex-row flex-grow items-center relative">
|
|
<Formy.Input
|
|
ref={focusTrap}
|
|
className="w-full"
|
|
placeholder="New unique key..."
|
|
onChange={handleKeyNameChange}
|
|
value={newKey}
|
|
disabled={isGeneratedKey}
|
|
/>
|
|
{isGeneratedKey && (
|
|
<div className="absolute h-full flex items-center z-10 right-3.5 top-0 bottom-0 font-mono font-sm opacity-50">
|
|
generated
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div className="flex flex-row gap-2">
|
|
<Button onClick={close} disabled={submitting}>
|
|
Cancel
|
|
</Button>
|
|
<Button
|
|
variant="primary"
|
|
onClick={handleSave}
|
|
disabled={submitting || newKey.length === 0}
|
|
>
|
|
Create
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
<div className="flex flex-col p-4">
|
|
<JsonSchemaForm
|
|
ref={formRef}
|
|
/* readonly={!editing} */
|
|
schema={omit(formSchema, ["title"])}
|
|
uiSchema={uiSchema}
|
|
onChange={handleFormChange}
|
|
className="legacy hide-required-mark fieldset-alternative mute-root"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</Modal>
|
|
</>
|
|
);
|
|
};
|