173 lines
4.8 KiB
Vue
173 lines
4.8 KiB
Vue
<script setup>
|
|
|
|
import { ref, computed, useTemplateRef, onMounted } from 'vue';
|
|
import { Button, TextInput, Spinner } from 'pankow';
|
|
import AppstoreModel from '../models/AppstoreModel.js';
|
|
import AppInstallDialog from '../components/AppInstallDialog.vue';
|
|
import ApplinkDialog from '../components/ApplinkDialog.vue';
|
|
import AppStoreItem from '../components/AppStoreItem.vue';
|
|
import { PROXY_APP_ID } from '../constants.js';
|
|
|
|
const appstoreModel = AppstoreModel.create();
|
|
|
|
const ready = ref(false);
|
|
const proxyApp = ref();
|
|
const apps = ref([]);
|
|
const search = ref('');
|
|
const filteredApps = computed(() => {
|
|
if (!search.value) return apps.value;
|
|
|
|
const s = search.value.toLowerCase();
|
|
|
|
return apps.value.filter(a => {
|
|
if (a.manifest.title.toLowerCase().indexOf(s) !== -1) return true;
|
|
if (a.manifest.tagline.toLowerCase().indexOf(s) !== -1) return true;
|
|
if (a.manifest.tags.join().toLowerCase().indexOf(s) !== -1) return true;
|
|
return false;
|
|
});
|
|
});
|
|
const filteredAllApps = computed(() => {
|
|
return filteredApps.value.filter(a => !a.featured);
|
|
});
|
|
const filteredPopularApps = computed(() => {
|
|
return filteredApps.value.filter(a => a.featured);
|
|
});
|
|
const appInstallDialog = useTemplateRef('appInstallDialog');
|
|
const searchInput = useTemplateRef('searchInput');
|
|
const applinkDialog = useTemplateRef('applinkDialog');
|
|
|
|
function onAppInstallDialogClose() {
|
|
window.location.href = '#/appstore';
|
|
}
|
|
|
|
function onApplinkDialogOpen() {
|
|
applinkDialog.value.open();
|
|
}
|
|
|
|
function onApplinkDialogSuccess() {
|
|
window.location.href = '#/apps';
|
|
}
|
|
|
|
function onInstall(app) {
|
|
window.location.href = `#/appstore/${app.manifest.id}?version=${app.manifest.version}`;
|
|
appInstallDialog.value.open(app);
|
|
}
|
|
|
|
async function getAppList() {
|
|
const [error, result] = await appstoreModel.list();
|
|
if (error) return console.error(error);
|
|
|
|
apps.value = result;
|
|
}
|
|
|
|
async function getApp(id, version = '') {
|
|
const [error, result] = await appstoreModel.get(id, version);
|
|
if (error) {
|
|
console.error(error);
|
|
return null;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
onMounted(async () => {
|
|
await getAppList();
|
|
ready.value = true;
|
|
|
|
const query = window.location.hash.slice('#/appstore/'.length);
|
|
if (query) {
|
|
const appId = query.split('?')[0];
|
|
const params = new URLSearchParams(window.location.hash.slice(window.location.hash.indexOf('?')));
|
|
const version = params.get('version') || 'latest';
|
|
|
|
const app = await getApp(appId, version);
|
|
if (app) {
|
|
appInstallDialog.value.open(app);
|
|
} else {
|
|
console.error('No such version found');
|
|
}
|
|
} else {
|
|
setTimeout(() => {
|
|
try {
|
|
searchInput.value.$el.focus();
|
|
// eslint-disable-next-line no-unused-vars
|
|
} catch(e) {;}
|
|
}, 100);
|
|
}
|
|
|
|
proxyApp.value = await getApp(PROXY_APP_ID);
|
|
});
|
|
|
|
</script>
|
|
|
|
<template>
|
|
<div class="content" style="width: 100%;">
|
|
<AppInstallDialog ref="appInstallDialog" @close="onAppInstallDialogClose"/>
|
|
<ApplinkDialog ref="applinkDialog" @success="onApplinkDialogSuccess()"/>
|
|
|
|
<div class="filter-bar">
|
|
<div></div>
|
|
<Spinner v-if="!ready" class="pankow-spinner-large"/>
|
|
<TextInput v-show="ready" ref="searchInput" @keydown.esc="search = ''" v-model="search" :placeholder="$t('appstore.searchPlaceholder')" style="max-width: 100%; width: 500px;"/>
|
|
<div>
|
|
<Button secondary plain icon="fas fa-exchange-alt" @click="onInstall(proxyApp)">{{ $t('apps.addAppproxyAction') }}</Button>
|
|
<Button secondary plain icon="fas fa-link" @click="onApplinkDialogOpen()">{{ $t('apps.addApplinkAction') }}</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="!search && ready">
|
|
<h4 v-show="filteredPopularApps.length">{{ $t('appstore.category.popular') }}</h4>
|
|
<div class="grid">
|
|
<AppStoreItem v-for="app in filteredPopularApps" :app="app" :key="app.id" :ref="'item-' + app.id" @click="onInstall(app)"/>
|
|
</div>
|
|
|
|
<h4 v-show="filteredAllApps.length">{{ $t('appstore.category.all') }}</h4>
|
|
<div class="grid">
|
|
<AppStoreItem v-for="app in filteredAllApps" :app="app" :key="app.id" :ref="'item-' + app.id" @click="onInstall(app)"/>
|
|
</div>
|
|
</div>
|
|
<div v-else-if="ready">
|
|
<div class="grid">
|
|
<AppStoreItem v-for="app in filteredApps" :app="app" :key="app.id" :ref="'item-' + app.id" @click="onInstall(app)"/>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
|
|
.grid-animation-move,
|
|
.grid-animation-enter-active,
|
|
.grid-animation-leave-active {
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.grid-animation-enter-from {
|
|
opacity: 0;
|
|
transform: scale(0);
|
|
}
|
|
|
|
.grid-animation-leave-to {
|
|
opacity: 0;
|
|
transform: scale(0);
|
|
}
|
|
|
|
.filter-bar {
|
|
width: 100%;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
margin-bottom: 30px;
|
|
padding-left: 20px;
|
|
padding-right: 20px;
|
|
}
|
|
|
|
.grid {
|
|
position: relative;
|
|
display: flex;
|
|
gap: 20px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
</style>
|