mirror of
https://github.com/tubearchivist/tubearchivist.git
synced 2025-02-19 06:20:13 +00:00
run pre-commit on all
This commit is contained in:
parent
cf54f6d7fc
commit
bc74bf80f4
@ -18,4 +18,4 @@ venv/
|
||||
assets/*
|
||||
|
||||
# for local testing only
|
||||
testing.sh
|
||||
testing.sh
|
||||
|
2
.gitattributes
vendored
2
.gitattributes
vendored
@ -1 +1 @@
|
||||
docker_assets\run.sh eol=lf
|
||||
docker_assets\run.sh eol=lf
|
||||
|
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@ -1,3 +1,3 @@
|
||||
github: bbilly1
|
||||
ko_fi: bbilly1
|
||||
custom: https://paypal.me/bbilly1
|
||||
custom: https://paypal.me/bbilly1
|
||||
|
2
.github/ISSUE_TEMPLATE/FEATURE-REQUEST.yml
vendored
2
.github/ISSUE_TEMPLATE/FEATURE-REQUEST.yml
vendored
@ -6,7 +6,7 @@ body:
|
||||
- type: checkboxes
|
||||
id: block
|
||||
attributes:
|
||||
label: "This project doesn't accept any new feature requests for the forseeable future. There is no shortage of ideas and the next development steps are clear for years to come."
|
||||
label: "This project doesn't accept any new feature requests for the foreseeable future. There is no shortage of ideas and the next development steps are clear for years to come."
|
||||
options:
|
||||
- label: I understand that this issue will be closed without comment.
|
||||
required: true
|
||||
|
48
.pre-commit-config.yaml
Normal file
48
.pre-commit-config.yaml
Normal file
@ -0,0 +1,48 @@
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v5.0.0
|
||||
hooks:
|
||||
- id: end-of-file-fixer
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 24.10.0
|
||||
hooks:
|
||||
- id: black
|
||||
alias: python
|
||||
files: ^backend/
|
||||
args: ["--line-length=79"]
|
||||
- repo: https://github.com/pycqa/isort
|
||||
rev: 5.13.2
|
||||
hooks:
|
||||
- id: isort
|
||||
name: isort (python)
|
||||
alias: python
|
||||
files: ^backend/
|
||||
args: ["--profile", "black", "-l 79"]
|
||||
- repo: https://github.com/pycqa/flake8
|
||||
rev: 7.1.1
|
||||
hooks:
|
||||
- id: flake8
|
||||
alias: python
|
||||
files: ^backend/
|
||||
args: [ "--max-complexity=10", "--max-line-length=79" ]
|
||||
- repo: https://github.com/codespell-project/codespell
|
||||
rev: v2.3.0
|
||||
hooks:
|
||||
- id: codespell
|
||||
exclude: ^frontend/package-lock.json
|
||||
# - repo: https://github.com/pre-commit/mirrors-eslint
|
||||
# rev: v9.17.0
|
||||
# hooks:
|
||||
# - id: eslint
|
||||
# name: eslint
|
||||
# entry: npm run --prefix ./frontend lint
|
||||
# pass_filenames: false
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
rev: v4.0.0-alpha.8
|
||||
hooks:
|
||||
- id: prettier
|
||||
entry: npm run --prefix ./frontend prettier
|
||||
args: ["--write", "."]
|
||||
pass_filenames: false
|
||||
|
||||
exclude: '.*(\.svg|/migrations/).*'
|
@ -684,4 +684,4 @@
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,6 @@
|
||||
-r requirements.txt
|
||||
black==24.10.0
|
||||
codespell==2.3.0
|
||||
flake8==7.1.1
|
||||
ipython==8.31.0
|
||||
isort==5.13.2
|
||||
pre-commit==4.0.1
|
||||
pylint-django==2.6.1
|
||||
pylint==3.3.3
|
||||
pytest-django==4.9.0
|
||||
|
@ -25,7 +25,10 @@ class Migration(migrations.Migration):
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("password", models.CharField(max_length=128, verbose_name="password")),
|
||||
(
|
||||
"password",
|
||||
models.CharField(max_length=128, verbose_name="password"),
|
||||
),
|
||||
(
|
||||
"last_login",
|
||||
models.DateTimeField(
|
||||
|
@ -53,4 +53,4 @@ server {
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html =404;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,4 +2,4 @@
|
||||
build
|
||||
dist
|
||||
coverage
|
||||
node_modules
|
||||
node_modules
|
||||
|
@ -1,26 +1,26 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/favicon/apple-touch-icon.png" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon/favicon-16x16.png" />
|
||||
<link rel="manifest" href="/favicon/site.webmanifest" />
|
||||
<link rel="mask-icon" href="/favicon/safari-pinned-tab.svg" color="#01202e" />
|
||||
<link rel="shortcut icon" href="/favicon/favicon.ico" />
|
||||
|
||||
<meta name="apple-mobile-web-app-title" content="TubeArchivist" />
|
||||
<meta name="application-name" content="TubeArchivist" />
|
||||
<meta name="msapplication-TileColor" content="#01202e" />
|
||||
<meta name="msapplication-config" content="/favicon/browserconfig.xml" />
|
||||
<meta name="theme-color" content="#01202e" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>TubeArchivist</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/favicon/apple-touch-icon.png" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon/favicon-16x16.png" />
|
||||
<link rel="manifest" href="/favicon/site.webmanifest" />
|
||||
<link rel="mask-icon" href="/favicon/safari-pinned-tab.svg" color="#01202e" />
|
||||
<link rel="shortcut icon" href="/favicon/favicon.ico" />
|
||||
|
||||
<meta name="apple-mobile-web-app-title" content="TubeArchivist" />
|
||||
<meta name="application-name" content="TubeArchivist" />
|
||||
<meta name="msapplication-TileColor" content="#01202e" />
|
||||
<meta name="msapplication-config" content="/favicon/browserconfig.xml" />
|
||||
<meta name="theme-color" content="#01202e" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>TubeArchivist</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
5906
frontend/package-lock.json
generated
5906
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,33 +1,34 @@
|
||||
{
|
||||
"name": "tubearchivist-frontend",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"build:deploy": "vite build",
|
||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"dompurify": "^3.2.3",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-router-dom": "^7.0.2",
|
||||
"zustand": "^5.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^19.0.1",
|
||||
"@types/react-dom": "^19.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "^8.18.0",
|
||||
"@typescript-eslint/parser": "^8.18.0",
|
||||
"@vitejs/plugin-react-swc": "^3.7.2",
|
||||
"eslint": "^9.16.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-react-hooks": "^5.1.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.16",
|
||||
"prettier": "3.4.2",
|
||||
"typescript": "^5.7.2",
|
||||
"vite": "^6.0.3"
|
||||
}
|
||||
}
|
||||
{
|
||||
"name": "tubearchivist-frontend",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"build:deploy": "vite build",
|
||||
"lint": "eslint . --report-unused-disable-directives --max-warnings 0",
|
||||
"prettier": "prettier --write .",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"dompurify": "^3.2.3",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-router-dom": "^7.0.2",
|
||||
"zustand": "^5.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^19.0.1",
|
||||
"@types/react-dom": "^19.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "^8.18.0",
|
||||
"@typescript-eslint/parser": "^8.18.0",
|
||||
"@vitejs/plugin-react-swc": "^3.7.2",
|
||||
"eslint": "^9.16.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-react-hooks": "^5.1.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.16",
|
||||
"prettier": "3.4.2",
|
||||
"typescript": "^5.7.2",
|
||||
"vite": "^6.0.3"
|
||||
}
|
||||
}
|
||||
|
@ -1,42 +1,42 @@
|
||||
import defaultHeaders from '../../configuration/defaultHeaders';
|
||||
import getApiUrl from '../../configuration/getApiUrl';
|
||||
import getFetchCredentials from '../../configuration/getFetchCredentials';
|
||||
import getCookie from '../../functions/getCookie';
|
||||
|
||||
const updateChannelSubscription = async (channelIds: string, status: boolean) => {
|
||||
const apiUrl = getApiUrl();
|
||||
const csrfCookie = getCookie('csrftoken');
|
||||
|
||||
const channels = [];
|
||||
const containsMultiple = channelIds.includes('\n');
|
||||
|
||||
if (containsMultiple) {
|
||||
const youtubeChannelIds = channelIds.split('\n');
|
||||
|
||||
youtubeChannelIds.forEach(channelId => {
|
||||
channels.push({ channel_id: channelId, channel_subscribed: status });
|
||||
});
|
||||
} else {
|
||||
channels.push({ channel_id: channelIds, channel_subscribed: status });
|
||||
}
|
||||
|
||||
const response = await fetch(`${apiUrl}/api/channel/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...defaultHeaders,
|
||||
'X-CSRFToken': csrfCookie || '',
|
||||
},
|
||||
credentials: getFetchCredentials(),
|
||||
|
||||
body: JSON.stringify({
|
||||
data: [...channels],
|
||||
}),
|
||||
});
|
||||
|
||||
const channelSubscription = await response.json();
|
||||
console.log('updateChannelSubscription', channelSubscription);
|
||||
|
||||
return channelSubscription;
|
||||
};
|
||||
|
||||
export default updateChannelSubscription;
|
||||
import defaultHeaders from '../../configuration/defaultHeaders';
|
||||
import getApiUrl from '../../configuration/getApiUrl';
|
||||
import getFetchCredentials from '../../configuration/getFetchCredentials';
|
||||
import getCookie from '../../functions/getCookie';
|
||||
|
||||
const updateChannelSubscription = async (channelIds: string, status: boolean) => {
|
||||
const apiUrl = getApiUrl();
|
||||
const csrfCookie = getCookie('csrftoken');
|
||||
|
||||
const channels = [];
|
||||
const containsMultiple = channelIds.includes('\n');
|
||||
|
||||
if (containsMultiple) {
|
||||
const youtubeChannelIds = channelIds.split('\n');
|
||||
|
||||
youtubeChannelIds.forEach(channelId => {
|
||||
channels.push({ channel_id: channelId, channel_subscribed: status });
|
||||
});
|
||||
} else {
|
||||
channels.push({ channel_id: channelIds, channel_subscribed: status });
|
||||
}
|
||||
|
||||
const response = await fetch(`${apiUrl}/api/channel/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...defaultHeaders,
|
||||
'X-CSRFToken': csrfCookie || '',
|
||||
},
|
||||
credentials: getFetchCredentials(),
|
||||
|
||||
body: JSON.stringify({
|
||||
data: [...channels],
|
||||
}),
|
||||
});
|
||||
|
||||
const channelSubscription = await response.json();
|
||||
console.log('updateChannelSubscription', channelSubscription);
|
||||
|
||||
return channelSubscription;
|
||||
};
|
||||
|
||||
export default updateChannelSubscription;
|
||||
|
@ -1,33 +1,33 @@
|
||||
import defaultHeaders from '../../configuration/defaultHeaders';
|
||||
import getApiUrl from '../../configuration/getApiUrl';
|
||||
import getFetchCredentials from '../../configuration/getFetchCredentials';
|
||||
import getCookie from '../../functions/getCookie';
|
||||
|
||||
export type ValidatedCookieType = {
|
||||
cookie_enabled: boolean;
|
||||
status: boolean;
|
||||
validated: number;
|
||||
validated_str: string;
|
||||
cookie_validated?: boolean;
|
||||
};
|
||||
|
||||
const updateCookie = async (): Promise<ValidatedCookieType> => {
|
||||
const apiUrl = getApiUrl();
|
||||
const csrfCookie = getCookie('csrftoken');
|
||||
|
||||
const response = await fetch(`${apiUrl}/api/appsettings/cookie/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...defaultHeaders,
|
||||
'X-CSRFToken': csrfCookie || '',
|
||||
},
|
||||
credentials: getFetchCredentials(),
|
||||
});
|
||||
|
||||
const validatedCookie = await response.json();
|
||||
console.log('updateCookie', validatedCookie);
|
||||
|
||||
return validatedCookie;
|
||||
};
|
||||
|
||||
export default updateCookie;
|
||||
import defaultHeaders from '../../configuration/defaultHeaders';
|
||||
import getApiUrl from '../../configuration/getApiUrl';
|
||||
import getFetchCredentials from '../../configuration/getFetchCredentials';
|
||||
import getCookie from '../../functions/getCookie';
|
||||
|
||||
export type ValidatedCookieType = {
|
||||
cookie_enabled: boolean;
|
||||
status: boolean;
|
||||
validated: number;
|
||||
validated_str: string;
|
||||
cookie_validated?: boolean;
|
||||
};
|
||||
|
||||
const updateCookie = async (): Promise<ValidatedCookieType> => {
|
||||
const apiUrl = getApiUrl();
|
||||
const csrfCookie = getCookie('csrftoken');
|
||||
|
||||
const response = await fetch(`${apiUrl}/api/appsettings/cookie/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...defaultHeaders,
|
||||
'X-CSRFToken': csrfCookie || '',
|
||||
},
|
||||
credentials: getFetchCredentials(),
|
||||
});
|
||||
|
||||
const validatedCookie = await response.json();
|
||||
console.log('updateCookie', validatedCookie);
|
||||
|
||||
return validatedCookie;
|
||||
};
|
||||
|
||||
export default updateCookie;
|
||||
|
@ -1,47 +1,47 @@
|
||||
import defaultHeaders from '../../configuration/defaultHeaders';
|
||||
import getApiUrl from '../../configuration/getApiUrl';
|
||||
import getFetchCredentials from '../../configuration/getFetchCredentials';
|
||||
import getCookie from '../../functions/getCookie';
|
||||
|
||||
const updateDownloadQueue = async (youtubeIdStrings: string, autostart: boolean) => {
|
||||
const apiUrl = getApiUrl();
|
||||
const csrfCookie = getCookie('csrftoken');
|
||||
|
||||
const urls = [];
|
||||
const containsMultiple = youtubeIdStrings.includes('\n');
|
||||
|
||||
if (containsMultiple) {
|
||||
const youtubeIds = youtubeIdStrings.split('\n');
|
||||
|
||||
youtubeIds.forEach(youtubeId => {
|
||||
urls.push({ youtube_id: youtubeId, status: 'pending' });
|
||||
});
|
||||
} else {
|
||||
urls.push({ youtube_id: youtubeIdStrings, status: 'pending' });
|
||||
}
|
||||
|
||||
let params = '';
|
||||
if (autostart) {
|
||||
params = '?autostart=true';
|
||||
}
|
||||
|
||||
const response = await fetch(`${apiUrl}/api/download/${params}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...defaultHeaders,
|
||||
'X-CSRFToken': csrfCookie || '',
|
||||
},
|
||||
credentials: getFetchCredentials(),
|
||||
|
||||
body: JSON.stringify({
|
||||
data: [...urls],
|
||||
}),
|
||||
});
|
||||
|
||||
const downloadState = await response.json();
|
||||
console.log('updateDownloadQueue', downloadState);
|
||||
|
||||
return downloadState;
|
||||
};
|
||||
|
||||
export default updateDownloadQueue;
|
||||
import defaultHeaders from '../../configuration/defaultHeaders';
|
||||
import getApiUrl from '../../configuration/getApiUrl';
|
||||
import getFetchCredentials from '../../configuration/getFetchCredentials';
|
||||
import getCookie from '../../functions/getCookie';
|
||||
|
||||
const updateDownloadQueue = async (youtubeIdStrings: string, autostart: boolean) => {
|
||||
const apiUrl = getApiUrl();
|
||||
const csrfCookie = getCookie('csrftoken');
|
||||
|
||||
const urls = [];
|
||||
const containsMultiple = youtubeIdStrings.includes('\n');
|
||||
|
||||
if (containsMultiple) {
|
||||
const youtubeIds = youtubeIdStrings.split('\n');
|
||||
|
||||
youtubeIds.forEach(youtubeId => {
|
||||
urls.push({ youtube_id: youtubeId, status: 'pending' });
|
||||
});
|
||||
} else {
|
||||
urls.push({ youtube_id: youtubeIdStrings, status: 'pending' });
|
||||
}
|
||||
|
||||
let params = '';
|
||||
if (autostart) {
|
||||
params = '?autostart=true';
|
||||
}
|
||||
|
||||
const response = await fetch(`${apiUrl}/api/download/${params}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...defaultHeaders,
|
||||
'X-CSRFToken': csrfCookie || '',
|
||||
},
|
||||
credentials: getFetchCredentials(),
|
||||
|
||||
body: JSON.stringify({
|
||||
data: [...urls],
|
||||
}),
|
||||
});
|
||||
|
||||
const downloadState = await response.json();
|
||||
console.log('updateDownloadQueue', downloadState);
|
||||
|
||||
return downloadState;
|
||||
};
|
||||
|
||||
export default updateDownloadQueue;
|
||||
|
@ -1,42 +1,42 @@
|
||||
import defaultHeaders from '../../configuration/defaultHeaders';
|
||||
import getApiUrl from '../../configuration/getApiUrl';
|
||||
import getFetchCredentials from '../../configuration/getFetchCredentials';
|
||||
import getCookie from '../../functions/getCookie';
|
||||
|
||||
const updatePlaylistSubscription = async (playlistIds: string, status: boolean) => {
|
||||
const apiUrl = getApiUrl();
|
||||
const csrfCookie = getCookie('csrftoken');
|
||||
|
||||
const playlists = [];
|
||||
const containsMultiple = playlistIds.includes('\n');
|
||||
|
||||
if (containsMultiple) {
|
||||
const youtubePlaylistIds = playlistIds.split('\n');
|
||||
|
||||
youtubePlaylistIds.forEach(playlistId => {
|
||||
playlists.push({ playlist_id: playlistId, playlist_subscribed: status });
|
||||
});
|
||||
} else {
|
||||
playlists.push({ playlist_id: playlistIds, playlist_subscribed: status });
|
||||
}
|
||||
|
||||
const response = await fetch(`${apiUrl}/api/playlist/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...defaultHeaders,
|
||||
'X-CSRFToken': csrfCookie || '',
|
||||
},
|
||||
credentials: getFetchCredentials(),
|
||||
|
||||
body: JSON.stringify({
|
||||
data: [...playlists],
|
||||
}),
|
||||
});
|
||||
|
||||
const playlistSubscription = await response.json();
|
||||
console.log('updatePlaylistSubscription', playlistSubscription);
|
||||
|
||||
return playlistSubscription;
|
||||
};
|
||||
|
||||
export default updatePlaylistSubscription;
|
||||
import defaultHeaders from '../../configuration/defaultHeaders';
|
||||
import getApiUrl from '../../configuration/getApiUrl';
|
||||
import getFetchCredentials from '../../configuration/getFetchCredentials';
|
||||
import getCookie from '../../functions/getCookie';
|
||||
|
||||
const updatePlaylistSubscription = async (playlistIds: string, status: boolean) => {
|
||||
const apiUrl = getApiUrl();
|
||||
const csrfCookie = getCookie('csrftoken');
|
||||
|
||||
const playlists = [];
|
||||
const containsMultiple = playlistIds.includes('\n');
|
||||
|
||||
if (containsMultiple) {
|
||||
const youtubePlaylistIds = playlistIds.split('\n');
|
||||
|
||||
youtubePlaylistIds.forEach(playlistId => {
|
||||
playlists.push({ playlist_id: playlistId, playlist_subscribed: status });
|
||||
});
|
||||
} else {
|
||||
playlists.push({ playlist_id: playlistIds, playlist_subscribed: status });
|
||||
}
|
||||
|
||||
const response = await fetch(`${apiUrl}/api/playlist/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...defaultHeaders,
|
||||
'X-CSRFToken': csrfCookie || '',
|
||||
},
|
||||
credentials: getFetchCredentials(),
|
||||
|
||||
body: JSON.stringify({
|
||||
data: [...playlists],
|
||||
}),
|
||||
});
|
||||
|
||||
const playlistSubscription = await response.json();
|
||||
console.log('updatePlaylistSubscription', playlistSubscription);
|
||||
|
||||
return playlistSubscription;
|
||||
};
|
||||
|
||||
export default updatePlaylistSubscription;
|
||||
|
@ -1,36 +1,36 @@
|
||||
import defaultHeaders from '../../configuration/defaultHeaders';
|
||||
import getApiUrl from '../../configuration/getApiUrl';
|
||||
import getFetchCredentials from '../../configuration/getFetchCredentials';
|
||||
import getCookie from '../../functions/getCookie';
|
||||
import isDevEnvironment from '../../functions/isDevEnvironment';
|
||||
|
||||
type ApiTokenResponse = {
|
||||
token: string;
|
||||
};
|
||||
|
||||
const loadApiToken = async (): Promise<ApiTokenResponse> => {
|
||||
const apiUrl = getApiUrl();
|
||||
const csrfCookie = getCookie('csrftoken');
|
||||
|
||||
try {
|
||||
const response = await fetch(`${apiUrl}/api/appsettings/token/`, {
|
||||
headers: {
|
||||
...defaultHeaders,
|
||||
'X-CSRFToken': csrfCookie || '',
|
||||
},
|
||||
credentials: getFetchCredentials(),
|
||||
});
|
||||
|
||||
const apiToken = await response.json();
|
||||
|
||||
if (isDevEnvironment()) {
|
||||
console.log('loadApiToken', apiToken);
|
||||
}
|
||||
|
||||
return apiToken;
|
||||
} catch (e) {
|
||||
return { token: '' };
|
||||
}
|
||||
};
|
||||
|
||||
export default loadApiToken;
|
||||
import defaultHeaders from '../../configuration/defaultHeaders';
|
||||
import getApiUrl from '../../configuration/getApiUrl';
|
||||
import getFetchCredentials from '../../configuration/getFetchCredentials';
|
||||
import getCookie from '../../functions/getCookie';
|
||||
import isDevEnvironment from '../../functions/isDevEnvironment';
|
||||
|
||||
type ApiTokenResponse = {
|
||||
token: string;
|
||||
};
|
||||
|
||||
const loadApiToken = async (): Promise<ApiTokenResponse> => {
|
||||
const apiUrl = getApiUrl();
|
||||
const csrfCookie = getCookie('csrftoken');
|
||||
|
||||
try {
|
||||
const response = await fetch(`${apiUrl}/api/appsettings/token/`, {
|
||||
headers: {
|
||||
...defaultHeaders,
|
||||
'X-CSRFToken': csrfCookie || '',
|
||||
},
|
||||
credentials: getFetchCredentials(),
|
||||
});
|
||||
|
||||
const apiToken = await response.json();
|
||||
|
||||
if (isDevEnvironment()) {
|
||||
console.log('loadApiToken', apiToken);
|
||||
}
|
||||
|
||||
return apiToken;
|
||||
} catch (e) {
|
||||
return { token: '' };
|
||||
}
|
||||
};
|
||||
|
||||
export default loadApiToken;
|
||||
|
@ -1,54 +1,54 @@
|
||||
import defaultHeaders from '../../configuration/defaultHeaders';
|
||||
import getApiUrl from '../../configuration/getApiUrl';
|
||||
import getFetchCredentials from '../../configuration/getFetchCredentials';
|
||||
import isDevEnvironment from '../../functions/isDevEnvironment';
|
||||
|
||||
export type AppSettingsConfigType = {
|
||||
subscriptions: {
|
||||
channel_size: number;
|
||||
live_channel_size: number;
|
||||
shorts_channel_size: number;
|
||||
auto_start: boolean;
|
||||
};
|
||||
downloads: {
|
||||
limit_speed: false | number;
|
||||
sleep_interval: number;
|
||||
autodelete_days: number;
|
||||
format: number | string;
|
||||
format_sort: boolean | string;
|
||||
add_metadata: boolean;
|
||||
add_thumbnail: boolean;
|
||||
subtitle: boolean | string;
|
||||
subtitle_source: boolean | string;
|
||||
subtitle_index: boolean;
|
||||
comment_max: string | number;
|
||||
comment_sort: string;
|
||||
cookie_import: boolean;
|
||||
throttledratelimit: false | number;
|
||||
extractor_lang: boolean | string;
|
||||
integrate_ryd: boolean;
|
||||
integrate_sponsorblock: boolean;
|
||||
};
|
||||
application: {
|
||||
enable_snapshot: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
const loadAppsettingsConfig = async (): Promise<AppSettingsConfigType> => {
|
||||
const apiUrl = getApiUrl();
|
||||
|
||||
const response = await fetch(`${apiUrl}/api/appsettings/config/`, {
|
||||
headers: defaultHeaders,
|
||||
credentials: getFetchCredentials(),
|
||||
});
|
||||
|
||||
const appSettingsConfig = await response.json();
|
||||
|
||||
if (isDevEnvironment()) {
|
||||
console.log('loadApplicationConfig', appSettingsConfig);
|
||||
}
|
||||
|
||||
return appSettingsConfig;
|
||||
};
|
||||
|
||||
export default loadAppsettingsConfig;
|
||||
import defaultHeaders from '../../configuration/defaultHeaders';
|
||||
import getApiUrl from '../../configuration/getApiUrl';
|
||||
import getFetchCredentials from '../../configuration/getFetchCredentials';
|
||||
import isDevEnvironment from '../../functions/isDevEnvironment';
|
||||
|
||||
export type AppSettingsConfigType = {
|
||||
subscriptions: {
|
||||
channel_size: number;
|
||||
live_channel_size: number;
|
||||
shorts_channel_size: number;
|
||||
auto_start: boolean;
|
||||
};
|
||||
downloads: {
|
||||
limit_speed: false | number;
|
||||
sleep_interval: number;
|
||||
autodelete_days: number;
|
||||
format: number | string;
|
||||
format_sort: boolean | string;
|
||||
add_metadata: boolean;
|
||||
add_thumbnail: boolean;
|
||||
subtitle: boolean | string;
|
||||
subtitle_source: boolean | string;
|
||||
subtitle_index: boolean;
|
||||
comment_max: string | number;
|
||||
comment_sort: string;
|
||||
cookie_import: boolean;
|
||||
throttledratelimit: false | number;
|
||||
extractor_lang: boolean | string;
|
||||
integrate_ryd: boolean;
|
||||
integrate_sponsorblock: boolean;
|
||||
};
|
||||
application: {
|
||||
enable_snapshot: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
const loadAppsettingsConfig = async (): Promise<AppSettingsConfigType> => {
|
||||
const apiUrl = getApiUrl();
|
||||
|
||||
const response = await fetch(`${apiUrl}/api/appsettings/config/`, {
|
||||
headers: defaultHeaders,
|
||||
credentials: getFetchCredentials(),
|
||||
});
|
||||
|
||||
const appSettingsConfig = await response.json();
|
||||
|
||||
if (isDevEnvironment()) {
|
||||
console.log('loadApplicationConfig', appSettingsConfig);
|
||||
}
|
||||
|
||||
return appSettingsConfig;
|
||||
};
|
||||
|
||||
export default loadAppsettingsConfig;
|
||||
|
@ -1,74 +1,74 @@
|
||||
import defaultHeaders from '../../configuration/defaultHeaders';
|
||||
import getApiUrl from '../../configuration/getApiUrl';
|
||||
import getFetchCredentials from '../../configuration/getFetchCredentials';
|
||||
import isDevEnvironment from '../../functions/isDevEnvironment';
|
||||
import { ConfigType, SortByType, SortOrderType, VideoType } from '../../pages/Home';
|
||||
import { PaginationType } from '../../components/Pagination';
|
||||
|
||||
export type VideoListByFilterResponseType = {
|
||||
data?: VideoType[];
|
||||
config?: ConfigType;
|
||||
paginate?: PaginationType;
|
||||
};
|
||||
|
||||
type WatchTypes = 'watched' | 'unwatched' | 'continue';
|
||||
export type VideoTypes = 'videos' | 'streams' | 'shorts';
|
||||
|
||||
type FilterType = {
|
||||
page?: number;
|
||||
playlist?: string;
|
||||
channel?: string;
|
||||
watch?: WatchTypes;
|
||||
sort?: SortByType;
|
||||
order?: SortOrderType;
|
||||
type?: VideoTypes;
|
||||
};
|
||||
|
||||
const loadVideoListByFilter = async (
|
||||
filter: FilterType,
|
||||
): Promise<VideoListByFilterResponseType> => {
|
||||
const apiUrl = getApiUrl();
|
||||
|
||||
const searchParams = new URLSearchParams();
|
||||
|
||||
if (filter.page) {
|
||||
searchParams.append('page', filter.page.toString());
|
||||
}
|
||||
|
||||
if (filter.playlist) {
|
||||
searchParams.append('playlist', filter.playlist);
|
||||
} else if (filter.channel) {
|
||||
searchParams.append('channel', filter.channel);
|
||||
}
|
||||
|
||||
if (filter.watch) {
|
||||
searchParams.append('watch', filter.watch);
|
||||
}
|
||||
|
||||
if (filter.sort) {
|
||||
searchParams.append('sort', filter.sort);
|
||||
}
|
||||
|
||||
if (filter.order) {
|
||||
searchParams.append('order', filter.order);
|
||||
}
|
||||
|
||||
if (filter.type) {
|
||||
searchParams.append('type', filter.type);
|
||||
}
|
||||
|
||||
const response = await fetch(`${apiUrl}/api/video/?${searchParams.toString()}`, {
|
||||
headers: defaultHeaders,
|
||||
credentials: getFetchCredentials(),
|
||||
});
|
||||
|
||||
const videos = await response.json();
|
||||
|
||||
if (isDevEnvironment()) {
|
||||
console.log('loadVideoListByFilter', filter, videos);
|
||||
}
|
||||
|
||||
return videos;
|
||||
};
|
||||
|
||||
export default loadVideoListByFilter;
|
||||
import defaultHeaders from '../../configuration/defaultHeaders';
|
||||
import getApiUrl from '../../configuration/getApiUrl';
|
||||
import getFetchCredentials from '../../configuration/getFetchCredentials';
|
||||
import isDevEnvironment from '../../functions/isDevEnvironment';
|
||||
import { ConfigType, SortByType, SortOrderType, VideoType } from '../../pages/Home';
|
||||
import { PaginationType } from '../../components/Pagination';
|
||||
|
||||
export type VideoListByFilterResponseType = {
|
||||
data?: VideoType[];
|
||||
config?: ConfigType;
|
||||
paginate?: PaginationType;
|
||||
};
|
||||
|
||||
type WatchTypes = 'watched' | 'unwatched' | 'continue';
|
||||
export type VideoTypes = 'videos' | 'streams' | 'shorts';
|
||||
|
||||
type FilterType = {
|
||||
page?: number;
|
||||
playlist?: string;
|
||||
channel?: string;
|
||||
watch?: WatchTypes;
|
||||
sort?: SortByType;
|
||||
order?: SortOrderType;
|
||||
type?: VideoTypes;
|
||||
};
|
||||
|
||||
const loadVideoListByFilter = async (
|
||||
filter: FilterType,
|
||||
): Promise<VideoListByFilterResponseType> => {
|
||||
const apiUrl = getApiUrl();
|
||||
|
||||
const searchParams = new URLSearchParams();
|
||||
|
||||
if (filter.page) {
|
||||
searchParams.append('page', filter.page.toString());
|
||||
}
|
||||
|
||||
if (filter.playlist) {
|
||||
searchParams.append('playlist', filter.playlist);
|
||||
} else if (filter.channel) {
|
||||
searchParams.append('channel', filter.channel);
|
||||
}
|
||||
|
||||
if (filter.watch) {
|
||||
searchParams.append('watch', filter.watch);
|
||||
}
|
||||
|
||||
if (filter.sort) {
|
||||
searchParams.append('sort', filter.sort);
|
||||
}
|
||||
|
||||
if (filter.order) {
|
||||
searchParams.append('order', filter.order);
|
||||
}
|
||||
|
||||
if (filter.type) {
|
||||
searchParams.append('type', filter.type);
|
||||
}
|
||||
|
||||
const response = await fetch(`${apiUrl}/api/video/?${searchParams.toString()}`, {
|
||||
headers: defaultHeaders,
|
||||
credentials: getFetchCredentials(),
|
||||
});
|
||||
|
||||
const videos = await response.json();
|
||||
|
||||
if (isDevEnvironment()) {
|
||||
console.log('loadVideoListByFilter', filter, videos);
|
||||
}
|
||||
|
||||
return videos;
|
||||
};
|
||||
|
||||
export default loadVideoListByFilter;
|
||||
|
@ -1,42 +1,42 @@
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
export interface ButtonProps {
|
||||
id?: string;
|
||||
name?: string;
|
||||
className?: string;
|
||||
type?: 'submit' | 'reset' | 'button' | undefined;
|
||||
label?: string | ReactNode | ReactNode[];
|
||||
children?: string | ReactNode | ReactNode[];
|
||||
value?: string;
|
||||
title?: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
const Button = ({
|
||||
id,
|
||||
name,
|
||||
className,
|
||||
type,
|
||||
label,
|
||||
children,
|
||||
value,
|
||||
title,
|
||||
onClick,
|
||||
}: ButtonProps) => {
|
||||
return (
|
||||
<button
|
||||
id={id}
|
||||
name={name}
|
||||
className={className}
|
||||
type={type}
|
||||
value={value}
|
||||
title={title}
|
||||
onClick={onClick}
|
||||
>
|
||||
{label}
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default Button;
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
export interface ButtonProps {
|
||||
id?: string;
|
||||
name?: string;
|
||||
className?: string;
|
||||
type?: 'submit' | 'reset' | 'button' | undefined;
|
||||
label?: string | ReactNode | ReactNode[];
|
||||
children?: string | ReactNode | ReactNode[];
|
||||
value?: string;
|
||||
title?: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
const Button = ({
|
||||
id,
|
||||
name,
|
||||
className,
|
||||
type,
|
||||
label,
|
||||
children,
|
||||
value,
|
||||
title,
|
||||
onClick,
|
||||
}: ButtonProps) => {
|
||||
return (
|
||||
<button
|
||||
id={id}
|
||||
name={name}
|
||||
className={className}
|
||||
type={type}
|
||||
value={value}
|
||||
title={title}
|
||||
onClick={onClick}
|
||||
>
|
||||
{label}
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default Button;
|
||||
|
@ -1,22 +1,22 @@
|
||||
import getApiUrl from '../configuration/getApiUrl';
|
||||
import defaultChannelImage from '/img/default-channel-banner.jpg';
|
||||
|
||||
type ChannelIconProps = {
|
||||
channelId: string;
|
||||
channelBannerUrl: string | undefined;
|
||||
};
|
||||
|
||||
const ChannelBanner = ({ channelId, channelBannerUrl }: ChannelIconProps) => {
|
||||
return (
|
||||
<img
|
||||
src={`${getApiUrl()}${channelBannerUrl}`}
|
||||
alt={`${channelId}-banner`}
|
||||
onError={({ currentTarget }) => {
|
||||
currentTarget.onerror = null; // prevents looping
|
||||
currentTarget.src = defaultChannelImage;
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChannelBanner;
|
||||
import getApiUrl from '../configuration/getApiUrl';
|
||||
import defaultChannelImage from '/img/default-channel-banner.jpg';
|
||||
|
||||
type ChannelIconProps = {
|
||||
channelId: string;
|
||||
channelBannerUrl: string | undefined;
|
||||
};
|
||||
|
||||
const ChannelBanner = ({ channelId, channelBannerUrl }: ChannelIconProps) => {
|
||||
return (
|
||||
<img
|
||||
src={`${getApiUrl()}${channelBannerUrl}`}
|
||||
alt={`${channelId}-banner`}
|
||||
onError={({ currentTarget }) => {
|
||||
currentTarget.onerror = null; // prevents looping
|
||||
currentTarget.src = defaultChannelImage;
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChannelBanner;
|
||||
|
@ -1,22 +1,22 @@
|
||||
import getApiUrl from '../configuration/getApiUrl';
|
||||
import defaultChannelIcon from '/img/default-channel-icon.jpg';
|
||||
|
||||
type ChannelIconProps = {
|
||||
channelId: string;
|
||||
channelThumbUrl: string | undefined;
|
||||
};
|
||||
|
||||
const ChannelIcon = ({ channelId, channelThumbUrl }: ChannelIconProps) => {
|
||||
return (
|
||||
<img
|
||||
src={`${getApiUrl()}${channelThumbUrl}`}
|
||||
alt={`${channelId}-thumb`}
|
||||
onError={({ currentTarget }) => {
|
||||
currentTarget.onerror = null; // prevents looping
|
||||
currentTarget.src = defaultChannelIcon;
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChannelIcon;
|
||||
import getApiUrl from '../configuration/getApiUrl';
|
||||
import defaultChannelIcon from '/img/default-channel-icon.jpg';
|
||||
|
||||
type ChannelIconProps = {
|
||||
channelId: string;
|
||||
channelThumbUrl: string | undefined;
|
||||
};
|
||||
|
||||
const ChannelIcon = ({ channelId, channelThumbUrl }: ChannelIconProps) => {
|
||||
return (
|
||||
<img
|
||||
src={`${getApiUrl()}${channelThumbUrl}`}
|
||||
alt={`${channelId}-thumb`}
|
||||
onError={({ currentTarget }) => {
|
||||
currentTarget.onerror = null; // prevents looping
|
||||
currentTarget.src = defaultChannelIcon;
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChannelIcon;
|
||||
|
@ -1,98 +1,97 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import { ChannelType } from '../pages/Channels';
|
||||
import Routes from '../configuration/routes/RouteList';
|
||||
import updateChannelSubscription from '../api/actions/updateChannelSubscription';
|
||||
import formatDate from '../functions/formatDates';
|
||||
import FormattedNumber from './FormattedNumber';
|
||||
import Button from './Button';
|
||||
import ChannelIcon from './ChannelIcon';
|
||||
import ChannelBanner from './ChannelBanner';
|
||||
import { useUserConfigStore } from '../stores/UserConfigStore';
|
||||
|
||||
type ChannelListProps = {
|
||||
channelList: ChannelType[] | undefined;
|
||||
refreshChannelList: (refresh: boolean) => void;
|
||||
};
|
||||
|
||||
const ChannelList = ({ channelList, refreshChannelList }: ChannelListProps) => {
|
||||
|
||||
const { userConfig } = useUserConfigStore();
|
||||
const viewLayout = userConfig.config.view_style_channel;
|
||||
|
||||
if (!channelList || channelList.length === 0) {
|
||||
return <p>No channels found.</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{channelList.map(channel => {
|
||||
return (
|
||||
<div key={channel.channel_id} className={`channel-item ${viewLayout}`}>
|
||||
<div className={`channel-banner ${viewLayout}`}>
|
||||
<Link to={Routes.Channel(channel.channel_id)}>
|
||||
<ChannelBanner
|
||||
channelId={channel.channel_id}
|
||||
channelBannerUrl={channel.channel_banner_url}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
<div className={`info-box info-box-2 ${viewLayout}`}>
|
||||
<div className="info-box-item">
|
||||
<div className="round-img">
|
||||
<Link to={Routes.Channel(channel.channel_id)}>
|
||||
<ChannelIcon
|
||||
channelId={channel.channel_id}
|
||||
channelThumbUrl={channel.channel_thumb_url}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
<div>
|
||||
<h3>
|
||||
<Link to={Routes.Channel(channel.channel_id)}>{channel.channel_name}</Link>
|
||||
</h3>
|
||||
<FormattedNumber text="Subscribers:" number={channel.channel_subs} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="info-box-item">
|
||||
<div>
|
||||
<p>Last refreshed: {formatDate(channel.channel_last_refresh)}</p>
|
||||
{channel.channel_subscribed && (
|
||||
<Button
|
||||
label="Unsubscribe"
|
||||
className="unsubscribe"
|
||||
type="button"
|
||||
title={`Unsubscribe from ${channel.channel_name}`}
|
||||
onClick={async () => {
|
||||
await updateChannelSubscription(channel.channel_id, false);
|
||||
setTimeout(() => {
|
||||
refreshChannelList(true);
|
||||
}, 1000);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!channel.channel_subscribed && (
|
||||
<Button
|
||||
label="Subscribe"
|
||||
type="button"
|
||||
title={`Subscribe to ${channel.channel_name}`}
|
||||
onClick={async () => {
|
||||
await updateChannelSubscription(channel.channel_id, true);
|
||||
|
||||
setTimeout(() => {
|
||||
refreshChannelList(true);
|
||||
}, 500);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChannelList;
|
||||
import { Link } from 'react-router-dom';
|
||||
import { ChannelType } from '../pages/Channels';
|
||||
import Routes from '../configuration/routes/RouteList';
|
||||
import updateChannelSubscription from '../api/actions/updateChannelSubscription';
|
||||
import formatDate from '../functions/formatDates';
|
||||
import FormattedNumber from './FormattedNumber';
|
||||
import Button from './Button';
|
||||
import ChannelIcon from './ChannelIcon';
|
||||
import ChannelBanner from './ChannelBanner';
|
||||
import { useUserConfigStore } from '../stores/UserConfigStore';
|
||||
|
||||
type ChannelListProps = {
|
||||
channelList: ChannelType[] | undefined;
|
||||
refreshChannelList: (refresh: boolean) => void;
|
||||
};
|
||||
|
||||
const ChannelList = ({ channelList, refreshChannelList }: ChannelListProps) => {
|
||||
const { userConfig } = useUserConfigStore();
|
||||
const viewLayout = userConfig.config.view_style_channel;
|
||||
|
||||
if (!channelList || channelList.length === 0) {
|
||||
return <p>No channels found.</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{channelList.map(channel => {
|
||||
return (
|
||||
<div key={channel.channel_id} className={`channel-item ${viewLayout}`}>
|
||||
<div className={`channel-banner ${viewLayout}`}>
|
||||
<Link to={Routes.Channel(channel.channel_id)}>
|
||||
<ChannelBanner
|
||||
channelId={channel.channel_id}
|
||||
channelBannerUrl={channel.channel_banner_url}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
<div className={`info-box info-box-2 ${viewLayout}`}>
|
||||
<div className="info-box-item">
|
||||
<div className="round-img">
|
||||
<Link to={Routes.Channel(channel.channel_id)}>
|
||||
<ChannelIcon
|
||||
channelId={channel.channel_id}
|
||||
channelThumbUrl={channel.channel_thumb_url}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
<div>
|
||||
<h3>
|
||||
<Link to={Routes.Channel(channel.channel_id)}>{channel.channel_name}</Link>
|
||||
</h3>
|
||||
<FormattedNumber text="Subscribers:" number={channel.channel_subs} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="info-box-item">
|
||||
<div>
|
||||
<p>Last refreshed: {formatDate(channel.channel_last_refresh)}</p>
|
||||
{channel.channel_subscribed && (
|
||||
<Button
|
||||
label="Unsubscribe"
|
||||
className="unsubscribe"
|
||||
type="button"
|
||||
title={`Unsubscribe from ${channel.channel_name}`}
|
||||
onClick={async () => {
|
||||
await updateChannelSubscription(channel.channel_id, false);
|
||||
setTimeout(() => {
|
||||
refreshChannelList(true);
|
||||
}, 1000);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!channel.channel_subscribed && (
|
||||
<Button
|
||||
label="Subscribe"
|
||||
type="button"
|
||||
title={`Subscribe to ${channel.channel_name}`}
|
||||
onClick={async () => {
|
||||
await updateChannelSubscription(channel.channel_id, true);
|
||||
|
||||
setTimeout(() => {
|
||||
refreshChannelList(true);
|
||||
}, 500);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChannelList;
|
||||
|
@ -1,78 +1,78 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import Routes from '../configuration/routes/RouteList';
|
||||
import updateChannelSubscription from '../api/actions/updateChannelSubscription';
|
||||
import FormattedNumber from './FormattedNumber';
|
||||
import Button from './Button';
|
||||
import ChannelIcon from './ChannelIcon';
|
||||
|
||||
type ChannelOverviewProps = {
|
||||
channelId: string;
|
||||
channelname: string;
|
||||
channelSubs: number;
|
||||
channelSubscribed: boolean;
|
||||
channelThumbUrl: string;
|
||||
showSubscribeButton?: boolean;
|
||||
isUserAdmin?: boolean;
|
||||
setRefresh: (status: boolean) => void;
|
||||
};
|
||||
|
||||
const ChannelOverview = ({
|
||||
channelId,
|
||||
channelSubs,
|
||||
channelSubscribed,
|
||||
channelname,
|
||||
channelThumbUrl,
|
||||
showSubscribeButton = false,
|
||||
isUserAdmin,
|
||||
setRefresh,
|
||||
}: ChannelOverviewProps) => {
|
||||
return (
|
||||
<>
|
||||
<div className="info-box-item">
|
||||
<div className="round-img">
|
||||
<Link to={Routes.Channel(channelId)}>
|
||||
<ChannelIcon channelId={channelId} channelThumbUrl={channelThumbUrl} />
|
||||
</Link>
|
||||
</div>
|
||||
<div>
|
||||
<h3>
|
||||
<Link to={Routes.ChannelVideo(channelId)}>{channelname}</Link>
|
||||
</h3>
|
||||
|
||||
<FormattedNumber text="Subscribers:" number={channelSubs} />
|
||||
|
||||
{showSubscribeButton && (
|
||||
<>
|
||||
{channelSubscribed && isUserAdmin && (
|
||||
<Button
|
||||
label="Unsubscribe"
|
||||
className="unsubscribe"
|
||||
type="button"
|
||||
title={`Unsubscribe from ${channelname}`}
|
||||
onClick={async () => {
|
||||
await updateChannelSubscription(channelId, false);
|
||||
setRefresh(true);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!channelSubscribed && (
|
||||
<Button
|
||||
label="Subscribe"
|
||||
type="button"
|
||||
title={`Subscribe to ${channelname}`}
|
||||
onClick={async () => {
|
||||
await updateChannelSubscription(channelId, true);
|
||||
setRefresh(true);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChannelOverview;
|
||||
import { Link } from 'react-router-dom';
|
||||
import Routes from '../configuration/routes/RouteList';
|
||||
import updateChannelSubscription from '../api/actions/updateChannelSubscription';
|
||||
import FormattedNumber from './FormattedNumber';
|
||||
import Button from './Button';
|
||||
import ChannelIcon from './ChannelIcon';
|
||||
|
||||
type ChannelOverviewProps = {
|
||||
channelId: string;
|
||||
channelname: string;
|
||||
channelSubs: number;
|
||||
channelSubscribed: boolean;
|
||||
channelThumbUrl: string;
|
||||
showSubscribeButton?: boolean;
|
||||
isUserAdmin?: boolean;
|
||||
setRefresh: (status: boolean) => void;
|
||||
};
|
||||
|
||||
const ChannelOverview = ({
|
||||
channelId,
|
||||
channelSubs,
|
||||
channelSubscribed,
|
||||
channelname,
|
||||
channelThumbUrl,
|
||||
showSubscribeButton = false,
|
||||
isUserAdmin,
|
||||
setRefresh,
|
||||
}: ChannelOverviewProps) => {
|
||||
return (
|
||||
<>
|
||||
<div className="info-box-item">
|
||||
<div className="round-img">
|
||||
<Link to={Routes.Channel(channelId)}>
|
||||
<ChannelIcon channelId={channelId} channelThumbUrl={channelThumbUrl} />
|
||||
</Link>
|
||||
</div>
|
||||
<div>
|
||||
<h3>
|
||||
<Link to={Routes.ChannelVideo(channelId)}>{channelname}</Link>
|
||||
</h3>
|
||||
|
||||
<FormattedNumber text="Subscribers:" number={channelSubs} />
|
||||
|
||||
{showSubscribeButton && (
|
||||
<>
|
||||
{channelSubscribed && isUserAdmin && (
|
||||
<Button
|
||||
label="Unsubscribe"
|
||||
className="unsubscribe"
|
||||
type="button"
|
||||
title={`Unsubscribe from ${channelname}`}
|
||||
onClick={async () => {
|
||||
await updateChannelSubscription(channelId, false);
|
||||
setRefresh(true);
|
||||