From d3cddfa68c8ba7dbbf33153eed00fbb00016f2dd Mon Sep 17 00:00:00 2001 From: Joseph Nuthalapati Date: Fri, 3 Nov 2017 12:09:46 +0530 Subject: [PATCH] Add icons for desktop applications and Apple App store. - Add yapf style file for consistency of formatting - Some minor changes and renaming - Add template tag filters for checking conditions - Move icons from img directory to icons directory Currently the client listing is in both the SetupView and ServceView for ease of development. Have to remove from ServiceView. Signed-off-by: Joseph Nuthalapati Reviewed-by: James Valleroy --- .style.yapf | 7 + LICENSES | 11 +- plinth/modules/deluge/manifest.py | 27 ++-- plinth/modules/minetest/manifest.py | 63 ++++----- plinth/modules/mumble/manifest.py | 130 ++++++++---------- plinth/modules/repro/manifest.py | 125 ++++++++--------- plinth/modules/syncthing/__init__.py | 3 +- plinth/modules/syncthing/manifest.py | 118 ++++++++++------ plinth/templates/clients.html | 47 +++++-- plinth/templates/service.html | 4 + plinth/templatetags/plinth_extras.py | 27 ++++ plinth/tests/test_templatetags.py | 56 ++++++-- plinth/views.py | 22 +-- static/themes/default/css/plinth.css | 14 ++ static/themes/default/icons/app-store.png | Bin 0 -> 9573 bytes .../themes/default/{img => icons}/apple.png | Bin .../themes/default/{img => icons}/f-droid.png | Bin static/themes/default/icons/gnu-linux.png | Bin 0 -> 14918 bytes .../default/{img => icons}/google-play.png | Bin .../themes/default/{img => icons}/windows.png | Bin static/themes/default/img/debian.png | Bin 4060 -> 0 bytes 21 files changed, 382 insertions(+), 272 deletions(-) create mode 100644 .style.yapf create mode 100644 static/themes/default/icons/app-store.png rename static/themes/default/{img => icons}/apple.png (100%) rename static/themes/default/{img => icons}/f-droid.png (100%) create mode 100644 static/themes/default/icons/gnu-linux.png rename static/themes/default/{img => icons}/google-play.png (100%) rename static/themes/default/{img => icons}/windows.png (100%) delete mode 100644 static/themes/default/img/debian.png diff --git a/.style.yapf b/.style.yapf new file mode 100644 index 000000000..03ac89877 --- /dev/null +++ b/.style.yapf @@ -0,0 +1,7 @@ +[style] +based_on_style = pep8 +spaces_before_comment = 2 +split_before_logical_operator = true +each_dict_entry_on_separate_line = true +coalesce_brackets = false +indent_dictionary_value = true \ No newline at end of file diff --git a/LICENSES b/LICENSES index 9888fd05b..f9652eb6b 100644 --- a/LICENSES +++ b/LICENSES @@ -63,8 +63,9 @@ otherwise. - static/themes/default/icons/tahoe.png :: [[https://github.com/thekishanraval/Logos][GPLv3+]] - static/themes/default/icons/transmission.png :: [[https://transmissionbt.com/][GPL]] - static/themes/default/icons/ttrss.png :: [[https://tt-rss.org/gitlab/fox/tt-rss][GPL]] -- static/themes/default/img/f-droid.png :: [[https://commons.wikimedia.org/wiki/File%3AGet_it_on_F-Droid_(material_design).svg][GPLv3]] -- static/themes/default/img/google-play.png :: [[https://upload.wikimedia.org/wikipedia/commons/c/cd/Get_it_on_Google_play.svg][Public Domain]] -- static/themes/default/img/debian.png :: [[https://commons.wikimedia.org/wiki/File:Debian_logo-black.png][GPL3+/CC-BY-SA]] -- static/themes/default/img/apple.png :: [[https://thenounproject.com/icon/1203053/download/color/000000/png][CC BY 3.0 US]] -- static/themes/default/img/windows.png :: [[https://thenounproject.com/icon/1206946/download/color/000000/png][CC BY 3.0 US]] +- static/themes/default/icons/f-droid.png :: [[https://commons.wikimedia.org/wiki/File%3AGet_it_on_F-Droid_(material_design).svg][GPLv3]] +- static/themes/default/icons/google-play.png :: [[https://upload.wikimedia.org/wikipedia/commons/c/cd/Get_it_on_Google_play.svg][Public Domain]] +- static/themes/default/icons/app-store.png :: [[https://upload.wikimedia.org/wikipedia/commons/thumb/3/3c/Download_on_the_App_Store_Badge.svg/500px-Download_on_the_App_Store_Badge.svg.png][Public Domain]] +- static/themes/default/icons/apple.png :: [[https://thenounproject.com/icon/1203053/download/color/000000/png][CC BY 3.0 US]] +- static/themes/default/icons/windows.png :: [[https://thenounproject.com/icon/1206946/download/color/000000/png][CC BY 3.0 US]] +- static/themes/default/icons/gnu-linux.png :: [[https://upload.wikimedia.org/wikipedia/commons/9/95/Tux-icon-mono.svg][Public Domain]] diff --git a/plinth/modules/deluge/manifest.py b/plinth/modules/deluge/manifest.py index 3306ecb7b..cb2d542c6 100644 --- a/plinth/modules/deluge/manifest.py +++ b/plinth/modules/deluge/manifest.py @@ -17,18 +17,17 @@ from django.utils.translation import ugettext_lazy as _ -clients = [ - { - 'name': _('Deluge'), - 'description': _('Bittorrent client written in Python/PyGTK '), - 'platforms': [ - { - 'type': 'web', - 'relative_path': '/deluge' - }, - { - 'type': 'apt', - 'os': 'Debian', - 'package_name': 'deluge' - }] +clients = [{ + 'name': + _('Deluge'), + 'description': + _('Bittorrent client written in Python/PyGTK '), + 'platforms': [{ + 'type': 'web', + 'relative_path': '/deluge' + }, { + 'type': 'package', + 'format': 'deb', + 'name': 'deluge', }] +}] diff --git a/plinth/modules/minetest/manifest.py b/plinth/modules/minetest/manifest.py index 394c21461..1ef242008 100644 --- a/plinth/modules/minetest/manifest.py +++ b/plinth/modules/minetest/manifest.py @@ -17,37 +17,32 @@ from django.utils.translation import ugettext_lazy as _ -clients = [ - { - 'name': _('Minetest'), - 'platforms': [ - { - 'type': 'download', - 'os': 'Windows(64-bit)', - 'os_version': 'XP, Vista, >=7', - 'url': 'https://github.com/minetest/minetest/releases' - '/download/0.4.16/minetest-0.4.16-win64.zip ' - }, - { - 'type': 'store', - 'os': 'Android', - 'store_name': 'google_play_store', - 'fully_qualified_name': 'net.minetest.minetest', - 'url': 'https://play.google.com/store/apps/details?id=net' - '.minetest.minetest ' - }, - { - 'type': 'store', - 'os': 'Android', - 'store_name': 'fdroid_store', - 'fully_qualified_name': 'net.minetest.minetest', - 'url': 'https://f-droid.org/packages/net.minetest.minetest/ ' - }, - { - 'type': 'apt', - 'os': 'Debian', - 'package_name': 'minetest' - } - ] - } -] +clients = [{ + 'name': + _('Minetest'), + 'platforms': [{ + 'type': 'download', + 'os': 'Windows', + 'arch': 'amd64', + 'os_version': 'XP, Vista, >=7', + 'url': 'https://github.com/minetest/minetest/releases' + '/download/0.4.16/minetest-0.4.16-win64.zip ' + }, { + 'type': 'store', + 'os': 'Android', + 'store_name': 'google_play_store', + 'fully_qualified_name': 'net.minetest.minetest', + 'url': 'https://play.google.com/store/apps/details?id=net' + '.minetest.minetest ' + }, { + 'type': 'store', + 'os': 'Android', + 'store_name': 'fdroid_store', + 'fully_qualified_name': 'net.minetest.minetest', + 'url': 'https://f-droid.org/packages/net.minetest.minetest/ ' + }, { + 'type': 'package', + 'format': 'deb', + 'name': 'minetest' + }] +}] diff --git a/plinth/modules/mumble/manifest.py b/plinth/modules/mumble/manifest.py index 5661773c4..3056cd9f5 100644 --- a/plinth/modules/mumble/manifest.py +++ b/plinth/modules/mumble/manifest.py @@ -17,73 +17,63 @@ from django.utils.translation import ugettext_lazy as _ -clients = [ - { - 'name': _('Mumble'), - 'platforms': [ - { - 'type': 'download', - 'os': 'Windows(32-bit)', - 'url': 'https://github.com/mumble-voip/mumble/releases' - '/download/1.2.19/mumble-1.2.19.msi ' - }, - { - 'type': 'download', - 'os': 'Windows(64-bit)', - 'url': 'https://dl.mumble.info/mumble-1.3.0~2569~gd196a4b' - '~snapshot.winx64.msi ' - }, - { - 'type': 'download', - 'os': 'macOS', - 'url': 'https://github.com/mumble-voip/mumble/releases' - '/download/1.2.19/Mumble-1.2.19.dmg ' - }, - { - 'type': 'apt', - 'os': 'Debian', - 'package_name': 'mumble' - }, - { - 'type': 'store', - 'os': 'iOS', - 'os_version': '>=8.0', - 'store_name': 'apple_store', - 'url': 'https://itunes.apple.com/us/app/mumble/id443472808' - } - ] - }, - { - 'name': _('Plumble'), - 'platforms': [ - { - 'type': 'store', - 'os': 'Android', - 'store_name': 'google_play_store', - 'url': 'https://play.google.com/store/apps/details?id=com' - '.morlunk.mumbleclient.free ', - 'fully_qualified_name': 'com.morlunk.mumbleclient' - }, - { - 'type': 'store', - 'os': 'Android', - 'store_name': 'fdroid_store', - 'url': 'https://play.google.com/store/apps/details?id=com' - '.morlunk.mumbleclient.free ', - 'fully_qualified_name': 'com.morlunk.mumbleclient' - } - ] - }, - { - 'name': _('Mumblefly'), - 'platforms': [ - { - 'type': 'store', - 'os': 'iOS', - 'os_version': '>=7.0', - 'store_name': 'apple_store', - 'url': 'https://itunes.apple.com/dk/app/mumblefy/id858752232' - } - ] - } -] +clients = [{ + 'name': + _('Mumble'), + 'platforms': [{ + 'type': 'download', + 'os': 'Windows', + 'arch': 'i386', + 'url': 'https://github.com/mumble-voip/mumble/releases' + '/download/1.2.19/mumble-1.2.19.msi ' + }, { + 'type': 'download', + 'os': 'Windows(64-bit)', + 'arch': 'amd64', + 'url': 'https://dl.mumble.info/mumble-1.3.0~2569~gd196a4b' + '~snapshot.winx64.msi ' + }, { + 'type': 'download', + 'os': 'macOS', + 'url': 'https://github.com/mumble-voip/mumble/releases' + '/download/1.2.19/Mumble-1.2.19.dmg ' + }, { + 'type': 'package', + 'format': 'deb', + 'name': 'mumble' + }, { + 'type': 'store', + 'os': 'iOS', + 'os_version': '>=8.0', + 'store_name': 'apple_store', + 'url': 'https://itunes.apple.com/us/app/mumble/id443472808' + }] +}, { + 'name': + _('Plumble'), + 'platforms': [{ + 'type': 'store', + 'os': 'Android', + 'store_name': 'google_play_store', + 'url': 'https://play.google.com/store/apps/details?id=com' + '.morlunk.mumbleclient.free ', + 'fully_qualified_name': 'com.morlunk.mumbleclient' + }, { + 'type': 'store', + 'os': 'Android', + 'store_name': 'fdroid_store', + 'url': 'https://play.google.com/store/apps/details?id=com' + '.morlunk.mumbleclient.free ', + 'fully_qualified_name': 'com.morlunk.mumbleclient' + }] +}, { + 'name': + _('Mumblefly'), + 'platforms': [{ + 'type': 'store', + 'os': 'iOS', + 'os_version': '>=7.0', + 'store_name': 'apple_store', + 'url': 'https://itunes.apple.com/dk/app/mumblefy/id858752232' + }] +}] diff --git a/plinth/modules/repro/manifest.py b/plinth/modules/repro/manifest.py index d3022d4cc..6f1490a13 100644 --- a/plinth/modules/repro/manifest.py +++ b/plinth/modules/repro/manifest.py @@ -17,73 +17,58 @@ from django.utils.translation import ugettext_lazy as _ -clients = [ - { - 'name': _('Jitsi Meet'), - 'description': _('Jitsi is a set of open-source projects that allows ' - 'you to easily build and deploy secure ' - 'videoconferencing solutions. At the heart of Jitsi ' - 'are Jitsi Videobridge and Jitsi Meet, which let you ' - 'have conferences on the internet, while other ' - 'projects in the community enable other features ' - 'such as audio, dial-in, recording, ' - 'and simulcasting.'), - 'platforms': [ - { - 'type': 'store', - 'os': 'Android', - 'store_name': 'google_play_store', - 'fully_qualified_name': 'org.jitsi.meet', - 'url': 'https://play.google.com/store/apps/details?id=org' - '.jitsi.meet ' - }, - { - 'type': 'store', - 'os': 'iOS', - 'store_name': 'apple_store', - 'url': 'https://itunes.apple.com/in/app/jitsi-meet/id1165103905' - }, - { - 'type': 'download', - 'os': 'Linux', - 'url': 'https://download.jitsi.org/jitsi/debian/' - }, - { - 'type': 'dnf', - 'os': 'Linux', - 'package_name': 'jitsi' - }, - { - 'type': 'download', - 'os': 'macOS', - 'url': 'https://download.jitsi.org/jitsi/macosx/jitsi-latest' - '.dmg ' - }, - { - 'type': 'download', - 'os': 'Windows', - 'url': 'https://download.jitsi.org/jitsi/windows/jitsi-latest' - '-x86.exe ' - }, - { - 'type': 'download', - 'os': 'macOS', - 'url': 'https://download.jitsi.org/jitsi/macosx/jitsi-latest' - '.dmg ' - } - ] - }, - { - 'name': _('CSipSimple'), - 'platforms': [ - { - 'type': 'store', - 'os': 'Android', - 'store_name': 'google_play_store', - 'fully_qualified_name': 'com.csipsimple', - 'url': 'https://play.google.com/store/apps/details?id=com' - '.csipsimple ' - } - ] - } -] +clients = [{ + 'name': + _('Jitsi Meet'), + 'description': + _('Jitsi is a set of open-source projects that allows ' + 'you to easily build and deploy secure ' + 'videoconferencing solutions. At the heart of Jitsi ' + 'are Jitsi Videobridge and Jitsi Meet, which let you ' + 'have conferences on the internet, while other ' + 'projects in the community enable other features ' + 'such as audio, dial-in, recording, ' + 'and simulcasting.'), + 'platforms': [{ + 'type': 'store', + 'os': 'Android', + 'store_name': 'google_play_store', + 'fully_qualified_name': 'org.jitsi.meet', + 'url': 'https://play.google.com/store/apps/details?id=org' + '.jitsi.meet ' + }, { + 'type': 'store', + 'os': 'iOS', + 'store_name': 'apple_store', + 'url': 'https://itunes.apple.com/in/app/jitsi-meet/id1165103905' + }, { + 'type': 'download', + 'os': 'GNU/Linux', + 'url': 'https://download.jitsi.org/jitsi/debian/' + }, { + 'type': 'package', + 'format': 'deb', + 'name': 'jitsi' + }, { + 'type': 'download', + 'os': 'macOS', + 'url': 'https://download.jitsi.org/jitsi/macosx/jitsi-latest' + '.dmg ' + }, { + 'type': 'download', + 'os': 'Windows', + 'url': 'https://download.jitsi.org/jitsi/windows/jitsi-latest' + '-x86.exe ' + }] +}, { + 'name': + _('CSipSimple'), + 'platforms': [{ + 'type': 'store', + 'os': 'Android', + 'store_name': 'google_play_store', + 'fully_qualified_name': 'com.csipsimple', + 'url': 'https://play.google.com/store/apps/details?id=com' + '.csipsimple ' + }] +}] diff --git a/plinth/modules/syncthing/__init__.py b/plinth/modules/syncthing/__init__.py index da667460e..b1aa02ce4 100644 --- a/plinth/modules/syncthing/__init__.py +++ b/plinth/modules/syncthing/__init__.py @@ -67,7 +67,8 @@ service = None def init(): """Intialize the module.""" menu = main_menu.get('apps') - menu.add_urlname(name, 'glyphicon-refresh', 'syncthing:index', short_description) + menu.add_urlname(name, 'glyphicon-refresh', + 'syncthing:index', short_description) global service setup_helper = globals()['setup_helper'] diff --git a/plinth/modules/syncthing/manifest.py b/plinth/modules/syncthing/manifest.py index 15ba3d943..da00f1545 100644 --- a/plinth/modules/syncthing/manifest.py +++ b/plinth/modules/syncthing/manifest.py @@ -17,45 +17,79 @@ from django.utils.translation import ugettext_lazy as _ -clients = [ - { - 'name': _('Syncthing'), - 'platforms': [ - { - 'type': 'download', - 'os': 'Debian', - 'url': 'https://apt.syncthing.net/', - }, - { - 'type': 'download', - 'os': 'macOS', - 'url': 'https://github.com/syncthing/syncthing/releases' - }, - { - 'type': 'download', - 'os': 'Windows', - 'url': 'https://github.com/syncthing/syncthing/releases' - }, - { - 'type': 'store', - 'os': 'Android', - 'store_name': 'google_play_store', - 'fully_qualified_name': 'com.nutomic.syncthingandroid', - 'url': 'https://play.google.com/store/apps/details?id=com' - '.nutomic.syncthingandroid ' - }, - { - 'type': 'store', - 'os': 'Android', - 'store_name': 'fdroid_store', - 'fully_qualified_name': 'com.nutomic.syncthingandroid', - 'url': 'https://f-droid.org/packages/com.nutomic' - '.syncthingandroid/ ' - }, - { - 'type': 'web', - 'relative_url': '/syncthing' - } - ] - } -] +metadata = { + 'syncthing': { + 'version': '0.14.39', + 'android-package-id': 'com.nutomic.syncthingandroid', + }, +} + +clients = [{ + 'name': + _('Syncthing'), + 'platforms': [{ + 'type': 'package', + 'format': 'deb', + 'name': 'syncthing', + }, { + 'type': 'package', + 'format': 'homebrew', + 'name': 'syncthing', + }, { + 'type': + 'download', + 'os': + 'all', + 'url': + 'https://github.com/syncthing/syncthing/releases/tag/v{}' + .format(metadata['syncthing']['version']) + }, { + 'type': 'download', + 'os': 'GNU/Linux', + 'arch': 'amd64', + 'url': 'https://github.com/syncthing/syncthing/releases/' + 'download/v{0}/syncthing-linux-amd64-v{0}.tar.gz' + .format(metadata['syncthing']['version']), + }, { + 'type': 'download', + 'os': 'macOS', + 'arch': 'amd64', + 'url': 'https://github.com/syncthing/syncthing/releases/' + 'download/v{0}/syncthing-macosx-amd64-v{0}.tar.gz' + .format(metadata['syncthing']['version']), + }, { + 'type': 'download', + 'os': 'Windows', + 'arch': 'amd64', + 'url': 'https://github.com/syncthing/syncthing/releases/' + 'download/v{0}/syncthing-windows-amd64-v{0}.zip' + .format(metadata['syncthing']['version']), + }, { + 'type': + 'store', + 'os': + 'Android', + 'store_name': + 'google_play_store', + 'fully_qualified_name': + 'com.nutomic.syncthingandroid', + 'url': + 'https://play.google.com/store/apps/details?id={}' + .format(metadata['syncthing']['android-package-id']) + }, { + 'type': + 'store', + 'os': + 'Android', + 'store_name': + 'fdroid_store', + 'fully_qualified_name': + 'com.nutomic.syncthingandroid', + 'url': + 'https://f-droid.org/packages/{}' + .format(metadata['syncthing']['android-package-id']) + }, { + 'type': 'web', + 'relative_url': '/syncthing' + }] +}] diff --git a/plinth/templates/clients.html b/plinth/templates/clients.html index d17b37dec..213fb4f12 100644 --- a/plinth/templates/clients.html +++ b/plinth/templates/clients.html @@ -18,28 +18,41 @@ {% endcomment %} {% load i18n %} +{% load plinth_extras %} {% if module.clients %} + + +
+{% if module.clients|has_web_clients %}

{% trans "Web Clients" %}:

    {% for client in module.clients %} - {% for platform in client.platforms %} - {% if platform.type == 'web' %} -
  • - {{ client.name}} -
  • - {% endif %} - {% endfor %} + {% if client|has_web_clients %} + {% for platform in client.platforms %} + {% if platform.type == 'web' %} +
  • + {{ client.name}} +
  • + {% endif %} + {% endfor %} + {% endif %} {% endfor %}
+{% endif %} +{% if module.clients|has_desktop_clients %}
{% load static %}

{% trans "Desktop Clients" %}:

+{% endif %} +{% if module.clients|has_mobile_clients %}
{% load static %}

{% trans "Mobile Clients" %}:

    {% for client in module.clients %} + {% if client|has_mobile_clients %}
  • {{ client.name }}
  • {% for platform in client.platforms %} @@ -74,21 +91,25 @@ {% if platform.store_name == 'fdroid_store' %} {% endif %} {% if platform.store_name == 'google_play_store' %} {% endif %} {% endif %} {% endfor %}
    + {% endif %} {% endfor %}
+
+ {% endif %} +
{% endif %} diff --git a/plinth/templates/service.html b/plinth/templates/service.html index 549a8e63d..31908381e 100644 --- a/plinth/templates/service.html +++ b/plinth/templates/service.html @@ -75,3 +75,7 @@ {% endblock %} {% endblock %} + + +{% include "clients.html" with clients=service.clients %} + diff --git a/plinth/templatetags/plinth_extras.py b/plinth/templatetags/plinth_extras.py index b862887ee..14bafd2b4 100644 --- a/plinth/templatetags/plinth_extras.py +++ b/plinth/templatetags/plinth_extras.py @@ -58,3 +58,30 @@ def show_subsubmenu(context, menu): """Mark the active menu item and display the subsubmenu""" menu = mark_active_menuitem(menu, context['request'].path) return {'subsubmenu': menu} + + +def __check(clients, cond): + """Check if any of a list of clients satisfies the given condition""" + clients = clients if isinstance(clients, list) else [clients] + return any(pf for client in clients for pf in client['platforms'] + if cond(pf)) + + +@register.filter(name='has_web_clients') +def has_web_clients(clients): + """Filter to find out whether an application has web clients""" + return __check(clients, lambda x: x['type'] == 'web') + + +@register.filter(name='has_mobile_clients') +def has_mobile_clients(clients): + """Filter to find out whether an application has mobile clients""" + return __check(clients, lambda x: x.get('os', '') == 'Android') + + +@register.filter(name='has_desktop_clients') +def has_desktop_clients(clients): + """Filter to find out whether an application has desktop clients""" + return __check( + clients, + lambda x: x.get('os', '') in ['Windows', 'macOS', 'GNU/Linux']) diff --git a/plinth/tests/test_templatetags.py b/plinth/tests/test_templatetags.py index b24334d9c..f47cfb9a3 100644 --- a/plinth/tests/test_templatetags.py +++ b/plinth/tests/test_templatetags.py @@ -14,18 +14,22 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # - """ Test module for custom Django template tags. """ import unittest -from plinth.templatetags.plinth_extras import mark_active_menuitem +from plinth.modules.syncthing.manifest import clients as syncthing_clients +from plinth.modules.infinoted.manifest import clients as infinoted_clients +from plinth.modules.deluge.manifest import clients as deluge_clients +from plinth.modules.quassel.manifest import clients as quassel_clients +from plinth.templatetags import plinth_extras class TestShowSubSubMenu(unittest.TestCase): """Verify that the highlighting of the subsubmenu is working correctly""" + def assert_active_url(self, menu, url): """Verify that only the given url is set as 'active' in the menu""" for item in menu: @@ -41,19 +45,45 @@ class TestShowSubSubMenu(unittest.TestCase): def test_highlighting(self): """Test detection of active subsubmenu items using request.path""" - menu = [{'url': '/abc/123/abc/', 'text': 'abc'}, - {'url': '/abc/123/', 'text': 'overview'}, - {'url': '/abc/123/crunch/', 'text': 'crunch'}, - {'url': '/abc/123/create/', 'text': 'create'}] + menu = [{ + 'url': '/abc/123/abc/', + 'text': 'abc' + }, { + 'url': '/abc/123/', + 'text': 'overview' + }, { + 'url': '/abc/123/crunch/', + 'text': 'crunch' + }, { + 'url': '/abc/123/create/', + 'text': 'create' + }] - tests = [['/abc/123/crunch/new/', '/abc/123/crunch/'], - ['/abc/123/create/', '/abc/123/create/'], - ['/abc/123/nolink/', '/abc/123/'], - ['/abc/123/abx/', '/abc/123/'], - ['/abc/123/ab/', '/abc/123/'], - ['/abc/123/', '/abc/123/']] + tests = [['/abc/123/crunch/new/', '/abc/123/crunch/'], [ + '/abc/123/create/', '/abc/123/create/' + ], ['/abc/123/nolink/', '/abc/123/'], ['/abc/123/abx/', '/abc/123/'], + ['/abc/123/ab/', '/abc/123/'], ['/abc/123/', '/abc/123/']] for check_path, expected_active_path in tests: - menu = mark_active_menuitem(menu, check_path) + menu = plinth_extras.mark_active_menuitem(menu, check_path) self.assert_active_url(menu, expected_active_path) self.assertTrue(self._verify_active_menuitems(menu)) + + def test_has_web_clients(self): + """Test for a utility function that returns + whether an application has web clients""" + self.assertTrue(plinth_extras.has_web_clients(syncthing_clients)) + self.assertFalse(plinth_extras.has_web_clients(quassel_clients)) + + def test_has_mobile_clients(self): + """Test for a utility function that returns + whether an application has mobile clients""" + self.assertTrue(plinth_extras.has_mobile_clients(syncthing_clients)) + self.assertFalse(plinth_extras.has_mobile_clients(infinoted_clients)) + + def test_has_desktop_clients(self): + """Test for a utility function that returns + whether an application has desktop clients""" + self.assertTrue( + plinth_extras.has_desktop_clients(syncthing_clients[0])) + self.assertFalse(plinth_extras.has_desktop_clients(deluge_clients)) diff --git a/plinth/views.py b/plinth/views.py index 8dcc1d6a3..efb8b363f 100644 --- a/plinth/views.py +++ b/plinth/views.py @@ -14,7 +14,6 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # - """ Main Plinth views """ @@ -48,13 +47,14 @@ def index(request): disk_views.warn_about_low_disk_space(request) - return TemplateResponse(request, 'index.html', - {'title': _('FreedomBox'), - 'shortcuts': shortcuts, - 'selected_id': selection, - 'details': details, - 'details_label': details_label, - 'configure_url': configure_url}) + return TemplateResponse(request, 'index.html', { + 'title': _('FreedomBox'), + 'shortcuts': shortcuts, + 'selected_id': selection, + 'details': details, + 'details_label': details_label, + 'configure_url': configure_url + }) def system_index(request): @@ -97,8 +97,10 @@ class ServiceView(FormView): def get_initial(self): """Return the status of the service to fill in the form.""" - return {'is_enabled': self.service.is_enabled(), - 'is_running': self.service.is_running()} + return { + 'is_enabled': self.service.is_enabled(), + 'is_running': self.service.is_running() + } def form_valid(self, form): """Enable/disable a service and set messages.""" diff --git a/static/themes/default/css/plinth.css b/static/themes/default/css/plinth.css index 5b3c49958..35e14ca92 100644 --- a/static/themes/default/css/plinth.css +++ b/static/themes/default/css/plinth.css @@ -150,3 +150,17 @@ footer license-info p{ .shortcut-label { min-height:50px; } + + +/* Icon when collapsible content is shown */ +.btn:after { + font-family: "Glyphicons Halflings"; + content: "\e114"; + margin-left: 5px; +} + +/* Icon when collapsible content is hidden */ +.btn.collapsed:after { + content: "\e080"; +} + diff --git a/static/themes/default/icons/app-store.png b/static/themes/default/icons/app-store.png new file mode 100644 index 0000000000000000000000000000000000000000..1842b0faef8c47565c32de308e759ab4abf3c585 GIT binary patch literal 9573 zcmZ`k>8c1kr4c$ZbM*4`HnV}8l!_Bq`Xa`0%=vM)+u2HdfL?@y+DQHN zZ~_8o7)dJyPn&01pUflvEgr=D0FSmqy*UVOwv?*&B~DHRGQmc3K(Qb0i;FJmUn`1R z<&)CNJggp1UxnEJU|>fotd0xX%$a|E?gY>SI1nwT@2ehnpT6Y#$cYyMD5Izs3fQ;k z_pM#FTSLEe0K&su(R;*7P7LQ}=q^Vrs04hOd|td;Cc`To)fcCSjOEWVwAtdtp6T1@!s8atNEAmZ5ZF(KW;NRm` z`Sc8vXE!-fTjS0F(OcoX9i6qeKWl$xA}GY&ZF>MVOzLTFj|$NqE+Zma%`Qkwc_+)2 zn8y;8_$7G<&{68cG)|dW-?Vd^0m5hHX_TQTfLF~xDMLk_!{D8Y;%}JG#lAc9Tgcy} zCTwTZE`ryd5@vmAnqs&(Z%KquFE8mf^+t%^Flol~E(zW+TuT%ZO$ zQKm^iQ~L$Qisi9d4({3F{t&C#$-_vREQQxxHF%YN_bgP@N;T(|AKif)<{ct(K!!^^ z?7x`+vbeU*fS*xZ)5i@;8im;{^(go857<)=1F@=s@adze zQ0;#Y;q+8V_GdN^!un{ozoN+UA4Z?8bIfmVd|W2 z;rV!l>(&v2b_aj38FJ!a#dlv(@CrxE$LNl?#5Hv4Nf0GGd{sHWMSEdlUZx|+xJF7n z?*B4322vA)BlqXtf0ctHmxwL1EAnLyK{G9pa&N%Y>0nEwB zBUI%%E)mqPOlM&ybrCcWZAF$2y0AqC)JxG}5GPUL+yEwcCj<{M_O-&J%7tV9YENcl zFHQDS0iB5p_9jYstn_{r_CO+k6BrI8>B6@kW2!r9g4UH@!=um$;iKW5Bjbs(@0o*k zDdk$VU{HxK?Vcm9utwUy428%vtYNn%~3?^-Wx9D9KjgZ0T_=Q0THU8TASs@9%t~2ivy+@T%GcdXfV7~lq1g^ z!Aj+;*_Xu$T1iM1`CIgEd|Ta&C~UuYg-Nu|{g+id#s`VwWYkD$3F_U7Y+i;CGzvAa z)-!{&u!z(Gc7)rkTJy{qXk$qm^#!r!8yO2`z(`|yN!B5Bbc|#{QW-TtR7*5ChW7o^p;Sw8bNFM@0+6wEnns;#vfYAZ#l0@CssMg znu2;WS`cNb%o+n^0YR}~Yc+kp)%DM$@AYl288eQob0ET`MR)GbOnMurqIDKmkHyO0 z*@0Ae{qCS3a~i9v24yW*J}%)MlQ4Ktr_|4PjvmO<##KgxZk z$&Q0pby8}ZSQrD8Gh5x_ua9b?{fbaApQM$bEb*;3N8u;B*zac)!-~@iuIs79$^2#R zHnx<^l|*Hl|$j+Bef5I+s_j^GX%g0<4FyOWN$cSsvK= zgH8-I-bq+hGw37^rD^o`P3GIeru=bkj&Z&nX+iM4_L%Ad44!aW(-Hv+O@1Gm^M90f zt-fLrF#cSMGum@R24%FCT_c$V?gJ{yj(ZU4**VQ}PrB@giVnHsqj1Z<@!bnFlfEAi zk%hVe+eZl}a6kbox78oE0eC%T0CiWs?HuP2|DdWqDV!zP+LB1PZu2`W%Hpiv#)@SY zc%jQ8Pnz|>40TJ`!|$ltQ{)GifEmeZvo6aesC2k9^VxhAVHsvrX9<)&7l@FJ?7FT% ze-t{)_zX4V8`V-fk8$`vaz{4NTbM(#lAKf8mB5!1{q_4uEX5;+Q!qBS6R`-jTh)oG z@;RO|hnF>itNq*Q3WW8%7c8n1ya3e+Kccn-jU->@zti|%Z+md7JFqpyORD223PXr{ z*Vvd($>LaMW_Z*G-AW!psd#i$0jR4=R;!@BFgi(zY;Y1EQc}YUMl69fZ1B_- zmtERQcah}A;Lu+o$PrU=K$T2_VE+Q#l5aq~Zc0+cuQn+G!Hzgw-oZb&mJ}x|*5f!Y ze$eWgbMrR_rEWi=H&UG_$@=3-!A(!Lh42Q0^q5gEaPD5@NK%}Jb}v=e-JW(5+!fcB z^#4Xus=11%<3rAik!v-S9OjDye?d?O{d`AjL+|%C8#kAteEPlkNP#=P1up z&76GuY_cSK))uw*!Yws8Dg7Q{9;Gt;qX2;Zd@*50Vuq)rW^>g6{F)M>8v2#Gp>4q2 zfkXNIUabfPk~*`6$@or0@o3UY%xlXwogW#ZF$K~p!R{YRBWmhm$v!q==V1wV zq;jdA-Z^qssX=F0!(_?&`v|`z%t~VT=(N9o&}*j-X1jo|vNS(?+DH9gi@21r70zOo zrha3n!bl1vlXO8Efu6gPw-p;NJna~1NY!6>hetJxKxWIY-=oQ0zMl6hEq*y1<+v?J zckC@?r`Xh>;Yi`urv##0Xv3&v2j2OXO^$WxGYo5*n1A!e3~R%|Q;eVYf%S$hGVF^1 z>|I)fylmyI0S~1$+<))6>ZW(y<4YeIvg~;b)kheu472zHag^s*nnZy>m@9a~QF9K) zl|D^&z8>Xh3#W-#me&w(Xhmx|0T>RDeFyzR{mZc(-;S3|b$8sgqqWsaJYRj+P4bP? zNHXIe_a287!sB@eRLH;pimsGi6Kk)-ZP)iu+{>95S=H0KO~4)Mhtw_sKg9Q(Ss3z? z^uJfpbzcI+ZRMPCJUMbHRM|v`c| zrOy4~|)Xr2A6lFx1`+aD>Sf)-K=slDG5Qd`ZP<0YQhIHxesFwiN^{ zG)3wm(oV46W@;cZiJ(MMoBkZ(;#~jrckkSB&;KjgohhYz{UnFun9sUmxz_kXi@{O5 z&lfvH4-hrPrcH`z{#~cD zb?xzxaP3Vn$T=Z%EKj)(p~M4*E?tJ|qfo7OM-q!>f~HqSCY??6UDBj*s}XjpudyAH z5|35&hrAM#v!&(7={b6s#8)V0q8{b?HSOipb;(*&TJ}o{8B#|blgC%xw5Yx;5uUtw zapuFN-zJmn{AoxO_02D`D%MgCXnI`u$w1c{~x)FQ@?Xw!vHsyI9C0CGsGh6sWG6zJUh}G2YZQ+;NIBUUS&2o|2 zO^S{76!^8#eJq;r&8KJ{-U;15KeADaS2$?bB)?Fw( zIR4z3@ct}^Bm#z7dtWCzY%mceb=qneJcseG$>3(FlAyk<3~RpQiYo`_Tz%OpCeziJ z{l+1@za}WylKHmal;3V0#}r0YR!|u}N2L5s5|v|)ZYm*uKqXPFIwnfal_{4Nx*Y7J zQYw?jO7*HzE##0GL}S`j@L=joqLVj@XJmfdp(x1*Yv3VZNB4LT7AtL!34D!{*SZ$#gCZJ>GKRORQx3&|Y~=)uoy zEd?*De7=&z;s7>RK(VwjAS@g1!izandE#327 zgHjZ(Q#;Ud=F!+CYeO$vi^=+T>jM&zsU=5XN@J}vetoLO#Qtiau?7J&c;-Z!x=(}m z(I{(Ubt!_DAcBYK-8hNx3|}ra@1-B)mXBCdpOxT??c9A!z_wPn6y|Ez^oVo7xKhpsr)Z?69u*B3{*n2%C`3Co$w-{qa?gE*Rgb z(iu9W(6nC#CE`Nc^vG+ab2uZPTJkc=?Df^sMrdz_ zN5ZP2Q+8@Q3q8c2Y-;isG3>z`gIWX#nrzqkX_oxl5`9*J5h$bB6uD56;1)u1(e}50 z&>6U9k{>!VZ;FpCa8OpRKRwIoL4)?3)5=q88@;I&!UCby#`J}|;Vjx@L&NLELD398 zh)#YzAkGKU=jxjR@Iypi~ki^%6z zdA2uypsA!|()=H?X|8zWiNt)Zw{gVvtG^2OsUZ=`&{8L>3_kWiIEBDKLwAHy?A@~Q zn1MPO+Ww$vG5BBe^diQ*c!yto8QxEHucS+r9}B6gxjR$s21+eyoxhNQkk$1uIxhf` zjBA5^iXU#x#}$HG+p=AC5x*yFmx*A{>M&C<<{tyuz@BthUsBk3qe--j^iy}wcrV@7 zpOW>MUIRfe6Bgk9R943CxX@$u5h*Mio2gCYlGQ>wio53ML-|OTkH+Q`C@T}D&0Y`+ z>q{j9)ZlpwPxB`x<&egUhfAif?OFVSz5`X{CkaDGtig&O6Gn1!0YmX5^A~SuYWjLQ zYZHBWE>Il#xc%d}StkV(JdQVasMJu=J@e-e+Oxgt6LY`@6Y(|3;rTJgKYR_@AW>s@; z`SNqD4LNI=4Xr34r;pL>;p1L7j#;Vc1gmfsvrk&fBHLWEN-I2_K<#%Uq_LtzT~Y{- zphFh2JpNc$fqP6-EmR_x^Wl(@uOEmis7M%R{2K+$-L@E^E+47(6Z}I)kZBCB4Iz?b zhc;86L;pdX(3yQCQxkL|DTgQYSI6sp8|}#!)IWwvJIK1twSNT)$DS=i6I-)Z`>Rz# zl$%kl6hUm7yiA9Z8RC3w+CZ-rW@Sm=)U0he<|@Yt3Swn}33)G)LCV5-kV>8q+EJc= zIafQ+tywtiMBDE)T?I`R@w?qFp-gg zScZrFbh;xvg<;3u6`?_|8VT(sA>`3FCWxsGQx-M>ZCD5>t1;{t z)jJm&FDLibRuvo){8KMl9FumXh*l-Q_iNa*br0PWYchV<1oPf!`tp zO;`Gk+!+0;hmiSvobZ}Gg9+CKjNc1wNdx@XB^t zznB7{jo~Z25xEpc{zc4mK+eqbY)=#`7ecSu0lA0H?Fk(hSj#N0!0+r?o#%nwcz1*qkufO;l zC_{jjEy5DSL-b3?SQkwHdJJ}Y^jA~y-kU8MknsmlMK4lKDIh7Xq51WAK{JBk_^FIb z3|D6+DdN`I3R;`tteGoiw_@w&WNt)R(1WmMg&pFk+#`Kv8l}R1yBHZ9VF7j*rRFdmNj(x`z{olWx&8u93$KVCHR=@gYij zI6sB8Y`OICjWoDaZ9Nqr63&%0p#!cG~KTBOL9+-ap-Vmt~^J^6G2GjRJT8poRy24Wj zlqZ^Uz{V))jsXnxvi;mfHhq^zl@Qx&4GV8P34B2aMw#Z!FlMcU$oy-t>C9h2qy0C`f{pv{o@2w;b<`?A zsDO~W*P2oBqf0Wau-j*JdqE=Su7VE6TuPxbuZJs59~iAxH$7NNe$B_M`% zB1mthI(yFpFG(#P2%~Tld^E%X@JzriKa+xIaRSwEp-OuxxyW%AXm81_A&CwQ8a?ce0jS9r2B4t zxrX)X;GmEkOo{vX6_22aB2i@C#4ec1;wX>_tu!;O3LnBf#w?!tq`)seV&BMMrHz(T`{qY0? zD`IbVkYPrReRi88fuz9_b3QzAS41ZBD#iv#tb847^vn%+n}SA96NiU}ds6J&v_`A? zFmz;-W#HJ}WISQ11MXg5ND|8ecV5z9H#-(lgZ!jhto9c+6So-$vtGVa9tobIj|C?G z8W!%$?JiQR+yIXIPj~vm=1tq@>#A7=u>j^PP`tx>=hpMEa-!+~2nCyjDa5m2b()isC#UUdxA2jH6+f zqSUH;lf~1h|^iT5z$|$kNwr0HO)#53_D}l zh?Xaga3pk12GbH@$)D&A^cmIh7=`$!!IzwgRM7(MODnEur5nFNzBVzRAVR0~9&er@cD-185XnAa|OI)$r z@4TnU6TtSV`xlFns`0go3!gAVJLilp;3<;SR#`mua??y#*Bpq$;*+Bib@r4CuhFnD(e_pauYFS z5&|jLkPj>1lT0zG+KdM90WX+9yPz2=`pU;D^}YX>NG%|+db zT-ZxUMWWKd19Z=z#P1bD$O-V8WH9Yxu9ZYH;_YS4{U~vh&(>lUoV2 z{n4ls%@xnI9@4i(7;JEb%X`C9y0q1FvD=n~H-qOzBhd%4NzN!nuQ+Xr-hfm0D$FpP zQQeb&l+c=Fr8x;?GUOjO0vAfs+89y_yG1PhK1|xvQl2PEV>fhymQX?ENk)E#i8BUy zUJBZSb*5PdX`R&>cn`a0p*3-CpR4cmBe1?4OoMqCubBZ!gNAcuSmx)XCcA!%ku!et zGi_Pp?&P_A65V-H#YWl^HrSA9r^;ZCtnRj0N3eJ+qjs;2<|VD6&HP`QvN>n(6^FJy z+S{MEW?l94S2H>HJ)-w6NT`GK_B(PcQISAPqGD*<UN#hUADhJ_HR03^0C{a}ac6)8!h;ya?z(u&qzxm7;&o0OUVrwn=Ei6DXRSLdBjGILRMrma zHO}nATQQqmLkl}-!u5zlKpwq>n};|{t2oPw(p^zUt!Z*v`;QCwmV@T0BwAwOe2pmE zXZvIqv+0SPd9*xExGgiOQS`n09VC4{6iE<*#DncEDzPOVt(UzP78@ERJm9JaabG+N zX_l{Zlt>zLqU6d$%B)~L1JZDZrm`4b9~%^U&(IX>@p-)Cfyvzy-9Mu z?5hLUnK9;md$N2C+oSwF7`GaYsCC0qn)xYBoJBHGknl^KOT<5)_$>XP&*lxUkT34H z0bc}O@46^&cY!GW5uz7oNX!F+w^Nhk4;*;0@a|(#J&RBx^fupNW9iL$*;?rbGgQbQ zxjsiWr#jGtX??>u0YL2$Ka3>aaN9PJ)KKI;c?m0bWiqwna=}0^lBX_T&rc969}m(Y ztim2mb$+CE?0cpo{`klB3`jr64CzDKXs~Tt(}Hz~%~;~{?o@}`l;d%2Ys879w$PPS1PHEH>usN<_DcgMi7DJ| ztgNsEZ{yLA27O4jLl<~_yWC)@6CwFN6ZqyMWcz)b%KO1~%sA$L-LNxonC&dgPr#C~ zEQ`+A#h<-MQ_qt1`-97}>$1cq^todUFHd3hp##i@=K!R{nHQi>&6QW;MSpw<{Q74F z5^%x0Y%G^m2dhfR*KlwV{5Ma)Hdi4@h<9TmLbp#sAM0DuOp5>e{LMOF73`=JEJT8- z@b6`geo~%G+JjnQs|lM=R0HDh8FMiV@8g@D1}}tHWs1&wU6lzOqK%hEz`9r=b0=OS zQMb?H7etAHOOTq%aIHf5sqYT_A%fPyfe9C-$#`RC@5x5upQ091SX89Pxh!)`d=FnQ z=bA&Bihu8)au%n@bWgdsV{b7<6pSy=K&UZKREC!wIY$>FlJh ze&D_^eW2=AZ>Y|SvuD;ZvMDQsLfL!I>~U_?$tHVbWt7NDB|Cdl*)oz9BFZk` zm(Sn7-~G7nM|U3QT<2Wx>ouNBlcS;{sBOiTtJ0CwwFIz<4;h~R@t(SH92oZv?AnGcL2L97q&-{Y* z4bQseCAIysi)c(V3Q`PP3vJVIY~PVmMb_Y{0}y)T z0iLdcu_k5Y1&k%qjGuT%fJuo<3d4v6`jlQuQ1Rkc zm4Ar3Nf}A@5tTu>5PBm@%N3_+OY$p$@RR6x7MB=d1p9|jJ2jP~K7Rf{3X$Yh3$F?- zQ4%3_9aOS@vhx6MBr7_ZrJRNy*>KTv5KCThA4ZKNM5nQo>$rs0dRC7vZy6vNs;VyL zXg#_~pQ*p33zy=Syck=AmN)&VXs`6z>JH4a-tu@7Xmb&x-Z-H#+&XTlYwq=#LV*Zl zZSb7~t@3w=A2a65E%8C6R34**s2lv=Uu>p+tE`NqBZpIZq8jvUWK@@l7U-{|PPlbH zN*uN-cBt*mp^!y$SPYszvrQ%8+qrNOON=q@`L|& zkmzO7zG$_=J^2~KX?J`KC2}c60N!3gGf2a@>){h5HH38CNrPHiq0%w7s9KZNnNkc_ z&bUb$AW1-SoA$+C1|C-Uk_cufg-lH(x&{M(i`&16(cx_tV*!Wzk2oRn%{OgvZZsIQca|TwvC9GxJ=rA zC;x$Y@r2fV=t;%w*RNl{NB{i(-Js58>UuyxKz-YUY|uWx^H^ExwRa>xRX9~BAMce_ zhcB(J-ZRX`7Vuq>_1FFR?VHsdRr8k6rr8wdlH%g(G=cjj8Xk6uL{yQB!36C4M1FpL zmC~W7@71lIYssJPM2rr;x~7@dGg!Y8$0%tOK|sb|EqOFyql9OPmS`UF_e-Mx{7A3bhg=0$H!*S+H9;jvUAyd4-Qy|=d~ z`eL3_O3FCV`R?7jHLS{yd+_OU_^-2WM6N6vaj`xQ_4xB+ET^`%mWBS$-d;PSd!wFn zY?0&V63?Whr0}nsTzSDCJ#(fCG7t4HoaSG>mQ+Q}jimos#LFi=i;=AGmdUUi%vzkT zb7n*FzKWimeIVvGYh-rkPT`*D>Tn)?oio?w$Ibt6FVy@!ImniIo^^(7;VjT*(gdI* zLr*O&JWkTh0;6c|xFDU8ZB*=PYqE}7TBMP5j7Wx*cgaYpi+YJlxbm0ygaT$J36~62 z*)Q-cpKY(xuzNpANLZbn9muWhK1j?;=1E$hth3ggSN8A_&XRC-N@5-<)}(({NPXps zD^;7Pr>A1+?eL)qXO;8o5q@9n=;&ZD_@$|#vD3{QX3-sgrKd-jzw3QVi@Zslli|{P zsL9;mUG<0|&(lLsm#G?jdLc8h!>)#h8=OTM_{ls}DqFEwRBh~h*x6r`)j1{^-`Gcy zk;3sy4Begs{rxen({Fn73w|uNV<#BO+~%7@3|;5@`}(Zl819)SFQFwIKXKfB@W3Uh z7dvu8ELW>U1!j#50e<4{!NJ5ysFM@#WAZD}7bvd2h`)I8;^HHEv)0@jL0Zx+d3kwe z6e@!0*1y+A(H;w!>0g7{lGguDkL>2*P+=16`0xCikkZ;*T`fJ}*jpXG>Fetow-hTf zkb8r%?or8z@4xBSM@tFv-=K?3=I_6AyK*}RU108Ld)+(R;PjLjV|Bt1j)xtIc%@kO zeX(G#iBZzc;^603>G>AF?W?{&XI~ziQb!eaiRkF)G|d~=Ii=1W+^aOJI0z9E5-M<& zNgRlRcPGTKGWQJ*jvdXpxBMmO`aN85aH=)Ol(V(5mbbQ#auRAhefretZ>Z~Fc6m9!Yk2!Wy`870$WWeqrH;y@_^(>w z%J3Xn(d+B$flYDKJv}`m8`TapIRgV)&+=M)x3Ux^{r(<5gi?`diNZ^gg7P>PdGTCS zN@{AeB&vD%nT%h&BTZ^}I8H_01ee)kv%TNzpOwBdv$wIfvEp&^@I)CJ8XA9}edj*k z)sCfbW=ij0MhESecfKu`3Oz-Ozl60T-IcF#PgghJo31h;!Lq;F=6dFBkY#x_Omc@ykbE1}cRc z!opR&qN253NKVJmE+8+m&)A+ z7l+Hs%lw?2T?-ml=;-dI=H;0$F;!Prn^a0Uec`U)>$vy&0S|WBchM$4i8p+tU#5$g z+{fjX-_C;K%Jz0;+3(k{U%S8@G~xR`t!(+1Ra{i$a50-g{&d7f z$oj{ZmtKQ^U-tF)PeDUY+UJFIQPa@WneBB%(Lm$(Q(HafLI3S3D!(vY*LLiGsdcq+ zf*`>pN`=j>Cz16@Z0z?>pFiu}xq}nZAY@=@xcI49^L@Zxi7~s1j@%K4S zwAvt|`bGh^#No-w(mOP9ELdBgMam;?X(C=L|5b`5`Z=vhR1 zIz23X5t|;$HuuT?evX9X#thdk zSYFJeB_c0iELmK9Vc3j8PD~THsiPJ)`45K#-E8i_AP)7}UwY6SJmuzo{A27DH$OjK zS6j5dQTil|G$?ME+Ema-Yv&J_@0^PiaXOC|6Y#>IoTikz?xf(e)xlyQzy6F zuU+mMfS)cHxT!*qat~KES5zpEk4TU2#fl;>N4v`=T%TaZRk!>*jv>1uouaCuQdQ?X zQ3E9qqcy!$y1D7HznpGfLw-fN3M#$kii~enP0jTt8lrT50VRsy;?IeqWc!^#uory-ND1+vNGO?&(Mie z-+Pmd9UQpfWmg;ge_2(0^Vt{|prfa^A7C@or}vf#b90)XqH4BXnQ z^71b{EKnqyd&BbfdU|^0u);Q9RW@6}Dj&N=ggc+X=7(ZS3s60r++JR7520yZJ;em3CMscO6BtXU2F_JN>1jPx{Wra`+gtj!&~f@}_J{O*Z~^xkE! zwA|bwwS=pKv0F1#Y=4h8D$4ElF^6e{Lm_;mSEq~k zwvkbWiS1mycWK{@<9zcCnXrG2oslbmO!?*Ha#0pc-PQ2qYT-`595;zOj|+2gacL68 zm!$8pWnNTtuurWHr=g+If38$oT3QRe{;Kg=rl@^QY%FP8-t<%kjr?v@xa(vD$3&Dzki$NXUFMt^$OFU7CJoKn|uFurSsAn$hEM%-m1{l#koK9_jx)37=d2w;^=J^*Q zA|m2V;1m{4tT3t$zer1)!SMjmYaP`JVph(q@pE+qSUPK`#@7&^``Pq1RiQ!Jd4R}=@9eHYMYR<_T2N&1U z&SHDn2m>oCYleK7oZUbMih_co*6j^c-z{zJ)ca`pBa&2Rlmm9!uZM*Yo1dwspMRI) z{4sxR3Yy^FX5-?*hWzTJ=a-PUTi>DJ4p_n}nM=nGfQsmc^&%lMCiKWtcEb-@cklNO z0Bfc#$7`hs)Zh4Q&YKzyAX2N@LQxR~PbNY$U6A$0jVlSv@{bL8A3@u(v9XQ3HkEnf z_H~*~vH`eTR;c~KKd)whDd~~xxtGtj)-(Nn}ndC;Ou7| zdDp#5l$4anO9K<;^p({=7KmsMle))dW*2gIZQXn%298Y9q7`ZAc~YQ^ExamAI@oAN z)Y+jKj@K=CL?6pcaF3NPX0*mu6JBAT8$fV9vs_Ma(A^{ebx4k($>b!8ZxSJDRnYGw zK_}(w5ycDaaeVQk6m~Mu02J01 zdud-XobmGIXbdLsSy-v@Rd?Jq1v|}f>llB9YNL@ct!)CPZcjkL@A|c}q#yD=Fg2~{ z?Dr(7qQS^#3K7Lu{360gtVEL`ufxv%c>r_i*9!buzq+upa(7-d^vL!zUTZ?W;W7tW zqC)`@9dy+|VYE2gfn97#JP(hEpkiWrFfXrM8+?p&_+(&G*1=2v>jI92ByC&Wz#lib z3g_{+7$FwwsqMYOni}e;sHhj^J6J^%go1L{4&0(GWX0D64D1vy=BUBM$=%?&+v zeA2nU^%Yw8L-UEJ&kcrg+$S8#oNd*2aS9mLg=cDCy508lULC4C(Q=-wVm)Wa0`k~1 z%uf0K-Ip|(c-_3l#0tDA3t|o<2<%hxBu}6u0HxCFB4b82RsNCF_CXQ!`BS%&C`H9V zoxF1xWyAJ1>OEI$>+S8$!o!+};=ao6!y}3x+qv{bW@V6?soH2@>htu=X%9cY?uE7p zOV!-SlC@yGe1S|UqIg5ueAI7R4HtC$5{!A*eZlm%ljXa=epQ#0khCy;Ey&1QQd@D$ zzoJiPw6aLL!2Du6z-IjJ+v}NHKHoZs8lpn1Ni8ic-v=GMp6&ZV*7CC)sd_SyE1Prt z&5#w!FH78cCD95Ofm?nAMNt&-PjutCC@Hs zZf;&1eU25EV_CjTku}_Vs_yaQsTFc{vqLry+r9W7x8uLTuaz3l}s}f|c@Dm$F_tRVXMyj)kToQI9@micT zrVYLo&Fl-tWY?};BP1tRGc(Jb)!2AiBWI{Yb#2F;R~#vBk$=vG|NZzKUUcW>+LxF54x@$m%fbmM zDYQPDlcRKvObXT8lXmBqjr@KOmHR}T^qqd`Z4L<&LJEqTDv@Ju^ux1uB-xc}L9517E#x@4PN;_+#?26fjC={w^ zV1V|6<29_+?_Hc!Ch?a8mI>8X>`AwPQ30WB1l0*qzEDi57ybTEA|q!J(YpS$B!S;sCRRV z>3f(Tpeh+#JZ)%fpImtX+7avtV|DeYq2XJ_cifiyQd8OqSdx2%{dX)pn7A_59yA4} z0_HY&vF9jfBv*2*cFBTx&*EcGL`)0`(8E80l2Bi>7}ReTv6RjjdS%{JVoo&l8iR~) zd7^yMeD`k-TE!`lTO)qqRLvS~clGuXwLd0n2i);6v}~H_nO{Ux8!vCcQ(D$SPDf; zVs^Dy3@C~gIMrciq5HGm!$ezlPJvS74L7BX-!Hldcj}%On)5jS>7NO6tzrB3zm~f7 z$+Iy={9Q06@foW%G&Gd@(*#D}R^(@k?DRL|WAH#eVJ1UJ#g}7XpE*p`T;t{C^@$$L zc5f!Kx3}-JrOA}>du`$E1wT$qNwIMhvS$T$mcVRjWp6&5d&Bc+*S2YH@QveVZtNh7 zu&}O_pE_mIWJ{Pl(AjRH+5d*${a1?o=4xYoLq+iCZ!xJFZ>rVw8^>E38lR4vK~s?o z`xi`Hbq$H37ou}HD+5X4oYrXSwEEl@;x7s17`6W3 zG&ME~+xF2uXbCmlI5{6g&XrASQv57SU2!xnPlC_>{RLL=-?c2m;L%GItp&Lt7=9qa zT)Gm(mPGjL*RS^vH_{~1bAUCqjEI_CxeAKj@1I}qC%z7c8@`U*x^=7SyF;oZhKK`= zhXmWkFG|4Bf>wO*xuh0%06ldu7qqQMejfw0Pl+=*GbDFoqH+n)GlPf~p8bEzoWY}+ z;4p|G7Q5280KjEWHfs@D5r6TfY>==>ZdAvT^?3O3q6#a>D#lP>RG{Fhw?#z~ z72Lk%u^Dr5<$Ig~Z)d(@L`rzOSR*69>4y}rNhJ>^ERTMP0zX~0fb_}1Huh1LVHSEo z%=n!%nqqK&AvMPB0y&dI)Hq`9!^9>*L=-PAgtH^IiU-HM24WG}_TweA z8$9}*-@l6(9*e^3_RK*3{B6@dmx=m%&VUKWhBd-2 zN$G#sLn72@u}^sfN~q__zALCgzu_JQ8=0t?E;yP9Qhrl8eYvBms*2D)>wFVi@bi+m zQwqyp^v#5;w#fO=(>&E`PX9bl#d{gmf$L!Fn>{dgQA9+fY@)Biq0>qvJuy+C{z12x z%OoY)WiiS^<6_~bhyI#=Og9y&`qJ>ahcbCO)MannIESBO8z4d;3y&$7$$@M1EeBeB zDpI!-T9ftqPN+{jnXDt{*qgV3mw$mMK7@wAB9SG5w`-)#kWah~>bot4N1r==BMKny z$C;TK{$&=sf){a7@lJK6rLfs=>Lqq(qurTp9swkBl{PAy;1k_YitUYGt zK>J9Mz~5ExaX*da_P3AXcVFam`&$@?!60=Lz_Mn64>_Ks2dX{s@8-IlgCJlt7zGxU z-PrW6mAzR>WL@wih@B7|p1VO?>+4ZKll0;$?SZp9`ZAk%0f+27&0QJHzF(uhj6x9z z-|{F=rjL}(EWs{)`-bCm+ZcKTA7JIbbiISTo_7H56XDF2=?d?$nUF^F?1LGbEmbUQif$ z5+DEew|)^|@4u{THLlf0q|D6Bb=Lp=_n*P%ib(M36ga3euhBwl;v>NRHC)9$;fS%f zRSU7@6K-)cFm;bH#1*cj{Gq&f5kE@BYFsMiN;gw?fpvF$8vm>?e75s#q8Y@VwC}d+ z$72Uz=L2z%L-#k04N>2zQ_^SZQzV0I? z$N9NJ&ta*_^-#ljZZs=mvf*EovAkGRTB<+z*6Omjlg++V%C8qQZ(W_&_iH+?_P0{u zm$sypMzMh6s8{jU3KSem-vB;IiSLz}=NkX$P4j{#Mlk>8icMTQ2}ifRKzl+kfEQT` zgqSMaq_Y-0sw_%zC$1sLrLprYfKM9?DZI2I(6m`v1tPFW*sS$wugz)?)O-c)06qL^YeUP zU#}=A=^o?JY~d)Dr)n1{!$cJSY>E*H6VT>W%)Tq>K4&m9mtMtYP0zAhc*ldmx~ISY zGv{R`vFe}63zKqjx)<)hl$MfO1PEx*utf`1f}n#Bs|4NE2}jkdXHLCY?!QdV%!a`+ z`QhQK>3;^1%RE9t-LMPJVOh_zmx`)t0Z2LGW32-hPodLWr)uqg0X}#sAziNOlR;$W zL!A0zi>93~m>|DQTBJZ2P0IGEhf#&|2Tol#`R&+?=ZA zAf(fRLUURC_=q4i)rj8?3|A}AAtekwzPLPELVLS(P`0gWjE(I(R`wI3c`WYi3V`2+O7FHeLyJ1kH#e!D&uC-NGKeDaKY;)urRH#c_^Ok&ReDA6|pf8VNTDFMHYUqZ+!u<`kH7U0}s$fDvx2 zZL8nW6}d7IWMH_36$@Q-n|V9P!am&7V*!XpVp<_0JlsfC6`$zQqejL|t&(xp2lv6@ zvjvx2U;~Cx1shAM##AD!at2UGVfo&VWAj!k3np zJ0)Bu6_i*le$3X_@`jm%?U6j}y*+nhAAJ2Q&Kh>t`9jW()ee9Z?mM+E4^a}_Q$IwLhbJ=usbv9GT$BPJ$>*B#I+ zTTj5bkSq7^(AxLcH`Tf&og05r@~x~fYy8D!PRv@%^qZhcy485XHXK`P&bi2UH_?JH z5s2B?u7s%Q=fVjRhy`f2y^Lg=duzHxsL9xf@ar`*$OWSbD6YEf_5ve_fGWRydIa($ zzj<@cw<{zH0RN|*!6S_^8=g&F{xBjkbHqIP>oV_6>KyzR@w;4fF~3G{GG~T-gpWHH zj0nP-!FGW{W9<}9{`Bci-2`|wbWOSSeEr&0`VHN4_*|5&e0}ty_Cp40KP5>3_MV>Z z4LeL$6)5IamX~*c{#<20eDx05f3i$)95LH2PQ821QV0IjOf##d2JYM|TU&9UZEIH1 zNW!Ltk9?GzoUE89#LeAWq@K+Evlqi*mZQr`i%A=a4c8BcO@vtutaIVeZ9H zBG#yK{2eaDzF6`)!03LlMtc5MC=^mRlWSaD)}w{0MOW>JZ-eSaH!1%>6`H>JiLcp3~Bs*%)!4-A|Bqk!ZxnC#&yL|;@mlPy8H-r3*p0t|xzw7=(ie1+F>gO#$C7HLte*2|ca5*`{F z+WhJ_Uy&@ucHWnI&UTGWO;aG|EA2VQ-U0*MVAbN0Qy}Rpn>A*LXXY$x8T~= zZ3O@QGm*vDd!b##ud|Lg2gQM)@o;hM@CgWNEZZMfpPjUvS%Y!=(1aCKdW$bb3Q;O| z+7G$cZ=Vag)6|h4-rE89!lPk?>-sLVqAEmRw70j5Z?RWQ-_OHQEyDfy(I3Z%bm6~d z4MrfZpaOh6S1n$fHr%beSor46m$lSo>ZnM8`)_0MIx)Z3OY_QhD?rhF=&KFoR~xCu zPgk3mn0Q%}>u?>+pn=}rmv0~5&BOJ-jN$8zs3N_vqkF&W$^TVqg9k58c)$y2&vFqRIo=!I5K~?DAz~{=w03ugbSGyrlp#H6p zz3K1%OxfvWDKDF6rU&o|1Kr)-4%NT*_N@8ty|#f_tyXzy=8YpwwynMpn(umh7 zaVt^;R}OjWaQCBetz9*kUDFSi{Z}&WwTO2)l5QCV2g^2uoykST=E~Io#hh5LgPekE z)!O0lan02mfmP4}L|$9K=xTK2|E6?ycZ)(#nZEU=Z`FND)o$D~+zR8T3L+FUF3Yzx zHASU3Yi@B^fvW2QC0kxqmDo`t-2~=bnJnmpPS4E4z)t7q=Dy{uD+v7uUi5I_@3ppg zW_h>j`NY)J&SvoI#z7S|-LHKenEz-bLI@*kTcUdNW-a7qvMX6YRudm}2d`z~ zY_umEO_WO3+uG6(LE$59&ZnH6&u=DdmORzU5N_KHc@1@K3@Q_z_o%D*FFz-v_a3ZK z9uK9xS4d-GW52w~ULH3Zt+4gd3tN^44M!FB;-;GVM7eQ$p1Kn?K0ZDW;&W5^t=rch zRXW8V%rtHJV5={})_D>W^Evp*{jaB2l2T6z3_UV|_RXYtZOO>U$fqg$?3!sAikCxN%rS64>$~7N; zBWe71yaDMor&}D+27wT!J03mlqeHOm z4uxvfJ)FLMJOF_lj5Z7KSHqtP!N9ZbiuLT_ycmF<9woF6%EBw_<^mkK@+#J+V`P#3*Yi<1f zNt15oQ-?{m!S}@%`g(f*oinwtHEs+$2ij!wR<`1lDDDOF4oH6C$(X}Y_-;+BUX91y zgGIS?dbD?{%xtu@{f4)V2fhUZu5nXfodpdOEUcZM4ckw#OZhqSVON=h^Gk%}m8d>v zI?Jo_SQ2tUZ+(1wy_Zdke0}|8B4+Z4Qy)PY;&)ACTQ;Oiw?R529Y5A>^891 zAOm&d^Uh@9$!%eaw@xH({QdnI!~n{H{;8&*cgkX=J?%%#ptp8{fqaN9aUAENW_nnI zypV9}>*?{}jyQrvwYa)!zaIehyeG^!ti$aj?DGY{Hxmye_O8(~k{St&U)M-|8SDul z0DC>FwN)V)3Ozh$&od`ULLQmXk$flJ(s#HcNW_0 z^04#WidWAtK3~RDRG;aLsw_~T zOdMh7aCfj3r=hQ$?|gml*=}jN3}E&-FdntA;m%I{f`WqZN3B)9ZJuBLWiy8!qMXOe z7n!H=EnW(IjTj>u0>E^|Dk?%ECnskIl(yF1^{~ZrDhv|qIM)Xd*^BP*oO`)6T(>9x zn7jc~tn6R*fMj+K=u@Akkw5o7_jG87LrzZJGk8#2o8hdr`oF8NV_{@c#xbokB ztM%u&lipm#$cW+JzY}+e>f|>zGM(#zFg(slIml8o4?{sJx&n@-0_;yDAGYUvpY^Bh zlkJdw0pVpZ?|B+UJ_(YYz3wOwE}Ah<{`H-E-q%N$#UJ@L_lz>*KOMm%uOxc)S4g7#G!YhDY8MgG)+J9n-3|ifBE$ilDn_FzVW%;7{x-1 zpyS(NxI5xk(SZzD4PawTzv=yZ2GFQ#8YUHEMH)eF%a99^hAhtaV>##Ht3%+`3J2`k z{3~cc9wXdrZ6eR+5Cgtpvw-D`@K_Q*tg*<-GR+6?#hILS!SL8x`3Iw5w9;I@+^i{U zj8kHN77GaUQtZ_mS3z#znkplt&lQ*r9LRj3~)aon5g5l35 zE`AZBH@FDc!Ox)?C0aZez-XspWQ=)rud)V0Lv}swo*hr9)GAzu(0u# z3#hqEVmM|#hm2#fdm46X?xnKr>OdarLjL$8f$?X~a&qxBxtHSYx7eu@26A~zbR-gG9xw*X_I~@nv@*IL) zk5c)l!j?3PSFJ~r1K?*e_{h+}pbEmV4rz-ARk_P*62o@3UGeaURN4npva|a?0$L<{ zORSIzoA3c=ay6t#Y607f&A6Y!fSm&Bm}$Ukd;9k75@Kz#w%)$Jn)RAjWo1@0oLVFN z4fDtob*VC*Z`XGNH!9wV^>1(> z?l-Ca`{1)^p!sZjzC~x3kFwT&xC@Y*O^*X1ZYwz9k3kq48XSzn#lVham5`u=5UwJq z?pb1vIN*C*qbeQ<-h@mG5WA$TtY~=qH#nR30Gk-BAx&llTaS){;35NqL#=Hcgw^;> z%jojAuVWN-@f7_Cf7C=c@n9rT`4BY#6vS3XiF7f*r9aaDy8P-DSv4-mk;|W`UxR5I zD*~fTxJ%c}j2uLgbDabN?M!fZ3SU*Tmr~wJgVDbEwP9kV&k=OvjO(_j{I&gMAv|3I1O&ykiArX(;9sgm zMMa5D8Hx}YWhz;B*9`aG{jN^RC=uJDXemO=*D@?qRxw_;kb^6XQmC&vvxF$n*avfXK^y zs2H~onx|=v(Zzc-elqDa`@UKu7y<<(nObVR?Mw1!M+qx)-+lSeAR*knvUd#Ec@h_= z3d#8veon<|m$t>~H1aO4{Iy##5mu`8w__l4qLeOZYJl^l+WIE;>%L(xxXr@ubF$iP zi~JAf1_ln|=$Kv_MCar%y%NAed9$B?ndpx($avBhqx1!bK)GsNQpPUSNCP`$Dw&TD zwiDjJN1sz>@IA@zJ%M^?hZK(kiF|4%i&mKw8m!FV;kL3{IyyIfe8hgv*0;;cQncE0 zh>i=Ovm$>r<~6^tz3gveQ`C0?(V_-Z_b?Lf{c>WkeSLl3HqBCh-ZjDFKHe`EN%My2 z&UsJ}AjJ+v3%P2Q)W4rp$A&fD-`!{1@hM3G?ojnTxrH7!jUu{)eNnmgBnHaC#-wFeZ;%Av*ik6xB6S z?`@xRBj|3x8XGOu#j}@mX~!xCrdLr&I3o5U^eaQOVtCis%l|~UZuqQGLiW>E+;(RX z%lr*6)OY4-atr<+j+_#D?+E5ltyV~s?!X_*4^L}rMd5cp=udXqDU7|mYP28Ap8xiU z_%`1-LH{6`0tph5Pxxt{922J-DXRXN{~ctuO|<`Q&({_fii|`uHlp+@Awk=9Oy`X=hF;l|eC~H6cnEx0QKJhws@p8i;U1w7d&OW+y0Z zKV<1%r|qN!>B=)+9v6>EIoMoZ?}e$LD^&fYJC1SH!Ai%7fr6WEsv?5TEb8{JUm#@y z=(U9uDIcGp;M>DAHn5jIosy-Oos==5WO(R*?OA~G@Q_nN#&>gM;EX1} zbv5#lBdLYmL(vk0^uxoWqiVq3wJ^`KY`??kb6NjXJTN$@y?5EZAY=ZU1Nz>4Lz(v( ztbaQCY>>}{VfTjqo5vkVEW_1HzlD;eSn%Uki+U41Ors;)cDKT$6Vw5ls{g>mO%MQk z5tJM!nNVbzWN*sM@ zSlqYW0|o~4EmZ2GCr=0?A3bWDn!1Fs`Va~@$Zf7M3Vb^NP7&3YX$)%%o@BiLuZB@a zx!N;_PqJ9olCc3T7yv`^U!VMm>6s45tdkRgpYxQ}PZv}|>?%t(vM z4Xvp$8n$v!vvdASWOwh}VVmseo18QntnrVic~)gtm`|va=omgCi?OQnr0G+87W>$W zs*HF39Iuiyp)JxTH|LtNA*1P#*6o+_HL_xXw9{pZB0popyXiwzQO;g6ekXbCOn%11 z=*4)EcWQ*v$ouC!laRf#IXfB-GqSZUZd3AJ8)1bAZ>jdFA4aW+F#JUqee1ggS0d86 z@mA2&uAhKBWu&1IJ)QCh7-tPI+bV#*>8fgKPBd8>$aciBG*S475bv~^`1Q)Fd;gf9?y9q%WXbrzFM=ZKs<%~MD_Mm9A5R3XApigX literal 0 HcmV?d00001 diff --git a/static/themes/default/img/google-play.png b/static/themes/default/icons/google-play.png similarity index 100% rename from static/themes/default/img/google-play.png rename to static/themes/default/icons/google-play.png diff --git a/static/themes/default/img/windows.png b/static/themes/default/icons/windows.png similarity index 100% rename from static/themes/default/img/windows.png rename to static/themes/default/icons/windows.png diff --git a/static/themes/default/img/debian.png b/static/themes/default/img/debian.png deleted file mode 100644 index 78db4dbfc6c8a73e8812c811b2c24428a630557c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4060 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