Add configurable grid + snap to the layout system
Two independent grids, each with a size and a snap toggle: - Garden grid (owner-set, in the garden settings form): drives the editor's visual grid and, when snapping is on, snaps objects on place/move and snaps their dimensions to whole grid steps on resize. - Bed grid (per plantable object, in the bed Inspector): when snapping is on, plants snap to the bed's grid on place/move, and the grid is drawn inside the focused bed. Plant radius stays free. Snapping defaults off on both, so existing gardens keep free placement. Grid spacing shares the [1cm, 100m] range and defaults to 1m (garden) / 30cm (bed); 0 is treated as unset and re-defaulted. Backend: migration 0005 adds grid_size_cm/snap_to_grid to gardens and garden_objects, threaded through domain/store/service/api with service + api round-trip tests. Frontend: new geometry snap helpers (unit-tested), schema/type plumbing, canvas + overlay snapping, and the two forms. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01JdQpdYYsTgtkJBxbcpAszi
This commit is contained in:
@@ -11,21 +11,23 @@ import (
|
||||
|
||||
// objectColumns lists garden_objects columns in the order scanObject expects.
|
||||
const objectColumns = `id, garden_id, kind, name, shape, points, x_cm, y_cm, width_cm, height_cm,
|
||||
rotation_deg, z_index, plantable, color, props, notes, version, created_at, updated_at`
|
||||
rotation_deg, z_index, plantable, color, props, grid_size_cm, snap_to_grid, notes, version, created_at, updated_at`
|
||||
|
||||
func scanObject(s scanner) (*domain.GardenObject, error) {
|
||||
var (
|
||||
o domain.GardenObject
|
||||
plantable int64
|
||||
snap int64
|
||||
)
|
||||
if err := s.Scan(
|
||||
&o.ID, &o.GardenID, &o.Kind, &o.Name, &o.Shape, &o.Points,
|
||||
&o.XCM, &o.YCM, &o.WidthCM, &o.HeightCM, &o.RotationDeg, &o.ZIndex,
|
||||
&plantable, &o.Color, &o.Props, &o.Notes, &o.Version, &o.CreatedAt, &o.UpdatedAt,
|
||||
&plantable, &o.Color, &o.Props, &o.GridSizeCM, &snap, &o.Notes, &o.Version, &o.CreatedAt, &o.UpdatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
o.Plantable = plantable != 0
|
||||
o.SnapToGrid = snap != 0
|
||||
return &o, nil
|
||||
}
|
||||
|
||||
@@ -35,11 +37,12 @@ func (d *DB) CreateObject(ctx context.Context, o *domain.GardenObject) (*domain.
|
||||
created, err := scanObject(d.sql.QueryRowContext(ctx,
|
||||
`INSERT INTO garden_objects
|
||||
(garden_id, kind, name, shape, points, x_cm, y_cm, width_cm, height_cm,
|
||||
rotation_deg, z_index, plantable, color, props, notes)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
rotation_deg, z_index, plantable, color, props, grid_size_cm, snap_to_grid, notes)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
RETURNING `+objectColumns,
|
||||
o.GardenID, o.Kind, o.Name, o.Shape, o.Points, o.XCM, o.YCM, o.WidthCM, o.HeightCM,
|
||||
o.RotationDeg, o.ZIndex, boolToInt(o.Plantable), o.Color, o.Props, o.Notes,
|
||||
o.RotationDeg, o.ZIndex, boolToInt(o.Plantable), o.Color, o.Props,
|
||||
o.GridSizeCM, boolToInt(o.SnapToGrid), o.Notes,
|
||||
))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("store: insert object: %w", err)
|
||||
@@ -95,13 +98,14 @@ func (d *DB) UpdateObject(ctx context.Context, o *domain.GardenObject) (*domain.
|
||||
`UPDATE garden_objects
|
||||
SET kind = ?, name = ?, shape = ?, points = ?, x_cm = ?, y_cm = ?,
|
||||
width_cm = ?, height_cm = ?, rotation_deg = ?, z_index = ?,
|
||||
plantable = ?, color = ?, props = ?, notes = ?,
|
||||
plantable = ?, color = ?, props = ?, grid_size_cm = ?, snap_to_grid = ?, notes = ?,
|
||||
version = version + 1,
|
||||
updated_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
|
||||
WHERE id = ? AND version = ?
|
||||
RETURNING `+objectColumns,
|
||||
o.Kind, o.Name, o.Shape, o.Points, o.XCM, o.YCM, o.WidthCM, o.HeightCM,
|
||||
o.RotationDeg, o.ZIndex, boolToInt(o.Plantable), o.Color, o.Props, o.Notes,
|
||||
o.RotationDeg, o.ZIndex, boolToInt(o.Plantable), o.Color, o.Props,
|
||||
o.GridSizeCM, boolToInt(o.SnapToGrid), o.Notes,
|
||||
o.ID, o.Version,
|
||||
))
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
|
||||
Reference in New Issue
Block a user