Compare commits
2 Commits
db5d326fa2
...
296201eee5
| Author | SHA1 | Date | |
|---|---|---|---|
|
296201eee5
|
|||
|
b43b45dac2
|
@@ -23,7 +23,8 @@ let options = $state({
|
|||||||
includeIngredients: false,
|
includeIngredients: false,
|
||||||
includeInstructions: false,
|
includeInstructions: false,
|
||||||
showLabel: true,
|
showLabel: true,
|
||||||
labelOverride: ''
|
labelOverride: '',
|
||||||
|
baseMultiplier: 1
|
||||||
});
|
});
|
||||||
|
|
||||||
// Reset options whenever type or modal state changes
|
// Reset options whenever type or modal state changes
|
||||||
@@ -46,6 +47,7 @@ function handleInsert() {
|
|||||||
selectedRecipe = null;
|
selectedRecipe = null;
|
||||||
options.labelOverride = '';
|
options.labelOverride = '';
|
||||||
options.showLabel = true;
|
options.showLabel = true;
|
||||||
|
options.baseMultiplier = 1;
|
||||||
closeModal();
|
closeModal();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,7 +136,8 @@ dialog h2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.selector-content select,
|
.selector-content select,
|
||||||
.selector-content input[type="text"] {
|
.selector-content input[type="text"],
|
||||||
|
.selector-content input[type="number"] {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0.5em 1em;
|
padding: 0.5em 1em;
|
||||||
margin-top: 0.5em;
|
margin-top: 0.5em;
|
||||||
@@ -149,7 +152,9 @@ dialog h2 {
|
|||||||
.selector-content select:hover,
|
.selector-content select:hover,
|
||||||
.selector-content select:focus,
|
.selector-content select:focus,
|
||||||
.selector-content input[type="text"]:hover,
|
.selector-content input[type="text"]:hover,
|
||||||
.selector-content input[type="text"]:focus {
|
.selector-content input[type="text"]:focus,
|
||||||
|
.selector-content input[type="number"]:hover,
|
||||||
|
.selector-content input[type="number"]:focus {
|
||||||
border-color: var(--nord9);
|
border-color: var(--nord9);
|
||||||
transform: scale(1.02, 1.02);
|
transform: scale(1.02, 1.02);
|
||||||
}
|
}
|
||||||
@@ -245,6 +250,18 @@ dialog h2 {
|
|||||||
</label>
|
</label>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Mengenfaktor (Multiplikator):
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
bind:value={options.baseMultiplier}
|
||||||
|
min="0.1"
|
||||||
|
step="0.1"
|
||||||
|
placeholder="1"
|
||||||
|
onkeydown={(event) => do_on_key(event, 'Enter', false, handleInsert)}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
<div class="button-group">
|
<div class="button-group">
|
||||||
<button class="button-insert" onclick={handleInsert} disabled={!selectedRecipe}>
|
<button class="button-insert" onclick={handleInsert} disabled={!selectedRecipe}>
|
||||||
Einfügen
|
Einfügen
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ function handleSelect(recipe: any, options: any) {
|
|||||||
includeIngredients: options.includeIngredients,
|
includeIngredients: options.includeIngredients,
|
||||||
showLabel: options.showLabel,
|
showLabel: options.showLabel,
|
||||||
labelOverride: options.labelOverride || '',
|
labelOverride: options.labelOverride || '',
|
||||||
|
baseMultiplier: options.baseMultiplier || 1,
|
||||||
itemsBefore: [],
|
itemsBefore: [],
|
||||||
itemsAfter: []
|
itemsAfter: []
|
||||||
};
|
};
|
||||||
@@ -746,6 +747,18 @@ h3{
|
|||||||
</div>
|
</div>
|
||||||
<div class="reference-badge">
|
<div class="reference-badge">
|
||||||
📋 {t[lang].baseRecipe}: {list.name || t[lang].unnamed}
|
📋 {t[lang].baseRecipe}: {list.name || t[lang].unnamed}
|
||||||
|
<div style="margin-top: 0.5em;">
|
||||||
|
<label style="font-size: 0.9em; display: flex; align-items: center; gap: 0.5em;">
|
||||||
|
{t[lang].baseMultiplier || 'Mengenfaktor'}:
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
bind:value={list.baseMultiplier}
|
||||||
|
min="0.1"
|
||||||
|
step="0.1"
|
||||||
|
style="width: 5em; padding: 0.25em 0.5em; border-radius: 5px; border: 1px solid var(--nord4);"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mod_icons">
|
<div class="mod_icons">
|
||||||
<button type="button" class="action_button button_subtle" onclick={() => removeReference(list_index)} aria-label={t[lang].removeReferenceAria}>
|
<button type="button" class="action_button button_subtle" onclick={() => removeReference(list_index)} aria-label={t[lang].removeReferenceAria}>
|
||||||
|
|||||||
@@ -125,6 +125,7 @@ function handleSelect(recipe: any, options: any) {
|
|||||||
includeInstructions: options.includeInstructions,
|
includeInstructions: options.includeInstructions,
|
||||||
showLabel: options.showLabel,
|
showLabel: options.showLabel,
|
||||||
labelOverride: options.labelOverride || '',
|
labelOverride: options.labelOverride || '',
|
||||||
|
baseMultiplier: options.baseMultiplier || 1,
|
||||||
stepsBefore: [],
|
stepsBefore: [],
|
||||||
stepsAfter: []
|
stepsAfter: []
|
||||||
};
|
};
|
||||||
@@ -246,7 +247,8 @@ export function add_new_step(){
|
|||||||
instructions[list_index].steps.push(new_step.step)
|
instructions[list_index].steps.push(new_step.step)
|
||||||
}
|
}
|
||||||
const el = document.querySelector("#step")
|
const el = document.querySelector("#step")
|
||||||
el.innerHTML = step_placeholder
|
el.innerHTML = ""
|
||||||
|
new_step.step = ""
|
||||||
instructions = instructions //tells svelte to update dom
|
instructions = instructions //tells svelte to update dom
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -797,6 +799,18 @@ h3{
|
|||||||
</div>
|
</div>
|
||||||
<div class="reference-badge">
|
<div class="reference-badge">
|
||||||
📋 {t[lang].baseRecipe}: {list.name || t[lang].unnamed}
|
📋 {t[lang].baseRecipe}: {list.name || t[lang].unnamed}
|
||||||
|
<div style="margin-top: 0.5em;">
|
||||||
|
<label style="font-size: 0.9em; display: flex; align-items: center; gap: 0.5em;">
|
||||||
|
{t[lang].baseMultiplier || 'Mengenfaktor'}:
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
bind:value={list.baseMultiplier}
|
||||||
|
min="0.1"
|
||||||
|
step="0.1"
|
||||||
|
style="width: 5em; padding: 0.25em 0.5em; border-radius: 5px; border: 1px solid var(--nord4);"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mod_icons">
|
<div class="mod_icons">
|
||||||
<button type="button" class="action_button button_subtle" onclick={() => removeReference(list_index)} aria-label={t[lang].removeReferenceAria}>
|
<button type="button" class="action_button button_subtle" onclick={() => removeReference(list_index)} aria-label={t[lang].removeReferenceAria}>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
let { note = $bindable("") } = $props<{ note?: string }>();
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
div{
|
div{
|
||||||
@@ -14,9 +15,25 @@ div{
|
|||||||
h3{
|
h3{
|
||||||
margin-block: 0;
|
margin-block: 0;
|
||||||
}
|
}
|
||||||
|
textarea {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 80px;
|
||||||
|
padding: 0.5em;
|
||||||
|
border-radius: 5px;
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
font-size: 1rem;
|
||||||
|
resize: vertical;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
font-family: sans-serif;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
textarea::placeholder {
|
||||||
|
color: rgba(255, 255, 255, 0.6);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3>Notiz:</h3>
|
<h3>Notiz:</h3>
|
||||||
<slot></slot>
|
<textarea bind:value={note} placeholder="Füge eine Notiz für dieses Rezept hinzu..."></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,8 +7,20 @@ import HefeSwapper from './HefeSwapper.svelte';
|
|||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
|
|
||||||
|
// Helper function to multiply numbers in ingredient amounts
|
||||||
|
function multiplyIngredientAmount(amount, multiplier) {
|
||||||
|
if (!amount || multiplier === 1) return amount;
|
||||||
|
return amount.replace(/(\d+(?:[\.,]\d+)?)/g, match => {
|
||||||
|
const number = match.includes(',') ? match.replace(/\./g, '').replace(',', '.') : match;
|
||||||
|
const multiplied = (parseFloat(number) * multiplier).toString();
|
||||||
|
const rounded = parseFloat(multiplied).toFixed(3);
|
||||||
|
const trimmed = parseFloat(rounded).toString();
|
||||||
|
return match.includes(',') ? trimmed.replace('.', ',') : trimmed;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Recursively flatten nested ingredient references
|
// Recursively flatten nested ingredient references
|
||||||
function flattenIngredientReferences(items, lang, visited = new Set()) {
|
function flattenIngredientReferences(items, lang, visited = new Set(), baseMultiplier = 1) {
|
||||||
const result = [];
|
const result = [];
|
||||||
|
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
@@ -29,18 +41,22 @@ function flattenIngredientReferences(items, lang, visited = new Set()) {
|
|||||||
? item.resolvedRecipe.translations.en.ingredients
|
? item.resolvedRecipe.translations.en.ingredients
|
||||||
: item.resolvedRecipe.ingredients || [];
|
: item.resolvedRecipe.ingredients || [];
|
||||||
|
|
||||||
// Recursively flatten nested references
|
// Calculate combined multiplier for this reference
|
||||||
const flattenedNested = flattenIngredientReferences(ingredientsToUse, lang, newVisited);
|
const itemBaseMultiplier = item.baseMultiplier || 1;
|
||||||
|
const combinedMultiplier = baseMultiplier * itemBaseMultiplier;
|
||||||
|
|
||||||
|
// Recursively flatten nested references with the combined multiplier
|
||||||
|
const flattenedNested = flattenIngredientReferences(ingredientsToUse, lang, newVisited, combinedMultiplier);
|
||||||
|
|
||||||
// Combine all items into one list
|
// Combine all items into one list
|
||||||
const combinedList = [];
|
const combinedList = [];
|
||||||
|
|
||||||
// Add items before
|
// Add items before (not affected by baseMultiplier)
|
||||||
if (item.itemsBefore && item.itemsBefore.length > 0) {
|
if (item.itemsBefore && item.itemsBefore.length > 0) {
|
||||||
combinedList.push(...item.itemsBefore);
|
combinedList.push(...item.itemsBefore);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add base recipe ingredients (now recursively flattened)
|
// Add base recipe ingredients (now recursively flattened with multiplier applied)
|
||||||
if (item.includeIngredients) {
|
if (item.includeIngredients) {
|
||||||
flattenedNested.forEach(section => {
|
flattenedNested.forEach(section => {
|
||||||
if (section.list) {
|
if (section.list) {
|
||||||
@@ -49,7 +65,7 @@ function flattenIngredientReferences(items, lang, visited = new Set()) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add items after
|
// Add items after (not affected by baseMultiplier)
|
||||||
if (item.itemsAfter && item.itemsAfter.length > 0) {
|
if (item.itemsAfter && item.itemsAfter.length > 0) {
|
||||||
combinedList.push(...item.itemsAfter);
|
combinedList.push(...item.itemsAfter);
|
||||||
}
|
}
|
||||||
@@ -69,12 +85,24 @@ function flattenIngredientReferences(items, lang, visited = new Set()) {
|
|||||||
name: item.showLabel ? (item.labelOverride || baseRecipeName) : '',
|
name: item.showLabel ? (item.labelOverride || baseRecipeName) : '',
|
||||||
list: combinedList,
|
list: combinedList,
|
||||||
isReference: item.showLabel,
|
isReference: item.showLabel,
|
||||||
short_name: baseRecipeShortName
|
short_name: baseRecipeShortName,
|
||||||
|
baseMultiplier: itemBaseMultiplier
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (item.type === 'section' || !item.type) {
|
} else if (item.type === 'section' || !item.type) {
|
||||||
// Regular section - pass through
|
// Regular section - pass through with multiplier applied to amounts
|
||||||
result.push(item);
|
if (baseMultiplier !== 1 && item.list) {
|
||||||
|
const adjustedList = item.list.map(ingredient => ({
|
||||||
|
...ingredient,
|
||||||
|
amount: multiplyIngredientAmount(ingredient.amount, baseMultiplier)
|
||||||
|
}));
|
||||||
|
result.push({
|
||||||
|
...item,
|
||||||
|
list: adjustedList
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
result.push(item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -488,7 +516,7 @@ h3 a:hover {
|
|||||||
{#each flattenedIngredients as list, listIndex}
|
{#each flattenedIngredients as list, listIndex}
|
||||||
{#if list.name}
|
{#if list.name}
|
||||||
{#if list.isReference}
|
{#if list.isReference}
|
||||||
<h3><a href="{list.short_name}?multiplier={multiplier}">{@html list.name}</a></h3>
|
<h3><a href="{list.short_name}?multiplier={multiplier * (list.baseMultiplier || 1)}">{@html list.name}</a></h3>
|
||||||
{:else}
|
{:else}
|
||||||
<h3>{@html list.name}</h3>
|
<h3>{@html list.name}</h3>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -60,12 +60,15 @@ function flattenInstructionReferences(items, lang, visited = new Set()) {
|
|||||||
? item.resolvedRecipe.translations.en.short_name
|
? item.resolvedRecipe.translations.en.short_name
|
||||||
: item.resolvedRecipe.short_name;
|
: item.resolvedRecipe.short_name;
|
||||||
|
|
||||||
|
const itemBaseMultiplier = item.baseMultiplier || 1;
|
||||||
|
|
||||||
result.push({
|
result.push({
|
||||||
type: 'section',
|
type: 'section',
|
||||||
name: item.showLabel ? (item.labelOverride || baseRecipeName) : '',
|
name: item.showLabel ? (item.labelOverride || baseRecipeName) : '',
|
||||||
steps: combinedSteps,
|
steps: combinedSteps,
|
||||||
isReference: item.showLabel,
|
isReference: item.showLabel,
|
||||||
short_name: baseRecipeShortName
|
short_name: baseRecipeShortName,
|
||||||
|
baseMultiplier: itemBaseMultiplier
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (item.type === 'section' || !item.type) {
|
} else if (item.type === 'section' || !item.type) {
|
||||||
@@ -211,7 +214,7 @@ h3 a:hover {
|
|||||||
{#each flattenedInstructions as list}
|
{#each flattenedInstructions as list}
|
||||||
{#if list.name}
|
{#if list.name}
|
||||||
{#if list.isReference}
|
{#if list.isReference}
|
||||||
<h3><a href="{list.short_name}?multiplier={multiplier}">{@html list.name}</a></h3>
|
<h3><a href="{list.short_name}?multiplier={multiplier * (list.baseMultiplier || 1)}">{@html list.name}</a></h3>
|
||||||
{:else}
|
{:else}
|
||||||
<h3>{@html list.name}</h3>
|
<h3>{@html list.name}</h3>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
<style>
|
<style>
|
||||||
.toggle-wrapper {
|
.toggle-wrapper {
|
||||||
display: flex;
|
display: inline-flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toggle-wrapper label {
|
.toggle-wrapper label {
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ const RecipeSchema = new mongoose.Schema(
|
|||||||
includeIngredients: { type: Boolean, default: true },
|
includeIngredients: { type: Boolean, default: true },
|
||||||
showLabel: { type: Boolean, default: true },
|
showLabel: { type: Boolean, default: true },
|
||||||
labelOverride: { type: String, default: "" },
|
labelOverride: { type: String, default: "" },
|
||||||
|
baseMultiplier: { type: Number, default: 1 },
|
||||||
itemsBefore: [{
|
itemsBefore: [{
|
||||||
name: { type: String, default: "" },
|
name: { type: String, default: "" },
|
||||||
unit: String,
|
unit: String,
|
||||||
@@ -70,6 +71,7 @@ const RecipeSchema = new mongoose.Schema(
|
|||||||
includeInstructions: { type: Boolean, default: true },
|
includeInstructions: { type: Boolean, default: true },
|
||||||
showLabel: { type: Boolean, default: true },
|
showLabel: { type: Boolean, default: true },
|
||||||
labelOverride: { type: String, default: "" },
|
labelOverride: { type: String, default: "" },
|
||||||
|
baseMultiplier: { type: Number, default: 1 },
|
||||||
stepsBefore: [String],
|
stepsBefore: [String],
|
||||||
stepsAfter: [String],
|
stepsAfter: [String],
|
||||||
}],
|
}],
|
||||||
@@ -115,6 +117,7 @@ const RecipeSchema = new mongoose.Schema(
|
|||||||
includeIngredients: { type: Boolean, default: true },
|
includeIngredients: { type: Boolean, default: true },
|
||||||
showLabel: { type: Boolean, default: true },
|
showLabel: { type: Boolean, default: true },
|
||||||
labelOverride: { type: String, default: "" },
|
labelOverride: { type: String, default: "" },
|
||||||
|
baseMultiplier: { type: Number, default: 1 },
|
||||||
itemsBefore: [{
|
itemsBefore: [{
|
||||||
name: { type: String, default: "" },
|
name: { type: String, default: "" },
|
||||||
unit: String,
|
unit: String,
|
||||||
@@ -134,6 +137,7 @@ const RecipeSchema = new mongoose.Schema(
|
|||||||
includeInstructions: { type: Boolean, default: true },
|
includeInstructions: { type: Boolean, default: true },
|
||||||
showLabel: { type: Boolean, default: true },
|
showLabel: { type: Boolean, default: true },
|
||||||
labelOverride: { type: String, default: "" },
|
labelOverride: { type: String, default: "" },
|
||||||
|
baseMultiplier: { type: Number, default: 1 },
|
||||||
stepsBefore: [String],
|
stepsBefore: [String],
|
||||||
stepsAfter: [String],
|
stepsAfter: [String],
|
||||||
}],
|
}],
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ export type IngredientReference = {
|
|||||||
includeIngredients: boolean;
|
includeIngredients: boolean;
|
||||||
showLabel: boolean;
|
showLabel: boolean;
|
||||||
labelOverride?: string;
|
labelOverride?: string;
|
||||||
|
baseMultiplier?: number;
|
||||||
itemsBefore?: [{
|
itemsBefore?: [{
|
||||||
name: string;
|
name: string;
|
||||||
unit: string;
|
unit: string;
|
||||||
@@ -61,6 +62,7 @@ export type InstructionReference = {
|
|||||||
includeInstructions: boolean;
|
includeInstructions: boolean;
|
||||||
showLabel: boolean;
|
showLabel: boolean;
|
||||||
labelOverride?: string;
|
labelOverride?: string;
|
||||||
|
baseMultiplier?: number;
|
||||||
stepsBefore?: [string];
|
stepsBefore?: [string];
|
||||||
stepsAfter?: [string];
|
stepsAfter?: [string];
|
||||||
// Populated after server-side resolution
|
// Populated after server-side resolution
|
||||||
|
|||||||
Reference in New Issue
Block a user