diff options
Diffstat (limited to 'src/view')
-rw-r--r-- | src/view/sort.ts | 53 | ||||
-rw-r--r-- | src/view/table.ts | 102 |
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' + } +} |