Merge branch 'develop' into remove_facets

This commit is contained in:
vabene1111 2023-09-12 15:36:14 +02:00 committed by GitHub
commit 3f63eab68c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 437 additions and 478 deletions

View File

@ -277,7 +277,7 @@ admin.site.register(RecipeBookEntry, RecipeBookEntryAdmin)
class MealPlanAdmin(admin.ModelAdmin): class MealPlanAdmin(admin.ModelAdmin):
list_display = ('user', 'recipe', 'meal_type', 'date') list_display = ('user', 'recipe', 'meal_type', 'from_date', 'to_date')
@staticmethod @staticmethod
def user(obj): def user(obj):

View File

@ -323,49 +323,6 @@ class ImportRecipeForm(forms.ModelForm):
} }
# TODO deprecate
class MealPlanForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
space = kwargs.pop('space')
super().__init__(*args, **kwargs)
self.fields['recipe'].queryset = Recipe.objects.filter(space=space).all()
self.fields['meal_type'].queryset = MealType.objects.filter(space=space).all()
self.fields['shared'].queryset = User.objects.filter(userpreference__space=space).all()
def clean(self):
cleaned_data = super(MealPlanForm, self).clean()
if cleaned_data['title'] == '' and cleaned_data['recipe'] is None:
raise forms.ValidationError(
_('You must provide at least a recipe or a title.')
)
return cleaned_data
class Meta:
model = MealPlan
fields = (
'recipe', 'title', 'meal_type', 'note',
'servings', 'date', 'shared'
)
help_texts = {
'shared': _('You can list default users to share recipes with in the settings.'),
'note': _('You can use markdown to format this field. See the <a href="/docs/markdown/">docs here</a>')
}
widgets = {
'recipe': SelectWidget,
'date': DateWidget,
'shared': MultiSelectWidget
}
field_classes = {
'recipe': SafeModelChoiceField,
'meal_type': SafeModelChoiceField,
'shared': SafeModelMultipleChoiceField,
}
class InviteLinkForm(forms.ModelForm): class InviteLinkForm(forms.ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
user = kwargs.pop('user') user = kwargs.pop('user')

View File

@ -0,0 +1,36 @@
# Generated by Django 4.1.10 on 2023-09-08 12:20
from django.db import migrations, models
from django.db.models import F
from django_scopes import scopes_disabled
def apply_migration(apps, schema_editor):
with scopes_disabled():
MealPlan = apps.get_model('cookbook', 'MealPlan')
MealPlan.objects.update(to_date=F('from_date'))
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0200_alter_propertytype_options_remove_keyword_icon_and_more'),
]
operations = [
migrations.RenameField(
model_name='mealplan',
old_name='date',
new_name='from_date',
),
migrations.AddField(
model_name='mealplan',
name='to_date',
field=models.DateField(blank=True, null=True),
),
migrations.RunPython(apply_migration),
migrations.AlterField(
model_name='mealplan',
name='to_date',
field=models.DateField(),
),
]

View File

@ -991,7 +991,8 @@ class MealPlan(ExportModelOperationsMixin('meal_plan'), models.Model, Permission
shared = models.ManyToManyField(User, blank=True, related_name='plan_share') shared = models.ManyToManyField(User, blank=True, related_name='plan_share')
meal_type = models.ForeignKey(MealType, on_delete=models.CASCADE) meal_type = models.ForeignKey(MealType, on_delete=models.CASCADE)
note = models.TextField(blank=True) note = models.TextField(blank=True)
date = models.DateField() from_date = models.DateField()
to_date = models.DateField()
space = models.ForeignKey(Space, on_delete=models.CASCADE) space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space') objects = ScopedManager(space='space')
@ -1005,7 +1006,7 @@ class MealPlan(ExportModelOperationsMixin('meal_plan'), models.Model, Permission
return self.meal_type.name return self.meal_type.name
def __str__(self): def __str__(self):
return f'{self.get_label()} - {self.date} - {self.meal_type.name}' return f'{self.get_label()} - {self.from_date} - {self.meal_type.name}'
class ShoppingListRecipe(ExportModelOperationsMixin('shopping_list_recipe'), models.Model, PermissionModelMixin): class ShoppingListRecipe(ExportModelOperationsMixin('shopping_list_recipe'), models.Model, PermissionModelMixin):

View File

@ -993,7 +993,7 @@ class MealPlanSerializer(SpacedModelSerializer, WritableNestedModelSerializer):
model = MealPlan model = MealPlan
fields = ( fields = (
'id', 'title', 'recipe', 'servings', 'note', 'note_markdown', 'id', 'title', 'recipe', 'servings', 'note', 'note_markdown',
'date', 'meal_type', 'created_by', 'shared', 'recipe_name', 'from_date', 'to_date', 'meal_type', 'created_by', 'shared', 'recipe_name',
'meal_type_name', 'shopping' 'meal_type_name', 'shopping'
) )
read_only_fields = ('created_by',) read_only_fields = ('created_by',)

View File

@ -12,6 +12,7 @@ from cookbook.tests.factories import RecipeFactory
LIST_URL = 'api:mealplan-list' LIST_URL = 'api:mealplan-list'
DETAIL_URL = 'api:mealplan-detail' DETAIL_URL = 'api:mealplan-detail'
# NOTE: auto adding shopping list from meal plan is tested in test_shopping_recipe as tests are identical # NOTE: auto adding shopping list from meal plan is tested in test_shopping_recipe as tests are identical
@ -22,13 +23,13 @@ def meal_type(space_1, u1_s1):
@pytest.fixture() @pytest.fixture()
def obj_1(space_1, recipe_1_s1, meal_type, u1_s1): def obj_1(space_1, recipe_1_s1, meal_type, u1_s1):
return MealPlan.objects.create(recipe=recipe_1_s1, space=space_1, meal_type=meal_type, date=datetime.now(), return MealPlan.objects.create(recipe=recipe_1_s1, space=space_1, meal_type=meal_type, from_date=datetime.now(), to_date=datetime.now(),
created_by=auth.get_user(u1_s1)) created_by=auth.get_user(u1_s1))
@pytest.fixture @pytest.fixture
def obj_2(space_1, recipe_1_s1, meal_type, u1_s1): def obj_2(space_1, recipe_1_s1, meal_type, u1_s1):
return MealPlan.objects.create(recipe=recipe_1_s1, space=space_1, meal_type=meal_type, date=datetime.now(), return MealPlan.objects.create(recipe=recipe_1_s1, space=space_1, meal_type=meal_type, from_date=datetime.now(), to_date=datetime.now(),
created_by=auth.get_user(u1_s1)) created_by=auth.get_user(u1_s1))
@ -109,7 +110,7 @@ def test_add(arg, request, u1_s2, recipe_1_s1, meal_type):
r = c.post( r = c.post(
reverse(LIST_URL), reverse(LIST_URL),
{'recipe': {'id': recipe_1_s1.id, 'name': recipe_1_s1.name, 'keywords': []}, 'meal_type': {'id': meal_type.id, 'name': meal_type.name}, {'recipe': {'id': recipe_1_s1.id, 'name': recipe_1_s1.name, 'keywords': []}, 'meal_type': {'id': meal_type.id, 'name': meal_type.name},
'date': (datetime.now()).strftime("%Y-%m-%d"), 'servings': 1, 'title': 'test', 'shared': []}, 'from_date': (datetime.now()).strftime("%Y-%m-%d"), 'to_date': (datetime.now()).strftime("%Y-%m-%d"), 'servings': 1, 'title': 'test', 'shared': []},
content_type='application/json' content_type='application/json'
) )
response = json.loads(r.content) response = json.loads(r.content)
@ -151,7 +152,7 @@ def test_add_with_shopping(u1_s1, meal_type):
r = u1_s1.post( r = u1_s1.post(
reverse(LIST_URL), reverse(LIST_URL),
{'recipe': {'id': recipe.id, 'name': recipe.name, 'keywords': []}, 'meal_type': {'id': meal_type.id, 'name': meal_type.name}, {'recipe': {'id': recipe.id, 'name': recipe.name, 'keywords': []}, 'meal_type': {'id': meal_type.id, 'name': meal_type.name},
'date': (datetime.now()).strftime("%Y-%m-%d"), 'servings': 1, 'title': 'test', 'shared': [], 'addshopping': True}, 'from_date': (datetime.now()).strftime("%Y-%m-%d"), 'to_date': (datetime.now()).strftime("%Y-%m-%d"), 'servings': 1, 'title': 'test', 'shared': [], 'addshopping': True},
content_type='application/json' content_type='application/json'
) )

View File

@ -246,7 +246,8 @@ class MealPlanFactory(factory.django.DjangoModelFactory):
meal_type = factory.SubFactory( meal_type = factory.SubFactory(
MealTypeFactory, space=factory.SelfAttribute('..space')) MealTypeFactory, space=factory.SelfAttribute('..space'))
note = factory.LazyAttribute(lambda x: faker.paragraph()) note = factory.LazyAttribute(lambda x: faker.paragraph())
date = factory.LazyAttribute(lambda x: faker.future_date()) from_date = factory.LazyAttribute(lambda x: faker.future_date())
to_date = factory.LazyAttribute(lambda x: faker.future_date())
space = factory.SubFactory(SpaceFactory) space = factory.SubFactory(SpaceFactory)
class Params: class Params:

View File

@ -166,7 +166,7 @@ urlpatterns = [
] ]
generic_models = ( generic_models = (
Recipe, RecipeImport, Storage, RecipeBook, MealPlan, SyncLog, Sync, Recipe, RecipeImport, Storage, RecipeBook, SyncLog, Sync,
Comment, RecipeBookEntry, ShoppingList, InviteLink, UserSpace, Space Comment, RecipeBookEntry, ShoppingList, InviteLink, UserSpace, Space
) )

View File

@ -666,11 +666,11 @@ class MealPlanViewSet(viewsets.ModelViewSet):
from_date = self.request.query_params.get('from_date', None) from_date = self.request.query_params.get('from_date', None)
if from_date is not None: if from_date is not None:
queryset = queryset.filter(date__gte=from_date) queryset = queryset.filter(to_date__gte=from_date)
to_date = self.request.query_params.get('to_date', None) to_date = self.request.query_params.get('to_date', None)
if to_date is not None: if to_date is not None:
queryset = queryset.filter(date__lte=to_date) queryset = queryset.filter(to_date__lte=to_date)
return queryset return queryset
@ -707,7 +707,7 @@ class AutoPlanViewSet(viewsets.ViewSet):
args = {'recipe_id': recipe['id'], 'servings': servings, args = {'recipe_id': recipe['id'], 'servings': servings,
'created_by': request.user, 'created_by': request.user,
'meal_type_id': serializer.validated_data['meal_type_id'], 'meal_type_id': serializer.validated_data['meal_type_id'],
'note': '', 'date': day, 'space': request.space} 'note': '', 'from_date': day, 'to_date': day, 'space': request.space}
m = MealPlan(**args) m = MealPlan(**args)
meal_plans.append(m) meal_plans.append(m)
@ -1633,8 +1633,11 @@ def get_plan_ical(request, from_date, to_date):
for p in queryset: for p in queryset:
event = Event() event = Event()
event['uid'] = p.id event['uid'] = p.id
event.add('dtstart', p.date) event.add('dtstart', p.from_date)
event.add('dtend', p.date) if p.to_date:
event.add('dtend', p.to_date)
else:
event.add('dtend', p.from_date)
event['summary'] = f'{p.meal_type.name}: {p.get_label()}' event['summary'] = f'{p.meal_type.name}: {p.get_label()}'
event['description'] = p.note event['description'] = p.note
cal.add_component(event) cal.add_component(event)

View File

@ -8,7 +8,7 @@ from django.utils.translation import gettext as _
from django.views.generic import UpdateView from django.views.generic import UpdateView
from django.views.generic.edit import FormMixin from django.views.generic.edit import FormMixin
from cookbook.forms import CommentForm, ExternalRecipeForm, MealPlanForm, StorageForm, SyncForm from cookbook.forms import CommentForm, ExternalRecipeForm, StorageForm, SyncForm
from cookbook.helper.permission_helper import GroupRequiredMixin, OwnerRequiredMixin, group_required, above_space_limit from cookbook.helper.permission_helper import GroupRequiredMixin, OwnerRequiredMixin, group_required, above_space_limit
from cookbook.models import (Comment, MealPlan, MealType, Recipe, RecipeImport, Storage, Sync, from cookbook.models import (Comment, MealPlan, MealType, Recipe, RecipeImport, Storage, Sync,
UserPreference) UserPreference)
@ -192,26 +192,6 @@ class ImportUpdate(GroupRequiredMixin, UpdateView):
return context return context
class MealPlanUpdate(OwnerRequiredMixin, UpdateView, SpaceFormMixing):
template_name = "generic/edit_template.html"
model = MealPlan
form_class = MealPlanForm
def get_success_url(self):
return reverse('view_plan_entry', kwargs={'pk': self.object.pk})
def get_form(self, form_class=None):
form = self.form_class(**self.get_form_kwargs())
form.fields['meal_type'].queryset = MealType.objects \
.filter(created_by=self.request.user).all()
return form
def get_context_data(self, **kwargs):
context = super(MealPlanUpdate, self).get_context_data(**kwargs)
context['title'] = _("Meal-Plan")
return context
class ExternalRecipeUpdate(GroupRequiredMixin, UpdateView, SpaceFormMixing): class ExternalRecipeUpdate(GroupRequiredMixin, UpdateView, SpaceFormMixing):
groups_required = ['user'] groups_required = ['user']
model = Recipe model = Recipe

View File

@ -12,7 +12,7 @@ from django.urls import reverse, reverse_lazy
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.views.generic import CreateView from django.views.generic import CreateView
from cookbook.forms import ImportRecipeForm, InviteLinkForm, MealPlanForm, Storage, StorageForm from cookbook.forms import ImportRecipeForm, InviteLinkForm, Storage, StorageForm
from cookbook.helper.permission_helper import GroupRequiredMixin, group_required, above_space_limit from cookbook.helper.permission_helper import GroupRequiredMixin, group_required, above_space_limit
from cookbook.models import (InviteLink, MealPlan, MealType, Recipe, RecipeBook, RecipeImport, from cookbook.models import (InviteLink, MealPlan, MealType, Recipe, RecipeBook, RecipeImport,
ShareLink, Step, UserPreference, UserSpace) ShareLink, Step, UserPreference, UserSpace)
@ -135,55 +135,3 @@ def create_new_external_recipe(request, import_id):
return render(request, 'forms/edit_import_recipe.html', {'form': form}) return render(request, 'forms/edit_import_recipe.html', {'form': form})
class MealPlanCreate(GroupRequiredMixin, CreateView, SpaceFormMixing):
groups_required = ['user']
template_name = "generic/new_template.html"
model = MealPlan
form_class = MealPlanForm
success_url = reverse_lazy('view_plan')
def get_form(self, form_class=None):
form = self.form_class(**self.get_form_kwargs())
form.fields['meal_type'].queryset = MealType.objects.filter(created_by=self.request.user,
space=self.request.space).all()
return form
def get_initial(self):
return dict(
meal_type=(
self.request.GET['meal']
if 'meal' in self.request.GET
else None
),
date=(
datetime.strptime(self.request.GET['date'], '%Y-%m-%d')
if 'date' in self.request.GET
else None
),
shared=(
self.request.user.userpreference.plan_share.all()
if self.request.user.userpreference.plan_share
else None
)
)
def form_valid(self, form):
obj = form.save(commit=False)
obj.created_by = self.request.user
obj.space = self.request.space
obj.save()
return HttpResponseRedirect(reverse('view_plan'))
def get_context_data(self, **kwargs):
context = super(MealPlanCreate, self).get_context_data(**kwargs)
context['title'] = _("Meal-Plan")
recipe = self.request.GET.get('recipe')
if recipe:
if re.match(r'^([0-9])+$', recipe):
if Recipe.objects.filter(pk=int(recipe), space=self.request.space).exists():
context['default_recipe'] = Recipe.objects.get(pk=int(recipe), space=self.request.space)
return context

View File

@ -1,219 +1,165 @@
<template> <template>
<div> <div>
<b-tabs content-class="mt-3" v-model="current_tab"> <div class="d-none d-lg-block">
<b-tab :title="$t('Planner')" active> <div class="row ">
<div class=" d-none d-lg-block"> <div class="col col-2">
<div class="row"> <h4>{{ $t('Meal_Types') }}</h4>
<div class="col col-2">
<h4>{{ $t('Meal_Types') }}</h4>
<b-form-checkbox :button-variant="danger" v-model="mt.checked" size="lg" v-for="mt in meal_types" v-bind:key="mt.id"> <div class="d-flex flex-row align-items-center border" v-for="mt in meal_types" v-bind:key="mt.id">
<div class="flex-column" style="width: 2.5rem; height: 2.5rem;" :style="{'background-color': mt.color}"></div>
<b-badge variant="primary" :style="{'background-color':mt.color}">{{ mt.name }}</b-badge> <div class="flex-column flex-grow-1 align-middle justify-content-center">
</b-form-checkbox> <div class="card-body p-2 align-middle">
{{ mt.name }}
<hr/>
<button class="btn btn-success shadow-none mt-1 btn-block" @click="createEntryClick(new Date())"><i
class="fas fa-calendar-plus"></i> {{ $t("Create") }}
</button>
<button class="btn btn-primary shadow-none mt-1 btn-block" @click="createAutoPlan(new Date())"><i
class="fas fa-calendar-plus"></i> {{ $t("Auto_Planner") }}
</button>
<a class="btn btn-primary shadow-none mt-1 btn-blockmt-1 btn-block" :href="iCalUrl"><i class="fas fa-download"></i>
{{ $t("Export_To_ICal") }}
</a>
</div>
<div class="col col-10">
<div class="row calender-row ">
<div class="col-12 calender-parent">
<calendar-view
:show-date="showDate"
:enable-date-selection="true"
class="theme-default"
:items="plan_items"
:display-period-uom="settings.displayPeriodUom"
:period-changed-callback="periodChangedCallback"
:enable-drag-drop="true"
:item-content-height="item_height"
@click-date="createEntryClick"
@drop-on-date="moveEntry"
:display-period-count="settings.displayPeriodCount"
:starting-day-of-week="settings.startingDayOfWeek"
:display-week-numbers="settings.displayWeekNumbers"
>
<template #item="{ value, weekStartDate, top }">
<meal-plan-card
:value="value"
:week-start-date="weekStartDate"
:top="top"
:detailed="detailed_items"
:item_height="item_height"
@dragstart="dragged_item = value"
@click-item="entryClick"
@open-context-menu="openContextMenu"
/>
</template>
<template #header="{ headerProps }">
<meal-plan-calender-header
ref="header"
:header-props="headerProps"
@input="setShowDate"
@delete-dragged="deleteEntry(dragged_item)"
@create-new="createEntryClick(new Date())"
@set-starting-day-back="setStartingDay(-1)"
@set-starting-day-forward="setStartingDay(1)"
:i-cal-url="iCalUrl"
:options="options"
:settings_prop="settings"
/>
</template>
</calendar-view>
</div>
</div> </div>
</div> </div>
</div> </div>
<div class="row">
<div class="col">
<div class="row d-block d-lg-none">
<div>
<div class="col-12">
<div class="col-12 d-flex justify-content-center mt-2">
<b-button-toolbar key-nav aria-label="Toolbar with button groups">
<b-button-group class="mx-1">
<b-button v-html="'<<'" class="p-2 pr-3 pl-3"
@click="setShowDate($refs.header.headerProps.previousPeriod)"></b-button>
</b-button-group>
<b-button-group class="mx-1">
<b-button @click="setShowDate($refs.header.headerProps.currentPeriod)"><i
class="fas fa-home"></i></b-button>
<b-form-datepicker right button-only button-variant="secondary" @context="datePickerChanged"></b-form-datepicker>
</b-button-group>
<b-button-group class="mx-1">
<b-button v-html="'>>'" class="p-2 pr-3 pl-3"
@click="setShowDate($refs.header.headerProps.nextPeriod)"></b-button>
</b-button-group>
</b-button-toolbar>
</div>
</div>
<div class="col-12 mt-2" style="padding-bottom: 60px">
<div v-for="day in mobileSimpleGrid" v-bind:key="day.day">
<b-list-group>
<b-list-group-item>
<div class="d-flex flex-row align-middle">
<h6 class="mb-0 mt-1 align-middle">{{ day.date_label }}</h6>
<div class="flex-grow-1 text-right"> <hr/>
<b-button class="btn-sm btn-outline-primary" @click="showMealPlanEditModal(null, day.create_default_date)"><i <button class="btn btn-success shadow-none mt-1 btn-block" @click="createEntryClick(new Date())"><i
class="fa fa-plus"></i></b-button> class="fas fa-calendar-plus"></i> {{ $t("Create") }}
</div> </button>
</div> <button class="btn btn-warning shadow-none mt-1 btn-block" @click="createAutoPlan(new Date())"><i
class="fas fa-calendar-plus"></i> {{ $t("Auto_Planner") }}
</button>
<a class="btn btn-primary shadow-none mt-1 btn-blockmt-1 btn-block" :href="iCalUrl"><i class="fas fa-download"></i>
{{ $t("Export_To_ICal") }}
</a>
</b-list-group-item> <a class="btn btn-info shadow-none mt-1 btn-block" :href="resolveDjangoUrl('view_settings')">
<b-list-group-item v-for="plan in day.plan_entries" v-bind:key="plan.entry.id"> <i class="fas fa-cogs"></i> {{ $t("Settings") }}
<div class="d-flex flex-row align-items-center"> </a>
<div> </div>
<b-img style="height: 50px; width: 50px; object-fit: cover" <div class="col col-10">
:src="plan.entry.recipe.image" rounded="circle" v-if="plan.entry.recipe?.image"></b-img> <div class="row calender-row ">
<b-img style="height: 50px; width: 50px; object-fit: cover" <div class="col-12 calender-parent">
:src="image_placeholder" rounded="circle" v-else></b-img> <calendar-view
</div> :show-date="showDate"
<div class="flex-grow-1 ml-2" :enable-date-selection="true"
style="text-overflow: ellipsis; overflow-wrap: anywhere;"> class="theme-default"
:items="plan_items"
:display-period-uom="settings.displayPeriodUom"
:period-changed-callback="periodChangedCallback"
:enable-drag-drop="true"
:item-content-height="item_height"
@click-date="createEntryClick"
@drop-on-date="moveEntry"
:display-period-count="settings.displayPeriodCount"
:starting-day-of-week="settings.startingDayOfWeek"
:display-week-numbers="settings.displayWeekNumbers"
>
<template #item="{ value, weekStartDate, top }">
<meal-plan-card
:value="value"
:week-start-date="weekStartDate"
:top="top"
:detailed="detailed_items"
:item_height="item_height"
@dragstart="dragged_item = value"
@click-item="entryClick"
@open-context-menu="openContextMenu"
/>
</template>
<template #header="{ headerProps }">
<meal-plan-calender-header
ref="header"
:header-props="headerProps"
@input="setShowDate"
@delete-dragged="deleteEntry(dragged_item)"
@create-new="createEntryClick(new Date())"
@set-starting-day-back="setStartingDay(-1)"
@set-starting-day-forward="setStartingDay(1)"
:i-cal-url="iCalUrl"
:options="options"
:settings_prop="settings"
/>
</template>
</calendar-view>
</div>
</div>
</div>
</div>
</div>
<div class="row d-block d-lg-none">
<div class="row">
<div class="col">
<div class="">
<div>
<div class="col-12">
<div class="col-12 d-flex justify-content-center mt-2">
<b-button-toolbar key-nav aria-label="Toolbar with button groups">
<b-button-group class="mx-1">
<b-button v-html="'<<'" class="p-2 pr-3 pl-3"
@click="setShowDate($refs.header.headerProps.previousPeriod)"></b-button>
</b-button-group>
<b-button-group class="mx-1">
<b-button @click="setShowDate($refs.header.headerProps.currentPeriod)"><i
class="fas fa-home"></i></b-button>
<b-form-datepicker right button-only button-variant="secondary" @context="datePickerChanged"></b-form-datepicker>
</b-button-group>
<b-button-group class="mx-1">
<b-button v-html="'>>'" class="p-2 pr-3 pl-3"
@click="setShowDate($refs.header.headerProps.nextPeriod)"></b-button>
</b-button-group>
</b-button-toolbar>
</div>
</div>
<div class="col-12 mt-2" style="padding-bottom: 60px">
<div v-for="day in mobileSimpleGrid" v-bind:key="day.day">
<b-list-group>
<b-list-group-item>
<div class="d-flex flex-row align-middle">
<h6 class="mb-0 mt-1 align-middle">{{ day.date_label }}</h6>
<div class="flex-grow-1 text-right">
<b-button class="btn-sm btn-outline-primary" @click="showMealPlanEditModal(null, day.create_default_date)"><i
class="fa fa-plus"></i></b-button>
</div>
</div>
</b-list-group-item>
<b-list-group-item v-for="plan in day.plan_entries" v-bind:key="plan.entry.id">
<div class="d-flex flex-row align-items-center">
<div>
<b-img style="height: 50px; width: 50px; object-fit: cover"
:src="plan.entry.recipe.image" rounded="circle" v-if="plan.entry.recipe?.image"></b-img>
<b-img style="height: 50px; width: 50px; object-fit: cover"
:src="image_placeholder" rounded="circle" v-else></b-img>
</div>
<div class="flex-grow-1 ml-2"
style="text-overflow: ellipsis; overflow-wrap: anywhere;">
<span class="two-row-text"> <span class="two-row-text">
<a :href="resolveDjangoUrl('view_recipe', plan.entry.recipe.id)" v-if="plan.entry.recipe">{{ plan.entry.recipe.name }}</a> <a :href="resolveDjangoUrl('view_recipe', plan.entry.recipe.id)" v-if="plan.entry.recipe">{{ plan.entry.recipe.name }}</a>
<span v-else>{{ plan.entry.title }}</span> <br/> <span v-else>{{ plan.entry.title }}</span> <br/>
</span> </span>
<span v-if="plan.entry.note" class="two-row-text"> <span v-if="plan.entry.note" class="two-row-text">
<small>{{ plan.entry.note }}</small> <br/> <small>{{ plan.entry.note }}</small> <br/>
</span> </span>
<small class="text-muted"> <small class="text-muted">
<span v-if="plan.entry.shopping" class="font-light"><i class="fas fa-shopping-cart fa-xs "/></span> <span v-if="plan.entry.shopping" class="font-light"><i class="fas fa-shopping-cart fa-xs "/></span>
{{ plan.entry.meal_type_name }} {{ plan.entry.meal_type_name }}
<span v-if="plan.entry.recipe"> <span v-if="plan.entry.recipe">
- <i class="fa fa-clock"></i> {{ plan.entry.recipe.working_time + plan.entry.recipe.waiting_time }} {{ $t('min') }} - <i class="fa fa-clock"></i> {{ plan.entry.recipe.working_time + plan.entry.recipe.waiting_time }} {{ $t('min') }}
</span> </span>
</small> </small>
</div> </div>
<div class="hover-button"> <div class="hover-button">
<a class="pr-2" @click.stop="openContextMenu($event, {originalItem: plan})"><i class="fas fa-ellipsis-v"></i></a> <a class="pr-2" @click.stop="openContextMenu($event, {originalItem: plan})"><i class="fas fa-ellipsis-v"></i></a>
</div> </div>
</div> </div>
</b-list-group-item> </b-list-group-item>
</b-list-group>
</div>
</div>
</b-list-group>
</div> </div>
</div> </div>
</div>
</div>
</div>
</b-tab>
<b-tab :title="$t('Settings')">
<div class="row mt-3">
<div class="col-12 col-md-9 col-lg-6">
<h5>{{ $t("Meal_Types") }}</h5>
<div>
<draggable :list="meal_types" group="meal_types" :empty-insert-threshold="10"
@sort="sortMealTypes()" ghost-class="ghost">
<b-card no-body class="mt-1 list-group-item p-2" style="cursor: move"
v-for="(meal_type, index) in meal_types" v-hover :key="meal_type.id">
<b-card-header class="p-2 border-0">
<div class="row">
<div class="col-2">
<button type="button" class="btn btn-lg shadow-none"><i
class="fas fa-arrows-alt-v"></i></button>
</div>
<div class="col-10">
<h5 class="mt-1 mb-1">
{{ meal_type.name }}<span class="float-right text-primary" style="cursor: pointer"
><i class="fa"
v-bind:class="{ 'fa-pen': !meal_type.editing, 'fa-save': meal_type.editing }"
@click="editOrSaveMealType(index)" aria-hidden="true"></i
></span>
</h5>
</div>
</div>
</b-card-header>
<b-card-body class="p-4" v-if="meal_type.editing">
<div class="form-group">
<label>{{ $t("Name") }}</label>
<input class="form-control" :placeholder="$t('Name')"
v-model="meal_type.name"/>
</div>
<div class="form-group">
<label>{{ $t("Color") }}</label>
<input class="form-control" type="color" name="Name"
:value="meal_type.color"
@change="meal_type.color = $event.target.value"/>
</div>
<b-form-checkbox id="checkbox-1" v-model="meal_type.default"
name="default_checkbox" class="mb-2">
{{ $t("Default") }}
</b-form-checkbox>
<button class="btn btn-danger" @click="deleteMealType(index)">{{
$t("Delete")
}}
</button>
<button class="btn btn-primary float-right" @click="editOrSaveMealType(index)">
{{ $t("Save") }}
</button>
</b-card-body>
</b-card>
</draggable>
<button class="btn btn-success float-right mt-1" @click="newMealType">
<i class="fas fa-plus"></i>
{{ $t("New_Meal_Type") }}
</button>
</div> </div>
</div> </div>
</div> </div>
</b-tab> </div>
</b-tabs> </div>
<ContextMenu ref="menu"> <ContextMenu ref="menu">
<template #menu="{ contextData }"> <template #menu="{ contextData }">
<ContextMenuItem <ContextMenuItem
@ -336,7 +282,6 @@ export default {
ContextMenu, ContextMenu,
ContextMenuItem, ContextMenuItem,
MealPlanCalenderHeader, MealPlanCalenderHeader,
draggable,
BottomNavigationBar, BottomNavigationBar,
}, },
mixins: [CalendarMathMixin, ApiMixin, ResolveUrlMixin], mixins: [CalendarMathMixin, ApiMixin, ResolveUrlMixin],
@ -362,7 +307,6 @@ export default {
displayWeekNumbers: true, displayWeekNumbers: true,
}, },
dragged_item: null, dragged_item: null,
current_tab: 0,
meal_types: [], meal_types: [],
current_context_menu_item: null, current_context_menu_item: null,
options: { options: {
@ -404,7 +348,7 @@ export default {
}, },
item_height: function () { item_height: function () {
if (this.settings.displayPeriodUom === "week") { if (this.settings.displayPeriodUom === "week") {
return "10rem" return "3rem"
} else { } else {
return "1.6rem" return "1.6rem"
} }
@ -428,7 +372,7 @@ export default {
date: moment_date, date: moment_date,
create_default_date: moment_date.format("YYYY-MM-DD"), // improve meal plan edit modal to do formatting itself and accept dates create_default_date: moment_date.format("YYYY-MM-DD"), // improve meal plan edit modal to do formatting itself and accept dates
date_label: moment_date.format('ddd DD.MM'), date_label: moment_date.format('ddd DD.MM'),
plan_entries: this.plan_items.filter((m) => moment(m.startDate).isSame(moment_date, 'day')) plan_entries: this.plan_items.filter((m) => moment_date.isBetween(moment(m.startDate), moment(m.endDate), 'day', '[]'))
}) })
} }
} }
@ -436,18 +380,14 @@ export default {
} }
}, },
mounted() { mounted() {
this.$nextTick(function () { this.settings = useMealPlanStore().client_settings
if (this.$cookies.isKey(SETTINGS_COOKIE_NAME)) {
this.settings = Object.assign({}, this.settings, this.$cookies.get(SETTINGS_COOKIE_NAME))
}
})
this.$i18n.locale = window.CUSTOM_LOCALE this.$i18n.locale = window.CUSTOM_LOCALE
moment.locale(window.CUSTOM_LOCALE) moment.locale(window.CUSTOM_LOCALE)
}, },
watch: { watch: {
settings: { settings: {
handler() { handler() {
this.$cookies.set(SETTINGS_COOKIE_NAME, this.settings, "360d") useMealPlanStore().updateClientSettings(this.settings)
}, },
deep: true, deep: true,
}, },
@ -554,12 +494,16 @@ export default {
moveEntry(null_object, target_date, drag_event) { moveEntry(null_object, target_date, drag_event) {
useMealPlanStore().plan_list.forEach((entry) => { useMealPlanStore().plan_list.forEach((entry) => {
if (entry.id === this.dragged_item.id) { if (entry.id === this.dragged_item.id) {
let fromToDiff = Math.abs(moment(entry.to_date).diff(moment(entry.from_date), 'days'))
if (drag_event.ctrlKey) { if (drag_event.ctrlKey) {
let new_entry = Object.assign({}, entry) let new_entry = Object.assign({}, entry)
new_entry.date = target_date new_entry.from_date = target_date
new_entry.to_date = moment(target_date).add(fromToDiff, 'd')
this.createEntry(new_entry) this.createEntry(new_entry)
} else { } else {
entry.date = target_date entry.from_date = target_date
entry.to_date = moment(target_date).add(fromToDiff, 'd')
this.saveEntry(entry) this.saveEntry(entry)
} }
} }
@ -568,7 +512,8 @@ export default {
moveEntryLeft(data) { moveEntryLeft(data) {
useMealPlanStore().plan_list.forEach((entry) => { useMealPlanStore().plan_list.forEach((entry) => {
if (entry.id === data.id) { if (entry.id === data.id) {
entry.date = moment(entry.date).subtract(1, "d") entry.from_date = moment(entry.from_date).subtract(1, "d")
entry.to_date = moment(entry.to_date).subtract(1, "d")
this.saveEntry(entry) this.saveEntry(entry)
} }
}) })
@ -576,7 +521,8 @@ export default {
moveEntryRight(data) { moveEntryRight(data) {
useMealPlanStore().plan_list.forEach((entry) => { useMealPlanStore().plan_list.forEach((entry) => {
if (entry.id === data.id) { if (entry.id === data.id) {
entry.date = moment(entry.date).add(1, "d") entry.from_date = moment(entry.from_date).add(1, "d")
entry.to_date = moment(entry.to_date).add(1, "d")
this.saveEntry(entry) this.saveEntry(entry)
} }
}) })
@ -594,7 +540,8 @@ export default {
openEntryEdit(entry) { openEntryEdit(entry) {
this.$bvModal.show(`id_meal_plan_edit_modal`) this.$bvModal.show(`id_meal_plan_edit_modal`)
this.entryEditing = entry this.entryEditing = entry
this.entryEditing.date = moment(entry.date).format("YYYY-MM-DD") this.entryEditing.from_date = moment(entry.from_date).format("YYYY-MM-DD")
this.entryEditing.to_date = moment(entry.to_date).format("YYYY-MM-DD")
if (this.entryEditing.recipe != null) { if (this.entryEditing.recipe != null) {
this.entryEditing.title_placeholder = this.entryEditing.recipe.name this.entryEditing.title_placeholder = this.entryEditing.recipe.name
} }
@ -617,21 +564,32 @@ export default {
}) })
}, },
saveEntry(entry) { saveEntry(entry) {
entry.date = moment(entry.date).format("YYYY-MM-DD") entry.from_date = moment(entry.from_date).format("YYYY-MM-DD")
entry.to_date = moment(entry.to_date).format("YYYY-MM-DD")
if (entry.from_date > entry.to_date) {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE)
entry.to_date = entry.from_date
} else {
useMealPlanStore().updateObject(entry)
}
useMealPlanStore().updateObject(entry)
}, },
createEntry(entry) { createEntry(entry) {
entry.date = moment(entry.date).format("YYYY-MM-DD") entry.from_date = moment(entry.from_date).format("YYYY-MM-DD")
entry.to_date = moment(entry.to_date).format("YYYY-MM-DD")
useMealPlanStore().createObject(entry) useMealPlanStore().createObject(entry)
}, },
buildItem(plan_entry) { buildItem(plan_entry) {
//dirty hack to order items within a day //dirty hack to order items within a day
let date = moment(plan_entry.date).add(plan_entry.meal_type.order, "m") let from_date = moment(plan_entry.from_date).add(plan_entry.meal_type.order, "m")
let to_date = moment(plan_entry.to_date).add(plan_entry.meal_type.order, "m")
return { return {
id: plan_entry.id, id: plan_entry.id,
startDate: date, startDate: from_date,
endDate: date, endDate: to_date,
entry: plan_entry, entry: plan_entry,
} }
}, },
@ -684,7 +642,7 @@ export default {
} }
.calender-row { .calender-row {
height: calc(100vh - 240px); height: calc(100vh - 140px);
} }
.calender-parent { .calender-parent {

View File

@ -723,9 +723,9 @@
<div class="row"> <div class="row">
<div class="col col-md-12"> <div class="col col-md-12">
<div <div
style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); column-gap: 0.5rem; row-gap: 0.5rem; grid-auto-rows: max-content"
> style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); column-gap: 0.5rem;row-gap: 0.5rem; grid-auto-rows: max-content; ">
<div v-for="day in meal_plan_grid" v-bind:key="day.day" :class="{ 'd-none d-sm-block': day.plan_entries.length === 0 }"> <div v-for="day in meal_plan_grid" v-bind:key="day.day" :class="{'d-none d-sm-block': day.plan_entries.length === 0}">
<b-list-group> <b-list-group>
<b-list-group-item class="hover-div pb-0"> <b-list-group-item class="hover-div pb-0">
<div class="d-flex flex-row align-items-center"> <div class="d-flex flex-row align-items-center">
@ -739,16 +739,13 @@
</div> </div>
</div> </div>
</b-list-group-item> </b-list-group-item>
<b-list-group-item v-for="plan in day.plan_entries" v-bind:key="plan.id" class="hover-div"> <b-list-group-item v-for="plan in day.plan_entries" v-bind:key="plan.id" class="hover-div p-0 pr-2">
<div class="d-flex flex-row align-items-center"> <div class="d-flex flex-row align-items-center">
<div> <div>
<b-img
style="height: 50px; width: 50px; object-fit: cover" <img style="height: 50px; width: 50px; object-fit: cover" :src="plan.recipe.image" v-if="plan.recipe?.image"/>
:src="plan.recipe.image" <img style="height: 50px; width: 50px; object-fit: cover" :src="image_placeholder" v-else/>
rounded="circle"
v-if="plan.recipe?.image"
></b-img>
<b-img style="height: 50px; width: 50px; object-fit: cover" :src="image_placeholder" rounded="circle" v-else></b-img>
</div> </div>
<div class="flex-grow-1 ml-2" style="text-overflow: ellipsis; overflow-wrap: anywhere"> <div class="flex-grow-1 ml-2" style="text-overflow: ellipsis; overflow-wrap: anywhere">
<span class="two-row-text"> <span class="two-row-text">
@ -986,8 +983,8 @@ export default {
grid.push({ grid.push({
date: moment_date, date: moment_date,
create_default_date: moment_date.format("YYYY-MM-DD"), // improve meal plan edit modal to do formatting itself and accept dates create_default_date: moment_date.format("YYYY-MM-DD"), // improve meal plan edit modal to do formatting itself and accept dates
date_label: moment_date.format("ddd DD.MM"), date_label: moment_date.format('ddd DD.MM'),
plan_entries: this.meal_plan_store.plan_list.filter((m) => moment(m.date).isSame(moment_date, "day")), plan_entries: this.meal_plan_store.plan_list.filter((m) => moment_date.isBetween(moment(m.from_date), moment(m.to_date), 'day', '[]'))
}) })
} }
} }
@ -1204,22 +1201,6 @@ export default {
console.log("loadMealpLan") console.log("loadMealpLan")
this.meal_plan_store = useMealPlanStore() this.meal_plan_store = useMealPlanStore()
this.meal_plan_store.refreshFromAPI(moment().format("YYYY-MM-DD"), moment().add(this.ui.meal_plan_days, "days").format("YYYY-MM-DD")) this.meal_plan_store.refreshFromAPI(moment().format("YYYY-MM-DD"), moment().add(this.ui.meal_plan_days, "days").format("YYYY-MM-DD"))
// if (this.ui.show_meal_plan) {
// let params = {
// options: {
// query: {
// from_date: moment().format("YYYY-MM-DD"),
// to_date: moment().add(this.ui.meal_plan_days, "days").format("YYYY-MM-DD"),
// },
// },
// }
// this.genericAPI(this.Models.MEAL_PLAN, this.Actions.LIST, params).then((result) => {
// this.meal_plans = result.data
// })
// } else {
// this.meal_plans = []
// }
}, },
genericSelectChanged: function (obj) { genericSelectChanged: function (obj) {
if (obj.var.includes("::")) { if (obj.var.includes("::")) {

View File

@ -29,7 +29,7 @@
<b-form-select class="ml-1" id="UomInput" v-model="settings.displayPeriodCount" :options="options.displayPeriodCount"></b-form-select> <b-form-select class="ml-1" id="UomInput" v-model="settings.displayPeriodCount" :options="options.displayPeriodCount"></b-form-select>
</span> </span>
<span <span
class="delete-area text-danger p-1 mr-2 ml-1 d-none d-sm-flex align-items-center" class="delete-area text-danger p-1 mr-2 ml-1 d-none d-sm-flex align-items-center rounded"
@drop.prevent="onDeleteDrop($event)" @drop.prevent="onDeleteDrop($event)"
@dragenter.prevent="onDeleteDragEnter($event)" @dragenter.prevent="onDeleteDragEnter($event)"
@dragleave.prevent="onDeleteDragLeave($event)" @dragleave.prevent="onDeleteDragLeave($event)"

View File

@ -1,52 +1,34 @@
<template> <template>
<div
v-hover
class="card cv-item meal-plan-card p-0"
:key="value.id"
:draggable="true"
:style="`top:${top};max-height:${item_height}`"
@dragstart="onDragItemStart(value, $event)"
@click="onClickItem(value, $event)"
:aria-grabbed="value == currentDragItem"
:class="value.classes"
@contextmenu.prevent="$emit('open-context-menu', $event, value)"
>
<div class="card-header p-1 text-center text-primary border-bottom-0" v-if="detailed" :style="`background-color: ${background_color}`">
<span class="font-light text-center" v-if="entry.entry.meal_type.icon != null">{{ entry.entry.meal_type.icon }}</span>
<span class="font-light d-none d-md-inline">{{ entry.entry.meal_type.name }}</span>
<span v-if="entry.entry.shopping" class="font-light"><i class="fas fa-shopping-cart fa-xs float-left" v-b-tooltip.hover.top :title="$t('in_shopping')"/></span>
</div>
<div class="card-img-overlay h-100 d-flex flex-column justify-content-right float-right text-right p-0" v-if="detailed">
<a>
<div style="position: static">
<div class="dropdown b-dropdown position-static btn-group">
<button
aria-haspopup="true"
aria-expanded="false"
type="button"
class="btn btn-link text-decoration-none text-body pr-2 dropdown-toggle-no-caret"
@click.stop="$emit('open-context-menu', $event, value)"
>
<i class="fas fa-ellipsis-v fa-lg"></i>
</button>
</div>
</div>
</a>
</div>
<div class="card-header p-1 text-center" v-if="detailed" :style="`background-color: ${background_color}`">
<span class="font-light">{{ title }}</span>
</div>
<b-img fluid class="card-img-bottom" :src="entry.entry.recipe.image" v-if="hasRecipe && detailed"></b-img>
<b-img fluid class="card-img-bottom" :src="image_placeholder" v-if="detailed && ((!hasRecipe && entry.entry.note === '') || (hasRecipe && entry.entry.recipe.image === null))"></b-img>
<div class="card-body p-1" v-if="detailed && entry.entry.recipe == null" :style="`background-color: ${background_color}`">
<p>{{ entry.entry.note }}</p>
</div>
<div class="row p-1 flex-nowrap" v-if="!detailed" :style="`background-color: ${background_color}`"> <div v-hover
<div class="col-10 d-inline-block text-truncate" :style="`max-height:${item_height}`"> class="card cv-item meal-plan-card p-0"
<span class="font-light">{{ title }}</span> :key="value.id"
:draggable="true"
:style="{'top': top, 'height': item_height, 'border-color': entry.entry.meal_type.color}"
@dragstart="onDragItemStart(value, $event)"
@click="onClickItem(value, $event)"
:aria-grabbed="value == currentDragItem"
:class="value.classes"
@contextmenu.prevent="$emit('open-context-menu', $event, value)">
<div class="d-flex flex-row align-items-center">
<div class="flex-column">
<img class="" style="object-fit: cover" :style="{'height': item_height, 'width': item_height}" :src="entry.entry.recipe.image"
v-if="hasRecipe && detailed"/>
<img class="" style="object-fit: cover" :style="{'height': item_height, 'width': item_height}" :src="image_placeholder"
v-if="detailed && ((!hasRecipe && entry.entry.note === '') || (hasRecipe && entry.entry.recipe.image === null))"/>
</div>
<div class="flex-column flex-grow-0 align-middle justify-content-center">
<div class="card-body p-0 pl-1 align-middle">
<span class="font-light" :class="{'two-line-text': detailed,'one-line-text': !detailed,}">
<i class="fas fa-shopping-cart fa-xs float-left" v-b-tooltip.hover.top :title="$t('in_shopping')" v-if="entry.entry.shopping"/>
{{ title }}</span>
</div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
@ -131,4 +113,22 @@ export default {
font-size: 13px; font-size: 13px;
} }
} }
.two-line-text {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
}
.one-line-text {
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
}
</style> </style>

View File

@ -6,14 +6,13 @@
<div class="row"> <div class="row">
<div class="col col-md-12"> <div class="col col-md-12">
<div class="row"> <div class="row">
<div class="col-6 col-lg-9"> <div class="col-12">
<b-input-group> <b-input-group>
<b-form-input id="TitleInput" v-model="entryEditing.title" <b-form-input id="TitleInput" v-model="entryEditing.title"
:placeholder="entryEditing.title_placeholder" :placeholder="entryEditing.title_placeholder"
@change="missing_recipe = false"></b-form-input> @change="missing_recipe = false"></b-form-input>
<b-input-group-append class="d-none d-lg-block"> <b-input-group-append class="d-none d-lg-block">
<b-button variant="primary" @click="entryEditing.title = ''"><i <b-button variant="primary" @click="entryEditing.title = ''"><i class="fa fa-eraser"></i></b-button>
class="fa fa-eraser"></i></b-button>
</b-input-group-append> </b-input-group-append>
</b-input-group> </b-input-group>
<span class="text-danger" v-if="missing_recipe">{{ <span class="text-danger" v-if="missing_recipe">{{
@ -23,13 +22,30 @@
$t("Title") $t("Title")
}}</small> }}</small>
</div> </div>
<div class="col-6 col-lg-3"> <div class="col-12 col-lg-6">
<input type="date" id="DateInput" class="form-control" v-model="entryEditing.date"/> <b-input-group>
<small tabindex="-1" class="form-text text-muted">{{ $t("Date") }}</small> <b-form-input type="date" v-model="entryEditing.from_date"></b-form-input>
<b-input-group-append>
<b-button variant="secondary" @click="entryEditing.from_date = changeDate(entryEditing.from_date, -1)"><i class="fas fa-minus"></i></b-button>
<b-button variant="primary" @click="entryEditing.from_date = changeDate(entryEditing.from_date, 1)"><i class="fas fa-plus"></i></b-button>
</b-input-group-append>
</b-input-group>
<small tabindex="-1" class="form-text text-muted">{{ $t("StartDate") }}</small>
</div>
<div class="col-12 col-lg-6">
<b-input-group>
<b-form-input type="date" v-model="entryEditing.to_date"></b-form-input>
<b-input-group-append>
<b-button variant="secondary" @click="entryEditing.to_date = changeDate(entryEditing.to_date, -1)"><i class="fas fa-minus"></i></b-button>
<b-button variant="primary" @click="entryEditing.to_date = changeDate(entryEditing.to_date, 1)"><i class="fas fa-plus"></i></b-button>
</b-input-group-append>
</b-input-group>
<small tabindex="-1" class="form-text text-muted">{{ $t("EndDate") }}</small>
</div> </div>
</div> </div>
<div class="row mt-3"> <div class="row">
<div class="col-12 col-lg-6 col-xl-6"> <div class="col-12">
<b-form-group> <b-form-group>
<generic-multiselect <generic-multiselect
@change="selectRecipe" @change="selectRecipe"
@ -43,7 +59,11 @@
></generic-multiselect> ></generic-multiselect>
<small tabindex="-1" class="form-text text-muted">{{ $t("Recipe") }}</small> <small tabindex="-1" class="form-text text-muted">{{ $t("Recipe") }}</small>
</b-form-group> </b-form-group>
<b-form-group class="mt-3"> </div>
</div>
<div class="row">
<div class="col-12 col-lg-6 col-xl-6">
<b-form-group class="">
<generic-multiselect <generic-multiselect
required required
@change="selectMealType" @change="selectMealType"
@ -142,6 +162,7 @@ const {ApiApiFactory} = require("@/utils/openapi/api")
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore"; import {useUserPreferenceStore} from "@/stores/UserPreferenceStore";
import {useMealPlanStore} from "@/stores/MealPlanStore"; import {useMealPlanStore} from "@/stores/MealPlanStore";
import ShoppingModal from "@/components/Modals/ShoppingModal.vue"; import ShoppingModal from "@/components/Modals/ShoppingModal.vue";
import moment from "moment";
Vue.use(BootstrapVue) Vue.use(BootstrapVue)
Vue.use(VueCookies) Vue.use(VueCookies)
@ -221,7 +242,8 @@ export default {
} }
if (this.create_date) { if (this.create_date) {
this.entryEditing.date = this.create_date this.entryEditing.from_date = this.create_date
this.entryEditing.to_date = this.create_date
} }
useUserPreferenceStore().getData().then(userPreference => { useUserPreferenceStore().getData().then(userPreference => {
@ -290,6 +312,9 @@ export default {
this.entryEditing.servings = 1 this.entryEditing.servings = 1
} }
}, },
changeDate(date, change){
return moment(date).add(change, 'd').format("YYYY-MM-DD")
}
}, },
} }
</script> </script>

View File

@ -184,7 +184,7 @@ export default {
localStorage.setItem("pinned_recipes", JSON.stringify(pinnedRecipes)) localStorage.setItem("pinned_recipes", JSON.stringify(pinnedRecipes))
}, },
saveMealPlan: function (entry) { saveMealPlan: function (entry) {
entry.date = moment(entry.date).format("YYYY-MM-DD") entry.from_date = moment(entry.from_date).format("YYYY-MM-DD")
let reviewshopping = entry.addshopping && entry.reviewshopping let reviewshopping = entry.addshopping && entry.reviewshopping
entry.addshopping = entry.addshopping && !entry.reviewshopping entry.addshopping = entry.addshopping && !entry.reviewshopping
@ -208,7 +208,7 @@ export default {
createMealPlan(data) { createMealPlan(data) {
this.entryEditing = this.options.entryEditing this.entryEditing = this.options.entryEditing
this.entryEditing.recipe = this.recipe this.entryEditing.recipe = this.recipe
this.entryEditing.date = moment(new Date()).format("YYYY-MM-DD") this.entryEditing.from_date = moment(new Date()).format("YYYY-MM-DD")
this.$nextTick(function () { this.$nextTick(function () {
this.$bvModal.show(`modal-meal-plan_${this.modal_id}`) this.$bvModal.show(`modal-meal-plan_${this.modal_id}`)
}) })

View File

@ -14,24 +14,24 @@
<hr/> <hr/>
<b-form v-if="meal_plan_store"> <b-form v-if="settings">
<b-form-group id="UomInput" :label="$t('Period')" :description="$t('Plan_Period_To_Show')" <b-form-group id="UomInput" :label="$t('Period')" :description="$t('Plan_Period_To_Show')"
label-for="UomInput"> label-for="UomInput">
<b-form-select id="UomInput" v-model="meal_plan_store.client_settings.displayPeriodUom" <b-form-select id="UomInput" v-model="settings.displayPeriodUom"
:options="calendar_options.displayPeriodUom"></b-form-select> :options="calendar_options.displayPeriodUom"></b-form-select>
</b-form-group> </b-form-group>
<b-form-group id="PeriodInput" :label="$t('Periods')" <b-form-group id="PeriodInput" :label="$t('Periods')"
:description="$t('Plan_Show_How_Many_Periods')" label-for="PeriodInput"> :description="$t('Plan_Show_How_Many_Periods')" label-for="PeriodInput">
<b-form-select id="PeriodInput" v-model="meal_plan_store.client_settings.displayPeriodCount" <b-form-select id="PeriodInput" v-model="settings.displayPeriodCount"
:options="calendar_options.displayPeriodCount"></b-form-select> :options="calendar_options.displayPeriodCount"></b-form-select>
</b-form-group> </b-form-group>
<b-form-group id="DaysInput" :label="$t('Starting_Day')" :description="$t('Starting_Day')" <b-form-group id="DaysInput" :label="$t('Starting_Day')" :description="$t('Starting_Day')"
label-for="DaysInput"> label-for="DaysInput">
<b-form-select id="DaysInput" v-model="meal_plan_store.client_settings.startingDayOfWeek" <b-form-select id="DaysInput" v-model="settings.startingDayOfWeek"
:options="dayNames()"></b-form-select> :options="dayNames()"></b-form-select>
</b-form-group> </b-form-group>
<b-form-group id="WeekNumInput" :label="$t('Week_Numbers')"> <b-form-group id="WeekNumInput" :label="$t('Week_Numbers')">
<b-form-checkbox v-model="meal_plan_store.client_settings.displayWeekNumbers" name="week_num"> <b-form-checkbox v-model="settings.displayWeekNumbers" name="week_num">
{{ $t("Show_Week_Numbers") }} {{ $t("Show_Week_Numbers") }}
</b-form-checkbox> </b-form-checkbox>
</b-form-group> </b-form-group>
@ -96,7 +96,7 @@ export default {
return { return {
user_preferences: undefined, user_preferences: undefined,
languages: [], languages: [],
meal_plan_store: undefined, settings: undefined,
calendar_options: { calendar_options: {
displayPeriodUom: [ displayPeriodUom: [
{text: this.$t("Week"), value: "week"}, {text: this.$t("Week"), value: "week"},
@ -108,14 +108,24 @@ export default {
meal_types: [], meal_types: [],
generic_action: null, generic_action: null,
editing_meal_type: null, editing_meal_type: null,
} }
}, },
watch: {
settings: {
handler() {
useMealPlanStore().updateClientSettings(this.settings)
},
deep: true,
},
},
mounted() { mounted() {
this.user_preferences = this.preferences this.user_preferences = this.preferences
this.languages = window.AVAILABLE_LANGUAGES this.languages = window.AVAILABLE_LANGUAGES
this.loadSettings() this.loadSettings()
this.meal_plan_store = useMealPlanStore() this.settings = useMealPlanStore().client_settings
this.loadMealTypes() this.loadMealTypes()
}, },

View File

@ -100,6 +100,8 @@
"Energy": "Energy", "Energy": "Energy",
"Nutrition": "Nutrition", "Nutrition": "Nutrition",
"Date": "Date", "Date": "Date",
"StartDate": "Start Date",
"EndDate": "End Date",
"Share": "Share", "Share": "Share",
"Automation": "Automation", "Automation": "Automation",
"Parameter": "Parameter", "Parameter": "Parameter",

View File

@ -478,5 +478,7 @@
"Auto_Sort": "自动分类", "Auto_Sort": "自动分类",
"Auto_Sort_Help": "将所有食材移动到最恰当的步骤。", "Auto_Sort_Help": "将所有食材移动到最恰当的步骤。",
"Create Recipe": "创建食谱", "Create Recipe": "创建食谱",
"Import Recipe": "导入食谱" "Import Recipe": "导入食谱",
"recipe_property_info": "您也可以为食物添加属性,以便根据食谱自动计算!",
"per_serving": "每份"
} }

View File

@ -2,6 +2,7 @@ import {defineStore} from 'pinia'
import {ApiApiFactory} from "@/utils/openapi/api"; import {ApiApiFactory} from "@/utils/openapi/api";
const _STORE_ID = 'meal_plan_store' const _STORE_ID = 'meal_plan_store'
const _LOCAL_STORAGE_KEY = 'MEAL_PLAN_CLIENT_SETTINGS'
import Vue from "vue" import Vue from "vue"
import {StandardToasts} from "@/utils/utils"; import {StandardToasts} from "@/utils/utils";
/* /*
@ -12,12 +13,7 @@ export const useMealPlanStore = defineStore(_STORE_ID, {
state: () => ({ state: () => ({
plans: {}, plans: {},
currently_updating: null, currently_updating: null,
client_settings: { settings: null,
displayPeriodUom: "week",
displayPeriodCount: 2,
startingDayOfWeek: 1,
displayWeekNumbers: true,
},
}), }),
getters: { getters: {
plan_list: function () { plan_list: function () {
@ -29,7 +25,8 @@ export const useMealPlanStore = defineStore(_STORE_ID, {
}, },
empty_meal_plan: function () { empty_meal_plan: function () {
return { return {
date: null, from_date: null,
to_date: null,
id: -1, id: -1,
meal_type: null, meal_type: null,
note: "", note: "",
@ -40,6 +37,12 @@ export const useMealPlanStore = defineStore(_STORE_ID, {
title: "", title: "",
title_placeholder: 'Title', // meal plan edit modal should be improved to not need this title_placeholder: 'Title', // meal plan edit modal should be improved to not need this
} }
},
client_settings: function () {
if (this.settings === null) {
this.settings = this.loadClientSettings()
}
return this.settings
} }
}, },
actions: { actions: {
@ -90,6 +93,23 @@ export const useMealPlanStore = defineStore(_STORE_ID, {
}).catch(err => { }).catch(err => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_DELETE, err) StandardToasts.makeStandardToast(this, StandardToasts.FAIL_DELETE, err)
}) })
},
updateClientSettings(settings) {
this.settings = settings
localStorage.setItem(_LOCAL_STORAGE_KEY, JSON.stringify(this.settings))
},
loadClientSettings() {
let s = localStorage.getItem(_LOCAL_STORAGE_KEY)
if (s === null || s === {}) {
return {
displayPeriodUom: "week",
displayPeriodCount: 3,
startingDayOfWeek: 1,
displayWeekNumbers: true,
}
} else {
return JSON.parse(s)
}
} }
}, },
}) })

View File

@ -1812,7 +1812,13 @@ export interface MealPlan {
* @type {string} * @type {string}
* @memberof MealPlan * @memberof MealPlan
*/ */
date: string; from_date: string;
/**
*
* @type {string}
* @memberof MealPlan
*/
to_date?: string | null;
/** /**
* *
* @type {MealPlanMealType} * @type {MealPlanMealType}

View File

@ -1165,7 +1165,9 @@
resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310"
integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==
"@babel/runtime@^7.11.2", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4":
"@babel/runtime@^7.11.2", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4":
version "7.22.15" version "7.22.15"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.15.tgz#38f46494ccf6cf020bd4eed7124b425e83e523b8" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.15.tgz#38f46494ccf6cf020bd4eed7124b425e83e523b8"
integrity sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA== integrity sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA==
@ -3128,13 +3130,13 @@ asn1.js@^5.2.0:
minimalistic-assert "^1.0.0" minimalistic-assert "^1.0.0"
safer-buffer "^2.1.0" safer-buffer "^2.1.0"
assert@^1.1.1:
version "1.5.0" version "1.5.1"
resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb" resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.1.tgz#038ab248e4ff078e7bc2485ba6e6388466c78f76"
integrity sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA== integrity sha512-zzw1uCAgLbsKwBfFc8CX78DDg+xZeBksSO3vwVIDDN5i94eOrPsSSyiVhmsSABFDM/OcpE2aagCat9dnWQLG1A==
dependencies: dependencies:
object-assign "^4.1.1" object.assign "^4.1.4"
util "0.10.3" util "^0.10.4"
assign-symbols@^1.0.0: assign-symbols@^1.0.0:
version "1.0.0" version "1.0.0"
@ -3290,6 +3292,12 @@ babel-generator@^6.26.0:
source-map "^0.5.7" source-map "^0.5.7"
trim-right "^1.0.1" trim-right "^1.0.1"
babel-helper-vue-jsx-merge-props@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-2.0.3.tgz#22aebd3b33902328e513293a8e4992b384f9f1b6"
integrity sha512-gsLiKK7Qrb7zYJNgiXKpXblxbV5ffSwR0f5whkPAaBAR4fhi6bwRZxX9wBlIc5M/v8CCkXUbXZL4N/nSE97cqg==
babel-helpers@^6.24.1: babel-helpers@^6.24.1:
version "6.24.1" version "6.24.1"
resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2" resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2"
@ -3892,9 +3900,10 @@ caniuse-api@^3.0.0:
lodash.uniq "^4.5.0" lodash.uniq "^4.5.0"
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001517, caniuse-lite@^1.0.30001520: caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001517, caniuse-lite@^1.0.30001520:
version "1.0.30001528" version "1.0.30001529"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001528.tgz#479972fc705b996f1114336c0032418a215fd0aa" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001529.tgz#c1f2a411e85fdaace4b1560e1bad078b00ac3181"
integrity sha512-0Db4yyjR9QMNlsxh+kKWzQtkyflkG/snYheSzkjmvdEtEXB1+jt7A2HmSEiO6XIJPIbo92lHNGNySvE5pZcs5Q== integrity sha512-n2pUQYGAkrLG4QYj2desAh+NqsJpHbNmVZz87imptDdxLAtjxary7Df/psdfyDGmskJK/9Dt9cPnx5RZ3CU4Og==
canvg@^3.0.6: canvg@^3.0.6:
version "3.0.10" version "3.0.10"
@ -5010,6 +5019,11 @@ duplexify@^3.4.2, duplexify@^3.6.0:
readable-stream "^2.0.0" readable-stream "^2.0.0"
stream-shift "^1.0.0" stream-shift "^1.0.0"
easings-css@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/easings-css/-/easings-css-1.0.0.tgz#dde569003bb7a4a0c0b77878f5db3e0be5679c81"
integrity sha512-7Uq7NdazNfVtr0RNmPAys8it0zKCuaqxJStYKEl72D3j4gbvXhhaM7iWNbqhA4C94ygCye6VuyhzBRQC4szeBg==
easy-stack@1.0.1: easy-stack@1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/easy-stack/-/easy-stack-1.0.1.tgz#8afe4264626988cabb11f3c704ccd0c835411066" resolved "https://registry.yarnpkg.com/easy-stack/-/easy-stack-1.0.1.tgz#8afe4264626988cabb11f3c704ccd0c835411066"
@ -5033,9 +5047,9 @@ ejs@^3.1.6:
jake "^10.8.5" jake "^10.8.5"
electron-to-chromium@^1.4.477: electron-to-chromium@^1.4.477:
version "1.4.510" version "1.4.512"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.510.tgz#446c50d7533c1e71a84b00a3b37ab06dd601d890" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.512.tgz#f6c14d4d2ddacf064f1de36dbd3f6a469821a7ee"
integrity sha512-xPfLIPFcN/WLXBpQ/K4UgE98oUBO5Tia6BD4rkSR0wE7ep/PwBVlgvPJQrIBpmJGVAmUzwPKuDbVt9XV6+uC2g== integrity sha512-1W8wRbYlQE4ph7eoj3TJ+uqwO6+xvAE/L+KGU7WTQQvX3tnSIGZAb90MTsMoJqzntamiwJhBAj4WZmygXhsOUg==
elliptic@^6.5.3: elliptic@^6.5.3:
version "6.5.4" version "6.5.4"
@ -5830,9 +5844,9 @@ flatted@^3.2.7:
integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==
flow-parser@0.*: flow-parser@0.*:
version "0.216.0" version "0.216.1"
resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.216.0.tgz#719ca725ef08c7e6ad69fa09181f3e9f90a185a8" resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.216.1.tgz#eeba9b0b689deeccc34a6b7d2b1f97b8f943afc0"
integrity sha512-ozczvnbZ++wfBJFseeV0FvINkJ0C6TmRBmb7U7FY1RledNQZuCDTMywRi6txkp8gdzFCJPUxzrU4E27txAktbA== integrity sha512-wstw46/C/8bRv/8RySCl15lK376j8DHxm41xFjD9eVL+jSS1UmVpbdLdA0LzGuS2v5uGgQiBLEj6mgSJQwW+MA==
flush-write-stream@^1.0.0: flush-write-stream@^1.0.0:
version "1.1.1" version "1.1.1"
@ -5999,6 +6013,12 @@ functions-have-names@^1.2.3:
resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834"
integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==
fuzzysearch@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/fuzzysearch/-/fuzzysearch-1.0.3.tgz#dffc80f6d6b04223f2226aa79dd194231096d008"
integrity sha512-s+kNWQuI3mo9OALw0HJ6YGmMbLqEufCh2nX/zzV5CrICQ/y4AwPxM+6TIiF9ItFCHXFCyM/BfCCmN57NTIJuPg==
gensync@^1.0.0-beta.2: gensync@^1.0.0-beta.2:
version "1.0.0-beta.2" version "1.0.0-beta.2"
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
@ -6633,11 +6653,6 @@ inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, i
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
inherits@2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1"
integrity sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA==
inherits@2.0.3: inherits@2.0.3:
version "2.0.3" version "2.0.3"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
@ -7603,7 +7618,7 @@ lodash.uniqby@^4.7.0:
resolved "https://registry.yarnpkg.com/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz#d99c07a669e9e6d24e1362dfe266c67616af1302" resolved "https://registry.yarnpkg.com/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz#d99c07a669e9e6d24e1362dfe266c67616af1302"
integrity sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww== integrity sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==
lodash@4, lodash@^4.17.14, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4: lodash@4, lodash@^4.0.0, lodash@^4.17.14, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4:
version "4.17.21" version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@ -7745,6 +7760,12 @@ map-visit@^1.0.0:
dependencies: dependencies:
object-visit "^1.0.0" object-visit "^1.0.0"
material-colors@^1.2.6:
version "1.2.6"
resolved "https://registry.yarnpkg.com/material-colors/-/material-colors-1.2.6.tgz#6d1958871126992ceecc72f4bcc4d8f010865f46"
integrity sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==
mavon-editor@^2.10.4: mavon-editor@^2.10.4:
version "2.10.4" version "2.10.4"
resolved "https://registry.yarnpkg.com/mavon-editor/-/mavon-editor-2.10.4.tgz#58d6c4dc208933f0ac4595c10c60655899ba8ba8" resolved "https://registry.yarnpkg.com/mavon-editor/-/mavon-editor-2.10.4.tgz#58d6c4dc208933f0ac4595c10c60655899ba8ba8"
@ -8236,7 +8257,7 @@ nth-check@^2.0.1:
dependencies: dependencies:
boolbase "^1.0.0" boolbase "^1.0.0"
object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0:
version "4.1.1" version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
@ -11060,12 +11081,13 @@ util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
util@0.10.3:
version "0.10.3" util@^0.10.4:
resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" version "0.10.4"
integrity sha512-5KiHfsmkqacuKjkRkdV7SsfDJ2EGiPsK92s2MhNSY0craxjTdKTtqKsJaCWp4LW33ZZ0OPUv1WO/TFvNQRiQxQ== resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901"
integrity sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==
dependencies: dependencies:
inherits "2.0.1" inherits "2.0.3"
util@^0.11.0: util@^0.11.0:
version "0.11.1" version "0.11.1"
@ -11322,6 +11344,12 @@ vuedraggable@^2.24.3:
dependencies: dependencies:
sortablejs "1.10.2" sortablejs "1.10.2"
watch-size@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/watch-size/-/watch-size-2.0.0.tgz#096ee28d0365bd7ea03d9c8bf1f2f50a73be1474"
integrity sha512-M92R89dNoTPWyCD+HuUEDdhaDnh9jxPGOwlDc0u51jAgmjUvzqaEMynXSr3BaWs+QdHYk4KzibPy1TFtjLmOZQ==
watchpack-chokidar2@^2.0.1: watchpack-chokidar2@^2.0.1:
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz#38500072ee6ece66f3769936950ea1771be1c957" resolved "https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz#38500072ee6ece66f3769936950ea1771be1c957"