feat(anime): anime yönetimi ve arayüzü ekle

Kullanıcı arayüzünde Anime sekmesi ve oynatıcı entegrasyonu eklendi.
Sunucu tarafında Anime için özel bir veri yapısı ve API uç noktaları oluşturuldu.

- Anime içerikleri için `_anime` klasöründe ayrı metadata saklama alanı eklendi.
- Kök dizindeki (root) dosyaların çöpe taşınması ve geri yüklenmesi için
  'root-trash' sistemi tanımlandı.
- TVDB sorgularında Anime için İngilizce dil tercihi uygulandı.
- Mail.ru indirmelerinde anime kapsamı (scope) desteği eklendi.
This commit is contained in:
2026-01-28 21:48:18 +03:00
parent 52bd325dc6
commit 41c602104e
6 changed files with 2784 additions and 104 deletions

View File

@@ -9,6 +9,7 @@
import Trash from "./routes/Trash.svelte";
import Movies from "./routes/Movies.svelte";
import TvShows from "./routes/TvShows.svelte";
import Anime from "./routes/Anime.svelte";
import Music from "./routes/Music.svelte";
import Profile from "./routes/Profile.svelte";
import Settings from "./routes/Settings.svelte";
@@ -16,6 +17,7 @@
import { API, getAccessToken } from "./utils/api.js";
import { refreshMovieCount } from "./stores/movieStore.js";
import { refreshTvShowCount } from "./stores/tvStore.js";
import { refreshAnimeCount } from "./stores/animeStore.js";
import { refreshMusicCount } from "./stores/musicStore.js";
import { fetchTrashItems } from "./stores/trashStore.js";
import { setAvatarUrl } from "./stores/avatarStore.js";
@@ -34,6 +36,7 @@
await Promise.all([
refreshMovieCount(),
refreshTvShowCount(),
refreshAnimeCount(),
refreshMusicCount(),
fetchTrashItems()
]);
@@ -85,6 +88,7 @@
if (token) {
refreshMovieCount();
refreshTvShowCount();
refreshAnimeCount();
refreshMusicCount();
fetchTrashItems();
loadUserProfile();
@@ -150,6 +154,7 @@
<Route path="/files" component={Files} />
<Route path="/movies" component={Movies} />
<Route path="/tv" component={TvShows} />
<Route path="/anime" component={Anime} />
<Route path="/music" component={Music} />
<Route path="/profile" component={Profile} />
<Route path="/settings" component={Settings} />

View File

@@ -3,6 +3,7 @@
import { createEventDispatcher, onDestroy, onMount, tick } from "svelte";
import { movieCount } from "../stores/movieStore.js";
import { tvShowCount } from "../stores/tvStore.js";
import { animeCount } from "../stores/animeStore.js";
import { musicCount } from "../stores/musicStore.js";
import { trashCount } from "../stores/trashStore.js";
import { apiFetch } from "../utils/api.js";
@@ -11,6 +12,7 @@ import { musicCount } from "../stores/musicStore.js";
const dispatch = createEventDispatcher();
let hasMovies = false;
let hasShows = false;
let hasAnime = false;
let hasTrash = false;
let hasMusic = false;
// Svelte store kullanarak reaktivite sağla
@@ -18,7 +20,7 @@ let hasMusic = false;
const diskSpaceStore = writable({ totalGB: '0', usedGB: '0', usedPercent: 0 });
let diskSpace;
let hasMedia = false;
$: hasMedia = hasMovies || hasShows || hasMusic;
$: hasMedia = hasMovies || hasShows || hasAnime || hasMusic;
// Store subscription'ı temizlemek için
let unsubscribeDiskSpace;
@@ -41,6 +43,10 @@ let hasMusic = false;
const unsubscribeTv = tvShowCount.subscribe((count) => {
hasShows = (count ?? 0) > 0;
});
const unsubscribeAnime = animeCount.subscribe((count) => {
hasAnime = (count ?? 0) > 0;
});
const unsubscribeTrash = trashCount.subscribe((count) => {
hasTrash = (count ?? 0) > 0;
@@ -53,6 +59,7 @@ const unsubscribeMusic = musicCount.subscribe((count) => {
onDestroy(() => {
unsubscribeMovie();
unsubscribeTv();
unsubscribeAnime();
unsubscribeTrash();
unsubscribeMusic();
if (unsubscribeDiskSpace) {
@@ -192,6 +199,20 @@ const unsubscribeMusic = musicCount.subscribe((count) => {
</Link>
{/if}
{#if hasAnime}
<Link
to="/anime"
class="item"
getProps={({ isCurrent }) => ({
class: isCurrent ? "item active" : "item",
})}
on:click={handleLinkClick}
>
<i class="fa-solid fa-ghost icon"></i>
Anime
</Link>
{/if}
{#if hasMusic}
<Link
to="/music"

File diff suppressed because it is too large Load Diff

View File

@@ -282,7 +282,8 @@
try {
const params = new URLSearchParams({
query,
type: "series"
type: "series",
scope: "anime"
});
if (mailruMatchYear.trim()) {
params.set("year", mailruMatchYear.trim());

View File

@@ -0,0 +1,43 @@
import { writable } from "svelte/store";
import { apiFetch } from "../utils/api.js";
export const animeCount = writable(0);
let requestSeq = 0;
let lastValue = 0;
let zeroTimer = null;
export async function refreshAnimeCount() {
const ticket = ++requestSeq;
try {
const resp = await apiFetch("/api/anime");
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
const list = await resp.json();
if (ticket !== requestSeq) return;
const nextVal = Array.isArray(list) ? list.length : 0;
if (nextVal > 0) {
if (zeroTimer) {
clearTimeout(zeroTimer);
zeroTimer = null;
}
lastValue = nextVal;
animeCount.set(nextVal);
} else if (lastValue > 0) {
if (zeroTimer) clearTimeout(zeroTimer);
const zeroTicket = requestSeq;
zeroTimer = setTimeout(() => {
if (zeroTicket === requestSeq) {
lastValue = 0;
animeCount.set(0);
}
zeroTimer = null;
}, 500);
} else {
lastValue = 0;
animeCount.set(0);
}
} catch (err) {
console.warn("⚠️ Anime sayacı güncellenemedi:", err?.message || err);
// Hata durumunda mevcut değeri koru, titreşimi önle
}
}

File diff suppressed because it is too large Load Diff