Skip to content

Commit

Permalink
Overhaul custom fields input
Browse files Browse the repository at this point in the history
  • Loading branch information
WithoutPants committed Nov 20, 2024
1 parent 1d7050f commit 997bcb4
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,8 @@ export const PerformerEditPanel: React.FC<IPerformerDetails> = ({

{renderInputField("ignore_auto_tag", "checkbox")}

<hr />

<CustomFieldsInput
values={formik.values.custom_fields}
onChange={(v) => formik.setFieldValue("custom_fields", v)}
Expand Down
3 changes: 2 additions & 1 deletion ui/v2.5/src/components/Shared/CollapseButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Button, Collapse } from "react-bootstrap";
import { Icon } from "./Icon";

interface IProps {
className?: string;
text: React.ReactNode;
}

Expand All @@ -17,7 +18,7 @@ export const CollapseButton: React.FC<React.PropsWithChildren<IProps>> = (
const [open, setOpen] = useState(false);

return (
<div>
<div className={props.className}>
<Button
onClick={() => setOpen(!open)}
className="minimal collapse-button"
Expand Down
164 changes: 113 additions & 51 deletions ui/v2.5/src/components/Shared/CustomFields.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import React, { useRef, useState } from "react";
import React, { useEffect, useMemo, useRef, useState } from "react";
import { CollapseButton } from "./CollapseButton";
import { DetailItem } from "./DetailItem";
import { Col, Form, Row } from "react-bootstrap";
import { Button, Col, Form, InputGroup, Row } from "react-bootstrap";
import { FormattedMessage, useIntl } from "react-intl";
import { cloneDeep } from "@apollo/client/utilities";
import { Icon } from "./Icon";
import { faMinus, faPlus } from "@fortawesome/free-solid-svg-icons";
import cx from "classnames";

export type CustomFieldMap = {
[key: string]: unknown;
Expand All @@ -14,14 +17,15 @@ interface ICustomFields {
}

export const CustomFields: React.FC<ICustomFields> = ({ values }) => {
const intl = useIntl();
if (Object.keys(values).length === 0) {
return null;
}

return (
// according to linter rule CSS classes shouldn't use underscores
<div className="custom-fields">
<CollapseButton text="Custom Fields">
<CollapseButton text={intl.formatMessage({ id: "custom_fields.title" })}>
{Object.entries(values).map(([key, value]) => (
<DetailItem
key={key}
Expand All @@ -40,14 +44,20 @@ const CustomFieldInput: React.FC<{
field: string;
value: unknown;
onChange: (field: string, value: unknown) => void;
}> = ({ field, value, onChange }) => {
isNew?: boolean;
}> = ({ field, value, onChange, isNew = false }) => {
const intl = useIntl();
const [currentField, setCurrentField] = useState(field);
const [currentValue, setCurrentValue] = useState(value);

const fieldRef = useRef<HTMLInputElement>(null);
const valueRef = useRef<HTMLInputElement>(null);

useEffect(() => {
setCurrentField(field);
setCurrentValue(value);
}, [field, value]);

function onBlur(event: React.FocusEvent<HTMLInputElement>) {
// don't fire an update if the user is tabbing between fields
// this prevents focus being stolen from the field
Expand All @@ -59,32 +69,71 @@ const CustomFieldInput: React.FC<{
return;
}

// only update on existing fields
if (!isNew) {
onChange(currentField, currentValue);
}
}

function onAdd() {
onChange(currentField, currentValue);
setCurrentField("");
setCurrentValue("");
}

function onDelete() {
onChange("", "");
}

return (
<Row className="custom-fields-row">
<Col xs={6}>
<Form.Control
ref={fieldRef}
className="input-control"
type="text"
value={(currentField as string) ?? ""}
placeholder={intl.formatMessage({ id: "field" })}
onChange={(event) => setCurrentField(event.currentTarget.value)}
onBlur={onBlur}
/>
<Row className={cx("custom-fields-row", { "custom-fields-new": isNew })}>
<Col sm={3} xl={2}>
{isNew ? (
<Form.Control
ref={fieldRef}
className="input-control"
type="text"
value={(currentField as string) ?? ""}
placeholder={intl.formatMessage({ id: "custom_fields.field" })}
onChange={(event) => setCurrentField(event.currentTarget.value)}
onBlur={onBlur}
/>
) : (
<Form.Label>{currentField}</Form.Label>
)}
</Col>
<Col xs={6}>
<Form.Control
ref={valueRef}
className="input-control"
type="text"
value={(currentValue as string) ?? ""}
placeholder={currentField}
onChange={(event) => setCurrentValue(event.currentTarget.value)}
onBlur={onBlur}
/>
<Col sm={9} xl={7}>
<InputGroup>
<Form.Control
ref={valueRef}
className="input-control"
type="text"
value={(currentValue as string) ?? ""}
placeholder={currentField}
onChange={(event) => setCurrentValue(event.currentTarget.value)}
onBlur={onBlur}
/>
<InputGroup.Append>
{isNew ? (
<Button
className="custom-fields-add"
variant="success"
onClick={() => onAdd()}
disabled={!currentField}
>
<Icon icon={faPlus} />
</Button>
) : (
<Button
className="custom-fields-remove"
variant="danger"
onClick={() => onDelete()}
>
<Icon icon={faMinus} />
</Button>
)}
</InputGroup.Append>
</InputGroup>
</Col>
</Row>
);
Expand All @@ -99,6 +148,14 @@ export const CustomFieldsInput: React.FC<ICustomFieldsInput> = ({
values,
onChange,
}) => {
const intl = useIntl();

const fields = useMemo(() => {
const ret = Object.keys(values);
ret.sort();
return ret;
}, [values]);

function fieldChanged(
currentField: string,
newField: string,
Expand All @@ -113,33 +170,38 @@ export const CustomFieldsInput: React.FC<ICustomFieldsInput> = ({
}

return (
<Row className="custom-fields-input">
<Col xl={9}>
<Row className="custom-fields-input-header">
<Form.Label column xs={6}>
<FormattedMessage id="field" />
</Form.Label>
<Form.Label column xs={6}>
<FormattedMessage id="value" />
</Form.Label>
</Row>
{Object.entries(values).map(([field, value]) => (
<CollapseButton
className="custom-fields-input"
text={intl.formatMessage({ id: "custom_fields.title" })}
>
<Row>
<Col xl={12}>
<Row className="custom-fields-input-header">
<Form.Label column sm={3} xl={2}>
<FormattedMessage id="custom_fields.field" />
</Form.Label>
<Form.Label column sm={9} xl={7}>
<FormattedMessage id="custom_fields.value" />
</Form.Label>
</Row>
{fields.map((field) => (
<CustomFieldInput
key={field}
field={field}
value={values[field]}
onChange={(newField, newValue) =>
fieldChanged(field, newField, newValue)
}
/>
))}
<CustomFieldInput
key={field}
field={field}
value={value}
onChange={(newField, newValue) =>
fieldChanged(field, newField, newValue)
}
field=""
value=""
onChange={(field, value) => fieldChanged("", field, value)}
isNew
/>
))}
<CustomFieldInput
key={Object.keys(values).length}
field=""
value=""
onChange={(field, value) => fieldChanged("", field, value)}
/>
</Col>
</Row>
</Col>
</Row>
</CollapseButton>
);
};
19 changes: 19 additions & 0 deletions ui/v2.5/src/components/Shared/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -687,3 +687,22 @@ button.btn.favorite-button {
}
}
}

.custom-fields-input > .collapse-button {
font-weight: 700;
}

.custom-fields-row {
align-items: center;
font-family: "Courier New", Courier, monospace;
font-size: 0.875rem;

.form-control,
.btn {
font-size: 0.875rem;
}

&.custom-fields-new > div:not(:last-child) {
padding-right: 0;
}
}
5 changes: 5 additions & 0 deletions ui/v2.5/src/locales/en-GB.json
Original file line number Diff line number Diff line change
Expand Up @@ -851,6 +851,11 @@
"only": "Only"
},
"custom": "Custom",
"custom_fields": {
"field": "Field",
"title": "Custom Fields",
"value": "Value"
},
"date": "Date",
"date_format": "YYYY-MM-DD",
"datetime_format": "YYYY-MM-DD HH:MM",
Expand Down

0 comments on commit 997bcb4

Please sign in to comment.