From fe80028c07e5453679fc49a356c87b4b3ea57490 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Mon, 1 Feb 2021 11:57:12 -0600 Subject: [PATCH] Add support for "nested" menu items some menus were just getting too long, so this gives us a way to collapse certain items, which user can expand as needed --- tailbone/menus.py | 131 +++++++++++++----- tailbone/static/themes/falafel/css/layout.css | 5 + tailbone/templates/themes/falafel/base.mako | 41 +++++- 3 files changed, 139 insertions(+), 38 deletions(-) diff --git a/tailbone/menus.py b/tailbone/menus.py index f28574bf..2402e768 100644 --- a/tailbone/menus.py +++ b/tailbone/menus.py @@ -2,7 +2,7 @@ ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2018 Lance Edgar +# Copyright © 2010-2021 Lance Edgar # # This file is part of Rattail. # @@ -33,6 +33,7 @@ from rattail.util import import_module_path class MenuGroup(Object): title = None items = None + is_menu = True is_link = False @@ -41,10 +42,19 @@ class MenuItem(Object): url = None target = None is_link = True + is_menu = False + is_sep = False + + +class MenuItemMenu(Object): + title = None + items = None + is_menu = True is_sep = False class MenuSeparator(object): + is_menu = False is_sep = True @@ -61,55 +71,108 @@ def make_simple_menus(request): # collect "simple" menus definition, but must refine that somewhat to # produce our final menus raw_menus = menus_module.simple_menus(request) + mark_allowed(request, raw_menus) final_menus = [] for topitem in raw_menus: - if topitem.get('type') == 'link': - final_menus.append( - MenuItem(title=topitem['title'], - url=topitem['url'], - target=topitem.get('target'))) + if topitem['allowed']: - else: # assuming 'menu' type + if topitem.get('type') == 'link': + final_menus.append(make_menu_entry(topitem)) - # figure out which ones the user has permission to access - allowed = [] - for item in topitem['items']: + else: # assuming 'menu' type - if item.get('type') == 'sep': - allowed.append(item) - - if item.get('perm'): - if request.has_perm(item['perm']): - allowed.append(item) - else: - allowed.append(item) - - if allowed: - - # user must have access to something; construct items for the menu menu_items = [] - for item in allowed: + for item in topitem['items']: + if not item['allowed']: + continue - # separator - if item.get('type') == 'sep': + # nested submenu + if item.get('type') == 'menu': + submenu_items = [] + for subitem in item['items']: + if subitem['allowed']: + submenu_items.append(make_menu_entry(subitem)) + menu_items.append(MenuItemMenu( + title=item['title'], + items=submenu_items)) + + elif item.get('type') == 'sep': + # we only want to add a sep, *if* we already have some + # menu items (i.e. there is something to separate) + # *and* the last menu item is not a sep (avoid doubles) if menu_items and not menu_items[-1].is_sep: - menu_items.append(MenuSeparator()) + menu_items.append(make_menu_entry(item)) - # menu item - else: - menu_items.append( - MenuItem(title=item['title'], - url=item['url'], - target=item.get('target'))) + else: # standard menu item + menu_items.append(make_menu_entry(item)) # remove final separator if present if menu_items and menu_items[-1].is_sep: menu_items.pop() # only add if we wound up with something + assert menu_items if menu_items: - final_menus.append( - MenuGroup(title=topitem['title'], items=menu_items)) + final_menus.append(MenuGroup( + title=topitem['title'], + items=menu_items)) return final_menus + + +def make_menu_entry(item): + """ + Convert a simple menu entry dict, into a proper menu-related object, for + use in constructing final menu. + """ + # separator + if item.get('type') == 'sep': + return MenuSeparator() + + # standard menu item + return MenuItem( + title=item['title'], + url=item['url'], + target=item.get('target')) + + +def is_allowed(request, item): + """ + Logic to determine if a given menu item is "allowed" for current user. + """ + perm = item.get('perm') + if perm: + return request.has_perm(perm) + return True + + +def mark_allowed(request, menus): + """ + Traverse the menu set, and mark each item as "allowed" (or not) based on + current user permissions. + """ + for topitem in menus: + + if topitem.get('type', 'menu') == 'menu': + topitem['allowed'] = False + + for item in topitem['items']: + + if item.get('type') == 'menu': + for subitem in item['items']: + subitem['allowed'] = is_allowed(request, subitem) + + item['allowed'] = False + for subitem in item['items']: + if subitem['allowed'] and subitem.get('type') != 'sep': + item['allowed'] = True + break + + else: + item['allowed'] = is_allowed(request, item) + + for item in topitem['items']: + if item['allowed'] and item.get('type') != 'sep': + topitem['allowed'] = True + break diff --git a/tailbone/static/themes/falafel/css/layout.css b/tailbone/static/themes/falafel/css/layout.css index b4fdccec..20fcf36e 100644 --- a/tailbone/static/themes/falafel/css/layout.css +++ b/tailbone/static/themes/falafel/css/layout.css @@ -46,6 +46,11 @@ header .level-left .global-title { font-weight: bold; } +/* indent nested menu items a bit */ +header .navbar-item.nested { + padding-left: 2.5rem; +} + header .level #current-context, header .level-left #current-context { font-size: 2em; diff --git a/tailbone/templates/themes/falafel/base.mako b/tailbone/templates/themes/falafel/base.mako index b3e19fd8..e4996a27 100644 --- a/tailbone/templates/themes/falafel/base.mako +++ b/tailbone/templates/themes/falafel/base.mako @@ -198,11 +198,28 @@