merge front end to server
This commit is contained in:
35
frontend/list/elements.tsx
Normal file
35
frontend/list/elements.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import type { Attributes } from "../elementcreate";
|
||||
import * as elements from "../elementcreate";
|
||||
|
||||
function MediaElement(attributes: Attributes, contents: string[]) {
|
||||
const ret = <div class="col media-element" id={attributes['id']}>
|
||||
<div class='card shadow-sm'>
|
||||
<img class='card-img-top' src={attributes['webImg']} width='100%' onerror={attributes['imageError']}></img>
|
||||
<div class='card-body'>
|
||||
<h5 class='card-title'>{attributes['title']}</h5>
|
||||
<p class='card-text'>{attributes['released']}</p>
|
||||
<div class="d-none justify-content-between align-items-center">
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" onclick={attributes['fun']}>delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
function MyHeader(attributes: Attributes, contents: string[]) {
|
||||
return <div class="row">
|
||||
<div class='col'>
|
||||
<h2 class='text-center'>{attributes['title']} {attributes['num'] ? ": " + attributes['num'] : ""}</h2>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
function MediaContainer(attributes: Attributes, contents: string[]) {
|
||||
return <div id={attributes['id']} class="row row-cols-2 row-cols-sm-2 row-cols-md-3 row-cols-lg-4 row-cols-xl-5 g-3">{contents[0]}</div>;
|
||||
}
|
||||
|
||||
export { MediaElement, MyHeader, MediaContainer }
|
25
frontend/list/functions.tsx
Normal file
25
frontend/list/functions.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
|
||||
function splitByTitle(movies: Array<Movie>): { [s: string]: Movie[]; } {
|
||||
const result = movies.reduce((r, a) => {
|
||||
var letter = a.title[0].toUpperCase();
|
||||
if (!isNaN(parseInt(letter))) letter = "#";
|
||||
r[letter] = r[letter] || [];
|
||||
r[letter].push(a);
|
||||
return r;
|
||||
}, Object.create(null));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function splitByYear(movies: Array<Movie>): { [s: string]: Movie[]; } {
|
||||
const result = movies.reduce((r, a) => {
|
||||
const year = new Date(a.released).getFullYear();
|
||||
r[year] = r[year] || [];
|
||||
r[year].push(a);
|
||||
return r;
|
||||
}, Object.create(null));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export { splitByTitle, splitByYear };
|
304
frontend/list/list.tsx
Normal file
304
frontend/list/list.tsx
Normal file
@@ -0,0 +1,304 @@
|
||||
import { MediaElement, MyHeader, MediaContainer } from "./elements";
|
||||
import { splitByTitle, splitByYear } from "./functions";
|
||||
|
||||
import * as elements from "../elementcreate";
|
||||
|
||||
|
||||
|
||||
|
||||
var sortType = 0;
|
||||
var listType = 0;
|
||||
|
||||
const sortTypeTitle = 0;
|
||||
const sortTypeYear = 1;
|
||||
const sortTypeId = 2;
|
||||
|
||||
const moviesType = 0;
|
||||
const gamesType = 1;
|
||||
const seriesType = 2;
|
||||
|
||||
var listButtons: Array<HTMLElement | null> = [];
|
||||
var sortButtons: Array<HTMLElement | null> = [];
|
||||
|
||||
var root: HTMLElement | null;
|
||||
var editButton: HTMLElement | null;
|
||||
var movieElements: HTMLElement[] = [];
|
||||
|
||||
function getLink(): string {
|
||||
switch (listType) {
|
||||
case moviesType:
|
||||
return "/api/media/movies";
|
||||
case gamesType:
|
||||
return "/api/media/games";
|
||||
case seriesType:
|
||||
return "/api/media/series";
|
||||
}
|
||||
return "/api/media/movies";
|
||||
}
|
||||
|
||||
function submitMedia(event: SubmitEvent) {
|
||||
event.preventDefault();
|
||||
|
||||
const pass = document.getElementById("pass") as HTMLInputElement | null;
|
||||
if (!pass) return;
|
||||
|
||||
const input_id = document.getElementById("input_id") as HTMLInputElement | null;
|
||||
if (!input_id) return;
|
||||
|
||||
|
||||
if (pass.value == "" || input_id.value == "") return;
|
||||
|
||||
|
||||
fetch(getLink(), {
|
||||
body: JSON.stringify({ pass: pass.value, code: input_id.value }),
|
||||
headers: { "Content-Type": "application/json" },
|
||||
method: "POST"
|
||||
})
|
||||
.then(async (response) => {
|
||||
if (response.status != 201) {
|
||||
const json = await response.json();
|
||||
console.log(json);
|
||||
alert(json.message);
|
||||
return;
|
||||
}
|
||||
|
||||
const movie: Movie = await response.json();
|
||||
var letter = movie.title[0].toUpperCase();
|
||||
if (!isNaN(parseInt(letter))) letter = "#";
|
||||
|
||||
const mediaElement = <MediaElement webImg={movie.webImg} title={movie.title} released={movie.released} id={movie.code} fun={removeMedia} imageError={onImgError} />;
|
||||
|
||||
const container = document.getElementById(letter);
|
||||
if (!container) {
|
||||
root?.appendChild(<MyHeader title={letter} />);
|
||||
root?.appendChild(<MediaContainer id={letter}>{mediaElement}</MediaContainer>);
|
||||
return
|
||||
};
|
||||
container.appendChild(mediaElement);
|
||||
|
||||
movieElements.push(mediaElement);
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err);
|
||||
});
|
||||
|
||||
input_id.value = "";
|
||||
}
|
||||
|
||||
function loadState() {
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
if (searchParams.has("listType")) {
|
||||
switch (searchParams.get("listType")) {
|
||||
case "movies":
|
||||
listType = moviesType;
|
||||
break;
|
||||
case "series":
|
||||
listType = seriesType;
|
||||
break;
|
||||
case "games":
|
||||
listType = gamesType;
|
||||
break;
|
||||
default:
|
||||
listType = moviesType;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (searchParams.has("sortType")) {
|
||||
switch (searchParams.get("sortType")) {
|
||||
case "title":
|
||||
sortType = sortTypeTitle;
|
||||
break;
|
||||
case "year":
|
||||
sortType = sortTypeYear;
|
||||
break;
|
||||
case "id":
|
||||
sortType = sortTypeId;
|
||||
break;
|
||||
default:
|
||||
sortType = sortTypeTitle;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} type
|
||||
*/
|
||||
function changeType(type: number) {
|
||||
listType = type;
|
||||
loadPage();
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
switch (listType) {
|
||||
case moviesType:
|
||||
searchParams.set("listType", "movies");
|
||||
break;
|
||||
case gamesType:
|
||||
searchParams.set("listType", "games");
|
||||
break;
|
||||
case seriesType:
|
||||
searchParams.set("listType", "series");
|
||||
break;
|
||||
}
|
||||
history.replaceState({}, '', window.location.pathname + '?' + searchParams.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} type
|
||||
*/
|
||||
function changeSort(type: number) {
|
||||
sortType = type;
|
||||
loadPage();
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
switch (type) {
|
||||
case sortTypeTitle:
|
||||
searchParams.set("sortType", "title");
|
||||
break;
|
||||
case sortTypeYear:
|
||||
searchParams.set("sortType", "year");
|
||||
break;
|
||||
case sortTypeId:
|
||||
searchParams.set("sortType", "id");
|
||||
break;
|
||||
}
|
||||
history.replaceState({}, '', window.location.pathname + '?' + searchParams.toString());
|
||||
}
|
||||
|
||||
|
||||
function splitBySort(movies: Array<Movie>): { [s: string]: Movie[]; } {
|
||||
switch (sortType) {
|
||||
case sortTypeYear:
|
||||
const sorted = movies.sort((a, b) => {
|
||||
const ay = Date.parse(a.released);
|
||||
const by = Date.parse(b.released);
|
||||
return ay - by;
|
||||
});
|
||||
return splitByYear(sorted);
|
||||
case sortTypeId:
|
||||
movies.sort((a, b) => a.id < b.id ? 1 : -1);
|
||||
return { "added": movies };
|
||||
default:
|
||||
return splitByTitle(movies.sort((a, b) => a.title.localeCompare(b.title)));
|
||||
}
|
||||
}
|
||||
|
||||
function toggleEdit() {
|
||||
movieElements.forEach(element => {
|
||||
const div = element.querySelector(".d-none");
|
||||
if (!div) return;
|
||||
div.classList.remove("d-none");
|
||||
div.classList.add("d-flex");
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
document.getElementById("myform")?.addEventListener("submit", submitMedia);
|
||||
|
||||
listButtons.push(document.getElementById("movieButton"));
|
||||
listButtons.push(document.getElementById("gameButton"));
|
||||
listButtons.push(document.getElementById("seriesButton"));
|
||||
listButtons.forEach((button, index) => button?.addEventListener("click", () => changeType(index)));
|
||||
|
||||
sortButtons.push(document.getElementById("titleButton"));
|
||||
sortButtons.push(document.getElementById("yearButton"));
|
||||
sortButtons.push(document.getElementById("idButton"));
|
||||
sortButtons.forEach((button, index) => button?.addEventListener("click", () => changeSort(index)));
|
||||
|
||||
editButton = document.getElementById("editButton");
|
||||
editButton?.addEventListener("click", () => toggleEdit());
|
||||
|
||||
loadState();
|
||||
loadPage();
|
||||
});
|
||||
|
||||
async function loadPage() {
|
||||
|
||||
listButtons.forEach(button => button?.classList.remove("active"));
|
||||
listButtons[listType]?.classList.add("active");
|
||||
|
||||
try {
|
||||
const response = await fetch(getLink());
|
||||
const movies = await response.json();
|
||||
renderMedias(movies);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function removeMedia(evt: Event) {
|
||||
const password = document.getElementById("pass") as HTMLInputElement | null;
|
||||
if (!password) return;
|
||||
if (password.value == "") return;
|
||||
|
||||
let elem = evt.target as HTMLElement | null;
|
||||
|
||||
while (elem && !elem.classList.contains('media-element')) {
|
||||
elem = elem.parentElement;
|
||||
}
|
||||
|
||||
if (!elem) return;
|
||||
const id = elem.id;
|
||||
|
||||
fetch(getLink(), {
|
||||
body: JSON.stringify({ pass: password.value, code: id }),
|
||||
headers: { "Content-Type": "application/json" },
|
||||
method: "DELETE"
|
||||
})
|
||||
.then(async (response) => {
|
||||
|
||||
if (response.status != 204) {
|
||||
console.log("error");
|
||||
console.log(response.body);
|
||||
return;
|
||||
}
|
||||
document.getElementById(id)?.remove();
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err);
|
||||
});
|
||||
password.value = "";
|
||||
}
|
||||
|
||||
function onImgError(evt: Event) {
|
||||
const imgT = evt.target as HTMLImageElement;
|
||||
imgT.src = "/images/no_poster.jpg";
|
||||
}
|
||||
|
||||
function renderMedias(unsorted_movies: Array<Movie>) {
|
||||
root = document.getElementById('root');
|
||||
if (!root) return;
|
||||
|
||||
root.innerHTML = "";
|
||||
|
||||
const splitMovies = splitBySort(unsorted_movies);
|
||||
|
||||
let years;
|
||||
if (sortType == sortTypeTitle) {
|
||||
years = Object.keys(splitMovies).sort((a, b) => -b.localeCompare(a));
|
||||
} else {
|
||||
years = Object.keys(splitMovies).sort((a, b) => b.localeCompare(a));
|
||||
}
|
||||
|
||||
|
||||
root.appendChild(<MyHeader title={unsorted_movies.length} />);
|
||||
|
||||
for (const letter of years) {
|
||||
|
||||
const movies = splitMovies[letter];
|
||||
|
||||
const header = <MyHeader title={letter} num={movies.length} />;
|
||||
root.appendChild(header);
|
||||
|
||||
const row =
|
||||
<MediaContainer id={letter}>
|
||||
{movies.map(movie => {
|
||||
const med = <MediaElement webImg={movie.webImg} title={movie.title} released={movie.released} id={movie.code} fun={removeMedia} imageError={onImgError}></MediaElement>;
|
||||
movieElements.push(med);
|
||||
return med;
|
||||
})}
|
||||
</MediaContainer>;
|
||||
|
||||
root.appendChild(row);
|
||||
}
|
||||
}
|
7
frontend/list/types.d.ts
vendored
Normal file
7
frontend/list/types.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
interface Movie {
|
||||
title: string;
|
||||
released: string;
|
||||
code: string;
|
||||
webImg: string;
|
||||
id: string;
|
||||
}
|
Reference in New Issue
Block a user