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:
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user