Importing RJSF lib with first application
This commit is contained in:
0
gui/rpk-gui/src/lib/crud/components/crud-card.tsx
Normal file
0
gui/rpk-gui/src/lib/crud/components/crud-card.tsx
Normal file
65
gui/rpk-gui/src/lib/crud/components/crud-form.tsx
Normal file
65
gui/rpk-gui/src/lib/crud/components/crud-form.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import validator from "@rjsf/validator-ajv8";
|
||||
import Form from "@rjsf/mui";
|
||||
import { RegistryFieldsType, RegistryWidgetsType } from "@rjsf/utils";
|
||||
import { useEffect, useState } from "react";
|
||||
import { jsonschemaProvider } from "../providers/jsonschema-provider";
|
||||
import { useForm } from "@refinedev/core";
|
||||
import CrudTextWidget from "./widgets/crud-text-widget";
|
||||
import UnionEnumField from "./fields/union-enum";
|
||||
|
||||
type Props = {
|
||||
schemaName: string,
|
||||
resource: string,
|
||||
id?: string,
|
||||
//onSubmit: (data: IChangeEvent, event: FormEvent<any>) => void
|
||||
}
|
||||
|
||||
const customWidgets: RegistryWidgetsType = {
|
||||
TextWidget: CrudTextWidget
|
||||
};
|
||||
|
||||
const customFields: RegistryFieldsType = {
|
||||
AnyOfField: UnionEnumField
|
||||
}
|
||||
|
||||
export const CrudForm: React.FC<Props> = ({schemaName, resource, id}) => {
|
||||
const { onFinish, query, formLoading } = useForm({
|
||||
resource: resource,
|
||||
action: id === undefined ? "create" : "edit",
|
||||
redirect: "show",
|
||||
id,
|
||||
});
|
||||
|
||||
const record = query?.data?.data;
|
||||
const [formData, setFormData] = useState(record);
|
||||
|
||||
const [schema, setSchema] = useState({});
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchSchema = async () => {
|
||||
try {
|
||||
const resourceSchema = await jsonschemaProvider.getResourceSchema(schemaName);
|
||||
setSchema(resourceSchema);
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
console.error('Error fetching data:', error);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
fetchSchema();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Form
|
||||
schema={schema}
|
||||
formData={record}
|
||||
onChange={(e) => setFormData(e.formData)}
|
||||
onSubmit={(e) => onFinish(e.formData)}
|
||||
validator={validator}
|
||||
omitExtraData={true}
|
||||
widgets={customWidgets}
|
||||
fields={customFields}
|
||||
/>
|
||||
)
|
||||
}
|
||||
0
gui/rpk-gui/src/lib/crud/components/crud-list.tsx
Normal file
0
gui/rpk-gui/src/lib/crud/components/crud-list.tsx
Normal file
100
gui/rpk-gui/src/lib/crud/components/fields/union-enum.tsx
Normal file
100
gui/rpk-gui/src/lib/crud/components/fields/union-enum.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
import {ERRORS_KEY, FieldProps, FormContextType, getUiOptions, RJSFSchema, StrictRJSFSchema} from "@rjsf/utils";
|
||||
import { getDefaultRegistry } from "@rjsf/core";
|
||||
import UnionEnumWidget from "../widgets/union-enum";
|
||||
|
||||
import get from 'lodash/get';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import omit from 'lodash/omit';
|
||||
|
||||
const {
|
||||
fields: { AnyOfField },
|
||||
} = getDefaultRegistry();
|
||||
|
||||
export default function UnionEnumField<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>(
|
||||
props: FieldProps
|
||||
) {
|
||||
const {
|
||||
name,
|
||||
disabled = false,
|
||||
errorSchema = {},
|
||||
formContext,
|
||||
formData,
|
||||
onChange,
|
||||
onBlur,
|
||||
onFocus,
|
||||
registry,
|
||||
schema,
|
||||
uiSchema,
|
||||
options,
|
||||
idSchema,
|
||||
} = props;
|
||||
|
||||
getDefaultRegistry<T,S,F>().widgets
|
||||
|
||||
const enumOptions: any[] = []
|
||||
|
||||
if (options.length == 2 && (options[0].type == "null" || options[1].type == "null")) {
|
||||
const { SchemaField: _SchemaField } = registry.fields;
|
||||
|
||||
let opt_schema = {...schema}
|
||||
delete(opt_schema.anyOf)
|
||||
|
||||
if (options[0].type == "null") {
|
||||
opt_schema = {...opt_schema, ...options[1]}
|
||||
} else if (options[1].type == "null") {
|
||||
opt_schema = {...opt_schema, ...options[0]}
|
||||
}
|
||||
return <_SchemaField {...props} schema={opt_schema} uiSchema={uiSchema} />
|
||||
}
|
||||
|
||||
for (let opt of options) {
|
||||
if (!opt.hasOwnProperty('enum')) {
|
||||
return (<AnyOfField {...props} />)
|
||||
}
|
||||
for (let val of opt.enum) {
|
||||
enumOptions.push({
|
||||
title: val,
|
||||
value: val,
|
||||
type: opt.title
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const { globalUiOptions, schemaUtils } = registry;
|
||||
const {
|
||||
placeholder,
|
||||
autofocus,
|
||||
autocomplete,
|
||||
title = schema.title,
|
||||
...uiOptions
|
||||
} = getUiOptions(uiSchema, globalUiOptions);
|
||||
|
||||
const rawErrors = get(errorSchema, ERRORS_KEY, []);
|
||||
const fieldErrorSchema = omit(errorSchema, [ERRORS_KEY]);
|
||||
const displayLabel = schemaUtils.getDisplayLabel(schema, uiSchema, globalUiOptions);
|
||||
|
||||
return (
|
||||
<UnionEnumWidget
|
||||
id={`${idSchema.$id}${schema.oneOf ? '__oneof_select' : '__anyof_select'}`}
|
||||
name={`${name}${schema.oneOf ? '__oneof_select' : '__anyof_select'}`}
|
||||
schema={schema}
|
||||
uiSchema={uiSchema}
|
||||
onChange={onChange}
|
||||
onBlur={onBlur}
|
||||
onFocus={onFocus}
|
||||
disabled={disabled || isEmpty(options)}
|
||||
multiple={false}
|
||||
rawErrors={rawErrors}
|
||||
errorSchema={fieldErrorSchema}
|
||||
value={formData}
|
||||
options={{enumOptions}}
|
||||
registry={registry}
|
||||
formContext={formContext}
|
||||
placeholder={placeholder}
|
||||
autocomplete={autocomplete}
|
||||
autofocus={autofocus}
|
||||
label={title ?? name}
|
||||
hideLabel={!displayLabel}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import {FormContextType, getTemplate, RJSFSchema, StrictRJSFSchema, WidgetProps} from "@rjsf/utils";
|
||||
|
||||
import ForeignKeyWidget from "./foreign-key";
|
||||
|
||||
export default function CrudTextWidget<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>(
|
||||
props: WidgetProps<T, S, F>
|
||||
) {
|
||||
if (props.schema.hasOwnProperty("foreign_key")) {
|
||||
return (<ForeignKeyWidget {...props} />);
|
||||
} else {
|
||||
const { options, registry } = props;
|
||||
const BaseInputTemplate = getTemplate<'BaseInputTemplate', T, S, F>('BaseInputTemplate', registry, options);
|
||||
return <BaseInputTemplate {...props} />;
|
||||
}
|
||||
}
|
||||
79
gui/rpk-gui/src/lib/crud/components/widgets/foreign-key.tsx
Normal file
79
gui/rpk-gui/src/lib/crud/components/widgets/foreign-key.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import {FormContextType, RJSFSchema, StrictRJSFSchema, WidgetProps} from '@rjsf/utils';
|
||||
import { Autocomplete } from "@mui/material";
|
||||
import { useState, useEffect } from "react";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import {BaseRecord, useList, useOne} from "@refinedev/core";
|
||||
|
||||
type ForeignKeySchema = RJSFSchema & {
|
||||
foreign_key?: {
|
||||
reference: {
|
||||
resource: string,
|
||||
label: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default function ForeignKeyWidget<T = any, S extends ForeignKeySchema = ForeignKeySchema, F extends FormContextType = any>(
|
||||
props: WidgetProps<T, S, F>
|
||||
) {
|
||||
if (props.schema.foreign_key === undefined) {
|
||||
return;
|
||||
}
|
||||
const resource = props.schema.foreign_key.reference.resource
|
||||
const labelField = props.schema.foreign_key.reference.label
|
||||
|
||||
const valueResult = useOne({
|
||||
resource: resource,
|
||||
id: props.value != null ? props.value : undefined
|
||||
});
|
||||
|
||||
const empty_option: BaseRecord = {
|
||||
id: undefined
|
||||
}
|
||||
empty_option[labelField] = "(None)"
|
||||
|
||||
const [inputValue, setInputValue] = useState<string>("");
|
||||
const [selectedValue, setSelectedValue] = useState(valueResult.data?.data || null);
|
||||
const [debouncedInputValue, setDebouncedInputValue] = useState<string>(inputValue);
|
||||
|
||||
useEffect(() => {
|
||||
const handler = setTimeout(() => setDebouncedInputValue(inputValue), 300); // Adjust debounce delay as needed
|
||||
return () => clearTimeout(handler);
|
||||
}, [inputValue]);
|
||||
|
||||
const listResult = useList({
|
||||
resource: resource,
|
||||
pagination: { current: 1, pageSize: 10 },
|
||||
filters: [{ field: "name", operator: "contains", value: debouncedInputValue }],
|
||||
sorters: [{ field: "name", order: "asc" }],
|
||||
});
|
||||
|
||||
const options = listResult.data?.data || [];
|
||||
if (! props.required) {
|
||||
options.unshift(empty_option);
|
||||
}
|
||||
const isLoading = listResult.isLoading || valueResult.isLoading;
|
||||
|
||||
if(! selectedValue && valueResult.data) {
|
||||
setSelectedValue(valueResult.data?.data)
|
||||
}
|
||||
|
||||
return (
|
||||
<Autocomplete
|
||||
value={selectedValue}
|
||||
onChange={(event, newValue) => {
|
||||
setSelectedValue(newValue ? newValue : empty_option);
|
||||
props.onChange(newValue ? newValue.id : null);
|
||||
return true;
|
||||
}}
|
||||
//inputValue={inputValue}
|
||||
onInputChange={(event, newInputValue) => setInputValue(newInputValue)}
|
||||
options={options}
|
||||
getOptionLabel={(option) => option ? option[labelField] : ""}
|
||||
loading={isLoading}
|
||||
renderInput={(params) => (
|
||||
<TextField {...params} label={ props.label } variant="outlined" />
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
55
gui/rpk-gui/src/lib/crud/components/widgets/union-enum.tsx
Normal file
55
gui/rpk-gui/src/lib/crud/components/widgets/union-enum.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import {EnumOptionsType, FormContextType, RJSFSchema, StrictRJSFSchema, WidgetProps} from "@rjsf/utils";
|
||||
import {useState} from "react";
|
||||
import Autocomplete from "@mui/material/Autocomplete";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import {UIOptionsType} from "@rjsf/utils/src/types";
|
||||
|
||||
type CrudEnumOptionsType<S extends StrictRJSFSchema = RJSFSchema> = EnumOptionsType<S> & {
|
||||
type: string,
|
||||
title: string
|
||||
}
|
||||
|
||||
interface CrudEnumWidgetProps<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>
|
||||
extends WidgetProps<T, S, F> {
|
||||
options: NonNullable<UIOptionsType<T, S, F>> & {
|
||||
/** The enum options list for a type that supports them */
|
||||
enumOptions?: CrudEnumOptionsType<S>[];
|
||||
};
|
||||
}
|
||||
|
||||
export default function UnionEnumWidget<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>(
|
||||
props: CrudEnumWidgetProps<T, S, F>
|
||||
) {
|
||||
const {
|
||||
options,
|
||||
value,
|
||||
} = props;
|
||||
const [selectedValue, setSelectedValue] = useState<CrudEnumOptionsType | null>(null);
|
||||
|
||||
if (! selectedValue && value && options.enumOptions) {
|
||||
for (const opt of options.enumOptions){
|
||||
if (opt.value == value) {
|
||||
setSelectedValue(opt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (options.enumOptions !== undefined) {
|
||||
return (
|
||||
<Autocomplete
|
||||
value={selectedValue}
|
||||
onChange={(event, newValue) => {
|
||||
setSelectedValue(newValue);
|
||||
props.onChange(newValue?.value);
|
||||
}}
|
||||
options={options.enumOptions}
|
||||
groupBy={(option) => option.type}
|
||||
getOptionLabel={(option) => option.title}
|
||||
renderInput={(params) => (
|
||||
<TextField {...params} label={ props.label } variant="outlined" />
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user