Files
test-x6/frontend/src/app/components/toolbar/toolbar.component.ts
Alina f355caa9ad
All checks were successful
continuous-integration/drone/push Build is passing
feat: миграция frontend React 18 → Angular 19
Полная миграция фронтенда на Angular 19 + PrimeNG + NgRx SignalStore.

- React 18 + AntD 5 + Zustand + Vite → Angular 19 + PrimeNG 19 + NgRx SignalStore + Angular CLI
- Ноды X6: React-компоненты → чистые DOM-функции через Shape.HTML.register с effect: ['data']
- Все 5 типов нод (site, device, cross-device, splice, card) переписаны как рендереры
- Zustand store → NgRx signalStore (schema.store.ts)
- AntD компоненты → PrimeNG (p-tree, p-table, p-menu, p-toggleSwitch, p-slider, p-button)
- 13 файлов чистого TypeScript переиспользованы as-is (типы, константы, утилиты, мок, layout, ports, edges)
- Структура файлов переименована в kebab-case

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 08:52:31 +03:00

146 lines
5.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Component, inject, computed } from '@angular/core';
import { ButtonModule } from 'primeng/button';
import { ToggleSwitchModule } from 'primeng/toggleswitch';
import { SliderModule } from 'primeng/slider';
import { TooltipModule } from 'primeng/tooltip';
import { FormsModule } from '@angular/forms';
import { MessageService } from 'primeng/api';
import { SchemaStore } from '../../store/schema.store';
@Component({
selector: 'app-toolbar',
standalone: true,
imports: [ButtonModule, ToggleSwitchModule, SliderModule, TooltipModule, FormsModule],
template: `
<div
style="
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 16px;
height: 48px;
border-bottom: 1px solid #f0f0f0;
background: #fff;
flex-shrink: 0;
"
>
<!-- Left: display settings -->
<div style="display: flex; align-items: center; gap: 16px">
<span pTooltip="Сетка" tooltipPosition="bottom" style="display: flex; align-items: center; gap: 4px">
<i class="pi pi-th-large" style="font-size: 12px; color: #666"></i>
<p-toggleSwitch
[ngModel]="store.displaySettings().showGrid"
(ngModelChange)="store.toggleGrid()"
/>
</span>
<span pTooltip="Мини-карта" tooltipPosition="bottom" style="display: flex; align-items: center; gap: 4px">
<i class="pi pi-map" style="font-size: 12px; color: #666"></i>
<p-toggleSwitch
[ngModel]="store.displaySettings().showMinimap"
(ngModelChange)="store.toggleMinimap()"
/>
</span>
<p-button
[icon]="'pi pi-share-alt'"
[severity]="store.displaySettings().lineType === 'manhattan' ? 'primary' : 'secondary'"
[outlined]="store.displaySettings().lineType !== 'manhattan'"
size="small"
pTooltip="{{ store.displaySettings().lineType === 'manhattan' ? 'Ломаные линии' : 'Прямые линии' }}"
tooltipPosition="bottom"
(onClick)="store.switchLineType()"
/>
<span pTooltip="Подписи" tooltipPosition="bottom" style="display: flex; align-items: center; gap: 4px">
<i class="pi pi-tag" style="font-size: 12px; color: #666"></i>
<p-toggleSwitch
[ngModel]="store.displaySettings().showLabels"
(ngModelChange)="store.toggleLabels()"
/>
</span>
</div>
<!-- Center: actions -->
<div style="display: flex; align-items: center; gap: 4px">
<p-button icon="pi pi-plus" size="small" [outlined]="true" pTooltip="Добавить объект" tooltipPosition="bottom" (onClick)="wip()" />
<p-button icon="pi pi-trash" size="small" [outlined]="true" pTooltip="Удалить" tooltipPosition="bottom" (onClick)="deleteSelected()" />
<p-button icon="pi pi-refresh" size="small" [outlined]="true" pTooltip="Обновить раскладку" tooltipPosition="bottom" (onClick)="wip()" />
<p-button
icon="pi pi-objects-column"
size="small"
[severity]="store.lassoActive() ? 'primary' : 'secondary'"
[outlined]="!store.lassoActive()"
pTooltip="Выделение лассо"
tooltipPosition="bottom"
(onClick)="store.toggleLasso()"
/>
<p-button icon="pi pi-image" size="small" [outlined]="true" pTooltip="Экспорт PNG" tooltipPosition="bottom" (onClick)="wip()" />
</div>
<!-- Right: zoom + mode -->
<div style="display: flex; align-items: center; gap: 12px">
<div style="display: flex; align-items: center; gap: 4px">
<p-button icon="pi pi-search-minus" size="small" [outlined]="true" pTooltip="Уменьшить" tooltipPosition="bottom" (onClick)="handleZoomOut()" />
<p-slider
[ngModel]="zoom()"
(ngModelChange)="handleZoomChange($event)"
[min]="10"
[max]="300"
[style]="{ width: '100px' }"
/>
<p-button icon="pi pi-search-plus" size="small" [outlined]="true" pTooltip="Увеличить" tooltipPosition="bottom" (onClick)="handleZoomIn()" />
<p-button icon="pi pi-arrows-alt" size="small" [outlined]="true" pTooltip="Уместить на экран" tooltipPosition="bottom" (onClick)="handleFit()" />
</div>
<p-button
[icon]="store.mode() === 'view' ? 'pi pi-eye' : 'pi pi-pencil'"
[severity]="store.mode() === 'edit' ? 'primary' : 'secondary'"
[outlined]="store.mode() !== 'edit'"
size="small"
[label]="store.mode() === 'view' ? 'Просмотр' : 'Редактирование'"
pTooltip="{{ store.mode() === 'view' ? 'Режим просмотра' : 'Режим редактирования' }}"
tooltipPosition="bottom"
(onClick)="store.setMode(store.mode() === 'view' ? 'edit' : 'view')"
/>
</div>
</div>
`,
})
export class ToolbarComponent {
readonly store = inject(SchemaStore);
private readonly messageService = inject(MessageService);
zoom = computed(() => {
const g = this.store.graph();
return g ? Math.round(g.zoom() * 100) : 100;
});
handleZoomIn() {
this.store.graph()?.zoom(0.1);
}
handleZoomOut() {
this.store.graph()?.zoom(-0.1);
}
handleFit() {
this.store.graph()?.zoomToFit({ padding: 40 });
}
handleZoomChange(value: number) {
const g = this.store.graph();
if (g) {
g.zoomTo(value / 100);
}
}
deleteSelected() {
const g = this.store.graph();
if (g) {
const cells = g.getSelectedCells();
if (cells.length) g.removeCells(cells);
}
}
wip() {
this.messageService.add({ severity: 'info', summary: 'В разработке' });
}
}