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: return React Component instead of render function in layout provider's getLayout #2605

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"request": "launch",
"preLaunchTask": "Start web dev server",
"url": "http://127.0.0.1:9000",
"webRoot": "${workspaceFolder}",
"webRoot": "${workspaceFolder}/web",
"sourceMaps": true,
"trace": true,
"outFiles": [
Expand Down Expand Up @@ -184,4 +184,4 @@
}
}
]
}
}
41 changes: 22 additions & 19 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,63 +1,59 @@
{
// Angular
"vsicons.presets.angular": true,

// CSS
"css.validate": false,
"scss.validate": false,

// Default Formatting
"editor.tabSize": 4,
"editor.detectIndentation": false,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": false,

// Per Language Formatting
// Keep this up to date with .prettierignore
// Run Prettier first, followed by ESLint
"[javascript]": {
"editor.formatOnSave": true,
"editor.codeActionsOnSave": [
"source.formatDocument",
"source.fixAll.eslint"
"source.formatDocument",
"source.fixAll.eslint"
]
},
"[typescript]": {
"editor.formatOnSave": true,
"editor.codeActionsOnSave": [
"source.formatDocument",
"source.fixAll.eslint"
"source.formatDocument",
"source.fixAll.eslint"
]
},
"[typescriptreact]": {
"editor.formatOnSave": true,
"editor.codeActionsOnSave": [
"source.formatDocument",
"source.fixAll.eslint"
"source.formatDocument",
"source.fixAll.eslint"
]
},
"[json]": {
"editor.formatOnSave": true,
"editor.codeActionsOnSave": [
"source.formatDocument",
"source.fixAll.eslint"
"source.formatDocument",
"source.fixAll.eslint"
]
},
"[jsonc]": {
"editor.formatOnSave": true,
"editor.codeActionsOnSave": [
"source.formatDocument",
"source.fixAll.eslint"
"source.formatDocument",
"source.fixAll.eslint"
]
},

// Python
"python.autoComplete.extraPaths": ["${workspaceRoot}/python"],
"python.autoComplete.extraPaths": [
"${workspaceRoot}/python"
],
"python.formatting.provider": "yapf",

// Typescript
"typescript.tsdk": "node_modules/typescript/lib",

// General
"search.exclude": {
"**/node_modules": true,
Expand All @@ -70,7 +66,14 @@
"desktop/dll/**": true
},
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true,
"**/Thumbs.db": true,
"**/__pycache__/**/*": true,
".awcache/**": true
".awcache/**": true,
"**/node_modules": true
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,11 @@ export const ActionFormDemo: React.FC = () => {
>
<MonacoEditor
controllerRef={controllerRef}
value={JSON.stringify(actions[action].form.values)}
value={JSON.stringify(
actions[action].form.values,
undefined,
4
)}
onChange={editorChangeHandler}
onChangeDelay={20}
language="json"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ export const MonacoEditor: React.FC<MonacoEditorImplProps> = (props) => {
if (props.controllerRef) {
controllerRef = props.controllerRef;
}
const onChangeRef = React.useRef(props.onChange);
onChangeRef.current = props.onChange;

const { value = "" } = props;

Expand All @@ -121,7 +123,12 @@ export const MonacoEditor: React.FC<MonacoEditorImplProps> = (props) => {

const model = controllerRef.current.model;

editor = _createEditor(model, props, containerRef.current);
editor = _createEditor(
model,
props,
containerRef.current,
onChangeRef
);
onWindowResize = () => {
if (editor) {
editor.layout();
Expand Down Expand Up @@ -160,7 +167,8 @@ export const MonacoEditor: React.FC<MonacoEditorImplProps> = (props) => {
function _createEditor(
model: monaco.editor.ITextModel,
props: MonacoEditorImplProps,
containerEl: HTMLElement
containerEl: HTMLElement,
onChangeRef: React.MutableRefObject<((value: string) => void) | undefined>
): monaco.editor.IStandaloneCodeEditor {
const editor = monaco.editor.create(containerEl, {
theme: props.theme,
Expand All @@ -169,11 +177,11 @@ function _createEditor(
});

let debouncedOnChange: DebouncedFunction<() => void> | undefined;
if (props.onChange) {
if (onChangeRef.current) {
debouncedOnChange = debounce(
() => {
if (props.onChange) {
props.onChange(model.getValue());
if (onChangeRef.current) {
onChangeRef.current(model.getValue());
}
},
props.onChangeDelay ?? defaultOnChangeDelay,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,5 +70,7 @@ export const FormContainer = <V extends FormValues>(
};
}, [form, formValidateHandler]);

return getBrowserEnvironment().getFormLayout(layout).render(form, buttons);
const LayoutComp = getBrowserEnvironment().getFormLayout<V>(layout);

return <LayoutComp form={form} button={buttons} />;
};
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import { FormLayout, FormLayoutProvider, FormLayoutType } from "./form-layout";
import { ListFormLayout } from "./list-form-layout";
import { FormValues } from "@batch/ui-common/lib/form";
import React from "react";
import { FormLayoutProvider, FormLayoutType, LayoutProps } from "./form-layout";
import { ListFormLayoutComp } from "./list-form-layout";

export class DefaultFormLayoutProvider implements FormLayoutProvider {
getLayout(layout: FormLayoutType): FormLayout {
getLayout<V extends FormValues>(
layout: FormLayoutType
): React.FC<LayoutProps<V>> {
if (layout === "list") {
return new ListFormLayout();
return ListFormLayoutComp;
} else if (layout === "steps") {
// TODO: Make a layout where each top-level section is a 'step'
// in the form.
return new ListFormLayout();
return ListFormLayoutComp;
}
throw new Error(`Invalid form layout type: ${layout}`);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,14 @@ import { FormButton } from "./form-container";
export type FormLayoutType = "list" | "steps";

export interface FormLayoutProvider {
getLayout(layout: FormLayoutType): FormLayout;
getLayout<V extends FormValues>(
layout: FormLayoutType
): React.FC<LayoutProps<V>>;
}

export interface LayoutProps<V extends FormValues> {
form: Form<V>;
button?: FormButton[];
}

export interface FormLayout {
Expand Down
75 changes: 37 additions & 38 deletions packages/react/src/ui-react/components/form/list-form-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,49 +9,48 @@ import { getBrowserEnvironment } from "../../environment";
import { useUniqueId } from "../../hooks";
import { useAppTheme } from "../../theme";
import { FormButton } from "./form-container";
import { FormLayout } from "./form-layout";

/**
* Render a form as a flat list.
* React Component to render a form as a flat list.
*/
export class ListFormLayout implements FormLayout {
render<V extends FormValues>(
form: Form<V>,
buttons?: FormButton[]
): JSX.Element {
const rows: JSX.Element[] = [];
this._renderChildEntries(form.childEntries(), rows);

return (
<div role="form" style={{ maxWidth: "480px" }}>
<h2 style={{ marginBottom: "16px" }}>
{form.title ?? "Untitled form"}
</h2>
<Stack tokens={{ childrenGap: 8 }}>{rows}</Stack>
<Stack tokens={{ childrenGap: 8 }}>
<ButtonContainer buttons={buttons} />
</Stack>
</div>
);
}
export function ListFormLayoutComp<V extends FormValues>(props: {
form: Form<V>;
buttons?: FormButton[];
}): JSX.Element {
const { form, buttons } = props;
const rows: JSX.Element[] = [];
_renderChildEntries(form.childEntries(), rows);

return (
<div role="form" style={{ maxWidth: "480px" }}>
<h2 style={{ marginBottom: "16px" }}>
{form.title ?? "Untitled form"}
</h2>
<Stack tokens={{ childrenGap: 8 }}>{rows}</Stack>
<Stack tokens={{ childrenGap: 8 }}>
<ButtonContainer buttons={buttons} />
</Stack>
</div>
);
}

private _renderChildEntries<V extends FormValues>(
entries: IterableIterator<Entry<V>>,
rows: JSX.Element[]
) {
for (const entry of entries) {
if (entry instanceof Parameter) {
rows.push(<ParameterRow key={entry.name} param={entry} />);
} else if (entry instanceof Section) {
rows.push(<SectionTitle key={entry.name} section={entry} />);
if (entry.childEntriesCount > 0) {
this._renderChildEntries(entry.childEntries(), rows);
}
} else if (entry instanceof SubForm) {
rows.push(<SubFormTitle key={entry.name} subForm={entry} />);
if (entry.childEntriesCount > 0) {
this._renderChildEntries(entry.childEntries(), rows);
}
function _renderChildEntries<V extends FormValues>(
entries: IterableIterator<Entry<V>>,
rows: JSX.Element[]
) {
for (const entry of entries) {
if (entry instanceof Parameter) {
rows.push(<ParameterRow key={entry.name} param={entry} />);
} else if (entry instanceof Section) {
rows.push(<SectionTitle key={entry.name} section={entry} />);
if (entry.childEntriesCount > 0) {
_renderChildEntries(entry.childEntries(), rows);
}
} else if (entry instanceof SubForm) {
rows.push(<SubFormTitle key={entry.name} subForm={entry} />);
if (entry.childEntriesCount > 0) {
_renderChildEntries(entry.childEntries(), rows);
}
}
}
Expand Down
11 changes: 8 additions & 3 deletions packages/react/src/ui-react/environment/browser-environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ import {
} from "@batch/ui-common/lib/environment";
import { FormValues } from "@batch/ui-common/lib/form";
import { StorageAccountService, SubscriptionService } from "@batch/ui-service";
import React from "react";
import {
FormLayout,
FormLayoutProvider,
FormLayoutType,
LayoutProps,
} from "../components/form/form-layout";
import {
FormControlOptions,
Expand All @@ -35,7 +36,9 @@ export interface BrowserEnvironment
opts?: FormControlOptions
): JSX.Element;

getFormLayout(layoutType?: FormLayoutType): FormLayout;
getFormLayout<V extends FormValues>(
layoutType?: FormLayoutType
): React.FC<LayoutProps<V>>;
}

export interface BrowserEnvironmentConfig extends EnvironmentConfig {
Expand Down Expand Up @@ -90,7 +93,9 @@ export class DefaultBrowserEnvironment
/**
* Get the form control for a given parameter
*/
getFormLayout(layoutType: FormLayoutType = "list"): FormLayout {
getFormLayout<V extends FormValues>(
layoutType: FormLayoutType = "list"
): React.FC<LayoutProps<V>> {
const provider = this.getInjectable<FormLayoutProvider>(
BrowserDependencyName.FormLayoutProvider
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { Parameter } from "@batch/ui-common";
import { MockEnvironment } from "@batch/ui-common/lib/environment";
import { FormValues } from "@batch/ui-common/lib/form";
import React from "react";
import { FormControlOptions, ParameterTypeResolver } from "../components/form";
import {
FormLayout,
FormLayoutProvider,
FormLayoutType,
LayoutProps,
} from "../components/form/form-layout";
import {
BrowserDependencyName,
Expand Down Expand Up @@ -53,7 +54,9 @@ export class MockBrowserEnvironment
}

// TODO: This code shouldn't need to be duplicated from DefaultBrowserEnvironment
getFormLayout(layoutType: FormLayoutType = "list"): FormLayout {
getFormLayout<V extends FormValues>(
layoutType: FormLayoutType = "list"
): React.FC<LayoutProps<V>> {
const provider = this.getInjectable<FormLayoutProvider>(
BrowserDependencyName.FormLayoutProvider
);
Expand Down
Loading