Skip to content

Commit

Permalink
Merge pull request #714 from Arthi-chaud/front/enhance-search-experience
Browse files Browse the repository at this point in the history
Search across multiple resources
  • Loading branch information
Arthi-chaud authored Aug 10, 2024
2 parents 8f9ac83 + a3cadd8 commit 0bf6066
Show file tree
Hide file tree
Showing 14 changed files with 631 additions and 285 deletions.
17 changes: 17 additions & 0 deletions front/src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ import Playlist, {
} from "../models/playlist";
import { isSSR } from "../utils/is-ssr";
import { ActiveTask, Task } from "../models/task";
import { SearchResult, SearchResultTransformer } from "../models/search";

const AuthenticationResponse = yup.object({
access_token: yup.string().required(),
Expand Down Expand Up @@ -835,6 +836,22 @@ export default class API {
};
}

/**
* Search artists, albums and songs, all at once
*/
static searchAll(query: string): Query<SearchResult[]> {
return {
key: ["search", query],
exec: () =>
API.fetch({
route: `/search/${query}`,
errorMessage: "Search failed",
parameters: {},
customValidator: SearchResultTransformer,
}),
};
}

/**
* Get the User object of the authentified user
* @returns A query to a User object
Expand Down
30 changes: 30 additions & 0 deletions front/src/api/use-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,36 @@ const useQueryClient = () => {
};
};

export const toInfiniteQuery = <T>(q: Query<T[]>): InfiniteQuery<T> => {
return {
key: q.key,
exec: () =>
q.exec().then((res) => ({
items: res,
metadata: {
next: null,
page: 0,
previous: null,
this: "",
},
})),
};
};

export const transformPage = <To, From>(
q: InfiniteQuery<From>,
transformer: (item: From, index: number) => To,
): InfiniteQuery<To> => {
return {
key: q.key,
exec: (p) =>
q.exec(p).then((res) => ({
items: res.items.map(transformer),
metadata: res.metadata,
})),
};
};

export type QueryClient = ReturnType<typeof useQueryClient>;

export {
Expand Down
157 changes: 0 additions & 157 deletions front/src/components/infinite/selectable-infinite-view.tsx

This file was deleted.

70 changes: 70 additions & 0 deletions front/src/components/tab-router.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want.
* Copyright (C) 2023
*
* Meelo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Meelo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import { NextRouter, useRouter } from "next/router";
import { useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";

/**
* Utilitary to update router when using tabs
* @param getTabValueFromRouter how to get the tab value from the router state
* @param defaultTab the default tab
* @param otherTabs the other tabs
*/
export const useTabRouter = <TabValue extends string>(
getTabValueFromRouter: (
router: NextRouter,
) => string | string[] | undefined,
// This should persist the tab change in the router
urlOnTabChange: (newTab: TabValue) => string,
defaultTab: TabValue,
...otherTabs: TabValue[]
) => {
const router = useRouter();
const { t } = useTranslation();
const tabs = useMemo(
() => [defaultTab, ...otherTabs],
[defaultTab, otherTabs],
);
const getTabFromQuery = () =>
tabs.find(
(availableTab) =>
availableTab.toLowerCase() ==
getTabValueFromRouter(router)?.toString().toLowerCase(),
);
const [selectedTab, selectTab] = useState<TabValue>(
getTabFromQuery() ?? defaultTab,
);

//Handle going back in history
useEffect(() => {
const tabFromQuery = getTabFromQuery();
const newTab = tabFromQuery ?? defaultTab;
if (newTab !== selectedTab) {
selectTab(tabFromQuery ?? defaultTab);
}
}, [router.asPath]);
useEffect(() => {
router.push(urlOnTabChange(selectedTab), undefined, {
shallow: true,
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedTab]);

return { selectedTab, selectTab };
};
62 changes: 62 additions & 0 deletions front/src/models/search.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want.
* Copyright (C) 2023
*
* Meelo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Meelo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import { RequireExactlyOne } from "type-fest";
import { SongWithRelations } from "./song";
import { ArtistWithRelations } from "./artist";
import { AlbumWithRelations } from "./album";

export type SearchResult = RequireExactlyOne<{
song: SongWithRelations<"artist" | "featuring" | "illustration" | "master">;
album: AlbumWithRelations<"artist" | "illustration">;
artist: ArtistWithRelations<"illustration">;
}>;

export const SearchResultTransformer = (
results: unknown,
): Promise<SearchResult[]> => {
if (!Array.isArray(results)) {
throw new Error("Search result is not an array");
}
return Promise.all(
results.map(async (result) => {
if ("groupId" in result) {
return {
song: await SongWithRelations([
"artist",
"featuring",
"illustration",
"master",
] as const).validate(result),
};
} else if ("masterId" in result) {
return {
album: await AlbumWithRelations([
"artist",
"illustration",
] as const).validate(result),
};
}
return {
artist: await ArtistWithRelations([
"illustration",
] as const).validate(result),
};
}),
);
};
Loading

0 comments on commit 0bf6066

Please sign in to comment.