feat: Add hidden channels functionality

This commit is contained in:
wander 2026-02-07 15:19:27 -05:00
parent a12d3f6fb1
commit fb12e3ce26
5 changed files with 295 additions and 45 deletions

View file

@ -5,6 +5,7 @@
import VideoTable from "./VideoTable.svelte";
import DashboardControls from "./DashboardControls.svelte";
import RecoveryModal from "./RecoveryModal.svelte";
import HiddenManager from "./HiddenManager.svelte";
let stats = {
total_videos: 0,
@ -16,6 +17,7 @@
let loading = true;
let error: string | null = null;
let showRecovery = false;
let showHiddenManager = false;
let interval: ReturnType<typeof setInterval>;
@ -102,6 +104,7 @@
<DashboardControls
on:scan={handleScanTriggered}
on:openRecovery={() => (showRecovery = true)}
on:openHidden={() => (showHiddenManager = true)}
/>
</div>
@ -116,4 +119,8 @@
{#if showRecovery}
<RecoveryModal on:close={() => (showRecovery = false)} />
{/if}
{#if showHiddenManager}
<HiddenManager on:close={() => (showHiddenManager = false)} />
{/if}
</div>

View file

@ -75,6 +75,13 @@
>
<i class="bi bi-bandaid"></i> Recovery Mode
</button>
<button
class="btn-cyber-secondary py-3 text-sm font-semibold border border-purple-500/30 text-purple-400 hover:bg-purple-500/10 transition-colors rounded-lg flex items-center justify-center gap-2 col-span-2"
on:click={() => dispatch("openHidden")}
>
<i class="bi bi-eye-slash"></i> Hidden Channels
</button>
</div>
{#if orphanResult}

View file

@ -0,0 +1,164 @@
<script lang="ts">
import { createEventDispatcher, onMount } from "svelte";
export let show = false;
const dispatch = createEventDispatcher();
let hiddenChannels: string[] = [];
let newChannel = "";
let loading = true;
let error: string | null = null;
async function fetchHidden() {
loading = true;
try {
const res = await fetch("/api/hidden");
if (!res.ok) throw new Error("Failed to fetch hidden list");
const data = await res.json();
hiddenChannels = data.channels || [];
error = null;
} catch (e: any) {
error = e.message;
} finally {
loading = false;
}
}
async function addChannel() {
if (!newChannel.trim()) return;
try {
const res = await fetch("/api/hidden", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ channel: newChannel.trim() }),
});
if (!res.ok) throw new Error("Failed to add channel");
newChannel = "";
fetchHidden();
} catch (e: any) {
error = e.message;
}
}
async function removeChannel(channel: string) {
if (
!confirm(
`Unhide channel "${channel}"? It will be moved to public target on next scan.`,
)
)
return;
try {
const res = await fetch("/api/hidden", {
method: "DELETE",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ channel }),
});
if (!res.ok) throw new Error("Failed to remove channel");
fetchHidden();
} catch (e: any) {
error = e.message;
}
}
onMount(() => {
fetchHidden();
});
</script>
<div
class="fixed inset-0 bg-black/80 backdrop-blur-sm z-50 flex items-center justify-center p-4"
>
<div
class="bg-gray-900 border border-gray-700 rounded-xl w-full max-w-lg shadow-2xl overflow-hidden"
>
<div
class="p-6 border-b border-gray-800 flex justifying-between items-center"
>
<h2 class="text-xl font-bold text-white flex items-center">
<i class="bi bi-eye-slash text-purple-400 mr-2"></i> Hidden Channels
</h2>
<button
class="text-gray-400 hover:text-white transition-colors"
on:click={() => dispatch("close")}
>
<i class="bi bi-x-lg text-lg"></i>
</button>
</div>
<div class="p-6 space-y-6">
<div
class="text-sm text-gray-400 bg-gray-800/50 p-3 rounded border border-gray-700"
>
<i class="bi bi-info-circle mr-1"></i>
Channels in this list will be synchronized to the
<code>/hidden</code>
folder instead of the public <code>/target</code> folder.
</div>
<!-- Add New -->
<div class="flex gap-2">
<input
type="text"
bind:value={newChannel}
placeholder="Enter Exact Channel Name..."
class="flex-1 bg-gray-800 border border-gray-700 rounded px-4 py-2 text-white focus:outline-none focus:border-purple-500 transition-colors"
on:keydown={(e) => e.key === "Enter" && addChannel()}
/>
<button
class="bg-purple-600 hover:bg-purple-500 text-white px-4 py-2 rounded font-medium transition-colors"
on:click={addChannel}
>
<i class="bi bi-plus-lg mr-1"></i> Hide
</button>
</div>
<!-- List -->
<div
class="max-h-60 overflow-y-auto space-y-2 pr-1 custom-scrollbar"
>
{#if loading}
<div class="text-center py-4 text-gray-500">Loading...</div>
{:else if hiddenChannels.length === 0}
<div class="text-center py-4 text-gray-500 italic">
No hidden channels
</div>
{:else}
{#each hiddenChannels as channel}
<div
class="flex items-center justify-between bg-gray-800 p-3 rounded group hover:bg-gray-750 transition-colors border border-transparent hover:border-gray-700"
>
<span class="text-gray-200 font-medium"
>{channel}</span
>
<button
class="text-gray-500 hover:text-red-400 transition-colors opacity-0 group-hover:opacity-100"
title="Unhide"
on:click={() => removeChannel(channel)}
>
<i class="bi bi-eye text-lg"></i>
</button>
</div>
{/each}
{/if}
</div>
{#if error}
<div
class="text-red-400 text-sm bg-red-900/20 p-2 rounded border border-red-900/50"
>
{error}
</div>
{/if}
</div>
<div class="p-4 bg-gray-950 border-t border-gray-800 flex justify-end">
<button
class="px-4 py-2 rounded text-gray-300 hover:bg-gray-800 transition-colors"
on:click={() => dispatch("close")}
>
Close
</button>
</div>
</div>
</div>