Trash eklendi
This commit is contained in:
@@ -12,6 +12,7 @@
|
|||||||
import { API } from "./utils/api.js";
|
import { API } from "./utils/api.js";
|
||||||
import { refreshMovieCount } from "./stores/movieStore.js";
|
import { refreshMovieCount } from "./stores/movieStore.js";
|
||||||
import { refreshTvShowCount } from "./stores/tvStore.js";
|
import { refreshTvShowCount } from "./stores/tvStore.js";
|
||||||
|
import { fetchTrashItems } from "./stores/trashStore.js";
|
||||||
|
|
||||||
const token = localStorage.getItem("token");
|
const token = localStorage.getItem("token");
|
||||||
|
|
||||||
@@ -24,7 +25,7 @@
|
|||||||
refreshTimer = setTimeout(async () => {
|
refreshTimer = setTimeout(async () => {
|
||||||
refreshTimer = null;
|
refreshTimer = null;
|
||||||
try {
|
try {
|
||||||
await Promise.all([refreshMovieCount(), refreshTvShowCount()]);
|
await Promise.all([refreshMovieCount(), refreshTvShowCount(), fetchTrashItems()]);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn("Medya sayacı yenileme başarısız:", err);
|
console.warn("Medya sayacı yenileme başarısız:", err);
|
||||||
}
|
}
|
||||||
@@ -45,6 +46,7 @@
|
|||||||
if (token) {
|
if (token) {
|
||||||
refreshMovieCount();
|
refreshMovieCount();
|
||||||
refreshTvShowCount();
|
refreshTvShowCount();
|
||||||
|
fetchTrashItems();
|
||||||
const authToken = localStorage.getItem("token");
|
const authToken = localStorage.getItem("token");
|
||||||
if (authToken) {
|
if (authToken) {
|
||||||
const wsUrl = `${API.replace("http", "ws")}?token=${authToken}`;
|
const wsUrl = `${API.replace("http", "ws")}?token=${authToken}`;
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
<script>
|
<script>
|
||||||
import { Link } from "svelte-routing";
|
import { Link } from "svelte-routing";
|
||||||
import { createEventDispatcher, onDestroy, onMount, tick } from "svelte";
|
import { createEventDispatcher, onDestroy, onMount, tick } from "svelte";
|
||||||
import { movieCount } from "../stores/movieStore.js";
|
import { movieCount } from "../stores/movieStore.js";
|
||||||
import { tvShowCount } from "../stores/tvStore.js";
|
import { tvShowCount } from "../stores/tvStore.js";
|
||||||
import { apiFetch } from "../utils/api.js";
|
import { trashCount } from "../stores/trashStore.js";
|
||||||
|
import { apiFetch } from "../utils/api.js";
|
||||||
|
|
||||||
export let menuOpen = false;
|
export let menuOpen = false;
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
let hasMovies = false;
|
let hasMovies = false;
|
||||||
let hasShows = false;
|
let hasShows = false;
|
||||||
|
let hasTrash = false;
|
||||||
// Svelte store kullanarak reaktivite sağla
|
// Svelte store kullanarak reaktivite sağla
|
||||||
import { writable } from 'svelte/store';
|
import { writable } from 'svelte/store';
|
||||||
const diskSpaceStore = writable({ totalGB: '0', usedGB: '0', usedPercent: 0 });
|
const diskSpaceStore = writable({ totalGB: '0', usedGB: '0', usedPercent: 0 });
|
||||||
@@ -37,9 +39,14 @@
|
|||||||
hasShows = (count ?? 0) > 0;
|
hasShows = (count ?? 0) > 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const unsubscribeTrash = trashCount.subscribe((count) => {
|
||||||
|
hasTrash = (count ?? 0) > 0;
|
||||||
|
});
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
unsubscribeMovie();
|
unsubscribeMovie();
|
||||||
unsubscribeTv();
|
unsubscribeTv();
|
||||||
|
unsubscribeTrash();
|
||||||
if (unsubscribeDiskSpace) {
|
if (unsubscribeDiskSpace) {
|
||||||
unsubscribeDiskSpace();
|
unsubscribeDiskSpace();
|
||||||
}
|
}
|
||||||
@@ -182,6 +189,9 @@
|
|||||||
>
|
>
|
||||||
<i class="fa-solid fa-trash icon"></i>
|
<i class="fa-solid fa-trash icon"></i>
|
||||||
Trash
|
Trash
|
||||||
|
{#if hasTrash}
|
||||||
|
<span class="badge">{$trashCount}</span>
|
||||||
|
{/if}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -208,7 +218,25 @@
|
|||||||
style="width: {diskSpace.usedPercent}%"
|
style="width: {diskSpace.usedPercent}%"
|
||||||
></div>
|
></div>
|
||||||
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import { onMount, tick } from "svelte";
|
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 { cleanFileName, extractTitleAndYear } from "../utils/filename.js";
|
||||||
import { refreshMovieCount } from "../stores/movieStore.js";
|
import { refreshMovieCount } from "../stores/movieStore.js";
|
||||||
import { refreshTvShowCount } from "../stores/tvStore.js";
|
import { refreshTvShowCount } from "../stores/tvStore.js";
|
||||||
@@ -29,6 +29,9 @@
|
|||||||
let lastDragPath = "";
|
let lastDragPath = "";
|
||||||
let searchTerm = "";
|
let searchTerm = "";
|
||||||
let hasSearch = false;
|
let hasSearch = false;
|
||||||
|
let renamingFolder = null;
|
||||||
|
let renameValue = "";
|
||||||
|
let renameInput;
|
||||||
|
|
||||||
const normalizePath = (value) => {
|
const normalizePath = (value) => {
|
||||||
if (!value) return "";
|
if (!value) return "";
|
||||||
@@ -193,7 +196,8 @@
|
|||||||
return crumbs;
|
return crumbs;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (segments.length <= MAX_TRAIL_SEGMENTS + 1) {
|
// Normal durumda tüm segmentleri göster
|
||||||
|
if (segments.length <= 4) {
|
||||||
segments.forEach((seg, index) => {
|
segments.forEach((seg, index) => {
|
||||||
const segPath = segments.slice(0, index + 1).join("/");
|
const segPath = segments.slice(0, index + 1).join("/");
|
||||||
crumbs.push({ label: seg, path: segPath });
|
crumbs.push({ label: seg, path: segPath });
|
||||||
@@ -201,20 +205,84 @@
|
|||||||
return crumbs;
|
return crumbs;
|
||||||
}
|
}
|
||||||
|
|
||||||
const firstSegment = segments[0];
|
// Overflow durumunda sadece ilk ve son 3'ü göster
|
||||||
crumbs.push({ label: firstSegment, path: firstSegment });
|
segments.forEach((seg, index) => {
|
||||||
crumbs.push({ label: "...", path: null, ellipsis: true });
|
const segPath = segments.slice(0, index + 1).join("/");
|
||||||
|
|
||||||
const tail = segments.slice(-MAX_TRAIL_SEGMENTS);
|
if (index === 0) {
|
||||||
tail.forEach((seg, index) => {
|
// İlk segmenti her zaman göster
|
||||||
const segPath = segments
|
crumbs.push({ label: seg, path: segPath });
|
||||||
.slice(0, segments.length - tail.length + index + 1)
|
} else if (index >= segments.length - 3) {
|
||||||
.join("/");
|
// Son 3 segmenti göster
|
||||||
crumbs.push({ label: seg, path: segPath });
|
crumbs.push({ label: seg, path: segPath });
|
||||||
|
}
|
||||||
|
// Aradaki segmentleri gösterme (ellipsis ile değiştirilecek)
|
||||||
});
|
});
|
||||||
|
|
||||||
return crumbs;
|
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) {
|
function updateVisibleState(fileList, path = currentPath) {
|
||||||
const dirs = buildDirectoryEntries(fileList);
|
const dirs = buildDirectoryEntries(fileList);
|
||||||
@@ -252,6 +320,16 @@
|
|||||||
);
|
);
|
||||||
applyOrdering(path);
|
applyOrdering(path);
|
||||||
breadcrumbs = computeBreadcrumbs(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) {
|
function resolveOriginalPathForDisplay(displayPath, fallbackOriginal = currentOriginalPath) {
|
||||||
@@ -369,6 +447,12 @@
|
|||||||
let isCreatingFolder = false;
|
let isCreatingFolder = false;
|
||||||
let newFolderName = "";
|
let newFolderName = "";
|
||||||
|
|
||||||
|
// Breadcrumb menü state
|
||||||
|
let breadcrumbContainer;
|
||||||
|
let showBreadcrumbMenu = false;
|
||||||
|
let breadcrumbMenuPosition = { top: 0, left: 0 };
|
||||||
|
let hiddenBreadcrumbs = [];
|
||||||
|
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== "undefined") {
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
const pathParam = params.get("path");
|
const pathParam = params.get("path");
|
||||||
@@ -521,6 +605,10 @@
|
|||||||
if (isCreatingFolder && !creating) {
|
if (isCreatingFolder && !creating) {
|
||||||
cancelCreateFolder();
|
cancelCreateFolder();
|
||||||
}
|
}
|
||||||
|
const renaming = event.target.closest(".folder-rename-input");
|
||||||
|
if (renamingFolder && !renaming) {
|
||||||
|
cancelRenameFolder();
|
||||||
|
}
|
||||||
if (selectedItems.size === 0) return;
|
if (selectedItems.size === 0) return;
|
||||||
const card = event.target.closest(".media-card");
|
const card = event.target.closest(".media-card");
|
||||||
const header = event.target.closest(".header-actions");
|
const header = event.target.closest(".header-actions");
|
||||||
@@ -1310,7 +1398,7 @@
|
|||||||
cancelCreateFolder();
|
cancelCreateFolder();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleFolderKeydown(event) {
|
function handleFolderKeydown(event) {
|
||||||
if (event.key === 'Enter') {
|
if (event.key === 'Enter') {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -1320,6 +1408,73 @@
|
|||||||
cancelCreateFolder();
|
cancelCreateFolder();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 () => {
|
onMount(async () => {
|
||||||
setSearchScope("files");
|
setSearchScope("files");
|
||||||
@@ -1327,6 +1482,25 @@
|
|||||||
const token = localStorage.getItem("token");
|
const token = localStorage.getItem("token");
|
||||||
const wsUrl = `${API.replace("http", "ws")}?token=${token}`;
|
const wsUrl = `${API.replace("http", "ws")}?token=${token}`;
|
||||||
const ws = new WebSocket(wsUrl);
|
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) => {
|
const handlePopState = (event) => {
|
||||||
if (typeof window === "undefined") return;
|
if (typeof window === "undefined") return;
|
||||||
const statePath =
|
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) {
|
if (msg.type === "progress" && msg.torrents) {
|
||||||
for (const t of msg.torrents) {
|
for (const t of msg.torrents) {
|
||||||
const savePath = t.savePath || "";
|
const savePath = t.savePath || "";
|
||||||
@@ -1468,6 +1647,10 @@
|
|||||||
cancelCreateFolder();
|
cancelCreateFolder();
|
||||||
handled = true;
|
handled = true;
|
||||||
}
|
}
|
||||||
|
if (renamingFolder) {
|
||||||
|
cancelRenameFolder();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
if (activeMenu) {
|
if (activeMenu) {
|
||||||
activeMenu = null;
|
activeMenu = null;
|
||||||
handled = true;
|
handled = true;
|
||||||
@@ -1550,6 +1733,9 @@
|
|||||||
if (activeMenu && !event.target.closest(".media-card")) {
|
if (activeMenu && !event.target.closest(".media-card")) {
|
||||||
activeMenu = null;
|
activeMenu = null;
|
||||||
}
|
}
|
||||||
|
if (showBreadcrumbMenu && !event.target.closest(".breadcrumb") && !event.target.closest(".breadcrumb-menu-portal")) {
|
||||||
|
closeBreadcrumbMenu();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener("click", handleClickOutside);
|
window.addEventListener("click", handleClickOutside);
|
||||||
@@ -1569,6 +1755,13 @@
|
|||||||
|
|
||||||
window.removeEventListener("click", handleClickOutside);
|
window.removeEventListener("click", handleClickOutside);
|
||||||
window.removeEventListener("popstate", handlePopState);
|
window.removeEventListener("popstate", handlePopState);
|
||||||
|
|
||||||
|
// Resize observer'ı temizle
|
||||||
|
if (breadcrumbContainer) {
|
||||||
|
const resizeObserver = new ResizeObserver(() => {});
|
||||||
|
resizeObserver.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ws.close();
|
ws.close();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -1582,21 +1775,61 @@
|
|||||||
<div class="files-header">
|
<div class="files-header">
|
||||||
<div class="header-title">
|
<div class="header-title">
|
||||||
<h2>Media Library</h2>
|
<h2>Media Library</h2>
|
||||||
<div class="breadcrumb">
|
<div class="breadcrumb-container" bind:this={breadcrumbContainer}>
|
||||||
{#each breadcrumbs as crumb, index (index)}
|
<div class="breadcrumb" class:has-overflow={showBreadcrumbMenu}>
|
||||||
{#if crumb.ellipsis}
|
{#if showBreadcrumbMenu && hiddenBreadcrumbs.length > 0}
|
||||||
<span class="crumb ellipsis">...</span>
|
<!-- İlk öğe (Home) -->
|
||||||
{:else}
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="crumb"
|
class="crumb"
|
||||||
class:is-active={normalizePath(crumb.path) === normalizePath(currentPath)}
|
class:is-active={normalizePath(breadcrumbs[0].path) === normalizePath(currentPath)}
|
||||||
on:click|stopPropagation={() => handleBreadcrumbClick(crumb)}
|
on:click|stopPropagation={() => handleBreadcrumbClick(breadcrumbs[0])}
|
||||||
>
|
>
|
||||||
{index === 0 ? "/Home" : `/${crumb.label}`}
|
{breadcrumbs[0].label === "Home" ? "Home" : breadcrumbs[0].label}
|
||||||
</button>
|
</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}
|
{/if}
|
||||||
{/each}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
@@ -1697,7 +1930,20 @@
|
|||||||
<img src={FOLDER_ICON_PATH} alt={`${entry.displayName} klasörü`} />
|
<img src={FOLDER_ICON_PATH} alt={`${entry.displayName} klasörü`} />
|
||||||
</div>
|
</div>
|
||||||
<div class="folder-info">
|
<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>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
{#if entry.thumbnail}
|
{#if entry.thumbnail}
|
||||||
@@ -1833,6 +2079,13 @@
|
|||||||
<i class="fa-solid fa-folder-open"></i>
|
<i class="fa-solid fa-folder-open"></i>
|
||||||
<span>Aç</span>
|
<span>Aç</span>
|
||||||
</button>
|
</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>
|
<div class="menu-divider"></div>
|
||||||
{:else}
|
{:else}
|
||||||
<button
|
<button
|
||||||
@@ -1861,6 +2114,28 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/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}
|
{#if showModal && selectedVideo}
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<div class="modal-overlay" on:click={closeModal}>
|
<div class="modal-overlay" on:click={closeModal}>
|
||||||
@@ -2302,13 +2577,22 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.breadcrumb-container {
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
.breadcrumb {
|
.breadcrumb {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 2px;
|
gap: 6px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #757575;
|
color: #757575;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.crumb {
|
.crumb {
|
||||||
border: none;
|
border: none;
|
||||||
@@ -2330,6 +2614,59 @@
|
|||||||
color: #1f78ff;
|
color: #1f78ff;
|
||||||
font-weight: 600;
|
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 {
|
.crumb.ellipsis {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
color: #9b9b9b;
|
color: #9b9b9b;
|
||||||
@@ -2432,14 +2769,13 @@
|
|||||||
background: rgba(245, 179, 51, 0.1);
|
background: rgba(245, 179, 51, 0.1);
|
||||||
border: 2px dashed var(--yellow);
|
border: 2px dashed var(--yellow);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 18px 12px;
|
padding: 12px 12px 8px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
gap: 18px;
|
gap: 8px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 210px;
|
|
||||||
animation: fadeIn 0.3s ease;
|
animation: fadeIn 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2453,32 +2789,34 @@
|
|||||||
|
|
||||||
.creating-folder .folder-thumb {
|
.creating-folder .folder-thumb {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 150px;
|
height: 110px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.creating-folder.list-view .folder-thumb {
|
.creating-folder.list-view .folder-thumb {
|
||||||
width: 135px;
|
width: 128px;
|
||||||
height: 135px;
|
height: 128px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.creating-folder .folder-thumb img {
|
.creating-folder .folder-thumb img {
|
||||||
width: 135px;
|
width: 95px;
|
||||||
height: 135px;
|
height: 95px;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
filter: drop-shadow(0 6px 18px rgba(0, 0, 0, 0.16));
|
filter: drop-shadow(0 6px 18px rgba(0, 0, 0, 0.16));
|
||||||
}
|
}
|
||||||
|
|
||||||
.creating-folder.list-view .folder-thumb img {
|
.creating-folder.list-view .folder-thumb img {
|
||||||
width: 135px;
|
width: 125px;
|
||||||
height: 135px;
|
height: 125px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.creating-folder .folder-info {
|
.creating-folder .folder-info {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.creating-folder.list-view .folder-info {
|
.creating-folder.list-view .folder-info {
|
||||||
@@ -2512,8 +2850,8 @@
|
|||||||
/* === GALERİ === */
|
/* === GALERİ === */
|
||||||
.gallery {
|
.gallery {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
||||||
gap: 20px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
.gallery.list-view {
|
.gallery.list-view {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -2536,8 +2874,7 @@
|
|||||||
box-shadow 0.18s ease,
|
box-shadow 0.18s ease,
|
||||||
flex-direction 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
flex-direction 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
||||||
padding 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),
|
gap 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
min-height 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.media-card::after {
|
.media-card::after {
|
||||||
@@ -2578,7 +2915,7 @@
|
|||||||
min-height: 96px;
|
min-height: 96px;
|
||||||
}
|
}
|
||||||
.media-card.list-view .thumb {
|
.media-card.list-view .thumb {
|
||||||
width: 120px;
|
width: 128px;
|
||||||
height: 72px;
|
height: 72px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
@@ -2639,7 +2976,7 @@
|
|||||||
}
|
}
|
||||||
.thumb {
|
.thumb {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 150px;
|
height: 110px;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
border-radius: 10px 10px 0 0;
|
border-radius: 10px 10px 0 0;
|
||||||
transition:
|
transition:
|
||||||
@@ -2655,7 +2992,7 @@
|
|||||||
background: #ddd;
|
background: #ddd;
|
||||||
}
|
}
|
||||||
.info {
|
.info {
|
||||||
padding: 10px;
|
padding: 8px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
@@ -2867,7 +3204,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.gallery {
|
.gallery {
|
||||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
||||||
}
|
}
|
||||||
.media-card.list-view {
|
.media-card.list-view {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -2890,7 +3227,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.gallery {
|
.gallery {
|
||||||
grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2928,8 +3265,8 @@
|
|||||||
/* === GALERİ === */
|
/* === GALERİ === */
|
||||||
.gallery {
|
.gallery {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
||||||
gap: 20px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
.gallery.list-view {
|
.gallery.list-view {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -3037,7 +3374,7 @@
|
|||||||
}
|
}
|
||||||
.thumb {
|
.thumb {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 150px;
|
height: 110px;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
.thumb.placeholder {
|
.thumb.placeholder {
|
||||||
@@ -3178,7 +3515,7 @@
|
|||||||
/* === RESPONSIVE === */
|
/* === RESPONSIVE === */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.gallery {
|
.gallery {
|
||||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
||||||
}
|
}
|
||||||
.media-card.list-view {
|
.media-card.list-view {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -3194,7 +3531,7 @@
|
|||||||
}
|
}
|
||||||
@media (max-width: 480px) {
|
@media (max-width: 480px) {
|
||||||
.gallery {
|
.gallery {
|
||||||
grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* Folder görünümü */
|
/* Folder görünümü */
|
||||||
@@ -3202,15 +3539,14 @@
|
|||||||
background: transparent;
|
background: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
padding: 24px 12px 18px;
|
padding: 12px 12px 8px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
min-height: 210px;
|
|
||||||
}
|
}
|
||||||
.folder-card::after {
|
.folder-card::after {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
.folder-card:hover {
|
.folder-card:hover {
|
||||||
background: transparent;
|
background: rgba(0, 0, 0, 0.03);
|
||||||
border: none;
|
border: none;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
@@ -3223,21 +3559,23 @@
|
|||||||
}
|
}
|
||||||
.folder-thumb {
|
.folder-thumb {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 150px;
|
height: 110px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
.folder-thumb img {
|
.folder-thumb img {
|
||||||
width: 135px;
|
width: 95px;
|
||||||
height: 135px;
|
height: 95px;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
filter: drop-shadow(0 6px 18px rgba(0, 0, 0, 0.16));
|
filter: drop-shadow(0 6px 18px rgba(0, 0, 0, 0.16));
|
||||||
}
|
}
|
||||||
.folder-info {
|
.folder-info {
|
||||||
margin-top: 14px;
|
margin-top: 4px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
.folder-name {
|
.folder-name {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
@@ -3246,9 +3584,24 @@
|
|||||||
line-height: 1.35;
|
line-height: 1.35;
|
||||||
word-break: break-word;
|
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:hover .folder-name,
|
||||||
.folder-card.is-selected .folder-name {
|
.folder-card.is-selected .folder-name {
|
||||||
color: #1f78ff;
|
color: #333;
|
||||||
}
|
}
|
||||||
.folder-card.list-view {
|
.folder-card.list-view {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
@@ -3258,8 +3611,8 @@
|
|||||||
gap: 18px;
|
gap: 18px;
|
||||||
}
|
}
|
||||||
.folder-card.list-view .folder-thumb {
|
.folder-card.list-view .folder-thumb {
|
||||||
width: 135px;
|
width: 128px;
|
||||||
height: 135px;
|
height: 128px;
|
||||||
}
|
}
|
||||||
.folder-card.list-view .folder-info {
|
.folder-card.list-view .folder-info {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -280,8 +280,8 @@ let filteredShows = [];
|
|||||||
const ext = name.split(".").pop()?.toLowerCase() || "";
|
const ext = name.split(".").pop()?.toLowerCase() || "";
|
||||||
const inferredType = ext ? `video/${ext}` : "video/mp4";
|
const inferredType = ext ? `video/${ext}` : "video/mp4";
|
||||||
const size =
|
const size =
|
||||||
|
Number(episode.fileSize) ||
|
||||||
Number(episode.mediaInfo?.format?.size) ||
|
Number(episode.mediaInfo?.format?.size) ||
|
||||||
Number(episode.mediaInfo?.format?.bit_rate) ||
|
|
||||||
null;
|
null;
|
||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
@@ -300,9 +300,19 @@ let filteredShows = [];
|
|||||||
)
|
)
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
|
|
||||||
|
const encodePathSegments = (value) =>
|
||||||
|
value
|
||||||
|
? value
|
||||||
|
.split(/[\\/]/)
|
||||||
|
.map((segment) => encodeURIComponent(segment))
|
||||||
|
.join("/")
|
||||||
|
: "";
|
||||||
|
|
||||||
$: selectedName = selectedVideo?.name ?? "";
|
$: selectedName = selectedVideo?.name ?? "";
|
||||||
$: encName = selectedName ? encodeURIComponent(selectedName) : "";
|
$: encName = encodePathSegments(selectedName);
|
||||||
$: downloadHref = encName ? `${API}/downloads/${encName}` : "#";
|
$: downloadHref = encName
|
||||||
|
? `${API}/downloads/${encName}?token=${localStorage.getItem("token") || ""}`
|
||||||
|
: "#";
|
||||||
$: selectedLabel = selectedVideo?.episode
|
$: selectedLabel = selectedVideo?.episode
|
||||||
? `${selectedVideo.show.title} · ${formatEpisodeCode(
|
? `${selectedVideo.show.title} · ${formatEpisodeCode(
|
||||||
selectedVideo.episode
|
selectedVideo.episode
|
||||||
@@ -414,8 +424,9 @@ async function openVideoAtIndex(index) {
|
|||||||
function getVideoURL() {
|
function getVideoURL() {
|
||||||
if (!selectedName) return "";
|
if (!selectedName) return "";
|
||||||
const token = localStorage.getItem("token");
|
const token = localStorage.getItem("token");
|
||||||
// selectedName zaten encode edilmiş, tekrar encode etme
|
const encoded = encodePathSegments(selectedName);
|
||||||
return `${API}/media/${selectedName}?token=${token}`;
|
if (!encoded) return "";
|
||||||
|
return `${API}/media/${encoded}?token=${token}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function playEpisodeFromCard(episode) {
|
function playEpisodeFromCard(episode) {
|
||||||
|
|||||||
67
client/src/stores/trashStore.js
Normal file
67
client/src/stores/trashStore.js
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,3 +19,36 @@ export async function apiFetch(path, options = {}) {
|
|||||||
}
|
}
|
||||||
return res;
|
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();
|
||||||
|
}
|
||||||
|
|||||||
1041
server/server.js
1041
server/server.js
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user