Build image / build-and-push (push) Successful in 14s
Co-authored-by: Steve Dudenhoeffer <[email protected]>
71 lines
2.3 KiB
TypeScript
71 lines
2.3 KiB
TypeScript
// Plantings ("plops") data layer: the zod shape for a /full planting plus the
|
|
// EditorPlanting the canvas renders. Optimistic create/update/remove mutations
|
|
// live in objects.ts alongside the FullGarden cache they patch (a plop is part
|
|
// of the one-shot editor payload).
|
|
|
|
import { z } from 'zod'
|
|
|
|
export const serverPlantingSchema = z.object({
|
|
id: z.number(),
|
|
objectId: z.number(),
|
|
plantId: z.number(),
|
|
xCm: z.number(),
|
|
yCm: z.number(),
|
|
radiusCm: z.number(),
|
|
count: z.number().nullable().optional(), // null/absent = derived
|
|
label: z.string().nullable().optional(),
|
|
plantedAt: z.string().nullable().optional(),
|
|
removedAt: z.string().nullable().optional(),
|
|
derivedCount: z.number(), // computed server-side from area / spacing²
|
|
version: z.number(),
|
|
createdAt: z.string(),
|
|
updatedAt: z.string(),
|
|
})
|
|
export type ServerPlanting = z.infer<typeof serverPlantingSchema>
|
|
|
|
/** The plop shape the canvas renders. x/y/radius are in the parent object's
|
|
* local frame (origin at object center), so a plop tracks its object. */
|
|
export interface EditorPlanting {
|
|
id: number
|
|
objectId: number
|
|
plantId: number
|
|
xCm: number
|
|
yCm: number
|
|
radiusCm: number
|
|
count: number | null
|
|
derivedCount: number
|
|
label: string | null
|
|
plantedAt: string | null
|
|
version: number
|
|
}
|
|
|
|
export function toEditorPlanting(p: ServerPlanting): EditorPlanting {
|
|
return {
|
|
id: p.id,
|
|
objectId: p.objectId,
|
|
plantId: p.plantId,
|
|
xCm: p.xCm,
|
|
yCm: p.yCm,
|
|
radiusCm: p.radiusCm,
|
|
count: p.count ?? null,
|
|
derivedCount: p.derivedCount,
|
|
label: p.label ?? null,
|
|
plantedAt: p.plantedAt ?? null,
|
|
version: p.version,
|
|
}
|
|
}
|
|
|
|
/** The count a plop shows: its explicit override, else the derived value. */
|
|
export function effectiveCount(p: { count: number | null; derivedCount: number }): number {
|
|
return p.count ?? p.derivedCount
|
|
}
|
|
|
|
/** Client-side mirror of the server's derived-count formula, for live display
|
|
* while resizing a plop (before the PATCH round-trips). max(1, round(π·r² /
|
|
* spacing²)), capped like the server. */
|
|
export function computeDerivedCount(radiusCm: number, spacingCm: number): number {
|
|
if (radiusCm <= 0 || spacingCm <= 0) return 1
|
|
const n = Math.round((Math.PI * radiusCm * radiusCm) / (spacingCm * spacingCm))
|
|
return Math.max(1, Math.min(1_000_000, n))
|
|
}
|