Skip to content

Commit 466bcd3

Browse files
committed
feat: implement nav-right nav-left buttons to scroll the image ribbon gallery
1 parent 3625f13 commit 466bcd3

File tree

1 file changed

+194
-10
lines changed

1 file changed

+194
-10
lines changed

src/LiveDevelopment/BrowserScripts/RemoteFunctions.js

Lines changed: 194 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1918,6 +1918,11 @@ function RemoteFunctions(config = {}) {
19181918
function ImageRibbonGallery(element) {
19191919
this.element = element;
19201920
this.remove = this.remove.bind(this);
1921+
this.currentPage = 1;
1922+
this.totalPages = 1;
1923+
this.allImages = [];
1924+
this.imagesPerPage = 10;
1925+
this.scrollPosition = 0;
19211926
this.create();
19221927
}
19231928

@@ -2014,6 +2019,25 @@ function RemoteFunctions(config = {}) {
20142019
font-size: 14px !important;
20152020
}
20162021
2022+
.phoenix-ribbon-nav {
2023+
font-size: 18px !important;
2024+
font-weight: 600 !important;
2025+
user-select: none !important;
2026+
transition: all 0.2s ease !important;
2027+
z-index: 10 !important;
2028+
}
2029+
2030+
.phoenix-ribbon-nav:hover {
2031+
background: rgba(21,25,36,0.85) !important;
2032+
border-color: rgba(255,255,255,0.25) !important;
2033+
transform: translateY(-50%) scale(1.05) !important;
2034+
box-shadow: 0 4px 12px rgba(0,0,0,0.3) !important;
2035+
}
2036+
2037+
.phoenix-ribbon-nav:active {
2038+
transform: translateY(-50%) scale(0.95) !important;
2039+
}
2040+
20172041
.phoenix-ribbon-nav.left {
20182042
left: 18px !important;
20192043
}
@@ -2184,10 +2208,13 @@ function RemoteFunctions(config = {}) {
21842208
`;
21852209
},
21862210

2187-
_fetchImages: function(searchQuery = 'sunshine') {
2211+
_fetchImages: function(searchQuery = 'sunshine', page = 1, append = false) {
21882212
this._currentSearchQuery = searchQuery;
2189-
const apiUrl = `https://images.phcode.dev/api/images/search?q=${encodeURIComponent(searchQuery)}&per_page=10`;
2190-
this._showLoading();
2213+
const apiUrl = `https://images.phcode.dev/api/images/search?q=${encodeURIComponent(searchQuery)}&per_page=10&page=${page}`;
2214+
2215+
if (!append) {
2216+
this._showLoading();
2217+
}
21912218

21922219
fetch(apiUrl)
21932220
.then(response => {
@@ -2198,17 +2225,103 @@ function RemoteFunctions(config = {}) {
21982225
})
21992226
.then(data => {
22002227
if (data.results && data.results.length > 0) {
2201-
this._renderImages(data.results);
2202-
} else {
2228+
if (append) {
2229+
this.allImages = this.allImages.concat(data.results);
2230+
this._renderImages(data.results, true); // true means need to append new images at the end
2231+
} else {
2232+
this.allImages = data.results;
2233+
this._renderImages(this.allImages, false); // false means its a new search
2234+
}
2235+
this.totalPages = data.total_pages || 1;
2236+
this.currentPage = page;
2237+
this._updateNavButtons();
2238+
} else if (!append) {
22032239
this._showError('No images found');
22042240
}
2241+
2242+
if (append) {
2243+
this._isLoadingMore = false;
2244+
this._hideLoadingMore();
2245+
}
22052246
})
22062247
.catch(error => {
22072248
console.error('Failed to fetch images:', error);
2208-
this._showError('Failed to load images');
2249+
if (!append) {
2250+
this._showError('Failed to load images');
2251+
} else {
2252+
this._isLoadingMore = false;
2253+
this._hideLoadingMore();
2254+
}
22092255
});
22102256
},
22112257

2258+
_handleNavLeft: function() {
2259+
const container = this._shadow.querySelector('.phoenix-ribbon-strip');
2260+
if (!container) { return; }
2261+
2262+
const containerWidth = container.clientWidth;
2263+
const scrollAmount = containerWidth;
2264+
2265+
this.scrollPosition = Math.max(0, this.scrollPosition - scrollAmount);
2266+
container.scrollTo({ left: this.scrollPosition, behavior: 'smooth' });
2267+
this._updateNavButtons();
2268+
},
2269+
2270+
_handleNavRight: function() {
2271+
const container = this._shadow.querySelector('.phoenix-ribbon-strip');
2272+
if (!container) { return; }
2273+
2274+
const containerWidth = container.clientWidth;
2275+
const totalWidth = container.scrollWidth;
2276+
const scrollAmount = containerWidth;
2277+
2278+
// if we're near the end, we need to load more images
2279+
const nearEnd = (this.scrollPosition + containerWidth + scrollAmount) >= totalWidth - 100;
2280+
if (nearEnd && this.currentPage < this.totalPages && !this._isLoadingMore) {
2281+
this._isLoadingMore = true;
2282+
this._showLoadingMore();
2283+
this._fetchImages(this._currentSearchQuery, this.currentPage + 1, true);
2284+
}
2285+
2286+
this.scrollPosition = Math.min(totalWidth - containerWidth, this.scrollPosition + scrollAmount);
2287+
container.scrollTo({ left: this.scrollPosition, behavior: 'smooth' });
2288+
this._updateNavButtons();
2289+
},
2290+
2291+
_updateNavButtons: function() {
2292+
// this function is responsible to update the nav buttons
2293+
// because when we're at the very left, then we style the nav-left button differently (reduce opacity)
2294+
// and when we're at the very right and no more pages available, we reduce opacity for nav-right
2295+
const navLeft = this._shadow.querySelector('.phoenix-ribbon-nav.left');
2296+
const navRight = this._shadow.querySelector('.phoenix-ribbon-nav.right');
2297+
const container = this._shadow.querySelector('.phoenix-ribbon-strip');
2298+
2299+
if (!navLeft || !navRight || !container) { return; }
2300+
2301+
// show/hide left button
2302+
if (this.scrollPosition <= 0) {
2303+
navLeft.style.opacity = '0.3';
2304+
navLeft.style.pointerEvents = 'none';
2305+
} else {
2306+
navLeft.style.opacity = '1';
2307+
navLeft.style.pointerEvents = 'auto';
2308+
}
2309+
2310+
// show/hide right button
2311+
const containerWidth = container.clientWidth;
2312+
const totalWidth = container.scrollWidth;
2313+
const atEnd = (this.scrollPosition + containerWidth) >= totalWidth - 10;
2314+
const hasMorePages = this.currentPage < this.totalPages;
2315+
2316+
if (atEnd && !hasMorePages) {
2317+
navRight.style.opacity = '0.3';
2318+
navRight.style.pointerEvents = 'none';
2319+
} else {
2320+
navRight.style.opacity = '1';
2321+
navRight.style.pointerEvents = 'auto';
2322+
}
2323+
},
2324+
22122325
_showLoading: function() {
22132326
const rowElement = this._shadow.querySelector('.phoenix-ribbon-row');
22142327
if (!rowElement) { return; }
@@ -2217,16 +2330,53 @@ function RemoteFunctions(config = {}) {
22172330
rowElement.className = 'phoenix-ribbon-row phoenix-ribbon-loading';
22182331
},
22192332

2333+
_showLoadingMore: function() {
2334+
const rowElement = this._shadow.querySelector('.phoenix-ribbon-row');
2335+
if (!rowElement) { return; }
2336+
2337+
// when loading more images we need to show the message at the end of the image ribbon
2338+
const loadingIndicator = window.document.createElement('div');
2339+
loadingIndicator.className = 'phoenix-loading-more';
2340+
loadingIndicator.style.cssText = `
2341+
display: flex !important;
2342+
align-items: center !important;
2343+
justify-content: center !important;
2344+
min-width: 120px !important;
2345+
height: 116px !important;
2346+
margin-left: 2px !important;
2347+
background: rgba(255,255,255,0.03) !important;
2348+
border-radius: 8px !important;
2349+
color: #e8eaf0 !important;
2350+
font-size: 12px !important;
2351+
border: 1px dashed rgba(255,255,255,0.1) !important;
2352+
`;
2353+
loadingIndicator.textContent = 'Loading...';
2354+
rowElement.appendChild(loadingIndicator);
2355+
},
2356+
2357+
_hideLoadingMore: function() {
2358+
const loadingIndicator = this._shadow.querySelector('.phoenix-loading-more');
2359+
if (loadingIndicator) {
2360+
loadingIndicator.remove();
2361+
}
2362+
},
2363+
22202364
_attachEventHandlers: function() {
22212365
const searchInput = this._shadow.querySelector('.phoenix-ribbon-search input');
22222366
const searchButton = this._shadow.querySelector('.phoenix-ribbon-search-btn');
22232367
const closeButton = this._shadow.querySelector('.phoenix-ribbon-close');
2368+
const navLeft = this._shadow.querySelector('.phoenix-ribbon-nav.left');
2369+
const navRight = this._shadow.querySelector('.phoenix-ribbon-nav.right');
22242370

22252371
if (searchInput && searchButton) {
22262372
const performSearch = (e) => {
22272373
e.stopPropagation();
22282374
const query = searchInput.value.trim();
22292375
if (query) {
2376+
// reset pagination when searching
2377+
this.currentPage = 1;
2378+
this.allImages = [];
2379+
this.scrollPosition = 0;
22302380
this._fetchImages(query);
22312381
}
22322382
};
@@ -2250,6 +2400,20 @@ function RemoteFunctions(config = {}) {
22502400
});
22512401
}
22522402

2403+
if (navLeft) {
2404+
navLeft.addEventListener('click', (e) => {
2405+
e.stopPropagation();
2406+
this._handleNavLeft();
2407+
});
2408+
}
2409+
2410+
if (navRight) {
2411+
navRight.addEventListener('click', (e) => {
2412+
e.stopPropagation();
2413+
this._handleNavRight();
2414+
});
2415+
}
2416+
22532417
// Prevent clicks anywhere inside the ribbon from bubbling up
22542418
const ribbonContainer = this._shadow.querySelector('.phoenix-image-ribbon');
22552419
if (ribbonContainer) {
@@ -2259,13 +2423,26 @@ function RemoteFunctions(config = {}) {
22592423
}
22602424
},
22612425

2262-
_renderImages: function(images) {
2426+
// append true means load more images (user clicked on nav-right)
2427+
// append false means its a new query
2428+
_renderImages: function(images, append = false) {
22632429
const rowElement = this._shadow.querySelector('.phoenix-ribbon-row');
22642430
if (!rowElement) { return; }
22652431

2266-
// remove the loading state
2267-
rowElement.innerHTML = '';
2268-
rowElement.className = 'phoenix-ribbon-row';
2432+
const container = this._shadow.querySelector('.phoenix-ribbon-strip');
2433+
const savedScrollPosition = container ? container.scrollLeft : 0;
2434+
2435+
// if not appending we clear the phoenix ribbon
2436+
if (!append) {
2437+
rowElement.innerHTML = '';
2438+
rowElement.className = 'phoenix-ribbon-row';
2439+
} else {
2440+
// when appending we add the new images at the end
2441+
const loadingIndicator = this._shadow.querySelector('.phoenix-loading-more');
2442+
if (loadingIndicator) {
2443+
loadingIndicator.remove();
2444+
}
2445+
}
22692446

22702447
// Create thumbnails from API data
22712448
images.forEach(image => {
@@ -2334,6 +2511,12 @@ function RemoteFunctions(config = {}) {
23342511
thumbDiv.appendChild(useImageBtn);
23352512
rowElement.appendChild(thumbDiv);
23362513
});
2514+
2515+
if (append && container && savedScrollPosition > 0) {
2516+
setTimeout(() => {
2517+
container.scrollLeft = savedScrollPosition;
2518+
}, 0);
2519+
}
23372520
},
23382521

23392522
_showError: function(message) {
@@ -2390,6 +2573,7 @@ function RemoteFunctions(config = {}) {
23902573
window.document.body.appendChild(this.body);
23912574
this._attachEventHandlers();
23922575
this._fetchImages();
2576+
setTimeout(() => this._updateNavButtons(), 0);
23932577
},
23942578

23952579
remove: function () {

0 commit comments

Comments
 (0)