diff --git a/frontend/src/api/actions/updateAppsettingsConfig.ts b/frontend/src/api/actions/updateAppsettingsConfig.ts index 59207d51..462ca3ed 100644 --- a/frontend/src/api/actions/updateAppsettingsConfig.ts +++ b/frontend/src/api/actions/updateAppsettingsConfig.ts @@ -1,37 +1,12 @@ import APIClient from '../../functions/APIClient'; -import { AppSettingsConfigType } from '../loader/loadAppsettingsConfig'; -type ObjectType = Record< - string, - string | number | boolean | Record ->; - -function flattenObject(ob: ObjectType) { - // source: https://stackoverflow.com/a/53739792 - const toReturn: ObjectType = {}; - - for (const i in ob) { - if (!Object.prototype.hasOwnProperty.call(ob, i)) continue; - - if (typeof ob[i] == 'object' && ob[i] !== null) { - const flatObject = flattenObject(ob[i]); - for (const x in flatObject) { - if (!Object.prototype.hasOwnProperty.call(flatObject, x)) continue; - - toReturn[i + '.' + x] = flatObject[x]; - } - } else { - toReturn[i] = ob[i]; - } - } - - return toReturn; -} - -const updateAppsettingsConfig = async (config: AppSettingsConfigType) => { +const updateAppsettingsConfig = async ( + configKey: string, + configValue: string | boolean | number | null, +) => { return APIClient('/api/appsettings/config/', { method: 'POST', - body: flattenObject(config), + body: { [configKey]: configValue }, }); }; diff --git a/frontend/src/api/loader/loadAppsettingsConfig.ts b/frontend/src/api/loader/loadAppsettingsConfig.ts index 1aeacc22..0cad1885 100644 --- a/frontend/src/api/loader/loadAppsettingsConfig.ts +++ b/frontend/src/api/loader/loadAppsettingsConfig.ts @@ -8,21 +8,21 @@ export type AppSettingsConfigType = { auto_start: boolean; }; downloads: { - limit_speed: false | number; + limit_speed: number | undefined; sleep_interval: number; autodelete_days: number; - format: number | string; - format_sort: boolean | string; + format: string | undefined; + format_sort: string | undefined; add_metadata: boolean; add_thumbnail: boolean; - subtitle: boolean | string; + subtitle: string | undefined; subtitle_source: boolean | string; subtitle_index: boolean; - comment_max: string | number; + comment_max: string | undefined; comment_sort: string; cookie_import: boolean; - throttledratelimit: false | number; - extractor_lang: boolean | string; + throttledratelimit: number | undefined; + extractor_lang: string | undefined; integrate_ryd: boolean; integrate_sponsorblock: boolean; }; diff --git a/frontend/src/components/InputConfig.tsx b/frontend/src/components/InputConfig.tsx index ff093fa9..e4ab0d6d 100644 --- a/frontend/src/components/InputConfig.tsx +++ b/frontend/src/components/InputConfig.tsx @@ -6,7 +6,7 @@ type InputTextProps = { | React.Dispatch> | React.Dispatch>; oldValue: string | number | undefined; - updateCallback: (arg0: string, arg1: string | number | null) => void; + updateCallback: (arg0: string, arg1: string | boolean | number | null) => void; }; const InputConfig = ({ type, name, value, setValue, oldValue, updateCallback }: InputTextProps) => { diff --git a/frontend/src/pages/SettingsApplication.tsx b/frontend/src/pages/SettingsApplication.tsx index d1200171..d5130ddc 100644 --- a/frontend/src/pages/SettingsApplication.tsx +++ b/frontend/src/pages/SettingsApplication.tsx @@ -5,12 +5,13 @@ import PaginationDummy from '../components/PaginationDummy'; import SettingsNavigation from '../components/SettingsNavigation'; import restoreSnapshot from '../api/actions/restoreSnapshot'; import queueSnapshot from '../api/actions/queueSnapshot'; -import updateCookie, { ValidatedCookieType } from '../api/actions/updateCookie'; +// import updateCookie, { ValidatedCookieType } from '../api/actions/updateCookie'; import deleteApiToken from '../api/actions/deleteApiToken'; import Button from '../components/Button'; import loadAppsettingsConfig, { AppSettingsConfigType } from '../api/loader/loadAppsettingsConfig'; import updateAppsettingsConfig from '../api/actions/updateAppsettingsConfig'; import loadApiToken from '../api/loader/loadApiToken'; +import InputConfig from '../components/InputConfig'; type SnapshotType = { id: string; @@ -44,84 +45,48 @@ const SettingsApplication = () => { const apiToken = response?.apiToken; // Subscriptions - const [videoPageSize, setVideoPageSize] = useState(0); - const [livePageSize, setLivePageSize] = useState(0); - const [shortPageSize, setShortPageSize] = useState(0); - const [isAutostart, setIsAutostart] = useState(false); + const [videoPageSize, setVideoPageSize] = useState(null); + const [livePageSize, setLivePageSize] = useState(null); + const [shortPageSize, setShortPageSize] = useState(null); + const [isAutostart, setIsAutostart] = useState(false); // Downloads - const [currentDownloadSpeed, setCurrentDownloadSpeed] = useState(0); - const [currentThrottledRate, setCurrentThrottledRate] = useState(0); - const [currentScrapingSleep, setCurrentScrapingSleep] = useState(0); - const [currentAutodelete, setCurrentAutodelete] = useState(0); + const [currentDownloadSpeed, setCurrentDownloadSpeed] = useState(null); + const [currentThrottledRate, setCurrentThrottledRate] = useState(null); + const [currentScrapingSleep, setCurrentScrapingSleep] = useState(null); + const [currentAutodelete, setCurrentAutodelete] = useState(null); // Download Format - const [downloadsFormat, setDownloadsFormat] = useState(''); - const [downloadsFormatSort, setDownloadsFormatSort] = useState(''); - const [downloadsExtractorLang, setDownloadsExtractorLang] = useState(''); + const [downloadsFormat, setDownloadsFormat] = useState(null); + const [downloadsFormatSort, setDownloadsFormatSort] = useState(null); + const [downloadsExtractorLang, setDownloadsExtractorLang] = useState(null); const [embedMetadata, setEmbedMetadata] = useState(false); const [embedThumbnail, setEmbedThumbnail] = useState(false); // Subtitles - const [subtitleLang, setSubtitleLang] = useState(''); + const [subtitleLang, setSubtitleLang] = useState(null); const [subtitleSource, setSubtitleSource] = useState(''); const [indexSubtitles, setIndexSubtitles] = useState(false); // Comments - const [commentsMax, setCommentsMax] = useState(''); + const [commentsMax, setCommentsMax] = useState(null); const [commentsSort, setCommentsSort] = useState(''); // Cookie - const [cookieImport, setCookieImport] = useState(false); - const [validatingCookie, setValidatingCookie] = useState(false); - const [cookieResponse, setCookieResponse] = useState(); + // const [cookieImport, setCookieImport] = useState(false); + // const [validatingCookie, setValidatingCookie] = useState(false); + // const [cookieResponse, setCookieResponse] = useState(); // Integrations const [showApiToken, setShowApiToken] = useState(false); const [downloadDislikes, setDownloadDislikes] = useState(false); const [enableSponsorBlock, setEnableSponsorBlock] = useState(false); - const [resetTokenResponse, setResetTokenResponse] = useState({}); // Snapshots const [enableSnapshots, setEnableSnapshots] = useState(false); const [isSnapshotQueued, setIsSnapshotQueued] = useState(false); const [restoringSnapshot, setRestoringSnapshot] = useState(false); - const onSubmit = async () => { - await updateAppsettingsConfig({ - application: { - enable_snapshot: enableSnapshots, - }, - downloads: { - limit_speed: currentDownloadSpeed, - sleep_interval: currentScrapingSleep, - autodelete_days: currentAutodelete, - format: downloadsFormat, - format_sort: downloadsFormatSort, - add_metadata: embedMetadata, - add_thumbnail: embedThumbnail, - subtitle: subtitleLang, - subtitle_source: subtitleSource, - subtitle_index: indexSubtitles, - comment_max: commentsMax, - comment_sort: commentsSort, - cookie_import: cookieImport, - throttledratelimit: currentThrottledRate, - extractor_lang: downloadsExtractorLang, - integrate_ryd: downloadDislikes, - integrate_sponsorblock: enableSponsorBlock, - }, - subscriptions: { - auto_start: isAutostart, - channel_size: videoPageSize, - live_channel_size: livePageSize, - shorts_channel_size: shortPageSize, - }, - }); - - setRefresh(true); - }; - const fetchData = async () => { const snapshotResponse = await loadSnapshots(); const appSettingsConfig = await loadAppsettingsConfig(); @@ -134,29 +99,29 @@ const SettingsApplication = () => { setIsAutostart(appSettingsConfig?.subscriptions.auto_start); // Downloads - setCurrentDownloadSpeed(appSettingsConfig?.downloads.limit_speed || 0); - setCurrentThrottledRate(appSettingsConfig?.downloads.throttledratelimit || 0); + setCurrentDownloadSpeed(appSettingsConfig?.downloads.limit_speed || null); + setCurrentThrottledRate(appSettingsConfig?.downloads.throttledratelimit || null); setCurrentScrapingSleep(appSettingsConfig?.downloads.sleep_interval); setCurrentAutodelete(appSettingsConfig?.downloads.autodelete_days); // Download Format - setDownloadsFormat(appSettingsConfig?.downloads.format.toString()); - setDownloadsFormatSort(appSettingsConfig?.downloads.format_sort.toString()); - setDownloadsExtractorLang(appSettingsConfig?.downloads.extractor_lang.toString()); + setDownloadsFormat(appSettingsConfig?.downloads.format || null); + setDownloadsFormatSort(appSettingsConfig?.downloads.format_sort || null); + setDownloadsExtractorLang(appSettingsConfig?.downloads.extractor_lang || null); setEmbedMetadata(appSettingsConfig?.downloads.add_metadata); setEmbedThumbnail(appSettingsConfig?.downloads.add_thumbnail); // Subtitles - setSubtitleLang(appSettingsConfig?.downloads.subtitle.toString()); + setSubtitleLang(appSettingsConfig?.downloads.subtitle || null); setSubtitleSource(appSettingsConfig?.downloads.subtitle_source.toString()); setIndexSubtitles(appSettingsConfig?.downloads.subtitle_index); // Comments - setCommentsMax(appSettingsConfig?.downloads.comment_max.toString()); + setCommentsMax(appSettingsConfig?.downloads.comment_max || null); setCommentsSort(appSettingsConfig?.downloads.comment_sort); // Cookie - setCookieImport(appSettingsConfig?.downloads.cookie_import); + // setCookieImport(appSettingsConfig?.downloads.cookie_import); // Integrations setDownloadDislikes(appSettingsConfig?.downloads.integrate_ryd); @@ -172,6 +137,14 @@ const SettingsApplication = () => { }); }; + const handleUpdateConfig = async ( + configKey: string, + configValue: string | boolean | number | null, + ) => { + await updateAppsettingsConfig(configKey, configValue); + setRefresh(true); + }; + useEffect(() => { fetchData(); }, []); @@ -193,768 +166,507 @@ const SettingsApplication = () => {

Application Configurations

-
{ - event.preventDefault(); - }} - > -
+
+

Subscriptions

Disable shorts or streams by setting their page size to 0 (zero).

- -
-

- YouTube page size:{' '} - - {appSettingsConfig?.subscriptions.channel_size} - -

- - Videos to scan to find new items for the Rescan subscriptions task, max - recommended 50. - -
- +
+

Videos page size

+
+ { - setVideoPageSize(Number(event.target.value)); - }} + setValue={setVideoPageSize} + oldValue={appSettingsConfig?.subscriptions.channel_size} + updateCallback={handleUpdateConfig} />
- -
-

- YouTube Live page size:{' '} - - {appSettingsConfig?.subscriptions.live_channel_size} - -

- - Live Videos to scan to find new items for the Rescan subscriptions task, max - recommended 50. - -
- +
+

Live Streams page size

+
+ { - setLivePageSize(Number(event.target.value)); - }} + setValue={setLivePageSize} + oldValue={appSettingsConfig?.subscriptions.live_channel_size} + updateCallback={handleUpdateConfig} />
- -
-

- YouTube Shorts page size:{' '} - - {appSettingsConfig?.subscriptions.shorts_channel_size} - -

- - Shorts Videos to scan to find new items for the Rescan subscriptions task, - max recommended 50. - -
- +
+

Shorts page size

+
+ { - setShortPageSize(Number(event.target.value)); - }} + setValue={setShortPageSize} + oldValue={appSettingsConfig?.subscriptions.shorts_channel_size} + updateCallback={handleUpdateConfig} />
- -
-

- Auto start download from your subscriptions:{' '} - - {appSettingsConfig?.subscriptions.auto_start.toString()} - -

- - Enable this will automatically start and prioritize videos from your subscriptions. - -
- +
+
+

Autostart download subscriptions

+
+
+
+ { + handleUpdateConfig('subscriptions.auto_start', event.target.checked || false); + }} + /> + {!isAutostart && ( + + )} + {isAutostart && ( + + )} +
+
-
+

Downloads

- -
-

- Current download speed limit in KB/s:{' '} - {appSettingsConfig?.downloads.limit_speed} -

- - Limit download speed. 0 (zero) to deactivate, e.g. 1000 (1MB/s). Speeds are in KB/s. - Setting takes effect on new download jobs or application restart. - -
- +
+

Download Speed limit

+
+ { - setCurrentDownloadSpeed(Number(event.target.value)); - }} + name="downloads.limit_speed" + value={currentDownloadSpeed} + setValue={setCurrentDownloadSpeed} + oldValue={appSettingsConfig?.downloads.limit_speed} + updateCallback={handleUpdateConfig} />
- -
-

- Current throttled rate limit in KB/s:{' '} - - {appSettingsConfig?.downloads.throttledratelimit} - -

- - Download will restart if speeds drop below specified amount. 0 (zero) to deactivate, - e.g. 100. Speeds are in KB/s. - -
- +
+

Throttled rate limit

+
+ { - setCurrentThrottledRate(Number(event.target.value)); - }} + name="downloads.throttledratelimit" + value={currentThrottledRate} + setValue={setCurrentThrottledRate} + oldValue={appSettingsConfig?.downloads.throttledratelimit} + updateCallback={handleUpdateConfig} />
- -
-

- Current scraping sleep interval:{' '} - - {appSettingsConfig?.downloads.sleep_interval} - -

- - Seconds to sleep between calls to YouTube. Might be necessary to avoid throttling. - Recommended 3. - -
- +
+

Sleep interval

+
+ { - setCurrentScrapingSleep(Number(event.target.value)); - }} + setValue={setCurrentScrapingSleep} + oldValue={appSettingsConfig?.downloads.sleep_interval} + updateCallback={handleUpdateConfig} />
- -
-

- Danger Zone: Current auto delete watched - videos:{' '} - - {appSettingsConfig?.downloads.autodelete_days} - -

- Auto delete watched videos after x days, 0 (zero) to deactivate: -
- +
+

Auto delete watched videos after x days

+
+ { - setCurrentAutodelete(Number(event.target.value)); - }} + name="downloads.autodelete_days" + value={currentAutodelete} + setValue={setCurrentAutodelete} + oldValue={appSettingsConfig?.downloads.autodelete_days} + updateCallback={handleUpdateConfig} />
-
+

Download Format

- -
-

- Limit video and audio quality format for yt-dlp. -
- Currently:{' '} - {appSettingsConfig?.downloads.format} -

-

Example configurations:

-
    -
  • - - bestvideo[height{'<='}720]+bestaudio/best[height{'<='}720] - - : best audio and max video height of 720p. -
  • -
  • - - bestvideo[height{'<='}1080]+bestaudio/best[height{'<='}1080] - - : best audio and max video height of 1080p. -
  • -
  • - - bestvideo[height{'<='} - 1080][vcodec*=avc1]+bestaudio[acodec*=mp4a]/mp4 - - : Max 1080p video height with iOS compatible video and audio codecs. -
  • -
  • - 0: deactivate and download the best - quality possible as decided by yt-dlp. -
  • -
- - Make sure your custom format gets merged into a single file. Check out the{' '} - - documentation - {' '} - for valid configurations. - -
- +
+

Limit video and audio quality format

+
+ { - setDownloadsFormat(event.target.value); - }} - /> -
-
- -
-

- Force sort order to have precedence over all yt-dlp fields. -
- Currently:{' '} - {appSettingsConfig?.downloads.format_sort} -

-

Example configurations:

-
    -
  • - res,codec:av1: prefer AV1 over all other - video codecs. -
  • -
  • - 0: deactivate and keep the default as - decided by yt-dlp. -
  • -
- - Not all codecs are supported by all browsers. The default value ensures best - compatibility. Check out the{' '} - - documentation - {' '} - for valid configurations. - -
- { - setDownloadsFormatSort(event.target.value); - }} - /> -
-
- -
-

- Prefer translated metadata language:{' '} - - {appSettingsConfig?.downloads.extractor_lang} - -

- - This will change the language this video gets indexed as. That will only be - available if the uploader provides translations. Add as two letter ISO language - code, check the{' '} - - documentation - {' '} - which languages are available. - -
- { - setDownloadsExtractorLang(event.target.value); - }} + name="downloads.format" + value={downloadsFormat} + setValue={setDownloadsFormat} + oldValue={appSettingsConfig?.downloads.format} + updateCallback={handleUpdateConfig} />
- -
-

- Current metadata embed setting:{' '} - - {appSettingsConfig?.downloads.add_metadata.toString()} - -

- Metadata is not embedded into the downloaded files by default. -
- +
+
+

Sort download formats

+
+
- -
-

- Current thumbnail embed setting:{' '} - - {appSettingsConfig?.downloads.add_thumbnail.toString()} - -

- Embed thumbnail into the mediafile. -
- +
+
+

Extractor Language

+
+ +
+
+
+

Embed metadata

+
+
+
+ { + handleUpdateConfig('downloads.add_metadata', event.target.checked || false); + }} + /> + {!embedMetadata && ( + + )} + {embedMetadata && ( + + )} +
+
+
+
+
+

Embed Thumbnail

+
+
+
+ { + handleUpdateConfig('downloads.add_thumbnail', event.target.checked || false); + }} + /> + {!embedThumbnail && ( + + )} + {embedThumbnail && ( + + )} +
+
- -
-

Subtitles

-
-

- Subtitles download setting:{' '} - {appSettingsConfig?.downloads.subtitle} -
- - Choose which subtitles to download, add comma separated language codes, -
- e.g. en, de, zh-Hans -
-
- { - setSubtitleLang(event.target.value); - }} - /> -

+
+

Subtitles

+
+
+

Choose subtitle language

+
+
-
-

- Subtitle source settings:{' '} - - {appSettingsConfig?.downloads.subtitle_source} - -

- Download only user generated, or also less accurate auto generated subtitles. -
- -
-
-

- Index and make subtitles searchable:{' '} - - {appSettingsConfig?.downloads.subtitle_index.toString()} - -

- Store subtitle lines in Elasticsearch. Not recommended for low-end hardware. -
- -
-
- -
-

Comments

-
-

- Download and index comments:{' '} - {appSettingsConfig?.downloads.comment_max} -
- - Follow the yt-dlp max_comments documentation,{' '} - - max-comments,max-parents,max-replies,max-replies-per-thread - - : - -
-

Example configurations:

-
    -
  • - all,100,all,30: Get 100 max-parents - and 30 max-replies-per-thread. -
  • -
  • - 1000,all,all,50: Get a total of 1000 - comments over all, 50 replies per thread. -
  • -
- { - setCommentsMax(event.target.value); - }} - /> -

-
-
-

- Selected comment sort method:{' '} - - {appSettingsConfig?.downloads.comment_sort} - -
- Select how many comments and threads to download: -
- -

-
-
- -
-

Cookie

-
-

- Import YouTube cookie:{' '} - - {appSettingsConfig?.downloads.cookie_import} - -
-

-

- For automatic cookie import use Tube Archivist Companion{' '} - - browser extension - - . -

- - For manual cookie import, place your cookie file named{' '} - cookies.google.txt in{' '} - cache/import before enabling. Instructions - in the{' '} - - Wiki. - - -
- -
- {validatingCookie && Processing.} - {validatingCookie && cookieResponse?.cookie_validated && ( - The cookie file is valid. - )} - {validatingCookie && !cookieResponse?.cookie_validated && ( - Warning, the cookie file is invalid. - )} - {!validatingCookie && ( - <> - {appSettingsConfig?.downloads.cookie_import && ( -
-
+
+
+

Enable subtitle index

+
+
+
+ { + handleUpdateConfig('downloads.subtitle_index', event.target.checked); + }} + /> + {!indexSubtitles && ( + + )} + {indexSubtitles && ( + + )} +
+
+
+ + )}
- -
-

Integrations

-
-

- API token:{' '} - + {showApiToken && (

+
+
+
+

Enable returnyoutubedislike

+
+
+
+ { + handleUpdateConfig('downloads.integrate_ryd', event.target.checked); + }} + /> + {!downloadDislikes && ( + + )} + {downloadDislikes && ( + + )}
- )} +
- -
-

- Integrate with{' '} - - returnyoutubedislike.com - {' '} - to get dislikes and average ratings back:{' '} - - {appSettingsConfig?.downloads.integrate_ryd.toString()} - -

- - Before activating that, make sure you have a scraping sleep interval of at least 3 - secs set to avoid ratelimiting issues. - -
- -
- -
-

- Integrate with{' '} - - SponsorBlock - {' '} - to get sponsored timestamps:{' '} - - {appSettingsConfig?.downloads.integrate_sponsorblock.toString()} - -

- - Before activating that, make sure you have a scraping sleep interval of at least 3 - secs set to avoid ratelimiting issues. - -
- +
+
+

Enable Sponsorblock

+
+
+
+ { + handleUpdateConfig('downloads.integrate_sponsorblock', event.target.checked); + }} + /> + {!enableSponsorBlock && ( + + )} + {enableSponsorBlock && ( + + )} +
+
- -
-

Snapshots

-
-

- Current system snapshot:{' '} - - {appSettingsConfig?.application.enable_snapshot} - -

- - Automatically create daily deduplicated snapshots of the index, stored in - Elasticsearch. Read first before activating:{' '} - - Wiki - - . - -
- -
- -
- {snapshots && ( - <> -

- Create next snapshot:{' '} - {snapshots.next_exec_str}, snapshots - expire after {snapshots.expire_after} - .
- {isSnapshotQueued && Snapshot in progress} - {!isSnapshotQueued && ( -

+
- -
diff --git a/frontend/src/style.css b/frontend/src/style.css index b13c8fba..ebcd71f9 100644 --- a/frontend/src/style.css +++ b/frontend/src/style.css @@ -108,6 +108,10 @@ input { border: solid 1px var(--main-font); } +input[readonly] { + background-color: var(--main-font); +} + textarea { width: 100%; }