EdJoPaTo / grammy-inline-menu

Inline Menus for Telegram made simple. Successor of telegraf-inline-menu.
MIT License
357 stars 44 forks source link

Menu whose state would be reset on entering #110

Closed kentnek closed 4 years ago

kentnek commented 4 years ago

Hi again, I'm trying to implement an inline "Cancel" button with confirmation, which will change to "Really cancel?" on first tap. On second tap, the actual cancel action is performed.

This is what I came up with so far:

let isCancelling = false;
// ...

menu.toggle('', 'cancel', {
  formatState: (context, text, state) => state ? "Really cancel?" : "Cancel",
  isSet: () => isCancelling,
  set: async (ctx, newState) => {
    isCancelling = newState;

    if (isCancelling) { // false -> true
      return true; // just update the button with the new state
    } else { // true -> false
      await ctx.answerCbQuery("Cancelling..");
      return ".."; // after cancelling, go back to the previous menu
    }
  }
});

menu.manualRow(backButtons);

This works well, except when the user taps the button only once (changing it to "Really cancel?") then taps "Back". Later, when this menu is entered again, isCancelling is still true, so the button shows "Really cancel?" instead of "Cancel".

How do I reset this menu's state upon entering? Thanks!

EdJoPaTo commented 4 years ago

How about a submenu for the cancel? Once you enter you have two possibles: 'Yes' which leads to the menu above and 'no' which leads back to the menu where the user is currently making changes.

That way the the state of isCanceling is embedded into the menu structure.

Maybe something like that:

const reallyCancelMenu = new MenuTemplate('Are you sure to cancel?')
// If you need to cleanup something you can also go for interact and return '../..'
reallyCancelMenu.navigate('Yes!', '../..')
reallyCancelMenu.navigate('No.', '..')

menu.submenu('Cancel', 'cancel', reallyCancelMenu)
kentnek commented 4 years ago

Yeah thanks for the suggestion, and I had to resort to that for now. However this won't be feasible when the menu containing the "Cancel" button is more complicated later on. I guess the way this library is currently structured won't allow an easy way to define a stateful menu.

EdJoPaTo commented 4 years ago

The idea of this library is to create a stateless menu with all the state either in telegram or from where the functions return it from but not inside the library. Also when you use a library for something this will always be the case: Some things are just easier without a generic approach.

Despite this, from the callbackQuery.data you cant know if someone is entering the menu just now or return to it from a submenu. Its the same for both ways. So you will need additional logic to determine this.

You can check a timer and reset some state after some time or check when other menus are used:

bot.action('/parentMenu/', ctx => { outside = true })
bot.action(/\/parentMenu\/path\/.+/, ctx => { outside = false })