Merge branch 'develop'

# Conflicts:
#	docs/faq.md
This commit is contained in:
vabene1111 2023-12-03 14:10:28 +01:00
commit abf8f79136
38 changed files with 4834 additions and 214 deletions

View File

@ -28,7 +28,7 @@ SECRET_KEY_FILE=
# ---------------------------------------------------------------
# your default timezone See https://timezonedb.com/time-zones for a list of timezones
TIMEZONE=Europe/Berlin
TZ=Europe/Berlin
# add only a database password if you want to run with the default postgres, otherwise change settings accordingly
DB_ENGINE=django.db.backends.postgresql
@ -183,3 +183,5 @@ REMOTE_USER_AUTH=0
# Recipe exports are cached for a certain time by default, adjust time if needed
# EXPORT_FILE_CACHE_DURATION=600
# if you want to do many requests to the FDC API you need to get a (free) API key. Demo key is limited to 30 requests / hour or 50 requests / day
#FDC_API_KEY=DEMO_KEY

View File

@ -349,7 +349,9 @@ admin.site.register(ShareLink, ShareLinkAdmin)
class PropertyTypeAdmin(admin.ModelAdmin):
list_display = ('id', 'name')
search_fields = ('space',)
list_display = ('id', 'space', 'name', 'fdc_id')
admin.site.register(PropertyType, PropertyTypeAdmin)

View File

@ -0,0 +1,19 @@
import json
def get_all_nutrient_types():
f = open('') # <--- download the foundation food or any other dataset and retrieve all nutrition ID's from it https://fdc.nal.usda.gov/download-datasets.html
json_data = json.loads(f.read())
nutrients = {}
for food in json_data['FoundationFoods']:
for entry in food['foodNutrients']:
nutrients[entry['nutrient']['id']] = {'name': entry['nutrient']['name'], 'unit': entry['nutrient']['unitName']}
nutrient_ids = list(nutrients.keys())
nutrient_ids.sort()
for nid in nutrient_ids:
print('{', f'value: {nid}, text: "{nutrients[nid]["name"]} [{nutrients[nid]["unit"]}] ({nid})"', '},')
get_all_nutrient_types()

View File

@ -163,10 +163,9 @@ def get_from_scraper(scrape, request):
if len(recipe_json['steps']) == 0:
recipe_json['steps'].append({'instruction': '', 'ingredients': [], })
recipe_json['description'] = recipe_json['description'][:512]
if len(recipe_json['description']) > 256: # split at 256 as long descriptions don't look good on recipe cards
recipe_json['steps'][0]['instruction'] = f"*{recipe_json['description']}* \n\n" + recipe_json['steps'][0]['instruction']
else:
recipe_json['description'] = recipe_json['description'][:512]
try:
for x in scrape.ingredients():
@ -259,12 +258,13 @@ def get_from_youtube_scraper(url, request):
]
}
# TODO add automation here
try:
automation_engine = AutomationEngine(request, source=url)
video = YouTube(url=url)
video = YouTube(url)
video.streams.first() # this is required to execute some kind of generator/web request that fetches the description
default_recipe_json['name'] = automation_engine.apply_regex_replace_automation(video.title, Automation.NAME_REPLACE)
default_recipe_json['image'] = video.thumbnail_url
if video.description:
default_recipe_json['steps'][0]['instruction'] = automation_engine.apply_regex_replace_automation(video.description, Automation.INSTRUCTION_REPLACE)
except Exception:

View File

@ -0,0 +1,23 @@
# Generated by Django 4.2.7 on 2023-11-29 19:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0204_propertytype_fdc_id'),
]
operations = [
migrations.AlterField(
model_name='food',
name='fdc_id',
field=models.IntegerField(blank=True, default=None, null=True),
),
migrations.AlterField(
model_name='propertytype',
name='fdc_id',
field=models.IntegerField(blank=True, default=None, null=True),
),
]

View File

@ -591,7 +591,7 @@ class Food(ExportModelOperationsMixin('food'), TreeModel, PermissionModelMixin):
preferred_unit = models.ForeignKey(Unit, on_delete=models.SET_NULL, null=True, blank=True, default=None, related_name='preferred_unit')
preferred_shopping_unit = models.ForeignKey(Unit, on_delete=models.SET_NULL, null=True, blank=True, default=None, related_name='preferred_shopping_unit')
fdc_id = models.CharField(max_length=128, null=True, blank=True, default=None)
fdc_id = models.IntegerField(null=True, default=None, blank=True)
open_data_slug = models.CharField(max_length=128, null=True, blank=True, default=None)
space = models.ForeignKey(Space, on_delete=models.CASCADE)
@ -767,7 +767,7 @@ class PropertyType(models.Model, PermissionModelMixin):
(PRICE, _('Price')), (GOAL, _('Goal')), (OTHER, _('Other'))), null=True, blank=True)
open_data_slug = models.CharField(max_length=128, null=True, blank=True, default=None)
fdc_id = models.CharField(max_length=128, null=True, blank=True, default=None)
fdc_id = models.IntegerField(null=True, default=None, blank=True)
# TODO show if empty property?
# TODO formatting property?
@ -809,7 +809,7 @@ class FoodProperty(models.Model):
class Meta:
constraints = [
models.UniqueConstraint(fields=['food', 'property'], name='property_unique_food')
models.UniqueConstraint(fields=['food', 'property'], name='property_unique_food'),
]

View File

@ -19,6 +19,7 @@ from oauth2_provider.models import AccessToken
from PIL import Image
from rest_framework import serializers
from rest_framework.exceptions import NotFound, ValidationError
from rest_framework.fields import IntegerField
from cookbook.helper.CustomStorageClass import CachedS3Boto3Storage
from cookbook.helper.HelperFunctions import str2bool
@ -524,6 +525,7 @@ class SupermarketSerializer(UniqueFieldsMixin, SpacedModelSerializer, OpenDataMo
class PropertyTypeSerializer(OpenDataModelMixin, WritableNestedModelSerializer, UniqueFieldsMixin):
id = serializers.IntegerField(required=False)
order = IntegerField(default=0, required=False)
def create(self, validated_data):
validated_data['name'] = validated_data['name'].strip()
@ -985,6 +987,8 @@ class MealPlanSerializer(SpacedModelSerializer, WritableNestedModelSerializer):
shared = UserSerializer(many=True, required=False, allow_null=True)
shopping = serializers.SerializerMethodField('in_shopping')
to_date = serializers.DateField(required=False)
def get_note_markdown(self, obj):
return markdown(obj.note)
@ -993,6 +997,10 @@ class MealPlanSerializer(SpacedModelSerializer, WritableNestedModelSerializer):
def create(self, validated_data):
validated_data['created_by'] = self.context['request'].user
if 'to_date' not in validated_data or validated_data['to_date'] is None:
validated_data['to_date'] = validated_data['from_date']
mealplan = super().create(validated_data)
if self.context['request'].data.get('addshopping', False) and self.context['request'].data.get('recipe', None):
SLR = RecipeShoppingEditor(user=validated_data['created_by'], space=validated_data['space'])

View File

@ -0,0 +1,31 @@
{% extends "base.html" %}
{% load render_bundle from webpack_loader %}
{% load static %}
{% load i18n %}
{% load l10n %}
{% block title %}{% trans 'Property Editor' %}{% endblock %}
{% block content_fluid %}
<div id="app">
<property-editor-view></property-editor-view>
</div>
{% endblock %}
{% block script %}
{% if debug %}
<script src="{% url 'js_reverse' %}"></script>
{% else %}
<script src="{% static 'django_js_reverse/reverse.js' %}"></script>
{% endif %}
<script type="application/javascript">
window.RECIPE_ID = {{ recipe_id }}
</script>
{% render_bundle 'property_editor_view' %}
{% endblock %}

View File

@ -43,7 +43,7 @@ router.register(r'recipe', api.RecipeViewSet)
router.register(r'recipe-book', api.RecipeBookViewSet)
router.register(r'recipe-book-entry', api.RecipeBookEntryViewSet)
router.register(r'unit-conversion', api.UnitConversionViewSet)
router.register(r'food-property-type', api.PropertyTypeViewSet)
router.register(r'food-property-type', api.PropertyTypeViewSet) # TODO rename + regenerate
router.register(r'food-property', api.PropertyViewSet)
router.register(r'shopping-list', api.ShoppingListViewSet)
router.register(r'shopping-list-entry', api.ShoppingListEntryViewSet)
@ -91,6 +91,7 @@ urlpatterns = [
path('history/', views.history, name='view_history'),
path('supermarket/', views.supermarket, name='view_supermarket'),
path('ingredient-editor/', views.ingredient_editor, name='view_ingredient_editor'),
path('property-editor/<int:pk>', views.property_editor, name='view_property_editor'),
path('abuse/<slug:token>', views.report_share_abuse, name='view_report_share_abuse'),
path('api/import/', api.import_files, name='view_import'),

View File

@ -26,7 +26,7 @@ from django.db.models import Case, Count, Exists, OuterRef, ProtectedError, Q, S
from django.db.models.fields.related import ForeignObjectRel
from django.db.models.functions import Coalesce, Lower
from django.db.models.signals import post_save
from django.http import FileResponse, HttpResponse, JsonResponse
from django.http import FileResponse, HttpResponse, JsonResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse
from django.utils import timezone
@ -75,7 +75,7 @@ from cookbook.models import (Automation, BookmarkletImport, CookLog, CustomFilte
ShareLink, ShoppingList, ShoppingListEntry, ShoppingListRecipe, Space,
Step, Storage, Supermarket, SupermarketCategory,
SupermarketCategoryRelation, Sync, SyncLog, Unit, UnitConversion,
UserFile, UserPreference, UserSpace, ViewLog)
UserFile, UserPreference, UserSpace, ViewLog, FoodProperty)
from cookbook.provider.dropbox import Dropbox
from cookbook.provider.local import Local
from cookbook.provider.nextcloud import Nextcloud
@ -104,6 +104,7 @@ from cookbook.serializer import (AccessTokenSerializer, AutomationSerializer,
UserSerializer, UserSpaceSerializer, ViewLogSerializer)
from cookbook.views.import_export import get_integration
from recipes import settings
from recipes.settings import FDC_API_KEY
class StandardFilterMixin(ViewSetMixin):
@ -595,6 +596,54 @@ class FoodViewSet(viewsets.ModelViewSet, TreeMixin):
created_by=request.user)
return Response(content, status=status.HTTP_204_NO_CONTENT)
@decorators.action(detail=True, methods=['POST'], )
def fdc(self, request, pk):
"""
updates the food with all possible data from the FDC Api
if properties with a fdc_id already exist they will be overridden, if existing properties don't have a fdc_id they won't be changed
"""
food = self.get_object()
response = requests.get(f'https://api.nal.usda.gov/fdc/v1/food/{food.fdc_id}?api_key={FDC_API_KEY}')
if response.status_code == 429:
return JsonResponse({'msg', 'API Key Rate Limit reached/exceeded, see https://api.data.gov/docs/rate-limits/ for more information. Configure your key in Tandoor using environment FDC_API_KEY variable.'}, status=429,
json_dumps_params={'indent': 4})
try:
data = json.loads(response.content)
food_property_list = []
# delete all properties where the property type has a fdc_id as these should be overridden
for fp in food.properties.all():
if fp.property_type.fdc_id:
fp.delete()
for pt in PropertyType.objects.filter(space=request.space, fdc_id__gte=0).all():
if pt.fdc_id:
for fn in data['foodNutrients']:
if fn['nutrient']['id'] == pt.fdc_id:
food_property_list.append(Property(
property_type_id=pt.id,
property_amount=round(fn['amount'], 2),
import_food_id=food.id,
space=self.request.space,
))
Property.objects.bulk_create(food_property_list, ignore_conflicts=True, unique_fields=('space', 'import_food_id', 'property_type',))
property_food_relation_list = []
for p in Property.objects.filter(space=self.request.space, import_food_id=food.id).values_list('import_food_id', 'id', ):
property_food_relation_list.append(Food.properties.through(food_id=p[0], property_id=p[1]))
FoodProperty.objects.bulk_create(property_food_relation_list, ignore_conflicts=True, unique_fields=('food_id', 'property_id',))
Property.objects.filter(space=self.request.space, import_food_id=food.id).update(import_food_id=None)
return self.retrieve(request, pk)
except Exception as e:
traceback.print_exc()
return JsonResponse({'msg': f'there was an error parsing the FDC data, please check the server logs'}, status=500, json_dumps_params={'indent': 4})
def destroy(self, *args, **kwargs):
try:
return (super().destroy(self, *args, **kwargs))
@ -1454,7 +1503,7 @@ def import_files(request):
"""
limit, msg = above_space_limit(request.space)
if limit:
return Response({'error': msg}, status=status.HTTP_400_BAD_REQUEST)
return Response({'error': True, 'msg': _('File is above space limit')}, status=status.HTTP_400_BAD_REQUEST)
form = ImportForm(request.POST, request.FILES)
if form.is_valid() and request.FILES != {}:

View File

@ -204,6 +204,11 @@ def ingredient_editor(request):
return render(request, 'ingredient_editor.html', template_vars)
@group_required('user')
def property_editor(request, pk):
return render(request, 'property_editor.html', {'recipe_id': pk})
@group_required('guest')
def shopping_settings(request):
if request.space.demo:

View File

@ -32,6 +32,17 @@ If you just set up your Tandoor instance and you're having issues like;
then make sure you have set [all required headers](install/docker.md#required-headers) in your reverse proxy correctly.
If that doesn't fix it, you can also refer to the appropriate sub section in the [reverse proxy documentation](install/docker.md#reverse-proxy) and verify your general webserver configuration.
### Required Headers
Navigate to `/system` and review the headers listed in the DEBUG section. At a minimum, if you are using a reverse proxy the headers must match the below conditions.
| Header | Requirement |
| :--- | :---- |
| HTTP_HOST:mydomain.tld | The host domain must match the url that you are using to open Tandoor. |
| HTTP_X_FORWARDED_HOST:mydomain.tld | The host domain must match the url that you are using to open Tandoor. |
| HTTP_X_FORWARDED_PROTO:http(s) | The protocol must match the url you are using to open Tandoor. There must be exactly one protocol listed. |
| HTTP_X_SCRIPT_NAME:/subfolder | If you are hosting Tandoor at a subfolder instead of a subdomain this header must exist. |
## Why am I getting CSRF Errors?
If you are getting CSRF Errors this is most likely due to a reverse proxy not passing the correct headers.
@ -48,6 +59,9 @@ The other common issue is that the recommended nginx container is removed from t
If removed, the nginx webserver needs to be replaced by something else that servers the /mediafiles/ directory or
`GUNICORN_MEDIA` needs to be enabled to allow media serving by the application container itself.
## Why am I getting an error stating database files are incompatible with server?
Your version of Postgres has been upgraded. See [Updating PostgreSQL](https://docs.tandoor.dev/system/updating/#postgresql)
## Why does the Text/Markdown preview look different than the final recipe?

View File

@ -6,6 +6,12 @@ It is possible to install this application using many different Docker configura
Please read the instructions on each example carefully and decide if this is the way for you.
## **DockSTARTer**
The main goal of [DockSTARTer](https://dockstarter.com/) is to make it quick and easy to get up and running with Docker.
You may choose to rely on DockSTARTer for various changes to your Docker system or use DockSTARTer as a stepping stone and learn to do more advanced configurations.
Follow the guide for installing DockSTARTer and then run `ds` then select 'Configuration' and 'Select Apps' to get Tandoor up and running quickly and easily.
## **Docker**
The docker image (`vabene1111/recipes`) simply exposes the application on the container's port `8080`.

View File

@ -56,3 +56,23 @@ You can now export recipes from Tandoor using the export function. This method r
Import:
Go to Import > from app > tandoor and select the zip file you want to import from.
## Backing up using the pgbackup container
You can add [pgbackup](https://hub.docker.com/r/prodrigestivill/postgres-backup-local) to manage the scheduling and automatic backup of your postgres database.
Modify the below to match your environment and add it to your `docker-compose.yml`
``` yaml
pgbackup:
container_name: pgbackup
environment:
BACKUP_KEEP_DAYS: "8"
BACKUP_KEEP_MONTHS: "6"
BACKUP_KEEP_WEEKS: "4"
POSTGRES_EXTRA_OPTS: -Z6 --schema=public --blobs
SCHEDULE: '@daily'
# Note: the tag must match the version of postgres you are using
image: prodrigestivill/postgres-backup-local:15
restart: unless-stopped
volumes:
- backups/postgres:/backups
```
You can manually initiate a backup by running `docker exec -it pgbackup ./backup.sh`

View File

@ -16,7 +16,79 @@ For all setups using Docker the updating process look something like this
For all setups using a manual installation updates usually involve downloading the latest source code from GitHub.
After that make sure to run:
1. `manage.py collectstatic`
2. `manage.py migrate`
1. `pip install -r requirements.txt`
2. `manage.py collectstatic`
3. `manage.py migrate`
4. `cd ./vue`
5. `yarn install`
6. `yarn build`
To apply all new migrations and collect new static files.
To install latest libraries, apply all new migrations and collect new static files.
## PostgreSQL
Postgres does not automatically upgrade database files when you change versions and requires manual intervention.
One option is to manually [backup/restore](https://docs.tandoor.dev/system/updating/#postgresql) the database.
A full list of options to upgrade a database provide in the [official PostgreSQL documentation](https://www.postgresql.org/docs/current/upgrading.html).
1. Collect information about your environment.
``` bash
grep -E 'POSTGRES|DATABASE' ~/.docker/compose/.env
docker ps -a --format 'table {{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}' | awk 'NR == 1 || /postgres/ || /recipes/'
```
2. Export the tandoor database
``` bash
docker exec -t {{database_container}} pg_dumpall -U {{djangouser}} > ~/tandoor.sql
```
3. Stop the postgres container
``` bash
docker stop {{database_container}} {{tandoor_container}}
```
4. Rename the tandoor volume
``` bash
sudo mv -R ~/.docker/compose/postgres ~/.docker/compose/postgres.old
```
5. Update image tag on postgres container.
``` yaml
db_recipes:
restart: always
image: postgres:16-alpine
volumes:
- ./postgresql:/var/lib/postgresql/data
env_file:
- ./.env
```
6. Pull and rebuild container.
``` bash
docker-compose pull && docker-compose up -d
```
7. Import the database export
``` bash
cat ~/tandoor.sql | sudo docker exec -i {{database_container}} psql postgres -U {{djangouser}}
```
8. Install postgres extensions
``` bash
docker exec -it {{database_container}} psql
```
then
``` psql
CREATE EXTENSION IF NOT EXISTS unaccent;
CREATE EXTENSION IF NOT EXISTS pg_trgm;
```
If anything fails, go back to the old postgres version and data directory and try again.
There are many articles and tools online that might provide a good starting point to help you upgrade [1](https://thomasbandt.com/postgres-docker-major-version-upgrade), [2](https://github.com/tianon/docker-postgres-upgrade), [3](https://github.com/vabene1111/DockerPostgresBackups).

View File

@ -89,7 +89,7 @@ DJANGO_TABLES2_PAGE_RANGE = 8
HCAPTCHA_SITEKEY = os.getenv('HCAPTCHA_SITEKEY', '')
HCAPTCHA_SECRET = os.getenv('HCAPTCHA_SECRET', '')
FDA_API_KEY = os.getenv('FDA_API_KEY', 'DEMO_KEY')
FDC_API_KEY = os.getenv('FDC_API_KEY', 'DEMO_KEY')
SHARING_ABUSE = bool(int(os.getenv('SHARING_ABUSE', False)))
SHARING_LIMIT = int(os.getenv('SHARING_LIMIT', 0))
@ -350,7 +350,7 @@ WSGI_APPLICATION = 'recipes.wsgi.application'
# Load settings from env files
if os.getenv('DATABASE_URL'):
match = re.match(
r'(?P<schema>\w+):\/\/(?:(?P<user>[\w\d_-]+)(?::(?P<password>[^@]+))?@)?(?P<host>[^:/]+)(?:(?P<port>\d+))?(?:/(?P<database>[\w\d/._-]+))?',
r'(?P<schema>\w+):\/\/(?:(?P<user>[\w\d_-]+)(?::(?P<password>[^@]+))?@)?(?P<host>[^:/]+)(?::(?P<port>\d+))?(?:/(?P<database>[\w\d/._-]+))?',
os.getenv('DATABASE_URL')
)
settings = match.groupdict()
@ -450,7 +450,11 @@ for p in PLUGINS:
LANGUAGE_CODE = 'en'
TIME_ZONE = os.getenv('TIMEZONE') if os.getenv('TIMEZONE') else 'Europe/Berlin'
if os.getenv('TIMEZONE') is not None:
print('DEPRECATION WARNING: Environment var "TIMEZONE" is deprecated. Please use "TZ" instead.')
TIME_ZONE = os.getenv('TIMEZONE') if os.getenv('TIMEZONE') else 'Europe/Berlin'
else:
TIME_ZONE = os.getenv('TZ') if os.getenv('TZ') else 'Europe/Berlin'
USE_I18N = True

View File

@ -1,5 +1,5 @@
Django==4.2.7
cryptography===41.0.4
cryptography===41.0.6
django-annoying==0.10.6
django-autocomplete-light==3.9.4
django-cleanup==8.0.0
@ -32,7 +32,7 @@ git+https://github.com/BITSOLVER/django-js-reverse@071e304fd600107bc64bbde6f2491
django-allauth==0.54.0
recipe-scrapers==14.52.0
django-scopes==2.0.0
pytest==7.3.1
pytest==7.4.3
pytest-django==4.6.0
django-treebeard==4.7
django-cors-headers==4.2.0

View File

@ -669,8 +669,7 @@ export default {
if (url !== '') {
this.failed_imports.push(url)
}
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_FETCH, err)
throw "Load Recipe Error"
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_IMPORT, err)
})
},
/**
@ -713,8 +712,7 @@ export default {
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(this, StandardToasts.FAIL_CREATE)
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_IMPORT, err)
})
},
/**

View File

@ -744,8 +744,12 @@ having to override as much.
.theme-default .cv-item.continued::before,
.theme-default .cv-item.toBeContinued::after {
/*
removed because it breaks a line and would increase item size https://github.com/TandoorRecipes/recipes/issues/2678
content: " \21e2 ";
color: #999;
*/
}
.theme-default .cv-item.toBeContinued {

View File

@ -186,10 +186,10 @@ export default {
case "ingredient-editor": {
let url = resolveDjangoUrl("view_ingredient_editor")
if (this.this_model === this.Models.FOOD) {
window.location.href = url + '?food_id=' + e.source.id
window.open(url + '?food_id=' + e.source.id, "_blank");
}
if (this.this_model === this.Models.UNIT) {
window.location.href = url + '?unit_id=' + e.source.id
window.open(url + '?unit_id=' + e.source.id, "_blank");
}
break
}

View File

@ -0,0 +1,227 @@
<template>
<div id="app">
<div>
<div class="row" v-if="recipe" style="max-height: 10vh">
<div class="col col-8">
<h2><a :href="resolveDjangoUrl('view_recipe', recipe.id)">{{ recipe.name }}</a></h2>
{{ recipe.description }}
<keywords-component :recipe="recipe"></keywords-component>
</div>
<div class="col col-4" v-if="recipe.image">
<img style="max-height: 10vh" class="img-thumbnail float-right" :src="recipe.image">
</div>
</div>
<div class="row mt-3">
<div class="col col-12">
<b-button variant="success" href="https://fdc.nal.usda.gov/index.html" target="_blank"><i class="fas fa-external-link-alt"></i> {{$t('FDC_Search')}}</b-button>
<table class="table table-sm table-bordered table-responsive mt-2 pb-5">
<thead>
<tr>
<td>{{ $t('Name') }}</td>
<td>FDC</td>
<td>{{ $t('Properties_Food_Amount') }}</td>
<td>{{ $t('Properties_Food_Unit') }}</td>
<td v-for="pt in property_types" v-bind:key="pt.id">
<b-button variant="primary" @click="editing_property_type = pt" class="btn-block">{{ pt.name }}
<span v-if="pt.unit !== ''">({{ pt.unit }}) </span> <br/>
<b-badge variant="light" ><i class="fas fa-sort-amount-down-alt"></i> {{ pt.order}}</b-badge>
<b-badge variant="success" v-if="pt.fdc_id > 0" class="mt-2" v-b-tooltip.hover :title="$t('property_type_fdc_hint')"><i class="fas fa-check"></i> FDC</b-badge>
<b-badge variant="warning" v-if="pt.fdc_id < 1" class="mt-2" v-b-tooltip.hover :title="$t('property_type_fdc_hint')"><i class="fas fa-times"></i> FDC</b-badge>
</b-button>
</td>
<td>
<b-button variant="success" @click="new_property_type = true"><i class="fas fa-plus"></i></b-button>
</td>
</tr>
</thead>
<tbody>
<tr v-for="f in this.foods" v-bind:key="f.id">
<td>
{{ f.name }}
</td>
<td style="width: 15em;">
<b-input-group>
<b-form-input v-model="f.fdc_id" type="number" @change="updateFood(f)" :disabled="f.loading"></b-form-input>
<b-input-group-append>
<b-button variant="success" @click="updateFoodFromFDC(f)" :disabled="f.loading"><i class="fas fa-sync-alt" :class="{'fa-spin': loading}"></i></b-button>
<b-button variant="info" :href="`https://fdc.nal.usda.gov/fdc-app.html#/food-details/${f.fdc_id}`" :disabled="f.fdc_id < 1" target="_blank"><i class="fas fa-external-link-alt"></i></b-button>
</b-input-group-append>
</b-input-group>
</td>
<td style="width: 5em; ">
<b-input v-model="f.properties_food_amount" type="number" @change="updateFood(f)" :disabled="f.loading"></b-input>
</td>
<td style="width: 11em;">
<generic-multiselect
@change="f.properties_food_unit = $event.val; updateFood(f)"
:initial_single_selection="f.properties_food_unit"
label="name" :model="Models.UNIT"
:multiple="false"
:disabled="f.loading"/>
</td>
<td v-for="p in f.properties" v-bind:key="`${f.id}_${p.property_type.id}`">
<b-input-group>
<b-form-input v-model="p.property_amount" type="number" :disabled="f.loading" v-b-tooltip.focus :title="p.property_type.name" @change="updateFood(f)"></b-form-input>
</b-input-group>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<generic-modal-form
:show="editing_property_type !== null"
:model="Models.PROPERTY_TYPE"
:action="Actions.UPDATE"
:item1="editing_property_type"
@finish-action="editing_property_type = null; loadData()">
</generic-modal-form>
<generic-modal-form
:show="new_property_type"
:model="Models.PROPERTY_TYPE"
:action="Actions.CREATE"
@finish-action="new_property_type = false; loadData()">
</generic-modal-form>
</div>
</div>
</template>
<script>
import Vue from "vue"
import {BootstrapVue} from "bootstrap-vue"
import "bootstrap-vue/dist/bootstrap-vue.css"
import {ApiMixin, resolveDjangoUrl, StandardToasts} from "@/utils/utils";
import axios from "axios";
import BetaWarning from "@/components/BetaWarning.vue";
import {ApiApiFactory} from "@/utils/openapi/api";
import GenericMultiselect from "@/components/GenericMultiselect.vue";
import GenericModalForm from "@/components/Modals/GenericModalForm.vue";
import KeywordsComponent from "@/components/KeywordsComponent.vue";
Vue.use(BootstrapVue)
export default {
name: "PropertyEditorView",
mixins: [ApiMixin],
components: {KeywordsComponent, GenericModalForm, GenericMultiselect},
computed: {},
data() {
return {
recipe: null,
property_types: [],
editing_property_type: null,
new_property_type: false,
loading: false,
foods: [],
}
},
mounted() {
this.$i18n.locale = window.CUSTOM_LOCALE
this.loadData();
},
methods: {
resolveDjangoUrl,
loadData: function () {
let apiClient = new ApiApiFactory()
apiClient.listPropertyTypes().then(result => {
this.property_types = result.data
apiClient.retrieveRecipe(window.RECIPE_ID).then(result => {
this.recipe = result.data
this.foods = []
this.recipe.steps.forEach(s => {
s.ingredients.forEach(i => {
if (this.foods.filter(x => (x.id === i.food.id)).length === 0) {
this.foods.push(this.buildFood(i.food))
}
})
})
this.loading = false;
}).catch((err) => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_FETCH, err)
})
}).catch((err) => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_FETCH, err)
})
},
buildFood: function (food) {
/**
* Prepare food for display in grid by making sure the food properties are in the same order as property_types and that no types are missing
* */
let existing_properties = {}
food.properties.forEach(fp => {
existing_properties[fp.property_type.id] = fp
})
let food_properties = []
this.property_types.forEach(pt => {
let new_food_property = {
property_type: pt,
property_amount: 0,
}
if (pt.id in existing_properties) {
new_food_property = existing_properties[pt.id]
}
food_properties.push(new_food_property)
})
this.$set(food, 'loading', false)
food.properties = food_properties
return food
},
spliceInFood: function (food) {
/**
* replace food in foods list, for example after updates from the server
*/
this.foods = this.foods.map(f => (f.id === food.id) ? food : f)
},
updateFood: function (food) {
let apiClient = new ApiApiFactory()
apiClient.partialUpdateFood(food.id, food).then(result => {
this.spliceInFood(this.buildFood(result.data))
StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_UPDATE)
}).catch((err) => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
})
},
updateFoodFromFDC: function (food) {
food.loading = true;
let apiClient = new ApiApiFactory()
apiClient.fdcFood(food.id).then(result => {
StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_UPDATE)
this.spliceInFood(this.buildFood(result.data))
}).catch((err) => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
food.loading = false;
})
}
},
}
</script>
<style>
</style>

View File

@ -0,0 +1,22 @@
import Vue from 'vue'
import App from './PropertyEditorView.vue'
import i18n from '@/i18n'
import {createPinia, PiniaVuePlugin} from "pinia";
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
Vue.use(PiniaVuePlugin)
const pinia = createPinia()
new Vue({
pinia,
i18n,
render: h => h(App),
}).$mount('#app')

View File

@ -2,34 +2,7 @@
<div id="app">
<div>
<h2 v-if="recipe">{{ recipe.name}}</h2>
<table class="table table-sm table-bordered">
<thead>
<tr>
<td>{{ $t('Name') }}</td>
<td v-for="pt in property_types" v-bind:key="pt.id">{{ pt.name }}
<input type="text" v-model="pt.unit" @change="updatePropertyType(pt)">
<input v-model="pt.fdc_id" type="number" placeholder="FDC ID" @change="updatePropertyType(pt)"></td>
</tr>
</thead>
<tbody>
<tr v-for="f in this.foods" v-bind:key="f.food.id">
<td>
{{ f.food.name }}
{{ $t('Property') }} / <input type="number" v-model="f.food.properties_food_amount" @change="updateFood(f.food)">
<generic-multiselect
@change="f.food.properties_food_unit = $event.val; updateFood(f.food)"
:initial_selection="f.food.properties_food_unit"
label="name" :model="Models.UNIT"
:multiple="false"/>
<input v-model="f.food.fdc_id" placeholder="FDC ID">
<button>Load FDC</button>
</td>
<td v-for="p in f.properties" v-bind:key="`${f.id}_${p.property_type.id}`"><input type="number" v-model="p.property_amount"> {{ p.property_type.unit }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
@ -45,6 +18,7 @@ import axios from "axios";
import BetaWarning from "@/components/BetaWarning.vue";
import {ApiApiFactory} from "@/utils/openapi/api";
import GenericMultiselect from "@/components/GenericMultiselect.vue";
import GenericModalForm from "@/components/Modals/GenericModalForm.vue";
Vue.use(BootstrapVue)
@ -53,66 +27,16 @@ Vue.use(BootstrapVue)
export default {
name: "TestView",
mixins: [ApiMixin],
components: {GenericMultiselect},
computed: {
foods: function () {
let foods = []
if (this.recipe !== null && this.property_types !== []) {
this.recipe.steps.forEach(s => {
s.ingredients.forEach(i => {
let food = {food: i.food, properties: {}}
this.property_types.forEach(pt => {
food.properties[pt.id] = {changed: false, property_amount: 0, property_type: pt}
})
i.food.properties.forEach(fp => {
food.properties[fp.property_type.id] = {changed: false, property_amount: fp.property_amount, property_type: fp.property_type}
})
foods.push(food)
})
})
}
return foods
}
},
components: {},
computed: {},
data() {
return {
recipe: null,
property_types: []
}
return {}
},
mounted() {
this.$i18n.locale = window.CUSTOM_LOCALE
let apiClient = new ApiApiFactory()
apiClient.retrieveRecipe("112").then(result => {
this.recipe = result.data
})
apiClient.listPropertyTypes().then(result => {
this.property_types = result.data
})
},
methods: {
updateFood: function (food) {
let apiClient = new ApiApiFactory()
apiClient.partialUpdateFood(food.id, food).then(result => {
//TODO handle properly
StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_UPDATE)
}).catch((err) => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
})
},
updatePropertyType: function (pt) {
let apiClient = new ApiApiFactory()
apiClient.partialUpdatePropertyType(pt.id, pt).then(result => {
//TODO handle properly
StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_UPDATE)
}).catch((err) => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
})
}
},
}
</script>

View File

@ -33,11 +33,11 @@
<h5><i class="fas fa-database"></i> {{ $t('Properties') }}</h5>
<b-form-group :label="$t('Properties Food Amount')" description=""> <!-- TODO localize -->
<b-form-group :label="$t('Properties_Food_Amount')" description="">
<b-form-input v-model="food.properties_food_amount"></b-form-input>
</b-form-group>
<b-form-group :label="$t('Properties Food Unit')" description=""> <!-- TODO localize -->
<b-form-group :label="$t('Properties_Food_Unit')" description="">
<generic-multiselect
@change="food.properties_food_unit = $event.val;"
:model="Models.UNIT"

View File

@ -20,6 +20,7 @@
@input="selectionChanged"
@tag="addNew"
@open="selectOpened()"
:disabled="disabled"
>
</multiselect>
</template>
@ -74,6 +75,7 @@ export default {
allow_create: { type: Boolean, default: false },
create_placeholder: { type: String, default: "You Forgot to Add a Tag Placeholder" },
clear: { type: Number },
disabled: {type: Boolean, default: false, },
},
watch: {
initial_selection: function (newVal, oldVal) {

View File

@ -25,6 +25,12 @@ export default {
},
mounted() {
this.new_value = this.value
if (this.new_value === "") { // if the selection is empty but the options are of type number, set to 0 instead of ""
if (typeof this.options[0]['value'] === 'number') {
this.new_value = 0
}
}
},
watch: {
new_value: function () {

View File

@ -14,7 +14,7 @@ export default {
props: {
field: { type: String, default: "You Forgot To Set Field Name" },
label: { type: String, default: "Text Field" },
value: { type: String, default: "" },
value: { type: Number, default: 0 },
placeholder: { type: Number, default: 0 },
help: { type: String, default: undefined },
subtitle: { type: String, default: undefined },

View File

@ -24,7 +24,7 @@
<table class="table table-bordered table-sm">
<tr >
<tr>
<td style="border-top: none"></td>
<td class="text-right" style="border-top: none">{{ $t('per_serving') }}</td>
<td class="text-right" style="border-top: none">{{ $t('total') }}</td>
@ -41,14 +41,18 @@
<td class="align-middle text-center" v-if="!show_recipe_properties">
<a href="#" @click="selected_property = p">
<i v-if="p.missing_value" class="text-warning fas fa-exclamation-triangle"></i>
<i v-if="!p.missing_value" class="text-muted fas fa-info-circle"></i>
<!-- <i v-if="p.missing_value" class="text-warning fas fa-exclamation-triangle"></i>-->
<!-- <i v-if="!p.missing_value" class="text-muted fas fa-info-circle"></i>-->
<i class="text-muted fas fa-info-circle"></i>
<!-- TODO find solution for missing values as 0 can either be missing or actually correct for any given property -->
</a>
</td>
</tr>
</table>
<div class="text-center">
<b-button variant="success" :href="resolveDjangoUrl('view_property_editor', recipe.id)"><i class="fas fa-table"></i> {{ $t('Property_Editor') }}</b-button>
</div>
</div>
@ -79,7 +83,7 @@
</template>
<script>
import {ApiMixin, roundDecimals, StandardToasts} from "@/utils/utils";
import {ApiMixin, resolveDjangoUrl, roundDecimals, StandardToasts} from "@/utils/utils";
import GenericModalForm from "@/components/Modals/GenericModalForm.vue";
import {ApiApiFactory} from "@/utils/openapi/api";
@ -153,11 +157,11 @@ export default {
}
}
function compare(a,b){
if(a.type.order > b.type.order){
function compare(a, b) {
if (a.type.order > b.type.order) {
return 1
}
if(a.type.order < b.type.order){
if (a.type.order < b.type.order) {
return -1
}
return 0
@ -172,6 +176,7 @@ export default {
}
},
methods: {
resolveDjangoUrl,
roundDecimals,
openFoodEditModal: function (food) {
console.log(food)

View File

@ -250,6 +250,10 @@ export default {
opacity: 1;
}
.content:hover .card-img-overlay {
opacity: 0;
}
.content-details {
position: absolute;
text-align: center;

View File

@ -10,6 +10,9 @@
<a class="dropdown-item" :href="resolveDjangoUrl('edit_recipe', recipe.id)" v-if="!disabled_options.edit"><i
class="fas fa-pencil-alt fa-fw"></i> {{ $t("Edit") }}</a>
<a class="dropdown-item" :href="resolveDjangoUrl('view_property_editor', recipe.id)" v-if="!disabled_options.edit">
<i class="fas fa-table"></i> {{ $t("Property_Editor") }}</a>
<a class="dropdown-item" :href="resolveDjangoUrl('edit_convert_recipe', recipe.id)"
v-if="!recipe.internal && !disabled_options.convert"><i class="fas fa-exchange-alt fa-fw"></i> {{ $t("convert_internal") }}</a>
@ -209,6 +212,7 @@ export default {
this.entryEditing = this.options.entryEditing
this.entryEditing.recipe = this.recipe
this.entryEditing.from_date = moment(new Date()).format("YYYY-MM-DD")
this.entryEditing.to_date = moment(new Date()).format("YYYY-MM-DD")
this.$nextTick(function () {
this.$bvModal.show(`modal-meal-plan_${this.modal_id}`)
})
@ -259,9 +263,11 @@ export default {
},
}
})
if (recipe.nutrition !== null) {
delete recipe.nutrition.id
}
recipe.properties = recipe.properties.map(p => {
return { ...p, ...{ id: undefined, } }
})
apiClient
.createRecipe(recipe)
.then((new_recipe) => {

View File

@ -277,9 +277,7 @@ export default {
}
},
handleResize: function () {
if (document.getElementById('nutrition_container') !== null) {
this.ingredient_height = document.getElementById('ingredient_container').clientHeight - document.getElementById('nutrition_container').clientHeight
} else {
if (document.getElementById('ingredient_container') !== null) {
this.ingredient_height = document.getElementById('ingredient_container').clientHeight
}
},

View File

@ -1,5 +1,5 @@
{
"warning_feature_beta": "Denne funktion er i øjeblikket i BETA (test) stadie. Forvent fejl og fremtidige ændringer (hvor data kan mistes) ved brug af denne funktion.",
"warning_feature_beta": "Denne funktion er i øjeblikket i BETA (test)-stadie. Forvent fejl og fremtidige ændringer (hvor data kan mistes) ved brug af denne funktion.",
"err_fetching_resource": "Der opstod en fejl under indlæsning af denne ressource!",
"err_creating_resource": "Der opstod en fejl under oprettelsen af denne ressource!",
"err_updating_resource": "Der opstod en fejl under opdateringen af denne ressource!",
@ -477,5 +477,62 @@
"Unpin": "Frigør",
"PinnedConfirmation": "{recipe} er fastgjort.",
"UnpinnedConfirmation": "{recipe} er frigjort.",
"Combine_All_Steps": "Kombiner alle trin til ét felt."
"Combine_All_Steps": "Kombiner alle trin til ét felt.",
"converted_unit": "Konverteret enhed",
"Property": "Egenskab",
"OrderInformation": "Objekter er rangeret fra små til store tal.",
"show_ingredients_table": "Vis ingredienser i en tabel ved siden af trinnets tekst",
"tsp": "teaspoon [tsp] (US, volumen)",
"imperial_fluid_ounce": "imperial fluid ounce [imp fl oz] (UK, volumen)",
"imperial_tsp": "imperial teaspoon [imp tsp] (UK, volumen)",
"open_data_help_text": "Tandoor Open Data projektet tilføjer netværksgenereret data til Tandoor. Dette felt bliver udfyldt automatisk under importering og muliggør fremtidige opdateringer.",
"converted_amount": "Konverteret mængde",
"StartDate": "Startdato",
"EndDate": "Slutdato",
"show_step_ingredients_setting": "Vis ingredienser ved siden af opskrifttrin",
"l": "liter [l] (metrisk, volumen)",
"g": "gram [g] (metrisk, vægt)",
"kg": "kilogram [kg] (metrisk, vægt)",
"ounce": "ounce [oz] (vægt)",
"pound": "pund (vægt)",
"ml": "milliliter [ml] (metrisk, volumen)",
"fluid_ounce": "flydende ounce [fl oz] (US, volumen)",
"pint": "pint [pt] (US, volumen)",
"Back": "Tilbage",
"quart": "quart [qt] (US, volumen)",
"recipe_property_info": "Du kan også tilføje næringsindhold til ingredienser for at udregne indholdet automatisk baseret på din opskrift!",
"per_serving": "per serveringer",
"Open_Data_Slug": "Open Data Slug",
"Open_Data_Import": "Open Data importering",
"Data_Import_Info": "Udbyg dit Space og gør din opskriftsamling bedre ved at importere en netværkskurateret liste af ingredienser, enheder og mere.",
"Update_Existing_Data": "Opdaterer eksisterende data",
"make_now_count": "Oftest manglende ingredienser",
"Welcome": "Velkommen",
"imperial_pint": "imperial pint [imp pt] (UK, volumen)",
"Alignment": "Justering",
"gallon": "gallon [gal] (US, volumen)",
"Never_Unit": "Aldrig enhed",
"FDC_ID": "FDC ID",
"FDC_ID_help": "FDC database ID",
"Use_Metric": "Benyt metriske enheder",
"Learn_More": "Lær mere",
"base_unit": "Basisenhed",
"base_amount": "Basismængde",
"Datatype": "Datatype",
"Number of Objects": "Antal objekter",
"Conversion": "Konversion",
"Properties": "Egenskaber",
"show_step_ingredients_setting_help": "Tilføj ingredienstabel ved siden af opskrifttrin. Tilføjes ved oprettelsen. Kan overskrives under rediger opskrift.",
"show_step_ingredients": "Vis trinnets ingredienser",
"hide_step_ingredients": "Skjul trinnets ingredienser",
"total": "total",
"tbsp": "tablespoon [tbsp] (US, volumen)",
"imperial_quart": "imperial quart [imp qt] (UK, volumen)",
"imperial_gallon": "imperial gal [imp gal] (UK, volumen)",
"imperial_tbsp": "imperial tablespoon [imp tbsp] (UK, volumen)",
"Choose_Category": "Vælg kategori",
"Transpose_Words": "Omstil ord",
"Name_Replace": "Erstat navn",
"Food_Replace": "Erstat ingrediens",
"Unit_Replace": "Erstat enhed"
}

View File

@ -7,6 +7,7 @@
"err_deleting_protected_resource": "The object you are trying to delete is still used and can't be deleted.",
"err_moving_resource": "There was an error moving a resource!",
"err_merging_resource": "There was an error merging a resource!",
"err_importing_recipe": "There was an error importing the recipe!",
"success_fetching_resource": "Successfully fetched a resource!",
"success_creating_resource": "Successfully created a resource!",
"success_updating_resource": "Successfully updated a resource!",
@ -80,8 +81,12 @@
"open_data_help_text": "The Tandoor Open Data project provides community contributed data for Tandoor. This field is filled automatically when importing it and allows updates in the future.",
"Open_Data_Slug": "Open Data Slug",
"Open_Data_Import": "Open Data Import",
"Properties_Food_Amount": "Properties Food Amount",
"Properties_Food_Unit": "Properties Food Unit",
"FDC_ID": "FDC ID",
"FDC_Search": "FDC Search",
"FDC_ID_help": "FDC database ID",
"property_type_fdc_hint": "Only property types with an FDC ID can automatically pull data from the FDC database",
"Data_Import_Info": "Enhance your Space by importing a community curated list of foods, units and more to improve your recipe collection.",
"Update_Existing_Data": "Update Existing Data",
"Use_Metric": "Use Metric Units",
@ -182,6 +187,7 @@
"move_title": "Move {type}",
"Food": "Food",
"Property": "Property",
"Property_Editor": "Property Editor",
"Conversion": "Conversion",
"Original_Text": "Original Text",
"Recipe_Book": "Recipe Book",

View File

@ -162,7 +162,7 @@
"Unit_Alias": "Alias Unità",
"Keyword_Alias": "Alias Parola Chiave",
"Table_of_Contents": "Indice dei contenuti",
"warning_feature_beta": "Questa funzione è attualmente in BETA (non è completa). Potrebbero verificarsi delle anomalie e modifiche che in futuro potrebbero bloccare la funzionalità stessa o rimuove i dati correlati a essa.",
"warning_feature_beta": "Questa funzione è attualmente in BETA (non è completa). Potrebbero verificarsi delle anomalie e modifiche che in futuro potrebbero bloccare la funzionalità stessa o rimuove i dati correlati ad essa.",
"Shopping_list": "Lista della spesa",
"Title": "Titolo",
"Create_New_Meal_Type": "Aggiungi nuovo tipo di pasto",
@ -393,7 +393,7 @@
"view_recipe": "Mostra ricetta",
"copy_to_new": "Copia in una nuova ricetta",
"Pinned": "Fissato",
"App": "App",
"App": "Applicazione",
"filter": "Filtro",
"explain": "Maggior informazioni",
"Website": "Sito web",

View File

@ -23,7 +23,7 @@ export class Models {
false: undefined,
},
},
tree: { default: undefined },
tree: {default: undefined},
},
},
delete: {
@ -50,7 +50,7 @@ export class Models {
type: "lookup",
field: "target",
list: "self",
sticky_options: [{ id: 0, name: "tree_root" }],
sticky_options: [{id: 0, name: "tree_root"}],
},
},
},
@ -71,7 +71,7 @@ export class Models {
food_onhand: true,
shopping: true,
},
tags: [{ field: "supermarket_category", label: "name", color: "info" }],
tags: [{field: "supermarket_category", label: "name", color: "info"}],
// REQUIRED: unordered array of fields that can be set during create
create: {
// if not defined partialUpdate will use the same parameters, prepending 'id'
@ -177,7 +177,7 @@ export class Models {
field: "substitute_siblings",
label: "substitute_siblings", // form.label always translated in utils.getForm()
help_text: "substitute_siblings_help", // form.help_text always translated
condition: { field: "parent", value: true, condition: "field_exists" },
condition: {field: "parent", value: true, condition: "field_exists"},
},
substitute_children: {
form_field: true,
@ -186,7 +186,7 @@ export class Models {
field: "substitute_children",
label: "substitute_children",
help_text: "substitute_children_help",
condition: { field: "numchild", value: 0, condition: "gt" },
condition: {field: "numchild", value: 0, condition: "gt"},
},
inherit_fields: {
form_field: true,
@ -196,7 +196,7 @@ export class Models {
field: "inherit_fields",
list: "FOOD_INHERIT_FIELDS",
label: "InheritFields",
condition: { field: "food_children_exist", value: true, condition: "preference_equals" },
condition: {field: "food_children_exist", value: true, condition: "preference_equals"},
help_text: "InheritFields_help",
},
child_inherit_fields: {
@ -207,7 +207,7 @@ export class Models {
field: "child_inherit_fields",
list: "FOOD_INHERIT_FIELDS",
label: "ChildInheritFields", // form.label always translated in utils.getForm()
condition: { field: "numchild", value: 0, condition: "gt" },
condition: {field: "numchild", value: 0, condition: "gt"},
help_text: "ChildInheritFields_help", // form.help_text always translated
},
reset_inherit: {
@ -217,7 +217,7 @@ export class Models {
field: "reset_inherit",
label: "reset_children",
help_text: "reset_children_help",
condition: { field: "numchild", value: 0, condition: "gt" },
condition: {field: "numchild", value: 0, condition: "gt"},
},
form_function: "FoodCreateDefault",
},
@ -304,24 +304,24 @@ export class Models {
form_field: true,
type: "choice",
options: [
{ value: "g", text: "g" },
{ value: "kg", text: "kg" },
{ value: "ounce", text: "ounce" },
{ value: "pound", text: "pound" },
{ value: "ml", text: "ml" },
{ value: "l", text: "l" },
{ value: "fluid_ounce", text: "fluid_ounce" },
{ value: "pint", text: "pint" },
{ value: "quart", text: "quart" },
{ value: "gallon", text: "gallon" },
{ value: "tbsp", text: "tbsp" },
{ value: "tsp", text: "tsp" },
{ value: "imperial_fluid_ounce", text: "imperial_fluid_ounce" },
{ value: "imperial_pint", text: "imperial_pint" },
{ value: "imperial_quart", text: "imperial_quart" },
{ value: "imperial_gallon", text: "imperial_gallon" },
{ value: "imperial_tbsp", text: "imperial_tbsp" },
{ value: "imperial_tsp", text: "imperial_tsp" },
{value: "g", text: "g"},
{value: "kg", text: "kg"},
{value: "ounce", text: "ounce"},
{value: "pound", text: "pound"},
{value: "ml", text: "ml"},
{value: "l", text: "l"},
{value: "fluid_ounce", text: "fluid_ounce"},
{value: "pint", text: "pint"},
{value: "quart", text: "quart"},
{value: "gallon", text: "gallon"},
{value: "tbsp", text: "tbsp"},
{value: "tsp", text: "tsp"},
{value: "imperial_fluid_ounce", text: "imperial_fluid_ounce"},
{value: "imperial_pint", text: "imperial_pint"},
{value: "imperial_quart", text: "imperial_quart"},
{value: "imperial_gallon", text: "imperial_gallon"},
{value: "imperial_tbsp", text: "imperial_tbsp"},
{value: "imperial_tsp", text: "imperial_tsp"},
],
field: "base_unit",
label: "Base Unit",
@ -457,7 +457,7 @@ export class Models {
static SUPERMARKET = {
name: "Supermarket",
apiName: "Supermarket",
ordered_tags: [{ field: "category_to_supermarket", label: "category::name", color: "info" }],
ordered_tags: [{field: "category_to_supermarket", label: "category::name", color: "info"}],
create: {
params: [["name", "description", "category_to_supermarket"]],
form: {
@ -540,16 +540,16 @@ export class Models {
form_field: true,
type: "choice",
options: [
{ value: "FOOD_ALIAS", text: "Food_Alias" },
{ value: "UNIT_ALIAS", text: "Unit_Alias" },
{ value: "KEYWORD_ALIAS", text: "Keyword_Alias" },
{ value: "NAME_REPLACE", text: "Name_Replace" },
{ value: "DESCRIPTION_REPLACE", text: "Description_Replace" },
{ value: "INSTRUCTION_REPLACE", text: "Instruction_Replace" },
{ value: "FOOD_REPLACE", text: "Food_Replace" },
{ value: "UNIT_REPLACE", text: "Unit_Replace" },
{ value: "NEVER_UNIT", text: "Never_Unit" },
{ value: "TRANSPOSE_WORDS", text: "Transpose_Words" },
{value: "FOOD_ALIAS", text: "Food_Alias"},
{value: "UNIT_ALIAS", text: "Unit_Alias"},
{value: "KEYWORD_ALIAS", text: "Keyword_Alias"},
{value: "NAME_REPLACE", text: "Name_Replace"},
{value: "DESCRIPTION_REPLACE", text: "Description_Replace"},
{value: "INSTRUCTION_REPLACE", text: "Instruction_Replace"},
{value: "FOOD_REPLACE", text: "Food_Replace"},
{value: "UNIT_REPLACE", text: "Unit_Replace"},
{value: "NEVER_UNIT", text: "Never_Unit"},
{value: "TRANSPOSE_WORDS", text: "Transpose_Words"},
],
field: "type",
label: "Type",
@ -700,7 +700,7 @@ export class Models {
},
},
create: {
params: [["name", "unit", "description", "order"]],
params: [["name", "unit", "description", "order", "fdc_id"]],
form: {
show_help: true,
name: {
@ -733,12 +733,241 @@ export class Models {
field: "order",
label: "Order",
placeholder: "",
optional: false,
optional: true,
help_text: "OrderInformation",
},
fdc_id: {
form_field: true,
type: "text",
type: "choice",
options: [
{value: 1002, text: "Nitrogen [g] (1002)"},
{value: 1003, text: "Protein [g] (1003)"},
{value: 1004, text: "Total lipid (fat) [g] (1004)"},
{value: 1005, text: "Carbohydrate, by difference [g] (1005)"},
{value: 1007, text: "Ash [g] (1007)"},
{value: 1008, text: "Energy [kcal] (1008)"},
{value: 1009, text: "Starch [g] (1009)"},
{value: 1010, text: "Sucrose [g] (1010)"},
{value: 1011, text: "Glucose [g] (1011)"},
{value: 1012, text: "Fructose [g] (1012)"},
{value: 1013, text: "Lactose [g] (1013)"},
{value: 1014, text: "Maltose [g] (1014)"},
{value: 1024, text: "Specific Gravity [sp gr] (1024)"},
{value: 1032, text: "Citric acid [mg] (1032)"},
{value: 1039, text: "Malic acid [mg] (1039)"},
{value: 1041, text: "Oxalic acid [mg] (1041)"},
{value: 1043, text: "Pyruvic acid [mg] (1043)"},
{value: 1044, text: "Quinic acid [mg] (1044)"},
{value: 1050, text: "Carbohydrate, by summation [g] (1050)"},
{value: 1051, text: "Water [g] (1051)"},
{value: 1062, text: "Energy [kJ] (1062)"},
{value: 1063, text: "Sugars, Total [g] (1063)"},
{value: 1075, text: "Galactose [g] (1075)"},
{value: 1076, text: "Raffinose [g] (1076)"},
{value: 1077, text: "Stachyose [g] (1077)"},
{value: 1079, text: "Fiber, total dietary [g] (1079)"},
{value: 1082, text: "Fiber, soluble [g] (1082)"},
{value: 1084, text: "Fiber, insoluble [g] (1084)"},
{value: 1085, text: "Total fat (NLEA) [g] (1085)"},
{value: 1087, text: "Calcium, Ca [mg] (1087)"},
{value: 1089, text: "Iron, Fe [mg] (1089)"},
{value: 1090, text: "Magnesium, Mg [mg] (1090)"},
{value: 1091, text: "Phosphorus, P [mg] (1091)"},
{value: 1092, text: "Potassium, K [mg] (1092)"},
{value: 1093, text: "Sodium, Na [mg] (1093)"},
{value: 1094, text: "Sulfur, S [mg] (1094)"},
{value: 1095, text: "Zinc, Zn [mg] (1095)"},
{value: 1097, text: "Cobalt, Co [µg] (1097)"},
{value: 1098, text: "Copper, Cu [mg] (1098)"},
{value: 1100, text: "Iodine, I [µg] (1100)"},
{value: 1101, text: "Manganese, Mn [mg] (1101)"},
{value: 1102, text: "Molybdenum, Mo [µg] (1102)"},
{value: 1103, text: "Selenium, Se [µg] (1103)"},
{value: 1105, text: "Retinol [µg] (1105)"},
{value: 1106, text: "Vitamin A, RAE [µg] (1106)"},
{value: 1107, text: "Carotene, beta [µg] (1107)"},
{value: 1108, text: "Carotene, alpha [µg] (1108)"},
{value: 1109, text: "Vitamin E (alpha-tocopherol) [mg] (1109)"},
{value: 1110, text: "Vitamin D (D2 + D3), International Units [IU] (1110)"},
{value: 1111, text: "Vitamin D2 (ergocalciferol) [µg] (1111)"},
{value: 1112, text: "Vitamin D3 (cholecalciferol) [µg] (1112)"},
{value: 1113, text: "25-hydroxycholecalciferol [µg] (1113)"},
{value: 1114, text: "Vitamin D (D2 + D3) [µg] (1114)"},
{value: 1116, text: "Phytoene [µg] (1116)"},
{value: 1117, text: "Phytofluene [µg] (1117)"},
{value: 1118, text: "Carotene, gamma [µg] (1118)"},
{value: 1119, text: "Zeaxanthin [µg] (1119)"},
{value: 1120, text: "Cryptoxanthin, beta [µg] (1120)"},
{value: 1121, text: "Lutein [µg] (1121)"},
{value: 1122, text: "Lycopene [µg] (1122)"},
{value: 1123, text: "Lutein + zeaxanthin [µg] (1123)"},
{value: 1125, text: "Tocopherol, beta [mg] (1125)"},
{value: 1126, text: "Tocopherol, gamma [mg] (1126)"},
{value: 1127, text: "Tocopherol, delta [mg] (1127)"},
{value: 1128, text: "Tocotrienol, alpha [mg] (1128)"},
{value: 1129, text: "Tocotrienol, beta [mg] (1129)"},
{value: 1130, text: "Tocotrienol, gamma [mg] (1130)"},
{value: 1131, text: "Tocotrienol, delta [mg] (1131)"},
{value: 1137, text: "Boron, B [µg] (1137)"},
{value: 1146, text: "Nickel, Ni [µg] (1146)"},
{value: 1159, text: "cis-beta-Carotene [µg] (1159)"},
{value: 1160, text: "cis-Lycopene [µg] (1160)"},
{value: 1161, text: "cis-Lutein/Zeaxanthin [µg] (1161)"},
{value: 1162, text: "Vitamin C, total ascorbic acid [mg] (1162)"},
{value: 1165, text: "Thiamin [mg] (1165)"},
{value: 1166, text: "Riboflavin [mg] (1166)"},
{value: 1167, text: "Niacin [mg] (1167)"},
{value: 1170, text: "Pantothenic acid [mg] (1170)"},
{value: 1175, text: "Vitamin B-6 [mg] (1175)"},
{value: 1176, text: "Biotin [µg] (1176)"},
{value: 1177, text: "Folate, total [µg] (1177)"},
{value: 1178, text: "Vitamin B-12 [µg] (1178)"},
{value: 1180, text: "Choline, total [mg] (1180)"},
{value: 1183, text: "Vitamin K (Menaquinone-4) [µg] (1183)"},
{value: 1184, text: "Vitamin K (Dihydrophylloquinone) [µg] (1184)"},
{value: 1185, text: "Vitamin K (phylloquinone) [µg] (1185)"},
{value: 1188, text: "5-methyl tetrahydrofolate (5-MTHF) [µg] (1188)"},
{value: 1191, text: "10-Formyl folic acid (10HCOFA) [µg] (1191)"},
{value: 1192, text: "5-Formyltetrahydrofolic acid (5-HCOH4 [µg] (1192)"},
{value: 1194, text: "Choline, free [mg] (1194)"},
{value: 1195, text: "Choline, from phosphocholine [mg] (1195)"},
{value: 1196, text: "Choline, from phosphotidyl choline [mg] (1196)"},
{value: 1197, text: "Choline, from glycerophosphocholine [mg] (1197)"},
{value: 1198, text: "Betaine [mg] (1198)"},
{value: 1199, text: "Choline, from sphingomyelin [mg] (1199)"},
{value: 1210, text: "Tryptophan [g] (1210)"},
{value: 1211, text: "Threonine [g] (1211)"},
{value: 1212, text: "Isoleucine [g] (1212)"},
{value: 1213, text: "Leucine [g] (1213)"},
{value: 1214, text: "Lysine [g] (1214)"},
{value: 1215, text: "Methionine [g] (1215)"},
{value: 1216, text: "Cystine [g] (1216)"},
{value: 1217, text: "Phenylalanine [g] (1217)"},
{value: 1218, text: "Tyrosine [g] (1218)"},
{value: 1219, text: "Valine [g] (1219)"},
{value: 1220, text: "Arginine [g] (1220)"},
{value: 1221, text: "Histidine [g] (1221)"},
{value: 1222, text: "Alanine [g] (1222)"},
{value: 1223, text: "Aspartic acid [g] (1223)"},
{value: 1224, text: "Glutamic acid [g] (1224)"},
{value: 1225, text: "Glycine [g] (1225)"},
{value: 1226, text: "Proline [g] (1226)"},
{value: 1227, text: "Serine [g] (1227)"},
{value: 1228, text: "Hydroxyproline [g] (1228)"},
{value: 1232, text: "Cysteine [g] (1232)"},
{value: 1253, text: "Cholesterol [mg] (1253)"},
{value: 1257, text: "Fatty acids, total trans [g] (1257)"},
{value: 1258, text: "Fatty acids, total saturated [g] (1258)"},
{value: 1259, text: "SFA 4:0 [g] (1259)"},
{value: 1260, text: "SFA 6:0 [g] (1260)"},
{value: 1261, text: "SFA 8:0 [g] (1261)"},
{value: 1262, text: "SFA 10:0 [g] (1262)"},
{value: 1263, text: "SFA 12:0 [g] (1263)"},
{value: 1264, text: "SFA 14:0 [g] (1264)"},
{value: 1265, text: "SFA 16:0 [g] (1265)"},
{value: 1266, text: "SFA 18:0 [g] (1266)"},
{value: 1267, text: "SFA 20:0 [g] (1267)"},
{value: 1268, text: "MUFA 18:1 [g] (1268)"},
{value: 1269, text: "PUFA 18:2 [g] (1269)"},
{value: 1270, text: "PUFA 18:3 [g] (1270)"},
{value: 1271, text: "PUFA 20:4 [g] (1271)"},
{value: 1272, text: "PUFA 22:6 n-3 (DHA) [g] (1272)"},
{value: 1273, text: "SFA 22:0 [g] (1273)"},
{value: 1276, text: "PUFA 18:4 [g] (1276)"},
{value: 1277, text: "MUFA 20:1 [g] (1277)"},
{value: 1278, text: "PUFA 20:5 n-3 (EPA) [g] (1278)"},
{value: 1279, text: "MUFA 22:1 [g] (1279)"},
{value: 1280, text: "PUFA 22:5 n-3 (DPA) [g] (1280)"},
{value: 1281, text: "TFA 14:1 t [g] (1281)"},
{value: 1284, text: "Ergosterol [mg] (1284)"},
{value: 1285, text: "Stigmasterol [mg] (1285)"},
{value: 1286, text: "Campesterol [mg] (1286)"},
{value: 1287, text: "Brassicasterol [mg] (1287)"},
{value: 1288, text: "Beta-sitosterol [mg] (1288)"},
{value: 1289, text: "Campestanol [mg] (1289)"},
{value: 1292, text: "Fatty acids, total monounsaturated [g] (1292)"},
{value: 1293, text: "Fatty acids, total polyunsaturated [g] (1293)"},
{value: 1294, text: "Beta-sitostanol [mg] (1294)"},
{value: 1296, text: "Delta-5-avenasterol [mg] (1296)"},
{value: 1298, text: "Phytosterols, other [mg] (1298)"},
{value: 1299, text: "SFA 15:0 [g] (1299)"},
{value: 1300, text: "SFA 17:0 [g] (1300)"},
{value: 1301, text: "SFA 24:0 [g] (1301)"},
{value: 1303, text: "TFA 16:1 t [g] (1303)"},
{value: 1304, text: "TFA 18:1 t [g] (1304)"},
{value: 1305, text: "TFA 22:1 t [g] (1305)"},
{value: 1306, text: "TFA 18:2 t not further defined [g] (1306)"},
{value: 1311, text: "PUFA 18:2 CLAs [g] (1311)"},
{value: 1312, text: "MUFA 24:1 c [g] (1312)"},
{value: 1313, text: "PUFA 20:2 n-6 c,c [g] (1313)"},
{value: 1314, text: "MUFA 16:1 c [g] (1314)"},
{value: 1315, text: "MUFA 18:1 c [g] (1315)"},
{value: 1316, text: "PUFA 18:2 n-6 c,c [g] (1316)"},
{value: 1317, text: "MUFA 22:1 c [g] (1317)"},
{value: 1321, text: "PUFA 18:3 n-6 c,c,c [g] (1321)"},
{value: 1323, text: "MUFA 17:1 [g] (1323)"},
{value: 1325, text: "PUFA 20:3 [g] (1325)"},
{value: 1329, text: "Fatty acids, total trans-monoenoic [g] (1329)"},
{value: 1330, text: "Fatty acids, total trans-dienoic [g] (1330)"},
{value: 1331, text: "Fatty acids, total trans-polyenoic [g] (1331)"},
{value: 1333, text: "MUFA 15:1 [g] (1333)"},
{value: 1334, text: "PUFA 22:2 [g] (1334)"},
{value: 1335, text: "SFA 11:0 [g] (1335)"},
{value: 1340, text: "Daidzein [mg] (1340)"},
{value: 1341, text: "Genistein [mg] (1341)"},
{value: 1404, text: "PUFA 18:3 n-3 c,c,c (ALA) [g] (1404)"},
{value: 1405, text: "PUFA 20:3 n-3 [g] (1405)"},
{value: 1406, text: "PUFA 20:3 n-6 [g] (1406)"},
{value: 1409, text: "PUFA 18:3i [g] (1409)"},
{value: 1411, text: "PUFA 22:4 [g] (1411)"},
{value: 1414, text: "PUFA 20:3 n-9 [g] (1414)"},
{value: 2000, text: "Sugars, total including NLEA [g] (2000)"},
{value: 2003, text: "SFA 5:0 [g] (2003)"},
{value: 2004, text: "SFA 7:0 [g] (2004)"},
{value: 2005, text: "SFA 9:0 [g] (2005)"},
{value: 2006, text: "SFA 21:0 [g] (2006)"},
{value: 2007, text: "SFA 23:0 [g] (2007)"},
{value: 2008, text: "MUFA 12:1 [g] (2008)"},
{value: 2009, text: "MUFA 14:1 c [g] (2009)"},
{value: 2010, text: "MUFA 17:1 c [g] (2010)"},
{value: 2012, text: "MUFA 20:1 c [g] (2012)"},
{value: 2013, text: "TFA 20:1 t [g] (2013)"},
{value: 2014, text: "MUFA 22:1 n-9 [g] (2014)"},
{value: 2015, text: "MUFA 22:1 n-11 [g] (2015)"},
{value: 2016, text: "PUFA 18:2 c [g] (2016)"},
{value: 2017, text: "TFA 18:2 t [g] (2017)"},
{value: 2018, text: "PUFA 18:3 c [g] (2018)"},
{value: 2019, text: "TFA 18:3 t [g] (2019)"},
{value: 2020, text: "PUFA 20:3 c [g] (2020)"},
{value: 2021, text: "PUFA 22:3 [g] (2021)"},
{value: 2022, text: "PUFA 20:4c [g] (2022)"},
{value: 2023, text: "PUFA 20:5c [g] (2023)"},
{value: 2024, text: "PUFA 22:5 c [g] (2024)"},
{value: 2025, text: "PUFA 22:6 c [g] (2025)"},
{value: 2026, text: "PUFA 20:2 c [g] (2026)"},
{value: 2028, text: "trans-beta-Carotene [µg] (2028)"},
{value: 2029, text: "trans-Lycopene [µg] (2029)"},
{value: 2032, text: "Cryptoxanthin, alpha [µg] (2032)"},
{value: 2033, text: "Total dietary fiber (AOAC 2011.25) [g] (2033)"},
{value: 2038, text: "High Molecular Weight Dietary Fiber (HMWDF) [g] (2038)"},
{value: 2047, text: "Energy (Atwater General Factors) [kcal] (2047)"},
{value: 2048, text: "Energy (Atwater Specific Factors) [kcal] (2048)"},
{value: 2049, text: "Daidzin [mg] (2049)"},
{value: 2050, text: "Genistin [mg] (2050)"},
{value: 2051, text: "Glycitin [mg] (2051)"},
{value: 2052, text: "Delta-7-Stigmastenol [mg] (2052)"},
{value: 2053, text: "Stigmastadiene [mg] (2053)"},
{value: 2057, text: "Ergothioneine [mg] (2057)"},
{value: 2058, text: "Beta-glucan [g] (2058)"},
{value: 2059, text: "Vitamin D4 [µg] (2059)"},
{value: 2060, text: "Ergosta-7-enol [mg] (2060)"},
{value: 2061, text: " Ergosta-7,22-dienol [mg] (2061)"},
{value: 2062, text: " Ergosta-5,7-dienol [mg] (2062)"},
{value: 2063, text: "Verbascose [g] (2063)"},
{value: 2065, text: "Low Molecular Weight Dietary Fiber (LMWDF) [g] (2065)"},
{value: 2066, text: "Vitamin A [mg] (2066)"},
{value: 2069, text: "Glutathione [mg] (2069)"}
],
field: "fdc_id",
label: "FDC_ID",
help_text: "FDC_ID_help",
@ -1020,7 +1249,7 @@ export class Actions {
},
],
},
ok_label: { function: "translate", phrase: "Save" },
ok_label: {function: "translate", phrase: "Save"},
},
}
static UPDATE = {
@ -1055,7 +1284,7 @@ export class Actions {
},
],
},
ok_label: { function: "translate", phrase: "Delete" },
ok_label: {function: "translate", phrase: "Delete"},
instruction: {
form_field: true,
type: "instruction",
@ -1082,17 +1311,17 @@ export class Actions {
suffix: "s",
params: ["query", "page", "pageSize", "options"],
config: {
query: { default: undefined },
page: { default: 1 },
pageSize: { default: 25 },
query: {default: undefined},
page: {default: 1},
pageSize: {default: 25},
},
}
static MERGE = {
function: "merge",
params: ["source", "target"],
config: {
source: { type: "string" },
target: { type: "string" },
source: {type: "string"},
target: {type: "string"},
},
form: {
title: {
@ -1107,7 +1336,7 @@ export class Actions {
},
],
},
ok_label: { function: "translate", phrase: "Merge" },
ok_label: {function: "translate", phrase: "Merge"},
instruction: {
form_field: true,
type: "instruction",
@ -1141,8 +1370,8 @@ export class Actions {
function: "move",
params: ["source", "target"],
config: {
source: { type: "string" },
target: { type: "string" },
source: {type: "string"},
target: {type: "string"},
},
form: {
title: {
@ -1157,7 +1386,7 @@ export class Actions {
},
],
},
ok_label: { function: "translate", phrase: "Move" },
ok_label: {function: "translate", phrase: "Move"},
instruction: {
form_field: true,
type: "instruction",

File diff suppressed because it is too large Load Diff

View File

@ -50,6 +50,7 @@ export class StandardToasts {
static FAIL_DELETE_PROTECTED = "FAIL_DELETE_PROTECTED"
static FAIL_MOVE = "FAIL_MOVE"
static FAIL_MERGE = "FAIL_MERGE"
static FAIL_IMPORT = "FAIL_IMPORT"
static makeStandardToast(context, toast, err = undefined, always_show_errors = false) {
let title = ''
@ -122,6 +123,11 @@ export class StandardToasts {
title = i18n.tc("Failure")
msg = i18n.tc("err_merging_resource")
break
case StandardToasts.FAIL_IMPORT:
variant = 'danger'
title = i18n.tc("Failure")
msg = i18n.tc("err_importing_recipe")
break
}
@ -131,12 +137,19 @@ export class StandardToasts {
console.trace();
}
if (err !== undefined && 'response' in err && 'headers' in err.response) {
if (DEBUG && err.response.headers['content-type'] === 'application/json' && err.response.status < 500) {
if (err !== undefined
&& 'response' in err
&& 'headers' in err.response
&& err.response.headers['content-type'] === 'application/json'
&& err.response.status < 500
&& err.response.data) {
// If the backend provides us with a nice error message, we print it, regardless of DEBUG mode
if (DEBUG || err.response.data.msg) {
const errMsg = err.response.data.msg ? err.response.data.msg : JSON.stringify(err.response.data)
msg = context.$createElement('div', {}, [
context.$createElement('span', {}, [msg]),
context.$createElement('br', {}, []),
context.$createElement('code', {'class': 'mt-2'}, [JSON.stringify(err.response.data)])
context.$createElement('code', {'class': 'mt-2'}, [errMsg])
])
}
}

View File

@ -53,6 +53,10 @@ const pages = {
entry: "./src/apps/IngredientEditorView/main.js",
chunks: ["chunk-vendors","locales-chunk","api-chunk"],
},
property_editor_view: {
entry: "./src/apps/PropertyEditorView/main.js",
chunks: ["chunk-vendors","locales-chunk","api-chunk"],
},
shopping_list_view: {
entry: "./src/apps/ShoppingListView/main.js",
chunks: ["chunk-vendors","locales-chunk","api-chunk"],
@ -137,7 +141,7 @@ module.exports = {
config.optimization.minimize(true)
//TODO somehow remov them as they are also added to the manifest config of the service worker
//TODO somehow remove them as they are also added to the manifest config of the service worker
/*
Object.keys(pages).forEach(page => {
config.plugins.delete(`html-${page}`);