mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-20 07:26:19 +00:00
feat(catalog): catalogo modern Hippiehotel in albero separato (catalog-modern)
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
import { FC } from 'react';
|
||||
import { LocalizeText } from '../../../../api';
|
||||
import { useCatalogActions, useCatalogUiState } from '../../../../hooks';
|
||||
|
||||
export const CatalogBreadcrumbView: FC<{}> = () =>
|
||||
{
|
||||
const { activeNodes = [] } = useCatalogUiState();
|
||||
const { activateNode } = useCatalogActions();
|
||||
|
||||
if(!activeNodes || activeNodes.length === 0)
|
||||
{
|
||||
return (
|
||||
<div className="nitro-catalog-classic-breadcrumb">
|
||||
<span>{ LocalizeText('catalog.title') }</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="nitro-catalog-classic-breadcrumb">
|
||||
{ activeNodes.map((node, index) => (
|
||||
<span key={ node.pageId } className="nitro-catalog-classic-breadcrumb-segment">
|
||||
<span className="nitro-catalog-classic-breadcrumb-separator">›</span>
|
||||
<span
|
||||
className={ `truncate ${ index === activeNodes.length - 1 ? 'font-semibold' : 'cursor-pointer hover:underline' }` }
|
||||
onClick={ index < activeNodes.length - 1 ? () => activateNode(node) : undefined }
|
||||
>
|
||||
{ node.localization }
|
||||
</span>
|
||||
</span>
|
||||
)) }
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,146 @@
|
||||
import { FC, useCallback, useRef, useState } from 'react';
|
||||
import { FaArrowsAlt, FaCaretDown, FaCaretUp, FaPlus, FaStar, FaTrash } from 'react-icons/fa';
|
||||
import { CatalogType, ICatalogNode, LocalizeText } from '../../../../api';
|
||||
import { useCatalogActions, useCatalogFavorites, useCatalogUiState } from '../../../../hooks';
|
||||
import { useCatalogAdmin } from '../../CatalogAdminContext';
|
||||
import { CatalogIconView } from '../catalog-icon/CatalogIconView';
|
||||
import { CatalogNavigationSetView } from './CatalogNavigationSetView';
|
||||
|
||||
export interface CatalogNavigationItemViewProps
|
||||
{
|
||||
node: ICatalogNode;
|
||||
child?: boolean;
|
||||
}
|
||||
|
||||
export const CatalogNavigationItemView: FC<CatalogNavigationItemViewProps> = props =>
|
||||
{
|
||||
const { node = null, child = false } = props;
|
||||
const { activateNode = null } = useCatalogActions();
|
||||
const { currentType = CatalogType.NORMAL } = useCatalogUiState();
|
||||
const catalogAdmin = useCatalogAdmin();
|
||||
const adminMode = catalogAdmin?.adminMode ?? false;
|
||||
const { isFavoritePage, toggleFavoritePage } = useCatalogFavorites();
|
||||
const isFav = node ? isFavoritePage(node.pageId) : false;
|
||||
const [ isDragOver, setIsDragOver ] = useState(false);
|
||||
const dragRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const handleDragStart = useCallback((e: React.DragEvent) =>
|
||||
{
|
||||
if(!adminMode) return;
|
||||
|
||||
e.dataTransfer.setData('text/plain', JSON.stringify({ pageId: node.pageId, parentId: node.parent?.pageId ?? -1 }));
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
}, [ adminMode, node ]);
|
||||
|
||||
const handleDragOver = useCallback((e: React.DragEvent) =>
|
||||
{
|
||||
if(!adminMode) return;
|
||||
|
||||
e.preventDefault();
|
||||
e.dataTransfer.dropEffect = 'move';
|
||||
setIsDragOver(true);
|
||||
}, [ adminMode ]);
|
||||
|
||||
const handleDragLeave = useCallback(() =>
|
||||
{
|
||||
setIsDragOver(false);
|
||||
}, []);
|
||||
|
||||
const handleDrop = useCallback((e: React.DragEvent) =>
|
||||
{
|
||||
if(!adminMode) return;
|
||||
|
||||
e.preventDefault();
|
||||
setIsDragOver(false);
|
||||
|
||||
try
|
||||
{
|
||||
const data = JSON.parse(e.dataTransfer.getData('text/plain'));
|
||||
|
||||
if(data.pageId && data.pageId !== node.pageId)
|
||||
{
|
||||
// Drop onto a branch = reparent under this node
|
||||
// Drop onto a leaf = reorder as sibling
|
||||
const targetParentId = node.isBranch ? node.pageId : (node.parent?.pageId ?? -1);
|
||||
const targetIndex = node.isBranch ? 0 : (node.parent?.children?.indexOf(node) ?? 0);
|
||||
|
||||
catalogAdmin?.reorderPage(data.pageId, targetParentId, targetIndex);
|
||||
}
|
||||
}
|
||||
catch(err)
|
||||
{
|
||||
// Invalid drag data
|
||||
}
|
||||
}, [ adminMode, node, catalogAdmin ]);
|
||||
|
||||
return (
|
||||
<div className={ `nitro-catalog-classic-navigation-node ${ child ? 'is-child' : '' }` }>
|
||||
<div
|
||||
ref={ dragRef }
|
||||
className={ `nitro-catalog-classic-navigation-item group/nav ${ node.isActive ? 'is-active' : '' } ${ node.isBranch ? 'is-branch' : 'is-leaf' } ${ node.isOpen ? 'is-open' : '' } ${ isDragOver ? 'is-drag-over' : '' }` }
|
||||
draggable={ adminMode }
|
||||
onClick={ () => activateNode(node) }
|
||||
onDragLeave={ adminMode ? handleDragLeave : undefined }
|
||||
onDragOver={ adminMode ? handleDragOver : undefined }
|
||||
onDragStart={ adminMode ? handleDragStart : undefined }
|
||||
onDrop={ adminMode ? handleDrop : undefined }
|
||||
>
|
||||
{ adminMode &&
|
||||
<FaArrowsAlt className="nitro-catalog-classic-navigation-drag text-[7px] text-muted cursor-grab shrink-0 opacity-0 group-hover/nav:opacity-60" /> }
|
||||
<div className="nitro-catalog-classic-navigation-icon">
|
||||
<CatalogIconView icon={ node.iconId } />
|
||||
</div>
|
||||
<span className="nitro-catalog-classic-navigation-label" title={ adminMode ? `Page ID: ${ node.pageId }` : undefined }>{ node.localization }</span>
|
||||
{ adminMode &&
|
||||
<div className="nitro-catalog-classic-navigation-admin flex items-center gap-1 opacity-0 group-hover/nav:opacity-100 transition-opacity">
|
||||
<FaPlus
|
||||
className="text-[8px] text-success hover:text-green-800"
|
||||
title={ LocalizeText('catalog.admin.create.subpage') }
|
||||
onClick={ e =>
|
||||
{
|
||||
e.stopPropagation();
|
||||
catalogAdmin.createPage({
|
||||
caption: 'New Page',
|
||||
captionSave: 'New Page',
|
||||
catalogMode: currentType,
|
||||
pageLayout: 'default_3x3',
|
||||
iconImage: 0,
|
||||
minRank: 1,
|
||||
visible: '1',
|
||||
enabled: '1',
|
||||
orderNum: 0,
|
||||
parentId: node.pageId,
|
||||
});
|
||||
} }
|
||||
/>
|
||||
<FaTrash
|
||||
className="text-[8px] text-danger hover:text-red-700"
|
||||
title={ LocalizeText('catalog.admin.delete.page') }
|
||||
onClick={ e =>
|
||||
{
|
||||
e.stopPropagation();
|
||||
if(confirm(LocalizeText('catalog.admin.delete.page.confirm', [ 'name' ], [ node.localization ])))
|
||||
{
|
||||
catalogAdmin.deletePage(node.pageId);
|
||||
}
|
||||
} }
|
||||
/>
|
||||
</div> }
|
||||
{ !adminMode && node.pageId > 0 &&
|
||||
<FaStar
|
||||
className={ `text-[8px] transition-all duration-100 cursor-pointer shrink-0 ${ isFav ? 'text-warning opacity-100' : 'text-muted opacity-0 group-hover/nav:opacity-100 hover:text-warning' }` }
|
||||
onClick={ e =>
|
||||
{
|
||||
e.stopPropagation(); toggleFavoritePage(node.pageId);
|
||||
} }
|
||||
/> }
|
||||
{ node.isBranch &&
|
||||
<span className="nitro-catalog-classic-navigation-caret text-[9px] text-muted shrink-0">
|
||||
{ node.isOpen ? <FaCaretUp /> : <FaCaretDown /> }
|
||||
</span> }
|
||||
</div>
|
||||
{ node.isOpen && node.isBranch &&
|
||||
<CatalogNavigationSetView child={ true } node={ node } /> }
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
import { FC } from 'react';
|
||||
import { ICatalogNode } from '../../../../api';
|
||||
import { CatalogNavigationItemView } from './CatalogNavigationItemView';
|
||||
|
||||
export interface CatalogNavigationSetViewProps
|
||||
{
|
||||
node: ICatalogNode;
|
||||
child?: boolean;
|
||||
}
|
||||
|
||||
export const CatalogNavigationSetView: FC<CatalogNavigationSetViewProps> = props =>
|
||||
{
|
||||
const { node = null, child = false } = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
{ node && (node.children.length > 0) && node.children.map((n, index) =>
|
||||
{
|
||||
if(!n.isVisible) return null;
|
||||
|
||||
return <CatalogNavigationItemView key={ index } child={ child } node={ n } />;
|
||||
}) }
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
import { FC } from 'react';
|
||||
import { ICatalogNode } from '../../../../api';
|
||||
import { useCatalogData } from '../../../../hooks';
|
||||
import { CatalogNavigationItemView } from './CatalogNavigationItemView';
|
||||
import { CatalogNavigationSetView } from './CatalogNavigationSetView';
|
||||
|
||||
export interface CatalogNavigationViewProps
|
||||
{
|
||||
node: ICatalogNode;
|
||||
}
|
||||
|
||||
export const CatalogNavigationView: FC<CatalogNavigationViewProps> = props =>
|
||||
{
|
||||
const { node = null } = props;
|
||||
const { searchResult = null } = useCatalogData();
|
||||
|
||||
return (
|
||||
<div className="nitro-catalog-classic-navigation-list">
|
||||
{ searchResult && (searchResult.filteredNodes.length > 0) && searchResult.filteredNodes.map((n, index) =>
|
||||
{
|
||||
return <CatalogNavigationItemView key={ index } node={ n } />;
|
||||
}) }
|
||||
{ !searchResult &&
|
||||
<CatalogNavigationSetView node={ node } /> }
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user