Trash eklendi

This commit is contained in:
2025-11-02 00:15:06 +03:00
parent 90009d9fbe
commit 3e07e2a270
8 changed files with 2670 additions and 135 deletions

View File

@@ -12,6 +12,7 @@
import { API } from "./utils/api.js";
import { refreshMovieCount } from "./stores/movieStore.js";
import { refreshTvShowCount } from "./stores/tvStore.js";
import { fetchTrashItems } from "./stores/trashStore.js";
const token = localStorage.getItem("token");
@@ -24,7 +25,7 @@
refreshTimer = setTimeout(async () => {
refreshTimer = null;
try {
await Promise.all([refreshMovieCount(), refreshTvShowCount()]);
await Promise.all([refreshMovieCount(), refreshTvShowCount(), fetchTrashItems()]);
} catch (err) {
console.warn("Medya sayacı yenileme başarısız:", err);
}
@@ -45,6 +46,7 @@
if (token) {
refreshMovieCount();
refreshTvShowCount();
fetchTrashItems();
const authToken = localStorage.getItem("token");
if (authToken) {
const wsUrl = `${API.replace("http", "ws")}?token=${authToken}`;

View File

@@ -1,14 +1,16 @@
<script>
import { Link } from "svelte-routing";
import { createEventDispatcher, onDestroy, onMount, tick } from "svelte";
import { movieCount } from "../stores/movieStore.js";
import { tvShowCount } from "../stores/tvStore.js";
import { apiFetch } from "../utils/api.js";
import { Link } from "svelte-routing";
import { createEventDispatcher, onDestroy, onMount, tick } from "svelte";
import { movieCount } from "../stores/movieStore.js";
import { tvShowCount } from "../stores/tvStore.js";
import { trashCount } from "../stores/trashStore.js";
import { apiFetch } from "../utils/api.js";
export let menuOpen = false;
const dispatch = createEventDispatcher();
let hasMovies = false;
let hasShows = false;
let hasTrash = false;
// Svelte store kullanarak reaktivite sağla
import { writable } from 'svelte/store';
const diskSpaceStore = writable({ totalGB: '0', usedGB: '0', usedPercent: 0 });
@@ -37,9 +39,14 @@
hasShows = (count ?? 0) > 0;
});
const unsubscribeTrash = trashCount.subscribe((count) => {
hasTrash = (count ?? 0) > 0;
});
onDestroy(() => {
unsubscribeMovie();
unsubscribeTv();
unsubscribeTrash();
if (unsubscribeDiskSpace) {
unsubscribeDiskSpace();
}
@@ -182,6 +189,9 @@
>
<i class="fa-solid fa-trash icon"></i>
Trash
{#if hasTrash}
<span class="badge">{$trashCount}</span>
{/if}
</Link>
</div>
@@ -208,7 +218,25 @@
style="width: {diskSpace.usedPercent}%"
></div>
</div>
</div>
</div>
<style>
.badge {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 18px;
height: 18px;
padding: 0 5px;
margin-left: 8px;
background: #f44336;
color: white;
font-size: 11px;
font-weight: 600;
border-radius: 9px;
line-height: 1;
}
</style>
</div>
</div>
</div>

View File

@@ -1,6 +1,6 @@
<script>
import { onMount, tick } from "svelte";
import { API, apiFetch } from "../utils/api.js";
import { API, apiFetch, renameFolder } from "../utils/api.js";
import { cleanFileName, extractTitleAndYear } from "../utils/filename.js";
import { refreshMovieCount } from "../stores/movieStore.js";
import { refreshTvShowCount } from "../stores/tvStore.js";
@@ -29,6 +29,9 @@
let lastDragPath = "";
let searchTerm = "";
let hasSearch = false;
let renamingFolder = null;
let renameValue = "";
let renameInput;
const normalizePath = (value) => {
if (!value) return "";
@@ -193,7 +196,8 @@
return crumbs;
}
if (segments.length <= MAX_TRAIL_SEGMENTS + 1) {
// Normal durumda tüm segmentleri göster
if (segments.length <= 4) {
segments.forEach((seg, index) => {
const segPath = segments.slice(0, index + 1).join("/");
crumbs.push({ label: seg, path: segPath });
@@ -201,21 +205,85 @@
return crumbs;
}
const firstSegment = segments[0];
crumbs.push({ label: firstSegment, path: firstSegment });
crumbs.push({ label: "...", path: null, ellipsis: true });
// Overflow durumunda sadece ilk ve son 3'ü göster
segments.forEach((seg, index) => {
const segPath = segments.slice(0, index + 1).join("/");
const tail = segments.slice(-MAX_TRAIL_SEGMENTS);
tail.forEach((seg, index) => {
const segPath = segments
.slice(0, segments.length - tail.length + index + 1)
.join("/");
crumbs.push({ label: seg, path: segPath });
if (index === 0) {
// İlk segmenti her zaman göster
crumbs.push({ label: seg, path: segPath });
} else if (index >= segments.length - 3) {
// Son 3 segmenti göster
crumbs.push({ label: seg, path: segPath });
}
// Aradaki segmentleri gösterme (ellipsis ile değiştirilecek)
});
return crumbs;
}
function checkBreadcrumbOverflow() {
if (!breadcrumbContainer) return;
const containerWidth = breadcrumbContainer.offsetWidth;
// Tüm breadcrumb'ları göstererek genişliği hesapla
const allBreadcrumbs = computeBreadcrumbs(currentPath);
const totalSegments = allBreadcrumbs.length;
// Eğer 4'ten az segment varsa overflow gösterme
if (totalSegments <= 4) {
showBreadcrumbMenu = false;
hiddenBreadcrumbs = [];
return;
}
// Container genişliğine göre overflow karar ver
// 533px'den dar olduğunda overflow göster
const shouldShowOverflow = containerWidth <= 533;
if (shouldShowOverflow) {
updateHiddenBreadcrumbs();
showBreadcrumbMenu = true;
} else {
showBreadcrumbMenu = false;
hiddenBreadcrumbs = [];
}
}
function updateHiddenBreadcrumbs() {
// İlk öğeyi (Home) koru, son 3 öğeyi koru, aradakileri gizle
if (breadcrumbs.length <= 4) {
hiddenBreadcrumbs = [];
return;
}
// Home'dan sonraki ve son 3'ten önceki öğeleri gizle
hiddenBreadcrumbs = breadcrumbs.slice(1, -3);
}
function toggleBreadcrumbMenu(event) {
event.preventDefault();
event.stopPropagation();
if (!hiddenBreadcrumbs.length) return;
const button = event.currentTarget;
const rect = button.getBoundingClientRect();
breadcrumbMenuPosition = {
top: rect.bottom + 4,
left: rect.left
};
// Menüyü aç
showBreadcrumbMenu = true;
}
function closeBreadcrumbMenu() {
showBreadcrumbMenu = false;
}
function updateVisibleState(fileList, path = currentPath) {
const dirs = buildDirectoryEntries(fileList);
const directoryMap = new Map();
@@ -252,6 +320,16 @@
);
applyOrdering(path);
breadcrumbs = computeBreadcrumbs(path);
// Breadcrumb'lar güncellendiğinde overflow kontrolünü tetikle
tick().then(() => {
if (breadcrumbContainer) {
// Biraz bekle DOM'un güncellenmesi için
setTimeout(() => {
checkBreadcrumbOverflow();
}, 10);
}
});
}
function resolveOriginalPathForDisplay(displayPath, fallbackOriginal = currentOriginalPath) {
@@ -369,6 +447,12 @@
let isCreatingFolder = false;
let newFolderName = "";
// Breadcrumb menü state
let breadcrumbContainer;
let showBreadcrumbMenu = false;
let breadcrumbMenuPosition = { top: 0, left: 0 };
let hiddenBreadcrumbs = [];
if (typeof window !== "undefined") {
const params = new URLSearchParams(window.location.search);
const pathParam = params.get("path");
@@ -521,6 +605,10 @@
if (isCreatingFolder && !creating) {
cancelCreateFolder();
}
const renaming = event.target.closest(".folder-rename-input");
if (renamingFolder && !renaming) {
cancelRenameFolder();
}
if (selectedItems.size === 0) return;
const card = event.target.closest(".media-card");
const header = event.target.closest(".header-actions");
@@ -1321,12 +1409,98 @@
}
}
function startRenameFolder(entry) {
if (!entry || isCreatingFolder) return;
if (renamingFolder && renamingFolder.name === entry.name) return;
if (renamingFolder) cancelRenameFolder();
renamingFolder = entry;
renameValue = entry.displayName || "";
activeMenu = null;
tick().then(() => {
if (renameInput) {
renameInput.focus();
renameInput.select();
}
});
}
function cancelRenameFolder() {
renamingFolder = null;
renameValue = "";
renameInput = null;
}
async function submitRenameFolder(entry = renamingFolder) {
if (!renamingFolder || !entry || renamingFolder.name !== entry.name) {
return;
}
const targetName = renameValue.trim();
const originalPath = normalizePath(
entry.primaryOriginalPath || entry.originalPaths?.[0] || entry.displayPath
);
if (!originalPath) {
cancelRenameFolder();
return;
}
if (!targetName || targetName === entry.displayName) {
cancelRenameFolder();
return;
}
try {
const response = await renameFolder(originalPath, targetName);
if (!response.success) {
alert(
"Yeniden adlandırma başarısız: " +
(response.error || response.message || "Bilinmeyen hata")
);
return;
}
await loadFiles();
cancelRenameFolder();
} catch (err) {
console.error("Yeniden adlandırma hatası:", err);
alert("Klasör yeniden adlandırılamadı.");
}
}
function handleRenameKeydown(event, entry) {
if (event.key === "Enter") {
event.preventDefault();
submitRenameFolder(entry);
} else if (event.key === "Escape") {
event.preventDefault();
cancelRenameFolder();
}
}
onMount(async () => {
setSearchScope("files");
await loadFiles(); // önce dosyaları getir
const token = localStorage.getItem("token");
const wsUrl = `${API.replace("http", "ws")}?token=${token}`;
const ws = new WebSocket(wsUrl);
// Breadcrumb overflow kontrolü için resize observer
const resizeObserver = new ResizeObserver(() => {
// Biraz bekle DOM'un güncellenmesi için
setTimeout(() => {
checkBreadcrumbOverflow();
}, 10);
});
// Breadcrumb container'ı DOM'a eklendikten sonra gözlemciyi başlat
tick().then(() => {
if (breadcrumbContainer) {
resizeObserver.observe(breadcrumbContainer);
// İlk kontrolü yap
setTimeout(() => {
checkBreadcrumbOverflow();
}, 50);
}
});
const handlePopState = (event) => {
if (typeof window === "undefined") return;
const statePath =
@@ -1391,6 +1565,11 @@
}
}
}
if (msg.type === "mediaDetected") {
console.log("🎬 Otomatik medya tespiti tamamlandı:", msg);
// Otomatik medya tespiti tamamlandığında dosyaları yeniden yükle
await loadFiles();
}
if (msg.type === "progress" && msg.torrents) {
for (const t of msg.torrents) {
const savePath = t.savePath || "";
@@ -1468,6 +1647,10 @@
cancelCreateFolder();
handled = true;
}
if (renamingFolder) {
cancelRenameFolder();
handled = true;
}
if (activeMenu) {
activeMenu = null;
handled = true;
@@ -1550,6 +1733,9 @@
if (activeMenu && !event.target.closest(".media-card")) {
activeMenu = null;
}
if (showBreadcrumbMenu && !event.target.closest(".breadcrumb") && !event.target.closest(".breadcrumb-menu-portal")) {
closeBreadcrumbMenu();
}
}
window.addEventListener("click", handleClickOutside);
@@ -1569,6 +1755,13 @@
window.removeEventListener("click", handleClickOutside);
window.removeEventListener("popstate", handlePopState);
// Resize observer'ı temizle
if (breadcrumbContainer) {
const resizeObserver = new ResizeObserver(() => {});
resizeObserver.disconnect();
}
try {
ws.close();
} catch (err) {
@@ -1582,21 +1775,61 @@
<div class="files-header">
<div class="header-title">
<h2>Media Library</h2>
<div class="breadcrumb">
{#each breadcrumbs as crumb, index (index)}
{#if crumb.ellipsis}
<span class="crumb ellipsis">...</span>
{:else}
<div class="breadcrumb-container" bind:this={breadcrumbContainer}>
<div class="breadcrumb" class:has-overflow={showBreadcrumbMenu}>
{#if showBreadcrumbMenu && hiddenBreadcrumbs.length > 0}
<!-- İlk öğe (Home) -->
<button
type="button"
class="crumb"
class:is-active={normalizePath(crumb.path) === normalizePath(currentPath)}
on:click|stopPropagation={() => handleBreadcrumbClick(crumb)}
class:is-active={normalizePath(breadcrumbs[0].path) === normalizePath(currentPath)}
on:click|stopPropagation={() => handleBreadcrumbClick(breadcrumbs[0])}
>
{index === 0 ? "/Home" : `/${crumb.label}`}
{breadcrumbs[0].label === "Home" ? "Home" : breadcrumbs[0].label}
</button>
<i class="fa-solid fa-caret-right breadcrumb-separator"></i>
<!-- Ellipsis butonu -->
<button
type="button"
class="crumb ellipsis"
on:click={toggleBreadcrumbMenu}
>
...
</button>
<i class="fa-solid fa-caret-right breadcrumb-separator"></i>
<!-- Son 3 öğe -->
{#each breadcrumbs.slice(-3) as crumb, index (crumb.path)}
<button
type="button"
class="crumb"
class:is-active={normalizePath(crumb.path) === normalizePath(currentPath)}
on:click|stopPropagation={() => handleBreadcrumbClick(crumb)}
>
{crumb.label}
</button>
{#if index < 2}
<i class="fa-solid fa-caret-right breadcrumb-separator"></i>
{/if}
{/each}
{:else}
<!-- Normal breadcrumb görüntüleme -->
{#each breadcrumbs as crumb, index (index)}
<button
type="button"
class="crumb"
class:is-active={normalizePath(crumb.path) === normalizePath(currentPath)}
on:click|stopPropagation={() => handleBreadcrumbClick(crumb)}
>
{index === 0 ? "Home" : crumb.label}
</button>
{#if index < breadcrumbs.length - 1}
<i class="fa-solid fa-caret-right breadcrumb-separator"></i>
{/if}
{/each}
{/if}
{/each}
</div>
</div>
</div>
<div class="header-actions">
@@ -1697,7 +1930,20 @@
<img src={FOLDER_ICON_PATH} alt={`${entry.displayName} klasörü`} />
</div>
<div class="folder-info">
<div class="folder-name">{cleanFileName(entry.displayName)}</div>
{#if renamingFolder?.name === entry.name}
<input
class="folder-rename-input"
type="text"
bind:this={renameInput}
bind:value={renameValue}
on:keydown={(event) => handleRenameKeydown(event, entry)}
on:blur={() => submitRenameFolder(entry)}
on:click|stopPropagation
autofocus
/>
{:else}
<div class="folder-name">{cleanFileName(entry.displayName)}</div>
{/if}
</div>
{:else}
{#if entry.thumbnail}
@@ -1833,6 +2079,13 @@
<i class="fa-solid fa-folder-open"></i>
<span></span>
</button>
<button
class="menu-item"
on:click|stopPropagation={() => startRenameFolder(activeMenu)}
>
<i class="fa-solid fa-pen"></i>
<span>Yeniden adlandır</span>
</button>
<div class="menu-divider"></div>
{:else}
<button
@@ -1861,6 +2114,28 @@
</div>
{/if}
{#if showBreadcrumbMenu && hiddenBreadcrumbs.length > 0}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class="breadcrumb-menu-portal"
style="top: {breadcrumbMenuPosition.top}px; left: {breadcrumbMenuPosition.left}px;"
on:click|stopPropagation
>
{#each hiddenBreadcrumbs as crumb, index (index)}
<button
class="breadcrumb-menu-item"
on:click|stopPropagation={() => {
handleBreadcrumbClick(crumb);
closeBreadcrumbMenu();
}}
>
<span class="breadcrumb-menu-text">{crumb.label}</span>
</button>
{/each}
</div>
{/if}
{#if showModal && selectedVideo}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="modal-overlay" on:click={closeModal}>
@@ -2302,13 +2577,22 @@
flex-direction: column;
align-items: flex-start;
gap: 4px;
min-width: 0;
flex: 1;
}
.breadcrumb-container {
width: 100%;
overflow: hidden;
position: relative;
}
.breadcrumb {
display: flex;
align-items: center;
gap: 2px;
gap: 6px;
font-size: 14px;
color: #757575;
white-space: nowrap;
overflow: hidden;
}
.crumb {
border: none;
@@ -2330,6 +2614,59 @@
color: #1f78ff;
font-weight: 600;
}
.breadcrumb-separator {
color: #999;
font-size: 12px;
margin: 0 2px;
flex-shrink: 0;
}
.crumb.ellipsis {
background: transparent;
color: #666;
cursor: pointer;
padding: 2px 6px;
border-radius: 4px;
transition: background 0.2s ease;
}
.crumb.ellipsis:hover {
background: rgba(0, 0, 0, 0.05);
}
.breadcrumb-menu-portal {
position: fixed;
background: #ffffff;
border-radius: 8px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
min-width: 180px;
max-width: 240px;
z-index: 10000;
animation: fadeIn 0.2s ease;
max-height: 300px;
overflow-y: auto;
border: 1px solid #e0e0e0;
}
.breadcrumb-menu-item {
display: flex;
align-items: center;
width: 100%;
padding: 10px 14px;
border: none;
background: transparent;
color: #333;
font-size: 14px;
cursor: pointer;
transition: background-color 0.2s ease;
text-align: left;
}
.breadcrumb-menu-item:hover {
background-color: #f5f5f5;
}
.breadcrumb-menu-text {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 100%;
}
.crumb.ellipsis {
cursor: default;
color: #9b9b9b;
@@ -2432,14 +2769,13 @@
background: rgba(245, 179, 51, 0.1);
border: 2px dashed var(--yellow);
border-radius: 8px;
padding: 18px 12px;
padding: 12px 12px 8px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
gap: 18px;
gap: 8px;
width: 100%;
min-height: 210px;
animation: fadeIn 0.3s ease;
}
@@ -2453,32 +2789,34 @@
.creating-folder .folder-thumb {
width: 100%;
height: 150px;
height: 110px;
display: flex;
align-items: center;
justify-content: center;
flex: 1;
}
.creating-folder.list-view .folder-thumb {
width: 135px;
height: 135px;
width: 128px;
height: 128px;
}
.creating-folder .folder-thumb img {
width: 135px;
height: 135px;
width: 95px;
height: 95px;
object-fit: contain;
filter: drop-shadow(0 6px 18px rgba(0, 0, 0, 0.16));
}
.creating-folder.list-view .folder-thumb img {
width: 135px;
height: 135px;
width: 125px;
height: 125px;
}
.creating-folder .folder-info {
width: 100%;
text-align: center;
flex-shrink: 0;
}
.creating-folder.list-view .folder-info {
@@ -2512,8 +2850,8 @@
/* === GALERİ === */
.gallery {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 20px;
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
gap: 12px;
}
.gallery.list-view {
display: flex;
@@ -2536,8 +2874,7 @@
box-shadow 0.18s ease,
flex-direction 0.3s cubic-bezier(0.4, 0, 0.2, 1),
padding 0.3s cubic-bezier(0.4, 0, 0.2, 1),
gap 0.3s cubic-bezier(0.4, 0, 0.2, 1),
min-height 0.3s cubic-bezier(0.4, 0, 0.2, 1);
gap 0.3s cubic-bezier(0.4, 0, 0.2, 1);
cursor: pointer;
}
.media-card::after {
@@ -2578,7 +2915,7 @@
min-height: 96px;
}
.media-card.list-view .thumb {
width: 120px;
width: 128px;
height: 72px;
border-radius: 8px;
object-fit: cover;
@@ -2639,7 +2976,7 @@
}
.thumb {
width: 100%;
height: 150px;
height: 110px;
object-fit: cover;
border-radius: 10px 10px 0 0;
transition:
@@ -2655,7 +2992,7 @@
background: #ddd;
}
.info {
padding: 10px;
padding: 8px;
display: flex;
flex-direction: column;
gap: 4px;
@@ -2867,7 +3204,7 @@
}
.gallery {
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
}
.media-card.list-view {
flex-direction: column;
@@ -2890,7 +3227,7 @@
}
.gallery {
grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
}
}
@@ -2928,8 +3265,8 @@
/* === GALERİ === */
.gallery {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 20px;
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
gap: 12px;
}
.gallery.list-view {
display: flex;
@@ -3037,7 +3374,7 @@
}
.thumb {
width: 100%;
height: 150px;
height: 110px;
object-fit: cover;
}
.thumb.placeholder {
@@ -3178,7 +3515,7 @@
/* === RESPONSIVE === */
@media (max-width: 768px) {
.gallery {
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
}
.media-card.list-view {
flex-direction: column;
@@ -3194,7 +3531,7 @@
}
@media (max-width: 480px) {
.gallery {
grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
}
}
/* Folder görünümü */
@@ -3202,15 +3539,14 @@
background: transparent;
border: none;
box-shadow: none;
padding: 24px 12px 18px;
padding: 12px 12px 8px;
align-items: center;
min-height: 210px;
}
.folder-card::after {
display: none;
}
.folder-card:hover {
background: transparent;
background: rgba(0, 0, 0, 0.03);
border: none;
box-shadow: none;
}
@@ -3223,21 +3559,23 @@
}
.folder-thumb {
width: 100%;
height: 150px;
height: 110px;
display: flex;
align-items: center;
justify-content: center;
flex: 1;
}
.folder-thumb img {
width: 135px;
height: 135px;
width: 95px;
height: 95px;
object-fit: contain;
filter: drop-shadow(0 6px 18px rgba(0, 0, 0, 0.16));
}
.folder-info {
margin-top: 14px;
margin-top: 4px;
width: 100%;
text-align: center;
flex-shrink: 0;
}
.folder-name {
font-weight: 600;
@@ -3246,9 +3584,24 @@
line-height: 1.35;
word-break: break-word;
}
.folder-rename-input {
width: 100%;
padding: 6px 8px;
border: 1px solid #c9c9c9;
border-radius: 6px;
font-size: 15px;
outline: none;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.folder-rename-input:focus {
border-color: #2d965a;
box-shadow: 0 0 0 2px rgba(45, 150, 90, 0.2);
}
.folder-card:hover .folder-name,
.folder-card.is-selected .folder-name {
color: #1f78ff;
color: #333;
}
.folder-card.list-view {
flex-direction: row;
@@ -3258,8 +3611,8 @@
gap: 18px;
}
.folder-card.list-view .folder-thumb {
width: 135px;
height: 135px;
width: 128px;
height: 128px;
}
.folder-card.list-view .folder-info {
margin-top: 0;

File diff suppressed because it is too large Load Diff

View File

@@ -280,8 +280,8 @@ let filteredShows = [];
const ext = name.split(".").pop()?.toLowerCase() || "";
const inferredType = ext ? `video/${ext}` : "video/mp4";
const size =
Number(episode.fileSize) ||
Number(episode.mediaInfo?.format?.size) ||
Number(episode.mediaInfo?.format?.bit_rate) ||
null;
return {
name,
@@ -300,9 +300,19 @@ let filteredShows = [];
)
.filter(Boolean);
const encodePathSegments = (value) =>
value
? value
.split(/[\\/]/)
.map((segment) => encodeURIComponent(segment))
.join("/")
: "";
$: selectedName = selectedVideo?.name ?? "";
$: encName = selectedName ? encodeURIComponent(selectedName) : "";
$: downloadHref = encName ? `${API}/downloads/${encName}` : "#";
$: encName = encodePathSegments(selectedName);
$: downloadHref = encName
? `${API}/downloads/${encName}?token=${localStorage.getItem("token") || ""}`
: "#";
$: selectedLabel = selectedVideo?.episode
? `${selectedVideo.show.title} · ${formatEpisodeCode(
selectedVideo.episode
@@ -414,8 +424,9 @@ async function openVideoAtIndex(index) {
function getVideoURL() {
if (!selectedName) return "";
const token = localStorage.getItem("token");
// selectedName zaten encode edilmiş, tekrar encode etme
return `${API}/media/${selectedName}?token=${token}`;
const encoded = encodePathSegments(selectedName);
if (!encoded) return "";
return `${API}/media/${encoded}?token=${token}`;
}
function playEpisodeFromCard(episode) {

View File

@@ -0,0 +1,67 @@
import { writable } from 'svelte/store';
import { getTrashItems, restoreFromTrash, deleteFromTrash } from '../utils/api';
export const trashItems = writable([]);
export const trashCount = writable(0);
// Çöp öğelerini API'den al
export async function fetchTrashItems() {
try {
const items = await getTrashItems();
const processedItems = Array.isArray(items)
? items.map((item) => {
const segments = String(item.name || "")
.split(/[\\/]/)
.filter(Boolean);
const displayName =
segments.length > 0 ? segments[segments.length - 1] : item.name;
const parentPath =
segments.length > 1 ? segments.slice(0, -1).join("/") : "";
return {
...item,
displayName,
parentPath
};
})
: [];
trashItems.set(processedItems);
trashCount.set(processedItems.length);
return processedItems;
} catch (error) {
console.error('Çöp öğeleri alınırken hata:', error);
trashCount.set(0);
}
return [];
}
// Çöpten geri yükle
export async function restoreItem(trashName) {
try {
const result = await restoreFromTrash(trashName);
if (result.success) {
// Listeyi yenile
await fetchTrashItems();
}
return result;
} catch (error) {
console.error('Öğe geri yüklenirken hata:', error);
throw error;
}
}
// Çöpten tamamen sil
export async function deleteItemPermanently(trashName) {
try {
const result = await deleteFromTrash(trashName);
if (result.success) {
// Listeyi yenile
await fetchTrashItems();
}
return result;
} catch (error) {
console.error('Öğe silinirken hata:', error);
throw error;
}
}

View File

@@ -19,3 +19,36 @@ export async function apiFetch(path, options = {}) {
}
return res;
}
// 🗑️ Çöp API'leri
export async function getTrashItems() {
const res = await apiFetch("/api/trash");
return res.json();
}
export async function restoreFromTrash(trashName) {
const res = await apiFetch("/api/trash/restore", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ trashName })
});
return res.json();
}
export async function deleteFromTrash(trashName) {
const res = await apiFetch(`/api/trash`, {
method: "DELETE",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ trashName })
});
return res.json();
}
export async function renameFolder(path, newName) {
const res = await apiFetch("/api/folder", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ path, newName })
});
return res.json();
}

File diff suppressed because it is too large Load Diff