mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-17 12:56:05 +00:00
public commit
This commit is contained in:
124
app/src/ui/routes/test/tests/appshell-accordions-test.tsx
Normal file
124
app/src/ui/routes/test/tests/appshell-accordions-test.tsx
Normal file
@@ -0,0 +1,124 @@
|
||||
import {
|
||||
IconAlignJustified,
|
||||
IconBolt,
|
||||
IconChevronDown,
|
||||
IconChevronUp,
|
||||
IconCirclesRelation,
|
||||
IconSettings,
|
||||
IconUser
|
||||
} from "@tabler/icons-react";
|
||||
import { useState } from "react";
|
||||
import { TbDots } from "react-icons/tb";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { Button } from "ui/components/buttons/Button";
|
||||
import { IconButton } from "ui/components/buttons/IconButton";
|
||||
import { Dropdown } from "ui/components/overlay/Dropdown";
|
||||
import * as AppShell from "ui/layouts/AppShell/AppShell";
|
||||
import { Breadcrumbs2 } from "ui/layouts/AppShell/Breadcrumbs2";
|
||||
import { routes } from "ui/lib/routes";
|
||||
|
||||
const Item = ({
|
||||
title,
|
||||
open,
|
||||
toggle,
|
||||
ActiveIcon = IconChevronUp,
|
||||
children,
|
||||
renderHeaderRight
|
||||
}: {
|
||||
title: string;
|
||||
open: boolean;
|
||||
toggle: () => void;
|
||||
ActiveIcon?: any;
|
||||
children?: React.ReactNode;
|
||||
renderHeaderRight?: (props: { open: boolean }) => React.ReactNode;
|
||||
}) => (
|
||||
<div
|
||||
style={{ minHeight: 49 }}
|
||||
className={twMerge(
|
||||
"flex flex-col flex-animate overflow-hidden",
|
||||
open
|
||||
? "flex-open border-b border-b-muted"
|
||||
: "flex-initial cursor-pointer hover:bg-primary/5"
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={twMerge(
|
||||
"flex flex-row border-muted border-b h-14 py-4 pr-4 pl-2 items-center gap-2"
|
||||
)}
|
||||
onClick={toggle}
|
||||
>
|
||||
<IconButton Icon={open ? ActiveIcon : IconChevronDown} disabled={open} />
|
||||
<h2 className="text-lg dark:font-bold font-semibold select-text">{title}</h2>
|
||||
<div className="flex flex-grow" />
|
||||
{renderHeaderRight?.({ open })}
|
||||
</div>
|
||||
<div
|
||||
className={twMerge(
|
||||
"overflow-y-scroll transition-all",
|
||||
open ? " flex-grow" : "h-0 opacity-0"
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-col gap-5 p-4 ">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
export default function AppShellAccordionsTest() {
|
||||
const [value, setValue] = useState("1");
|
||||
|
||||
function toggle(value) {
|
||||
return () => setValue(value);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
<AppShell.SectionHeader
|
||||
right={
|
||||
<>
|
||||
<Dropdown
|
||||
items={[
|
||||
{
|
||||
label: "Settings"
|
||||
}
|
||||
]}
|
||||
position="bottom-end"
|
||||
>
|
||||
<IconButton Icon={TbDots} />
|
||||
</Dropdown>
|
||||
</>
|
||||
}
|
||||
className="pl-3"
|
||||
>
|
||||
<Breadcrumbs2
|
||||
path={[{ label: "Schema", href: "/" }, { label: "Quizzes" }]}
|
||||
backTo="/"
|
||||
/>
|
||||
</AppShell.SectionHeader>
|
||||
<Item
|
||||
title="Fields"
|
||||
open={value === "1"}
|
||||
toggle={toggle("1")}
|
||||
ActiveIcon={IconAlignJustified}
|
||||
renderHeaderRight={({ open }) =>
|
||||
open ? (
|
||||
<Button variant="primary" disabled={!open}>
|
||||
Update
|
||||
</Button>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
<Item
|
||||
title="Settings"
|
||||
open={value === "2"}
|
||||
toggle={toggle("2")}
|
||||
ActiveIcon={IconSettings}
|
||||
/>
|
||||
<Item
|
||||
title="Relations"
|
||||
open={value === "3"}
|
||||
toggle={toggle("3")}
|
||||
ActiveIcon={IconCirclesRelation}
|
||||
/>
|
||||
<Item title="Indices" open={value === "4"} toggle={toggle("4")} ActiveIcon={IconBolt} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
17
app/src/ui/routes/test/tests/dropdown-test.tsx
Normal file
17
app/src/ui/routes/test/tests/dropdown-test.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Dropdown } from "../../../components/overlay/Dropdown";
|
||||
|
||||
export default function DropdownTest() {
|
||||
return (
|
||||
<div className="flex flex-col w-full h-full justify-center items-center">
|
||||
<Dropdown
|
||||
items={[
|
||||
{ label: "Item 1", value: "item1" },
|
||||
{ label: "Item 2", value: "item2" },
|
||||
{ label: "Item 3", value: "item3" },
|
||||
]}
|
||||
>
|
||||
<button>Dropdown</button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
296
app/src/ui/routes/test/tests/entity-fields-form.tsx
Normal file
296
app/src/ui/routes/test/tests/entity-fields-form.tsx
Normal file
@@ -0,0 +1,296 @@
|
||||
import { typeboxResolver } from "@hookform/resolvers/typebox";
|
||||
import { Select, Switch, Tabs, TextInput, Textarea, Tooltip } from "@mantine/core";
|
||||
import { useDisclosure } from "@mantine/hooks";
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import { StringEnum, StringIdentifier, transformObject } from "core/utils";
|
||||
import { FieldClassMap } from "data";
|
||||
import { omit } from "lodash-es";
|
||||
import {
|
||||
type FieldArrayWithId,
|
||||
type FieldValues,
|
||||
type UseControllerProps,
|
||||
type UseFormReturn,
|
||||
useController,
|
||||
useFieldArray,
|
||||
useForm
|
||||
} from "react-hook-form";
|
||||
import { TbChevronDown, TbChevronUp, TbGripVertical, TbTrash } from "react-icons/tb";
|
||||
import { Button } from "../../../components/buttons/Button";
|
||||
import { IconButton } from "../../../components/buttons/IconButton";
|
||||
import { MantineSelect } from "../../../components/form/hook-form-mantine/MantineSelect";
|
||||
|
||||
const fieldSchemas = transformObject(omit(FieldClassMap, ["primary"]), (value) => value.schema);
|
||||
const fieldSchema = Type.Union(
|
||||
Object.entries(fieldSchemas).map(([type, schema]) =>
|
||||
Type.Object(
|
||||
{
|
||||
type: Type.Const(type),
|
||||
name: StringIdentifier,
|
||||
config: Type.Optional(schema)
|
||||
},
|
||||
{
|
||||
additionalProperties: false
|
||||
}
|
||||
)
|
||||
)
|
||||
);
|
||||
const schema = Type.Object({
|
||||
fields: Type.Array(fieldSchema)
|
||||
});
|
||||
|
||||
const fieldSchema2 = Type.Object({
|
||||
type: StringEnum(Object.keys(fieldSchemas)),
|
||||
name: StringIdentifier
|
||||
});
|
||||
|
||||
function specificFieldSchema(type: keyof typeof fieldSchemas) {
|
||||
return Type.Omit(fieldSchemas[type], [
|
||||
"label",
|
||||
"description",
|
||||
"required",
|
||||
"fillable",
|
||||
"hidden",
|
||||
"virtual"
|
||||
]);
|
||||
}
|
||||
|
||||
export default function EntityFieldsForm() {
|
||||
const {
|
||||
control,
|
||||
formState: { isValid, errors },
|
||||
getValues,
|
||||
handleSubmit,
|
||||
watch,
|
||||
register,
|
||||
setValue
|
||||
} = useForm({
|
||||
mode: "onTouched",
|
||||
resolver: typeboxResolver(schema),
|
||||
defaultValues: {
|
||||
fields: [{ type: "text", name: "", config: {} }],
|
||||
sort: { by: "-1", dir: "asc" }
|
||||
}
|
||||
});
|
||||
const defaultType = Object.keys(fieldSchemas)[0];
|
||||
const { fields, append, prepend, remove, swap, move, insert, update } = useFieldArray({
|
||||
control, // control props comes from useForm (optional: if you are using FormProvider)
|
||||
name: "fields" // unique name for your Field Array
|
||||
});
|
||||
|
||||
function handleAppend() {
|
||||
append({ type: "text", name: "", config: {} });
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-1 p-8">
|
||||
{/*{fields.map((field, index) => (
|
||||
<EntityField
|
||||
key={field.id}
|
||||
field={field}
|
||||
index={index}
|
||||
form={{ watch, register, setValue, getValues, control }}
|
||||
defaultType={defaultType}
|
||||
remove={remove}
|
||||
/>
|
||||
))}*/}
|
||||
{fields.map((field, index) => (
|
||||
<EntityFieldForm key={field.id} value={field} index={index} update={update} />
|
||||
))}
|
||||
|
||||
<Button className="justify-center" onClick={handleAppend}>
|
||||
Add Field
|
||||
</Button>
|
||||
|
||||
<div>
|
||||
<pre>{JSON.stringify(watch(), null, 2)}</pre>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function EntityFieldForm({ update, index, value }) {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
control,
|
||||
formState: { errors }
|
||||
} = useForm({
|
||||
mode: "onBlur",
|
||||
resolver: typeboxResolver(
|
||||
Type.Object({
|
||||
type: StringEnum(Object.keys(fieldSchemas)),
|
||||
name: Type.String({ minLength: 1, maxLength: 3 })
|
||||
})
|
||||
),
|
||||
defaultValues: value
|
||||
});
|
||||
|
||||
function handleUpdate({ id, ...data }) {
|
||||
console.log("data", data);
|
||||
update(index, data);
|
||||
}
|
||||
|
||||
return (
|
||||
<form>
|
||||
<MantineSelect
|
||||
control={control}
|
||||
name="type"
|
||||
data={[...Object.keys(fieldSchemas), "test"]}
|
||||
/>
|
||||
<TextInput
|
||||
label="Name"
|
||||
placeholder="name"
|
||||
classNames={{ root: "w-full" }}
|
||||
{...register("name")}
|
||||
error={errors.name?.message as any}
|
||||
/>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
function EntityFieldController({
|
||||
name,
|
||||
control,
|
||||
defaultValue,
|
||||
rules,
|
||||
shouldUnregister
|
||||
}: UseControllerProps & {
|
||||
index: number;
|
||||
}) {
|
||||
const {
|
||||
field: { value, onChange: fieldOnChange, ...field },
|
||||
fieldState
|
||||
} = useController({
|
||||
name,
|
||||
control,
|
||||
defaultValue,
|
||||
rules,
|
||||
shouldUnregister
|
||||
});
|
||||
|
||||
return <div>field</div>;
|
||||
}
|
||||
|
||||
function EntityField({
|
||||
field,
|
||||
index,
|
||||
form: { watch, register, setValue, getValues, control },
|
||||
remove,
|
||||
defaultType
|
||||
}: {
|
||||
field: FieldArrayWithId;
|
||||
index: number;
|
||||
form: Pick<UseFormReturn<any>, "watch" | "register" | "setValue" | "getValues" | "control">;
|
||||
remove: (index: number) => void;
|
||||
defaultType: string;
|
||||
}) {
|
||||
const [opened, handlers] = useDisclosure(false);
|
||||
const prefix = `fields.${index}` as const;
|
||||
const name = watch(`${prefix}.name`);
|
||||
const enabled = name?.length > 0;
|
||||
const type = watch(`${prefix}.type`);
|
||||
//const config = watch(`${prefix}.config`);
|
||||
const selectFieldRegister = register(`${prefix}.type`);
|
||||
//console.log("type", type, specificFieldSchema(type as any));
|
||||
|
||||
function handleDelete(index: number) {
|
||||
return () => {
|
||||
if (name.length === 0) {
|
||||
remove(index);
|
||||
return;
|
||||
}
|
||||
window.confirm(`Sure to delete "${name}"?`) && remove(index);
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={field.id} className="flex flex-col border border-muted rounded">
|
||||
<div className="flex flex-row gap-2 px-2 pt-1 pb-2">
|
||||
<div className="flex items-center">
|
||||
<IconButton Icon={TbGripVertical} className="mt-1" />
|
||||
</div>
|
||||
<div className="flex flex-row flex-grow gap-1">
|
||||
<div>
|
||||
<Select
|
||||
label="Type"
|
||||
data={[...Object.keys(fieldSchemas), "test"]}
|
||||
defaultValue={defaultType}
|
||||
onBlur={selectFieldRegister.onBlur}
|
||||
onChange={(value) => {
|
||||
setValue(`${prefix}.type`, value as any);
|
||||
setValue(`${prefix}.config`, {} as any);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<TextInput
|
||||
label="Name"
|
||||
placeholder="name"
|
||||
classNames={{ root: "w-full" }}
|
||||
{...register(`fields.${index}.name`)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-end ">
|
||||
<div className="flex flex-row gap-1">
|
||||
<Tooltip label="Specify a property name to see options." disabled={enabled}>
|
||||
<Button
|
||||
IconRight={opened ? TbChevronUp : TbChevronDown}
|
||||
onClick={handlers.toggle}
|
||||
variant={opened ? "default" : "ghost"}
|
||||
disabled={!enabled}
|
||||
>
|
||||
Options
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<IconButton Icon={TbTrash} onClick={handleDelete(index)} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/*{enabled && opened && (
|
||||
<div className="flex flex-col border-t border-t-muted px-3 py-2">
|
||||
<Tabs defaultValue="general">
|
||||
<Tabs.List>
|
||||
<Tabs.Tab value="general">General</Tabs.Tab>
|
||||
<Tabs.Tab value="specific">Specific</Tabs.Tab>
|
||||
<Tabs.Tab value="visibility">Visiblity</Tabs.Tab>
|
||||
</Tabs.List>
|
||||
<Tabs.Panel value="general">
|
||||
<div className="flex flex-col gap-2 pt-3 pb-1" key={`${prefix}_${type}`}>
|
||||
<Switch
|
||||
label="Required"
|
||||
{...register(`${prefix}.config.required` as any)}
|
||||
/>
|
||||
<TextInput
|
||||
label="Label"
|
||||
placeholder="Label"
|
||||
{...register(`${prefix}.config.label` as any)}
|
||||
/>
|
||||
<Textarea
|
||||
label="Description"
|
||||
placeholder="Description"
|
||||
{...register(`${prefix}.config.description` as any)}
|
||||
/>
|
||||
<Switch label="Virtual" {...register(`${prefix}.config.virtual` as any)} />
|
||||
</div>
|
||||
</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)}
|
||||
uiSchema={dataFieldsUiSchema.config}
|
||||
className="legacy hide-required-mark fieldset-alternative mute-root"
|
||||
onChange={(value) => {
|
||||
setValue(`${prefix}.config`, {
|
||||
...getValues([`fields.${index}.config`])[0],
|
||||
...value
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Tabs.Panel>
|
||||
</Tabs>
|
||||
</div>
|
||||
)}*/}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
25
app/src/ui/routes/test/tests/flow-create-schema-test.tsx
Normal file
25
app/src/ui/routes/test/tests/flow-create-schema-test.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
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/JsonSchemaForm";
|
||||
import { Scrollable } from "../../../layouts/AppShell/AppShell";
|
||||
|
||||
export default function FlowCreateSchemaTest() {
|
||||
//const schema = flowsConfigSchema;
|
||||
const schema = new AppFlows().getSchema();
|
||||
const [data, setData] = useState(parse(schema, {}));
|
||||
|
||||
return (
|
||||
<Scrollable>
|
||||
<div className="flex flex-col p-3">
|
||||
<JsonSchemaForm
|
||||
schema={schema}
|
||||
onChange={setData}
|
||||
className="legacy hide-required-mark"
|
||||
/>
|
||||
</div>
|
||||
<JsonViewer json={data} expand={9} />
|
||||
</Scrollable>
|
||||
);
|
||||
}
|
||||
18
app/src/ui/routes/test/tests/flow-form-test.tsx
Normal file
18
app/src/ui/routes/test/tests/flow-form-test.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { FetchTask } from "flows";
|
||||
import { useState } from "react";
|
||||
import { TaskForm } from "ui/modules/flows/components/form/TaskForm";
|
||||
|
||||
export default function FlowFormTest() {
|
||||
const [data, setData] = useState(null);
|
||||
const task = new FetchTask("Fetch Something", {
|
||||
url: "https://jsonplaceholder.typicode.com/todos/1",
|
||||
method: "{{ input.mode }}"
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="flex flex-col p-3">
|
||||
<TaskForm task={task} onChange={setData as any} />
|
||||
<pre>{JSON.stringify(data, null, 2)}</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
243
app/src/ui/routes/test/tests/flows-test.tsx
Normal file
243
app/src/ui/routes/test/tests/flows-test.tsx
Normal file
@@ -0,0 +1,243 @@
|
||||
import { type ElementProps, Input, Select, TagsInput, TextInput } from "@mantine/core";
|
||||
import { useToggle } from "@mantine/hooks";
|
||||
import {
|
||||
IconBolt,
|
||||
IconBoltFilled,
|
||||
IconDatabase,
|
||||
IconGlobeFilled,
|
||||
IconMinus,
|
||||
IconPlayerPlay,
|
||||
IconPlus,
|
||||
IconTrash,
|
||||
IconWorld
|
||||
} from "@tabler/icons-react";
|
||||
import { useState } from "react";
|
||||
import { TbPlayerPlayFilled } from "react-icons/tb";
|
||||
import { DefaultNode } from "ui/components/canvas/components/nodes/DefaultNode";
|
||||
import { KeyValueInput } from "ui/modules/flows/components2/form/KeyValueInput";
|
||||
import type { AppFlowsSchema } from "../../../../modules";
|
||||
import { Button } from "../../../components/buttons/Button";
|
||||
import { IconButton } from "../../../components/buttons/IconButton";
|
||||
import { JsonEditor } from "../../../components/code/JsonEditor";
|
||||
import { FloatingSelect } from "../../../components/form/FloatingSelect/FloatingSelect";
|
||||
import { SegmentedControl } from "../../../components/form/SegmentedControl";
|
||||
import { Scrollable } from "../../../layouts/AppShell/AppShell";
|
||||
|
||||
const TRIGGERS = {
|
||||
http: {
|
||||
type: "http",
|
||||
config: {
|
||||
mode: "sync",
|
||||
method: "GET",
|
||||
response_type: "json",
|
||||
path: "/trigger_http"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const TASKS = {
|
||||
fetch: {
|
||||
type: "fetch",
|
||||
params: {
|
||||
method: "GET",
|
||||
headers: [],
|
||||
url: "https://jsonplaceholder.typicode.com/todos/1"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default function FlowsTest() {
|
||||
return (
|
||||
<Scrollable>
|
||||
<div className="flex flex-col justify-center p-4 w-full h-full items-center gap-10">
|
||||
<TriggerComponent />
|
||||
<TaskDbQueryMultipleComponent />
|
||||
<TaskFetchComponent />
|
||||
</div>
|
||||
</Scrollable>
|
||||
);
|
||||
}
|
||||
|
||||
const NodeHeader = ({
|
||||
Icon,
|
||||
iconProps,
|
||||
rightSection,
|
||||
initialValue,
|
||||
onChange
|
||||
}: {
|
||||
Icon: React.FC<any>;
|
||||
iconProps?: ElementProps<"svg">;
|
||||
rightSection?: React.ReactNode;
|
||||
initialValue: string;
|
||||
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
}) => (
|
||||
<DefaultNode.Header className="justify-between gap-3">
|
||||
<div className="flex flex-row flex-grow gap-1 items-center">
|
||||
<Icon {...{ width: 16, height: 16, ...(iconProps ?? {}) }} />
|
||||
<input
|
||||
type="text"
|
||||
value={initialValue}
|
||||
onChange={onChange || (() => {})}
|
||||
className="font-mono font-semibold bg-transparent w-full rounded-lg outline-none pl-1.5 hover:bg-background/30 transition-colors focus:bg-background/60"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
{/*{rightSection}*/}
|
||||
<IconButton Icon={TbPlayerPlayFilled} size="sm" />
|
||||
</div>
|
||||
</DefaultNode.Header>
|
||||
);
|
||||
|
||||
const TriggerComponent = () => {
|
||||
return (
|
||||
<DefaultNode className="w-96">
|
||||
<NodeHeader Icon={IconBoltFilled} initialValue="test_flow" />
|
||||
<DefaultNode.Content className="gap-3">
|
||||
<div className="flex flex-row justify-between items-center">
|
||||
<SegmentedControl
|
||||
label="Trigger Type"
|
||||
defaultValue="manual"
|
||||
data={[
|
||||
{ label: "Manual", value: "manual" },
|
||||
{ label: "HTTP", value: "http" },
|
||||
{ label: "Event", value: "event", disabled: true }
|
||||
]}
|
||||
/>
|
||||
<SegmentedControl
|
||||
label="Execution Mode"
|
||||
defaultValue="async"
|
||||
data={[
|
||||
{ label: "Async", value: "async" },
|
||||
{ label: "Sync", value: "sync" }
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-row gap-2 items-center">
|
||||
<Select
|
||||
className="w-36"
|
||||
label="Method"
|
||||
defaultValue="GET"
|
||||
data={["GET", "POST", "PATCH", "PUT", "DEL"]}
|
||||
/>
|
||||
<TextInput
|
||||
className="w-full"
|
||||
label="Mapping Path"
|
||||
placeholder="/trigger_http"
|
||||
classNames={{ wrapper: "font-mono pt-px" }}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-row gap-2 items-center">
|
||||
<SegmentedControl
|
||||
className="w-full"
|
||||
label="Response Type"
|
||||
defaultValue="json"
|
||||
data={[
|
||||
{ label: "JSON", value: "json" },
|
||||
{ label: "HTML", value: "html" },
|
||||
{ label: "Text", value: "text" }
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</DefaultNode.Content>
|
||||
</DefaultNode>
|
||||
);
|
||||
};
|
||||
|
||||
const TaskFetchComponent = () => {
|
||||
const [advanced, toggle] = useToggle([true, false]);
|
||||
|
||||
return (
|
||||
<DefaultNode className="w-[400px]">
|
||||
<NodeHeader Icon={IconWorld} initialValue="fetch_something" />
|
||||
<DefaultNode.Content className="gap-3">
|
||||
<div className="flex flex-row gap-2 items-center">
|
||||
<Select
|
||||
className="w-36"
|
||||
label="Method"
|
||||
defaultValue="GET"
|
||||
data={["GET", "POST", "PATCH", "PUT", "DEL"]}
|
||||
/>
|
||||
<TextInput
|
||||
className="w-full"
|
||||
label="Mapping Path"
|
||||
placeholder="/trigger_http"
|
||||
classNames={{ wrapper: "font-mono pt-px" }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
onClick={toggle as any}
|
||||
className="justify-center"
|
||||
size="small"
|
||||
variant="ghost"
|
||||
iconSize={14}
|
||||
IconLeft={advanced ? IconMinus : IconPlus}
|
||||
>
|
||||
More options
|
||||
</Button>
|
||||
|
||||
{advanced && (
|
||||
<>
|
||||
<KeyValueInput label="URL query" />
|
||||
<KeyValueInput label="Headers" />
|
||||
<div className="flex flex-row gap-2 items-center">
|
||||
<Input.Wrapper className="w-full">
|
||||
<Input.Label>Body</Input.Label>
|
||||
<SegmentedControl data={["None", "Form", "JSON"]} defaultValue="JSON" />
|
||||
<KeyValueInput label="" />
|
||||
</Input.Wrapper>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</DefaultNode.Content>
|
||||
</DefaultNode>
|
||||
);
|
||||
};
|
||||
|
||||
const TaskDbQueryMultipleComponent = () => {
|
||||
return (
|
||||
<DefaultNode className="w-[400px]">
|
||||
<NodeHeader Icon={IconDatabase} initialValue="query_multiple" />
|
||||
<DefaultNode.Content className="gap-3">
|
||||
<div className="flex flex-row gap-2 items-center">
|
||||
<Select
|
||||
className="w-6/12"
|
||||
label="Entity"
|
||||
placeholder="Select entity"
|
||||
data={["users", "posts", "comments"]}
|
||||
/>
|
||||
<Select
|
||||
className="w-4/12"
|
||||
label="Sort by"
|
||||
data={["id", "title", "username"]}
|
||||
defaultValue="id"
|
||||
/>
|
||||
<Select
|
||||
className="w-2/12"
|
||||
label="Sort dir"
|
||||
data={["asc", "desc"]}
|
||||
defaultValue="asc"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<TagsInput
|
||||
label="Select properties"
|
||||
data={["id", "title", "username"]}
|
||||
placeholder="All selected"
|
||||
/>
|
||||
<TagsInput
|
||||
label="Embed relations"
|
||||
data={["posts", "comments"]}
|
||||
placeholder="None selected"
|
||||
/>
|
||||
|
||||
<Input.Wrapper className="w-full">
|
||||
<Input.Label>Where object</Input.Label>
|
||||
<div className="text-sm placeholder:text-slate-400 placeholder:opacity-80">
|
||||
<JsonEditor basicSetup={{ lineNumbers: false, foldGutter: false }} />
|
||||
</div>
|
||||
</Input.Wrapper>
|
||||
</DefaultNode.Content>
|
||||
</DefaultNode>
|
||||
);
|
||||
};
|
||||
65
app/src/ui/routes/test/tests/jsonform-test/index.tsx
Normal file
65
app/src/ui/routes/test/tests/jsonform-test/index.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import Form from "@rjsf/core";
|
||||
import type { RJSFSchema, UiSchema } from "@rjsf/utils";
|
||||
import { useRef } from "react";
|
||||
import { TbPlus, TbTrash } from "react-icons/tb";
|
||||
import { Button } from "../../../../components/buttons/Button";
|
||||
import * as Formy from "../../../../components/form/Formy";
|
||||
import {
|
||||
JsonSchemaForm,
|
||||
type JsonSchemaFormRef
|
||||
} from "../../../../components/form/json-schema/JsonSchemaForm";
|
||||
import * as AppShell from "../../../../layouts/AppShell/AppShell";
|
||||
|
||||
class CfJsonSchemaValidator {}
|
||||
|
||||
export default function JsonFormTest() {
|
||||
const schema: RJSFSchema = {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: {
|
||||
type: "string",
|
||||
title: "Name",
|
||||
minLength: 3
|
||||
},
|
||||
variants: {
|
||||
anyOf: [{ type: "string" }, { type: "number" }]
|
||||
}
|
||||
}
|
||||
};
|
||||
const ref = useRef<JsonSchemaFormRef>(null);
|
||||
|
||||
function onSubmit() {
|
||||
console.log("submit", ref.current?.formData());
|
||||
console.log("isvalid", ref.current?.validateForm());
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<AppShell.SectionHeader
|
||||
right={
|
||||
<Button type="button" variant="primary" onClick={onSubmit}>
|
||||
Submit
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
JSON Schema
|
||||
</AppShell.SectionHeader>
|
||||
<div>
|
||||
<div className="flex flex-grow flex-col gap-3 p-3">
|
||||
<Formy.Group>
|
||||
<Formy.Label htmlFor="name">Name</Formy.Label>
|
||||
<Formy.Input id="name" name="name" />
|
||||
</Formy.Group>
|
||||
<Formy.Group>
|
||||
<JsonSchemaForm ref={ref} schema={schema} />
|
||||
</Formy.Group>
|
||||
|
||||
<Formy.Group>
|
||||
<Formy.Label htmlFor="name">Options</Formy.Label>
|
||||
<Formy.Select id="options" name="options" />
|
||||
</Formy.Group>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
15
app/src/ui/routes/test/tests/liquid-js-test.tsx
Normal file
15
app/src/ui/routes/test/tests/liquid-js-test.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { TextInput } from "@mantine/core";
|
||||
import { LiquidJsEditor } from "../../../components/code/LiquidJsEditor";
|
||||
import * as Formy from "../../../components/form/Formy";
|
||||
|
||||
export function LiquidJsTest() {
|
||||
return (
|
||||
<div className="flex flex-col p-4 gap-3">
|
||||
<h1>LiquidJsTest</h1>
|
||||
<LiquidJsEditor />
|
||||
|
||||
<TextInput />
|
||||
<Formy.Input />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
42
app/src/ui/routes/test/tests/mantine-test.tsx
Normal file
42
app/src/ui/routes/test/tests/mantine-test.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import { Button, Modal, Switch, Tooltip, useMantineColorScheme } from "@mantine/core";
|
||||
import { useColorScheme, useDisclosure } from "@mantine/hooks";
|
||||
import { Button as AppButton } from "../../../components/buttons/Button";
|
||||
|
||||
export default function MantineTest() {
|
||||
return (
|
||||
<div className="p-4 flex flex-col gap-2 items-start">
|
||||
<h1>mantine</h1>
|
||||
<div className="flex flex-row gap-2 justify-center content-center items-center">
|
||||
<Button color="blue">Mantine</Button>
|
||||
<AppButton>Button</AppButton>
|
||||
<AppButton variant="primary">Button</AppButton>
|
||||
</div>
|
||||
<MantineModal />
|
||||
<MantineTooltip />
|
||||
<Switch defaultChecked label="I agree to sell my privacy" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function MantineModal() {
|
||||
const [opened, { open, close }] = useDisclosure(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal opened={opened} onClose={close} title="Authentication">
|
||||
<div>Modal content</div>
|
||||
</Modal>
|
||||
|
||||
<Button onClick={open}>Open modal</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function MantineTooltip() {
|
||||
const { colorScheme } = useMantineColorScheme();
|
||||
return (
|
||||
<Tooltip label="Tooltip">
|
||||
<span>Hover me ({colorScheme})</span>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
17
app/src/ui/routes/test/tests/modal-test.tsx
Normal file
17
app/src/ui/routes/test/tests/modal-test.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { useDisclosure } from "@mantine/hooks";
|
||||
import { Modal } from "../../../components/overlay/Modal";
|
||||
|
||||
export default function ModalTest() {
|
||||
const [opened, { open, close }] = useDisclosure(true);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col w-full h-full justify-center items-center">
|
||||
<button onClick={open}>Open</button>
|
||||
<Modal open={opened} onClose={close}>
|
||||
<div className="border-blue-500 border-2 p-10">
|
||||
Modal content <button onClick={close}>close</button>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
158
app/src/ui/routes/test/tests/query-jsonform.tsx
Normal file
158
app/src/ui/routes/test/tests/query-jsonform.tsx
Normal file
@@ -0,0 +1,158 @@
|
||||
import type { Schema } from "@cfworker/json-schema";
|
||||
import { useState } from "react";
|
||||
import { JsonSchemaForm } from "../../../components/form/json-schema/JsonSchemaForm";
|
||||
import { Scrollable } from "../../../layouts/AppShell/AppShell";
|
||||
|
||||
const schema: Schema = {
|
||||
definitions: {
|
||||
primitive: {
|
||||
anyOf: [
|
||||
{
|
||||
title: "String",
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
title: "Number",
|
||||
type: "number",
|
||||
},
|
||||
{
|
||||
title: "Boolean",
|
||||
type: "boolean",
|
||||
},
|
||||
],
|
||||
},
|
||||
numeric: {
|
||||
anyOf: [
|
||||
{
|
||||
title: "Number",
|
||||
type: "number",
|
||||
},
|
||||
{
|
||||
title: "Datetime",
|
||||
type: "string",
|
||||
format: "date-time",
|
||||
},
|
||||
{
|
||||
title: "Date",
|
||||
type: "string",
|
||||
format: "date",
|
||||
},
|
||||
{
|
||||
title: "Time",
|
||||
type: "string",
|
||||
format: "time",
|
||||
},
|
||||
],
|
||||
},
|
||||
boolean: {
|
||||
title: "Boolean",
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
type: "object",
|
||||
properties: {
|
||||
operand: {
|
||||
enum: ["$and", "$or"],
|
||||
default: "$and",
|
||||
type: "string",
|
||||
},
|
||||
conditions: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {
|
||||
operand: {
|
||||
enum: ["$and", "$or"],
|
||||
default: "$and",
|
||||
type: "string",
|
||||
},
|
||||
key: {
|
||||
type: "string",
|
||||
},
|
||||
operator: {
|
||||
type: "array",
|
||||
items: {
|
||||
anyOf: [
|
||||
{
|
||||
title: "Equals",
|
||||
type: "object",
|
||||
properties: {
|
||||
$eq: {
|
||||
$ref: "#/definitions/primitive",
|
||||
},
|
||||
},
|
||||
required: ["$eq"],
|
||||
},
|
||||
{
|
||||
title: "Lower than",
|
||||
type: "object",
|
||||
properties: {
|
||||
$lt: {
|
||||
$ref: "#/definitions/numeric",
|
||||
},
|
||||
},
|
||||
required: ["$lt"],
|
||||
},
|
||||
{
|
||||
title: "Greather than",
|
||||
type: "object",
|
||||
properties: {
|
||||
$gt: {
|
||||
$ref: "#/definitions/numeric",
|
||||
},
|
||||
},
|
||||
required: ["$gt"],
|
||||
},
|
||||
{
|
||||
title: "Between",
|
||||
type: "object",
|
||||
properties: {
|
||||
$between: {
|
||||
type: "array",
|
||||
items: {
|
||||
$ref: "#/definitions/numeric",
|
||||
},
|
||||
minItems: 2,
|
||||
maxItems: 2,
|
||||
},
|
||||
},
|
||||
required: ["$between"],
|
||||
},
|
||||
{
|
||||
title: "In",
|
||||
type: "object",
|
||||
properties: {
|
||||
$in: {
|
||||
type: "array",
|
||||
items: {
|
||||
$ref: "#/definitions/primitive",
|
||||
},
|
||||
minItems: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
minItems: 1,
|
||||
},
|
||||
},
|
||||
required: ["key", "operator"],
|
||||
},
|
||||
minItems: 1,
|
||||
},
|
||||
},
|
||||
required: ["operand", "conditions"],
|
||||
};
|
||||
|
||||
export default function QueryJsonFormTest() {
|
||||
const [data, setData] = useState(null);
|
||||
|
||||
return (
|
||||
<Scrollable>
|
||||
<div className="flex flex-col gap-3 p-3">
|
||||
<JsonSchemaForm schema={schema} onChange={setData as any} />
|
||||
<pre>{JSON.stringify(data, null, 2)}</pre>
|
||||
</div>
|
||||
</Scrollable>
|
||||
);
|
||||
}
|
||||
48
app/src/ui/routes/test/tests/react-hook-errors.tsx
Normal file
48
app/src/ui/routes/test/tests/react-hook-errors.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import { typeboxResolver } from "@hookform/resolvers/typebox";
|
||||
import { TextInput } from "@mantine/core";
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
const schema = Type.Object({
|
||||
example: Type.Optional(Type.String()),
|
||||
exampleRequired: Type.String({ minLength: 2 })
|
||||
});
|
||||
|
||||
export default function ReactHookErrors() {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
watch,
|
||||
formState: { errors }
|
||||
} = useForm({
|
||||
resolver: typeboxResolver(schema)
|
||||
});
|
||||
const onSubmit = (data) => console.log(data);
|
||||
|
||||
console.log(watch("example")); // watch input value by passing the name of it
|
||||
console.log("errors", errors);
|
||||
|
||||
return (
|
||||
/* "handleSubmit" will validate your inputs before invoking "onSubmit" */
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
{/* register your input into the hook by invoking the "register" function */}
|
||||
<TextInput defaultValue="test" {...register("example")} />
|
||||
|
||||
{/* @ts-ignore include validation with required or other standard HTML validation rules */}
|
||||
<TextInput {...register("exampleRequired")} error={errors.exampleRequired?.message} />
|
||||
|
||||
<div>
|
||||
<input type="submit" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{Object.entries(errors).map(([key, value]) => (
|
||||
<p key={key}>
|
||||
{/* @ts-ignore */}
|
||||
{key}: {value.message}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
15
app/src/ui/routes/test/tests/reactflow-test.tsx
Normal file
15
app/src/ui/routes/test/tests/reactflow-test.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { ReactFlow } from "@xyflow/react";
|
||||
|
||||
const initialNodes = [
|
||||
{ id: "1", position: { x: 0, y: 0 }, data: { label: "1" } },
|
||||
{ id: "2", position: { x: 0, y: 100 }, data: { label: "2" } }
|
||||
];
|
||||
const initialEdges = [{ id: "e1-2", source: "1", target: "2" }];
|
||||
|
||||
export default function ReactFlowTest() {
|
||||
return (
|
||||
<div style={{ width: "100vw", height: "100vh" }}>
|
||||
<ReactFlow nodes={initialNodes} edges={initialEdges} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
113
app/src/ui/routes/test/tests/schema-test.tsx
Normal file
113
app/src/ui/routes/test/tests/schema-test.tsx
Normal file
@@ -0,0 +1,113 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { useBknd } from "../../../client/BkndProvider";
|
||||
import { JsonSchemaForm } from "../../../components/form/json-schema/JsonSchemaForm";
|
||||
import { Scrollable } from "../../../layouts/AppShell/AppShell";
|
||||
|
||||
function useSchema() {
|
||||
const [schema, setSchema] = useState<any>();
|
||||
|
||||
useEffect(() => {
|
||||
if (schema) return;
|
||||
|
||||
fetch("/api/system/schema")
|
||||
.then((res) => res.json())
|
||||
.then((data) => setSchema(data));
|
||||
}, []);
|
||||
|
||||
return schema;
|
||||
}
|
||||
|
||||
const uiSchema = {
|
||||
auth: {
|
||||
jwt: {
|
||||
basepath: {
|
||||
"ui:options": {
|
||||
label: false
|
||||
}
|
||||
},
|
||||
fields: {
|
||||
"ui:options": {
|
||||
label: false,
|
||||
orderable: false
|
||||
}
|
||||
}
|
||||
},
|
||||
strategies: {
|
||||
additionalProperties: {
|
||||
"ui:widget": "select",
|
||||
type: {
|
||||
"ui:widget": "hidden"
|
||||
}
|
||||
},
|
||||
type: {
|
||||
"ui:widget": "hidden"
|
||||
}
|
||||
}
|
||||
},
|
||||
server: {
|
||||
cors: {
|
||||
allow_methods: {
|
||||
"ui:widget": "checkboxes"
|
||||
},
|
||||
allow_headers: {
|
||||
"ui:options": {
|
||||
orderable: false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
media: {
|
||||
adapter: {
|
||||
"ui:options": {
|
||||
label: false
|
||||
},
|
||||
type: {
|
||||
"ui:widget": "hidden"
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default function SchemaTest() {
|
||||
const { app, schema } = useBknd();
|
||||
const keys = ["auth", "server", "media", "data"] as const;
|
||||
const [tab, setTab] = useState(keys[0]);
|
||||
console.log("schema", schema, app.config);
|
||||
|
||||
if (!schema) return;
|
||||
|
||||
const current = {
|
||||
key: tab,
|
||||
schema: schema[tab],
|
||||
uiSchema: uiSchema[tab] || {},
|
||||
config: app.config[tab]
|
||||
};
|
||||
console.log("current", current);
|
||||
|
||||
return (
|
||||
<Scrollable>
|
||||
<div className="flex flex-col gap-2 p-3">
|
||||
<div className="flex flex-row gap-2">
|
||||
{keys.map((key) => (
|
||||
<button
|
||||
key={key}
|
||||
role="button"
|
||||
className={twMerge("flex py-2 px-3", key === tab && "bg-primary/5")}
|
||||
onClick={() => setTab(key as any)}
|
||||
>
|
||||
{key}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<JsonSchemaForm
|
||||
schema={current.schema}
|
||||
formData={current.config}
|
||||
uiSchema={current.uiSchema}
|
||||
onChange={console.log}
|
||||
className="legacy"
|
||||
/>
|
||||
</div>
|
||||
</Scrollable>
|
||||
);
|
||||
}
|
||||
107
app/src/ui/routes/test/tests/sortable-test.tsx
Normal file
107
app/src/ui/routes/test/tests/sortable-test.tsx
Normal file
@@ -0,0 +1,107 @@
|
||||
import {
|
||||
DragDropContext,
|
||||
Draggable,
|
||||
type DraggableProvided,
|
||||
type DraggableRubric,
|
||||
type DraggableStateSnapshot,
|
||||
Droppable,
|
||||
type DroppableProps
|
||||
} from "@hello-pangea/dnd";
|
||||
import { useListState } from "@mantine/hooks";
|
||||
import { IconGripVertical } from "@tabler/icons-react";
|
||||
import type React from "react";
|
||||
import { useEffect } from "react";
|
||||
import { useId } from "react";
|
||||
|
||||
export default function SortableTest() {
|
||||
return (
|
||||
<div>
|
||||
sortable
|
||||
<div className="p-4">
|
||||
<SortableList
|
||||
data={[
|
||||
{ id: "C", name: "Carbon" },
|
||||
{ id: "N", name: "Nitrogen" },
|
||||
{ id: "Y", name: "Yttrium" },
|
||||
{ id: "Ba", name: "Barium" },
|
||||
{ id: "Ce", name: "Cerium" }
|
||||
]}
|
||||
onReordered={(...args) => console.log("reordered", args)}
|
||||
onChange={(data) => console.log("changed", data)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type SortableItemProps<Item = any> = {
|
||||
item: Item;
|
||||
dnd: { provided: DraggableProvided; snapshot: DraggableStateSnapshot; rubic: DraggableRubric };
|
||||
};
|
||||
|
||||
type SortableListProps<Item = any> = {
|
||||
data: Item[];
|
||||
extractId?: (item: Item) => string;
|
||||
renderItem?: (props: SortableItemProps<Item>) => React.ReactNode;
|
||||
dndProps?: Omit<DroppableProps, "children">;
|
||||
onReordered?: (from: number, to: number) => void;
|
||||
onChange?: (data: Item[]) => void;
|
||||
};
|
||||
|
||||
export function SortableList({
|
||||
data = [],
|
||||
extractId,
|
||||
renderItem,
|
||||
dndProps = { droppableId: "sortable-list", direction: "vertical" },
|
||||
onReordered,
|
||||
onChange
|
||||
}: SortableListProps) {
|
||||
const [state, handlers] = useListState(data);
|
||||
|
||||
function onDragEnd({ destination, source }) {
|
||||
const change = { from: source.index, to: destination?.index || 0 };
|
||||
handlers.reorder(change);
|
||||
onReordered?.(change.from, change.to);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
onChange?.(state);
|
||||
}, [state]);
|
||||
|
||||
const items = state.map((item, index) => {
|
||||
const id = extractId ? extractId(item) : useId();
|
||||
return (
|
||||
<Draggable key={id} index={index} draggableId={id}>
|
||||
{(provided, snapshot, rubic) =>
|
||||
renderItem ? (
|
||||
renderItem({ ...item, dnd: { provided, snapshot, rubic } })
|
||||
) : (
|
||||
<div
|
||||
className="flex flex-row gap-2 p-2 border border-gray-200 rounded-md mb-3 bg-white items-center"
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
>
|
||||
<div {...provided.dragHandleProps}>
|
||||
<IconGripVertical className="size-5" stroke={1.5} />
|
||||
</div>
|
||||
<p>{JSON.stringify(item)}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</Draggable>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
<Droppable {...dndProps}>
|
||||
{(provided) => (
|
||||
<div {...provided.droppableProps} ref={provided.innerRef}>
|
||||
{items}
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
);
|
||||
}
|
||||
37
app/src/ui/routes/test/tests/sql-ai-test.tsx
Normal file
37
app/src/ui/routes/test/tests/sql-ai-test.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export function SqlAiTest() {
|
||||
const [answer, setAnswer] = useState<any>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
async function handleStart() {
|
||||
if (loading) return;
|
||||
|
||||
setAnswer([]);
|
||||
setLoading(true);
|
||||
const source = new EventSource("/api/system/test/sql");
|
||||
source.onmessage = (event) => {
|
||||
if (event.data === "[DONE]") {
|
||||
setLoading(false);
|
||||
source.close();
|
||||
return;
|
||||
}
|
||||
const data = JSON.parse(event.data);
|
||||
setAnswer((prev) => [...prev, data]);
|
||||
console.log("data", data);
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2 p-4">
|
||||
<h1>ai sql test</h1>
|
||||
<button onClick={handleStart}>{loading ? "..." : "start"}</button>
|
||||
|
||||
<div>
|
||||
{answer.map((item, key) => (
|
||||
<span key={key}>{item.response}</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
42
app/src/ui/routes/test/tests/swagger-test.tsx
Normal file
42
app/src/ui/routes/test/tests/swagger-test.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import { useEffect } from "react";
|
||||
import { Scrollable } from "ui/layouts/AppShell/AppShell";
|
||||
|
||||
function SwaggerUI() {
|
||||
useEffect(() => {
|
||||
// Create a script element to load the Swagger UI bundle
|
||||
const script = document.createElement("script");
|
||||
script.src = "https://unpkg.com/swagger-ui-dist@5.11.0/swagger-ui-bundle.js";
|
||||
script.crossOrigin = "anonymous";
|
||||
script.async = true;
|
||||
|
||||
// Append the script to the body and set up Swagger UI once loaded
|
||||
script.onload = () => {
|
||||
// @ts-ignore
|
||||
if (window.SwaggerUIBundle) {
|
||||
// @ts-ignore
|
||||
window.ui = window.SwaggerUIBundle({
|
||||
url: "http://localhost:28623/api/system/openapi.json",
|
||||
dom_id: "#swagger-ui"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
document.body.appendChild(script);
|
||||
|
||||
// Cleanup script on unmount
|
||||
return () => {
|
||||
document.body.removeChild(script);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5.11.0/swagger-ui.css" />
|
||||
<Scrollable>
|
||||
<div id="swagger-ui" />
|
||||
</Scrollable>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default SwaggerUI;
|
||||
Reference in New Issue
Block a user