Co-authored-by: Steve Dudenhoeffer <[email protected]>
This commit was merged in pull request #63.
This commit is contained in:
@@ -3,6 +3,8 @@ import { getRouteApi } from '@tanstack/react-router'
|
||||
import { Alert } from '@/components/ui/Alert'
|
||||
import { Button } from '@/components/ui/Button'
|
||||
import { GardenCanvas } from '@/editor/GardenCanvas'
|
||||
import { EditorRail, type RailTab } from '@/editor/EditorRail'
|
||||
import { HistoryPanel } from '@/editor/HistoryPanel'
|
||||
import { Inspector } from '@/editor/Inspector'
|
||||
import { PlopInspector } from '@/editor/PlopInspector'
|
||||
import { PlantPicker } from '@/editor/PlantPicker'
|
||||
@@ -38,11 +40,10 @@ export function GardenEditorPage() {
|
||||
const selectPlanting = useEditorStore((s) => s.selectPlanting)
|
||||
const focusedObjectId = useEditorStore((s) => s.focusedObjectId)
|
||||
const setFocusedObject = useEditorStore((s) => s.setFocusedObject)
|
||||
const railTab = useEditorStore((s) => s.railTab)
|
||||
const setRailTab = useEditorStore((s) => s.setRailTab)
|
||||
const armedPlant = useEditorStore((s) => s.armedPlant)
|
||||
const setArmedPlant = useEditorStore((s) => s.setArmedPlant)
|
||||
const setArmedKind = useEditorStore((s) => s.setArmedKind)
|
||||
const setLiveObject = useEditorStore((s) => s.setLiveObject)
|
||||
const setLivePlanting = useEditorStore((s) => s.setLivePlanting)
|
||||
|
||||
const updatePlanting = useUpdatePlanting(gid)
|
||||
const updateObject = useUpdateObject(gid)
|
||||
@@ -79,14 +80,9 @@ export function GardenEditorPage() {
|
||||
// gardens (and on leaving). `focus` is intentionally read once here, not a dep,
|
||||
// so URL syncs below don't retrigger a full reset.
|
||||
useEffect(() => {
|
||||
const clear = () => {
|
||||
select(null)
|
||||
selectPlanting(null)
|
||||
setArmedKind(null)
|
||||
setArmedPlant(null)
|
||||
setLiveObject(null)
|
||||
setLivePlanting(null)
|
||||
}
|
||||
// resetTransient is the single list of ephemeral editor state, so new state
|
||||
// (the rail tab did exactly this) can't be forgotten here.
|
||||
const clear = () => useEditorStore.getState().resetTransient()
|
||||
clear()
|
||||
setFocusedObject(focus ?? null)
|
||||
return () => {
|
||||
@@ -110,6 +106,17 @@ export function GardenEditorPage() {
|
||||
}
|
||||
}, [focusedObjectId, objects, full.data, setFocusedObject])
|
||||
|
||||
// Selecting anything lands you in the inspector without a click — the rail
|
||||
// must never be something you operate before you can edit. Keyed off the
|
||||
// selected ids rather than a "something is selected" boolean, so selecting a
|
||||
// DIFFERENT object while History is open still brings the inspector forward.
|
||||
// Deselecting drops back out of the inspector but leaves History/Chat open if
|
||||
// that's where you were, since those aren't about the selection.
|
||||
useEffect(() => {
|
||||
if (selectedId != null || selectedPlantingId != null) setRailTab('inspector')
|
||||
else if (useEditorStore.getState().railTab === 'inspector') setRailTab(null)
|
||||
}, [selectedId, selectedPlantingId, setRailTab])
|
||||
|
||||
const exitFocus = () => {
|
||||
setFocusedObject(null)
|
||||
setArmedPlant(null)
|
||||
@@ -270,6 +277,48 @@ export function GardenEditorPage() {
|
||||
setPicker(null)
|
||||
}
|
||||
|
||||
// One rail, tabs inside it — the layout decision #49 settles for the journal
|
||||
// (#53) and chat (#57) panels too: they add a tab here rather than each
|
||||
// claiming their own strip of screen.
|
||||
const railTabs: RailTab[] = [
|
||||
{
|
||||
id: 'inspector',
|
||||
label: 'Inspector',
|
||||
render: () =>
|
||||
selectedObject ? (
|
||||
<Inspector
|
||||
key={`obj-${selectedObject.id}`}
|
||||
object={selectedObject}
|
||||
gardenId={gid}
|
||||
unit={garden.unitPref}
|
||||
readOnly={!canEdit}
|
||||
onFocus={() => {
|
||||
setFocusedObject(selectedObject.id)
|
||||
select(null)
|
||||
}}
|
||||
/>
|
||||
) : selectedPlop ? (
|
||||
<PlopInspector
|
||||
key={`plop-${selectedPlop.id}`}
|
||||
plop={selectedPlop}
|
||||
plant={plantsById.get(selectedPlop.plantId)}
|
||||
gardenId={gid}
|
||||
unit={garden.unitPref}
|
||||
readOnly={!canEdit}
|
||||
onChangePlant={() => setPicker('change')}
|
||||
onClose={() => selectPlanting(null)}
|
||||
/>
|
||||
) : (
|
||||
<p className="text-sm text-muted">Select a bed or a planting to edit it.</p>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'history',
|
||||
label: 'History',
|
||||
render: () => <HistoryPanel gardenId={gid} canEdit={canEdit} />,
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="flex h-[calc(100vh-8rem)] flex-col gap-3 md:flex-row">
|
||||
<div className="shrink-0 md:w-40">
|
||||
@@ -285,6 +334,13 @@ export function GardenEditorPage() {
|
||||
<p className="mb-2 rounded-md bg-border/40 px-2 py-1 text-xs text-muted">👁 View only</p>
|
||||
)}
|
||||
{focusedObjectId == null && canEdit && <Palette />}
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="mt-2 w-full text-sm"
|
||||
onClick={() => setRailTab(railTab === 'history' ? null : 'history')}
|
||||
>
|
||||
History
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="relative min-h-0 flex-1">
|
||||
@@ -335,34 +391,21 @@ export function GardenEditorPage() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{(selectedObject || selectedPlop) && (
|
||||
<div className="fixed inset-x-0 bottom-0 z-30 max-h-[65vh] overflow-y-auto rounded-t-xl border-t border-border bg-surface p-4 shadow-lg md:static md:max-h-none md:w-72 md:shrink-0 md:rounded-xl md:border md:p-4 md:shadow-sm">
|
||||
{selectedObject && (
|
||||
<Inspector
|
||||
key={`obj-${selectedObject.id}`}
|
||||
object={selectedObject}
|
||||
gardenId={gid}
|
||||
unit={garden.unitPref}
|
||||
readOnly={!canEdit}
|
||||
onFocus={() => {
|
||||
setFocusedObject(selectedObject.id)
|
||||
select(null)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{selectedPlop && (
|
||||
<PlopInspector
|
||||
key={`plop-${selectedPlop.id}`}
|
||||
plop={selectedPlop}
|
||||
plant={plantsById.get(selectedPlop.plantId)}
|
||||
gardenId={gid}
|
||||
unit={garden.unitPref}
|
||||
readOnly={!canEdit}
|
||||
onChangePlant={() => setPicker('change')}
|
||||
onClose={() => selectPlanting(null)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{railTab && (
|
||||
<EditorRail
|
||||
tabs={railTabs}
|
||||
activeId={railTab}
|
||||
onActivate={setRailTab}
|
||||
onClose={() => {
|
||||
// Only the inspector is *about* the selection, so only closing it
|
||||
// deselects; dismissing History leaves the canvas as you had it.
|
||||
if (railTab === 'inspector') {
|
||||
select(null)
|
||||
selectPlanting(null)
|
||||
}
|
||||
setRailTab(null)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{picker && (
|
||||
|
||||
Reference in New Issue
Block a user