From 2407290d42a8ac41c5d51f6f41cb8715ee931534 Mon Sep 17 00:00:00 2001 From: Samuel Reichert Date: Tue, 21 Oct 2025 09:39:32 +0200 Subject: [PATCH 1/6] feat(gallery-web): add refresh interval property --- packages/pluggableWidgets/gallery-web/src/Gallery.xml | 4 ++++ .../pluggableWidgets/gallery-web/typings/GalleryProps.d.ts | 2 ++ 2 files changed, 6 insertions(+) diff --git a/packages/pluggableWidgets/gallery-web/src/Gallery.xml b/packages/pluggableWidgets/gallery-web/src/Gallery.xml index 1c9a0b931e..ffe357ae00 100644 --- a/packages/pluggableWidgets/gallery-web/src/Gallery.xml +++ b/packages/pluggableWidgets/gallery-web/src/Gallery.xml @@ -16,6 +16,10 @@ Data source + + Refresh time (in seconds) + + Selection diff --git a/packages/pluggableWidgets/gallery-web/typings/GalleryProps.d.ts b/packages/pluggableWidgets/gallery-web/typings/GalleryProps.d.ts index b0cfa98317..8f5b2b1f24 100644 --- a/packages/pluggableWidgets/gallery-web/typings/GalleryProps.d.ts +++ b/packages/pluggableWidgets/gallery-web/typings/GalleryProps.d.ts @@ -29,6 +29,7 @@ export interface GalleryContainerProps { tabIndex?: number; filtersPlaceholder?: ReactNode; datasource: ListValue; + refreshInterval: number; itemSelection?: SelectionSingleValue | SelectionMultiValue; itemSelectionMode: ItemSelectionModeEnum; keepSelection: boolean; @@ -76,6 +77,7 @@ export interface GalleryPreviewProps { translate: (text: string) => string; filtersPlaceholder: { widgetCount: number; renderer: ComponentType<{ children: ReactNode; caption?: string }> }; datasource: {} | { caption: string } | { type: string } | null; + refreshInterval: number | null; itemSelection: "None" | "Single" | "Multi"; itemSelectionMode: ItemSelectionModeEnum; keepSelection: boolean; From d537ded7681b6d4a9ba115ec4d96ea58089616a5 Mon Sep 17 00:00:00 2001 From: Samuel Reichert Date: Tue, 21 Oct 2025 09:40:01 +0200 Subject: [PATCH 2/6] chore(gallery-web): update test-utils --- .../pluggableWidgets/gallery-web/src/utils/test-utils.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/pluggableWidgets/gallery-web/src/utils/test-utils.tsx b/packages/pluggableWidgets/gallery-web/src/utils/test-utils.tsx index 4fc8a0b41b..0d5ac84aa3 100644 --- a/packages/pluggableWidgets/gallery-web/src/utils/test-utils.tsx +++ b/packages/pluggableWidgets/gallery-web/src/utils/test-utils.tsx @@ -58,7 +58,8 @@ export function createMockGalleryContext(): GalleryRootScope { storeSort: false, refreshIndicator: false, keepSelection: false, - selectionCountPosition: "bottom" + selectionCountPosition: "bottom", + refreshInterval: 0 }; // Create a proper gate provider and gate @@ -76,7 +77,8 @@ export function createMockGalleryContext(): GalleryRootScope { stateStorageType: "localStorage", storeFilters: false, storeSort: false, - refreshIndicator: false + refreshIndicator: false, + refreshInterval: 0 }); const mockSelectHelper = new SelectActionHandler("None", undefined); From 99618f9c22916b88b906ed69d95ec36c2016d855 Mon Sep 17 00:00:00 2001 From: Samuel Reichert Date: Tue, 21 Oct 2025 09:40:58 +0200 Subject: [PATCH 3/6] feat(gallery-web): update derived loader controller to use refresh interval --- .../controllers/DerivedLoaderController.ts | 27 ++++++++++++++++--- .../gallery-web/src/stores/GalleryStore.ts | 9 +++++-- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/packages/pluggableWidgets/gallery-web/src/controllers/DerivedLoaderController.ts b/packages/pluggableWidgets/gallery-web/src/controllers/DerivedLoaderController.ts index c4ff2931d0..0585e621db 100644 --- a/packages/pluggableWidgets/gallery-web/src/controllers/DerivedLoaderController.ts +++ b/packages/pluggableWidgets/gallery-web/src/controllers/DerivedLoaderController.ts @@ -4,20 +4,39 @@ import { computed, makeObservable } from "mobx"; export class DerivedLoaderController { constructor( private datasourceService: DatasourceService, - private refreshIndicator: boolean + private refreshIndicator: boolean, + private showSilentRefresh: boolean ) { makeObservable(this, { - isRefreshing: computed, - showRefreshIndicator: computed + isFirstLoad: computed, + isFetchingNextBatch: computed, + isRefreshing: computed }); } + get isFirstLoad(): boolean { + return this.datasourceService.isFirstLoad; + } + + get isFetchingNextBatch(): boolean { + return this.datasourceService.isFetchingNextBatch; + } + get isRefreshing(): boolean { const { isSilentRefresh, isRefreshing } = this.datasourceService; + + if (this.showSilentRefresh) { + return isSilentRefresh || isRefreshing; + } + return !isSilentRefresh && isRefreshing; } get showRefreshIndicator(): boolean { - return this.refreshIndicator && this.isRefreshing; + if (!this.refreshIndicator) { + return false; + } + + return this.isRefreshing; } } diff --git a/packages/pluggableWidgets/gallery-web/src/stores/GalleryStore.ts b/packages/pluggableWidgets/gallery-web/src/stores/GalleryStore.ts index c92645b252..c5fbda28fe 100644 --- a/packages/pluggableWidgets/gallery-web/src/stores/GalleryStore.ts +++ b/packages/pluggableWidgets/gallery-web/src/stores/GalleryStore.ts @@ -36,6 +36,7 @@ interface StaticProps { storeFilters: boolean; storeSort: boolean; refreshIndicator: boolean; + refreshInterval: number; } export type GalleryPropsGate = DerivedPropsGate; @@ -63,7 +64,7 @@ export class GalleryStore extends SetupHost { this.name = spec.name; - this._query = new DatasourceService(this, spec.gate, 0 * 1000); + this._query = new DatasourceService(this, spec.gate, spec.refreshInterval * 1000); this.paging = new PaginationController({ query: this._query, @@ -95,7 +96,11 @@ export class GalleryStore extends SetupHost { host: this._sortHost }; - this.loaderCtrl = new DerivedLoaderController(this._query, spec.refreshIndicator); + this.loaderCtrl = new DerivedLoaderController({ + showSilentRefresh: spec.refreshInterval > 1, + refreshIndicator: spec.refreshIndicator, + query: this._query + }); const useStorage = spec.storeFilters || spec.storeSort; if (useStorage) { From f23ac3adfb45ba86f0fab6e90aebb6248a772878 Mon Sep 17 00:00:00 2001 From: Samuel Reichert Date: Tue, 21 Oct 2025 09:41:12 +0200 Subject: [PATCH 4/6] chore(gallery-web): update changelog --- packages/pluggableWidgets/gallery-web/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/pluggableWidgets/gallery-web/CHANGELOG.md b/packages/pluggableWidgets/gallery-web/CHANGELOG.md index 7b53818115..39a2fed332 100644 --- a/packages/pluggableWidgets/gallery-web/CHANGELOG.md +++ b/packages/pluggableWidgets/gallery-web/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Added + +- We added a refresh intervel property, to allow defining an interval (in seconds) for refreshing the content in Gallery + ## [3.7.0] - 2025-11-11 ### Added From 6b3120a23073dd07bd27ab4f508d6f86c817e2cc Mon Sep 17 00:00:00 2001 From: Samuel Reichert Date: Fri, 14 Nov 2025 10:08:40 +0100 Subject: [PATCH 5/6] fix(gallery-web): change how parameter are used on DerivedLoaderController --- .../pluggableWidgets/gallery-web/src/stores/GalleryStore.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/pluggableWidgets/gallery-web/src/stores/GalleryStore.ts b/packages/pluggableWidgets/gallery-web/src/stores/GalleryStore.ts index c5fbda28fe..68d5d9cd9e 100644 --- a/packages/pluggableWidgets/gallery-web/src/stores/GalleryStore.ts +++ b/packages/pluggableWidgets/gallery-web/src/stores/GalleryStore.ts @@ -96,11 +96,7 @@ export class GalleryStore extends SetupHost { host: this._sortHost }; - this.loaderCtrl = new DerivedLoaderController({ - showSilentRefresh: spec.refreshInterval > 1, - refreshIndicator: spec.refreshIndicator, - query: this._query - }); + this.loaderCtrl = new DerivedLoaderController(this._query, spec.refreshIndicator, spec.refreshInterval > 1); const useStorage = spec.storeFilters || spec.storeSort; if (useStorage) { From 7591f6e3de56e038a554ebe29160c16144b4475f Mon Sep 17 00:00:00 2001 From: Samuel Reichert Date: Mon, 17 Nov 2025 14:54:56 +0100 Subject: [PATCH 6/6] chore(gallery-web): fix comments --- .../pluggableWidgets/gallery-web/CHANGELOG.md | 2 +- .../src/components/__tests__/Gallery.spec.tsx | 25 +++++++++++++++++-- .../gallery-web/src/stores/GalleryStore.ts | 2 +- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/packages/pluggableWidgets/gallery-web/CHANGELOG.md b/packages/pluggableWidgets/gallery-web/CHANGELOG.md index 39a2fed332..cf7ad62fe3 100644 --- a/packages/pluggableWidgets/gallery-web/CHANGELOG.md +++ b/packages/pluggableWidgets/gallery-web/CHANGELOG.md @@ -8,7 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Added -- We added a refresh intervel property, to allow defining an interval (in seconds) for refreshing the content in Gallery +- We added a refresh interval property, to allow defining an interval (in seconds) for refreshing the content in Gallery ## [3.7.0] - 2025-11-11 diff --git a/packages/pluggableWidgets/gallery-web/src/components/__tests__/Gallery.spec.tsx b/packages/pluggableWidgets/gallery-web/src/components/__tests__/Gallery.spec.tsx index 4d92d8ba90..3551501fef 100644 --- a/packages/pluggableWidgets/gallery-web/src/components/__tests__/Gallery.spec.tsx +++ b/packages/pluggableWidgets/gallery-web/src/components/__tests__/Gallery.spec.tsx @@ -1,11 +1,16 @@ -import { listAction, listExp, setupIntersectionObserverStub } from "@mendix/widget-plugin-test-utils"; +import { listAction, listExpression, setupIntersectionObserverStub } from "@mendix/widget-plugin-test-utils"; import "@testing-library/jest-dom"; import { render, waitFor } from "@testing-library/react"; import { ObjectItem } from "mendix"; +import { createElement } from "react"; import { ItemHelperBuilder } from "../../utils/builders/ItemHelperBuilder"; import { mockItemHelperWithAction, mockProps, setup, withGalleryContext } from "../../utils/test-utils"; import { Gallery } from "../Gallery"; +jest.mock("@mendix/widget-plugin-component-kit/RefreshIndicator", () => ({ + RefreshIndicator: (_props: any) => createElement("div", { "data-testid": "refresh-indicator" }) +})); + describe("Gallery", () => { beforeAll(() => { setupIntersectionObserverStub(); @@ -24,6 +29,20 @@ describe("Gallery", () => { expect(asFragment()).toMatchSnapshot(); }); + + it("renders RefreshIndicator when `showRefreshIndicator` is true", () => { + const base = mockProps(); + const props = { ...base, showRefreshIndicator: true }; + const { getByTestId } = render(withGalleryContext()); + expect(getByTestId("refresh-indicator")).toBeInTheDocument(); + }); + + it("does not render RefreshIndicator when `showRefreshIndicator` is false", () => { + const base = mockProps(); + const props = { ...base, showRefreshIndicator: false }; + const { queryByTestId } = render(withGalleryContext()); + expect(queryByTestId("refresh-indicator")).toBeNull(); + }); }); describe("with on click action", () => { @@ -84,7 +103,9 @@ describe("Gallery", () => { withGalleryContext( b.withItemClass(listExp(() => "custom-class")))} + itemHelper={ItemHelperBuilder.sample(b => + b.withItemClass(listExpression(() => "custom-class")) + )} /> ) ); diff --git a/packages/pluggableWidgets/gallery-web/src/stores/GalleryStore.ts b/packages/pluggableWidgets/gallery-web/src/stores/GalleryStore.ts index 68d5d9cd9e..448a280229 100644 --- a/packages/pluggableWidgets/gallery-web/src/stores/GalleryStore.ts +++ b/packages/pluggableWidgets/gallery-web/src/stores/GalleryStore.ts @@ -96,7 +96,7 @@ export class GalleryStore extends SetupHost { host: this._sortHost }; - this.loaderCtrl = new DerivedLoaderController(this._query, spec.refreshIndicator, spec.refreshInterval > 1); + this.loaderCtrl = new DerivedLoaderController(this._query, spec.refreshIndicator, spec.refreshInterval >= 1); const useStorage = spec.storeFilters || spec.storeSort; if (useStorage) {