From c7ace9e66856620dc369ba906585f0432220c98e Mon Sep 17 00:00:00 2001 From: FinnRG Date: Sat, 6 May 2023 12:59:57 +0200 Subject: [PATCH 1/5] refactor: Move SongListsForUser to API Controller --- .../Controllers/Api/SongApiController.cs | 13 +++++++++++ VocaDbWeb/Controllers/SongController.cs | 23 ++++++------------- .../Scripts/Repositories/SongRepository.ts | 9 +++++--- 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/VocaDbWeb/Controllers/Api/SongApiController.cs b/VocaDbWeb/Controllers/Api/SongApiController.cs index 3dee2362c4..8206446925 100644 --- a/VocaDbWeb/Controllers/Api/SongApiController.cs +++ b/VocaDbWeb/Controllers/Api/SongApiController.cs @@ -564,6 +564,19 @@ public EntryWithArchivedVersionsForApiContract GetSongWithAr public ArchivedSongVersionDetailsForApiContract GetVersionDetails(int id, int comparedVersionId = 0) => _queries.GetVersionDetailsForApi(id, comparedVersionId); + + /// + /// + [HttpGet("songlists")] + [ApiExplorerSettings(IgnoreApi = true)] + public ActionResult GetSongListsForUser(int ignoreSongId = 0) + { + if (ignoreSongId == 0) + return NotFound("No ID specified"); + + return _service.GetSongListsForCurrentUser(ignoreSongId); + } + [HttpPost("")] [Authorize] [EnableCors(AuthenticationConstants.AuthenticatedCorsApiPolicy)] diff --git a/VocaDbWeb/Controllers/SongController.cs b/VocaDbWeb/Controllers/SongController.cs index 6fa1cc66ac..d6773fedb9 100644 --- a/VocaDbWeb/Controllers/SongController.cs +++ b/VocaDbWeb/Controllers/SongController.cs @@ -115,15 +115,6 @@ public ActionResult Index(IndexRouteParams indexParams) }); } - public ActionResult SongListsForSong(int songId = InvalidId) - { - if (songId == InvalidId) - return NoId(); - - var lists = _queries.GetPublicSongListsForSong(songId); - return PartialView("Partials/_SongInListsDialogContent", lists); - } - public ActionResult SongListsForUser(int ignoreSongId = InvalidId) { if (ignoreSongId == InvalidId) @@ -185,13 +176,13 @@ public ActionResult Details(int id = InvalidId, int albumId = 0) prop.Robots = model.Deleted ? PagePropertiesData.Robots_Noindex_Follow : string.Empty; - return File("index.html", "text/html") ; + return File("index.html", "text/html"); } [Authorize] public ActionResult Create(string pvUrl) { - return File("index.html", "text/html") ; + return File("index.html", "text/html"); } // @@ -199,7 +190,7 @@ public ActionResult Create(string pvUrl) [Authorize] public ActionResult Edit(int id, int? albumId = null) { - return File("index.html", "text/html") ; + return File("index.html", "text/html"); } [HttpPost] @@ -282,7 +273,7 @@ public ActionResult ManageTagUsages(int id) public ActionResult Merge() { - return File("index.html", "text/html") ; + return File("index.html", "text/html"); } /// @@ -357,7 +348,7 @@ public ActionResult Rankings() { PageProperties.Title = _brandableStringsManager.Song.RankingsTitle; - return File("index.html", "text/html") ; + return File("index.html", "text/html"); } public ActionResult Related(int id = InvalidId) @@ -438,7 +429,7 @@ public ActionResult Versions(int id = InvalidId) PageProperties.Title = ViewRes.EntryDetailsStrings.Revisions + " - " + contract.Name; PageProperties.Robots = PagePropertiesData.Robots_Noindex_Nofollow; - return File("index.html", "text/html") ; + return File("index.html", "text/html"); } public ActionResult ViewVersion(int id, int? ComparedVersionId) @@ -448,6 +439,6 @@ public ActionResult ViewVersion(int id, int? ComparedVersionId) PageProperties.Title = "Revision " + contract.ArchivedVersion.Version + " for " + contract.Name; PageProperties.Robots = PagePropertiesData.Robots_Noindex_Nofollow; - return File("index.html", "text/html") ; + return File("index.html", "text/html"); } } diff --git a/VocaDbWeb/Scripts/Repositories/SongRepository.ts b/VocaDbWeb/Scripts/Repositories/SongRepository.ts index 4a171b05fe..e79b3d03d1 100644 --- a/VocaDbWeb/Scripts/Repositories/SongRepository.ts +++ b/VocaDbWeb/Scripts/Repositories/SongRepository.ts @@ -141,9 +141,12 @@ export class SongRepository }: { ignoreSongId: number; }): Promise => { - return this.post('/SongListsForUser', { - ignoreSongId: ignoreSongId, - }); + return this.httpClient.get( + this.urlMapper.mapRelative('/api/songs/songlists'), + { + ignoreSongId, + }, + ); }; } From 234a2111dc7d875022a2c7bffc070d5542ae1698 Mon Sep 17 00:00:00 2001 From: FinnRG Date: Sun, 7 May 2023 16:59:54 +0200 Subject: [PATCH 2/5] refactor(react): Convert RelatedSongs to react --- VocaDbWeb/Controllers/SongController.cs | 6 +-- .../DataContracts/Song/RelatedSongs.ts | 7 +++ VocaDbWeb/Scripts/Pages/Song/SongRelated.tsx | 44 ++++++++++++++----- .../Scripts/Repositories/SongRepository.ts | 16 +++++++ .../Scripts/Stores/Song/SongDetailsStore.ts | 8 ++++ 5 files changed, 64 insertions(+), 17 deletions(-) create mode 100644 VocaDbWeb/Scripts/DataContracts/Song/RelatedSongs.ts diff --git a/VocaDbWeb/Controllers/SongController.cs b/VocaDbWeb/Controllers/SongController.cs index d6773fedb9..b931b65c00 100644 --- a/VocaDbWeb/Controllers/SongController.cs +++ b/VocaDbWeb/Controllers/SongController.cs @@ -353,11 +353,7 @@ public ActionResult Rankings() public ActionResult Related(int id = InvalidId) { - if (id == InvalidId) - return NoId(); - - var related = _queries.GetRelatedSongs(id, SongOptionalFields.AdditionalNames | SongOptionalFields.ThumbUrl, null); - return PartialView("RelatedSongs", related); + return File("index.html", "text/html"); } [Authorize] diff --git a/VocaDbWeb/Scripts/DataContracts/Song/RelatedSongs.ts b/VocaDbWeb/Scripts/DataContracts/Song/RelatedSongs.ts new file mode 100644 index 0000000000..f5cff7a26a --- /dev/null +++ b/VocaDbWeb/Scripts/DataContracts/Song/RelatedSongs.ts @@ -0,0 +1,7 @@ +import { SongApiContract } from './SongApiContract'; + +export interface RelatedSongs { + artistMatches: SongApiContract[]; + likeMatches: SongApiContract[]; + tagMatches: SongApiContract[]; +} diff --git a/VocaDbWeb/Scripts/Pages/Song/SongRelated.tsx b/VocaDbWeb/Scripts/Pages/Song/SongRelated.tsx index 67762bc0ea..e25c2eeee2 100644 --- a/VocaDbWeb/Scripts/Pages/Song/SongRelated.tsx +++ b/VocaDbWeb/Scripts/Pages/Song/SongRelated.tsx @@ -1,9 +1,13 @@ +import { SongGrid } from '@/Components/Shared/Partials/Song/SongGrid'; +import { RelatedSongs } from '@/DataContracts/Song/RelatedSongs'; import { SongDetailsForApi } from '@/DataContracts/Song/SongDetailsForApi'; import { SongDetailsTabs } from '@/Pages/Song/SongDetailsRoutes'; import { httpClient } from '@/Shared/HttpClient'; import { urlMapper } from '@/Shared/UrlMapper'; import { SongDetailsStore } from '@/Stores/Song/SongDetailsStore'; +import { useVdb } from '@/VdbContext'; import React from 'react'; +import { useTranslation } from 'react-i18next'; interface SongRelatedProps { model: SongDetailsForApi; @@ -14,13 +18,15 @@ const SongRelated = ({ model, songDetailsStore, }: SongRelatedProps): React.ReactElement => { - const [contentHtml, setContentHtml] = React.useState(); + const { t } = useTranslation(['ViewRes.Song']); + + const [relatedSongs, setRelatedSongs] = React.useState< + RelatedSongs | undefined + >(undefined); React.useEffect(() => { - httpClient - .get(urlMapper.mapRelative(`/Song/Related/${model.id}`)) - .then((contentHtml) => setContentHtml(contentHtml)); - }, [model]); + songDetailsStore.getRelated().then(setRelatedSongs); + }, [model, songDetailsStore]); return ( - {contentHtml && ( - // TODO: Replace this with React -
+ {relatedSongs !== undefined && ( + <> + {relatedSongs.artistMatches.length > 0 && ( + <> +

{t('ViewRes.Song:Details.MatchingArtists')}

+ + + )} + {relatedSongs.likeMatches.length > 0 && ( + <> +

{t('ViewRes.Song:Details.MatchingLikes')}

+ + + )} + {relatedSongs.tagMatches.length > 0 && ( + <> +

{t('ViewRes.Song:Details.MatchingTags')}

+ + + )} + )} ); diff --git a/VocaDbWeb/Scripts/Repositories/SongRepository.ts b/VocaDbWeb/Scripts/Repositories/SongRepository.ts index e79b3d03d1..a2fea845a3 100644 --- a/VocaDbWeb/Scripts/Repositories/SongRepository.ts +++ b/VocaDbWeb/Scripts/Repositories/SongRepository.ts @@ -7,6 +7,7 @@ import { PartialFindResultContract } from '@/DataContracts/PartialFindResultCont import { ArchivedSongVersionDetailsContract } from '@/DataContracts/Song/ArchivedSongVersionDetailsContract'; import { CreateSongContract } from '@/DataContracts/Song/CreateSongContract'; import { LyricsForSongContract } from '@/DataContracts/Song/LyricsForSongContract'; +import { RelatedSongs } from '@/DataContracts/Song/RelatedSongs'; import { SongApiContract } from '@/DataContracts/Song/SongApiContract'; import { SongContract } from '@/DataContracts/Song/SongContract'; import { SongDetailsContract } from '@/DataContracts/Song/SongDetailsContract'; @@ -711,6 +712,21 @@ export class SongRepository { headers: { requestVerificationToken: requestToken } }, ); }; + + getRelated = ({ + songId, + lang, + }: { + songId: number; + lang: ContentLanguagePreference; + }): Promise => { + return this.httpClient.get( + this.urlMapper.mapRelative(`/api/songs/${songId}/related`), + { + lang, + }, + ); + }; } export interface PVEmbedParams { diff --git a/VocaDbWeb/Scripts/Stores/Song/SongDetailsStore.ts b/VocaDbWeb/Scripts/Stores/Song/SongDetailsStore.ts index b7232c1375..a5dce14666 100644 --- a/VocaDbWeb/Scripts/Stores/Song/SongDetailsStore.ts +++ b/VocaDbWeb/Scripts/Stores/Song/SongDetailsStore.ts @@ -1,4 +1,5 @@ import { LyricsForSongContract } from '@/DataContracts/Song/LyricsForSongContract'; +import { RelatedSongs } from '@/DataContracts/Song/RelatedSongs'; import { SongApiContract } from '@/DataContracts/Song/SongApiContract'; import { SongDetailsAjax } from '@/DataContracts/Song/SongDetailsForApi'; import { SongListBaseContract } from '@/DataContracts/SongListBaseContract'; @@ -376,4 +377,11 @@ export class SongDetailsStore { @action showAllVersions = (): void => { this.allVersionsVisible = true; }; + + getRelated = (): Promise => { + return this.songRepo.getRelated({ + songId: this.id, + lang: this.values.languagePreference, + }); + }; } From 6b18a50c8ad8d70a0987fc2d058033afb79786ee Mon Sep 17 00:00:00 2001 From: FinnRG Date: Mon, 8 May 2023 19:10:51 +0200 Subject: [PATCH 3/5] fix: Add SongListsForSong back --- VocaDbWeb/Controllers/SongController.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/VocaDbWeb/Controllers/SongController.cs b/VocaDbWeb/Controllers/SongController.cs index b931b65c00..ee49b3d4f2 100644 --- a/VocaDbWeb/Controllers/SongController.cs +++ b/VocaDbWeb/Controllers/SongController.cs @@ -115,13 +115,13 @@ public ActionResult Index(IndexRouteParams indexParams) }); } - public ActionResult SongListsForUser(int ignoreSongId = InvalidId) + public ActionResult SongListsForSong(int songId = InvalidId) { - if (ignoreSongId == InvalidId) + if (songId == InvalidId) return NoId(); - var result = Service.GetSongListsForCurrentUser(ignoreSongId); - return LowercaseJson(result); + var lists = _queries.GetPublicSongListsForSong(songId); + return PartialView("Partials/_SongInListsDialogContent", lists); } // From efeea476721c778620b52ae3e6b7164984d3376b Mon Sep 17 00:00:00 2001 From: FinnRG Date: Sat, 13 May 2023 18:13:55 +0200 Subject: [PATCH 4/5] refactor: Convert SongListsForSong to React --- .../Controllers/Api/SongApiController.cs | 10 +++++ VocaDbWeb/Controllers/SongController.cs | 9 ---- .../Pages/Song/Partials/SongInListsDialog.tsx | 41 +++++++++++++++---- .../Scripts/Repositories/SongRepository.ts | 14 +++++-- .../Scripts/Stores/Song/SongDetailsStore.ts | 5 ++- .../Tests/TestSupport/FakeSongRepository.ts | 5 ++- 6 files changed, 60 insertions(+), 24 deletions(-) diff --git a/VocaDbWeb/Controllers/Api/SongApiController.cs b/VocaDbWeb/Controllers/Api/SongApiController.cs index 8206446925..1bf2e81bcc 100644 --- a/VocaDbWeb/Controllers/Api/SongApiController.cs +++ b/VocaDbWeb/Controllers/Api/SongApiController.cs @@ -577,6 +577,16 @@ public ActionResult GetSongListsForUser(int ignoreSongId return _service.GetSongListsForCurrentUser(ignoreSongId); } + [HttpGet("{id:int}/songlists")] + [ApiExplorerSettings(IgnoreApi = true)] + public ActionResult GetSongListsForSong(int id = 0) + { + if (id == 0) + return NotFound("No ID specified"); + + return _queries.GetPublicSongListsForSong(id); + } + [HttpPost("")] [Authorize] [EnableCors(AuthenticationConstants.AuthenticatedCorsApiPolicy)] diff --git a/VocaDbWeb/Controllers/SongController.cs b/VocaDbWeb/Controllers/SongController.cs index ee49b3d4f2..af88c65e1e 100644 --- a/VocaDbWeb/Controllers/SongController.cs +++ b/VocaDbWeb/Controllers/SongController.cs @@ -115,15 +115,6 @@ public ActionResult Index(IndexRouteParams indexParams) }); } - public ActionResult SongListsForSong(int songId = InvalidId) - { - if (songId == InvalidId) - return NoId(); - - var lists = _queries.GetPublicSongListsForSong(songId); - return PartialView("Partials/_SongInListsDialogContent", lists); - } - // // GET: /Song/Details/5 diff --git a/VocaDbWeb/Scripts/Pages/Song/Partials/SongInListsDialog.tsx b/VocaDbWeb/Scripts/Pages/Song/Partials/SongInListsDialog.tsx index 698e25a6d8..a99ebdcdd1 100644 --- a/VocaDbWeb/Scripts/Pages/Song/Partials/SongInListsDialog.tsx +++ b/VocaDbWeb/Scripts/Pages/Song/Partials/SongInListsDialog.tsx @@ -1,9 +1,13 @@ +import { EntryLink } from '@/Components/Shared/Partials/Shared/EntryLink'; +import { UserIconLink_UserForApiContract } from '@/Components/Shared/Partials/User/UserIconLink_UserForApiContract'; import JQueryUIDialog from '@/JQueryUI/JQueryUIDialog'; +import { SongListFeaturedCategory } from '@/Models/SongLists/SongListFeaturedCategory'; import { SongInListsStore } from '@/Stores/Song/SongDetailsStore'; import { runInAction } from 'mobx'; import { observer } from 'mobx-react-lite'; import React from 'react'; import { useTranslation } from 'react-i18next'; +import { Link } from 'react-router-dom'; interface SongInListsDialogProps { songInListsStore: SongInListsStore; @@ -11,7 +15,14 @@ interface SongInListsDialogProps { const SongInListsDialog = observer( ({ songInListsStore }: SongInListsDialogProps): React.ReactElement => { - const { t } = useTranslation(['ViewRes.Song']); + const { t } = useTranslation(['ViewRes.Song', 'Resources']); + + const byCategory = songInListsStore.listsForSong + .filter((l) => l.featuredCategory != SongListFeaturedCategory.Nothing) + .groupBy((l) => l.featuredCategory); + const customLists = songInListsStore.listsForSong.filter( + (l) => l.featuredCategory == SongListFeaturedCategory.Nothing, + ); return ( - {songInListsStore.contentHtml && ( - // TODO: Replace this with React -
+ {Object.keys(byCategory).map((category, index) => ( + +

{t(`Resources:SongListFeaturedCategoryNames.${category}`)}

+ + {byCategory[category].first()?.name} + +
+ ))} + {customLists.length > 0 && ( + <> +

{t(`ViewRes.Song:Details.CustomLists`)}

+ {customLists.map((list, index) => ( +
+ {list.name} + {' ('} + {/* eslint-disable-next-line react/jsx-pascal-case */} + + {' )'} +
+ ))} + )} ); diff --git a/VocaDbWeb/Scripts/Repositories/SongRepository.ts b/VocaDbWeb/Scripts/Repositories/SongRepository.ts index a2fea845a3..5fcafdc390 100644 --- a/VocaDbWeb/Scripts/Repositories/SongRepository.ts +++ b/VocaDbWeb/Scripts/Repositories/SongRepository.ts @@ -12,6 +12,7 @@ import { SongApiContract } from '@/DataContracts/Song/SongApiContract'; import { SongContract } from '@/DataContracts/Song/SongContract'; import { SongDetailsContract } from '@/DataContracts/Song/SongDetailsContract'; import { SongForEditContract } from '@/DataContracts/Song/SongForEditContract'; +import { SongListContract } from '@/DataContracts/Song/SongListContract'; import { SongWithPVPlayerAndVoteContract } from '@/DataContracts/Song/SongWithPVPlayerAndVoteContract'; import { SongListBaseContract } from '@/DataContracts/SongListBaseContract'; import { TagUsageForApiContract } from '@/DataContracts/Tag/TagUsageForApiContract'; @@ -133,8 +134,11 @@ export class SongRepository songId, }: { songId: number; - }): Promise => { - return this.get('/SongListsForSong', { songId: songId }); + }): Promise => { + return this.httpClient.get( + `/api/songs/${songId}/songlists`, + {}, + ); }; this.songListsForUser = ({ @@ -506,7 +510,11 @@ export class SongRepository //songListsForSong: (songId: number, callback: (result: SongListContract[]) => void) => void; - songListsForSong: ({ songId }: { songId: number }) => Promise; + songListsForSong: ({ + songId, + }: { + songId: number; + }) => Promise; songListsForUser: ({ ignoreSongId, diff --git a/VocaDbWeb/Scripts/Stores/Song/SongDetailsStore.ts b/VocaDbWeb/Scripts/Stores/Song/SongDetailsStore.ts index a5dce14666..d8be2e304d 100644 --- a/VocaDbWeb/Scripts/Stores/Song/SongDetailsStore.ts +++ b/VocaDbWeb/Scripts/Stores/Song/SongDetailsStore.ts @@ -2,6 +2,7 @@ import { LyricsForSongContract } from '@/DataContracts/Song/LyricsForSongContrac import { RelatedSongs } from '@/DataContracts/Song/RelatedSongs'; import { SongApiContract } from '@/DataContracts/Song/SongApiContract'; import { SongDetailsAjax } from '@/DataContracts/Song/SongDetailsForApi'; +import { SongListContract } from '@/DataContracts/Song/SongListContract'; import { SongListBaseContract } from '@/DataContracts/SongListBaseContract'; import { TagSelectionContract } from '@/DataContracts/Tag/TagSelectionContract'; import { RatedSongForUserForApiContract } from '@/DataContracts/User/RatedSongForUserForApiContract'; @@ -90,7 +91,7 @@ interface SongLinkWithUrl { } export class SongInListsStore { - @observable contentHtml?: string; + @observable listsForSong: SongListContract[] = []; @observable dialogVisible = false; constructor( @@ -103,7 +104,7 @@ export class SongInListsStore { show = (): void => { this.songRepo.songListsForSong({ songId: this.songId }).then((result) => runInAction(() => { - this.contentHtml = result; + this.listsForSong = result; this.dialogVisible = true; }), ); diff --git a/VocaDbWeb/Scripts/Tests/TestSupport/FakeSongRepository.ts b/VocaDbWeb/Scripts/Tests/TestSupport/FakeSongRepository.ts index cda0576acd..26d0926f06 100644 --- a/VocaDbWeb/Scripts/Tests/TestSupport/FakeSongRepository.ts +++ b/VocaDbWeb/Scripts/Tests/TestSupport/FakeSongRepository.ts @@ -1,5 +1,6 @@ import { NewSongCheckResultContract } from '@/DataContracts/NewSongCheckResultContract'; import { SongApiContract } from '@/DataContracts/Song/SongApiContract'; +import { SongListContract } from '@/DataContracts/Song/SongListContract'; import { SongListBaseContract } from '@/DataContracts/SongListBaseContract'; import { ContentLanguagePreference } from '@/Models/Globalization/ContentLanguagePreference'; import { @@ -84,8 +85,8 @@ export class FakeSongRepository extends SongRepository { songId, }: { songId: number; - }): Promise => { - return FakePromise.resolve('Miku!'); + }): Promise => { + return FakePromise.resolve([]); }; this.songListsForUser = ({ From fd251f201aa90736f4a223ea1c165a246aec63a5 Mon Sep 17 00:00:00 2001 From: FinnRG Date: Sat, 13 May 2023 18:49:18 +0200 Subject: [PATCH 5/5] fix: Fix loading if loggedUser.stylesheet is undefined --- VocaDbWeb/Scripts/App.tsx | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/VocaDbWeb/Scripts/App.tsx b/VocaDbWeb/Scripts/App.tsx index 56512c8e81..8f362693fc 100644 --- a/VocaDbWeb/Scripts/App.tsx +++ b/VocaDbWeb/Scripts/App.tsx @@ -11,19 +11,17 @@ import { LoginManagerProvider } from '@/LoginManagerContext'; import { MutedUsersProvider } from '@/MutedUsersContext'; import { VdbProvider, useVdb } from '@/VdbContext'; import '@/i18n'; +import '@/styles/css.less'; import { NostalgicDivaProvider } from '@vocadb/nostalgic-diva'; import { ScrollToTop } from '@vocadb/route-sphere'; import React from 'react'; import { Toaster } from 'react-hot-toast'; import { BrowserRouter } from 'react-router-dom'; -import "@/styles/css.less" - -const TetoDB = React.lazy(() => import("./styles/tetoDb")) -const DarkAngel = React.lazy(() => import("./styles/darkAngel")) +const TetoDB = React.lazy(() => import('./styles/tetoDb')); +const DarkAngel = React.lazy(() => import('./styles/darkAngel')); const AppContainer = (): React.ReactElement => { - const vdb = useVdb(); return ( @@ -44,15 +42,18 @@ const AppContainer = (): React.ReactElement => {
- - {vdb.values.loggedUser?.stylesheet.toLowerCase().startsWith("darkangel") && ( - - )} - - {vdb.values.loggedUser?.stylesheet.toLowerCase().startsWith("tetodb") && ( - - )} - + + {vdb.values.loggedUser?.stylesheet !== undefined && ( + <> + {vdb.values.loggedUser?.stylesheet + .toLowerCase() + .startsWith('darkangel') && } + {vdb.values.loggedUser?.stylesheet + .toLowerCase() + .startsWith('tetodb') && } + + )} + ); };