Merge branch 'feature/importer_to_vue' into develop
# Conflicts: # vue/src/apps/RecipeView/RecipeView.vue
This commit is contained in:
671
vue/src/apps/ImportView/ImportView.vue
Normal file
671
vue/src/apps/ImportView/ImportView.vue
Normal file
@ -0,0 +1,671 @@
|
||||
<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">
|
||||
<!-- URL Tab -->
|
||||
<b-tab v-bind:title="$t('Website')" id="id_tab_url" active>
|
||||
|
||||
<!-- URL -->
|
||||
<b-card no-body>
|
||||
<b-card-header header-tag="header" class="p-1" role="tab">
|
||||
<b-col cols="12" md="6" offset="0" offset-md="3">
|
||||
<b-button block v-b-toggle.id_accordion_url variant="info">Website</b-button>
|
||||
<!-- TODO localize -->
|
||||
</b-col>
|
||||
</b-card-header>
|
||||
<b-collapse id="id_accordion_url" visible accordion="url_import_accordion"
|
||||
role="tabpanel" v-model="collapse_visible.url">
|
||||
<div class="row justify-content-center p-2">
|
||||
<div class="col-12 col-lg-10 col-xl-8 mt-3 mb-3">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12 justify-content-cente">
|
||||
<b-checkbox v-model="import_multiple" switch><span
|
||||
v-if="import_multiple">Multiple Recipes</span><span
|
||||
v-if="!import_multiple">Single Recipe</span></b-checkbox>
|
||||
<!-- TODO localize -->
|
||||
</div>
|
||||
</div>
|
||||
<b-input-group class="mt-2" :class="{ bounce: empty_input }"
|
||||
v-if="!import_multiple">
|
||||
<b-input
|
||||
class="form-control form-control-lg form-control-borderless form-control-search"
|
||||
v-model="website_url"
|
||||
placeholder="Website URL" @paste="onURLPaste"></b-input>
|
||||
<!-- TODO localize -->
|
||||
<b-input-group-append>
|
||||
<b-button variant="primary"
|
||||
@click="loadRecipe(website_url,false,undefined)"><i
|
||||
class="fas fa-search fa-fw"></i>
|
||||
</b-button>
|
||||
</b-input-group-append>
|
||||
</b-input-group>
|
||||
<b-textarea rows="10" placeholder="Enter one URL per line"
|
||||
v-model="website_url_list"
|
||||
v-if="import_multiple"> <!-- TODO localize -->
|
||||
</b-textarea>
|
||||
|
||||
<b-button class="float-right" v-if="import_multiple"
|
||||
:disabled="website_url_list.length < 1"
|
||||
@click="autoImport()">Import
|
||||
</b-button>
|
||||
|
||||
<div class="row mt-2"> <!-- TODO remove -->
|
||||
<div class="col col-md-12">
|
||||
<div v-if="!import_multiple">
|
||||
<a href="#" @click="clearRecentImports()">Clear recent
|
||||
imports</a>
|
||||
<ul>
|
||||
<li v-for="x in recent_urls" v-bind:key="x">
|
||||
<a href="#"
|
||||
@click="loadRecipe(x, false, undefined)">{{
|
||||
x
|
||||
}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</b-collapse>
|
||||
</b-card>
|
||||
|
||||
<!-- Loading -->
|
||||
<b-card no-body v-if="loading">
|
||||
<loading-spinner></loading-spinner>
|
||||
</b-card>
|
||||
|
||||
<!-- OPTIONS -->
|
||||
<b-card no-body v-if="recipe_json !== undefined">
|
||||
<b-card-header header-tag="header" class="p-1" role="tab">
|
||||
<b-col cols="12" md="6" offset="0" offset-md="3">
|
||||
<b-button block v-b-toggle.id_accordion_add_options variant="info"
|
||||
:disabled="recipe_json === undefined">Options
|
||||
</b-button>
|
||||
</b-col>
|
||||
</b-card-header>
|
||||
<b-collapse id="id_accordion_add_options" accordion="url_import_accordion"
|
||||
role="tabpanel" v-model="collapse_visible.options">
|
||||
<b-card-body v-if="recipe_json !== undefined"
|
||||
class="p-1 pb-md-5 pr-md-5 pl-md-5 pt-md-2">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-8 offset-0 offset-md-2">
|
||||
<h4 class="text-center flex-grow-1" v-b-tooltip.hover.bottom
|
||||
:title="$t('Click_To_Edit')" v-if="!edit_name"
|
||||
@click="edit_name = true">{{
|
||||
recipe_json.name
|
||||
}} <span class="text-primary"><i class="fa fa-edit"></i> </span>
|
||||
</h4>
|
||||
<b-input-group v-if="edit_name" class="mb-2">
|
||||
<b-input
|
||||
class="form-control form-control-borderless form-control-search"
|
||||
v-model="recipe_json.name"></b-input>
|
||||
<b-input-group-append>
|
||||
<b-button variant="primary" @click="edit_name = false"><i
|
||||
class="fas fa-save fa-fw"></i>
|
||||
</b-button>
|
||||
</b-input-group-append>
|
||||
</b-input-group>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col col-md-12 text-center">
|
||||
<b-img rounded fluid :src="recipe_json.image"
|
||||
style="max-height: 30vh"></b-img>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-1">
|
||||
<div class="col col-md-12 text-center">
|
||||
<small class="text-muted">Click the image you want to import for this
|
||||
recipe</small> <!-- TODO localize -->
|
||||
<span v-if="recipe_images.length === 0">No additional images found in source.</span>
|
||||
<div class="scrolling-wrapper-flexbox">
|
||||
<div class="wrapper-card" v-for="i in recipe_images"
|
||||
v-bind:key="i"
|
||||
@click="recipe_json.image = i">
|
||||
<b-img rounded thumbnail fluid :src="i"
|
||||
style="max-height: 10vh"
|
||||
></b-img>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-2 mt-md-4">
|
||||
<div class="col-12 col-md-6 offset-0 offset-md-3">
|
||||
<b-card no-body>
|
||||
<b-card-title>
|
||||
<div class="clearfix">
|
||||
<span class="float-left h5">Keywords</span>
|
||||
<b-button-group class="float-right">
|
||||
<b-button class="float-right" variant="primary"
|
||||
@click="$set(recipe_json, 'keywords', recipe_json.keywords.map(x => {x.show = true; return x}))">
|
||||
<i
|
||||
class="fa fa-check-double"></i></b-button>
|
||||
<b-button class="float-right" variant="secondary"
|
||||
@click="$set(recipe_json, 'keywords', recipe_json.keywords.map(x => {x.show = false; return x}))">
|
||||
<i
|
||||
class="fa fa-times"></i></b-button>
|
||||
</b-button-group>
|
||||
</div>
|
||||
</b-card-title>
|
||||
<b-card-body class="m-0 p-0 p-md-5">
|
||||
<b-list-group>
|
||||
<b-list-group-item
|
||||
v-for="(k, index) in recipe_json.keywords"
|
||||
v-bind:key="k.name" style="cursor: pointer"
|
||||
v-hover v-bind:class="{ 'bg-success': k.show }"
|
||||
@click="k.show = (!k.show);$set(recipe_json.keywords, index, k)">
|
||||
<div class="clearfix">
|
||||
<span class="float-left">{{
|
||||
k.label
|
||||
}} </span>
|
||||
<b-checkbox class="float-right" v-model="k.show"
|
||||
disabled></b-checkbox>
|
||||
</div>
|
||||
</b-list-group-item>
|
||||
</b-list-group>
|
||||
</b-card-body>
|
||||
</b-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<import-view-step-editor :recipe="recipe_json"
|
||||
@change="recipe_json = $event"></import-view-step-editor>
|
||||
|
||||
</b-card-body>
|
||||
</b-collapse>
|
||||
</b-card>
|
||||
|
||||
<!-- IMPORT -->
|
||||
<b-card no-body
|
||||
v-if="recipe_json !== undefined || (imported_recipes.length > 0 || failed_imports.length>0)">
|
||||
<b-card-header header-tag="header" class="p-1" role="tab">
|
||||
<b-col cols="12" md="6" offset="0" offset-md="3">
|
||||
<b-button block v-b-toggle.id_accordion_import variant="info"
|
||||
:disabled="recipe_json === undefined">Import
|
||||
</b-button>
|
||||
</b-col>
|
||||
</b-card-header>
|
||||
<b-collapse id="id_accordion_import" visible accordion="url_import_accordion"
|
||||
role="tabpanel" v-model="collapse_visible.import">
|
||||
<b-card-body class="text-center">
|
||||
<b-row>
|
||||
<b-col cols="12" md="6" xl="4" offset="0" offset-md="3" offset-xl="4"
|
||||
v-if="!import_multiple">
|
||||
|
||||
<recipe-card :recipe="recipe_json" :detailed="false"
|
||||
:show_context_menu="false"
|
||||
></recipe-card>
|
||||
</b-col>
|
||||
<b-col>
|
||||
<b-list-group>
|
||||
<b-list-group-item
|
||||
v-for="r in imported_recipes"
|
||||
v-bind:key="r.id"
|
||||
v-hover>
|
||||
<div class="clearfix">
|
||||
<a class="float-left" target="_blank"
|
||||
rel="noreferrer nofollow"
|
||||
:href="resolveDjangoUrl('view_recipe',r.id)">{{
|
||||
r.name
|
||||
}}</a>
|
||||
<span class="float-right">Imported</span>
|
||||
<!-- TODO localize -->
|
||||
</div>
|
||||
</b-list-group-item>
|
||||
|
||||
<b-list-group-item
|
||||
v-for="u in failed_imports"
|
||||
v-bind:key="u"
|
||||
v-hover>
|
||||
<div class="clearfix">
|
||||
<a class="float-left" target="_blank"
|
||||
rel="noreferrer nofollow" :href="u">{{ u }}</a>
|
||||
<span class="float-right">Failed</span>
|
||||
<!-- TODO localize -->
|
||||
</div>
|
||||
</b-list-group-item>
|
||||
</b-list-group>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-card-body>
|
||||
<b-card-footer class="text-center">
|
||||
<b-button-group>
|
||||
<b-button @click="importRecipe('view')" v-if="!import_multiple">Import &
|
||||
View
|
||||
</b-button> <!-- TODO localize -->
|
||||
<b-button @click="importRecipe('edit')" variant="success"
|
||||
v-if="!import_multiple">Import & Edit
|
||||
</b-button>
|
||||
<b-button @click="importRecipe('import')" v-if="!import_multiple">Import &
|
||||
Restart
|
||||
</b-button>
|
||||
<b-button @click="location.reload()">Restart
|
||||
</b-button>
|
||||
</b-button-group>
|
||||
</b-card-footer>
|
||||
</b-collapse>
|
||||
</b-card>
|
||||
</b-tab>
|
||||
<!-- App Tab -->
|
||||
<b-tab v-bind:title="$t('App')">
|
||||
|
||||
<select class="form-control" v-model="recipe_app">
|
||||
<option v-for="i in INTEGRATIONS" :value="i.id" v-bind:key="i.id">{{ i.name }}</option>
|
||||
</select>
|
||||
|
||||
<b-form-checkbox v-model="import_duplicates" name="check-button" switch
|
||||
style="margin-top: 1vh">
|
||||
{{ $t('import_duplicates') }}
|
||||
</b-form-checkbox>
|
||||
|
||||
<a href="recipe_app_info.help_url"
|
||||
v-if="recipe_app_info !== undefined && recipe_app_info.help_url !== ''">{{
|
||||
$t('Help')
|
||||
}}</a>
|
||||
|
||||
<b-form-file
|
||||
class="my-2"
|
||||
multiple
|
||||
v-model="recipe_files"
|
||||
:placeholder="$t('Select_File')"
|
||||
drop-placeholder="Drop recipe files here...">
|
||||
</b-form-file>
|
||||
<button @click="importAppRecipe()" class="btn btn-primary shadow-none" type="button"
|
||||
id="id_btn_app"><i class="fas fa-file-archive"></i> {{ $t('Import') }}
|
||||
</button>
|
||||
</b-tab>
|
||||
<!-- Source Tab -->
|
||||
<b-tab v-bind:title="$t('Source')">
|
||||
|
||||
<div class="input-group mt-4">
|
||||
<b-textarea class="form-control input-group-append" v-model="source_data" rows=10
|
||||
:placeholder="$t('paste_json')" style="font-size: 12px">
|
||||
</b-textarea>
|
||||
</div>
|
||||
<b-button @click="loadRecipe('',false, undefined)" variant="primary"><i
|
||||
class="fas fa-code"></i>
|
||||
{{ $t('Import') }}
|
||||
</b-button>
|
||||
|
||||
</b-tab>
|
||||
<!-- Bookmarklet Tab -->
|
||||
<b-tab v-bind:title="$t('Bookmarklet')">
|
||||
<!-- TODO localize -->
|
||||
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"
|
||||
: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-tab>
|
||||
|
||||
</b-tabs>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from 'vue'
|
||||
import {BootstrapVue} from 'bootstrap-vue'
|
||||
|
||||
import 'bootstrap-vue/dist/bootstrap-vue.css'
|
||||
|
||||
import {resolveDjangoStatic, resolveDjangoUrl, ResolveUrlMixin, StandardToasts, ToastMixin} from "@/utils/utils";
|
||||
import axios from "axios";
|
||||
import {ApiApiFactory} from "@/utils/openapi/api";
|
||||
import {INTEGRATIONS} from "@/utils/integration";
|
||||
import ImportViewStepEditor from "@/apps/ImportView/ImportViewStepEditor";
|
||||
import RecipeCard from "@/components/RecipeCard";
|
||||
import LoadingSpinner from "@/components/LoadingSpinner";
|
||||
|
||||
Vue.use(BootstrapVue)
|
||||
|
||||
export default {
|
||||
name: 'ImportView',
|
||||
mixins: [
|
||||
ResolveUrlMixin,
|
||||
ToastMixin,
|
||||
],
|
||||
components: {
|
||||
LoadingSpinner,
|
||||
RecipeCard,
|
||||
ImportViewStepEditor
|
||||
},
|
||||
computed: {
|
||||
recipe_app_info: function () {
|
||||
return this.INTEGRATIONS.filter(x => x.id === this.recipe_app)[0]
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tab_index: 0,
|
||||
collapse_visible: {
|
||||
url: true,
|
||||
options: false,
|
||||
advanced_options: false,
|
||||
import: false,
|
||||
},
|
||||
// URL import
|
||||
LS_IMPORT_RECENT: 'import_recent_urls', //TODO use central helper to manage all local storage keys (and maybe even access)
|
||||
website_url: '',
|
||||
website_url_list: 'https://madamedessert.de/schokoladenpudding-rezept-mit-echter-schokolade/\nhttps://www.essen-und-trinken.de/rezepte/58294-rzpt-schokoladenpudding\n' +
|
||||
'https://www.chefkoch.de/rezepte/1825781296124455/Schokoladenpudding-selbst-gemacht.html\ntest.com\nhttps://bla.com',
|
||||
|
||||
import_multiple: false,
|
||||
recent_urls: [],
|
||||
source_data: '',
|
||||
recipe_json: undefined,
|
||||
recipe_html: undefined,
|
||||
recipe_tree: undefined,
|
||||
recipe_images: [],
|
||||
imported_recipes: [],
|
||||
failed_imports: [],
|
||||
// App Import
|
||||
INTEGRATIONS: INTEGRATIONS,
|
||||
recipe_app: undefined,
|
||||
import_duplicates: false,
|
||||
recipe_files: [],
|
||||
loading: false,
|
||||
empty_input: false,
|
||||
edit_name: false,
|
||||
// Bookmarklet
|
||||
BOOKMARKLET_CODE: window.BOOKMARKLET_CODE
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
let local_storage_recent = JSON.parse(window.localStorage.getItem(this.LS_IMPORT_RECENT))
|
||||
this.recent_urls = local_storage_recent !== null ? local_storage_recent : []
|
||||
this.tab_index = 0 //TODO add ability to pass open tab via get parameter
|
||||
|
||||
if (window.BOOKMARKLET_IMPORT_ID !== -1) {
|
||||
this.loadRecipe('', false, window.BOOKMARKLET_IMPORT_ID)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* Import recipe based on the data configured by the client
|
||||
* @param action: action to perform after import (options are: edit, view, import)
|
||||
* @param data: if parameter is passed ignore global application state and import form data variable
|
||||
* @param silent do not show any messages for imports
|
||||
*/
|
||||
importRecipe: function (action, data, silent) {
|
||||
if (this.recipe_json !== undefined) {
|
||||
this.$set(this.recipe_json, 'keywords', this.recipe_json.keywords.filter(k => k.show))
|
||||
}
|
||||
|
||||
let apiFactory = new ApiApiFactory()
|
||||
let recipe_json = data !== undefined ? data : this.recipe_json
|
||||
if (recipe_json !== undefined) {
|
||||
apiFactory.createRecipe(recipe_json).then(response => { // save recipe
|
||||
let recipe = response.data
|
||||
apiFactory.imageRecipe(response.data.id, undefined, recipe_json.image).then(response => { // save recipe image
|
||||
if (!silent) {
|
||||
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_CREATE)
|
||||
}
|
||||
this.afterImportAction(action, recipe)
|
||||
}).catch(e => {
|
||||
if (!silent) {
|
||||
StandardToasts.makeStandardToast(StandardToasts.FAIL_UPDATE)
|
||||
}
|
||||
this.afterImportAction(action, recipe)
|
||||
})
|
||||
}).catch(err => {
|
||||
if (recipe_json.source_url !== '') {
|
||||
this.failed_imports.push(recipe_json.source_url)
|
||||
}
|
||||
if (!silent) {
|
||||
StandardToasts.makeStandardToast(StandardToasts.FAIL_CREATE)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
console.log('cant import recipe without data')
|
||||
if (!silent) {
|
||||
StandardToasts.makeStandardToast(StandardToasts.FAIL_CREATE)
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Action performed after URL import
|
||||
* @param action: action to perform after import
|
||||
* edit: edit imported recipe
|
||||
* view: view imported recipe
|
||||
* import: restart the importer
|
||||
* nothing: do nothing
|
||||
* @param recipe: recipe that was imported
|
||||
*/
|
||||
afterImportAction: function (action, recipe) {
|
||||
switch (action) {
|
||||
case 'edit':
|
||||
window.location = resolveDjangoUrl('edit_recipe', recipe.id)
|
||||
break;
|
||||
case 'view':
|
||||
window.location = resolveDjangoUrl('view_recipe', recipe.id)
|
||||
break;
|
||||
case 'import':
|
||||
location.reload();
|
||||
break;
|
||||
case 'multi_import':
|
||||
this.imported_recipes.push(recipe)
|
||||
break;
|
||||
case 'nothing':
|
||||
break;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Requests the recipe to be loaded form the source (url/data) from the server
|
||||
* Updates all variables to contain what they need to render either simple preview or manual mapping mode
|
||||
* @param url url to import (optional, empty string for bookmarklet imports)
|
||||
* @param silent do not open the options tab after loading the recipe
|
||||
* @param bookmarklet id of bookmarklet import to load instead of url, default undefined
|
||||
*/
|
||||
loadRecipe: function (url, silent, bookmarklet) {
|
||||
// keep list of recently imported urls
|
||||
if (url !== '') {
|
||||
if (this.recent_urls.length > 5) {
|
||||
this.recent_urls.pop()
|
||||
}
|
||||
if (this.recent_urls.filter(x => x === url).length === 0) {
|
||||
this.recent_urls.push(url)
|
||||
}
|
||||
window.localStorage.setItem(this.LS_IMPORT_RECENT, JSON.stringify(this.recent_urls))
|
||||
}
|
||||
|
||||
if (url === '' && bookmarklet === undefined) {
|
||||
this.empty_input = true
|
||||
setTimeout(() => {
|
||||
this.empty_input = false
|
||||
}, 1000)
|
||||
return
|
||||
}
|
||||
|
||||
if (!silent) {
|
||||
this.loading = true
|
||||
}
|
||||
|
||||
// reset all variables
|
||||
this.recipe_html = undefined
|
||||
this.recipe_json = undefined
|
||||
this.recipe_tree = undefined
|
||||
this.recipe_images = []
|
||||
|
||||
// load recipe
|
||||
let payload = {
|
||||
'url': url,
|
||||
'data': this.source_data,
|
||||
}
|
||||
|
||||
if (bookmarklet !== undefined) {
|
||||
payload['bookmarklet'] = bookmarklet
|
||||
}
|
||||
|
||||
return axios.post(resolveDjangoUrl('api_recipe_from_source'), payload,).then((response) => {
|
||||
this.loading = false
|
||||
this.recipe_json = response.data['recipe_json'];
|
||||
|
||||
this.recipe_json.keywords.map(x => {
|
||||
if (x.id === undefined) {
|
||||
x.show = false
|
||||
} else {
|
||||
x.show = true
|
||||
}
|
||||
return x
|
||||
})
|
||||
|
||||
this.recipe_tree = response.data['recipe_tree'];
|
||||
this.recipe_html = response.data['recipe_html'];
|
||||
this.recipe_images = response.data['recipe_images'] !== undefined ? response.data['recipe_images'] : [];
|
||||
|
||||
if (!silent) {
|
||||
this.tab_index = 0 // open tab 0 with import wizard
|
||||
this.collapse_visible.options = true // open options collapse
|
||||
}
|
||||
return this.recipe_json
|
||||
}).catch((err) => {
|
||||
if (url !== '') {
|
||||
this.failed_imports.push(url)
|
||||
}
|
||||
StandardToasts.makeStandardToast(StandardToasts.FAIL_FETCH, err.response.data.msg)
|
||||
throw "Load Recipe Error"
|
||||
})
|
||||
},
|
||||
/**
|
||||
* Function to automatically import multiple urls
|
||||
* Takes input from website_url_list
|
||||
*/
|
||||
autoImport: function () {
|
||||
this.collapse_visible.import = true
|
||||
this.website_url_list.split('\n').forEach(r => {
|
||||
this.loadRecipe(r, true, undefined).then((recipe_json) => {
|
||||
this.importRecipe('multi_import', recipe_json, true)
|
||||
})
|
||||
})
|
||||
this.website_url_list = ''
|
||||
},
|
||||
/**
|
||||
* Import recipes with uploaded files and app integration
|
||||
*/
|
||||
importAppRecipe: function () {
|
||||
let formData = new FormData();
|
||||
formData.append('type', this.recipe_app);
|
||||
formData.append('duplicates', this.import_duplicates)
|
||||
for (let i = 0; i < this.recipe_files.length; i++) {
|
||||
formData.append('files', this.recipe_files[i]);
|
||||
}
|
||||
axios.post(resolveDjangoUrl('view_import'), formData, {headers: {'Content-Type': 'multipart/form-data'}}).then((response) => {
|
||||
window.location.href = resolveDjangoUrl('view_import_response', response.data['import_id'])
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
StandardToasts.makeStandardToast(StandardToasts.FAIL_CREATE)
|
||||
})
|
||||
},
|
||||
/**
|
||||
* Handles pasting URLs
|
||||
*/
|
||||
onURLPaste: function (evt) {
|
||||
this.website_url = evt.clipboardData.getData('text')
|
||||
this.loadRecipe(false, undefined)
|
||||
return true;
|
||||
},
|
||||
/**loadRecipe(false,undefined)
|
||||
* Clear list of recently imported recipe urls
|
||||
*/
|
||||
clearRecentImports: function () {
|
||||
window.localStorage.setItem(this.LS_IMPORT_RECENT, JSON.stringify([]))
|
||||
this.recent_urls = []
|
||||
},
|
||||
/**
|
||||
* Create the code required for the bookmarklet
|
||||
* @returns {string} javascript:// protocol code to be loaded into href attribute of link that can be bookmarked
|
||||
*/
|
||||
makeBookmarklet: function () {
|
||||
return 'javascript:(function(){' +
|
||||
'if(window.bookmarkletTandoor!==undefined){' +
|
||||
'bookmarkletTandoor();' +
|
||||
'} else {' +
|
||||
`localStorage.setItem("importURL", "${localStorage.getItem('BASE_PATH')}${this.resolveDjangoUrl('api:bookmarkletimport-list')}");` +
|
||||
`localStorage.setItem("redirectURL", "${localStorage.getItem('BASE_PATH')}${this.resolveDjangoUrl('data_import_url')}");` +
|
||||
`localStorage.setItem("token", "${window.API_TOKEN}");` +
|
||||
`document.body.appendChild(document.createElement("script")).src="${localStorage.getItem('BASE_PATH')}${resolveDjangoStatic('/js/bookmarklet.js')}?r="+Math.floor(Math.random()*999999999)}` +
|
||||
`})()`
|
||||
},
|
||||
},
|
||||
directives: {
|
||||
hover: {
|
||||
inserted: function (el) {
|
||||
el.addEventListener("mouseenter", () => {
|
||||
el.classList.add("shadow")
|
||||
})
|
||||
el.addEventListener("mouseleave", () => {
|
||||
el.classList.remove("shadow")
|
||||
})
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
.bounce {
|
||||
animation: bounce 0.82s cubic-bezier(0.36, 0.07, 0.19, 0.97) both;
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
10%,
|
||||
90% {
|
||||
transform: translate3d(-1px, 0, 0);
|
||||
}
|
||||
|
||||
20%,
|
||||
80% {
|
||||
transform: translate3d(2px, 0, 0);
|
||||
}
|
||||
|
||||
30%,
|
||||
50%,
|
||||
70% {
|
||||
transform: translate3d(-4px, 0, 0);
|
||||
}
|
||||
|
||||
40%,
|
||||
60% {
|
||||
transform: translate3d(4px, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.scrolling-wrapper-flexbox {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.wrapper-card {
|
||||
flex: 0 0 auto;
|
||||
margin-right: 3px;
|
||||
}
|
||||
</style>
|
151
vue/src/apps/ImportView/ImportViewStepEditor.vue
Normal file
151
vue/src/apps/ImportView/ImportViewStepEditor.vue
Normal file
@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<div v-if="recipe_json !== undefined" class="mt-2 mt-md-0">
|
||||
<h5>Steps</h5>
|
||||
<div class="row">
|
||||
<div class="col col-md-12 text-center">
|
||||
<b-button @click="splitAllSteps('\n')" variant="secondary" v-b-tooltip.hover :title="$t('Split_All_Steps')"><i
|
||||
class="fas fa-expand-arrows-alt"></i> {{ $t('All') }}
|
||||
</b-button>
|
||||
<b-button @click="mergeAllSteps()" variant="primary" class="ml-1" v-b-tooltip.hover :title="$t('Combine_All_Steps')"><i
|
||||
class="fas fa-compress-arrows-alt"></i> {{ $t('All') }}
|
||||
</b-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-2" v-for="(s, index) in recipe_json.steps"
|
||||
v-bind:key="index">
|
||||
<div class="col col-md-4 d-none d-md-block">
|
||||
<draggable :list="s.ingredients" group="ingredients"
|
||||
:empty-insert-threshold="10">
|
||||
<b-list-group-item v-for="i in s.ingredients"
|
||||
v-bind:key="i.original_text"><i
|
||||
class="fas fa-arrows-alt"></i> {{ i.original_text }}
|
||||
</b-list-group-item>
|
||||
</draggable>
|
||||
</div>
|
||||
<div class="col col-md-8 col-12">
|
||||
<b-input-group>
|
||||
<b-textarea
|
||||
style="white-space: pre-wrap" v-model="s.instruction"
|
||||
max-rows="10"></b-textarea>
|
||||
<b-input-group-append>
|
||||
<b-button variant="secondary" @click="splitStep(s,'\n')"><i
|
||||
class="fas fa-expand-arrows-alt"></i></b-button>
|
||||
<b-button variant="danger"
|
||||
@click="recipe_json.steps.splice(recipe_json.steps.findIndex(x => x === s),1)">
|
||||
<i class="fas fa-trash-alt"></i>
|
||||
</b-button>
|
||||
|
||||
</b-input-group-append>
|
||||
</b-input-group>
|
||||
|
||||
<div class="text-center mt-1">
|
||||
<b-button @click="mergeStep(s)" variant="primary"
|
||||
v-if="index + 1 < recipe_json.steps.length"><i
|
||||
class="fas fa-compress-arrows-alt"></i>
|
||||
</b-button>
|
||||
|
||||
<b-button variant="success"
|
||||
@click="recipe_json.steps.splice(recipe_json.steps.findIndex(x => x === s) +1,0,{ingredients:[], instruction: ''})">
|
||||
<i class="fas fa-plus"></i>
|
||||
</b-button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
|
||||
import draggable from "vuedraggable";
|
||||
|
||||
export default {
|
||||
name: "ImportViewStepEditor",
|
||||
components: {
|
||||
draggable
|
||||
},
|
||||
props: {
|
||||
recipe: undefined
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
recipe_json: undefined
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
recipe_json: function () {
|
||||
this.$emit('change', this.recipe_json)
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.recipe_json = this.recipe
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* utility function used by splitAllSteps and splitStep to split a single step object into multiple step objects
|
||||
* @param step: single step
|
||||
* @param split_character: character to split steps at
|
||||
* @return array of step objects
|
||||
*/
|
||||
splitStepObject: function (step, split_character) {
|
||||
let steps = []
|
||||
step.instruction.split(split_character).forEach(part => {
|
||||
if (part.trim() !== '') {
|
||||
steps.push({'instruction': part, 'ingredients': []})
|
||||
}
|
||||
})
|
||||
steps[0].ingredients = step.ingredients // put all ingredients from the original step in the ingredients of the first step of the split step list
|
||||
return steps
|
||||
},
|
||||
/**
|
||||
* Splits all steps of a given recipe_json at the split character (e.g. \n or \n\n)
|
||||
* @param split_character: character to split steps at
|
||||
*/
|
||||
splitAllSteps: function (split_character) {
|
||||
let steps = []
|
||||
this.recipe_json.steps.forEach(step => {
|
||||
steps = steps.concat(this.splitStepObject(step, split_character))
|
||||
})
|
||||
this.recipe_json.steps = steps
|
||||
},
|
||||
/**
|
||||
* Splits the given step at the split character (e.g. \n or \n\n)
|
||||
* @param step: step ingredients to split
|
||||
* @param split_character: character to split steps at
|
||||
*/
|
||||
splitStep: function (step, split_character) {
|
||||
let old_index = this.recipe_json.steps.findIndex(x => x === step)
|
||||
let new_steps = this.splitStepObject(step, split_character)
|
||||
this.recipe_json.steps.splice(old_index, 1, ...new_steps)
|
||||
},
|
||||
/**
|
||||
* Merge all steps of a given recipe_json into one
|
||||
*/
|
||||
mergeAllSteps: function () {
|
||||
let step = {'instruction': '', 'ingredients': []}
|
||||
this.recipe_json.steps.forEach(s => {
|
||||
step.instruction += s.instruction + '\n'
|
||||
step.ingredients = step.ingredients.concat(s.ingredients)
|
||||
})
|
||||
this.recipe_json.steps = [step]
|
||||
},
|
||||
/**
|
||||
* Merge two steps (the given and next one)
|
||||
*/
|
||||
mergeStep: function (step) {
|
||||
let step_index = this.recipe_json.steps.findIndex(x => x === step)
|
||||
let removed_steps = this.recipe_json.steps.splice(step_index, 2)
|
||||
|
||||
this.recipe_json.steps.splice(step_index, 0, {
|
||||
'instruction': removed_steps.flatMap(x => x.instruction).join('\n'),
|
||||
'ingredients': removed_steps.flatMap(x => x.ingredients)
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
18
vue/src/apps/ImportView/main.js
Normal file
18
vue/src/apps/ImportView/main.js
Normal file
@ -0,0 +1,18 @@
|
||||
import Vue from 'vue'
|
||||
import App from './ImportView.vue'
|
||||
import i18n from '@/i18n'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
// TODO move this and other default stuff to centralized JS file (verify nothing breaks)
|
||||
let publicPath = localStorage.STATIC_URL + 'vue/'
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
publicPath = 'http://localhost:8080/'
|
||||
}
|
||||
export default __webpack_public_path__ = publicPath // eslint-disable-line
|
||||
|
||||
|
||||
new Vue({
|
||||
i18n,
|
||||
render: h => h(App),
|
||||
}).$mount('#app')
|
@ -370,6 +370,9 @@
|
||||
<div v-for="(ingredient, index) in step.ingredients"
|
||||
:key="ingredient.id">
|
||||
<hr class="d-md-none"/>
|
||||
<div class="text-center">
|
||||
<small class="text-muted"><i class="fas fa-globe"></i> {{ingredient.original_text}}</small>
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
<div class="flex-grow-0 handle align-self-start">
|
||||
<button type="button"
|
||||
|
@ -138,6 +138,9 @@
|
||||
@checked-state-changed="updateIngredientCheckedState"
|
||||
></step-component>
|
||||
</div>
|
||||
|
||||
<h5 class="d-print-none"><i class="fas fa-file-import"></i> {{ $t("Imported_From") }}</h5>
|
||||
<span class="text-muted mt-1"><a :href="recipe.source_url">{{ recipe.source_url }}</a></span>
|
||||
</div>
|
||||
|
||||
<add-recipe-to-book :recipe="recipe"></add-recipe-to-book>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div v-if="recipe.keywords.length > 0">
|
||||
<span :key="k.id" v-for="k in recipe.keywords" class="pl-1">
|
||||
<span :key="k.id" v-for="k in recipe.keywords.filter((kk) => { return kk.show || kk.show === undefined })" class="pl-1">
|
||||
<a :href="`${resolveDjangoUrl('view_search')}?keyword=${k.id}`"><b-badge pill variant="light"
|
||||
class="font-weight-normal">{{ k.label }}</b-badge></a>
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
<b-card no-body v-hover v-if="recipe">
|
||||
<a :href="clickUrl()">
|
||||
<b-card-img-lazy style="height: 15vh; object-fit: cover" class="" :src="recipe_image" v-bind:alt="$t('Recipe_Image')" top></b-card-img-lazy>
|
||||
<div class="card-img-overlay h-100 d-flex flex-column justify-content-right float-right text-right pt-2 pr-1">
|
||||
<div class="card-img-overlay h-100 d-flex flex-column justify-content-right float-right text-right pt-2 pr-1" v-if="show_context_menu">
|
||||
<a>
|
||||
<recipe-context-menu :recipe="recipe" class="float-right" v-if="recipe !== null"></recipe-context-menu>
|
||||
</a>
|
||||
@ -24,7 +24,7 @@
|
||||
<b-card-text style="text-overflow: ellipsis">
|
||||
<template v-if="recipe !== null">
|
||||
<recipe-rating :recipe="recipe"></recipe-rating>
|
||||
<template v-if="recipe.description !== null">
|
||||
<template v-if="recipe.description !== null && recipe.description !== undefined">
|
||||
<span v-if="recipe.description.length > text_length">
|
||||
{{ recipe.description.substr(0, text_length) + "\u2026" }}
|
||||
</span>
|
||||
@ -78,6 +78,7 @@ export default {
|
||||
footer_text: String,
|
||||
footer_icon: String,
|
||||
detailed: { type: Boolean, default: true },
|
||||
show_context_menu: { type: Boolean, default: true }
|
||||
},
|
||||
mounted() {},
|
||||
computed: {
|
||||
|
@ -336,6 +336,11 @@
|
||||
"ingredient_list": "Ingredient List",
|
||||
"explain": "Explain",
|
||||
"filter": "Filter",
|
||||
"Website": "Website",
|
||||
"App": "App",
|
||||
"Bookmarklet": "Bookmarklet",
|
||||
"import_duplicates": "To prevent duplicates recipes with the same name as existing ones are ignored. Check this box to import everything.",
|
||||
"paste_json": "Paste json or html source here to load recipe.",
|
||||
"search_no_recipes": "Could not find any recipes!",
|
||||
"search_import_help_text": "Import a recipe from an external website or application.",
|
||||
"search_create_help_text": "Create a new recipe directly in Tandoor.",
|
||||
|
24
vue/src/utils/integration.js
Normal file
24
vue/src/utils/integration.js
Normal file
@ -0,0 +1,24 @@
|
||||
// containing all data and functions regarding the different integrations
|
||||
|
||||
export const INTEGRATIONS = [
|
||||
{id: 'DEFAULT', name: "Tandoor", import: true, export: true, help_url: 'https://docs.tandoor.dev/features/import_export/#default'},
|
||||
{id: 'CHEFTAP', name: "Cheftap", import: true, export: false, help_url: 'https://docs.tandoor.dev/features/import_export/#cheftap'},
|
||||
{id: 'CHOWDOWN', name: "Chowdown", import: true, export: false, help_url: 'https://docs.tandoor.dev/features/import_export/#chowdown'},
|
||||
{id: 'COOKBOOKAPP', name: "CookBookApp", import: true, export: false, help_url: 'https://docs.tandoor.dev/features/import_export/#cookbookapp'},
|
||||
{id: 'COOKMATE', name: "Cookmate", import: true, export: false, help_url: 'https://docs.tandoor.dev/features/import_export/#cookmate'},
|
||||
{id: 'COPYMETHAT', name: "CopyMeThat", import: true, export: false, help_url: 'https://docs.tandoor.dev/features/import_export/#copymethat'},
|
||||
{id: 'DOMESTICA', name: "Domestica", import: true, export: false, help_url: 'https://docs.tandoor.dev/features/import_export/#domestica'},
|
||||
{id: 'MEALIE', name: "Mealie", import: true, export: false, help_url: 'https://docs.tandoor.dev/features/import_export/#mealie'},
|
||||
{id: 'MEALMASTER', name: "Mealmaster", import: true, export: false, help_url: 'https://docs.tandoor.dev/features/import_export/#mealmaster'},
|
||||
{id: 'MELARECIPES', name: "Melarecipes", import: true, export: false, help_url: 'https://docs.tandoor.dev/features/import_export/#melarecipes'},
|
||||
{id: 'NEXTCLOUD', name: "Nextcloud Cookbook", import: true, export: false, help_url: 'https://docs.tandoor.dev/features/import_export/#nextcloud'},
|
||||
{id: 'OPENEATS', name: "Openeats", import: true, export: false, help_url: 'https://docs.tandoor.dev/features/import_export/#openeats'},
|
||||
{id: 'PAPRIKA', name: "Paprika", import: true, export: false, help_url: 'https://docs.tandoor.dev/features/import_export/#paprika'},
|
||||
{id: 'PEPPERPLATE', name: "Pepperplate", import: true, export: false, help_url: 'https://docs.tandoor.dev/features/import_export/#pepperplate'},
|
||||
{id: 'PLANTOEAT', name: "Plantoeat", import: true, export: false, help_url: 'https://docs.tandoor.dev/features/import_export/#plantoeat'},
|
||||
{id: 'RECETTETEK', name: "RecetteTek", import: true, export: false, help_url: 'https://docs.tandoor.dev/features/import_export/#recettetek'},
|
||||
{id: 'RECIPEKEEPER', name: "Recipekeeper", import: true, export: false, help_url: 'https://docs.tandoor.dev/features/import_export/#recipekeeper'},
|
||||
{id: 'RECIPESAGE', name: "Recipesage", import: true, export: true, help_url: 'https://docs.tandoor.dev/features/import_export/#recipesage'},
|
||||
{id: 'REZKONV', name: "Rezkonv", import: true, export: false, help_url: 'https://docs.tandoor.dev/features/import_export/#rezkonv'},
|
||||
{id: 'SAFRON', name: "Safron", import: true, export: true, help_url: 'https://docs.tandoor.dev/features/import_export/#safron'},
|
||||
]
|
@ -472,7 +472,7 @@ export interface FoodRecipe {
|
||||
* @type {string}
|
||||
* @memberof FoodRecipe
|
||||
*/
|
||||
name: string;
|
||||
name?: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
@ -537,7 +537,7 @@ export interface FoodSubstitute {
|
||||
* @type {string}
|
||||
* @memberof FoodSubstitute
|
||||
*/
|
||||
name: string;
|
||||
name?: string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
@ -752,12 +752,6 @@ export interface Ingredient {
|
||||
* @memberof Ingredient
|
||||
*/
|
||||
original_text?: string | null;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof Ingredient
|
||||
*/
|
||||
used_in_recipes?: string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
@ -1862,6 +1856,12 @@ export interface RecipeImage {
|
||||
* @memberof RecipeImage
|
||||
*/
|
||||
image?: any | null;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof RecipeImage
|
||||
*/
|
||||
image_url?: string | null;
|
||||
}
|
||||
/**
|
||||
*
|
||||
@ -1923,12 +1923,6 @@ export interface RecipeIngredients {
|
||||
* @memberof RecipeIngredients
|
||||
*/
|
||||
original_text?: string | null;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof RecipeIngredients
|
||||
*/
|
||||
used_in_recipes?: string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
@ -2197,7 +2191,7 @@ export interface RecipeSimple {
|
||||
* @type {string}
|
||||
* @memberof RecipeSimple
|
||||
*/
|
||||
name: string;
|
||||
name?: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
@ -5239,10 +5233,11 @@ export const ApiApiAxiosParamCreator = function (configuration?: Configuration)
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this recipe.
|
||||
* @param {any} [image]
|
||||
* @param {string} [imageUrl]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
imageRecipe: async (id: string, image?: any, options: any = {}): Promise<RequestArgs> => {
|
||||
imageRecipe: async (id: string, image?: any, imageUrl?: string, options: any = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'id' is not null or undefined
|
||||
assertParamExists('imageRecipe', 'id', id)
|
||||
const localVarPath = `/api/recipe/{id}/image/`
|
||||
@ -5264,6 +5259,10 @@ export const ApiApiAxiosParamCreator = function (configuration?: Configuration)
|
||||
localVarFormParams.append('image', image as any);
|
||||
}
|
||||
|
||||
if (imageUrl !== undefined) {
|
||||
localVarFormParams.append('image_url', imageUrl as any);
|
||||
}
|
||||
|
||||
|
||||
localVarHeaderParameter['Content-Type'] = 'multipart/form-data';
|
||||
|
||||
@ -10353,11 +10352,12 @@ export const ApiApiFp = function(configuration?: Configuration) {
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this recipe.
|
||||
* @param {any} [image]
|
||||
* @param {string} [imageUrl]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async imageRecipe(id: string, image?: any, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<RecipeImage>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.imageRecipe(id, image, options);
|
||||
async imageRecipe(id: string, image?: any, imageUrl?: string, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<RecipeImage>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.imageRecipe(id, image, imageUrl, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
@ -12186,11 +12186,12 @@ export const ApiApiFactory = function (configuration?: Configuration, basePath?:
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this recipe.
|
||||
* @param {any} [image]
|
||||
* @param {string} [imageUrl]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
imageRecipe(id: string, image?: any, options?: any): AxiosPromise<RecipeImage> {
|
||||
return localVarFp.imageRecipe(id, image, options).then((request) => request(axios, basePath));
|
||||
imageRecipe(id: string, image?: any, imageUrl?: string, options?: any): AxiosPromise<RecipeImage> {
|
||||
return localVarFp.imageRecipe(id, image, imageUrl, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
@ -14004,12 +14005,13 @@ export class ApiApi extends BaseAPI {
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this recipe.
|
||||
* @param {any} [image]
|
||||
* @param {string} [imageUrl]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof ApiApi
|
||||
*/
|
||||
public imageRecipe(id: string, image?: any, options?: any) {
|
||||
return ApiApiFp(this.configuration).imageRecipe(id, image, options).then((request) => request(this.axios, this.basePath));
|
||||
public imageRecipe(id: string, image?: any, imageUrl?: string, options?: any) {
|
||||
return ApiApiFp(this.configuration).imageRecipe(id, image, imageUrl, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -50,37 +50,37 @@ export class StandardToasts {
|
||||
static FAIL_MOVE = "FAIL_MOVE"
|
||||
static FAIL_MERGE = "FAIL_MERGE"
|
||||
|
||||
static makeStandardToast(toast, err_details = undefined) {
|
||||
static makeStandardToast(toast, err_details = undefined) { //TODO err_details render very ugly, improve this maybe by using a custom toast component (in conjunction with error logging maybe)
|
||||
switch (toast) {
|
||||
case StandardToasts.SUCCESS_CREATE:
|
||||
makeToast(i18n.tc("Success"), i18n.tc("success_creating_resource"), "success")
|
||||
makeToast(i18n.tc("Success"), i18n.tc("success_creating_resource") + (err_details ? "\n" + err_details : ""), "success")
|
||||
break
|
||||
case StandardToasts.SUCCESS_FETCH:
|
||||
makeToast(i18n.tc("Success"), i18n.tc("success_fetching_resource"), "success")
|
||||
makeToast(i18n.tc("Success"), i18n.tc("success_fetching_resource") + (err_details ? "\n" + err_details : ""), "success")
|
||||
break
|
||||
case StandardToasts.SUCCESS_UPDATE:
|
||||
makeToast(i18n.tc("Success"), i18n.tc("success_updating_resource"), "success")
|
||||
makeToast(i18n.tc("Success"), i18n.tc("success_updating_resource") + (err_details ? "\n" + err_details : ""), "success")
|
||||
break
|
||||
case StandardToasts.SUCCESS_DELETE:
|
||||
makeToast(i18n.tc("Success"), i18n.tc("success_deleting_resource"), "success")
|
||||
makeToast(i18n.tc("Success"), i18n.tc("success_deleting_resource") + (err_details ? "\n" + err_details : ""), "success")
|
||||
break
|
||||
case StandardToasts.SUCCESS_MOVE:
|
||||
makeToast(i18n.tc("Success"), i18n.tc("success_moving_resource"), "success")
|
||||
makeToast(i18n.tc("Success"), i18n.tc("success_moving_resource") + (err_details ? "\n" + err_details : ""), "success")
|
||||
break
|
||||
case StandardToasts.SUCCESS_MERGE:
|
||||
makeToast(i18n.tc("Success"), i18n.tc("success_merging_resource"), "success")
|
||||
makeToast(i18n.tc("Success"), i18n.tc("success_merging_resource") + (err_details ? "\n" + err_details : ""), "success")
|
||||
break
|
||||
case StandardToasts.FAIL_CREATE:
|
||||
makeToast(i18n.tc("Failure"), i18n.tc("err_creating_resource"), "danger")
|
||||
makeToast(i18n.tc("Failure"), i18n.tc("err_creating_resource") + (err_details ? "\n" + err_details : ""), "danger")
|
||||
break
|
||||
case StandardToasts.FAIL_FETCH:
|
||||
makeToast(i18n.tc("Failure"), i18n.tc("err_fetching_resource"), "danger")
|
||||
makeToast(i18n.tc("Failure"), i18n.tc("err_fetching_resource") + (err_details ? "\n" + err_details : ""), "danger")
|
||||
break
|
||||
case StandardToasts.FAIL_UPDATE:
|
||||
makeToast(i18n.tc("Failure"), i18n.tc("err_updating_resource"), "danger")
|
||||
makeToast(i18n.tc("Failure"), i18n.tc("err_updating_resource") + (err_details ? "\n" + err_details : ""), "danger")
|
||||
break
|
||||
case StandardToasts.FAIL_DELETE:
|
||||
makeToast(i18n.tc("Failure"), i18n.tc("err_deleting_resource"), "danger")
|
||||
makeToast(i18n.tc("Failure"), i18n.tc("err_deleting_resource") + (err_details ? "\n" + err_details : ""), "danger")
|
||||
break
|
||||
case StandardToasts.FAIL_DELETE_PROTECTED:
|
||||
makeToast(i18n.tc("Protected"), i18n.tc("err_deleting_protected_resource"), "danger")
|
||||
@ -149,6 +149,27 @@ export function resolveDjangoUrl(url, params = null) {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Utility functions to use djangos static files
|
||||
* */
|
||||
|
||||
export const StaticMixin = {
|
||||
methods: {
|
||||
/**
|
||||
* access django static files from javascript
|
||||
* @param {string} param path to static file
|
||||
*/
|
||||
resolveDjangoStatic: function (param) {
|
||||
return resolveDjangoStatic(param)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export function resolveDjangoStatic(param) {
|
||||
let url = localStorage.getItem('STATIC_URL') + param
|
||||
return url.replace('//','/') //replace // with / in case param started with / which resulted in // after the static base url
|
||||
}
|
||||
|
||||
/*
|
||||
* other utilities
|
||||
* */
|
||||
|
Reference in New Issue
Block a user