Skip to content

Commit a21fde6

Browse files
[WC-3112]: Implement Gallery refresh interval (#1947)
2 parents 63452cb + 7591f6e commit a21fde6

File tree

7 files changed

+63
-10
lines changed

7 files changed

+63
-10
lines changed

packages/pluggableWidgets/gallery-web/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66

77
## [Unreleased]
88

9+
### Added
10+
11+
- We added a refresh interval property, to allow defining an interval (in seconds) for refreshing the content in Gallery
12+
913
## [3.7.0] - 2025-11-11
1014

1115
### Added

packages/pluggableWidgets/gallery-web/src/Gallery.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616
<caption>Data source</caption>
1717
<description />
1818
</property>
19+
<property key="refreshInterval" type="integer" defaultValue="0">
20+
<caption>Refresh time (in seconds)</caption>
21+
<description />
22+
</property>
1923
<property key="itemSelection" type="selection" dataSource="datasource">
2024
<caption>Selection</caption>
2125
<description />

packages/pluggableWidgets/gallery-web/src/components/__tests__/Gallery.spec.tsx

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
1-
import { listAction, listExp, setupIntersectionObserverStub } from "@mendix/widget-plugin-test-utils";
1+
import { listAction, listExpression, setupIntersectionObserverStub } from "@mendix/widget-plugin-test-utils";
22
import "@testing-library/jest-dom";
33
import { render, waitFor } from "@testing-library/react";
44
import { ObjectItem } from "mendix";
5+
import { createElement } from "react";
56
import { ItemHelperBuilder } from "../../utils/builders/ItemHelperBuilder";
67
import { mockItemHelperWithAction, mockProps, setup, withGalleryContext } from "../../utils/test-utils";
78
import { Gallery } from "../Gallery";
89

10+
jest.mock("@mendix/widget-plugin-component-kit/RefreshIndicator", () => ({
11+
RefreshIndicator: (_props: any) => createElement("div", { "data-testid": "refresh-indicator" })
12+
}));
13+
914
describe("Gallery", () => {
1015
beforeAll(() => {
1116
setupIntersectionObserverStub();
@@ -24,6 +29,20 @@ describe("Gallery", () => {
2429

2530
expect(asFragment()).toMatchSnapshot();
2631
});
32+
33+
it("renders RefreshIndicator when `showRefreshIndicator` is true", () => {
34+
const base = mockProps();
35+
const props = { ...base, showRefreshIndicator: true };
36+
const { getByTestId } = render(withGalleryContext(<Gallery {...props} />));
37+
expect(getByTestId("refresh-indicator")).toBeInTheDocument();
38+
});
39+
40+
it("does not render RefreshIndicator when `showRefreshIndicator` is false", () => {
41+
const base = mockProps();
42+
const props = { ...base, showRefreshIndicator: false };
43+
const { queryByTestId } = render(withGalleryContext(<Gallery {...props} />));
44+
expect(queryByTestId("refresh-indicator")).toBeNull();
45+
});
2746
});
2847

2948
describe("with on click action", () => {
@@ -84,7 +103,9 @@ describe("Gallery", () => {
84103
withGalleryContext(
85104
<Gallery
86105
{...mockProps()}
87-
itemHelper={ItemHelperBuilder.sample(b => b.withItemClass(listExp(() => "custom-class")))}
106+
itemHelper={ItemHelperBuilder.sample(b =>
107+
b.withItemClass(listExpression(() => "custom-class"))
108+
)}
88109
/>
89110
)
90111
);

packages/pluggableWidgets/gallery-web/src/controllers/DerivedLoaderController.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,39 @@ import { computed, makeObservable } from "mobx";
44
export class DerivedLoaderController {
55
constructor(
66
private datasourceService: DatasourceService,
7-
private refreshIndicator: boolean
7+
private refreshIndicator: boolean,
8+
private showSilentRefresh: boolean
89
) {
910
makeObservable(this, {
10-
isRefreshing: computed,
11-
showRefreshIndicator: computed
11+
isFirstLoad: computed,
12+
isFetchingNextBatch: computed,
13+
isRefreshing: computed
1214
});
1315
}
1416

17+
get isFirstLoad(): boolean {
18+
return this.datasourceService.isFirstLoad;
19+
}
20+
21+
get isFetchingNextBatch(): boolean {
22+
return this.datasourceService.isFetchingNextBatch;
23+
}
24+
1525
get isRefreshing(): boolean {
1626
const { isSilentRefresh, isRefreshing } = this.datasourceService;
27+
28+
if (this.showSilentRefresh) {
29+
return isSilentRefresh || isRefreshing;
30+
}
31+
1732
return !isSilentRefresh && isRefreshing;
1833
}
1934

2035
get showRefreshIndicator(): boolean {
21-
return this.refreshIndicator && this.isRefreshing;
36+
if (!this.refreshIndicator) {
37+
return false;
38+
}
39+
40+
return this.isRefreshing;
2241
}
2342
}

packages/pluggableWidgets/gallery-web/src/stores/GalleryStore.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ interface StaticProps {
3636
storeFilters: boolean;
3737
storeSort: boolean;
3838
refreshIndicator: boolean;
39+
refreshInterval: number;
3940
}
4041

4142
export type GalleryPropsGate = DerivedPropsGate<DynamicProps>;
@@ -63,7 +64,7 @@ export class GalleryStore extends SetupHost {
6364

6465
this.name = spec.name;
6566

66-
this._query = new DatasourceService(this, spec.gate, 0 * 1000);
67+
this._query = new DatasourceService(this, spec.gate, spec.refreshInterval * 1000);
6768

6869
this.paging = new PaginationController({
6970
query: this._query,
@@ -95,7 +96,7 @@ export class GalleryStore extends SetupHost {
9596
host: this._sortHost
9697
};
9798

98-
this.loaderCtrl = new DerivedLoaderController(this._query, spec.refreshIndicator);
99+
this.loaderCtrl = new DerivedLoaderController(this._query, spec.refreshIndicator, spec.refreshInterval >= 1);
99100

100101
const useStorage = spec.storeFilters || spec.storeSort;
101102
if (useStorage) {

packages/pluggableWidgets/gallery-web/src/utils/test-utils.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ export function createMockGalleryContext(): GalleryRootScope {
5858
storeSort: false,
5959
refreshIndicator: false,
6060
keepSelection: false,
61-
selectionCountPosition: "bottom"
61+
selectionCountPosition: "bottom",
62+
refreshInterval: 0
6263
};
6364

6465
// Create a proper gate provider and gate
@@ -76,7 +77,8 @@ export function createMockGalleryContext(): GalleryRootScope {
7677
stateStorageType: "localStorage",
7778
storeFilters: false,
7879
storeSort: false,
79-
refreshIndicator: false
80+
refreshIndicator: false,
81+
refreshInterval: 0
8082
});
8183

8284
const mockSelectHelper = new SelectActionHandler("None", undefined);

packages/pluggableWidgets/gallery-web/typings/GalleryProps.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export interface GalleryContainerProps {
2929
tabIndex?: number;
3030
filtersPlaceholder?: ReactNode;
3131
datasource: ListValue;
32+
refreshInterval: number;
3233
itemSelection?: SelectionSingleValue | SelectionMultiValue;
3334
itemSelectionMode: ItemSelectionModeEnum;
3435
keepSelection: boolean;
@@ -76,6 +77,7 @@ export interface GalleryPreviewProps {
7677
translate: (text: string) => string;
7778
filtersPlaceholder: { widgetCount: number; renderer: ComponentType<{ children: ReactNode; caption?: string }> };
7879
datasource: {} | { caption: string } | { type: string } | null;
80+
refreshInterval: number | null;
7981
itemSelection: "None" | "Single" | "Multi";
8082
itemSelectionMode: ItemSelectionModeEnum;
8183
keepSelection: boolean;

0 commit comments

Comments
 (0)