Custom CSS, auto delete fixes, #build

Changed:
- Re-added custom CSS functionality
- Fixed playlist video type
- Fixed autodelete channel overwrite
- Fixed autodelete for watched state toggle
- newest yt-dlp
This commit is contained in:
Simon 2025-05-10 22:25:15 +07:00
commit f53988b826
No known key found for this signature in database
GPG Key ID: 2C15AA5E89985DD4
26 changed files with 1431 additions and 567 deletions

View File

@ -231,7 +231,13 @@ def ta_host_parser(ta_host: str) -> tuple[list[str], list[str]]:
def get_stylesheets() -> list:
"""Get all valid stylesheets from /static/css"""
stylesheets = ["dark.css", "light.css", "matrix.css", "midnight.css"]
stylesheets = [
"dark.css",
"light.css",
"matrix.css",
"midnight.css",
"custom.css",
]
return stylesheets

View File

@ -311,8 +311,19 @@ class DownloadPostProcess(DownloaderBase):
print(f"auto delete older than {autodelete_days} days")
now_lte = str(self.now - autodelete_days * 24 * 60 * 60)
channel_overwrite = "channel.channel_overwrites.autodelete_days"
data = {
"query": {"range": {"player.watched_date": {"lte": now_lte}}},
"query": {
"bool": {
"must": [
{"range": {"player.watched_date": {"lte": now_lte}}},
{"term": {"player.watched": True}},
],
"must_not": [
{"exists": {"field": channel_overwrite}},
],
}
},
"sort": [{"player.watched_date": {"order": "asc"}}],
}
self._auto_delete_watched(data)
@ -327,6 +338,7 @@ class DownloadPostProcess(DownloaderBase):
must_list = [
{"range": {"player.watched_date": {"lte": now_lte}}},
{"term": {"channel.channel_id": {"value": channel_id}}},
{"term": {"player.watched": True}},
]
data = {
"query": {"bool": {"must": must_list}},

View File

@ -1,8 +1,8 @@
-r requirements.txt
ipython==9.0.2
ipython==9.2.0
pre-commit==4.2.0
pylint-django==2.6.1
pylint==3.3.6
pylint==3.3.7
pytest-django==4.11.1
pytest==8.3.5
python-dotenv==1.1.0

View File

@ -1,15 +1,15 @@
apprise==1.9.3
celery==5.5.0
django-auth-ldap==5.1.0
django-celery-beat==2.7.0
celery==5.5.2
django-auth-ldap==5.2.0
django-celery-beat==2.8.0
django-cors-headers==4.7.0
Django==5.1.8
Django==5.2.1
djangorestframework==3.16.0
drf-spectacular==0.28.0
Pillow==11.1.0
redis==5.2.1
Pillow==11.2.1
redis==6.0.0
requests==2.32.3
ryd-client==0.0.6
uvicorn==0.34.0
uvicorn==0.34.2
whitenoise==6.9.0
yt-dlp[default]==2025.3.31
yt-dlp[default]==2025.4.30

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "tubearchivist-frontend",
"version": "0.1.0",
"version": "0.5.1",
"type": "module",
"scripts": {
"dev": "vite",
@ -11,27 +11,27 @@
"preview": "vite preview"
},
"dependencies": {
"dompurify": "^3.2.4",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-router-dom": "^7.1.5",
"zustand": "^5.0.3"
"dompurify": "^3.2.5",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-router-dom": "^7.6.0",
"zustand": "^5.0.4"
},
"devDependencies": {
"@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3",
"@typescript-eslint/eslint-plugin": "^8.24.0",
"@typescript-eslint/parser": "^8.24.0",
"@vitejs/plugin-react-swc": "^3.8.0",
"eslint": "^9.20.1",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-react-refresh": "^0.4.19",
"globals": "^15.15.0",
"prettier": "3.5.1",
"typescript": "^5.7.3",
"typescript-eslint": "^8.24.0",
"vite": ">=6.2.4",
"vite-plugin-checker": "^0.8.0"
"@types/react": "^19.1.3",
"@types/react-dom": "^19.1.3",
"@typescript-eslint/eslint-plugin": "^8.32.0",
"@typescript-eslint/parser": "^8.32.0",
"@vitejs/plugin-react-swc": "^3.9.0",
"eslint": "^9.26.0",
"eslint-config-prettier": "^10.1.5",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.20",
"globals": "^16.1.0",
"prettier": "3.5.3",
"typescript": "^5.8.3",
"typescript-eslint": "^8.32.0",
"vite": ">=6.3.5",
"vite-plugin-checker": "^0.9.3"
}
}

View File

@ -0,0 +1,16 @@
:root {
--main-bg: #00202f;
--highlight-bg: #00293b;
--highlight-error: #990202;
--highlight-error-light: #c44343;
--highlight-bg-transparent: #00293baf;
--main-font: #eeeeee;
--accent-font-dark: #259485;
--accent-font-light: #97d4c8;
--img-filter: invert(50%) sepia(9%) saturate(2940%) hue-rotate(122deg) brightness(94%)
contrast(90%);
--img-filter-error: invert(16%) sepia(60%) saturate(3717%) hue-rotate(349deg) brightness(86%)
contrast(120%);
--banner: url('/img/banner-tube-archivist-dark.png');
--logo: url('/img/logo-tube-archivist-dark.png');
}

View File

@ -1,7 +1,12 @@
import { SortByType, SortOrderType, ViewLayoutType } from '../../pages/Home';
import APIClient from '../../functions/APIClient';
export type ColourVariants = 'dark.css' | 'light.css' | 'matrix.css' | 'midnight.css';
export type ColourVariants =
| 'dark.css'
| 'light.css'
| 'matrix.css'
| 'midnight.css'
| 'custom.css';
export const FileSizeUnits = {
Binary: 'binary',

View File

@ -0,0 +1,34 @@
import { useUserConfigStore } from '../../stores/UserConfigStore';
import { ColourConstant } from './colourConstant';
import CustomStylesheet from './components/Custom';
import DarkStylesheet from './components/Dark';
import LightStylesheet from './components/Light';
import MatrixStylesheet from './components/Matrix';
import MidnightStylesheet from './components/Midnight';
const Colours = () => {
const { userConfig } = useUserConfigStore();
const stylesheet = userConfig?.stylesheet;
switch (stylesheet) {
case ColourConstant.Dark:
return <DarkStylesheet />;
case ColourConstant.Matrix:
return <MatrixStylesheet />;
case ColourConstant.Midnight:
return <MidnightStylesheet />;
case ColourConstant.Light:
return <LightStylesheet />;
case ColourConstant.Custom:
return <CustomStylesheet />;
default:
return <DarkStylesheet />;
}
};
export default Colours;

View File

@ -0,0 +1,7 @@
export const ColourConstant = {
Dark: 'dark.css',
Light: 'light.css',
Matrix: 'matrix.css',
Midnight: 'midnight.css',
Custom: 'custom.css',
};

View File

@ -0,0 +1,9 @@
const CustomStylesheet = () => {
return (
<head>
<link rel="stylesheet" href="/css/custom.css" />
</head>
);
};
export default CustomStylesheet;

View File

@ -1,7 +1,9 @@
import './css/dark.css';
const DarkStylesheet = () => {
return <></>;
return (
<head>
<link rel="stylesheet" href="/css/dark.css" />
</head>
);
};
export default DarkStylesheet;

View File

@ -1,7 +1,9 @@
import './css/light.css';
const LightStylesheet = () => {
return <></>;
return (
<head>
<link rel="stylesheet" href="/css/light.css" />
</head>
);
};
export default LightStylesheet;

View File

@ -1,7 +1,9 @@
import './css/matrix.css';
const MatrixStylesheet = () => {
return <></>;
return (
<head>
<link rel="stylesheet" href="/css/matrix.css" />
</head>
);
};
export default MatrixStylesheet;

View File

@ -1,7 +1,9 @@
import './css/midnight.css';
const MidnightStylesheet = () => {
return <></>;
return (
<head>
<link rel="stylesheet" href="/css/midnight.css" />
</head>
);
};
export default MidnightStylesheet;

View File

@ -1,32 +0,0 @@
import { useUserConfigStore } from '../../stores/UserConfigStore';
export const ColourConstant = {
Dark: 'dark.css',
Light: 'light.css',
Matrix: 'matrix.css',
Midnight: 'midnight.css',
};
const useColours = () => {
const { userConfig } = useUserConfigStore();
const stylesheet = userConfig?.stylesheet;
switch (stylesheet) {
case ColourConstant.Dark:
return import('./components/Dark');
case ColourConstant.Matrix:
return import('./components/Matrix');
case ColourConstant.Midnight:
return import('./components/Midnight');
case ColourConstant.Light:
return import('./components/Light');
default:
return import('./components/Dark');
}
};
export default useColours;

View File

@ -1,6 +1,6 @@
import { Outlet, useLoaderData, useLocation, useSearchParams } from 'react-router-dom';
import Footer from '../components/Footer';
import useColours from '../configuration/colours/useColours';
import Colours from '../configuration/colours/Colours';
import { UserConfigType } from '../api/actions/updateUserConfig';
import { useEffect, useState } from 'react';
import Navigation from '../components/Navigation';
@ -94,10 +94,9 @@ const Base = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentPage]);
useColours();
return (
<>
<Colours />
<div className="main-content">
<Navigation />
{/** Outlet: https://reactrouter.com/en/main/components/outlet */}

View File

@ -1,5 +1,5 @@
import { useRouteError } from 'react-router-dom';
import useColours from '../configuration/colours/useColours';
import Colours from '../configuration/colours/Colours';
// This is not always the correct response
type ErrorType = {
@ -9,13 +9,13 @@ type ErrorType = {
const ErrorPage = () => {
const error = useRouteError() as ErrorType;
useColours();
console.error('ErrorPage', error);
return (
<>
<title>TA | Oops!</title>
<Colours />
<div id="error-page" style={{ margin: '10%' }}>
<h1>Oops!</h1>

View File

@ -1,14 +1,12 @@
import { useEffect, useState } from 'react';
import Routes from '../configuration/routes/RouteList';
import { useNavigate } from 'react-router-dom';
import useColours from '../configuration/colours/useColours';
import Colours from '../configuration/colours/Colours';
import Button from '../components/Button';
import signIn from '../api/actions/signIn';
import loadAuth from '../api/loader/loadAuth';
const Login = () => {
useColours();
const navigate = useNavigate();
const [username, setUsername] = useState('');
@ -76,6 +74,7 @@ const Login = () => {
return (
<>
<title>TA | Welcome</title>
<Colours />
<div className="boxed-content login-page">
<img alt="tube-archivist-logo" />
<h1>Tube Archivist</h1>

View File

@ -1,13 +1,12 @@
import { Link } from 'react-router-dom';
import useColours from '../configuration/colours/useColours';
import Colours from '../configuration/colours/Colours';
import Routes from '../configuration/routes/RouteList';
const NotFound = ({ failType = 'page' }) => {
useColours();
return (
<>
<title>404 | Not found</title>
<Colours />
<div id="error-page" style={{ margin: '10%' }}>
<h1>Oops!</h1>
<p>

View File

@ -60,7 +60,7 @@ const Playlist = () => {
const palylistEntries = playlistResponseData?.playlist_entries;
const videoArchivedCount = Number(palylistEntries?.filter(video => video.downloaded).length);
const videoInPlaylistCount = pagination?.total_hits;
const videoInPlaylistCount = Number(palylistEntries?.length);
const view = userConfig.view_style_home;
const gridItems = userConfig.grid_items;
@ -304,7 +304,7 @@ const Playlist = () => {
<div className={`boxed-content ${gridView}`}>
<Filterbar
hideToggleText="Hide watched videos:"
viewStyleName={ViewStyleNames.playlist}
viewStyleName={ViewStyleNames.home}
showSort={false}
/>
</div>

View File

@ -1,10 +1,8 @@
import { useNavigate } from 'react-router-dom';
import updateUserConfig, {
ColourVariants,
FileSizeUnits,
UserConfigType,
} from '../api/actions/updateUserConfig';
import { ColourConstant } from '../configuration/colours/useColours';
import SettingsNavigation from '../components/SettingsNavigation';
import Notifications from '../components/Notifications';
import Button from '../components/Button';
@ -12,14 +10,13 @@ import useIsAdmin from '../functions/useIsAdmin';
import { useUserConfigStore } from '../stores/UserConfigStore';
import { useEffect, useState } from 'react';
import ToggleConfig from '../components/ToggleConfig';
import { ColourConstant } from '../configuration/colours/colourConstant';
const SettingsUser = () => {
const { userConfig, setUserConfig } = useUserConfigStore();
const isAdmin = useIsAdmin();
const navigate = useNavigate();
const [styleSheet, setStyleSheet] = useState<ColourVariants>(userConfig.stylesheet);
const [styleSheetRefresh, setStyleSheetRefresh] = useState(false);
const [pageSize, setPageSize] = useState<number>(userConfig.page_size);
const [showHelpText, setShowHelpText] = useState(userConfig.show_help_text);
const [selectedFileSizeUnit, setSelectedFileSizeUnit] = useState(FileSizeUnits.Binary);
@ -41,7 +38,6 @@ const SettingsUser = () => {
const handleStyleSheetChange = async (selectedStyleSheet: ColourVariants) => {
handleUserConfigUpdate({ stylesheet: selectedStyleSheet });
setStyleSheet(selectedStyleSheet);
setStyleSheetRefresh(true);
};
const handlePageSizeChange = async () => {
@ -65,11 +61,6 @@ const SettingsUser = () => {
}
};
const handlePageRefresh = () => {
navigate(0);
setStyleSheetRefresh(false);
};
return (
<>
<title>TA | User Settings</title>
@ -104,7 +95,6 @@ const SettingsUser = () => {
);
})}
</select>
{styleSheetRefresh && <button onClick={handlePageRefresh}>Refresh</button>}
</div>
</div>