Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

41 changes: 35 additions & 6 deletions src/components/editor/NavigationPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
} from '@apicurio/data-models';
import { CreatePathModal } from '@components/modals/CreatePathModal';
import { CreateSchemaModal } from '@components/modals/CreateSchemaModal';
import { ConfirmDeleteModal } from '@components/modals/ConfirmDeleteModal';
import { CreatePathCommand } from '@commands/CreatePathCommand';
import { CreateSchemaCommand } from '@commands/CreateSchemaCommand';
import { DeletePathCommand } from '@commands/DeletePathCommand';
Expand All @@ -40,6 +41,8 @@ export const NavigationPanel: React.FC = () => {
const { executeCommand } = useCommand();
const [isCreatePathModalOpen, setIsCreatePathModalOpen] = useState(false);
const [isCreateSchemaModalOpen, setIsCreateSchemaModalOpen] = useState(false);
const [isDeleteConfirmModalOpen, setIsDeleteConfirmModalOpen] = useState(false);
const [deleteInfo, setDeleteInfo] = useState<{ name: string; type: 'path' | 'schema' }>({ name: '', type: 'path' });
const [filterText, setFilterText] = useState('');

/**
Expand Down Expand Up @@ -175,17 +178,29 @@ export const NavigationPanel: React.FC = () => {
* Handle deleting a path from context menu
*/
const handleDeletePath = (pathName: string) => {
const command = new DeletePathCommand(pathName);
executeCommand(command, `Delete path ${pathName}`);
selectRoot();
setDeleteInfo({ name: pathName, type: 'path' });
setIsDeleteConfirmModalOpen(true);
};

/**
* Handle deleting a schema from context menu
*/
const handleDeleteSchema = (schemaName: string) => {
const command = new DeleteSchemaCommand(schemaName);
executeCommand(command, `Delete schema ${schemaName}`);
setDeleteInfo({ name: schemaName, type: 'schema' });
setIsDeleteConfirmModalOpen(true);
};

/**
* Confirm deletion of path or schema
*/
const handleConfirmDelete = () => {
if (deleteInfo.type === 'path') {
const command = new DeletePathCommand(deleteInfo.name);
executeCommand(command, `Delete path ${deleteInfo.name}`);
} else {
const command = new DeleteSchemaCommand(deleteInfo.name);
executeCommand(command, `Delete schema ${deleteInfo.name}`);
}
selectRoot();
};

Expand All @@ -211,7 +226,7 @@ export const NavigationPanel: React.FC = () => {

return (
<>
<Nav aria-label="Navigation" onSelect={() => {}}>
<Nav aria-label="Navigation" onSelect={() => { }}>
<NavList>
{/* Main/Info Section */}
<NavItem
Expand Down Expand Up @@ -280,6 +295,20 @@ export const NavigationPanel: React.FC = () => {
onClose={() => setIsCreateSchemaModalOpen(false)}
onConfirm={handleCreateSchema}
/>

{/* Confirm Delete Modal */}
<ConfirmDeleteModal
isOpen={isDeleteConfirmModalOpen}
onClose={() => setIsDeleteConfirmModalOpen(false)}
onConfirm={handleConfirmDelete}
title={`Confirm Delete ${deleteInfo.type === 'path' ? 'Path' : 'Schema'}`}
message={
<p>
Are you sure you want to delete the {deleteInfo.type}{' '}
<strong>{deleteInfo.name}</strong>? This action cannot be undone.
</p>
}
/>
</>
);
};
32 changes: 32 additions & 0 deletions src/components/editor/NavigationPanelSection.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,35 @@
a.pf-contextMenu {
background-color: #eee;
}

.nav-item-content {
display: flex;
align-items: center;
width: 100%;
}

.nav-item-link {
flex-grow: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding-right: 8px;
}

.item-delete-button {
padding: 2px !important;
min-width: 0 !important;
min-height: 0 !important;
margin-left: auto;
opacity: 0;
transition: opacity 0.2s;
color: var(--pf-v6-global--Color--200);
}

.nav-panel-item:hover .item-delete-button {
opacity: 1;
}

.item-delete-button:hover {
color: var(--pf-v6-global--danger-color--100) !important;
}
81 changes: 55 additions & 26 deletions src/components/editor/NavigationPanelSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import {
MenuList,
MenuItem, Tooltip,
} from '@patternfly/react-core';
import { PlusCircleIcon } from '@patternfly/react-icons';
import { PlusCircleIcon, TrashIcon } from '@patternfly/react-icons';
import { ExpandablePanel } from '@components/common/ExpandablePanel';
import {PathLabel} from "@components/common/PathLabel.tsx";
import { PathLabel } from "@components/common/PathLabel.tsx";

/**
* Context menu action definition
Expand Down Expand Up @@ -185,30 +185,59 @@ export const NavigationPanelSection: React.FC<NavigationPanelSectionProps> = ({
{isFiltered ? `No matching ${itemType}s` : `No ${itemType}s defined`}
</NavItem>
) : (
items.map((itemName) => {
const isActive = isItemActive(itemName);
const hasContextMenu = contextMenuItem === itemName;

return (
<NavItem
key={itemName}
itemId={`${itemType}-${itemName}`}
isActive={isActive}
onClick={() => onItemClick(itemName)}
style={hasContextMenu ? { backgroundColor: 'var(--pf-v6-global--BackgroundColor--200)' } : undefined}
>
{isTooltipEnabled ? (
<a className={hasContextMenu ? "pf-contextMenu" : undefined} onContextMenu={(e) => handleContextMenu(e, itemName)} style={{width: "100%", overflowX: "hidden", textWrap: "nowrap"}}>
<Tooltip content={<div>{itemName}</div>}>
<PathLabel path={itemName} />
</Tooltip>
</a>
) : (
<a className={hasContextMenu ? "pf-contextMenu" : undefined} onContextMenu={(e) => handleContextMenu(e, itemName)}>{itemName}</a>
)}
</NavItem>
);
})
(() => {
const deleteAction = actions.find(a => a.label.toLowerCase().includes('delete'));

return items.map((itemName) => {
const isActive = isItemActive(itemName);
const hasContextMenu = contextMenuItem === itemName;

return (
<NavItem
key={itemName}
itemId={`${itemType}-${itemName}`}
isActive={isActive}
onClick={() => onItemClick(itemName)}
style={hasContextMenu ? { backgroundColor: 'var(--pf-v6-global--BackgroundColor--200)' } : undefined}
className="nav-panel-item"
>
<div className="nav-item-content">
{isTooltipEnabled ? (
<a
className={hasContextMenu ? "pf-contextMenu nav-item-link" : "nav-item-link"}
onContextMenu={(e) => handleContextMenu(e, itemName)}
style={{ flexGrow: 1, overflowX: "hidden", textWrap: "nowrap" }}
>
<Tooltip content={<div>{itemName}</div>}>
<PathLabel path={itemName} />
</Tooltip>
</a>
) : (
<a
className={hasContextMenu ? "pf-contextMenu nav-item-link" : "nav-item-link"}
onContextMenu={(e) => handleContextMenu(e, itemName)}
style={{ flexGrow: 1 }}
>
{itemName}
</a>
)}
{deleteAction && (
<Button
variant="plain"
aria-label={`Delete ${itemType} ${itemName}`}
onClick={(e) => {
e.stopPropagation();
deleteAction.onClick(itemName);
}}
icon={<TrashIcon />}
className="item-delete-button"
/>
)}
</div>
</NavItem>
);
});
})()
)}
</ExpandablePanel>

Expand Down
51 changes: 51 additions & 0 deletions src/components/modals/ConfirmDeleteModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React from 'react';
import {
Modal,
ModalVariant,
ModalHeader,
ModalBody,
ModalFooter,
Button,
} from '@patternfly/react-core';

export interface ConfirmDeleteModalProps {
isOpen: boolean;
onClose: () => void;
onConfirm: () => void;
title: string;
message: string | React.ReactNode;
}

export const ConfirmDeleteModal: React.FC<ConfirmDeleteModalProps> = ({
isOpen,
onClose,
onConfirm,
title,
message
}) => {
return (
<Modal
variant={ModalVariant.small}
isOpen={isOpen}
onClose={onClose}
aria-labelledby="confirm-delete-modal-title"
aria-describedby="confirm-delete-modal-body"
>
<ModalHeader title={title} labelId="confirm-delete-modal-title" />
<ModalBody id="confirm-delete-modal-body">
{message}
</ModalBody>
<ModalFooter>
<Button key="cancel" variant="link" onClick={onClose}>
Cancel
</Button>
<Button key="confirm" variant="danger" onClick={() => {
onConfirm();
onClose();
}}>
Delete
</Button>
</ModalFooter>
</Modal>
);
};
Loading