shopping list and import view ux

This commit is contained in:
Kaibu
2022-04-23 14:47:10 +02:00
parent 161ae9879a
commit 8149192455
5 changed files with 375 additions and 132 deletions

View File

@@ -1,42 +1,156 @@
<template>
<div id="app">
<br/>
<template v-if="import_info !== undefined">
<template v-if="import_info.running">
<h5 style="text-align: center">{{ $t('Importing') }}...</h5>
<b-progress :max="import_info.total_recipes">
<b-progress-bar :value="import_info.imported_recipes" :label="`${import_info.imported_recipes}/${import_info.total_recipes}`"></b-progress-bar>
<b-progress :max="import_info.total_recipes" height="2rem">
<b-progress-bar :value="import_info.imported_recipes"
:label="`${import_info.imported_recipes}/${import_info.total_recipes}`"></b-progress-bar>
</b-progress>
<loading-spinner :size="25"></loading-spinner>
</template>
<b-row>
<b-col>
<b-card no-body>
<b-card-header>
<h4>{{ $t('Information') }}
<b-badge variant="success" class="float-right" v-if="!import_info.running">{{
$t('Import_finished')
}}!
</b-badge>
<b-badge variant="primary" v-else class="float-right">
{{ $t('Import_running') }}
<b-spinner small class="d-inline-block"></b-spinner>
</b-badge>
</h4>
<b-alert variant="danger" show v-if="imported_recipes.error">{{
$t('Import_Error')
}}
</b-alert>
<b-alert variant="warning" show v-if="imported_recipes.duplicates_total > 0">{{
$t("Import_Result_Info", {
imported: imported_recipes.imported_total,
total: imported_recipes.recipes.length
})
}}
</b-alert>
<b-alert variant="success" show v-if="imported_recipes.duplicates_total === 0">{{
$t("Import_Result_Info", {
imported: imported_recipes.imported_total,
total: imported_recipes.recipes.length
})
}}
</b-alert>
<b-row>
<b-col cols="12" class="text-center">
<h5><a
:href="`${resolveDjangoUrl('view_search') }?keyword=${import_info.keyword.id}`"
v-if="import_info.keyword !== null">{{ $t('View_Recipes') }}</a></h5>
</b-col>
</b-row>
</b-card-header>
<b-card-body>
<transition-group name="slide" tag="div" class="row">
<b-media v-for="(recipe, index) in imported_recipes.recipes"
v-bind:key="recipe.id"
class="p-3 mt-2 col-md-6 col-12 shadow-sm" v-hover>
<template #aside>
<b-avatar target="_blank"
:href="resolveDjangoUrl('view_recipe', recipe.id)"
v-if="recipe.imported !== undefined && recipe.imported"
class="mr-3"><i :class="recipe.icon"></i></b-avatar>
<b-avatar v-else class="mr-3"><i :class="recipe.icon"></i></b-avatar>
</template>
<h5 class="mt-0 mb-1">
<a :href="resolveDjangoUrl('view_recipe', recipe.id)"
v-if="recipe.imported !== undefined && recipe.imported"
target="_blank">{{
recipe.recipe_name
}}</a> <span v-else>{{ recipe.recipe_name }}</span>
<b-badge class="float-right text-white">{{ index + 1 }}</b-badge>
</h5>
<p class="mb-0">
<b-badge v-if="recipe.imported !== undefined && recipe.imported"
variant="success">{{ $t('Imported') }}
</b-badge>
<b-badge v-if="recipe.imported !== undefined && !recipe.imported"
variant="warning">{{ $t('Duplicate') }}
</b-badge>
<b-badge v-if="recipe.imported === undefined">
<b-spinner small class="d-inline-block"></b-spinner>
</b-badge>
</p>
</b-media>
</transition-group>
</b-card-body>
<b-card-footer>
<h4>{{ $t('Information') }}
<b-badge variant="success" class="float-right" v-if="!import_info.running">{{
$t('Import_finished')
}}!
</b-badge>
<b-badge variant="primary" v-else class="float-right">
{{ $t('Import_running') }}
<b-spinner small class="d-inline-block"></b-spinner>
</b-badge>
</h4>
<b-alert variant="danger" show v-if="imported_recipes.error">{{
$t('Import_Error')
}}
</b-alert>
<b-alert variant="warning" show v-if="imported_recipes.duplicates_total > 0">{{
$t("Import_Result_Info", {
imported: imported_recipes.imported_total,
total: imported_recipes.recipes.length
})
}}
</b-alert>
<b-alert variant="success" show v-if="imported_recipes.duplicates_total === 0">{{
$t("Import_Result_Info", {
imported: imported_recipes.imported_total,
total: imported_recipes.recipes.length
})
}}
</b-alert>
<b-row>
<b-col cols="12" class="text-center">
<h5><a
:href="`${resolveDjangoUrl('view_search') }?keyword=${import_info.keyword.id}`"
v-if="import_info.keyword !== null">{{ $t('View_Recipes') }}</a></h5>
</b-col>
</b-row>
</b-card-footer>
</b-card>
</b-col>
</b-row>
<div class="row">
<div class="col col-md-12" v-if="!import_info.running">
<span>{{ $t('Import_finished') }}! </span>
<a :href="`${resolveDjangoUrl('view_search') }?keyword=${import_info.keyword.id}`"
v-if="import_info.keyword !== null">{{ $t('View_Recipes') }}</a>
<b-row>
<b-col cols="12">
<b-card no-body>
<b-card-header>
<h4>{{ $t('Details') }}
<b-button v-b-toggle.collapse-details variant="primary" class="float-right">
{{ $t('Toggle') }}
</b-button>
</h4>
</b-card-header>
<b-card-body>
</div>
</div>
<br/>
<div class="row">
<div class="col col-md-12">
<label for="id_textarea">{{ $t('Information') }}</label>
<textarea id="id_textarea" ref="output_text" class="form-control" style="height: 50vh"
<b-collapse id="collapse-details" class="mt-2">
<b-card>
<textarea id="id_textarea" ref="output_text" class="form-control"
style="height: 50vh"
v-html="import_info.msg"
disabled></textarea>
</div>
</div>
<br/>
<br/>
</b-card>
</b-collapse>
</b-card-body>
</b-card>
</b-col>
</b-row>
</template>
</div>
@@ -49,7 +163,7 @@ import {BootstrapVue} from 'bootstrap-vue'
import 'bootstrap-vue/dist/bootstrap-vue.css'
import {ResolveUrlMixin, ToastMixin} from "@/utils/utils";
import {ResolveUrlMixin, ToastMixin, RandomIconMixin} from "@/utils/utils";
import LoadingSpinner from "@/components/LoadingSpinner";
@@ -62,10 +176,61 @@ export default {
mixins: [
ResolveUrlMixin,
ToastMixin,
RandomIconMixin
],
components: {
LoadingSpinner
},
computed: {
imported_recipes: function () {
let msg = this.import_info.msg.split(/\r?\n/)
let out = {recipes: [], imported_total: 0, duplicates_total: 0, info: '', error: false}
if (this.import_info.msg.includes("--------------------")) {
out.error = true
}
msg.forEach((cur, i) => {
let line = cur.trim()
if (line === '') {
return
}
if (this.isNumber(line.charAt(0))) {
let recipe = line.split(/-(.*)/s)
out.recipes.push({
id: recipe[0].trim(),
recipe_name: recipe[1].trim(),
icon: this.getRandomFoodIcon()
})
} else {
if (i === msg.length - 4) {
out.info = line
}
if (i === msg.length - 2) {
out.imported_total = parseInt(line.match(/\d+/)[0])
}
}
if (out.info !== '') {
let items = out.info.split(/:(.*)/s)[1]
items = items.split(",")
out.duplicates_total = items.length
out.recipes.forEach((recipe) => {
recipe.imported = true
items.forEach((item) => {
if (recipe.recipe_name === item.trim()) {
recipe.imported = false
}
})
})
} else {
if (out.imported_total > 0) {
out.recipes.forEach((recipe) => {
recipe.imported = true
})
}
}
})
return out
}
},
data() {
return {
import_id: window.IMPORT_ID,
@@ -90,15 +255,48 @@ export default {
apiClient.retrieveImportLog(this.import_id).then(result => {
this.import_info = result.data
})
},
isNumber: function (char) {
if (typeof char !== 'string') {
return false;
}
if (char.trim() === '') {
return false;
}
return !isNaN(char);
}
}, directives: {
hover: {
inserted: function (el) {
el.addEventListener("mouseenter", () => {
el.classList.add("shadow")
})
el.addEventListener("mouseleave", () => {
el.classList.remove("shadow")
})
},
},
},
}
</script>
<style>
.slide-leave-active,
.slide-enter-active {
transition: 1s;
}
.slide-enter {
transform: translate(100%, 0);
}
.slide-leave-to {
transform: translate(-100%, 0);
}
</style>

View File

@@ -1,12 +1,6 @@
<template>
<div id="app">
<div>
<div class="row">
<div class="col col-md-12">
<h2>{{ $t('Import') }}</h2>
</div>
</div>
<div class="row">
<div class="col col-md-12">
<b-tabs content-class="mt-3" v-model="tab_index">
@@ -282,13 +276,13 @@
<b-badge variant="success" class="ml-1" v-b-tooltip:top
:title="$t('Import_Supported')"><i
class="fas fa-file-import fa-fw" v-if="i.import"></i></b-badge>
<b-badge variant="warning" class="ml-1" v-b-tooltip:top
<b-badge variant="warning" class="ml-1 text-muted" v-b-tooltip:top
:title="$t('Import_Not_Yet_Supported')"><i
class="fas fa-file-import fa-fw" v-if="!i.import"></i></b-badge>
<b-badge variant="success" class="ml-1" v-b-tooltip:top
:title="$t('Export_Supported')"><i
class="fas fa-file-export fa-fw" v-if="i.export"></i></b-badge>
<b-badge variant="warning" class="ml-1" v-b-tooltip:top
<b-badge variant="warning" class="ml-1 text-muted" v-b-tooltip:top
:title="$t('Export_Not_Yet_Supported')"><i
class="fas fa-file-export fa-fw" v-if="!i.export"></i></b-badge>
</b-list-group-item>
@@ -311,13 +305,13 @@
<b-badge variant="success" class="ml-1" v-b-tooltip:top
:title="$t('Import_Supported')"><i
class="fas fa-file-import fa-fw" v-if="i.import"></i></b-badge>
<b-badge variant="warning" class="ml-1" v-b-tooltip:top
<b-badge variant="warning" class="ml-1 text-muted" v-b-tooltip:top
:title="$t('Import_Not_Yet_Supported')"><i
class="fas fa-file-import fa-fw" v-if="!i.import"></i></b-badge>
<b-badge variant="success" class="ml-1" v-b-tooltip:top
:title="$t('Export_Supported')"><i
class="fas fa-file-export fa-fw" v-if="i.export"></i></b-badge>
<b-badge variant="warning" class="ml-1" v-b-tooltip:top
<b-badge variant="warning" class="ml-1 text-muted" v-b-tooltip:top
:title="$t('Export_Not_Yet_Supported')"><i
class="fas fa-file-export fa-fw" v-if="!i.export"></i></b-badge>
</b-list-group-item>
@@ -361,7 +355,7 @@
:placeholder="$t('paste_json')" style="font-size: 12px">
</b-textarea>
</div>
<b-button @click="loadRecipe('',false, undefined)" variant="primary"><i
<b-button class="mt-2" @click="loadRecipe('',false, undefined)" variant="primary"><i
class="fas fa-code"></i>
{{ $t('Import') }}
</b-button>
@@ -369,16 +363,23 @@
</b-tab>
<!-- Bookmarklet Tab -->
<b-tab v-bind:title="$t('Bookmarklet')">
<b-container>
<b-row class="mt-4">
<b-col cols="12">
<!-- TODO localize -->
Some pages cannot be imported from their URL, the Bookmarklet can be used to import from
Some pages cannot be imported from their URL, the Bookmarklet can be used to
import from
some of them anyway.<br/>
1. Drag the following button to your bookmarks bar <a class="btn btn-outline-info btn-sm"
1. Drag the following button to your bookmarks bar: <br/> <a
class="btn btn-outline-info btn-sm"
:href="makeBookmarklet()">Import into
Tandoor</a> <br/>
2. Open the page you want to import from <br/>
3. Click on the bookmark to perform the import <br/>
</b-col>
</b-row>
</b-container>
</b-tab>
</b-tabs>
@@ -446,7 +447,7 @@ export default {
},
data() {
return {
tab_index: 1,
tab_index: 0,
collapse_visible: {
url: true,
options: false,

View File

@@ -22,7 +22,7 @@
:label="$t('copy_markdown_table')" icon="fab fa-markdown"/>
</div>
</b-button>
<i id="id_filters_button" class="fas fa-filter fa-fw mt-1" style="font-size: 16px"
<i id="id_filters_button" class="fas fa-filter fa-fw mt-1" style="font-size: 16px; cursor: pointer"
:class="filterApplied ? 'text-danger' : 'text-primary'"/>
</div>
</div>
@@ -35,7 +35,7 @@
<i v-if="!loading" class="fas fa-shopping-cart fa-fw d-inline-block d-md-none"></i>
<span class="d-none d-md-block">{{ $t('Shopping_list') }}</span>
</template>
<div class="container p-0" id="shoppinglist">
<div class="container p-0 p-md-3" id="shoppinglist">
<div class="row">
<div class="col col-md-12 p-0 p-lg-3">
<div role="tablist">
@@ -52,6 +52,7 @@
:description="$t('Amount')"
v-model="new_item.amount"
style="font-size: 16px; border-radius: 5px !important; border: 1px solid #e8e8e8 !important"
ref="amount_input_complex"
></b-form-input>
</b-col>
<b-col cols="12" md="4" v-if="!ui.entry_mode_simple" class="mt-1">
@@ -67,10 +68,11 @@
<b-col cols="12" md="11" v-if="ui.entry_mode_simple" class="mt-1">
<b-form-input size="lg" type="text" :placeholder="$t('QuickEntry')"
v-model="new_item.ingredient"
@keyup.enter="addItem"></b-form-input>
@keyup.enter="addItem"
ref="amount_input_simple"></b-form-input>
</b-col>
<b-col cols="12" md="1" class="d-none d-md-block mt-1">
<b-button variant="link" class="px-0">
<b-button variant="link" class="px-0" type="submit">
<i class="btn fas fa-cart-plus fa-lg px-0 text-success"
@click="addItem"/>
</b-button>
@@ -861,7 +863,7 @@ export default {
delay: 0,
clear: Math.random(),
ui: {
entry_mode_simple: false,
entry_mode_simple: true,
selected_supermarket: undefined,
},
settings: {
@@ -1019,14 +1021,23 @@ export default {
watch: {
ui: {
handler() {
this.$cookies.set(SETTINGS_COOKIE_NAME, this.ui)
this.$cookies.set(SETTINGS_COOKIE_NAME, {ui: this.ui, settings: {entrymode: this.entrymode}})
if (this.entrymode) {
this.$nextTick(function () {
this.setFocus()
})
}
},
deep: true,
},
entrymode: {
handler() {
this.$cookies.set(SETTINGS_COOKIE_NAME, {ui: this.ui, settings: {entrymode: this.entrymode}})
if (this.entrymode) {
document.getElementById('shoppinglist').scrollTop = 0
this.$nextTick(function () {
this.setFocus()
})
}
}
},
@@ -1078,13 +1089,23 @@ export default {
}
this.$nextTick(function () {
if (this.$cookies.isKey(SETTINGS_COOKIE_NAME)) {
this.ui = Object.assign({}, this.ui, this.$cookies.get(SETTINGS_COOKIE_NAME))
this.ui = Object.assign({}, this.ui, this.$cookies.get(SETTINGS_COOKIE_NAME).ui)
this.entrymode = this.$cookies.get(SETTINGS_COOKIE_NAME).settings.entrymode
}
})
this.$i18n.locale = window.CUSTOM_LOCALE
console.log(window.CUSTOM_LOCALE)
},
methods: {
setFocus() {
if (this.ui.entry_mode_simple) {
this.$refs['amount_input_simple'].focus()
} else {
if (this.$refs['amount_input_complex']) {
this.$refs['amount_input_complex'].focus()
}
}
},
// this.genericAPI inherited from ApiMixin
addItem: function () {
if (this.ui.entry_mode_simple) {
@@ -1106,6 +1127,7 @@ export default {
} else {
this.addEntry()
}
this.setFocus()
},
addEntry: function (x) {
let api = new ApiApiFactory()
@@ -1646,7 +1668,13 @@ export default {
padding-left: 5px;
}
@media (min-width: 768px) {
@media (max-width: 991.9px) {
#shoppinglist {
max-width: none;
}
}
@media (min-width: 992px) {
#shoppinglist {
overflow-y: auto;
overflow-x: auto;

View File

@@ -20,7 +20,7 @@
aria-expanded="false"
type="button"
:class="settings.left_handed ? 'dropdown-spacing' : ''"
class="btn dropdown-toggle btn-link text-decoration-none text-body pr-0 pl-1 dropdown-toggle-no-caret">
class="btn dropdown-toggle btn-link text-decoration-none text-body pr-0 pl-1 pr-md-3 pl-md-3 dropdown-toggle-no-caret">
<i class="fas fa-ellipsis-v"></i>
</button>
</div>
@@ -53,7 +53,7 @@
class="p-0 mr-0 mr-md-2 p-md-2 text-decoration-none" variant="link">
<div class="text-nowrap">
<i class="fa fa-chevron-right rotate" :class="showDetails ? 'rotated' : ''"></i>
<span class="d-none d-md-inline-block">{{ $t("Details") }}</span>
<span class="d-none d-md-inline-block"><span class="ml-2">{{ $t("Details") }}</span></span>
</div>
</b-button>
</b-col>
@@ -112,7 +112,7 @@
aria-expanded="false"
type="button"
:class="settings.left_handed ? 'dropdown-spacing' : ''"
class="btn dropdown-toggle btn-link text-decoration-none text-body pr-1 dropdown-toggle-no-caret"
class="btn dropdown-toggle btn-link text-decoration-none text-body pr-1 pr-md-3 pl-md-3 dropdown-toggle-no-caret"
>
<i class="fas fa-ellipsis-v fa-lg"></i>
</button>
@@ -453,4 +453,16 @@ export default {
.invis-border {
border: 1px solid transparent;
}
@media (min-width: 992px) {
.fa-ellipsis-v {
font-size: 20px;
}
}
@media (min-width: 576px) {
.fa-ellipsis-v {
font-size: 16px;
}
}
</style>

View File

@@ -400,5 +400,9 @@
"Import_Supported": "Import supported",
"Export_Supported": "Export supported",
"Import_Not_Yet_Supported": "Import not yet supported",
"Export_Not_Yet_Supported": "Export not yet supported"
"Export_Not_Yet_Supported": "Export not yet supported",
"Import_Result_Info": "{imported} of {total} recipes were imported",
"Recipes_In_Import": "Recipes in your import file",
"Toggle": "Toggle",
"Import_Error": "An Error occurred during your import. Please expand the Details at the bottom of the page to view it."
}