From acc38d419ca647feb694ff06b919322ca13affbd Mon Sep 17 00:00:00 2001 From: szbk Date: Sun, 26 Oct 2025 22:19:10 +0300 Subject: [PATCH] =?UTF-8?q?Grid/list=20se=C3=A7im=20modunu=20ekle,=20meta?= =?UTF-8?q?=20bilgileri=20g=C3=B6ster=20ve=20se=C3=A7ili=20=C3=B6=C4=9Fele?= =?UTF-8?q?r=20i=C3=A7in=20toplu=20silme=20butonu=20ekle.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/App.svelte | 14 +- client/src/routes/Files.svelte | 1009 +++++++++++++++++++++++++++----- server/.ignoreFiles | 1 + server/server.js | 139 ++++- 4 files changed, 1023 insertions(+), 140 deletions(-) diff --git a/client/src/App.svelte b/client/src/App.svelte index 7a87329..7ddf087 100644 --- a/client/src/App.svelte +++ b/client/src/App.svelte @@ -42,7 +42,19 @@ {#if menuOpen} -
+
{ + if (event.key === "Enter" || event.key === " ") { + event.preventDefault(); + closeSidebar(); + } + }} + >
{/if} diff --git a/client/src/routes/Files.svelte b/client/src/routes/Files.svelte index 9000aab..74e06ca 100644 --- a/client/src/routes/Files.svelte +++ b/client/src/routes/Files.svelte @@ -2,14 +2,15 @@ import { onMount, tick } from "svelte"; import { API, apiFetch } from "../utils/api.js"; import { cleanFileName } from "../utils/filename.js"; - let files = []; let showModal = false; let selectedVideo = null; let subtitleURL = null; let subtitleLang = "en"; let subtitleLabel = "Custom Subtitles"; - + let viewMode = "grid"; + let selectedItems = new Set(); + let allSelected = false; // 🎬 Player kontrolleri let videoEl; let isPlaying = false; @@ -17,35 +18,68 @@ let duration = 0; let volume = 1; let currentIndex; - let showImageModal = false; let selectedImage = null; - // ✅ REACTIVE: selectedVideo güvenli kullanımlar $: selectedName = selectedVideo?.name ?? ""; $: encName = encodeURIComponent(selectedName); - // ✅ Token'lı video URL'ini fonksiyonla üret (başta çağrılmasın) function getVideoURL() { if (!selectedName) return ""; const token = localStorage.getItem("token"); return `${API}/media/${encName}?token=${token}`; } - // 📂 Dosyaları yükle (tokenlı) async function loadFiles() { const r = await apiFetch("/api/files"); if (!r.ok) return; files = await r.json(); + const existing = new Set(files.map((f) => f.name)); + selectedItems = new Set( + [...selectedItems].filter((name) => existing.has(name)), + ); + allSelected = files.length > 0 && selectedItems.size === files.length; } - function formatSize(bytes) { if (!bytes) return "0 MB"; if (bytes < 1e6) return (bytes / 1e3).toFixed(1) + " KB"; if (bytes < 1e9) return (bytes / 1e6).toFixed(1) + " MB"; return (bytes / 1e9).toFixed(2) + " GB"; } - + function formatDateTime(value) { + if (!value) return "—"; + const date = new Date(Number(value)); + if (Number.isNaN(date.getTime())) return "—"; + return date.toLocaleString(); + } + function toggleView() { + viewMode = viewMode === "grid" ? "list" : "grid"; + } + function toggleSelection(file) { + const next = new Set(selectedItems); + if (next.has(file.name)) next.delete(file.name); + else next.add(file.name); + selectedItems = next; + allSelected = files.length > 0 && next.size === files.length; + } + function selectAll() { + if (allSelected) { + selectedItems = new Set(); + allSelected = false; + } else { + selectedItems = new Set(files.map((f) => f.name)); + allSelected = files.length > 0; + } + } + function handleFilesClick(event) { + if (selectedItems.size === 0) return; + const card = event.target.closest(".media-card"); + const header = event.target.closest(".header-actions"); + if (header) return; + if (card) return; + selectedItems = new Set(); + allSelected = false; + } async function openModal(f) { stopCurrentVideo(); videoEl = null; @@ -53,10 +87,8 @@ currentTime = 0; duration = 0; subtitleURL = null; // ← eklendi - const index = files.findIndex((file) => file.name === f.name); currentIndex = index; - if (f.type?.startsWith("video/")) { selectedImage = null; showImageModal = false; @@ -71,7 +103,6 @@ showImageModal = true; } } - function stopCurrentVideo() { if (videoEl) { try { @@ -83,21 +114,18 @@ } } } - async function showNext() { if (files.length === 0) return; stopCurrentVideo(); currentIndex = (currentIndex + 1) % files.length; await openModal(files[currentIndex]); // ← await } - async function showPrev() { if (files.length === 0) return; stopCurrentVideo(); currentIndex = (currentIndex - 1 + files.length) % files.length; await openModal(files[currentIndex]); // ← await } - function closeModal() { stopCurrentVideo(); // 🔴 video tamamen durur showModal = false; @@ -105,7 +133,6 @@ subtitleURL = null; isPlaying = false; } - // 🎞️ Video kontrolleri async function togglePlay() { if (!videoEl) return; @@ -122,15 +149,12 @@ isPlaying = false; } } - function updateProgress() { currentTime = videoEl?.currentTime || 0; } - function updateDuration() { duration = videoEl?.duration || 0; } - function seekVideo(e) { if (!videoEl) return; const newTime = parseFloat(e.target.value); @@ -138,20 +162,17 @@ videoEl.currentTime = newTime; } } - function changeVolume(e) { if (!videoEl) return; const val = parseFloat(e.target.value); videoEl.volume = val; e.target.style.setProperty("--fill", (val || 0) * 100); } - function toggleFullscreen() { if (!videoEl) return; if (document.fullscreenElement) document.exitFullscreen(); else videoEl.requestFullscreen(); } - function formatTime(seconds) { const m = Math.floor(seconds / 60) .toString() @@ -161,7 +182,6 @@ .padStart(2, "0"); return `${m}:${s}`; } - function handleSubtitleUpload(e) { const file = e.target.files?.[0]; if (!file) return; @@ -187,70 +207,74 @@ }; reader.readAsArrayBuffer(file); } + async function deleteSelectedFiles() { + if (selectedItems.size === 0) return; + if (!confirm(`${selectedItems.size} öğeyi silmek istediğine emin misin?`)) + return; - async function deleteFile(file) { const token = localStorage.getItem("token"); - if (!confirm("Bu dosyayı silmek istediğine emin misin?")) return; + const names = [...selectedItems]; + const failed = []; - // 1️⃣ Önce dosyayı backend'den sil - const resp = await fetch( - `${API}/api/file?path=${encodeURIComponent(file.name)}`, - { - method: "DELETE", - headers: { Authorization: `Bearer ${token}` } - } - ); + for (const name of names) { + const file = files.find((f) => f.name === name); + if (!file) continue; - if (resp.ok) { - console.log("🗑️ Dosya silindi:", file.name); - files = files.filter((f) => f.name !== file.name); - - // 2️⃣ Ek olarak Transfers listesindeki torrent'i de sil - // hash = dosya yolundaki ilk klasör (örnek: downloads//video.mp4) - const hash = file.name.split("/")[0]; - console.log("🔄 Transfers listesinden de siliyorum:", hash); try { + const resp = await fetch( + `${API}/api/file?path=${encodeURIComponent(file.name)}`, + { + method: "DELETE", + headers: { Authorization: `Bearer ${token}` }, + }, + ); + + if (!resp.ok) { + const data = await resp.json().catch(() => ({})); + alert("Silme hatası: " + (data.error || resp.statusText)); + failed.push(name); + continue; + } + + files = files.filter((f) => f.name !== file.name); + + const hash = file.name.split("/")[0]; await fetch(`${API}/api/torrents/${hash}`, { method: "DELETE", - headers: { Authorization: `Bearer ${token}` } + headers: { Authorization: `Bearer ${token}` }, }); } catch (err) { - console.warn("⚠️ Transfers tarafı silinemedi:", err); + console.warn("⚠️ Silme işlemi başarısız:", err); + failed.push(name); } - } else { - const data = await resp.json(); - alert("Silme hatası: " + (data.error || resp.statusText)); } - } + selectedItems = new Set(failed); + allSelected = failed.length > 0 && failed.length === files.length; + } onMount(async () => { await loadFiles(); // önce dosyaları getir - const token = localStorage.getItem("token"); const wsUrl = `${API.replace("http", "ws")}?token=${token}`; const ws = new WebSocket(wsUrl); - ws.onmessage = async (event) => { try { const msg = JSON.parse(event.data); - if (msg.type === "fileUpdate") { console.log("📸 Yeni thumbnail bildirimi:", msg.path); await loadFiles(); } - if (msg.type === "progress" && msg.torrents) { for (const t of msg.torrents) { const savePath = t.savePath || ""; const folderId = savePath.split("/").pop(); - files = files.map((f) => { const fileFolder = f.name.split("/")[0]; if (fileFolder === folderId) { return t.progress < 1 ? { ...f, - progressText: `${Math.floor(t.progress * 100)}%` + progressText: `${Math.floor(t.progress * 100)}%`, } : { ...f, progressText: null }; } @@ -263,8 +287,16 @@ console.warn("WebSocket mesajı çözümlenemedi:", err); } }; - function handleKey(e) { + const isCmd = e.metaKey || e.ctrlKey; + if (isCmd && e.key.toLowerCase() === "a") { + e.preventDefault(); + if (files.length > 0) { + selectedItems = new Set(files.map((f) => f.name)); + allSelected = true; + } + return; + } if (e.key === "Escape") { if (showModal) closeModal(); if (showImageModal) showImageModal = false; @@ -273,26 +305,58 @@ if (e.key === "ArrowLeft") showPrev(); } } - window.addEventListener("keydown", handleKey); return () => window.removeEventListener("keydown", handleKey); }); -
-

Media Library

- +
+
+

Media Library

+
+ {#if files.length > 0 && selectedItems.size > 0} + + {/if} + +
+
{#if files.length === 0}
No media found
{:else} - + +{/if} +{#if showImageModal && selectedImage} + + +
(showImageModal = false)}> + + + + + +
+ {selectedImage.name} +
+
+{/if} +{#if selectedItems.size > 0} + +{/if} +{#if showModal && selectedVideo} + +