From d113cf0a7b324466e100e4b4b89df435283dd44a Mon Sep 17 00:00:00 2001 From: Kevin Hoang Date: Wed, 3 Jul 2024 03:19:11 -0500 Subject: [PATCH] 1.5.2 --- CHANGELOG.md | 6 + README.md | 3 +- help/InstallHelp.md | 2 +- release/app/package-lock.json | 4 +- release/app/package.json | 2 +- src/main/main.ts | 4 + src/renderer/App.tsx | 10 + .../components/app/main/MediaCard.tsx | 41 ++++ .../components/app/main/MediaCardCompact.tsx | 37 +++- .../components/app/sidebar/SortMenu.tsx | 58 ++++++ .../components/settings/SettingsMain.tsx | 2 + .../app/NextAiringEpisodeMainList.tsx | 39 ++++ .../functions/sort/sortMainFunctions.ts | 110 +++++++---- .../functions/view/DataTableFunctions.tsx | 48 ++++- .../functions/view/MainViewFunctions.tsx | 180 ++++++++++-------- src/renderer/store.ts | 8 + 16 files changed, 427 insertions(+), 127 deletions(-) create mode 100644 src/renderer/components/settings/app/NextAiringEpisodeMainList.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index b8c5459..0ace8e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changes +## 1.5.2 + +- Releasing/Not Yet Released anime list cards now have the next airing episode timer just like the seasonal anime cards. +- Next Airing Time is now shown as a sort option on the user's anime list. +- An option has been added settings menu to hide/show the next airing time for anime media cards in the user's anime list. By default the option is set to show. + ## 1.5.1 - Changed list table header and container colors. diff --git a/README.md b/README.md index 3ec14d0..bf2efa0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@

- # AniCour AniCour is an anime, manga, and light novel tracking app that helps you discover, manage and track your lists from your desktop. @@ -39,7 +38,7 @@ Downloads can be found [here](https://github.com/ReStartQ/AniCour/releases). Che **_Whenever you get a warning message in the browser, you will have to allow for the download with an alternate option that may show up as something like "download suspicious file" (chrome) or "keep file" (edge)._** 1. Download the setup file from the latest releases. The setup file is labeled as AniCour-Setup-x.x.x.exe, where x.x.x denotes the version. -
**Ex: AniCour-Setup-1.5.1.exe** +
**Ex: AniCour-Setup-1.5.2.exe** 2. Run the setup file, Windows will give a message like below, click on "More info"

3. A new option will appear, "Run anyway". Click on it.

4. The installer menu will open up to allow you to install it on your computer.

diff --git a/help/InstallHelp.md b/help/InstallHelp.md index f321618..be17af8 100644 --- a/help/InstallHelp.md +++ b/help/InstallHelp.md @@ -6,7 +6,7 @@ You can download the app [here](https://github.com/ReStartQ/AniCour/releases). 1. Download the setup file from the latest release and install it on your computer. 2. The setup file is labeled as AniCour-Setup-x.x.x.exe, where x denotes a number for the version. - **Ex: AniCour-Setup-1.5.1.exe** + **Ex: AniCour-Setup-1.5.2.exe** 3. When you run the exe file, Windows will give a message like below, click on "More info"

4. A new option will appear, "Run anyway". Click on it.

5. The installer menu will open up to allow you to install it on your computer.

diff --git a/release/app/package-lock.json b/release/app/package-lock.json index e60474a..fc93056 100644 --- a/release/app/package-lock.json +++ b/release/app/package-lock.json @@ -1,12 +1,12 @@ { "name": "anicour", - "version": "1.5.1", + "version": "1.5.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "anicour", - "version": "1.5.1", + "version": "1.5.2", "hasInstallScript": true, "license": "GPL-3.0" } diff --git a/release/app/package.json b/release/app/package.json index 11fa76b..5111ce2 100644 --- a/release/app/package.json +++ b/release/app/package.json @@ -1,6 +1,6 @@ { "name": "anicour", - "version": "1.5.1", + "version": "1.5.2", "description": "Anime, Manga, and Light Novel Tracker Desktop Application for Windows. A fast and interactive way for AniList users to track and manage their anime/manga lists. ", "license": "GPL-3.0", "author": { diff --git a/src/main/main.ts b/src/main/main.ts index 3b94c04..e57a4cb 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -89,6 +89,10 @@ const schema: any = { type: 'string', default: 'Early', }, + nextAiringEpisode: { + type: 'string', + default: 'Show', + }, }; const store = new Store({ schema }); diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 0ce9ea7..c5f930d 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -90,6 +90,7 @@ import { AdvancedThemeSongsContextProvider } from './context/advanced/AdvancedTh import { appResetDialogOpenAtom, myStore, + nextAiringEpisodeAtom, notificationAltOpenAtom, notificationMediaNamesAtom, notificationOpenAtom, @@ -265,6 +266,10 @@ const Hello = () => { const [seasonChange, setSeasonChange] = useAtom(seasonChangeAtom); const [openResetDialog, setOpenResetDialog] = useAtom(appResetDialogOpenAtom); + const [nextAiringEpisode, setNextAiringEpisode] = useAtom( + nextAiringEpisodeAtom, + ); + const handleClose = (event?: SyntheticEvent | Event, reason?: string) => { if (reason === 'clickaway') { return; @@ -362,6 +367,10 @@ const Hello = () => { setDefaultAddStatus('CURRENT'); setSeasonChange('Early'); break; + case 'nextAiringEpisode': + console.log('nextAiringEpisode'); + setNextAiringEpisode(arg[1]); + break; default: console.log('test'); } @@ -376,6 +385,7 @@ const Hello = () => { setAniListToken, setAniListUsername, setDefaultAddStatus, + setNextAiringEpisode, setOpenResetDialog, setSeasonChange, ]); diff --git a/src/renderer/components/app/main/MediaCard.tsx b/src/renderer/components/app/main/MediaCard.tsx index dd9a4c3..21d4cdb 100644 --- a/src/renderer/components/app/main/MediaCard.tsx +++ b/src/renderer/components/app/main/MediaCard.tsx @@ -39,6 +39,7 @@ import { import { useMainMediaList } from 'renderer/functions/MainMediaListFunctions'; import { useAtom } from 'jotai'; import { + nextAiringEpisodeAtom, notificationMediaNamesAtom, notificationOpenAtom, notificationTypeAtom, @@ -47,6 +48,7 @@ import { useAdvancedMedia } from 'renderer/context/advanced/AdvancedMediaContext import { useCategory } from 'renderer/context/CategoryContext'; import { Button, Tooltip } from '@mui/joy'; import { useSidebarButton } from 'renderer/context/SidebarContext'; +import { getTime, getTimeFormat } from 'renderer/functions/SeasonsFunctions'; import ContextMenu from '../etc/ContextMenu'; import ProgressStepper from '../etc/ProgressStepper'; import ProgressVolumesStepper from '../etc/ProgressVolumesStepper'; @@ -79,11 +81,31 @@ export default function MediaCard({ props }: any) { const { isLoading, isError, error, data, refetch, dateUpdatedAt }: any = useMainMediaList(myUserName.AniListUsername, myToken.AniListToken); + const [time, setTime] = useState( + props.nextAiringEpisode !== null + ? props.nextAiringEpisode.timeUntilAiring + : null, + ); + + const [nextAiringEpisode, setNextAiringEpisode] = useAtom( + nextAiringEpisodeAtom, + ); + const [myAdvancedInput, inputDispatch] = useReducer( AdvancedInputContextReducer, props.mediaListEntry, ); + useEffect(() => { + const timer = setTimeout(() => { + if (time !== null && time >= 0 && props.nextAiringEpisode !== null) { + // eslint-disable-next-line no-plusplus + setTime(getTime(props.nextAiringEpisode.airingAt)); + } + }, 1000); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [time]); + useEffect(() => { // every time the mediaListEntry is updated through advanced window, myAdvancedInput is also updated inputDispatch({ @@ -285,6 +307,25 @@ export default function MediaCard({ props }: any) { maxHeight: '200px', }} /> + {props.nextAiringEpisode !== null && nextAiringEpisode === 'Show' ? ( + + + {`EP${props.nextAiringEpisode.episode}: ${getTimeFormat( + props.nextAiringEpisode.airingAt, + )}`} + + + ) : null} {props.mediaListEntry.notes !== null ? ( { + const timer = setTimeout(() => { + if (time !== null && time >= 0 && props.nextAiringEpisode !== null) { + // eslint-disable-next-line no-plusplus + setTime(getTime(props.nextAiringEpisode.airingAt)); + } + }, 1000); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [time]); + const [contextMenu, setContextMenu] = useState<{ mouseX: number; mouseY: number; @@ -141,13 +164,23 @@ export default function MediaCardCompact({ props }: any) { overflow: 'hidden', textOverflow: 'ellipsis', display: '-webkit-box', - WebkitLineClamp: '2', + WebkitLineClamp: + props.nextAiringEpisode !== null && nextAiringEpisode === 'Show' + ? '1' + : '2', WebkitBoxOrient: 'vertical', }} color={getStatusColor(props.status)} > {getTitle(titlePreference.title, props)} + {props.nextAiringEpisode !== null && nextAiringEpisode === 'Show' ? ( + + {`EP${props.nextAiringEpisode.episode}: ${getTimeFormat( + props.nextAiringEpisode.airingAt, + )}`} + + ) : null} + {[ + 'Next Airing Time', + 'Status', + 'Title', + 'Episode Progress', + 'Score', + 'Type', + 'Season', + ].map((text, index) => ( + + { + switch (sortValue.sort) { + case index + 1: + sortValue.setSort(index + 1.5); + sortLast.dispatch({ + type: 'updateAnimeSort', + payload: index + 1.5, + }); + break; + case index + 1.5: + sortValue.setSort(0); + sortLast.dispatch({ + type: 'updateAnimeSort', + payload: 0, + }); + break; + default: + sortValue.setSort(index + 1); + sortLast.dispatch({ + type: 'updateAnimeSort', + payload: index + 1, + }); + } + }} + > + + + + + + + ))} + + ); + } if (sidebar.sidebar === 0) { // anime list return ( diff --git a/src/renderer/components/settings/SettingsMain.tsx b/src/renderer/components/settings/SettingsMain.tsx index dd03b96..3fc6ede 100644 --- a/src/renderer/components/settings/SettingsMain.tsx +++ b/src/renderer/components/settings/SettingsMain.tsx @@ -24,6 +24,7 @@ import DefaultStatus from './app/DefaultStatus'; import SelectDefaultView from './app/SelectDefaultView'; import AboutSection from './about/AboutSection'; import SelectSeasonChange from './app/SelectSeasonChange'; +import NextAiringEpisodeMainList from './app/NextAiringEpisodeMainList'; function SettingsMainTab({ view }: any) { if (view === 0) { @@ -59,6 +60,7 @@ function SettingsMainTab({ view }: any) { + ); } diff --git a/src/renderer/components/settings/app/NextAiringEpisodeMainList.tsx b/src/renderer/components/settings/app/NextAiringEpisodeMainList.tsx new file mode 100644 index 0000000..8c82c7d --- /dev/null +++ b/src/renderer/components/settings/app/NextAiringEpisodeMainList.tsx @@ -0,0 +1,39 @@ +import { Box, FormControl, InputLabel, NativeSelect } from '@mui/material'; +import { useAtom } from 'jotai'; +import React from 'react'; +import { nextAiringEpisodeAtom } from 'renderer/store'; + +function NextAiringEpisodeMainList() { + const [nextAiringEpisode, setNextAiringEpisode] = useAtom( + nextAiringEpisodeAtom, + ); + + const handleChange = (event: any) => { + setNextAiringEpisode(event.target.value as string); + window.electron.store.set('nextAiringEpisode', event.target.value); + window.electron.ipcRenderer.sendMessage('updateMainFromSettings', [ + 'nextAiringEpisode', + event.target.value, + ]); + }; + + return ( + + + + Show next airing episode on the user's anime list + + + + + + + + ); +} + +export default NextAiringEpisodeMainList; diff --git a/src/renderer/functions/sort/sortMainFunctions.ts b/src/renderer/functions/sort/sortMainFunctions.ts index 0ee49d5..5d26dc1 100644 --- a/src/renderer/functions/sort/sortMainFunctions.ts +++ b/src/renderer/functions/sort/sortMainFunctions.ts @@ -1,4 +1,6 @@ import { + sortByAiringDateAscending, + sortByAiringDateDescending, sortByProgressAscending, sortByProgressDescending, sortByReleaseDateAscending, @@ -21,40 +23,82 @@ export function sortMyListAnime( sort: number, titlePreference: string, data: any, + nextAiringEpisode: string, ) { - switch (sort) { - case 0: - return data; - case 1: - return [...data].sort(sortByStatusAscending); - case 1.5: - return [...data].sort(sortByStatusDescending); - case 2: - return [...data].sort((a, b) => { - return sortByTitleDescending(a, b, titlePreference); - }); - case 2.5: - return [...data].sort((a, b) => { - return sortByTitleAscending(a, b, titlePreference); - }); - case 3: - return [...data].sort(sortByProgressDescending); - case 3.5: - return [...data].sort(sortByProgressAscending); - case 4: - return [...data].sort(sortByScoreDescending); - case 4.5: - return [...data].sort(sortByScoreAscending); - case 5: - return [...data].sort(sortByTypeDescending); - case 5.5: - return [...data].sort(sortByTypeAscending); - case 6: - return [...data].sort(sortBySeasonDescending); - case 6.5: - return [...data].sort(sortBySeasonAscending); - default: - return data; + if (nextAiringEpisode === 'Show') { + switch (sort) { + case 0: + return data; + case 1: + return [...data].sort(sortByAiringDateAscending); + case 1.5: + return [...data].sort(sortByAiringDateDescending); + case 2: + return [...data].sort(sortByStatusAscending); + case 2.5: + return [...data].sort(sortByStatusDescending); + case 3: + return [...data].sort((a, b) => { + return sortByTitleDescending(a, b, titlePreference); + }); + case 3.5: + return [...data].sort((a, b) => { + return sortByTitleAscending(a, b, titlePreference); + }); + case 4: + return [...data].sort(sortByProgressDescending); + case 4.5: + return [...data].sort(sortByProgressAscending); + case 5: + return [...data].sort(sortByScoreDescending); + case 5.5: + return [...data].sort(sortByScoreAscending); + case 6: + return [...data].sort(sortByTypeDescending); + case 6.5: + return [...data].sort(sortByTypeAscending); + case 7: + return [...data].sort(sortBySeasonDescending); + case 7.5: + return [...data].sort(sortBySeasonAscending); + default: + return data; + } + } else { + switch (sort) { + case 0: + return data; + case 1: + return [...data].sort(sortByStatusAscending); + case 1.5: + return [...data].sort(sortByStatusDescending); + case 2: + return [...data].sort((a, b) => { + return sortByTitleDescending(a, b, titlePreference); + }); + case 2.5: + return [...data].sort((a, b) => { + return sortByTitleAscending(a, b, titlePreference); + }); + case 3: + return [...data].sort(sortByProgressDescending); + case 3.5: + return [...data].sort(sortByProgressAscending); + case 4: + return [...data].sort(sortByScoreDescending); + case 4.5: + return [...data].sort(sortByScoreAscending); + case 5: + return [...data].sort(sortByTypeDescending); + case 5.5: + return [...data].sort(sortByTypeAscending); + case 6: + return [...data].sort(sortBySeasonDescending); + case 6.5: + return [...data].sort(sortBySeasonAscending); + default: + return data; + } } } diff --git a/src/renderer/functions/view/DataTableFunctions.tsx b/src/renderer/functions/view/DataTableFunctions.tsx index 8e5581d..26991ce 100644 --- a/src/renderer/functions/view/DataTableFunctions.tsx +++ b/src/renderer/functions/view/DataTableFunctions.tsx @@ -1,6 +1,6 @@ import { useAniListUsername } from 'renderer/context/services/AniListUsernameContext'; import { useAniListToken } from 'renderer/context/services/AniListTokenContext'; -import { filterTypeAtom } from 'renderer/store'; +import { filterTypeAtom, nextAiringEpisodeAtom } from 'renderer/store'; import { useAtom } from 'jotai'; import { useMainMediaList } from '../MainMediaListFunctions'; import { getFilteredTabData, isFilteredTermOnList } from './FilterFunctions'; @@ -33,12 +33,16 @@ export const MainTableView = ( title: any, ) => { const [filterType, setFilterType] = useAtom(filterTypeAtom); + const [nextAiringEpisode, setNextAiringEpisode] = useAtom( + nextAiringEpisodeAtom, + ); if (filter !== '' && sidebar === 0) { return sortMyListAnime( sort, title, getFilteredTabData(category, sidebar, data), + nextAiringEpisode, ).filter((media: any) => { return isFilteredTermOnList(media, filter, filterType); }); @@ -62,22 +66,52 @@ export const MainTableView = ( }); } if (sidebar === 0 && category === 0) { - return sortMyListAnime(sort, title, data?.anime.animeWatching); + return sortMyListAnime( + sort, + title, + data?.anime.animeWatching, + nextAiringEpisode, + ); } if (sidebar === 0 && category === 1) { - return sortMyListAnime(sort, title, data?.anime.animeCompleted); + return sortMyListAnime( + sort, + title, + data?.anime.animeCompleted, + nextAiringEpisode, + ); } if (sidebar === 0 && category === 2) { - return sortMyListAnime(sort, title, data?.anime.animeOnHold); + return sortMyListAnime( + sort, + title, + data?.anime.animeOnHold, + nextAiringEpisode, + ); } if (sidebar === 0 && category === 3) { - return sortMyListAnime(sort, title, data?.anime.animeDropped); + return sortMyListAnime( + sort, + title, + data?.anime.animeDropped, + nextAiringEpisode, + ); } if (sidebar === 0 && category === 4) { - return sortMyListAnime(sort, title, data?.anime.animePlanToWatch); + return sortMyListAnime( + sort, + title, + data?.anime.animePlanToWatch, + nextAiringEpisode, + ); } if (sidebar === 0 && category === 5) { - return sortMyListAnime(sort, title, data?.anime.animeAll); + return sortMyListAnime( + sort, + title, + data?.anime.animeAll, + nextAiringEpisode, + ); } if (sidebar === 1 && category === 0) { return sortMyListManga(sort, title, data?.manga.mangaReading); diff --git a/src/renderer/functions/view/MainViewFunctions.tsx b/src/renderer/functions/view/MainViewFunctions.tsx index d1a178b..5854426 100644 --- a/src/renderer/functions/view/MainViewFunctions.tsx +++ b/src/renderer/functions/view/MainViewFunctions.tsx @@ -6,7 +6,7 @@ import { memo, useEffect, useMemo, useState } from 'react'; import { useAniListToken } from 'renderer/context/services/AniListTokenContext'; import { useAniListUsername } from 'renderer/context/services/AniListUsernameContext'; import { useSort } from 'renderer/context/SortContext'; -import { filterTypeAtom } from 'renderer/store'; +import { filterTypeAtom, nextAiringEpisodeAtom } from 'renderer/store'; import { useAtom } from 'jotai'; import { getFilteredTabData, isFilteredTermOnList } from './FilterFunctions'; import { sortMyListAnime, sortMyListManga } from '../sort/sortMainFunctions'; @@ -77,6 +77,9 @@ export const MainCategoryView = memo( const myToken: any = useAniListToken(); const myUsername: any = useAniListUsername(); const [filterType, setFilterType] = useAtom(filterTypeAtom); + const [nextAiringEpisode, setNextAiringEpisode] = useAtom( + nextAiringEpisodeAtom, + ); const { isLoading, isError, error, data, refetch }: any = useMainMediaList( myUsername.AniListUsername, @@ -88,6 +91,7 @@ export const MainCategoryView = memo( sort, title, getFilteredTabData(category, sidebar, data), + nextAiringEpisode, ) .filter((media: any) => { return isFilteredTermOnList(media, filter, filterType); @@ -150,94 +154,112 @@ export const MainCategoryView = memo( }); } if (sidebar === 0 && category === 0) { - return sortMyListAnime(sort, title, data?.anime.animeWatching).map( - (media: any, index: any) => { - return ( - - ); - }, - ); + return sortMyListAnime( + sort, + title, + data?.anime.animeWatching, + nextAiringEpisode, + ).map((media: any, index: any) => { + return ( + + ); + }); } if (sidebar === 0 && category === 1) { - return sortMyListAnime(sort, title, data?.anime.animeCompleted).map( - (media: any, index: any) => { - return ( - - ); - }, - ); + return sortMyListAnime( + sort, + title, + data?.anime.animeCompleted, + nextAiringEpisode, + ).map((media: any, index: any) => { + return ( + + ); + }); } if (sidebar === 0 && category === 2) { - return sortMyListAnime(sort, title, data?.anime.animeOnHold).map( - (media: any, index: any) => { - return ( - - ); - }, - ); + return sortMyListAnime( + sort, + title, + data?.anime.animeOnHold, + nextAiringEpisode, + ).map((media: any, index: any) => { + return ( + + ); + }); } if (sidebar === 0 && category === 3) { - return sortMyListAnime(sort, title, data?.anime.animeDropped).map( - (media: any, index: any) => { - return ( - - ); - }, - ); + return sortMyListAnime( + sort, + title, + data?.anime.animeDropped, + nextAiringEpisode, + ).map((media: any, index: any) => { + return ( + + ); + }); } if (sidebar === 0 && category === 4) { - return sortMyListAnime(sort, title, data?.anime.animePlanToWatch).map( - (media: any, index: any) => { - return ( - - ); - }, - ); + return sortMyListAnime( + sort, + title, + data?.anime.animePlanToWatch, + nextAiringEpisode, + ).map((media: any, index: any) => { + return ( + + ); + }); } if (sidebar === 0 && category === 5) { - return sortMyListAnime(sort, title, data?.anime.animeAll).map( - (media: any, index: any) => { - return ( - - ); - }, - ); + return sortMyListAnime( + sort, + title, + data?.anime.animeAll, + nextAiringEpisode, + ).map((media: any, index: any) => { + return ( + + ); + }); } if (sidebar === 1 && category === 0) { return sortMyListManga(sort, title, data?.manga.mangaReading).map( diff --git a/src/renderer/store.ts b/src/renderer/store.ts index 24f0fc9..6329f12 100644 --- a/src/renderer/store.ts +++ b/src/renderer/store.ts @@ -165,3 +165,11 @@ myStore.set(appResetDialogOpenAtom, false); export const notificationOpenSettingsAtom = atom(false); export const notificationOpenAltSettingsAtom = atom(false); + +export const nextAiringEpisodeAtom = atom( + window.electron.store.get('nextAiringEpisode'), +); +myStore.set( + nextAiringEpisodeAtom, + window.electron.store.get('nextAiringEpisode'), +);