mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-01-21 07:55:00 +00:00
clients: Cleanup framework
- Move all utilities to a separate clients.py module. Tests too. - Use fewer custom template tags. Actually only one tag is really required. Keeping custom tags minimal is a goal. - Merge the methods to generate app store URLs. - Implement a validator for validating client information and use that instead of enums. - Internationalize the text on template page. - Add missing RPM package case. - Cleanup CSS. Remove unused styles, minimize the styles set. Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org> Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
parent
13c4c687da
commit
acd248e506
146
plinth/clients.py
Normal file
146
plinth/clients.py
Normal file
@ -0,0 +1,146 @@
|
||||
#
|
||||
# This file is part of Plinth.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
"""
|
||||
Utility methods for providing client information.
|
||||
"""
|
||||
|
||||
from django.utils.functional import Promise
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class Desktop_OS(Enum):
|
||||
GNU_LINUX = 'gnu-linux'
|
||||
MAC_OS = 'macos'
|
||||
WINDOWS = 'windows'
|
||||
|
||||
|
||||
class Mobile_OS(Enum):
|
||||
ANDROID = 'android'
|
||||
IOS = 'ios'
|
||||
|
||||
|
||||
class Store(Enum):
|
||||
APP_STORE = 'app-store'
|
||||
F_DROID = 'f-droid'
|
||||
GOOGLE_PLAY = 'google-play'
|
||||
|
||||
|
||||
class Package(Enum):
|
||||
DEB = 'deb'
|
||||
HOMEBREW = 'brew'
|
||||
RPM = 'rpm'
|
||||
|
||||
|
||||
def enum_values(enum):
|
||||
return [option.value for option in list(enum)]
|
||||
|
||||
|
||||
def _check(client, condition):
|
||||
"""Check if any of a list of clients satisfies the given condition"""
|
||||
return any(platform for platform in client['platforms']
|
||||
if condition(platform))
|
||||
|
||||
|
||||
def _client_has_desktop(client):
|
||||
"""Filter to find out whether an application has desktop clients"""
|
||||
return _check(
|
||||
client, lambda platform: platform.get('os') in enum_values(Desktop_OS))
|
||||
|
||||
|
||||
def _client_has_mobile(client):
|
||||
"""Filter to find out whether an application has mobile clients"""
|
||||
return _check(
|
||||
client, lambda platform: platform.get('os') in enum_values(Mobile_OS))
|
||||
|
||||
|
||||
def _client_has_web(client):
|
||||
"""Filter to find out whether an application has web clients"""
|
||||
return _check(client, lambda platform: platform['type'] == 'web')
|
||||
|
||||
|
||||
def _client_has_package(client):
|
||||
"""Filter to find out whether an application has web clients"""
|
||||
return _check(client, lambda platform: platform['type'] == 'package')
|
||||
|
||||
|
||||
def of_type(clients, client_type):
|
||||
"""Filter and get clients of a particular type"""
|
||||
filters = {
|
||||
'mobile': _client_has_mobile,
|
||||
'desktop': _client_has_desktop,
|
||||
'web': _client_has_web,
|
||||
'package': _client_has_package,
|
||||
}
|
||||
return list(filter(filters[client_type], clients))
|
||||
|
||||
|
||||
def store_url(store, package_id):
|
||||
"""Return a full App store URL given package id and type of store."""
|
||||
stores = {
|
||||
'google-play': 'https://play.google.com/store/apps/details?id={}',
|
||||
'f-droid': 'https://f-droid.org/packages/{}'
|
||||
}
|
||||
return stores[store].format(package_id)
|
||||
|
||||
|
||||
def validate(clients):
|
||||
"""Validate the clients' information schema."""
|
||||
assert isinstance(clients, list)
|
||||
for client in clients:
|
||||
_validate_client(client)
|
||||
|
||||
return clients
|
||||
|
||||
|
||||
def _validate_client(client):
|
||||
"""Validate a single client's information schema."""
|
||||
assert isinstance(client, dict)
|
||||
assert 'name' in client
|
||||
assert isinstance(client['platforms'], list)
|
||||
for platform in client['platforms']:
|
||||
_validate_platform(platform)
|
||||
|
||||
|
||||
def _validate_platform(platform):
|
||||
"""Validate a single platform's schema."""
|
||||
assert platform['type'] in ('package', 'download', 'store', 'web')
|
||||
validate_method = globals()['_validate_platform_' + platform['type']]
|
||||
validate_method(platform)
|
||||
|
||||
|
||||
def _validate_platform_package(platform):
|
||||
"""Validate a platform of type package."""
|
||||
assert platform['format'] in enum_values(Package)
|
||||
assert isinstance(platform['name'], (str, Promise))
|
||||
|
||||
|
||||
def _validate_platform_download(platform):
|
||||
"""Validate a platform of type download."""
|
||||
assert platform['os'] in enum_values(Desktop_OS)
|
||||
assert isinstance(platform['url'], (str, Promise))
|
||||
|
||||
|
||||
def _validate_platform_store(platform):
|
||||
"""Validate a platform of type store."""
|
||||
assert platform['os'] in enum_values(Mobile_OS)
|
||||
assert platform['store_name'] in enum_values(Store)
|
||||
assert isinstance(platform['url'], (str, Promise))
|
||||
|
||||
|
||||
def _validate_platform_web(platform):
|
||||
"""Validate a platform of type web."""
|
||||
assert isinstance(platform['url'], (str, Promise))
|
||||
@ -23,113 +23,126 @@
|
||||
|
||||
{% if clients %}
|
||||
<p>
|
||||
<button id="collapsible-button" type="button" class="btn btn-default collapsed"
|
||||
<button id="clients-button" type="button" class="btn btn-default collapsed"
|
||||
data-toggle="collapse" data-target="#clients">
|
||||
Client Apps
|
||||
{% trans "Client Apps" %}
|
||||
<span class="glyphicon glyphicon-chevron-right" aria-hidden="true"></span>
|
||||
</button>
|
||||
</p>
|
||||
|
||||
<table id="clients" class="table table-hover collapse">
|
||||
<div>
|
||||
<table id="clients" class="table table-striped collapse" style="width: 100%">
|
||||
|
||||
{% if clients|has_web_clients %}
|
||||
{% with clients|of_type:'web' as web_clients %}
|
||||
{% for client in web_clients %}
|
||||
<tr>
|
||||
{% if forloop.counter == 1 %}
|
||||
<th rowspan=" {{ web_clients|length }}"> Web </th>
|
||||
{% with clients|clients_of_type:'web' as web_clients %}
|
||||
{% for client in web_clients %}
|
||||
<tr>
|
||||
{% if forloop.counter == 1 %}
|
||||
<th rowspan="{{ web_clients|length }}">{% trans "Web" %}</th>
|
||||
{% endif %}
|
||||
{% for platform in client.platforms %}
|
||||
{% if platform.type == 'web' %}
|
||||
<td>{{ client.name }}</td>
|
||||
<td>
|
||||
<a class="btn btn-success" href="{{ platform.url }}" role="button">
|
||||
{% trans "Launch" %}
|
||||
<span class="glyphicon glyphicon-new-window"></span>
|
||||
</a>
|
||||
</td>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endwith %}
|
||||
|
||||
{% with clients|clients_of_type:'desktop' as desktop_clients %}
|
||||
{% for client in desktop_clients %}
|
||||
<tr>
|
||||
{% if forloop.counter == 1 %}
|
||||
<th rowspan="{{ desktop_clients|length }}">{% trans "Desktop" %}</th>
|
||||
{% endif %}
|
||||
<td>{{ client.name }}</td>
|
||||
<td>
|
||||
{% for platform in client.platforms %}
|
||||
{% if platform.type == 'web' %}
|
||||
<td> {{ client.name }} </td>
|
||||
<td>
|
||||
<a class="btn btn-success" href="{{ platform.url }}" role="button">
|
||||
Launch <span class="glyphicon glyphicon-new-window"></span>
|
||||
</a>
|
||||
</td>
|
||||
{% if platform.type == 'download' %}
|
||||
<a class="btn btn-default" href="{{ platform.url }}" role="button">
|
||||
{% with 'theme/icons/'|add:platform.os|add:'.png' as icon %}
|
||||
<img class="client-icon" src="{% static icon %}" />
|
||||
{% if platform.os == 'gnu-linux' %}
|
||||
{% trans 'Play Store' %}
|
||||
{% elif platform.os == 'windows' %}
|
||||
{% trans 'Windows' %}
|
||||
{% elif platform.os == 'macos' %}
|
||||
{% trans 'macOS' %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endwith %}
|
||||
|
||||
{% if clients|has_desktop_clients %}
|
||||
{% with clients|of_type:'desktop' as desktop_clients %}
|
||||
{% for client in desktop_clients %}
|
||||
<tr>
|
||||
{% if forloop.counter == 1 %}
|
||||
<th rowspan="{{ desktop_clients|length }}"> Desktop </th>
|
||||
{% endif %}
|
||||
<td> {{ client.name }} </td>
|
||||
<td>
|
||||
{% for platform in client.platforms %}
|
||||
{% if platform.type == 'download' %}
|
||||
<a class="btn btn-default" href="{{ platform.url }}" role="button">
|
||||
{% with 'theme/icons/'|add:platform.os|add:'.png' as icon %}
|
||||
<img class="client-icon" src="{% static icon %}" /> {{ platform.os|display_name }}
|
||||
{% endwith %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
|
||||
{% if clients|has_mobile_clients %}
|
||||
{% with clients|of_type:'mobile' as mobile_clients %}
|
||||
{% for client in mobile_clients %}
|
||||
<tr>
|
||||
{% if forloop.counter == 1 %}
|
||||
<th rowspan="{{ mobile_clients|length }}"> Mobile </th>
|
||||
{% endif %}
|
||||
<td> {{ client.name }} </td>
|
||||
<td>
|
||||
{% for platform in client.platforms %}
|
||||
{% if platform.type == 'store' and platform.os == 'android' or platform.os == 'ios' %}
|
||||
<a class="btn btn-default" href="{{ platform.url }}" role="button">
|
||||
{% with 'theme/icons/'|add:platform.store_name|add:'.png' as icon %}
|
||||
<img class="client-icon" src="{% static icon %}" /> {{ platform.store_name|display_name }}
|
||||
{% endwith %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
|
||||
{% if clients|has_package_clients %}
|
||||
{% with clients|of_type:'package' as package_clients %}
|
||||
{% for client in package_clients %}
|
||||
<tr>
|
||||
{% if forloop.counter == 1 %}
|
||||
<th rowspan="{{ package_clients|length }}"> Package </th>
|
||||
{% endif %}
|
||||
<td> {{ client.name }} </td>
|
||||
<td>
|
||||
<div class="row">
|
||||
<ul>
|
||||
{% for platform in client.platforms %}
|
||||
{% if platform.type == 'package' %}
|
||||
{% if platform.type == 'package' and platform.format == 'deb' %}
|
||||
<li> <strong> Debian: </strong> {{ platform.name }} </li>
|
||||
{% endif %}
|
||||
{% if platform.type == 'package' and platform.format == 'homebrew' %}
|
||||
<li> <strong> HomeBrew: </strong> {{ platform.name }} </li>
|
||||
{% endif %}
|
||||
{% with clients|clients_of_type:'mobile' as mobile_clients %}
|
||||
{% for client in mobile_clients %}
|
||||
<tr>
|
||||
{% if forloop.counter == 1 %}
|
||||
<th rowspan="{{ mobile_clients|length }}">{% trans "Mobile" %}</th>
|
||||
{% endif %}
|
||||
<td>{{ client.name }}</td>
|
||||
<td>
|
||||
{% for platform in client.platforms %}
|
||||
{% if platform.type == 'store' and platform.os == 'android' or platform.os == 'ios' %}
|
||||
<a class="btn btn-default" href="{{ platform.url }}" role="button">
|
||||
{% with 'theme/icons/'|add:platform.store_name|add:'.png' as icon %}
|
||||
<img class="client-icon" src="{% static icon %}" />
|
||||
{% if platform.store_name == 'google-play' %}
|
||||
{% trans 'Play Store' %}
|
||||
{% elif platform.store_name == 'f-droid' %}
|
||||
{% trans 'F-Droid' %}
|
||||
{% elif platform.store_name == 'app-store' %}
|
||||
{% trans 'App Store' %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endwith %}
|
||||
|
||||
{% with clients|clients_of_type:'package' as package_clients %}
|
||||
{% for client in package_clients %}
|
||||
<tr>
|
||||
{% if forloop.counter == 1 %}
|
||||
<th rowspan="{{ package_clients|length }}">{% trans "Package" %}</th>
|
||||
{% endif %}
|
||||
<td>{{ client.name }}</td>
|
||||
<td>
|
||||
<div class="row">
|
||||
<ul>
|
||||
{% for platform in client.platforms %}
|
||||
{% if platform.type == 'package' %}
|
||||
{% if platform.format == 'deb' %}
|
||||
<li><strong>{% trans "Debian:" %}</strong> {{ platform.name }}</li>
|
||||
{% endif %}
|
||||
{% if platform.format == 'homebrew' %}
|
||||
<li><strong>{% trans "Homebrew:" %}</strong> {{ platform.name }}</li>
|
||||
{% endif %}
|
||||
{% if platform.format == 'rpm' %}
|
||||
<li><strong>{% trans "RPM:" %}</strong> {{ platform.name }}</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endwith %}
|
||||
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
@ -16,40 +16,14 @@
|
||||
#
|
||||
|
||||
import os
|
||||
from enum import Enum
|
||||
|
||||
from django import template
|
||||
|
||||
from plinth import clients as clients_module
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
class Desktop_OS(Enum):
|
||||
GNU_LINUX = 'gnu-linux'
|
||||
MAC_OS = 'mac-os'
|
||||
WINDOWS = 'windows'
|
||||
|
||||
|
||||
class Mobile_OS(Enum):
|
||||
ANDROID = 'android'
|
||||
IOS = 'ios'
|
||||
|
||||
|
||||
class Store(Enum):
|
||||
APP_STORE = 'app-store'
|
||||
F_DROID = 'f-droid'
|
||||
GOOGLE_PLAY = 'google-play'
|
||||
|
||||
|
||||
class Package(Enum):
|
||||
DEB = 'deb'
|
||||
HOMEBREW = 'brew'
|
||||
RPM = 'rpm'
|
||||
|
||||
|
||||
def enum_values(enum):
|
||||
return [x.value for x in list(enum)]
|
||||
|
||||
|
||||
def mark_active_menuitem(menu, path):
|
||||
"""Mark the best-matching menu item with 'active'
|
||||
|
||||
@ -89,59 +63,7 @@ def show_subsubmenu(context, menu):
|
||||
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_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 enum_values(Desktop_OS))
|
||||
|
||||
|
||||
@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', '') in enum_values(Mobile_OS))
|
||||
|
||||
|
||||
@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_package_clients')
|
||||
def has_package_clients(clients):
|
||||
"""Filter to find out whether an application has web clients"""
|
||||
return __check(clients, lambda x: x['type'] == 'package')
|
||||
|
||||
|
||||
@register.filter(name='of_type')
|
||||
def of_type(clients, typ):
|
||||
@register.filter(name='clients_of_type')
|
||||
def clients_of_type(clients, client_type):
|
||||
"""Filter and get clients of a particular type"""
|
||||
filters = {
|
||||
'mobile': has_mobile_clients,
|
||||
'desktop': has_desktop_clients,
|
||||
'web': has_web_clients,
|
||||
'package': has_package_clients,
|
||||
}
|
||||
return list(filter(filters.get(typ, lambda x: x), clients))
|
||||
|
||||
|
||||
@register.filter(name='display_name')
|
||||
def display_name(string):
|
||||
names = {
|
||||
'gnu-linux': 'GNU/Linux',
|
||||
'windows': 'Windows',
|
||||
'mac-os': 'macOS',
|
||||
'google-play': 'Play Store',
|
||||
'f-droid': 'F-Droid',
|
||||
'app-store': 'App Store'
|
||||
}
|
||||
return names.get(string, string)
|
||||
return clients_module.of_type(clients, client_type)
|
||||
|
||||
52
plinth/tests/test_clients.py
Normal file
52
plinth/tests/test_clients.py
Normal file
@ -0,0 +1,52 @@
|
||||
#
|
||||
# This file is part of Plinth.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
"""
|
||||
Test module for clients module.
|
||||
"""
|
||||
|
||||
import unittest
|
||||
|
||||
from plinth import clients
|
||||
from plinth.modules.deluge.manifest import clients as deluge_clients
|
||||
from plinth.modules.infinoted.manifest import clients as infinoted_clients
|
||||
from plinth.modules.quassel.manifest import clients as quassel_clients
|
||||
from plinth.modules.syncthing.manifest import clients as syncthing_clients
|
||||
from plinth.modules.tor.manifest import clients as tor_clients
|
||||
|
||||
|
||||
class TestClients(unittest.TestCase):
|
||||
"""Test utilities provided by clients module."""
|
||||
|
||||
def test_of_type_web(self):
|
||||
"""Test filtering clients of type web."""
|
||||
self.assertTrue(clients.of_type(syncthing_clients, 'web'))
|
||||
self.assertFalse(clients.of_type(quassel_clients, 'web'))
|
||||
|
||||
def test_of_type_mobile(self):
|
||||
"""Test filtering clients of type mobile."""
|
||||
self.assertTrue(clients.of_type(syncthing_clients, 'mobile'))
|
||||
self.assertFalse(clients.of_type(infinoted_clients, 'mobile'))
|
||||
|
||||
def test_of_type_desktop(self):
|
||||
"""Test filtering clients of type desktop."""
|
||||
self.assertTrue(clients.of_type(syncthing_clients, 'desktop'))
|
||||
self.assertFalse(clients.of_type(deluge_clients, 'desktop'))
|
||||
|
||||
def test_of_type_package(self):
|
||||
"""Test filtering clients of type package."""
|
||||
self.assertTrue(clients.of_type(syncthing_clients, 'package'))
|
||||
self.assertFalse(clients.of_type(tor_clients, 'package'))
|
||||
@ -20,10 +20,6 @@ Test module for custom Django template tags.
|
||||
|
||||
import unittest
|
||||
|
||||
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
|
||||
|
||||
|
||||
@ -68,22 +64,3 @@ class TestShowSubSubMenu(unittest.TestCase):
|
||||
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))
|
||||
|
||||
@ -115,12 +115,3 @@ class YAMLFile(object):
|
||||
|
||||
def is_file_empty(self):
|
||||
return os.stat(self.yaml_file).st_size == 0
|
||||
|
||||
|
||||
def play_store_url(package_id):
|
||||
return 'https://play.google.com/store/apps/details?id={}'.format(
|
||||
package_id)
|
||||
|
||||
|
||||
def f_droid_url(package_id):
|
||||
return 'https://f-droid.org/packages/{}'.format(package_id)
|
||||
|
||||
@ -43,10 +43,8 @@ body {
|
||||
}
|
||||
|
||||
.running-status.loading {
|
||||
border: 4px solid #f3f3f3;
|
||||
/* Light grey */
|
||||
border-top: 4px solid #3498db;
|
||||
/* Blue */
|
||||
border: 4px solid #f3f3f3; /* Light grey */
|
||||
border-top: 4px solid #3498db; /* Blue */
|
||||
border-radius: 50%;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
@ -68,13 +66,11 @@ body {
|
||||
}
|
||||
|
||||
/* Hide log out button if user dropdown is available */
|
||||
|
||||
.js #logout-nojs {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Hide the dropdown icon when javascript is not available */
|
||||
|
||||
.no-js .nav .dropdown .caret {
|
||||
display: none;
|
||||
}
|
||||
@ -85,7 +81,6 @@ body {
|
||||
|
||||
/* Sticky footer styles
|
||||
-------------------------------------------------- */
|
||||
|
||||
footer .license-info {
|
||||
opacity: 0.75;
|
||||
}
|
||||
@ -125,25 +120,13 @@ footer license-info p {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.clients-info {
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
||||
.heading {
|
||||
font-weight: bold;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.clients-info li {
|
||||
padding-left: 15px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.clients-info li span {
|
||||
position: relative;
|
||||
left: -18px;
|
||||
.shortcut-label {
|
||||
min-height: 50px;
|
||||
}
|
||||
|
||||
/*
|
||||
* Clients information
|
||||
*/
|
||||
.client-icon {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
@ -162,20 +145,17 @@ footer license-info p {
|
||||
line-height: 3.1em;
|
||||
}
|
||||
|
||||
.shortcut-label {
|
||||
min-height: 50px;
|
||||
}
|
||||
|
||||
|
||||
/* Icon when collapsible content is shown */
|
||||
#collapsible-button:after {
|
||||
font-family: "Glyphicons Halflings";
|
||||
content: "\e114";
|
||||
#clients-button .glyphicon {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
/* Icon when collapsible content is hidden */
|
||||
#collapsible-button.collapsed:after {
|
||||
#clients-button .glyphicon:before,
|
||||
.no-js #clients-button.collapsed .glyphicon:before {
|
||||
content: "\e114";
|
||||
}
|
||||
|
||||
#clients-button.collapsed .glyphicon:before {
|
||||
content: "\e080";
|
||||
}
|
||||
|
||||
@ -183,14 +163,3 @@ footer license-info p {
|
||||
.no-js .collapse {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.no-js #collapsible-button:after {
|
||||
font-family: "Glyphicons Halflings";
|
||||
content: "\e114";
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.no-js #collapsible-button.collapsed:after {
|
||||
content: "\e114";
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user