diff --git a/src/main/resources/static/add.css b/src/main/resources/static/add.css index 6d62a6f..e4cc24d 100644 --- a/src/main/resources/static/add.css +++ b/src/main/resources/static/add.css @@ -1,9 +1,5 @@ @import url("main.css"); -:root { - --accent-color: oklch(65.33% 0.158 247.76); -} - form { display: grid; grid-template-columns: 1fr 1fr auto; diff --git a/src/main/resources/static/add.js b/src/main/resources/static/add.js index 280eb67..53292ad 100644 --- a/src/main/resources/static/add.js +++ b/src/main/resources/static/add.js @@ -1,3 +1,5 @@ +import Dataset from "./dataset.js"; + const form = document.forms[0]; const { title: titleEntry, @@ -23,7 +25,11 @@ const validationListener = () => { fullDescriptionEntry, ].forEach(input => input.addEventListener("input", validationListener)); -form.addEventListener("submit", e => { +cancelBtn.addEventListener("click", () => { + window.location.href = location.origin; +}) + +form.addEventListener("submit", async e => { e.preventDefault(); if (!form.reportValidity()) return; @@ -43,17 +49,20 @@ form.addEventListener("submit", e => { // Don't allow several requests to be sent at the same time addBtn.disabled = true; - fetch("/api/v1/datasets", { + let response = await fetch("/api/v1/datasets", { method: "POST", body: JSON.stringify(newContent), headers: { "Content-Type": "application/json;charset=utf-8" } - }).then(response => { - if (response.status == 200) { - location.assign("/"); - } else { - addBtn.disabled = !form.checkValidity(); - } + }); + let data = await response.json(); + let dataset = new Dataset(data); + dataset.storageSetKey("created-locally", true); + if (response.ok) { + location.assign("/"); + } else { + addBtn.disabled = !form.checkValidity(); + } }); diff --git a/src/main/resources/static/constants.js b/src/main/resources/static/constants.js new file mode 100644 index 0000000..ed33038 --- /dev/null +++ b/src/main/resources/static/constants.js @@ -0,0 +1,2 @@ +export const DATASET_ENDPOINT = "/api/v1/datasets"; +export const getBaseURL = () => location.origin; diff --git a/src/main/resources/static/dataset.js b/src/main/resources/static/dataset.js index cc654e1..e7f0934 100644 --- a/src/main/resources/static/dataset.js +++ b/src/main/resources/static/dataset.js @@ -1,13 +1,13 @@ -import { DATASET_ENDPOINT, getBaseURL } from "./main.js"; +import { DATASET_ENDPOINT, getBaseURL } from "./constants.js"; export default class Dataset { static #datasets = new Map(); - #abstract; + #shortDescription; #author; - #categories; + #category; #date; - #description; + #fullDescription; #id; #rating; #title; @@ -15,48 +15,52 @@ export default class Dataset { #upvotes; #url; #votes; + #license; + #termsOfUse; #elements = []; 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; + constructor({ abst: shortDescription, author, categorie, date, description, id, raiting, title, type, upvotes, url, votes, license, termsOfUse }) { + this.#shortDescription = shortDescription; this.#author = author; - this.#categories = categories; + this.#category = categorie; this.#date = date; - this.#description = description; + this.#fullDescription = description; this.#id = id; - this.#rating = rating; + this.#rating = raiting; this.#title = title; this.#type = type; this.#upvotes = upvotes; this.#url = url; this.#votes = votes; + this.#license = license; + this.#termsOfUse = termsOfUse; Dataset.#datasets.set(id, this); } // Getters - get abstract() { - return this.#abstract; + get shortDescription() { + return this.#shortDescription; } get author() { return this.#author; } - get categories() { - return this.#categories; + get category() { + return this.#category; } get date() { return this.#date; } - get description() { - return this.#description; + get fullDescription() { + return this.#fullDescription; } get id() { @@ -87,6 +91,14 @@ export default class Dataset { return this.#votes; } + get license() { + return this.#license; + } + + get termsOfUse() { + return this.#termsOfUse; + } + get mainElement() { return this.#elements[0]; } @@ -95,6 +107,10 @@ export default class Dataset { return this.#elements; } + parseDate() { + return new Date(this.#date); + } + // Main methods // Only on main page @@ -114,7 +130,7 @@ export default class Dataset { } }) clone.querySelector(".dataset-title").innerText = this.#title; - clone.querySelector(".dataset-description").innerText = this.#description; + clone.querySelector(".dataset-description").innerText = this.#shortDescription; clone.querySelector(".upvote-count").innerText = this.#upvotes; this.#elements.push(clone.children[0]); diff --git a/src/main/resources/static/details.css b/src/main/resources/static/details.css new file mode 100644 index 0000000..0290013 --- /dev/null +++ b/src/main/resources/static/details.css @@ -0,0 +1,180 @@ +@import url("./main.css"); +@import url('https://fonts.googleapis.com/css2?family=Flow+Circular&display=swap'); + +:root { + --min-card-size: min(60ch, calc(100vw - var(--pad-main))); + --rating-color: gold; +} + +main { + & > :is(header, section, footer) { + background-color: var(--fg-color); + padding: var(--pad-datasets-block) var(--pad-datasets-inline); + position: relative; + } + + & > :first-child { + border-radius: var(--corner-radius) var(--corner-radius) 0 0; + margin-top: var(--pad-main); + } + + & > :last-child { + border-radius: 0 0 var(--corner-radius) var(--corner-radius); + margin-bottom: var(--pad-main); + } + + & > :not(:last-child):is(header, section)::after { + content: ''; + position: absolute; + inset-inline: calc(var(--pad-datasets-inline) - var(--gap-small)); + bottom: 0; + border-bottom: 3px solid var(--bg-color); + opacity: .25; + transform: translateY(50%); + z-index: 1; + } +} + +header { + margin-inline: 0; + display: grid; + grid-template-columns: 1fr 1fr max-content; + align-items: center; + grid-gap: var(--gap-medium); +} + +#title { + grid-column: 1 / 3; + margin-block: var(--gap-medium) 0; + text-align: center; + + &::after { + content: attr(data-type); + background-color: var(--accent-color); + font-size: .5em; + font-weight: initial; + position: relative; + bottom: .25lh; + margin-inline: var(--gap-small); + padding: 2pt 4pt; + border-radius: 4pt; + } + + &[data-type="dataset"]::after { + content: "Dataset"; + } + + &[data-type="api"]::after { + content: "API"; + } +} + +:has(#rating), #url { + text-align: start; + grid-column: 1 / 3; +} + +#rating { + color: color-mix(in oklab, var(--text-color) 80%, black); +} + +#rating::after { + content: ""; + display: inline-block; + width: 5em; + height: 1lh; + vertical-align: bottom; + margin-inline: .5ch; + mask-image: url("stars.svg"); + -webkit-mask-image: url("stars.svg"); + mask-size: 100% 100%; + mask-mode: alpha; + --rating-percent: calc((var(--rating, 0) / 5) * 100%); + background: linear-gradient(to right, var(--rating-color) var(--rating-percent), var(--bg-color) var(--rating-percent)); +} + +a { + --text-color: var(--accent-color); + + /* + Why doesn't it do this automatically? It is inherited from body, + so I should be able to just change --text-color, right? + */ + color: var(--text-color); +} + +#terms-of-use { + /* text-align: end; */ +} + +.upvote { + margin: var(--gap-medium) 0; + align-self: self-start; + grid-column: 3; + grid-row: 1 / 4; +} + +#metadata { + display: flex; + justify-content: space-around; + flex-wrap: wrap; + gap: var(--gap-large); +} + +#full-description { + text-wrap: balance; + text-wrap: pretty; + margin-top: 0; + + br { + margin-bottom: .5lh; + } +} + +.skeleton { + font-family: "Flow Circular"; + font-weight: 400; + font-style: normal; + user-select: none; + --rating-color: var(--text-color); + + a, .btn { + pointer-events: none; + } + + & > * { + cursor: progress; + } + + @media screen and not (prefers-reduced-motion) { + :is(header, section) > :is(p, span, h1, a) { + animation: infinite 2s linear skeleton-loading; + background: radial-gradient( + circle farthest-side, + var(--highlight-color, white) 20%, + var(--text-color) 40% + ) no-repeat, var(--text-color); + background-clip: text; + -webkit-background-clip: text; + color: transparent; + + &:is(a) { + --highlight-color: color-mix(in oklab, white, var(--text-color)); + } + } + } + + #title::after { + color: color-mix(in oklab, var(--accent-color) 50%, currentcolor); + } +} + +@keyframes skeleton-loading { + from { + background-position-x: calc(-1.4 * var(--min-card-size)), 0; + } + + to { + background-position-x: calc(1.4 * var(--min-card-size)), 0; + } +} diff --git a/src/main/resources/static/details.html b/src/main/resources/static/details.html new file mode 100644 index 0000000..ab97874 --- /dev/null +++ b/src/main/resources/static/details.html @@ -0,0 +1,63 @@ + + + + + + Dataset details + + + + + + + + + +
+
+

Title

+ + 4 + Lorem ipsum dolor sit amet consectetur adipisicing elit. Perspiciatis recusandae laborum odio corrupti voluptas quisquam dicta, quibusdam ipsum qui exercitationem. + + https://example.com/dataset + +
+ +
+ Added on: 1. January 1970 + Category: Something + License: MIT + Terms of Use +
+ +
+

Full description
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Beatae + nihil saepe et numquam quo id voluptatum recusandae assumenda + doloremque pariatur consequatur molestias delectus dolore + corrupti, odio modi vitae repellat tempora sed eos voluptates + temporibus veritatis repudiandae. Cum eveniet molestias, in + beatae non reiciendis quia voluptatem id, facere architecto + vitae harum ipsum earum deleniti dolor atque recusandae odit + corporis error dolorum blanditiis vel maxime pariatur quibusdam! +
Saepe debitis ab possimus, dolorem neque ad voluptatibus ex + quisquam itaque. Nihil et non consequuntur error ipsa. + Necessitatibus voluptatibus aliquid itaque id ipsum, pariatur + odio explicabo, dolores ex, incidunt tenetur dolore. Assumenda + ipsam nobis quis. +

+
+
+ + \ No newline at end of file diff --git a/src/main/resources/static/details.js b/src/main/resources/static/details.js new file mode 100644 index 0000000..26991c6 --- /dev/null +++ b/src/main/resources/static/details.js @@ -0,0 +1,51 @@ +import Dataset from "./dataset.js"; + +const mainElement = document.getElementsByTagName("main")[0]; + +const title = document.getElementById("title"); +const rating = document.getElementById("rating"); +const shortDescription = document.getElementById("short-description"); +const url = document.getElementById("url"); +const date = document.getElementById("date"); +const category = document.getElementById("category"); +const license = document.getElementById("license"); +const termsOfUse = document.getElementById("terms-of-use"); +const fullDescription = document.getElementById("full-description"); + +const currentLocation = new URL(location.href); +if (currentLocation.searchParams.has("id")) { + const id = currentLocation.searchParams.get("id"); + const response = await fetch(`${currentLocation.origin}/api/v1/datasets/id/${id}`); + console.dir(response); + + if (response.ok) { + const data = await response.json(); + const dataset = new Dataset(data); + console.dir(data, dataset); + const upvoteComponent = dataset.createUpvoteComponent(); + + title.innerText = dataset.title; + title.dataset.type = dataset.type.toLowerCase(); + rating.innerText = dataset.rating; + rating.style.setProperty("--rating", `${dataset.rating}`); + shortDescription.innerText = dataset.shortDescription; + url.href = dataset.url; + url.innerText = dataset.url; + mainElement.querySelector(".upvote").replaceWith(upvoteComponent); + + date.innerText = dataset.parseDate().toLocaleDateString(undefined, { + day: "numeric", + month: "long", + year: "numeric", + }); + date.dataset.date = dataset.parseDate().getTime(); + category.innerText = dataset.category.name; + category.dataset.id = dataset.category.id; + license.innerText = dataset.license; + termsOfUse.href = dataset.termsOfUse; + + fullDescription.innerText = dataset.fullDescription; + + mainElement.classList.remove("skeleton"); + } +} diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html index c6e00e9..7f58693 100644 --- a/src/main/resources/static/index.html +++ b/src/main/resources/static/index.html @@ -4,6 +4,9 @@ DataDash + + + diff --git a/src/main/resources/static/main.css b/src/main/resources/static/main.css index ae3eb64..0ed8d3a 100644 --- a/src/main/resources/static/main.css +++ b/src/main/resources/static/main.css @@ -2,7 +2,12 @@ --bg-color: #222; --fg-color: #555; --text-color: #dbdbdb; - --pad-datasets: 1rem; + --accent-color: oklch(65.33% 0.158 247.76); + --pad-datasets-block: 1rem; + --pad-datasets-inline: 2rem; + --gap-large: 1.5rem; + --gap-medium: 1rem; + --gap-small: .5rem; --pad-main: 2rem; --min-card-size: min(60ch, 33vw); --corner-radius: 1rem; @@ -17,7 +22,7 @@ body { } main { - max-width: calc(2 * var(--min-card-size) + var(--pad-main) + var(--pad-datasets)); + max-width: calc(2 * var(--min-card-size) + var(--pad-main) + 2 * var(--pad-datasets-inline)); padding-inline: var(--pad-main); margin-inline: auto; container-type: inline-size; @@ -47,12 +52,12 @@ header { display: flex; flex-direction: row; float: right; - gap: .5rem; + gap: var(--gap-small); background-color: var(--fg-color, darkgrey); padding: .5rem 1rem; - margin-bottom: var(--pad-datasets); - margin-left: var(--pad-datasets); - margin-right: var(--pad-datasets); + margin-bottom: var(--pad-datasets-block); + margin-left: var(--pad-datasets-inline); + margin-right: var(--pad-datasets-inline); border-radius: 1.5rem; } @@ -83,10 +88,10 @@ header { } .datasets { - padding-inline: var(--pad-datasets); + padding-inline: var(--pad-datasets-inline); display: grid; grid-template-columns: repeat(auto-fit, minmax(var(--min-card-size), 1fr)); - gap: 1rem; + gap: var(--gap-medium); } .hidden { @@ -101,7 +106,7 @@ header { } .dataset { - padding: var(--pad-datasets) 2rem; + padding: var(--pad-datasets-block) var(--pad-datasets-inline); background-color: var(--fg-color, darkgrey); border-radius: var(--corner-radius); list-style: none; @@ -122,7 +127,7 @@ header { display: flex; flex-direction: column; align-items: center; - gap: .5em; + gap: var(--gap-small); } /* Buttons */ diff --git a/src/main/resources/static/main.js b/src/main/resources/static/main.js index 08ba6b1..ad98c3b 100644 --- a/src/main/resources/static/main.js +++ b/src/main/resources/static/main.js @@ -1,9 +1,7 @@ +import { DATASET_ENDPOINT, getBaseURL } from "./constants.js"; import { fetchQuery } from "./contentUtility.js"; import Dataset from "./dataset.js"; -export const DATASET_ENDPOINT = "/api/v1/datasets"; -export const getBaseURL = () => location.origin; - const defaultPagingValue = 20; export const lastQuery = { totalPages: 0, diff --git a/src/main/resources/static/stars.svg b/src/main/resources/static/stars.svg new file mode 100644 index 0000000..0dbeb2d --- /dev/null +++ b/src/main/resources/static/stars.svg @@ -0,0 +1,127 @@ + + + +