public commit

This commit is contained in:
dswbx
2024-11-16 12:01:47 +01:00
commit 90f80c4280
582 changed files with 49291 additions and 0 deletions

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
};

View 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>
</>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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;