First almost fully functioning MVP.

Lacking:
- Seasons cannot be added/edited
- image upload
- layout recipe/adding
This commit is contained in:
Alexander Bocken 2023-06-23 17:23:14 +02:00
parent 4afaf7f6f3
commit 3d0d3f41e2
Signed by: Alexander
GPG Key ID: 1D237BE83F9B05E8
24 changed files with 891 additions and 275 deletions

View File

@ -1,6 +1,7 @@
<script lang='ts'> <script lang='ts'>
export let href export let href
import "$lib/components/nordtheme.css" import "$lib/components/nordtheme.css"
import "$lib/css/action_button.css"
</script> </script>
<style> <style>
@ -8,14 +9,13 @@ import "$lib/components/nordtheme.css"
position: fixed; position: fixed;
bottom:0; bottom:0;
right:0; right:0;
width: 2rem; width: 1rem;
height: 2rem; height: 1rem;
padding: 2rem; padding: 2rem;
border-radius: 1000px; border-radius: 1000px;
margin: 2rem; margin: 2rem;
transition: 200ms; transition: 200ms;
background-color: var(--red); background-color: var(--red);
box-shadow: 0em 0em 0.2em 0.2em rgba(0,0,0,0.2);
display: grid; display: grid;
justify-content: center; justify-content: center;
align-content: center; align-content: center;
@ -24,7 +24,7 @@ align-content: center;
:global(.icon_svg){ :global(.icon_svg){
width: 2rem; width: 2rem;
height: 2rem; height: 2rem;
fill: var(--nord4); fill: white;
} }
:root{ :root{
@ -33,7 +33,7 @@ fill: var(--nord4);
.container:hover, .container:hover,
.container:focus-within .container:focus-within
{ {
background-color: var(--nord3); background-color: var(--nord0);
box-shadow: 0em 0em 0.5em 0.5em rgba(0,0,0,0.2); box-shadow: 0em 0em 0.5em 0.5em rgba(0,0,0,0.2);
/*transform: scale(1.2,1.2);*/ /*transform: scale(1.2,1.2);*/
animation: shake 0.5s; animation: shake 0.5s;
@ -73,6 +73,6 @@ box-shadow: 0em 0em 0.5em 0.5em rgba(0,0,0,0.2);
} }
} }
</style> </style>
<a class=container {href}> <a class="container action_button" {href}>
<slot></slot> <slot></slot>
</a> </a>

View File

@ -2,6 +2,7 @@
export let recipe export let recipe
export let current_month export let current_month
export let icon_override = false; export let icon_override = false;
export let search = "search_me"
if(icon_override){ if(icon_override){
current_month = recipe.season[0] current_month = recipe.season[0]
@ -29,9 +30,9 @@ if(icon_override){
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: end; justify-content: end;
transition: 200ms;
background-color: var(--blue); background-color: var(--blue);
box-shadow: 0em 0em 2em 0.1em rgba(0, 0, 0, 0.3); box-shadow: 0em 0em 2em 0.1em rgba(0, 0, 0, 0.3);
transition: 200ms;
} }
.card .icon{ .card .icon{
text-decoration: unset; text-decoration: unset;
@ -45,17 +46,24 @@ if(icon_override){
border-radius:1000px; border-radius:1000px;
box-shadow: 0em 0em 2em 0.1em rgba(0, 0, 0, 0.6); box-shadow: 0em 0em 2em 0.1em rgba(0, 0, 0, 0.6);
} }
.card:hover,
.card:focus-within{
transform: scale(1.02,1.02);
background-color: var(--red);
box-shadow: 0.2em 0.2em 2em 1em rgba(0, 0, 0, 0.3);
}
.card:active{
scale: 0.95 0.95;
}
.card .icon:hover, .card .icon:hover,
.card .icon:focus-visible .card .icon:focus-visible
{ {
box-shadow: 0em 0em 1em 0.2em rgba(0, 0, 0, 0.6); box-shadow: 0em 0em 1em 0.2em rgba(0, 0, 0, 0.6);
transform:scale(1.2, 1.2) transform:scale(1.2, 1.2)
} }
.card:hover, .icon:active{
.card:focus-within{ scale: 0.8 0.8;
transform: scale(1.02,1.02); rotate: 30deg;
background-color: var(--red);
box-shadow: 0.2em 0.2em 2em 1em rgba(0, 0, 0, 0.3);
} }
.card img{ .card img{
@ -116,7 +124,10 @@ if(icon_override){
background-color: var(--orange); background-color: var(--orange);
box-shadow: 0.2em 0.2em 0.2em 0.1em rgba(0, 0, 0, 0.3); box-shadow: 0.2em 0.2em 0.2em 0.1em rgba(0, 0, 0, 0.3);
} }
.card .tag:active{
transition: 100ms;
scale: 0.8 0.8;
}
.card .title .category{ .card .title .category{
position: absolute; position: absolute;
box-shadow: 0em 0em 1em 0.1em rgba(0, 0, 0, 0.6); box-shadow: 0em 0em 1em 0.1em rgba(0, 0, 0, 0.6);
@ -138,6 +149,10 @@ if(icon_override){
background-color: var(--nord3); background-color: var(--nord3);
transform: scale(1.05, 1.05) transform: scale(1.05, 1.05)
} }
.card .category:active{
scale: 0.9 0.9;
}
.card:hover .icon, .card:hover .icon,
.card:focus-visible .icon .card:focus-visible .icon
{ {
@ -174,7 +189,7 @@ if(icon_override){
} }
</style> </style>
<a class=card href="/rezepte/{recipe.short_name}" data-tags=[{recipe.tags}]> <a class="card {search}" href="/rezepte/{recipe.short_name}" data-tags=[{recipe.tags}]>
{#if icon_override || recipe.season.includes(current_month)} {#if icon_override || recipe.season.includes(current_month)}
<a class=icon href="/rezepte/season/{current_month}">{recipe.icon}</a> <a class=icon href="/rezepte/season/{current_month}">{recipe.icon}</a>
{/if} {/if}

View File

@ -2,9 +2,15 @@
import Cross from '$lib/assets/icons/Cross.svelte' import Cross from '$lib/assets/icons/Cross.svelte'
export let tags:string[] = [] // all data shared with rest of page in card_data
let new_tag export let card_data
if(!card_data.tags){
card_data.tags = []
}
//locals
let new_tag
let image_preview_url let image_preview_url
// Winter: ❄️ // Winter: ❄️
@ -13,7 +19,6 @@ let image_preview_url
// Fastenzeit: ✝️ // Fastenzeit: ✝️
// Herbst: 🍂 // Herbst: 🍂
// Sommer: ☀️ // Sommer: ☀️
import upload_src from "$lib/assets/icons/upload.svg"
export function show_local_image(){ export function show_local_image(){
@ -38,15 +43,15 @@ export function remove_selected_images(){
export function add_to_tags(){ export function add_to_tags(){
if(new_tag){ if(new_tag){
if(! tags.includes(new_tag)){ if(! card_data.tags.includes(new_tag)){
tags.push(new_tag) card_data.tags.push(new_tag)
tags = tags; card_data.tags = card_data.tags;
} }
} }
new_tag = "" new_tag = ""
} }
export function remove_from_tags(tag){ export function remove_from_tags(tag){
tags = tags.filter(item => item !== tag) card_data.tags = card_data.tags.filter(item => item !== tag)
} }
export function add_on_enter(event){ export function add_on_enter(event){
if(event.key === 'Enter'){ if(event.key === 'Enter'){
@ -55,14 +60,14 @@ export function add_on_enter(event){
} }
export function remove_on_enter(event, tag){ export function remove_on_enter(event, tag){
if(event.key === 'Enter'){ if(event.key === 'Enter'){
tags = tags.filter(item => item !== tag) card_data.tags = card_data.tags.filter(item => item !== tag)
} }
} }
</script> </script>
<style> <style>
.card{ .card{
position: relative; position: relative;
margin-left: 100px; margin-inline: auto;
--card-width: 300px; --card-width: 300px;
text-decoration: none; text-decoration: none;
position: relative; position: relative;
@ -127,11 +132,6 @@ export function remove_on_enter(event, tag){
z-index: 4; z-index: 4;
transition:200ms; transition:200ms;
} }
.delete svg{
width: 2rem;
height: 2rem;
fill: white;
}
.delete:hover{ .delete:hover{
transform: scale(1.2, 1.2); transform: scale(1.2, 1.2);
} }
@ -333,7 +333,7 @@ input::placeholder{
<div class=card href="" > <div class=card href="" >
<input class=icon placeholder=😀/> <input class=icon placeholder=😀 bind:value={card_data.icon}/>
{#if image_preview_url} {#if image_preview_url}
<img src={image_preview_url} class=img_preview width=300px height=300px /> <img src={image_preview_url} class=img_preview width=300px height=300px />
{/if} {/if}
@ -351,13 +351,13 @@ input::placeholder{
</div> </div>
<input type="file" id=img_picker accept="image/webp image/jpeg" on:change={show_local_image}> <input type="file" id=img_picker accept="image/webp image/jpeg" on:change={show_local_image}>
<div class=title> <div class=title>
<input class=category placeholder=Kategorie.../> <input class=category placeholder=Kategorie... bind:value={card_data.category}/>
<div> <div>
<input class=name placeholder=Name.../> <input class=name placeholder=Name... bind:value={card_data.name}/>
<input class=description placeholder=Kurzbeschreibung.../> <input class=description placeholder=Kurzbeschreibung... bind:value={card_data.description}/>
</div> </div>
<div class=tags> <div class=tags>
{#each tags as tag} {#each card_data.tags as tag}
<div class="tag" tabindex="0" on:keypress={remove_on_enter(event ,tag)} on:click='{remove_from_tags(tag)}'>{tag}</div> <div class="tag" tabindex="0" on:keypress={remove_on_enter(event ,tag)} on:click='{remove_from_tags(tag)}'>{tag}</div>
{/each} {/each}
<div class="tag input_wrapper"><span class=input>+</span><input class="tag_input" type="text" on:keypress={add_on_enter} on:focusout={add_to_tags} size="1" bind:value={new_tag} placeholder=Stichwort...></div> <div class="tag input_wrapper"><span class=input>+</span><input class="tag_input" type="text" on:keypress={add_on_enter} on:focusout={add_to_tags} size="1" bind:value={new_tag} placeholder=Stichwort...></div>

View File

@ -5,11 +5,11 @@ import Cross from '$lib/assets/icons/Cross.svelte'
import Plus from '$lib/assets/icons/Plus.svelte' import Plus from '$lib/assets/icons/Plus.svelte'
import Check from '$lib/assets/icons/Check.svelte' import Check from '$lib/assets/icons/Check.svelte'
let ingredients_lists = [ import "$lib/css/action_button.css"
{name: "",
ingredients: [], import { do_on_key } from '$lib/components/do_on_key.js'
}
] export let ingredients
let new_ingredient = { let new_ingredient = {
amount: "", amount: "",
@ -22,6 +22,7 @@ let edit_ingredient = {
amount: "", amount: "",
unit: "", unit: "",
name: "", name: "",
sublist: "",
list_index: "", list_index: "",
ingredient_index: "", ingredient_index: "",
} }
@ -40,112 +41,345 @@ function get_sublist_index(sublist_name, list){
return -1 return -1
} }
export function show_modal_edit_subheading_ingredient(list_index){ export function show_modal_edit_subheading_ingredient(list_index){
edit_heading.name = ingredients_lists[list_index].name edit_heading.name = ingredients[list_index].name
edit_heading.list_index = list_index edit_heading.list_index = list_index
const el = document.querySelector('#edit_subheading_ingredient_modal') const el = document.querySelector('#edit_subheading_ingredient_modal')
el.showModal() el.showModal()
} }
export function edit_subheading_and_close_modal(){ export function edit_subheading_and_close_modal(){
ingredients_lists[edit_heading.list_index].name = edit_heading.name ingredients[edit_heading.list_index].name = edit_heading.name
const el = document.querySelector('#edit_subheading_ingredient_modal') const el = document.querySelector('#edit_subheading_ingredient_modal')
el.close() el.close()
} }
export function add_new_ingredient(){ export function add_new_ingredient(){
let list_index = get_sublist_index(new_ingredient.sublist, ingredients_lists) if(!new_ingredient.name){
if(list_index == -1){ return
ingredients_lists.push({
name: new_ingredient.sublist,
ingredients: [],
})
list_index = ingredients_lists.length - 1
} }
ingredients_lists[list_index].ingredients.push({ ...new_ingredient}) let list_index = get_sublist_index(new_ingredient.sublist, ingredients)
ingredients_lists = ingredients_lists //tells svelte to update dom if(list_index == -1){
ingredients.push({
name: new_ingredient.sublist,
list: [],
})
list_index = ingredients.length - 1
}
ingredients[list_index].list.push({ ...new_ingredient})
ingredients = ingredients //tells svelte to update dom
} }
export function remove_list(list_index){ export function remove_list(list_index){
ingredients_lists.splice(list_index, 1); if(ingredients[list_index].list.length > 1){
ingredients_lists = ingredients_lists //tells svelte to update dom const response = confirm("Bist du dir sicher, dass du diese Liste löschen möchtest? Alle Zutaten der Liste werden hiermit auch gelöscht.");
if(!response){
return
}
}
ingredients.splice(list_index, 1);
ingredients = ingredients //tells svelte to update dom
} }
export function remove_ingredient(list_index, ingredient_index){ export function remove_ingredient(list_index, ingredient_index){
ingredients_lists[list_index].ingredients.splice(ingredient_index, 1) ingredients[list_index].list.splice(ingredient_index, 1)
ingredients_lists = ingredients_lists //tells svelte to update dom ingredients = ingredients //tells svelte to update dom
} }
export function show_modal_edit_ingredient(list_index, ingredient_index){ export function show_modal_edit_ingredient(list_index, ingredient_index){
edit_ingredient = {...ingredients_lists[list_index].ingredients[ingredient_index]} edit_ingredient = {...ingredients[list_index].list[ingredient_index]}
edit_ingredient.list_index = list_index edit_ingredient.list_index = list_index
edit_ingredient.ingredient_index = ingredient_index edit_ingredient.ingredient_index = ingredient_index
edit_ingredient.sublist = ingredients[list_index].name
const modal_el = document.querySelector("#edit_ingredient_modal"); const modal_el = document.querySelector("#edit_ingredient_modal");
modal_el.showModal(); modal_el.showModal();
} }
export function edit_ingredient_and_close_modal(){ export function edit_ingredient_and_close_modal(){
ingredients_lists[edit_ingredient.list_index].ingredients[edit_ingredient.ingredient_index] = { ingredients[edit_ingredient.list_index].list[edit_ingredient.ingredient_index] = {
amount: edit_ingredient.amount, amount: edit_ingredient.amount,
unit: edit_ingredient.unit, unit: edit_ingredient.unit,
name: edit_ingredient.name, name: edit_ingredient.name,
} }
ingredients[edit_ingredient.list_index].name = edit_ingredient.sublist
const modal_el = document.querySelector("#edit_ingredient_modal"); const modal_el = document.querySelector("#edit_ingredient_modal");
modal_el.close(); modal_el.close();
} }
export function show_keys(event){
console.log(event.ctrlKey, event.key)
}
</script> </script>
<style> <style>
input::placeholder{
all:unset;
}
input{
all:unset;
}
input.heading{
all: unset;
background-color: var(--nord0);
padding: 1rem;
padding-inline: 2rem;
font-size: 1.5rem;
width: 100%;
border-radius: 1000px;
color: white;
justify-content: center;
align-items: center;
transition: 200ms;
}
input.heading:hover{
background-color: var(--nord1);
}
.heading_wrapper{
position: relative;
width: 300px;
margin-inline: auto;
transition: 200ms;
}
.heading_wrapper:hover
{
transform:scale(1.1,1.1);
}
.heading_wrapper button{
position: absolute;
bottom: -1.5rem;
right: -5rem;
}
.adder{
margin-inline: auto;
position: relative;
margin-block: 3rem;
width: 400px;
border-radius: 20px;
transition: 200ms;
}
.shadow{
box-shadow: 0 0 1em 0.2em rgba(0,0,0,0.3);
}
.shadow:hover{
box-shadow: 0 0 1em 0.4em rgba(0,0,0,0.3);
}
.adder button{
position: absolute;
right: -1.5rem;
bottom: -1.5rem;
}
.category{
all: unset;
position: absolute;
--font_size: 1.5rem;
top: -1em;
left: -1em;
font-family: sans-serif;
font-size: 1.5rem;
background-color: var(--nord0);
color: var(--nord4);
border-radius: 1000000px;
width: 23ch;
padding: 0.5em 1em;
transition: 100ms;
box-shadow: 0.5em 0.5em 1em 0.4em rgba(0,0,0,0.3);
}
.category:hover{
background-color: var(--nord1);
transform: scale(1.05,1.05);
}
.adder:hover,
.adder:focus-within
{
transform: scale(1.05, 1.05);
}
.add_ingredient{
font-family: sans-serif;
width: 100%;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
align-items: center;
font-size: 1.2rem;
padding: 2rem;
padding-top: 2.5rem;
border-radius: 20px;
background-color: var(--blue);
color: #bbb;
transition: 200ms;
gap: 0.5rem;
}
.add_ingredient input{
border: 2px solid var(--nord4);
color: var(--nord4);
border-radius: 1000px;
padding: 0.5em 1em;
transition: 100ms;
}
.add_ingredient input:hover,
.add_ingredient input:focus-visible
{
border-color: white;
color: white;
transform: scale(1.02, 1.02);
}
.add_ingredient input:nth-of-type(1){
max-width: 8ch;
}
.add_ingredient input:nth-of-type(2){
max-width: 8ch;
}
.add_ingredient input:nth-of-type(3){
max-width: 30ch;
}
dialog{
box-sizing: content-box;
width: 100%;
height: 100%;
background-color: rgba(255,255,255, 0.001);
border: unset;
margin: 0;
transition: 500ms;
}
dialog[open]{
animation: show 200ms ease forwards;
}
@keyframes show{
from {
backdrop-filter: blur(0px);
}
to {
backdrop-filter: blur(10px);
}
}
dialog .adder{
margin-top: 5rem;
}
dialog h2{
font-size: 3rem;
font-family: sans-serif;
color: white;
text-align: center;
margin-top: 30vh;
margin-top: 30dvh;
filter: drop-shadow(0 0 0.4em black)
drop-shadow(0 0 1em black)
;
}
ul{
width: fit-content;
margin-inline: auto;
}
li{
font-size: 1.2rem;
max-width: 1000px;
align-items: center;
}
.li_wrapper{
display: flex;
justify-content: space-between;
}
.mod_icons{
display: flex;
flex-direction: row;
margin-left: 2rem;
}
li:nth-child(2n){
background-color: var(--nord4);
}
li:nth-child(2n+1){
background-color: var(--nord6);
}
.button_subtle{
padding: 0em;
animation: unset;
margin: 0.2em 0.1em;
background-color: transparent;
box-shadow: unset;
}
.button_subtle:hover{
scale: 1.2 1.2;
}
h3{
margin-inline: auto;
width: fit-content;
display: flex;
flex-direction: row;
max-width: 1000px;
justify-content: space-between;
}
</style> </style>
{#each ingredients_lists as list, list_index} {#each ingredients as list, list_index}
<h3> <h3>
<div>
{#if list.name } {#if list.name }
{list.name} {list.name}
{:else} {:else}
Leer Leer
{/if} {/if}
<button class=edit on:click="{() => show_modal_edit_subheading_ingredient(list_index)}"> </div>
<Pen></Pen> </button> <div class=mod_icons>
<button class=remove on:click="{() => remove_list(list_index)}"> <button class="action_button button_subtle" on:click="{() => show_modal_edit_subheading_ingredient(list_index)}">
<Cross></Cross> <Pen fill=var(--nord1)></Pen> </button>
<button class="action_button button_subtle" on:click="{() => remove_list(list_index)}">
<Cross fill=var(--nord1)></Cross>
</button> </button>
</div>
</h3> </h3>
<ul> <ul>
{#each list.ingredients as ingredient, ingredient_index} {#each list.list as ingredient, ingredient_index}
<li>{ingredient.amount} {ingredient.unit} {ingredient.name} <li><div class=li_wrapper><div>{ingredient.amount} {ingredient.unit} {ingredient.name}</div>
<button class=edit on:click={() => show_modal_edit_ingredient(list_index, ingredient_index)}> <div class=mod_icons><button class="action_button button_subtle" on:click={() => show_modal_edit_ingredient(list_index, ingredient_index)}>
<Pen></Pen> <Pen fill=var(--nord1) height=1em width=1em></Pen>
</button>
<button class=remove on:click="{() => remove_ingredient(list_index, ingredient_index)}">
<Cross></Cross>
</button> </button>
<button class="action_button button_subtle" on:click="{() => remove_ingredient(list_index, ingredient_index)}">
<Cross fill=var(--nord1) height=1em width=1em></Cross>
</button></div></div>
</li> </li>
{/each} {/each}
</ul> </ul>
{/each} {/each}
<input type="text" bind:value={new_ingredient.sublist} placeholder="Unterkategorie (optional)"> <div class="adder shadow">
<div class=ingredient> <input class=category type="text" bind:value={new_ingredient.sublist} placeholder="Kategorie (optional)" on:keypress={(event) => do_on_key(event, 'Enter', false, add_new_ingredient)}>
<input type="text" id=amount placeholder="250..." bind:value={new_ingredient.amount}> <div class=add_ingredient>
<input type="text" id=unit placeholder="mL..." bind:value={new_ingredient.unit}> <input type="text" placeholder="250..." bind:value={new_ingredient.amount} on:keypress={(event) => do_on_key(event, 'Enter', false, add_new_ingredient)}>
<input type="text" id=name placeholder="Milch..." bind:value={new_ingredient.name}> <input type="text" placeholder="mL..." bind:value={new_ingredient.unit} on:keypress={(event) => do_on_key(event, 'Enter', false, add_new_ingredient)}>
<button on:click={() => add_new_ingredient()}> <input type="text" placeholder="Milch..." bind:value={new_ingredient.name} on:keypress={(event) => do_on_key(event, 'Enter', false, add_new_ingredient)}>
<Plus></Plus> <button on:click={() => add_new_ingredient()} class=action_button>
<Plus fill=white style="width: 2rem; height: 2rem;"></Plus>
</button> </button>
</div>
</div> </div>
<dialog class=ingredient id=edit_ingredient_modal> <dialog id=edit_ingredient_modal>
<input type="text" id=amount placeholder="250..." bind:value={edit_ingredient.amount}> <h2>Zutat verändern</h2>
<input type="text" id=unit placeholder="mL..." bind:value={edit_ingredient.unit}> <div class=adder>
<input type="text" id=name placeholder="Milch..." bind:value={edit_ingredient.name}> <input class=category type="text" bind:value={edit_ingredient.sublist} placeholder="Kategorie (optional)">
<button on:click={edit_ingredient_and_close_modal}> <div class=add_ingredient on:keypress={(event) => do_on_key(event, 'Enter', false, add_new_ingredient)}>
<Check></Check> <input type="text" placeholder="250..." bind:value={edit_ingredient.amount} on:keypress={(event) => do_on_key(event, 'Enter', false, add_new_ingredient)}>
<input type="text" placeholder="mL..." bind:value={edit_ingredient.unit} on:keypress={(event) => do_on_key(event, 'Enter', false, add_new_ingredient)}>
<input type="text" placeholder="Milch..." bind:value={edit_ingredient.name} on:keypress={(event) => do_on_key(event, 'Enter', false, edit_ingredient_and_close_modal)}>
<button class=action_button on:click={edit_ingredient_and_close_modal}>
<Check fill=white style="width: 2rem; height: 2rem;"></Check>
</button> </button>
</div>
</div>
</dialog> </dialog>
<dialog id=edit_subheading_ingredient_modal> <dialog id=edit_subheading_ingredient_modal>
<input type="text" bind:value={edit_heading.name}> <h2>Kategorie umbenennen</h2>
<button on:click={edit_subheading_and_close_modal}> <div class=heading_wrapper>
<Check></Check> <input class=heading type="text" bind:value={edit_heading.name} on:keypress={(event) => do_on_key(event, 'Enter', false, add_new_ingredient)}>
<button class=action_button on:click={edit_subheading_and_close_modal}>
<Check fill=white style="width:2rem; height:2rem;"></Check>
</button> </button>
</div>
</dialog> </dialog>

View File

@ -6,16 +6,16 @@ import Plus from '$lib/assets/icons/Plus.svelte'
import Check from '$lib/assets/icons/Check.svelte' import Check from '$lib/assets/icons/Check.svelte'
import '$lib/components/nordtheme.css' import '$lib/components/nordtheme.css'
import "$lib/css/action_button.css"
import { do_on_key } from '$lib/components/do_on_key.js'
const step_placeholder = "Kartoffeln schälen..." const step_placeholder = "Kartoffeln schälen..."
let instructions = [{ export let instructions
name: "",
steps: [],
}]
let new_step = { let new_step = {
name: "", name: "",
step: "Kartoffeln schälen..." step: step_placeholder
} }
let edit_heading = { let edit_heading = {
@ -37,6 +37,9 @@ export function remove_list(list_index){
} }
export function add_new_step(){ export function add_new_step(){
if(new_step.step == step_placeholder){
return
}
let list_index = get_sublist_index(new_step.name, instructions) let list_index = get_sublist_index(new_step.name, instructions)
if(list_index == -1){ if(list_index == -1){
instructions.push({ instructions.push({
@ -51,9 +54,6 @@ export function add_new_step(){
const el = document.querySelector("#step") const el = document.querySelector("#step")
el.innerHTML = step_placeholder el.innerHTML = step_placeholder
instructions = instructions //tells svelte to update dom instructions = instructions //tells svelte to update dom
new_step.step = ""
add_placeholder()
} }
export function remove_step(list_index, step_index){ export function remove_step(list_index, step_index){
@ -107,7 +107,7 @@ export function clear_step(){
export function add_placeholder(){ export function add_placeholder(){
const el = document.querySelector("#step") const el = document.querySelector("#step")
if(el.innerHTML == ""){ if(el.innerHTML == ""){
el.innerHTML = "Kartoffeln schälen..." el.innerHTML = step_placeholder
} }
} }
</script> </script>
@ -117,6 +117,43 @@ input::placeholder{
all:unset; all:unset;
} }
input.heading{
all: unset;
background-color: var(--nord0);
padding: 1rem;
padding-inline: 2rem;
font-size: 1.5rem;
width: 100%;
border-radius: 1000px;
color: white;
justify-content: center;
align-items: center;
transition: 200ms;
}
input.heading:hover,
input.heading:focus-visible
{
background-color: var(--nord1);
}
.heading_wrapper{
position: relative;
width: 300px;
margin-inline: auto;
transition: 200ms;
}
.heading_wrapper:hover,
.heading_wrapper:focus-visible
{
transform:scale(1.1,1.1);
}
.heading_wrapper button{
position: absolute;
bottom: -1.5rem;
right: -5rem;
}
.adder{ .adder{
margin-inline: auto; margin-inline: auto;
position: relative; position: relative;
@ -129,24 +166,9 @@ input::placeholder{
box-shadow: 0 0 1em 0.2em rgba(0,0,0,0.3); box-shadow: 0 0 1em 0.2em rgba(0,0,0,0.3);
} }
.adder button{ .adder button{
all:unset;
position: absolute; position: absolute;
right: -1.5rem; right: -1.5rem;
bottom: -1.5rem; bottom: -1.5rem;
cursor: pointer;
display:flex;
justify-content: center;
align-items: center;
background-color: var(--red);
border-radius:100000px;
padding: 1rem;
transition: 100ms;
box-shadow: 0 0 1em 0.4em rgba(0,0,0,0.3);
}
.adder button:hover{
background-color: var(--nord0);
transform: scale(1.1,1.1);
box-shadow: 0 0 1em 0.8em rgba(0,0,0,0.3);
} }
.category{ .category{
all: unset; all: unset;
@ -164,11 +186,15 @@ input::placeholder{
transition: 100ms; transition: 100ms;
box-shadow: 0.5em 0.5em 1em 0.4em rgba(0,0,0,0.3); box-shadow: 0.5em 0.5em 1em 0.4em rgba(0,0,0,0.3);
} }
.category:hover{ .category:hover,
.category:focus-visible
{
background-color: var(--nord1); background-color: var(--nord1);
transform: scale(1.05,1.05); transform: scale(1.05,1.05);
} }
.adder:hover{ .adder:hover,
.adder:focus-within
{
transform: scale(1.1, 1.1); transform: scale(1.1, 1.1);
} }
@ -193,9 +219,11 @@ dialog{
box-sizing: content-box; box-sizing: content-box;
width: 100%; width: 100%;
height: 100%; height: 100%;
background-color: rgba(0, 0, 0, 0.6); background-color: rgba(255,255,255, 0.001);
border: unset; border: unset;
backdrop-filter: blur(10px);
margin: 0; margin: 0;
transition: 200ms;
} }
dialog .adder{ dialog .adder{
margin-top: 5rem; margin-top: 5rem;
@ -205,35 +233,21 @@ dialog h2{
font-family: sans-serif; font-family: sans-serif;
color: white; color: white;
text-align: center; text-align: center;
margin-top: 30%; margin-top: 30vh;
} margin-top: 30dvh;
filter: drop-shadow(0 0 0.4em black)
@keyframes shake{ drop-shadow(0 0 1em black)
0%{
transform: rotate(0)
scale(1,1);
}
25%{
box-shadow: 0em 0em 1em 0.2em rgba(0, 0, 0, 0.6);
transform: rotate(30deg)
scale(1.2,1.2)
; ;
} }
50%{ dialog[open]{
animation: show 200ms ease forwards;
box-shadow: 0em 0em 1em 0.2em rgba(0, 0, 0, 0.6);
transform: rotate(-30deg)
scale(1.2,1.2);
} }
74%{ @keyframes show{
from {
box-shadow: 0em 0em 1em 0.2em rgba(0, 0, 0, 0.6); backdrop-filter: blur(0px);
transform: rotate(30deg)
scale(1.2, 1.2);
} }
100%{ to {
transform: rotate(0) backdrop-filter: blur(10px);
scale(1,1);
} }
} }
</style> </style>
@ -269,10 +283,10 @@ dialog h2{
{/each} {/each}
<div class='adder shadow'> <div class='adder shadow'>
<input class=category type="text" bind:value={new_step.name} placeholder="Unterkategorie (optional)"> <input class=category type="text" bind:value={new_step.name} placeholder="Kategorie (optional)"on:keypress={(event) => do_on_key(event, 'Enter', false , add_new_step)} >
<div class=add_step> <div class=add_step>
<p id=step contenteditable on:focus='{clear_step}' on:blur={add_placeholder} bind:innerHTML={new_step.step}></p> <p id=step contenteditable on:focus='{clear_step}' on:blur={add_placeholder} bind:innerHTML={new_step.step} on:keypress={(event) => do_on_key(event, 'Enter', true , add_new_step)}></p>
<button on:click={() => add_new_step()}> <button on:click={() => add_new_step()} class=action_button>
<Plus fill=white style="height: 2rem; width: 2rem"></Plus> <Plus fill=white style="height: 2rem; width: 2rem"></Plus>
</button> </button>
@ -281,18 +295,21 @@ dialog h2{
<dialog id=edit_step_modal> <dialog id=edit_step_modal>
<h2>Schritt verändern</h2> <h2>Schritt verändern</h2>
<div class=adder> <div class=adder>
<input class=category type="text" bind:value={edit_step.name} placeholder="Unterkategorie (optional)"> <input class=category type="text" bind:value={edit_step.name} placeholder="Unterkategorie (optional)" on:keypress={(event) => do_on_key(event, 'Enter', false , edit_step_and_close_modal)}>
<div class=add_step> <div class=add_step>
<p id=step contenteditable bind:innerHTML={edit_step.step}></p> <p id=step contenteditable bind:innerHTML={edit_step.step} on:keypress={(event) => do_on_key(event, 'Enter', true , edit_step_and_close_modal)}></p>
<button on:click="{() => edit_step_and_close_modal()}" > <button class=action_button on:click="{() => edit_step_and_close_modal()}" >
<Check fill=white style="height: 2rem; width: 2rem"></Check> <Check fill=white style="height: 2rem; width: 2rem"></Check>
</button> </button>
</div> </div>
</dialog> </dialog>
<dialog id=edit_subheading_steps_modal> <dialog id=edit_subheading_steps_modal>
<input type="text" bind:value={edit_heading.name}> <h2>Kategorie umbenennen</h2>
<button on:click={edit_subheading_steps_and_close_modal}> <div class=heading_wrapper>
<Check></Check> <input class="heading" type="text" bind:value={edit_heading.name} on:keypress={(event) => do_on_key(event, 'Enter', false, edit_subheading_steps_and_close_modal)}>
<button on:click={edit_subheading_steps_and_close_modal} class=action_button>
<Check fill=white style="height: 2rem; width: 2rem"></Check>
</button> </button>
</div>
</dialog> </dialog>

View File

@ -0,0 +1,76 @@
<script lang="ts">
export let card_data ={
}
let short_name
let password
let datecreated = new Date()
let datemodified = datecreated
import CardAdd from '$lib/components/CardAdd.svelte';
import MediaScroller from '$lib/components/MediaScroller.svelte';
import Card from '$lib/components/Card.svelte';
import Search from '$lib/components/Search.svelte';
export let season = []
import SeasonSelect from '$lib/components/SeasonSelect.svelte';
import CreateIngredientList from '$lib/components/CreateIngredientList.svelte';
export let ingredients = []
import CreateStepList from '$lib/components/CreateStepList.svelte';
export let instructions = []
async function doPost () {
const res = await fetch('/api/add', {
method: 'POST',
body: JSON.stringify({
recipe: {
season: season,
...card_data,
images: [{
mediapath: short_name + '.webp',
alt: "",
caption: ""
}],
short_name,
datecreated,
datemodified,
instructions,
ingredients,
},
headers: {
'content-type': 'application/json',
bearer: password,
}
})
})
const json = await res.json()
result = JSON.stringify(json)
console.log(result)
}
</script>
<style>
input.temp{
all: unset;
display: block;
margin: 1rem auto;
padding: 0.2em 1em;
border-radius: 1000px;
background-color: var(--nord4);
}
</style>
<CardAdd {card_data}></CardAdd>
<input class=temp bind:value={short_name} placeholder="Kurzname"/>
<SeasonSelect {season}></SeasonSelect>
<button on:click={() => console.log(season)}>PRINTOUT season</button>
<h2>Zutaten</h2>
<CreateIngredientList {ingredients}></CreateIngredientList>
<h2>Zubereitung</h2>
<CreateStepList {instructions} ></CreateStepList>
<input class=temp type="password" placeholder=Passwort bind:value={password}>

View File

@ -1,11 +1,10 @@
<script> <script>
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL v3.0 import {onMount} from "svelte";
document.addEventListener("DOMContentLoaded", () => { import "$lib/css/nordtheme.css";
for (e of document.getElementsByClassName("js-only")) {
e.classList.remove("js-only");
}
const recipes = document.querySelectorAll(".card");
onMount(() => {
const recipes = document.querySelectorAll(".search_me");
console.log("######", recipes) console.log("######", recipes)
const search = document.getElementById("search"); const search = document.getElementById("search");
const clearSearch = document.getElementById("clear-search"); const clearSearch = document.getElementById("clear-search");
@ -21,23 +20,10 @@ document.addEventListener("DOMContentLoaded", () => {
const searchString = `${recipe.textContent} ${recipe.dataset.tags}`.toLowerCase().normalize('NFD').replace(/\p{Diacritic}/gu, ""); const searchString = `${recipe.textContent} ${recipe.dataset.tags}`.toLowerCase().normalize('NFD').replace(/\p{Diacritic}/gu, "");
const isMatch = searchTerms.every(term => searchString.includes(term)); const isMatch = searchTerms.every(term => searchString.includes(term));
recipe.hidden = !isMatch; recipe.style.display = (isMatch ? 'block' : 'none');
recipe.classList.toggle("matched-recipe", hasFilter && isMatch); recipe.classList.toggle("matched-recipe", hasFilter && isMatch);
}) })
recipes.forEach(recipe => {
if(recipe.hidden == false){
recipe.parentElement.previousElementSibling.hidden = false;}
})
if(click_only_result){
let matched_recipes = document.querySelectorAll(".matched-recipe");
if(matched_recipes.length == 1 &&
matched_recipes[0].parentElement.previousElementSibling != noch_zu_probieren_header){
matched_recipes[0].lastElementChild.click();
} }
}
}
search.addEventListener("input", () => { search.addEventListener("input", () => {
do_search(); do_search();
@ -46,7 +32,7 @@ document.addEventListener("DOMContentLoaded", () => {
clearSearch.addEventListener("click", () => { clearSearch.addEventListener("click", () => {
search.value = ""; search.value = "";
recipes.forEach(recipe => { recipes.forEach(recipe => {
recipe.hidden = false; recipe.style.display = 'block';
recipe.classList.remove("matched-recipe"); recipe.classList.remove("matched-recipe");
}) })
}) })
@ -61,45 +47,66 @@ document.addEventListener("DOMContentLoaded", () => {
do_search(click_only_result=true); do_search(click_only_result=true);
} }
} }
}) });
// @license-end // @license-end
</script> </script>
<style> <style>
input#search { input#search {
all: unset; all: unset;
background: #222; font-family: sans-serif;
background: var(--nord0);
color: #fff; color: #fff;
padding: 0.7rem 1rem; padding: 0.7rem 2rem;
border-radius: 5px; border-radius: 1000px;
width: 100%; width: 100%;
} }
input::placeholder{
color: var(--nord6);
}
.search { .search {
width: 400px; width: 500px;
max-width: 85vw; max-width: 85vw;
position: relative; position: relative;
margin: 2.5rem auto 1.2rem; margin: 2.5rem auto 1.2rem;
font-size: 1.6rem;
display: flex; display: flex;
align-items: center; align-items: center;
transition: 100ms;
filter: drop-shadow(0.4em 0.5em 0.4em rgba(0,0,0,0.4))
} }
.search:hover,
.search:focus-within
{
scale: 1.02 1.02;
filter: drop-shadow(0.4em 0.5em 1em rgba(0,0,0,0.6))
}
button#clear-search { button#clear-search {
all: unset; all: unset;
display: flex;
justify-content: center;
align-items: center;
position: absolute; position: absolute;
right: 6px; right: 0.5em;
height: 30px; width: 1.5em;
width: 30px; height: 1.5em;
color: #888; color: var(--nord6);
cursor: pointer; cursor: pointer;
transition: color 180ms ease-in-out; transition: color 180ms ease-in-out;
} }
button#clear-search:hover { button#clear-search:hover {
color: #eee; color: white;
scale: 1.1 1.1;
}
button#clear-search:active{
transition: 50ms;
scale: 0.8 0.8;
} }
</style> </style>
<div class="search js-only"> <div class="search js-only">
<input type="text" id="search" placeholder="Suche nach Stichwörtern..."> <input type="text" id="search" placeholder="Suche nach Stichwörtern...">
<button id="clear-search"> <button id="clear-search">
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><title>Sucheintrag löschen</title><path d="M135.19 390.14a28.79 28.79 0 0021.68 9.86h246.26A29 29 0 00432 371.13V140.87A29 29 0 00403.13 112H156.87a28.84 28.84 0 00-21.67 9.84v0L46.33 256l88.86 134.11z" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"></path><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M336.67 192.33L206.66 322.34M336.67 322.34L206.66 192.33M336.67 192.33L206.66 322.34M336.67 322.34L206.66 192.33"></path></svg> <svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><title>Sucheintrag löschen</title><path d="M135.19 390.14a28.79 28.79 0 0021.68 9.86h246.26A29 29 0 00432 371.13V140.87A29 29 0 00403.13 112H156.87a28.84 28.84 0 00-21.67 9.84v0L46.33 256l88.86 134.11z" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"></path><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M336.67 192.33L206.66 322.34M336.67 322.34L206.66 192.33M336.67 192.33L206.66 322.34M336.67 322.34L206.66 192.33"></path></svg></button>
</button>
</div> </div>

View File

@ -1,12 +1,15 @@
<script lang="ts"> <script lang="ts">
import '$lib/components/nordtheme.css'; import '$lib/components/nordtheme.css';
import Recipes from '$lib/components/Recipes.svelte'; import Recipes from '$lib/components/Recipes.svelte';
import Search from './Search.svelte';
let months = ["Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"] let months = ["Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"]
let month : number; let month : number;
</script> </script>
<style> <style>
a.month{ a.month{
text-decoration: unset; text-decoration: unset;
font-family: sans-serif;
border-radius: 1000px; border-radius: 1000px;
background-color: var(--blue); background-color: var(--blue);
color: var(--nord5); color: var(--nord5);
@ -36,7 +39,9 @@ a.month:hover{
<a class=month href="/rezepte/season/{i+1}">{month}</a> <a class=month href="/rezepte/season/{i+1}">{month}</a>
{/each} {/each}
</div> </div>
<section>
<Search></Search>
</section>
<section> <section>
<slot name=recipes></slot> <slot name=recipes></slot>
</section> </section>

View File

@ -0,0 +1,80 @@
<script lang=ts>
import "$lib/components/nordtheme.css"
import {onMount} from "svelte";
let months = ["Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"]
export let season : Number[]
export function set_season(){
let temp = []
const el = document.getElementById("labels");
for(var i = 0; i < el.children.length; i++){
if(el.children[i].children[0].children[0].checked){
temp.push(i+1)
}
}
season = temp
}
function write_season(season){
const el = document.getElementById("labels");
for(var i = 0; i < season.length; i++){
el.children[i].children[0].children[0].checked = true
}
}
onMount(() => {
write_season(season)
});
</script>
<style>
label{
background-color: var(--nord0);
color: white;
padding: 0.25em 1em;
border-radius: 1000px;
cursor: pointer;
position: relative;
transition: 100ms;
}
.checkbox_container{
transition: 100ms;
}
.checkbox_container:hover{
transform: scale(1.1,1.1);
}
label:hover{
background-color: var(--lightblue);
}
label:has(input:checked){
background-color: var(--blue);
}
input[type=checkbox],
input[type=checkbox]::before,
input[type=checkbox]::after
{
all: unset;
}
#labels{
display: flex;
flex-wrap: wrap;
flex-direction: row;
justify-content: center;
gap: min(1rem, 1dvh);
}
</style>
<div id=labels>
{#each months as month}
<div class=checkbox_container>
<label><input type="checkbox" name="checkbox" value="value" on:click={set_season}>{month}</label>
</div>
{/each}
</div>
<button on:click={() => console.log("season", season)}> PRINT SEASON FROM SEASON_SELECT</button>

View File

@ -0,0 +1,8 @@
export function do_on_key(event, key, needsctrl, fn){
if(event.key == key){
if(needsctrl && !event.ctrlKey){
return
}
fn()
}
}

View File

@ -0,0 +1,56 @@
.action_button{
all: unset;
cursor: pointer;
background-color: var(--red);
transition: 200ms;
box-shadow: 0 0 1em 0.2em rgba(0,0,0,0.3);
padding: 1rem;
border-radius: 1000px;
display: flex;
justify-content: center;
align-items: center;
}
.action_button:hover,
.action_button:focus
{
background-color: var(--nord0);
transform: scale(1.2,1.2);
box-shadow: 0 0 1em 0.4em rgba(0,0,0,0.3);
animation: shake 0.5s ease forwards;
}
.action_button:active{
transition: 50ms;
scale: 0.8 0.8;
rotate: 30deg;
}
@keyframes shake{
0%{
transform: rotate(0)
scale(1,1);
}
25%{
box-shadow: 0em 0em 1em 0.2em rgba(0, 0, 0, 0.6);
transform: rotate(15deg)
scale(1.2,1.2)
;
}
50%{
box-shadow: 0em 0em 1em 0.2em rgba(0, 0, 0, 0.6);
transform: rotate(-15deg)
scale(1.2,1.2);
}
74%{
box-shadow: 0em 0em 1em 0.2em rgba(0, 0, 0, 0.6);
transform: rotate(15deg)
scale(1.2, 1.2);
}
100%{
transform: rotate(0)
scale(1.2, 1.2);
}
}

25
src/lib/css/nordtheme.css Normal file
View File

@ -0,0 +1,25 @@
:root{
--nord0: #2E3440;
--nord1: #3B4252;
--nord2: #434C5E;
--nord3: #4C566A;
--nord4: #D8DEE9;
--nord5: #E5E9F0;
--nord6: #ECEFF4;
--nord7: #8FBCBB;
--nord8: #88C0D0;
--nord9: #81A1C1;
--nord10: #5E81AC;
--nord11: #BF616A;
--nord12: #D08770;
--nord13: #EBCB8B;
--nord14: #A3BE8C;
--nord15: #B48EAD;
--lightblue: var(--nord9);
--blue: var(--nord10);
--red: var(--nord11);
--orange: var(--nord12);
--yellow: var(--nord13);
--green: var(--nord14);
--purple: var(--nord15);
}

View File

@ -2,7 +2,7 @@ import mongoose from 'mongoose';
const RecipeSchema = new mongoose.Schema( const RecipeSchema = new mongoose.Schema(
{ {
short_name: {type: String, required: true}, short_name: {type: String, required: true, unique: true},
name : {type: String, required: true,}, name : {type: String, required: true,},
category : {type: String, required: true,}, category : {type: String, required: true,},
icon: {type: String, required: true}, icon: {type: String, required: true},

View File

@ -12,12 +12,14 @@ export const POST: RequestHandler = async ({request}) => {
console.log("RECIPE:", recipe_json) console.log("RECIPE:", recipe_json)
console.log("BEARER:", bearer_token) console.log("BEARER:", bearer_token)
if(bearer_token === BEARER_TOKEN){ if(bearer_token === BEARER_TOKEN){
console.log("PASSWORD CORRECT")
await dbConnect(); await dbConnect();
await Recipe.create(recipe_json); await Recipe.create(recipe_json);
await dbDisconnect(); await dbDisconnect();
return {status: 400} return {status: 400} //TODO: cleanup error throwing
} }
else{ else{
console.log("PASSWORD INCORRECT")
return {status: 403} return {status: 403}
} }
}; };

View File

@ -0,0 +1,25 @@
import type { RequestHandler } from '@sveltejs/kit';
import { Recipe } from '../../../models/Recipe';
import { dbConnect, dbDisconnect } from '../../../utils/db';
import type {RecipeModelType} from '../../../types/types';
import { BEARER_TOKEN } from '$env/static/private'
// header: use for bearer token for now
// recipe json in body
export const POST: RequestHandler = async ({request}) => {
console.log("AT EDIT API")
let message = await request.json()
const recipe_json = message.recipe
const bearer_token = message.headers.bearer
console.log("RECIPE:", recipe_json)
console.log("BEARER:", bearer_token)
if(bearer_token === BEARER_TOKEN){
await dbConnect();
await Recipe.findOneAndUpdate({short_name: message.old_short_name }, recipe_json);
await dbDisconnect();
return {status: 400} //TODO: cleanup error throwing
}
else{
console.log("PASSWORD INCORRECT")
return {status: 403}
}
};

View File

@ -8,30 +8,17 @@
export let data: PageData; export let data: PageData;
export let current_month = new Date().getMonth() + 1 export let current_month = new Date().getMonth() + 1
</script> </script>
<style>
.accordion{
display:flex;
background-color: #111111;
flex-direction: row;
margin-inline: auto;
padding-inline: 1rem;
padding-block: 3rem;
margin-block: 3rem;
align-items:center;
justify-content: center;
gap: 1rem;
}
</style>
<h1>Rezepte</h1> <h1>Rezepte</h1>
<h2>In Saison</h2> <h2>In Saison</h2>
<section> <section>
<MediaScroller> <MediaScroller>
{#each data.season as recipe} {#each data.season as recipe}
<Card {recipe} {current_month}></Card> <Card {recipe} {current_month} search=""></Card>
{/each} {/each}
</MediaScroller> </MediaScroller>
</section> </section>
<!--<Search></Search>--> <Search></Search>
<h2>Alle Rezepte</h2> <h2>Alle Rezepte</h2>
<Recipes> <Recipes>
{#each data.all_brief as recipe} {#each data.all_brief as recipe}

View File

@ -1,41 +1,43 @@
<script lang="ts"> <script lang="ts">
let name export let card_data ={
}
let short_name let short_name
let category let password
let icon
let description
let datecreated = new Date() let datecreated = new Date()
let datemodified = datecreated let datemodified = datecreated
let tags
import type { PageData } from './$types';
import CardAdd from '$lib/components/CardAdd.svelte'; import CardAdd from '$lib/components/CardAdd.svelte';
import MediaScroller from '$lib/components/MediaScroller.svelte';
import Card from '$lib/components/Card.svelte'; import SeasonSelect from '$lib/components/SeasonSelect.svelte';
import Search from '$lib/components/Search.svelte'; export let season = []
import CreateIngredientList from '$lib/components/CreateIngredientList.svelte'; import CreateIngredientList from '$lib/components/CreateIngredientList.svelte';
export let ingredients = []
import CreateStepList from '$lib/components/CreateStepList.svelte'; import CreateStepList from '$lib/components/CreateStepList.svelte';
export let data: PageData; export let instructions = []
export let current_month = new Date().getMonth() + 1
async function doPost () { async function doPost () {
const res = await fetch('/api/add', { const res = await fetch('/api/add', {
method: 'POST', method: 'POST',
body: JSON.stringify({ body: JSON.stringify({
bearer: "password1234",
recipe: { recipe: {
season: get_season(),
...card_data,
images: [{
mediapath: short_name + '.webp',
alt: "",
caption: ""
}],
short_name, short_name,
name,
category,
datecreated, datecreated,
datemodified, datemodified,
tags, instructions,
description, ingredients,
icon
}, },
headers: { headers: {
'content-type': 'application/json', 'content-type': 'application/json',
bearer: "password1234", bearer: password,
} }
}) })
}) })
@ -44,68 +46,31 @@
result = JSON.stringify(json) result = JSON.stringify(json)
console.log(result) console.log(result)
} }
</script> </script>
<style> <style>
input{ input.temp{
all: unset; all: unset;
display: block; display: block;
margin: 1rem; margin: 1rem auto;
padding: 0.2em 1em; padding: 0.2em 1em;
border-radius: 1000px; border-radius: 1000px;
background-color: var(--nord4); background-color: var(--nord4);
} }
.ingredient{
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
}
.ingredient > input{
display: inline;
margin-inline: 0.25em;
}
.ingredient>#unit{
max-width: 40px;
}
.ingredient>#amount{
max-width: 100px;
}
.ingredient button{
all: unset;
background-color: var(--red);
padding: 0.3em;
height: 100%;
border-radius: 1000px;
display:flex;
justify-content: center;
align-items: center;
cursor: pointer;
transition: 100ms;
}
.ingredient button svg{
fill: white;
width: 1.5rem;
height: 1.5rem;
}
.ingredient button:hover{
background-color: var(--orange);
transform: scale(1.1, 1.1);
}
.ingredient button:hover svg{
transform: scale(1.1, 1.1);
}
</style> </style>
<h1>Rezept hinzufügen</h1> <h1>Rezept hinzufügen</h1>
<CardAdd></CardAdd> <CardAdd {card_data}></CardAdd>
<input class=temp bind:value={short_name} placeholder="Kurzname"/>
<SeasonSelect {season}></SeasonSelect>
<input bind:value={short_name} placeholder="Kurzname"/>
<h2>Zutaten</h2> <h2>Zutaten</h2>
<CreateIngredientList></CreateIngredientList> <CreateIngredientList {ingredients}></CreateIngredientList>
<h2>Zubereitung</h2> <h2>Zubereitung</h2>
<CreateStepList></CreateStepList> <CreateStepList {instructions} ></CreateStepList>
<input class=temp type="password" placeholder=Passwort bind:value={password}>
<button on:click={doPost}>ADD RECIPE</button>

View File

@ -1,12 +1,14 @@
<script lang="ts"> <script lang="ts">
import type { PageData } from './$types'; import type { PageData } from './$types';
import Recipes from '$lib/components/Recipes.svelte'; import Recipes from '$lib/components/Recipes.svelte';
import Search from '$lib/components/Search.svelte';
export let data: PageData; export let data: PageData;
export let current_month = new Date().getMonth() + 1; export let current_month = new Date().getMonth() + 1;
import Card from '$lib/components/Card.svelte' import Card from '$lib/components/Card.svelte'
</script> </script>
<h1>Rezepte</h1> <h1>Rezepte</h1>
<h2>In Kategorie {data.category}</h2> <h2>In Kategorie {data.category}</h2>
<Search></Search>
<section> <section>
<Recipes> <Recipes>
{#each data.recipes as recipe} {#each data.recipes as recipe}

View File

@ -0,0 +1 @@
{"terminal": "nvimterm"}

View File

@ -0,0 +1,102 @@
<script lang="ts">
export let data: PageData;
let old_short_name = data.recipe.short_name
export let card_data ={
icon: data.recipe.icon,
category: data.recipe.category,
name: data.recipe.name,
description: data.recipe.description,
tags: data.recipe.tags,
}
let images = data.recipe.images
let season = data.recipe.season
let short_name = data.recipe.short_name
let password
let datecreated = data.recipe.datecreated
let datemodified = new Date()
import type { PageData } from './$types';
import CardAdd from '$lib/components/CardAdd.svelte';
import MediaScroller from '$lib/components/MediaScroller.svelte';
import Card from '$lib/components/Card.svelte';
import Search from '$lib/components/Search.svelte';
import CreateIngredientList from '$lib/components/CreateIngredientList.svelte';
export let ingredients = data.recipe.ingredients
import CreateStepList from '$lib/components/CreateStepList.svelte';
export let instructions = data.recipe.instructions
function get_season(){
let season = []
const el = document.getElementById("labels");
for(var i = 0; i < el.children.length; i++){
if(el.children[i].children[0].children[0].checked){
season.push(i+1)
}
}
return season
}
function write_season(season){
const el = document.getElementById("labels");
for(var i = 0; i < season.length; i++){
el.children[i].children[0].children[0].checked = true
}
}
async function doPost () {
const res = await fetch('/api/edit', {
method: 'POST',
body: JSON.stringify({
recipe: {
...card_data,
images, // TODO
season: get_season(),
short_name,
datecreated,
datemodified,
instructions,
ingredients,
},
old_short_name,
headers: {
'content-type': 'application/json',
bearer: password,
}
})
})
const json = await res.json()
result = JSON.stringify(json)
console.log(result)
}
</script>
<style>
input{
all: unset;
display: block;
margin: 1rem auto;
padding: 0.2em 1em;
border-radius: 1000px;
background-color: var(--nord4);
}
</style>
<h1>Rezept hinzufügen</h1>
<CardAdd {card_data}></CardAdd>
<button on:click={console.log(JSON.stringify(ingredients, null, 4))}>Printout Ingredients</button>
<button on:click={console.log(JSON.stringify(instructions, null, 4))}>Printout Instructions</button>
<button on:click={console.log(JSON.stringify(card_data, null, 4))}>Prinout Card Data</button>
<input bind:value={short_name} placeholder="Kurzname"/>
<h2>Zutaten</h2>
<CreateIngredientList {ingredients}></CreateIngredientList>
<h2>Zubereitung</h2>
<CreateStepList {instructions} ></CreateStepList>
<input type="password" placeholder=Passwort bind:value={password}>
<button on:click={doPost}>EDIT RECIPE</button>

View File

@ -0,0 +1,8 @@
import type { PageLoad } from "./$types";
export async function load({ fetch, params}) {
let current_month = new Date().getMonth() + 1
const res = await fetch(`/api/items/${params.name}`);
const recipe = await res.json();
return {recipe};
};

View File

@ -0,0 +1 @@
{"terminal": "nvimterm"}

View File

@ -11,9 +11,7 @@
</script> </script>
<SeasonLayout> <SeasonLayout>
<h2 slot=test>Rezepte des Monats </h2> <h2 slot=test>Rezepte des Monats </h2>
<Recipes slot=recipes> <Recipes slot=recipes>
{#each data.season as recipe} {#each data.season as recipe}
<Card {recipe} {current_month}></Card> <Card {recipe} {current_month}></Card>

View File

@ -4,9 +4,11 @@
export let data: PageData; export let data: PageData;
export let current_month = new Date().getMonth() + 1; export let current_month = new Date().getMonth() + 1;
import Card from '$lib/components/Card.svelte' import Card from '$lib/components/Card.svelte'
import Search from '$lib/components/Search.svelte';
</script> </script>
<h1>Rezepte</h1> <h1>Rezepte</h1>
<h2>In Tag {data.tag}</h2> <h2>In Tag {data.tag}</h2>
<Search></Search>
<section> <section>
<Recipes> <Recipes>
{#each data.recipes as recipe} {#each data.recipes as recipe}