Skip to content

Commit 3b38e52

Browse files
Meriem-BenIsmailpre-commit-ci[bot]github-actions[bot]
authored
Replace @jupyterlab/rjsf with FormComponent from @jupyterlab/ui-components (#625)
* added form component * custom array field * custom form field validation * css color picker * removed lumino * array field error validation * handle different types of input in custom array fields. * eslint * ui test update * input step option added * input step option added * updated schema * rebase on main * removed unused properties. * hide additional properties fields. * css edit * css for required span. * submit form with enter. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update Playwright Snapshots --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent 872bc06 commit 3b38e52

File tree

11 files changed

+434
-801
lines changed

11 files changed

+434
-801
lines changed

packages/base/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535
"watch": "tsc -w"
3636
},
3737
"dependencies": {
38-
"@deathbeds/jupyterlab-rjsf": "^1.1.0",
3938
"@jupyter/collaborative-drive": "^3.1.0-alpha.0",
4039
"@jupyter/ydoc": "^3.0.0",
4140
"@jupytercad/occ-worker": "^3.0.0",
@@ -51,7 +50,7 @@
5150
"@jupyterlab/observables": "^5.0.0",
5251
"@jupyterlab/services": "^7.0.0",
5352
"@jupyterlab/translation": "^4.0.0",
54-
"@jupyterlab/ui-components": "^4.0.0",
53+
"@jupyterlab/ui-components": "^4.3.1",
5554
"@lumino/commands": "^2.0.0",
5655
"@lumino/coreutils": "^2.0.0",
5756
"@lumino/messaging": "^2.0.0",
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import React from 'react';
2+
3+
interface IProps {
4+
formData?: any[];
5+
name: string;
6+
required: boolean;
7+
schema: any;
8+
errorSchema?: { [key: string]: any };
9+
onChange: (updatedValue: any[]) => void;
10+
onBlur: (name: string, value: any) => void;
11+
}
12+
13+
const CustomArrayField: React.FC<IProps> = props => {
14+
const {
15+
formData = [],
16+
name,
17+
required,
18+
schema,
19+
errorSchema = {},
20+
onChange,
21+
onBlur
22+
} = props;
23+
24+
const handleInputChange = (index: number, value: any) => {
25+
const updatedValue = [...formData];
26+
updatedValue[index] = value;
27+
onChange(updatedValue);
28+
};
29+
30+
const renderInputField = (value: any, index: number) => {
31+
const { enum: enumOptions, type: itemType } = schema.items || {};
32+
if (enumOptions) {
33+
return (
34+
<select
35+
value={value || ''}
36+
required={required}
37+
onChange={e => handleInputChange(index, e.target.value)}
38+
onBlur={() => onBlur(name, value)}
39+
>
40+
{enumOptions.map((option: string, i: number) => (
41+
<option key={i} value={option}>
42+
{option}
43+
</option>
44+
))}
45+
</select>
46+
);
47+
} else if (itemType === 'number') {
48+
return (
49+
<input
50+
type="number"
51+
value={value}
52+
step="any"
53+
required={required}
54+
onChange={e =>
55+
handleInputChange(
56+
index,
57+
e.target.value === '' ? null : parseFloat(e.target.value)
58+
)
59+
}
60+
onBlur={() => onBlur(name, value)}
61+
/>
62+
);
63+
} else if (itemType === 'boolean') {
64+
return (
65+
<input
66+
type="checkbox"
67+
checked={!!value}
68+
onChange={e => handleInputChange(index, e.target.checked)}
69+
onBlur={() => onBlur(name, value)}
70+
/>
71+
);
72+
} else {
73+
return (
74+
<input
75+
type="text"
76+
value={value}
77+
required={required}
78+
onChange={e => handleInputChange(index, e.target.value)}
79+
onBlur={() => onBlur(name, value)}
80+
/>
81+
);
82+
}
83+
};
84+
85+
return (
86+
<fieldset>
87+
<legend>{name}</legend>
88+
<p className="field-description">{schema.description}</p>
89+
<div className="custom-array-wrapper">
90+
{formData.map((value: any, index: number) => (
91+
<div key={index} className="array-item">
92+
{renderInputField(value, index)}
93+
94+
{errorSchema?.[index]?.__errors?.length > 0 && (
95+
<div className="validationErrors">
96+
{errorSchema?.[index]?.__errors.map(
97+
(error: string, errorIndex: number) => (
98+
<div key={`${index}-${errorIndex}`} className="error">
99+
{error}
100+
</div>
101+
)
102+
)}
103+
</div>
104+
)}
105+
</div>
106+
))}
107+
</div>
108+
</fieldset>
109+
);
110+
};
111+
112+
export default CustomArrayField;

packages/base/src/panelview/formbuilder.tsx

Lines changed: 66 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
import { SchemaForm } from '@deathbeds/jupyterlab-rjsf';
2-
import { MessageLoop } from '@lumino/messaging';
3-
import { Widget } from '@lumino/widgets';
41
import { ISubmitEvent } from '@rjsf/core';
52
import * as React from 'react';
6-
3+
import { FormComponent } from '@jupyterlab/ui-components';
4+
import validatorAjv8 from '@rjsf/validator-ajv8';
75
import { IDict } from '../types';
6+
import CustomArrayField from './customarrayfield';
87

98
interface IStates {
109
internalData?: IDict;
@@ -24,33 +23,18 @@ interface IProps {
2423
cancel?: () => void;
2524
}
2625

27-
// Reusing the datalayer/jupyter-react component:
28-
// https://github.com/datalayer/jupyter-react/blob/main/packages/react/src/jupyter/lumino/Lumino.tsx
29-
export const LuminoSchemaForm = (
30-
props: React.PropsWithChildren<any>
31-
): JSX.Element => {
32-
const ref = React.useRef<HTMLDivElement>(null);
33-
const { children } = props;
34-
React.useEffect(() => {
35-
const widget = children as SchemaForm;
36-
try {
37-
MessageLoop.sendMessage(widget, Widget.Msg.BeforeAttach);
38-
ref.current!.insertBefore(widget.node, null);
39-
MessageLoop.sendMessage(widget, Widget.Msg.AfterAttach);
40-
} catch (e) {
41-
console.warn('Exception while attaching Lumino widget.', e);
42-
}
43-
return () => {
44-
try {
45-
if (widget.isAttached || widget.node.isConnected) {
46-
Widget.detach(widget);
47-
}
48-
} catch (e) {
49-
// The widget is destroyed already by React.
50-
}
51-
};
52-
}, [children]);
53-
return <div ref={ref} />;
26+
const WrappedFormComponent = (props: any): JSX.Element => {
27+
const { fields, ...rest } = props;
28+
return (
29+
<FormComponent
30+
{...rest}
31+
validator={validatorAjv8}
32+
fields={{
33+
...fields,
34+
ArrayField: CustomArrayField
35+
}}
36+
/>
37+
);
5438
};
5539

5640
export class ObjectPropertiesForm extends React.Component<IProps, IStates> {
@@ -76,10 +60,14 @@ export class ObjectPropertiesForm extends React.Component<IProps, IStates> {
7660
);
7761
};
7862

79-
componentDidUpdate(prevProps: IProps, prevState: IStates): void {
63+
componentDidUpdate(prevProps: IProps): void {
8064
if (prevProps.sourceData !== this.props.sourceData) {
8165
this.setState(old => ({ ...old, internalData: this.props.sourceData }));
8266
}
67+
68+
if (prevProps.schema !== this.props.schema) {
69+
this.setState(old => ({ ...old, schema: this.props.schema }));
70+
}
8371
}
8472

8573
buildForm(): JSX.Element[] {
@@ -159,33 +147,54 @@ export class ObjectPropertiesForm extends React.Component<IProps, IStates> {
159147

160148
const submitRef = React.createRef<HTMLButtonElement>();
161149

162-
const formSchema = new SchemaForm(schema ?? {}, {
163-
liveValidate: true,
164-
formData: this.state.internalData,
165-
onSubmit: this.onFormSubmit,
166-
onFocus: (id, value) => {
167-
this.props.syncSelectedField
168-
? this.props.syncSelectedField(id, value, this.props.parentType)
169-
: null;
170-
},
171-
onBlur: (id, value) => {
172-
this.props.syncSelectedField
173-
? this.props.syncSelectedField(null, value, this.props.parentType)
174-
: null;
175-
},
176-
uiSchema: this.generateUiSchema(this.props.schema),
177-
children: (
178-
<button ref={submitRef} type="submit" style={{ display: 'none' }} />
179-
)
180-
});
181150
return (
182151
<div
183152
className="jpcad-property-panel"
184153
data-path={this.props.filePath ?? ''}
185154
>
186-
<div className="jpcad-property-outer jp-scrollbar-tiny">
187-
<LuminoSchemaForm>{formSchema}</LuminoSchemaForm>
155+
<div
156+
className="jpcad-property-outer jp-scrollbar-tiny"
157+
onKeyUp={(e: React.KeyboardEvent) => {
158+
if (e.key === 'Enter') {
159+
e.preventDefault();
160+
submitRef.current?.click();
161+
}
162+
}}
163+
>
164+
<WrappedFormComponent
165+
schema={schema}
166+
uiSchema={this.generateUiSchema(this.props.schema)}
167+
formData={this.state.internalData}
168+
onSubmit={this.onFormSubmit}
169+
liveValidate
170+
onFocus={(id, value) => {
171+
this.props.syncSelectedField
172+
? this.props.syncSelectedField(
173+
id,
174+
value,
175+
this.props.parentType
176+
)
177+
: null;
178+
}}
179+
onBlur={(id, value) => {
180+
this.props.syncSelectedField
181+
? this.props.syncSelectedField(
182+
null,
183+
value,
184+
this.props.parentType
185+
)
186+
: null;
187+
}}
188+
children={
189+
<button
190+
ref={submitRef}
191+
type="submit"
192+
style={{ display: 'none' }}
193+
/>
194+
}
195+
/>
188196
</div>
197+
189198
<div className="jpcad-property-buttons">
190199
{this.props.cancel ? (
191200
<button
@@ -198,7 +207,10 @@ export class ObjectPropertiesForm extends React.Component<IProps, IStates> {
198207

199208
<button
200209
className="jp-Dialog-button jp-mod-accept jp-mod-styled"
201-
onClick={() => submitRef.current?.click()}
210+
type="button"
211+
onClick={() => {
212+
submitRef.current?.click();
213+
}}
202214
>
203215
<div className="jp-Dialog-buttonLabel">Submit</div>
204216
</button>

0 commit comments

Comments
 (0)