Browse Source

Implement app details in sidebar

Signed-off-by: Julius Härtl <jus@bitgrid.net>
tags/v14.0.0beta1
Julius Härtl 6 years ago
parent
commit
7fc2a29c22
No account linked to committer's email address

+ 17
- 2
settings/css/settings.scss View File

@@ -756,6 +756,23 @@ span.version {
opacity: .5;
}

.app-settings-content {
#searchresults {
display: none;
}
}
#apps-list.store {
.app-name {
display: block;
}
}

#app-sidebar #app-details-view {
.app-description p {
margin-bottom: 10px;
}
}

@media (min-width: 1601px) {
#apps-list .section {
width: 22%;
@@ -946,8 +963,6 @@ span.version {
height: 64px;
opacity: .1;
}
position: relative;
height: 100%;
display: flex;
flex-wrap: wrap;
align-content: flex-start;

+ 99
- 143
settings/src/components/appDetails.vue View File

@@ -21,167 +21,123 @@
-->

<template>
<div id="app-sidebar">
<div id="app-details-view" style="padding: 20px;">
<h2>{{ app.name }}</h2>
<img :src="app.preview" width="100%" />
<app-score v-if="app.ratingNumThresholdReached" :score="app.score"></app-score>
<div class="app-author">
{{ author }}
{{ licence }}
</div>
<div class="actions">
<div class="warning hidden"></div>
<input v-if="app.update" class="update" type="button" :value="t('settings', 'Update to %s', app.update)" />
<input v-if="app.canUnInstall" class="uninstall" type="button" :value="t('settings', 'Remove')" />
<input v-if="app.active" class="enable" type="button" :value="t('settings','Disable')" v-on:click="disable(app.id)" />
<input v-if="!app.active && !app.needsDownload" class="enable" type="button" :value="t('settings','Enable')" v-on:click="enable(app.id)" :disabled="!app.canInstall" />
<input v-if="!app.active && app.needsDownload" class="enable needs-download" type="button" :value="t('settings', 'Enable')" :disabled="!app.canInstall"/>
</div>
<p class="documentation">
<a class="appslink" v-if="app.website" :href="app.website" target="_blank" rel="noreferrer noopener">{{ t('settings', 'Visit website') }} ↗</a>
<a class="appslink" v-if="app.bugs" :href="app.bugs" target="_blank" rel="noreferrer noopener">{{ t('settings', 'Report a bug') }} ↗</a>

<a class="appslink" v-if="app.documentation.user" :href="app.documentation.user" target="_blank" rel="noreferrer noopener">{{ t('settings', 'User documentation') }} ↗</a>
<a class="appslink" v-if="app.documentation.admin" :href="app.documentation.admin" target="_blank" rel="noreferrer noopener">{{ t('settings', 'Admin documentation') }} ↗</a>
<a class="appslink" v-if="app.documentation.developer" :href="app.documentation.developer" target="_blank" rel="noreferrer noopener">{{ t('settings', 'Developer documentation') }} ↗</a>
</p>

<ul class="app-dependencies">
<li v-if="app.missingMinOwnCloudVersion">{{ t('settings', 'This app has no minimum Nextcloud version assigned. This will be an error in the future.') }}</li>
<li v-if="app.missingMaxOwnCloudVersion">{{ t('settings', 'This app has no maximum Nextcloud version assigned. This will be an error in the future.') }}</li>
<li v-if="!app.canInstall">
{{ t('settings', 'This app cannot be installed because the following dependencies are not fulfilled:') }}
<ul class="missing-dependencies">
<li v-for="dep in app.missingDependencies">{{ dep }}</li>
</ul>
</li>
</ul>

<div class="app-description" v-html="renderMarkdown"></div>
</div>
</template>

<script>
import userRow from './userList/userRow';
import Multiselect from 'vue-multiselect';
import InfiniteLoading from 'vue-infinite-loading';
import Vue from 'vue';

import AppScore from './appList/appScore';
export default {
name: 'userList',
props: ['users', 'showConfig', 'selectedGroup'],
name: 'appDetails',
props: ['app'],
components: {
userRow,
Multiselect,
InfiniteLoading
},
data() {
let unlimitedQuota = {id:'none', label:t('settings', 'Unlimited')},
defaultQuota = {id:'default', label:t('settings', 'Default quota')};
return {
unlimitedQuota: unlimitedQuota,
defaultQuota: defaultQuota,
loading: false,
scrolled: false,
newUser: {
id:'',
displayName:'',
password:'',
mailAddress:'',
groups: [],
subAdminsGroups: [],
quota: defaultQuota,
language: {code: 'en', name: t('settings', 'Default language')}
}
};
},
mounted() {
if (!this.settings.canChangePassword) {
OC.Notification.showTemporary(t('settings', 'Password change is disabled because the master key is disabled'));
}
/**
* Init default language from server data. The use of this.settings
* requires a computed variable,vwhich break the v-model binding of the form,
* this is a much easier solution than getter and setter
*/
Vue.set(this.newUser.language, 'code', this.settings.defaultLanguage);
AppScore
},
computed: {
settings() {
return this.$store.getters.getServerData;
},
filteredUsers() {
if (this.selectedGroup === 'disabled') {
let disabledUsers = this.users.filter(user => user.enabled !== true);
if (disabledUsers.length===0 && this.$refs.infiniteLoading && this.$refs.infiniteLoading.isComplete) {
// disabled group is empty, redirection to all users
this.$router.push('users');
this.$refs.infiniteLoading.$emit('$InfiniteLoading:reset');
}
return disabledUsers;
}
return this.users.filter(user => user.enabled === true);
},
groups() {
// data provided php side + remove the disabled group
return this.$store.getters.getGroups.filter(group => group.id !== 'disabled');
},
subAdminsGroups() {
// data provided php side
return this.$store.getters.getServerData.subadmingroups;
},
quotaOptions() {
// convert the preset array into objects
let quotaPreset = this.settings.quotaPreset.reduce((acc, cur) => acc.concat({id:cur, label:cur}), []);
// add default presets
quotaPreset.unshift(this.unlimitedQuota);
quotaPreset.unshift(this.defaultQuota);
return quotaPreset;
licence() {
return this.app.license + t('settings', '-licensed');
},
minPasswordLength() {
return this.$store.getters.getPasswordPolicyMinLength;
},
usersOffset() {
return this.$store.getters.getUsersOffset;
},
usersLimit() {
return this.$store.getters.getUsersLimit;
author() {
return t('settings', 'by') + ' ' + this.app.author;
},
renderMarkdown() {
// TODO: bundle marked as well
var renderer = new window.marked.Renderer();
renderer.link = function(href, title, text) {
try {
var prot = decodeURIComponent(unescape(href))
.replace(/[^\w:]/g, '')
.toLowerCase();
} catch (e) {
return '';
}

/* LANGUAGES */
languages() {
return Array(
{
label: t('settings', 'Common languages'),
languages: this.settings.languages.commonlanguages
},
if (prot.indexOf('http:') !== 0 && prot.indexOf('https:') !== 0) {
return '';
}

var out = '<a href="' + href + '" rel="noreferrer noopener"';
if (title) {
out += ' title="' + title + '"';
}
out += '>' + text + '</a>';
return out;
};
renderer.image = function(href, title, text) {
if (text) {
return text;
}
return title;
};
renderer.blockquote = function(quote) {
return quote;
};
return DOMPurify.sanitize(
window.marked(this.app.description.trim(), {
renderer: renderer,
gfm: false,
highlight: false,
tables: false,
breaks: false,
pedantic: false,
sanitize: true,
smartLists: true,
smartypants: false
}),
{
label: t('settings', 'All languages'),
languages: this.settings.languages.languages
SAFE_FOR_JQUERY: true,
ALLOWED_TAGS: [
'strong',
'p',
'a',
'ul',
'ol',
'li',
'em',
'del',
'blockquote'
]
}
);
}
},
watch: {
// watch url change and group select
selectedGroup: function (val, old) {
this.$store.commit('resetUsers');
this.$refs.infiniteLoading.$emit('$InfiniteLoading:reset');
}
},
methods: {
onScroll(event) {
this.scrolled = event.target.scrollTop>0;
},

/**
* Validate quota string to make sure it's a valid human file size
*
* @param {string} quota Quota in readable format '5 GB'
* @returns {Object}
*/
validateQuota(quota) {
// only used for new presets sent through @Tag
let validQuota = OC.Util.computerFileSize(quota);
if (validQuota !== null && validQuota > 0) {
// unify format output
quota = OC.Util.humanFileSize(OC.Util.computerFileSize(quota));
return this.newUser.quota = {id: quota, label: quota};
}
// Default is unlimited
return this.newUser.quota = this.quotaOptions[0];
},

infiniteHandler($state) {
this.$store.dispatch('getUsers', {
offset: this.usersOffset,
limit: this.usersLimit,
group: this.selectedGroup !== 'disabled' ? this.selectedGroup : ''})
.then((response) => {response?$state.loaded():$state.complete()});
},

resetForm() {
// revert form to original state
Object.assign(this.newUser, this.$options.data.call(this).newUser);
this.loading = false;
},
createUser() {
this.loading = true;
this.$store.dispatch('addUser', {
userid: this.newUser.id,
password: this.newUser.password,
email: this.newUser.mailAddress,
groups: this.newUser.groups.map(group => group.id),
subadmin: this.newUser.subAdminsGroups.map(group => group.id),
quota: this.newUser.quota.id,
language: this.newUser.language.code,
}).then(() => this.resetForm());
}
}
}
</script>

+ 53
- 15
settings/src/components/appList.vue View File

@@ -21,19 +21,43 @@
-->

<template>
<div id="app-content" :class="{ 'with-app-sidebar': app }">
<div id="apps-list" class="installed">
<div class="apps-header" v-if="category === 'app-bundles'">
<div class="app-image"></div>
<h2>Firmen-Paket <input class="enable" type="submit" data-bundleid="EnterpriseBundle" data-active="true" value="Alle aktivieren"></h2>
<div class="app-version"></div>
<div class="app-level"></div>
<div class="app-groups"></div>
<div class="actions">&nbsp;</div>
<div id="app-content" class="app-settings-content" :class="{ 'with-app-sidebar': app }">
<div id="apps-list" class="installed" v-if="useListView">
<app-item v-for="app in apps" :key="app.id" :app="app" :category="category" />
</div>

<div id="apps-list" class="installed" v-if="useBundleView">
<template v-for="app in apps">
<div class="apps-header" v-if="app.newCategory">
<div class="app-image"></div>
<h2>{{ app.categoryName }} <input class="enable" type="button" value="Alle aktivieren"></h2>
<div class="app-version"></div>
<div class="app-level"></div>
<div class="app-groups"></div>
<div class="actions">&nbsp;</div>
</div>
<app-item v-else :key="app.id" :app="app" :category="category"/>
</template>
</div>

<div id="apps-list" class="store" v-if="useAppStoreView">
<app-item v-for="app in apps" :key="app.id" :app="app" :category="category" :list-view="false" />
</div>

<div id="apps-list" class="installed" v-if="search !== ''">
<div>
<div></div>
<h2>{{ t('settings', 'Results from other categories') }}</h2>
</div>
<app-item v-for="app in searchApps" :key="app.id" :app="app" :category="category" :list-view="true" />
</div>

<app-item v-for="app in apps" :key="app.id" :app="app" :category="category" />
<div id="apps-list-empty" class="emptycontent emptycontent-search" v-if="apps.length == 0 && loading">
<div id="app-list-empty-icon" class="icon-search"></div>
<h2>{{ t('settings', 'No apps found for your versoin')}}</h2>
</div>

<div id="searchresults"></div>
</div>
</template>

@@ -43,7 +67,7 @@ import Multiselect from 'vue-multiselect';

export default {
name: 'appList',
props: ['category', 'app'],
props: ['category', 'app', 'search'],
components: {
Multiselect,
appItem
@@ -65,14 +89,28 @@ export default {
},
computed: {
apps() {
return this.$store.getters.getApps;
return this.$store.getters.getApps
.filter(app => app.name.toLowerCase().search(this.search.toLowerCase()) !== -1)
},
searchApps() {
return this.$store.getters.getAllApps
.filter(app => app.name.toLowerCase().search(this.search.toLowerCase()) !== -1)
},
groups() {
console.log(this.$store.getters.getGroups);
return this.$store.getters.getGroups;
/*.filter(group => group.id !== 'disabled')
.sort((a, b) => a.name.localeCompare(b.name));*/
return this.$store.getters.getGroups
.filter(group => group.id !== 'disabled')
.sort((a, b) => a.name.localeCompare(b.name));
},
useAppStoreView() {
return !this.useListView && !this.useBundleView;
},
useListView() {
return (this.category === 'installed' || this.category === 'enabled' || this.category === 'disabled' || this.category === 'updates');
},
useBundleView() {
return (this.category === 'app-bundles');
}
},
methods: {
prefix(prefix, content) {

+ 46
- 20
settings/src/components/appList/appItem.vue View File

@@ -21,23 +21,26 @@
-->

<template>
<div class="section" v-on:click="showAppDetails">
<div class="app-image app-image-icon">
<svg width="32" height="32" viewBox="0 0 32 32">
<div class="section">
<div class="app-image app-image-icon" v-on:click="showAppDetails">
<img :src="app.preview" v-if="!app.previewAsIcon" width="100%" />
<svg v-else width="32" height="32" viewBox="0 0 32 32" v-if="app.previewAsIcon">
<defs><filter id="invertIconApps-606"><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>
<image x="0" y="0" width="32" height="32" preserveAspectRatio="xMinYMin meet" filter="url(#invertIconApps-606)" xlink:href="/core/img/places/default-app-icon.svg?v=13.0.2.1" class="app-icon"></image>
<image x="0" y="0" width="32" height="32" preserveAspectRatio="xMinYMin meet" filter="url(#invertIconApps-606)" :xlink:href="app.preview" class="app-icon"></image>
</svg>
</div>
<div class="app-name">
<div class="app-name" v-on:click="showAppDetails">
{{ app.name }}
</div>
<div class="app-version">{{ app.version }}</div>
<div class="app-score" v-if="!listView"><app-score :score="app.score"></app-score> </div>
<div class="app-summary" v-if="!listView">{{ app.summary }}</div>
<div class="app-level">
<span class="official icon-checkmark" v-if="app.level === 200">{{ t('settings', 'Official') }}</span>
<a :href="app.appstoreUrl" v-if="!app.internal">Im Store anzeigen ↗</a>
<a :href="appstoreUrl" v-if="!app.internal && listView">Im Store anzeigen ↗</a>
</div>

<div class="app-groups">
<div class="app-groups" v-if="listView">
<div class="groups-enable" v-if="app.active && canLimitToGroups(app)">
<input type="checkbox" :value="app.id" v-model="groupCheckedAppsData" v-on:change="setGroupLimit" class="groups-enable__checkbox checkbox" :id="prefix('groups_enable', app.id)">
<label :for="prefix('groups_enable', app.id)">Auf Gruppen beschränken</label>
@@ -53,26 +56,31 @@

<div class="actions">
<div class="warning hidden"></div>
<input v-if="app.update" class="update" type="button" :value="t('settings', 'Update to %s', app.update)" :data-appid="app.id" />
<input v-if="app.canUnInstall" class="uninstall" type="button" :value="t('settings', 'Remove')" :data-appid="app.id" />
<input v-if="app.active" class="enable" type="button" :data-appid="app.id" data-active="true" :value="t('settings','Disable')" v-on:click="disable(app.id)" />
<input v-if="!app.active && !app.needsDownload" class="enable" type="button" :data-appid="app.id" :value="t('settings','Enable')" v-on:click="enable(app.id)" />
<!--if !canInstall -> disable the enable button {{#unless canInstall}}disabled="disabled"{{/unless}} //-->
<input v-if="!app.active && app.needsDownload" class="enable needs-download" type="button" :data-appid="app.id" :value="t('settings', 'Enable')"/>
<!-- <php /* data-active="false" {{#unless canInstall}}disabled="disabled"{{/unless}} */ ?>//-->

<input v-if="app.update" class="update" type="button" :value="t('settings', 'Update to %s', app.update)" v-on:click="update(app.id)" />
<input v-if="app.canUnInstall" class="uninstall" type="button" :value="t('settings', 'Remove')" v-on:click="remove(app.id)" />
<input v-if="app.active" class="enable" type="button" :value="t('settings','Disable')" v-on:click="disable(app.id)" />
<input v-if="!app.active" class="enable" type="button" :value="enableButtonText" v-on:click="enable(app.id)" :disabled="!app.canInstall" />
</div>
</div>
</template>

<script>
import Multiselect from 'vue-multiselect';
import AppScore from './appScore';

export default {
name: 'appItem',
props: ['app', 'category'],
props: {
app: {},
category: {},
listView: {
type: Boolean,
default: true,
}
},
components: {
Multiselect,
AppScore,
},
data() {
return {
@@ -87,15 +95,23 @@
}
},
computed: {
appstoreUrl() {
return `https://apps.nextcloud.com/apps/${this.app.id}`;
},
appGroups() {
return this.app.groups.map(group => {return {id: group, name: group}});
},
groups() {
console.log(this.$store.getters.getGroups);
return this.$store.getters.getGroups
.filter(group => group.id !== 'disabled')
.sort((a, b) => a.name.localeCompare(b.name));
},
enableButtonText() {
if (this.app.needsDownload) {
return t('settings','Download and enable');
}
return t('settings','Enable');
}
},
watchers: {

@@ -145,15 +161,25 @@
this.$store.dispatch('enableApp', { appId: this.app.id, groups: currentGroups});
},
enable(appId) {
this.$store.dispatch('enableApp', { appId: appId })
this.$store.dispatch('enableApp', { appId: appId, groups: [] })
.catch((error) => { OC.Notification.show(error)});
},
disable(appId) {
this.$store.dispatch('disableApp', { appId: appId })
.catch((error) => { OC.Notification.show(error)});
},
remove() {},
install() {},
remove(appId) {
this.$store.dispatch('uninstallApp', { appId: appId })
.catch((error) => { OC.Notification.show(error)});
},
install(appId) {
this.$store.dispatch('installApp', { appId: appId })
.catch((error) => { OC.Notification.show(error)});
},
update(appId) {
this.$store.dispatch('updateApp', { appId: appId })
.catch((error) => { OC.Notification.show(error)});
},
createUser() {
this.loading = true;
this.$store.dispatch('addUser', {

+ 38
- 0
settings/src/components/appList/appScore.vue View File

@@ -0,0 +1,38 @@
<!--
- @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
-
- @author Julius Härtl <jus@bitgrid.net>
-
- @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>
<img :src="scoreImage" />
</template>
<script>
export default {
name: 'appScore',
props: ['score'],
computed: {
scoreImage() {
let score = Math.round( this.score * 10 );
let imageName = 'rating/s' + score + '.svg';
return OC.imagePath('core', imageName);
}
}
};
</script>

+ 1
- 1
settings/src/store/api.js View File

@@ -71,7 +71,7 @@ export default {
waitForpassword();
});
},
get(url, data) {
get(url) {
return axios.get(sanitize(url), tokenHeaders)
.then((response) => Promise.resolve(response))
.catch((error) => Promise.reject(error));

+ 71
- 10
settings/src/store/apps.js View File

@@ -25,11 +25,18 @@ import axios from 'axios/index';

const state = {
apps: [],
allApps: [],
categories: [],
updateCount: 0
};

const mutations = {

APPS_API_FAILURE(state, error) {
OC.Notification.showHtml(t('settings','An error occured during the request. Unable to proceed.')+'<br>'+error.error.response.data.data.message, {timeout: 7});
console.log(state, error);
},

initCategories(state, {categories, updateCount}) {
state.categories = categories;
state.updateCount = updateCount;
@@ -52,16 +59,36 @@ const mutations = {
state.apps = apps;
},

setAllApps(state, apps) {
state.allApps = apps;
},

enableApp(state, {appId, groups}) {
state.apps.find(app => app.id === appId).active = true;
state.apps.find(app => app.id === appId).groups = groups;
console.log(state.apps.find(app => app.id === appId).groups);
let app = state.apps.find(app => app.id === appId);
app.active = true;
app.groups = groups;
},

disableApp(state, appId) {
let app = state.apps.find(app => app.id === appId);
app.active = false;
app.groups = [];
if (app.removable) {
app.canUnInstall = true;
}
},

uninstallApp(state, appId) {
state.apps.find(app => app.id === appId).active = false;
state.apps.find(app => app.id === appId).groups = [];
state.apps.find(app => app.id === appId).needsDownload = true;
state.apps.find(app => app.id === appId).canUnInstall = false;
state.apps.find(app => app.id === appId).canInstall = true;
},

resetApps(state) {
state.apps = [];
},
reset(state) {
state.apps = [];
state.categories = [];
@@ -74,7 +101,18 @@ const getters = {
return state.categories;
},
getApps(state) {
return state.apps;
return state.apps.concat([]).sort(function (a, b) {
if (a.active !== b.active) {
return (a.active ? -1 : 1)
}
if (a.update !== b.update) {
return (a.update ? -1 : 1)
}
return OC.Util.naturalSortCompare(a.name, b.name);
});
},
getAllApps(state) {
return state.allApps;
},
getUpdateCount(state) {
return state.updateCount;
@@ -92,7 +130,7 @@ const actions = {
context.commit('enableApp', {appId: appId, groups: groups});
return true;
})
.catch((error) => {throw error;})
.catch((error) => context.commit('APPS_API_FAILURE', { appId, error }))
}).catch((error) => context.commit('API_FAILURE', { appId, error }));

},
@@ -103,14 +141,28 @@ const actions = {
context.commit('disableApp', appId);
return true;
})
.catch((error) => {throw error;})
.catch((error) => context.commit('APPS_API_FAILURE', { appId, error }))
}).catch((error) => context.commit('API_FAILURE', { appId, error }));
},
installApp(appId) {

installApp(context, { appId }) {
return api.requireAdmin().then((response) => {
return api.get(OC.generateUrl(`settings/apps/enable/${appId}`))
.then((response) => {
context.commit('enableApp', appId);
return true;
})
.catch((error) => context.commit('APPS_API_FAILURE', { appId, error }))
}).catch((error) => context.commit('API_FAILURE', { appId, error }));
},
uninstallApp(appId) {

uninstallApp(context, { appId }) {
return api.requireAdmin().then((response) => {
return api.get(OC.generateUrl(`settings/apps/uninstall/${appId}`))
.then((response) => {
context.commit('uninstallApp', appId);
return true;
})
.catch((error) => context.commit('APPS_API_FAILURE', { appId, error }))
}).catch((error) => context.commit('API_FAILURE', { appId, error }));
},

getApps(context, { category }) {
@@ -122,6 +174,15 @@ const actions = {
.catch((error) => context.commit('API_FAILURE', error))
},

getAllApps(context) {
return api.get(OC.generateUrl(`settings/apps/list`))
.then((response) => {
context.commit('setAllApps', response.data.apps);
return true;
})
.catch((error) => context.commit('API_FAILURE', error))
},

getCategories(context) {
return api.get(OC.generateUrl('settings/apps/categories'))
.then((response) => {

+ 22
- 7
settings/src/views/Apps.vue View File

@@ -23,12 +23,10 @@
<template>
<div id="app">
<app-navigation :menu="menu" />
<app-list :category="category" :app="currentApp"></app-list>
<app-list :category="category" :app="currentApp" :search="search"></app-list>
<div id="app-sidebar" v-if="currentApp">
{{ currentApp.name }}
<div class="app-description">
{{ currentApp.description }}
</div>
{{ search }}
<app-details :app="currentApp"></app-details>
</div>
</div>
</template>
@@ -41,6 +39,7 @@ import Vue from 'vue';
import VueLocalStorage from 'vue-localstorage'
import Multiselect from 'vue-multiselect';
import api from '../store/api';
import AppDetails from '../components/appDetails';

Vue.use(VueLocalStorage)
Vue.use(VueLocalStorage)
@@ -58,17 +57,33 @@ export default {
}
},
components: {
AppDetails,
appNavigation,
appList,
},
methods: {
setSearch(search) {
this.search = search;
}
},
beforeMount() {
this.$store.dispatch('getCategories');
this.$store.dispatch('getApps', { category: this.category });
this.$store.dispatch('getApps', {category: this.category});
this.$store.dispatch('getAllApps');
this.$store.commit('setUpdateCount', this.$store.getters.getServerData.updateCount)
console.log(this.$store.getters.getServerData.updateCount);
},
mounted() {
// TODO: remove jQuery once we have a proper standardisation of the search
$('#searchbox').show();
let self = this;
$('#searchbox').change(function(e) {
self.setSearch($('#searchbox').val());
});
},
data() {
return {

search: ''
}
},
watch: {

Loading…
Cancel
Save