From 64b2f285c9057d0e0bccdd5f525503bf8bb637e0 Mon Sep 17 00:00:00 2001 From: dvladir Date: Wed, 19 Nov 2025 13:27:51 +0300 Subject: [PATCH 1/9] SED-4373 Move plan editor controls to separate component --- .../plan-artefact-list.component.ts | 21 ----- .../artefact-drop-info.pipe.ts | 5 +- .../plan-artefact-list.component.html | 0 .../plan-artefact-list.component.scss | 0 .../plan-artefact-list.component.ts | 23 +++++ .../plan-controls.component.html | 17 ++++ .../plan-controls.component.scss} | 0 .../plan-controls/plan-controls.component.ts | 90 +++++++++++++++++++ .../keyword-drop-info.pipe.ts | 5 +- .../plan-function-list.component.html | 0 .../plan-function-list.component.scss} | 0 .../plan-function-list.component.ts | 5 +- .../plan-drop-info.pipe.ts | 5 +- .../plan-otherplan-list.component.html | 0 .../plan-otherplan-list.component.scss | 0 .../plan-otherplan-list.component.ts | 5 +- .../plan-editor-base.component.html | 25 ++---- .../plan-editor-base.component.ts | 50 ++--------- .../plan-nodes-drag-preview.component.ts | 4 +- .../modules/plan-editor/plan-editor.module.ts | 16 +--- 20 files changed, 161 insertions(+), 110 deletions(-) delete mode 100644 projects/step-frontend/src/lib/modules/plan-editor/components/plan-artefact-list/plan-artefact-list.component.ts rename projects/step-frontend/src/lib/modules/plan-editor/{pipes => components/plan-controls/plan-artefact-list}/artefact-drop-info.pipe.ts (68%) rename projects/step-frontend/src/lib/modules/plan-editor/components/{ => plan-controls}/plan-artefact-list/plan-artefact-list.component.html (100%) rename projects/step-frontend/src/lib/modules/plan-editor/components/{ => plan-controls}/plan-artefact-list/plan-artefact-list.component.scss (100%) create mode 100644 projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-artefact-list/plan-artefact-list.component.ts create mode 100644 projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-controls.component.html rename projects/step-frontend/src/lib/modules/plan-editor/components/{plan-function-list/plan-function-list.component.scss => plan-controls/plan-controls.component.scss} (100%) create mode 100644 projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-controls.component.ts rename projects/step-frontend/src/lib/modules/plan-editor/{pipes => components/plan-controls/plan-function-list}/keyword-drop-info.pipe.ts (67%) rename projects/step-frontend/src/lib/modules/plan-editor/components/{ => plan-controls}/plan-function-list/plan-function-list.component.html (100%) rename projects/step-frontend/src/lib/modules/plan-editor/components/{plan-otherplan-list/plan-otherplan-list.component.scss => plan-controls/plan-function-list/plan-function-list.component.scss} (100%) rename projects/step-frontend/src/lib/modules/plan-editor/components/{ => plan-controls}/plan-function-list/plan-function-list.component.ts (90%) rename projects/step-frontend/src/lib/modules/plan-editor/{pipes => components/plan-controls/plan-otherplan-list}/plan-drop-info.pipe.ts (66%) rename projects/step-frontend/src/lib/modules/plan-editor/components/{ => plan-controls}/plan-otherplan-list/plan-otherplan-list.component.html (100%) create mode 100644 projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-otherplan-list/plan-otherplan-list.component.scss rename projects/step-frontend/src/lib/modules/plan-editor/components/{ => plan-controls}/plan-otherplan-list/plan-otherplan-list.component.ts (90%) diff --git a/projects/step-frontend/src/lib/modules/plan-editor/components/plan-artefact-list/plan-artefact-list.component.ts b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-artefact-list/plan-artefact-list.component.ts deleted file mode 100644 index d43dffc3d8..0000000000 --- a/projects/step-frontend/src/lib/modules/plan-editor/components/plan-artefact-list/plan-artefact-list.component.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Component, EventEmitter, inject, Output, ViewEncapsulation } from '@angular/core'; -import { ArtefactService, TableIndicatorMode } from '@exense/step-core'; - -@Component({ - selector: 'step-plan-artefact-list', - templateUrl: './plan-artefact-list.component.html', - styleUrls: ['./plan-artefact-list.component.scss'], - encapsulation: ViewEncapsulation.None, - standalone: false, -}) -export class PlanArtefactListComponent { - readonly availableArtefacts$ = inject(ArtefactService).availableArtefacts$; - - @Output() onSelection = new EventEmitter(); - - addControl(id: string): void { - this.onSelection.emit(id); - } - - protected readonly TableIndicatorMode = TableIndicatorMode; -} diff --git a/projects/step-frontend/src/lib/modules/plan-editor/pipes/artefact-drop-info.pipe.ts b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-artefact-list/artefact-drop-info.pipe.ts similarity index 68% rename from projects/step-frontend/src/lib/modules/plan-editor/pipes/artefact-drop-info.pipe.ts rename to projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-artefact-list/artefact-drop-info.pipe.ts index 36549b481b..b9348521dc 100644 --- a/projects/step-frontend/src/lib/modules/plan-editor/pipes/artefact-drop-info.pipe.ts +++ b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-artefact-list/artefact-drop-info.pipe.ts @@ -1,11 +1,10 @@ import { inject, Pipe, PipeTransform } from '@angular/core'; -import { ControlDropInfoFactoryService } from '../injectables/control-drop-info-factory.service'; import { ArtefactType, DropInfo } from '@exense/step-core'; -import { ControlDropInfo } from '../types/control-drop-info.interface'; +import { ControlDropInfoFactoryService } from '../../../injectables/control-drop-info-factory.service'; +import { ControlDropInfo } from '../../../types/control-drop-info.interface'; @Pipe({ name: 'artefactDropInfo', - standalone: false, }) export class ArtefactDropInfoPipe implements PipeTransform { private _controlDropInfoFactory = inject(ControlDropInfoFactoryService); diff --git a/projects/step-frontend/src/lib/modules/plan-editor/components/plan-artefact-list/plan-artefact-list.component.html b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-artefact-list/plan-artefact-list.component.html similarity index 100% rename from projects/step-frontend/src/lib/modules/plan-editor/components/plan-artefact-list/plan-artefact-list.component.html rename to projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-artefact-list/plan-artefact-list.component.html diff --git a/projects/step-frontend/src/lib/modules/plan-editor/components/plan-artefact-list/plan-artefact-list.component.scss b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-artefact-list/plan-artefact-list.component.scss similarity index 100% rename from projects/step-frontend/src/lib/modules/plan-editor/components/plan-artefact-list/plan-artefact-list.component.scss rename to projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-artefact-list/plan-artefact-list.component.scss diff --git a/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-artefact-list/plan-artefact-list.component.ts b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-artefact-list/plan-artefact-list.component.ts new file mode 100644 index 0000000000..07a872555d --- /dev/null +++ b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-artefact-list/plan-artefact-list.component.ts @@ -0,0 +1,23 @@ +import { Component, inject, output, ViewEncapsulation } from '@angular/core'; +import { ArtefactService, StepCoreModule, TableIndicatorMode } from '@exense/step-core'; +import { ArtefactDropInfoPipe } from './artefact-drop-info.pipe'; +import { PlanNodesDragPreviewComponent } from '../../plan-nodes-drag-preview/plan-nodes-drag-preview.component'; + +@Component({ + selector: 'step-plan-artefact-list', + templateUrl: './plan-artefact-list.component.html', + styleUrls: ['./plan-artefact-list.component.scss'], + encapsulation: ViewEncapsulation.None, + imports: [StepCoreModule, ArtefactDropInfoPipe, PlanNodesDragPreviewComponent], +}) +export class PlanArtefactListComponent { + readonly availableArtefacts$ = inject(ArtefactService).availableArtefacts$; + + readonly onSelection = output(); + + addControl(id: string): void { + this.onSelection.emit(id); + } + + protected readonly TableIndicatorMode = TableIndicatorMode; +} diff --git a/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-controls.component.html b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-controls.component.html new file mode 100644 index 0000000000..b357a8b18f --- /dev/null +++ b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-controls.component.html @@ -0,0 +1,17 @@ + +
+ @switch (selectedTab()) { + @case ('controls') { + + } + @case ('keywords') { + + } + @case ('other') { + + } + @case ('console') { + + } + } +
diff --git a/projects/step-frontend/src/lib/modules/plan-editor/components/plan-function-list/plan-function-list.component.scss b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-controls.component.scss similarity index 100% rename from projects/step-frontend/src/lib/modules/plan-editor/components/plan-function-list/plan-function-list.component.scss rename to projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-controls.component.scss diff --git a/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-controls.component.ts b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-controls.component.ts new file mode 100644 index 0000000000..a6220c079b --- /dev/null +++ b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-controls.component.ts @@ -0,0 +1,90 @@ +import { Component, computed, DestroyRef, inject, model, OnInit, output, signal, viewChild } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { StepCoreModule, Tab, TabsComponent } from '@exense/step-core'; +import { distinctUntilChanged, filter } from 'rxjs'; +import { ExecutionModule } from '../../../execution/execution.module'; +import { PlanArtefactListComponent } from './plan-artefact-list/plan-artefact-list.component'; +import { PlanFunctionListComponent } from './plan-function-list/plan-function-list.component'; +import { PlanOtherplanListComponent } from './plan-otherplan-list/plan-otherplan-list.component'; +import { InteractiveSessionService } from '../../injectables/interactive-session.service'; +import { KeywordCallsComponent } from '../../../execution/components/keyword-calls/keyword-calls.component'; + +export type PlanControlsTab = 'controls' | 'keywords' | 'other' | 'console'; + +const TABS = { + controls: { id: 'controls', label: 'Controls' } as Tab, + keywords: { id: 'keywords', label: 'Keywords' } as Tab, + other: { id: 'other', label: 'Other Plans' } as Tab, + console: { id: 'console', label: 'Console' } as Tab, +}; + +@Component({ + selector: 'step-plan-controls', + imports: [ + StepCoreModule, + ExecutionModule, + PlanArtefactListComponent, + PlanFunctionListComponent, + PlanOtherplanListComponent, + ], + templateUrl: './plan-controls.component.html', + styleUrl: './plan-controls.component.scss', +}) +export class PlanControlsComponent implements OnInit { + private _destroyRef = inject(DestroyRef); + protected readonly _interactiveSession = inject(InteractiveSessionService); + + private tabs = viewChild('tabs', { read: TabsComponent }); + private keywordCalls = viewChild('keywordCalls', { read: KeywordCallsComponent }); + + readonly addControl = output(); + readonly addKeywords = output(); + readonly addPlans = output(); + + protected readonly selectedTab = model('controls'); + protected readonly componentTabs = signal[]>([TABS.controls, TABS.keywords, TABS.other]); + + private hasConsoleTab = computed(() => { + const tabs = this.componentTabs(); + return tabs.some((tab) => tab.id === TABS.console.id); + }); + + ngOnInit(): void { + this.initConsoleTabToggle(); + } + + updateTabsPagination(): void { + this.tabs()?.updatePagination?.(); + } + + reloadConsoleLog(): void { + this.keywordCalls()?._leafReportsDataSource?.reload?.(); + } + + setTab(tab: PlanControlsTab): void { + this.selectedTab.set(tab); + } + + private initConsoleTabToggle(): void { + this._interactiveSession.isActive$ + .pipe( + filter((shouldConsoleExists) => { + const hasConsole = this.hasConsoleTab(); + return hasConsole !== shouldConsoleExists; + }), + distinctUntilChanged(), + takeUntilDestroyed(this._destroyRef), + ) + .subscribe((withConsole) => { + this.componentTabs.update((tabs) => { + if (withConsole) { + return [...tabs, TABS.console]; + } + return tabs.filter((tab) => tab.id !== TABS.console.id); + }); + if (withConsole) { + this.selectedTab.set(TABS.console.id); + } + }); + } +} diff --git a/projects/step-frontend/src/lib/modules/plan-editor/pipes/keyword-drop-info.pipe.ts b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-function-list/keyword-drop-info.pipe.ts similarity index 67% rename from projects/step-frontend/src/lib/modules/plan-editor/pipes/keyword-drop-info.pipe.ts rename to projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-function-list/keyword-drop-info.pipe.ts index 461c2f4e47..de00010df5 100644 --- a/projects/step-frontend/src/lib/modules/plan-editor/pipes/keyword-drop-info.pipe.ts +++ b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-function-list/keyword-drop-info.pipe.ts @@ -1,11 +1,10 @@ import { inject, Pipe, PipeTransform } from '@angular/core'; -import { ControlDropInfoFactoryService } from '../injectables/control-drop-info-factory.service'; import { Keyword } from '@exense/step-core'; -import { ControlDropInfo } from '../types/control-drop-info.interface'; +import { ControlDropInfoFactoryService } from '../../../injectables/control-drop-info-factory.service'; +import { ControlDropInfo } from '../../../types/control-drop-info.interface'; @Pipe({ name: 'keywordDropInfo', - standalone: false, }) export class KeywordDropInfoPipe implements PipeTransform { private _controlDropInfoFactory = inject(ControlDropInfoFactoryService); diff --git a/projects/step-frontend/src/lib/modules/plan-editor/components/plan-function-list/plan-function-list.component.html b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-function-list/plan-function-list.component.html similarity index 100% rename from projects/step-frontend/src/lib/modules/plan-editor/components/plan-function-list/plan-function-list.component.html rename to projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-function-list/plan-function-list.component.html diff --git a/projects/step-frontend/src/lib/modules/plan-editor/components/plan-otherplan-list/plan-otherplan-list.component.scss b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-function-list/plan-function-list.component.scss similarity index 100% rename from projects/step-frontend/src/lib/modules/plan-editor/components/plan-otherplan-list/plan-otherplan-list.component.scss rename to projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-function-list/plan-function-list.component.scss diff --git a/projects/step-frontend/src/lib/modules/plan-editor/components/plan-function-list/plan-function-list.component.ts b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-function-list/plan-function-list.component.ts similarity index 90% rename from projects/step-frontend/src/lib/modules/plan-editor/components/plan-function-list/plan-function-list.component.ts rename to projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-function-list/plan-function-list.component.ts index 52f71a0443..65921961da 100644 --- a/projects/step-frontend/src/lib/modules/plan-editor/components/plan-function-list/plan-function-list.component.ts +++ b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-function-list/plan-function-list.component.ts @@ -6,16 +6,20 @@ import { entitySelectionStateProvider, Keyword, SelectionList, + StepCoreModule, TableApiWrapperService, tableColumnsConfigProvider, TableRemoteDataSource, } from '@exense/step-core'; import { catchError, filter, map, Observable, of, switchMap } from 'rxjs'; +import { PlanNodesDragPreviewComponent } from '../../plan-nodes-drag-preview/plan-nodes-drag-preview.component'; +import { KeywordDropInfoPipe } from './keyword-drop-info.pipe'; @Component({ selector: 'step-plan-function-list', templateUrl: './plan-function-list.component.html', styleUrls: ['./plan-function-list.component.scss'], + imports: [StepCoreModule, PlanNodesDragPreviewComponent, KeywordDropInfoPipe], encapsulation: ViewEncapsulation.None, providers: [ ...entitySelectionStateProvider('id'), @@ -29,7 +33,6 @@ import { catchError, filter, map, Observable, of, switchMap } from 'rxjs'; host: { class: 'plan-editor-control-selections', }, - standalone: false, }) export class PlanFunctionListComponent { private _selectionState = inject>(EntitySelectionState); diff --git a/projects/step-frontend/src/lib/modules/plan-editor/pipes/plan-drop-info.pipe.ts b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-otherplan-list/plan-drop-info.pipe.ts similarity index 66% rename from projects/step-frontend/src/lib/modules/plan-editor/pipes/plan-drop-info.pipe.ts rename to projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-otherplan-list/plan-drop-info.pipe.ts index 1d4261c8f8..17bf5e37f9 100644 --- a/projects/step-frontend/src/lib/modules/plan-editor/pipes/plan-drop-info.pipe.ts +++ b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-otherplan-list/plan-drop-info.pipe.ts @@ -1,11 +1,10 @@ import { inject, Pipe, PipeTransform } from '@angular/core'; -import { ControlDropInfoFactoryService } from '../injectables/control-drop-info-factory.service'; import { Plan } from '@exense/step-core'; -import { ControlDropInfo } from '../types/control-drop-info.interface'; +import { ControlDropInfoFactoryService } from '../../../injectables/control-drop-info-factory.service'; +import { ControlDropInfo } from '../../../types/control-drop-info.interface'; @Pipe({ name: 'planDropInfo', - standalone: false, }) export class PlanDropInfoPipe implements PipeTransform { private _controlDropInfoFactory = inject(ControlDropInfoFactoryService); diff --git a/projects/step-frontend/src/lib/modules/plan-editor/components/plan-otherplan-list/plan-otherplan-list.component.html b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-otherplan-list/plan-otherplan-list.component.html similarity index 100% rename from projects/step-frontend/src/lib/modules/plan-editor/components/plan-otherplan-list/plan-otherplan-list.component.html rename to projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-otherplan-list/plan-otherplan-list.component.html diff --git a/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-otherplan-list/plan-otherplan-list.component.scss b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-otherplan-list/plan-otherplan-list.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/projects/step-frontend/src/lib/modules/plan-editor/components/plan-otherplan-list/plan-otherplan-list.component.ts b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-otherplan-list/plan-otherplan-list.component.ts similarity index 90% rename from projects/step-frontend/src/lib/modules/plan-editor/components/plan-otherplan-list/plan-otherplan-list.component.ts rename to projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-otherplan-list/plan-otherplan-list.component.ts index 18ba546613..90b34c960d 100644 --- a/projects/step-frontend/src/lib/modules/plan-editor/components/plan-otherplan-list/plan-otherplan-list.component.ts +++ b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-otherplan-list/plan-otherplan-list.component.ts @@ -6,16 +6,20 @@ import { entitySelectionStateProvider, Plan, SelectionList, + StepCoreModule, TableApiWrapperService, tableColumnsConfigProvider, TableRemoteDataSource, } from '@exense/step-core'; import { catchError, filter, map, Observable, of, switchMap } from 'rxjs'; +import { PlanNodesDragPreviewComponent } from '../../plan-nodes-drag-preview/plan-nodes-drag-preview.component'; +import { PlanDropInfoPipe } from './plan-drop-info.pipe'; @Component({ selector: 'step-plan-otherplan-list', templateUrl: './plan-otherplan-list.component.html', styleUrls: ['./plan-otherplan-list.component.scss'], + imports: [StepCoreModule, PlanNodesDragPreviewComponent, PlanDropInfoPipe], encapsulation: ViewEncapsulation.None, providers: [ ...entitySelectionStateProvider('id'), @@ -29,7 +33,6 @@ import { catchError, filter, map, Observable, of, switchMap } from 'rxjs'; host: { class: 'plan-editor-control-selections', }, - standalone: false, }) export class PlanOtherplanListComponent { private _selectionState = inject>(EntitySelectionState); diff --git a/projects/step-frontend/src/lib/modules/plan-editor/components/plan-editor-base/plan-editor-base.component.html b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-editor-base/plan-editor-base.component.html index 226f175711..909e62d860 100644 --- a/projects/step-frontend/src/lib/modules/plan-editor/components/plan-editor-base/plan-editor-base.component.html +++ b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-editor-base/plan-editor-base.component.html @@ -54,25 +54,14 @@ - -
- @switch (selectedTab) { - @case ('controls') { - - } - @case ('keywords') { - - } - @case ('other') { - - } - @case ('console') { - - } - } -
+
diff --git a/projects/step-frontend/src/lib/modules/plan-editor/components/plan-editor-base/plan-editor-base.component.ts b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-editor-base/plan-editor-base.component.ts index d8224e803e..6b7bb72fb5 100644 --- a/projects/step-frontend/src/lib/modules/plan-editor/components/plan-editor-base/plan-editor-base.component.ts +++ b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-editor-base/plan-editor-base.component.ts @@ -1,6 +1,5 @@ import { Component, - DestroyRef, effect, EventEmitter, forwardRef, @@ -13,7 +12,7 @@ import { Output, SimpleChanges, untracked, - ViewChild, + viewChild, ViewEncapsulation, } from '@angular/core'; import { FormControl } from '@angular/forms'; @@ -43,16 +42,15 @@ import { PlanContext, AuthService, } from '@exense/step-core'; -import { catchError, debounceTime, filter, map, Observable, of, pairwise, Subject, switchMap, takeUntil } from 'rxjs'; -import { KeywordCallsComponent } from '../../../execution/components/keyword-calls/keyword-calls.component'; +import { catchError, debounceTime, filter, map, Observable, of, Subject, switchMap, takeUntil } from 'rxjs'; import { ArtefactTreeNodeUtilsService } from '../../injectables/artefact-tree-node-utils.service'; import { InteractiveSessionService } from '../../injectables/interactive-session.service'; import { PlanHistoryService } from '../../injectables/plan-history.service'; import { ActivatedRoute, Router } from '@angular/router'; import { MatDialog } from '@angular/material/dialog'; import { PlanSourceDialogComponent } from '../plan-source-dialog/plan-source-dialog.component'; -import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { ArtefactTreeStateService } from '../../injectables/artefact-tree-state.service'; +import { PlanControlsComponent } from '../plan-controls/plan-controls.component'; const PLAN_SIZE = 'PLAN_SIZE'; const PLAN_CONTROLS_SIZE = 'PLAN_CONTROLS_SIZE'; @@ -123,7 +121,6 @@ export class PlanEditorBaseComponent private _matDialog = inject(MatDialog); private _router = inject(Router); private _commonEntitiesUrls = inject(CommonEntitiesUrlsService); - private _destroyRef = inject(DestroyRef); private _auth = inject(AuthService); private _injector = inject(Injector); @@ -145,8 +142,6 @@ export class PlanEditorBaseComponent }; @Output() runPlan = new EventEmitter(); - selectedTab = 'controls'; - readonly isInteractiveSessionActive$ = this._interactiveSession.isActive$; readonly showInteractiveWarning$ = this.isInteractiveSessionActive$.pipe(debounceTime(300)); @@ -159,16 +154,10 @@ export class PlanEditorBaseComponent }), ); protected planTypeControl = new FormControl<{ planType: string; icon: string } | null>(null); - protected componentTabs = [ - { id: 'controls', label: 'Controls' }, - { id: 'keywords', label: 'Keywords' }, - { id: 'other', label: 'Other Plans' }, - ]; protected repositoryObjectRef?: RepositoryObjectReference; protected planClass?: string; - @ViewChild('keywordCalls', { read: KeywordCallsComponent, static: false }) - private keywords?: KeywordCallsComponent; + private planControls = viewChild('planControls', { read: PlanControlsComponent }); protected planSize = this._planEditorPersistenceState.getPanelSize(PLAN_SIZE); protected planControlsSize = this._planEditorPersistenceState.getPanelSize(PLAN_CONTROLS_SIZE); @@ -186,7 +175,6 @@ export class PlanEditorBaseComponent ngOnInit(): void { this._interactiveSession.init(); - this.initConsoleTabToggle(); } ngOnDestroy() { @@ -261,7 +249,7 @@ export class PlanEditorBaseComponent } stopInteractive(): void { - this._interactiveSession.stopInteractive().subscribe(() => (this.selectedTab = 'controls')); + this._interactiveSession.stopInteractive().subscribe(() => this.planControls()?.setTab?.('controls')); } resetInteractive(): void { @@ -345,9 +333,7 @@ export class PlanEditorBaseComponent } this._interactiveSession.execute(this.currentPlanId!, artefactIds).subscribe(() => { - if (this.keywords) { - this.keywords._leafReportsDataSource.reload(); - } + this.planControls()?.reloadConsoleLog?.(); }); } @@ -379,30 +365,6 @@ export class PlanEditorBaseComponent } } - private initConsoleTabToggle(): void { - const consoleTab = { id: 'console', label: 'Console' }; - this._interactiveSession.isActive$ - .pipe( - filter((shouldConsoleExists) => { - const hasConsole = this.componentTabs.some((tab) => tab.id === consoleTab.id); - return hasConsole !== shouldConsoleExists; - }), - map((withConsole) => { - const tabs = withConsole - ? [...this.componentTabs, { ...consoleTab }] - : this.componentTabs.filter((tab) => tab.id !== consoleTab.id); - return { tabs, withConsole }; - }), - takeUntilDestroyed(this._destroyRef), - ) - .subscribe(({ tabs, withConsole }) => { - this.componentTabs = tabs; - if (withConsole) { - this.selectedTab = consoleTab.id; - } - }); - } - private openFunctionEditor(keyword: Keyword): Observable { return this._functionActions.openFunctionEditor(keyword); } diff --git a/projects/step-frontend/src/lib/modules/plan-editor/components/plan-nodes-drag-preview/plan-nodes-drag-preview.component.ts b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-nodes-drag-preview/plan-nodes-drag-preview.component.ts index fc7a5700ea..72be1e6b3a 100644 --- a/projects/step-frontend/src/lib/modules/plan-editor/components/plan-nodes-drag-preview/plan-nodes-drag-preview.component.ts +++ b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-nodes-drag-preview/plan-nodes-drag-preview.component.ts @@ -1,5 +1,5 @@ import { Component, computed, inject } from '@angular/core'; -import { DragDataService } from '@exense/step-core'; +import { DragDataService, StepCoreModule } from '@exense/step-core'; import { toSignal } from '@angular/core/rxjs-interop'; import { ControlDropInfo } from '../../types/control-drop-info.interface'; @@ -7,7 +7,7 @@ import { ControlDropInfo } from '../../types/control-drop-info.interface'; selector: 'step-plan-nodes-drag-preview', templateUrl: './plan-nodes-drag-preview.component.html', styleUrl: './plan-nodes-drag-preview.component.scss', - standalone: false, + imports: [StepCoreModule], }) export class PlanNodesDragPreviewComponent { private _dragDataService = inject(DragDataService); diff --git a/projects/step-frontend/src/lib/modules/plan-editor/plan-editor.module.ts b/projects/step-frontend/src/lib/modules/plan-editor/plan-editor.module.ts index fff31d8c4b..f7c3e9fd15 100644 --- a/projects/step-frontend/src/lib/modules/plan-editor/plan-editor.module.ts +++ b/projects/step-frontend/src/lib/modules/plan-editor/plan-editor.module.ts @@ -3,36 +3,24 @@ import { PlanTypeRegistryService } from '@exense/step-core'; import { StepCommonModule } from '../_common/step-common.module'; import { ExecutionModule } from '../execution/execution.module'; import { PlanEditorBaseComponent } from './components/plan-editor-base/plan-editor-base.component'; -import { PlanArtefactListComponent } from './components/plan-artefact-list/plan-artefact-list.component'; -import { PlanFunctionListComponent } from './components/plan-function-list/plan-function-list.component'; -import { PlanOtherplanListComponent } from './components/plan-otherplan-list/plan-otherplan-list.component'; import { PlanEditorActionsComponent } from './components/plan-editor-actions/plan-editor-actions.component'; import { PlanCommonTreeEditorFormComponent } from './components/plan-common-tree-editor-form/plan-common-tree-editor-form.component'; import { PlanEditorKeyHandlerDirective } from './directives/plan-editor-key-handler.directive'; import { PlanAlertsComponent } from './components/plan-alerts/plan-alerts.component'; import { PlanSourceDialogComponent } from './components/plan-source-dialog/plan-source-dialog.component'; import { PlanNodesDragPreviewComponent } from './components/plan-nodes-drag-preview/plan-nodes-drag-preview.component'; -import { PlanDropInfoPipe } from './pipes/plan-drop-info.pipe'; -import { KeywordDropInfoPipe } from './pipes/keyword-drop-info.pipe'; -import { ArtefactDropInfoPipe } from './pipes/artefact-drop-info.pipe'; +import { PlanControlsComponent } from './components/plan-controls/plan-controls.component'; @NgModule({ declarations: [ PlanEditorBaseComponent, - PlanArtefactListComponent, - PlanFunctionListComponent, - PlanOtherplanListComponent, PlanEditorActionsComponent, PlanCommonTreeEditorFormComponent, PlanEditorKeyHandlerDirective, PlanAlertsComponent, PlanSourceDialogComponent, - PlanNodesDragPreviewComponent, - ArtefactDropInfoPipe, - KeywordDropInfoPipe, - PlanDropInfoPipe, ], - imports: [StepCommonModule, ExecutionModule], + imports: [StepCommonModule, ExecutionModule, PlanNodesDragPreviewComponent, PlanControlsComponent], exports: [ PlanEditorBaseComponent, PlanCommonTreeEditorFormComponent, From 2b1402974725e51799d924b628af5fac0305d456 Mon Sep 17 00:00:00 2001 From: dvladir Date: Wed, 19 Nov 2025 17:38:13 +0300 Subject: [PATCH 2/9] SED-4373 Plain split view for plan editor --- ...wport-height-minus-offset-top.directive.ts | 7 +- .../lib/modules/basics/step-basics.module.ts | 3 + .../item-component/plan-type.component.ts | 7 +- .../plan-tree-panel-sizes.directive.ts | 39 +++++ .../plan-tree/plan-tree.component.html | 25 ++- .../plan-tree/plan-tree.component.ts | 41 +++-- .../source-plan-editor.component.html | 29 ++-- .../source-plan-editor.component.scss | 1 - .../source-plan-editor.component.ts | 142 +++++++++--------- .../plan-tree-left-panel.directive.ts | 10 ++ .../plan-tree-right-panel.directive.ts | 10 ++ .../src/lib/modules/plan-common/index.ts | 7 + .../split-area/split-area.component.ts | 2 +- .../split-gutter/split-gutter.component.ts | 94 +++++++----- .../components/split/split.component.html | 2 +- .../split/components/split/split.component.ts | 6 +- .../step-core/src/lib/step-core.module.ts | 4 - ...lan-common-tree-editor-form.component.html | 8 +- .../plan-common-tree-editor-form.component.ts | 10 +- .../plan-controls.component.html | 6 +- .../plan-controls.component.scss | 3 + .../plan-controls/plan-controls.component.ts | 7 +- .../plan-editor-base.component.html | 11 +- .../yaml-plan-editor.component.html | 2 +- .../yaml-plan-editor.component.ts | 9 +- 25 files changed, 303 insertions(+), 182 deletions(-) rename projects/step-core/src/lib/{ => modules/basics}/directives/max-height-viewport-height-minus-offset-top.directive.ts (80%) create mode 100644 projects/step-core/src/lib/modules/plan-common/components/plan-tree/plan-tree-panel-sizes.directive.ts create mode 100644 projects/step-core/src/lib/modules/plan-common/directives/plan-tree-left-panel.directive.ts create mode 100644 projects/step-core/src/lib/modules/plan-common/directives/plan-tree-right-panel.directive.ts diff --git a/projects/step-core/src/lib/directives/max-height-viewport-height-minus-offset-top.directive.ts b/projects/step-core/src/lib/modules/basics/directives/max-height-viewport-height-minus-offset-top.directive.ts similarity index 80% rename from projects/step-core/src/lib/directives/max-height-viewport-height-minus-offset-top.directive.ts rename to projects/step-core/src/lib/modules/basics/directives/max-height-viewport-height-minus-offset-top.directive.ts index e10072da8d..ff1b8b20b2 100644 --- a/projects/step-core/src/lib/directives/max-height-viewport-height-minus-offset-top.directive.ts +++ b/projects/step-core/src/lib/modules/basics/directives/max-height-viewport-height-minus-offset-top.directive.ts @@ -1,11 +1,10 @@ -import { AfterViewInit, DestroyRef, Directive, ElementRef, inject, OnDestroy, Renderer2 } from '@angular/core'; -import { debounceTime, map, startWith, Subject, takeUntil } from 'rxjs'; -import { resizeObservable } from '../modules/basics/step-basics.module'; +import { AfterViewInit, DestroyRef, Directive, ElementRef, inject, Renderer2 } from '@angular/core'; +import { debounceTime, map, startWith } from 'rxjs'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { resizeObservable } from '../types/resize-observable'; @Directive({ selector: '[stepMaxHeightViewportHeightMinusOffsetTop]', - standalone: false, }) export class MaxHeightViewportHeightMinusOffsetTopDirective implements AfterViewInit { private _elementRef = inject>(ElementRef); diff --git a/projects/step-core/src/lib/modules/basics/step-basics.module.ts b/projects/step-core/src/lib/modules/basics/step-basics.module.ts index 2e37f2d174..a975dc5823 100644 --- a/projects/step-core/src/lib/modules/basics/step-basics.module.ts +++ b/projects/step-core/src/lib/modules/basics/step-basics.module.ts @@ -80,6 +80,7 @@ import { FilterAddonDirective } from './directives/filter-addon.directive'; import { LongInlineTextComponent } from './components/long-inline-text/long-inline-text.component'; import { ResourceUrlPipe } from './pipes/resource-url.pipe'; import { NumberSeparateThousandsPipe } from './pipes/number-separate-thousands.pipe'; +import { MaxHeightViewportHeightMinusOffsetTopDirective } from './directives/max-height-viewport-height-minus-offset-top.directive'; @NgModule({ imports: [ @@ -99,6 +100,7 @@ import { NumberSeparateThousandsPipe } from './pipes/number-separate-thousands.p LongInlineTextComponent, ResourceUrlPipe, NumberSeparateThousandsPipe, + MaxHeightViewportHeightMinusOffsetTopDirective, ], declarations: [ ResourceLabelComponent, @@ -250,6 +252,7 @@ import { NumberSeparateThousandsPipe } from './pipes/number-separate-thousands.p LongInlineTextComponent, ResourceUrlPipe, NumberSeparateThousandsPipe, + MaxHeightViewportHeightMinusOffsetTopDirective, ], }) export class StepBasicsModule {} diff --git a/projects/step-core/src/lib/modules/custom-registeries/components/item-component/plan-type.component.ts b/projects/step-core/src/lib/modules/custom-registeries/components/item-component/plan-type.component.ts index 72b9c9a64f..b891f66bc9 100644 --- a/projects/step-core/src/lib/modules/custom-registeries/components/item-component/plan-type.component.ts +++ b/projects/step-core/src/lib/modules/custom-registeries/components/item-component/plan-type.component.ts @@ -1,8 +1,12 @@ import { BaseItemComponent } from './base-item.component'; import { CustomRegistryType } from '../../shared/custom-registry-type.enum'; -import { Component, ViewEncapsulation } from '@angular/core'; +import { Component, Input, TemplateRef, ViewEncapsulation } from '@angular/core'; import { CustomRegistryItem } from '../../shared/custom-registry-item'; +export interface PlanTypeContext { + templateControls: TemplateRef; +} + @Component({ selector: 'step-plan-type', templateUrl: './base-item.component.html', @@ -12,4 +16,5 @@ import { CustomRegistryItem } from '../../shared/custom-registry-item'; }) export class PlanTypeComponent extends BaseItemComponent { protected override readonly registryType: CustomRegistryType = CustomRegistryType.PLAN_TYPE; + @Input() override context?: PlanTypeContext; } diff --git a/projects/step-core/src/lib/modules/plan-common/components/plan-tree/plan-tree-panel-sizes.directive.ts b/projects/step-core/src/lib/modules/plan-common/components/plan-tree/plan-tree-panel-sizes.directive.ts new file mode 100644 index 0000000000..20af2050d7 --- /dev/null +++ b/projects/step-core/src/lib/modules/plan-common/components/plan-tree/plan-tree-panel-sizes.directive.ts @@ -0,0 +1,39 @@ +import { Directive, effect, inject, model } from '@angular/core'; +import { PlanEditorPersistenceStateService } from '../../injectables/plan-editor-persistence-state.service'; + +const TREE_SIZE = 'TREE_SIZE'; +const ARTEFACT_DETAILS_SIZE = 'ARTEFACT_DETAILS_SIZE'; +const LEFT_PANEL_SIZE = 'LEFT_PANEL_SIZE'; +const RIGHT_PANEL_SIZE = 'RIGHT_PANEL_SIZE'; + +@Directive({ + selector: '[stepPlanTreePanelSizes]', +}) +export class PlanTreePanelSizesDirective { + private _planPersistenceState = inject(PlanEditorPersistenceStateService); + + readonly leftPanelSize = model(this._planPersistenceState.getPanelSize(LEFT_PANEL_SIZE)); + readonly treeSize = model(this._planPersistenceState.getPanelSize(TREE_SIZE)); + readonly artefactDetailsSize = model(this._planPersistenceState.getPanelSize(ARTEFACT_DETAILS_SIZE)); + readonly rightPanelSize = model(this._planPersistenceState.getPanelSize(RIGHT_PANEL_SIZE)); + + private effectLeftPanelSizeChange = effect(() => { + const size = this.leftPanelSize(); + this._planPersistenceState.setPanelSize(LEFT_PANEL_SIZE, size ?? 0); + }); + + private effectTreePanelSizeChange = effect(() => { + const size = this.treeSize(); + this._planPersistenceState.setPanelSize(TREE_SIZE, size ?? 0); + }); + + private effectArtefactDetailsSizeChange = effect(() => { + const size = this.artefactDetailsSize(); + this._planPersistenceState.setPanelSize(ARTEFACT_DETAILS_SIZE, size ?? 0); + }); + + private effectRightPanelSizeChange = effect(() => { + const size = this.rightPanelSize(); + this._planPersistenceState.setPanelSize(RIGHT_PANEL_SIZE, size ?? 0); + }); +} diff --git a/projects/step-core/src/lib/modules/plan-common/components/plan-tree/plan-tree.component.html b/projects/step-core/src/lib/modules/plan-common/components/plan-tree/plan-tree.component.html index 4362b85ad1..f186779838 100644 --- a/projects/step-core/src/lib/modules/plan-common/components/plan-tree/plan-tree.component.html +++ b/projects/step-core/src/lib/modules/plan-common/components/plan-tree/plan-tree.component.html @@ -1,5 +1,12 @@ - - + + @if (templateLeftPanel(); as left) { + + + + + } + + - + @if (activeNode(); as activeNode) { @if (activeNode.originalArtefact; as selectedArtefact) { @@ -76,4 +78,11 @@ } } + + @if (templateRightPanel(); as right) { + + + + + } diff --git a/projects/step-core/src/lib/modules/plan-common/components/plan-tree/plan-tree.component.ts b/projects/step-core/src/lib/modules/plan-common/components/plan-tree/plan-tree.component.ts index 786725be68..88ea490dd0 100644 --- a/projects/step-core/src/lib/modules/plan-common/components/plan-tree/plan-tree.component.ts +++ b/projects/step-core/src/lib/modules/plan-common/components/plan-tree/plan-tree.component.ts @@ -2,6 +2,7 @@ import { AfterViewInit, Component, computed, + contentChild, DestroyRef, ElementRef, forwardRef, @@ -20,7 +21,6 @@ import { ArtefactChildContainerSettingsComponent } from '../artefact-child-conta import { ArtefactDetailsComponent } from '../artefact-details/artefact-details.component'; import { ArtefactTreeNode } from '../../types/artefact-tree-node'; import { PlanArtefactResolverService } from '../../injectables/plan-artefact-resolver.service'; -import { PlanEditorPersistenceStateService } from '../../injectables/plan-editor-persistence-state.service'; import { PlanEditorService } from '../../injectables/plan-editor.service'; import { PlanInteractiveSessionService } from '../../injectables/plan-interactive-session.service'; import { PlanTreeAction } from '../../types/plan-tree-action.enum'; @@ -30,9 +30,10 @@ import { SPLIT_EXPORTS } from '../../../split'; import { StepIconsModule } from '../../../step-icons/step-icons.module'; import { AsyncPipe } from '@angular/common'; import { StepMaterialModule } from '../../../step-material/step-material.module'; - -const TREE_SIZE = 'TREE_SIZE'; -const ARTEFACT_DETAILS_SIZE = 'ARTEFACT_DETAILS_SIZE'; +import { StepBasicsModule } from '../../../basics/step-basics.module'; +import { PlanTreeLeftPanelDirective } from '../../directives/plan-tree-left-panel.directive'; +import { PlanTreeRightPanelDirective } from '../../directives/plan-tree-right-panel.directive'; +import { PlanTreePanelSizesDirective } from './plan-tree-panel-sizes.directive'; @Component({ selector: 'step-plan-tree', @@ -43,12 +44,14 @@ const ARTEFACT_DETAILS_SIZE = 'ARTEFACT_DETAILS_SIZE'; SPLIT_EXPORTS, DRAG_DROP_EXPORTS, StepMaterialModule, + StepBasicsModule, TREE_EXPORTS, StepIconsModule, ArtefactDetailsComponent, ArtefactChildContainerSettingsComponent, AsyncPipe, ], + hostDirectives: [PlanTreePanelSizesDirective], providers: [ { provide: TreeActionsService, @@ -61,12 +64,29 @@ export class PlanTreeComponent implements AfterViewInit, TreeActionsService { private _treeState = inject>(TreeStateService); private _planArtefactResolver? = inject(PlanArtefactResolverService, { optional: true }); - private _planPersistenceState = inject(PlanEditorPersistenceStateService); readonly _planEditService = inject(PlanEditorService); readonly _planInteractiveSession? = inject(PlanInteractiveSessionService, { optional: true }); + protected readonly _panelSizes = inject(PlanTreePanelSizesDirective, { self: true }); + readonly activeNode: Signal = this._treeState.selectedNode; + private readonly leftPanel = contentChild(PlanTreeLeftPanelDirective); + private readonly rightPanel = contentChild(PlanTreeRightPanelDirective); + + protected readonly templateLeftPanel = computed(() => this.leftPanel()?._templateRef); + protected readonly templateRightPanel = computed(() => this.rightPanel()?._templateRef); + + protected readonly sizeTypeLeftPanel = computed(() => { + const leftPanel = this.leftPanel(); + return leftPanel?.sizeType?.() || 'pixel'; + }); + + protected readonly sizeTypeRightPanel = computed(() => { + const rightPanel = this.rightPanel(); + return rightPanel?.sizeType?.() || 'pixel'; + }); + /** @Output() **/ readonly externalObjectDrop = output(); @@ -79,9 +99,6 @@ export class PlanTreeComponent implements AfterViewInit, TreeActionsService { /** @ViewChild **/ private dragData = viewChild(DragDataService); - protected treeSize = this._planPersistenceState.getPanelSize(TREE_SIZE); - protected artefactDetailsSize = this._planPersistenceState.getPanelSize(ARTEFACT_DETAILS_SIZE); - private actions: TreeAction[] = [ { id: PlanTreeAction.OPEN, label: 'Open (Ctrl + O)' }, { id: PlanTreeAction.RENAME, label: 'Rename (F2)' }, @@ -274,14 +291,6 @@ export class PlanTreeComponent implements AfterViewInit, TreeActionsService { }, 200); } - handleTreeSizeChange(size: number): void { - this._planPersistenceState.setPanelSize(TREE_SIZE, size); - } - - handleArtefactDetailsSizeChange(size: number): void { - this._planPersistenceState.setPanelSize(ARTEFACT_DETAILS_SIZE, size); - } - private canOpenArtefact(artefact?: AbstractArtefact): boolean { if (!artefact) { return false; diff --git a/projects/step-core/src/lib/modules/plan-common/components/source-plan-editor/source-plan-editor.component.html b/projects/step-core/src/lib/modules/plan-common/components/source-plan-editor/source-plan-editor.component.html index 47e0989f88..72a582c213 100644 --- a/projects/step-core/src/lib/modules/plan-common/components/source-plan-editor/source-plan-editor.component.html +++ b/projects/step-core/src/lib/modules/plan-common/components/source-plan-editor/source-plan-editor.component.html @@ -1,16 +1,13 @@ - - - - Source - -
-
-
-
- - - - - - -
+ + + Source + +
+
+
+ @if (templateControls(); as controls) { +
+ +
+ } +
diff --git a/projects/step-core/src/lib/modules/plan-common/components/source-plan-editor/source-plan-editor.component.scss b/projects/step-core/src/lib/modules/plan-common/components/source-plan-editor/source-plan-editor.component.scss index f41d0e0bdf..406c72127d 100644 --- a/projects/step-core/src/lib/modules/plan-common/components/source-plan-editor/source-plan-editor.component.scss +++ b/projects/step-core/src/lib/modules/plan-common/components/source-plan-editor/source-plan-editor.component.scss @@ -1,5 +1,4 @@ :host { - display: flex; height: 100%; } diff --git a/projects/step-core/src/lib/modules/plan-common/components/source-plan-editor/source-plan-editor.component.ts b/projects/step-core/src/lib/modules/plan-common/components/source-plan-editor/source-plan-editor.component.ts index 9b269bc8a2..c8af388ad0 100644 --- a/projects/step-core/src/lib/modules/plan-common/components/source-plan-editor/source-plan-editor.component.ts +++ b/projects/step-core/src/lib/modules/plan-common/components/source-plan-editor/source-plan-editor.component.ts @@ -1,43 +1,41 @@ import { - AfterViewInit, Component, computed, + effect, ElementRef, inject, input, OnDestroy, OnInit, signal, + TemplateRef, + untracked, viewChild, } from '@angular/core'; import { forkJoin, map } from 'rxjs'; import * as ace from 'ace-builds'; import 'ace-builds/src-min-noconflict/ext-searchbox'; import { PlansService, KeywordsService, AbstractArtefact } from '../../../../client/step-client-module'; -import { DialogsService } from '../../../basics/step-basics.module'; +import { DialogsService, StepBasicsModule } from '../../../basics/step-basics.module'; import { AceMode } from '../../../rich-editor'; import { TreeStateService } from '../../../tree'; -import { SPLIT_EXPORTS } from '../../../split'; -import { StepMaterialModule } from '../../../step-material/step-material.module'; import { SourceEditorAutocompleteService } from '../../injectables/source-editor-autocomplete.service'; import { PlanEditorStrategy } from '../../types/plan-editor-strategy'; import { PlanContextApiService } from '../../injectables/plan-context-api.service'; import { PlanEditorService } from '../../injectables/plan-editor.service'; import { ArtefactTreeNode } from '../../types/artefact-tree-node'; import { PlanContext } from '../../types/plan-context.interface'; -import { PlanEditorPersistenceStateService } from '../../injectables/plan-editor-persistence-state.service'; import { PlanTreeComponent } from '../plan-tree/plan-tree.component'; - -const EDITOR_SIZE = 'DUAL_EDITOR_SIZE'; -const PLAN_SIZE = 'DUAL_PLAN_SIZE'; +import { PlanTreeLeftPanelDirective } from '../../directives/plan-tree-left-panel.directive'; +import { PlanTreeRightPanelDirective } from '../../directives/plan-tree-right-panel.directive'; @Component({ selector: 'step-source-plan-editor', templateUrl: './source-plan-editor.component.html', styleUrls: ['./source-plan-editor.component.scss'], - imports: [StepMaterialModule, SPLIT_EXPORTS, PlanTreeComponent], + imports: [StepBasicsModule, PlanTreeComponent, PlanTreeLeftPanelDirective, PlanTreeRightPanelDirective], }) -export class SourcePlanEditorComponent implements AfterViewInit, PlanEditorStrategy, OnInit, OnDestroy { +export class SourcePlanEditorComponent implements PlanEditorStrategy, OnInit, OnDestroy { private _sourceEditorAutocomplete = inject(SourceEditorAutocompleteService, { optional: true }); private _planContextApi = inject(PlanContextApiService); private _planApi = inject(PlansService); @@ -45,9 +43,9 @@ export class SourcePlanEditorComponent implements AfterViewInit, PlanEditorStrat private _planEditorService = inject(PlanEditorService); private _treeState = inject>(TreeStateService); private _dialogs = inject(DialogsService); - private _planEditorPersistenceState = inject(PlanEditorPersistenceStateService); readonly mode = input.required(); + readonly templateControls = input | undefined>(undefined); private editorElement = viewChild>('editor'); @@ -59,8 +57,13 @@ export class SourcePlanEditorComponent implements AfterViewInit, PlanEditorStrat private updateEditorWithoutSave = false; - protected editorSize = this._planEditorPersistenceState.getPanelSize(EDITOR_SIZE); - protected planSize = this._planEditorPersistenceState.getPanelSize(PLAN_SIZE); + private effectElementReady = effect(() => { + const editorElement = this.editorElement(); + this.destroyEditor(); + if (!!editorElement?.nativeElement) { + untracked(() => this.createEditor(editorElement.nativeElement)); + } + }); private parseCallback = () => { if (this.updateEditorWithoutSave) { @@ -69,62 +72,6 @@ export class SourcePlanEditorComponent implements AfterViewInit, PlanEditorStrat this.parse(); }; - ngAfterViewInit(): void { - ace.require('ace/ext/language_tools'); - this.editor = ace.edit(this.editorElement()!.nativeElement); - this.editor.getSession().getUndoManager().reset(); - this.editor.setTheme('ace/theme/chrome'); - this.editor.getSession().setMode(this.mode()); - this.editor.getSession().setUseWorker(false); - if (this._sourceEditorAutocomplete) { - this.editor.setOptions({ - enableBasicAutocompletion: [ - { - getCompletions: ( - editor: ace.Editor, - session: ace.EditSession, - position: ace.Ace.Point, - prefix: string, - callback: ace.Ace.CompleterCallback, - ) => { - this._sourceEditorAutocomplete!.autocomplete(prefix).subscribe((parsingResult) => { - callback( - null, - parsingResult.map((text) => ({ - name: text, - value: text, - meta: 'Keyword', - })), - ); - }); - }, - }, - ], - enableSnippets: true, - enableLiveAutocompletion: false, - }); - } - const planContext = this.planContext(); - if (planContext) { - const plan = planContext.plan; - this.updateEditorWithoutSave = true; - this.editor.setValue((plan as any).source, 1); - this.updateEditorWithoutSave = false; - } - - this.editor.getSession().on('change', this.parseCallback); - - this.editor.focus(); - } - - handleEditorSizeChange(size: number): void { - this._planEditorPersistenceState.setPanelSize(EDITOR_SIZE, size); - } - - handlePlanSizeChange(size: number): void { - this._planEditorPersistenceState.setPanelSize(PLAN_SIZE, size); - } - addControl(artefactTypeId: string): void { this._dialogs .showErrorMsg( @@ -197,13 +144,66 @@ export class SourcePlanEditorComponent implements AfterViewInit, PlanEditorStrat } ngOnInit(): void { + ace.require('ace/ext/language_tools'); this._planEditorService.useStrategy(this); } ngOnDestroy(): void { + this.destroyEditor(); this._planEditorService.removeStrategy(); - this.editor!.getSession().off('change', this.parseCallback); - this.editor!.destroy(); + } + + private destroyEditor(): void { + this.editor?.getSession()?.off?.('change', this.parseCallback); + this.editor?.destroy?.(); + this.editor = undefined; + } + + private createEditor(element: HTMLDivElement): void { + this.editor = ace.edit(element); + this.editor.getSession().getUndoManager().reset(); + this.editor.setTheme('ace/theme/chrome'); + this.editor.getSession().setMode(this.mode()); + this.editor.getSession().setUseWorker(false); + if (this._sourceEditorAutocomplete) { + this.editor.setOptions({ + enableBasicAutocompletion: [ + { + getCompletions: ( + editor: ace.Editor, + session: ace.EditSession, + position: ace.Ace.Point, + prefix: string, + callback: ace.Ace.CompleterCallback, + ) => { + this._sourceEditorAutocomplete!.autocomplete(prefix).subscribe((parsingResult) => { + callback( + null, + parsingResult.map((text) => ({ + name: text, + value: text, + meta: 'Keyword', + })), + ); + }); + }, + }, + ], + enableSnippets: true, + enableLiveAutocompletion: false, + }); + } + const planContext = this.planContext(); + if (planContext) { + const plan = planContext.plan; + this.updateEditorWithoutSave = true; + this.editor.setValue((plan as any).source, 1); + this.updateEditorWithoutSave = false; + } + + this.editor.getSession().on('change', this.parseCallback); + + this.editor.focus(); } handlePlanContextChange(planContext?: PlanContext): void { diff --git a/projects/step-core/src/lib/modules/plan-common/directives/plan-tree-left-panel.directive.ts b/projects/step-core/src/lib/modules/plan-common/directives/plan-tree-left-panel.directive.ts new file mode 100644 index 0000000000..b5dee27ddc --- /dev/null +++ b/projects/step-core/src/lib/modules/plan-common/directives/plan-tree-left-panel.directive.ts @@ -0,0 +1,10 @@ +import { Directive, inject, input, TemplateRef } from '@angular/core'; +import { SplitAreaSizeType } from '../../split'; + +@Directive({ + selector: '[stepPlanTreeLeftPanel]', +}) +export class PlanTreeLeftPanelDirective { + readonly _templateRef = inject(TemplateRef); + readonly sizeType = input('', { alias: 'stepPlanTreeLeftPanel' }); +} diff --git a/projects/step-core/src/lib/modules/plan-common/directives/plan-tree-right-panel.directive.ts b/projects/step-core/src/lib/modules/plan-common/directives/plan-tree-right-panel.directive.ts new file mode 100644 index 0000000000..ae36cc6c97 --- /dev/null +++ b/projects/step-core/src/lib/modules/plan-common/directives/plan-tree-right-panel.directive.ts @@ -0,0 +1,10 @@ +import { Directive, inject, input, TemplateRef } from '@angular/core'; +import { SplitAreaSizeType } from '../../split'; + +@Directive({ + selector: '[stepPlanTreeRightPanel]', +}) +export class PlanTreeRightPanelDirective { + readonly _templateRef = inject(TemplateRef); + readonly sizeType = input('', { alias: 'stepPlanTreeRightPanel' }); +} diff --git a/projects/step-core/src/lib/modules/plan-common/index.ts b/projects/step-core/src/lib/modules/plan-common/index.ts index f21475012c..1aa5479ba8 100644 --- a/projects/step-core/src/lib/modules/plan-common/index.ts +++ b/projects/step-core/src/lib/modules/plan-common/index.ts @@ -9,6 +9,8 @@ import { ArtefactDetailsComponent } from './components/artefact-details/artefact import { PlanTreeComponent } from './components/plan-tree/plan-tree.component'; import { SourcePlanEditorComponent } from './components/source-plan-editor/source-plan-editor.component'; import { PlanEditorAttributesDirective } from './directives/plan-editor-attributes.directive'; +import { PlanTreePanelSizesDirective } from './components/plan-tree/plan-tree-panel-sizes.directive'; +import { PlanTreeRightPanelDirective } from './directives/plan-tree-right-panel.directive'; export * from './components/plan-create-dialog/plan-create-dialog.component'; export * from './components/select-plan/select-plan.component'; @@ -17,9 +19,12 @@ export * from './components/thread-distribution-wizard-dialog/thread-distributio export * from './components/artefact-child-container-settings/artefact-child-container-settings.component'; export * from './components/artefact-details/artefact-details.component'; export * from './components/plan-tree/plan-tree.component'; +export * from './components/plan-tree/plan-tree-panel-sizes.directive'; export * from './components/source-plan-editor/source-plan-editor.component'; export * from './directives/plan-editor-attributes.directive'; +export * from './directives/plan-tree-left-panel.directive'; +export * from './directives/plan-tree-right-panel.directive'; export * from './injectables/plan-artefact-resolver.service'; export * from './injectables/plan-by-id-cache.service'; @@ -56,4 +61,6 @@ export const PLAN_COMMON_EXPORTS = [ PlanTreeComponent, SourcePlanEditorComponent, PlanEditorAttributesDirective, + PlanTreePanelSizesDirective, + PlanTreeRightPanelDirective, ]; diff --git a/projects/step-core/src/lib/modules/split/components/split-area/split-area.component.ts b/projects/step-core/src/lib/modules/split/components/split-area/split-area.component.ts index 1af687d1cf..f77fd26967 100644 --- a/projects/step-core/src/lib/modules/split/components/split-area/split-area.component.ts +++ b/projects/step-core/src/lib/modules/split/components/split-area/split-area.component.ts @@ -13,7 +13,7 @@ import { } from '@angular/core'; import { debounceTime, Subject } from 'rxjs'; -type SplitAreaSizeType = 'pixel' | 'percent' | 'flex'; +export type SplitAreaSizeType = 'pixel' | 'percent' | 'flex'; @Component({ selector: 'step-split-area', diff --git a/projects/step-core/src/lib/modules/split/components/split-gutter/split-gutter.component.ts b/projects/step-core/src/lib/modules/split/components/split-gutter/split-gutter.component.ts index ed6595290c..c56b028c9b 100644 --- a/projects/step-core/src/lib/modules/split/components/split-gutter/split-gutter.component.ts +++ b/projects/step-core/src/lib/modules/split/components/split-gutter/split-gutter.component.ts @@ -1,75 +1,94 @@ -import { AfterViewInit, Component, HostBinding, HostListener, inject } from '@angular/core'; -import { SplitAreaComponent } from '../split-area/split-area.component'; +import { Component, computed, inject, signal } from '@angular/core'; import { SplitComponent } from '../split/split.component'; @Component({ selector: 'step-split-gutter', templateUrl: './split-gutter.component.html', styleUrls: ['./split-gutter.component.scss'], + host: { + '[class.active]': 'isActive()', + '(mousedown)': 'handleMouseDown($event)', + '(window:mousemove)': 'handleMouseMove($event)', + '(window:mouseup)': 'handleMouseUp()', + }, }) -export class SplitGutterComponent implements AfterViewInit { +export class SplitGutterComponent { private _splitComponent = inject(SplitComponent); - private leftArea?: SplitAreaComponent; - private rightArea?: SplitAreaComponent; + private eventX?: number; private leftAreaWidth?: number; private rightAreaWidth?: number; - @HostBinding('class.active') - active?: boolean; - - ngAfterViewInit(): void { - if (!this._splitComponent.areas || !this._splitComponent.gutters) { - return; + private areas = computed(() => this._splitComponent.areas()); + private gutters = computed(() => this._splitComponent.gutters()); + private index = computed(() => { + const gutters = this.gutters(); + if (!gutters) { + return -1; } + return gutters.indexOf(this); + }); + private leftArea = computed(() => { + const areas = this.areas(); + const index = this.index(); + if (!areas || index < 0) { + return undefined; + } + return areas[index]; + }); + private rightArea = computed(() => { + const areas = this.areas(); + const index = this.index(); + if (!areas || index < 0) { + return undefined; + } + return areas[index + 1]; + }); - const gutters = this._splitComponent.gutters.toArray(); - const areas = this._splitComponent.areas.toArray(); - const index = gutters.indexOf(this); - - this.leftArea = areas[index]; - this.rightArea = areas[index + 1]; - } + protected isActive = signal(false); setAreaWidths(): void { - if (!this.leftArea || !this.rightArea) { + const leftArea = this.leftArea(); + const rightArea = this.rightArea(); + if (!leftArea || !rightArea) { return; } - this.leftAreaWidth = this.leftArea.width; - this.rightAreaWidth = this.rightArea.width; + this.leftAreaWidth = leftArea.width; + this.rightAreaWidth = rightArea.width; } setFlex(): void { - this.leftArea?.setSize(this.leftAreaWidth!); - this.rightArea?.setSize(this.rightAreaWidth!); + this.leftArea()?.setSize?.(this.leftAreaWidth!); + this.rightArea()?.setSize?.(this.rightAreaWidth!); } - @HostListener('mousedown', ['$event']) - onMouseDown(event: MouseEvent): void { + protected handleMouseDown(event: MouseEvent): void { event.preventDefault(); - this.active = true; + this.isActive.set(true); this.eventX = event.clientX; - if (!this._splitComponent.gutters) { + const gutters = this.gutters(); + if (!gutters) { return; } - this._splitComponent.gutters.forEach((gutter) => { + gutters.forEach((gutter) => { gutter.setAreaWidths(); }); - this._splitComponent.gutters.forEach((gutter) => { + gutters.forEach((gutter) => { gutter.setFlex(); }); } - @HostListener('window:mousemove', ['$event']) - onMouseMove(event: MouseEvent): void { + protected handleMouseMove(event: MouseEvent): void { + const leftArea = this.leftArea(); + const rightArea = this.rightArea(); if ( this.eventX === undefined || - !this.leftArea || - !this.rightArea || + !leftArea || + !rightArea || this.leftAreaWidth === undefined || this.rightAreaWidth === undefined ) { @@ -89,13 +108,12 @@ export class SplitGutterComponent implements AfterViewInit { const leftAreaWidth = this.leftAreaWidth - delta; const rightAreaWidth = this.rightAreaWidth + delta; - this.leftArea?.setSize(leftAreaWidth); - this.rightArea?.setSize(rightAreaWidth); + this.leftArea()?.setSize?.(leftAreaWidth); + this.rightArea()?.setSize?.(rightAreaWidth); } - @HostListener('window:mouseup') - onMouseUp(): void { - delete this.active; + protected handleMouseUp(): void { + this.isActive.set(false); delete this.eventX; delete this.leftAreaWidth; delete this.rightAreaWidth; diff --git a/projects/step-core/src/lib/modules/split/components/split/split.component.html b/projects/step-core/src/lib/modules/split/components/split/split.component.html index 6dbc743063..40b3726403 100644 --- a/projects/step-core/src/lib/modules/split/components/split/split.component.html +++ b/projects/step-core/src/lib/modules/split/components/split/split.component.html @@ -1 +1 @@ - + diff --git a/projects/step-core/src/lib/modules/split/components/split/split.component.ts b/projects/step-core/src/lib/modules/split/components/split/split.component.ts index 1639e594f8..8972145201 100644 --- a/projects/step-core/src/lib/modules/split/components/split/split.component.ts +++ b/projects/step-core/src/lib/modules/split/components/split/split.component.ts @@ -1,4 +1,4 @@ -import { Component, ContentChildren, QueryList } from '@angular/core'; +import { Component, contentChildren } from '@angular/core'; import { SplitAreaComponent } from '../split-area/split-area.component'; import { SplitGutterComponent } from '../split-gutter/split-gutter.component'; @@ -8,6 +8,6 @@ import { SplitGutterComponent } from '../split-gutter/split-gutter.component'; styleUrls: ['./split.component.scss'], }) export class SplitComponent { - @ContentChildren(SplitAreaComponent) areas?: QueryList; - @ContentChildren(SplitGutterComponent) gutters?: QueryList; + readonly areas = contentChildren(SplitAreaComponent); + readonly gutters = contentChildren(SplitGutterComponent); } diff --git a/projects/step-core/src/lib/step-core.module.ts b/projects/step-core/src/lib/step-core.module.ts index dbddae5e05..2ea90e0936 100644 --- a/projects/step-core/src/lib/step-core.module.ts +++ b/projects/step-core/src/lib/step-core.module.ts @@ -17,7 +17,6 @@ import { CapsLockDirective } from './directives/caps-lock.directive'; import { ClampFadeDirective } from './directives/clamp-fade.directive'; import { FocusableDirective } from './directives/focusable.directive'; import { FocusablesDirective } from './directives/focusables.directive'; -import { MaxHeightViewportHeightMinusOffsetTopDirective } from './directives/max-height-viewport-height-minus-offset-top.directive'; import { RecursiveTabIndexDirective } from './directives/recursive-tab-index.directive'; import { TooltipImmediateCloseDirective } from './directives/tooltip-immediate-close.directive'; import { TrapFocusDirective } from './directives/trap-focus.directive'; @@ -94,7 +93,6 @@ import { ScreenInputOptionsPipe } from './pipes/screen-input-options.pipe'; TrapFocusDirective, FocusableDirective, FocusablesDirective, - MaxHeightViewportHeightMinusOffsetTopDirective, RecursiveTabIndexDirective, ReferenceArtefactNameComponent, PlanNameComponent, @@ -185,7 +183,6 @@ import { ScreenInputOptionsPipe } from './pipes/screen-input-options.pipe'; TrapFocusDirective, FocusableDirective, FocusablesDirective, - MaxHeightViewportHeightMinusOffsetTopDirective, RecursiveTabIndexDirective, PlanNameComponent, PredefinedOptionsInputComponent, @@ -280,7 +277,6 @@ export * from './directives/caps-lock.directive'; export * from './directives/clamp-fade.directive'; export { FocusableDirective } from './directives/focusable.directive'; export { FocusablesDirective } from './directives/focusables.directive'; -export { MaxHeightViewportHeightMinusOffsetTopDirective } from './directives/max-height-viewport-height-minus-offset-top.directive'; export { RecursiveTabIndexDirective } from './directives/recursive-tab-index.directive'; export * from './directives/tooltip-immediate-close.directive'; export { TrapFocusDirective } from './directives/trap-focus.directive'; diff --git a/projects/step-frontend/src/lib/modules/plan-editor/components/plan-common-tree-editor-form/plan-common-tree-editor-form.component.html b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-common-tree-editor-form/plan-common-tree-editor-form.component.html index c013d95665..81211f1134 100644 --- a/projects/step-frontend/src/lib/modules/plan-editor/components/plan-common-tree-editor-form/plan-common-tree-editor-form.component.html +++ b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-common-tree-editor-form/plan-common-tree-editor-form.component.html @@ -1 +1,7 @@ - + + @if (controlTemplate(); as ctrlTemplate) { +
+ +
+ } +
diff --git a/projects/step-frontend/src/lib/modules/plan-editor/components/plan-common-tree-editor-form/plan-common-tree-editor-form.component.ts b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-common-tree-editor-form/plan-common-tree-editor-form.component.ts index 9ed69f6c9e..4a4f10a1d1 100644 --- a/projects/step-frontend/src/lib/modules/plan-editor/components/plan-common-tree-editor-form/plan-common-tree-editor-form.component.ts +++ b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-common-tree-editor-form/plan-common-tree-editor-form.component.ts @@ -1,4 +1,4 @@ -import { Component, DestroyRef, inject, OnDestroy, OnInit, signal } from '@angular/core'; +import { Component, DestroyRef, inject, OnDestroy, OnInit, signal, TemplateRef } from '@angular/core'; import { AbstractArtefact, ArtefactsFactoryService, @@ -14,6 +14,7 @@ import { PlanContextApiService, PlanContext, DropInfo, + PlanTypeContext, } from '@exense/step-core'; import { BehaviorSubject, filter, first, forkJoin, map, merge, Observable, of, Subject, switchMap, tap } from 'rxjs'; import { PlanHistoryService } from '../../injectables/plan-history.service'; @@ -42,7 +43,12 @@ export class PlanCommonTreeEditorFormComponent implements CustomComponent, PlanE private _artefactsFactory = inject(ArtefactsFactoryService); private _destroyRef = inject(DestroyRef); - context?: any; + protected readonly controlTemplate = signal | undefined>(undefined); + + contextChange(previousContext?: PlanTypeContext, currentContext?: PlanTypeContext): void { + this.controlTemplate.set(currentContext?.templateControls); + } + private planContextChange$ = new Subject(); private selectedNode$ = toObservable(this._treeState.selectedNode); diff --git a/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-controls.component.html b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-controls.component.html index b357a8b18f..a848c7aaae 100644 --- a/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-controls.component.html +++ b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-controls.component.html @@ -2,13 +2,13 @@
@switch (selectedTab()) { @case ('controls') { - + } @case ('keywords') { - + } @case ('other') { - + } @case ('console') { diff --git a/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-controls.component.scss b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-controls.component.scss index e69de29bb2..961d1e6e35 100644 --- a/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-controls.component.scss +++ b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-controls.component.scss @@ -0,0 +1,3 @@ +step-tabs { + width: auto !important; +} diff --git a/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-controls.component.ts b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-controls.component.ts index a6220c079b..905d8fb1e3 100644 --- a/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-controls.component.ts +++ b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-controls.component.ts @@ -1,6 +1,6 @@ import { Component, computed, DestroyRef, inject, model, OnInit, output, signal, viewChild } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; -import { StepCoreModule, Tab, TabsComponent } from '@exense/step-core'; +import { PlanEditorService, StepCoreModule, Tab, TabsComponent } from '@exense/step-core'; import { distinctUntilChanged, filter } from 'rxjs'; import { ExecutionModule } from '../../../execution/execution.module'; import { PlanArtefactListComponent } from './plan-artefact-list/plan-artefact-list.component'; @@ -32,15 +32,12 @@ const TABS = { }) export class PlanControlsComponent implements OnInit { private _destroyRef = inject(DestroyRef); + protected readonly _planEditorService = inject(PlanEditorService); protected readonly _interactiveSession = inject(InteractiveSessionService); private tabs = viewChild('tabs', { read: TabsComponent }); private keywordCalls = viewChild('keywordCalls', { read: KeywordCallsComponent }); - readonly addControl = output(); - readonly addKeywords = output(); - readonly addPlans = output(); - protected readonly selectedTab = model('controls'); protected readonly componentTabs = signal[]>([TABS.controls, TABS.keywords, TABS.other]); diff --git a/projects/step-frontend/src/lib/modules/plan-editor/components/plan-editor-base/plan-editor-base.component.html b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-editor-base/plan-editor-base.component.html index 909e62d860..22e203b510 100644 --- a/projects/step-frontend/src/lib/modules/plan-editor/components/plan-editor-base/plan-editor-base.component.html +++ b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-editor-base/plan-editor-base.component.html @@ -43,11 +43,15 @@
+ @if (planClass) { + + + + + } +
diff --git a/projects/step-frontend/src/lib/os-plugins/modules/yaml-plan-editor/components/yaml-plan-editor/yaml-plan-editor.component.html b/projects/step-frontend/src/lib/os-plugins/modules/yaml-plan-editor/components/yaml-plan-editor/yaml-plan-editor.component.html index 585709bab1..1b75f2d2e7 100644 --- a/projects/step-frontend/src/lib/os-plugins/modules/yaml-plan-editor/components/yaml-plan-editor/yaml-plan-editor.component.html +++ b/projects/step-frontend/src/lib/os-plugins/modules/yaml-plan-editor/components/yaml-plan-editor/yaml-plan-editor.component.html @@ -1 +1 @@ - + diff --git a/projects/step-frontend/src/lib/os-plugins/modules/yaml-plan-editor/components/yaml-plan-editor/yaml-plan-editor.component.ts b/projects/step-frontend/src/lib/os-plugins/modules/yaml-plan-editor/components/yaml-plan-editor/yaml-plan-editor.component.ts index 5f7239aeee..afe2549a33 100644 --- a/projects/step-frontend/src/lib/os-plugins/modules/yaml-plan-editor/components/yaml-plan-editor/yaml-plan-editor.component.ts +++ b/projects/step-frontend/src/lib/os-plugins/modules/yaml-plan-editor/components/yaml-plan-editor/yaml-plan-editor.component.ts @@ -1,5 +1,5 @@ -import { Component } from '@angular/core'; -import { AceMode, CustomComponent } from '@exense/step-core'; +import { Component, signal, TemplateRef } from '@angular/core'; +import { AceMode, CustomComponent, PlanTypeContext } from '@exense/step-core'; @Component({ selector: 'step-yaml-plan-editor', @@ -8,6 +8,9 @@ import { AceMode, CustomComponent } from '@exense/step-core'; standalone: false, }) export class YamlPlanEditorComponent implements CustomComponent { - context?: unknown; protected readonly AceMode = AceMode; + protected readonly templateControls = signal | undefined>(undefined); + contextChange(previousContext?: PlanTypeContext, currentContext?: PlanTypeContext): void { + this.templateControls.set(currentContext?.templateControls); + } } From eecd783c91bac9119449997e5d697f5680c24627 Mon Sep 17 00:00:00 2001 From: dvladir Date: Wed, 19 Nov 2025 18:09:39 +0300 Subject: [PATCH 3/9] SED-4373 Change split area to signals --- .../split-area/split-area.component.html | 4 +- .../split-area/split-area.component.ts | 151 ++++++++---------- 2 files changed, 66 insertions(+), 89 deletions(-) diff --git a/projects/step-core/src/lib/modules/split/components/split-area/split-area.component.html b/projects/step-core/src/lib/modules/split/components/split-area/split-area.component.html index 4e262bff97..c01b8c80a5 100644 --- a/projects/step-core/src/lib/modules/split/components/split-area/split-area.component.html +++ b/projects/step-core/src/lib/modules/split/components/split-area/split-area.component.html @@ -1,3 +1,3 @@ -
- +
+
diff --git a/projects/step-core/src/lib/modules/split/components/split-area/split-area.component.ts b/projects/step-core/src/lib/modules/split/components/split-area/split-area.component.ts index f77fd26967..ba7e33e835 100644 --- a/projects/step-core/src/lib/modules/split/components/split-area/split-area.component.ts +++ b/projects/step-core/src/lib/modules/split/components/split-area/split-area.component.ts @@ -1,17 +1,17 @@ import { AfterViewInit, Component, + effect, ElementRef, - EventEmitter, - HostListener, inject, - Input, - OnChanges, + input, + linkedSignal, OnDestroy, - Output, - SimpleChanges, + output, + signal, } from '@angular/core'; -import { debounceTime, Subject } from 'rxjs'; +import { debounceTime, map, Subject, switchMap } from 'rxjs'; +import { takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop'; export type SplitAreaSizeType = 'pixel' | 'percent' | 'flex'; @@ -19,94 +19,35 @@ export type SplitAreaSizeType = 'pixel' | 'percent' | 'flex'; selector: 'step-split-area', templateUrl: './split-area.component.html', styleUrls: ['./split-area.component.scss'], + host: { + '(focusin)': 'handleFocusIn()', + }, }) -export class SplitAreaComponent implements AfterViewInit, OnChanges, OnDestroy { +export class SplitAreaComponent implements AfterViewInit, OnDestroy { private _elementRef = inject>(ElementRef); - private sizeUpdateInternal$?: Subject; - private isViewInitialized = false; + private sizeUpdateInternal$ = new Subject(); - @Input() sizeUpdateDebounce: number = 300; - @Input() sizeType: SplitAreaSizeType = 'pixel'; - @Input() size?: number; - @Input() padding?: string; + private isViewInitialized = signal(false); - @Output() sizeChange = new EventEmitter(); + readonly padding = input(undefined); + readonly sizeType = input('pixel'); + readonly size = input(undefined); - ngAfterViewInit(): void { - this.isViewInitialized = true; - this.changeSize(); - this.setupSizeUpdate(); - } - - ngOnChanges(changes: SimpleChanges): void { - const cSizeUpdateDebounce = changes['sizeUpdateDebounce']; - if (cSizeUpdateDebounce?.previousValue !== cSizeUpdateDebounce?.currentValue || cSizeUpdateDebounce?.firstChange) { - this.setupSizeUpdate(cSizeUpdateDebounce?.currentValue); - } + private sizeInternal = linkedSignal(() => this.size()); - const cSizeType = changes['sizeType']; - const cSize = changes['size']; + readonly sizeUpdateDebounce = input(300); - let size: number | undefined; - let sizeType: SplitAreaSizeType | undefined; + readonly sizeChange = output(); - if (cSize?.currentValue !== cSize?.previousValue || cSize?.firstChange) { - size = cSize?.currentValue; - } - - if (cSizeType?.currentValue !== cSizeType?.previousValue || cSizeType?.firstChange) { - sizeType = cSizeType.currentValue; - } + private effectUpdateSize = effect(() => { + const isViewInitialized = this.isViewInitialized(); + const sizeType = this.sizeType(); + const size = this.sizeInternal(); - if (size || sizeType) { - this.changeSize(size, sizeType); - } - } - - ngOnDestroy(): void { - this.destroySizeUpdate(); - } - - get width(): number { - return this.boundingClientRect.width; - } - - @HostListener('focusin') - onFocusIn(): void { - this._elementRef.nativeElement.scrollTop = 0; - this._elementRef.nativeElement.scrollLeft = 0; - } - - setSize(size: number): void { - this.size = size; - this.changeSize(size); - } - - private get boundingClientRect(): DOMRect { - return this._elementRef.nativeElement.getBoundingClientRect(); - } - - private destroySizeUpdate(): void { - if (!this.sizeUpdateInternal$) { + if (!isViewInitialized) { return; } - this.sizeUpdateInternal$.complete(); - this.sizeUpdateInternal$ = undefined; - } - - private setupSizeUpdate(sizeUpdateDebounce?: number): void { - this.destroySizeUpdate(); - sizeUpdateDebounce = sizeUpdateDebounce || this.sizeUpdateDebounce; - this.sizeUpdateInternal$ = new Subject(); - this.sizeUpdateInternal$.pipe(debounceTime(sizeUpdateDebounce)).subscribe(() => this.sizeChange.emit(this.size)); - } - private changeSize(size?: number, sizeType?: SplitAreaSizeType): void { - if (!this.isViewInitialized) { - return; - } - size = size ?? this.size; - sizeType = sizeType ?? this.sizeType; switch (sizeType) { case 'percent': this.setFlex({ @@ -127,7 +68,43 @@ export class SplitAreaComponent implements AfterViewInit, OnChanges, OnDestroy { }); break; } + }); + + private sizeUpdateDebounce$ = toObservable(this.sizeUpdateDebounce); + + private sizeDebounceSubscription = this.sizeUpdateDebounce$ + .pipe( + switchMap((debounceValue) => this.sizeUpdateInternal$.pipe(debounceTime(debounceValue))), + map(() => this.sizeInternal()), + takeUntilDestroyed(), + ) + .subscribe((size) => this.sizeChange.emit(size)); + + ngAfterViewInit(): void { + this.isViewInitialized.set(true); } + + ngOnDestroy(): void { + this.sizeUpdateInternal$.complete(); + } + + get width(): number { + return this.boundingClientRect.width; + } + + setSize(size: number): void { + this.sizeInternal.set(size); + } + + protected handleFocusIn(): void { + this._elementRef.nativeElement.scrollTop = 0; + this._elementRef.nativeElement.scrollLeft = 0; + } + + private get boundingClientRect(): DOMRect { + return this._elementRef.nativeElement.getBoundingClientRect(); + } + private setFlex({ flexBasis = '0', flexGrow = '0', @@ -137,9 +114,9 @@ export class SplitAreaComponent implements AfterViewInit, OnChanges, OnDestroy { flexGrow?: string; flexShrink?: string; }): void { - this._elementRef.nativeElement.style.setProperty('flex-basis', flexBasis || ''); - this._elementRef.nativeElement.style.setProperty('flex-grow', flexGrow || ''); - this._elementRef.nativeElement.style.setProperty('flex-shrink', flexShrink || ''); - this.sizeUpdateInternal$?.next(); + this._elementRef?.nativeElement?.style?.setProperty?.('flex-basis', flexBasis || ''); + this._elementRef?.nativeElement?.style?.setProperty?.('flex-grow', flexGrow || ''); + this._elementRef?.nativeElement?.style?.setProperty?.('flex-shrink', flexShrink || ''); + this.sizeUpdateInternal$.next(); } } From 692d14f4acbc02959324fb05effe9fc61b0f73b4 Mon Sep 17 00:00:00 2001 From: dvladir Date: Thu, 20 Nov 2025 11:34:45 +0300 Subject: [PATCH 4/9] SED-4373: Fix build --- projects/step-core/src/lib/modules/basics/step-basics.module.ts | 1 + .../components/item-component/plan-type.component.ts | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/step-core/src/lib/modules/basics/step-basics.module.ts b/projects/step-core/src/lib/modules/basics/step-basics.module.ts index a975dc5823..fc88f54d05 100644 --- a/projects/step-core/src/lib/modules/basics/step-basics.module.ts +++ b/projects/step-core/src/lib/modules/basics/step-basics.module.ts @@ -402,3 +402,4 @@ export * from './pipes/resource-url.pipe'; export * from './utils/toggle-validators'; export * from './utils/resource-id'; export * from './pipes/number-separate-thousands.pipe'; +export * from './directives/max-height-viewport-height-minus-offset-top.directive'; diff --git a/projects/step-core/src/lib/modules/custom-registeries/components/item-component/plan-type.component.ts b/projects/step-core/src/lib/modules/custom-registeries/components/item-component/plan-type.component.ts index b891f66bc9..621e444766 100644 --- a/projects/step-core/src/lib/modules/custom-registeries/components/item-component/plan-type.component.ts +++ b/projects/step-core/src/lib/modules/custom-registeries/components/item-component/plan-type.component.ts @@ -16,5 +16,4 @@ export interface PlanTypeContext { }) export class PlanTypeComponent extends BaseItemComponent { protected override readonly registryType: CustomRegistryType = CustomRegistryType.PLAN_TYPE; - @Input() override context?: PlanTypeContext; } From 24ab0ee73bef31225d01195ab6befa635ee4f976 Mon Sep 17 00:00:00 2001 From: dvladir Date: Thu, 20 Nov 2025 14:18:25 +0300 Subject: [PATCH 5/9] SED-4373 Move size peristence to split area --- .../plan-tree-panel-sizes.directive.ts | 39 --------------- .../plan-tree/plan-tree.component.html | 10 ++-- .../plan-tree/plan-tree.component.ts | 7 ++- .../source-plan-editor.component.html | 4 +- .../plan-tree-left-panel.directive.ts | 3 +- .../plan-tree-right-panel.directive.ts | 3 +- .../src/lib/modules/plan-common/index.ts | 4 -- .../plan-editor-persistence-state.service.ts | 33 ------------- .../split-area/split-area.component.ts | 33 ++++++++++--- .../split/components/split/split.component.ts | 5 +- .../directives/split-section.directive.ts | 11 +++++ .../step-core/src/lib/modules/split/index.ts | 4 +- .../split-area-size-persistence.service.ts | 47 +++++++++++++++++++ ...lan-common-tree-editor-form.component.html | 2 +- .../plan-editor-base.component.html | 20 -------- .../plan-editor-base.component.ts | 25 ---------- 16 files changed, 107 insertions(+), 143 deletions(-) delete mode 100644 projects/step-core/src/lib/modules/plan-common/components/plan-tree/plan-tree-panel-sizes.directive.ts delete mode 100644 projects/step-core/src/lib/modules/plan-common/injectables/plan-editor-persistence-state.service.ts create mode 100644 projects/step-core/src/lib/modules/split/directives/split-section.directive.ts create mode 100644 projects/step-core/src/lib/modules/split/injectables/split-area-size-persistence.service.ts diff --git a/projects/step-core/src/lib/modules/plan-common/components/plan-tree/plan-tree-panel-sizes.directive.ts b/projects/step-core/src/lib/modules/plan-common/components/plan-tree/plan-tree-panel-sizes.directive.ts deleted file mode 100644 index 20af2050d7..0000000000 --- a/projects/step-core/src/lib/modules/plan-common/components/plan-tree/plan-tree-panel-sizes.directive.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Directive, effect, inject, model } from '@angular/core'; -import { PlanEditorPersistenceStateService } from '../../injectables/plan-editor-persistence-state.service'; - -const TREE_SIZE = 'TREE_SIZE'; -const ARTEFACT_DETAILS_SIZE = 'ARTEFACT_DETAILS_SIZE'; -const LEFT_PANEL_SIZE = 'LEFT_PANEL_SIZE'; -const RIGHT_PANEL_SIZE = 'RIGHT_PANEL_SIZE'; - -@Directive({ - selector: '[stepPlanTreePanelSizes]', -}) -export class PlanTreePanelSizesDirective { - private _planPersistenceState = inject(PlanEditorPersistenceStateService); - - readonly leftPanelSize = model(this._planPersistenceState.getPanelSize(LEFT_PANEL_SIZE)); - readonly treeSize = model(this._planPersistenceState.getPanelSize(TREE_SIZE)); - readonly artefactDetailsSize = model(this._planPersistenceState.getPanelSize(ARTEFACT_DETAILS_SIZE)); - readonly rightPanelSize = model(this._planPersistenceState.getPanelSize(RIGHT_PANEL_SIZE)); - - private effectLeftPanelSizeChange = effect(() => { - const size = this.leftPanelSize(); - this._planPersistenceState.setPanelSize(LEFT_PANEL_SIZE, size ?? 0); - }); - - private effectTreePanelSizeChange = effect(() => { - const size = this.treeSize(); - this._planPersistenceState.setPanelSize(TREE_SIZE, size ?? 0); - }); - - private effectArtefactDetailsSizeChange = effect(() => { - const size = this.artefactDetailsSize(); - this._planPersistenceState.setPanelSize(ARTEFACT_DETAILS_SIZE, size ?? 0); - }); - - private effectRightPanelSizeChange = effect(() => { - const size = this.rightPanelSize(); - this._planPersistenceState.setPanelSize(RIGHT_PANEL_SIZE, size ?? 0); - }); -} diff --git a/projects/step-core/src/lib/modules/plan-common/components/plan-tree/plan-tree.component.html b/projects/step-core/src/lib/modules/plan-common/components/plan-tree/plan-tree.component.html index f186779838..4eff8e2755 100644 --- a/projects/step-core/src/lib/modules/plan-common/components/plan-tree/plan-tree.component.html +++ b/projects/step-core/src/lib/modules/plan-common/components/plan-tree/plan-tree.component.html @@ -1,12 +1,12 @@ - + @if (templateLeftPanel(); as left) { - + } - + - + @if (activeNode(); as activeNode) { @if (activeNode.originalArtefact; as selectedArtefact) { @@ -81,7 +81,7 @@ @if (templateRightPanel(); as right) { - + } diff --git a/projects/step-core/src/lib/modules/plan-common/components/plan-tree/plan-tree.component.ts b/projects/step-core/src/lib/modules/plan-common/components/plan-tree/plan-tree.component.ts index 88ea490dd0..9d80fa72a5 100644 --- a/projects/step-core/src/lib/modules/plan-common/components/plan-tree/plan-tree.component.ts +++ b/projects/step-core/src/lib/modules/plan-common/components/plan-tree/plan-tree.component.ts @@ -33,7 +33,6 @@ import { StepMaterialModule } from '../../../step-material/step-material.module' import { StepBasicsModule } from '../../../basics/step-basics.module'; import { PlanTreeLeftPanelDirective } from '../../directives/plan-tree-left-panel.directive'; import { PlanTreeRightPanelDirective } from '../../directives/plan-tree-right-panel.directive'; -import { PlanTreePanelSizesDirective } from './plan-tree-panel-sizes.directive'; @Component({ selector: 'step-plan-tree', @@ -51,7 +50,6 @@ import { PlanTreePanelSizesDirective } from './plan-tree-panel-sizes.directive'; ArtefactChildContainerSettingsComponent, AsyncPipe, ], - hostDirectives: [PlanTreePanelSizesDirective], providers: [ { provide: TreeActionsService, @@ -67,8 +65,6 @@ export class PlanTreeComponent implements AfterViewInit, TreeActionsService { readonly _planEditService = inject(PlanEditorService); readonly _planInteractiveSession? = inject(PlanInteractiveSessionService, { optional: true }); - protected readonly _panelSizes = inject(PlanTreePanelSizesDirective, { self: true }); - readonly activeNode: Signal = this._treeState.selectedNode; private readonly leftPanel = contentChild(PlanTreeLeftPanelDirective); @@ -77,6 +73,9 @@ export class PlanTreeComponent implements AfterViewInit, TreeActionsService { protected readonly templateLeftPanel = computed(() => this.leftPanel()?._templateRef); protected readonly templateRightPanel = computed(() => this.rightPanel()?._templateRef); + protected readonly headerLeftPanel = computed(() => this.leftPanel()?.header()); + protected readonly headerRightPanel = computed(() => this.rightPanel()?.header()); + protected readonly sizeTypeLeftPanel = computed(() => { const leftPanel = this.leftPanel(); return leftPanel?.sizeType?.() || 'pixel'; diff --git a/projects/step-core/src/lib/modules/plan-common/components/source-plan-editor/source-plan-editor.component.html b/projects/step-core/src/lib/modules/plan-common/components/source-plan-editor/source-plan-editor.component.html index 72a582c213..65b54f3fe8 100644 --- a/projects/step-core/src/lib/modules/plan-common/components/source-plan-editor/source-plan-editor.component.html +++ b/projects/step-core/src/lib/modules/plan-common/components/source-plan-editor/source-plan-editor.component.html @@ -1,12 +1,12 @@ - + Source
@if (templateControls(); as controls) { -
+
} diff --git a/projects/step-core/src/lib/modules/plan-common/directives/plan-tree-left-panel.directive.ts b/projects/step-core/src/lib/modules/plan-common/directives/plan-tree-left-panel.directive.ts index b5dee27ddc..f0d7a4ec58 100644 --- a/projects/step-core/src/lib/modules/plan-common/directives/plan-tree-left-panel.directive.ts +++ b/projects/step-core/src/lib/modules/plan-common/directives/plan-tree-left-panel.directive.ts @@ -6,5 +6,6 @@ import { SplitAreaSizeType } from '../../split'; }) export class PlanTreeLeftPanelDirective { readonly _templateRef = inject(TemplateRef); - readonly sizeType = input('', { alias: 'stepPlanTreeLeftPanel' }); + readonly header = input.required({ alias: 'stepPlanTreeLeftPanel' }); + readonly sizeType = input('pixel', { alias: 'stepPlanTreeLeftPanelSizeType' }); } diff --git a/projects/step-core/src/lib/modules/plan-common/directives/plan-tree-right-panel.directive.ts b/projects/step-core/src/lib/modules/plan-common/directives/plan-tree-right-panel.directive.ts index ae36cc6c97..46355831a8 100644 --- a/projects/step-core/src/lib/modules/plan-common/directives/plan-tree-right-panel.directive.ts +++ b/projects/step-core/src/lib/modules/plan-common/directives/plan-tree-right-panel.directive.ts @@ -6,5 +6,6 @@ import { SplitAreaSizeType } from '../../split'; }) export class PlanTreeRightPanelDirective { readonly _templateRef = inject(TemplateRef); - readonly sizeType = input('', { alias: 'stepPlanTreeRightPanel' }); + readonly header = input.required({ alias: 'stepPlanTreeRightPanel' }); + readonly sizeType = input('pixel', { alias: 'stepPlanTreeRightPanelSizeType' }); } diff --git a/projects/step-core/src/lib/modules/plan-common/index.ts b/projects/step-core/src/lib/modules/plan-common/index.ts index 1aa5479ba8..df0a035b09 100644 --- a/projects/step-core/src/lib/modules/plan-common/index.ts +++ b/projects/step-core/src/lib/modules/plan-common/index.ts @@ -9,7 +9,6 @@ import { ArtefactDetailsComponent } from './components/artefact-details/artefact import { PlanTreeComponent } from './components/plan-tree/plan-tree.component'; import { SourcePlanEditorComponent } from './components/source-plan-editor/source-plan-editor.component'; import { PlanEditorAttributesDirective } from './directives/plan-editor-attributes.directive'; -import { PlanTreePanelSizesDirective } from './components/plan-tree/plan-tree-panel-sizes.directive'; import { PlanTreeRightPanelDirective } from './directives/plan-tree-right-panel.directive'; export * from './components/plan-create-dialog/plan-create-dialog.component'; @@ -19,7 +18,6 @@ export * from './components/thread-distribution-wizard-dialog/thread-distributio export * from './components/artefact-child-container-settings/artefact-child-container-settings.component'; export * from './components/artefact-details/artefact-details.component'; export * from './components/plan-tree/plan-tree.component'; -export * from './components/plan-tree/plan-tree-panel-sizes.directive'; export * from './components/source-plan-editor/source-plan-editor.component'; export * from './directives/plan-editor-attributes.directive'; @@ -31,7 +29,6 @@ export * from './injectables/plan-by-id-cache.service'; export * from './injectables/plan-dialogs.service'; export * from './injectables/plan-editor.service'; export * from './injectables/plan-context-api.service'; -export * from './injectables/plan-editor-persistence-state.service'; export * from './injectables/plan-interactive-session.service'; export * from './injectables/plan-open.service'; export * from './injectables/plan-context-initializer.service'; @@ -61,6 +58,5 @@ export const PLAN_COMMON_EXPORTS = [ PlanTreeComponent, SourcePlanEditorComponent, PlanEditorAttributesDirective, - PlanTreePanelSizesDirective, PlanTreeRightPanelDirective, ]; diff --git a/projects/step-core/src/lib/modules/plan-common/injectables/plan-editor-persistence-state.service.ts b/projects/step-core/src/lib/modules/plan-common/injectables/plan-editor-persistence-state.service.ts deleted file mode 100644 index f272eaa40c..0000000000 --- a/projects/step-core/src/lib/modules/plan-common/injectables/plan-editor-persistence-state.service.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { inject, Inject, Injectable } from '@angular/core'; -import { filter, pairwise } from 'rxjs'; -import { SCREEN_WIDTH, LOCAL_STORAGE, StorageProxy } from '../../basics/step-basics.module'; - -@Injectable({ - providedIn: 'root', -}) -export class PlanEditorPersistenceStateService extends StorageProxy { - private _screenWidth$ = inject(SCREEN_WIDTH); - - constructor(@Inject(LOCAL_STORAGE) _storage: Storage) { - super(_storage, 'PLAN_EDITOR_STATE'); - this.setupTokensCleanupOnScreenChange(); - } - - getPanelSize(panelKey: string): number | undefined { - const size = parseInt(this.getItem(`PANEL_${panelKey}`) ?? ''); - return !isNaN(size) ? size : undefined; - } - - setPanelSize(panelKey: string, value: number): void { - this.setItem(`PANEL_${panelKey}`, value.toString()); - } - - private setupTokensCleanupOnScreenChange(): void { - this._screenWidth$ - .pipe( - pairwise(), - filter(([previousWidth, currentWidth]) => previousWidth !== currentWidth), - ) - .subscribe(() => this.clearTokens()); - } -} diff --git a/projects/step-core/src/lib/modules/split/components/split-area/split-area.component.ts b/projects/step-core/src/lib/modules/split/components/split-area/split-area.component.ts index ba7e33e835..5f701c93fe 100644 --- a/projects/step-core/src/lib/modules/split/components/split-area/split-area.component.ts +++ b/projects/step-core/src/lib/modules/split/components/split-area/split-area.component.ts @@ -1,17 +1,20 @@ import { AfterViewInit, Component, + computed, effect, ElementRef, inject, input, linkedSignal, OnDestroy, - output, signal, + untracked, } from '@angular/core'; import { debounceTime, map, Subject, switchMap } from 'rxjs'; import { takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop'; +import { SplitAreaSizePersistenceService } from '../../injectables/split-area-size-persistence.service'; +import { SplitComponent } from '../split/split.component'; export type SplitAreaSizeType = 'pixel' | 'percent' | 'flex'; @@ -25,19 +28,34 @@ export type SplitAreaSizeType = 'pixel' | 'percent' | 'flex'; }) export class SplitAreaComponent implements AfterViewInit, OnDestroy { private _elementRef = inject>(ElementRef); + private _splitComponent = inject(SplitComponent); + private _splitAreaSizePersistenceService = inject(SplitAreaSizePersistenceService); + private sizeUpdateInternal$ = new Subject(); private isViewInitialized = signal(false); readonly padding = input(undefined); readonly sizeType = input('pixel'); - readonly size = input(undefined); + readonly sizePrefix = input.required(); - private sizeInternal = linkedSignal(() => this.size()); + private appliedPrefix = computed(() => { + const splitPrefix = this._splitComponent.sizePrefix(); + const sizePrefix = this.sizePrefix(); + return `${splitPrefix}_${sizePrefix}`; + }); - readonly sizeUpdateDebounce = input(300); + private splitAreaSizeController = computed(() => { + const appliedPrefix = this.appliedPrefix(); + return this._splitAreaSizePersistenceService.createSplitAreaSizeController(appliedPrefix); + }); - readonly sizeChange = output(); + private sizeInternal = linkedSignal(() => { + const sizeController = this.splitAreaSizeController(); + return sizeController.getSize(); + }); + + readonly sizeUpdateDebounce = input(300); private effectUpdateSize = effect(() => { const isViewInitialized = this.isViewInitialized(); @@ -78,7 +96,10 @@ export class SplitAreaComponent implements AfterViewInit, OnDestroy { map(() => this.sizeInternal()), takeUntilDestroyed(), ) - .subscribe((size) => this.sizeChange.emit(size)); + .subscribe((size) => { + const sizeController = untracked(() => this.splitAreaSizeController()); + sizeController.setSize(size ?? 0); + }); ngAfterViewInit(): void { this.isViewInitialized.set(true); diff --git a/projects/step-core/src/lib/modules/split/components/split/split.component.ts b/projects/step-core/src/lib/modules/split/components/split/split.component.ts index 8972145201..b6aac41873 100644 --- a/projects/step-core/src/lib/modules/split/components/split/split.component.ts +++ b/projects/step-core/src/lib/modules/split/components/split/split.component.ts @@ -1,6 +1,7 @@ -import { Component, contentChildren } from '@angular/core'; +import { Component, contentChildren, input } from '@angular/core'; import { SplitAreaComponent } from '../split-area/split-area.component'; import { SplitGutterComponent } from '../split-gutter/split-gutter.component'; +import { v4 } from 'uuid'; @Component({ selector: 'step-split', @@ -10,4 +11,6 @@ import { SplitGutterComponent } from '../split-gutter/split-gutter.component'; export class SplitComponent { readonly areas = contentChildren(SplitAreaComponent); readonly gutters = contentChildren(SplitGutterComponent); + + readonly sizePrefix = input(`SPLIT_${v4()}`); } diff --git a/projects/step-core/src/lib/modules/split/directives/split-section.directive.ts b/projects/step-core/src/lib/modules/split/directives/split-section.directive.ts new file mode 100644 index 0000000000..f3e7e414b0 --- /dev/null +++ b/projects/step-core/src/lib/modules/split/directives/split-section.directive.ts @@ -0,0 +1,11 @@ +import { Directive, inject, input, TemplateRef } from '@angular/core'; +import { SplitAreaSizeType } from '@exense/step-core'; + +@Directive({ + selector: '[stepSplitSection]', +}) +export class SplitSectionDirective { + readonly _templateRef = inject(TemplateRef); + readonly header = input.required({ alias: 'stepSplitSection' }); + readonly sizeType = input('pixel', { alias: 'stepSplitSectionSizeType' }); +} diff --git a/projects/step-core/src/lib/modules/split/index.ts b/projects/step-core/src/lib/modules/split/index.ts index 0825105884..3850df2c49 100644 --- a/projects/step-core/src/lib/modules/split/index.ts +++ b/projects/step-core/src/lib/modules/split/index.ts @@ -1,9 +1,11 @@ import { SplitComponent } from './components/split/split.component'; import { SplitAreaComponent } from './components/split-area/split-area.component'; import { SplitGutterComponent } from './components/split-gutter/split-gutter.component'; +import { SplitSectionDirective } from './directives/split-section.directive'; export * from './components/split/split.component'; export * from './components/split-area/split-area.component'; export * from './components/split-gutter/split-gutter.component'; +export * from './directives/split-section.directive'; -export const SPLIT_EXPORTS = [SplitComponent, SplitAreaComponent, SplitGutterComponent]; +export const SPLIT_EXPORTS = [SplitComponent, SplitAreaComponent, SplitGutterComponent, SplitSectionDirective]; diff --git a/projects/step-core/src/lib/modules/split/injectables/split-area-size-persistence.service.ts b/projects/step-core/src/lib/modules/split/injectables/split-area-size-persistence.service.ts new file mode 100644 index 0000000000..f62b21aba4 --- /dev/null +++ b/projects/step-core/src/lib/modules/split/injectables/split-area-size-persistence.service.ts @@ -0,0 +1,47 @@ +import { Inject, inject, Injectable } from '@angular/core'; +import { filter, pairwise } from 'rxjs'; +import { LOCAL_STORAGE, SCREEN_WIDTH, StorageProxy } from '../../basics/step-basics.module'; + +export interface SplitAreaSizeController { + getSize(): number | undefined; + setSize(value: number): void; +} + +@Injectable({ + providedIn: 'root', +}) +export class SplitAreaSizePersistenceService extends StorageProxy { + private _screenWidth$ = inject(SCREEN_WIDTH); + + constructor(@Inject(LOCAL_STORAGE) _storage: Storage) { + super(_storage, 'SPLIT_AREA_SIZE_STATE'); + this.setupTokensCleanupOnScreenChange(); + } + + createSplitAreaSizeController(key: string): SplitAreaSizeController { + const getSize = () => this.getPanelSize(`${key}_PANEL`); + const setSize = (value: number) => this.setPanelSize(`${key}_PANEL`, value); + return { + getSize, + setSize, + }; + } + + private getPanelSize(panelKey: string): number | undefined { + const size = parseInt(this.getItem(panelKey) ?? ''); + return !isNaN(size) ? size : undefined; + } + + private setPanelSize(panelKey: string, value: number): void { + this.setItem(panelKey, value.toString()); + } + + private setupTokensCleanupOnScreenChange(): void { + this._screenWidth$ + .pipe( + pairwise(), + filter(([previousWidth, currentWidth]) => previousWidth !== currentWidth), + ) + .subscribe(() => this.clearTokens()); + } +} diff --git a/projects/step-frontend/src/lib/modules/plan-editor/components/plan-common-tree-editor-form/plan-common-tree-editor-form.component.html b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-common-tree-editor-form/plan-common-tree-editor-form.component.html index 81211f1134..fec22522b9 100644 --- a/projects/step-frontend/src/lib/modules/plan-editor/components/plan-common-tree-editor-form/plan-common-tree-editor-form.component.html +++ b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-common-tree-editor-form/plan-common-tree-editor-form.component.html @@ -1,6 +1,6 @@ @if (controlTemplate(); as ctrlTemplate) { -
+
} diff --git a/projects/step-frontend/src/lib/modules/plan-editor/components/plan-editor-base/plan-editor-base.component.html b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-editor-base/plan-editor-base.component.html index 22e203b510..dbc0bdcdd7 100644 --- a/projects/step-frontend/src/lib/modules/plan-editor/components/plan-editor-base/plan-editor-base.component.html +++ b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-editor-base/plan-editor-base.component.html @@ -49,25 +49,5 @@ } -
diff --git a/projects/step-frontend/src/lib/modules/plan-editor/components/plan-editor-base/plan-editor-base.component.ts b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-editor-base/plan-editor-base.component.ts index 6b7bb72fb5..f89f88261b 100644 --- a/projects/step-frontend/src/lib/modules/plan-editor/components/plan-editor-base/plan-editor-base.component.ts +++ b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-editor-base/plan-editor-base.component.ts @@ -36,7 +36,6 @@ import { PlanOpenService, PlanContextInitializerService, PlanContextApiService, - PlanEditorPersistenceStateService, AugmentedPlansService, CommonEntitiesUrlsService, PlanContext, @@ -117,7 +116,6 @@ export class PlanEditorBaseComponent public _planEditService = inject(PlanEditorService); private _activatedRoute = inject(ActivatedRoute); private _planOpen = inject(PlanOpenService); - private _planEditorPersistenceState = inject(PlanEditorPersistenceStateService); private _matDialog = inject(MatDialog); private _router = inject(Router); private _commonEntitiesUrls = inject(CommonEntitiesUrlsService); @@ -159,9 +157,6 @@ export class PlanEditorBaseComponent protected planClass?: string; private planControls = viewChild('planControls', { read: PlanControlsComponent }); - protected planSize = this._planEditorPersistenceState.getPanelSize(PLAN_SIZE); - protected planControlsSize = this._planEditorPersistenceState.getPanelSize(PLAN_CONTROLS_SIZE); - private effectCheckAccessToPlanTypeControl = effect(() => { const planEditorType = this._planEditService.plan(); untracked(() => { @@ -191,26 +186,6 @@ export class PlanEditorBaseComponent } } - handlePlanSizeChange(size: number): void { - this._planEditorPersistenceState.setPanelSize(PLAN_SIZE, size); - } - - handlePlanControlsChange(size: number): void { - this._planEditorPersistenceState.setPanelSize(PLAN_CONTROLS_SIZE, size); - } - - addControl(artefactTypeId: string): void { - this._planEditService.addControl(artefactTypeId); - } - - addKeywords(keywordIds: string[]): void { - this._planEditService.addKeywords(keywordIds); - } - - addPlans(planIds: string[]): void { - this._planEditService.addPlans(planIds); - } - exportPlan(): void { if (!this.currentPlanId) { return; From 2fbfe86c4a8414b5924ad921327f07929bbfd7dc Mon Sep 17 00:00:00 2001 From: dvladir Date: Thu, 20 Nov 2025 14:30:22 +0300 Subject: [PATCH 6/9] SED-4373 Define split content with sections --- .../plan-tree/plan-tree.component.html | 21 +++++++------------ .../components/split/split.component.html | 9 +++++++- .../split/components/split/split.component.ts | 10 ++++++--- .../directives/split-section.directive.ts | 1 + .../step-core/src/lib/modules/split/index.ts | 4 +--- 5 files changed, 25 insertions(+), 20 deletions(-) diff --git a/projects/step-core/src/lib/modules/plan-common/components/plan-tree/plan-tree.component.html b/projects/step-core/src/lib/modules/plan-common/components/plan-tree/plan-tree.component.html index 4eff8e2755..26cbb45cc5 100644 --- a/projects/step-core/src/lib/modules/plan-common/components/plan-tree/plan-tree.component.html +++ b/projects/step-core/src/lib/modules/plan-common/components/plan-tree/plan-tree.component.html @@ -1,12 +1,11 @@ @if (templateLeftPanel(); as left) { - + - - + } - + - - - - - + + @if (activeNode(); as activeNode) { @if (activeNode.originalArtefact; as selectedArtefact) { @@ -77,12 +73,11 @@ /> } } - + @if (templateRightPanel(); as right) { - - + - + } diff --git a/projects/step-core/src/lib/modules/split/components/split/split.component.html b/projects/step-core/src/lib/modules/split/components/split/split.component.html index 40b3726403..a56da3d41c 100644 --- a/projects/step-core/src/lib/modules/split/components/split/split.component.html +++ b/projects/step-core/src/lib/modules/split/components/split/split.component.html @@ -1 +1,8 @@ - +@for (section of sections(); track section.header(); let isLast = $last) { + + + + @if (!isLast) { + + } +} diff --git a/projects/step-core/src/lib/modules/split/components/split/split.component.ts b/projects/step-core/src/lib/modules/split/components/split/split.component.ts index b6aac41873..91a5b94fed 100644 --- a/projects/step-core/src/lib/modules/split/components/split/split.component.ts +++ b/projects/step-core/src/lib/modules/split/components/split/split.component.ts @@ -1,16 +1,20 @@ -import { Component, contentChildren, input } from '@angular/core'; +import { Component, contentChildren, input, viewChildren } from '@angular/core'; import { SplitAreaComponent } from '../split-area/split-area.component'; import { SplitGutterComponent } from '../split-gutter/split-gutter.component'; import { v4 } from 'uuid'; +import { SplitSectionDirective } from '../../directives/split-section.directive'; +import { NgTemplateOutlet } from '@angular/common'; @Component({ selector: 'step-split', templateUrl: './split.component.html', styleUrls: ['./split.component.scss'], + imports: [SplitAreaComponent, NgTemplateOutlet, SplitGutterComponent], }) export class SplitComponent { - readonly areas = contentChildren(SplitAreaComponent); - readonly gutters = contentChildren(SplitGutterComponent); + readonly sections = contentChildren(SplitSectionDirective); + readonly areas = viewChildren(SplitAreaComponent); + readonly gutters = viewChildren(SplitGutterComponent); readonly sizePrefix = input(`SPLIT_${v4()}`); } diff --git a/projects/step-core/src/lib/modules/split/directives/split-section.directive.ts b/projects/step-core/src/lib/modules/split/directives/split-section.directive.ts index f3e7e414b0..b455d7ae44 100644 --- a/projects/step-core/src/lib/modules/split/directives/split-section.directive.ts +++ b/projects/step-core/src/lib/modules/split/directives/split-section.directive.ts @@ -8,4 +8,5 @@ export class SplitSectionDirective { readonly _templateRef = inject(TemplateRef); readonly header = input.required({ alias: 'stepSplitSection' }); readonly sizeType = input('pixel', { alias: 'stepSplitSectionSizeType' }); + readonly padding = input(undefined, { alias: 'stepSplitSectionPadding' }); } diff --git a/projects/step-core/src/lib/modules/split/index.ts b/projects/step-core/src/lib/modules/split/index.ts index 3850df2c49..c61220b3cf 100644 --- a/projects/step-core/src/lib/modules/split/index.ts +++ b/projects/step-core/src/lib/modules/split/index.ts @@ -1,6 +1,4 @@ import { SplitComponent } from './components/split/split.component'; -import { SplitAreaComponent } from './components/split-area/split-area.component'; -import { SplitGutterComponent } from './components/split-gutter/split-gutter.component'; import { SplitSectionDirective } from './directives/split-section.directive'; export * from './components/split/split.component'; @@ -8,4 +6,4 @@ export * from './components/split-area/split-area.component'; export * from './components/split-gutter/split-gutter.component'; export * from './directives/split-section.directive'; -export const SPLIT_EXPORTS = [SplitComponent, SplitAreaComponent, SplitGutterComponent, SplitSectionDirective]; +export const SPLIT_EXPORTS = [SplitComponent, SplitSectionDirective]; From ef2d1b9f5046bb3c0671107d9af51e32926c7b03 Mon Sep 17 00:00:00 2001 From: dvladir Date: Thu, 20 Nov 2025 14:38:59 +0300 Subject: [PATCH 7/9] SED-4373 Rename split-area to split-resizable-area --- .../split-resizable-area.component.html} | 0 .../split-resizable-area.component.scss} | 0 .../split-resizable-area.component.ts} | 8 ++++---- .../modules/split/components/split/split.component.html | 8 ++++++-- .../lib/modules/split/components/split/split.component.ts | 6 +++--- projects/step-core/src/lib/modules/split/index.ts | 2 +- 6 files changed, 14 insertions(+), 10 deletions(-) rename projects/step-core/src/lib/modules/split/components/{split-area/split-area.component.html => split-resizable-area/split-resizable-area.component.html} (100%) rename projects/step-core/src/lib/modules/split/components/{split-area/split-area.component.scss => split-resizable-area/split-resizable-area.component.scss} (100%) rename projects/step-core/src/lib/modules/split/components/{split-area/split-area.component.ts => split-resizable-area/split-resizable-area.component.ts} (94%) diff --git a/projects/step-core/src/lib/modules/split/components/split-area/split-area.component.html b/projects/step-core/src/lib/modules/split/components/split-resizable-area/split-resizable-area.component.html similarity index 100% rename from projects/step-core/src/lib/modules/split/components/split-area/split-area.component.html rename to projects/step-core/src/lib/modules/split/components/split-resizable-area/split-resizable-area.component.html diff --git a/projects/step-core/src/lib/modules/split/components/split-area/split-area.component.scss b/projects/step-core/src/lib/modules/split/components/split-resizable-area/split-resizable-area.component.scss similarity index 100% rename from projects/step-core/src/lib/modules/split/components/split-area/split-area.component.scss rename to projects/step-core/src/lib/modules/split/components/split-resizable-area/split-resizable-area.component.scss diff --git a/projects/step-core/src/lib/modules/split/components/split-area/split-area.component.ts b/projects/step-core/src/lib/modules/split/components/split-resizable-area/split-resizable-area.component.ts similarity index 94% rename from projects/step-core/src/lib/modules/split/components/split-area/split-area.component.ts rename to projects/step-core/src/lib/modules/split/components/split-resizable-area/split-resizable-area.component.ts index 5f701c93fe..3ea5468c93 100644 --- a/projects/step-core/src/lib/modules/split/components/split-area/split-area.component.ts +++ b/projects/step-core/src/lib/modules/split/components/split-resizable-area/split-resizable-area.component.ts @@ -19,14 +19,14 @@ import { SplitComponent } from '../split/split.component'; export type SplitAreaSizeType = 'pixel' | 'percent' | 'flex'; @Component({ - selector: 'step-split-area', - templateUrl: './split-area.component.html', - styleUrls: ['./split-area.component.scss'], + selector: 'step-split-resizable-area', + templateUrl: './split-resizable-area.component.html', + styleUrls: ['./split-resizable-area.component.scss'], host: { '(focusin)': 'handleFocusIn()', }, }) -export class SplitAreaComponent implements AfterViewInit, OnDestroy { +export class SplitResizableAreaComponent implements AfterViewInit, OnDestroy { private _elementRef = inject>(ElementRef); private _splitComponent = inject(SplitComponent); private _splitAreaSizePersistenceService = inject(SplitAreaSizePersistenceService); diff --git a/projects/step-core/src/lib/modules/split/components/split/split.component.html b/projects/step-core/src/lib/modules/split/components/split/split.component.html index a56da3d41c..de6355c759 100644 --- a/projects/step-core/src/lib/modules/split/components/split/split.component.html +++ b/projects/step-core/src/lib/modules/split/components/split/split.component.html @@ -1,7 +1,11 @@ @for (section of sections(); track section.header(); let isLast = $last) { - + - + @if (!isLast) { } diff --git a/projects/step-core/src/lib/modules/split/components/split/split.component.ts b/projects/step-core/src/lib/modules/split/components/split/split.component.ts index 91a5b94fed..52bc20eb4b 100644 --- a/projects/step-core/src/lib/modules/split/components/split/split.component.ts +++ b/projects/step-core/src/lib/modules/split/components/split/split.component.ts @@ -1,5 +1,5 @@ import { Component, contentChildren, input, viewChildren } from '@angular/core'; -import { SplitAreaComponent } from '../split-area/split-area.component'; +import { SplitResizableAreaComponent } from '../split-resizable-area/split-resizable-area.component'; import { SplitGutterComponent } from '../split-gutter/split-gutter.component'; import { v4 } from 'uuid'; import { SplitSectionDirective } from '../../directives/split-section.directive'; @@ -9,11 +9,11 @@ import { NgTemplateOutlet } from '@angular/common'; selector: 'step-split', templateUrl: './split.component.html', styleUrls: ['./split.component.scss'], - imports: [SplitAreaComponent, NgTemplateOutlet, SplitGutterComponent], + imports: [SplitResizableAreaComponent, NgTemplateOutlet, SplitGutterComponent], }) export class SplitComponent { readonly sections = contentChildren(SplitSectionDirective); - readonly areas = viewChildren(SplitAreaComponent); + readonly areas = viewChildren(SplitResizableAreaComponent); readonly gutters = viewChildren(SplitGutterComponent); readonly sizePrefix = input(`SPLIT_${v4()}`); diff --git a/projects/step-core/src/lib/modules/split/index.ts b/projects/step-core/src/lib/modules/split/index.ts index c61220b3cf..bf1139b9fa 100644 --- a/projects/step-core/src/lib/modules/split/index.ts +++ b/projects/step-core/src/lib/modules/split/index.ts @@ -2,7 +2,7 @@ import { SplitComponent } from './components/split/split.component'; import { SplitSectionDirective } from './directives/split-section.directive'; export * from './components/split/split.component'; -export * from './components/split-area/split-area.component'; +export * from './components/split-resizable-area/split-resizable-area.component'; export * from './components/split-gutter/split-gutter.component'; export * from './directives/split-section.directive'; From 94a7315f72f9efd9af98580a8c0bf12395d8a9f6 Mon Sep 17 00:00:00 2001 From: dvladir Date: Thu, 20 Nov 2025 17:25:59 +0300 Subject: [PATCH 8/9] SED-4373 Mobile support --- .../plan-tree/plan-tree.component.html | 6 +- .../plan-tree/plan-tree.component.ts | 4 -- .../split-foldable-area.component.html | 8 +++ .../split-foldable-area.component.scss | 25 ++++++++ .../split-foldable-area.component.ts | 27 ++++++++ .../split-resizable-area.component.ts | 8 +-- .../components/split/split.component.html | 34 +++++++--- .../components/split/split.component.scss | 23 ++++++- .../split/components/split/split.component.ts | 63 +++++++++++++++++-- .../directives/split-section.directive.ts | 2 +- ...split-foldable-area-persistence.service.ts | 11 ++++ ...lit-resizable-area-persistence.service.ts} | 4 +- 12 files changed, 186 insertions(+), 29 deletions(-) create mode 100644 projects/step-core/src/lib/modules/split/components/split-foldable-area/split-foldable-area.component.html create mode 100644 projects/step-core/src/lib/modules/split/components/split-foldable-area/split-foldable-area.component.scss create mode 100644 projects/step-core/src/lib/modules/split/components/split-foldable-area/split-foldable-area.component.ts create mode 100644 projects/step-core/src/lib/modules/split/injectables/split-foldable-area-persistence.service.ts rename projects/step-core/src/lib/modules/split/injectables/{split-area-size-persistence.service.ts => split-resizable-area-persistence.service.ts} (91%) diff --git a/projects/step-core/src/lib/modules/plan-common/components/plan-tree/plan-tree.component.html b/projects/step-core/src/lib/modules/plan-common/components/plan-tree/plan-tree.component.html index 26cbb45cc5..91e1de305a 100644 --- a/projects/step-core/src/lib/modules/plan-common/components/plan-tree/plan-tree.component.html +++ b/projects/step-core/src/lib/modules/plan-common/components/plan-tree/plan-tree.component.html @@ -1,11 +1,11 @@ - + @if (templateLeftPanel(); as left) { } - + - + @if (activeNode(); as activeNode) { @if (activeNode.originalArtefact; as selectedArtefact) { diff --git a/projects/step-core/src/lib/modules/plan-common/components/plan-tree/plan-tree.component.ts b/projects/step-core/src/lib/modules/plan-common/components/plan-tree/plan-tree.component.ts index 9d80fa72a5..b975b0bad5 100644 --- a/projects/step-core/src/lib/modules/plan-common/components/plan-tree/plan-tree.component.ts +++ b/projects/step-core/src/lib/modules/plan-common/components/plan-tree/plan-tree.component.ts @@ -86,16 +86,12 @@ export class PlanTreeComponent implements AfterViewInit, TreeActionsService { return rightPanel?.sizeType?.() || 'pixel'; }); - /** @Output() **/ readonly externalObjectDrop = output(); @Input() isReadonly: boolean = false; - @ViewChild('area') splitAreaElementRef?: ElementRef; - @ViewChild(TreeComponent) tree?: TreeComponent; - /** @ViewChild **/ private dragData = viewChild(DragDataService); private actions: TreeAction[] = [ diff --git a/projects/step-core/src/lib/modules/split/components/split-foldable-area/split-foldable-area.component.html b/projects/step-core/src/lib/modules/split/components/split-foldable-area/split-foldable-area.component.html new file mode 100644 index 0000000000..25b1f3ea38 --- /dev/null +++ b/projects/step-core/src/lib/modules/split/components/split-foldable-area/split-foldable-area.component.html @@ -0,0 +1,8 @@ +@if (isFolded()) { +
+ + {{ header() }} +
+} @else { + +} diff --git a/projects/step-core/src/lib/modules/split/components/split-foldable-area/split-foldable-area.component.scss b/projects/step-core/src/lib/modules/split/components/split-foldable-area/split-foldable-area.component.scss new file mode 100644 index 0000000000..a72eb78783 --- /dev/null +++ b/projects/step-core/src/lib/modules/split/components/split-foldable-area/split-foldable-area.component.scss @@ -0,0 +1,25 @@ +@use 'projects/step-core/styles/core-variables' as var; + +step-split-foldable-area { + .folded { + display: flex; + flex-direction: column; + gap: 1rem; + justify-content: center; + align-items: center; + height: 100%; + min-height: calc(100vh - 15rem); + min-width: 5rem; + cursor: pointer; + &:hover { + background: var.$gray-300; + } + span { + writing-mode: sideways-lr; + user-select: none; + } + } + &.active { + padding: 0 1rem; + } +} diff --git a/projects/step-core/src/lib/modules/split/components/split-foldable-area/split-foldable-area.component.ts b/projects/step-core/src/lib/modules/split/components/split-foldable-area/split-foldable-area.component.ts new file mode 100644 index 0000000000..661d950255 --- /dev/null +++ b/projects/step-core/src/lib/modules/split/components/split-foldable-area/split-foldable-area.component.ts @@ -0,0 +1,27 @@ +import { Component, computed, input, output, ViewEncapsulation } from '@angular/core'; +import { StepIconsModule } from '../../../step-icons/step-icons.module'; + +@Component({ + selector: 'step-split-foldable-area', + imports: [StepIconsModule], + host: { + '[class.active]': 'isActive()', + }, + templateUrl: './split-foldable-area.component.html', + styleUrl: './split-foldable-area.component.scss', + encapsulation: ViewEncapsulation.None, +}) +export class SplitFoldableAreaComponent { + readonly header = input.required(); + readonly activeSectionHeader = input(); + protected readonly isFolded = computed(() => { + const header = this.header(); + const active = this.activeSectionHeader(); + return header !== active; + }); + protected readonly isActive = computed(() => { + const isFolded = this.isFolded(); + return !isFolded; + }); + readonly activate = output(); +} diff --git a/projects/step-core/src/lib/modules/split/components/split-resizable-area/split-resizable-area.component.ts b/projects/step-core/src/lib/modules/split/components/split-resizable-area/split-resizable-area.component.ts index 3ea5468c93..e3a6849f78 100644 --- a/projects/step-core/src/lib/modules/split/components/split-resizable-area/split-resizable-area.component.ts +++ b/projects/step-core/src/lib/modules/split/components/split-resizable-area/split-resizable-area.component.ts @@ -13,7 +13,7 @@ import { } from '@angular/core'; import { debounceTime, map, Subject, switchMap } from 'rxjs'; import { takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop'; -import { SplitAreaSizePersistenceService } from '../../injectables/split-area-size-persistence.service'; +import { SplitResizableAreaPersistenceService } from '../../injectables/split-resizable-area-persistence.service'; import { SplitComponent } from '../split/split.component'; export type SplitAreaSizeType = 'pixel' | 'percent' | 'flex'; @@ -29,7 +29,7 @@ export type SplitAreaSizeType = 'pixel' | 'percent' | 'flex'; export class SplitResizableAreaComponent implements AfterViewInit, OnDestroy { private _elementRef = inject>(ElementRef); private _splitComponent = inject(SplitComponent); - private _splitAreaSizePersistenceService = inject(SplitAreaSizePersistenceService); + private _splitResizableAreaPersistenceService = inject(SplitResizableAreaPersistenceService); private sizeUpdateInternal$ = new Subject(); @@ -40,14 +40,14 @@ export class SplitResizableAreaComponent implements AfterViewInit, OnDestroy { readonly sizePrefix = input.required(); private appliedPrefix = computed(() => { - const splitPrefix = this._splitComponent.sizePrefix(); + const splitPrefix = this._splitComponent.persistencePrefix(); const sizePrefix = this.sizePrefix(); return `${splitPrefix}_${sizePrefix}`; }); private splitAreaSizeController = computed(() => { const appliedPrefix = this.appliedPrefix(); - return this._splitAreaSizePersistenceService.createSplitAreaSizeController(appliedPrefix); + return this._splitResizableAreaPersistenceService.createSplitAreaSizeController(appliedPrefix); }); private sizeInternal = linkedSignal(() => { diff --git a/projects/step-core/src/lib/modules/split/components/split/split.component.html b/projects/step-core/src/lib/modules/split/components/split/split.component.html index de6355c759..ecc4fb936d 100644 --- a/projects/step-core/src/lib/modules/split/components/split/split.component.html +++ b/projects/step-core/src/lib/modules/split/components/split/split.component.html @@ -1,12 +1,26 @@ -@for (section of sections(); track section.header(); let isLast = $last) { - - - - @if (!isLast) { - +@if (isFoldableView()) { +
+ @for (section of sections(); track section.header()) { + + + + } +
+} @else { + @for (section of sections(); track section.header(); let isLast = $last) { + + + + @if (!isLast) { + + } } } diff --git a/projects/step-core/src/lib/modules/split/components/split/split.component.scss b/projects/step-core/src/lib/modules/split/components/split/split.component.scss index f4f7d61275..8aa62191fc 100644 --- a/projects/step-core/src/lib/modules/split/components/split/split.component.scss +++ b/projects/step-core/src/lib/modules/split/components/split/split.component.scss @@ -1,4 +1,25 @@ -:host { +@use 'projects/step-core/styles/core-variables' as var; + +step-split { flex: 1; display: flex; + + .foldable-items { + display: flex; + gap: 1.2rem; + justify-content: space-between; + width: 100%; + + step-split-foldable-area { + &:not(:first-child) { + border-left: 0.1rem solid var.$gray-300; + } + &:not(:last-child) { + border-right: 0.1rem solid var.$gray-300; + } + &.active { + flex: 1; + } + } + } } diff --git a/projects/step-core/src/lib/modules/split/components/split/split.component.ts b/projects/step-core/src/lib/modules/split/components/split/split.component.ts index 52bc20eb4b..a5b4c27977 100644 --- a/projects/step-core/src/lib/modules/split/components/split/split.component.ts +++ b/projects/step-core/src/lib/modules/split/components/split/split.component.ts @@ -1,20 +1,75 @@ -import { Component, contentChildren, input, viewChildren } from '@angular/core'; +import { + AfterContentInit, + Component, + computed, + contentChildren, + inject, + input, + linkedSignal, + viewChildren, + ViewEncapsulation, +} from '@angular/core'; import { SplitResizableAreaComponent } from '../split-resizable-area/split-resizable-area.component'; import { SplitGutterComponent } from '../split-gutter/split-gutter.component'; import { v4 } from 'uuid'; import { SplitSectionDirective } from '../../directives/split-section.directive'; +import { SplitFoldableAreaPersistenceService } from '../../injectables/split-foldable-area-persistence.service'; +import { SplitFoldableAreaComponent } from '../split-foldable-area/split-foldable-area.component'; import { NgTemplateOutlet } from '@angular/common'; +import { SCREEN_WIDTH } from '../../../basics/types/screen-width.token'; +import { toSignal } from '@angular/core/rxjs-interop'; @Component({ selector: 'step-split', templateUrl: './split.component.html', styleUrls: ['./split.component.scss'], - imports: [SplitResizableAreaComponent, NgTemplateOutlet, SplitGutterComponent], + imports: [SplitResizableAreaComponent, SplitFoldableAreaComponent, NgTemplateOutlet, SplitGutterComponent], + encapsulation: ViewEncapsulation.None, }) -export class SplitComponent { +export class SplitComponent implements AfterContentInit { + private _screenWidth = inject(SCREEN_WIDTH); + private _splitFoldableAreaPersistenceService = inject(SplitFoldableAreaPersistenceService); + readonly sections = contentChildren(SplitSectionDirective); readonly areas = viewChildren(SplitResizableAreaComponent); readonly gutters = viewChildren(SplitGutterComponent); - readonly sizePrefix = input(`SPLIT_${v4()}`); + readonly persistencePrefix = input(`SPLIT_${v4()}`); + readonly foldableViewScreenWidthThreshold = input(800); + + private screenWidth = toSignal(this._screenWidth); + + protected isFoldableView = computed(() => { + const screenWidth = this.screenWidth(); + const threshold = this.foldableViewScreenWidthThreshold(); + if (!screenWidth) { + return false; + } + return screenWidth <= threshold; + }); + + protected activeFoldableArea = linkedSignal(() => { + const prefix = this.persistencePrefix(); + return this._splitFoldableAreaPersistenceService.getItem(prefix) ?? ''; + }); + + protected activateFoldableArea(areaHeader: string) { + this.activeFoldableArea.set(areaHeader); + const prefix = this.persistencePrefix(); + this._splitFoldableAreaPersistenceService.setItem(prefix, areaHeader); + } + + ngAfterContentInit(): void { + this.initializeFirstFoldableItem(); + } + + private initializeFirstFoldableItem(): void { + const prefix = this.persistencePrefix(); + if (!this._splitFoldableAreaPersistenceService.getItem(prefix)) { + const firstSectionHeader = this.sections()?.[0]?.header?.(); + if (firstSectionHeader) { + this._splitFoldableAreaPersistenceService.setItem(prefix, firstSectionHeader); + } + } + } } diff --git a/projects/step-core/src/lib/modules/split/directives/split-section.directive.ts b/projects/step-core/src/lib/modules/split/directives/split-section.directive.ts index b455d7ae44..80fa71677a 100644 --- a/projects/step-core/src/lib/modules/split/directives/split-section.directive.ts +++ b/projects/step-core/src/lib/modules/split/directives/split-section.directive.ts @@ -1,5 +1,5 @@ import { Directive, inject, input, TemplateRef } from '@angular/core'; -import { SplitAreaSizeType } from '@exense/step-core'; +import { SplitAreaSizeType } from '../components/split-resizable-area/split-resizable-area.component'; @Directive({ selector: '[stepSplitSection]', diff --git a/projects/step-core/src/lib/modules/split/injectables/split-foldable-area-persistence.service.ts b/projects/step-core/src/lib/modules/split/injectables/split-foldable-area-persistence.service.ts new file mode 100644 index 0000000000..e2b8fb7bdb --- /dev/null +++ b/projects/step-core/src/lib/modules/split/injectables/split-foldable-area-persistence.service.ts @@ -0,0 +1,11 @@ +import { Inject, Injectable } from '@angular/core'; +import { LOCAL_STORAGE, StorageProxy } from '../../basics/step-basics.module'; + +@Injectable({ + providedIn: 'root', +}) +export class SplitFoldableAreaPersistenceService extends StorageProxy { + constructor(@Inject(LOCAL_STORAGE) _storage: Storage) { + super(_storage, 'SPLIT_FOLDABLE_AREA_STATE'); + } +} diff --git a/projects/step-core/src/lib/modules/split/injectables/split-area-size-persistence.service.ts b/projects/step-core/src/lib/modules/split/injectables/split-resizable-area-persistence.service.ts similarity index 91% rename from projects/step-core/src/lib/modules/split/injectables/split-area-size-persistence.service.ts rename to projects/step-core/src/lib/modules/split/injectables/split-resizable-area-persistence.service.ts index f62b21aba4..b7f794e39c 100644 --- a/projects/step-core/src/lib/modules/split/injectables/split-area-size-persistence.service.ts +++ b/projects/step-core/src/lib/modules/split/injectables/split-resizable-area-persistence.service.ts @@ -10,11 +10,11 @@ export interface SplitAreaSizeController { @Injectable({ providedIn: 'root', }) -export class SplitAreaSizePersistenceService extends StorageProxy { +export class SplitResizableAreaPersistenceService extends StorageProxy { private _screenWidth$ = inject(SCREEN_WIDTH); constructor(@Inject(LOCAL_STORAGE) _storage: Storage) { - super(_storage, 'SPLIT_AREA_SIZE_STATE'); + super(_storage, 'SPLIT_RESIZALBE_AREA_STATE'); this.setupTokensCleanupOnScreenChange(); } From 4e7c08ea925d3a94b44f5f364adb533940c195b5 Mon Sep 17 00:00:00 2001 From: dvladir Date: Thu, 20 Nov 2025 17:38:24 +0300 Subject: [PATCH 9/9] SED-4373 Fixes after merge --- .../plan-function-list/plan-function-list.component.ts | 2 +- .../plan-otherplan-list/plan-otherplan-list.component.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-function-list/plan-function-list.component.ts b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-function-list/plan-function-list.component.ts index 0c2bb7eab2..93ba93b203 100644 --- a/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-function-list/plan-function-list.component.ts +++ b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-function-list/plan-function-list.component.ts @@ -15,7 +15,7 @@ import { import { catchError, filter, map, Observable, of, switchMap } from 'rxjs'; import { PlanNodesDragPreviewComponent } from '../../plan-nodes-drag-preview/plan-nodes-drag-preview.component'; import { KeywordDropInfoPipe } from './keyword-drop-info.pipe'; -import { createActivatableEntitiesTableParams } from '../../injectables/activatable-entities-table-params'; +import { createActivatableEntitiesTableParams } from '../../../injectables/activatable-entities-table-params'; @Component({ selector: 'step-plan-function-list', diff --git a/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-otherplan-list/plan-otherplan-list.component.ts b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-otherplan-list/plan-otherplan-list.component.ts index eb9f96d7bc..1a48289a20 100644 --- a/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-otherplan-list/plan-otherplan-list.component.ts +++ b/projects/step-frontend/src/lib/modules/plan-editor/components/plan-controls/plan-otherplan-list/plan-otherplan-list.component.ts @@ -15,7 +15,7 @@ import { import { catchError, filter, map, Observable, of, switchMap } from 'rxjs'; import { PlanNodesDragPreviewComponent } from '../../plan-nodes-drag-preview/plan-nodes-drag-preview.component'; import { PlanDropInfoPipe } from './plan-drop-info.pipe'; -import { createActivatableEntitiesTableParams } from '../../injectables/activatable-entities-table-params'; +import { createActivatableEntitiesTableParams } from '../../../injectables/activatable-entities-table-params'; @Component({ selector: 'step-plan-otherplan-list',