Merge pull request #205 from e-lie/enh-add-levels-to-applist

Redesign app install list : levels, multinstance confusing
This commit is contained in:
Alexandre Aubin 2018-10-27 16:21:40 +02:00 committed by GitHub
commit 15a430f16d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 237 additions and 23 deletions

View file

@ -10,6 +10,7 @@
"handlebars": "4.0.11",
"sammy": "0.7.6",
"js-cookie": "2.1.0",
"isotope-layout": "3.0.6",
"source-sans-pro": "git://github.com/adobe-fonts/source-sans-pro.git#2.020R-ro/1.075R-it",
"source-code-pro": "git://github.com/adobe-fonts/source-code-pro.git#2.010R-ro/1.030R-it"
}

View file

@ -557,6 +557,60 @@ input[type='radio'].nice-radio {
}
}
// only one card for small screens
.app-card {
width: 100%;
.btn-group {
width: 100%;
.btn{
margin-left: 0;
}
}
}
#app-filter-input{
.dropdown-menu{
min-width: 120px;
right: 0;
left: auto;
}
.menu-item{
padding: 3px 10px;
}
}
.app-title {
margin-top: 5px;
}
.app-card-desc {
height: 6rem;
overflow: hidden;
}
.app-card-date-maintainer {
text-align: right;
max-height: 18px;
margin-bottom: 3px;
margin-right: 7px;
margin-top: -5px;
}
.app-card .panel-body {
padding: 1.5rem;
h3 {
margin-top: 0;
margin-bottom: .5rem;
}
.category {
margin-bottom: .5rem;
}
}
/** Flash messages **/
#flashMessage {
@ -600,6 +654,17 @@ input[type='radio'].nice-radio {
}
}
// display 2 cards between 640 and 992px
.app-card {
width: 47.9%;
margin: 1%;
}
.grid {
background: #f5f5f5;
border: 1px solid #ddd;
}
#flashMessage {
position: fixed;
top: 0;
@ -689,3 +754,12 @@ input[type='radio'].nice-radio {
}
}
// bootstrap breakpoint for large screen is 992px
@media screen and (min-width: 992px) {
.app-card {
// display 3 cards by row
width: 31.3%;
margin: 1%;
}
}

View file

@ -47,6 +47,7 @@ gulp.task('js', function() {
'bower_components/sammy/lib/plugins/sammy.json.js',
'bower_components/sammy/lib/plugins/sammy.storage.js',
'bower_components/bootstrap/dist/js/bootstrap.js',
'bower_components/isotope-layout/dist/isotope.pkgd.js',
'js/yunohost/y18n.js',
'js/yunohost/main.js',
'js/yunohost/helpers.js',

View file

@ -17,16 +17,68 @@
});
});
function levelToColor(level) {
if (level > 6) {
return 'success';
}
else if (level >= 2) {
return 'warning';
}
else if (isNaN(level)) {
return 'default';
} else {
return 'danger'
}
}
function stateToColor(state) {
if (state === "working" || state === "official") {
return 'success';
}
else {
return 'danger';
}
}
function combineColors(stateColor, levelColor, installable) {
if (stateColor === "dangers" || levelColor === "danger") {
return 'danger';
}
if (stateColor === "warnings" || levelColor === "warnings" || levelColor === "default") {
return 'warning';
}
else {
return 'success';
}
}
// List available apps
app.get('#/apps/install', function (c) {
c.api('/apps', function(data) { // http://api.yunohost.org/#!/app/app_list_get_8
c.api('/apps?raw', function(dataraw) { // http://api.yunohost.org/#!/app/app_list_get_8
var apps = [];
c.api('/apps', function (data) { // http://api.yunohost.org/#!/app/app_list_get_8
c.api('/apps?raw', function (dataraw) { // http://api.yunohost.org/#!/app/app_list_get_8
var apps = []
$.each(data['apps'], function(k, v) {
// Keep only uninstalled apps, or multi-instance apps
if ((!v['installed'] || dataraw[v['id']].manifest.multi_instance) && !v['id'].match(/__[0-9]{1,5}$/)) {
// Check app source
dataraw[v['id']]['official'] = (dataraw[v['id']]['repository'] == 'yunohost');
if (dataraw[v['id']]['state'] === "validated")
{
dataraw[v['id']]['state'] = "official";
}
var state = dataraw[v['id']]['state'];
var levelFormatted = parseInt(dataraw[v['id']]['level']);
var isWorking = (state === 'working' || state === 'official') && levelFormatted > 0;
// Keep only the first instance of each app and remove community not working apps
if (!v['id'].match(/__[0-9]{1,5}$/) && (dataraw[v['id']]['repository'] === 'yunohost' || state !== 'notworking')) {
dataraw[v['id']]['installable'] = (!v['installed'] || dataraw[v['id']].manifest.multi_instance)
dataraw[v['id']]['isCommunity'] = !(dataraw[v['id']]['repository'] === 'yunohost');
dataraw[v['id']]['levelFormatted'] = isNaN(levelFormatted) ? '?' : levelFormatted;
dataraw[v['id']]['levelColor'] = levelToColor(levelFormatted);
dataraw[v['id']]['stateColor'] = stateToColor(state);
dataraw[v['id']]['installColor'] = combineColors(dataraw[v['id']]['stateColor'], dataraw[v['id']]['levelColor']);
dataraw[v['id']]['displayLicense'] = (dataraw[v['id']]['manifest']['license'] !== undefined
&& dataraw[v['id']]['manifest']['license'] !== 'free');
dataraw[v['id']]['updateDate'] = dataraw[v['id']]['lastUpdate'] * 1000 || 0;
dataraw[v['id']]['isSafe'] = (dataraw[v['id']]['installColor'] !== 'danger');
dataraw[v['id']]['isWorking'] = isWorking ? "isworking" : "notFullyWorking";
jQuery.extend(dataraw[v['id']], v);
apps.push(dataraw[v['id']]);
@ -35,7 +87,45 @@
// Sort app list
c.arraySortById(apps);
c.view('app/app_list_install', {apps: apps});
// setup filtering of apps once the view is loaded
function setupFilterEvents () {
// Uses plugin isotope to filter apps (we could had ordering to)
var cardGrid = jQuery('.grid').isotope({
itemSelector: '.app-card',
layoutMode: 'fitRows',
transitionDuration: 200
});
filterByClassAndName = function () {
var input = jQuery("#filter-app-cards").val().toLowerCase();
var inputMatch = (jQuery(this).find('.app-title').text().toLowerCase().indexOf(input) > -1);
var filterClass = jQuery("#dropdownFilter").attr("data-filter");
var classMatch = (filterClass === '*') ? true : jQuery(this).hasClass(filterClass);
return inputMatch && classMatch;
},
// Keep only official apps at first render
cardGrid.isotope({ filter: '.isworking' });
jQuery('.dropdownFilter').on('click', function() {
// change dropdown label
jQuery('#app-cards-list-filter-text').text(jQuery(this).find('.menu-item').text());
// change filter attribute
jQuery('#dropdownFilter').attr("data-filter", jQuery(this).attr("data-filter"));
// filter !
cardGrid.isotope({ filter: filterByClassAndName });
});
jQuery("#filter-app-cards").on("keyup", function() {
cardGrid.isotope({ filter: filterByClassAndName });
});
};
// render
c.view('app/app_list_install', {apps: apps}, setupFilterEvents);
});
});
});
@ -681,7 +771,7 @@
c.view('app/app_changelabel', data);
});
});
// Change app label
app.post('#/apps/:app/changelabel', function (c) {
params = {'new_label': c.params['label']};
@ -718,7 +808,7 @@
});
});
});
// Change app URL
app.post('#/apps/:app/changeurl', function (c) {
c.confirm(
@ -736,5 +826,5 @@
c.redirect('#/apps/'+ c.params['app'] + '/changeurl');
}
);
});
});
})();

View file

@ -5,6 +5,7 @@
"remove": "Remove",
"administration_password": "Administration password",
"allowed_users": "Allowed users",
"all_apps": "All apps",
"api_not_responding": "API is not responding",
"app_access": "Access",
"app_access_addall_btn": "Enable access to all",
@ -28,6 +29,8 @@
"app_install_cancel": "Installation cancelled.",
"app_install_custom_no_manifest": "No manifest.json file",
"app_list": "App list",
"app_license": "License of the app",
"app_level": "App level",
"app_make_default": "Make default",
"app_no_actions": "This application doesn't have any actions",
"app_repository": "Application origin: ",
@ -95,6 +98,7 @@
"cpu_load": "CPU Load",
"created_at": "Created at",
"cumulative_usage": "Cumulative usage",
"current_maintainer_title": "Current maintainer of this package",
"custom_app_install": "Install custom app",
"custom_app_url_only_github": "Currently only from GitHub",
"default": "Default",
@ -178,6 +182,7 @@
"ipv6": "IPv6",
"label": "Label",
"label_for_manifestname": "Label for %s",
"level": "level",
"loading": "Loading …",
"local_archives": "Local archives",
"local_ip": "Local IP",
@ -215,6 +220,8 @@
"no_user_to_add": "No more users to add.",
"non_compatible_api": "Non-compatible API",
"ok": "OK",
"only_validated_apps": "Only validated apps",
"only_working_apps": "Only working apps",
"open": "Open",
"operations": "Operations",
"os": "OS",
@ -270,6 +277,7 @@
"run": "Run",
"running": "Running",
"save": "Save",
"search_for_apps": "Search for apps...",
"select_user": "Select user",
"service_description": "Description:",
"service_log": "%s log",
@ -330,6 +338,7 @@
"uninstall": "Uninstall",
"unknown_action": "Unknown action %s",
"unknown_argument": "Unknown argument : %s",
"unmaintained": "Unmaintained",
"upload": "Upload",
"upload_archive": "Archive upload",
"upnp": "UPnP",

View file

@ -15,19 +15,58 @@
<div class="separator"></div>
<div class="list-group">
<div class="input-group" id="app-filter-input">
<span class="input-group-addon"><i class="fas fa-search"></i></span>
<input type="text" id="filter-app-cards" class="form-control" role="textbox" placeholder="{{t 'search_for_apps'}}" aria-describedby="basic-addon0"/>
<div class="input-group-btn">
<button type="button" role="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span id="app-cards-list-filter-text">{{t 'only_working_apps'}}</span> <span class="caret"></span>
</button>
<ul id="dropdownFilter" class="dropdown-menu" data-filter="isworking" role="menu">
<li role="presentation" class="button dropdownFilter" data-filter="isworking"><a class="menu-item" role="menu-item" tabindex="-1">{{t 'only_working_apps'}}</a></li>
<li role="presentation" class="button dropdownFilter" data-filter="validated"><a class="menu-item" role="menu-item" tabindex="-1">{{t 'only_validated_apps'}}</a></li>
<li role="presentation" class="button dropdownFilter" data-filter="*"><a class="menu-item" role="menu-item" tabindex="-1">{{t 'all_apps'}}</a></li>
</ul>
</div>
</div>
<div class="separator"></div>
<div class="list-group grid">
{{#apps}}
<a href="#/apps/install/{{id}}" class="list-group-item slide" title="{{t 'install'}}">
<span class="fa-chevron-right pull-right"></span>
<h2 class="list-group-item-heading">
{{name}} <small>{{id}}</small>
</h2>
<p class="list-group-item-text">{{description}}</p>
{{^official}}
<p class="list-group-item-text">{{t 'app_repository'}}{{repository}}</p>
<p class="list-group-item-text">{{t 'app_state'}}{{state}}</p>
{{/official}}
</a>
<div class="app-card panel panel-default {{status}} {{state}} {{isWorking}} {{level}}-level">
<div class="panel-body">
<h2 class="app-title">{{name}}</h2>
<div class="category">
{{#isCommunity}} <span class="label label-info label-as-badge app-status">{{t 'community'}}</span>{{/isCommunity}}
<span class="label label-{{stateColor}} label-as-badge app-state">{{t state}}</span>
<a target="_BLANK" href="https://yunohost.org/#/packaging_apps_levels"><span class="label label-{{levelColor}} label-as-badge app-level" title="{{t 'app_level'}}">{{t 'level'}} {{levelFormatted}}</span></a>
{{#displayLicense}}<span class="label label-default app-license" title="{{t 'app_license'}}">{{license}}</span>{{/displayLicense}}
</div>
<div class="app-card-desc">{{description}}</div>
</div>
<div class="app-card-date-maintainer">
<i class="fas fa-sync"></i>{{formatDate updateDate day="numeric" month="long" year="numeric"}} -
{{#maintained}}<span title="{{t 'current_maintainer_title'}}" class="maintained"></span><i class="fa-user"></i> {{manifest.maintainer.name}}</span>{{/maintained}}
{{^maintained}}<i class="fas fa-warning"></i> {{t 'unmaintained'}}{{/maintained}}
</div>
<div class="btn-group" role="group">
<a href="{{git.url}}" target="_BLANK" type="button" role="button" class="btn btn-default col-xs-4">
<i class="fa-globe"></i> Code
</a>
<a href="{{git.url}}/blob/master/README.md" target="_BLANK" type="button" role="button" class="btn btn-default col-xs-4">
<i class="fa-book"></i> Readme
</a>
{{#installable}}
<a href="#/apps/install/{{id}}" type="button" role="button" class="btn btn-{{installColor}} col-xs-4 active">
<i class="fa-plus"></i> {{t 'install'}}{{^isSafe}} <i class="fa-warning"></i>{{/isSafe}}
</a>
{{/installable}}
{{^installable}}
<span type="button" class="btn btn-default col-sm-4 active disabled"> {{t 'installed'}}</span>
{{/installable}}
</div>
</div>
{{/apps}}
</div>