UNPKG

seti-ramesesv1

Version:

Reusable components and context for Next.js apps

262 lines (259 loc) 12 kB
import { jsxs, jsx } from 'react/jsx-runtime'; import { useState, useEffect } from 'react'; import styles from '../../../styles/CompassEditor.module.css.js'; import RowItem from './CompassEditorRowItem.js'; import Pencil from '../../../node_modules/lucide-react/dist/esm/icons/pencil.js'; import Copy from '../../../node_modules/lucide-react/dist/esm/icons/copy.js'; import Trash from '../../../node_modules/lucide-react/dist/esm/icons/trash.js'; // Simple function to generate MongoDB-like ObjectId const generateObjectId = () => Math.floor(Math.random() * 0xffffffff) .toString(16) .padStart(8, "0") + Math.floor(Math.random() * 0xffffffff) .toString(16) .padStart(8, "0") + Math.floor(Math.random() * 0xffffffff) .toString(16) .padStart(8, "0"); const CompassEditor = ({ data, onGetJson, }) => { const [editMode, setEditMode] = useState(false); const [rows, setRows] = useState([]); const [previousRows, setPreviousRows] = useState([]); const [expanded, setExpanded] = useState(new Map()); const [error, setError] = useState(null); const convertToRows = (inputData, parentId) => { const rows = []; let currentId = parentId || Date.now(); if (!parentId && !inputData.hasOwnProperty("_id")) { const id = currentId++; rows.push({ id, keyName: "_id", value: generateObjectId(), dataType: "string", keyWidth: 50, valWidth: 200, }); } for (const [key, value] of Object.entries(inputData)) { const id = currentId++; let dataType = typeof value === "object" ? "object" : typeof value === "boolean" ? "boolean" : typeof value === "number" ? "number" : "string"; let rowValue = value.toString(); if (dataType === "object" && value !== null) { const children = convertToRows(value, currentId); currentId += children.length; rows.push({ id, keyName: key, value: "Object", dataType, keyWidth: 50, valWidth: 200, children }); } else { rows.push({ id, keyName: key, value: rowValue, dataType, keyWidth: 50, valWidth: 50 }); } } return rows; }; useEffect(() => { const initialRows = data ? convertToRows(data) : [ { id: Date.now(), keyName: "_id", value: generateObjectId(), dataType: "string", keyWidth: 50, valWidth: 200, }, { id: Date.now() + 1, keyName: "", value: "", dataType: "string", keyWidth: 50, valWidth: 50, }, ]; setRows(initialRows); setPreviousRows([...initialRows]); // Initialize expanded state for all parent rows const initialExpanded = new Map(); initialRows.forEach((row) => { if (row.children && row.children.length > 0) { initialExpanded.set(row.id, true); // Expand by default } }); setExpanded(initialExpanded); }, [data]); const handleUpdate = () => { // ✅ Validation const invalidRows = findInvalidRows(rows); if (invalidRows.length > 0) { setError("Unable to update, key and value cannot be empty."); return; } setError(null); // ✅ Build cleanData directly from rows in current UI order, including parents with children const cleanData = rows.reduce((obj, r) => { if (r.keyName.trim()) { // Include parent rows if they have a key, even if value is "Object" const convertValue = (row) => { let val = row.value; if (row.dataType === "number") val = Number(row.value); if (row.dataType === "boolean" && typeof row.value === "string") val = row.value.toLowerCase() === "true"; if (row.dataType === "object" && row.children) { // Recursively process children in their current order val = row.children.reduce((childObj, child) => { if (child.keyName.trim() && child.value.trim()) { childObj[child.keyName.trim()] = convertValue(child); } return childObj; }, {}); } return val; }; // Include the row if it has a key, and use convertValue to handle children obj[r.keyName.trim()] = convertValue(r); } return obj; }, {}); onGetJson?.(cleanData); setPreviousRows([...rows]); // Save current state as new baseline setEditMode(false); }; const handleAddRow = (id, isParentLevel = false) => { const newRow = { id: Date.now(), keyName: "", value: "", dataType: "string", keyWidth: 50, valWidth: 50 }; const insertRow = (rows, targetId, isParent, parentRows = rows) => { if (isParent) { const index = rows.findIndex((r) => r.id === targetId); if (index !== -1) { return [...rows.slice(0, index + 1), newRow, ...rows.slice(index + 1)]; } return rows; } return rows.reduce((acc, r, index) => { if (r.id === targetId) { // Add a new row as a sibling immediately below the target row return [...acc, r, newRow]; } else if (r.children && r.children.length > 0) { return [...acc, { ...r, children: insertRow(r.children, targetId, false, r.children) }]; } return [...acc, r]; }, []); }; setRows((prev) => { const parentRow = findParentRow(prev, id); return insertRow(prev, id, isParentLevel, parentRow ? parentRow.children || prev : prev); }); }; const findParentRow = (rows, targetId) => { for (const r of rows) { if (r.children && r.children.some((child) => child.id === targetId)) { return r; } if (r.children && r.children.length > 0) { const parent = findParentRow(r.children, targetId); if (parent) return parent; } } return undefined; }; const findInvalidRows = (rows) => { let invalid = []; for (const r of rows) { if (!r.keyName.trim() || (!r.children && !r.value.trim())) { invalid.push(r); } if (r.children && r.children.length > 0) { invalid = invalid.concat(findInvalidRows(r.children)); } } return invalid; }; const deleteRow = (rows, targetId) => { return rows.reduce((acc, r) => { if (r.id === targetId) { return acc; // Skip this row (delete it) } else if (r.children && r.children.length > 0) { const updatedChildren = deleteRow(r.children, targetId); if (updatedChildren.length === 0) { updatedChildren.push({ id: Date.now(), keyName: "", value: "", dataType: "string", keyWidth: 50, valWidth: 50, }); } return [...acc, { ...r, children: updatedChildren }]; } return [...acc, r]; }, []); }; const handleDeleteRow = (id) => { setRows((prev) => { const updatedRows = deleteRow(prev, id); if (updatedRows.length === 1 && updatedRows[0].keyName === "_id") { updatedRows.push({ id: Date.now(), keyName: "", value: "", dataType: "string", keyWidth: 50, valWidth: 50 }); } return updatedRows; }); }; const updateRow = (id, field, value, parentRows = rows) => { return parentRows.map((r) => { if (r.id === id) { const updated = { ...r, [field]: value }; if (field === "dataType" && value === "object" && (!r.children || r.children.length === 0)) { updated.children = [ { id: Date.now() + 1, keyName: "", value: "", dataType: "string", keyWidth: 50, valWidth: 50 }, ]; } return updated; } if (r.children && r.children.length > 0) { return { ...r, children: updateRow(id, field, value, r.children) }; } return r; }); }; const handleUpdateRow = (id, field, value) => { setRows((prev) => updateRow(id, field, value, prev)); }; const isInvalid = (value, type) => { if (value.trim() === "") return false; if (type === "number") return !/^-?\d+$/.test(value.trim()); if (type === "boolean") return value.toLowerCase() !== "true" && value.toLowerCase() !== "false"; return false; }; const toggleExpand = (id) => { setExpanded((prev) => { const newExpanded = new Map(prev); newExpanded.set(id, !prev.get(id)); return newExpanded; }); }; const renderRows = (rows, depth = 0, counter = { value: 1 }) => rows.map((row) => (jsx(RowItem, { row: row, depth: depth, number: counter.value++, editMode: editMode, expanded: expanded, onToggleExpand: toggleExpand, onUpdateRow: handleUpdateRow, onAddRow: handleAddRow, onDeleteRow: handleDeleteRow, isInvalid: isInvalid, renderChildren: (children, d) => renderRows(children, d, counter) }, row.id))); const handleCancel = () => { setRows([...previousRows]); // Revert to the previous state setEditMode(false); setError(null); }; return (jsxs("div", { className: styles.container, children: [jsx("div", { className: styles.actionWrapper, children: !editMode && (jsxs("div", { className: styles.actionGroup, children: [jsx("button", { onClick: () => { setPreviousRows([...rows]); setEditMode(true); }, className: styles.iconButton, children: jsx(Pencil, { size: 16 }) }), jsx("button", { onClick: () => console.log("Copy clicked"), className: styles.iconButton, children: jsx(Copy, { size: 16 }) }), jsx("button", { onClick: () => console.log("Delete clicked"), className: styles.iconButton, children: jsx(Trash, { size: 16 }) })] })) }), renderRows(rows), jsxs("div", { className: `${styles.footer} ${error ? styles.footerError : ""}`, children: [error && jsx("div", { className: styles.errorText, children: error }), editMode && (jsxs("div", { className: styles.footerButtons, children: [jsx("button", { onClick: handleCancel, className: styles.cancelBtn, children: "Cancel" }), jsx("button", { onClick: handleUpdate, disabled: rows.some((r) => isInvalid(r.value, r.dataType)), className: `${styles.updateBtn} ${rows.some((r) => isInvalid(r.value, r.dataType)) ? styles.updateBtnDisabled : styles.updateBtnActive}`, children: "Update" })] }))] })] })); }; export { CompassEditor as default }; //# sourceMappingURL=CompassEditor.js.map