Fixed linting issues, added container
This commit is contained in:
parent
2beeb06fc7
commit
034937a0fe
|
@ -0,0 +1,39 @@
|
||||||
|
// For more information on how to run this SPFx project in a VS Code Remote Container, please visit https://aka.ms/spfx-devcontainer
|
||||||
|
{
|
||||||
|
"name": "SPFx 1.17.1",
|
||||||
|
"image": "docker.io/m365pnp/spfx:1.17.1",
|
||||||
|
// Set *default* container specific settings.json values on container create.
|
||||||
|
"settings": {},
|
||||||
|
// Add the IDs of extensions you want installed when the container is created.
|
||||||
|
"extensions": [
|
||||||
|
"editorconfig.editorconfig",
|
||||||
|
"dbaeumer.vscode-eslint"
|
||||||
|
],
|
||||||
|
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||||
|
"forwardPorts": [
|
||||||
|
4321,
|
||||||
|
35729
|
||||||
|
],
|
||||||
|
"portsAttributes": {
|
||||||
|
"4321": {
|
||||||
|
"protocol": "https",
|
||||||
|
"label": "Manifest",
|
||||||
|
"onAutoForward": "silent",
|
||||||
|
"requireLocalPort": true
|
||||||
|
},
|
||||||
|
// Not needed for SPFx>= 1.12.1
|
||||||
|
// "5432": {
|
||||||
|
// "protocol": "https",
|
||||||
|
// "label": "Workbench",
|
||||||
|
// "onAutoForward": "silent"
|
||||||
|
// },
|
||||||
|
"35729": {
|
||||||
|
"protocol": "https",
|
||||||
|
"label": "LiveReload",
|
||||||
|
"onAutoForward": "silent",
|
||||||
|
"requireLocalPort": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"postCreateCommand": "bash .devcontainer/spfx-startup.sh",
|
||||||
|
"remoteUser": "node"
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
echo
|
||||||
|
echo -e "\e[1;94mInstalling Node dependencies\e[0m"
|
||||||
|
npm install
|
||||||
|
|
||||||
|
## commands to create dev certificate and copy it to the root folder of the project
|
||||||
|
echo
|
||||||
|
echo -e "\e[1;94mGenerating dev certificate\e[0m"
|
||||||
|
gulp trust-dev-cert
|
||||||
|
|
||||||
|
# Convert the generated PEM certificate to a CER certificate
|
||||||
|
openssl x509 -inform PEM -in ~/.rushstack/rushstack-serve.pem -outform DER -out ./spfx-dev-cert.cer
|
||||||
|
|
||||||
|
# Copy the PEM ecrtificate for non-Windows hosts
|
||||||
|
cp ~/.rushstack/rushstack-serve.pem ./spfx-dev-cert.pem
|
||||||
|
|
||||||
|
## add *.cer to .gitignore to prevent certificates from being saved in repo
|
||||||
|
if ! grep -Fxq '*.cer' ./.gitignore
|
||||||
|
then
|
||||||
|
echo "# .CER Certificates" >> .gitignore
|
||||||
|
echo "*.cer" >> .gitignore
|
||||||
|
fi
|
||||||
|
|
||||||
|
## add *.pem to .gitignore to prevent certificates from being saved in repo
|
||||||
|
if ! grep -Fxq '*.pem' ./.gitignore
|
||||||
|
then
|
||||||
|
echo "# .PEM Certificates" >> .gitignore
|
||||||
|
echo "*.pem" >> .gitignore
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo -e "\e[1;92mReady!\e[0m"
|
||||||
|
|
||||||
|
echo -e "\n\e[1;94m**********\nOptional: if you plan on using gulp serve, don't forget to add the container certificate to your local machine. Please visit https://aka.ms/spfx-devcontainer for more information\n**********"
|
|
@ -1,5 +1,6 @@
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||||
export default function useObject<T>(InitialValue?: T) {
|
export default function useObject<T>(InitialValue?: T) {
|
||||||
const [value, setValue] = useState<T>(InitialValue ?? {} as T);
|
const [value, setValue] = useState<T>(InitialValue ?? {} as T);
|
||||||
const updateValue: (Updates: Partial<T>) => void = (Updates: Partial<T>) => setValue((prev) => ({ ...prev, ...Updates }))
|
const updateValue: (Updates: Partial<T>) => void = (Updates: Partial<T>) => setValue((prev) => ({ ...prev, ...Updates }))
|
||||||
|
|
|
@ -2,5 +2,6 @@ import { IForm } from "./Form";
|
||||||
|
|
||||||
export interface SaveObject {
|
export interface SaveObject {
|
||||||
form: IForm;
|
form: IForm;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
response: any;
|
response: any;
|
||||||
}
|
}
|
|
@ -13,11 +13,11 @@ const UNSUPPORTED_LOOKUP_FIELDTYPES: FieldType[] = [FieldType.PlaceHolder, Field
|
||||||
export const GetLookupFields: (Fields: IField[]) => IField[] = (Fields: IField[]) => {
|
export const GetLookupFields: (Fields: IField[]) => IField[] = (Fields: IField[]) => {
|
||||||
const arr: IField[] = [];
|
const arr: IField[] = [];
|
||||||
|
|
||||||
for (let field of Fields) {
|
for (const field of Fields) {
|
||||||
if (field.Type == FieldType.FieldGroup) {
|
if (field.Type === FieldType.FieldGroup) {
|
||||||
arr.push(...GetLookupFields((field as IGroupField).Fields))
|
arr.push(...GetLookupFields((field as IGroupField).Fields))
|
||||||
} else if (field.Type == FieldType.Conditional) {
|
} else if (field.Type === FieldType.Conditional) {
|
||||||
if ((field as IConditionalField).Field.Type == FieldType.FieldGroup) {
|
if ((field as IConditionalField).Field.Type === FieldType.FieldGroup) {
|
||||||
arr.push(...GetLookupFields(((field as IConditionalField).Field as IGroupField).Fields))
|
arr.push(...GetLookupFields(((field as IConditionalField).Field as IGroupField).Fields))
|
||||||
} else {
|
} else {
|
||||||
arr.push((field as IConditionalField).Field);
|
arr.push((field as IConditionalField).Field);
|
||||||
|
@ -27,5 +27,5 @@ export const GetLookupFields: (Fields: IField[]) => IField[] = (Fields: IField[]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return arr.filter(x => x.DisplayName != null && !UNSUPPORTED_LOOKUP_FIELDTYPES.some(bannedType => bannedType == x.Type));;
|
return arr.filter(x => x.DisplayName !== null && !UNSUPPORTED_LOOKUP_FIELDTYPES.some(bannedType => bannedType === x.Type));
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,7 @@ export default class JsonFormWebPart extends BaseClientSideWebPart<IJsonFormWebP
|
||||||
JsonForm,
|
JsonForm,
|
||||||
{
|
{
|
||||||
Form: JSON.parse(this.properties.formJson),
|
Form: JSON.parse(this.properties.formJson),
|
||||||
|
// eslint-disable-next-line no-return-assign
|
||||||
SaveForm: (updated: IForm) => this.properties.formJson = JSON.stringify({ ...JSON.parse(this.properties.formJson), ...updated }, null, 2),
|
SaveForm: (updated: IForm) => this.properties.formJson = JSON.stringify({ ...JSON.parse(this.properties.formJson), ...updated }, null, 2),
|
||||||
Mode: this.displayMode,
|
Mode: this.displayMode,
|
||||||
ListId: this.properties.listId,
|
ListId: this.properties.listId,
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { Label } from 'office-ui-fabric-react';
|
||||||
|
|
||||||
export interface IFieldProps {
|
export interface IFieldProps {
|
||||||
onChange: (updates: object) => void;
|
onChange: (updates: object) => void;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
form: any;
|
form: any;
|
||||||
field: IField;
|
field: IField;
|
||||||
readonly: boolean;
|
readonly: boolean;
|
||||||
|
@ -50,6 +51,7 @@ export const Field: React.FunctionComponent<IFieldProps> = (props: React.PropsWi
|
||||||
/>;
|
/>;
|
||||||
|
|
||||||
case FieldType.Choice: return <Dropdown
|
case FieldType.Choice: return <Dropdown
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
options={(field as any as IChoiceField).Options.map(x => ({ key: x, text: x }))}
|
options={(field as any as IChoiceField).Options.map(x => ({ key: x, text: x }))}
|
||||||
selectedKey={form[field.Id] ?? ""}
|
selectedKey={form[field.Id] ?? ""}
|
||||||
label={field.DisplayName}
|
label={field.DisplayName}
|
||||||
|
@ -57,32 +59,34 @@ export const Field: React.FunctionComponent<IFieldProps> = (props: React.PropsWi
|
||||||
/>
|
/>
|
||||||
|
|
||||||
case FieldType.MultiChoice: return <Dropdown
|
case FieldType.MultiChoice: return <Dropdown
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
options={(field as any as IChoiceField).Options.map(x => ({ key: x, text: x }))}
|
options={(field as any as IChoiceField).Options.map(x => ({ key: x, text: x }))}
|
||||||
selectedKeys={form[field.Id] ?? []}
|
selectedKeys={form[field.Id] ?? []}
|
||||||
label={field.DisplayName}
|
label={field.DisplayName}
|
||||||
multiSelect
|
multiSelect
|
||||||
onChange={readonly ? null : (_, val) => {
|
onChange={readonly ? null : (_, val) => {
|
||||||
let selected: string[] = form[field.Id] ?? [];
|
let selected: string[] = form[field.Id] ?? [];
|
||||||
selected = val.selected ? [...selected, val.key as string] : selected.filter(x => x != val.key);
|
selected = val.selected ? [...selected, val.key as string] : selected.filter(x => x !== val.key);
|
||||||
onChange({ [field.Id]: selected })
|
onChange({ [field.Id]: selected })
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
case FieldType.FieldGroup: return <div>
|
case FieldType.FieldGroup: return <div>
|
||||||
{(field.DisplayName != null || field.DisplayName != "") && <Label>{field.DisplayName}</Label>}
|
{(field.DisplayName !== null || field.DisplayName !== "") && <Label>{field.DisplayName}</Label>}
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "grid",
|
display: "grid",
|
||||||
gridTemplateColumns: (field as IGroupField).Direction == GroupDirection.Horizontal ? `repeat(auto-fill,minmax(calc(${100 / (field as any as IGroupField).Fields.length}% - 10px),1fr))` : '',
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
gridTemplateColumns: (field as IGroupField).Direction === GroupDirection.Horizontal ? `repeat(auto-fill,minmax(calc(${100 / (field as any as IGroupField).Fields.length}% - 10px),1fr))` : '',
|
||||||
gap: 10
|
gap: 10
|
||||||
}}>
|
}}>
|
||||||
{(field as IGroupField).Fields.map(f => <Field {...props} field={f} />)}
|
{(field as IGroupField).Fields.map((f, index) => <Field {...props} field={f} key={index} />)}
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
|
|
||||||
case FieldType.Conditional: {
|
case FieldType.Conditional: {
|
||||||
const f = (field as IConditionalField);
|
const f = (field as IConditionalField);
|
||||||
const visible = form[f.LookupFieldId] == f.MatchValue
|
const visible = form[f.LookupFieldId] === f.MatchValue
|
||||||
if (!visible) return <></>
|
if (!visible) return <></>
|
||||||
return <Field readonly={readonly} field={f.Field} form={form} onChange={onChange} />
|
return <Field readonly={readonly} field={f.Field} form={form} onChange={onChange} />
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-use-before-define */
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { FieldType, GroupDirection, IChoiceField, IConditionalField, IField, IGroupField } from '../../../../Models/FormField';
|
import { FieldType, GroupDirection, IChoiceField, IConditionalField, IField, IGroupField } from '../../../../Models/FormField';
|
||||||
import { ActionButton, ChoiceGroup, DefaultButton, Dialog, DialogFooter, Dropdown, Label, Position, PrimaryButton, SpinButton, Stack, TextField } from '@fluentui/react';
|
import { ActionButton, ChoiceGroup, DefaultButton, Dialog, DialogFooter, Dropdown, Label, Position, PrimaryButton, SpinButton, Stack, TextField } from '@fluentui/react';
|
||||||
|
@ -42,16 +43,16 @@ export const FieldEditorDialog: React.FunctionComponent<IFieldEditorDialogProps>
|
||||||
const t = val.key as FieldType;
|
const t = val.key as FieldType;
|
||||||
const updates: Partial<IField | IChoiceField | IGroupField | IConditionalField> = { Type: t }
|
const updates: Partial<IField | IChoiceField | IGroupField | IConditionalField> = { Type: t }
|
||||||
|
|
||||||
if (t == FieldType.Choice || t == FieldType.MultiChoice)
|
if (t === FieldType.Choice || t === FieldType.MultiChoice)
|
||||||
if ((field as IChoiceField).Options == null)
|
if ((field as IChoiceField).Options === null)
|
||||||
(updates as Partial<IChoiceField>).Options = [];
|
(updates as Partial<IChoiceField>).Options = [];
|
||||||
|
|
||||||
if (t == FieldType.Conditional)
|
if (t === FieldType.Conditional)
|
||||||
if ((field as IConditionalField).Field == null)
|
if ((field as IConditionalField).Field === null)
|
||||||
(updates as Partial<IConditionalField>).Field = NewField();
|
(updates as Partial<IConditionalField>).Field = NewField();
|
||||||
|
|
||||||
if (t == FieldType.FieldGroup)
|
if (t === FieldType.FieldGroup)
|
||||||
if ((field as IGroupField).Fields == null) {
|
if ((field as IGroupField).Fields === null) {
|
||||||
(updates as Partial<IGroupField>).Fields = [];
|
(updates as Partial<IGroupField>).Fields = [];
|
||||||
(updates as Partial<IGroupField>).Direction = GroupDirection.Horizontal;
|
(updates as Partial<IGroupField>).Direction = GroupDirection.Horizontal;
|
||||||
}
|
}
|
||||||
|
@ -60,12 +61,12 @@ export const FieldEditorDialog: React.FunctionComponent<IFieldEditorDialogProps>
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{FieldType.Conditional != field.Type && <TextField label='Title' value={field.DisplayName} onChange={(_, val) => updateValue({ DisplayName: val })} />}
|
{FieldType.Conditional !== field.Type && <TextField label='Title' value={field.DisplayName} onChange={(_, val) => updateValue({ DisplayName: val })} />}
|
||||||
|
|
||||||
|
|
||||||
{[FieldType.Choice, FieldType.MultiChoice].some(x => x == field.Type) && <ChoiceFieldOptions field={field as IChoiceField} updateValue={updateValue} />}
|
{[FieldType.Choice, FieldType.MultiChoice].some(x => x === field.Type) && <ChoiceFieldOptions field={field as IChoiceField} updateValue={updateValue} />}
|
||||||
{FieldType.Conditional == field.Type && <ConditionalFieldOptions field={field as IConditionalField} updateValue={updateValue} allFieldsFlat={props.allFieldsFlat} />}
|
{FieldType.Conditional === field.Type && <ConditionalFieldOptions field={field as IConditionalField} updateValue={updateValue} allFieldsFlat={props.allFieldsFlat} />}
|
||||||
{FieldType.FieldGroup == field.Type && <GroupFieldOptions field={field as IGroupField} updateValue={updateValue} />}
|
{FieldType.FieldGroup === field.Type && <GroupFieldOptions field={field as IGroupField} updateValue={updateValue} />}
|
||||||
|
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<PrimaryButton text='Delete' iconProps={{ iconName: 'delete' }} onClick={() => props.delete()} styles={{ root: { backgroundColor: "#FF0000" }, rootHovered: { backgroundColor: "#D10000" }, rootChecked: { backgroundColor: "#A30000" } }} />
|
<PrimaryButton text='Delete' iconProps={{ iconName: 'delete' }} onClick={() => props.delete()} styles={{ root: { backgroundColor: "#FF0000" }, rootHovered: { backgroundColor: "#D10000" }, rootChecked: { backgroundColor: "#A30000" } }} />
|
||||||
|
@ -90,9 +91,12 @@ export const GroupFieldOptions: React.FunctionComponent<IGroupFieldOptionsProps>
|
||||||
label='Direction'
|
label='Direction'
|
||||||
selectedKey={field.Direction}
|
selectedKey={field.Direction}
|
||||||
options={[
|
options={[
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
{ key: GroupDirection.Horizontal as any as string, text: "Horizontal", iconProps: { iconName: "AlignVerticalCenter" } },
|
{ key: GroupDirection.Horizontal as any as string, text: "Horizontal", iconProps: { iconName: "AlignVerticalCenter" } },
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
{ key: GroupDirection.Vertical as any as string, text: "Vertical", iconProps: { iconName: "AlignHorizontalCenter" } }
|
{ key: GroupDirection.Vertical as any as string, text: "Vertical", iconProps: { iconName: "AlignHorizontalCenter" } }
|
||||||
]}
|
]}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
onChange={(_, val) => updateValue({ Direction: val.key as any as GroupDirection })}
|
onChange={(_, val) => updateValue({ Direction: val.key as any as GroupDirection })}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
@ -110,7 +114,7 @@ const ChoiceFieldOptions: React.FunctionComponent<IChoiceFieldOptionsProps> = (p
|
||||||
return (<>
|
return (<>
|
||||||
<Label>Options</Label>
|
<Label>Options</Label>
|
||||||
{(field as IChoiceField).Options.map((val, index) => {
|
{(field as IChoiceField).Options.map((val, index) => {
|
||||||
return <div style={{ display: "flex" }}>
|
return <div style={{ display: "flex" }} key={index}>
|
||||||
<TextField
|
<TextField
|
||||||
styles={{ root: { flexGrow: 1 } }}
|
styles={{ root: { flexGrow: 1 } }}
|
||||||
value={val} onChange={(_, val) => {
|
value={val} onChange={(_, val) => {
|
||||||
|
@ -118,7 +122,7 @@ const ChoiceFieldOptions: React.FunctionComponent<IChoiceFieldOptionsProps> = (p
|
||||||
options[index] = val
|
options[index] = val
|
||||||
updateValue({ Options: options });
|
updateValue({ Options: options });
|
||||||
}} />
|
}} />
|
||||||
<ActionButton iconProps={{ iconName: "Delete" }} onClick={() => updateValue({ Options: (field as IChoiceField).Options.filter((_, i) => i != index) })} />
|
<ActionButton iconProps={{ iconName: "Delete" }} onClick={() => updateValue({ Options: (field as IChoiceField).Options.filter((_, i) => i !== index) })} />
|
||||||
</div>
|
</div>
|
||||||
})}
|
})}
|
||||||
<PrimaryButton iconProps={{ iconName: "Add" }} text='Add option' onClick={() => {
|
<PrimaryButton iconProps={{ iconName: "Add" }} text='Add option' onClick={() => {
|
||||||
|
@ -136,7 +140,7 @@ interface IConditionalFieldOptionsProps {
|
||||||
|
|
||||||
const ConditionalFieldOptions: React.FunctionComponent<IConditionalFieldOptionsProps> = (props: React.PropsWithChildren<IConditionalFieldOptionsProps>) => {
|
const ConditionalFieldOptions: React.FunctionComponent<IConditionalFieldOptionsProps> = (props: React.PropsWithChildren<IConditionalFieldOptionsProps>) => {
|
||||||
const { allFieldsFlat, field, updateValue } = props
|
const { allFieldsFlat, field, updateValue } = props
|
||||||
const targetField = allFieldsFlat.filter(x => x.Id == (field as IConditionalField).LookupFieldId)[0]
|
const targetField = allFieldsFlat.filter(x => x.Id === (field as IConditionalField).LookupFieldId)[0]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -147,23 +151,23 @@ const ConditionalFieldOptions: React.FunctionComponent<IConditionalFieldOptionsP
|
||||||
onChange={(_, val) => updateValue({ LookupFieldId: val.key as string })}
|
onChange={(_, val) => updateValue({ LookupFieldId: val.key as string })}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{targetField != null &&
|
{targetField !== null &&
|
||||||
<>
|
<>
|
||||||
{FieldType.Choice == targetField.Type && <Dropdown
|
{FieldType.Choice === targetField.Type && <Dropdown
|
||||||
label='Show if is equal to'
|
label='Show if is equal to'
|
||||||
options={(targetField as IChoiceField).Options.map(x => ({ key: x, text: x }))}
|
options={(targetField as IChoiceField).Options.map(x => ({ key: x, text: x }))}
|
||||||
selectedKey={(field as IConditionalField).MatchValue as string}
|
selectedKey={(field as IConditionalField).MatchValue as string}
|
||||||
onChange={(_, val) => updateValue({ MatchValue: val.text })}
|
onChange={(_, val) => updateValue({ MatchValue: val.text })}
|
||||||
/>}
|
/>}
|
||||||
|
|
||||||
{FieldType.Boolean == targetField.Type && <Dropdown
|
{FieldType.Boolean === targetField.Type && <Dropdown
|
||||||
label='Show if is equal to'
|
label='Show if is equal to'
|
||||||
options={[{ key: true.toString(), text: "Yes" }, { key: false.toString(), text: "No" }]}
|
options={[{ key: true.toString(), text: "Yes" }, { key: false.toString(), text: "No" }]}
|
||||||
selectedKey={(field as IConditionalField).MatchValue?.toString()}
|
selectedKey={(field as IConditionalField).MatchValue?.toString()}
|
||||||
onChange={(_, val) => updateValue({ MatchValue: val.key == true.toString() })}
|
onChange={(_, val) => updateValue({ MatchValue: val.key === true.toString() })}
|
||||||
/>}
|
/>}
|
||||||
|
|
||||||
{FieldType.Number == targetField.Type && <SpinButton
|
{FieldType.Number === targetField.Type && <SpinButton
|
||||||
label='Show if is equal to'
|
label='Show if is equal to'
|
||||||
inputMode='numeric'
|
inputMode='numeric'
|
||||||
labelPosition={Position.top}
|
labelPosition={Position.top}
|
||||||
|
@ -172,7 +176,7 @@ const ConditionalFieldOptions: React.FunctionComponent<IConditionalFieldOptionsP
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|
||||||
{[FieldType.Text, FieldType.MultilineText].some(x => x == targetField.Type) && <TextField
|
{[FieldType.Text, FieldType.MultilineText].some(x => x === targetField.Type) && <TextField
|
||||||
value={(field as IConditionalField).MatchValue as string}
|
value={(field as IConditionalField).MatchValue as string}
|
||||||
label={"Value to look for"}
|
label={"Value to look for"}
|
||||||
onChange={(_, val) => updateValue({ MatchValue: val })}
|
onChange={(_, val) => updateValue({ MatchValue: val })}
|
||||||
|
|
|
@ -17,7 +17,7 @@ const MAX_NUMBER_OF_ITEMS_IN_GROUP: number = 5;
|
||||||
|
|
||||||
export const FormFieldCustomizer: React.FunctionComponent<IFormFieldCustomizer> = (props: React.PropsWithChildren<IFormFieldCustomizer>) => {
|
export const FormFieldCustomizer: React.FunctionComponent<IFormFieldCustomizer> = (props: React.PropsWithChildren<IFormFieldCustomizer>) => {
|
||||||
const { field } = props;
|
const { field } = props;
|
||||||
const [shouldEdit, setShouldEdit] = React.useState<Boolean>(false);
|
const [shouldEdit, setShouldEdit] = React.useState<boolean>(false);
|
||||||
|
|
||||||
const editDialog = <FieldEditorDialog
|
const editDialog = <FieldEditorDialog
|
||||||
allFieldsFlat={props.allFieldsFlat}
|
allFieldsFlat={props.allFieldsFlat}
|
||||||
|
@ -34,20 +34,21 @@ export const FormFieldCustomizer: React.FunctionComponent<IFormFieldCustomizer>
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
||||||
if (FieldType.FieldGroup == field.Type) {
|
if (FieldType.FieldGroup === field.Type) {
|
||||||
const f = (field as IGroupField)
|
const f = (field as IGroupField)
|
||||||
const AtCapacity = f.Fields.length == MAX_NUMBER_OF_ITEMS_IN_GROUP;
|
const AtCapacity = f.Fields.length === MAX_NUMBER_OF_ITEMS_IN_GROUP;
|
||||||
return <div>
|
return <div>
|
||||||
{shouldEdit && editDialog}
|
{shouldEdit && editDialog}
|
||||||
<div style={{ display: "flex" }}>
|
<div style={{ display: "flex" }}>
|
||||||
{(f.DisplayName != null || f.DisplayName != "") && <Label disabled>{f.DisplayName}</Label>}
|
{(f.DisplayName !== null || f.DisplayName !== "") && <Label disabled>{f.DisplayName}</Label>}
|
||||||
<ActionButton iconProps={{ iconName: "Edit" }} onClick={() => setShouldEdit(true)} />
|
<ActionButton iconProps={{ iconName: "Edit" }} onClick={() => setShouldEdit(true)} />
|
||||||
<ActionButton iconProps={{ iconName: "Delete" }} onClick={() => props.delete()} />
|
<ActionButton iconProps={{ iconName: "Delete" }} onClick={() => props.delete()} />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "grid",
|
display: "grid",
|
||||||
gridTemplateColumns: (field as IGroupField).Direction == GroupDirection.Horizontal ? `repeat(auto-fill,minmax(calc(${100 / ((field as any as IGroupField).Fields.length + (!AtCapacity ? 1 : 0))}% - 10px),1fr))` : '',
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
gridTemplateColumns: (field as IGroupField).Direction === GroupDirection.Horizontal ? `repeat(auto-fill,minmax(calc(${100 / ((field as any as IGroupField).Fields.length + (!AtCapacity ? 1 : 0))}% - 10px),1fr))` : '',
|
||||||
gap: 10
|
gap: 10
|
||||||
}}>
|
}}>
|
||||||
{f.Fields.map((child, index) => {
|
{f.Fields.map((child, index) => {
|
||||||
|
@ -55,9 +56,9 @@ export const FormFieldCustomizer: React.FunctionComponent<IFormFieldCustomizer>
|
||||||
key={child.Id}
|
key={child.Id}
|
||||||
allFieldsFlat={props.allFieldsFlat}
|
allFieldsFlat={props.allFieldsFlat}
|
||||||
field={child}
|
field={child}
|
||||||
delete={() => props.update({ Fields: (field as IGroupField).Fields.filter(x => x.Id != child.Id) })}
|
delete={() => props.update({ Fields: (field as IGroupField).Fields.filter(x => x.Id !== child.Id) })}
|
||||||
update={(val) => {
|
update={(val) => {
|
||||||
let children = cloneDeep(f.Fields)
|
const children = cloneDeep(f.Fields)
|
||||||
children[index] = { ...children[index], ...val };
|
children[index] = { ...children[index], ...val };
|
||||||
props.update({ Fields: children });
|
props.update({ Fields: children });
|
||||||
}}
|
}}
|
||||||
|
@ -68,14 +69,14 @@ export const FormFieldCustomizer: React.FunctionComponent<IFormFieldCustomizer>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FieldType.Conditional == field.Type) {
|
if (FieldType.Conditional === field.Type) {
|
||||||
const f = (field as IConditionalField);
|
const f = (field as IConditionalField);
|
||||||
const lookupField = props.allFieldsFlat.filter(x => x.Id == f.LookupFieldId)[0];
|
const lookupField = props.allFieldsFlat.filter(x => x.Id === f.LookupFieldId)[0];
|
||||||
return <>
|
return <>
|
||||||
{shouldEdit && editDialog}
|
{shouldEdit && editDialog}
|
||||||
<div style={{ border: `1px solid ${getTheme().palette.themeDarkAlt}` }}>
|
<div style={{ border: `1px solid ${getTheme().palette.themeDarkAlt}` }}>
|
||||||
<div style={{ display: "flex", background: getTheme().palette.themeLighter, alignItems: 'center', paddingLeft: "1em" }}>
|
<div style={{ display: "flex", background: getTheme().palette.themeLighter, alignItems: 'center', paddingLeft: "1em" }}>
|
||||||
<Label>Visible if '{lookupField?.DisplayName}' is equal to '{f.MatchValue?.toString()}'</Label>
|
<Label>Visible if '{lookupField?.DisplayName}' is equal to '{f.MatchValue?.toString()}'</Label>
|
||||||
<ActionButton iconProps={{ iconName: "Edit" }} onClick={() => setShouldEdit(true)} />
|
<ActionButton iconProps={{ iconName: "Edit" }} onClick={() => setShouldEdit(true)} />
|
||||||
<ActionButton iconProps={{ iconName: "Delete" }} onClick={() => props.delete()} />
|
<ActionButton iconProps={{ iconName: "Delete" }} onClick={() => props.delete()} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -111,15 +112,15 @@ export const FormFieldCustomizer: React.FunctionComponent<IFormFieldCustomizer>
|
||||||
{shouldEdit && editDialog}
|
{shouldEdit && editDialog}
|
||||||
<div className={styles.EditField} onClick={() => setShouldEdit(true)}>
|
<div className={styles.EditField} onClick={() => setShouldEdit(true)}>
|
||||||
|
|
||||||
{FieldType.Label == field.Type && <Label styles={{ root: { cursor: "pointer" } }} disabled>{field.DisplayName}</Label>}
|
{FieldType.Label === field.Type && <Label styles={{ root: { cursor: "pointer" } }} disabled>{field.DisplayName}</Label>}
|
||||||
{FieldType.Header == field.Type && <Text variant='xLarge'>{field.DisplayName}</Text>}
|
{FieldType.Header === field.Type && <Text variant='xLarge'>{field.DisplayName}</Text>}
|
||||||
{FieldType.Text == field.Type && <TextField {...genericProps} />}
|
{FieldType.Text === field.Type && <TextField {...genericProps} />}
|
||||||
{FieldType.MultilineText == field.Type && <TextField {...genericProps} rows={5} multiline />}
|
{FieldType.MultilineText === field.Type && <TextField {...genericProps} rows={5} multiline />}
|
||||||
{FieldType.Number == field.Type && <SpinButton {...genericProps} inputMode='numeric' labelPosition={Position.top} />}
|
{FieldType.Number === field.Type && <SpinButton {...genericProps} inputMode='numeric' labelPosition={Position.top} />}
|
||||||
{FieldType.Boolean == field.Type && <Checkbox {...genericProps} styles={{ root: { marginTop: "2.5em" } }} />}
|
{FieldType.Boolean === field.Type && <Checkbox {...genericProps} styles={{ root: { marginTop: "2.5em" } }} />}
|
||||||
{FieldType.Choice == field.Type && <Dropdown {...genericProps} options={[]} />}
|
{FieldType.Choice === field.Type && <Dropdown {...genericProps} options={[]} />}
|
||||||
{FieldType.MultiChoice == field.Type && <Dropdown {...genericProps} options={[]} multiSelect />}
|
{FieldType.MultiChoice === field.Type && <Dropdown {...genericProps} options={[]} multiSelect />}
|
||||||
{FieldType.PlaceHolder == field.Type && <MessageBar messageBarType={MessageBarType.info} styles={{ root: { marginTop: "2.1em" } }}>Press here to setup the field!</MessageBar>}
|
{FieldType.PlaceHolder === field.Type && <MessageBar messageBarType={MessageBarType.info} styles={{ root: { marginTop: "2.1em" } }}>Press here to setup the field!</MessageBar>}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -21,42 +21,44 @@ export interface IJsonFormProps {
|
||||||
export const JsonForm: React.FunctionComponent<IJsonFormProps> = (props: React.PropsWithChildren<IJsonFormProps>) => {
|
export const JsonForm: React.FunctionComponent<IJsonFormProps> = (props: React.PropsWithChildren<IJsonFormProps>) => {
|
||||||
const { Mode, ServerRelativeUrl } = props;
|
const { Mode, ServerRelativeUrl } = props;
|
||||||
const { value: Form, updateValue: UpdateForm, overwriteData: __SETFORM } = useObject<IForm>(props.ServerRelativeUrl ? { Fields: [], Title: "" } : props.Form);
|
const { value: Form, updateValue: UpdateForm, overwriteData: __SETFORM } = useObject<IForm>(props.ServerRelativeUrl ? { Fields: [], Title: "" } : props.Form);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const { value: filledForm, updateValue, overwriteData: __SETFILLEDFORM } = useObject<any>();
|
const { value: filledForm, updateValue, overwriteData: __SETFILLEDFORM } = useObject<any>();
|
||||||
const { provider } = React.useContext(SPFxContext);
|
const { provider } = React.useContext(SPFxContext);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (ServerRelativeUrl != null) {
|
if (ServerRelativeUrl !== null) {
|
||||||
const fetch = async () => {
|
const fetch = async (): Promise<void> => {
|
||||||
const result = await provider.GetSubmission(ServerRelativeUrl);
|
const result = await provider.GetSubmission(ServerRelativeUrl);
|
||||||
__SETFILLEDFORM(result.response);
|
__SETFILLEDFORM(result.response);
|
||||||
__SETFORM(result.form);
|
__SETFORM(result.form);
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
fetch();
|
fetch();
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const saveForm = async () => {
|
const saveForm = async (): Promise<void> => {
|
||||||
const serverRelativeUrl = await provider.SaveSubmission({ form: Form, response: filledForm });
|
const serverRelativeUrl = await provider.SaveSubmission({ form: Form, response: filledForm });
|
||||||
var searchParams = new URLSearchParams(window.location.search);
|
const searchParams = new URLSearchParams(window.location.search);
|
||||||
searchParams.set(FILLED_FORM_QUERY_KEY, serverRelativeUrl);
|
searchParams.set(FILLED_FORM_QUERY_KEY, serverRelativeUrl);
|
||||||
window.location.search = searchParams.toString();
|
window.location.search = searchParams.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.ListId == null || props.ListId == "")
|
if (props.ListId === null || props.ListId === "")
|
||||||
return <Placeholder description={'Open the property pane and select a list to store responses'} iconName={'Edit'} iconText={'Please configure web part'} />
|
return <Placeholder description={'Open the property pane and select a list to store responses'} iconName={'Edit'} iconText={'Please configure web part'} />
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<WebPartTitle displayMode={Mode} title={Form.Title} updateProperty={(val) => UpdateForm({ Title: val })} />
|
<WebPartTitle displayMode={Mode} title={Form.Title} updateProperty={(val) => UpdateForm({ Title: val })} />
|
||||||
|
|
||||||
{Mode == DisplayMode.Read &&
|
{Mode === DisplayMode.Read &&
|
||||||
<>
|
<>
|
||||||
<Stack tokens={{ childrenGap: 5 }}>
|
<Stack tokens={{ childrenGap: 5 }}>
|
||||||
{Form.Fields.map(field => {
|
{Form.Fields.map((field, index) => {
|
||||||
return <Field readonly={props.ServerRelativeUrl != null} field={field} onChange={updateValue} form={filledForm} />
|
return <Field readonly={props.ServerRelativeUrl !== null} field={field} onChange={updateValue} form={filledForm} key={index} />
|
||||||
})}
|
})}
|
||||||
</Stack>
|
</Stack>
|
||||||
{props.ServerRelativeUrl == null &&
|
{props.ServerRelativeUrl === null &&
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<PrimaryButton text='Submit' iconProps={{ iconName: "Accept" }} onClick={() => saveForm()} />
|
<PrimaryButton text='Submit' iconProps={{ iconName: "Accept" }} onClick={() => saveForm()} />
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
|
@ -64,19 +66,20 @@ export const JsonForm: React.FunctionComponent<IJsonFormProps> = (props: React.P
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
||||||
{Mode == DisplayMode.Edit &&
|
{Mode === DisplayMode.Edit &&
|
||||||
<>
|
<>
|
||||||
<Stack tokens={{ childrenGap: 5 }}>
|
<Stack tokens={{ childrenGap: 5 }}>
|
||||||
{Form.Fields.map((Field, index) => {
|
{Form.Fields.map((Field, index) => {
|
||||||
return <FormFieldCustomizer
|
return <FormFieldCustomizer
|
||||||
|
key={index}
|
||||||
allFieldsFlat={GetLookupFields(Form.Fields)}
|
allFieldsFlat={GetLookupFields(Form.Fields)}
|
||||||
field={Field}
|
field={Field}
|
||||||
delete={() => {
|
delete={() => {
|
||||||
let fields = cloneDeep(Form.Fields).filter((x, i) => index != i);
|
const fields = cloneDeep(Form.Fields).filter((x, i) => index !== i);
|
||||||
UpdateForm({ Fields: fields });
|
UpdateForm({ Fields: fields });
|
||||||
}}
|
}}
|
||||||
update={(val) => {
|
update={(val) => {
|
||||||
let fields = cloneDeep(Form.Fields)
|
const fields = cloneDeep(Form.Fields)
|
||||||
fields[index] = { ...fields[index], ...val };
|
fields[index] = { ...fields[index], ...val };
|
||||||
UpdateForm({ Fields: fields });
|
UpdateForm({ Fields: fields });
|
||||||
}}
|
}}
|
||||||
|
|
Loading…
Reference in New Issue