feat: миграция frontend React 18 → Angular 19
All checks were successful
continuous-integration/drone/push Build is passing

Полная миграция фронтенда на 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>
This commit is contained in:
Alina
2026-02-18 08:52:31 +03:00
parent 323410ead7
commit f355caa9ad
66 changed files with 15508 additions and 4324 deletions

View File

@ -0,0 +1,145 @@
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: 'В разработке' });
}
}