diff --git a/dashboard/src/views/AppstoreView.vue b/dashboard/src/views/AppstoreView.vue
index 8ea9aaeae..ff2fa84f8 100644
--- a/dashboard/src/views/AppstoreView.vue
+++ b/dashboard/src/views/AppstoreView.vue
@@ -4,8 +4,9 @@ import { useI18n } from 'vue-i18n';
const i18n = useI18n();
const t = i18n.t;
-import { ref, computed, useTemplateRef, onMounted, inject } from 'vue';
-import { Button, TextInput, Spinner, InputDialog } from 'pankow';
+import moment from 'moment';
+import { ref, computed, useTemplateRef, onMounted, inject, watch } from 'vue';
+import { Button, TextInput, Spinner, InputDialog, SingleSelect } from 'pankow';
import AppsModel from '../models/AppsModel.js';
import AppstoreModel from '../models/AppstoreModel.js';
import AppInstallDialog from '../components/AppInstallDialog.vue';
@@ -20,7 +21,37 @@ const ready = ref(false);
const proxyApp = ref();
const apps = ref([]);
const search = ref('');
+
+// clear category on search
+watch(search, (newValue) => {
+ if (newValue) category.value ='';
+});
+
+function filterForNewApps(apps) {
+ var minApps = apps.length < 12 ? apps.length : 12; // prevent endless loop
+ var tmp = [];
+ var i = 0;
+
+ do {
+ var offset = moment().subtract(i++, 'days');
+ tmp = apps.filter(function (app) { return moment(app.publishedAt).isAfter(offset); });
+ } while(tmp.length < minApps);
+
+ return tmp;
+}
+
const filteredApps = computed(() => {
+ if (category.value) {
+ if (category.value === 'new') {
+ return filterForNewApps(apps.value);
+ } else {
+ return apps.value.filter(a => {
+ if (a.manifest.tags.join().toLowerCase().indexOf(category.value) !== -1) return true;
+ return false;
+ });
+ }
+ }
+
if (!search.value) return apps.value;
const s = search.value.toLowerCase();
@@ -45,6 +76,36 @@ const inputDialog = useTemplateRef('inputDialog');
const features = inject('features');
const installedApps = ref([]);
+const category = ref('');
+const categories = [
+ { id: '', label: t('appstore.category.all') },
+ { id: 'new', label: t('appstore.category.newApps') },
+ { id: 'analytics', label: 'Analytics'},
+ { id: 'automation', label: 'Automation'},
+ { id: 'blog', label: 'Blog'},
+ { id: 'chat', label: 'Chat'},
+ { id: 'crm', label: 'CRM'},
+ { id: 'document', label: 'Documents'},
+ { id: 'email', label: 'Email'},
+ { id: 'federated', label: 'Federated'},
+ { id: 'finance', label: 'Finance'},
+ { id: 'forum', label: 'Forum'},
+ { id: 'fun', label: 'Fun'},
+ { id: 'gallery', label: 'Gallery'},
+ { id: 'game', label: 'Games'},
+ { id: 'git', label: 'Code Hosting'},
+ { id: 'hosting', label: 'Web Hosting'},
+ { id: 'learning', label: 'Learning'},
+ { id: 'media', label: 'Media'},
+ { id: 'no-code', label: 'No-code'},
+ { id: 'notes', label: 'Notes'},
+ { id: 'project', label: 'Project Management'},
+ { id: 'sync', label: 'File Sync'},
+ { id: 'voip', label: 'VoIP'},
+ { id: 'vpn', label: 'VPN'},
+ { id: 'wiki', label: 'Wiki'},
+];
+
function onAppInstallDialogClose() {
window.location.href = '#/appstore';
}
@@ -127,14 +188,15 @@ onMounted(async () => {