views: Add a separate page for adding new apps

- It avoids confusion with two separate app sections in a single page. The
sections/pages can be better titled.

- In future, we can style the apps page (with more status display) differently
from apps-add page (with more search and featured apps).

- Move page specific parts into individual pages instead of in the common
template cards.html.

- Add special message when there are no apps enabled and when there are no more
apps to enable.

- Drop show_disabled flag in cards template that is not needed anymore.

Tests:

- Unit tests work.

- Functional tests work on bepasty when starting with uninstalled, disabled, or
enabled states.

- Apps page shows special message when there are no apps enabled. It is
centered.

- Apps page shows 'Add new app' button with icon. Clicking it takes us to
apps-add page.

- Apps page does not have disabled apps list (even hidden).

- Disabled and uninstalled apps are not shown on apps page.

- Apps-add page shows special message when there are no more apps to be
installed or enabled (test by altering view code to have empty list of apps).

- Apps-add page shows list of apps that are disabled or uninstalled but not
enabled apps. It shows a title 'Add New App'.

Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
Sunil Mohan Adapa 2026-07-24 13:55:49 -07:00 committed by James Valleroy
parent 37cfb0fbfd
commit 2764c58c53
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
6 changed files with 93 additions and 38 deletions

View File

@ -46,7 +46,7 @@
{% if app_info.tags %}
<div class="app-tags d-flex align-items-center flex-wrap">
{% for tag in app_info.tags %}
{% if not forloop.first %}•{% endif %}
{% if not forloop.first %}•{% endif %}
<a href="{{ active_section_url }}?tag={{ tag|urlencode }}"
class="btn btn-default rounded-pill tag">
{% trans tag %}

View File

@ -0,0 +1,25 @@
{% extends 'cards.html' %}
{% comment %}
# SPDX-License-Identifier: AGPL-3.0-or-later
{% endcomment %}
{% load static %}
{% load i18n %}
{% block page_js %}
<script src="{% static 'tags.js' %}" defer></script>
{% endblock %}
{% block body_class %}apps-add-page{% endblock %}
{% block header-container %}
<div class="card-section-title">{% trans "Add New App" %}</div>
{% endblock %}
{% block cards-empty %}
<p class="text-center">
{% blocktrans trimmed %}
There are no more apps available to add.
{% endblocktrans %}
</p>
{% endblock %}

View File

@ -5,9 +5,27 @@
{% load static %}
{% load i18n %}
{% load extras %}
{% block page_js %}
<script src="{% static 'tags.js' %}" defer></script>
{% endblock %}
{% block body_class %}apps-page{% endblock %}
{% block toolbar-container %}
<div class="btn-toolbar" role="toolbar">
<a class="btn btn-primary" href="{% url 'apps-add' %}" role="button">
{% icon 'plus' %}
{% trans "Add new app" %}
</a>
</div>
{% endblock %}
{% block cards-empty %}
<p class="text-center">
{% blocktrans trimmed %}
There are currently no apps added.
{% endblocktrans %}
</p>
{% endblock %}

View File

@ -20,45 +20,28 @@
{% include "tags.html" %}
{% endblock %}
<div class="btn-toolbar container" role="toolbar">
<a class="btn btn-primary" href="#available-for-install-container"
data-bs-toggle="collapse" role="button" aria-expanded="false"
aria-controls="available-for-install-container">
{% icon 'plus' %}
{% trans "Add new app" %}
</a>
</div>
<div class="container card-container">
<div class="row">
<div class="card-list card-list-primary">
{% for item in menu_items %}
{% if not show_disabled or item.is_enabled %}
{% block header-container %}
{% endblock %}
{% block toolbar-container %}
{% endblock %}
{% if menu_items %}
<div class="row">
<div class="card-list">
{% for item in menu_items %}
{% if advanced_mode or not item.advanced %}
{% include "card.html" %}
{% endif %}
{% endif %}
{% endfor %}
</div>
</div>
</div>
{% if show_disabled %}
<div class="container card-container collapse" id="available-for-install-container">
<div class="card-section-title">{% trans "Available for install" %}</div>
<div class="row">
<div class="card-list card-list-disabled">
{% for item in menu_items %}
{% if not item.is_enabled %}
{% if advanced_mode or not item.advanced %}
{% include "card.html" %}
{% endif %}
{% endif %}
{% endfor %}
</div>
</div>
</div>
{% endif %}
{% else %}
{% block cards-empty %}
{% endblock %}
{% endif %}
</div>
{% block content-container %}
{% endblock %}

View File

@ -23,7 +23,8 @@ urlpatterns = [
re_path(r'^language-selection/$',
public(views.LanguageSelectionView.as_view()),
name='language-selection'),
re_path(r'^apps/$', views.AppsIndexView.as_view(), name='apps'),
re_path(r'^apps/$', views.AppsView.as_view(), name='apps'),
re_path(r'^apps/add/$', views.AppsAddView.as_view(), name='apps-add'),
re_path(r'^sys/$', views.system_index, name='system'),
re_path(r'', include((system_urlpatterns, 'system'))),
re_path(r'^uninstall/(?P<app_id>[1-9a-z\-_]+)/$',

View File

@ -266,8 +266,8 @@ def _get_all_tags(menu_items: list[menu.Menu]) -> list[str]:
return sorted(get_tags(menu_items), key=_)
class AppsIndexView(TemplateView):
"""View for apps index.
class AppsView(TemplateView):
"""View for showing installed apps.
This view supports filtering apps by one or more tags. If no tags are
provided, it will show all the apps. If one or more tags are provided,
@ -277,11 +277,39 @@ class AppsIndexView(TemplateView):
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context['show_disabled'] = True
context['advanced_mode'] = get_advanced_mode()
tags = self.request.GET.getlist('tag', [])
menu_items = menu.main_menu.active_item(self.request).items
menu_items = [
item for item in menu.main_menu.active_item(self.request).items
if item.is_enabled()
]
context['tags'] = tags
context['all_tags'] = _get_all_tags(menu_items)
context['menu_items'] = _pick_menu_items(menu_items, tags)
return context
class AppsAddView(TemplateView):
"""View for showing apps that can be installed.
This view supports filtering apps by one or more tags. If no tags are
provided, it will show all the apps. If one or more tags are provided,
it will select apps matching any of the provided tags.
"""
template_name = 'apps-add.html'
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context['advanced_mode'] = get_advanced_mode()
tags = self.request.GET.getlist('tag', [])
menu_items = [
item for item in menu.main_menu.active_item(self.request).items
if not item.is_enabled()
]
context['tags'] = tags
context['all_tags'] = _get_all_tags(menu_items)