Merge branch '22-integrate-api-and-frontend' into '39-add-tests'

# Conflicts:
#   src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/Dataset/DatasetController.java
This commit is contained in:
Erik Foris 2024-07-06 18:23:09 +02:00
commit 54b7a916ed
18 changed files with 592 additions and 149 deletions

View File

@ -10,6 +10,7 @@ import de.uni_passau.fim.PADAS.group3.DataDash.category.Category;
import java.sql.Date;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
@ -46,6 +47,9 @@ public class Dataset {
private URL url;
@Column(name = "terms_of_use")
private URL termsOfUse;
private String licence;
private static final List<String> sortable = Arrays.asList("author", "title", "upvotes", "date");
@ -121,6 +125,10 @@ public class Dataset {
return url;
}
public URL getTermsOfUse() {
return termsOfUse;
}
public String getLicence() {
return licence;
}
@ -152,6 +160,10 @@ public class Dataset {
public void setUrl(URL url) {
this.url = url;
}
public void setTermsOfUse(URL termsOfUse) {
this.termsOfUse = termsOfUse;
}
public void setTitle(String title) {
this.title = title.substring(0, Math.min(title.length(), 50));

View File

@ -80,24 +80,6 @@ public class DatasetController {
return new ResponseEntity<>(datasetService.getDatasetById(id), HttpStatus.OK);
}
@GetMapping
public Page<Dataset> getDatasetsByDateAfter(@RequestParam(value = "author", required = false) String author,
@RequestParam(value = "title", required = false) String title,
@RequestParam(value = "description", required = false) String description,
@RequestParam(value = "abst", required = false) String abst,
@RequestParam(value = "type", required = false) Type type,
@RequestParam(value = "min-rating", required = false) Float rating,
@RequestParam(value = "page", required = false, defaultValue = "0") int page,
@RequestParam(value = "size", required = false, defaultValue = "20") int size,
@RequestParam(value = "sort", required = false, defaultValue = "upvotes") String sort,
@RequestParam(value = "direction", required = false, defaultValue = "desc") String direction,
@RequestParam(value = "category", required = false) Category category) {
Pageable pageable = PageRequest.of(page, size,
Sort.by(Sort.Direction.fromString(direction), sort));
return datasetService.getDatasetsByOptionalCriteria(title, description, author, abst, type, rating, category,
pageable);
}
@GetMapping("/search")
public ResponseEntity<Page<Dataset>> search(
@RequestParam(value = "search", required = false, defaultValue = "%") String search,

View File

@ -23,10 +23,6 @@ public class DatasetService {
this.categoryRepository = categoryRepository;
}
public List<Dataset> getAllDatasets() {
return datasetRepository.findAll();
}
public Dataset getDatasetById(UUID id) {
return datasetRepository.getDatasetById(id);
}
@ -36,10 +32,6 @@ public class DatasetService {
return datasetRepository.save(dataset);
}
public void updateDatasetTitle(UUID id, String title) {
datasetRepository.getDatasetById(id).setTitle(title);
}
public void voteDataset(UUID id, int vote) {
Dataset dataset = datasetRepository.getDatasetById(id);
dataset.vote(vote);
@ -51,34 +43,6 @@ public class DatasetService {
datasetRepository.delete(dataset);
}
public List<Dataset> getDatasetsByTitle(String title) {
return datasetRepository.findByTitle(title);
}
public List<Dataset> getDatasetsByTitleLike(String title) {
return datasetRepository.findByTitleLike(title);
}
public List<Dataset> findByDescriptionLike(String description) {
return datasetRepository.findByDescriptionLike(description);
}
public List<Dataset> getDatasetsByAuthorLike(String author) {
return datasetRepository.findByAuthorLike(author);
}
public List<Dataset> getDatasetsByType(Type type) {
return datasetRepository.findByType(type);
}
public List<Dataset> getDatasetsByAbstLike(String abst) {
return datasetRepository.findByAbstLike(abst);
}
public List<Dataset> getDatasetsByRaitingGreaterThan(float raiting) {
return datasetRepository.findByRaitingGreaterThan(raiting);
}
public void upvoteDataset(UUID id) {
Dataset dataset = datasetRepository.getDatasetById(id);
dataset.upvote();
@ -91,14 +55,6 @@ public class DatasetService {
datasetRepository.save(dataset);
}
public Page<Dataset> getDatasetsByOptionalCriteria(String title, String description, String author, String abst,
Type type, Float raiting, Category category, Pageable pageable) {
return datasetRepository.findByOptionalCriteria(Optional.ofNullable(title), Optional.ofNullable(description),
Optional.ofNullable(author), Optional.ofNullable(abst), Optional.ofNullable(type),
Optional.ofNullable(category),
Optional.ofNullable(raiting), pageable);
}
public Page<Dataset> searchByOptionalCriteria(String search, String categories, String type, Pageable pageable) {
Category category = categories.equals("%") ? null
: categoryRepository.getCategoryById(UUID.fromString(categories));

View File

@ -17,32 +17,6 @@ public interface dataRepository extends JpaRepository<Dataset, UUID> {
Dataset getDatasetById(UUID id);
List<Dataset> findByTitle(String title);
List<Dataset> findByTitleLike(String title);
List<Dataset> findByAuthorLike(String author);
List<Dataset> findByType(Type type);
List<Dataset> findByAuthor(String author);
List<Dataset> findByAbstLike(String abst);
List<Dataset> findByDescriptionLike(String description);
List<Dataset> findByRaitingGreaterThan(float raiting);
List<Dataset> findByVotesGreaterThan(int votes);
List<Dataset> findByDateAfter(Date date);
List<Dataset> findByDateBefore(Date date);
List<Dataset> findByCategorie(Category categorie);
List<Dataset> findByDateBetween(Date date1, Date date2);
@SuppressWarnings("null")
Page<Dataset> findAll(Pageable pageable);

View File

@ -17,4 +17,4 @@ public interface CategoryRepository extends JpaRepository<Category, UUID>{
@SuppressWarnings("null")
Optional<Category> findById(UUID id);
}
}

View File

@ -10,21 +10,21 @@ INSERT INTO category (id, name) VALUES
('123e4567-e89b-12d3-a456-426614174002', 'Health');
-- Insert sample data into dataset
INSERT INTO dataset (date, raiting, upvotes, votes, categorie_id, id, abst, author, description, title, url, type, licence) VALUES
('2023-01-01', 4.5, 100, 120, '123e4567-e89b-12d3-a456-426614174000', '123e4567-e89b-12d3-a456-426614174100', 'Abstract 1', 'Author 1', 'Description 1', 'Title 1', 'http://example.com/1', 'API', 'MIT'),
('2023-01-02', 4.7, 150, 170, '123e4567-e89b-12d3-a456-426614174001', '123e4567-e89b-12d3-a456-426614174101', 'Abstract 2', 'Author 2', 'Description 2', 'Title 2', 'http://example.com/2', 'DATASET', 'MIT'),
('2023-01-03', 4.9, 200, 220, '123e4567-e89b-12d3-a456-426614174002', '123e4567-e89b-12d3-a456-426614174102', 'Abstract 3', 'Author 3', 'Description 3', 'Title 3', 'http://example.com/3', 'API', 'MIT'),
('2023-01-04', 4.2, 80, 100, '123e4567-e89b-12d3-a456-426614174003', '123e4567-e89b-12d3-a456-426614174103', 'Abstract 4', 'Author 4', 'Description 4', 'Title 4', 'http://example.com/4', 'DATASET', 'MIT'),
('2023-01-05', 4.6, 120, 140, '123e4567-e89b-12d3-a456-426614174004', '123e4567-e89b-12d3-a456-426614174104', 'Abstract 5', 'Author 5', 'Description 5', 'Title 5', 'http://example.com/5', 'API', 'MIT');
INSERT INTO dataset (date, raiting, upvotes, votes, categorie_id, id, abst, author, description, title, url, type, licence, terms_of_use) VALUES
('2023-01-01', 4.5, 100, 120, '123e4567-e89b-12d3-a456-426614174000', '123e4567-e89b-12d3-a456-426614174100', 'Abstract 1', 'Author 1', 'Description 1', 'Title 1', 'http://example.com/1', 'API', 'MIT', 'http://url.de'),
('2023-01-02', 4.7, 150, 170, '123e4567-e89b-12d3-a456-426614174001', '123e4567-e89b-12d3-a456-426614174101', 'Abstract 2', 'Author 2', 'Description 2', 'Title 2', 'http://example.com/2', 'DATASET', 'MIT', 'http://url.de'),
('2023-01-03', 4.9, 200, 220, '123e4567-e89b-12d3-a456-426614174002', '123e4567-e89b-12d3-a456-426614174102', 'Abstract 3', 'Author 3', 'Description 3', 'Title 3', 'http://example.com/3', 'API', 'MIT', 'http://url.de'),
('2023-01-04', 4.2, 80, 100, '123e4567-e89b-12d3-a456-426614174003', '123e4567-e89b-12d3-a456-426614174103', 'Abstract 4', 'Author 4', 'Description 4', 'Title 4', 'http://example.com/4', 'DATASET', 'MIT', 'http://url.de'),
('2023-01-05', 4.6, 120, 140, '123e4567-e89b-12d3-a456-426614174004', '123e4567-e89b-12d3-a456-426614174104', 'Abstract 5', 'Author 5', 'Description 5', 'Title 5', 'http://example.com/5', 'API', 'MIT', 'http://url.de');
-- Insert 10 more sample data into dataset
INSERT INTO dataset (date, raiting, upvotes, votes, categorie_id, id, abst, author, description, title, url, type, licence) VALUES
('2023-01-06', 4.8, 180, 200, '123e4567-e89b-12d3-a456-426614174005', '123e4567-e89b-12d3-a456-426614174105', 'Abstract 6', 'Author 6', 'Description 6', 'Title 6', 'http://example.com/6', 'API', 'MIT'),
('2023-01-07', 4.3, 90, 110, '123e4567-e89b-12d3-a456-426614174006', '123e4567-e89b-12d3-a456-426614174106', 'Abstract 7', 'Author 7', 'Description 7', 'Title 7', 'http://example.com/7', 'DATASET', 'MIT'),
('2023-01-08', 4.7, 150, 170, '123e4567-e89b-12d3-a456-426614174007', '123e4567-e89b-12d3-a456-426614174107', 'Abstract 8', 'Author 8', 'Description 8', 'Title 8', 'http://example.com/8', 'API', 'MIT'),
('2023-01-09', 4.9, 200, 220, '123e4567-e89b-12d3-a456-426614174000', '123e4567-e89b-12d3-a456-426614174108', 'Abstract 9', 'Author 9', 'Description 9', 'Title 9', 'http://example.com/9', 'DATASET', 'MIT'),
('2023-01-10', 4.2, 80, 100, '123e4567-e89b-12d3-a456-426614174001', '123e4567-e89b-12d3-a456-426614174109', 'Abstract 10', 'Author 10', 'Description 10', 'Title 10', 'http://example.com/10', 'API', 'MIT'),
('2023-11-11', 4.6, 120, 140, '123e4567-e89b-12d3-a456-426614174002', '123e4567-e89b-12d3-a456-426614174110', 'Abstract 11', 'Author 11', 'Description 11', 'Title 11', 'http://example.com/11', 'DATASET', 'MIT'),
('2023-09-12', 4.8, 180, 200, '123e4567-e89b-12d3-a456-426614174003', '123e4567-e89b-12d3-a456-426614174111', 'Abstract 12', 'Author 12', 'Description 12', 'Title 12', 'http://example.com/12', 'API', 'MIT'),
('2023-03-13', 4.3, 90, 110, '123e4567-e89b-12d3-a456-426614174004', '123e4567-e89b-12d3-a456-426614174112', 'Abstract 13', 'Author 13', 'Description 13', 'Title 13', 'http://example.com/13', 'DATASET', 'MIT'),
('2021-01-14', 4.7, 150, 170, '123e4567-e89b-12d3-a456-426614174005', '123e4567-e89b-12d3-a456-426614174113', 'Abstract 14', 'Author 14', 'Description 14', 'Title 14', 'http://example.com/14', 'API', 'MIT'),
('2024-01-15', 4.9, 200, 220, '123e4567-e89b-12d3-a456-426614174006', '123e4567-e89b-12d3-a456-426614174114', 'Abstract 15', 'Author 15', 'Description 15', 'Title 15', 'http://example.com/15', 'DATASET', 'MIT');
INSERT INTO dataset (date, raiting, upvotes, votes, categorie_id, id, abst, author, description, title, url, type, licence, terms_of_use) VALUES
('2023-01-06', 4.8, 180, 200, '123e4567-e89b-12d3-a456-426614174005', '123e4567-e89b-12d3-a456-426614174105', 'Abstract 6', 'Author 6', 'Description 6', 'Title 6', 'http://example.com/6', 'API', 'MIT', 'http://zip.com'),
('2023-01-07', 4.3, 90, 110, '123e4567-e89b-12d3-a456-426614174006', '123e4567-e89b-12d3-a456-426614174106', 'Abstract 7', 'Author 7', 'Description 7', 'Title 7', 'http://example.com/7', 'DATASET', 'MIT', 'http://zip.com'),
('2023-01-08', 4.7, 150, 170, '123e4567-e89b-12d3-a456-426614174007', '123e4567-e89b-12d3-a456-426614174107', 'Abstract 8', 'Author 8', 'Description 8', 'Title 8', 'http://example.com/8', 'API', 'MIT', 'http://zip.com'),
('2023-01-09', 4.9, 200, 220, '123e4567-e89b-12d3-a456-426614174000', '123e4567-e89b-12d3-a456-426614174108', 'Abstract 9', 'Author 9', 'Description 9', 'Title 9', 'http://example.com/9', 'DATASET', 'MIT', 'http://zip.com'),
('2023-01-10', 4.2, 80, 100, '123e4567-e89b-12d3-a456-426614174001', '123e4567-e89b-12d3-a456-426614174109', 'Abstract 10', 'Author 10', 'Description 10', 'Title 10', 'http://example.com/10', 'API', 'MIT', 'http://zip.com'),
('2023-11-11', 4.6, 120, 140, '123e4567-e89b-12d3-a456-426614174002', '123e4567-e89b-12d3-a456-426614174110', 'Abstract 11', 'Author 11', 'Description 11', 'Title 11', 'http://example.com/11', 'DATASET', 'MIT', 'http://zip.com'),
('2023-09-12', 4.8, 180, 200, '123e4567-e89b-12d3-a456-426614174003', '123e4567-e89b-12d3-a456-426614174111', 'Abstract 12', 'Author 12', 'Description 12', 'Title 12', 'http://example.com/12', 'API', 'MIT', 'http://zip.com'),
('2023-03-13', 4.3, 90, 110, '123e4567-e89b-12d3-a456-426614174004', '123e4567-e89b-12d3-a456-426614174112', 'Abstract 13', 'Author 13', 'Description 13', 'Title 13', 'http://example.com/13', 'DATASET', 'MIT', 'http://zip.com'),
('2021-01-14', 4.7, 150, 170, '123e4567-e89b-12d3-a456-426614174005', '123e4567-e89b-12d3-a456-426614174113', 'Abstract 14', 'Author 14', 'Description 14', 'Title 14', 'http://example.com/14', 'API', 'MIT', 'http://zip.com'),
('2024-01-15', 4.9, 200, 220, '123e4567-e89b-12d3-a456-426614174006', '123e4567-e89b-12d3-a456-426614174114', 'Abstract 15', 'Author 15', 'Description 15', 'Title 15', 'http://example.com/15', 'DATASET', 'MIT', 'http://zip.com');

View File

@ -3,5 +3,5 @@ DROP TABLE IF EXISTS category;
create table category (id uuid not null, name varchar(255), primary key (id));
create table dataset (date date, raiting float(24) not null, upvotes integer not null, votes integer not null, categorie_id uuid, id uuid not null, abst varchar(255), author varchar(255), description varchar(255), title varchar(255), url varchar(255), type enum ('API','DATASET'), licence varchar(255), primary key (id));
create table dataset (date date, raiting float(24) not null, upvotes integer not null, votes integer not null, categorie_id uuid, id uuid not null, abst varchar(255), author varchar(255), description varchar(200000), title varchar(255), url varchar(2048), terms_of_use varchar(2048), type enum ('API','DATASET'), licence varchar(255), primary key (id));
alter table if exists dataset add constraint FKq6qwq6u473f89h71s7rf97ruy foreign key (categorie_id) references category;

View File

@ -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;

View File

@ -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();
}
});

View File

@ -0,0 +1,2 @@
export const DATASET_ENDPOINT = "/api/v1/datasets";
export const getBaseURL = () => location.origin;

View File

@ -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
@ -104,9 +120,17 @@ export default class Dataset {
return null;
}
clone.querySelector(".dataset").dataset.id = this.#id;
let datasetContainer = clone.querySelector(".dataset");
datasetContainer.dataset.id = this.#id;
datasetContainer.addEventListener("click", event => {
if (!event.target.classList.contains("btn")) {
let detailsPage = new URL("/details.html", location.origin);
detailsPage.searchParams.append("id", this.#id);
window.location.href = detailsPage.toString();
}
})
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]);

View File

@ -0,0 +1,213 @@
@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-top-left-radius: var(--corner-radius);
border-top-right-radius: var(--corner-radius);
margin-top: var(--pad-main);
}
& > :last-child {
border-bottom-left-radius: var(--corner-radius);
border-bottom-right-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);
}
h1 {
margin-block: var(--gap-medium) 0;
text-align: center;
}
#title {
grid-column: 1 / 3;
&::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";
}
}
#details summary, #url {
text-align: start;
grid-column: 1 / 3;
}
#rating {
color: color-mix(in oklab, var(--text-color) 80%, black);
color: transparent;
width: 5lh;
height: 1lh;
margin-inline: .5ch;
mask-image: url("stars.svg");
-webkit-mask-image: url("stars.svg");
mask-size: contain;
mask-mode: alpha;
--rating-percent: calc((var(--rating, 0) / 5) * 100%);
background: var(--bg-color);
vertical-align: bottom;
}
#rating::-webkit-meter-bar {
background: var(--bg-color);
border: none;
border-radius: 0;
grid-row: 1 / -1;
}
#rating::-webkit-meter-optimum-value,
#rating::-webkit-meter-suboptimum-value,
#rating::-webkit-meter-even-less-good-value {
background-color: var(--rating-color);
}
#rating::-moz-meter-bar {
background: var(--rating-color);
}
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);
}
.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;
}
:is(#full-description, #not-found) 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;
}
}
#not-found:not(.hidden) {
min-height: calc(100vh - 2 * var(--pad-main));
display: flex;
flex-direction: column;
gap: var(--gap-large);
justify-content: space-evenly;
container: nothing-found / inline-size;
p, h1 {
text-align: center;
text-wrap: balance;
text-wrap: pretty;
}
}
#nothing-found-bg {
background-position-x: calc(50% + 3cqh);
}

View File

@ -0,0 +1,76 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dataset details</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preload" as="style" href="https://fonts.googleapis.com/css2?family=Flow+Circular&display=swap" crossorigin>
<link rel="stylesheet" href="details.css">
<script defer type="module" src="details.js"></script>
</head>
<body>
<template id="voting-template">
<aside class="upvote">
<button class="upvote-btn btn flat">Upvote</button>
<span class="upvote-count">0</span>
<button class="downvote-btn btn flat">Downvote</button>
</aside>
</template>
<main id="details" class="skeleton">
<header data-id="dataset-id">
<h1 id="title" data-type="api">Title</h1>
<summary>
<span id="rating-text">4</span><meter id="rating" value="4" max="5"></meter>
<span id="short-description">Lorem ipsum dolor sit amet consectetur adipisicing elit. Perspiciatis recusandae laborum odio corrupti voluptas quisquam dicta, quibusdam ipsum qui exercitationem.</span>
</summary>
<a id="url">https://example.com/dataset</a>
<aside class="upvote">
<button disabled class="upvote-btn btn flat">Upvote</button>
<span class="upvote-count">0</span>
<button disabled class="downvote-btn btn flat">Downvote</button>
</aside>
</header>
<section id="metadata">
<span>Added on: <time id="date" datetime="0">1. January 1970</time></span>
<span>Category: <span id="category">Something</span></span>
<span>License: <span id="license">MIT</span></span>
<a id="terms-of-use">Terms of Use</a>
</section>
<section>
<p id="full-description">Full description<br>
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!
<br>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.
</p>
</section>
</main>
<main id="not-found" class="hidden">
<h1>This is not the page you're looking for.</h1>
<div id="nothing-found-bg"></div>
<p>
The dataset or API you were sent to is not in our database.
Either it has been deleted by its author or we didn't know of it
to begin with.
<br>
You can try browsing for other datasets and APIs on our
<a href="/">homepage</a>.
</p>
</main>
</body>
</html>

View File

@ -0,0 +1,59 @@
import Dataset from "./dataset.js";
const mainPage = document.getElementById("details");
const notFoundPage = document.getElementById("not-found");
const title = document.getElementById("title");
const rating = document.getElementById("rating");
const ratingText = document.getElementById("rating-text");
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.value = dataset.rating;
ratingText.innerText = dataset.rating;
shortDescription.innerText = dataset.shortDescription;
url.href = dataset.url;
url.innerText = dataset.url;
mainPage.querySelector(".upvote").replaceWith(upvoteComponent);
date.datetime = dataset.date;
date.innerText = dataset.parseDate().toLocaleDateString(undefined, {
day: "numeric",
month: "long",
year: "numeric",
});
category.innerText = dataset.category.name;
category.dataset.id = dataset.category.id;
license.innerText = dataset.license;
termsOfUse.href = dataset.termsOfUse;
fullDescription.innerText = dataset.fullDescription;
mainPage.classList.remove("skeleton");
} else {
mainPage.classList.add("hidden");
notFoundPage.classList.remove("hidden");
}
} else {
mainPage.classList.add("hidden");
notFoundPage.classList.remove("hidden");
}

View File

@ -4,7 +4,12 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DataDash</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="prefetch" href="https://fonts.googleapis.com/css2?family=Flow+Circular&display=swap">
<link rel="stylesheet" href="main.css">
<link rel="prefetch" href="details.html">
<link rel="prefetch" href="details.css">
<script type="module" src="main.js" defer></script>
</head>
<body>

View File

@ -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;
@ -109,6 +114,11 @@ header {
align-items: center;
justify-content: space-between;
box-sizing: border-box;
cursor: pointer;
}
.dataset:hover {
filter: brightness(1.2);
}
.upvote {
@ -117,7 +127,7 @@ header {
display: flex;
flex-direction: column;
align-items: center;
gap: .5em;
gap: var(--gap-small);
}
/* Buttons */

View File

@ -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,

View File

@ -0,0 +1,127 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg"
width="4.52333in" height="0.853333in"
viewBox="0 0 1357 256">
<path id="Selection"
fill="black" stroke="black" stroke-width="1"
d="M 145.00,19.00
C 148.26,23.95 151.83,34.12 154.20,40.00
154.20,40.00 167.00,71.00 167.00,71.00
168.58,74.95 174.23,90.23 176.65,92.40
178.81,94.36 189.75,94.96 193.00,95.00
193.00,95.00 256.00,98.00 256.00,98.00
252.97,103.56 245.88,108.30 241.00,112.42
241.00,112.42 212.00,137.08 212.00,137.08
209.10,139.50 197.48,148.71 196.46,151.43
195.50,153.97 199.97,168.40 200.87,172.00
200.87,172.00 216.00,229.00 216.00,229.00
216.00,229.00 168.00,199.95 168.00,199.95
164.08,197.50 150.78,188.29 147.00,188.29
143.53,188.30 130.58,197.29 127.00,199.67
127.00,199.67 79.00,230.00 79.00,230.00
79.00,230.00 92.79,173.00 92.79,173.00
92.79,173.00 96.68,152.17 96.68,152.17
96.68,152.17 80.00,137.39 80.00,137.39
80.00,137.39 52.00,114.42 52.00,114.42
52.00,114.42 36.00,100.00 36.00,100.00
36.00,100.00 92.00,96.09 92.00,96.09
96.36,95.79 111.98,95.53 114.78,93.40
118.14,90.85 122.99,76.57 124.68,72.00
124.68,72.00 145.00,19.00 145.00,19.00 Z
M 411.00,19.00
C 414.26,23.95 417.83,34.12 420.20,40.00
420.20,40.00 433.00,71.00 433.00,71.00
434.58,74.95 440.23,90.23 442.65,92.40
444.81,94.36 455.75,94.96 459.00,95.00
459.00,95.00 522.00,98.00 522.00,98.00
518.97,103.56 511.88,108.30 507.00,112.42
507.00,112.42 478.00,137.08 478.00,137.08
475.10,139.50 463.48,148.71 462.46,151.43
461.50,153.97 465.97,168.40 466.87,172.00
466.87,172.00 482.00,229.00 482.00,229.00
482.00,229.00 434.00,199.95 434.00,199.95
430.08,197.50 416.78,188.29 413.00,188.29
409.53,188.30 396.58,197.29 393.00,199.67
393.00,199.67 345.00,230.00 345.00,230.00
345.00,230.00 358.79,173.00 358.79,173.00
358.79,173.00 362.68,152.17 362.68,152.17
362.68,152.17 346.00,137.39 346.00,137.39
346.00,137.39 318.00,114.42 318.00,114.42
318.00,114.42 302.00,100.00 302.00,100.00
302.00,100.00 358.00,96.09 358.00,96.09
362.36,95.79 377.98,95.53 380.78,93.40
384.14,90.85 388.99,76.57 390.68,72.00
390.68,72.00 411.00,19.00 411.00,19.00 Z
M 678.00,19.00
C 681.26,23.95 684.83,34.12 687.20,40.00
687.20,40.00 700.00,71.00 700.00,71.00
701.58,74.95 707.23,90.23 709.65,92.40
711.81,94.36 722.75,94.96 726.00,95.00
726.00,95.00 789.00,98.00 789.00,98.00
785.97,103.56 778.88,108.30 774.00,112.42
774.00,112.42 745.00,137.08 745.00,137.08
742.10,139.50 730.48,148.71 729.46,151.43
728.50,153.97 732.97,168.40 733.87,172.00
733.87,172.00 749.00,229.00 749.00,229.00
749.00,229.00 701.00,199.95 701.00,199.95
697.08,197.50 683.78,188.29 680.00,188.29
676.53,188.30 663.58,197.29 660.00,199.67
660.00,199.67 612.00,230.00 612.00,230.00
612.00,230.00 625.79,173.00 625.79,173.00
625.79,173.00 629.68,152.17 629.68,152.17
629.68,152.17 613.00,137.39 613.00,137.39
613.00,137.39 585.00,114.42 585.00,114.42
585.00,114.42 569.00,100.00 569.00,100.00
569.00,100.00 625.00,96.09 625.00,96.09
629.36,95.79 644.98,95.53 647.78,93.40
651.14,90.85 655.99,76.57 657.68,72.00
657.68,72.00 678.00,19.00 678.00,19.00 Z
M 944.00,19.00
C 947.26,23.95 950.83,34.12 953.20,40.00
953.20,40.00 966.00,71.00 966.00,71.00
967.58,74.95 973.23,90.23 975.65,92.40
977.81,94.36 988.75,94.96 992.00,95.00
992.00,95.00 1055.00,98.00 1055.00,98.00
1051.97,103.56 1044.88,108.30 1040.00,112.42
1040.00,112.42 1011.00,137.08 1011.00,137.08
1008.10,139.50 996.48,148.71 995.46,151.43
994.50,153.97 998.97,168.40 999.87,172.00
999.87,172.00 1015.00,229.00 1015.00,229.00
1015.00,229.00 967.00,199.95 967.00,199.95
963.08,197.50 949.78,188.29 946.00,188.29
942.53,188.30 929.58,197.29 926.00,199.67
926.00,199.67 878.00,230.00 878.00,230.00
878.00,230.00 891.79,173.00 891.79,173.00
891.79,173.00 895.68,152.17 895.68,152.17
895.68,152.17 879.00,137.39 879.00,137.39
879.00,137.39 851.00,114.42 851.00,114.42
851.00,114.42 835.00,100.00 835.00,100.00
835.00,100.00 891.00,96.09 891.00,96.09
895.36,95.79 910.98,95.53 913.78,93.40
917.14,90.85 921.99,76.57 923.68,72.00
923.68,72.00 944.00,19.00 944.00,19.00 Z
M 1211.00,19.00
C 1214.26,23.95 1217.83,34.12 1220.20,40.00
1220.20,40.00 1233.00,71.00 1233.00,71.00
1234.58,74.95 1240.23,90.23 1242.65,92.40
1244.81,94.36 1255.75,94.96 1259.00,95.00
1259.00,95.00 1322.00,98.00 1322.00,98.00
1318.97,103.56 1311.88,108.30 1307.00,112.42
1307.00,112.42 1278.00,137.08 1278.00,137.08
1275.10,139.50 1263.48,148.71 1262.46,151.43
1261.50,153.97 1265.97,168.40 1266.87,172.00
1266.87,172.00 1282.00,229.00 1282.00,229.00
1282.00,229.00 1234.00,199.95 1234.00,199.95
1230.08,197.50 1216.78,188.29 1213.00,188.29
1209.53,188.30 1196.58,197.29 1193.00,199.67
1193.00,199.67 1145.00,230.00 1145.00,230.00
1145.00,230.00 1158.79,173.00 1158.79,173.00
1158.79,173.00 1162.68,152.17 1162.68,152.17
1162.68,152.17 1146.00,137.39 1146.00,137.39
1146.00,137.39 1118.00,114.42 1118.00,114.42
1118.00,114.42 1102.00,100.00 1102.00,100.00
1102.00,100.00 1158.00,96.09 1158.00,96.09
1162.36,95.79 1177.98,95.53 1180.78,93.40
1184.14,90.85 1188.99,76.57 1190.68,72.00
1190.68,72.00 1211.00,19.00 1211.00,19.00 Z" />
</svg>

After

Width:  |  Height:  |  Size: 6.5 KiB