Skip to content

Commit

Permalink
Add form support for raw dictionary input and array of raw dictionary…
Browse files Browse the repository at this point in the history
… inputs (#3691)

Co-authored-by: Justin Rhee <[email protected]>
  • Loading branch information
jusrhee and Justin Rhee authored Sep 29, 2023
1 parent 3dfa522 commit 04422e9
Show file tree
Hide file tree
Showing 5 changed files with 470 additions and 1 deletion.
8 changes: 8 additions & 0 deletions dashboard/src/components/porter-form/PorterForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
ServiceIPListField,
TextAreaField,
UrlLinkField,
DictionaryField,
DictionaryArrayField,
} from "./types";
import TabRegion, { TabOption } from "../TabRegion";
import Heading from "../form-components/Heading";
Expand All @@ -32,6 +34,8 @@ import CronInput from "./field-components/CronInput";
import TextAreaInput from "./field-components/TextAreaInput";
import UrlLink from "./field-components/UrlLink";
import Button from "components/porter/Button";
import DictionaryArray from "./field-components/DictionaryArray";
import Dictionary from "./field-components/Dictionary";

interface Props {
leftTabOptions?: TabOption[];
Expand Down Expand Up @@ -88,6 +92,10 @@ const PorterForm: React.FC<Props> = (props) => {
return <Helper>{field.label}</Helper>;
case "input":
return <Input {...(bundledProps as InputField)} />;
case "dictionary":
return <Dictionary {...(bundledProps as DictionaryField)} />;
case "dictionary-array":
return <DictionaryArray {...(bundledProps as DictionaryArrayField)} />;
case "checkbox":
return <Checkbox {...(bundledProps as CheckboxField)} />;
case "key-value-array":
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React, { useEffect } from "react";
import InputRow from "../../form-components/InputRow";
import useFormField from "../hooks/useFormField";
import {
GetFinalVariablesFunction,
DictionaryField,
DictionaryFieldState,
} from "../types";
import DictionaryEditor from "components/porter/DictionaryEditor";
import { hasSetValue } from "../utils";

const Dictionary: React.FC<DictionaryField> = (props) => {
const {
state,
variables,
setVars,
setValidation,
} = useFormField<DictionaryFieldState>(props.id, {
initValidation: {
validated: hasSetValue(props),
},
initVars: {
[props.variable]: hasSetValue(props) ? props.value[0] : undefined,
},
});

if (state == undefined) return <></>;

return (
<DictionaryEditor
value={props?.value && props.value[0]}
onChange={(x: any) => {
setVars((vars) => {
return {
...vars,
[props.variable]: x,
};
});
setValidation((prev) => {
return {
...prev,
validated: true,
};
});
}}
/>
);
};

export const getFinalVariablesForStringInput: GetFinalVariablesFunction = (
vars,
props: DictionaryField
) => {
const val =
vars[props.variable] != undefined && vars[props.variable] != null
? vars[props.variable] : hasSetValue(props)
? props.value[0] : undefined;

return {
[props.variable]:
props.settings?.unit && !props.settings.omitUnitFromValue
? val + props.settings.unit
: val,
};
};

export default Dictionary;
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
import React from "react";
import styled from "styled-components";
import {
DictionaryArrayField,
DictionaryArrayFieldState,
GetFinalVariablesFunction,
} from "../types";
import useFormField from "../hooks/useFormField";
import { hasSetValue } from "../utils";
import DictionaryEditor from "components/porter/DictionaryEditor";

// this is used to set validation for the below form component in case
// input validation needs to get more complicated in the future
const validateArray = (arr: any[]) => {
return true;
};

const DictionaryArray: React.FC<DictionaryArrayField> = (props) => {
const {
state,
variables,
setVars,
setValidation,
} = useFormField<DictionaryArrayFieldState>(props.id, {
initVars: {
[props.variable]: hasSetValue(props) ? props.value[0] : [],
},
initValidation: {
validated: validateArray(hasSetValue(props) ? props.value[0] : []),
},
});

if (state == undefined) return <></>;

const renderDeleteButton = (values: string[], i: number) => {
if (!props.isReadOnly) {
return (
<DeleteButton
onClick={() => {
setVars((prev) => {
const val = prev[props.variable]
.slice(0, i)
.concat(prev[props.variable].slice(i + 1));
setValidation((prev) => {
return {
...prev,
validated: validateArray(val),
};
});
return {
[props.variable]: val,
};
});
}}
>
<i className="material-icons">cancel</i>
</DeleteButton>
);
}
};

const renderInputList = (values: string[]) => {
return (
<>
{values.length > 0 && values.map((value: string, i: number) => {
return (
<InputWrapper>
<DictionaryEditor
key={i}
value={value}
onChange={(e: any) => {
setVars((prev) => {
const val = prev[props.variable]?.map(
(t: string, j: number) => {
return i == j ? e : t;
}
);
setValidation((prev) => {
return {
...prev,
validated: validateArray(val),
};
});
return {
[props.variable]: val,
};
});
}}
/>
{renderDeleteButton(values, i)}
</InputWrapper>
);
})}
</>
);
};

return (
<StyledInputArray>
<Label>
{props.label}
{props.required && <Required>{" *"}</Required>}
</Label>
{variables[props.variable] === 0 ? (
<></>
) : (
renderInputList(variables[props.variable])
)}
<AddRowButton
onClick={() => {
setVars((prev) => {
return {
[props.variable]: [...prev[props.variable], ""],
};
});
}}
>
<i className="material-icons">add</i> Create new entry
</AddRowButton>
</StyledInputArray>
);
};

export default DictionaryArray;

export const getFinalVariablesForArrayInput: GetFinalVariablesFunction = (
vars,
props: DictionaryArrayField
) => {
return vars[props.variable] != undefined && vars[props.variable] != null
? {}
: {
[props.variable]: hasSetValue(props) ? props.value[0] : [],
};
};

const AddRowButton = styled.div`
display: flex;
align-items: center;
margin-top: 5px;
width: 270px;
font-size: 13px;
color: #aaaabb;
height: 30px;
border-radius: 3px;
cursor: pointer;
background: #ffffff11;
:hover {
background: #ffffff22;
}
> i {
color: #ffffff44;
font-size: 16px;
margin-left: 8px;
margin-right: 10px;
display: flex;
align-items: center;
justify-content: center;
}
`;

const DeleteButton = styled.div`
width: 15px;
height: 15px;
display: flex;
align-items: center;
margin-left: 8px;
margin-top: -3px;
justify-content: center;
> i {
font-size: 17px;
color: #ffffff44;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
:hover {
color: #ffffff88;
}
}
`;

const InputWrapper = styled.div`
display: flex;
align-items: center;
`;

const Input = styled.input`
outline: none;
border: none;
margin-bottom: 5px;
font-size: 13px;
background: #ffffff11;
border: 1px solid #ffffff55;
border-radius: 3px;
width: ${(props: { disabled?: boolean; width: string }) =>
props.width ? props.width : "270px"};
color: ${(props: { disabled?: boolean; width: string }) =>
props.disabled ? "#ffffff44" : "white"};
padding: 5px 10px;
height: 35px;
`;

const Label = styled.div`
color: #ffffff;
margin-bottom: 10px;
`;

const StyledInputArray = styled.div`
margin-bottom: 15px;
margin-top: 22px;
`;

const Required = styled.span`
color: #fc4976;
`;
18 changes: 17 additions & 1 deletion dashboard/src/components/porter-form/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,16 @@ export interface ArrayInputField extends GenericInputField {
label?: string;
}

export interface DictionaryField extends GenericInputField {
type: "dictionary";
label?: string;
}

export interface DictionaryArrayField extends GenericInputField {
type: "dictionary-array";
label?: string;
}

export interface SelectField extends GenericInputField {
type: "select";
settings:
Expand Down Expand Up @@ -168,6 +178,8 @@ export type FormField =
| VariableField
| CronField
| TextAreaField
| DictionaryField
| DictionaryArrayField
| UrlLinkField;

export interface ShowIfAnd {
Expand Down Expand Up @@ -256,13 +268,17 @@ export interface KeyValueArrayFieldState {
synced_env_groups: PopulatedEnvGroup[];
}
export interface ArrayInputFieldState { }
export interface DictionaryFieldState {}
export interface DictionaryArrayFieldState { }
export interface SelectFieldState { }

export type PorterFormFieldFieldState =
| StringInputFieldState
| CheckboxFieldState
| KeyValueArrayField
| ArrayInputFieldState
| DictionaryFieldState
| DictionaryArrayFieldState
| SelectFieldState;

// reducer interfaces
Expand Down Expand Up @@ -324,7 +340,7 @@ export type PorterFormAction =

export type GetFinalVariablesFunction = (
vars: PorterFormVariableList,
props: FormField,
props: FormField | any,
state: PorterFormFieldFieldState,
context: Partial<ContextProps>
) => PorterFormVariableList;
Expand Down
Loading

0 comments on commit 04422e9

Please sign in to comment.