|
|
@@ -1,149 +1,15 @@
|
|
|
<script>
|
|
|
- import {notify} from "$lib/notifier.svelte.js";
|
|
|
- import {goto} from "$app/navigation";
|
|
|
-
|
|
|
- let nameInput, quantityInput, stepsInput;
|
|
|
- let name = $state("");
|
|
|
- let quantity = $state();
|
|
|
- let unit = $state("");
|
|
|
- let ingredientName = $state("");
|
|
|
- let ingredients = $state([]);
|
|
|
- let stepString = $state("");
|
|
|
- let steps = $state([]);
|
|
|
- let notes = $state("");
|
|
|
- let visibility = $state("private");
|
|
|
-
|
|
|
- const submit = async (event)=>{
|
|
|
- event.preventDefault();
|
|
|
-
|
|
|
- if(!name){
|
|
|
- notify("error", "Please enter recipe name");
|
|
|
- nameInput.focus();
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- const data = {
|
|
|
- name, ingredients, steps, notes,
|
|
|
- private: visibility === "private" ? true : false
|
|
|
- };
|
|
|
-
|
|
|
- const response = await fetch("/recipe/new", {
|
|
|
- method: "POST",
|
|
|
- headers: {"Content-Type": "application/json"},
|
|
|
- credentials: "include",
|
|
|
- body: JSON.stringify(data)
|
|
|
- });
|
|
|
-
|
|
|
- const recipe = await response.json();
|
|
|
- if(!response.ok) return notify("error", recipe.message);
|
|
|
-
|
|
|
- goto("/dashboard");
|
|
|
- }
|
|
|
-
|
|
|
- const addIngredient = ()=>{
|
|
|
- if(quantity <= 0) return notify("error", "Quantity must be greater than 0");
|
|
|
- if(!unit) return notify("error", "Unit is required");
|
|
|
- if(!ingredientName) return notify("error", "Ingredient name is required");
|
|
|
-
|
|
|
- ingredients.push({
|
|
|
- quantity: quantity,
|
|
|
- unit: unit,
|
|
|
- name: ingredientName
|
|
|
- });
|
|
|
-
|
|
|
- quantity = null;
|
|
|
- unit = "";
|
|
|
- ingredientName = "";
|
|
|
-
|
|
|
- quantityInput.focus();
|
|
|
- }
|
|
|
-
|
|
|
- const addStep = ()=>{
|
|
|
- if(!stepString) return notify("error", "Please enter some text");
|
|
|
- steps.push(stepString);
|
|
|
- stepString = "";
|
|
|
- stepsInput.focus();
|
|
|
- }
|
|
|
-
|
|
|
- $effect(()=>{
|
|
|
- nameInput?.focus();
|
|
|
- });
|
|
|
+ import EditableRecipe from "../EditableRecipe.svelte";
|
|
|
</script>
|
|
|
|
|
|
<div class="container">
|
|
|
- <form class="standardForm" onsubmit={submit}>
|
|
|
- <h1>New Recipe</h1>
|
|
|
-
|
|
|
- <label>Name
|
|
|
- <input type="text" bind:this={nameInput} bind:value={name}>
|
|
|
- </label>
|
|
|
-
|
|
|
- <div class="ingredients">
|
|
|
- <h2>Ingredients</h2>
|
|
|
-
|
|
|
- <ul>
|
|
|
- {#each ingredients as i}
|
|
|
- <li>{i.name}, {i.quantity} {i.unit}</li>
|
|
|
- {/each}
|
|
|
- </ul>
|
|
|
-
|
|
|
- <label>Quantity
|
|
|
- <input type="number" min="0" bind:value={quantity} bind:this={quantityInput}>
|
|
|
- </label>
|
|
|
-
|
|
|
- <label>Unit
|
|
|
- <input type="text" bind:value={unit}>
|
|
|
- </label>
|
|
|
-
|
|
|
- <label>Name
|
|
|
- <input type="text" bind:value={ingredientName}>
|
|
|
- </label>
|
|
|
-
|
|
|
- <button class="button" type="button" onclick={addIngredient}>Add</button>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="steps">
|
|
|
- <h2>Steps</h2>
|
|
|
-
|
|
|
- <ol>
|
|
|
- {#each steps as s}
|
|
|
- <li>{s}</li>
|
|
|
- {/each}
|
|
|
- </ol>
|
|
|
-
|
|
|
- <textarea bind:value={stepString} rows="2" bind:this={stepsInput}></textarea>
|
|
|
-
|
|
|
- <button class="button" type="button" onclick={addStep}>Add</button>
|
|
|
- </div>
|
|
|
-
|
|
|
- <textarea bind:value={notes} rows="4" placeholder="Notes"></textarea>
|
|
|
-
|
|
|
- <fieldset class="visibility">
|
|
|
- <legend>Visibility</legend>
|
|
|
- <div class="visibilityOptions">
|
|
|
- <label>
|
|
|
- <input
|
|
|
- type="radio"
|
|
|
- name="visibility"
|
|
|
- value="private"
|
|
|
- bind:group={visibility}
|
|
|
- >
|
|
|
- <span>Private</span>
|
|
|
- </label>
|
|
|
- <label>
|
|
|
- <input
|
|
|
- type="radio"
|
|
|
- name="visibility"
|
|
|
- value="public"
|
|
|
- bind:group={visibility}
|
|
|
- >
|
|
|
- <span>Public</span>
|
|
|
- </label>
|
|
|
- </div>
|
|
|
- </fieldset>
|
|
|
-
|
|
|
- <button>Create Recipe</button>
|
|
|
- </form>
|
|
|
+ <EditableRecipe
|
|
|
+ name=""
|
|
|
+ ingredients={[]}
|
|
|
+ steps={[]}
|
|
|
+ notes=""
|
|
|
+ visibility="private"
|
|
|
+ />
|
|
|
</div>
|
|
|
|
|
|
<style>
|
|
|
@@ -160,317 +26,4 @@
|
|
|
overflow-x: clip;
|
|
|
box-sizing: border-box;
|
|
|
}
|
|
|
-
|
|
|
- .standardForm{
|
|
|
- width: min(640px, 100%);
|
|
|
- max-width: 100%;
|
|
|
- min-width: 0;
|
|
|
- padding: 1.15rem 0.9rem;
|
|
|
- container-type: inline-size;
|
|
|
- container-name: recipe-form;
|
|
|
- }
|
|
|
-
|
|
|
- h1{
|
|
|
- font-size: clamp(1.25rem, 1rem + 2vw, 1.5rem);
|
|
|
- }
|
|
|
-
|
|
|
- h2{
|
|
|
- color: var(--color-text);
|
|
|
- font-size: clamp(1rem, 0.9rem + 0.5vw, 1.1rem);
|
|
|
- font-weight: 700;
|
|
|
- letter-spacing: 0.01em;
|
|
|
- }
|
|
|
-
|
|
|
- input,
|
|
|
- textarea{
|
|
|
- width: 100%;
|
|
|
- min-width: 0;
|
|
|
- max-width: 100%;
|
|
|
- font-size: 1rem;
|
|
|
- }
|
|
|
-
|
|
|
- .ingredients,
|
|
|
- .steps{
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- gap: 0.75rem;
|
|
|
- padding: 0.75rem;
|
|
|
- min-width: 0;
|
|
|
- max-width: 100%;
|
|
|
- background: var(--color-surface-alt);
|
|
|
- border: 1px solid var(--color-border);
|
|
|
- border-radius: 8px;
|
|
|
- }
|
|
|
-
|
|
|
- ul,
|
|
|
- ol{
|
|
|
- margin: 0;
|
|
|
- padding: 0;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- gap: 0.45rem;
|
|
|
- list-style: none;
|
|
|
- min-width: 0;
|
|
|
- max-width: 100%;
|
|
|
- }
|
|
|
-
|
|
|
- ul:not(:has(li)),
|
|
|
- ol:not(:has(li)){
|
|
|
- display: none;
|
|
|
- }
|
|
|
-
|
|
|
- li{
|
|
|
- padding: 0.55rem 0.7rem;
|
|
|
- background: var(--color-surface);
|
|
|
- border: 1px solid var(--color-border);
|
|
|
- border-left: 3px solid var(--color-primary);
|
|
|
- border-radius: 6px;
|
|
|
- font-size: 0.95rem;
|
|
|
- line-height: 1.4;
|
|
|
- color: var(--color-text);
|
|
|
- overflow-wrap: anywhere;
|
|
|
- word-break: break-word;
|
|
|
- min-width: 0;
|
|
|
- max-width: 100%;
|
|
|
- }
|
|
|
-
|
|
|
- ol{
|
|
|
- counter-reset: step;
|
|
|
- }
|
|
|
-
|
|
|
- ol li{
|
|
|
- display: grid;
|
|
|
- grid-template-columns: 1.5rem minmax(0, 1fr);
|
|
|
- align-items: start;
|
|
|
- gap: 0.7rem;
|
|
|
- counter-increment: step;
|
|
|
- }
|
|
|
-
|
|
|
- ol li::before{
|
|
|
- content: counter(step);
|
|
|
- flex-shrink: 0;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- width: 1.5rem;
|
|
|
- height: 1.5rem;
|
|
|
- border-radius: 50%;
|
|
|
- background: var(--color-primary);
|
|
|
- color: var(--color-text-on-primary);
|
|
|
- font-size: 0.75rem;
|
|
|
- font-weight: 700;
|
|
|
- line-height: 1;
|
|
|
- }
|
|
|
-
|
|
|
- .ingredients{
|
|
|
- display: grid;
|
|
|
- grid-template-columns: minmax(0, 1fr);
|
|
|
- align-items: stretch;
|
|
|
- gap: 0.75rem;
|
|
|
- }
|
|
|
-
|
|
|
- .ingredients > *{
|
|
|
- min-width: 0;
|
|
|
- max-width: 100%;
|
|
|
- }
|
|
|
-
|
|
|
- .button{
|
|
|
- margin: 0;
|
|
|
- width: 100%;
|
|
|
- height: auto;
|
|
|
- min-height: 2.5rem;
|
|
|
- white-space: nowrap;
|
|
|
- }
|
|
|
-
|
|
|
- textarea{
|
|
|
- resize: vertical;
|
|
|
- min-height: 3.5rem;
|
|
|
- }
|
|
|
-
|
|
|
- .standardForm > textarea{
|
|
|
- min-height: 5rem;
|
|
|
- }
|
|
|
-
|
|
|
- .visibility{
|
|
|
- border: 0;
|
|
|
- margin: 0;
|
|
|
- padding: 0;
|
|
|
- min-width: 0;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- gap: 0.4rem;
|
|
|
- }
|
|
|
-
|
|
|
- .visibility legend{
|
|
|
- float: left;
|
|
|
- width: 100%;
|
|
|
- padding: 0;
|
|
|
- margin: 0;
|
|
|
- color: var(--color-text);
|
|
|
- font-size: 0.9rem;
|
|
|
- font-weight: 600;
|
|
|
- }
|
|
|
-
|
|
|
- .visibilityOptions{
|
|
|
- position: relative;
|
|
|
- display: grid;
|
|
|
- grid-template-columns: 1fr 1fr;
|
|
|
- gap: 0;
|
|
|
- width: 100%;
|
|
|
- clear: both;
|
|
|
- padding: 0.2rem;
|
|
|
- background: var(--color-surface-alt);
|
|
|
- border: 1px solid var(--color-border);
|
|
|
- border-radius: 999px;
|
|
|
- isolation: isolate;
|
|
|
- }
|
|
|
-
|
|
|
- .visibilityOptions::before{
|
|
|
- content: "";
|
|
|
- position: absolute;
|
|
|
- top: 0.2rem;
|
|
|
- left: 0.2rem;
|
|
|
- width: calc(50% - 0.2rem);
|
|
|
- height: calc(100% - 0.4rem);
|
|
|
- background: var(--color-primary);
|
|
|
- border-radius: 999px;
|
|
|
- transition: transform 0.2s ease;
|
|
|
- pointer-events: none;
|
|
|
- z-index: 0;
|
|
|
- }
|
|
|
-
|
|
|
- .visibilityOptions:has(input[value="public"]:checked)::before{
|
|
|
- transform: translateX(100%);
|
|
|
- }
|
|
|
-
|
|
|
- .visibilityOptions label{
|
|
|
- position: relative;
|
|
|
- z-index: 1;
|
|
|
- flex-direction: row;
|
|
|
- align-items: stretch;
|
|
|
- justify-content: stretch;
|
|
|
- gap: 0;
|
|
|
- margin: 0;
|
|
|
- cursor: pointer;
|
|
|
- }
|
|
|
-
|
|
|
- .visibilityOptions input{
|
|
|
- position: absolute;
|
|
|
- inset: 0;
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- margin: 0;
|
|
|
- padding: 0;
|
|
|
- opacity: 0;
|
|
|
- border: 0;
|
|
|
- background: none;
|
|
|
- appearance: none;
|
|
|
- cursor: pointer;
|
|
|
- }
|
|
|
-
|
|
|
- .visibilityOptions span{
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- width: 100%;
|
|
|
- min-height: 2.5rem;
|
|
|
- padding: 0.45rem 0.8rem;
|
|
|
- background: transparent;
|
|
|
- border: 0;
|
|
|
- border-radius: 999px;
|
|
|
- color: var(--color-text-muted);
|
|
|
- font-size: 0.95rem;
|
|
|
- font-weight: 600;
|
|
|
- line-height: 1.2;
|
|
|
- text-align: center;
|
|
|
- transition: color 0.15s ease;
|
|
|
- }
|
|
|
-
|
|
|
- .visibilityOptions input:hover + span{
|
|
|
- color: var(--color-text);
|
|
|
- }
|
|
|
-
|
|
|
- .visibilityOptions input:checked + span{
|
|
|
- color: var(--color-text-on-primary);
|
|
|
- }
|
|
|
-
|
|
|
- .visibilityOptions input:focus-visible + span{
|
|
|
- outline: 2px solid var(--color-primary);
|
|
|
- outline-offset: 2px;
|
|
|
- }
|
|
|
-
|
|
|
- @container recipe-form (min-width: 260px){
|
|
|
- .ingredients{
|
|
|
- grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
|
|
- align-items: end;
|
|
|
- }
|
|
|
-
|
|
|
- .ingredients h2,
|
|
|
- .ingredients ul,
|
|
|
- .ingredients label:last-of-type,
|
|
|
- .ingredients .button{
|
|
|
- grid-column: 1 / -1;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- @container recipe-form (min-width: 480px){
|
|
|
- .ingredients,
|
|
|
- .steps{
|
|
|
- padding: 1rem;
|
|
|
- }
|
|
|
-
|
|
|
- .ingredients{
|
|
|
- grid-template-columns: minmax(0, 5.5rem) minmax(0, 6.5rem) minmax(0, 1fr) auto;
|
|
|
- align-items: end;
|
|
|
- }
|
|
|
-
|
|
|
- .ingredients h2,
|
|
|
- .ingredients ul{
|
|
|
- grid-column: 1 / -1;
|
|
|
- }
|
|
|
-
|
|
|
- .ingredients label:last-of-type,
|
|
|
- .ingredients .button{
|
|
|
- grid-column: auto;
|
|
|
- }
|
|
|
-
|
|
|
- .ingredients .button{
|
|
|
- width: auto;
|
|
|
- min-height: 0;
|
|
|
- height: fit-content;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- @media (min-width: 640px){
|
|
|
- .container{
|
|
|
- padding: 2rem 1rem 3rem;
|
|
|
- }
|
|
|
-
|
|
|
- .standardForm{
|
|
|
- padding: 2rem;
|
|
|
- }
|
|
|
-
|
|
|
- .standardForm > textarea{
|
|
|
- min-height: 6rem;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- @media (max-height: 500px){
|
|
|
- .container{
|
|
|
- padding-top: 0.75rem;
|
|
|
- padding-bottom: 1rem;
|
|
|
- }
|
|
|
-
|
|
|
- .standardForm > textarea{
|
|
|
- min-height: 3.5rem;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- @media (pointer: coarse){
|
|
|
- .button,
|
|
|
- .standardForm button,
|
|
|
- .visibilityOptions span{
|
|
|
- min-height: 44px;
|
|
|
- }
|
|
|
- }
|
|
|
</style>
|