diff --git a/docs/en/studio/release-notes.md b/docs/en/studio/release-notes.md index 366288cb41..7778860259 100644 --- a/docs/en/studio/release-notes.md +++ b/docs/en/studio/release-notes.md @@ -9,7 +9,18 @@ This document contains **brief release notes** for each ABP Studio release. Release notes only include **major features** and **visible enhancements**. Therefore, they don't include all the development done in the related version. -## 3.1.0 (2026-08-24) Latest +## 3.1.1 (2026-08-28) Latest + +* Refactor release version resolution logic to ensure proper tag handling +* Fix chmod command syntax in set-executable-permissions scripts for Li… +* Restore macOS signing env values used by the 3.0 pipelines +* Show preparing-solution banner only after creation +* Add top spacing above the browser address bar +* Improve fullscreen flyout interaction and small-window clipping +* Fix Admin Console command input export +* Prefer Docker Compose v2 when starting containers + +## 3.1.0 (2026-08-24) * Clarify CLI tracked-process stop method: //github.com/volosoft/abp-studio/pull/4517 * Action of studio template test is working with self hosted agent diff --git a/docs/en/studio/version-mapping.md b/docs/en/studio/version-mapping.md index 5d652fc71d..6cdbd43f31 100644 --- a/docs/en/studio/version-mapping.md +++ b/docs/en/studio/version-mapping.md @@ -11,7 +11,7 @@ This document provides a general overview of the relationship between various ve | **ABP Studio Version** | **ABP Version of Startup Template** | |------------------------|---------------------------| -| 3.0.9 - 3.1.0 | 10.6.0 | +| 3.0.9 - 3.1.1 | 10.6.0 | | 3.0.6 - 3.0.8 | 10.5.0 | | 3.0.4 - 3.0.5 | 10.4.1 | | 3.0.3 | 10.4.0 | diff --git a/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.ts b/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.ts index 30d1cf9796..0bf15dff88 100644 --- a/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.ts +++ b/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.ts @@ -1,8 +1,10 @@ import { + afterNextRender, AfterViewInit, ChangeDetectionStrategy, Component, computed, + DestroyRef, inject, Injector, LOCALE_ID, @@ -17,9 +19,10 @@ import { contentChild, viewChild, } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { AsyncPipe, isPlatformBrowser, NgComponentOutlet, NgTemplateOutlet } from '@angular/common'; -import { Observable, filter, map, Subject, debounceTime, distinctUntilChanged } from 'rxjs'; +import { Observable, map, Subject, debounceTime, distinctUntilChanged } from 'rxjs'; import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap'; import { @@ -92,6 +95,7 @@ const DEFAULT_ACTIONS_COLUMN_WIDTH = 150; }) export class ExtensibleTableComponent implements AfterViewInit, OnDestroy { readonly #injector = inject(Injector); + readonly #destroyRef = inject(DestroyRef); readonly getInjected = this.#injector.get.bind(this.#injector); protected readonly locale = inject(LOCALE_ID); protected readonly config = inject(ConfigStateService); @@ -186,6 +190,10 @@ export class ExtensibleTableComponent implements AfterViewInit, OnDestr }, ); + private horizontalScrollOffset = 0; + + private hasPendingHorizontalScrollOffset = false; + hasAtLeastOnePermittedAction: boolean; readonly propList: EntityPropList; @@ -232,6 +240,7 @@ export class ExtensibleTableComponent implements AfterViewInit, OnDestr } this._data.set(dataValue.map((record, index) => this.prepareRecord(record, index))); + this.restoreHorizontalScrollOffset(); }); } @@ -363,13 +372,66 @@ export class ExtensibleTableComponent implements AfterViewInit, OnDestr ngAfterViewInit(): void { if (!this.infiniteScroll()) { this.list() - ?.requestStatus$?.pipe(filter(status => status === 'loading')) - .subscribe(() => { - this._data.set([]); + ?.requestStatus$?.pipe(takeUntilDestroyed(this.#destroyRef)) + .subscribe(status => { + if (status === 'loading') { + this.rememberHorizontalScrollOffset(); + this._data.set([]); + return; + } + + // A failed request never reaches the data input, restore from here instead + if (status === 'error') { + this.restoreHorizontalScrollOffset(); + } }); } } + private getBodyElement(): HTMLElement | null { + if (!this.isBrowser) { + return null; + } + + return this.table()?.element?.querySelector('datatable-body') ?? null; + } + + // The element carrying the column width is gone while loading without rows, so the browser + // resets the horizontal scroll position and the header keeps the offset it had before + private rememberHorizontalScrollOffset(): void { + const body = this.getBodyElement(); + this.hasPendingHorizontalScrollOffset = true; + + // Not scrollable while a previous request is still in flight, keep the offset taken back then + if (body && body.scrollWidth > body.clientWidth) { + this.horizontalScrollOffset = body.scrollLeft; + } + } + + private restoreHorizontalScrollOffset(): void { + if (!this.hasPendingHorizontalScrollOffset) { + return; + } + + this.hasPendingHorizontalScrollOffset = false; + + afterNextRender( + () => { + const body = this.getBodyElement(); + + if (!body) { + return; + } + + body.scrollLeft = this.horizontalScrollOffset; + + // The header only follows a scroll event, scrolling to the same position does not raise one + body.dispatchEvent(new Event('scroll')); + }, + { injector: this.#injector }, + ); + } + ngOnDestroy(): void { this.loadMoreSubscription.unsubscribe(); }