mirror of
https://github.com/wowlikon/LiB.git
synced 2026-02-04 04:31:09 +00:00
Обновление фронтэнда
This commit is contained in:
@@ -11,9 +11,9 @@ from .misc import router as misc_router
|
||||
api_router = APIRouter()
|
||||
|
||||
# Подключение всех маршрутов
|
||||
api_router.include_router(auth_router)
|
||||
api_router.include_router(authors_router)
|
||||
api_router.include_router(books_router)
|
||||
api_router.include_router(genres_router)
|
||||
api_router.include_router(relationships_router)
|
||||
api_router.include_router(misc_router)
|
||||
api_router.include_router(auth_router, prefix="/api")
|
||||
api_router.include_router(authors_router, prefix="/api")
|
||||
api_router.include_router(books_router, prefix="/api")
|
||||
api_router.include_router(genres_router, prefix="/api")
|
||||
api_router.include_router(relationships_router, prefix="/api")
|
||||
|
||||
@@ -34,6 +34,13 @@ async def root(request: Request, app=Depends(get_app)):
|
||||
return templates.TemplateResponse(request, "index.html", get_info(app))
|
||||
|
||||
|
||||
@router.get("/auth", include_in_schema=False)
|
||||
async def root(request: Request, app=Depends(get_app)):
|
||||
"""Эндпоинт страницы авторизации"""
|
||||
return templates.TemplateResponse(request, "auth.html", get_info(app))
|
||||
|
||||
|
||||
|
||||
@router.get("/api", include_in_schema=False)
|
||||
async def root(request: Request, app=Depends(get_app)):
|
||||
"""Страница с сылками на документацию API"""
|
||||
|
||||
@@ -1,72 +1,59 @@
|
||||
// Load authors and genres asynchronously
|
||||
Promise.all([
|
||||
$(document).ready(function () {
|
||||
Promise.all([
|
||||
fetch("/authors").then((response) => response.json()),
|
||||
fetch("/genres").then((response) => response.json()),
|
||||
])
|
||||
])
|
||||
.then(([authorsData, genresData]) => {
|
||||
// Populate authors dropdown
|
||||
const dropdown = document.getElementById("author-dropdown");
|
||||
const $dropdown = $("#author-dropdown");
|
||||
authorsData.authors.forEach((author) => {
|
||||
const div = document.createElement("div");
|
||||
div.className = "p-2 hover:bg-gray-100 cursor-pointer";
|
||||
div.setAttribute("data-value", author.name);
|
||||
div.textContent = author.name;
|
||||
dropdown.appendChild(div);
|
||||
const $div = $("<div>", {
|
||||
class: "p-2 hover:bg-gray-100 cursor-pointer",
|
||||
"data-value": author.name,
|
||||
text: author.name,
|
||||
});
|
||||
$dropdown.append($div);
|
||||
});
|
||||
|
||||
// Populate genres list
|
||||
const list = document.getElementById("genres-list");
|
||||
const $list = $("#genres-list");
|
||||
genresData.genres.forEach((genre) => {
|
||||
const li = document.createElement("li");
|
||||
li.className = "mb-1";
|
||||
li.innerHTML = `
|
||||
const $li = $("<li>", { class: "mb-1" });
|
||||
$li.html(`
|
||||
<label class="custom-checkbox flex items-center">
|
||||
<input type="checkbox" />
|
||||
<span class="checkmark"></span>
|
||||
${genre.name}
|
||||
</label>
|
||||
`;
|
||||
list.appendChild(li);
|
||||
`);
|
||||
$list.append($li);
|
||||
});
|
||||
|
||||
initializeAuthorDropdown();
|
||||
})
|
||||
.catch((error) => console.error("Error loading data:", error));
|
||||
|
||||
function initializeAuthorDropdown() {
|
||||
const authorSearchInput = document.getElementById("author-search-input");
|
||||
const authorDropdown = document.getElementById("author-dropdown");
|
||||
const selectedAuthorsContainer = document.getElementById(
|
||||
"selected-authors-container",
|
||||
);
|
||||
const dropdownItems = authorDropdown.querySelectorAll("[data-value]");
|
||||
function initializeAuthorDropdown() {
|
||||
const $authorSearchInput = $("#author-search-input");
|
||||
const $authorDropdown = $("#author-dropdown");
|
||||
const $selectedAuthorsContainer = $("#selected-authors-container");
|
||||
const $dropdownItems = $authorDropdown.find("[data-value]");
|
||||
let selectedAuthors = new Set();
|
||||
|
||||
// Function to update highlights in dropdown
|
||||
const updateDropdownHighlights = () => {
|
||||
dropdownItems.forEach((item) => {
|
||||
const value = item.dataset.value;
|
||||
if (selectedAuthors.has(value)) {
|
||||
item.classList.add("bg-gray-200");
|
||||
} else {
|
||||
item.classList.remove("bg-gray-200");
|
||||
}
|
||||
$dropdownItems.each(function () {
|
||||
const value = $(this).data("value");
|
||||
$(this).toggleClass("bg-gray-200", selectedAuthors.has(value));
|
||||
});
|
||||
};
|
||||
|
||||
// Function to render selected authors
|
||||
const renderSelectedAuthors = () => {
|
||||
Array.from(selectedAuthorsContainer.children).forEach((child) => {
|
||||
if (child.id !== "author-search-input") {
|
||||
child.remove();
|
||||
}
|
||||
});
|
||||
$selectedAuthorsContainer.children().not("#author-search-input").remove();
|
||||
|
||||
selectedAuthors.forEach((author) => {
|
||||
const authorChip = document.createElement("span");
|
||||
authorChip.className =
|
||||
"flex items-center bg-gray-200 text-gray-800 text-sm font-medium px-2.5 py-0.5 rounded-full";
|
||||
authorChip.innerHTML = `
|
||||
const $authorChip = $("<span>", {
|
||||
class:
|
||||
"flex items-center bg-gray-200 text-gray-800 text-sm font-medium px-2.5 py-0.5 rounded-full",
|
||||
});
|
||||
$authorChip.html(`
|
||||
${author}
|
||||
<button type="button" class="ml-1 inline-flex items-center p-0.5 text-sm text-gray-400 bg-transparent rounded-sm hover:bg-gray-200 hover:text-gray-900" data-author="${author}">
|
||||
<svg class="w-2 h-2" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
|
||||
@@ -74,62 +61,55 @@ function initializeAuthorDropdown() {
|
||||
</svg>
|
||||
<span class="sr-only">Remove author</span>
|
||||
</button>
|
||||
`;
|
||||
selectedAuthorsContainer.insertBefore(authorChip, authorSearchInput);
|
||||
`);
|
||||
$selectedAuthorsContainer.append($authorChip);
|
||||
});
|
||||
updateDropdownHighlights();
|
||||
};
|
||||
|
||||
// Handle input focus to show dropdown
|
||||
authorSearchInput.addEventListener("focus", () => {
|
||||
authorDropdown.classList.remove("hidden");
|
||||
$authorSearchInput.on("focus", () => {
|
||||
$authorDropdown.removeClass("hidden");
|
||||
});
|
||||
|
||||
// Handle input for filtering
|
||||
authorSearchInput.addEventListener("input", () => {
|
||||
const query = authorSearchInput.value.toLowerCase();
|
||||
dropdownItems.forEach((item) => {
|
||||
const text = item.textContent.toLowerCase();
|
||||
item.style.display = text.includes(query) ? "block" : "none";
|
||||
$authorSearchInput.on("input", function () {
|
||||
const query = $(this).val().toLowerCase();
|
||||
$dropdownItems.each(function () {
|
||||
const text = $(this).text().toLowerCase();
|
||||
$(this).css("display", text.includes(query) ? "block" : "none");
|
||||
});
|
||||
authorDropdown.classList.remove("hidden");
|
||||
$authorDropdown.removeClass("hidden");
|
||||
});
|
||||
|
||||
// Handle clicks outside to hide dropdown
|
||||
document.addEventListener("click", (event) => {
|
||||
$(document).on("click", function (event) {
|
||||
if (
|
||||
!selectedAuthorsContainer.contains(event.target) &&
|
||||
!authorDropdown.contains(event.target)
|
||||
!$selectedAuthorsContainer.is(event.target) &&
|
||||
!$selectedAuthorsContainer.has(event.target).length &&
|
||||
!$authorDropdown.is(event.target) &&
|
||||
!$authorDropdown.has(event.target).length
|
||||
) {
|
||||
authorDropdown.classList.add("hidden");
|
||||
$authorDropdown.addClass("hidden");
|
||||
}
|
||||
});
|
||||
|
||||
// Handle author selection from dropdown
|
||||
authorDropdown.addEventListener("click", (event) => {
|
||||
const selectedValue = event.target.dataset.value;
|
||||
if (selectedValue) {
|
||||
$authorDropdown.on("click", "[data-value]", function () {
|
||||
const selectedValue = $(this).data("value");
|
||||
if (selectedAuthors.has(selectedValue)) {
|
||||
selectedAuthors.delete(selectedValue);
|
||||
} else {
|
||||
selectedAuthors.add(selectedValue);
|
||||
}
|
||||
authorSearchInput.value = "";
|
||||
$authorSearchInput.val("");
|
||||
renderSelectedAuthors();
|
||||
authorSearchInput.focus();
|
||||
}
|
||||
$authorSearchInput.focus();
|
||||
});
|
||||
|
||||
// Handle removing selected author chip
|
||||
selectedAuthorsContainer.addEventListener("click", (event) => {
|
||||
if (event.target.closest("button")) {
|
||||
const authorToRemove = event.target.closest("button").dataset.author;
|
||||
$selectedAuthorsContainer.on("click", "button", function () {
|
||||
const authorToRemove = $(this).data("author");
|
||||
selectedAuthors.delete(authorToRemove);
|
||||
renderSelectedAuthors();
|
||||
authorSearchInput.focus();
|
||||
}
|
||||
$authorSearchInput.focus();
|
||||
});
|
||||
|
||||
// Initial render and highlights (without auto-focus)
|
||||
renderSelectedAuthors();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -50,11 +50,12 @@
|
||||
<p>Current Time: {{ server_time }}</p>
|
||||
<p>Status: {{ status }}</p>
|
||||
<ul>
|
||||
<li><a href="/docs">Swagger UI</a></li>
|
||||
<li><a href="/redoc">ReDoc</a></li>
|
||||
<li><a href="/">Home page</a></li>
|
||||
<li>
|
||||
<a href="https://github.com/wowlikon/LibraryAPI">Source Code</a>
|
||||
</li>
|
||||
<li><a href="/docs">Swagger UI</a></li>
|
||||
<li><a href="/redoc">ReDoc</a></li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>LiB</title>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>LiB</title>
|
||||
<script src="https://unpkg.com/cash-dom@1.4.0/dist/cash.min.js"></script>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link rel="stylesheet" href="static/styles.css" />
|
||||
</head>
|
||||
@@ -21,7 +22,7 @@
|
||||
<a href="/" class="hover:text-gray-200">Home</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="hover:text-gray-200">Products</a>
|
||||
<a href="#" class="hover:text-gray-200">books</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="hover:text-gray-200">About</a>
|
||||
|
||||
Reference in New Issue
Block a user