четвертая лаба
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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, "Удаление", "Удаление успешно завершено");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
1
dist/assets/client-BDIKmhNV.js
vendored
Normal 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};
|
||||
1
dist/assets/page3-B-T1pNgL.js
vendored
1
dist/assets/page3-B-T1pNgL.js
vendored
@@ -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
5
dist/assets/page3-C-EbPu6q.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/assets/page6-CVUz5E0R.js
vendored
1
dist/assets/page6-CVUz5E0R.js
vendored
@@ -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
1
dist/assets/page6-TNsk-iOd.js
vendored
Normal file
File diff suppressed because one or more lines are too long
14
dist/assets/toast-helper-Cn8ctBTk.js
vendored
14
dist/assets/toast-helper-Cn8ctBTk.js
vendored
File diff suppressed because one or more lines are too long
4
dist/pageAccount.html
vendored
4
dist/pageAccount.html
vendored
@@ -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
4
dist/pageForm.html
vendored
@@ -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
9
server.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"/": {
|
||||
"cors": true,
|
||||
"headers": {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user