// Shared UI primitives for zlap-termin // Exposes to window: Icon, Dot, StatusBadge, LevelBadge, Sparkline, RelTime, // TickingSince, fmt, useInterval, useNow, cls const { useState, useEffect, useRef, useMemo, useCallback } = React; function cls(...args) { return args.filter(Boolean).join(" "); } /* ---------- Minimal SVG icon set (no external lib) ---------- */ const ICONS = { activity: "M22 12h-4l-3 9L9 3l-3 9H2", settings: "M12 1v6m0 10v6m11-11h-6M7 12H1m17.5-6.5l-4.2 4.2M9.7 14.3l-4.2 4.2m0-12.7l4.2 4.2m4.6 4.6l4.2 4.2", list: "M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01", terminal: "M4 17l6-6-6-6m8 14h8", bell: "M6 8a6 6 0 0112 0c0 7 3 9 3 9H3s3-2 3-9m3 13a3 3 0 006 0", play: "M5 3l14 9-14 9V3z", pause: "M6 4h4v16H6zM14 4h4v16h-4z", restart: "M3 12a9 9 0 0115-6.7L21 8M21 3v5h-5M21 12a9 9 0 01-15 6.7L3 16m0 5v-5h5", search: "M11 19a8 8 0 100-16 8 8 0 000 16zM21 21l-4.3-4.3", external: "M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6M15 3h6v6M10 14L21 3", check: "M20 6L9 17l-5-5", x: "M18 6L6 18M6 6l12 12", chevDown: "M6 9l6 6 6-6", chevRight: "M9 6l6 6-6 6", chevLeft: "M15 6l-6 6 6 6", chevUp: "M18 15l-6-6-6 6", filter: "M22 3H2l8 9.5V19l4 2v-8.5L22 3z", eye: "M1 12s4-8 11-8 11 8 11 8-4 8-11 8S1 12 1 12zM12 15a3 3 0 100-6 3 3 0 000 6z", eyeOff: "M17.94 17.94A10.94 10.94 0 0112 20c-7 0-11-8-11-8a20.16 20.16 0 015.06-6.06M9.9 4.24A10.94 10.94 0 0112 4c7 0 11 8 11 8a20.16 20.16 0 01-3.17 4.18M1 1l22 22M14.12 14.12A3 3 0 119.88 9.88", mail: "M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2zM22 6l-10 7L2 6", send: "M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z", calendar: "M3 4h18v18H3zM3 10h18M8 2v4M16 2v4", clock: "M12 2a10 10 0 100 20 10 10 0 000-20zM12 6v6l4 2", shield: "M12 2l9 4v6c0 5-4 9-9 10-5-1-9-5-9-10V6l9-4z", logout: "M9 21H5a2 2 0 01-2-2V5a2 2 0 012-2h4M16 17l5-5-5-5M21 12H9", user: "M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2M12 11a4 4 0 100-8 4 4 0 000 8z", moon: "M21 12.8A9 9 0 1111.2 3a7 7 0 009.8 9.8z", sun: "M12 5V1M12 23v-4M4.2 4.2l2.9 2.9M16.9 16.9l2.9 2.9M1 12h4M19 12h4M4.2 19.8l2.9-2.9M16.9 7.1l2.9-2.9M12 17a5 5 0 100-10 5 5 0 000 10z", menu: "M3 6h18M3 12h18M3 18h18", zap: "M13 2L3 14h9l-1 8 10-12h-9l1-8z", trash: "M3 6h18M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2M19 6l-1 14a2 2 0 01-2 2H8a2 2 0 01-2-2L5 6", info: "M12 2a10 10 0 100 20 10 10 0 000-20zM12 16v-4M12 8h.01", alert: "M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0zM12 9v4M12 17h.01", link: "M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.72M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71", copy: "M20 9h-9a2 2 0 00-2 2v9a2 2 0 002 2h9a2 2 0 002-2v-9a2 2 0 00-2-2zM5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1", download: "M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3", }; function Icon({ name, size = 14, stroke = 1.6, className = "", style = {} }) { const d = ICONS[name]; if (!d) return null; return ( ); } function Dot({ color = "var(--ok)", pulse = false, size = 8 }) { return ( ); } function StatusBadge({ state }) { // state: 'online' | 'offline' | 'rate' | 'paused' const map = { online: { label: "ONLINE", cls: "ok", color: "var(--ok)", pulse: true }, offline:{ label: "OFFLINE", cls: "err", color: "var(--err)", pulse: false }, rate: { label: "RATE LIMITED", cls: "warn", color: "var(--warn)", pulse: true }, paused: { label: "PAUSED", cls: "muted",color: "var(--fg-muted)", pulse: false }, }; const m = map[state] || map.offline; return ( {m.label} ); } function LevelBadge({ level }) { const cls = level === "ERROR" ? "err" : level === "WARN" ? "warn" : "info"; return {level}; } /* ---------- Time utils ---------- */ const fmt = { hms(d) { const p = (n) => String(n).padStart(2, "0"); return `${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`; }, hm(d) { const p = (n) => String(n).padStart(2, "0"); return `${p(d.getHours())}:${p(d.getMinutes())}`; }, ymd(d) { const p = (n) => String(n).padStart(2, "0"); return `${d.getFullYear()}-${p(d.getMonth()+1)}-${p(d.getDate())}`; }, full(d) { return `${fmt.ymd(d)} ${fmt.hms(d)}`; }, rel(from, to = new Date()) { const diff = Math.max(0, Math.floor((to - from) / 1000)); if (diff < 5) return "przed chwilÄ…"; if (diff < 60) return `${diff} s temu`; if (diff < 3600) return `${Math.floor(diff/60)} min temu`; if (diff < 86400) return `${Math.floor(diff/3600)} h temu`; const d = Math.floor(diff/86400); return `${d} d temu`; }, durShort(seconds) { const s = Math.max(0, Math.floor(seconds)); if (s < 60) return `${s}s`; const m = Math.floor(s/60); const sec = s % 60; if (m < 60) return `${m}m ${sec}s`; const h = Math.floor(m/60); return `${h}h ${m % 60}m`; } }; function useInterval(fn, delay) { const saved = useRef(fn); useEffect(() => { saved.current = fn; }, [fn]); useEffect(() => { if (delay == null) return; const id = setInterval(() => saved.current(), delay); return () => clearInterval(id); }, [delay]); } function useNow(tickMs = 1000) { const [n, setN] = useState(() => new Date()); useInterval(() => setN(new Date()), tickMs); return n; } function RelTime({ date }) { const now = useNow(1000); return {fmt.rel(date, now)}; } function TickingSince({ date, prefix = "", live = true }) { const now = useNow(live ? 1000 : null); const diff = Math.max(0, Math.floor((now - date) / 1000)); return {prefix}{fmt.durShort(diff)}; } /* ---------- Tiny sparkline ---------- */ function Sparkline({ values, width = 120, height = 32, color = "var(--info)", fill = true }) { if (!values || values.length === 0) return null; const max = Math.max(...values, 1); const min = Math.min(...values, 0); const step = width / (values.length - 1 || 1); const y = (v) => height - 4 - ((v - min) / (max - min || 1)) * (height - 8); const points = values.map((v, i) => `${i * step},${y(v)}`).join(" "); const path = "M " + points.replace(/ /g, " L "); const area = `M 0,${height} L ${points.replace(/ /g, " L ")} L ${width},${height} Z`; return ( {fill && } ); } Object.assign(window, { cls, Icon, Dot, StatusBadge, LevelBadge, Sparkline, RelTime, TickingSince, fmt, useInterval, useNow, });