Move toplevel views into views/
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
<script setup>
|
||||
|
||||
import { ref, computed, useTemplateRef, onMounted } from 'vue';
|
||||
import { Button, ButtonGroup, TextInput } from 'pankow';
|
||||
import AppstoreModel from '../models/AppstoreModel.js';
|
||||
import AppInstallDialog from '../components/AppInstallDialog.vue';
|
||||
import ApplinkDialog from '../components/ApplinkDialog.vue';
|
||||
import { PROXY_APP_ID } from '../constants.js';
|
||||
|
||||
const API_ORIGIN = import.meta.env.VITE_API_ORIGIN ? import.meta.env.VITE_API_ORIGIN : window.location.origin;
|
||||
|
||||
const appstoreModel = AppstoreModel.create(API_ORIGIN, localStorage.token);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
apps.value = await appstoreModel.list();
|
||||
ready.value = true;
|
||||
|
||||
const query = window.location.hash.slice('#/appstore/'.length);
|
||||
if (query) {
|
||||
const appId = query.split('?')[0];
|
||||
const version = query.slice(query.indexOf('version=')+'version='.length);
|
||||
|
||||
const app = await appstoreModel.get(appId, version);
|
||||
if (app) {
|
||||
appInstallDialog.value.open(app);
|
||||
} else {
|
||||
console.error('No such version found');
|
||||
}
|
||||
} else {
|
||||
searchInput.value.$el.focus();
|
||||
}
|
||||
|
||||
proxyApp.value = await appstoreModel.get(PROXY_APP_ID);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<AppInstallDialog ref="appInstallDialog" @close="onAppInstallDialogClose"/>
|
||||
<ApplinkDialog ref="applinkDialog" @success="onApplinkDialogSuccess()"/>
|
||||
|
||||
<div class="filter-bar">
|
||||
<div></div>
|
||||
<TextInput ref="searchInput" @keydown.esc="search = ''" v-model="search" :placeholder="$t('appstore.searchPlaceholder')" style="max-width: 100%; width: 500px;"/>
|
||||
<ButtonGroup>
|
||||
<Button outline icon="fas fa-exchange-alt" @click="onInstall(proxyApp)">{{ $t('apps.addAppproxyAction') }}</Button>
|
||||
<Button outline icon="fas fa-link" @click="onApplinkDialogOpen()">{{ $t('apps.addApplinkAction') }}</Button>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
|
||||
<div v-if="!search">
|
||||
<h4 v-show="filteredPopularApps.length">{{ $t('appstore.category.popular') }}</h4>
|
||||
<TransitionGroup name="grid-animation" tag="div" class="grid" v-show="ready">
|
||||
<div class="item" v-for="app in filteredPopularApps" :key="app.id" :ref="'item-' + app.id" @click="onInstall(app)">
|
||||
<img class="icon" :src="app.iconUrl" />
|
||||
<div class="description">
|
||||
<div class="title">{{ app.manifest.title }}</div>
|
||||
<div class="tagline">{{ app.manifest.tagline }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</TransitionGroup>
|
||||
|
||||
<h4 v-show="filteredAllApps.length">{{ $t('appstore.category.all') }}</h4>
|
||||
<TransitionGroup name="grid-animation" tag="div" class="grid" v-show="ready">
|
||||
<div class="item" v-for="app in filteredAllApps" :key="app.id" :ref="'item-' + app.id" @click="onInstall(app)">
|
||||
<img class="icon" :src="app.iconUrl" />
|
||||
<div class="description">
|
||||
<div class="title">{{ app.manifest.title }}</div>
|
||||
<div class="tagline">{{ app.manifest.tagline }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
<div v-else>
|
||||
<TransitionGroup name="grid-animation" tag="div" class="grid" v-show="ready">
|
||||
<div class="item" v-for="app in filteredApps" :key="app.id" :ref="'item-' + app.id" @click="onInstall(app)">
|
||||
<img class="icon" :src="app.iconUrl" />
|
||||
<div class="description">
|
||||
<div class="title">{{ app.manifest.title }}</div>
|
||||
<div class="tagline">{{ app.manifest.tagline }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</TransitionGroup>
|
||||
</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;
|
||||
}
|
||||
|
||||
.grid {
|
||||
position: relative;
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 300px;
|
||||
padding: 10px 15px;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.item:hover {
|
||||
background-color: var(--card-background);
|
||||
}
|
||||
|
||||
.item.active {
|
||||
width: 100%;
|
||||
background-color: var(--card-background);
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
object-fit: contain;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.tagline {
|
||||
font-size: 12px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
</style>
|
||||
Reference in New Issue
Block a user