@thangdevalone/meet-layout-grid-core 1.0.8 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -9
- package/dist/index.cjs +591 -275
- package/dist/index.d.cts +154 -14
- package/dist/index.d.mts +154 -14
- package/dist/index.d.ts +154 -14
- package/dist/index.mjs +589 -276
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -16,6 +16,212 @@ function parseAspectRatio(ratio) {
|
|
|
16
16
|
}
|
|
17
17
|
return { widthRatio: width, heightRatio: height };
|
|
18
18
|
}
|
|
19
|
+
function calculateContentDimensions(cellDimensions, itemRatio, defaultRatio) {
|
|
20
|
+
const { width: cellW, height: cellH } = cellDimensions;
|
|
21
|
+
const effectiveRatio = itemRatio ?? (defaultRatio ? defaultRatio : void 0);
|
|
22
|
+
if (!effectiveRatio || effectiveRatio === "fill" || effectiveRatio === "auto") {
|
|
23
|
+
return {
|
|
24
|
+
width: cellW,
|
|
25
|
+
height: cellH,
|
|
26
|
+
offsetTop: 0,
|
|
27
|
+
offsetLeft: 0
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const ratio = getAspectRatio(effectiveRatio);
|
|
31
|
+
let contentW = cellW;
|
|
32
|
+
let contentH = contentW * ratio;
|
|
33
|
+
if (contentH > cellH) {
|
|
34
|
+
contentH = cellH;
|
|
35
|
+
contentW = contentH / ratio;
|
|
36
|
+
}
|
|
37
|
+
const offsetTop = (cellH - contentH) / 2;
|
|
38
|
+
const offsetLeft = (cellW - contentW) / 2;
|
|
39
|
+
return {
|
|
40
|
+
width: contentW,
|
|
41
|
+
height: contentH,
|
|
42
|
+
offsetTop,
|
|
43
|
+
offsetLeft
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function createGetItemContentDimensions(getItemDimensions, itemAspectRatios, defaultRatio) {
|
|
47
|
+
return (index, itemRatio) => {
|
|
48
|
+
const cellDimensions = getItemDimensions(index);
|
|
49
|
+
const effectiveRatio = itemRatio ?? itemAspectRatios?.[index] ?? defaultRatio;
|
|
50
|
+
return calculateContentDimensions(cellDimensions, effectiveRatio, defaultRatio);
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function calculateFlexLayout(options) {
|
|
54
|
+
const { dimensions, count, aspectRatio, gap, itemAspectRatios, preferHorizontal } = options;
|
|
55
|
+
if (count === 0 || dimensions.width === 0 || dimensions.height === 0) {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
const containerWidth = dimensions.width - gap * 2;
|
|
59
|
+
const containerHeight = dimensions.height - gap * 2;
|
|
60
|
+
const getItemAspectValue = (index) => {
|
|
61
|
+
const itemRatio = itemAspectRatios?.[index] ?? aspectRatio;
|
|
62
|
+
if (!itemRatio || itemRatio === "fill" || itemRatio === "auto") {
|
|
63
|
+
return 16 / 9;
|
|
64
|
+
}
|
|
65
|
+
const parsed = parseAspectRatio(itemRatio);
|
|
66
|
+
return parsed ? parsed.widthRatio / parsed.heightRatio : 16 / 9;
|
|
67
|
+
};
|
|
68
|
+
const aspectValues = [];
|
|
69
|
+
for (let i = 0; i < count; i++) {
|
|
70
|
+
aspectValues.push(getItemAspectValue(i));
|
|
71
|
+
}
|
|
72
|
+
const avgAspect = aspectValues.reduce((a, b) => a + b, 0) / count;
|
|
73
|
+
const containerAspect = containerWidth / containerHeight;
|
|
74
|
+
let numRows;
|
|
75
|
+
if (preferHorizontal) {
|
|
76
|
+
const sumAspects = aspectValues.reduce((a, b) => a + b, 0);
|
|
77
|
+
const singleRowHeight = containerWidth / sumAspects;
|
|
78
|
+
if (singleRowHeight <= containerHeight) {
|
|
79
|
+
numRows = 1;
|
|
80
|
+
} else {
|
|
81
|
+
numRows = Math.ceil(Math.sqrt(count * containerHeight / containerWidth / avgAspect));
|
|
82
|
+
numRows = Math.max(1, Math.min(count, numRows));
|
|
83
|
+
}
|
|
84
|
+
} else {
|
|
85
|
+
numRows = Math.round(Math.sqrt(count / (containerAspect / avgAspect)));
|
|
86
|
+
numRows = Math.max(1, Math.min(count, numRows));
|
|
87
|
+
}
|
|
88
|
+
const itemsPerRow = Math.ceil(count / numRows);
|
|
89
|
+
const rows = [];
|
|
90
|
+
for (let i = 0; i < count; i += itemsPerRow) {
|
|
91
|
+
const row = [];
|
|
92
|
+
for (let j = i; j < Math.min(i + itemsPerRow, count); j++) {
|
|
93
|
+
row.push(j);
|
|
94
|
+
}
|
|
95
|
+
rows.push(row);
|
|
96
|
+
}
|
|
97
|
+
const rowHeights = [];
|
|
98
|
+
for (const row of rows) {
|
|
99
|
+
const sumAspects = row.reduce((sum, idx) => sum + aspectValues[idx], 0);
|
|
100
|
+
const totalGapWidth = (row.length - 1) * gap;
|
|
101
|
+
const height = (containerWidth - totalGapWidth) / sumAspects;
|
|
102
|
+
rowHeights.push(height);
|
|
103
|
+
}
|
|
104
|
+
const totalGapHeight = (rows.length - 1) * gap;
|
|
105
|
+
const totalNaturalHeight = rowHeights.reduce((a, b) => a + b, 0);
|
|
106
|
+
const scale = Math.min(1, (containerHeight - totalGapHeight) / totalNaturalHeight);
|
|
107
|
+
for (let i = 0; i < rowHeights.length; i++) {
|
|
108
|
+
rowHeights[i] *= scale;
|
|
109
|
+
}
|
|
110
|
+
const totalScaledHeight = rowHeights.reduce((a, b) => a + b, 0) + totalGapHeight;
|
|
111
|
+
const verticalOffset = (containerHeight - totalScaledHeight) / 2;
|
|
112
|
+
const items = [];
|
|
113
|
+
let currentTop = gap + verticalOffset;
|
|
114
|
+
for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {
|
|
115
|
+
const row = rows[rowIndex];
|
|
116
|
+
const rowHeight = rowHeights[rowIndex];
|
|
117
|
+
let rowWidth = 0;
|
|
118
|
+
for (const idx of row) {
|
|
119
|
+
rowWidth += rowHeight * aspectValues[idx];
|
|
120
|
+
}
|
|
121
|
+
rowWidth += (row.length - 1) * gap;
|
|
122
|
+
const horizontalOffset = (containerWidth - rowWidth) / 2;
|
|
123
|
+
let currentLeft = gap + horizontalOffset;
|
|
124
|
+
for (const itemIndex of row) {
|
|
125
|
+
const aspect = aspectValues[itemIndex];
|
|
126
|
+
const itemWidth = rowHeight * aspect;
|
|
127
|
+
items[itemIndex] = {
|
|
128
|
+
index: itemIndex,
|
|
129
|
+
width: itemWidth,
|
|
130
|
+
height: rowHeight,
|
|
131
|
+
left: currentLeft,
|
|
132
|
+
top: currentTop,
|
|
133
|
+
row: rowIndex,
|
|
134
|
+
widthFactor: aspect
|
|
135
|
+
};
|
|
136
|
+
currentLeft += itemWidth + gap;
|
|
137
|
+
}
|
|
138
|
+
currentTop += rowHeight + gap;
|
|
139
|
+
}
|
|
140
|
+
return items;
|
|
141
|
+
}
|
|
142
|
+
function calculateFlexStrip(options) {
|
|
143
|
+
const { dimensions, count, aspectRatio, gap, itemAspectRatios, direction, offset = { left: 0, top: 0 } } = options;
|
|
144
|
+
if (count === 0 || dimensions.width === 0 || dimensions.height === 0) {
|
|
145
|
+
return [];
|
|
146
|
+
}
|
|
147
|
+
const stripWidth = dimensions.width;
|
|
148
|
+
const stripHeight = dimensions.height;
|
|
149
|
+
const getItemAspectValue = (index) => {
|
|
150
|
+
const itemRatio = itemAspectRatios?.[index] ?? aspectRatio;
|
|
151
|
+
if (!itemRatio || itemRatio === "fill" || itemRatio === "auto") {
|
|
152
|
+
return 16 / 9;
|
|
153
|
+
}
|
|
154
|
+
const parsed = parseAspectRatio(itemRatio);
|
|
155
|
+
return parsed ? parsed.widthRatio / parsed.heightRatio : 16 / 9;
|
|
156
|
+
};
|
|
157
|
+
const aspectValues = [];
|
|
158
|
+
for (let i = 0; i < count; i++) {
|
|
159
|
+
aspectValues.push(getItemAspectValue(i));
|
|
160
|
+
}
|
|
161
|
+
const items = [];
|
|
162
|
+
if (direction === "horizontal") {
|
|
163
|
+
const availableHeight = stripHeight;
|
|
164
|
+
const totalGaps = (count - 1) * gap;
|
|
165
|
+
let totalNaturalWidth = 0;
|
|
166
|
+
for (let i = 0; i < count; i++) {
|
|
167
|
+
totalNaturalWidth += availableHeight * aspectValues[i];
|
|
168
|
+
}
|
|
169
|
+
totalNaturalWidth += totalGaps;
|
|
170
|
+
let scale = (stripWidth - totalGaps) / (totalNaturalWidth - totalGaps);
|
|
171
|
+
scale = Math.min(scale, 2);
|
|
172
|
+
const itemHeight = availableHeight * scale;
|
|
173
|
+
let scaledTotalWidth = totalGaps;
|
|
174
|
+
for (let i = 0; i < count; i++) {
|
|
175
|
+
scaledTotalWidth += itemHeight * aspectValues[i];
|
|
176
|
+
}
|
|
177
|
+
let currentLeft = offset.left + (stripWidth - scaledTotalWidth) / 2;
|
|
178
|
+
const itemTop = offset.top + (stripHeight - itemHeight) / 2;
|
|
179
|
+
for (let i = 0; i < count; i++) {
|
|
180
|
+
const itemWidth = itemHeight * aspectValues[i];
|
|
181
|
+
items[i] = {
|
|
182
|
+
index: i,
|
|
183
|
+
width: itemWidth,
|
|
184
|
+
height: itemHeight,
|
|
185
|
+
left: currentLeft,
|
|
186
|
+
top: itemTop,
|
|
187
|
+
row: 0,
|
|
188
|
+
widthFactor: aspectValues[i]
|
|
189
|
+
};
|
|
190
|
+
currentLeft += itemWidth + gap;
|
|
191
|
+
}
|
|
192
|
+
} else {
|
|
193
|
+
const availableWidth = stripWidth;
|
|
194
|
+
const totalGaps = (count - 1) * gap;
|
|
195
|
+
let totalNaturalHeight = 0;
|
|
196
|
+
for (let i = 0; i < count; i++) {
|
|
197
|
+
totalNaturalHeight += availableWidth / aspectValues[i];
|
|
198
|
+
}
|
|
199
|
+
totalNaturalHeight += totalGaps;
|
|
200
|
+
let scale = (stripHeight - totalGaps) / (totalNaturalHeight - totalGaps);
|
|
201
|
+
scale = Math.min(scale, 2);
|
|
202
|
+
const itemWidth = availableWidth * scale;
|
|
203
|
+
let scaledTotalHeight = totalGaps;
|
|
204
|
+
for (let i = 0; i < count; i++) {
|
|
205
|
+
scaledTotalHeight += itemWidth / aspectValues[i];
|
|
206
|
+
}
|
|
207
|
+
let currentTop = offset.top + (stripHeight - scaledTotalHeight) / 2;
|
|
208
|
+
const itemLeft = offset.left + (stripWidth - itemWidth) / 2;
|
|
209
|
+
for (let i = 0; i < count; i++) {
|
|
210
|
+
const itemHeight = itemWidth / aspectValues[i];
|
|
211
|
+
items[i] = {
|
|
212
|
+
index: i,
|
|
213
|
+
width: itemWidth,
|
|
214
|
+
height: itemHeight,
|
|
215
|
+
left: itemLeft,
|
|
216
|
+
top: currentTop,
|
|
217
|
+
row: i,
|
|
218
|
+
widthFactor: aspectValues[i]
|
|
219
|
+
};
|
|
220
|
+
currentTop += itemHeight + gap;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return items;
|
|
224
|
+
}
|
|
19
225
|
function getGridItemDimensions({
|
|
20
226
|
count,
|
|
21
227
|
dimensions,
|
|
@@ -35,8 +241,7 @@ function getGridItemDimensions({
|
|
|
35
241
|
let h = 0;
|
|
36
242
|
let a = 1;
|
|
37
243
|
let b = 1;
|
|
38
|
-
const
|
|
39
|
-
const minCols = N >= 2 && isPortrait ? 2 : 1;
|
|
244
|
+
const minCols = 1;
|
|
40
245
|
const widths = [];
|
|
41
246
|
for (let n = 1; n <= N; n++) {
|
|
42
247
|
widths.push((W - s * (n - 1)) / n, (H - s * (n - 1)) / (n * r));
|
|
@@ -129,20 +334,30 @@ function createGrid({ aspectRatio, count, dimensions, gap }) {
|
|
|
129
334
|
};
|
|
130
335
|
}
|
|
131
336
|
function createSidebarGrid(options) {
|
|
132
|
-
const { dimensions, gap, aspectRatio, count, sidebarPosition = "right", sidebarRatio = 0.25, pinnedIndex = 0,
|
|
337
|
+
const { dimensions, gap, aspectRatio, count, sidebarPosition = "right", sidebarRatio = 0.25, pinnedIndex = 0, maxVisible = 0, currentVisiblePage = 0, flexLayout = false, itemAspectRatios } = options;
|
|
133
338
|
if (count === 0) {
|
|
134
339
|
return createEmptyMeetGridResult("sidebar");
|
|
135
340
|
}
|
|
136
341
|
if (count === 1) {
|
|
137
|
-
const
|
|
342
|
+
const { width: W2, height: H2 } = dimensions;
|
|
343
|
+
const mainWidth2 = W2 - gap * 2;
|
|
344
|
+
const mainHeight2 = H2 - gap * 2;
|
|
345
|
+
const getItemDimensions2 = () => ({ width: mainWidth2, height: mainHeight2 });
|
|
138
346
|
const pagination2 = createDefaultPagination(1);
|
|
139
347
|
return {
|
|
140
|
-
|
|
348
|
+
width: mainWidth2,
|
|
349
|
+
height: mainHeight2,
|
|
350
|
+
rows: 1,
|
|
351
|
+
cols: 1,
|
|
141
352
|
layoutMode: "sidebar",
|
|
142
|
-
|
|
353
|
+
getPosition: () => ({ top: gap, left: gap }),
|
|
354
|
+
getItemDimensions: getItemDimensions2,
|
|
143
355
|
isMainItem: () => true,
|
|
144
356
|
pagination: pagination2,
|
|
145
|
-
isItemVisible: () => true
|
|
357
|
+
isItemVisible: () => true,
|
|
358
|
+
hiddenCount: 0,
|
|
359
|
+
getLastVisibleOthersIndex: () => -1,
|
|
360
|
+
getItemContentDimensions: createGetItemContentDimensions(getItemDimensions2, options.itemAspectRatios, aspectRatio)
|
|
146
361
|
};
|
|
147
362
|
}
|
|
148
363
|
const { width: W, height: H } = dimensions;
|
|
@@ -153,76 +368,84 @@ function createSidebarGrid(options) {
|
|
|
153
368
|
let sidebarWidth;
|
|
154
369
|
let sidebarHeight;
|
|
155
370
|
if (isVertical) {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
371
|
+
const totalOthersCalc = count - 1;
|
|
372
|
+
const visibleOthersCalc = maxVisible > 0 ? Math.min(maxVisible, totalOthersCalc) : totalOthersCalc;
|
|
373
|
+
const baseSidebarW = W - gap * 2;
|
|
374
|
+
const targetRatio = 1 / ratio;
|
|
375
|
+
const isPortraitMode = H > W * 1.2;
|
|
376
|
+
const maxSidebarRatio = isPortraitMode ? 0.45 : 0.38;
|
|
377
|
+
const minSidebarRatio = isPortraitMode ? 0.25 : 0.18;
|
|
378
|
+
const minThumbH = isPortraitMode ? 70 : 60;
|
|
379
|
+
let bestSidebarH = 0;
|
|
380
|
+
let bestThumbArea = 0;
|
|
381
|
+
const maxRows = Math.min(3, visibleOthersCalc || 1);
|
|
382
|
+
for (let rows = 1; rows <= maxRows; rows++) {
|
|
383
|
+
const cols = Math.ceil((visibleOthersCalc || 1) / rows);
|
|
384
|
+
const thumbW = (baseSidebarW - (cols - 1) * gap) / cols;
|
|
385
|
+
const thumbH = thumbW / targetRatio;
|
|
386
|
+
const requiredSidebarH = rows * thumbH + (rows - 1) * gap + gap * 2;
|
|
387
|
+
const sidebarRatioCalc = requiredSidebarH / H;
|
|
388
|
+
if (sidebarRatioCalc > maxSidebarRatio + 0.05 || thumbH < minThumbH)
|
|
389
|
+
continue;
|
|
390
|
+
const thumbArea = thumbW * thumbH;
|
|
391
|
+
if (thumbArea > bestThumbArea) {
|
|
392
|
+
bestThumbArea = thumbArea;
|
|
393
|
+
bestSidebarH = requiredSidebarH;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
if (bestSidebarH === 0) {
|
|
397
|
+
bestSidebarH = H * (isPortraitMode ? 0.3 : 0.25);
|
|
398
|
+
}
|
|
399
|
+
if (bestSidebarH / H < minSidebarRatio)
|
|
400
|
+
bestSidebarH = H * minSidebarRatio;
|
|
401
|
+
else if (bestSidebarH / H > maxSidebarRatio)
|
|
402
|
+
bestSidebarH = H * maxSidebarRatio;
|
|
403
|
+
sidebarHeight = bestSidebarH;
|
|
404
|
+
sidebarWidth = baseSidebarW;
|
|
405
|
+
mainHeight = H - sidebarHeight - gap * 3;
|
|
406
|
+
mainWidth = baseSidebarW;
|
|
160
407
|
} else {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
408
|
+
const totalOthersCalc = count - 1;
|
|
409
|
+
const visibleOthersCalc = maxVisible > 0 ? Math.min(maxVisible, totalOthersCalc) : totalOthersCalc;
|
|
410
|
+
const baseSidebarH = H - gap * 2;
|
|
411
|
+
let bestSidebarW = W * sidebarRatio - gap;
|
|
412
|
+
let bestScore = 0;
|
|
413
|
+
const maxCols = Math.min(3, visibleOthersCalc || 1);
|
|
414
|
+
for (let cols = 1; cols <= maxCols; cols++) {
|
|
415
|
+
const rows = Math.ceil((visibleOthersCalc || 1) / cols);
|
|
416
|
+
const thumbH = (baseSidebarH - (rows - 1) * gap) / rows;
|
|
417
|
+
const thumbW = thumbH / ratio;
|
|
418
|
+
const requiredSidebarW = cols * thumbW + (cols - 1) * gap + gap * 2;
|
|
419
|
+
const sidebarRatioTest = requiredSidebarW / W;
|
|
420
|
+
const thumbArea = thumbW * thumbH;
|
|
421
|
+
const mainAreaBonus = (1 - sidebarRatioTest) * 0.5;
|
|
422
|
+
const score = thumbArea * (1 + mainAreaBonus);
|
|
423
|
+
if (sidebarRatioTest >= 0.12 && sidebarRatioTest <= 0.4 && score > bestScore) {
|
|
424
|
+
bestSidebarW = requiredSidebarW;
|
|
425
|
+
bestScore = score;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
if (bestSidebarW / W < 0.12)
|
|
429
|
+
bestSidebarW = W * 0.15;
|
|
430
|
+
else if (bestSidebarW / W > 0.4)
|
|
431
|
+
bestSidebarW = W * 0.35;
|
|
432
|
+
sidebarWidth = bestSidebarW;
|
|
433
|
+
sidebarHeight = baseSidebarH;
|
|
434
|
+
mainWidth = W - sidebarWidth - gap * 2;
|
|
435
|
+
mainHeight = baseSidebarH;
|
|
171
436
|
}
|
|
437
|
+
const mainItemWidth = mainWidth;
|
|
438
|
+
const mainItemHeight = mainHeight;
|
|
172
439
|
const totalOthers = count - 1;
|
|
173
|
-
const visibleOthers =
|
|
174
|
-
const othersTotalPages =
|
|
175
|
-
const
|
|
176
|
-
const startOthersIndex =
|
|
440
|
+
const visibleOthers = maxVisible > 0 ? Math.min(maxVisible, totalOthers) : totalOthers;
|
|
441
|
+
const othersTotalPages = maxVisible > 0 ? Math.ceil(totalOthers / maxVisible) : 1;
|
|
442
|
+
const safeCurrentVisiblePage = Math.min(currentVisiblePage, Math.max(0, othersTotalPages - 1));
|
|
443
|
+
const startOthersIndex = safeCurrentVisiblePage * (maxVisible > 0 ? maxVisible : totalOthers);
|
|
177
444
|
const endOthersIndex = Math.min(startOthersIndex + visibleOthers, totalOthers);
|
|
178
445
|
const itemsOnPage = endOthersIndex - startOthersIndex;
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
let thumbWidth = 0;
|
|
183
|
-
let thumbHeight = 0;
|
|
184
|
-
if (visibleOthers > 0) {
|
|
185
|
-
let bestArea = 0;
|
|
186
|
-
if (isVertical) {
|
|
187
|
-
for (let cols = 1; cols <= visibleOthers; cols++) {
|
|
188
|
-
const rows = Math.ceil(visibleOthers / cols);
|
|
189
|
-
const maxTileW = (sidebarWidth - (cols - 1) * gap) / cols;
|
|
190
|
-
const maxTileH = (sidebarHeight - (rows - 1) * gap) / rows;
|
|
191
|
-
let tileW = maxTileW;
|
|
192
|
-
let tileH = tileW * ratio;
|
|
193
|
-
if (tileH > maxTileH) {
|
|
194
|
-
tileH = maxTileH;
|
|
195
|
-
tileW = tileH / ratio;
|
|
196
|
-
}
|
|
197
|
-
if (tileW * tileH > bestArea) {
|
|
198
|
-
bestArea = tileW * tileH;
|
|
199
|
-
thumbCols = cols;
|
|
200
|
-
thumbRows = rows;
|
|
201
|
-
thumbWidth = tileW;
|
|
202
|
-
thumbHeight = tileH;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
} else {
|
|
206
|
-
for (let rows = 1; rows <= visibleOthers; rows++) {
|
|
207
|
-
const cols = Math.ceil(visibleOthers / rows);
|
|
208
|
-
const maxTileH = (sidebarHeight - (rows - 1) * gap) / rows;
|
|
209
|
-
const maxTileW = (sidebarWidth - (cols - 1) * gap) / cols;
|
|
210
|
-
let tileH = maxTileH;
|
|
211
|
-
let tileW = tileH / ratio;
|
|
212
|
-
if (tileW > maxTileW) {
|
|
213
|
-
tileW = maxTileW;
|
|
214
|
-
tileH = tileW * ratio;
|
|
215
|
-
}
|
|
216
|
-
if (tileW * tileH > bestArea) {
|
|
217
|
-
bestArea = tileW * tileH;
|
|
218
|
-
thumbCols = cols;
|
|
219
|
-
thumbRows = rows;
|
|
220
|
-
thumbWidth = tileW;
|
|
221
|
-
thumbHeight = tileH;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
}
|
|
446
|
+
const isPaginationMode = othersTotalPages > 1;
|
|
447
|
+
const isActivelyPaginating = isPaginationMode && currentVisiblePage > 0;
|
|
448
|
+
const hiddenCount = isActivelyPaginating ? 0 : totalOthers > itemsOnPage ? totalOthers - itemsOnPage + 1 : 0;
|
|
226
449
|
const positions = [];
|
|
227
450
|
let mainLeft;
|
|
228
451
|
let mainTop;
|
|
@@ -237,68 +460,200 @@ function createSidebarGrid(options) {
|
|
|
237
460
|
position: { top: mainTop, left: mainLeft },
|
|
238
461
|
dimensions: { width: mainItemWidth, height: mainItemHeight }
|
|
239
462
|
};
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
let gridStartLeft;
|
|
243
|
-
let gridStartTop;
|
|
463
|
+
let sidebarOffsetLeft;
|
|
464
|
+
let sidebarOffsetTop;
|
|
244
465
|
if (isVertical) {
|
|
245
|
-
|
|
246
|
-
|
|
466
|
+
sidebarOffsetLeft = gap;
|
|
467
|
+
sidebarOffsetTop = sidebarPosition === "top" ? gap : mainHeight + gap * 2;
|
|
247
468
|
} else {
|
|
248
|
-
|
|
249
|
-
|
|
469
|
+
sidebarOffsetLeft = sidebarPosition === "left" ? gap : mainWidth + gap * 2;
|
|
470
|
+
sidebarOffsetTop = gap;
|
|
250
471
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
let rowLeft = gridStartLeft;
|
|
262
|
-
const lastRowIndex = Math.ceil(itemsOnPage / thumbCols) - 1;
|
|
263
|
-
if (row === lastRowIndex && itemsInLastRow < thumbCols) {
|
|
264
|
-
const rowWidth = itemsInLastRow * thumbWidth + (itemsInLastRow - 1) * gap;
|
|
265
|
-
if (isVertical) {
|
|
266
|
-
rowLeft = gap + (sidebarWidth - rowWidth) / 2;
|
|
267
|
-
} else {
|
|
268
|
-
rowLeft = (sidebarPosition === "left" ? gap : mainWidth + gap * 2) + (sidebarWidth - rowWidth) / 2;
|
|
269
|
-
}
|
|
472
|
+
if (flexLayout && itemAspectRatios && itemsOnPage > 0) {
|
|
473
|
+
const othersAspectRatios = [];
|
|
474
|
+
const originalIndices = [];
|
|
475
|
+
let othersIdx = 0;
|
|
476
|
+
for (let i = 0; i < count; i++) {
|
|
477
|
+
if (i === pinnedIndex)
|
|
478
|
+
continue;
|
|
479
|
+
if (othersIdx >= startOthersIndex && othersIdx < endOthersIndex) {
|
|
480
|
+
othersAspectRatios.push(itemAspectRatios[i]);
|
|
481
|
+
originalIndices.push(i);
|
|
270
482
|
}
|
|
271
|
-
|
|
483
|
+
othersIdx++;
|
|
484
|
+
}
|
|
485
|
+
const flexItems = calculateFlexLayout({
|
|
486
|
+
dimensions: { width: sidebarWidth + gap * 2, height: sidebarHeight + gap * 2 },
|
|
487
|
+
count: itemsOnPage,
|
|
488
|
+
aspectRatio,
|
|
489
|
+
gap,
|
|
490
|
+
itemAspectRatios: othersAspectRatios,
|
|
491
|
+
preferHorizontal: isVertical
|
|
492
|
+
// bottom/top sidebars should use horizontal layout
|
|
493
|
+
});
|
|
494
|
+
for (let i = 0; i < flexItems.length; i++) {
|
|
495
|
+
const originalIndex = originalIndices[i];
|
|
496
|
+
const flexItem = flexItems[i];
|
|
497
|
+
positions[originalIndex] = {
|
|
272
498
|
position: {
|
|
273
|
-
top:
|
|
274
|
-
left:
|
|
499
|
+
top: flexItem.top + sidebarOffsetTop - gap,
|
|
500
|
+
left: flexItem.left + sidebarOffsetLeft - gap
|
|
275
501
|
},
|
|
276
|
-
dimensions: { width:
|
|
502
|
+
dimensions: { width: flexItem.width, height: flexItem.height }
|
|
277
503
|
};
|
|
504
|
+
}
|
|
505
|
+
let sidebarIndex = 0;
|
|
506
|
+
for (let i = 0; i < count; i++) {
|
|
507
|
+
if (i === pinnedIndex)
|
|
508
|
+
continue;
|
|
509
|
+
const isInVisibleRange = sidebarIndex >= startOthersIndex && sidebarIndex < endOthersIndex;
|
|
510
|
+
if (!isInVisibleRange) {
|
|
511
|
+
positions[i] = {
|
|
512
|
+
position: { top: -9999, left: -9999 },
|
|
513
|
+
dimensions: { width: 0, height: 0 }
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
sidebarIndex++;
|
|
517
|
+
}
|
|
518
|
+
} else {
|
|
519
|
+
let thumbCols = 1;
|
|
520
|
+
let thumbRows = 1;
|
|
521
|
+
let thumbWidth = 0;
|
|
522
|
+
let thumbHeight = 0;
|
|
523
|
+
if (visibleOthers > 0) {
|
|
524
|
+
if (isVertical) {
|
|
525
|
+
let bestScore = -1;
|
|
526
|
+
for (let cols = 1; cols <= visibleOthers; cols++) {
|
|
527
|
+
const rows = Math.ceil(visibleOthers / cols);
|
|
528
|
+
const maxTileW = (sidebarWidth - (cols - 1) * gap) / cols;
|
|
529
|
+
const maxTileH = (sidebarHeight - (rows - 1) * gap) / rows;
|
|
530
|
+
let tileW = maxTileW;
|
|
531
|
+
let tileH = tileW * ratio;
|
|
532
|
+
if (tileH > maxTileH) {
|
|
533
|
+
tileH = maxTileH;
|
|
534
|
+
tileW = tileH / ratio;
|
|
535
|
+
}
|
|
536
|
+
const area = tileW * tileH;
|
|
537
|
+
const itemRatio = tileW / tileH;
|
|
538
|
+
const idealRatio = 16 / 9;
|
|
539
|
+
const ratioScore = 1 / (1 + Math.abs(Math.log(itemRatio / idealRatio)));
|
|
540
|
+
const colsMultiplier = cols >= rows ? 1.5 : 0.5;
|
|
541
|
+
const score = area * ratioScore * colsMultiplier;
|
|
542
|
+
if (score > bestScore) {
|
|
543
|
+
bestScore = score;
|
|
544
|
+
thumbCols = cols;
|
|
545
|
+
thumbRows = rows;
|
|
546
|
+
thumbWidth = tileW;
|
|
547
|
+
thumbHeight = tileH;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
} else {
|
|
551
|
+
let bestScore = -1;
|
|
552
|
+
const targetRatio = 1 / ratio;
|
|
553
|
+
for (let rows = 1; rows <= visibleOthers; rows++) {
|
|
554
|
+
const cols = Math.ceil(visibleOthers / rows);
|
|
555
|
+
const maxTileH = (sidebarHeight - (rows - 1) * gap) / rows;
|
|
556
|
+
const idealTileW = maxTileH * targetRatio;
|
|
557
|
+
const maxTileW = (sidebarWidth - (cols - 1) * gap) / cols;
|
|
558
|
+
let tileW, tileH;
|
|
559
|
+
if (idealTileW <= maxTileW) {
|
|
560
|
+
tileW = idealTileW;
|
|
561
|
+
tileH = maxTileH;
|
|
562
|
+
} else {
|
|
563
|
+
tileW = maxTileW;
|
|
564
|
+
tileH = tileW / targetRatio;
|
|
565
|
+
}
|
|
566
|
+
const area = tileW * tileH * visibleOthers;
|
|
567
|
+
const score = area;
|
|
568
|
+
if (score > bestScore) {
|
|
569
|
+
bestScore = score;
|
|
570
|
+
thumbCols = cols;
|
|
571
|
+
thumbRows = rows;
|
|
572
|
+
thumbWidth = tileW;
|
|
573
|
+
thumbHeight = tileH;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
const totalGridWidth = thumbCols * thumbWidth + (thumbCols - 1) * gap;
|
|
579
|
+
const totalGridHeight = thumbRows * thumbHeight + (thumbRows - 1) * gap;
|
|
580
|
+
let gridStartLeft;
|
|
581
|
+
let gridStartTop;
|
|
582
|
+
if (isVertical) {
|
|
583
|
+
gridStartLeft = gap + (sidebarWidth - totalGridWidth) / 2;
|
|
584
|
+
gridStartTop = sidebarPosition === "top" ? gap + (sidebarHeight - totalGridHeight) / 2 : mainHeight + gap * 2 + (sidebarHeight - totalGridHeight) / 2;
|
|
278
585
|
} else {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
586
|
+
gridStartLeft = sidebarPosition === "left" ? gap + (sidebarWidth - totalGridWidth) / 2 : mainWidth + gap * 2 + (sidebarWidth - totalGridWidth) / 2;
|
|
587
|
+
gridStartTop = gap + (sidebarHeight - totalGridHeight) / 2;
|
|
588
|
+
}
|
|
589
|
+
let sidebarIndex = 0;
|
|
590
|
+
for (let i = 0; i < count; i++) {
|
|
591
|
+
if (i === pinnedIndex)
|
|
592
|
+
continue;
|
|
593
|
+
const isInVisibleRange = sidebarIndex >= startOthersIndex && sidebarIndex < endOthersIndex;
|
|
594
|
+
if (isInVisibleRange) {
|
|
595
|
+
const pageRelativeIndex = sidebarIndex - startOthersIndex;
|
|
596
|
+
const row = Math.floor(pageRelativeIndex / thumbCols);
|
|
597
|
+
const col = pageRelativeIndex % thumbCols;
|
|
598
|
+
const itemsInLastRow = itemsOnPage % thumbCols || thumbCols;
|
|
599
|
+
let rowLeft = gridStartLeft;
|
|
600
|
+
const lastRowIndex = Math.ceil(itemsOnPage / thumbCols) - 1;
|
|
601
|
+
if (row === lastRowIndex && itemsInLastRow < thumbCols) {
|
|
602
|
+
const rowWidth = itemsInLastRow * thumbWidth + (itemsInLastRow - 1) * gap;
|
|
603
|
+
if (isVertical) {
|
|
604
|
+
rowLeft = gap + (sidebarWidth - rowWidth) / 2;
|
|
605
|
+
} else {
|
|
606
|
+
rowLeft = (sidebarPosition === "left" ? gap : mainWidth + gap * 2) + (sidebarWidth - rowWidth) / 2;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
positions[i] = {
|
|
610
|
+
position: {
|
|
611
|
+
top: gridStartTop + row * (thumbHeight + gap),
|
|
612
|
+
left: rowLeft + col * (thumbWidth + gap)
|
|
613
|
+
},
|
|
614
|
+
dimensions: { width: thumbWidth, height: thumbHeight }
|
|
615
|
+
};
|
|
616
|
+
} else {
|
|
617
|
+
positions[i] = {
|
|
618
|
+
position: { top: -9999, left: -9999 },
|
|
619
|
+
dimensions: { width: 0, height: 0 }
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
sidebarIndex++;
|
|
283
623
|
}
|
|
284
|
-
sidebarIndex++;
|
|
285
624
|
}
|
|
286
625
|
const pagination = {
|
|
287
|
-
enabled:
|
|
288
|
-
currentPage:
|
|
626
|
+
enabled: maxVisible > 0 && totalOthers > maxVisible,
|
|
627
|
+
currentPage: safeCurrentVisiblePage,
|
|
289
628
|
totalPages: othersTotalPages,
|
|
290
629
|
itemsOnPage,
|
|
291
630
|
startIndex: startOthersIndex,
|
|
292
631
|
endIndex: endOthersIndex
|
|
293
632
|
};
|
|
633
|
+
const getItemDimensions = (index) => positions[index]?.dimensions ?? { width: 0, height: 0 };
|
|
634
|
+
const getLastVisibleOthersIndex = () => {
|
|
635
|
+
if (itemsOnPage === 0)
|
|
636
|
+
return -1;
|
|
637
|
+
let othersIdx = 0;
|
|
638
|
+
let lastVisibleOriginalIdx = -1;
|
|
639
|
+
for (let i = 0; i < count; i++) {
|
|
640
|
+
if (i === pinnedIndex)
|
|
641
|
+
continue;
|
|
642
|
+
if (othersIdx >= startOthersIndex && othersIdx < endOthersIndex) {
|
|
643
|
+
lastVisibleOriginalIdx = i;
|
|
644
|
+
}
|
|
645
|
+
othersIdx++;
|
|
646
|
+
}
|
|
647
|
+
return lastVisibleOriginalIdx;
|
|
648
|
+
};
|
|
294
649
|
return {
|
|
295
650
|
width: mainItemWidth,
|
|
296
651
|
height: mainItemHeight,
|
|
297
|
-
rows: isVertical ?
|
|
298
|
-
cols: isVertical ?
|
|
652
|
+
rows: isVertical ? 2 : 1,
|
|
653
|
+
cols: isVertical ? 1 : 2,
|
|
299
654
|
layoutMode: "sidebar",
|
|
300
655
|
getPosition: (index) => positions[index]?.position ?? { top: 0, left: 0 },
|
|
301
|
-
getItemDimensions
|
|
656
|
+
getItemDimensions,
|
|
302
657
|
isMainItem: (index) => index === pinnedIndex,
|
|
303
658
|
pagination,
|
|
304
659
|
isItemVisible: (index) => {
|
|
@@ -311,160 +666,36 @@ function createSidebarGrid(options) {
|
|
|
311
666
|
}
|
|
312
667
|
return sIdx >= startOthersIndex && sIdx < endOthersIndex;
|
|
313
668
|
},
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
}
|
|
318
|
-
function createSpeakerGrid(options) {
|
|
319
|
-
const { dimensions, gap, aspectRatio, count, speakerIndex = 0, maxVisibleOthers = 0, currentOthersPage = 0 } = options;
|
|
320
|
-
if (count === 0) {
|
|
321
|
-
return createEmptyMeetGridResult("speaker");
|
|
322
|
-
}
|
|
323
|
-
if (count === 1) {
|
|
324
|
-
const grid = createGrid({ ...options, count: 1 });
|
|
325
|
-
const pagination2 = createDefaultPagination(1);
|
|
326
|
-
return {
|
|
327
|
-
...grid,
|
|
328
|
-
layoutMode: "speaker",
|
|
329
|
-
getItemDimensions: () => ({ width: grid.width, height: grid.height }),
|
|
330
|
-
isMainItem: () => true,
|
|
331
|
-
pagination: pagination2,
|
|
332
|
-
isItemVisible: () => true
|
|
333
|
-
};
|
|
334
|
-
}
|
|
335
|
-
const { width: W, height: H } = dimensions;
|
|
336
|
-
const ratio = getAspectRatio(aspectRatio);
|
|
337
|
-
const totalOthers = count - 1;
|
|
338
|
-
const visibleOthers = maxVisibleOthers > 0 ? Math.min(maxVisibleOthers, totalOthers) : totalOthers;
|
|
339
|
-
const othersTotalPages = maxVisibleOthers > 0 ? Math.ceil(totalOthers / maxVisibleOthers) : 1;
|
|
340
|
-
const safeCurrentOthersPage = Math.min(currentOthersPage, Math.max(0, othersTotalPages - 1));
|
|
341
|
-
const startOthersIndex = safeCurrentOthersPage * maxVisibleOthers;
|
|
342
|
-
const endOthersIndex = Math.min(startOthersIndex + visibleOthers, totalOthers);
|
|
343
|
-
const itemsOnPage = endOthersIndex - startOthersIndex;
|
|
344
|
-
const hiddenCount = totalOthers - itemsOnPage;
|
|
345
|
-
const speakerAreaHeight = (H - gap * 3) * 0.65;
|
|
346
|
-
const othersAreaHeight = (H - gap * 3) * 0.35;
|
|
347
|
-
const othersAreaWidth = W - gap * 2;
|
|
348
|
-
let speakerW = W - gap * 2;
|
|
349
|
-
let speakerH = speakerW * ratio;
|
|
350
|
-
if (speakerH > speakerAreaHeight) {
|
|
351
|
-
speakerH = speakerAreaHeight;
|
|
352
|
-
speakerW = speakerH / ratio;
|
|
353
|
-
}
|
|
354
|
-
let bestCols = 1;
|
|
355
|
-
let bestTileW = 0;
|
|
356
|
-
let bestTileH = 0;
|
|
357
|
-
if (visibleOthers > 0) {
|
|
358
|
-
for (let cols = 1; cols <= visibleOthers; cols++) {
|
|
359
|
-
const rows = Math.ceil(visibleOthers / cols);
|
|
360
|
-
const maxTileW = (othersAreaWidth - (cols - 1) * gap) / cols;
|
|
361
|
-
const maxTileH = (othersAreaHeight - (rows - 1) * gap) / rows;
|
|
362
|
-
let tileW = maxTileW;
|
|
363
|
-
let tileH = tileW * ratio;
|
|
364
|
-
if (tileH > maxTileH) {
|
|
365
|
-
tileH = maxTileH;
|
|
366
|
-
tileW = tileH / ratio;
|
|
367
|
-
}
|
|
368
|
-
if (tileW * tileH > bestTileW * bestTileH) {
|
|
369
|
-
bestCols = cols;
|
|
370
|
-
bestTileW = tileW;
|
|
371
|
-
bestTileH = tileH;
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
const otherCols = bestCols;
|
|
376
|
-
const otherRows = Math.ceil(visibleOthers / otherCols) || 1;
|
|
377
|
-
const otherW = bestTileW;
|
|
378
|
-
const otherH = bestTileH;
|
|
379
|
-
const positions = [];
|
|
380
|
-
positions[speakerIndex] = {
|
|
381
|
-
position: {
|
|
382
|
-
top: gap + (speakerAreaHeight - speakerH) / 2,
|
|
383
|
-
left: gap + (W - gap * 2 - speakerW) / 2
|
|
384
|
-
},
|
|
385
|
-
dimensions: { width: speakerW, height: speakerH }
|
|
386
|
-
};
|
|
387
|
-
const totalGridWidth = otherCols * otherW + (otherCols - 1) * gap;
|
|
388
|
-
const totalGridHeight = otherRows * otherH + (otherRows - 1) * gap;
|
|
389
|
-
const gridStartLeft = gap + (othersAreaWidth - totalGridWidth) / 2;
|
|
390
|
-
const gridStartTop = speakerAreaHeight + gap * 2 + (othersAreaHeight - totalGridHeight) / 2;
|
|
391
|
-
let otherIndex = 0;
|
|
392
|
-
for (let i = 0; i < count; i++) {
|
|
393
|
-
if (i === speakerIndex)
|
|
394
|
-
continue;
|
|
395
|
-
const isInVisibleRange = otherIndex >= startOthersIndex && otherIndex < endOthersIndex;
|
|
396
|
-
if (isInVisibleRange) {
|
|
397
|
-
const pageRelativeIndex = otherIndex - startOthersIndex;
|
|
398
|
-
const row = Math.floor(pageRelativeIndex / otherCols);
|
|
399
|
-
const col = pageRelativeIndex % otherCols;
|
|
400
|
-
const lastRowIndex = Math.ceil(itemsOnPage / otherCols) - 1;
|
|
401
|
-
const itemsInLastRow = itemsOnPage % otherCols || otherCols;
|
|
402
|
-
let rowStartLeft = gridStartLeft;
|
|
403
|
-
if (row === lastRowIndex && itemsInLastRow < otherCols) {
|
|
404
|
-
const rowWidth = itemsInLastRow * otherW + (itemsInLastRow - 1) * gap;
|
|
405
|
-
rowStartLeft = gap + (othersAreaWidth - rowWidth) / 2;
|
|
406
|
-
}
|
|
407
|
-
positions[i] = {
|
|
408
|
-
position: {
|
|
409
|
-
top: gridStartTop + row * (otherH + gap),
|
|
410
|
-
left: rowStartLeft + col * (otherW + gap)
|
|
411
|
-
},
|
|
412
|
-
dimensions: { width: otherW, height: otherH }
|
|
413
|
-
};
|
|
414
|
-
} else {
|
|
415
|
-
positions[i] = {
|
|
416
|
-
position: { top: -9999, left: -9999 },
|
|
417
|
-
dimensions: { width: 0, height: 0 }
|
|
418
|
-
};
|
|
419
|
-
}
|
|
420
|
-
otherIndex++;
|
|
421
|
-
}
|
|
422
|
-
const pagination = {
|
|
423
|
-
enabled: maxVisibleOthers > 0 && totalOthers > maxVisibleOthers,
|
|
424
|
-
currentPage: safeCurrentOthersPage,
|
|
425
|
-
totalPages: othersTotalPages,
|
|
426
|
-
itemsOnPage,
|
|
427
|
-
startIndex: startOthersIndex,
|
|
428
|
-
endIndex: endOthersIndex
|
|
429
|
-
};
|
|
430
|
-
return {
|
|
431
|
-
width: speakerW,
|
|
432
|
-
height: speakerH,
|
|
433
|
-
rows: 1 + otherRows,
|
|
434
|
-
cols: otherCols,
|
|
435
|
-
layoutMode: "speaker",
|
|
436
|
-
getPosition: (index) => positions[index]?.position ?? { top: 0, left: 0 },
|
|
437
|
-
getItemDimensions: (index) => positions[index]?.dimensions ?? { width: 0, height: 0 },
|
|
438
|
-
isMainItem: (index) => index === speakerIndex,
|
|
439
|
-
pagination,
|
|
440
|
-
isItemVisible: (index) => {
|
|
441
|
-
if (index === speakerIndex)
|
|
442
|
-
return true;
|
|
443
|
-
let sIdx = 0;
|
|
444
|
-
for (let i = 0; i < index; i++) {
|
|
445
|
-
if (i !== speakerIndex)
|
|
446
|
-
sIdx++;
|
|
447
|
-
}
|
|
448
|
-
return sIdx >= startOthersIndex && sIdx < endOthersIndex;
|
|
449
|
-
},
|
|
450
|
-
hiddenCount
|
|
669
|
+
hiddenCount,
|
|
670
|
+
getLastVisibleOthersIndex,
|
|
671
|
+
getItemContentDimensions: createGetItemContentDimensions(getItemDimensions, options.itemAspectRatios, aspectRatio)
|
|
451
672
|
};
|
|
452
673
|
}
|
|
453
674
|
function createSpotlightGrid(options) {
|
|
454
|
-
const { dimensions, gap, aspectRatio, pinnedIndex = 0 } = options;
|
|
675
|
+
const { dimensions, gap, aspectRatio, pinnedIndex = 0, flexLayout = false, itemAspectRatios } = options;
|
|
455
676
|
const { width: W, height: H } = dimensions;
|
|
456
|
-
const
|
|
457
|
-
|
|
458
|
-
let
|
|
459
|
-
|
|
677
|
+
const itemRatio = flexLayout && itemAspectRatios?.[pinnedIndex];
|
|
678
|
+
const shouldFill = itemRatio === "fill" || itemRatio === "auto";
|
|
679
|
+
let spotWidth;
|
|
680
|
+
let spotHeight;
|
|
681
|
+
if (shouldFill) {
|
|
682
|
+
spotWidth = W - gap * 2;
|
|
460
683
|
spotHeight = H - gap * 2;
|
|
461
|
-
|
|
684
|
+
} else {
|
|
685
|
+
const ratio = itemRatio ? getAspectRatio(itemRatio) : getAspectRatio(aspectRatio);
|
|
686
|
+
spotWidth = W - gap * 2;
|
|
687
|
+
spotHeight = spotWidth * ratio;
|
|
688
|
+
if (spotHeight > H - gap * 2) {
|
|
689
|
+
spotHeight = H - gap * 2;
|
|
690
|
+
spotWidth = spotHeight / ratio;
|
|
691
|
+
}
|
|
462
692
|
}
|
|
463
693
|
const position = {
|
|
464
694
|
top: gap + (H - gap * 2 - spotHeight) / 2,
|
|
465
695
|
left: gap + (W - gap * 2 - spotWidth) / 2
|
|
466
696
|
};
|
|
467
697
|
const pagination = createDefaultPagination(1);
|
|
698
|
+
const getItemDimensions = (index) => index === pinnedIndex ? { width: spotWidth, height: spotHeight } : { width: 0, height: 0 };
|
|
468
699
|
return {
|
|
469
700
|
width: spotWidth,
|
|
470
701
|
height: spotHeight,
|
|
@@ -472,10 +703,13 @@ function createSpotlightGrid(options) {
|
|
|
472
703
|
cols: 1,
|
|
473
704
|
layoutMode: "spotlight",
|
|
474
705
|
getPosition: (index) => index === pinnedIndex ? position : { top: -9999, left: -9999 },
|
|
475
|
-
getItemDimensions
|
|
706
|
+
getItemDimensions,
|
|
476
707
|
isMainItem: (index) => index === pinnedIndex,
|
|
477
708
|
pagination,
|
|
478
|
-
isItemVisible: (index) => index === pinnedIndex
|
|
709
|
+
isItemVisible: (index) => index === pinnedIndex,
|
|
710
|
+
hiddenCount: 0,
|
|
711
|
+
getLastVisibleOthersIndex: () => -1,
|
|
712
|
+
getItemContentDimensions: createGetItemContentDimensions(getItemDimensions, options.itemAspectRatios, aspectRatio)
|
|
479
713
|
};
|
|
480
714
|
}
|
|
481
715
|
function createDefaultPagination(count) {
|
|
@@ -506,6 +740,7 @@ function createPaginationInfo(count, maxItemsPerPage, currentPage) {
|
|
|
506
740
|
};
|
|
507
741
|
}
|
|
508
742
|
function createEmptyMeetGridResult(layoutMode) {
|
|
743
|
+
const getItemDimensions = () => ({ width: 0, height: 0 });
|
|
509
744
|
return {
|
|
510
745
|
width: 0,
|
|
511
746
|
height: 0,
|
|
@@ -513,51 +748,129 @@ function createEmptyMeetGridResult(layoutMode) {
|
|
|
513
748
|
cols: 0,
|
|
514
749
|
layoutMode,
|
|
515
750
|
getPosition: () => ({ top: 0, left: 0 }),
|
|
516
|
-
getItemDimensions
|
|
751
|
+
getItemDimensions,
|
|
517
752
|
isMainItem: () => false,
|
|
518
753
|
pagination: createDefaultPagination(0),
|
|
519
|
-
isItemVisible: () => false
|
|
754
|
+
isItemVisible: () => false,
|
|
755
|
+
hiddenCount: 0,
|
|
756
|
+
getLastVisibleOthersIndex: () => -1,
|
|
757
|
+
getItemContentDimensions: () => ({ width: 0, height: 0, offsetTop: 0, offsetLeft: 0 })
|
|
520
758
|
};
|
|
521
759
|
}
|
|
522
760
|
function createMeetGrid(options) {
|
|
523
|
-
const { layoutMode = "gallery", count } = options;
|
|
761
|
+
const { layoutMode = "gallery", count, flexLayout = false } = options;
|
|
524
762
|
if (count === 0) {
|
|
525
763
|
return createEmptyMeetGridResult(layoutMode);
|
|
526
764
|
}
|
|
527
765
|
switch (layoutMode) {
|
|
528
766
|
case "spotlight":
|
|
529
767
|
return createSpotlightGrid(options);
|
|
530
|
-
case "speaker":
|
|
531
|
-
return createSpeakerGrid(options);
|
|
532
768
|
case "sidebar":
|
|
533
769
|
return createSidebarGrid(options);
|
|
534
770
|
case "gallery":
|
|
535
771
|
default: {
|
|
536
|
-
const { maxItemsPerPage, currentPage, pinnedIndex, sidebarPosition = "right" } = options;
|
|
772
|
+
const { maxItemsPerPage, currentPage, pinnedIndex, sidebarPosition = "right", dimensions, maxVisible = 0 } = options;
|
|
537
773
|
if (pinnedIndex !== void 0 && pinnedIndex >= 0 && pinnedIndex < count) {
|
|
538
|
-
|
|
774
|
+
const isPortrait = dimensions.width < dimensions.height;
|
|
775
|
+
const effectiveSidebarPosition = isPortrait ? "bottom" : sidebarPosition;
|
|
776
|
+
return createSidebarGrid({ ...options, sidebarPosition: effectiveSidebarPosition });
|
|
777
|
+
}
|
|
778
|
+
let visibleCount = count;
|
|
779
|
+
let hiddenCount = 0;
|
|
780
|
+
let startIndex = 0;
|
|
781
|
+
let endIndex = count;
|
|
782
|
+
if (maxItemsPerPage && maxItemsPerPage > 0) {
|
|
783
|
+
const pagination2 = createPaginationInfo(count, maxItemsPerPage, currentPage);
|
|
784
|
+
visibleCount = pagination2.itemsOnPage;
|
|
785
|
+
startIndex = pagination2.startIndex;
|
|
786
|
+
endIndex = pagination2.endIndex;
|
|
787
|
+
} else if (maxVisible > 0 && count > maxVisible) {
|
|
788
|
+
visibleCount = maxVisible;
|
|
789
|
+
hiddenCount = count - maxVisible + 1;
|
|
790
|
+
startIndex = 0;
|
|
791
|
+
endIndex = maxVisible;
|
|
792
|
+
}
|
|
793
|
+
const pagination = maxItemsPerPage && maxItemsPerPage > 0 ? createPaginationInfo(count, maxItemsPerPage, currentPage) : {
|
|
794
|
+
enabled: false,
|
|
795
|
+
currentPage: 0,
|
|
796
|
+
totalPages: 1,
|
|
797
|
+
itemsOnPage: visibleCount,
|
|
798
|
+
startIndex,
|
|
799
|
+
endIndex
|
|
800
|
+
};
|
|
801
|
+
const effectiveCount = visibleCount;
|
|
802
|
+
if (flexLayout && options.itemAspectRatios) {
|
|
803
|
+
const pageItemRatios = options.itemAspectRatios.slice(startIndex, startIndex + effectiveCount);
|
|
804
|
+
const flexItems = calculateFlexLayout({
|
|
805
|
+
dimensions: options.dimensions,
|
|
806
|
+
count: effectiveCount,
|
|
807
|
+
aspectRatio: options.aspectRatio,
|
|
808
|
+
gap: options.gap,
|
|
809
|
+
itemAspectRatios: pageItemRatios
|
|
810
|
+
});
|
|
811
|
+
const getPosition2 = (index) => {
|
|
812
|
+
const relativeIndex = index - startIndex;
|
|
813
|
+
if (relativeIndex < 0 || relativeIndex >= effectiveCount) {
|
|
814
|
+
return { top: -9999, left: -9999 };
|
|
815
|
+
}
|
|
816
|
+
const item = flexItems[relativeIndex];
|
|
817
|
+
return item ? { top: item.top, left: item.left } : { top: -9999, left: -9999 };
|
|
818
|
+
};
|
|
819
|
+
const getItemDimensions2 = (index) => {
|
|
820
|
+
const relativeIndex = index - startIndex;
|
|
821
|
+
if (relativeIndex < 0 || relativeIndex >= effectiveCount) {
|
|
822
|
+
return { width: 0, height: 0 };
|
|
823
|
+
}
|
|
824
|
+
const item = flexItems[relativeIndex];
|
|
825
|
+
return item ? { width: item.width, height: item.height } : { width: 0, height: 0 };
|
|
826
|
+
};
|
|
827
|
+
const getItemContentDimensions = (index) => {
|
|
828
|
+
const dims = getItemDimensions2(index);
|
|
829
|
+
return {
|
|
830
|
+
width: dims.width,
|
|
831
|
+
height: dims.height,
|
|
832
|
+
offsetTop: 0,
|
|
833
|
+
offsetLeft: 0
|
|
834
|
+
};
|
|
835
|
+
};
|
|
836
|
+
const lastVisibleIndex2 = endIndex - 1;
|
|
837
|
+
return {
|
|
838
|
+
width: flexItems[0]?.width ?? 0,
|
|
839
|
+
height: flexItems[0]?.height ?? 0,
|
|
840
|
+
rows: Math.max(...flexItems.map((i) => i.row)) + 1,
|
|
841
|
+
cols: flexItems.filter((i) => i.row === 0).length,
|
|
842
|
+
layoutMode: "gallery",
|
|
843
|
+
getPosition: getPosition2,
|
|
844
|
+
getItemDimensions: getItemDimensions2,
|
|
845
|
+
isMainItem: () => false,
|
|
846
|
+
pagination,
|
|
847
|
+
isItemVisible: (index) => index >= startIndex && index < endIndex,
|
|
848
|
+
hiddenCount,
|
|
849
|
+
getLastVisibleOthersIndex: () => hiddenCount > 0 ? lastVisibleIndex2 : -1,
|
|
850
|
+
getItemContentDimensions
|
|
851
|
+
};
|
|
539
852
|
}
|
|
540
|
-
const pagination = createPaginationInfo(count, maxItemsPerPage, currentPage);
|
|
541
|
-
const effectiveCount = pagination.enabled ? pagination.itemsOnPage : count;
|
|
542
853
|
const grid = createGrid({ ...options, count: effectiveCount });
|
|
543
854
|
const getPosition = (index) => {
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
}
|
|
547
|
-
const pageRelativeIndex = index - pagination.startIndex;
|
|
548
|
-
if (pageRelativeIndex < 0 || pageRelativeIndex >= pagination.itemsOnPage) {
|
|
855
|
+
const relativeIndex = index - startIndex;
|
|
856
|
+
if (relativeIndex < 0 || relativeIndex >= effectiveCount) {
|
|
549
857
|
return { top: -9999, left: -9999 };
|
|
550
858
|
}
|
|
551
|
-
return grid.getPosition(
|
|
859
|
+
return grid.getPosition(relativeIndex);
|
|
552
860
|
};
|
|
861
|
+
const getItemDimensions = () => ({ width: grid.width, height: grid.height });
|
|
862
|
+
const lastVisibleIndex = endIndex - 1;
|
|
553
863
|
return {
|
|
554
864
|
...grid,
|
|
555
865
|
layoutMode: "gallery",
|
|
556
866
|
getPosition,
|
|
557
|
-
getItemDimensions
|
|
867
|
+
getItemDimensions,
|
|
558
868
|
isMainItem: () => false,
|
|
559
869
|
pagination,
|
|
560
|
-
isItemVisible: (index) => index >=
|
|
870
|
+
isItemVisible: (index) => index >= startIndex && index < endIndex,
|
|
871
|
+
hiddenCount,
|
|
872
|
+
getLastVisibleOthersIndex: () => hiddenCount > 0 ? lastVisibleIndex : -1,
|
|
873
|
+
getItemContentDimensions: createGetItemContentDimensions(getItemDimensions, options.itemAspectRatios, options.aspectRatio)
|
|
561
874
|
};
|
|
562
875
|
}
|
|
563
876
|
}
|
|
@@ -579,4 +892,4 @@ function getSpringConfig(preset = "smooth") {
|
|
|
579
892
|
};
|
|
580
893
|
}
|
|
581
894
|
|
|
582
|
-
export { createGrid, createGridItemPositioner, createMeetGrid, getAspectRatio, getGridItemDimensions, getSpringConfig, parseAspectRatio, springPresets };
|
|
895
|
+
export { calculateContentDimensions, calculateFlexLayout, calculateFlexStrip, createGrid, createGridItemPositioner, createMeetGrid, getAspectRatio, getGridItemDimensions, getSpringConfig, parseAspectRatio, springPresets };
|