Skip to content

Commit

Permalink
refactor: add new Card type and save smaller data in localstorage
Browse files Browse the repository at this point in the history
  • Loading branch information
Stéphane committed Oct 19, 2023
1 parent 156fa2c commit 574f6ed
Show file tree
Hide file tree
Showing 45 changed files with 675 additions and 339 deletions.
78 changes: 58 additions & 20 deletions src/components/ButtonFavorite.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,32 +13,58 @@ import { db } from "../database";
import { getFavoritePlaylist } from "../database/utils";
import { useFavorite, useSetFavorite } from "../providers/Favorite";
import { usePlayerVideo } from "../providers/Player";
import {
Card,
CardChannel,
CardPlaylist,
CardVideo,
} from "../types/interfaces/Card";
import { Channel } from "../types/interfaces/Channel";
import { Playlist } from "../types/interfaces/Playlist";
import { Video } from "../types/interfaces/Video";
import { cleanVideoThumbnailsUrl } from "../utils/cleanVideoThumbnailsUrl";
import {
formatedCardChannel,
formatedCardPlaylist,
formatedCardVideo,
} from "../utils/formatData";

type ButtonFavoriteCard = Card | Video | Playlist | Channel;
type FavoriteChannel = CardChannel | Channel;
type FavoritePlaylist = CardPlaylist | Playlist;
type FavoriteVideo = CardVideo | Video;

interface ButtonFavoriteProps extends ActionIconProps {
video?: Video;
card?: ButtonFavoriteCard;
iconSize?: number;
buttonSize?: number;
render?: "menu";
}

const getItemId = (item: Video) => {
export const getCardId = (item: ButtonFavoriteCard) => {
if ((item as FavoritePlaylist)?.playlistId) {
return (item as FavoritePlaylist).playlistId;
}
if (
(item as FavoriteChannel)?.authorId &&
(item as FavoriteChannel).type === "channel"
) {
return (item as FavoriteChannel).authorId;
}
return (item as FavoriteVideo)?.videoId;
};

export const getCardTitle = (item: ButtonFavoriteCard) => {
switch (item.type) {
case "channel":
return item.authorId;
case "playlist":
// @ts-ignore
return item.playlistId;
return item.author;
default:
return item.videoId;
return item.title;
}
};

export const ButtonFavorite: React.FC<ButtonFavoriteProps> = memo(
({
video: parentVideo,
card: parentCard,
iconSize = 18,
variant = "default",
buttonSize = 36,
Expand All @@ -50,14 +76,18 @@ export const ButtonFavorite: React.FC<ButtonFavoriteProps> = memo(
const { t } = useTranslation();
const theme = useMantineTheme();

const video = parentVideo ?? (currentVideo as Video);
const card = parentCard ?? (currentVideo as Video);

if (!card) {
return null;
}

const isFavorite = favorite.videos.find(
(favVideo) => getItemId(favVideo) === getItemId(video),
(favVideo) => getCardId(favVideo) === getCardId(card),
);

const updateAndCommit = (updatedFavoritePlaylist: Playlist) => {
db.update(
const updateAndCommit = async (updatedFavoritePlaylist: Playlist) => {
await db.update(
"playlists",
{ title: "Favorites" },
() => updatedFavoritePlaylist,
Expand All @@ -67,16 +97,24 @@ export const ButtonFavorite: React.FC<ButtonFavoriteProps> = memo(
};

const handleAdd = () => {
const formatedCard = (() => {
switch (card.type) {
case "channel":
return formatedCardChannel(card as FavoriteChannel);
case "playlist":
return formatedCardPlaylist(card as FavoritePlaylist);
default:
return formatedCardVideo(card);
}
})();

updateAndCommit({
...favorite,
videos: [
cleanVideoThumbnailsUrl({ ...video, videoId: getItemId(video) }),
...favorite.videos,
],
videos: [formatedCard, ...favorite.videos],
});

notifications.show({
title: video.title ?? video.author,
title: getCardTitle(card),
message: t("favorite.add.success.message"),
});
};
Expand All @@ -85,12 +123,12 @@ export const ButtonFavorite: React.FC<ButtonFavoriteProps> = memo(
updateAndCommit({
...favorite,
videos: favorite.videos.filter(
(favVideo) => getItemId(favVideo) !== getItemId(video),
(favVideo) => getCardId(favVideo) !== getCardId(card),
),
});

notifications.show({
title: video.title ?? video.author,
title: getCardTitle(card),
message: t("favorite.remove.success.message"),
});
};
Expand Down
10 changes: 5 additions & 5 deletions src/components/CardImage.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
import { Flex } from "@mantine/core";

import { VideoThumbnail } from "../types/interfaces/Video";
import classes from "./CardImage.module.css";
import { Image } from "./Image";

interface CardImageProps {
image: VideoThumbnail;
src: string;
title: string;
domain?: string;
children?: React.ReactNode;
}

export const CardImage: React.FC<CardImageProps> = ({
image,
src,
title,
domain = "",
children,
}) => {
const domainUrl = image.url.startsWith("https") ? "" : domain;
const domainUrl =
src.startsWith("https") || src.startsWith("//") ? "" : domain;

return (
<Flex
Expand All @@ -26,7 +26,7 @@ export const CardImage: React.FC<CardImageProps> = ({
justify="flex-end"
>
<Image
src={`${domainUrl}${image.url}`}
src={`${domainUrl}${src}`}
alt={title}
className={classes.image}
loading="lazy"
Expand Down
43 changes: 30 additions & 13 deletions src/components/CardList.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,57 @@
import { Box } from "@mantine/core";
import { memo } from "react";
import { FC, memo } from "react";

import { Video } from "../types/interfaces/Video";
import { Card } from "./Card";
import { useSettings } from "../providers/Settings";
import { Card as CardType } from "../types/interfaces/Card";
import { getCardTitle } from "./ButtonFavorite";
import classes from "./CardList.module.css";
import { ChannelCard } from "./ChannelCard";
import { PlaylistCard } from "./PlaylistCard";
import { VideoCard } from "./VideoCard";

interface CardListProps {
data: Video[];
data: CardType[];
scrollable?: boolean;
}

export const CardList: React.FC<CardListProps> = memo(
export const CardList: FC<CardListProps> = memo(
({ data, scrollable = false }) => {
const { currentInstance } = useSettings();

if (!data.length) {
return null;
}

console.log(classes);

return (
<Box className={scrollable ? classes.flexGrid : classes.grid}>
{data.map((item, index) => (
{data.map((card, index) => (
<Box
key={`${document.location.pathname}${item.title}-${index}`}
key={`${document.location.pathname}${getCardTitle(card)}-${index}`}
className={scrollable ? classes.flexColumn : classes.column}
>
{(() => {
switch (item.type) {
switch (card.type) {
case "playlist":
return <PlaylistCard playlist={item as any} />;
return (
<PlaylistCard
playlist={card}
currentInstanceUri={currentInstance?.uri ?? ""}
/>
);
case "channel":
return <ChannelCard channel={item as any} />;
return (
<ChannelCard
channel={card}
currentInstanceUri={currentInstance?.uri ?? ""}
/>
);
default:
return <Card video={item} />;
return (
<VideoCard
video={card}
currentInstanceUri={currentInstance?.uri ?? ""}
/>
);
}
})()}
</Box>
Expand Down
10 changes: 5 additions & 5 deletions src/components/CardMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ import { memo, useState } from "react";
import { useTranslation } from "react-i18next";

import { useIsLocalPlaylist } from "../hooks/useIsLocalPlaylist";
import { Video } from "../types/interfaces/Video";
import { CardVideo } from "../types/interfaces/Card";
import { ModalAddToPlaylist } from "./ModalAddToPlaylist";
import { ModalDeleteFromPlaylist } from "./ModalDeleteFromPlaylist";

interface CardMenuProps {
video: Video;
card: CardVideo;
}

export const CardMenu: React.FC<CardMenuProps> = memo(({ video }) => {
export const CardMenu: React.FC<CardMenuProps> = memo(({ card }) => {
const [addToPlaylistModalOpened, setAddToPlaylistModalOpened] =
useState(false);
const [deleteFromPlaylistModalOpened, setDeleteFromPlaylistModalOpened] =
Expand Down Expand Up @@ -68,12 +68,12 @@ export const CardMenu: React.FC<CardMenuProps> = memo(({ video }) => {
<ModalAddToPlaylist
opened={addToPlaylistModalOpened}
onClose={() => setAddToPlaylistModalOpened(false)}
video={video}
video={card}
/>
<ModalDeleteFromPlaylist
opened={deleteFromPlaylistModalOpened}
onClose={() => setDeleteFromPlaylistModalOpened(false)}
video={video}
video={card}
/>
</>
);
Expand Down
104 changes: 58 additions & 46 deletions src/components/ChannelCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,61 +12,73 @@ import { memo } from "react";
import { useTranslation } from "react-i18next";

import { useStableNavigate } from "../providers/Navigate";
import { Channel } from "../types/interfaces/Channel";
import { CardChannel } from "../types/interfaces/Card";
import { VideoThumbnail } from "../types/interfaces/Video";
import { ButtonFavorite } from "./ButtonFavorite";
import { CardImage } from "./CardImage";
import classes from "./ChannelCard.module.css";

interface ChannelCardProps {
channel: Channel;
channel: CardChannel;
currentInstanceUri: string;
}

export const ChannelCard: React.FC<ChannelCardProps> = memo(({ channel }) => {
const navigate = useStableNavigate();
const { t } = useTranslation();
export const ChannelCard: React.FC<ChannelCardProps> = memo(
({ channel, currentInstanceUri }) => {
const navigate = useStableNavigate();
const { t } = useTranslation();

const goToChannel = () => {
navigate(`/channels/${channel.authorId}`);
};
const goToChannel = () => {
navigate(`/channels/${channel.authorId}`);
};

const image = channel.authorThumbnails.find(
(thumbnail) => thumbnail.width === 512,
) as VideoThumbnail;
const imageSrc =
channel.thumbnail ??
(
channel.authorThumbnails?.find(
(thumbnail) => thumbnail.width === 512,
) as VideoThumbnail
).url;

return (
<Card withBorder radius="md" p="sm" className={classes.card}>
<UnstyledButton style={{ width: "100%" }} onClick={goToChannel}>
<CardImage image={image} title={channel.author} />
<Flex align="center" gap={8}>
<Title order={3} mb="xs" mt="md">
<Text lineClamp={1}>{channel.author}</Text>
</Title>
{channel.authorVerified ? (
<Box mt={12}>
<Tooltip label={t("channel.author.verified")}>
<IconDiscountCheckFilled size={16} />
</Tooltip>
</Box>
) : null}
return (
<Card withBorder radius="md" p="sm" className={classes.card}>
<UnstyledButton style={{ width: "100%" }} onClick={goToChannel}>
<CardImage
src={imageSrc}
title={channel.author}
domain={currentInstanceUri}
/>
<Flex align="center" gap={8}>
<Title order={3} mb="xs" mt="md">
<Text style={{ fontSize: 22 }} lineClamp={1}>
<strong>{channel.author}</strong>
</Text>
</Title>
{channel.authorVerified ? (
<Box mt={12}>
<Tooltip label={t("channel.author.verified")}>
<IconDiscountCheckFilled size={16} />
</Tooltip>
</Box>
) : null}
</Flex>
<Text lineClamp={3}>{channel.description}</Text>
</UnstyledButton>
<Flex align="center" justify="space-between" mt="xs">
<Box mt="xs">
<Text size="xs" mb={4}>
<strong>
{new Intl.NumberFormat().format(channel.subCount)}{" "}
{t("channel.subscribers")}
</strong>
</Text>
<Text size="xs">
<strong>{channel.videoCount} videos</strong>
</Text>
</Box>
<ButtonFavorite card={channel} />
</Flex>
<Text lineClamp={3}>{channel.description}</Text>
</UnstyledButton>
<Flex align="center" justify="space-between" mt="xs">
<Box mt="xs">
<Text size="xs" mb={4}>
<strong>
{new Intl.NumberFormat().format(channel.subCount)}{" "}
{t("channel.subscribers")}
</strong>
</Text>
<Text size="xs">
<strong>{channel.videoCount} videos</strong>
</Text>
</Box>
{/* @ts-ignore */}
<ButtonFavorite video={channel} />
</Flex>
</Card>
);
});
</Card>
);
},
);
Loading

0 comments on commit 574f6ed

Please sign in to comment.