Skip to content

Commit 9467a27

Browse files
committed
feat: muted channels
1 parent a48d545 commit 9467a27

File tree

9 files changed

+68
-10
lines changed

9 files changed

+68
-10
lines changed

locales/en/messages.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,9 @@
239239
"optionValue_logout": {
240240
"message": "Logout"
241241
},
242+
"optionValue_muteChannel": {
243+
"message": "Mute channel"
244+
},
242245
"optionValue_newCollection": {
243246
"message": "New collection"
244247
},

locales/fr/messages.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,9 @@
239239
"optionValue_logout": {
240240
"message": "Déconnexion"
241241
},
242+
"optionValue_muteChannel": {
243+
"message": "Chaîne en sourdine"
244+
},
242245
"optionValue_newCollection": {
243246
"message": "Nouvelle collection"
244247
},

src/background/index.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { backup, reset, restore } from "./modules/maintenance";
99
import { sendStreamNotifications } from "./modules/notification";
1010
import {
1111
authorize,
12+
filterMutedStreams,
1213
filterNewStreams,
1314
getCurrentUser,
1415
getFollowedStreams,
@@ -34,12 +35,14 @@ async function refresh(withNotifications: boolean) {
3435
}
3536
}
3637

37-
refreshActionBadge(!!currentUser, followedStreams.length);
38+
const filteredStreams = await filterMutedStreams(followedStreams);
3839

3940
if (withNotifications) {
40-
sendStreamNotifications(await filterNewStreams(followedStreams));
41+
sendStreamNotifications(await filterNewStreams(filteredStreams));
4142
}
4243

44+
refreshActionBadge(!!currentUser, filteredStreams.length);
45+
4346
stores.currentUser.set(currentUser);
4447
stores.followedStreams.set(followedStreams);
4548
}

src/background/modules/twitch.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,3 +175,11 @@ export async function filterNewStreams(streams: HelixStream[]) {
175175
return oldStream == null || (withCategoryChanges && oldStream.gameId !== stream.gameId);
176176
});
177177
}
178+
179+
export async function filterMutedStreams(streams: HelixStream[]) {
180+
const mutedUsers = await stores.mutedUsers.get();
181+
182+
return streams.filter((stream) => {
183+
return !mutedUsers.includes(stream.userId);
184+
});
185+
}

src/browser/components/cards/StreamCard.tsx

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,16 +41,27 @@ const StyledDropdownButton = styled(DropdownButton)`
4141
${tw`absolute invisible end-6 -top-2 z-20`}
4242
`;
4343

44-
const Wrapper = styled(Card)`
44+
interface WrapperProps {
45+
isMuted?: boolean;
46+
}
47+
48+
const Wrapper = styled(Card)<WrapperProps>`
4549
${tw`py-2 relative`}
4650
47-
:hover ${StyledDropdownButton} {
48-
${tw`visible`}
51+
:hover {
52+
${tw`opacity-100`}
53+
54+
${StyledDropdownButton} {
55+
${tw`visible`}
56+
}
4957
}
58+
59+
${(props) => props.isMuted && tw`opacity-50`}
5060
`;
5161

5262
export interface StreamCardProps {
5363
stream: HelixStream;
64+
isMuted?: boolean;
5465

5566
onNewCollection?(): void;
5667
}
@@ -82,6 +93,7 @@ function StreamCard(props: StreamCardProps) {
8293
return (
8394
<Anchor to={defaultAction}>
8495
<Wrapper
96+
isMuted={props.isMuted}
8597
title={
8698
<Title>
8799
<UserName login={stream.userLogin} name={stream.userName} />

src/browser/components/dropdowns/StreamDropdown.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { useNavigate } from "react-router";
55
import { openUrl, t, template } from "~/common/helpers";
66
import { HelixStream } from "~/common/types";
77

8-
import { useCollections, useSettings } from "~/browser/hooks";
8+
import { useCollections, useMutedUsers, useSettings } from "~/browser/hooks";
99

1010
import DropdownMenu, { DropdownMenuItemProps } from "../DropdownMenu";
1111

@@ -23,13 +23,20 @@ function StreamDropdown(props: StreamDropdownProps) {
2323

2424
const [settings] = useSettings();
2525
const [collections, { toggleCollectionItem }] = useCollections("user");
26+
const [mutedUsers, { toggle }] = useMutedUsers();
2627

2728
const {
2829
dropdownMenu: { customActions },
2930
} = settings;
3031

3132
const items = useMemo(() => {
3233
const result = new Array<DropdownMenuItemProps>(
34+
{
35+
type: "checkbox",
36+
title: t("optionValue_muteChannel"),
37+
checked: mutedUsers.includes(stream.userId),
38+
onChange: () => toggle(stream.userId),
39+
},
3340
{
3441
type: "normal",
3542
title: t("optionValue_openChannel"),

src/browser/hooks/store.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,19 @@ export function useCollections(
164164
];
165165
}
166166

167+
export function useMutedUsers(options?: UseStoreOptions) {
168+
const [mutedUsers, store] = useStore(stores.mutedUsers, options);
169+
170+
return [
171+
mutedUsers,
172+
{
173+
toggle(userId: string) {
174+
store.set((mutedUsers) => xor(mutedUsers, [userId]));
175+
},
176+
},
177+
] as const;
178+
}
179+
167180
export function useCurrentUser(options?: UseStoreOptions) {
168181
return useStore(stores.currentUser, options);
169182
}

src/browser/views/popup/FollowedStreams.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { FollowedStreamState } from "~/common/types";
77

88
import { useRefreshHandler } from "~/browser/contexts";
99
import { filterList, isEmpty } from "~/browser/helpers";
10-
import { useFollowedStreams, useFollowedStreamState } from "~/browser/hooks";
10+
import { useFollowedStreams, useFollowedStreamState, useMutedUsers } from "~/browser/hooks";
1111

1212
import StreamCard from "~/browser/components/cards/StreamCard";
1313

@@ -30,6 +30,10 @@ function ChildComponent(props: ChildComponentProps) {
3030
suspense: true,
3131
});
3232

33+
const [mutedUsers] = useMutedUsers({
34+
suspense: true,
35+
});
36+
3337
const filteredStreams = useMemo(() => {
3438
let { sortDirection } = followedStreamState;
3539

@@ -39,10 +43,10 @@ function ChildComponent(props: ChildComponentProps) {
3943

4044
return orderBy(
4145
filterList(followedStreams, ["gameName", "title", "userLogin", "userName"], searchQuery),
42-
followedStreamState.sortField,
43-
sortDirection,
46+
[(stream) => mutedUsers.includes(stream.userId), followedStreamState.sortField],
47+
["asc", sortDirection],
4448
);
45-
}, [followedStreamState, followedStreams, searchQuery]);
49+
}, [followedStreamState, followedStreams, mutedUsers, searchQuery]);
4650

4751
useRefreshHandler(async () => {
4852
await sendRuntimeMessage("refresh", true);
@@ -67,6 +71,7 @@ function ChildComponent(props: ChildComponentProps) {
6771
<StreamCard
6872
key={item.id}
6973
stream={item}
74+
isMuted={mutedUsers.includes(item.userId)}
7075
onNewCollection={() => createCollection([item.userId])}
7176
/>
7277
))}

src/common/stores.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,10 @@ export const stores = {
266266
status: null,
267267
},
268268
}),
269+
mutedUsers: new Store<string[]>("local", "mutedUsers", {
270+
schema: array(string()),
271+
defaultValue: [],
272+
}),
269273
};
270274

271275
browser.storage.onChanged.addListener((changes, areaName) => {

0 commit comments

Comments
 (0)