Files
musicland/src/hooks/useSearch.js
T
Thomas Demirdjian d7d9211fbe clear lot of tickets
2025-11-07 17:33:06 +01:00

221 lines
5.7 KiB
JavaScript

import { useEffect, useState } from "react";
import useAlgoliaSearch from "react-native-minuit/src/hooks/useAlgoliaSearch";
import { AlgoliaUserConfig, AlgoliaProjectConfig } from "../data/keys";
import { projectsRef } from "../config/firebase";
import { useUserData } from "../providers/UserDataProvider";
const batchSizes = {
users: 5,
projects: 6,
};
const isValidUri = (value) =>
typeof value === "string" && value.trim().length > 0;
const hasCoverAsset = (project) => {
if (!project) return false;
if (isValidUri(project?.coverUrl)) {
return true;
}
const cover = project?.cover;
if (!cover || typeof cover !== "object") {
return false;
}
const coverCandidates = [
cover?.result,
cover?.finalUrl,
cover?.generatedBackground,
];
if (Array.isArray(cover?.options)) {
coverCandidates.push(
...cover.options.flatMap((option) => [
option?.finalUrl,
option?.generatedUrl,
]),
);
}
return coverCandidates.some(isValidUri);
};
const hasThumbnailAsset = (project) =>
isValidUri(project?.thumbnailUrl) || isValidUri(project?.songThumbnailUrl);
const mergeProjectAssets = (
project,
firestoreData,
{ mergeCover = false, mergeThumbnails = false } = {},
) => {
if (!firestoreData || typeof firestoreData !== "object") {
return project;
}
const mergedProject = { ...project };
if (mergeCover) {
if (!isValidUri(mergedProject.coverUrl) && isValidUri(firestoreData.coverUrl)) {
mergedProject.coverUrl = firestoreData.coverUrl;
}
const firestoreCover =
firestoreData.cover && typeof firestoreData.cover === "object"
? firestoreData.cover
: null;
if (firestoreCover) {
mergedProject.cover = {
...(typeof mergedProject.cover === "object" ? mergedProject.cover : {}),
...firestoreCover,
};
}
}
if (mergeThumbnails) {
if (!isValidUri(mergedProject.thumbnailUrl) && isValidUri(firestoreData.thumbnailUrl)) {
mergedProject.thumbnailUrl = firestoreData.thumbnailUrl;
}
if (
!isValidUri(mergedProject.songThumbnailUrl) &&
isValidUri(firestoreData.songThumbnailUrl)
) {
mergedProject.songThumbnailUrl = firestoreData.songThumbnailUrl;
}
}
return mergedProject;
};
const hydrateProjects = async (
projects = [],
{ ensureCover = false, ensureThumbnails = false } = {},
) => {
if (!Array.isArray(projects) || projects.length === 0) {
return [];
}
return Promise.all(
projects.map(async (project) => {
const needsCover = ensureCover && !hasCoverAsset(project);
const needsThumbnail = ensureThumbnails && !hasThumbnailAsset(project);
if ((!needsCover && !needsThumbnail) || !project?.id) {
return project;
}
try {
const snapshot = await projectsRef.doc(project.id).get();
if (!snapshot.exists) {
return project;
}
const firestoreData = snapshot.data() || {};
return mergeProjectAssets(project, firestoreData, {
mergeCover: needsCover,
mergeThumbnails: needsThumbnail,
});
} catch (error) {
console.log("useSearch.hydrateProjects error", error?.message || error);
return project;
}
}),
);
};
const useSearch = () => {
const [selected, setSelected] = useState(null);
const [search, setSearch] = useState("");
const [hydratedMusics, setHydratedMusics] = useState(null);
const [hydratedPlaybacks, setHydratedPlaybacks] = useState(null);
const { currentUID } = useUserData();
const { hits: users, loading: userLoading } = useAlgoliaSearch({
query: search,
algoliaObject: AlgoliaUserConfig,
batch: batchSizes.users,
searchParams: {
filters: `NOT objectID:${currentUID}`,
},
});
const { hits: playbacks, loading: playbackLoading } = useAlgoliaSearch({
query: search,
algoliaObject: AlgoliaProjectConfig,
batch: batchSizes.projects,
searchParams: {
filters: `hasPlayback:true AND hasSong:true`,
},
});
const { hits: musics, loading: musicLoading } = useAlgoliaSearch({
query: search,
algoliaObject: AlgoliaProjectConfig,
batch: batchSizes.projects,
searchParams: {
filters: `hasSong:true`,
},
});
useEffect(() => {
let isCancelled = false;
const nextMusics = Array.isArray(musics) ? musics : [];
const requiresHydration =
nextMusics.length > 0 &&
nextMusics.some((project) => !hasCoverAsset(project));
if (!requiresHydration) {
setHydratedMusics(null);
return () => {
isCancelled = true;
};
}
setHydratedMusics(null);
(async () => {
const hydrated = await hydrateProjects(nextMusics, { ensureCover: true });
if (!isCancelled) {
setHydratedMusics(hydrated);
}
})();
return () => {
isCancelled = true;
};
}, [musics]);
useEffect(() => {
let isCancelled = false;
const nextPlaybacks = Array.isArray(playbacks) ? playbacks : [];
const requiresHydration =
nextPlaybacks.length > 0 &&
nextPlaybacks.some((project) => !hasThumbnailAsset(project));
if (!requiresHydration) {
setHydratedPlaybacks(null);
return () => {
isCancelled = true;
};
}
setHydratedPlaybacks(null);
(async () => {
const hydrated = await hydrateProjects(nextPlaybacks, {
ensureThumbnails: true,
});
if (!isCancelled) {
setHydratedPlaybacks(hydrated);
}
})();
return () => {
isCancelled = true;
};
}, [playbacks]);
return {
search,
setSearch,
selected,
setSelected,
users,
musics: hydratedMusics ?? musics,
playbacks: hydratedPlaybacks ?? playbacks,
loading: userLoading || musicLoading || playbackLoading,
};
};
export default useSearch;