Merge pull request #3195 from nextcloud/settings-apps-tabular
Make apps settings tabular
This commit is contained in:
commit
6f2df5e495
6 changed files with 205 additions and 54 deletions
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 1 KiB |
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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());
|
||||
|
|
Loading…
Reference in a new issue