From f4db19cc11f1bcdaf6482c8b212d83a01dfc6266 Mon Sep 17 00:00:00 2001 From: MBR#0001 Date: Sun, 23 Jul 2023 21:34:24 +0200 Subject: [PATCH 001/142] Add ability to upload hearing-impaired subs --- .../subtitleuploader/subtitleuploader.js | 28 +++++++++++++++++-- .../subtitleuploader.template.html | 4 +++ src/strings/en-us.json | 3 +- 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/components/subtitleuploader/subtitleuploader.js b/src/components/subtitleuploader/subtitleuploader.js index 90e04b16d..5bfc27011 100644 --- a/src/components/subtitleuploader/subtitleuploader.js +++ b/src/components/subtitleuploader/subtitleuploader.js @@ -1,5 +1,7 @@ import escapeHtml from 'escape-html'; +import { getSubtitleApi } from '@jellyfin/sdk/lib/utils/api/subtitle-api'; +import { toApi } from 'utils/jellyfin-apiclient/compat'; import dialogHelper from '../../components/dialogHelper/dialogHelper'; import ServerConnections from '../ServerConnections'; import dom from '../../scripts/dom'; @@ -75,7 +77,20 @@ function setFiles(page, files) { reader.readAsDataURL(file); } -function onSubmit(e) { +function getStringFromFile(file) { + return new Promise(function (resolve, reject) { + const reader = new FileReader(); + reader.onload = (e) => { + // Split by a comma to remove the url: prefix + const data = e.target.result.split(',')[1]; + resolve(data); + }; + reader.onerror = reject; + reader.readAsDataURL(file); + }); +} + +async function onSubmit(e) { const file = currentFile; if (!isValidSubtitleFile(file)) { @@ -89,8 +104,17 @@ function onSubmit(e) { const dlg = dom.parentWithClass(this, 'dialog'); const language = dlg.querySelector('#selectLanguage').value; const isForced = dlg.querySelector('#chkIsForced').checked; + const isHearingImpaired = dlg.querySelector('#chkIsHearingImpaired').checked; - ServerConnections.getApiClient(currentServerId).uploadItemSubtitle(currentItemId, language, isForced, file).then(function () { + const subtitleApi = getSubtitleApi(toApi(ServerConnections.getApiClient(currentServerId))); + + const data = await getStringFromFile(file); + const format = file.name.substring(file.name.lastIndexOf('.') + 1).toLowerCase(); + + subtitleApi.uploadSubtitle({ + itemId: currentItemId, + uploadSubtitleDto: { Data: data, Language: language, IsForced: isForced, Format: format, IsHearingImpaired: isHearingImpaired } + }).then(function () { dlg.querySelector('#uploadSubtitle').value = ''; loading.hide(); hasChanges = true; diff --git a/src/components/subtitleuploader/subtitleuploader.template.html b/src/components/subtitleuploader/subtitleuploader.template.html index ba43e0041..7febd1e7a 100644 --- a/src/components/subtitleuploader/subtitleuploader.template.html +++ b/src/components/subtitleuploader/subtitleuploader.template.html @@ -31,6 +31,10 @@ ${LabelIsForced} +
diff --git a/src/strings/en-us.json b/src/strings/en-us.json index 10639d58e..7ac3e3bd5 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -1715,5 +1715,6 @@ "Unreleased": "Not yet released", "LabelTonemappingMode": "Tone mapping mode", "TonemappingModeHelp": "Select the tone mapping mode. If you experience blown out highlights try switching to the RGB mode.", - "Unknown": "Unknown" + "Unknown": "Unknown", + "LabelIsHearingImpaired": "For hearing impaired (SDH)" } From d24b030962e2de2e9beccca8816422eb6aed4ef7 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Fri, 15 Sep 2023 10:31:48 -0400 Subject: [PATCH 002/142] Migrate quick connect page to react --- src/apps/experimental/App.tsx | 4 +- .../AppToolbar/menus/AppUserMenu.tsx | 2 +- .../experimental/routes/asyncRoutes/user.ts | 1 + .../experimental/routes/legacyRoutes/user.ts | 6 -- src/apps/stable/App.tsx | 4 +- src/apps/stable/routes/_redirects.ts | 6 ++ src/apps/stable/routes/asyncRoutes/user.ts | 1 + src/apps/stable/routes/legacyRoutes/user.ts | 6 -- src/apps/stable/routes/quickConnect/index.tsx | 91 +++++++++++++++++++ src/components/router/Redirect.tsx | 17 ++++ src/controllers/user/menu/index.js | 2 +- src/controllers/user/quickConnect/helper.js | 17 ---- src/controllers/user/quickConnect/index.html | 15 --- src/controllers/user/quickConnect/index.js | 25 ----- src/elements/InputElement.tsx | 70 +++++++++++--- 15 files changed, 179 insertions(+), 88 deletions(-) create mode 100644 src/apps/stable/routes/_redirects.ts create mode 100644 src/apps/stable/routes/quickConnect/index.tsx create mode 100644 src/components/router/Redirect.tsx delete mode 100644 src/controllers/user/quickConnect/helper.js delete mode 100644 src/controllers/user/quickConnect/index.html delete mode 100644 src/controllers/user/quickConnect/index.js diff --git a/src/apps/experimental/App.tsx b/src/apps/experimental/App.tsx index 0c0778b74..57ad22eae 100644 --- a/src/apps/experimental/App.tsx +++ b/src/apps/experimental/App.tsx @@ -9,6 +9,8 @@ import { toViewManagerPageRoute } from 'components/router/LegacyRoute'; import AppLayout from './AppLayout'; import { ASYNC_ADMIN_ROUTES, ASYNC_USER_ROUTES } from './routes/asyncRoutes'; import { LEGACY_ADMIN_ROUTES, LEGACY_PUBLIC_ROUTES, LEGACY_USER_ROUTES } from './routes/legacyRoutes'; +import { REDIRECTS } from 'apps/stable/routes/_redirects'; +import { toRedirectRoute } from 'components/router/Redirect'; const ExperimentalApp = () => { return ( @@ -38,7 +40,7 @@ const ExperimentalApp = () => { {/* Redirects for old paths */} - } /> + {REDIRECTS.map(toRedirectRoute)} ); diff --git a/src/apps/experimental/components/AppToolbar/menus/AppUserMenu.tsx b/src/apps/experimental/components/AppToolbar/menus/AppUserMenu.tsx index 54fdd3ae6..7119a4f50 100644 --- a/src/apps/experimental/components/AppToolbar/menus/AppUserMenu.tsx +++ b/src/apps/experimental/components/AppToolbar/menus/AppUserMenu.tsx @@ -140,7 +140,7 @@ const AppUserMenu: FC = ({ diff --git a/src/apps/experimental/routes/asyncRoutes/user.ts b/src/apps/experimental/routes/asyncRoutes/user.ts index d7ea0dd7d..023e292ed 100644 --- a/src/apps/experimental/routes/asyncRoutes/user.ts +++ b/src/apps/experimental/routes/asyncRoutes/user.ts @@ -1,6 +1,7 @@ import { AsyncRoute, AsyncRouteType } from '../../../../components/router/AsyncRoute'; export const ASYNC_USER_ROUTES: AsyncRoute[] = [ + { path: 'quickconnect', page: 'quickConnect' }, { path: 'search.html', page: 'search' }, { path: 'userprofile.html', page: 'user/userprofile' }, { path: 'home.html', page: 'home', type: AsyncRouteType.Experimental }, diff --git a/src/apps/experimental/routes/legacyRoutes/user.ts b/src/apps/experimental/routes/legacyRoutes/user.ts index 49afc715f..965767d91 100644 --- a/src/apps/experimental/routes/legacyRoutes/user.ts +++ b/src/apps/experimental/routes/legacyRoutes/user.ts @@ -49,12 +49,6 @@ export const LEGACY_USER_ROUTES: LegacyRoute[] = [ controller: 'user/home/index', view: 'user/home/index.html' } - }, { - path: 'mypreferencesquickconnect.html', - pageProps: { - controller: 'user/quickConnect/index', - view: 'user/quickConnect/index.html' - } }, { path: 'mypreferencesplayback.html', pageProps: { diff --git a/src/apps/stable/App.tsx b/src/apps/stable/App.tsx index 73c73f588..3ad72bcb8 100644 --- a/src/apps/stable/App.tsx +++ b/src/apps/stable/App.tsx @@ -10,6 +10,8 @@ import { toViewManagerPageRoute } from 'components/router/LegacyRoute'; import { ASYNC_ADMIN_ROUTES, ASYNC_USER_ROUTES } from './routes/asyncRoutes'; import { LEGACY_ADMIN_ROUTES, LEGACY_PUBLIC_ROUTES, LEGACY_USER_ROUTES } from './routes/legacyRoutes'; +import { REDIRECTS } from './routes/_redirects'; +import { toRedirectRoute } from 'components/router/Redirect'; const Layout = () => ( <> @@ -53,7 +55,7 @@ const StableApp = () => ( {/* Redirects for old paths */} - } /> + {REDIRECTS.map(toRedirectRoute)} ); diff --git a/src/apps/stable/routes/_redirects.ts b/src/apps/stable/routes/_redirects.ts new file mode 100644 index 000000000..fb24865d8 --- /dev/null +++ b/src/apps/stable/routes/_redirects.ts @@ -0,0 +1,6 @@ +import type { Redirect } from 'components/router/Redirect'; + +export const REDIRECTS: Redirect[] = [ + { from: 'mypreferencesquickconnect.html', to: '/quickconnect' }, + { from: 'serveractivity.html', to: '/dashboard/activity' } +]; diff --git a/src/apps/stable/routes/asyncRoutes/user.ts b/src/apps/stable/routes/asyncRoutes/user.ts index 6a683323b..153e310b3 100644 --- a/src/apps/stable/routes/asyncRoutes/user.ts +++ b/src/apps/stable/routes/asyncRoutes/user.ts @@ -1,6 +1,7 @@ import { AsyncRoute } from '../../../../components/router/AsyncRoute'; export const ASYNC_USER_ROUTES: AsyncRoute[] = [ + { path: 'quickconnect', page: 'quickConnect' }, { path: 'search.html', page: 'search' }, { path: 'userprofile.html', page: 'user/userprofile' } ]; diff --git a/src/apps/stable/routes/legacyRoutes/user.ts b/src/apps/stable/routes/legacyRoutes/user.ts index f87bd4383..fa1fa43ad 100644 --- a/src/apps/stable/routes/legacyRoutes/user.ts +++ b/src/apps/stable/routes/legacyRoutes/user.ts @@ -49,12 +49,6 @@ export const LEGACY_USER_ROUTES: LegacyRoute[] = [ controller: 'user/home/index', view: 'user/home/index.html' } - }, { - path: 'mypreferencesquickconnect.html', - pageProps: { - controller: 'user/quickConnect/index', - view: 'user/quickConnect/index.html' - } }, { path: 'mypreferencesplayback.html', pageProps: { diff --git a/src/apps/stable/routes/quickConnect/index.tsx b/src/apps/stable/routes/quickConnect/index.tsx new file mode 100644 index 000000000..1887b90a1 --- /dev/null +++ b/src/apps/stable/routes/quickConnect/index.tsx @@ -0,0 +1,91 @@ +import { getQuickConnectApi } from '@jellyfin/sdk/lib/utils/api/quick-connect-api'; +import React, { FC, FormEvent, useCallback, useMemo, useState } from 'react'; +import { useSearchParams } from 'react-router-dom'; + +import Page from 'components/Page'; +import globalize from 'scripts/globalize'; +import InputElement from 'elements/InputElement'; +import ButtonElement from 'elements/ButtonElement'; +import toast from 'components/toast/toast'; +import { useApi } from 'hooks/useApi'; + +const QuickConnectPage: FC = () => { + const { api, user } = useApi(); + const [ searchParams ] = useSearchParams(); + // eslint-disable-next-line react-hooks/exhaustive-deps + const initialValue = useMemo(() => searchParams.get('code') ?? '', []); + const [ code, setCode ] = useState(initialValue); + + const onCodeChange = useCallback((value: string) => { + setCode(value); + }, []); + + const onSubmitCode = useCallback((e: FormEvent) => { + e.preventDefault(); + const form = e.currentTarget; + + if (!form.checkValidity()) { + toast(globalize.translate('QuickConnectInvalidCode')); + return; + } + + if (!api) { + console.error('[QuickConnect] cannot authorize, missing api instance'); + return; + } + + const userId = searchParams.get('userId') ?? user?.Id; + const normalizedCode = code.replace(/\s/g, ''); + console.log('[QuickConnect] authorizing code %s as user %s', normalizedCode, userId); + + getQuickConnectApi(api) + .authorizeQuickConnect({ + code: normalizedCode, + userId + }) + .then(() => { + toast(globalize.translate('QuickConnectAuthorizeSuccess')); + }) + .catch(() => { + toast(globalize.translate('QuickConnectAuthorizeFail')); + }); + }, [api, code, searchParams, user?.Id]); + + return ( + +
+
+
+

+ {globalize.translate('QuickConnect')} +

+
+ {globalize.translate('QuickConnectDescription')} +
+
+ + +
+
+
+
+ ); +}; + +export default QuickConnectPage; diff --git a/src/components/router/Redirect.tsx b/src/components/router/Redirect.tsx new file mode 100644 index 000000000..44fd57bbf --- /dev/null +++ b/src/components/router/Redirect.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { Navigate, Route } from 'react-router-dom'; + +export interface Redirect { + from: string + to: string +} + +export function toRedirectRoute({ from, to }: Redirect) { + return ( + } + /> + ); +} diff --git a/src/controllers/user/menu/index.js b/src/controllers/user/menu/index.js index 7f8ae7ecb..035d07492 100644 --- a/src/controllers/user/menu/index.js +++ b/src/controllers/user/menu/index.js @@ -31,7 +31,7 @@ export default function (view, params) { page.querySelector('.lnkHomePreferences').setAttribute('href', '#/mypreferenceshome.html?userId=' + userId); page.querySelector('.lnkPlaybackPreferences').setAttribute('href', '#/mypreferencesplayback.html?userId=' + userId); page.querySelector('.lnkSubtitlePreferences').setAttribute('href', '#/mypreferencessubtitles.html?userId=' + userId); - page.querySelector('.lnkQuickConnectPreferences').setAttribute('href', '#/mypreferencesquickconnect.html?userId=' + userId); + page.querySelector('.lnkQuickConnectPreferences').setAttribute('href', '#/quickconnect?userId=' + userId); page.querySelector('.lnkControlsPreferences').setAttribute('href', '#/mypreferencescontrols.html?userId=' + userId); const supportsClientSettings = appHost.supports('clientsettings'); diff --git a/src/controllers/user/quickConnect/helper.js b/src/controllers/user/quickConnect/helper.js deleted file mode 100644 index 3b306a9f8..000000000 --- a/src/controllers/user/quickConnect/helper.js +++ /dev/null @@ -1,17 +0,0 @@ -import globalize from '../../../scripts/globalize'; -import toast from '../../../components/toast/toast'; - -export const authorize = (code, userId) => { - const url = ApiClient.getUrl('/QuickConnect/Authorize?Code=' + code + '&UserId=' + userId); - ApiClient.ajax({ - type: 'POST', - url: url - }, true).then(() => { - toast(globalize.translate('QuickConnectAuthorizeSuccess')); - }).catch(() => { - toast(globalize.translate('QuickConnectAuthorizeFail')); - }); - - // prevent bubbling - return false; -}; diff --git a/src/controllers/user/quickConnect/index.html b/src/controllers/user/quickConnect/index.html deleted file mode 100644 index 6e0e0cbf6..000000000 --- a/src/controllers/user/quickConnect/index.html +++ /dev/null @@ -1,15 +0,0 @@ -
-
-
-

${QuickConnect}

-
${QuickConnectDescription}
-
-
- -
- -
-
-
diff --git a/src/controllers/user/quickConnect/index.js b/src/controllers/user/quickConnect/index.js deleted file mode 100644 index 2af89cafc..000000000 --- a/src/controllers/user/quickConnect/index.js +++ /dev/null @@ -1,25 +0,0 @@ -import { authorize } from './helper'; -import globalize from '../../../scripts/globalize'; -import toast from '../../../components/toast/toast'; - -export default function (view, params) { - const userId = params.userId || ApiClient.getCurrentUserId(); - - view.addEventListener('viewshow', function () { - const codeElement = view.querySelector('#txtQuickConnectCode'); - - view.querySelector('.quickConnectSettingsContainer').addEventListener('submit', (e) => { - e.preventDefault(); - - if (!codeElement.validity.valid) { - toast(globalize.translate('QuickConnectInvalidCode')); - - return; - } - - // Remove spaces from code - const normalizedCode = codeElement.value.replace(/\s/g, ''); - authorize(normalizedCode, userId); - }); - }); -} diff --git a/src/elements/InputElement.tsx b/src/elements/InputElement.tsx index 99b2e8a08..8c82a6a34 100644 --- a/src/elements/InputElement.tsx +++ b/src/elements/InputElement.tsx @@ -1,32 +1,72 @@ -import React, { FunctionComponent } from 'react'; +import React, { type FC, useCallback, useEffect, useMemo, useRef } from 'react'; + import globalize from '../scripts/globalize'; -const createInputElement = ({ type, id, label, options }: { type?: string, id?: string, label?: string, options?: string }) => ({ +interface CreateInputElementParams { + type?: string + id?: string + label?: string + initialValue?: string + options?: string +} + +const createInputElement = ({ type, id, label, initialValue, options }: CreateInputElementParams) => ({ __html: `` }); -type IProps = { - type?: string; - id?: string; - label?: string; - options?: string -}; +type InputElementProps = { + containerClassName?: string + onChange?: (value: string) => void +} & CreateInputElementParams; + +const InputElement: FC = ({ + containerClassName, + initialValue: initialValue, + onChange = () => { /* no-op */ }, + type, + id, + label, + options = '' +}) => { + const container = useRef(null); + + // NOTE: We need to memoize the input html because any re-render will break the webcomponent + const inputHtml = useMemo(() => ( + createInputElement({ + type, + id, + label: globalize.translate(label), + initialValue, + options + }) + // eslint-disable-next-line react-hooks/exhaustive-deps + ), []); + + const onInput = useCallback((e: Event) => { + onChange((e.target as HTMLInputElement).value); + }, [ onChange ]); + + useEffect(() => { + const inputElement = container?.current?.querySelector('input'); + inputElement?.addEventListener('input', onInput); + + return () => { + inputElement?.removeEventListener('input', onInput); + }; + }, [ container, onInput ]); -const InputElement: FunctionComponent = ({ type, id, label, options }: IProps) => { return (
); }; From a405577519506b3de6b4f5595169e6c5a44e87f5 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Fri, 15 Sep 2023 11:08:31 -0400 Subject: [PATCH 003/142] Fix code smell --- src/elements/InputElement.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/elements/InputElement.tsx b/src/elements/InputElement.tsx index 8c82a6a34..c1cb4863d 100644 --- a/src/elements/InputElement.tsx +++ b/src/elements/InputElement.tsx @@ -28,7 +28,7 @@ type InputElementProps = { const InputElement: FC = ({ containerClassName, - initialValue: initialValue, + initialValue, onChange = () => { /* no-op */ }, type, id, From 8553807c0d41c107ce5c55821f329ef7d6edfd16 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Sat, 16 Sep 2023 03:02:55 -0400 Subject: [PATCH 004/142] Update quick connect success/error ui --- src/apps/stable/routes/quickConnect/index.tsx | 65 +++++++++++++------ .../routes/quickConnect/quickConnect.scss | 7 ++ src/strings/en-us.json | 4 +- 3 files changed, 55 insertions(+), 21 deletions(-) create mode 100644 src/apps/stable/routes/quickConnect/quickConnect.scss diff --git a/src/apps/stable/routes/quickConnect/index.tsx b/src/apps/stable/routes/quickConnect/index.tsx index 1887b90a1..8bff06c7a 100644 --- a/src/apps/stable/routes/quickConnect/index.tsx +++ b/src/apps/stable/routes/quickConnect/index.tsx @@ -1,20 +1,23 @@ import { getQuickConnectApi } from '@jellyfin/sdk/lib/utils/api/quick-connect-api'; import React, { FC, FormEvent, useCallback, useMemo, useState } from 'react'; -import { useSearchParams } from 'react-router-dom'; +import { Link, useSearchParams } from 'react-router-dom'; import Page from 'components/Page'; import globalize from 'scripts/globalize'; import InputElement from 'elements/InputElement'; import ButtonElement from 'elements/ButtonElement'; -import toast from 'components/toast/toast'; import { useApi } from 'hooks/useApi'; +import './quickConnect.scss'; + const QuickConnectPage: FC = () => { const { api, user } = useApi(); const [ searchParams ] = useSearchParams(); // eslint-disable-next-line react-hooks/exhaustive-deps const initialValue = useMemo(() => searchParams.get('code') ?? '', []); const [ code, setCode ] = useState(initialValue); + const [ error, setError ] = useState(); + const [ success, setSuccess ] = useState(false); const onCodeChange = useCallback((value: string) => { setCode(value); @@ -22,15 +25,17 @@ const QuickConnectPage: FC = () => { const onSubmitCode = useCallback((e: FormEvent) => { e.preventDefault(); - const form = e.currentTarget; + setError(undefined); + const form = e.currentTarget; if (!form.checkValidity()) { - toast(globalize.translate('QuickConnectInvalidCode')); + setError('QuickConnectInvalidCode'); return; } if (!api) { console.error('[QuickConnect] cannot authorize, missing api instance'); + setError('UnknownError'); return; } @@ -44,10 +49,10 @@ const QuickConnectPage: FC = () => { userId }) .then(() => { - toast(globalize.translate('QuickConnectAuthorizeSuccess')); + setSuccess(true); }) .catch(() => { - toast(globalize.translate('QuickConnectAuthorizeFail')); + setError('QuickConnectAuthorizeFail'); }); }, [api, code, searchParams, user?.Id]); @@ -67,20 +72,40 @@ const QuickConnectPage: FC = () => { {globalize.translate('QuickConnectDescription')}

- - + + {error && ( +
+ {globalize.translate(error)} +
+ )} + + {success ? ( +
+

+ {globalize.translate('QuickConnectAuthorizeSuccess')} +

+ + {globalize.translate('GoHome')} + +
+ ) : ( + <> + + + + )}
diff --git a/src/apps/stable/routes/quickConnect/quickConnect.scss b/src/apps/stable/routes/quickConnect/quickConnect.scss new file mode 100644 index 000000000..d81e71561 --- /dev/null +++ b/src/apps/stable/routes/quickConnect/quickConnect.scss @@ -0,0 +1,7 @@ +.quickConnectError { + border-radius: 0.2em; + background-color: #160b0b; + color: #f4c7c3; + padding: 0.7em 0.5em; + margin-bottom: 1em; +} diff --git a/src/strings/en-us.json b/src/strings/en-us.json index bc6dc0da0..551026c25 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -297,6 +297,7 @@ "Genre": "Genre", "Genres": "Genres", "GetThePlugin": "Get the Plugin", + "GoHome": "Go Home", "GoogleCastUnsupported": "Google Cast Unsupported", "GroupBySeries": "Group by series", "GroupVersions": "Group versions", @@ -1358,7 +1359,7 @@ "QuickConnectActivationSuccessful": "Successfully activated", "QuickConnectAuthorizeCode": "Enter code {0} to login", "QuickConnectAuthorizeFail": "Unknown Quick Connect code", - "QuickConnectAuthorizeSuccess": "Request authorized", + "QuickConnectAuthorizeSuccess": "You have successfully authenticated your device!", "QuickConnectDeactivated": "Quick Connect was deactivated before the login request could be approved", "QuickConnectDescription": "To sign in with Quick Connect, select the 'Quick Connect' button on the device you are logging in from and enter the displayed code below.", "QuickConnectInvalidCode": "Invalid Quick Connect code", @@ -1554,6 +1555,7 @@ "Typewriter": "Typewriter", "Uniform": "Uniform", "UninstallPluginConfirmation": "Are you sure you wish to uninstall {0}?", + "UnknownError": "An unknown error occurred.", "Unmute": "Unmute", "Unplayed": "Unplayed", "Unrated": "Unrated", From 1b844ef624f3480b63356cc5337f842f8fbb0417 Mon Sep 17 00:00:00 2001 From: grafixeyehero Date: Sun, 17 Sep 2023 23:27:02 +0300 Subject: [PATCH 005/142] Add AlphabetPicker --- .../components/library/AlphabetPicker.tsx | 85 +++++++++++++++++++ src/types/library.ts | 3 +- 2 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 src/apps/experimental/components/library/AlphabetPicker.tsx diff --git a/src/apps/experimental/components/library/AlphabetPicker.tsx b/src/apps/experimental/components/library/AlphabetPicker.tsx new file mode 100644 index 000000000..4b8c6019e --- /dev/null +++ b/src/apps/experimental/components/library/AlphabetPicker.tsx @@ -0,0 +1,85 @@ +import React, { useCallback } from 'react'; +import classNames from 'classnames'; + +import Box from '@mui/material/Box'; +import ToggleButton from '@mui/material/ToggleButton'; +import ToggleButtonGroup from '@mui/material/ToggleButtonGroup'; + +import { LibraryViewSettings } from 'types/library'; +import 'components/alphaPicker/style.scss'; + +interface AlphabetPickerProps { + className?: string; + libraryViewSettings: LibraryViewSettings; + setLibraryViewSettings: React.Dispatch< + React.SetStateAction + >; +} + +const AlphabetPicker: React.FC = ({ + className, + libraryViewSettings, + setLibraryViewSettings +}) => { + const handelValue = useCallback( + ( + event: React.MouseEvent, + newValue: string | null | undefined + ) => { + setLibraryViewSettings((prevState) => ({ + ...prevState, + StartIndex: 0, + Alphabet: newValue + })); + }, + [setLibraryViewSettings] + ); + + const containerClassName = classNames( + 'alphaPicker', + className, + 'alphaPicker-fixed-right' + ); + + const btnClassName = classNames( + 'paper-icon-button-light', + 'alphaPickerButton' + ); + + const letters = ['#', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']; + + return ( + + + {letters.map((l) => ( + + {l} + + ))} + + + ); +}; + +export default AlphabetPicker; diff --git a/src/types/library.ts b/src/types/library.ts index 4bf275abe..862ee3d6d 100644 --- a/src/types/library.ts +++ b/src/types/library.ts @@ -62,6 +62,5 @@ export interface LibraryViewSettings { ShowTitle: boolean; ShowYear?: boolean; Filters?: Filters; - NameLessThan?: string | null; - NameStartsWith?: string | null; + Alphabet?: string | null; } From 42bedc3b1a975374cb0fb029baf00b5d7ac538db Mon Sep 17 00:00:00 2001 From: mcc Date: Mon, 18 Sep 2023 21:01:49 +0000 Subject: [PATCH 006/142] Translated using Weblate (Chinese (Traditional)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/zh_Hant/ --- src/strings/zh-tw.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/strings/zh-tw.json b/src/strings/zh-tw.json index dd498b898..a36fd175c 100644 --- a/src/strings/zh-tw.json +++ b/src/strings/zh-tw.json @@ -1762,5 +1762,6 @@ "HeaderConfirmRepositoryInstallation": "確認外掛來源庫", "MessageRepositoryInstallDisclaimer": "警告:安裝第三方外掛程式庫有風險。其中可能包不穩定或含惡意的程式,並且可能隨時變化。請只安裝你信任的作者提供的外掛程式庫。", "LabelEnableLUFSScanHelp": "針對音樂啟用 LUFS 掃描(需要更多時間和資源)。", - "AllowCollectionManagement": "允許用戶管理合輯" + "AllowCollectionManagement": "允許用戶管理合輯", + "GridView": "網格檢視" } From 805f4e0fdc663646bb67e96c30f2614fb59f5ba7 Mon Sep 17 00:00:00 2001 From: mcc Date: Mon, 18 Sep 2023 23:13:24 +0000 Subject: [PATCH 007/142] Translated using Weblate (Chinese (Traditional)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/zh_Hant/ --- src/strings/zh-tw.json | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/strings/zh-tw.json b/src/strings/zh-tw.json index a36fd175c..b4825af3b 100644 --- a/src/strings/zh-tw.json +++ b/src/strings/zh-tw.json @@ -336,7 +336,7 @@ "DoNotRecord": "不錄製", "Down": "下", "DownloadsValue": "{0} 個下載", - "DrmChannelsNotImported": "受 DMR 保護的頻道將不會被導入。", + "DrmChannelsNotImported": "受 DRM 保護的頻道將不會被導入。", "DropShadow": "陰影", "EasyPasswordHelp": "你的簡易 PIN 碼將會用於在支援的 Jellyfin 應用上進行離線存取,同時也可被用於區域網路的登入。", "EditMetadata": "編輯媒體資訊", @@ -1763,5 +1763,10 @@ "MessageRepositoryInstallDisclaimer": "警告:安裝第三方外掛程式庫有風險。其中可能包不穩定或含惡意的程式,並且可能隨時變化。請只安裝你信任的作者提供的外掛程式庫。", "LabelEnableLUFSScanHelp": "針對音樂啟用 LUFS 掃描(需要更多時間和資源)。", "AllowCollectionManagement": "允許用戶管理合輯", - "GridView": "網格檢視" + "GridView": "網格檢視", + "AllowAv1Encoding": "允許以 AV1 格式編碼", + "ListView": "清單檢視", + "AiTranslated": "AI翻譯", + "MachineTranslated": "機器翻譯", + "ForeignPartsOnly": "僅限強制或外語部分" } From dcaa2a9b9ff451993706ab059d849d698a502147 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Kucharczyk?= Date: Tue, 19 Sep 2023 04:58:05 +0000 Subject: [PATCH 008/142] Translated using Weblate (Czech) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/cs/ --- src/strings/cs.json | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/strings/cs.json b/src/strings/cs.json index 123769085..e5a25897c 100644 --- a/src/strings/cs.json +++ b/src/strings/cs.json @@ -336,7 +336,7 @@ "Help": "Nápověda", "Hide": "Skrýt", "HideWatchedContentFromLatestMedia": "Skrýt zhlédnuté položky v seznamu Nedávno přidaná média", - "Home": "Domů", + "Home": "Domovská obrazovka", "Identify": "Identifikovat", "Images": "Obrázky", "ImportFavoriteChannelsHelp": "Jen kanály označené jako oblíbené na zařízení tuneru budou importovány.", @@ -1112,7 +1112,7 @@ "LabelSortOrder": "Pořadí řazení", "LabelSpecialSeasonsDisplayName": "Zobrazovaný název pro zvláštní sezónu", "LabelSubtitleDownloaders": "Stahovače titulků", - "LabelTVHomeScreen": "Hlavní obrazovka TV režimu", + "LabelTVHomeScreen": "Domovská obrazovka TV režimu", "LabelTag": "Tag", "LabelTypeMetadataDownloaders": "Stahovače metadat ({0})", "LabelTypeText": "Text", @@ -1408,7 +1408,7 @@ "QuickConnectDescription": "Chcete-li se připojit snadněji, na přihlašovaném zařízení stiskněte tlačítko Rychlé připojení a zadejte níže uvedený kód.", "QuickConnectDeactivated": "Rychlé připojení bylo vypnuto dříve, než mohl být požadavek schválen", "QuickConnectAuthorizeFail": "Neznámý kód pro rychlé připojení", - "QuickConnectAuthorizeSuccess": "Požadavek autorizován", + "QuickConnectAuthorizeSuccess": "Vaše zařízení bylo úspěšně ověřeno!", "QuickConnectAuthorizeCode": "Pro přihlášení zadejte kód {0}", "QuickConnectActivationSuccessful": "Úspěšně aktivováno", "QuickConnect": "Rychlé připojení", @@ -1771,5 +1771,7 @@ "ForeignPartsOnly": "Pouze vynucené", "HearingImpairedShort": "Titulky pro neslyšící", "AiTranslated": "Přeložené pomocí UI", - "HeaderGuestCast": "Zvláštní hosté" + "HeaderGuestCast": "Zvláštní hosté", + "GoHome": "Přejít na domovskou obrazovku", + "UnknownError": "Došlo k neznámé chybě." } From a681a5c4e063659093b34f158a255ca2c2c63096 Mon Sep 17 00:00:00 2001 From: Kityn Date: Tue, 19 Sep 2023 10:16:17 +0000 Subject: [PATCH 009/142] Translated using Weblate (Polish) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/pl/ --- src/strings/pl.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/strings/pl.json b/src/strings/pl.json index ef951021c..8f5de2efb 100644 --- a/src/strings/pl.json +++ b/src/strings/pl.json @@ -1461,7 +1461,7 @@ "SyncPlayGroupDefaultTitle": "Grupa {0}", "QuickConnectNotAvailable": "Poproś administratora serwera, aby włączył szybkie łączenie", "QuickConnectNotActive": "Szybkie łączenie nie jest aktywne na tym serwerze", - "QuickConnectAuthorizeSuccess": "Żądanie autoryzowane", + "QuickConnectAuthorizeSuccess": "Pomyślnie uwierzytelniono urządzenie!", "QuickConnectAuthorizeCode": "Wpisz kod {0}, aby się zalogować", "PluginFromRepo": "{0} z repozytorium {1}", "OptionMaxActiveSessionsHelp": "Wartość \"0\" wyłączy tę funkcję.", @@ -1771,5 +1771,7 @@ "AiTranslated": "Przetłumaczono przez SI", "MachineTranslated": "Przetłumaczono maszynowo", "ForeignPartsOnly": "Tylko części wymuszone/obce", - "HearingImpairedShort": "HI/SDH" + "HearingImpairedShort": "HI/SDH", + "GoHome": "Wróć na start", + "UnknownError": "Wystąpił nieznany błąd." } From 1e47abb88f3ee56b4e7285d7c75c596a5bd1d40f Mon Sep 17 00:00:00 2001 From: stanol Date: Tue, 19 Sep 2023 11:13:36 +0000 Subject: [PATCH 010/142] Translated using Weblate (Ukrainian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/uk/ --- src/strings/uk.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/strings/uk.json b/src/strings/uk.json index f2cbf649b..7442a8b48 100644 --- a/src/strings/uk.json +++ b/src/strings/uk.json @@ -1229,7 +1229,7 @@ "QuickConnectInvalidCode": "Недійсний код швидкого підключення", "QuickConnectDescription": "Щоб увійти за допомогою швидкого підключення, виберіть кнопку «Швидке підключення» на пристрої, з якого ви входите, і введіть код, що відображається нижче.", "QuickConnectDeactivated": "Швидке підключення було деактивовано до того, як запит на вхід було схвалено", - "QuickConnectAuthorizeSuccess": "Запит авторизований", + "QuickConnectAuthorizeSuccess": "Ви успішно автентифікували свій пристрій!", "QuickConnectAuthorizeFail": "Невідомий код швидкого підключення", "QuickConnectAuthorizeCode": "Введіть код {0} для входу", "QuickConnectActivationSuccessful": "Успішно активовано", @@ -1768,5 +1768,6 @@ "HearingImpairedShort": "HI/SDH", "GridView": "У вигляді сітки", "ListView": "У вигляді списку", - "AiTranslated": "Перекладено за допомогою ШІ" + "AiTranslated": "Перекладено за допомогою ШІ", + "UnknownError": "Виникла невідома помилка." } From 4bd16343478dbb7a33cc4229eb4c72e817431159 Mon Sep 17 00:00:00 2001 From: Andi Chandler Date: Tue, 19 Sep 2023 18:58:11 +0000 Subject: [PATCH 011/142] Translated using Weblate (English (United Kingdom)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/en_GB/ --- src/strings/en-gb.json | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/strings/en-gb.json b/src/strings/en-gb.json index 6aeccaaaa..372502349 100644 --- a/src/strings/en-gb.json +++ b/src/strings/en-gb.json @@ -1392,7 +1392,7 @@ "LabelTonemappingRange": "Tone mapping range", "TonemappingAlgorithmHelp": "Tone mapping can be fine-tuned. If you are not familiar with these options, just keep the default. The recommended value is 'BT.2390'.", "LabelTonemappingAlgorithm": "Select the Tone mapping algorithm to use", - "AllowTonemappingHelp": "Tone mapping can transform the dynamic range of a video from HDR to SDR while maintaining image details and colours, which are very important information for representing the original scene. Currently works only with 10bit HDR10,HLG and DoVi videos. This requires the corresponding OpenCL or CUDA runtime.", + "AllowTonemappingHelp": "Tone mapping can transform the dynamic range of a video from HDR to SDR while maintaining image details and colours, which are very important information for representing the original scene. Currently works only with 10bit HDR10, HLG and DoVi videos. This requires the corresponding OpenCL or CUDA runtime.", "EnableTonemapping": "Enable Tone mapping", "LabelOpenclDeviceHelp": "This is the OpenCL device that is used for tone mapping. The left side of the dot is the platform number, and the right side is the device number on the platform. The default value is 0.0. The FFmpeg application file containing the OpenCL hardware acceleration method is required.", "LabelOpenclDevice": "OpenCL Device", @@ -1411,7 +1411,7 @@ "QuickConnectDescription": "To sign in with Quick Connect, select the 'Quick Connect' button on the device you are logging in from and enter the displayed code below.", "QuickConnectDeactivated": "Quick Connect was deactivated before the login request could be approved", "QuickConnectAuthorizeFail": "Unknown Quick Connect code", - "QuickConnectAuthorizeSuccess": "Request authorised", + "QuickConnectAuthorizeSuccess": "You have successfully authenticated your device!", "QuickConnectAuthorizeCode": "Enter code {0} to login", "QuickConnectActivationSuccessful": "Successfully activated", "QuickConnect": "Quick Connect", @@ -1761,5 +1761,17 @@ "LabelThrottleDelaySeconds": "Throttle after", "LabelThrottleDelaySecondsHelp": "Time in seconds after which the transcoder will be throttled. Must be large enough for the client to maintain a healthy buffer. Only works if throttling is enabled.", "LabelSegmentKeepSeconds": "Time to keep segments", - "LabelSegmentKeepSecondsHelp": "Time in seconds for which segments should be kept before they are overwritten. Must be greater than \"Throttle after\". Only works if segment deletion is enabled." + "LabelSegmentKeepSecondsHelp": "Time in seconds for which segments should be kept before they are overwritten. Must be greater than \"Throttle after\". Only works if segment deletion is enabled.", + "AllowAv1Encoding": "Allow encoding in AV1 format", + "GoHome": "Go Home", + "UnknownError": "An unknown error occurred.", + "GridView": "Grid View", + "ListView": "List View", + "LabelBackdropScreensaverInterval": "Backdrop Screensaver Interval", + "LabelBackdropScreensaverIntervalHelp": "The time in seconds between different backdrops when using the backdrop screensaver.", + "AiTranslated": "AI Translated", + "MachineTranslated": "Machine Translated", + "ForeignPartsOnly": "Forced/Foreign parts only", + "HearingImpairedShort": "HI/SDH", + "HeaderGuestCast": "Guest Stars" } From cbbf8060376a0406cb7d39cc9ba9f19c964b41c8 Mon Sep 17 00:00:00 2001 From: Sverre Date: Tue, 19 Sep 2023 19:25:02 +0000 Subject: [PATCH 012/142] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegi?= =?UTF-8?q?an=20Bokm=C3=A5l)=20Translation:=20Jellyfin/Jellyfin=20Web=20Tr?= =?UTF-8?q?anslate-URL:=20https://translate.jellyfin.org/projects/jellyfin?= =?UTF-8?q?/jellyfin-web/nb=5FNO/?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/strings/nb.json | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/strings/nb.json b/src/strings/nb.json index 6f7f1c770..e984012d0 100644 --- a/src/strings/nb.json +++ b/src/strings/nb.json @@ -1385,7 +1385,7 @@ "QuickConnectDescription": "Hvis du vil logge på med hurtigtilkobling velger du 'Hurtigtilkobling'-knappen på enheten du logger på fra, og skriver inn koden som vises nedenfor.", "QuickConnectDeactivated": "Hurtigtilkobling ble deaktivert før påloggingsforespørselen kunne godkjennes", "QuickConnectAuthorizeFail": "Ukjent hurtigtilkoblings-kode", - "QuickConnectAuthorizeSuccess": "Forespørsel autorisert", + "QuickConnectAuthorizeSuccess": "Du har autentisert enheten din!", "QuickConnectAuthorizeCode": "Skriv inn kode {0} for å logge inn", "QuickConnectActivationSuccessful": "Aktivert", "QuickConnect": "Hurtigtilkobling", @@ -1418,7 +1418,7 @@ "LabelTonemappingRange": "Tonekartlegging-område", "TonemappingAlgorithmHelp": "Tonekartlegging kan finjusteres. Hvis du ikke er kjent med disse alternativene er det bare å beholde standardinnstillingen. Anbefalt verdi er BT.2390.", "LabelTonemappingAlgorithm": "Velg algoritmen som skal brukes for tonekartlegging", - "AllowTonemappingHelp": "Tonekartlegging kan forvandle det dynamiske området på en video fra HDR til SDR samtidig som bildedetaljer og farger opprettholdes, noe som er veldig viktig informasjon for å representere den opprinnelige scenen. Fungerer for øyeblikket bare med HDR10-, HLG- og DoVi-videoer. Dette krever korresponderende OpenCL eller CUDA runtime.", + "AllowTonemappingHelp": "Tonekartlegging kan forvandle det dynamiske området på en video fra HDR til SDR samtidig som bildedetaljer og farger opprettholdes, noe som er veldig viktig informasjon for å representere den opprinnelige scenen. Fungerer for øyeblikket bare med HDR10, HLG og DoVi-videoer. Dette krever korresponderende OpenCL eller CUDA runtime.", "OptionMaxActiveSessionsHelp": "En verdi på 0 skrur av denne funksjonen.", "OptionMaxActiveSessions": "Sett maksimalt antall tilgjengelige brukerøkter.", "LabelUserMaxActiveSessions": "Maksimalt antall samtidige brukerøkter", @@ -1760,5 +1760,13 @@ "LabelThrottleDelaySecondsHelp": "Tid i sekunder hvoretter transkoderen vil bli begrenset. Må være stor nok til at klienten kan opprettholde en sunn buffer. Funker kun om begrensning er påskrudd.", "LabelSegmentKeepSeconds": "Tid å beholde segmenter", "LabelSegmentKeepSecondsHelp": "Tid i sekunder som segmenter skal beholdes for før de blir overskrevet. Må være større enn \"Begrens etter\". Funker kun om segmentsletting er påskrudd.", - "LabelLevel": "Nivå" + "LabelLevel": "Nivå", + "GoHome": "Hjem", + "UnknownError": "En ukjent feil har oppstått.", + "GridView": "Rutenett", + "ListView": "Listevisning", + "AllowAv1Encoding": "Tillat enkoding i AV1-format", + "AiTranslated": "AI-oversatt", + "MachineTranslated": "Maskinoversatt", + "HeaderGuestCast": "Gjestestjerner" } From 5d047d3450a09c80a4cca06d431b900016b65a89 Mon Sep 17 00:00:00 2001 From: Evan Carroll Date: Tue, 19 Sep 2023 17:19:08 -0500 Subject: [PATCH 013/142] Add speeds for 2.5x 3x 3.5x 4x Changes: Increase max playback speeds. Adds speeds between 2x-4x in 0.5x increments Feature request: 1916 --- src/plugins/htmlVideoPlayer/plugin.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js index 194678b7e..5f20eb116 100644 --- a/src/plugins/htmlVideoPlayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -1978,6 +1978,18 @@ export class HtmlVideoPlayer { }, { name: '2x', id: 2.0 + }, { + name: '2.5x', + id: 2.5 + }, { + name: '3x', + id: 3.0 + }, { + name: '3.5x', + id: 3.5 + }, { + name: '4.0x', + id: 4.0 }]; } From 055a5008513dc2f1cc370bfbdc5b67c3af1ea8f4 Mon Sep 17 00:00:00 2001 From: Vincent Yeung Date: Wed, 20 Sep 2023 01:04:54 +0000 Subject: [PATCH 014/142] Translated using Weblate (Chinese (Traditional, Hong Kong)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/zh_Hant_HK/ --- src/strings/zh-hk.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/strings/zh-hk.json b/src/strings/zh-hk.json index b9416a426..81784bf2f 100644 --- a/src/strings/zh-hk.json +++ b/src/strings/zh-hk.json @@ -1155,5 +1155,8 @@ "ValueMinutes": "{0}分", "MinutesBefore": "分鐘前", "MessagePleaseWait": "請稍等。", - "AllowCollectionManagement": "允許用戶管理合輯" + "AllowCollectionManagement": "允許用戶管理合輯", + "AllowAv1Encoding": "允許AV1格式轉碼", + "AllowSegmentDeletion": "移除選段", + "AiTranslated": "AI 翻譯" } From 399157b08d526cd17472f17459d12a8c41a2190d Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Wed, 20 Sep 2023 00:04:08 -0400 Subject: [PATCH 015/142] Add search params to redirects --- src/components/router/Redirect.tsx | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/components/router/Redirect.tsx b/src/components/router/Redirect.tsx index 44fd57bbf..7354f16c5 100644 --- a/src/components/router/Redirect.tsx +++ b/src/components/router/Redirect.tsx @@ -1,17 +1,28 @@ import React from 'react'; -import { Navigate, Route } from 'react-router-dom'; +import { Navigate, Route, useLocation } from 'react-router-dom'; export interface Redirect { from: string to: string } +const RedirectWithSearch = ({ to }: { to: string }) => { + const { search } = useLocation(); + + return ( + + ); +}; + export function toRedirectRoute({ from, to }: Redirect) { return ( } + element={} /> ); } From a6a1a39eafee53f31caddc219ca37ed8aa7ec2e2 Mon Sep 17 00:00:00 2001 From: ZouTao Date: Wed, 20 Sep 2023 02:52:48 +0000 Subject: [PATCH 016/142] Translated using Weblate (Chinese (Simplified)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/zh_Hans/ --- src/strings/zh-cn.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/strings/zh-cn.json b/src/strings/zh-cn.json index ad9b426be..1300a28e8 100644 --- a/src/strings/zh-cn.json +++ b/src/strings/zh-cn.json @@ -1415,7 +1415,7 @@ "QuickConnectDescription": "要使用快速连接登录,请点击要登录的设备上的快速连接按钮,然后在下方输入显示的验证码。", "QuickConnectDeactivated": "在批准登录请求之前,快速连接已被禁用", "QuickConnectAuthorizeFail": "未知的快速连接验证码", - "QuickConnectAuthorizeSuccess": "请求已被授权成功", + "QuickConnectAuthorizeSuccess": "您的设备验证成功!", "QuickConnectAuthorizeCode": "输入验证码 {0} 以登陆", "QuickConnectActivationSuccessful": "启用成功", "QuickConnect": "快速连接", @@ -1771,5 +1771,6 @@ "AiTranslated": "AI翻译", "MachineTranslated": "机器翻译", "ForeignPartsOnly": "仅限强制开启/外语部分", - "HearingImpairedShort": "听障/聋哑人士字幕" + "HearingImpairedShort": "听障/聋哑人士字幕", + "UnknownError": "发生未知错误。" } From 2c608aa7d6bae02bf70956136d583e30b7ea2bff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Kucharczyk?= Date: Wed, 20 Sep 2023 07:47:14 +0200 Subject: [PATCH 017/142] Make screensaver names translateable --- src/components/displaySettings/displaySettings.js | 2 +- src/plugins/backdropScreensaver/plugin.js | 2 +- src/plugins/logoScreensaver/plugin.js | 2 +- src/strings/en-us.json | 2 ++ 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/displaySettings/displaySettings.js b/src/components/displaySettings/displaySettings.js index 9103011ac..99c1ab3e2 100644 --- a/src/components/displaySettings/displaySettings.js +++ b/src/components/displaySettings/displaySettings.js @@ -36,7 +36,7 @@ function loadScreensavers(context, userSettings) { const selectScreensaver = context.querySelector('.selectScreensaver'); const options = pluginManager.ofType(PluginType.Screensaver).map(plugin => { return { - name: plugin.name, + name: globalize.translate(plugin.name), value: plugin.id }; }); diff --git a/src/plugins/backdropScreensaver/plugin.js b/src/plugins/backdropScreensaver/plugin.js index 5f5f440c1..392eb5f7a 100644 --- a/src/plugins/backdropScreensaver/plugin.js +++ b/src/plugins/backdropScreensaver/plugin.js @@ -5,7 +5,7 @@ import * as userSettings from '../../scripts/settings/userSettings'; class BackdropScreensaver { constructor() { - this.name = 'Backdrop ScreenSaver'; + this.name = "BackdropScreensaver"; this.type = PluginType.Screensaver; this.id = 'backdropscreensaver'; this.supportsAnonymous = false; diff --git a/src/plugins/logoScreensaver/plugin.js b/src/plugins/logoScreensaver/plugin.js index 9cd0e1a44..06d643d77 100644 --- a/src/plugins/logoScreensaver/plugin.js +++ b/src/plugins/logoScreensaver/plugin.js @@ -4,7 +4,7 @@ import { randomInt } from '../../utils/number.ts'; export default function () { const self = this; - self.name = 'Logo ScreenSaver'; + self.name = 'LogoScreensaver'; self.type = PluginType.Screensaver; self.id = 'logoscreensaver'; self.supportsAnonymous = true; diff --git a/src/strings/en-us.json b/src/strings/en-us.json index c2f93fdf3..c11d9b5df 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -59,6 +59,7 @@ "Auto": "Auto", "Backdrop": "Backdrop", "Backdrops": "Backdrops", + "BackdropScreensaver": "Backdrop Screensaver", "Banner": "Banner", "BirthDateValue": "Born: {0}", "BirthLocation": "Birth location", @@ -1016,6 +1017,7 @@ "LogLevel.Critical": "Critical", "LogLevel.None": "None", "Logo": "Logo", + "LogoScreensaver": "Logo Screensaver", "Lyricist": "Lyricist", "ManageLibrary": "Manage library", "ManageRecording": "Manage recording", From aad5f9dc95e8faf971f5fce4b6e478c755a98ddd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Kucharczyk?= Date: Wed, 20 Sep 2023 07:57:16 +0200 Subject: [PATCH 018/142] Change double quotes to single quotes --- src/plugins/backdropScreensaver/plugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/backdropScreensaver/plugin.js b/src/plugins/backdropScreensaver/plugin.js index 392eb5f7a..60d28a67c 100644 --- a/src/plugins/backdropScreensaver/plugin.js +++ b/src/plugins/backdropScreensaver/plugin.js @@ -5,7 +5,7 @@ import * as userSettings from '../../scripts/settings/userSettings'; class BackdropScreensaver { constructor() { - this.name = "BackdropScreensaver"; + this.name = 'BackdropScreensaver'; this.type = PluginType.Screensaver; this.id = 'backdropscreensaver'; this.supportsAnonymous = false; From 56ae9d8fd011fb4dc87dff1a6b364b6e90bb0267 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Kucharczyk?= Date: Wed, 20 Sep 2023 08:47:16 +0200 Subject: [PATCH 019/142] Explain what LUFS scanning does. --- src/strings/en-us.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings/en-us.json b/src/strings/en-us.json index c2f93fdf3..75ec004ea 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -667,7 +667,7 @@ "LabelEnableIP6": "Enable IPv6", "LabelEnableIP6Help": "Enable IPv6 functionality.", "LabelEnableLUFSScan": "Enable LUFS scan", - "LabelEnableLUFSScanHelp": "Enable LUFS scan for music (This will take longer and more resources).", + "LabelEnableLUFSScanHelp": "Clients can normalize audio playback to get equal loudness across tracks. This will make the scan longer and take more resources.", "LabelEnableRealtimeMonitor": "Enable real time monitoring", "LabelEnableRealtimeMonitorHelp": "Changes to files will be processed immediately on supported file systems.", "LabelEnableSingleImageInDidlLimit": "Limit to single embedded image", From dffe1b0d98dc05bb218a4667d1de47d2f91d7953 Mon Sep 17 00:00:00 2001 From: Oskari Lavinto Date: Wed, 20 Sep 2023 06:45:14 +0000 Subject: [PATCH 020/142] Translated using Weblate (Finnish) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fi/ --- src/strings/fi.json | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/strings/fi.json b/src/strings/fi.json index 501942ee4..81a239a00 100644 --- a/src/strings/fi.json +++ b/src/strings/fi.json @@ -911,7 +911,7 @@ "LabelAlbumArtists": "Albumin esittäjät", "Items": "Kohteet", "ItemCount": "{0} kohdetta", - "Home": "Koti", + "Home": "Aloitusnäyttö", "Help": "Apua", "HeaderXmlSettings": "XML-asetukset", "HeaderXmlDocumentAttributes": "XML-dokumentin attribuutit", @@ -1242,7 +1242,7 @@ "LabelIdentificationFieldHelp": "Regex-lauseke tai alaotsikko (kirjainten koolla ei välillä).", "LabelIconMaxResHelp": "'upnp:icon'-tietueen välityksellä näytettävien kuvakkeiden enimmäistarkkuus.", "LabelTVHomeScreen": "Televisiotilan aloitusnäyttö", - "LabelHomeScreenSectionValue": "Aloitusnäyttö-osio {0}", + "LabelHomeScreenSectionValue": "Aloitusnäytön osio {0}", "LabelHDHomerunPortRangeHelp": "Rajoittaa HD Homerun -laitteiden UDP-porttialueen tähän arvoon (oletus on 1024 - 65535).", "LabelExtractChaptersDuringLibraryScanHelp": "Pura kirjastopäivityksen yhteydessä tuotavien videoiden kappalekuvat. Muutoin tämä tapahtuu kappalekuvien purun ajoitetun tehtävän aikana, jolloin kirjaston perustarkastus nopeutuu.", "LabelHDHomerunPortRange": "HDHomeRun -portin alue", @@ -1386,7 +1386,7 @@ "MessageNoCollectionsAvailable": "Kokoelmien avulla voit nauttia elokuvien, sarjojen ja albumien mukautetuista ryhmityksistä. Luo kokoelmia painamalla '+'-painiketta.", "MessageLeaveEmptyToInherit": "Jätä tyhjäksi periäksesi asetukset ylemmän tason kohteesta tai globaalista oletusarvosta.", "MessageGetInstalledPluginsError": "Noudettaessa listaa asennetuista lisäosista tapahtui virhe.", - "MessageForgotPasswordInNetworkRequired": "Ole hyvä ja yritä uudestaan kotiverkossasi aloittaaksesi salasanan palautuksen.", + "MessageForgotPasswordInNetworkRequired": "Yritä aloittaa salasanan palautus uudelleen kotiverkostasi.", "MessageForgotPasswordFileCreated": "Seuraava tiedosto on luotu palvelimellesi, joka sisältää ohjeet jatkamiseen", "MessageFileReadError": "Luettaessa tiedostoa tapahtui virhe. Yritä uudelleen.", "MessageDirectoryPickerLinuxInstruction": "Linux-järjestelmille Arch linux, CentOS, Debian, Fedora, openSUSE tai Ubuntu, sinun on annettava palvelukäyttäjälle vähintään lukuoikeudet tallennustiloihisi.", @@ -1439,7 +1439,7 @@ "QuickConnectInvalidCode": "Virheellinen Pikayhdistyskoodi", "QuickConnectDescription": "Kirjautuaksesi Pikayhdistyksellä, valitse 'Pikayhdistys'-painike laitteelta, josta yrität kirjautua ja syötä alla oleva koodi.", "QuickConnectDeactivated": "Pikayhdistys katkaistiin ennen kirjautumispyynnön hyväksyntää", - "QuickConnectAuthorizeSuccess": "Pyyntö hyväksytty", + "QuickConnectAuthorizeSuccess": "Laitteesi on todennettu!", "QuickConnectAuthorizeFail": "Tuntematon Pikayhdistyskoodi", "QuickConnectAuthorizeCode": "Kirjaudu syöttämällä koodi {0}", "QuickConnectActivationSuccessful": "Käyttöönotto onnistui", @@ -1769,5 +1769,7 @@ "MachineTranslated": "Konekäännetty", "ForeignPartsOnly": "Pakotettu/vain vieraskieliset osat", "HearingImpairedShort": "HI/SDH", - "HeaderGuestCast": "Vierailevat pääosat" + "HeaderGuestCast": "Vierailevat pääosat", + "GoHome": "Siirry aloitusnäyttöön", + "UnknownError": "Tapahtui tuntematon virhe." } From a622a08388ebca521cc9b7d7db954f3e499cabac Mon Sep 17 00:00:00 2001 From: Bas Date: Wed, 20 Sep 2023 11:15:54 +0000 Subject: [PATCH 021/142] Translated using Weblate (Dutch) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/nl/ --- src/strings/nl.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/strings/nl.json b/src/strings/nl.json index c5e349631..f403e75bc 100644 --- a/src/strings/nl.json +++ b/src/strings/nl.json @@ -1380,7 +1380,7 @@ "QuickConnectDescription": "Om je aan te melden met Quick Connect selecteer je de 'Quick Connect'-knop op het apparaat waarop je je wilt aanmelden en en voer je de weergegeven code hieronder in.", "QuickConnectDeactivated": "Quick Connect is gedeactiveerd voordat het aanmeldverzoek goedgekeurd kon worden", "QuickConnectAuthorizeFail": "Onbekende Quick Connect-code", - "QuickConnectAuthorizeSuccess": "Verzoek goedgekeurd", + "QuickConnectAuthorizeSuccess": "Je hebt je apparaat succesvol goedgekeurd!", "QuickConnectAuthorizeCode": "Vul code {0} in om aan te melden", "QuickConnectActivationSuccessful": "Succesvol geactiveerd", "LabelKnownProxies": "Bekende proxy's", @@ -1770,5 +1770,7 @@ "ListView": "Lijstweergave", "AiTranslated": "Vertaald door AI", "HeaderGuestCast": "Gastrollen", - "HearingImpairedShort": "Voor slechthorenden" + "HearingImpairedShort": "Voor slechthorenden", + "GoHome": "Startpagina", + "UnknownError": "Er is een onbekende fout opgetreden." } From 0bcb25d0a3da497b0fdec45e325e5cb5441465ce Mon Sep 17 00:00:00 2001 From: stanol Date: Wed, 20 Sep 2023 09:42:19 +0000 Subject: [PATCH 022/142] Translated using Weblate (Ukrainian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/uk/ --- src/strings/uk.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/strings/uk.json b/src/strings/uk.json index 7442a8b48..6314267cc 100644 --- a/src/strings/uk.json +++ b/src/strings/uk.json @@ -1769,5 +1769,6 @@ "GridView": "У вигляді сітки", "ListView": "У вигляді списку", "AiTranslated": "Перекладено за допомогою ШІ", - "UnknownError": "Виникла невідома помилка." + "UnknownError": "Виникла невідома помилка.", + "GoHome": "На головну" } From 55a2ca3590e051e577b3da17068c81bfc8112557 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Wed, 20 Sep 2023 12:38:43 -0400 Subject: [PATCH 023/142] Fix error on unmount in syncplay menu --- .../components/AppToolbar/menus/SyncPlayMenu.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/apps/experimental/components/AppToolbar/menus/SyncPlayMenu.tsx b/src/apps/experimental/components/AppToolbar/menus/SyncPlayMenu.tsx index cb377784e..1163a9252 100644 --- a/src/apps/experimental/components/AppToolbar/menus/SyncPlayMenu.tsx +++ b/src/apps/experimental/components/AppToolbar/menus/SyncPlayMenu.tsx @@ -56,9 +56,12 @@ const SyncPlayMenu: FC = ({ }, []); useEffect(() => { + let isMounted = true; + const fetchGroups = async () => { if (api) { - setGroups((await getSyncPlayApi(api).syncPlayGetGroups()).data); + const response = await getSyncPlayApi(api).syncPlayGetGroups(); + if (isMounted) setGroups(response.data); } }; @@ -66,6 +69,10 @@ const SyncPlayMenu: FC = ({ .catch(err => { console.error('[SyncPlayMenu] unable to fetch SyncPlay groups', err); }); + + return () => { + isMounted = false; + }; }, [ api ]); const onGroupAddClick = useCallback(() => { From 9e18dc91de997f86a07b02bd1c97a38a2d72f0ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Kucharczyk?= Date: Thu, 21 Sep 2023 12:22:18 +0200 Subject: [PATCH 024/142] Better wording --- src/strings/en-us.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings/en-us.json b/src/strings/en-us.json index 75ec004ea..f77355208 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -667,7 +667,7 @@ "LabelEnableIP6": "Enable IPv6", "LabelEnableIP6Help": "Enable IPv6 functionality.", "LabelEnableLUFSScan": "Enable LUFS scan", - "LabelEnableLUFSScanHelp": "Clients can normalize audio playback to get equal loudness across tracks. This will make the scan longer and take more resources.", + "LabelEnableLUFSScanHelp": "Clients can normalize audio playback to get equal loudness across tracks. This will make library scans longer and take more resources.", "LabelEnableRealtimeMonitor": "Enable real time monitoring", "LabelEnableRealtimeMonitorHelp": "Changes to files will be processed immediately on supported file systems.", "LabelEnableSingleImageInDidlLimit": "Limit to single embedded image", From c6bd18ab3221272b6f2d68d284239e41264c56e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Kucharczyk?= Date: Thu, 21 Sep 2023 18:05:53 +0000 Subject: [PATCH 025/142] Translated using Weblate (Czech) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/cs/ --- src/strings/cs.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/strings/cs.json b/src/strings/cs.json index e5a25897c..679ee7528 100644 --- a/src/strings/cs.json +++ b/src/strings/cs.json @@ -1733,7 +1733,7 @@ "EnableAudioNormalizationHelp": "Normalizace hlasitosti udržuje průměrnou hlasitost na požadované úrovni (-18 dB) přidáním konstantního zisku.", "EnableAudioNormalization": "Normalizace hlasitosti", "LabelEnableLUFSScan": "Povolit skenování LUFS", - "LabelEnableLUFSScanHelp": "Povolit tvorbu informací LUFS při skenování hudby. Prodlužuje skenování a je náročnější na výkon.", + "LabelEnableLUFSScanHelp": "Umožňuje klientům normalizovat hlasitost u přehrávaných skladeb. Prodlužuje skenování knihovny a je náročnější na výkon.", "GetThePlugin": "Získat zásuvný modul", "Notifications": "Oznámení", "NotificationsMovedMessage": "Funkce oznámení se přesunula do zásuvného modulu Webhook.", @@ -1773,5 +1773,7 @@ "AiTranslated": "Přeložené pomocí UI", "HeaderGuestCast": "Zvláštní hosté", "GoHome": "Přejít na domovskou obrazovku", - "UnknownError": "Došlo k neznámé chybě." + "UnknownError": "Došlo k neznámé chybě.", + "BackdropScreensaver": "Pozadí", + "LogoScreensaver": "Logo" } From 4a6d0f93878cfb05bfbb97173dfad38661ae3318 Mon Sep 17 00:00:00 2001 From: blob03 Date: Thu, 21 Sep 2023 19:35:50 +0000 Subject: [PATCH 026/142] Translated using Weblate (French) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fr/ --- src/strings/fr.json | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/strings/fr.json b/src/strings/fr.json index ea1d55880..e97a60fb0 100644 --- a/src/strings/fr.json +++ b/src/strings/fr.json @@ -1409,7 +1409,7 @@ "QuickConnectDescription": "Pour utiliser la connexion rapide, appuyez sur le bouton 'Connexion rapide' de l'appareil à connecter et entrez le code affiché ci-dessous.", "QuickConnectDeactivated": "La connexion rapide a été désactivée avant que la requête ne puisse être approuvée", "QuickConnectAuthorizeFail": "Code de connexion rapide inconnu", - "QuickConnectAuthorizeSuccess": "Requête autorisée", + "QuickConnectAuthorizeSuccess": "Vous avez authentifié votre appareil avec succès !", "QuickConnectAuthorizeCode": "Saisir le code {0} pour se connecter", "QuickConnectActivationSuccessful": "Activé avec succès", "QuickConnect": "Connexion rapide", @@ -1733,7 +1733,7 @@ "EnableAudioNormalizationHelp": "La normalisation audio ajoutera un gain constant pour maintenir la moyenne au niveau souhaité (-18dB).", "EnableAudioNormalization": "Normalisation audio", "LabelEnableLUFSScan": "Activer l’analyse LUFS", - "LabelEnableLUFSScanHelp": "Activer l’analyse LUFS pour la musique (cela prendra plus de temps et de ressources).", + "LabelEnableLUFSScanHelp": "Les clients peuvent normaliser la lecture audio pour obtenir une intensité sonore égale sur toutes les pistes. L'analyse de bibliothèque nécessitera alors plus de temps et de ressources.", "GetThePlugin": "Obtenir l'extension", "Notifications": "Notifications", "NotificationsMovedMessage": "La fonctionnalité de notifications a été transférée à l'extension Webhook.", @@ -1748,7 +1748,7 @@ "LabelLevel": "Niveau", "LabelMediaDetails": "Détails du média", "LabelSystem": "Système", - "LogLevel.Trace": "Suivi", + "LogLevel.Trace": "Trace", "LogLevel.Debug": "Debug", "LogLevel.Information": "Information", "LogLevel.Warning": "Avertissement", @@ -1764,5 +1764,16 @@ "LabelSegmentKeepSecondsHelp": "Durée en secondes de conservation des segments avant écrasement. La valeur doit être supérieure au délai d'ajustement. Ne fonctionne que si la suppression des segments est activée.", "LabelBackdropScreensaverIntervalHelp": "Le temps en secondes entre différents fonds d'écran lors de l'utilisation de l'économiseur d'écran à fonds d'écran.", "LabelBackdropScreensaverInterval": "Intervalle de l'économiseur d'écran à fonds d'écran", - "AllowAv1Encoding": "Autoriser l'encodage au format AV1" + "AllowAv1Encoding": "Autoriser l'encodage au format AV1", + "GoHome": "Retour à l'accueil", + "ListView": "Vue en liste", + "GridView": "Vue en grille", + "BackdropScreensaver": "Diaporama de fond d’écran", + "LogoScreensaver": "Logo de Jellyfin", + "UnknownError": "Une erreur inconnue s’est produite.", + "MachineTranslated": "Traduction automatique", + "AiTranslated": "Traduction par IA", + "ForeignPartsOnly": "Parties forcées/étrangères uniquement", + "HearingImpairedShort": "HI/SDH", + "HeaderGuestCast": "Stars invitées" } From 447245edbca42523dbf8f225724126b72034be18 Mon Sep 17 00:00:00 2001 From: Shey AlN Date: Thu, 21 Sep 2023 23:26:29 +0000 Subject: [PATCH 027/142] Translated using Weblate (Arabic) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/ar/ --- src/strings/ar.json | 39 ++++++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/src/strings/ar.json b/src/strings/ar.json index 563688382..f86a87892 100644 --- a/src/strings/ar.json +++ b/src/strings/ar.json @@ -524,10 +524,10 @@ "MessageNoPluginsInstalled": "ليس عندك أي ملحقات مثبتة.", "MessageNoTrailersFound": "قم بتثبيت قناة العروض الإعلانية لتحسين متعة المشاهدة بإضافة مكتبة عروض إعلانية من الإنترنت.", "MessageNothingHere": "لا شىء هنا.", - "MessagePasswordResetForUsers": "تم إعادة تعيين كلمات المرور الخاصة بهم للمستخدمين التاليين. يمكنهم الآن تسجيل الدخول باستخدام رموز PIN سهلة الاستخدام التي تم استخدامها لإعادة الضبط.", + "MessagePasswordResetForUsers": "تم إعادة تعيين كلمات المرور للمستخدمين التاليين. يمكنهم الآن تسجيل الدخول باستخدام رموز الـPIN التي تم استخدامها لإعادة الضبط.", "MessagePleaseEnsureInternetMetadata": "الرجاء التأكد من أن إمكانية إنزال واصفات البيانات من الإنترنت ممكنة.", "MessagePluginConfigurationRequiresLocalAccess": "لضبط هذا البرنامج المساعد ، يرجى تسجيل الدخول إلى الخادم المحلي الخاص بك مباشرة.", - "MessagePluginInstallDisclaimer": "إن الملحقات التي بناها أعضاء مجتمع Jellyfin لهي طريقة رائعة لتحسين متعة استخدام Jellyfin وذلك بإضافة المزايا والخدمات الجديدة. قبل تثبيت الملحقات، نرجو أخذ العلم بالآثار التي قد تلحقها بخادم Jellyfin الخاص بك، مثل أوقات أطولة لتمشيط مكتبتك، والعمليات الخلفية الإضافية وتقليل استقرار نظامك.", + "MessagePluginInstallDisclaimer": "تحذير: تنصيب الملحقات التي بناها أعضاء مجتمع Jellyfin هي طريقة رائعة لتحسين متعة استخدام Jellyfin عن طريق أضافة مزايا وخدمات الجديدة. ولكن تنصيب ملحقات من مصادر ثالثة تحمل بعظ الخطورة.\nقبل تثبيت الملحقات، نرجو أخذ العلم بالآثار التي قد تلحقها بخادم Jellyfin الخاص بك، مثل أوقات أطولة لتمشيط مكتبتك، والعمليات الخلفية الإضافية وتقليل استقرار نظامك.", "MessageReenableUser": "أنظر أدناه لإعادة التفعيل", "MessageTheFollowingLocationWillBeRemovedFromLibrary": "مكان الوسائط التالي سيزال من مكتبة Jellyfin الخاصة بك", "MessageUnableToConnectToServer": "لم نستطع الاتصال إلى الخادم المختار في الوقت الحالي. الرجاء التأكد من أنه يعمل ثم المحاولة مرة أخرى.", @@ -1018,7 +1018,7 @@ "Image": "صورة", "Other": "اخري", "EnableQuickConnect": "تفعيل الاتصال السريع على هذا الخادم", - "EnableAutoCast": "تعيين كافتراضي", + "EnableAutoCast": "تعيين كالخيار ألأفتراضي", "Data": "بيانات", "ButtonUseQuickConnect": "استخدم الاتصال السريع", "ButtonActivate": "تفعيل", @@ -1564,7 +1564,7 @@ "LabelAutomaticallyAddToCollection": "إضافة إلى المجموعة تلقائيا", "Console": "وحدة التحكم", "Casual": "غير رسمي", - "AllowTonemappingHelp": "يمكن أن يؤدي تعيين النغمة إلى تحويل النطاق الديناميكي لمقطع فيديو من HDR إلى SDR مع الحفاظ على تفاصيل الصورة والألوان ، وهي معلومات مهمة جدًا لتمثيل المشهد الأصلي. يعمل حاليًا فقط مع مقاطع فيديو HDR10 أو HLG. يتطلب هذا وقت تشغيل OpenCL أو CUDA المقابل.", + "AllowTonemappingHelp": "يمكن أن يؤدي تعيين النغمة إلى تحويل النطاق الديناميكي لمقاطع الفيديو من HDR إلى SDR مع الحفاظ على تفاصيل الصورة والألوان. هذه بينات مهمة جدًا لتمثيل المشهد الأصلي بشكل وفي للمقطع ألأصلي. حاليًا يعمل هذا ألاعداد فقط مع مقاطع فيديو HDR10 أو HLG. يتطلب هذا ألأعداد وقت تشغيل OpenCL أو CUDA.", "RefFramesNotSupported": "الإطارات المرجعية غير مدعومة", "InterlacedVideoNotSupported": "الفيديو المتشابك غير مدعوم", "AnamorphicVideoNotSupported": "لا يتم دعم الفيديو ذي الصورة المشوهة", @@ -1683,8 +1683,8 @@ "Unreleased": "لم يصدر حتى الآن", "MessageRenameMediaFolder": "يرجى ملاحظة أنه ستحذف جميع البيانات الوصفية عند إعادة تسمية مكتبة الوسائط.", "DownloadAll": "تحميل الكل", - "LabelChapterImageResolutionHelp": "دقة الصور المستأصلة من الفصل.", - "LabelDummyChapterDurationHelp": "فترة استئصال الصور من الفصل بالثواني.", + "LabelChapterImageResolutionHelp": "دقة الصور المستأصلة من الفصل. تغيير هذا ألاعداد لن يؤثر على الفصول الوهمية الذي تم أنشائها مسبقاً.", + "LabelDummyChapterDurationHelp": "فترة الوقت بين ألفصول ألوهمية بالثواني. قم بتعيينه ألى 0 لتعطيل ميزة أنشاء الفصول ألوهمية. تغيير هذا ألاعداد لن يؤثر على الفصول الوهمية الذي تم أنشائها مسبقاً.", "Experimental": "تجريبي", "HeaderDummyChapter": "صور الفصل", "HeaderPerformance": "الأداء", @@ -1696,5 +1696,30 @@ "LabelChapterImageResolution": "الدقة", "LabelEnableAudioVbr": "فعل تشفير VBR الصوتي", "AllowCollectionManagement": "السماح لهذا المستخدم بإدارة المجموعات", - "Notifications": "الإشعارات" + "Notifications": "الإشعارات", + "LabelEnableLUFSScan": "تفعيل فحص LUFS", + "LabelEnableLUFSScanHelp": "يمكن للعملاء توسيط ألصوت للحصول على مستوى صوت متساوي عبر المقاطع الصوتية. سيجعل هذا فحص المكتبة أطول ويستهلك مزيدًا من الموارد.", + "LabelMediaDetails": "تفاصيل الميديا", + "LogLevel.Warning": "تحذير", + "LogLevel.Information": "ألمعلومات", + "BackdropScreensaver": "خلفية شاشة التوقف", + "LabelDate": "تاريخ", + "LabelDeveloper": "مُطور", + "LabelBackdropScreensaverIntervalHelp": "ألوقت بالثواني بين الخلفيات المختلفة عند أستخدام خلفية شاشة التوقف.", + "LogLevel.Error": "خطأ", + "LogoScreensaver": "علامة حامية الشاشة", + "MenuClose": "أغلق القائمة", + "LabelSystem": "ألنظام", + "Unknown": "غير معروف", + "LogLevel.Critical": "خطير", + "LogLevel.None": "لا شيئ", + "MenuOpen": "أفتح القائمة", + "AllowSegmentDeletion": "ألغاء القسم", + "AllowSegmentDeletionHelp": "ألغي الأقسام القديمة بعد أن يتم أرسالها للعميل. هذا يساهم بمنع أن يتم تخزين الملف ألذي تم أعادة ترميزه. ستعمل هذه الميزة فقط عندما يتم كبح الترميز. قم بأيقاف هذا الأعداد في حال واجهت مشاكل بتشغيل ألصوت أو ألفديو.", + "LabelThrottleDelaySeconds": "أكبح بعد", + "LabelThrottleDelaySecondsHelp": "ألزمن بالثواني الذي سيتم بعده كبح أعادة الترميز. يجب أن يكون الزمن طويلاً بكفاية ليحافظ العميل على مخزون صحي. يجب أن يكون كفح أعادة الترميز مفعلاً ليعمل هذا ألاعاداد.", + "LabelSegmentKeepSeconds": "الممدة للأحتفاظ على الشرائح", + "LabelEnableAudioVbrHelp": "معدل البِت المتغير ينتج على جودة أفضل مقارنة بمعدل البت المتوسط، ولكن في بعض الحالات النادرة قد يسبب مشاكل في التخزين المؤقت والتوافق.", + "LabelSegmentKeepSecondsHelp": "الزمن بالثواني الذي يجب الاحتفاظ به للشرائح قبل أن يتم الكتابة فوقها. يجب أن يكون أكبر من \"بعد الخنق\". يعمل هذا ألأعداد فقط إذا كان حذف الشرائح مفعلًا.", + "AiTranslated": "مترجمة من قبل ذكاء اسطناعي" } From e7cb4ba670173d092aec72bd80d885774bde0096 Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Tue, 23 May 2023 00:39:52 +0300 Subject: [PATCH 028/142] Don't show OSD for Fullscreen and Mute (cherry picked from commit 51bd2bef1a9c3e248167e337b87c592c4d091d9a) --- src/controllers/playback/video/index.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/controllers/playback/video/index.js b/src/controllers/playback/video/index.js index 4fd441975..5669fa24f 100644 --- a/src/controllers/playback/video/index.js +++ b/src/controllers/playback/video/index.js @@ -1230,12 +1230,10 @@ export default function (view) { case 'f': if (!e.ctrlKey && !e.metaKey) { playbackManager.toggleFullscreen(currentPlayer); - showOsd(); } break; case 'm': playbackManager.toggleMute(currentPlayer); - showOsd(); break; case 'p': case 'P': From 0381af80f1858643488d67d4ad1697cff182ee78 Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Tue, 23 May 2023 00:40:37 +0300 Subject: [PATCH 029/142] Focus on corresponding button (cherry picked from commit 884ce171ea6056f7da0e856692ce87afcebd3bf7) --- src/controllers/playback/video/index.js | 33 ++++++++++++++++--------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/controllers/playback/video/index.js b/src/controllers/playback/video/index.js index 5669fa24f..d51148d3d 100644 --- a/src/controllers/playback/video/index.js +++ b/src/controllers/playback/video/index.js @@ -1,4 +1,5 @@ import escapeHtml from 'escape-html'; +import debounce from 'lodash-es/debounce'; import { playbackManager } from '../../../components/playback/playbackmanager'; import browser from '../../../scripts/browser'; import dom from '../../../scripts/dom'; @@ -258,9 +259,9 @@ export default function (view) { let mouseIsDown = false; - function showOsd() { + function showOsd(focusElement) { slideDownToShow(headerElement); - showMainOsdControls(); + showMainOsdControls(focusElement); resetIdle(); } @@ -313,7 +314,9 @@ export default function (view) { }); } - function showMainOsdControls() { + const _focus = debounce((focusElement) => focusManager.focus(focusElement), 50); + + function showMainOsdControls(focusElement) { if (!currentVisibleMenu) { const elem = osdBottomElement; currentVisibleMenu = 'osd'; @@ -321,12 +324,14 @@ export default function (view) { elem.classList.remove('hide'); elem.classList.remove('videoOsdBottom-hidden'); + focusElement ||= elem.querySelector('.btnPause'); + if (!layoutManager.mobile) { - setTimeout(function () { - focusManager.focus(elem.querySelector('.btnPause')); - }, 50); + _focus(focusElement); } toggleSubtitleSync(); + } else if (currentVisibleMenu === 'osd' && focusElement && !layoutManager.mobile) { + _focus(focusElement); } } @@ -1174,15 +1179,19 @@ export default function (view) { const key = keyboardnavigation.getKeyName(e); const isKeyModified = e.ctrlKey || e.altKey || e.metaKey; + const btnPlayPause = osdBottomElement.querySelector('.btnPause'); + if (e.keyCode === 32) { if (e.target.tagName !== 'BUTTON' || !layoutManager.tv) { playbackManager.playPause(currentPlayer); + showOsd(btnPlayPause); e.preventDefault(); e.stopPropagation(); // Trick Firefox with a null element to skip next click clickedElement = null; + } else { + showOsd(); } - showOsd(); return; } @@ -1205,7 +1214,7 @@ export default function (view) { break; case 'k': playbackManager.playPause(currentPlayer); - showOsd(); + showOsd(btnPlayPause); break; case 'ArrowUp': case 'Up': @@ -1219,13 +1228,13 @@ export default function (view) { case 'ArrowRight': case 'Right': playbackManager.fastForward(currentPlayer); - showOsd(); + showOsd(btnFastForward); break; case 'j': case 'ArrowLeft': case 'Left': playbackManager.rewind(currentPlayer); - showOsd(); + showOsd(btnRewind); break; case 'f': if (!e.ctrlKey && !e.metaKey) { @@ -1253,7 +1262,7 @@ export default function (view) { // Ignores gamepad events that are always triggered, even when not focused. if (document.hasFocus()) { /* eslint-disable-line compat/compat */ playbackManager.rewind(currentPlayer); - showOsd(); + showOsd(btnRewind); } break; case 'NavigationRight': @@ -1262,7 +1271,7 @@ export default function (view) { // Ignores gamepad events that are always triggered, even when not focused. if (document.hasFocus()) { /* eslint-disable-line compat/compat */ playbackManager.fastForward(currentPlayer); - showOsd(); + showOsd(btnFastForward); } break; case 'Home': From 8c8e24079243c69bfc7a707a3672a5690f1e1599 Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Tue, 23 May 2023 00:41:35 +0300 Subject: [PATCH 030/142] Change behavior of arrow keys and Enter when OSD is hidden (cherry picked from commit 76c55116ce7a1edd41ea57f6d170ce7a6e19cb82) --- src/controllers/playback/video/index.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/controllers/playback/video/index.js b/src/controllers/playback/video/index.js index d51148d3d..2cb099856 100644 --- a/src/controllers/playback/video/index.js +++ b/src/controllers/playback/video/index.js @@ -1195,6 +1195,21 @@ export default function (view) { return; } + if (layoutManager.tv && !currentVisibleMenu) { + // Change the behavior of some keys when the OSD is hidden + switch (key) { + case 'ArrowLeft': + case 'ArrowRight': + showOsd(nowPlayingPositionSlider); + nowPlayingPositionSlider.dispatchEvent(new KeyboardEvent(e.type, e)); + return; + case 'Enter': + playbackManager.playPause(currentPlayer); + showOsd(btnPlayPause); + return; + } + } + if (layoutManager.tv && keyboardnavigation.isNavigationKey(key)) { showOsd(); return; From ab5c4949c3b4af3d1104524827ff68ad5ff9c7fc Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Mon, 17 Jul 2023 21:47:31 +0300 Subject: [PATCH 031/142] Add KeyboardEvent constructor polyfill (cherry picked from commit 11ae2ff43ffe2fdd563bc39dac24f0cfb5f7c3d3) --- src/index.jsx | 1 + src/legacy/keyboardEvent.js | 48 +++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 src/legacy/keyboardEvent.js diff --git a/src/index.jsx b/src/index.jsx index 16f610931..cf0e6cafb 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -32,6 +32,7 @@ import './components/playback/playerSelectionMenu'; import './legacy/domParserTextHtml'; import './legacy/focusPreventScroll'; import './legacy/htmlMediaElement'; +import './legacy/keyboardEvent'; import './legacy/vendorStyles'; import { currentSettings } from './scripts/settings/userSettings'; import taskButton from './scripts/taskbutton'; diff --git a/src/legacy/keyboardEvent.js b/src/legacy/keyboardEvent.js new file mode 100644 index 000000000..8b40e617d --- /dev/null +++ b/src/legacy/keyboardEvent.js @@ -0,0 +1,48 @@ +/** + * Polyfill for KeyboardEvent + * - Constructor. + */ + +(function (window) { + 'use strict'; + + try { + new window.KeyboardEvent('event', { bubbles: true, cancelable: true }); + } catch (e) { + // We can't use `KeyboardEvent` in old WebKit because `initKeyboardEvent` + // doesn't seem to populate some properties (`keyCode`, `which`) that + // are read-only. + const KeyboardEventOriginal = window.Event; + + const KeyboardEvent = function (eventName, options) { + options = options || {}; + + const event = document.createEvent('Event'); + + event.initEvent(eventName, !!options.bubbles, !!options.cancelable); + + event.view = options.view || document.defaultView; + + event.key = options.key || options.keyIdentifier || ''; + event.keyCode = options.keyCode || 0; + event.code = options.code || ''; + event.charCode = options.charCode || 0; + event.char = options.char || ''; + event.which = options.which || 0; + + event.location = options.location || options.keyLocation || 0; + + event.ctrlKey = !!options.ctrlKey; + event.altKey = !!options.altKey; + event.shiftKey = !!options.shiftKey; + event.metaKey = !!options.metaKey; + + event.repeat = !!options.repeat; + + return event; + }; + + KeyboardEvent.prototype = KeyboardEventOriginal.prototype; + window.KeyboardEvent = KeyboardEvent; + } +}(window)); From 2d0e18e7ed5f69861999cea923d8ca9f032615fc Mon Sep 17 00:00:00 2001 From: Sverre Date: Fri, 22 Sep 2023 08:22:53 +0000 Subject: [PATCH 032/142] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegi?= =?UTF-8?q?an=20Bokm=C3=A5l)=20Translation:=20Jellyfin/Jellyfin=20Web=20Tr?= =?UTF-8?q?anslate-URL:=20https://translate.jellyfin.org/projects/jellyfin?= =?UTF-8?q?/jellyfin-web/nb=5FNO/?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/strings/nb.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/strings/nb.json b/src/strings/nb.json index e984012d0..eaf915e98 100644 --- a/src/strings/nb.json +++ b/src/strings/nb.json @@ -1734,7 +1734,7 @@ "LabelParallelImageEncodingLimitHelp": "Høyeste antall bildeenkodinger som tillates å kjøre parallelt. Å sette denne til 0 vil velge en grensse basert på systemspesifikasjonene dine.", "LabelChapterImageResolutionHelp": "Oppløsningen til kapittelbildene. Enring av dette vil ikke ha noen effekt på eksisterende kapittelbilder.", "LabelParallelImageEncodingLimit": "Parallell bildeenkodingsgrense", - "LabelEnableLUFSScanHelp": "Slå på LUFS-skanning for musikk (dette vil ta lengre tid og mere ressurser).", + "LabelEnableLUFSScanHelp": "Klienter kan normalisere lydnivå for å få lik lydstyrke på tvers av spor. Dette vil gjøre bibliotekskanning lengre og bruke flere ressurser.", "ResolutionMatchSource": "Bruk kildetreff", "SaveRecordingNFOHelp": "Lagre metadata fra EPG-listekilde sammen med media.", "AllowCollectionManagement": "La denne brukeren organisere samlinger", @@ -1768,5 +1768,7 @@ "AllowAv1Encoding": "Tillat enkoding i AV1-format", "AiTranslated": "AI-oversatt", "MachineTranslated": "Maskinoversatt", - "HeaderGuestCast": "Gjestestjerner" + "HeaderGuestCast": "Gjestestjerner", + "HearingImpairedShort": "HI/SDH", + "LogoScreensaver": "Logoskjermsparer" } From faf29e7ee7d62c8ab083a8732f80c3619fbffffd Mon Sep 17 00:00:00 2001 From: Kityn Date: Fri, 22 Sep 2023 12:21:06 +0000 Subject: [PATCH 033/142] Translated using Weblate (Polish) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/pl/ --- src/strings/pl.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings/pl.json b/src/strings/pl.json index 8f5de2efb..641afdea4 100644 --- a/src/strings/pl.json +++ b/src/strings/pl.json @@ -1733,7 +1733,7 @@ "EnableAudioNormalizationHelp": "Normalizacja dźwięku doda stałe wzmocnienie, aby utrzymać średnią na pożądanym poziomie (-18dB).", "EnableAudioNormalization": "Normalizacja dźwięku", "LabelEnableLUFSScan": "Włącz skanowanie głośności dźwięku", - "LabelEnableLUFSScanHelp": "Włącz skanowanie głośności dźwięku dla muzyki (To zajmie więcej czasu i więcej zasobów).", + "LabelEnableLUFSScanHelp": "Klienty mogą normalizować odtwarzanie dźwięku, aby uzyskać jednakową głośność na wszystkich ścieżkach. Wydłuży to skanowanie biblioteki i zajmie więcej zasobów.", "Notifications": "Powiadomienia", "PasswordRequiredForAdmin": "Hasło jest wymagane do kont administratorów.", "LabelSyncPlayNoGroups": "Brak dostępnych grup", From 0c443053d313f154353004658b4c1b0f60bf80f5 Mon Sep 17 00:00:00 2001 From: layfu Date: Fri, 22 Sep 2023 12:31:12 +0000 Subject: [PATCH 034/142] Translated using Weblate (Chinese (Simplified)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/zh_Hans/ --- src/strings/zh-cn.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings/zh-cn.json b/src/strings/zh-cn.json index 1300a28e8..f6144f291 100644 --- a/src/strings/zh-cn.json +++ b/src/strings/zh-cn.json @@ -1188,7 +1188,7 @@ "PlayNextEpisodeAutomatically": "自动播放下一集", "Premieres": "首映", "Raised": "提高", - "Recordings": "录音", + "Recordings": "录制", "RefreshDialogHelp": "元数据根据设置和仪表盘中启用的网络服务进行刷新。", "RepeatEpisodes": "重复剧集", "Schedule": "日程", From a8deca68ea000388912fe82746ac188c84bab038 Mon Sep 17 00:00:00 2001 From: grafixeyehero Date: Fri, 22 Sep 2023 21:16:04 +0300 Subject: [PATCH 035/142] fix typo --- src/apps/experimental/components/library/AlphabetPicker.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/apps/experimental/components/library/AlphabetPicker.tsx b/src/apps/experimental/components/library/AlphabetPicker.tsx index 4b8c6019e..aec6d2eb9 100644 --- a/src/apps/experimental/components/library/AlphabetPicker.tsx +++ b/src/apps/experimental/components/library/AlphabetPicker.tsx @@ -21,7 +21,7 @@ const AlphabetPicker: React.FC = ({ libraryViewSettings, setLibraryViewSettings }) => { - const handelValue = useCallback( + const handleValue = useCallback( ( event: React.MouseEvent, newValue: string | null | undefined @@ -66,7 +66,7 @@ const AlphabetPicker: React.FC = ({ exclusive color='primary' size='small' - onChange={handelValue} + onChange={handleValue} > {letters.map((l) => ( Date: Fri, 22 Sep 2023 16:14:40 +0000 Subject: [PATCH 036/142] Translated using Weblate (Finnish) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fi/ --- src/strings/fi.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/strings/fi.json b/src/strings/fi.json index 81a239a00..77da53d7d 100644 --- a/src/strings/fi.json +++ b/src/strings/fi.json @@ -1731,7 +1731,7 @@ "EnableAudioNormalizationHelp": "Äänen normalisointi asettaa äänelle kiinteän vahvistuksen keskivoimakkuuden vakiotasolla (-18 dB).", "EnableAudioNormalization": "Äänen normalisointi", "LabelEnableLUFSScan": "Suorita LUFS-tarkistus", - "LabelEnableLUFSScanHelp": "Käytä musiikin LUFS-tarkistusta (tämä vaatii enemmän aikaa ja resurseja).", + "LabelEnableLUFSScanHelp": "Päätteet voivat nyt tasoittaa ääniraitojen välisiä äänenvoimakkuuseroja. Tämä pidentää kirjastotarkistuksia ja kuormittaa laitteistoa enemmän.", "GetThePlugin": "Hanki lisäosa", "Notifications": "Ilmoitukset", "NotificationsMovedMessage": "Ilmoitustoiminnallisuus on siirtynyt Webhook-lisäosaan.", @@ -1771,5 +1771,7 @@ "HearingImpairedShort": "HI/SDH", "HeaderGuestCast": "Vierailevat pääosat", "GoHome": "Siirry aloitusnäyttöön", - "UnknownError": "Tapahtui tuntematon virhe." + "UnknownError": "Tapahtui tuntematon virhe.", + "BackdropScreensaver": "Backdrop-näytönsäästäjä", + "LogoScreensaver": "Logo-näytönsäästäjä" } From c8adb6cad13c9ce948060d430a08127b7f9b380d Mon Sep 17 00:00:00 2001 From: Recrucity Date: Fri, 22 Sep 2023 16:45:45 +0000 Subject: [PATCH 037/142] Translated using Weblate (Swedish) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/sv/ --- src/strings/sv.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/strings/sv.json b/src/strings/sv.json index a8e91a2f8..d9c4b0927 100644 --- a/src/strings/sv.json +++ b/src/strings/sv.json @@ -1760,5 +1760,13 @@ "LabelDeveloper": "Utväcklare", "Notifications": "Notifieringar", "UserMenu": "Användarmeny", - "LabelTonemappingMode": "Tonmappningsläge" + "LabelTonemappingMode": "Tonmappningsläge", + "AllowAv1Encoding": "Tillåt kodning i AV1-format", + "GoHome": "Hem", + "UnknownError": "Ett okänt fel har uppstått.", + "HeaderGuestCast": "Gästroller", + "LabelBackdropScreensaverIntervalHelp": "Tiden i sekunder mellan olika bakgrunder när bakgrunds-skärmsläckaren används.", + "AiTranslated": "AI-översatt", + "MachineTranslated": "Maskinöversatt", + "HearingImpairedShort": "HI/SDH" } From f0a96d6c6a2d03e3a98f369cb331ba54b5813720 Mon Sep 17 00:00:00 2001 From: Recrucity Date: Fri, 22 Sep 2023 16:25:23 +0000 Subject: [PATCH 038/142] Translated using Weblate (Japanese) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/ja/ --- src/strings/ja.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/strings/ja.json b/src/strings/ja.json index 0e9537755..ff0acbdb1 100644 --- a/src/strings/ja.json +++ b/src/strings/ja.json @@ -1758,5 +1758,11 @@ "LogLevel.Error": "エラー", "LogLevel.Critical": "致命的なエラー", "MessageRepositoryInstallDisclaimer": "【警告】コミュニティメンバーによって作成されたプラグインはリスクを伴います。不安定、または悪質な挙動、予期せぬ時に変更されるなどの可能性があります。信用性の高い開発者からのプラグインのみをインストールすることをお勧めします。", - "Select": "選択" + "Select": "選択", + "AllowAv1Encoding": "AV1フォーマットのエンコードを許可する", + "GoHome": "ホームへ", + "UnknownError": "未知のエラーが発生しました。", + "LogoScreensaver": "ロゴスクリーンセーバー", + "AiTranslated": "AI翻訳", + "MachineTranslated": "機械翻訳" } From cbf33c15f21008ac60e8d1097fdb1e774b44adab Mon Sep 17 00:00:00 2001 From: grafixeyehero Date: Thu, 21 Sep 2023 04:55:05 +0300 Subject: [PATCH 039/142] Remove unused path quickConnect.html from legacyRoutes admin route --- src/apps/experimental/routes/legacyRoutes/admin.ts | 6 ------ src/apps/stable/routes/legacyRoutes/admin.ts | 6 ------ 2 files changed, 12 deletions(-) diff --git a/src/apps/experimental/routes/legacyRoutes/admin.ts b/src/apps/experimental/routes/legacyRoutes/admin.ts index b53f86ce8..35a397644 100644 --- a/src/apps/experimental/routes/legacyRoutes/admin.ts +++ b/src/apps/experimental/routes/legacyRoutes/admin.ts @@ -31,12 +31,6 @@ export const LEGACY_ADMIN_ROUTES: LegacyRoute[] = [ controller: 'dashboard/devices/device', view: 'dashboard/devices/device.html' } - }, { - path: 'quickConnect.html', - pageProps: { - controller: 'dashboard/quickConnect', - view: 'dashboard/quickConnect.html' - } }, { path: 'dlnaprofile.html', pageProps: { diff --git a/src/apps/stable/routes/legacyRoutes/admin.ts b/src/apps/stable/routes/legacyRoutes/admin.ts index 92aaef804..fd430feeb 100644 --- a/src/apps/stable/routes/legacyRoutes/admin.ts +++ b/src/apps/stable/routes/legacyRoutes/admin.ts @@ -31,12 +31,6 @@ export const LEGACY_ADMIN_ROUTES: LegacyRoute[] = [ controller: 'dashboard/devices/device', view: 'dashboard/devices/device.html' } - }, { - path: 'quickConnect.html', - pageProps: { - controller: 'dashboard/quickConnect', - view: 'dashboard/quickConnect.html' - } }, { path: 'dlnaprofile.html', pageProps: { From 9e2f6756dbebde04d11d1a2f79e51f80eda77734 Mon Sep 17 00:00:00 2001 From: stanol Date: Fri, 22 Sep 2023 21:06:58 +0000 Subject: [PATCH 040/142] Translated using Weblate (Ukrainian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/uk/ --- src/strings/uk.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings/uk.json b/src/strings/uk.json index 6314267cc..7d25f5257 100644 --- a/src/strings/uk.json +++ b/src/strings/uk.json @@ -1728,7 +1728,7 @@ "Studio": "Студія", "AllowCollectionManagement": "Дозволити цьому користувачеві керувати колекціями", "EnableAudioNormalizationHelp": "Нормалізація звуку додасть постійний коефіцієнт підсилення, щоб утримати середній рівень на потрібному рівні (-18 дБ).", - "LabelEnableLUFSScanHelp": "Увімкніть сканування LUFS для пошуку музики (це займе більше часу і ресурсів).", + "LabelEnableLUFSScanHelp": "Клієнти можуть нормалізувати відтворення звуку, щоб отримати однакову гучність на всіх доріжках. Це зробить сканування бібліотеки довшим і забиратиме більше ресурсів.", "EnableAudioNormalization": "Нормалізація звуку", "LabelEnableLUFSScan": "Увімкнути сканування LUFS", "GetThePlugin": "Отримати плагін", From 48bfeb1ea58bfe4fc3c5153820ace1e73109a1b8 Mon Sep 17 00:00:00 2001 From: minerprosvk Date: Sat, 23 Sep 2023 05:38:56 +0000 Subject: [PATCH 041/142] Translated using Weblate (Slovak) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/sk/ --- src/strings/sk.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/strings/sk.json b/src/strings/sk.json index e7bd6c2fe..a4fea164a 100644 --- a/src/strings/sk.json +++ b/src/strings/sk.json @@ -1736,5 +1736,6 @@ "UserMenu": "Užívateľská ponuka", "LabelEnableLUFSScan": "Povoliť skenovanie LUFS", "LabelEnableLUFSScanHelp": "Povoliť skenovanie LUFS pre hudbu (Predlžuje skenovanie a je náročnejšie na výkon).", - "MenuOpen": "Otvoriť ponuku" + "MenuOpen": "Otvoriť ponuku", + "AllowSegmentDeletion": "Zmazať oddiel" } From 12f557299837fb49848d93e9f42e7103c507798c Mon Sep 17 00:00:00 2001 From: that-skyfox Date: Sat, 23 Sep 2023 09:30:24 +0000 Subject: [PATCH 042/142] Translated using Weblate (Latvian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/lv/ --- src/strings/lv.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/strings/lv.json b/src/strings/lv.json index 5490a7aed..5cd5b0d55 100644 --- a/src/strings/lv.json +++ b/src/strings/lv.json @@ -1480,5 +1480,7 @@ "QuickConnectInvalidCode": "Nederīgs Quick Connect kods", "OptionAutomaticallyGroupSeries": "Automātiski apvienot sērijas, kuras izvietotas vairākās mapēs", "PreferEmbeddedTitlesOverFileNamesHelp": "Noteikt redzamo nosaukumu, kas jāizmanto, ja nav pieejami interneta metadati vai vietējie metadati.", - "OptionSpecialEpisode": "Speciālizlaidumi" + "OptionSpecialEpisode": "Speciālizlaidumi", + "AllowSegmentDeletion": "Dzēst segmentus", + "AllowSegmentDeletionHelp": "atskaņošanas" } From 3370d49d51b6907df8e76f707e19605a6387403b Mon Sep 17 00:00:00 2001 From: that-skyfox Date: Sat, 23 Sep 2023 09:40:31 +0000 Subject: [PATCH 043/142] Translated using Weblate (Latvian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/lv/ --- src/strings/lv.json | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/strings/lv.json b/src/strings/lv.json index 5cd5b0d55..a4ecea26d 100644 --- a/src/strings/lv.json +++ b/src/strings/lv.json @@ -1228,7 +1228,7 @@ "ErrorPlayerNotFound": "Atskaņotājs pieprasītajam mēdijam nav atrasts.", "ErrorAddingListingsToSchedulesDirect": "Pievienojot sarakstu jūsu Schedules Direct kontam. Schedules Direct atļauj vienā kontā tikai ierobežotu grupu skaitu. Pirms turpināt, jums būs jāpiesakās Schedules Direct vietnē un jānoņem citi ieraksti no sava konta.", "Engineer": "Skaņas inženieris", - "DirectPlayHelp": "Avota fails ir pilnībā saderīgs ar šo klientu, un šī sesija saņem failu bez modifikācijas.", + "DirectPlayHelp": "Oriģinālais fails ir pilnībā saderīgs ar šo klientu un šī sesija saņem failu bez modifikācijas.", "Cursive": "Kursīvs", "Console": "Konsole", "Conductor": "Diriģents", @@ -1482,5 +1482,19 @@ "PreferEmbeddedTitlesOverFileNamesHelp": "Noteikt redzamo nosaukumu, kas jāizmanto, ja nav pieejami interneta metadati vai vietējie metadati.", "OptionSpecialEpisode": "Speciālizlaidumi", "AllowSegmentDeletion": "Dzēst segmentus", - "AllowSegmentDeletionHelp": "atskaņošanas" + "AllowSegmentDeletionHelp": "Dzēst vecus segmentus pēc to nosūtīšanas klientam. Šis lauj neglabāt visu pārkodēto failu diskā. Izslēdziet šo tikai ja jums ir problēmas ar atskanosanu.", + "GoHome": "Doties mājās", + "HeaderEpisodesStatus": "Epizožu status", + "LabelDate": "Datums", + "LabelBackdropScreensaverInterval": "Fona ekrānsaudzētāja intervāls", + "LabelBackdropScreensaverIntervalHelp": "Laiks sekundēs starp dažādiem foniem kad tiek izmantots fona ekrāns audzētājs.", + "GridView": "Tabulas skats", + "BackdropScreensaver": "Fona ekrānsaudzētājs", + "LabelLevel": "Līmenis", + "HeaderConfirmRepositoryInstallation": "Apstipriniet spraudņu krātuves instalāciju", + "LabelDeveloper": "Izstrādātājs", + "LabelThrottleDelaySeconds": "Bremzēt pēc", + "LabelThrottleDelaySecondsHelp": "Laiks sekundēs pēc kura pārkodētājs tiks pēc kura pārkodētājs tiks bremzēts. Šim jābūt pietiekami lielam lai klients saglabātu veselīgu buferi. Šis strādā tikai ja bremzēšana ir ieslēgta.", + "LabelSegmentKeepSeconds": "Cik ilgi paturēt segmentus.", + "HeaderGuestCast": "Viesu zvaigznes" } From 320c1dc28aceb62cc05098165e1f5c82d5a0218a Mon Sep 17 00:00:00 2001 From: Niels van Velzen Date: Sat, 23 Sep 2023 17:17:51 +0200 Subject: [PATCH 044/142] Allow any application id for cast receiver --- src/plugins/chromecastPlayer/plugin.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/plugins/chromecastPlayer/plugin.js b/src/plugins/chromecastPlayer/plugin.js index e5663c2f0..c4c90c15b 100644 --- a/src/plugins/chromecastPlayer/plugin.js +++ b/src/plugins/chromecastPlayer/plugin.js @@ -106,10 +106,9 @@ class CastPlayer { return; } - let applicationID = applicationStable; - if (userSettings.chromecastVersion() === 'unstable') { - applicationID = applicationUnstable; - } + let applicationID = userSettings.chromecastVersion(); + if (applicationID === 'stable') applicationID = applicationStable; + if (applicationID === 'unstable') applicationID = applicationUnstable; // request session const sessionRequest = new chrome.cast.SessionRequest(applicationID); @@ -117,7 +116,7 @@ class CastPlayer { this.sessionListener.bind(this), this.receiverListener.bind(this)); - console.debug('chromecast.initialize'); + console.debug(`chromecast.initialize (applicationId=${applicationID})`); chrome.cast.initialize(apiConfig, this.onInitSuccess.bind(this), this.errorHandler); } From 42925802fb4cdb8348181815d96add7f609ff4db Mon Sep 17 00:00:00 2001 From: that-skyfox Date: Sat, 23 Sep 2023 14:18:58 +0000 Subject: [PATCH 045/142] Translated using Weblate (Latvian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/lv/ --- src/strings/lv.json | 41 +++++++++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/src/strings/lv.json b/src/strings/lv.json index a4ecea26d..03a6911e1 100644 --- a/src/strings/lv.json +++ b/src/strings/lv.json @@ -643,7 +643,7 @@ "Recordings": "Ieraksti", "RecordingCancelled": "Ieraksts atcelts.", "RecordSeries": "Ierakstīt sēriju", - "Record": "Ierakstīt", + "Record": "Ieraksts", "RecommendationBecauseYouWatched": "Tāpēc ka tu skatījies {0}", "RecommendationBecauseYouLike": "Tāpēc ka tev patīk {0}", "RecentlyWatched": "Nesen skatīts", @@ -887,7 +887,7 @@ "MediaInfoAspectRatio": "Attēla proporcijas", "MaxParentalRatingHelp": "Saturs ar augstāku reitingu tiks paslēpts no šī lietotāja.", "LibraryAccessHelp": "Izvēlies bibliotēkas, ko koplietot ar šo lietotāju. Administratori spēs rediģēt visas bibliotēkas izmantojot metadatu pārvaldnieku.", - "LearnHowYouCanContribute": "Uzziniet, kā jūs varat dot ieguldījumu.", + "LearnHowYouCanContribute": "Uzziniet, kā jūs varat palīdzēt.", "LabelUserLoginAttemptsBeforeLockout": "Neizdevušies piekļuves mēģinājumi pirms lietotājs tiek bloķēts", "LabelTranscodingThreadCount": "Pārkodēšanas pavedienu daudzums", "LabelTranscodes": "Transkodi", @@ -1302,7 +1302,7 @@ "AllowEmbeddedSubtitlesAllowTextOption": "Atļaut tekstu", "OptionResElement": "'res' elements", "QuickConnectAuthorizeCode": "Lai autorizētos, ievadiet kodu {0}", - "QuickConnectAuthorizeSuccess": "Pieprasījums autorizēts", + "QuickConnectAuthorizeSuccess": "Jūsu ierīce ir autorizēta!", "MediaInfoTitle": "Nosaukums", "MillisecondsUnit": "ms", "Photo": "Attēls", @@ -1363,7 +1363,7 @@ "EnableAudioNormalizationHelp": "Audio normalizācija pievienos konstantu skaļuma pastiprinājumu, lai saglabātu vidējo skaļumu vēlamajā līmenī (-18dB).", "EnableAudioNormalization": "Audio Normalizācija", "LabelEnableLUFSScan": "Iespējot LUFS skenēšanu", - "LabelEnableLUFSScanHelp": "Iespējot LUFS skenēšanu mūzikai (Tas aizņems vairāk laika un resursu).", + "LabelEnableLUFSScanHelp": "Klienti var normalizēt audio atskaņošanu, lai iegūtu vienādu skaļumu visos ierakstos. (Tas pataisīs bibliotēkas skenēšanu lēnāku un patērēs vairāk resursus).", "MessageNoItemsAvailable": "Pašlaik nav pieejams neviens vienums.", "MessageNoCollectionsAvailable": "Kolekcijas ļauj izmantot personalizētas filmu, seriālu un albumu grupas. Noklikšķiniet uz pogas \"+\", lai sāktu veidot kolekcijas.", "MessageNoRepositories": "Nav repozitoriju.", @@ -1377,8 +1377,8 @@ "MessageNoFavoritesAvailable": "Pašlaik nav pieejami nekādi favorīti.", "MessageImageTypeNotSelected": "Lūdzu, izvēlaties attēla veidu no nolaižamās izvēlnes.", "MessageLeaveEmptyToInherit": "Atstājiet tukšu, lai mantotu iestatījumus no vecākā elementa vai globālās noklusējuma vērtības.", - "MessagePluginInstallDisclaimer": "Kopienas dalībnieku izveidoti spraudņi ir lielisks veids, kā uzlabot savu pieredzi, izmantojot papildu funkcijas un priekšrocības. Pirms instalēšanas ņemiet vērā to iespējamo ietekmi uz jūsu serveri, piemēram, ilgāku bibliotēkas skenēšanu, papildu fona apstrādi un sistēmas stabilitātes samazināšanos.", - "MessagePasswordResetForUsers": "Šādu lietotāju paroles ir atiestatītas. Tagad viņi var pierakstīties, izmantojot Easy PIN kodus, kas tika izmantoti atiestatīšanai.", + "MessagePluginInstallDisclaimer": "Kopienas dalībnieku izveidoti spraudņi ir lielisks veids, kā uzlabot savu pieredzi, izmantojot papildu funkcijas un priekšrocības, bet šādi spraudņi var saturēt bīstamu vai nestabilu kodu. Pirms instalēšanas ņemiet vērā to iespējamo ietekmi uz jūsu serveri, piemēram, ilgāku bibliotēkas skenēšanu, papildu fona apstrādi un sistēmas stabilitātes samazināšanos.", + "MessagePasswordResetForUsers": "Šo lietotāju paroles ir atiestatītas. Tagad viņi var pierakstīties, izmantojot PIN kodus, kas tika izmantoti atiestatīšanai.", "MessagePluginInstalled": "Spraudnis tika veiksmīgi instalēts. Lai izmaiņas stātos spēkā, serveris ir jārestartē.", "HeaderRecordingMetadataSaving": "Metadatu Ierakstīšana", "AllowCollectionManagement": "Ļaut konkrētajam lietotājam pārvaldīt kolekciju", @@ -1484,10 +1484,10 @@ "AllowSegmentDeletion": "Dzēst segmentus", "AllowSegmentDeletionHelp": "Dzēst vecus segmentus pēc to nosūtīšanas klientam. Šis lauj neglabāt visu pārkodēto failu diskā. Izslēdziet šo tikai ja jums ir problēmas ar atskanosanu.", "GoHome": "Doties mājās", - "HeaderEpisodesStatus": "Epizožu status", + "HeaderEpisodesStatus": "Epizožu statuss", "LabelDate": "Datums", "LabelBackdropScreensaverInterval": "Fona ekrānsaudzētāja intervāls", - "LabelBackdropScreensaverIntervalHelp": "Laiks sekundēs starp dažādiem foniem kad tiek izmantots fona ekrāns audzētājs.", + "LabelBackdropScreensaverIntervalHelp": "Laiks sekundēs starp dažādiem foniem, ja tiek izmantots fona ekrānsaudzētājs.", "GridView": "Tabulas skats", "BackdropScreensaver": "Fona ekrānsaudzētājs", "LabelLevel": "Līmenis", @@ -1495,6 +1495,27 @@ "LabelDeveloper": "Izstrādātājs", "LabelThrottleDelaySeconds": "Bremzēt pēc", "LabelThrottleDelaySecondsHelp": "Laiks sekundēs pēc kura pārkodētājs tiks pēc kura pārkodētājs tiks bremzēts. Šim jābūt pietiekami lielam lai klients saglabātu veselīgu buferi. Šis strādā tikai ja bremzēšana ir ieslēgta.", - "LabelSegmentKeepSeconds": "Cik ilgi paturēt segmentus.", - "HeaderGuestCast": "Viesu zvaigznes" + "LabelSegmentKeepSeconds": "Cik ilgi paturēt segmentus", + "HeaderGuestCast": "Vieszvaigznes", + "RecommendationStarring": "Lomās {0}", + "LogLevel.Trace": "Izsekot", + "ShowTitle": "Parādīt virsrakstu", + "ShowYear": "Parādīt gadu", + "RecordingScheduled": "Ieraksts ieplānots.", + "LabelMediaDetails": "Multivides detaļas", + "LogLevel.Information": "Informācija", + "RefreshMetadata": "Atsvaidzināt metadatus", + "RefreshDialogHelp": "Metadati tiek atsvaidzināti balstoties uz iestatījumiem un interneta pakalpojumiem, kas ir iespējoti info panelī.", + "LabelSyncPlayNoGroups": "Neviena grupa nav pieejama", + "ListView": "Saraksta skats", + "LogLevel.Debug": "Atkļūdošana", + "LogLevel.Error": "Brīdinājums", + "LogoScreensaver": "Logo ekrānsaudzētājs", + "RecommendationDirectedBy": "Režisēja {0}", + "MessageRepositoryInstallDisclaimer": "BRĪDINĀJUMS: Trešās puses spraudņa instalācija ir riskanta. Tas var saturēt nestabilu vai bīstamu kodu un var mainīties jebkurā brīdī. Instalējiet tikai spraudņus no uzticamiem avotiem.", + "PleaseConfirmRepositoryInstallation": "Lūdzu spiediet OK lai apstiprināku ka esat izlasījuši brīdinājumu un vēlaties turpināt spraudņu krātuves instalāciju.", + "LogLevel.None": "Nekas", + "RefreshQueued": "Atsvaidzināšana ieplānota.", + "LabelSystem": "Sistēma", + "LogLevel.Critical": "Kritisks" } From e7c96a250ef0b09f04f7d91095d81a1ccbc88f43 Mon Sep 17 00:00:00 2001 From: tuyuribr Date: Sat, 23 Sep 2023 17:06:26 +0000 Subject: [PATCH 046/142] Translated using Weblate (Portuguese) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/pt/ --- src/strings/pt.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/strings/pt.json b/src/strings/pt.json index 4356e6ad8..8669e7502 100644 --- a/src/strings/pt.json +++ b/src/strings/pt.json @@ -1421,5 +1421,11 @@ "LabelParallelImageEncodingLimit": "Limite de codificação de imagens em paralelo", "LabelEnableLUFSScan": "Habilitar busca LUFS", "LabelEnableLUFSScanHelp": "Habilitar busca LUFS para música (Isto necessitará de mais tempo e recursos).", - "LabelParallelImageEncodingLimitHelp": "Quantidade máxima de imagens codificadas que são permitidas rodar em paralelo. Ajustar este valor para 0 ira selecionar um limite baseado nas especificaçõrs do seu sistema." + "LabelParallelImageEncodingLimitHelp": "Quantidade máxima de imagens codificadas que são permitidas rodar em paralelo. Ajustar este valor para 0 ira selecionar um limite baseado nas especificaçõrs do seu sistema.", + "GoHome": "Ir ao início", + "GridView": "Visão em grade", + "HeaderConfirmRepositoryInstallation": "Confirme a instalação do repositório de plugin", + "AllowSegmentDeletion": "Deletar segmentos", + "AllowSegmentDeletionHelp": "Exclua segmentos antigos após terem sido enviados ao cliente. Isso evita ter que armazenar todo o arquivo transcodificado no disco. Funcionará apenas com throttling habilitado. Desligue esta opção se tiver problemas de reprodução.", + "LabelSegmentKeepSeconds": "Tempo em segundos para manter os segmentos" } From 25dad6a390396274ee041699a2114172772625b5 Mon Sep 17 00:00:00 2001 From: da lo Date: Sat, 23 Sep 2023 21:13:57 +0000 Subject: [PATCH 047/142] Translated using Weblate (German) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/de/ --- src/strings/de.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/strings/de.json b/src/strings/de.json index 6f4d18534..c136697d8 100644 --- a/src/strings/de.json +++ b/src/strings/de.json @@ -1761,5 +1761,13 @@ "LabelThrottleDelaySeconds": "Limitieren nach", "LabelThrottleDelaySecondsHelp": "Zeit, in Sekunden, nach der die Transkodierung limitiert wird. Muss groß genug sein um dem Client eine problemlose Wiedergabe zu ermöglichen. Funktioniert nur wenn \"Transkodierung drosseln\" aktiviert ist.", "LabelSegmentKeepSeconds": "Zeit um Segmente zu behalten", - "LabelSegmentKeepSecondsHelp": "Zeit, in Sekunden, in der Segmente nicht überschrieben werden dürfen. Muss größer sein als \"Limitieren nach\". Funktioniert nur wenn \"Segmente löschen\" aktiviert ist." + "LabelSegmentKeepSecondsHelp": "Zeit, in Sekunden, in der Segmente nicht überschrieben werden dürfen. Muss größer sein als \"Limitieren nach\". Funktioniert nur wenn \"Segmente löschen\" aktiviert ist.", + "LogoScreensaver": "Logo Bildschirmschoner", + "UnknownError": "Ein unbekannter Fehler trat auf.", + "GridView": "Kachelansicht", + "ListView": "Listenansicht", + "GoHome": "Startseite", + "AiTranslated": "AI übersetzt", + "MachineTranslated": "maschinenübersetzt", + "AllowAv1Encoding": "Encodierung ins AV1 Format erlauben" } From b078f25edab95b3b9da01fa9c7bab319d5de1438 Mon Sep 17 00:00:00 2001 From: Kityn Date: Sun, 24 Sep 2023 06:18:20 +0000 Subject: [PATCH 048/142] Translated using Weblate (Polish) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/pl/ --- src/strings/pl.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings/pl.json b/src/strings/pl.json index 641afdea4..33757d04a 100644 --- a/src/strings/pl.json +++ b/src/strings/pl.json @@ -1105,7 +1105,7 @@ "ThemeSongs": "Motywy muzyczne", "ThemeVideos": "Motywy wideo", "TheseSettingsAffectSubtitlesOnThisDevice": "Te ustawienia dotyczą napisów na tym urządzeniu", - "ThisWizardWillGuideYou": "Niniejszy kreator pomoże Ci przejść przez proces instalacji. Najpierw, wybierz preferowany przez siebie język.", + "ThisWizardWillGuideYou": "Niniejszy kreator pomoże Ci przejść przez proces instalacji. Najpierw wybierz preferowany przez siebie język.", "Thumb": "Miniatura", "Thursday": "Czwartek", "TitleHardwareAcceleration": "Akceleracja sprzętowa", From f94ef6c005c5855047104a5fabad80654281a4b4 Mon Sep 17 00:00:00 2001 From: Kityn Date: Sun, 24 Sep 2023 06:51:23 +0000 Subject: [PATCH 049/142] Translated using Weblate (Polish) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/pl/ --- src/strings/pl.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings/pl.json b/src/strings/pl.json index 33757d04a..d7705b96e 100644 --- a/src/strings/pl.json +++ b/src/strings/pl.json @@ -1516,7 +1516,7 @@ "MessageSent": "Wiadomość została wysłana.", "TextSent": "Tekst został wysłany.", "LabelSlowResponseTime": "Czas w milisekundach po którym odpowiedź uznana będzie za powolną", - "LabelSlowResponseEnabled": "Zaloguj ostrzeżenie gdy serwer wolno odpowiada", + "LabelSlowResponseEnabled": "Zarejestruj ostrzeżenie, gdy serwer wolno odpowiada", "UseEpisodeImagesInNextUpHelp": "Sekcje 'Do obejrzenia' i 'Kontynuuj odtwarzanie' pokażą grafikę epizodu jako podgląd zamiast głównej miniatury serii.", "UseEpisodeImagesInNextUp": "Użyj grafik epizodów w sekcjach 'Do obejrzenia' i 'Kontynuuj odtwarzanie'", "AudioBitDepthNotSupported": "Głębia bitowa dźwięku nie jest obsługiwana", From 236ed28ff6ccbdf70ab6b3627a8f9b1ad468fa6d Mon Sep 17 00:00:00 2001 From: Vitabytes Date: Sun, 24 Sep 2023 10:16:06 +0000 Subject: [PATCH 050/142] Translated using Weblate (Greek) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/el/ --- src/strings/el.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/strings/el.json b/src/strings/el.json index 93c12cebf..152e3147b 100644 --- a/src/strings/el.json +++ b/src/strings/el.json @@ -1696,5 +1696,7 @@ "LabelDummyChapterCount": "Όριο", "LabelDummyChapterCountHelp": "Ο μέγιστος αριθμός εικόνων κεφαλαίου που θα εξαχθεί για κάθε αρχείο πολυμέσων.", "LabelChapterImageResolution": "Ανάλυση", - "LabelChapterImageResolutionHelp": "Η ανάλυση των εξαγόμενων εικόνων κεφαλαίου." + "LabelChapterImageResolutionHelp": "Η ανάλυση των εξαγόμενων εικόνων κεφαλαίου.", + "AllowCollectionManagement": "Επίτρεψε στον χρήστη να διαχειρίζεται συλλογές", + "AllowSegmentDeletion": "Διαγραφή τμημάτων" } From 13691bb8a4b7899c240b8b0209a41a825453807a Mon Sep 17 00:00:00 2001 From: Bas Date: Sun, 24 Sep 2023 12:02:34 +0000 Subject: [PATCH 051/142] Translated using Weblate (Dutch) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/nl/ --- src/strings/nl.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/strings/nl.json b/src/strings/nl.json index f403e75bc..0ae9a0642 100644 --- a/src/strings/nl.json +++ b/src/strings/nl.json @@ -1735,7 +1735,7 @@ "NotificationsMovedMessage": "De meldingsfunctionaliteit is verplaatst naar de Webhook-plug-in.", "EnableAudioNormalizationHelp": "Geluidsnormalisatie past een constante versterking toe om het gemiddelde op een gewenst niveau (-18dB) te houden.", "LabelEnableLUFSScan": "LUFS-scan inschakelen", - "LabelEnableLUFSScanHelp": "LUFS-scan voor muziek inschakelen. Dit duurt langer en is systeemintensief.", + "LabelEnableLUFSScanHelp": "Cliënten kunnen audio normaliseren zodat verschillende nummers even luid zijn. Dit is systeemintensief en bibliotheekscans zullen langer duren.", "PasswordRequiredForAdmin": "Voor beheerdersaccounts is een wachtwoord vereist.", "LabelSyncPlayNoGroups": "Geen groepen beschikbaar", "HeaderConfirmRepositoryInstallation": "Installatie plug-in-repository bevestigen", @@ -1772,5 +1772,7 @@ "HeaderGuestCast": "Gastrollen", "HearingImpairedShort": "Voor slechthorenden", "GoHome": "Startpagina", - "UnknownError": "Er is een onbekende fout opgetreden." + "UnknownError": "Er is een onbekende fout opgetreden.", + "BackdropScreensaver": "Schermbeveiliging met achtergronden", + "LogoScreensaver": "Schermbeveiliging met logo" } From e23be51e4f631eb13442cdac7fa34627ba90a309 Mon Sep 17 00:00:00 2001 From: grafixeyehero Date: Sun, 17 Sep 2023 23:27:52 +0300 Subject: [PATCH 052/142] Add Pagination --- .../components/library/Pagination.tsx | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 src/apps/experimental/components/library/Pagination.tsx diff --git a/src/apps/experimental/components/library/Pagination.tsx b/src/apps/experimental/components/library/Pagination.tsx new file mode 100644 index 000000000..3d1026254 --- /dev/null +++ b/src/apps/experimental/components/library/Pagination.tsx @@ -0,0 +1,91 @@ +import React, { FC, useCallback } from 'react'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; +import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; +import Box from '@mui/material/Box'; +import ButtonGroup from '@mui/material/ButtonGroup'; +import IconButton from '@mui/material/IconButton'; + +import globalize from 'scripts/globalize'; +import * as userSettings from 'scripts/settings/userSettings'; +import { LibraryViewSettings } from 'types/library'; + +interface PaginationProps { + libraryViewSettings: LibraryViewSettings; + setLibraryViewSettings: React.Dispatch>; + totalRecordCount: number; +} + +const Pagination: FC = ({ + libraryViewSettings, + setLibraryViewSettings, + totalRecordCount +}) => { + const limit = userSettings.libraryPageSize(undefined); + const startIndex = libraryViewSettings.StartIndex || 0; + const recordsStart = totalRecordCount ? startIndex + 1 : 0; + const recordsEnd = limit ? + Math.min(startIndex + limit, totalRecordCount) : + totalRecordCount; + const showControls = limit > 0 && limit < totalRecordCount; + + const onNextPageClick = useCallback(() => { + if (limit > 0) { + const newIndex = startIndex + limit; + setLibraryViewSettings((prevState) => ({ + ...prevState, + StartIndex: newIndex + })); + } + }, [limit, setLibraryViewSettings, startIndex]); + + const onPreviousPageClick = useCallback(() => { + if (limit > 0) { + const newIndex = Math.max(0, startIndex - limit); + setLibraryViewSettings((prevState) => ({ + ...prevState, + StartIndex: newIndex + })); + } + }, [limit, setLibraryViewSettings, startIndex]); + + return ( + + + + {globalize.translate( + 'ListPaging', + recordsStart, + recordsEnd, + totalRecordCount + )} + + {showControls && ( + + + + + + = totalRecordCount } + onClick={onNextPageClick} + > + + + + )} + + + ); +}; + +export default Pagination; From 621e3af85dd54b7f1402e0b14bba477846c4c53a Mon Sep 17 00:00:00 2001 From: trailfullideal Date: Sun, 24 Sep 2023 15:40:12 +0000 Subject: [PATCH 053/142] Translated using Weblate (Chinese (Simplified)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/zh_Hans/ --- src/strings/zh-cn.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/strings/zh-cn.json b/src/strings/zh-cn.json index f6144f291..8a0f36db6 100644 --- a/src/strings/zh-cn.json +++ b/src/strings/zh-cn.json @@ -1733,7 +1733,7 @@ "EnableAudioNormalizationHelp": "音频标准化将添加一个恒定的增益,以保持平均音量在所需的级别(-18dB)。", "EnableAudioNormalization": "音频标准化", "LabelEnableLUFSScan": "启用 LUFS 扫描", - "LabelEnableLUFSScanHelp": "启用音乐的LUFS扫描(这将需要更长时间和更多资源)。", + "LabelEnableLUFSScanHelp": "客户端可以将音频播放进行归一化,以实现曲目之间的音量均衡。这将导致库扫描时间更长,并消耗更多的资源。", "GetThePlugin": "获取插件", "Notifications": "通知", "NotificationsMovedMessage": "通知功能已经转移到Webhook插件中。", @@ -1772,5 +1772,6 @@ "MachineTranslated": "机器翻译", "ForeignPartsOnly": "仅限强制开启/外语部分", "HearingImpairedShort": "听障/聋哑人士字幕", - "UnknownError": "发生未知错误。" + "UnknownError": "发生未知错误。", + "GoHome": "回家" } From 9279b14499736dcc25424c8c71ecb862803b539e Mon Sep 17 00:00:00 2001 From: trailfullideal Date: Sun, 24 Sep 2023 15:15:21 +0000 Subject: [PATCH 054/142] Translated using Weblate (Zulu) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/zu/ --- src/strings/zu.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/strings/zu.json b/src/strings/zu.json index 23e286b8f..f388785dc 100644 --- a/src/strings/zu.json +++ b/src/strings/zu.json @@ -113,5 +113,7 @@ "Aired": "Kusakazwa", "Add": "Yengeza", "Actor": "Umlingisi", - "Absolute": "Impela" + "Absolute": "Impela", + "HeaderAlbumArtists": "Abasethi wenkulumo", + "HeaderContinueWatching": "Buyela Ukubona" } From 286bbcbebce854169734197e638518373bb181f8 Mon Sep 17 00:00:00 2001 From: trailfullideal Date: Sun, 24 Sep 2023 15:01:20 +0000 Subject: [PATCH 055/142] Translated using Weblate (Assamese) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/as/ --- src/strings/as.json | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/strings/as.json b/src/strings/as.json index 8a1f2b069..020baefba 100644 --- a/src/strings/as.json +++ b/src/strings/as.json @@ -1,4 +1,16 @@ { "Actor": "অভিনেতা", - "Absolute": "নিৰংকুশ" + "Absolute": "নিৰংকুশ", + "HeaderAlbumArtists": "অ্যালবাম শিল্পী", + "Albums": "এলবাম", + "Books": "পুস্তক", + "Channels": "চেনেলস", + "Movies": "চলচ্চিত্ৰ", + "Artists": "শিল্পী", + "Collections": "সংগ্রহ", + "Default": "ডিফল্ট", + "Favorites": "পছন্দসই", + "Folders": "ফোল্ডাৰ", + "Genres": "শ্রেণী", + "HeaderContinueWatching": "দেখা চালিয়ে যান" } From 5bb83fd185ba4c36d0a9d046332b647d7ae26be0 Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Mon, 18 Sep 2023 02:14:51 +0300 Subject: [PATCH 056/142] Fix homesections in legacy browser (cherry picked from commit d3e3bc7282774144d71ef8c0ec6efb6f940ed929) --- src/components/homesections/homesections.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/components/homesections/homesections.js b/src/components/homesections/homesections.js index 1b26f8624..212d976a8 100644 --- a/src/components/homesections/homesections.js +++ b/src/components/homesections/homesections.js @@ -69,11 +69,14 @@ export function loadSections(elem, apiClient, user, userSettings) { promises.push(loadSection(elem, apiClient, user, userSettings, userViews, sections, i)); } - return Promise.all(promises).then(function () { - return resume(elem, { - refresh: true + return Promise.all(promises) + // Timeout for polyfilled CustomElements (webOS 1.2) + .then(() => new Promise((resolve) => setTimeout(resolve, 0))) + .then(() => { + return resume(elem, { + refresh: true + }); }); - }); } else { let noLibDescription; if (user.Policy?.IsAdministrator) { From b7be3af21c25e8d50b93abf2028afda64f2a58f8 Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Wed, 23 Aug 2023 23:58:07 +0300 Subject: [PATCH 057/142] Fix slider step Use the value of the `step` attribute if no keyboard steps are specified. (cherry picked from commit 7d27596d6b7545a4d48cc03e26b4f32a3729c591) --- src/elements/emby-slider/emby-slider.js | 29 +++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/elements/emby-slider/emby-slider.js b/src/elements/emby-slider/emby-slider.js index afd949092..5e44fb03c 100644 --- a/src/elements/emby-slider/emby-slider.js +++ b/src/elements/emby-slider/emby-slider.js @@ -19,6 +19,27 @@ if (Object.getOwnPropertyDescriptor && Object.defineProperty) { } } +/** + * Returns normalized slider step. + * + * @param {HTMLInputElement} range slider itself + * @param {number|undefined} step step + * @returns {number} normalized slider step. + */ +function normalizeSliderStep(range, step) { + if (step > 0) { + return step; + } + + step = parseFloat(range.step); + + if (step > 0) { + return step; + } + + return 1; +} + /** * Returns slider fraction corresponding to client position. * @@ -37,7 +58,7 @@ function mapClientToFraction(range, clientX) { // Snap to step const valueRange = range.max - range.min; if (range.step !== 'any' && valueRange !== 0) { - const step = (range.step || 1) / valueRange; + const step = normalizeSliderStep(range) / valueRange; fraction = Math.round(fraction / step) * step; } @@ -56,7 +77,7 @@ function mapFractionToValue(range, fraction) { // Snap to step if (range.step !== 'any') { - const step = range.step || 1; + const step = normalizeSliderStep(range); value = Math.round(value / step) * step; } @@ -455,13 +476,13 @@ function onKeyDown(e) { switch (keyboardnavigation.getKeyName(e)) { case 'ArrowLeft': case 'Left': - stepKeyboard(this, -this.keyboardStepDown || -1); + stepKeyboard(this, -normalizeSliderStep(this, this.keyboardStepDown)); e.preventDefault(); e.stopPropagation(); break; case 'ArrowRight': case 'Right': - stepKeyboard(this, this.keyboardStepUp || 1); + stepKeyboard(this, normalizeSliderStep(this, this.keyboardStepUp)); e.preventDefault(); e.stopPropagation(); break; From 2946ed4e35d5d2c1333731f0cf84466eb16f8bac Mon Sep 17 00:00:00 2001 From: Nicolas Viviani Date: Mon, 25 Sep 2023 19:45:13 +0000 Subject: [PATCH 058/142] Translated using Weblate (French) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fr/ --- src/strings/fr.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/strings/fr.json b/src/strings/fr.json index e97a60fb0..00408d1e0 100644 --- a/src/strings/fr.json +++ b/src/strings/fr.json @@ -1409,7 +1409,7 @@ "QuickConnectDescription": "Pour utiliser la connexion rapide, appuyez sur le bouton 'Connexion rapide' de l'appareil à connecter et entrez le code affiché ci-dessous.", "QuickConnectDeactivated": "La connexion rapide a été désactivée avant que la requête ne puisse être approuvée", "QuickConnectAuthorizeFail": "Code de connexion rapide inconnu", - "QuickConnectAuthorizeSuccess": "Vous avez authentifié votre appareil avec succès !", + "QuickConnectAuthorizeSuccess": "Vous avez authentifié votre appareil avec succès !", "QuickConnectAuthorizeCode": "Saisir le code {0} pour se connecter", "QuickConnectActivationSuccessful": "Activé avec succès", "QuickConnect": "Connexion rapide", @@ -1749,7 +1749,7 @@ "LabelMediaDetails": "Détails du média", "LabelSystem": "Système", "LogLevel.Trace": "Trace", - "LogLevel.Debug": "Debug", + "LogLevel.Debug": "Débogage", "LogLevel.Information": "Information", "LogLevel.Warning": "Avertissement", "LogLevel.Error": "Erreur", @@ -1773,7 +1773,7 @@ "UnknownError": "Une erreur inconnue s’est produite.", "MachineTranslated": "Traduction automatique", "AiTranslated": "Traduction par IA", - "ForeignPartsOnly": "Parties forcées/étrangères uniquement", + "ForeignPartsOnly": "Parties forcées/en langues étrangères uniquement", "HearingImpairedShort": "HI/SDH", - "HeaderGuestCast": "Stars invitées" + "HeaderGuestCast": "Invités vedettes" } From e46330c70d48c42f9c913a8f975784bcc01ed16b Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Tue, 26 Sep 2023 01:25:42 -0400 Subject: [PATCH 059/142] Use react query to fetch syncplay groups --- .../AppToolbar/menus/SyncPlayMenu.tsx | 26 +++---------------- src/hooks/useSyncPlayGroups.ts | 24 +++++++++++++++++ 2 files changed, 28 insertions(+), 22 deletions(-) create mode 100644 src/hooks/useSyncPlayGroups.ts diff --git a/src/apps/experimental/components/AppToolbar/menus/SyncPlayMenu.tsx b/src/apps/experimental/components/AppToolbar/menus/SyncPlayMenu.tsx index 1163a9252..6e08b3fea 100644 --- a/src/apps/experimental/components/AppToolbar/menus/SyncPlayMenu.tsx +++ b/src/apps/experimental/components/AppToolbar/menus/SyncPlayMenu.tsx @@ -19,6 +19,7 @@ import React, { FC, useCallback, useEffect, useState } from 'react'; import { pluginManager } from 'components/pluginManager'; import { useApi } from 'hooks/useApi'; +import { useSyncPlayGroups } from 'hooks/useSyncPlayGroups'; import globalize from 'scripts/globalize'; import { PluginType } from 'types/plugin'; import Events from 'utils/events'; @@ -47,7 +48,6 @@ const SyncPlayMenu: FC = ({ }) => { const [ syncPlay, setSyncPlay ] = useState(); const { __legacyApiClient__, api, user } = useApi(); - const [ groups, setGroups ] = useState([]); const [ currentGroup, setCurrentGroup ] = useState(); const isSyncPlayEnabled = Boolean(currentGroup); @@ -55,25 +55,7 @@ const SyncPlayMenu: FC = ({ setSyncPlay(pluginManager.firstOfType(PluginType.SyncPlay)?.instance); }, []); - useEffect(() => { - let isMounted = true; - - const fetchGroups = async () => { - if (api) { - const response = await getSyncPlayApi(api).syncPlayGetGroups(); - if (isMounted) setGroups(response.data); - } - }; - - fetchGroups() - .catch(err => { - console.error('[SyncPlayMenu] unable to fetch SyncPlay groups', err); - }); - - return () => { - isMounted = false; - }; - }, [ api ]); + const { data: groups } = useSyncPlayGroups(); const onGroupAddClick = useCallback(() => { if (api && user) { @@ -231,7 +213,7 @@ const SyncPlayMenu: FC = ({ /> ); - } else if (groups.length === 0 && user?.Policy?.SyncPlayAccess !== SyncPlayUserAccessType.CreateAndJoinGroups) { + } else if (!groups?.length && user?.Policy?.SyncPlayAccess !== SyncPlayUserAccessType.CreateAndJoinGroups) { menuItems.push( @@ -241,7 +223,7 @@ const SyncPlayMenu: FC = ({ ); } else { - if (groups.length > 0) { + if (groups && groups.length > 0) { groups.forEach(group => { menuItems.push( { + const { api } = currentApi; + if (!api) throw new Error('No API instance available'); + + const response = await getSyncPlayApi(api) + .syncPlayGetGroups(options); + return response.data; +}; + +export const useSyncPlayGroups = () => { + const currentApi = useApi(); + return useQuery({ + queryKey: [ 'SyncPlay', 'Groups' ], + queryFn: ({ signal }) => fetchSyncPlayGroups(currentApi, { signal }) + }); +}; From 9403ada794f78d48e7e75cdc5ce60e1959d70982 Mon Sep 17 00:00:00 2001 From: millallo Date: Tue, 26 Sep 2023 08:54:46 +0000 Subject: [PATCH 060/142] Translated using Weblate (Italian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/it/ --- src/strings/it.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/strings/it.json b/src/strings/it.json index 076aba22b..df915cbac 100644 --- a/src/strings/it.json +++ b/src/strings/it.json @@ -1390,7 +1390,7 @@ "QuickConnectInvalidCode": "Codice Quick Connect non valido", "QuickConnectDescription": "Per registrarsi usando Quick Connect, seleziona il pulsante Quick Connect nel dispositivo che stai usando per accedere ed inserisci il codice sottostante.", "QuickConnectDeactivated": "Quick Connect è stato disattivato prima che la richiesta di login venisse approvata", - "QuickConnectAuthorizeSuccess": "Richiesta autorizzata", + "QuickConnectAuthorizeSuccess": "Device autenticato con successo!", "QuickConnectActivationSuccessful": "Attivato con successo", "QuickConnect": "Connessione Rapida", "LabelQuickConnectCode": "Codice Quick Connect", @@ -1741,7 +1741,7 @@ "HeaderConfirmRepositoryInstallation": "Conferma dell'installazione del repository dei plugin", "LabelDeveloper": "Sviluppatore", "LabelEnableLUFSScan": "Abilita LUFS scan", - "LabelEnableLUFSScanHelp": "Abilita LUFS scan per la musica (Impiegherà più tempo e più risorse).", + "LabelEnableLUFSScanHelp": "I client possono normalizzare la riproduzione dell'audio per avere un uguale volume tra le tracce. Ciò impiegherà più tempo durante lo scan delle librerie e più risorse.", "MessageRepositoryInstallDisclaimer": "ATTENZIONE: L'installazione di repository di plugin di terze parti può portare dei rischi. Può contenere codice instabile o malevolo e può cambiare in qualsiasi momento. Installa solo plugin degli autori di cui ti fidi.", "Unknown": "Sconosciuto", "LabelDate": "Data", @@ -1761,5 +1761,6 @@ "LabelThrottleDelaySeconds": "Throttle dopo", "LabelThrottleDelaySecondsHelp": "Tempo in secondi dopo cui il transcodificatore sarà messo in throttle. Deve essere sufficientemente grande perché il client mantenga un buon buffer. Funziona solo se il throttling è abilitato.", "LabelSegmentKeepSeconds": "Il tempo per cui tenere i segmenti", - "LabelSegmentKeepSecondsHelp": "Tempo in secondi per cui i segmenti saranno tenuti prima di essere sovrascritti. Deve essere più grande di \"Throttle dopo\". Funziona solo se l'eliminazione dei segmenti è abilitata." + "LabelSegmentKeepSecondsHelp": "Tempo in secondi per cui i segmenti saranno tenuti prima di essere sovrascritti. Deve essere più grande di \"Throttle dopo\". Funziona solo se l'eliminazione dei segmenti è abilitata.", + "AllowAv1Encoding": "Permetti la codifica nel formato AV1" } From 9e8c7d788a79414c44f9761308c93f453c3b595b Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Mon, 25 Sep 2023 23:37:45 +0300 Subject: [PATCH 061/142] Fix slider rounding --- src/elements/emby-slider/emby-slider.js | 13 ++++++++++++- src/utils/number.ts | 13 +++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/elements/emby-slider/emby-slider.js b/src/elements/emby-slider/emby-slider.js index 5e44fb03c..d08b87993 100644 --- a/src/elements/emby-slider/emby-slider.js +++ b/src/elements/emby-slider/emby-slider.js @@ -6,6 +6,7 @@ import './emby-slider.scss'; import 'webcomponents.js/webcomponents-lite'; import '../emby-input/emby-input'; import globalize from '../../scripts/globalize'; +import { decimalCount } from '../../utils/number'; const EmbySliderPrototype = Object.create(HTMLInputElement.prototype); @@ -75,13 +76,23 @@ function mapClientToFraction(range, clientX) { function mapFractionToValue(range, fraction) { let value = (range.max - range.min) * fraction; + let decimals = null; + // Snap to step if (range.step !== 'any') { const step = normalizeSliderStep(range); + decimals = decimalCount(step); value = Math.round(value / step) * step; } - value += parseFloat(range.min); + const min = parseFloat(range.min); + + value += min; + + if (decimals != null) { + decimals = Math.max(decimals, decimalCount(min)); + value = parseFloat(value.toFixed(decimals)); + } return Math.min(Math.max(value, range.min), range.max); } diff --git a/src/utils/number.ts b/src/utils/number.ts index 16797d7d5..553280c1f 100644 --- a/src/utils/number.ts +++ b/src/utils/number.ts @@ -32,3 +32,16 @@ export function toPercent(value: number | null | undefined, locale: string): str return `${Math.round(value * 100)}%`; } + +/** + * Gets decimal count of a Number. + * @param {number} value Number. + * @returns {number} Decimal count of a Number. + */ +export function decimalCount(value: number): number { + if (Number.isInteger(value)) return 0; + + const arr = value.toString().split('.'); + + return arr[1].length; +} From 111958e2a5da5fdf2bcc500819de05ccc793ecc3 Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Thu, 24 Aug 2023 00:48:47 +0300 Subject: [PATCH 062/142] Pass slider value to bubble text generators --- src/elements/emby-slider/emby-slider.js | 26 ++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/elements/emby-slider/emby-slider.js b/src/elements/emby-slider/emby-slider.js index d08b87993..cf804849d 100644 --- a/src/elements/emby-slider/emby-slider.js +++ b/src/elements/emby-slider/emby-slider.js @@ -146,12 +146,12 @@ function updateValues(isValueSet) { }); } -function updateBubble(range, value, bubble) { +function updateBubble(range, percent, value, bubble) { requestAnimationFrame(function () { const bubbleTrackRect = range.sliderBubbleTrack.getBoundingClientRect(); const bubbleRect = bubble.getBoundingClientRect(); - let bubblePos = bubbleTrackRect.width * value / 100; + let bubblePos = bubbleTrackRect.width * percent / 100; if (globalize.getIsElementRTL(range)) { bubblePos = bubbleTrackRect.width - bubblePos; } @@ -159,18 +159,20 @@ function updateBubble(range, value, bubble) { bubble.style.left = bubblePos + 'px'; + let html; + if (range.getBubbleHtml) { - value = range.getBubbleHtml(value); + html = range.getBubbleHtml(percent, value); } else { if (range.getBubbleText) { - value = range.getBubbleText(value); + html = range.getBubbleText(percent, value); } else { - value = mapFractionToValue(range, value / 100).toLocaleString(); + html = value.toLocaleString(); } - value = '

' + value + '

'; + html = '

' + html + '

'; } - bubble.innerHTML = value; + bubble.innerHTML = html; }); } @@ -306,8 +308,8 @@ EmbySliderPrototype.attachedCallback = function () { updateValues.call(this); } - const bubbleValue = mapValueToFraction(this, this.value) * 100; - updateBubble(this, bubbleValue, sliderBubble); + const percent = mapValueToFraction(this, this.value) * 100; + updateBubble(this, percent, parseFloat(this.value), sliderBubble); if (hasHideBubbleClass) { sliderBubble.classList.remove('hide'); @@ -333,9 +335,11 @@ EmbySliderPrototype.attachedCallback = function () { /* eslint-disable-next-line compat/compat */ dom.addEventListener(this, (window.PointerEvent ? 'pointermove' : 'mousemove'), function (e) { if (!this.dragging) { - const bubbleValue = mapClientToFraction(this, e.clientX) * 100; + const fraction = mapClientToFraction(this, e.clientX); + const percent = fraction * 100; + const value = mapFractionToValue(this, fraction); - updateBubble(this, bubbleValue, sliderBubble); + updateBubble(this, percent, value, sliderBubble); if (hasHideBubbleClass) { sliderBubble.classList.remove('hide'); From 6063ba6db57acb02bb6f88fd644681b6508e10cf Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Tue, 26 Sep 2023 00:04:31 +0300 Subject: [PATCH 063/142] Use slider to round and limit offset --- src/components/subtitlesync/subtitlesync.js | 51 +++++-------------- .../subtitlesync/subtitlesync.template.html | 2 +- 2 files changed, 15 insertions(+), 38 deletions(-) diff --git a/src/components/subtitlesync/subtitlesync.js b/src/components/subtitlesync/subtitlesync.js index ba8984486..57ae63cb8 100644 --- a/src/components/subtitlesync/subtitlesync.js +++ b/src/components/subtitlesync/subtitlesync.js @@ -46,15 +46,8 @@ function init(instance) { if (inputOffset) { inputOffset = inputOffset[0]; inputOffset = parseFloat(inputOffset); - inputOffset = Math.min(30, Math.max(-30, inputOffset)); - // replace current text by considered offset - this.textContent = inputOffset + 's'; - // set new offset - playbackManager.setSubtitleOffset(inputOffset, player); - // synchronize with slider value - subtitleSyncSlider.updateOffset( - getSliderValueFromOffset(inputOffset)); + subtitleSyncSlider.updateOffset(inputOffset); } else { this.textContent = (playbackManager.getPlayerSubtitleOffset(player) || 0) + 's'; } @@ -79,23 +72,26 @@ function init(instance) { } }; + function updateSubtitleOffset() { + const value = parseFloat(subtitleSyncSlider.value); + // set new offset + playbackManager.setSubtitleOffset(value, player); + // synchronize with textField value + subtitleSyncTextField.updateOffset(value); + } + subtitleSyncSlider.updateOffset = function (sliderValue) { // default value is 0s = 0ms this.value = sliderValue === undefined ? 0 : sliderValue; + + updateSubtitleOffset(); }; - subtitleSyncSlider.addEventListener('change', function () { - // set new offset - playbackManager.setSubtitleOffset(getOffsetFromSliderValue(this.value), player); - // synchronize with textField value - subtitleSyncTextField.updateOffset( - getOffsetFromSliderValue(this.value)); - }); + subtitleSyncSlider.addEventListener('change', () => updateSubtitleOffset()); - subtitleSyncSlider.getBubbleHtml = function (value) { - const newOffset = getOffsetFromPercentage(value); + subtitleSyncSlider.getBubbleHtml = function (_, value) { return '

' - + (newOffset > 0 ? '+' : '') + parseFloat(newOffset) + 's' + + (value > 0 ? '+' : '') + parseFloat(value) + 's' + '

'; }; @@ -107,25 +103,6 @@ function init(instance) { instance.element = parent; } -function getOffsetFromPercentage(value) { - // convert percentage to fraction - let offset = (value - 50) / 50; - // multiply by offset min/max range value (-x to +x) : - offset *= 30; - return offset.toFixed(1); -} - -function getOffsetFromSliderValue(value) { - // convert slider value to offset - const offset = value / 10; - return offset.toFixed(1); -} - -function getSliderValueFromOffset(value) { - const sliderValue = value * 10; - return Math.min(300, Math.max(-300, sliderValue.toFixed(1))); -} - class SubtitleSync { constructor(currentPlayer) { player = currentPlayer; diff --git a/src/components/subtitlesync/subtitlesync.template.html b/src/components/subtitlesync/subtitlesync.template.html index a055d24fc..eb36d5826 100644 --- a/src/components/subtitlesync/subtitlesync.template.html +++ b/src/components/subtitlesync/subtitlesync.template.html @@ -3,7 +3,7 @@
0s
- +
From 491055a5bab9edc34fb93044779d8de54262d018 Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Wed, 23 Aug 2023 00:15:39 +0300 Subject: [PATCH 064/142] refactor: Split and extract video range support detection --- src/scripts/browserDeviceProfile.js | 46 +++++++++++++++++++---------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/src/scripts/browserDeviceProfile.js b/src/scripts/browserDeviceProfile.js index c00e188d4..f6289522f 100644 --- a/src/scripts/browserDeviceProfile.js +++ b/src/scripts/browserDeviceProfile.js @@ -188,6 +188,27 @@ function supportsVc1(videoTestElement) { return browser.tizen || browser.web0s || browser.edgeUwp || videoTestElement.canPlayType('video/mp4; codecs="vc-1"').replace(/no/, ''); } +function supportsHdr10(options) { + return options.supportsHdr10 ?? (false // eslint-disable-line sonarjs/no-redundant-boolean + || browser.tizen + || browser.web0s + || browser.safari && ((browser.iOS && browser.iOSVersion >= 11) || browser.osx) + // Chrome mobile and Firefox have no client side tone-mapping + // Edge Chromium on Nvidia is known to have color issues on 10-bit video + || browser.chrome && !browser.mobile + ); +} + +function supportsHlg(options) { + return options.supportsHlg ?? supportsHdr10(options); +} + +function supportsDolbyVision(options) { + return options.supportsDolbyVision ?? (false // eslint-disable-line sonarjs/no-redundant-boolean + || browser.safari && ((browser.iOS && browser.iOSVersion >= 13) || browser.osx) + ); +} + function getDirectPlayProfileForVideoContainer(container, videoAudioCodecs, videoTestElement, options) { let supported = false; let profileContainer = container; @@ -897,25 +918,20 @@ export default function (options) { let vp9VideoRangeTypes = 'SDR'; let av1VideoRangeTypes = 'SDR'; - if (browser.safari && ((browser.iOS && browser.iOSVersion >= 11) || browser.osx)) { - hevcVideoRangeTypes += '|HDR10|HLG'; - if ((browser.iOS && browser.iOSVersion >= 13) || browser.osx) { - hevcVideoRangeTypes += '|DOVI'; - } + if (supportsHdr10(options)) { + hevcVideoRangeTypes += '|HDR10'; + vp9VideoRangeTypes += '|HDR10'; + av1VideoRangeTypes += '|HDR10'; } - if (browser.tizen || browser.web0s) { - hevcVideoRangeTypes += '|HDR10|HLG'; - vp9VideoRangeTypes += '|HDR10|HLG'; - av1VideoRangeTypes += '|HDR10|HLG'; + if (supportsHlg(options)) { + hevcVideoRangeTypes += '|HLG'; + vp9VideoRangeTypes += '|HLG'; + av1VideoRangeTypes += '|HLG'; } - // Chrome mobile and Firefox have no client side tone-mapping - // Edge Chromium on Nvidia is known to have color issues on 10-bit video - if (browser.chrome && !browser.mobile) { - hevcVideoRangeTypes += '|HDR10|HLG'; - vp9VideoRangeTypes += '|HDR10|HLG'; - av1VideoRangeTypes += '|HDR10|HLG'; + if (supportsDolbyVision(options)) { + hevcVideoRangeTypes += '|DOVI'; } const h264CodecProfileConditions = [ From 9024ebea390551cdaf40c9f25af0b3278c1e6d3e Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Tue, 26 Sep 2023 23:41:31 +0300 Subject: [PATCH 065/142] Handle pressing Enter to finish keyboard dragging of slider --- src/elements/emby-slider/emby-slider.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/elements/emby-slider/emby-slider.js b/src/elements/emby-slider/emby-slider.js index cf804849d..ddbe9c596 100644 --- a/src/elements/emby-slider/emby-slider.js +++ b/src/elements/emby-slider/emby-slider.js @@ -501,6 +501,13 @@ function onKeyDown(e) { e.preventDefault(); e.stopPropagation(); break; + case 'Enter': + if (this.keyboardDragging) { + finishKeyboardDragging(this); + e.preventDefault(); + e.stopPropagation(); + } + break; } } From 15938e8aaf69c2c82c4b4430d4eadff5265ae5ea Mon Sep 17 00:00:00 2001 From: Prasaedonium Date: Wed, 27 Sep 2023 00:00:56 +0000 Subject: [PATCH 066/142] Translated using Weblate (Spanish (Mexico)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/es_MX/ --- src/strings/es-mx.json | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/strings/es-mx.json b/src/strings/es-mx.json index 93e8c3142..206fc431b 100644 --- a/src/strings/es-mx.json +++ b/src/strings/es-mx.json @@ -1400,7 +1400,7 @@ "QuickConnectDescription": "Para entrar con Conexión Rápida, selecciona el botón de 'Conexión rápida' en el dispositivo desde donde intentas entrar e introduce el código que se muestra debajo.", "QuickConnectDeactivated": "La Conexión Rápida fue desactivada antes de que la petición pudiera ser aprobada", "QuickConnectAuthorizeFail": "Código de Conexión Rápida desconocido", - "QuickConnectAuthorizeSuccess": "Petición autorizada", + "QuickConnectAuthorizeSuccess": "¡Has autenticado tu dispositivo exitosamente!", "QuickConnectAuthorizeCode": "Introduce el codigo {0} para entrar", "QuickConnectActivationSuccessful": "Activada exitosamente", "QuickConnect": "Conexión rápida", @@ -1697,10 +1697,10 @@ "SaveRecordingNFOHelp": "Guardar metadatos del proveedor de listados EPG junto con los archivos multimedia.", "VideoBitrateNotSupported": "La taza de bits del vídeo no es compatible", "UnknownVideoStreamInfo": "La información de transmisión de video es desconocida", - "EnableAudioNormalizationHelp": "La normalización de audio añadirá una ganancia constante para mantener la media en el nivel deseado (-18dB)", + "EnableAudioNormalizationHelp": "La normalización de audio añadirá una ganancia constante para mantener la media en el nivel deseado (-18dB).", "HeaderPerformance": "Rendimiento", "LabelEnableLUFSScan": "Habilitar escaneó LUFS", - "LabelEnableLUFSScanHelp": "Habilitar escaneó LUFS para musica (Esto tomara más tiempo y usará más recursos).", + "LabelEnableLUFSScanHelp": "Los clientes pueden normalizar la reproducción de audio para tener el mismo nivel de ruido entre las pistas de audio. Esto hará que los escaneos de biblioteca tomen más tiempo y recursos.", "MenuClose": "Cerrar Menu", "MenuOpen": "Abrir Menu", "SubtitleBlack": "Negro", @@ -1755,9 +1755,23 @@ "TonemappingModeHelp": "Seleccione el modo de mapeado de tono. Si experimenta sobreiluminación intente cambiar al modo RGB.", "AllowSegmentDeletion": "Borrar segmentos", "HeaderEpisodesStatus": "Estatus de los Episodios", - "AllowSegmentDeletionHelp": "Borrar los viejos segmentos después de que hayan sido enviados al cliente. Esto previene que se tenga almacenado la totalidad de la transcodificación en el disco. Esto funciona unicamente cuando se tenga habilitado el throttling. Apagar esta opción cuando se tengan problemas de reproducción.", + "AllowSegmentDeletionHelp": "Borrar los viejos segmentos después de que hayan sido enviados al cliente. Esto previene que se tenga almacenado la totalidad de la transcodificación en el disco. Esto funciona unicamente cuando se tenga habilitado el throttling. Apagar esta opción cuando se tengan problemas de reproducción.", "LabelThrottleDelaySeconds": "Acelerar después", - "LabelThrottleDelaySecondsHelp": "Tiempo en segundos después de que la transcodificación entre en throttled. Deben ser los suficientes para que el buffer del cliente siga operando. Unicamente funciona si el throtting está habilitdo.", - "LabelSegmentKeepSeconds": "Tiempo para que permanescan los segmentos", - "LabelSegmentKeepSecondsHelp": "Tiempo en segundos en los que los segmentos deben permanecer antes de que sean sobrescritos. Estos deben de ser mayores a los indicados en \"Acelerar despues de\". Esto funciona unicamente si esta habilitada la opción de eliminar el segmento." + "LabelThrottleDelaySecondsHelp": "Tiempo en segundos después de que la transcodificación entre en aceleración. Deben ser los suficientes para que el buffer del cliente siga operando. Unicamente funciona si la aceleración está habilitada.", + "LabelSegmentKeepSeconds": "Tiempo para guardar segmentos", + "LabelSegmentKeepSecondsHelp": "Tiempo en segundos en los que los segmentos deben permanecer antes de que sean sobrescritos. Estos deben de ser mayores a los indicados en \"Acelerar despues de\". Esto funciona unicamente si esta habilitada la opción de eliminar el segmento.", + "AllowAv1Encoding": "Permitir encodificación en formato AV1", + "GoHome": "Ir a Inicio", + "UnknownError": "Un error desconocido ocurrió.", + "GridView": "Vista en Cuadrícula", + "LabelBackdropScreensaverInterval": "Intervalo del Protector de Pantalla de Fondo", + "BackdropScreensaver": "Protector de pantalla de Fondo", + "LabelBackdropScreensaverIntervalHelp": "El tiempo en segundos entre diferentes fondos cuando se usa el protector de pantalla de fondo.", + "ListView": "Vista en Lista", + "LogoScreensaver": "Protector de Pantalla de Logo", + "AiTranslated": "Traducido por IA", + "MachineTranslated": "Traducido por Máquina", + "ForeignPartsOnly": "Solamente partes Forzadas/Foráneas", + "HearingImpairedShort": "HI/SDH", + "HeaderGuestCast": "Estrellas Invitadas" } From 7f5e6c791bc442456ddd6ce78a1f94f0abc09fa6 Mon Sep 17 00:00:00 2001 From: Prasaedonium Date: Tue, 26 Sep 2023 23:51:27 +0000 Subject: [PATCH 067/142] Translated using Weblate (Spanish) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/es/ --- src/strings/es.json | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/strings/es.json b/src/strings/es.json index ecd3e6974..30ba43bb8 100644 --- a/src/strings/es.json +++ b/src/strings/es.json @@ -1378,7 +1378,7 @@ "QuickConnectDescription": "Para iniciar sesión con conexión rápida, pulsa el botón \"Conexión Rápida\" en el dispositivo desde el que está iniciando sesión e introduce el código que se muestra a continuación.", "QuickConnectDeactivated": "La conexión rápida se desactivó antes que se pudiera aprobar la solicitud de inicio de sesión", "QuickConnectAuthorizeFail": "Código de conexión rápida desconocido", - "QuickConnectAuthorizeSuccess": "Solicitar autorización", + "QuickConnectAuthorizeSuccess": "¡Tu dispositivo ha sido exitosamente autenticado!", "QuickConnectAuthorizeCode": "Introducir código de identificación {0}", "QuickConnectActivationSuccessful": "Activado satisfactoriamente", "QuickConnect": "Conexión rápida", @@ -1738,7 +1738,7 @@ "LabelSyncPlayNoGroups": "No hay grupos disponibles", "Notifications": "Notificaciones", "EnableAudioNormalization": "Normalización de audio", - "LabelEnableLUFSScanHelp": "Habilitar escaneo LUFS para música (Esto tardará más y consumirá más recursos).", + "LabelEnableLUFSScanHelp": "Los clientes pueden normalizar la reproducción de audio para obtener un nivel de ruido igual en todas las pistas de audio. Esto hará que la biblioteca escaneé por más tiempo y utilice más recursos.", "LabelDate": "Fecha", "LabelLevel": "Nivel", "LabelMediaDetails": "Detalles de medios", @@ -1763,5 +1763,17 @@ "LabelThrottleDelaySeconds": "Limitar trás", "LabelSegmentKeepSecondsHelp": "Tiempo en segundos durante el cual se deben conservar los segmentos antes de que se sobrescriban. Debe ser mayor que \"Acelerar después\". Solo funciona si la eliminación de segmentos está habilitada.", "LabelBackdropScreensaverInterval": "Intervalo del fondo protector de pantalla", - "LabelBackdropScreensaverIntervalHelp": "El tiempo en segundos entre diferentes fondos cuando se utiliza el fondo protector de pantalla." + "LabelBackdropScreensaverIntervalHelp": "El tiempo en segundos entre diferentes fondos cuando se utiliza el fondo protector de pantalla.", + "AllowAv1Encoding": "Permitir encodificación en formato AV1", + "GoHome": "Ir a Inicio", + "UnknownError": "Un error desconocido ocurrió.", + "GridView": "Vista en Cuadrícula", + "ListView": "Vista en Lista", + "BackdropScreensaver": "Protector de Pantalla de Fondo", + "LogoScreensaver": "Protector de Pantallas de Logo", + "HearingImpairedShort": "HI/SDH", + "AiTranslated": "Traducido por IA", + "MachineTranslated": "Traducido por Máquina", + "HeaderGuestCast": "Estrellas Invitadas", + "ForeignPartsOnly": "Partes Forzadas/Foráneas solamente" } From 44678a61c2b0cbd2b48775619ac080f340559481 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Wed, 27 Sep 2023 02:07:40 -0400 Subject: [PATCH 068/142] Refactor app layouts and common components --- src/RootApp.tsx | 54 ++++--- src/apps/experimental/App.tsx | 10 +- src/apps/experimental/AppLayout.tsx | 117 +++++++-------- .../components/AppToolbar/index.tsx | 138 ++++-------------- .../components/drawers/AppDrawer.tsx | 28 +++- .../components/drawers/MainDrawerContent.tsx | 4 +- .../dashboard/AdvancedDrawerSection.tsx | 3 +- .../dashboard/DevicesDrawerSection.tsx | 3 +- .../drawers/dashboard/LiveTvDrawerSection.tsx | 3 +- .../drawers/dashboard/PluginDrawerSection.tsx | 3 +- .../drawers/dashboard/ServerDrawerSection.tsx | 3 +- src/apps/stable/App.tsx | 21 +-- src/components/AppBody.tsx | 24 +++ src/components/AppHeader.tsx | 18 ++- .../components/ElevationScroll.tsx | 0 .../drawers => components}/ListItemLink.tsx | 0 .../ResponsiveDrawer.tsx | 12 +- src/components/router/AsyncRoute.tsx | 40 +++-- src/components/toolbar/AppToolbar.tsx | 129 ++++++++++++++++ .../toolbar}/AppUserMenu.tsx | 0 .../toolbar}/UserMenuButton.tsx | 4 +- src/{apps/experimental => themes}/theme.ts | 1 + 22 files changed, 353 insertions(+), 262 deletions(-) create mode 100644 src/components/AppBody.tsx rename src/{apps/experimental => }/components/ElevationScroll.tsx (100%) rename src/{apps/experimental/components/drawers => components}/ListItemLink.tsx (100%) rename src/{apps/experimental/components/drawers => components}/ResponsiveDrawer.tsx (87%) create mode 100644 src/components/toolbar/AppToolbar.tsx rename src/{apps/experimental/components/AppToolbar/menus => components/toolbar}/AppUserMenu.tsx (100%) rename src/{apps/experimental/components/AppToolbar => components/toolbar}/UserMenuButton.tsx (96%) rename src/{apps/experimental => themes}/theme.ts (96%) diff --git a/src/RootApp.tsx b/src/RootApp.tsx index 62223f723..5ee5ffab5 100644 --- a/src/RootApp.tsx +++ b/src/RootApp.tsx @@ -1,37 +1,53 @@ import loadable from '@loadable/component'; +import { ThemeProvider } from '@mui/material/styles'; import { History } from '@remix-run/router'; import React from 'react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; -import StableApp from './apps/stable/App'; -import { HistoryRouter } from './components/router/HistoryRouter'; -import { ApiProvider } from './hooks/useApi'; -import { WebConfigProvider } from './hooks/useWebConfig'; +import StableApp from 'apps/stable/App'; +import AppHeader from 'components/AppHeader'; +import Backdrop from 'components/Backdrop'; +import { HistoryRouter } from 'components/router/HistoryRouter'; +import { ApiProvider } from 'hooks/useApi'; +import { WebConfigProvider } from 'hooks/useWebConfig'; +import theme from 'themes/theme'; const ExperimentalApp = loadable(() => import('./apps/experimental/App')); const queryClient = new QueryClient(); -const RootApp = ({ history }: { history: History }) => { +const RootAppLayout = () => { const layoutMode = localStorage.getItem('layout'); + const isExperimentalLayout = layoutMode === 'experimental'; return ( - - - - - { - layoutMode === 'experimental' ? - : - - } - - - - - + <> + + + + { + isExperimentalLayout ? + : + + } + ); }; +const RootApp = ({ history }: { history: History }) => ( + + + + + + + + + + + + +); + export default RootApp; diff --git a/src/apps/experimental/App.tsx b/src/apps/experimental/App.tsx index 57ad22eae..44c6d24b2 100644 --- a/src/apps/experimental/App.tsx +++ b/src/apps/experimental/App.tsx @@ -1,16 +1,16 @@ import React from 'react'; import { Navigate, Route, Routes } from 'react-router-dom'; +import { REDIRECTS } from 'apps/stable/routes/_redirects'; import ConnectionRequired from 'components/ConnectionRequired'; import ServerContentPage from 'components/ServerContentPage'; import { toAsyncPageRoute } from 'components/router/AsyncRoute'; import { toViewManagerPageRoute } from 'components/router/LegacyRoute'; +import { toRedirectRoute } from 'components/router/Redirect'; import AppLayout from './AppLayout'; import { ASYNC_ADMIN_ROUTES, ASYNC_USER_ROUTES } from './routes/asyncRoutes'; import { LEGACY_ADMIN_ROUTES, LEGACY_PUBLIC_ROUTES, LEGACY_USER_ROUTES } from './routes/legacyRoutes'; -import { REDIRECTS } from 'apps/stable/routes/_redirects'; -import { toRedirectRoute } from 'components/router/Redirect'; const ExperimentalApp = () => { return ( @@ -38,10 +38,10 @@ const ExperimentalApp = () => { {LEGACY_PUBLIC_ROUTES.map(toViewManagerPageRoute)}
- - {/* Redirects for old paths */} - {REDIRECTS.map(toRedirectRoute)} + + {/* Redirects for old paths */} + {REDIRECTS.map(toRedirectRoute)} ); }; diff --git a/src/apps/experimental/AppLayout.tsx b/src/apps/experimental/AppLayout.tsx index 71824e0d7..1f4860637 100644 --- a/src/apps/experimental/AppLayout.tsx +++ b/src/apps/experimental/AppLayout.tsx @@ -1,18 +1,17 @@ import React, { useCallback, useEffect, useState } from 'react'; import AppBar from '@mui/material/AppBar'; import Box from '@mui/material/Box'; -import { ThemeProvider } from '@mui/material/styles'; +import { useTheme } from '@mui/material/styles'; import { Outlet, useLocation } from 'react-router-dom'; -import AppHeader from 'components/AppHeader'; -import Backdrop from 'components/Backdrop'; +import AppBody from 'components/AppBody'; +import ElevationScroll from 'components/ElevationScroll'; +import { DRAWER_WIDTH } from 'components/ResponsiveDrawer'; import { useApi } from 'hooks/useApi'; import { useLocalStorage } from 'hooks/useLocalStorage'; import AppToolbar from './components/AppToolbar'; -import AppDrawer, { DRAWER_WIDTH, isDrawerPath } from './components/drawers/AppDrawer'; -import ElevationScroll from './components/ElevationScroll'; -import theme from './theme'; +import AppDrawer, { isDrawerPath } from './components/drawers/AppDrawer'; import './AppOverrides.scss'; @@ -29,6 +28,7 @@ const AppLayout = () => { const [ isDrawerActive, setIsDrawerActive ] = useState(appSettings.isDrawerPinned); const { user } = useApi(); const location = useLocation(); + const theme = useTheme(); const isDrawerAvailable = isDrawerPath(location.pathname); const isDrawerOpen = isDrawerActive && isDrawerAvailable && Boolean(user); @@ -47,67 +47,54 @@ const AppLayout = () => { }, [ isDrawerActive, setIsDrawerActive ]); return ( - - - -
- {/* - * TODO: These components are not used, but views interact with them directly so the need to be - * present in the dom. We add them in a hidden element to prevent errors. - */} - -
- - - - muiTheme.zIndex.drawer + 1 }} - > - - - - - - - + + muiTheme.zIndex.drawer + 1 }} > -
-
- -
- + + + + + + + + + + - + ); }; diff --git a/src/apps/experimental/components/AppToolbar/index.tsx b/src/apps/experimental/components/AppToolbar/index.tsx index 0317ae92a..ed447ae69 100644 --- a/src/apps/experimental/components/AppToolbar/index.tsx +++ b/src/apps/experimental/components/AppToolbar/index.tsx @@ -1,22 +1,14 @@ -import ArrowBack from '@mui/icons-material/ArrowBack'; -import MenuIcon from '@mui/icons-material/Menu'; import SearchIcon from '@mui/icons-material/Search'; -import Box from '@mui/material/Box'; import IconButton from '@mui/material/IconButton'; -import Toolbar from '@mui/material/Toolbar'; import Tooltip from '@mui/material/Tooltip'; -import Typography from '@mui/material/Typography'; import React, { FC } from 'react'; import { Link, useLocation } from 'react-router-dom'; -import appIcon from 'assets/img/icon-transparent.png'; -import { appRouter } from 'components/router/appRouter'; -import { useApi } from 'hooks/useApi'; +import AppToolbar from 'components/toolbar/AppToolbar'; import globalize from 'scripts/globalize'; import AppTabs from '../tabs/AppTabs'; import { isDrawerPath } from '../drawers/AppDrawer'; -import UserMenuButton from './UserMenuButton'; import RemotePlayButton from './RemotePlayButton'; import SyncPlayButton from './SyncPlayButton'; @@ -25,120 +17,40 @@ interface AppToolbarProps { onDrawerButtonClick: (event: React.MouseEvent) => void } -const onBackButtonClick = () => { - appRouter.back() - .catch(err => { - console.error('[AppToolbar] error calling appRouter.back', err); - }); -}; - -const AppToolbar: FC = ({ +const ExperimentalAppToolbar: FC = ({ isDrawerOpen, onDrawerButtonClick }) => { - const { user } = useApi(); - const isUserLoggedIn = Boolean(user); const location = useLocation(); - const isDrawerAvailable = isDrawerPath(location.pathname); - const isBackButtonAvailable = appRouter.canGoBack(); return ( - - {isUserLoggedIn && isDrawerAvailable && ( - - - - - - )} - - {isBackButtonAvailable && ( - - - - - - )} - - - - - Jellyfin - - - - - - {isUserLoggedIn && ( + - - - + + - - - - - - - - - - + + + + + - )} - + } + isDrawerAvailable={isDrawerAvailable} + isDrawerOpen={isDrawerOpen} + onDrawerButtonClick={onDrawerButtonClick} + > + + ); }; -export default AppToolbar; +export default ExperimentalAppToolbar; diff --git a/src/apps/experimental/components/drawers/AppDrawer.tsx b/src/apps/experimental/components/drawers/AppDrawer.tsx index ffd9ba119..c414e6ba7 100644 --- a/src/apps/experimental/components/drawers/AppDrawer.tsx +++ b/src/apps/experimental/components/drawers/AppDrawer.tsx @@ -1,5 +1,7 @@ import React, { FC } from 'react'; -import { Route, Routes } from 'react-router-dom'; +import { Route, Routes, useLocation } from 'react-router-dom'; + +import ResponsiveDrawer, { ResponsiveDrawerProps } from 'components/ResponsiveDrawer'; import { ASYNC_ADMIN_ROUTES, ASYNC_USER_ROUTES } from '../../routes/asyncRoutes'; import { LEGACY_ADMIN_ROUTES, LEGACY_USER_ROUTES } from '../../routes/legacyRoutes'; @@ -10,7 +12,7 @@ import LiveTvDrawerSection from './dashboard/LiveTvDrawerSection'; import PluginDrawerSection from './dashboard/PluginDrawerSection'; import ServerDrawerSection from './dashboard/ServerDrawerSection'; import MainDrawerContent from './MainDrawerContent'; -import ResponsiveDrawer, { ResponsiveDrawerProps } from './ResponsiveDrawer'; +import { isTabPath } from '../tabs/tabRoutes'; export const DRAWER_WIDTH = 240; @@ -36,6 +38,20 @@ export const isDrawerPath = (path: string) => ( || ADMIN_DRAWER_ROUTES.some(route => route.path === path || `/${route.path}` === path) ); +const Drawer: FC = ({ children, ...props }) => { + const location = useLocation(); + const hasSecondaryToolBar = isTabPath(location.pathname); + + return ( + + {children} + + ); +}; + const AppDrawer: FC = ({ open = false, onClose, @@ -48,13 +64,13 @@ const AppDrawer: FC = ({ key={route.path} path={route.path} element={ - - + } /> )) @@ -65,7 +81,7 @@ const AppDrawer: FC = ({ key={route.path} path={route.path} element={ - = ({ - + } /> )) diff --git a/src/apps/experimental/components/drawers/MainDrawerContent.tsx b/src/apps/experimental/components/drawers/MainDrawerContent.tsx index f402189cf..4d2a74b8a 100644 --- a/src/apps/experimental/components/drawers/MainDrawerContent.tsx +++ b/src/apps/experimental/components/drawers/MainDrawerContent.tsx @@ -17,12 +17,12 @@ import ListSubheader from '@mui/material/ListSubheader'; import React, { useEffect, useState } from 'react'; import { useLocation } from 'react-router-dom'; +import ListItemLink from 'components/ListItemLink'; +import { appRouter } from 'components/router/appRouter'; import { useApi } from 'hooks/useApi'; import { useWebConfig } from 'hooks/useWebConfig'; import globalize from 'scripts/globalize'; -import { appRouter } from 'components/router/appRouter'; -import ListItemLink from './ListItemLink'; import LibraryIcon from '../LibraryIcon'; const MainDrawerContent = () => { diff --git a/src/apps/experimental/components/drawers/dashboard/AdvancedDrawerSection.tsx b/src/apps/experimental/components/drawers/dashboard/AdvancedDrawerSection.tsx index 7e0bb4e36..5a74c6863 100644 --- a/src/apps/experimental/components/drawers/dashboard/AdvancedDrawerSection.tsx +++ b/src/apps/experimental/components/drawers/dashboard/AdvancedDrawerSection.tsx @@ -15,10 +15,9 @@ import ListSubheader from '@mui/material/ListSubheader'; import React from 'react'; import { useLocation } from 'react-router-dom'; +import ListItemLink from 'components/ListItemLink'; import globalize from 'scripts/globalize'; -import ListItemLink from '../ListItemLink'; - const PLUGIN_PATHS = [ '/installedplugins.html', '/availableplugins.html', diff --git a/src/apps/experimental/components/drawers/dashboard/DevicesDrawerSection.tsx b/src/apps/experimental/components/drawers/dashboard/DevicesDrawerSection.tsx index cb3cbf33c..fe3ec0921 100644 --- a/src/apps/experimental/components/drawers/dashboard/DevicesDrawerSection.tsx +++ b/src/apps/experimental/components/drawers/dashboard/DevicesDrawerSection.tsx @@ -8,10 +8,9 @@ import ListSubheader from '@mui/material/ListSubheader'; import React from 'react'; import { useLocation } from 'react-router-dom'; +import ListItemLink from 'components/ListItemLink'; import globalize from 'scripts/globalize'; -import ListItemLink from '../ListItemLink'; - const DLNA_PATHS = [ '/dlnasettings.html', '/dlnaprofiles.html' diff --git a/src/apps/experimental/components/drawers/dashboard/LiveTvDrawerSection.tsx b/src/apps/experimental/components/drawers/dashboard/LiveTvDrawerSection.tsx index 505973b82..e3d20e154 100644 --- a/src/apps/experimental/components/drawers/dashboard/LiveTvDrawerSection.tsx +++ b/src/apps/experimental/components/drawers/dashboard/LiveTvDrawerSection.tsx @@ -6,10 +6,9 @@ import ListItemText from '@mui/material/ListItemText'; import ListSubheader from '@mui/material/ListSubheader'; import React from 'react'; +import ListItemLink from 'components/ListItemLink'; import globalize from 'scripts/globalize'; -import ListItemLink from '../ListItemLink'; - const LiveTvDrawerSection = () => { return ( { const { api } = useApi(); const [ pagesInfo, setPagesInfo ] = useState([]); diff --git a/src/apps/experimental/components/drawers/dashboard/ServerDrawerSection.tsx b/src/apps/experimental/components/drawers/dashboard/ServerDrawerSection.tsx index 388c1feee..2ed6b73f8 100644 --- a/src/apps/experimental/components/drawers/dashboard/ServerDrawerSection.tsx +++ b/src/apps/experimental/components/drawers/dashboard/ServerDrawerSection.tsx @@ -8,10 +8,9 @@ import ListSubheader from '@mui/material/ListSubheader'; import React from 'react'; import { useLocation } from 'react-router-dom'; +import ListItemLink from 'components/ListItemLink'; import globalize from 'scripts/globalize'; -import ListItemLink from '../ListItemLink'; - const LIBRARY_PATHS = [ '/library.html', '/librarydisplay.html', diff --git a/src/apps/stable/App.tsx b/src/apps/stable/App.tsx index 3ad72bcb8..8285cbc9e 100644 --- a/src/apps/stable/App.tsx +++ b/src/apps/stable/App.tsx @@ -1,8 +1,7 @@ import React from 'react'; import { Navigate, Outlet, Route, Routes } from 'react-router-dom'; -import AppHeader from 'components/AppHeader'; -import Backdrop from 'components/Backdrop'; +import AppBody from 'components/AppBody'; import ServerContentPage from 'components/ServerContentPage'; import ConnectionRequired from 'components/ConnectionRequired'; import { toAsyncPageRoute } from 'components/router/AsyncRoute'; @@ -14,15 +13,9 @@ import { REDIRECTS } from './routes/_redirects'; import { toRedirectRoute } from 'components/router/Redirect'; const Layout = () => ( - <> - - - -
-
- -
- + + + ); const StableApp = () => ( @@ -53,10 +46,10 @@ const StableApp = () => ( {/* Suppress warnings for unhandled routes */} - - {/* Redirects for old paths */} - {REDIRECTS.map(toRedirectRoute)} + + {/* Redirects for old paths */} + {REDIRECTS.map(toRedirectRoute)} ); diff --git a/src/components/AppBody.tsx b/src/components/AppBody.tsx new file mode 100644 index 000000000..5d10bca2c --- /dev/null +++ b/src/components/AppBody.tsx @@ -0,0 +1,24 @@ +import React, { FC, useEffect } from 'react'; +import viewContainer from './viewContainer'; + +/** + * A simple component that includes the correct structure for ViewManager pages + * to exist alongside standard React pages. + */ +const AppBody: FC = ({ children }) => { + useEffect(() => () => { + // Reset view container state on unload + viewContainer.reset(); + }, []); + + return ( + <> +
+
+ {children} +
+ + ); +}; + +export default AppBody; diff --git a/src/components/AppHeader.tsx b/src/components/AppHeader.tsx index 941d36a94..1ce6a6a8c 100644 --- a/src/components/AppHeader.tsx +++ b/src/components/AppHeader.tsx @@ -1,19 +1,29 @@ -import React, { useEffect } from 'react'; +import React, { FC, useEffect } from 'react'; -const AppHeader = () => { +interface AppHeaderParams { + isHidden?: boolean +} + +const AppHeader: FC = ({ + isHidden = false +}) => { useEffect(() => { // Initialize the UI components after first render import('../scripts/libraryMenu'); }, []); return ( - <> + /** + * NOTE: These components are not used with the new layouts, but legacy views interact with the elements + * directly so they need to be present in the DOM. We use display: none to hide them and prevent errors. + */ +
- +
); }; diff --git a/src/apps/experimental/components/ElevationScroll.tsx b/src/components/ElevationScroll.tsx similarity index 100% rename from src/apps/experimental/components/ElevationScroll.tsx rename to src/components/ElevationScroll.tsx diff --git a/src/apps/experimental/components/drawers/ListItemLink.tsx b/src/components/ListItemLink.tsx similarity index 100% rename from src/apps/experimental/components/drawers/ListItemLink.tsx rename to src/components/ListItemLink.tsx diff --git a/src/apps/experimental/components/drawers/ResponsiveDrawer.tsx b/src/components/ResponsiveDrawer.tsx similarity index 87% rename from src/apps/experimental/components/drawers/ResponsiveDrawer.tsx rename to src/components/ResponsiveDrawer.tsx index 1f7a554cf..bc463cca2 100644 --- a/src/apps/experimental/components/drawers/ResponsiveDrawer.tsx +++ b/src/components/ResponsiveDrawer.tsx @@ -5,14 +5,13 @@ import SwipeableDrawer from '@mui/material/SwipeableDrawer'; import Toolbar from '@mui/material/Toolbar'; import useMediaQuery from '@mui/material/useMediaQuery'; import React, { FC, useCallback } from 'react'; -import { useLocation } from 'react-router-dom'; import browser from 'scripts/browser'; -import { DRAWER_WIDTH } from './AppDrawer'; -import { isTabPath } from '../tabs/tabRoutes'; +export const DRAWER_WIDTH = 240; export interface ResponsiveDrawerProps { + hasSecondaryToolBar?: boolean open: boolean onClose: () => void onOpen: () => void @@ -20,18 +19,17 @@ export interface ResponsiveDrawerProps { const ResponsiveDrawer: FC = ({ children, + hasSecondaryToolBar = false, open = false, onClose, onOpen }) => { - const location = useLocation(); const isSmallScreen = useMediaQuery((theme: Theme) => theme.breakpoints.up('sm')); const isLargeScreen = useMediaQuery((theme: Theme) => theme.breakpoints.up('lg')); - const isTallToolbar = isTabPath(location.pathname) && !isLargeScreen; const getToolbarStyles = useCallback((theme: Theme) => ({ - marginBottom: isTallToolbar ? theme.spacing(6) : 0 - }), [ isTallToolbar ]); + marginBottom: (hasSecondaryToolBar && !isLargeScreen) ? theme.spacing(6) : 0 + }), [ hasSecondaryToolBar, isLargeScreen ]); return ( isSmallScreen ? ( /* DESKTOP DRAWER */ diff --git a/src/components/router/AsyncRoute.tsx b/src/components/router/AsyncRoute.tsx index 9ed49544f..031e5700e 100644 --- a/src/components/router/AsyncRoute.tsx +++ b/src/components/router/AsyncRoute.tsx @@ -1,4 +1,4 @@ -import loadable from '@loadable/component'; +import loadable, { LoadableComponent } from '@loadable/component'; import React from 'react'; import { Route } from 'react-router-dom'; @@ -10,13 +10,18 @@ export enum AsyncRouteType { export interface AsyncRoute { /** The URL path for this route. */ path: string - /** The relative path to the page component in the routes directory. */ - page: string - /** The route should use the page component from the experimental app. */ + /** + * The relative path to the page component in the routes directory. + * Will fallback to using the `path` value if not specified. + */ + page?: string + /** The page element to render. */ + element?: LoadableComponent + /** The page type used to load the correct page element. */ type?: AsyncRouteType } -interface AsyncPageProps { +export interface AsyncPageProps { /** The relative path to the page component in the routes directory. */ page: string } @@ -31,14 +36,19 @@ const StableAsyncPage = loadable( { cacheKey: (props: AsyncPageProps) => props.page } ); -export const toAsyncPageRoute = ({ path, page, type = AsyncRouteType.Stable }: AsyncRoute) => ( - { + const Element = element + || ( type === AsyncRouteType.Experimental ? - : - - )} - /> -); + ExperimentalAsyncPage : + StableAsyncPage + ); + + return ( + } + /> + ); +}; diff --git a/src/components/toolbar/AppToolbar.tsx b/src/components/toolbar/AppToolbar.tsx new file mode 100644 index 000000000..8ed92eee1 --- /dev/null +++ b/src/components/toolbar/AppToolbar.tsx @@ -0,0 +1,129 @@ +import ArrowBack from '@mui/icons-material/ArrowBack'; +import MenuIcon from '@mui/icons-material/Menu'; +import Box from '@mui/material/Box'; +import IconButton from '@mui/material/IconButton'; +import Toolbar from '@mui/material/Toolbar'; +import Tooltip from '@mui/material/Tooltip'; +import Typography from '@mui/material/Typography'; +import React, { FC, ReactNode } from 'react'; +import { Link } from 'react-router-dom'; + +import appIcon from 'assets/img/icon-transparent.png'; +import { appRouter } from 'components/router/appRouter'; +import { useApi } from 'hooks/useApi'; +import globalize from 'scripts/globalize'; + +import UserMenuButton from './UserMenuButton'; + +interface AppToolbarProps { + buttons?: ReactNode + isDrawerAvailable: boolean + isDrawerOpen: boolean + onDrawerButtonClick: (event: React.MouseEvent) => void +} + +const onBackButtonClick = () => { + appRouter.back() + .catch(err => { + console.error('[AppToolbar] error calling appRouter.back', err); + }); +}; + +const AppToolbar: FC = ({ + buttons, + children, + isDrawerAvailable, + isDrawerOpen, + onDrawerButtonClick +}) => { + const { user } = useApi(); + const isUserLoggedIn = Boolean(user); + + const isBackButtonAvailable = appRouter.canGoBack(); + + return ( + + {isUserLoggedIn && isDrawerAvailable && ( + + + + + + )} + + {isBackButtonAvailable && ( + + + + + + )} + + + + + Jellyfin + + + + {children} + + {isUserLoggedIn && ( + <> + + {buttons} + + + + + + + )} + + ); +}; + +export default AppToolbar; diff --git a/src/apps/experimental/components/AppToolbar/menus/AppUserMenu.tsx b/src/components/toolbar/AppUserMenu.tsx similarity index 100% rename from src/apps/experimental/components/AppToolbar/menus/AppUserMenu.tsx rename to src/components/toolbar/AppUserMenu.tsx diff --git a/src/apps/experimental/components/AppToolbar/UserMenuButton.tsx b/src/components/toolbar/UserMenuButton.tsx similarity index 96% rename from src/apps/experimental/components/AppToolbar/UserMenuButton.tsx rename to src/components/toolbar/UserMenuButton.tsx index 89f18669e..fa47ce85e 100644 --- a/src/apps/experimental/components/AppToolbar/UserMenuButton.tsx +++ b/src/components/toolbar/UserMenuButton.tsx @@ -2,11 +2,11 @@ import IconButton from '@mui/material/IconButton'; import Tooltip from '@mui/material/Tooltip'; import React, { useCallback, useState } from 'react'; +import UserAvatar from 'components/UserAvatar'; import { useApi } from 'hooks/useApi'; import globalize from 'scripts/globalize'; -import AppUserMenu, { ID } from './menus/AppUserMenu'; -import UserAvatar from 'components/UserAvatar'; +import AppUserMenu, { ID } from './AppUserMenu'; const UserMenuButton = () => { const { user } = useApi(); diff --git a/src/apps/experimental/theme.ts b/src/themes/theme.ts similarity index 96% rename from src/apps/experimental/theme.ts rename to src/themes/theme.ts index d3f84366b..e223e24c5 100644 --- a/src/apps/experimental/theme.ts +++ b/src/themes/theme.ts @@ -1,5 +1,6 @@ import { createTheme } from '@mui/material/styles'; +/** The default Jellyfin app theme for mui */ const theme = createTheme({ palette: { mode: 'dark', From f94b965bd312c5a2dc75af8b00a5060cf9069580 Mon Sep 17 00:00:00 2001 From: arnausc Date: Wed, 27 Sep 2023 11:50:33 +0000 Subject: [PATCH 069/142] Translated using Weblate (Catalan) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/ca/ --- src/strings/ca.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/strings/ca.json b/src/strings/ca.json index 995dc1a0a..23482272a 100644 --- a/src/strings/ca.json +++ b/src/strings/ca.json @@ -1731,5 +1731,10 @@ "Notifications": "Notificacions", "NotificationsMovedMessage": "Les funcions de notificacions s'han mogut al plugin Webhook.", "UserMenu": "Menú d'usuari", - "AllowCollectionManagement": "Permet a aquest usuari gestionar col·leccions" + "AllowCollectionManagement": "Permet a aquest usuari gestionar col·leccions", + "AllowSegmentDeletion": "Suprimeix segments", + "AllowSegmentDeletionHelp": "Suprimeix els segments antics després d'haver estat enviats al client. Això impedeix haver d'emmagatzemar tot el fitxer transcodificat al disc. Només funcionarà amb l'activació de transcodes de l'acceleració. Desactiveu-ho si teniu problemes de reproducció.", + "LabelThrottleDelaySeconds": "Acceleració després de", + "LabelThrottleDelaySecondsHelp": "Temps en segons després del qual el transcodificador s'accelerarà. Ha de ser prou gran perquè el client mantingui una memòria intermèdia saludable. Només funciona si s'habilita l'acceleració.", + "LabelSegmentKeepSeconds": "Temps per mantenir els segments" } From eccfd5316d4d7393ed523884f11f7c3a8b97af2c Mon Sep 17 00:00:00 2001 From: MBR#0001 Date: Wed, 27 Sep 2023 16:42:31 +0200 Subject: [PATCH 070/142] Move function to util --- .../subtitleuploader/subtitleuploader.js | 16 ++-------------- src/utils/file.ts | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 14 deletions(-) create mode 100644 src/utils/file.ts diff --git a/src/components/subtitleuploader/subtitleuploader.js b/src/components/subtitleuploader/subtitleuploader.js index 5bfc27011..6cd3f1ff2 100644 --- a/src/components/subtitleuploader/subtitleuploader.js +++ b/src/components/subtitleuploader/subtitleuploader.js @@ -15,6 +15,7 @@ import '../../elements/emby-button/emby-button'; import '../../elements/emby-select/emby-select'; import '../formdialog.scss'; import './style.scss'; +import { readFileAsBase64 } from 'utils/file'; let currentItemId; let currentServerId; @@ -77,19 +78,6 @@ function setFiles(page, files) { reader.readAsDataURL(file); } -function getStringFromFile(file) { - return new Promise(function (resolve, reject) { - const reader = new FileReader(); - reader.onload = (e) => { - // Split by a comma to remove the url: prefix - const data = e.target.result.split(',')[1]; - resolve(data); - }; - reader.onerror = reject; - reader.readAsDataURL(file); - }); -} - async function onSubmit(e) { const file = currentFile; @@ -108,7 +96,7 @@ async function onSubmit(e) { const subtitleApi = getSubtitleApi(toApi(ServerConnections.getApiClient(currentServerId))); - const data = await getStringFromFile(file); + const data = await readFileAsBase64(file); const format = file.name.substring(file.name.lastIndexOf('.') + 1).toLowerCase(); subtitleApi.uploadSubtitle({ diff --git a/src/utils/file.ts b/src/utils/file.ts new file mode 100644 index 000000000..b85178e2f --- /dev/null +++ b/src/utils/file.ts @@ -0,0 +1,15 @@ +/** + * Reads and returns the file encoded in base64 + */ +export function readFileAsBase64(file: File): Promise { + return new Promise(function (resolve, reject) { + const reader = new FileReader(); + reader.onload = (e) => { + // Split by a comma to remove the url: prefix + const data = (e.target?.result as string)?.split?.(',')[1]; + resolve(data); + }; + reader.onerror = reject; + reader.readAsDataURL(file); + }); +} From a8ce0a9e60225bade60b5fffc5a018f3b47185ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Kucharczyk?= Date: Wed, 27 Sep 2023 18:18:10 +0000 Subject: [PATCH 071/142] Translated using Weblate (Czech) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/cs/ --- src/strings/cs.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/strings/cs.json b/src/strings/cs.json index 679ee7528..6d04706b1 100644 --- a/src/strings/cs.json +++ b/src/strings/cs.json @@ -1775,5 +1775,6 @@ "GoHome": "Přejít na domovskou obrazovku", "UnknownError": "Došlo k neznámé chybě.", "BackdropScreensaver": "Pozadí", - "LogoScreensaver": "Logo" + "LogoScreensaver": "Logo", + "LabelIsHearingImpaired": "Titulky pro neslyšící" } From c442113a3ffc913da520c29931fd82067f530612 Mon Sep 17 00:00:00 2001 From: Nicolas Viviani Date: Wed, 27 Sep 2023 19:05:56 +0000 Subject: [PATCH 072/142] Translated using Weblate (French) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fr/ --- src/strings/fr.json | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/strings/fr.json b/src/strings/fr.json index 00408d1e0..edde92935 100644 --- a/src/strings/fr.json +++ b/src/strings/fr.json @@ -654,7 +654,7 @@ "LabelSaveLocalMetadata": "Enregistrer les illustrations dans les dossiers des médias", "LabelSaveLocalMetadataHelp": "L'enregistrement des illustrations dans les dossiers des médias les placera à un endroit où elles seront facilement modifiables.", "LabelScheduledTaskLastRan": "Dernière exécution {0}, durée {1}.", - "LabelScreensaver": "Économiseur d'écran", + "LabelScreensaver": "Écran de veille", "LabelSeasonNumber": "Numéro de saison", "LabelSelectFolderGroups": "Grouper automatiquement le contenu des dossiers suivants par catégories telles que 'Films', 'Musiques' et 'Séries TV'", "LabelSelectFolderGroupsHelp": "Les dossiers non cochés seront affichés individuellement dans leur vue propre.", @@ -1762,18 +1762,19 @@ "LabelThrottleDelaySecondsHelp": "Durée en secondes après laquelle le débit du transcodage sera ajusté. Doit être suffisamment grande pour que le client puisse conserver une mémoire tampon saine. Ne fonctionne que si l'adaptation de la vitesse de transcodage est activée.", "LabelSegmentKeepSeconds": "Durée de conservation des segments", "LabelSegmentKeepSecondsHelp": "Durée en secondes de conservation des segments avant écrasement. La valeur doit être supérieure au délai d'ajustement. Ne fonctionne que si la suppression des segments est activée.", - "LabelBackdropScreensaverIntervalHelp": "Le temps en secondes entre différents fonds d'écran lors de l'utilisation de l'économiseur d'écran à fonds d'écran.", - "LabelBackdropScreensaverInterval": "Intervalle de l'économiseur d'écran à fonds d'écran", + "LabelBackdropScreensaverIntervalHelp": "Le temps en secondes entre différents fonds d'écran lors de l'utilisation du diaporama d'écrans de veille.", + "LabelBackdropScreensaverInterval": "Intervalle du diaporama d'écrans de veille", "AllowAv1Encoding": "Autoriser l'encodage au format AV1", "GoHome": "Retour à l'accueil", "ListView": "Vue en liste", "GridView": "Vue en grille", - "BackdropScreensaver": "Diaporama de fond d’écran", - "LogoScreensaver": "Logo de Jellyfin", + "BackdropScreensaver": "Diaporama d'écrans de veille", + "LogoScreensaver": "Logo d'écran de veille", "UnknownError": "Une erreur inconnue s’est produite.", "MachineTranslated": "Traduction automatique", "AiTranslated": "Traduction par IA", "ForeignPartsOnly": "Parties forcées/en langues étrangères uniquement", "HearingImpairedShort": "HI/SDH", - "HeaderGuestCast": "Invités vedettes" + "HeaderGuestCast": "Invités vedettes", + "LabelIsHearingImpaired": "Sous-titrage pour sourds et malentendants" } From b1847a5fcc989ad9436964bd8633e663b5e7766c Mon Sep 17 00:00:00 2001 From: Anand CU Date: Wed, 27 Sep 2023 17:26:11 +0000 Subject: [PATCH 073/142] Translated using Weblate (Kannada) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/kn/ --- src/strings/kn.json | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/strings/kn.json b/src/strings/kn.json index 4766f78b3..2162b1e31 100644 --- a/src/strings/kn.json +++ b/src/strings/kn.json @@ -7,5 +7,24 @@ "LanNetworksHelp": "ಬ್ಯಾಂಡ್‌ವಿಡ್ತ್ ನಿರ್ಬಂಧಗಳನ್ನು ಜಾರಿಗೊಳಿಸುವಾಗ ಸ್ಥಳೀಯ ನೆಟ್‌ವರ್ಕ್‌ನಲ್ಲಿ ಪರಿಗಣಿಸಲಾಗುವ ನೆಟ್‌ವರ್ಕ್‌ಗಳಿಗಾಗಿ IP ವಿಳಾಸಗಳ ಅಲ್ಪವಿರಾಮದಿಂದ ಬೇರ್ಪಡಿಸಿದ ಪಟ್ಟಿ ಅಥವಾ IP/ನೆಟ್‌ಮಾಸ್ಕ್ ನಮೂದುಗಳು. ಹೊಂದಿಸಿದರೆ, ಎಲ್ಲಾ ಇತರ IP ವಿಳಾಸಗಳನ್ನು ಬಾಹ್ಯ ನೆಟ್‌ವರ್ಕ್‌ನಲ್ಲಿ ಎಂದು ಪರಿಗಣಿಸಲಾಗುತ್ತದೆ ಮತ್ತು ಬಾಹ್ಯ ಬ್ಯಾಂಡ್‌ವಿಡ್ತ್ ನಿರ್ಬಂಧಗಳಿಗೆ ಒಳಪಟ್ಟಿರುತ್ತದೆ. ಖಾಲಿ ಬಿಟ್ಟರೆ, ಸರ್ವರ್‌ನ ಸಬ್‌ನೆಟ್ ಮಾತ್ರ ಸ್ಥಳೀಯ ನೆಟ್‌ವರ್ಕ್‌ನಲ್ಲಿದೆ ಎಂದು ಪರಿಗಣಿಸಲಾಗುತ್ತದೆ.", "Actor": "ನಟ", "AccessRestrictedTryAgainLater": "ಪ್ರಸ್ತುತ ಪ್ರವೇಶವನ್ನು ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ. ದಯವಿಟ್ಟು ನಂತರ ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ.", - "Absolute": "ಸಂಪೂರ್ಣ" + "Absolute": "ಸಂಪೂರ್ಣ", + "Default": "ಪೂರ್ವನಿಯೋಜಿತ", + "Channels": "ಮೂಲಗಳು", + "Folders": "ಫೋಲ್ಡರ್‌ಗಳು", + "HeaderContinueWatching": "ನೋಡುವುದನ್ನು ಮುಂದುವರಿಸಿ", + "Playlists": "ಪ್ಲೇಪಟ್ಟಿಗಳು", + "Songs": "ಹಾಡುಗಳು", + "Movies": "ಚಲನಚಿತ್ರಗಳು", + "MusicVideos": "ಸಂಗೀತ ವೀಡಿಯೊಗಳು", + "Sync": "ಹೊಂದಿಕೆ", + "ValueSpecialEpisodeName": "ವಿಶೇಷ - {0}", + "Albums": "ಸಂಪುಟ", + "Genres": "ಪ್ರಕಾರಗಳು", + "Favorites": "ಮೆಚ್ಚಿನವುಗಳು", + "HeaderAlbumArtists": "ಸಂಪುಟ ಕಲಾವಿದರು", + "Collections": "ಸಂಗ್ರಹಣೆಗಳು", + "Shows": "ಧಾರವಾಹಿಗಳು", + "Photos": "ಚಿತ್ರಗಳು", + "Artists": "ಕಲಾವಿದರು", + "Books": "ಪುಸ್ತಕಗಳು" } From 05181616aee495b3aa30d6a1a2fa6d8ffcf3ac81 Mon Sep 17 00:00:00 2001 From: Arimil Date: Wed, 27 Sep 2023 23:50:53 -0400 Subject: [PATCH 074/142] Update to node 20 fix some issues with dockerfiles combine installation statements into a single layer --- .ci/azure-pipelines-build.yml | 2 +- .github/workflows/lint.yml | 8 ++++---- .github/workflows/tsc.yml | 2 +- deployment/Dockerfile.centos | 4 ++-- deployment/Dockerfile.debian | 8 +++++--- deployment/Dockerfile.fedora | 3 ++- deployment/Dockerfile.portable | 7 +++++-- fedora/Makefile | 2 +- fedora/jellyfin-web.spec | 8 ++++---- package-lock.json | 4 ++-- package.json | 4 ++-- 11 files changed, 29 insertions(+), 23 deletions(-) diff --git a/.ci/azure-pipelines-build.yml b/.ci/azure-pipelines-build.yml index 9c3a51c9f..d87317239 100644 --- a/.ci/azure-pipelines-build.yml +++ b/.ci/azure-pipelines-build.yml @@ -16,7 +16,7 @@ jobs: - task: NodeTool@0 displayName: 'Install Node' inputs: - versionSpec: '16.x' + versionSpec: '20.x' - task: Cache@2 displayName: 'Cache node_modules' diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 085e9d789..b754665bc 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -18,7 +18,7 @@ jobs: - name: Setup node environment uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 with: - node-version: 16 + node-version: 20 check-latest: true cache: npm @@ -42,7 +42,7 @@ jobs: - name: Setup node environment uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 with: - node-version: 16 + node-version: 20 check-latest: true cache: npm @@ -63,7 +63,7 @@ jobs: - name: Setup node environment uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 with: - node-version: 16 + node-version: 20 check-latest: true cache: npm @@ -87,7 +87,7 @@ jobs: - name: Setup node environment uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 with: - node-version: 16 + node-version: 20 check-latest: true cache: npm diff --git a/.github/workflows/tsc.yml b/.github/workflows/tsc.yml index 2d30664b6..54b3208c8 100644 --- a/.github/workflows/tsc.yml +++ b/.github/workflows/tsc.yml @@ -18,7 +18,7 @@ jobs: - name: Setup node environment uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 with: - node-version: 16 + node-version: 20 check-latest: true cache: npm diff --git a/deployment/Dockerfile.centos b/deployment/Dockerfile.centos index 566180ad2..32ade8de4 100644 --- a/deployment/Dockerfile.centos +++ b/deployment/Dockerfile.centos @@ -13,8 +13,8 @@ ENV IS_DOCKER=YES RUN yum update -y \ && yum install -y epel-release \ && yum install -y rpmdevtools git autoconf automake glibc-devel gcc-c++ make \ - && curl -fsSL https://rpm.nodesource.com/setup_16.x | bash - \ - && yum install -y nodejs + && yum install https://rpm.nodesource.com/pub_20.x/nodistro/repo/nodesource-release-nodistro-1.noarch.rpm -y \ + && yum install nodejs -y --setopt=nodesource-nodejs.module_hotfixes=1 # Link to build script RUN ln -sf ${SOURCE_DIR}/deployment/build.centos /build.sh diff --git a/deployment/Dockerfile.debian b/deployment/Dockerfile.debian index 1d39d5b59..d42b812bf 100644 --- a/deployment/Dockerfile.debian +++ b/deployment/Dockerfile.debian @@ -12,11 +12,13 @@ ENV IS_DOCKER=YES # Prepare Debian build environment RUN apt-get update \ - && apt-get install -y debhelper mmv git curl \ - && curl -fsSL https://deb.nodesource.com/setup_16.x | bash - \ + && apt-get install -y debhelper mmv git curl gnupg ca-certificates \ + && mkdir -p /etc/apt/keyrings \ + && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \ + && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list \ + && apt-get update \ && apt-get install -y nodejs - # Link to build script RUN ln -sf ${SOURCE_DIR}/deployment/build.debian /build.sh diff --git a/deployment/Dockerfile.fedora b/deployment/Dockerfile.fedora index 5e44024f2..8c77fae0c 100644 --- a/deployment/Dockerfile.fedora +++ b/deployment/Dockerfile.fedora @@ -11,7 +11,8 @@ ENV IS_DOCKER=YES # Prepare Fedora environment RUN dnf update -y \ - && dnf install -y @buildsys-build rpmdevtools git dnf-plugins-core nodejs autoconf automake glibc-devel make + && yum install https://rpm.nodesource.com/pub_20.x/nodistro/repo/nodesource-release-nodistro-1.noarch.rpm -y \ + && dnf install -y @buildsys-build rpmdevtools git dnf-plugins-core nodejs autoconf automake glibc-devel make --setopt=nodesource-nodejs.module_hotfixes=1 # Link to build script RUN ln -sf ${SOURCE_DIR}/deployment/build.fedora /build.sh diff --git a/deployment/Dockerfile.portable b/deployment/Dockerfile.portable index 7044cca62..e57052178 100644 --- a/deployment/Dockerfile.portable +++ b/deployment/Dockerfile.portable @@ -11,8 +11,11 @@ ENV IS_DOCKER=YES # Prepare Debian build environment RUN apt-get update \ - && apt-get install -y mmv curl git \ - && curl -fsSL https://deb.nodesource.com/setup_16.x | bash - \ + && apt-get install -y mmv curl git gnupg ca-certificates \ + && mkdir -p /etc/apt/keyrings \ + && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \ + && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list \ + && apt-get update \ && apt-get install -y nodejs # Link to build script diff --git a/fedora/Makefile b/fedora/Makefile index c094073bc..adc5ecf67 100644 --- a/fedora/Makefile +++ b/fedora/Makefile @@ -7,7 +7,7 @@ RELEASE := $(shell set -x; sed -ne '/^Release:/s/.* *\(.*\)%{.*}.*/\1/p' $(DIR) SRPM := jellyfin-web-$(subst -,~,$(VERSION))-$(RELEASE)$(shell rpm --eval %dist).src.rpm TARBALL :=$(NAME)-$(subst -,~,$(VERSION)).tar.gz -epel-7-x86_64_repos := https://rpm.nodesource.com/pub_16.x/el/\$$releasever/\$$basearch/ +epel-7-x86_64_repos := https://rpm.nodesource.com/pub_20.x/nodistro/\$$basearch/ fed_ver := $(shell rpm -E %fedora) # fallback when not running on Fedora diff --git a/fedora/jellyfin-web.spec b/fedora/jellyfin-web.spec index 595ef33f2..fa1c1722e 100644 --- a/fedora/jellyfin-web.spec +++ b/fedora/jellyfin-web.spec @@ -14,10 +14,10 @@ BuildArch: noarch BuildRequires: nodejs %else BuildRequires: git -# Nodejs 16 is required and npm >= 8 should bring in NodeJS 16 -# This requires the build environment to use the nodejs:16 module stream: -# dnf module {install|switch-to}:web nodejs:16 -BuildRequires: npm >= 8 +# Nodejs 20 is required and npm >= 10 should bring in NodeJS 20 +# This requires the build environment to use the nodejs:20 module stream: +# dnf module {install|switch-to}:web nodejs:20 +BuildRequires: npm >= 10 %endif %description diff --git a/package-lock.json b/package-lock.json index 2ecbd9d15..5297c56d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -125,8 +125,8 @@ "worker-loader": "3.0.8" }, "engines": { - "node": ">=16.13.1", - "npm": ">=8.1.2", + "node": ">=20.7.0", + "npm": ">=10.1.0", "yarn": "YARN NO LONGER USED - use npm instead." } }, diff --git a/package.json b/package.json index 988c2a021..6eb78d111 100644 --- a/package.json +++ b/package.json @@ -150,8 +150,8 @@ "stylelint:scss": "stylelint --config=\".stylelintrc.scss.json\" \"src/**/*.scss\"" }, "engines": { - "node": ">=16.13.1", - "npm": ">=8.1.2", + "node": ">=20.7.0", + "npm": ">=10.1.0", "yarn": "YARN NO LONGER USED - use npm instead." } } From 32610adf537eeae43156b753c8eb3b4de7e4d4fd Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Thu, 28 Sep 2023 00:51:11 -0400 Subject: [PATCH 075/142] Update node version in docker build --- deployment/Dockerfile.docker | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/Dockerfile.docker b/deployment/Dockerfile.docker index 5605e1150..bd5a93996 100644 --- a/deployment/Dockerfile.docker +++ b/deployment/Dockerfile.docker @@ -1,4 +1,4 @@ -FROM node:lts-alpine +FROM node:20-alpine ARG SOURCE_DIR=/src ARG ARTIFACT_DIR=/jellyfin-web From 30291e7b95fbc7e4fc2e725d116409b0087b5fff Mon Sep 17 00:00:00 2001 From: Kityn Date: Thu, 28 Sep 2023 05:41:32 +0000 Subject: [PATCH 076/142] Translated using Weblate (Polish) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/pl/ --- src/strings/pl.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/strings/pl.json b/src/strings/pl.json index d7705b96e..aaa0b1ab5 100644 --- a/src/strings/pl.json +++ b/src/strings/pl.json @@ -1773,5 +1773,8 @@ "ForeignPartsOnly": "Tylko części wymuszone/obce", "HearingImpairedShort": "HI/SDH", "GoHome": "Wróć na start", - "UnknownError": "Wystąpił nieznany błąd." + "UnknownError": "Wystąpił nieznany błąd.", + "BackdropScreensaver": "Wygaszacz ekranu z fototapetami", + "LogoScreensaver": "Wygaszacz ekranu z logo", + "LabelIsHearingImpaired": "Dla osób niedosłyszących" } From e079378c28e1d72111a858e0a186a15ec9a361ce Mon Sep 17 00:00:00 2001 From: LoremFooBar Date: Thu, 28 Sep 2023 10:30:38 +0000 Subject: [PATCH 077/142] Translated using Weblate (Hebrew) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/he/ --- src/strings/he.json | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/strings/he.json b/src/strings/he.json index 28ed1c274..4ffb1d6e3 100644 --- a/src/strings/he.json +++ b/src/strings/he.json @@ -853,7 +853,7 @@ "EnableBlurHashHelp": "תמונות שעדיין נטענות יוצגו עם מציין מיקום ייחודי.", "Bwdif": "BWDIF", "ButtonCast": "שדר למכשיר", - "AllowTonemappingHelp": "מיפוי-טונים מאפשר המרה של וידאו מ-HDR ל-SDR תוך שמירה על פרטי וצבעי תמונה, החשובים לשימור מידע מהסצנה המקורית. כרגע עובד רק בקידוד קבצים של HDR10 או HLG. תכונה זו מצריכה הרצה של OpenCL או CUDA בהתאם.", + "AllowTonemappingHelp": "מיפוי-טונים מאפשר המרה של וידאו מ-HDR ל-SDR תוך שמירה על פרטי וצבעי תמונה, החשובים לשימור מידע מהסצנה המקורית. כרגע עובד רק בקידוד קבצים של HDR10, HLG ו-DoVi. תכונה זו מצריכה הרצה של OpenCL או CUDA בהתאם.", "Subtitle": "כתובית", "StopRecording": "הפסק הקלטה", "SortByValue": "מיין לפי {0}", @@ -1181,5 +1181,18 @@ "AllowSegmentDeletionHelp": "מחק אותות ישנים אחרי שהם נשלחו ללקוח. זה ימנע שמירת כל הקובץ המפוענח על הדיסק. עובד רק כאשר מצב חנק פעיל. כבה אפשרות זו אם הינך חווה בעיות בנגן.", "LabelThrottleDelaySeconds": "חנוק אחרי", "LabelSegmentKeepSeconds": "זמן שמירת אותות", - "LabelSegmentKeepSecondsHelp": "זמן בשניות שבהן הקטעים צריכים להישאר לפני שהם נדרסים. צריך להיות גדול מ\"חנוק אחרי\". עובד רק כאשר מחיקת אות מאופשרת." + "LabelSegmentKeepSecondsHelp": "זמן בשניות שבהן הקטעים צריכים להישאר לפני שהם נדרסים. צריך להיות גדול מ\"חנוק אחרי\". עובד רק כאשר מחיקת אות מאופשרת.", + "GoHome": "מעבר למסך הבית", + "LabelEnableDlnaClientDiscoveryIntervalHelp": "קובע את משך הזמן בשניות בין שני חיפושי SSDP.", + "LabelDropSubtitleHere": "גרור קובץ כתוביות לכאן, או לחץ לבחירת קובץ.", + "LabelEmbedAlbumArtDidlHelp": "שיטה זו למציאת תמונת אלבום עדיפה עבור מכשירים מסויימים. במכשירים אחרים ייתכן שהניגון ייכשל.", + "GridView": "תצוגת רשת", + "EnableAudioNormalizationHelp": "נירמול יגביר את עוצמת השמע כדי לשמר ממוצע רצוי (-18 דציבלים).", + "LabelEnableAudioVbr": "אפשר קידוד שמע בקצב משתנה (VBR)", + "LabelDummyChapterDuration": "פרק זמן", + "LabelEmbedAlbumArtDidl": "הטמע תמונת אלבום ב-DIDL", + "LabelEnableAudioVbrHelp": "קידוד בקצב משתנה מציע איכות טובה יותר בהשוואה לקצב ממוצע, אבל במקרים נדירים עלול לגרום לבעיות טעינה (buffering) ותאימות.", + "LabelEnableDlnaPlayToHelp": "מצא מכשירים ברשת שלך ואפשר שליטה מרחוק בהם.", + "LabelCreateHttpPortMapHelp": "אפשר מיפוי פורטים אוטומטי כדי לייצר כלל עבור תעבורת HTTP, בנוסף לתעבורת HTTPS.", + "LabelChapterImageResolutionHelp": "רזולוציה של תמונות הפרקים שיופקו. שינוי הערך לא ישפיע על פרקי דמה קיימים." } From 128605e3fcde13db9f62b2e46b97610c356d758b Mon Sep 17 00:00:00 2001 From: Bas Date: Thu, 28 Sep 2023 11:49:50 +0000 Subject: [PATCH 078/142] Translated using Weblate (Dutch) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/nl/ --- src/strings/nl.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/strings/nl.json b/src/strings/nl.json index 0ae9a0642..1a2f32fe4 100644 --- a/src/strings/nl.json +++ b/src/strings/nl.json @@ -1770,9 +1770,10 @@ "ListView": "Lijstweergave", "AiTranslated": "Vertaald door AI", "HeaderGuestCast": "Gastrollen", - "HearingImpairedShort": "Voor slechthorenden", + "HearingImpairedShort": "ODS", "GoHome": "Startpagina", "UnknownError": "Er is een onbekende fout opgetreden.", "BackdropScreensaver": "Schermbeveiliging met achtergronden", - "LogoScreensaver": "Schermbeveiliging met logo" + "LogoScreensaver": "Schermbeveiliging met logo", + "LabelIsHearingImpaired": "Voor slechthorenden (ODS)" } From cad0f1672e14a79427bdc64ee2f76c9787a2fdee Mon Sep 17 00:00:00 2001 From: Venson Date: Thu, 28 Sep 2023 21:24:59 +0300 Subject: [PATCH 079/142] Added github container definition --- .devcontainer/devcontainer.json | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .devcontainer/devcontainer.json diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 000000000..78b93eaf3 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,22 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node +{ + "name": "Node.js & TypeScript", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "image": "mcr.microsoft.com/devcontainers/typescript-node:1-20-bullseye", + + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + "forwardPorts": [80] + + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "yarn install", + + // Configure tool-specific properties. + // "customizations": {}, + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} From 4265e5075ea74d4647ca03f328a581b583f29e23 Mon Sep 17 00:00:00 2001 From: Venson Date: Thu, 28 Sep 2023 21:36:24 +0300 Subject: [PATCH 080/142] Changed used image --- .devcontainer/devcontainer.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 78b93eaf3..666be2b07 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,15 +1,15 @@ // For format details, see https://aka.ms/devcontainer.json. For config options, see the -// README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node +// README at: https://github.com/devcontainers/templates/tree/main/src/javascript-node { - "name": "Node.js & TypeScript", + "name": "Node.js", // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile - "image": "mcr.microsoft.com/devcontainers/typescript-node:1-20-bullseye", + "image": "mcr.microsoft.com/devcontainers/javascript-node:1-20-bullseye" // Features to add to the dev container. More info: https://containers.dev/features. // "features": {}, // Use 'forwardPorts' to make a list of ports inside the container available locally. - "forwardPorts": [80] + // "forwardPorts": [], // Use 'postCreateCommand' to run commands after the container is created. // "postCreateCommand": "yarn install", From 8bf82b5192be8f72cda9abfd9e8ecea393edecd6 Mon Sep 17 00:00:00 2001 From: Venson Date: Thu, 28 Sep 2023 22:11:46 +0300 Subject: [PATCH 081/142] Test vmn install 20 --- .devcontainer/devcontainer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 666be2b07..8a6442296 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -3,7 +3,7 @@ { "name": "Node.js", // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile - "image": "mcr.microsoft.com/devcontainers/javascript-node:1-20-bullseye" + "image": "mcr.microsoft.com/devcontainers/javascript-node:1-20-bullseye", // Features to add to the dev container. More info: https://containers.dev/features. // "features": {}, @@ -12,7 +12,7 @@ // "forwardPorts": [], // Use 'postCreateCommand' to run commands after the container is created. - // "postCreateCommand": "yarn install", + "postCreateCommand": "nvm install 20" // Configure tool-specific properties. // "customizations": {}, From 46ab95df31bea7d1a44e2036cb13e2d97c82b283 Mon Sep 17 00:00:00 2001 From: Venson Date: Thu, 28 Sep 2023 22:15:51 +0300 Subject: [PATCH 082/142] I hate this i hate this i hate this --- .devcontainer/devcontainer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 8a6442296..4f6611561 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -12,7 +12,8 @@ // "forwardPorts": [], // Use 'postCreateCommand' to run commands after the container is created. - "postCreateCommand": "nvm install 20" + //https://github.com/microsoft/vscode-dev-containers/issues/559 + "postCreateCommand": "source $NVM_DIR/nvm.sh && nvm install 20" // Configure tool-specific properties. // "customizations": {}, From cfb43807ad58af1514dfb65dc8c777442cb47fe6 Mon Sep 17 00:00:00 2001 From: blob03 Date: Thu, 28 Sep 2023 18:40:12 +0000 Subject: [PATCH 083/142] Translated using Weblate (French) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fr/ --- src/strings/fr.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/strings/fr.json b/src/strings/fr.json index edde92935..242cdd9d7 100644 --- a/src/strings/fr.json +++ b/src/strings/fr.json @@ -1762,14 +1762,14 @@ "LabelThrottleDelaySecondsHelp": "Durée en secondes après laquelle le débit du transcodage sera ajusté. Doit être suffisamment grande pour que le client puisse conserver une mémoire tampon saine. Ne fonctionne que si l'adaptation de la vitesse de transcodage est activée.", "LabelSegmentKeepSeconds": "Durée de conservation des segments", "LabelSegmentKeepSecondsHelp": "Durée en secondes de conservation des segments avant écrasement. La valeur doit être supérieure au délai d'ajustement. Ne fonctionne que si la suppression des segments est activée.", - "LabelBackdropScreensaverIntervalHelp": "Le temps en secondes entre différents fonds d'écran lors de l'utilisation du diaporama d'écrans de veille.", - "LabelBackdropScreensaverInterval": "Intervalle du diaporama d'écrans de veille", + "LabelBackdropScreensaverIntervalHelp": "Temps en secondes entre chaque fond d'écran de l'écran de veille Diaporama.", + "LabelBackdropScreensaverInterval": "Intervalle de l'écran de veille Diaporama", "AllowAv1Encoding": "Autoriser l'encodage au format AV1", "GoHome": "Retour à l'accueil", "ListView": "Vue en liste", "GridView": "Vue en grille", - "BackdropScreensaver": "Diaporama d'écrans de veille", - "LogoScreensaver": "Logo d'écran de veille", + "BackdropScreensaver": "Écran de veille Diaporama", + "LogoScreensaver": "Écran de veille Logo", "UnknownError": "Une erreur inconnue s’est produite.", "MachineTranslated": "Traduction automatique", "AiTranslated": "Traduction par IA", From 0e6c0bb253a37dff432dc8992cf61fef25c3106b Mon Sep 17 00:00:00 2001 From: queeup Date: Thu, 28 Sep 2023 17:20:28 +0000 Subject: [PATCH 084/142] Translated using Weblate (Turkish) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/tr/ --- src/strings/tr.json | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/strings/tr.json b/src/strings/tr.json index 8153695db..af792eead 100644 --- a/src/strings/tr.json +++ b/src/strings/tr.json @@ -908,7 +908,7 @@ "QuickConnectInvalidCode": "Geçersiz Hızlı Bağlantı kodu", "QuickConnectDescription": "Hızlı Bağlantı ile oturum açmak için, oturum açtığınız cihazda Hızlı Bağlan düğmesini seçin ve aşağıda görüntülenen kodu girin.", "QuickConnectDeactivated": "Giriş isteği onaylanamadan Hızlı Bağlantı devre dışı bırakıldı", - "QuickConnectAuthorizeSuccess": "Yetkili istek", + "QuickConnectAuthorizeSuccess": "Cihazınızın kimliğini başarıyla doğruladınız!", "QuickConnectAuthorizeFail": "Bilinmeyen Hızlı Bağlantı kodu", "QuickConnectAuthorizeCode": "Giriş yapmak için {0} kodunu girin", "QuickConnectActivationSuccessful": "Başarıyla etkinleştirildi", @@ -1726,7 +1726,7 @@ "MenuOpen": "Açık Menü", "LabelEnableAudioVbrHelp": "Değişken bit hızı, ortalama bit hızı oranına göre daha iyi kalite sunar, ancak bazı nadir durumlarda arabelleğe alma ve uyumluluk sorunlarına neden olabilir.", "LabelEnableLUFSScan": "LUFS taramasını etkinleştir", - "LabelEnableLUFSScanHelp": "Müzik için LUFS taramasını etkinleştirin (Bu daha uzun sürecek ve daha fazla kaynak gerektirecektir).", + "LabelEnableLUFSScanHelp": "İstemciler, parçalar arasında eşit ses yüksekliği elde etmek için ses oynatmayı normalleştirebilir. Bu, kütüphane taramalarını daha uzun hale getirecek ve daha fazla kaynak kullanacaktır.", "LabelDummyChapterDuration": "Aralık", "LabelChapterImageResolutionHelp": "Çıkarılan bölüm görüntülerinin çözünürlüğü. Bunu değiştirmenin mevcut sahte bölümler üzerinde hiçbir etkisi olmayacaktır.", "LabelParallelImageEncodingLimit": "Paralel görüntü kodlama sınırı", @@ -1755,5 +1755,18 @@ "LogLevel.None": "Hiçbiri", "LabelThrottleDelaySecondsHelp": "Kodek dönüştürmenin kısılacağı saniye cinsinden süre. İstemcinin sağlıklı bir arabellek tutması için yeterince büyük olmalıdır. Yalnızca kısıtlama etkinleştirildiğinde çalışır.", "LabelBackdropScreensaverInterval": "Arka Plan Ekran Koruyucu Arlığı", - "LabelBackdropScreensaverIntervalHelp": "Arka plan ekran koruyucusu kullanımdayken farklı arka planlar arasında geçen saniye cinsinden süre." + "LabelBackdropScreensaverIntervalHelp": "Arka plan ekran koruyucusu kullanımdayken farklı arka planlar arasında geçen saniye cinsinden süre.", + "AllowAv1Encoding": "AV1 biçiminde kodlamaya izin ver", + "BackdropScreensaver": "Arka Plan Ekran Koruyucu", + "LogoScreensaver": "Logo Ekran Koruyucu", + "LabelIsHearingImpaired": "İşitme engelliler için (SDH)", + "HeaderGuestCast": "Konuk Oyuncular", + "GoHome": "Ana Sayfaya Git", + "UnknownError": "Bilinmeyen bir hata oluştu.", + "GridView": "Izgara Görünümü", + "ListView": "Liste Görünümü", + "AiTranslated": "AI Çevirisi", + "MachineTranslated": "Makine Çevirisi", + "ForeignPartsOnly": "Gömülü/Yalnız yabancı parçalar", + "HearingImpairedShort": "HI/SDH" } From 2cc4b2d7d3df090eb2d9de0bb6411e6ae72254eb Mon Sep 17 00:00:00 2001 From: stanol Date: Thu, 28 Sep 2023 17:53:24 +0000 Subject: [PATCH 085/142] Translated using Weblate (Ukrainian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/uk/ --- src/strings/uk.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/strings/uk.json b/src/strings/uk.json index 7d25f5257..583d7f09b 100644 --- a/src/strings/uk.json +++ b/src/strings/uk.json @@ -1770,5 +1770,8 @@ "ListView": "У вигляді списку", "AiTranslated": "Перекладено за допомогою ШІ", "UnknownError": "Виникла невідома помилка.", - "GoHome": "На головну" + "GoHome": "На головну", + "BackdropScreensaver": "Фонова заставка", + "LogoScreensaver": "Заставка з логотипом", + "LabelIsHearingImpaired": "Для людей з вадами слуху (SDH)" } From 1ade31d499f950ad43209b659e8e94acf14f0bc6 Mon Sep 17 00:00:00 2001 From: hoanghuy309 Date: Thu, 28 Sep 2023 15:40:58 +0000 Subject: [PATCH 086/142] Translated using Weblate (Vietnamese) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/vi/ --- src/strings/vi.json | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/strings/vi.json b/src/strings/vi.json index 54afe66d4..a53d197c4 100644 --- a/src/strings/vi.json +++ b/src/strings/vi.json @@ -814,7 +814,7 @@ "LabelSeasonNumber": "Số phần", "EnableFasterAnimationsHelp": "Sử dụng hoạt ảnh và chuyển tiếp nhanh hơn.", "EnableFasterAnimations": "Hoạt ảnh nhanh hơn", - "LabelScreensaver": "Bảo vệ màn hình", + "LabelScreensaver": "Màn hình chờ", "LabelRuntimeMinutes": "Thời lượng", "LabelRequireHttpsHelp": "Nếu chọn, máy chủ sẽ tự động chuyển hướng tất cả các yêu cầu qua HTTP sang HTTPS. Điều này không ảnh hưởng nếu máy chủ không nghe trên HTTPS.", "LabelRequireHttps": "Yêu cầu HTTPS", @@ -1221,7 +1221,7 @@ "QuickConnectDescription": "Để đăng nhập với Kết Nối Nhanh, hãy chọn nút 'Kết Nối Nhanh' trên thiết bị bạn đang đăng nhập và nhập mã hiển thị bên dưới.", "QuickConnectDeactivated": "Kết Nối Nhanh đã bị vô hiệu hóa trước khi yêu cầu đăng nhập được chấp nhận", "QuickConnectAuthorizeFail": "Mã Kết Nối Nhanh không xác định", - "QuickConnectAuthorizeSuccess": "Yêu cầu đã được cho phép", + "QuickConnectAuthorizeSuccess": "Bạn đã xác thực thành công thiết bị của mình!", "QuickConnectAuthorizeCode": "Nhập mã {0} để đăng nhập", "QuickConnectActivationSuccessful": "Kích hoạt thành công", "QuickConnect": "Kết Nối Nhanh", @@ -1724,7 +1724,7 @@ "Notifications": "Thông báo", "NotificationsMovedMessage": "Chức năng thông báo đã chuyển sang plugin Webhook.", "LabelEnableLUFSScan": "Bật tính năng quét LUFS", - "LabelEnableLUFSScanHelp": "Bật tính năng quét LUFS để tìm nhạc (Việc này sẽ mất nhiều thời gian hơn và tốn nhiều tài nguyên hơn).", + "LabelEnableLUFSScanHelp": "Khách hàng có thể bình thường hóa việc phát lại âm thanh để có được âm lượng như nhau trên các bản nhạc. Điều này sẽ khiến việc quét thư viện lâu hơn và tốn nhiều tài nguyên hơn.", "PasswordRequiredForAdmin": "Cần có mật khẩu cho tài khoản quản trị viên.", "LabelSyncPlayNoGroups": "Không có nhóm nào", "LabelDate": "Ngày", @@ -1747,6 +1747,15 @@ "AllowSegmentDeletion": "Xóa phân đoạn", "LabelSegmentKeepSeconds": "Thời gian giữ phân đoạn", "HeaderEpisodesStatus": "Trạng Thái Tập", - "LabelBackdropScreensaverInterval": "Thời Gian Phông Nền Màn Hình Bảo Vệ", - "LabelBackdropScreensaverIntervalHelp": "Thời gian tính bằng giây giữa các phông nền khác nhau khi sử dụng trình bảo vệ màn hình phông nền." + "LabelBackdropScreensaverInterval": "Thời Gian Phông Nền Màn Hình Chờ", + "LabelBackdropScreensaverIntervalHelp": "Thời gian tính bằng giây giữa các phông nền khác nhau khi dùng phông nền màn hình chờ.", + "LogoScreensaver": "Biểu Tượng Màn Hình Chờ", + "LabelIsHearingImpaired": "Dành cho người khiếm thính (SDH)", + "AllowAv1Encoding": "Cho phép mã hóa ở định dạng AV1", + "BackdropScreensaver": "Phông Nền Màn Hình Chờ", + "UnknownError": "Đã xảy ra lỗi không xác định.", + "HeaderGuestCast": "Ngôi Sao Khách Mời", + "GoHome": "Về Trang Chủ", + "AiTranslated": "Được AI Dịch", + "MachineTranslated": "Được Máy Dịch" } From 4fa03c6a464c1b515100f53ca03da27666a6b542 Mon Sep 17 00:00:00 2001 From: grafixeyehero Date: Fri, 29 Sep 2023 02:59:04 +0300 Subject: [PATCH 087/142] Fix: musicartists page mode --- src/controllers/music/musicartists.js | 8 ++++---- src/controllers/music/musicrecommended.js | 18 +++++++++++------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/controllers/music/musicartists.js b/src/controllers/music/musicartists.js index 2d89fd807..c42bda4c7 100644 --- a/src/controllers/music/musicartists.js +++ b/src/controllers/music/musicartists.js @@ -9,7 +9,7 @@ import Events from '../../utils/events.ts'; import '../../elements/emby-itemscontainer/emby-itemscontainer'; -export default function (view, params, tabContent) { +export default function (view, params, tabContent, options) { function getPageData() { const key = getSavedQueryKey(); let pageData = data[key]; @@ -45,7 +45,7 @@ export default function (view, params, tabContent) { } function getSavedQueryKey() { - return `${params.topParentId}-${this.mode}`; + return `${params.topParentId}-${options.mode}`; } const onViewStyleChange = () => { @@ -67,7 +67,7 @@ export default function (view, params, tabContent) { loading.show(); isLoading = true; const query = getQuery(); - const promise = this.mode == 'albumartists' ? + const promise = options.mode == 'albumartists' ? ApiClient.getAlbumArtists(ApiClient.getCurrentUserId(), query) : ApiClient.getArtists(ApiClient.getCurrentUserId(), query); promise.then((result) => { @@ -169,7 +169,7 @@ export default function (view, params, tabContent) { import('../../components/filterdialog/filterdialog').then(({ default: FilterDialog }) => { const filterDialog = new FilterDialog({ query: getQuery(), - mode: this.mode, + mode: options.mode, serverId: ApiClient.serverId() }); Events.on(filterDialog, 'filterchange', function () { diff --git a/src/controllers/music/musicrecommended.js b/src/controllers/music/musicrecommended.js index dc32aca6f..b66ae0ff7 100644 --- a/src/controllers/music/musicrecommended.js +++ b/src/controllers/music/musicrecommended.js @@ -260,6 +260,14 @@ export default function (view, params) { mainTabsManager.setTabs(view, currentTabIndex, getTabs, getTabContainers, onBeforeTabChange, onTabChange); } + function getMode(index) { + if (index === 2) { + return 'albumartists'; + } else if (index === 3) { + return 'artists'; + } + } + const getTabController = (page, index, callback) => { let depends; @@ -306,13 +314,9 @@ export default function (view, params) { if (index === 1) { controller = this; } else { - controller = new ControllerFactory(view, params, tabContent); - } - - if (index == 2) { - controller.mode = 'albumartists'; - } else if (index == 3) { - controller.mode = 'artists'; + controller = new ControllerFactory(view, params, tabContent, { + mode: getMode(index) + }); } tabControllers[index] = controller; From 459681a5128ea6f6c7137513f74eb60052776b74 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Fri, 29 Sep 2023 01:07:47 -0400 Subject: [PATCH 088/142] Move stable app to loadable --- src/RootApp.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/RootApp.tsx b/src/RootApp.tsx index 5ee5ffab5..0d66c15ad 100644 --- a/src/RootApp.tsx +++ b/src/RootApp.tsx @@ -5,7 +5,6 @@ import React from 'react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; -import StableApp from 'apps/stable/App'; import AppHeader from 'components/AppHeader'; import Backdrop from 'components/Backdrop'; import { HistoryRouter } from 'components/router/HistoryRouter'; @@ -14,6 +13,7 @@ import { WebConfigProvider } from 'hooks/useWebConfig'; import theme from 'themes/theme'; const ExperimentalApp = loadable(() => import('./apps/experimental/App')); +const StableApp = loadable(() => import('./apps/stable/App')); const queryClient = new QueryClient(); From 08ad8e56e100e240edaeb3cd32f209f7ce7efb20 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Fri, 29 Sep 2023 01:30:57 -0400 Subject: [PATCH 089/142] Remove caching of main animated pages element --- src/components/viewContainer.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/components/viewContainer.js b/src/components/viewContainer.js index c2156a7d5..b5509f8b5 100644 --- a/src/components/viewContainer.js +++ b/src/components/viewContainer.js @@ -3,11 +3,7 @@ import './viewManager/viewContainer.scss'; import Dashboard from '../utils/dashboard'; const getMainAnimatedPages = () => { - if (!mainAnimatedPages) { - mainAnimatedPages = document.querySelector('.mainAnimatedPages'); - } - - return mainAnimatedPages; + return document.querySelector('.mainAnimatedPages'); }; function setControllerClass(view, options) { @@ -61,7 +57,9 @@ export function loadView(options) { view.classList.add('mainAnimatedPage'); - if (!getMainAnimatedPages()) { + const mainAnimatedPages = getMainAnimatedPages(); + + if (!mainAnimatedPages) { console.warn('[viewContainer] main animated pages element is not present'); return; } @@ -187,6 +185,7 @@ export function setOnBeforeChange(fn) { } export function tryRestoreView(options) { + console.debug('[viewContainer] tryRestoreView', options); const url = options.url; const index = currentUrls.indexOf(url); @@ -232,14 +231,15 @@ function triggerDestroy(view) { } export function reset() { + console.debug('[viewContainer] resetting view cache'); allPages = []; currentUrls = []; + const mainAnimatedPages = getMainAnimatedPages(); if (mainAnimatedPages) mainAnimatedPages.innerHTML = ''; selectedPageIndex = -1; } let onBeforeChange; -let mainAnimatedPages; let allPages = []; let currentUrls = []; const pageContainerCount = 3; @@ -248,8 +248,8 @@ reset(); getMainAnimatedPages()?.classList.remove('hide'); export default { - loadView: loadView, - tryRestoreView: tryRestoreView, - reset: reset, - setOnBeforeChange: setOnBeforeChange + loadView, + tryRestoreView, + reset, + setOnBeforeChange }; From 5ba383787f37c17d5ded42ac6831599235cf433b Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Fri, 29 Sep 2023 09:44:21 -0400 Subject: [PATCH 090/142] Relax node and npm version requirements --- package-lock.json | 4 ++-- package.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5297c56d9..5584abb5f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -125,8 +125,8 @@ "worker-loader": "3.0.8" }, "engines": { - "node": ">=20.7.0", - "npm": ">=10.1.0", + "node": ">=20.0.0", + "npm": ">=10.0.0", "yarn": "YARN NO LONGER USED - use npm instead." } }, diff --git a/package.json b/package.json index 6eb78d111..460b59cbc 100644 --- a/package.json +++ b/package.json @@ -150,8 +150,8 @@ "stylelint:scss": "stylelint --config=\".stylelintrc.scss.json\" \"src/**/*.scss\"" }, "engines": { - "node": ">=20.7.0", - "npm": ">=10.1.0", + "node": ">=20.0.0", + "npm": ">=10.0.0", "yarn": "YARN NO LONGER USED - use npm instead." } } From 51bd9595ce4d298b244a8f82fc1f99d9768f0ffc Mon Sep 17 00:00:00 2001 From: LoremFooBar Date: Fri, 29 Sep 2023 14:40:06 +0000 Subject: [PATCH 091/142] Translated using Weblate (Hebrew) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/he/ --- src/strings/he.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/strings/he.json b/src/strings/he.json index 4ffb1d6e3..089bbe949 100644 --- a/src/strings/he.json +++ b/src/strings/he.json @@ -1194,5 +1194,13 @@ "LabelEnableAudioVbrHelp": "קידוד בקצב משתנה מציע איכות טובה יותר בהשוואה לקצב ממוצע, אבל במקרים נדירים עלול לגרום לבעיות טעינה (buffering) ותאימות.", "LabelEnableDlnaPlayToHelp": "מצא מכשירים ברשת שלך ואפשר שליטה מרחוק בהם.", "LabelCreateHttpPortMapHelp": "אפשר מיפוי פורטים אוטומטי כדי לייצר כלל עבור תעבורת HTTP, בנוסף לתעבורת HTTPS.", - "LabelChapterImageResolutionHelp": "רזולוציה של תמונות הפרקים שיופקו. שינוי הערך לא ישפיע על פרקי דמה קיימים." + "LabelChapterImageResolutionHelp": "רזולוציה של תמונות הפרקים שיופקו. שינוי הערך לא ישפיע על פרקי דמה קיימים.", + "LabelKodiMetadataEnablePathSubstitution": "אפשר החלפות בשדה path", + "LabelH264Crf": "CRF של קידוד H.264", + "LabelHardwareAccelerationTypeHelp": "האצת חומרה דורשת הגדרות נוספות.", + "LabelH265Crf": "CRF של קידוד H.265", + "LabelKodiMetadataEnableExtraThumbsHelp": "תמונות שהורדו יכולות להישמר לשדות extrafanart ו-extrathumbs בו זמנית לצורך התאמה מירבית לסקינים של קודי.", + "LabelKodiMetadataEnableExtraThumbs": "העתק extrafanart לשדה extrathumbs", + "LabelGroupMoviesIntoCollectionsHelp": "כל הסרטים באוסף יופיעו בתור פריט מקובץ אחד ברשימות סרטים.", + "LabelHomeScreenSectionValue": "איזור {0} בעמוד הבית" } From c0d14715fb07d430bca57ed880b6163ba2f391d7 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Wed, 20 Sep 2023 00:02:26 -0400 Subject: [PATCH 092/142] Migrate dashboard to separate app --- src/RootApp.tsx | 3 + src/apps/dashboard/App.tsx | 47 ++++++++++++++ src/apps/dashboard/AppLayout.tsx | 30 +++++++++ src/apps/dashboard/routes/_asyncRoutes.ts | 12 ++++ .../routes/_legacyRoutes.ts} | 62 +++++++++---------- src/apps/dashboard/routes/_redirects.ts | 51 +++++++++++++++ src/apps/experimental/App.tsx | 20 +++--- .../components/drawers/AppDrawer.tsx | 8 +-- .../experimental/routes/asyncRoutes/admin.ts | 12 ---- .../experimental/routes/asyncRoutes/index.ts | 1 - .../experimental/routes/legacyRoutes/index.ts | 1 - src/apps/stable/App.tsx | 20 +++--- src/apps/stable/routes/_redirects.ts | 3 +- src/apps/stable/routes/asyncRoutes/index.ts | 1 - src/apps/stable/routes/legacyRoutes/index.ts | 1 - 15 files changed, 190 insertions(+), 82 deletions(-) create mode 100644 src/apps/dashboard/App.tsx create mode 100644 src/apps/dashboard/AppLayout.tsx create mode 100644 src/apps/dashboard/routes/_asyncRoutes.ts rename src/apps/{experimental/routes/legacyRoutes/admin.ts => dashboard/routes/_legacyRoutes.ts} (76%) create mode 100644 src/apps/dashboard/routes/_redirects.ts delete mode 100644 src/apps/experimental/routes/asyncRoutes/admin.ts diff --git a/src/RootApp.tsx b/src/RootApp.tsx index 0d66c15ad..956e57b1b 100644 --- a/src/RootApp.tsx +++ b/src/RootApp.tsx @@ -12,6 +12,7 @@ import { ApiProvider } from 'hooks/useApi'; import { WebConfigProvider } from 'hooks/useWebConfig'; import theme from 'themes/theme'; +const DashboardApp = loadable(() => import('./apps/dashboard/App')); const ExperimentalApp = loadable(() => import('./apps/experimental/App')); const StableApp = loadable(() => import('./apps/stable/App')); @@ -31,6 +32,8 @@ const RootAppLayout = () => { : } + + ); }; diff --git a/src/apps/dashboard/App.tsx b/src/apps/dashboard/App.tsx new file mode 100644 index 000000000..a5f09d97a --- /dev/null +++ b/src/apps/dashboard/App.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { Route, Routes } from 'react-router-dom'; + +import ConnectionRequired from 'components/ConnectionRequired'; +import { toViewManagerPageRoute } from 'components/router/LegacyRoute'; +import { toAsyncPageRoute } from 'components/router/AsyncRoute'; +import { toRedirectRoute } from 'components/router/Redirect'; +import ServerContentPage from 'components/ServerContentPage'; + +import { REDIRECTS } from './routes/_redirects'; +import { ASYNC_ADMIN_ROUTES } from './routes/_asyncRoutes'; +import { LEGACY_ADMIN_ROUTES } from './routes/_legacyRoutes'; +import AppLayout from './AppLayout'; + +const DashboardApp = () => ( + + }> + }> + + {ASYNC_ADMIN_ROUTES.map(toAsyncPageRoute)} + {LEGACY_ADMIN_ROUTES.map(toViewManagerPageRoute)} + + + {/* TODO: Should the metadata manager be a separate app? */} + {toViewManagerPageRoute({ + path: 'metadata', + pageProps: { + controller: 'edititemmetadata', + view: 'edititemmetadata.html' + } + })} + + + } /> + + {/* Suppress warnings for unhandled routes */} + + + + + {/* Redirects for old paths */} + {REDIRECTS.map(toRedirectRoute)} + +); + +export default DashboardApp; diff --git a/src/apps/dashboard/AppLayout.tsx b/src/apps/dashboard/AppLayout.tsx new file mode 100644 index 000000000..1e427d3c6 --- /dev/null +++ b/src/apps/dashboard/AppLayout.tsx @@ -0,0 +1,30 @@ +import { ThemeProvider } from '@mui/material/styles'; +import React from 'react'; +import { Outlet } from 'react-router-dom'; + +import AppHeader from 'components/AppHeader'; +import Backdrop from 'components/Backdrop'; +import theme from 'themes/theme'; + +const AppLayout = () => { + return ( + + + +
+ {/* + * TODO: These components are not used, but views interact with them directly so the need to be + * present in the dom. We add them in a hidden element to prevent errors. + */} + +
+ +
+
+ +
+ + ); +}; + +export default AppLayout; diff --git a/src/apps/dashboard/routes/_asyncRoutes.ts b/src/apps/dashboard/routes/_asyncRoutes.ts new file mode 100644 index 000000000..4dc364beb --- /dev/null +++ b/src/apps/dashboard/routes/_asyncRoutes.ts @@ -0,0 +1,12 @@ +import type { AsyncRoute } from 'components/router/AsyncRoute'; + +export const ASYNC_ADMIN_ROUTES: AsyncRoute[] = [ + { path: 'activity', page: 'dashboard/activity' }, + { path: 'notifications', page: 'dashboard/notifications' }, + { path: 'users/new', page: 'user/usernew' }, + { path: 'users', page: 'user/userprofiles' }, + { path: 'users/profile', page: 'user/useredit' }, + { path: 'users/access', page: 'user/userlibraryaccess' }, + { path: 'users/parentalcontrol', page: 'user/userparentalcontrol' }, + { path: 'users/password', page: 'user/userpassword' } +]; diff --git a/src/apps/experimental/routes/legacyRoutes/admin.ts b/src/apps/dashboard/routes/_legacyRoutes.ts similarity index 76% rename from src/apps/experimental/routes/legacyRoutes/admin.ts rename to src/apps/dashboard/routes/_legacyRoutes.ts index 35a397644..efdd543a4 100644 --- a/src/apps/experimental/routes/legacyRoutes/admin.ts +++ b/src/apps/dashboard/routes/_legacyRoutes.ts @@ -1,170 +1,164 @@ -import { LegacyRoute } from '../../../../components/router/LegacyRoute'; +import type { LegacyRoute } from 'components/router/LegacyRoute'; export const LEGACY_ADMIN_ROUTES: LegacyRoute[] = [ { - path: 'dashboard.html', + path: '/dashboard', pageProps: { controller: 'dashboard/dashboard', view: 'dashboard/dashboard.html' } }, { - path: 'dashboardgeneral.html', + path: 'settings', pageProps: { controller: 'dashboard/general', view: 'dashboard/general.html' } }, { - path: 'networking.html', + path: 'networking', pageProps: { controller: 'dashboard/networking', view: 'dashboard/networking.html' } }, { - path: 'devices.html', + path: 'devices', pageProps: { controller: 'dashboard/devices/devices', view: 'dashboard/devices/devices.html' } }, { - path: 'device.html', + path: 'devices/edit', pageProps: { controller: 'dashboard/devices/device', view: 'dashboard/devices/device.html' } }, { - path: 'dlnaprofile.html', + path: 'dlna/profiles/edit', pageProps: { controller: 'dashboard/dlna/profile', view: 'dashboard/dlna/profile.html' } }, { - path: 'dlnaprofiles.html', + path: 'dlna/profiles', pageProps: { controller: 'dashboard/dlna/profiles', view: 'dashboard/dlna/profiles.html' } }, { - path: 'dlnasettings.html', + path: 'dlna', pageProps: { controller: 'dashboard/dlna/settings', view: 'dashboard/dlna/settings.html' } }, { - path: 'addplugin.html', + path: 'plugins/add', pageProps: { controller: 'dashboard/plugins/add/index', view: 'dashboard/plugins/add/index.html' } }, { - path: 'library.html', + path: 'libraries', pageProps: { controller: 'dashboard/library', view: 'dashboard/library.html' } }, { - path: 'librarydisplay.html', + path: 'libraries/display', pageProps: { controller: 'dashboard/librarydisplay', view: 'dashboard/librarydisplay.html' } }, { - path: 'edititemmetadata.html', - pageProps: { - controller: 'edititemmetadata', - view: 'edititemmetadata.html' - } - }, { - path: 'encodingsettings.html', + path: 'playback/transcoding', pageProps: { controller: 'dashboard/encodingsettings', view: 'dashboard/encodingsettings.html' } }, { - path: 'log.html', + path: 'logs', pageProps: { controller: 'dashboard/logs', view: 'dashboard/logs.html' } }, { - path: 'metadataimages.html', + path: 'libraries/metadata', pageProps: { controller: 'dashboard/metadataImages', view: 'dashboard/metadataimages.html' } }, { - path: 'metadatanfo.html', + path: 'libraries/nfo', pageProps: { controller: 'dashboard/metadatanfo', view: 'dashboard/metadatanfo.html' } }, { - path: 'playbackconfiguration.html', + path: 'playback/resume', pageProps: { controller: 'dashboard/playback', view: 'dashboard/playback.html' } }, { - path: 'availableplugins.html', + path: 'plugins/catalog', pageProps: { controller: 'dashboard/plugins/available/index', view: 'dashboard/plugins/available/index.html' } }, { - path: 'repositories.html', + path: 'plugins/repositories', pageProps: { controller: 'dashboard/plugins/repositories/index', view: 'dashboard/plugins/repositories/index.html' } }, { - path: 'livetvguideprovider.html', + path: 'livetv/guide', pageProps: { controller: 'livetvguideprovider', view: 'livetvguideprovider.html' } }, { - path: 'livetvsettings.html', + path: 'recordings', pageProps: { controller: 'livetvsettings', view: 'livetvsettings.html' } }, { - path: 'livetvstatus.html', + path: 'livetv', pageProps: { controller: 'livetvstatus', view: 'livetvstatus.html' } }, { - path: 'livetvtuner.html', + path: 'livetv/tuner', pageProps: { controller: 'livetvtuner', view: 'livetvtuner.html' } }, { - path: 'installedplugins.html', + path: 'plugins', pageProps: { controller: 'dashboard/plugins/installed/index', view: 'dashboard/plugins/installed/index.html' } }, { - path: 'scheduledtask.html', + path: 'tasks/edit', pageProps: { controller: 'dashboard/scheduledtasks/scheduledtask', view: 'dashboard/scheduledtasks/scheduledtask.html' } }, { - path: 'scheduledtasks.html', + path: 'tasks', pageProps: { controller: 'dashboard/scheduledtasks/scheduledtasks', view: 'dashboard/scheduledtasks/scheduledtasks.html' } }, { - path: 'apikeys.html', + path: 'keys', pageProps: { controller: 'dashboard/apikeys', view: 'dashboard/apikeys.html' } }, { - path: 'streamingsettings.html', + path: 'playback/streaming', pageProps: { view: 'dashboard/streaming.html', controller: 'dashboard/streaming' diff --git a/src/apps/dashboard/routes/_redirects.ts b/src/apps/dashboard/routes/_redirects.ts new file mode 100644 index 000000000..a2786fe53 --- /dev/null +++ b/src/apps/dashboard/routes/_redirects.ts @@ -0,0 +1,51 @@ +import type { Redirect } from 'components/router/Redirect'; + +export const REDIRECTS: Redirect[] = [ + // FIXME: URL params are not included in redirects + { from: 'addplugin.html', to: '/dashboard/plugins/add' }, + { from: 'apikeys.html', to: '/dashboard/keys' }, + { from: 'availableplugins.html', to: '/dashboard/plugins/catalog' }, + { from: 'dashboard.html', to: '/dashboard' }, + { from: 'dashboardgeneral.html', to: '/dashboard/settings' }, + // FIXME: URL params are not included in redirects + { from: 'device.html', to: '/dashboard/devices/edit' }, + { from: 'devices.html', to: '/dashboard/devices' }, + // FIXME: URL params are not included in redirects + { from: 'dlnaprofile.html', to: '/dashboard/dlna/profiles/edit' }, + { from: 'dlnaprofiles.html', to: '/dashboard/dlna/profiles' }, + { from: 'dlnasettings.html', to: '/dashboard/dlna' }, + { from: 'edititemmetadata.html', to: '/metadata' }, + { from: 'encodingsettings.html', to: '/dashboard/playback/transcoding' }, + { from: 'installedplugins.html', to: '/dashboard/plugins' }, + { from: 'library.html', to: '/dashboard/libraries' }, + { from: 'librarydisplay.html', to: '/dashboard/libraries/display' }, + // FIXME: URL params are not included in redirects + { from: 'livetvguideprovider.html', to: '/dashboard/livetv/guide' }, + { from: 'livetvsettings.html', to: '/dashboard/recordings' }, + { from: 'livetvstatus.html', to: '/dashboard/livetv' }, + // FIXME: URL params are not included in redirects + { from: 'livetvtuner.html', to: '/dashboard/livetv/tuner' }, + { from: 'log.html', to: '/dashboard/logs' }, + { from: 'metadataimages.html', to: '/dashboard/libraries/metadata' }, + { from: 'metadatanfo.html', to: '/dashboard/libraries/nfo' }, + { from: 'networking.html', to: '/dashboard/networking' }, + { from: 'notificationsettings.html', to: '/dashboard/notifications' }, + { from: 'playbackconfiguration.html', to: '/dashboard/playback/resume' }, + { from: 'quickConnect.html', to: '/dashboard/quickconnect' }, + { from: 'repositories.html', to: '/dashboard/plugins/repositories' }, + // FIXME: URL params are not included in redirects + { from: 'scheduledtask.html', to: '/dashboard/tasks/edit' }, + { from: 'scheduledtasks.html', to: '/dashboard/tasks' }, + { from: 'serveractivity.html', to: '/dashboard/activity' }, + { from: 'streamingsettings.html', to: '/dashboard/playback/streaming' }, + { from: 'usernew.html', to: '/dashboard/users/add' }, + { from: 'userprofiles.html', to: '/dashboard/users' }, + // FIXME: URL params are not included in redirects + { from: 'useredit.html', to: '/dashboard/users/profile' }, + // FIXME: URL params are not included in redirects + { from: 'userlibraryaccess.html', to: '/dashboard/users/access' }, + // FIXME: URL params are not included in redirects + { from: 'userparentalcontrol.html', to: '/dashboard/users/parentalcontrol' }, + // FIXME: URL params are not included in redirects + { from: 'userpassword.html', to: '/dashboard/users/password' } +]; diff --git a/src/apps/experimental/App.tsx b/src/apps/experimental/App.tsx index 44c6d24b2..a4f67e795 100644 --- a/src/apps/experimental/App.tsx +++ b/src/apps/experimental/App.tsx @@ -3,14 +3,13 @@ import { Navigate, Route, Routes } from 'react-router-dom'; import { REDIRECTS } from 'apps/stable/routes/_redirects'; import ConnectionRequired from 'components/ConnectionRequired'; -import ServerContentPage from 'components/ServerContentPage'; import { toAsyncPageRoute } from 'components/router/AsyncRoute'; import { toViewManagerPageRoute } from 'components/router/LegacyRoute'; import { toRedirectRoute } from 'components/router/Redirect'; import AppLayout from './AppLayout'; -import { ASYNC_ADMIN_ROUTES, ASYNC_USER_ROUTES } from './routes/asyncRoutes'; -import { LEGACY_ADMIN_ROUTES, LEGACY_PUBLIC_ROUTES, LEGACY_USER_ROUTES } from './routes/legacyRoutes'; +import { ASYNC_USER_ROUTES } from './routes/asyncRoutes'; +import { LEGACY_PUBLIC_ROUTES, LEGACY_USER_ROUTES } from './routes/legacyRoutes'; const ExperimentalApp = () => { return ( @@ -22,16 +21,6 @@ const ExperimentalApp = () => { {LEGACY_USER_ROUTES.map(toViewManagerPageRoute)} - {/* Admin routes */} - }> - {ASYNC_ADMIN_ROUTES.map(toAsyncPageRoute)} - {LEGACY_ADMIN_ROUTES.map(toViewManagerPageRoute)} - - - } /> - - {/* Public routes */} }> } /> @@ -42,6 +31,11 @@ const ExperimentalApp = () => { {/* Redirects for old paths */} {REDIRECTS.map(toRedirectRoute)} + + {/* Ignore dashboard routes */} + + + ); }; diff --git a/src/apps/experimental/components/drawers/AppDrawer.tsx b/src/apps/experimental/components/drawers/AppDrawer.tsx index c414e6ba7..06d8a4917 100644 --- a/src/apps/experimental/components/drawers/AppDrawer.tsx +++ b/src/apps/experimental/components/drawers/AppDrawer.tsx @@ -3,8 +3,8 @@ import { Route, Routes, useLocation } from 'react-router-dom'; import ResponsiveDrawer, { ResponsiveDrawerProps } from 'components/ResponsiveDrawer'; -import { ASYNC_ADMIN_ROUTES, ASYNC_USER_ROUTES } from '../../routes/asyncRoutes'; -import { LEGACY_ADMIN_ROUTES, LEGACY_USER_ROUTES } from '../../routes/legacyRoutes'; +import { ASYNC_USER_ROUTES } from '../../routes/asyncRoutes'; +import { LEGACY_USER_ROUTES } from '../../routes/legacyRoutes'; import AdvancedDrawerSection from './dashboard/AdvancedDrawerSection'; import DevicesDrawerSection from './dashboard/DevicesDrawerSection'; @@ -27,8 +27,8 @@ const MAIN_DRAWER_ROUTES = [ ].filter(route => !DRAWERLESS_ROUTES.includes(route.path)); const ADMIN_DRAWER_ROUTES = [ - ...ASYNC_ADMIN_ROUTES, - ...LEGACY_ADMIN_ROUTES, + // ...ASYNC_ADMIN_ROUTES, + // ...LEGACY_ADMIN_ROUTES, { path: '/configurationpage' } // Plugin configuration page ].filter(route => !DRAWERLESS_ROUTES.includes(route.path)); diff --git a/src/apps/experimental/routes/asyncRoutes/admin.ts b/src/apps/experimental/routes/asyncRoutes/admin.ts deleted file mode 100644 index 7e8c0eca1..000000000 --- a/src/apps/experimental/routes/asyncRoutes/admin.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { AsyncRoute, AsyncRouteType } from 'components/router/AsyncRoute'; - -export const ASYNC_ADMIN_ROUTES: AsyncRoute[] = [ - { path: 'dashboard/activity', page: 'dashboard/activity', type: AsyncRouteType.Experimental }, - { path: 'notificationsettings.html', page: 'dashboard/notifications' }, - { path: 'usernew.html', page: 'user/usernew' }, - { path: 'userprofiles.html', page: 'user/userprofiles' }, - { path: 'useredit.html', page: 'user/useredit' }, - { path: 'userlibraryaccess.html', page: 'user/userlibraryaccess' }, - { path: 'userparentalcontrol.html', page: 'user/userparentalcontrol' }, - { path: 'userpassword.html', page: 'user/userpassword' } -]; diff --git a/src/apps/experimental/routes/asyncRoutes/index.ts b/src/apps/experimental/routes/asyncRoutes/index.ts index 9dd4fb3c9..e5abc8565 100644 --- a/src/apps/experimental/routes/asyncRoutes/index.ts +++ b/src/apps/experimental/routes/asyncRoutes/index.ts @@ -1,2 +1 @@ -export * from './admin'; export * from './user'; diff --git a/src/apps/experimental/routes/legacyRoutes/index.ts b/src/apps/experimental/routes/legacyRoutes/index.ts index 2931c568e..bc46c94c5 100644 --- a/src/apps/experimental/routes/legacyRoutes/index.ts +++ b/src/apps/experimental/routes/legacyRoutes/index.ts @@ -1,3 +1,2 @@ -export * from './admin'; export * from './public'; export * from './user'; diff --git a/src/apps/stable/App.tsx b/src/apps/stable/App.tsx index 8285cbc9e..de6cdddb9 100644 --- a/src/apps/stable/App.tsx +++ b/src/apps/stable/App.tsx @@ -2,13 +2,12 @@ import React from 'react'; import { Navigate, Outlet, Route, Routes } from 'react-router-dom'; import AppBody from 'components/AppBody'; -import ServerContentPage from 'components/ServerContentPage'; import ConnectionRequired from 'components/ConnectionRequired'; import { toAsyncPageRoute } from 'components/router/AsyncRoute'; import { toViewManagerPageRoute } from 'components/router/LegacyRoute'; -import { ASYNC_ADMIN_ROUTES, ASYNC_USER_ROUTES } from './routes/asyncRoutes'; -import { LEGACY_ADMIN_ROUTES, LEGACY_PUBLIC_ROUTES, LEGACY_USER_ROUTES } from './routes/legacyRoutes'; +import { ASYNC_USER_ROUTES } from './routes/asyncRoutes'; +import { LEGACY_PUBLIC_ROUTES, LEGACY_USER_ROUTES } from './routes/legacyRoutes'; import { REDIRECTS } from './routes/_redirects'; import { toRedirectRoute } from 'components/router/Redirect'; @@ -27,16 +26,6 @@ const StableApp = () => ( {LEGACY_USER_ROUTES.map(toViewManagerPageRoute)} - {/* Admin routes */} - }> - {ASYNC_ADMIN_ROUTES.map(toAsyncPageRoute)} - {LEGACY_ADMIN_ROUTES.map(toViewManagerPageRoute)} - - - } /> - - {/* Public routes */} }> } /> @@ -44,6 +33,11 @@ const StableApp = () => ( {LEGACY_PUBLIC_ROUTES.map(toViewManagerPageRoute)} + {/* Ignore dashboard routes */} + + + + {/* Suppress warnings for unhandled routes */} diff --git a/src/apps/stable/routes/_redirects.ts b/src/apps/stable/routes/_redirects.ts index fb24865d8..d48c48d99 100644 --- a/src/apps/stable/routes/_redirects.ts +++ b/src/apps/stable/routes/_redirects.ts @@ -1,6 +1,5 @@ import type { Redirect } from 'components/router/Redirect'; export const REDIRECTS: Redirect[] = [ - { from: 'mypreferencesquickconnect.html', to: '/quickconnect' }, - { from: 'serveractivity.html', to: '/dashboard/activity' } + { from: 'mypreferencesquickconnect.html', to: '/quickconnect' } ]; diff --git a/src/apps/stable/routes/asyncRoutes/index.ts b/src/apps/stable/routes/asyncRoutes/index.ts index 9dd4fb3c9..e5abc8565 100644 --- a/src/apps/stable/routes/asyncRoutes/index.ts +++ b/src/apps/stable/routes/asyncRoutes/index.ts @@ -1,2 +1 @@ -export * from './admin'; export * from './user'; diff --git a/src/apps/stable/routes/legacyRoutes/index.ts b/src/apps/stable/routes/legacyRoutes/index.ts index 2931c568e..bc46c94c5 100644 --- a/src/apps/stable/routes/legacyRoutes/index.ts +++ b/src/apps/stable/routes/legacyRoutes/index.ts @@ -1,3 +1,2 @@ -export * from './admin'; export * from './public'; export * from './user'; From bd1ae96b62f7ba1b50f1a2951d1975d076c9e411 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Wed, 20 Sep 2023 16:25:11 -0400 Subject: [PATCH 093/142] Move routes to dashboard app --- src/apps/dashboard/App.tsx | 17 +- .../components/activityTable/LogLevelChip.tsx | 0 .../components/activityTable/OverviewCell.tsx | 0 .../dataGrid}/GridActionsCellLink.tsx | 0 src/apps/dashboard/routes/_asyncRoutes.ts | 16 +- src/apps/dashboard/routes/_redirects.ts | 16 +- .../routes}/activity.tsx | 6 +- .../routes}/notifications.tsx | 0 .../routes/users/access.tsx} | 0 .../routes/users/index.tsx} | 0 .../routes/users/new.tsx} | 0 .../routes/users/parentalcontrol.tsx} | 0 .../routes/users/password.tsx} | 0 .../routes/users/profile.tsx} | 0 .../components/drawers/AppDrawer.tsx | 2 - src/apps/stable/routes/asyncRoutes/admin.ts | 11 -- src/apps/stable/routes/legacyRoutes/admin.ts | 179 ------------------ 17 files changed, 29 insertions(+), 218 deletions(-) rename src/apps/{experimental => dashboard}/components/activityTable/LogLevelChip.tsx (100%) rename src/apps/{experimental => dashboard}/components/activityTable/OverviewCell.tsx (100%) rename src/apps/{experimental/components => dashboard/components/dataGrid}/GridActionsCellLink.tsx (100%) rename src/apps/{experimental/routes/dashboard => dashboard/routes}/activity.tsx (97%) rename src/apps/{stable/routes/dashboard => dashboard/routes}/notifications.tsx (100%) rename src/apps/{stable/routes/user/userlibraryaccess.tsx => dashboard/routes/users/access.tsx} (100%) rename src/apps/{stable/routes/user/userprofiles.tsx => dashboard/routes/users/index.tsx} (100%) rename src/apps/{stable/routes/user/usernew.tsx => dashboard/routes/users/new.tsx} (100%) rename src/apps/{stable/routes/user/userparentalcontrol.tsx => dashboard/routes/users/parentalcontrol.tsx} (100%) rename src/apps/{stable/routes/user/userpassword.tsx => dashboard/routes/users/password.tsx} (100%) rename src/apps/{stable/routes/user/useredit.tsx => dashboard/routes/users/profile.tsx} (100%) delete mode 100644 src/apps/stable/routes/asyncRoutes/admin.ts delete mode 100644 src/apps/stable/routes/legacyRoutes/admin.ts diff --git a/src/apps/dashboard/App.tsx b/src/apps/dashboard/App.tsx index a5f09d97a..232c8e77c 100644 --- a/src/apps/dashboard/App.tsx +++ b/src/apps/dashboard/App.tsx @@ -1,9 +1,10 @@ +import loadable from '@loadable/component'; import React from 'react'; import { Route, Routes } from 'react-router-dom'; import ConnectionRequired from 'components/ConnectionRequired'; import { toViewManagerPageRoute } from 'components/router/LegacyRoute'; -import { toAsyncPageRoute } from 'components/router/AsyncRoute'; +import { AsyncPageProps, AsyncRoute, toAsyncPageRoute } from 'components/router/AsyncRoute'; import { toRedirectRoute } from 'components/router/Redirect'; import ServerContentPage from 'components/ServerContentPage'; @@ -12,12 +13,24 @@ import { ASYNC_ADMIN_ROUTES } from './routes/_asyncRoutes'; import { LEGACY_ADMIN_ROUTES } from './routes/_legacyRoutes'; import AppLayout from './AppLayout'; +const DashboardAsyncPage = loadable( + (props: { page: string }) => import(/* webpackChunkName: "[request]" */ `./routes/${props.page}`), + { cacheKey: (props: AsyncPageProps) => props.page } +); + +const toDashboardAsyncPageRoute = (route: AsyncRoute) => ( + toAsyncPageRoute({ + ...route, + element: DashboardAsyncPage + }) +); + const DashboardApp = () => ( }> }> - {ASYNC_ADMIN_ROUTES.map(toAsyncPageRoute)} + {ASYNC_ADMIN_ROUTES.map(toDashboardAsyncPageRoute)} {LEGACY_ADMIN_ROUTES.map(toViewManagerPageRoute)} diff --git a/src/apps/experimental/components/activityTable/LogLevelChip.tsx b/src/apps/dashboard/components/activityTable/LogLevelChip.tsx similarity index 100% rename from src/apps/experimental/components/activityTable/LogLevelChip.tsx rename to src/apps/dashboard/components/activityTable/LogLevelChip.tsx diff --git a/src/apps/experimental/components/activityTable/OverviewCell.tsx b/src/apps/dashboard/components/activityTable/OverviewCell.tsx similarity index 100% rename from src/apps/experimental/components/activityTable/OverviewCell.tsx rename to src/apps/dashboard/components/activityTable/OverviewCell.tsx diff --git a/src/apps/experimental/components/GridActionsCellLink.tsx b/src/apps/dashboard/components/dataGrid/GridActionsCellLink.tsx similarity index 100% rename from src/apps/experimental/components/GridActionsCellLink.tsx rename to src/apps/dashboard/components/dataGrid/GridActionsCellLink.tsx diff --git a/src/apps/dashboard/routes/_asyncRoutes.ts b/src/apps/dashboard/routes/_asyncRoutes.ts index 4dc364beb..8cae66877 100644 --- a/src/apps/dashboard/routes/_asyncRoutes.ts +++ b/src/apps/dashboard/routes/_asyncRoutes.ts @@ -1,12 +1,12 @@ import type { AsyncRoute } from 'components/router/AsyncRoute'; export const ASYNC_ADMIN_ROUTES: AsyncRoute[] = [ - { path: 'activity', page: 'dashboard/activity' }, - { path: 'notifications', page: 'dashboard/notifications' }, - { path: 'users/new', page: 'user/usernew' }, - { path: 'users', page: 'user/userprofiles' }, - { path: 'users/profile', page: 'user/useredit' }, - { path: 'users/access', page: 'user/userlibraryaccess' }, - { path: 'users/parentalcontrol', page: 'user/userparentalcontrol' }, - { path: 'users/password', page: 'user/userpassword' } + { path: 'activity' }, + { path: 'notifications' }, + { path: 'users/new' }, + { path: 'users' }, + { path: 'users/profile' }, + { path: 'users/access' }, + { path: 'users/parentalcontrol' }, + { path: 'users/password' } ]; diff --git a/src/apps/dashboard/routes/_redirects.ts b/src/apps/dashboard/routes/_redirects.ts index a2786fe53..2191a3faf 100644 --- a/src/apps/dashboard/routes/_redirects.ts +++ b/src/apps/dashboard/routes/_redirects.ts @@ -1,16 +1,13 @@ import type { Redirect } from 'components/router/Redirect'; export const REDIRECTS: Redirect[] = [ - // FIXME: URL params are not included in redirects { from: 'addplugin.html', to: '/dashboard/plugins/add' }, { from: 'apikeys.html', to: '/dashboard/keys' }, { from: 'availableplugins.html', to: '/dashboard/plugins/catalog' }, { from: 'dashboard.html', to: '/dashboard' }, { from: 'dashboardgeneral.html', to: '/dashboard/settings' }, - // FIXME: URL params are not included in redirects { from: 'device.html', to: '/dashboard/devices/edit' }, { from: 'devices.html', to: '/dashboard/devices' }, - // FIXME: URL params are not included in redirects { from: 'dlnaprofile.html', to: '/dashboard/dlna/profiles/edit' }, { from: 'dlnaprofiles.html', to: '/dashboard/dlna/profiles' }, { from: 'dlnasettings.html', to: '/dashboard/dlna' }, @@ -19,11 +16,9 @@ export const REDIRECTS: Redirect[] = [ { from: 'installedplugins.html', to: '/dashboard/plugins' }, { from: 'library.html', to: '/dashboard/libraries' }, { from: 'librarydisplay.html', to: '/dashboard/libraries/display' }, - // FIXME: URL params are not included in redirects { from: 'livetvguideprovider.html', to: '/dashboard/livetv/guide' }, { from: 'livetvsettings.html', to: '/dashboard/recordings' }, { from: 'livetvstatus.html', to: '/dashboard/livetv' }, - // FIXME: URL params are not included in redirects { from: 'livetvtuner.html', to: '/dashboard/livetv/tuner' }, { from: 'log.html', to: '/dashboard/logs' }, { from: 'metadataimages.html', to: '/dashboard/libraries/metadata' }, @@ -33,19 +28,14 @@ export const REDIRECTS: Redirect[] = [ { from: 'playbackconfiguration.html', to: '/dashboard/playback/resume' }, { from: 'quickConnect.html', to: '/dashboard/quickconnect' }, { from: 'repositories.html', to: '/dashboard/plugins/repositories' }, - // FIXME: URL params are not included in redirects { from: 'scheduledtask.html', to: '/dashboard/tasks/edit' }, { from: 'scheduledtasks.html', to: '/dashboard/tasks' }, { from: 'serveractivity.html', to: '/dashboard/activity' }, { from: 'streamingsettings.html', to: '/dashboard/playback/streaming' }, - { from: 'usernew.html', to: '/dashboard/users/add' }, - { from: 'userprofiles.html', to: '/dashboard/users' }, - // FIXME: URL params are not included in redirects { from: 'useredit.html', to: '/dashboard/users/profile' }, - // FIXME: URL params are not included in redirects { from: 'userlibraryaccess.html', to: '/dashboard/users/access' }, - // FIXME: URL params are not included in redirects + { from: 'usernew.html', to: '/dashboard/users/add' }, { from: 'userparentalcontrol.html', to: '/dashboard/users/parentalcontrol' }, - // FIXME: URL params are not included in redirects - { from: 'userpassword.html', to: '/dashboard/users/password' } + { from: 'userpassword.html', to: '/dashboard/users/password' }, + { from: 'userprofiles.html', to: '/dashboard/users' } ]; diff --git a/src/apps/experimental/routes/dashboard/activity.tsx b/src/apps/dashboard/routes/activity.tsx similarity index 97% rename from src/apps/experimental/routes/dashboard/activity.tsx rename to src/apps/dashboard/routes/activity.tsx index f007e104d..125140ca0 100644 --- a/src/apps/experimental/routes/dashboard/activity.tsx +++ b/src/apps/dashboard/routes/activity.tsx @@ -19,9 +19,9 @@ import { parseISO8601Date, toLocaleDateString, toLocaleTimeString } from 'script import globalize from 'scripts/globalize'; import { toBoolean } from 'utils/string'; -import LogLevelChip from '../../components/activityTable/LogLevelChip'; -import OverviewCell from '../../components/activityTable/OverviewCell'; -import GridActionsCellLink from '../../components/GridActionsCellLink'; +import LogLevelChip from '../components/activityTable/LogLevelChip'; +import OverviewCell from '../components/activityTable/OverviewCell'; +import GridActionsCellLink from '../components/dataGrid/GridActionsCellLink'; const DEFAULT_PAGE_SIZE = 25; const VIEW_PARAM = 'useractivity'; diff --git a/src/apps/stable/routes/dashboard/notifications.tsx b/src/apps/dashboard/routes/notifications.tsx similarity index 100% rename from src/apps/stable/routes/dashboard/notifications.tsx rename to src/apps/dashboard/routes/notifications.tsx diff --git a/src/apps/stable/routes/user/userlibraryaccess.tsx b/src/apps/dashboard/routes/users/access.tsx similarity index 100% rename from src/apps/stable/routes/user/userlibraryaccess.tsx rename to src/apps/dashboard/routes/users/access.tsx diff --git a/src/apps/stable/routes/user/userprofiles.tsx b/src/apps/dashboard/routes/users/index.tsx similarity index 100% rename from src/apps/stable/routes/user/userprofiles.tsx rename to src/apps/dashboard/routes/users/index.tsx diff --git a/src/apps/stable/routes/user/usernew.tsx b/src/apps/dashboard/routes/users/new.tsx similarity index 100% rename from src/apps/stable/routes/user/usernew.tsx rename to src/apps/dashboard/routes/users/new.tsx diff --git a/src/apps/stable/routes/user/userparentalcontrol.tsx b/src/apps/dashboard/routes/users/parentalcontrol.tsx similarity index 100% rename from src/apps/stable/routes/user/userparentalcontrol.tsx rename to src/apps/dashboard/routes/users/parentalcontrol.tsx diff --git a/src/apps/stable/routes/user/userpassword.tsx b/src/apps/dashboard/routes/users/password.tsx similarity index 100% rename from src/apps/stable/routes/user/userpassword.tsx rename to src/apps/dashboard/routes/users/password.tsx diff --git a/src/apps/stable/routes/user/useredit.tsx b/src/apps/dashboard/routes/users/profile.tsx similarity index 100% rename from src/apps/stable/routes/user/useredit.tsx rename to src/apps/dashboard/routes/users/profile.tsx diff --git a/src/apps/experimental/components/drawers/AppDrawer.tsx b/src/apps/experimental/components/drawers/AppDrawer.tsx index 06d8a4917..7e4abf33c 100644 --- a/src/apps/experimental/components/drawers/AppDrawer.tsx +++ b/src/apps/experimental/components/drawers/AppDrawer.tsx @@ -27,8 +27,6 @@ const MAIN_DRAWER_ROUTES = [ ].filter(route => !DRAWERLESS_ROUTES.includes(route.path)); const ADMIN_DRAWER_ROUTES = [ - // ...ASYNC_ADMIN_ROUTES, - // ...LEGACY_ADMIN_ROUTES, { path: '/configurationpage' } // Plugin configuration page ].filter(route => !DRAWERLESS_ROUTES.includes(route.path)); diff --git a/src/apps/stable/routes/asyncRoutes/admin.ts b/src/apps/stable/routes/asyncRoutes/admin.ts deleted file mode 100644 index 72bcc6f32..000000000 --- a/src/apps/stable/routes/asyncRoutes/admin.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { AsyncRoute } from '../../../../components/router/AsyncRoute'; - -export const ASYNC_ADMIN_ROUTES: AsyncRoute[] = [ - { path: 'notificationsettings.html', page: 'dashboard/notifications' }, - { path: 'usernew.html', page: 'user/usernew' }, - { path: 'userprofiles.html', page: 'user/userprofiles' }, - { path: 'useredit.html', page: 'user/useredit' }, - { path: 'userlibraryaccess.html', page: 'user/userlibraryaccess' }, - { path: 'userparentalcontrol.html', page: 'user/userparentalcontrol' }, - { path: 'userpassword.html', page: 'user/userpassword' } -]; diff --git a/src/apps/stable/routes/legacyRoutes/admin.ts b/src/apps/stable/routes/legacyRoutes/admin.ts deleted file mode 100644 index fd430feeb..000000000 --- a/src/apps/stable/routes/legacyRoutes/admin.ts +++ /dev/null @@ -1,179 +0,0 @@ -import { LegacyRoute } from '../../../../components/router/LegacyRoute'; - -export const LEGACY_ADMIN_ROUTES: LegacyRoute[] = [ - { - path: 'dashboard.html', - pageProps: { - controller: 'dashboard/dashboard', - view: 'dashboard/dashboard.html' - } - }, { - path: 'dashboardgeneral.html', - pageProps: { - controller: 'dashboard/general', - view: 'dashboard/general.html' - } - }, { - path: 'networking.html', - pageProps: { - controller: 'dashboard/networking', - view: 'dashboard/networking.html' - } - }, { - path: 'devices.html', - pageProps: { - controller: 'dashboard/devices/devices', - view: 'dashboard/devices/devices.html' - } - }, { - path: 'device.html', - pageProps: { - controller: 'dashboard/devices/device', - view: 'dashboard/devices/device.html' - } - }, { - path: 'dlnaprofile.html', - pageProps: { - controller: 'dashboard/dlna/profile', - view: 'dashboard/dlna/profile.html' - } - }, { - path: 'dlnaprofiles.html', - pageProps: { - controller: 'dashboard/dlna/profiles', - view: 'dashboard/dlna/profiles.html' - } - }, { - path: 'dlnasettings.html', - pageProps: { - controller: 'dashboard/dlna/settings', - view: 'dashboard/dlna/settings.html' - } - }, { - path: 'addplugin.html', - pageProps: { - controller: 'dashboard/plugins/add/index', - view: 'dashboard/plugins/add/index.html' - } - }, { - path: 'library.html', - pageProps: { - controller: 'dashboard/library', - view: 'dashboard/library.html' - } - }, { - path: 'librarydisplay.html', - pageProps: { - controller: 'dashboard/librarydisplay', - view: 'dashboard/librarydisplay.html' - } - }, { - path: 'edititemmetadata.html', - pageProps: { - controller: 'edititemmetadata', - view: 'edititemmetadata.html' - } - }, { - path: 'encodingsettings.html', - pageProps: { - controller: 'dashboard/encodingsettings', - view: 'dashboard/encodingsettings.html' - } - }, { - path: 'log.html', - pageProps: { - controller: 'dashboard/logs', - view: 'dashboard/logs.html' - } - }, { - path: 'metadataimages.html', - pageProps: { - controller: 'dashboard/metadataImages', - view: 'dashboard/metadataimages.html' - } - }, { - path: 'metadatanfo.html', - pageProps: { - controller: 'dashboard/metadatanfo', - view: 'dashboard/metadatanfo.html' - } - }, { - path: 'playbackconfiguration.html', - pageProps: { - controller: 'dashboard/playback', - view: 'dashboard/playback.html' - } - }, { - path: 'availableplugins.html', - pageProps: { - controller: 'dashboard/plugins/available/index', - view: 'dashboard/plugins/available/index.html' - } - }, { - path: 'repositories.html', - pageProps: { - controller: 'dashboard/plugins/repositories/index', - view: 'dashboard/plugins/repositories/index.html' - } - }, { - path: 'livetvguideprovider.html', - pageProps: { - controller: 'livetvguideprovider', - view: 'livetvguideprovider.html' - } - }, { - path: 'livetvsettings.html', - pageProps: { - controller: 'livetvsettings', - view: 'livetvsettings.html' - } - }, { - path: 'livetvstatus.html', - pageProps: { - controller: 'livetvstatus', - view: 'livetvstatus.html' - } - }, { - path: 'livetvtuner.html', - pageProps: { - controller: 'livetvtuner', - view: 'livetvtuner.html' - } - }, { - path: 'installedplugins.html', - pageProps: { - controller: 'dashboard/plugins/installed/index', - view: 'dashboard/plugins/installed/index.html' - } - }, { - path: 'scheduledtask.html', - pageProps: { - controller: 'dashboard/scheduledtasks/scheduledtask', - view: 'dashboard/scheduledtasks/scheduledtask.html' - } - }, { - path: 'scheduledtasks.html', - pageProps: { - controller: 'dashboard/scheduledtasks/scheduledtasks', - view: 'dashboard/scheduledtasks/scheduledtasks.html' - } - }, { - path: 'dashboard/activity', - pageProps: { - controller: 'dashboard/serveractivity', - view: 'dashboard/serveractivity.html' - } - }, { - path: 'apikeys.html', - pageProps: { - controller: 'dashboard/apikeys', - view: 'dashboard/apikeys.html' - } - }, { - path: 'streamingsettings.html', - pageProps: { - view: 'dashboard/streaming.html', - controller: 'dashboard/streaming' - } - } -]; From 6101e04ca870822dd8276800a7932b3abec2a7fc Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Wed, 20 Sep 2023 17:34:48 -0400 Subject: [PATCH 094/142] Update README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index bcb0c5331..2516be0a2 100644 --- a/README.md +++ b/README.md @@ -77,8 +77,9 @@ Jellyfin Web is the frontend used for most of the clients available for end user . └── src ├── apps - │   ├── experimental # New experimental app layout - │   └── stable # Classic (stable) app layout + │   ├── dashboard # Admin dashboard app layout and routes + │   ├── experimental # New experimental app layout and routes + │   └── stable # Classic (stable) app layout and routes ├── assets # Static assets ├── components # Higher order visual components and React components ├── controllers # Legacy page views and controllers 🧹 @@ -87,7 +88,6 @@ Jellyfin Web is the frontend used for most of the clients available for end user ├── legacy # Polyfills for legacy browsers ├── libraries # Third party libraries 🧹 ├── plugins # Client plugins - ├── routes # React routes/pages ├── scripts # Random assortment of visual components and utilities 🐉 ├── strings # Translation files ├── styles # Common app Sass stylesheets From d5e703287a3408be5922927a54efa363fe3346bc Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Thu, 21 Sep 2023 16:40:08 -0400 Subject: [PATCH 095/142] Move shared components to common layout --- src/RootApp.tsx | 8 +++++++- src/apps/dashboard/App.tsx | 12 +++++++++--- src/apps/dashboard/AppLayout.tsx | 26 ++++++-------------------- src/apps/experimental/App.tsx | 11 ++++++++--- src/apps/stable/App.tsx | 13 +++++++++---- 5 files changed, 39 insertions(+), 31 deletions(-) diff --git a/src/RootApp.tsx b/src/RootApp.tsx index 956e57b1b..26f12eb39 100644 --- a/src/RootApp.tsx +++ b/src/RootApp.tsx @@ -11,6 +11,8 @@ import { HistoryRouter } from 'components/router/HistoryRouter'; import { ApiProvider } from 'hooks/useApi'; import { WebConfigProvider } from 'hooks/useWebConfig'; import theme from 'themes/theme'; +import { useLocation } from 'react-router-dom'; +import { DASHBOARD_APP_PATHS } from './apps/dashboard/App'; const DashboardApp = loadable(() => import('./apps/dashboard/App')); const ExperimentalApp = loadable(() => import('./apps/experimental/App')); @@ -22,10 +24,14 @@ const RootAppLayout = () => { const layoutMode = localStorage.getItem('layout'); const isExperimentalLayout = layoutMode === 'experimental'; + const location = useLocation(); + const isNewLayoutPath = Object.values(DASHBOARD_APP_PATHS) + .some(path => location.pathname.startsWith(`/${path}`)); + return ( <> - + { isExperimentalLayout ? diff --git a/src/apps/dashboard/App.tsx b/src/apps/dashboard/App.tsx index 232c8e77c..2127a807f 100644 --- a/src/apps/dashboard/App.tsx +++ b/src/apps/dashboard/App.tsx @@ -25,25 +25,31 @@ const toDashboardAsyncPageRoute = (route: AsyncRoute) => ( }) ); +export const DASHBOARD_APP_PATHS = { + Dashboard: 'dashboard', + MetadataManager: 'metadata', + PluginConfig: 'configurationpage' +}; + const DashboardApp = () => ( }> }> - + {ASYNC_ADMIN_ROUTES.map(toDashboardAsyncPageRoute)} {LEGACY_ADMIN_ROUTES.map(toViewManagerPageRoute)} {/* TODO: Should the metadata manager be a separate app? */} {toViewManagerPageRoute({ - path: 'metadata', + path: DASHBOARD_APP_PATHS.MetadataManager, pageProps: { controller: 'edititemmetadata', view: 'edititemmetadata.html' } })} - } /> diff --git a/src/apps/dashboard/AppLayout.tsx b/src/apps/dashboard/AppLayout.tsx index 1e427d3c6..19fe09e1b 100644 --- a/src/apps/dashboard/AppLayout.tsx +++ b/src/apps/dashboard/AppLayout.tsx @@ -1,29 +1,15 @@ -import { ThemeProvider } from '@mui/material/styles'; import React from 'react'; import { Outlet } from 'react-router-dom'; -import AppHeader from 'components/AppHeader'; -import Backdrop from 'components/Backdrop'; -import theme from 'themes/theme'; +import AppBody from 'components/AppBody'; + +import '../experimental/AppOverrides.scss'; const AppLayout = () => { return ( - - - -
- {/* - * TODO: These components are not used, but views interact with them directly so the need to be - * present in the dom. We add them in a hidden element to prevent errors. - */} - -
- -
-
- -
- + + + ); }; diff --git a/src/apps/experimental/App.tsx b/src/apps/experimental/App.tsx index a4f67e795..d804352f9 100644 --- a/src/apps/experimental/App.tsx +++ b/src/apps/experimental/App.tsx @@ -10,6 +10,7 @@ import { toRedirectRoute } from 'components/router/Redirect'; import AppLayout from './AppLayout'; import { ASYNC_USER_ROUTES } from './routes/asyncRoutes'; import { LEGACY_PUBLIC_ROUTES, LEGACY_USER_ROUTES } from './routes/legacyRoutes'; +import { DASHBOARD_APP_PATHS } from 'apps/dashboard/App'; const ExperimentalApp = () => { return ( @@ -33,9 +34,13 @@ const ExperimentalApp = () => { {REDIRECTS.map(toRedirectRoute)} {/* Ignore dashboard routes */} - - - + {Object.entries(DASHBOARD_APP_PATHS).map(([ key, path ]) => ( + + ))} ); }; diff --git a/src/apps/stable/App.tsx b/src/apps/stable/App.tsx index de6cdddb9..f9103eb6e 100644 --- a/src/apps/stable/App.tsx +++ b/src/apps/stable/App.tsx @@ -5,11 +5,12 @@ import AppBody from 'components/AppBody'; import ConnectionRequired from 'components/ConnectionRequired'; import { toAsyncPageRoute } from 'components/router/AsyncRoute'; import { toViewManagerPageRoute } from 'components/router/LegacyRoute'; +import { toRedirectRoute } from 'components/router/Redirect'; import { ASYNC_USER_ROUTES } from './routes/asyncRoutes'; import { LEGACY_PUBLIC_ROUTES, LEGACY_USER_ROUTES } from './routes/legacyRoutes'; import { REDIRECTS } from './routes/_redirects'; -import { toRedirectRoute } from 'components/router/Redirect'; +import { DASHBOARD_APP_PATHS } from 'apps/dashboard/App'; const Layout = () => ( @@ -34,9 +35,13 @@ const StableApp = () => ( {/* Ignore dashboard routes */} - - - + {Object.entries(DASHBOARD_APP_PATHS).map(([ key, path ]) => ( + + ))} {/* Suppress warnings for unhandled routes */} From f28542fbfb332ff266aabdd3fb7416170b29b8ef Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Sun, 24 Sep 2023 02:42:29 -0400 Subject: [PATCH 096/142] Fix app body not being unloaded when leaving dashboard --- src/apps/dashboard/App.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/apps/dashboard/App.tsx b/src/apps/dashboard/App.tsx index 2127a807f..db1776509 100644 --- a/src/apps/dashboard/App.tsx +++ b/src/apps/dashboard/App.tsx @@ -52,10 +52,10 @@ const DashboardApp = () => ( } /> - - {/* Suppress warnings for unhandled routes */} - + + {/* Suppress warnings for unhandled routes */} + {/* Redirects for old paths */} From 73aa0f1962e867caccc91e849b1e5f0973aa2337 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Sun, 24 Sep 2023 03:40:16 -0400 Subject: [PATCH 097/142] Fix app body not being unloaded when leaving dashboard --- src/apps/stable/App.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/apps/stable/App.tsx b/src/apps/stable/App.tsx index f9103eb6e..b0219312a 100644 --- a/src/apps/stable/App.tsx +++ b/src/apps/stable/App.tsx @@ -34,21 +34,21 @@ const StableApp = () => ( {LEGACY_PUBLIC_ROUTES.map(toViewManagerPageRoute)} - {/* Ignore dashboard routes */} - {Object.entries(DASHBOARD_APP_PATHS).map(([ key, path ]) => ( - - ))} - {/* Suppress warnings for unhandled routes */} {/* Redirects for old paths */} {REDIRECTS.map(toRedirectRoute)} + + {/* Ignore dashboard routes */} + {Object.entries(DASHBOARD_APP_PATHS).map(([ key, path ]) => ( + + ))} ); From b5dcdbf4b44e507b678bcbc84bd61a5170c29ae4 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Mon, 25 Sep 2023 00:00:36 -0400 Subject: [PATCH 098/142] Update dashboard paths --- src/apps/dashboard/routes/_redirects.ts | 1 - src/apps/dashboard/routes/activity.tsx | 2 +- src/apps/dashboard/routes/notifications.tsx | 2 +- src/apps/dashboard/routes/users/index.tsx | 8 ++-- src/apps/dashboard/routes/users/new.tsx | 2 +- src/apps/dashboard/routes/users/profile.tsx | 2 +- .../components/drawers/AppDrawer.tsx | 2 +- .../components/drawers/MainDrawerContent.tsx | 4 +- .../dashboard/AdvancedDrawerSection.tsx | 26 ++++++------- .../dashboard/DevicesDrawerSection.tsx | 12 +++--- .../drawers/dashboard/LiveTvDrawerSection.tsx | 4 +- .../drawers/dashboard/ServerDrawerSection.tsx | 38 +++++++++---------- .../dashboard/users/SectionTabs.tsx | 8 ++-- .../dashboard/users/UserCardBox.tsx | 2 +- src/components/homesections/homesections.js | 2 +- src/components/router/appRouter.js | 2 +- src/components/toolbar/AppUserMenu.tsx | 4 +- src/controllers/dashboard/dashboard.html | 6 +-- src/controllers/dashboard/devices/devices.js | 4 +- src/controllers/dashboard/dlna/profile.html | 2 +- src/controllers/dashboard/dlna/profile.js | 2 +- src/controllers/dashboard/dlna/profiles.html | 2 +- src/controllers/dashboard/dlna/profiles.js | 6 +-- src/controllers/dashboard/dlna/settings.js | 4 +- src/controllers/dashboard/encodingsettings.js | 6 +-- src/controllers/dashboard/library.js | 8 ++-- src/controllers/dashboard/librarydisplay.js | 8 ++-- src/controllers/dashboard/metadataImages.js | 8 ++-- src/controllers/dashboard/metadatanfo.js | 8 ++-- src/controllers/dashboard/playback.js | 6 +-- .../dashboard/plugins/available/index.js | 8 ++-- .../dashboard/plugins/installed/index.js | 8 ++-- .../dashboard/plugins/repositories/index.js | 6 +-- .../scheduledtasks/scheduledtasks.js | 4 +- src/controllers/dashboard/streaming.js | 6 +-- src/controllers/livetvguideprovider.js | 2 +- src/controllers/livetvstatus.js | 10 ++--- src/controllers/livetvtuner.js | 2 +- src/controllers/user/menu/index.html | 4 +- src/scripts/libraryMenu.js | 34 ++++++++--------- 40 files changed, 137 insertions(+), 138 deletions(-) diff --git a/src/apps/dashboard/routes/_redirects.ts b/src/apps/dashboard/routes/_redirects.ts index 2191a3faf..94211c79c 100644 --- a/src/apps/dashboard/routes/_redirects.ts +++ b/src/apps/dashboard/routes/_redirects.ts @@ -26,7 +26,6 @@ export const REDIRECTS: Redirect[] = [ { from: 'networking.html', to: '/dashboard/networking' }, { from: 'notificationsettings.html', to: '/dashboard/notifications' }, { from: 'playbackconfiguration.html', to: '/dashboard/playback/resume' }, - { from: 'quickConnect.html', to: '/dashboard/quickconnect' }, { from: 'repositories.html', to: '/dashboard/plugins/repositories' }, { from: 'scheduledtask.html', to: '/dashboard/tasks/edit' }, { from: 'scheduledtasks.html', to: '/dashboard/tasks' }, diff --git a/src/apps/dashboard/routes/activity.tsx b/src/apps/dashboard/routes/activity.tsx index 125140ca0..fa3a9135b 100644 --- a/src/apps/dashboard/routes/activity.tsx +++ b/src/apps/dashboard/routes/activity.tsx @@ -68,7 +68,7 @@ const Activity = () => { sx={{ padding: 0 }} title={users[row.UserId]?.Name ?? undefined} component={Link} - to={`/useredit.html?userId=${row.UserId}`} + to={`/dashboard/users/profile?userId=${row.UserId}`} > diff --git a/src/apps/dashboard/routes/notifications.tsx b/src/apps/dashboard/routes/notifications.tsx index ca874d133..6f673c753 100644 --- a/src/apps/dashboard/routes/notifications.tsx +++ b/src/apps/dashboard/routes/notifications.tsx @@ -9,7 +9,7 @@ const PluginLink = () => ( __html: ` ${globalize.translate('GetThePlugin')} ` diff --git a/src/apps/dashboard/routes/users/index.tsx b/src/apps/dashboard/routes/users/index.tsx index dc8a6e86f..6789a00ce 100644 --- a/src/apps/dashboard/routes/users/index.tsx +++ b/src/apps/dashboard/routes/users/index.tsx @@ -85,21 +85,21 @@ const UserProfiles: FunctionComponent = () => { callback: function (id: string) { switch (id) { case 'open': - Dashboard.navigate('useredit.html?userId=' + userId) + Dashboard.navigate('/dashboard/users/profile?userId=' + userId) .catch(err => { console.error('[userprofiles] failed to navigate to user edit page', err); }); break; case 'access': - Dashboard.navigate('userlibraryaccess.html?userId=' + userId) + Dashboard.navigate('/dashboard/users/access?userId=' + userId) .catch(err => { console.error('[userprofiles] failed to navigate to user library page', err); }); break; case 'parentalcontrol': - Dashboard.navigate('userparentalcontrol.html?userId=' + userId) + Dashboard.navigate('/dashboard/users/parentalcontrol?userId=' + userId) .catch(err => { console.error('[userprofiles] failed to navigate to parental control page', err); }); @@ -146,7 +146,7 @@ const UserProfiles: FunctionComponent = () => { }); (page.querySelector('#btnAddUser') as HTMLButtonElement).addEventListener('click', function() { - Dashboard.navigate('usernew.html') + Dashboard.navigate('/dashboard/users/add') .catch(err => { console.error('[userprofiles] failed to navigate to new user page', err); }); diff --git a/src/apps/dashboard/routes/users/new.tsx b/src/apps/dashboard/routes/users/new.tsx index 22758500e..116895e94 100644 --- a/src/apps/dashboard/routes/users/new.tsx +++ b/src/apps/dashboard/routes/users/new.tsx @@ -140,7 +140,7 @@ const UserNew: FunctionComponent = () => { } window.ApiClient.updateUserPolicy(user.Id, user.Policy).then(function () { - Dashboard.navigate('useredit.html?userId=' + user.Id) + Dashboard.navigate('/dashboard/users/profile?userId=' + user.Id) .catch(err => { console.error('[usernew] failed to navigate to edit user page', err); }); diff --git a/src/apps/dashboard/routes/users/profile.tsx b/src/apps/dashboard/routes/users/profile.tsx index c4acdfaae..05d3b72cd 100644 --- a/src/apps/dashboard/routes/users/profile.tsx +++ b/src/apps/dashboard/routes/users/profile.tsx @@ -32,7 +32,7 @@ const getCheckedElementDataIds = (elements: NodeListOf) => ( ); function onSaveComplete() { - Dashboard.navigate('userprofiles.html') + Dashboard.navigate('/dashboard/users') .catch(err => { console.error('[useredit] failed to navigate to user profile', err); }); diff --git a/src/apps/experimental/components/drawers/AppDrawer.tsx b/src/apps/experimental/components/drawers/AppDrawer.tsx index 7e4abf33c..dc48221b8 100644 --- a/src/apps/experimental/components/drawers/AppDrawer.tsx +++ b/src/apps/experimental/components/drawers/AppDrawer.tsx @@ -17,7 +17,7 @@ import { isTabPath } from '../tabs/tabRoutes'; export const DRAWER_WIDTH = 240; const DRAWERLESS_ROUTES = [ - 'edititemmetadata.html', // metadata manager + 'metadata', // metadata manager 'video' // video player ]; diff --git a/src/apps/experimental/components/drawers/MainDrawerContent.tsx b/src/apps/experimental/components/drawers/MainDrawerContent.tsx index 4d2a74b8a..351076a02 100644 --- a/src/apps/experimental/components/drawers/MainDrawerContent.tsx +++ b/src/apps/experimental/components/drawers/MainDrawerContent.tsx @@ -150,7 +150,7 @@ const MainDrawerContent = () => { } > - + @@ -158,7 +158,7 @@ const MainDrawerContent = () => { - + diff --git a/src/apps/experimental/components/drawers/dashboard/AdvancedDrawerSection.tsx b/src/apps/experimental/components/drawers/dashboard/AdvancedDrawerSection.tsx index 5a74c6863..97993a76c 100644 --- a/src/apps/experimental/components/drawers/dashboard/AdvancedDrawerSection.tsx +++ b/src/apps/experimental/components/drawers/dashboard/AdvancedDrawerSection.tsx @@ -19,10 +19,10 @@ import ListItemLink from 'components/ListItemLink'; import globalize from 'scripts/globalize'; const PLUGIN_PATHS = [ - '/installedplugins.html', - '/availableplugins.html', - '/repositories.html', - '/addplugin.html', + '/dashboard/plugins', + '/dashboard/plugins/catalog', + '/dashboard/plugins/repositories', + '/dashboard/plugins/add', '/configurationpage' ]; @@ -41,7 +41,7 @@ const AdvancedDrawerSection = () => { } > - + @@ -49,7 +49,7 @@ const AdvancedDrawerSection = () => { - + @@ -57,7 +57,7 @@ const AdvancedDrawerSection = () => { - +
@@ -65,7 +65,7 @@ const AdvancedDrawerSection = () => { - + @@ -73,7 +73,7 @@ const AdvancedDrawerSection = () => { - + @@ -83,19 +83,19 @@ const AdvancedDrawerSection = () => { - + - + - + - + diff --git a/src/apps/experimental/components/drawers/dashboard/DevicesDrawerSection.tsx b/src/apps/experimental/components/drawers/dashboard/DevicesDrawerSection.tsx index fe3ec0921..6cc7ab79f 100644 --- a/src/apps/experimental/components/drawers/dashboard/DevicesDrawerSection.tsx +++ b/src/apps/experimental/components/drawers/dashboard/DevicesDrawerSection.tsx @@ -12,8 +12,8 @@ import ListItemLink from 'components/ListItemLink'; import globalize from 'scripts/globalize'; const DLNA_PATHS = [ - '/dlnasettings.html', - '/dlnaprofiles.html' + '/dashboard/dlna', + '/dashboard/dlna/profiles' ]; const DevicesDrawerSection = () => { @@ -31,7 +31,7 @@ const DevicesDrawerSection = () => { } > - + @@ -47,7 +47,7 @@ const DevicesDrawerSection = () => { - + @@ -57,10 +57,10 @@ const DevicesDrawerSection = () => { - + - + diff --git a/src/apps/experimental/components/drawers/dashboard/LiveTvDrawerSection.tsx b/src/apps/experimental/components/drawers/dashboard/LiveTvDrawerSection.tsx index e3d20e154..35ea15ce0 100644 --- a/src/apps/experimental/components/drawers/dashboard/LiveTvDrawerSection.tsx +++ b/src/apps/experimental/components/drawers/dashboard/LiveTvDrawerSection.tsx @@ -20,7 +20,7 @@ const LiveTvDrawerSection = () => { } > - + @@ -28,7 +28,7 @@ const LiveTvDrawerSection = () => { - + diff --git a/src/apps/experimental/components/drawers/dashboard/ServerDrawerSection.tsx b/src/apps/experimental/components/drawers/dashboard/ServerDrawerSection.tsx index 2ed6b73f8..01e26ace8 100644 --- a/src/apps/experimental/components/drawers/dashboard/ServerDrawerSection.tsx +++ b/src/apps/experimental/components/drawers/dashboard/ServerDrawerSection.tsx @@ -12,16 +12,16 @@ import ListItemLink from 'components/ListItemLink'; import globalize from 'scripts/globalize'; const LIBRARY_PATHS = [ - '/library.html', - '/librarydisplay.html', - '/metadataimages.html', - '/metadatanfo.html' + '/dashboard/libraries', + '/dashboard/libraries/display', + '/dashboard/libraries/metadata', + '/dashboard/libraries/nfo' ]; const PLAYBACK_PATHS = [ - '/encodingsettings.html', - '/playbackconfiguration.html', - '/streamingsettings.html' + '/dashboard/playback/transcoding', + '/dashboard/playback/resume', + '/dashboard/playback/streaming' ]; const ServerDrawerSection = () => { @@ -40,7 +40,7 @@ const ServerDrawerSection = () => { } > - + @@ -48,7 +48,7 @@ const ServerDrawerSection = () => { - + @@ -56,7 +56,7 @@ const ServerDrawerSection = () => { - + @@ -64,7 +64,7 @@ const ServerDrawerSection = () => { - + @@ -74,22 +74,22 @@ const ServerDrawerSection = () => { - + - + - + - + - + @@ -99,13 +99,13 @@ const ServerDrawerSection = () => { - + - + - + diff --git a/src/components/dashboard/users/SectionTabs.tsx b/src/components/dashboard/users/SectionTabs.tsx index 0fad3469d..1befb5912 100644 --- a/src/components/dashboard/users/SectionTabs.tsx +++ b/src/components/dashboard/users/SectionTabs.tsx @@ -10,28 +10,28 @@ const createLinkElement = (activeTab: string) => ({ is="emby-linkbutton" data-role="button" class="${activeTab === 'useredit' ? 'ui-btn-active' : ''}" - onclick="Dashboard.navigate('useredit.html', true);"> + onclick="Dashboard.navigate('/dashboard/users/profile', true);"> ${globalize.translate('Profile')} + onclick="Dashboard.navigate('/dashboard/users/access', true);"> ${globalize.translate('TabAccess')} + onclick="Dashboard.navigate('/dashboard/users/parentalcontrol', true);"> ${globalize.translate('TabParentalControl')} + onclick="Dashboard.navigate('/dashboard/users/password', true);"> ${globalize.translate('HeaderPassword')} ` }); diff --git a/src/components/dashboard/users/UserCardBox.tsx b/src/components/dashboard/users/UserCardBox.tsx index b535f7a16..f0fbdf96a 100644 --- a/src/components/dashboard/users/UserCardBox.tsx +++ b/src/components/dashboard/users/UserCardBox.tsx @@ -11,7 +11,7 @@ const createLinkElement = ({ user, renderImgUrl }: { user: UserDto, renderImgUrl __html: ` ${renderImgUrl} ` diff --git a/src/components/homesections/homesections.js b/src/components/homesections/homesections.js index 212d976a8..6f911cdad 100644 --- a/src/components/homesections/homesections.js +++ b/src/components/homesections/homesections.js @@ -94,7 +94,7 @@ export function loadSections(elem, apiClient, user, userSettings) { const createNowLink = elem.querySelector('#button-createLibrary'); if (createNowLink) { createNowLink.addEventListener('click', function () { - Dashboard.navigate('library.html'); + Dashboard.navigate('dashboard/libraries'); }); } } diff --git a/src/components/router/appRouter.js b/src/components/router/appRouter.js index 253fdee92..c56d7fc6b 100644 --- a/src/components/router/appRouter.js +++ b/src/components/router/appRouter.js @@ -527,7 +527,7 @@ class AppRouter { } if (item === 'manageserver') { - return '#/dashboard.html'; + return '#/dashboard'; } if (item === 'recordedtv') { diff --git a/src/components/toolbar/AppUserMenu.tsx b/src/components/toolbar/AppUserMenu.tsx index 7119a4f50..634d02cf8 100644 --- a/src/components/toolbar/AppUserMenu.tsx +++ b/src/components/toolbar/AppUserMenu.tsx @@ -115,7 +115,7 @@ const AppUserMenu: FC = ({ @@ -127,7 +127,7 @@ const AppUserMenu: FC = ({ diff --git a/src/controllers/dashboard/dashboard.html b/src/controllers/dashboard/dashboard.html index a76cf1197..aed6bb206 100644 --- a/src/controllers/dashboard/dashboard.html +++ b/src/controllers/dashboard/dashboard.html @@ -3,7 +3,7 @@
- +

${TabServer}

@@ -33,7 +33,7 @@
- +

${HeaderActiveDevices}

@@ -70,7 +70,7 @@
- +

${HeaderPaths}

diff --git a/src/controllers/dashboard/devices/devices.js b/src/controllers/dashboard/devices/devices.js index 1c5ede953..c62571a1b 100644 --- a/src/controllers/dashboard/devices/devices.js +++ b/src/controllers/dashboard/devices/devices.js @@ -73,7 +73,7 @@ function showDeviceMenu(view, btn, deviceId) { callback: function (id) { switch (id) { case 'open': - Dashboard.navigate('device.html?id=' + deviceId); + Dashboard.navigate('dashboard/devices/edit?id=' + deviceId); break; case 'delete': @@ -94,7 +94,7 @@ function load(page, devices) { deviceHtml += '
'; deviceHtml += ' diff --git a/src/controllers/dashboard/dlna/profile.js b/src/controllers/dashboard/dlna/profile.js index 0a88f4214..0f92a3200 100644 --- a/src/controllers/dashboard/dlna/profile.js +++ b/src/controllers/dashboard/dlna/profile.js @@ -639,7 +639,7 @@ function saveProfile(page, profile) { data: JSON.stringify(profile), contentType: 'application/json' }).then(function () { - Dashboard.navigate('dlnaprofiles.html'); + Dashboard.navigate('dashboard/dlna/profiles'); }, Dashboard.processErrorResponse); } diff --git a/src/controllers/dashboard/dlna/profiles.html b/src/controllers/dashboard/dlna/profiles.html index 6eb60d1c3..f1696632c 100644 --- a/src/controllers/dashboard/dlna/profiles.html +++ b/src/controllers/dashboard/dlna/profiles.html @@ -8,7 +8,7 @@
diff --git a/src/controllers/dashboard/dlna/profiles.js b/src/controllers/dashboard/dlna/profiles.js index a7c8f045c..f69a0c6bf 100644 --- a/src/controllers/dashboard/dlna/profiles.js +++ b/src/controllers/dashboard/dlna/profiles.js @@ -40,7 +40,7 @@ function renderProfiles(page, element, profiles) { html += '
'; html += ''; html += ''; @@ -78,10 +78,10 @@ function deleteProfile(page, id) { function getTabs() { return [{ - href: '#/dlnasettings.html', + href: '#/dashboard/dlna', name: globalize.translate('Settings') }, { - href: '#/dlnaprofiles.html', + href: '#/dashboard/dlna/profiles', name: globalize.translate('TabProfiles') }]; } diff --git a/src/controllers/dashboard/dlna/settings.js b/src/controllers/dashboard/dlna/settings.js index fcc7d7d10..d12b6744a 100644 --- a/src/controllers/dashboard/dlna/settings.js +++ b/src/controllers/dashboard/dlna/settings.js @@ -37,10 +37,10 @@ function onSubmit() { function getTabs() { return [{ - href: '#/dlnasettings.html', + href: '#/dashboard/dlna', name: globalize.translate('Settings') }, { - href: '#/dlnaprofiles.html', + href: '#/dashboard/dlna/profiles', name: globalize.translate('TabProfiles') }]; } diff --git a/src/controllers/dashboard/encodingsettings.js b/src/controllers/dashboard/encodingsettings.js index 9c268c086..24946cadb 100644 --- a/src/controllers/dashboard/encodingsettings.js +++ b/src/controllers/dashboard/encodingsettings.js @@ -167,13 +167,13 @@ function setDecodingCodecsVisible(context, value) { function getTabs() { return [{ - href: '#/encodingsettings.html', + href: '#/dashboard/playback/transcoding', name: globalize.translate('Transcoding') }, { - href: '#/playbackconfiguration.html', + href: '#/dashboard/playback/resume', name: globalize.translate('ButtonResume') }, { - href: '#/streamingsettings.html', + href: '#/dashboard/playback/streaming', name: globalize.translate('TabStreaming') }]; } diff --git a/src/controllers/dashboard/library.js b/src/controllers/dashboard/library.js index 51ca3af71..62d932996 100644 --- a/src/controllers/dashboard/library.js +++ b/src/controllers/dashboard/library.js @@ -360,16 +360,16 @@ function getVirtualFolderHtml(page, virtualFolder, index) { function getTabs() { return [{ - href: '#/library.html', + href: '#/dashboard/libraries', name: globalize.translate('HeaderLibraries') }, { - href: '#/librarydisplay.html', + href: '#/dashboard/libraries/display', name: globalize.translate('Display') }, { - href: '#/metadataimages.html', + href: '#/dashboard/libraries/metadata', name: globalize.translate('Metadata') }, { - href: '#/metadatanfo.html', + href: '#/dashboard/libraries/nfo', name: globalize.translate('TabNfoSettings') }]; } diff --git a/src/controllers/dashboard/librarydisplay.js b/src/controllers/dashboard/librarydisplay.js index b418984fb..a38608277 100644 --- a/src/controllers/dashboard/librarydisplay.js +++ b/src/controllers/dashboard/librarydisplay.js @@ -7,16 +7,16 @@ import Dashboard from '../../utils/dashboard'; function getTabs() { return [{ - href: '#/library.html', + href: '#/dashboard/libraries', name: globalize.translate('HeaderLibraries') }, { - href: '#/librarydisplay.html', + href: '#/dashboard/libraries/display', name: globalize.translate('Display') }, { - href: '#/metadataimages.html', + href: '#/dashboard/libraries/metadata', name: globalize.translate('Metadata') }, { - href: '#/metadatanfo.html', + href: '#/dashboard/libraries/nfo', name: globalize.translate('TabNfoSettings') }]; } diff --git a/src/controllers/dashboard/metadataImages.js b/src/controllers/dashboard/metadataImages.js index 53633d691..779727dc7 100644 --- a/src/controllers/dashboard/metadataImages.js +++ b/src/controllers/dashboard/metadataImages.js @@ -88,16 +88,16 @@ function onSubmit() { function getTabs() { return [{ - href: '#/library.html', + href: '#/dashboard/libraries', name: globalize.translate('HeaderLibraries') }, { - href: '#/librarydisplay.html', + href: '#/dashboard/libraries/display', name: globalize.translate('Display') }, { - href: '#/metadataimages.html', + href: '#/dashboard/libraries/metadata', name: globalize.translate('Metadata') }, { - href: '#/metadatanfo.html', + href: '#/dashboard/libraries/nfo', name: globalize.translate('TabNfoSettings') }]; } diff --git a/src/controllers/dashboard/metadatanfo.js b/src/controllers/dashboard/metadatanfo.js index 38e2ec71f..1a68d4d63 100644 --- a/src/controllers/dashboard/metadatanfo.js +++ b/src/controllers/dashboard/metadatanfo.js @@ -46,16 +46,16 @@ function showConfirmMessage() { function getTabs() { return [{ - href: '#/library.html', + href: '#/dashboard/libraries', name: globalize.translate('HeaderLibraries') }, { - href: '#/librarydisplay.html', + href: '#/dashboard/libraries/display', name: globalize.translate('Display') }, { - href: '#/metadataimages.html', + href: '#/dashboard/libraries/metadata', name: globalize.translate('Metadata') }, { - href: '#/metadatanfo.html', + href: '#/dashboard/libraries/nfo', name: globalize.translate('TabNfoSettings') }]; } diff --git a/src/controllers/dashboard/playback.js b/src/controllers/dashboard/playback.js index f24e77efd..6b4985df3 100644 --- a/src/controllers/dashboard/playback.js +++ b/src/controllers/dashboard/playback.js @@ -31,13 +31,13 @@ function onSubmit() { function getTabs() { return [{ - href: '#/encodingsettings.html', + href: '#/dashboard/playback/transcoding', name: globalize.translate('Transcoding') }, { - href: '#/playbackconfiguration.html', + href: '#/dashboard/playback/resume', name: globalize.translate('ButtonResume') }, { - href: '#/streamingsettings.html', + href: '#/dashboard/playback/streaming', name: globalize.translate('TabStreaming') }]; } diff --git a/src/controllers/dashboard/plugins/available/index.js b/src/controllers/dashboard/plugins/available/index.js index 78368b2c1..b3445b5cb 100644 --- a/src/controllers/dashboard/plugins/available/index.js +++ b/src/controllers/dashboard/plugins/available/index.js @@ -120,7 +120,7 @@ function onSearchBarType(searchBar) { function getPluginHtml(plugin, options, installedPlugins) { let html = ''; - let href = plugin.externalUrl ? plugin.externalUrl : '#/addplugin.html?name=' + encodeURIComponent(plugin.name) + '&guid=' + plugin.guid; + let href = plugin.externalUrl ? plugin.externalUrl : '#/dashboard/plugins/add?name=' + encodeURIComponent(plugin.name) + '&guid=' + plugin.guid; if (options.context) { href += '&context=' + options.context; @@ -161,13 +161,13 @@ function getPluginHtml(plugin, options, installedPlugins) { function getTabs() { return [{ - href: '#/installedplugins.html', + href: '#/dashboard/plugins', name: globalize.translate('TabMyPlugins') }, { - href: '#/availableplugins.html', + href: '#/dashboard/plugins/catalog', name: globalize.translate('TabCatalog') }, { - href: '#/repositories.html', + href: '#/dashboard/plugins/repositories', name: globalize.translate('TabRepositories') }]; } diff --git a/src/controllers/dashboard/plugins/installed/index.js b/src/controllers/dashboard/plugins/installed/index.js index f6442710f..91ebcfdff 100644 --- a/src/controllers/dashboard/plugins/installed/index.js +++ b/src/controllers/dashboard/plugins/installed/index.js @@ -130,7 +130,7 @@ function populateList(page, plugins, pluginConfigurationPages) { } else { html += '
'; html += '

' + globalize.translate('MessageNoPluginsInstalled') + '

'; - html += '

'; + html += '

'; html += globalize.translate('MessageBrowsePluginCatalog'); html += '

'; html += '
'; @@ -221,13 +221,13 @@ function reloadList(page) { function getTabs() { return [{ - href: '#/installedplugins.html', + href: '#/dashboard/plugins', name: globalize.translate('TabMyPlugins') }, { - href: '#/availableplugins.html', + href: '#/dashboard/plugins/catalog', name: globalize.translate('TabCatalog') }, { - href: '#/repositories.html', + href: '#/dashboard/plugins/repositories', name: globalize.translate('TabRepositories') }]; } diff --git a/src/controllers/dashboard/plugins/repositories/index.js b/src/controllers/dashboard/plugins/repositories/index.js index 3c83b6e39..55ff12a45 100644 --- a/src/controllers/dashboard/plugins/repositories/index.js +++ b/src/controllers/dashboard/plugins/repositories/index.js @@ -105,13 +105,13 @@ function getRepositoryElement(repository) { function getTabs() { return [{ - href: '#/installedplugins.html', + href: '#/dashboard/plugins', name: globalize.translate('TabMyPlugins') }, { - href: '#/availableplugins.html', + href: '#/dashboard/plugins/catalog', name: globalize.translate('TabCatalog') }, { - href: '#/repositories.html', + href: '#/dashboard/plugins/repositories', name: globalize.translate('TabRepositories') }]; } diff --git a/src/controllers/dashboard/scheduledtasks/scheduledtasks.js b/src/controllers/dashboard/scheduledtasks/scheduledtasks.js index f17bc5c93..53966e18d 100644 --- a/src/controllers/dashboard/scheduledtasks/scheduledtasks.js +++ b/src/controllers/dashboard/scheduledtasks/scheduledtasks.js @@ -53,12 +53,12 @@ function populateList(page, tasks) { html += '
'; } html += '
'; - html += ""; + html += ""; html += ''; html += ''; html += '
'; const textAlignStyle = globalize.getIsRTL() ? 'right' : 'left'; - html += ""; + html += ""; html += "

" + task.Name + '

'; html += "
" + getTaskProgressHtml(task) + '
'; html += '
'; diff --git a/src/controllers/dashboard/streaming.js b/src/controllers/dashboard/streaming.js index ba9d76751..c02a5cdbd 100644 --- a/src/controllers/dashboard/streaming.js +++ b/src/controllers/dashboard/streaming.js @@ -22,13 +22,13 @@ function onSubmit() { function getTabs() { return [{ - href: '#/encodingsettings.html', + href: '#/dashboard/playback/transcoding', name: globalize.translate('Transcoding') }, { - href: '#/playbackconfiguration.html', + href: '#/dashboard/playback/resume', name: globalize.translate('ButtonResume') }, { - href: '#/streamingsettings.html', + href: '#/dashboard/playback/streaming', name: globalize.translate('TabStreaming') }]; } diff --git a/src/controllers/livetvguideprovider.js b/src/controllers/livetvguideprovider.js index 7a133945f..e87f2ace3 100644 --- a/src/controllers/livetvguideprovider.js +++ b/src/controllers/livetvguideprovider.js @@ -5,7 +5,7 @@ import { getParameterByName } from '../utils/url.ts'; import Events from '../utils/events.ts'; function onListingsSubmitted() { - Dashboard.navigate('livetvstatus.html'); + Dashboard.navigate('dashboard/livetv'); } function init(page, type, providerId) { diff --git a/src/controllers/livetvstatus.js b/src/controllers/livetvstatus.js index 3c0e30493..8532e8ae2 100644 --- a/src/controllers/livetvstatus.js +++ b/src/controllers/livetvstatus.js @@ -220,9 +220,9 @@ function getProviderName(providerId) { function getProviderConfigurationUrl(providerId) { switch (providerId.toLowerCase()) { case 'xmltv': - return '#/livetvguideprovider.html?type=xmltv'; + return '#/dashboard/livetv/guide?type=xmltv'; case 'schedulesdirect': - return '#/livetvguideprovider.html?type=schedulesdirect'; + return '#/dashboard/livetv/guide?type=schedulesdirect'; } } @@ -249,7 +249,7 @@ function addProvider(button) { } function addDevice() { - Dashboard.navigate('livetvtuner.html'); + Dashboard.navigate('dashboard/livetv/tuner'); } function showDeviceMenu(button, tunerDeviceId) { @@ -274,7 +274,7 @@ function showDeviceMenu(button, tunerDeviceId) { break; case 'edit': - Dashboard.navigate('livetvtuner.html?id=' + tunerDeviceId); + Dashboard.navigate('dashboard/livetv/tuner?id=' + tunerDeviceId); } }); }); @@ -290,7 +290,7 @@ function onDevicesListClick(e) { if (btnCardOptions) { showDeviceMenu(btnCardOptions, id); } else { - Dashboard.navigate('livetvtuner.html?id=' + id); + Dashboard.navigate('dashboard/livetv/tuner?id=' + id); } } } diff --git a/src/controllers/livetvtuner.js b/src/controllers/livetvtuner.js index 7f6ec2027..de73b608d 100644 --- a/src/controllers/livetvtuner.js +++ b/src/controllers/livetvtuner.js @@ -96,7 +96,7 @@ function submitForm(page) { contentType: 'application/json' }).then(function () { Dashboard.processServerConfigurationUpdateResult(); - Dashboard.navigate('livetvstatus.html'); + Dashboard.navigate('dashboard/livetv'); }, function () { loading.hide(); Dashboard.alert({ diff --git a/src/controllers/user/menu/index.html b/src/controllers/user/menu/index.html index 1c83bd9d6..8fe6326fc 100644 --- a/src/controllers/user/menu/index.html +++ b/src/controllers/user/menu/index.html @@ -77,7 +77,7 @@

${HeaderAdmin}

- +
@@ -85,7 +85,7 @@
- +
'; } @@ -429,28 +429,28 @@ function createToolsMenuList(pluginItems) { name: globalize.translate('TabServer') }, { name: globalize.translate('TabDashboard'), - href: '#/dashboard.html', + href: '#/dashboard', pageIds: ['dashboardPage'], icon: 'dashboard' }, { name: globalize.translate('General'), - href: '#/dashboardgeneral.html', + href: '#/dashboard/settings', pageIds: ['dashboardGeneralPage'], icon: 'settings' }, { name: globalize.translate('HeaderUsers'), - href: '#/userprofiles.html', + href: '#/dashboard/users', pageIds: ['userProfilesPage', 'newUserPage', 'editUserPage', 'userLibraryAccessPage', 'userParentalControlPage', 'userPasswordPage'], icon: 'people' }, { name: globalize.translate('HeaderLibraries'), - href: '#/library.html', + href: '#/dashboard/libraries', pageIds: ['mediaLibraryPage', 'librarySettingsPage', 'libraryDisplayPage', 'metadataImagesConfigurationPage', 'metadataNfoPage'], icon: 'folder' }, { name: globalize.translate('TitlePlayback'), icon: 'play_arrow', - href: '#/encodingsettings.html', + href: '#/dashboard/playback/transcoding', pageIds: ['encodingSettingsPage', 'playbackConfigurationPage', 'streamingSettingsPage'] }]; addPluginPagesToMainMenu(links, pluginItems, 'server'); @@ -460,7 +460,7 @@ function createToolsMenuList(pluginItems) { }); links.push({ name: globalize.translate('HeaderDevices'), - href: '#/devices.html', + href: '#/dashboard/devices', pageIds: ['devicesPage', 'devicePage'], icon: 'devices' }); @@ -472,7 +472,7 @@ function createToolsMenuList(pluginItems) { }); links.push({ name: globalize.translate('DLNA'), - href: '#/dlnasettings.html', + href: '#/dashboard/dlna', pageIds: ['dlnaSettingsPage', 'dlnaProfilesPage', 'dlnaProfilePage'], icon: 'input' }); @@ -482,13 +482,13 @@ function createToolsMenuList(pluginItems) { }); links.push({ name: globalize.translate('LiveTV'), - href: '#/livetvstatus.html', + href: '#/dashboard/livetv', pageIds: ['liveTvStatusPage', 'liveTvTunerPage'], icon: 'live_tv' }); links.push({ name: globalize.translate('HeaderDVR'), - href: '#/livetvsettings.html', + href: '#/dashboard/recordings', pageIds: ['liveTvSettingsPage'], icon: 'dvr' }); @@ -500,35 +500,35 @@ function createToolsMenuList(pluginItems) { links.push({ name: globalize.translate('TabNetworking'), icon: 'cloud', - href: '#/networking.html', + href: '#/dashboard/networking', pageIds: ['networkingPage'] }); links.push({ name: globalize.translate('HeaderApiKeys'), icon: 'vpn_key', - href: '#/apikeys.html', + href: '#/dashboard/keys', pageIds: ['apiKeysPage'] }); links.push({ name: globalize.translate('TabLogs'), - href: '#/log.html', + href: '#/dashboard/logs', pageIds: ['logPage'], icon: 'bug_report' }); links.push({ name: globalize.translate('Notifications'), icon: 'notifications', - href: '#/notificationsettings.html' + href: '#/dashboard/notifications' }); links.push({ name: globalize.translate('TabPlugins'), icon: 'shopping_cart', - href: '#/installedplugins.html', + href: '#/dashboard/plugins', pageIds: ['pluginsPage', 'pluginCatalogPage'] }); links.push({ name: globalize.translate('TabScheduledTasks'), - href: '#/scheduledtasks.html', + href: '#/dashboard/tasks', pageIds: ['scheduledTasksPage', 'scheduledTaskPage'], icon: 'schedule' }); From 06386a8eb6c16d2407eb865739e3c94d4f4d0205 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Mon, 25 Sep 2023 00:16:10 -0400 Subject: [PATCH 099/142] Remove legacy dashboard drawer --- src/scripts/libraryMenu.js | 252 +------------------------------------ 1 file changed, 1 insertion(+), 251 deletions(-) diff --git a/src/scripts/libraryMenu.js b/src/scripts/libraryMenu.js index 6a3e77f26..f90ece9d7 100644 --- a/src/scripts/libraryMenu.js +++ b/src/scripts/libraryMenu.js @@ -376,249 +376,6 @@ function refreshLibraryInfoInDrawer(user) { } } -function refreshDashboardInfoInDrawer(page, apiClient) { - currentDrawerType = 'admin'; - loadNavDrawer(); - - if (navDrawerScrollContainer.querySelector('.adminDrawerLogo')) { - updateDashboardMenuSelectedItem(page); - } else { - createDashboardMenu(page, apiClient); - } -} - -function isUrlInCurrentView(url) { - return window.location.href.toString().toLowerCase().indexOf(url.toLowerCase()) !== -1; -} - -function updateDashboardMenuSelectedItem(page) { - const links = navDrawerScrollContainer.querySelectorAll('.navMenuOption'); - const currentViewId = page.id; - - for (let i = 0, length = links.length; i < length; i++) { - let link = links[i]; - let selected = false; - let pageIds = link.getAttribute('data-pageids'); - - if (pageIds) { - pageIds = pageIds.split('|'); - selected = pageIds.indexOf(currentViewId) != -1; - } - - let pageUrls = link.getAttribute('data-pageurls'); - - if (pageUrls) { - pageUrls = pageUrls.split('|'); - selected = pageUrls.filter(isUrlInCurrentView).length > 0; - } - - if (selected) { - link.classList.add('navMenuOption-selected'); - let title = ''; - link = link.querySelector('.navMenuOptionText') || link; - title += (link.innerText || link.textContent).trim(); - LibraryMenu.setTitle(title); - } else { - link.classList.remove('navMenuOption-selected'); - } - } -} - -function createToolsMenuList(pluginItems) { - const links = [{ - name: globalize.translate('TabServer') - }, { - name: globalize.translate('TabDashboard'), - href: '#/dashboard', - pageIds: ['dashboardPage'], - icon: 'dashboard' - }, { - name: globalize.translate('General'), - href: '#/dashboard/settings', - pageIds: ['dashboardGeneralPage'], - icon: 'settings' - }, { - name: globalize.translate('HeaderUsers'), - href: '#/dashboard/users', - pageIds: ['userProfilesPage', 'newUserPage', 'editUserPage', 'userLibraryAccessPage', 'userParentalControlPage', 'userPasswordPage'], - icon: 'people' - }, { - name: globalize.translate('HeaderLibraries'), - href: '#/dashboard/libraries', - pageIds: ['mediaLibraryPage', 'librarySettingsPage', 'libraryDisplayPage', 'metadataImagesConfigurationPage', 'metadataNfoPage'], - icon: 'folder' - }, { - name: globalize.translate('TitlePlayback'), - icon: 'play_arrow', - href: '#/dashboard/playback/transcoding', - pageIds: ['encodingSettingsPage', 'playbackConfigurationPage', 'streamingSettingsPage'] - }]; - addPluginPagesToMainMenu(links, pluginItems, 'server'); - links.push({ - divider: true, - name: globalize.translate('HeaderDevices') - }); - links.push({ - name: globalize.translate('HeaderDevices'), - href: '#/dashboard/devices', - pageIds: ['devicesPage', 'devicePage'], - icon: 'devices' - }); - links.push({ - name: globalize.translate('HeaderActivity'), - href: '#/dashboard/activity', - pageIds: ['serverActivityPage'], - icon: 'assessment' - }); - links.push({ - name: globalize.translate('DLNA'), - href: '#/dashboard/dlna', - pageIds: ['dlnaSettingsPage', 'dlnaProfilesPage', 'dlnaProfilePage'], - icon: 'input' - }); - links.push({ - divider: true, - name: globalize.translate('LiveTV') - }); - links.push({ - name: globalize.translate('LiveTV'), - href: '#/dashboard/livetv', - pageIds: ['liveTvStatusPage', 'liveTvTunerPage'], - icon: 'live_tv' - }); - links.push({ - name: globalize.translate('HeaderDVR'), - href: '#/dashboard/recordings', - pageIds: ['liveTvSettingsPage'], - icon: 'dvr' - }); - addPluginPagesToMainMenu(links, pluginItems, 'livetv'); - links.push({ - divider: true, - name: globalize.translate('TabAdvanced') - }); - links.push({ - name: globalize.translate('TabNetworking'), - icon: 'cloud', - href: '#/dashboard/networking', - pageIds: ['networkingPage'] - }); - links.push({ - name: globalize.translate('HeaderApiKeys'), - icon: 'vpn_key', - href: '#/dashboard/keys', - pageIds: ['apiKeysPage'] - }); - links.push({ - name: globalize.translate('TabLogs'), - href: '#/dashboard/logs', - pageIds: ['logPage'], - icon: 'bug_report' - }); - links.push({ - name: globalize.translate('Notifications'), - icon: 'notifications', - href: '#/dashboard/notifications' - }); - links.push({ - name: globalize.translate('TabPlugins'), - icon: 'shopping_cart', - href: '#/dashboard/plugins', - pageIds: ['pluginsPage', 'pluginCatalogPage'] - }); - links.push({ - name: globalize.translate('TabScheduledTasks'), - href: '#/dashboard/tasks', - pageIds: ['scheduledTasksPage', 'scheduledTaskPage'], - icon: 'schedule' - }); - if (hasUnsortedPlugins(pluginItems)) { - links.push({ - divider: true, - name: globalize.translate('TabPlugins') - }); - addPluginPagesToMainMenu(links, pluginItems); - } - return links; -} - -function hasUnsortedPlugins(pluginItems) { - for (const pluginItem of pluginItems) { - if (pluginItem.EnableInMainMenu && pluginItem.MenuSection === undefined) { - return true; - } - } - return false; -} - -function addPluginPagesToMainMenu(links, pluginItems, section) { - for (const pluginItem of pluginItems) { - if (pluginItem.EnableInMainMenu && pluginItem.MenuSection === section) { - links.push({ - name: pluginItem.DisplayName, - icon: pluginItem.MenuIcon || 'folder', - href: Dashboard.getPluginUrl(pluginItem.Name), - pageUrls: [Dashboard.getPluginUrl(pluginItem.Name)] - }); - } - } -} - -function getToolsMenuLinks(apiClient) { - return apiClient.getJSON(apiClient.getUrl('web/configurationpages') + '?pageType=PluginConfiguration&EnableInMainMenu=true').then(createToolsMenuList, function () { - return createToolsMenuList([]); - }); -} - -function getToolsLinkHtml(item) { - let menuHtml = ''; - let pageIds = item.pageIds ? item.pageIds.join('|') : ''; - pageIds = pageIds ? ' data-pageids="' + pageIds + '"' : ''; - let pageUrls = item.pageUrls ? item.pageUrls.join('|') : ''; - pageUrls = pageUrls ? ' data-pageurls="' + pageUrls + '"' : ''; - menuHtml += ''; - - if (item.icon) { - menuHtml += ''; - } - - menuHtml += ''; - menuHtml += escapeHtml(item.name); - menuHtml += ''; - return menuHtml + ''; -} - -function getToolsMenuHtml(apiClient) { - return getToolsMenuLinks(apiClient).then(function (items) { - let menuHtml = ''; - menuHtml += '
'; - - for (const item of items) { - if (item.href) { - menuHtml += getToolsLinkHtml(item); - } else if (item.name) { - menuHtml += '

'; - menuHtml += escapeHtml(item.name); - menuHtml += '

'; - } - } - - return menuHtml + '
'; - }); -} - -function createDashboardMenu(page, apiClient) { - return getToolsMenuHtml(apiClient).then(function (toolsMenuHtml) { - let html = ''; - html += ''; - html += toolsMenuHtml; - navDrawerScrollContainer.innerHTML = html; - updateDashboardMenuSelectedItem(page); - }); -} - function onSidebarLinkClick() { const section = this.getElementsByClassName('sectionName')[0]; const text = section ? section.innerHTML : this.innerHTML; @@ -1026,15 +783,8 @@ pageClassOn('pageshow', 'page', function (e) { const isDashboardPage = page.classList.contains('type-interior'); const isHomePage = page.classList.contains('homePage'); const isLibraryPage = !isDashboardPage && page.classList.contains('libraryPage'); - const apiClient = getCurrentApiClient(); - if (isDashboardPage) { - if (mainDrawerButton) { - mainDrawerButton.classList.remove('hide'); - } - - refreshDashboardInfoInDrawer(page, apiClient); - } else { + if (!isDashboardPage) { if (mainDrawerButton) { if (enableLibraryNavDrawer || (isHomePage && enableLibraryNavDrawerHome)) { mainDrawerButton.classList.remove('hide'); From 1a934c79563e067d448248ee565f106e5eeda4da Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Mon, 25 Sep 2023 02:13:16 -0400 Subject: [PATCH 100/142] Refactor common navigation components --- src/apps/dashboard/AppLayout.tsx | 98 ++++++++++++++++++- .../dashboard/components/drawer/AppDrawer.tsx | 29 ++++++ .../sections}/AdvancedDrawerSection.tsx | 0 .../drawer/sections}/DevicesDrawerSection.tsx | 0 .../drawer/sections}/LiveTvDrawerSection.tsx | 0 .../drawer/sections}/PluginDrawerSection.tsx | 0 .../drawer/sections}/ServerDrawerSection.tsx | 0 .../components/drawers/AppDrawer.tsx | 86 +++------------- 8 files changed, 138 insertions(+), 75 deletions(-) create mode 100644 src/apps/dashboard/components/drawer/AppDrawer.tsx rename src/apps/{experimental/components/drawers/dashboard => dashboard/components/drawer/sections}/AdvancedDrawerSection.tsx (100%) rename src/apps/{experimental/components/drawers/dashboard => dashboard/components/drawer/sections}/DevicesDrawerSection.tsx (100%) rename src/apps/{experimental/components/drawers/dashboard => dashboard/components/drawer/sections}/LiveTvDrawerSection.tsx (100%) rename src/apps/{experimental/components/drawers/dashboard => dashboard/components/drawer/sections}/PluginDrawerSection.tsx (100%) rename src/apps/{experimental/components/drawers/dashboard => dashboard/components/drawer/sections}/ServerDrawerSection.tsx (100%) diff --git a/src/apps/dashboard/AppLayout.tsx b/src/apps/dashboard/AppLayout.tsx index 19fe09e1b..7afe655a3 100644 --- a/src/apps/dashboard/AppLayout.tsx +++ b/src/apps/dashboard/AppLayout.tsx @@ -1,15 +1,103 @@ -import React from 'react'; -import { Outlet } from 'react-router-dom'; +import AppBar from '@mui/material/AppBar'; +import Box from '@mui/material/Box'; +import { useTheme } from '@mui/material/styles'; +import React, { useCallback, useEffect, useState } from 'react'; +import { Outlet, useLocation } from 'react-router-dom'; import AppBody from 'components/AppBody'; +import ElevationScroll from 'components/ElevationScroll'; +import { DRAWER_WIDTH } from 'components/ResponsiveDrawer'; +import { useApi } from 'hooks/useApi'; +import { useLocalStorage } from 'hooks/useLocalStorage'; +import AppDrawer from './components/drawer/AppDrawer'; + +// FIXME: Remove main app override styles import '../experimental/AppOverrides.scss'; +import AppToolbar from 'components/toolbar/AppToolbar'; + +interface DashboardAppSettings { + isDrawerPinned: boolean +} + +const DEFAULT_APP_SETTINGS: DashboardAppSettings = { + isDrawerPinned: false +}; const AppLayout = () => { + const [ appSettings, setAppSettings ] = useLocalStorage('DashboardAppSettings', DEFAULT_APP_SETTINGS); + const [ isDrawerActive, setIsDrawerActive ] = useState(appSettings.isDrawerPinned); + const location = useLocation(); + const theme = useTheme(); + const { user } = useApi(); + + // FIXME: Use const for metadata editor + const isDrawerAvailable = !location.pathname.startsWith('/metadata'); + const isDrawerOpen = isDrawerActive && isDrawerAvailable && Boolean(user); + + useEffect(() => { + if (isDrawerActive !== appSettings.isDrawerPinned) { + setAppSettings({ + ...appSettings, + isDrawerPinned: isDrawerActive + }); + } + }, [ appSettings, isDrawerActive, setAppSettings ]); + + const onToggleDrawer = useCallback(() => { + setIsDrawerActive(!isDrawerActive); + }, [ isDrawerActive, setIsDrawerActive ]); + return ( - - - + + + muiTheme.zIndex.drawer + 1 }} + > + + + + + + + + + + + + ); }; diff --git a/src/apps/dashboard/components/drawer/AppDrawer.tsx b/src/apps/dashboard/components/drawer/AppDrawer.tsx new file mode 100644 index 000000000..7b1e18012 --- /dev/null +++ b/src/apps/dashboard/components/drawer/AppDrawer.tsx @@ -0,0 +1,29 @@ +import React, { FC } from 'react'; + +import ResponsiveDrawer, { ResponsiveDrawerProps } from 'components/ResponsiveDrawer'; + +import ServerDrawerSection from './sections/ServerDrawerSection'; +import DevicesDrawerSection from './sections/DevicesDrawerSection'; +import LiveTvDrawerSection from './sections/LiveTvDrawerSection'; +import AdvancedDrawerSection from './sections/AdvancedDrawerSection'; +import PluginDrawerSection from './sections/PluginDrawerSection'; + +const AppDrawer: FC = ({ + open = false, + onClose, + onOpen +}) => ( + + + + + + + +); + +export default AppDrawer; diff --git a/src/apps/experimental/components/drawers/dashboard/AdvancedDrawerSection.tsx b/src/apps/dashboard/components/drawer/sections/AdvancedDrawerSection.tsx similarity index 100% rename from src/apps/experimental/components/drawers/dashboard/AdvancedDrawerSection.tsx rename to src/apps/dashboard/components/drawer/sections/AdvancedDrawerSection.tsx diff --git a/src/apps/experimental/components/drawers/dashboard/DevicesDrawerSection.tsx b/src/apps/dashboard/components/drawer/sections/DevicesDrawerSection.tsx similarity index 100% rename from src/apps/experimental/components/drawers/dashboard/DevicesDrawerSection.tsx rename to src/apps/dashboard/components/drawer/sections/DevicesDrawerSection.tsx diff --git a/src/apps/experimental/components/drawers/dashboard/LiveTvDrawerSection.tsx b/src/apps/dashboard/components/drawer/sections/LiveTvDrawerSection.tsx similarity index 100% rename from src/apps/experimental/components/drawers/dashboard/LiveTvDrawerSection.tsx rename to src/apps/dashboard/components/drawer/sections/LiveTvDrawerSection.tsx diff --git a/src/apps/experimental/components/drawers/dashboard/PluginDrawerSection.tsx b/src/apps/dashboard/components/drawer/sections/PluginDrawerSection.tsx similarity index 100% rename from src/apps/experimental/components/drawers/dashboard/PluginDrawerSection.tsx rename to src/apps/dashboard/components/drawer/sections/PluginDrawerSection.tsx diff --git a/src/apps/experimental/components/drawers/dashboard/ServerDrawerSection.tsx b/src/apps/dashboard/components/drawer/sections/ServerDrawerSection.tsx similarity index 100% rename from src/apps/experimental/components/drawers/dashboard/ServerDrawerSection.tsx rename to src/apps/dashboard/components/drawer/sections/ServerDrawerSection.tsx diff --git a/src/apps/experimental/components/drawers/AppDrawer.tsx b/src/apps/experimental/components/drawers/AppDrawer.tsx index dc48221b8..f03a59167 100644 --- a/src/apps/experimental/components/drawers/AppDrawer.tsx +++ b/src/apps/experimental/components/drawers/AppDrawer.tsx @@ -1,23 +1,15 @@ import React, { FC } from 'react'; -import { Route, Routes, useLocation } from 'react-router-dom'; +import { useLocation } from 'react-router-dom'; import ResponsiveDrawer, { ResponsiveDrawerProps } from 'components/ResponsiveDrawer'; import { ASYNC_USER_ROUTES } from '../../routes/asyncRoutes'; import { LEGACY_USER_ROUTES } from '../../routes/legacyRoutes'; -import AdvancedDrawerSection from './dashboard/AdvancedDrawerSection'; -import DevicesDrawerSection from './dashboard/DevicesDrawerSection'; -import LiveTvDrawerSection from './dashboard/LiveTvDrawerSection'; -import PluginDrawerSection from './dashboard/PluginDrawerSection'; -import ServerDrawerSection from './dashboard/ServerDrawerSection'; import MainDrawerContent from './MainDrawerContent'; import { isTabPath } from '../tabs/tabRoutes'; -export const DRAWER_WIDTH = 240; - const DRAWERLESS_ROUTES = [ - 'metadata', // metadata manager 'video' // video player ]; @@ -26,75 +18,29 @@ const MAIN_DRAWER_ROUTES = [ ...LEGACY_USER_ROUTES ].filter(route => !DRAWERLESS_ROUTES.includes(route.path)); -const ADMIN_DRAWER_ROUTES = [ - { path: '/configurationpage' } // Plugin configuration page -].filter(route => !DRAWERLESS_ROUTES.includes(route.path)); - /** Utility function to check if a path has a drawer. */ export const isDrawerPath = (path: string) => ( MAIN_DRAWER_ROUTES.some(route => route.path === path || `/${route.path}` === path) - || ADMIN_DRAWER_ROUTES.some(route => route.path === path || `/${route.path}` === path) ); -const Drawer: FC = ({ children, ...props }) => { - const location = useLocation(); - const hasSecondaryToolBar = isTabPath(location.pathname); - - return ( - - {children} - - ); -}; - const AppDrawer: FC = ({ open = false, onClose, onOpen -}) => ( - - { - MAIN_DRAWER_ROUTES.map(route => ( - - - - } - /> - )) - } - { - ADMIN_DRAWER_ROUTES.map(route => ( - - - - - - - - } - /> - )) - } - -); +}) => { + const location = useLocation(); + const hasSecondaryToolBar = isTabPath(location.pathname); + + return ( + + + + ); +}; export default AppDrawer; From 35457b5abb2f4d8342bdbda942fda383c1d468a9 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Mon, 25 Sep 2023 13:54:33 -0400 Subject: [PATCH 101/142] Extract style overrides for dashboard --- src/apps/dashboard/App.tsx | 2 +- src/apps/dashboard/AppLayout.tsx | 18 +++++++++++------- src/apps/dashboard/AppOverrides.scss | 22 ++++++++++++++++++++++ src/apps/experimental/AppOverrides.scss | 8 +------- 4 files changed, 35 insertions(+), 15 deletions(-) create mode 100644 src/apps/dashboard/AppOverrides.scss diff --git a/src/apps/dashboard/App.tsx b/src/apps/dashboard/App.tsx index db1776509..30e3eda94 100644 --- a/src/apps/dashboard/App.tsx +++ b/src/apps/dashboard/App.tsx @@ -34,7 +34,7 @@ export const DASHBOARD_APP_PATHS = { const DashboardApp = () => ( }> - }> + }> {ASYNC_ADMIN_ROUTES.map(toDashboardAsyncPageRoute)} {LEGACY_ADMIN_ROUTES.map(toViewManagerPageRoute)} diff --git a/src/apps/dashboard/AppLayout.tsx b/src/apps/dashboard/AppLayout.tsx index 7afe655a3..ce74f4989 100644 --- a/src/apps/dashboard/AppLayout.tsx +++ b/src/apps/dashboard/AppLayout.tsx @@ -1,10 +1,11 @@ import AppBar from '@mui/material/AppBar'; import Box from '@mui/material/Box'; import { useTheme } from '@mui/material/styles'; -import React, { useCallback, useEffect, useState } from 'react'; +import React, { FC, useCallback, useEffect, useState } from 'react'; import { Outlet, useLocation } from 'react-router-dom'; import AppBody from 'components/AppBody'; +import AppToolbar from 'components/toolbar/AppToolbar'; import ElevationScroll from 'components/ElevationScroll'; import { DRAWER_WIDTH } from 'components/ResponsiveDrawer'; import { useApi } from 'hooks/useApi'; @@ -12,9 +13,11 @@ import { useLocalStorage } from 'hooks/useLocalStorage'; import AppDrawer from './components/drawer/AppDrawer'; -// FIXME: Remove main app override styles -import '../experimental/AppOverrides.scss'; -import AppToolbar from 'components/toolbar/AppToolbar'; +import './AppOverrides.scss'; + +interface AppLayoutProps { + drawerlessPaths: string[] +} interface DashboardAppSettings { isDrawerPinned: boolean @@ -24,15 +27,16 @@ const DEFAULT_APP_SETTINGS: DashboardAppSettings = { isDrawerPinned: false }; -const AppLayout = () => { +const AppLayout: FC = ({ + drawerlessPaths +}) => { const [ appSettings, setAppSettings ] = useLocalStorage('DashboardAppSettings', DEFAULT_APP_SETTINGS); const [ isDrawerActive, setIsDrawerActive ] = useState(appSettings.isDrawerPinned); const location = useLocation(); const theme = useTheme(); const { user } = useApi(); - // FIXME: Use const for metadata editor - const isDrawerAvailable = !location.pathname.startsWith('/metadata'); + const isDrawerAvailable = !drawerlessPaths.some(path => location.pathname.startsWith(`/${path}`)); const isDrawerOpen = isDrawerActive && isDrawerAvailable && Boolean(user); useEffect(() => { diff --git a/src/apps/dashboard/AppOverrides.scss b/src/apps/dashboard/AppOverrides.scss new file mode 100644 index 000000000..c8597ee57 --- /dev/null +++ b/src/apps/dashboard/AppOverrides.scss @@ -0,0 +1,22 @@ +// Default MUI breakpoints +// https://mui.com/material-ui/customization/breakpoints/#default-breakpoints +$mui-bp-sm: 600px; +$mui-bp-md: 900px; +$mui-bp-lg: 1200px; +$mui-bp-xl: 1536px; + +// Fix dashboard pages layout to work with drawer +.dashboardDocument { + .mainAnimatedPage { + position: relative; + } + + .skinBody { + position: unset !important; + } + + // Fix the padding of dashboard pages + .content-primary.content-primary { + padding-top: 3.25rem !important; + } +} diff --git a/src/apps/experimental/AppOverrides.scss b/src/apps/experimental/AppOverrides.scss index c365a5b29..cece6608c 100644 --- a/src/apps/experimental/AppOverrides.scss +++ b/src/apps/experimental/AppOverrides.scss @@ -10,11 +10,6 @@ $mui-bp-xl: 1536px; position: relative; } -// Fix dashboard pages layout to work with drawer -.dashboardDocument .skinBody { - position: unset; -} - // Hide some items from the user "settings" page that are in the drawer #myPreferencesMenuPage { .lnkQuickConnectPreferences, @@ -26,8 +21,7 @@ $mui-bp-xl: 1536px; // Fix the padding of some pages .homePage.libraryPage, // Home page -.libraryPage:not(.withTabs), // Tabless library pages -.content-primary.content-primary { // Dashboard pages +.libraryPage:not(.withTabs) { // Tabless library pages padding-top: 3.25rem !important; } From b4b57f5e55ea732ae3e3763a2ffb243de24d3a61 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Tue, 26 Sep 2023 00:37:48 -0400 Subject: [PATCH 102/142] Update metadata editor comment --- src/apps/dashboard/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apps/dashboard/App.tsx b/src/apps/dashboard/App.tsx index 30e3eda94..7c5215725 100644 --- a/src/apps/dashboard/App.tsx +++ b/src/apps/dashboard/App.tsx @@ -40,7 +40,7 @@ const DashboardApp = () => ( {LEGACY_ADMIN_ROUTES.map(toViewManagerPageRoute)} - {/* TODO: Should the metadata manager be a separate app? */} + {/* NOTE: The metadata editor might deserve a dedicated app in the future */} {toViewManagerPageRoute({ path: DASHBOARD_APP_PATHS.MetadataManager, pageProps: { From 6f1b1dfd844b927e3836b87bd0c8b29fb5581012 Mon Sep 17 00:00:00 2001 From: Oskari Lavinto Date: Sat, 30 Sep 2023 00:31:55 +0000 Subject: [PATCH 103/142] Translated using Weblate (Finnish) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fi/ --- src/strings/fi.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/strings/fi.json b/src/strings/fi.json index 77da53d7d..4ecc91506 100644 --- a/src/strings/fi.json +++ b/src/strings/fi.json @@ -1773,5 +1773,6 @@ "GoHome": "Siirry aloitusnäyttöön", "UnknownError": "Tapahtui tuntematon virhe.", "BackdropScreensaver": "Backdrop-näytönsäästäjä", - "LogoScreensaver": "Logo-näytönsäästäjä" + "LogoScreensaver": "Logo-näytönsäästäjä", + "LabelIsHearingImpaired": "Kuulorajoitteisille (SDH)" } From 187cefdcb13392c9f29cdda819766a67a0b916b3 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Sat, 30 Sep 2023 01:17:37 -0400 Subject: [PATCH 104/142] Update import order --- src/RootApp.tsx | 6 +++--- src/apps/dashboard/App.tsx | 2 +- src/apps/experimental/App.tsx | 2 +- src/apps/experimental/components/drawers/AppDrawer.tsx | 2 +- src/apps/stable/App.tsx | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/RootApp.tsx b/src/RootApp.tsx index 26f12eb39..cc10ca7ba 100644 --- a/src/RootApp.tsx +++ b/src/RootApp.tsx @@ -1,18 +1,18 @@ import loadable from '@loadable/component'; import { ThemeProvider } from '@mui/material/styles'; import { History } from '@remix-run/router'; -import React from 'react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; +import React from 'react'; +import { useLocation } from 'react-router-dom'; +import { DASHBOARD_APP_PATHS } from 'apps/dashboard/App'; import AppHeader from 'components/AppHeader'; import Backdrop from 'components/Backdrop'; import { HistoryRouter } from 'components/router/HistoryRouter'; import { ApiProvider } from 'hooks/useApi'; import { WebConfigProvider } from 'hooks/useWebConfig'; import theme from 'themes/theme'; -import { useLocation } from 'react-router-dom'; -import { DASHBOARD_APP_PATHS } from './apps/dashboard/App'; const DashboardApp = loadable(() => import('./apps/dashboard/App')); const ExperimentalApp = loadable(() => import('./apps/experimental/App')); diff --git a/src/apps/dashboard/App.tsx b/src/apps/dashboard/App.tsx index 7c5215725..c5ab67929 100644 --- a/src/apps/dashboard/App.tsx +++ b/src/apps/dashboard/App.tsx @@ -8,10 +8,10 @@ import { AsyncPageProps, AsyncRoute, toAsyncPageRoute } from 'components/router/ import { toRedirectRoute } from 'components/router/Redirect'; import ServerContentPage from 'components/ServerContentPage'; +import AppLayout from './AppLayout'; import { REDIRECTS } from './routes/_redirects'; import { ASYNC_ADMIN_ROUTES } from './routes/_asyncRoutes'; import { LEGACY_ADMIN_ROUTES } from './routes/_legacyRoutes'; -import AppLayout from './AppLayout'; const DashboardAsyncPage = loadable( (props: { page: string }) => import(/* webpackChunkName: "[request]" */ `./routes/${props.page}`), diff --git a/src/apps/experimental/App.tsx b/src/apps/experimental/App.tsx index d804352f9..b17e9054e 100644 --- a/src/apps/experimental/App.tsx +++ b/src/apps/experimental/App.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { Navigate, Route, Routes } from 'react-router-dom'; +import { DASHBOARD_APP_PATHS } from 'apps/dashboard/App'; import { REDIRECTS } from 'apps/stable/routes/_redirects'; import ConnectionRequired from 'components/ConnectionRequired'; import { toAsyncPageRoute } from 'components/router/AsyncRoute'; @@ -10,7 +11,6 @@ import { toRedirectRoute } from 'components/router/Redirect'; import AppLayout from './AppLayout'; import { ASYNC_USER_ROUTES } from './routes/asyncRoutes'; import { LEGACY_PUBLIC_ROUTES, LEGACY_USER_ROUTES } from './routes/legacyRoutes'; -import { DASHBOARD_APP_PATHS } from 'apps/dashboard/App'; const ExperimentalApp = () => { return ( diff --git a/src/apps/experimental/components/drawers/AppDrawer.tsx b/src/apps/experimental/components/drawers/AppDrawer.tsx index f03a59167..21926d6c5 100644 --- a/src/apps/experimental/components/drawers/AppDrawer.tsx +++ b/src/apps/experimental/components/drawers/AppDrawer.tsx @@ -5,9 +5,9 @@ import ResponsiveDrawer, { ResponsiveDrawerProps } from 'components/ResponsiveDr import { ASYNC_USER_ROUTES } from '../../routes/asyncRoutes'; import { LEGACY_USER_ROUTES } from '../../routes/legacyRoutes'; +import { isTabPath } from '../tabs/tabRoutes'; import MainDrawerContent from './MainDrawerContent'; -import { isTabPath } from '../tabs/tabRoutes'; const DRAWERLESS_ROUTES = [ 'video' // video player diff --git a/src/apps/stable/App.tsx b/src/apps/stable/App.tsx index b0219312a..9b0adbab9 100644 --- a/src/apps/stable/App.tsx +++ b/src/apps/stable/App.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { Navigate, Outlet, Route, Routes } from 'react-router-dom'; +import { DASHBOARD_APP_PATHS } from 'apps/dashboard/App'; import AppBody from 'components/AppBody'; import ConnectionRequired from 'components/ConnectionRequired'; import { toAsyncPageRoute } from 'components/router/AsyncRoute'; @@ -10,7 +11,6 @@ import { toRedirectRoute } from 'components/router/Redirect'; import { ASYNC_USER_ROUTES } from './routes/asyncRoutes'; import { LEGACY_PUBLIC_ROUTES, LEGACY_USER_ROUTES } from './routes/legacyRoutes'; import { REDIRECTS } from './routes/_redirects'; -import { DASHBOARD_APP_PATHS } from 'apps/dashboard/App'; const Layout = () => ( From 189904256d9eb3473a05c6106ae5b197f5124812 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Sat, 30 Sep 2023 01:18:03 -0400 Subject: [PATCH 105/142] Remove old activity dashboard page --- src/controllers/dashboard/serveractivity.html | 12 ------- src/controllers/dashboard/serveractivity.js | 32 ------------------- 2 files changed, 44 deletions(-) delete mode 100644 src/controllers/dashboard/serveractivity.html delete mode 100644 src/controllers/dashboard/serveractivity.js diff --git a/src/controllers/dashboard/serveractivity.html b/src/controllers/dashboard/serveractivity.html deleted file mode 100644 index 29cacd300..000000000 --- a/src/controllers/dashboard/serveractivity.html +++ /dev/null @@ -1,12 +0,0 @@ -
-
-
-
-

-
-
-
-
-
-
-
diff --git a/src/controllers/dashboard/serveractivity.js b/src/controllers/dashboard/serveractivity.js deleted file mode 100644 index 72e84a89d..000000000 --- a/src/controllers/dashboard/serveractivity.js +++ /dev/null @@ -1,32 +0,0 @@ -import ActivityLog from '../../components/activitylog'; -import globalize from '../../scripts/globalize'; -import { toBoolean } from '../../utils/string.ts'; - -export default function (view, params) { - let activityLog; - - if (toBoolean(params.useractivity, true)) { - view.querySelector('.activityItems').setAttribute('data-useractivity', 'true'); - view.querySelector('.sectionTitle').innerHTML = globalize.translate('HeaderActivity'); - } else { - view.querySelector('.activityItems').setAttribute('data-useractivity', 'false'); - view.querySelector('.sectionTitle').innerHTML = globalize.translate('Alerts'); - } - - view.addEventListener('viewshow', function () { - if (!activityLog) { - activityLog = new ActivityLog({ - serverId: ApiClient.serverId(), - element: view.querySelector('.activityItems') - }); - } - }); - view.addEventListener('viewdestroy', function () { - if (activityLog) { - activityLog.destroy(); - } - - activityLog = null; - }); -} - From 81372903083c4e810992a7b7c7e62180a6104e6e Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Sat, 30 Sep 2023 01:49:15 -0400 Subject: [PATCH 106/142] Fix minimum npm version --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5584abb5f..f32057882 100644 --- a/package-lock.json +++ b/package-lock.json @@ -126,7 +126,7 @@ }, "engines": { "node": ">=20.0.0", - "npm": ">=10.0.0", + "npm": ">=9.6.4", "yarn": "YARN NO LONGER USED - use npm instead." } }, diff --git a/package.json b/package.json index 460b59cbc..378942f26 100644 --- a/package.json +++ b/package.json @@ -151,7 +151,7 @@ }, "engines": { "node": ">=20.0.0", - "npm": ">=10.0.0", + "npm": ">=9.6.4", "yarn": "YARN NO LONGER USED - use npm instead." } } From 2b8912e97d35904dad97e7587217b3a110c96293 Mon Sep 17 00:00:00 2001 From: queeup Date: Sat, 30 Sep 2023 09:32:28 +0000 Subject: [PATCH 107/142] Translated using Weblate (Turkish) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/tr/ --- src/strings/tr.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/strings/tr.json b/src/strings/tr.json index af792eead..8769e19db 100644 --- a/src/strings/tr.json +++ b/src/strings/tr.json @@ -18,8 +18,8 @@ "ButtonRemove": "Sil", "ButtonSelectDirectory": "Dosyayı Seç", "ButtonSend": "Gönder", - "ButtonSignIn": "Giriş Yap", - "ButtonSignOut": "Çıkış Yap", + "ButtonSignIn": "Oturum Aç", + "ButtonSignOut": "Oturumu Kapat", "ButtonStop": "Durdur", "ChannelAccessHelp": "Bu kullanıcıyla paylaşmak üzere kanalları seç. Yöneticiler bütün kanalları metada yöneticisi ile düzenleyebilecekler.", "Continuing": "Devam ediyor", @@ -49,7 +49,7 @@ "HeaderLibraryFolders": "Media Klasörleri", "HeaderMediaFolders": "Medya Klasörleri", "HeaderPlayAll": "Hepsini Oynat", - "HeaderPleaseSignIn": "Lütfen Giriş Yapın", + "HeaderPleaseSignIn": "Lütfen oturum açın", "HeaderPreferredMetadataLanguage": "Tercih Edilen Metaveri Dili", "HeaderRecentlyPlayed": "En Son Oynatılanlar", "HeaderRemoteControl": "Uzaktan Kontrol", @@ -1297,7 +1297,7 @@ "LabelSyncPlaySettingsSkipToSyncHelp": "Tahmini konumu aramayı içeren senkronizasyon düzeltme yöntemi. Senkronizasyon Düzeltme etkinleştirilmelidir.", "LabelSyncPlaySettingsSkipToSync": "SkipToSync'i Etkinleştir", "LabelSyncPlaySettingsSpeedToSyncHelp": "Oynatmayı hızlandırmayı içeren senkronizasyon düzeltme yöntemi. Senkronizasyon Düzeltme etkinleştirilmelidir.", - "ButtonExitApp": "Çıkış uygulaması", + "ButtonExitApp": "Uygulamadan Çık", "ShowAdvancedSettings": "Gelişmiş ayarları göster", "MediaInfoTitle": "Başlık", "Track": "Parça", From f173824c9388553b7882e917926d8ad342f973cf Mon Sep 17 00:00:00 2001 From: HiSkyZen Date: Sun, 1 Oct 2023 03:11:51 +0000 Subject: [PATCH 108/142] Translated using Weblate (Korean) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/ko/ --- src/strings/ko.json | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/strings/ko.json b/src/strings/ko.json index 404cb7150..798c0faab 100644 --- a/src/strings/ko.json +++ b/src/strings/ko.json @@ -1219,7 +1219,7 @@ "QuickConnectDescription": "퀵커넥트로 로그인하려면 로그인중인 장치에서 퀵커넥트 버튼을 선택하고 아래 표시된 코드를 입력하십시오.", "QuickConnectDeactivated": "로그인 요청이 승인되기 전에 Quick connect가 비활성화되었습니다", "QuickConnectAuthorizeFail": "알수없는 퀵커넥트 코드", - "QuickConnectAuthorizeSuccess": "승인 요청", + "QuickConnectAuthorizeSuccess": "기기가 성공적으로 인증되었습니다!", "QuickConnectAuthorizeCode": "로그인하려면 {0} 코드를 입력하세요", "QuickConnectActivationSuccessful": "성공적으로 활성화되었습니다", "QuickConnect": "퀵커넥트", @@ -1349,7 +1349,7 @@ "ResumeAt": "{0}에서 재생", "MessageChangeRecordingPath": "녹음 폴더를 변경해도 기존 녹음은 이전 위치에서 새 위치로 마이그레이션되지 않습니다. 원하는 경우 수동으로 이동해야합니다.", "Premieres": "첫날", - "AllowTonemappingHelp": "톤 매핑은 원본 장면을 보여주는데 매우 중요한 정보인 이미지 디테일과 색을 유지하면서 HDR에서 SDR로 비디오의 다이나믹 레인지를 변환할 수 있습니다. 현재 10비트 HDR10, HLG, DoVi가 지원되는 비디오에만 작동합니다. 이 기능은 해당되는 OpenCL이나 CUDA 런타임이 필요합니다.", + "AllowTonemappingHelp": "톤 매핑은 원본 장면을 재현하는데 매우 중요한 정보인 이미지 디테일과 색을 유지하면서 HDR에서 SDR로 비디오의 다이나믹 레인지를 변환할 수 있습니다. 현재 10비트 HDR10, HLG, DoVi가 지원되는 비디오에만 작동합니다. 이 기능은 해당되는 OpenCL이나 CUDA 런타임이 필요합니다.", "EnableTonemapping": "톤 매핑 활성화", "LabelOpenclDeviceHelp": "이것은 톤 매핑에 사용되는 OpenCL 장치입니다. 점의 왼쪽은 플랫폼 번호이고 오른쪽은 플랫폼의 장치 번호입니다. 기본값은 0.0입니다. OpenCL 하드웨어 가속 방법이 포함 된 ffmpeg 응용프로그램 파일이 필요합니다.", "LabelOpenclDevice": "OpenCL 장치", @@ -1691,7 +1691,7 @@ "LabelHardwareEncodingOptions": "하드웨어 인코딩 옵션", "VideoBitrateNotSupported": "비디오의 비트레이트가 지원되지 않습니다", "LabelEnableLUFSScan": "LUFS 분석 활성화", - "LabelEnableLUFSScanHelp": "음악에 대한 LUFS 분석 활성화 (더 많은 시간과 리소스 사용).", + "LabelEnableLUFSScanHelp": "클라이언트가 트랙들이 일정한 음량을 갖도록 오디오 트랙을 평준화할 수 있게 합니다. 라이브러리 스캔에 더 많은 시간과 리소스가 사용됩니다.", "MessageRenameMediaFolder": "미디어 라이브러리의 이름을 바꾸면 모든 메타데이터가 손실되므로 주의하여 진행하세요.", "PreferEmbeddedExtrasTitlesOverFileNames": "엑스트라의 경우 파일 이름보다 내장된 제목을 선호", "ReleaseGroup": "발매 그룹", @@ -1758,5 +1758,20 @@ "SubtitleWhite": "하양", "UnknownAudioStreamInfo": "오디오 스트림 정보를 알 수 없습니다", "MediaInfoBlPresentFlag": "DV bl 프리셋 플래그", - "TonemappingModeHelp": "톤 매핑 모드를 선택하세요. 하이라이트 부분이 날아가는 경우 RGB 모드로 전환해 보세요." + "TonemappingModeHelp": "톤 매핑 모드를 선택하세요. 하이라이트 부분이 날아가는 경우 RGB 모드로 전환해 보세요.", + "LabelIsHearingImpaired": "청각 장애인용 (SDH)", + "AllowAv1Encoding": "AV1 포맷으로 인코딩 허용", + "GoHome": "홈으로", + "GridView": "그리드 뷰", + "LabelBackdropScreensaverInterval": "배경 화면보호기 간격", + "LabelBackdropScreensaverIntervalHelp": "배경 화면보호기를 사용할 때 서로 다른 배경으로 바뀌는 간격 시간(초)", + "ListView": "리스트 뷰", + "BackdropScreensaver": "배경 화면보호기", + "LogoScreensaver": "로고 화면보호기", + "UnknownError": "알 수 없는 에러가 발생하였습니다.", + "AiTranslated": "AI 번역", + "MachineTranslated": "기계 번역", + "ForeignPartsOnly": "강제/외부 파트만", + "HearingImpairedShort": "청각장애/SDH", + "HeaderGuestCast": "게스트" } From 1e3fa5418c5fbeafd0e5d708149c7c27bbaa52a1 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Sun, 1 Oct 2023 02:49:36 -0400 Subject: [PATCH 109/142] Remove duplicate card shape functions --- src/components/cardbuilder/cardBuilder.js | 39 +++++----- src/components/favoriteitems.js | 44 +++++------ src/components/homesections/homesections.js | 51 ++++++------- src/controllers/favorites.js | 56 ++++++-------- src/controllers/itemDetails/index.js | 85 +++++++++------------ src/controllers/livetv/livetvrecordings.js | 18 +++-- src/controllers/livetv/livetvschedule.js | 24 +++--- src/controllers/livetv/livetvsuggested.js | 49 +++++------- src/controllers/movies/moviegenres.js | 35 ++++----- src/controllers/movies/moviesrecommended.js | 50 ++++++------ src/controllers/music/musicrecommended.js | 48 ++++++------ src/controllers/shows/tvgenres.js | 35 ++++----- src/controllers/shows/tvrecommended.js | 42 +++++----- src/controllers/shows/tvupcoming.js | 25 +++--- src/scripts/livetvcomponents.js | 12 ++- src/utils/card.ts | 20 +++++ 16 files changed, 294 insertions(+), 339 deletions(-) create mode 100644 src/utils/card.ts diff --git a/src/components/cardbuilder/cardBuilder.js b/src/components/cardbuilder/cardBuilder.js index 96d7edb06..c2f5436b3 100644 --- a/src/components/cardbuilder/cardBuilder.js +++ b/src/components/cardbuilder/cardBuilder.js @@ -5,24 +5,29 @@ */ import escapeHtml from 'escape-html'; -import datetime from '../../scripts/datetime'; -import imageLoader from '../images/imageLoader'; -import itemHelper from '../itemHelper'; + +import browser from 'scripts/browser'; +import datetime from 'scripts/datetime'; +import dom from 'scripts/dom'; +import globalize from 'scripts/globalize'; +import imageHelper from 'scripts/imagehelper'; +import { getBackdropShape, getPortraitShape, getSquareShape } from 'utils/card'; +import { randomInt } from 'utils/number'; + import focusManager from '../focusManager'; +import imageLoader from '../images/imageLoader'; import indicators from '../indicators/indicators'; -import globalize from '../../scripts/globalize'; +import itemHelper from '../itemHelper'; import layoutManager from '../layoutManager'; -import dom from '../../scripts/dom'; -import browser from '../../scripts/browser'; import { playbackManager } from '../playback/playbackmanager'; -import itemShortcuts from '../shortcuts'; -import imageHelper from '../../scripts/imagehelper'; -import { randomInt } from '../../utils/number.ts'; -import './card.scss'; -import '../../elements/emby-button/paper-icon-button-light'; -import '../guide/programs.scss'; -import ServerConnections from '../ServerConnections'; import { appRouter } from '../router/appRouter'; +import ServerConnections from '../ServerConnections'; +import itemShortcuts from '../shortcuts'; + +import 'elements/emby-button/paper-icon-button-light'; + +import './card.scss'; +import '../guide/programs.scss'; const enableFocusTransform = !browser.slow && !browser.edge; @@ -301,16 +306,16 @@ function setCardData(items, options) { options.shape = 'banner'; options.coverImage = true; } else if (primaryImageAspectRatio >= 1.33) { - options.shape = requestedShape === 'autooverflow' ? 'overflowBackdrop' : 'backdrop'; + options.shape = getBackdropShape(requestedShape === 'autooverflow'); } else if (primaryImageAspectRatio > 0.71) { - options.shape = requestedShape === 'autooverflow' ? 'overflowSquare' : 'square'; + options.shape = getSquareShape(requestedShape === 'autooverflow'); } else { - options.shape = requestedShape === 'autooverflow' ? 'overflowPortrait' : 'portrait'; + options.shape = getPortraitShape(requestedShape === 'autooverflow'); } } if (!options.shape) { - options.shape = options.defaultShape || (requestedShape === 'autooverflow' ? 'overflowSquare' : 'square'); + options.shape = options.defaultShape || getSquareShape(requestedShape === 'autooverflow'); } } diff --git a/src/components/favoriteitems.js b/src/components/favoriteitems.js index ac0f3c0de..b26c25ede 100644 --- a/src/components/favoriteitems.js +++ b/src/components/favoriteitems.js @@ -1,50 +1,42 @@ -import loading from './loading/loading'; -import cardBuilder from './cardbuilder/cardBuilder'; -import dom from '../scripts/dom'; +import dom from 'scripts/dom'; +import globalize from 'scripts/globalize'; +import { getBackdropShape, getPortraitShape, getSquareShape } from 'utils/card'; +import { getParameterByName } from 'utils/url'; + import { appHost } from './apphost'; +import cardBuilder from './cardbuilder/cardBuilder'; import imageLoader from './images/imageLoader'; -import globalize from '../scripts/globalize'; import layoutManager from './layoutManager'; -import { getParameterByName } from '../utils/url.ts'; -import '../styles/scrollstyles.scss'; -import '../elements/emby-itemscontainer/emby-itemscontainer'; +import loading from './loading/loading'; + +import 'elements/emby-itemscontainer/emby-itemscontainer'; + +import 'styles/scrollstyles.scss'; function enableScrollX() { return !layoutManager.desktop; } -function getThumbShape() { - return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; -} - -function getPosterShape() { - return enableScrollX() ? 'overflowPortrait' : 'portrait'; -} - -function getSquareShape() { - return enableScrollX() ? 'overflowSquare' : 'square'; -} - function getSections() { return [{ name: 'Movies', types: 'Movie', id: 'favoriteMovies', - shape: getPosterShape(), + shape: getPortraitShape(enableScrollX()), showTitle: false, overlayPlayButton: true }, { name: 'Shows', types: 'Series', id: 'favoriteShows', - shape: getPosterShape(), + shape: getPortraitShape(enableScrollX()), showTitle: false, overlayPlayButton: true }, { name: 'Episodes', types: 'Episode', id: 'favoriteEpisode', - shape: getThumbShape(), + shape: getBackdropShape(enableScrollX()), preferThumb: false, showTitle: true, showParentTitle: true, @@ -55,7 +47,7 @@ function getSections() { name: 'Videos', types: 'Video,MusicVideo', id: 'favoriteVideos', - shape: getThumbShape(), + shape: getBackdropShape(enableScrollX()), preferThumb: true, showTitle: true, overlayPlayButton: true, @@ -65,7 +57,7 @@ function getSections() { name: 'Artists', types: 'MusicArtist', id: 'favoriteArtists', - shape: getSquareShape(), + shape: getSquareShape(enableScrollX()), preferThumb: false, showTitle: true, overlayText: false, @@ -77,7 +69,7 @@ function getSections() { name: 'Albums', types: 'MusicAlbum', id: 'favoriteAlbums', - shape: getSquareShape(), + shape: getSquareShape(enableScrollX()), preferThumb: false, showTitle: true, overlayText: false, @@ -89,7 +81,7 @@ function getSections() { name: 'Songs', types: 'Audio', id: 'favoriteSongs', - shape: getSquareShape(), + shape: getSquareShape(enableScrollX()), preferThumb: false, showTitle: true, overlayText: false, diff --git a/src/components/homesections/homesections.js b/src/components/homesections/homesections.js index 212d976a8..6f7be53f6 100644 --- a/src/components/homesections/homesections.js +++ b/src/components/homesections/homesections.js @@ -1,18 +1,23 @@ import escapeHtml from 'escape-html'; + +import globalize from 'scripts/globalize'; +import imageHelper from 'scripts/imagehelper'; +import { getBackdropShape, getPortraitShape, getSquareShape } from 'utils/card'; +import Dashboard from 'utils/dashboard'; + import cardBuilder from '../cardbuilder/cardBuilder'; -import layoutManager from '../layoutManager'; import imageLoader from '../images/imageLoader'; -import globalize from '../../scripts/globalize'; +import layoutManager from '../layoutManager'; import { appRouter } from '../router/appRouter'; -import imageHelper from '../../scripts/imagehelper'; -import '../../elements/emby-button/paper-icon-button-light'; -import '../../elements/emby-itemscontainer/emby-itemscontainer'; -import '../../elements/emby-scroller/emby-scroller'; -import '../../elements/emby-button/emby-button'; -import './homesections.scss'; -import Dashboard from '../../utils/dashboard'; import ServerConnections from '../ServerConnections'; +import 'elements/emby-button/paper-icon-button-light'; +import 'elements/emby-itemscontainer/emby-itemscontainer'; +import 'elements/emby-scroller/emby-scroller'; +import 'elements/emby-button/emby-button'; + +import './homesections.scss'; + export function getDefaultSection(index) { switch (index) { case 0: @@ -169,18 +174,6 @@ function enableScrollX() { return true; } -function getSquareShape() { - return enableScrollX() ? 'overflowSquare' : 'square'; -} - -function getThumbShape() { - return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; -} - -function getPortraitShape() { - return enableScrollX() ? 'overflowPortrait' : 'portrait'; -} - function getLibraryButtonsHtml(items) { let html = ''; @@ -244,11 +237,11 @@ function getLatestItemsHtmlFn(itemType, viewType) { const cardLayout = false; let shape; if (itemType === 'Channel' || viewType === 'movies' || viewType === 'books' || viewType === 'tvshows') { - shape = getPortraitShape(); + shape = getPortraitShape(enableScrollX()); } else if (viewType === 'music' || viewType === 'homevideos') { - shape = getSquareShape(); + shape = getSquareShape(enableScrollX()); } else { - shape = getThumbShape(); + shape = getBackdropShape(enableScrollX()); } return cardBuilder.getCardsHtml({ @@ -345,7 +338,7 @@ export function loadLibraryTiles(elem, apiClient, user, userSettings, shape, use html += cardBuilder.getCardsHtml({ items: userViews, - shape: getThumbShape(), + shape: getBackdropShape(enableScrollX()), showTitle: true, centerText: true, overlayText: false, @@ -423,7 +416,9 @@ function getItemsToResumeHtmlFn(useEpisodeImages, mediaType) { items: items, preferThumb: true, inheritThumb: !useEpisodeImages, - shape: (mediaType === 'Book') ? getPortraitShape() : getThumbShape(), + shape: (mediaType === 'Book') ? + getPortraitShape(enableScrollX()) : + getBackdropShape(enableScrollX()), overlayText: false, showTitle: true, showParentTitle: true, @@ -471,7 +466,7 @@ function getOnNowItemsHtml(items) { showChannelName: false, showAirDateTime: false, showAirEndTime: true, - defaultShape: getThumbShape(), + defaultShape: getBackdropShape(enableScrollX()), lines: 3, overlayPlayButton: true }); @@ -614,7 +609,7 @@ function getNextUpItemsHtmlFn(useEpisodeImages) { items: items, preferThumb: true, inheritThumb: !useEpisodeImages, - shape: getThumbShape(), + shape: getBackdropShape(enableScrollX()), overlayText: false, showTitle: true, showParentTitle: true, diff --git a/src/controllers/favorites.js b/src/controllers/favorites.js index f14727a76..50c53d90b 100644 --- a/src/controllers/favorites.js +++ b/src/controllers/favorites.js @@ -1,35 +1,25 @@ -import { appRouter } from '../components/router/appRouter'; -import cardBuilder from '../components/cardbuilder/cardBuilder'; -import dom from '../scripts/dom'; -import globalize from '../scripts/globalize'; -import { appHost } from '../components/apphost'; -import layoutManager from '../components/layoutManager'; -import focusManager from '../components/focusManager'; -import '../elements/emby-itemscontainer/emby-itemscontainer'; -import '../elements/emby-scroller/emby-scroller'; -import ServerConnections from '../components/ServerConnections'; +import { appHost } from 'components/apphost'; +import cardBuilder from 'components/cardbuilder/cardBuilder'; +import focusManager from 'components/focusManager'; +import layoutManager from 'components/layoutManager'; +import { appRouter } from 'components/router/appRouter'; +import ServerConnections from 'components/ServerConnections'; +import dom from 'scripts/dom'; +import globalize from 'scripts/globalize'; +import { getBackdropShape, getPortraitShape, getSquareShape } from 'utils/card'; + +import 'elements/emby-itemscontainer/emby-itemscontainer'; +import 'elements/emby-scroller/emby-scroller'; function enableScrollX() { return true; } -function getThumbShape() { - return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; -} - -function getPosterShape() { - return enableScrollX() ? 'overflowPortrait' : 'portrait'; -} - -function getSquareShape() { - return enableScrollX() ? 'overflowSquare' : 'square'; -} - function getSections() { return [{ name: 'Movies', types: 'Movie', - shape: getPosterShape(), + shape: getPortraitShape(enableScrollX()), showTitle: true, showYear: true, overlayPlayButton: true, @@ -38,7 +28,7 @@ function getSections() { }, { name: 'Shows', types: 'Series', - shape: getPosterShape(), + shape: getPortraitShape(enableScrollX()), showTitle: true, showYear: true, overlayPlayButton: true, @@ -47,7 +37,7 @@ function getSections() { }, { name: 'Episodes', types: 'Episode', - shape: getThumbShape(), + shape: getBackdropShape(enableScrollX()), preferThumb: false, showTitle: true, showParentTitle: true, @@ -57,7 +47,7 @@ function getSections() { }, { name: 'Videos', types: 'Video', - shape: getThumbShape(), + shape: getBackdropShape(enableScrollX()), preferThumb: true, showTitle: true, overlayPlayButton: true, @@ -66,7 +56,7 @@ function getSections() { }, { name: 'Collections', types: 'BoxSet', - shape: getPosterShape(), + shape: getPortraitShape(enableScrollX()), showTitle: true, overlayPlayButton: true, overlayText: false, @@ -74,7 +64,7 @@ function getSections() { }, { name: 'Playlists', types: 'Playlist', - shape: getSquareShape(), + shape: getSquareShape(enableScrollX()), preferThumb: false, showTitle: true, overlayText: false, @@ -85,7 +75,7 @@ function getSections() { }, { name: 'People', types: 'Person', - shape: getPosterShape(), + shape: getPortraitShape(enableScrollX()), preferThumb: false, showTitle: true, overlayText: false, @@ -96,7 +86,7 @@ function getSections() { }, { name: 'Artists', types: 'MusicArtist', - shape: getSquareShape(), + shape: getSquareShape(enableScrollX()), preferThumb: false, showTitle: true, overlayText: false, @@ -107,7 +97,7 @@ function getSections() { }, { name: 'Albums', types: 'MusicAlbum', - shape: getSquareShape(), + shape: getSquareShape(enableScrollX()), preferThumb: false, showTitle: true, overlayText: false, @@ -118,7 +108,7 @@ function getSections() { }, { name: 'Songs', types: 'Audio', - shape: getSquareShape(), + shape: getSquareShape(enableScrollX()), preferThumb: false, showTitle: true, overlayText: false, @@ -130,7 +120,7 @@ function getSections() { }, { name: 'Books', types: 'Book', - shape: getPosterShape(), + shape: getPortraitShape(enableScrollX()), showTitle: true, showYear: true, overlayPlayButton: true, diff --git a/src/controllers/itemDetails/index.js b/src/controllers/itemDetails/index.js index 8b000348e..4efe69e78 100644 --- a/src/controllers/itemDetails/index.js +++ b/src/controllers/itemDetails/index.js @@ -4,39 +4,42 @@ import { marked } from 'marked'; import escapeHtml from 'escape-html'; import isEqual from 'lodash-es/isEqual'; -import { appHost } from '../../components/apphost'; -import loading from '../../components/loading/loading'; -import { appRouter } from '../../components/router/appRouter'; -import layoutManager from '../../components/layoutManager'; -import Events from '../../utils/events.ts'; -import * as userSettings from '../../scripts/settings/userSettings'; -import cardBuilder from '../../components/cardbuilder/cardBuilder'; -import datetime from '../../scripts/datetime'; -import mediaInfo from '../../components/mediainfo/mediainfo'; -import { clearBackdrop, setBackdrops } from '../../components/backdrop/backdrop'; -import listView from '../../components/listview/listview'; -import itemContextMenu from '../../components/itemContextMenu'; -import itemHelper from '../../components/itemHelper'; -import dom from '../../scripts/dom'; -import imageLoader from '../../components/images/imageLoader'; -import libraryMenu from '../../scripts/libraryMenu'; -import globalize from '../../scripts/globalize'; -import browser from '../../scripts/browser'; -import { playbackManager } from '../../components/playback/playbackmanager'; -import '../../styles/scrollstyles.scss'; -import '../../elements/emby-itemscontainer/emby-itemscontainer'; -import '../../elements/emby-checkbox/emby-checkbox'; -import '../../elements/emby-button/emby-button'; -import '../../elements/emby-playstatebutton/emby-playstatebutton'; -import '../../elements/emby-ratingbutton/emby-ratingbutton'; -import '../../elements/emby-scroller/emby-scroller'; -import '../../elements/emby-select/emby-select'; -import itemShortcuts from '../../components/shortcuts'; -import Dashboard from '../../utils/dashboard'; -import ServerConnections from '../../components/ServerConnections'; -import confirm from '../../components/confirm/confirm'; -import { download } from '../../scripts/fileDownloader'; -import { getItemBackdropImageUrl } from '../../utils/jellyfin-apiclient/backdropImage'; +import { appHost } from 'components/apphost'; +import { clearBackdrop, setBackdrops } from 'components/backdrop/backdrop'; +import cardBuilder from 'components/cardbuilder/cardBuilder'; +import confirm from 'components/confirm/confirm'; +import imageLoader from 'components/images/imageLoader'; +import itemContextMenu from 'components/itemContextMenu'; +import itemHelper from 'components/itemHelper'; +import mediaInfo from 'components/mediainfo/mediainfo'; +import layoutManager from 'components/layoutManager'; +import listView from 'components/listview/listview'; +import loading from 'components/loading/loading'; +import { playbackManager } from 'components/playback/playbackmanager'; +import { appRouter } from 'components/router/appRouter'; +import itemShortcuts from 'components/shortcuts'; +import ServerConnections from 'components/ServerConnections'; +import browser from 'scripts/browser'; +import datetime from 'scripts/datetime'; +import dom from 'scripts/dom'; +import { download } from 'scripts/fileDownloader'; +import globalize from 'scripts/globalize'; +import libraryMenu from 'scripts/libraryMenu'; +import * as userSettings from 'scripts/settings/userSettings'; +import { getPortraitShape, getSquareShape } from 'utils/card'; +import Dashboard from 'utils/dashboard'; +import Events from 'utils/events'; +import { getItemBackdropImageUrl } from 'utils/jellyfin-apiclient/backdropImage'; + +import 'elements/emby-itemscontainer/emby-itemscontainer'; +import 'elements/emby-checkbox/emby-checkbox'; +import 'elements/emby-button/emby-button'; +import 'elements/emby-playstatebutton/emby-playstatebutton'; +import 'elements/emby-ratingbutton/emby-ratingbutton'; +import 'elements/emby-scroller/emby-scroller'; +import 'elements/emby-select/emby-select'; + +import 'styles/scrollstyles.scss'; function autoFocus(container) { import('../../components/autoFocuser').then(({ default: autoFocuser }) => { @@ -1069,22 +1072,6 @@ function enableScrollX() { return browser.mobile && window.screen.availWidth <= 1000; } -function getPortraitShape(scrollX) { - if (scrollX == null) { - scrollX = enableScrollX(); - } - - return scrollX ? 'overflowPortrait' : 'portrait'; -} - -function getSquareShape(scrollX) { - if (scrollX == null) { - scrollX = enableScrollX(); - } - - return scrollX ? 'overflowSquare' : 'square'; -} - function renderMoreFromSeason(view, item, apiClient) { const section = view.querySelector('.moreFromSeasonSection'); diff --git a/src/controllers/livetv/livetvrecordings.js b/src/controllers/livetv/livetvrecordings.js index 73afca81d..be62a110a 100644 --- a/src/controllers/livetv/livetvrecordings.js +++ b/src/controllers/livetv/livetvrecordings.js @@ -1,10 +1,12 @@ -import loading from '../../components/loading/loading'; -import cardBuilder from '../../components/cardbuilder/cardBuilder'; -import imageLoader from '../../components/images/imageLoader'; -import '../../scripts/livetvcomponents'; -import '../../components/listview/listview.scss'; -import '../../elements/emby-itemscontainer/emby-itemscontainer'; -import Dashboard from '../../utils/dashboard'; +import cardBuilder from 'components/cardbuilder/cardBuilder'; +import imageLoader from 'components/images/imageLoader'; +import loading from 'components/loading/loading'; +import { getBackdropShape } from 'utils/card'; +import Dashboard from 'utils/dashboard'; + +import 'scripts/livetvcomponents'; +import 'components/listview/listview.scss'; +import 'elements/emby-itemscontainer/emby-itemscontainer'; function renderRecordings(elem, recordings, cardOptions, scrollX) { if (!elem) { @@ -32,7 +34,7 @@ function renderRecordings(elem, recordings, cardOptions, scrollX) { recordingItems.innerHTML = cardBuilder.getCardsHtml(Object.assign({ items: recordings, shape: scrollX ? 'autooverflow' : 'auto', - defaultShape: scrollX ? 'overflowBackdrop' : 'backdrop', + defaultShape: getBackdropShape(scrollX), showTitle: true, showParentTitle: true, coverImage: true, diff --git a/src/controllers/livetv/livetvschedule.js b/src/controllers/livetv/livetvschedule.js index 605930e9f..595daab7a 100644 --- a/src/controllers/livetv/livetvschedule.js +++ b/src/controllers/livetv/livetvschedule.js @@ -1,11 +1,13 @@ -import layoutManager from '../../components/layoutManager'; -import cardBuilder from '../../components/cardbuilder/cardBuilder'; -import imageLoader from '../../components/images/imageLoader'; -import loading from '../../components/loading/loading'; -import '../../scripts/livetvcomponents'; -import '../../elements/emby-button/emby-button'; -import '../../elements/emby-itemscontainer/emby-itemscontainer'; -import Dashboard from '../../utils/dashboard'; +import cardBuilder from 'components/cardbuilder/cardBuilder'; +import imageLoader from 'components/images/imageLoader'; +import layoutManager from 'components/layoutManager'; +import loading from 'components/loading/loading'; +import { getBackdropShape } from 'utils/card'; +import Dashboard from 'utils/dashboard'; + +import 'elements/emby-button/emby-button'; +import 'elements/emby-itemscontainer/emby-itemscontainer'; +import 'scripts/livetvcomponents'; function enableScrollX() { return !layoutManager.desktop; @@ -50,15 +52,11 @@ function renderRecordings(elem, recordings, cardOptions) { imageLoader.lazyChildren(recordingItems); } -function getBackdropShape() { - return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; -} - function renderActiveRecordings(context, promise) { promise.then(function (result) { renderRecordings(context.querySelector('#activeRecordings'), result.Items, { shape: enableScrollX() ? 'autooverflow' : 'auto', - defaultShape: getBackdropShape(), + defaultShape: getBackdropShape(enableScrollX()), showParentTitle: false, showParentTitleOrTitle: true, showTitle: true, diff --git a/src/controllers/livetv/livetvsuggested.js b/src/controllers/livetv/livetvsuggested.js index d8325f7a2..b01e75429 100644 --- a/src/controllers/livetv/livetvsuggested.js +++ b/src/controllers/livetv/livetvsuggested.js @@ -1,36 +1,25 @@ -import layoutManager from '../../components/layoutManager'; -import * as userSettings from '../../scripts/settings/userSettings'; -import inputManager from '../../scripts/inputManager'; -import loading from '../../components/loading/loading'; -import globalize from '../../scripts/globalize'; -import * as mainTabsManager from '../../components/maintabsmanager'; -import cardBuilder from '../../components/cardbuilder/cardBuilder'; -import imageLoader from '../../components/images/imageLoader'; -import '../../styles/scrollstyles.scss'; -import '../../elements/emby-itemscontainer/emby-itemscontainer'; -import '../../elements/emby-tabs/emby-tabs'; -import '../../elements/emby-button/emby-button'; -import { LibraryTab } from '../../types/libraryTab.ts'; -import Dashboard from '../../utils/dashboard'; +import cardBuilder from 'components/cardbuilder/cardBuilder'; +import imageLoader from 'components/images/imageLoader'; +import layoutManager from 'components/layoutManager'; +import loading from 'components/loading/loading'; +import * as mainTabsManager from 'components/maintabsmanager'; +import globalize from 'scripts/globalize'; +import inputManager from 'scripts/inputManager'; +import * as userSettings from 'scripts/settings/userSettings'; +import { LibraryTab } from 'types/libraryTab'; +import Dashboard from 'utils/dashboard'; +import { getBackdropShape, getPortraitShape } from 'utils/card'; + +import 'elements/emby-itemscontainer/emby-itemscontainer'; +import 'elements/emby-tabs/emby-tabs'; +import 'elements/emby-button/emby-button'; + +import 'styles/scrollstyles.scss'; function enableScrollX() { return !layoutManager.desktop; } -function getBackdropShape() { - if (enableScrollX()) { - return 'overflowBackdrop'; - } - return 'backdrop'; -} - -function getPortraitShape() { - if (enableScrollX()) { - return 'overflowPortrait'; - } - return 'portrait'; -} - function getLimit() { if (enableScrollX()) { return 12; @@ -96,7 +85,7 @@ function reload(page, enableFullRender) { EnableImageTypes: 'Primary,Thumb' }).then(function (result) { renderItems(page, result.Items, 'upcomingTvMovieItems', null, { - shape: getPortraitShape(), + shape: getPortraitShape(enableScrollX()), preferThumb: null, showParentTitle: false }); @@ -147,7 +136,7 @@ function renderItems(page, items, sectionClass, overlayButton, cardOptions) { preferThumb: 'auto', inheritThumb: false, shape: enableScrollX() ? 'autooverflow' : 'auto', - defaultShape: getBackdropShape(), + defaultShape: getBackdropShape(enableScrollX()), showParentTitle: true, showTitle: true, centerText: true, diff --git a/src/controllers/movies/moviegenres.js b/src/controllers/movies/moviegenres.js index 3d866c6e9..36c433c70 100644 --- a/src/controllers/movies/moviegenres.js +++ b/src/controllers/movies/moviegenres.js @@ -1,12 +1,15 @@ import escapeHtml from 'escape-html'; -import layoutManager from '../../components/layoutManager'; -import loading from '../../components/loading/loading'; -import * as userSettings from '../../scripts/settings/userSettings'; -import cardBuilder from '../../components/cardbuilder/cardBuilder'; -import lazyLoader from '../../components/lazyLoader/lazyLoaderIntersectionObserver'; -import globalize from '../../scripts/globalize'; -import { appRouter } from '../../components/router/appRouter'; -import '../../elements/emby-button/emby-button'; + +import cardBuilder from 'components/cardbuilder/cardBuilder'; +import layoutManager from 'components/layoutManager'; +import lazyLoader from 'components/lazyLoader/lazyLoaderIntersectionObserver'; +import loading from 'components/loading/loading'; +import { appRouter } from 'components/router/appRouter'; +import globalize from 'scripts/globalize'; +import * as userSettings from 'scripts/settings/userSettings'; +import { getBackdropShape, getPortraitShape } from 'utils/card'; + +import 'elements/emby-button/emby-button'; export default function (view, params, tabContent) { function getPageData() { @@ -49,14 +52,6 @@ export default function (view, params, tabContent) { return !layoutManager.desktop; } - function getThumbShape() { - return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; - } - - function getPortraitShape() { - return enableScrollX() ? 'overflowPortrait' : 'portrait'; - } - const fillItemsContainer = (entry) => { const elem = entry.target; const id = elem.getAttribute('data-id'); @@ -85,7 +80,7 @@ export default function (view, params, tabContent) { if (viewStyle == 'Thumb') { cardBuilder.buildCards(result.Items, { itemsContainer: elem, - shape: getThumbShape(), + shape: getBackdropShape(enableScrollX()), preferThumb: true, showTitle: true, scalable: true, @@ -96,7 +91,7 @@ export default function (view, params, tabContent) { } else if (viewStyle == 'ThumbCard') { cardBuilder.buildCards(result.Items, { itemsContainer: elem, - shape: getThumbShape(), + shape: getBackdropShape(enableScrollX()), preferThumb: true, showTitle: true, scalable: true, @@ -107,7 +102,7 @@ export default function (view, params, tabContent) { } else if (viewStyle == 'PosterCard') { cardBuilder.buildCards(result.Items, { itemsContainer: elem, - shape: getPortraitShape(), + shape: getPortraitShape(enableScrollX()), showTitle: true, scalable: true, centerText: false, @@ -117,7 +112,7 @@ export default function (view, params, tabContent) { } else if (viewStyle == 'Poster') { cardBuilder.buildCards(result.Items, { itemsContainer: elem, - shape: getPortraitShape(), + shape: getPortraitShape(enableScrollX()), scalable: true, overlayMoreButton: true, allowBottomPadding: true, diff --git a/src/controllers/movies/moviesrecommended.js b/src/controllers/movies/moviesrecommended.js index 1dc60c30e..ea593f805 100644 --- a/src/controllers/movies/moviesrecommended.js +++ b/src/controllers/movies/moviesrecommended.js @@ -1,35 +1,29 @@ import escapeHtml from 'escape-html'; -import layoutManager from '../../components/layoutManager'; -import inputManager from '../../scripts/inputManager'; -import * as userSettings from '../../scripts/settings/userSettings'; -import libraryMenu from '../../scripts/libraryMenu'; -import * as mainTabsManager from '../../components/maintabsmanager'; -import cardBuilder from '../../components/cardbuilder/cardBuilder'; -import dom from '../../scripts/dom'; -import imageLoader from '../../components/images/imageLoader'; -import { playbackManager } from '../../components/playback/playbackmanager'; -import globalize from '../../scripts/globalize'; -import { LibraryTab } from '../../types/libraryTab.ts'; -import Dashboard from '../../utils/dashboard'; -import Events from '../../utils/events.ts'; -import '../../elements/emby-scroller/emby-scroller'; -import '../../elements/emby-itemscontainer/emby-itemscontainer'; -import '../../elements/emby-tabs/emby-tabs'; -import '../../elements/emby-button/emby-button'; +import cardBuilder from 'components/cardbuilder/cardBuilder'; +import imageLoader from 'components/images/imageLoader'; +import layoutManager from 'components/layoutManager'; +import * as mainTabsManager from 'components/maintabsmanager'; +import { playbackManager } from 'components/playback/playbackmanager'; +import dom from 'scripts/dom'; +import globalize from 'scripts/globalize'; +import inputManager from 'scripts/inputManager'; +import libraryMenu from 'scripts/libraryMenu'; +import * as userSettings from 'scripts/settings/userSettings'; +import { LibraryTab } from 'types/libraryTab'; +import { getBackdropShape, getPortraitShape } from 'utils/card'; +import Dashboard from 'utils/dashboard'; +import Events from 'utils/events'; + +import 'elements/emby-scroller/emby-scroller'; +import 'elements/emby-itemscontainer/emby-itemscontainer'; +import 'elements/emby-tabs/emby-tabs'; +import 'elements/emby-button/emby-button'; function enableScrollX() { return !layoutManager.desktop; } -function getPortraitShape() { - return enableScrollX() ? 'overflowPortrait' : 'portrait'; -} - -function getThumbShape() { - return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; -} - function loadLatest(page, userId, parentId) { const options = { IncludeItemTypes: 'Movie', @@ -45,7 +39,7 @@ function loadLatest(page, userId, parentId) { const container = page.querySelector('#recentlyAddedItems'); cardBuilder.buildCards(items, { itemsContainer: container, - shape: getPortraitShape(), + shape: getPortraitShape(enableScrollX()), scalable: true, overlayPlayButton: true, allowBottomPadding: allowBottomPadding, @@ -87,7 +81,7 @@ function loadResume(page, userId, parentId) { cardBuilder.buildCards(result.Items, { itemsContainer: container, preferThumb: true, - shape: getThumbShape(), + shape: getBackdropShape(enableScrollX()), scalable: true, overlayPlayButton: true, allowBottomPadding: allowBottomPadding, @@ -138,7 +132,7 @@ function getRecommendationHtml(recommendation) { } html += cardBuilder.getCardsHtml(recommendation.Items, { - shape: getPortraitShape(), + shape: getPortraitShape(enableScrollX()), scalable: true, overlayPlayButton: true, allowBottomPadding: allowBottomPadding, diff --git a/src/controllers/music/musicrecommended.js b/src/controllers/music/musicrecommended.js index b66ae0ff7..6dc14b9b5 100644 --- a/src/controllers/music/musicrecommended.js +++ b/src/controllers/music/musicrecommended.js @@ -1,22 +1,24 @@ -import browser from '../../scripts/browser'; -import layoutManager from '../../components/layoutManager'; -import * as userSettings from '../../scripts/settings/userSettings'; -import inputManager from '../../scripts/inputManager'; -import loading from '../../components/loading/loading'; -import cardBuilder from '../../components/cardbuilder/cardBuilder'; -import dom from '../../scripts/dom'; -import imageLoader from '../../components/images/imageLoader'; -import libraryMenu from '../../scripts/libraryMenu'; -import * as mainTabsManager from '../../components/maintabsmanager'; -import globalize from '../../scripts/globalize'; -import { LibraryTab } from '../../types/libraryTab.ts'; -import Dashboard from '../../utils/dashboard'; +import cardBuilder from 'components/cardbuilder/cardBuilder'; +import imageLoader from 'components/images/imageLoader'; +import layoutManager from 'components/layoutManager'; +import loading from 'components/loading/loading'; +import * as mainTabsManager from 'components/maintabsmanager'; +import browser from 'scripts/browser'; +import dom from 'scripts/dom'; +import globalize from 'scripts/globalize'; +import inputManager from 'scripts/inputManager'; +import libraryMenu from 'scripts/libraryMenu'; +import * as userSettings from 'scripts/settings/userSettings'; +import { LibraryTab } from 'types/libraryTab'; +import Dashboard from 'utils/dashboard'; +import { getSquareShape } from 'utils/card'; -import '../../styles/scrollstyles.scss'; -import '../../elements/emby-itemscontainer/emby-itemscontainer'; -import '../../elements/emby-tabs/emby-tabs'; -import '../../elements/emby-button/emby-button'; -import '../../styles/flexstyles.scss'; +import 'elements/emby-itemscontainer/emby-itemscontainer'; +import 'elements/emby-tabs/emby-tabs'; +import 'elements/emby-button/emby-button'; + +import 'styles/flexstyles.scss'; +import 'styles/scrollstyles.scss'; function itemsPerRow() { const screenWidth = dom.getWindowSize().innerWidth; @@ -40,10 +42,6 @@ function enableScrollX() { return !layoutManager.desktop; } -function getSquareShape() { - return enableScrollX() ? 'overflowSquare' : 'square'; -} - function loadLatest(page, parentId) { loading.show(); const userId = ApiClient.getCurrentUserId(); @@ -62,7 +60,7 @@ function loadLatest(page, parentId) { items: items, showUnplayedIndicator: false, showLatestItemsPopup: false, - shape: getSquareShape(), + shape: getSquareShape(enableScrollX()), showTitle: true, showParentTitle: true, lazy: true, @@ -108,7 +106,7 @@ function loadRecentlyPlayed(page, parentId) { itemsContainer.innerHTML = cardBuilder.getCardsHtml({ items: result.Items, showUnplayedIndicator: false, - shape: getSquareShape(), + shape: getSquareShape(enableScrollX()), showTitle: true, showParentTitle: true, action: 'instantmix', @@ -150,7 +148,7 @@ function loadFrequentlyPlayed(page, parentId) { itemsContainer.innerHTML = cardBuilder.getCardsHtml({ items: result.Items, showUnplayedIndicator: false, - shape: getSquareShape(), + shape: getSquareShape(enableScrollX()), showTitle: true, showParentTitle: true, action: 'instantmix', diff --git a/src/controllers/shows/tvgenres.js b/src/controllers/shows/tvgenres.js index 061089af8..3a45be7d5 100644 --- a/src/controllers/shows/tvgenres.js +++ b/src/controllers/shows/tvgenres.js @@ -1,12 +1,15 @@ import escapeHtml from 'escape-html'; -import layoutManager from '../../components/layoutManager'; -import loading from '../../components/loading/loading'; -import * as userSettings from '../../scripts/settings/userSettings'; -import cardBuilder from '../../components/cardbuilder/cardBuilder'; -import lazyLoader from '../../components/lazyLoader/lazyLoaderIntersectionObserver'; -import globalize from '../../scripts/globalize'; -import { appRouter } from '../../components/router/appRouter'; -import '../../elements/emby-button/emby-button'; + +import cardBuilder from 'components/cardbuilder/cardBuilder'; +import lazyLoader from 'components/lazyLoader/lazyLoaderIntersectionObserver'; +import layoutManager from 'components/layoutManager'; +import loading from 'components/loading/loading'; +import { appRouter } from 'components/router/appRouter'; +import globalize from 'scripts/globalize'; +import * as userSettings from 'scripts/settings/userSettings'; +import { getBackdropShape, getPortraitShape } from 'utils/card'; + +import 'elements/emby-button/emby-button'; export default function (view, params, tabContent) { function getPageData() { @@ -49,14 +52,6 @@ export default function (view, params, tabContent) { return !layoutManager.desktop; } - function getThumbShape() { - return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; - } - - function getPortraitShape() { - return enableScrollX() ? 'overflowPortrait' : 'portrait'; - } - function fillItemsContainer(entry) { const elem = entry.target; const id = elem.getAttribute('data-id'); @@ -85,7 +80,7 @@ export default function (view, params, tabContent) { if (viewStyle == 'Thumb') { cardBuilder.buildCards(result.Items, { itemsContainer: elem, - shape: getThumbShape(), + shape: getBackdropShape(enableScrollX()), preferThumb: true, showTitle: true, scalable: true, @@ -96,7 +91,7 @@ export default function (view, params, tabContent) { } else if (viewStyle == 'ThumbCard') { cardBuilder.buildCards(result.Items, { itemsContainer: elem, - shape: getThumbShape(), + shape: getBackdropShape(enableScrollX()), preferThumb: true, showTitle: true, scalable: true, @@ -107,7 +102,7 @@ export default function (view, params, tabContent) { } else if (viewStyle == 'PosterCard') { cardBuilder.buildCards(result.Items, { itemsContainer: elem, - shape: getPortraitShape(), + shape: getPortraitShape(enableScrollX()), showTitle: true, scalable: true, centerText: false, @@ -117,7 +112,7 @@ export default function (view, params, tabContent) { } else if (viewStyle == 'Poster') { cardBuilder.buildCards(result.Items, { itemsContainer: elem, - shape: getPortraitShape(), + shape: getPortraitShape(enableScrollX()), scalable: true, showTitle: true, centerText: true, diff --git a/src/controllers/shows/tvrecommended.js b/src/controllers/shows/tvrecommended.js index d3673bf6a..982420bde 100644 --- a/src/controllers/shows/tvrecommended.js +++ b/src/controllers/shows/tvrecommended.js @@ -1,21 +1,23 @@ +import autoFocuser from 'components/autoFocuser'; +import cardBuilder from 'components/cardbuilder/cardBuilder'; +import layoutManager from 'components/layoutManager'; +import loading from 'components/loading/loading'; +import * as mainTabsManager from 'components/maintabsmanager'; +import { playbackManager } from 'components/playback/playbackmanager'; +import dom from 'scripts/dom'; +import globalize from 'scripts/globalize'; +import inputManager from 'scripts/inputManager'; +import libraryMenu from 'scripts/libraryMenu'; +import * as userSettings from 'scripts/settings/userSettings'; +import { LibraryTab } from 'types/libraryTab'; +import { getBackdropShape } from 'utils/card'; +import Dashboard from 'utils/dashboard'; +import Events from 'utils/events'; -import inputManager from '../../scripts/inputManager'; -import libraryMenu from '../../scripts/libraryMenu'; -import layoutManager from '../../components/layoutManager'; -import loading from '../../components/loading/loading'; -import dom from '../../scripts/dom'; -import * as userSettings from '../../scripts/settings/userSettings'; -import cardBuilder from '../../components/cardbuilder/cardBuilder'; -import { playbackManager } from '../../components/playback/playbackmanager'; -import * as mainTabsManager from '../../components/maintabsmanager'; -import globalize from '../../scripts/globalize'; -import '../../styles/scrollstyles.scss'; -import '../../elements/emby-itemscontainer/emby-itemscontainer'; -import '../../elements/emby-button/emby-button'; -import { LibraryTab } from '../../types/libraryTab.ts'; -import Dashboard from '../../utils/dashboard'; -import Events from '../../utils/events.ts'; -import autoFocuser from '../../components/autoFocuser'; +import 'elements/emby-itemscontainer/emby-itemscontainer'; +import 'elements/emby-button/emby-button'; + +import 'styles/scrollstyles.scss'; function getTabs() { return [{ @@ -119,7 +121,7 @@ function loadResume(view, userId, parentId) { itemsContainer: container, preferThumb: true, inheritThumb: !userSettings.useEpisodeImagesInNextUpAndResume(), - shape: getThumbShape(), + shape: getBackdropShape(enableScrollX()), scalable: true, overlayPlayButton: true, allowBottomPadding: allowBottomPadding, @@ -217,10 +219,6 @@ function enableScrollX() { return !layoutManager.desktop; } -function getThumbShape() { - return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; -} - export default function (view, params) { function onBeforeTabChange(e) { preLoadTab(view, parseInt(e.detail.selectedTabIndex, 10)); diff --git a/src/controllers/shows/tvupcoming.js b/src/controllers/shows/tvupcoming.js index f8b2f31ea..f3c7d90ec 100644 --- a/src/controllers/shows/tvupcoming.js +++ b/src/controllers/shows/tvupcoming.js @@ -1,11 +1,14 @@ -import layoutManager from '../../components/layoutManager'; -import loading from '../../components/loading/loading'; -import datetime from '../../scripts/datetime'; -import cardBuilder from '../../components/cardbuilder/cardBuilder'; -import imageLoader from '../../components/images/imageLoader'; -import globalize from '../../scripts/globalize'; -import '../../styles/scrollstyles.scss'; -import '../../elements/emby-itemscontainer/emby-itemscontainer'; +import cardBuilder from 'components/cardbuilder/cardBuilder'; +import imageLoader from 'components/images/imageLoader'; +import layoutManager from 'components/layoutManager'; +import loading from 'components/loading/loading'; +import datetime from 'scripts/datetime'; +import globalize from 'scripts/globalize'; +import { getBackdropShape } from 'utils/card'; + +import 'elements/emby-itemscontainer/emby-itemscontainer'; + +import 'styles/scrollstyles.scss'; function getUpcomingPromise(context, params) { loading.show(); @@ -40,10 +43,6 @@ function enableScrollX() { return !layoutManager.desktop; } -function getThumbShape() { - return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; -} - function renderUpcoming(elem, items) { const groups = []; let currentGroupName = ''; @@ -105,7 +104,7 @@ function renderUpcoming(elem, items) { html += cardBuilder.getCardsHtml({ items: group.items, showLocationTypeIndicator: false, - shape: getThumbShape(), + shape: getBackdropShape(enableScrollX()), showTitle: true, preferThumb: true, lazy: true, diff --git a/src/scripts/livetvcomponents.js b/src/scripts/livetvcomponents.js index 38035c3dc..68a2c5345 100644 --- a/src/scripts/livetvcomponents.js +++ b/src/scripts/livetvcomponents.js @@ -1,15 +1,13 @@ -import layoutManager from '../components/layoutManager'; +import cardBuilder from 'components/cardbuilder/cardBuilder'; +import layoutManager from 'components/layoutManager'; +import { getBackdropShape } from 'utils/card'; + import datetime from './datetime'; -import cardBuilder from '../components/cardbuilder/cardBuilder'; function enableScrollX() { return !layoutManager.desktop; } -function getBackdropShape() { - return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; -} - function getTimersHtml(timers, options) { options = options || {}; @@ -78,7 +76,7 @@ function getTimersHtml(timers, options) { html += cardBuilder.getCardsHtml({ items: group.items, - shape: getBackdropShape(), + shape: getBackdropShape(enableScrollX()), showTitle: true, showParentTitleOrTitle: true, showAirTime: true, diff --git a/src/utils/card.ts b/src/utils/card.ts new file mode 100644 index 000000000..c3f047a79 --- /dev/null +++ b/src/utils/card.ts @@ -0,0 +1,20 @@ +enum CardShape { + Backdrop = 'backdrop', + BackdropOverflow = 'overflowBackdrop', + Portrait = 'portrait', + PortraitOverflow = 'overflowPortrait', + Square = 'square', + SquareOverflow = 'overflowSquare' +} + +export function getSquareShape(enableOverflow = true) { + return enableOverflow ? CardShape.SquareOverflow : CardShape.Square; +} + +export function getBackdropShape(enableOverflow = true) { + return enableOverflow ? CardShape.BackdropOverflow : CardShape.Backdrop; +} + +export function getPortraitShape(enableOverflow = true) { + return enableOverflow ? CardShape.PortraitOverflow : CardShape.Portrait; +} From cc5f9f1aa890152eef0ae66d9747e672db9c8b39 Mon Sep 17 00:00:00 2001 From: Oliver Bastholm Date: Sun, 1 Oct 2023 23:49:43 +0000 Subject: [PATCH 110/142] Translated using Weblate (Danish) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/da/ --- src/strings/da.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings/da.json b/src/strings/da.json index 5bed42a5c..82c8a8cc7 100644 --- a/src/strings/da.json +++ b/src/strings/da.json @@ -926,7 +926,7 @@ "Collections": "Samlinger", "Favorites": "Favoritter", "Folders": "Mapper", - "HeaderAlbumArtists": "Albums kunstnere", + "HeaderAlbumArtists": "Albumkunstnere", "Absolute": "Absolut", "AccessRestrictedTryAgainLater": "Adgang er begrænset lige nu. Prøv venligst igen senere.", "Aired": "Udgivet", From fc2745488ee5dc133499dcba63a84c5097992add Mon Sep 17 00:00:00 2001 From: officialdanielamani Date: Sun, 1 Oct 2023 23:19:21 +0000 Subject: [PATCH 111/142] Translated using Weblate (Malay) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/ms/ --- src/strings/ms.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/strings/ms.json b/src/strings/ms.json index 86ade162a..1accc4dbb 100644 --- a/src/strings/ms.json +++ b/src/strings/ms.json @@ -20,7 +20,7 @@ "AllLibraries": "Semua pustaka", "AllowMediaConversion": "Membolehkan penukaran media", "AllowMediaConversionHelp": "Memberi atau menolak akses penukaran ciri media.", - "Albums": "Albums", + "Albums": "Album", "Alerts": "Amaran", "AllChannels": "Semua saluran", "AllComplexFormats": "Semua format kompleks (ASS, SSA, VobSub, PGS, SUB, IDX, …)", @@ -246,5 +246,8 @@ "EnableAudioNormalizationHelp": "Normilasi audio akan meletakkan gain yang konstant agar purata di tahap yang dikehendaki (-18dB).", "Console": "konsol", "DeathDateValue": "Died: {0} , program error", - "AllowCollectionManagement": "Benarkan pengguna ini meguruskan koleksi" + "AllowCollectionManagement": "Benarkan pengguna ini meguruskan koleksi", + "AllowSegmentDeletion": "Padam segment", + "AllowSegmentDeletionHelp": "Padam segmen lama setelah ia dihantar ke pelayan. Ini menghalang file transcode disimpan dalam disk. Ia akan berfungsi dengan penghad haju dihidupkan. Matikan tetapan ini jika anda mengalami isu dengan pemain video.", + "LabelThrottleDelaySeconds": "Penghad laju setelah" } From a2b544f7b0915b6f63b3f92c77c83276baaae35c Mon Sep 17 00:00:00 2001 From: amphibolite Date: Mon, 2 Oct 2023 06:13:09 +0000 Subject: [PATCH 112/142] Translated using Weblate (Filipino) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fil/ --- src/strings/fil.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/strings/fil.json b/src/strings/fil.json index 79f2daf59..98ea9041e 100644 --- a/src/strings/fil.json +++ b/src/strings/fil.json @@ -1666,5 +1666,9 @@ "HeaderPerformance": "Pagsasagawa", "LabelStereoDownmixAlgorithm": "Stereo Downmix Algorithm", "LabelDummyChapterCount": "Hangganan", - "LabelDummyChapterCountHelp": "Ang pinakamataas na bilang ng mga larawan ng kabanata na kukunin para sa bawat media file." + "LabelDummyChapterCountHelp": "Ang pinakamataas na bilang ng mga larawan ng kabanata na kukunin para sa bawat media file.", + "AllowCollectionManagement": "Payagan ang user na pangasiwaan ang koleksyon", + "AllowSegmentDeletion": "Burahin ang segment", + "AllowSegmentDeletionHelp": "Burahin ang lumang segment pagkatapos mapadala sa kliyente. Pinipigilan nito ang pag-imbak ng buong transcoded file sa disk. Gumagana lamang kapag nakabukas ang throttling. Patayin ito kung nakakaranas ng problema sa playback.", + "LabelThrottleDelaySeconds": "I-throttle pagkatapos" } From f563fce5a569a330fee1ab097e093e7896dde30f Mon Sep 17 00:00:00 2001 From: Fredrik Lindqvist Date: Mon, 2 Oct 2023 09:21:15 +0000 Subject: [PATCH 113/142] Translated using Weblate (Swedish) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/sv/ --- src/strings/sv.json | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/strings/sv.json b/src/strings/sv.json index d9c4b0927..39b1c90a0 100644 --- a/src/strings/sv.json +++ b/src/strings/sv.json @@ -1371,7 +1371,7 @@ "EnableQuickConnect": "Aktivera snabbanslutning på denna server", "QuickConnectDeactivated": "Snabbanslutning inaktiverades innan inloggningsförsöket kunde godkännas", "QuickConnectAuthorizeFail": "Okänd snabbanslutningskod", - "QuickConnectAuthorizeSuccess": "Anrop auktoriserat", + "QuickConnectAuthorizeSuccess": "Du har autentiserat din enhet!", "QuickConnectAuthorizeCode": "Ange kod {0} för att logga in", "QuickConnectActivationSuccessful": "Aktivering lyckades", "QuickConnect": "Snabbanslutning", @@ -1768,5 +1768,12 @@ "LabelBackdropScreensaverIntervalHelp": "Tiden i sekunder mellan olika bakgrunder när bakgrunds-skärmsläckaren används.", "AiTranslated": "AI-översatt", "MachineTranslated": "Maskinöversatt", - "HearingImpairedShort": "HI/SDH" + "HearingImpairedShort": "HI/SDH", + "BackdropScreensaver": "Bakgrunds skärmsläckare", + "LogoScreensaver": "Logotyp skärmsläckare", + "LabelIsHearingImpaired": "För hörselskadade (SDH)", + "LabelBackdropScreensaverInterval": "Bakgrunds skärmsläckare intervall", + "GridView": "Rutnätsvy", + "ListView": "Listvy", + "ForeignPartsOnly": "Forcerad/Främmande delar enbart" } From 2e99ca1e4ade848a38ae91b4eba0221893581419 Mon Sep 17 00:00:00 2001 From: Guilherme Augusto Belintani Date: Mon, 2 Oct 2023 16:43:16 +0000 Subject: [PATCH 114/142] Translated using Weblate (Portuguese (Brazil)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/pt_BR/ --- src/strings/pt-br.json | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/strings/pt-br.json b/src/strings/pt-br.json index 5239d9d3c..71068d9ba 100644 --- a/src/strings/pt-br.json +++ b/src/strings/pt-br.json @@ -1751,5 +1751,25 @@ "Unknown": "Desconhecido", "HeaderConfirmRepositoryInstallation": "Confirme a instalação do repositório de plug-ins", "LabelDeveloper": "Desenvolvedor", - "PleaseConfirmRepositoryInstallation": "Por favor, clique em OK para confirmar que você leu o acima e deseja prosseguir com a instalação do repositório de plug-ins." + "PleaseConfirmRepositoryInstallation": "Por favor, clique em OK para confirmar que você leu o acima e deseja prosseguir com a instalação do repositório de plug-ins.", + "LabelIsHearingImpaired": "Para deficientes auditivos (SDH)", + "BackdropScreensaver": "Imagem de fundo do protetor de tela", + "LogoScreensaver": "Logo da proteção de tela", + "AllowAv1Encoding": "Permitir codificação em formato AV1", + "HeaderGuestCast": "Estrelas Convidadas", + "HeaderEpisodesStatus": "Situação dos Episódios", + "GoHome": "Tela Inicial", + "UnknownError": "Um erro desconhecido aconteceu.", + "LabelBackdropScreensaverInterval": "Intervalo da imagem de fundo da proteção de tela", + "LabelBackdropScreensaverIntervalHelp": "Tempo em segundos entre as diferentes imagens de fundo do protetor de tela.", + "GridView": "Visualização em grelha", + "ListView": "Visualização em Lista", + "AiTranslated": "Traduzido por IA", + "MachineTranslated": "Traduzido por Máquina", + "AllowSegmentDeletion": "Remover segmentos", + "AllowSegmentDeletionHelp": "Remover segmentos antigos após serem enviados ao cliente. Isso previne armazenar o arquivo transcodificado em disco. Funciona apenas com limitação habilitada. Desligue esta opção se você experenciar problemas com a reprodução.", + "LabelThrottleDelaySeconds": "Limitar após", + "LabelThrottleDelaySecondsHelp": "Tempo em segundos em que o transcodificador será limitado. É necessário que seja grande o suficiente para que o cliente mantenha um buffer saudável. Funciona apenas se o limitador estiver habilitado.", + "LabelSegmentKeepSeconds": "Tempo para armazenar seguimentos", + "LabelSegmentKeepSecondsHelp": "Tempo em segundos para que os seguimentos sejam armazenados antes de serem sobrescritos. É necessário que seja maior que \"Limitar após\". Funciona apenas se a remoção de segmentos estiver habilitada." } From d1f2f1caa0bb313798f38a74f59bb8308c5ece22 Mon Sep 17 00:00:00 2001 From: grafixeyehero Date: Sun, 24 Sep 2023 19:45:29 +0300 Subject: [PATCH 115/142] Add items helper --- src/utils/items.ts | 158 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 src/utils/items.ts diff --git a/src/utils/items.ts b/src/utils/items.ts new file mode 100644 index 000000000..08936a3fd --- /dev/null +++ b/src/utils/items.ts @@ -0,0 +1,158 @@ +import { ItemFields } from '@jellyfin/sdk/lib/generated-client/models/item-fields'; +import { ImageType } from '@jellyfin/sdk/lib/generated-client/models/image-type'; +import { ItemSortBy } from '@jellyfin/sdk/lib/models/api/item-sort-by'; +import { SortOrder } from '@jellyfin/sdk/lib/generated-client/models/sort-order'; +import * as userSettings from 'scripts/settings/userSettings'; +import { EpisodeFilter, FeatureFilters, LibraryViewSettings, ParentId, VideoBasicFilter, ViewMode } from '../types/library'; +import { LibraryTab } from 'types/libraryTab'; + +export const getVideoBasicFilter = (libraryViewSettings: LibraryViewSettings) => { + let isHd; + + if (libraryViewSettings.Filters?.VideoBasicFilter?.includes(VideoBasicFilter.IsHD)) { + isHd = true; + } + + if (libraryViewSettings.Filters?.VideoBasicFilter?.includes(VideoBasicFilter.IsSD)) { + isHd = false; + } + + return { + isHd, + is4K: libraryViewSettings.Filters?.VideoBasicFilter?.includes(VideoBasicFilter.Is4K) ? + true : + undefined, + is3D: libraryViewSettings.Filters?.VideoBasicFilter?.includes(VideoBasicFilter.Is3D) ? + true : + undefined + }; +}; + +export const getFeatureFilters = (libraryViewSettings: LibraryViewSettings) => { + return { + hasSubtitles: libraryViewSettings.Filters?.Features?.includes(FeatureFilters.HasSubtitles) ? + true : + undefined, + hasTrailer: libraryViewSettings.Filters?.Features?.includes(FeatureFilters.HasTrailer) ? + true : + undefined, + hasSpecialFeature: libraryViewSettings.Filters?.Features?.includes( + FeatureFilters.HasSpecialFeature + ) ? + true : + undefined, + hasThemeSong: libraryViewSettings.Filters?.Features?.includes(FeatureFilters.HasThemeSong) ? + true : + undefined, + hasThemeVideo: libraryViewSettings.Filters?.Features?.includes( + FeatureFilters.HasThemeVideo + ) ? + true : + undefined + }; +}; + +export const getEpisodeFilter = ( + viewType: LibraryTab, + libraryViewSettings: LibraryViewSettings +) => { + return { + parentIndexNumber: libraryViewSettings.Filters?.EpisodeFilter?.includes( + EpisodeFilter.ParentIndexNumber + ) ? + 0 : + undefined, + isMissing: + viewType === LibraryTab.Episodes ? + !!libraryViewSettings.Filters?.EpisodeFilter?.includes(EpisodeFilter.IsMissing) : + undefined, + isUnaired: libraryViewSettings.Filters?.EpisodeFilter?.includes(EpisodeFilter.IsUnaired) ? + true : + undefined + }; +}; + +const getItemFieldsEnum = ( + viewType: LibraryTab, + libraryViewSettings: LibraryViewSettings +) => { + const itemFields: ItemFields[] = []; + + if (viewType !== LibraryTab.Networks) { + itemFields.push(ItemFields.BasicSyncInfo, ItemFields.MediaSourceCount); + } + + if (libraryViewSettings.ImageType === ImageType.Primary) { + itemFields.push(ItemFields.PrimaryImageAspectRatio); + } + + if (viewType === LibraryTab.Networks) { + itemFields.push( + ItemFields.DateCreated, + ItemFields.PrimaryImageAspectRatio + ); + } + + return itemFields; +}; + +export const getFieldsQuery = ( + viewType: LibraryTab, + libraryViewSettings: LibraryViewSettings +) => { + return { + fields: getItemFieldsEnum(viewType, libraryViewSettings) + }; +}; + +export const getLimitQuery = () => { + return { + limit: userSettings.libraryPageSize(undefined) || undefined + }; +}; + +export const getAlphaPickerQuery = (libraryViewSettings: LibraryViewSettings) => { + const alphabetValue = libraryViewSettings.Alphabet !== null ? + libraryViewSettings.Alphabet : undefined; + + return { + nameLessThan: alphabetValue === '#' ? 'A' : undefined, + nameStartsWith: alphabetValue === '#' ? undefined : alphabetValue + }; +}; + +export const getFiltersQuery = ( + viewType: LibraryTab, + libraryViewSettings: LibraryViewSettings +) => { + return { + ...getFeatureFilters(libraryViewSettings), + ...getEpisodeFilter(viewType, libraryViewSettings), + ...getVideoBasicFilter(libraryViewSettings), + seriesStatus: libraryViewSettings?.Filters?.SeriesStatus, + videoTypes: libraryViewSettings?.Filters?.VideoTypes, + filters: libraryViewSettings?.Filters?.Status, + genres: libraryViewSettings?.Filters?.Genres, + officialRatings: libraryViewSettings?.Filters?.OfficialRatings, + tags: libraryViewSettings?.Filters?.Tags, + years: libraryViewSettings?.Filters?.Years, + studioIds: libraryViewSettings?.Filters?.StudioIds + }; +}; + +export const getSettingsKey = (viewType: LibraryTab, parentId: ParentId) => { + return `${viewType} - ${parentId}`; +}; + +export const getDefaultLibraryViewSettings = (): LibraryViewSettings => { + return { + ShowTitle: true, + ShowYear: false, + ViewMode: ViewMode.GridView, + ImageType: ImageType.Primary, + CardLayout: false, + SortBy: ItemSortBy.SortName, + SortOrder: SortOrder.Ascending, + StartIndex: 0 + }; +}; From ab0ffb857cb81491301e051824add0ca2e74bd55 Mon Sep 17 00:00:00 2001 From: grafixeyehero Date: Mon, 2 Oct 2023 23:38:03 +0300 Subject: [PATCH 116/142] Add Play, Queue, Shuffle and NewCollection Buttons --- .../library/NewCollectionButton.tsx | 34 +++++++++++ .../components/library/PlayAllButton.tsx | 57 +++++++++++++++++++ .../components/library/QueueButton.tsx | 39 +++++++++++++ .../components/library/ShuffleButton.tsx | 49 ++++++++++++++++ .../components/library/SortButton.tsx | 2 +- .../components/library/ViewSettingsButton.tsx | 2 +- 6 files changed, 181 insertions(+), 2 deletions(-) create mode 100644 src/apps/experimental/components/library/NewCollectionButton.tsx create mode 100644 src/apps/experimental/components/library/PlayAllButton.tsx create mode 100644 src/apps/experimental/components/library/QueueButton.tsx create mode 100644 src/apps/experimental/components/library/ShuffleButton.tsx diff --git a/src/apps/experimental/components/library/NewCollectionButton.tsx b/src/apps/experimental/components/library/NewCollectionButton.tsx new file mode 100644 index 000000000..e337de7dd --- /dev/null +++ b/src/apps/experimental/components/library/NewCollectionButton.tsx @@ -0,0 +1,34 @@ +import React, { FC, useCallback } from 'react'; +import { IconButton } from '@mui/material'; +import AddIcon from '@mui/icons-material/Add'; +import globalize from 'scripts/globalize'; + +const NewCollectionButton: FC = () => { + const showCollectionEditor = useCallback(() => { + import('components/collectionEditor/collectionEditor').then( + ({ default: CollectionEditor }) => { + const serverId = window.ApiClient.serverId(); + const collectionEditor = new CollectionEditor(); + collectionEditor.show({ + items: [], + serverId: serverId + }).catch(() => { + // closed collection editor + }); + }).catch(err => { + console.error('[NewCollection] failed to load collection editor', err); + }); + }, []); + + return ( + + + + ); +}; + +export default NewCollectionButton; diff --git a/src/apps/experimental/components/library/PlayAllButton.tsx b/src/apps/experimental/components/library/PlayAllButton.tsx new file mode 100644 index 000000000..d7fb09038 --- /dev/null +++ b/src/apps/experimental/components/library/PlayAllButton.tsx @@ -0,0 +1,57 @@ +import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client'; +import React, { FC, useCallback } from 'react'; +import { IconButton } from '@mui/material'; +import PlayArrowIcon from '@mui/icons-material/PlayArrow'; + +import { playbackManager } from 'components/playback/playbackmanager'; +import globalize from 'scripts/globalize'; +import { getFiltersQuery } from 'utils/items'; +import { LibraryViewSettings } from 'types/library'; +import { LibraryTab } from 'types/libraryTab'; + +interface PlayAllButtonProps { + item: BaseItemDto | undefined; + items: BaseItemDto[]; + viewType: LibraryTab; + hasFilters: boolean; + libraryViewSettings: LibraryViewSettings +} + +const PlayAllButton: FC = ({ item, items, viewType, hasFilters, libraryViewSettings }) => { + const play = useCallback(() => { + if (item && !hasFilters) { + playbackManager.play({ + items: [item], + autoplay: true, + queryOptions: { + SortBy: [libraryViewSettings.SortBy], + SortOrder: [libraryViewSettings.SortOrder] + } + }); + } else { + playbackManager.play({ + items: items, + autoplay: true, + queryOptions: { + ParentId: item?.Id ?? undefined, + ...getFiltersQuery(viewType, libraryViewSettings), + SortBy: [libraryViewSettings.SortBy], + SortOrder: [libraryViewSettings.SortOrder] + } + + }); + } + }, [hasFilters, item, items, libraryViewSettings, viewType]); + + return ( + + + + ); +}; + +export default PlayAllButton; diff --git a/src/apps/experimental/components/library/QueueButton.tsx b/src/apps/experimental/components/library/QueueButton.tsx new file mode 100644 index 000000000..fdc6a7666 --- /dev/null +++ b/src/apps/experimental/components/library/QueueButton.tsx @@ -0,0 +1,39 @@ +import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client'; +import React, { FC, useCallback } from 'react'; +import { IconButton } from '@mui/material'; +import QueueIcon from '@mui/icons-material/Queue'; + +import { playbackManager } from 'components/playback/playbackmanager'; +import globalize from 'scripts/globalize'; + +interface QueueButtonProps { + item: BaseItemDto | undefined + items: BaseItemDto[]; + hasFilters: boolean; +} + +const QueueButton: FC = ({ item, items, hasFilters }) => { + const queue = useCallback(() => { + if (item && !hasFilters) { + playbackManager.queue({ + items: [item] + }); + } else { + playbackManager.queue({ + items: items + }); + } + }, [hasFilters, item, items]); + + return ( + + + + ); +}; + +export default QueueButton; diff --git a/src/apps/experimental/components/library/ShuffleButton.tsx b/src/apps/experimental/components/library/ShuffleButton.tsx new file mode 100644 index 000000000..c81ee4c4b --- /dev/null +++ b/src/apps/experimental/components/library/ShuffleButton.tsx @@ -0,0 +1,49 @@ +import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client'; +import { ItemSortBy } from '@jellyfin/sdk/lib/models/api/item-sort-by'; +import React, { FC, useCallback } from 'react'; +import { IconButton } from '@mui/material'; +import ShuffleIcon from '@mui/icons-material/Shuffle'; + +import { playbackManager } from 'components/playback/playbackmanager'; +import globalize from 'scripts/globalize'; +import { getFiltersQuery } from 'utils/items'; +import { LibraryViewSettings } from 'types/library'; +import { LibraryTab } from 'types/libraryTab'; + +interface ShuffleButtonProps { + item: BaseItemDto | undefined; + items: BaseItemDto[]; + viewType: LibraryTab + hasFilters: boolean; + libraryViewSettings: LibraryViewSettings +} + +const ShuffleButton: FC = ({ item, items, viewType, hasFilters, libraryViewSettings }) => { + const shuffle = useCallback(() => { + if (item && !hasFilters) { + playbackManager.shuffle(item); + } else { + playbackManager.play({ + items: items, + autoplay: true, + queryOptions: { + ParentId: item?.Id ?? undefined, + ...getFiltersQuery(viewType, libraryViewSettings), + SortBy: [ItemSortBy.Random] + } + }); + } + }, [hasFilters, item, items, libraryViewSettings, viewType]); + + return ( + + + + ); +}; + +export default ShuffleButton; diff --git a/src/apps/experimental/components/library/SortButton.tsx b/src/apps/experimental/components/library/SortButton.tsx index 7deeae349..2c7425f0d 100644 --- a/src/apps/experimental/components/library/SortButton.tsx +++ b/src/apps/experimental/components/library/SortButton.tsx @@ -98,7 +98,7 @@ const SortButton: FC = ({ title={globalize.translate('Sort')} sx={{ ml: 2 }} aria-describedby={id} - className='paper-icon-button-light btnShuffle autoSize' + className='paper-icon-button-light btnSort autoSize' onClick={handleClick} > diff --git a/src/apps/experimental/components/library/ViewSettingsButton.tsx b/src/apps/experimental/components/library/ViewSettingsButton.tsx index cec5090ac..b1ca1679e 100644 --- a/src/apps/experimental/components/library/ViewSettingsButton.tsx +++ b/src/apps/experimental/components/library/ViewSettingsButton.tsx @@ -100,7 +100,7 @@ const ViewSettingsButton: FC = ({ title={globalize.translate('ButtonSelectView')} sx={{ ml: 2 }} aria-describedby={id} - className='paper-icon-button-light btnShuffle autoSize' + className='paper-icon-button-light btnSelectView autoSize' onClick={handleClick} > From 7ee0262c0c5bbecd74eea99d9fffbe53b372befc Mon Sep 17 00:00:00 2001 From: Prasaedonium Date: Tue, 3 Oct 2023 00:59:54 +0000 Subject: [PATCH 117/142] Translated using Weblate (Spanish (Mexico)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/es_MX/ --- src/strings/es-mx.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/strings/es-mx.json b/src/strings/es-mx.json index 206fc431b..3fb0abb6a 100644 --- a/src/strings/es-mx.json +++ b/src/strings/es-mx.json @@ -1773,5 +1773,6 @@ "MachineTranslated": "Traducido por Máquina", "ForeignPartsOnly": "Solamente partes Forzadas/Foráneas", "HearingImpairedShort": "HI/SDH", - "HeaderGuestCast": "Estrellas Invitadas" + "HeaderGuestCast": "Estrellas Invitadas", + "LabelIsHearingImpaired": "Para personas con discapacidad auditiva (SDH)" } From 3c443e29df87ee319977792047acff9a3f7eb650 Mon Sep 17 00:00:00 2001 From: Prasaedonium Date: Tue, 3 Oct 2023 00:54:43 +0000 Subject: [PATCH 118/142] Translated using Weblate (Spanish) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/es/ --- src/strings/es.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/strings/es.json b/src/strings/es.json index 30ba43bb8..ab3ac87d9 100644 --- a/src/strings/es.json +++ b/src/strings/es.json @@ -1775,5 +1775,6 @@ "AiTranslated": "Traducido por IA", "MachineTranslated": "Traducido por Máquina", "HeaderGuestCast": "Estrellas Invitadas", - "ForeignPartsOnly": "Partes Forzadas/Foráneas solamente" + "ForeignPartsOnly": "Partes Forzadas/Foráneas solamente", + "LabelIsHearingImpaired": "Para personas con discapacidad auditiva (SDH)" } From ceb501907e4cb37dd588eed43d119cd199aa2d8a Mon Sep 17 00:00:00 2001 From: Slavi Asenov Date: Tue, 3 Oct 2023 04:55:29 +0000 Subject: [PATCH 119/142] Translated using Weblate (Bulgarian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/bg/ --- src/strings/bg-bg.json | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/strings/bg-bg.json b/src/strings/bg-bg.json index f9f61bb28..21502d640 100644 --- a/src/strings/bg-bg.json +++ b/src/strings/bg-bg.json @@ -528,7 +528,7 @@ "Screenshots": "Снимки на екрана", "Search": "Търсене", "SearchForCollectionInternetMetadata": "Търсене в интернет за картини и метаданни", - "SearchForMissingMetadata": "Търсене за лисващи метаданни", + "SearchForMissingMetadata": "Търсене за липсващи метаданни", "SearchForSubtitles": "Търсене на субтитри", "SendMessage": "Изпращане на съобщение", "SeriesYearToPresent": "{0} - Настояще", @@ -1166,11 +1166,11 @@ "MessageUnsetContentHelp": "Съдържанието ще се показва като обикновени папки. За най-добри резултати използвайте мениджъра на метаданни, за да зададете типовете съдържание на подпапките.", "MessageUnableToConnectToServer": "В момента не можем да се свържем с избрания сървър. Моля, уверете се, че работи и опитайте отново.", "MessageReenableUser": "Вижте по-долу, за да активирате отново", - "MessagePluginInstallDisclaimer": "Приставките, създадени от членове на общността, са чудесен начин да подобрите изживяването с Джелифин чрез допълнителните функции и предимства.Преди да инсталирате, имайте предвид ефектите, които те могат да имат върху вашия Джелифин сървър, като по-дълго време за сканиране на библиотеки, допълнителна обработка на заден фон и намалена стабилност на системата.", + "MessagePluginInstallDisclaimer": "ПРЕДУПРЕЖДЕНИЕ: Инсталирането на плъгин на трета страна носи рискове. Може да съдържа нестабилен или злонамерен код и може да се промени по всяко време. Инсталирайте само плъгини от автори на които имате доверие! Имайте предвид потенциалните ефекти, които може да има, включително заявки към външни услуги, по-дълги сканирания на библиотеки или допълнителна фонова обработка.", "MessagePluginConfigurationRequiresLocalAccess": "За да конфигурирате тази приставка, моля, впишете се директно в локалния си сървър.", "MessagePleaseWait": "Моля,изчакайте. Това може да отнеме минута.", "MessagePlayAccessRestricted": "Възпроизвеждането на това съдържание в момента е ограничено.Моля, свържете се с администратора на вашия сървър за повече информация.", - "MessagePasswordResetForUsers": "Следните потребители са занулили паролите си.Те вече могат да влязат с пин кодовете, използвани за извършване на нулирането.", + "MessagePasswordResetForUsers": "Следните потребители са занулили паролите си.Те вече могат да влязат с ПИН кодовете, използвани за извършване на нулирането.", "MessageNoTrailersFound": "За да подобрите филмовото изживяване инсталирайте канал за трейлъри,може да подредите няколко канала в библиотека.", "MessageNoServersAvailable": "Не са намерени сървъри, използващи функцията за автоматично откриване на сървър.", "MessageNoMovieSuggestionsAvailable": "Понастоящем няма предложени филми. Започнете да гледате и оценявате филмите си, а след това се върнете, за да видите препоръките си.", @@ -1452,7 +1452,7 @@ "LabelAlbumArtMaxResHelp": "Максимална резолюция на изображенията предоставена чрез \"upnp:albumArtURI\" полето.", "KnownProxiesHelp": "Списък от IP ареси или хост имена на известни прокси сървъри, разделени със запетая, използвани при свързване с Jellyfin сървър. Това е задължително за да се използва правилнен \"X-Forwarded-For\" хедър. Изисква рестартиране след прилагане.", "HomeVideosPhotos": "Домашни видеа и снимки", - "DirectPlayHelp": "Основният файл е напълно съвместим с този клиент, което значи че го получавате без модификации.", + "DirectPlayHelp": "Премахване на изображение", "AllowTonemappingHelp": "Тоналното картографиране може да трансформира динамичния обхват на видеото от HDR към SDR, като същевременно запазва детайлите и цветовете на изображението, които са много важна информация за представяне на оригиналната сцена. В момента работи само с 10-битови HDR10,HLG и DoVi видеоклипове. Това изисква съответното време за изпълнение от OpenCL или CUDA.", "LabelMaxAudiobookResumeHelp": "Приема се ,че файловете се възпроизведени до края , ако се спре след като оставащото време е по-малко от тази стойност.", "Experimental": "Експериментални", @@ -1478,5 +1478,10 @@ "EnableAudioNormalizationHelp": "Нормализацията на звука ще усили сигналът за да поддържа средните честоти на желано ниво (-18dB).", "EnableAudioNormalization": "Нормализация на звука", "Unknown": "Неизвестен", - "LabelThrottleDelaySeconds": "Ограничи след" + "LabelThrottleDelaySeconds": "Ограничи след", + "GetThePlugin": "Вземете приставката", + "LabelLocalCustomCss": "Персонализиран CSS код за стилизиране, който се отнася само за този клиент. Може да искате да деактивирате персонализирания CSS код на сървъра.", + "LabelOriginalName": "Оригинално име", + "LabelQuickConnectCode": "Код за бързо свързване", + "LabelMaxVideoResolution": "Максимално разрешена разделителна способност на транскодиране на видео" } From d8b36ed152e7a27b0816bb43f542ba1ac6a6dffc Mon Sep 17 00:00:00 2001 From: FuXiang Shu Date: Tue, 3 Oct 2023 04:23:57 +0000 Subject: [PATCH 120/142] Translated using Weblate (Malay) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/ms/ --- src/strings/ms.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/strings/ms.json b/src/strings/ms.json index 1accc4dbb..6a000566e 100644 --- a/src/strings/ms.json +++ b/src/strings/ms.json @@ -4,7 +4,7 @@ "LabelFinish": "Habis", "LabelYoureDone": "Kamu Selesai!", "ParentalRating": "Parental Rating", - "SettingsSaved": "Seting Disimpan.", + "SettingsSaved": "Tetapan Disimpan.", "Absolute": "Mutlak", "AccessRestrictedTryAgainLater": "Akses dihalang pada masa ini. Sila cuba sebentar lagi.", "Actor": "Pelakon", @@ -249,5 +249,7 @@ "AllowCollectionManagement": "Benarkan pengguna ini meguruskan koleksi", "AllowSegmentDeletion": "Padam segment", "AllowSegmentDeletionHelp": "Padam segmen lama setelah ia dihantar ke pelayan. Ini menghalang file transcode disimpan dalam disk. Ia akan berfungsi dengan penghad haju dihidupkan. Matikan tetapan ini jika anda mengalami isu dengan pemain video.", - "LabelThrottleDelaySeconds": "Penghad laju setelah" + "LabelThrottleDelaySeconds": "Penghad laju setelah", + "Settings": "Tetapan", + "SelectServer": "Pilih pelayan" } From c794b51995049fffdf15b7167a58782954ba7633 Mon Sep 17 00:00:00 2001 From: Nicolas Berens Date: Tue, 3 Oct 2023 08:23:33 +0000 Subject: [PATCH 121/142] Translated using Weblate (German) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/de/ --- src/strings/de.json | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/strings/de.json b/src/strings/de.json index c136697d8..625489b77 100644 --- a/src/strings/de.json +++ b/src/strings/de.json @@ -1377,7 +1377,7 @@ "LabelColorSpace": "Farbraum", "MediaInfoColorSpace": "Farbraum", "VideoAudio": "Videoton", - "AllowTonemappingHelp": "Tone-Mapping kann den Dynamikumfang eines Videos von HDR nach SDR wandeln und dabei die für die Darstellung der Originalszene sehr wichtigen Bilddetails und Farben beibehalten. Dies funktioniert zurzeit nur bei HDR10-, HLG- und Dolby-Vision-Videos und benötigt die entsprechende OpenCL- oder CUDA-Laufzeitumgebung.", + "AllowTonemappingHelp": "Tone-Mapping kann den Dynamikumfang eines Videos von HDR nach SDR wandeln und dabei die für die Darstellung der Originalszene sehr wichtigen Bilddetails und Farben beibehalten. Dies funktioniert zurzeit nur bei HDR10, HLG und Dolby-Vision Videos und benötigt die entsprechende OpenCL- oder CUDA-Laufzeitumgebung.", "TonemappingRangeHelp": "Wähle den Ausgabefarbraum aus. Auto ist derselbe wie der Eingabefarbraum.", "TonemappingAlgorithmHelp": "Das Tone-Mapping kann fein abgestimmt werden. Wenn du mit diesen Optionen nicht vertraut bist, behalte einfach den Standardwert bei. Der empfohlene Wert ist \"BT.2390\".", "LabelTonemappingAlgorithm": "Wähle den zu verwendenden Tone-Mapping-Algorithmus aus", @@ -1407,7 +1407,7 @@ "QuickConnectDescription": "Für das Einloggen mit Quick Connect wähle den 'Quick Connect'-Knopf auf deinem Gerät, mit dem du dich anmelden möchtest, und gib den unten angezeigten Code ein.", "QuickConnectDeactivated": "Quick Connect wurde deaktiviert, bevor der Login verifiziert werden konnte", "QuickConnectAuthorizeFail": "Unbekannter Quick Connect-Code", - "QuickConnectAuthorizeSuccess": "Anfrage autorisiert", + "QuickConnectAuthorizeSuccess": "Das Gerät wurde erfolgreich authentifiziert!", "QuickConnectAuthorizeCode": "Login Code {0} eingeben", "QuickConnectActivationSuccessful": "Erfolgreich aktiviert", "EnableQuickConnect": "Quick Connect auf diesem Server aktivieren", @@ -1733,7 +1733,7 @@ "PasswordRequiredForAdmin": "Für Admin Konten wird ein Passwort benötigt.", "LabelEnableLUFSScan": "LUFS-Scan aktivieren", "LabelSyncPlayNoGroups": "Keine Gruppen verfügbar", - "LabelEnableLUFSScanHelp": "Aktiviert den LUFS-Scan für Musik (Dies erfordert mehr Zeit und Ressourcen).", + "LabelEnableLUFSScanHelp": "Clients können die Audio Wiedergabe normalisieren um die selbe Lautstärke für mehrere Stücke zu bekommen.\nDie verlängert den Bibiliotheks Scan und benötigt mehr Ressourcen.", "Notifications": "Benachrichtigungen", "NotificationsMovedMessage": "Die Benachrichtigungsfunktion wurde zum Webhook Plugin verschoben.", "EnableAudioNormalizationHelp": "Die Audionormalisierung fügt eine konstante Verstärkung hinzu, um den Durchschnitt auf einem gewünschten Pegel zu halten (-18 dB).", @@ -1769,5 +1769,11 @@ "GoHome": "Startseite", "AiTranslated": "AI übersetzt", "MachineTranslated": "maschinenübersetzt", - "AllowAv1Encoding": "Encodierung ins AV1 Format erlauben" + "AllowAv1Encoding": "Encodierung ins AV1 Format erlauben", + "LabelIsHearingImpaired": "Für Hörgeschädigte (SDH)", + "LabelBackdropScreensaverInterval": "Hintergrund Bildschirmschoner Interval", + "BackdropScreensaver": "Hintergrund Bildschirmschoner", + "ForeignPartsOnly": "Erzwungen/Nur ausländische Teile", + "HearingImpairedShort": "BaFa/SDH", + "HeaderGuestCast": "Gast Stars" } From 6f645bf0b279bcba5d064f5c9a40bcbb07adb430 Mon Sep 17 00:00:00 2001 From: R1ckj Date: Tue, 3 Oct 2023 07:52:34 +0000 Subject: [PATCH 122/142] Translated using Weblate (Italian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/it/ --- src/strings/it.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/strings/it.json b/src/strings/it.json index df915cbac..49aa147f1 100644 --- a/src/strings/it.json +++ b/src/strings/it.json @@ -1762,5 +1762,7 @@ "LabelThrottleDelaySecondsHelp": "Tempo in secondi dopo cui il transcodificatore sarà messo in throttle. Deve essere sufficientemente grande perché il client mantenga un buon buffer. Funziona solo se il throttling è abilitato.", "LabelSegmentKeepSeconds": "Il tempo per cui tenere i segmenti", "LabelSegmentKeepSecondsHelp": "Tempo in secondi per cui i segmenti saranno tenuti prima di essere sovrascritti. Deve essere più grande di \"Throttle dopo\". Funziona solo se l'eliminazione dei segmenti è abilitata.", - "AllowAv1Encoding": "Permetti la codifica nel formato AV1" + "AllowAv1Encoding": "Permetti la codifica nel formato AV1", + "GoHome": "Vai alla Home", + "GridView": "Vista Griglia" } From 9fa62ffc63038798bacbd8ede357f029fc4ffd13 Mon Sep 17 00:00:00 2001 From: engelkek Date: Tue, 3 Oct 2023 12:20:33 +0000 Subject: [PATCH 123/142] Translated using Weblate (German) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/de/ --- src/strings/de.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/strings/de.json b/src/strings/de.json index 625489b77..4ec655c88 100644 --- a/src/strings/de.json +++ b/src/strings/de.json @@ -1771,9 +1771,10 @@ "MachineTranslated": "maschinenübersetzt", "AllowAv1Encoding": "Encodierung ins AV1 Format erlauben", "LabelIsHearingImpaired": "Für Hörgeschädigte (SDH)", - "LabelBackdropScreensaverInterval": "Hintergrund Bildschirmschoner Interval", + "LabelBackdropScreensaverInterval": "Hintergrund-Bildschirmschoner-Intervall", "BackdropScreensaver": "Hintergrund Bildschirmschoner", "ForeignPartsOnly": "Erzwungen/Nur ausländische Teile", "HearingImpairedShort": "BaFa/SDH", - "HeaderGuestCast": "Gast Stars" + "HeaderGuestCast": "Gast Stars", + "LabelBackdropScreensaverIntervalHelp": "Die Zeit in Sekunden zwischen dem Wechsel verschiedener Hintergrundbilder im Bildschirmschoner" } From 71089846d1e617cb454f1d4cb18f4827d71a6b37 Mon Sep 17 00:00:00 2001 From: Lukas H Date: Tue, 3 Oct 2023 13:49:08 +0000 Subject: [PATCH 124/142] Translated using Weblate (Lithuanian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/lt/ --- src/strings/lt-lt.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/strings/lt-lt.json b/src/strings/lt-lt.json index 16694a71e..5a00e5fba 100644 --- a/src/strings/lt-lt.json +++ b/src/strings/lt-lt.json @@ -1162,5 +1162,7 @@ "LabelMaxVideoResolution": "Maksimali leistina video transkodavimo resoliucija", "LabelEnableAudioVbrHelp": "Kintama bitų sparta siūlo geresnę kokybę lyginant su vidutine bitų sparta, bet retais atvejais gali sukelti krovimo ir palaikymo problemas.", "LabelKodiMetadataEnablePathSubstitutionHelp": "Įjungti kelio pakeitimą nuotraukoms naudojant serverio kelio pakeitimo nustatymus.", - "LabelKodiMetadataDateFormatHelp": "Visos datos iš NFO failų bus ištraukiamos šiuo formatu." + "LabelKodiMetadataDateFormatHelp": "Visos datos iš NFO failų bus ištraukiamos šiuo formatu.", + "AllowSegmentDeletion": "Ištrinti segmentus", + "AllowSegmentDeletionHelp": "Ištrinkite senus segmentus, kai jie buvo išsiųsti klientui. Taip išvengiama viso perkoduoto failo saugojimo diske. Veiks tik su įjungtu droseliu. Išjunkite tai, jei kyla atkūrimo problemų." } From 62f9e7581acf445acf6a2aac4158ada2158c0aac Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Tue, 3 Oct 2023 13:15:52 -0400 Subject: [PATCH 125/142] Fix add user route --- src/apps/dashboard/routes/_asyncRoutes.ts | 6 +++--- src/apps/dashboard/routes/users/{new.tsx => add.tsx} | 0 2 files changed, 3 insertions(+), 3 deletions(-) rename src/apps/dashboard/routes/users/{new.tsx => add.tsx} (100%) diff --git a/src/apps/dashboard/routes/_asyncRoutes.ts b/src/apps/dashboard/routes/_asyncRoutes.ts index 8cae66877..09d40de0e 100644 --- a/src/apps/dashboard/routes/_asyncRoutes.ts +++ b/src/apps/dashboard/routes/_asyncRoutes.ts @@ -3,10 +3,10 @@ import type { AsyncRoute } from 'components/router/AsyncRoute'; export const ASYNC_ADMIN_ROUTES: AsyncRoute[] = [ { path: 'activity' }, { path: 'notifications' }, - { path: 'users/new' }, { path: 'users' }, - { path: 'users/profile' }, { path: 'users/access' }, + { path: 'users/add' }, { path: 'users/parentalcontrol' }, - { path: 'users/password' } + { path: 'users/password' }, + { path: 'users/profile' } ]; diff --git a/src/apps/dashboard/routes/users/new.tsx b/src/apps/dashboard/routes/users/add.tsx similarity index 100% rename from src/apps/dashboard/routes/users/new.tsx rename to src/apps/dashboard/routes/users/add.tsx From 1a8f24ba9eef26e71c68da1888e2188de973a496 Mon Sep 17 00:00:00 2001 From: BartalD Date: Tue, 3 Oct 2023 15:22:31 -0400 Subject: [PATCH 126/142] Added translation using Weblate (Faroese) --- src/strings/fo.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/strings/fo.json diff --git a/src/strings/fo.json b/src/strings/fo.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/src/strings/fo.json @@ -0,0 +1 @@ +{} From 09441da88a430ae2275f81b772fb1997031df2b3 Mon Sep 17 00:00:00 2001 From: forkh Date: Tue, 3 Oct 2023 19:28:01 +0000 Subject: [PATCH 127/142] Translated using Weblate (Faroese) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fo/ --- src/strings/fo.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/strings/fo.json b/src/strings/fo.json index 0967ef424..9b15385fd 100644 --- a/src/strings/fo.json +++ b/src/strings/fo.json @@ -1 +1,3 @@ -{} +{ + "AccessRestrictedTryAgainLater": "Atgongd er avmarkað. Vinaliga prøva aftur seinni." +} From bcf6ecb6ddb26f162c589876ccd17d5f36bae76a Mon Sep 17 00:00:00 2001 From: krvi Date: Tue, 3 Oct 2023 19:30:51 +0000 Subject: [PATCH 128/142] Translated using Weblate (Faroese) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fo/ --- src/strings/fo.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/strings/fo.json b/src/strings/fo.json index 9b15385fd..b02892480 100644 --- a/src/strings/fo.json +++ b/src/strings/fo.json @@ -1,3 +1,4 @@ { - "AccessRestrictedTryAgainLater": "Atgongd er avmarkað. Vinaliga prøva aftur seinni." + "AccessRestrictedTryAgainLater": "Atgongd er avmarkað. Vinaliga royn aftur seinni.", + "Actor": "Sjónleikari" } From d29b47f54be51b76ad57cb13cc4e00cd18754bfc Mon Sep 17 00:00:00 2001 From: forkh Date: Tue, 3 Oct 2023 19:37:19 +0000 Subject: [PATCH 129/142] Translated using Weblate (Faroese) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fo/ --- src/strings/fo.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/strings/fo.json b/src/strings/fo.json index b02892480..2a8f445ab 100644 --- a/src/strings/fo.json +++ b/src/strings/fo.json @@ -1,4 +1,9 @@ { "AccessRestrictedTryAgainLater": "Atgongd er avmarkað. Vinaliga royn aftur seinni.", - "Actor": "Sjónleikari" + "Actor": "Sjónleikari", + "Add": "Legg afturat", + "AddedOnValue": "{0} lagt afturat", + "AddToCollection": "Koyr í samling", + "AddToFavorites": "Legg til yndislistan", + "AddToPlaylist": "Legg til spælilistan" } From 359dffb73df333a977e0ef10ad67fda7e345aad3 Mon Sep 17 00:00:00 2001 From: BartalD Date: Tue, 3 Oct 2023 20:16:11 +0000 Subject: [PATCH 130/142] Translated using Weblate (Faroese) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fo/ --- src/strings/fo.json | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/strings/fo.json b/src/strings/fo.json index 2a8f445ab..2c028c71a 100644 --- a/src/strings/fo.json +++ b/src/strings/fo.json @@ -5,5 +5,21 @@ "AddedOnValue": "{0} lagt afturat", "AddToCollection": "Koyr í samling", "AddToFavorites": "Legg til yndislistan", - "AddToPlaylist": "Legg til spælilistan" + "AddToPlaylist": "Legg til spælilistan", + "Alerts": "Ávaringar", + "All": "Øll", + "AllEpisodes": "Allir partar", + "AllLanguages": "Øll tungumál", + "LabelTonemappingMode": "Tónaavmyndingarháttur", + "HearingImpairedShort": "Hoyriveik/SDH", + "MachineTranslated": "Maskintýðing", + "AiTranslated": "Vitlíkistýðing", + "AllowAv1Encoding": "Loyva koding í AV1 bygnaði", + "LabelIsHearingImpaired": "Til hoyriveik (SDH)", + "Unknown": "Ókend", + "TonemappingModeHelp": "Vel tónaavmyndingarháttin. Um tú verður fyri útblástum hálýsingum, royn so heldur RGB-støðuna.", + "Unreleased": "Ikki latið út enn", + "AlbumArtist": "Album Listafólk", + "AllChannels": "Allar rásir", + "AllComplexFormats": "Allar Kompleksu Formatir (ASS, SSA, VobSub, PGS, SUB, IDX, ...)" } From a3193c5b4922fd168faa53d7b26bb83f1ab961b7 Mon Sep 17 00:00:00 2001 From: krvi Date: Tue, 3 Oct 2023 20:09:48 +0000 Subject: [PATCH 131/142] Translated using Weblate (Faroese) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fo/ --- src/strings/fo.json | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/strings/fo.json b/src/strings/fo.json index 2c028c71a..d348fa1e1 100644 --- a/src/strings/fo.json +++ b/src/strings/fo.json @@ -21,5 +21,22 @@ "Unreleased": "Ikki latið út enn", "AlbumArtist": "Album Listafólk", "AllChannels": "Allar rásir", - "AllComplexFormats": "Allar Kompleksu Formatir (ASS, SSA, VobSub, PGS, SUB, IDX, ...)" + "AllComplexFormats": "Allar Kompleksu Formatir (ASS, SSA, VobSub, PGS, SUB, IDX, ...)", + "Directors": "Leikstjórar", + "AgeValue": "({0} ára gamalt)", + "AllLibraries": "Øll søvn", + "Artist": "Listafólk", + "Artists": "Listafólk", + "Books": "Bøkur", + "Composer": "Tónaskald", + "DailyAt": "Dagligani kl. {0}", + "DashboardVersionNumber": "Útgáva: {0}", + "DeathDateValue": "Deyð(ur): {0}", + "Digital": "Talgilt", + "Director": "Leikstjóri", + "Friday": "Fríggjadag", + "HeaderAdmin": "Umsiting", + "HeaderDevices": "Eindir", + "HeaderError": "Feilur", + "HeaderForKids": "Fyri Børn" } From a4601efc583146a9780832092f0d39a7a59f9b2f Mon Sep 17 00:00:00 2001 From: BartalD Date: Tue, 3 Oct 2023 20:22:43 +0000 Subject: [PATCH 132/142] Translated using Weblate (Faroese) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fo/ --- src/strings/fo.json | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/strings/fo.json b/src/strings/fo.json index d348fa1e1..c901edc34 100644 --- a/src/strings/fo.json +++ b/src/strings/fo.json @@ -38,5 +38,18 @@ "HeaderAdmin": "Umsiting", "HeaderDevices": "Eindir", "HeaderError": "Feilur", - "HeaderForKids": "Fyri Børn" + "HeaderForKids": "Fyri Børn", + "AllowSegmentDeletion": "Strika partar", + "LabelThrottleDelaySeconds": "Kyrkja", + "AllowMediaConversion": "miðla", + "AllowOnTheFlySubtitleExtraction": "undirteksta", + "AllowCollectionManagement": "brúkara", + "AllowFfmpegThrottling": "Kyrkja", + "AllowFfmpegThrottlingHelp": "umkoding avspæling avspælingar", + "AllowSegmentDeletionHelp": "avspælingar", + "LabelSegmentKeepSecondsHelp": "Kyrkja parta", + "AllowHWTranscodingHelp": "avkoda ambætarinum", + "AllowMediaConversionHelp": "miðla", + "AllowOnTheFlySubtitleExtractionHelp": "Íkervnir undirtekstir avspæling íkervnar undirtekstir", + "AllowRemoteAccess": "ambætaran" } From a2d5c468be03f2119c91d94320377b5df378d6a3 Mon Sep 17 00:00:00 2001 From: krvi Date: Tue, 3 Oct 2023 20:23:13 +0000 Subject: [PATCH 133/142] Translated using Weblate (Faroese) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fo/ --- src/strings/fo.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/strings/fo.json b/src/strings/fo.json index c901edc34..04d766248 100644 --- a/src/strings/fo.json +++ b/src/strings/fo.json @@ -51,5 +51,14 @@ "AllowHWTranscodingHelp": "avkoda ambætarinum", "AllowMediaConversionHelp": "miðla", "AllowOnTheFlySubtitleExtractionHelp": "Íkervnir undirtekstir avspæling íkervnar undirtekstir", - "AllowRemoteAccess": "ambætaran" + "AllowRemoteAccess": "ambætaran", + "HeaderPassword": "Loyniorð", + "HeaderLibraries": "Søvn", + "HeaderParentalRatings": "Aldursmark", + "HeaderSecondsValue": "{0} sekund", + "HeaderSendMessage": "Send boð", + "Kids": "Børn", + "LabelArtists": "Listafólk", + "LabelCountry": "Land", + "LabelDeveloper": "Mennari" } From 5968e7637bdc1dab7f2de41e6be8364d49c72355 Mon Sep 17 00:00:00 2001 From: forkh Date: Tue, 3 Oct 2023 22:41:59 +0000 Subject: [PATCH 134/142] Translated using Weblate (Faroese) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fo/ --- src/strings/fo.json | 158 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 155 insertions(+), 3 deletions(-) diff --git a/src/strings/fo.json b/src/strings/fo.json index 04d766248..6532f3e13 100644 --- a/src/strings/fo.json +++ b/src/strings/fo.json @@ -12,7 +12,7 @@ "AllLanguages": "Øll tungumál", "LabelTonemappingMode": "Tónaavmyndingarháttur", "HearingImpairedShort": "Hoyriveik/SDH", - "MachineTranslated": "Maskintýðing", + "MachineTranslated": "Maskin týðing", "AiTranslated": "Vitlíkistýðing", "AllowAv1Encoding": "Loyva koding í AV1 bygnaði", "LabelIsHearingImpaired": "Til hoyriveik (SDH)", @@ -47,7 +47,7 @@ "AllowFfmpegThrottling": "Kyrkja", "AllowFfmpegThrottlingHelp": "umkoding avspæling avspælingar", "AllowSegmentDeletionHelp": "avspælingar", - "LabelSegmentKeepSecondsHelp": "Kyrkja parta", + "LabelSegmentKeepSecondsHelp": "Kyrkja parta.", "AllowHWTranscodingHelp": "avkoda ambætarinum", "AllowMediaConversionHelp": "miðla", "AllowOnTheFlySubtitleExtractionHelp": "Íkervnir undirtekstir avspæling íkervnar undirtekstir", @@ -60,5 +60,157 @@ "Kids": "Børn", "LabelArtists": "Listafólk", "LabelCountry": "Land", - "LabelDeveloper": "Mennari" + "LabelDeveloper": "Mennari", + "Absolute": "Absolut", + "AddToPlayQueue": "Legg til spæl bíðirøð", + "AirDate": "Útgávu ár", + "Aired": "Útgivið", + "Album": "Album", + "Albums": "Album", + "LabelSegmentKeepSeconds": "Tíð at halda petti", + "AroundTime": "Umleið {0}", + "Ascending": "Hækkandi", + "SearchForMissingMetadata": "Leita eftir manglandi metadata", + "SearchForSubtitles": "Leita eftir undirtekstið", + "SearchResults": "Leitiúrslit", + "SelectServer": "Vel ambætara", + "SendMessage": "Send boð", + "Season": "Sesong", + "SeriesDisplayOrderHelp": "Raða partar eftir dato, DVD ordan ella absolut nummerering.", + "ServerNameIsRestarting": "Ambætarin á {0} endurbyrjar.", + "ServerNameIsShuttingDown": "Ambætarin á {0} sløknar.", + "ServerUpdateNeeded": "Hesin ambætarin má dagførast. Fyri at heinta nýggjastu útgávuna, vinarliga vitja {0}.", + "Share": "Býta", + "ShowIndicatorsFor": "Vís indikator fyri", + "ShowLess": "Vís minni", + "ShowMore": "Vís meiri", + "ShowParentImages": "Vís røð myndir", + "Shuffle": "Blanda", + "Shows": "Røðir", + "ShowTitle": "Vís heitið", + "Small": "Lítið", + "SmallCaps": "Lítlir stavir", + "Smaller": "Minni", + "Songs": "Sangir", + "Sort": "Skipa", + "SpecialFeatures": "Serstøk eyðkenni", + "StopRecording": "Steðga upptøku", + "Studio": "Filmsfelag", + "Studios": "Filmsfeløg", + "Subtitle": "Undirtekstur", + "SubtitleCyan": "Blágrønur", + "SubtitleGreen": "Grønt", + "SubtitleOffset": "Undirtekstur offset", + "SubtitleRed": "Reytt", + "Subtitles": "Undirtekstur", + "Suggestions": "Uppskot", + "Sync": "Synkronisera", + "SyncPlayGroupDefaultTitle": "{0}'sa bólkur", + "TabAdvanced": "Framkomin", + "TabCatalog": "Skrá", + "TabDashboard": "Kunningarbretti", + "TabDirectPlay": "Beinleiðis avspæling", + "TabLatest": "Lagt afturat fyri stuttum", + "TabLogs": "Gerðalistar", + "TabNetworking": "Net", + "TabNfoSettings": "NFO stillingar", + "TabPlugins": "Ískoytisforrit", + "TabProfiles": "Vangamyndir", + "TabRepositories": "Goymslur", + "TabServer": "Ambætari", + "TabStreaming": "Stroyming", + "TabUpcoming": "Komandi", + "Tags": "Frámerkir", + "TellUsAboutYourself": "Fortel um teg sjálvan", + "ThemeSongs": "Tema sangir", + "ThemeVideos": "Tema sjónbond", + "ThumbCard": "Tummlakort", + "TitleHardwareAcceleration": "Tólbúnað ferðøking", + "TitleHostingSettings": "Hýsingastillingar", + "TrackCount": "{0} spor", + "Trailers": "Forfilmar", + "Tuesday": "Týsdag", + "TV": "Sjónvarp", + "TypeOptionPluralAudio": "Ljóð", + "TypeOptionPluralBoxSet": "Boks sett", + "TypeOptionPluralMusicAlbum": "Tónleika album", + "TypeOptionPluralMusicVideo": "Tónleikasjónbond", + "TypeOptionPluralSeason": "Sesongir", + "TypeOptionPluralVideo": "Sjónbond", + "Typewriter": "Skrivimaskina", + "Uniform": "Einsháttað", + "Unmute": "Skrúva ljóðið upp", + "Unrated": "Eingin meting", + "Up": "Upp", + "ValueAudioCodec": "Ljóð codec: {0}", + "ValueCodec": "Codec: {0}", + "ValueContainer": "Bingja: {0}", + "ValueEpisodeCount": "{0} partar", + "ValueMinutes": "{0} min", + "ValueMovieCount": "{0} filmar", + "ValueOneEpisode": "1 partur", + "ValueOneMovie": "1 filmur", + "ValueOneMusicVideo": "1 tónleika sjónband", + "ValueOneSeries": "1 røð", + "ValueSeconds": "{0} sekund", + "ValueSeriesCount": "{0} røðir", + "Sports": "Ítróttur", + "Smart": "Smart", + "ShowYear": "Vís árið", + "SimultaneousConnectionLimitHelp": "Mest loyvda antalið av samstundis stroymingum. Skriva 0 fyri einki hámark.", + "Settings": "Stillingar", + "SettingsSaved": "Stillingar eru goymdar.", + "SelectAdminUsername": "Vinarliga vel eitt brúkaranavn til fyrisitara kontu.", + "Series": "Røð", + "SeriesCancelled": "Røðin er steðga.", + "Search": "Leita", + "SearchForCollectionInternetMetadata": "Leita eftir list og metadata á alnótini", + "SeriesSettings": "Røð stillingar", + "SeriesYearToPresent": "{0} - Núverandi", + "ServerRestartNeededAfterPluginInstall": "Jellyfin má endurbyrjast, aftaná at eitt ískoytisforrit er lagt inn.", + "ShowAdvancedSettings": "Vís framkomnar stillingar", + "StopPlayback": "Steðga avspæling", + "SubtitleGray": "Grátt", + "SubtitleLightGray": "Ljósagráður", + "SubtitleMagenta": "Viólreyður", + "AllowedRemoteAddressesHelp": "Komma býttur listið av IP addressum ella IP/netmask inngangur fyri netverk, ið verða loyvd at fjarbinda. Um hesin teigur er blankur, so eru allar fjar addressur loyvdar.", + "SubtitleWhite": "Hvítt", + "Arranger": "Fyrireikari", + "SubtitleYellow": "Gult", + "AskAdminToCreateLibrary": "Bið ein fyrisitari upprætta eitt savn.", + "TabAccess": "Atgongd", + "TypeOptionPluralEpisode": "Partar", + "TabContainers": "Kassar", + "TitlePlayback": "Avspæling", + "TabMusic": "Tónleikur", + "TabOther": "Annað", + "TagsValue": "Frámerkir: {0}", + "TabMyPlugins": "Míni ískoytisforrit", + "TabParentalControl": "Foreldra ræði", + "Sunday": "Sunnudag", + "SubtitleBlack": "Svart", + "TabResponses": "Svar", + "SubtitleBlue": "Blátt", + "TextSent": "Tekst sent.", + "TheseSettingsAffectSubtitlesOnThisDevice": "Hesir stillingar ávirka undirteksir á hesari eindini", + "Thumb": "Tummil", + "Thursday": "Hósdagur", + "Track": "Spor", + "Transcoding": "Umkodning", + "TypeOptionPluralBook": "Bøkur", + "TypeOptionPluralMovie": "Filmar", + "TypeOptionPluralMusicArtist": "Tónleikarar", + "TypeOptionPluralSeries": "Sjónvarpsrøðir", + "UnknownError": "Ein ókendur feilur hendi.", + "Unplayed": "Ikki spældur", + "Upload": "Send upp", + "UseEpisodeImagesInNextUp": "Brúka partamyndir í 'Komandi' og 'Hyggj víðari' teigum", + "UserMenu": "Brúkara valmynd", + "ValueAlbumCount": "{0} album", + "ValueConditions": "Treytir: {0}", + "ValueDiscNumber": "Fløga {0}", + "ValueMusicVideoCount": "{0} tónleika sjónbond", + "ValueOneAlbum": "1 album", + "ValueOneSong": "1 sangur", + "ValueSongCount": "{0} sangir" } From f758aea13b9515d46ea8413ff848101d6a838002 Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Wed, 4 Oct 2023 00:45:17 +0300 Subject: [PATCH 135/142] fix: Use userId from params --- src/utils/jellyfin-apiclient/getItems.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/jellyfin-apiclient/getItems.ts b/src/utils/jellyfin-apiclient/getItems.ts index 4bbe711f8..37d35f840 100644 --- a/src/utils/jellyfin-apiclient/getItems.ts +++ b/src/utils/jellyfin-apiclient/getItems.ts @@ -67,7 +67,7 @@ function mergeResults(results: BaseItemDtoQueryResult[]) { export function getItems(apiClient: ApiClient, userId: string, options?: GetItemsRequest) { const ids = options?.Ids?.split(','); if (!options || !ids || ids.length <= ITEMS_PER_REQUEST_LIMIT) { - return apiClient.getItems(apiClient.getCurrentUserId(), options); + return apiClient.getItems(userId, options); } const results = getItemsSplit(apiClient, userId, options); From c077a177e376553f57614331358e64bd70400d05 Mon Sep 17 00:00:00 2001 From: EddieFAF Date: Wed, 4 Oct 2023 17:18:53 +0000 Subject: [PATCH 136/142] Translated using Weblate (German) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/de/ --- src/strings/de.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/strings/de.json b/src/strings/de.json index 4ec655c88..0e2dd7749 100644 --- a/src/strings/de.json +++ b/src/strings/de.json @@ -1733,7 +1733,7 @@ "PasswordRequiredForAdmin": "Für Admin Konten wird ein Passwort benötigt.", "LabelEnableLUFSScan": "LUFS-Scan aktivieren", "LabelSyncPlayNoGroups": "Keine Gruppen verfügbar", - "LabelEnableLUFSScanHelp": "Clients können die Audio Wiedergabe normalisieren um die selbe Lautstärke für mehrere Stücke zu bekommen.\nDie verlängert den Bibiliotheks Scan und benötigt mehr Ressourcen.", + "LabelEnableLUFSScanHelp": "Clients können die Audio Wiedergabe normalisieren, um die selbe Lautstärke für mehrere Stücke zu bekommen.\nDies verlängert den Bibliotheksscan und benötigt mehr Ressourcen.", "Notifications": "Benachrichtigungen", "NotificationsMovedMessage": "Die Benachrichtigungsfunktion wurde zum Webhook Plugin verschoben.", "EnableAudioNormalizationHelp": "Die Audionormalisierung fügt eine konstante Verstärkung hinzu, um den Durchschnitt auf einem gewünschten Pegel zu halten (-18 dB).", @@ -1758,8 +1758,8 @@ "HeaderEpisodesStatus": "Episodenstatus", "AllowSegmentDeletion": "Segmente löschen", "AllowSegmentDeletionHelp": "Alte Segmente löschen, nachdem sie zum Client gesendet wurden. Damit muss nicht die gesamte transkodierte Datei zwischengespeichert werden. Sollten Wiedergabeprobleme auftreten, kann diese Einstellung deaktiviert werden.", - "LabelThrottleDelaySeconds": "Limitieren nach", - "LabelThrottleDelaySecondsHelp": "Zeit, in Sekunden, nach der die Transkodierung limitiert wird. Muss groß genug sein um dem Client eine problemlose Wiedergabe zu ermöglichen. Funktioniert nur wenn \"Transkodierung drosseln\" aktiviert ist.", + "LabelThrottleDelaySeconds": "Drosseln nach", + "LabelThrottleDelaySecondsHelp": "Zeit, in Sekunden, nach der die Transkodierung gedrosselt wird. Muss groß genug sein um dem Client eine problemlose Wiedergabe zu ermöglichen. Funktioniert nur wenn \"Transkodierung drosseln\" aktiviert ist.", "LabelSegmentKeepSeconds": "Zeit um Segmente zu behalten", "LabelSegmentKeepSecondsHelp": "Zeit, in Sekunden, in der Segmente nicht überschrieben werden dürfen. Muss größer sein als \"Limitieren nach\". Funktioniert nur wenn \"Segmente löschen\" aktiviert ist.", "LogoScreensaver": "Logo Bildschirmschoner", @@ -1776,5 +1776,5 @@ "ForeignPartsOnly": "Erzwungen/Nur ausländische Teile", "HearingImpairedShort": "BaFa/SDH", "HeaderGuestCast": "Gast Stars", - "LabelBackdropScreensaverIntervalHelp": "Die Zeit in Sekunden zwischen dem Wechsel verschiedener Hintergrundbilder im Bildschirmschoner" + "LabelBackdropScreensaverIntervalHelp": "Die Zeit in Sekunden zwischen dem Wechsel verschiedener Hintergrundbilder im Bildschirmschoner." } From 480e683ad606ebf4fde1cbdb9a021fdea9085c70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cosmin=20Cioacl=C4=83?= Date: Tue, 3 Oct 2023 22:02:10 +0200 Subject: [PATCH 137/142] fix: remove unnecessary renaming --- CONTRIBUTORS.md | 1 + src/components/itemContextMenu.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index eccfd4cf2..920fd9288 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -66,6 +66,7 @@ - [Fishbigger](https://github.com/fishbigger) - [sleepycatcoding](https://github.com/sleepycatcoding) - [TheMelmacian](https://github.com/TheMelmacian) + - [tehciolo](https://github.com/tehciolo) # Emby Contributors diff --git a/src/components/itemContextMenu.js b/src/components/itemContextMenu.js index 0ec982f15..0416ce471 100644 --- a/src/components/itemContextMenu.js +++ b/src/components/itemContextMenu.js @@ -444,7 +444,7 @@ function executeCommand(item, id, options) { }); break; case 'multiSelect': - import('./multiSelect/multiSelect').then(({ startMultiSelect: startMultiSelect }) => { + import('./multiSelect/multiSelect').then(({ startMultiSelect }) => { const card = dom.parentWithClass(options.positionTo, 'card'); startMultiSelect(card); }); From 10101488af48a377c7ebf466c0b7dd40cf0c3fcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cosmin=20Cioacl=C4=83?= Date: Wed, 4 Oct 2023 10:23:59 +0200 Subject: [PATCH 138/142] chore: enable `no-useless-rename` lint rule --- .eslintrc.js | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintrc.js b/.eslintrc.js index 7b94a83ed..4caf5f2b9 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -66,6 +66,7 @@ module.exports = { 'no-unused-expressions': ['off'], '@typescript-eslint/no-unused-expressions': ['error', { 'allowShortCircuit': true, 'allowTernary': true, 'allowTaggedTemplates': true }], 'no-unused-private-class-members': ['error'], + 'no-useless-rename': ['error'], 'no-useless-constructor': ['off'], '@typescript-eslint/no-useless-constructor': ['error'], 'no-var': ['error'], From 8caadde2792c25b71f06ba0f925fb6b9dbb01d18 Mon Sep 17 00:00:00 2001 From: TowyTowy Date: Thu, 5 Oct 2023 00:56:24 +0000 Subject: [PATCH 139/142] Translated using Weblate (Danish) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/da/ --- src/strings/da.json | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/strings/da.json b/src/strings/da.json index 82c8a8cc7..0b4176d23 100644 --- a/src/strings/da.json +++ b/src/strings/da.json @@ -1336,7 +1336,7 @@ "EnableFasterAnimations": "Hurtigere animationer", "DisablePlugin": "Deaktiver", "EnablePlugin": "Aktiver", - "DirectPlayHelp": "Kilde filen er kompatibel med denne klient, og modtager filen uden brug af omkodning.", + "DirectPlayHelp": "Kilde filen er kompatibel med denne klient og modtager filen uden brug af omkodning.", "EnableEnhancedNvdecDecoder": "Aktiver forbedret NVDEC-dekoder", "MessagePlaybackError": "Der opstod en fejl under afspilning af denne fil på din Google Cast modtager.", "MessageChromecastConnectionError": "Din Google Cast modtager kan ikke komme i kontakt med Jellyfin serveren. Undersøg venligst forbindelsen og prøv igen.", @@ -1702,5 +1702,19 @@ "SubtitleCyan": "Cyan", "SubtitleMagenta": "Magenta", "AllowCollectionManagement": "Tillad denne bruger at administrere samlinger", - "AllowSegmentDeletion": "Slet segmenter" + "AllowSegmentDeletion": "Slet segmenter", + "HeaderEpisodesStatus": "Episodestatus", + "GoHome": "Gå Hjem", + "EnableAudioNormalizationHelp": "Audionormalisering tilføjer en konstant forstærkning for at holde gennemsnittet på et ønsket niveau (-18 dB).", + "EnableAudioNormalization": "Audio Normalisering", + "GridView": "Gittervisning", + "HeaderConfirmRepositoryInstallation": "Bekræft installation af plugin-repositorium", + "BackdropScreensaver": "Screensaver baggrund", + "GetThePlugin": "Få pluginnet", + "AllowSegmentDeletionHelp": "Slet gamle segmenter, når de er blevet sendt til klienten. Dette forhindrer, at man skal gemme hele den transkodede fil på disken. Fungerer kun med throttling aktiveret. Slå dette fra, hvis du oplever afspilningsproblemer.", + "LabelThrottleDelaySeconds": "Begræns efter", + "LabelThrottleDelaySecondsHelp": "Tid i sekunder, hvorefter transcoderen vil blive begrænset. Skal være stor nok til, at klienten kan opretholde en sund buffer. Virker kun, hvis throttling er aktiveret.", + "LabelSegmentKeepSeconds": "Tid at gemme segmenter i", + "LabelSegmentKeepSecondsHelp": "Tid i sekunder, som segmenter skal gemmes i, før de overskrives. Skal være større end \"Begræns efter\". Virker kun, hvis sletning af segmenter er aktiveret.", + "HeaderGuestCast": "Gæstestjerner" } From 705d0c9a0f61d19c0235d3b40513995095f38d2d Mon Sep 17 00:00:00 2001 From: Dmitriy Dubson Date: Tue, 3 Oct 2023 10:25:00 -0400 Subject: [PATCH 140/142] Add vitest test framework Adds two new npm scripts: - 'test' - runs test suite once and exits - 'test:watch' - runs test suite perpetually. Any file suffixed with '.test.[js|ts]' is considered a test suite --- .github/workflows/tsc.yml | 3 + package-lock.json | 1674 +++++++++++++++++++++++++++++++++++++ package.json | 3 + 3 files changed, 1680 insertions(+) diff --git a/.github/workflows/tsc.yml b/.github/workflows/tsc.yml index 54b3208c8..35bde340f 100644 --- a/.github/workflows/tsc.yml +++ b/.github/workflows/tsc.yml @@ -27,3 +27,6 @@ jobs: - name: Run tsc run: npm run build:check + + - name: Run test suite + run: npm run test diff --git a/package-lock.json b/package-lock.json index f32057882..f2a76ca97 100644 --- a/package-lock.json +++ b/package-lock.json @@ -116,6 +116,7 @@ "stylelint-scss": "5.0.0", "ts-loader": "9.4.4", "typescript": "5.0.4", + "vitest": "0.34.6", "webpack": "5.88.1", "webpack-bundle-analyzer": "4.9.1", "webpack-cli": "5.1.4", @@ -2719,6 +2720,358 @@ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" }, + "node_modules/@esbuild/android-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -2926,6 +3279,18 @@ "axios": "^1.3.4" } }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", @@ -3531,6 +3896,12 @@ "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", "dev": true }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, "node_modules/@surma/rollup-plugin-off-main-thread": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", @@ -3646,6 +4017,21 @@ "@types/node": "*" } }, + "node_modules/@types/chai": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.6.tgz", + "integrity": "sha512-VOVRLM1mBxIRxydiViqPcKn6MIxZytrbMpd6RJLIWKxUNr3zux8no0Oc7kJx0WAPIitgZ0gkrDS+btlqQpubpw==", + "dev": true + }, + "node_modules/@types/chai-subset": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@types/chai-subset/-/chai-subset-1.3.3.tgz", + "integrity": "sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==", + "dev": true, + "dependencies": { + "@types/chai": "*" + } + }, "node_modules/@types/connect": { "version": "3.4.35", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", @@ -4241,6 +4627,119 @@ "integrity": "sha512-V3vzdXunOKKob1F+2ldv/4iGQoQA/iyqtW8PVlr1v16xCCKL831pGUubT+vs5/9wxTE/zBKEhjIjmmpK6nqw2A==", "dev": true }, + "node_modules/@vitest/expect": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.34.6.tgz", + "integrity": "sha512-QUzKpUQRc1qC7qdGo7rMK3AkETI7w18gTCUrsNnyjjJKYiuUB9+TQK3QnR1unhCnWRC0AbKv2omLGQDF/mIjOw==", + "dev": true, + "dependencies": { + "@vitest/spy": "0.34.6", + "@vitest/utils": "0.34.6", + "chai": "^4.3.10" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.34.6.tgz", + "integrity": "sha512-1CUQgtJSLF47NnhN+F9X2ycxUP0kLHQ/JWvNHbeBfwW8CzEGgeskzNnHDyv1ieKTltuR6sdIHV+nmR6kPxQqzQ==", + "dev": true, + "dependencies": { + "@vitest/utils": "0.34.6", + "p-limit": "^4.0.0", + "pathe": "^1.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner/node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/runner/node_modules/yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/snapshot": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-0.34.6.tgz", + "integrity": "sha512-B3OZqYn6k4VaN011D+ve+AA4whM4QkcwcrwaKwAbyyvS/NB1hCWjFIBQxAQQSQir9/RtyAAGuq+4RJmbn2dH4w==", + "dev": true, + "dependencies": { + "magic-string": "^0.30.1", + "pathe": "^1.1.1", + "pretty-format": "^29.5.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@vitest/snapshot/node_modules/magic-string": { + "version": "0.30.4", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.4.tgz", + "integrity": "sha512-Q/TKtsC5BPm0kGqgBIF9oXAs/xEf2vRKiIB4wCRQTJOQIByZ1d+NnUOotvJOvNpi5RNIgVOMC3pOuaP1ZTDlVg==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@vitest/spy": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.34.6.tgz", + "integrity": "sha512-xaCvneSaeBw/cz8ySmF7ZwGvL0lBjfvqc1LpQ/vcdHEvpLn3Ff1vAvjw+CoGn0802l++5L/pxb7whwcWAw+DUQ==", + "dev": true, + "dependencies": { + "tinyspy": "^2.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.34.6.tgz", + "integrity": "sha512-IG5aDD8S6zlvloDsnzHw0Ut5xczlF+kv2BOTo+iXfPr54Yhi5qbVOgGB1hZaVq4iJ4C/MZ2J0y15IlsV/ZcI0A==", + "dev": true, + "dependencies": { + "diff-sequences": "^29.4.3", + "loupe": "^2.3.6", + "pretty-format": "^29.5.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@webassemblyjs/ast": { "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", @@ -4817,6 +5316,15 @@ "node": ">=0.10.0" } }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", @@ -5399,6 +5907,15 @@ "node": ">= 0.8" } }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/cache-base": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", @@ -5570,6 +6087,24 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/chai": { + "version": "4.3.10", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.10.tgz", + "integrity": "sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.0.8" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -5623,6 +6158,18 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -6654,6 +7201,18 @@ "node": ">=8" } }, + "node_modules/deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/deep-equal": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.0.tgz", @@ -6858,6 +7417,15 @@ "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", "dev": true }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -7390,6 +7958,43 @@ "ext": "^1.1.2" } }, + "node_modules/esbuild": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" + } + }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -9300,6 +9905,15 @@ "node": ">=6.9.0" } }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/get-intrinsic": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", @@ -11083,6 +11697,12 @@ "node": ">=6" } }, + "node_modules/jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true + }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -11262,6 +11882,18 @@ "node": ">=8.9.0" } }, + "node_modules/local-pkg": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.3.tgz", + "integrity": "sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/localforage": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", @@ -11405,6 +12037,15 @@ "node": ">=0.10.0" } }, + "node_modules/loupe": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", + "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.0" + } + }, "node_modules/lower-case": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", @@ -11882,6 +12523,30 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/mlly": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.4.2.tgz", + "integrity": "sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==", + "dev": true, + "dependencies": { + "acorn": "^8.10.0", + "pathe": "^1.1.1", + "pkg-types": "^1.0.3", + "ufo": "^1.3.0" + } + }, + "node_modules/mlly/node_modules/acorn": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/mrmime": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz", @@ -12669,6 +13334,21 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.1.tgz", + "integrity": "sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==", + "dev": true + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/pdfjs-dist": { "version": "3.6.172", "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-3.6.172.tgz", @@ -12744,6 +13424,17 @@ "node": ">=8" } }, + "node_modules/pkg-types": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz", + "integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==", + "dev": true, + "dependencies": { + "jsonc-parser": "^3.2.0", + "mlly": "^1.2.0", + "pathe": "^1.1.0" + } + }, "node_modules/plur": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/plur/-/plur-2.1.2.tgz", @@ -14465,6 +15156,38 @@ "renderkid": "^3.0.0" } }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/pretty-format/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -15598,6 +16321,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true + }, "node_modules/signal-exit": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", @@ -16183,6 +16912,12 @@ "node": "*" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true + }, "node_modules/state-toggle": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.3.tgz", @@ -16227,6 +16962,12 @@ "node": ">= 0.6" } }, + "node_modules/std-env": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.4.3.tgz", + "integrity": "sha512-f9aPhy8fYBuMN+sNfakZV18U39PbalgjXG3lLB9WkaYTxijru61wb57V9wxxNthXM5Sd88ETBWi29qLAsHO52Q==", + "dev": true + }, "node_modules/stop-iteration-iterator": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", @@ -16415,6 +17156,30 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strip-literal": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.3.0.tgz", + "integrity": "sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==", + "dev": true, + "dependencies": { + "acorn": "^8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/strip-literal/node_modules/acorn": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/style-loader": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.3.tgz", @@ -19397,6 +20162,30 @@ "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", "dev": true }, + "node_modules/tinybench": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.5.1.tgz", + "integrity": "sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==", + "dev": true + }, + "node_modules/tinypool": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.7.0.tgz", + "integrity": "sha512-zSYNUlYSMhJ6Zdou4cJwo/p7w5nmAH17GRfU/ui3ctvjXFErXXkruT4MWW6poDeXgCaIBlGLrfU6TbTXxyGMww==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.0.tgz", + "integrity": "sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -19702,6 +20491,15 @@ "node": ">= 0.8.0" } }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -19740,6 +20538,12 @@ "node": ">=12.20" } }, + "node_modules/ufo": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.3.1.tgz", + "integrity": "sha512-uY/99gMLIOlJPwATcMVYfqDSxUR9//AUcgZMzwfSTJPDKzA1S8mX4VLqa+fiAtveraQUBCz4FFcwVZBGbwBXIw==", + "dev": true + }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -20105,6 +20909,235 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/vite": { + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz", + "integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==", + "dev": true, + "dependencies": { + "esbuild": "^0.18.10", + "postcss": "^8.4.27", + "rollup": "^3.27.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-0.34.6.tgz", + "integrity": "sha512-nlBMJ9x6n7/Amaz6F3zJ97EBwR2FkzhBRxF5e+jE6LA3yi6Wtc2lyTij1OnDMIr34v5g/tVQtsVAzhT0jc5ygA==", + "dev": true, + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "mlly": "^1.4.0", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": ">=v14.18.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/vite/node_modules/rollup": { + "version": "3.29.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", + "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/vitest": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.34.6.tgz", + "integrity": "sha512-+5CALsOvbNKnS+ZHMXtuUC7nL8/7F1F2DnHGjSsszX8zCjWSSviphCb/NuS9Nzf4Q03KyyDRBAXhF/8lffME4Q==", + "dev": true, + "dependencies": { + "@types/chai": "^4.3.5", + "@types/chai-subset": "^1.3.3", + "@types/node": "*", + "@vitest/expect": "0.34.6", + "@vitest/runner": "0.34.6", + "@vitest/snapshot": "0.34.6", + "@vitest/spy": "0.34.6", + "@vitest/utils": "0.34.6", + "acorn": "^8.9.0", + "acorn-walk": "^8.2.0", + "cac": "^6.7.14", + "chai": "^4.3.10", + "debug": "^4.3.4", + "local-pkg": "^0.4.3", + "magic-string": "^0.30.1", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.3.3", + "strip-literal": "^1.0.1", + "tinybench": "^2.5.0", + "tinypool": "^0.7.0", + "vite": "^3.1.0 || ^4.0.0 || ^5.0.0-0", + "vite-node": "0.34.6", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": ">=v14.18.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@vitest/browser": "*", + "@vitest/ui": "*", + "happy-dom": "*", + "jsdom": "*", + "playwright": "*", + "safaridriver": "*", + "webdriverio": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "playwright": { + "optional": true + }, + "safaridriver": { + "optional": true + }, + "webdriverio": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/vitest/node_modules/acorn": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/vitest/node_modules/magic-string": { + "version": "0.30.4", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.4.tgz", + "integrity": "sha512-Q/TKtsC5BPm0kGqgBIF9oXAs/xEf2vRKiIB4wCRQTJOQIByZ1d+NnUOotvJOvNpi5RNIgVOMC3pOuaP1ZTDlVg==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", @@ -20628,6 +21661,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/why-is-node-running": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", + "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", + "dev": true, + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", @@ -22800,6 +23849,160 @@ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" }, + "@esbuild/android-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "dev": true, + "optional": true + }, "@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -22957,6 +24160,15 @@ "integrity": "sha512-1+GXATaJLP5akFnUrpxYzoshLtTPZXJEdy/ozhY1g/DkULlz4LthLTaTJ2qImF0mb8Ayk7LNbh00n4ATk0JycA==", "requires": {} }, + "@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.27.8" + } + }, "@jridgewell/gen-mapping": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", @@ -23331,6 +24543,12 @@ } } }, + "@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, "@surma/rollup-plugin-off-main-thread": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", @@ -23406,6 +24624,21 @@ "@types/node": "*" } }, + "@types/chai": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.6.tgz", + "integrity": "sha512-VOVRLM1mBxIRxydiViqPcKn6MIxZytrbMpd6RJLIWKxUNr3zux8no0Oc7kJx0WAPIitgZ0gkrDS+btlqQpubpw==", + "dev": true + }, + "@types/chai-subset": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@types/chai-subset/-/chai-subset-1.3.3.tgz", + "integrity": "sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==", + "dev": true, + "requires": { + "@types/chai": "*" + } + }, "@types/connect": { "version": "3.4.35", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", @@ -23889,6 +25122,93 @@ "integrity": "sha512-V3vzdXunOKKob1F+2ldv/4iGQoQA/iyqtW8PVlr1v16xCCKL831pGUubT+vs5/9wxTE/zBKEhjIjmmpK6nqw2A==", "dev": true }, + "@vitest/expect": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.34.6.tgz", + "integrity": "sha512-QUzKpUQRc1qC7qdGo7rMK3AkETI7w18gTCUrsNnyjjJKYiuUB9+TQK3QnR1unhCnWRC0AbKv2omLGQDF/mIjOw==", + "dev": true, + "requires": { + "@vitest/spy": "0.34.6", + "@vitest/utils": "0.34.6", + "chai": "^4.3.10" + } + }, + "@vitest/runner": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.34.6.tgz", + "integrity": "sha512-1CUQgtJSLF47NnhN+F9X2ycxUP0kLHQ/JWvNHbeBfwW8CzEGgeskzNnHDyv1ieKTltuR6sdIHV+nmR6kPxQqzQ==", + "dev": true, + "requires": { + "@vitest/utils": "0.34.6", + "p-limit": "^4.0.0", + "pathe": "^1.1.1" + }, + "dependencies": { + "p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "requires": { + "yocto-queue": "^1.0.0" + } + }, + "yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true + } + } + }, + "@vitest/snapshot": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-0.34.6.tgz", + "integrity": "sha512-B3OZqYn6k4VaN011D+ve+AA4whM4QkcwcrwaKwAbyyvS/NB1hCWjFIBQxAQQSQir9/RtyAAGuq+4RJmbn2dH4w==", + "dev": true, + "requires": { + "magic-string": "^0.30.1", + "pathe": "^1.1.1", + "pretty-format": "^29.5.0" + }, + "dependencies": { + "@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "magic-string": { + "version": "0.30.4", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.4.tgz", + "integrity": "sha512-Q/TKtsC5BPm0kGqgBIF9oXAs/xEf2vRKiIB4wCRQTJOQIByZ1d+NnUOotvJOvNpi5RNIgVOMC3pOuaP1ZTDlVg==", + "dev": true, + "requires": { + "@jridgewell/sourcemap-codec": "^1.4.15" + } + } + } + }, + "@vitest/spy": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.34.6.tgz", + "integrity": "sha512-xaCvneSaeBw/cz8ySmF7ZwGvL0lBjfvqc1LpQ/vcdHEvpLn3Ff1vAvjw+CoGn0802l++5L/pxb7whwcWAw+DUQ==", + "dev": true, + "requires": { + "tinyspy": "^2.1.1" + } + }, + "@vitest/utils": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.34.6.tgz", + "integrity": "sha512-IG5aDD8S6zlvloDsnzHw0Ut5xczlF+kv2BOTo+iXfPr54Yhi5qbVOgGB1hZaVq4iJ4C/MZ2J0y15IlsV/ZcI0A==", + "dev": true, + "requires": { + "diff-sequences": "^29.4.3", + "loupe": "^2.3.6", + "pretty-format": "^29.5.0" + } + }, "@webassemblyjs/ast": { "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", @@ -24344,6 +25664,12 @@ "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", "dev": true }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, "assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", @@ -24783,6 +26109,12 @@ "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", "dev": true }, + "cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true + }, "cache-base": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", @@ -24908,6 +26240,21 @@ "integrity": "sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg==", "dev": true }, + "chai": { + "version": "4.3.10", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.10.tgz", + "integrity": "sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.0.8" + } + }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -24942,6 +26289,15 @@ "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", "dev": true }, + "check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "requires": { + "get-func-name": "^2.0.2" + } + }, "chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -25681,6 +27037,15 @@ "mimic-response": "^2.0.0" } }, + "deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, "deep-equal": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.0.tgz", @@ -25843,6 +27208,12 @@ "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", "dev": true }, + "diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true + }, "dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -26274,6 +27645,36 @@ "ext": "^1.1.2" } }, + "esbuild": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "dev": true, + "requires": { + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" + } + }, "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -27715,6 +29116,12 @@ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true }, + "get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true + }, "get-intrinsic": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", @@ -29007,6 +30414,12 @@ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, + "jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true + }, "jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -29162,6 +30575,12 @@ "json5": "^2.1.2" } }, + "local-pkg": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.3.tgz", + "integrity": "sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==", + "dev": true + }, "localforage": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", @@ -29292,6 +30711,15 @@ "signal-exit": "^3.0.0" } }, + "loupe": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", + "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", + "dev": true, + "requires": { + "get-func-name": "^2.0.0" + } + }, "lower-case": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", @@ -29639,6 +31067,26 @@ "minimist": "^1.2.5" } }, + "mlly": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.4.2.tgz", + "integrity": "sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==", + "dev": true, + "requires": { + "acorn": "^8.10.0", + "pathe": "^1.1.1", + "pkg-types": "^1.0.3", + "ufo": "^1.3.0" + }, + "dependencies": { + "acorn": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "dev": true + } + } + }, "mrmime": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz", @@ -30245,6 +31693,18 @@ "resolved": "https://registry.npmjs.org/path2d-polyfill/-/path2d-polyfill-2.0.1.tgz", "integrity": "sha512-ad/3bsalbbWhmBo0D6FZ4RNMwsLsPpL6gnvhuSaU5Vm7b06Kr5ubSltQQ0T7YKsiJQO+g22zJ4dJKNTXIyOXtA==" }, + "pathe": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.1.tgz", + "integrity": "sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==", + "dev": true + }, + "pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true + }, "pdfjs-dist": { "version": "3.6.172", "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-3.6.172.tgz", @@ -30297,6 +31757,17 @@ "find-up": "^4.0.0" } }, + "pkg-types": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz", + "integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==", + "dev": true, + "requires": { + "jsonc-parser": "^3.2.0", + "mlly": "^1.2.0", + "pathe": "^1.1.0" + } + }, "plur": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/plur/-/plur-2.1.2.tgz", @@ -31411,6 +32882,31 @@ "renderkid": "^3.0.0" } }, + "pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "requires": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + } + } + }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -32277,6 +33773,12 @@ "object-inspect": "^1.9.0" } }, + "siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true + }, "signal-exit": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", @@ -32750,6 +34252,12 @@ "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", "dev": true }, + "stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true + }, "state-toggle": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.3.tgz", @@ -32783,6 +34291,12 @@ "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", "dev": true }, + "std-env": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.4.3.tgz", + "integrity": "sha512-f9aPhy8fYBuMN+sNfakZV18U39PbalgjXG3lLB9WkaYTxijru61wb57V9wxxNthXM5Sd88ETBWi29qLAsHO52Q==", + "dev": true + }, "stop-iteration-iterator": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", @@ -32928,6 +34442,23 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, + "strip-literal": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.3.0.tgz", + "integrity": "sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==", + "dev": true, + "requires": { + "acorn": "^8.10.0" + }, + "dependencies": { + "acorn": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "dev": true + } + } + }, "style-loader": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.3.tgz", @@ -35236,6 +36767,24 @@ "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", "dev": true }, + "tinybench": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.5.1.tgz", + "integrity": "sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==", + "dev": true + }, + "tinypool": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.7.0.tgz", + "integrity": "sha512-zSYNUlYSMhJ6Zdou4cJwo/p7w5nmAH17GRfU/ui3ctvjXFErXXkruT4MWW6poDeXgCaIBlGLrfU6TbTXxyGMww==", + "dev": true + }, + "tinyspy": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.0.tgz", + "integrity": "sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==", + "dev": true + }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -35471,6 +37020,12 @@ "prelude-ls": "^1.2.1" } }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, "type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -35493,6 +37048,12 @@ "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", "dev": true }, + "ufo": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.3.1.tgz", + "integrity": "sha512-uY/99gMLIOlJPwATcMVYfqDSxUR9//AUcgZMzwfSTJPDKzA1S8mX4VLqa+fiAtveraQUBCz4FFcwVZBGbwBXIw==", + "dev": true + }, "unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -35770,6 +37331,109 @@ "unist-util-stringify-position": "^2.0.0" } }, + "vite": { + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz", + "integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==", + "dev": true, + "requires": { + "esbuild": "^0.18.10", + "fsevents": "~2.3.2", + "postcss": "^8.4.27", + "rollup": "^3.27.1" + }, + "dependencies": { + "postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "dev": true, + "requires": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + } + }, + "rollup": { + "version": "3.29.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", + "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", + "dev": true, + "requires": { + "fsevents": "~2.3.2" + } + } + } + }, + "vite-node": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-0.34.6.tgz", + "integrity": "sha512-nlBMJ9x6n7/Amaz6F3zJ97EBwR2FkzhBRxF5e+jE6LA3yi6Wtc2lyTij1OnDMIr34v5g/tVQtsVAzhT0jc5ygA==", + "dev": true, + "requires": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "mlly": "^1.4.0", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0-0" + } + }, + "vitest": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.34.6.tgz", + "integrity": "sha512-+5CALsOvbNKnS+ZHMXtuUC7nL8/7F1F2DnHGjSsszX8zCjWSSviphCb/NuS9Nzf4Q03KyyDRBAXhF/8lffME4Q==", + "dev": true, + "requires": { + "@types/chai": "^4.3.5", + "@types/chai-subset": "^1.3.3", + "@types/node": "*", + "@vitest/expect": "0.34.6", + "@vitest/runner": "0.34.6", + "@vitest/snapshot": "0.34.6", + "@vitest/spy": "0.34.6", + "@vitest/utils": "0.34.6", + "acorn": "^8.9.0", + "acorn-walk": "^8.2.0", + "cac": "^6.7.14", + "chai": "^4.3.10", + "debug": "^4.3.4", + "local-pkg": "^0.4.3", + "magic-string": "^0.30.1", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.3.3", + "strip-literal": "^1.0.1", + "tinybench": "^2.5.0", + "tinypool": "^0.7.0", + "vite": "^3.1.0 || ^4.0.0 || ^5.0.0-0", + "vite-node": "0.34.6", + "why-is-node-running": "^2.2.2" + }, + "dependencies": { + "@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "acorn": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "dev": true + }, + "magic-string": { + "version": "0.30.4", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.4.tgz", + "integrity": "sha512-Q/TKtsC5BPm0kGqgBIF9oXAs/xEf2vRKiIB4wCRQTJOQIByZ1d+NnUOotvJOvNpi5RNIgVOMC3pOuaP1ZTDlVg==", + "dev": true, + "requires": { + "@jridgewell/sourcemap-codec": "^1.4.15" + } + } + } + }, "watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", @@ -36148,6 +37812,16 @@ "is-typed-array": "^1.1.10" } }, + "why-is-node-running": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", + "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", + "dev": true, + "requires": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + } + }, "wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", diff --git a/package.json b/package.json index 378942f26..cacf69304 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "stylelint-scss": "5.0.0", "ts-loader": "9.4.4", "typescript": "5.0.4", + "vitest": "0.34.6", "webpack": "5.88.1", "webpack-bundle-analyzer": "4.9.1", "webpack-cli": "5.1.4", @@ -145,6 +146,8 @@ "build:check": "tsc --noEmit", "escheck": "es-check", "lint": "eslint \"./\"", + "test": "vitest --watch=false", + "test:watch": "vitest", "stylelint": "npm run stylelint:css && npm run stylelint:scss", "stylelint:css": "stylelint \"src/**/*.css\"", "stylelint:scss": "stylelint --config=\".stylelintrc.scss.json\" \"src/**/*.scss\"" From d77d69b57022c0799c78ac04a89b4bc90818a2a2 Mon Sep 17 00:00:00 2001 From: Dmitriy Dubson Date: Tue, 3 Oct 2023 10:34:40 -0400 Subject: [PATCH 141/142] Refactor desired aspect and posters per row functions to reduce cognitive complexity Fixes gh-4828 --- src/components/cardbuilder/cardBuilder.js | 242 +--------- .../cardbuilder/cardBuilderUtils.js | 173 ++++++++ .../cardbuilder/cardBuilderUtils.test.js | 417 ++++++++++++++++++ 3 files changed, 594 insertions(+), 238 deletions(-) create mode 100644 src/components/cardbuilder/cardBuilderUtils.js create mode 100644 src/components/cardbuilder/cardBuilderUtils.test.js diff --git a/src/components/cardbuilder/cardBuilder.js b/src/components/cardbuilder/cardBuilder.js index c2f5436b3..d52713283 100644 --- a/src/components/cardbuilder/cardBuilder.js +++ b/src/components/cardbuilder/cardBuilder.js @@ -6,6 +6,7 @@ import escapeHtml from 'escape-html'; +import cardBuilderUtils from './cardBuilderUtils'; import browser from 'scripts/browser'; import datetime from 'scripts/datetime'; import dom from 'scripts/dom'; @@ -46,217 +47,6 @@ export function getCardsHtml(items, options) { return buildCardsHtmlInternal(items, options); } -/** - * Computes the number of posters per row. - * @param {string} shape - Shape of the cards. - * @param {number} screenWidth - Width of the screen. - * @param {boolean} isOrientationLandscape - Flag for the orientation of the screen. - * @returns {number} Number of cards per row for an itemsContainer. - */ -function getPostersPerRow(shape, screenWidth, isOrientationLandscape) { - switch (shape) { - case 'portrait': - if (layoutManager.tv) { - return 100 / 16.66666667; - } - if (screenWidth >= 2200) { - return 100 / 10; - } - if (screenWidth >= 1920) { - return 100 / 11.1111111111; - } - if (screenWidth >= 1600) { - return 100 / 12.5; - } - if (screenWidth >= 1400) { - return 100 / 14.28571428571; - } - if (screenWidth >= 1200) { - return 100 / 16.66666667; - } - if (screenWidth >= 800) { - return 5; - } - if (screenWidth >= 700) { - return 4; - } - if (screenWidth >= 500) { - return 100 / 33.33333333; - } - return 100 / 33.33333333; - case 'square': - if (layoutManager.tv) { - return 100 / 16.66666667; - } - if (screenWidth >= 2200) { - return 100 / 10; - } - if (screenWidth >= 1920) { - return 100 / 11.1111111111; - } - if (screenWidth >= 1600) { - return 100 / 12.5; - } - if (screenWidth >= 1400) { - return 100 / 14.28571428571; - } - if (screenWidth >= 1200) { - return 100 / 16.66666667; - } - if (screenWidth >= 800) { - return 5; - } - if (screenWidth >= 700) { - return 4; - } - if (screenWidth >= 500) { - return 100 / 33.33333333; - } - return 2; - case 'banner': - if (screenWidth >= 2200) { - return 100 / 25; - } - if (screenWidth >= 1200) { - return 100 / 33.33333333; - } - if (screenWidth >= 800) { - return 2; - } - return 1; - case 'backdrop': - if (layoutManager.tv) { - return 100 / 25; - } - if (screenWidth >= 2500) { - return 6; - } - if (screenWidth >= 1600) { - return 5; - } - if (screenWidth >= 1200) { - return 4; - } - if (screenWidth >= 770) { - return 3; - } - if (screenWidth >= 420) { - return 2; - } - return 1; - case 'smallBackdrop': - if (screenWidth >= 1600) { - return 100 / 12.5; - } - if (screenWidth >= 1400) { - return 100 / 14.2857142857; - } - if (screenWidth >= 1200) { - return 100 / 16.66666667; - } - if (screenWidth >= 1000) { - return 5; - } - if (screenWidth >= 800) { - return 4; - } - if (screenWidth >= 500) { - return 100 / 33.33333333; - } - return 2; - case 'overflowSmallBackdrop': - if (layoutManager.tv) { - return 100 / 18.9; - } - if (isOrientationLandscape) { - if (screenWidth >= 800) { - return 100 / 15.5; - } - return 100 / 23.3; - } else { - if (screenWidth >= 540) { - return 100 / 30; - } - return 100 / 72; - } - case 'overflowPortrait': - - if (layoutManager.tv) { - return 100 / 15.5; - } - if (isOrientationLandscape) { - if (screenWidth >= 1700) { - return 100 / 11.6; - } - return 100 / 15.5; - } else { - if (screenWidth >= 1400) { - return 100 / 15; - } - if (screenWidth >= 1200) { - return 100 / 18; - } - if (screenWidth >= 760) { - return 100 / 23; - } - if (screenWidth >= 400) { - return 100 / 31.5; - } - return 100 / 42; - } - case 'overflowSquare': - if (layoutManager.tv) { - return 100 / 15.5; - } - if (isOrientationLandscape) { - if (screenWidth >= 1700) { - return 100 / 11.6; - } - return 100 / 15.5; - } else { - if (screenWidth >= 1400) { - return 100 / 15; - } - if (screenWidth >= 1200) { - return 100 / 18; - } - if (screenWidth >= 760) { - return 100 / 23; - } - if (screenWidth >= 540) { - return 100 / 31.5; - } - return 100 / 42; - } - case 'overflowBackdrop': - if (layoutManager.tv) { - return 100 / 23.3; - } - if (isOrientationLandscape) { - if (screenWidth >= 1700) { - return 100 / 18.5; - } - return 100 / 23.3; - } else { - if (screenWidth >= 1800) { - return 100 / 23.5; - } - if (screenWidth >= 1400) { - return 100 / 30; - } - if (screenWidth >= 760) { - return 100 / 40; - } - if (screenWidth >= 640) { - return 100 / 56; - } - return 100 / 72; - } - default: - return 4; - } -} - /** * Checks if the window is resizable. * @param {number} windowWidth - Width of the device's screen. @@ -283,7 +73,7 @@ function isResizable(windowWidth) { * @returns {number} Width of the image for a card. */ function getImageWidth(shape, screenWidth, isOrientationLandscape) { - const imagesPerRow = getPostersPerRow(shape, screenWidth, isOrientationLandscape); + const imagesPerRow = cardBuilderUtils.getPostersPerRow(shape, screenWidth, isOrientationLandscape, layoutManager.tv); return Math.round(screenWidth / imagesPerRow); } @@ -323,7 +113,7 @@ function setCardData(items, options) { options.preferThumb = options.shape === 'backdrop' || options.shape === 'overflowBackdrop'; } - options.uiAspect = getDesiredAspect(options.shape); + options.uiAspect = cardBuilderUtils.getDesiredAspect(options.shape); options.primaryImageAspectRatio = primaryImageAspectRatio; if (!options.width && options.widths) { @@ -465,30 +255,6 @@ function buildCardsHtmlInternal(items, options) { return html; } -/** - * Computes the aspect ratio for a card given its shape. - * @param {string} shape - Shape for which to get the aspect ratio. - * @returns {null|number} Ratio of the shape. - */ -function getDesiredAspect(shape) { - if (shape) { - shape = shape.toLowerCase(); - if (shape.indexOf('portrait') !== -1) { - return (2 / 3); - } - if (shape.indexOf('backdrop') !== -1) { - return (16 / 9); - } - if (shape.indexOf('square') !== -1) { - return 1; - } - if (shape.indexOf('banner') !== -1) { - return (1000 / 185); - } - } - return null; -} - /** * @typedef {Object} CardImageUrl * @property {string} imgUrl - Image URL. @@ -514,7 +280,7 @@ function getCardImageUrl(item, apiClient, options, shape) { let imgUrl = null; let imgTag = null; let coverImage = false; - const uiAspect = getDesiredAspect(shape); + const uiAspect = cardBuilderUtils.getDesiredAspect(shape); let imgType = null; let itemId = null; diff --git a/src/components/cardbuilder/cardBuilderUtils.js b/src/components/cardbuilder/cardBuilderUtils.js new file mode 100644 index 000000000..494dcaf64 --- /dev/null +++ b/src/components/cardbuilder/cardBuilderUtils.js @@ -0,0 +1,173 @@ +const ASPECT_RATIOS = { + portrait: (2 / 3), + backdrop: (16 / 9), + square: 1, + banner: (1000 / 185) +}; + +/** + * Computes the aspect ratio for a card given its shape. + * @param {string} shape - Shape for which to get the aspect ratio. + * @returns {null|number} Ratio of the shape. + */ +function getDesiredAspect(shape) { + if (!shape) { + return null; + } + + shape = shape.toLowerCase(); + if (shape.indexOf('portrait') !== -1) { + return ASPECT_RATIOS.portrait; + } + if (shape.indexOf('backdrop') !== -1) { + return ASPECT_RATIOS.backdrop; + } + if (shape.indexOf('square') !== -1) { + return ASPECT_RATIOS.square; + } + if (shape.indexOf('banner') !== -1) { + return ASPECT_RATIOS.banner; + } + + return null; +} + +/** + * Computes the number of posters per row. + * @param {string} shape - Shape of the cards. + * @param {number} screenWidth - Width of the screen. + * @param {boolean} isOrientationLandscape - Flag for the orientation of the screen. + * @param {boolean} isTV - Flag to denote if posters are rendered on a television screen. + * @returns {number} Number of cards per row for an itemsContainer. + */ +function getPostersPerRow(shape, screenWidth, isOrientationLandscape, isTV) { + switch (shape) { + case 'portrait': return postersPerRowPortrait(screenWidth, isTV); + case 'square': return postersPerRowSquare(screenWidth, isTV); + case 'banner': return postersPerRowBanner(screenWidth); + case 'backdrop': return postersPerRowBackdrop(screenWidth, isTV); + case 'smallBackdrop': return postersPerRowSmallBackdrop(screenWidth); + case 'overflowSmallBackdrop': return postersPerRowOverflowSmallBackdrop(screenWidth, isOrientationLandscape, isTV); + case 'overflowPortrait': return postersPerRowOverflowPortrait(screenWidth, isOrientationLandscape, isTV); + case 'overflowSquare': return postersPerRowOverflowSquare(screenWidth, isOrientationLandscape, isTV); + case 'overflowBackdrop': return postersPerRowOverflowBackdrop(screenWidth, isOrientationLandscape, isTV); + default: return 4; + } +} + +const postersPerRowPortrait = (screenWidth, isTV) => { + switch (true) { + case isTV: return 100 / 16.66666667; + case screenWidth >= 2200: return 10; + case screenWidth >= 1920: return 100 / 11.1111111111; + case screenWidth >= 1600: return 8; + case screenWidth >= 1400: return 100 / 14.28571428571; + case screenWidth >= 1200: return 100 / 16.66666667; + case screenWidth >= 800: return 5; + case screenWidth >= 700: return 4; + case screenWidth >= 500: return 100 / 33.33333333; + default: return 100 / 33.33333333; + } +}; + +const postersPerRowSquare = (screenWidth, isTV) => { + switch (true) { + case isTV: return 100 / 16.66666667; + case screenWidth >= 2200: return 10; + case screenWidth >= 1920: return 100 / 11.1111111111; + case screenWidth >= 1600: return 8; + case screenWidth >= 1400: return 100 / 14.28571428571; + case screenWidth >= 1200: return 100 / 16.66666667; + case screenWidth >= 800: return 5; + case screenWidth >= 700: return 4; + case screenWidth >= 500: return 100 / 33.33333333; + default: return 2; + } +}; + +const postersPerRowBanner = (screenWidth) => { + switch (true) { + case screenWidth >= 2200: return 4; + case screenWidth >= 1200: return 100 / 33.33333333; + case screenWidth >= 800: return 2; + default: return 1; + } +}; + +const postersPerRowBackdrop = (screenWidth, isTV) => { + switch (true) { + case isTV: return 4; + case screenWidth >= 2500: return 6; + case screenWidth >= 1600: return 5; + case screenWidth >= 1200: return 4; + case screenWidth >= 770: return 3; + case screenWidth >= 420: return 2; + default: return 1; + } +}; + +function postersPerRowSmallBackdrop(screenWidth) { + switch (true) { + case screenWidth >= 1600: return 8; + case screenWidth >= 1400: return 100 / 14.2857142857; + case screenWidth >= 1200: return 100 / 16.66666667; + case screenWidth >= 1000: return 5; + case screenWidth >= 800: return 4; + case screenWidth >= 500: return 100 / 33.33333333; + default: return 2; + } +} + +const postersPerRowOverflowSmallBackdrop = (screenWidth, isLandscape, isTV) => { + switch (true) { + case isTV: return 100 / 18.9; + case isLandscape && screenWidth >= 800: return 100 / 15.5; + case isLandscape: return 100 / 23.3; + case screenWidth >= 540: return 100 / 30; + default: return 100 / 72; + } +}; + +const postersPerRowOverflowPortrait = (screenWidth, isLandscape, isTV) => { + switch (true) { + case isTV: return 100 / 15.5; + case isLandscape && screenWidth >= 1700: return 100 / 11.6; + case isLandscape: return 100 / 15.5; + case screenWidth >= 1400: return 100 / 15; + case screenWidth >= 1200: return 100 / 18; + case screenWidth >= 760: return 100 / 23; + case screenWidth >= 400: return 100 / 31.5; + default: return 100 / 42; + } +}; + +const postersPerRowOverflowSquare = (screenWidth, isLandscape, isTV) => { + switch (true) { + case isTV: return 100 / 15.5; + case isLandscape && screenWidth >= 1700: return 100 / 11.6; + case isLandscape: return 100 / 15.5; + case screenWidth >= 1400: return 100 / 15; + case screenWidth >= 1200: return 100 / 18; + case screenWidth >= 760: return 100 / 23; + case screenWidth >= 540: return 100 / 31.5; + default: return 100 / 42; + } +}; + +const postersPerRowOverflowBackdrop = (screenWidth, isLandscape, isTV) => { + switch (true) { + case isTV: return 100 / 23.3; + case isLandscape && screenWidth >= 1700: return 100 / 18.5; + case isLandscape: return 100 / 23.3; + case screenWidth >= 1800: return 100 / 23.5; + case screenWidth >= 1400: return 100 / 30; + case screenWidth >= 760: return 100 / 40; + case screenWidth >= 640: return 100 / 56; + default: return 100 / 72; + } +}; + +export default { + getDesiredAspect, + getPostersPerRow +}; diff --git a/src/components/cardbuilder/cardBuilderUtils.test.js b/src/components/cardbuilder/cardBuilderUtils.test.js new file mode 100644 index 000000000..46599135d --- /dev/null +++ b/src/components/cardbuilder/cardBuilderUtils.test.js @@ -0,0 +1,417 @@ +import { describe, expect, test } from 'vitest'; +import cardBuilderUtils from './cardBuilderUtils'; + +describe('getDesiredAspect', () => { + test('"portrait" (case insensitive)', () => { + expect(cardBuilderUtils.getDesiredAspect('portrait')).toEqual((2 / 3)); + expect(cardBuilderUtils.getDesiredAspect('PorTRaIt')).toEqual((2 / 3)); + }); + + test('"backdrop" (case insensitive)', () => { + expect(cardBuilderUtils.getDesiredAspect('backdrop')).toEqual((16 / 9)); + expect(cardBuilderUtils.getDesiredAspect('BaCkDroP')).toEqual((16 / 9)); + }); + + test('"square" (case insensitive)', () => { + expect(cardBuilderUtils.getDesiredAspect('square')).toEqual(1); + expect(cardBuilderUtils.getDesiredAspect('sQuArE')).toEqual(1); + }); + + test('"banner" (case insensitive)', () => { + expect(cardBuilderUtils.getDesiredAspect('banner')).toEqual((1000 / 185)); + expect(cardBuilderUtils.getDesiredAspect('BaNnEr')).toEqual((1000 / 185)); + }); + + test('invalid shape', () => { + expect(cardBuilderUtils.getDesiredAspect('invalid')).toBeNull(); + }); + + test('shape is not provided', () => { + expect(cardBuilderUtils.getDesiredAspect('')).toBeNull(); + }); +}); + +describe('getPostersPerRow', () => { + test('resolves to default of 4 posters per row if shape is not provided', () => { + expect(cardBuilderUtils.getPostersPerRow('', 0, false, false)).toEqual(4); + }); + + describe('portrait', () => { + const postersPerRowForPortrait = (screenWidth, isTV) => (cardBuilderUtils.getPostersPerRow('portrait', screenWidth, false, isTV)); + + test('television', () => { + expect(postersPerRowForPortrait(0, true)).toEqual(100 / 16.66666667); + }); + + test('screen width less than 500px', () => { + expect(postersPerRowForPortrait(100, false)).toEqual(100 / 33.33333333); + expect(postersPerRowForPortrait(499, false)).toEqual(100 / 33.33333333); + }); + + test('screen width greater or equal to 500px', () => { + expect(postersPerRowForPortrait(500, false)).toEqual(100 / 33.33333333); + expect(postersPerRowForPortrait(501, false)).toEqual(100 / 33.33333333); + }); + + test('screen width greater or equal to 700px', () => { + expect(postersPerRowForPortrait(700, false)).toEqual(4); + expect(postersPerRowForPortrait(701, false)).toEqual(4); + }); + + test('screen width greater or equal to 800px', () => { + expect(postersPerRowForPortrait(800, false)).toEqual(5); + expect(postersPerRowForPortrait(801, false)).toEqual(5); + }); + + test('screen width greater or equal to 1200px', () => { + expect(postersPerRowForPortrait(1200, false)).toEqual(100 / 16.66666667); + expect(postersPerRowForPortrait(1201, false)).toEqual(100 / 16.66666667); + }); + + test('screen width greater or equal to 1400px', () => { + expect(postersPerRowForPortrait(1400, false)).toEqual( 100 / 14.28571428571); + expect(postersPerRowForPortrait(1401, false)).toEqual( 100 / 14.28571428571); + }); + + test('screen width greater or equal to 1600px', () => { + expect(postersPerRowForPortrait(1600, false)).toEqual( 8); + expect(postersPerRowForPortrait(1601, false)).toEqual( 8); + }); + + test('screen width greater or equal to 1920px', () => { + expect(postersPerRowForPortrait(1920, false)).toEqual( 100 / 11.1111111111); + expect(postersPerRowForPortrait(1921, false)).toEqual( 100 / 11.1111111111); + }); + + test('screen width greater or equal to 2200px', () => { + expect(postersPerRowForPortrait(2200, false)).toEqual( 10); + expect(postersPerRowForPortrait(2201, false)).toEqual( 10); + }); + }); + + describe('square', () => { + const postersPerRowForSquare = (screenWidth, isTV) => (cardBuilderUtils.getPostersPerRow('square', screenWidth, false, isTV)); + + test('television', () => { + expect(postersPerRowForSquare(0, true)).toEqual(100 / 16.66666667); + }); + + test('screen width less than 500px', () => { + expect(postersPerRowForSquare(100, false)).toEqual(2); + expect(postersPerRowForSquare(499, false)).toEqual(2); + }); + + test('screen width greater or equal to 500px', () => { + expect(postersPerRowForSquare(500, false)).toEqual(100 / 33.33333333); + expect(postersPerRowForSquare(501, false)).toEqual(100 / 33.33333333); + }); + + test('screen width greater or equal to 700px', () => { + expect(postersPerRowForSquare(700, false)).toEqual(4); + expect(postersPerRowForSquare(701, false)).toEqual(4); + }); + + test('screen width greater or equal to 800px', () => { + expect(postersPerRowForSquare(800, false)).toEqual(5); + expect(postersPerRowForSquare(801, false)).toEqual(5); + }); + + test('screen width greater or equal to 1200px', () => { + expect(postersPerRowForSquare(1200, false)).toEqual(100 / 16.66666667); + expect(postersPerRowForSquare(1201, false)).toEqual(100 / 16.66666667); + }); + + test('screen width greater or equal to 1400px', () => { + expect(postersPerRowForSquare(1400, false)).toEqual( 100 / 14.28571428571); + expect(postersPerRowForSquare(1401, false)).toEqual( 100 / 14.28571428571); + }); + + test('screen width greater or equal to 1600px', () => { + expect(postersPerRowForSquare(1600, false)).toEqual(8); + expect(postersPerRowForSquare(1601, false)).toEqual(8); + }); + + test('screen width greater or equal to 1920px', () => { + expect(postersPerRowForSquare(1920, false)).toEqual(100 / 11.1111111111); + expect(postersPerRowForSquare(1921, false)).toEqual(100 / 11.1111111111); + }); + + test('screen width greater or equal to 2200px', () => { + expect(postersPerRowForSquare(2200, false)).toEqual( 10); + expect(postersPerRowForSquare(2201, false)).toEqual( 10); + }); + }); + + describe('banner', () => { + const postersPerRowForBanner = (screenWidth) => (cardBuilderUtils.getPostersPerRow('banner', screenWidth, false, false)); + + test('screen width less than 800px', () => { + expect(postersPerRowForBanner(799)).toEqual(1); + }); + + test('screen width greater than or equal to 800px', () => { + expect(postersPerRowForBanner(800)).toEqual(2); + expect(postersPerRowForBanner(801)).toEqual(2); + }); + + test('screen width greater than or equal to 1200px', () => { + expect(postersPerRowForBanner(1200)).toEqual(100 / 33.33333333); + expect(postersPerRowForBanner(1201)).toEqual(100 / 33.33333333); + }); + + test('screen width greater than or equal to 2200px', () => { + expect(postersPerRowForBanner(2200)).toEqual(4); + expect(postersPerRowForBanner(2201)).toEqual(4); + }); + }); + + describe('backdrop', () => { + const postersPerRowForBackdrop = (screenWidth, isTV) => (cardBuilderUtils.getPostersPerRow('backdrop', screenWidth, false, isTV)); + + test('television', () => { + expect(postersPerRowForBackdrop(0, true)).toEqual(4); + }); + + test('screen width less than 420px', () => { + expect(postersPerRowForBackdrop(100, false)).toEqual(1); + expect(postersPerRowForBackdrop(419, false)).toEqual(1); + }); + + test('screen width greater or equal to 420px', () => { + expect(postersPerRowForBackdrop(420, false)).toEqual(2); + expect(postersPerRowForBackdrop(421, false)).toEqual(2); + }); + + test('screen width greater or equal to 770px', () => { + expect(postersPerRowForBackdrop(770, false)).toEqual(3); + expect(postersPerRowForBackdrop(771, false)).toEqual(3); + }); + + test('screen width greater or equal to 1200px', () => { + expect(postersPerRowForBackdrop(1200, false)).toEqual(4); + expect(postersPerRowForBackdrop(1201, false)).toEqual(4); + }); + + test('screen width greater or equal to 1600px', () => { + expect(postersPerRowForBackdrop(1600, false)).toEqual(5); + expect(postersPerRowForBackdrop(1601, false)).toEqual(5); + }); + + test('screen width greater or equal to 2500px', () => { + expect(postersPerRowForBackdrop(2500, false)).toEqual(6); + expect(postersPerRowForBackdrop(2501, false)).toEqual(6); + }); + }); + + describe('small backdrop', () => { + const postersPerRowForSmallBackdrop = (screenWidth) => (cardBuilderUtils.getPostersPerRow('smallBackdrop', screenWidth, false, false)); + + test('screen width less than 500px', () => { + expect(postersPerRowForSmallBackdrop(100)).toEqual(2); + expect(postersPerRowForSmallBackdrop(499)).toEqual(2); + }); + + test('screen width greater or equal to 500px', () => { + expect(postersPerRowForSmallBackdrop(500)).toEqual(100 / 33.33333333); + expect(postersPerRowForSmallBackdrop(501)).toEqual(100 / 33.33333333); + }); + + test('screen width greater or equal to 800px', () => { + expect(postersPerRowForSmallBackdrop(800)).toEqual(4); + expect(postersPerRowForSmallBackdrop(801)).toEqual(4); + }); + + test('screen width greater or equal to 1000px', () => { + expect(postersPerRowForSmallBackdrop(1000)).toEqual(5); + expect(postersPerRowForSmallBackdrop(1001)).toEqual(5); + }); + + test('screen width greater or equal to 1200px', () => { + expect(postersPerRowForSmallBackdrop(1200)).toEqual(100 / 16.66666667); + expect(postersPerRowForSmallBackdrop(1201)).toEqual(100 / 16.66666667); + }); + + test('screen width greater or equal to 1400px', () => { + expect(postersPerRowForSmallBackdrop(1400)).toEqual(100 / 14.2857142857); + expect(postersPerRowForSmallBackdrop(1401)).toEqual(100 / 14.2857142857); + }); + + test('screen width greater or equal to 1600px', () => { + expect(postersPerRowForSmallBackdrop(1600)).toEqual(8); + expect(postersPerRowForSmallBackdrop(1601)).toEqual(8); + }); + }); + + describe('overflow small backdrop', () => { + const postersPerRowForOverflowSmallBackdrop = (screenWidth, isLandscape, isTV) => (cardBuilderUtils.getPostersPerRow('overflowSmallBackdrop', screenWidth, isLandscape, isTV)); + + test('television', () => { + expect(postersPerRowForOverflowSmallBackdrop(0, false, true)).toEqual( 100 / 18.9); + }); + + describe('non-landscape', () => { + test('screen width greater or equal to 540px', () => { + expect(postersPerRowForOverflowSmallBackdrop(540, false)).toEqual(100 / 30); + expect(postersPerRowForOverflowSmallBackdrop(541, false)).toEqual(100 / 30); + }); + + test('screen width is less than 540px', () => { + expect(postersPerRowForOverflowSmallBackdrop(539, false)).toEqual(100 / 72); + expect(postersPerRowForOverflowSmallBackdrop(100, false)).toEqual(100 / 72); + }); + }); + + describe('landscape', () => { + test('screen width greater or equal to 800px', () => { + expect(postersPerRowForOverflowSmallBackdrop(800, true)).toEqual(100 / 15.5); + expect(postersPerRowForOverflowSmallBackdrop(801, true)).toEqual(100 / 15.5); + }); + + test('screen width is less than 800px', () => { + expect(postersPerRowForOverflowSmallBackdrop(799, true)).toEqual(100 / 23.3); + expect(postersPerRowForOverflowSmallBackdrop(100, true)).toEqual(100 / 23.3); + }); + }); + }); + + describe('overflow portrait', () => { + const postersPerRowForOverflowPortrait = (screenWidth, isLandscape, isTV) => (cardBuilderUtils.getPostersPerRow('overflowPortrait', screenWidth, isLandscape, isTV)); + + test('television', () => { + expect(postersPerRowForOverflowPortrait(0, false, true)).toEqual( 100 / 15.5); + }); + + describe('non-landscape', () => { + test('screen width greater or equal to 1400px', () => { + expect(postersPerRowForOverflowPortrait(1400, false)).toEqual(100 / 15); + expect(postersPerRowForOverflowPortrait(1401, false)).toEqual(100 / 15); + }); + + test('screen width greater or equal to 1200px', () => { + expect(postersPerRowForOverflowPortrait(1200, false)).toEqual(100 / 18); + expect(postersPerRowForOverflowPortrait(1201, false)).toEqual(100 / 18); + }); + + test('screen width greater or equal to 760px', () => { + expect(postersPerRowForOverflowPortrait(760, false)).toEqual(100 / 23); + expect(postersPerRowForOverflowPortrait(761, false)).toEqual(100 / 23); + }); + + test('screen width greater or equal to 400px', () => { + expect(postersPerRowForOverflowPortrait(400, false)).toEqual(100 / 31.5); + expect(postersPerRowForOverflowPortrait(401, false)).toEqual(100 / 31.5); + }); + + test('screen width is less than 400px', () => { + expect(postersPerRowForOverflowPortrait(399, false)).toEqual(100 / 42); + expect(postersPerRowForOverflowPortrait(100, false)).toEqual(100 / 42); + }); + }); + + describe('landscape', () => { + test('screen width greater or equal to 1700px', () => { + expect(postersPerRowForOverflowPortrait(1700, true)).toEqual(100 / 11.6); + expect(postersPerRowForOverflowPortrait(1701, true)).toEqual(100 / 11.6); + }); + + test('screen width is less than 1700px', () => { + expect(postersPerRowForOverflowPortrait(1699, true)).toEqual(100 / 15.5); + expect(postersPerRowForOverflowPortrait(100, true)).toEqual(100 / 15.5); + }); + }); + }); + + describe('overflow square', () => { + const postersPerRowForOverflowSquare = (screenWidth, isLandscape, isTV) => (cardBuilderUtils.getPostersPerRow('overflowSquare', screenWidth, isLandscape, isTV)); + + test('television', () => { + expect(postersPerRowForOverflowSquare(0, false, true)).toEqual( 100 / 15.5); + }); + + describe('non-landscape', () => { + test('screen width greater or equal to 1400px', () => { + expect(postersPerRowForOverflowSquare(1400, false)).toEqual(100 / 15); + expect(postersPerRowForOverflowSquare(1401, false)).toEqual(100 / 15); + }); + + test('screen width greater or equal to 1200px', () => { + expect(postersPerRowForOverflowSquare(1200, false)).toEqual(100 / 18); + expect(postersPerRowForOverflowSquare(1201, false)).toEqual(100 / 18); + }); + + test('screen width greater or equal to 760px', () => { + expect(postersPerRowForOverflowSquare(760, false)).toEqual(100 / 23); + expect(postersPerRowForOverflowSquare(761, false)).toEqual(100 / 23); + }); + + test('screen width greater or equal to 540px', () => { + expect(postersPerRowForOverflowSquare(540, false)).toEqual(100 / 31.5); + expect(postersPerRowForOverflowSquare(541, false)).toEqual(100 / 31.5); + }); + + test('screen width is less than 540px', () => { + expect(postersPerRowForOverflowSquare(539, false)).toEqual(100 / 42); + expect(postersPerRowForOverflowSquare(100, false)).toEqual(100 / 42); + }); + }); + + describe('landscape', () => { + test('screen width greater or equal to 1700px', () => { + expect(postersPerRowForOverflowSquare(1700, true)).toEqual(100 / 11.6); + expect(postersPerRowForOverflowSquare(1701, true)).toEqual(100 / 11.6); + }); + + test('screen width is less than 1700px', () => { + expect(postersPerRowForOverflowSquare(1699, true)).toEqual(100 / 15.5); + expect(postersPerRowForOverflowSquare(100, true)).toEqual(100 / 15.5); + }); + }); + }); + + describe('overflow backdrop', () => { + const postersPerRowForOverflowBackdrop = (screenWidth, isLandscape, isTV) => (cardBuilderUtils.getPostersPerRow('overflowBackdrop', screenWidth, isLandscape, isTV)); + + test('television', () => { + expect(postersPerRowForOverflowBackdrop(0, false, true)).toEqual( 100 / 23.3); + }); + + describe('non-landscape', () => { + test('screen width greater or equal to 1800px', () => { + expect(postersPerRowForOverflowBackdrop(1800, false)).toEqual(100 / 23.5); + expect(postersPerRowForOverflowBackdrop(1801, false)).toEqual(100 / 23.5); + }); + + test('screen width greater or equal to 1400px', () => { + expect(postersPerRowForOverflowBackdrop(1400, false)).toEqual(100 / 30); + expect(postersPerRowForOverflowBackdrop(1401, false)).toEqual(100 / 30); + }); + + test('screen width greater or equal to 760px', () => { + expect(postersPerRowForOverflowBackdrop(760, false)).toEqual(100 / 40); + expect(postersPerRowForOverflowBackdrop(761, false)).toEqual(100 / 40); + }); + + test('screen width greater or equal to 640px', () => { + expect(postersPerRowForOverflowBackdrop(640, false)).toEqual(100 / 56); + expect(postersPerRowForOverflowBackdrop(641, false)).toEqual(100 / 56); + }); + + test('screen width is less than 640px', () => { + expect(postersPerRowForOverflowBackdrop(639, false)).toEqual(100 / 72); + expect(postersPerRowForOverflowBackdrop(100, false)).toEqual(100 / 72); + }); + }); + + describe('landscape', () => { + test('screen width greater or equal to 1700px', () => { + expect(postersPerRowForOverflowBackdrop(1700, true)).toEqual(100 / 18.5); + expect(postersPerRowForOverflowBackdrop(1701, true)).toEqual(100 / 18.5); + }); + + test('screen width is less than 1700px', () => { + expect(postersPerRowForOverflowBackdrop(1699, true)).toEqual(100 / 23.3); + expect(postersPerRowForOverflowBackdrop(100, true)).toEqual(100 / 23.3); + }); + }); + }); +}); From 87ff0e4fda8db912b9acf9f1eda31e6e0516cf54 Mon Sep 17 00:00:00 2001 From: Zoe Date: Thu, 5 Oct 2023 17:15:50 +0000 Subject: [PATCH 142/142] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegi?= =?UTF-8?q?an=20Bokm=C3=A5l)=20Translation:=20Jellyfin/Jellyfin=20Web=20Tr?= =?UTF-8?q?anslate-URL:=20https://translate.jellyfin.org/projects/jellyfin?= =?UTF-8?q?/jellyfin-web/nb=5FNO/?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/strings/nb.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/strings/nb.json b/src/strings/nb.json index eaf915e98..91f77b623 100644 --- a/src/strings/nb.json +++ b/src/strings/nb.json @@ -1770,5 +1770,10 @@ "MachineTranslated": "Maskinoversatt", "HeaderGuestCast": "Gjestestjerner", "HearingImpairedShort": "HI/SDH", - "LogoScreensaver": "Logoskjermsparer" + "LogoScreensaver": "Logoskjermsparer", + "LabelIsHearingImpaired": "For hørselshemmede (SDH)", + "LabelBackdropScreensaverInterval": "Skjermsparingsbakgrunn-intervall", + "LabelBackdropScreensaverIntervalHelp": "Tid i milisekunder mellom forskjellige bakgrunner ved bruk av skjermsparingsbakgrunner.", + "BackdropScreensaver": "Skjermsparingsbakgrunn", + "ForeignPartsOnly": "Kun tvungne/fremmede deler" }