| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241 |
- import { createSignal, onMount, onCleanup } from "solid-js";
- import { getRefreshInterval } from "../utils/timeUtils";
- import type { WidgetSchema, WidgetSettings } from "../types/widget";
- interface StoicQuoteProps {
- settings: WidgetSettings;
- }
- interface Quote {
- text: string;
- author: string;
- }
- // Cache to prevent spam and persist across re-renders
- const quoteCooldowns = new Map<string, number>();
- const quoteCache = new Map<string, Quote>();
- export function StoicQuote(props: StoicQuoteProps) {
- const widgetKey = "stoic-quote";
- const COOLDOWN_MS = 10000; // 10 second cooldown
- // Initialize quote from cache or default
- const initialQuote = quoteCache.get(widgetKey) || {
- text: "Loading wisdom...",
- author: "",
- };
- const [quote, setQuote] = createSignal<Quote>(initialQuote);
- const [error, setError] = createSignal<string>("");
- const [renderKey, setRenderKey] = createSignal<number>(0);
- const fetchQuote = async (bypassCooldown: boolean = false) => {
- const now = Date.now();
- const lastFetch = quoteCooldowns.get(widgetKey) || 0;
- // Cooldown check - only for manual refreshes, not interval refreshes
- if (!bypassCooldown && now - lastFetch < COOLDOWN_MS) {
- console.log(`[StoicQuote] Cooldown active, skipping manual refresh`);
- return;
- }
- quoteCooldowns.set(widgetKey, now);
- console.log("[StoicQuote] Fetching quote from API...");
- try {
- const response = await fetch("https://stoic-quotes.com/api/quote");
- if (!response.ok) {
- throw new Error("Failed to fetch quote");
- }
- const data = await response.json();
- const newQuote: Quote = {
- text: data.text,
- author: data.author,
- };
- console.log("[StoicQuote] ✨ New quote received:", {
- author: newQuote.author,
- preview: newQuote.text.substring(0, 50) + "...",
- timestamp: new Date().toLocaleTimeString(),
- });
- setQuote(newQuote);
- setError("");
- quoteCache.set(widgetKey, newQuote);
- setRenderKey((prev) => prev + 1); // Force re-render
- console.log(
- "[StoicQuote] ✅ Quote state updated, render key incremented, forcing re-render",
- );
- } catch (err) {
- console.error("[StoicQuote] Error:", err);
- setError("Failed to load quote");
- }
- };
- onMount(() => {
- // Load initial quote if not cached
- if (!quoteCache.has(widgetKey)) {
- fetchQuote(true); // Bypass cooldown on mount
- }
- // Refresh based on interval setting
- const refreshInterval = getRefreshInterval(props.settings, 1, "hours");
- console.log(`[StoicQuote] Setting up interval: ${refreshInterval}ms`);
- const intervalId = setInterval(() => {
- console.log("[StoicQuote] Interval tick - fetching new quote");
- fetchQuote(true); // Bypass cooldown for scheduled refreshes
- }, refreshInterval);
- onCleanup(() => {
- console.log("[StoicQuote] Cleaning up interval");
- clearInterval(intervalId);
- });
- });
- // Make these reactive by moving them inside the render
- const showAuthor = () => props.settings.showAuthor !== false;
- const fontSize = () => (props.settings.fontSize as string) || "medium";
- const getSizes = () => {
- const fontSizeMap = {
- small: {
- quote: "clamp(0.7rem, 1.5vw, 1.2rem)",
- author: "clamp(0.6rem, 1.2vw, 0.9rem)",
- },
- medium: {
- quote: "clamp(0.9rem, 2vw, 1.5rem)",
- author: "clamp(0.7rem, 1.5vw, 1.1rem)",
- },
- large: {
- quote: "clamp(1.1rem, 2.5vw, 2rem)",
- author: "clamp(0.8rem, 1.8vw, 1.3rem)",
- },
- };
- return (
- fontSizeMap[fontSize() as keyof typeof fontSizeMap] || fontSizeMap.medium
- );
- };
- return (
- <div
- data-render-key={renderKey()}
- style={{
- border: "1px solid var(--border)",
- padding: "1rem",
- width: "100%",
- height: "100%",
- "box-sizing": "border-box",
- display: "flex",
- "flex-direction": "column",
- "justify-content": "center",
- "align-items": "center",
- background: "var(--bg, #fff)",
- position: "relative",
- }}
- >
- {/* Stoic symbol - smaller and positioned absolutely */}
- <div
- style={{
- position: "absolute",
- top: "0.5rem",
- left: "0.5rem",
- "font-size": "1.5rem",
- "line-height": "1",
- color: "var(--border, #ccc)",
- "font-family": "Georgia, serif",
- "font-weight": "bold",
- opacity: "0.3",
- }}
- >
- Σ
- </div>
- {/* Quote text - direct child of flex container like Clock */}
- <div
- style={{
- "text-align": "center",
- "max-width": "95%",
- }}
- >
- {error() ? (
- <div style={{ color: "var(--gray, #999)" }}>{error()}</div>
- ) : (
- <>
- <div
- style={{
- "font-size": getSizes().quote,
- "line-height": "1.4",
- "font-style": "italic",
- }}
- >
- "{quote().text}"
- </div>
- {showAuthor() && quote().author && (
- <div
- style={{
- "margin-top": "0.5rem",
- "font-size": getSizes().author,
- color: "var(--gray, #666)",
- "font-weight": "500",
- }}
- >
- — {quote().author}
- </div>
- )}
- </>
- )}
- </div>
- {/* Stoic badge */}
- <div
- style={{
- position: "absolute",
- bottom: "8px",
- left: "8px",
- "font-size": "0.7rem",
- color: "var(--gray, #999)",
- "text-transform": "uppercase",
- "letter-spacing": "0.05em",
- opacity: "0.5",
- }}
- >
- Stoic Wisdom
- </div>
- </div>
- );
- }
- export const stoicQuoteSchema: WidgetSchema = {
- name: "Stoic Quote",
- description:
- "Display stoic wisdom from Marcus Aurelius, Seneca, and Epictetus",
- settingsSchema: {
- fontSize: {
- type: "select",
- label: "Font Size",
- options: ["small", "medium", "large"],
- default: "medium",
- },
- showAuthor: {
- type: "boolean",
- label: "Show Author",
- default: true,
- },
- refreshIntervalValue: {
- type: "number",
- label: "Refresh Interval",
- default: 1,
- required: true,
- },
- refreshIntervalUnit: {
- type: "select",
- label: "Unit",
- options: ["seconds", "minutes", "hours", "days"],
- default: "hours",
- required: true,
- },
- },
- };
|