Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
dc0bfea
Add action menu to meals
Frylen Jul 18, 2025
5b2c866
Add action to set the timestamp for items in a meal category
Frylen Jul 18, 2025
39e0aef
Add string to locale
Frylen Jul 18, 2025
148d324
Move action menu button next to add button
Frylen Jul 19, 2025
1fd9608
Add quick add to action sheet
Frylen Jul 19, 2025
271c232
Fix quick add action calling wrong method
Frylen Jul 19, 2025
ace879f
Only show valid actions in action menu
Frylen Jul 19, 2025
23e3b97
Fix action not finding the item in the DB entry
Frylen Jul 19, 2025
de8820a
Place action menu button inside its own div
Frylen Jul 19, 2025
49eb8fd
Add horizontal margin to action menu button
Frylen Jul 19, 2025
1d17742
Use click event instead of touchstart
Frylen Jul 19, 2025
7544cb3
Handle "auto" locale in timestamps
Frylen Jul 19, 2025
8e834af
Add backdrop to smartSelect and picker
Frylen Jul 19, 2025
91fe85b
Add cancel button to meal selection
Frylen Jul 21, 2025
b90e5fd
Fix pre-selection of meal category
Frylen Jul 21, 2025
4881700
Use F7 time picker in calendar component
Frylen Jul 21, 2025
7f58695
Use F7 method to force open the time picker on calendar open
Frylen Jul 21, 2025
1ce2e4c
Force LTR layout for time picker columns
Frylen Jul 21, 2025
25f2699
Add OS back button support for smart select sheet
Frylen Jul 21, 2025
3750ea7
Preserve diary entry date in time picker
Frylen Jul 22, 2025
4dacd29
Fix scroll events on columns in RTL languages
Frylen Jul 22, 2025
d3653e0
Only update meal category on confirmation
Frylen Jul 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
294 changes: 291 additions & 3 deletions www/activities/diary/js/group.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,291 @@ app.Group = {

let nutrition = await app.FoodsMealsRecipes.getTotalNutrition(this.items, "subtract");

app.Group.renderFooter(ul, this.id, nutrition);
app.Group.renderFooter(self, ul, this.id, nutrition);
},

renderFooter: function(ul, id, nutrition) {
addActionMenu: function(self, buttonsDiv) {
let actionMenuDiv = document.createElement("div");
actionMenuDiv.className = "action-menu margin-horizontal-half";
buttonsDiv.appendChild(actionMenuDiv);

let iconAnchor = document.createElement("a");
actionMenuDiv.appendChild(iconAnchor);

let icon = document.createElement("i");
icon.className = "icon material-icons";
icon.textContent = "more_horiz";
iconAnchor.appendChild(icon);

actionMenuDiv.addEventListener("click", function(e) {
e.preventDefault();
app.Group.openActionMenu(self);
});
},

openActionMenu: function(self) {
let isGroupEmpty = self.items.length == 0;
let actions = [{
name: app.strings.diary["quick-add"] || "Quick Add",
callback: app.Group.quickAddAction,
disabled: false
}, {
name: app.strings.dialogs["clear-group-items"] || "Remove all items from meal",
callback: app.Group.clearGroupItems,
disabled: isGroupEmpty
}, {
name: app.strings.dialogs["move-group-items"] || "Move items to another meal",
callback: app.Group.moveGroupItems,
disabled: isGroupEmpty
}
];

if (app.Settings.get("diary", "timestamps") == true) {
actions.push({
name: app.strings.dialogs["set-meal-time"] || "Update timestamps",
callback: app.Group.setGroupTimestamps,
disabled: isGroupEmpty
});
}

let options = [];
actions.filter(action => action.disabled == false).forEach((action) => {
options.push({
text: action.name,
onClick: () => { action.callback(self) }
});
});

let actionMenu = app.f7.actions.create({
buttons: options,
closeOnEscape: true,
animate: !app.Settings.get("appearance", "animations")
});

actionMenu.open();
},

quickAddAction: function(self) {
// wrapping quick add call since actions only receive `self`
app.Diary.quickAdd(self.id);
},

clearGroupItems: function(self) {
let title = app.strings.dialogs["clear-meal-items"] || "Remove all items";
let text = app.strings.dialogs["confirm"] || "Are you sure?";

let confirmDialog = app.f7.dialog.create({
title: title,
content: app.Utils.getDialogTextDiv(text),
buttons: [{
text: app.strings.dialogs.cancel || "Cancel",
keyCodes: app.Utils.escapeKeyCode
},
{
text: app.strings.dialogs.yes || "Yes",
keyCodes: app.Utils.enterKeyCode,
onClick: async () => {
let entry = await app.Diary.getEntryFromDB();
if (entry === undefined) {
return;
}

entry.items = entry.items.filter(item => item.category != self.id);

await dbHandler.put(entry, "diary");
let scrollPosition = { position: $(".page-current .page-content").scrollTop() };
app.Diary.render(scrollPosition);
}
}]
})

confirmDialog.open();
},

moveGroupItems: function(self) {
let mealNames = app.Settings.get("diary", "meal-names") || [];
if (mealNames.length == 0) {
return;
}

let smartSelect = app.Group.createSelectStructure(self, mealNames);
let hasUserConfirmedSelection = false;
let mealSelection = app.f7.smartSelect.create({
el: smartSelect,
openIn: "sheet",
sheetBackdrop: true,
sheetCloseLinkText: app.strings.dialogs["ok"] || "OK",
on: {
open: (selection) => {
let smartSelectContainers = selection.$containerEl[0];
let leftToolbar = smartSelectContainers.querySelector(".left");
if (leftToolbar != null) {
let cancelButton = document.createElement("a");
cancelButton.className = "link sheet-close";
cancelButton.innerText = app.strings.dialogs["cancel"] || "Cancel";
leftToolbar.appendChild(cancelButton);

leftToolbar.addEventListener("click", () => {
selection.close();
});
}

let rightToolbar = smartSelectContainers.querySelector(".right");
if (leftToolbar != null) {
rightToolbar.addEventListener("click", () => {
hasUserConfirmedSelection = true;
selection.close();
});
}
},
closed: (selection) => {
let selectedMealIndex = selection.$selectEl.val();
selection.destroy();
smartSelect.remove();

if (selectedMealIndex != self.id && hasUserConfirmedSelection == true) {
app.Group.updateItemGroup(self, selectedMealIndex);
}
}
}
});

mealSelection.open();
},

createSelectStructure: function(self, mealNames) {
let span = document.createElement("span");
span.className = "item-link smart-select"

let select = document.createElement("select");
select.className = "group-select";
span.appendChild(select);

mealNames.forEach((mealName, index) => {
if(mealName == null || mealName == "") {
return;
}

let option = document.createElement("option");
option.value = index;
option.innerText = app.strings.diary["default-meals"][mealName.toLowerCase()] || mealName;
if (index == self.id) {
option.setAttribute("selected", "")
};
select.appendChild(option);
});

document.body.appendChild(span);
return span;
},

updateItemGroup: async function(self, targetMealIndex) {
let entry = await app.Diary.getEntryFromDB();
if (entry === undefined) {
return;
}

entry.items.forEach(item => {
if (item.category == self.id) {
item.category = targetMealIndex;
}
});

await dbHandler.put(entry, "diary");
let scrollPosition = { position: $(".page-current .page-content").scrollTop() };
app.Diary.render(scrollPosition);
},

setGroupTimestamps: function(self) {
let locale = app.Settings.get("appearance", "locale");
if (locale == "auto") {
locale = app.getLanguage();
}

let mealDate = app.Group.getEntryDateWithCurrentTime();
let hasUserConfirmedSelection = false;
let timePicker = app.f7.calendar.create({
locale: locale,
backdrop: true,
animate: !app.Settings.get("appearance", "animations"),
timePicker: true,
value: [mealDate],
on: {
open: (calendar) => {
// force the time picker to open to hide the calender
calendar.openTimePicker();

let timePicker = document.querySelector(".calendar-time-picker");
let left = timePicker.querySelector(".left");
let cancelButton = document.createElement("a");
cancelButton.innerText = app.strings.dialogs["cancel"] || "Cancel";
cancelButton.className = "link calendar-time-picker-close"
left.appendChild(cancelButton);
left.addEventListener("click", () => {
calendar.closeTimePicker();
calendar.close();
});

let okButton = timePicker.querySelector(".right .link.calendar-time-picker-close");
okButton.innerText = app.strings.dialogs["ok"] || "OK";
okButton.parentElement.addEventListener("click", () => {
hasUserConfirmedSelection = true;
calendar.closeTimePicker();
calendar.close();
});

if (calendar.inverter == -1) {
// app is in rtl mode, we still need to display time in ltr
let columnWrapper = timePicker.querySelector(".picker-columns");
columnWrapper.style.direction = "ltr";

// change the last and first class, so the scroll events get passed to the correct column
let columns = timePicker.querySelectorAll(".picker-column");
columns[0].className = "picker-column picker-column-last";
columns[columns.length - 1].className = "picker-column picker-column-first";
}
},
close: (calendar) => {
if (hasUserConfirmedSelection == true) {
app.Group.updateTimestamps(self, calendar.value[0]);
}
}
}
});

timePicker.open();
},

getEntryDateWithCurrentTime: function() {
if (app.Diary == null || app.Diary.date == null) {
return new Date();
}

let entryDate = app.Diary.date;
let currentTime = new Date();
currentTime.setFullYear(entryDate.getFullYear());
currentTime.setMonth(entryDate.getMonth());
currentTime.setDate(entryDate.getDate());
return currentTime;
},

updateTimestamps: async function(self, newDate) {
let entry = await app.Diary.getEntryFromDB();
if (entry === undefined) {
return;
}

let mealItems = entry.items.filter(item => item.category == self.id);
mealItems.forEach(item => {
item.dateTime = newDate;
});

await dbHandler.put(entry, "diary");
let scrollPosition = { position: $(".page-current .page-content").scrollTop() };
app.Diary.render(scrollPosition);
},

renderFooter: function(self, ul, id, nutrition) {

let li = document.createElement("li");
li.className = "noselect";
Expand All @@ -94,11 +375,15 @@ app.Group = {
row.className = "row item-content";
li.appendChild(row);

let buttons = document.createElement("div");
buttons.className = "display-inline-flex";
row.appendChild(buttons);

//Add button
let left = document.createElement("div");
left.className = "add-button";
left.id = "add-button-" + id;
row.appendChild(left);
buttons.appendChild(left);

let a = document.createElement("a");
left.addEventListener("click", function(e) {
Expand All @@ -116,6 +401,9 @@ app.Group = {
icon.innerText = "add";
a.appendChild(icon);

// Action Menu
app.Group.addActionMenu(self, buttons);

//Energy
const energyUnit = app.Settings.get("units", "energy");
const energyName = app.Utils.getEnergyUnitName(energyUnit);
Expand Down
5 changes: 4 additions & 1 deletion www/assets/locales/locale-en.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,10 @@
"upload-failed": "Unfortunately the upload failed. Please try again or contact the developer.",
"what-meal": "What meal is this?",
"press-back-again-exit": "Press Back again to exit the app",
"press-back-again-editor": "Press Back again to leave the editor"
"press-back-again-editor": "Press Back again to leave the editor",
"clear-meal-items": "Remove all items from meal",
"move-meal-items": "Move items to another meal",
"set-meal-time": "Update timestamps"
},
"days": {
"0": "Sunday",
Expand Down
2 changes: 1 addition & 1 deletion www/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,7 @@ document.addEventListener("backbutton", (e) => {
return false;
}

let smartSelects = document.querySelectorAll(".smart-select-popover");
let smartSelects = document.querySelectorAll(".smart-select-popover,.smart-select-sheet");
if (smartSelects.length) {
document.querySelectorAll(".smart-select").forEach((el) => { app.f7.smartSelect.close(el) });
return false;
Expand Down