Files
aerbim-ht-monitor/frontend/components/model/SceneToolbar.tsx

207 lines
6.8 KiB
TypeScript

import React, { useState } from 'react';
import Image from 'next/image';
import useNavigationStore from '@/app/store/navigationStore';
import type { Zone } from '@/app/types';
interface ToolbarButton {
icon: string;
label: string;
onClick: () => void;
onMouseDown?: () => void;
onMouseUp?: () => void;
active?: boolean;
children?: ToolbarButton[];
}
interface SceneToolbarProps {
onZoomIn?: () => void;
onZoomOut?: () => void;
onTopView?: () => void;
onPan?: () => void;
onSelectModel?: (modelPath: string) => void;
panActive?: boolean;
navMenuActive?: boolean;
}
const SceneToolbar: React.FC<SceneToolbarProps> = ({
onZoomIn,
onZoomOut,
onTopView,
onPan,
onSelectModel,
panActive = false,
navMenuActive = false,
}) => {
const [isZoomOpen, setIsZoomOpen] = useState(false);
const { showMonitoring, openMonitoring, closeMonitoring, currentZones, loadZones, currentObject } = useNavigationStore();
const handleToggleNavMenu = () => {
if (showMonitoring) {
closeMonitoring();
} else {
openMonitoring();
}
};
const handleHomeClick = async () => {
if (!onSelectModel) return;
try {
let zones: Zone[] = Array.isArray(currentZones) ? currentZones : [];
// Если зоны ещё не загружены, откройте Monitoring и загрузите зоны для текущего объекта
if ((!zones || zones.length === 0) && currentObject?.id) {
if (!showMonitoring) {
openMonitoring();
}
await loadZones(currentObject.id);
zones = useNavigationStore.getState().currentZones || [];
}
if (!Array.isArray(zones) || zones.length === 0) {
console.warn('No zones available to select a model from.');
return;
}
const sorted = zones.slice().sort((a: Zone, b: Zone) => {
const oa = typeof a.order === 'number' ? a.order : 0;
const ob = typeof b.order === 'number' ? b.order : 0;
if (oa !== ob) return oa - ob;
return (a.name || '').localeCompare(b.name || '');
});
const top = sorted[0];
let chosenPath: string | null = top?.model_path && String(top.model_path).trim() ? top.model_path! : null;
if (!chosenPath) {
const nextWithModel = sorted.find((z) => z.model_path && String(z.model_path).trim());
chosenPath = nextWithModel?.model_path ?? null;
}
if (chosenPath) {
onSelectModel(chosenPath);
} else {
console.warn('No zone has a valid model_path to open.');
}
} catch (error) {
console.error('Error selecting top zone model:', error);
}
};
const defaultButtons: ToolbarButton[] = [
{
icon: '/icons/Zoom.png',
label: 'Zoom',
onClick: () => setIsZoomOpen(!isZoomOpen),
active: isZoomOpen,
children: [
{
icon: '/icons/plus.svg',
label: 'Zoom In',
onClick: onZoomIn || (() => {}),
},
{
icon: '/icons/minus.svg',
label: 'Zoom Out',
onClick: onZoomOut || (() => {}),
},
]
},
{
icon: '/icons/Video.png',
label: "Top View",
onClick: onTopView || (() => console.log('Top View')),
},
{
icon: '/icons/Pointer.png',
label: 'Pan',
onClick: onPan || (() => console.log('Pan')),
active: panActive,
},
{
icon: '/icons/Warehouse.png',
label: 'Home',
onClick: handleHomeClick,
},
{
icon: '/icons/Layers.png',
label: 'Levels',
onClick: handleToggleNavMenu,
active: navMenuActive,
},
];
return (
<div className="fixed right-5 top-1/2 transform -translate-y-1/2 z-50">
<div className="flex flex-col gap-0">
<div
className="flex flex-col items-center gap-2 py-4 bg-[#161824] rounded-[15px] border border-white/10 shadow-[0_8px_32px_rgba(0,0,0,0.3)]"
style={{ minHeight: '320px' }}
>
{defaultButtons.map((button, index) => (
<div key={index} className="flex flex-col items-center gap-2">
<button
onClick={button.onClick}
className={`
relative group flex items-center justify-center w-16 h-12 rounded-lg transition-all duration-200
hover:bg-blue-600/20 hover:scale-110 hover:shadow-lg
focus:outline-none focus:ring-2 focus:ring-blue-500/50
${button.active
? 'bg-blue-600/30 text-blue-400 shadow-md'
: 'bg-transparent text-gray-300 hover:text-blue-400'
}
`}
title={button.label}
>
<Image
src={button.icon}
alt={button.label}
width={20}
height={20}
className="w-5 h-5 transition-transform duration-200 group-hover:scale-110"
/>
<div className="absolute right-full mr-3 top-1/2 transform -translate-y-1/2
opacity-0 group-hover:opacity-100 transition-opacity duration-200
pointer-events-none z-60">
<div className="bg-gray-900 text-white text-xs px-2 py-1 rounded
whitespace-nowrap shadow-lg border border-gray-700">
{button.label}
</div>
<div className="absolute left-full top-1/2 transform -translate-y-1/2
w-0 h-0 border-t-4 border-t-transparent
border-b-4 border-b-transparent
border-l-4 border-l-gray-900">
</div>
</div>
</button>
{button.active && button.children && (
<div className="flex flex-col gap-2 mt-2">
{button.children.map((childButton, childIndex) => (
<button
key={childIndex}
onClick={childButton.onClick}
onMouseDown={childButton.onMouseDown}
onMouseUp={childButton.onMouseUp}
className="relative group flex items-center justify-center w-12 h-10 bg-gray-800/50 rounded-md transition-all duration-200 hover:bg-blue-600/30"
title={childButton.label}
>
<Image
src={childButton.icon}
alt={childButton.label}
width={16}
height={16}
className="w-4 h-4"
/>
</button>
))}
</div>
)}
</div>
))}
</div>
</div>
</div>
);
};
export default SceneToolbar;