split between userConfig and userAccount

This commit is contained in:
Simon
2025-02-13 21:43:01 +07:00
parent ad7af09159
commit d96ac486e4
30 changed files with 640 additions and 180 deletions

View File

@@ -1,17 +1,6 @@
import { SortByType, SortOrderType, ViewLayoutType } from '../../pages/Home';
import APIClient from '../../functions/APIClient';
export type UserMeType = {
id: number;
name: string;
is_superuser: boolean;
is_staff: boolean;
groups: [];
user_permissions: [];
last_login: string;
config: UserConfigType;
};
export type ColourVariants = 'dark.css' | 'light.css' | 'matrix.css' | 'midnight.css';
export type UserConfigType = {
@@ -33,7 +22,7 @@ export type UserConfigType = {
const updateUserConfig = async (config: Partial<UserConfigType>): Promise<UserConfigType> => {
return APIClient('/api/user/me/', {
method: 'POST',
body: { config: config },
body: config,
});
};

View File

@@ -0,0 +1,8 @@
import APIClient from '../../functions/APIClient';
import { UserAccountType } from '../../pages/Base';
const loadUserAccount = async (): Promise<UserAccountType> => {
return APIClient('/api/user/account/');
};
export default loadUserAccount;

View File

@@ -1,7 +1,7 @@
import { UserMeType } from '../actions/updateUserConfig';
import { UserConfigType } from '../actions/updateUserConfig';
import APIClient from '../../functions/APIClient';
const loadUserMeConfig = async (): Promise<UserMeType> => {
const loadUserMeConfig = async (): Promise<UserConfigType> => {
return APIClient('/api/user/me/');
};

View File

@@ -16,7 +16,7 @@ type ChannelListProps = {
const ChannelList = ({ channelList, refreshChannelList }: ChannelListProps) => {
const { userConfig } = useUserConfigStore();
const viewLayout = userConfig.config.view_style_channel;
const viewLayout = userConfig.view_style_channel;
if (!channelList || channelList.length === 0) {
return <p>No channels found.</p>;

View File

@@ -16,8 +16,8 @@ type DownloadListItemProps = {
const DownloadListItem = ({ download, setRefresh }: DownloadListItemProps) => {
const { userConfig } = useUserConfigStore();
const view = userConfig.config.view_style_downloads;
const showIgnored = userConfig.config.show_ignored_only;
const view = userConfig.view_style_downloads;
const showIgnored = userConfig.show_ignored_only;
const [hideDownload, setHideDownload] = useState(false);

View File

@@ -69,6 +69,7 @@ const EmbeddableVideoPlayer = ({ videoId }: EmbeddableVideoPlayerProps) => {
setRefresh(false);
}
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [videoId, refresh]);
useEffect(() => {

View File

@@ -7,6 +7,7 @@ import iconListView from '/img/icon-listview.svg';
import { SortByType, SortOrderType } from '../pages/Home';
import { useUserConfigStore } from '../stores/UserConfigStore';
import { ViewStyles } from '../configuration/constants/ViewStyle';
import updateUserConfig, { UserConfigType } from '../api/actions/updateUserConfig';
type FilterbarProps = {
hideToggleText: string;
@@ -15,9 +16,14 @@ type FilterbarProps = {
};
const Filterbar = ({ hideToggleText, viewStyleName, showSort = true }: FilterbarProps) => {
const { userConfig, setPartialConfig } = useUserConfigStore();
const { userConfig, setUserConfig } = useUserConfigStore();
const [showHidden, setShowHidden] = useState(false);
const isGridView = userConfig.config.view_style_home === ViewStyles.grid;
const isGridView = userConfig.view_style_home === ViewStyles.grid;
const handleUserConfigUpdate = async (config: Partial<UserConfigType>) => {
const updatedUserConfig = await updateUserConfig(config);
setUserConfig(updatedUserConfig);
};
return (
<div className="view-controls three">
@@ -27,13 +33,13 @@ const Filterbar = ({ hideToggleText, viewStyleName, showSort = true }: Filterbar
<input
id="hide_watched"
type="checkbox"
checked={userConfig.config.hide_watched}
checked={userConfig.hide_watched}
onChange={() => {
setPartialConfig({ hide_watched: !userConfig.config.hide_watched });
handleUserConfigUpdate({ hide_watched: !userConfig.hide_watched });
}}
/>
{userConfig.config.hide_watched ? (
{userConfig.hide_watched ? (
<label htmlFor="" className="onbtn">
On
</label>
@@ -52,9 +58,9 @@ const Filterbar = ({ hideToggleText, viewStyleName, showSort = true }: Filterbar
<select
name="sort_by"
id="sort"
value={userConfig.config.sort_by}
value={userConfig.sort_by}
onChange={event => {
setPartialConfig({ sort_by: event.target.value as SortByType });
handleUserConfigUpdate({ sort_by: event.target.value as SortByType });
}}
>
<option value="published">date published</option>
@@ -67,9 +73,9 @@ const Filterbar = ({ hideToggleText, viewStyleName, showSort = true }: Filterbar
<select
name="sort_order"
id="sort-order"
value={userConfig.config.sort_order}
value={userConfig.sort_order}
onChange={event => {
setPartialConfig({ sort_order: event.target.value as SortOrderType });
handleUserConfigUpdate({ sort_order: event.target.value as SortOrderType });
}}
>
<option value="asc">asc</option>
@@ -90,22 +96,22 @@ const Filterbar = ({ hideToggleText, viewStyleName, showSort = true }: Filterbar
/>
)}
{userConfig.config.grid_items !== undefined && isGridView && (
{userConfig.grid_items !== undefined && isGridView && (
<div className="grid-count">
{userConfig.config.grid_items < 7 && (
{userConfig.grid_items < 7 && (
<img
src={iconAdd}
onClick={() => {
setPartialConfig({ grid_items: userConfig.config.grid_items + 1 });
handleUserConfigUpdate({ grid_items: userConfig.grid_items + 1 });
}}
alt="grid plus row"
/>
)}
{userConfig.config.grid_items > 3 && (
{userConfig.grid_items > 3 && (
<img
src={iconSubstract}
onClick={() => {
setPartialConfig({ grid_items: userConfig.config.grid_items - 1 });
handleUserConfigUpdate({ grid_items: userConfig.grid_items - 1 });
}}
alt="grid minus row"
/>
@@ -115,14 +121,14 @@ const Filterbar = ({ hideToggleText, viewStyleName, showSort = true }: Filterbar
<img
src={iconGridView}
onClick={() => {
setPartialConfig({ [viewStyleName]: 'grid' });
handleUserConfigUpdate({ [viewStyleName]: 'grid' });
}}
alt="grid view"
/>
<img
src={iconListView}
onClick={() => {
setPartialConfig({ [viewStyleName]: 'list' });
handleUserConfigUpdate({ [viewStyleName]: 'list' });
}}
alt="list view"
/>

View File

@@ -93,8 +93,10 @@ const GoogleCast = ({ video, setRefresh, onWatchStateChanged }: GoogleCastProps)
const [isConnected, setIsConnected] = useState(false);
const setup = useCallback(() => {
const cast = globalThis.cast;
const chrome = globalThis.chrome;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const cast = (globalThis as any).cast;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const chrome = (globalThis as any).chrome;
cast.framework.CastContext.getInstance().setOptions({
receiverApplicationId: chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID, // Use built in receiver app on cast device, see https://developers.google.com/cast/docs/styled_receiver if you want to be able to add a theme, splash screen or watermark. Has a $5 one time fee.
@@ -132,8 +134,10 @@ const GoogleCast = ({ video, setRefresh, onWatchStateChanged }: GoogleCastProps)
}, [setRefresh, video]);
const startPlayback = useCallback(() => {
const chrome = globalThis.chrome;
const cast = globalThis.cast;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const chrome = (globalThis as any).chrome;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const cast = (globalThis as any).cast;
const castSession = cast.framework.CastContext.getInstance().getCurrentSession();
const mediaUrl = video?.media_url;

View File

@@ -14,7 +14,7 @@ type PlaylistListProps = {
const PlaylistList = ({ playlistList, setRefresh }: PlaylistListProps) => {
const { userConfig } = useUserConfigStore();
const viewLayout = userConfig.config.view_style_playlist;
const viewLayout = userConfig.view_style_playlist;
if (!playlistList || playlistList.length === 0) {
return <p>No playlists found.</p>;

View File

@@ -59,7 +59,6 @@ const Subtitles = ({ subtitles }: SubtitlesProp) => {
const handleTimeUpdate =
(
youtubeId: string,
duration: number,
watched: boolean,
sponsorBlock?: SponsorBlockType,
setSponsorSegmentSkipped?: Dispatch<SetStateAction<SponsorSegmentsSkippedType>>,
@@ -337,7 +336,6 @@ const VideoPlayer = ({
}}
onTimeUpdate={handleTimeUpdate(
videoId,
duration,
watched,
sponsorBlock,
setSkippedSegments,

View File

@@ -9,7 +9,7 @@ export const ColourConstant = {
const useColours = () => {
const { userConfig } = useUserConfigStore();
const stylesheet = userConfig?.config.stylesheet;
const stylesheet = userConfig?.stylesheet;
switch (stylesheet) {
case ColourConstant.Dark:

View File

@@ -1,8 +1,8 @@
import { useUserConfigStore } from '../stores/UserConfigStore';
import { useUserAccountStore } from '../stores/UserAccountStore';
const useIsAdmin = () => {
const { userConfig } = useUserConfigStore();
const isAdmin = userConfig?.is_staff || userConfig?.is_superuser;
const { userAccount } = useUserAccountStore();
const isAdmin = userAccount?.is_staff || userAccount?.is_superuser;
return isAdmin;
};

View File

@@ -25,6 +25,7 @@ import ChannelVideo from './pages/ChannelVideo';
import ChannelPlaylist from './pages/ChannelPlaylist';
import ChannelAbout from './pages/ChannelAbout';
import Download from './pages/Download';
import loadUserAccount from './api/loader/loadUserAccount';
const router = createBrowserRouter(
[
@@ -41,8 +42,9 @@ const router = createBrowserRouter(
const authData = await auth.json();
const userConfig = await loadUserMeConfig();
const userAccount = await loadUserAccount();
return { userConfig, auth: authData };
return { userConfig, userAccount, auth: authData };
},
element: <Base />,
errorElement: <ErrorPage />,

View File

@@ -1,11 +1,12 @@
import { Outlet, useLoaderData, useLocation, useSearchParams } from 'react-router-dom';
import Footer from '../components/Footer';
import useColours from '../configuration/colours/useColours';
import { UserMeType } from '../api/actions/updateUserConfig';
import { UserConfigType } from '../api/actions/updateUserConfig';
import { useEffect, useState } from 'react';
import Navigation from '../components/Navigation';
import { useAuthStore } from '../stores/AuthDataStore';
import { useUserConfigStore } from '../stores/UserConfigStore';
import { useUserAccountStore } from '../stores/UserAccountStore';
export type TaUpdateType = {
version?: string;
@@ -19,8 +20,19 @@ export type AuthenticationType = {
ta_update: TaUpdateType;
};
export type UserAccountType = {
id: number;
name: string;
is_superuser: boolean;
is_staff: boolean;
groups: [];
user_permissions: [];
last_login: string;
};
type BaseLoaderData = {
userConfig: UserMeType;
userConfig: UserConfigType;
userAccount: UserAccountType;
auth: AuthenticationType;
};
@@ -32,7 +44,8 @@ export type OutletContextType = {
const Base = () => {
const { setAuth } = useAuthStore();
const { setUserConfig } = useUserConfigStore();
const { userConfig, auth } = useLoaderData() as BaseLoaderData;
const { setUserAccount } = useUserAccountStore();
const { userConfig, userAccount, auth } = useLoaderData() as BaseLoaderData;
const location = useLocation();
@@ -46,6 +59,7 @@ const Base = () => {
useEffect(() => {
setAuth(auth);
setUserConfig(userConfig);
setUserAccount(userAccount);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

View File

@@ -244,7 +244,7 @@ const ChannelAbout = () => {
<div className="info-box">
<div className="info-box-item">
<h2>Channel Customization</h2>
{userConfig.config.show_help_text && (
{userConfig.show_help_text && (
<div className="help-text">
<ul>
<li>Overwrite the download format over the format set globally.</li>
@@ -334,7 +334,7 @@ const ChannelAbout = () => {
</div>
<div className="info-box-item">
<h2>Page Size Overrides</h2>
{userConfig.config.show_help_text && (
{userConfig.show_help_text && (
<div className="help-text">
<p>
Disable standard videos, shorts, or streams for this channel by setting their

View File

@@ -10,10 +10,11 @@ import { PlaylistsResponseType } from './Playlists';
import iconGridView from '/img/icon-gridview.svg';
import iconListView from '/img/icon-listview.svg';
import { useUserConfigStore } from '../stores/UserConfigStore';
import updateUserConfig, { UserConfigType } from '../api/actions/updateUserConfig';
const ChannelPlaylist = () => {
const { channelId } = useParams();
const { userConfig, setPartialConfig } = useUserConfigStore();
const { userConfig, setUserConfig } = useUserConfigStore();
const { currentPage, setCurrentPage } = useOutletContext() as OutletContextType;
const [refreshPlaylists, setRefreshPlaylists] = useState(false);
@@ -23,8 +24,13 @@ const ChannelPlaylist = () => {
const playlistList = playlistsResponse?.data;
const pagination = playlistsResponse?.paginate;
const view = userConfig.config.view_style_playlist;
const showSubedOnly = userConfig.config.show_subed_only;
const view = userConfig.view_style_playlist;
const showSubedOnly = userConfig.show_subed_only;
const handleUserConfigUpdate = async (config: Partial<UserConfigType>) => {
const updatedUserConfig = await updateUserConfig(config);
setUserConfig(updatedUserConfig);
};
useEffect(() => {
(async () => {
@@ -52,7 +58,7 @@ const ChannelPlaylist = () => {
<input
checked={showSubedOnly}
onChange={() => {
setPartialConfig({ show_subed_only: !showSubedOnly });
handleUserConfigUpdate({ show_subed_only: !showSubedOnly });
setRefreshPlaylists(true);
}}
type="checkbox"
@@ -73,14 +79,14 @@ const ChannelPlaylist = () => {
<img
src={iconGridView}
onClick={() => {
setPartialConfig({ view_style_playlist: 'grid' });
handleUserConfigUpdate({ view_style_playlist: 'grid' });
}}
alt="grid view"
/>
<img
src={iconListView}
onClick={() => {
setPartialConfig({ view_style_playlist: 'list' });
handleUserConfigUpdate({ view_style_playlist: 'list' });
}}
alt="list view"
/>

View File

@@ -49,10 +49,10 @@ const ChannelVideo = ({ videoType }: ChannelVideoProps) => {
const hasVideos = videoResponse?.data?.length !== 0;
const showEmbeddedVideo = videoId !== null;
const view = userConfig.config.view_style_home;
const view = userConfig.view_style_home;
const isGridView = view === ViewStyles.grid;
const gridView = isGridView ? `boxed-${userConfig.config.grid_items}` : '';
const gridViewGrid = isGridView ? `grid-${userConfig.config.grid_items}` : '';
const gridView = isGridView ? `boxed-${userConfig.grid_items}` : '';
const gridViewGrid = isGridView ? `grid-${userConfig.grid_items}` : '';
useEffect(() => {
(async () => {
@@ -60,9 +60,9 @@ const ChannelVideo = ({ videoType }: ChannelVideoProps) => {
const videos = await loadVideoListByFilter({
channel: channelId,
page: currentPage,
watch: userConfig.config.hide_watched ? 'unwatched' : undefined,
sort: userConfig.config.sort_by,
order: userConfig.config.sort_order,
watch: userConfig.hide_watched ? 'unwatched' : undefined,
sort: userConfig.sort_by,
order: userConfig.sort_order,
type: videoType,
});
const channelAggs = await loadChannelAggs(channelId);
@@ -74,9 +74,9 @@ const ChannelVideo = ({ videoType }: ChannelVideoProps) => {
})();
}, [
refresh,
userConfig.config.sort_by,
userConfig.config.sort_order,
userConfig.config.hide_watched,
userConfig.sort_by,
userConfig.sort_order,
userConfig.hide_watched,
currentPage,
channelId,
pagination?.current_page,

View File

@@ -14,6 +14,7 @@ import Button from '../components/Button';
import updateBulkChannelSubscriptions from '../api/actions/updateBulkChannelSubscriptions';
import useIsAdmin from '../functions/useIsAdmin';
import { useUserConfigStore } from '../stores/UserConfigStore';
import updateUserConfig, { UserConfigType } from '../api/actions/updateUserConfig';
type ChannelOverwritesType = {
download_format?: string;
@@ -48,7 +49,7 @@ type ChannelsListResponse = {
};
const Channels = () => {
const { userConfig, setPartialConfig } = useUserConfigStore();
const { userConfig, setUserConfig } = useUserConfigStore();
const { currentPage, setCurrentPage } = useOutletContext() as OutletContextType;
const isAdmin = useIsAdmin();
@@ -63,18 +64,20 @@ const Channels = () => {
// const channelCount = pagination?.total_hits;
const hasChannels = channels?.length !== 0;
const handleUserConfigUpdate = async (config: Partial<UserConfigType>) => {
const updatedUserConfig = await updateUserConfig(config);
setUserConfig(updatedUserConfig);
};
useEffect(() => {
(async () => {
const channelListResponse = await loadChannelList(
currentPage,
userConfig.config.show_subed_only,
);
const channelListResponse = await loadChannelList(currentPage, userConfig.show_subed_only);
setChannelListResponse(channelListResponse);
setShowNotification(false);
setRefresh(false);
})();
}, [refresh, userConfig.config.show_subed_only, currentPage, pagination?.current_page]);
}, [refresh, userConfig.show_subed_only, currentPage, pagination?.current_page]);
return (
<>
@@ -145,18 +148,18 @@ const Channels = () => {
<input
id="show_subed_only"
onChange={async () => {
setPartialConfig({ show_subed_only: !userConfig.config.show_subed_only });
handleUserConfigUpdate({ show_subed_only: !userConfig.show_subed_only });
setRefresh(true);
}}
type="checkbox"
checked={userConfig.config.show_subed_only}
checked={userConfig.show_subed_only}
/>
{!userConfig.config.show_subed_only && (
{!userConfig.show_subed_only && (
<label htmlFor="" className="ofbtn">
Off
</label>
)}
{userConfig.config.show_subed_only && (
{userConfig.show_subed_only && (
<label htmlFor="" className="onbtn">
On
</label>
@@ -167,7 +170,7 @@ const Channels = () => {
<img
src={iconGridView}
onClick={() => {
setPartialConfig({ view_style_channel: 'grid' });
handleUserConfigUpdate({ view_style_channel: 'grid' });
}}
data-origin="channel"
data-value="grid"
@@ -176,7 +179,7 @@ const Channels = () => {
<img
src={iconListView}
onClick={() => {
setPartialConfig({ view_style_channel: 'list' });
handleUserConfigUpdate({ view_style_channel: 'list' });
}}
data-origin="channel"
data-value="list"
@@ -186,7 +189,7 @@ const Channels = () => {
</div>
{/* {hasChannels && <h2>Total channels: {channelCount}</h2>} */}
<div className={`channel-list ${userConfig.config.view_style_channel}`}>
<div className={`channel-list ${userConfig.view_style_channel}`}>
{!hasChannels && <h2>No channels found...</h2>}
{hasChannels && <ChannelList channelList={channels} refreshChannelList={setRefresh} />}

View File

@@ -19,6 +19,7 @@ import Button from '../components/Button';
import DownloadListItem from '../components/DownloadListItem';
import loadDownloadAggs, { DownloadAggsType } from '../api/loader/loadDownloadAggs';
import { useUserConfigStore } from '../stores/UserConfigStore';
import updateUserConfig, { UserConfigType } from '../api/actions/updateUserConfig';
type Download = {
auto_start: boolean;
@@ -46,7 +47,7 @@ export type DownloadResponseType = {
const Download = () => {
const [searchParams, setSearchParams] = useSearchParams();
const { userConfig, setPartialConfig } = useUserConfigStore();
const { userConfig, setUserConfig } = useUserConfigStore();
const { currentPage, setCurrentPage } = useOutletContext() as OutletContextType;
const channelFilterFromUrl = searchParams.get('channel');
@@ -75,13 +76,18 @@ const Download = () => {
? downloadResponse?.data[0].channel_name
: '';
const view = userConfig.config.view_style_downloads;
const gridItems = userConfig.config.grid_items;
const showIgnored = userConfig.config.show_ignored_only;
const view = userConfig.view_style_downloads;
const gridItems = userConfig.grid_items;
const showIgnored = userConfig.show_ignored_only;
const isGridView = view === ViewStyles.grid;
const gridView = isGridView ? `boxed-${gridItems}` : '';
const gridViewGrid = isGridView ? `grid-${gridItems}` : '';
const handleUserConfigUpdate = async (config: Partial<UserConfigType>) => {
const updatedUserConfig = await updateUserConfig(config);
setUserConfig(updatedUserConfig);
};
useEffect(() => {
(async () => {
const videos = await loadDownloadQueue(currentPage, channelFilterFromUrl, showIgnored);
@@ -208,7 +214,7 @@ const Download = () => {
<input
id="showIgnored"
onChange={() => {
setPartialConfig({ show_ignored_only: !showIgnored });
handleUserConfigUpdate({ show_ignored_only: !showIgnored });
setRefresh(true);
}}
type="checkbox"
@@ -263,7 +269,7 @@ const Download = () => {
<img
src={iconAdd}
onClick={() => {
setPartialConfig({ grid_items: gridItems + 1 });
handleUserConfigUpdate({ grid_items: gridItems + 1 });
}}
alt="grid plus row"
/>
@@ -272,7 +278,7 @@ const Download = () => {
<img
src={iconSubstract}
onClick={() => {
setPartialConfig({ grid_items: gridItems - 1 });
handleUserConfigUpdate({ grid_items: gridItems - 1 });
}}
alt="grid minus row"
/>
@@ -283,14 +289,14 @@ const Download = () => {
<img
src={iconGridView}
onClick={() => {
setPartialConfig({ view_style_downloads: 'grid' });
handleUserConfigUpdate({ view_style_downloads: 'grid' });
}}
alt="grid view"
/>
<img
src={iconListView}
onClick={() => {
setPartialConfig({ view_style_downloads: 'list' });
handleUserConfigUpdate({ view_style_downloads: 'list' });
}}
alt="list view"
/>

View File

@@ -108,8 +108,6 @@ const Home = () => {
const [searchParams] = useSearchParams();
const videoId = searchParams.get('videoId');
const userMeConfig = userConfig.config;
const [refreshVideoList, setRefreshVideoList] = useState(false);
const [videoResponse, setVideoReponse] = useState<VideoListByFilterResponseType>();
@@ -123,17 +121,17 @@ const Home = () => {
const hasVideos = videoResponse?.data?.length !== 0;
const showEmbeddedVideo = videoId !== null;
const isGridView = userMeConfig.view_style_home === ViewStyles.grid;
const gridView = isGridView ? `boxed-${userMeConfig.grid_items}` : '';
const gridViewGrid = isGridView ? `grid-${userMeConfig.grid_items}` : '';
const isGridView = userConfig.view_style_home === ViewStyles.grid;
const gridView = isGridView ? `boxed-${userConfig.grid_items}` : '';
const gridViewGrid = isGridView ? `grid-${userConfig.grid_items}` : '';
useEffect(() => {
(async () => {
const videos = await loadVideoListByFilter({
page: currentPage,
watch: userMeConfig.hide_watched ? 'unwatched' : undefined,
sort: userMeConfig.sort_by,
order: userMeConfig.sort_order,
watch: userConfig.hide_watched ? 'unwatched' : undefined,
sort: userConfig.sort_by,
order: userConfig.sort_order,
});
try {
@@ -150,9 +148,9 @@ const Home = () => {
})();
}, [
refreshVideoList,
userMeConfig.sort_by,
userMeConfig.sort_order,
userMeConfig.hide_watched,
userConfig.sort_by,
userConfig.sort_order,
userConfig.hide_watched,
currentPage,
pagination?.current_page,
showEmbeddedVideo,
@@ -171,10 +169,10 @@ const Home = () => {
<div className="title-bar">
<h1>Continue Watching</h1>
</div>
<div className={`video-list ${userMeConfig.view_style_home} ${gridViewGrid}`}>
<div className={`video-list ${userConfig.view_style_home} ${gridViewGrid}`}>
<VideoList
videoList={continueVideos}
viewLayout={userMeConfig.view_style_home}
viewLayout={userConfig.view_style_home}
refreshVideoList={setRefreshVideoList}
/>
</div>
@@ -189,7 +187,7 @@ const Home = () => {
</div>
<div className={`boxed-content ${gridView}`}>
<div className={`video-list ${userMeConfig.view_style_home} ${gridViewGrid}`}>
<div className={`video-list ${userConfig.view_style_home} ${gridViewGrid}`}>
{!hasVideos && (
<>
<h2>No videos found...</h2>
@@ -204,7 +202,7 @@ const Home = () => {
{hasVideos && (
<VideoList
videoList={videoList}
viewLayout={userMeConfig.view_style_home}
viewLayout={userConfig.view_style_home}
refreshVideoList={setRefreshVideoList}
/>
)}

View File

@@ -81,9 +81,9 @@ const Playlist = () => {
const videoInPlaylistCount = pagination?.total_hits;
const showEmbeddedVideo = videoId !== null;
const view = userConfig.config.view_style_home;
const gridItems = userConfig.config.grid_items;
const hideWatched = userConfig.config.hide_watched;
const view = userConfig.view_style_home;
const gridItems = userConfig.grid_items;
const hideWatched = userConfig.hide_watched;
const isGridView = view === ViewStyles.grid;
const gridView = isGridView ? `boxed-${gridItems}` : '';
const gridViewGrid = isGridView ? `grid-${gridItems}` : '';
@@ -112,7 +112,7 @@ const Playlist = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
playlistId,
userConfig.config.hide_watched,
userConfig.hide_watched,
refresh,
currentPage,
pagination?.current_page,

View File

@@ -18,6 +18,7 @@ import Button from '../components/Button';
import useIsAdmin from '../functions/useIsAdmin';
import { useUserConfigStore } from '../stores/UserConfigStore';
import Notifications from '../components/Notifications';
import updateUserConfig, { UserConfigType } from '../api/actions/updateUserConfig';
export type PlaylistEntryType = {
youtube_id: string;
@@ -34,7 +35,7 @@ export type PlaylistsResponseType = {
};
const Playlists = () => {
const { userConfig, setPartialConfig } = useUserConfigStore();
const { userConfig, setUserConfig } = useUserConfigStore();
const { currentPage, setCurrentPage } = useOutletContext() as OutletContextType;
const isAdmin = useIsAdmin();
@@ -51,8 +52,8 @@ const Playlists = () => {
const hasPlaylists = playlistResponse?.data?.length !== 0;
const view = userConfig.config.view_style_playlist;
const showSubedOnly = userConfig.config.show_subed_only;
const view = userConfig.view_style_playlist;
const showSubedOnly = userConfig.show_subed_only;
useEffect(() => {
(async () => {
@@ -66,7 +67,12 @@ const Playlists = () => {
setShowNotification(false);
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [refresh, userConfig.config.show_subed_only, currentPage, pagination?.current_page]);
}, [refresh, userConfig.show_subed_only, currentPage, pagination?.current_page]);
const handleUserConfigUpdate = async (config: Partial<UserConfigType>) => {
const updatedUserConfig = await updateUserConfig(config);
setUserConfig(updatedUserConfig);
};
return (
<>
@@ -156,7 +162,7 @@ const Playlists = () => {
<input
checked={showSubedOnly}
onChange={() => {
setPartialConfig({ show_subed_only: !showSubedOnly });
handleUserConfigUpdate({ show_subed_only: !showSubedOnly });
}}
type="checkbox"
/>
@@ -176,14 +182,14 @@ const Playlists = () => {
<img
src={iconGridView}
onClick={() => {
setPartialConfig({ view_style_playlist: 'grid' });
handleUserConfigUpdate({ view_style_playlist: 'grid' });
}}
alt="grid view"
/>
<img
src={iconListView}
onClick={() => {
setPartialConfig({ view_style_playlist: 'list' });
handleUserConfigUpdate({ view_style_playlist: 'list' });
}}
alt="list view"
/>

View File

@@ -39,12 +39,11 @@ const Search = () => {
const { userConfig } = useUserConfigStore();
const [searchParams] = useSearchParams();
const videoId = searchParams.get('videoId');
const userMeConfig = userConfig.config;
const viewVideos = userMeConfig.view_style_home;
const viewChannels = userMeConfig.view_style_channel;
const viewPlaylists = userMeConfig.view_style_playlist;
const gridItems = userMeConfig.grid_items || 3;
const viewVideos = userConfig.view_style_home;
const viewChannels = userConfig.view_style_channel;
const viewPlaylists = userConfig.view_style_playlist;
const gridItems = userConfig.grid_items || 3;
const [searchTerm, setSearchTerm] = useState('');
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState<string>('');

View File

@@ -209,7 +209,7 @@ const SettingsApplication = () => {
<div className="info-box">
<div className="info-box-item">
<h2 id="subscriptions">Subscription Scan</h2>
{userConfig.config.show_help_text && (
{userConfig.show_help_text && (
<div className="help-text">
<p>Configure how a subscription Scan tracks videos.</p>
<ul>
@@ -275,7 +275,7 @@ const SettingsApplication = () => {
</div>
<div className="info-box-item">
<h2 id="downloads">Downloads</h2>
{userConfig.config.show_help_text && (
{userConfig.show_help_text && (
<div className="help-text">
<ul>
<li>
@@ -362,7 +362,7 @@ const SettingsApplication = () => {
</div>
<div className="info-box-item">
<h2 id="format">Download Format</h2>
{userConfig.config.show_help_text && (
{userConfig.show_help_text && (
<div className="help-text">
<ul>
<li>
@@ -507,7 +507,7 @@ const SettingsApplication = () => {
</div>
<div className="info-box-item">
<h2 id="subtitles">Subtitles</h2>
{userConfig.config.show_help_text && (
{userConfig.show_help_text && (
<div className="help-text">
<p>Additional subtitle options show once you choose a language.</p>
<ul>
@@ -586,7 +586,7 @@ const SettingsApplication = () => {
</div>
<div className="info-box-item">
<h2 id="comments">Comments</h2>
{userConfig.config.show_help_text && (
{userConfig.show_help_text && (
<div className="help-text">
<p>Additional options show once you set a comment index option.</p>
<ul>
@@ -650,7 +650,7 @@ const SettingsApplication = () => {
</div>
<div className="info-box-item">
<h2 id="cookie">Cookie</h2>
{userConfig.config.show_help_text && (
{userConfig.show_help_text && (
<div className="help-text">
<p>
Importing your cookie will authenticate requests to YT with your user account.
@@ -777,7 +777,7 @@ const SettingsApplication = () => {
</div>
<div className="info-box-item">
<h2 id="sntegrations">Integrations</h2>
{userConfig.config.show_help_text && (
{userConfig.show_help_text && (
<div className="help-text">
<ul>
<li>The API token is used to make automater requests to TA through the API.</li>
@@ -868,7 +868,7 @@ const SettingsApplication = () => {
</div>
<div className="info-box-item">
<h2>Snapshots</h2>
{userConfig.config.show_help_text && (
{userConfig.show_help_text && (
<div className="help-text">
<p>
Automatically create daily deduplicated snapshots of the index, stored in

View File

@@ -1,5 +1,5 @@
import { useNavigate } from 'react-router-dom';
import { ColourVariants } from '../api/actions/updateUserConfig';
import updateUserConfig, { ColourVariants, UserConfigType } from '../api/actions/updateUserConfig';
import { ColourConstant } from '../configuration/colours/useColours';
import SettingsNavigation from '../components/SettingsNavigation';
import Notifications from '../components/Notifications';
@@ -10,35 +10,40 @@ import { useEffect, useState } from 'react';
import ToggleConfig from '../components/ToggleConfig';
const SettingsUser = () => {
const { userConfig, setPartialConfig } = useUserConfigStore();
const { userConfig, setUserConfig } = useUserConfigStore();
const isAdmin = useIsAdmin();
const navigate = useNavigate();
const [styleSheet, setStyleSheet] = useState<ColourVariants>(userConfig.config.stylesheet);
const [styleSheet, setStyleSheet] = useState<ColourVariants>(userConfig.stylesheet);
const [styleSheetRefresh, setStyleSheetRefresh] = useState(false);
const [pageSize, setPageSize] = useState<number>(userConfig.config.page_size);
const [showHelpText, setShowHelpText] = useState(userConfig.config.show_help_text);
const [pageSize, setPageSize] = useState<number>(userConfig.page_size);
const [showHelpText, setShowHelpText] = useState(userConfig.show_help_text);
useEffect(() => {
(async () => {
setStyleSheet(userConfig.config.stylesheet);
setPageSize(userConfig.config.page_size);
setShowHelpText(userConfig.config.show_help_text);
setStyleSheet(userConfig.stylesheet);
setPageSize(userConfig.page_size);
setShowHelpText(userConfig.show_help_text);
})();
}, [userConfig.config.page_size, userConfig.config.stylesheet, userConfig.config.show_help_text]);
}, [userConfig.page_size, userConfig.stylesheet, userConfig.show_help_text]);
const handleStyleSheetChange = async (selectedStyleSheet: ColourVariants) => {
setPartialConfig({ stylesheet: selectedStyleSheet });
handleUserConfigUpdate({ stylesheet: selectedStyleSheet });
setStyleSheet(selectedStyleSheet);
setStyleSheetRefresh(true);
};
const handlePageSizeChange = async () => {
setPartialConfig({ page_size: pageSize });
handleUserConfigUpdate({ page_size: pageSize });
};
const handleShowHelpTextChange = async (configKey: string, configValue: boolean) => {
setPartialConfig({ [configKey]: configValue });
handleUserConfigUpdate({ [configKey]: configValue });
};
const handleUserConfigUpdate = async (config: Partial<UserConfigType>) => {
const updatedUserConfig = await updateUserConfig(config);
setUserConfig(updatedUserConfig);
};
const handlePageRefresh = () => {
@@ -99,12 +104,10 @@ const SettingsUser = () => {
/>
<div className="button-box">
{userConfig.config.page_size !== pageSize && (
{userConfig.page_size !== pageSize && (
<>
<button onClick={handlePageSizeChange}>Update</button>
<button onClick={() => setPageSize(userConfig.config.page_size)}>
Cancel
</button>
<button onClick={() => setPageSize(userConfig.page_size)}>Cancel</button>
</>
)}
</div>

View File

@@ -0,0 +1,12 @@
import { create } from 'zustand';
import { UserAccountType } from '../pages/Base';
interface AccountState {
userAccount: UserAccountType | null;
setUserAccount: (userAccount: UserAccountType) => void;
}
export const useUserAccountStore = create<AccountState>(set => ({
userAccount: null,
setUserAccount: userAccount => set({ userAccount }),
}));

View File

@@ -1,45 +1,26 @@
import { create } from 'zustand';
import updateUserConfig, { UserMeType, UserConfigType } from '../api/actions/updateUserConfig';
import { UserConfigType } from '../api/actions/updateUserConfig';
interface UserConfigState {
userConfig: UserMeType;
setUserConfig: (userConfig: UserMeType) => void;
setPartialConfig: (userConfig: Partial<UserConfigType>) => void;
userConfig: UserConfigType;
setUserConfig: (userConfig: UserConfigType) => void;
}
export const useUserConfigStore = create<UserConfigState>(set => ({
userConfig: {
id: 0,
name: '',
is_superuser: false,
is_staff: false,
groups: [],
user_permissions: [],
last_login: '',
config: {
stylesheet: 'dark.css',
page_size: 12,
sort_by: 'published',
sort_order: 'desc',
view_style_home: 'grid',
view_style_channel: 'list',
view_style_downloads: 'list',
view_style_playlist: 'grid',
grid_items: 3,
hide_watched: false,
show_ignored_only: false,
show_subed_only: false,
show_help_text: true,
},
stylesheet: 'dark.css',
page_size: 12,
sort_by: 'published',
sort_order: 'desc',
view_style_home: 'grid',
view_style_channel: 'list',
view_style_downloads: 'list',
view_style_playlist: 'grid',
grid_items: 3,
hide_watched: false,
show_ignored_only: false,
show_subed_only: false,
show_help_text: true,
},
setUserConfig: userConfig => set({ userConfig }),
setPartialConfig: async (userConfig: Partial<UserConfigType>) => {
const userConfigResponse = await updateUserConfig(userConfig);
set(state => ({
userConfig: state.userConfig
? { ...state.userConfig, config: userConfigResponse }
: state.userConfig,
}));
},
}));