mirror of https://github.com/kcal-app/kcal.git
Add frontend logic for recipe ingredients "separator"
This commit is contained in:
parent
ca27b06d6e
commit
1ff88d5075
|
|
@ -4,7 +4,7 @@
|
||||||
<x-slot name="header">
|
<x-slot name="header">
|
||||||
<h1 class="font-semibold text-xl text-gray-800 leading-tight">{{ $title }}</h1>
|
<h1 class="font-semibold text-xl text-gray-800 leading-tight">{{ $title }}</h1>
|
||||||
</x-slot>
|
</x-slot>
|
||||||
<form method="POST" enctype="multipart/form-data" action="{{ ($recipe->exists ? route('recipes.update', $recipe) : route('recipes.store')) }}">
|
<form x-data method="POST" enctype="multipart/form-data" action="{{ ($recipe->exists ? route('recipes.update', $recipe) : route('recipes.store')) }}">
|
||||||
@if ($recipe->exists)@method('put')@endif
|
@if ($recipe->exists)@method('put')@endif
|
||||||
@csrf
|
@csrf
|
||||||
<div class="flex flex-col space-y-4 md:flex-row md:space-x-4 md:space-y-0">
|
<div class="flex flex-col space-y-4 md:flex-row md:space-x-4 md:space-y-0">
|
||||||
|
|
@ -131,14 +131,20 @@
|
||||||
@empty
|
@empty
|
||||||
@include('recipes.partials.ingredient-input')
|
@include('recipes.partials.ingredient-input')
|
||||||
@endforelse
|
@endforelse
|
||||||
<div class="entry-template hidden">
|
<div class="templates hidden">
|
||||||
@include('recipes.partials.ingredient-input')
|
<div class="ingredient-template">
|
||||||
|
@include('recipes.partials.ingredient-input')
|
||||||
|
</div>
|
||||||
|
<div class="separator-template">
|
||||||
|
@include('recipes.partials.separator-input')
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<x-inputs.icon-button type="button" color="green" x-on:click="addEntryNode($el);">
|
<x-inputs.button type="button" color="green" x-on:click="addNodeFromTemplate($el, 'ingredient');">
|
||||||
<svg class="h-10 w-10 pointer-events-none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
Add Ingredient
|
||||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-11a1 1 0 10-2 0v2H7a1 1 0 100 2h2v2a1 1 0 102 0v-2h2a1 1 0 100-2h-2V7z" clip-rule="evenodd" />
|
</x-inputs.button>
|
||||||
</svg>
|
<x-inputs.button type="button" color="blue" x-on:click="addNodeFromTemplate($el, 'separator');">
|
||||||
</x-inputs.icon-button>
|
Add Separator
|
||||||
|
</x-inputs.button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Steps -->
|
<!-- Steps -->
|
||||||
|
|
@ -149,18 +155,18 @@
|
||||||
@empty
|
@empty
|
||||||
@include('recipes.partials.step-input')
|
@include('recipes.partials.step-input')
|
||||||
@endforelse
|
@endforelse
|
||||||
<div class="entry-template hidden">
|
<div class="templates hidden">
|
||||||
@include('recipes.partials.step-input')
|
<div class="step-template">
|
||||||
|
@include('recipes.partials.step-input')
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<x-inputs.icon-button type="button" color="green" x-on:click="addEntryNode($el);">
|
<x-inputs.button type="button" color="green" x-on:click="addNodeFromTemplate($el, 'step');">
|
||||||
<svg class="h-10 w-10" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
Add Step
|
||||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-11a1 1 0 10-2 0v2H7a1 1 0 100 2h2v2a1 1 0 102 0v-2h2a1 1 0 100-2h-2V7z" clip-rule="evenodd" />
|
</x-inputs.button>
|
||||||
</svg>
|
|
||||||
</x-inputs.icon-button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div x-data class="flex items-center justify-end mt-4">
|
<div class="flex items-center justify-end mt-4">
|
||||||
<x-inputs.button x-on:click="prepareForm();" class="ml-3">
|
<x-inputs.button x-on:click="prepareForm($el);" class="ml-3">
|
||||||
{{ ($recipe->exists ? 'Save' : 'Add') }}
|
{{ ($recipe->exists ? 'Save' : 'Add') }}
|
||||||
</x-inputs.button>
|
</x-inputs.button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -215,9 +221,9 @@
|
||||||
// Recalculate weight (order) of all ingredients.
|
// Recalculate weight (order) of all ingredients.
|
||||||
ingredientsSortable.on('drag:stopped', (e) => {
|
ingredientsSortable.on('drag:stopped', (e) => {
|
||||||
Array.from(e.sourceContainer.children)
|
Array.from(e.sourceContainer.children)
|
||||||
.filter(el => el.classList.contains('ingredient'))
|
.filter(el => el.classList.contains('draggable'))
|
||||||
.forEach((el, index) => {
|
.forEach((el, index) => {
|
||||||
el.querySelector('input[name="ingredients[weight][]"]').value = index;
|
el.querySelector('input[name$="[weight][]"]').value = index;
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -233,35 +239,39 @@
|
||||||
</script>
|
</script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
/**
|
/**
|
||||||
* Adds a set of entry form fields from the template.
|
* Adds a node to ingredients or steps based on a template.
|
||||||
*
|
*
|
||||||
* @param {object} $el Entry lines parent element.
|
* @param {object} $el Parent element.
|
||||||
|
* @param {string} type Template type -- "ingredient", "separator", or "step".
|
||||||
*/
|
*/
|
||||||
let addEntryNode = ($el) => {
|
let addNodeFromTemplate = ($el, type) => {
|
||||||
// Create clone of template entry.
|
// Create clone of relevant template.
|
||||||
const template = $el.querySelector(':scope .entry-template');
|
const templates = $el.querySelector(`:scope .templates`);
|
||||||
const newEntry = template.cloneNode(true).firstElementChild;
|
const template = templates.querySelector(`:scope .${type}-template`);
|
||||||
|
const newNode = template.cloneNode(true).firstElementChild;
|
||||||
|
|
||||||
// Set weight based on previous sibling.
|
// Set weight based on previous sibling.
|
||||||
const lastWeight = template.previousElementSibling.querySelector('input[name="ingredients[weight][]"]');
|
const lastWeight = templates.previousElementSibling.querySelector('input[name$="[weight][]"]');
|
||||||
if (lastWeight && lastWeight.value) {
|
if (lastWeight && lastWeight.value) {
|
||||||
newEntry.querySelector('input[name="ingredients[weight][]"]').value = Number.parseInt(lastWeight.value) + 1;
|
newNode.querySelector('input[name$="[weight][]"]').value = Number.parseInt(lastWeight.value) + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert new entry before add button.
|
// Insert new node before templates.
|
||||||
$el.insertBefore(newEntry, template);
|
$el.insertBefore(newNode, templates);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare form values for submit.
|
* Prepare form values for submit.
|
||||||
|
*
|
||||||
|
* @param {object} $el Form element.
|
||||||
*/
|
*/
|
||||||
let prepareForm = () => {
|
let prepareForm = ($el) => {
|
||||||
// Remove any hidden templates before form submit.
|
// Remove any hidden templates before form submit.
|
||||||
document.querySelectorAll(':scope .entry-template').forEach(e => e.remove());
|
$el.querySelectorAll(':scope .templates').forEach(e => e.remove());
|
||||||
|
|
||||||
// Add description values to hidden fields.
|
// Add description values to hidden fields.
|
||||||
document.querySelector('input[name="description_delta"]').value = JSON.stringify(description.getContents());
|
$el.querySelector('input[name="description_delta"]').value = JSON.stringify(description.getContents());
|
||||||
document.querySelector('input[name="description"]').value = description.root.innerHTML
|
$el.querySelector('input[name="description"]').value = description.root.innerHTML
|
||||||
// Remove extraneous spaces from rendered result.
|
// Remove extraneous spaces from rendered result.
|
||||||
.replaceAll('<p><br></p>', '');
|
.replaceAll('<p><br></p>', '');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
<div class="separator draggable">
|
||||||
|
<x-inputs.input type="hidden" name="separators[key][]" :value="$key ?? null" />
|
||||||
|
<x-inputs.input type="hidden" name="separators[weight][]" :value="$weight ?? null" />
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<div class="flex flex-col space-y-4 md:flex-row md:space-x-4 md:space-y-0 w-full">
|
||||||
|
<div class="draggable-handle self-center text-gray-500 bg-gray-100 w-full md:w-auto p-2 cursor-move">
|
||||||
|
<svg class="h-6 w-6 mx-auto" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path fill-rule="evenodd" d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 15a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z" clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<x-inputs.label for="source" value="Separator text" class="hidden" />
|
||||||
|
|
||||||
|
<x-inputs.input name="separators[text][]"
|
||||||
|
type="text"
|
||||||
|
placeholder="Separator text (optional)"
|
||||||
|
class="block w-full"
|
||||||
|
:value="$text ?? null" />
|
||||||
|
</div>
|
||||||
|
<div class="flex-none">
|
||||||
|
<x-inputs.icon-button type="button"
|
||||||
|
color="red"
|
||||||
|
x-on:click="$event.target.parentNode.parentNode.parentNode.remove();">
|
||||||
|
<svg class="h-8 w-8 pointer-events-none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</x-inputs.icon-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
Loading…
Reference in New Issue