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
+
+
+
+
+
+
+
+
+
+ Upvote
+ 0
+ Downvote
+
+
+
+
+
+ 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
+
+ Upvote
+ 0
+ Downvote
+
+
+
+
+
+
+ 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 @@
+
+
+
+