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:
@@ -9,6 +9,7 @@
|
|||||||
import Trash from "./routes/Trash.svelte";
|
import Trash from "./routes/Trash.svelte";
|
||||||
import Movies from "./routes/Movies.svelte";
|
import Movies from "./routes/Movies.svelte";
|
||||||
import TvShows from "./routes/TvShows.svelte";
|
import TvShows from "./routes/TvShows.svelte";
|
||||||
|
import Anime from "./routes/Anime.svelte";
|
||||||
import Music from "./routes/Music.svelte";
|
import Music from "./routes/Music.svelte";
|
||||||
import Profile from "./routes/Profile.svelte";
|
import Profile from "./routes/Profile.svelte";
|
||||||
import Settings from "./routes/Settings.svelte";
|
import Settings from "./routes/Settings.svelte";
|
||||||
@@ -16,6 +17,7 @@
|
|||||||
import { API, getAccessToken } from "./utils/api.js";
|
import { API, getAccessToken } 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 { refreshAnimeCount } from "./stores/animeStore.js";
|
||||||
import { refreshMusicCount } from "./stores/musicStore.js";
|
import { refreshMusicCount } from "./stores/musicStore.js";
|
||||||
import { fetchTrashItems } from "./stores/trashStore.js";
|
import { fetchTrashItems } from "./stores/trashStore.js";
|
||||||
import { setAvatarUrl } from "./stores/avatarStore.js";
|
import { setAvatarUrl } from "./stores/avatarStore.js";
|
||||||
@@ -34,6 +36,7 @@
|
|||||||
await Promise.all([
|
await Promise.all([
|
||||||
refreshMovieCount(),
|
refreshMovieCount(),
|
||||||
refreshTvShowCount(),
|
refreshTvShowCount(),
|
||||||
|
refreshAnimeCount(),
|
||||||
refreshMusicCount(),
|
refreshMusicCount(),
|
||||||
fetchTrashItems()
|
fetchTrashItems()
|
||||||
]);
|
]);
|
||||||
@@ -85,6 +88,7 @@
|
|||||||
if (token) {
|
if (token) {
|
||||||
refreshMovieCount();
|
refreshMovieCount();
|
||||||
refreshTvShowCount();
|
refreshTvShowCount();
|
||||||
|
refreshAnimeCount();
|
||||||
refreshMusicCount();
|
refreshMusicCount();
|
||||||
fetchTrashItems();
|
fetchTrashItems();
|
||||||
loadUserProfile();
|
loadUserProfile();
|
||||||
@@ -150,6 +154,7 @@
|
|||||||
<Route path="/files" component={Files} />
|
<Route path="/files" component={Files} />
|
||||||
<Route path="/movies" component={Movies} />
|
<Route path="/movies" component={Movies} />
|
||||||
<Route path="/tv" component={TvShows} />
|
<Route path="/tv" component={TvShows} />
|
||||||
|
<Route path="/anime" component={Anime} />
|
||||||
<Route path="/music" component={Music} />
|
<Route path="/music" component={Music} />
|
||||||
<Route path="/profile" component={Profile} />
|
<Route path="/profile" component={Profile} />
|
||||||
<Route path="/settings" component={Settings} />
|
<Route path="/settings" component={Settings} />
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
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 { animeCount } from "../stores/animeStore.js";
|
||||||
import { musicCount } from "../stores/musicStore.js";
|
import { musicCount } from "../stores/musicStore.js";
|
||||||
import { trashCount } from "../stores/trashStore.js";
|
import { trashCount } from "../stores/trashStore.js";
|
||||||
import { apiFetch } from "../utils/api.js";
|
import { apiFetch } from "../utils/api.js";
|
||||||
@@ -11,6 +12,7 @@ import { musicCount } from "../stores/musicStore.js";
|
|||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
let hasMovies = false;
|
let hasMovies = false;
|
||||||
let hasShows = false;
|
let hasShows = false;
|
||||||
|
let hasAnime = false;
|
||||||
let hasTrash = false;
|
let hasTrash = false;
|
||||||
let hasMusic = false;
|
let hasMusic = false;
|
||||||
// Svelte store kullanarak reaktivite sağla
|
// Svelte store kullanarak reaktivite sağla
|
||||||
@@ -18,7 +20,7 @@ let hasMusic = false;
|
|||||||
const diskSpaceStore = writable({ totalGB: '0', usedGB: '0', usedPercent: 0 });
|
const diskSpaceStore = writable({ totalGB: '0', usedGB: '0', usedPercent: 0 });
|
||||||
let diskSpace;
|
let diskSpace;
|
||||||
let hasMedia = false;
|
let hasMedia = false;
|
||||||
$: hasMedia = hasMovies || hasShows || hasMusic;
|
$: hasMedia = hasMovies || hasShows || hasAnime || hasMusic;
|
||||||
|
|
||||||
// Store subscription'ı temizlemek için
|
// Store subscription'ı temizlemek için
|
||||||
let unsubscribeDiskSpace;
|
let unsubscribeDiskSpace;
|
||||||
@@ -42,6 +44,10 @@ let hasMusic = false;
|
|||||||
hasShows = (count ?? 0) > 0;
|
hasShows = (count ?? 0) > 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const unsubscribeAnime = animeCount.subscribe((count) => {
|
||||||
|
hasAnime = (count ?? 0) > 0;
|
||||||
|
});
|
||||||
|
|
||||||
const unsubscribeTrash = trashCount.subscribe((count) => {
|
const unsubscribeTrash = trashCount.subscribe((count) => {
|
||||||
hasTrash = (count ?? 0) > 0;
|
hasTrash = (count ?? 0) > 0;
|
||||||
});
|
});
|
||||||
@@ -53,6 +59,7 @@ const unsubscribeMusic = musicCount.subscribe((count) => {
|
|||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
unsubscribeMovie();
|
unsubscribeMovie();
|
||||||
unsubscribeTv();
|
unsubscribeTv();
|
||||||
|
unsubscribeAnime();
|
||||||
unsubscribeTrash();
|
unsubscribeTrash();
|
||||||
unsubscribeMusic();
|
unsubscribeMusic();
|
||||||
if (unsubscribeDiskSpace) {
|
if (unsubscribeDiskSpace) {
|
||||||
@@ -192,6 +199,20 @@ const unsubscribeMusic = musicCount.subscribe((count) => {
|
|||||||
</Link>
|
</Link>
|
||||||
{/if}
|
{/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}
|
{#if hasMusic}
|
||||||
<Link
|
<Link
|
||||||
to="/music"
|
to="/music"
|
||||||
|
|||||||
1870
client/src/routes/Anime.svelte
Normal file
1870
client/src/routes/Anime.svelte
Normal file
File diff suppressed because it is too large
Load Diff
@@ -282,7 +282,8 @@
|
|||||||
try {
|
try {
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
query,
|
query,
|
||||||
type: "series"
|
type: "series",
|
||||||
|
scope: "anime"
|
||||||
});
|
});
|
||||||
if (mailruMatchYear.trim()) {
|
if (mailruMatchYear.trim()) {
|
||||||
params.set("year", mailruMatchYear.trim());
|
params.set("year", mailruMatchYear.trim());
|
||||||
|
|||||||
43
client/src/stores/animeStore.js
Normal file
43
client/src/stores/animeStore.js
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
908
server/server.js
908
server/server.js
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user