Skip to content

Commit

Permalink
feat(docs): ACT-1360 - Components For Complex Types (#1370)
Browse files Browse the repository at this point in the history
* feat(docs): added rpc-parser component for reference pages

* feat(docs): added mm install msg

* feat(docs): change mm condition

* feat(docs): refactored styles

* feat(docs): added request/response section

* feat(docs): added error section

* feat(docs): added eth_call

* added default logic for InteractiveBox

* Made enhancements for components

* returned condition for disabled button

* removed not needed styles in Drawer

* started implementing of handling complex types

* made some progress

* added UI and logic for complex array

* added label clear on cancel and back

* fixed styles for chevron icon

* feat(docs): added cancel func for complex types

* implemented complex types except object

* redesigned drawer

* fixed some issues

* improved some styles

* fixed a bug

* fixed icon color for theme change

* fixed code style

* fixed code style #2

---------

Co-authored-by: ae_atrofimov <anton.trofimov-ext@consensys.net>
  • Loading branch information
aednikanov and TrofimovAnton85 authored Jul 3, 2024
1 parent 94ad9a2 commit c4c3e73
Show file tree
Hide file tree
Showing 19 changed files with 630 additions and 185 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,4 @@
"$root$": false
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import React, { useContext, useEffect, useState } from "react";
import { FieldTemplateProps } from "@rjsf/utils";
import { BaseInputTemplate } from "@site/src/components/ParserOpenRPC/InteractiveBox/templates/BaseInputTemplate";
import { SelectWidget } from "@site/src/components/ParserOpenRPC/InteractiveBox/widgets/SelectWidget";
import styles from "@site/src/components/ParserOpenRPC/InteractiveBox/styles.module.css";
import { ParserOpenRPCContext } from "@site/src/components/ParserOpenRPC";
import clsx from "clsx";

export const ConditionalField = (props: FieldTemplateProps) => {
const [isOpened, setIsOpened] = useState(false);
const [selectedTypeSchema, setSelectedTypeSchema] = useState(null);
const [isEditView, setIsEditView] = useState(false);
const { setIsDrawerContentFixed, setDrawerLabel, isComplexTypeView, setIsComplexTypeView } = useContext(ParserOpenRPCContext);
const { formData, schema, name, onChange } = props;
const listItems = schema?.anyOf ? schema?.anyOf : schema?.oneOf;
const checkForNullTypeSchema = (type) => type === "null";
const showComplexTypeView = () => {
setDrawerLabel(name);
setIsDrawerContentFixed(true);
setIsEditView(true);
setIsComplexTypeView(true);
}
const onDropdownOptionClick = (e) => {
const selectedSchema = listItems.find(({ title }) => title === e.target.dataset.value);
const isNullTypeSchema = checkForNullTypeSchema(selectedSchema?.type);
if (isNullTypeSchema) {
onChange(null);
} else {
setSelectedTypeSchema(listItems.find(({ title }) => title === e.target.dataset.value));
showComplexTypeView();
}
setIsOpened(false);
}
const selectWidgetProps = {
...props,
schema: selectedTypeSchema,
label: name,
value: formData,
...(selectedTypeSchema?.enum && {
options:{
enumOptions: selectedTypeSchema?.enum.map(item => ({ label: item, value: item }))
}
})
}
const baseInputProps = {
...props,
schema: selectedTypeSchema
}

useEffect(() => {
if(!isComplexTypeView) {
setIsEditView(false);
setSelectedTypeSchema(null);
}
}, [isComplexTypeView])

return listItems?.length > 0 ? (
<>
<div className={styles.tableRow}>
<div className={styles.tableColumn}>
<label className={styles.tableColumnParam}>{name}</label>
</div>
<div className={styles.tableColumn}>
<div className={clsx(styles.tableValueRow, styles.tableValueRowPadding)}>
{formData === undefined ? "" : String(formData)}
<span className={styles.tableColumnType}>
<span className={styles.dropdown} onClick={() => { setIsOpened(!isOpened); }}>
{schema?.anyOf ? "anyOf" : "oneOf"}
<span className={clsx(styles.tableColumnIcon, styles.chevronIcon, styles.dropdownChevronIcon, !isOpened && styles.chevronIconDown)}/>
<span className={clsx(styles.chevronIcon, styles.dropdownChevronIcon, !isOpened && styles.chevronIconDown)}/>
</span>
<ul className={clsx(styles.dropdownList, !isOpened && styles.dropdownListClosed)}>
{listItems?.map((listItem, index) => (
<li
className={styles.dropdownItem}
key={index}
onClick={onDropdownOptionClick}
data-value={listItem.title}
>
{`${listItem.title}: ${listItem?.enum ? "enum" : listItem.type}`}
</li>
))}
</ul>
</span>
</div>
</div>
</div>
{isComplexTypeView && isEditView && selectedTypeSchema && selectedTypeSchema.type !== "null" ?
<div className={styles.tableComplexType}>
{selectedTypeSchema?.enum ? <SelectWidget {...selectWidgetProps} /> : <BaseInputTemplate {...baseInputProps} />}
</div>
: null
}
</>
) : null;
}
94 changes: 73 additions & 21 deletions src/components/ParserOpenRPC/InteractiveBox/index.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,44 @@
import React, { useEffect, useRef, useState } from "react";
import React, { useContext, useEffect, useRef, useState } from "react";
import Form from "@rjsf/core";
import clsx from "clsx";
import { RJSFSchema, UiSchema, RegistryWidgetsType } from "@rjsf/utils";
import {RJSFSchema, UiSchema, RegistryWidgetsType, RegistryFieldsType} from "@rjsf/utils";
import validator from "@rjsf/validator-ajv8";
import $RefParser from "@apidevtools/json-schema-ref-parser";
import { MethodExample, MethodParam, SchemaComponents } from "@site/src/components/ParserOpenRPC/interfaces";
import styles from "./styles.module.css";
import global from "../global.module.css";
import { BaseInputTemplate } from "@site/src/components/ParserOpenRPC/InteractiveBox/templates/BaseInputTemplate";
import { ArrayFieldTemplate } from "@site/src/components/ParserOpenRPC/InteractiveBox/templates/ArrayFieldTemplate";
import { ConditionalField } from "@site/src/components/ParserOpenRPC/InteractiveBox/fields/ConditionalField";
import { DropdownWidget } from "@site/src/components/ParserOpenRPC/InteractiveBox/widgets/DropdownWidget";
import { SelectWidget } from "@site/src/components/ParserOpenRPC/InteractiveBox/widgets/SelectWidget";
import { Tooltip } from "@site/src/components/ParserOpenRPC/Tooltip";
import { useColorMode } from "@docusaurus/theme-common";
import { ParserOpenRPCContext } from "@site/src/components/ParserOpenRPC";

interface InteractiveBoxProps {
params: MethodParam[];
components: SchemaComponents;
examples: MethodExample[];
onParamChange: (data) => void;
drawerLabel?: string | null
closeComplexTypeView?: () => void;
}

export default function InteractiveBox({ params, components, examples, onParamChange }: InteractiveBoxProps) {
export default function InteractiveBox({
params,
components,
examples,
onParamChange,
drawerLabel,
closeComplexTypeView
}:InteractiveBoxProps) {
const [parsedSchema, setParsedSchema] = useState<RJSFSchema>(null);
const [defaultFormData, setDefaultFormData] = useState<any>({});
const [isFormReseted, setIsFormReseted] = useState(false);
const formRef = useRef(null);
const { colorMode } = useColorMode();
const { isComplexTypeView } = useContext(ParserOpenRPCContext);

const defaultExampleFormData = examples ? Object.fromEntries(examples[0].params.map(({ name, value }) => [name, value])) : {};
const schema: RJSFSchema = {
Expand All @@ -40,10 +54,20 @@ export default function InteractiveBox({ params, components, examples, onParamCh
},
"ui:widget": "checkbox",
};
const templates = {
BaseInputTemplate,
ArrayFieldTemplate,
FieldErrorTemplate: () => null,
ErrorListTemplate: () => null,
}
const widgets: RegistryWidgetsType = {
CheckboxWidget: DropdownWidget,
SelectWidget: SelectWidget,
};
const fields: RegistryFieldsType = {
AnyOfField: ConditionalField,
OneOfField: ConditionalField,
};
const log = (type) => console.log.bind(console, type);
const handleResetForm = (e) => {
e.preventDefault();
setDefaultFormData(defaultExampleFormData);
Expand Down Expand Up @@ -76,8 +100,34 @@ export default function InteractiveBox({ params, components, examples, onParamCh

const onChangeHandler = (data) => {
onParamChange(data);
setDefaultFormData(data);
};

const cloneAndSetNullIfExists = (obj, key) => {
if (typeof obj !== 'object' || obj === null) return obj;
const newObj = Array.isArray(obj) ? [] : {};
for (let prop in obj) {
if (obj.hasOwnProperty(prop)) {
if (prop === key) {
newObj[prop] = [];
} else if (typeof obj[prop] === 'object' && obj[prop] !== null) {
newObj[prop] = cloneAndSetNullIfExists(obj[prop], key);
} else {
newObj[prop] = obj[prop];
}
}
}
return newObj;
}

const handleCancelClick = () => {
if (drawerLabel) {
const upData = cloneAndSetNullIfExists(defaultFormData, drawerLabel);
setDefaultFormData(upData)
}
closeComplexTypeView();
}

return parsedSchema ? (
<>
<div className={styles.tableHeadingRow}>
Expand All @@ -95,38 +145,40 @@ export default function InteractiveBox({ params, components, examples, onParamCh
validator={validator}
liveValidate
noHtml5Validate
onChange={(data) => {
onChangeHandler(data.formData);
setDefaultFormData(data.formData);
}}
onSubmit={() => {log("submitted");}}
onError={log("errors")}
templates={{
BaseInputTemplate,
ArrayFieldTemplate,
FieldErrorTemplate: () => null,
ErrorListTemplate: () => null,
}}
onChange={(data) => { onChangeHandler(data.formData); }}
templates={templates}
uiSchema={uiSchema}
widgets={widgets}
ref={formRef}
fields={fields}
>
<div className={clsx(styles.tableFooterRow, isLightTheme ? styles.tableFooterRowLight : styles.tableFooterRowDark)}>
<div className={styles.footerButtons}>
<Tooltip message="Reset fields" theme={isLightTheme ? "dark" : "light"}>
<button className={styles.footerButton} onClick={handleResetForm}>
<div className={clsx(styles.footerButtons, styles.footerButtonsLeft)}>
<Tooltip message="Reset fields">
<button className={styles.footerButtonLeft} onClick={handleResetForm}>
<img className={styles.footerButtonIcon} src="/img/icons/reset-icon.svg"/>
</button>
</Tooltip>
<Tooltip message="Clear fields" theme={isLightTheme ? "dark" : "light"}>
<Tooltip message="Clear fields">
<button
className={styles.footerButton}
className={styles.footerButtonLeft}
onClick={handleClearForm}
>
<img className={styles.footerButtonIcon} src="/img/icons/clear-icon.svg"/>
</button>
</Tooltip>
</div>
{isComplexTypeView ?
<div className={clsx(styles.footerButtons)}>
<button className={clsx(global.secondaryBtn, styles.footerButtonRight, styles.footerButtonRightOutline)} onClick={handleCancelClick}>
Cancel
</button>
<button className={clsx(global.primaryBtn, styles.footerButtonRight)} onClick={closeComplexTypeView}>
Save
</button>
</div> :
null
}
</div>
</Form>
</>
Expand Down
Loading

0 comments on commit c4c3e73

Please sign in to comment.