Skip to content

Commit

Permalink
feat(home): add options to collapse channels by default & display vid…
Browse files Browse the repository at this point in the history
…eos count per channel
  • Loading branch information
AXeL-dev committed Nov 5, 2022
1 parent 5e67fdc commit f9b31b0
Show file tree
Hide file tree
Showing 16 changed files with 311 additions and 23 deletions.
51 changes: 51 additions & 0 deletions src/providers/ChannelOptionsProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { createContext, FC, memo, useContext, useMemo, useState } from 'react';
import { useAppSelector } from 'store';
import { selectViewChannelOption } from 'store/selectors/settings';
import { HomeView } from 'types';

type ChannelOptionsContextType = {
collapseByDefault: boolean;
collapsed: boolean;
setCollapsed: (value: boolean) => void;
};

const ChannelOptionsContext = createContext<
ChannelOptionsContextType | undefined
>(undefined);

export const ChannelOptionsProvider: FC<{ view: HomeView }> = memo(
({ view, children }) => {
const collapseByDefault = useAppSelector(
selectViewChannelOption(view, 'collapseByDefault'),
);
const [collapsed, setCollapsed] = useState(collapseByDefault);

const value = useMemo(
() => ({
collapseByDefault,
collapsed: collapseByDefault && collapsed,
setCollapsed,
}),
[collapseByDefault, collapsed],
);

return (
<ChannelOptionsContext.Provider value={value}>
{children}
</ChannelOptionsContext.Provider>
);
},
);

export function useChannelOptions(): ChannelOptionsContextType {
const context = useContext(ChannelOptionsContext);

if (context === undefined) {
throw new Error(
'useChannelOptions must be used within a ChannelOptionsContext',
);
}

return context;
}
14 changes: 13 additions & 1 deletion src/providers/ChannelVideosProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type ChannelVideosContextType = {
view: HomeView,
channelId: string,
) => Video | undefined;
getChannelVideosCount: (view: HomeView, channelId: string) => number;
};

const initialVideosCount = views.reduce(
Expand Down Expand Up @@ -97,7 +98,13 @@ export const ChannelVideosProvider: FC = memo(({ children }) => {
};

const getLatestChannelVideo = (view: HomeView, channelId: string) => {
return channelsMap.current[view].get(channelId)?.items[0];
const channelData = channelsMap.current[view].get(channelId);
return channelData?.items[0];
};

const getChannelVideosCount = (view: HomeView, channelId: string) => {
const channelData = channelsMap.current[view].get(channelId);
return channelData?.total || 0;
};

const value = useMemo(
Expand All @@ -106,6 +113,7 @@ export const ChannelVideosProvider: FC = memo(({ children }) => {
setChannelData,
clearChannelsData,
getLatestChannelVideo,
getChannelVideosCount,
}),
[videosCount],
);
Expand All @@ -122,6 +130,7 @@ type ChannelVideosHookType = {
setChannelData: (data: ChannelData) => void;
clearChannelsData: () => void;
getLatestChannelVideo: (channelId: string) => Video | undefined;
getChannelVideosCount: (channelId: string) => number;
};

export function useChannelVideos(view: HomeView): ChannelVideosHookType {
Expand All @@ -138,6 +147,7 @@ export function useChannelVideos(view: HomeView): ChannelVideosHookType {
setChannelData,
clearChannelsData,
getLatestChannelVideo,
getChannelVideosCount,
} = context;

return {
Expand All @@ -146,5 +156,7 @@ export function useChannelVideos(view: HomeView): ChannelVideosHookType {
clearChannelsData: () => clearChannelsData(view),
getLatestChannelVideo: (channelId) =>
getLatestChannelVideo(view, channelId),
getChannelVideosCount: (channelId) =>
getChannelVideosCount(view, channelId),
};
}
1 change: 1 addition & 0 deletions src/providers/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './ChannelVideosProvider';
export * from './ChannelOptionsProvider';
29 changes: 29 additions & 0 deletions src/store/reducers/settings.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import {
ChannelOptions,
HomeDisplayOptions,
HomeView,
QueryTimeout,
Expand Down Expand Up @@ -30,6 +31,10 @@ export const defaultSettings = {
ignored: false,
others: true,
},
channels: {
collapseByDefault: false,
displayVideosCount: false,
},
videosSeniority: VideosSeniority.Any,
},
[HomeView.WatchLater]: {
Expand All @@ -42,6 +47,10 @@ export const defaultSettings = {
archived: true,
others: true,
},
channels: {
collapseByDefault: false,
displayVideosCount: false,
},
videosSeniority: VideosSeniority.Any,
},
[HomeView.Bookmarks]: {
Expand All @@ -53,6 +62,10 @@ export const defaultSettings = {
watchLater: true,
others: true,
},
channels: {
collapseByDefault: false,
displayVideosCount: false,
},
videosSeniority: VideosSeniority.Any,
},
},
Expand Down Expand Up @@ -101,6 +114,21 @@ export const settingsSlice = createSlice({
};
}
},
setViewChannelOptions: (
state,
action: PayloadAction<{
view: HomeView;
options: Partial<ChannelOptions>;
}>,
) => {
const { view, options } = action.payload;
if (state.viewOptions[view]) {
state.viewOptions[view].channels = {
...state.viewOptions[view].channels,
...options,
};
}
},
setViewSorting: (
state,
action: PayloadAction<{
Expand Down Expand Up @@ -154,6 +182,7 @@ export const {
setViewFilters,
setViewSorting,
setVideosSeniority,
setViewChannelOptions,
setHomeDisplayOptions,
} = settingsSlice.actions;

Expand Down
16 changes: 15 additions & 1 deletion src/store/selectors/settings.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { RootState } from 'store';
import { createSelector } from 'reselect';
import { HomeView, ViewFilters, ExtraVideoAction } from 'types';
import { HomeView, ViewFilters, ExtraVideoAction, ChannelOptions } from 'types';
import { defaultSettings } from 'store/reducers/settings';

export const selectSettings = (state: RootState) => state.settings;
Expand All @@ -17,6 +17,20 @@ export const selectViewFilters = (view: HomeView) =>
} as ViewFilters;
});

export const selectViewChannelOptions = (view: HomeView) =>
createSelector(selectSettings, (settings): ChannelOptions => {
return {
...defaultSettings.viewOptions[view].channels,
...settings.viewOptions[view].channels,
} as ChannelOptions;
});

export const selectViewChannelOption = (
view: HomeView,
option: keyof ChannelOptions,
) =>
createSelector(selectViewChannelOptions(view), (options) => options[option]);

const legacyViewFilterKeys = ['uncategorised', 'viewed'];

export const getActiveViewFilters = (filters: ViewFilters) =>
Expand Down
3 changes: 3 additions & 0 deletions src/store/utils/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,16 +66,19 @@ const replaceLegacySettings = (
...rest,
viewOptions: {
[HomeView.All]: {
...defaultSettings.viewOptions[HomeView.All],
sorting: recentViewSorting,
filters: recentViewFilters,
videosSeniority: recentVideosSeniority,
},
[HomeView.WatchLater]: {
...defaultSettings.viewOptions[HomeView.WatchLater],
sorting: watchLaterViewSorting,
filters: watchLaterViewFilters,
videosSeniority: VideosSeniority.Any,
},
[HomeView.Bookmarks]: {
...defaultSettings.viewOptions[HomeView.Bookmarks],
sorting: bookmarksViewSorting,
filters: bookmarksViewFilters,
videosSeniority: VideosSeniority.Any,
Expand Down
8 changes: 8 additions & 0 deletions src/types/Settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,19 @@ export interface Settings {
[HomeView.All]: {
sorting: ViewSorting;
filters: AllViewFilters;
channels: ChannelOptions;
videosSeniority: VideosSeniority;
};
[HomeView.WatchLater]: {
sorting: ViewSorting;
filters: WatchLaterViewFilters;
channels: ChannelOptions;
videosSeniority: VideosSeniority;
};
[HomeView.Bookmarks]: {
sorting: ViewSorting;
filters: BookmarksViewFilters;
channels: ChannelOptions;
videosSeniority: VideosSeniority;
};
};
Expand All @@ -31,6 +34,11 @@ export interface Settings {
queryTimeout: number;
}

export interface ChannelOptions {
collapseByDefault: boolean;
displayVideosCount: boolean;
}

export enum ExtraVideoAction {
CopyLink = 'copyLink',
}
Expand Down
45 changes: 34 additions & 11 deletions src/ui/components/pages/Home/ChannelRenderer/ChannelRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Box } from '@mui/material';
import { Channel, HomeView, Video } from 'types';
import ChannelTitle from './ChannelTitle';
import ChannelVideos from './ChannelVideos';
import { ChannelOptionsProvider, useChannelOptions } from 'providers';

export interface ChannelRendererProps {
view: HomeView;
Expand All @@ -18,12 +19,21 @@ export interface ChannelRendererProps {
}

function ChannelRenderer(props: ChannelRendererProps) {
const { channel, videos, count, total, isLoading, maxResults, ...rest } =
props;
const {
view,
channel,
videos,
count,
total,
isLoading,
maxResults,
...rest
} = props;
const videosCount = count || videos.length;
const hasVideos = isLoading || videos.length > 0;
const hasMore =
videosCount > 0 && videosCount >= maxResults && total > maxResults;
const { collapsed } = useChannelOptions();

return hasVideos ? (
<Box
Expand All @@ -33,18 +43,31 @@ function ChannelRenderer(props: ChannelRendererProps) {
pb: 3,
}}
>
<ChannelTitle channel={channel} />
<ChannelVideos
videos={videos}
isLoading={isLoading}
maxResults={maxResults}
hasMore={hasMore}
{...rest}
/>
<ChannelTitle view={view} channel={channel} />
{!collapsed ? (
<ChannelVideos
view={view}
videos={videos}
isLoading={isLoading}
maxResults={maxResults}
hasMore={hasMore}
{...rest}
/>
) : null}
</Box>
) : null;
}

function ChannelRendererWrapper(props: ChannelRendererProps) {
const { view } = props;

return (
<ChannelOptionsProvider view={view}>
<ChannelRenderer {...props} />
</ChannelOptionsProvider>
);
}

function propsAreEqual(
prevProps: ChannelRendererProps,
nextProps: ChannelRendererProps,
Expand All @@ -61,4 +84,4 @@ function propsAreEqual(
);
}

export default React.memo(ChannelRenderer, propsAreEqual);
export default React.memo(ChannelRendererWrapper, propsAreEqual);
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react';
import { Avatar, Badge } from '@mui/material';
import { Channel, HomeView } from 'types';
import { useAppSelector } from 'store';
import { selectViewChannelOption } from 'store/selectors/settings';
import { useChannelVideos } from 'providers';

interface ChannelAvatarProps {
view: HomeView;
channel: Channel;
}

function ChannelAvatar(props: ChannelAvatarProps) {
const { view, channel } = props;
const displayVideosCount = useAppSelector(
selectViewChannelOption(view, 'displayVideosCount'),
);
const { getChannelVideosCount } = useChannelVideos(view);
const videosCount = displayVideosCount
? getChannelVideosCount(channel.id)
: null;

return (
<Badge
color="primary"
badgeContent={videosCount}
title={`${videosCount || ''}`}
>
<Avatar alt={channel.title} src={channel.thumbnail} />
</Badge>
);
}

export default ChannelAvatar;
Loading

0 comments on commit f9b31b0

Please sign in to comment.