Skip to content

Commit 0af27bd

Browse files
committed
feat: sorting options and ability to hide streamers
1 parent 5909134 commit 0af27bd

12 files changed

Lines changed: 435 additions & 43 deletions

File tree

apps/extension/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"@radix-ui/react-avatar": "^1.1.10",
2828
"@radix-ui/react-dialog": "^1.1.14",
2929
"@radix-ui/react-label": "^2.1.7",
30+
"@radix-ui/react-select": "^2.2.5",
3031
"@radix-ui/react-slot": "^1.2.3",
3132
"@radix-ui/react-switch": "^1.2.5",
3233
"@radix-ui/react-tabs": "^1.1.12",

apps/extension/src/background/notifications.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,21 @@ chrome.runtime.onMessage.addListener(async (message) => {
5151
const settings = await storage.get<Settings>('settings');
5252
const favorites =
5353
(await storage.get<Streamer['id'][]>('favorites')) || [];
54+
const blockedStreamers =
55+
(await storage.get<Streamer['id'][]>('blocked')) || [];
5456

5557
// Skip if notifications are disabled
5658
if (settings?.notifyWhenStreamerIsLive === false) {
5759
logger.info('Notifications disabled, skipping browser notification');
5860
return;
5961
}
6062

63+
// Skip if streamer is blocked
64+
if (blockedStreamers.includes(parsed.data.data.id)) {
65+
logger.info('Streamer is blocked, skipping browser notification');
66+
return;
67+
}
68+
6169
// Skip if only favorites should be notified and this streamer is not a favorite
6270
if (
6371
settings?.onlyNotifyWhenFavoriteStreamerIsLive &&
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import type { SortOption } from '@/lib/types';
2+
import {
3+
Select,
4+
SelectContent,
5+
SelectItem,
6+
SelectTrigger,
7+
SelectValue,
8+
} from '../ui/select';
9+
10+
interface SortDropdownProps {
11+
value: SortOption;
12+
onChange: (value: SortOption) => void;
13+
}
14+
15+
export function SortDropdown({ value, onChange }: SortDropdownProps) {
16+
return (
17+
<Select
18+
defaultValue="default"
19+
onValueChange={(value) => onChange(value as SortOption)}
20+
value={value}
21+
>
22+
<SelectTrigger>
23+
<SelectValue placeholder="Sort by" />
24+
</SelectTrigger>
25+
<SelectContent>
26+
<SelectItem value="default">Default (Live first)</SelectItem>
27+
<SelectItem value="viewers-desc">Most viewers first</SelectItem>
28+
<SelectItem value="viewers-asc">Least viewers first</SelectItem>
29+
<SelectItem value="name-asc">Name (A-Z)</SelectItem>
30+
<SelectItem value="name-desc">Name (Z-A)</SelectItem>
31+
</SelectContent>
32+
</Select>
33+
);
34+
}

apps/extension/src/components/molecules/streamer-card.tsx

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Eye, EyeOff } from 'lucide-react';
12
import { FaRegStar, FaStar } from 'react-icons/fa6';
23
import { FiTwitch } from 'react-icons/fi';
34
import { GoDotFill } from 'react-icons/go';
@@ -22,13 +23,19 @@ const streamerDescription = (streamer: Streamer) => {
2223
export function StreamerCard({
2324
streamer,
2425
isFavorite,
26+
isBlocked,
2527
onFavorite,
28+
onBlock,
2629
}: {
2730
streamer: Streamer;
2831
isFavorite: boolean;
32+
isBlocked: boolean;
2933
onFavorite: (streamer: Streamer) => void;
34+
onBlock: (streamer: Streamer) => void;
3035
}) {
3136
const StarIcon = isFavorite ? FaStar : FaRegStar;
37+
const BlockIcon = isBlocked ? EyeOff : Eye;
38+
3239
const onClick = () => {
3340
const twitchUrl = streamer.twitchUsername
3441
? `https://www.twitch.tv/${streamer.twitchUsername}`
@@ -43,9 +50,7 @@ export function StreamerCard({
4350

4451
if (streamer.isLive) {
4552
window.open(
46-
streamer.livePlatform === 'twitch'
47-
? twitchUrl
48-
: kickUrl,
53+
streamer.livePlatform === 'twitch' ? twitchUrl : kickUrl,
4954
'_blank'
5055
);
5156
} else {
@@ -61,13 +66,33 @@ export function StreamerCard({
6166
}
6267
)}
6368
>
64-
<div className="flex flex-row gap-2 items-center">
65-
<div
66-
className="px-2 cursor-pointer text-primary"
67-
onClick={() => onFavorite(streamer)}
68-
>
69-
<StarIcon size={16} />
70-
</div>
69+
<div className="flex flex-col gap-1 items-center">
70+
<Tooltip>
71+
<TooltipTrigger>
72+
<div
73+
className="px-2 cursor-pointer text-primary"
74+
onClick={() => onFavorite(streamer)}
75+
>
76+
<StarIcon size={16} />
77+
</div>
78+
</TooltipTrigger>
79+
<TooltipContent>
80+
{isFavorite ? 'Remove from favorites' : 'Add to favorites'}
81+
</TooltipContent>
82+
</Tooltip>
83+
<Tooltip>
84+
<TooltipTrigger>
85+
<div
86+
className="px-2 cursor-pointer text-muted-foreground hover:text-primary"
87+
onClick={() => onBlock(streamer)}
88+
>
89+
<BlockIcon size={16} />
90+
</div>
91+
</TooltipTrigger>
92+
<TooltipContent>
93+
{isBlocked ? 'Show streamer' : 'Hide streamer'}
94+
</TooltipContent>
95+
</Tooltip>
7196
</div>
7297

7398
<div className="flex flex-row gap-2 justify-start items-center flex-grow">

apps/extension/src/components/molecules/streamer-list.tsx

Lines changed: 49 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,68 @@
11
import { useStorage } from '@plasmohq/storage/hook';
2-
import type { Streamer } from '@/lib/types';
2+
import type { SortOption, Streamer } from '@/lib/types';
3+
import { sortStreamers } from '@/lib/utils';
34
import { StreamerCard } from './streamer-card';
45

5-
export function StreamerList({ streamers, favoriteOnly = false, offlineOnly = false }: { streamers: Streamer[], favoriteOnly?: boolean, offlineOnly?: boolean }) {
6+
export function StreamerList({
7+
streamers,
8+
favoriteOnly = false,
9+
liveOnly = false,
10+
sortBy = 'default',
11+
}: {
12+
streamers: Streamer[];
13+
favoriteOnly?: boolean;
14+
liveOnly?: boolean;
15+
sortBy?: SortOption;
16+
}) {
617
const [favorites, setFavorites] = useStorage<Streamer['id'][]>(
718
'favorites',
819
[]
920
);
21+
const [blockedStreamers, setBlockedStreamers] = useStorage<Streamer['id'][]>(
22+
'blocked',
23+
[]
24+
);
1025

11-
const filteredStreamers = streamers.filter((streamer) => {
12-
if (favoriteOnly) {
13-
return favorites.includes(streamer.id);
14-
}
15-
if (offlineOnly) {
16-
return !streamer.isLive;
17-
}
18-
return true;
19-
});
26+
const filteredAndSortedStreamers = sortStreamers(
27+
streamers.filter((streamer) => {
28+
if (favoriteOnly) {
29+
return (
30+
favorites.includes(streamer.id) &&
31+
!blockedStreamers.includes(streamer.id)
32+
);
33+
}
34+
35+
if (liveOnly) {
36+
return streamer.isLive && !blockedStreamers.includes(streamer.id);
37+
}
38+
return true;
39+
}),
40+
sortBy
41+
);
2042

2143
return (
22-
<div className="w-full flex flex-col gap-4 overflow-y-auto h-[320px]">
23-
{filteredStreamers.length === 0 && (
44+
<div className="w-full flex flex-col gap-4 overflow-y-auto h-[320px] overflow-x-hidden">
45+
{filteredAndSortedStreamers.length === 0 && (
2446
<div className="w-full flex flex-col gap-4 items-center justify-center">
25-
<p className="text-sm text-muted-foreground">
26-
No streamers found.
27-
</p>
47+
<p className="text-sm text-muted-foreground">No streamers found.</p>
2848
</div>
2949
)}
30-
{filteredStreamers.map((streamer) => (
50+
{filteredAndSortedStreamers.map((streamer) => (
3151
<StreamerCard
52+
isBlocked={blockedStreamers.includes(streamer.id)}
3253
isFavorite={favorites.includes(streamer.id)}
3354
key={streamer.id}
55+
onBlock={(streamer) => {
56+
setBlockedStreamers((blocked) => {
57+
const newBlocked = [...blocked];
58+
if (newBlocked.includes(streamer.id)) {
59+
newBlocked.splice(newBlocked.indexOf(streamer.id), 1);
60+
} else {
61+
newBlocked.push(streamer.id);
62+
}
63+
return newBlocked;
64+
});
65+
}}
3466
onFavorite={(streamer) => {
3567
setFavorites((favorites) => {
3668
const newFavorites = [...favorites];

0 commit comments

Comments
 (0)