четвертая лаба

This commit is contained in:
2025-05-23 22:01:34 +04:00
parent 38c52febe2
commit 8adf0a5727
20 changed files with 328 additions and 190 deletions

View File

@@ -10,6 +10,7 @@ const makeRequest = async (path, params, vars, method = "GET", data = null) => {
options.headers = { "Content-Type": "application/json;charset=utf-8" };
options.body = JSON.stringify(data);
}
console.log(`${URL}${path}${pathVariables}${requestParams}`);
const response = await fetch(`${URL}${path}${pathVariables}${requestParams}`, options);
if (!response.ok) {
throw new Error(`Response status: ${response?.status}`);
@@ -27,11 +28,37 @@ const makeRequest = async (path, params, vars, method = "GET", data = null) => {
}
};
export const getAllItems = (path, params) => makeRequest(path, params);
export async function getAllItems(path, query = "") {
const url = `${URL}${path}?${query}`;
try {
const response = await fetch(url, {
headers: {
Accept: "application/json",
},
});
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
return await response.json();
} catch (error) {
console.error(`Failed to fetch ${url}:`, error);
throw error;
}
}
export const getItem = (path, id) => makeRequest(path, null, id);
export const createItem = (path, data) => makeRequest(path, null, null, "POST", data);
export const createItem = async (path, data) => {
const response = await fetch(`${URL}${path}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
// eslint-disable-next-line no-return-await
return await response.json();
};
export const updateItem = (path, id, data) => makeRequest(path, null, id, "PUT", data);

View File

@@ -1,10 +1,26 @@
const populateSelect = (select, data) => {
data.forEach((playlist) => {
const option = document.createElement("option");
option.value = playlist.id;
option.innerText = playlist.name;
select.appendChild(option);
});
};
export default function populateSelect(selectElement, items) {
if (!selectElement || !(selectElement instanceof HTMLSelectElement)) {
throw new Error("Invalid select element");
}
export default populateSelect;
// Сохраняем статичные options
const staticOptions = Array.from(selectElement.options).filter((opt) => !opt.value);
// Создаем новый select
const newSelect = selectElement.cloneNode(false);
// Восстанавливаем статичные options
staticOptions.forEach((opt) => newSelect.add(opt.cloneNode(true)));
// Добавляем динамические items
if (Array.isArray(items)) {
items.forEach((item) => {
newSelect.add(new Option(item.name || item.title || `Item ${item.id}`, item.id));
});
}
// Заменяем элемент в DOM
selectElement.replaceWith(newSelect);
return newSelect;
}

View File

@@ -1,12 +1,17 @@
import { buttonIcon } from "./button-helper";
const deepGet = (obj, keys) => keys.reduce((xs, x) => xs?.[x] ?? null, obj);
const deepGetByPath = (obj, path) => {
const keys = path.split(".");
let result = obj;
const deepGetByPath = (obj, path) =>
deepGet(
obj,
path.split(".").filter((t) => t !== "")
);
// eslint-disable-next-line no-restricted-syntax
for (const key of keys) {
if (!result) break;
result = result[key];
}
return result || "";
};
const headCell = (text) => {
const column = document.createElement("th");

View File

@@ -1,70 +0,0 @@
import { Toast } from "bootstrap";
// eslint-disable-next-line import/no-extraneous-dependencies
import moment from "moment";
export const toastsInit = () => {
const wrapper = document.createElement("div");
wrapper.classList.add("toast-container", "position-fixed", "top-0", "end-0", "p-3", "mt-5");
const body = document.querySelector("body");
body.appendChild(wrapper);
return wrapper;
};
const toast = () => {
const toastElement = document.createElement("div");
toastElement.classList.add("toast", "bg-dark", "text-light");
toastElement.setAttribute("role", "alert");
return toastElement;
};
const caption = (title) => {
const captionElement = document.createElement("strong");
captionElement.classList.add("me-auto");
captionElement.innerText = title;
return captionElement;
};
const dateTime = () => {
const dateTimeElement = document.createElement("small");
dateTimeElement.classList.add("text-light");
dateTimeElement.innerText = moment().format("DD.MM HH:mm");
return dateTimeElement;
};
const closeButton = () => {
const closeElement = document.createElement("button");
closeElement.setAttribute("type", "button");
closeElement.setAttribute("data-bs-dismiss", "toast");
closeElement.classList.add("btn-close", "btn-close-white");
return closeElement;
};
const header = (title) => {
const headerElement = document.createElement("div");
headerElement.classList.add("toast-header", "bg-dark", "text-light");
headerElement.appendChild(caption(title));
headerElement.appendChild(dateTime());
headerElement.appendChild(closeButton());
return headerElement;
};
const body = (message) => {
const bodyElement = document.createElement("div");
bodyElement.classList.add("toast-body");
bodyElement.innerText = message;
return bodyElement;
};
export const showToast = (container, title, message) => {
const toastElement = toast();
toastElement.appendChild(header(title));
toastElement.appendChild(body(message));
toastElement.addEventListener("hidden.bs.toast", () => {
container.removeChild(toastElement);
});
container.appendChild(toastElement);
new Toast(toastElement).show();
};

View File

@@ -6,48 +6,77 @@ class FormElement extends HTMLElement {
super();
this.currentId = null;
console.log("Initializing FormElement...");
const saveCallback = this.save.bind(this);
const backCallback = this.back.bind(this);
this.view = new FormView(this, saveCallback, backCallback);
this.model = new FormModel();
console.log("Model initialized:", this.model);
}
async connectedCallback() {
const params = new URLSearchParams(document.location.search);
this.currentId = params.get("id") || null;
console.log("FormElement connected");
try {
this.view.render(this.model);
// Загружаем справочники
await this.model.getPlaylists();
await this.model.getCategories();
console.log("Playlists:", this.model.playlists);
console.log("Categories:", this.model.categories);
this.view.update(this.model);
if (!this.currentId) {
const nextId = await this.model.getNextId();
this.model.resetForNew();
this.model.element.id = nextId.toString();
// Устанавливаем первые доступные значения
if (this.model.playlists.length > 0) {
this.model.element.playlistId = this.model.playlists[0].id;
}
if (this.model.categories.length > 0) {
this.model.element.categoryId = this.model.categories[0].id;
}
}
this.view.render(this.model);
if (this.currentId) {
await this.get();
await this.model.get(this.currentId);
}
} catch (error) {
console.error("Error loading data:", error);
console.error("Error:", error);
}
}
async get() {
if (this.currentId) {
await this.model.get(this.currentId);
async get(id) {
if (id) {
await this.model.get(id);
}
this.view.update(this.model);
this.view.update(this.model); // Обновляем view в любом случае
}
async save() {
if (!this.currentId) {
await this.model.create();
} else {
await this.model.update();
try {
// Проверка заполненности обязательных полей
if (!this.model.element.name || !this.model.element.playlistId || !this.model.element.categoryId) {
console.error("Заполните все обязательные поля");
return;
}
const result = !this.currentId ? await this.model.create() : await this.model.update();
if (!result || !result.id) {
throw new Error("Сервер не вернул созданную запись");
}
this.currentId = result.id;
console.log("Успешно сохранено:", result);
// Перенаправляем на страницу редактирования
window.location.href = `/pageForm?id=${result.id}`;
} catch (error) {
console.error("Ошибка сохранения:", error);
}
this.view.successToast();
this.view.update(this.model);
}
back() {

View File

@@ -1,22 +1,53 @@
import { createItem, getAllItems, getItem, updateItem } from "../../api/client";
const PATH = "videos";
const PATH = "video";
export default class FormModel {
constructor() {
this.element = {};
this.resetForNew();
this.playlists = [];
this.categories = [];
this.lastId = 0;
}
resetForNew() {
this.element = {
id: "",
name: "",
image: "",
description: "",
playlistId: "",
categoryId: "",
};
}
async getNextId() {
try {
const items = await getAllItems(PATH);
this.lastId = items.length > 0 ? Math.max(...items.map((item) => parseInt(item.id, 10))) + 1 : 1;
return this.lastId;
} catch (error) {
console.error("Error fetching last ID:", error);
return Date.now(); // Fallback
}
}
async getPlaylists() {
this.playlists = [];
this.playlists = await getAllItems("playlists");
this.playlists = await getAllItems("playlist");
}
async getCategories() {
this.categories = [];
this.categories = await getAllItems("categories");
this.categories = await getAllItems("category");
}
getFullData() {
return {
...this.element,
playlistName: this.playlists.find((p) => p.id === this.element.playlistId)?.name || "",
categoryName: this.categories.find((c) => c.id === this.element.categoryId)?.name || "",
};
}
async get(id) {
@@ -28,9 +59,18 @@ export default class FormModel {
async create() {
if (!this.element || Object.keys(this.element).length === 0) {
throw new Error("Item is null or empty!");
throw new Error("Объект для создания пуст");
}
this.element = await createItem(PATH, this.element);
// Убедимся, что передаем все обязательные поля
const dataToSend = {
...this.element,
playlistId: this.element.playlistId || this.playlists[0]?.id,
categoryId: this.element.categoryId || this.categories[0]?.id,
};
this.element = await createItem(PATH, dataToSend);
return this.element; // Возвращаем обновленный элемент
}
async update() {

View File

@@ -1,6 +1,4 @@
import validation from "../../../js/validation";
import populateSelect from "../../select-helper";
import { showToast, toastsInit } from "../../toast-helper";
const getKey = (input) => input.getAttribute("id").replace("-", "_");
@@ -9,62 +7,134 @@ export default class FormView {
this.root = root;
this.saveCallback = saveCallback;
this.backCallback = backCallback;
this.playlistsSelector = null; // Явная инициализация
this.categoriesSelector = null;
this.inputs = null;
}
render(model) {
const template = document.getElementById("videos-form-template").content.cloneNode(true);
this.root.innerHTML = ""; // Очищаем перед рендером
// Клонируем шаблон
const template = document.getElementById("videos-form-template");
if (!template) {
console.error("Template not found!");
return;
}
console.debug("GOOD");
const form = template.querySelector("form");
form.addEventListener("submit", async () => {
if (!form.checkValidity()) {
return;
}
this.saveCallback();
const content = template.content.cloneNode(true);
this.root.appendChild(content);
console.log("Model data when rendering:", {
playlists: model.playlists,
categories: model.categories,
element: model.element,
});
this.inputs = template.querySelectorAll("input");
this.inputs.forEach((input) => {
input.addEventListener("change", (event) => {
model.setValue(getKey(event.target), event.target.value);
// Инициализируем элементы
this.form = this.root.querySelector("form");
this.inputs = this.root.querySelectorAll("input");
this.currentId = this.root.querySelector("id");
this.playlistsSelector = this.root.querySelector("#playlist");
this.categoriesSelector = this.root.querySelector("#category");
this.backButton = this.root.querySelector("#btn-back");
// Добавляем обработчики только если элементы существуют
if (this.form) {
this.form.addEventListener("submit", async (e) => {
e.preventDefault();
if (this.form.checkValidity()) {
await this.saveCallback();
}
});
});
}
this.playlistsSelector = template.getElementById("playlist");
this.playlistsSelector.addEventListener("change", (event) => {
model.setValue("playlistId", event.target.value);
});
populateSelect(this.playlistsSelector, model.playlists);
if (this.inputs) {
this.inputs.forEach((input) => {
input.addEventListener("change", (event) => {
model.setValue(getKey(event.target), event.target.value);
});
});
}
this.categoriesSelector = template.getElementById("category");
this.categoriesSelector.addEventListener("change", (event) => {
model.setValue("categoryId", event.target.value);
});
populateSelect(this.categoriesSelector, model.categories);
if (this.playlistsSelector && model.playlists.length > 0) {
this.playlistsSelector.value = model.element.playlistId || model.playlists[0].id;
}
const backButton = template.getElementById("btn-back");
backButton.addEventListener("click", this.backCallback);
if (this.playlistsSelector) {
this.playlistsSelector.addEventListener("change", (event) => {
model.setValue("playlistId", event.target.value);
});
this.updateSelect(this.playlistsSelector, model.playlists, model.element.playlistId);
}
this.root.appendChild(template);
if (this.categoriesSelector) {
this.categoriesSelector.addEventListener("change", (event) => {
model.setValue("categoryId", event.target.value);
});
this.updateSelect(this.categoriesSelector, model.categories, model.element.categoryId);
}
this.toasts = toastsInit();
if (this.backButton) {
this.backButton.addEventListener("click", this.backCallback);
}
validation();
// Первоначальное заполнение данных
this.update(model);
}
update(model) {
this.inputs.forEach((input) => {
const control = input;
control.value = model.getValue(getKey(input));
});
this.playlistsSelector.value = model.element.playlistId || "";
this.categoriesSelector.value = model.element.categoryId || "";
if (!model || !model.element) return;
// Обновляем inputs
if (this.inputs) {
this.inputs.forEach((input) => {
const key = getKey(input);
// eslint-disable-next-line no-param-reassign
input.value = model.getValue(key) || "";
});
}
// Принудительно обновляем select'ы
if (this.playlistsSelector) {
this.playlistsSelector.value = model.element.playlistId || "";
}
if (this.categoriesSelector) {
this.categoriesSelector.value = model.element.categoryId || "";
}
}
successToast() {
showToast(this.toasts, "Сохранение", "Сохранение успешно завершено");
updateSelect(selectElement, items, selectedValue) {
if (!selectElement) return;
// Создаем новый select
const newSelect = selectElement.cloneNode(false);
// Сохраняем статичные options (с пустым value)
const staticOptions = Array.from(selectElement.options).filter((opt) => !opt.value);
// Добавляем статичные options
staticOptions.forEach((opt) => newSelect.add(opt.cloneNode(true)));
// Добавляем динамические items
if (Array.isArray(items)) {
items.forEach((item) => {
newSelect.add(new Option(item.name, item.id));
});
}
// Устанавливаем выбранное значение
if (selectedValue) {
newSelect.value = selectedValue;
}
// Заменяем элемент в DOM
selectElement.replaceWith(newSelect);
// Обновляем ссылку в классе, если это основной select
if (selectElement === this.playlistsSelector) {
this.playlistsSelector = newSelect;
} else if (selectElement === this.categoriesSelector) {
this.categoriesSelector = newSelect;
}
}
}

View File

@@ -33,7 +33,6 @@ class TableElement extends HTMLElement {
async deleteVideo(item) {
if (await this.view.deleteQuestion(item)) {
await this.model.delete(item);
this.view.successToast();
this.getAllVideos();
}
}

View File

@@ -11,11 +11,20 @@ export default class TableModel {
}
async getAll() {
const elements = await getAllItems(PATH, "_embed=playlist&_embed=category");
this.data = elements.map((item) => ({
...item,
star: this.stars.includes(item.id),
}));
try {
const videos = await getAllItems("video");
const playlists = await getAllItems("playlist");
const categories = await getAllItems("category");
this.data = videos.map((video) => ({
...video,
playlistName: playlists.find((p) => p.id === video.playlistId)?.name || "Не указан",
categoryName: categories.find((c) => c.id === video.categoryId)?.name || "Не указан",
}));
} catch (error) {
console.error("Failed to load videos:", error);
this.data = [];
}
}
async delete(item) {

View File

@@ -1,6 +1,5 @@
import showQuestion from "../../modal-helper";
import { populateTable, table } from "../../table-helper";
import { showToast, toastsInit } from "../../toast-helper";
export default class TableView {
constructor(root, toggleStarCallback, editCallback, deleteCallback) {
@@ -12,7 +11,7 @@ export default class TableView {
render() {
const columns = ["", "№", "Название", "Превью", "Описание", "Плейлист", "Категория", "Группа", "", ""];
this.keys = ["id", "name", "image", "description", "playlist.name", "category.name"];
this.keys = ["id", "name", "image", "description", "playlistName", "categoryName"];
this.callbacks = {
star: this.toggleStarCallback,
edit: this.editCallback,
@@ -24,19 +23,25 @@ export default class TableView {
tableWrapper.classList.add("table-responsive");
tableWrapper.appendChild(this.tableElement);
this.root.appendChild(tableWrapper);
this.toasts = toastsInit();
}
update(model) {
populateTable(this.tableElement, model.data, this.keys, this.callbacks);
const displayData =
model.data.length > 0
? model.data
: [
{
id: "Нет данных",
name: "",
playlistName: "",
categoryName: "",
},
];
populateTable(this.tableElement, displayData, this.keys, this.callbacks);
}
deleteQuestion(item = null) {
return showQuestion("Удаление", "Удалить", `Удалить элемент '${item.id}'?`);
}
successToast() {
showToast(this.toasts, "Удаление", "Удаление успешно завершено");
}
}

View File

@@ -6,7 +6,7 @@
"image": "adfefwefw",
"description": "frefeerfe",
"playlistId": "1",
"сategoryId": "1"
"categoryId": "1"
},
{
"id": "2",
@@ -14,7 +14,7 @@
"image": "fdsdvrvr",
"description": "vvgtgtg",
"playlistId": "0",
"сategoryId": "1"
"categoryId": "1"
},
{
"id": "3",
@@ -22,15 +22,23 @@
"image": "gergreg",
"description": "wsfrf3",
"playlistId": "3",
"сategoryId": "2"
"categoryId": "2"
},
{
"id": "4",
"name": "new_vid4",
"image": "btbtb4b",
"description": "xccrfr",
"name": "NEW VIDEO",
"image": "preview",
"description": "da",
"playlistId": "2",
"сategoryId": "3"
"categoryId": "3"
},
{
"id": "5",
"name": "1234124",
"image": "4аывацу",
"description": "3213133",
"playlistId": "1",
"categoryId": "1"
}
],
"playlist": [
@@ -47,7 +55,7 @@
"name": "How to make a nuclear bomb at home! Guide"
}
],
"сategory": [
"category": [
{
"id": "1",
"name": "jst vibing"

1
dist/assets/client-BDIKmhNV.js vendored Normal file
View File

@@ -0,0 +1 @@
(function(){const r=document.createElement("link").relList;if(r&&r.supports&&r.supports("modulepreload"))return;for(const e of document.querySelectorAll('link[rel="modulepreload"]'))n(e);new MutationObserver(e=>{for(const t of e)if(t.type==="childList")for(const c of t.addedNodes)c.tagName==="LINK"&&c.rel==="modulepreload"&&n(c)}).observe(document,{childList:!0,subtree:!0});function s(e){const t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),e.crossOrigin==="use-credentials"?t.credentials="include":e.crossOrigin==="anonymous"?t.credentials="omit":t.credentials="same-origin",t}function n(e){if(e.ep)return;e.ep=!0;const t=s(e);fetch(e.href,t)}})();const i="http://localhost:3000/",u=async(o,r,s,n="GET",e=null)=>{try{const t=r?`?${r}`:"",c=s?`/${s}`:"",l={method:n};(n==="POST"||n==="PUT")&&e&&(l.headers={"Content-Type":"application/json;charset=utf-8"},l.body=JSON.stringify(e)),console.log(`${i}${o}${c}${t}`);const a=await fetch(`${i}${o}${c}${t}`,l);if(!a.ok)throw new Error(`Response status: ${a==null?void 0:a.status}`);const f=await a.json();return console.debug(o,f),f}catch(t){throw t instanceof SyntaxError?new Error("There was a SyntaxError",t):new Error("There was an error",t)}};async function p(o,r=""){const s=`${i}${o}?${r}`;try{const n=await fetch(s,{headers:{Accept:"application/json"}});if(!n.ok)throw new Error(`HTTP error! status: ${n.status}`);return await n.json()}catch(n){throw console.error(`Failed to fetch ${s}:`,n),n}}const h=(o,r)=>u(o,null,r),y=async(o,r)=>{const s=await fetch(`${i}${o}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)});if(!s.ok)throw new Error(`HTTP error! status: ${s.status}`);return s.json()},w=(o,r,s)=>u(o,null,r,"PUT",s),$=(o,r)=>u(o,null,r,"DELETE");export{h as a,y as c,$ as d,p as g,w as u};

View File

@@ -1 +0,0 @@
import{g as y,d as f,M as w,t as A,s as v}from"./toast-helper-Cn8ctBTk.js";/* empty css */const p=(e,t)=>{localStorage.setItem(e,JSON.stringify(t))},T=e=>JSON.parse(localStorage.getItem(e))||[],u="video",c="video-stars";class S{constructor(){this.data=[],this.stars=T(c)}async getAll(){const t=await y(u,"_embed=playlist&_embed=category");this.data=t.map(n=>({...n,star:this.stars.includes(n.id)}))}async delete(t){await f(u,t.id),this.stars=this.stars.filter(n=>n!==t.id),p(c,this.stars)}toggleStar(t){const n=t;n.star=!n.star,n.star?this.stars.push(n.id):this.stars=this.stars.filter(s=>s!==n.id),p(c,this.stars)}}const L=()=>{const e=document.createElement("div");return e.classList.add("modal"),e.setAttribute("tabindex",-1),e},d=e=>{const t=document.createElement("div");return t.classList.add(e),t},C=e=>{const t=document.createElement("button");return t.setAttribute("type","button"),t.addEventListener("click",()=>e()),t},k=(e,t)=>{const n=d("modal-header"),s=document.createElement("h5");s.innerText=e,n.appendChild(s);const o=C(t);return o.classList.add("btn-close"),n.appendChild(o),n},V=(e,t,n)=>{const s=d("modal-footer"),o=document.createElement("button");o.classList.add("btn","btn-primary"),o.setAttribute("type","button"),o.innerText=e,o.addEventListener("click",()=>t()),s.appendChild(o);const a=C(n);return a.classList.add("btn","btn-secondary"),a.innerText="Отмена",s.appendChild(a),s},I=(e,t,n,s,o)=>{const a=L(),i=d("modal-dialog"),l=d("modal-content"),r=k(e,o);l.appendChild(r);const m=d("modal-body");m.appendChild(n),l.appendChild(m);const g=V(t,s,o);return l.appendChild(g),i.appendChild(l),a.appendChild(i),a},x=(e,t,n)=>{const s=document.createElement("p");return s.innerText=n,new Promise(o=>{let a;const r=I(e,t,s,()=>{a.hide(),o(!0)},()=>{a.hide(),o(!1)});a=new w(r),a.show()})},b=e=>{const t=document.createElement("i");return t.classList.add("bi",`bi-${e}`),t},B=(e,t,n="primary")=>{const s=document.createElement("button");return s.classList.add("btn",`btn-${n}`),s.setAttribute("type","button"),b&&s.appendChild(b(t)),s},M=(e,t)=>B(null,e,t),$=(e,t)=>t.reduce((n,s)=>(n==null?void 0:n[s])??null,e),O=(e,t)=>$(e,t.split(".").filter(n=>n!=="")),P=e=>{const t=document.createElement("th");return t.setAttribute("scope","col"),t.innerText=e,t},Q=e=>{if(!e||!Array.isArray(e)||e.length===0)throw new Error("Columns are not defined");const t=document.createElement("thead"),n=document.createElement("tr");return e.forEach(s=>n.appendChild(P(s))),t.appendChild(n),t},E=e=>{const t=document.createElement("td");return t.innerText=e,t},h=(e,t,n,s)=>{const o=M(t,n);o.addEventListener("click",()=>s(e));const a=document.createElement("td");return a.appendChild(o),a},R=(e,t,n)=>{const s=document.createElement("tr");if(n!=null&&n.star){const o=e.star?"star-fill":"star";s.appendChild(h(e,o,"null",n.star))}return!t||!Array.isArray(t)?Object.values(e).forEach(o=>s.appendChild(E(o))):t.forEach(o=>s.appendChild(E(O(e,o)))),n&&(n.edit&&s.appendChild(h(e,"pencil-fill","warning",n.edit)),n.delete&&s.appendChild(h(e,"trash-fill","danger",n.delete))),s},_=(e,t,n)=>{if(!e||!Array.isArray(e))throw new Error("Data is not defined");const s=document.createElement("tbody");return e.forEach(o=>s.appendChild(R(o,t,n))),s},q=e=>{const t=document.createElement("table");return t.classList.add("table","table-hover","table-sm"),t.appendChild(Q(e)),t},D=(e,t,n,s)=>{const o=e.querySelector("tbody");o&&e.removeChild(o),e.appendChild(_(t,n,s))};class G{constructor(t,n,s,o){this.root=t,this.toggleStarCallback=n,this.editCallback=s,this.deleteCallback=o}render(){const t=["","№","Название","Превью","Описание","Плейлист","Категория","Группа","",""];this.keys=["id","name","image","description","playlist.name","category.name"],this.callbacks={star:this.toggleStarCallback,edit:this.editCallback,delete:this.deleteCallback},this.tableElement=q(t);const n=document.createElement("div");n.classList.add("table-responsive"),n.appendChild(this.tableElement),this.root.appendChild(n),this.toasts=A()}update(t){D(this.tableElement,t.data,this.keys,this.callbacks)}deleteQuestion(t=null){return x("Удаление","Удалить",`Удалить элемент '${t.id}'?`)}successToast(){v(this.toasts,"Удаление","Удаление успешно завершено")}}class H extends HTMLElement{constructor(){super();const t=this.toggleStar.bind(this),n=this.editVideo.bind(this),s=this.deleteVideo.bind(this);this.view=new G(this,t,n,s),this.model=new S}connectedCallback(){this.view.render(),this.getAllVideos()}async getAllVideos(){await this.model.getAll(),this.view.update(this.model)}editVideo(t){window.location.href=`/pageForm?id=${t.id}`}toggleStar(t){this.model.toggleStar(t),this.view.update(this.model)}async deleteVideo(t){await this.view.deleteQuestion(t)&&(await this.model.delete(t),this.view.successToast(),this.getAllVideos())}}customElements.define("videos-table",H);

5
dist/assets/page3-C-EbPu6q.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
import{g as n,a as h,c as d,u as m,t as u,s as g}from"./toast-helper-Cn8ctBTk.js";/* empty css */const l="videos";class p{constructor(){this.element={},this.playlists=[],this.categories=[]}async getPlaylists(){this.playlists=[],this.playlists=await n("playlists")}async getCategories(){this.categories=[],this.categories=await n("categories")}async get(e){if(!e)throw new Error("Element id is not defined!");this.element=await h(l,e)}async create(){if(!this.element||Object.keys(this.element).length===0)throw new Error("Item is null or empty!");this.element=await d(l,this.element)}async update(){if(!this.element||Object.keys(this.element).length===0)throw new Error("Item is null or empty!");this.element=await m(l,this.element.id,this.element)}getValue(e){return this.element[e]||""}setValue(e,t){this.element[e]=t}}const y=()=>{document.querySelectorAll("form.needs-validation").forEach(e=>{e.setAttribute("novalidate",""),e.addEventListener("submit",t=>{t.preventDefault(),t.stopPropagation(),e.checkValidity()?e.classList.remove("was-validated"):e.classList.add("was-validated")})})},c=(a,e)=>{e.forEach(t=>{const s=document.createElement("option");s.value=t.id,s.innerText=t.name,a.appendChild(s)})},r=a=>a.getAttribute("id").replace("-","_");class v{constructor(e,t,s){this.root=e,this.saveCallback=t,this.backCallback=s}render(e){const t=document.getElementById("videos-form-template").content.cloneNode(!0);if(!t){console.error("Template not found!");return}console.debug("GOOD");const s=t.querySelector("form");s.addEventListener("submit",async()=>{s.checkValidity()&&this.saveCallback()}),this.inputs=t.querySelectorAll("input"),this.inputs.forEach(i=>{i.addEventListener("change",o=>{e.setValue(r(o.target),o.target.value)})}),this.playlistsSelector=t.getElementById("playlist"),this.playlistsSelector.addEventListener("change",i=>{e.setValue("playlistId",i.target.value)}),c(this.playlistsSelector,e.playlists),this.categoriesSelector=t.getElementById("category"),this.categoriesSelector.addEventListener("change",i=>{e.setValue("categoryId",i.target.value)}),c(this.categoriesSelector,e.categories),t.getElementById("btn-back").addEventListener("click",this.backCallback),this.root.appendChild(t),this.toasts=u(),y()}update(e){this.inputs.forEach(t=>{const s=t;s.value=e.getValue(r(t))}),this.playlistsSelector.value=e.element.playlistId||"",this.categoriesSelector.value=e.element.categoryId||""}successToast(){g(this.toasts,"Сохранение","Сохранение успешно завершено")}}class w extends HTMLElement{constructor(){super(),this.currentId=null;const e=this.save.bind(this),t=this.back.bind(this);this.view=new v(this,e,t),this.model=new p}async connectedCallback(){const e=new URLSearchParams(document.location.search);this.currentId=e.get("id")||null,console.log("FormElement connected");try{this.view.render(this.model),await this.model.getPlaylists(),await this.model.getCategories(),console.log("Playlists:",this.model.playlists),console.log("Categories:",this.model.categories),this.view.update(this.model),this.currentId&&await this.get()}catch(t){console.error("Error loading data:",t)}}async get(){this.currentId&&await this.model.get(this.currentId),this.view.update(this.model)}async save(){this.currentId?await this.model.update():await this.model.create(),this.view.successToast(),this.view.update(this.model)}back(){window.location.href="/pageAccount"}}customElements.define("videos-form",w);

1
dist/assets/page6-TNsk-iOd.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -6,8 +6,8 @@
<title>Мой аккаунт</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.5/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-SgOJa3DmI69IUzQ2PVdRZhwQ+dy64/BUtbMJw1MZ8t5HZApcHrRKUc4W0kG879m7" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css">
<script type="module" crossorigin src="/assets/page3-B-T1pNgL.js"></script>
<script type="module" crossorigin src="/assets/page3-C-EbPu6q.js"></script>
<link rel="modulepreload" crossorigin href="/assets/client-BDIKmhNV.js">
<link rel="stylesheet" crossorigin href="/assets/styleStreamingService-CUHvxLFI.css">
</head>

4
dist/pageForm.html vendored
View File

@@ -6,8 +6,8 @@
<title>Новое видео</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.5/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-SgOJa3DmI69IUzQ2PVdRZhwQ+dy64/BUtbMJw1MZ8t5HZApcHrRKUc4W0kG879m7" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css">
<script type="module" crossorigin src="/assets/page6-CVUz5E0R.js"></script>
<script type="module" crossorigin src="/assets/page6-TNsk-iOd.js"></script>
<link rel="modulepreload" crossorigin href="/assets/client-BDIKmhNV.js">
<link rel="stylesheet" crossorigin href="/assets/styleStreamingService-CUHvxLFI.css">
</head>

9
server.json Normal file
View File

@@ -0,0 +1,9 @@
{
"/": {
"cors": true,
"headers": {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS"
}
}
}