Skip to content
This repository was archived by the owner on Nov 28, 2023. It is now read-only.

Commit 4a754e0

Browse files
committed
v0.3.7
1 parent 2b47cc3 commit 4a754e0

File tree

15 files changed

+255
-65
lines changed

15 files changed

+255
-65
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## 0.3.7
6+
7+
- 💄 New Home Page design
8+
- 🔄 Added loading overlay while scanning
9+
- ⏪️ Disabled Anify as source for episode covers
10+
511
## 0.3.6
612

713
- 🦺 Fixed incorrect completion status in episode list

README.md

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66

77
<h4 align="center">User-friendly, self-hosted web app for managing your local library with AniList integration</h4>
88

9-
<img src="docs/images/main_5.png" alt="preview" width="100%"/>
9+
<img src="docs/images/main_6.png" alt="preview" width="100%"/>
1010

1111
🚨 This project is a hobby, it's not meant to fix every shortcoming or include every requested feature. Some features
12-
might not work as intended. Feel free to fork the project, contribute or ask questions.
12+
might not work as intended. Feel free to fork the project, contribute or open issues.
1313

1414
# Setup
1515

@@ -113,43 +113,43 @@ npm run build
113113

114114
## Known issues
115115

116-
- Loading toast may persist after scan is complete
117-
- Streaming only works intermittently
116+
- Loading overlay may persist after scan is complete
118117
- :shrug:
119118

120119
## Not planned
121120

122121
- Watch together feature / social features
123-
- Torrent streaming (use [Miru](https://github.com/ThaUnknown/miru/))
122+
- Torrent streaming
124123
- MAL support
125124
- Mobile app
126125

127126
## Future plans
128127

129128
- Manga support
130-
- Desktop app
129+
- Desktop client
131130
- Theming
132131
- Plugins
133132

133+
## Contributing
134+
135+
Contributions are welcome, feel free to open issues or pull requests.
136+
134137
## Resources
135138

136139
Resources used to build Seanime.
137140

138141
- [React](https://react.dev/)
139-
- [Next.js 13](https://nextjs.org/) - React framework + Server actions
140-
- [AniList](https://github.com/AniList/ApiV2-GraphQL-Docs) - API upon which Seanime is built
142+
- [Next.js 13](https://nextjs.org/)
143+
- [AniList](https://github.com/AniList/ApiV2-GraphQL-Docs)
141144
- [Jotai](https://jotai.org/docs/recipes/large-objects) - State management library
142-
- [Tailwind](https://tailwindcss.com/) - CSS framework built for scale
143-
- [5rahim/chalk-ui](https://chalk.rahim.app/) - UI Components (shameless plug)
144-
- [Consumet](https://github.com/consumet/api.consumet.org) - API for streaming sources
145-
- [rakun](https://github.com/lowlighter/rakun/) - JS Parser for folder and file names
145+
- [Tailwind](https://tailwindcss.com/) - CSS framework
146+
- [5rahim/chalk-ui](https://chalk.rahim.app/) - UI Components
147+
- [rakun](https://github.com/lowlighter/rakun/) - Parser
146148
- [nyaasi-api](https://github.com/ejnshtein/nyaasi-api) - Nyaa search API
147-
- [@robertklep/qbittorrent](https://github.com/robertklep/qbittorrent) Original qBittorent API code
148-
- [MPC-HC API](https://github.com/rzcoder/mpc-hc-control) - Original MPC-HC API code
149-
- [VLC API](https://github.com/alexandrucancescu/node-vlc-client) - Original VLC API code
150-
- [GraphQL Codegen](https://the-guild.dev/graphql/codegen) - GraphQL code generation
151-
- [M3U8Proxy](https://github.com/chaycee/M3U8Proxy) - Video streaming proxy
152-
- [Anify](https://github.com/Eltik/Anify/tree/main) - Covers for episodes
149+
- [@robertklep/qbittorrent](https://github.com/robertklep/qbittorrent) qBittorent API
150+
- [MPC-HC API](https://github.com/rzcoder/mpc-hc-control) - MPC-HC API
151+
- [VLC API](https://github.com/alexandrucancescu/node-vlc-client) - VLC API
152+
- [GraphQL Codegen](https://the-guild.dev/graphql/codegen)
153153

154154
## Acknowledgements
155155

docs/images/main_5.png

-1010 KB
Binary file not shown.

docs/images/main_6.png

1.45 MB
Loading

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "seanime",
3-
"version": "0.3.6",
3+
"version": "0.3.7",
44
"author": "5rahim",
55
"scripts": {
66
"dev": "next dev --port=43200",

src/app/(main)/(library)/_containers/continue-watching/continue-watching.tsx

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useSelectAtom } from "@/atoms/helpers"
22
import { useMediaDownloadInfo } from "@/lib/download/media-download-info"
33
import { useLibraryEntryDynamicDetails } from "@/atoms/library/local-file.atoms"
4-
import React, { memo, useMemo } from "react"
4+
import React, { memo, startTransition, useEffect, useMemo } from "react"
55
import { useQuery } from "@tanstack/react-query"
66
import { Skeleton } from "@/components/ui/skeleton"
77
import { AnilistShowcaseMedia } from "@/lib/anilist/fragment"
@@ -14,6 +14,8 @@ import { anilist_getCurrentEpisodeCeilingFromMedia } from "@/lib/anilist/utils"
1414
import { anizip_getEpisode } from "@/lib/anizip/utils"
1515
import { fetchAniZipData } from "@/lib/anizip/helpers"
1616
import { AniZipData } from "@/lib/anizip/types"
17+
import { useSetAtom } from "jotai/react"
18+
import { __libraryHeaderImageAtom } from "@/app/(main)/(library)/_containers/local-library/_components/library-header"
1719

1820
export function ContinueWatching(props: { entryAtom: Atom<LibraryEntry> }) {
1921

@@ -110,6 +112,17 @@ const EpisodeItem = memo(({ media, nextEpisodeProgress, nextEpisodeNumber, aniZi
110112
const date = episodeData?.airdate ? new Date(episodeData?.airdate) : undefined
111113
const mediaIsOlder = useMemo(() => date ? isBefore(date, subYears(new Date(), 2)) : undefined, [])
112114

115+
const setHeaderImage = useSetAtom(__libraryHeaderImageAtom)
116+
117+
useEffect(() => {
118+
setHeaderImage(prev => {
119+
if (prev === null) {
120+
return media.bannerImage || media.coverImage?.large || null
121+
}
122+
return prev
123+
})
124+
}, [])
125+
113126
return (
114127
<>
115128
<LargeEpisodeListItem
@@ -126,6 +139,12 @@ const EpisodeItem = memo(({ media, nextEpisodeProgress, nextEpisodeNumber, aniZi
126139
onClick={() => {
127140
router.push(`/view/${media.id}?playNext=true`)
128141
}}
142+
onMouseEnter={() => {
143+
startTransition(() => {
144+
setHeaderImage(media.bannerImage || media.coverImage?.large || null)
145+
})
146+
}}
147+
larger
129148
/>
130149
</>
131150
)
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { atom, useAtomValue } from "jotai"
2+
import Image from "next/image"
3+
import React, { useEffect, useState } from "react"
4+
import { Transition } from "@headlessui/react"
5+
import { cn } from "@/components/ui/core"
6+
import { useWindowScroll } from "react-use"
7+
8+
export const __libraryHeaderImageAtom = atom<string | null>(null)
9+
10+
// ugly but works
11+
export function LibraryHeader() {
12+
13+
const image = useAtomValue(__libraryHeaderImageAtom)
14+
const [actualImage, setActualImage] = useState<string | null>(null)
15+
const [prevImage, setPrevImage] = useState<string | null>(null)
16+
const [dimmed, setDimmed] = useState(false)
17+
18+
useEffect(() => {
19+
if (actualImage === null) {
20+
setActualImage(image)
21+
} else {
22+
setActualImage(null)
23+
}
24+
const t = setTimeout(() => {
25+
setActualImage(image)
26+
}, 500)
27+
28+
return () => {
29+
// @ts-ignore
30+
clearTimeout(t)
31+
}
32+
}, [image])
33+
34+
useEffect(() => {
35+
if (actualImage)
36+
setPrevImage(actualImage)
37+
}, [actualImage])
38+
39+
const { y } = useWindowScroll()
40+
41+
useEffect(() => {
42+
console.log(y)
43+
if (y > 100)
44+
setDimmed(true)
45+
else
46+
setDimmed(false)
47+
}, [(y > 100)])
48+
49+
if (!image) return null
50+
51+
return (
52+
<div className={"__header h-[18rem] z-[5] top-0 w-full md:w-[calc(100%-5rem)] fixed group/library-header"}>
53+
<div
54+
className="h-[25rem] z-[0] w-full flex-none object-cover object-center absolute top-0 overflow-hidden">
55+
<div
56+
className={"w-full absolute z-[2] top-0 h-[15rem] bg-gradient-to-b from-[--background-color] to-transparent via"}
57+
/>
58+
<Transition
59+
show={!!actualImage}
60+
enter="transition-opacity duration-500"
61+
enterFrom="opacity-0"
62+
enterTo="opacity-100"
63+
leave="transition-opacity duration-500"
64+
leaveFrom="opacity-100"
65+
leaveTo="opacity-0"
66+
>
67+
{(actualImage || prevImage) && <Image
68+
src={actualImage || prevImage!}
69+
alt={"banner image"}
70+
fill
71+
quality={100}
72+
priority
73+
sizes="100vw"
74+
className={cn(
75+
"object-cover object-center z-[1] opacity-50 transition-all",
76+
// "group-hover/library-header:opacity-100",
77+
{ "opacity-20": dimmed },
78+
)}
79+
/>}
80+
</Transition>
81+
{prevImage && <Image
82+
src={prevImage}
83+
alt={"banner image"}
84+
fill
85+
quality={100}
86+
priority
87+
sizes="100vw"
88+
className={cn(
89+
"object-cover object-center z-[1] opacity-50 transition-all",
90+
{ "opacity-10": dimmed },
91+
)}
92+
/>}
93+
<div
94+
className={"w-full z-[2] absolute bottom-0 h-[40rem] bg-gradient-to-t from-[--background-color] via-opacity-50 via-10% to-transparent"}
95+
/>
96+
<div
97+
className={"w-[4rem] z-[2] absolute top-0 right-0 h-[40rem] bg-gradient-to-l from-[--background-color] via-opacity-50 via-10% to-transparent"}
98+
/>
99+
</div>
100+
</div>
101+
)
102+
103+
}

src/app/(main)/(library)/_containers/local-library/_components/library-toolbar.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { Divider } from "@/components/ui/divider"
3131
import { BetaBadge } from "@/components/application/beta-badge"
3232
import { FiSearch } from "@react-icons/all-files/fi/FiSearch"
3333
import { HiOutlineSparkles } from "@react-icons/all-files/hi/HiOutlineSparkles"
34+
import { LoadingOverlay } from "@/components/ui/loading-spinner"
3435

3536
export function LibraryToolbar() {
3637

@@ -111,7 +112,11 @@ export function LibraryToolbar() {
111112

112113
return (
113114
<>
114-
<div className={"p-4"}>
115+
{isScanning && <LoadingOverlay className={"fixed w-full h-full z-[80]"}>
116+
<h3 className={"mt-2"}>Scanning...</h3>
117+
<h4 className={"mt-2"}>Do not leave this page</h4>
118+
</LoadingOverlay>}
119+
<div className={"p-4 relative z-[8]"}>
115120
<div className={"flex w-full justify-between gap-2"}>
116121

117122
<div className={"inline-flex gap-2 items-center"}>

src/app/(main)/(library)/_containers/local-library/_lib/scan.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export function useManageLibraryEntries(opts: UseManageEntriesOptions) {
3636

3737
const handleRefreshEntries = useCallback(async () => {
3838
if (user && token) {
39-
const tID = toast.loading("Loading")
39+
// const tID = toast.loading("Loading")
4040
setIsLoading(true)
4141

4242
const result = await scanLocalFiles({
@@ -74,7 +74,7 @@ export function useManageLibraryEntries(opts: UseManageEntriesOptions) {
7474
}
7575

7676
opts.onComplete()
77-
toast.remove(tID)
77+
// toast.remove(tID)
7878
setIsLoading(false)
7979
} else {
8080
unauthenticatedAlert()
@@ -84,7 +84,7 @@ export function useManageLibraryEntries(opts: UseManageEntriesOptions) {
8484

8585
const handleRescanEntries = useCallback(async () => {
8686
if (user && token) {
87-
const tID = toast.loading("Loading")
87+
// const tID = toast.loading("Loading")
8888
setIsLoading(true)
8989

9090
const result = await scanLocalFiles({
@@ -129,8 +129,8 @@ export function useManageLibraryEntries(opts: UseManageEntriesOptions) {
129129
} else if (result.error) {
130130
toast.error(result.error)
131131
}
132-
opts.onComplete
133-
toast.remove(tID)
132+
opts.onComplete()
133+
// toast.remove(tID)
134134
setIsLoading(false)
135135
} else {
136136
unauthenticatedAlert()

src/app/(main)/(library)/_containers/local-library/local-library.tsx

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,27 @@ import {
55
completed_libraryEntryAtoms,
66
currentlyWatching_libraryEntryAtoms,
77
LibraryEntry,
8+
paused_libraryEntryAtoms,
9+
planning_libraryEntryAtoms,
810
rest_libraryEntryAtoms,
911
} from "@/atoms/library/library-entry.atoms"
1012
import { Atom } from "jotai"
1113
import { useSelectAtom } from "@/atoms/helpers"
1214
import { AnimeListItem } from "@/components/shared/anime-list-item"
13-
import { Divider } from "@/components/ui/divider"
1415
import { useAtomValue } from "jotai/react"
1516
import { Slider } from "@/components/shared/slider"
1617
import { ContinueWatching } from "@/app/(main)/(library)/_containers/continue-watching/continue-watching"
1718

1819
export function LocalLibrary() {
1920

2021
const currentlyWatchingEntryAtoms = useAtomValue(currentlyWatching_libraryEntryAtoms)
22+
const pausedEntryAtoms = useAtomValue(paused_libraryEntryAtoms)
23+
const planningEntryAtoms = useAtomValue(planning_libraryEntryAtoms)
2124
const restEntryAtoms = useAtomValue(rest_libraryEntryAtoms)
2225
const completedEntryAtoms = useAtomValue(completed_libraryEntryAtoms)
2326

2427
return (
25-
<div className={"p-4 space-y-8"}>
28+
<div className={"p-4 space-y-8 relative z-[5]"}>
2629
{currentlyWatchingEntryAtoms.length > 0 && <>
2730
<h2>Continue watching</h2>
2831
<Slider>
@@ -33,24 +36,49 @@ export function LocalLibrary() {
3336
/>
3437
})}
3538
</Slider>
36-
<Divider/>
39+
{/*<Divider/>*/}
3740
<h2>Currently watching</h2>
3841
<div
3942
className={"grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-7 min-[2000px]:grid-cols-8 gap-4"}>
4043
{currentlyWatchingEntryAtoms.map(entryAtom => {
4144
return <EntryAnimeItem key={`${entryAtom}`} entryAtom={entryAtom}/>
4245
})}
4346
</div>
44-
<Divider/>
47+
{/*<Divider/>*/}
48+
</>}
49+
{pausedEntryAtoms.length > 0 && <>
50+
<h2>Paused</h2>
51+
<div
52+
className={"grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-7 min-[2000px]:grid-cols-8 gap-4"}
53+
>
54+
{pausedEntryAtoms.map(entryAtom => {
55+
return <EntryAnimeItem key={`${entryAtom}`} entryAtom={entryAtom}/>
56+
})}
57+
</div>
58+
{/*<Divider/>*/}
59+
</>}
60+
{planningEntryAtoms.length > 0 && <>
61+
<h2>Planned</h2>
62+
<div
63+
className={"grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-7 min-[2000px]:grid-cols-8 gap-4"}
64+
>
65+
{planningEntryAtoms.map(entryAtom => {
66+
return <EntryAnimeItem key={`${entryAtom}`} entryAtom={entryAtom}/>
67+
})}
68+
</div>
69+
{/*<Divider/>*/}
70+
</>}
71+
{restEntryAtoms.length > 0 && <>
72+
<div
73+
className={"grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-7 min-[2000px]:grid-cols-8 gap-4"}
74+
>
75+
{restEntryAtoms.map(entryAtom => {
76+
return <EntryAnimeItem key={`${entryAtom}`} entryAtom={entryAtom}/>
77+
})}
78+
</div>
79+
{/*<Divider/>*/}
4580
</>}
46-
<div
47-
className={"grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-7 min-[2000px]:grid-cols-8 gap-4"}>
48-
{restEntryAtoms.map(entryAtom => {
49-
return <EntryAnimeItem key={`${entryAtom}`} entryAtom={entryAtom}/>
50-
})}
51-
</div>
5281
{completedEntryAtoms.length > 0 && <>
53-
<Divider/>
5482
<h2>Completed</h2>
5583
<div
5684
className={"grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-7 min-[2000px]:grid-cols-8 gap-4"}>

0 commit comments

Comments
 (0)