diff --git a/src/main/resources/static/contentUtility.js b/src/main/resources/static/contentUtility.js index c8adbb8..6854b66 100644 --- a/src/main/resources/static/contentUtility.js +++ b/src/main/resources/static/contentUtility.js @@ -1,30 +1,35 @@ -import {searchBarTimeout, searchSection, lastQuery} from "./main.js" -import Dataset from "./dataset.js" // TODO consider renaming this to "searchUtility.js" -export function fetchQuery(fetchString, clearResults) { + +import { searchBarTimeout, searchSection, lastQuery } from "./main.js" +import Dataset from "./dataset.js" + +export async function fetchQuery(fetchString, clearResults) { clearTimeout(searchBarTimeout); - fetch(fetchString) - .then(resp => resp.json()) - .then((data) => { - parseContent(data.content, clearResults); - lastQuery.totalPages = data.totalPages; - if (clearResults) { - lastQuery.currentPage = 0; - } - }); + const response = await fetch(fetchString); + const data = await response.json(); + + parseContent(data.content, clearResults); + lastQuery.totalPages = data.totalPages; + if (clearResults) { + lastQuery.currentPage = 0; + } } function parseContent(content, clearResults) { + const nothingFoundElement = searchSection.querySelector("#nothing-found"); + if (content.length === 0) { - searchSection.querySelector("#nothing-found ").classList.remove("hidden"); + nothingFoundElement.classList.remove("hidden"); searchSection.querySelector(".datasets").classList.add("hidden"); } else { - searchSection.querySelector("#nothing-found").classList.add("hidden"); + nothingFoundElement.classList.add("hidden"); + + const datasets = content.map(dataset => Dataset.get(dataset.id) ?? new Dataset(dataset)); searchSection.querySelector(".datasets").classList.remove("hidden"); - const datasets = content.map(dataset => new Dataset(dataset)); if (clearResults) { Array.from(searchSection.querySelectorAll(".datasets .dataset")).forEach(e => e.remove()); } + for (const dataset of datasets) { searchSection.querySelector(".datasets") .appendChild(dataset.createDatasetHTMLElement()); diff --git a/src/main/resources/static/dataset.js b/src/main/resources/static/dataset.js index bd33176..1c5d40a 100644 --- a/src/main/resources/static/dataset.js +++ b/src/main/resources/static/dataset.js @@ -1,6 +1,8 @@ -import { vote } from "./main.js"; +import { DATASET_ENDPOINT, getBaseURL } from "./main.js"; export default class Dataset { + static #datasets = new Map(); + #abstract; #author; #categories; @@ -13,8 +15,13 @@ export default class Dataset { #upvotes; #url; #votes; + #elements = []; - constructor({abst: shortDescription, author, categories, date, description, id, rating, title, type, upvotes, url, votes}) { + static get(id) { + return this.#datasets.get(id); + } + + constructor({ abst: shortDescription, author, categories, date, description, id, rating, title, type, upvotes, url, votes }) { this.#abstract = shortDescription; this.#author = author; this.#categories = categories; @@ -27,34 +34,179 @@ export default class Dataset { this.#upvotes = upvotes; this.#url = url; this.#votes = votes; + + Dataset.#datasets.set(id, this); } + // Getters + get abstract() { + return this.#abstract; + } + + get author() { + return this.#author; + } + + get categories() { + return this.#categories; + } + + get date() { + return this.#date; + } + + get description() { + return this.#description; + } + + get id() { + return this.#id; + } + + get rating() { + return this.#rating; + } + + get title() { + return this.#title; + } + + get type() { + return this.#type; + } + + get upvotes() { + return this.#upvotes; + } + + get url() { + return this.#url; + } + + get votes() { + return this.#votes; + } + + get mainElement() { + return this.#elements[0]; + } + + get elements() { + return this.#elements; + } + + // Main methods + + // Only on main page createDatasetHTMLElement() { - let template = document.querySelector("#dataset-template"); - const clone = template.content.cloneNode(true); - clone.querySelector(".dataset").dataset.id = this.#id; - clone.querySelector("h3").innerText = this.#title; - clone.querySelector("p").innerText = this.#description; - clone.querySelector("span").innerText = this.#upvotes; - let votedIDs = window.localStorage; - // depending on whether the button has been up/downvoted, its according button gets disabled and hidden - if (votedIDs.getItem(this.#id)) { - let votedButton = clone.querySelector(votedIDs.getItem(this.#id)? ".upvote-btn":".downvote-btn"); - votedButton.classList.add("isVoted"); - votedButton.disabled = true; - let notVotedButton = clone.querySelector(votedIDs.getItem(this.#id)? ".downvote-btn":".upvote-btn"); - notVotedButton.style.visibility = "hidden"; + const clone = this.#createTemplateInstance("dataset-template"); + if (clone == null) { + return null; } - // Event Listeners - clone.querySelector(".upvote-btn").addEventListener("click", () => { - vote(this.#id, true); - }); + clone.querySelector(".dataset").dataset.id = this.#id; + clone.querySelector(".dataset-title").innerText = this.#title; + clone.querySelector(".dataset-description").innerText = this.#description; + clone.querySelector(".upvote-count").innerText = this.#upvotes; - clone.querySelector(".downvote-btn").addEventListener("click", () => { - vote(this.#id, false); - }) + this.#elements.push(clone.children[0]); + this.#createUpvoteButtons(clone); return clone; } + + // Only on details page + createUpvoteComponent() { + let clone = this.#createTemplateInstance("voting-template") + + clone.querySelector(".upvote-count").innerText = this.#upvotes; + + this.#elements.push(clone.children[0]); + this.#createUpvoteButtons(clone); + + return clone; + } + + #createUpvoteButtons(templateInstance) { + if (this.storageGetKey("is-voted", false)) { + // The template instance (clone) has to be pushed before this can work + this.#disableVoteBtns(); + } + + // Event Listeners + templateInstance.querySelector(".upvote-btn").addEventListener("click", () => { + this.vote(); + }); + + templateInstance.querySelector(".downvote-btn").addEventListener("click", () => { + this.vote(false); + }) + } + + #createTemplateInstance(templateId) { + let template = document.getElementById(templateId); + if (template == null) { + return null; + } + + return template.content.cloneNode(true); + } + + isMainElement(element) { + return this.#elements.length > 0 && this.#elements[0].isEqualNode(element); + } + + async vote(up = true) { + const fetchURL = new URL( + `${DATASET_ENDPOINT}/id/${this.#id}/${up ? "up" : "down"}vote`, + getBaseURL(), + ); + + const response = await fetch(fetchURL, { + method: "PUT", + headers: { + 'Content-Type': 'application/json', + } + }); + const data = await response.json(); + + this.#upvotes = data.upvotes; + for (const element of this.#elements) { + element.querySelector(".upvote-count").innerText = this.#upvotes; + } + + this.storageSetKey("vote-type", up ? "upvote" : "downvote"); + this.storageSetKey("is-voted", true); + this.#disableVoteBtns(); + } + + #disableVoteBtns() { + if (this.#elements.length > 0) { + const voteType = this.storageGetKey("vote-type", null); + + this.#elements.flatMap(e => Array.from(e.querySelectorAll(".upvote .btn"))) + .forEach(btn => { + btn.disabled = true; + + if (btn.classList.contains(`${voteType}-btn`)) { + btn.classList.add("isVoted"); + } + }); + } + } + + storageGet() { + const json = localStorage.getItem(this.#id); + return json != null ? JSON.parse(json) : {}; + } + + storageSetKey(key, value) { + let currentStorage = this.storageGet(); + currentStorage[key] = value; + localStorage.setItem(this.#id, JSON.stringify(currentStorage)); + } + + storageGetKey(key, defaultValue) { + return this.storageGet()?.[key] ?? defaultValue; + } } diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html index 5f8cbdd..c6e00e9 100644 --- a/src/main/resources/static/index.html +++ b/src/main/resources/static/index.html @@ -19,8 +19,8 @@
  • -

    title

    -

    Simply daily accountability phone call, powered by AI

    +

    title

    +

    Simply daily accountability phone call, powered by AI