Dosya taşıma ve "Full Rescan" özelliği eklendi.

This commit is contained in:
2025-11-02 20:48:39 +03:00
parent 3e07e2a270
commit e5c0e8626e
5 changed files with 665 additions and 99 deletions

View File

@@ -1,6 +1,6 @@
<script>
import { onMount, tick } from "svelte";
import { API, apiFetch, renameFolder } from "../utils/api.js";
import { API, apiFetch, moveEntry, renameFolder } from "../utils/api.js";
import { cleanFileName, extractTitleAndYear } from "../utils/filename.js";
import { refreshMovieCount } from "../stores/movieStore.js";
import { refreshTvShowCount } from "../stores/tvStore.js";
@@ -617,6 +617,23 @@
selectedItems = new Set();
}
function resolveEntryOriginalPath(entry) {
if (!entry) return "";
if (entry.isDirectory) {
const original =
entry.primaryOriginalPath ||
(Array.isArray(entry.originalPaths) ? entry.originalPaths[0] : null) ||
resolveOriginalPathForDisplay(entry.displayPath, currentOriginalPath);
return normalizePath(original);
}
return normalizePath(entry?.name);
}
function clearDragState() {
draggingItem = null;
dragOverItem = null;
}
function handleDragStart(entry, event) {
draggingItem = entry;
dragOverItem = null;
@@ -646,23 +663,30 @@
}
}
function handleDrop(entry, event) {
async function handleDrop(entry, event) {
if (!draggingItem) return;
if (normalizePath(currentPath) !== lastDragPath) {
draggingItem = null;
dragOverItem = null;
clearDragState();
return;
}
event.preventDefault();
event.stopPropagation();
reorderEntries(draggingItem, entry);
draggingItem = null;
dragOverItem = null;
const source = draggingItem;
clearDragState();
if (!source || !entry) return;
if (entry.isDirectory) {
if (source.name === entry.name) return;
await moveEntryToDirectory(source, entry);
return;
}
reorderEntries(source, entry);
}
function handleDragEnd() {
draggingItem = null;
dragOverItem = null;
clearDragState();
}
function handleContainerDragOver(event) {
@@ -674,8 +698,7 @@
function handleContainerDrop(event) {
if (!draggingItem) return;
if (normalizePath(currentPath) !== lastDragPath) {
draggingItem = null;
dragOverItem = null;
clearDragState();
return;
}
event.preventDefault();
@@ -687,8 +710,7 @@
filtered.push(draggingItem.name);
customOrder.set(key, filtered);
applyOrdering(currentPath);
draggingItem = null;
dragOverItem = null;
clearDragState();
}
function reorderEntries(source, target) {
@@ -707,6 +729,51 @@
applyOrdering(currentPath);
}
async function moveEntryToDirectory(source, target) {
const sourcePath = resolveEntryOriginalPath(source);
const targetPath = resolveEntryOriginalPath(target);
if (!sourcePath || !targetPath) return;
const normalizedSource = normalizePath(sourcePath);
const normalizedTarget = normalizePath(targetPath);
if (!normalizedSource || !normalizedTarget) return;
if (source?.isDirectory) {
if (
normalizedTarget === normalizedSource ||
normalizedTarget.startsWith(`${normalizedSource}/`)
) {
alert("Bir klasörü kendi içine taşıyamazsın.");
return;
}
}
const parentOfSource = normalizedSource.split("/").slice(0, -1).join("/");
if (parentOfSource === normalizedTarget) {
return;
}
try {
const result = await moveEntry(normalizedSource, normalizedTarget);
if (!result?.success) {
const message =
result?.error || "Öğe taşınırken bir hata oluştu.";
alert(message);
return;
}
if (!result?.unchanged) {
await loadFiles();
}
selectedItems = new Set();
} catch (err) {
console.error("❌ Taşıma hatası:", err);
alert("Öğe taşınamadı. Lütfen tekrar dene.");
}
}
function updateUrlPath(
path,
originalPath = currentOriginalPath,

View File

@@ -11,6 +11,7 @@
let movies = [];
let loading = true;
let refreshing = false;
let rescanning = false;
let error = null;
let selectedMovie = null;
let selectedRuntime = null;
@@ -327,30 +328,51 @@ async function loadMovies() {
}
}
function posterUrl(movie) {
if (!movie.poster) return null;
const token = localStorage.getItem("token");
return `${API}${movie.poster}?token=${token}&t=${Date.now()}`;
}
function posterUrl(movie) {
if (!movie.poster) return null;
const token = localStorage.getItem("token");
return `${API}${movie.poster}?token=${token}&t=${Date.now()}`;
}
function backdropUrl(movie) {
if (!movie.backdrop) return null;
const token = localStorage.getItem("token");
return `${API}${movie.backdrop}?token=${token}&t=${Date.now()}`;
}
function backdropUrl(movie) {
if (!movie.backdrop) return null;
const token = localStorage.getItem("token");
return `${API}${movie.backdrop}?token=${token}&t=${Date.now()}`;
}
async function refreshMovies() {
try {
refreshing = true;
await apiFetch("/api/movies/refresh", { method: "POST" });
await loadMovies();
} catch (err) {
console.error("Movies refresh error:", err);
error = err?.message || "Refresh failed.";
} finally {
refreshing = false;
async function refreshMovies() {
try {
refreshing = true;
const resp = await apiFetch("/api/movies/refresh", { method: "POST" });
if (!resp.ok) {
const data = await resp.json().catch(() => ({}));
throw new Error(data?.error || `HTTP ${resp.status}`);
}
await loadMovies();
} catch (err) {
console.error("Movies refresh error:", err);
error = err?.message || "Refresh failed.";
} finally {
refreshing = false;
}
}
async function rescanMovies() {
try {
rescanning = true;
const resp = await apiFetch("/api/movies/rescan", { method: "POST" });
if (!resp.ok) {
const data = await resp.json().catch(() => ({}));
throw new Error(data?.error || `HTTP ${resp.status}`);
}
await loadMovies();
} catch (err) {
console.error("Movies rescan error:", err);
error = err?.message || "Tam tarama sırasında bir sorun oluştu.";
} finally {
rescanning = false;
}
}
function openMovie(movie) {
selectedMovie = movie;
@@ -393,13 +415,22 @@ async function loadMovies() {
<div class="section-accent"></div>
<div class="movies-header">
<h2>Movies</h2>
<button
class="refresh-btn"
on:click={refreshMovies}
disabled={loading || refreshing}
>
{refreshing ? "Refreshing…" : "Refresh Metadata"}
</button>
<div class="header-actions">
<button
class="refresh-btn"
on:click={rescanMovies}
disabled={loading || refreshing || rescanning}
>
{rescanning ? "Rebuilding…" : "Full Rescan"}
</button>
<button
class="refresh-btn"
on:click={refreshMovies}
disabled={loading || refreshing || rescanning}
>
{refreshing ? "Refreshing…" : "Refresh Metadata"}
</button>
</div>
</div>
{#if loading}
@@ -655,6 +686,11 @@ async function loadMovies() {
justify-content: space-between;
}
.header-actions {
display: flex;
gap: 10px;
}
.section-accent {
height: 2px;
width: calc(100% + 52px);

View File

@@ -11,6 +11,7 @@
let shows = [];
let loading = true;
let refreshing = false;
let rescanning = false;
let error = null;
let selectedShow = null;
@@ -219,7 +220,11 @@ let filteredShows = [];
async function refreshShows() {
try {
refreshing = true;
await apiFetch("/api/tvshows/refresh", { method: "POST" });
const resp = await apiFetch("/api/tvshows/refresh", { method: "POST" });
if (!resp.ok) {
const data = await resp.json().catch(() => ({}));
throw new Error(data?.error || `HTTP ${resp.status}`);
}
await loadShows();
} catch (err) {
console.error("TV metadata refresh error:", err);
@@ -229,6 +234,23 @@ let filteredShows = [];
}
}
async function rescanShows() {
try {
rescanning = true;
const resp = await apiFetch("/api/tvshows/rescan", { method: "POST" });
if (!resp.ok) {
const data = await resp.json().catch(() => ({}));
throw new Error(data?.error || `HTTP ${resp.status}`);
}
await loadShows();
} catch (err) {
console.error("TV metadata rescan error:", err);
error = err?.message || "Tam tarama sırasında bir sorun oluştu.";
} finally {
rescanning = false;
}
}
function openShow(show) {
if (!show) return;
selectedShow = show;
@@ -611,13 +633,22 @@ async function openVideoAtIndex(index) {
<div class="section-accent"></div>
<div class="tv-header">
<h2>Tv Shows</h2>
<button
class="refresh-btn"
disabled={loading || refreshing}
on:click={refreshShows}
>
{refreshing ? "Refreshing…" : "Refresh Metadata"}
</button>
<div class="header-actions">
<button
class="refresh-btn"
disabled={loading || refreshing || rescanning}
on:click={rescanShows}
>
{rescanning ? "Rebuilding…" : "Full Rescan"}
</button>
<button
class="refresh-btn"
disabled={loading || refreshing || rescanning}
on:click={refreshShows}
>
{refreshing ? "Refreshing…" : "Refresh Metadata"}
</button>
</div>
</div>
{#if loading}
@@ -1019,6 +1050,11 @@ async function openVideoAtIndex(index) {
justify-content: space-between;
}
.header-actions {
display: flex;
gap: 10px;
}
.tv-header h2 {
font-size: 26px;
margin: 0;

View File

@@ -44,6 +44,18 @@ export async function deleteFromTrash(trashName) {
return res.json();
}
export async function moveEntry(sourcePath, targetDirectory) {
const res = await apiFetch("/api/file/move", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
sourcePath,
targetDirectory
})
});
return res.json();
}
export async function renameFolder(path, newName) {
const res = await apiFetch("/api/folder", {
method: "PATCH",