221 lines
5.7 KiB
JavaScript
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;
|