Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(docs): ACT-1360 - Components For Complex Types #1370

Merged
merged 30 commits into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
1622ec2
feat(docs): added rpc-parser component for reference pages
TrofimovAnton85 Jun 4, 2024
91e4c73
feat(docs): added mm install msg
TrofimovAnton85 Jun 4, 2024
6fcb4e3
feat(docs): change mm condition
TrofimovAnton85 Jun 4, 2024
f5f83fd
feat(docs): refactored styles
TrofimovAnton85 Jun 5, 2024
65909aa
feat(docs): added request/response section
TrofimovAnton85 Jun 5, 2024
8ef761b
feat(docs): added error section
TrofimovAnton85 Jun 6, 2024
dccba1c
feat(docs): added eth_call
TrofimovAnton85 Jun 6, 2024
b9f308f
added default logic for InteractiveBox
aednikanov Jun 18, 2024
b26ed1b
Merge branch 'main' into ACT-1359-add-params-table
aednikanov Jun 18, 2024
3280670
Made enhancements for components
aednikanov Jun 19, 2024
4d56f44
returned condition for disabled button
aednikanov Jun 19, 2024
d708ed3
removed not needed styles in Drawer
aednikanov Jun 19, 2024
b4b3bdb
started implementing of handling complex types
aednikanov Jun 20, 2024
e58c3be
Merge branch 'main' into ACT-1360-components-for-complex-types
aednikanov Jun 21, 2024
53b88b8
made some progress
aednikanov Jun 25, 2024
36e2edd
Merge branch 'main' into ACT-1360-components-for-complex-types
aednikanov Jun 25, 2024
cb51841
added UI and logic for complex array
aednikanov Jun 27, 2024
10fe4de
Merge branch 'main' into ACT-1360-components-for-complex-types
aednikanov Jun 27, 2024
df8f609
added label clear on cancel and back
aednikanov Jun 27, 2024
803ee2b
fixed styles for chevron icon
aednikanov Jun 27, 2024
af514a8
feat(docs): added cancel func for complex types
TrofimovAnton85 Jun 27, 2024
bce1631
implemented complex types except object
aednikanov Jul 2, 2024
797c236
Merge branch 'main' into ACT-1360-components-for-complex-types
aednikanov Jul 2, 2024
0e640e2
redesigned drawer
aednikanov Jul 2, 2024
b9ee669
fixed some issues
aednikanov Jul 2, 2024
104a6cd
improved some styles
aednikanov Jul 3, 2024
356070a
fixed a bug
aednikanov Jul 3, 2024
0470d9a
fixed icon color for theme change
aednikanov Jul 3, 2024
2d9ca72
fixed code style
aednikanov Jul 3, 2024
d73ef0d
fixed code style #2
aednikanov Jul 3, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,95 @@
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.innerText);
const isNullTypeSchema = checkForNullTypeSchema(selectedSchema?.type);
if (isNullTypeSchema) {
onChange(null);
} else {
setSelectedTypeSchema(listItems.find(({ title }) => title === e.target.innerText));
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(({ title }, index) => (
<li
className={styles.dropdownItem}
key={index}
onClick={onDropdownOptionClick}
>
{String(title)}
</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;
}
90 changes: 71 additions & 19 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}>
<div className={clsx(styles.footerButtons, styles.footerButtonsLeft)}>
<Tooltip message="Reset fields" theme={isLightTheme ? "dark" : "light"}>
<button className={styles.footerButton} onClick={handleResetForm}>
<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"}>
<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(styles.footerButtonRight, styles.footerButtonRightOutline)} onClick={handleCancelClick}>
Cancel
</button>
<button className={clsx(global.primaryBtn, styles.footerButtonRight)} onClick={closeComplexTypeView}>
Back
</button>
</div> :
null
}
</div>
</Form>
</>
Expand Down
Loading