aboutsummaryrefslogtreecommitdiff
path: root/src/view
diff options
context:
space:
mode:
Diffstat (limited to 'src/view')
-rw-r--r--src/view/sort.ts53
-rw-r--r--src/view/table.ts102
2 files changed, 155 insertions, 0 deletions
diff --git a/src/view/sort.ts b/src/view/sort.ts
new file mode 100644
index 0000000..3fed7fd
--- /dev/null
+++ b/src/view/sort.ts
@@ -0,0 +1,53 @@
+import * as Food from 'food'
+import * as Search from 'lib/search'
+
+export interface Sort {
+ field: Field,
+ order: Order,
+}
+
+export enum Field {
+ Name,
+ GlycemicIndex,
+ Carbohydrates,
+ GlycemicLoad,
+}
+
+export enum Order {
+ Down,
+ Up,
+}
+
+export const init: Sort = {
+ field: Field.Name,
+ order: Order.Down,
+}
+
+export function aliments(a1: Food.Aliment, a2: Food.Aliment, sort: Sort) {
+ return getField(a1, sort.field) > getField(a2, sort.field)
+ ? (sort.order === Order.Down ? 1 : -1)
+ : (sort.order === Order.Down ? -1 : 1)
+}
+
+function getField(a: Food.Aliment, sort: Field) {
+ if (sort === Field.Name) {
+ return Search.format(a.name)
+ } else if (sort === Field.GlycemicIndex) {
+ return a.glycemicIndex
+ } else if (sort === Field.Carbohydrates) {
+ return a.carbohydrates
+ } else {
+ return Food.glycemicLoad(a)
+ }
+}
+
+export function toggle(sort: Sort, field: Field): Sort {
+ return {
+ field,
+ order: sort.field === field ? reverseOrder(sort.order) : Order.Down
+ }
+}
+
+function reverseOrder(order: Order): Order {
+ return order === Order.Down ? Order.Up : Order.Down
+}
diff --git a/src/view/table.ts b/src/view/table.ts
new file mode 100644
index 0000000..171300d
--- /dev/null
+++ b/src/view/table.ts
@@ -0,0 +1,102 @@
+import h from 'lib/h'
+import * as Format from 'lib/format'
+import * as Dom from 'lib/dom'
+import * as Function from 'lib/function'
+import * as Food from 'food'
+import * as Sort from 'view/sort'
+
+export function view(): Element {
+ const maxGlycemicLoad: number = Math.max(...Food.all.map(Food.glycemicLoad))
+ const aliments = h('div',
+ { className: 'g-Aliments' },
+ ...Food.all.sort((a1, a2) => Sort.aliments(a1, a2, Sort.init)).map(aliment => line(aliment, maxGlycemicLoad))
+ )
+
+ return h('div',
+ { className: 'g-Page' },
+ header(
+ (search: string, sort: Sort.Sort) => {
+ const lines = Food
+ .search(search)
+ .sort((a1, a2) => Sort.aliments(a1, a2, sort))
+ .map(aliment => line(aliment, maxGlycemicLoad))
+ Dom.replaceChildren(aliments, ...lines)
+ }
+ ),
+ aliments
+ )
+}
+
+function header(
+ onUpdateTable: (search: string, sort: Sort.Sort) => void
+): Element {
+ let search = ''
+ let sort = Sort.init
+
+ const alimentHeader = h('button', { className: 'g-Header--Sorted' }, 'Aliment')
+ const glycemicIndexHeader = h('button', {}, 'Index glycémique')
+ const carbohydratesHeader = h('button', {}, 'Glucides pour 100 g')
+ const glycemicLoadHeader = h('button', {}, 'Charge glycémique')
+
+ const onSort = (field: Sort.Field) => () => {
+ sort = Sort.toggle(sort, field)
+ onUpdateTable(search, sort)
+ alimentHeader.className = field === Sort.Field.Name ? 'g-Header--Sorted' : ''
+ glycemicIndexHeader.className = field === Sort.Field.GlycemicIndex ? 'g-Header--Sorted' : ''
+ carbohydratesHeader.className = field === Sort.Field.Carbohydrates ? 'g-Header--Sorted' : ''
+ glycemicLoadHeader.className = field === Sort.Field.GlycemicLoad ? 'g-Header--Sorted' : ''
+ }
+
+ alimentHeader.addEventListener('click', onSort(Sort.Field.Name))
+ glycemicIndexHeader.addEventListener('click', onSort(Sort.Field.GlycemicIndex))
+ carbohydratesHeader.addEventListener('click', onSort(Sort.Field.Carbohydrates))
+ glycemicLoadHeader.addEventListener('click', onSort(Sort.Field.GlycemicLoad))
+
+ return h('header',
+ { className: 'g-Header' },
+ h('div',
+ { className: 'g-Title g-Line' },
+ alimentHeader,
+ glycemicIndexHeader,
+ carbohydratesHeader,
+ glycemicLoadHeader,
+ ),
+ h('input',
+ { className: 'g-Search g-Line',
+ placeholder: 'Rechercher…',
+ oninput: Function.debounce((e: Event) => {
+ search = (e.target as HTMLInputElement).value
+ onUpdateTable(search, sort)
+ })
+ }
+ )
+ )
+}
+
+function line(aliment: Food.Aliment, maxGlycemicLoad: number): Element {
+ return h('div',
+ { className: 'g-Aliment g-Line' },
+ h('div', {}, aliment.name),
+ h('div', { className: 'g-Number' }, aliment.glycemicIndex),
+ h('div', { className: 'g-Number' }, `${Math.round(aliment.carbohydrates)} g`),
+ h('div', { className: 'g-Number' }, formatGlycemicLoad(aliment, maxGlycemicLoad))
+ )
+}
+
+function formatGlycemicLoad(aliment: Food.Aliment, maxGlycemicLoad: number): Element {
+ const glycemicLoad = Food.glycemicLoad(aliment)
+ return h('div',
+ { className: `number ${glycemicLoadNumberClass(glycemicLoad)}` },
+ Format.decimal(glycemicLoad)
+ )
+}
+
+function glycemicLoadNumberClass(index: number): string {
+ if (index < 8) {
+ return 'g-Number--Low'
+ } else if (index < 20) {
+ return 'g-Number--Middle'
+ } else {
+ return 'g-Number--High'
+ }
+}