Fix plant placement + add a Seed Tray (#39)
The picker offers the full catalog, but onPickPlant resolved the choice against the garden's *referenced* plants (the /full payload only carries ListReferencedPlants). A not-yet-placed plant wasn't in that map, so the handler returned early — before arming and before closing the picker, so nothing happened and the picker stayed open. Use the full Plant the picker already hands back, and splice it into the /full cache so its plops render with an icon/color before the next refetch (useEnsurePlantInFull). Add a per-garden Seed Tray: a curated row of plant chips in the focus toolbar. Tap a chip to arm that plant for tap-to-place; picking from the catalog adds it; ✕ removes it. Persisted in localStorage, mirroring the PlantPicker's recent-plants mechanism. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01JdQpdYYsTgtkJBxbcpAszi
This commit is contained in:
@@ -7,6 +7,7 @@ import { Inspector } from '@/editor/Inspector'
|
||||
import { PlopInspector } from '@/editor/PlopInspector'
|
||||
import { PlantPicker } from '@/editor/PlantPicker'
|
||||
import { Palette } from '@/editor/Palette'
|
||||
import { SeedTray } from '@/editor/SeedTray'
|
||||
import { ClearBedModal } from '@/editor/ClearBedModal'
|
||||
import { EditorHint } from '@/editor/EditorHint'
|
||||
import { objectDisplayName } from '@/editor/kinds'
|
||||
@@ -14,8 +15,10 @@ import { useEditorStore } from '@/editor/store'
|
||||
import type { EditorGarden } from '@/editor/types'
|
||||
import { ShareGardenModal } from '@/components/gardens/ShareGardenModal'
|
||||
import { useMe } from '@/lib/auth'
|
||||
import { toEditorObject, useGardenFull, useUpdateObject, useUpdatePlanting } from '@/lib/objects'
|
||||
import { toEditorObject, useEnsurePlantInFull, useGardenFull, useUpdateObject, useUpdatePlanting } from '@/lib/objects'
|
||||
import { toEditorPlanting } from '@/lib/plantings'
|
||||
import type { Plant } from '@/lib/plants'
|
||||
import { useSeedTray } from '@/lib/seedTray'
|
||||
import { usePageTitle } from '@/lib/usePageTitle'
|
||||
|
||||
const routeApi = getRouteApi('/gardens/$gardenId')
|
||||
@@ -43,6 +46,8 @@ export function GardenEditorPage() {
|
||||
|
||||
const updatePlanting = useUpdatePlanting(gid)
|
||||
const updateObject = useUpdateObject(gid)
|
||||
const ensurePlant = useEnsurePlantInFull(gid)
|
||||
const { trayPlants, add: addToTray, remove: removeFromTray } = useSeedTray(gid)
|
||||
|
||||
// Which plant-picker flow is open: 'place' arms a plant for repeat placement;
|
||||
// 'change' swaps the selected plop's plant.
|
||||
@@ -231,14 +236,26 @@ export function GardenEditorPage() {
|
||||
const selectedPlop = plantings.find((p) => p.id === selectedPlantingId) ?? null
|
||||
const focusedPlops = focusedObjectId != null ? plantings.filter((p) => p.objectId === focusedObjectId) : []
|
||||
|
||||
function onPickPlant(plantId: number) {
|
||||
// Arm a plant for tap-to-place. The full catalog carries plants not yet in this
|
||||
// garden's /full payload, so make sure the chosen one is in the cache first —
|
||||
// otherwise its placed plops render without an icon/color until a refetch.
|
||||
function armPlant(plant: Plant) {
|
||||
ensurePlant(plant)
|
||||
setArmedPlant(plant)
|
||||
}
|
||||
|
||||
// The picker hands back the full Plant (from the whole catalog), so use it
|
||||
// directly rather than re-resolving against the garden's referenced-plant map —
|
||||
// a not-yet-placed plant isn't in that map, which used to silently abort the
|
||||
// pick (nothing armed, picker left open).
|
||||
function onPickPlant(plant: Plant) {
|
||||
if (!canEdit) return // defense in depth: viewers can't reach the picker anyway
|
||||
const plant = plantsById.get(plantId)
|
||||
if (!plant) return
|
||||
addToTray(plant)
|
||||
if (picker === 'change' && selectedPlop) {
|
||||
ensurePlant(plant)
|
||||
updatePlanting.mutate({ id: selectedPlop.id, version: selectedPlop.version, plantId: plant.id })
|
||||
} else {
|
||||
setArmedPlant(plant) // 'place' mode: arm for repeat placement
|
||||
armPlant(plant) // 'place' mode: arm for repeat placement
|
||||
}
|
||||
setPicker(null)
|
||||
}
|
||||
@@ -267,18 +284,21 @@ export function GardenEditorPage() {
|
||||
← {garden.name}
|
||||
</button>
|
||||
<span className="max-w-[8rem] truncate text-muted">{objectDisplayName(focusedObject)}</span>
|
||||
{canEdit &&
|
||||
(!focusedObject.plantable ? (
|
||||
<span className="text-xs text-muted">Not plantable</span>
|
||||
) : armedPlant ? (
|
||||
<Button variant="ghost" className="px-2 py-1 text-xs" onClick={() => setArmedPlant(null)}>
|
||||
Placing {armedPlant.icon} — Done
|
||||
</Button>
|
||||
) : (
|
||||
<Button className="px-2 py-1 text-xs" onClick={() => setPicker('place')}>
|
||||
+ Add plant
|
||||
</Button>
|
||||
))}
|
||||
{canEdit && !focusedObject.plantable && <span className="text-xs text-muted">Not plantable</span>}
|
||||
{canEdit && focusedObject.plantable && (
|
||||
<SeedTray
|
||||
plants={trayPlants}
|
||||
armedPlantId={armedPlant?.id ?? null}
|
||||
onArm={armPlant}
|
||||
onRemove={removeFromTray}
|
||||
onOpenPicker={() => setPicker('place')}
|
||||
/>
|
||||
)}
|
||||
{canEdit && focusedObject.plantable && armedPlant && (
|
||||
<Button variant="ghost" className="px-2 py-1 text-xs" onClick={() => setArmedPlant(null)}>
|
||||
Done
|
||||
</Button>
|
||||
)}
|
||||
{canEdit && focusedObject.plantable && focusedPlops.length > 0 && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -297,7 +317,7 @@ export function GardenEditorPage() {
|
||||
<EditorHint>Pick a shape from the palette, then tap the field to place your first bed.</EditorHint>
|
||||
)}
|
||||
{canEdit && focusedObject?.plantable && focusedPlops.length === 0 && !armedPlant && (
|
||||
<EditorHint position="top">Tap “+ Add plant”, then tap inside the bed to place plops.</EditorHint>
|
||||
<EditorHint position="top">Pick a plant from the tray, then tap inside the bed to place it.</EditorHint>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -335,7 +355,7 @@ export function GardenEditorPage() {
|
||||
<PlantPicker
|
||||
unit={garden.unitPref}
|
||||
onClose={() => setPicker(null)}
|
||||
onSelect={(p) => onPickPlant(p.id)}
|
||||
onSelect={(p) => onPickPlant(p)}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user