aboutsummaryrefslogtreecommitdiff
path: root/src/view/books.ts
blob: 5ec019a07e8bb2353745e8655d9b60dc5880746b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
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<Array<Book.Book>>): Html {
  return h('div',
    { className: 'g-Books' },
    withVar<Book.Book | undefined>(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: 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,
        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<string>): Html {
  return h('div',
    h('dt', term, descriptions.length > 1 && 's', ' :'),
    h('dd', ' ', descriptions.join(', '))
  )
}