mirror of
https://github.com/tubearchivist/tubearchivist.git
synced 2025-07-02 23:31:10 +00:00
build improvements, #build
Changed: - bump dependencies - faster builds and better caching - auto update yt-dlp
This commit is contained in:
commit
4a64bdea33
2
.gitignore
vendored
2
.gitignore
vendored
@ -12,3 +12,5 @@ backend/.env
|
|||||||
|
|
||||||
# JavaScript stuff
|
# JavaScript stuff
|
||||||
node_modules
|
node_modules
|
||||||
|
|
||||||
|
.editorconfig
|
||||||
|
14
Dockerfile
14
Dockerfile
@ -1,20 +1,24 @@
|
|||||||
# multi stage to build tube archivist
|
# multi stage to build tube archivist
|
||||||
# build python wheel, download and extract ffmpeg, copy into final image
|
# build python wheel, download and extract ffmpeg, copy into final image
|
||||||
|
|
||||||
|
FROM node:lts-alpine AS npm-builder
|
||||||
|
COPY frontend/package.json frontend/package-lock.json /
|
||||||
|
RUN npm i
|
||||||
|
|
||||||
FROM node:lts-alpine AS node-builder
|
FROM node:lts-alpine AS node-builder
|
||||||
|
|
||||||
# RUN npm config set registry https://registry.npmjs.org/
|
# RUN npm config set registry https://registry.npmjs.org/
|
||||||
|
|
||||||
|
COPY --from=npm-builder ./node_modules /frontend/node_modules
|
||||||
COPY ./frontend /frontend
|
COPY ./frontend /frontend
|
||||||
|
|
||||||
WORKDIR /frontend
|
WORKDIR /frontend
|
||||||
RUN npm i
|
|
||||||
RUN npm run build:deploy
|
RUN npm run build:deploy
|
||||||
|
|
||||||
WORKDIR /
|
WORKDIR /
|
||||||
|
|
||||||
# First stage to build python wheel
|
# First stage to build python wheel
|
||||||
FROM python:3.11.8-slim-bookworm AS builder
|
FROM python:3.11.13-slim-bookworm AS builder
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
build-essential gcc libldap2-dev libsasl2-dev libssl-dev git
|
build-essential gcc libldap2-dev libsasl2-dev libssl-dev git
|
||||||
@ -24,7 +28,7 @@ COPY ./backend/requirements.txt /requirements.txt
|
|||||||
RUN pip install --user -r requirements.txt
|
RUN pip install --user -r requirements.txt
|
||||||
|
|
||||||
# build ffmpeg
|
# build ffmpeg
|
||||||
FROM python:3.11.8-slim-bookworm AS ffmpeg-builder
|
FROM python:3.11.13-slim-bookworm AS ffmpeg-builder
|
||||||
|
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
|
|
||||||
@ -32,7 +36,7 @@ COPY docker_assets/ffmpeg_download.py ffmpeg_download.py
|
|||||||
RUN python ffmpeg_download.py $TARGETPLATFORM
|
RUN python ffmpeg_download.py $TARGETPLATFORM
|
||||||
|
|
||||||
# build final image
|
# build final image
|
||||||
FROM python:3.11.8-slim-bookworm AS tubearchivist
|
FROM python:3.11.13-slim-bookworm AS tubearchivist
|
||||||
|
|
||||||
ARG INSTALL_DEBUG
|
ARG INSTALL_DEBUG
|
||||||
|
|
||||||
|
@ -71,6 +71,7 @@ All environment variables are explained in detail in the docs [here](https://doc
|
|||||||
| ELASTIC_USER | Change the default ElasticSearch user | Optional |
|
| ELASTIC_USER | Change the default ElasticSearch user | Optional |
|
||||||
| TA_LDAP | Configure TA to use LDAP Authentication | [Read more](https://docs.tubearchivist.com/configuration/ldap/) |
|
| TA_LDAP | Configure TA to use LDAP Authentication | [Read more](https://docs.tubearchivist.com/configuration/ldap/) |
|
||||||
| DISABLE_STATIC_AUTH | Remove authentication from media files, (Google Cast...) | [Read more](https://docs.tubearchivist.com/installation/env-vars/#disable_static_auth) |
|
| DISABLE_STATIC_AUTH | Remove authentication from media files, (Google Cast...) | [Read more](https://docs.tubearchivist.com/installation/env-vars/#disable_static_auth) |
|
||||||
|
| TA_AUTO_UPDATE_YTDLP | Configure TA to automatically install the latest yt-dlp on container start | Optional |
|
||||||
| DJANGO_DEBUG | Return additional error messages, for debug only | Optional |
|
| DJANGO_DEBUG | Return additional error messages, for debug only | Optional |
|
||||||
| TA_LOGIN_AUTH_MODE | Configure the order of login authentication backends (Default: single) | Optional |
|
| TA_LOGIN_AUTH_MODE | Configure the order of login authentication backends (Default: single) | Optional |
|
||||||
|
|
||||||
|
@ -316,7 +316,7 @@ CORS_ALLOW_HEADERS = list(default_headers) + [
|
|||||||
|
|
||||||
# TA application settings
|
# TA application settings
|
||||||
TA_UPSTREAM = "https://github.com/tubearchivist/tubearchivist"
|
TA_UPSTREAM = "https://github.com/tubearchivist/tubearchivist"
|
||||||
TA_VERSION = "v0.5.4"
|
TA_VERSION = "v0.5.5-unstable"
|
||||||
|
|
||||||
# API
|
# API
|
||||||
REST_FRAMEWORK = {
|
REST_FRAMEWORK = {
|
||||||
|
@ -4,7 +4,7 @@ pre-commit==4.2.0
|
|||||||
pylint-django==2.6.1
|
pylint-django==2.6.1
|
||||||
pylint==3.3.7
|
pylint==3.3.7
|
||||||
pytest-django==4.11.1
|
pytest-django==4.11.1
|
||||||
pytest==8.4.0
|
pytest==8.4.1
|
||||||
python-dotenv==1.1.0
|
python-dotenv==1.1.1
|
||||||
requirementscheck==0.0.6
|
requirementscheck==0.0.6
|
||||||
types-requests==2.32.0.20250602
|
types-requests==2.32.4.20250611
|
||||||
|
@ -3,13 +3,13 @@ celery==5.5.3
|
|||||||
django-auth-ldap==5.2.0
|
django-auth-ldap==5.2.0
|
||||||
django-celery-beat==2.8.1
|
django-celery-beat==2.8.1
|
||||||
django-cors-headers==4.7.0
|
django-cors-headers==4.7.0
|
||||||
Django==5.2.2
|
Django==5.2.3
|
||||||
djangorestframework==3.16.0
|
djangorestframework==3.16.0
|
||||||
drf-spectacular==0.28.0
|
drf-spectacular==0.28.0
|
||||||
Pillow==11.2.1
|
Pillow==11.2.1
|
||||||
redis==6.2.0
|
redis==6.2.0
|
||||||
requests==2.32.4
|
requests==2.32.4
|
||||||
ryd-client==0.0.6
|
ryd-client==0.0.6
|
||||||
uvicorn==0.34.3
|
uvicorn==0.35.0
|
||||||
whitenoise==6.9.0
|
whitenoise==6.9.0
|
||||||
yt-dlp[default]==2025.6.9
|
yt-dlp[default]==2025.6.30
|
||||||
|
@ -9,6 +9,15 @@ else
|
|||||||
LOGLEVEL="INFO"
|
LOGLEVEL="INFO"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# update yt-dlp if needed
|
||||||
|
if [[ "${TA_AUTO_UPDATE_YTDLP,,}" =~ ^(release|nightly)$ ]]; then
|
||||||
|
echo "Updating yt-dlp..."
|
||||||
|
preflag=$([[ "${TA_AUTO_UPDATE_YTDLP,,}" == "nightly" ]] && echo "--pre" || echo "")
|
||||||
|
python -m pip install --target=/root/.local/bin --upgrade $preflag "yt-dlp[default]" || {
|
||||||
|
echo "yt-dlp update failed"
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
# stop on pending manual migration
|
# stop on pending manual migration
|
||||||
python manage.py ta_stop_on_error
|
python manage.py ta_stop_on_error
|
||||||
|
|
||||||
|
@ -47,7 +47,11 @@ const DownloadListItem = ({ download, setRefresh }: DownloadListItemProps) => {
|
|||||||
|
|
||||||
{!download.channel_indexed && <span>{download.channel_name}</span>}
|
{!download.channel_indexed && <span>{download.channel_name}</span>}
|
||||||
|
|
||||||
<a href={`https://www.youtube.com/watch?v=${download.youtube_id}`} target="_blank">
|
<a
|
||||||
|
href={`https://www.youtube.com/watch?v=${download.youtube_id}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
<h3>{download.title}</h3>
|
<h3>{download.title}</h3>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import LoadingIndicator from './LoadingIndicator';
|
||||||
|
|
||||||
type InputTextProps = {
|
type InputTextProps = {
|
||||||
type: 'text' | 'number';
|
type: 'text' | 'number';
|
||||||
@ -51,13 +52,7 @@ const InputConfig = ({ type, name, value, setValue, oldValue, updateCallback }:
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{oldValue !== null && <button onClick={() => handleUpdate(name, null)}>reset</button>}
|
{oldValue !== null && <button onClick={() => handleUpdate(name, null)}>reset</button>}
|
||||||
{loading && (
|
{loading && <LoadingIndicator />}
|
||||||
<>
|
|
||||||
<div className="lds-ring" style={{ color: 'var(--accent-font-dark)' }}>
|
|
||||||
<div />
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{success && <span>✅</span>}
|
{success && <span>✅</span>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
9
frontend/src/components/LoadingIndicator.tsx
Normal file
9
frontend/src/components/LoadingIndicator.tsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
const LoadingIndicator = () => {
|
||||||
|
return (
|
||||||
|
<div className="lds-ring" style={{ color: 'var(--accent-font-dark)' }}>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LoadingIndicator;
|
@ -140,6 +140,8 @@ const VideoPlayer = ({
|
|||||||
const [showHelpDialog, setShowHelpDialog] = useState(false);
|
const [showHelpDialog, setShowHelpDialog] = useState(false);
|
||||||
const [showInfoDialog, setShowInfoDialog] = useState(false);
|
const [showInfoDialog, setShowInfoDialog] = useState(false);
|
||||||
const [infoDialogContent, setInfoDialogContent] = useState('');
|
const [infoDialogContent, setInfoDialogContent] = useState('');
|
||||||
|
const [isTheaterMode, setIsTheaterMode] = useState(false);
|
||||||
|
const [theaterModeKeyPressed, setTheaterModeKeyPressed] = useState(false);
|
||||||
|
|
||||||
const questionmarkPressed = useKeyPress('?');
|
const questionmarkPressed = useKeyPress('?');
|
||||||
const mutePressed = useKeyPress('m');
|
const mutePressed = useKeyPress('m');
|
||||||
@ -151,6 +153,8 @@ const VideoPlayer = ({
|
|||||||
const arrowRightPressed = useKeyPress('ArrowRight');
|
const arrowRightPressed = useKeyPress('ArrowRight');
|
||||||
const arrowLeftPressed = useKeyPress('ArrowLeft');
|
const arrowLeftPressed = useKeyPress('ArrowLeft');
|
||||||
const pPausedPressed = useKeyPress('p');
|
const pPausedPressed = useKeyPress('p');
|
||||||
|
const theaterModePressed = useKeyPress('t');
|
||||||
|
const escapePressed = useKeyPress('Escape');
|
||||||
|
|
||||||
const videoId = video.youtube_id;
|
const videoId = video.youtube_id;
|
||||||
const videoUrl = video.media_url;
|
const videoUrl = video.media_url;
|
||||||
@ -345,10 +349,42 @@ const VideoPlayer = ({
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [questionmarkPressed]);
|
}, [questionmarkPressed]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (embed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (theaterModePressed && !theaterModeKeyPressed) {
|
||||||
|
setTheaterModeKeyPressed(true);
|
||||||
|
|
||||||
|
const newTheaterMode = !isTheaterMode;
|
||||||
|
setIsTheaterMode(newTheaterMode);
|
||||||
|
|
||||||
|
infoDialog(newTheaterMode ? 'Theater mode' : 'Normal mode');
|
||||||
|
} else if (!theaterModePressed) {
|
||||||
|
setTheaterModeKeyPressed(false);
|
||||||
|
}
|
||||||
|
}, [theaterModePressed, isTheaterMode, theaterModeKeyPressed]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (embed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (escapePressed && isTheaterMode) {
|
||||||
|
setIsTheaterMode(false);
|
||||||
|
|
||||||
|
infoDialog('Normal mode');
|
||||||
|
}
|
||||||
|
}, [escapePressed, isTheaterMode]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div id="player" className={embed ? '' : 'player-wrapper'}>
|
<div
|
||||||
<div className={embed ? '' : 'video-main'}>
|
id="player"
|
||||||
|
className={embed ? '' : `player-wrapper ${isTheaterMode ? 'theater-mode' : ''}`}
|
||||||
|
>
|
||||||
|
<div className={embed ? '' : `video-main ${isTheaterMode ? 'theater-mode' : ''}`}>
|
||||||
<video
|
<video
|
||||||
ref={videoRef}
|
ref={videoRef}
|
||||||
key={`${getApiUrl()}${videoUrl}`}
|
key={`${getApiUrl()}${videoUrl}`}
|
||||||
@ -423,6 +459,18 @@ const VideoPlayer = ({
|
|||||||
<td>Toggle fullscreen</td>
|
<td>Toggle fullscreen</td>
|
||||||
<td>f</td>
|
<td>f</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
{!embed && (
|
||||||
|
<>
|
||||||
|
<tr>
|
||||||
|
<td>Toggle theater mode</td>
|
||||||
|
<td>t</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Exit theater mode</td>
|
||||||
|
<td>Esc</td>
|
||||||
|
</tr>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<tr>
|
<tr>
|
||||||
<td>Toggle subtitles (if available)</td>
|
<td>Toggle subtitles (if available)</td>
|
||||||
<td>c</td>
|
<td>c</td>
|
||||||
@ -467,11 +515,19 @@ const VideoPlayer = ({
|
|||||||
<h4>
|
<h4>
|
||||||
This video doesn't have any sponsor segments added. To add a segment go to{' '}
|
This video doesn't have any sponsor segments added. To add a segment go to{' '}
|
||||||
<u>
|
<u>
|
||||||
<a href={`https://www.youtube.com/watch?v=${videoId}`}>this video on YouTube</a>
|
<a
|
||||||
|
href={`https://www.youtube.com/watch?v=${videoId}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
this video on YouTube
|
||||||
|
</a>
|
||||||
</u>{' '}
|
</u>{' '}
|
||||||
and add a segment using the{' '}
|
and add a segment using the{' '}
|
||||||
<u>
|
<u>
|
||||||
<a href="https://sponsor.ajay.app/">SponsorBlock</a>
|
<a href="https://sponsor.ajay.app/" target="_blank" rel="noopener noreferrer">
|
||||||
|
SponsorBlock
|
||||||
|
</a>
|
||||||
</u>{' '}
|
</u>{' '}
|
||||||
extension.
|
extension.
|
||||||
</h4>
|
</h4>
|
||||||
@ -480,11 +536,19 @@ const VideoPlayer = ({
|
|||||||
<h4>
|
<h4>
|
||||||
This video has unlocked sponsor segments. Go to{' '}
|
This video has unlocked sponsor segments. Go to{' '}
|
||||||
<u>
|
<u>
|
||||||
<a href={`https://www.youtube.com/watch?v=${videoId}`}>this video on YouTube</a>
|
<a
|
||||||
|
href={`https://www.youtube.com/watch?v=${videoId}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
this video on YouTube
|
||||||
|
</a>
|
||||||
</u>{' '}
|
</u>{' '}
|
||||||
and vote on the segments using the{' '}
|
and vote on the segments using the{' '}
|
||||||
<u>
|
<u>
|
||||||
<a href="https://sponsor.ajay.app/">SponsorBlock</a>
|
<a href="https://sponsor.ajay.app/" target="_blank" rel="noopener noreferrer">
|
||||||
|
SponsorBlock
|
||||||
|
</a>
|
||||||
</u>{' '}
|
</u>{' '}
|
||||||
extension.
|
extension.
|
||||||
</h4>
|
</h4>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import iconUnseen from '/img/icon-unseen.svg';
|
import iconUnseen from '/img/icon-unseen.svg';
|
||||||
import iconSeen from '/img/icon-seen.svg';
|
import iconSeen from '/img/icon-seen.svg';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
import LoadingIndicator from './LoadingIndicator';
|
||||||
|
|
||||||
type WatchedCheckBoxProps = {
|
type WatchedCheckBoxProps = {
|
||||||
watched: boolean;
|
watched: boolean;
|
||||||
@ -32,9 +33,7 @@ const WatchedCheckBox = ({ watched, onClick, onDone }: WatchedCheckBoxProps) =>
|
|||||||
<>
|
<>
|
||||||
{loading && (
|
{loading && (
|
||||||
<>
|
<>
|
||||||
<div className="lds-ring" style={{ color: 'var(--accent-font-dark)' }}>
|
<LoadingIndicator />
|
||||||
<div />
|
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{!loading && watched && (
|
{!loading && watched && (
|
||||||
|
@ -130,7 +130,11 @@ const ChannelAbout = () => {
|
|||||||
{channel.channel_active && (
|
{channel.channel_active && (
|
||||||
<p>
|
<p>
|
||||||
Youtube:{' '}
|
Youtube:{' '}
|
||||||
<a href={`https://www.youtube.com/channel/${channel.channel_id}`} target="_blank">
|
<a
|
||||||
|
href={`https://www.youtube.com/channel/${channel.channel_id}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
Active
|
Active
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
@ -316,7 +320,7 @@ const ChannelAbout = () => {
|
|||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
Overwrite{' '}
|
Overwrite{' '}
|
||||||
<a href="https://sponsor.ajay.app/" target="_blank">
|
<a href="https://sponsor.ajay.app/" target="_blank" rel="noopener noreferrer">
|
||||||
SponsorBlock
|
SponsorBlock
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
@ -97,16 +97,8 @@ const ChannelVideo = ({ videoType }: ChannelVideoProps) => {
|
|||||||
videoId,
|
videoId,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!channel) {
|
|
||||||
return (
|
|
||||||
<div className="boxed-content">
|
|
||||||
<br />
|
|
||||||
<h2>Channel {channelId} not found!</h2>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
channel && (
|
||||||
<>
|
<>
|
||||||
<title>{`TA | Channel: ${channel.channel_name}`}</title>
|
<title>{`TA | Channel: ${channel.channel_name}`}</title>
|
||||||
<ScrollToTopOnNavigate />
|
<ScrollToTopOnNavigate />
|
||||||
@ -180,8 +172,8 @@ const ChannelVideo = ({ videoType }: ChannelVideoProps) => {
|
|||||||
<>
|
<>
|
||||||
<h2>No videos found...</h2>
|
<h2>No videos found...</h2>
|
||||||
<p>
|
<p>
|
||||||
Try going to the <Link to={Routes.Downloads}>downloads page</Link> to start the scan
|
Try going to the <Link to={Routes.Downloads}>downloads page</Link> to start the
|
||||||
and download tasks.
|
scan and download tasks.
|
||||||
</p>
|
</p>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@ -195,6 +187,7 @@ const ChannelVideo = ({ videoType }: ChannelVideoProps) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import Colours from '../configuration/colours/Colours';
|
|||||||
import Button from '../components/Button';
|
import Button from '../components/Button';
|
||||||
import signIn from '../api/actions/signIn';
|
import signIn from '../api/actions/signIn';
|
||||||
import loadAuth from '../api/loader/loadAuth';
|
import loadAuth from '../api/loader/loadAuth';
|
||||||
|
import LoadingIndicator from '../components/LoadingIndicator';
|
||||||
|
|
||||||
const Login = () => {
|
const Login = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@ -134,10 +135,7 @@ const Login = () => {
|
|||||||
{waitingForBackend && (
|
{waitingForBackend && (
|
||||||
<>
|
<>
|
||||||
<p>
|
<p>
|
||||||
Waiting for backend{' '}
|
Waiting for backend <LoadingIndicator />
|
||||||
<div className="lds-ring" style={{ color: 'var(--accent-font-dark)' }}>
|
|
||||||
<div />
|
|
||||||
</div>
|
|
||||||
</p>
|
</p>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
@ -180,6 +180,7 @@ const Playlist = () => {
|
|||||||
<a
|
<a
|
||||||
href={`https://www.youtube.com/playlist?list=${playlist.playlist_id}`}
|
href={`https://www.youtube.com/playlist?list=${playlist.playlist_id}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
Active
|
Active
|
||||||
</a>
|
</a>
|
||||||
|
@ -779,7 +779,11 @@ const SettingsApplication = () => {
|
|||||||
<li>Make sure to contribute to this excellent project.</li>
|
<li>Make sure to contribute to this excellent project.</li>
|
||||||
<li>
|
<li>
|
||||||
More details{' '}
|
More details{' '}
|
||||||
<a target="_blank" href="https://returnyoutubedislike.com/">
|
<a
|
||||||
|
href="https://returnyoutubedislike.com/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
here
|
here
|
||||||
</a>
|
</a>
|
||||||
.
|
.
|
||||||
@ -794,7 +798,11 @@ const SettingsApplication = () => {
|
|||||||
<li>Make sure to contribute to this excellent project.</li>
|
<li>Make sure to contribute to this excellent project.</li>
|
||||||
<li>
|
<li>
|
||||||
More details{' '}
|
More details{' '}
|
||||||
<a target="_blank" href="https://sponsor.ajay.app/">
|
<a
|
||||||
|
href="https://sponsor.ajay.app/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
here
|
here
|
||||||
</a>
|
</a>
|
||||||
.
|
.
|
||||||
@ -831,7 +839,11 @@ const SettingsApplication = () => {
|
|||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
Enable{' '}
|
Enable{' '}
|
||||||
<a target="_blank" href="https://returnyoutubedislike.com/">
|
<a
|
||||||
|
href="https://returnyoutubedislike.com/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
returnyoutubedislike
|
returnyoutubedislike
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
@ -846,7 +858,7 @@ const SettingsApplication = () => {
|
|||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
Enable{' '}
|
Enable{' '}
|
||||||
<a href="https://sponsor.ajay.app/" target="_blank">
|
<a href="https://sponsor.ajay.app/" target="_blank" rel="noopener noreferrer">
|
||||||
Sponsorblock
|
Sponsorblock
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
@ -279,7 +279,11 @@ const Video = () => {
|
|||||||
{video.active && (
|
{video.active && (
|
||||||
<p>
|
<p>
|
||||||
Youtube:{' '}
|
Youtube:{' '}
|
||||||
<a href={`https://www.youtube.com/watch?v=${video.youtube_id}`} target="_blank">
|
<a
|
||||||
|
href={`https://www.youtube.com/watch?v=${video.youtube_id}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
Active
|
Active
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
@ -458,6 +458,36 @@ button:hover {
|
|||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.player-wrapper.theater-mode {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
z-index: 1000;
|
||||||
|
background-color: rgba(0, 0, 0, 0.9);
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-main.theater-mode {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-main.theater-mode video {
|
||||||
|
max-height: 95vh;
|
||||||
|
max-width: 95vw;
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.video-player {
|
.video-player {
|
||||||
display: grid;
|
display: grid;
|
||||||
align-content: space-evenly;
|
align-content: space-evenly;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user