From a110c200e86d2325af07167531fac0f61d9681a0 Mon Sep 17 00:00:00 2001 From: Joris Guyonvarch Date: Fri, 26 Dec 2025 18:41:26 +0100 Subject: Switch to GUI to manage the library Allow to regroup the CLI and the view into one unique tool. --- library/client/view/books.ts | 116 -------------------------------- library/client/view/components/modal.ts | 38 ----------- library/client/view/filters.ts | 90 ------------------------- 3 files changed, 244 deletions(-) delete mode 100644 library/client/view/books.ts delete mode 100644 library/client/view/components/modal.ts delete mode 100644 library/client/view/filters.ts (limited to 'library/client/view') diff --git a/library/client/view/books.ts b/library/client/view/books.ts deleted file mode 100644 index aba55c1..0000000 --- a/library/client/view/books.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { h, withVar, mount, Html, Rx } from 'lib/rx' -import * as Book from 'book' -import * as Modal from 'view/components/modal' - -export function view(books: Rx>): Html { - return h('div', - { className: 'g-Books' }, - withVar(undefined, (focusBook, updateFocusBook) => [ - books.map(bs => [ - focusBook.map(book => { - if (book !== undefined) { - let onKeyup = keyupHandler({ - books: bs, - book, - onUpdate: (book: Book.Book) => updateFocusBook(_ => book) - }) - - return bookDetailModal({ - book, - onClose: () => updateFocusBook(_ => undefined), - onmount: () => addEventListener('keyup', onKeyup), - onunmount: () => removeEventListener('keyup', onKeyup) - }) - } - }), - bs.map(book => viewBook({ - book, - onSelect: (book) => updateFocusBook(_ => book) - })) - ]) - ]) - ) -} - -interface KeyupHandlerParams { - books: Array - book: Book.Book - onUpdate: (book: Book.Book) => void -} - -function keyupHandler({ books, book, onUpdate }: KeyupHandlerParams): ((e: KeyboardEvent) => void) { - return (e: KeyboardEvent) => { - if (e.key === 'ArrowLeft') { - const indexedBooks = books.map((b, i) => ({ b, i })) - const focus = indexedBooks.find(({ b }) => b == book) - if (focus !== undefined && focus.i > 0) { - onUpdate(books[focus.i - 1]) - } - } else if (e.key === 'ArrowRight') { - const indexedBooks = books.map((b, i) => ({ b, i })) - const focus = indexedBooks.find(({ b }) => b == book) - if (focus !== undefined && focus.i < books.length - 1) { - onUpdate(books[focus.i + 1]) - } - } - } -} - -interface ViewBookParams { - book: Book.Book - onSelect: (book: Book.Book) => void -} - -function viewBook({ book, onSelect }: ViewBookParams): Html { - return h('button', - { className: 'g-Book' }, - h('img', - { src: book.cover, - alt: book.title, - className: 'g-Book__Image', - onclick: () => onSelect(book) - } - ) - ) -} - -interface BookDetailModalParams { - book: Book.Book - onClose: () => void - onmount: () => void - onunmount: () => void -} - -function bookDetailModal({ book, onClose, onmount, onunmount }: BookDetailModalParams): Html { - return Modal.view({ - header: h('div', - h('div', { className: 'g-BookDetail__Title' }, `${book.title}, ${book.date}`), - book.subtitle && h('div', { className: 'g-BookDetail__Subtitle' }, book.subtitle) - ), - body: h('div', - { className: 'g-BookDetail' }, - h('img', { src: book.cover }), - h('div', - h('dl', - metadata('Auteur', book.authors), - metadata('Genre', book.genres) - ), - book.summary && book.summary - .split('\n') - .map(str => str.trim()) - .filter(str => str != '') - .map(str => h('p', str)) - ) - ), - onClose, - onmount, - onunmount - }) -} - -function metadata(term: string, descriptions: Array): Html { - return h('div', - h('dt', term, descriptions.length > 1 && 's', ' :'), - h('dd', ' ', descriptions.join(', ')) - ) -} diff --git a/library/client/view/components/modal.ts b/library/client/view/components/modal.ts deleted file mode 100644 index 5e845e1..0000000 --- a/library/client/view/components/modal.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { h, Html } from 'lib/rx' - -interface Params { - header: Html - body: Html - onClose: () => void - onmount?: (element: Element) => void - onunmount?: (element: Element) => void -} - -export function view({ header, body, onClose, onmount, onunmount }: Params): Html { - return h('div', - { className: 'g-Modal', - onclick: () => onClose(), - onmount: (element: Element) => onmount && onmount(element), - onunmount: (element: Element) => onunmount && onunmount(element) - }, - h('div', - { className: 'g-Modal__Content', - onclick: (e: Event) => e.stopPropagation() - }, - h('div', - { className: 'g-Modal__Header' }, - header, - h('button', - { className: 'g-Modal__Close', - onclick: () => onClose() - }, - '✕' - ) - ), - h('div', - { className: 'g-Modal__Body' }, - body - ) - ) - ) -} diff --git a/library/client/view/filters.ts b/library/client/view/filters.ts deleted file mode 100644 index efe4115..0000000 --- a/library/client/view/filters.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { h, Rx, Html } from 'lib/rx' -import * as Book from 'book' -import * as I18n from 'lib/i18n' - -// Model - -export interface Model { - read?: Book.ReadStatus -} - -const init: Model = {} - -// View - -interface ViewFiltersParams { - filteredBooks: Rx> - filters: Rx - updateFilters: (f: (filters: Model) => Model) => void -} - -export function view({ filteredBooks, filters, updateFilters }: ViewFiltersParams): Html { - return h('ul', - h('li', [ - h('div', { className: 'g-FilterTitle' }, 'Lecture'), - readFilter({ - filteredBooks, - readStatus: filters.map(fs => fs.read), - update: (status?: Book.ReadStatus) => updateFilters(fs => { - fs.read = status - return fs - }) - }) - ]) - ) -} - -interface ReadFilterParams { - filteredBooks: Rx> - readStatus: Rx - update: (status?: Book.ReadStatus) => void -} - -function readFilter({ filteredBooks, readStatus, update }: ReadFilterParams): Html { - return h('ul', - { className: 'g-Filters' }, - readStatus.map(currentStatus => { - if (currentStatus !== undefined) { - return h('li', - { className: 'g-Filter g-Filter--Selected' }, - h('button', - { onclick: () => update(undefined) }, - filteredBooks.map(xs => unit(xs.length, readStatusLabels(currentStatus))) - ) - ) - } else { - return Book.readStatuses.map(status => - filteredBooks.map(xs => { - const count = xs.filter(b => b.read === status).length - - return count !== 0 - ? h('li', - { className: 'g-Filter g-Filter--Unselected' }, - h('button', - { onclick: () => update(status) }, - unit(count, readStatusLabels(status)) - ) - ) - : undefined - }) - ) - } - }) - ) -} - -function unit(n: number, labels: Array): string { - return I18n.unit(n, labels[0], labels[1], (n, str) => `${str} (${n})`) -} - -function readStatusLabels(status: Book.ReadStatus): Array { - if (status === 'Read') { - return ['lu', 'lus'] - } else if (status === 'Unread') { - return ['non lu', 'non lus'] - } else if (status === 'Reading') { - return ['en cours', 'en cours'] - } else { - return ['arrêté', 'arrêtés'] - } -} -- cgit v1.2.3