Add "Scan a packet" to the editor's Plants mode
#102 shipped the seed-packet scan UI in the Plants catalog; Steve's call was to also surface it in the editor's Plants mode, so you can add a variety mid-planting without leaving the garden. Adds a capability-gated "📷 Scan packet" entry: - in the shared PlantPlacementTools cluster (so it appears in both the desktop focus toolbar and the mobile Plants strip when a plantable bed is focused), and - in the mobile Plants-mode hint shown before a bed is focused, both opening the same ScanPacketModal (gated on `capabilities.vision`, so it's never a dead button). Vite hoists the now-two-importer modal into its own shared chunk, so nothing is duplicated. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01H3zbym8Doka2d7D48maSgZ
This commit is contained in:
@@ -13,6 +13,7 @@ import { PlantPicker } from '@/editor/PlantPicker'
|
|||||||
import { Palette } from '@/editor/Palette'
|
import { Palette } from '@/editor/Palette'
|
||||||
import { SeedTray } from '@/editor/SeedTray'
|
import { SeedTray } from '@/editor/SeedTray'
|
||||||
import { RecentPlants } from '@/editor/RecentPlants'
|
import { RecentPlants } from '@/editor/RecentPlants'
|
||||||
|
import { ScanPacketModal } from '@/components/plants/ScanPacketModal'
|
||||||
import { ClearBedModal } from '@/editor/ClearBedModal'
|
import { ClearBedModal } from '@/editor/ClearBedModal'
|
||||||
import { EditorHint } from '@/editor/EditorHint'
|
import { EditorHint } from '@/editor/EditorHint'
|
||||||
import { SeasonBanner, SeasonPicker } from '@/editor/SeasonPicker'
|
import { SeasonBanner, SeasonPicker } from '@/editor/SeasonPicker'
|
||||||
@@ -108,6 +109,7 @@ export function GardenEditorPage() {
|
|||||||
const [picker, setPicker] = useState<'place' | 'change' | null>(null)
|
const [picker, setPicker] = useState<'place' | 'change' | null>(null)
|
||||||
const [sharing, setSharing] = useState(false)
|
const [sharing, setSharing] = useState(false)
|
||||||
const [clearing, setClearing] = useState(false)
|
const [clearing, setClearing] = useState(false)
|
||||||
|
const [scanning, setScanning] = useState(false)
|
||||||
const nudgeTimer = useRef<number | null>(null)
|
const nudgeTimer = useRef<number | null>(null)
|
||||||
const nudgeFire = useRef<(() => void) | null>(null)
|
const nudgeFire = useRef<(() => void) | null>(null)
|
||||||
|
|
||||||
@@ -632,6 +634,8 @@ export function GardenEditorPage() {
|
|||||||
onClear={() => setClearing(true)}
|
onClear={() => setClearing(true)}
|
||||||
onFill={fillBed}
|
onFill={fillBed}
|
||||||
filling={fillObject.isPending}
|
filling={fillObject.isPending}
|
||||||
|
canScan={!!capabilities.data?.vision}
|
||||||
|
onScan={() => setScanning(true)}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<span className="text-xs text-muted">Not plantable</span>
|
<span className="text-xs text-muted">Not plantable</span>
|
||||||
@@ -706,6 +710,8 @@ export function GardenEditorPage() {
|
|||||||
onClear={() => setClearing(true)}
|
onClear={() => setClearing(true)}
|
||||||
onFill={fillBed}
|
onFill={fillBed}
|
||||||
filling={fillObject.isPending}
|
filling={fillObject.isPending}
|
||||||
|
canScan={!!capabilities.data?.vision}
|
||||||
|
onScan={() => setScanning(true)}
|
||||||
/>
|
/>
|
||||||
<Button variant="ghost" className="ml-auto px-2 py-1 text-xs" onClick={exitFocus}>
|
<Button variant="ghost" className="ml-auto px-2 py-1 text-xs" onClick={exitFocus}>
|
||||||
Done planting
|
Done planting
|
||||||
@@ -724,7 +730,18 @@ export function GardenEditorPage() {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
<p className="px-1 text-xs text-muted">Tap a bed, then “🌱 Plant here” to start planting.</p>
|
<div className="flex items-center gap-2">
|
||||||
|
<p className="px-1 text-xs text-muted">Tap a bed, then “🌱 Plant here” to start planting.</p>
|
||||||
|
{capabilities.data?.vision && (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
className="ml-auto px-2 py-1 text-xs"
|
||||||
|
onClick={() => setScanning(true)}
|
||||||
|
>
|
||||||
|
📷 Scan packet
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -741,6 +758,8 @@ export function GardenEditorPage() {
|
|||||||
|
|
||||||
{sharing && <ShareGardenModal garden={g} onClose={() => setSharing(false)} />}
|
{sharing && <ShareGardenModal garden={g} onClose={() => setSharing(false)} />}
|
||||||
|
|
||||||
|
{scanning && <ScanPacketModal unit={garden.unitPref} onClose={() => setScanning(false)} />}
|
||||||
|
|
||||||
{clearing && focusedObject && (
|
{clearing && focusedObject && (
|
||||||
<ClearBedModal
|
<ClearBedModal
|
||||||
objectId={focusedObject.id}
|
objectId={focusedObject.id}
|
||||||
@@ -768,6 +787,8 @@ function PlantPlacementTools({
|
|||||||
onClear,
|
onClear,
|
||||||
onFill,
|
onFill,
|
||||||
filling,
|
filling,
|
||||||
|
canScan,
|
||||||
|
onScan,
|
||||||
}: {
|
}: {
|
||||||
recentPlants: Plant[]
|
recentPlants: Plant[]
|
||||||
trayPlants: Plant[]
|
trayPlants: Plant[]
|
||||||
@@ -780,6 +801,10 @@ function PlantPlacementTools({
|
|||||||
onClear: () => void
|
onClear: () => void
|
||||||
onFill: (layout: FillLayout) => void
|
onFill: (layout: FillLayout) => void
|
||||||
filling: boolean
|
filling: boolean
|
||||||
|
// Scanning a packet adds a variety to the catalog mid-planting; only offered
|
||||||
|
// where a vision model is configured.
|
||||||
|
canScan: boolean
|
||||||
|
onScan: () => void
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -791,6 +816,11 @@ function PlantPlacementTools({
|
|||||||
onRemove={onRemove}
|
onRemove={onRemove}
|
||||||
onOpenPicker={onOpenPicker}
|
onOpenPicker={onOpenPicker}
|
||||||
/>
|
/>
|
||||||
|
{canScan && (
|
||||||
|
<Button variant="ghost" className="px-2 py-1 text-xs" onClick={onScan}>
|
||||||
|
📷 Scan packet
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
{armedPlant && <FillControl onFill={onFill} busy={filling} />}
|
{armedPlant && <FillControl onFill={onFill} busy={filling} />}
|
||||||
{armedPlant && (
|
{armedPlant && (
|
||||||
<Button variant="ghost" className="px-2 py-1 text-xs" onClick={onDisarm}>
|
<Button variant="ghost" className="px-2 py-1 text-xs" onClick={onDisarm}>
|
||||||
|
|||||||
Reference in New Issue
Block a user