mirror of
				https://gitlab.com/Alamantus/Readlebee.git
				synced 2025-11-04 10:17:03 +01:00 
			
		
		
		
	Load covers in resultDetails sorted by publish date
This commit is contained in:
		
							parent
							
								
									46d3da8010
								
							
						
					
					
						commit
						0ebafaeb9e
					
				
					 9 changed files with 133 additions and 9 deletions
				
			
		| 
						 | 
					@ -35,10 +35,11 @@
 | 
				
			||||||
@import '../../node_modules/picnic/src/plugins/modal/plugin';
 | 
					@import '../../node_modules/picnic/src/plugins/modal/plugin';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// @import '../../node_modules/picnic/src/plugins/dropimage/plugin';
 | 
					// @import '../../node_modules/picnic/src/plugins/dropimage/plugin';
 | 
				
			||||||
// @import '../../node_modules/picnic/src/plugins/tabs/plugin';
 | 
					@import '../../node_modules/picnic/src/plugins/tabs/plugin';
 | 
				
			||||||
@import '../../node_modules/picnic/src/plugins/tooltip/plugin';
 | 
					@import '../../node_modules/picnic/src/plugins/tooltip/plugin';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Custom global styling
 | 
					// Custom global styling
 | 
				
			||||||
 | 
					@import './picnic-customizations/tabs';
 | 
				
			||||||
@import './picnic-customizations/custom';
 | 
					@import './picnic-customizations/custom';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// View styling
 | 
					// View styling
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										42
									
								
								app/styles/picnic-customizations/_tabs.scss
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								app/styles/picnic-customizations/_tabs.scss
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,42 @@
 | 
				
			||||||
 | 
					$tabs: five, six, seven, eight, nine, ten, eleven, twelve, thirteen, fourteen, fifteen, sixteen, seventeen, eighteen, nineteen, twenty;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.tabs {
 | 
				
			||||||
 | 
					  &.one {
 | 
				
			||||||
 | 
					    > .row {
 | 
				
			||||||
 | 
					      width: 100%;
 | 
				
			||||||
 | 
					      left: 0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    > input:nth-of-type(1):checked ~ .row {
 | 
				
			||||||
 | 
					      margin-left: 0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    > label img {
 | 
				
			||||||
 | 
					      display: none;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  // This is probably really stupid, but whatever. I need more than the 4 built-in tabs that Picnic provides
 | 
				
			||||||
 | 
					  @for $tab-index from 1 through length($tabs) {
 | 
				
			||||||
 | 
					    $number: $tab-index + 4;
 | 
				
			||||||
 | 
					    $tab: nth($tabs, $tab-index);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    &.#{$tab} {
 | 
				
			||||||
 | 
					      > .row {
 | 
				
			||||||
 | 
					        width: 100% * $number;
 | 
				
			||||||
 | 
					        left: -100% * ($number - 1);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      @for $item from 1 through ($number - 1) {
 | 
				
			||||||
 | 
					        > input:nth-of-type(#{$item}):checked ~ .row {
 | 
				
			||||||
 | 
					          margin-left: (100% * ($number - 1)) - (100% * ($item - 1));// 400%;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					      > label img {
 | 
				
			||||||
 | 
					        width: floor(100% / $number) - 2%;
 | 
				
			||||||
 | 
					        margin: 4% 0 4% 4%;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -10,6 +10,8 @@ export const modal = (modalId, controller, contentHTML, options = {}) => {
 | 
				
			||||||
   * headerText <string>: Displayed in an `<h3>` if no header is specified
 | 
					   * headerText <string>: Displayed in an `<h3>` if no header is specified
 | 
				
			||||||
   * noFooter <bool>: Set to `true` and exclude footerHTML to not include a modal footer
 | 
					   * noFooter <bool>: Set to `true` and exclude footerHTML to not include a modal footer
 | 
				
			||||||
   * footerHTML <choo/html>: Displayed in place of the default footer; Recommended to use `<footer>` tag
 | 
					   * footerHTML <choo/html>: Displayed in place of the default footer; Recommended to use `<footer>` tag
 | 
				
			||||||
 | 
					   * onShow <function>: Runs when the modal opens.
 | 
				
			||||||
 | 
					   * onHide <function>: Runs when the modal closes.
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
  const isOpen = controller.openModal === modalId;
 | 
					  const isOpen = controller.openModal === modalId;
 | 
				
			||||||
| 
						 | 
					@ -23,9 +25,17 @@ export const modal = (modalId, controller, contentHTML, options = {}) => {
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // Modals in Picnic CSS uses pure CSS with clever usage of invisible checkboxes and labels
 | 
					    // Modals in Picnic CSS uses pure CSS with clever usage of invisible checkboxes and labels
 | 
				
			||||||
    html`<div class="modal">
 | 
					    html`<div class="modal">
 | 
				
			||||||
      <input id=${modalId} type="checkbox" ${!isOpen ? null : 'checked'}
 | 
					      <input id=${modalId} type="checkbox" ${isOpen ? 'checked' : null} onchange=${event => {
 | 
				
			||||||
        onchange=${() => controller.openModal = isOpen ? modalId : null }/>
 | 
					        controller.openModal = !isOpen ? modalId : null;  // If it's not already open, set it to the open one
 | 
				
			||||||
 | 
					        if (typeof options.onShow !== 'undefined' && event.target.checked) {
 | 
				
			||||||
 | 
					          options.onShow();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (typeof options.onHide !== 'undefined' && !event.target.checked) {
 | 
				
			||||||
 | 
					          options.onHide();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }}>
 | 
				
			||||||
      <label for=${modalId} class="overlay"></label>
 | 
					      <label for=${modalId} class="overlay"></label>
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
      <article style=${typeof options.styles !== 'undefined' ? options.styles : null}>
 | 
					      <article style=${typeof options.styles !== 'undefined' ? options.styles : null}>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ${typeof options.headerHTML === 'undefined'
 | 
					        ${typeof options.headerHTML === 'undefined'
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -56,4 +56,21 @@ export class SearchController extends ViewController {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return Promise.resolve();
 | 
					    return Promise.resolve();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getCovers(inventaireURI) {
 | 
				
			||||||
 | 
					    // This should only be callable after results are displayed.
 | 
				
			||||||
 | 
					    const workIndex = this.results.works.findIndex(work => work.uri === inventaireURI);
 | 
				
			||||||
 | 
					    if (workIndex > -1) { // This should never be false, but just in case...
 | 
				
			||||||
 | 
					      if (typeof this.state.results.works[workIndex].covers === 'undefined') {
 | 
				
			||||||
 | 
					        // Only fetch covers if not already fetched.
 | 
				
			||||||
 | 
					        return fetch(`/api/books/covers?uri=${inventaireURI}&lang=${this.appState.language}`)
 | 
				
			||||||
 | 
					          .then(response => response.json())
 | 
				
			||||||
 | 
					          .then(responseJSON => {
 | 
				
			||||||
 | 
					            this.state.results.works[workIndex].covers = responseJSON;
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return Promise.resolve();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -35,7 +35,7 @@ export const searchView = (state, emit) => {
 | 
				
			||||||
                ${result.description ? html`<h4 class="subtitle">${result.description}</h4>` : null}
 | 
					                ${result.description ? html`<h4 class="subtitle">${result.description}</h4>` : null}
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
              <div class="third-800 half-500">
 | 
					              <div class="third-800 half-500">
 | 
				
			||||||
                ${resultDetails(controller, result)}
 | 
					                ${resultDetails(controller, result, emit)}
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
            </div>`;
 | 
					            </div>`;
 | 
				
			||||||
          }),
 | 
					          }),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,7 @@ import html from 'choo/html';
 | 
				
			||||||
import { starRating } from '../partials/starRating';
 | 
					import { starRating } from '../partials/starRating';
 | 
				
			||||||
import { modal } from '../partials/modal';
 | 
					import { modal } from '../partials/modal';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const resultDetails = (searchController, result) => {
 | 
					export const resultDetails = (searchController, result, emit = () => {}) => {
 | 
				
			||||||
  const { i18n } = searchController;
 | 
					  const { i18n } = searchController;
 | 
				
			||||||
  const modalId = `result_${result.uri}`;
 | 
					  const modalId = `result_${result.uri}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -17,10 +17,41 @@ export const resultDetails = (searchController, result) => {
 | 
				
			||||||
    </span>
 | 
					    </span>
 | 
				
			||||||
  </label>`;
 | 
					  </label>`;
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
 | 
					  const tabNames = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen', 'twenty'];
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
  const modalContent = html`<article class="flex">
 | 
					  const modalContent = html`<article class="flex">
 | 
				
			||||||
    <div class="sixth-700" style="text-align:center;">
 | 
					    <div class="sixth-700" style="text-align:center;">
 | 
				
			||||||
      <h4>Covers</h4>
 | 
					      <h4>Covers</h4>
 | 
				
			||||||
      <span style="font-size:3em;"><i class="icon-loading animate-spin"></i></span>
 | 
					      ${typeof result.covers === 'undefined'
 | 
				
			||||||
 | 
					        ? html`<span style="font-size:3em;"><i class="icon-loading animate-spin"></i></span>`
 | 
				
			||||||
 | 
					        : html`<div class="tabs ${typeof tabNames[result.covers.length - 1] !== 'undefined' ? tabNames[result.covers.length - 1] : null}">
 | 
				
			||||||
 | 
					          ${result.covers.map((cover, index) => {
 | 
				
			||||||
 | 
					            return [
 | 
				
			||||||
 | 
					              html`<input id="cover_${cover.uri}" type="radio" name="${modalId}_covers" ${index === 0 ? 'checked' : null} />`,
 | 
				
			||||||
 | 
					              // html`<label class="small pseudo button toggle" for="cover_${cover.uri}">•</label>`,
 | 
				
			||||||
 | 
					            ];
 | 
				
			||||||
 | 
					          })}
 | 
				
			||||||
 | 
					          <div class="row">
 | 
				
			||||||
 | 
					          ${result.covers.map((cover, index, allCovers) => {
 | 
				
			||||||
 | 
					            return html`<div>
 | 
				
			||||||
 | 
					              <img src=${cover.url} alt="${cover.uri.replace(':', ' ').toUpperCase()}, Published: ${cover.publishDate}">
 | 
				
			||||||
 | 
					              ${typeof allCovers[index - 1] === 'undefined'
 | 
				
			||||||
 | 
					                ? null
 | 
				
			||||||
 | 
					                : html`<label class="button" for="cover_${allCovers[index - 1].uri}" style="margin-right:8px;">
 | 
				
			||||||
 | 
					                  ${'<'}
 | 
				
			||||||
 | 
					                </label>`
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					              ${typeof allCovers[index + 1] === 'undefined'
 | 
				
			||||||
 | 
					                ? null
 | 
				
			||||||
 | 
					                : html`<label class="button" for="cover_${allCovers[index + 1].uri}">
 | 
				
			||||||
 | 
					                  ${'>'}
 | 
				
			||||||
 | 
					                </label>`
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            </div>`;
 | 
				
			||||||
 | 
					          })}
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </div>`
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
    <div class="two-third-700">
 | 
					    <div class="two-third-700">
 | 
				
			||||||
      <h4>${i18n.__('interaction.average_rating')}</h4>
 | 
					      <h4>${i18n.__('interaction.average_rating')}</h4>
 | 
				
			||||||
| 
						 | 
					@ -84,5 +115,10 @@ export const resultDetails = (searchController, result) => {
 | 
				
			||||||
    styles: "width:90%;",
 | 
					    styles: "width:90%;",
 | 
				
			||||||
    buttonHTML, // This should be replaced with buttonHTML containing the ratings and number of reviews etc.
 | 
					    buttonHTML, // This should be replaced with buttonHTML containing the ratings and number of reviews etc.
 | 
				
			||||||
    headerText: result.name,
 | 
					    headerText: result.name,
 | 
				
			||||||
 | 
					    onShow: () => {
 | 
				
			||||||
 | 
					      if (typeof result.covers === 'undefined') {
 | 
				
			||||||
 | 
					        searchController.getCovers(result.uri).then(() => emit('render'));
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -185,16 +185,26 @@ class BooksController {
 | 
				
			||||||
        return [];
 | 
					        return [];
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return Object.keys(responseJSON.entities).filter(key => {
 | 
					      const covers = Object.keys(responseJSON.entities).filter(key => {
 | 
				
			||||||
        const entity = responseJSON.entities[key];
 | 
					        const entity = responseJSON.entities[key];
 | 
				
			||||||
        return entity.originalLang === this.lang && typeof entity.claims !== undefined && typeof entity.claims['invp:P2'] !== undefined;
 | 
					        return entity.originalLang === this.lang && typeof entity.claims !== undefined && typeof entity.claims['invp:P2'] !== undefined;
 | 
				
			||||||
      }).map(key => {
 | 
					      }).map(key => {
 | 
				
			||||||
        const entity = responseJSON.entities[key];
 | 
					        const entity = responseJSON.entities[key];
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
          uri: entity.uri,
 | 
					          uri: entity.uri,
 | 
				
			||||||
          url: `${this.inventaire}/img/entities/${entity.claims['invp:P2'][0]}`,
 | 
					          url: typeof entity.claims['invp:P2'] !== 'undefined' ? `${this.inventaire}/img/entities/${entity.claims['invp:P2'][0]}` : null,
 | 
				
			||||||
 | 
					          publishDate: typeof entity.claims['wdt:P577'] !== 'undefined' ? entity.claims['wdt:P577'][0] : null,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      covers.sort((a, b) => {
 | 
				
			||||||
 | 
					        if (a.publishDate === b.publishDate) return 0;
 | 
				
			||||||
 | 
					        if (!a.publishDate && !!b.publishDate) return 1;
 | 
				
			||||||
 | 
					        if (!!a.publishDate && !b.publishDate) return 1;
 | 
				
			||||||
 | 
					        return a.publishDate < b.publishDate ? -1 : 1;
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return covers;
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -34,7 +34,7 @@ fastify.addHook('onRequest', (request, reply, done) => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Routes
 | 
					// Routes
 | 
				
			||||||
fastify.register(require('./routes/public'));
 | 
					fastify.register(require('./routes/public'));
 | 
				
			||||||
// fastify.register(require('./routes/home'));
 | 
					fastify.register(require('./routes/books'));
 | 
				
			||||||
// fastify.register(require('./routes/account'));
 | 
					// fastify.register(require('./routes/account'));
 | 
				
			||||||
fastify.register(require('./routes/search'));
 | 
					fastify.register(require('./routes/search'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,6 +8,14 @@ async function routes(fastify, options) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return books.getBookData();
 | 
					    return books.getBookData();
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  fastify.get('/api/books/covers', async (request, reply) => {
 | 
				
			||||||
 | 
					    const bookURI = typeof request.query.uri !== 'undefined' ? request.query.uri.trim() : '';
 | 
				
			||||||
 | 
					    const language = typeof request.query.lang !== 'undefined' ? request.query.lang.trim().split('-')[0] : undefined; // Get base language in cases like 'en-US'
 | 
				
			||||||
 | 
					    const books = new BooksController(fastify.siteConfig.inventaireDomain, bookURI, language);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return await books.getInventaireCovers();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = routes
 | 
					module.exports = routes
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue