Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
7cdd8c1
Generator with no templates enforced. Also router POC
silviogutierrez May 10, 2022
3a98a07
List is working
silviogutierrez May 10, 2022
893d3ef
Update food is working
silviogutierrez May 10, 2022
154a329
Generating update/list is working. Create needs testing
silviogutierrez May 12, 2022
fb83d5a
Handle form serialization
silviogutierrez May 14, 2022
7610d51
Remove nulls
silviogutierrez May 16, 2022
4dbdced
Hooks working
silviogutierrez May 17, 2022
03b2d8f
Hook work
silviogutierrez May 20, 2022
aa563c5
Render props start
silviogutierrez May 20, 2022
25bb22d
Actions and tabs working
silviogutierrez May 20, 2022
2decb5a
Create view
silviogutierrez May 23, 2022
5399dc6
More typing and stuff
silviogutierrez May 23, 2022
b37a80c
Add locator to children
silviogutierrez May 24, 2022
ceca578
Context retrieve
silviogutierrez May 25, 2022
3e42ba7
Processing of RPC and withcomponent
silviogutierrez May 27, 2022
2b951c7
Tests , typing, formset and more
silviogutierrez May 28, 2022
710fc60
Form groups are working, backend only
silviogutierrez May 28, 2022
d178925
Creation works
silviogutierrez May 31, 2022
c50cc07
Contact is sort of working
silviogutierrez Jun 2, 2022
007116e
Fix hooks
silviogutierrez Jun 3, 2022
517cd10
Extendable tab definition
silviogutierrez Jun 4, 2022
3dd3268
Prop injection
silviogutierrez Jun 6, 2022
e7f9835
forms and redux injection
silviogutierrez Jun 18, 2022
528d7d3
Fix forms and add password input
silviogutierrez Jun 21, 2022
d56c4f0
Keyed-hooks, ripe for a refactor
silviogutierrez Jun 23, 2022
927f980
Transition and await
silviogutierrez Jun 23, 2022
3162004
Fix hooks
silviogutierrez Jun 23, 2022
ca0dfdb
ID for messages
silviogutierrez Jun 24, 2022
d6996b8
Handle no arg RPC
silviogutierrez Jun 25, 2022
02a3299
Related context
silviogutierrez Jul 6, 2022
a34a27b
static form serialization
silviogutierrez Jul 7, 2022
4af42a4
Form tweaks
silviogutierrez Jul 9, 2022
79163fc
Init form group
silviogutierrez Jul 10, 2022
664213e
Some form group work
silviogutierrez Jul 13, 2022
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
3 changes: 2 additions & 1 deletion .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ exclude =
.git,
__pycache__,
.venv
node_modules
node_modules,
reactivated/rpc.py
147 changes: 106 additions & 41 deletions packages/reactivated/forms/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import produce, {castDraft} from "immer";
import {produce, castDraft} from "immer";
import {FileInfoResult} from "prettier";
import React from "react";

Expand Down Expand Up @@ -79,6 +79,7 @@ export interface FormHandler<T extends FieldMap> {
nonFieldErrors: string[] | null;

setValue: <K extends keyof T>(name: K, value: FormValues<T>[K]) => void;
setValues: (values: FormValues<T>) => void;
setErrors: (errors: FormErrors<T>) => void;
iterate: (
iterator: Array<Extract<keyof T, string>>,
Expand Down Expand Up @@ -116,10 +117,9 @@ export const getInitialFormState = <T extends FieldMap>(form: FormLike<T>) => {

export const getInitialFormSetState = <T extends FieldMap>(
forms: Array<FormLike<T>>,
initial?: FormValues<T>[],
) => {
return Object.fromEntries(
forms.map((form) => [form.prefix, getInitialFormState(form)] as const),
);
return forms.map((form, index) => initial?.[index] ?? getInitialFormState(form));
};

export const getInitialFormSetErrors = <T extends FieldMap>(
Expand Down Expand Up @@ -311,6 +311,9 @@ export const getFormHandler = <T extends FieldMap>({
iterate,
reset,
setErrors,
setValues: (values) => {
setValues(() => values);
},
setValue: (fieldName, value) => {
changeValues(fieldName, (prevValues) => ({
...prevValues,
Expand All @@ -320,23 +323,34 @@ export const getFormHandler = <T extends FieldMap>({
};
};

export const useForm = <T extends FieldMap>({
form,
...options
}: {
export const useForm = <
T extends FieldMap,
S extends Array<keyof T> = [],
R extends {[P in Exclude<keyof T, S[number]>]: T[P]} = {
[P in Exclude<keyof T, S[number]>]: T[P];
},
>(options: {
form: FormLike<T>;
initial?: FormValues<R>;
exclude?: [...S];
fieldInterceptor?: (
fieldName: keyof T,
field: FieldHandler<T[keyof T]["widget"]>,
values: FormValues<T>,
fieldName: keyof R,
field: FieldHandler<R[keyof R]["widget"]>,
values: FormValues<R>,
) => typeof field;
changeInterceptor?: (
name: keyof T,
prevValues: FormValues<T>,
nextValues: FormValues<T>,
) => FormValues<T>;
}): FormHandler<T> => {
const initial = getInitialFormState(form);
name: keyof R,
prevValues: FormValues<R>,
nextValues: FormValues<R>,
) => FormValues<R>;
}): FormHandler<R> => {
const form = {
...(options.form as any as FormLike<R>),
iterator: options.form.iterator.filter(
(field) => options.exclude == null || !options.exclude.includes(field),
),
} as any as FormLike<R>;
const initial = options.initial ?? getInitialFormState(form);
const [values, formSetValues] = React.useState(initial);
const [errors, setErrors] = React.useState(form.errors);

Expand Down Expand Up @@ -492,11 +506,19 @@ export const Widget = (props: {field: FieldHandler<widgets.CoreWidget>}) => {
onChange={field.handler}
/>
);
} else if (field.tag === "django.forms.widgets.PasswordInput") {
return (
<widgets.TextInput
name={field.name}
value={field.value}
onChange={field.handler}
type="password"
/>
);
} else if (
field.tag === "django.forms.widgets.TextInput" ||
field.tag === "django.forms.widgets.DateInput" ||
field.tag === "django.forms.widgets.URLInput" ||
field.tag === "django.forms.widgets.PasswordInput" ||
field.tag === "django.forms.widgets.EmailInput" ||
field.tag === "django.forms.widgets.TimeInput" ||
field.tag === "django.forms.widgets.NumberInput"
Expand All @@ -506,6 +528,7 @@ export const Widget = (props: {field: FieldHandler<widgets.CoreWidget>}) => {
name={field.name}
value={field.value}
onChange={field.handler}
placeholder={field.widget.attrs.placeholder}
/>
);
} else if (field.tag === "django.forms.widgets.Select") {
Expand Down Expand Up @@ -573,6 +596,7 @@ export const ManagementForm = <T extends FieldMap>({
export const useFormSet = <T extends FieldMap>(options: {
formSet: FormSetLike<T>;
onAddForm?: (form: FormLike<T>) => void;
initial?: FormValues<T>[];
fieldInterceptor?: (
fieldName: keyof T,
field: FieldHandler<T[keyof T]["widget"]>,
Expand All @@ -584,14 +608,39 @@ export const useFormSet = <T extends FieldMap>(options: {
nextValues: FormValues<T>,
) => FormValues<T>;
}) => {
const [formSet, setFormSet] = React.useState(options.formSet);

const initialFormSetState = getInitialFormSetState(options.formSet.forms);
const createForm = (index: number) => {
return produce(options.formSet.empty_form, (draftState) => {
for (const fieldName of draftState.iterator) {
const prefix = `${options.formSet.prefix}-${index}`;
const field = draftState.fields[fieldName];
const htmlName = `${prefix}-${field.name}`;
draftState.fields[fieldName].widget.name = htmlName;
draftState.fields[fieldName].widget.attrs.id = `id_${htmlName}`;
draftState.prefix = prefix;
}
});
}

const formSetFromInitialValues = produce(options.formSet, (draftState) => {
if (options.initial == null) {
return;
}
draftState.total_form_count = options.initial.length;
draftState.forms = options.initial.map((_, index) => {
return castDraft(createForm(index));
})
});


const [formSet, setFormSet] = React.useState(formSetFromInitialValues);

const initialFormSetState = getInitialFormSetState(formSetFromInitialValues.forms, options.initial);
const initialFormSetErrors = getInitialFormSetErrors(options.formSet.forms);
const [values, formSetSetValues] =
React.useState<Partial<typeof initialFormSetState>>(initialFormSetState);
React.useState<typeof initialFormSetState>(initialFormSetState);
const [errors, formSetSetErrors] =
React.useState<Partial<typeof initialFormSetErrors>>(initialFormSetErrors);
React.useState<typeof initialFormSetErrors>(initialFormSetErrors);

const emptyFormValues = getInitialFormState(formSet.empty_form);

Expand All @@ -600,43 +649,33 @@ export const useFormSet = <T extends FieldMap>(options: {
form,
changeInterceptor: options.changeInterceptor,
fieldInterceptor: options.fieldInterceptor,
values: values[form.prefix] ?? emptyFormValues,
errors: errors[form.prefix] ?? {},
values: values[index] ?? emptyFormValues,
errors: errors[index] ?? {},
setErrors: (nextErrors) => {
formSetSetErrors((prevErrors) => ({
...prevErrors,
[form.prefix]: nextErrors,
[index]: nextErrors,
}));
},
initial: initialFormSetState[index] ?? emptyFormValues,
setValues: (getValuesToSetFromPrevValues) => {
formSetSetValues((prevValues) => {
const nextValues = getValuesToSetFromPrevValues(
prevValues[form.prefix] ?? emptyFormValues,
prevValues[index] ?? emptyFormValues,
);
return {
...prevValues,
[form.prefix]: nextValues,
};
return produce(prevValues, (draftState) => {
draftState[index] = castDraft(nextValues);
});
});
},
});
});

const addForm = () => {
const {total_form_count} = formSet;
type AdditionalForm = typeof formSet["forms"][number];

const extraForm = produce(formSet.empty_form, (draftState) => {
for (const fieldName of draftState.iterator) {
const prefix = `${formSet.prefix}-${formSet.total_form_count}`;
const field = draftState.fields[fieldName];
const htmlName = `${prefix}-${field.name}`;
draftState.fields[fieldName].widget.name = htmlName;
draftState.fields[fieldName].widget.attrs.id = `id_${htmlName}`;
draftState.prefix = prefix;
}
});
const extraForm = createForm(formSet.total_form_count);

const updated = produce(formSet, (draftState) => {
draftState.forms.push(castDraft(extraForm));
draftState.total_form_count += 1;
Expand Down Expand Up @@ -767,3 +806,29 @@ export const FormSet = <T extends FieldMap<widgets.CoreWidget>>(props: {
</>
);
};

export type UnknownFormValues<T extends FieldMap> = {
[K in keyof T]: T[K] extends {enum: unknown}
? T[K]["enum"] | null
: unknown;
}

// TODO: Should be T extends Record<string, FormLike<any> | FormSetLike<any>>
// but jsonschema outputs interfaces instead of types. Figure out how to output a type.
export type FormOrFormSetValues<T> = T extends {tag: "FormGroup"}
? Omit<{[K in keyof T]: FormOrFormSetValues<T[K]>}, "tag">
: T extends FormLike<any>
? UnknownFormValues<T["fields"]>
: T extends FormSetLike<any>
? Array<UnknownFormValues<T["empty_form"]["fields"]>>
: T extends null ? null
: never;

export type FormOrFormSetErrors<T> = T extends {tag: "FormGroup"}
? Omit<{[K in keyof T]: FormOrFormSetErrors<T[K]>}, "tag">
: T extends FormLike<any>
? NonNullable<T["errors"]>
: T extends FormSetLike<any>
? Array<NonNullable<T["empty_form"]["errors"]>>
: T extends null ? null
: never;
5 changes: 4 additions & 1 deletion packages/reactivated/forms/widgets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,15 @@ export const TextInput = (props: {
className?: string;
value: string | null;
onChange: (value: string) => void;
placeholder?: string;
type?: "text" | "password",
}) => {
return (
<input
type="text"
type={props.type ?? "text"}
name={props.name}
className={props.className}
placeholder={props.placeholder}
value={props.value ?? ""}
onChange={(event) => props.onChange(event.target.value)}
/>
Expand Down
Loading