mirror of
https://github.com/grocy/grocy.git
synced 2026-04-05 20:36:15 +02:00
This *absolute commit monster* does the following things: - Introduce gulp to build javascript and css files. This includes moving node_modules out of the public/ folder. Use `gulp --tasks` to get a list of all tasks; however some of them are automatically generated. Use `gulp live` to watch for changes and automatically recompile what's needed. - Upgrade to yarn2 - Upgrade FullCalendar to 4.4.2 I know that 5.x is the current version, but two major version upgrades are too much right now. Also v5 would break any custom css as they renamed a bunch of classes. - Move Styles to sass (Most) global styles are now included in one sass file. This also means that we now compile our own bootstrap. - Javascript is now in strict mode Because everything is a module now, `use strict` is now in effect for all javascript files. There are probably still some parts left where implicit variable declarations were used. - grocy*.js were split up in modules. `window.Grocy` is now an instance of GrocyClass. API-wise nothing has changed (albeit some functions were added regarding Undo actions) At the Moment, this leaks a whole bunch of functions into window (that was easier than tracking those down). - FindObjectIn... style functions were removed. Array.prototype.find and Array.prototype.filter suffice. - Use babel to preprocess javascript. - Use rollup to bundle javascript. rollup bundles and tree-shakes es6 javascript bundles. It also allows to "import" css files and generate css files specific to this javascript file. This is used in viewjs scripts, for example when importing FullCalendar, to generate an associated viewcss file. - Use postcss to post-process css files. postcss uses autoprefixer to handle browser compatiblity. Internally this uses the package `browserslist`; and is currently configured to the default setting. - Minify assets when building in production `gulp publish` builds all assets in production mode, that is, the assets get minified. This includes javascript as well as css files. - css bundling concatCss is used to pull @imports of non-sass-files into one grocy.css - animate.css is now in the main bundle animate.css was used in so many places that it is now located in the main style bundle.
374 lines
13 KiB
PHP
374 lines
13 KiB
PHP
@extends('layout.default')
|
|
|
|
@if($mode == 'edit')
|
|
@section('title', $__t('Edit recipe'))
|
|
@else
|
|
@section('title', $__t('Create recipe'))
|
|
@endif
|
|
|
|
@section('viewJsName', 'recipeform')
|
|
|
|
@section('content')
|
|
<div class="row">
|
|
<div class="col">
|
|
<h2 class="title">@yield('title')</h2>
|
|
|
|
<script>
|
|
GrocyConfig.EditMode = '{{ $mode }}';
|
|
GrocyConfig.QuantityUnits = {!! json_encode($quantityunits) !!};
|
|
GrocyConfig.QuantityUnitConversionsResolved = {!! json_encode($quantityUnitConversionsResolved) !!};
|
|
</script>
|
|
|
|
@if($mode == 'edit')
|
|
<script>
|
|
GrocyConfig.EditObjectId = {{ $recipe->id }};
|
|
</script>
|
|
|
|
@if(!empty($recipe->picture_file_name))
|
|
<script>
|
|
GrocyConfig.RecipePictureFileName = '{{ $recipe->picture_file_name }}';
|
|
</script>
|
|
@endif
|
|
@endif
|
|
</div>
|
|
</div>
|
|
|
|
<hr class="my-2">
|
|
|
|
<div class="row">
|
|
<div class="col-xs-12 col-md-7 pb-3">
|
|
<form id="recipe-form"
|
|
novalidate>
|
|
|
|
<div class="form-group">
|
|
<label for="name">{{ $__t('Name') }}</label>
|
|
<input type="text"
|
|
class="form-control"
|
|
required
|
|
id="name"
|
|
name="name"
|
|
value="@if($mode == 'edit'){{ $recipe->name }}@endif">
|
|
<div class="invalid-feedback">{{ $__t('A name is required') }}</div>
|
|
</div>
|
|
|
|
@php if($mode == 'edit') { $value = $recipe->base_servings; } else { $value = 1; } @endphp
|
|
@include('components.numberpicker', array(
|
|
'id' => 'base_servings',
|
|
'label' => 'Servings',
|
|
'min' => $DEFAULT_MIN_AMOUNT,
|
|
'decimals' => $userSettings['stock_decimal_places_amounts'],
|
|
'value' => $value,
|
|
'hint' => $__t('The ingredients listed here result in this amount of servings'),
|
|
'additionalCssClasses' => 'locale-number-input locale-number-quantity-amount'
|
|
))
|
|
|
|
<div class="form-group">
|
|
<div class="custom-control custom-checkbox">
|
|
<input @if($mode=='edit'
|
|
&&
|
|
$recipe->not_check_shoppinglist == 1) checked @endif class="form-check-input custom-control-input" type="checkbox" id="not_check_shoppinglist" name="not_check_shoppinglist" value="1">
|
|
<label class="form-check-label custom-control-label"
|
|
for="not_check_shoppinglist">
|
|
{{ $__t('Do not check against the shopping list when adding missing items to it') }}
|
|
<i class="fas fa-question-circle text-muted"
|
|
data-toggle="tooltip"
|
|
title="{{ $__t('By default the amount to be added to the shopping list is "needed amount - stock amount - shopping list amount" - when this is enabled, it is only checked against the stock amount, not against what is already on the shopping list') }}"></i>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
@include('components.productpicker', array(
|
|
'products' => $products,
|
|
'isRequired' => false,
|
|
'label' => 'Produces product',
|
|
'prefillById' => $mode == 'edit' ? $recipe->product_id : '',
|
|
'hint' => $__t('When a product is selected, one unit (per serving in stock quantity unit) will be added to stock on consuming this recipe'),
|
|
'disallowAllProductWorkflows' => true,
|
|
))
|
|
|
|
@include('components.userfieldsform', array(
|
|
'userfields' => $userfields,
|
|
'entity' => 'recipes'
|
|
))
|
|
|
|
<div class="form-group">
|
|
<label for="description">{{ $__t('Preparation') }}</label>
|
|
<textarea id="description"
|
|
class="form-control wysiwyg-editor"
|
|
name="description">@if($mode == 'edit'){{ $recipe->description }}@endif</textarea>
|
|
</div>
|
|
|
|
<small class="my-2 form-text text-muted @if($mode == 'edit') d-none @endif">{{ $__t('Save & continue to add ingredients and included recipes') }}</small>
|
|
|
|
<button class="save-recipe btn btn-success mb-2"
|
|
data-location="continue">{{ $__t('Save & continue') }}</button>
|
|
<button class="save-recipe btn btn-info mb-2"
|
|
data-location="return">{{ $__t('Save & return to recipes') }}</button>
|
|
|
|
</form>
|
|
</div>
|
|
|
|
<div class="col-xs-12 col-md-5 pb-3 @if($mode == 'create') d-none @endif">
|
|
<div class="row">
|
|
<div class="col">
|
|
<div class="title-related-links">
|
|
<h4>
|
|
{{ $__t('Ingredients list') }}
|
|
</h4>
|
|
<button class="btn btn-outline-dark d-md-none mt-2 float-right order-1 order-md-3"
|
|
type="button"
|
|
data-toggle="collapse"
|
|
data-target="#related-links">
|
|
<i class="fas fa-ellipsis-v"></i>
|
|
</button>
|
|
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100"
|
|
id="related-links">
|
|
<a id="recipe-pos-add-button"
|
|
class="btn btn-outline-primary btn-sm recipe-pos-add-button m-1 mt-md-0 mb-md-0 float-right"
|
|
type="button"
|
|
href="#">
|
|
{{ $__t('Add') }}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<table id="recipes-pos-table"
|
|
class="table table-sm table-striped nowrap w-100">
|
|
<thead>
|
|
<tr>
|
|
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
|
|
data-toggle="tooltip"
|
|
data-toggle="tooltip"
|
|
title="{{ $__t('Table options') }}"
|
|
data-table-selector="#recipes-pos-table"
|
|
href="#"><i class="fas fa-eye"></i></a>
|
|
</th>
|
|
<th>{{ $__t('Product') }}</th>
|
|
<th>{{ $__t('Amount') }}</th>
|
|
<th class="fit-content">{{ $__t('Note') }}</th>
|
|
<th>{{ $__t('Ingredient group') }}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="d-none">
|
|
@if($mode == "edit")
|
|
@foreach($recipePositions as $recipePosition)
|
|
<tr>
|
|
<td class="fit-content border-right">
|
|
<a class="btn btn-sm btn-info recipe-pos-edit-button"
|
|
type="button"
|
|
href="#"
|
|
data-recipe-pos-id="{{ $recipePosition->id }}"
|
|
data-product-id="{{ $recipePosition->product_id }}">
|
|
<i class="fas fa-edit"></i>
|
|
</a>
|
|
<a class="btn btn-sm btn-danger recipe-pos-delete-button"
|
|
href="#"
|
|
data-recipe-pos-id="{{ $recipePosition->id }}"
|
|
data-recipe-pos-name="{{ FindObjectInArrayByPropertyValue($products, 'id', $recipePosition->product_id)->name }}">
|
|
<i class="fas fa-trash"></i>
|
|
</a>
|
|
</td>
|
|
<td>
|
|
{{ FindObjectInArrayByPropertyValue($products, 'id', $recipePosition->product_id)->name }}
|
|
</td>
|
|
<td>
|
|
@php
|
|
$product = FindObjectInArrayByPropertyValue($products, 'id', $recipePosition->product_id);
|
|
$productQuConversions = FindAllObjectsInArrayByPropertyValue($quantityUnitConversionsResolved, 'product_id', $product->id);
|
|
$productQuConversions = FindAllObjectsInArrayByPropertyValue($productQuConversions, 'from_qu_id', $product->qu_id_stock);
|
|
$productQuConversion = FindObjectInArrayByPropertyValue($productQuConversions, 'to_qu_id', $recipePosition->qu_id);
|
|
if ($productQuConversion && $recipePosition->only_check_single_unit_in_stock == 0)
|
|
{
|
|
$recipePosition->amount = $recipePosition->amount * $productQuConversion->factor;
|
|
}
|
|
@endphp
|
|
@if(!empty($recipePosition->variable_amount))
|
|
{{ $recipePosition->variable_amount }}
|
|
@else
|
|
<span class="locale-number locale-number-quantity-amount">@if($recipePosition->amount == round($recipePosition->amount)){{ round($recipePosition->amount) }}@else{{ $recipePosition->amount }}@endif</span>
|
|
@endif
|
|
{{ $__n($recipePosition->amount, FindObjectInArrayByPropertyValue($quantityunits, 'id', $recipePosition->qu_id)->name, FindObjectInArrayByPropertyValue($quantityunits, 'id', $recipePosition->qu_id)->name_plural) }}
|
|
|
|
@if(!empty($recipePosition->variable_amount))
|
|
<div class="small text-muted font-italic">{{ $__t('Variable amount') }}</div>
|
|
@endif
|
|
</td>
|
|
<td class="fit-content">
|
|
<a class="btn btn-sm btn-info recipe-pos-show-note-button @if(empty($recipePosition->note)) disabled @endif"
|
|
href="#"
|
|
data-toggle="tooltip"
|
|
data-placement="top"
|
|
title="{{ $__t('Show notes') }}"
|
|
data-recipe-pos-note="{{ $recipePosition->note }}">
|
|
<i class="fas fa-eye"></i>
|
|
</a>
|
|
</td>
|
|
<td>
|
|
{{ $recipePosition->ingredient_group }}
|
|
</td>
|
|
</tr>
|
|
@endforeach
|
|
@endif
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row mt-5">
|
|
<div class="col">
|
|
<div class="title-related-links">
|
|
<h4>
|
|
{{ $__t('Included recipes') }}
|
|
</h4>
|
|
<button class="btn btn-outline-dark d-md-none mt-2 float-right order-1 order-md-3"
|
|
type="button"
|
|
data-toggle="collapse"
|
|
data-target="#related-links">
|
|
<i class="fas fa-ellipsis-v"></i>
|
|
</button>
|
|
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100"
|
|
id="related-links">
|
|
<a id="recipe-include-add-button"
|
|
class="btn btn-outline-primary btn-sm m-1 mt-md-0 mb-md-0 float-right"
|
|
href="#">
|
|
{{ $__t('Add') }}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<table id="recipes-includes-table"
|
|
class="table table-sm table-striped nowrap w-100">
|
|
<thead>
|
|
<tr>
|
|
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
|
|
data-toggle="tooltip"
|
|
data-toggle="tooltip"
|
|
title="{{ $__t('Table options') }}"
|
|
data-table-selector="#recipes-includes-table"
|
|
href="#"><i class="fas fa-eye"></i></a>
|
|
</th>
|
|
<th>{{ $__t('Recipe') }}</th>
|
|
<th>{{ $__t('Servings') }}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="d-none">
|
|
@if($mode == "edit")
|
|
@foreach($recipeNestings as $recipeNesting)
|
|
<tr>
|
|
<td class="fit-content border-right">
|
|
<a class="btn btn-sm btn-info recipe-include-edit-button"
|
|
href="#"
|
|
data-recipe-include-id="{{ $recipeNesting->id }}"
|
|
data-recipe-included-recipe-id="{{ $recipeNesting->includes_recipe_id }}"
|
|
data-recipe-included-recipe-servings="{{ $recipeNesting->servings }}">
|
|
<i class="fas fa-edit"></i>
|
|
</a>
|
|
<a class="btn btn-sm btn-danger recipe-include-delete-button"
|
|
href="#"
|
|
data-recipe-include-id="{{ $recipeNesting->id }}"
|
|
data-recipe-include-name="{{ FindObjectInArrayByPropertyValue($recipes, 'id', $recipeNesting->includes_recipe_id)->name }}">
|
|
<i class="fas fa-trash"></i>
|
|
</a>
|
|
</td>
|
|
<td>
|
|
{{ FindObjectInArrayByPropertyValue($recipes, 'id', $recipeNesting->includes_recipe_id)->name }}
|
|
</td>
|
|
<td>
|
|
{{ $recipeNesting->servings }}
|
|
</td>
|
|
</tr>
|
|
@endforeach
|
|
@endif
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row mt-5">
|
|
<div class="col">
|
|
<div class="title-related-links">
|
|
<h4>
|
|
{{ $__t('Picture') }}
|
|
</h4>
|
|
<div class="form-group w-75 m-0">
|
|
<div class="input-group">
|
|
<div class="custom-file">
|
|
<input type="file"
|
|
class="custom-file-input"
|
|
id="recipe-picture"
|
|
accept="image/*">
|
|
<label id="recipe-picture-label"
|
|
class="custom-file-label @if(empty($recipe->picture_file_name)) d-none @endif"
|
|
for="recipe-picture">
|
|
{{ $recipe->picture_file_name }}
|
|
</label>
|
|
<label id="recipe-picture-label-none"
|
|
class="custom-file-label @if(!empty($recipe->picture_file_name)) d-none @endif"
|
|
for="recipe-picture">
|
|
{{ $__t('No file selected') }}
|
|
</label>
|
|
</div>
|
|
<div class="input-group-append">
|
|
<span class="input-group-text"><i class="fas fa-trash"
|
|
id="delete-current-recipe-picture-button"></i></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@if(!empty($recipe->picture_file_name))
|
|
<img id="current-recipe-picture"
|
|
data-src="{{ $U('/api/files/recipepictures/' . base64_encode($recipe->picture_file_name) . '?force_serve_as=picture&best_fit_width=400') }}"
|
|
class="img-fluid img-thumbnail mt-2 lazy mb-5">
|
|
<p id="delete-current-recipe-picture-on-save-hint"
|
|
class="form-text text-muted font-italic d-none mb-5">{{ $__t('The current picture will be deleted on save') }}</p>
|
|
@else
|
|
<p id="no-current-recipe-picture-hint"
|
|
class="form-text text-muted font-italic mb-5">{{ $__t('No picture available') }}</p>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="modal fade"
|
|
id="recipe-include-editform-modal"
|
|
tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content text-center">
|
|
<div class="modal-header">
|
|
<h4 id="recipe-include-editform-title"
|
|
class="modal-title w-100"></h4>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="recipe-include-form"
|
|
novalidate>
|
|
|
|
@include('components.recipepicker', array(
|
|
'recipes' => $recipes,
|
|
'isRequired' => true
|
|
))
|
|
|
|
@include('components.numberpicker', array(
|
|
'id' => 'includes_servings',
|
|
'label' => 'Servings',
|
|
'min' => $DEFAULT_MIN_AMOUNT,
|
|
'decimals' => $userSettings['stock_decimal_places_amounts'],
|
|
'value' => '1',
|
|
'additionalCssClasses' => 'locale-number-input locale-number-quantity-amount'
|
|
))
|
|
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button"
|
|
class="btn btn-secondary"
|
|
data-dismiss="modal">{{ $__t('Cancel') }}</button>
|
|
<button id="save-recipe-include-button"
|
|
data-dismiss="modal"
|
|
class="btn btn-success">{{ $__t('Save') }}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@stop
|