diff --git a/src/bower.json b/src/bower.json index 98521387..0d0e89f5 100644 --- a/src/bower.json +++ b/src/bower.json @@ -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" } diff --git a/src/css/style.less b/src/css/style.less index 8816d715..5eaaf22f 100644 --- a/src/css/style.less +++ b/src/css/style.less @@ -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%; + } +} diff --git a/src/gulpfile.js b/src/gulpfile.js index add727a7..5875faf9 100644 --- a/src/gulpfile.js +++ b/src/gulpfile.js @@ -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', diff --git a/src/js/yunohost/controllers/apps.js b/src/js/yunohost/controllers/apps.js index b1fe4cab..f9e0a028 100644 --- a/src/js/yunohost/controllers/apps.js +++ b/src/js/yunohost/controllers/apps.js @@ -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'); } ); - }); + }); })(); diff --git a/src/locales/en.json b/src/locales/en.json index 857c211d..6954d7c0 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -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", diff --git a/src/views/app/app_list_install.ms b/src/views/app/app_list_install.ms index 00165fe4..cded477d 100644 --- a/src/views/app/app_list_install.ms +++ b/src/views/app/app_list_install.ms @@ -15,19 +15,58 @@
-{{description}}
- {{^official}} -{{t 'app_repository'}}{{repository}}
-{{t 'app_state'}}{{state}}
- {{/official}} - +