Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Convert remaining Song/ pages to react #1406

Merged
merged 6 commits into from
May 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions VocaDbWeb/Controllers/Api/SongApiController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,29 @@ public EntryWithArchivedVersionsForApiContract<SongForApiContract> GetSongWithAr
public ArchivedSongVersionDetailsForApiContract GetVersionDetails(int id, int comparedVersionId = 0) =>
_queries.GetVersionDetailsForApi(id, comparedVersionId);


/// <summary>
/// </summary>
[HttpGet("songlists")]
[ApiExplorerSettings(IgnoreApi = true)]
public ActionResult<SongListBaseContract[]> GetSongListsForUser(int ignoreSongId = 0)
{
if (ignoreSongId == 0)
return NotFound("No ID specified");

return _service.GetSongListsForCurrentUser(ignoreSongId);
}

[HttpGet("{id:int}/songlists")]
[ApiExplorerSettings(IgnoreApi = true)]
public ActionResult<SongListContract[]> GetSongListsForSong(int id = 0)
{
if (id == 0)
return NotFound("No ID specified");

return _queries.GetPublicSongListsForSong(id);
}

[HttpPost("")]
[Authorize]
[EnableCors(AuthenticationConstants.AuthenticatedCorsApiPolicy)]
Expand Down
38 changes: 8 additions & 30 deletions VocaDbWeb/Controllers/SongController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,24 +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)
return NoId();

var result = Service.GetSongListsForCurrentUser(ignoreSongId);
return LowercaseJson(result);
}

//
// GET: /Song/Details/5

Expand Down Expand Up @@ -185,21 +167,21 @@ 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");
}

//
// GET: /Song/Edit/5
[Authorize]
public ActionResult Edit(int id, int? albumId = null)
{
return File("index.html", "text/html") ;
return File("index.html", "text/html");
}

[HttpPost]
Expand Down Expand Up @@ -282,7 +264,7 @@ public ActionResult ManageTagUsages(int id)

public ActionResult Merge()
{
return File("index.html", "text/html") ;
return File("index.html", "text/html");
}

/// <summary>
Expand Down Expand Up @@ -357,16 +339,12 @@ 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)
{
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");
}

public ActionResult Restore(int id)
Expand Down Expand Up @@ -429,7 +407,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)
Expand All @@ -439,6 +417,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");
}
}
31 changes: 15 additions & 16 deletions VocaDbWeb/Scripts/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -45,17 +43,18 @@ const AppContainer = (): React.ReactElement => {
<AboutDisclaimer />
</div>
<React.Suspense fallback={null}>
{vdb.values.loggedUser?.stylesheet && (
<>
{vdb.values.loggedUser?.stylesheet
.toLowerCase()
.startsWith('darkangel') && <DarkAngel />}

{vdb.values.loggedUser?.stylesheet
.toLowerCase()
.startsWith('tetodb') && <TetoDB />}
</>
)}
{vdb.values.loggedUser?.stylesheet !== undefined && (
{vdb.values.loggedUser?.stylesheet && (
<>
{vdb.values.loggedUser?.stylesheet
.toLowerCase()
.startsWith('darkangel') && <DarkAngel />}
{vdb.values.loggedUser?.stylesheet
.toLowerCase()
.startsWith('tetodb') && <TetoDB />}
</>
)}
)}
</React.Suspense>
</Container>
);
Expand Down
7 changes: 7 additions & 0 deletions VocaDbWeb/Scripts/DataContracts/Song/RelatedSongs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { SongApiContract } from './SongApiContract';

export interface RelatedSongs {
artistMatches: SongApiContract[];
likeMatches: SongApiContract[];
tagMatches: SongApiContract[];
}
41 changes: 33 additions & 8 deletions VocaDbWeb/Scripts/Pages/Song/Partials/SongInListsDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
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;
}

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 (
<JQueryUIDialog
Expand All @@ -24,13 +35,27 @@ const SongInListsDialog = observer(
})
}
>
{songInListsStore.contentHtml && (
// TODO: Replace this with React
<div
dangerouslySetInnerHTML={{
__html: songInListsStore.contentHtml,
}}
/>
{Object.keys(byCategory).map((category, index) => (
<React.Fragment key={index}>
<h4>{t(`Resources:SongListFeaturedCategoryNames.${category}`)}</h4>
<Link to={`/L/${byCategory[category].first()?.id}`}>
{byCategory[category].first()?.name}
</Link>
</React.Fragment>
))}
{customLists.length > 0 && (
<>
<h4>{t(`ViewRes.Song:Details.CustomLists`)}</h4>
{customLists.map((list, index) => (
<div key={index}>
<Link to={`/L/${list.id}`}>{list.name}</Link>
{' ('}
{/* eslint-disable-next-line react/jsx-pascal-case */}
<UserIconLink_UserForApiContract user={list.author} />
{' )'}
</div>
))}
</>
)}
</JQueryUIDialog>
);
Expand Down
44 changes: 32 additions & 12 deletions VocaDbWeb/Scripts/Pages/Song/SongRelated.tsx
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -14,27 +18,43 @@ const SongRelated = ({
model,
songDetailsStore,
}: SongRelatedProps): React.ReactElement => {
const [contentHtml, setContentHtml] = React.useState<string | undefined>();
const { t } = useTranslation(['ViewRes.Song']);

const [relatedSongs, setRelatedSongs] = React.useState<
RelatedSongs | undefined
>(undefined);

React.useEffect(() => {
httpClient
.get<string>(urlMapper.mapRelative(`/Song/Related/${model.id}`))
.then((contentHtml) => setContentHtml(contentHtml));
}, [model]);
songDetailsStore.getRelated().then(setRelatedSongs);
}, [model, songDetailsStore]);

return (
<SongDetailsTabs
model={model}
songDetailsStore={songDetailsStore}
tab="related"
>
{contentHtml && (
// TODO: Replace this with React
<div
dangerouslySetInnerHTML={{
__html: contentHtml,
}}
/>
{relatedSongs !== undefined && (
<>
{relatedSongs.artistMatches.length > 0 && (
<>
<h3>{t('ViewRes.Song:Details.MatchingArtists')}</h3>
<SongGrid songs={relatedSongs.artistMatches} columns={2} />
</>
)}
{relatedSongs.likeMatches.length > 0 && (
<>
<h3>{t('ViewRes.Song:Details.MatchingLikes')}</h3>
<SongGrid songs={relatedSongs.likeMatches} columns={2} />
</>
)}
{relatedSongs.tagMatches.length > 0 && (
<>
<h3>{t('ViewRes.Song:Details.MatchingTags')}</h3>
<SongGrid songs={relatedSongs.tagMatches} columns={2} />
</>
)}
</>
)}
</SongDetailsTabs>
);
Expand Down
39 changes: 33 additions & 6 deletions VocaDbWeb/Scripts/Repositories/SongRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ 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';
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';
Expand Down Expand Up @@ -133,18 +135,24 @@ export class SongRepository
songId,
}: {
songId: number;
}): Promise<string> => {
return this.get<string>('/SongListsForSong', { songId: songId });
}): Promise<SongListContract[]> => {
return this.httpClient.get<SongListContract[]>(
`/api/songs/${songId}/songlists`,
{},
);
};

this.songListsForUser = ({
ignoreSongId,
}: {
ignoreSongId: number;
}): Promise<SongListBaseContract[]> => {
return this.post<SongListBaseContract[]>('/SongListsForUser', {
ignoreSongId: ignoreSongId,
});
return this.httpClient.get<SongListBaseContract[]>(
this.urlMapper.mapRelative('/api/songs/songlists'),
{
ignoreSongId,
},
);
};
}

Expand Down Expand Up @@ -513,7 +521,11 @@ export class SongRepository

//songListsForSong: (songId: number, callback: (result: SongListContract[]) => void) => void;

songListsForSong: ({ songId }: { songId: number }) => Promise<string>;
songListsForSong: ({
songId,
}: {
songId: number;
}) => Promise<SongListContract[]>;

songListsForUser: ({
ignoreSongId,
Expand Down Expand Up @@ -719,6 +731,21 @@ export class SongRepository
{ headers: { requestVerificationToken: requestToken } },
);
};

getRelated = ({
songId,
lang,
}: {
songId: number;
lang: ContentLanguagePreference;
}): Promise<RelatedSongs> => {
return this.httpClient.get<RelatedSongs>(
this.urlMapper.mapRelative(`/api/songs/${songId}/related`),
{
lang,
},
);
};
}

export interface PVEmbedParams {
Expand Down
Loading