Skip to content

Commit 1685e85

Browse files
itaisinaiIzaacAyelin
authored andcommitted
feat: expose thumbnails aspect ratio
1 parent 53c9a5b commit 1685e85

14 files changed

Lines changed: 239 additions & 35 deletions

File tree

packages/gallery/src/components/gallery/proGallery/galleryContainer.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,7 @@ export class GalleryContainer extends React.Component {
332332
this.galleryStructure = ItemsHelper.convertToGalleryItems(structure, {
333333
// TODO use same objects in the memory when the galleryItems are changed
334334
thumbnailSize: options[optionsMap.layoutParams.thumbnails.size],
335+
thumbnailRatio: options[optionsMap.layoutParams.thumbnails.ratio] || 1,
335336
sharpParams: options.sharpParams,
336337
createMediaUrl,
337338
});

packages/gallery/src/components/gallery/proGallery/navigationPanel.js

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,25 @@ class NavigationPanel extends React.Component {
2323
);
2424
const activeIndex = utils.inRange(this.props.activeIndex, clearedGalleryItems.length);
2525

26-
const { horizontalThumbnails, items, thumbnailsMargins, thumbnailsStyle, activeIndexOffsetMemory } =
27-
thumbnailsLogic.getThumbnailsData({
28-
items: this.props.items,
29-
activeIndex,
30-
options,
31-
galleryStructure,
32-
thumbnailAlignment,
33-
containerHeight: this.props.container.height,
34-
containerWidth: this.props.container.width,
35-
activeIndexOffsetMemory: this.activeIndexOffsetMemory,
36-
prevActiveIndex: this.prevActiveIndex,
37-
});
26+
const {
27+
horizontalThumbnails,
28+
items,
29+
thumbnailsMargins,
30+
thumbnailsStyle,
31+
activeIndexOffsetMemory,
32+
thumbnailWidth,
33+
thumbnailHeight,
34+
} = thumbnailsLogic.getThumbnailsData({
35+
items: this.props.items,
36+
activeIndex,
37+
options,
38+
galleryStructure,
39+
thumbnailAlignment,
40+
containerHeight: this.props.container.height,
41+
containerWidth: this.props.container.width,
42+
activeIndexOffsetMemory: this.activeIndexOffsetMemory,
43+
prevActiveIndex: this.prevActiveIndex,
44+
});
3845

3946
this.prevActiveIndex = activeIndex;
4047
this.activeIndexOffsetMemory = activeIndexOffsetMemory;
@@ -72,8 +79,8 @@ class NavigationPanel extends React.Component {
7279
{items.map(({ thumbnailItem, location, idx }) => {
7380
const highlighted = idx === activeIndex % clearedGalleryItems.length;
7481
const itemStyle = {
75-
width: options[optionsMap.layoutParams.thumbnails.size],
76-
height: options[optionsMap.layoutParams.thumbnails.size],
82+
width: thumbnailWidth,
83+
height: thumbnailHeight,
7784
overflow: 'hidden',
7885
backgroundImage: `url(${thumbnailItem.createUrl(
7986
GALLERY_CONSTS.urlSizes.THUMBNAIL,
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import GalleryDriver from '../drivers/reactDriver';
2+
import { expect } from 'chai';
3+
import { GALLERY_CONSTS, optionsMap } from 'pro-gallery-lib';
4+
import { images2 } from '../drivers/mocks/items';
5+
import { options, container } from '../drivers/mocks/styles';
6+
7+
describe('options - layoutParams_thumbnails_ratio', () => {
8+
let driver;
9+
let initialProps;
10+
beforeEach(() => {
11+
driver = new GalleryDriver();
12+
initialProps = {
13+
container,
14+
items: images2,
15+
options,
16+
};
17+
});
18+
it('should use default ratio of 1 (square thumbnails)', async () => {
19+
initialProps.options = Object.assign(initialProps.options, {
20+
[optionsMap.layoutParams.structure.galleryLayout]:
21+
GALLERY_CONSTS[optionsMap.layoutParams.structure.galleryLayout].THUMBNAIL,
22+
[optionsMap.layoutParams.thumbnails.size]: 120,
23+
});
24+
driver.mount.proGallery(initialProps);
25+
await driver.update();
26+
const thumbnailItem = driver.find.selector('.thumbnailItem').at(0);
27+
const { width, height } = thumbnailItem.props().style;
28+
expect(width).to.eq(120);
29+
expect(height).to.eq(120);
30+
driver.detach.proGallery();
31+
});
32+
it('should apply ratio of 0.75 (3:4 portrait thumbnails)', async () => {
33+
initialProps.options = Object.assign(initialProps.options, {
34+
[optionsMap.layoutParams.structure.galleryLayout]:
35+
GALLERY_CONSTS[optionsMap.layoutParams.structure.galleryLayout].THUMBNAIL,
36+
[optionsMap.layoutParams.thumbnails.size]: 120,
37+
[optionsMap.layoutParams.thumbnails.ratio]: 0.75,
38+
});
39+
driver.mount.proGallery(initialProps);
40+
await driver.update();
41+
const thumbnailItem = driver.find.selector('.thumbnailItem').at(0);
42+
const { width, height } = thumbnailItem.props().style;
43+
expect(width).to.eq(90); // 120 * 0.75 = 90
44+
expect(height).to.eq(120);
45+
driver.detach.proGallery();
46+
});
47+
it('should apply ratio of 1.333 (4:3 landscape thumbnails)', async () => {
48+
initialProps.options = Object.assign(initialProps.options, {
49+
[optionsMap.layoutParams.structure.galleryLayout]:
50+
GALLERY_CONSTS[optionsMap.layoutParams.structure.galleryLayout].THUMBNAIL,
51+
[optionsMap.layoutParams.thumbnails.size]: 120,
52+
[optionsMap.layoutParams.thumbnails.ratio]: 1.333,
53+
});
54+
driver.mount.proGallery(initialProps);
55+
await driver.update();
56+
const thumbnailItem = driver.find.selector('.thumbnailItem').at(0);
57+
const { width, height } = thumbnailItem.props().style;
58+
expect(width).to.be.closeTo(160, 1); // 120 * 1.333 ≈ 160
59+
expect(height).to.eq(120);
60+
driver.detach.proGallery();
61+
});
62+
it('should update thumbnail width when ratio changes', async () => {
63+
initialProps.options = Object.assign(initialProps.options, {
64+
[optionsMap.layoutParams.structure.galleryLayout]:
65+
GALLERY_CONSTS[optionsMap.layoutParams.structure.galleryLayout].THUMBNAIL,
66+
[optionsMap.layoutParams.thumbnails.size]: 200,
67+
[optionsMap.layoutParams.thumbnails.ratio]: 2,
68+
});
69+
driver.mount.proGallery(initialProps);
70+
await driver.update();
71+
const thumbnailItem = driver.find.selector('.thumbnailItem').at(0);
72+
const { width, height } = thumbnailItem.props().style;
73+
expect(width).to.eq(400); // 200 * 2 = 400
74+
expect(height).to.eq(200);
75+
driver.detach.proGallery();
76+
});
77+
it('should apply ratio to height for vertical thumbnails (LEFT placement)', async () => {
78+
initialProps.options = Object.assign(initialProps.options, {
79+
[optionsMap.layoutParams.structure.galleryLayout]:
80+
GALLERY_CONSTS[optionsMap.layoutParams.structure.galleryLayout].THUMBNAIL,
81+
[optionsMap.layoutParams.thumbnails.size]: 120,
82+
[optionsMap.layoutParams.thumbnails.ratio]: 1.333,
83+
[optionsMap.layoutParams.thumbnails.alignment]: GALLERY_CONSTS[optionsMap.layoutParams.thumbnails.alignment].LEFT,
84+
});
85+
driver.mount.proGallery(initialProps);
86+
await driver.update();
87+
const thumbnailItem = driver.find.selector('.thumbnailItem').at(0);
88+
const { width, height } = thumbnailItem.props().style;
89+
// For vertical thumbnails: maintain width, adjust height
90+
expect(width).to.eq(120); // width stays constant
91+
expect(height).to.be.closeTo(90, 1); // 120 / 1.333 ≈ 90
92+
driver.detach.proGallery();
93+
});
94+
it('should apply ratio to height for vertical thumbnails (RIGHT placement)', async () => {
95+
initialProps.options = Object.assign(initialProps.options, {
96+
[optionsMap.layoutParams.structure.galleryLayout]:
97+
GALLERY_CONSTS[optionsMap.layoutParams.structure.galleryLayout].THUMBNAIL,
98+
[optionsMap.layoutParams.thumbnails.size]: 120,
99+
[optionsMap.layoutParams.thumbnails.ratio]: 0.75,
100+
[optionsMap.layoutParams.thumbnails.alignment]:
101+
GALLERY_CONSTS[optionsMap.layoutParams.thumbnails.alignment].RIGHT,
102+
});
103+
driver.mount.proGallery(initialProps);
104+
await driver.update();
105+
const thumbnailItem = driver.find.selector('.thumbnailItem').at(0);
106+
const { width, height } = thumbnailItem.props().style;
107+
// For vertical thumbnails: maintain width, adjust height
108+
expect(width).to.eq(120); // width stays constant
109+
expect(height).to.be.closeTo(160, 1); // 120 / 0.75 = 160
110+
driver.detach.proGallery();
111+
});
112+
it('should apply ratio to width for horizontal thumbnails (TOP placement)', async () => {
113+
initialProps.options = Object.assign(initialProps.options, {
114+
[optionsMap.layoutParams.structure.galleryLayout]:
115+
GALLERY_CONSTS[optionsMap.layoutParams.structure.galleryLayout].THUMBNAIL,
116+
[optionsMap.layoutParams.thumbnails.size]: 120,
117+
[optionsMap.layoutParams.thumbnails.ratio]: 1.333,
118+
[optionsMap.layoutParams.thumbnails.alignment]: GALLERY_CONSTS[optionsMap.layoutParams.thumbnails.alignment].TOP,
119+
});
120+
driver.mount.proGallery(initialProps);
121+
await driver.update();
122+
const thumbnailItem = driver.find.selector('.thumbnailItem').at(0);
123+
const { width, height } = thumbnailItem.props().style;
124+
// For horizontal thumbnails: maintain height, adjust width
125+
expect(height).to.eq(120); // height stays constant
126+
expect(width).to.be.closeTo(160, 1); // 120 * 1.333 ≈ 160
127+
driver.detach.proGallery();
128+
});
129+
it('should apply ratio of 0.5625 (9:16 portrait thumbnails)', async () => {
130+
initialProps.options = Object.assign(initialProps.options, {
131+
[optionsMap.layoutParams.structure.galleryLayout]:
132+
GALLERY_CONSTS[optionsMap.layoutParams.structure.galleryLayout].THUMBNAIL,
133+
[optionsMap.layoutParams.thumbnails.size]: 120,
134+
[optionsMap.layoutParams.thumbnails.ratio]: 9 / 16,
135+
});
136+
driver.mount.proGallery(initialProps);
137+
await driver.update();
138+
const thumbnailItem = driver.find.selector('.thumbnailItem').at(0);
139+
const { width, height } = thumbnailItem.props().style;
140+
expect(width).to.be.closeTo(67.5, 1); // 120 * (9/16) = 120 * 0.5625 = 67.5
141+
expect(height).to.eq(120);
142+
driver.detach.proGallery();
143+
});
144+
});

packages/layouts/src/classes/galleryItem.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ class GalleryItem {
7474
this.sharpParams.usm = {};
7575
}
7676
this.thumbnailSize = config.thumbnailSize || 120;
77+
this.thumbnailRatio = config.thumbnailRatio || 1;
7778

7879
this.resetUrls();
7980
this.updateSharpParams();
@@ -389,10 +390,11 @@ class GalleryItem {
389390

390391
get thumbnail_url() {
391392
if (!this.urls.thumbnail_url) {
393+
const thumbnailHeight = this.thumbnailSize / this.thumbnailRatio;
392394
this.urls.thumbnail_url = this.processedMediaUrl(
393395
GALLERY_CONSTS.resizeMethods.FILL,
394396
this.thumbnailSize,
395-
this.thumbnailSize,
397+
thumbnailHeight,
396398
{ quality: 70 }
397399
);
398400
}

packages/lib/src/common/defaultOptions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ const defaultOptions = flattenObject({
5454
enable: false,
5555
position: GALLERY_CONSTS[optionsMap.layoutParams.thumbnails.position].OUTSIDE_GALLERY,
5656
alignment: GALLERY_CONSTS[optionsMap.layoutParams.thumbnails.alignment].BOTTOM,
57+
ratio: 1,
5758
},
5859
navigationArrows: {
5960
enable: true,

packages/lib/src/common/interfaces/layoutParams.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export interface Thumbnails {
2525
marginToGallery?: number;
2626
size?: number;
2727
alignment?: 'BOTTOM' | 'RIGHT' | 'LEFT' | 'TOP';
28+
ratio?: number;
2829
}
2930

3031
export interface Scatter {

packages/lib/src/common/v4DefaultOptions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ const defaultV4Options = {
5252
enable: false,
5353
position: GALLERY_CONSTS[optionsMap.layoutParams.thumbnails.position].OUTSIDE_GALLERY,
5454
alignment: GALLERY_CONSTS[optionsMap.layoutParams.thumbnails.alignment].BOTTOM,
55+
ratio: 1,
5556
},
5657
navigationArrows: {
5758
enable: true,

packages/lib/src/core/helpers/dimensionsHelper.js

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -106,34 +106,35 @@ class DimensionsHelper {
106106
});
107107
}
108108

109-
getThumbnailSize() {
110-
const fixedThumbnailSize =
109+
_getThumbnailDeltaSize() {
110+
return (
111111
this.options[optionsMap.layoutParams.thumbnails.size] +
112112
this.options[optionsMap.layoutParams.structure.gallerySpacing] +
113-
this.options[optionsMap.layoutParams.thumbnails.marginToGallery];
114-
return fixedThumbnailSize;
113+
this.options[optionsMap.layoutParams.thumbnails.marginToGallery]
114+
);
115115
}
116116

117117
getThumbnailHeightDelta() {
118118
switch (this.options[optionsMap.layoutParams.thumbnails.alignment]) {
119119
case GALLERY_CONSTS[optionsMap.layoutParams.thumbnails.alignment].TOP:
120120
case GALLERY_CONSTS[optionsMap.layoutParams.thumbnails.alignment].BOTTOM:
121-
return this.getThumbnailSize();
121+
return this._getThumbnailDeltaSize();
122122
case GALLERY_CONSTS[optionsMap.layoutParams.thumbnails.alignment].RIGHT:
123123
case GALLERY_CONSTS[optionsMap.layoutParams.thumbnails.alignment].LEFT:
124124
return 0;
125125
default:
126126
break;
127127
}
128128
}
129+
129130
getThumbnailWidthDelta() {
130131
switch (this.options[optionsMap.layoutParams.thumbnails.alignment]) {
131132
case GALLERY_CONSTS[optionsMap.layoutParams.thumbnails.alignment].TOP:
132133
case GALLERY_CONSTS[optionsMap.layoutParams.thumbnails.alignment].BOTTOM:
133134
return 0;
134135
case GALLERY_CONSTS[optionsMap.layoutParams.thumbnails.alignment].RIGHT:
135136
case GALLERY_CONSTS[optionsMap.layoutParams.thumbnails.alignment].LEFT:
136-
return this.getThumbnailSize();
137+
return this._getThumbnailDeltaSize();
137138
default:
138139
break;
139140
}

packages/lib/src/core/helpers/optionsBackwardConverter.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ function addOldOptions(flatOptions) {
2424

2525
function reverseMigrateOptions(flatOptionsObject) {
2626
let oldOptions = { ...flatOptionsObject };
27+
delete oldOptions[optionsMap.layoutParams.thumbnails.ratio]; // Remove new-only property that doesn't exist in old format
2728
///----------- LAYOUT -------------///
2829
oldOptions = changeNames(
2930
oldOptions,

0 commit comments

Comments
 (0)