1135 lines
26 KiB
Svelte
1135 lines
26 KiB
Svelte
<script>
|
||
import { onMount, tick } from "svelte";
|
||
import { API, getAccessToken } from "../utils/api.js";
|
||
import { cleanFileName } from "../utils/filename.js";
|
||
import { refreshMovieCount } from "../stores/movieStore.js";
|
||
import { refreshTvShowCount } from "../stores/tvStore.js";
|
||
import { trashItems, fetchTrashItems, restoreItem, deleteItemPermanently } from "../stores/trashStore.js";
|
||
import {
|
||
activeSearchTerm,
|
||
setSearchScope,
|
||
clearSearch
|
||
} from "../stores/searchStore.js";
|
||
|
||
const FOLDER_ICON_PATH = "/folder.svg";
|
||
let selectedItems = new Set();
|
||
let allSelected = false;
|
||
let viewMode = "grid";
|
||
let activeMenu = null;
|
||
let menuPosition = { top: 0, left: 0 };
|
||
let searchTerm = "";
|
||
let hasSearch = false;
|
||
let isLoading = true;
|
||
|
||
const VIEW_KEY = "trashViewMode";
|
||
|
||
if (typeof window !== "undefined") {
|
||
const storedView = window.localStorage.getItem(VIEW_KEY);
|
||
if (storedView === "grid" || storedView === "list") {
|
||
viewMode = storedView;
|
||
}
|
||
}
|
||
|
||
$: searchTerm = $activeSearchTerm;
|
||
$: hasSearch = searchTerm.trim().length > 0;
|
||
|
||
function filterItemsBySearch(items, term) {
|
||
const query = term.trim().toLowerCase();
|
||
if (!query) return items;
|
||
return items.filter((item) => {
|
||
const labels = [
|
||
item.name,
|
||
item.displayName,
|
||
item.parentPath
|
||
]
|
||
.filter(Boolean)
|
||
.join(" ")
|
||
.toLowerCase();
|
||
return labels.includes(query);
|
||
});
|
||
}
|
||
|
||
$: {
|
||
const keys = filteredTrashItems.map((item) => item.trashName).filter(Boolean);
|
||
allSelected = keys.length > 0 && keys.every((key) => selectedItems.has(key));
|
||
}
|
||
|
||
$: filteredTrashItems = filterItemsBySearch($trashItems, searchTerm);
|
||
|
||
async function loadTrashItems() {
|
||
isLoading = true;
|
||
try {
|
||
await fetchTrashItems();
|
||
} catch (err) {
|
||
console.error("Çöp öğeleri yüklenemedi:", err);
|
||
} finally {
|
||
isLoading = false;
|
||
}
|
||
}
|
||
|
||
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(timestamp) {
|
||
if (!timestamp) return "—";
|
||
const date = new Date(Number(timestamp));
|
||
if (Number.isNaN(date.getTime())) return "—";
|
||
return date.toLocaleString();
|
||
}
|
||
|
||
function buildThumbnailUrl(item) {
|
||
const token = getAccessToken();
|
||
const cacheBuster = `t=${Date.now()}`;
|
||
const authPart = token ? `token=${token}` : null;
|
||
const query = [authPart, cacheBuster].filter(Boolean).join("&");
|
||
return `${API}${item.thumbnail}?${query}`;
|
||
}
|
||
|
||
function toggleView() {
|
||
viewMode = viewMode === "grid" ? "list" : "grid";
|
||
if (typeof window !== "undefined") {
|
||
window.localStorage.setItem(VIEW_KEY, viewMode);
|
||
}
|
||
activeMenu = null;
|
||
}
|
||
|
||
function toggleSelection(item) {
|
||
if (!item?.trashName) return;
|
||
const next = new Set(selectedItems);
|
||
if (next.has(item.trashName)) next.delete(item.trashName);
|
||
else next.add(item.trashName);
|
||
selectedItems = next;
|
||
}
|
||
|
||
function selectAll() {
|
||
if (allSelected) {
|
||
selectedItems = new Set();
|
||
} else {
|
||
const keys = filteredTrashItems.map((item) => item.trashName).filter(Boolean);
|
||
selectedItems = new Set(keys);
|
||
}
|
||
}
|
||
|
||
function handleTrashClick(event) {
|
||
if (selectedItems.size === 0) return;
|
||
const card = event.target.closest(".media-card");
|
||
if (card) return;
|
||
selectedItems = new Set();
|
||
}
|
||
|
||
function toggleMenu(item, event) {
|
||
event.stopPropagation();
|
||
|
||
if (activeMenu?.trashName === item.trashName) {
|
||
activeMenu = null;
|
||
return;
|
||
}
|
||
|
||
activeMenu = item;
|
||
|
||
tick().then(() => {
|
||
const button = event.currentTarget;
|
||
const rect = button.getBoundingClientRect();
|
||
const menuWidth = 160;
|
||
const menuHeight = 140;
|
||
|
||
let top = rect.bottom + 4;
|
||
let left = rect.right - 8;
|
||
|
||
if (left + menuWidth > window.innerWidth) {
|
||
left = window.innerWidth - menuWidth - 12;
|
||
}
|
||
|
||
if (left < 0) {
|
||
left = 12;
|
||
}
|
||
|
||
if (top + menuHeight > window.innerHeight) {
|
||
top = rect.top - menuHeight - 4;
|
||
}
|
||
|
||
menuPosition = { top, left };
|
||
});
|
||
}
|
||
|
||
function closeMenu() {
|
||
activeMenu = null;
|
||
}
|
||
|
||
async function restoreItemFromMenu(item) {
|
||
if (!item?.trashName) return;
|
||
|
||
try {
|
||
const result = await restoreItem(item.trashName);
|
||
if (result.success) {
|
||
await Promise.all([refreshMovieCount(), refreshTvShowCount()]);
|
||
} else {
|
||
alert("Geri yükleme hatası: " + (result.error || "Bilinmeyen hata"));
|
||
}
|
||
} catch (err) {
|
||
console.error("Geri yükleme hatası:", err);
|
||
alert("Geri yükleme sırasında bir hata oluştu.");
|
||
}
|
||
|
||
closeMenu();
|
||
}
|
||
|
||
async function deleteItemFromMenu(item) {
|
||
if (!item?.trashName) return;
|
||
|
||
if (!confirm("Bu öğeyi kalıcı olarak silmek istediğinizden emin misiniz?")) {
|
||
closeMenu();
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const result = await deleteItemPermanently(item.trashName);
|
||
if (!result.success) {
|
||
alert("Silme hatası: " + (result.error || "Bilinmeyen hata"));
|
||
}
|
||
} catch (err) {
|
||
console.error("Silme hatası:", err);
|
||
alert("Silme sırasında bir hata oluştu.");
|
||
}
|
||
|
||
closeMenu();
|
||
}
|
||
|
||
async function restoreSelectedItems() {
|
||
if (selectedItems.size === 0) return;
|
||
|
||
if (!confirm(`${selectedItems.size} öğeyi geri yüklemek istediğinizden emin misiniz?`))
|
||
return;
|
||
|
||
const ids = [...selectedItems];
|
||
let successCount = 0;
|
||
let errorCount = 0;
|
||
|
||
for (const id of ids) {
|
||
try {
|
||
const result = await restoreItem(id);
|
||
if (result.success) {
|
||
successCount++;
|
||
} else {
|
||
errorCount++;
|
||
}
|
||
} catch (err) {
|
||
console.error("Geri yükleme hatası:", err);
|
||
errorCount++;
|
||
}
|
||
}
|
||
|
||
await Promise.all([refreshMovieCount(), refreshTvShowCount()]);
|
||
|
||
if (errorCount > 0) {
|
||
alert(`${successCount} öğe geri yüklendi, ${errorCount} öğede hata oluştu.`);
|
||
}
|
||
|
||
selectedItems = new Set();
|
||
activeMenu = null;
|
||
}
|
||
|
||
async function deleteSelectedItems() {
|
||
if (selectedItems.size === 0) return;
|
||
|
||
if (!confirm(`${selectedItems.size} öğeyi kalıcı olarak silmek istediğinizden emin misiniz?`))
|
||
return;
|
||
|
||
const ids = [...selectedItems];
|
||
let successCount = 0;
|
||
let errorCount = 0;
|
||
|
||
for (const id of ids) {
|
||
try {
|
||
const result = await deleteItemPermanently(id);
|
||
if (result.success) {
|
||
successCount++;
|
||
} else {
|
||
errorCount++;
|
||
}
|
||
} catch (err) {
|
||
console.error("Silme hatası:", err);
|
||
errorCount++;
|
||
}
|
||
}
|
||
|
||
if (errorCount > 0) {
|
||
alert(`${successCount} öğe silindi, ${errorCount} öğede hata oluştu.`);
|
||
}
|
||
|
||
selectedItems = new Set();
|
||
activeMenu = null;
|
||
}
|
||
|
||
async function emptyTrash() {
|
||
if ($trashItems.length === 0) return;
|
||
|
||
if (!confirm("Tüm çöpü kalıcı olarak boşaltmak istediğinizden emin misiniz? Bu işlem geri alınamaz."))
|
||
return;
|
||
|
||
let successCount = 0;
|
||
let errorCount = 0;
|
||
|
||
for (const item of $trashItems) {
|
||
if (item.trashName) {
|
||
try {
|
||
const result = await deleteItemPermanently(item.trashName);
|
||
if (result.success) {
|
||
successCount++;
|
||
} else {
|
||
errorCount++;
|
||
}
|
||
} catch (err) {
|
||
console.error("Silme hatası:", err);
|
||
errorCount++;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (errorCount > 0) {
|
||
alert(`${successCount} öğe silindi, ${errorCount} öğede hata oluştu.`);
|
||
}
|
||
}
|
||
|
||
onMount(async () => {
|
||
setSearchScope("trash");
|
||
await loadTrashItems();
|
||
|
||
function handleClickOutside(event) {
|
||
if (activeMenu && !event.target.closest(".media-card")) {
|
||
activeMenu = null;
|
||
}
|
||
}
|
||
|
||
window.addEventListener("click", handleClickOutside);
|
||
|
||
return () => {
|
||
window.removeEventListener("click", handleClickOutside);
|
||
};
|
||
});
|
||
</script>
|
||
|
||
<section class="files" on:click={handleTrashClick}>
|
||
<div class="files-header">
|
||
<div class="header-title">
|
||
<h2>Trash</h2>
|
||
</div>
|
||
<div class="header-actions">
|
||
{#if filteredTrashItems.length > 0 && selectedItems.size > 0}
|
||
<span class="selection-count">{selectedItems.size} öğe seçildi</span>
|
||
{/if}
|
||
{#if filteredTrashItems.length > 0 && selectedItems.size > 0}
|
||
<button
|
||
class="select-all-btn"
|
||
type="button"
|
||
on:click|stopPropagation={selectAll}
|
||
aria-label={allSelected ? "Seçimi temizle" : "Tümünü seç"}
|
||
>
|
||
<i class="fa-solid fa-square-check"></i>
|
||
</button>
|
||
{/if}
|
||
{#if selectedItems.size > 0}
|
||
<button
|
||
class="restore-btn"
|
||
type="button"
|
||
on:click|stopPropagation={restoreSelectedItems}
|
||
aria-label="Seçili öğeleri geri yükle"
|
||
>
|
||
<i class="fa-solid fa-rotate-left"></i>
|
||
</button>
|
||
<button
|
||
class="delete-btn"
|
||
type="button"
|
||
on:click|stopPropagation={deleteSelectedItems}
|
||
aria-label="Seçili öğeleri kalıcı olarak sil"
|
||
>
|
||
<i class="fa-solid fa-trash"></i>
|
||
</button>
|
||
{/if}
|
||
{#if $trashItems.length > 0}
|
||
<button
|
||
class="empty-trash-btn"
|
||
type="button"
|
||
on:click|stopPropagation={emptyTrash}
|
||
aria-label="Tüm çöpü boşalt"
|
||
>
|
||
<i class="fa-solid fa-broom"></i>
|
||
</button>
|
||
{/if}
|
||
<button
|
||
class="view-toggle"
|
||
class:list-active={viewMode === "list"}
|
||
type="button"
|
||
on:click|stopPropagation={toggleView}
|
||
aria-label={viewMode === "grid"
|
||
? "Liste görünümüne geç"
|
||
: "Izgara görünümüne geç"}
|
||
>
|
||
{#if viewMode === "grid"}
|
||
<i class="fa-solid fa-list"></i>
|
||
{:else}
|
||
<i class="fa-solid fa-border-all"></i>
|
||
{/if}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{#if isLoading}
|
||
<div class="loading">
|
||
<div class="loading-spinner"><i class="fa-solid fa-spinner fa-spin"></i></div>
|
||
<div>Çöp yükleniyor...</div>
|
||
</div>
|
||
{:else if filteredTrashItems.length === 0}
|
||
<div class="empty">
|
||
<div style="font-size:42px"><i class="fa-solid fa-trash"></i></div>
|
||
<div style="font-weight:700">
|
||
{#if hasSearch}
|
||
Aramanla eşleşen öğe bulunamadı
|
||
{:else}
|
||
Çöp boş
|
||
{/if}
|
||
</div>
|
||
</div>
|
||
{:else}
|
||
<div class="gallery" class:list-view={viewMode === "list"}>
|
||
{#each filteredTrashItems as item (item.trashName)}
|
||
<div
|
||
class="media-card"
|
||
class:folder-card={item.isDirectory}
|
||
class:list-view={viewMode === "list"}
|
||
class:is-selected={selectedItems.has(item.trashName)}
|
||
on:click={() => toggleSelection(item)}
|
||
>
|
||
{#if item.isDirectory}
|
||
<div class="folder-thumb">
|
||
<img src={FOLDER_ICON_PATH} alt={`${item.name} klasörü`} />
|
||
</div>
|
||
<div class="folder-info">
|
||
<div class="folder-name">{cleanFileName(item.displayName || item.name)}</div>
|
||
{#if item.parentPath}
|
||
<div class="folder-path">{item.parentPath}</div>
|
||
{/if}
|
||
<div class="folder-meta">Silinme: {formatDateTime(item.movedAt)}</div>
|
||
</div>
|
||
{:else}
|
||
{#if item.thumbnail}
|
||
<img
|
||
src={buildThumbnailUrl(item)}
|
||
alt={item.name}
|
||
class="thumb"
|
||
on:load={(e) => e.target.classList.add("loaded")}
|
||
/>
|
||
{:else}
|
||
<div class="thumb placeholder">
|
||
<i class="fa-regular fa-image"></i>
|
||
</div>
|
||
{/if}
|
||
<div class="info">
|
||
<div class="name">{cleanFileName(item.displayName || item.name)}</div>
|
||
{#if item.parentPath}
|
||
<div class="path">{item.parentPath}</div>
|
||
{/if}
|
||
<div class="size">{formatSize(item.size)}</div>
|
||
<div class="list-meta">
|
||
<div class="meta-line primary">
|
||
<span>Silinme: {formatDateTime(item.movedAt)}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{/if}
|
||
<button
|
||
class="selection-toggle"
|
||
class:is-selected={selectedItems.has(item.trashName)}
|
||
type="button"
|
||
on:click|stopPropagation={() => toggleSelection(item)}
|
||
aria-label={selectedItems.has(item.trashName)
|
||
? "Seçimi kaldır"
|
||
: "Bu öğeyi seç"}
|
||
>
|
||
{#if selectedItems.has(item.trashName)}
|
||
<i class="fa-solid fa-circle-check"></i>
|
||
{:else}
|
||
<i class="fa-regular fa-circle"></i>
|
||
{/if}
|
||
</button>
|
||
<button
|
||
class="menu-toggle"
|
||
type="button"
|
||
on:click|stopPropagation={(e) => toggleMenu(item, e)}
|
||
aria-label="Menü"
|
||
>
|
||
<i class="fa-solid fa-ellipsis"></i>
|
||
</button>
|
||
</div>
|
||
{/each}
|
||
</div>
|
||
{/if}
|
||
</section>
|
||
|
||
{#if activeMenu}
|
||
<div
|
||
class="dropdown-menu-portal"
|
||
style="top: {menuPosition.top}px; left: {menuPosition.left}px;"
|
||
on:click|stopPropagation
|
||
>
|
||
<button
|
||
class="menu-item"
|
||
on:click|stopPropagation={() => restoreItemFromMenu(activeMenu)}
|
||
>
|
||
<i class="fa-solid fa-rotate-left"></i>
|
||
<span>Geri Yükle</span>
|
||
</button>
|
||
<div class="menu-divider"></div>
|
||
<button
|
||
class="menu-item delete"
|
||
on:click|stopPropagation={() => deleteItemFromMenu(activeMenu)}
|
||
>
|
||
<i class="fa-solid fa-trash"></i>
|
||
<span>Kalıcı Sil</span>
|
||
</button>
|
||
</div>
|
||
{/if}
|
||
|
||
<style>
|
||
:root {
|
||
--yellow: #ffc107;
|
||
--yellow-dark: #e0a800;
|
||
--green: #4caf50;
|
||
--green-dark: #388e3c;
|
||
--red: #f44336;
|
||
--red-dark: #d32f2f;
|
||
}
|
||
|
||
.files-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 18px;
|
||
gap: 12px;
|
||
}
|
||
|
||
.header-title {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
gap: 4px;
|
||
min-width: 0;
|
||
flex: 1;
|
||
}
|
||
|
||
.header-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
}
|
||
|
||
.selection-count {
|
||
font-size: 13px;
|
||
color: #6a6a6a;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.select-all-btn {
|
||
background: #2e2e2e;
|
||
border: none;
|
||
color: #f5f5f5;
|
||
width: 36px;
|
||
height: 36px;
|
||
border-radius: 8px;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
outline: none;
|
||
transition:
|
||
background 0.2s ease,
|
||
transform 0.2s ease;
|
||
}
|
||
|
||
.select-all-btn i {
|
||
font-size: 16px;
|
||
}
|
||
|
||
.restore-btn {
|
||
background: var(--green);
|
||
border: none;
|
||
color: white;
|
||
padding: 8px 12px;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
font-size: 14px;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.restore-btn:hover {
|
||
background: var(--green-dark);
|
||
transform: scale(1.05);
|
||
}
|
||
|
||
.delete-btn {
|
||
background: var(--red);
|
||
border: none;
|
||
color: white;
|
||
padding: 8px 12px;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
font-size: 14px;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.delete-btn:hover {
|
||
background: var(--red-dark);
|
||
transform: scale(1.05);
|
||
}
|
||
|
||
.empty-trash-btn {
|
||
background: transparent;
|
||
border: 1px solid #ddd;
|
||
color: #666;
|
||
padding: 8px 12px;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
font-size: 14px;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.empty-trash-btn:hover {
|
||
background: var(--red);
|
||
border-color: var(--red-dark);
|
||
color: white;
|
||
transform: scale(1.05);
|
||
}
|
||
|
||
.view-toggle {
|
||
background: transparent;
|
||
border: 1px solid #ddd;
|
||
color: #666;
|
||
padding: 10px 14px;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 36px;
|
||
width: 36px;
|
||
transition: all 0.2s ease;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.view-toggle:hover {
|
||
background: var(--yellow);
|
||
border-color: var(--yellow-dark);
|
||
color: #222;
|
||
transform: scale(1.05);
|
||
}
|
||
|
||
.view-toggle:active {
|
||
transform: scale(0.95);
|
||
}
|
||
|
||
.view-toggle.list-active {
|
||
background: var(--yellow);
|
||
border-color: var(--yellow-dark);
|
||
color: #222;
|
||
}
|
||
|
||
.loading {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 40px;
|
||
gap: 16px;
|
||
color: #666;
|
||
}
|
||
|
||
.loading-spinner {
|
||
font-size: 24px;
|
||
}
|
||
|
||
.gallery {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
||
gap: 12px;
|
||
}
|
||
|
||
.gallery.list-view {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 14px;
|
||
}
|
||
|
||
.media-card {
|
||
position: relative;
|
||
background: #f6f6f6;
|
||
border-radius: 10px;
|
||
overflow: visible;
|
||
border: 1px solid #e2e2e2;
|
||
box-shadow: 0 1px 2px rgba(15, 15, 15, 0.04);
|
||
display: flex;
|
||
flex-direction: column;
|
||
isolation: isolate;
|
||
transition:
|
||
border-color 0.18s ease,
|
||
background 0.18s ease,
|
||
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);
|
||
cursor: pointer;
|
||
}
|
||
|
||
.media-card::after {
|
||
content: "";
|
||
position: absolute;
|
||
inset: 0;
|
||
background: rgba(0, 0, 0, 0.08);
|
||
opacity: 0;
|
||
pointer-events: none;
|
||
transition: opacity 0.18s ease;
|
||
}
|
||
|
||
.media-card:hover {
|
||
border-color: #d4d4d4;
|
||
background: #f1f1f1;
|
||
box-shadow: 0 2px 4px rgba(15, 15, 15, 0.06);
|
||
}
|
||
|
||
.media-card:hover::after {
|
||
opacity: 0.16;
|
||
}
|
||
|
||
.media-card.is-selected {
|
||
border-color: #2d965a;
|
||
background: #f4fbf7;
|
||
box-shadow:
|
||
0 0 0 1px rgba(45, 150, 90, 0.35),
|
||
0 4px 12px rgba(45, 150, 90, 0.12);
|
||
}
|
||
|
||
.media-card.is-selected::after {
|
||
opacity: 0.12;
|
||
}
|
||
|
||
.media-card.is-selected:hover::after {
|
||
opacity: 0.18;
|
||
}
|
||
|
||
.media-card.list-view {
|
||
flex-direction: row;
|
||
align-items: center;
|
||
padding: 12px 16px;
|
||
gap: 16px;
|
||
min-height: 96px;
|
||
}
|
||
|
||
.media-card.list-view .thumb {
|
||
width: 128px;
|
||
height: 72px;
|
||
border-radius: 8px;
|
||
object-fit: cover;
|
||
flex-shrink: 0;
|
||
transition:
|
||
width 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
||
height 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
||
border-radius 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||
}
|
||
|
||
.selection-toggle {
|
||
position: absolute;
|
||
top: 12px;
|
||
left: 12px;
|
||
width: 34px;
|
||
height: 34px;
|
||
border-radius: 50%;
|
||
border: none;
|
||
background: rgba(0, 0, 0, 0.45);
|
||
color: #f5f5f5;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
opacity: 0;
|
||
outline: none;
|
||
transform: scale(0.88);
|
||
transition:
|
||
opacity 0.2s ease,
|
||
transform 0.2s ease,
|
||
background 0.2s ease;
|
||
cursor: pointer;
|
||
pointer-events: none;
|
||
z-index: 2;
|
||
}
|
||
|
||
.selection-toggle i {
|
||
font-size: 14px;
|
||
}
|
||
|
||
.media-card:hover .selection-toggle,
|
||
.media-card.is-selected .selection-toggle {
|
||
opacity: 1;
|
||
transform: scale(1);
|
||
pointer-events: auto;
|
||
}
|
||
|
||
.selection-toggle.is-selected {
|
||
background: rgba(45, 150, 90, 0.85);
|
||
}
|
||
|
||
.selection-toggle.is-selected i {
|
||
color: #fff;
|
||
}
|
||
|
||
.media-card.list-view .selection-toggle {
|
||
top: 16px;
|
||
left: 16px;
|
||
}
|
||
|
||
.thumb {
|
||
width: 100%;
|
||
height: 110px;
|
||
object-fit: cover;
|
||
border-radius: 10px 10px 0 0;
|
||
transition:
|
||
width 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
||
height 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
||
border-radius 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||
}
|
||
|
||
.thumb.placeholder {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 42px;
|
||
background: #ddd;
|
||
}
|
||
|
||
.info {
|
||
padding: 8px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
transition:
|
||
padding 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
||
gap 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
||
flex 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||
}
|
||
|
||
.name {
|
||
font-weight: 600;
|
||
font-size: 14px;
|
||
overflow: hidden;
|
||
white-space: nowrap;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.info .path {
|
||
font-size: 12px;
|
||
color: #777;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.size {
|
||
font-size: 12px;
|
||
color: #666;
|
||
transition:
|
||
opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
||
max-height 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||
}
|
||
|
||
.media-card.list-view .info {
|
||
flex: 1;
|
||
padding: 0;
|
||
gap: 6px;
|
||
}
|
||
|
||
.media-card.list-view .name {
|
||
font-size: 15px;
|
||
}
|
||
|
||
.media-card.list-view .size {
|
||
display: none;
|
||
}
|
||
|
||
.list-meta {
|
||
display: none;
|
||
opacity: 0;
|
||
max-height: 0;
|
||
transition:
|
||
opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
||
max-height 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||
}
|
||
|
||
.media-card.list-view .list-meta {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
font-size: 13px;
|
||
color: #7a7a7a;
|
||
opacity: 1;
|
||
max-height: 200px;
|
||
}
|
||
|
||
.meta-line.primary {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
}
|
||
|
||
.menu-toggle {
|
||
position: absolute;
|
||
top: 12px;
|
||
right: 12px;
|
||
width: 34px;
|
||
height: 34px;
|
||
border-radius: 50%;
|
||
border: none;
|
||
background: rgba(0, 0, 0, 0.45);
|
||
color: #f5f5f5;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
opacity: 0;
|
||
outline: none;
|
||
transform: scale(0.88);
|
||
transition:
|
||
opacity 0.2s ease,
|
||
transform 0.2s ease,
|
||
background 0.2s ease;
|
||
cursor: pointer;
|
||
pointer-events: none;
|
||
z-index: 2;
|
||
}
|
||
|
||
.menu-toggle i {
|
||
font-size: 14px;
|
||
}
|
||
|
||
.media-card:hover .menu-toggle,
|
||
.media-card.is-selected .menu-toggle {
|
||
opacity: 1;
|
||
transform: scale(1);
|
||
pointer-events: auto;
|
||
}
|
||
|
||
.menu-toggle:hover {
|
||
background: rgba(0, 0, 0, 0.65);
|
||
}
|
||
|
||
.media-card.list-view .menu-toggle {
|
||
top: 16px;
|
||
right: 16px;
|
||
}
|
||
|
||
.dropdown-menu-portal {
|
||
position: fixed;
|
||
background: #ffffff;
|
||
border-radius: 8px;
|
||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
||
min-width: 160px;
|
||
z-index: 10000;
|
||
animation: fadeIn 0.2s ease;
|
||
}
|
||
|
||
@keyframes fadeIn {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateY(-8px);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
}
|
||
|
||
.menu-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
width: 100%;
|
||
padding: 12px 16px;
|
||
border: none;
|
||
background: transparent;
|
||
color: #333;
|
||
font-size: 14px;
|
||
cursor: pointer;
|
||
transition: background-color 0.2s ease;
|
||
text-align: left;
|
||
}
|
||
|
||
.menu-item:first-child {
|
||
border-radius: 8px 8px 0 0;
|
||
}
|
||
|
||
.menu-item:last-child {
|
||
border-radius: 0 0 8px 8px;
|
||
}
|
||
|
||
.menu-item:hover {
|
||
background-color: #f5f5f5;
|
||
}
|
||
|
||
.menu-item.delete {
|
||
color: #e53935;
|
||
}
|
||
|
||
.menu-item.delete:hover {
|
||
background-color: #ffebee;
|
||
}
|
||
|
||
.menu-item i {
|
||
font-size: 14px;
|
||
width: 16px;
|
||
text-align: center;
|
||
}
|
||
|
||
.menu-divider {
|
||
height: 1px;
|
||
background-color: #e0e0e0;
|
||
margin: 0;
|
||
}
|
||
|
||
.empty {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 40px;
|
||
gap: 16px;
|
||
color: #666;
|
||
}
|
||
|
||
/* Folder görünümü */
|
||
.folder-card {
|
||
background: transparent;
|
||
border: none;
|
||
box-shadow: none;
|
||
padding: 12px 12px 8px;
|
||
align-items: center;
|
||
}
|
||
|
||
.folder-card::after {
|
||
display: none;
|
||
}
|
||
|
||
.folder-card:hover {
|
||
background: rgba(0, 0, 0, 0.03);
|
||
border: none;
|
||
box-shadow: none;
|
||
}
|
||
|
||
.folder-card.is-selected {
|
||
background: rgba(45, 150, 90, 0.12);
|
||
box-shadow: none;
|
||
}
|
||
|
||
.folder-card.is-selected::after {
|
||
display: none;
|
||
}
|
||
|
||
.folder-thumb {
|
||
width: 100%;
|
||
height: 110px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex: 1;
|
||
}
|
||
|
||
.folder-thumb img {
|
||
width: 95px;
|
||
height: 95px;
|
||
object-fit: contain;
|
||
filter: drop-shadow(0 6px 18px rgba(0, 0, 0, 0.16));
|
||
}
|
||
|
||
.folder-card.list-view .folder-thumb {
|
||
width: 128px;
|
||
height: 128px;
|
||
}
|
||
|
||
.folder-card.list-view .folder-thumb img {
|
||
width: 125px;
|
||
height: 125px;
|
||
}
|
||
|
||
.folder-info {
|
||
margin-top: 4px;
|
||
width: 100%;
|
||
text-align: center;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.folder-card.list-view .folder-info {
|
||
margin-top: 0;
|
||
text-align: left;
|
||
}
|
||
|
||
.folder-name {
|
||
font-weight: 600;
|
||
font-size: 15px;
|
||
color: #2d2d2d;
|
||
line-height: 1.35;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.folder-path,
|
||
.folder-meta {
|
||
font-size: 12px;
|
||
color: #666;
|
||
margin-top: 4px;
|
||
}
|
||
|
||
.folder-path {
|
||
word-break: break-word;
|
||
}
|
||
|
||
.folder-meta {
|
||
font-style: italic;
|
||
}
|
||
|
||
.folder-card:hover .folder-name,
|
||
.folder-card.is-selected .folder-name {
|
||
color: #333;
|
||
}
|
||
|
||
.folder-card.list-view .folder-name {
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
/* Responsive */
|
||
@media (max-width: 768px) {
|
||
.gallery {
|
||
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
||
}
|
||
.media-card.list-view {
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
}
|
||
.media-card.list-view .thumb {
|
||
width: 100%;
|
||
height: 160px;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 480px) {
|
||
.gallery {
|
||
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
|
||
}
|
||
}
|
||
</style>
|