Skip to content

Commit

Permalink
refactor: refactor export and import data
Browse files Browse the repository at this point in the history
  • Loading branch information
Stéphane committed Oct 19, 2023
1 parent 3d25840 commit 96152a3
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 141 deletions.
51 changes: 20 additions & 31 deletions src/components/ExportData.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,36 @@
import { Box, Button, Flex } from "@mantine/core";
import { notifications } from "@mantine/notifications";
import { memo, useState } from "react";
import { memo } from "react";
import { useTranslation } from "react-i18next";

import { getAllPlaylists } from "../database/utils";
import { Playlist } from "../types/interfaces/Playlist";
import { generateAndDownloadFile } from "../utils/generateAndDownloadFile";
import { TransferList } from "./TransferList";
import { TransferList, TransferListData } from "./TransferList";

const loadPlaylistData = (data: any[]) => {
const loadPlaylistData = (playlistsTitle: string[]) => {
const playlists = getAllPlaylists();
return data.map((playlist) =>
playlists.find((p) => p.playlistId ?? String(p.ID) === playlist.value),
);
return playlists.filter((p) => playlistsTitle.includes(p.title));
};

// const formateToTransferList = (data: Playlist[]) => {
// return data
// .filter((item) => item.videos.length > 0)
// .map((item) => ({
// value: item.playlistId ?? String(item.ID),
// label: `${item.title} (${item.videos.length} videos)`,
// }))
// .flat();
// };
const formatePlaylistsToExport = (playlists: Playlist[]) => {
return playlists.map((p) => ({
...p,
videos: p.videos.map((v) => v.videoId),
}));
};

export const ExportData = memo(() => {
const userData = getAllPlaylists().map((p) => p.title);
const { t } = useTranslation("translation", {
keyPrefix: "settings.data.export",
});
const [data] = useState<any>(getAllPlaylists().map((p) => p.title));

console.log(data);

const handleClick = () => {
generateAndDownloadFile(loadPlaylistData(data[1]));
const handleSubmit = (data: TransferListData) => {
const [, importData] = data;
const playlists = loadPlaylistData(importData);
const formatedPlaylists = formatePlaylistsToExport(playlists);
generateAndDownloadFile({ playlists: formatedPlaylists });
notifications.show({
title: t("notification.title"),
message: t("notification.message"),
Expand All @@ -43,18 +40,10 @@ export const ExportData = memo(() => {
return (
<Box mt="lg">
<TransferList
data={data}
// onChange={setData}
// titles={[t("left"), t("right")]}
// breakpoint="sm"
// searchPlaceholder={t("search.placeholder") as string}
// nothingFound={t("search.nothing.found")}
data={userData}
handleSubmit={handleSubmit}
buttonSubmitLabel={t("button.submit")}
/>
<Flex justify="flex-end" mt="lg">
<Button onClick={handleClick} disabled={!data.length}>
{t("button.submit")}
</Button>
</Flex>
</Box>
);
});
198 changes: 106 additions & 92 deletions src/components/ImportData.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
import {
Alert,
Box,
Button,
Flex,
Group,
LoadingOverlay,
Text,
useMantineColorScheme,
useMantineTheme,
} from "@mantine/core";
import { Dropzone } from "@mantine/dropzone";
import { Dropzone, FileRejection } from "@mantine/dropzone";
import "@mantine/dropzone/styles.css";
import { notifications } from "@mantine/notifications";
import { IconFileBarcode, IconFileX, IconUpload } from "@tabler/icons-react";
import { memo, useState } from "react";
import {
IconFileBarcode,
IconFileX,
IconInfoCircle,
IconUpload,
} from "@tabler/icons-react";
// @ts-ignore
import { map as cappedAll } from "awaiting";
import { FC, memo, useState } from "react";
import { useTranslation } from "react-i18next";

import {
Expand All @@ -23,31 +31,16 @@ import { getPlaylists } from "../database/utils";
import { useSetFavorite } from "../providers/Favorite";
import { useSetPlaylists } from "../providers/Playlist";
import { getVideo } from "../services/video";
import { Playlist } from "../types/interfaces/Playlist";
import { Video } from "../types/interfaces/Video";
import { TransferList } from "./TransferList";

export const formateToTransferList = (data: ImportDataType[]) => {
return data
.filter((item) => item.videos.length > 0)
.map((item) => ({
// @ts-ignore
value: String(item.ID),
label: `${item.playlistName} (${item.videos.length} videos)`,
}));
};

interface ImportDataType {
playlistName: string;
protected: boolean;
videos: Video[];
}
import { TransferList, TransferListData } from "./TransferList";

export const ImportData = memo(() => {
const theme = useMantineTheme();
const { colorScheme } = useMantineColorScheme();
const [importedFileData, setImportedFileData] = useState<
ImportDataType[] | null
>(null);
const [importedFileData, setImportedFileData] = useState<Playlist[] | null>(
null,
);
const { t } = useTranslation("translation", {
keyPrefix: "settings.data.import",
});
Expand All @@ -58,23 +51,33 @@ export const ImportData = memo(() => {

reader.addEventListener("load", (event) => {
const fileData = JSON.parse(event.target?.result as string);
setImportedFileData(fileData);
setImportedFileData(fileData?.playlists ?? fileData);
});
reader.readAsText(file);
};

const handleReject = ([file]: FileRejection[]) => {
const [error] = file.errors;
notifications.show({
title: t("notification.error.title"),
message: error.message,
color: "red",
});
};

return (
<Box mt="lg">
<AlertImportInfos />
{importedFileData ? (
<Test
importedFileData={importedFileData}
<TransferListContainer
data={importedFileData}
onClear={() => setImportedFileData(null)}
/>
) : (
<Dropzone
onDrop={handleDrop}
onReject={(files) => console.log("rejected files", files)}
maxSize={3 * 1024 ** 2}
onReject={handleReject}
maxSize={10 * 1024 ** 2}
accept={["application/document", "application/json"]}
>
<Group
Expand Down Expand Up @@ -117,63 +120,91 @@ export const ImportData = memo(() => {
);
});

const Test = memo(
({
importedFileData,
onClear,
}: {
importedFileData: ImportDataType[];
onClear: () => void;
}) => {
const AlertImportInfos = memo(() => {
return (
<Alert mb="lg">
<Flex gap="xs">
<IconInfoCircle />
<Text size="md">
Only <strong>Invidious</strong> and <strong>HoloPlay</strong> export
file can be imported for now.
</Text>
</Flex>
</Alert>
);
});

interface TransferListContainerProps {
data: Playlist[];
onClear(): void;
}

const loadPlaylistsFromFileData = (
playlists: Playlist[],
playlistsTitle: string[],
) => playlists.filter((p) => playlistsTitle.includes(p.title));

type FetchVideosData = {
video: Video;
url: string;
}[];

const getVideosData = async (
videos: Video[],
): Promise<{
validData: FetchVideosData;
invalidData: FetchVideosData;
}> => {
const data = (await cappedAll(videos, 5, (video: Video) =>
getVideo(video.videoId),
)) as FetchVideosData;
const validData = data.filter(
({ url }) => url !== undefined,
) as FetchVideosData;
const invalidData = data.filter(
({ url }) => url === undefined,
) as FetchVideosData;

return {
validData,
invalidData,
};
};

const TransferListContainer: FC<TransferListContainerProps> = memo(
({ data: importedFileData, onClear }) => {
const setFavorite = useSetFavorite();
const setPlaylists = useSetPlaylists();
const [loading, setLoading] = useState(false);
const [importData, setImportData] = useState<any>([
formateToTransferList(importedFileData),
[],
]);
const { t } = useTranslation("translation", {
keyPrefix: "settings.data.import",
});

const handleImportData = async () => {
const handleSubmit = async (transferListData: TransferListData) => {
setLoading(true);

try {
const favoritesData = importedFileData.find(
(data) => data.playlistName === "Favorites",
const [, importData] = transferListData;
const playlists = loadPlaylistsFromFileData(
importedFileData,
importData,
);
const playlistsData = importedFileData.filter(
(data) => data.playlistName !== "Favorites",
);

if (favoritesData) {
const promises = [];

for (const video of favoritesData.videos) {
promises.push(getVideo(video.videoId));
}
const favoritePlaylist = playlists.find((p) => p.title === "Favorites");
const userPlaylists = playlists.filter((p) => p.title !== "Favorites");

const videosData = await Promise.all(promises);

importVideosToFavorites(videosData.map(({ video }) => video));
if (favoritePlaylist) {
const { validData } = await getVideosData(favoritePlaylist.videos);
importVideosToFavorites(validData.map(({ video }) => video));
setFavorite(getFavoritePlaylist());
}

if (playlistsData.length > 0) {
playlistsData.map(async (playlist) => {
const promises = [];

for (const video of playlist.videos) {
promises.push(getVideo(video.videoId));
}

const videosData = await Promise.all(promises);

if (userPlaylists.length > 0) {
userPlaylists.map(async (playlist) => {
const { validData: videos } = await getVideosData(playlist.videos);
importPlaylist({
title: playlist.playlistName,
videos: videosData.map(({ video }) => video),
videoCount: videosData.length,
title: playlist.title,
videos: videos.map(({ video }) => video),
videoCount: videos.length,
});
setPlaylists(getPlaylists());
});
Expand All @@ -183,9 +214,6 @@ const Test = memo(
title: t("notification.title"),
message: t("notification.message"),
});

onClear();
setImportData([[], []]);
} catch (error) {
notifications.show({
title: t("notification.error.title"),
Expand All @@ -198,27 +226,13 @@ const Test = memo(
};

return (
<Box mt="lg">
<Box mt="lg" pos="relative">
<LoadingOverlay visible={loading} />
<TransferList
// @ts-ignore
value={importData}
onChange={setImportData}
titles={[t("left"), t("right")]}
breakpoint="sm"
searchPlaceholder={t("search.placeholder") as string}
nothingFound={t("search.nothing.found")}
listHeight={300}
data={importedFileData.map((p) => p.title)}
handleSubmit={handleSubmit}
buttonSubmitLabel={t("button.submit")}
/>
<Flex justify="flex-end">
<Button
mt="lg"
loading={loading}
disabled={!importData[1].length}
onClick={handleImportData}
>
{t("button.submit")}
</Button>
</Flex>
</Box>
);
},
Expand Down
Loading

0 comments on commit 96152a3

Please sign in to comment.