TMDB Entegrasyonu
This commit is contained in:
@@ -2,18 +2,42 @@
|
||||
import { onMount, tick } from "svelte";
|
||||
import { API, apiFetch } from "../utils/api.js";
|
||||
import { cleanFileName } from "../utils/filename.js";
|
||||
import { refreshMovieCount } from "../stores/movieStore.js";
|
||||
let files = [];
|
||||
let showModal = false;
|
||||
let selectedVideo = null;
|
||||
let subtitleURL = null;
|
||||
let subtitleLang = "en";
|
||||
let subtitleLabel = "Custom Subtitles";
|
||||
let viewMode = "grid";
|
||||
let selectedItems = new Set();
|
||||
let allSelected = false;
|
||||
// 🎬 Player kontrolleri
|
||||
let videoEl;
|
||||
let isPlaying = false;
|
||||
const VIEW_KEY = "filesViewMode";
|
||||
let viewMode = "grid";
|
||||
if (typeof window !== "undefined") {
|
||||
const storedView = window.localStorage.getItem(VIEW_KEY);
|
||||
if (storedView === "grid" || storedView === "list") {
|
||||
viewMode = storedView;
|
||||
}
|
||||
}
|
||||
let selectedItems = new Set();
|
||||
let allSelected = false;
|
||||
let pendingPlayTarget = null;
|
||||
if (typeof window !== "undefined") {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const playParam = params.get("play");
|
||||
if (playParam) {
|
||||
try {
|
||||
pendingPlayTarget = decodeURIComponent(playParam);
|
||||
} catch (err) {
|
||||
pendingPlayTarget = playParam;
|
||||
}
|
||||
params.delete("play");
|
||||
const search = params.toString();
|
||||
const newUrl = `${window.location.pathname}${search ? `?${search}` : ""}${window.location.hash}`;
|
||||
window.history.replaceState({}, "", newUrl);
|
||||
}
|
||||
}
|
||||
// 🎬 Player kontrolleri
|
||||
let videoEl;
|
||||
let isPlaying = false;
|
||||
let currentTime = 0;
|
||||
let duration = 0;
|
||||
let volume = 1;
|
||||
@@ -39,6 +63,8 @@
|
||||
[...selectedItems].filter((name) => existing.has(name)),
|
||||
);
|
||||
allSelected = files.length > 0 && selectedItems.size === files.length;
|
||||
tryAutoPlay();
|
||||
refreshMovieCount();
|
||||
}
|
||||
function formatSize(bytes) {
|
||||
if (!bytes) return "0 MB";
|
||||
@@ -52,8 +78,33 @@
|
||||
if (Number.isNaN(date.getTime())) return "—";
|
||||
return date.toLocaleString();
|
||||
}
|
||||
function formatVideoCodec(info) {
|
||||
if (!info) return null;
|
||||
const codec = info.codec ? info.codec.toUpperCase() : null;
|
||||
const resolution =
|
||||
info.resolution || (info.height ? `${info.height}p` : null);
|
||||
return [codec, resolution].filter(Boolean).join(" · ");
|
||||
}
|
||||
function formatAudioCodec(info) {
|
||||
if (!info) return null;
|
||||
const codec = info.codec ? info.codec.toUpperCase() : null;
|
||||
let channels = null;
|
||||
if (info.channelLayout) channels = info.channelLayout.toUpperCase();
|
||||
else if (info.channels) {
|
||||
channels =
|
||||
info.channels === 6
|
||||
? "5.1"
|
||||
: info.channels === 2
|
||||
? "2.0"
|
||||
: `${info.channels}`;
|
||||
}
|
||||
return [codec, channels].filter(Boolean).join(" · ");
|
||||
}
|
||||
function toggleView() {
|
||||
viewMode = viewMode === "grid" ? "list" : "grid";
|
||||
if (typeof window !== "undefined") {
|
||||
window.localStorage.setItem(VIEW_KEY, viewMode);
|
||||
}
|
||||
}
|
||||
function toggleSelection(file) {
|
||||
const next = new Set(selectedItems);
|
||||
@@ -80,6 +131,27 @@
|
||||
selectedItems = new Set();
|
||||
allSelected = false;
|
||||
}
|
||||
|
||||
function tryAutoPlay() {
|
||||
if (!pendingPlayTarget || files.length === 0) return;
|
||||
const normalizedTarget = pendingPlayTarget
|
||||
.replace(/^\.?\//, "")
|
||||
.replace(/\\/g, "/");
|
||||
const candidate =
|
||||
files.find((f) => {
|
||||
const normalizedName = f.name
|
||||
.replace(/^\.?\//, "")
|
||||
.replace(/\\/g, "/");
|
||||
return (
|
||||
normalizedName === normalizedTarget ||
|
||||
normalizedName.endsWith(normalizedTarget)
|
||||
);
|
||||
}) || null;
|
||||
if (candidate) {
|
||||
pendingPlayTarget = null;
|
||||
openModal(candidate);
|
||||
}
|
||||
}
|
||||
async function openModal(f) {
|
||||
stopCurrentVideo();
|
||||
videoEl = null;
|
||||
@@ -251,6 +323,7 @@
|
||||
|
||||
selectedItems = new Set(failed);
|
||||
allSelected = failed.length > 0 && failed.length === files.length;
|
||||
await refreshMovieCount();
|
||||
}
|
||||
onMount(async () => {
|
||||
await loadFiles(); // önce dosyaları getir
|
||||
@@ -288,6 +361,32 @@
|
||||
}
|
||||
};
|
||||
function handleKey(e) {
|
||||
const active = document.activeElement;
|
||||
const tag = active?.tagName;
|
||||
const type = active?.type?.toLowerCase();
|
||||
const isTextInput =
|
||||
tag === "INPUT" &&
|
||||
[
|
||||
"text",
|
||||
"search",
|
||||
"email",
|
||||
"password",
|
||||
"number",
|
||||
"url",
|
||||
"tel"
|
||||
].includes(type);
|
||||
const isEditable =
|
||||
(tag === "TEXTAREA" || isTextInput || active?.isContentEditable) ?? false;
|
||||
|
||||
if (e.metaKey && e.key && e.key.toLowerCase() === "backspace") {
|
||||
if (isEditable) return;
|
||||
if (selectedItems.size > 0) {
|
||||
e.preventDefault();
|
||||
deleteSelectedFiles();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const isCmd = e.metaKey || e.ctrlKey;
|
||||
if (isCmd && e.key.toLowerCase() === "a") {
|
||||
e.preventDefault();
|
||||
@@ -312,8 +411,13 @@
|
||||
|
||||
<section class="files" on:click={handleFilesClick}>
|
||||
<div class="files-header">
|
||||
<h2>Media Library</h2>
|
||||
<div class="header-title">
|
||||
<h2>Media Library</h2>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
{#if files.length > 0 && selectedItems.size > 0}
|
||||
<span class="selection-count">{selectedItems.size} dosya seçildi</span>
|
||||
{/if}
|
||||
{#if files.length > 0 && selectedItems.size > 0}
|
||||
<button
|
||||
class="select-all-btn"
|
||||
@@ -394,6 +498,35 @@
|
||||
{f.tracker ? f.tracker : "Bilinmiyor"}
|
||||
</span>
|
||||
</div>
|
||||
{#if f.mediaInfo?.video || f.mediaInfo?.audio}
|
||||
<div class="meta-line codecs">
|
||||
{#if f.extension}
|
||||
<span class="codec-chip file-type">
|
||||
{#if f.type?.startsWith("image/")}
|
||||
<i class="fa-solid fa-file-image"></i>
|
||||
{:else}
|
||||
<i class="fa-solid fa-file-video"></i>
|
||||
{/if}
|
||||
{f.extension.toUpperCase()}
|
||||
</span>
|
||||
{/if}
|
||||
{#if f.mediaInfo?.video}
|
||||
<span class="codec-chip">
|
||||
<i class="fa-solid fa-film"></i>
|
||||
{formatVideoCodec(f.mediaInfo.video)}
|
||||
</span>
|
||||
{/if}
|
||||
{#if f.mediaInfo?.video && f.mediaInfo?.audio}
|
||||
<span class="codec-separator">|</span>
|
||||
{/if}
|
||||
{#if f.mediaInfo?.audio}
|
||||
<span class="codec-chip">
|
||||
<i class="fa-solid fa-volume-high"></i>
|
||||
{formatAudioCodec(f.mediaInfo.audio)}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="media-type-icon">
|
||||
@@ -733,6 +866,16 @@
|
||||
margin-bottom: 18px;
|
||||
gap: 12px;
|
||||
}
|
||||
.header-title {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 6px;
|
||||
}
|
||||
.selection-count {
|
||||
font-size: 13px;
|
||||
color: #6a6a6a;
|
||||
font-weight: 500;
|
||||
}
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -967,6 +1110,32 @@
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.meta-line.codecs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
color: #5a5a5a;
|
||||
font-size: 13px;
|
||||
}
|
||||
.codec-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
color: #4e4e4e;
|
||||
font-weight: 500;
|
||||
}
|
||||
.codec-chip.file-type {
|
||||
color: #1f1f1f;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.codec-chip i {
|
||||
color: #ffc107;
|
||||
font-size: 12px;
|
||||
}
|
||||
.codec-separator {
|
||||
color: #7a7a7a;
|
||||
font-weight: 500;
|
||||
}
|
||||
.status-badge {
|
||||
color: #2f8a4d;
|
||||
font-weight: 600;
|
||||
|
||||
1047
client/src/routes/Movies.svelte
Normal file
1047
client/src/routes/Movies.svelte
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user