import React, { useEffect, useRef, useState } from 'react'; import { FormSpy } from '@data-driven-forms/react-form-renderer'; import useFieldApi from '@data-driven-forms/react-form-renderer/use-field-api'; import useFormApi from '@data-driven-forms/react-form-renderer/use-form-api'; import { Alert, Button, Popover, Text, TextContent, TextVariants, } from '@patternfly/react-core'; import { HelpIcon, MinusCircleIcon, PlusCircleIcon, } from '@patternfly/react-icons'; import styles from '@patternfly/react-styles/css/components/Table/table'; import { TableComposable, Tbody, Td, Th, Thead, Tr, } from '@patternfly/react-table'; import { v4 as uuidv4 } from 'uuid'; import MountPoint from './MountPoint'; import SizeUnit from './SizeUnit'; import { UNIT_GIB } from '../../../constants'; let initialRow = { id: uuidv4(), mountpoint: '/', fstype: 'xfs', size: 10, unit: UNIT_GIB, }; const FileSystemConfiguration = ({ ...props }) => { const { change, getState } = useFormApi(); const { input } = useFieldApi(props); const [draggedItemId, setDraggedItemId] = useState(null); const [draggingToItemIndex, setDraggingToItemIndex] = useState(null); const [isDragging, setIsDragging] = useState(false); const [itemOrder, setItemOrder] = useState([initialRow.id]); const [tempItemOrder, setTempItemOrder] = useState([]); const bodyref = useRef(); const [rows, setRows] = useState([initialRow]); useEffect(() => { const fsc = getState()?.values?.['file-system-configuration']; if (!fsc) { return; } const newRows = []; const newOrder = []; fsc.map((r) => { const id = uuidv4(); newRows.push({ id, mountpoint: r.mountpoint, fstype: 'xfs', size: r.size, unit: r.unit, }); newOrder.push(id); }); setRows(newRows); setItemOrder(newOrder); }, []); const showErrors = () => getState()?.values?.['file-system-config-show-errors']; useEffect(() => { change( input.name, itemOrder.map((r) => { for (const r2 of rows) { if (r2.id === r) { return { mountpoint: r2.mountpoint, size: r2.size, unit: r2.unit, }; } } }) ); }, [rows, itemOrder]); const addRow = () => { const id = uuidv4(); setRows( rows.concat([ { id, mountpoint: '/home', fstype: 'xfs', size: 1, unit: UNIT_GIB, }, ]) ); setItemOrder(itemOrder.concat([id])); }; const removeRow = (id) => { let removeIndex = rows.map((e) => e.id).indexOf(id); let newRows = [...rows]; newRows.splice(removeIndex, 1); let removeOrderIndex = itemOrder.indexOf(id); let newOrder = [...itemOrder]; newOrder.splice(removeOrderIndex, 1); setRows(newRows); setItemOrder(newOrder); }; const moveItem = (arr, i1, toIndex) => { 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) => { const ulNode = bodyref.current; 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); } itemOrder.forEach((id) => { ulNode.appendChild(nodes.find((n) => n.id === id)); }); }; const onDragOver = (evt) => { evt.preventDefault(); const curListItem = evt.target.closest('tr'); if (!curListItem || !bodyref.current.contains(curListItem)) { return null; } const dragId = curListItem.id; const newDraggingToItemIndex = Array.from( bodyref.current.children ).findIndex((item) => item.id === dragId); if (newDraggingToItemIndex !== draggingToItemIndex) { const tempItemOrder = moveItem( [...itemOrder], draggedItemId, newDraggingToItemIndex ); move(tempItemOrder); setDraggingToItemIndex(newDraggingToItemIndex); setTempItemOrder(tempItemOrder); } }; const isValidDrop = (evt) => { const ulRect = bodyref.current.getBoundingClientRect(); return ( evt.clientX > ulRect.x && evt.clientX < ulRect.x + ulRect.width && evt.clientY > ulRect.y && evt.clientY < ulRect.y + ulRect.height ); }; const onDragLeave = (evt) => { if (!isValidDrop(evt)) { move(itemOrder); setDraggingToItemIndex(null); } }; const onDrop = (evt) => { if (isValidDrop(evt)) { setItemOrder(tempItemOrder); } }; const onDragStart = (evt) => { evt.dataTransfer.effectAllowed = 'move'; evt.dataTransfer.setData('text/plain', evt.currentTarget.id); evt.currentTarget.classList.add(styles.modifiers.ghostRow); evt.currentTarget.setAttribute('aria-pressed', 'true'); setDraggedItemId(evt.currentTarget.id); setIsDragging(true); }; const onDragEnd = (evt) => { evt.target.classList.remove(styles.modifiers.ghostRow); evt.target.setAttribute('aria-pressed', 'false'); setDraggedItemId(null); setDraggingToItemIndex(null); setIsDragging(false); }; const setMountpoint = (id, mp) => { let newRows = [...rows]; for (let i = 0; i < newRows.length; i++) { if (newRows[i].id === id) { let newRow = { ...newRows[i] }; newRow.mountpoint = mp; newRows.splice(i, 1, newRow); break; } } setRows(newRows); }; const setSize = (id, s, u) => { let newRows = [...rows]; for (let i = 0; i < newRows.length; i++) { if (newRows[i].id === id) { let newRow = { ...newRows[i] }; newRow.size = s; newRow.unit = u; newRows.splice(i, 1, newRow); break; } } setRows(newRows); }; return ( {() => ( <> Configure partitions {rows.length > 1 && getState()?.errors?.['file-system-configuration']?.duplicates ?.length !== 0 && showErrors() && ( )} {rows.length >= 1 && getState()?.errors?.['file-system-configuration']?.root === false && showErrors() && ( )} Partitions have been generated and given default values based on best practices from Red Hat, and your selections in previous steps of the wizard. Mount point Type Minimum size Image Builder may extend this size based on requirements, selected packages, and configurations. } > {rows.map((row, rowIndex) => ( setMountpoint(row.id, mp)} /> {getState().errors['file-system-configuration']?.duplicates .length !== 0 && getState().errors[ 'file-system-configuration' ]?.duplicates.indexOf(row.mountpoint) !== -1 && showErrors() && ( )} {/* always xfs */} {row.fstype} setSize(row.id, s, u)} /> )} ); }; export default FileSystemConfiguration;