diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/library/__init__.py | 0 | ||||
-rw-r--r-- | src/library/command.py | 21 | ||||
-rw-r--r-- | src/main.py | 56 | ||||
-rw-r--r-- | src/view/__init__.py | 0 | ||||
-rw-r--r-- | src/view/client/book.ts (renamed from src/book.ts) | 0 | ||||
-rw-r--r-- | src/view/client/lib/functions.ts (renamed from src/lib/functions.ts) | 0 | ||||
-rw-r--r-- | src/view/client/lib/i18n.ts (renamed from src/lib/i18n.ts) | 0 | ||||
-rw-r--r-- | src/view/client/lib/rx.ts (renamed from src/lib/rx.ts) | 0 | ||||
-rw-r--r-- | src/view/client/lib/search.ts (renamed from src/lib/search.ts) | 0 | ||||
-rw-r--r-- | src/view/client/main.ts (renamed from src/main.ts) | 0 | ||||
-rw-r--r-- | src/view/client/view/books.ts (renamed from src/view/books.ts) | 1 | ||||
-rw-r--r-- | src/view/client/view/components/modal.ts (renamed from src/view/components/modal.ts) | 0 | ||||
-rw-r--r-- | src/view/client/view/filters.ts (renamed from src/view/filters.ts) | 0 | ||||
-rw-r--r-- | src/view/command.py | 17 | ||||
-rw-r--r-- | src/view/public/index.html | 7 | ||||
-rw-r--r-- | src/view/public/main.css | 235 | ||||
-rw-r--r-- | src/view/tsconfig.json | 13 |
17 files changed, 350 insertions, 0 deletions
diff --git a/src/library/__init__.py b/src/library/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/library/__init__.py diff --git a/src/library/command.py b/src/library/command.py new file mode 100644 index 0000000..6b8577e --- /dev/null +++ b/src/library/command.py @@ -0,0 +1,21 @@ +import glob +import json +import os +import tomllib + +def run(book_library): + print(get(book_library)) + +def get(book_library): + metadatas = [] + + for path in glob.glob(f'{book_library}/**/metadata.toml', recursive=True): + with open(path, 'rb') as f: + directory = os.path.dirname(os.path.realpath(path)) + metadata = tomllib.load(f) + for p in glob.glob(f'{directory}/cover.*'): + metadata['cover'] = p + break + metadatas.append(metadata) + + return json.dumps(metadatas) diff --git a/src/main.py b/src/main.py new file mode 100644 index 0000000..618cc5a --- /dev/null +++ b/src/main.py @@ -0,0 +1,56 @@ +# Manage book library. +# +# Required dependencies: +# +# - python >= 3.11 +# - requests +# - pillow +# - ebook-convert CLI (from calibre) + +import sys +import os + +import library.command +import view.command + +def print_help(title='Manage book library'): + print(f"""{title} + +- Print library metadata as json: + + $ python {sys.argv[0]} library + +- View books in web page: + + $ python {sys.argv[0]} view browser-cmd + +Environment variables: + + BOOK_LIBRARY: path to book library.""") + +def get_book_library(): + path = os.getenv('BOOK_LIBRARY') + if path is None or not os.path.isdir(path): + print_help(title='BOOK_LIBRARY environment variable is required.') + exit(1) + else: + return path + +def main(): + match sys.argv: + case [ _, 'library' ]: + book_library = get_book_library() + library.command.run(book_library) + case [ _, 'view', browser_cmd ]: + book_library = get_book_library() + view.command.run(book_library, browser_cmd) + case [ _, '--help' ]: + print_help() + case [ _, '-h' ]: + print_help() + case _: + print_help('Command not found.') + exit(1) + +if __name__ == "__main__": + main() diff --git a/src/view/__init__.py b/src/view/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/view/__init__.py diff --git a/src/book.ts b/src/view/client/book.ts index 680cc11..680cc11 100644 --- a/src/book.ts +++ b/src/view/client/book.ts diff --git a/src/lib/functions.ts b/src/view/client/lib/functions.ts index 21fdad9..21fdad9 100644 --- a/src/lib/functions.ts +++ b/src/view/client/lib/functions.ts diff --git a/src/lib/i18n.ts b/src/view/client/lib/i18n.ts index 3716367..3716367 100644 --- a/src/lib/i18n.ts +++ b/src/view/client/lib/i18n.ts diff --git a/src/lib/rx.ts b/src/view/client/lib/rx.ts index bf01b6d..bf01b6d 100644 --- a/src/lib/rx.ts +++ b/src/view/client/lib/rx.ts diff --git a/src/lib/search.ts b/src/view/client/lib/search.ts index 026cb94..026cb94 100644 --- a/src/lib/search.ts +++ b/src/view/client/lib/search.ts diff --git a/src/main.ts b/src/view/client/main.ts index 5885871..5885871 100644 --- a/src/main.ts +++ b/src/view/client/main.ts diff --git a/src/view/books.ts b/src/view/client/view/books.ts index 5ec019a..aba55c1 100644 --- a/src/view/books.ts +++ b/src/view/client/view/books.ts @@ -66,6 +66,7 @@ function viewBook({ book, onSelect }: ViewBookParams): Html { { className: 'g-Book' }, h('img', { src: book.cover, + alt: book.title, className: 'g-Book__Image', onclick: () => onSelect(book) } diff --git a/src/view/components/modal.ts b/src/view/client/view/components/modal.ts index 5e845e1..5e845e1 100644 --- a/src/view/components/modal.ts +++ b/src/view/client/view/components/modal.ts diff --git a/src/view/filters.ts b/src/view/client/view/filters.ts index efe4115..efe4115 100644 --- a/src/view/filters.ts +++ b/src/view/client/view/filters.ts diff --git a/src/view/command.py b/src/view/command.py new file mode 100644 index 0000000..e50027f --- /dev/null +++ b/src/view/command.py @@ -0,0 +1,17 @@ +import os +import shutil +import subprocess +import tempfile +import time + +import library.command + +def run(book_library, browser_cmd): + tmp_dir = tempfile.mkdtemp() + directory = os.path.dirname(os.path.realpath(__file__)) + shutil.copytree(f'{directory}/public', tmp_dir, dirs_exist_ok=True) + with open(f'{tmp_dir}/books.js', 'w') as f: + json = library.command.get(book_library) + f.write(f'const bookLibrary = {json}') + browser_cmd = f'{browser_cmd} {tmp_dir}/index.html' + subprocess.run(browser_cmd.split(' ')) diff --git a/src/view/public/index.html b/src/view/public/index.html new file mode 100644 index 0000000..ce4d568 --- /dev/null +++ b/src/view/public/index.html @@ -0,0 +1,7 @@ +<!DOCTYPE html> +<meta charset="UTF-8"> +<link rel="stylesheet" href="main.css"> +<title>Bibliothèque</title> +<body></body> +<script src="books.js"></script> +<script src="main.js"></script> diff --git a/src/view/public/main.css b/src/view/public/main.css new file mode 100644 index 0000000..f361cbe --- /dev/null +++ b/src/view/public/main.css @@ -0,0 +1,235 @@ +/* Variables */ + +:root { + --color-focus: #888833; + + --font-size-dog: 1.5rem; + + --spacing-mouse: 0.25rem; + --spacing-cat: 0.5rem; + --spacing-dog: 1rem; + --spacing-horse: 2rem; + + --width-close-button: 3rem; + --width-book: 13rem; +} + +/* Top level */ + +html { + height: 100%; +} + +body { + margin: 0; + display: flex; + height: 100%; + font-family: sans-serif; +} + +dl { + margin: 0; +} + +dd { + margin-left: 0; +} + +p { + margin: 0; +} + +/* Modal */ + +.g-Modal { + width: 100vw; + height: 100vh; + position: fixed; + top: 0; + left: 0; + display: flex; + justify-content: center; + align-items: center; + background-color: rgba(0, 0, 0, 0.5); + z-index: 1; +} + +.g-Modal__Content { + position: relative; + background-color: white; + width: 50%; + border-radius: 0.2rem; + border: 1px solid #EEE; +} + +.g-Modal__Header { + display: flex; + justify-content: space-between; + align-items: center; + padding: var(--spacing-dog) var(--spacing-horse); + border-bottom: 1px solid #EEE; +} + +.g-Modal__Body { + padding: var(--spacing-horse); + max-height: 50vh; + overflow-y: scroll; +} + +.g-Modal__Close { + cursor: pointer; + font-weight: bold; + border-radius: 50%; + border: 1px solid #EEE; + background-color: transparent; + width: var(--width-close-button); + height: var(--width-close-button); + font-size: 1.7rem; + flex-shrink: 0; +} + +.g-Modal__Close:hover, .g-Modal__Close:focus { + background-color: #EEE; +} + +/* Filters */ + +aside { + background-color: #333; + color: white; + width: 200px; + overflow-y: auto; +} + +ul { + margin: 0; + padding: 0; + list-style-type: none; +} + +.g-FilterTitle { + padding: 0.5rem 1rem; + background-color: #555; + border-left: 8px solid transparent; + font-weight: bold; +} + +.g-Filter--Unselected { + border-left: 8px solid #555; +} + +.g-Filter--Selected { + border-left: 8px solid #883333; +} + +.g-Filter button { + border: none; + background-color: transparent; + color: inherit; + cursor: pointer; + padding: 0.5rem 1rem; + width: 100%; + text-align: left; +} + +.g-Filter button:hover { + background-color: var(--color-focus); +} + +/* Books */ + +main { + width: 100%; + padding: 1rem; + overflow-y: auto; +} + +header { + display: flex; + font-size: 120%; + margin-bottom: 1rem; +} + +.g-Search { + margin-right: 1rem; +} + +.g-Books { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(var(--width-book), 1fr)); + grid-gap: 1rem; +} + +.g-Book { + align-self: center; + border: 1px solid #DDDDDD; + padding: 0; + width: fit-content; +} + +.g-Book:hover { + cursor: pointer; + outline: none; +} + +.g-Book:hover .g-Book__Image { + transform: scale(105%); + opacity: 0.9; +} + +.g-Book__Image { + display: flex; + width: var(--width-book); + transition: all 0.2s ease-in-out; +} + +/* Book detail */ + +.g-BookDetail { + display: flex; + align-items: flex-start; +} + +.g-BookDetail img { + width: var(--width-book); + margin-right: var(--spacing-horse); + border: 1px solid #EEE; +} + +.g-BookDetail__Title { + font-size: var(--font-size-dog); + margin-bottom: var(--spacing-mouse); +} + +.g-BookDetail__Subtitle { + font-style: italic; + margin-bottom: var(--spacing-mouse); +} + +.g-BookDetail dl { + display: flex; + flex-direction: column; + gap: var(--spacing-cat); + margin-bottom: var(--spacing-dog); +} + +.g-BookDetail dt { + display: inline; + text-decoration: underline; +} + +.g-BookDetail dd { + display: inline; +} + +.g-BookDetail p { + font-family: serif; + text-align: justify; + line-height: 1.4rem; + font-size: 1.1rem; + text-indent: 1.5rem; +} + +.g-BookDetail p:not(:last-child) { + margin-bottom: var(--spacing-dog); +} diff --git a/src/view/tsconfig.json b/src/view/tsconfig.json new file mode 100644 index 0000000..1e07c37 --- /dev/null +++ b/src/view/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "module": "amd", + "target": "es2020", + "baseUrl": "client", + "outFile": "public/main.js", + "noImplicitAny": true, + "strictNullChecks": true, + "removeComments": true, + "preserveConstEnums": true + }, + "include": ["client/**/*"] +} |