From d702ad938a1c2fd4d34d6a5a4cc48621241d8bfc Mon Sep 17 00:00:00 2001 From: grafixeyehero Date: Sun, 11 Feb 2024 23:32:14 +0300 Subject: [PATCH 01/34] add show method for PlaylistEditor Class --- src/components/itemContextMenu.js | 3 ++- src/components/multiSelect/multiSelect.js | 14 ++++++++++---- src/components/playlisteditor/playlisteditor.js | 2 +- src/components/remotecontrol/remotecontrol.js | 7 ++++++- src/components/shortcuts.js | 8 ++++++-- src/controllers/list.js | 7 ++++++- src/scripts/playlists.js | 7 ++++++- 7 files changed, 37 insertions(+), 11 deletions(-) diff --git a/src/components/itemContextMenu.js b/src/components/itemContextMenu.js index 69caf9cbc..6d1c3c24f 100644 --- a/src/components/itemContextMenu.js +++ b/src/components/itemContextMenu.js @@ -339,7 +339,8 @@ function executeCommand(item, id, options) { break; case 'addtoplaylist': import('./playlisteditor/playlisteditor').then(({ default: PlaylistEditor }) => { - new PlaylistEditor({ + const playlistEditor = new PlaylistEditor(); + playlistEditor.show({ items: [itemId], serverId: serverId }).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id)); diff --git a/src/components/multiSelect/multiSelect.js b/src/components/multiSelect/multiSelect.js index 48cbce0e1..d69c7b33b 100644 --- a/src/components/multiSelect/multiSelect.js +++ b/src/components/multiSelect/multiSelect.js @@ -6,7 +6,6 @@ import dom from '../../scripts/dom'; import './multiSelect.scss'; import ServerConnections from '../ServerConnections'; import alert from '../alert'; -import PlaylistEditor from '../playlisteditor/playlisteditor'; import confirm from '../confirm/confirm'; import itemHelper from '../itemHelper'; import datetime from '../../scripts/datetime'; @@ -269,9 +268,16 @@ function showMenuForSelectedItems(e) { dispatchNeedsRefresh(); break; case 'playlist': - new PlaylistEditor({ - items: items, - serverId: serverId + import('../playlisteditor/playlisteditor').then(({ default: PlaylistEditor }) => { + const playlistEditor = new PlaylistEditor(); + playlistEditor.show({ + items: items, + serverId: serverId + }).catch(() => { + // Dialog closed + }); + }).catch(err => { + console.error('[AddToPlaylist] failed to load playlist editor', err); }); hideSelections(); dispatchNeedsRefresh(); diff --git a/src/components/playlisteditor/playlisteditor.js b/src/components/playlisteditor/playlisteditor.js index ee7cd2b41..625135a47 100644 --- a/src/components/playlisteditor/playlisteditor.js +++ b/src/components/playlisteditor/playlisteditor.js @@ -222,7 +222,7 @@ function centerFocus(elem, horiz, on) { } export class PlaylistEditor { - constructor(options) { + show(options) { const items = options.items || {}; currentServerId = options.serverId; diff --git a/src/components/remotecontrol/remotecontrol.js b/src/components/remotecontrol/remotecontrol.js index 1120e03c0..9406e16e2 100644 --- a/src/components/remotecontrol/remotecontrol.js +++ b/src/components/remotecontrol/remotecontrol.js @@ -704,15 +704,20 @@ export default function () { import('../playlisteditor/playlisteditor').then(({ default: PlaylistEditor }) => { getSaveablePlaylistItems().then(function (items) { const serverId = items.length ? items[0].ServerId : ApiClient.serverId(); - new PlaylistEditor({ + const playlistEditor = new PlaylistEditor(); + playlistEditor.show({ items: items.map(function (i) { return i.Id; }), serverId: serverId, enableAddToPlayQueue: false, defaultValue: 'new' + }).catch(() => { + // Dialog closed }); }); + }).catch(err => { + console.error('[savePlaylist] failed to load playlist editor', err); }); } diff --git a/src/components/shortcuts.js b/src/components/shortcuts.js index 03418f3e2..cadb8cc20 100644 --- a/src/components/shortcuts.js +++ b/src/components/shortcuts.js @@ -282,11 +282,15 @@ function executeAction(card, target, action) { function addToPlaylist(item) { import('./playlisteditor/playlisteditor').then(({ default: PlaylistEditor }) => { - new PlaylistEditor().show({ + const playlistEditor = new PlaylistEditor(); + playlistEditor.show({ items: [item.Id], serverId: item.ServerId - + }).catch(() => { + // Dialog closed }); + }).catch(err => { + console.error('[addToPlaylist] failed to load playlist editor', err); }); } diff --git a/src/controllers/list.js b/src/controllers/list.js index c5497be27..69b713e15 100644 --- a/src/controllers/list.js +++ b/src/controllers/list.js @@ -401,10 +401,15 @@ function onNewItemClick() { const instance = this; import('../components/playlisteditor/playlisteditor').then(({ default: PlaylistEditor }) => { - new PlaylistEditor({ + const playlistEditor = new PlaylistEditor(); + playlistEditor.show({ items: [], serverId: instance.params.serverId + }).catch(() => { + // Dialog closed }); + }).catch(err => { + console.error('[onNewItemClick] failed to load playlist editor', err); }); } diff --git a/src/scripts/playlists.js b/src/scripts/playlists.js index b8b7380b5..33963bf8a 100644 --- a/src/scripts/playlists.js +++ b/src/scripts/playlists.js @@ -187,10 +187,15 @@ export default function (view) { view.querySelector('.btnNewPlaylist').addEventListener('click', function () { import('../components/playlisteditor/playlisteditor').then(({ default: PlaylistEditor }) => { const serverId = ApiClient.serverInfo().Id; - new PlaylistEditor({ + const playlistEditor = new PlaylistEditor(); + playlistEditor.show({ items: [], serverId: serverId + }).catch(() => { + // Dialog closed }); + }).catch(err => { + console.error('[btnNewPlaylist] failed to load playlist editor', err); }); }); onViewStyleChange(); From ed3e91086031171f3ab6f045f0bb962e46a8a034 Mon Sep 17 00:00:00 2001 From: Brad Beattie Date: Wed, 18 Oct 2023 10:10:35 -0700 Subject: [PATCH 02/34] Update search.tsx --- src/apps/stable/routes/search.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/apps/stable/routes/search.tsx b/src/apps/stable/routes/search.tsx index c9285f607..10583c381 100644 --- a/src/apps/stable/routes/search.tsx +++ b/src/apps/stable/routes/search.tsx @@ -11,6 +11,7 @@ import globalize from '../../../scripts/globalize'; const Search: FunctionComponent = () => { const [ query, setQuery ] = useState(); const [ searchParams ] = useSearchParams(); + if (!query && searchParams.get('q')) setQuery(searchParams.get('q') || ''); return ( Date: Wed, 18 Oct 2023 10:15:04 -0700 Subject: [PATCH 03/34] Update search.tsx --- src/apps/stable/routes/search.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apps/stable/routes/search.tsx b/src/apps/stable/routes/search.tsx index 10583c381..ca7764f30 100644 --- a/src/apps/stable/routes/search.tsx +++ b/src/apps/stable/routes/search.tsx @@ -11,7 +11,7 @@ import globalize from '../../../scripts/globalize'; const Search: FunctionComponent = () => { const [ query, setQuery ] = useState(); const [ searchParams ] = useSearchParams(); - if (!query && searchParams.get('q')) setQuery(searchParams.get('q') || ''); + if (!query && searchParams.get('query')) setQuery(searchParams.get('query') || ''); return ( Date: Wed, 18 Oct 2023 10:15:52 -0700 Subject: [PATCH 04/34] Update index.js --- src/controllers/itemDetails/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/itemDetails/index.js b/src/controllers/itemDetails/index.js index 58e780bd7..99f6d76f0 100644 --- a/src/controllers/itemDetails/index.js +++ b/src/controllers/itemDetails/index.js @@ -1258,11 +1258,11 @@ function renderTags(page, item) { } for (let i = 0, length = tags.length; i < length; i++) { - tagElements.push(tags[i]); + tagElements.push('' + tags[i] + ''); } if (tagElements.length) { - itemTags.innerText = globalize.translate('TagsValue', tagElements.join(', ')); + itemTags.innerHTML = globalize.translate('TagsValue', tagElements.join(', ')); itemTags.classList.remove('hide'); } else { itemTags.innerHTML = ''; From 6a1706ba78c41f24261350d272ce62d8a4241df3 Mon Sep 17 00:00:00 2001 From: Brad Beattie Date: Thu, 19 Oct 2023 10:22:34 -0700 Subject: [PATCH 05/34] Update SearchFields.tsx --- src/components/search/SearchFields.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/search/SearchFields.tsx b/src/components/search/SearchFields.tsx index cdc8c69a3..70bbb8a76 100644 --- a/src/components/search/SearchFields.tsx +++ b/src/components/search/SearchFields.tsx @@ -31,17 +31,23 @@ const createInputElement = () => ({ const normalizeInput = (value = '') => value.trim(); type SearchFieldsProps = { + query: string, onSearch?: (query: string) => void }; // eslint-disable-next-line @typescript-eslint/no-empty-function -const SearchFields: FunctionComponent = ({ onSearch = () => {} }: SearchFieldsProps) => { +const SearchFields: FunctionComponent = ({ onSearch = () => {}, query }: SearchFieldsProps) => { const element = useRef(null); const getSearchInput = () => element?.current?.querySelector('.searchfields-txtSearch'); const debouncedOnSearch = useMemo(() => debounce(onSearch, 400), [onSearch]); + const initSearchInput = getSearchInput(); + if (initSearchInput) { + initSearchInput.value = query; + } + useEffect(() => { getSearchInput()?.addEventListener('input', e => { debouncedOnSearch(normalizeInput((e.target as HTMLInputElement).value)); From ba099a0e3d600ee11287d47a8e0d6ef7dffa35a4 Mon Sep 17 00:00:00 2001 From: Brad Beattie Date: Thu, 19 Oct 2023 10:23:16 -0700 Subject: [PATCH 06/34] Update search.tsx --- src/apps/stable/routes/search.tsx | 34 +++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/src/apps/stable/routes/search.tsx b/src/apps/stable/routes/search.tsx index ca7764f30..882e97d44 100644 --- a/src/apps/stable/routes/search.tsx +++ b/src/apps/stable/routes/search.tsx @@ -1,4 +1,4 @@ -import React, { FunctionComponent, useState } from 'react'; +import React, { FunctionComponent, useEffect, useRef, useState } from 'react'; import { useSearchParams } from 'react-router-dom'; import Page from '../../../components/Page'; @@ -7,11 +7,37 @@ import SearchResults from '../../../components/search/SearchResults'; import SearchSuggestions from '../../../components/search/SearchSuggestions'; import LiveTVSearchResults from '../../../components/search/LiveTVSearchResults'; import globalize from '../../../scripts/globalize'; +import { history } from '../../../components/router/appRouter'; + +function usePrevious(value: string) { + const ref = useRef(''); + useEffect(() => { + ref.current = value; + }); + return ref.current; +} const Search: FunctionComponent = () => { - const [ query, setQuery ] = useState(); const [ searchParams ] = useSearchParams(); - if (!query && searchParams.get('query')) setQuery(searchParams.get('query') || ''); + const [ query, setQuery ] = useState(searchParams.get('query') || ''); + const prevQuery = usePrevious(query); + + console.error('Search component initialized', { 'urlParam': searchParams.get('query'), 'obj.query': query, 'prevQuery': prevQuery }); + if (query == prevQuery && searchParams.get('query') != query) { + console.error('SET QUERY VIA URL', searchParams.get('query')); + setQuery(searchParams.get('query') || ''); + } + + useEffect(() => { + const newSearch = query ? `?query=${query}` : ''; + if (query != prevQuery && newSearch != history.location.search) { + /* Explicitly using `window.history.pushState` instead of `history.replace` as the use of the latter + triggers a re-rendering of this component, resulting in double-execution searches. If there's a + way to use `history` without this side effect, it would likely be preferable. */ + console.error('PUSH STATE VIA QUERY', query); + window.history.pushState({}, '', `/#${history.location.pathname}${newSearch}`); + } + }, [query, prevQuery]); return ( { title={globalize.translate('Search')} className='mainAnimatedPage libraryPage allLibraryPage noSecondaryNavPage' > - + {!query && Date: Thu, 19 Oct 2023 10:23:52 -0700 Subject: [PATCH 07/34] Update search.tsx --- src/apps/stable/routes/search.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/apps/stable/routes/search.tsx b/src/apps/stable/routes/search.tsx index 882e97d44..309e7820c 100644 --- a/src/apps/stable/routes/search.tsx +++ b/src/apps/stable/routes/search.tsx @@ -22,9 +22,7 @@ const Search: FunctionComponent = () => { const [ query, setQuery ] = useState(searchParams.get('query') || ''); const prevQuery = usePrevious(query); - console.error('Search component initialized', { 'urlParam': searchParams.get('query'), 'obj.query': query, 'prevQuery': prevQuery }); if (query == prevQuery && searchParams.get('query') != query) { - console.error('SET QUERY VIA URL', searchParams.get('query')); setQuery(searchParams.get('query') || ''); } @@ -34,7 +32,6 @@ const Search: FunctionComponent = () => { /* Explicitly using `window.history.pushState` instead of `history.replace` as the use of the latter triggers a re-rendering of this component, resulting in double-execution searches. If there's a way to use `history` without this side effect, it would likely be preferable. */ - console.error('PUSH STATE VIA QUERY', query); window.history.pushState({}, '', `/#${history.location.pathname}${newSearch}`); } }, [query, prevQuery]); From d6b8ce0f49e011210c76f75c4c8c6abf16c9e15d Mon Sep 17 00:00:00 2001 From: Brad Beattie Date: Thu, 19 Oct 2023 14:51:11 -0700 Subject: [PATCH 08/34] Update search.tsx --- src/apps/stable/routes/search.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/apps/stable/routes/search.tsx b/src/apps/stable/routes/search.tsx index 309e7820c..095f73585 100644 --- a/src/apps/stable/routes/search.tsx +++ b/src/apps/stable/routes/search.tsx @@ -19,11 +19,12 @@ function usePrevious(value: string) { const Search: FunctionComponent = () => { const [ searchParams ] = useSearchParams(); - const [ query, setQuery ] = useState(searchParams.get('query') || ''); + const urlQuery = searchParams.get('query') || ''; + const [ query, setQuery ] = useState(urlQuery); const prevQuery = usePrevious(query); - if (query == prevQuery && searchParams.get('query') != query) { - setQuery(searchParams.get('query') || ''); + if (query == prevQuery && urlQuery != query) { + setQuery(urlQuery); } useEffect(() => { From bdecaa99306700d7385b2e2deb785ca5a1c51d92 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Tue, 13 Feb 2024 01:09:08 -0500 Subject: [PATCH 09/34] Fix issues with search url param --- package-lock.json | 29 +++- package.json | 1 + src/apps/stable/routes/search.tsx | 59 +++---- src/components/search/LiveTVSearchResults.tsx | 156 +++++++++--------- src/components/search/SearchFields.tsx | 96 ++++------- src/components/search/SearchResults.tsx | 28 ++-- src/elements/emby-input/Input.tsx | 59 +++++++ src/hooks/usePrevious.ts | 17 ++ 8 files changed, 256 insertions(+), 189 deletions(-) create mode 100644 src/elements/emby-input/Input.tsx create mode 100644 src/hooks/usePrevious.ts diff --git a/package-lock.json b/package-lock.json index cf80543d0..c371ea714 100644 --- a/package-lock.json +++ b/package-lock.json @@ -58,6 +58,7 @@ "screenfull": "6.0.2", "sortablejs": "1.15.2", "swiper": "11.0.5", + "usehooks-ts": "2.14.0", "webcomponents.js": "0.7.24", "whatwg-fetch": "3.6.20" }, @@ -12671,8 +12672,7 @@ "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "dev": true + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" }, "node_modules/lodash.memoize": { "version": "4.1.2", @@ -21900,6 +21900,20 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/usehooks-ts": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-2.14.0.tgz", + "integrity": "sha512-jnhrjTRJoJS7cFxz63tRYc5mzTKf/h+Ii8P0PDHymT9qDe4ZA2/gzDRmDR4WGausg5X8wMIdghwi3BBCN9JKow==", + "dependencies": { + "lodash.debounce": "^4.0.8" + }, + "engines": { + "node": ">=16.15.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -31866,8 +31880,7 @@ "lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "dev": true + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" }, "lodash.memoize": { "version": "4.1.2", @@ -38668,6 +38681,14 @@ "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", "requires": {} }, + "usehooks-ts": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-2.14.0.tgz", + "integrity": "sha512-jnhrjTRJoJS7cFxz63tRYc5mzTKf/h+Ii8P0PDHymT9qDe4ZA2/gzDRmDR4WGausg5X8wMIdghwi3BBCN9JKow==", + "requires": { + "lodash.debounce": "^4.0.8" + } + }, "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 91b87c28f..ee026b811 100644 --- a/package.json +++ b/package.json @@ -119,6 +119,7 @@ "screenfull": "6.0.2", "sortablejs": "1.15.2", "swiper": "11.0.5", + "usehooks-ts": "2.14.0", "webcomponents.js": "0.7.24", "whatwg-fetch": "3.6.20" }, diff --git a/src/apps/stable/routes/search.tsx b/src/apps/stable/routes/search.tsx index 095f73585..da55e9d8e 100644 --- a/src/apps/stable/routes/search.tsx +++ b/src/apps/stable/routes/search.tsx @@ -1,41 +1,36 @@ -import React, { FunctionComponent, useEffect, useRef, useState } from 'react'; -import { useSearchParams } from 'react-router-dom'; +import React, { type FC, useEffect, useState } from 'react'; +import { useNavigate, useSearchParams } from 'react-router-dom'; -import Page from '../../../components/Page'; -import SearchFields from '../../../components/search/SearchFields'; -import SearchResults from '../../../components/search/SearchResults'; -import SearchSuggestions from '../../../components/search/SearchSuggestions'; -import LiveTVSearchResults from '../../../components/search/LiveTVSearchResults'; -import globalize from '../../../scripts/globalize'; -import { history } from '../../../components/router/appRouter'; +import Page from 'components/Page'; +import SearchFields from 'components/search/SearchFields'; +import SearchResults from 'components/search/SearchResults'; +import SearchSuggestions from 'components/search/SearchSuggestions'; +import LiveTVSearchResults from 'components/search/LiveTVSearchResults'; +import { usePrevious } from 'hooks/usePrevious'; +import globalize from 'scripts/globalize'; -function usePrevious(value: string) { - const ref = useRef(''); - useEffect(() => { - ref.current = value; - }); - return ref.current; -} - -const Search: FunctionComponent = () => { - const [ searchParams ] = useSearchParams(); +const Search: FC = () => { + const navigate = useNavigate(); + const [ searchParams, setSearchParams ] = useSearchParams(); const urlQuery = searchParams.get('query') || ''; - const [ query, setQuery ] = useState(urlQuery); - const prevQuery = usePrevious(query); - - if (query == prevQuery && urlQuery != query) { - setQuery(urlQuery); - } + const [ query, setQuery ] = useState(urlQuery); + const prevQuery = usePrevious(query, ''); useEffect(() => { - const newSearch = query ? `?query=${query}` : ''; - if (query != prevQuery && newSearch != history.location.search) { - /* Explicitly using `window.history.pushState` instead of `history.replace` as the use of the latter - triggers a re-rendering of this component, resulting in double-execution searches. If there's a - way to use `history` without this side effect, it would likely be preferable. */ - window.history.pushState({}, '', `/#${history.location.pathname}${newSearch}`); + if (query !== prevQuery) { + if (query === '' && urlQuery !== '') { + // The query input has been cleared; navigate back to the search landing page + navigate(-1); + } else if (query !== urlQuery) { + // Update the query url param value + searchParams.set('query', query); + setSearchParams(searchParams, { replace: !!urlQuery }); + } + } else if (query !== urlQuery) { + // Update the query if the query url param has changed + setQuery(urlQuery); } - }, [query, prevQuery]); + }, [query, prevQuery, navigate, searchParams, setSearchParams, urlQuery]); return ( = ({ serverId = window.ApiClient.serverId(), parentId, collectionType, query }: LiveTVSearchResultsProps) => { +const LiveTVSearchResults: FC = ({ serverId = window.ApiClient.serverId(), parentId, collectionType, query }: LiveTVSearchResultsProps) => { const [ movies, setMovies ] = useState([]); const [ episodes, setEpisodes ] = useState([]); const [ sports, setSports ] = useState([]); @@ -38,23 +39,24 @@ const LiveTVSearchResults: FunctionComponent = ({ serv const [ news, setNews ] = useState([]); const [ programs, setPrograms ] = useState([]); const [ channels, setChannels ] = useState([]); + const [ debouncedQuery ] = useDebounceValue(query, 500); + + const getDefaultParameters = useCallback(() => ({ + ParentId: parentId, + searchTerm: debouncedQuery, + Limit: 24, + Fields: 'PrimaryImageAspectRatio,CanDelete,MediaSourceCount', + Recursive: true, + EnableTotalRecordCount: false, + ImageTypeLimit: 1, + IncludePeople: false, + IncludeMedia: false, + IncludeGenres: false, + IncludeStudios: false, + IncludeArtists: false + }), [ parentId, debouncedQuery ]); useEffect(() => { - const getDefaultParameters = () => ({ - ParentId: parentId, - searchTerm: query, - Limit: 24, - Fields: 'PrimaryImageAspectRatio,CanDelete,MediaSourceCount', - Recursive: true, - EnableTotalRecordCount: false, - ImageTypeLimit: 1, - IncludePeople: false, - IncludeMedia: false, - IncludeGenres: false, - IncludeStudios: false, - IncludeArtists: false - }); - const fetchItems = (apiClient: ApiClient, params = {}) => apiClient?.getItems( apiClient?.getCurrentUserId(), { @@ -73,65 +75,67 @@ const LiveTVSearchResults: FunctionComponent = ({ serv setPrograms([]); setChannels([]); - if (query && collectionType === CollectionType.Livetv) { - const apiClient = ServerConnections.getApiClient(serverId); - - // Movies row - fetchItems(apiClient, { - IncludeItemTypes: 'LiveTvProgram', - IsMovie: true - }) - .then(result => setMovies(result.Items || [])) - .catch(() => setMovies([])); - // Episodes row - fetchItems(apiClient, { - IncludeItemTypes: 'LiveTvProgram', - IsMovie: false, - IsSeries: true, - IsSports: false, - IsKids: false, - IsNews: false - }) - .then(result => setEpisodes(result.Items || [])) - .catch(() => setEpisodes([])); - // Sports row - fetchItems(apiClient, { - IncludeItemTypes: 'LiveTvProgram', - IsSports: true - }) - .then(result => setSports(result.Items || [])) - .catch(() => setSports([])); - // Kids row - fetchItems(apiClient, { - IncludeItemTypes: 'LiveTvProgram', - IsKids: true - }) - .then(result => setKids(result.Items || [])) - .catch(() => setKids([])); - // News row - fetchItems(apiClient, { - IncludeItemTypes: 'LiveTvProgram', - IsNews: true - }) - .then(result => setNews(result.Items || [])) - .catch(() => setNews([])); - // Programs row - fetchItems(apiClient, { - IncludeItemTypes: 'LiveTvProgram', - IsMovie: false, - IsSeries: false, - IsSports: false, - IsKids: false, - IsNews: false - }) - .then(result => setPrograms(result.Items || [])) - .catch(() => setPrograms([])); - // Channels row - fetchItems(apiClient, { IncludeItemTypes: 'TvChannel' }) - .then(result => setChannels(result.Items || [])) - .catch(() => setChannels([])); + if (!debouncedQuery || collectionType !== CollectionType.Livetv) { + return; } - }, [collectionType, parentId, query, serverId]); + + const apiClient = ServerConnections.getApiClient(serverId); + + // Movies row + fetchItems(apiClient, { + IncludeItemTypes: 'LiveTvProgram', + IsMovie: true + }) + .then(result => setMovies(result.Items || [])) + .catch(() => setMovies([])); + // Episodes row + fetchItems(apiClient, { + IncludeItemTypes: 'LiveTvProgram', + IsMovie: false, + IsSeries: true, + IsSports: false, + IsKids: false, + IsNews: false + }) + .then(result => setEpisodes(result.Items || [])) + .catch(() => setEpisodes([])); + // Sports row + fetchItems(apiClient, { + IncludeItemTypes: 'LiveTvProgram', + IsSports: true + }) + .then(result => setSports(result.Items || [])) + .catch(() => setSports([])); + // Kids row + fetchItems(apiClient, { + IncludeItemTypes: 'LiveTvProgram', + IsKids: true + }) + .then(result => setKids(result.Items || [])) + .catch(() => setKids([])); + // News row + fetchItems(apiClient, { + IncludeItemTypes: 'LiveTvProgram', + IsNews: true + }) + .then(result => setNews(result.Items || [])) + .catch(() => setNews([])); + // Programs row + fetchItems(apiClient, { + IncludeItemTypes: 'LiveTvProgram', + IsMovie: false, + IsSeries: false, + IsSports: false, + IsKids: false, + IsNews: false + }) + .then(result => setPrograms(result.Items || [])) + .catch(() => setPrograms([])); + // Channels row + fetchItems(apiClient, { IncludeItemTypes: 'TvChannel' }) + .then(result => setChannels(result.Items || [])) + .catch(() => setChannels([])); + }, [collectionType, debouncedQuery, getDefaultParameters, parentId, serverId]); return (
= ({ serv 'searchResults', 'padded-bottom-page', 'padded-top', - { 'hide': !query || collectionType !== CollectionType.Livetv } + { 'hide': !debouncedQuery || collectionType !== CollectionType.Livetv } )} > ({ - __html: `` -}); +import 'material-design-icons-iconfont'; -const normalizeInput = (value = '') => value.trim(); +import '../../styles/flexstyles.scss'; +import './searchfields.scss'; type SearchFieldsProps = { query: string, onSearch?: (query: string) => void }; -// eslint-disable-next-line @typescript-eslint/no-empty-function -const SearchFields: FunctionComponent = ({ onSearch = () => {}, query }: SearchFieldsProps) => { - const element = useRef(null); - - const getSearchInput = () => element?.current?.querySelector('.searchfields-txtSearch'); - - const debouncedOnSearch = useMemo(() => debounce(onSearch, 400), [onSearch]); - - const initSearchInput = getSearchInput(); - if (initSearchInput) { - initSearchInput.value = query; - } - - useEffect(() => { - getSearchInput()?.addEventListener('input', e => { - debouncedOnSearch(normalizeInput((e.target as HTMLInputElement).value)); - }); - getSearchInput()?.focus(); - - return () => { - debouncedOnSearch.cancel(); - }; - }, [debouncedOnSearch]); - +const SearchFields: FC = ({ + onSearch = () => { /* no-op */ }, + query +}: SearchFieldsProps) => { const onAlphaPicked = useCallback((e: Event) => { const value = (e as CustomEvent).detail.value; - const searchInput = getSearchInput(); - - if (!searchInput) { - console.error('Unexpected null reference'); - return; - } if (value === 'backspace') { - const currentValue = searchInput.value; - searchInput.value = currentValue.length ? currentValue.substring(0, currentValue.length - 1) : ''; + onSearch(query.length ? query.substring(0, query.length - 1) : ''); } else { - searchInput.value += value; + onSearch(query + value); } + }, [ onSearch, query ]); - searchInput.dispatchEvent(new CustomEvent('input', { bubbles: true })); - }, []); + const onChange = useCallback((e: ChangeEvent) => { + onSearch(e.target.value); + }, [ onSearch ]); return ( -
+
{layoutManager.tv && !browser.tv && diff --git a/src/components/search/SearchResults.tsx b/src/components/search/SearchResults.tsx index e267a4f5d..dbba7eb70 100644 --- a/src/components/search/SearchResults.tsx +++ b/src/components/search/SearchResults.tsx @@ -1,8 +1,9 @@ import type { BaseItemDto, BaseItemDtoQueryResult } from '@jellyfin/sdk/lib/generated-client'; import type { ApiClient } from 'jellyfin-apiclient'; import classNames from 'classnames'; -import React, { FunctionComponent, useCallback, useEffect, useState } from 'react'; +import React, { type FC, useCallback, useEffect, useState } from 'react'; import { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type'; +import { useDebounceValue } from 'usehooks-ts'; import globalize from '../../scripts/globalize'; import ServerConnections from '../ServerConnections'; @@ -30,7 +31,7 @@ const isTVShows = (collectionType: string) => collectionType === CollectionType. /* * React component to display search result rows for global search and non-live tv library search */ -const SearchResults: FunctionComponent = ({ serverId = window.ApiClient.serverId(), parentId, collectionType, query }: SearchResultsProps) => { +const SearchResults: FC = ({ serverId = window.ApiClient.serverId(), parentId, collectionType, query }: SearchResultsProps) => { const [ movies, setMovies ] = useState([]); const [ shows, setShows ] = useState([]); const [ episodes, setEpisodes ] = useState([]); @@ -47,11 +48,12 @@ const SearchResults: FunctionComponent = ({ serverId = windo const [ books, setBooks ] = useState([]); const [ people, setPeople ] = useState([]); const [ collections, setCollections ] = useState([]); - const [isLoading, setIsLoading] = useState(false); + const [ isLoading, setIsLoading ] = useState(false); + const [ debouncedQuery ] = useDebounceValue(query, 500); const getDefaultParameters = useCallback(() => ({ ParentId: parentId, - searchTerm: query, + searchTerm: debouncedQuery, Limit: 100, Fields: 'PrimaryImageAspectRatio,CanDelete,MediaSourceCount', Recursive: true, @@ -62,7 +64,7 @@ const SearchResults: FunctionComponent = ({ serverId = windo IncludeGenres: false, IncludeStudios: false, IncludeArtists: false - }), [parentId, query]); + }), [ parentId, debouncedQuery ]); const fetchArtists = useCallback((apiClient: ApiClient, params = {}) => ( apiClient?.getArtists( @@ -97,6 +99,10 @@ const SearchResults: FunctionComponent = ({ serverId = windo ).then(ensureNonNullItems) ), [getDefaultParameters]); + useEffect(() => { + if (query) setIsLoading(true); + }, [ query ]); + useEffect(() => { // Reset state setMovies([]); @@ -116,13 +122,11 @@ const SearchResults: FunctionComponent = ({ serverId = windo setPeople([]); setCollections([]); - if (!query) { + if (!debouncedQuery) { setIsLoading(false); return; } - setIsLoading(true); - const apiClient = ServerConnections.getApiClient(serverId); const fetchPromises = []; @@ -230,7 +234,7 @@ const SearchResults: FunctionComponent = ({ serverId = windo console.error('An error occurred while fetching data:', error); setIsLoading(false); // Set loading to false even if an error occurs }); - }, [collectionType, fetchArtists, fetchItems, fetchPeople, query, serverId]); + }, [collectionType, fetchArtists, fetchItems, fetchPeople, debouncedQuery, serverId]); const allEmpty = [movies, shows, episodes, videos, programs, channels, playlists, artists, albums, songs, photoAlbums, photos, audioBooks, books, people, collections].every(arr => arr.length === 0); @@ -240,7 +244,7 @@ const SearchResults: FunctionComponent = ({ serverId = windo 'searchResults', 'padded-bottom-page', 'padded-top', - { 'hide': !query || collectionType === CollectionType.Livetv } + { 'hide': !debouncedQuery || collectionType === CollectionType.Livetv } )} > {isLoading ? ( @@ -335,8 +339,8 @@ const SearchResults: FunctionComponent = ({ serverId = windo cardOptions={{ coverImage: true }} /> - {allEmpty && query && !isLoading && ( -
{globalize.translate('SearchResultsEmpty', query)}
+ {allEmpty && debouncedQuery && !isLoading && ( +
{globalize.translate('SearchResultsEmpty', debouncedQuery)}
)} )} diff --git a/src/elements/emby-input/Input.tsx b/src/elements/emby-input/Input.tsx new file mode 100644 index 000000000..327976dc7 --- /dev/null +++ b/src/elements/emby-input/Input.tsx @@ -0,0 +1,59 @@ +import React, { type DetailedHTMLProps, type InputHTMLAttributes, type FC, useState, useCallback } from 'react'; + +import './emby-input.scss'; +import classNames from 'classnames'; + +interface InputProps extends DetailedHTMLProps, HTMLInputElement> { + id: string, + label?: string +} + +const Input: FC = ({ + id, + label, + className, + onBlur, + onFocus, + ...props +}) => { + const [ isFocused, setIsFocused ] = useState(false); + + const onBlurInternal = useCallback(e => { + setIsFocused(false); + onBlur?.(e); + }, [ onBlur ]); + + const onFocusInternal = useCallback(e => { + setIsFocused(true); + onFocus?.(e); + }, [ onFocus ]); + + return ( + <> + + + + ); +}; + +export default Input; diff --git a/src/hooks/usePrevious.ts b/src/hooks/usePrevious.ts new file mode 100644 index 000000000..c9e74361b --- /dev/null +++ b/src/hooks/usePrevious.ts @@ -0,0 +1,17 @@ +import { useEffect, useRef } from 'react'; + +/** + * A hook that returns the previous value of a stateful value. + * @param value A stateful value created by a `useState` hook. + * @param initialValue The default value. + * @returns The previous value. + */ +export function usePrevious(value: T, initialValue?: T): T | undefined { + const ref = useRef(initialValue); + + useEffect(() => { + ref.current = value; + }); + + return ref.current; +} From 48eba8953f1abe5d10146b8fce7061c386519b9f Mon Sep 17 00:00:00 2001 From: Andi Chandler Date: Tue, 13 Feb 2024 15:38:52 +0000 Subject: [PATCH 10/34] 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 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings/en-gb.json b/src/strings/en-gb.json index d28801c63..6849dc19f 100644 --- a/src/strings/en-gb.json +++ b/src/strings/en-gb.json @@ -984,7 +984,7 @@ "LabelCustomCertificatePathHelp": "Path to a PKCS #12 file containing a certificate and private key to enable TLS support on a custom domain.", "LabelCurrentPassword": "Current password", "LabelCriticRating": "Critics rating", - "LabelCountry": "Country", + "LabelCountry": "Country/Region", "LabelContentType": "Content type", "LabelCommunityRating": "Community rating", "LabelCertificatePassword": "Certificate password", From dd79544593ab6284bcf7512f5653af9b6f97d1a5 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Tue, 13 Feb 2024 12:47:44 -0500 Subject: [PATCH 11/34] Babelify usehooks-ts --- webpack.common.js | 1 + 1 file changed, 1 insertion(+) diff --git a/webpack.common.js b/webpack.common.js index dd85d0318..45baccfb9 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -204,6 +204,7 @@ const config = { path.resolve(__dirname, 'node_modules/screenfull'), path.resolve(__dirname, 'node_modules/ssr-window'), path.resolve(__dirname, 'node_modules/swiper'), + path.resolve(__dirname, 'node_modules/usehooks-ts'), path.resolve(__dirname, 'src') ], use: [{ From 9fa0b4f5b2546df28469fea7e7a9115fbaa0dc14 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Tue, 13 Feb 2024 17:10:48 -0500 Subject: [PATCH 12/34] Escape html in tags --- src/controllers/itemDetails/index.js | 10 +++++++--- src/elements/emby-input/Input.tsx | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/controllers/itemDetails/index.js b/src/controllers/itemDetails/index.js index 99f6d76f0..e169a82c3 100644 --- a/src/controllers/itemDetails/index.js +++ b/src/controllers/itemDetails/index.js @@ -1257,9 +1257,13 @@ function renderTags(page, item) { tags = []; } - for (let i = 0, length = tags.length; i < length; i++) { - tagElements.push('' + tags[i] + ''); - } + tags.forEach(tag => { + tagElements.push( + `` + + escapeHtml(tag) + + '' + ); + }); if (tagElements.length) { itemTags.innerHTML = globalize.translate('TagsValue', tagElements.join(', ')); diff --git a/src/elements/emby-input/Input.tsx b/src/elements/emby-input/Input.tsx index 327976dc7..63cde7084 100644 --- a/src/elements/emby-input/Input.tsx +++ b/src/elements/emby-input/Input.tsx @@ -1,7 +1,7 @@ +import classNames from 'classnames'; import React, { type DetailedHTMLProps, type InputHTMLAttributes, type FC, useState, useCallback } from 'react'; import './emby-input.scss'; -import classNames from 'classnames'; interface InputProps extends DetailedHTMLProps, HTMLInputElement> { id: string, From 3b31d775e9833a07c899e1ad4550010c023682dd Mon Sep 17 00:00:00 2001 From: felix920506 Date: Tue, 13 Feb 2024 23:39:39 +0000 Subject: [PATCH 13/34] Translated using Weblate (Chinese (Traditional)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/zh_Hant/ --- src/strings/zh-tw.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings/zh-tw.json b/src/strings/zh-tw.json index ab2177b9f..114d756f8 100644 --- a/src/strings/zh-tw.json +++ b/src/strings/zh-tw.json @@ -264,7 +264,7 @@ "ButtonMore": "更多", "ButtonNetwork": "網路", "ButtonNextTrack": "下一首", - "ButtonOpen": "開", + "ButtonOpen": "開啟", "ButtonParentalControl": "家長控制", "ButtonPause": "暫停", "ButtonPreviousTrack": "上一首", From 3bbaf5626f9404949bc80d1c516a799f161085c7 Mon Sep 17 00:00:00 2001 From: AntonVucinic Date: Wed, 14 Feb 2024 12:10:15 +0000 Subject: [PATCH 14/34] Translated using Weblate (Croatian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/hr/ --- src/strings/hr.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/strings/hr.json b/src/strings/hr.json index 91789471a..8c105b61d 100644 --- a/src/strings/hr.json +++ b/src/strings/hr.json @@ -294,7 +294,7 @@ "LabelCollection": "Kolekcija", "LabelCommunityRating": "Ocjene zajednice", "LabelContentType": "Tip sadržaja", - "LabelCountry": "Zemlja", + "LabelCountry": "Zemlja/Regija", "LabelCriticRating": "Ocjena kritičara", "LabelCurrentPassword": "Trenutna lozinka", "LabelCustomCss": "Prilagođeni CSS kod", @@ -1512,5 +1512,8 @@ "ShowYear": "Prikaži godinu", "LabelLocalCustomCss": "Prilagođeni CSS kod za stil koji se odnosi samo na ovog klijenta. Možda ćete htjeti onemogućiti prilagođeni CSS kod poslužitelja.", "LabelPlayerDimensions": "Dimenzije izvođača", - "LabelSegmentKeepSecondsHelp": "Vrijeme u sekundama za koje se segmenti trebaju čuvati prije nego što se prebrišu. Mora biti veći od \"Throttle after\". Radi samo ako je omogućeno brisanje segmenta." + "LabelSegmentKeepSecondsHelp": "Vrijeme u sekundama za koje se segmenti trebaju čuvati prije nego što se prebrišu. Mora biti veći od \"Throttle after\". Radi samo ako je omogućeno brisanje segmenta.", + "LabelKnownProxies": "Poznati proxy-i", + "ButtonBackspace": "Backspace", + "LabelHomeScreenSectionValue": "{0}. odjeljak početne" } From a31f31fcd8433ed7d80e10998af873e9f2b6333a Mon Sep 17 00:00:00 2001 From: stanol Date: Wed, 14 Feb 2024 20:19:00 +0000 Subject: [PATCH 15/34] 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 cc3925083..46b82fc78 100644 --- a/src/strings/uk.json +++ b/src/strings/uk.json @@ -17,7 +17,7 @@ "HeaderUsers": "Користувачі", "LabelBirthDate": "Дата народження", "LabelBirthYear": "Рік народження", - "LabelCountry": "Країна", + "LabelCountry": "Країна/Регіон", "LabelCurrentPassword": "Поточний пароль", "LabelDeathDate": "Дата смерті", "LabelLanguage": "Мова", From 2d667cabbaa72e85588a3969f485bb4496ff6868 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Thu, 15 Feb 2024 11:28:10 -0500 Subject: [PATCH 16/34] Fix missing dependency in effect --- src/hooks/usePrevious.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/usePrevious.ts b/src/hooks/usePrevious.ts index c9e74361b..a97ced0bd 100644 --- a/src/hooks/usePrevious.ts +++ b/src/hooks/usePrevious.ts @@ -11,7 +11,7 @@ export function usePrevious(value: T, initialValue?: T): T | undefined { useEffect(() => { ref.current = value; - }); + }, [ value ]); return ref.current; } From dc11fd8b2ee1352aea38d4796051770ea20ea43e Mon Sep 17 00:00:00 2001 From: gearoidkeane Date: Thu, 15 Feb 2024 15:47:16 -0500 Subject: [PATCH 17/34] Added translation using Weblate (Irish) --- src/strings/ga.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/strings/ga.json diff --git a/src/strings/ga.json b/src/strings/ga.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/src/strings/ga.json @@ -0,0 +1 @@ +{} From 16844d4565e9d49016c25548defa3d50baca6247 Mon Sep 17 00:00:00 2001 From: gearoidkeane Date: Thu, 15 Feb 2024 20:51:01 +0000 Subject: [PATCH 18/34] Translated using Weblate (Irish) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/ga/ --- src/strings/ga.json | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/strings/ga.json b/src/strings/ga.json index 0967ef424..8dca7f76c 100644 --- a/src/strings/ga.json +++ b/src/strings/ga.json @@ -1 +1,26 @@ -{} +{ + "Actor": "Aisteoir", + "Add": "Cuir le", + "AddedOnValue": "Curtha {0}", + "AddToFavorites": "Cur le ceanáin", + "AgeValue": "({0} bliana d'aois)", + "AirDate": "Dáta Croalta", + "Aired": "Craolta", + "AlbumArtist": "Ealaíontóir Albam", + "Albums": "Albaim", + "Alerts": "Foláirimh", + "AllChannels": "Gach cainéal", + "AllLanguages": "Gach teanga", + "AllEpisodes": "Gach eagrán", + "AllLibraries": "Gach leabharlann", + "AllowCollectionManagement": "Lig don úsáideoir seo bailiúcháin a bhainistiú", + "Absolute": "Absalóideach", + "AccessRestrictedTryAgainLater": "Tá rochtain srianta faoi láthair. Bain triail eile as ar ball.", + "All": "Uilig", + "Album": "Albam", + "AddToCollection": "Cur le bailiúcháin", + "AddToPlaylist": "Cur le seinnliosta", + "AddToPlayQueue": "Cur le scuaine seinnteoir", + "AllComplexFormats": "Gach formáid chasta (ASS, SSA, VobSub, PGS, SUB, IDX, ...)", + "AllowedRemoteAddressesHelp": "Liosta scartha camóg de seoltaí IP nó iontráil IP/netmask do líonraí a ligfear nascadh go cianda. Má fhágtar folamh é, ceadófar gach ciansheoladh." +} From eaab0364d7d40a00d126760e17221e923b41b498 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Fri, 16 Feb 2024 20:22:15 -0500 Subject: [PATCH 19/34] Fix weird navigation when changing query param --- src/apps/stable/routes/search.tsx | 35 ++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/src/apps/stable/routes/search.tsx b/src/apps/stable/routes/search.tsx index da55e9d8e..34438c133 100644 --- a/src/apps/stable/routes/search.tsx +++ b/src/apps/stable/routes/search.tsx @@ -9,25 +9,36 @@ import LiveTVSearchResults from 'components/search/LiveTVSearchResults'; import { usePrevious } from 'hooks/usePrevious'; import globalize from 'scripts/globalize'; +const COLLECTION_TYPE_PARAM = 'collectionType'; +const PARENT_ID_PARAM = 'parentId'; +const QUERY_PARAM = 'query'; +const SERVER_ID_PARAM = 'serverId'; + const Search: FC = () => { const navigate = useNavigate(); const [ searchParams, setSearchParams ] = useSearchParams(); - const urlQuery = searchParams.get('query') || ''; + const urlQuery = searchParams.get(QUERY_PARAM) || ''; const [ query, setQuery ] = useState(urlQuery); const prevQuery = usePrevious(query, ''); useEffect(() => { if (query !== prevQuery) { if (query === '' && urlQuery !== '') { - // The query input has been cleared; navigate back to the search landing page - navigate(-1); + // The query input has been cleared; remove the url param + searchParams.delete(QUERY_PARAM); + setSearchParams(searchParams, { replace: true }); } else if (query !== urlQuery) { // Update the query url param value - searchParams.set('query', query); - setSearchParams(searchParams, { replace: !!urlQuery }); + searchParams.set(QUERY_PARAM, query); + setSearchParams(searchParams, { replace: true }); } } else if (query !== urlQuery) { // Update the query if the query url param has changed + if (!urlQuery) { + searchParams.delete(QUERY_PARAM); + setSearchParams(searchParams, { replace: true }); + } + setQuery(urlQuery); } }, [query, prevQuery, navigate, searchParams, setSearchParams, urlQuery]); @@ -41,19 +52,19 @@ const Search: FC = () => { {!query && } From 96692e4693aab9c3ddd802c67ecb637424323c15 Mon Sep 17 00:00:00 2001 From: yanghong Date: Sat, 17 Feb 2024 06:53:10 +0000 Subject: [PATCH 20/34] Translated using Weblate (Chinese (Simplified)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/zh_Hans/ --- src/strings/zh-cn.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings/zh-cn.json b/src/strings/zh-cn.json index 9c8eff1b4..62243cba3 100644 --- a/src/strings/zh-cn.json +++ b/src/strings/zh-cn.json @@ -272,7 +272,7 @@ "HeaderImageSettings": "图片设置", "HeaderInstall": "安装", "HeaderInstantMix": "速成合辑", - "HeaderKodiMetadataHelp": "要启用或禁用 NFO 元数据,请编辑库并找到“元数据保护程序”部分。", + "HeaderKodiMetadataHelp": "要启用或禁用 NFO 元数据,请编辑库并找到“元数据储存方式”部分。", "HeaderLatestEpisodes": "新增剧集", "HeaderLatestMedia": "新增媒体", "HeaderLatestMovies": "新增电影", From 798a93b20447372142a72cc1b579ca94aa152d95 Mon Sep 17 00:00:00 2001 From: queeup Date: Sat, 17 Feb 2024 21:15:21 +0000 Subject: [PATCH 21/34] Translated using Weblate (Turkish) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/tr/ --- src/strings/tr.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/strings/tr.json b/src/strings/tr.json index e1e0d98d0..ec3f81247 100644 --- a/src/strings/tr.json +++ b/src/strings/tr.json @@ -68,7 +68,7 @@ "LabelCachePath": "Önbellek yolu", "LabelCachePathHelp": "Sunucunun resim vb. önbellek dosyalarını tutması için bir yer belirt. Öntanımlı yeri kullanmak için boş bırak.", "LabelContentType": "İçerik türü", - "LabelCountry": "Ülke", + "LabelCountry": "Ülke/Bölge", "LabelCurrentPassword": "Kullanımdaki parola", "LabelDay": "Haftanın günü", "LabelEnableDlnaServer": "DLNA sunucusunu etkinleştir", @@ -248,7 +248,7 @@ "ChannelNameOnly": "Yalnızca {0} kanalı", "ConfirmDeleteItems": "Bu ögeleri silmek, onları hem dosya sisteminden hem de medya kütüphanenizden siler. Devam etmek istediğinize emin misiniz?", "ConfirmDeletion": "Silme İşlemini Onayla", - "ConfirmEndPlayerSession": "Jellyfin'i {0} tarihinde kapatmak ister misiniz?", + "ConfirmEndPlayerSession": "{0} üzerindeki Jellyfin'i kapatmak ister misiniz?", "Connect": "Bağlan", "Disconnect": "Bağlantıyı Kes", "Display": "Görünüm", From ac29b9b3387f6838d9bd13b8ba8c41defc3adadd Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Sun, 18 Feb 2024 01:11:42 -0500 Subject: [PATCH 22/34] Remove unused variable and fix no results alignment --- src/apps/stable/routes/search.tsx | 5 ++--- src/components/search/SearchResults.tsx | 4 +++- src/components/search/searchfields.scss | 11 ----------- 3 files changed, 5 insertions(+), 15 deletions(-) diff --git a/src/apps/stable/routes/search.tsx b/src/apps/stable/routes/search.tsx index 34438c133..5c483c9a8 100644 --- a/src/apps/stable/routes/search.tsx +++ b/src/apps/stable/routes/search.tsx @@ -1,5 +1,5 @@ import React, { type FC, useEffect, useState } from 'react'; -import { useNavigate, useSearchParams } from 'react-router-dom'; +import { useSearchParams } from 'react-router-dom'; import Page from 'components/Page'; import SearchFields from 'components/search/SearchFields'; @@ -15,7 +15,6 @@ const QUERY_PARAM = 'query'; const SERVER_ID_PARAM = 'serverId'; const Search: FC = () => { - const navigate = useNavigate(); const [ searchParams, setSearchParams ] = useSearchParams(); const urlQuery = searchParams.get(QUERY_PARAM) || ''; const [ query, setQuery ] = useState(urlQuery); @@ -41,7 +40,7 @@ const Search: FC = () => { setQuery(urlQuery); } - }, [query, prevQuery, navigate, searchParams, setSearchParams, urlQuery]); + }, [query, prevQuery, searchParams, setSearchParams, urlQuery]); return ( = ({ serverId = window.ApiClient.ser /> {allEmpty && debouncedQuery && !isLoading && ( -
{globalize.translate('SearchResultsEmpty', debouncedQuery)}
+
+ {globalize.translate('SearchResultsEmpty', debouncedQuery)} +
)} )} diff --git a/src/components/search/searchfields.scss b/src/components/search/searchfields.scss index b93638bdf..08d8515c8 100644 --- a/src/components/search/searchfields.scss +++ b/src/components/search/searchfields.scss @@ -9,14 +9,3 @@ font-size: 2em; align-self: flex-end; } - -.sorry-text { - font-size: 2em; - text-align: center; - font-family: inherit; - width: 100%; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); -} From a60683c1e232ea9a950a34ef64f5e1ff33fff11e Mon Sep 17 00:00:00 2001 From: hoanghuy309 Date: Sun, 18 Feb 2024 04:31:19 +0000 Subject: [PATCH 23/34] Translated using Weblate (Vietnamese) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/vi/ --- src/strings/vi.json | 43 ++++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/src/strings/vi.json b/src/strings/vi.json index 4588baae7..bad206862 100644 --- a/src/strings/vi.json +++ b/src/strings/vi.json @@ -35,7 +35,7 @@ "LabelNewPassword": "Mật khẩu mới", "LabelNewPasswordConfirm": "Xác nhận mật khẩu mới", "LabelSaveLocalMetadata": "Lưu các ảnh bìa minh họa và dữ liệu mô tả vào trong các thư mục phương tiện", - "LabelSaveLocalMetadataHelp": "Lưu ảnh bìa minh họa vào các thư mục phương tiện giúp đưa chúng vào một nơi dễ chỉnh sửa hơn.", + "LabelSaveLocalMetadataHelp": "Lưu ảnh bìa minh họa vào thư mục phương tiện để đưa chúng vào nơi dễ chỉnh sửa hơn.", "LabelTime": "Thời gian", "LabelYoureDone": "Bạn đã hoàn thành!", "MaxParentalRatingHelp": "Nội dung với đánh giá cao hơn sẽ được ẩn đi từ người dùng này.", @@ -99,7 +99,7 @@ "ButtonGotIt": "Hiểu rồi", "ButtonFullscreen": "Toàn màn hình", "ButtonForgotPassword": "Quên mật khẩu", - "ButtonEditOtherUserPreferences": "Chỉnh sửa hồ sơ, ảnh và sở thích cá nhân của người dùng này.", + "ButtonEditOtherUserPreferences": "Sửa hồ sơ, ảnh và sở thích cá nhân của người dùng này.", "ButtonChangeServer": "Đổi máy chủ", "ButtonBack": "Quay lại", "ButtonAudioTracks": "Bản Ghi Âm Thanh", @@ -206,7 +206,7 @@ "BoxSet": "", "Box": "Hộp", "Banner": "Ảnh bìa", - "Art": "", + "Art": "Minh họa", "Artist": "Ca Sĩ", "AllowFfmpegThrottlingHelp": "Tạm dừng quá trình chuyển mã hoặc chuyển đổi định dạng để tiết kiệm tài nguyên máy chủ khi việc này đã đủ để phát so với vị trí hiện tại. Nó hữu ích khi xem mà không cần tua. Tắt tính năng này nếu bạn gặp sự cố phát lại.", "AllowFfmpegThrottling": "Điều tiết sự chuyển mã", @@ -235,7 +235,7 @@ "DeleteDeviceConfirmation": "Bạn có chắc chắn muốn xoá thiết bị này? Nó sẽ xuất hiện lại khi người dùng đăng nhập bằng thiết bị đó.", "DeinterlaceMethodHelp": "Chọn cách khử xen kẽ để dùng khi phần mềm chuyển mã xen kẽ nội dung. Khi hỗ trợ tăng tốc phần cứng khử xen kẽ được bật, trình khử xen kẽ phần cứng sẽ dùng cài đặt này.", "DefaultSubtitlesHelp": "Phụ đề được tải dựa trên mặc định và bắt buộc gắn cờ trong dữ liệu mô tả. Ngôn ngữ ưa thích được xem xét khi có nhiều lựa chọn.", - "DefaultMetadataLangaugeDescription": "Đây là thiết lập mặc định chung, bạn có thể tuỳ chỉnh thiết lập riêng cho từng thư viện.", + "DefaultMetadataLangaugeDescription": "Đây là mặc định của bạn và có thể tùy chỉnh trên cơ sở từng thư viện.", "DisplayModeHelp": "Chọn kiểu bố trí giao diện mà bạn muốn.", "Download": "Tải về", "Down": "Xuống", @@ -243,9 +243,9 @@ "EnableCinemaMode": "Chế độ rạp phim", "EnableBackdropsHelp": "Hiển thị phông nền trong nền của một số trang trong khi duyệt thư viện.", "EditSubtitles": "Chỉnh sửa phụ đề", - "EditMetadata": "Chỉnh sửa thông tin", - "EditImages": "Chỉnh sửa hình ảnh", - "Edit": "Chỉnh sửa", + "EditMetadata": "Sửa dữ liệu mô tả", + "EditImages": "Sửa ảnh", + "Edit": "Sửa", "EasyPasswordHelp": "Mã PIN Tiện Lợi dùng cho việc truy cập ngoại tuyến trên thiết bị hỗ trợ và cũng dùng để đăng nhập trong mạng dễ dàng.", "DropShadow": "Bóng đổ", "DrmChannelsNotImported": "Những kênh được bảo vệ bản quyền sẽ không được nhập vào.", @@ -405,7 +405,7 @@ "HeaderError": "Lỗi", "HeaderEnabledFieldsHelp": "Bỏ chọn một mục để khóa nó và ngăn dữ liệu của nó bị thay đổi.", "HeaderEnabledFields": "Những Mục Được Kích Hoạt", - "HeaderEditImages": "Chỉnh Sửa Hình Ảnh", + "HeaderEditImages": "Sửa Ảnh", "HeaderEasyPinCode": "Mã PIN Tiện Lợi", "HeaderDownloadSync": "Tải Xuống Và Đồng Bộ", "HeaderDirectPlayProfileHelp": "Thêm cấu hình phát lại trực tiếp để chỉ ra những định dạng mà thiết bị có thể xử lý nguyên bản.", @@ -439,7 +439,7 @@ "HeaderScenes": "Phân Cảnh", "HeaderRunningTasks": "Những Tác Vụ Hoạt Động", "HeaderRevisionHistory": "Lịch Sử Chỉnh Sửa", - "HeaderResponseProfileHelp": "Hồ sơ phản hồi là phương thức tuỳ chỉnh thông tin gửi về thiết bị phát khi phát một số nội dung nhất định.", + "HeaderResponseProfileHelp": "Cấu hình phản hồi là phương thức tuỳ chỉnh thông tin gửi về thiết bị phát khi phát một số loại phương tiện.", "HeaderResponseProfile": "Hồ Sơ Phản Hồi", "HeaderRemoveMediaLocation": "Xoá Đường Dẫn Nội Dung", "HeaderRemoveMediaFolder": "Xoá Thư Mục Phương Tiện", @@ -605,7 +605,7 @@ "HeaderDVR": "DVR", "LabelExtractChaptersDuringLibraryScanHelp": "Tạo hình ảnh phân cảnh khi video được nhập trong quá trình quét thư viện. Nếu không chúng sẽ được trích xuất thông qua những tác vụ định kì, giúp cho quá trình quét thư viện diễn ra nhanh hơn.", "LabelExtractChaptersDuringLibraryScan": "Trích xuất hình ảnh phân đoạn khi quét thư viện", - "LabelBaseUrlHelp": "Thêm một thư mục con tùy chỉnh vào đường dẫn máy chủ. Ví dụ: http://example.com/<baseurl>", + "LabelBaseUrlHelp": "Thêm một thư mục con tùy chỉnh vào URL máy chủ. Ví dụ: http://example.com/<baseurl>", "LabelLoginDisclaimerHelp": "Một thông báo sẽ được hiển thị ở cuối trang đăng nhập.", "LabelLoginDisclaimer": "Hiển thị khi đăng nhập", "LabelLockItemToPreventChanges": "Khoá mục này để ngăn những thay đổi trong tương lai", @@ -954,7 +954,7 @@ "LiveBroadcasts": "Chương trình phát sóng trực tiếp", "Live": "Trực tiếp", "List": "Danh sách", - "LibraryAccessHelp": "Chọn thư viện để chia sẻ với người dùng này. Quản trị viên có thể chỉnh sửa các thư mục bằng trình quản lý dữ liệu mô tả.", + "LibraryAccessHelp": "Chọn thư viện chia sẻ với người dùng này. Quản trị viên có thể sửa các thư mục bằng trình quản lý dữ liệu mô tả.", "LeaveBlankToNotSetAPassword": "Bạn có thể để trống trường này để không đặt mật khẩu.", "LearnHowYouCanContribute": "Tìm hiểu cách bạn có thể đóng góp.", "LatestFromLibrary": "{0} Đã Thêm Vào Gần Đây", @@ -999,7 +999,7 @@ "LabelTranscodingProgress": "Tiến trình chuyển mã", "LabelTranscodingFramerate": "Tốc độ khung hình chuyển mã", "LabelTranscodes": "Chuyển mã", - "LabelTranscodingTempPathHelp": "Chỉ định một đường dẫn tùy chỉnh cho các tệp chuyển mã được cung cấp cho máy khách. Để trống để sử dụng mặc định của máy chủ.", + "LabelTranscodingTempPathHelp": "Tùy chỉnh một đường dẫn cho các tệp chuyển mã để cung cấp cho máy khách. Để trống để dùng mặc định.", "LabelTranscodePath": "Đường dẫn chuyển mã", "LabelTrackNumber": "Số bản ghi", "LabelTitle": "Tiêu đề", @@ -1271,7 +1271,7 @@ "Transcoding": "Chuyển mã", "Trailers": "Đoạn giới thiệu", "TabAccess": "Truy cập", - "SystemDlnaProfilesHelp": "Cấu hình hệ thống ở chế độ chỉ đọc. Thay đổi cấu hình hệ thống sẽ được lưu vào cấu hình tùy chỉnh mới.", + "SystemDlnaProfilesHelp": "Cấu hình hệ thống là chỉ đọc. Thay đổi cấu hình hệ thống sẽ được lưu vào cấu hình tùy chỉnh mới.", "Sports": "Thể thao", "SpecialFeatures": "Các tính năng đặc biệt", "SortName": "Sắp xếp tên", @@ -1322,7 +1322,7 @@ "UnsupportedPlayback": "Jellyfin không thể giải mã nội dung bảo vệ DRM nhưng sẽ cố gắng thử với tất cả nội dung, bao gồm cả các tiêu đề được bảo vệ. Một số tệp có thể xuất hiện toàn màu đen do mã hóa hoặc tính năng không được hỗ trợ, chẳng hạn như tiêu đề tương tác.", "EnableBlurHashHelp": "Hình ảnh đang được tải sẽ được hiển thị với một trình giữ chỗ duy nhất.", "ButtonPlayer": "Trình Phát", - "LabelOpenclDeviceHelp": "Đây là thiết bị OpenCL dùng để chỉnh tông màu. Phía bên trái của dấu chấm là số nền tảng và phía bên phải là số thiết bị trên nền tảng. Mặc định là 0.0. Bắt buộc tệp ứng dụng FFmpeg phải có tính năng tăng tốc phần cứng OpenCL.", + "LabelOpenclDeviceHelp": "Đây là thiết bị OpenCL dùng để chỉnh tông màu. Phía bên trái của dấu chấm là số nền tảng và phía bên phải là số thiết bị trên nền tảng. Mặc định là 0.0. Cần có tệp ứng dụng FFmpeg chứa phương pháp tăng tốc phần cứng OpenCL.", "LabelMaxMuxingQueueSizeHelp": "Số gói tối đa có thể được lưu vào bộ đệm trong khi chờ tất cả các luồng khởi tạo. Hãy thử tăng lên nếu bạn vẫn gặp lỗi \"Quá nhiều gói được lưu vào bộ đệm cho luồng đầu ra\" trong nhật ký FFmpeg. Giá trị đề xuất là 2048.", "ClearQueue": "Xóa hàng đợi", "LabelTonemappingParamHelp": "Điều chỉnh thuật toán ánh xạ tông màu. Các giá trị được đề xuất và mặc định là NaN. Nói chung là để trống.", @@ -1427,7 +1427,7 @@ "HeaderDeleteDevices": "Xóa Tất Cả Thiết Bị", "DeleteDevicesConfirmation": "Bạn có chắc muốn xóa hết các thiết bị không? Tất cả các phiên khác sẽ được đăng xuất. Thiết bị sẽ xuất hiện lại vào lần tiếp theo khi người dùng đăng nhập.", "DeleteAll": "Xóa Hết", - "EnableFallbackFontHelp": "Bật phông chữ thay thế tùy chỉnh. Điều này có thể tránh sự cố hiển thị phụ đề không chính xác.", + "EnableFallbackFontHelp": "Bật phông chữ thay thế tùy chỉnh. Nó có thể tránh lỗi kết xuất phụ đề không chính xác.", "EnableFallbackFont": "Bật phông chữ dự phòng", "LabelFallbackFontPathHelp": "Những phông chữ này được một số máy khách sử dụng để hiển thị phụ đề. Vui lòng tham khảo tài liệu để biết thêm thông tin.", "LabelFallbackFontPath": "Đường dẫn thư mục phông chữ dự phòng", @@ -1665,7 +1665,7 @@ "VideoRangeTypeNotSupported": "Không hỗ trợ loại dải động của video", "Interview": "Phỏng vấn", "Sample": "Mẫu", - "Trailer": "", + "Trailer": "Đoạn giới thiệu", "TypeOptionPluralVideo": "Videos", "ButtonSpace": "Cách", "ButtonBackspace": "Xóa", @@ -1722,7 +1722,7 @@ "EnableAudioNormalization": "Chuẩn hóa âm thanh", "GetThePlugin": "Nhận phần bổ sung", "Notifications": "Thông báo", - "NotificationsMovedMessage": "Chức năng thông báo đã chuyển sang plugin Webhook.", + "NotificationsMovedMessage": "Chức năng thông báo đã được chuyển sang plugin Webhook.", "LabelEnableLUFSScan": "Bật tính năng quét LUFS", "LabelEnableLUFSScanHelp": "Khách hàng có thể bình thường hóa việc phát lại âm thanh để có được âm lượng như nhau trên các bản nhạc. Điều này sẽ khiến việc quét thư viện lâu hơn và tốn nhiều tài nguyên hơn.", "PasswordRequiredForAdmin": "Cần có mật khẩu cho tài khoản quản trị viên.", @@ -1761,13 +1761,14 @@ "LabelSelectAudioNormalization": "Chuẩn Hóa Âm Thanh", "SearchResultsEmpty": "Rất tiếc! Không có kết quả tìm thấy cho \"{0}\"", "HeaderAllRecordings": "Tất Cả Bản Ghi", - "LabelTrackGain": "Chỉnh Nhạc", + "LabelTrackGain": "Điều Chỉnh Nhạc", "GridView": "Xem Lưới", "ListView": "Xem Danh Sách", - "SelectAudioNormalizationHelp": "Chỉnh nhạc - điều chỉnh âm lượng của mỗi bản nhạc để chúng phát lại với cùng một độ to. Chỉnh Album - điều chỉnh âm lượng của tất cả các bản nhạc trong một album, giữ nguyên dải động của album.", - "LabelAlbumGain": "Chỉnh Album", + "SelectAudioNormalizationHelp": "Điều chỉnh nhạc - điều chỉnh âm lượng của mỗi bản nhạc để chúng phát lại với cùng một độ to. Điều chỉnh album - điều chỉnh âm lượng tất cả bản nhạc trong album, giữ nguyên dải động của album.", + "LabelAlbumGain": "Điều Chỉnh Album", "LabelSegmentKeepSecondsHelp": "Thời gian tính bằng giây mà các phân đoạn cần được giữ lại trước khi chúng bị ghi đè. Phải lớn hơn \"Throle after\". Chỉ hoạt động nếu tính năng xóa phân đoạn được bật.", "MenuClose": "Đóng Menu", "MenuOpen": "Mở Menu", - "UserMenu": "Menu Người Dùng" + "UserMenu": "Menu Người Dùng", + "LogLevel.Trace": "Dấu vết" } From 74f81f43274582cbe0963975991b4efbf684389b Mon Sep 17 00:00:00 2001 From: Csaba Seprenyi Date: Sun, 18 Feb 2024 21:00:52 +0000 Subject: [PATCH 24/34] Translated using Weblate (Hungarian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/hu/ --- src/strings/hu.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/strings/hu.json b/src/strings/hu.json index f79fab178..60ac4513e 100644 --- a/src/strings/hu.json +++ b/src/strings/hu.json @@ -170,7 +170,7 @@ "LabelCollection": "Gyűjtemény", "LabelCommunityRating": "Közösségi értékelés", "LabelContentType": "Tartalom típusa", - "LabelCountry": "Ország", + "LabelCountry": "Ország/Régió", "LabelCriticRating": "Kritikusok értékelése", "LabelCurrentPassword": "Jelenlegi jelszó", "LabelCustomDeviceDisplayNameHelp": "Adj meg egy egyedi nevet, vagy hagyd üresen a készülék által elküldött név használatához.", @@ -1781,5 +1781,6 @@ "LabelTrackGain": "Címnyereség", "ForeignPartsOnly": "Kényszerített/külföldi alkatrészek", "HearingImpairedShort": "HI/SDH", - "HeaderGuestCast": "Vendégsztárok" + "HeaderGuestCast": "Vendégsztárok", + "HeaderAllRecordings": "Összes Felvétel" } From 6c8967110eb97a27459948b6f757d935ae042a70 Mon Sep 17 00:00:00 2001 From: Moritz Date: Mon, 19 Feb 2024 13:27:16 +0000 Subject: [PATCH 25/34] Translated using Weblate (German) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/de/ --- src/strings/de.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings/de.json b/src/strings/de.json index 9b339775b..283bca082 100644 --- a/src/strings/de.json +++ b/src/strings/de.json @@ -428,7 +428,7 @@ "LabelCollection": "Sammlung", "LabelCommunityRating": "Community Bewertung", "LabelContentType": "Typ des Inhalts", - "LabelCountry": "Land", + "LabelCountry": "Land/Region", "LabelCriticRating": "Kritikerbewertung", "LabelCurrentPassword": "Aktuelles Passwort", "LabelCustomCertificatePath": "Benutzerdefinierter Pfad zum SSL-Zertifikat", From f7dc39e787726f85a123a9fdf18326d90815f4b5 Mon Sep 17 00:00:00 2001 From: btopherjohnson Date: Tue, 20 Feb 2024 20:42:36 +0000 Subject: [PATCH 26/34] Translated using Weblate (Malay) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/ms/ --- src/strings/ms.json | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/strings/ms.json b/src/strings/ms.json index d950d6ff6..bb6b6b71e 100644 --- a/src/strings/ms.json +++ b/src/strings/ms.json @@ -258,5 +258,28 @@ "Cursive": "Sambung", "DefaultSubtitlesHelp": "Sari kata dimuatkan berdasarkan bendera lalai dan paksa dalam metadata yang disematkan. Keutamaan bahasa diambil kira apabila terdapat beberapa pilihan.", "LabelThrottleDelaySecondsHelp": "Masa dalam saat selepas mana transkoder akan dihadkan. Mesti cukup besar untuk klien mengekalkan penimbal yang sihat. Hanya berfungsi jika penghadan diaktifkan.", - "LabelSegmentKeepSeconds": "Masa untuk mengekalkan segmen" + "LabelSegmentKeepSeconds": "Masa untuk mengekalkan segmen", + "PreviousTrack": "Langkau ke sebelumnya", + "QuickConnectAuthorizeFail": "Kod Sambung Pantas tidak dikenali", + "AllowEmbeddedSubtitlesAllowImageOption": "Benarkan Imej", + "QuickConnect": "Sambung Pantas", + "Production": "Penerbitan", + "Primary": "Utama", + "Print": "Cetak", + "Producer": "Penerbit", + "Profile": "Profil", + "Quality": "Kualiti", + "ProductionLocations": "Lokasi penerbitan", + "QuickConnectNotActive": "Sambung Pantas tidak aktif pada pelayan ini", + "QuickConnectNotAvailable": "Minta pentadbir pelayan anda untuk mendayakan Sambung Pantas", + "Rate": "Kadar", + "PreviousChapter": "Bab sebelumnya", + "QuickConnectAuthorizeCode": "Masukkan kod {0} untuk log masuk", + "QuickConnectDeactivated": "Sambungan Pantas telah dinyahaktifkan sebelum permintaan log masuk dapat diluluskan", + "Previous": "Sebelumnya", + "LabelSegmentKeepSecondsHelp": "Masa dalam saat yang segmen harus disimpan sebelum ia ditulis ganti. Mesti melebihi \"Penghad laju setelah\". Hanya berfungsi jika pemadaman segmen didayakan.", + "AllowEmbeddedSubtitlesAllowAllOption": "Benarkan Semua", + "AllowEmbeddedSubtitlesAllowTextOption": "Benarkan Teks", + "QuickConnectDescription": "Untuk log masuk dengan Sambung Pantas, pilihlah butang 'Sambung Pantas' pada peranti yang anda menggunakan untuk log masuk dan memasukkan kod yang muncul di bawah.", + "QuickConnectInvalidCode": "Kod Sambung Pantas tidak sah" } From 08b922e1f75ebbb70c41de47e74abe76a0da4064 Mon Sep 17 00:00:00 2001 From: Filan Fisteku Date: Tue, 20 Feb 2024 20:31:22 +0000 Subject: [PATCH 27/34] Translated using Weblate (Albanian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/sq/ --- src/strings/sq.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/strings/sq.json b/src/strings/sq.json index 64f0afa9c..de2b0a5ea 100644 --- a/src/strings/sq.json +++ b/src/strings/sq.json @@ -581,5 +581,7 @@ "HeaderAutoDiscovery": "Zbulim ne rrjet", "HeaderStopRecording": "Ndaloni regjistrimin", "HeaderSubtitleProfilesHelp": "Profilet e titrave përshkruajnë formatin e titrave që suportohen nga paisja.", - "HeaderSyncPlaySelectGroup": "Bashkohuni një grupi" + "HeaderSyncPlaySelectGroup": "Bashkohuni një grupi", + "DirectStreamHelp2": "Energjia e konsumuar nga transmetimi i drejtpërdrejtë zakonisht varet nga profili i audios. Vetëm transmetimi i videove është pa humbje.", + "EnableDetailsBannerHelp": "Shfaqni një imazh baneri në krye të faqes së artikullit." } From 4a6efbe359ddb9900102e7e1081c84a78ef5341e Mon Sep 17 00:00:00 2001 From: btopherjohnson Date: Tue, 20 Feb 2024 22:42:17 +0000 Subject: [PATCH 28/34] Translated using Weblate (Malay) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/ms/ --- src/strings/ms.json | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/strings/ms.json b/src/strings/ms.json index bb6b6b71e..19bee8014 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-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.", + "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 Vision. Ini memerlukan pemadanan bersesuian dengan OpenCL atau CUDA runtime.", "Songs": "Lagu-lagu", "Playlists": "Senarai ulangmain", "Photos": "Gambar-gambar", @@ -210,7 +210,7 @@ "DisplayInMyMedia": "Pamerkan di skrin utama", "DirectStreaming": "Mainkan secara terus", "DirectStreamHelp1": "Strim video ini serasi dengan peranti ini, tetapi ia mempunya format audio (DTS, Dolby TrueHD, dll.) ataupun jumlah saluran audio yang tidak sepadan. Strim video ini akan dipakej tanpa dimampatkan secara masa sebenar sebelum dihantar ke peranti. Hanya strim audio sahaja yang akan ditranskod.", - "DirectPlayHelp": "Fail sumber ini serasi sepenuhnya dengan klien ini, dan sesi ini menerima fail tanpa pengubahsuaian.", + "DirectPlayHelp": "Fail sumber ini serasi sepenuhnya dengan klien ini dan sesi ini menerima fail tanpa pengubahsuaian.", "DirectPlaying": "Mainkan secara terus", "Directors": "Pengarah", "Digital": "Digital", @@ -281,5 +281,19 @@ "AllowEmbeddedSubtitlesAllowAllOption": "Benarkan Semua", "AllowEmbeddedSubtitlesAllowTextOption": "Benarkan Teks", "QuickConnectDescription": "Untuk log masuk dengan Sambung Pantas, pilihlah butang 'Sambung Pantas' pada peranti yang anda menggunakan untuk log masuk dan memasukkan kod yang muncul di bawah.", - "QuickConnectInvalidCode": "Kod Sambung Pantas tidak sah" + "QuickConnectInvalidCode": "Kod Sambung Pantas tidak sah", + "EnableNextVideoInfoOverlay": "Tunjukkan informasi video seterusnya semasa main balik", + "EnablePhotosHelp": "Imej akan dikesan dan dipaparkan bersama fail media lain.", + "Help": "Bantuan", + "DownloadAll": "Muat Turun Semua", + "EnablePhotos": "Paparkan gambar", + "EnableQuickConnect": "Dayakan Sambung Pantas pada pelayan ini", + "DisplayMissingEpisodesWithinSeasonsHelp": "Ini juga mesti didayakan untuk perpustakaan TV dalam konfigurasi pelayan.", + "EnableExternalVideoPlayers": "Pemain video luaran", + "Desktop": "Desktop", + "EnableNextVideoInfoOverlayHelp": "Pada penghujung video, paparkan maklumat tentang video seterusnya yang muncul dalam senarai main semasa.", + "EnableCinemaMode": "Mod pawagam", + "DisableCustomCss": "Lumpuhkan kod CSS kustom yang disediakan oleh pelayan", + "EnablePlugin": "Dayakan", + "DisablePlugin": "Lumpuhkan" } From 1aabbae3a5176cb9ca979311f1e3bec99cb5afba Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Wed, 21 Feb 2024 00:47:03 -0500 Subject: [PATCH 29/34] Add build and web versions to dashboard --- .eslintrc.js | 6 ++- .../components/drawers/DrawerHeaderLink.tsx | 4 +- src/controllers/dashboard/dashboard.html | 14 ++++-- src/controllers/dashboard/dashboard.js | 45 +++++++++++-------- src/controllers/dashboard/dashboard.scss | 17 +++++++ src/global.d.ts | 4 ++ src/hooks/useSystemInfo.ts | 31 +++++++++---- src/index.jsx | 2 +- src/scripts/settings/webSettings.js | 2 +- src/strings/en-us.json | 5 ++- webpack.common.js | 4 ++ 11 files changed, 95 insertions(+), 39 deletions(-) create mode 100644 src/controllers/dashboard/dashboard.scss diff --git a/.eslintrc.js b/.eslintrc.js index a43399e96..2b65ee767 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -261,7 +261,11 @@ module.exports = { 'ServerNotifications': 'writable', 'TaskButton': 'writable', 'UserParentalControlPage': 'writable', - 'Windows': 'readonly' + 'Windows': 'readonly', + // Build time definitions + __JF_BUILD_VERSION__: 'readonly', + __USE_SYSTEM_FONTS__: 'readonly', + __WEBPACK_SERVE__: 'readonly' }, rules: { '@typescript-eslint/prefer-string-starts-ends-with': ['error'] diff --git a/src/apps/experimental/components/drawers/DrawerHeaderLink.tsx b/src/apps/experimental/components/drawers/DrawerHeaderLink.tsx index 451577976..a60dd2a6c 100644 --- a/src/apps/experimental/components/drawers/DrawerHeaderLink.tsx +++ b/src/apps/experimental/components/drawers/DrawerHeaderLink.tsx @@ -3,15 +3,13 @@ import ListItemIcon from '@mui/material/ListItemIcon'; import ListItemText from '@mui/material/ListItemText'; import React from 'react'; -import { useApi } from 'hooks/useApi'; import { useSystemInfo } from 'hooks/useSystemInfo'; import ListItemLink from 'components/ListItemLink'; import appIcon from 'assets/img/icon-transparent.png'; const DrawerHeaderLink = () => { - const { api } = useApi(); - const { data: systemInfo } = useSystemInfo(api); + const { data: systemInfo } = useSystemInfo(); return ( diff --git a/src/controllers/dashboard/dashboard.html b/src/controllers/dashboard/dashboard.html index aed6bb206..4dd260877 100644 --- a/src/controllers/dashboard/dashboard.html +++ b/src/controllers/dashboard/dashboard.html @@ -8,16 +8,22 @@ -
-

-

+
+
${LabelServerName}
+
+
${LabelServerVersion}
+
+
${LabelWebVersion}
+
+
${LabelBuildVersion}
+
-