Merge pull request #3195 from nextcloud/settings-apps-tabular

Make apps settings tabular
This commit is contained in:
Morris Jobke 2017-04-25 10:25:29 -03:00 committed by GitHub
commit 6f2df5e495
6 changed files with 205 additions and 54 deletions

View file

Before

Width:  |  Height:  |  Size: 1 KiB

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -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) {

View file

@ -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 {

View file

@ -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');

View file

@ -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>

View file

@ -91,15 +91,20 @@ 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());