Zmiana iframe listy kanałow na pobierane lokalnie i parsowane

This commit is contained in:
dm
2025-11-26 09:15:29 +01:00
parent 284009d411
commit 8bf578e6d9
7 changed files with 129 additions and 96 deletions

View File

@@ -1,20 +1,26 @@
---
import ChannelSwitcher from "../../islands/ChannelSwitcher.jsx";
const { section } = Astro.props;
---
<section class="fuz-section bg-transparent">
<section class="f-section">
<div class="max-w-7xl mx-auto text-center">
{section.title && (
<h2 class="fuz-section-title">{section.title}</h2>
<h2 class="f-section-title">{section.title}</h2>
)}
{section.content && (
<div class="fuz-markdown mb-10" set:html={section.html} />
<div class="f-markdown mb-10" set:html={section.html} />
)}
<ChannelSwitcher client:load sets={section.iframe_sets} title={section.title} />
{section.iframe_sets && section.iframe_sets.length > 0 && (
<ChannelSwitcher
client:load
sets={section.iframe_sets}
title={section.title}
/>
)}
</div>
</section>

View File

@@ -1,19 +1,32 @@
import { useState } from "preact/hooks";
import { useState, useEffect } from "preact/hooks";
export default function ChannelSwitcher({ sets = [], title = "" }) {
const [activeId, setActiveId] = useState(sets[0]?.id);
const [channels, setChannels] = useState([]);
const active = sets.find((x) => x.id === activeId);
const iframeSrc = `https://www.jambox.pl/iframe-pakiet-logo?p=${active?.p}`;
useEffect(() => {
if (!active) return;
fetch(`/api/jambox/${active.p}`)
.then((r) => r.json())
.then((data) => {
setChannels(data);
})
.catch(() => setChannels([]));
}, [active]);
return (
<div class="w-full">
{/* SWITCHER */}
<div class="flex justify-center mb-10">
<div class="fuz-switch-group">
<div class="f-switch-group">
{sets.map((s) => (
<button
type="button"
class={`fuz-switch ${activeId === s.id ? "active" : ""}`}
class={`f-switch ${activeId === s.id ? "active" : ""}`}
onClick={() => setActiveId(s.id)}
title={title}
>
@@ -23,16 +36,31 @@ export default function ChannelSwitcher({ sets = [], title = "" }) {
</div>
</div>
{/* 🔹 Iframe */}
<div class="w-full">
<div class="fuz-iframe-wrapper">
<iframe
title="Lista kanałów"
src={iframeSrc}
class="fuz-iframe"
{/* LISTA KANAŁÓW */}
<div class="f-section-channel">
{channels.length === 0 && (
<p class="text-center col-span-full py-1">
Ładowanie
</p>
)}
{channels.map((ch) => (
<div
class="f-channel-box"
>
<img
src={ch.logo}
alt={ch.title}
class="h-14 object-contain "
loading="lazy"
></iframe>
/>
<p class="text-center text-sm text-[var(--fuz-text)] mt-2">
{ch.title}
</p>
</div>
))}
</div>
</div>

View File

@@ -0,0 +1,42 @@
import type { APIRoute } from "astro";
import { JSDOM } from "jsdom";
interface Channel {
title: string;
logo: string;
}
const cache = new Map<string, { time: number; data: Channel[] }>();
const CACHE_TIME = 1000 * 60 * 60 * 24 * 30; //miesiąc
export const GET: APIRoute = async ({ params }) => {
const id = params.id!;
const cached = cache.get(id);
if (cached && Date.now() - cached.time < CACHE_TIME) {
return new Response(JSON.stringify(cached.data), {
headers: { "Content-Type": "application/json" }
});
}
const url = `https://www.jambox.pl/iframe-pakiet-logo?p=${id}`;
const resp = await fetch(url, { headers: { "User-Agent": "Mozilla/5.0" } });
const html = await resp.text();
const dom = new JSDOM(html);
const images = [
...dom.window.document.querySelectorAll("img.imagefield-field_logo")
];
const channels = images.map((img) => ({
title: img.getAttribute("alt")?.trim() ?? "",
logo: img.getAttribute("src") ?? "",
}));
cache.set(id, { time: Date.now(), data: channels });
return new Response(JSON.stringify(channels), {
headers: { "Content-Type": "application/json" }
});
};

View File

@@ -1,14 +1,13 @@
@layer components {
#cookie-banner {
@apply fixed bottom-0 left-0 w-full translate-y-full transition-transform duration-500 ease-in-out;
background: var(--f-background-invert);
color: var(--f-text-invert);
border-top: 1px solid rgba(0, 0, 0, 0.1);
background: var(--f-cookie-background);
color: var(--f-cookie-text);
/* border-top: 1px solid rgba(0, 0, 0, 0.1); */
}
.f-cookie-panel-inner {
@apply max-w-4xl mx-auto flex flex-col md:flex-row items-start
md:items-center justify-between gap-4 px-6 py-6;
@apply max-w-5xl mx-auto flex flex-col md:flex-row items-start md:items-center justify-between gap-4 px-6 py-6;
}
.f-cookie-text {
@@ -17,32 +16,21 @@
.f-cookie-privacy-link {
@apply ml-3;
color: var(--btn-outline-invert);
color: var(--f-link-text);
}
.f-cookie-accept,
.f-cookie-reject {
@apply px-4 py-2 rounded-md text-sm font-medium transition-colors;
@apply px-8 py-4 rounded-md text-sm font-medium transition-colors;
}
.f-cookie-accept {
background: var(--btn-bg-invert);
color: var(--btn-text-invert);
}
.f-cookie-accept:hover {
background: var(--fuz-accent-hover);
background: var(--f-cookie-accept-background);
color: var(--f-cookie-accept-text);
}
.f-cookie-reject {
@apply border;
border-color: var(--btn-outline-invert);
color: var(--btn-outline-invert);
background: transparent;
background: var(--f-cookie-reject-background);
color: var(--f-cookie-reject-text);
}
.f-cookie-reject:hover {
background: var(--btn-outline-bg-invert);
}
}

View File

@@ -1,9 +1,9 @@
.f-switches-wrapper {
@apply flex flex-wrap justify-center gap-6 mb-12;
@apply flex flex-wrap justify-center gap-6 mb-10;
}
.f-switch-group {
@apply inline-flex overflow-hidden relative bg-[var(--f-background-switch)];
@apply inline-flex overflow-hidden relative bg-[var(--f-background-switch)] mt-8;
}
.f-switch {

View File

@@ -43,7 +43,15 @@
}
.fuz-iframe-box {
.f-section-channel {
@apply grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-8 gap-2;
}
.f-channel-box {
@apply flex flex-col items-center p-4 rounded-xl bg-[var(--f-background)] border border-[var(--f-offers-border)] shadow-sm hover:shadow-md transition
}
/* .fuz-iframe-box {
@apply bg-white dark:bg-slate-800 p-4 rounded-xl border border-gray-200 dark:border-slate-700 shadow;
}
@@ -64,4 +72,4 @@
.dark .fuz-iframe {
background: transparent !important;
}
} */

View File

@@ -69,27 +69,12 @@
--f-offers-popular: var(--brand-light);
--f-offers-popular-bg: color-mix(in srgb, var(--f-offers-popular) 22%, transparent);
/* Invert Color */
/* --f-background-invert: #0d1117;
--f-text-invert: #e6edf3;
--btn-bg-invert: #58a6ff;
--btn-text-invert: #0d1117;
--btn-outline-invert: #58a6ff;
--btn-outline-bg-invert: rgba(88, 166, 255, 0.15); */
/* Links */
/* --fuz-link: #0050c8;
--fuz-link-hover: #003f9a; */
--f-cookie-background: var(--text1-light);
--f-cookie-text: var(--surface2-light);
/* Accent (buttons, highlights) */
/* --fuz-accent: #0066ff;
--fuz-accent-hover: #004bcc;
--fuz-accent-text: #ffffff;
--btn-ghost-text: var(--f-text);
--btn-ghost-hover-bg: rgba(0, 0, 0, 0.05);
---f-border-color: rgb(209 213 219); */
/* / var(--tw-border-opacity, 1)); */
--f-cookie-accept-background: green;
/* --f-cookie-accept-text: */
--f-cookie-reject-background: var(--text2-light);
}
:root.dark {
@@ -128,36 +113,12 @@
--f-offers-popular: var(--brand-dark);
--f-offers-popular-bg: color-mix(in srgb, var(--f-offers-popular) 22%, transparent);
--f-cookie-background: var(--text1-light);
--f-cookie-text: var(--surface2-light);
/* Invert Color */
--f-background-invert: #ffffff;
--f-text-invert: #0d0d0d;
--btn-bg-invert: #0066ff;
--btn-text-invert: #ffffff;
--btn-outline-invert: #0066ff;
/* Links (GitHub Dark palette) */
--fuz-link: var(--brand-dim);
--fuz-link-hover: #79b7ff;
/* Accent */
--fuz-accent: hsl(var(--hue) calc(var(--saturation) / 1.25) calc(var(--lightness) / 1.25));
--fuz-accent-hover: #79b7ff;
--fuz-accent-text: #0d1117;
/* Buttons */
--btn-ghost-text: var(--f-text);
--btn-ghost-hover-bg: rgba(255, 255, 255, 0.08);
--f-border-color: rgb(209 213 219);
/* / var(--tw-border-opacity, 1)) */
--f-cookie-accept-background: green;
/* --f-cookie-accept-text: */
--f-cookie-reject-background: var(--text2-light);
}
/* Body */