mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-01-21 07:55:00 +00:00
ui: tags: Add tag search/filter for system page
Tests: - In apps page, notice that all the tags are displayed as expected. - Inside an app from apps sections, clicking on an tag shows the apps with that tag filtered. - Clicking on the search bar shows the list of all tags. - Clicking on tag from search list adds that tag to the search list. - Labels are shown properly in the search bar. - Clicking on label removes it from search. - Search results are sorted based on the number of matches. - Clicking on the close button the tags search input removes filtering. - All the above tests work for systems page with systems app. Sections are shown even when apps are filtered by tags. Sections without results are not shown. Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org> Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
parent
9555697140
commit
a5ab31c1af
@ -6,6 +6,10 @@
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_js %}
|
||||
<script type="text/javascript" src="{% static 'tags.js' %}" defer></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block body_class %}system-page{% endblock %}
|
||||
|
||||
{% block container %}
|
||||
@ -17,12 +21,16 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% block tags %}
|
||||
{% include "tags.html" %}
|
||||
{% endblock %}
|
||||
|
||||
<div class="container card-container">
|
||||
{% for section_item in menu_items %}
|
||||
<div class="system-section-title">{{ section_item.name }}</div>
|
||||
<div class="row">
|
||||
<div class="card-list card-list-primary">
|
||||
{% for item in section_item.sorted_items %}
|
||||
{% for item in section_item.items %}
|
||||
{% if advanced_mode or not item.advanced %}
|
||||
{% include "card.html" %}
|
||||
{% endif %}
|
||||
|
||||
@ -174,13 +174,30 @@ def index(request):
|
||||
def _pick_menu_items(menu_items, selected_tags):
|
||||
"""Return a sorted list of menu items filtered by tags."""
|
||||
|
||||
class MenuProxy:
|
||||
"""A proxy for the menu item to hold filtered children."""
|
||||
|
||||
def __init__(self, menu_item: menu.Menu):
|
||||
"""Initialize a menu proxy object."""
|
||||
self.menu_item = menu_item
|
||||
self.items: list[menu.Menu] = []
|
||||
tags = menu_item.tags or []
|
||||
for item in menu_item.items:
|
||||
tags += item.tags or []
|
||||
|
||||
self.tags = list(tags)
|
||||
|
||||
def __getattr__(self, name: str):
|
||||
"""Return attributed from proxied object."""
|
||||
return getattr(self.menu_item, name)
|
||||
|
||||
def _mismatch_map(menu_item) -> list[bool]:
|
||||
"""Return a list of mismatches for selected tags.
|
||||
|
||||
A mismatch is when a selected tag is *not* present in the list of
|
||||
tags for menu item.
|
||||
"""
|
||||
menu_tags = set(menu_item.tags)
|
||||
menu_tags = set(menu_item.tags or [])
|
||||
return [tag not in menu_tags for tag in selected_tags]
|
||||
|
||||
def _sort_key(menu_item):
|
||||
@ -194,17 +211,39 @@ def _pick_menu_items(menu_items, selected_tags):
|
||||
return (_mismatch_map(menu_item).count(True), _mismatch_map(menu_item),
|
||||
menu_item.order, menu_item.name.lower())
|
||||
|
||||
proxied_menu_items = []
|
||||
for menu_item in menu_items:
|
||||
proxied_item = MenuProxy(menu_item)
|
||||
proxied_item.items = _pick_menu_items(menu_item.items, selected_tags)
|
||||
proxied_menu_items.append(proxied_item)
|
||||
|
||||
# Filter out menu items that don't match any of the selected tags. If
|
||||
# no tags are selected, return all menu items. Otherwise, return all
|
||||
# menu items that have at least one matching tag.
|
||||
filtered_menu_items = [
|
||||
menu_item for menu_item in menu_items
|
||||
menu_item for menu_item in proxied_menu_items
|
||||
if (not selected_tags) or (not all(_mismatch_map(menu_item)))
|
||||
]
|
||||
|
||||
return sorted(filtered_menu_items, key=_sort_key)
|
||||
|
||||
|
||||
def _get_all_tags(menu_items: list[menu.Menu]) -> list[str]:
|
||||
"""Return a sorted list of all tags present in the given menu items."""
|
||||
|
||||
def get_tags(menu_items: list[menu.Menu]) -> set[str]:
|
||||
"""Return a list of tags, unsorted."""
|
||||
all_tags = set()
|
||||
for menu_item in menu_items:
|
||||
all_tags.update(menu_item.tags or [])
|
||||
all_tags |= get_tags(menu_item.items)
|
||||
|
||||
return all_tags
|
||||
|
||||
# Sort tags by localized string
|
||||
return sorted(get_tags(menu_items), key=_)
|
||||
|
||||
|
||||
class AppsIndexView(TemplateView):
|
||||
"""View for apps index.
|
||||
|
||||
@ -223,12 +262,7 @@ class AppsIndexView(TemplateView):
|
||||
menu_items = menu.main_menu.active_item(self.request).items
|
||||
|
||||
context['tags'] = tags
|
||||
# Sorted tags by localized string
|
||||
all_tags = set()
|
||||
for menu_item in menu_items:
|
||||
all_tags.update(menu_item.tags or [])
|
||||
|
||||
context['all_tags'] = sorted(all_tags, key=_)
|
||||
context['all_tags'] = _get_all_tags(menu_items)
|
||||
context['menu_items'] = _pick_menu_items(menu_items, tags)
|
||||
|
||||
return context
|
||||
@ -236,11 +270,16 @@ class AppsIndexView(TemplateView):
|
||||
|
||||
def system_index(request):
|
||||
"""Serve the system index page."""
|
||||
menu_items = menu.main_menu.active_item(request).sorted_items()
|
||||
return TemplateResponse(request, 'system.html', {
|
||||
'advanced_mode': get_advanced_mode(),
|
||||
'menu_items': menu_items
|
||||
})
|
||||
tags = request.GET.getlist('tag', [])
|
||||
menu_items = menu.main_menu.active_item(request).items
|
||||
|
||||
return TemplateResponse(
|
||||
request, 'system.html', {
|
||||
'advanced_mode': get_advanced_mode(),
|
||||
'menu_items': _pick_menu_items(menu_items, tags),
|
||||
'tags': tags,
|
||||
'all_tags': _get_all_tags(menu_items)
|
||||
})
|
||||
|
||||
|
||||
class LanguageSelectionView(FormView):
|
||||
@ -471,9 +510,8 @@ class SetupView(TemplateView):
|
||||
context['setup_state'] = setup_state
|
||||
context['operations'] = operation.manager.filter(app.app_id)
|
||||
context['show_rerun_setup'] = False
|
||||
context['show_uninstall'] = (
|
||||
not app.info.is_essential
|
||||
and setup_state != app_module.App.SetupState.NEEDS_SETUP)
|
||||
context['show_uninstall'] = (not app.info.is_essential and setup_state
|
||||
!= app_module.App.SetupState.NEEDS_SETUP)
|
||||
|
||||
# Perform expensive operation only if needed.
|
||||
if not context['operations']:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user