diff options
-rw-r--r-- | core/img/places/default-app-icon.svg (renamed from core/img/default-app-icon.svg) | 0 | ||||
-rw-r--r-- | settings/Controller/AppSettingsController.php | 26 | ||||
-rw-r--r-- | settings/css/settings.css | 89 | ||||
-rw-r--r-- | settings/js/apps.js | 63 | ||||
-rw-r--r-- | settings/templates/apps.php | 68 | ||||
-rw-r--r-- | tests/Settings/Controller/AppSettingsControllerTest.php | 13 |
6 files changed, 205 insertions, 54 deletions
diff --git a/core/img/default-app-icon.svg b/core/img/places/default-app-icon.svg index 7ef7f086921..7ef7f086921 100644 --- a/core/img/default-app-icon.svg +++ b/core/img/places/default-app-icon.svg diff --git a/settings/Controller/AppSettingsController.php b/settings/Controller/AppSettingsController.php index 6a5b5210c0f..7be6c2bf562 100644 --- a/settings/Controller/AppSettingsController.php +++ b/settings/Controller/AppSettingsController.php @@ -49,6 +49,7 @@ use OCP\L10N\IFactory; class AppSettingsController extends Controller { const CAT_ENABLED = 0; const CAT_DISABLED = 1; + const CAT_ALL_INSTALLED = 2; /** @var \OCP\IL10N */ private $l10n; @@ -103,7 +104,7 @@ class AppSettingsController extends Controller { */ public function viewApps($category = '') { if ($category === '') { - $category = 'enabled'; + $category = 'installed'; } $params = []; @@ -128,8 +129,9 @@ class AppSettingsController extends Controller { $currentLanguage = substr($this->l10nFactory->findLanguage(), 0, 2); $formattedCategories = [ - ['id' => self::CAT_ENABLED, 'ident' => 'enabled', 'displayName' => (string)$this->l10n->t('Enabled')], - ['id' => self::CAT_DISABLED, 'ident' => 'disabled', 'displayName' => (string)$this->l10n->t('Not enabled')], + ['id' => self::CAT_ALL_INSTALLED, 'ident' => 'installed', 'displayName' => (string)$this->l10n->t('Your apps')], + ['id' => self::CAT_ENABLED, 'ident' => 'enabled', 'displayName' => (string)$this->l10n->t('Enabled apps')], + ['id' => self::CAT_DISABLED, 'ident' => 'disabled', 'displayName' => (string)$this->l10n->t('Disabled apps')], ]; $categories = $this->categoryFetcher->get(); foreach($categories as $category) { @@ -270,6 +272,24 @@ class AppSettingsController extends Controller { switch ($category) { // installed apps + case 'installed': + $apps = $appClass->listAllApps(); + + foreach($apps as $key => $app) { + $newVersion = \OC\Installer::isUpdateAvailable($app['id'], $this->appFetcher); + $apps[$key]['update'] = $newVersion; + } + + usort($apps, function ($a, $b) { + $a = (string)$a['name']; + $b = (string)$b['name']; + if ($a === $b) { + return 0; + } + return ($a < $b) ? -1 : 1; + }); + break; + // enabled apps case 'enabled': $apps = $appClass->listAllApps(); $apps = array_filter($apps, function ($app) { diff --git a/settings/css/settings.css b/settings/css/settings.css index 5ca62248c09..a046148dcc4 100644 --- a/settings/css/settings.css +++ b/settings/css/settings.css @@ -525,6 +525,10 @@ input.userFilter {width: 200px;} width: 0; } +#app-category-disabled { + margin-bottom: 20px; +} + .appinfo { margin: 1em 40px; } #app-navigation .appwarning { background: #fcc; @@ -541,9 +545,7 @@ span.version { #app-navigation .app-external, .app-version { - -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=50)"; - filter: alpha(opacity=50); - opacity: .5; + color: rgba(85,85,85,.5); } .app-level { @@ -566,6 +568,7 @@ span.version { .app-score { position: relative; top: 4px; + opacity: .5; } #apps-list { @@ -638,16 +641,22 @@ span.version { } } -@media (max-width: 600px), (min-width: 771px) and (max-width: 900px) { - #apps-list .section { - width: 100%; - box-sizing: border-box; +/* hide app version and level on narrower screens */ +@media only screen and (max-width: 768px) { + #apps-list.installed .app-version, + #apps-list.installed .app-level { + display: none !important; + } +} +@media only screen and (max-width: 700px) { + #apps-list.installed .app-groups { + display: none !important; } } .section h2.app-name { - margin-bottom: 8px; - display: inline; + display: block; + margin: 8px 0; } form.section { position: relative; @@ -661,11 +670,10 @@ form.section { position: relative; } .app-image { - float: left; - padding-right: 10px; - width: 80px; - height: 80px; - opacity: 0.8; + position: relative; + height: 150px; + opacity: 1; + overflow: hidden; } .app-name, .app-version, @@ -725,6 +733,59 @@ form.section { margin-bottom: 1em; } +#apps-list.installed { + display: table; + width: 100%; + height: auto; +} + +#apps-list.installed .section { + display: table-row; + padding: 0; + margin: 0; +} + +#apps-list.installed .section > *{ + display: table-cell; + height: initial; + vertical-align: middle; + float: none; + border-bottom: 1px solid #eee; + padding: 6px; + box-sizing: border-box; +} + +#apps-list.installed .app-image { + width: 44px; + text-align: right; +} + +#apps-list.installed .app-image-icon svg { + margin-top: 5px; + width: 20px; + height: 20px; + opacity: .5; +} + +#apps-list:not(.installed) .app-image-icon svg { + position: absolute; + bottom: 43px; /* position halfway vertically */ + width: 64px; + height: 64px; + opacity: .1; +} + +.installed .actions { + text-align: right; +} + +#apps-list.installed .groups-enable { + margin-top: 0; +} + +#apps-list.installed .groups-enable label { + margin-right: 3px; +} /* LOG */ #log { diff --git a/settings/js/apps.js b/settings/js/apps.js index 9a3ff09337d..6da8c395ecb 100644 --- a/settings/js/apps.js +++ b/settings/js/apps.js @@ -40,8 +40,9 @@ OC.Settings.Apps = OC.Settings.Apps || { } var categories = [ - {displayName: t('settings', 'Enabled'), ident: 'enabled', id: '0'}, - {displayName: t('settings', 'Not enabled'), ident: 'disabled', id: '1'} + {displayName: t('settings', 'Enabled apps'), ident: 'enabled', id: '0'}, + {displayName: t('settings', 'Disabled apps'), ident: 'disabled', id: '1'}, + {displayName: t('settings', 'Your apps'), ident: 'installed', id: '2'} ]; var source = $("#categories-template").html(); @@ -73,8 +74,9 @@ OC.Settings.Apps = OC.Settings.Apps || { if (this._loadCategoryCall) { this._loadCategoryCall.abort(); } + + $('#app-content').addClass('icon-loading'); $('#apps-list') - .addClass('icon-loading') .removeClass('hidden') .html(''); $('#apps-list-empty').addClass('hidden'); @@ -94,16 +96,27 @@ OC.Settings.Apps = OC.Settings.Apps || { // default values for missing fields return _.extend({level: 0}, app); }); - var source = $("#app-template").html(); + var source + if (categoryId === 'enabled' || categoryId === 'disabled' || categoryId === 'installed') { + source = $("#app-template-installed").html(); + $('#apps-list').addClass('installed'); + } else { + source = $("#app-template").html(); + $('#apps-list').removeClass('installed'); + } var template = Handlebars.compile(source); if (appList.length) { appList.sort(function(a,b) { - var levelDiff = b.level - a.level; - if (levelDiff === 0) { - return OC.Util.naturalSortCompare(a.name, b.name); + if (a.active !== b.active) { + return (a.active ? -1 : 1) + } else { + var levelDiff = b.level - a.level; + if (levelDiff === 0) { + return OC.Util.naturalSortCompare(a.name, b.name); + } + return levelDiff; } - return levelDiff; }); var firstExperimental = false; @@ -154,7 +167,7 @@ OC.Settings.Apps = OC.Settings.Apps || { }); }, complete: function() { - $('#apps-list').removeClass('icon-loading'); + $('#app-content').removeClass('icon-loading'); } }); }, @@ -170,7 +183,7 @@ OC.Settings.Apps = OC.Settings.Apps || { app.firstExperimental = firstExperimental; if (!app.preview) { - app.preview = OC.imagePath('core', 'default-app-icon'); + app.preview = OC.imagePath('core', 'places/default-app-icon'); app.previewAsIcon = true; } @@ -222,9 +235,16 @@ OC.Settings.Apps = OC.Settings.Apps || { currentImage.src = app.preview; currentImage.onload = function() { - page.find('.app-image') - .append(OC.Settings.Apps.imageUrl(app.preview, app.fromAppStore)) - .fadeIn(); + /* Trigger color inversion for placeholder image too */ + if(app.previewAsIcon) { + page.find('.app-image') + .append(OC.Settings.Apps.imageUrl(app.preview, false)) + .removeClass('icon-loading'); + } else { + page.find('.app-image') + .append(OC.Settings.Apps.imageUrl(app.preview, app.fromAppStore)) + .removeClass('icon-loading'); + } }; } @@ -257,12 +277,13 @@ OC.Settings.Apps = OC.Settings.Apps || { */ imageUrl : function (url, appfromstore) { - var img = '<svg width="72" height="72" viewBox="0 0 72 72">'; - + var img; if (appfromstore) { + img = '<svg viewBox="0 0 72 72">'; img += '<image x="0" y="0" width="72" height="72" preserveAspectRatio="xMinYMin meet" xlink:href="' + url + '" class="app-icon" /></svg>'; } else { - img += '<image x="0" y="0" width="72" height="72" preserveAspectRatio="xMinYMin meet" filter="url(#invertIcon)" xlink:href="' + url + '?v=' + oc_config.version + '" class="app-icon"></image></svg>'; + img = '<svg width="32" height="32" viewBox="0 0 32 32">'; + img += '<image x="0" y="0" width="32" height="32" preserveAspectRatio="xMinYMin meet" filter="url(#invertIcon)" xlink:href="' + url + '?v=' + oc_config.version + '" class="app-icon"></image></svg>'; } return img; }, @@ -435,15 +456,15 @@ OC.Settings.Apps = OC.Settings.Apps || { } OC.Settings.Apps.hideErrorMessage(appId); - element.val(t('settings','Uninstalling …')); + element.val(t('settings','Removing …')); $.post(OC.filePath('settings','ajax','uninstallapp.php'),{appid:appId},function(result) { if(!result || result.status !== 'success') { - OC.Settings.Apps.showErrorMessage(appId, t('settings','Error while uninstalling app')); - element.val(t('settings','Uninstall')); + OC.Settings.Apps.showErrorMessage(appId, t('settings','Error while removing app')); + element.val(t('settings','Remove')); } else { OC.Settings.Apps.rebuildNavigation(); - element.parent().fadeOut(function() { - element.remove(); + element.parents('#apps-list > .section').fadeOut(function() { + this.remove(); }); } },'json'); diff --git a/settings/templates/apps.php b/settings/templates/apps.php index 0adf5dfcc6f..310513722cf 100644 --- a/settings/templates/apps.php +++ b/settings/templates/apps.php @@ -29,10 +29,54 @@ script( <?php endif; ?> </script> + +<script id="app-template-installed" type="text/x-handlebars"> +<div class="section" id="app-{{id}}"> + <div class="app-image app-image-icon"></div> + <div class="app-name"> + {{#if detailpage}} + <a href="{{detailpage}}" target="_blank" rel="noreferrer">{{name}}</a> + {{else}} + {{name}} + {{/if}} + </div> + <div class="app-version">{{version}}</div> + <div class="app-level"> + {{{level}}}{{#unless internal}}<a href="https://apps.nextcloud.com/apps/{{id}}"><?php p($l->t('View in store'));?> ↗</a>{{/unless}} + </div> + + <div class="app-groups"> + {{#if active}} + <div class="groups-enable"> + <input type="checkbox" class="groups-enable__checkbox checkbox" id="groups_enable-{{id}}"/> + <label for="groups_enable-{{id}}"><?php p($l->t('Limit to groups')); ?></label> + <input type="hidden" id="group_select" title="<?php p($l->t('All')); ?>"> + </div> + {{/if}} + </div> + + <div class="actions"> + <div class="app-dependencies update hidden"> + <p><?php p($l->t('This app has an update available.')); ?></p> + </div> + <div class="warning hidden"></div> + <input class="update hidden" type="submit" value="<?php p($l->t('Update to %s', array('{{update}}'))); ?>" data-appid="{{id}}" /> + {{#if canUnInstall}} + <input class="uninstall" type="submit" value="<?php p($l->t('Remove')); ?>" data-appid="{{id}}" /> + {{/if}} + {{#if active}} + <input class="enable" type="submit" data-appid="{{id}}" data-active="true" value="<?php p($l->t("Disable"));?>"/> + {{else}} + <input class="enable{{#if needsDownload}} needs-download{{/if}}" type="submit" data-appid="{{id}}" data-active="false" {{#unless canInstall}}disabled="disabled"{{/unless}} value="<?php p($l->t("Enable"));?>"/> + {{/if}} + </div> +</div> +</script> + <script id="app-template" type="text/x-handlebars"> <div class="section" id="app-{{id}}"> {{#if preview}} - <div class="app-image{{#if previewAsIcon}} app-image-icon{{/if}} hidden"> + <div class="app-image{{#if previewAsIcon}} app-image-icon{{/if}} icon-loading"> </div> {{/if}} <h2 class="app-name"> @@ -42,14 +86,6 @@ script( {{name}} {{/if}} </h2> - <div class="app-version"> {{version}}</div> - {{#if profilepage}}<a href="{{profilepage}}" target="_blank" rel="noreferrer">{{/if}} - <div class="app-author"><?php p($l->t('by %s', ['{{author}}']));?> - {{#if licence}} - (<?php p($l->t('%s-licensed', ['{{licence}}'])); ?>) - {{/if}} - </div> - {{#if profilepage}}</a>{{/if}} <div class="app-level"> {{{level}}} </div> @@ -59,6 +95,14 @@ script( <div class="app-detailpage"></div> <div class="app-description-container hidden"> + <div class="app-version">{{version}}</div> + {{#if profilepage}}<a href="{{profilepage}}" target="_blank" rel="noreferrer">{{/if}} + <div class="app-author"><?php p($l->t('by %s', ['{{author}}']));?> + {{#if licence}} + (<?php p($l->t('%s-licensed', ['{{licence}}'])); ?>) + {{/if}} + </div> + {{#if profilepage}}</a>{{/if}} <div class="app-description">{{{description}}}</div> <!--<div class="app-changed">{{changed}}</div>--> {{#if documentation}} @@ -134,7 +178,7 @@ script( <input class="enable{{#if needsDownload}} needs-download{{/if}}" type="submit" data-appid="{{id}}" data-active="false" {{#unless canInstall}}disabled="disabled"{{/unless}} value="<?php p($l->t("Enable"));?>"/> {{/if}} {{#if canUnInstall}} - <input class="uninstall" type="submit" value="<?php p($l->t('Uninstall app')); ?>" data-appid="{{id}}" /> + <input class="uninstall" type="submit" value="<?php p($l->t('Remove')); ?>" data-appid="{{id}}" /> {{/if}} <div class="warning hidden"></div> @@ -147,11 +191,11 @@ script( </ul> </div> -<div id="app-content"> +<div id="app-content" class="icon-loading"> <svg class="app-filter"> <defs><filter id="invertIcon"><feColorMatrix in="SourceGraphic" type="matrix" values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0"></feColorMatrix></filter></defs> </svg> - <div id="apps-list" class="icon-loading"></div> + <div id="apps-list"></div> <div id="apps-list-empty" class="hidden emptycontent emptycontent-search"> <div class="icon-search"></div> <h2><?php p($l->t('No apps found for your version')) ?></h2> diff --git a/tests/Settings/Controller/AppSettingsControllerTest.php b/tests/Settings/Controller/AppSettingsControllerTest.php index a3e4a6fd828..14dc33ca191 100644 --- a/tests/Settings/Controller/AppSettingsControllerTest.php +++ b/tests/Settings/Controller/AppSettingsControllerTest.php @@ -92,14 +92,19 @@ class AppSettingsControllerTest extends TestCase { public function testListCategories() { $expected = new JSONResponse([ [ + 'id' => 2, + 'ident' => 'installed', + 'displayName' => 'Your apps', + ], + [ 'id' => 0, 'ident' => 'enabled', - 'displayName' => 'Enabled', + 'displayName' => 'Enabled apps', ], [ 'id' => 1, 'ident' => 'disabled', - 'displayName' => 'Not enabled', + 'displayName' => 'Disabled apps', ], [ 'id' => 'auth', @@ -175,7 +180,7 @@ class AppSettingsControllerTest extends TestCase { $policy = new ContentSecurityPolicy(); $policy->addAllowedImageDomain('https://usercontent.apps.nextcloud.com'); - $expected = new TemplateResponse('settings', 'apps', ['category' => 'enabled', 'appstoreEnabled' => true], 'user'); + $expected = new TemplateResponse('settings', 'apps', ['category' => 'installed', 'appstoreEnabled' => true], 'user'); $expected->setContentSecurityPolicy($policy); $this->assertEquals($expected, $this->appSettingsController->viewApps()); @@ -195,7 +200,7 @@ class AppSettingsControllerTest extends TestCase { $policy = new ContentSecurityPolicy(); $policy->addAllowedImageDomain('https://usercontent.apps.nextcloud.com'); - $expected = new TemplateResponse('settings', 'apps', ['category' => 'enabled', 'appstoreEnabled' => false], 'user'); + $expected = new TemplateResponse('settings', 'apps', ['category' => 'installed', 'appstoreEnabled' => false], 'user'); $expected->setContentSecurityPolicy($policy); $this->assertEquals($expected, $this->appSettingsController->viewApps()); |