Gas. Sekarang kita bikin filter konten.
Ini upgrade kecil, tapi efeknya besar banget ke rasa “website jadi”.
Target
Bikin tombol:
- All
- Video
- Stream
- Image
Klik tombol → gallery langsung terfilter.
1. Tambahkan HTML filter di index.html
Taruh di atas <div id="gallery"></div>:
<div id="filters" class="filters">
<button class="filter-btn active" data-filter="all">All</button>
<button class="filter-btn" data-filter="video">Video</button>
<button class="filter-btn" data-filter="stream">Stream</button>
<button class="filter-btn" data-filter="image">Image</button>
</div>
<div id="gallery"></div>
Jadi bagian body kira-kira begini:
<h1>My Video Gallery</h1>
<div id="filters" class="filters">
<button class="filter-btn active" data-filter="all">All</button>
<button class="filter-btn" data-filter="video">Video</button>
<button class="filter-btn" data-filter="stream">Stream</button>
<button class="filter-btn" data-filter="image">Image</button>
</div>
<div id="gallery"></div>
2. Replace app.js dengan versi ini
Ini versi yang sudah support filter:
const gallery = document.getElementById("gallery");
const modal = document.getElementById("playerModal");
const player = document.getElementById("player");
const filterButtons = document.querySelectorAll(".filter-btn");
let currentHls = null;
let allItems = [];
function formatDuration(seconds) {
if (!seconds || isNaN(seconds)) return "";
const hrs = Math.floor(seconds / 3600);
const mins = Math.floor((seconds % 3600) / 60);
const secs = Math.floor(seconds % 60);
if (hrs > 0) {
return `${hrs}:${String(mins).padStart(2, "0")}:${String(secs).padStart(2, "0")}`;
}
return `${mins}:${String(secs).padStart(2, "0")}`;
}
function getBadgeText(item) {
if (item.kind === "video") return "VIDEO";
if (item.kind === "stream") return "STREAM";
if (item.kind === "image") return "IMAGE";
return item.type ? item.type.toUpperCase() : "MEDIA";
}
function renderGallery(items) {
gallery.innerHTML = "";
if (!items.length) {
gallery.innerHTML = `<p>Tidak ada item untuk filter ini.</p>`;
return;
}
items.forEach(item => {
const div = document.createElement("div");
div.className = "card";
const durationText =
item.kind === "video" && item.duration
? `<span class="card-duration">${formatDuration(item.duration)}</span>`
: "";
div.innerHTML = `
<div class="thumb-wrap">
<img src="${item.thumb}" alt="${item.title}">
<span class="card-badge">${getBadgeText(item)}</span>
${durationText}
</div>
<div class="card-body">
<p class="card-title">${item.title}</p>
</div>
`;
div.onclick = () => openPlayer(item);
gallery.appendChild(div);
});
}
function applyFilter(filter) {
if (filter === "all") {
renderGallery(allItems);
return;
}
const filtered = allItems.filter(item => item.kind === filter);
renderGallery(filtered);
}
fetch("data.json")
.then(res => res.json())
.then(data => {
allItems = data;
renderGallery(allItems);
})
.catch(err => {
console.error("Gagal load data.json:", err);
gallery.innerHTML = `<p>Gagal memuat data gallery.</p>`;
});
filterButtons.forEach(btn => {
btn.addEventListener("click", () => {
filterButtons.forEach(b => b.classList.remove("active"));
btn.classList.add("active");
const filter = btn.dataset.filter;
applyFilter(filter);
});
});
function openPlayer(item) {
modal.classList.remove("hidden");
if (currentHls) {
currentHls.destroy();
currentHls = null;
}
player.innerHTML = "";
if (item.type === "mp4") {
player.innerHTML = `
<div class="video-wrapper">
<video controls autoplay playsinline>
<source src="${item.src}" type="video/mp4">
</video>
</div>
`;
} else if (item.type === "image") {
player.innerHTML = `
<div class="image-wrapper">
<img src="${item.src}" alt="${item.title}">
</div>
`;
} else if (item.type === "iframe") {
player.innerHTML = `
<div class="iframe-wrapper">
<iframe
src="${item.src}"
allowfullscreen
loading="lazy"
referrerpolicy="strict-origin-when-cross-origin">
</iframe>
</div>
`;
} else if (item.type === "m3u8") {
player.innerHTML = `
<div class="video-wrapper">
<video id="hlsPlayer" controls autoplay playsinline></video>
</div>
`;
const video = document.getElementById("hlsPlayer");
if (window.Hls && Hls.isSupported()) {
currentHls = new Hls();
currentHls.loadSource(item.src);
currentHls.attachMedia(video);
} else if (video.canPlayType("application/vnd.apple.mpegurl")) {
video.src = item.src;
} else {
player.innerHTML = `<p>Browser tidak mendukung HLS.</p>`;
}
} else {
player.innerHTML = `<p>Tipe media tidak dikenali.</p>`;
}
}
function closePlayer() {
modal.classList.add("hidden");
if (currentHls) {
currentHls.destroy();
currentHls = null;
}
player.innerHTML = "";
}
3. Tambahkan CSS filter ke style.css
Tambahkan ini:
.filters {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-bottom: 20px;
}
.filter-btn {
background: #222;
color: white;
border: 1px solid #333;
border-radius: 8px;
padding: 8px 14px;
cursor: pointer;
transition: 0.2s ease;
}
.filter-btn:hover {
background: #333;
}
.filter-btn.active {
background: #fff;
color: #111;
border-color: #fff;
}
4. Hasilnya
Setelah reload:
- klik All → semua tampil
- klik Video → hanya mp4
- klik Stream → hanya m3u8
- klik Image → hanya gambar
Ini sudah bikin gallery terasa lebih proper.
5. Kenapa step ini penting
Karena sekarang struktur datamu mulai benar-benar dipakai:
kind: videokind: streamkind: image
Artinya fondasi kamu sudah sehat.
6. Pelajaran berikutnya yang paling masuk akal
Setelah filter, arah terbaik ada 2:
A. Sort
- terbaru
- title A-Z
- durasi terpanjang
B. Workflow satu perintah
- generate thumbnail
- generate data.json
- langsung jadi
Menurut gue, yang paling berguna berikutnya adalah workflow satu perintah, karena itu sangat cocok dengan gaya kerja kamu.