Dosya taşıma ve "Full Rescan" özelliği eklendi.
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user