bug/mobile-fixes (#426)
* search dialog rewrite * lazy-load shopping list * fit search bar * event table * set urls for static content Co-authored-by: hay-kot <hay-kot@pm.me>
This commit is contained in:
parent
475cafae49
commit
8f8127a5fc
12 changed files with 326 additions and 289 deletions
|
@ -75,6 +75,9 @@ export default {
|
|||
</script>
|
||||
|
||||
<style>
|
||||
.top-dialog {
|
||||
align-self: flex-start;
|
||||
}
|
||||
:root {
|
||||
scrollbar-color: transparent transparent;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<v-row>
|
||||
<SearchDialog ref="mealselect" @select="setSlug" />
|
||||
<SearchDialog ref="mealselect" @selected="setSlug" />
|
||||
<BaseDialog
|
||||
title="Custom Meal"
|
||||
:title-icon="$globals.icons.primary"
|
||||
|
@ -78,7 +78,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import SearchDialog from "../UI/Search/SearchDialog";
|
||||
import SearchDialog from "../UI/Dialogs/SearchDialog";
|
||||
import BaseDialog from "@/components/UI/Dialogs/BaseDialog";
|
||||
import { api } from "@/api";
|
||||
import CardImage from "../Recipe/CardImage.vue";
|
||||
|
@ -129,13 +129,13 @@ export default {
|
|||
this.value[this.activeIndex]["meals"][0]["name"] = name;
|
||||
this.value[this.activeIndex]["meals"][0]["description"] = description;
|
||||
},
|
||||
setSlug(name, slug) {
|
||||
setSlug(recipe) {
|
||||
switch (this.mode) {
|
||||
case this.modes.primary:
|
||||
this.setPrimary(name, slug);
|
||||
this.setPrimary(recipe.name, recipe.slug);
|
||||
break;
|
||||
default:
|
||||
this.setSide(name, slug);
|
||||
this.setSide(recipe.name, recipe.slug);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,36 +1,44 @@
|
|||
<template>
|
||||
<v-card :ripple="false" class="mx-auto" hover :to="`/recipe/${slug}`" @click="$emit('selected')">
|
||||
<v-list-item three-line>
|
||||
<v-list-item-avatar tile size="125" class="v-mobile-img rounded-sm my-0 ml-n4">
|
||||
<v-img
|
||||
v-if="!fallBackImage"
|
||||
:src="getImage(slug)"
|
||||
@load="fallBackImage = false"
|
||||
@error="fallBackImage = true"
|
||||
></v-img>
|
||||
<v-icon v-else color="primary" class="icon-position" size="100">
|
||||
{{ $globals.icons.primary }}
|
||||
</v-icon>
|
||||
</v-list-item-avatar>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title class=" mb-1">{{ name }} </v-list-item-title>
|
||||
<v-list-item-subtitle> {{ description }} </v-list-item-subtitle>
|
||||
<div class="d-flex justify-center align-center">
|
||||
<v-rating
|
||||
color="secondary"
|
||||
class="ml-auto"
|
||||
background-color="secondary lighten-3"
|
||||
dense
|
||||
length="5"
|
||||
size="15"
|
||||
:value="rating"
|
||||
></v-rating>
|
||||
<v-spacer></v-spacer>
|
||||
<ContextMenu :slug="slug" menu-icon="mdi-dots-horizontal" />
|
||||
</div>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</v-card>
|
||||
<v-expand-transition>
|
||||
<v-card
|
||||
:ripple="false"
|
||||
class="mx-auto"
|
||||
hover
|
||||
:to="this.$listeners.selected ? undefined : `/recipe/${slug}`"
|
||||
@click="$emit('selected')"
|
||||
>
|
||||
<v-list-item three-line>
|
||||
<v-list-item-avatar tile size="125" class="v-mobile-img rounded-sm my-0 ml-n4">
|
||||
<v-img
|
||||
v-if="!fallBackImage"
|
||||
:src="getImage(slug)"
|
||||
@load="fallBackImage = false"
|
||||
@error="fallBackImage = true"
|
||||
></v-img>
|
||||
<v-icon v-else color="primary" class="icon-position" size="100">
|
||||
{{ $globals.icons.primary }}
|
||||
</v-icon>
|
||||
</v-list-item-avatar>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title class=" mb-1">{{ name }} </v-list-item-title>
|
||||
<v-list-item-subtitle> {{ description }} </v-list-item-subtitle>
|
||||
<div class="d-flex justify-center align-center">
|
||||
<v-rating
|
||||
color="secondary"
|
||||
class="ml-auto"
|
||||
background-color="secondary lighten-3"
|
||||
dense
|
||||
length="5"
|
||||
size="15"
|
||||
:value="rating"
|
||||
></v-rating>
|
||||
<v-spacer></v-spacer>
|
||||
<ContextMenu :slug="slug" menu-icon="mdi-dots-horizontal" />
|
||||
</div>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</v-card>
|
||||
</v-expand-transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
|
|
@ -115,8 +115,5 @@ export default {
|
|||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.top-dialog {
|
||||
align-self: flex-start;
|
||||
}
|
||||
<style>
|
||||
</style>
|
||||
|
|
161
frontend/src/components/UI/Dialogs/SearchDialog.vue
Normal file
161
frontend/src/components/UI/Dialogs/SearchDialog.vue
Normal file
|
@ -0,0 +1,161 @@
|
|||
<template>
|
||||
<div>
|
||||
<slot v-bind="{ open, close }"> </slot>
|
||||
<v-dialog
|
||||
v-model="dialog"
|
||||
:width="isMobile ? undefined : '700'"
|
||||
:height="isMobile ? undefined : '0'"
|
||||
:fullscreen="isMobile"
|
||||
content-class="top-dialog"
|
||||
:scrollable="false"
|
||||
>
|
||||
<v-app-bar sticky dark color="primary lighten-1" :rounded="!isMobile">
|
||||
<FuseSearchBar :raw-data="allItems" @results="filterItems" :search="searchString">
|
||||
<v-text-field
|
||||
id="arrow-search"
|
||||
autofocus
|
||||
v-model="searchString"
|
||||
solo
|
||||
flat
|
||||
autocomplete="off"
|
||||
background-color="primary lighten-1"
|
||||
color="white"
|
||||
dense
|
||||
:clearable="!isMobile"
|
||||
class="mx-2 arrow-search"
|
||||
hide-details
|
||||
single-line
|
||||
:placeholder="$t('search.search')"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
>
|
||||
</v-text-field>
|
||||
</FuseSearchBar>
|
||||
<v-btn v-if="isMobile" x-small fab light @click="dialog = false">
|
||||
<v-icon>
|
||||
mdi-close
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
</v-app-bar>
|
||||
<v-card class="mt-1 pa-1" relative>
|
||||
<v-card-actions>
|
||||
<div class="mr-auto">
|
||||
Results
|
||||
</div>
|
||||
<router-link to="/search"> Advanced Search </router-link>
|
||||
</v-card-actions>
|
||||
<MobileRecipeCard
|
||||
v-for="(recipe, index) in results.slice(0, 10)"
|
||||
:tabindex="index"
|
||||
:key="index"
|
||||
class="ma-1 arrow-nav"
|
||||
:name="recipe.name"
|
||||
:description="recipe.description"
|
||||
:slug="recipe.slug"
|
||||
:rating="recipe.rating"
|
||||
:image="recipe.image"
|
||||
:route="true"
|
||||
v-on="$listeners.selected ? { selected: () => grabRecipe(recipe) } : {}"
|
||||
/>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const SELECTED_EVENT = "selected";
|
||||
import FuseSearchBar from "@/components/UI/Search/FuseSearchBar";
|
||||
import MobileRecipeCard from "@/components/Recipe/MobileRecipeCard";
|
||||
export default {
|
||||
components: {
|
||||
FuseSearchBar,
|
||||
MobileRecipeCard,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedIndex: -1,
|
||||
dialog: false,
|
||||
searchString: "",
|
||||
searchResults: [],
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
$route() {
|
||||
this.dialog = false;
|
||||
},
|
||||
dialog(val) {
|
||||
if (!val) {
|
||||
this.resetSelected();
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$store.dispatch("requestAllRecipes");
|
||||
document.addEventListener("keydown", this.onUpDown);
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.removeEventListener("keydown", this.onUpDown);
|
||||
},
|
||||
|
||||
computed: {
|
||||
isMobile() {
|
||||
return this.$vuetify.breakpoint.name === "xs";
|
||||
},
|
||||
allItems() {
|
||||
return this.$store.getters.getAllRecipes;
|
||||
},
|
||||
results() {
|
||||
if (this.searchString != null && this.searchString.length >= 1) {
|
||||
return this.searchResults;
|
||||
}
|
||||
return this.allItems;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
open() {
|
||||
this.dialog = true;
|
||||
},
|
||||
close() {
|
||||
this.dialog = false;
|
||||
},
|
||||
filterItems(val) {
|
||||
this.searchResults = val.map(x => x.item);
|
||||
},
|
||||
grabRecipe(recipe) {
|
||||
this.dialog = false;
|
||||
this.$emit(SELECTED_EVENT, recipe);
|
||||
},
|
||||
onUpDown(e) {
|
||||
if (e.keyCode === 38) {
|
||||
e.preventDefault();
|
||||
this.selectedIndex--;
|
||||
} else if (e.keyCode === 40) {
|
||||
e.preventDefault();
|
||||
this.selectedIndex++;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
this.selectRecipe();
|
||||
},
|
||||
resetSelected() {
|
||||
this.searchString = "";
|
||||
this.selectedIndex = -1;
|
||||
document.getElementsByClassName("arrow-nav")[0].focus();
|
||||
},
|
||||
selectRecipe() {
|
||||
const recipeCards = document.getElementsByClassName("arrow-nav");
|
||||
if (recipeCards) {
|
||||
if (this.selectedIndex < 0) {
|
||||
this.selectedIndex = -1;
|
||||
document.getElementById("arrow-search").focus();
|
||||
return;
|
||||
}
|
||||
this.selectedIndex >= recipeCards.length ? (this.selectedIndex = recipeCards.length - 1) : null;
|
||||
recipeCards[this.selectedIndex].focus();
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style >
|
||||
</style>
|
|
@ -73,4 +73,8 @@ export default {
|
|||
};
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
<style scoped>
|
||||
div {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,24 +1,20 @@
|
|||
<template>
|
||||
<v-menu v-model="menuModel" readonly offset-y offset-overflow max-height="75vh">
|
||||
<template #activator="{ attrs }">
|
||||
<SearchDialog ref="searchDialog">
|
||||
<template v-slot="{ open }">
|
||||
<v-text-field
|
||||
readonly
|
||||
@click="open"
|
||||
ref="searchInput"
|
||||
class="my-auto pt-1"
|
||||
v-model="search"
|
||||
v-bind="attrs"
|
||||
:dense="dense"
|
||||
class="my-auto mt-5 pt-1"
|
||||
dense
|
||||
light
|
||||
dark
|
||||
flat
|
||||
:placeholder="$t('search.search-mealie')"
|
||||
background-color="primary lighten-1"
|
||||
color="white"
|
||||
:solo="solo"
|
||||
:style="`max-width: ${maxWidth};`"
|
||||
@focus="onFocus"
|
||||
@blur="isFocused = false"
|
||||
autocomplete="off"
|
||||
:autofocus="autofocus"
|
||||
solo=""
|
||||
:style="`max-width: 450;`"
|
||||
>
|
||||
<template #prepend-inner>
|
||||
<v-icon color="grey lighten-3" size="29">
|
||||
|
@ -27,122 +23,24 @@
|
|||
</template>
|
||||
</v-text-field>
|
||||
</template>
|
||||
<v-card v-if="showResults" max-height="75vh" :max-width="maxWidth" scrollable>
|
||||
<v-card-text class="flex row mx-auto ">
|
||||
<div class="mr-auto">
|
||||
Results
|
||||
</div>
|
||||
<router-link to="/search"> Advanced Search </router-link>
|
||||
</v-card-text>
|
||||
<v-divider></v-divider>
|
||||
<v-list scrollable v-if="autoResults">
|
||||
<v-list-item
|
||||
v-for="(item, index) in autoResults.slice(0, 15)"
|
||||
:key="index"
|
||||
:to="navOnClick ? `/recipe/${item.item.slug}` : null"
|
||||
@click="navOnClick ? null : selected(item.item.slug, item.item.name)"
|
||||
>
|
||||
<v-list-item-avatar>
|
||||
<v-img :src="getImage(item.item.slug)"></v-img>
|
||||
</v-list-item-avatar>
|
||||
<v-list-item-content @click="showResults ? null : selected(item.item.slug, item.item.name)">
|
||||
<v-list-item-title v-html="highlight(item.item.name)"> </v-list-item-title>
|
||||
<v-rating dense v-if="item.item.rating" :value="item.item.rating" size="12"> </v-rating>
|
||||
<v-list-item-subtitle v-html="highlight(item.item.description)"> </v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-card>
|
||||
</v-menu>
|
||||
</SearchDialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Fuse from "fuse.js";
|
||||
import { api } from "@/api";
|
||||
import SearchDialog from "@/components/UI/Dialogs/SearchDialog";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
showResults: {
|
||||
default: false,
|
||||
},
|
||||
maxWidth: {
|
||||
default: "450px",
|
||||
},
|
||||
dense: {
|
||||
default: true,
|
||||
},
|
||||
navOnClick: {
|
||||
default: true,
|
||||
},
|
||||
solo: {
|
||||
default: true,
|
||||
},
|
||||
autofocus: {
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isFocused: false,
|
||||
searchSlug: "",
|
||||
search: "",
|
||||
menuModel: false,
|
||||
result: [],
|
||||
fuseResults: [],
|
||||
options: {
|
||||
shouldSort: true,
|
||||
threshold: 0.6,
|
||||
location: 0,
|
||||
distance: 100,
|
||||
findAllMatches: true,
|
||||
maxPatternLength: 32,
|
||||
minMatchCharLength: 2,
|
||||
keys: ["name", "description"],
|
||||
},
|
||||
};
|
||||
components: {
|
||||
SearchDialog,
|
||||
},
|
||||
|
||||
mounted() {
|
||||
document.addEventListener("keydown", this.onDocumentKeydown);
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.removeEventListener("keydown", this.onDocumentKeydown);
|
||||
},
|
||||
computed: {
|
||||
data() {
|
||||
return this.$store.getters.getAllRecipes;
|
||||
},
|
||||
autoResults() {
|
||||
return this.fuseResults.length > 1 ? this.fuseResults : this.results;
|
||||
},
|
||||
fuse() {
|
||||
return new Fuse(this.data, this.options);
|
||||
},
|
||||
isSearching() {
|
||||
return this.search && this.search.length > 0;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
isSearching(val) {
|
||||
val ? (this.menuModel = true) : this.resetSearch();
|
||||
},
|
||||
|
||||
search() {
|
||||
try {
|
||||
this.result = this.fuse.search(this.search.trim());
|
||||
} catch {
|
||||
this.result = this.data.map(x => ({ item: x })).sort((a, b) => (a.name > b.name ? 1 : -1));
|
||||
}
|
||||
this.$emit("results", this.result);
|
||||
|
||||
if (this.showResults === true) {
|
||||
this.fuseResults = this.result;
|
||||
}
|
||||
},
|
||||
|
||||
searchSlug() {
|
||||
this.selected(this.searchSlug);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
highlight(string) {
|
||||
if (!this.search) {
|
||||
|
@ -150,23 +48,7 @@ export default {
|
|||
}
|
||||
return string.replace(new RegExp(this.search, "gi"), match => `<mark>${match}</mark>`);
|
||||
},
|
||||
getImage(image) {
|
||||
return api.recipes.recipeTinyImage(image);
|
||||
},
|
||||
selected(slug, name) {
|
||||
this.$emit("selected", slug, name);
|
||||
},
|
||||
async onFocus() {
|
||||
this.$store.dispatch("requestAllRecipes");
|
||||
this.isFocused = true;
|
||||
},
|
||||
resetSearch() {
|
||||
this.$nextTick(() => {
|
||||
this.search = "";
|
||||
this.isFocused = false;
|
||||
this.menuModel = false;
|
||||
});
|
||||
},
|
||||
|
||||
onDocumentKeydown(e) {
|
||||
if (
|
||||
e.key === "/" &&
|
||||
|
@ -174,23 +56,10 @@ export default {
|
|||
!document.activeElement.id.startsWith("input")
|
||||
) {
|
||||
e.preventDefault();
|
||||
this.$refs.searchInput.focus();
|
||||
this.$refs.searchDialog.open();
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.color-transition {
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="sass" scoped>
|
||||
.v-menu__content
|
||||
width: 100
|
||||
&, & > *
|
||||
display: flex
|
||||
flex-direction: column
|
||||
</style>
|
||||
|
|
|
@ -1,21 +1,23 @@
|
|||
<template>
|
||||
<div class="text-center ">
|
||||
<v-dialog v-model="dialog" width="600px" height="0" :fullscreen="isMobile" content-class="top-dialog">
|
||||
<v-card>
|
||||
<v-dialog
|
||||
v-model="dialog"
|
||||
:width="isMobile ? undefined : '600'"
|
||||
:height="isMobile ? undefined : '0'"
|
||||
:fullscreen="isMobile"
|
||||
content-class="top-dialog"
|
||||
>
|
||||
<v-card relative>
|
||||
<v-app-bar dark color="primary lighten-1" rounded="0">
|
||||
<SearchBar
|
||||
ref="mealSearchBar"
|
||||
@results="updateResults"
|
||||
@selected="emitSelect"
|
||||
:show-results="!isMobile"
|
||||
max-width="568"
|
||||
:dense="false"
|
||||
:nav-on-click="false"
|
||||
:autofocus="true"
|
||||
/>
|
||||
<v-btn icon @click="dialog = false" class="mt-1">
|
||||
<v-icon> mdi-close </v-icon>
|
||||
</v-btn>
|
||||
</v-app-bar>
|
||||
<v-card-text v-if="isMobile">
|
||||
<div v-for="recipe in searchResults.slice(0, 7)" :key="recipe.name">
|
||||
|
@ -31,6 +33,9 @@
|
|||
/>
|
||||
</div>
|
||||
</v-card-text>
|
||||
<v-btn v-if="isMobile" fab bottom @click="dialog = false" class="ma-2">
|
||||
<v-icon> mdi-close </v-icon>
|
||||
</v-btn>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</div>
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
<script>
|
||||
import TheSiteMenu from "@/components/UI/TheSiteMenu";
|
||||
import SearchBar from "@/components/UI/Search/SearchBar";
|
||||
import SearchDialog from "@/components/UI/Search/SearchDialog";
|
||||
import SearchDialog from "@/components/UI/Dialogs/SearchDialog";
|
||||
import TheRecipeFab from "@/components/UI/TheRecipeFab";
|
||||
import TheSidebar from "@/components/UI/TheSidebar";
|
||||
import { user } from "@/mixins/user";
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
|
||||
<v-spacer v-if="!isMobile"> </v-spacer>
|
||||
|
||||
<fuse-search-bar :raw-data="allItems" @results="filterItems" :search="searchString">
|
||||
<fuse-search-bar class="fit-search mr-2" :raw-data="allItems" @results="filterItems" :search="searchString">
|
||||
<v-text-field
|
||||
v-model="searchString"
|
||||
clearable
|
||||
|
@ -218,4 +218,7 @@ export default {
|
|||
height: auto !important;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.fit-search {
|
||||
max-width: 300px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -62,85 +62,46 @@
|
|||
</template>
|
||||
</BaseDialog>
|
||||
</v-card-actions>
|
||||
<v-simple-table>
|
||||
<template v-slot:default>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">
|
||||
{{ $t("general.type") }}
|
||||
</th>
|
||||
<th class="text-center">
|
||||
{{ $t("general.name") }}
|
||||
</th>
|
||||
<th class="text-center">
|
||||
{{ $t("general.general") }}
|
||||
</th>
|
||||
<th class="text-center">
|
||||
{{ $t("general.recipe") }}
|
||||
</th>
|
||||
<th class="text-center">
|
||||
{{ $t("events.database") }}
|
||||
</th>
|
||||
<th class="text-center">
|
||||
{{ $t("events.scheduled") }}
|
||||
</th>
|
||||
<th class="text-center">
|
||||
{{ $t("settings.migrations") }}
|
||||
</th>
|
||||
<th class="text-center">
|
||||
{{ $t("group.group") }}
|
||||
</th>
|
||||
<th class="text-center">
|
||||
{{ $t("user.user") }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(item, index) in notifications" :key="index">
|
||||
<td>
|
||||
<v-avatar size="35" class="ma-1" :color="getIcon(item.type).icon ? 'primary' : undefined">
|
||||
<v-icon dark v-if="getIcon(item.type).icon"> {{ getIcon(item.type).icon }}</v-icon>
|
||||
<v-img v-else :src="getIcon(item.type).image"> </v-img>
|
||||
</v-avatar>
|
||||
{{ item.type }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.name }}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<v-icon color="success"> {{ item.general ? "mdi-check" : "" }} </v-icon>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<v-icon color="success"> {{ item.recipe ? "mdi-check" : "" }} </v-icon>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<v-icon color="success"> {{ item.backup ? "mdi-check" : "" }} </v-icon>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<v-icon color="success"> {{ item.scheduled ? "mdi-check" : "" }} </v-icon>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<v-icon color="success"> {{ item.migration ? "mdi-check" : "" }} </v-icon>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<v-icon color="success"> {{ item.group ? "mdi-check" : "" }} </v-icon>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<v-icon color="success"> {{ item.user ? "mdi-check" : "" }} </v-icon>
|
||||
</td>
|
||||
<td>
|
||||
<TheButton delete small minor @click="deleteNotification(item.id)"> </TheButton>
|
||||
<TheButton edit small @click="testByID(item.id)">
|
||||
<template v-slot:icon>
|
||||
mdi-test-tube
|
||||
</template>
|
||||
{{ $t("general.test") }}
|
||||
</TheButton>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<v-data-table
|
||||
disable-sort
|
||||
:headers="headers"
|
||||
:items="notifications"
|
||||
class="elevation-1 text-center"
|
||||
:footer-props="{
|
||||
'items-per-page-options': [10, 20, 30, 40, -1],
|
||||
}"
|
||||
:items-per-page="10"
|
||||
>
|
||||
<template v-for="boolHeader in headers" v-slot:[`item.${boolHeader.value}`]="{ item }">
|
||||
<div :key="boolHeader.value">
|
||||
<div v-if="boolHeader.value === 'type'">
|
||||
<v-avatar size="35" class="ma-1" :color="getIcon(item.type).icon ? 'primary' : undefined">
|
||||
<v-icon dark v-if="getIcon(item.type).icon"> {{ getIcon(item.type).icon }}</v-icon>
|
||||
<v-img v-else :src="getIcon(item.type).image"> </v-img>
|
||||
</v-avatar>
|
||||
{{ item[boolHeader.value] }}
|
||||
</div>
|
||||
<v-icon
|
||||
v-else-if="item[boolHeader.value] === true || item[boolHeader.value] === false"
|
||||
:color="item[boolHeader.value] ? 'success' : 'gray'"
|
||||
>
|
||||
{{ item[boolHeader.value] ? "mdi-check" : "mdi-close" }}
|
||||
</v-icon>
|
||||
<div v-else-if="boolHeader.text === 'Actions'">
|
||||
<TheButton class="mr-1" delete x-small minor @click="deleteNotification(item.id)" />
|
||||
<TheButton edit x-small @click="testByID(item.id)">
|
||||
<template v-slot:icon>
|
||||
mdi-test-tube
|
||||
</template>
|
||||
{{ $t("general.test") }}
|
||||
</TheButton>
|
||||
</div>
|
||||
<div v-else>
|
||||
{{ item[boolHeader.value] }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</v-simple-table>
|
||||
</v-data-table>
|
||||
</v-card>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -179,19 +140,19 @@ export default {
|
|||
},
|
||||
{
|
||||
text: "Discord",
|
||||
image: "./static/discord.svg",
|
||||
image: "/static/discord.svg",
|
||||
},
|
||||
{
|
||||
text: "Gotify",
|
||||
image: "./static/gotify.png",
|
||||
image: "/static/gotify.png",
|
||||
},
|
||||
{
|
||||
text: "Home Assistant",
|
||||
image: "./static/home-assistant.png",
|
||||
image: "/static/home-assistant.png",
|
||||
},
|
||||
{
|
||||
text: "Pushover",
|
||||
image: "./static/pushover.svg",
|
||||
image: "/static/pushover.svg",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
@ -199,6 +160,22 @@ export default {
|
|||
mounted() {
|
||||
this.getAllNotifications();
|
||||
},
|
||||
computed: {
|
||||
headers() {
|
||||
return [
|
||||
{ text: this.$t("general.type"), value: "type" },
|
||||
{ text: this.$t("general.name"), value: "name" },
|
||||
{ text: this.$t("general.general"), value: "general", align: "center" },
|
||||
{ text: this.$t("general.recipe"), value: "recipe", align: "center" },
|
||||
{ text: this.$t("events.database"), value: "backup", align: "center" },
|
||||
{ text: this.$t("events.scheduled"), value: "scheduled", align: "center" },
|
||||
{ text: this.$t("settings.migrations"), value: "migration", align: "center" },
|
||||
{ text: this.$t("group.group"), value: "group", align: "center" },
|
||||
{ text: this.$t("user.user"), value: "user", align: "center" },
|
||||
{ text: "Actions", align: "center" },
|
||||
];
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getIcon(textValue) {
|
||||
return this.notificationTypes.find(x => x.text === textValue);
|
||||
|
@ -228,3 +205,9 @@ export default {
|
|||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
th {
|
||||
text-align: center !important;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -92,20 +92,24 @@
|
|||
|
||||
<p v-if="!edit" class="mb-0">{{ item.quantity }}</p>
|
||||
|
||||
<v-icon v-if="!edit" small class="mx-3">
|
||||
<v-icon v-if="!edit" x-small class="mx-3">
|
||||
mdi-window-close
|
||||
</v-icon>
|
||||
|
||||
<vue-markdown v-if="!edit" class="dense-markdown" :source="item.text"> </vue-markdown>
|
||||
<v-textarea
|
||||
single-line
|
||||
rows="1"
|
||||
auto-grow
|
||||
class="mb-n2 pa-0"
|
||||
dense
|
||||
v-else
|
||||
v-model="activeList.items[index].text"
|
||||
></v-textarea>
|
||||
<v-lazy>
|
||||
<div>
|
||||
<vue-markdown v-if="!edit" class="dense-markdown" :source="item.text"> </vue-markdown>
|
||||
<v-textarea
|
||||
single-line
|
||||
rows="1"
|
||||
auto-grow
|
||||
class="mb-n2 pa-0"
|
||||
dense
|
||||
v-else
|
||||
v-model="activeList.items[index].text"
|
||||
></v-textarea>
|
||||
</div>
|
||||
</v-lazy>
|
||||
</v-col>
|
||||
<v-divider class="ma-1"></v-divider>
|
||||
</v-row>
|
||||
|
|
Loading…
Reference in a new issue