From 0ee92b5d3f73b7982d6c38a9e5aabbe11e4c5809 Mon Sep 17 00:00:00 2001 From: Kout95 <65901733+Kout95@users.noreply.github.com> Date: Thu, 4 Jul 2024 11:56:46 +0200 Subject: [PATCH] feat: add loader in search-results (#209) Part of #191 --- frontend/public/off.html | 4 +- frontend/src/mixins/search-ctl.ts | 6 ++- frontend/src/search-results.ts | 62 +++++++++++++++++++++++++++++-- frontend/src/signals.ts | 19 ++++++++++ 4 files changed, 85 insertions(+), 6 deletions(-) diff --git a/frontend/public/off.html b/frontend/public/off.html index 2b3e4b3c..89ccac0f 100644 --- a/frontend/public/off.html +++ b/frontend/public/off.html @@ -43,10 +43,10 @@ color: rgba(0,0,0,.75); } /* search results styling */ - searchalicious-results::part(results) { + searchalicious-results::part(results), searchalicious-results::part(results-loading) { list-style: none; display: grid; - gap: 5px; + gap: 10px; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); margin: 0; padding: 0; diff --git a/frontend/src/mixins/search-ctl.ts b/frontend/src/mixins/search-ctl.ts index 54a026c1..59e64e11 100644 --- a/frontend/src/mixins/search-ctl.ts +++ b/frontend/src/mixins/search-ctl.ts @@ -26,7 +26,7 @@ import { SearchaliciousHistoryMixin, } from './history'; import {SearchaliciousChart} from '../search-chart'; -import {canResetSearch, isSearchChanged} from '../signals'; +import {canResetSearch, isSearchChanged, isSearchLoading} from '../signals'; import {SignalWatcher} from '@lit-labs/preact-signals'; import {isTheSameSearchName} from '../utils/search'; @@ -490,6 +490,9 @@ export const SearchaliciousSearchMixin = >( async search(page = 1) { const {searchUrl, method, params, history} = this._searchUrl(page); setCurrentURLHistory(history); + + isSearchLoading(this.name).value = true; + let response; if (method === 'GET') { response = await fetch( @@ -512,6 +515,7 @@ export const SearchaliciousSearchMixin = >( this._currentPage = data.page; this._pageCount = data.page_count; + isSearchLoading(this.name).value = false; this.updateSearchSignals(); // dispatch an event with the results diff --git a/frontend/src/search-results.ts b/frontend/src/search-results.ts index f87e4138..9e33557f 100644 --- a/frontend/src/search-results.ts +++ b/frontend/src/search-results.ts @@ -1,4 +1,4 @@ -import {LitElement, html} from 'lit'; +import {LitElement, html, css} from 'lit'; import {customElement, property, state} from 'lit/decorators.js'; import {repeat} from 'lit/directives/repeat.js'; @@ -9,6 +9,8 @@ import { MultipleResultTemplateError, } from './errors'; import {localized, msg} from '@lit/localize'; +import {SignalWatcher} from '@lit-labs/preact-signals'; +import {isSearchLoading} from './signals'; // we need it to declare functions type htmlType = typeof html; @@ -23,9 +25,31 @@ type htmlType = typeof html; */ @customElement('searchalicious-results') @localized() -export class SearchaliciousResults extends SearchaliciousResultCtlMixin( - LitElement +export class SearchaliciousResults extends SignalWatcher( + SearchaliciousResultCtlMixin(LitElement) ) { + static override styles = css` + .loading { + height: 300px; + border-radius: 8px; + animation: loading 2.25s ease infinite; + box-shadow: 0 4px 4px rgba(0, 0, 0, 0.25); + background-color: var(--first-loading-color, #cacaca); + } + + @keyframes loading { + 0% { + background-color: var(--first-loading-color, #cacaca); + } + 50% { + background-color: var(--second-loading-color, #bbbbbb); + } + 100% { + background-color: var(--first-loading-color, #cacaca); + } + } + `; + // the last search results @state() // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -35,6 +59,16 @@ export class SearchaliciousResults extends SearchaliciousResultCtlMixin( @property({attribute: 'result-id'}) resultId = ''; + @property({attribute: 'loadind-card-size', type: Number}) + loadingCardSize = 200; + + /** + * The parent width, used to compute the number of loading cards to display + */ + get parentWidth() { + return this.parentElement?.offsetWidth || 1200; + } + /** * A function rendering a single result. We define this just to get it's prototype right. * @@ -109,9 +143,31 @@ export class SearchaliciousResults extends SearchaliciousResultCtlMixin( return slots[0].innerHTML; } + /** + * Render the loading cards + * We display 2 columns of loading cards + */ + renderLoading() { + // we take the row width and display 2 columns of loading cards + const numCols = Math.floor(this.parentWidth / this.loadingCardSize) * 2; + + return html` + +
    + ${Array(numCols) + .fill(0) + .map(() => html`
  • `)} +
+
+ `; + } + override render() { if (this.results.length) { return this.renderResults(); + // if we are loading, we display the loading cards + } else if (isSearchLoading(this.searchName).value) { + return this.renderLoading(); } else if (this.searchLaunched) { return html`${this.noResults}`; } else { diff --git a/frontend/src/signals.ts b/frontend/src/signals.ts index f98fb8e0..6b536b51 100644 --- a/frontend/src/signals.ts +++ b/frontend/src/signals.ts @@ -18,6 +18,14 @@ const _isSearchChanged: Record = {} as Record< Signal >; +/** + * Signals to indicate if the search is loading. + */ +const _isSearchLoading: Record = {} as Record< + string, + Signal +>; + /** * Function to get or create a signal by search name. * If the signal does not exist, it creates it. @@ -37,6 +45,7 @@ const _getOrCreateSignal = ( /** * Function to get the signal to indicate if the search has changed. * It is use by reset-button to know if it should be displayed. + * @param searchName */ export const canResetSearch = (searchName: string) => { return _getOrCreateSignal(_canResetSearch, searchName); @@ -45,7 +54,17 @@ export const canResetSearch = (searchName: string) => { /** * Function to get the signal to indicate if the search has changed. * it is used by the search button to know if it should be displayed. + * @param searchName */ export const isSearchChanged = (searchName: string) => { return _getOrCreateSignal(_isSearchChanged, searchName); }; + +/** + * Function to get the signal to indicate if the search is loading. + * The search-results utilize this to determine whether the loading card should be displayed. + * @param searchName + */ +export const isSearchLoading = (searchName: string) => { + return _getOrCreateSignal(_isSearchLoading, searchName); +};