Revert "refactor: replace custom masonry JS with Bootstrap grid + Masonry.js approach"
This reverts commit 04d425b88d.
This commit is contained in:
@@ -62,12 +62,12 @@
|
||||
<h4 class="text-info">No assets in this album</h4>
|
||||
</div>
|
||||
} else {
|
||||
<div class="@(viewMode == "masonry" ? "row g-2 album-assets-masonry" : "album-assets-grid grid")">
|
||||
<div class="@("album-assets-grid " + viewMode)">
|
||||
@for (int i = 0; i < album.Images.Count; i++) {
|
||||
var assetId = album.Images[i];
|
||||
var preview = album.AssetPreviews.Count > i ? album.AssetPreviews[i] : null;
|
||||
var index = i;
|
||||
<div class="@(viewMode == "masonry" ? "col-6 col-sm-4 col-lg-3 col-xxl-2" : "") masonry-tile" @key="assetId"
|
||||
<div class="masonry-tile" @key="assetId"
|
||||
style="aspect-ratio:@GetAspectRatio(preview)"
|
||||
@onclick="() => OpenPreview(index)">
|
||||
@if (preview == null || preview.HasThumbnail) {
|
||||
@@ -128,10 +128,9 @@
|
||||
|
||||
string TokenParam => string.IsNullOrEmpty(token) ? "" : $"?token={token}";
|
||||
string viewMode = "masonry";
|
||||
bool _masonryInited;
|
||||
|
||||
void SetMasonry() { viewMode = "masonry"; _masonryInited = false; StateHasChanged(); }
|
||||
void SetGrid() { viewMode = "grid"; _masonryInited = false; StateHasChanged(); }
|
||||
void SetMasonry() { viewMode = "masonry"; StateHasChanged(); }
|
||||
void SetGrid() { viewMode = "grid"; StateHasChanged(); }
|
||||
|
||||
AlbumAssetPreviewDto? currentPreview => album?.AssetPreviews != null && selectedIndex >= 0 && selectedIndex < album.AssetPreviews.Count
|
||||
? album.AssetPreviews[selectedIndex]
|
||||
@@ -144,16 +143,8 @@
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender) {
|
||||
if (viewMode == "masonry" && album?.Images.Count > 0) {
|
||||
if (!_masonryInited) {
|
||||
await jsRuntime.InvokeVoidAsync("masonryInit", ".album-assets-masonry");
|
||||
_masonryInited = true;
|
||||
} else {
|
||||
await jsRuntime.InvokeVoidAsync("masonryReload", ".album-assets-masonry");
|
||||
}
|
||||
} else {
|
||||
_masonryInited = false;
|
||||
await jsRuntime.InvokeVoidAsync("masonryDestroy", ".album-assets-masonry");
|
||||
if (album != null && album.Images.Count > 0 && viewMode == "masonry") {
|
||||
await jsRuntime.InvokeVoidAsync("masonryLayout.apply", ".album-assets-grid.masonry");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,16 +36,29 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Masonry ── */
|
||||
.album-assets-grid.masonry {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* ── Grid ── */
|
||||
.album-assets-grid.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 8px;
|
||||
padding: 0 8px;
|
||||
position: static !important;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.album-assets-grid.grid .masonry-tile {
|
||||
aspect-ratio: 1;
|
||||
aspect-ratio: 1 !important;
|
||||
position: relative !important;
|
||||
left: auto !important;
|
||||
top: auto !important;
|
||||
width: auto !important;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
/* ── Tile shared ── */
|
||||
@@ -57,6 +70,10 @@
|
||||
transition: filter 0.2s;
|
||||
}
|
||||
|
||||
.album-assets-grid.grid .masonry-tile {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.masonry-tile:hover {
|
||||
filter: brightness(1.15);
|
||||
}
|
||||
|
||||
@@ -49,9 +49,9 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="@(viewMode == "masonry" ? "row g-2 album-grid-masonry" : "album-grid grid")">
|
||||
<div class="@("album-grid " + viewMode)">
|
||||
@foreach (var album in albums) {
|
||||
<div class="@(viewMode == "masonry" ? "col-6 col-sm-4 col-lg-3 col-xxl-2" : "") album-card" @key="album.Id"
|
||||
<div class="album-card" @key="album.Id"
|
||||
@onclick="() => NavigateToAlbum(album.Id)"
|
||||
@onclick:preventDefault="true">
|
||||
@if (album.CoverAssetId.HasValue) {
|
||||
@@ -104,18 +104,10 @@
|
||||
string token = string.Empty;
|
||||
string apiBase => serviceOptions.Value.BaseUrl.TrimEnd('/');
|
||||
string viewMode = "masonry";
|
||||
bool _masonryInited;
|
||||
bool IsAdminOrCurator => loginService.LoggedUser?.AccessLevel is EAccessLevel.Admin or EAccessLevel.Curator;
|
||||
|
||||
void SetMasonry() {
|
||||
viewMode = "masonry";
|
||||
_masonryInited = false;
|
||||
}
|
||||
|
||||
void SetGrid() {
|
||||
viewMode = "grid";
|
||||
_masonryInited = false;
|
||||
}
|
||||
void SetMasonry() => viewMode = "masonry";
|
||||
void SetGrid() => viewMode = "grid";
|
||||
|
||||
string TokenParam => string.IsNullOrEmpty(token) ? "" : $"?token={token}";
|
||||
|
||||
@@ -131,18 +123,6 @@
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender) {
|
||||
if (viewMode == "masonry" && albums.Count > 0) {
|
||||
if (!_masonryInited) {
|
||||
await jsRuntime.InvokeVoidAsync("masonryInit", ".album-grid-masonry");
|
||||
_masonryInited = true;
|
||||
} else {
|
||||
await jsRuntime.InvokeVoidAsync("masonryReload", ".album-grid-masonry");
|
||||
}
|
||||
} else {
|
||||
_masonryInited = false;
|
||||
await jsRuntime.InvokeVoidAsync("masonryDestroy", ".album-grid-masonry");
|
||||
}
|
||||
|
||||
if (albums.Count > 0 && dotNetRef == null) {
|
||||
dotNetRef = DotNetObjectReference.Create(this);
|
||||
await jsRuntime.InvokeVoidAsync(
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
/* ── Masonry ── */
|
||||
.album-grid.masonry {
|
||||
column-count: 4;
|
||||
column-gap: 8px;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
/* ── Grid ── */
|
||||
.album-grid.grid {
|
||||
display: grid;
|
||||
@@ -6,10 +13,6 @@
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.album-grid.grid .album-card {
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
|
||||
/* ── Card shared ── */
|
||||
.album-card {
|
||||
overflow: hidden;
|
||||
@@ -17,6 +20,7 @@
|
||||
cursor: pointer;
|
||||
background: var(--bs-dark, #1a1a1a);
|
||||
transition: filter 0.2s, transform 0.2s;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.album-card:hover {
|
||||
@@ -32,7 +36,6 @@
|
||||
|
||||
.album-card-fallback {
|
||||
width: 100%;
|
||||
aspect-ratio: 4 / 3;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -41,6 +44,20 @@
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.album-grid.masonry .album-card {
|
||||
break-inside: avoid;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.album-grid.grid .album-card {
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
|
||||
.album-grid.masonry .album-card-fallback {
|
||||
aspect-ratio: 4 / 3;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/* ── Info overlay ── */
|
||||
.album-card-info {
|
||||
position: absolute;
|
||||
@@ -86,5 +103,15 @@
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.album-grid.masonry { column-count: 2; }
|
||||
.album-grid.grid { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); }
|
||||
}
|
||||
@media (min-width: 577px) and (max-width: 992px) {
|
||||
.album-grid.masonry { column-count: 3; }
|
||||
}
|
||||
@media (min-width: 993px) and (max-width: 1400px) {
|
||||
.album-grid.masonry { column-count: 4; }
|
||||
}
|
||||
@media (min-width: 1401px) {
|
||||
.album-grid.masonry { column-count: 5; }
|
||||
}
|
||||
|
||||
@@ -32,12 +32,12 @@
|
||||
<LoadSpinner/>
|
||||
}
|
||||
|
||||
<div class="row g-2">
|
||||
<div class="masonry-grid">
|
||||
@{ var idx = 0; }
|
||||
@foreach (var page in allAssetPages) {
|
||||
@foreach (var asset in page) {
|
||||
var i = idx++;
|
||||
<div class="col-6 col-sm-4 col-lg-3 col-xxl-2 masonry-tile" @key="asset.Id"
|
||||
<div class="masonry-tile" @key="asset.Id"
|
||||
style="aspect-ratio:@GetAspectRatio(asset)"
|
||||
@onclick="() => OpenPreview(asset, i)">
|
||||
@if (asset.HasThumbnail) {
|
||||
@@ -149,21 +149,14 @@
|
||||
}
|
||||
|
||||
protected async override Task OnAfterRenderAsync(bool firstRender) {
|
||||
if (flatList.Count > 0) {
|
||||
if (firstRender) {
|
||||
await jsRuntime.InvokeVoidAsync("masonryInit", ".row.g-2");
|
||||
} else {
|
||||
await jsRuntime.InvokeVoidAsync("masonryReload", ".row.g-2");
|
||||
}
|
||||
if (dotNetRef == null) {
|
||||
dotNetRef = DotNetObjectReference.Create(this);
|
||||
await jsRuntime.InvokeVoidAsync(
|
||||
"masonryObserver.observeBottom", sentinelRef, dotNetRef
|
||||
);
|
||||
await jsRuntime.InvokeVoidAsync(
|
||||
"masonryObserver.observeTop", topSentinelRef, dotNetRef
|
||||
);
|
||||
}
|
||||
if (flatList.Count > 0 && dotNetRef == null) {
|
||||
dotNetRef = DotNetObjectReference.Create(this);
|
||||
await jsRuntime.InvokeVoidAsync(
|
||||
"masonryObserver.observeBottom", sentinelRef, dotNetRef
|
||||
);
|
||||
await jsRuntime.InvokeVoidAsync(
|
||||
"masonryObserver.observeTop", topSentinelRef, dotNetRef
|
||||
);
|
||||
}
|
||||
|
||||
if (selectedAsset != null)
|
||||
|
||||
@@ -1,9 +1,20 @@
|
||||
.masonry-grid {
|
||||
column-count: 4;
|
||||
column-gap: 8px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.masonry-tile {
|
||||
break-inside: avoid;
|
||||
margin-bottom: 8px;
|
||||
overflow: hidden;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
background: var(--bs-dark, #1a1a1a);
|
||||
transition: filter 0.2s;
|
||||
position: relative;
|
||||
content-visibility: auto;
|
||||
contain-intrinsic-size: 200px;
|
||||
}
|
||||
|
||||
.masonry-tile:hover {
|
||||
@@ -74,3 +85,16 @@
|
||||
.masonry-sentinel {
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.masonry-grid { column-count: 2; }
|
||||
}
|
||||
@media (min-width: 577px) and (max-width: 992px) {
|
||||
.masonry-grid { column-count: 3; }
|
||||
}
|
||||
@media (min-width: 993px) and (max-width: 1400px) {
|
||||
.masonry-grid { column-count: 4; }
|
||||
}
|
||||
@media (min-width: 1401px) {
|
||||
.masonry-grid { column-count: 5; }
|
||||
}
|
||||
|
||||
@@ -26,11 +26,8 @@
|
||||
</div>
|
||||
|
||||
<script src="lib/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/masonry-layout@4.2.2/dist/masonry.pkgd.min.js"
|
||||
integrity="sha384-GNFwBvfVxBkLMJpYMOABq3c+d3KnQxudP/mGPkzpZSTYykLBNsZEnG2D9G/X/+7D"
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="js/infiniteScroll.js"></script>
|
||||
<script src="_framework/blazor.webassembly.js"></script>
|
||||
<script src="js/masonryObserver.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
window.masonryObserver = {
|
||||
_bottom: null,
|
||||
_top: null,
|
||||
_ref: null,
|
||||
|
||||
observeBottom(sentinel, dotNetRef) {
|
||||
this._ref = dotNetRef;
|
||||
this._bottom?.disconnect();
|
||||
this._bottom = new IntersectionObserver(
|
||||
(entries) => { if (entries[0].isIntersecting) dotNetRef.invokeMethodAsync('LoadNextPage'); },
|
||||
{ rootMargin: '600px' }
|
||||
);
|
||||
this._bottom.observe(sentinel);
|
||||
},
|
||||
|
||||
observeTop(sentinel, dotNetRef) {
|
||||
this._ref = dotNetRef;
|
||||
this._top?.disconnect();
|
||||
this._top = new IntersectionObserver(
|
||||
(entries) => { if (entries[0].isIntersecting) dotNetRef.invokeMethodAsync('LoadPreviousPage'); },
|
||||
{ rootMargin: '600px' }
|
||||
);
|
||||
this._top.observe(sentinel);
|
||||
},
|
||||
|
||||
dispose() {
|
||||
this._bottom?.disconnect();
|
||||
this._top?.disconnect();
|
||||
this._bottom = null;
|
||||
this._top = null;
|
||||
this._ref = null;
|
||||
},
|
||||
|
||||
lockBodyScroll() { document.body.style.overflow = 'hidden'; },
|
||||
unlockBodyScroll() { document.body.style.overflow = ''; },
|
||||
|
||||
async downloadFile(url) {
|
||||
const response = await fetch(url);
|
||||
const blob = await response.blob();
|
||||
const a = document.createElement('a');
|
||||
a.href = URL.createObjectURL(blob);
|
||||
a.download = '';
|
||||
document.body.append(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
URL.revokeObjectURL(a.href);
|
||||
},
|
||||
};
|
||||
|
||||
window.albumObserver = {
|
||||
_observer: null,
|
||||
_ref: null,
|
||||
|
||||
observeBottom(sentinel, dotNetRef) {
|
||||
this._ref = dotNetRef;
|
||||
this._observer?.disconnect();
|
||||
this._observer = new IntersectionObserver(
|
||||
(entries) => { if (entries[0].isIntersecting) dotNetRef.invokeMethodAsync('LoadMoreAlbums'); },
|
||||
{ rootMargin: '600px' }
|
||||
);
|
||||
this._observer.observe(sentinel);
|
||||
},
|
||||
|
||||
dispose() {
|
||||
this._observer?.disconnect();
|
||||
this._observer = null;
|
||||
this._ref = null;
|
||||
},
|
||||
};
|
||||
|
||||
function masonryInit(selector, options) {
|
||||
const el = typeof selector === 'string' ? document.querySelector(selector) : selector;
|
||||
if (!el || typeof Masonry === 'undefined') return false;
|
||||
new Masonry(el, Object.assign({ percentPosition: true }, options));
|
||||
return true;
|
||||
}
|
||||
|
||||
function masonryReload(selector) {
|
||||
const el = typeof selector === 'string' ? document.querySelector(selector) : selector;
|
||||
const msnry = el ? Masonry.data(el) : null;
|
||||
if (msnry) { msnry.reloadItems(); msnry.layout(); }
|
||||
}
|
||||
|
||||
function masonryDestroy(selector) {
|
||||
const el = typeof selector === 'string' ? document.querySelector(selector) : selector;
|
||||
const msnry = el ? Masonry.data(el) : null;
|
||||
if (msnry) { msnry.destroy(); }
|
||||
}
|
||||
203
MilkStream.Client/wwwroot/js/masonryObserver.js
Normal file
203
MilkStream.Client/wwwroot/js/masonryObserver.js
Normal file
@@ -0,0 +1,203 @@
|
||||
window.masonryObserver = {
|
||||
bottomObserver: null,
|
||||
topObserver: null,
|
||||
dotNetRef: null,
|
||||
|
||||
observeBottom: function (sentinelElement, dotNetRef) {
|
||||
this.dotNetRef = dotNetRef;
|
||||
if (this.bottomObserver) this.bottomObserver.disconnect();
|
||||
this.bottomObserver = new IntersectionObserver(
|
||||
(entries) => {
|
||||
if (entries[0].isIntersecting) {
|
||||
dotNetRef.invokeMethodAsync('LoadNextPage');
|
||||
}
|
||||
},
|
||||
{ rootMargin: '600px' }
|
||||
);
|
||||
this.bottomObserver.observe(sentinelElement);
|
||||
},
|
||||
|
||||
observeTop: function (sentinelElement, dotNetRef) {
|
||||
this.dotNetRef = dotNetRef;
|
||||
if (this.topObserver) this.topObserver.disconnect();
|
||||
this.topObserver = new IntersectionObserver(
|
||||
(entries) => {
|
||||
if (entries[0].isIntersecting) {
|
||||
dotNetRef.invokeMethodAsync('LoadPreviousPage');
|
||||
}
|
||||
},
|
||||
{ rootMargin: '600px' }
|
||||
);
|
||||
this.topObserver.observe(sentinelElement);
|
||||
},
|
||||
|
||||
dispose: function () {
|
||||
if (this.bottomObserver) {
|
||||
this.bottomObserver.disconnect();
|
||||
this.bottomObserver = null;
|
||||
}
|
||||
if (this.topObserver) {
|
||||
this.topObserver.disconnect();
|
||||
this.topObserver = null;
|
||||
}
|
||||
this.dotNetRef = null;
|
||||
},
|
||||
|
||||
lockBodyScroll: function () {
|
||||
document.body.style.overflow = 'hidden';
|
||||
},
|
||||
|
||||
unlockBodyScroll: function () {
|
||||
document.body.style.overflow = '';
|
||||
},
|
||||
|
||||
downloadFile: async function (url) {
|
||||
const response = await fetch(url);
|
||||
const blob = await response.blob();
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = blobUrl;
|
||||
a.download = '';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
}
|
||||
};
|
||||
|
||||
window.albumObserver = {
|
||||
bottomObserver: null,
|
||||
dotNetRef: null,
|
||||
|
||||
observeBottom: function (sentinelElement, dotNetRef) {
|
||||
this.dotNetRef = dotNetRef;
|
||||
if (this.bottomObserver) this.bottomObserver.disconnect();
|
||||
this.bottomObserver = new IntersectionObserver(
|
||||
(entries) => {
|
||||
if (entries[0].isIntersecting) {
|
||||
dotNetRef.invokeMethodAsync('LoadMoreAlbums');
|
||||
}
|
||||
},
|
||||
{ rootMargin: '600px' }
|
||||
);
|
||||
this.bottomObserver.observe(sentinelElement);
|
||||
},
|
||||
|
||||
dispose: function () {
|
||||
if (this.bottomObserver) {
|
||||
this.bottomObserver.disconnect();
|
||||
this.bottomObserver = null;
|
||||
}
|
||||
this.dotNetRef = null;
|
||||
}
|
||||
};
|
||||
|
||||
window.pageLayout = {
|
||||
columnCount: function () {
|
||||
const w = window.innerWidth;
|
||||
if (w <= 576) return 2;
|
||||
if (w <= 992) return 3;
|
||||
if (w <= 1400) return 4;
|
||||
return 5;
|
||||
}
|
||||
};
|
||||
|
||||
window.masonryLayout = {
|
||||
apply: function (containerSelector) {
|
||||
const container = document.querySelector(containerSelector);
|
||||
if (!container) return;
|
||||
|
||||
const items = Array.from(container.children);
|
||||
if (items.length === 0) return;
|
||||
|
||||
const gap = 8;
|
||||
const cols = window.pageLayout.columnCount();
|
||||
const containerWidth = container.clientWidth;
|
||||
const colWidth = (containerWidth - gap * (cols - 1)) / cols;
|
||||
|
||||
container.style.position = 'relative';
|
||||
const heights = new Array(cols).fill(0);
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items[i];
|
||||
item.style.position = 'absolute';
|
||||
item.style.width = colWidth + 'px';
|
||||
item.style.margin = '0';
|
||||
|
||||
// Force layout to get correct offsetHeight from aspect-ratio
|
||||
item.offsetHeight;
|
||||
|
||||
let minIdx = 0;
|
||||
for (let c = 1; c < cols; c++) {
|
||||
if (heights[c] < heights[minIdx]) minIdx = c;
|
||||
}
|
||||
|
||||
const left = minIdx * (colWidth + gap);
|
||||
const top = heights[minIdx];
|
||||
|
||||
item.style.left = left + 'px';
|
||||
item.style.top = top + 'px';
|
||||
|
||||
heights[minIdx] = top + item.offsetHeight + gap;
|
||||
}
|
||||
|
||||
container.style.height = Math.max(...heights, 0) + 'px';
|
||||
}
|
||||
};
|
||||
|
||||
window.justifiedLayout = {
|
||||
apply: function (containerSelector, gap, targetRowHeight) {
|
||||
const container = document.querySelector(containerSelector);
|
||||
if (!container) return;
|
||||
|
||||
const items = Array.from(container.children);
|
||||
if (items.length === 0) return;
|
||||
|
||||
gap = gap || 8;
|
||||
targetRowHeight = targetRowHeight || 250;
|
||||
|
||||
const containerWidth = container.clientWidth;
|
||||
container.style.position = 'relative';
|
||||
|
||||
let top = 0;
|
||||
let rowItems = [];
|
||||
let rowAspectSum = 0;
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items[i];
|
||||
const ar = item.style.aspectRatio || '4/3';
|
||||
const parts = ar.split('/');
|
||||
const a = parts.length === 2 ? parseFloat(parts[0]) / parseFloat(parts[1]) : 4 / 3;
|
||||
|
||||
item.style.position = 'absolute';
|
||||
|
||||
rowItems.push({ item, aspect: a });
|
||||
rowAspectSum += a;
|
||||
|
||||
const tentativeWidth = targetRowHeight * rowAspectSum;
|
||||
const rowGaps = (rowItems.length - 1) * gap;
|
||||
const isLastItem = i === items.length - 1;
|
||||
const rowIsShy = isLastItem && tentativeWidth + rowGaps < containerWidth * 0.5;
|
||||
|
||||
if (tentativeWidth + rowGaps >= containerWidth || (isLastItem && !rowIsShy)) {
|
||||
const availableWidth = containerWidth - rowGaps;
|
||||
const actualHeight = rowIsShy ? targetRowHeight : availableWidth / rowAspectSum;
|
||||
|
||||
let left = 0;
|
||||
for (const ri of rowItems) {
|
||||
ri.item.style.left = left + 'px';
|
||||
ri.item.style.top = top + 'px';
|
||||
ri.item.style.width = (actualHeight * ri.aspect) + 'px';
|
||||
ri.item.style.height = actualHeight + 'px';
|
||||
left += actualHeight * ri.aspect + gap;
|
||||
}
|
||||
|
||||
top += actualHeight + gap;
|
||||
rowItems = [];
|
||||
rowAspectSum = 0;
|
||||
}
|
||||
}
|
||||
|
||||
container.style.height = Math.max(top - gap, 0) + 'px';
|
||||
}
|
||||
};
|
||||
@@ -55,11 +55,3 @@ h1:focus {
|
||||
.darker-border-checkbox.form-check-input {
|
||||
border-color: #929292;
|
||||
}
|
||||
|
||||
/* Masonry: override col-xxl-2 (6 cols) to 5 cols at ≥1400px */
|
||||
@media (min-width: 1400px) {
|
||||
.row.g-2 > .col-xxl-2 {
|
||||
flex: 0 0 auto;
|
||||
width: 20%;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user