feat: add disabled state to ImageRotatorCard and handle paused state in ImageRotatorWidget
All checks were successful
Build App / build (push) Successful in 7m14s
All checks were successful
Build App / build (push) Successful in 7m14s
This commit is contained in:
@@ -68,6 +68,7 @@ interface ClockCard {
|
|||||||
interface ImageRotatorCard {
|
interface ImageRotatorCard {
|
||||||
id: string; type: "image_rotator"; name: string;
|
id: string; type: "image_rotator"; name: string;
|
||||||
config: { images: string[]; interval: number; fit: "cover" | "contain" };
|
config: { images: string[]; interval: number; fit: "cover" | "contain" };
|
||||||
|
disabled?: boolean;
|
||||||
layout?: CardLayout;
|
layout?: CardLayout;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,6 +86,7 @@ export type DataCard = CustomJsonCard | StaticTextCard | ClockCard | ImageRotato
|
|||||||
interface FormState {
|
interface FormState {
|
||||||
type: CardType;
|
type: CardType;
|
||||||
name: string;
|
name: string;
|
||||||
|
disabled: boolean;
|
||||||
// layout
|
// layout
|
||||||
grid_col: number; grid_row: number; col_span: number; row_span: number;
|
grid_col: number; grid_row: number; col_span: number; row_span: number;
|
||||||
// display (custom_json / static_text / clock)
|
// display (custom_json / static_text / clock)
|
||||||
@@ -112,6 +114,7 @@ interface FormState {
|
|||||||
const EMPTY_FORM: FormState = {
|
const EMPTY_FORM: FormState = {
|
||||||
type: "custom_json",
|
type: "custom_json",
|
||||||
name: "",
|
name: "",
|
||||||
|
disabled: false,
|
||||||
grid_col: 1, grid_row: 1, col_span: 1, row_span: 1,
|
grid_col: 1, grid_row: 1, col_span: 1, row_span: 1,
|
||||||
font_size: "16",
|
font_size: "16",
|
||||||
text_color: "#ffffff",
|
text_color: "#ffffff",
|
||||||
@@ -186,6 +189,7 @@ function cardToForm(card: DataCard): FormState {
|
|||||||
image_urls: card.config.images ?? [],
|
image_urls: card.config.images ?? [],
|
||||||
image_interval: String(card.config.interval ?? 10),
|
image_interval: String(card.config.interval ?? 10),
|
||||||
image_fit: card.config.fit ?? "cover",
|
image_fit: card.config.fit ?? "cover",
|
||||||
|
disabled: (card as ImageRotatorCard).disabled ?? false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (card.type === "spotify") {
|
if (card.type === "spotify") {
|
||||||
@@ -246,6 +250,7 @@ function formToCard(form: FormState, id: string): DataCard {
|
|||||||
fit: form.image_fit,
|
fit: form.image_fit,
|
||||||
},
|
},
|
||||||
layout,
|
layout,
|
||||||
|
disabled: form.disabled,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (form.type === "spotify") {
|
if (form.type === "spotify") {
|
||||||
@@ -967,6 +972,17 @@ function FormView({ form, onChange, onBoolChange, onLayoutChange, onTypeChange,
|
|||||||
<FieldLabel text="Name *" />
|
<FieldLabel text="Name *" />
|
||||||
<TextInput style={styles.input} placeholder="My Widget" placeholderTextColor={colors.placeholderColor} value={form.name} onChangeText={(v) => onChange("name", v)} />
|
<TextInput style={styles.input} placeholder="My Widget" placeholderTextColor={colors.placeholderColor} value={form.name} onChangeText={(v) => onChange("name", v)} />
|
||||||
</View>
|
</View>
|
||||||
|
{form.type === "image_rotator" && (
|
||||||
|
<View style={[styles.field, styles.row, { alignItems: "center" }] }>
|
||||||
|
<Text style={[styles.label, { flex: 1 }]}>Paused</Text>
|
||||||
|
<Switch
|
||||||
|
value={form.disabled}
|
||||||
|
onValueChange={(v) => onBoolChange("disabled", v)}
|
||||||
|
trackColor={{ true: colors.accent, false: colors.border }}
|
||||||
|
thumbColor="#fff"
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
<SectionLabel text={form.type === "custom_json" ? "Data Source" : form.type === "static_text" ? "Content" : form.type === "clock" ? "Clock Settings" : form.type === "spotify" ? "Spotify Settings" : "Images"} />
|
<SectionLabel text={form.type === "custom_json" ? "Data Source" : form.type === "static_text" ? "Content" : form.type === "clock" ? "Clock Settings" : form.type === "spotify" ? "Spotify Settings" : "Images"} />
|
||||||
{form.type === "custom_json" && <CustomJsonFields form={form} onChange={onChange} />}
|
{form.type === "custom_json" && <CustomJsonFields form={form} onChange={onChange} />}
|
||||||
@@ -1030,7 +1046,10 @@ function cardSubtitle(card: DataCard): string {
|
|||||||
|
|
||||||
function cardMeta(card: DataCard): string {
|
function cardMeta(card: DataCard): string {
|
||||||
if (card.type === "custom_json") return `Refresh: ${(card as CustomJsonCard).config.refresh_interval}s`;
|
if (card.type === "custom_json") return `Refresh: ${(card as CustomJsonCard).config.refresh_interval}s`;
|
||||||
if (card.type === "image_rotator") return `Fit: ${card.config.fit}`;
|
if (card.type === "image_rotator") {
|
||||||
|
const paused = (card as ImageRotatorCard).disabled ? "Paused · " : "";
|
||||||
|
return `${paused}Fit: ${card.config.fit}`;
|
||||||
|
}
|
||||||
if (card.type === "clock") return card.config.mode === "time" ? "Mode: live time" : "Mode: countdown";
|
if (card.type === "clock") return card.config.mode === "time" ? "Mode: live time" : "Mode: countdown";
|
||||||
if (card.type === "spotify") return `Refresh: ${(card as SpotifyCard).config.refresh_interval}s`;
|
if (card.type === "spotify") return `Refresh: ${(card as SpotifyCard).config.refresh_interval}s`;
|
||||||
return "";
|
return "";
|
||||||
|
|||||||
@@ -242,6 +242,7 @@ function ImageRotatorWidget({ card, isNightMode, nightMessage }: { card: ImageRo
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isNightMode) return;
|
if (isNightMode) return;
|
||||||
|
if (card.disabled) return;
|
||||||
if (images.length <= 1) return;
|
if (images.length <= 1) return;
|
||||||
const ms = Math.max(2000, (card.config.interval ?? 10) * 1000);
|
const ms = Math.max(2000, (card.config.interval ?? 10) * 1000);
|
||||||
const timer = setInterval(() => {
|
const timer = setInterval(() => {
|
||||||
@@ -252,7 +253,7 @@ function ImageRotatorWidget({ card, isNightMode, nightMessage }: { card: ImageRo
|
|||||||
}, 400);
|
}, 400);
|
||||||
}, ms);
|
}, ms);
|
||||||
return () => clearInterval(timer);
|
return () => clearInterval(timer);
|
||||||
}, [images.length, card.config.interval, isNightMode]);
|
}, [images.length, card.config.interval, isNightMode, card.disabled]);
|
||||||
|
|
||||||
// ── Night mode overlay ────────────────────────────────────────────────────
|
// ── Night mode overlay ────────────────────────────────────────────────────
|
||||||
if (isNightMode) {
|
if (isNightMode) {
|
||||||
@@ -292,6 +293,13 @@ function ImageRotatorWidget({ card, isNightMode, nightMessage }: { card: ImageRo
|
|||||||
display: "block",
|
display: "block",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
{/* Paused overlay when disabled */}
|
||||||
|
{card.disabled && (
|
||||||
|
<div className="absolute inset-0 flex flex-col items-center justify-center bg-black/50">
|
||||||
|
<span className="text-5xl">⏸</span>
|
||||||
|
<span className="text-white text-lg font-semibold mt-2">Paused</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{/* Name overlay */}
|
{/* Name overlay */}
|
||||||
<div className="absolute bottom-0 left-0 right-0 px-4 py-2 bg-gradient-to-t from-black/70 to-transparent">
|
<div className="absolute bottom-0 left-0 right-0 px-4 py-2 bg-gradient-to-t from-black/70 to-transparent">
|
||||||
<span className="text-white text-xs font-semibold truncate">{card.name}</span>
|
<span className="text-white text-xs font-semibold truncate">{card.name}</span>
|
||||||
|
|||||||
@@ -115,6 +115,8 @@ export interface ImageRotatorCard {
|
|||||||
name: string;
|
name: string;
|
||||||
config: ImageRotatorConfig;
|
config: ImageRotatorConfig;
|
||||||
layout?: CardLayout;
|
layout?: CardLayout;
|
||||||
|
/** When true the rotator is temporarily paused on the TV */
|
||||||
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── spotify ──────────────────────────────────────────────────────────────────
|
// ─── spotify ──────────────────────────────────────────────────────────────────
|
||||||
|
|||||||
Reference in New Issue
Block a user