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

Commit

Permalink
v0.3.7
Browse files Browse the repository at this point in the history
  • Loading branch information
5rahim committed Oct 10, 2023
1 parent 2b47cc3 commit 4a754e0
Show file tree
Hide file tree
Showing 15 changed files with 255 additions and 65 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

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

## 0.3.7

- 💄 New Home Page design
- 🔄 Added loading overlay while scanning
- ⏪️ Disabled Anify as source for episode covers

## 0.3.6

- 🦺 Fixed incorrect completion status in episode list
Expand Down
36 changes: 18 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@

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

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

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

# Setup

Expand Down Expand Up @@ -113,43 +113,43 @@ npm run build

## Known issues

- Loading toast may persist after scan is complete
- Streaming only works intermittently
- Loading overlay may persist after scan is complete
- :shrug:

## Not planned

- Watch together feature / social features
- Torrent streaming (use [Miru](https://github.com/ThaUnknown/miru/))
- Torrent streaming
- MAL support
- Mobile app

## Future plans

- Manga support
- Desktop app
- Desktop client
- Theming
- Plugins

## Contributing

Contributions are welcome, feel free to open issues or pull requests.

## Resources

Resources used to build Seanime.

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

## Acknowledgements

Expand Down
Binary file removed docs/images/main_5.png
Binary file not shown.
Binary file added docs/images/main_6.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "seanime",
"version": "0.3.6",
"version": "0.3.7",
"author": "5rahim",
"scripts": {
"dev": "next dev --port=43200",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useSelectAtom } from "@/atoms/helpers"
import { useMediaDownloadInfo } from "@/lib/download/media-download-info"
import { useLibraryEntryDynamicDetails } from "@/atoms/library/local-file.atoms"
import React, { memo, useMemo } from "react"
import React, { memo, startTransition, useEffect, useMemo } from "react"
import { useQuery } from "@tanstack/react-query"
import { Skeleton } from "@/components/ui/skeleton"
import { AnilistShowcaseMedia } from "@/lib/anilist/fragment"
Expand All @@ -14,6 +14,8 @@ import { anilist_getCurrentEpisodeCeilingFromMedia } from "@/lib/anilist/utils"
import { anizip_getEpisode } from "@/lib/anizip/utils"
import { fetchAniZipData } from "@/lib/anizip/helpers"
import { AniZipData } from "@/lib/anizip/types"
import { useSetAtom } from "jotai/react"
import { __libraryHeaderImageAtom } from "@/app/(main)/(library)/_containers/local-library/_components/library-header"

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

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

const setHeaderImage = useSetAtom(__libraryHeaderImageAtom)

useEffect(() => {
setHeaderImage(prev => {
if (prev === null) {
return media.bannerImage || media.coverImage?.large || null
}
return prev
})
}, [])

return (
<>
<LargeEpisodeListItem
Expand All @@ -126,6 +139,12 @@ const EpisodeItem = memo(({ media, nextEpisodeProgress, nextEpisodeNumber, aniZi
onClick={() => {
router.push(`/view/${media.id}?playNext=true`)
}}
onMouseEnter={() => {
startTransition(() => {
setHeaderImage(media.bannerImage || media.coverImage?.large || null)
})
}}
larger
/>
</>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { atom, useAtomValue } from "jotai"
import Image from "next/image"
import React, { useEffect, useState } from "react"
import { Transition } from "@headlessui/react"
import { cn } from "@/components/ui/core"
import { useWindowScroll } from "react-use"

export const __libraryHeaderImageAtom = atom<string | null>(null)

// ugly but works
export function LibraryHeader() {

const image = useAtomValue(__libraryHeaderImageAtom)
const [actualImage, setActualImage] = useState<string | null>(null)
const [prevImage, setPrevImage] = useState<string | null>(null)
const [dimmed, setDimmed] = useState(false)

useEffect(() => {
if (actualImage === null) {
setActualImage(image)
} else {
setActualImage(null)
}
const t = setTimeout(() => {
setActualImage(image)
}, 500)

return () => {
// @ts-ignore
clearTimeout(t)
}
}, [image])

useEffect(() => {
if (actualImage)
setPrevImage(actualImage)
}, [actualImage])

const { y } = useWindowScroll()

useEffect(() => {
console.log(y)
if (y > 100)
setDimmed(true)
else
setDimmed(false)
}, [(y > 100)])

if (!image) return null

return (
<div className={"__header h-[18rem] z-[5] top-0 w-full md:w-[calc(100%-5rem)] fixed group/library-header"}>
<div
className="h-[25rem] z-[0] w-full flex-none object-cover object-center absolute top-0 overflow-hidden">
<div
className={"w-full absolute z-[2] top-0 h-[15rem] bg-gradient-to-b from-[--background-color] to-transparent via"}
/>
<Transition
show={!!actualImage}
enter="transition-opacity duration-500"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition-opacity duration-500"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
{(actualImage || prevImage) && <Image
src={actualImage || prevImage!}
alt={"banner image"}
fill
quality={100}
priority
sizes="100vw"
className={cn(
"object-cover object-center z-[1] opacity-50 transition-all",
// "group-hover/library-header:opacity-100",
{ "opacity-20": dimmed },
)}
/>}
</Transition>
{prevImage && <Image
src={prevImage}
alt={"banner image"}
fill
quality={100}
priority
sizes="100vw"
className={cn(
"object-cover object-center z-[1] opacity-50 transition-all",
{ "opacity-10": dimmed },
)}
/>}
<div
className={"w-full z-[2] absolute bottom-0 h-[40rem] bg-gradient-to-t from-[--background-color] via-opacity-50 via-10% to-transparent"}
/>
<div
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"}
/>
</div>
</div>
)

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { Divider } from "@/components/ui/divider"
import { BetaBadge } from "@/components/application/beta-badge"
import { FiSearch } from "@react-icons/all-files/fi/FiSearch"
import { HiOutlineSparkles } from "@react-icons/all-files/hi/HiOutlineSparkles"
import { LoadingOverlay } from "@/components/ui/loading-spinner"

export function LibraryToolbar() {

Expand Down Expand Up @@ -111,7 +112,11 @@ export function LibraryToolbar() {

return (
<>
<div className={"p-4"}>
{isScanning && <LoadingOverlay className={"fixed w-full h-full z-[80]"}>
<h3 className={"mt-2"}>Scanning...</h3>
<h4 className={"mt-2"}>Do not leave this page</h4>
</LoadingOverlay>}
<div className={"p-4 relative z-[8]"}>
<div className={"flex w-full justify-between gap-2"}>

<div className={"inline-flex gap-2 items-center"}>
Expand Down
10 changes: 5 additions & 5 deletions src/app/(main)/(library)/_containers/local-library/_lib/scan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function useManageLibraryEntries(opts: UseManageEntriesOptions) {

const handleRefreshEntries = useCallback(async () => {
if (user && token) {
const tID = toast.loading("Loading")
// const tID = toast.loading("Loading")
setIsLoading(true)

const result = await scanLocalFiles({
Expand Down Expand Up @@ -74,7 +74,7 @@ export function useManageLibraryEntries(opts: UseManageEntriesOptions) {
}

opts.onComplete()
toast.remove(tID)
// toast.remove(tID)
setIsLoading(false)
} else {
unauthenticatedAlert()
Expand All @@ -84,7 +84,7 @@ export function useManageLibraryEntries(opts: UseManageEntriesOptions) {

const handleRescanEntries = useCallback(async () => {
if (user && token) {
const tID = toast.loading("Loading")
// const tID = toast.loading("Loading")
setIsLoading(true)

const result = await scanLocalFiles({
Expand Down Expand Up @@ -129,8 +129,8 @@ export function useManageLibraryEntries(opts: UseManageEntriesOptions) {
} else if (result.error) {
toast.error(result.error)
}
opts.onComplete
toast.remove(tID)
opts.onComplete()
// toast.remove(tID)
setIsLoading(false)
} else {
unauthenticatedAlert()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,27 @@ import {
completed_libraryEntryAtoms,
currentlyWatching_libraryEntryAtoms,
LibraryEntry,
paused_libraryEntryAtoms,
planning_libraryEntryAtoms,
rest_libraryEntryAtoms,
} from "@/atoms/library/library-entry.atoms"
import { Atom } from "jotai"
import { useSelectAtom } from "@/atoms/helpers"
import { AnimeListItem } from "@/components/shared/anime-list-item"
import { Divider } from "@/components/ui/divider"
import { useAtomValue } from "jotai/react"
import { Slider } from "@/components/shared/slider"
import { ContinueWatching } from "@/app/(main)/(library)/_containers/continue-watching/continue-watching"

export function LocalLibrary() {

const currentlyWatchingEntryAtoms = useAtomValue(currentlyWatching_libraryEntryAtoms)
const pausedEntryAtoms = useAtomValue(paused_libraryEntryAtoms)
const planningEntryAtoms = useAtomValue(planning_libraryEntryAtoms)
const restEntryAtoms = useAtomValue(rest_libraryEntryAtoms)
const completedEntryAtoms = useAtomValue(completed_libraryEntryAtoms)

return (
<div className={"p-4 space-y-8"}>
<div className={"p-4 space-y-8 relative z-[5]"}>
{currentlyWatchingEntryAtoms.length > 0 && <>
<h2>Continue watching</h2>
<Slider>
Expand All @@ -33,24 +36,49 @@ export function LocalLibrary() {
/>
})}
</Slider>
<Divider/>
{/*<Divider/>*/}
<h2>Currently watching</h2>
<div
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"}>
{currentlyWatchingEntryAtoms.map(entryAtom => {
return <EntryAnimeItem key={`${entryAtom}`} entryAtom={entryAtom}/>
})}
</div>
<Divider/>
{/*<Divider/>*/}
</>}
{pausedEntryAtoms.length > 0 && <>
<h2>Paused</h2>
<div
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"}
>
{pausedEntryAtoms.map(entryAtom => {
return <EntryAnimeItem key={`${entryAtom}`} entryAtom={entryAtom}/>
})}
</div>
{/*<Divider/>*/}
</>}
{planningEntryAtoms.length > 0 && <>
<h2>Planned</h2>
<div
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"}
>
{planningEntryAtoms.map(entryAtom => {
return <EntryAnimeItem key={`${entryAtom}`} entryAtom={entryAtom}/>
})}
</div>
{/*<Divider/>*/}
</>}
{restEntryAtoms.length > 0 && <>
<div
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"}
>
{restEntryAtoms.map(entryAtom => {
return <EntryAnimeItem key={`${entryAtom}`} entryAtom={entryAtom}/>
})}
</div>
{/*<Divider/>*/}
</>}
<div
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"}>
{restEntryAtoms.map(entryAtom => {
return <EntryAnimeItem key={`${entryAtom}`} entryAtom={entryAtom}/>
})}
</div>
{completedEntryAtoms.length > 0 && <>
<Divider/>
<h2>Completed</h2>
<div
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"}>
Expand Down
Loading

0 comments on commit 4a754e0

Please sign in to comment.