[PIE-13] New recipe page (!11)
Add `/recipe/new` path with form to add new recipe Mostly designed but not fully: steps and ingredients are inputtable with final styling but it cannot be submitted yet, and other crucial components like description, rating, etc are not yet implemented. Many design changes too cos i couldnt help myself More additions will certainly be required but this PR is huge so I will split it out into more Reviewed-on: #11 Co-authored-by: june <self@breadone.net> Co-committed-by: june <self@breadone.net>
This commit is contained in:
parent
26cab10c14
commit
0b1334d508
@ -10,7 +10,7 @@ const ings = await Promise.all(
|
|||||||
|
|
||||||
<div class={className}>
|
<div class={className}>
|
||||||
{ings.map(i => (
|
{ings.map(i => (
|
||||||
<div>
|
<div class="text-sm">
|
||||||
<p>• {i.quantity} {i.unit || " "} {i.name}</p>
|
<p>• {i.quantity} {i.unit || " "} {i.name}</p>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
@ -3,10 +3,10 @@ import { record } from "astro:schema"
|
|||||||
import client from "@/data/pocketbase"
|
import client from "@/data/pocketbase"
|
||||||
import StepIngredientSideView from "./StepIngredientSideView.astro"
|
import StepIngredientSideView from "./StepIngredientSideView.astro"
|
||||||
|
|
||||||
const { steps } = Astro.props
|
const { steps, class: className } = Astro.props
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="md:ml-5 mt-2 md:mt-0">
|
<div class={className}>
|
||||||
<p class="text-[22pt] font-bold md:hidden">Steps</p>
|
<p class="text-[22pt] font-bold md:hidden">Steps</p>
|
||||||
{ steps.map(s => (
|
{ steps.map(s => (
|
||||||
<div class="bg-[#2a2b2c] rounded-lg mb-2 p-3">
|
<div class="bg-[#2a2b2c] rounded-lg mb-2 p-3">
|
||||||
@ -15,7 +15,7 @@ const { steps } = Astro.props
|
|||||||
<div class="flex flex-col md:flex-row md:items-stretch">
|
<div class="flex flex-col md:flex-row md:items-stretch">
|
||||||
<p class="w-full md:flex-2/3 pr-1 text-left">{s.instruction}</p>
|
<p class="w-full md:flex-2/3 pr-1 text-left">{s.instruction}</p>
|
||||||
{s.ingredients && s.ingredients.length > 0 && (
|
{s.ingredients && s.ingredients.length > 0 && (
|
||||||
<div class="w-full md:w-auto md:flex-1/3 mt-2 md:mt-0 md:ml-2 md:pl-3 md:border-l text-white/70 border-white/70">
|
<div class="w-full md:w-auto md:flex-2/5 mt-2 md:mt-0 md:ml-2 md:pl-3 md:border-l text-white/70 border-white/70">
|
||||||
<StepIngredientSideView class="text-left" ingredients={s.ingredients} />
|
<StepIngredientSideView class="text-left" ingredients={s.ingredients} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -7,8 +7,8 @@
|
|||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="ml-auto space-x-5">
|
<div class="ml-auto space-x-5">
|
||||||
<a>new</a>
|
<a class="hover:underline underline-offset-4 " href="/recipe/new">new</a>
|
||||||
<a>tags</a>
|
<a class="hover:underline underline-offset-4 " >tags</a>
|
||||||
<a>search</a>
|
<a class="hover:underline underline-offset-4 " >search</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
@ -28,10 +28,10 @@ const ingredients = await Promise.all(
|
|||||||
---
|
---
|
||||||
|
|
||||||
<SiteLayout>
|
<SiteLayout>
|
||||||
<div class="flex flex-col md:flex-row">
|
<div class="flex flex-col md:flex-row mx-auto justify-center w-full lg:max-w-3/4 xl:max-w-2/3 2xl:max-w-1/2">
|
||||||
<div class="flex flex-col mt-2 md:mt-4 sticky">
|
<div class="flex md:flex-1/3 flex-col mt-2 md:mt-4 sticky">
|
||||||
<ImageCarousel class="w-full" recipe={re} />
|
<ImageCarousel class="w-full" recipe={re} />
|
||||||
<p class=" md:hidden text-[28pt] font-bold leading-none mt-2">{re.name}</p>
|
<p class="text-[28pt] font-bold leading-11 mt-2">{re.name}</p>
|
||||||
|
|
||||||
<!-- Details -->
|
<!-- Details -->
|
||||||
<InfoView re={re} />
|
<InfoView re={re} />
|
||||||
@ -40,11 +40,9 @@ const ingredients = await Promise.all(
|
|||||||
<IngredientTableView class:list={['md:w-80', 'px-4']} ingredients={ingredients} />
|
<IngredientTableView class:list={['md:w-80', 'px-4']} ingredients={ingredients} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex w-full flex-col">
|
<div class="flex mt-4 md:flex-2/3 w-full flex-col">
|
||||||
<p class="hidden md:block text-[28pt] font-bold pl-5">{re.name}</p>
|
|
||||||
|
|
||||||
<!-- Steps -->
|
<!-- Steps -->
|
||||||
<StepView steps={steps} />
|
<StepView class="md:ml-3" steps={steps} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
94
src/pages/recipe/new.astro
Normal file
94
src/pages/recipe/new.astro
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
---
|
||||||
|
import SiteLayout from "@/layouts/base";
|
||||||
|
|
||||||
|
const { recipeid } = Astro.params;
|
||||||
|
|
||||||
|
// Actually post the recipe with the stored variables
|
||||||
|
async function submitRecipe() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
<script src="@/script/newRecipe.ts"/>
|
||||||
|
|
||||||
|
<SiteLayout>
|
||||||
|
<div class="flex flex-col md:flex-row mx-auto justify-center w-full lg:max-w-3/4 xl:max-w-2/3 2xl:max-w-1/2">
|
||||||
|
<div class="flex md:flex-1/3 flex-col mt-2 md:mt-4 sticky">
|
||||||
|
<div class="relative">
|
||||||
|
<input
|
||||||
|
id="photo"
|
||||||
|
type="file"
|
||||||
|
accept="image/png,image/jpeg"
|
||||||
|
class="w-full bg-white/10 rounded-lg h-50
|
||||||
|
file:mr-4 file:py-2 file:px-4
|
||||||
|
file:rounded-lg file:border-0 file:hidden
|
||||||
|
before:content-['camera'] before:w-full before:h-full before:flex
|
||||||
|
before:items-center before:justify-center before:absolute
|
||||||
|
relative cursor-pointer
|
||||||
|
[&::-webkit-file-upload-button]:hidden"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<textarea
|
||||||
|
id="rec-name"
|
||||||
|
rows="1"
|
||||||
|
placeholder="Name"
|
||||||
|
class="text-[28pt] font-bold p-1 leading-none mt-2 bg-white/10 rounded-lg resize-none overflow-hidden"
|
||||||
|
oninput="this.style.height = ''; this.style.height = this.scrollHeight + 'px'"
|
||||||
|
/>
|
||||||
|
<!-- if it works :3 -->
|
||||||
|
|
||||||
|
<!-- Details -->
|
||||||
|
<!-- <InfoView re={re} /> -->
|
||||||
|
|
||||||
|
<div class="flex flex-row align-middle items-center">
|
||||||
|
<p class="mt-4 text-[22pt] font-bold 'mt-4'">Ingredients</p>
|
||||||
|
<button disabled class="disabled:text-white/20 transition-colors ml-auto mt-5 text-white bg-white/10 rounded-lg px-3 py-1 " id="add-ingredient-btn" >Add</button>
|
||||||
|
</div>
|
||||||
|
<table class={`table-fixed text-left bg-[#2a2b2c] rounded-lg w-full`}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="px-4 py-2 w-18">Qty</th>
|
||||||
|
<th class="px-4 py-2 w-20">Unit</th>
|
||||||
|
<th class="px-4 py-2">Ingredient</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="ingredient-table" class="w-full border-t px-4 py-2 border-white/10">
|
||||||
|
<tr id="ingredient-input" class="">
|
||||||
|
<td class="px-2 py-1">
|
||||||
|
<input id="ing-qty" class="w-full h-9 my-1 bg-white/10 rounded-lg px-2 py-2" type="text" placeholder="Qty">
|
||||||
|
</td>
|
||||||
|
<td class="px-2 py-1">
|
||||||
|
<input id="ing-unit" class="w-full h-9 bg-white/10 rounded-lg px-2 py-2" type="text" placeholder="Unit">
|
||||||
|
</td>
|
||||||
|
<td class="px-2 py-1">
|
||||||
|
<!-- <textarea id="ing-name" class="w-full h-11 bg-white/20 rounded-lg px-2 py-3 mt-1 resize-none leading-tight" placeholder="Ingredient" rows="1"/> -->
|
||||||
|
<input id="ing-name" class="w-full h-9 bg-white/10 rounded-lg px-2 py-2" type="text" placeholder="Ingredient">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex mt-4 md:flex-2/3 w-full flex-col md:ml-3">
|
||||||
|
<!-- <p class="hidden md:block text-[28pt] font-bold pl-5">Helloi</p> -->
|
||||||
|
|
||||||
|
<!-- Steps -->
|
||||||
|
<p class="text-[22pt] font-bold md:hidden">Steps</p>
|
||||||
|
|
||||||
|
<div class="bg-[#2a2b2c] rounded-lg mb-2 p-2">
|
||||||
|
<!-- <p class="text-bold text-[10pt]">Step</p> -->
|
||||||
|
<textarea
|
||||||
|
id="new-instruction"
|
||||||
|
class="block bg-white/10 w-full h-full rounded-lg resize-none px-3 py-1"
|
||||||
|
placeholder="New Step"
|
||||||
|
oninput="this.style.height = ''; this.style.height = this.scrollHeight + 'px'"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button disabled class="disabled:text-white/20 ml-auto mb-2 transition-colors text-white bg-white/10 rounded-lg px-3 py-1 " id="add-step-btn" >Add</button>
|
||||||
|
|
||||||
|
<ul id="step-list"></ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</SiteLayout>
|
185
src/script/newRecipe.ts
Normal file
185
src/script/newRecipe.ts
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
let ingredientFields: HTMLInputElement[] = []
|
||||||
|
let ingredientTable: HTMLTableSectionElement = document.querySelector('#ingredient-table')!
|
||||||
|
let ingredientAddButton: HTMLButtonElement = document.querySelector('#add-ingredient-btn')!
|
||||||
|
|
||||||
|
let stepInput: HTMLTextAreaElement = document.querySelector('#new-instruction')!
|
||||||
|
let stepAddButton: HTMLButtonElement = document.querySelector('#add-step-btn')!
|
||||||
|
let stepList: HTMLUListElement = document.querySelector('#step-list')!
|
||||||
|
let currentStepIndex = 0
|
||||||
|
|
||||||
|
// - VARS
|
||||||
|
let ingredients: {qty: string, unit: string, name: string}[] = []
|
||||||
|
let steps: {
|
||||||
|
index: number,
|
||||||
|
instruction: string,
|
||||||
|
ingredients: string[], // IDs of ingredient fields
|
||||||
|
}[] = []
|
||||||
|
|
||||||
|
// - INIT
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
|
||||||
|
ingredientFields.push(
|
||||||
|
document.querySelector('#ing-qty')!,
|
||||||
|
document.querySelector('#ing-unit')!,
|
||||||
|
document.querySelector('#ing-name')!
|
||||||
|
)
|
||||||
|
|
||||||
|
stepInput.addEventListener('input', showAddStepButton)
|
||||||
|
|
||||||
|
// show plus button once the user types in the text fields
|
||||||
|
ingredientFields.forEach(f => {
|
||||||
|
f.addEventListener('input', showAddIngredientButton)
|
||||||
|
f.addEventListener('keydown', showAddIngredientButton)
|
||||||
|
})
|
||||||
|
// onclick for add button
|
||||||
|
document.querySelector('#add-ingredient-btn')?.addEventListener('click', addIngredient);
|
||||||
|
|
||||||
|
// Enter key navigation for ingredient fields
|
||||||
|
ingredientFields[0].addEventListener('keydown', e => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
ingredientFields[1].focus() // Move from qty to unit
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
ingredientFields[1].addEventListener('keydown', e => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
ingredientFields[2].focus() // Move from unit to name
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// for pressing enter to add ingredient (on the last field)
|
||||||
|
ingredientFields[2].addEventListener('keydown', e => {if (e.key === 'Enter') addIngredient()} )
|
||||||
|
|
||||||
|
// Initial check for button state
|
||||||
|
showAddIngredientButton()
|
||||||
|
showAddStepButton()
|
||||||
|
|
||||||
|
// Steps
|
||||||
|
stepInput.addEventListener('keyup', e => { if (e.key === 'Enter' && e.shiftKey) addStep() } )
|
||||||
|
stepAddButton.addEventListener('click', addStep)
|
||||||
|
});
|
||||||
|
|
||||||
|
// - ADD
|
||||||
|
function addIngredient() {
|
||||||
|
const ing = {
|
||||||
|
qty: ingredientFields[0].value,
|
||||||
|
unit: ingredientFields[1].value,
|
||||||
|
name: ingredientFields[2].value
|
||||||
|
}
|
||||||
|
|
||||||
|
ingredients.push(ing)
|
||||||
|
|
||||||
|
const newRow = document.createElement('tr')
|
||||||
|
newRow.innerHTML = `
|
||||||
|
<td class="px-4 py-2 border-t border-white/10">${ing.qty}</td>
|
||||||
|
<td class="px-4 py-2 border-t border-white/10">${ing.unit}</td>
|
||||||
|
<td class="px-4 py-2 border-t border-white/10">${ing.name}</td>
|
||||||
|
`
|
||||||
|
|
||||||
|
// Add row to table and clear fields
|
||||||
|
ingredientTable.appendChild(newRow)
|
||||||
|
ingredientFields.forEach(f => f.value = '')
|
||||||
|
ingredientAddButton.disabled = true // Hide Add Ingredient button
|
||||||
|
// move cursor to Qty field again
|
||||||
|
ingredientFields[0].focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
function addStep() {
|
||||||
|
const step = {
|
||||||
|
index: currentStepIndex++,
|
||||||
|
instruction: stepInput.value,
|
||||||
|
ingredients: []
|
||||||
|
}
|
||||||
|
|
||||||
|
steps.push(step)
|
||||||
|
renderSteps()
|
||||||
|
stepInput.value = ''
|
||||||
|
stepAddButton.disabled = true
|
||||||
|
stepInput.focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderSteps() {
|
||||||
|
// clear the step list
|
||||||
|
stepList.innerHTML = ''
|
||||||
|
|
||||||
|
// re-render all steps in their current order
|
||||||
|
steps.forEach((step, displayIndex) => {
|
||||||
|
const newStep = document.createElement('div')
|
||||||
|
// times like this i regret using astro
|
||||||
|
newStep.innerHTML = `
|
||||||
|
<div class="flex justify-between items-start ">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<p class="text-bold text-[10pt]">Step ${displayIndex + 1}</p>
|
||||||
|
<div class="flex flex-col md:flex-row md:items-stretch">
|
||||||
|
<p class="w-full md:flex-2/3 pr-1 text-left">${step.instruction}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<button id="move-up-btn" class="px-2 py-1 bg-white/10 hover:bg-white/30 transition-colors rounded text-xs" data-step-index="${step.index}" ${displayIndex === 0 ? 'disabled' : ''}>↑</button>
|
||||||
|
|
||||||
|
<button id="move-down-btn" class="px-2 py-1 bg-white/10 hover:bg-white/30 transition-colors rounded text-xs" data-step-index="${step.index}" ${displayIndex === steps.length - 1 ? 'disabled' : ''}>↓</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
newStep.id = `step-${step.index}`
|
||||||
|
newStep.className = "bg-[#2a2b2c] rounded-lg mb-2 p-3"
|
||||||
|
stepList.appendChild(newStep)
|
||||||
|
})
|
||||||
|
|
||||||
|
// event listeners to reorder buttons
|
||||||
|
document.querySelectorAll('#move-up-btn').forEach(btn => {
|
||||||
|
btn.addEventListener('click', (e) => {
|
||||||
|
const stepIndex = parseInt((e.target as HTMLButtonElement).dataset.stepIndex!)
|
||||||
|
moveStep(stepIndex, -1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
document.querySelectorAll('#move-down-btn').forEach(btn => {
|
||||||
|
btn.addEventListener('click', (e) => {
|
||||||
|
const stepIndex = parseInt((e.target as HTMLButtonElement).dataset.stepIndex!)
|
||||||
|
moveStep(stepIndex, 1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// - UTILS
|
||||||
|
function showAddIngredientButton() {
|
||||||
|
const hasQty = ingredientFields[0].value.trim().length > 0
|
||||||
|
const hasName = ingredientFields[2].value.trim().length > 0
|
||||||
|
|
||||||
|
if (hasQty && hasName) {
|
||||||
|
ingredientAddButton.disabled = false
|
||||||
|
} else {
|
||||||
|
ingredientAddButton.disabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showAddStepButton() {
|
||||||
|
if (stepInput.value.trim().length > 0) {
|
||||||
|
stepAddButton.disabled = false
|
||||||
|
} else {
|
||||||
|
stepAddButton.disabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// shift: the direction to move. should be +1 to move down or -1 to move up the list
|
||||||
|
function moveStep(stepIndex: number, shift: number) {
|
||||||
|
// Find the step in the array
|
||||||
|
const currentStepArrayIndex = steps.findIndex(step => step.index === stepIndex)
|
||||||
|
|
||||||
|
if (currentStepArrayIndex === -1) return // step not found
|
||||||
|
|
||||||
|
const newIndex = currentStepArrayIndex + shift
|
||||||
|
|
||||||
|
// check bounds
|
||||||
|
if (newIndex < 0 || newIndex >= steps.length) return
|
||||||
|
|
||||||
|
// swap the steps in the array
|
||||||
|
const temp = steps[currentStepArrayIndex]
|
||||||
|
steps[currentStepArrayIndex] = steps[newIndex]
|
||||||
|
steps[newIndex] = temp
|
||||||
|
|
||||||
|
// re-render the steps
|
||||||
|
renderSteps()
|
||||||
|
}
|
@ -7,6 +7,7 @@
|
|||||||
"paths": {
|
"paths": {
|
||||||
"@/components/*": ["src/components/*.astro"],
|
"@/components/*": ["src/components/*.astro"],
|
||||||
"@/layouts/*": ["src/layouts/*.astro"],
|
"@/layouts/*": ["src/layouts/*.astro"],
|
||||||
|
"@/script/*": ["src/script/*"],
|
||||||
"@/utils": ["src/utils/index.ts"],
|
"@/utils": ["src/utils/index.ts"],
|
||||||
"@/data/*": ["src/data/*"],
|
"@/data/*": ["src/data/*"],
|
||||||
"@/site-config": ["src/site.config.ts"]
|
"@/site-config": ["src/site.config.ts"]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user