From b1af9ae85c8f2614b73c51f941ec158dc2778fe0 Mon Sep 17 00:00:00 2001 From: Theodore Mentis Date: Sat, 21 Oct 2023 10:30:23 +0300 Subject: [PATCH] Pagination & function clean up --- .gitignore | 5 +- package-lock.json | 89 ++++++++++++++++ package.json | 6 +- public/css/style.css | 126 ++++++++++++++--------- public/index.html | 141 ++++++++++++++++---------- public/js/rip.js | 235 +++++++++++++++++++++++++------------------ 6 files changed, 393 insertions(+), 209 deletions(-) diff --git a/.gitignore b/.gitignore index 3c8e882..54081be 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,7 @@ # ignored folders +.vscode/ node_modules/ src/ -# ignored files -.vscode/launch.json -jsconfig.json - # ignore the app configuration file public/js/the_config.js \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 323c63e..a788650 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,8 @@ "@tailwindcss/container-queries": "^0.1.1", "@tailwindcss/forms": "^0.5.6", "@tailwindcss/typography": "^0.5.10", + "prettier": "^3.0.3", + "prettier-plugin-tailwindcss": "^0.5.4", "tailwindcss": "^3.3.3" } }, @@ -829,6 +831,93 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true }, + "node_modules/prettier": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", + "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-tailwindcss": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.5.4.tgz", + "integrity": "sha512-QZzzB1bID6qPsKHTeA9qPo1APmmxfFrA5DD3LQ+vbTmAnY40eJI7t9Q1ocqel2EKMWNPLJqdTDWZj1hKYgqSgg==", + "dev": true, + "engines": { + "node": ">=14.21.3" + }, + "peerDependencies": { + "@ianvs/prettier-plugin-sort-imports": "*", + "@prettier/plugin-pug": "*", + "@shopify/prettier-plugin-liquid": "*", + "@shufo/prettier-plugin-blade": "*", + "@trivago/prettier-plugin-sort-imports": "*", + "prettier": "^3.0", + "prettier-plugin-astro": "*", + "prettier-plugin-css-order": "*", + "prettier-plugin-import-sort": "*", + "prettier-plugin-jsdoc": "*", + "prettier-plugin-organize-attributes": "*", + "prettier-plugin-organize-imports": "*", + "prettier-plugin-style-order": "*", + "prettier-plugin-svelte": "*" + }, + "peerDependenciesMeta": { + "@ianvs/prettier-plugin-sort-imports": { + "optional": true + }, + "@prettier/plugin-pug": { + "optional": true + }, + "@shopify/prettier-plugin-liquid": { + "optional": true + }, + "@shufo/prettier-plugin-blade": { + "optional": true + }, + "@trivago/prettier-plugin-sort-imports": { + "optional": true + }, + "prettier-plugin-astro": { + "optional": true + }, + "prettier-plugin-css-order": { + "optional": true + }, + "prettier-plugin-import-sort": { + "optional": true + }, + "prettier-plugin-jsdoc": { + "optional": true + }, + "prettier-plugin-marko": { + "optional": true + }, + "prettier-plugin-organize-attributes": { + "optional": true + }, + "prettier-plugin-organize-imports": { + "optional": true + }, + "prettier-plugin-style-order": { + "optional": true + }, + "prettier-plugin-svelte": { + "optional": true + }, + "prettier-plugin-twig-melody": { + "optional": true + } + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", diff --git a/package.json b/package.json index 5e9b5ee..3625a66 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "public/index.html", "scripts": { "start": "npm run build && live-server public", - "build": "tailwindcss build src/css/input.css -o public/css/style.css --watch" + "build": "tailwindcss build -i src/css/input.css -o public/css/style.css --watch" }, "repository": { "type": "git", @@ -21,6 +21,8 @@ "@tailwindcss/container-queries": "^0.1.1", "@tailwindcss/forms": "^0.5.6", "@tailwindcss/typography": "^0.5.10", + "prettier": "^3.0.3", + "prettier-plugin-tailwindcss": "^0.5.4", "tailwindcss": "^3.3.3" } -} +} \ No newline at end of file diff --git a/public/css/style.css b/public/css/style.css index 9f0b4a4..21ede9c 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -759,6 +759,19 @@ select { position: relative; } +.sticky { + position: sticky; +} + +.bottom-0 { + bottom: 0px; +} + +.mx-2 { + margin-left: 0.5rem; + margin-right: 0.5rem; +} + .mx-auto { margin-left: auto; margin-right: auto; @@ -773,6 +786,10 @@ select { margin-top: 0.25rem; } +.mt-auto { + margin-top: auto; +} + .block { display: block; } @@ -813,12 +830,20 @@ select { height: 1.5rem; } +.min-h-screen { + min-height: 100vh; +} + +.w-1\/2 { + width: 50%; +} + .w-1\/6 { width: 16.666667%; } -.w-36 { - width: 9rem; +.w-40 { + width: 10rem; } .w-5\/6 { @@ -833,13 +858,8 @@ select { width: 1.5rem; } -.w-40 { - width: 10rem; -} - -.min-w-fit { - min-width: -moz-fit-content; - min-width: fit-content; +.w-full { + width: 100%; } .min-w-full { @@ -892,6 +912,10 @@ select { border-radius: 0.25rem; } +.rounded-full { + border-radius: 9999px; +} + .rounded-l-lg { border-top-left-radius: 0.5rem; border-bottom-left-radius: 0.5rem; @@ -920,6 +944,11 @@ select { background-color: rgb(209 213 219 / var(--tw-bg-opacity)); } +.bg-slate-800 { + --tw-bg-opacity: 1; + background-color: rgb(30 41 59 / var(--tw-bg-opacity)); +} + .bg-slate-900 { --tw-bg-opacity: 1; background-color: rgb(15 23 42 / var(--tw-bg-opacity)); @@ -930,24 +959,19 @@ select { background-color: rgb(255 255 255 / var(--tw-bg-opacity)); } -.bg-slate-200 { - --tw-bg-opacity: 1; - background-color: rgb(226 232 240 / var(--tw-bg-opacity)); -} - .object-cover { -o-object-fit: cover; object-fit: cover; } -.p-4 { - padding: 1rem; -} - .p-2 { padding: 0.5rem; } +.p-4 { + padding: 1rem; +} + .px-8 { padding-left: 2rem; padding-right: 2rem; @@ -958,6 +982,21 @@ select { padding-bottom: 1rem; } +.px-4 { + padding-left: 1rem; + padding-right: 1rem; +} + +.py-2 { + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.py-3 { + padding-top: 0.75rem; + padding-bottom: 0.75rem; +} + .pt-8 { padding-top: 2rem; } @@ -971,6 +1010,15 @@ select { line-height: 2rem; } +.text-xs { + font-size: 0.75rem; + line-height: 1rem; +} + +.font-bold { + font-weight: 700; +} + .text-white { --tw-text-opacity: 1; color: rgb(255 255 255 / var(--tw-text-opacity)); @@ -982,23 +1030,12 @@ select { box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); } -.shadow-md { - --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); - --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); -} - .shadow-lg { --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color); box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); } -.shadow-slate-800 { - --tw-shadow-color: #1e293b; - --tw-shadow: var(--tw-shadow-colored); -} - .blur { --tw-blur: blur(8px); filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); @@ -1008,12 +1045,6 @@ select { filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); } -.transition-shadow { - transition-property: box-shadow; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 150ms; -} - .transition { transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; @@ -1026,14 +1057,6 @@ select { transition-duration: 150ms; } -.duration-200 { - transition-duration: 200ms; -} - -.duration-100 { - transition-duration: 100ms; -} - @keyframes spin { to { transform: rotate(360deg); @@ -1058,6 +1081,16 @@ select { background-color: rgb(226 232 240 / var(--tw-bg-opacity)); } +.hover\:bg-slate-800:hover { + --tw-bg-opacity: 1; + background-color: rgb(30 41 59 / var(--tw-bg-opacity)); +} + +.hover\:bg-slate-400:hover { + --tw-bg-opacity: 1; + background-color: rgb(148 163 184 / var(--tw-bg-opacity)); +} + .hover\:shadow-sm:hover { --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color); @@ -1078,11 +1111,4 @@ select { --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color); box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); -} - -@media (prefers-color-scheme: dark) { - .dark\:bg-slate-900 { - --tw-bg-opacity: 1; - background-color: rgb(15 23 42 / var(--tw-bg-opacity)); - } } \ No newline at end of file diff --git a/public/index.html b/public/index.html index 13850cf..7cf0d82 100644 --- a/public/index.html +++ b/public/index.html @@ -1,60 +1,91 @@ - + - R.I.P. Actors 0.3 - - - - - - - + R.I.P. Actors 0.3 + + + + + + + + + + + - - -
- -
- -
- -
-
- R.I.P. Actors -

- Find out whether your favourite actor is still with us. -

-
-
- - -
- - - -
- - -
- -
- - -
- - - + +
+ +
+ +
+ +
+
+ R.I.P. Actors +

Find out whether your favourite actor is still with us.

+
+
+ + +
+ + + +
- \ No newline at end of file +
+ +
+
+ + + + + + diff --git a/public/js/rip.js b/public/js/rip.js index 0597e05..4f45037 100644 --- a/public/js/rip.js +++ b/public/js/rip.js @@ -2,38 +2,107 @@ import { apiKey } from "./the_config.js"; function anazitisi() { - // Let's cache elements we access frequently for performance - var $btnS = $("#btnS"); - var $actor = $("#actor"); - var $list_group = $(".list-group"); + // Cache frequently accessed elements + const searchButton = $("#btnS"); + const actorInput = $("#actor"); + const listGroup = $(".list-group"); // Disable search button and change the text - $btnS.attr("disabled", true); - $btnS.text("Searching..."); + searchButton.prop("disabled", true).text("Searching..."); // Empty the list - $list_group.hide().empty(); + listGroup.hide().empty(); + + // Get the search query, sanitize the input and empty the field + const actor = actorInput.val().trim(); + actorInput.val(""); + + // Perform the search and handle the results + performSearch(actor, listGroup, searchButton) + .then(() => { + // Enable search button again after a delay + setTimeout(() => { + searchButton.prop("disabled", false).text("Search"); + }, 5000); + }) + .catch((error) => { + // Handle errors + console.error(error); + searchButton.prop("disabled", false).text("Search"); + }); +} + +// Function to perform the search +async function performSearch(actor, listGroup, searchButton, page = 1, pageSize = 10) { + // Declare the API URL + const apiUrl = `https://api.themoviedb.org/3/search/person?api_key=${apiKey}&query=${actor}&page=${page}`; + + try { + // Fetch the search results from the API + const data = await fetchJson(apiUrl); - //get the search query, sanitize the input and empty the field - const actor = $actor.val(); - $actor.val(""); + // Get the actor data for each search result + const actors = await getActors(data.results.slice(0, pageSize)); - performSearch(actor, $list_group, $btnS); + // Create an actor card for each actor + const actorCards = actors.map(createActorCard); + + // Append the actor cards to the list group and show it + appendActorCards(actorCards, listGroup); + listGroup.show(); + + // Check if there are more pages and add a "Load More" button + if (data.total_pages > page) { + addLoadMoreButton(actor, listGroup, searchButton, page + 1, pageSize); + } + + } catch (error) { + // Handle errors + console.error(error); + } finally { + // Enable search button again + searchButton.prop("disabled", false).text("Search"); + } +} + +// Function to fetch JSON data from a URL +async function fetchJson(url) { + const response = await fetch(url); + return response.json(); +} + + +// Function to get the actor data for each search result +async function getActors(actorArray) { + // Map each search result to a Promise that resolves to the actor data + const promises = actorArray.map(async (actorInfo) => { + const id = actorInfo.id; + const actor = await getActor(id); + return actor; + }); + // Wait for all Promises to resolve and return the actor data + return Promise.all(promises); +} - //enable search button again, wait for 10 sec and change the text - setTimeout(function () { - document.getElementById("btnS").disabled = false; - document.getElementById("btnS").innerHTML = "Search"; - }, 5000); +// Function to get the actor data for a single actor ID +async function getActor(actorId) { + // Declare the API URL + const apiUrl = `https://api.themoviedb.org/3/person/${actorId}?api_key=${apiKey}`; + + // Fetch the actor data from the API and return the relevant properties + const data = await fetchJson(apiUrl); + return { + id: data.id, + name: data.name, + deathday: data.deathday ? data.deathday : "N/A", + birthday: data.birthday ? data.birthday : "N/A", + image: data.profile_path ? "https://image.tmdb.org/t/p/w185" + data.profile_path : "https://via.placeholder.com/185x278?text=No+Image", + }; } -// Θα προσπαθήσω να γράψω την performSearch με promises αντι για callbacks -// τελικά χρησιμοποίησα το async/await γιατί είναι πιο εύκολο και κατανοητό -async function performSearch(actor, $list_group, $btnS) { - /* declare api url */ - const url = `https://api.themoviedb.org/3/search/person?api_key=${apiKey}&query=${actor}`; - console.log(url); - /* declare icons */ +// Function to create an actor card HTML string +function createActorCard(actor) { + // Declare icons const icon_heart = '' + '' + ''; - try { - const response = await fetch(url); - const data = await response.json(); - const actor_array = data.results; - const len = actor_array.length; - - /* if no results found */ - if (len === 0) { - $list_group.append("
  • No results found
  • "); - return; // stop execution - } - - /* if results found */ - const promises = actor_array.map(async (actorInfo) => { - const id = actorInfo.id; - const actor = await get_actor(id); - return actor; // return the actor object - }); - - /* wait for all promises to resolve */ - const actors = await Promise.all(promises); - - /* create the actor cards */ - actors.forEach(actor => { - let new_span; - if (!actor.deathday || actor.deathday === "undefined") { - new_span = $("" + icon_question + " N/A"); - } else if (actor.deathday.indexOf("-") >= 0) { - new_span = $("" + icon_rip + "R.I.P."); - } else { - new_span = $("" + icon_heart + " ALIVE"); - } - - const new_actor = ` -
    - - ${actor.name} - ${actor.name} - Birthday: ${actor.birthday} - Deathday: ${actor.deathday} - ${new_span.prop('outerHTML')} - -
    - `; - - $list_group.append(new_actor); - }); - - } catch (error) { - $list_group.append("
  • Something went wrong.
  • "); - console.error(error); - } finally { - /* show the list */ - $list_group.slideDown("slow"); - $btnS.attr("disabled", false); // enable search button - $btnS.text("Search"); + // Create the actor card HTML string + let new_span; + if (!actor.deathday || actor.deathday === "undefined") { + new_span = $("" + icon_question + " N/A"); + } else if (actor.deathday.indexOf("-") >= 0) { + new_span = $("" + icon_rip + "R.I.P."); + } else { + new_span = $("" + icon_heart + " ALIVE"); } -} -// Θα προσπαθήσω να γράψω την get_actor με async/await αντι για promises -async function get_actor(id) { - const url = `https://api.themoviedb.org/3/person/${id}?api_key=${apiKey}`; + const new_actor = ` +
    + + ${actor.name} + ${actor.name} + Birthday: ${actor.birthday} + Deathday: ${actor.deathday} + ${new_span.prop('outerHTML')} + +
    + `; + return new_actor; +} - try { - const response = await fetch(url); - const data = await response.json(); - const actor = { - id: data.id, - name: data.name, - deathday: data.deathday ? data.deathday : "N/A", - birthday: data.birthday ? data.birthday : "N/A", - image: data.profile_path ? "https://image.tmdb.org/t/p/w185" + data.profile_path : "https://via.placeholder.com/185x278?text=No+Image", - - }; - return actor; - } catch (error) { - console.error(error); - throw error; +// Function to append the actor cards to the list group +function appendActorCards(actorCards, listGroup) { + if (actorCards.length === 0) { + listGroup.append("
    No results found
    "); + } else { + listGroup.append(actorCards.join("")); } } +// Function to add a "Load More" button to the list group +function addLoadMoreButton(actor, listGroup, searchButton, nextPage) { + const loadMoreButton = $("