WizardV2: Add draggable mount point
This commit is contained in:
parent
0a62e0d286
commit
e5bfc19194
3 changed files with 252 additions and 51 deletions
|
|
@ -3,7 +3,6 @@ import React, { useEffect, useState } from 'react';
|
|||
import {
|
||||
Alert,
|
||||
Button,
|
||||
Popover,
|
||||
Text,
|
||||
TextContent,
|
||||
TextInput,
|
||||
|
|
@ -12,15 +11,13 @@ import {
|
|||
WizardFooterWrapper,
|
||||
} from '@patternfly/react-core';
|
||||
import { Select, SelectOption } from '@patternfly/react-core/deprecated';
|
||||
import {
|
||||
HelpIcon,
|
||||
MinusCircleIcon,
|
||||
PlusCircleIcon,
|
||||
} from '@patternfly/react-icons';
|
||||
import { MinusCircleIcon, PlusCircleIcon } from '@patternfly/react-icons';
|
||||
import { ExternalLinkAltIcon } from '@patternfly/react-icons';
|
||||
import { Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table';
|
||||
import { Td, Tr } from '@patternfly/react-table';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import FileSystemTable from './FileSystemTable';
|
||||
|
||||
import { UNIT_GIB, UNIT_KIB, UNIT_MIB } from '../../../../constants';
|
||||
import { useAppDispatch, useAppSelector } from '../../../../store/hooks';
|
||||
import {
|
||||
|
|
@ -158,47 +155,7 @@ const FileSystemConfiguration = () => {
|
|||
title="Filesystem customizations are not applied to 'Bare metal - Installer' images"
|
||||
/>
|
||||
)}
|
||||
<Table aria-label="File system table" variant="compact">
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th />
|
||||
<Th>Mount point</Th>
|
||||
<Th></Th>
|
||||
<Th>Type</Th>
|
||||
<Th>
|
||||
Minimum size
|
||||
<Popover
|
||||
hasAutoWidth
|
||||
bodyContent={
|
||||
<TextContent>
|
||||
<Text>
|
||||
Image Builder may extend this size based on requirements,
|
||||
selected packages, and configurations.
|
||||
</Text>
|
||||
</TextContent>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
variant="plain"
|
||||
aria-label="File system configuration info"
|
||||
aria-describedby="file-system-configuration-info"
|
||||
className="pf-c-form__group-label-help"
|
||||
>
|
||||
<HelpIcon />
|
||||
</Button>
|
||||
</Popover>
|
||||
</Th>
|
||||
<Th />
|
||||
<Th />
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody data-testid="file-system-configuration-tbody">
|
||||
{partitions &&
|
||||
partitions.map((partition) => (
|
||||
<Row key={partition.id} partition={partition} />
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
<FileSystemTable />
|
||||
<TextContent>
|
||||
<Button
|
||||
ouiaId="add-partition"
|
||||
|
|
@ -217,6 +174,9 @@ const FileSystemConfiguration = () => {
|
|||
|
||||
type RowPropTypes = {
|
||||
partition: Partition;
|
||||
onDrop?: (event: React.DragEvent<HTMLTableRowElement>) => void;
|
||||
onDragEnd?: (event: React.DragEvent<HTMLTableRowElement>) => void;
|
||||
onDragStart?: (event: React.DragEvent<HTMLTableRowElement>) => void;
|
||||
};
|
||||
|
||||
const getPrefix = (mountpoint: string) => {
|
||||
|
|
@ -227,7 +187,12 @@ const getSuffix = (mountpoint: string) => {
|
|||
return mountpoint.substring(prefix.length);
|
||||
};
|
||||
|
||||
const Row = ({ partition }: RowPropTypes) => {
|
||||
export const Row = ({
|
||||
partition,
|
||||
onDragEnd,
|
||||
onDragStart,
|
||||
onDrop,
|
||||
}: RowPropTypes) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const partitions = useAppSelector(selectPartitions);
|
||||
const handleRemovePartition = (id: string) => {
|
||||
|
|
@ -237,8 +202,18 @@ const Row = ({ partition }: RowPropTypes) => {
|
|||
const duplicates = getDuplicateMountPoints(partitions);
|
||||
|
||||
return (
|
||||
<Tr>
|
||||
<Td />
|
||||
<Tr
|
||||
draggable
|
||||
id={partition.id}
|
||||
onDrop={onDrop}
|
||||
onDragStart={onDragStart}
|
||||
onDragEnd={onDragEnd}
|
||||
>
|
||||
<Td
|
||||
draggableRow={{
|
||||
id: `draggable-row-${partition.id}`,
|
||||
}}
|
||||
/>
|
||||
<Td className="pf-m-width-20">
|
||||
<MountpointPrefix partition={partition} />
|
||||
{!isNextButtonPristine &&
|
||||
|
|
|
|||
|
|
@ -0,0 +1,220 @@
|
|||
import React, { useRef, useState } from 'react';
|
||||
|
||||
import { Popover, TextContent, Text, Button } from '@patternfly/react-core';
|
||||
import { HelpIcon } from '@patternfly/react-icons';
|
||||
import styles from '@patternfly/react-styles/css/components/Table/table';
|
||||
import {
|
||||
Table,
|
||||
Th,
|
||||
Thead,
|
||||
Tbody,
|
||||
Tr,
|
||||
TrProps,
|
||||
TbodyProps,
|
||||
} from '@patternfly/react-table';
|
||||
|
||||
import { Row } from './FileSystemConfiguration';
|
||||
|
||||
import { useAppDispatch, useAppSelector } from '../../../../store/hooks';
|
||||
import {
|
||||
changePartitionOrder,
|
||||
selectPartitions,
|
||||
} from '../../../../store/wizardSlice';
|
||||
|
||||
const FileSystemTable = () => {
|
||||
const [draggedItemId, setDraggedItemId] = useState<string | null>(null);
|
||||
const [draggingToItemIndex, setDraggingToItemIndex] = useState<number | null>(
|
||||
null
|
||||
);
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const [tempItemOrder, setTempItemOrder] = useState<string[]>([]);
|
||||
|
||||
const bodyRef = useRef<HTMLTableSectionElement>(null);
|
||||
const partitions = useAppSelector(selectPartitions);
|
||||
const itemOrder = partitions.map((partition) => partition.id);
|
||||
const dispatch = useAppDispatch();
|
||||
const isValidDrop = (
|
||||
evt: React.DragEvent<HTMLTableSectionElement | HTMLTableRowElement>
|
||||
) => {
|
||||
const ulRect = bodyRef.current?.getBoundingClientRect();
|
||||
if (!ulRect) return false;
|
||||
return (
|
||||
evt.clientX > ulRect.x &&
|
||||
evt.clientX < ulRect.x + ulRect.width &&
|
||||
evt.clientY > ulRect.y &&
|
||||
evt.clientY < ulRect.y + ulRect.height
|
||||
);
|
||||
};
|
||||
|
||||
const onDragStart: TrProps['onDragStart'] = (evt) => {
|
||||
evt.dataTransfer.effectAllowed = 'move';
|
||||
evt.dataTransfer.setData('text/plain', evt.currentTarget.id);
|
||||
const draggedItemId = evt.currentTarget.id;
|
||||
|
||||
evt.currentTarget.classList.add(styles.modifiers.ghostRow);
|
||||
evt.currentTarget.setAttribute('aria-pressed', 'true');
|
||||
|
||||
setDraggedItemId(draggedItemId);
|
||||
setIsDragging(true);
|
||||
};
|
||||
|
||||
const onDragCancel = () => {
|
||||
const children = bodyRef.current?.children;
|
||||
if (children) {
|
||||
Array.from(children).forEach((el) => {
|
||||
el.classList.remove(styles.modifiers.ghostRow);
|
||||
el.setAttribute('aria-pressed', 'false');
|
||||
});
|
||||
}
|
||||
setDraggedItemId(null);
|
||||
setDraggingToItemIndex(null);
|
||||
setIsDragging(false);
|
||||
};
|
||||
|
||||
const onDragLeave: TbodyProps['onDragLeave'] = (evt) => {
|
||||
if (!isValidDrop(evt)) {
|
||||
move(itemOrder);
|
||||
setDraggingToItemIndex(null);
|
||||
}
|
||||
};
|
||||
|
||||
const onDrop: TrProps['onDrop'] = (evt) => {
|
||||
if (isValidDrop(evt)) {
|
||||
dispatch(changePartitionOrder(tempItemOrder));
|
||||
} else {
|
||||
onDragCancel();
|
||||
}
|
||||
};
|
||||
|
||||
const onDragOver: TbodyProps['onDragOver'] = (evt) => {
|
||||
evt.preventDefault();
|
||||
|
||||
const curListItem = (evt.target as HTMLTableSectionElement).closest('tr');
|
||||
if (
|
||||
!curListItem ||
|
||||
!bodyRef.current?.contains(curListItem) ||
|
||||
curListItem.id === draggedItemId
|
||||
) {
|
||||
return null;
|
||||
} else {
|
||||
const dragId = curListItem.id;
|
||||
const newDraggingToItemIndex = Array.from(
|
||||
bodyRef.current.children
|
||||
).findIndex((item) => item.id === dragId);
|
||||
if (newDraggingToItemIndex !== draggingToItemIndex && draggedItemId) {
|
||||
const tempItemOrder = moveItem(
|
||||
[...itemOrder],
|
||||
draggedItemId,
|
||||
newDraggingToItemIndex
|
||||
);
|
||||
move(tempItemOrder);
|
||||
setDraggingToItemIndex(newDraggingToItemIndex);
|
||||
setTempItemOrder(tempItemOrder);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onDragEnd: TrProps['onDragEnd'] = (evt) => {
|
||||
const target = evt.target as HTMLTableRowElement;
|
||||
target.classList.remove(styles.modifiers.ghostRow);
|
||||
target.setAttribute('aria-pressed', 'false');
|
||||
setDraggedItemId(null);
|
||||
setDraggingToItemIndex(null);
|
||||
setIsDragging(false);
|
||||
};
|
||||
|
||||
const moveItem = (arr: string[], i1: string, toIndex: number) => {
|
||||
const fromIndex = arr.indexOf(i1);
|
||||
if (fromIndex === toIndex) {
|
||||
return arr;
|
||||
}
|
||||
const temp = arr.splice(fromIndex, 1);
|
||||
arr.splice(toIndex, 0, temp[0]);
|
||||
|
||||
return arr;
|
||||
};
|
||||
|
||||
const move = (itemOrder: string[]) => {
|
||||
const ulNode = bodyRef.current;
|
||||
if (!ulNode) {
|
||||
return;
|
||||
}
|
||||
const nodes = Array.from(ulNode.children);
|
||||
if (nodes.map((node) => node.id).every((id, i) => id === itemOrder[i])) {
|
||||
return;
|
||||
}
|
||||
while (ulNode.firstChild) {
|
||||
ulNode.removeChild(ulNode.lastChild as Node);
|
||||
}
|
||||
|
||||
itemOrder.forEach((id) => {
|
||||
const node = nodes.find((n) => n.id === id);
|
||||
if (node) {
|
||||
ulNode.appendChild(node);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Table
|
||||
className={isDragging ? styles.modifiers.dragOver : ''}
|
||||
aria-label="File system table"
|
||||
variant="compact"
|
||||
>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th />
|
||||
<Th>Mount point</Th>
|
||||
<Th></Th>
|
||||
<Th>Type</Th>
|
||||
<Th>
|
||||
Minimum size
|
||||
<Popover
|
||||
hasAutoWidth
|
||||
bodyContent={
|
||||
<TextContent>
|
||||
<Text>
|
||||
Image Builder may extend this size based on requirements,
|
||||
selected packages, and configurations.
|
||||
</Text>
|
||||
</TextContent>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
variant="plain"
|
||||
aria-label="File system configuration info"
|
||||
aria-describedby="file-system-configuration-info"
|
||||
className="pf-c-form__group-label-help"
|
||||
>
|
||||
<HelpIcon />
|
||||
</Button>
|
||||
</Popover>
|
||||
</Th>
|
||||
<Th />
|
||||
<Th />
|
||||
</Tr>
|
||||
</Thead>
|
||||
|
||||
<Tbody
|
||||
onDragOver={onDragOver}
|
||||
onDrop={onDragOver}
|
||||
onDragLeave={onDragLeave}
|
||||
ref={bodyRef}
|
||||
data-testid="file-system-configuration-tbody"
|
||||
>
|
||||
{partitions &&
|
||||
partitions.map((partition) => (
|
||||
<Row
|
||||
onDrop={onDrop}
|
||||
onDragEnd={onDragEnd}
|
||||
onDragStart={onDragStart}
|
||||
key={partition.id}
|
||||
partition={partition}
|
||||
/>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
);
|
||||
};
|
||||
|
||||
export default FileSystemTable;
|
||||
Loading…
Add table
Add a link
Reference in a new issue