expose h_align/v_align through full stack

display_hint becomes {kind, h_align, v_align} object in API, SQLite
gets alignment columns, SPA widget form gets alignment selects, layout
preview reflects actual alignment instead of hardcoded center
This commit is contained in:
2026-06-19 10:28:09 +02:00
parent ca2ef61097
commit b448fa15fe
8 changed files with 200 additions and 43 deletions

View File

@@ -1,4 +1,12 @@
export type DisplayHint = "icon_value" | "text_block" | "key_value"
export type DisplayHintKind = "icon_value" | "text_block" | "key_value"
export type HAlign = "left" | "center" | "right"
export type VAlign = "top" | "middle" | "bottom"
export interface DisplayHint {
kind: DisplayHintKind
h_align: HAlign
v_align: VAlign
}
export type SourceType = "weather" | "media" | "rss" | "http_json" | "webhook"
export type SizingType = "fixed" | "flex"
export type Direction = "row" | "column"

View File

@@ -54,6 +54,11 @@ export function LayoutPreview({
const box = bounds.get(wid)
if (!box) return null
const w = widgets.find((w) => w.id === wid)
const hAlign = w?.display_hint?.h_align ?? "left"
const vAlign = w?.display_hint?.v_align ?? "top"
const flexAlign = hAlign === "center" ? "center" : hAlign === "right" ? "flex-end" : "flex-start"
const flexJustify = vAlign === "middle" ? "center" : vAlign === "bottom" ? "flex-end" : "flex-start"
const textAlign = hAlign === "center" ? "center" as const : hAlign === "right" ? "right" as const : "left" as const
return (
<div
key={wid}
@@ -67,8 +72,8 @@ export function LayoutPreview({
boxSizing: "border-box",
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
alignItems: flexAlign,
justifyContent: flexJustify,
overflow: "hidden",
padding: 2 * scale,
}}
@@ -77,7 +82,7 @@ export function LayoutPreview({
style={{
fontSize: 10 * scale,
color: colorToCSS(theme.text),
textAlign: "center",
textAlign,
lineHeight: 1.2,
}}
>
@@ -88,10 +93,10 @@ export function LayoutPreview({
style={{
fontSize: 8 * scale,
color: colorToCSS(theme.accent),
textAlign: "center",
textAlign,
}}
>
{w.display_hint}
{w.display_hint.kind}
</span>
)}
</div>

View File

@@ -7,7 +7,7 @@ import {
useWidgetPreview,
} from "@/api/widgets"
import { useDataSources } from "@/api/data-sources"
import type { Widget, DisplayHint, KeyMapping } from "@/api/types"
import type { Widget, DisplayHintKind, HAlign, VAlign, KeyMapping } from "@/api/types"
import { Button } from "@/components/ui/button"
import {
Card,
@@ -46,13 +46,12 @@ import { Badge } from "@/components/ui/badge"
import { Plus, Pencil, Trash2, X, Eye } from "lucide-react"
import { toast } from "sonner"
const DISPLAY_HINTS: DisplayHint[] = ["icon_value", "text_block", "key_value"]
const DISPLAY_HINT_KINDS: DisplayHintKind[] = ["icon_value", "text_block", "key_value"]
const EMPTY: Widget = {
id: 0,
name: "",
display_hint: "icon_value",
display_hint: { kind: "icon_value", h_align: "left", v_align: "top" },
data_source_id: 0,
mappings: [],
max_data_size: 2048,
@@ -141,7 +140,7 @@ export function WidgetsPage() {
<div className="space-y-1">
<CardTitle className="text-base">{w.name}</CardTitle>
<CardDescription className="flex items-center gap-2">
<Badge variant="secondary">{w.display_hint}</Badge>
<Badge variant="secondary">{w.display_hint.kind}</Badge>
<span>source: {sourceName(w.data_source_id)}</span>
<span>{w.mappings.length} mapping(s)</span>
</CardDescription>
@@ -326,14 +325,14 @@ function WidgetForm({
<div className="grid gap-2">
<Label>Display Hint</Label>
<Select
value={value.display_hint}
onValueChange={(v) => set("display_hint", v as DisplayHint)}
value={value.display_hint.kind}
onValueChange={(v) => set("display_hint", { ...value.display_hint, kind: v as DisplayHintKind })}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
{DISPLAY_HINTS.map((h) => (
{DISPLAY_HINT_KINDS.map((h) => (
<SelectItem key={h} value={h}>
{h}
</SelectItem>
@@ -341,6 +340,40 @@ function WidgetForm({
</SelectContent>
</Select>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="grid gap-2">
<Label>H Align</Label>
<Select
value={value.display_hint.h_align}
onValueChange={(v) => set("display_hint", { ...value.display_hint, h_align: v as HAlign })}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="left">Left</SelectItem>
<SelectItem value="center">Center</SelectItem>
<SelectItem value="right">Right</SelectItem>
</SelectContent>
</Select>
</div>
<div className="grid gap-2">
<Label>V Align</Label>
<Select
value={value.display_hint.v_align}
onValueChange={(v) => set("display_hint", { ...value.display_hint, v_align: v as VAlign })}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="top">Top</SelectItem>
<SelectItem value="middle">Middle</SelectItem>
<SelectItem value="bottom">Bottom</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="grid gap-2">
<Label>Data Source</Label>
<Select