From b49af89cfc3c3b4a46803b6f47da63513f890022 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Mon, 28 Nov 2022 16:39:13 -0500 Subject: [PATCH 01/51] Add SyncPlay button to app toolbar --- .../components/AppToolbar/SyncPlayButton.tsx | 61 ++++ .../components/AppToolbar/index.tsx | 2 + .../AppToolbar/menus/SyncPlayMenu.tsx | 308 ++++++++++++++++++ src/strings/en-us.json | 1 + 4 files changed, 372 insertions(+) create mode 100644 src/apps/experimental/components/AppToolbar/SyncPlayButton.tsx create mode 100644 src/apps/experimental/components/AppToolbar/menus/SyncPlayMenu.tsx diff --git a/src/apps/experimental/components/AppToolbar/SyncPlayButton.tsx b/src/apps/experimental/components/AppToolbar/SyncPlayButton.tsx new file mode 100644 index 000000000..94a083487 --- /dev/null +++ b/src/apps/experimental/components/AppToolbar/SyncPlayButton.tsx @@ -0,0 +1,61 @@ +import { SyncPlayUserAccessType } from '@jellyfin/sdk/lib/generated-client/models/sync-play-user-access-type'; +import Groups from '@mui/icons-material/Groups'; +import IconButton from '@mui/material/IconButton'; +import Tooltip from '@mui/material/Tooltip'; +import React, { useCallback, useState } from 'react'; + +import { pluginManager } from 'components/pluginManager'; +import { useApi } from 'hooks/useApi'; +import globalize from 'scripts/globalize'; +import { PluginType } from 'types/plugin'; + +import AppSyncPlayMenu, { ID } from './menus/SyncPlayMenu'; + +const SyncPlayButton = () => { + const { user } = useApi(); + + const [ syncPlayMenuAnchorEl, setSyncPlayMenuAnchorEl ] = useState(null); + const isSyncPlayMenuOpen = Boolean(syncPlayMenuAnchorEl); + + const onSyncPlayButtonClick = useCallback((event) => { + setSyncPlayMenuAnchorEl(event.currentTarget); + }, [ setSyncPlayMenuAnchorEl ]); + + const onSyncPlayMenuClose = useCallback(() => { + setSyncPlayMenuAnchorEl(null); + }, [ setSyncPlayMenuAnchorEl ]); + + if ( + // SyncPlay not enabled for user + (user?.Policy && user.Policy.SyncPlayAccess === SyncPlayUserAccessType.None) + // SyncPlay plugin is not loaded + || pluginManager.ofType(PluginType.SyncPlay).length === 0 + ) { + return null; + } + + return ( + <> + + + + + + + + + ); +}; + +export default SyncPlayButton; diff --git a/src/apps/experimental/components/AppToolbar/index.tsx b/src/apps/experimental/components/AppToolbar/index.tsx index 433fa4c83..8d8796151 100644 --- a/src/apps/experimental/components/AppToolbar/index.tsx +++ b/src/apps/experimental/components/AppToolbar/index.tsx @@ -16,6 +16,7 @@ import AppTabs from '../tabs/AppTabs'; import { isDrawerPath } from '../drawers/AppDrawer'; import UserMenuButton from './UserMenuButton'; import RemotePlayButton from './RemotePlayButton'; +import SyncPlayButton from './SyncPlayButton'; interface AppToolbarProps { isDrawerOpen: boolean @@ -90,6 +91,7 @@ const AppToolbar: FC = ({ {isUserLoggedIn && ( <> + diff --git a/src/apps/experimental/components/AppToolbar/menus/SyncPlayMenu.tsx b/src/apps/experimental/components/AppToolbar/menus/SyncPlayMenu.tsx new file mode 100644 index 000000000..847b0a80a --- /dev/null +++ b/src/apps/experimental/components/AppToolbar/menus/SyncPlayMenu.tsx @@ -0,0 +1,308 @@ +import type { GroupInfoDto } from '@jellyfin/sdk/lib/generated-client/models/group-info-dto'; +import { SyncPlayUserAccessType } from '@jellyfin/sdk/lib/generated-client/models/sync-play-user-access-type'; +import { getSyncPlayApi } from '@jellyfin/sdk/lib/utils/api/sync-play-api'; +import GroupAdd from '@mui/icons-material/GroupAdd'; +import PersonAdd from '@mui/icons-material/PersonAdd'; +import PersonOff from '@mui/icons-material/PersonOff'; +import PersonRemove from '@mui/icons-material/PersonRemove'; +import PlayCircle from '@mui/icons-material/PlayCircle'; +import StopCircle from '@mui/icons-material/StopCircle'; +import Tune from '@mui/icons-material/Tune'; +import Divider from '@mui/material/Divider'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import ListItemText from '@mui/material/ListItemText'; +import ListSubheader from '@mui/material/ListSubheader'; +import Menu, { MenuProps } from '@mui/material/Menu'; +import MenuItem from '@mui/material/MenuItem'; +import type { ApiClient } from 'jellyfin-apiclient'; +import React, { FC, useCallback, useEffect, useState } from 'react'; + +import { pluginManager } from 'components/pluginManager'; +import { useApi } from 'hooks/useApi'; +import globalize from 'scripts/globalize'; +import { PluginType } from 'types/plugin'; +import Events from 'utils/events'; + +export const ID = 'app-sync-play-menu'; + +interface SyncPlayMenuProps extends MenuProps { + onMenuClose: () => void +} + +interface SyncPlayInstance { + Manager: { + getGroupInfo: () => GroupInfoDto | null | undefined + getTimeSyncCore: () => object + isPlaybackActive: () => boolean + isPlaylistEmpty: () => boolean + haltGroupPlayback: (apiClient: ApiClient) => void + resumeGroupPlayback: (apiClient: ApiClient) => void + } +} + +const SyncPlayMenu: FC = ({ + anchorEl, + open, + onMenuClose +}) => { + const [ SyncPlay, setSyncPlay ] = useState(); + const { __legacyApiClient__, api, user } = useApi(); + const [ groups, setGroups ] = useState([]); + const [ currentGroup, setCurrentGroup ] = useState(); + const isSyncPlayEnabled = Boolean(currentGroup); + + useEffect(() => { + setSyncPlay(pluginManager.firstOfType(PluginType.SyncPlay)?.instance); + }, []); + + useEffect(() => { + const fetchGroups = async () => { + if (api) { + setGroups((await getSyncPlayApi(api).syncPlayGetGroups()).data); + } + }; + + fetchGroups() + .catch(err => { + console.error('[SyncPlayMenu] unable to fetch SyncPlay groups', err); + }); + }, [ api ]); + + const onGroupAddClick = useCallback(() => { + if (api && user) { + getSyncPlayApi(api) + .syncPlayCreateGroup({ + newGroupRequestDto: { + GroupName: globalize.translate('SyncPlayGroupDefaultTitle', user.Name) + } + }) + .catch(err => { + console.error('[SyncPlayMenu] failed to create a SyncPlay group', err); + }); + + onMenuClose(); + } + }, [ api, onMenuClose, user ]); + + const onGroupLeaveClick = useCallback(() => { + if (api) { + getSyncPlayApi(api) + .syncPlayLeaveGroup() + .catch(err => { + console.error('[SyncPlayMenu] failed to leave SyncPlay group', err); + }); + + onMenuClose(); + } + }, [ api, onMenuClose ]); + + const onGroupJoinClick = useCallback((GroupId: string) => { + if (api) { + getSyncPlayApi(api) + .syncPlayJoinGroup({ + joinGroupRequestDto: { + GroupId + } + }) + .catch(err => { + console.error('[SyncPlayMenu] failed to join SyncPlay group', err); + }); + + onMenuClose(); + } + }, [ api, onMenuClose ]); + + const onGroupSettingsClick = useCallback(async () => { + if (!SyncPlay) return; + + // TODO: Rewrite settings UI + const SyncPlaySettingsEditor = (await import('../../../../../plugins/syncPlay/ui/settings/SettingsEditor')).default; + new SyncPlaySettingsEditor( + __legacyApiClient__, + SyncPlay.Manager.getTimeSyncCore(), + { + groupInfo: currentGroup + }) + .embed() + .catch(err => { + if (err) { + console.error('[SyncPlayMenu] Error creating SyncPlay settings editor', err); + } + }); + + onMenuClose(); + }, [ __legacyApiClient__, currentGroup, onMenuClose, SyncPlay ]); + + const onStartGroupPlaybackClick = useCallback(() => { + if (__legacyApiClient__) { + SyncPlay?.Manager.resumeGroupPlayback(__legacyApiClient__); + onMenuClose(); + } + }, [ __legacyApiClient__, onMenuClose, SyncPlay ]); + + const onStopGroupPlaybackClick = useCallback(() => { + if (__legacyApiClient__) { + SyncPlay?.Manager.haltGroupPlayback(__legacyApiClient__); + onMenuClose(); + } + }, [ __legacyApiClient__, onMenuClose, SyncPlay ]); + + const updateSyncPlayGroup = useCallback((_e, enabled) => { + if (SyncPlay && enabled) { + setCurrentGroup(SyncPlay.Manager.getGroupInfo() ?? undefined); + } else { + setCurrentGroup(undefined); + } + }, [ SyncPlay ]); + + useEffect(() => { + if (!SyncPlay) return; + + Events.on(SyncPlay.Manager, 'enabled', updateSyncPlayGroup); + + return () => { + Events.off(SyncPlay.Manager, 'enabled', updateSyncPlayGroup); + }; + }, [ updateSyncPlayGroup, SyncPlay ]); + + const menuItems = []; + if (isSyncPlayEnabled) { + if (!SyncPlay?.Manager.isPlaylistEmpty() && !SyncPlay?.Manager.isPlaybackActive()) { + menuItems.push( + + + + + + + ); + } else if (SyncPlay?.Manager.isPlaybackActive()) { + menuItems.push( + + + + + + + ); + } + + menuItems.push( + + + + + + + ); + + menuItems.push( + + ); + + menuItems.push( + + + + + + + ); + } else if (groups.length === 0 && user?.Policy?.SyncPlayAccess !== SyncPlayUserAccessType.CreateAndJoinGroups) { + menuItems.push( + + + + + + + ); + } else { + if (groups.length > 0) { + groups.forEach(group => { + menuItems.push( + group.GroupId && onGroupJoinClick(group.GroupId)} + > + + + + + + ); + }); + + menuItems.push( + + ); + } + + if (user?.Policy?.SyncPlayAccess === SyncPlayUserAccessType.CreateAndJoinGroups) { + menuItems.push( + + + + + + + ); + } + } + + const MenuListProps = isSyncPlayEnabled ? { + 'aria-labelledby': 'sync-play-active-subheader', + subheader: ( + + {currentGroup?.GroupName} + + ) + } : undefined; + + return ( + + {menuItems} + + ); +}; + +export default SyncPlayMenu; diff --git a/src/strings/en-us.json b/src/strings/en-us.json index eb2fc0680..46fe9f4e7 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -905,6 +905,7 @@ "LabelSyncPlayLeaveGroupDescription": "Disable SyncPlay", "LabelSyncPlayNewGroup": "New group", "LabelSyncPlayNewGroupDescription": "Create a new group", + "LabelSyncPlayNoGroups": "No groups available", "LabelSyncPlayPlaybackDiff": "Playback time difference", "LabelSyncPlayResumePlayback": "Resume local playback", "LabelSyncPlayResumePlaybackDescription": "Join back group playback", From 17e8ccc93a5965053da7a4f0947d2493cbf21746 Mon Sep 17 00:00:00 2001 From: grafixeyehero Date: Wed, 7 Jun 2023 03:38:39 +0300 Subject: [PATCH 02/51] refactor: suggestionview and genresview --- package-lock.json | 184 +++++++++++ package.json | 2 + src/RootApp.tsx | 29 +- .../library/GenresItemsContainer.tsx | 52 ++++ .../library/GenresSectionContainer.tsx | 79 +++++ .../library/RecommendationContainer.tsx | 61 ++++ .../components/library/SectionContainer.tsx | 73 +++++ .../library/SuggestionsItemsContainer.tsx | 206 +++++++++++++ .../library/SuggestionsSectionContainer.tsx | 49 +++ .../routes/movies/CollectionsView.tsx | 8 +- .../routes/movies/FavoritesView.tsx | 8 +- .../experimental/routes/movies/GenresView.tsx | 44 +-- .../experimental/routes/movies/MoviesView.tsx | 8 +- .../routes/movies/SuggestionsView.tsx | 191 +++--------- .../routes/movies/TrailersView.tsx | 8 +- src/apps/experimental/routes/movies/index.tsx | 79 ++--- .../common/GenresItemsContainer.tsx | 128 -------- .../common/RecommendationContainer.tsx | 48 --- src/components/common/SectionContainer.tsx | 62 ---- src/components/common/ViewItemsContainer.tsx | 3 +- src/hooks/useApi.tsx | 2 +- src/hooks/useFetchItems.ts | 287 ++++++++++++++++++ src/types/cardOptions.ts | 74 +++++ src/types/interface.ts | 93 ------ src/types/library.ts | 45 +++ src/types/suggestionsSections.ts | 28 ++ webpack.common.js | 4 +- 27 files changed, 1253 insertions(+), 602 deletions(-) create mode 100644 src/apps/experimental/components/library/GenresItemsContainer.tsx create mode 100644 src/apps/experimental/components/library/GenresSectionContainer.tsx create mode 100644 src/apps/experimental/components/library/RecommendationContainer.tsx create mode 100644 src/apps/experimental/components/library/SectionContainer.tsx create mode 100644 src/apps/experimental/components/library/SuggestionsItemsContainer.tsx create mode 100644 src/apps/experimental/components/library/SuggestionsSectionContainer.tsx delete mode 100644 src/components/common/GenresItemsContainer.tsx delete mode 100644 src/components/common/RecommendationContainer.tsx delete mode 100644 src/components/common/SectionContainer.tsx create mode 100644 src/hooks/useFetchItems.ts create mode 100644 src/types/cardOptions.ts create mode 100644 src/types/library.ts create mode 100644 src/types/suggestionsSections.ts diff --git a/package-lock.json b/package-lock.json index 64c7e6147..8bcb3c653 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,8 @@ "@loadable/component": "5.15.3", "@mui/icons-material": "5.11.16", "@mui/material": "5.13.3", + "@tanstack/react-query": "4.29.12", + "@tanstack/react-query-devtools": "4.29.12", "blurhash": "2.0.5", "classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz", "classnames": "2.3.2", @@ -3472,6 +3474,75 @@ "string.prototype.matchall": "^4.0.6" } }, + "node_modules/@tanstack/match-sorter-utils": { + "version": "8.8.4", + "resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.8.4.tgz", + "integrity": "sha512-rKH8LjZiszWEvmi01NR72QWZ8m4xmXre0OOwlRGnjU01Eqz/QnN+cqpty2PJ0efHblq09+KilvyR7lsbzmXVEw==", + "dependencies": { + "remove-accents": "0.4.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kentcdodds" + } + }, + "node_modules/@tanstack/query-core": { + "version": "4.29.11", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.29.11.tgz", + "integrity": "sha512-8C+hF6SFAb/TlFZyS9FItgNwrw4PMa7YeX+KQYe2ZAiEz6uzg6yIr+QBzPkUwZ/L0bXvGd1sufTm3wotoz+GwQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "4.29.12", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.29.12.tgz", + "integrity": "sha512-zhcN6+zF6cxprxhTHQajHGlvxgK8npnp9uLe9yaWhGc6sYcPWXzyO4raL4HomUzQOPzu3jLvkriJQ7BOrDM8vA==", + "dependencies": { + "@tanstack/query-core": "4.29.11", + "use-sync-external-store": "^1.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-native": "*" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/@tanstack/react-query-devtools": { + "version": "4.29.12", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-4.29.12.tgz", + "integrity": "sha512-ug4YGQhMhh6QI8/sWJhjXxuvdeehxf1cyxpTifGMH5qreQ5ECHT6vzqG/aKvADQDzqLBGrF0q4wTDnRRYvvtrA==", + "dependencies": { + "@tanstack/match-sorter-utils": "^8.7.0", + "superjson": "^1.10.0", + "use-sync-external-store": "^1.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@tanstack/react-query": "4.29.12", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/@trysound/sax": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", @@ -5880,6 +5951,20 @@ "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", "dev": true }, + "node_modules/copy-anything": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz", + "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", + "dependencies": { + "is-what": "^4.1.8" + }, + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/copy-descriptor": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", @@ -10460,6 +10545,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-what": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.13.tgz", + "integrity": "sha512-Aoe8pT24sWzyoO0S2PTDyutGp9l7qYHyFtzYlC8hMLshyqV/minljBANT4f2hiS5OxnWvcKMiA5io+VaLMJ1oA==", + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/is-whitespace-character": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz", @@ -14466,6 +14562,11 @@ "node": ">= 0.10" } }, + "node_modules/remove-accents": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz", + "integrity": "sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==" + }, "node_modules/renderkid": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", @@ -18396,6 +18497,17 @@ "node": ">=6" } }, + "node_modules/superjson": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-1.12.3.tgz", + "integrity": "sha512-0j+U70KUtP8+roVPbwfqkyQI7lBt7ETnuA7KXbTDX3mCKiD/4fXs2ldKSMdt0MCfpTwiMxo20yFU3vu6ewETpQ==", + "dependencies": { + "copy-anything": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -19430,6 +19542,14 @@ "node": ">=0.10.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -22627,6 +22747,38 @@ "string.prototype.matchall": "^4.0.6" } }, + "@tanstack/match-sorter-utils": { + "version": "8.8.4", + "resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.8.4.tgz", + "integrity": "sha512-rKH8LjZiszWEvmi01NR72QWZ8m4xmXre0OOwlRGnjU01Eqz/QnN+cqpty2PJ0efHblq09+KilvyR7lsbzmXVEw==", + "requires": { + "remove-accents": "0.4.2" + } + }, + "@tanstack/query-core": { + "version": "4.29.11", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.29.11.tgz", + "integrity": "sha512-8C+hF6SFAb/TlFZyS9FItgNwrw4PMa7YeX+KQYe2ZAiEz6uzg6yIr+QBzPkUwZ/L0bXvGd1sufTm3wotoz+GwQ==" + }, + "@tanstack/react-query": { + "version": "4.29.12", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.29.12.tgz", + "integrity": "sha512-zhcN6+zF6cxprxhTHQajHGlvxgK8npnp9uLe9yaWhGc6sYcPWXzyO4raL4HomUzQOPzu3jLvkriJQ7BOrDM8vA==", + "requires": { + "@tanstack/query-core": "4.29.11", + "use-sync-external-store": "^1.2.0" + } + }, + "@tanstack/react-query-devtools": { + "version": "4.29.12", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-4.29.12.tgz", + "integrity": "sha512-ug4YGQhMhh6QI8/sWJhjXxuvdeehxf1cyxpTifGMH5qreQ5ECHT6vzqG/aKvADQDzqLBGrF0q4wTDnRRYvvtrA==", + "requires": { + "@tanstack/match-sorter-utils": "^8.7.0", + "superjson": "^1.10.0", + "use-sync-external-store": "^1.2.0" + } + }, "@trysound/sax": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", @@ -24508,6 +24660,14 @@ "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", "dev": true }, + "copy-anything": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz", + "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", + "requires": { + "is-what": "^4.1.8" + } + }, "copy-descriptor": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", @@ -27907,6 +28067,11 @@ "get-intrinsic": "^1.1.1" } }, + "is-what": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.13.tgz", + "integrity": "sha512-Aoe8pT24sWzyoO0S2PTDyutGp9l7qYHyFtzYlC8hMLshyqV/minljBANT4f2hiS5OxnWvcKMiA5io+VaLMJ1oA==" + }, "is-whitespace-character": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz", @@ -30779,6 +30944,11 @@ "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", "dev": true }, + "remove-accents": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz", + "integrity": "sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==" + }, "renderkid": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", @@ -33876,6 +34046,14 @@ } } }, + "superjson": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-1.12.3.tgz", + "integrity": "sha512-0j+U70KUtP8+roVPbwfqkyQI7lBt7ETnuA7KXbTDX3mCKiD/4fXs2ldKSMdt0MCfpTwiMxo20yFU3vu6ewETpQ==", + "requires": { + "copy-anything": "^3.0.2" + } + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -34626,6 +34804,12 @@ "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", "dev": true }, + "use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "requires": {} + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index 0a3d6f7e6..fe5c1b6d9 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,8 @@ "@loadable/component": "5.15.3", "@mui/icons-material": "5.11.16", "@mui/material": "5.13.3", + "@tanstack/react-query": "4.29.12", + "@tanstack/react-query-devtools": "4.29.12", "blurhash": "2.0.5", "classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz", "classnames": "2.3.2", diff --git a/src/RootApp.tsx b/src/RootApp.tsx index a44e25d56..62223f723 100644 --- a/src/RootApp.tsx +++ b/src/RootApp.tsx @@ -1,6 +1,8 @@ import loadable from '@loadable/component'; 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'; @@ -9,21 +11,26 @@ import { WebConfigProvider } from './hooks/useWebConfig'; const ExperimentalApp = loadable(() => import('./apps/experimental/App')); +const queryClient = new QueryClient(); + const RootApp = ({ history }: { history: History }) => { const layoutMode = localStorage.getItem('layout'); return ( - - - - { - layoutMode === 'experimental' ? - : - - } - - - + + + + + { + layoutMode === 'experimental' ? + : + + } + + + + + ); }; diff --git a/src/apps/experimental/components/library/GenresItemsContainer.tsx b/src/apps/experimental/components/library/GenresItemsContainer.tsx new file mode 100644 index 000000000..52968f5f1 --- /dev/null +++ b/src/apps/experimental/components/library/GenresItemsContainer.tsx @@ -0,0 +1,52 @@ +import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; +import React, { FC } from 'react'; +import { useGetGenres } from 'hooks/useFetchItems'; +import globalize from 'scripts/globalize'; +import Loading from 'components/loading/LoadingComponent'; +import GenresSectionContainer from './GenresSectionContainer'; +import { CollectionType } from 'types/collectionType'; + +interface GenresItemsContainerProps { + parentId?: string | null; + collectionType?: CollectionType; + itemType: BaseItemKind; +} + +const GenresItemsContainer: FC = ({ + parentId, + collectionType, + itemType +}) => { + const { isLoading, data: genresResult } = useGetGenres( + parentId, + itemType + ); + + if (isLoading) { + return ; + } + + return ( + <> + {!genresResult?.Items?.length ? ( +
+

{globalize.translate('MessageNothingHere')}

+

{globalize.translate('MessageNoGenresAvailable')}

+
+ ) : ( + genresResult?.Items + && genresResult?.Items.map((genre) => ( + + )) + )} + + ); +}; + +export default GenresItemsContainer; diff --git a/src/apps/experimental/components/library/GenresSectionContainer.tsx b/src/apps/experimental/components/library/GenresSectionContainer.tsx new file mode 100644 index 000000000..74f57782b --- /dev/null +++ b/src/apps/experimental/components/library/GenresSectionContainer.tsx @@ -0,0 +1,79 @@ +import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client'; +import { ItemFields } from '@jellyfin/sdk/lib/generated-client/models/item-fields'; +import { ImageType } from '@jellyfin/sdk/lib/generated-client/models/image-type'; +import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; +import { ItemSortBy } from '@jellyfin/sdk/lib/models/api/item-sort-by'; +import { SortOrder } from '@jellyfin/sdk/lib/generated-client/models/sort-order'; +import escapeHTML from 'escape-html'; +import React, { FC } from 'react'; + +import { useGetItems } from 'hooks/useFetchItems'; +import Loading from 'components/loading/LoadingComponent'; +import { appRouter } from 'components/router/appRouter'; +import SectionContainer from './SectionContainer'; +import { CollectionType } from 'types/collectionType'; + +interface GenresSectionContainerProps { + parentId?: string | null; + collectionType?: CollectionType; + itemType: BaseItemKind; + genre: BaseItemDto; +} + +const GenresSectionContainer: FC = ({ + parentId, + collectionType, + itemType, + genre +}) => { + const getParametersOptions = () => { + return { + sortBy: [ItemSortBy.Random], + sortOrder: [SortOrder.Ascending], + includeItemTypes: [itemType], + recursive: true, + fields: [ + ItemFields.PrimaryImageAspectRatio, + ItemFields.MediaSourceCount, + ItemFields.BasicSyncInfo + ], + imageTypeLimit: 1, + enableImageTypes: [ImageType.Primary], + limit: 25, + genreIds: genre.Id ? [genre.Id] : undefined, + enableTotalRecordCount: false, + parentId: parentId ?? undefined + }; + }; + + const { isLoading, data: itemsResult } = useGetItems(getParametersOptions()); + + const getRouteUrl = (item: BaseItemDto) => { + return appRouter.getRouteUrl(item, { + context: collectionType, + parentId: parentId + }); + }; + + if (isLoading) { + return ; + } + + return ; +}; + +export default GenresSectionContainer; diff --git a/src/apps/experimental/components/library/RecommendationContainer.tsx b/src/apps/experimental/components/library/RecommendationContainer.tsx new file mode 100644 index 000000000..f91ed8c22 --- /dev/null +++ b/src/apps/experimental/components/library/RecommendationContainer.tsx @@ -0,0 +1,61 @@ +import { RecommendationDto, RecommendationType } from '@jellyfin/sdk/lib/generated-client'; +import React, { FC } from 'react'; + +import globalize from 'scripts/globalize'; +import escapeHTML from 'escape-html'; +import SectionContainer from './SectionContainer'; + +interface RecommendationContainerProps { + recommendation?: RecommendationDto; +} + +const RecommendationContainer: FC = ({ + recommendation = {} +}) => { + let title = ''; + + switch (recommendation.RecommendationType) { + case RecommendationType.SimilarToRecentlyPlayed: + title = globalize.translate( + 'RecommendationBecauseYouWatched', + recommendation.BaselineItemName + ); + break; + + case RecommendationType.SimilarToLikedItem: + title = globalize.translate( + 'RecommendationBecauseYouLike', + recommendation.BaselineItemName + ); + break; + + case RecommendationType.HasDirectorFromRecentlyPlayed: + case RecommendationType.HasLikedDirector: + title = globalize.translate( + 'RecommendationDirectedBy', + recommendation.BaselineItemName + ); + break; + + case RecommendationType.HasActorFromRecentlyPlayed: + case RecommendationType.HasLikedActor: + title = globalize.translate( + 'RecommendationStarring', + recommendation.BaselineItemName + ); + break; + } + + return ( + + ); +}; + +export default RecommendationContainer; diff --git a/src/apps/experimental/components/library/SectionContainer.tsx b/src/apps/experimental/components/library/SectionContainer.tsx new file mode 100644 index 000000000..325c950a0 --- /dev/null +++ b/src/apps/experimental/components/library/SectionContainer.tsx @@ -0,0 +1,73 @@ +import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client'; +import React, { FC, useEffect, useRef } from 'react'; + +import cardBuilder from 'components/cardbuilder/cardBuilder'; +import ItemsContainerElement from 'elements/ItemsContainerElement'; +import Scroller from 'elements/emby-scroller/Scroller'; +import LinkButton from 'elements/emby-button/LinkButton'; +import imageLoader from 'components/images/imageLoader'; + +import { CardOptions } from 'types/cardOptions'; + +interface SectionContainerProps { + url?: string; + sectionTitle: string; + items: BaseItemDto[]; + cardOptions: CardOptions; +} + +const SectionContainer: FC = ({ + sectionTitle, + url, + items, + cardOptions +}) => { + const element = useRef(null); + + useEffect(() => { + const itemsContainer = element.current?.querySelector('.itemsContainer'); + cardBuilder.buildCards(items, { + itemsContainer: itemsContainer, + parentContainer: element.current, + + ...cardOptions + }); + + imageLoader.lazyChildren(itemsContainer); + }, [cardOptions, items]); + + return ( +
+
+ {url && items.length > 5 ? ( + +

+ {sectionTitle} +

+ +
+ ) : ( +

+ {sectionTitle} +

+ )} +
+ + + + +
+ ); +}; + +export default SectionContainer; diff --git a/src/apps/experimental/components/library/SuggestionsItemsContainer.tsx b/src/apps/experimental/components/library/SuggestionsItemsContainer.tsx new file mode 100644 index 000000000..d985592a8 --- /dev/null +++ b/src/apps/experimental/components/library/SuggestionsItemsContainer.tsx @@ -0,0 +1,206 @@ +import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; +import { ItemSortBy } from '@jellyfin/sdk/lib/models/api/item-sort-by'; +import { SortOrder } from '@jellyfin/sdk/lib/generated-client/models/sort-order'; +import React, { FC } from 'react'; +import * as userSettings from 'scripts/settings/userSettings'; +import SuggestionsSectionContainer from './SuggestionsSectionContainer'; +import { Sections, SectionsView, SectionsViewType } from 'types/suggestionsSections'; + +const getSuggestionsSections = (): Sections[] => { + return [ + { + name: 'HeaderContinueWatching', + viewType: SectionsViewType.ResumeItems, + type: 'Movie', + view: SectionsView.ContinueWatchingMovies, + parametersOptions: { + includeItemTypes: [BaseItemKind.Movie] + }, + cardOptions: { + scalable: true, + overlayPlayButton: true, + showTitle: true, + centerText: true, + cardLayout: false, + preferThumb: true, + shape: 'overflowBackdrop', + showYear: true + } + }, + { + name: 'HeaderLatestMovies', + viewType: SectionsViewType.LatestMedia, + type: 'Movie', + view: SectionsView.LatestMovies, + parametersOptions: { + includeItemTypes: [BaseItemKind.Movie] + }, + cardOptions: { + scalable: true, + overlayPlayButton: true, + showTitle: true, + centerText: true, + cardLayout: false, + shape: 'overflowPortrait', + showYear: true + } + }, + { + name: 'HeaderContinueWatching', + viewType: SectionsViewType.ResumeItems, + type: 'Episode', + view: SectionsView.ContinueWatchingEpisode, + parametersOptions: { + includeItemTypes: [BaseItemKind.Episode] + }, + cardOptions: { + scalable: true, + overlayPlayButton: true, + showTitle: true, + centerText: true, + cardLayout: false, + shape: 'overflowBackdrop', + preferThumb: true, + inheritThumb: + !userSettings.useEpisodeImagesInNextUpAndResume(undefined), + showYear: true + } + }, + { + name: 'HeaderLatestEpisodes', + viewType: SectionsViewType.LatestMedia, + type: 'Episode', + view: SectionsView.LatestEpisode, + parametersOptions: { + includeItemTypes: [BaseItemKind.Episode] + }, + cardOptions: { + scalable: true, + overlayPlayButton: true, + showTitle: true, + centerText: true, + cardLayout: false, + shape: 'overflowBackdrop', + preferThumb: true, + showSeriesYear: true, + showParentTitle: true, + overlayText: false, + showUnplayedIndicator: false, + showChildCountIndicator: true, + lazy: true, + lines: 2 + } + }, + { + name: 'NextUp', + viewType: SectionsViewType.NextUp, + type: 'nextup', + view: SectionsView.NextUp, + cardOptions: { + scalable: true, + overlayPlayButton: true, + showTitle: true, + centerText: true, + cardLayout: false, + shape: 'overflowBackdrop', + preferThumb: true, + inheritThumb: + !userSettings.useEpisodeImagesInNextUpAndResume(undefined), + showParentTitle: true, + overlayText: false + } + }, + { + name: 'HeaderLatestMusic', + viewType: SectionsViewType.LatestMedia, + type: 'Audio', + view: SectionsView.LatestMusic, + parametersOptions: { + includeItemTypes: [BaseItemKind.Audio] + }, + cardOptions: { + showUnplayedIndicator: false, + shape: 'overflowSquare', + showTitle: true, + showParentTitle: true, + lazy: true, + centerText: true, + overlayPlayButton: true, + cardLayout: false, + coverImage: true + } + }, + { + name: 'HeaderRecentlyPlayed', + type: 'Audio', + view: SectionsView.RecentlyPlayedMusic, + parametersOptions: { + sortBy: [ItemSortBy.DatePlayed], + sortOrder: [SortOrder.Descending], + includeItemTypes: [BaseItemKind.Audio] + }, + cardOptions: { + showUnplayedIndicator: false, + shape: 'overflowSquare', + showTitle: true, + showParentTitle: true, + action: 'instantmix', + lazy: true, + centerText: true, + overlayMoreButton: true, + cardLayout: false, + coverImage: true + } + }, + { + name: 'HeaderFrequentlyPlayed', + type: 'Audio', + view: SectionsView.FrequentlyPlayedMusic, + parametersOptions: { + sortBy: [ItemSortBy.PlayCount], + sortOrder: [SortOrder.Descending], + includeItemTypes: [BaseItemKind.Audio] + }, + cardOptions: { + showUnplayedIndicator: false, + shape: 'overflowSquare', + showTitle: true, + showParentTitle: true, + action: 'instantmix', + lazy: true, + centerText: true, + overlayMoreButton: true, + cardLayout: false, + coverImage: true + } + } + ]; +}; + +interface SuggestionsItemsContainerProps { + parentId?: string | null; + sectionsView: SectionsView[]; +} + +const SuggestionsItemsContainer: FC = ({ + parentId, + sectionsView +}) => { + const suggestionsSections = getSuggestionsSections(); + + return ( + <> + {suggestionsSections + .filter((section) => sectionsView.includes(section.view)) + .map((section) => ( + + ))} + + ); +}; + +export default SuggestionsItemsContainer; diff --git a/src/apps/experimental/components/library/SuggestionsSectionContainer.tsx b/src/apps/experimental/components/library/SuggestionsSectionContainer.tsx new file mode 100644 index 000000000..4c52d712e --- /dev/null +++ b/src/apps/experimental/components/library/SuggestionsSectionContainer.tsx @@ -0,0 +1,49 @@ +import React, { FC } from 'react'; +import { useGetItemsBySectionType } from 'hooks/useFetchItems'; +import globalize from 'scripts/globalize'; + +import Loading from 'components/loading/LoadingComponent'; +import { appRouter } from 'components/router/appRouter'; +import SectionContainer from './SectionContainer'; + +import { Sections } from 'types/suggestionsSections'; + +interface SuggestionsSectionContainerProps { + parentId?: string | null; + section: Sections; +} + +const SuggestionsSectionContainer: FC = ({ + parentId, + section +}) => { + const getRouteUrl = () => { + return appRouter.getRouteUrl('list', { + serverId: window.ApiClient.serverId(), + itemTypes: section.type, + parentId: parentId + }); + }; + + const { isLoading, data: items } = useGetItemsBySectionType( + section, + parentId + ); + + if (isLoading) { + return ; + } + + return ( + + ); +}; + +export default SuggestionsSectionContainer; diff --git a/src/apps/experimental/routes/movies/CollectionsView.tsx b/src/apps/experimental/routes/movies/CollectionsView.tsx index b58cc957e..ef574b916 100644 --- a/src/apps/experimental/routes/movies/CollectionsView.tsx +++ b/src/apps/experimental/routes/movies/CollectionsView.tsx @@ -1,9 +1,9 @@ import React, { FC, useCallback } from 'react'; -import ViewItemsContainer from '../../../../components/common/ViewItemsContainer'; -import { LibraryViewProps } from '../../../../types/interface'; +import ViewItemsContainer from 'components/common/ViewItemsContainer'; +import { LibraryViewProps } from 'types/library'; -const CollectionsView: FC = ({ topParentId }) => { +const CollectionsView: FC = ({ parentId }) => { const getBasekey = useCallback(() => { return 'collections'; }, []); @@ -18,7 +18,7 @@ const CollectionsView: FC = ({ topParentId }) => { return ( = ({ topParentId }) => { +const FavoritesView: FC = ({ parentId }) => { const getBasekey = useCallback(() => { return 'favorites'; }, []); @@ -18,7 +18,7 @@ const FavoritesView: FC = ({ topParentId }) => { return ( = ({ topParentId }) => { - const [ itemsResult, setItemsResult ] = useState({}); - - const reloadItems = useCallback(() => { - loading.show(); - window.ApiClient.getGenres( - window.ApiClient.getCurrentUserId(), - { - SortBy: 'SortName', - SortOrder: 'Ascending', - IncludeItemTypes: 'Movie', - Recursive: true, - EnableTotalRecordCount: false, - ParentId: topParentId - } - ).then((result) => { - setItemsResult(result); - loading.hide(); - }).catch(err => { - console.error('[GenresView] failed to fetch genres', err); - }); - }, [topParentId]); - - useEffect(() => { - reloadItems(); - }, [reloadItems]); +import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; +import React, { FC } from 'react'; +import GenresItemsContainer from '../../components/library/GenresItemsContainer'; +import { LibraryViewProps } from 'types/library'; +import { CollectionType } from 'types/collectionType'; +const GenresView: FC = ({ parentId }) => { return ( ); }; diff --git a/src/apps/experimental/routes/movies/MoviesView.tsx b/src/apps/experimental/routes/movies/MoviesView.tsx index 510ed9e2b..8796c9a71 100644 --- a/src/apps/experimental/routes/movies/MoviesView.tsx +++ b/src/apps/experimental/routes/movies/MoviesView.tsx @@ -1,9 +1,9 @@ import React, { FC, useCallback } from 'react'; -import ViewItemsContainer from '../../../../components/common/ViewItemsContainer'; -import { LibraryViewProps } from '../../../../types/interface'; +import ViewItemsContainer from 'components/common/ViewItemsContainer'; +import { LibraryViewProps } from 'types/library'; -const MoviesView: FC = ({ topParentId }) => { +const MoviesView: FC = ({ parentId }) => { const getBasekey = useCallback(() => { return 'movies'; }, []); @@ -18,7 +18,7 @@ const MoviesView: FC = ({ topParentId }) => { return ( = ({ topParentId }) => { - const [ latestItems, setLatestItems ] = useState([]); - const [ resumeResult, setResumeResult ] = useState({}); - const [ recommendations, setRecommendations ] = useState([]); - const element = useRef(null); +const SuggestionsView: FC = ({ parentId }) => { + const { + isLoading, + data: movieRecommendationsItems + } = useGetMovieRecommendations(parentId); - const enableScrollX = useCallback(() => { - return !layoutManager.desktop; - }, []); - - const getPortraitShape = useCallback(() => { - return enableScrollX() ? 'overflowPortrait' : 'portrait'; - }, [enableScrollX]); - - const getThumbShape = useCallback(() => { - return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; - }, [enableScrollX]); - - const autoFocus = useCallback((page) => { - import('../../../../components/autoFocuser').then(({ default: autoFocuser }) => { - autoFocuser.autoFocus(page); - }).catch(err => { - console.error('[SuggestionsView] failed to load data', err); - }); - }, []); - - const loadResume = useCallback((page, userId, parentId) => { - loading.show(); - const screenWidth = dom.getWindowSize().innerWidth; - const options = { - SortBy: 'DatePlayed', - SortOrder: 'Descending', - IncludeItemTypes: 'Movie', - Filters: 'IsResumable', - Limit: screenWidth >= 1600 ? 5 : 3, - Recursive: true, - Fields: 'PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo', - CollapseBoxSetItems: false, - ParentId: parentId, - ImageTypeLimit: 1, - EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', - EnableTotalRecordCount: false - }; - window.ApiClient.getItems(userId, options).then(result => { - setResumeResult(result); - - loading.hide(); - autoFocus(page); - }).catch(err => { - console.error('[SuggestionsView] failed to fetch items', err); - }); - }, [autoFocus]); - - const loadLatest = useCallback((page: HTMLDivElement, userId: string, parentId: string | null) => { - const options = { - IncludeItemTypes: 'Movie', - Limit: 18, - Fields: 'PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo', - ParentId: parentId, - ImageTypeLimit: 1, - EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', - EnableTotalRecordCount: false - }; - window.ApiClient.getJSON(window.ApiClient.getUrl('Users/' + userId + '/Items/Latest', options)).then(items => { - setLatestItems(items); - - autoFocus(page); - }).catch(err => { - console.error('[SuggestionsView] failed to fetch latest items', err); - }); - }, [autoFocus]); - - const loadSuggestions = useCallback((page, userId) => { - const screenWidth = dom.getWindowSize().innerWidth; - let itemLimit = 5; - if (screenWidth >= 1600) { - itemLimit = 8; - } else if (screenWidth >= 1200) { - itemLimit = 6; - } - const url = window.ApiClient.getUrl('Movies/Recommendations', { - userId: userId, - categoryLimit: 6, - ItemLimit: itemLimit, - Fields: 'PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo', - ImageTypeLimit: 1, - EnableImageTypes: 'Primary,Backdrop,Banner,Thumb' - }); - window.ApiClient.getJSON(url).then(result => { - setRecommendations(result); - - autoFocus(page); - }).catch(err => { - console.error('[SuggestionsView] failed to fetch recommendations', err); - }); - }, [autoFocus]); - - const loadSuggestionsTab = useCallback((view) => { - const parentId = topParentId; - const userId = window.ApiClient.getCurrentUserId(); - loadResume(view, userId, parentId); - loadLatest(view, userId, parentId); - loadSuggestions(view, userId); - }, [loadLatest, loadResume, loadSuggestions, topParentId]); - - useEffect(() => { - const page = element.current; - - if (!page) { - console.error('Unexpected null reference'); - return; - } - - loadSuggestionsTab(page); - }, [loadSuggestionsTab]); + if (isLoading) { + return ; + } return ( -
- + - - - {!recommendations.length ?
-

{globalize.translate('MessageNothingHere')}

-

{globalize.translate('MessageNoMovieSuggestionsAvailable')}

-
: recommendations.map(recommendation => { - return ; - })} -
+ {!movieRecommendationsItems?.length ? ( +
+

{globalize.translate('MessageNothingHere')}

+

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

+
+ ) : ( + movieRecommendationsItems.map((recommendation, index) => { + return ( + + ); + }) + )} + ); }; diff --git a/src/apps/experimental/routes/movies/TrailersView.tsx b/src/apps/experimental/routes/movies/TrailersView.tsx index 55f6189cf..ff0ff0e73 100644 --- a/src/apps/experimental/routes/movies/TrailersView.tsx +++ b/src/apps/experimental/routes/movies/TrailersView.tsx @@ -1,10 +1,10 @@ import React, { FC, useCallback } from 'react'; -import ViewItemsContainer from '../../../../components/common/ViewItemsContainer'; -import { LibraryViewProps } from '../../../../types/interface'; +import ViewItemsContainer from 'components/common/ViewItemsContainer'; +import { LibraryViewProps } from 'types/library'; -const TrailersView: FC = ({ topParentId }) => { +const TrailersView: FC = ({ parentId }) => { const getBasekey = useCallback(() => { return 'trailers'; }, []); @@ -19,7 +19,7 @@ const TrailersView: FC = ({ topParentId }) => { return ( { const location = useLocation(); const [ searchParams ] = useSearchParams(); + const searchParamsParentId = searchParams.get('topParentId'); const searchParamsTab = searchParams.get('tab'); const currentTabIndex = searchParamsTab !== null ? parseInt(searchParamsTab, 10) : - getDefaultTabIndex(location.pathname, searchParams.get('topParentId')); - const element = useRef(null); + getDefaultTabIndex(location.pathname, searchParamsParentId); const getTabComponent = (index: number) => { if (index == null) { @@ -32,72 +30,41 @@ const Movies: FC = () => { let component; switch (index) { - case 0: - component = ; - break; - case 1: - component = ; + component = ; break; case 2: - component = ; + component = ; break; case 3: - component = ; + component = ; break; case 4: - component = ; + component = ; break; case 5: - component = ; + component = ; break; + default: + component = ; } return component; }; - useEffect(() => { - const page = element.current; - - if (!page) { - console.error('Unexpected null reference'); - return; - } - - if (!page.getAttribute('data-title')) { - const parentId = searchParams.get('topParentId'); - - if (parentId) { - window.ApiClient.getItem(window.ApiClient.getCurrentUserId(), parentId).then((item) => { - page.setAttribute('data-title', item.Name as string); - libraryMenu.setTitle(item.Name); - }).catch(err => { - console.error('[movies] failed to fetch library', err); - page.setAttribute('data-title', globalize.translate('Movies')); - libraryMenu.setTitle(globalize.translate('Movies')); - }); - } else { - page.setAttribute('data-title', globalize.translate('Movies')); - libraryMenu.setTitle(globalize.translate('Movies')); - } - } - }, [ searchParams ]); - return ( -
- - {getTabComponent(currentTabIndex)} + + {getTabComponent(currentTabIndex)} - -
+ ); }; diff --git a/src/components/common/GenresItemsContainer.tsx b/src/components/common/GenresItemsContainer.tsx deleted file mode 100644 index 09623e7e5..000000000 --- a/src/components/common/GenresItemsContainer.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import '../../elements/emby-button/emby-button'; -import '../../elements/emby-itemscontainer/emby-itemscontainer'; - -import type { BaseItemDtoQueryResult } from '@jellyfin/sdk/lib/generated-client'; -import escapeHTML from 'escape-html'; -import React, { FC, useCallback, useEffect, useRef } from 'react'; - -import { appRouter } from '../router/appRouter'; -import cardBuilder from '../cardbuilder/cardBuilder'; -import layoutManager from '../layoutManager'; -import lazyLoader from '../lazyLoader/lazyLoaderIntersectionObserver'; -import globalize from '../../scripts/globalize'; -import ItemsScrollerContainerElement from '../../elements/ItemsScrollerContainerElement'; -import ItemsContainerElement from '../../elements/ItemsContainerElement'; - -const createLinkElement = ({ className, title, href }: { className?: string, title?: string | null, href?: string }) => ({ - __html: ` -

- ${title} -

- -
` -}); - -interface GenresItemsContainerProps { - topParentId?: string | null; - itemsResult: BaseItemDtoQueryResult; -} - -const GenresItemsContainer: FC = ({ - topParentId, - itemsResult = {} -}) => { - const element = useRef(null); - - const enableScrollX = useCallback(() => { - return !layoutManager.desktop; - }, []); - - const getPortraitShape = useCallback(() => { - return enableScrollX() ? 'overflowPortrait' : 'portrait'; - }, [enableScrollX]); - - const fillItemsContainer = useCallback((entry) => { - const elem = entry.target; - const id = elem.getAttribute('data-id'); - - const query = { - SortBy: 'Random', - SortOrder: 'Ascending', - IncludeItemTypes: 'Movie', - Recursive: true, - Fields: 'PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo', - ImageTypeLimit: 1, - EnableImageTypes: 'Primary', - Limit: 12, - GenreIds: id, - EnableTotalRecordCount: false, - ParentId: topParentId - }; - window.ApiClient.getItems(window.ApiClient.getCurrentUserId(), query).then((result) => { - cardBuilder.buildCards(result.Items || [], { - itemsContainer: elem, - shape: getPortraitShape(), - scalable: true, - overlayMoreButton: true, - allowBottomPadding: true, - showTitle: true, - centerText: true, - showYear: true - }); - }).catch(err => { - console.error('[GenresItemsContainer] failed to fetch items', err); - }); - }, [getPortraitShape, topParentId]); - - useEffect(() => { - const elem = element.current; - lazyLoader.lazyChildren(elem, fillItemsContainer); - }, [itemsResult.Items, fillItemsContainer]); - - const items = itemsResult.Items || []; - return ( -
- { - !items.length ? ( -
-

{globalize.translate('MessageNothingHere')}

-

{globalize.translate('MessageNoGenresAvailable')}

-
- ) : items.map(item => ( -
-
- - {enableScrollX() ? - : - } -
- )) - } -
- ); -}; - -export default GenresItemsContainer; diff --git a/src/components/common/RecommendationContainer.tsx b/src/components/common/RecommendationContainer.tsx deleted file mode 100644 index 6c28272c9..000000000 --- a/src/components/common/RecommendationContainer.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import type { RecommendationDto } from '@jellyfin/sdk/lib/generated-client'; -import React, { FC } from 'react'; - -import globalize from '../../scripts/globalize'; -import escapeHTML from 'escape-html'; -import SectionContainer from './SectionContainer'; - -interface RecommendationContainerProps { - getPortraitShape: () => string; - enableScrollX: () => boolean; - recommendation?: RecommendationDto; -} - -const RecommendationContainer: FC = ({ getPortraitShape, enableScrollX, recommendation = {} }) => { - let title = ''; - - switch (recommendation.RecommendationType) { - case 'SimilarToRecentlyPlayed': - title = globalize.translate('RecommendationBecauseYouWatched', recommendation.BaselineItemName); - break; - - case 'SimilarToLikedItem': - title = globalize.translate('RecommendationBecauseYouLike', recommendation.BaselineItemName); - break; - - case 'HasDirectorFromRecentlyPlayed': - case 'HasLikedDirector': - title = globalize.translate('RecommendationDirectedBy', recommendation.BaselineItemName); - break; - - case 'HasActorFromRecentlyPlayed': - case 'HasLikedActor': - title = globalize.translate('RecommendationStarring', recommendation.BaselineItemName); - break; - } - - return ; -}; - -export default RecommendationContainer; diff --git a/src/components/common/SectionContainer.tsx b/src/components/common/SectionContainer.tsx deleted file mode 100644 index 13c29ee61..000000000 --- a/src/components/common/SectionContainer.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import '../../elements/emby-itemscontainer/emby-itemscontainer'; - -import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client'; -import React, { FC, useEffect, useRef } from 'react'; - -import cardBuilder from '../cardbuilder/cardBuilder'; -import ItemsContainerElement from '../../elements/ItemsContainerElement'; -import ItemsScrollerContainerElement from '../../elements/ItemsScrollerContainerElement'; -import { CardOptions } from '../../types/interface'; - -interface SectionContainerProps { - sectionTitle: string; - enableScrollX: () => boolean; - items?: BaseItemDto[]; - cardOptions?: CardOptions; -} - -const SectionContainer: FC = ({ - sectionTitle, - enableScrollX, - items = [], - cardOptions = {} -}) => { - const element = useRef(null); - - useEffect(() => { - cardBuilder.buildCards(items, { - itemsContainer: element.current?.querySelector('.itemsContainer'), - parentContainer: element.current?.querySelector('.verticalSection'), - scalable: true, - overlayPlayButton: true, - showTitle: true, - centerText: true, - cardLayout: false, - ...cardOptions - }); - }, [cardOptions, enableScrollX, items]); - - return ( -
-
-
-

- {sectionTitle} -

-
- - {enableScrollX() ? : } - -
-
- ); -}; - -export default SectionContainer; diff --git a/src/components/common/ViewItemsContainer.tsx b/src/components/common/ViewItemsContainer.tsx index 5cbc7aace..e501ffaef 100644 --- a/src/components/common/ViewItemsContainer.tsx +++ b/src/components/common/ViewItemsContainer.tsx @@ -12,12 +12,13 @@ import Shuffle from './Shuffle'; import Sort from './Sort'; import NewCollection from './NewCollection'; import globalize from '../../scripts/globalize'; -import { CardOptions, ViewQuerySettings } from '../../types/interface'; import ServerConnections from '../ServerConnections'; import { useLocalStorage } from '../../hooks/useLocalStorage'; import listview from '../listview/listview'; import cardBuilder from '../cardbuilder/cardBuilder'; +import { ViewQuerySettings } from '../../types/interface'; +import { CardOptions } from '../../types/cardOptions'; interface ViewItemsContainerProps { topParentId: string | null; isBtnShuffleEnabled?: boolean; diff --git a/src/hooks/useApi.tsx b/src/hooks/useApi.tsx index ff94b7a53..a1eb4b624 100644 --- a/src/hooks/useApi.tsx +++ b/src/hooks/useApi.tsx @@ -7,7 +7,7 @@ import ServerConnections from '../components/ServerConnections'; import events from '../utils/events'; import { toApi } from '../utils/jellyfin-apiclient/compat'; -interface JellyfinApiContext { +export interface JellyfinApiContext { __legacyApiClient__?: ApiClient api?: Api user?: UserDto diff --git a/src/hooks/useFetchItems.ts b/src/hooks/useFetchItems.ts new file mode 100644 index 000000000..bd24387e8 --- /dev/null +++ b/src/hooks/useFetchItems.ts @@ -0,0 +1,287 @@ +import type { ItemsApiGetItemsRequest } from '@jellyfin/sdk/lib/generated-client'; +import { AxiosRequestConfig } from 'axios'; + +import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; +import { ImageType } from '@jellyfin/sdk/lib/generated-client/models/image-type'; +import { ItemFields } from '@jellyfin/sdk/lib/generated-client/models/item-fields'; +import { ItemFilter } from '@jellyfin/sdk/lib/generated-client/models/item-filter'; +import { SortOrder } from '@jellyfin/sdk/lib/generated-client/models/sort-order'; +import { ItemSortBy } from '@jellyfin/sdk/lib/models/api/item-sort-by'; +import { getGenresApi } from '@jellyfin/sdk/lib/utils/api/genres-api'; +import { getItemsApi } from '@jellyfin/sdk/lib/utils/api/items-api'; +import { getMoviesApi } from '@jellyfin/sdk/lib/utils/api/movies-api'; +import { getTvShowsApi } from '@jellyfin/sdk/lib/utils/api/tv-shows-api'; +import { getUserLibraryApi } from '@jellyfin/sdk/lib/utils/api/user-library-api'; +import { useQuery } from '@tanstack/react-query'; + +import { JellyfinApiContext, useApi } from './useApi'; +import { Sections, SectionsViewType } from 'types/suggestionsSections'; + +type ParentId = string | null | undefined; + +const fetchGetItem = async ( + currentApi: JellyfinApiContext, + parentId: ParentId, + options?: AxiosRequestConfig +) => { + const { api, user } = currentApi; + if (api && user?.Id && parentId) { + const response = await getUserLibraryApi(api).getItem( + { + userId: user.Id, + itemId: parentId + }, + { + signal: options?.signal + } + ); + return response.data; + } +}; + +export const useGetItem = (parentId: ParentId) => { + const currentApi = useApi(); + return useQuery({ + queryKey: ['Item', parentId], + queryFn: ({ signal }) => fetchGetItem(currentApi, parentId, { signal }), + enabled: !!parentId + }); +}; + +const fetchGetItems = async ( + currentApi: JellyfinApiContext, + parametersOptions: ItemsApiGetItemsRequest, + options?: AxiosRequestConfig +) => { + const { api, user } = currentApi; + if (api && user?.Id) { + const response = await getItemsApi(api).getItems( + { + userId: user.Id, + ...parametersOptions + }, + { + signal: options?.signal + } + ); + return response.data; + } +}; + +export const useGetItems = (parametersOptions: ItemsApiGetItemsRequest) => { + const currentApi = useApi(); + return useQuery({ + queryKey: [ + 'Items', + { + ...parametersOptions + } + ], + queryFn: ({ signal }) => + fetchGetItems(currentApi, parametersOptions, { signal }) + }); +}; + +const fetchGetMovieRecommendations = async ( + currentApi: JellyfinApiContext, + parentId: ParentId, + options?: AxiosRequestConfig +) => { + const { api, user } = currentApi; + if (api && user?.Id) { + const response = await getMoviesApi(api).getMovieRecommendations( + { + userId: user.Id, + fields: [ + ItemFields.PrimaryImageAspectRatio, + ItemFields.MediaSourceCount, + ItemFields.BasicSyncInfo + ], + parentId: parentId ?? undefined, + categoryLimit: 6, + itemLimit: 20 + }, + { + signal: options?.signal + } + ); + return response.data; + } +}; + +export const useGetMovieRecommendations = (parentId: ParentId) => { + const currentApi = useApi(); + return useQuery({ + queryKey: ['MovieRecommendations', parentId], + queryFn: ({ signal }) => + fetchGetMovieRecommendations(currentApi, parentId, { signal }), + enabled: !!parentId + }); +}; + +const fetchGetItemsBySuggestionsType = async ( + currentApi: JellyfinApiContext, + sections: Sections, + parentId: ParentId, + options?: AxiosRequestConfig +) => { + const { api, user } = currentApi; + if (api && user?.Id) { + let response; + switch (sections.viewType) { + case SectionsViewType.NextUp: { + response = ( + await getTvShowsApi(api).getNextUp( + { + userId: user.Id, + limit: 25, + fields: [ + ItemFields.PrimaryImageAspectRatio, + ItemFields.MediaSourceCount, + ItemFields.BasicSyncInfo + ], + parentId: parentId ?? undefined, + imageTypeLimit: 1, + enableImageTypes: [ + ImageType.Primary, + ImageType.Backdrop, + ImageType.Thumb + ], + enableTotalRecordCount: false, + ...sections.parametersOptions + }, + { + signal: options?.signal + } + ) + ).data.Items; + break; + } + case SectionsViewType.ResumeItems: { + response = ( + await getItemsApi(api).getResumeItems( + { + userId: user?.Id, + parentId: parentId ?? undefined, + fields: [ + ItemFields.PrimaryImageAspectRatio, + ItemFields.MediaSourceCount, + ItemFields.BasicSyncInfo + ], + imageTypeLimit: 1, + enableImageTypes: [ImageType.Thumb], + enableTotalRecordCount: false, + ...sections.parametersOptions + }, + { + signal: options?.signal + } + ) + ).data.Items; + break; + } + case SectionsViewType.LatestMedia: { + response = ( + await getUserLibraryApi(api).getLatestMedia( + { + userId: user.Id, + fields: [ + ItemFields.PrimaryImageAspectRatio, + ItemFields.MediaSourceCount, + ItemFields.BasicSyncInfo + ], + parentId: parentId ?? undefined, + imageTypeLimit: 1, + enableImageTypes: [ImageType.Primary], + ...sections.parametersOptions + }, + { + signal: options?.signal + } + ) + ).data; + break; + } + default: { + response = ( + await getItemsApi(api).getItems( + { + userId: user.Id, + parentId: parentId ?? undefined, + recursive: true, + fields: [ItemFields.PrimaryImageAspectRatio], + filters: [ItemFilter.IsPlayed], + imageTypeLimit: 1, + enableImageTypes: [ + ImageType.Primary, + ImageType.Backdrop, + ImageType.Thumb + ], + limit: 25, + enableTotalRecordCount: false, + ...sections.parametersOptions + }, + { + signal: options?.signal + } + ) + ).data.Items; + break; + } + } + return response; + } +}; + +export const useGetItemsBySectionType = ( + sections: Sections, + parentId: ParentId +) => { + const currentApi = useApi(); + return useQuery({ + queryKey: ['ItemsBySuggestionsType', sections.view], + queryFn: ({ signal }) => + fetchGetItemsBySuggestionsType( + currentApi, + sections, + parentId, + { signal } + ), + enabled: !!sections.view + }); +}; + +const fetchGetGenres = async ( + currentApi: JellyfinApiContext, + parentId: ParentId, + itemType: BaseItemKind, + options?: AxiosRequestConfig +) => { + const { api, user } = currentApi; + if (api && user?.Id) { + const response = await getGenresApi(api).getGenres( + { + userId: user.Id, + sortBy: [ItemSortBy.SortName], + sortOrder: [SortOrder.Ascending], + includeItemTypes: [itemType], + enableTotalRecordCount: false, + parentId: parentId ?? undefined + }, + { + signal: options?.signal + } + ); + return response.data; + } +}; + +export const useGetGenres = (parentId: ParentId, itemType: BaseItemKind) => { + const currentApi = useApi(); + return useQuery({ + queryKey: ['Genres', parentId], + queryFn: ({ signal }) => + fetchGetGenres(currentApi, parentId, itemType, { signal }), + enabled: !!parentId + }); +}; diff --git a/src/types/cardOptions.ts b/src/types/cardOptions.ts new file mode 100644 index 000000000..7864ab315 --- /dev/null +++ b/src/types/cardOptions.ts @@ -0,0 +1,74 @@ +import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client'; + +export interface CardOptions { + itemsContainer?: HTMLElement | null; + parentContainer?: HTMLElement | null; + items?: BaseItemDto[] | null; + allowBottomPadding?: boolean; + centerText?: boolean; + coverImage?: boolean; + inheritThumb?: boolean; + overlayMoreButton?: boolean; + overlayPlayButton?: boolean; + overlayText?: boolean; + preferThumb?: boolean; + preferDisc?: boolean; + preferLogo?: boolean; + scalable?: boolean; + shape?: string | null; + lazy?: boolean; + cardLayout?: boolean | string; + showParentTitle?: boolean; + showParentTitleOrTitle?: boolean; + showAirTime?: boolean; + showAirDateTime?: boolean; + showChannelName?: boolean; + showTitle?: boolean | string; + showYear?: boolean | string; + showDetailsMenu?: boolean; + missingIndicator?: boolean; + showLocationTypeIndicator?: boolean; + showSeriesYear?: boolean; + showUnplayedIndicator?: boolean; + showChildCountIndicator?: boolean; + lines?: number; + context?: string | null; + action?: string | null; + defaultShape?: string; + indexBy?: string; + parentId?: string | null; + showMenu?: boolean; + cardCssClass?: string | null; + cardClass?: string | null; + centerPlayButton?: boolean; + overlayInfoButton?: boolean; + autoUpdate?: boolean; + cardFooterAside?: string; + includeParentInfoInTitle?: boolean; + maxLines?: number; + overlayMarkPlayedButton?: boolean; + overlayRateButton?: boolean; + showAirEndTime?: boolean; + showCurrentProgram?: boolean; + showCurrentProgramTime?: boolean; + showItemCounts?: boolean; + showPersonRoleOrType?: boolean; + showProgressBar?: boolean; + showPremiereDate?: boolean; + showRuntime?: boolean; + showSeriesTimerTime?: boolean; + showSeriesTimerChannel?: boolean; + showSongCount?: boolean; + width?: number; + showChannelLogo?: boolean; + showLogo?: boolean; + serverId?: string; + collectionId?: string | null; + playlistId?: string | null; + defaultCardImageIcon?: string; + disableHoverMenu?: boolean; + disableIndicators?: boolean; + showGroupCount?: boolean; + containerClass?: string; + noItemsMessage?: string; +} diff --git a/src/types/interface.ts b/src/types/interface.ts index 67c6565e2..c577f84e2 100644 --- a/src/types/interface.ts +++ b/src/types/interface.ts @@ -1,19 +1,3 @@ -import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client'; - -export interface Query extends ViewQuerySettings { - IncludeItemTypes?: string; - Recursive?: boolean; - Fields?: string | null; - ImageTypeLimit?: number; - EnableTotalRecordCount?: boolean; - EnableImageTypes?: string; - StartIndex?: number; - ParentId?: string | null; - IsMissing?: boolean | null; - Limit?:number; - Filters?: string | null; -} - export interface ViewQuerySettings { showTitle?: boolean; showYear?: boolean; @@ -43,80 +27,3 @@ export interface ViewQuerySettings { NameStartsWith?: string | null; StartIndex?: number; } - -export interface CardOptions { - itemsContainer?: HTMLElement | null; - parentContainer?: HTMLElement | null; - items?: BaseItemDto[] | null; - allowBottomPadding?: boolean; - centerText?: boolean; - coverImage?: boolean; - inheritThumb?: boolean; - overlayMoreButton?: boolean; - overlayPlayButton?: boolean; - overlayText?: boolean; - preferThumb?: boolean; - preferDisc?: boolean; - preferLogo?: boolean; - scalable?: boolean; - shape?: string | null; - lazy?: boolean; - cardLayout?: boolean | string; - showParentTitle?: boolean; - showParentTitleOrTitle?: boolean; - showAirTime?: boolean; - showAirDateTime?: boolean; - showChannelName?: boolean; - showTitle?: boolean | string; - showYear?: boolean | string; - showDetailsMenu?: boolean; - missingIndicator?: boolean; - showLocationTypeIndicator?: boolean; - showSeriesYear?: boolean; - showUnplayedIndicator?: boolean; - showChildCountIndicator?: boolean; - lines?: number; - context?: string | null; - action?: string | null; - defaultShape?: string; - indexBy?: string; - parentId?: string | null; - showMenu?: boolean; - cardCssClass?: string | null; - cardClass?: string | null; - centerPlayButton?: boolean; - overlayInfoButton?: boolean; - autoUpdate?: boolean; - cardFooterAside?: string; - includeParentInfoInTitle?: boolean; - maxLines?: number; - overlayMarkPlayedButton?: boolean; - overlayRateButton?: boolean; - showAirEndTime?: boolean; - showCurrentProgram?: boolean; - showCurrentProgramTime?: boolean; - showItemCounts?: boolean; - showPersonRoleOrType?: boolean; - showProgressBar?: boolean; - showPremiereDate?: boolean; - showRuntime?: boolean; - showSeriesTimerTime?: boolean; - showSeriesTimerChannel?: boolean; - showSongCount?: boolean; - width?: number; - showChannelLogo?: boolean; - showLogo?: boolean; - serverId?: string; - collectionId?: string | null; - playlistId?: string | null; - defaultCardImageIcon?: string; - disableHoverMenu?: boolean; - disableIndicators?: boolean; - showGroupCount?: boolean; - containerClass?: string; - noItemsMessage?: string; -} - -export interface LibraryViewProps { - topParentId: string | null; -} diff --git a/src/types/library.ts b/src/types/library.ts new file mode 100644 index 000000000..2a25ca719 --- /dev/null +++ b/src/types/library.ts @@ -0,0 +1,45 @@ +import { ItemFields } from '@jellyfin/sdk/lib/generated-client/models/item-fields'; +import { ItemFilter } from '@jellyfin/sdk/lib/generated-client/models/item-filter'; +import { VideoType } from '@jellyfin/sdk/lib/generated-client/models/video-type'; +import { ImageType } from '@jellyfin/sdk/lib/generated-client/models/image-type'; +import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; +import { ItemSortBy } from '@jellyfin/sdk/lib/models/api/item-sort-by'; +import { SortOrder } from '@jellyfin/sdk/lib/generated-client/models/sort-order'; +import { SeriesStatus } from '@jellyfin/sdk/lib/generated-client/models/series-status'; + +export interface ParametersOptions { + sortBy?: ItemSortBy[]; + sortOrder?: SortOrder[]; + includeItemTypes?: BaseItemKind[]; + fields?: ItemFields[]; + enableImageTypes?: ImageType[]; + videoTypes?: VideoType[]; + seriesStatus?: SeriesStatus[]; + filters?: ItemFilter[]; + limit?: number; + isFavorite?: boolean; + genres?: string[]; + officialRatings?: string[]; + tags?: string[]; + years?: number[]; + is4K?: boolean; + isHd?: boolean; + is3D?: boolean; + hasSubtitles?: boolean; + hasTrailer?: boolean; + hasSpecialFeature?: boolean; + hasThemeSong?: boolean; + hasThemeVideo?: boolean; + parentIndexNumber?: number; + isMissing?: boolean; + isUnaired?: boolean; + startIndex?: number; + nameLessThan?: string; + nameStartsWith?: string; + collapseBoxSetItems?: boolean; + enableTotalRecordCount?: boolean; +} + +export interface LibraryViewProps { + parentId: string | null; +} diff --git a/src/types/suggestionsSections.ts b/src/types/suggestionsSections.ts new file mode 100644 index 000000000..e0e89edb5 --- /dev/null +++ b/src/types/suggestionsSections.ts @@ -0,0 +1,28 @@ +import { CardOptions } from './cardOptions'; +import { ParametersOptions } from './library'; + +export enum SectionsViewType { + ResumeItems = 'resumeItems', + LatestMedia = 'latestMedia', + NextUp = 'nextUp', +} + +export enum SectionsView { + ContinueWatchingMovies = 'continuewatchingmovies', + LatestMovies = 'latestmovies', + ContinueWatchingEpisode = 'continuewatchingepisode', + LatestEpisode = 'latestepisode', + NextUp = 'nextUp', + LatestMusic = 'latestmusic', + RecentlyPlayedMusic = 'recentlyplayedmusic', + FrequentlyPlayedMusic = 'frequentlyplayedmusic', +} + +export interface Sections { + name: string; + view: SectionsView; + type: string; + viewType?: SectionsViewType, + parametersOptions?: ParametersOptions; + cardOptions: CardOptions; +} diff --git a/webpack.common.js b/webpack.common.js index e1448db74..b1fccfd5f 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -161,12 +161,14 @@ const config = { } }, { - test: /\.(js|jsx)$/, + test: /\.(js|jsx|mjs)$/, include: [ path.resolve(__dirname, 'node_modules/event-target-polyfill'), path.resolve(__dirname, 'node_modules/rvfc-polyfill'), path.resolve(__dirname, 'node_modules/@jellyfin/sdk'), path.resolve(__dirname, 'node_modules/@remix-run/router'), + path.resolve(__dirname, 'node_modules/@tanstack/query-core'), + path.resolve(__dirname, 'node_modules/@tanstack/react-query'), path.resolve(__dirname, 'node_modules/@uupaa/dynamic-import-polyfill'), path.resolve(__dirname, 'node_modules/axios'), path.resolve(__dirname, 'node_modules/blurhash'), From 68fcea482bd0cb57bb6f1fb361717e615de3c3c5 Mon Sep 17 00:00:00 2001 From: oad Date: Sun, 11 Jun 2023 15:01:46 +0000 Subject: [PATCH 03/51] Translated using Weblate (Slovak) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/sk/ --- src/strings/sk.json | 46 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/src/strings/sk.json b/src/strings/sk.json index a2d80e1bd..56980492c 100644 --- a/src/strings/sk.json +++ b/src/strings/sk.json @@ -14,7 +14,7 @@ "AllowRemoteAccessHelp": "Nezaškrtnuté znamená, že všetky vzdialené pripojenia budú blokované.", "AlwaysPlaySubtitles": "Vždy zobrazovať", "AnyLanguage": "Akýkoľvek jazyk", - "AroundTime": "Okolo", + "AroundTime": "Okolo {0}", "Artists": "Interpreti", "AsManyAsPossible": "Najviac ako je možné", "Ascending": "Vzostupne", @@ -1447,7 +1447,7 @@ "AspectRatioCover": "Obal", "VideoAudio": "Video Zvuk", "Video": "Video", - "AllowTonemappingHelp": "Mapovanie tónov umožňuje zmeniť dynamicky rozsah videa z HDR na SDR bez straty veľmi dôležitých informácií o pôvodnom obraze, ako napr. detaily a farby. V súčasnosti táto funkcia funguje len pri videách s HDR10 alebo HLG. Táto funkcia vyžaduje OpenCL alebo CUDA.", + "AllowTonemappingHelp": "Tone-mapping can transform the dynamic range of a video from HDR to SDR while maintaining image details and colors, 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.", "LabelTonemappingThresholdHelp": "Parametre algoritmu mapovania tónov sú prispôsobené jednotlivým scénam. A tento prah sa používa na zistenie, či sa scéna zmenila alebo nie. Pokiaľ rozdiel medzi súčasnou priemernou svetlosťou snímku a priebežným priemerom tento prah prekročí, bude priemerná a vrchná svetlosť scény prepočítaná. Doporučené a predvolené hodnoty sú 0.8 a 0.2.", "LabelUDPPortRangeHelp": "Obmedzí UDP pripojenie Jellyfinu na tento rozsah. (Predvolená hodnota je 1024 - 65535).
Poznámka: Niektoré funkcie vyžadujú určité porty, ktoré sa môžu nachádzať mimo tohto rozsahu.", "Remuxing": "Remuxovanie", @@ -1666,9 +1666,9 @@ "MediaInfoVideoRangeType": "Typ rozsahu videa", "LabelVideoRangeType": "Typ rozsahu videa", "VideoRangeTypeNotSupported": "Typ rozsahu videa nie je podporovaný", - "LabelVppTonemappingContrastHelp": "Aplikuje zvýšenie kontrastu pri mapovaní VPP tónov. Odporúčaná hodnota je 1.2 a predvolená hodnota je 1.", + "LabelVppTonemappingContrastHelp": "Aplikuje zvýšenie kontrastu pri mapovaní VPP tónov. Odporúčaná a predvolená hodnota je 1.", "LabelVppTonemappingContrast": "Zvýšenie kontrastu mapovania VPP tónov", - "LabelVppTonemappingBrightnessHelp": "Aplikuje zvýšenie jasu pri mapovaní VPP tónov. Odporúčaná a predvolená hodnota je 0.", + "LabelVppTonemappingBrightnessHelp": "Aplikuje zvýšenie jasu pri mapovaní VPP tónov. Odporúčané a predvolené hodnoty sú 16 a 0.", "LabelVppTonemappingBrightness": "Zvýšenie jasu mapovania VPP tónov", "ScreenResolution": "Rozlíšenie obrazovky", "RememberSubtitleSelectionsHelp": "Pokúsi sa nastaviť titulky tak, aby sa čo najviac zhodovali s posledným videom.", @@ -1694,15 +1694,47 @@ "LabelDummyChapterCount": "Limit", "LabelDummyChapterCountHelp": "Maximálny počet obrázkov kapitol, ktoré budú extrahované pre každý mediálny súbor.", "LabelChapterImageResolution": "Rozlíšenie", - "LabelChapterImageResolutionHelp": "Rozlíšenie extrahovaných obrázkov kapitol.", + "LabelChapterImageResolutionHelp": "Rozlíšenie extrahovaných obrázkov kapitol. Zmena tohto nastavenia nemá žiaden vplyv na existujúce kapitoly.", "ResolutionMatchSource": "Rovnaké ako zdroj", "SaveRecordingNFOHelp": "Uloží metadáta z EPG položiek sprievodcu spolu s médiami.", "SaveRecordingImages": "Uložiť obrázky EPG nahrávky", - "LabelDummyChapterDurationHelp": "Interval extrakcie obrázkov kapitol v sekundách.", + "LabelDummyChapterDurationHelp": "Interval medzi kapitolami. Vytváranie kapitol je možné vypnúť nastavením na 0. Zmena tohto nastavenia nemá žiaden vplyv na existujúce kapitoly.", "SaveRecordingNFO": "Uložiť metadáta nahrávky zo sprievodcu EPG do NFO", "SaveRecordingImagesHelp": "Uloží obrázky z EPG položiek sprievodcu spolu s médiami.", "HeaderRecordingMetadataSaving": "Metadáta nahrávok", "SecondarySubtitles": "Sekundárne titulky", "LabelEnableAudioVbr": "Povoliť kódovanie zvuku VBR", - "HeaderPerformance": "Výkon" + "HeaderPerformance": "Výkon", + "AllowCollectionManagement": "Povoliť tomuto používateľovi spravovať kolekcie", + "LabelParallelImageEncodingLimitHelp": "Maximálny počet kódovania obrázkov, ktoré môžu naraz bežať. Nastavením na 0 bude limit nastavení podľa parametrov systému.", + "TonemappingModeHelp": "Vyberte režim mapovania tónu. Ak narazíte na preexponované svetlé miesta, skúste prepnúť na režim RGB.", + "Featurette": "Stredne dlhý film", + "Short": "Krátky film", + "PasswordRequiredForAdmin": "Administrátorské úcty musia mať nastavené heslo.", + "LabelTonemappingMode": "Režim mapovania tónu", + "SubtitleCyan": "Tyrkysová", + "SubtitleGray": "Sivá", + "Studio": "Štúdio", + "SubtitleBlue": "Modrá", + "SubtitleBlack": "Čierna", + "SubtitleGreen": "Zelená", + "SubtitleLightGray": "Svetlo sivá", + "SubtitleRed": "Červená", + "SubtitleYellow": "Žltá", + "SubtitleWhite": "Biela", + "Select": "Vybrať", + "EnableAudioNormalization": "Normalizácia hlasitosti", + "GetThePlugin": "Získať zásuvný modul", + "LabelParallelImageEncodingLimit": "Limit paralelného kódovania obrázkov", + "Notifications": "Upozornenia", + "NotificationsMovedMessage": "Funkcia upozornení bola presunutá do zásuvného modulu Webhook.", + "PreferEmbeddedExtrasTitlesOverFileNames": "Preferovať vložené názvy pred názvami súborov pre doplnky", + "PreferEmbeddedExtrasTitlesOverFileNamesHelp": "Doplnky väčšinou majú totožní vložení názov ako nadriadená položka. Zaškrtnutím ich môžete napriek uprednostniť.", + "SubtitleMagenta": "Fialová", + "LabelEnableAudioVbrHelp": "Premenlivý bitový tok ponúka lepší pomer medzi kvalitou a priemerným bitovým tokom, ale niekdy môže spôsobiť dodatočné načítavanie alebo problémy s kompatibilitou.", + "MenuClose": "Zatvoriť ponuku", + "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" } From b13b1ff76d71381bce0d74c1d0ac78d919acaab0 Mon Sep 17 00:00:00 2001 From: thaibert Date: Sun, 11 Jun 2023 22:41:41 +0000 Subject: [PATCH 04/51] 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 676448e34..9004249c0 100644 --- a/src/strings/da.json +++ b/src/strings/da.json @@ -20,7 +20,7 @@ "AllowRemoteAccessHelp": "Hvis ikke markeret, vil alle forbindelser udefra blive blokeret.", "AllowedRemoteAddressesHelp": "Komma separeret liste over IP-adresser eller IP/rundsendings-adresser til netværk som har tilladelse til at forbinde udefra. Hvis dette felt er tomt, er alle eksterne adresser tilladt.", "Anytime": "Altid", - "AroundTime": "Omkring", + "AroundTime": "Cirka [0]", "AsManyAsPossible": "Så mange som muligt", "AspectRatio": "Billedformat", "Audio": "Lyd", From 4545814e914365535852b8313b48a7801f06f4fe Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Fri, 16 Jun 2023 16:19:44 -0400 Subject: [PATCH 05/51] Fix syncplay variable capitalization --- .../AppToolbar/menus/SyncPlayMenu.tsx | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/apps/experimental/components/AppToolbar/menus/SyncPlayMenu.tsx b/src/apps/experimental/components/AppToolbar/menus/SyncPlayMenu.tsx index 847b0a80a..cb377784e 100644 --- a/src/apps/experimental/components/AppToolbar/menus/SyncPlayMenu.tsx +++ b/src/apps/experimental/components/AppToolbar/menus/SyncPlayMenu.tsx @@ -45,7 +45,7 @@ const SyncPlayMenu: FC = ({ open, onMenuClose }) => { - const [ SyncPlay, setSyncPlay ] = useState(); + const [ syncPlay, setSyncPlay ] = useState(); const { __legacyApiClient__, api, user } = useApi(); const [ groups, setGroups ] = useState([]); const [ currentGroup, setCurrentGroup ] = useState(); @@ -113,13 +113,13 @@ const SyncPlayMenu: FC = ({ }, [ api, onMenuClose ]); const onGroupSettingsClick = useCallback(async () => { - if (!SyncPlay) return; + if (!syncPlay) return; // TODO: Rewrite settings UI const SyncPlaySettingsEditor = (await import('../../../../../plugins/syncPlay/ui/settings/SettingsEditor')).default; new SyncPlaySettingsEditor( __legacyApiClient__, - SyncPlay.Manager.getTimeSyncCore(), + syncPlay.Manager.getTimeSyncCore(), { groupInfo: currentGroup }) @@ -131,43 +131,43 @@ const SyncPlayMenu: FC = ({ }); onMenuClose(); - }, [ __legacyApiClient__, currentGroup, onMenuClose, SyncPlay ]); + }, [ __legacyApiClient__, currentGroup, onMenuClose, syncPlay ]); const onStartGroupPlaybackClick = useCallback(() => { if (__legacyApiClient__) { - SyncPlay?.Manager.resumeGroupPlayback(__legacyApiClient__); + syncPlay?.Manager.resumeGroupPlayback(__legacyApiClient__); onMenuClose(); } - }, [ __legacyApiClient__, onMenuClose, SyncPlay ]); + }, [ __legacyApiClient__, onMenuClose, syncPlay ]); const onStopGroupPlaybackClick = useCallback(() => { if (__legacyApiClient__) { - SyncPlay?.Manager.haltGroupPlayback(__legacyApiClient__); + syncPlay?.Manager.haltGroupPlayback(__legacyApiClient__); onMenuClose(); } - }, [ __legacyApiClient__, onMenuClose, SyncPlay ]); + }, [ __legacyApiClient__, onMenuClose, syncPlay ]); const updateSyncPlayGroup = useCallback((_e, enabled) => { - if (SyncPlay && enabled) { - setCurrentGroup(SyncPlay.Manager.getGroupInfo() ?? undefined); + if (syncPlay && enabled) { + setCurrentGroup(syncPlay.Manager.getGroupInfo() ?? undefined); } else { setCurrentGroup(undefined); } - }, [ SyncPlay ]); + }, [ syncPlay ]); useEffect(() => { - if (!SyncPlay) return; + if (!syncPlay) return; - Events.on(SyncPlay.Manager, 'enabled', updateSyncPlayGroup); + Events.on(syncPlay.Manager, 'enabled', updateSyncPlayGroup); return () => { - Events.off(SyncPlay.Manager, 'enabled', updateSyncPlayGroup); + Events.off(syncPlay.Manager, 'enabled', updateSyncPlayGroup); }; - }, [ updateSyncPlayGroup, SyncPlay ]); + }, [ updateSyncPlayGroup, syncPlay ]); const menuItems = []; if (isSyncPlayEnabled) { - if (!SyncPlay?.Manager.isPlaylistEmpty() && !SyncPlay?.Manager.isPlaybackActive()) { + if (!syncPlay?.Manager.isPlaylistEmpty() && !syncPlay?.Manager.isPlaybackActive()) { menuItems.push( = ({ ); - } else if (SyncPlay?.Manager.isPlaybackActive()) { + } else if (syncPlay?.Manager.isPlaybackActive()) { menuItems.push( Date: Sat, 17 Jun 2023 20:50:16 +0000 Subject: [PATCH 06/51] Translated using Weblate (Belarusian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/be/ --- src/strings/be-by.json | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/strings/be-by.json b/src/strings/be-by.json index 5e8b80f92..3d5c913b1 100644 --- a/src/strings/be-by.json +++ b/src/strings/be-by.json @@ -1684,11 +1684,11 @@ "PreferEmbeddedExtrasTitlesOverFileNamesHelp": "Extras часта маюць такое ж убудаванае імя, што і бацькоўскі, пазначце гэта, каб у любым выпадку выкарыстоўваць для іх убудаваныя загалоўкі.", "HeaderDummyChapter": "Выявы раздзела", "LabelDummyChapterDuration": "Інтэрвал", - "LabelDummyChapterDurationHelp": "Інтэрвал вымання выявы главы ў секундах.", + "LabelDummyChapterDurationHelp": "Інтэрвал паміж малюнкамі раздзелаў. Усталюйце 0, каб адключыць стварэнне вобразаў раздзелаў. Змена гэтай опцыі не паўплывае на існуючыя выявы раздзелаў.", "LabelDummyChapterCount": "Ліміт", "LabelDummyChapterCountHelp": "Максімальная колькасць выяваў раздзелаў, якія будуць выняты для кожнага медыяфайла.", "LabelChapterImageResolution": "Дазвол", - "LabelChapterImageResolutionHelp": "Дазвол вынятых малюнкаў раздзелаў.", + "LabelChapterImageResolutionHelp": "Разрозненне створаных малюнкаў раздзелаў. Змена гэтай опцыі не паўплывае на існуючыя выявы раздзелаў.", "ResolutionMatchSource": "Супадзенне з крыніцай", "SecondarySubtitles": "Дадатковыя субтытры", "SubtitleBlack": "Чорны", @@ -1719,5 +1719,10 @@ "MenuOpen": "Адкрыць меню", "MenuClose": "Зачыніць меню", "AllowCollectionManagement": "Дазволіць гэтаму карыстальніку кіраваць калекцыямі", - "UserMenu": "Меню карыстальніка" + "UserMenu": "Меню карыстальніка", + "PasswordRequiredForAdmin": "Для ўліковых запісаў адміністратара патрабуецца пароль.", + "GetThePlugin": "Атрымаць плагін", + "LabelSyncPlayNoGroups": "Няма даступных груп", + "Notifications": "Апавяшчэнні", + "NotificationsMovedMessage": "Функцыянальнасць апавяшчэнняў перанесена ў плагін Webhook." } From a370b5aa99ea55facdf864f6854686a64854d917 Mon Sep 17 00:00:00 2001 From: stanol Date: Sat, 17 Jun 2023 20:15:36 +0000 Subject: [PATCH 07/51] 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 2e33c4b6c..5c24b2968 100644 --- a/src/strings/uk.json +++ b/src/strings/uk.json @@ -1734,5 +1734,6 @@ "GetThePlugin": "Отримати плагін", "NotificationsMovedMessage": "Функціонал сповіщень переїхав до плагіна Webhook.", "Notifications": "Сповіщення", - "PasswordRequiredForAdmin": "Для облікових записів адміністратора потрібен пароль." + "PasswordRequiredForAdmin": "Для облікових записів адміністратора потрібен пароль.", + "LabelSyncPlayNoGroups": "Групи не доступні" } From f0f213fe32dce7319fe2c0fa3fcb6cc2d6105f1b Mon Sep 17 00:00:00 2001 From: Oskari Lavinto Date: Sun, 18 Jun 2023 03:28:47 +0000 Subject: [PATCH 08/51] Translated using Weblate (Finnish) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fi/ --- src/strings/fi.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/strings/fi.json b/src/strings/fi.json index 1f08725f9..d0093b9fe 100644 --- a/src/strings/fi.json +++ b/src/strings/fi.json @@ -1734,5 +1734,7 @@ "LabelEnableLUFSScanHelp": "Käytä musiikin LUFS-tarkistusta (tämä vaatii enemmän aikaa ja resurseja).", "GetThePlugin": "Hanki laajennus", "Notifications": "Ilmoitukset", - "NotificationsMovedMessage": "Ilmoitustoiminnallisuus on siirtynyt Webhook-laajennukseen." + "NotificationsMovedMessage": "Ilmoitustoiminnallisuus on siirtynyt Webhook-laajennukseen.", + "PasswordRequiredForAdmin": "Ylläpitotileille on määritettävä salasana.", + "LabelSyncPlayNoGroups": "Ryhmiä ei ole käytettävissä" } From bcd20d0c8a18b2f3392c57efc460c10107f51b62 Mon Sep 17 00:00:00 2001 From: robertscerri Date: Mon, 19 Jun 2023 08:12:28 -0400 Subject: [PATCH 09/51] Added translation using Weblate (Maltese) --- src/strings/mt.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/strings/mt.json diff --git a/src/strings/mt.json b/src/strings/mt.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/src/strings/mt.json @@ -0,0 +1 @@ +{} From bccd839392495dae00da7e5b3d2656af61b40b84 Mon Sep 17 00:00:00 2001 From: Ozeliurs Date: Mon, 19 Jun 2023 12:22:20 +0000 Subject: [PATCH 10/51] Translated using Weblate (French) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fr/ --- src/strings/fr.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/strings/fr.json b/src/strings/fr.json index 4544664cf..7e412607e 100644 --- a/src/strings/fr.json +++ b/src/strings/fr.json @@ -1737,5 +1737,6 @@ "GetThePlugin": "Obtenir le plugin", "Notifications": "Notifications", "NotificationsMovedMessage": "La fonctionnalité de notifications a été transférée au plugin Webhook.", - "PasswordRequiredForAdmin": "Un mot de passe est requis pour les comptes administrateur." + "PasswordRequiredForAdmin": "Un mot de passe est requis pour les comptes administrateur.", + "LabelSyncPlayNoGroups": "Pas de groupes disponibles" } From c661d219eca9d1083cbab45ee7b96c5bd1977580 Mon Sep 17 00:00:00 2001 From: Bas Date: Mon, 19 Jun 2023 12:58:34 +0000 Subject: [PATCH 11/51] Translated using Weblate (Dutch) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/nl/ --- src/strings/nl.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/strings/nl.json b/src/strings/nl.json index 3b4b777ff..f824ca130 100644 --- a/src/strings/nl.json +++ b/src/strings/nl.json @@ -1736,5 +1736,6 @@ "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.", - "PasswordRequiredForAdmin": "Voor beheerdersaccounts is een wachtwoord vereist." + "PasswordRequiredForAdmin": "Voor beheerdersaccounts is een wachtwoord vereist.", + "LabelSyncPlayNoGroups": "Geen groepen beschikbaar" } From 1cbd8b1ceb0dd7bc9c977c18ffa9a3a26e353a5e Mon Sep 17 00:00:00 2001 From: rubjo Date: Mon, 19 Jun 2023 12:10:04 +0000 Subject: [PATCH 12/51] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegian?= =?UTF-8?q?=20Bokm=C3=A5l)=20Translation:=20Jellyfin/Jellyfin=20Web=20Tran?= =?UTF-8?q?slate-URL:=20https://translate.jellyfin.org/projects/jellyfin/j?= =?UTF-8?q?ellyfin-web/nb=5FNO/?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/strings/nb.json | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/src/strings/nb.json b/src/strings/nb.json index 5e62ebfff..8df422c37 100644 --- a/src/strings/nb.json +++ b/src/strings/nb.json @@ -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 eller HLG 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", @@ -1663,7 +1663,7 @@ "IgnoreDtsHelp": "Deaktiviering av dette valget kan løse noen problemer, f.eks. manglende lyd på kanaler med seperat lyd og videospor.", "IgnoreDts": "Ignorer DTS (dekoding tidsmerke)", "MessageNoFavoritesAvailable": "Ingen favoritter er tilgjengelige for øyeblikket.", - "LabelVppTonemappingBrightnessHelp": "Bruk lysstyrkeforsterkning i VPP-tonemapping. Både anbefalte og standardverdier er 0.", + "LabelVppTonemappingBrightnessHelp": "Bruk lysstyrkeforsterkning i VPP-tonemapping. Både anbefalte og standardverdier er 16 og 0.", "MediaInfoDoViTitle": "DV tittel", "MediaInfoElPresentFlag": "DV el forhåndsinnstilt flagg", "Unreleased": "Ennå ikke utgitt", @@ -1672,7 +1672,7 @@ "MediaInfoDvLevel": "DV nivå", "MediaInfoRpuPresentFlag": "DV rpu forhåndsinnstilt flagg", "LabelVppTonemappingContrast": "VPP-tonemapping kontrastforsterkning", - "LabelVppTonemappingContrastHelp": "Bruk kontrastforsterkning i VPP-tonemapping. De anbefalte og standardverdiene er 1.2 og 1.", + "LabelVppTonemappingContrastHelp": "Bruk kontrastforsterkning i VPP-tonemapping. Den anbefalte standardverdien er 1.", "VideoRangeTypeNotSupported": "Videoens områdetype støttes ikke", "LabelVideoRangeType": "Video områdetype", "MediaInfoVideoRangeType": "Video områdetype", @@ -1694,7 +1694,7 @@ "LabelDummyChapterDuration": "Intervall", "HeaderRecordingMetadataSaving": "Opptak metadata", "HeaderPerformance": "Ytelse", - "LabelDummyChapterDurationHelp": "Tid mellom innsamling av kapittelbilder i sekunder.", + "LabelDummyChapterDurationHelp": "Tid mellom dummykapitler. Sett til 0 for å slå av dummykapittelgenerering. Endring av dette vil ha ingen effekt på eksiterende dummykapitler.", "LabelEnableAudioVbrHelp": "Variabel bithastighet tilbyr bedre forhold mellom kvalitet og gjennomsnittlig bithastighet, men kan i visse tilfeller forårsake buffering og problemer med kompatibilitet.", "LabelEnableAudioVbr": "Aktiver VBR lydkoding", "SubtitleBlack": "Svart", @@ -1710,5 +1710,32 @@ "SubtitleRed": "Rød", "SubtitleYellow": "Gul", "SubtitleWhite": "Hvit", - "Select": "Velg" + "Select": "Velg", + "SaveRecordingImages": "Lagre EPG-opptaksbilder", + "PasswordRequiredForAdmin": "Et passord et påkrevd for administratorkontoer.", + "PreferEmbeddedExtrasTitlesOverFileNames": "Foretrekk innbakte titler foran filnavn for ekstramateriell", + "SaveRecordingNFO": "Lagre EPG-opptaksdata i NFO", + "SaveRecordingImagesHelp": "Lagre bilder fra EPG-listekilde sammen med media.", + "StereoDownmixAlgorithmHelp": "Algoritme benyttet til å mikse ned multikanals lyd til stereo.", + "Studio": "Studio", + "SubtitleCyan": "Turkis", + "UserMenu": "Brukermenyen", + "Featurette": "Featurette", + "LabelTonemappingMode": "Tonemappingsmodus", + "PreferEmbeddedExtrasTitlesOverFileNamesHelp": "Ekstramateriale har ofte det samme innebygde navnet som det opprinnelige materialet. Kryss av for denne for å bruke den innebygde tittelen likevel.", + "LabelSyncPlayNoGroups": "Ingen grupper tilgjengelig", + "NotificationsMovedMessage": "Varslingsfunksjonaliteten er blitt flyttet til Webhook-programtillegget.", + "EnableAudioNormalization": "Lynormalisering", + "GetThePlugin": "Skaff deg programtillegget", + "Notifications": "Varsler", + "TonemappingModeHelp": "Velg tonemappingsmodus. Hvis du merker utvaskede høylysområder, prøv å bytte til RGB-modus.", + "EnableAudioNormalizationHelp": "Lydnormalisering vil legge på en konstant lydforsterkning for å holde gjennomsnittet på ønsket nivå (-18db).", + "LabelEnableLUFSScan": "Slå på LUFS-skanning", + "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).", + "ResolutionMatchSource": "Bruk kildetreff", + "SaveRecordingNFOHelp": "Lagre metadata fra EPG-listekilde sammen med media.", + "AllowCollectionManagement": "La denne brukeren organisere samlinger" } From 97dbcddd6227cbb612422bb0870e13317fbe75ab Mon Sep 17 00:00:00 2001 From: robertscerri Date: Mon, 19 Jun 2023 12:14:28 +0000 Subject: [PATCH 13/51] Translated using Weblate (Maltese) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/mt/ --- src/strings/mt.json | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/strings/mt.json b/src/strings/mt.json index 0967ef424..d00dc1217 100644 --- a/src/strings/mt.json +++ b/src/strings/mt.json @@ -1 +1,22 @@ -{} +{ + "Absolute": "Assolut", + "AddToPlaylist": "Żid fil-playlist", + "AddToPlayQueue": "Żid fil-play queue", + "Album": "Album", + "AllEpisodes": "L-episodji kollha", + "AllLanguages": "Il-lingwi kollha", + "AllLibraries": "Il-libreriji kollha", + "AccessRestrictedTryAgainLater": "Bħalissa l-aċċess huwa ristrett. Jekk jogħgbok erġa' prova iktar tard.", + "Actor": "Attur", + "Add": "Żid", + "AddedOnValue": "{0} Miżjuda", + "AddToCollection": "Żid fil-kollezzjoni", + "AddToFavorites": "Żid fil-lista tal-favoriti", + "AgeValue": "({0} snin)", + "AirDate": "Data tax-xandir", + "Aired": "Imxandar", + "Albums": "Albums", + "All": "Kollox", + "AllChannels": "L-istazzjonijiet kollha", + "AllComplexFormats": "Il-formats ikkumplikati kollha (ASS, SSA, VobSub, PGS, SUB, IDX, …)" +} From 82d6a37d9aaa3280310297ca1da05cb19b9c037f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=9A=87=E7=94=AB=E6=9C=9D=E4=BA=91?= Date: Tue, 20 Jun 2023 00:19:29 +0000 Subject: [PATCH 14/51] 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 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/strings/zh-cn.json b/src/strings/zh-cn.json index 866af25d1..5fc3ee980 100644 --- a/src/strings/zh-cn.json +++ b/src/strings/zh-cn.json @@ -1737,5 +1737,6 @@ "GetThePlugin": "获取插件", "Notifications": "通知", "NotificationsMovedMessage": "通知功能已经转移到Webhook插件中。", - "PasswordRequiredForAdmin": "管理员账户需要密码。" + "PasswordRequiredForAdmin": "管理员账户需要密码。", + "LabelSyncPlayNoGroups": "没有可用的组" } From 76460f3abe965c9ebdc90f82b7d19ca6cecb318f Mon Sep 17 00:00:00 2001 From: Andi Chandler Date: Tue, 20 Jun 2023 22:35:53 +0000 Subject: [PATCH 15/51] 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 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/strings/en-gb.json b/src/strings/en-gb.json index c9827ad23..938f0dc18 100644 --- a/src/strings/en-gb.json +++ b/src/strings/en-gb.json @@ -1737,5 +1737,6 @@ "GetThePlugin": "Get the Plugin", "Notifications": "Notifications", "NotificationsMovedMessage": "The notifications functionality has moved to the Webhook plugin.", - "PasswordRequiredForAdmin": "A password is required for admin accounts." + "PasswordRequiredForAdmin": "A password is required for admin accounts.", + "LabelSyncPlayNoGroups": "No groups available" } From 8a390e3f2a68d225ac70af018f055fbef7368743 Mon Sep 17 00:00:00 2001 From: millallo Date: Wed, 21 Jun 2023 06:38:32 +0000 Subject: [PATCH 16/51] 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, 6 insertions(+), 1 deletion(-) diff --git a/src/strings/it.json b/src/strings/it.json index e018038d9..1303a549c 100644 --- a/src/strings/it.json +++ b/src/strings/it.json @@ -1731,5 +1731,10 @@ "Studio": "Studio", "GetThePlugin": "Ottieni il Plugin", "Notifications": "Notifiche", - "AllowCollectionManagement": "Permetti a questo utente di gestire le collezioni" + "AllowCollectionManagement": "Permetti a questo utente di gestire le collezioni", + "PasswordRequiredForAdmin": "La password è obbligatoria per gli account amministratori.", + "LabelSyncPlayNoGroups": "Nessun gruppo disponibile", + "NotificationsMovedMessage": "Le notifiche sono state spostate nel plugin Webhook.", + "EnableAudioNormalizationHelp": "La normalizzazione dell'audio aggiunge un guadagno per mantenerlo ad un livello desiderato (-18dB).", + "EnableAudioNormalization": "Normalizzazione Audio" } From 418543a74b8c0bbee64fa8ad2392260d8a678348 Mon Sep 17 00:00:00 2001 From: hoanghuy309 Date: Wed, 21 Jun 2023 16:02:02 +0000 Subject: [PATCH 17/51] Translated using Weblate (Vietnamese) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/vi/ --- src/strings/vi.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/strings/vi.json b/src/strings/vi.json index ed29622e7..ef4416ea8 100644 --- a/src/strings/vi.json +++ b/src/strings/vi.json @@ -1724,5 +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": "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).", + "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" } From 00ec92cc02a7e67def62e6f9af72c1cacb719401 Mon Sep 17 00:00:00 2001 From: grafixeyehero Date: Sun, 25 Jun 2023 03:15:21 +0300 Subject: [PATCH 18/51] apply suggestion --- .../library/GenresItemsContainer.tsx | 7 ++-- .../library/RecommendationContainer.tsx | 7 +++- .../routes/movies/SuggestionsView.tsx | 2 +- src/components/common/ViewItemsContainer.tsx | 1 + src/hooks/useFetchItems.ts | 23 +++++----- src/types/library.ts | 42 ------------------- src/types/suggestionsSections.ts | 10 ++++- 7 files changed, 31 insertions(+), 61 deletions(-) diff --git a/src/apps/experimental/components/library/GenresItemsContainer.tsx b/src/apps/experimental/components/library/GenresItemsContainer.tsx index 52968f5f1..41fba412d 100644 --- a/src/apps/experimental/components/library/GenresItemsContainer.tsx +++ b/src/apps/experimental/components/library/GenresItemsContainer.tsx @@ -18,8 +18,8 @@ const GenresItemsContainer: FC = ({ itemType }) => { const { isLoading, data: genresResult } = useGetGenres( - parentId, - itemType + itemType, + parentId ); if (isLoading) { @@ -34,8 +34,7 @@ const GenresItemsContainer: FC = ({

{globalize.translate('MessageNoGenresAvailable')}

) : ( - genresResult?.Items - && genresResult?.Items.map((genre) => ( + genresResult?.Items?.map((genre) => ( = ({ items={recommendation.Items || []} cardOptions={{ shape: 'overflowPortrait', - showYear: true + showYear: true, + scalable: true, + overlayPlayButton: true, + showTitle: true, + centerText: true, + cardLayout: false }} /> ); diff --git a/src/apps/experimental/routes/movies/SuggestionsView.tsx b/src/apps/experimental/routes/movies/SuggestionsView.tsx index c521485b1..a31200f58 100644 --- a/src/apps/experimental/routes/movies/SuggestionsView.tsx +++ b/src/apps/experimental/routes/movies/SuggestionsView.tsx @@ -39,7 +39,7 @@ const SuggestionsView: FC = ({ parentId }) => { return ( ); diff --git a/src/components/common/ViewItemsContainer.tsx b/src/components/common/ViewItemsContainer.tsx index e501ffaef..1ea5ef989 100644 --- a/src/components/common/ViewItemsContainer.tsx +++ b/src/components/common/ViewItemsContainer.tsx @@ -19,6 +19,7 @@ import cardBuilder from '../cardbuilder/cardBuilder'; import { ViewQuerySettings } from '../../types/interface'; import { CardOptions } from '../../types/cardOptions'; + interface ViewItemsContainerProps { topParentId: string | null; isBtnShuffleEnabled?: boolean; diff --git a/src/hooks/useFetchItems.ts b/src/hooks/useFetchItems.ts index bd24387e8..fcc2b2125 100644 --- a/src/hooks/useFetchItems.ts +++ b/src/hooks/useFetchItems.ts @@ -17,11 +17,9 @@ import { useQuery } from '@tanstack/react-query'; import { JellyfinApiContext, useApi } from './useApi'; import { Sections, SectionsViewType } from 'types/suggestionsSections'; -type ParentId = string | null | undefined; - const fetchGetItem = async ( currentApi: JellyfinApiContext, - parentId: ParentId, + parentId?: string | null, options?: AxiosRequestConfig ) => { const { api, user } = currentApi; @@ -39,7 +37,7 @@ const fetchGetItem = async ( } }; -export const useGetItem = (parentId: ParentId) => { +export const useGetItem = (parentId?: string | null) => { const currentApi = useApi(); return useQuery({ queryKey: ['Item', parentId], @@ -78,13 +76,14 @@ export const useGetItems = (parametersOptions: ItemsApiGetItemsRequest) => { } ], queryFn: ({ signal }) => - fetchGetItems(currentApi, parametersOptions, { signal }) + fetchGetItems(currentApi, parametersOptions, { signal }), + cacheTime: parametersOptions.sortBy?.includes(ItemSortBy.Random) ? 0 : undefined }); }; const fetchGetMovieRecommendations = async ( currentApi: JellyfinApiContext, - parentId: ParentId, + parentId?: string | null, options?: AxiosRequestConfig ) => { const { api, user } = currentApi; @@ -109,7 +108,7 @@ const fetchGetMovieRecommendations = async ( } }; -export const useGetMovieRecommendations = (parentId: ParentId) => { +export const useGetMovieRecommendations = (parentId?: string | null) => { const currentApi = useApi(); return useQuery({ queryKey: ['MovieRecommendations', parentId], @@ -122,7 +121,7 @@ export const useGetMovieRecommendations = (parentId: ParentId) => { const fetchGetItemsBySuggestionsType = async ( currentApi: JellyfinApiContext, sections: Sections, - parentId: ParentId, + parentId?: string | null, options?: AxiosRequestConfig ) => { const { api, user } = currentApi; @@ -235,7 +234,7 @@ const fetchGetItemsBySuggestionsType = async ( export const useGetItemsBySectionType = ( sections: Sections, - parentId: ParentId + parentId?: string | null ) => { const currentApi = useApi(); return useQuery({ @@ -253,8 +252,8 @@ export const useGetItemsBySectionType = ( const fetchGetGenres = async ( currentApi: JellyfinApiContext, - parentId: ParentId, itemType: BaseItemKind, + parentId?: string | null, options?: AxiosRequestConfig ) => { const { api, user } = currentApi; @@ -276,12 +275,12 @@ const fetchGetGenres = async ( } }; -export const useGetGenres = (parentId: ParentId, itemType: BaseItemKind) => { +export const useGetGenres = (itemType: BaseItemKind, parentId?: string | null) => { const currentApi = useApi(); return useQuery({ queryKey: ['Genres', parentId], queryFn: ({ signal }) => - fetchGetGenres(currentApi, parentId, itemType, { signal }), + fetchGetGenres(currentApi, itemType, parentId, { signal }), enabled: !!parentId }); }; diff --git a/src/types/library.ts b/src/types/library.ts index 2a25ca719..1c04dae30 100644 --- a/src/types/library.ts +++ b/src/types/library.ts @@ -1,45 +1,3 @@ -import { ItemFields } from '@jellyfin/sdk/lib/generated-client/models/item-fields'; -import { ItemFilter } from '@jellyfin/sdk/lib/generated-client/models/item-filter'; -import { VideoType } from '@jellyfin/sdk/lib/generated-client/models/video-type'; -import { ImageType } from '@jellyfin/sdk/lib/generated-client/models/image-type'; -import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; -import { ItemSortBy } from '@jellyfin/sdk/lib/models/api/item-sort-by'; -import { SortOrder } from '@jellyfin/sdk/lib/generated-client/models/sort-order'; -import { SeriesStatus } from '@jellyfin/sdk/lib/generated-client/models/series-status'; - -export interface ParametersOptions { - sortBy?: ItemSortBy[]; - sortOrder?: SortOrder[]; - includeItemTypes?: BaseItemKind[]; - fields?: ItemFields[]; - enableImageTypes?: ImageType[]; - videoTypes?: VideoType[]; - seriesStatus?: SeriesStatus[]; - filters?: ItemFilter[]; - limit?: number; - isFavorite?: boolean; - genres?: string[]; - officialRatings?: string[]; - tags?: string[]; - years?: number[]; - is4K?: boolean; - isHd?: boolean; - is3D?: boolean; - hasSubtitles?: boolean; - hasTrailer?: boolean; - hasSpecialFeature?: boolean; - hasThemeSong?: boolean; - hasThemeVideo?: boolean; - parentIndexNumber?: number; - isMissing?: boolean; - isUnaired?: boolean; - startIndex?: number; - nameLessThan?: string; - nameStartsWith?: string; - collapseBoxSetItems?: boolean; - enableTotalRecordCount?: boolean; -} - export interface LibraryViewProps { parentId: string | null; } diff --git a/src/types/suggestionsSections.ts b/src/types/suggestionsSections.ts index e0e89edb5..afb198a97 100644 --- a/src/types/suggestionsSections.ts +++ b/src/types/suggestionsSections.ts @@ -1,5 +1,13 @@ +import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; +import { ItemSortBy } from '@jellyfin/sdk/lib/models/api/item-sort-by'; +import { SortOrder } from '@jellyfin/sdk/lib/generated-client/models/sort-order'; import { CardOptions } from './cardOptions'; -import { ParametersOptions } from './library'; + +interface ParametersOptions { + sortBy?: ItemSortBy[]; + sortOrder?: SortOrder[]; + includeItemTypes?: BaseItemKind[]; +} export enum SectionsViewType { ResumeItems = 'resumeItems', From cea1e94ee87b2ccd9c776f2cd243ea23680ff839 Mon Sep 17 00:00:00 2001 From: Max Musterman Date: Sun, 25 Jun 2023 16:05:38 +0000 Subject: [PATCH 19/51] 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, 7 insertions(+), 1 deletion(-) diff --git a/src/strings/de.json b/src/strings/de.json index f0b8c994c..4c71a71e7 100644 --- a/src/strings/de.json +++ b/src/strings/de.json @@ -1729,5 +1729,11 @@ "MenuClose": "Menü schließen", "UserMenu": "Benutzermenü", "Studio": "Studio", - "AllowCollectionManagement": "Dieser Benutzer darf Sammlungen verwalten" + "AllowCollectionManagement": "Dieser Benutzer darf Sammlungen verwalten", + "PasswordRequiredForAdmin": "Für Admin Konten wird ein Passwort benötigt.", + "LabelEnableLUFSScan": "LUFS scan aktivieren", + "LabelSyncPlayNoGroups": "Keine Gruppen verfügbar", + "LabelEnableLUFSScanHelp": "LUFS scan für Musik aktivieren (Dies erfodert mehr Zeit und Ressourcen)", + "Notifications": "Benachrichtigungen", + "NotificationsMovedMessage": "Die Benachrichtigungsfunktion wurde zum Webhook Plugin verschoben." } From 97d1f71057c0672ce377eba31d7042e4b5bcd1ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Fern=C3=A1ndez=20Alcoba?= Date: Mon, 26 Jun 2023 07:48:11 +0000 Subject: [PATCH 20/51] Translated using Weblate (Spanish) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/es/ --- src/strings/es.json | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/strings/es.json b/src/strings/es.json index 0ccb65dba..88bbe9ad5 100644 --- a/src/strings/es.json +++ b/src/strings/es.json @@ -1695,7 +1695,7 @@ "SaveRecordingImages": "Guardar grabación de imágenes EPG", "SaveRecordingImagesHelp": "Guardar imágenes del proveedor de listados EPG junto con los archivos multimedia.", "LabelDummyChapterDuration": "Intervalo", - "LabelDummyChapterDurationHelp": "Intervalo de extracción de imágenes de los capítulos en segundos.", + "LabelDummyChapterDurationHelp": "Intervalo entre capítulos dummy. Dejar a 0 para deshabilitar la generación de capítulos dummy. Cambiar esta opción no tendrá efecto para capítulos dummy ya existentes.", "HeaderDummyChapter": "Imágenes de capítulos", "LabelDummyChapterCount": "Límite", "Featurette": "Reportaje extra", @@ -1704,7 +1704,7 @@ "SecondarySubtitles": "Subtítulos secundarios", "LabelParallelImageEncodingLimit": "Límite de codificación de imágenes en paralelo", "LabelEnableAudioVbr": "Habilitar codificación de audio VBR", - "LabelChapterImageResolutionHelp": "Resolución de las imágenes de los capítulos extraídos.", + "LabelChapterImageResolutionHelp": "Resolución de las imágenes extraídas de los capítulos. Cambiar esta opción no tendrá efecto sobre capítulos dummy ya existentes.", "PreferEmbeddedExtrasTitlesOverFileNames": "Prefiere títulos incrustados sobre nombres de archivo para extras", "LabelDummyChapterCountHelp": "Número máximo de imágenes de capítulos que se extraerán para cada archivo multimedia.", "LabelEnableAudioVbrHelp": "La tasa de bits variable ofrece una mejor relación entre calidad y tasa de bits promedio, pero en algunos casos raros puede causar problemas de almacenamiento de búfer y compatibilidad.", @@ -1729,5 +1729,14 @@ "MenuClose": "Cerrar Menú", "UserMenu": "Menú de Usuario", "Studio": "Estudio", - "AllowCollectionManagement": "Permitir que este usuario administre colecciones" + "AllowCollectionManagement": "Permitir que este usuario administre colecciones", + "EnableAudioNormalizationHelp": "La normalización de audio añadirá una ganancia constante para mantener la media en el nivel deseado (-18dB).", + "LabelEnableLUFSScan": "Habilitar escaneo LUFS", + "PasswordRequiredForAdmin": "Se requiere contraseña para las cuentas de administrador.", + "GetThePlugin": "Obtener el Plugin", + "NotificationsMovedMessage": "La funcionalidad de notificaciones se ha movido al plugin Webhook.", + "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)." } From 67457228cc1e5bac1754e71649691287f2b0cf7b Mon Sep 17 00:00:00 2001 From: Ruben Domingues Date: Mon, 26 Jun 2023 11:23:28 +0000 Subject: [PATCH 21/51] Translated using Weblate (Portuguese (Portugal)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/pt_PT/ --- src/strings/pt-pt.json | 54 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/src/strings/pt-pt.json b/src/strings/pt-pt.json index dccfd8975..8e65c2eb1 100644 --- a/src/strings/pt-pt.json +++ b/src/strings/pt-pt.json @@ -607,7 +607,7 @@ "XmlDocumentAttributeListHelp": "Estes atributos são aplicados ao elemento principal de cada resposta XML.", "AccessRestrictedTryAgainLater": "O acesso está atualmente restrito. Por favor, tente mais tarde.", "AddToCollection": "Adicionar à coleção", - "AddToPlayQueue": "Adicionar à fila de reprodução", + "AddToPlayQueue": "Adicionar à lista de reprodução", "AddedOnValue": "Adicionado {0}", "AirDate": "Data de estreia", "Albums": "Álbuns", @@ -1525,7 +1525,7 @@ "DirectPlayHelp": "O ficheiro de origem é totalmente compatível com este cliente e a sessão está a receber o ficheiro sem modificações.", "Conductor": "Maestro", "Arranger": "Organizador", - "AgeValue": "({0} anos de idade)", + "AgeValue": "({0} anos)", "LabelSyncPlaySettingsSpeedToSync": "Ativar SpeedToSync", "LabelAutomaticallyAddToCollection": "Adicionar automaticamente à colecção", "ItemDetails": "Detalhes", @@ -1677,5 +1677,53 @@ "BehindTheScenes": "Nos bastidores", "DownloadAll": "Transferir Todas", "MessageNoItemsAvailable": "Nenhum item disponível atualmente.", - "MessageNoFavoritesAvailable": "Nenhum favorito disponível atualmente." + "MessageNoFavoritesAvailable": "Nenhum favorito disponível atualmente.", + "SubtitleBlack": "Preto", + "Short": "Curta-metragem", + "PasswordRequiredForAdmin": "A password é obrigatória para contas de administrador.", + "PreferEmbeddedExtrasTitlesOverFileNames": "Prefira títulos incorporados em vez de nomes de ficheiros para extras", + "SubtitleGreen": "Verde", + "SubtitleLightGray": "Cinzento Claro", + "SubtitleMagenta": "Magenta", + "SubtitleRed": "Vermelho", + "SubtitleYellow": "Amarelo", + "Featurette": "Featurette", + "Studio": "Estúdio", + "StereoDownmixAlgorithmHelp": "Algoritmo usado para fazer downmix de áudio multicanal para estéreo.", + "Unreleased": "Ainda não foi lançado", + "SubtitleBlue": "Azul", + "SubtitleCyan": "Ciano", + "LabelSyncPlayNoGroups": "Não há grupos disponíveis", + "Select": "Selecciona", + "AllowCollectionManagement": "Permitir este utilizador gerir as coleções", + "SecondarySubtitles": "Legendas secundárias", + "LabelDummyChapterDuration": "Intervalo", + "LabelDummyChapterDurationHelp": "O intervalo entre os capítulos fictícios. Defina como 0 para desativar a criação de capítulo fictício. Alterar isso não terá efeito nos capítulos fictícios existentes.", + "LabelChapterImageResolution": "Resolução", + "LabelChapterImageResolutionHelp": "A resolução das imagens do capítulo extraídas. Alterar isso não terá efeito nos capítulos fictícios existentes.", + "LabelParallelImageEncodingLimit": "Limite de codificação paralela de imagens", + "LabelParallelImageEncodingLimitHelp": "Quantidade máxima de codificações de imagem permitidas para execução em paralelo. Definir como 0 escolherá um limite com base nas especificações do sistema.", + "GetThePlugin": "Obter Plugin", + "Notifications": "Notificações", + "NotificationsMovedMessage": "A funcionalidade de notificações foi movida para o plugin Webhook.", + "LabelEnableAudioVbr": "Ativar codificação do áudio VBR", + "ResolutionMatchSource": "Correspondente ao original", + "MenuOpen": "Abrir menu", + "MenuClose": "Fechar menu", + "SubtitleGray": "Cinzento", + "SubtitleWhite": "Branco", + "UserMenu": "Menu de utilizador", + "HeaderPerformance": "Desempenho", + "EnableAudioNormalizationHelp": "A normalização do áudio adicionará um ganho constante para manter a média no nível desejado (-18dB).", + "EnableAudioNormalization": "Normalização de áudio", + "HeaderRecordingMetadataSaving": "Gravar metadados", + "OptionDateEpisodeAdded": "Data de adição do episódio", + "LabelEnableLUFSScanHelp": "Ative a verificação LUFS para música (isso levará mais tempo e mais recursos).", + "LabelEnableLUFSScan": "Ative a verificação LUFS", + "HeaderDummyChapter": "Imagens dos Capítulos", + "LabelEnableAudioVbrHelp": "A taxa de bits variável oferece melhor qualidade relativamente à taxa de bits média, mas, em alguns casos raros, pode causar problemas de ‘buffer’ e compatibilidade.", + "Experimental": "Experimental", + "MessageRenameMediaFolder": "Renomear uma biblioteca de multimédia fará com que todos os metadados sejam perdidos, proceda com cautela.", + "EnableCardLayout": "Mostrar em formato mosaico", + "OptionDateShowAdded": "Data de adição da Série" } From eca79feb3efe60ab3d6939e1a5293dce3ab93f01 Mon Sep 17 00:00:00 2001 From: Marcinbar Date: Tue, 27 Jun 2023 08:15:14 +0000 Subject: [PATCH 22/51] Translated using Weblate (Polish) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/pl/ --- src/strings/pl.json | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/strings/pl.json b/src/strings/pl.json index e2fb9e1ec..bbc918808 100644 --- a/src/strings/pl.json +++ b/src/strings/pl.json @@ -1693,10 +1693,10 @@ "SaveRecordingNFOHelp": "Zapisz metadane od dostawcy list EPG wraz z mediami.", "HeaderDummyChapter": "Obrazy rozdziału", "LabelDummyChapterDuration": "Interwał", - "LabelDummyChapterDurationHelp": "Interwał ekstrakcji obrazu rozdziału w sekundach.", + "LabelDummyChapterDurationHelp": "Odstęp między fikcyjnymi rozdziałami. Ustaw na 0, aby wyłączyć generowanie fikcyjnych rozdziałów. Zmiana tego nie będzie miała wpływu na istniejące fikcyjne rozdziały.", "LabelDummyChapterCount": "Ograniczenie", "LabelChapterImageResolution": "Rozdzielczość", - "LabelChapterImageResolutionHelp": "Rozdzielczość wyodrębnionych obrazów rozdziałów.", + "LabelChapterImageResolutionHelp": "Rozdzielczość wyodrębnionych obrazów rozdziałów. Zmiana tego ustawienia nie ma wpływu na istniejące rozdziały.", "ResolutionMatchSource": "Źródło dopasowania", "SaveRecordingNFO": "Zapisz nagrane metadane EPG w NFO", "SaveRecordingImages": "Zapisywanie obrazów EPG", @@ -1734,5 +1734,9 @@ "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).", - "Notifications": "Powiadomienia" + "Notifications": "Powiadomienia", + "PasswordRequiredForAdmin": "Hasło jest wymagane do kont administratorów.", + "LabelSyncPlayNoGroups": "Brak dostępnych grup", + "GetThePlugin": "Pobierz wtyczkę", + "NotificationsMovedMessage": "Funkcjonalność powiadomień została przeniesiona do wtyczki Webhook." } From efa5da85f4b647e95dc6d320cbf9e678434fc38b Mon Sep 17 00:00:00 2001 From: Murat Hasar Date: Tue, 27 Jun 2023 19:04:36 +0000 Subject: [PATCH 23/51] Translated using Weblate (Turkish) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/tr/ --- src/strings/tr.json | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/strings/tr.json b/src/strings/tr.json index cf91a1e59..3f2674cfd 100644 --- a/src/strings/tr.json +++ b/src/strings/tr.json @@ -269,7 +269,7 @@ "AlwaysPlaySubtitlesHelp": "Dil tercihi ile eşleşen altyazılar, ses diline bakılmaksızın yüklenir.", "AnyLanguage": "Herhangi bir dil", "Anytime": "İstediğin zaman", - "AroundTime": "Civarında", + "AroundTime": "Civarında {0}", "Art": "Temiz sanat", "AsManyAsPossible": "Mümkün olduğunca çok", "Ascending": "Artan", @@ -881,7 +881,7 @@ "Menu": "Menü", "EnableBlurHashHelp": "Hala yüklenmekte olan resimler benzersiz bir yer tutucuyla görüntülenecektir.", "EnableBlurHash": "Resimler için bulanık yer tutucuları etkinleştir", - "AllowTonemappingHelp": "Ton eşleme, orijinal sahneyi temsil etmek için çok önemli bilgiler olan görüntü ayrıntılarını ve renkleri korurken bir videonun dinamik aralığını HDR'den SDR'ye dönüştürebilir. Şu anda yalnızca HDR10 veya HLG videolar ile çalışır. İlgili OpenCL veya CUDA çalışma zamanını gerektirir.", + "AllowTonemappingHelp": "Ton eşleme, orijinal sahneyi temsil etmek için çok önemli bilgiler olan görüntü ayrıntılarını ve renkleri korurken bir videonun dinamik aralığını HDR'den SDR'ye dönüştürebilir. Şu anda yalnızca 10bit HDR10,HLG ve DoVi videolarla çalışmaktadır. Bunun için ilgili OpenCL veya CUDA çalışma zamanı gerekir.", "LabelAutomaticDiscovery": "Otomatik Keşfetmeyi Etkinleştir", "LabelAutoDiscoveryTracingHelp": "Etkinleştirildiğinde, otomatik keşfetme bağlantı noktasına gelen paketler günlüğe kaydedilir.", "LabelAutoDiscoveryTracing": "Otomatik Keşfetme izlemesini etkinleştirin.", @@ -1589,9 +1589,9 @@ "MediaInfoVideoRangeType": "Videonun aralık türü", "LabelVideoRangeType": "Videonun aralık türü", "VideoRangeTypeNotSupported": "Videonun aralık türü desteklenmiyor", - "LabelVppTonemappingContrastHelp": "VPP ton eşlemede kontrast kazancını uygulayın. Önerilen ve varsayılan değerler 1,2 ve 1'dir.", + "LabelVppTonemappingContrastHelp": "VPP ton eşlemesinde kontrast kazancı uygulayın. Hem önerilen hem de varsayılan değerler 1'dir.", "LabelVppTonemappingContrast": "VPP Ton eşleme kontrast kazancı", - "LabelVppTonemappingBrightnessHelp": "VPP ton eşlemesinde parlaklık kazancını uygulayın. Hem önerilen hem de varsayılan değerler 0'dır.", + "LabelVppTonemappingBrightnessHelp": "VPP ton eşlemesinde parlaklık kazancı uygulayın. Önerilen ve varsayılan değerler 16 ve 0'dır.", "LabelVppTonemappingBrightness": "VPP Ton eşleme parlaklık kazancı", "EnableSplashScreen": "Açılış ekranını etkinleştir", "EnableEnhancedNvdecDecoderHelp": "Deneysel NVDEC uyarlanması, kod çözme hatalarıyla karşılaşmadığınız sürece bu seçeneği etkinleştirmeyin.", @@ -1674,5 +1674,20 @@ "TabContainers": "Barındırıcılar", "OptionDateShowAdded": "Dizi Eklenme Tarihi", "OptionDateEpisodeAdded": "Bölüm Eklenme Tarihi", - "DownloadAll": "Hepsini indir" + "DownloadAll": "Hepsini indir", + "AllowCollectionManagement": "Bu kullanıcının koleksiyonları yönetmesine izin ver", + "Experimental": "Deneysel", + "Featurette": "Tanıtım", + "Select": "Seçiniz", + "EnableCardLayout": "Görsel Kart kutusunu göster", + "TonemappingModeHelp": "Ton eşleme modunu seçin. Vurguların patladığını görürseniz RGB moduna geçmeyi deneyin.", + "Unreleased": "Henüz yayınlanmadı", + "LabelTonemappingMode": "Ton eşleme modu", + "GetThePlugin": "Eklentiyi edinin", + "HeaderDummyChapter": "Bölüm Görüntüleri", + "HeaderPerformance": "Performans", + "EnableAudioNormalizationHelp": "Ses normalizasyonu, ortalamayı istenen seviyede (-18dB) tutmak için sabit bir kazanç ekleyecektir.", + "EnableAudioNormalization": "Ses Normalizasyonu", + "HeaderRecordingMetadataSaving": "Meta Verilerin Kaydedilmesi", + "Short": "Kısa" } From 72a7f0f3bc0ea01a15992dcd47e633cd176d65b1 Mon Sep 17 00:00:00 2001 From: Murat Hasar Date: Tue, 27 Jun 2023 21:17:20 +0000 Subject: [PATCH 24/51] Translated using Weblate (Turkish) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/tr/ --- src/strings/tr.json | 45 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/src/strings/tr.json b/src/strings/tr.json index 3f2674cfd..e1081d2b6 100644 --- a/src/strings/tr.json +++ b/src/strings/tr.json @@ -1148,7 +1148,7 @@ "HeaderContinueReading": "Okumaya devam et", "DisableCustomCss": "Sunucu tarafından sağlanan özel CSS kodunu devre dışı bırak", "DirectPlayHelp": "Kaynak dosyası bu istemci ile tamamen uyumlu ve oturum dosyayı değişiklik yapmadan alıyor.", - "OptionCaptionInfoExSamsung": "CaptionInfoEx (Samsung)", + "OptionCaptionInfoExSamsung": "Altyazı Bilgisi Ex (Samsung)", "OptionBluray": "BD", "LabelAutomaticallyAddToCollection": "Otomatik olarak koleksiyona ekle", "HeaderSyncPlayPlaybackSettings": "Oynatma", @@ -1689,5 +1689,46 @@ "EnableAudioNormalizationHelp": "Ses normalizasyonu, ortalamayı istenen seviyede (-18dB) tutmak için sabit bir kazanç ekleyecektir.", "EnableAudioNormalization": "Ses Normalizasyonu", "HeaderRecordingMetadataSaving": "Meta Verilerin Kaydedilmesi", - "Short": "Kısa" + "Short": "Kısa", + "LabelDummyChapterDurationHelp": "Taklit bölümler arasındaki aralık. Taklit bölüm oluşturmayı devre dışı bırakmak için 0 olarak ayarlayın. Bunu değiştirmenin mevcut taklit bölümler üzerinde hiçbir etkisi olmayacaktır.", + "LabelChapterImageResolution": "Çözünürlük", + "ResolutionMatchSource": "Eşleşme Kaynağı", + "SaveRecordingImages": "EPG görüntülerini kaydet", + "SubtitleBlack": "Siyah", + "SubtitleCyan": "Cam göbeği", + "SubtitleRed": "Kırmızı", + "PasswordRequiredForAdmin": "Yönetici hesapları için bir parola gereklidir.", + "SaveRecordingImagesHelp": "EPG listeleme sağlayıcısından görüntüleri medyanın yanına kaydedin.", + "StereoDownmixAlgorithmHelp": "Çok kanallı sesi stereoya düşürmek için kullanılan algoritma.", + "SubtitleGray": "Gri", + "SubtitleMagenta": "Eflatun", + "SubtitleYellow": "Sarı", + "UserMenu": "Kullanıcı Menüsü", + "Studio": "Stüdyo", + "PreferEmbeddedExtrasTitlesOverFileNamesHelp": "Ekstralar genellikle ana adla aynı gömülü ada sahiptir, yine de gömülü başlıkları kullanmak için bunu kontrol edin.", + "PreferEmbeddedExtrasTitlesOverFileNames": "Ekstralar için dosya adları yerine gömülü başlıkları tercih edin", + "MessageNoItemsAvailable": "Şu anda hiçbir ürün mevcut değildir.", + "LabelSyncPlayNoGroups": "Mevcut grup yok", + "MessageNoFavoritesAvailable": "Şu anda hiçbir favori mevcut değildir.", + "SaveRecordingNFO": "EPG meta verilerini NFO'ya kaydet", + "SaveRecordingNFOHelp": "EPG listeleme sağlayıcısından gelen meta verileri medya ile birlikte kaydedin.", + "SubtitleBlue": "Mavi", + "SubtitleGreen": "Yeşil", + "SubtitleLightGray": "Açık Gri", + "SubtitleWhite": "Beyaz", + "SecondarySubtitles": "İkincil Altyazılar", + "LabelStereoDownmixAlgorithm": "Stereo Karıştırma Algoritması", + "LabelEnableAudioVbr": "VBR ses kodlamasını etkinleştir", + "MenuClose": "Menüyü Kapat", + "MessageRenameMediaFolder": "Bir medya kitaplığını yeniden adlandırmak tüm meta verilerin kaybolmasına neden olacaktır, dikkatli olun.", + "Notifications": "Bildirimler", + "NotificationsMovedMessage": "Bildirim işlevi Webhook eklentisine taşındı.", + "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).", + "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ı", + "LabelParallelImageEncodingLimitHelp": "Paralel olarak çalışmasına izin verilen maksimum görüntü kodlaması miktarı. Bunu 0 olarak ayarlamak, sistem özelliklerinize göre bir sınır seçecektir." } From 132bc53b8647d8d5e9e73adc7d14554ecff513cd Mon Sep 17 00:00:00 2001 From: lucaperl Date: Thu, 29 Jun 2023 13:45:54 +0000 Subject: [PATCH 25/51] Translated using Weblate (German) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/de/ --- src/strings/de.json | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/strings/de.json b/src/strings/de.json index 4c71a71e7..e2c73c1b0 100644 --- a/src/strings/de.json +++ b/src/strings/de.json @@ -1700,7 +1700,7 @@ "HeaderDummyChapter": "Kapitel Bilder", "HeaderRecordingMetadataSaving": "Aufzeichnung von Metadaten", "LabelDummyChapterDuration": "Intervall", - "LabelDummyChapterDurationHelp": "Das Intervall für die Extraktion des Kapitelbildes in Sekunden.", + "LabelDummyChapterDurationHelp": "Das Intervall zwischen Dummy-Kapiteln. Auf 0 setzen, um die Erzeugung von Dummy-Kapiteln zu deaktivieren. Eine Änderung dieses Wertes hat keine Auswirkung auf bestehende Dummy-Kapitel.", "LabelDummyChapterCount": "Limit", "LabelDummyChapterCountHelp": "Die maximale Anzahl von Kapitelbildern, die für jede Mediendatei extrahiert werden.", "LabelChapterImageResolution": "Auflösung", @@ -1733,7 +1733,10 @@ "PasswordRequiredForAdmin": "Für Admin Konten wird ein Passwort benötigt.", "LabelEnableLUFSScan": "LUFS scan aktivieren", "LabelSyncPlayNoGroups": "Keine Gruppen verfügbar", - "LabelEnableLUFSScanHelp": "LUFS scan für Musik aktivieren (Dies erfodert mehr Zeit und Ressourcen)", + "LabelEnableLUFSScanHelp": "LUFS scan für Musik aktivieren (Dies erfodert mehr Zeit und Ressourcen).", "Notifications": "Benachrichtigungen", - "NotificationsMovedMessage": "Die Benachrichtigungsfunktion wurde zum Webhook Plugin verschoben." + "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).", + "EnableAudioNormalization": "Audionormalisierung", + "GetThePlugin": "Plugin laden" } From 5598f49c32ca7030d5fcd3df2350993508a0c7e8 Mon Sep 17 00:00:00 2001 From: grafixeyehero Date: Thu, 29 Jun 2023 23:17:59 +0300 Subject: [PATCH 26/51] fix resizeobserver loop --- package-lock.json | 62 +++++++++++++++++++ package.json | 1 + .../emby-scrollbuttons/ScrollButtons.tsx | 1 - src/elements/emby-scroller/Scroller.tsx | 29 ++++----- src/hooks/useElementSize.ts | 25 ++++++++ webpack.common.js | 3 + 6 files changed, 103 insertions(+), 18 deletions(-) create mode 100644 src/hooks/useElementSize.ts diff --git a/package-lock.json b/package-lock.json index 8bcb3c653..1f1d748f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "@loadable/component": "5.15.3", "@mui/icons-material": "5.11.16", "@mui/material": "5.13.3", + "@react-hook/resize-observer": "1.2.6", "@tanstack/react-query": "4.29.12", "@tanstack/react-query-devtools": "4.29.12", "blurhash": "2.0.5", @@ -2980,6 +2981,11 @@ "@jridgewell/sourcemap-codec": "1.4.14" } }, + "node_modules/@juggle/resize-observer": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz", + "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==" + }, "node_modules/@leichtgewicht/ip-codec": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", @@ -3375,6 +3381,35 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@react-hook/latest": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@react-hook/latest/-/latest-1.0.3.tgz", + "integrity": "sha512-dy6duzl+JnAZcDbNTfmaP3xHiKtbXYOaz3G51MGVljh548Y8MWzTr+PHLOfvpypEVW9zwvl+VyKjbWKEVbV1Rg==", + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/@react-hook/passive-layout-effect": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@react-hook/passive-layout-effect/-/passive-layout-effect-1.2.1.tgz", + "integrity": "sha512-IwEphTD75liO8g+6taS+4oqz+nnroocNfWVHWz7j+N+ZO2vYrc6PV1q7GQhuahL0IOR7JccFTsFKQ/mb6iZWAg==", + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/@react-hook/resize-observer": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@react-hook/resize-observer/-/resize-observer-1.2.6.tgz", + "integrity": "sha512-DlBXtLSW0DqYYTW3Ft1/GQFZlTdKY5VAFIC4+km6IK5NiPPDFchGbEJm1j6pSgMqPRHbUQgHJX7RaR76ic1LWA==", + "dependencies": { + "@juggle/resize-observer": "^3.3.1", + "@react-hook/latest": "^1.0.2", + "@react-hook/passive-layout-effect": "^1.2.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, "node_modules/@remix-run/router": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.6.1.tgz", @@ -22455,6 +22490,11 @@ "@jridgewell/sourcemap-codec": "1.4.14" } }, + "@juggle/resize-observer": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz", + "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==" + }, "@leichtgewicht/ip-codec": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", @@ -22677,6 +22717,28 @@ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.7.tgz", "integrity": "sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw==" }, + "@react-hook/latest": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@react-hook/latest/-/latest-1.0.3.tgz", + "integrity": "sha512-dy6duzl+JnAZcDbNTfmaP3xHiKtbXYOaz3G51MGVljh548Y8MWzTr+PHLOfvpypEVW9zwvl+VyKjbWKEVbV1Rg==", + "requires": {} + }, + "@react-hook/passive-layout-effect": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@react-hook/passive-layout-effect/-/passive-layout-effect-1.2.1.tgz", + "integrity": "sha512-IwEphTD75liO8g+6taS+4oqz+nnroocNfWVHWz7j+N+ZO2vYrc6PV1q7GQhuahL0IOR7JccFTsFKQ/mb6iZWAg==", + "requires": {} + }, + "@react-hook/resize-observer": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@react-hook/resize-observer/-/resize-observer-1.2.6.tgz", + "integrity": "sha512-DlBXtLSW0DqYYTW3Ft1/GQFZlTdKY5VAFIC4+km6IK5NiPPDFchGbEJm1j6pSgMqPRHbUQgHJX7RaR76ic1LWA==", + "requires": { + "@juggle/resize-observer": "^3.3.1", + "@react-hook/latest": "^1.0.2", + "@react-hook/passive-layout-effect": "^1.2.0" + } + }, "@remix-run/router": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.6.1.tgz", diff --git a/package.json b/package.json index fe5c1b6d9..0741a67a2 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,7 @@ "@loadable/component": "5.15.3", "@mui/icons-material": "5.11.16", "@mui/material": "5.13.3", + "@react-hook/resize-observer": "1.2.6", "@tanstack/react-query": "4.29.12", "@tanstack/react-query-devtools": "4.29.12", "blurhash": "2.0.5", diff --git a/src/elements/emby-scrollbuttons/ScrollButtons.tsx b/src/elements/emby-scrollbuttons/ScrollButtons.tsx index 6f4bce382..aaeb9bc23 100644 --- a/src/elements/emby-scrollbuttons/ScrollButtons.tsx +++ b/src/elements/emby-scrollbuttons/ScrollButtons.tsx @@ -10,7 +10,6 @@ enum Direction { } interface ScrollButtonsProps { - scrollRef?: React.MutableRefObject; scrollerFactoryRef: React.MutableRefObject; scrollState: { scrollSize: number; diff --git a/src/elements/emby-scroller/Scroller.tsx b/src/elements/emby-scroller/Scroller.tsx index 773d184c5..57349bad4 100644 --- a/src/elements/emby-scroller/Scroller.tsx +++ b/src/elements/emby-scroller/Scroller.tsx @@ -1,5 +1,6 @@ import React, { FC, useCallback, useEffect, useRef, useState } from 'react'; import classNames from 'classnames'; +import useElementSize from 'hooks/useElementSize'; import layoutManager from '../../components/layoutManager'; import dom from '../../scripts/dom'; import browser from '../../scripts/browser'; @@ -32,14 +33,14 @@ const Scroller: FC = ({ isAllowNativeSmoothScrollEnabled, children }) => { + const [scrollRef, size] = useElementSize(); + const [showControls, setShowControls] = useState(false); const [scrollState, setScrollState] = useState({ - scrollSize: 0, + scrollSize: size.width, scrollPos: 0, scrollWidth: 0 }); - - const scrollRef = useRef(null); const scrollerFactoryRef = useRef(null); const getScrollSlider = useCallback(() => { @@ -125,7 +126,7 @@ const Scroller: FC = ({ }); }, [getScrollPosition, getScrollSize, getScrollWidth]); - const initCenterFocus = useCallback((elem: EventTarget, scrollerInstance: scrollerFactory) => { + const initCenterFocus = useCallback((elem, scrollerInstance: scrollerFactory) => { dom.addEventListener(elem, 'focus', function (e: FocusEvent) { const focused = focusManager.focusableParent(e.target); if (focused) { @@ -150,15 +151,10 @@ const Scroller: FC = ({ }, [scrollerFactoryRef]); useEffect(() => { - const scrollerElement = scrollRef.current as HTMLDivElement; - const horizontal = isHorizontalEnabled !== false; const scrollbuttons = isScrollButtonsEnabled !== false; const mousewheel = isMouseWheelEnabled !== false; - const slider = scrollerElement.querySelector('.scrollSlider'); - - const scrollFrame = scrollerElement; const enableScrollButtons = layoutManager.desktop && horizontal && scrollbuttons; const options = { @@ -166,7 +162,7 @@ const Scroller: FC = ({ mouseDragging: 1, mouseWheel: mousewheel, touchDragging: 1, - slidee: slider, + slidee: scrollRef.current?.querySelector('.scrollSlider'), scrollBy: 200, speed: horizontal ? 270 : 240, elasticBounds: 1, @@ -183,12 +179,12 @@ const Scroller: FC = ({ }; // If just inserted it might not have any height yet - yes this is a hack - scrollerFactoryRef.current = new scrollerFactory(scrollFrame, options); + scrollerFactoryRef.current = new scrollerFactory(scrollRef.current, options); scrollerFactoryRef.current.init(); scrollerFactoryRef.current.reload(); if (layoutManager.tv && isCenterFocusEnabled) { - initCenterFocus(scrollerElement, scrollerFactoryRef.current); + initCenterFocus(scrollRef.current, scrollerFactoryRef.current); } if (enableScrollButtons) { @@ -200,9 +196,8 @@ const Scroller: FC = ({ } return () => { - const scrollerInstance = scrollerFactoryRef.current; - if (scrollerInstance) { - scrollerInstance.destroy(); + if (scrollerFactoryRef.current) { + scrollerFactoryRef.current.destroy(); scrollerFactoryRef.current = null; } @@ -223,7 +218,8 @@ const Scroller: FC = ({ isScrollEventEnabled, isSkipFocusWhenVisibleEnabled, onScroll, - removeScrollEventListener + removeScrollEventListener, + scrollRef ]); return ( @@ -231,7 +227,6 @@ const Scroller: FC = ({ { showControls && scrollState.scrollWidth > scrollState.scrollSize + 20 && diff --git a/src/hooks/useElementSize.ts b/src/hooks/useElementSize.ts new file mode 100644 index 000000000..aeb7000ee --- /dev/null +++ b/src/hooks/useElementSize.ts @@ -0,0 +1,25 @@ +import { MutableRefObject, useLayoutEffect, useRef, useState } from 'react'; +import useResizeObserver from '@react-hook/resize-observer'; + +interface Size { + width: number; + height: number; +} + +export default function useElementSize< + T extends HTMLElement = HTMLDivElement +>(): [MutableRefObject, Size] { + const target = useRef(null); + const [size, setSize] = useState({ + width: 0, + height: 0 + }); + + useLayoutEffect(() => { + target.current && setSize(target.current.getBoundingClientRect()); + }, [target]); + + useResizeObserver(target, (entry) => setSize(entry.contentRect)); + + return [target, size]; +} diff --git a/webpack.common.js b/webpack.common.js index b1fccfd5f..c4b67af58 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -166,6 +166,9 @@ const config = { path.resolve(__dirname, 'node_modules/event-target-polyfill'), path.resolve(__dirname, 'node_modules/rvfc-polyfill'), path.resolve(__dirname, 'node_modules/@jellyfin/sdk'), + path.resolve(__dirname, 'node_modules/@react-hook/latest'), + path.resolve(__dirname, 'node_modules/@react-hook/passive-layout-effect'), + path.resolve(__dirname, 'node_modules/@react-hook/resize-observer'), path.resolve(__dirname, 'node_modules/@remix-run/router'), path.resolve(__dirname, 'node_modules/@tanstack/query-core'), path.resolve(__dirname, 'node_modules/@tanstack/react-query'), From a38ea09f3fdf5a398366f12efe5d7e65deaf7c2a Mon Sep 17 00:00:00 2001 From: Charles Ewert Date: Thu, 29 Jun 2023 22:39:03 -0400 Subject: [PATCH 27/51] remove unneeded comma --- src/strings/en-gb.json | 2 +- src/strings/en-us.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/strings/en-gb.json b/src/strings/en-gb.json index 938f0dc18..f6389ab25 100644 --- a/src/strings/en-gb.json +++ b/src/strings/en-gb.json @@ -1517,7 +1517,7 @@ "MessagePlaybackError": "There was an error playing this file on your Google Cast receiver.", "MessageChromecastConnectionError": "Your Google Cast receiver is unable to contact the Jellyfin server. Please check the connection and try again.", "Framerate": "Framerate", - "DirectPlayHelp": "The source file is entirely compatible with this client, and the session is receiving the file without modifications.", + "DirectPlayHelp": "The source file is entirely compatible with this client and the session is receiving the file without modifications.", "EnableGamepadHelp": "Listen for input from any connected controllers. (Requires: 'TV' Display Mode)", "LabelEnableGamepad": "Enable Gamepad", "Controls": "Controls", diff --git a/src/strings/en-us.json b/src/strings/en-us.json index d423c2ce3..424c3e79f 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -189,7 +189,7 @@ "Director": "Director", "Directors": "Directors", "DirectPlaying": "Direct playing", - "DirectPlayHelp": "The source file is entirely compatible with this client, and the session is receiving the file without modifications.", + "DirectPlayHelp": "The source file is entirely compatible with this client and the session is receiving the file without modifications.", "DirectStreamHelp1": "The video stream is compatible with the device, but has an incompatible audio format (DTS, Dolby TrueHD, etc.) or number of audio channels. The video stream will be repackaged losslessly on the fly before being sent to the device. Only the audio stream will be transcoded.", "DirectStreamHelp2": "Power consumed by direct streaming usually depends on the audio profile. Only the video stream is lossless.", "DirectStreaming": "Direct streaming", From 67132dc3cc746fbb2ddc1f412359eed76633dd02 Mon Sep 17 00:00:00 2001 From: Oskari Lavinto Date: Sat, 1 Jul 2023 04:19:53 +0000 Subject: [PATCH 28/51] Translated using Weblate (Finnish) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fi/ --- src/strings/fi.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings/fi.json b/src/strings/fi.json index d0093b9fe..c04d8bdb7 100644 --- a/src/strings/fi.json +++ b/src/strings/fi.json @@ -1259,7 +1259,7 @@ "Framerate": "Virkistystaajuus", "DisablePlugin": "Poista käytöstä", "EnablePlugin": "Ota käyttöön", - "DirectPlayHelp": "Lähdetiedosto on täysin yhteensopiva päätesovelluksen kanssa ja istunto vastaanottaa tiedoston ilman muuntoa.", + "DirectPlayHelp": "Lähdetiedosto on täysin yhteensopiva tämän päätesovelluksen kanssa ja istunto vastaanottaa tiedoston ilman muuntoa.", "LabelMaxStreamingBitrateHelp": "Määritä suoratoiston enimmäisbittinopeus.", "LabelMinAudiobookResumeHelp": "Kohteita pidetään toistamattomina, jos toisto keskeytetään ennen tätä aikaa.", "LabelMaxStreamingBitrate": "Suoratoiston enimmäislaatu", From a0fa46b3fd89bcfea3e1be561f3dc985d957df8b Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Sun, 2 Jul 2023 01:55:57 -0400 Subject: [PATCH 29/51] Backport pull request #4627 from jellyfin/release-10.8.z Fix overlap of slider bubble Original-merge: 35a7dfbed68f6681d10423f44774401975e5ff3d Merged-by: Bill Thornton Backported-by: Bill Thornton --- src/elements/emby-slider/emby-slider.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/elements/emby-slider/emby-slider.scss b/src/elements/emby-slider/emby-slider.scss index a1d690b0f..c0f7d5e74 100644 --- a/src/elements/emby-slider/emby-slider.scss +++ b/src/elements/emby-slider/emby-slider.scss @@ -230,6 +230,7 @@ display: flex; align-items: center; justify-content: center; + z-index: 1; } .sliderBubbleText { From 2ce99884989ee7c59c7f316d75bbc59f247f4476 Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Sun, 2 Jul 2023 02:01:42 -0400 Subject: [PATCH 30/51] Backport pull request #4628 from jellyfin/release-10.8.z Fix initial state of volume slider Original-merge: 62246fe0a93cd32db0dce051791e114df37d8a9a Merged-by: Bill Thornton Backported-by: Bill Thornton --- src/elements/emby-slider/emby-slider.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/elements/emby-slider/emby-slider.js b/src/elements/emby-slider/emby-slider.js index bdba41638..16c52f586 100644 --- a/src/elements/emby-slider/emby-slider.js +++ b/src/elements/emby-slider/emby-slider.js @@ -382,6 +382,8 @@ EmbySliderPrototype.attachedCallback = function () { } else { startInterval(this); } + + updateValues.call(this); }; /** From b372953671290608e31d093a6516fae4c4f71103 Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Sun, 2 Jul 2023 02:06:24 -0400 Subject: [PATCH 31/51] Backport pull request #4654 from jellyfin/release-10.8.z Add confirmation for 3rd party repos Original-merge: 331fa87216904dcc853a1112ee6c774d89765ed2 Merged-by: Bill Thornton Backported-by: Bill Thornton --- .../dashboard/plugins/add/index.html | 4 +- .../dashboard/plugins/add/index.js | 16 +++++++- .../dashboard/plugins/repositories/index.js | 37 +++++++++++++++---- src/strings/en-us.json | 9 ++++- 4 files changed, 54 insertions(+), 12 deletions(-) diff --git a/src/controllers/dashboard/plugins/add/index.html b/src/controllers/dashboard/plugins/add/index.html index 7f8330460..2be40c620 100644 --- a/src/controllers/dashboard/plugins/add/index.html +++ b/src/controllers/dashboard/plugins/add/index.html @@ -34,7 +34,9 @@
-

+

${LabelDeveloper}:

+

${LabelRepositoryName}:

+

${LabelRepositoryUrl}:

diff --git a/src/controllers/dashboard/plugins/add/index.js b/src/controllers/dashboard/plugins/add/index.js index 62ae70ab4..ca19d3f4d 100644 --- a/src/controllers/dashboard/plugins/add/index.js +++ b/src/controllers/dashboard/plugins/add/index.js @@ -64,6 +64,16 @@ function renderPackage(pkg, installedPlugins, page) { $('#description', page).text(pkg.description); $('#developer', page).text(pkg.owner); + // This is a hack; the repository name and URL should be part of the global values + // for the plugin, not each individual version. So we just use the top (latest) + // version to get this information. If it's missing (no versions), then say so. + if (pkg.versions.length) { + $('#repositoryName', page).text(pkg.versions[0].repositoryName); + $('#repositoryUrl', page).text(pkg.versions[0].repositoryUrl); + } else { + $('#repositoryName', page).text(globalize.translate('Unknown')); + $('#repositoryUrl', page).text(globalize.translate('Unknown')); + } if (installedPlugin) { const currentVersionText = globalize.translate('MessageYouHaveVersionInstalled', '' + installedPlugin.Version + ''); @@ -80,7 +90,7 @@ function alertText(options) { } function performInstallation(page, name, guid, version) { - const developer = $('#developer', page).html().toLowerCase(); + const repositoryUrl = $('#repositoryUrl', page).html().toLowerCase(); const alertCallback = function () { loading.show(); @@ -93,7 +103,9 @@ function performInstallation(page, name, guid, version) { }); }; - if (developer !== 'jellyfin') { + // Check the repository URL for the official Jellyfin repository domain, or + // present the warning for 3rd party plugins. + if (!repositoryUrl.startsWith('https://repo.jellyfin.org/')) { loading.hide(); let msg = globalize.translate('MessagePluginInstallDisclaimer'); msg += '
'; diff --git a/src/controllers/dashboard/plugins/repositories/index.js b/src/controllers/dashboard/plugins/repositories/index.js index 14ceff290..3c83b6e39 100644 --- a/src/controllers/dashboard/plugins/repositories/index.js +++ b/src/controllers/dashboard/plugins/repositories/index.js @@ -2,6 +2,7 @@ import loading from '../../../../components/loading/loading'; import libraryMenu from '../../../../scripts/libraryMenu'; import globalize from '../../../../scripts/globalize'; import dialogHelper from '../../../../components/dialogHelper/dialogHelper'; +import confirm from '../../../../components/confirm/confirm'; import '../../../../elements/emby-button/emby-button'; import '../../../../elements/emby-checkbox/emby-checkbox'; @@ -166,14 +167,36 @@ export default function(view) { dialog.querySelector('.newPluginForm').addEventListener('submit', e => { e.preventDefault(); - repositories.push({ - Name: dialog.querySelector('#txtRepositoryName').value, - Url: dialog.querySelector('#txtRepositoryUrl').value, - Enabled: true - }); + const repositoryUrl = dialog.querySelector('#txtRepositoryUrl').value.toLowerCase(); + + const alertCallback = function () { + repositories.push({ + Name: dialog.querySelector('#txtRepositoryName').value, + Url: dialog.querySelector('#txtRepositoryUrl').value, + Enabled: true + }); + saveList(view); + dialogHelper.close(dialog); + }; + + // Check the repository URL for the official Jellyfin repository domain, or + // present the warning for 3rd party plugins. + if (!repositoryUrl.startsWith('https://repo.jellyfin.org/')) { + let msg = globalize.translate('MessageRepositoryInstallDisclaimer'); + msg += '
'; + msg += '
'; + msg += globalize.translate('PleaseConfirmRepositoryInstallation'); + + confirm(msg, globalize.translate('HeaderConfirmRepositoryInstallation')).then(function () { + alertCallback(); + }).catch(() => { + console.debug('repository not installed'); + dialogHelper.close(dialog); + }); + } else { + alertCallback(); + } - saveList(view); - dialogHelper.close(dialog); return false; }); diff --git a/src/strings/en-us.json b/src/strings/en-us.json index 424c3e79f..4bb461d21 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -337,6 +337,7 @@ "HeaderCodecProfileHelp": "Codec profiles indicate the limitations of a device when playing specific codecs. If a limitation applies then the media will be transcoded, even if the codec is configured for direct playback.", "HeaderConfigureRemoteAccess": "Set up Remote Access", "HeaderConfirmPluginInstallation": "Confirm Plugin Installation", + "HeaderConfirmRepositoryInstallation": "Confirm Plugin Repository Installation", "HeaderConfirmProfileDeletion": "Confirm Profile Deletion", "HeaderConfirmRevokeApiKey": "Revoke API Key", "HeaderConnectionFailure": "Connection Failure", @@ -612,6 +613,7 @@ "LabelDefaultUser": "Default user", "LabelDefaultUserHelp": "Determine which user library should be displayed on connected devices. This can be overridden for each device using profiles.", "LabelDeinterlaceMethod": "Deinterlacing method", + "LabelDeveloper": "Developer", "LabelDeviceDescription": "Device description", "LabelDidlMode": "DIDL mode", "LabelDisableCustomCss": "Disable custom CSS code for theming/branding provided from the server.", @@ -1099,11 +1101,12 @@ "MessagePleaseEnsureInternetMetadata": "Please ensure downloading of internet metadata is enabled.", "MessagePleaseWait": "Please wait. This may take a minute.", "MessagePluginConfigurationRequiresLocalAccess": "To set up this plugin please sign in to your local server directly.", - "MessagePluginInstallDisclaimer": "Plugins built by community members are a great way to enhance your experience with additional features and benefits. Before installing, please be aware of the effects they may have on your server, such as longer library scans, additional background processing, and decreased system stability.", + "MessagePluginInstallDisclaimer": "WARNING: Installing a third party plugin carries risks. It may contain unstable or malicious code, and may change at any time. Only install plugins from authors that you trust, and please be aware of the potential effects it may have, including external service queries, longer library scans, or additional background processing.", "MessagePluginInstalled": "The plugin has been successfully installed. The server will need to be restarted for changes to take effect.", "MessagePluginInstallError": "An error occurred while installing the plugin.", "MessageReenableUser": "See below to reenable", "MessageRenameMediaFolder": "Renaming a media library will cause all metadata to be lost, proceed with caution.", + "MessageRepositoryInstallDisclaimer": "WARNING: Installing a third party plugin repository carries risks. It may contain unstable or malicious code, and may change at any time. Only install repositories from authors that you trust.", "MessageSent": "Message sent.", "MessageSyncPlayCreateGroupDenied": "Permission required to create a group.", "MessageSyncPlayDisabled": "SyncPlay disabled.", @@ -1318,6 +1321,7 @@ "PlayNextEpisodeAutomatically": "Play next episode automatically", "PleaseAddAtLeastOneFolder": "Please add at least one folder to this library by clicking the '+' button in 'Folders' section.", "PleaseConfirmPluginInstallation": "Please click OK to confirm you've read the above and wish to proceed with the plugin installation.", + "PleaseConfirmRepositoryInstallation": "Please click OK to confirm you've read the above and wish to proceed with the plugin repository installation.", "PleaseEnterNameOrId": "Please enter a name or an external ID.", "PleaseRestartServerName": "Please restart Jellyfin on {0}.", "PleaseSelectTwoItems": "Please select at least two items.", @@ -1710,5 +1714,6 @@ "MediaInfoDvBlSignalCompatibilityId": "DV bl signal compatibility id", "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." + "TonemappingModeHelp": "Select the tone mapping mode. If you experience blown out highlights try switching to the RGB mode.", + "Unknown": "Unknown" } From ba0acc6b048897ded25d5cb43fae6893e222d46c Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Sun, 2 Jul 2023 02:06:26 -0400 Subject: [PATCH 32/51] Backport pull request #4657 from jellyfin/release-10.8.z Fix xss in custom subtitles element Original-merge: 5cc91f2ee03d06f1fc3eda3e924b3e25c6f95170 Merged-by: Bill Thornton Backported-by: Bill Thornton --- src/plugins/htmlVideoPlayer/plugin.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js index e0a30cb6b..0ec83d636 100644 --- a/src/plugins/htmlVideoPlayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -1,3 +1,5 @@ +import DOMPurify from 'dompurify'; + import browser from '../../scripts/browser'; import { appHost } from '../../components/apphost'; import loading from '../../components/loading/loading'; @@ -1535,7 +1537,8 @@ export class HtmlVideoPlayer { } if (selectedTrackEvent && selectedTrackEvent.Text) { - subtitleTextElement.innerHTML = normalizeTrackEventText(selectedTrackEvent.Text, true); + subtitleTextElement.innerHTML = DOMPurify.sanitize( + normalizeTrackEventText(selectedTrackEvent.Text, true)); subtitleTextElement.classList.remove('hide'); } else { subtitleTextElement.classList.add('hide'); From 942926058b46850b12f6fa6caee81e81a39bf02c Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Sun, 2 Jul 2023 02:12:45 -0400 Subject: [PATCH 33/51] Backport pull request #4688 from jellyfin/release-10.8.z Disable ALAC on MacOS in non-Safari browsers Original-merge: 6304e27940e59d41ba9de41f9f0d5da7392cffbb Merged-by: Bill Thornton Backported-by: Bill Thornton --- src/scripts/browserDeviceProfile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripts/browserDeviceProfile.js b/src/scripts/browserDeviceProfile.js index f99e0ab34..c4623718b 100644 --- a/src/scripts/browserDeviceProfile.js +++ b/src/scripts/browserDeviceProfile.js @@ -121,7 +121,7 @@ function canPlayAudioFormat(format) { typeString = 'audio/ogg; codecs="opus"'; } else if (format === 'alac') { - if (browser.iOS || browser.osx) { + if (browser.iOS || browser.osx && browser.safari) { return true; } } else if (format === 'mp2') { From 33159544c4c37e5269622ff9155ed1ff75892815 Mon Sep 17 00:00:00 2001 From: Bas Date: Sun, 2 Jul 2023 13:12:39 +0000 Subject: [PATCH 34/51] Translated using Weblate (Dutch) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/nl/ --- src/strings/nl.json | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/strings/nl.json b/src/strings/nl.json index f824ca130..feece59d8 100644 --- a/src/strings/nl.json +++ b/src/strings/nl.json @@ -759,7 +759,7 @@ "MessagePleaseEnsureInternetMetadata": "Zorg ervoor dat het downloaden van internet-metadata is ingeschakeld.", "MessagePleaseWait": "Even geduld. Dit kan even duren.", "MessagePluginConfigurationRequiresLocalAccess": "Om deze plug-in te configuren moet je je direct op de lokale server aanmelden.", - "MessagePluginInstallDisclaimer": "Plug-ins ontwikkeld door leden van de gemeenschap zijn een geweldige manier om uw ervaring met extra functies en voordelen te verbeteren. Wees voor het installeren bewust van de effecten die zij op uw server kunnen hebben, zoals langere bibliotheekscans, meer achtergrondverwerking en een verminderde stabiliteit van het systeem.", + "MessagePluginInstallDisclaimer": "WAARSCHUWING: het installeren van een plug-in van derden brengt risico's met zich mee. De plug-in kan ieder moment veranderen en instabiele of kwaadaardige code bevatten. Installeer alleen plug-ins van auteurs die je vertrouwt en wees je bewust van de mogelijke gevolgen, zoals verbindingen met externe diensten, langer durende bibliotheekscans of meer achtergrondverwerking.", "MessageReenableUser": "Zie hieronder hoe opnieuw in te schakelen", "MessageTheFollowingLocationWillBeRemovedFromLibrary": "De volgende medialocaties worden verwijderd uit je bibliotheek", "MessageUnableToConnectToServer": "Het is momenteel niet mogelijk met de geselecteerde server te verbinden. Controleer of deze draait en probeer het opnieuw.", @@ -1737,5 +1737,10 @@ "LabelEnableLUFSScan": "LUFS-scan inschakelen", "LabelEnableLUFSScanHelp": "LUFS-scan voor muziek inschakelen. Dit duurt langer en is systeemintensief.", "PasswordRequiredForAdmin": "Voor beheerdersaccounts is een wachtwoord vereist.", - "LabelSyncPlayNoGroups": "Geen groepen beschikbaar" + "LabelSyncPlayNoGroups": "Geen groepen beschikbaar", + "HeaderConfirmRepositoryInstallation": "Installatie plug-in-repository bevestigen", + "LabelDeveloper": "Ontwikkelaar", + "MessageRepositoryInstallDisclaimer": "WAARSCHUWING: het installeren van een plug-in-repository van derden brengt risico's met zich mee. De repository kan ieder moment veranderen en instabiele of kwaadaardige code bevatten. Installeer alleen repository's van auteurs die je vertrouwt.", + "PleaseConfirmRepositoryInstallation": "Druk op OK als je bovenstaande gelezen hebt en wenst door te gaan met de installatie van de plug-in-repository.", + "Unknown": "Onbekend" } From b81f0a4775e87879134f1abbf0eb6cfb73b0892c Mon Sep 17 00:00:00 2001 From: Nicolas Viviani Date: Sun, 2 Jul 2023 17:36:51 +0000 Subject: [PATCH 35/51] Translated using Weblate (French) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fr/ --- src/strings/fr.json | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/strings/fr.json b/src/strings/fr.json index 7e412607e..2f0ace53f 100644 --- a/src/strings/fr.json +++ b/src/strings/fr.json @@ -804,7 +804,7 @@ "MessagePleaseEnsureInternetMetadata": "Veuillez vous assurer que le téléchargement des métadonnées depuis Internet est activé.", "MessagePleaseWait": "Veuillez patienter. Ceci peut prendre quelques minutes.", "MessagePluginConfigurationRequiresLocalAccess": "Pour configurer cette extension, veuillez vous connecter directement à votre serveur local.", - "MessagePluginInstallDisclaimer": "Les extensions développées par les membres de la communauté sont une excellente manière d'améliorer votre expérience avec de nouvelles fonctionnalités. Avant toute installation, veuillez prendre connaissance de l'impact qu'elles peuvent avoir sur le serveur, comme l'augmentation de la durée d'actualisation de la médiathèque, de nouvelles tâches de fond, ou un système moins stable.", + "MessagePluginInstallDisclaimer": "ATTENTION : Installer une extension tierce comporte des risques. Celle-ci peut contenir du code instable ou malveillant et peut être modifiée à tout moment. N'installez que des extensions provenant d'auteurs en qui vous avez confiance et soyez conscient des effets potentiels que cela peut avoir, requête vers des services externes, augmentation de la durée d'actualisation de la médiathèque, ou tâches de fonds additionnelles.", "MessageReenableUser": "Voir ci-dessous pour le réactiver", "MessageTheFollowingLocationWillBeRemovedFromLibrary": "Ces emplacements de médias vont être supprimés de votre médiathèque", "MessageUnableToConnectToServer": "Nous sommes dans l'impossibilité de nous connecter au serveur sélectionné. Veuillez vérifier qu'il est opérationnel et réessayez.", @@ -1508,7 +1508,7 @@ "Framerate": "Images par seconde", "DisablePlugin": "Désactiver", "EnablePlugin": "Activer", - "DirectPlayHelp": "Le fichier source est entièrement compatible avec le client et la session reçoit le fichier sans modifications.", + "DirectPlayHelp": "Le fichier source est entièrement compatible avec ce client et la session reçoit le fichier sans modifications.", "HeaderContinueReading": "Reprendre la lecture", "TextSent": "Message envoyé.", "EnableGamepadHelp": "Détecter le signal d'entrée de toute manette connectée. (Nécessite le mode d’affichage 'TV'.)", @@ -1697,7 +1697,7 @@ "LabelDummyChapterDurationHelp": "Intervalle entre deux chapitres factices ou 0 pour désactiver la génération. Changer la valeur n’affectera pas les chapitres existants.", "LabelDummyChapterCount": "Limite", "LabelChapterImageResolution": "Résolution", - "LabelChapterImageResolutionHelp": "La résolution d'image des chapitre factices. Changer la valeur n’aura aucun effet sur les chapitres existants.", + "LabelChapterImageResolutionHelp": "La résolution d'image des chapitre factices. Changer la valeur n’affectera pas les chapitres existants.", "ResolutionMatchSource": "Résolution de la source", "HeaderDummyChapter": "Images des chapitres", "LabelDummyChapterDuration": "Intervalle", @@ -1734,9 +1734,14 @@ "EnableAudioNormalization": "Normalisation audio", "LabelEnableLUFSScan": "Activer l’analyse LUFS", "LabelEnableLUFSScanHelp": "Activer l’analyse LUFS pour la musique (cela prendra plus de temps et de ressources).", - "GetThePlugin": "Obtenir le plugin", + "GetThePlugin": "Obtenir l'extension", "Notifications": "Notifications", - "NotificationsMovedMessage": "La fonctionnalité de notifications a été transférée au plugin Webhook.", + "NotificationsMovedMessage": "La fonctionnalité de notifications a été transférée à l'extension Webhook.", "PasswordRequiredForAdmin": "Un mot de passe est requis pour les comptes administrateur.", - "LabelSyncPlayNoGroups": "Pas de groupes disponibles" + "LabelSyncPlayNoGroups": "Pas de groupes disponibles", + "HeaderConfirmRepositoryInstallation": "Confirmer l'installation du dépôt d'extensions", + "LabelDeveloper": "Développeur", + "Unknown": "Inconnu", + "MessageRepositoryInstallDisclaimer": "ATTENTION : Installer un dépôt d'extensions tierces comporte des risques. Celui-ci peut contenir du code instable ou malveillant et peut être modifiée à tout moment. N'installez que des dépôts provenant d'auteurs en qui vous avez confiance.", + "PleaseConfirmRepositoryInstallation": "Veuillez cliquer sur OK pour confirmer que vous avez lu ce qui précède et que vous souhaitez poursuivre l'installation du dépôt d'extensions." } From 1ae31605a00cc0b2f3f52c14ba89ae8d1c63d3d2 Mon Sep 17 00:00:00 2001 From: House079 Date: Sun, 2 Jul 2023 16:44:15 +0000 Subject: [PATCH 36/51] Translated using Weblate (Polish) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/pl/ --- src/strings/pl.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/strings/pl.json b/src/strings/pl.json index bbc918808..299c4c767 100644 --- a/src/strings/pl.json +++ b/src/strings/pl.json @@ -1738,5 +1738,10 @@ "PasswordRequiredForAdmin": "Hasło jest wymagane do kont administratorów.", "LabelSyncPlayNoGroups": "Brak dostępnych grup", "GetThePlugin": "Pobierz wtyczkę", - "NotificationsMovedMessage": "Funkcjonalność powiadomień została przeniesiona do wtyczki Webhook." + "NotificationsMovedMessage": "Funkcjonalność powiadomień została przeniesiona do wtyczki Webhook.", + "HeaderConfirmRepositoryInstallation": "Potwierdź instalację repozytorium wtyczek", + "LabelDeveloper": "Programista", + "MessageRepositoryInstallDisclaimer": "OSTRZEŻENIE: Instalacja repozytorium wtyczek stron trzecich wiąże się z ryzykiem. Może ono zawierać niestabilny lub złośliwy kod i może ulec zmianie w dowolnym momencie. Instaluj tylko repozytoria od zaufanych autorów.", + "PleaseConfirmRepositoryInstallation": "Kliknij OK, aby potwierdzić, że przeczytałeś powyższe informacje i chcesz kontynuować instalację repozytorium wtyczek.", + "Unknown": "Nieznane" } From 8621462603c55ef13f92a58083100c1d756f9615 Mon Sep 17 00:00:00 2001 From: Eric Date: Sun, 2 Jul 2023 19:42:30 +0000 Subject: [PATCH 37/51] Translated using Weblate (German) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/de/ --- src/strings/de.json | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/strings/de.json b/src/strings/de.json index e2c73c1b0..6556d49be 100644 --- a/src/strings/de.json +++ b/src/strings/de.json @@ -1508,7 +1508,7 @@ "DisablePlugin": "Deaktivieren", "EnablePlugin": "Aktivieren", "Framerate": "Bildrate", - "DirectPlayHelp": "Die Quelldatei ist vollständig mit diesem Client kompatibel, und die Sitzung empfängt die Datei ohne Änderungen.", + "DirectPlayHelp": "Die Quelldatei ist vollständig mit diesem Client kompatibel und die Sitzung empfängt die Datei ohne Änderungen.", "HeaderContinueReading": "Weiterlesen", "EnableGamepadHelp": "Auf Eingaben aller verbundenen Controller hören. (Erfordert: 'TV'-Anzeigemodus)", "LabelEnableGamepad": "Gamepad aktivieren", @@ -1738,5 +1738,10 @@ "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).", "EnableAudioNormalization": "Audionormalisierung", - "GetThePlugin": "Plugin laden" + "GetThePlugin": "Plugin laden", + "HeaderConfirmRepositoryInstallation": "Plugin-Repository-Installation bestätigen", + "LabelDeveloper": "Entwickler", + "MessageRepositoryInstallDisclaimer": "WARNUNG: Eine Drittanbieterquelle für Plugins kann instabilen oder schadhaften Code beinhalten und kann sich zu jedem Zeitpunkt ändern. Bitte installiere nur Quellen von Autoren, denen du vertraust.", + "PleaseConfirmRepositoryInstallation": "Bitte klicke auf OK um zu bestätigen, dass du den obigen Text gelesen hast und mit Plugin-Quellen-Installation fortfahren willst.", + "Unknown": "Unbekannt" } From 5a8e8f29fd5a17be128d64db0bf2434624e73cf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Kucharczyk?= Date: Mon, 3 Jul 2023 00:48:06 +0000 Subject: [PATCH 38/51] 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, 8 insertions(+), 2 deletions(-) diff --git a/src/strings/cs.json b/src/strings/cs.json index fb896cb30..de60684bd 100644 --- a/src/strings/cs.json +++ b/src/strings/cs.json @@ -696,7 +696,7 @@ "MessagePlayAccessRestricted": "Přehrávání tohoto obsahu je aktuálně omezeno. Další informace získáte od správce serveru.", "MessagePleaseEnsureInternetMetadata": "Prosím zkontrolujte, zda máte povoleno stahování metadat z internetu.", "MessagePluginConfigurationRequiresLocalAccess": "Pro konfiguraci zásuvného modulu se přihlaste přímo na lokální server.", - "MessagePluginInstallDisclaimer": "Zásuvné moduly vytvořené členy komunity jsou skvělým způsobem, jak si zlepšit prožitek pomocí dalších funkcí. Před instalací se prosím seznamte se všemi dopady, které mohou doplňky na server mít, např.: pomalejší skenování knihovny, delší zpracování na pozadí nebo snížená stabilita systému.", + "MessagePluginInstallDisclaimer": "UPOZORNĚNÍ: Instalace zásuvného modulu třetí strany má určitá rizika. Může obsahovat nestabilní nebo zákeřný kód, a může se kdykoliv změnit. Instalujte zásuvné moduly jen těch autorů, kterým důvěřujete, a mějte na vědomi, jaké potenciální dopady to může mít, včetně kontaktování externích služeb, delšího skenování knihovny, nebo dalších procesů na pozadí.", "MessageReenableUser": "Viz níže pro znovuzapnutí", "MessageTheFollowingLocationWillBeRemovedFromLibrary": "Z vaší knihovny budou odstraněny následující zdroje médií", "MessageUnableToConnectToServer": "Nejsme schopni se připojit k vybranému serveru právě teď. Prosím, ujistěte se, že je spuštěn a zkuste to znovu.", @@ -1737,5 +1737,11 @@ "GetThePlugin": "Získat zásuvný modul", "Notifications": "Oznámení", "NotificationsMovedMessage": "Funkce oznámení se přesunula do zásuvného modulu Webhook.", - "PasswordRequiredForAdmin": "Administrátorské účty musejí mít nastaveno heslo." + "PasswordRequiredForAdmin": "Administrátorské účty musejí mít nastaveno heslo.", + "PleaseConfirmRepositoryInstallation": "Kliknutím na OK potvrďte, ze jste si přečetli text uvedený výše, a že chcete pokračovat v instalaci repozitáře zásuvného modulu.", + "Unknown": "Neznámý", + "LabelSyncPlayNoGroups": "Žádné skupiny nejsou k dispozici", + "HeaderConfirmRepositoryInstallation": "Potvrdit instalaci repozitáře zásuvných modulů", + "LabelDeveloper": "Vývojář", + "MessageRepositoryInstallDisclaimer": "UPOZORNĚNÍ: Instalace zásuvného modulu třetí strany má určitá rizika. Může obsahovat nestabilní nebo zákeřný kód, a může se kdykoliv změnit. Instalujte zásuvné moduly jen těch autorů, kterým důvěřujete." } From 962c72f5d776e4f122c4b516344c3590fb02e2fc Mon Sep 17 00:00:00 2001 From: Oskari Lavinto Date: Mon, 3 Jul 2023 04:35:16 +0000 Subject: [PATCH 39/51] Translated using Weblate (Finnish) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fi/ --- src/strings/fi.json | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/strings/fi.json b/src/strings/fi.json index c04d8bdb7..812839c22 100644 --- a/src/strings/fi.json +++ b/src/strings/fi.json @@ -1375,7 +1375,7 @@ "MessageSent": "Viesti lähetetty.", "MessagePluginInstallError": "Asennettaessa lisäosaa tapahtui virhe.", "MessagePluginInstalled": "Lisäosan asennus onnistui. Palvelin on käynnistettävä uudelleen, jotta muutokset tulevat voimaan.", - "MessagePluginInstallDisclaimer": "Yhteisön kehittämät lisäosat ovat mainio tapa parantaa käyttökokemustasi lisäominaisuuksilla. Huomioi ennen lisäosien asennusta, että ne voivat vaikuttaa palvelimen toimintaan, mm. pidentämällä kirjastopäivityksiä ja lisäämällä taustakuormitusta, sekä aiheuttaa järjestelmän epävakautta.", + "MessagePluginInstallDisclaimer": "VAROITUS: Ulkopuolisten tahojen kehittämien lisäosien asennus on aina riskialtista ja ne voivat sisältää epävakaata tai haitallista koodia, ja muuttua koska tahansa. Asenna lisäosia vain kehittäjiltä, joihin luotat ja ymmärrä niiden mahdolliset vaikutukset, kuten yhteydenotot ulkoisiin palveluihin, pidentyvät kirjastotarkistukset tai erilaiset taustaprosessit.", "MessagePlayAccessRestricted": "Tämän sisällön toistoa on rajoitettu. Lisätietoja saat palvelimen ylläpidolta.", "MessagePasswordResetForUsers": "Seuraavien käyttäjien salasanat on tyhjennetty ja he voivat nyt kirjautua käyttäen tyhjennykseen käytettäjä Helppo PIN -koodeja.", "MessageNoTrailersFound": "Asenna trailerit-kanava parantaaksesi elokuvakokemusta lisäämällä internet-trailereiden kirjasto.", @@ -1732,9 +1732,14 @@ "EnableAudioNormalization": "Äänen normalisointi", "LabelEnableLUFSScan": "Suorita LUFS-tarkistus", "LabelEnableLUFSScanHelp": "Käytä musiikin LUFS-tarkistusta (tämä vaatii enemmän aikaa ja resurseja).", - "GetThePlugin": "Hanki laajennus", + "GetThePlugin": "Hanki lisäosa", "Notifications": "Ilmoitukset", - "NotificationsMovedMessage": "Ilmoitustoiminnallisuus on siirtynyt Webhook-laajennukseen.", + "NotificationsMovedMessage": "Ilmoitustoiminnallisuus on siirtynyt Webhook-lisäosaan.", "PasswordRequiredForAdmin": "Ylläpitotileille on määritettävä salasana.", - "LabelSyncPlayNoGroups": "Ryhmiä ei ole käytettävissä" + "LabelSyncPlayNoGroups": "Ryhmiä ei ole käytettävissä", + "HeaderConfirmRepositoryInstallation": "Vahvista lisäosahakemiston asennus", + "LabelDeveloper": "Kehittäjä", + "MessageRepositoryInstallDisclaimer": "VAROITUS: Ulkopuolisten tahojen kehittämien lisäosien asennus on aina riskialtista ja ne voivat sisältää epävakaata tai haitallista koodia, ja muuttua koska tahansa. Asenna lisäosia vain kehittäjiltä, joihin luotat.", + "Unknown": "Tuntematon", + "PleaseConfirmRepositoryInstallation": "Vahvista lukeneesi yllä olevan tekstin ja jatkaaksesi lisäosan asennusta painamalla OK." } From 369aba43daae6bcc345f0e599a00419015fe16c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E9=80=A0xu=5Fzh?= Date: Mon, 3 Jul 2023 04:02:07 +0000 Subject: [PATCH 40/51] 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 | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/strings/zh-cn.json b/src/strings/zh-cn.json index 5fc3ee980..099365960 100644 --- a/src/strings/zh-cn.json +++ b/src/strings/zh-cn.json @@ -775,7 +775,7 @@ "MessagePleaseEnsureInternetMetadata": "请确认已启用从网络上下载媒体资料的选项。", "MessagePleaseWait": "请稍等。这将花费大约1分钟的时间。", "MessagePluginConfigurationRequiresLocalAccess": "请直接登录你的本地服务器以设置这个插件。", - "MessagePluginInstallDisclaimer": "安装社区成员构建的插件来获取额外的功能是增强您体验的一种很好的方式。但在安装之前请意识到他们可能会对你的服务器造成影响,如更长的媒体库扫描时间、额外的后台处理、以及系统稳定性的降低等。", + "MessagePluginInstallDisclaimer": "警告:安装第三方插件有风险。它可能包含不稳定或恶意的代码,并且可能随时变化。请仅安装您信任的作者提供的插件,并请在安装之前意识到它可能造成的影响,例如对外部服务的查询、更长的媒体库扫描时间、以及额外的后台处理等。", "MessageReenableUser": "请参阅以下以重新启用", "MessageTheFollowingLocationWillBeRemovedFromLibrary": "以下媒体路径将从你的媒体库移除", "MessageUnableToConnectToServer": "现在无法连接所选择的服务器,请确保该服务器目前正在运行。", @@ -1738,5 +1738,10 @@ "Notifications": "通知", "NotificationsMovedMessage": "通知功能已经转移到Webhook插件中。", "PasswordRequiredForAdmin": "管理员账户需要密码。", - "LabelSyncPlayNoGroups": "没有可用的组" + "LabelSyncPlayNoGroups": "没有可用的组", + "LabelDeveloper": "开发者", + "MessageRepositoryInstallDisclaimer": "警告:安装第三方插件仓库有风险。它可能包含不稳定或恶意的代码,并且可能随时变化。请仅安装您信任的作者提供的插件仓库。", + "PleaseConfirmRepositoryInstallation": "请点击确定键来确认您已经阅读了上述内容,并希望继续进行插件仓库的安装。", + "Unknown": "未知", + "HeaderConfirmRepositoryInstallation": "确认安装插件仓库" } From 088d204151e056171a89937f5421382b9d126cb0 Mon Sep 17 00:00:00 2001 From: Andi Chandler Date: Mon, 3 Jul 2023 15:32:32 +0000 Subject: [PATCH 41/51] 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 | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/strings/en-gb.json b/src/strings/en-gb.json index f6389ab25..bedd90247 100644 --- a/src/strings/en-gb.json +++ b/src/strings/en-gb.json @@ -584,7 +584,7 @@ "MinutesBefore": "minutes before", "MetadataSettingChangeHelp": "Changing metadata settings will affect new content added going forward. To refresh existing content, open the detail screen and click the 'Refresh' button, or do bulk refreshes using the 'Metadata Manager'.", "MetadataManager": "Metadata Manager", - "MessagePluginInstallDisclaimer": "Plugins built by community members are a great way to enhance your experience with additional features and benefits. Before installing, please be aware of the effects they may have on your server, such as longer library scans, additional background processing, and decreased system stability.", + "MessagePluginInstallDisclaimer": "WARNING: Installing a third party plugin carries risks. It may contain unstable or malicious code, and may change at any time. Only install plugins from authors that you trust, and please be aware of the potential effects it may have, including external service queries, longer library scans, or additional background processing.", "MessagePluginConfigurationRequiresLocalAccess": "To configure this plugin please sign in to your local server directly.", "MessagePleaseWait": "Please wait. This may take a minute.", "MessagePleaseEnsureInternetMetadata": "Please ensure downloading of internet metadata is enabled.", @@ -1738,5 +1738,10 @@ "Notifications": "Notifications", "NotificationsMovedMessage": "The notifications functionality has moved to the Webhook plugin.", "PasswordRequiredForAdmin": "A password is required for admin accounts.", - "LabelSyncPlayNoGroups": "No groups available" + "LabelSyncPlayNoGroups": "No groups available", + "HeaderConfirmRepositoryInstallation": "Confirm Plugin Repository Installation", + "LabelDeveloper": "Developer", + "MessageRepositoryInstallDisclaimer": "WARNING: Installing a third party plugin repository carries risks. It may contain unstable or malicious code, and may change at any time. Only install repositories from authors that you trust.", + "PleaseConfirmRepositoryInstallation": "Please click OK to confirm you've read the above and wish to proceed with the plugin repository installation.", + "Unknown": "Unknown" } From 6c8ac494d6ce97de9b9f725f45562fef34708a22 Mon Sep 17 00:00:00 2001 From: millallo Date: Mon, 3 Jul 2023 17:24:33 +0000 Subject: [PATCH 42/51] Translated using Weblate (Italian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/it/ --- src/strings/it.json | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/strings/it.json b/src/strings/it.json index 1303a549c..cd2f0a8a9 100644 --- a/src/strings/it.json +++ b/src/strings/it.json @@ -768,7 +768,7 @@ "MessagePleaseEnsureInternetMetadata": "Assicurarsi che il download dei metadati Internet sia abilitato.", "MessagePleaseWait": "Per favore attendi. La procedura potrebbe impiegare qualche minuto.", "MessagePluginConfigurationRequiresLocalAccess": "Per configurare questo plugin si prega di accedere al proprio server locale direttamente.", - "MessagePluginInstallDisclaimer": "I plugin creati dai membri della comunità sono un ottimo modo per migliorare l'esperienza con funzionalità e vantaggi aggiuntivi. Prima di installarli, si prega di notare gli effetti che possono avere sul tuo Server, come le scansioni più lunghe della libreria, l'elaborazione di sfondo aggiuntiva e la stabilità del sistema diminuita.", + "MessagePluginInstallDisclaimer": "ATTENZIONE: L'installazione 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 e tieni presente gli effetti collaterali che potrebbe avere, inclusi query esterne, scan più lunghi or processi in background aggiuntivi.", "MessageReenableUser": "Guarda in basso per ri-abilitare", "MessageTheFollowingLocationWillBeRemovedFromLibrary": "I seguenti percorsi ai file multimediali saranno rimossi dalla tua libreria", "MessageUnableToConnectToServer": "Non siamo in grado di connettersi al server selezionato al momento. Per favore assicurati che sia in esecuzione e riprova.", @@ -1736,5 +1736,12 @@ "LabelSyncPlayNoGroups": "Nessun gruppo disponibile", "NotificationsMovedMessage": "Le notifiche sono state spostate nel plugin Webhook.", "EnableAudioNormalizationHelp": "La normalizzazione dell'audio aggiunge un guadagno per mantenerlo ad un livello desiderato (-18dB).", - "EnableAudioNormalization": "Normalizzazione Audio" + "EnableAudioNormalization": "Normalizzazione Audio", + "PleaseConfirmRepositoryInstallation": "Clicca OK per confermare di aver letto sopra e desideri procedere con l'installazione del repository del plugin.", + "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).", + "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" } From e8868cdbc9d1ca5f39609366daed939ce8bc7f7f Mon Sep 17 00:00:00 2001 From: Denis Doria Date: Mon, 3 Jul 2023 16:35:56 +0000 Subject: [PATCH 43/51] 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 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/strings/pt-br.json b/src/strings/pt-br.json index d8bceeefd..1547ac220 100644 --- a/src/strings/pt-br.json +++ b/src/strings/pt-br.json @@ -1182,7 +1182,7 @@ "OptionLoginAttemptsBeforeLockoutHelp": "Um valor zero significa herdar o padrão de três tentativas para usuários normais e cinco para administradores. Definir como -1 desativará o recurso.", "OptionProtocolHls": "Transmissão ao Vivo por HTTP (HLS)", "OptionProtocolHttp": "HTTP", - "OptionRegex": "Regex", + "OptionRegex": "Expressão Regular", "OptionSubstring": "Substring", "PasswordResetProviderHelp": "Escolha um provedor de reset de senha a ser usado quando este usuário solicitar uma redefinição de senha.", "PictureInPicture": "vídeo destacado", @@ -1564,7 +1564,7 @@ "Remixer": "Remixar", "ReleaseGroup": "Grupo de Realease", "PlaybackErrorPlaceHolder": "Este é um espaço reservado para mídia física que o Jellyfin não pode reproduzir. Insira o disco a ser reproduzido.", - "Mixer": "Mixer", + "Mixer": "Mixador", "LabelSyncPlaySettingsSkipToSyncHelp": "Método de correção de sincronismo que consiste em buscar a posição estimada. A correção de sincronização deve estar habilitada.", "LabelSyncPlaySettingsSkipToSync": "Habilitar SkipToSync", "LabelSyncPlaySettingsSpeedToSyncHelp": "Método de correção de sincronização que consiste em acelerar a reprodução. A correção de sincronização deve estar habilitada.", From 68eab4041a5a4049285f76f90f6ecba291845adf Mon Sep 17 00:00:00 2001 From: stanol Date: Mon, 3 Jul 2023 14:22:39 +0000 Subject: [PATCH 44/51] Translated using Weblate (Ukrainian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/uk/ --- src/strings/uk.json | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/strings/uk.json b/src/strings/uk.json index 5c24b2968..e5e4e76d4 100644 --- a/src/strings/uk.json +++ b/src/strings/uk.json @@ -414,7 +414,7 @@ "HeaderChannelAccess": "Доступ до каналів", "Framerate": "Частота кадрів", "EnableThemeSongsHelp": "Відтворювання тематичної музики у фоновому режимі під час перегляду медіатеки.", - "DirectPlayHelp": "Вихідний файл повністю сумісний з цим клієнтом, а сеанс отримує файл без модифікацій.", + "DirectPlayHelp": "Вихідний файл повністю сумісний з цим клієнтом і сеанс отримує файл без модифікацій.", "DeviceAccessHelp": "Це стосується лише пристроїв, які можна однозначно ідентифікувати та не блокують доступ до браузера. Фільтрування доступу користувацьких пристроїв не дозволить їм використовувати нові пристрої, доки вони не будуть затверджені тут.", "DeinterlaceMethodHelp": "Оберіть метод деінтерлейсингу, який використовуватиметься під час програмного перекодування вмісту. Якщо ввімкнено апаратне прискорення з підтримкою апаратного деінтерлейсингу, воно буде використовуватись натомість.", "MusicVideos": "Відеокліпи", @@ -1151,7 +1151,7 @@ "MessageReenableUser": "Щоб повторно ввімкнути, дивіться нижче", "MessagePluginInstallError": "Під час встановлення плагіна сталася помилка.", "MessagePluginInstalled": "Плагін успішно встановлено. Щоб зміни набули чинності, сервер потрібно буде перезапустити.", - "MessagePluginInstallDisclaimer": "Плагіни, створені учасниками спільноти, — це чудовий спосіб покращити ваш досвід за допомогою додаткових функцій і переваг. Перед встановленням зверніть увагу на наслідки, які вони можуть мати на вашому сервері, наприклад, триваліші сканування медіатек, додаткова фонова обробка та зниження стабільності системи.", + "MessagePluginInstallDisclaimer": "ПОПЕРЕДЖЕННЯ: Встановлення сторонніх плагінів несе певні ризики. Вони можуть містити нестабільний або шкідливий код і можуть змінюватися в будь-який час. Встановлюйте плагіни лише від авторів, яким ви довіряєте, і будь ласка, пам'ятайте про потенційні наслідки, які вони можуть мати, зокрема зовнішні запити до служб, довше сканування бібліотек або додаткову фонову обробку.", "MessagePluginConfigurationRequiresLocalAccess": "Щоб налаштувати цей плагін, увійдіть безпосередньо на свій локальний сервер.", "MessagePleaseWait": "Будь ласка, зачекайте. Це може зайняти хвилину.", "MessagePleaseEnsureInternetMetadata": "Переконайтеся, що завантаження Інтернет-метаданих увімкнено.", @@ -1735,5 +1735,10 @@ "NotificationsMovedMessage": "Функціонал сповіщень переїхав до плагіна Webhook.", "Notifications": "Сповіщення", "PasswordRequiredForAdmin": "Для облікових записів адміністратора потрібен пароль.", - "LabelSyncPlayNoGroups": "Групи не доступні" + "LabelSyncPlayNoGroups": "Групи не доступні", + "HeaderConfirmRepositoryInstallation": "Підтвердити встановлення репозиторію плагінів", + "LabelDeveloper": "Розробник", + "MessageRepositoryInstallDisclaimer": "ПОПЕРЕДЖЕННЯ: Встановлення стороннього репозиторію плагінів несе певні ризики. Він може містити нестабільний або шкідливий код і може змінюватися в будь-який час. Встановлюйте репозиторії лише від авторів, яким ви довіряєте.", + "PleaseConfirmRepositoryInstallation": "Будь ласка, натисніть OK, щоб підтвердити, що ви прочитали вищезазначене і бажаєте продовжити встановлення репозиторію плагінів.", + "Unknown": "Невідомо" } From deeb10dc0f003502b5f468b2631e6907d4f9184d Mon Sep 17 00:00:00 2001 From: svgaming234 Date: Tue, 4 Jul 2023 12:09:49 +0000 Subject: [PATCH 45/51] Translated using Weblate (Croatian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/hr/ --- src/strings/hr.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/strings/hr.json b/src/strings/hr.json index b2c7cf2e6..fe109b9da 100644 --- a/src/strings/hr.json +++ b/src/strings/hr.json @@ -1361,5 +1361,14 @@ "LabelAutomaticallyAddToCollectionHelp": "Kada najmanje 2 filma imaju isti naziv kolekcije, automatski će se dodati u kolekciju.", "Suggestions": "Prijedlozi", "Trailers": "Najave", - "Small": "Malo" + "Small": "Malo", + "HeaderDummyChapter": "Slike Poglavlja", + "EnableAudioNormalization": "Normalizacija zvuka", + "Experimental": "Eksperimentalno", + "HeaderPerformance": "Performanse", + "DownloadAll": "Preuzmi sve", + "HeaderRecordingMetadataSaving": "Snimanje metapodataka", + "LabelChapterImageResolution": "Rezolucija", + "AllowCollectionManagement": "Dozvoli ovom korisniku da upravlja kolekcijama", + "LabelDummyChapterDuration": "Interval" } From ea28a4b4acfbbda12c229822d4c46c8d18849e98 Mon Sep 17 00:00:00 2001 From: Radvin-89 Date: Tue, 4 Jul 2023 14:26:22 +0000 Subject: [PATCH 46/51] Translated using Weblate (Persian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fa/ --- src/strings/fa.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/strings/fa.json b/src/strings/fa.json index a20a849ed..f6e990bc4 100644 --- a/src/strings/fa.json +++ b/src/strings/fa.json @@ -1505,7 +1505,7 @@ "EnableAutoCast": "تنظیم به عنوان پیشفرض", "DisablePlugin": "غیرفعال کردن", "EnablePlugin": "فعال کردن", - "DirectPlayHelp": "فایل منبع کاملاً با این سرویس گیرنده سازگار است و جلسه بدون تغییر پرونده در حال دریافت فایل است.", + "DirectPlayHelp": "فایل منبع کاملاً با این سرویس گیرنده سازگار است و جلسه در حال دریافت فایل بدون تغییر است.", "DeleteDevicesConfirmation": "آیا مطمئن هستید که می خواهید همه دستگاه ها را حذف کنید؟ تمام جلسات دیگر از سیستم خارج می شوند. بار دیگر که کاربر وارد سیستم شود ، دستگاه ها دوباره ظاهر می شوند.", "Bwdif": "BWDIF", "ButtonUseQuickConnect": "از اتصال سریع استفاده کنید", @@ -1601,5 +1601,6 @@ "HeaderPerformance": "کارایی", "IgnoreDtsHelp": "غیر فعال کردن این گزینه ممکن است برخی اشکالات را رفع کند، مثل نبودن صدا بر روی کانال هایی که جریان صدا و تصویر جداگانه دارند.", "LabelDummyChapterDurationHelp": "وقفه استخراج تصاویر فصل به ثانیه.", - "HeaderDummyChapter": "تصاویر فصل" + "HeaderDummyChapter": "تصاویر فصل", + "EnableAudioNormalization": "معمول سازی صوت" } From c5503a71d1cdb38d85df0a4d0abe3974aa04d93a Mon Sep 17 00:00:00 2001 From: stanol Date: Tue, 4 Jul 2023 15:14:56 +0000 Subject: [PATCH 47/51] 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 e5e4e76d4..3786681ef 100644 --- a/src/strings/uk.json +++ b/src/strings/uk.json @@ -1303,7 +1303,7 @@ "OptionPlainVideoItems": "Відображати всі відео як звичайні відеоелементи", "OptionPlainStorageFolders": "Відображати всі папки як звичайні папки зберігання", "OptionParentalRating": "Батьківський рейтинг", - "OptionOnInterval": "На інтервалі", + "OptionOnInterval": "З інтервалом", "OptionNew": "Новий…", "OptionMissingEpisode": "Відсутні епізоди", "OptionMaxActiveSessionsHelp": "Значення 0 вимкне функцію.", From ea75e51e9a730906c88754eb01e2204ee180df60 Mon Sep 17 00:00:00 2001 From: officialdanielamani Date: Tue, 4 Jul 2023 21:19:09 +0000 Subject: [PATCH 48/51] Translated using Weblate (Malay) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/ms/ --- src/strings/ms.json | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/strings/ms.json b/src/strings/ms.json index 377349bab..cd1d725c9 100644 --- a/src/strings/ms.json +++ b/src/strings/ms.json @@ -102,7 +102,7 @@ "AskAdminToCreateLibrary": "Minta pentadbir untuk membuat perpustakaan.", "Artist": "Artis", "ApiKeysCaption": "Senarai kunci API yang diaktifkan sekarang", - "AllowTonemappingHelp": "Pemetaan nada dapat mengubah rentang dinamik video dari HDR ke SDR sambil mengekalkan perincian dan warna gambar, yang merupakan maklumat yang sangat penting untuk mewakili pemandangan asal. Pada masa ini ia hanya berfungsi dengan video HDR10 atau HLG. Ini memerlukan pemadanan dengan OpenCL atau CUDA semasa.", + "AllowTonemappingHelp": "Pemetaan-tone dapat mengubah dinamik video daripada HDR ke SDR sambil mengekalkan perincian dan warna gambar, yang merupakan maklumat yang sangat penting untuk mewakili pemandangan asal. Pada masa ini ia hanya berfungsi dengan video 10bit HDR10, HLG dan Dolby Visoion. Ini memerlukan pemadanan bersesuian dengan OpenCL atau CUDA runtime.", "Songs": "Lagu-lagu", "Playlists": "Senarai ulangmain", "Photos": "Gambar-gambar", @@ -240,5 +240,11 @@ "ButtonExitApp": "Tamatkan aplikasi", "ButtonClose": "Tutup", "AgeValue": "({0} tahun)", - "AddToFavorites": "Tambah ke kegemaran" + "AddToFavorites": "Tambah ke kegemaran", + "Arranger": "Penyusunan", + "DefaultMetadataLangaugeDescription": "Ini adalah tetapan umum dan boleh diubahsuai mengikut koleksi.", + "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" } From f1e14b841f03683a32599eef9665be4e598c1580 Mon Sep 17 00:00:00 2001 From: Letradical Date: Wed, 5 Jul 2023 06:50:48 +0000 Subject: [PATCH 49/51] Translated using Weblate (Hebrew) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/he/ --- src/strings/he.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/strings/he.json b/src/strings/he.json index e4020ec76..69eae8db0 100644 --- a/src/strings/he.json +++ b/src/strings/he.json @@ -1056,5 +1056,6 @@ "HeaderPerformance": "ביצועים", "HeaderRecordingMetadataSaving": "מטאדאטה של הקלטות", "HeaderDummyChapter": "תמונות פרק", - "Unreleased": "לא יצא עדיין" + "Unreleased": "לא יצא עדיין", + "AllowCollectionManagement": "הרשה למשתמש זה לנהל אוספים" } From 7568733f9422a5f987a60de17a61cd0f2833b2f0 Mon Sep 17 00:00:00 2001 From: blob03 Date: Wed, 5 Jul 2023 12:20:07 +0000 Subject: [PATCH 50/51] Translated using Weblate (French) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fr/ --- src/strings/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings/fr.json b/src/strings/fr.json index 2f0ace53f..7105f5f53 100644 --- a/src/strings/fr.json +++ b/src/strings/fr.json @@ -1742,6 +1742,6 @@ "HeaderConfirmRepositoryInstallation": "Confirmer l'installation du dépôt d'extensions", "LabelDeveloper": "Développeur", "Unknown": "Inconnu", - "MessageRepositoryInstallDisclaimer": "ATTENTION : Installer un dépôt d'extensions tierces comporte des risques. Celui-ci peut contenir du code instable ou malveillant et peut être modifiée à tout moment. N'installez que des dépôts provenant d'auteurs en qui vous avez confiance.", + "MessageRepositoryInstallDisclaimer": "ATTENTION : Installer un dépôt d'extensions tierces comporte des risques. Celui-ci peut contenir du code instable ou malveillant et peut être modifié à tout moment. N'installez que des dépôts provenant d'auteurs en qui vous avez confiance.", "PleaseConfirmRepositoryInstallation": "Veuillez cliquer sur OK pour confirmer que vous avez lu ce qui précède et que vous souhaitez poursuivre l'installation du dépôt d'extensions." } From e74e568edcdfc6657485cddc852a590ffcd88e96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Mesk=C3=B3?= Date: Wed, 5 Jul 2023 13:32:39 +0000 Subject: [PATCH 51/51] Translated using Weblate (Hungarian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/hu/ --- src/strings/hu.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/strings/hu.json b/src/strings/hu.json index 3f4df9146..d086b699c 100644 --- a/src/strings/hu.json +++ b/src/strings/hu.json @@ -388,7 +388,7 @@ "Subtitles": "Feliratok", "Suggestions": "Javaslatok", "Sunday": "Vasárnap", - "Sync": "Szinkronizál", + "Sync": "Szinkronizálás", "TabAccess": "Hozzáférés", "TabAdvanced": "Speciális", "TabCatalog": "Katalógus", @@ -563,7 +563,7 @@ "HeaderActivity": "Tevékenységek", "HeaderAdditionalParts": "További részek", "HeaderAdmin": "Felügyelet", - "HeaderAlbumArtists": "Album előadó(k)", + "HeaderAlbumArtists": "Albumelőadók", "HeaderAlert": "Figyelem", "HeaderAllowMediaDeletionFrom": "Médiatörlés engedélyezése innen", "HeaderApiKey": "API kulcs", @@ -638,8 +638,8 @@ "Photos": "Fényképek", "Playlists": "Lejátszási listák", "Shows": "Sorozatok", - "Songs": "Dalok", - "ValueSpecialEpisodeName": "Special - {0}", + "Songs": "Számok", + "ValueSpecialEpisodeName": "Különkiadás – {0}", "EnableThemeVideosHelp": "Témavideókat játszhat a háttérben a könyvtár böngészése közben.", "HeaderBlockItemsWithNoRating": "Blokkolja azokat az elemeket amelyek tiltott, vagy nem felismerhető minősítésűek", "HeaderSeriesStatus": "Sorozat állapot",