initial commit
This commit is contained in:
65
misc/menus/menu.json
Normal file
65
misc/menus/menu.json
Normal file
@ -0,0 +1,65 @@
|
||||
[
|
||||
{
|
||||
"date": "2024-09-09",
|
||||
"menus": [
|
||||
{
|
||||
"name": "Risotto à la courge",
|
||||
"extra": [
|
||||
"Chips de courge",
|
||||
"Fromage râpé"
|
||||
],
|
||||
"prices": {
|
||||
"E": 8.1,
|
||||
"C": 11.0,
|
||||
"D": 9.9,
|
||||
"V": 12.6
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Ragoût de cerf",
|
||||
"extra": [
|
||||
"Spätzli au beurre",
|
||||
"Choux de Bruxelles et chou rouge"
|
||||
],
|
||||
"prices": {
|
||||
"E": 11.5,
|
||||
"C": 14.4,
|
||||
"D": 13.3,
|
||||
"V": 16.0
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"date": "2024-09-10",
|
||||
"menus": [
|
||||
{
|
||||
"name": "Rösti aux champignons",
|
||||
"extra": [
|
||||
"Sauce crême",
|
||||
"Salade du chef"
|
||||
],
|
||||
"prices": {
|
||||
"E": 7.0,
|
||||
"C": 9.0,
|
||||
"D": 8.0,
|
||||
"V": 10.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Boulette de poulet",
|
||||
"extra": [
|
||||
"Sauce alfredo",
|
||||
"Pâtes spaghetti",
|
||||
"Jardinière de légumes"
|
||||
],
|
||||
"prices": {
|
||||
"E": 10.5,
|
||||
"C": 13.4,
|
||||
"D": 12.3,
|
||||
"V": 15.0
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
BIN
misc/menus/menu.png
Normal file
BIN
misc/menus/menu.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 173 KiB |
202
misc/menus/menu.typ
Normal file
202
misc/menus/menu.typ
Normal file
@ -0,0 +1,202 @@
|
||||
#let path = sys.inputs.at("path", default: "menu.json")
|
||||
#let all-combos = sys.inputs.at("all-combos", default: "false") == "true"
|
||||
#let categories = sys.inputs.at("categories", default: "E,D,C,V").split(",")
|
||||
#let dark = sys.inputs.at("dark", default: "false") == "true"
|
||||
#let days = json(path)
|
||||
#let nbsp = sym.space.nobreak
|
||||
|
||||
#let colors = if dark {(
|
||||
bg: rgb("#1f1f24"),
|
||||
text: white,
|
||||
categories: rgb("#70c947"),
|
||||
menu-sep: rgb("#ababab"),
|
||||
title-underline: rgb("#eb2324"),
|
||||
day-sep: rgb("#a3cf40")
|
||||
)} else {(
|
||||
bg: white,
|
||||
text: black,
|
||||
categories: rgb("#577F25"),
|
||||
menu-sep: rgb("#ababab"),
|
||||
title-underline: rgb("#e34243"),
|
||||
day-sep: rgb("#82BC00")
|
||||
)}
|
||||
|
||||
#let width = if days.len() > 1 {20cm} else {10cm}
|
||||
|
||||
#set page(
|
||||
margin: 0cm,
|
||||
height: auto,
|
||||
width: width,
|
||||
fill: colors.bg
|
||||
)
|
||||
|
||||
#set text(
|
||||
fill: colors.text
|
||||
)
|
||||
|
||||
#let parse-date(date-txt) = {
|
||||
let (year, month, day) = date-txt.split("-")
|
||||
return datetime(year: int(year), month: int(month), day: int(day))
|
||||
}
|
||||
|
||||
#let fmt-number(
|
||||
number,
|
||||
decimals: 2,
|
||||
decimal-sep: ".",
|
||||
thousands-sep: "'"
|
||||
) = {
|
||||
let integer = calc.trunc(number)
|
||||
let decimal = calc.fract(number)
|
||||
let res = str(integer).clusters()
|
||||
.rev()
|
||||
.chunks(3)
|
||||
.map(c => c.join(""))
|
||||
.join(thousands-sep)
|
||||
.rev()
|
||||
|
||||
if decimals != 0 {
|
||||
res += decimal-sep
|
||||
decimal = decimal * calc.pow(10, decimals)
|
||||
decimal = calc.round(decimal)
|
||||
decimal = str(decimal) + ("0" * decimals)
|
||||
res += decimal.slice(0, decimals)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
#let day-names = (
|
||||
"Lundi",
|
||||
"Mardi",
|
||||
"Mercredi",
|
||||
"Jeudi",
|
||||
"Vendredi",
|
||||
"Samedi",
|
||||
"Dimanche"
|
||||
)
|
||||
|
||||
#let display-menus(
|
||||
days,
|
||||
categories,
|
||||
day-sep-stroke: colors.day-sep + 1pt,
|
||||
title-underline-stroke: colors.title-underline + 2pt,
|
||||
menu-sep-stroke: colors.menu-sep + 1pt,
|
||||
price-categ-color: colors.categories
|
||||
) = {
|
||||
set text(font: "Liberation Sans", hyphenate: true)
|
||||
let cols = calc.min(2, days.len())
|
||||
|
||||
let cells = ()
|
||||
|
||||
for day in days {
|
||||
let date = parse-date(day.date)
|
||||
let weekday = date.weekday() - 1
|
||||
let day-name = day-names.at(weekday)
|
||||
let menus = ()
|
||||
for menu in day.menus {
|
||||
let price-cell = grid.cell(rowspan: 2)[]
|
||||
let prices = menu.at(
|
||||
"prices",
|
||||
default: (:)
|
||||
)
|
||||
if menu.keys().contains("price") {
|
||||
prices = ("": menu.price)
|
||||
}
|
||||
|
||||
let pairs = prices.pairs()
|
||||
.filter(p => p.first() in categories)
|
||||
.sorted(key: p => p.last())
|
||||
|
||||
let prices = pairs.map(p => {
|
||||
let categ = emph(
|
||||
text(
|
||||
fill: price-categ-color,
|
||||
p.first()
|
||||
)
|
||||
)
|
||||
let price = fmt-number(p.last())
|
||||
categ + ":" + nbsp + "CHF" + nbsp + price
|
||||
})
|
||||
price-cell = grid.cell(
|
||||
rowspan: menu.extra.len() + 1,
|
||||
stack(dir: ttb, spacing: .4em, ..prices)
|
||||
)
|
||||
//let price = "CHF" + sym.space.nobreak + str(menu.price)
|
||||
menus.push(
|
||||
grid(
|
||||
columns: (1fr, auto),
|
||||
column-gutter: .4em,
|
||||
row-gutter: .4em,
|
||||
[*#menu.name*],
|
||||
price-cell,
|
||||
..menu.extra.map(l => text(10pt, l))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
let title = align(
|
||||
center,
|
||||
stack(
|
||||
dir: ttb,
|
||||
spacing: .5em,
|
||||
text(size: 14pt, weight: "bold", day-name),
|
||||
text(size: 10pt, date.display("[day].[month]"))
|
||||
)
|
||||
)
|
||||
let title-box = box(
|
||||
width: 50%,
|
||||
inset: .6em,
|
||||
stroke: (bottom: title-underline-stroke),
|
||||
title
|
||||
)
|
||||
let title-cell = grid.cell(align: center, title-box)
|
||||
|
||||
cells.push(box(width: 100%, grid(
|
||||
columns: (100%),
|
||||
inset: (x, y) => (
|
||||
top: if y == 0 {0pt} else {.8em},
|
||||
bottom: if y == 0 {0pt} else {2em}
|
||||
),
|
||||
stroke: (_, y) => (top: if y < 2 {none} else {menu-sep-stroke}),
|
||||
title-cell,
|
||||
..menus
|
||||
)))
|
||||
}
|
||||
|
||||
if calc.rem(cells.len(), 2) == 1 {
|
||||
cells.last() = grid.cell(colspan: calc.min(2, cols), cells.last())
|
||||
}
|
||||
|
||||
grid(
|
||||
columns: (100% / cols,) * cols,
|
||||
inset: (x: 1.5em, y: .8em),
|
||||
stroke: (x, y) => {
|
||||
let borders = (:)
|
||||
if x != 0 {
|
||||
borders.insert("left", day-sep-stroke)
|
||||
}
|
||||
if y != 0 {
|
||||
borders.insert("top", day-sep-stroke)
|
||||
}
|
||||
return borders
|
||||
},
|
||||
..cells
|
||||
)
|
||||
}
|
||||
|
||||
#{
|
||||
if all-combos {
|
||||
let n-combos = calc.pow(2, categories.len())
|
||||
for i in range(n-combos) {
|
||||
let filtered-categories = categories.enumerate().filter(p => {
|
||||
let (j, categ) = p
|
||||
return i.bit-rshift(j).bit-and(1) != 0
|
||||
}).map(p => p.last())
|
||||
display-menus(days, filtered-categories)
|
||||
pagebreak(weak: true)
|
||||
}
|
||||
|
||||
} else {
|
||||
display-menus(days, categories)
|
||||
}
|
||||
}
|
BIN
misc/menus/menu_dark.png
Normal file
BIN
misc/menus/menu_dark.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 174 KiB |
BIN
misc/menus/menu_student.png
Normal file
BIN
misc/menus/menu_student.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 121 KiB |
Reference in New Issue
Block a user