From ee088b33407901f4b53c1eb177795f55769f3c51 Mon Sep 17 00:00:00 2001 From: sijanec Date: Wed, 23 Sep 2020 01:23:17 +0200 Subject: manifest.json now links to google play app --- NOT TESTED! --- assets/root/manifest.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'assets') diff --git a/assets/root/manifest.json b/assets/root/manifest.json index b8bf5da..d436954 100644 --- a/assets/root/manifest.json +++ b/assets/root/manifest.json @@ -56,4 +56,11 @@ "productivity", "utilities" ] -} \ No newline at end of file + "prefer_related_applications": true, + "related_applications": [ + { + "platform": "play", + "id": "tk.gimb.app" + } + ] +} -- cgit v1.2.3 From 6a41e1b61cf0e3298fa8e05bb4ec7434ebd7cf68 Mon Sep 17 00:00:00 2001 From: sijanec Date: Wed, 23 Sep 2020 01:32:45 +0200 Subject: json bi lahko preveril --- assets/root/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'assets') diff --git a/assets/root/manifest.json b/assets/root/manifest.json index d436954..99fd18d 100644 --- a/assets/root/manifest.json +++ b/assets/root/manifest.json @@ -55,7 +55,7 @@ "education", "productivity", "utilities" - ] + ], "prefer_related_applications": true, "related_applications": [ { -- cgit v1.2.3 From 616c225661687b05ab82160f5b7d6dcb4d39b8e6 Mon Sep 17 00:00:00 2001 From: sijanec Date: Wed, 23 Sep 2020 18:37:16 +0200 Subject: fixed meals layout - fullcalendar -- do not merge! todo: fix sidenav in meals --- assets/css/styles.css | 14 +- assets/js/gradings.js | 318 +++++++++---------- assets/js/lang/bundle.js | 8 +- assets/js/meals.js | 710 +++++++++++++++++++++--------------------- assets/pages-src/gradings.bvr | 143 ++++----- assets/pages-src/meals.bvr | 262 +++++++++------- 6 files changed, 767 insertions(+), 688 deletions(-) (limited to 'assets') diff --git a/assets/css/styles.css b/assets/css/styles.css index 0e11125..a363449 100644 --- a/assets/css/styles.css +++ b/assets/css/styles.css @@ -428,4 +428,16 @@ h1, h2, h3, h4, h5, h6 { ol { color: var(--color-text) !important; -} \ No newline at end of file +} + +/* meals: */ + +.selected-meal { + color: green !important; + font-weight: bold !important; +} + +.to-be-selected-meal { + color: brown !important; + font-weight: italic !important; +} diff --git a/assets/js/gradings.js b/assets/js/gradings.js index 4730248..92e696d 100644 --- a/assets/js/gradings.js +++ b/assets/js/gradings.js @@ -2,197 +2,197 @@ var calendar_obj = null; var gradings; async function checkLogin() { - localforage.getItem("logged_in").then((value) => { - // This code runs once the value has been loaded - // from the offline store. - if (value !== true) { - window.location.replace("/index.html"); - } - }).catch((err) => { - // This code runs if there were any errors - console.log(err); - }); + localforage.getItem("logged_in").then((value) => { + // This code runs once the value has been loaded + // from the offline store. + if (value !== true) { + window.location.replace("/index.html"); + } + }).catch((err) => { + // This code runs if there were any errors + console.log(err); + }); } // Set loading bar visibility function setLoading(state) { - if (state) { - $("#loading-bar").removeClass("hidden"); - } else { - $("#loading-bar").addClass("hidden"); - } + if (state) { + $("#loading-bar").removeClass("hidden"); + } else { + $("#loading-bar").addClass("hidden"); + } } // GET COLOR FROM STRING function hashCode(str) { // java String#hashCode - var hash = 0; - for (var i = 0; i < str.length; i++) { - hash = str.charCodeAt(i) + ((hash << 5) - hash); - } - return hash; + var hash = 0; + for (var i = 0; i < str.length; i++) { + hash = str.charCodeAt(i) + ((hash << 5) - hash); + } + return hash; } function intToRGB(i) { - var c = (i & 0x00FFFFFF) - .toString(16) - .toUpperCase(); + var c = (i & 0x00FFFFFF) + .toString(16) + .toUpperCase(); - return "00000".substring(0, 6 - c.length) + c; + return "00000".substring(0, 6 - c.length) + c; } // http://www.w3.org/TR/AERT#color-contrast function getForegroundFromBackground(background_color) { - let color_hex = background_color.replace("#", ""); - let rgb = [ - parseInt(color_hex.substring(0, 2), 16), - parseInt(color_hex.substring(2, 4), 16), - parseInt(color_hex.substring(4, 6), 16) - ]; - let o = Math.round(((parseInt(rgb[0]) * 299) + (parseInt(rgb[1]) * 587) + (parseInt(rgb[2]) * 114)) / 1000); - if (o > 180) { - return "#000000"; - } else { - return "#ffffff"; - } + let color_hex = background_color.replace("#", ""); + let rgb = [ + parseInt(color_hex.substring(0, 2), 16), + parseInt(color_hex.substring(2, 4), 16), + parseInt(color_hex.substring(4, 6), 16) + ]; + let o = Math.round(((parseInt(rgb[0]) * 299) + (parseInt(rgb[1]) * 587) + (parseInt(rgb[2]) * 114)) / 1000); + if (o > 180) { + return "#000000"; + } else { + return "#ffffff"; + } } function getHexColorFromString(str) { - return "#" + intToRGB(hashCode(str)); + return "#" + intToRGB(hashCode(str)); } // -------------------------------------------------- function getDateString() { - let date = new Date(); + let date = new Date(); - let year_str = date.getFullYear(); - let month_str = date.getMonth() + 1 - month_str = month_str.toString().padStart(2, "0"); - let day_str = date.getDate(); - day_str = day_str.toString().padStart(2, "0"); + let year_str = date.getFullYear(); + let month_str = date.getMonth() + 1 + month_str = month_str.toString().padStart(2, "0"); + let day_str = date.getDate(); + day_str = day_str.toString().padStart(2, "0"); - let date_string = year_str + "-" + month_str + "-" + day_str; - return date_string; + let date_string = year_str + "-" + month_str + "-" + day_str; + return date_string; } async function loadGradings(force_refresh = false) { - setLoading(true); - let promises_to_run = [ - localforage.getItem("username").then((value) => { - username = value; - }), - localforage.getItem("password").then((value) => { - password = value; - }), - localforage.getItem("gradings").then((value) => { - gradings = value; - }) - ]; - await Promise.all(promises_to_run); - if (gradings == null || gradings == [] || gradings == -1 || force_refresh) { - try { - let gsecInstance = new gsec(); - await gsecInstance.login(username, password); - gsecInstance.fetchGradings().then( (value) => { - gradings = value; - localforage.setItem("gradings", value).then(() => { - displayData(); - setLoading(false); - }); - setLoading(false); - }).catch( (err) => { - gsecErrorHandlerUI(err); - setLoading(false); - }); - } catch (err) { - gsecErrorHandlerUI(err); - setLoading(false); - } - } else { - displayData(); - setLoading(false); - } + setLoading(true); + let promises_to_run = [ + localforage.getItem("username").then((value) => { + username = value; + }), + localforage.getItem("password").then((value) => { + password = value; + }), + localforage.getItem("gradings").then((value) => { + gradings = value; + }) + ]; + await Promise.all(promises_to_run); + if (gradings == null || gradings == [] || gradings == -1 || force_refresh) { + try { + let gsecInstance = new gsec(); + await gsecInstance.login(username, password); + gsecInstance.fetchGradings().then((value) => { + gradings = value; + localforage.setItem("gradings", value).then(() => { + displayData(); + setLoading(false); + }); + setLoading(false); + }).catch((err) => { + gsecErrorHandlerUI(err); + setLoading(false); + }); + } catch (err) { + gsecErrorHandlerUI(err); + setLoading(false); + } + } else { + displayData(); + setLoading(false); + } } function displayData() { - let transformed_gradings = []; - gradings.forEach((element, index) => { - let bg_color = getHexColorFromString(element["acronym"]); - let fg_color = getForegroundFromBackground(bg_color); - let grading_object = { - start: element["date"].toISOString().substring(0, 10), // če se da direktno date object, se doda še 1a zraven (prefixa tajtlu) (verjetno 1am ura) - title: element["acronym"], - id: index.toString(), - backgroundColor: bg_color, - textColor: fg_color - }; - transformed_gradings.push(grading_object); - }); - calendar_obj.removeAllEvents(); - calendar_obj.addEventSource(transformed_gradings); + let transformed_gradings = []; + gradings.forEach((element, index) => { + let bg_color = getHexColorFromString(element["acronym"]); + let fg_color = getForegroundFromBackground(bg_color); + let grading_object = { + start: element["date"].toISOString().substring(0, 10), // če se da direktno date object, se doda še 1a zraven (prefixa tajtlu) (verjetno 1am ura) + title: element["acronym"], + id: index.toString(), + backgroundColor: bg_color, + textColor: fg_color + }; + transformed_gradings.push(grading_object); + }); + calendar_obj.removeAllEvents(); + calendar_obj.addEventSource(transformed_gradings); } async function validateInputs() { - if ($("#input-grading-name").val() != null && $("#input-grading-name").val().length > 0) { - $("#btn-add-grading").removeAttr("disabled"); - $("#input-grading-name").addClass("valid"); - $("#input-grading-name").removeClass("invalid"); - } else { - $("#btn-add-grading").attr("disabled", "disabled"); - $("#input-grading-name").addClass("invalid"); - $("#input-grading-name").removeClass("valid"); - } + if ($("#input-grading-name").val() != null && $("#input-grading-name").val().length > 0) { + $("#btn-add-grading").removeAttr("disabled"); + $("#input-grading-name").addClass("valid"); + $("#input-grading-name").removeClass("invalid"); + } else { + $("#btn-add-grading").attr("disabled", "disabled"); + $("#input-grading-name").addClass("invalid"); + $("#input-grading-name").removeClass("valid"); + } } function gradingClickHandler(eventClickInfo) { - let grading_id = parseInt(eventClickInfo.event.id); - let grading_subject = gradings[grading_id]["subject"]; - let grading_date_obj = gradings[grading_id]["date"]; - let grading_date = dateString.longFormatted(grading_date_obj); - let grading_description = gradings[grading_id]["description"]; - $("#grading-subject").text(grading_subject); - $("#grading-date").text(grading_date); - $("#grading-description").text(grading_description); - const modal = document.querySelectorAll(".side-modal")[0]; - M.Sidenav.getInstance(modal).open(); + let grading_id = parseInt(eventClickInfo.event.id); + let grading_subject = gradings[grading_id]["subject"]; + let grading_date_obj = gradings[grading_id]["date"]; + let grading_date = dateString.longFormatted(grading_date_obj); + let grading_description = gradings[grading_id]["description"]; + $("#grading-subject").text(grading_subject); + $("#grading-date").text(grading_date); + $("#grading-description").text(grading_description); + const modal = document.querySelectorAll(".side-modal")[0]; + M.Sidenav.getInstance(modal).open(); } function setupPickers() { - // Setup pickers, todo (adding an event), to be stored in messages - var date_object = new Date(); - let elems = document.querySelectorAll('#datepicker-add'); - let options = { - autoClose: true, - format: "dd.mm.yyyy", - defaultDate: date_object, - setDefaultDate: true, - firstDay: 1 - } - instances = M.Datepicker.init(elems, options); + // Setup pickers, todo (adding an event), to be stored in messages + var date_object = new Date(); + let elems = document.querySelectorAll('#datepicker-add'); + let options = { + autoClose: true, + format: "dd.mm.yyyy", + defaultDate: date_object, + setDefaultDate: true, + firstDay: 1 + } + instances = M.Datepicker.init(elems, options); } document.addEventListener("DOMContentLoaded", () => { - checkLogin(); - // Calendar setup - var calendarEl = document.getElementById("calendar"); - calendar_obj = new FullCalendar.Calendar(calendarEl, { - firstDay: 1, - plugins: ["dayGrid"], - defaultDate: getDateString(), - navLinks: false, - editable: false, - events: [], - eventClick: gradingClickHandler, - height: "parent" - }); - calendar_obj.render(); - - // Modal for adding gradings - - // setupPickers(); // todo (adding an event), to be stored in messages - // // Setup modals + checkLogin(); + // Calendar setup + var calendarEl = document.getElementById("calendar"); + calendar_obj = new FullCalendar.Calendar(calendarEl, { + firstDay: 1, + plugins: ["dayGrid"], + defaultDate: getDateString(), + navLinks: false, + editable: false, + events: [], + eventClick: gradingClickHandler, + height: "parent" + }); + calendar_obj.render(); + + // Modal for adding gradings + + // setupPickers(); // todo (adding an event), to be stored in messages + // // Setup modals // const modal_elems = document.querySelectorAll('.modal'); // const modal_options = { // onOpenStart: () => { $("#fab-new").hide() }, @@ -202,15 +202,21 @@ document.addEventListener("DOMContentLoaded", () => { // M.Modal.init(modal_elems, modal_options); - loadGradings(true); - // Setup refresh handler - $("#refresh-icon").click(() => { - loadGradings(true); - }); - // Setup side menu - const menus = document.querySelectorAll(".side-menu"); - M.Sidenav.init(menus, { edge: "right", draggable: true }); - // Setup side modal - const modals = document.querySelectorAll('.side-modal'); - M.Sidenav.init(modals, { edge: 'left', draggable: false }); + loadGradings(true); + // Setup refresh handler + $("#refresh-icon").click(() => { + loadGradings(true); + }); + // Setup side menu + const menus = document.querySelectorAll(".side-menu"); + M.Sidenav.init(menus, { + edge: "right", + draggable: true + }); + // Setup side modal + const modals = document.querySelectorAll('.side-modal'); + M.Sidenav.init(modals, { + edge: 'left', + draggable: false + }); }); diff --git a/assets/js/lang/bundle.js b/assets/js/lang/bundle.js index 10cfd20..4e15832 100644 --- a/assets/js/lang/bundle.js +++ b/assets/js/lang/bundle.js @@ -241,7 +241,7 @@ var langstrings = { usage: "usage", mealsUsageNote: "click on a date to open the collapsible menu with choices and click on a specific meal to select it. Reload the meals when you're done and check the entries.", lunchesNote: "app was not tested with lunches in mind. Meals probably won't work with lunches and having a lunch subscription may even break its functionality.", - mealNotShownNote: "if a meal is not present in the meals collapsible field, this does not necessarily mean it does not exist. Meals that haven't been altered by you and are unchangable (read-only) are not shown for clarity.", + mealNotShownNote: "editable meals are highlighted in gold, read-only meals are highlighted in grey and cannot be changed. Meals that provide no options for menus are not shown for clarity, same applies for days where there are no meals", mealsContributeNote: "you are welcome to contribute to the LopolisAPI project and add features, such as checkouts.", authenticationError: "authentication error", lopolisAPIConnectionError: "LopolisAPI server connection error", @@ -252,6 +252,7 @@ var langstrings = { errorSettingMeals: "error setting meals", mealSet: "meal set! Reload meals to be sure", selected: "selected", + meal: "meal", // about version: "version", authors: "authors", @@ -439,7 +440,7 @@ var langstrings = { recipientNotInDirectory: "izbrane osebe ni v imeniku", chatExternalInfo: "dobili ste kratko sporočilo v standardu, ki ga GimSIS ne podpira. Pri odgovarjanju spremenite zadevo. Vsebina sporočila: ", // meals - loginError: "napaka pri prijavi", + loginError: "napaka pri prijavi", loginToLopolis: "prijava v Lopolis", loginToLopolisNote: "izgleda, da niste prijavljeni v eRestavracijo, zato se vam je prikazal prijavni obrazec. Za uporavljanje s prehrano se uporablja druga kombinacija uporabniškega imena in gesla, zato se prijavite s svojimi Lopolis prijavnimi podatki za nadaljevanje.", logInToLopolis: "prijava v Lopolis", @@ -448,7 +449,7 @@ var langstrings = { usage: "uporaba", mealsUsageNote: "kliknite na datum za prikaz menijev, nato pa si enega izberite s klikom na ime menija. Po nastavitvi menijev ponovno naložite menije in se prepričajte o pravilnih nastavitvah.", lunchesNote: "aplikacija ni testirana za naročanje na kosila, zato verjetno to ne deluje. Če ste naročeni na kosila lahko naročanje na menije sploh ne deluje ali pa deluje narobe.", - mealNotShownNote: "če nek dan manjka med meniji, to verjetno pomeni, da ni več spremenljiv in zanj niste ročno spremenili menija", + mealNotShownNote: "obroki, označeni z zlato so nastavljivi, tisti, označeni s sivo, niso, če pa pri kakšnem dnevu obroka ni, pa pomeni, da ga ni moč nastaviti ali pa da ne obrok ne obstaja", mealsContributeNote: "vabimo vas k urejanju LopolisAPI programa za upravljanje z meniji.", authenticationError: "napaka avtentikacije", lopolisAPIConnectionError: "napaka povezave na LopolisAPI strežnik", @@ -459,6 +460,7 @@ var langstrings = { errorSettingMeals: "napaka pri nastavljanju menijev", mealSet: "obrok nastavljen! osvežite obroke in se prepričajte sami", selected: "izbrano", + meal: "obrok", // about version: "različica", authors: "avtorji", diff --git a/assets/js/meals.js b/assets/js/meals.js index 891feae..bf9a48e 100644 --- a/assets/js/meals.js +++ b/assets/js/meals.js @@ -1,394 +1,410 @@ const API_ENDPOINT = "https://lopolis-api.gimb.tk/"; +var meals_calendar_obj = null; +var meals_data_global = {}; + +function getDateString() { // ne mene gledat, ne vem, kaj je to. + let date = new Date(); + + let year_str = date.getFullYear(); + let month_str = date.getMonth() + 1 + month_str = month_str.toString().padStart(2, "0"); + let day_str = date.getDate(); + day_str = day_str.toString().padStart(2, "0"); + + let date_string = year_str + "-" + month_str + "-" + day_str; + return date_string; +} + async function checkLogin() { - localforage.getItem("logged_in_lopolis").then((value) => { - if (value != true) { - $("#meals-container").hide(); - $("#meals-login-container").show(); - } else { - $("#meals-container").show(); - $("#meals-login-container").hide(); - loadMeals(); - } - }).catch((err) => { - console.log(err); - }); + localforage.getItem("logged_in_lopolis").then((value) => { + if (value != true) { + $("#meals-container").hide(); + $("#meals-login-container").show(); + } else { + $("#meals-container").show(); + $("#meals-login-container").hide(); + loadMeals(); + } + }).catch((err) => { + console.log(err); + }); } function setLoading(state) { - if (state) { - $("#loading-bar").removeClass("hidden"); - } else { - $("#loading-bar").addClass("hidden"); - } + if (state) { + $("#loading-bar").removeClass("hidden"); + } else { + $("#loading-bar").addClass("hidden"); + } } async function getToken(callback, callbackparams = []) { - setLoading(true); - let promises_to_run = [ - localforage.getItem("lopolis_username").then((value) => { - username = value; - }), - localforage.getItem("lopolis_password").then((value) => { - password = value; - }) - ]; - await Promise.all(promises_to_run); - - $.ajax({ - url: API_ENDPOINT + "gettoken", - crossDomain: true, - contentType: "application/json", - data: JSON.stringify({ - "username": username, - "password": password - }), - - dataType: "json", - cache: false, - type: "POST", - - success: (dataauth) => { - if(dataauth == null || dataauth.error == true) { - UIAlert(D("authenticationError"), "getToken(): response error or null"); - localforage.setItem("logged_in_lopolis", false).then( function(){ - checkLogin(); - }); - } else if (dataauth.error == false) { - let empty = {}; - empty.token = dataauth.data; - let argumentsToCallback = [empty].concat(callbackparams); - callback(...argumentsToCallback); // poslje token v {token: xxx} - } else { - UIAlert( D("authenticationError"), "getToken(): invalid response, no condition met"); - } - setLoading(false); - }, - error: () => { - UIAlert( D("lopolisAPIConnectionError"), "getToken(): AJAX error"); - setLoading(false); - } - }); + setLoading(true); + let promises_to_run = [ + localforage.getItem("lopolis_username").then((value) => { + username = value; + }), + localforage.getItem("lopolis_password").then((value) => { + password = value; + }) + ]; + await Promise.all(promises_to_run); + + $.ajax({ + url: API_ENDPOINT + "gettoken", + crossDomain: true, + contentType: "application/json", + data: JSON.stringify({ + "username": username, + "password": password + }), + + dataType: "json", + cache: false, + type: "POST", + + success: (dataauth) => { + if (dataauth == null || dataauth.error == true) { + UIAlert(D("authenticationError"), "getToken(): response error or null"); + localforage.setItem("logged_in_lopolis", false).then(function() { + checkLogin(); + }); + } else if (dataauth.error == false) { + let empty = {}; + empty.token = dataauth.data; + let argumentsToCallback = [empty].concat(callbackparams); + callback(...argumentsToCallback); // poslje token v {token: xxx} + } else { + UIAlert(D("authenticationError"), "getToken(): invalid response, no condition met"); + } + setLoading(false); + }, + error: () => { + UIAlert(D("lopolisAPIConnectionError"), "getToken(): AJAX error"); + setLoading(false); + } + }); } async function getMenus(dataauth, callback, callbackparams = []) { - setLoading(true); - let current_date = new Date(); - // naloži za dva meseca vnaprej (če so zadnji dnevi v mesecu) - let mealsgathered = {}; - let promises_to_wait_for = []; - for (let iteration = 1; iteration <= 2; iteration++) { - - promises_to_wait_for[iteration] = $.ajax({ - url: API_ENDPOINT+"getmenus", - crossDomain: true, - contentType: "application/json", - data: JSON.stringify({ - "month": current_date.getMonth() + iteration, - "year": current_date.getFullYear() - }), - - headers: { - "Authorization": `Bearer ${dataauth.token}` - }, - - dataType: "json", - cache: false, - type: "POST", - - success: (meals) => { - if(meals == null || meals.error == true) { - UIAlert( D("errorGettingMenus"), "getMenus(): response error or null"); - setLoading(false); - localforage.setItem("logged_in_lopolis", false).then( () => { - checkLogin(); - }); - } else if (meals.error == false) { - setLoading(false); - mealsgathered[iteration] = meals; - } else { - setLoading(false); - UIAlert( D("errorUnexpectedResponse") , "getMenus(): invalid response, no condition met"); - } - }, - - error: () => { - setLoading(false); - UIAlert( D("lopolisAPIConnectionError"), "getMenus(): AJAX error"); - } - }); - } - - await Promise.all(promises_to_wait_for); // javascript is ducking amazing - - let allmeals = {}; - let passtocallback = {}; - - for (const [index, monthmeals] of Object.entries(mealsgathered)) { // although this is not very javascripty - allmeals = mergeDeep(allmeals, monthmeals.data); - } - - passtocallback.data = allmeals; - passtocallback.token = dataauth.token; - let toBePassed = [passtocallback].concat(callbackparams); - callback(...toBePassed); + setLoading(true); + let current_date = new Date(); + // naloži za dva meseca vnaprej (če so zadnji dnevi v mesecu) + let mealsgathered = {}; + let promises_to_wait_for = []; + for (let iteration = 1; iteration <= 2; iteration++) { + + promises_to_wait_for[iteration] = $.ajax({ + url: API_ENDPOINT + "getmenus", + crossDomain: true, + contentType: "application/json", + data: JSON.stringify({ + "month": current_date.getMonth() + iteration, + "year": current_date.getFullYear() + }), + + headers: { + "Authorization": `Bearer ${dataauth.token}` + }, + + dataType: "json", + cache: false, + type: "POST", + + success: (meals) => { + if (meals == null || meals.error == true) { + UIAlert(D("errorGettingMenus"), "getMenus(): response error or null"); + setLoading(false); + localforage.setItem("logged_in_lopolis", false).then(() => { + checkLogin(); + }); + } else if (meals.error == false) { + setLoading(false); + mealsgathered[iteration] = meals; + } else { + setLoading(false); + UIAlert(D("errorUnexpectedResponse"), "getMenus(): invalid response, no condition met"); + } + }, + + error: () => { + setLoading(false); + UIAlert(D("lopolisAPIConnectionError"), "getMenus(): AJAX error"); + } + }); + } + + await Promise.all(promises_to_wait_for); // javascript is ducking amazing + + let allmeals = {}; + let passtocallback = {}; + + for (const [index, monthmeals] of Object.entries(mealsgathered)) { // although this is not very javascripty + allmeals = mergeDeep(allmeals, monthmeals.data); + } + + passtocallback.data = allmeals; + passtocallback.token = dataauth.token; + let toBePassed = [passtocallback].concat(callbackparams); + callback(...toBePassed); } async function loadMeals() { - getToken(getMenus, [displayMeals, []]); + getToken(getMenus, [displayMeals, []]); } function displayMeals(meals) { - // console.log(JSON.stringify(meals)); // debug // dela! - - let root_element = document.getElementById("meals-collapsible"); - for (const [date, mealzz] of Object.entries(meals.data)) { - let unabletochoosequestionmark = ""; - let readonly = mealzz.readonly; - var datum = new Date(date); - - // Create root element for a date entry - let subject_entry = document.createElement("li"); - - // Create subject collapsible header - let subject_header = document.createElement("div"); - subject_header.classList.add("collapsible-header"); - subject_header.classList.add("collapsible-header-root"); - - // Create header text element - let subject_header_text = document.createElement("span"); - - if(mealzz.readonly) { - unabletochoosequestionmark = `*${S("readOnly")}*`; - } - - // Use ES6 templates - subject_header_text = `${dateString.day(datum.getDay())}, ${datum.getDate()}. ${dateString.month(datum.getMonth())} ${datum.getFullYear()} (${mealzz.meal} @ ${mealzz.location}) ${unabletochoosequestionmark}`; - - // Create collection for displaying individuals meals - let subject_body = document.createElement("div"); - subject_body.className = "collapsible-body"; - let subject_body_root = document.createElement("ul"); - subject_body_root.className = "collection"; - - for(const [dindex, dmil] of Object.entries(mealzz.menu_options)) { - // Create element for individual meal - let meal_node = document.createElement("li"); - meal_node.className = "collection-item"; - meal_node.classList.add("collection-item") - meal_node.classList.add("meal-node"); - meal_node.dataset["index"] = dindex; - - if (!readonly) { - meal_node.onclick = () => { - setMenu(date, dmil.value); - } - } - - let meal_node_div = document.createElement("div"); - // Node for left text - let meal_lefttext = document.createElement("span"); - // Node for the right text - let meal_righttext = document.createElement("div"); - meal_righttext.className = "secondary-content"; - // Apply different style, if the meal is selected - if (dmil.selected) { - // Text - meal_lefttext.innerHTML = `${dmil.text}`; - // Number - meal_righttext.innerText = S("selected"); - } else { - // Text - meal_lefttext.innerText = dmil.text; - // Number - meal_righttext.innerText = ""; - } - meal_node_div.appendChild(meal_lefttext); - meal_node_div.appendChild(meal_righttext); - meal_node.appendChild(meal_node_div); - subject_body_root.appendChild(meal_node); - } - var subject_header_text_span = document.createElement("span"); - subject_header_text_span.innerText = subject_header_text; - subject_header.appendChild(subject_header_text_span); - subject_body.append(subject_body_root); - subject_entry.append(subject_header); - subject_entry.append(subject_body); - root_element.append(subject_entry); - } - $("#meals-collapsible").append(root_element); - // refreshClickHandlers(); + // console.log(JSON.stringify(meals)); // debug // dela! + meals_data_global = meals.data; + let transformed_meals = []; + for (const [date, mealzz] of Object.entries(meals.data)) { + let bg_color = "#877F02"; let fg_color = "#FFFFFF"; + if (mealzz.readonly) bg_color = "#8d9288"; + let meal_date = new Date(date+"+00:00"); // idk u figure it out. timezones + let meal_object = { + start: meal_date.toISOString().substring(0,10), // zakaj? poglej gradings.js - NUJNO! poglej, če so timezoni v redu! da slučajno ne preskakuje na naslednji dan! + title: S("meal"), + id: date, + allDay: true, + backgroundColor: bg_color, + textColor: fg_color + } + transformed_meals.push(meal_object); + } + meals_calendar_obj.removeAllEvents(); + meals_calendar_obj.addEventSource(transformed_meals); + return; } function clearMeals() { - const table = document.getElementById("meals-collapsible"); - while (table.firstChild) { - table.removeChild(table.firstChild); - } + meals_calendar_obj.removeAllEvents(); } function refreshMeals() { - clearMeals(); - loadMeals(); + clearMeals(); + loadMeals(); } function lopolisLogout() { - localforage.setItem("logged_in_lopolis", false); - $("#meals-collapsible").html(""); - checkLogin(); + localforage.setItem("logged_in_lopolis", false); + $("#meals-collapsible").html(""); + checkLogin(); } async function lopolisLogin() { - setLoading(true); - var usernameEl = $("#meals-username"); - var passwordEl = $("#meals-password"); - $.ajax({ - url: API_ENDPOINT+"gettoken", - crossDomain: true, - contentType: "application/json", - data: JSON.stringify({ - "username": usernameEl.val(), - "password": passwordEl.val() - }), - - dataType: "json", - cache: false, - type: "POST", - - success: async function(data) { - if(data == null) { - UIAlert( S("requestForAuthenticationFailed"), "lopolisLogin(): date is is null"); - setLoading(false); - usernameEl.val(""); - passwordEl.val(""); - } else if(data.error == true) { - UIAlert( S("loginFailed"), "lopolisLogin(): login failed. data.error is true"); - usernameEl.val(""); - passwordEl.val(""); - setLoading(false); - } else { - let promises_to_run = [ - localforage.setItem("logged_in_lopolis", true), - localforage.setItem("lopolis_username", usernameEl.val()), - localforage.setItem("lopolis_password", passwordEl.val()) - ]; - await Promise.all(promises_to_run); - checkLogin(); - UIAlert("Credential match!"); - } - }, - - error: () => { - UIAlert( D("loginError"), "lopolisLogin(): ajax.error"); - setLoading(false); - } - }); + setLoading(true); + var usernameEl = $("#meals-username"); + var passwordEl = $("#meals-password"); + $.ajax({ + url: API_ENDPOINT + "gettoken", + crossDomain: true, + contentType: "application/json", + data: JSON.stringify({ + "username": usernameEl.val(), + "password": passwordEl.val() + }), + + dataType: "json", + cache: false, + type: "POST", + + success: async function(data) { + if (data == null) { + UIAlert(S("requestForAuthenticationFailed"), "lopolisLogin(): date is is null"); + setLoading(false); + usernameEl.val(""); + passwordEl.val(""); + } else if (data.error == true) { + UIAlert(S("loginFailed"), "lopolisLogin(): login failed. data.error is true"); + usernameEl.val(""); + passwordEl.val(""); + setLoading(false); + } else { + let promises_to_run = [ + localforage.setItem("logged_in_lopolis", true), + localforage.setItem("lopolis_username", usernameEl.val()), + localforage.setItem("lopolis_password", passwordEl.val()) + ]; + await Promise.all(promises_to_run); + checkLogin(); + UIAlert("Credential match!"); + } + }, + + error: () => { + UIAlert(D("loginError"), "lopolisLogin(): ajax.error"); + setLoading(false); + } + }); } async function setMenus(currentmeals = 69, toBeSentChoices) { // currentmeals je getMenus response in vsebuje tudi token. - if (currentmeals === 69) { - getToken(getMenus, [setMenus, toBeSentChoices]); - return; - } - - for(const [mealzzdate, mealzz] of Object.entries(currentmeals.data)) { - if (mealzzdate in toBeSentChoices === false) { - for (const [mealid, mealdata] of Object.entries(mealzz.menu_options)) { - console.log(mealdata); - if(mealdata.selected == true || mealzz.readonly == true) { - toBeSentChoices[mealzzdate] = mealdata.value; - break; - } - } - } - } - - setLoading(true); - - $.ajax({ - url: API_ENDPOINT + "setmenus", - crossDomain: true, - contentType: "application/json", - data: JSON.stringify( { "choices": toBeSentChoices } ), - headers: { - "Authorization": "Bearer " + currentmeals.token - }, - dataType: "json", - cache: false, - type: "POST", - - success: (response) => { - if(response === null || response.error == true) { - UIAlert( D("errorSettingMeals"), "setMenus(): response error or null"); - } else if (response.error == false) { - UIAlert( D("mealSet"), "setMenus(): meni nastavljen"); - } else { - UIAlert( D("errorUnexpectedResponse"), "setMenus(): invalid response, no condition met"); - } - setLoading(false); - }, - - error: () => { - setLoading(false); - UIAlert( D("lopolisAPIConnectionError"), "setMenus(): AJAX error"); - } - }); + if (currentmeals === 69) { + getToken(getMenus, [setMenus, toBeSentChoices]); + return; + } + + for (const [mealzzdate, mealzz] of Object.entries(currentmeals.data)) { + if (mealzzdate in toBeSentChoices === false) { + for (const [mealid, mealdata] of Object.entries(mealzz.menu_options)) { + // console.log(mealdata); + if (mealdata.selected == true || mealzz.readonly == true) { + toBeSentChoices[mealzzdate] = mealdata.value; + break; + } + } + } + } + + setLoading(true); + + $.ajax({ + url: API_ENDPOINT + "setmenus", + crossDomain: true, + contentType: "application/json", + data: JSON.stringify({ + "choices": toBeSentChoices + }), + headers: { + "Authorization": "Bearer " + currentmeals.token + }, + dataType: "json", + cache: false, + type: "POST", + + success: (response) => { + if (response === null || response.error == true) { + UIAlert(D("errorSettingMeals"), "setMenus(): response error or null"); + } else if (response.error == false) { + UIAlert(D("mealSet"), "setMenus(): meni nastavljen"); + } else { + UIAlert(D("errorUnexpectedResponse"), "setMenus(): invalid response, no condition met"); + } + setLoading(false); + }, + + error: () => { + setLoading(false); + UIAlert(D("lopolisAPIConnectionError"), "setMenus(): AJAX error"); + } + }); } async function setMenu(date, menu) { - let choice = {}; - choice[date] = menu; - getToken(getMenus, [setMenus, choice]); + let choice = {}; + choice[date] = menu; + getToken(getMenus, [setMenus, choice]); } function setupEventListeners() { - $("#meals-login").click(() => { - lopolisLogin(); - }); + $("#meals-login").click(() => { + lopolisLogin(); + }); + + $("#meals-logout").click(() => { + lopolisLogout(); + }); +} - $("#meals-logout").click(() => { - lopolisLogout(); - }); +var mealClickHandler = (eventClickInfo) => { + // console.log("meal clicked!"); // debug + let meal_date = eventClickInfo.event.id; + let meal_object = meals_data_global[meal_date]; + $("#meal-type").text(meal_object.meal); + let meal_date_obj = new Date(meal_date); + $("#meal-date").text(dateString.longFormatted(meal_date_obj)); + if(!(meal_object.readonly)) { // če je beljiv + document.getElementById("meal-readonly").style.display="none"; + } else { + document.getElementById("meal-readonly").style.display="block"; + } + document.getElementById("meal-options").innerHTML = ""; + for(const [option_index, option_object] of Object.entries(meal_object.menu_options)) { + let menu_option_li_el = document.createElement("li"); + let menu_option_a_el = document.createElement("a"); + menu_option_a_el.innerText = option_object.text; + // console.log(JSON.stringify(meal_object)); // debug + if (option_object.selected != null) { + if(option_object.selected) { + // console.log("selected"); // debug + // + menu_option_a_el.className = "selected-meal"; + } + } + menu_option_a_el.style = "waves-effect"; + menu_option_a_el.id = "menu_index_"+option_index; + if(!(meal_object.readonly)) { + menu_option_a_el.onclick = () => { + setMenu(meal_date, option_object.value); + menu_option_a_el.className = "to-be-selected-meal"; + let sidenav_element = document.getElementById("meal-info"); + let sidenav_instance = M.Sidenav.getInstance(sidenav_element); + sidenav_instance.close(); + }; + } + menu_option_li_el.appendChild(menu_option_a_el); + document.getElementById("meal-options").appendChild(menu_option_li_el); + } + let sidenav_element = document.getElementById("meal-info"); + let sidenav_instance = M.Sidenav.getInstance(sidenav_element); + sidenav_instance.open(); } // Initialization code document.addEventListener("DOMContentLoaded", async () => { - checkLogin(); - - setupEventListeners(); - - let coll_elem = document.querySelectorAll('.collapsible'); - M.Collapsible.init(coll_elem, {}); - - // Setup refresh handler - $("#refresh-icon").click(function () { - refreshMeals(); - }); - - let elems = document.querySelectorAll('.modal'); - M.Modal.init(elems, {}); - // Setup side menu - const menus = document.querySelectorAll('.side-menu'); - M.Sidenav.init(menus, { edge: 'right', draggable: true }); - - // Setup side modal - const modals = document.querySelectorAll('.side-modal'); - M.Sidenav.init(modals, { edge: 'left', draggable: false }); - - var elemsx = document.querySelectorAll('select'); - M.FormSelect.init(elemsx); - - var datepickerelems = document.querySelectorAll('.datepicker'); - var today = new Date(); - M.Datepicker.init(datepickerelems, { - firstDay: 1, - minDate: today, - showDaysInNextAndPreviousMonths: true, - showClearBtn: true, - format: "dddd, dd. mmmm yyyy" - }); - - refreshMeals(); + checkLogin(); + + var calendarEl = document.getElementById("meals-calendar"); + meals_calendar_obj = new FullCalendar.Calendar(calendarEl, { + firstDay: 1, + plugins: ["dayGrid"], + defaultDate: getDateString(), + navLinks: false, + editable: false, + events: [], + eventClick: mealClickHandler, + height: "parent" + }); + meals_calendar_obj.render(); + + + setupEventListeners(); + + // Setup refresh handler + $("#refresh-icon").click(function() { + refreshMeals(); + }); + + // Setup side menu + const menus = document.querySelectorAll('.side-menu'); + M.Sidenav.init(menus, { + edge: 'right', + draggable: true + }); + + // Setup side modal + const modals = document.querySelectorAll('.side-modal'); + M.Sidenav.init(modals, { + edge: 'left', + draggable: false + }); + + // ne vem, kaj je to spodaj ˇˇˇ + var elemsx = document.querySelectorAll('select'); + M.FormSelect.init(elemsx); + + var datepickerelems = document.querySelectorAll('.datepicker'); + var today = new Date(); + M.Datepicker.init(datepickerelems, { + firstDay: 1, + minDate: today, + showDaysInNextAndPreviousMonths: true, + showClearBtn: true, + format: "dddd, dd. mmmm yyyy" + }); + + refreshMeals(); }); diff --git a/assets/pages-src/gradings.bvr b/assets/pages-src/gradings.bvr index 20a2d39..9a18dae 100644 --- a/assets/pages-src/gradings.bvr +++ b/assets/pages-src/gradings.bvr @@ -1,89 +1,90 @@ <@?i global@> - - - - - - - - Gradings « BežiApp + + - - - - + + + + + + Gradings « BežiApp - - + + + + - - - - - - + + - - - + + + + + + - - - - - + + + - - - - - + + + + + - - + + + + + + + + <@?i navigation@> - + -
-
-
-
+
+
+
+
- - + - <@?i grading-add-modal@> + <@?i grading-add-modal@> - + - + \ No newline at end of file diff --git a/assets/pages-src/meals.bvr b/assets/pages-src/meals.bvr index 0621766..cdfd7d1 100644 --- a/assets/pages-src/meals.bvr +++ b/assets/pages-src/meals.bvr @@ -1,116 +1,158 @@ <@?i global@> - - - - - - - - Meals « BežiApp + + - - - - + + + + + + Meals « BežiApp - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - + + <@?i navigation@> -
- - - + +
+ + + - + -- cgit v1.2.3 From bd565dab653a0e10e083d5b1a2898d00a1f6a4da Mon Sep 17 00:00:00 2001 From: sijanec Date: Wed, 23 Sep 2020 19:02:16 +0200 Subject: =?UTF-8?q?skril=20today=20button=20in=20popravil=20barvo=20today?= =?UTF-8?q?=20dneva=20na=20\!important=20na=C4=8Din?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/css/styles.css | 3 +-- assets/js/meals.js | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) (limited to 'assets') diff --git a/assets/css/styles.css b/assets/css/styles.css index a363449..06da571 100644 --- a/assets/css/styles.css +++ b/assets/css/styles.css @@ -134,9 +134,8 @@ a.collection-item { } .fc-unthemed td.fc-today { - background: var(--color-primary-transparent); + background: var(--color-primary-transparent) !important; } - .fc-icon { color: var(--color-text); } diff --git a/assets/js/meals.js b/assets/js/meals.js index bf9a48e..d614153 100644 --- a/assets/js/meals.js +++ b/assets/js/meals.js @@ -391,7 +391,7 @@ document.addEventListener("DOMContentLoaded", async () => { edge: 'left', draggable: false }); - + document.getElementsByClassName("fc-today-button")[0].style = "display:none !important"; // počasi bomo rabili nestane important stavke // ne vem, kaj je to spodaj ˇˇˇ var elemsx = document.querySelectorAll('select'); M.FormSelect.init(elemsx); -- cgit v1.2.3 From 47c8dd3a9874dcaf505abcea61cd550a2e87c816 Mon Sep 17 00:00:00 2001 From: sijanec Date: Wed, 23 Sep 2020 19:48:48 +0200 Subject: ugly hack --- assets/js/meals.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'assets') diff --git a/assets/js/meals.js b/assets/js/meals.js index d614153..49d787d 100644 --- a/assets/js/meals.js +++ b/assets/js/meals.js @@ -324,7 +324,7 @@ var mealClickHandler = (eventClickInfo) => { document.getElementById("meal-options").innerHTML = ""; for(const [option_index, option_object] of Object.entries(meal_object.menu_options)) { let menu_option_li_el = document.createElement("li"); - let menu_option_a_el = document.createElement("a"); + let menu_option_a_el = document.createElement("button"); menu_option_a_el.innerText = option_object.text; // console.log(JSON.stringify(meal_object)); // debug if (option_object.selected != null) { @@ -334,7 +334,8 @@ var mealClickHandler = (eventClickInfo) => { menu_option_a_el.className = "selected-meal"; } } - menu_option_a_el.style = "waves-effect"; + menu_option_a_el.classList = "waves-effect waves-light btn-large"; + menu_option_a_el.style = "color: var(--color-text); background-color: rgba(0,0,0,0); line-height: 1.2; height:auto !important;"; menu_option_a_el.id = "menu_index_"+option_index; if(!(meal_object.readonly)) { menu_option_a_el.onclick = () => { -- cgit v1.2.3 From 9887173427b46fc860438440c7f0139e1f68419f Mon Sep 17 00:00:00 2001 From: sijanec Date: Wed, 23 Sep 2020 20:01:42 +0200 Subject: final fixes and minification --- assets/js/meals.js | 5 +- assets/pages-src/changelog.bvr | 305 +++++++++++++++++++++-------------------- assets/pages-src/meals.bvr | 2 +- 3 files changed, 162 insertions(+), 150 deletions(-) (limited to 'assets') diff --git a/assets/js/meals.js b/assets/js/meals.js index 49d787d..15accdb 100644 --- a/assets/js/meals.js +++ b/assets/js/meals.js @@ -327,15 +327,16 @@ var mealClickHandler = (eventClickInfo) => { let menu_option_a_el = document.createElement("button"); menu_option_a_el.innerText = option_object.text; // console.log(JSON.stringify(meal_object)); // debug + let classlist = ""; if (option_object.selected != null) { if(option_object.selected) { // console.log("selected"); // debug // - menu_option_a_el.className = "selected-meal"; + classlist = "color: green; font-weight: bold"; } } menu_option_a_el.classList = "waves-effect waves-light btn-large"; - menu_option_a_el.style = "color: var(--color-text); background-color: rgba(0,0,0,0); line-height: 1.2; height:auto !important;"; + menu_option_a_el.style = "color: var(--color-text); background-color: rgba(0,0,0,0); line-height: 1.2; height:auto; "+classlist+" !important"; menu_option_a_el.id = "menu_index_"+option_index; if(!(meal_object.readonly)) { menu_option_a_el.onclick = () => { diff --git a/assets/pages-src/changelog.bvr b/assets/pages-src/changelog.bvr index 870b446..56b883c 100644 --- a/assets/pages-src/changelog.bvr +++ b/assets/pages-src/changelog.bvr @@ -1,148 +1,159 @@ <@?i global@> - - - - - - - - Changelog « BežiApp - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-

changelog

-
    -
  • -
    Version 1.0.14-beta
    -
    -
      -
    • Added themes (dark, light, night, defaulting to light)
    • -
    • "Additional options" (before anyone gets triggered, their order is random)
    • -
    • New messaging look
    • -
    • Messaging has been overhauled, now better then ever. Powered by GimSIS™
    • -
    • Added videoconferencing (via video.gimb)
    • -
    • More network requests go directly to GimSISExt (with gsec.js)
    • -
    • Many bug fixes
    • -
    -
    - -
    Version 1.0.13-beta
    -
    -
      -
    • Various bug fixes
    • -
    -
    - -
    Version 1.0.12-beta
    -
    -
      -
    • Now update does not clear whole database => you stay logged in
    • -
    • Added multilanguage support
    • -
    -
    - -
    Version 1.0.11-beta
    -
    -
      -
    • Added end-to-end encrypton support to messaging
    • -
    -
    - -
    Version 1.0.10-beta
    -
    -
      -
    • Added meals
    • -
    -
    - -
    Version 1.0.9-beta
    -
    -
      -
    • Added image sending support to messaging
    • -
    -
    - -
    Version 1.0.8-beta
    -
    -
      -
    • Added messaging
    • -
    -
    - -
    Version 1.0.7-beta
    -
    -
      -
    • Enter key now triggers login in the login form
    • -
    -
    - -
    Version 1.0.6-beta
    -
    -
      -
    • Fixed a typo (related to #5)
    • -
    -
    - -
    Version 1.0.5-beta
    -
    -
      -
    • Fixed default view on startup
    • -
    • Changed promise handling in gradings (#5)
    • -
    -
    - -
    Version 1.0.4-beta
    -
    -
      -
    • Introduced the changelog
    • -
    • Fixed date picker color
    • -
    • Changed weekly timetable accent color
    • -
    • Option for including/excluding temporary grades in average - calculation (credit: Tinkara)
    • -
    -
    -
  • -
-
-
- - - - + + + + + + + + + Changelog « BežiApp + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ changelog +

+
    +
  • +
    Version 1.0.15-beta
    +
    +
      +
    • Changed meals layout
    • +
    +
    + + +
    Version 1.0.14-beta
    +
    +
      +
    • Added themes (dark, light, night, defaulting to light)
    • +
    • "Additional options" (before anyone gets triggered, their order is random)
    • +
    • New messaging look
    • +
    • Messaging has been overhauled, now better then ever. Powered by GimSIS™
    • +
    • Added videoconferencing (via video.gimb)
    • +
    • More network requests go directly to GimSISExt (with gsec.js)
    • +
    • Many bug fixes
    • +
    +
    + +
    Version 1.0.13-beta
    +
    +
      +
    • Various bug fixes
    • +
    +
    + +
    Version 1.0.12-beta
    +
    +
      +
    • Now update does not clear whole database => you stay logged in
    • +
    • Added multilanguage support
    • +
    +
    + +
    Version 1.0.11-beta
    +
    +
      +
    • Added end-to-end encrypton support to messaging
    • +
    +
    + +
    Version 1.0.10-beta
    +
    +
      +
    • Added meals
    • +
    +
    + +
    Version 1.0.9-beta
    +
    +
      +
    • Added image sending support to messaging
    • +
    +
    + +
    Version 1.0.8-beta
    +
    +
      +
    • Added messaging
    • +
    +
    + +
    Version 1.0.7-beta
    +
    +
      +
    • Enter key now triggers login in the login form
    • +
    +
    + +
    Version 1.0.6-beta
    +
    +
      +
    • Fixed a typo (related to #5)
    • +
    +
    + +
    Version 1.0.5-beta
    +
    +
      +
    • Fixed default view on startup
    • +
    • Changed promise handling in gradings (#5)
    • +
    +
    + +
    Version 1.0.4-beta
    +
    +
      +
    • Introduced the changelog
    • +
    • Fixed date picker color
    • +
    • Changed weekly timetable accent color
    • +
    • Option for including/excluding temporary grades in average + calculation (credit: Tinkara)
    • +
    +
    +
  • +
+
+
+ + + + diff --git a/assets/pages-src/meals.bvr b/assets/pages-src/meals.bvr index cdfd7d1..c0d655b 100644 --- a/assets/pages-src/meals.bvr +++ b/assets/pages-src/meals.bvr @@ -120,7 +120,7 @@