Skip to content

Commit

Permalink
feat(forms) simpler one click edit mode for edit forms on instances, …
Browse files Browse the repository at this point in the history
…profiles networks, storage pools, storage volumes and projects

Signed-off-by: David Edler <[email protected]>
  • Loading branch information
edlerd committed Aug 28, 2024
1 parent fe13643 commit f99260a
Show file tree
Hide file tree
Showing 39 changed files with 434 additions and 316 deletions.
39 changes: 35 additions & 4 deletions src/components/ConfigurationRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { NetworkFormValues } from "pages/networks/forms/NetworkForm";
import { ProjectFormValues } from "pages/projects/CreateProject";
import { getConfigRowMetadata } from "util/configInheritance";
import { StoragePoolFormValues } from "pages/storage/forms/StoragePoolForm";
import { ensureEditMode } from "util/instanceEdit";

export type ConfigurationRowFormikValues =
| InstanceAndProfileFormValues
Expand All @@ -22,7 +23,7 @@ export type ConfigurationRowFormikValues =
| ProjectFormValues
| StoragePoolFormValues;

type ConfigurationRowFormikProps =
export type ConfigurationRowFormikProps =
| InstanceAndProfileFormikProps
| FormikProps<NetworkFormValues>
| FormikProps<ProjectFormValues>
Expand Down Expand Up @@ -60,12 +61,20 @@ export const getConfigurationRow = ({
const overrideValue = readOnlyRenderer(value === "" ? "-" : value);
const metadata = getConfigRowMetadata(formik.values, name);

const enableOverride = () => {
void formik.setFieldValue(name, defaultValue);
};

const focusOverride = () => {
setTimeout(() => document.getElementById(name)?.focus(), 100);
};

const toggleDefault = () => {
if (isOverridden) {
void formik.setFieldValue(name, undefined);
} else {
void formik.setFieldValue(name, defaultValue);
setTimeout(() => document.getElementById(name)?.focus(), 100);
enableOverride();
focusOverride();
}
};

Expand Down Expand Up @@ -132,7 +141,29 @@ export const getConfigurationRow = ({

const renderOverride = (): ReactNode => {
if (formik.values.readOnly) {
return overrideValue;
return (
<>
{overrideValue}
{!disabled && (
<Button
onClick={() => {
ensureEditMode(formik);
if (!isOverridden) {
enableOverride();
}
focusOverride();
}}
className="u-no-margin--bottom"
type="button"
appearance="base"
title={isOverridden ? "Edit" : "Create override"}
hasIcon
>
<Icon name="edit" />
</Button>
)}
</>
);
}
if (isOverridden) {
return getForm();
Expand Down
137 changes: 86 additions & 51 deletions src/components/forms/DiskDeviceFormCustom.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { FC } from "react";
import { Icon, Input, Label } from "@canonical/react-components";
import React, { FC } from "react";
import { Button, Icon, Input, Label } from "@canonical/react-components";
import { InstanceAndProfileFormikProps } from "./instanceAndProfileFormValues";
import { EditInstanceFormValues } from "pages/instances/EditInstance";
import { InheritedVolume } from "util/configInheritance";
Expand All @@ -13,6 +13,7 @@ import DetachDiskDeviceBtn from "pages/instances/actions/DetachDiskDeviceBtn";
import classnames from "classnames";
import { LxdStorageVolume } from "types/storage";
import { isDiskDeviceMountPointMissing } from "util/instanceValidation";
import { ensureEditMode } from "util/instanceEdit";

interface Props {
formik: InstanceAndProfileFormikProps;
Expand All @@ -30,6 +31,10 @@ const DiskDeviceFormCustom: FC<Props> = ({
.filter((device) => device.name !== "root" && device.type === "disk")
.map((device) => device as FormDiskDevice);

const focusField = (name: string) => {
setTimeout(() => document.getElementById(name)?.focus(), 100);
};

const addVolume = (volume: LxdStorageVolume) => {
const copy = [...formik.values.devices];
copy.push({
Expand All @@ -42,7 +47,7 @@ const DiskDeviceFormCustom: FC<Props> = ({
void formik.setFieldValue("devices", copy);

const name = `devices.${copy.length - 1}.path`;
setTimeout(() => document.getElementById(name)?.focus(), 100);
focusField(name);
};

const changeVolume = (
Expand Down Expand Up @@ -71,6 +76,22 @@ const DiskDeviceFormCustom: FC<Props> = ({
return candidate;
};

const editButton = (fieldName: string) => (
<Button
appearance="base"
className="u-no-margin--bottom"
hasIcon
dense
title="Edit"
onClick={() => {
ensureEditMode(formik);
focusField(fieldName);
}}
>
<Icon name="edit" />
</Button>
);

const rows: MainTableRow[] = [];

customVolumes.map((formVolume) => {
Expand All @@ -83,15 +104,20 @@ const DiskDeviceFormCustom: FC<Props> = ({
<RenameDiskDeviceInput
name={formVolume.name}
index={index}
readOnly={readOnly}
setName={(name) =>
void formik.setFieldValue(`devices.${index}.name`, name)
}
setName={(name) => {
ensureEditMode(formik);
void formik.setFieldValue(`devices.${index}.name`, name);
}}
/>
),
inherited: "",
override: !readOnly && (
<DetachDiskDeviceBtn onDetach={() => removeDevice(index, formik)} />
override: (
<DetachDiskDeviceBtn
onDetach={() => {
ensureEditMode(formik);
removeDevice(index, formik);
}}
/>
),
}),
);
Expand All @@ -105,33 +131,29 @@ const DiskDeviceFormCustom: FC<Props> = ({
inherited: (
<div className="custom-disk-volume-source">
<div
className={classnames("mono-font", {
"u-truncate": !formik.values.readOnly,
})}
title={
formik.values.readOnly
? undefined
: `${formVolume.pool} / ${formVolume.source ?? ""}`
}
className={classnames("mono-font", "u-truncate")}
title={`${formVolume.pool} / ${formVolume.source ?? ""}`}
>
<b>
{formVolume.pool} / {formVolume.source}
</b>
</div>
{!readOnly && (
<CustomVolumeSelectBtn
project={project}
setValue={(volume) => changeVolume(volume, formVolume, index)}
buttonProps={{
id: `devices.${index}.pool`,
appearance: "base",
className: "u-no-margin--bottom",
"aria-label": `Select storage volume`,
}}
>
<Icon name="edit" />
</CustomVolumeSelectBtn>
)}
<CustomVolumeSelectBtn
project={project}
setValue={(volume) => {
ensureEditMode(formik);
changeVolume(volume, formVolume, index);
}}
buttonProps={{
id: `devices.${index}.pool`,
appearance: "base",
className: "u-no-margin--bottom",
title: "Select storage volume",
dense: true,
}}
>
<Icon name="edit" />
</CustomVolumeSelectBtn>
</div>
),
override: "",
Expand All @@ -149,8 +171,11 @@ const DiskDeviceFormCustom: FC<Props> = ({
</Label>
),
inherited: readOnly ? (
<div className="mono-font">
<b>{formVolume.path}</b>
<div className="custom-disk-read-mode">
<div className="mono-font custom-disk-value">
<b>{formVolume.path}</b>
</div>
{editButton(`devices.${index}.path`)}
</div>
) : (
<Input
Expand Down Expand Up @@ -182,12 +207,15 @@ const DiskDeviceFormCustom: FC<Props> = ({
<Label forId={`devices.${index}.limits.read`}>Read limit</Label>
),
inherited: readOnly ? (
<div className="mono-font">
<b>
{formVolume.limits?.read
? `${formVolume.limits.read} IOPS`
: "none"}
</b>
<div className="custom-disk-read-mode">
<div className="mono-font custom-disk-value">
<b>
{formVolume.limits?.read
? `${formVolume.limits.read} IOPS`
: "none"}
</b>
</div>
{editButton(`devices.${index}.limits.read`)}
</div>
) : (
<div className="custom-disk-device-limits">
Expand Down Expand Up @@ -220,12 +248,15 @@ const DiskDeviceFormCustom: FC<Props> = ({
<Label forId={`devices.${index}.limits.write`}>Write limit</Label>
),
inherited: readOnly ? (
<div className="mono-font">
<b>
{formVolume.limits?.write
? `${formVolume.limits.write} IOPS`
: "none"}
</b>
<div className="custom-disk-read-mode">
<div className="mono-font custom-disk-value">
<b>
{formVolume.limits?.write
? `${formVolume.limits.write} IOPS`
: "none"}
</b>
</div>
{editButton(`devices.${index}.limits.write`)}
</div>
) : (
<div className="custom-disk-device-limits">
Expand Down Expand Up @@ -262,12 +293,16 @@ const DiskDeviceFormCustom: FC<Props> = ({
<ConfigurationTable rows={rows} />
</>
)}
{!readOnly && (
<CustomVolumeSelectBtn project={project} setValue={addVolume}>
<Icon name="plus" />
<span>Attach disk device</span>
</CustomVolumeSelectBtn>
)}
<CustomVolumeSelectBtn
project={project}
setValue={(volume) => {
ensureEditMode(formik);
addVolume(volume);
}}
>
<Icon name="plus" />
<span>Attach disk device</span>
</CustomVolumeSelectBtn>
</div>
);
};
Expand Down
19 changes: 12 additions & 7 deletions src/components/forms/DiskDeviceFormInherited.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { MainTableRow } from "@canonical/react-components/dist/components/MainTa
import classnames from "classnames";
import { removeDevice } from "util/formDevices";
import DetachDiskDeviceBtn from "pages/instances/actions/DetachDiskDeviceBtn";
import { ensureEditMode } from "util/instanceEdit";

interface Props {
formik: InstanceAndProfileFormikProps;
Expand Down Expand Up @@ -53,23 +54,27 @@ const DiskDeviceFormInherited: FC<Props> = ({ formik, inheritedVolumes }) => {
</div>
),
inherited: "",
override: readOnly ? (
isNoneDevice ? (
<>Detached</>
) : null
) : isNoneDevice ? (
override: isNoneDevice ? (
<Button
appearance="base"
type="button"
title="Reattach volume"
onClick={() => removeDevice(noneDeviceId, formik)}
onClick={() => {
ensureEditMode(formik);
removeDevice(noneDeviceId, formik);
}}
className="has-icon u-no-margin--bottom"
>
<Icon name="connected"></Icon>
<span>Reattach</span>
</Button>
) : (
<DetachDiskDeviceBtn onDetach={() => addNoneDevice(item.key)} />
<DetachDiskDeviceBtn
onDetach={() => {
ensureEditMode(formik);
addNoneDevice(item.key);
}}
/>
),
}),
);
Expand Down
47 changes: 26 additions & 21 deletions src/components/forms/DiskDeviceFormRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { LxdStoragePool } from "types/storage";
import { LxdProfile } from "types/profile";
import { removeDevice } from "util/formDevices";
import { hasNoRootDisk } from "util/instanceValidation";
import { ensureEditMode } from "util/instanceEdit";

interface Props {
formik: InstanceAndProfileFormikProps;
Expand Down Expand Up @@ -64,33 +65,37 @@ const DiskDeviceFormRoot: FC<Props> = ({
className: "override-with-form",
configuration: <b className="device-name">Root storage</b>,
inherited: "",
override:
!readOnly &&
(hasRootStorage ? (
<div>
<Button
onClick={() => removeDevice(rootIndex, formik)}
type="button"
appearance="base"
title="Clear override"
hasIcon
className="u-no-margin--bottom"
>
<Icon name="close" className="clear-configuration-icon" />
</Button>
</div>
) : (
override: hasRootStorage ? (
<div>
<Button
onClick={addRootStorage}
onClick={() => {
ensureEditMode(formik);
removeDevice(rootIndex, formik);
}}
type="button"
appearance="base"
title="Create override"
className="u-no-margin--bottom"
title="Clear override"
hasIcon
className="u-no-margin--bottom"
>
<Icon name="edit" />
<Icon name="close" className="clear-configuration-icon" />
</Button>
)),
</div>
) : (
<Button
onClick={() => {
ensureEditMode(formik);
addRootStorage();
}}
type="button"
appearance="base"
title="Create override"
className="u-no-margin--bottom"
hasIcon
>
<Icon name="edit" />
</Button>
),
}),

getDiskDeviceRow({
Expand Down
Loading

0 comments on commit f99260a

Please sign in to comment.