feat: frontend MVP — детальная схема связей устройств (AntV X6)
- React 18 + TypeScript strict + AntV X6 2.x + AntD 5 + Zustand - Custom nodes: SiteNode, CrossDeviceNode, SpliceNode, DeviceNode, CardNode - 8-слойный автолейаут, порты (left/right), линии с цветами по статусу - Toolbar, дерево навигации, карточка объекта, таблица соединений - Контекстные меню, легенда, drag линий/нод, создание линий из портов - Моковые данные: 3 сайта, 10 устройств, 15 линий Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
176
frontend/src/components/Toolbar/Toolbar.tsx
Normal file
176
frontend/src/components/Toolbar/Toolbar.tsx
Normal file
@ -0,0 +1,176 @@
|
||||
import { Button, Slider, Space, Switch, Tooltip, message } from 'antd';
|
||||
import {
|
||||
ZoomInOutlined,
|
||||
ZoomOutOutlined,
|
||||
ExpandOutlined,
|
||||
PlusOutlined,
|
||||
DeleteOutlined,
|
||||
ReloadOutlined,
|
||||
PictureOutlined,
|
||||
AppstoreOutlined,
|
||||
NodeIndexOutlined,
|
||||
EyeOutlined,
|
||||
EditOutlined,
|
||||
InfoCircleOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { useSchemaStore } from '../../store/schemaStore.ts';
|
||||
|
||||
export function Toolbar() {
|
||||
const graph = useSchemaStore((s) => s.graph);
|
||||
const mode = useSchemaStore((s) => s.mode);
|
||||
const setMode = useSchemaStore((s) => s.setMode);
|
||||
const displaySettings = useSchemaStore((s) => s.displaySettings);
|
||||
const toggleGrid = useSchemaStore((s) => s.toggleGrid);
|
||||
const toggleMinimap = useSchemaStore((s) => s.toggleMinimap);
|
||||
const switchLineType = useSchemaStore((s) => s.switchLineType);
|
||||
const toggleLabels = useSchemaStore((s) => s.toggleLabels);
|
||||
const setLegendVisible = useSchemaStore((s) => s.setLegendVisible);
|
||||
|
||||
const zoom = graph ? Math.round(graph.zoom() * 100) : 100;
|
||||
|
||||
const handleZoomIn = () => graph?.zoom(0.1);
|
||||
const handleZoomOut = () => graph?.zoom(-0.1);
|
||||
const handleFit = () => graph?.zoomToFit({ padding: 40 });
|
||||
const handleZoomChange = (value: number) => {
|
||||
if (graph) {
|
||||
graph.zoomTo(value / 100);
|
||||
}
|
||||
};
|
||||
|
||||
const handleExportPng = () => {
|
||||
message.info('В разработке');
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
padding: '0 16px',
|
||||
height: 48,
|
||||
borderBottom: '1px solid #f0f0f0',
|
||||
background: '#fff',
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
{/* Left: display settings */}
|
||||
<Space size="middle">
|
||||
<Tooltip title="Сетка">
|
||||
<Switch
|
||||
size="small"
|
||||
checked={displaySettings.showGrid}
|
||||
onChange={toggleGrid}
|
||||
checkedChildren={<AppstoreOutlined />}
|
||||
unCheckedChildren={<AppstoreOutlined />}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="Мини-карта">
|
||||
<Switch
|
||||
size="small"
|
||||
checked={displaySettings.showMinimap}
|
||||
onChange={toggleMinimap}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
title={
|
||||
displaySettings.lineType === 'manhattan'
|
||||
? 'Ломаные линии'
|
||||
: 'Прямые линии'
|
||||
}
|
||||
>
|
||||
<Button
|
||||
size="small"
|
||||
icon={<NodeIndexOutlined />}
|
||||
onClick={switchLineType}
|
||||
type={displaySettings.lineType === 'manhattan' ? 'primary' : 'default'}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="Подписи">
|
||||
<Switch
|
||||
size="small"
|
||||
checked={displaySettings.showLabels}
|
||||
onChange={toggleLabels}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="Легенда">
|
||||
<Button
|
||||
size="small"
|
||||
icon={<InfoCircleOutlined />}
|
||||
onClick={() => setLegendVisible(true)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Space>
|
||||
|
||||
{/* Center: actions */}
|
||||
<Space>
|
||||
<Tooltip title="Добавить объект">
|
||||
<Button
|
||||
size="small"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => message.info('В разработке')}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="Удалить">
|
||||
<Button
|
||||
size="small"
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={() => {
|
||||
if (graph) {
|
||||
const cells = graph.getSelectedCells();
|
||||
if (cells.length) graph.removeCells(cells);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="Обновить раскладку">
|
||||
<Button
|
||||
size="small"
|
||||
icon={<ReloadOutlined />}
|
||||
onClick={() => message.info('В разработке')}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="Экспорт PNG">
|
||||
<Button
|
||||
size="small"
|
||||
icon={<PictureOutlined />}
|
||||
onClick={handleExportPng}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Space>
|
||||
|
||||
{/* Right: zoom + mode */}
|
||||
<Space size="middle">
|
||||
<Space size={4}>
|
||||
<Tooltip title="Уменьшить">
|
||||
<Button size="small" icon={<ZoomOutOutlined />} onClick={handleZoomOut} />
|
||||
</Tooltip>
|
||||
<Slider
|
||||
style={{ width: 100 }}
|
||||
min={10}
|
||||
max={300}
|
||||
value={zoom}
|
||||
onChange={handleZoomChange}
|
||||
tooltip={{ formatter: (v) => `${v}%` }}
|
||||
/>
|
||||
<Tooltip title="Увеличить">
|
||||
<Button size="small" icon={<ZoomInOutlined />} onClick={handleZoomIn} />
|
||||
</Tooltip>
|
||||
<Tooltip title="Уместить на экран">
|
||||
<Button size="small" icon={<ExpandOutlined />} onClick={handleFit} />
|
||||
</Tooltip>
|
||||
</Space>
|
||||
<Tooltip title={mode === 'view' ? 'Режим просмотра' : 'Режим редактирования'}>
|
||||
<Button
|
||||
size="small"
|
||||
type={mode === 'edit' ? 'primary' : 'default'}
|
||||
icon={mode === 'view' ? <EyeOutlined /> : <EditOutlined />}
|
||||
onClick={() => setMode(mode === 'view' ? 'edit' : 'view')}
|
||||
>
|
||||
{mode === 'view' ? 'Просмотр' : 'Редактирование'}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user