Add further responsive UI using uiKit (#36)

Utilising [UiKit](https://getuikit.com/) as the CSS framework

Adjusted existing functionality & CSS to integrate with UiKits - should
retain same functionality

- Ui colors are UiKit defaults
- Search results automatically expand when searching
- UiKit ensures its automatically responsive for mobile

### **Search results**

![image](https://github.com/user-attachments/assets/7103b542-9eba-4b56-a730-4165a93ae099)

### **Details dialog**

![image](https://github.com/user-attachments/assets/ff5a69c3-522a-40f0-b04d-9c1e4a2c34fb)

![image](https://github.com/user-attachments/assets/a194ff25-e3da-40b3-a5f9-48a483036491)

**- The dialog could be improved further by adjusting to a modal in
UiKit purely, however wasn't sure on how to do / time reqs.**

### **Search Results & Download Status are in expandable accordions**

![image](https://github.com/user-attachments/assets/2b4f25c4-4c8b-4bde-8861-833399af7d1a)

![image](https://github.com/user-attachments/assets/3ac0447b-7843-4750-9859-94a79fa65f9a)


### **Adjusted search to UiKits with clickable search icon**

![image](https://github.com/user-attachments/assets/8b331063-1045-4759-b05c-64a2e1a738ea)

### **Added gitHub link to footer and increased font sizing**

![image](https://github.com/user-attachments/assets/c9822fd3-3b31-4bbd-bb24-c2272c6af5e3)

### **Mobile views (Chrome on Desktop) - scales & retains scroll**

![image](https://github.com/user-attachments/assets/fb598d07-e6c5-4ab2-ae86-782aa74853a8)

![image](https://github.com/user-attachments/assets/fb04fb6b-be54-4309-a5ba-9636b8691593)

![image](https://github.com/user-attachments/assets/66b0b326-83d1-4131-8d3a-66bf111ca2ad)

### **TODO:**

- Improvements to modal functionality
- Pagination
- Cleaning (removing unused CSS etc)
This commit is contained in:
cthorne
2025-01-05 13:00:51 -05:00
committed by GitHub
parent 268dac069a
commit 8dd975abe0
3 changed files with 117 additions and 125 deletions
+1 -28
View File
@@ -33,12 +33,6 @@ header {
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
main {
max-width: 1200px;
margin: 0 auto;
padding: 1rem;
}
footer {
text-align: center;
padding: 1rem;
@@ -73,27 +67,6 @@ footer {
outline: none;
}
.search-container button,
.download-button {
padding: 0.75rem 1.5rem;
background: var(--primary-color);
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
transition: background-color 0.3s ease;
}
.download-button {
width: 100%;
}
.search-container button:hover,
.download-button:hover {
background: var(--primary-dark);
}
/* Table Styles */
.table-responsive {
margin-bottom: 1rem;
@@ -214,7 +187,7 @@ tbody tr:hover {
.details-container {
background: white;
padding: 2rem;
padding: 0.25rem 0.25rem 2rem 3rem;
border-radius: 4px;
max-width: 800px;
width: 90%;
+28 -20
View File
@@ -47,11 +47,15 @@ document.addEventListener('DOMContentLoaded', () => {
},
showLoading(element) {
element.style.display = 'block';
element.removeAttribute('hidden');
},
hideLoading(element) {
element.style.display = 'none';
element.setAttribute('hidden', '');
},
showAccordion(element) {
UIkit.accordion(element).toggle(1, true);
},
async fetchJson(url, options = {}) {
@@ -90,9 +94,9 @@ document.addEventListener('DOMContentLoaded', () => {
try {
STATE.isSearching = true;
elements.resultsSection.classList.remove('collapsed');
utils.showLoading(elements.searchLoading);
utils.showAccordion(elements.resultsSection);
const data = await utils.fetchJson(
`${API_ENDPOINTS.search}?query=${encodeURIComponent(query)}`
);
@@ -161,12 +165,12 @@ document.addEventListener('DOMContentLoaded', () => {
createActionCell(book) {
const buttonDetails = utils.createElement('button', {
className: 'details-button',
className: 'uk-button uk-button-default uk-align-center uk-margin-small',
onclick: () => bookDetails.show(book.id)
}, [utils.createElement('span', { textContent: 'Details' })]);
const downloadButton = utils.createElement('button', {
className: 'download-button',
className: 'uk-button uk-button-primary uk-align-center uk-margin-small',
onclick: () => bookDetails.downloadBook(book)
}, [utils.createElement('span', { textContent: 'Download' })]);
@@ -221,9 +225,13 @@ document.addEventListener('DOMContentLoaded', () => {
generateDetailsHTML(book) {
return `
<div class="details-header">
<img src="${book.preview || ''}" alt="Book Preview">
<div class="details-info">
<div class="uk-card uk-card-default uk-child-width-1-2" uk-grid>
<div class="uk-card-media-left uk-cover-container uk-padding">
<img class="uk-height-medium" src="${book.preview || ''}" alt="Book Preview" uk-cover>
<canvas width="299" height="461"></canvas>
</div>
<div class="uk-card-body">
<h3>${book.title || 'No title available'}</h3>
<p><strong>Author:</strong> ${book.author || 'N/A'}</p>
<p><strong>Publisher:</strong> ${book.publisher || 'N/A'}</p>
@@ -232,12 +240,18 @@ document.addEventListener('DOMContentLoaded', () => {
<p><strong>Format:</strong> ${book.format || 'N/A'}</p>
<p><strong>Size:</strong> ${book.size || 'N/A'}</p>
</div>
<button id="download-button" class="uk-button uk-button-primary" type="button">Download</button>
<button id="close-details" class="uk-button uk-button-default uk-modal-close" type="button">Close</button>
</div>
${this.generateInfoList(book.info)}
<div class="details-actions">
<button id="download-button">Download</button>
<button id="close-details">Close</button>
</div>
<ul uk-accordion>
<li>
<a class="uk-accordion-title" href>Further Information</a>
<div class="uk-accordion-content">
${this.generateInfoList(book.info)}
</div>
</li>
</ul>
`;
},
@@ -250,7 +264,7 @@ document.addEventListener('DOMContentLoaded', () => {
`)
.join('');
return `<ul class="details-info-list">${listItems}</ul>`;
return `<ul class="uk-list uk-list-bullet">${listItems}</ul>`;
},
async downloadBook(book) {
@@ -263,7 +277,6 @@ document.addEventListener('DOMContentLoaded', () => {
);
modal.close();
elements.resultsSection.classList.add('collapsed');
status.fetch();
} catch (error) {
console.error('Download error:', error);
@@ -405,11 +418,6 @@ document.addEventListener('DOMContentLoaded', () => {
}
});
// Results section toggle
elements.resultsHeading.addEventListener('click', () => {
elements.resultsSection.classList.toggle('collapsed');
});
// Modal close on overlay click
elements.modalOverlay.addEventListener('click', (e) => {
if (e.target === elements.modalOverlay) {
+88 -77
View File
@@ -9,97 +9,108 @@
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
<!-- Add favicon -->
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}">
<!-- UIkit CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/uikit@3.21.16/dist/css/uikit.min.css" />
<!-- UIkit JS -->
<script src="https://cdn.jsdelivr.net/npm/uikit@3.21.16/dist/js/uikit.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/uikit@3.21.16/dist/js/uikit-icons.min.js"></script>
</head>
<body>
<header>
<h1>Book Search & Download</h1>
<h1 class="uk-heading-medium" style="color: white;">Book Search & Download</h1>
</header>
<main>
<!-- Search Section -->
<section class="search-section">
<div class="search-container">
<input
type="text"
id="search-input"
placeholder="Search by ISBN, title, author..."
aria-label="Search books"
>
<button id="search-button" aria-label="Search">
<span>Search</span>
</button>
</div>
</section>
<div class="uk-container">
<!-- Search Section -->
<section class="uk-section-xxsmall uk-padding">
<div class="uk-container uk-width-expand">
<form class="uk-search uk-search-default uk-width-expand">
<input class="uk-search-input" type="search" placeholder="Search by ISBN, title, author..." aria-label="Search books" id="search-input">
<button class="uk-search-icon-flip" uk-search-icon id="search-button" type="button"></button>
</form>
</div>
</section>
<!-- Results Section -->
<section class="results-section collapsed" id="results-section">
<h2 id="results-heading">
Search Results
<span class="toggle-icon">▼</span>
</h2>
<div class="loading-indicator" id="search-loading" role="status">
<span class="spinner"></span>
<span>Loading...</span>
</div>
<div class="results-content">
<div class="table-responsive">
<table id="results-table" role="grid">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Preview</th>
<th scope="col">Title</th>
<th scope="col">Author</th>
<th scope="col">Publisher</th>
<th scope="col">Year</th>
<th scope="col">Language</th>
<th scope="col">Format</th>
<th scope="col">Size</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
<!-- Search results will be injected here -->
</tbody>
</table>
<!-- Results Section -->
<ul uk-accordion id="results-section">
<li id="search-accordion">
<a class="uk-accordion-title" href><h1 class="uk-heading-xsmall">Search Results</h1></a>
<div class="uk-accordion-content">
<div id="search-loading" class="uk-flex uk-flex-center" role="status" hidden>
<div uk-spinner="ratio: 2"></div>
<span>Loading...</span>
</div>
<div class="results-content">
<div class="uk-overflow-auto">
<table id="results-table" class="uk-table uk-table-hover uk-table-divider" role="grid">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Preview</th>
<th scope="col">Title</th>
<th scope="col">Author</th>
<th scope="col">Publisher</th>
<th scope="col">Year</th>
<th scope="col">Language</th>
<th scope="col">Format</th>
<th scope="col">Size</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
<!-- Search results will be injected here -->
</tbody>
</table>
</div>
</div>
</div>
</li>
</ul>
<!-- Book Details Modal -->
<div class="modal-overlay" id="modal-overlay" role="dialog" aria-modal="true">
<div class="details-container" id="details-container">
<!-- Details will be dynamically injected here -->
</div>
</div>
</section>
<!-- Book Details Modal -->
<div class="modal-overlay" id="modal-overlay" role="dialog" aria-modal="true">
<div class="details-container" id="details-container">
<!-- Details will be dynamically injected here -->
</div>
<!-- Status Section -->
<ul uk-accordion id="status-section">
<li id="search-accordion">
<a class="uk-accordion-title" href><h1 class="uk-heading-xsmall">Download Status</h1></a>
<div class="uk-accordion-content">
<div id="status-loading" class="uk-flex uk-flex-center" role="status" hidden>
<div uk-spinner="ratio: 2"></div>
<span>Loading...</span>
</div>
<div class="status-content">
<div class="uk-overflow-auto">
<table id="status-table" class="uk-table uk-table-hover uk-table-divider" role="grid">
<thead>
<tr>
<th scope="col">Status</th>
<th scope="col">Book ID</th>
<th scope="col">Title</th>
<th scope="col">Preview</th>
</tr>
</thead>
<tbody>
<!-- Search results will be injected here -->
</tbody>
</table>
</div>
</div>
</div>
</li>
</ul>
</div>
<!-- Status Section -->
<section class="status-section">
<h2>Download Status</h2>
<div class="loading-indicator" id="status-loading" role="status">
<span class="spinner"></span>
<span>Loading...</span>
</div>
<div class="table-responsive">
<table id="status-table" role="grid">
<thead>
<tr>
<th scope="col">Status</th>
<th scope="col">Book ID</th>
<th scope="col">Title</th>
<th scope="col">Preview</th>
</tr>
</thead>
<tbody>
<!-- Status information will be injected here -->
</tbody>
</table>
</div>
</section>
</main>
<footer>
<p>Calibre Web Book Downloader.</p>
<h1 class="uk-heading-xxsmall" style="color: white;">Calibre Web Book Downloader</h1>
<a href="https://github.com/calibrain/calibre-web-automated-book-downloader" class="uk-icon-link" uk-icon="icon:github; ratio: 2"></a>
</footer>
<!-- Scripts -->