// 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 (
);
}
Object.assign(window, {
cls, Icon, Dot, StatusBadge, LevelBadge, Sparkline,
RelTime, TickingSince, fmt, useInterval, useNow,
});