Add a dedicated page for the recommended apps installation

Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
Signed-off-by: npmbuildbot[bot] <npmbuildbot[bot]@users.noreply.github.com>
This commit is contained in:
Christoph Wurst 2019-12-04 20:18:58 +01:00 committed by Roeland Jago Douma
parent 97deaf85b9
commit 302558cfd2
No known key found for this signature in database
GPG Key ID: F941078878347C0C
28 changed files with 504 additions and 148 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
!function(t){function e(e){for(var n,o,i=e[0],a=e[1],s=0,c=[];s<i.length;s++)o=i[s],Object.prototype.hasOwnProperty.call(r,o)&&r[o]&&c.push(r[o][0]),r[o]=0;for(n in a)Object.prototype.hasOwnProperty.call(a,n)&&(t[n]=a[n]);for(u&&u(e);c.length;)c.shift()()}var n={},r={2:0};function o(e){if(n[e])return n[e].exports;var r=n[e]={i:e,l:!1,exports:{}};return t[e].call(r.exports,r,r.exports,o),r.l=!0,r.exports}o.e=function(t){var e=[],n=r[t];if(0!==n)if(n)e.push(n[2]);else{var i=new Promise((function(e,o){n=r[t]=[e,o]}));e.push(n[2]=i);var a,s=document.createElement("script");s.charset="utf-8",s.timeout=120,o.nc&&s.setAttribute("nonce",o.nc),s.src=function(t){return o.p+"vue-"+({}[t]||t)+".js?v="+{0:"f1063acdadacc88028ec",4:"57623c76b02684c2b53b",5:"6aedce1af43a98e73126",6:"60121aec3037a1fa3701"}[t]}(t);var u=new Error;a=function(e){s.onerror=s.onload=null,clearTimeout(c);var n=r[t];if(0!==n){if(n){var o=e&&("load"===e.type?"missing":e.type),i=e&&e.target&&e.target.src;u.message="Loading chunk "+t+" failed.\n("+o+": "+i+")",u.name="ChunkLoadError",u.type=o,u.request=i,n[1](u)}r[t]=void 0}};var c=setTimeout((function(){a({type:"timeout",target:s})}),12e4);s.onerror=s.onload=a,document.head.appendChild(s)}return Promise.all(e)},o.m=t,o.c=n,o.d=function(t,e,n){o.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:n})},o.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},o.t=function(t,e){if(1&e&&(t=o(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var n=Object.create(null);if(o.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var r in t)o.d(n,r,function(e){return t[e]}.bind(null,r));return n},o.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return o.d(e,"a",e),e},o.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},o.p="/js/",o.oe=function(t){throw console.error(t),t};var i=window.webpackJsonpSettings=window.webpackJsonpSettings||[],a=i.push.bind(i);i.push=e,i=i.slice();for(var s=0;s<i.length;s++)e(i[s]);var u=a;o(o.s=313)}([function(t,e,n){var r=n(2),o=n(34),i=n(50),a=n(224),s=r.Symbol,u=o("wks");t.exports=function(t){return u[t]||(u[t]=a&&s[t]||(a?s:i)("Symbol."+t))}},function(t,e,n){"use strict";var r=n(79),o=n(163),i=Object.prototype.toString;function a(t){return"[object Array]"===i.call(t)}function s(t){return null!==t&&"object"==typeof t}function u(t){return"[object Function]"===i.call(t)}function c(t,e){if(null!=t)if("object"!=typeof t&&(t=[t]),a(t))for(var n=0,r=t.length;n<r;n++)e.call(null,t[n],n,t);else for(var o in t)Object.prototype.hasOwnProperty.call(t,o)&&e.call(null,t[o],o,t)}t.exports={isArray:a,isArrayBuffer:function(t){return"[object ArrayBuffer]"===i.call(t)},isBuffer:o,isFormData:function(t){return"undefined"!=typeof FormData&&t instanceof FormData},isArrayBufferView:function(t){return"undefined"!=typeof ArrayBuffer&&ArrayBuffer.isView?ArrayBuffer.isView(t):t&&t.buffer&&t.buffer instanceof ArrayBuffer},isString:function(t){return"string"==typeof t},isNumber:function(t){return"number"==typeof t},isObject:s,isUndefined:function(t){return void 0===t},isDate:function(t){return"[object Date]"===i.call(t)},isFile:function(t){return"[object File]"===i.call(t)},isBlob:function(t){return"[object Blob]"===i.call(t)},isFunction:u,isStream:function(t){return s(t)&&u(t.pipe)},isURLSearchParams:function(t){return"undefined"!=typeof URLSearchParams&&t instanceof URLSearchParams},isStandardBrowserEnv:function(){return("undefined"==typeof navigator||"ReactNative"!==navigator.product&&"NativeScript"!==navigator.product&&"NS"!==navigator.product)&&("undefined"!=typeof window&&"undefined"!=typeof document)},forEach:c,merge:function t(){var e={};function n(n,r){"object"==typeof e[r]&&"object"==typeof n?e[r]=t(e[r],n):e[r]=n}for(var r=0,o=arguments.length;r<o;r++)c(arguments[r],n);return e},deepMerge:function t(){var e={};function n(n,r){"object"==typeof e[r]&&"object"==typeof n?e[r]=t(e[r],n):e[r]="object"==typeof n?t({},n):n}for(var r=0,o=arguments.length;r<o;r++)c(arguments[r],n);return e},extend:function(t,e,n){return c(e,(function(e,o){t[o]=n&&"function"==typeof e?r(e,n):e})),t},trim:function(t){return t.replace(/^\s*/,"").replace(/\s*$/,"")}}},function(t,e,n){(function(e){var n="object",r=function(t){return t&&t.Math==Math&&t};t.exports=r(typeof globalThis==n&&globalThis)||r(typeof window==n&&window)||r(typeof self==n&&self)||r(typeof e==n&&e)||Function("return this")()}).call(this,n(7))},function(t,e){t.exports=function(t){try{return!!t()}catch(t){return!0}}},function(t,e,n){"use strict";n.r(e),function(t,n){
!function(t){function e(e){for(var n,o,i=e[0],a=e[1],s=0,c=[];s<i.length;s++)o=i[s],Object.prototype.hasOwnProperty.call(r,o)&&r[o]&&c.push(r[o][0]),r[o]=0;for(n in a)Object.prototype.hasOwnProperty.call(a,n)&&(t[n]=a[n]);for(u&&u(e);c.length;)c.shift()()}var n={},r={2:0};function o(e){if(n[e])return n[e].exports;var r=n[e]={i:e,l:!1,exports:{}};return t[e].call(r.exports,r,r.exports,o),r.l=!0,r.exports}o.e=function(t){var e=[],n=r[t];if(0!==n)if(n)e.push(n[2]);else{var i=new Promise((function(e,o){n=r[t]=[e,o]}));e.push(n[2]=i);var a,s=document.createElement("script");s.charset="utf-8",s.timeout=120,o.nc&&s.setAttribute("nonce",o.nc),s.src=function(t){return o.p+"vue-"+({}[t]||t)+".js?v="+{0:"f1063acdadacc88028ec",4:"1b9a9a854189f065cd3c",5:"d26e956d157c0c427e8e",6:"60121aec3037a1fa3701"}[t]}(t);var u=new Error;a=function(e){s.onerror=s.onload=null,clearTimeout(c);var n=r[t];if(0!==n){if(n){var o=e&&("load"===e.type?"missing":e.type),i=e&&e.target&&e.target.src;u.message="Loading chunk "+t+" failed.\n("+o+": "+i+")",u.name="ChunkLoadError",u.type=o,u.request=i,n[1](u)}r[t]=void 0}};var c=setTimeout((function(){a({type:"timeout",target:s})}),12e4);s.onerror=s.onload=a,document.head.appendChild(s)}return Promise.all(e)},o.m=t,o.c=n,o.d=function(t,e,n){o.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:n})},o.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},o.t=function(t,e){if(1&e&&(t=o(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var n=Object.create(null);if(o.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var r in t)o.d(n,r,function(e){return t[e]}.bind(null,r));return n},o.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return o.d(e,"a",e),e},o.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},o.p="/js/",o.oe=function(t){throw console.error(t),t};var i=window.webpackJsonpSettings=window.webpackJsonpSettings||[],a=i.push.bind(i);i.push=e,i=i.slice();for(var s=0;s<i.length;s++)e(i[s]);var u=a;o(o.s=313)}([function(t,e,n){var r=n(2),o=n(34),i=n(50),a=n(224),s=r.Symbol,u=o("wks");t.exports=function(t){return u[t]||(u[t]=a&&s[t]||(a?s:i)("Symbol."+t))}},function(t,e,n){"use strict";var r=n(79),o=n(163),i=Object.prototype.toString;function a(t){return"[object Array]"===i.call(t)}function s(t){return null!==t&&"object"==typeof t}function u(t){return"[object Function]"===i.call(t)}function c(t,e){if(null!=t)if("object"!=typeof t&&(t=[t]),a(t))for(var n=0,r=t.length;n<r;n++)e.call(null,t[n],n,t);else for(var o in t)Object.prototype.hasOwnProperty.call(t,o)&&e.call(null,t[o],o,t)}t.exports={isArray:a,isArrayBuffer:function(t){return"[object ArrayBuffer]"===i.call(t)},isBuffer:o,isFormData:function(t){return"undefined"!=typeof FormData&&t instanceof FormData},isArrayBufferView:function(t){return"undefined"!=typeof ArrayBuffer&&ArrayBuffer.isView?ArrayBuffer.isView(t):t&&t.buffer&&t.buffer instanceof ArrayBuffer},isString:function(t){return"string"==typeof t},isNumber:function(t){return"number"==typeof t},isObject:s,isUndefined:function(t){return void 0===t},isDate:function(t){return"[object Date]"===i.call(t)},isFile:function(t){return"[object File]"===i.call(t)},isBlob:function(t){return"[object Blob]"===i.call(t)},isFunction:u,isStream:function(t){return s(t)&&u(t.pipe)},isURLSearchParams:function(t){return"undefined"!=typeof URLSearchParams&&t instanceof URLSearchParams},isStandardBrowserEnv:function(){return("undefined"==typeof navigator||"ReactNative"!==navigator.product&&"NativeScript"!==navigator.product&&"NS"!==navigator.product)&&("undefined"!=typeof window&&"undefined"!=typeof document)},forEach:c,merge:function t(){var e={};function n(n,r){"object"==typeof e[r]&&"object"==typeof n?e[r]=t(e[r],n):e[r]=n}for(var r=0,o=arguments.length;r<o;r++)c(arguments[r],n);return e},deepMerge:function t(){var e={};function n(n,r){"object"==typeof e[r]&&"object"==typeof n?e[r]=t(e[r],n):e[r]="object"==typeof n?t({},n):n}for(var r=0,o=arguments.length;r<o;r++)c(arguments[r],n);return e},extend:function(t,e,n){return c(e,(function(e,o){t[o]=n&&"function"==typeof e?r(e,n):e})),t},trim:function(t){return t.replace(/^\s*/,"").replace(/\s*$/,"")}}},function(t,e,n){(function(e){var n="object",r=function(t){return t&&t.Math==Math&&t};t.exports=r(typeof globalThis==n&&globalThis)||r(typeof window==n&&window)||r(typeof self==n&&self)||r(typeof e==n&&e)||Function("return this")()}).call(this,n(7))},function(t,e){t.exports=function(t){try{return!!t()}catch(t){return!0}}},function(t,e,n){"use strict";n.r(e),function(t,n){
/*!
* Vue.js v2.6.10
* (c) 2014-2019 Evan You
@ -110,4 +110,4 @@ o.default.use(Jt.a);var he={API_FAILURE:function(e,n){try{var r=n.error.response
*
*/
o.default.use(i.a,{defaultHtml:!1}),Object(a.sync)(ve,Xt),r.nc=btoa(OC.requestToken),r.p=OC.linkTo("settings","js/"),o.default.prototype.t=t,o.default.prototype.OC=OC,o.default.prototype.OCA=OCA,o.default.prototype.oc_userconfig=oc_userconfig;var me=new o.default({router:Xt,store:ve,render:function(t){return t(c)}}).$mount("#content")}]);
//# sourceMappingURL=vue-settings-apps-users-management.js.map?v=6f99ac5627310d3591f4
//# sourceMappingURL=vue-settings-apps-users-management.js.map?v=346dee5757d76be545c2

File diff suppressed because one or more lines are too long

View File

@ -96,11 +96,9 @@
</template>
<script>
import pLimit from 'p-limit'
import AppItem from './AppList/AppItem'
import PrefixMixin from './PrefixMixin'
import recommended from '../recommendedApps'
import pLimit from 'p-limit'
export default {
name: 'AppList',
@ -131,26 +129,26 @@ export default {
return OC.Util.naturalSortCompare(sortStringA, sortStringB)
})
switch (this.category) {
case 'installed':
if (this.category === 'installed') {
return apps.filter(app => app.installed)
case 'recommended':
return apps.filter(app => recommended.includes(app.id))
case 'enabled':
return apps.filter(app => app.active && app.installed)
case 'disabled':
return apps.filter(app => !app.active && app.installed)
case 'app-bundles':
return apps.filter(app => app.bundles)
case 'updates':
return apps.filter(app => app.update)
default:
// filter app store categories
return apps.filter(app => {
return app.appstore && app.category !== undefined
&& (app.category === this.category || app.category.indexOf(this.category) > -1)
})
}
if (this.category === 'enabled') {
return apps.filter(app => app.active && app.installed)
}
if (this.category === 'disabled') {
return apps.filter(app => !app.active && app.installed)
}
if (this.category === 'app-bundles') {
return apps.filter(app => app.bundles)
}
if (this.category === 'updates') {
return apps.filter(app => app.update)
}
// filter app store categories
return apps.filter(app => {
return app.appstore && app.category !== undefined
&& (app.category === this.category || app.category.indexOf(this.category) > -1)
})
},
bundles() {
return this.$store.getters.getServerData.bundles.filter(bundle => this.bundleApps(bundle.id).length > 0)
@ -177,7 +175,7 @@ export default {
return !this.useListView && !this.useBundleView
},
useListView() {
return ['installed', 'recommended', 'enabled', 'disabled', 'updates'].includes(this.category)
return (this.category === 'installed' || this.category === 'enabled' || this.category === 'disabled' || this.category === 'updates')
},
useBundleView() {
return (this.category === 'app-bundles')
@ -198,24 +196,6 @@ export default {
}
}
},
mounted() {
if (this.category === 'recommended' && 'download' in this.$route.query) {
const limit = pLimit(1)
const installing = this.apps
.filter(app => !app.active && app.canInstall)
.map(app => limit(() => this.$store.dispatch('enableApp', { appId: app.id, groups: [] })))
console.debug(`installing ${installing.length} recommended apps`)
Promise.all(installing)
.then(() => {
console.info('recommended apps installed')
if ('returnTo' in this.$route.query) {
window.location = this.$route.query.returnTo
}
})
.catch(e => console.error('could not install recommended apps', e))
}
},
methods: {
toggleBundle(id) {
if (this.allBundlesEnabled(id)) {

View File

@ -31,10 +31,7 @@
</ul>
</AppNavigation>
<AppContent class="app-settings-content" :class="{ 'icon-loading': loadingList }">
<AppList v-if="!loadingList"
:category="category"
:app="currentApp"
:search="searchQuery" />
<AppList :category="category" :app="currentApp" :search="searchQuery" />
</AppContent>
<AppSidebar v-if="id && currentApp" @close="hideAppDetails">
<AppDetails :category="category" :app="currentApp" />
@ -136,21 +133,13 @@ export default {
icon: 'icon-category-installed',
text: t('settings', 'Your apps')
},
{
id: 'app-category-recommended',
classes: [],
router: { name: 'apps-category', params: { category: 'recommended' } },
icon: 'icon-category-installed',
text: t('settings', 'Recommended apps')
},
{
id: 'app-category-enabled',
classes: [],
icon: 'icon-category-enabled',
router: { name: 'apps-category', params: { category: 'enabled' } },
text: t('settings', 'Active apps')
},
{
}, {
id: 'app-category-disabled',
classes: [],
icon: 'icon-category-disabled',

View File

@ -0,0 +1,53 @@
<?php declare(strict_types=1);
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace OC\Core\Controller;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\ContentSecurityPolicy;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Http\StandaloneTemplateResponse;
use OCP\IInitialStateService;
use OCP\IRequest;
class RecommendedAppsController extends Controller {
/** @var IInitialStateService */
private $initialStateService;
public function __construct(IRequest $request,
IInitialStateService $initialStateService) {
parent::__construct('core', $request);
$this->initialStateService = $initialStateService;
}
/**
* @NoCSRFRequired
* @return Response
*/
public function index(): Response {
$this->initialStateService->provideInitialState('core', 'defaultPageUrl', \OC_Util::getDefaultPageUrl());
return new StandaloneTemplateResponse($this->appName, 'recommendedapps', [], 'guest');
}
}

View File

@ -123,7 +123,7 @@ class SetupController {
if ($installRecommended) {
$urlGenerator = \OC::$server->getURLGenerator();
$location = $urlGenerator->getAbsoluteURL('/index.php/settings/apps/recommended?download&returnTo=' . urlencode(\OC_Util::getDefaultPageUrl()));
$location = $urlGenerator->getAbsoluteURL('index.php/core/apps/recommended');
header('Location: ' . $location);
exit();
}

40
core/js/dist/login.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

58
core/js/dist/main.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

36
core/js/dist/recommendedapps.js vendored Normal file

File diff suppressed because one or more lines are too long

1
core/js/dist/recommendedapps.js.map vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -74,6 +74,7 @@ $application->registerRoutes($this, [
['name' => 'OCJS#getConfig', 'url' => '/core/js/oc.js', 'verb' => 'GET'],
['name' => 'Preview#getPreviewByFileId', 'url' => '/core/preview', 'verb' => 'GET'],
['name' => 'Preview#getPreview', 'url' => '/core/preview.png', 'verb' => 'GET'],
['name' => 'RecommendedApps#index', 'url' => '/core/apps/recommended', 'verb' => 'GET'],
['name' => 'Svg#getSvgFromCore', 'url' => '/svg/core/{folder}/{fileName}', 'verb' => 'GET'],
['name' => 'Svg#getSvgFromApp', 'url' => '/svg/{app}/{fileName}', 'verb' => 'GET'],
['name' => 'Css#getCss', 'url' => '/css/{appName}/{fileName}', 'verb' => 'GET'],

View File

@ -0,0 +1,194 @@
<!--
- @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
-
- @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
-
- @license GNU AGPL version 3 or any later version
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<template>
<div class="update">
<h2>{{ t('core', 'Recommended apps') }}</h2>
<p v-if="loadingApps" class="loading">
{{ t('core', 'Loading apps …') }}
</p>
<p v-else-if="loadingAppsError" class="loading-error">
{{ t('core', 'Could not fetch list of apps from the app store.') }}
</p>
<p v-else>
{{ t('core', 'Installing recommended apps …') }}
</p>
<div v-for="app in recommendedApps" :key="app.id" class="app">
<img :src="customIcon(app.id)" :alt="t('core', 'Nextcloud app {app}', { app: app.name })">
<div class="info">
<h3>
{{ app.name }}
<span v-if="app.loading" class="icon icon-loading-small" />
<span v-else-if="app.active" class="icon icon-checkmark-white" />
</h3>
<p v-html="customDescription(app.id)" />
<p v-if="app.installationError" class="error">
{{ t('core', 'App download or installation failed') }}
</p>
<p v-else-if="!app.isCompatible" class="error">
{{ t('core', 'Can\'t install this app because it is not compatible') }}
</p>
<p v-else-if="!app.canInstall" class="error">
{{ t('core', 'Can\'t install this app') }}
</p>
</div>
</div>
<a :href="defaultPageUrl">{{ t('core', 'Go back') }}</a>
</div>
</template>
<script>
import axios from '@nextcloud/axios'
import { generateUrl, imagePath } from '@nextcloud/router'
import { loadState } from '@nextcloud/initial-state'
import pLimit from 'p-limit'
import { translate as t } from '@nextcloud/l10n'
import logger from '../../logger'
const recommended = {
calendar: {
description: t('core', 'Schedule work & meetings, synced with all your devices.'),
icon: imagePath('core', 'places/calendar.svg')
},
contacts: {
description: t('core', 'Keep your colleagues and friends in one place without leaking their private info.'),
icon: imagePath('core', 'places/contacts.svg')
},
mail: {
description: t('core', 'Simple email app nicely integrated with Files, Contacts and Calendar.'),
icon: imagePath('core', 'actions/mail.svg')
},
talk: {
description: t('core', 'Screensharing, online meetings and web conferencing on desktop and with mobile apps.')
}
}
const recommendedIds = Object.keys(recommended)
const defaultPageUrl = loadState('core', 'defaultPageUrl')
export default {
name: 'RecommendedApps',
data() {
return {
loadingApps: true,
loadingAppsError: false,
apps: [],
defaultPageUrl
}
},
computed: {
recommendedApps() {
return this.apps.filter(app => recommendedIds.includes(app.id))
}
},
mounted() {
return axios.get(generateUrl('settings/apps/list'))
.then(resp => resp.data)
.then(data => {
logger.info(`${data.apps.length} apps fetched`)
this.apps = data.apps.map(app => Object.assign(app, { loading: false, installationError: false }))
logger.debug(`${this.recommendedApps.length} recommended apps found`, { apps: this.recommendedApps })
this.installApps()
})
.catch(error => {
logger.error('could not fetch app list', { error })
this.loadingAppsError = true
})
.then(() => {
this.loadingApps = false
})
},
methods: {
installApps() {
const limit = pLimit(1)
const installing = this.recommendedApps
.filter(app => !app.active && app.isCompatible && app.canInstall)
.map(app => limit(() => {
logger.info(`installing ${app.id}`)
app.loading = true
return axios.post(generateUrl(`settings/apps/enable`), { appIds: [app.id], groups: [] })
.catch(error => {
logger.error(`could not install ${app.id}`, { error })
app.installationError = true
})
.then(() => {
logger.info(`installed ${app.id}`)
app.loading = false
})
}))
logger.debug(`installing ${installing.length} recommended apps`)
Promise.all(installing)
.then(() => {
logger.info('all recommended apps installed, redirecting …')
window.location = defaultPageUrl
})
.catch(error => logger.error('could not install recommended apps', { error }))
},
customIcon(appId) {
if (!(appId in recommended)) {
logger.warn(`no app icon for recommended app ${appId}`)
return imagePath('core', 'places/default-app-icon.svg')
}
return recommended[appId].icon
},
customDescription(appId) {
if (!(appId in recommended)) {
logger.warn(`no app description for recommended app ${appId}`)
return ''
}
return recommended[appId].description
}
}
}
</script>
<style lang="scss" scoped>
p.loading, p.loading-error {
height: 100px;
}
.app {
display: flex;
flex-direction: row;
img {
height: 64px;
width: 64px;
}
img, .info {
padding: 12px;
}
.info {
h3 {
text-align: left;
}
h3 > span.icon {
display: inline-block;
}
}
}
</style>

37
core/src/logger.js Normal file
View File

@ -0,0 +1,37 @@
/*
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { getCurrentUser } from '@nextcloud/auth'
import { getLoggerBuilder } from '@nextcloud/logger'
const getLogger = user => {
if (user === null) {
return getLoggerBuilder()
.setApp('core')
.build()
}
return getLoggerBuilder()
.setApp('core')
.setUid(user.uid)
.build()
}
export default getLogger(getCurrentUser())

View File

@ -0,0 +1,44 @@
/*
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { getRequestToken } from '@nextcloud/auth'
import { generateFilePath } from '@nextcloud/router'
import { translate as t } from '@nextcloud/l10n'
import Vue from 'vue'
import logger from './logger'
import RecommendedApps from './components/setup/RecommendedApps'
// eslint-disable-next-line camelcase
__webpack_nonce__ = btoa(getRequestToken())
// eslint-disable-next-line camelcase
__webpack_public_path__ = generateFilePath('core', '', 'js/')
Vue.mixin({
methods: {
t
}
})
const View = Vue.extend(RecommendedApps)
new View().$mount('#recommended-apps')
logger.debug('recommended apps view rendered')

View File

@ -1,4 +1,6 @@
/*
<?php declare(strict_types=1);
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
@ -19,8 +21,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
export default [
'contacts',
'calendar',
'mail'
]
script('core', 'dist/recommendedapps');
?>
<div id="recommended-apps"></div>

View File

@ -7,6 +7,7 @@ module.exports = [
login: path.join(__dirname, 'src/login.js'),
main: path.join(__dirname, 'src/main.js'),
maintenance: path.join(__dirname, 'src/maintenance.js'),
recommendedapps: path.join(__dirname, 'src/recommendedapps.js'),
},
output: {
filename: '[name].js',

View File

@ -789,6 +789,7 @@ return array(
'OC\\Core\\Controller\\OCJSController' => $baseDir . '/core/Controller/OCJSController.php',
'OC\\Core\\Controller\\OCSController' => $baseDir . '/core/Controller/OCSController.php',
'OC\\Core\\Controller\\PreviewController' => $baseDir . '/core/Controller/PreviewController.php',
'OC\\Core\\Controller\\RecommendedAppsController' => $baseDir . '/core/Controller/RecommendedAppsController.php',
'OC\\Core\\Controller\\SearchController' => $baseDir . '/core/Controller/SearchController.php',
'OC\\Core\\Controller\\SetupController' => $baseDir . '/core/Controller/SetupController.php',
'OC\\Core\\Controller\\SvgController' => $baseDir . '/core/Controller/SvgController.php',

View File

@ -818,6 +818,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\Core\\Controller\\OCJSController' => __DIR__ . '/../../..' . '/core/Controller/OCJSController.php',
'OC\\Core\\Controller\\OCSController' => __DIR__ . '/../../..' . '/core/Controller/OCSController.php',
'OC\\Core\\Controller\\PreviewController' => __DIR__ . '/../../..' . '/core/Controller/PreviewController.php',
'OC\\Core\\Controller\\RecommendedAppsController' => __DIR__ . '/../../..' . '/core/Controller/RecommendedAppsController.php',
'OC\\Core\\Controller\\SearchController' => __DIR__ . '/../../..' . '/core/Controller/SearchController.php',
'OC\\Core\\Controller\\SetupController' => __DIR__ . '/../../..' . '/core/Controller/SetupController.php',
'OC\\Core\\Controller\\SvgController' => __DIR__ . '/../../..' . '/core/Controller/SvgController.php',

43
package-lock.json generated
View File

@ -2168,6 +2168,21 @@
}
}
},
"@nextcloud/l10n": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/@nextcloud/l10n/-/l10n-0.2.1.tgz",
"integrity": "sha512-iLdyxluCehsRibR4R/nH3O8T9CcGoAaW3eWEdQW2qPtn6eEiBXASek5nWhXa5hko1GvE7koYia4FoTWuL85/Ng==",
"requires": {
"core-js": "3.2.1"
},
"dependencies": {
"core-js": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.2.1.tgz",
"integrity": "sha512-Qa5XSVefSVPRxy2XfUC13WbvqkxhkwB3ve+pgCQveNgYzbM/UxZeu1dcOX/xr4UmfUd+muuvsaxilQzCyUurMw=="
}
}
},
"@nextcloud/logger": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/@nextcloud/logger/-/logger-0.1.0.tgz",
@ -3122,7 +3137,7 @@
},
"browserify-aes": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
"resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
"integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
"dev": true,
"requires": {
@ -3159,7 +3174,7 @@
},
"browserify-rsa": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
"resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
"integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
"dev": true,
"requires": {
@ -3203,7 +3218,7 @@
},
"buffer": {
"version": "4.9.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
"resolved": "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
"integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=",
"dev": true,
"requires": {
@ -3644,7 +3659,7 @@
},
"create-hash": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
"resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
"integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
"dev": true,
"requires": {
@ -3657,7 +3672,7 @@
},
"create-hmac": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
"resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
"integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
"dev": true,
"requires": {
@ -3918,7 +3933,7 @@
},
"diffie-hellman": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
"resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
"integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==",
"dev": true,
"requires": {
@ -4514,7 +4529,7 @@
},
"events": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz",
"resolved": "http://registry.npmjs.org/events/-/events-3.0.0.tgz",
"integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==",
"dev": true
},
@ -5551,7 +5566,7 @@
},
"get-stream": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
"resolved": "http://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
"integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
"dev": true,
"requires": {
@ -8420,7 +8435,7 @@
},
"safe-regex": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
"resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
"integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
"requires": {
"ret": "~0.1.10"
@ -8748,7 +8763,7 @@
},
"sha.js": {
"version": "2.4.11",
"resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
"resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
"integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
"dev": true,
"requires": {
@ -9062,7 +9077,7 @@
},
"stream-browserify": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz",
"resolved": "http://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz",
"integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==",
"dev": true,
"requires": {
@ -9162,7 +9177,7 @@
},
"strip-eof": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
"resolved": "http://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
"integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
"dev": true
},
@ -9408,7 +9423,7 @@
},
"tty-browserify": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
"resolved": "http://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
"integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=",
"dev": true
},
@ -9462,7 +9477,7 @@
},
"underscore": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz",
"resolved": "http://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz",
"integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg=="
},
"unicode-canonical-property-names-ecmascript": {

View File

@ -30,6 +30,7 @@
"@nextcloud/dialogs": "^0.1.1",
"@nextcloud/event-bus": "^0.2.1",
"@nextcloud/initial-state": "^0.2.0",
"@nextcloud/l10n": "^0.2.1",
"@nextcloud/logger": "^0.1.0",
"@nextcloud/paths": "^0.2.0",
"@nextcloud/router": "^0.1.0",