super very basics of manual mapping page working
This commit is contained in:
@ -1084,7 +1084,6 @@ def recipe_from_source(request):
|
|||||||
:param request: standard request with additional post parameters
|
:param request: standard request with additional post parameters
|
||||||
- url: url to use for importing recipe
|
- url: url to use for importing recipe
|
||||||
- data: if no url is given recipe is imported from provided source data
|
- data: if no url is given recipe is imported from provided source data
|
||||||
- auto: true to return just the recipe as json, false to return source json, html and images as well
|
|
||||||
- (optional) bookmarklet: id of bookmarklet import to use, overrides URL and data attributes
|
- (optional) bookmarklet: id of bookmarklet import to use, overrides URL and data attributes
|
||||||
:return: JsonResponse containing the parsed json, original html,json and images
|
:return: JsonResponse containing the parsed json, original html,json and images
|
||||||
"""
|
"""
|
||||||
@ -1092,7 +1091,6 @@ def recipe_from_source(request):
|
|||||||
url = request_payload.get('url', None)
|
url = request_payload.get('url', None)
|
||||||
data = request_payload.get('data', None)
|
data = request_payload.get('data', None)
|
||||||
bookmarklet = request_payload.get('bookmarklet', None)
|
bookmarklet = request_payload.get('bookmarklet', None)
|
||||||
auto = True if request_payload.get('auto', 'true') == 'true' else False
|
|
||||||
|
|
||||||
if bookmarklet := BookmarkletImport.objects.filter(pk=bookmarklet).first():
|
if bookmarklet := BookmarkletImport.objects.filter(pk=bookmarklet).first():
|
||||||
url = bookmarklet.url
|
url = bookmarklet.url
|
||||||
@ -1108,69 +1106,28 @@ def recipe_from_source(request):
|
|||||||
'msg': _('Nothing to do.')
|
'msg': _('Nothing to do.')
|
||||||
}, status=400)
|
}, status=400)
|
||||||
|
|
||||||
# in auto mode scrape url directly with recipe scrapers library
|
# in manual mode request complete page to return it later
|
||||||
if url and auto:
|
if url:
|
||||||
try:
|
try:
|
||||||
scrape = scrape_me(url)
|
data = requests.get(url, headers=external_request_headers).content
|
||||||
except (WebsiteNotImplementedError, AttributeError):
|
except requests.exceptions.ConnectionError:
|
||||||
try:
|
|
||||||
scrape = scrape_me(url, wild_mode=True)
|
|
||||||
except NoSchemaFoundInWildMode:
|
|
||||||
return JsonResponse({
|
|
||||||
'error': True,
|
|
||||||
'msg': _('The requested site provided malformed data and cannot be read.')
|
|
||||||
}, status=400)
|
|
||||||
except ConnectionError:
|
|
||||||
return JsonResponse({
|
return JsonResponse({
|
||||||
'error': True,
|
'error': True,
|
||||||
'msg': _('The requested page could not be found.')
|
'msg': _('Connection Refused.')
|
||||||
}, status=400)
|
}, status=400)
|
||||||
|
recipe_json, recipe_tree, recipe_html, recipe_images = get_recipe_from_source(data, url, request)
|
||||||
try:
|
if len(recipe_tree) == 0 and len(recipe_json) == 0:
|
||||||
instructions = scrape.instructions()
|
|
||||||
except Exception:
|
|
||||||
instructions = ""
|
|
||||||
try:
|
|
||||||
ingredients = scrape.ingredients()
|
|
||||||
except Exception:
|
|
||||||
ingredients = []
|
|
||||||
|
|
||||||
if len(ingredients) + len(instructions) == 0:
|
|
||||||
return JsonResponse({
|
|
||||||
'error': True,
|
|
||||||
'msg': _('The requested site does not provide any recognized data format to import the recipe from.')
|
|
||||||
}, status=400)
|
|
||||||
else:
|
|
||||||
return JsonResponse({"recipe_json": get_from_scraper(scrape, request)})
|
|
||||||
elif data or (url and not auto):
|
|
||||||
# in manual mode request complete page to return it later
|
|
||||||
if not data or data == 'undefined':
|
|
||||||
try:
|
|
||||||
data = requests.get(url, headers=external_request_headers).content
|
|
||||||
except requests.exceptions.ConnectionError:
|
|
||||||
return JsonResponse({
|
|
||||||
'error': True,
|
|
||||||
'msg': _('Connection Refused.')
|
|
||||||
}, status=400)
|
|
||||||
recipe_json, recipe_tree, recipe_html, recipe_images = get_recipe_from_source(data, url, request)
|
|
||||||
if len(recipe_tree) == 0 and len(recipe_json) == 0:
|
|
||||||
return JsonResponse({
|
|
||||||
'error': True,
|
|
||||||
'msg': _('No usable data could be found.')
|
|
||||||
}, status=400)
|
|
||||||
else:
|
|
||||||
return JsonResponse({
|
|
||||||
'recipe_json': recipe_json,
|
|
||||||
'recipe_tree': recipe_tree,
|
|
||||||
'recipe_html': recipe_html,
|
|
||||||
'recipe_images': recipe_images,
|
|
||||||
})
|
|
||||||
|
|
||||||
else:
|
|
||||||
return JsonResponse({
|
return JsonResponse({
|
||||||
'error': True,
|
'error': True,
|
||||||
'msg': _('I couldn\'t find anything to do.')
|
'msg': _('No usable data could be found.')
|
||||||
}, status=400)
|
}, status=400)
|
||||||
|
else:
|
||||||
|
return JsonResponse({
|
||||||
|
'recipe_json': recipe_json,
|
||||||
|
'recipe_tree': recipe_tree,
|
||||||
|
'recipe_html': recipe_html,
|
||||||
|
'recipe_images': recipe_images,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
@group_required('admin')
|
@group_required('admin')
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
"vue-cookies": "^1.7.4",
|
"vue-cookies": "^1.7.4",
|
||||||
"vue-i18n": "^8.26.8",
|
"vue-i18n": "^8.26.8",
|
||||||
"vue-infinite-loading": "^2.4.5",
|
"vue-infinite-loading": "^2.4.5",
|
||||||
|
"vue-jstree": "^2.1.6",
|
||||||
"vue-multiselect": "^2.1.6",
|
"vue-multiselect": "^2.1.6",
|
||||||
"vue-property-decorator": "^9.1.2",
|
"vue-property-decorator": "^9.1.2",
|
||||||
"vue-simple-calendar": "^5.0.1",
|
"vue-simple-calendar": "^5.0.1",
|
||||||
|
@ -121,8 +121,10 @@
|
|||||||
</b-card-header>
|
</b-card-header>
|
||||||
<b-collapse id="id_accordion_advanced_options" visible accordion="url_import_accordion"
|
<b-collapse id="id_accordion_advanced_options" visible accordion="url_import_accordion"
|
||||||
role="tabpanel" v-model="collapse_visible.advanced_options">
|
role="tabpanel" v-model="collapse_visible.advanced_options">
|
||||||
<b-card-body>
|
<b-card-body v-if="recipe_json !== undefined">
|
||||||
|
|
||||||
|
<import-view-advanced-mapping :recipe="recipe_json" :recipe_tree="recipe_tree" :recipe_images="recipe_images" :recipe_html="recipe_html"
|
||||||
|
@change="recipe_json = $event"></import-view-advanced-mapping>
|
||||||
|
|
||||||
</b-card-body>
|
</b-card-body>
|
||||||
</b-collapse>
|
</b-collapse>
|
||||||
@ -224,6 +226,7 @@ import {ApiApiFactory} from "@/utils/openapi/api";
|
|||||||
import draggable from "vuedraggable";
|
import draggable from "vuedraggable";
|
||||||
import {INTEGRATIONS} from "@/utils/integration";
|
import {INTEGRATIONS} from "@/utils/integration";
|
||||||
import ImportViewStepEditor from "@/apps/ImportView/ImportViewStepEditor";
|
import ImportViewStepEditor from "@/apps/ImportView/ImportViewStepEditor";
|
||||||
|
import ImportViewAdvancedMapping from "@/apps/ImportView/ImportViewAdvancedMapping";
|
||||||
|
|
||||||
Vue.use(BootstrapVue)
|
Vue.use(BootstrapVue)
|
||||||
|
|
||||||
@ -234,8 +237,9 @@ export default {
|
|||||||
ToastMixin,
|
ToastMixin,
|
||||||
],
|
],
|
||||||
components: {
|
components: {
|
||||||
|
ImportViewAdvancedMapping,
|
||||||
ImportViewStepEditor,
|
ImportViewStepEditor,
|
||||||
draggable
|
draggable,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@ -252,7 +256,7 @@ export default {
|
|||||||
recent_urls: [],
|
recent_urls: [],
|
||||||
source_data: '',
|
source_data: '',
|
||||||
recipe_json: undefined,
|
recipe_json: undefined,
|
||||||
recipe_data: undefined,
|
recipe_html: undefined,
|
||||||
recipe_tree: undefined,
|
recipe_tree: undefined,
|
||||||
recipe_images: [],
|
recipe_images: [],
|
||||||
// App Import
|
// App Import
|
||||||
@ -269,7 +273,7 @@ export default {
|
|||||||
this.recent_urls = local_storage_recent !== null ? local_storage_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
|
this.tab_index = 0 //TODO add ability to pass open tab via get parameter
|
||||||
|
|
||||||
if (window.BOOKMARKLET_IMPORT_ID !== -1){
|
if (window.BOOKMARKLET_IMPORT_ID !== -1) {
|
||||||
this.loadRecipe(window.BOOKMARKLET_IMPORT_ID)
|
this.loadRecipe(window.BOOKMARKLET_IMPORT_ID)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -332,7 +336,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// reset all variables
|
// reset all variables
|
||||||
this.recipe_data = undefined
|
this.recipe_html = undefined
|
||||||
this.recipe_json = undefined
|
this.recipe_json = undefined
|
||||||
this.recipe_tree = undefined
|
this.recipe_tree = undefined
|
||||||
this.recipe_images = []
|
this.recipe_images = []
|
||||||
@ -341,11 +345,9 @@ export default {
|
|||||||
let payload = {
|
let payload = {
|
||||||
'url': this.website_url,
|
'url': this.website_url,
|
||||||
'data': this.source_data,
|
'data': this.source_data,
|
||||||
'auto': this.automatic,
|
|
||||||
'mode': this.mode
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bookmarklet !== undefined){
|
if (bookmarklet !== undefined) {
|
||||||
payload['bookmarklet'] = bookmarklet
|
payload['bookmarklet'] = bookmarklet
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -365,38 +367,6 @@ export default {
|
|||||||
StandardToasts.makeStandardToast(StandardToasts.FAIL_FETCH, err.response.data.msg)
|
StandardToasts.makeStandardToast(StandardToasts.FAIL_FETCH, err.response.data.msg)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
*/
|
|
||||||
loadBookmarkletRecipe: function () {
|
|
||||||
|
|
||||||
this.recipe_data = undefined
|
|
||||||
this.recipe_json = undefined
|
|
||||||
this.recipe_tree = undefined
|
|
||||||
this.recipe_images = []
|
|
||||||
|
|
||||||
axios.post(resolveDjangoUrl('api_recipe_from_source'), {
|
|
||||||
'url': this.website_url,
|
|
||||||
'data': this.source_data,
|
|
||||||
'auto': this.automatic,
|
|
||||||
'mode': this.mode
|
|
||||||
},).then((response) => {
|
|
||||||
this.recipe_json = response.data['recipe_json'];
|
|
||||||
|
|
||||||
this.$set(this.recipe_json, 'unused_keywords', this.recipe_json.keywords.filter(k => k.id === undefined))
|
|
||||||
this.$set(this.recipe_json, 'keywords', this.recipe_json.keywords.filter(k => k.id !== undefined))
|
|
||||||
|
|
||||||
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'] : [];
|
|
||||||
|
|
||||||
this.tab_index = 0 // open tab 0 with import wizard
|
|
||||||
this.collapse_visible.options = true // open options collapse
|
|
||||||
}).catch((err) => {
|
|
||||||
StandardToasts.makeStandardToast(StandardToasts.FAIL_FETCH, err.response.data.msg)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
/**
|
/**
|
||||||
* Import recipes with uploaded files and app integration
|
* Import recipes with uploaded files and app integration
|
||||||
*/
|
*/
|
||||||
@ -421,6 +391,10 @@ export default {
|
|||||||
window.localStorage.setItem(this.LS_IMPORT_RECENT, JSON.stringify([]))
|
window.localStorage.setItem(this.LS_IMPORT_RECENT, JSON.stringify([]))
|
||||||
this.recent_urls = []
|
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 () {
|
makeBookmarklet: function () {
|
||||||
return 'javascript:(function(){' +
|
return 'javascript:(function(){' +
|
||||||
'if(window.bookmarkletTandoor!==undefined){' +
|
'if(window.bookmarkletTandoor!==undefined){' +
|
||||||
|
408
vue/src/apps/ImportView/ImportViewAdvancedMapping.vue
Normal file
408
vue/src/apps/ImportView/ImportViewAdvancedMapping.vue
Normal file
@ -0,0 +1,408 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<!-- recipe preview before Import -->
|
||||||
|
<div class="container-fluid" v-if="recipe_json" id="manage_tree">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col" style="max-width:50%">
|
||||||
|
<!-- start of preview card -->
|
||||||
|
<div class="card card-border-primary">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3>Preview Recipe Data</h3>
|
||||||
|
<div class='small text-muted'>Drag recipe attributes from the right into the
|
||||||
|
appropriate box below.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-2">
|
||||||
|
|
||||||
|
<div class="card mb-2">
|
||||||
|
<div class="card-header" v-b-toggle.collapse-name>
|
||||||
|
<div class="row px-3" style="justify-content:space-between;">
|
||||||
|
Name
|
||||||
|
<i class="fas fa-eraser" style="cursor:pointer;" @click="recipe_json.name=''"
|
||||||
|
title="Clear Contents"></i>
|
||||||
|
</div>
|
||||||
|
<div class="small text-muted">Text dragged here will be appended to the
|
||||||
|
name.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<b-collapse id="collapse-name" visible class="mt-2">
|
||||||
|
<div class="card-body drop-zone" @drop="replacePreview('name', $event)"
|
||||||
|
@dragover.prevent @dragenter.prevent>
|
||||||
|
<div class="card-text">{{ recipe_json.name }}</div>
|
||||||
|
</div>
|
||||||
|
</b-collapse>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card mb-2">
|
||||||
|
<div class="card-header" v-b-toggle.collapse-description>
|
||||||
|
<div class="row px-3" style="justify-content:space-between;">
|
||||||
|
Description
|
||||||
|
<i class="fas fa-eraser" style="cursor:pointer;"
|
||||||
|
@click="recipe_json.description=''" title="Clear Contents"></i>
|
||||||
|
</div>
|
||||||
|
<div class="small text-muted">Text dragged here will be appended to the
|
||||||
|
description.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<b-collapse id="collapse-description" visible class="mt-2">
|
||||||
|
<div class="card-body drop-zone" @drop="replacePreview('description', $event)"
|
||||||
|
@dragover.prevent @dragenter.prevent>
|
||||||
|
<div class="card-text">{{ recipe_json.description }}</div>
|
||||||
|
</div>
|
||||||
|
</b-collapse>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card mb-2">
|
||||||
|
<div class="card-header" v-b-toggle.collapse-kw>
|
||||||
|
<div class="row px-3" style="justify-content:space-between;">
|
||||||
|
Keywords
|
||||||
|
<i class="fas fa-eraser" style="cursor:pointer;"
|
||||||
|
@click="recipe_json.keywords=[]" title="Clear Contents"></i>
|
||||||
|
</div>
|
||||||
|
<div class="small text-muted">Keywords dragged here will be appended to
|
||||||
|
current list
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<b-collapse id="collapse-kw" visible class="mt-2">
|
||||||
|
<div class="card-body drop-zone" @drop="replacePreview('keywords', $event)"
|
||||||
|
@dragover.prevent @dragenter.prevent>
|
||||||
|
<div v-for="kw in recipe_json.keywords" v-bind:key="kw.name">
|
||||||
|
<div class="card-text">{{ kw.text }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</b-collapse>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card mb-2">
|
||||||
|
<div class="card-header" v-b-toggle.collapse-image
|
||||||
|
style="display:flex; justify-content:space-between;">
|
||||||
|
Image
|
||||||
|
<i class="fas fa-eraser" style="cursor:pointer;" @click="recipe_json.image=''"
|
||||||
|
title="Clear Contents"></i>
|
||||||
|
</div>
|
||||||
|
<b-collapse id="collapse-image" visible class="mt-2">
|
||||||
|
<div class="card-body m-0 p-0 drop-zone" @drop="replacePreview('image', $event)"
|
||||||
|
@dragover.prevent @dragenter.prevent>
|
||||||
|
<img class="card-img" v-bind:src="recipe_json.image" alt="Recipe Image">
|
||||||
|
</div>
|
||||||
|
</b-collapse>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header p-1"
|
||||||
|
style="display:flex; justify-content:space-between;">
|
||||||
|
Servings
|
||||||
|
<i class="fas fa-eraser" style="cursor:pointer;"
|
||||||
|
@click="recipe_json.servings=''"
|
||||||
|
title="Clear Contents"></i>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-2 drop-zone" @drop="replacePreview('servings', $event)"
|
||||||
|
@dragover.prevent @dragenter.prevent>
|
||||||
|
<div class="card-text">{{ recipe_json.servings }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header p-1"
|
||||||
|
style="display:flex; justify-content:space-between;">
|
||||||
|
Prep Time
|
||||||
|
<i class="fas fa-eraser" style="cursor:pointer;"
|
||||||
|
@click="recipe_json.working_time=''"
|
||||||
|
title="Clear Contents"></i>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-2 drop-zone" @drop="replacePreview('prepTime', $event)"
|
||||||
|
@dragover.prevent @dragenter.prevent>
|
||||||
|
<div class="card-text">{{ recipe_json.working_time }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header p-1"
|
||||||
|
style="display:flex; justify-content:space-between;">
|
||||||
|
Cook Time
|
||||||
|
<i class="fas fa-eraser" style="cursor:pointer;"
|
||||||
|
@click="recipe_json.waiting_time=''"
|
||||||
|
title="Clear Contents"></i>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-2 drop-zone" @drop="replacePreview('cookTime', $event)"
|
||||||
|
@dragover.prevent @dragenter.prevent>
|
||||||
|
<div class="card-text">{{ recipe_json.waiting_time }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card mb-2">
|
||||||
|
<div class="card-header" v-b-toggle.collapse-ing>
|
||||||
|
<div class="row px-3" style="display:flex; justify-content:space-between;">
|
||||||
|
Ingredients
|
||||||
|
<i class="fas fa-eraser" style="cursor:pointer;"
|
||||||
|
@click="recipe_json.recipeIngredient=[]"
|
||||||
|
title="Clear Contents"></i>
|
||||||
|
</div>
|
||||||
|
<div class="small text-muted">Ingredients dragged here will be appended to
|
||||||
|
current list.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<b-collapse id="collapse-ing" visible class="mt-2">
|
||||||
|
<div class="card-body drop-zone" @drop="replacePreview('ingredients', $event)"
|
||||||
|
@dragover.prevent @dragenter.prevent>
|
||||||
|
<ul class="list-group list-group">
|
||||||
|
<div v-for="i in recipe_json.recipeIngredient" v-bind:key="i.note">
|
||||||
|
<li class="row border-light">
|
||||||
|
<div class="col-sm-1 border">{{ i.amount }}</div>
|
||||||
|
<div class="col-sm border">{{ i.unit.text }}</div>
|
||||||
|
<div class="col-sm border">{{ i.ingredient.text }}</div>
|
||||||
|
<div class="col-sm border">{{ i.note }}</div>
|
||||||
|
</li>
|
||||||
|
</div>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</b-collapse>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card mb-2">
|
||||||
|
<div class="card-header" v-b-toggle.collapse-instructions>
|
||||||
|
<div class="row px-3" style="justify-content:space-between;">
|
||||||
|
Instructions
|
||||||
|
<i class="fas fa-eraser" style="cursor:pointer;"
|
||||||
|
@click="recipe_json.recipeInstructions=''"
|
||||||
|
title="Clear Contents"></i>
|
||||||
|
</div>
|
||||||
|
<div class="small text-muted">Recipe instructions dragged here will be
|
||||||
|
appended to current instructions.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<b-collapse id="collapse-instructions" visible class="mt-2">
|
||||||
|
<div class="card-body drop-zone" @drop="replacePreview('instructions', $event)"
|
||||||
|
@dragover.prevent @dragenter.prevent>
|
||||||
|
<div class="card-text">{{ recipe_json.recipeInstructions }}</div>
|
||||||
|
</div>
|
||||||
|
</b-collapse>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
<!-- end of preview card -->
|
||||||
|
<button class="btn btn-primary shadow-none" type="button"
|
||||||
|
style="margin-bottom: 2vh"
|
||||||
|
id="id_btn_json"><i class="fas fa-code"></i> Import
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- start of source data -->
|
||||||
|
<div class="col" style="max-width:50%;">
|
||||||
|
<div class="card card-border-primary sticky-top" style="z-index: 100;">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3>Discovered Attributes</h3>
|
||||||
|
<div class='small text-muted'>
|
||||||
|
Drag recipe attributes from below into the appropriate box on the left. Click
|
||||||
|
any node to display its full properties.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="btn-group btn-group-toggle" data-toggle="buttons">
|
||||||
|
<label class="btn btn-outline-info btn-sm active" @click="preview_type='json'">
|
||||||
|
<input type="radio" autocomplete="off" checked> json
|
||||||
|
</label>
|
||||||
|
<label class="btn btn-outline-info btn-sm" @click="preview_type='html'">
|
||||||
|
<input type="radio" autocomplete="off"> html
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<i :class="[show_blank ? 'fa-chevron-up' : 'fa-chevron-down', 'fas']"
|
||||||
|
style="cursor:pointer;"
|
||||||
|
@click="show_blank=!show_blank"
|
||||||
|
title="Show Blank Field"></i>
|
||||||
|
<div class="card-body p-1">
|
||||||
|
<div class="card card-border-primary" v-if="show_blank">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="row px-3" style="justify-content:space-between;">
|
||||||
|
Blank Field
|
||||||
|
<i class="fas fa-eraser justify-content-end" style="cursor:pointer;"
|
||||||
|
@click="blank_field=''" title="Clear Contents"></i>
|
||||||
|
</div>
|
||||||
|
<div class="small text-muted">Items dragged to Blank Field will be
|
||||||
|
appended.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body drop-zone"
|
||||||
|
@drop="replacePreview('blank', $event)"
|
||||||
|
@dragover.prevent
|
||||||
|
@dragenter.prevent
|
||||||
|
draggable
|
||||||
|
@dragstart="htmlDragStart($event)">
|
||||||
|
{{ blank_field }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- start of json data -->
|
||||||
|
|
||||||
|
<!-- eslint-disable vue/no-deprecated-scope-attribute -->
|
||||||
|
<v-jstree v-if="preview_type=='json'" :data="recipe_tree"
|
||||||
|
text-field-name="name"
|
||||||
|
collapse:true
|
||||||
|
draggable
|
||||||
|
@item-drag-start="itemDragStart"
|
||||||
|
@item-click="itemClick">
|
||||||
|
<template scope="_">
|
||||||
|
<div class="col" @click.ctrl="customItemClickWithCtrl">
|
||||||
|
<div class="row clearfix" style="width:100%">
|
||||||
|
<div class="col-es" style="align-right">
|
||||||
|
<button
|
||||||
|
style="border: 0px; background-color: transparent; cursor: pointer;"
|
||||||
|
@click="deleteNode(_.vm, _.model, $event)"><i
|
||||||
|
class="fas fa-minus-square" style="color:red"></i></button>
|
||||||
|
</div>
|
||||||
|
<div class="col overflow-hidden">
|
||||||
|
<i :class="_.vm.themeIconClasses" role="presentation"
|
||||||
|
v-if="!_.model.loading"></i>
|
||||||
|
{{ _.model.name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
</v-jstree>
|
||||||
|
<!-- eslint-disable vue/no-deprecated-scope-attribute -->
|
||||||
|
|
||||||
|
<!-- start of html data -->
|
||||||
|
<div v-if="preview_type=='html'">
|
||||||
|
|
||||||
|
<ul class="list-group list-group-flush" v-for="(txt, key) in recipe_html"
|
||||||
|
v-bind:key="key">
|
||||||
|
<div class="list-group-item bg-light m-0 small"
|
||||||
|
draggable
|
||||||
|
@dragstart="htmlDragStart($event)"
|
||||||
|
style="display:flex; justify-content:space-between;">
|
||||||
|
{{ txt }}
|
||||||
|
<i class="fas fa-minus-square" style="cursor:pointer; color:red"
|
||||||
|
@click="$delete(recipe_html, key)" title="Delete Text"></i>
|
||||||
|
</div>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- end of json tree -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- end of recipe preview before Import -->
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {StandardToasts} from "@/utils/utils";
|
||||||
|
import VJstree from 'vue-jstree'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ImportViewAdvancedMapping",
|
||||||
|
components: {
|
||||||
|
VJstree,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
recipe: undefined,
|
||||||
|
recipe_html: undefined,
|
||||||
|
recipe_tree: undefined,
|
||||||
|
recipe_images: undefined,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
recipe_json: undefined,
|
||||||
|
preview_type: 'json',
|
||||||
|
show_blank: false,
|
||||||
|
blank_field: '',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
recipe_json: function () {
|
||||||
|
this.$emit('change', this.recipe_json)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.recipe_json = this.recipe //TODO check if changed not only if mounted, same for step editor
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
deleteNode: function (node, item, e) {
|
||||||
|
e.stopPropagation()
|
||||||
|
var index = node.parentItem.indexOf(item)
|
||||||
|
node.parentItem.splice(index, 1)
|
||||||
|
},
|
||||||
|
itemClick: function (node, item, e) {
|
||||||
|
this.makeToast('Details', node.model.value, 'info')
|
||||||
|
},
|
||||||
|
itemDragStart(node, item, e) {
|
||||||
|
if (node.model.children.length > 0) {
|
||||||
|
e.dataTransfer.setData('hasChildren', true)
|
||||||
|
}
|
||||||
|
e.dataTransfer.setData('value', node.model.value)
|
||||||
|
|
||||||
|
},
|
||||||
|
htmlDragStart: function (e) {
|
||||||
|
e.dataTransfer.setData('value', e.target.innerText)
|
||||||
|
},
|
||||||
|
imageDragStart: function (e) {
|
||||||
|
e.dataTransfer.setData('value', e.target.src)
|
||||||
|
},
|
||||||
|
replacePreview: function (field, e) {
|
||||||
|
let v = e.dataTransfer.getData('value')
|
||||||
|
if (e.dataTransfer.getData('hasChildren')) {
|
||||||
|
this.makeToast('Error', 'Items with children cannot be dropped here!', 'danger')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch (field) {
|
||||||
|
case 'name':
|
||||||
|
this.recipe_json.name = [this.recipe_json.name, v].filter(Boolean).join(" ");
|
||||||
|
break;
|
||||||
|
case 'description':
|
||||||
|
this.recipe_json.description = [this.recipe_json.description, v].filter(Boolean).join(" ");
|
||||||
|
break;
|
||||||
|
case 'image':
|
||||||
|
this.recipe_json.image = v
|
||||||
|
break;
|
||||||
|
case 'keywords':
|
||||||
|
this.recipe_json.keywords.push({'text': v, 'id': null})
|
||||||
|
break;
|
||||||
|
case 'servings':
|
||||||
|
this.recipe_json.servings = parseInt(v.match(/\b\d+\b/)[0])
|
||||||
|
break;
|
||||||
|
case 'prepTime':
|
||||||
|
this.recipe_json.prepTime = v
|
||||||
|
break;
|
||||||
|
case 'cookTime':
|
||||||
|
this.recipe_json.cookTime = v
|
||||||
|
break;
|
||||||
|
case 'ingredients':
|
||||||
|
this.$http.post('string_from_ingredients', {text: v}, {emulateJSON: true}
|
||||||
|
).then((response) => {
|
||||||
|
let new_ingredient = {
|
||||||
|
unit: {id: Math.random() * 1000, text: response.body.unit},
|
||||||
|
amount: String(response.body.amount),
|
||||||
|
ingredient: {id: Math.random() * 1000, text: response.body.food},
|
||||||
|
note: response.body.note,
|
||||||
|
original_text: v
|
||||||
|
}
|
||||||
|
this.recipe_json.recipeIngredient.push(new_ingredient)
|
||||||
|
}).catch((err) => {
|
||||||
|
console.log(err)
|
||||||
|
StandardToasts.makeStandardToast(StandardToasts.FAIL_CREATE)
|
||||||
|
})
|
||||||
|
break;
|
||||||
|
case 'instructions':
|
||||||
|
this.recipe_json.recipeInstructions = [this.recipe_json.recipeInstructions, v].filter(Boolean).join("\n\n");
|
||||||
|
break;
|
||||||
|
case 'blank':
|
||||||
|
this.blank_field = [this.blank_field, v].filter(Boolean).join(" ");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
@ -10612,6 +10612,11 @@ vue-infinite-loading@^2.4.5:
|
|||||||
resolved "https://registry.yarnpkg.com/vue-infinite-loading/-/vue-infinite-loading-2.4.5.tgz#cc20fd40af7f20188006443c99b60470cf1de1b3"
|
resolved "https://registry.yarnpkg.com/vue-infinite-loading/-/vue-infinite-loading-2.4.5.tgz#cc20fd40af7f20188006443c99b60470cf1de1b3"
|
||||||
integrity sha512-xhq95Mxun060bRnsOoLE2Be6BR7jYwuC89kDe18+GmCLVrRA/dU0jrGb12Xu6NjmKs+iTW0AA6saSEmEW4cR7g==
|
integrity sha512-xhq95Mxun060bRnsOoLE2Be6BR7jYwuC89kDe18+GmCLVrRA/dU0jrGb12Xu6NjmKs+iTW0AA6saSEmEW4cR7g==
|
||||||
|
|
||||||
|
vue-jstree@^2.1.6:
|
||||||
|
version "2.1.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/vue-jstree/-/vue-jstree-2.1.6.tgz#44827ad72953ed77da6590ce4e8f37f7787f8653"
|
||||||
|
integrity sha512-vtUmhLbfE2JvcnYNRXauJPkNJSRO/f9BTsbxV+ESXP/mMQPVUIYI4EkSHKSEOxVDHTU7SfLp/AxplmaAl6ctcg==
|
||||||
|
|
||||||
"vue-loader-v16@npm:vue-loader@^16.1.0":
|
"vue-loader-v16@npm:vue-loader@^16.1.0":
|
||||||
version "16.8.3"
|
version "16.8.3"
|
||||||
resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-16.8.3.tgz#d43e675def5ba9345d6c7f05914c13d861997087"
|
resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-16.8.3.tgz#d43e675def5ba9345d6c7f05914c13d861997087"
|
||||||
|
Reference in New Issue
Block a user