Merge branch '22-integrate-api-and-frontend' into 34-revisit-design
This commit is contained in:
14
pom.xml
14
pom.xml
@@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-parent</artifactId>
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
<version>3.3.0</version>
|
<version>3.3.1</version>
|
||||||
<relativePath/> <!-- lookup parent from repository -->
|
<relativePath/> <!-- lookup parent from repository -->
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>de.uni-passau.fim.PADAS.group3</groupId>
|
<groupId>de.uni-passau.fim.PADAS.group3</groupId>
|
||||||
@@ -23,11 +23,6 @@
|
|||||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-web</artifactId>
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
@@ -57,13 +52,6 @@
|
|||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.mockito</groupId>
|
|
||||||
<artifactId>mockito-core</artifactId>
|
|
||||||
<version>4.5.1</version>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
@@ -52,7 +52,7 @@ public class Dataset {
|
|||||||
|
|
||||||
private String licence;
|
private String licence;
|
||||||
|
|
||||||
private static final List<String> sortable = Arrays.asList("author", "title", "upvotes", "date");
|
private static final List<String> sortable = Arrays.asList("author", "title", "upvotes", "raiting", "date");
|
||||||
|
|
||||||
@ManyToOne
|
@ManyToOne
|
||||||
private Category categorie;
|
private Category categorie;
|
||||||
|
@@ -71,7 +71,7 @@ public class DatasetController {
|
|||||||
if (datasetService.getDatasetById(id) == null) {
|
if (datasetService.getDatasetById(id) == null) {
|
||||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||||
}
|
}
|
||||||
if (!(stars > 0 && stars < 6)) {
|
if (!(stars >= 0 && stars < 6)) {
|
||||||
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
datasetService.voteDataset(id, stars);
|
datasetService.voteDataset(id, stars);
|
||||||
|
@@ -17,44 +17,44 @@ public class DatasetService {
|
|||||||
private dataRepository datasetRepository;
|
private dataRepository datasetRepository;
|
||||||
private CategoryRepository categoryRepository;
|
private CategoryRepository categoryRepository;
|
||||||
|
|
||||||
public DatasetService(dataRepository datasetRepository, CategoryRepository categoryRepository) {
|
DatasetService(dataRepository datasetRepository, CategoryRepository categoryRepository) {
|
||||||
this.datasetRepository = datasetRepository;
|
this.datasetRepository = datasetRepository;
|
||||||
this.categoryRepository = categoryRepository;
|
this.categoryRepository = categoryRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Dataset getDatasetById(UUID id) {
|
Dataset getDatasetById(UUID id) {
|
||||||
return datasetRepository.getDatasetById(id);
|
return datasetRepository.getDatasetById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Dataset addDataset(Dataset dataset) {
|
Dataset addDataset(Dataset dataset) {
|
||||||
dataset.setDate(LocalDate.now());
|
dataset.setDate(LocalDate.now());
|
||||||
return datasetRepository.save(dataset);
|
return datasetRepository.save(dataset);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void voteDataset(UUID id, int vote) {
|
void voteDataset(UUID id, int vote) {
|
||||||
Dataset dataset = datasetRepository.getDatasetById(id);
|
Dataset dataset = datasetRepository.getDatasetById(id);
|
||||||
dataset.vote(vote);
|
dataset.vote(vote);
|
||||||
datasetRepository.save(dataset);
|
datasetRepository.save(dataset);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deleteDataset(UUID id) {
|
void deleteDataset(UUID id) {
|
||||||
Dataset dataset = datasetRepository.getDatasetById(id);
|
Dataset dataset = datasetRepository.getDatasetById(id);
|
||||||
datasetRepository.delete(dataset);
|
datasetRepository.delete(dataset);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void upvoteDataset(UUID id) {
|
void upvoteDataset(UUID id) {
|
||||||
Dataset dataset = datasetRepository.getDatasetById(id);
|
Dataset dataset = datasetRepository.getDatasetById(id);
|
||||||
dataset.upvote();
|
dataset.upvote();
|
||||||
datasetRepository.save(dataset);
|
datasetRepository.save(dataset);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void downvoteDataset(UUID id) {
|
void downvoteDataset(UUID id) {
|
||||||
Dataset dataset = datasetRepository.getDatasetById(id);
|
Dataset dataset = datasetRepository.getDatasetById(id);
|
||||||
dataset.downvote();
|
dataset.downvote();
|
||||||
datasetRepository.save(dataset);
|
datasetRepository.save(dataset);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Page<Dataset> searchByOptionalCriteria(String search, String categories, String type, Pageable pageable) {
|
Page<Dataset> searchByOptionalCriteria(String search, String categories, String type, Pageable pageable) {
|
||||||
Category category = categories.equals("%") ? null
|
Category category = categories.equals("%") ? null
|
||||||
: categoryRepository.getCategoryById(UUID.fromString(categories));
|
: categoryRepository.getCategoryById(UUID.fromString(categories));
|
||||||
Type t = type.equals("%") ? null : Type.valueOf(type);
|
Type t = type.equals("%") ? null : Type.valueOf(type);
|
||||||
|
@@ -37,8 +37,8 @@ public class CategoryController {
|
|||||||
}
|
}
|
||||||
@ResponseStatus(HttpStatus.CREATED)
|
@ResponseStatus(HttpStatus.CREATED)
|
||||||
@PostMapping
|
@PostMapping
|
||||||
public void createCategory(@RequestBody CategoryDto dto) {
|
public Category createCategory(@RequestBody CategoryDto dto) {
|
||||||
categoryService.addCategory(dto);
|
return categoryService.addCategory(dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -8,22 +8,22 @@ import java.util.UUID;
|
|||||||
public class CategoryService {
|
public class CategoryService {
|
||||||
private CategoryRepository categoryRepository;
|
private CategoryRepository categoryRepository;
|
||||||
|
|
||||||
public CategoryService(CategoryRepository categoryRepository) {
|
CategoryService(CategoryRepository categoryRepository) {
|
||||||
this.categoryRepository = categoryRepository;
|
this.categoryRepository = categoryRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addCategory(CategoryDto category) {
|
Category addCategory(CategoryDto category) {
|
||||||
Category cat = new Category(category.getName());
|
Category cat = new Category(category.getName());
|
||||||
categoryRepository.save(cat);
|
return categoryRepository.save(cat);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<CategoryDto> getAllCategories() {
|
List<CategoryDto> getAllCategories() {
|
||||||
List<Category> tmp = categoryRepository.findAll();
|
List<Category> tmp = categoryRepository.findAll();
|
||||||
List<CategoryDto> s = tmp.stream().map(CategoryDtoMapper::toDto).toList();
|
List<CategoryDto> s = tmp.stream().map(CategoryDtoMapper::toDto).toList();
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CategoryDto getCategoryById(UUID id) {
|
CategoryDto getCategoryById(UUID id) {
|
||||||
Category c = categoryRepository.getCategoryById(id);
|
Category c = categoryRepository.getCategoryById(id);
|
||||||
if (c == null) {
|
if (c == null) {
|
||||||
return null;
|
return null;
|
||||||
|
@@ -171,3 +171,30 @@ form :has(#url) {
|
|||||||
.btn:disabled {
|
.btn:disabled {
|
||||||
filter: var(--drop-shadow) grayscale(.5) brightness(.5);
|
filter: var(--drop-shadow) grayscale(.5) brightness(.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#category[value="new"] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
label[for="category"] {
|
||||||
|
width: 0;
|
||||||
|
user-select: none;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
span:has(#category) {
|
||||||
|
gap: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
#new-category-group:not(.hidden) {
|
||||||
|
display: flex;
|
||||||
|
justify-content: stretch;
|
||||||
|
gap: var(--gap-small);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#new-category-group button {
|
||||||
|
background-image: url("./sort.svg");
|
||||||
|
background-size: contain;
|
||||||
|
background-origin: content-box;
|
||||||
|
}
|
||||||
|
@@ -27,7 +27,7 @@
|
|||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span id="author-box">
|
<span id="author-box">
|
||||||
<label for="author">Author</label>
|
<label for="author">Author/provider</label>
|
||||||
<input type="text" name="author" id="author" required>
|
<input type="text" name="author" id="author" required>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
@@ -48,6 +48,29 @@
|
|||||||
<input type="url" name="url" id="url" required>
|
<input type="url" name="url" id="url" required>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
<span>
|
||||||
|
<label for="terms-of-use">Terms of Use URL</label>
|
||||||
|
<input type="url" name="terms-of-use" id="terms-of-use" required>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span>
|
||||||
|
<label for="license">License</label>
|
||||||
|
<input type="text" name="license" id="license" required>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span>
|
||||||
|
<label for="category">Category</label>
|
||||||
|
<select name="category" id="category" class="btn" required autocomplete="off">
|
||||||
|
<option value="" selected disabled hidden>Category</option>
|
||||||
|
<option value="new">New category</option>
|
||||||
|
<hr>
|
||||||
|
</select>
|
||||||
|
<span id="new-category-group" class="hidden">
|
||||||
|
<input type="text" id="new-category" size="2" required disabled autocomplete="off">
|
||||||
|
<button id="change-category-btn" class="btn flat" title="change category"></button>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
<span id="full-description-box">
|
<span id="full-description-box">
|
||||||
<label for="full-description">Full description</label>
|
<label for="full-description">Full description</label>
|
||||||
<textarea name="full-description" id="full-description" spellcheck="true"></textarea>
|
<textarea name="full-description" id="full-description" spellcheck="true"></textarea>
|
||||||
|
@@ -7,10 +7,16 @@ const {
|
|||||||
["is-dataset"]: isDatasetSwitch,
|
["is-dataset"]: isDatasetSwitch,
|
||||||
["short-description"]: shortDescriptionEntry,
|
["short-description"]: shortDescriptionEntry,
|
||||||
url: urlEntry,
|
url: urlEntry,
|
||||||
|
["terms-of-use"]: termsOfUseEntry,
|
||||||
|
license: licenseEntry,
|
||||||
|
category: categorySpinner,
|
||||||
|
["new-category"]: newCategoryEntry,
|
||||||
|
["change-category-btn"]: changeCategoryBtn,
|
||||||
["full-description"]: fullDescriptionEntry,
|
["full-description"]: fullDescriptionEntry,
|
||||||
["btn-add"]: addBtn,
|
["btn-add"]: addBtn,
|
||||||
["btn-cancel"]: cancelBtn,
|
["btn-cancel"]: cancelBtn,
|
||||||
} = form.elements;
|
} = form.elements;
|
||||||
|
const newCategoryGroup = document.getElementById("new-category-group");
|
||||||
|
|
||||||
const validationListener = () => {
|
const validationListener = () => {
|
||||||
addBtn.disabled = !form.checkValidity();
|
addBtn.disabled = !form.checkValidity();
|
||||||
@@ -22,44 +28,127 @@ const validationListener = () => {
|
|||||||
authorEntry,
|
authorEntry,
|
||||||
shortDescriptionEntry,
|
shortDescriptionEntry,
|
||||||
urlEntry,
|
urlEntry,
|
||||||
|
termsOfUseEntry,
|
||||||
|
licenseEntry,
|
||||||
|
newCategoryEntry,
|
||||||
fullDescriptionEntry,
|
fullDescriptionEntry,
|
||||||
].forEach(input => input.addEventListener("input", validationListener));
|
].forEach(input => input.addEventListener("input", validationListener));
|
||||||
|
|
||||||
|
// Category spinner
|
||||||
|
const categorySpinnerSet = (...args) => {
|
||||||
|
if (args.length > 0) {
|
||||||
|
categorySpinner.value = args[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
categorySpinner.setAttribute("value", categorySpinner.value);
|
||||||
|
|
||||||
|
if (categorySpinner.value == "new") {
|
||||||
|
newCategoryGroup.classList.remove("hidden");
|
||||||
|
newCategoryEntry.disabled = false;
|
||||||
|
newCategoryEntry.focus();
|
||||||
|
} else {
|
||||||
|
newCategoryGroup.classList.add("hidden");
|
||||||
|
newCategoryEntry.disabled = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCategory = () => {
|
||||||
|
return categorySpinner.value == "new"
|
||||||
|
? newCategoryEntry.value
|
||||||
|
: categorySpinner.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
categorySpinner.addEventListener("input", e => {
|
||||||
|
categorySpinnerSet();
|
||||||
|
validationListener();
|
||||||
|
});
|
||||||
|
|
||||||
|
changeCategoryBtn.addEventListener("click", e => {
|
||||||
|
e.preventDefault();
|
||||||
|
categorySpinnerSet("");
|
||||||
|
validationListener();
|
||||||
|
});
|
||||||
|
|
||||||
|
let categoriesResponse = await fetch(`${location.origin}/api/v1/categories`);
|
||||||
|
let categories = [];
|
||||||
|
if (!categoriesResponse.ok) {
|
||||||
|
console.warn("Could not load categories!");
|
||||||
|
} else {
|
||||||
|
categories = await categoriesResponse.json();
|
||||||
|
for (const category of categories) {
|
||||||
|
let option = document.createElement("option");
|
||||||
|
option.value = category.id;
|
||||||
|
option.text = category.name;
|
||||||
|
categorySpinner.add(option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Form listeners
|
||||||
cancelBtn.addEventListener("click", () => {
|
cancelBtn.addEventListener("click", () => {
|
||||||
window.location.href = location.origin;
|
window.location.href = location.origin;
|
||||||
})
|
})
|
||||||
|
|
||||||
form.addEventListener("submit", async e => {
|
form.addEventListener("submit", async e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!form.reportValidity()) return;
|
if (!form.reportValidity()) {
|
||||||
|
addBtn.disabled = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let categoryID = categorySpinner.value;
|
||||||
|
|
||||||
|
if (categoryID == "new") {
|
||||||
|
const newCategoryName = newCategoryEntry.value.trim();
|
||||||
|
|
||||||
|
if (!categories.map(c => c.name).includes(newCategoryName)) {
|
||||||
|
// Try to add the new category
|
||||||
|
const newCategoryResponse = await fetch(`/api/v1/categories`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json;charset=utf-8" },
|
||||||
|
body: JSON.stringify({ name: newCategoryName }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!newCategoryResponse.ok) {
|
||||||
|
newCategoryEntry.setCustomValidity(
|
||||||
|
`Could not create new category: ${newCategoryResponse.statusText}`
|
||||||
|
);
|
||||||
|
form.reportValidity();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newCategory = await newCategoryResponse.json();
|
||||||
|
categoryID = newCategory.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create the request body
|
// Create the request body
|
||||||
const newContent = {
|
const newContent = {
|
||||||
title: titleEntry.value,
|
title: titleEntry.value,
|
||||||
author: authorEntry.value,
|
author: authorEntry.value,
|
||||||
|
type: isDatasetSwitch.checked ? "API" : "DATASET",
|
||||||
abst: shortDescriptionEntry.value,
|
abst: shortDescriptionEntry.value,
|
||||||
url: urlEntry.value,
|
url: urlEntry.value,
|
||||||
|
termsOfUse: termsOfUseEntry.value,
|
||||||
|
licence: licenseEntry.value,
|
||||||
|
categorie: {
|
||||||
|
id: categoryID,
|
||||||
|
},
|
||||||
description: fullDescriptionEntry.value,
|
description: fullDescriptionEntry.value,
|
||||||
type: isDatasetSwitch.checked ? "API" : "DATASET",
|
|
||||||
categories: [],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
console.debug(newContent);
|
|
||||||
|
|
||||||
// Don't allow several requests to be sent at the same time
|
// Don't allow several requests to be sent at the same time
|
||||||
addBtn.disabled = true;
|
addBtn.disabled = true;
|
||||||
|
|
||||||
let response = await fetch("/api/v1/datasets", {
|
let response = await fetch("/api/v1/datasets", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json;charset=utf-8" },
|
||||||
body: JSON.stringify(newContent),
|
body: JSON.stringify(newContent),
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json;charset=utf-8"
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
});
|
||||||
let data = await response.json();
|
let data = await response.json();
|
||||||
|
|
||||||
let dataset = new Dataset(data);
|
let dataset = new Dataset(data);
|
||||||
dataset.storageSetKey("created-locally", true);
|
dataset.storageSetKey("created-locally", true);
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
location.assign("/");
|
location.assign("/");
|
||||||
} else {
|
} else {
|
||||||
|
@@ -23,7 +23,7 @@ export default class Dataset {
|
|||||||
return this.#datasets.get(id);
|
return this.#datasets.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor({ abst: shortDescription, author, categorie, date, description, id, raiting, title, type, upvotes, url, votes, license, termsOfUse }) {
|
constructor({ abst: shortDescription, author, categorie, date, description, id, raiting, title, type, upvotes, url, votes, licence: license, termsOfUse }) {
|
||||||
this.#shortDescription = shortDescription;
|
this.#shortDescription = shortDescription;
|
||||||
this.#author = author;
|
this.#author = author;
|
||||||
this.#category = categorie;
|
this.#category = categorie;
|
||||||
|
@@ -79,6 +79,17 @@ h1 {
|
|||||||
grid-column: 1 / 3;
|
grid-column: 1 / 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#rating-input {
|
||||||
|
mask-image: url("stars.svg");
|
||||||
|
-webkit-mask-image: url("stars.svg");
|
||||||
|
mask-size: contain;
|
||||||
|
mask-mode: alpha;
|
||||||
|
width: 5lh;
|
||||||
|
height: 1lh;
|
||||||
|
margin-inline: .5ch;
|
||||||
|
background: linear-gradient(to right, yellow 33%, black 33%);
|
||||||
|
}
|
||||||
|
|
||||||
#rating {
|
#rating {
|
||||||
color: color-mix(in oklab, var(--text-color) 80%, black);
|
color: color-mix(in oklab, var(--text-color) 80%, black);
|
||||||
color: transparent;
|
color: transparent;
|
||||||
@@ -208,6 +219,51 @@ a {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#details-btns {
|
||||||
|
grid-column: 1 / 4;
|
||||||
|
justify-content: end;
|
||||||
|
gap: 1rem;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
#delete-btn {
|
||||||
|
background: #861c1c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* button styling to be revisited */
|
||||||
|
.btn {
|
||||||
|
padding: .5lh 1lh;
|
||||||
|
border: none;
|
||||||
|
border-radius: .5lh;
|
||||||
|
--btn-color: var(--fg-color);
|
||||||
|
background-color: var(--btn-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1rem;
|
||||||
|
transition: background-color 100ms, filter 200ms;
|
||||||
|
transition-timing-function: ease-out;
|
||||||
|
--drop-shadow-opacity: .5;
|
||||||
|
--drop-shadow-offset-y: 0;
|
||||||
|
--drop-shadow-blur: .25rem;
|
||||||
|
--drop-shadow: drop-shadow(
|
||||||
|
rgba(0, 0, 0, var(--drop-shadow-opacity))
|
||||||
|
0 var(--drop-shadow-offset-y) var(--drop-shadow-blur)
|
||||||
|
);
|
||||||
|
filter: var(--drop-shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:focus-visible, #is-dataset:focus-visible + #is-dataset-toggle {
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:not(:disabled):hover {
|
||||||
|
background-color: color-mix(in oklab, var(--btn-color) 80%, var(--bg-color));
|
||||||
|
--drop-shadow-opacity: .8;
|
||||||
|
--drop-shadow-offset-y: .25rem;
|
||||||
|
--drop-shadow-blur: .4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#nothing-found-bg {
|
#nothing-found-bg {
|
||||||
background-position-x: calc(50% + 3cqh);
|
background-position-x: calc(50% + 3cqh);
|
||||||
}
|
}
|
||||||
|
@@ -24,6 +24,7 @@
|
|||||||
<h1 id="title" data-type="api">Title</h1>
|
<h1 id="title" data-type="api">Title</h1>
|
||||||
<summary>
|
<summary>
|
||||||
<span id="rating-text">4</span><meter id="rating" value="4" max="5"></meter>
|
<span id="rating-text">4</span><meter id="rating" value="4" max="5"></meter>
|
||||||
|
<span id="rating-input"></span>
|
||||||
<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>
|
<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>
|
</summary>
|
||||||
<a id="url">https://example.com/dataset</a>
|
<a id="url">https://example.com/dataset</a>
|
||||||
@@ -58,6 +59,10 @@
|
|||||||
ipsam nobis quis.
|
ipsam nobis quis.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
<section id="details-btns">
|
||||||
|
<button id="back-btn" class="btn">Back to main page</button>
|
||||||
|
<button id="delete-btn" class="hidden btn">Delete</button>
|
||||||
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<main id="not-found" class="hidden">
|
<main id="not-found" class="hidden">
|
||||||
|
@@ -13,23 +13,35 @@ const category = document.getElementById("category");
|
|||||||
const license = document.getElementById("license");
|
const license = document.getElementById("license");
|
||||||
const termsOfUse = document.getElementById("terms-of-use");
|
const termsOfUse = document.getElementById("terms-of-use");
|
||||||
const fullDescription = document.getElementById("full-description");
|
const fullDescription = document.getElementById("full-description");
|
||||||
|
const backButton = document.getElementById("back-btn");
|
||||||
|
const deleteButton = document.getElementById("delete-btn");
|
||||||
|
|
||||||
|
let dataset = null;
|
||||||
|
let currentRating = 0;
|
||||||
|
let isRated = false;
|
||||||
|
|
||||||
const currentLocation = new URL(location.href);
|
const currentLocation = new URL(location.href);
|
||||||
if (currentLocation.searchParams.has("id")) {
|
if (currentLocation.searchParams.has("id")) {
|
||||||
const id = currentLocation.searchParams.get("id");
|
const id = currentLocation.searchParams.get("id");
|
||||||
const response = await fetch(`${currentLocation.origin}/api/v1/datasets/id/${id}`);
|
const response = await fetch(`${currentLocation.origin}/api/v1/datasets/id/${id}`);
|
||||||
console.dir(response);
|
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
const dataset = new Dataset(data);
|
dataset = new Dataset(data);
|
||||||
console.dir(data, dataset);
|
|
||||||
const upvoteComponent = dataset.createUpvoteComponent();
|
const upvoteComponent = dataset.createUpvoteComponent();
|
||||||
|
|
||||||
|
console.log(dataset.storageGet());
|
||||||
|
debugger
|
||||||
|
if (dataset.storageGetKey("created-locally", false)) {
|
||||||
|
deleteButton.classList.remove("hidden");
|
||||||
|
}
|
||||||
|
isRated = dataset.storageGetKey("is-rated", false)
|
||||||
|
|
||||||
|
|
||||||
title.innerText = dataset.title;
|
title.innerText = dataset.title;
|
||||||
title.dataset.type = dataset.type.toLowerCase();
|
title.dataset.type = dataset.type.toLowerCase();
|
||||||
rating.value = dataset.rating;
|
rating.value = dataset.rating;
|
||||||
ratingText.innerText = dataset.rating;
|
ratingText.innerText = parseFloat(dataset.rating).toFixed(1);
|
||||||
shortDescription.innerText = dataset.shortDescription;
|
shortDescription.innerText = dataset.shortDescription;
|
||||||
url.href = dataset.url;
|
url.href = dataset.url;
|
||||||
url.innerText = dataset.url;
|
url.innerText = dataset.url;
|
||||||
@@ -57,3 +69,53 @@ if (currentLocation.searchParams.has("id")) {
|
|||||||
mainPage.classList.add("hidden");
|
mainPage.classList.add("hidden");
|
||||||
notFoundPage.classList.remove("hidden");
|
notFoundPage.classList.remove("hidden");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
backButton.addEventListener("click", () => {
|
||||||
|
window.location.href = location.origin;
|
||||||
|
})
|
||||||
|
|
||||||
|
deleteButton.addEventListener("click", () => {
|
||||||
|
if (dataset != null) {
|
||||||
|
fetch(`${currentLocation.origin}/api/v1/datasets/id/` + dataset.id, {
|
||||||
|
method: 'DELETE'
|
||||||
|
}).then(resp => {
|
||||||
|
if (resp.ok) {
|
||||||
|
window.location.href = location.origin;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
rating.addEventListener("mousemove", (event) => {
|
||||||
|
if (!isRated) {
|
||||||
|
let bounds = rating.getBoundingClientRect();
|
||||||
|
currentRating = Math.round(((event.clientX - bounds.left) / bounds.width) * 5);
|
||||||
|
console.log(currentRating);
|
||||||
|
rating.value = currentRating;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
rating.addEventListener("mouseleave", () => {
|
||||||
|
rating.value = dataset.rating;
|
||||||
|
});
|
||||||
|
|
||||||
|
rating.addEventListener("click", () => {
|
||||||
|
if (!isRated) {
|
||||||
|
fetch(`${currentLocation.origin}/api/v1/datasets/id/` + dataset.id + "/stars?stars=" + currentRating, {
|
||||||
|
method: 'PUT'
|
||||||
|
}).then(resp => {
|
||||||
|
if (resp.ok) {
|
||||||
|
dataset.storageSetKey("is-rated", true);
|
||||||
|
isRated = true;
|
||||||
|
fetch(`${currentLocation.origin}/api/v1/datasets/id/` + dataset.id)
|
||||||
|
.then(resp => resp.json())
|
||||||
|
.then((data) => {
|
||||||
|
dataset = new Dataset(data);
|
||||||
|
ratingText.innerText = parseFloat(dataset.rating).toFixed(1);
|
||||||
|
rating.value = dataset.rating;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
@@ -45,8 +45,8 @@
|
|||||||
<option>Author Z-A</option>
|
<option>Author Z-A</option>
|
||||||
<option>Title A-Z</option>
|
<option>Title A-Z</option>
|
||||||
<option>Title Z-A</option>
|
<option>Title Z-A</option>
|
||||||
<option>Stars ↑</option>
|
<option>Raiting ↑</option>
|
||||||
<option>Stars ↓</option>
|
<option>Raiting ↓</option>
|
||||||
<option>Upvotes ↑</option>
|
<option>Upvotes ↑</option>
|
||||||
<option>Upvotes ↓</option>
|
<option>Upvotes ↓</option>
|
||||||
</select>
|
</select>
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { DATASET_ENDPOINT, getBaseURL } from "./constants.js";
|
import { DATASET_ENDPOINT, getBaseURL } from "./constants.js"
|
||||||
import { fetchQuery } from "./contentUtility.js";
|
import { fetchQuery } from "./contentUtility.js";
|
||||||
import Dataset from "./dataset.js";
|
import Dataset from "./dataset.js";
|
||||||
|
|
||||||
|
@@ -185,17 +185,6 @@ public class DatasetControllerTests {
|
|||||||
.andExpect(status().isBadRequest());
|
.andExpect(status().isBadRequest());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
void postMethodName_whenInvalidStars2() throws Exception {
|
|
||||||
UUID id = UUID.randomUUID();
|
|
||||||
Dataset dataset = new Dataset("Title", "abst", "desc", "auth", null, null, Type.API, "MIT");
|
|
||||||
|
|
||||||
given(datasetService.getDatasetById(id)).willReturn(dataset);
|
|
||||||
|
|
||||||
mockMvc.perform(put("/api/v1/datasets/id/" + id + "/stars?stars=0"))
|
|
||||||
.andExpect(status().isBadRequest());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void postMethodName_whenInvalidStars3() throws Exception {
|
void postMethodName_whenInvalidStars3() throws Exception {
|
||||||
UUID id = UUID.randomUUID();
|
UUID id = UUID.randomUUID();
|
||||||
|
Reference in New Issue
Block a user