feat: миграция frontend React 18 → Angular 19
All checks were successful
continuous-integration/drone/push Build is passing
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:
145
frontend/src/app/components/toolbar/toolbar.component.ts
Normal file
145
frontend/src/app/components/toolbar/toolbar.component.ts
Normal 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: 'В разработке' });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user