mirror of
https://github.com/th3r00t/pyShelf.git
synced 2026-04-28 01:59:35 -04:00
Working pagination and download of book files
This commit is contained in:
13
src/backend/lib/storage.py
vendored
13
src/backend/lib/storage.py
vendored
@@ -244,3 +244,16 @@ class Storage:
|
||||
_result = session.execute(select(Collection).join(Book)).all()
|
||||
session.close()
|
||||
return _result
|
||||
|
||||
def get_collection(self, name):
|
||||
"""Get collection from database.
|
||||
|
||||
Returns
|
||||
-------
|
||||
_result : ScalarResult Object
|
||||
"""
|
||||
session = Session(self.engine)
|
||||
breakpoint()
|
||||
_result = session.execute(select(Collection).where(Collection.name == name).join(Book)).all()
|
||||
session.close()
|
||||
return _result
|
||||
|
||||
2
src/frontend/compile.sh
vendored
Executable file
2
src/frontend/compile.sh
vendored
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
exec npx tsc static/script/pyshelf.ts
|
||||
33
src/frontend/lib/FastAPIServer.py
vendored
33
src/frontend/lib/FastAPIServer.py
vendored
@@ -8,7 +8,7 @@ import datetime
|
||||
from json import dumps
|
||||
from base64 import b64encode
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.responses import HTMLResponse, JSONResponse
|
||||
from fastapi.responses import HTMLResponse, JSONResponse, FileResponse
|
||||
from fastapi.routing import APIRoute
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.templating import Jinja2Templates
|
||||
@@ -85,7 +85,7 @@ def books_tojson(obj) -> dumps:
|
||||
def book_tojson(book) -> dumps:
|
||||
"""Convert a book object to a json."""
|
||||
return dumps({
|
||||
"book_id": book[0].book_id,
|
||||
"book_id": book[0].id,
|
||||
"title": book[0].title,
|
||||
"author": book[0].author,
|
||||
"categories": book[0].categories,
|
||||
@@ -147,7 +147,12 @@ class FastAPIServer():
|
||||
_pyShelf_src = sass.compile(
|
||||
filename='src/frontend/static/styles/pyShelf.sass',
|
||||
source_map_filename='src/frontend/static/styles/pyShelf.sass',
|
||||
output_style='compressed')
|
||||
output_style='compressed',
|
||||
include_paths=[
|
||||
'node_modules',
|
||||
'src/frontend/static/styles'
|
||||
]
|
||||
)
|
||||
with open('src/frontend/static/styles/pyShelf.css', 'w') as _pyShelf:
|
||||
_pyShelf.write(_pyShelf_src[0])
|
||||
|
||||
@@ -161,12 +166,12 @@ class FastAPIServer():
|
||||
route.operation_id = route.name
|
||||
|
||||
@app.get("/", response_class=HTMLResponse)
|
||||
async def index(request: Request, skip: int = 0, limit: int = 10):
|
||||
async def index(request: Request, skip: int = 0, limit: int = 30):
|
||||
storage = Storage(Config(os.path.abspath(os.getcwd())))
|
||||
books = storage.get_books(collection=None, skip=skip, limit=limit)
|
||||
books = storage.get_books(collection=None, skip=skip*limit, limit=limit)
|
||||
collections = storage.get_collections()
|
||||
"""Home page responder."""
|
||||
context = {"request": request, "books": books, "collections": collections}
|
||||
context = {"request": request, "books": books, "collections": collections, "page": skip, "limit": limit}
|
||||
return templates.TemplateResponse("index.html", context)
|
||||
|
||||
@app.get("/api/books", response_class=JSONResponse)
|
||||
@@ -184,6 +189,16 @@ class FastAPIServer():
|
||||
book = storage.get_book(book_id)
|
||||
"""Home page responder."""
|
||||
return JSONResponse(content=book_tojson(book))
|
||||
|
||||
@app.get("/api/get_book/{book_id}", response_class=FileResponse)
|
||||
async def book(request: Request, book_id: int):
|
||||
storage = Storage(Config(os.path.abspath(os.getcwd())))
|
||||
book = storage.get_book(book_id)
|
||||
file_path = book[0].file_name
|
||||
if not os.path.exists(file_path):
|
||||
return JSONResponse(status_code=404, content={"error": "File not found"})
|
||||
"""Book file responder."""
|
||||
return FileResponse(path=file_path, filename=os.path.basename(file_path), media_type="application/octet-stream")
|
||||
|
||||
@app.get("/api/collections", response_class=JSONResponse)
|
||||
async def collections(request: Request):
|
||||
@@ -192,6 +207,12 @@ class FastAPIServer():
|
||||
"""Home page responder."""
|
||||
return JSONResponse(content=collections_tojson(collections))
|
||||
|
||||
@app.get("/api/collection/{collection_id}", response_class=JSONResponse)
|
||||
async def collection(request: Request, collection_name: str):
|
||||
storage = Storage(Config(os.path.abspath(os.getcwd())))
|
||||
collection = storage.get_collection(collection_name)
|
||||
"""Collection file responder."""
|
||||
return JSONResponse(content=collections_tojson(collection))
|
||||
|
||||
async def run(self):
|
||||
"""Front end server entrypoint."""
|
||||
|
||||
124
src/frontend/static/styles/pyShelf.sass
vendored
124
src/frontend/static/styles/pyShelf.sass
vendored
@@ -24,72 +24,8 @@ $navbar-item-color: $white
|
||||
$navbar-item-hover-background-color: $ps-color-secondary
|
||||
$navbar-dropdown-background-color: $ps-color-primary-trans
|
||||
$navbar-dropdown-item-hover-color: $ps-color-background !important
|
||||
$footer-background-color: $ps-color-primary-trans
|
||||
$footer-padding: 0.5rem 0.5rem
|
||||
//$navbar-dropdown-item-hover-background-color: $background !default
|
||||
//$navbar-dropdown-item-active-color: $link !default
|
||||
//$navbar-dropdown-item-active-background-color: $background !default
|
||||
@import "../../node_modules/bulma/sass/utilities/_all.sass";
|
||||
@import "../../node_modules/bulma/sass/utilities/initial-variables.sass";
|
||||
@import "../../node_modules/bulma/sass/utilities/functions.sass";
|
||||
@import "../../node_modules/bulma/sass/utilities/derived-variables.sass";
|
||||
@import "../../node_modules/bulma/sass/utilities/mixins.sass";
|
||||
@import "../../node_modules/bulma/sass/utilities/controls.sass";
|
||||
@import "../../node_modules/bulma/sass/utilities/extends.sass";
|
||||
@import "../../node_modules/bulma/sass/base/_all.sass";
|
||||
@import "../../node_modules/bulma/sass/base/minireset.sass";
|
||||
@import "../../node_modules/bulma/sass/base/generic.sass";
|
||||
@import "../../node_modules/bulma/sass/base/animations.sass";
|
||||
@import "../../node_modules/bulma/sass/elements/_all.sass";
|
||||
@import "../../node_modules/bulma/sass/elements/box.sass";
|
||||
@import "../../node_modules/bulma/sass/elements/button.sass";
|
||||
@import "../../node_modules/bulma/sass/elements/container.sass";
|
||||
@import "../../node_modules/bulma/sass/elements/content.sass";
|
||||
@import "../../node_modules/bulma/sass/elements/icon.sass";
|
||||
@import "../../node_modules/bulma/sass/elements/image.sass";
|
||||
@import "../../node_modules/bulma/sass/elements/notification.sass";
|
||||
@import "../../node_modules/bulma/sass/elements/progress.sass";
|
||||
@import "../../node_modules/bulma/sass/elements/table.sass";
|
||||
@import "../../node_modules/bulma/sass/elements/tag.sass";
|
||||
@import "../../node_modules/bulma/sass/elements/title.sass";
|
||||
@import "../../node_modules/bulma/sass/elements/other.sass";
|
||||
@import "../../node_modules/bulma/sass/form/_all.sass";
|
||||
@import "../../node_modules/bulma/sass/form/shared.sass";
|
||||
@import "../../node_modules/bulma/sass/form/input-textarea.sass";
|
||||
@import "../../node_modules/bulma/sass/form/checkbox-radio.sass";
|
||||
@import "../../node_modules/bulma/sass/form/select.sass";
|
||||
@import "../../node_modules/bulma/sass/form/file.sass";
|
||||
@import "../../node_modules/bulma/sass/form/tools.sass";
|
||||
@import "../../node_modules/bulma/sass/components/_all.sass";
|
||||
@import "../../node_modules/bulma/sass/components/breadcrumb.sass";
|
||||
@import "../../node_modules/bulma/sass/components/card.sass";
|
||||
@import "../../node_modules/bulma/sass/components/dropdown.sass";
|
||||
@import "../../node_modules/bulma/sass/components/level.sass";
|
||||
@import "../../node_modules/bulma/sass/components/media.sass";
|
||||
@import "../../node_modules/bulma/sass/components/menu.sass";
|
||||
@import "../../node_modules/bulma/sass/components/message.sass";
|
||||
@import "../../node_modules/bulma/sass/components/modal.sass";
|
||||
@import "../../node_modules/bulma/sass/components/navbar.sass";
|
||||
@import "../../node_modules/bulma/sass/components/pagination.sass";
|
||||
@import "../../node_modules/bulma/sass/components/panel.sass";
|
||||
@import "../../node_modules/bulma/sass/components/tabs.sass";
|
||||
@import "../../node_modules/bulma/sass/grid/_all.sass";
|
||||
@import "../../node_modules/bulma/sass/grid/columns.sass";
|
||||
@import "../../node_modules/bulma/sass/grid/tiles.sass";
|
||||
@import "../../node_modules/bulma/sass/helpers/_all.sass";
|
||||
@import "../../node_modules/bulma/sass/helpers/color.sass";
|
||||
@import "../../node_modules/bulma/sass/helpers/flexbox.sass";
|
||||
@import "../../node_modules/bulma/sass/helpers/float.sass";
|
||||
@import "../../node_modules/bulma/sass/helpers/other.sass";
|
||||
@import "../../node_modules/bulma/sass/helpers/overflow.sass";
|
||||
@import "../../node_modules/bulma/sass/helpers/position.sass";
|
||||
@import "../../node_modules/bulma/sass/helpers/spacing.sass";
|
||||
@import "../../node_modules/bulma/sass/helpers/typography.sass";
|
||||
@import "../../node_modules/bulma/sass/helpers/visibility.sass";
|
||||
@import "../../node_modules/bulma/sass/layout/_all.sass";
|
||||
@import "../../node_modules/bulma/sass/layout/hero.sass";
|
||||
@import "../../node_modules/bulma/sass/layout/section.sass";
|
||||
@import "../../node_modules/bulma/sass/layout/footer.sass"
|
||||
$footer-background-color: $ps-color-primary-trans !important
|
||||
// $footer-padding: 0.5rem 0.5rem
|
||||
|
||||
.center-all
|
||||
align-items: center;
|
||||
@@ -110,12 +46,60 @@ $footer-padding: 0.5rem 0.5rem
|
||||
|
||||
#book-shelf
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)) // <== add this
|
||||
gap: 1rem // space between rows and columns
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
// align-items: center;
|
||||
align-content: center;
|
||||
padding: 1rem;
|
||||
margin: 1rem;
|
||||
// background-color: $ps-color-background;
|
||||
|
||||
.book-thumbnail
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
|
||||
.no-image-title
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: $white;
|
||||
font-size: 1.2rem;
|
||||
text-align: center;
|
||||
background-color: rgba(0,0,0,0.7);
|
||||
padding: 0.5rem;
|
||||
border-radius: 5px;
|
||||
|
||||
.no-image-author
|
||||
position: absolute;
|
||||
top: 60%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: $white;
|
||||
font-size: 1rem;
|
||||
text-align: center;
|
||||
background-color: rgba(0,0,0,0.7);
|
||||
padding: 0.5rem;
|
||||
border-radius: 5px;
|
||||
|
||||
.display-alt::after
|
||||
content: attr(alt);
|
||||
display: block !important;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: rgba(0,0,0,0.7);
|
||||
color: white;
|
||||
font-size: 0.8rem;
|
||||
padding: 0.2rem;
|
||||
|
||||
.title, .subtitle
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
top: -155px;
|
||||
|
||||
.collection
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -123,3 +107,9 @@ $footer-padding: 0.5rem 0.5rem
|
||||
align-items: center;
|
||||
align-content: center;
|
||||
background-color: $ps-color-background;
|
||||
|
||||
.footer
|
||||
display: flex;
|
||||
padding: 1rem !important;
|
||||
|
||||
@import "../../node_modules/bulma/bulma.scss"
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
<footer class="footer is-dark" id="footer-main">
|
||||
<a href="https://python.org">
|
||||
<img
|
||||
src="{{ url_for('static', path='images/python-logo-transparent.svg') }}"
|
||||
alt="Powered by Python"
|
||||
width="128"
|
||||
height="24">
|
||||
</a>
|
||||
<a href="https://bulma.io">
|
||||
<img
|
||||
src="https://bulma.io/images/made-with-bulma--white.png"
|
||||
alt="Made with Bulma"
|
||||
width="128"
|
||||
height="24">
|
||||
</a>
|
||||
<a href="https://python.org">
|
||||
<img
|
||||
src="{{ url_for('static', path='images/python-logo-transparent.svg') }}"
|
||||
alt="Powered by Python"
|
||||
width="128"
|
||||
height="24">
|
||||
</a>
|
||||
<a href="https://bulma.io">
|
||||
<img
|
||||
src="https://bulma.io/assets/images/made-with-bulma--dark.png"
|
||||
alt="Made with Bulma"
|
||||
width="128"
|
||||
height="24">
|
||||
</a>
|
||||
</footer>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,40 +1,53 @@
|
||||
<!doctype html>
|
||||
<!DOCTYPE html>
|
||||
{% block javascript %}
|
||||
<script type="text/javascript" src=static/script/pako.min.js>
|
||||
<script type="text/javascript">
|
||||
const books = {{ books|books_tojson }};
|
||||
let inflatedJSON = {};
|
||||
const pako = require('pako');
|
||||
inflatedJSON = JSON.parse(pako.inflate(books, { to: 'string'}));
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% include 'header.html' %}
|
||||
{% include 'navigation.html' %}
|
||||
<section id="master-container">
|
||||
<div id="book-shelf" class="container is-dark">
|
||||
{% for book in books %}
|
||||
{% set cover = book[0].cover|b64decode %}
|
||||
{% if cover != 'None' %}
|
||||
<div class="is-dark book" id="{{book[0].id}}">
|
||||
<div class="image book-thumbnail">
|
||||
<figure class="image is-4by3">
|
||||
<img src="data:;base64,{{ book[0].cover|b64decode }}" alt="{{ book[0].title }}">
|
||||
</figure>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="is-dark book" id="{{book[0].id}}">
|
||||
<div class="image book-thumbnail">
|
||||
<figure class="image is-4by3">
|
||||
<img src="static/images/no-cover.jpg" alt="{{ book[0].title }}">
|
||||
</figure>
|
||||
</div>
|
||||
<h3 class="title is-3">{{ book[0].title }}</h3>
|
||||
<h4 class="subtitle is-4">{{ book[0].author }}</h4>
|
||||
<p class="content">{{ book[0].description|summarize }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</section>
|
||||
{% include 'footer.html' %}
|
||||
<script type="text/javascript">
|
||||
const books = {{ books|books_tojson }};
|
||||
let inflatedJSON = {};
|
||||
const pako = require('pako');
|
||||
inflatedJSON = JSON.parse(pako.inflate(books, { to: 'string'}));
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% include 'header.html' %}
|
||||
{% include 'navigation.html' %}
|
||||
<section id="master-container">
|
||||
<p>Total books: {{ books|length }}</p>
|
||||
<!-- <div id="book-shelf" class="container is-dark"> -->
|
||||
<div id="book-shelf" class="is-dark">
|
||||
{% for book in books %}
|
||||
{% set cover = book[0].cover|b64decode %}
|
||||
{% if cover != 'None' %}
|
||||
<div class="is-dark book" id="{{book[0].id}}" onclick="window.location.href='/api/get_book/{{ book[0].id }}'">
|
||||
<div class="image book-thumbnail">
|
||||
<figure class="image is-4by3">
|
||||
<img src="data:;base64,{{ book[0].cover|b64decode }}" alt="{{ book[0].title }}">
|
||||
</figure>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="is-dark book" id="{{book[0].id}}" onclick="window.location.href='/api/get_book/{{ book[0].id }}'">
|
||||
<div class="image book-thumbnail"
|
||||
style="
|
||||
background-image: url('static/images/no-cover.jpg');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
">
|
||||
<figure class="image is-4by3">
|
||||
<div class="no-image-title">{{ book[0].title }}</div>
|
||||
<!-- alt="{{ book[0].title }}" -->
|
||||
</figure>
|
||||
</div>
|
||||
<!-- <p class="content">{{ book[0].description|summarize }}</p> -->
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div id="pagination" class="container is-dark">
|
||||
<nav class="pagination is-centered" role="navigation" aria-label="pagination">
|
||||
<a class="pagination-previous" href="/?skip={{ page-1 }}" id="prev-page">Previous</a>
|
||||
<a class="pagination-next" href="/?skip={{ page+1 }}" id="next-page">Next</a>
|
||||
</nav>
|
||||
</div>
|
||||
</section>
|
||||
{% include 'footer.html' %}
|
||||
|
||||
@@ -49,7 +49,11 @@
|
||||
<div class="select is-small is-rounded is-link" id="collection_dropdown">
|
||||
<select id="collection_select">
|
||||
{% for collection in collections %}
|
||||
<option value={{collection[0].id}} class="collection_selection">{{collection[0].collection}}</option>
|
||||
<option
|
||||
value={{collection[0].id}}
|
||||
class="collection_selection"
|
||||
onclick="window.location.href='/api/collection/{{ collection[0].collection }}'"
|
||||
>{{collection[0].collection}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user