mirror of
https://github.com/jellyfin/jellyfin-web.git
synced 2026-09-02 19:43:13 +03:00
Merge branch 'master' into libbitsub
This commit is contained in:
@@ -20,21 +20,21 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository ⬇️
|
- name: Checkout repository ⬇️
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
ref: ${{ inputs.commit }}
|
ref: ${{ inputs.commit }}
|
||||||
show-progress: false
|
show-progress: false
|
||||||
|
|
||||||
- name: Initialize CodeQL 🛠️
|
- name: Initialize CodeQL 🛠️
|
||||||
uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
|
uses: github/codeql-action/init@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
|
||||||
with:
|
with:
|
||||||
queries: security-and-quality
|
queries: security-and-quality
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
|
|
||||||
- name: Autobuild 📦
|
- name: Autobuild 📦
|
||||||
uses: github/codeql-action/autobuild@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
|
uses: github/codeql-action/autobuild@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis 🧪
|
- name: Perform CodeQL Analysis 🧪
|
||||||
uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
|
uses: github/codeql-action/analyze@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
|
||||||
with:
|
with:
|
||||||
category: '/language:${{matrix.language}}'
|
category: '/language:${{matrix.language}}'
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ jobs:
|
|||||||
github-token: ${{ github.token }}
|
github-token: ${{ github.token }}
|
||||||
|
|
||||||
- name: Publish to Cloudflare Pages 📃
|
- name: Publish to Cloudflare Pages 📃
|
||||||
uses: cloudflare/wrangler-action@9acf94ace14e7dc412b076f2c5c20b8ce93c79cd # v3.15.0
|
uses: cloudflare/wrangler-action@ebbaa1584979971c8614a24965b4405ff95890e0 # v4.0.0
|
||||||
id: cf
|
id: cf
|
||||||
with:
|
with:
|
||||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
|
|||||||
@@ -14,12 +14,12 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out Git repository
|
- name: Check out Git repository
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
ref: ${{ inputs.commit || github.sha }}
|
ref: ${{ inputs.commit || github.sha }}
|
||||||
|
|
||||||
- name: Setup node environment
|
- name: Setup node environment
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
|
||||||
with:
|
with:
|
||||||
node-version-file: .nvmrc
|
node-version-file: .nvmrc
|
||||||
cache: npm
|
cache: npm
|
||||||
|
|||||||
@@ -14,13 +14,13 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repository
|
- name: Checkout Repository
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
ref: ${{ inputs.commit }}
|
ref: ${{ inputs.commit }}
|
||||||
show-progress: false
|
show-progress: false
|
||||||
|
|
||||||
- name: Scan
|
- name: Scan
|
||||||
uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4.9.0
|
uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0
|
||||||
with:
|
with:
|
||||||
## Workaround from https://github.com/actions/dependency-review-action/issues/456
|
## Workaround from https://github.com/actions/dependency-review-action/issues/456
|
||||||
## TODO: Remove when necessary
|
## TODO: Remove when necessary
|
||||||
@@ -42,13 +42,13 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout ⬇️
|
- name: Checkout ⬇️
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
ref: ${{ inputs.commit }}
|
ref: ${{ inputs.commit }}
|
||||||
show-progress: false
|
show-progress: false
|
||||||
|
|
||||||
- name: Setup node environment ⚙️
|
- name: Setup node environment ⚙️
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
|
||||||
with:
|
with:
|
||||||
node-version-file: .nvmrc
|
node-version-file: .nvmrc
|
||||||
cache: npm
|
cache: npm
|
||||||
|
|||||||
@@ -46,12 +46,12 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out Git repository
|
- name: Check out Git repository
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
|
|
||||||
- name: Setup node environment
|
- name: Setup node environment
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
|
||||||
with:
|
with:
|
||||||
node-version-file: .nvmrc
|
node-version-file: .nvmrc
|
||||||
cache: npm
|
cache: npm
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: ${{ contains(github.repository, 'jellyfin/') }}
|
if: ${{ contains(github.repository, 'jellyfin/') }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0
|
- uses: actions/stale@1e223db275d687790206a7acac4d1a11bd6fe629 # v10.4.0
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
||||||
operations-per-run: 75
|
operations-per-run: 75
|
||||||
|
|||||||
@@ -108,6 +108,7 @@
|
|||||||
- [lmaotrigine](https://github.com/lmaotrigine)
|
- [lmaotrigine](https://github.com/lmaotrigine)
|
||||||
- [bjorntp](https://github.com/bjorntp)
|
- [bjorntp](https://github.com/bjorntp)
|
||||||
- [austinhardaway](https://github.com/austinhardaway)
|
- [austinhardaway](https://github.com/austinhardaway)
|
||||||
|
- [Alex Dickens](https://github.com/alex-dicko)
|
||||||
- [shindouj](https://github.com/shindouj)
|
- [shindouj](https://github.com/shindouj)
|
||||||
|
|
||||||
## Emby Contributors
|
## Emby Contributors
|
||||||
|
|||||||
Generated
+421
-552
File diff suppressed because it is too large
Load Diff
+27
-27
@@ -10,7 +10,7 @@
|
|||||||
"@babel/preset-env": "7.29.7",
|
"@babel/preset-env": "7.29.7",
|
||||||
"@babel/preset-react": "7.29.7",
|
"@babel/preset-react": "7.29.7",
|
||||||
"@eslint-community/eslint-plugin-eslint-comments": "4.7.2",
|
"@eslint-community/eslint-plugin-eslint-comments": "4.7.2",
|
||||||
"@eslint/js": "9.39.4",
|
"@eslint/js": "9.39.5",
|
||||||
"@stylistic/eslint-plugin": "4.4.1",
|
"@stylistic/eslint-plugin": "4.4.1",
|
||||||
"@stylistic/stylelint-plugin": "3.1.3",
|
"@stylistic/stylelint-plugin": "3.1.3",
|
||||||
"@types/dompurify": "3.0.5",
|
"@types/dompurify": "3.0.5",
|
||||||
@@ -22,12 +22,12 @@
|
|||||||
"@types/react-dom": "18.3.7",
|
"@types/react-dom": "18.3.7",
|
||||||
"@types/react-lazy-load-image-component": "1.6.5",
|
"@types/react-lazy-load-image-component": "1.6.5",
|
||||||
"@types/sortablejs": "1.15.9",
|
"@types/sortablejs": "1.15.9",
|
||||||
"@typescript-eslint/parser": "8.62.0",
|
"@typescript-eslint/parser": "8.65.0",
|
||||||
"@uupaa/dynamic-import-polyfill": "1.0.2",
|
"@uupaa/dynamic-import-polyfill": "1.0.2",
|
||||||
"@vitest/coverage-v8": "3.2.6",
|
"@vitest/coverage-v8": "3.2.7",
|
||||||
"autoprefixer": "10.5.2",
|
"autoprefixer": "10.5.4",
|
||||||
"babel-loader": "10.1.1",
|
"babel-loader": "10.1.1",
|
||||||
"caniuse-lite": "1.0.30001799",
|
"caniuse-lite": "1.0.30001806",
|
||||||
"clean-webpack-plugin": "4.0.0",
|
"clean-webpack-plugin": "4.0.0",
|
||||||
"confusing-browser-globals": "1.0.11",
|
"confusing-browser-globals": "1.0.11",
|
||||||
"copy-webpack-plugin": "14.0.0",
|
"copy-webpack-plugin": "14.0.0",
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
"css-loader": "7.1.4",
|
"css-loader": "7.1.4",
|
||||||
"cssnano": "7.1.9",
|
"cssnano": "7.1.9",
|
||||||
"es-check": "9.6.4",
|
"es-check": "9.6.4",
|
||||||
"eslint": "9.39.4",
|
"eslint": "9.39.5",
|
||||||
"eslint-plugin-compat": "6.2.1",
|
"eslint-plugin-compat": "6.2.1",
|
||||||
"eslint-plugin-import": "2.32.0",
|
"eslint-plugin-import": "2.32.0",
|
||||||
"eslint-plugin-jsx-a11y": "6.10.2",
|
"eslint-plugin-jsx-a11y": "6.10.2",
|
||||||
@@ -47,14 +47,14 @@
|
|||||||
"fork-ts-checker-webpack-plugin": "9.1.0",
|
"fork-ts-checker-webpack-plugin": "9.1.0",
|
||||||
"globals": "17.7.0",
|
"globals": "17.7.0",
|
||||||
"html-loader": "5.1.0",
|
"html-loader": "5.1.0",
|
||||||
"html-webpack-plugin": "5.6.7",
|
"html-webpack-plugin": "5.6.8",
|
||||||
"jsdom": "29.1.1",
|
"jsdom": "29.1.1",
|
||||||
"mini-css-extract-plugin": "2.10.2",
|
"mini-css-extract-plugin": "2.10.2",
|
||||||
"postcss": "8.5.15",
|
"postcss": "8.5.23",
|
||||||
"postcss-loader": "8.2.1",
|
"postcss-loader": "8.2.1",
|
||||||
"postcss-preset-env": "10.6.1",
|
"postcss-preset-env": "10.6.1",
|
||||||
"postcss-scss": "4.0.9",
|
"postcss-scss": "4.0.9",
|
||||||
"sass": "1.101.0",
|
"sass": "1.102.0",
|
||||||
"sass-loader": "16.0.8",
|
"sass-loader": "16.0.8",
|
||||||
"source-map-loader": "5.0.0",
|
"source-map-loader": "5.0.0",
|
||||||
"speed-measure-webpack-plugin": "1.6.0",
|
"speed-measure-webpack-plugin": "1.6.0",
|
||||||
@@ -66,35 +66,35 @@
|
|||||||
"stylelint-scss": "6.14.0",
|
"stylelint-scss": "6.14.0",
|
||||||
"ts-loader": "9.6.2",
|
"ts-loader": "9.6.2",
|
||||||
"typescript": "5.9.3",
|
"typescript": "5.9.3",
|
||||||
"typescript-eslint": "8.62.0",
|
"typescript-eslint": "8.65.0",
|
||||||
"vite-tsconfig-paths": "6.1.1",
|
"vite-tsconfig-paths": "6.1.1",
|
||||||
"vitest": "3.2.6",
|
"vitest": "3.2.7",
|
||||||
"webpack": "5.107.2",
|
"webpack": "5.109.0",
|
||||||
"webpack-bundle-analyzer": "5.3.0",
|
"webpack-bundle-analyzer": "5.3.1",
|
||||||
"webpack-cli": "7.0.3",
|
"webpack-cli": "7.2.1",
|
||||||
"webpack-dev-server": "5.2.5",
|
"webpack-dev-server": "5.2.6",
|
||||||
"webpack-merge": "6.0.1",
|
"webpack-merge": "6.0.1",
|
||||||
"worker-loader": "3.0.8"
|
"worker-loader": "3.0.8"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/react": "11.14.0",
|
"@emotion/react": "11.14.0",
|
||||||
"@emotion/styled": "11.14.1",
|
"@emotion/styled": "11.14.1",
|
||||||
"@fontsource/noto-sans": "5.2.10",
|
"@fontsource/noto-sans": "5.3.0",
|
||||||
"@fontsource/noto-sans-hk": "5.2.9",
|
"@fontsource/noto-sans-hk": "5.3.0",
|
||||||
"@fontsource/noto-sans-jp": "5.2.9",
|
"@fontsource/noto-sans-jp": "5.3.0",
|
||||||
"@fontsource/noto-sans-kr": "5.2.9",
|
"@fontsource/noto-sans-kr": "5.3.0",
|
||||||
"@fontsource/noto-sans-sc": "5.2.9",
|
"@fontsource/noto-sans-sc": "5.3.0",
|
||||||
"@fontsource/noto-sans-tc": "5.2.9",
|
"@fontsource/noto-sans-tc": "5.3.0",
|
||||||
"@jellyfin/libass-wasm": "4.2.4",
|
"@jellyfin/libass-wasm": "4.2.4",
|
||||||
"@jellyfin/sdk": "0.0.0-unstable.202607010629",
|
"@jellyfin/sdk": "0.0.0-unstable.202607220710",
|
||||||
"@jellyfin/ux-web": "1.0.0",
|
"@jellyfin/ux-web": "1.0.0",
|
||||||
"@mui/icons-material": "6.5.0",
|
"@mui/icons-material": "6.5.0",
|
||||||
"@mui/material": "6.5.0",
|
"@mui/material": "6.5.0",
|
||||||
"@mui/x-date-pickers": "7.29.4",
|
"@mui/x-date-pickers": "7.29.4",
|
||||||
"@react-hook/resize-observer": "2.0.2",
|
"@react-hook/resize-observer": "2.0.2",
|
||||||
"@tanstack/react-query": "5.101.1",
|
"@tanstack/react-query": "5.91.3",
|
||||||
"@tanstack/react-query-devtools": "5.101.1",
|
"@tanstack/react-query-devtools": "5.91.3",
|
||||||
"@tanstack/react-query-persist-client": "5.101.1",
|
"@tanstack/react-query-persist-client": "5.90.27",
|
||||||
"abortcontroller-polyfill": "1.7.8",
|
"abortcontroller-polyfill": "1.7.8",
|
||||||
"axios": "1.18.1",
|
"axios": "1.18.1",
|
||||||
"blurhash": "2.0.5",
|
"blurhash": "2.0.5",
|
||||||
@@ -111,7 +111,7 @@
|
|||||||
"headroom.js": "0.12.0",
|
"headroom.js": "0.12.0",
|
||||||
"history": "5.3.0",
|
"history": "5.3.0",
|
||||||
"hls.js": "1.6.16",
|
"hls.js": "1.6.16",
|
||||||
"idb-keyval": "6.2.5",
|
"idb-keyval": "6.3.0",
|
||||||
"intersection-observer": "0.12.2",
|
"intersection-observer": "0.12.2",
|
||||||
"jellyfin-apiclient": "1.11.0",
|
"jellyfin-apiclient": "1.11.0",
|
||||||
"jquery": "3.7.1",
|
"jquery": "3.7.1",
|
||||||
@@ -119,7 +119,7 @@
|
|||||||
"libarchive.js": "2.0.2",
|
"libarchive.js": "2.0.2",
|
||||||
"libbitsub": "1.11.0",
|
"libbitsub": "1.11.0",
|
||||||
"lodash-es": "4.18.1",
|
"lodash-es": "4.18.1",
|
||||||
"markdown-it": "14.2.0",
|
"markdown-it": "14.3.0",
|
||||||
"material-design-icons-iconfont": "6.7.0",
|
"material-design-icons-iconfont": "6.7.0",
|
||||||
"material-react-table": "3.2.1",
|
"material-react-table": "3.2.1",
|
||||||
"native-promise-only": "0.8.1",
|
"native-promise-only": "0.8.1",
|
||||||
|
|||||||
@@ -59,7 +59,6 @@ export const Component: FC = () => {
|
|||||||
<StrictMode>
|
<StrictMode>
|
||||||
<OffsetAppBar
|
<OffsetAppBar
|
||||||
dense
|
dense
|
||||||
elevation={4}
|
|
||||||
sx={{
|
sx={{
|
||||||
width: {
|
width: {
|
||||||
xs: '100%',
|
xs: '100%',
|
||||||
@@ -79,6 +78,7 @@ export const Component: FC = () => {
|
|||||||
buttons={
|
buttons={
|
||||||
<HelpButton />
|
<HelpButton />
|
||||||
}
|
}
|
||||||
|
className='dashboard-appBar'
|
||||||
>
|
>
|
||||||
{isMetadataManager && (
|
{isMetadataManager && (
|
||||||
<ServerButton />
|
<ServerButton />
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import React from 'react';
|
import { getDisplayVersion } from '@jellyfin/sdk/lib/utils/versioning';
|
||||||
import globalize from 'lib/globalize';
|
|
||||||
import Widget from './Widget';
|
|
||||||
import Paper from '@mui/material/Paper';
|
import Paper from '@mui/material/Paper';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import Stack from '@mui/material/Stack';
|
import Stack from '@mui/material/Stack';
|
||||||
@@ -9,8 +7,12 @@ import Skeleton from '@mui/material/Skeleton';
|
|||||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||||
import RestartAltIcon from '@mui/icons-material/RestartAlt';
|
import RestartAltIcon from '@mui/icons-material/RestartAlt';
|
||||||
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
|
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
import { useSystemInfo } from 'hooks/useSystemInfo';
|
import { useSystemInfo } from 'hooks/useSystemInfo';
|
||||||
import { getDisplayVersion } from 'utils/versions';
|
import globalize from 'lib/globalize';
|
||||||
|
|
||||||
|
import Widget from './Widget';
|
||||||
|
|
||||||
type ServerInfoWidgetProps = {
|
type ServerInfoWidgetProps = {
|
||||||
onScanLibrariesClick?: () => void;
|
onScanLibrariesClick?: () => void;
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import type { ActivityLogApiGetLogEntriesRequest } from '@jellyfin/sdk/lib/generated-client/api/activity-log-api';
|
import type { SystemApiGetLogEntriesRequest } from '@jellyfin/sdk/lib/generated-client/api/system-api';
|
||||||
import type { AxiosRequestConfig } from 'axios';
|
import type { AxiosRequestConfig } from 'axios';
|
||||||
import type { Api } from '@jellyfin/sdk';
|
import type { Api } from '@jellyfin/sdk';
|
||||||
import { getActivityLogApi } from '@jellyfin/sdk/lib/utils/api/activity-log-api';
|
import { getSystemApi } from '@jellyfin/sdk/lib/utils/api/system-api';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
import { useApi } from 'hooks/useApi';
|
import { useApi } from 'hooks/useApi';
|
||||||
|
|
||||||
const fetchLogEntries = async (
|
const fetchLogEntries = async (
|
||||||
api: Api,
|
api: Api,
|
||||||
requestParams?: ActivityLogApiGetLogEntriesRequest,
|
requestParams?: SystemApiGetLogEntriesRequest,
|
||||||
options?: AxiosRequestConfig
|
options?: AxiosRequestConfig
|
||||||
) => {
|
) => {
|
||||||
const response = await getActivityLogApi(api).getLogEntries(requestParams, {
|
const response = await getSystemApi(api).getLogEntries(requestParams, {
|
||||||
signal: options?.signal
|
signal: options?.signal
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ const fetchLogEntries = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const useLogEntries = (
|
export const useLogEntries = (
|
||||||
requestParams: ActivityLogApiGetLogEntriesRequest
|
requestParams: SystemApiGetLogEntriesRequest
|
||||||
) => {
|
) => {
|
||||||
const { api } = useApi();
|
const { api } = useApi();
|
||||||
return useQuery({
|
return useQuery({
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ const RestoreConfirmationDialog: FunctionComponent<IProps> = ({ open, onClose, o
|
|||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
|
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogContentText>
|
<DialogContentText sx={{ whiteSpace: 'pre-line' }}>
|
||||||
{globalize.translate('MessageRestoreDisclaimer')}
|
{globalize.translate('MessageRestoreDisclaimer')}
|
||||||
</DialogContentText>
|
</DialogContentText>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { DevicesApiDeleteDeviceRequest } from '@jellyfin/sdk/lib/generated-client/api/devices-api';
|
import type { DeviceApiDeleteDeviceRequest } from '@jellyfin/sdk/lib/generated-client/api/device-api';
|
||||||
import { getDevicesApi } from '@jellyfin/sdk/lib/utils/api/devices-api';
|
import { getDeviceApi } from '@jellyfin/sdk/lib/utils/api/device-api';
|
||||||
import { useMutation } from '@tanstack/react-query';
|
import { useMutation } from '@tanstack/react-query';
|
||||||
|
|
||||||
import { useApi } from 'hooks/useApi';
|
import { useApi } from 'hooks/useApi';
|
||||||
@@ -10,8 +10,8 @@ export const useDeleteDevice = () => {
|
|||||||
const { api } = useApi();
|
const { api } = useApi();
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (params: DevicesApiDeleteDeviceRequest) => (
|
mutationFn: (params: DeviceApiDeleteDeviceRequest) => (
|
||||||
getDevicesApi(api!)
|
getDeviceApi(api!)
|
||||||
.deleteDevice(params)
|
.deleteDevice(params)
|
||||||
),
|
),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { DevicesApiGetDevicesRequest } from '@jellyfin/sdk/lib/generated-client/api/devices-api';
|
import type { DeviceApiGetDevicesRequest } from '@jellyfin/sdk/lib/generated-client/api/device-api';
|
||||||
import type { AxiosRequestConfig } from 'axios';
|
import type { AxiosRequestConfig } from 'axios';
|
||||||
import type { Api } from '@jellyfin/sdk';
|
import type { Api } from '@jellyfin/sdk';
|
||||||
import { getDevicesApi } from '@jellyfin/sdk/lib/utils/api/devices-api';
|
import { getDeviceApi } from '@jellyfin/sdk/lib/utils/api/device-api';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
import { useApi } from 'hooks/useApi';
|
import { useApi } from 'hooks/useApi';
|
||||||
@@ -10,10 +10,10 @@ export const QUERY_KEY = 'Devices';
|
|||||||
|
|
||||||
const fetchDevices = async (
|
const fetchDevices = async (
|
||||||
api: Api,
|
api: Api,
|
||||||
requestParams?: DevicesApiGetDevicesRequest,
|
requestParams?: DeviceApiGetDevicesRequest,
|
||||||
options?: AxiosRequestConfig
|
options?: AxiosRequestConfig
|
||||||
) => {
|
) => {
|
||||||
const response = await getDevicesApi(api).getDevices(requestParams, {
|
const response = await getDeviceApi(api).getDevices(requestParams, {
|
||||||
signal: options?.signal
|
signal: options?.signal
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ const fetchDevices = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const useDevices = (
|
export const useDevices = (
|
||||||
requestParams: DevicesApiGetDevicesRequest
|
requestParams: DeviceApiGetDevicesRequest
|
||||||
) => {
|
) => {
|
||||||
const { api } = useApi();
|
const { api } = useApi();
|
||||||
return useQuery({
|
return useQuery({
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { DevicesApiUpdateDeviceOptionsRequest } from '@jellyfin/sdk/lib/generated-client/api/devices-api';
|
import type { DeviceApiUpdateDeviceOptionsRequest } from '@jellyfin/sdk/lib/generated-client/api/device-api';
|
||||||
import { getDevicesApi } from '@jellyfin/sdk/lib/utils/api/devices-api';
|
import { getDeviceApi } from '@jellyfin/sdk/lib/utils/api/device-api';
|
||||||
import { useMutation } from '@tanstack/react-query';
|
import { useMutation } from '@tanstack/react-query';
|
||||||
|
|
||||||
import { useApi } from 'hooks/useApi';
|
import { useApi } from 'hooks/useApi';
|
||||||
@@ -10,8 +10,8 @@ export const useUpdateDevice = () => {
|
|||||||
const { api } = useApi();
|
const { api } = useApi();
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (params: DevicesApiUpdateDeviceOptionsRequest) => (
|
mutationFn: (params: DeviceApiUpdateDeviceOptionsRequest) => (
|
||||||
getDevicesApi(api!)
|
getDeviceApi(api!)
|
||||||
.updateDeviceOptions(params)
|
.updateDeviceOptions(params)
|
||||||
),
|
),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useCallback, useMemo, useState } from 'react';
|
import React, { useCallback, useMemo, useState } from 'react';
|
||||||
import type { SessionInfo } from '@jellyfin/sdk/lib/generated-client/models/session-info';
|
import type { SessionInfoDto } from '@jellyfin/sdk/lib/generated-client/models/session-info-dto';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import Card from '@mui/material/Card';
|
import Card from '@mui/material/Card';
|
||||||
import CardMedia from '@mui/material/CardMedia';
|
import CardMedia from '@mui/material/CardMedia';
|
||||||
@@ -27,7 +27,7 @@ import InputDialog from 'components/InputDialog';
|
|||||||
import { useSendMessage } from '../../sessions/api/useSendMessage';
|
import { useSendMessage } from '../../sessions/api/useSendMessage';
|
||||||
|
|
||||||
type DeviceCardProps = {
|
type DeviceCardProps = {
|
||||||
device: SessionInfo;
|
device: SessionInfoDto;
|
||||||
};
|
};
|
||||||
|
|
||||||
const DeviceCard = ({ device }: DeviceCardProps) => {
|
const DeviceCard = ({ device }: DeviceCardProps) => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Api } from '@jellyfin/sdk';
|
import { Api } from '@jellyfin/sdk';
|
||||||
import { getApiKeyApi } from '@jellyfin/sdk/lib/utils/api/api-key-api';
|
import { getAuthenticationApi } from '@jellyfin/sdk/lib/utils/api/authentication-api';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { AxiosRequestConfig } from 'axios';
|
import { AxiosRequestConfig } from 'axios';
|
||||||
import { useApi } from 'hooks/useApi';
|
import { useApi } from 'hooks/useApi';
|
||||||
@@ -7,7 +7,7 @@ import { useApi } from 'hooks/useApi';
|
|||||||
export const QUERY_KEY = 'ApiKeys';
|
export const QUERY_KEY = 'ApiKeys';
|
||||||
|
|
||||||
const fetchApiKeys = async (api: Api, options?: AxiosRequestConfig) => {
|
const fetchApiKeys = async (api: Api, options?: AxiosRequestConfig) => {
|
||||||
const response = await getApiKeyApi(api).getKeys(options);
|
const response = await getAuthenticationApi(api).getKeys(options);
|
||||||
|
|
||||||
return response.data;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ApiKeyApiCreateKeyRequest } from '@jellyfin/sdk/lib/generated-client/api/api-key-api';
|
import { AuthenticationApiCreateKeyRequest } from '@jellyfin/sdk/lib/generated-client/api/authentication-api';
|
||||||
import { getApiKeyApi } from '@jellyfin/sdk/lib/utils/api/api-key-api';
|
import { getAuthenticationApi } from '@jellyfin/sdk/lib/utils/api/authentication-api';
|
||||||
import { useMutation } from '@tanstack/react-query';
|
import { useMutation } from '@tanstack/react-query';
|
||||||
import { useApi } from 'hooks/useApi';
|
import { useApi } from 'hooks/useApi';
|
||||||
import { queryClient } from 'utils/query/queryClient';
|
import { queryClient } from 'utils/query/queryClient';
|
||||||
@@ -9,8 +9,8 @@ export const useCreateKey = () => {
|
|||||||
const { api } = useApi();
|
const { api } = useApi();
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (params: ApiKeyApiCreateKeyRequest) => (
|
mutationFn: (params: AuthenticationApiCreateKeyRequest) => (
|
||||||
getApiKeyApi(api!)
|
getAuthenticationApi(api!)
|
||||||
.createKey(params)
|
.createKey(params)
|
||||||
),
|
),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { ApiKeyApiRevokeKeyRequest } from '@jellyfin/sdk/lib/generated-client/api/api-key-api';
|
import type { AuthenticationApiRevokeKeyRequest } from '@jellyfin/sdk/lib/generated-client/api/authentication-api';
|
||||||
import { getApiKeyApi } from '@jellyfin/sdk/lib/utils/api/api-key-api';
|
import { getAuthenticationApi } from '@jellyfin/sdk/lib/utils/api/authentication-api';
|
||||||
import { useMutation } from '@tanstack/react-query';
|
import { useMutation } from '@tanstack/react-query';
|
||||||
import { useApi } from 'hooks/useApi';
|
import { useApi } from 'hooks/useApi';
|
||||||
import { queryClient } from 'utils/query/queryClient';
|
import { queryClient } from 'utils/query/queryClient';
|
||||||
@@ -9,8 +9,8 @@ export const useRevokeKey = () => {
|
|||||||
const { api } = useApi();
|
const { api } = useApi();
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (params: ApiKeyApiRevokeKeyRequest) => (
|
mutationFn: (params: AuthenticationApiRevokeKeyRequest) => (
|
||||||
getApiKeyApi(api!)
|
getAuthenticationApi(api!)
|
||||||
.revokeKey(params)
|
.revokeKey(params)
|
||||||
),
|
),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
|||||||
@@ -27,7 +27,12 @@ const LogItemList: FunctionComponent<LogItemProps> = ({ logs }: LogItemProps) =>
|
|||||||
secondary={getDate(log)}
|
secondary={getDate(log)}
|
||||||
slotProps={{
|
slotProps={{
|
||||||
primary: {
|
primary: {
|
||||||
variant: 'h3'
|
variant: 'h3',
|
||||||
|
sx: {
|
||||||
|
whiteSpace: 'normal',
|
||||||
|
overflowWrap: 'anywhere',
|
||||||
|
wordBreak: 'break-word'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
secondary: {
|
secondary: {
|
||||||
variant: 'body1'
|
variant: 'body1'
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { Api } from '@jellyfin/sdk';
|
import type { Api } from '@jellyfin/sdk';
|
||||||
import type { DashboardApiGetConfigurationPagesRequest } from '@jellyfin/sdk/lib/generated-client/api/dashboard-api';
|
import type { PluginApiGetConfigurationPagesRequest } from '@jellyfin/sdk/lib/generated-client/api/plugin-api';
|
||||||
import { getDashboardApi } from '@jellyfin/sdk/lib/utils/api/dashboard-api';
|
import { getPluginApi } from '@jellyfin/sdk/lib/utils/api/plugin-api';
|
||||||
import { queryOptions, useQuery } from '@tanstack/react-query';
|
import { queryOptions, useQuery } from '@tanstack/react-query';
|
||||||
import type { AxiosRequestConfig } from 'axios';
|
import type { AxiosRequestConfig } from 'axios';
|
||||||
|
|
||||||
@@ -10,17 +10,17 @@ import { QueryKey } from './queryKey';
|
|||||||
|
|
||||||
const fetchConfigurationPages = async (
|
const fetchConfigurationPages = async (
|
||||||
api: Api,
|
api: Api,
|
||||||
params?: DashboardApiGetConfigurationPagesRequest,
|
params?: PluginApiGetConfigurationPagesRequest,
|
||||||
options?: AxiosRequestConfig
|
options?: AxiosRequestConfig
|
||||||
) => {
|
) => {
|
||||||
const response = await getDashboardApi(api)
|
const response = await getPluginApi(api)
|
||||||
.getConfigurationPages(params, options);
|
.getConfigurationPages(params, options);
|
||||||
return response.data;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getConfigurationPagesQuery = (
|
const getConfigurationPagesQuery = (
|
||||||
api?: Api,
|
api?: Api,
|
||||||
params?: DashboardApiGetConfigurationPagesRequest
|
params?: PluginApiGetConfigurationPagesRequest
|
||||||
) => queryOptions({
|
) => queryOptions({
|
||||||
queryKey: [ QueryKey.ConfigurationPages, params?.enableInMainMenu ],
|
queryKey: [ QueryKey.ConfigurationPages, params?.enableInMainMenu ],
|
||||||
queryFn: ({ signal }) => fetchConfigurationPages(api!, params, { signal }),
|
queryFn: ({ signal }) => fetchConfigurationPages(api!, params, { signal }),
|
||||||
@@ -28,7 +28,7 @@ const getConfigurationPagesQuery = (
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const useConfigurationPages = (
|
export const useConfigurationPages = (
|
||||||
params?: DashboardApiGetConfigurationPagesRequest
|
params?: PluginApiGetConfigurationPagesRequest
|
||||||
) => {
|
) => {
|
||||||
const { api } = useApi();
|
const { api } = useApi();
|
||||||
return useQuery(getConfigurationPagesQuery(api, params));
|
return useQuery(getConfigurationPagesQuery(api, params));
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { PluginsApiDisablePluginRequest } from '@jellyfin/sdk/lib/generated-client/api/plugins-api';
|
import type { PluginApiDisablePluginRequest } from '@jellyfin/sdk/lib/generated-client/api/plugin-api';
|
||||||
import { getPluginsApi } from '@jellyfin/sdk/lib/utils/api/plugins-api';
|
import { getPluginApi } from '@jellyfin/sdk/lib/utils/api/plugin-api';
|
||||||
import { useMutation } from '@tanstack/react-query';
|
import { useMutation } from '@tanstack/react-query';
|
||||||
|
|
||||||
import { useApi } from 'hooks/useApi';
|
import { useApi } from 'hooks/useApi';
|
||||||
@@ -10,8 +10,8 @@ import { QueryKey } from './queryKey';
|
|||||||
export const useDisablePlugin = () => {
|
export const useDisablePlugin = () => {
|
||||||
const { api } = useApi();
|
const { api } = useApi();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (params: PluginsApiDisablePluginRequest) => (
|
mutationFn: (params: PluginApiDisablePluginRequest) => (
|
||||||
getPluginsApi(api!)
|
getPluginApi(api!)
|
||||||
.disablePlugin(params)
|
.disablePlugin(params)
|
||||||
),
|
),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { PluginsApiEnablePluginRequest } from '@jellyfin/sdk/lib/generated-client/api/plugins-api';
|
import type { PluginApiEnablePluginRequest } from '@jellyfin/sdk/lib/generated-client/api/plugin-api';
|
||||||
import { getPluginsApi } from '@jellyfin/sdk/lib/utils/api/plugins-api';
|
import { getPluginApi } from '@jellyfin/sdk/lib/utils/api/plugin-api';
|
||||||
import { useMutation } from '@tanstack/react-query';
|
import { useMutation } from '@tanstack/react-query';
|
||||||
|
|
||||||
import { useApi } from 'hooks/useApi';
|
import { useApi } from 'hooks/useApi';
|
||||||
@@ -10,8 +10,8 @@ import { QueryKey } from './queryKey';
|
|||||||
export const useEnablePlugin = () => {
|
export const useEnablePlugin = () => {
|
||||||
const { api } = useApi();
|
const { api } = useApi();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (params: PluginsApiEnablePluginRequest) => (
|
mutationFn: (params: PluginApiEnablePluginRequest) => (
|
||||||
getPluginsApi(api!)
|
getPluginApi(api!)
|
||||||
.enablePlugin(params)
|
.enablePlugin(params)
|
||||||
),
|
),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { PackageApiInstallPackageRequest } from '@jellyfin/sdk/lib/generated-client/api/package-api';
|
import type { PluginApiInstallPackageRequest } from '@jellyfin/sdk/lib/generated-client/api/plugin-api';
|
||||||
import { getPackageApi } from '@jellyfin/sdk/lib/utils/api/package-api';
|
import { getPluginApi } from '@jellyfin/sdk/lib/utils/api/plugin-api';
|
||||||
import { useMutation } from '@tanstack/react-query';
|
import { useMutation } from '@tanstack/react-query';
|
||||||
|
|
||||||
import { useApi } from 'hooks/useApi';
|
import { useApi } from 'hooks/useApi';
|
||||||
@@ -10,8 +10,8 @@ import { QueryKey } from './queryKey';
|
|||||||
export const useInstallPackage = () => {
|
export const useInstallPackage = () => {
|
||||||
const { api } = useApi();
|
const { api } = useApi();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (params: PackageApiInstallPackageRequest) => (
|
mutationFn: (params: PluginApiInstallPackageRequest) => (
|
||||||
getPackageApi(api!)
|
getPluginApi(api!)
|
||||||
.installPackage(params)
|
.installPackage(params)
|
||||||
),
|
),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { queryOptions, useQuery } from '@tanstack/react-query';
|
import { queryOptions, useQuery } from '@tanstack/react-query';
|
||||||
import type { Api } from '@jellyfin/sdk';
|
import type { Api } from '@jellyfin/sdk';
|
||||||
import type { PackageApiGetPackageInfoRequest } from '@jellyfin/sdk/lib/generated-client/api/package-api';
|
import type { PluginApiGetPackageInfoRequest } from '@jellyfin/sdk/lib/generated-client/api/plugin-api';
|
||||||
import { getPackageApi } from '@jellyfin/sdk/lib/utils/api/package-api';
|
import { getPluginApi } from '@jellyfin/sdk/lib/utils/api/plugin-api';
|
||||||
import type { AxiosRequestConfig } from 'axios';
|
import type { AxiosRequestConfig } from 'axios';
|
||||||
|
|
||||||
import { useApi } from 'hooks/useApi';
|
import { useApi } from 'hooks/useApi';
|
||||||
@@ -12,7 +12,7 @@ import type { PackageInfo } from '@jellyfin/sdk/lib/generated-client/models/pack
|
|||||||
|
|
||||||
const fetchPackageInfo = async (
|
const fetchPackageInfo = async (
|
||||||
api: Api,
|
api: Api,
|
||||||
params: PackageApiGetPackageInfoRequest,
|
params: PluginApiGetPackageInfoRequest,
|
||||||
options?: AxiosRequestConfig
|
options?: AxiosRequestConfig
|
||||||
) => {
|
) => {
|
||||||
const packagesData = queryClient.getQueryData([ QueryKey.Packages ]) as PackageInfo[];
|
const packagesData = queryClient.getQueryData([ QueryKey.Packages ]) as PackageInfo[];
|
||||||
@@ -25,14 +25,14 @@ const fetchPackageInfo = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await getPackageApi(api)
|
const response = await getPluginApi(api)
|
||||||
.getPackageInfo(params, options);
|
.getPackageInfo(params, options);
|
||||||
return response.data;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPackageInfoQuery = (
|
const getPackageInfoQuery = (
|
||||||
api: Api | undefined,
|
api: Api | undefined,
|
||||||
params?: PackageApiGetPackageInfoRequest
|
params?: PluginApiGetPackageInfoRequest
|
||||||
) => queryOptions({
|
) => queryOptions({
|
||||||
// Don't retry since requests for plugins not available in repos fail
|
// Don't retry since requests for plugins not available in repos fail
|
||||||
retry: false,
|
retry: false,
|
||||||
@@ -42,7 +42,7 @@ const getPackageInfoQuery = (
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const usePackageInfo = (
|
export const usePackageInfo = (
|
||||||
params?: PackageApiGetPackageInfoRequest
|
params?: PluginApiGetPackageInfoRequest
|
||||||
) => {
|
) => {
|
||||||
const { api } = useApi();
|
const { api } = useApi();
|
||||||
return useQuery(getPackageInfoQuery(api, params));
|
return useQuery(getPackageInfoQuery(api, params));
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { Api } from '@jellyfin/sdk';
|
import type { Api } from '@jellyfin/sdk';
|
||||||
import { getPackageApi } from '@jellyfin/sdk/lib/utils/api/package-api';
|
import { getPluginApi } from '@jellyfin/sdk/lib/utils/api/plugin-api';
|
||||||
import { queryOptions, useQuery } from '@tanstack/react-query';
|
import { queryOptions, useQuery } from '@tanstack/react-query';
|
||||||
import type { AxiosRequestConfig } from 'axios';
|
import type { AxiosRequestConfig } from 'axios';
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ const fetchPackages = async (
|
|||||||
api: Api,
|
api: Api,
|
||||||
options?: AxiosRequestConfig
|
options?: AxiosRequestConfig
|
||||||
) => {
|
) => {
|
||||||
const response = await getPackageApi(api)
|
const response = await getPluginApi(api)
|
||||||
.getPackages(options);
|
.getPackages(options);
|
||||||
return response.data;
|
return response.data;
|
||||||
};
|
};
|
||||||
@@ -29,4 +29,3 @@ export const usePackages = () => {
|
|||||||
const { api } = useApi();
|
const { api } = useApi();
|
||||||
return useQuery(getPackagesQuery(api));
|
return useQuery(getPackagesQuery(api));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { Api } from '@jellyfin/sdk';
|
import type { Api } from '@jellyfin/sdk';
|
||||||
import { getPluginsApi } from '@jellyfin/sdk/lib/utils/api/plugins-api';
|
import { getPluginApi } from '@jellyfin/sdk/lib/utils/api/plugin-api';
|
||||||
import { queryOptions, useQuery } from '@tanstack/react-query';
|
import { queryOptions, useQuery } from '@tanstack/react-query';
|
||||||
import type { AxiosRequestConfig } from 'axios';
|
import type { AxiosRequestConfig } from 'axios';
|
||||||
|
|
||||||
@@ -11,8 +11,7 @@ const fetchPlugins = async (
|
|||||||
api: Api,
|
api: Api,
|
||||||
options?: AxiosRequestConfig
|
options?: AxiosRequestConfig
|
||||||
) => {
|
) => {
|
||||||
const response = await getPluginsApi(api)
|
const response = await getPluginApi(api).getPlugins(options);
|
||||||
.getPlugins(options);
|
|
||||||
return response.data;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -28,4 +27,3 @@ export const usePlugins = () => {
|
|||||||
const { api } = useApi();
|
const { api } = useApi();
|
||||||
return useQuery(getPluginsQuery(api));
|
return useQuery(getPluginsQuery(api));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ import { queryOptions, useQuery } from '@tanstack/react-query';
|
|||||||
import type { AxiosRequestConfig } from 'axios';
|
import type { AxiosRequestConfig } from 'axios';
|
||||||
import { useApi } from 'hooks/useApi';
|
import { useApi } from 'hooks/useApi';
|
||||||
import { QueryKey } from './queryKey';
|
import { QueryKey } from './queryKey';
|
||||||
import { getPackageApi } from '@jellyfin/sdk/lib/utils/api/package-api';
|
import { getPluginApi } from '@jellyfin/sdk/lib/utils/api/plugin-api';
|
||||||
|
|
||||||
const fetchRepositories = async (
|
const fetchRepositories = async (
|
||||||
api: Api,
|
api: Api,
|
||||||
options?: AxiosRequestConfig
|
options?: AxiosRequestConfig
|
||||||
) => {
|
) => {
|
||||||
const response = await getPackageApi(api)
|
const response = await getPluginApi(api)
|
||||||
.getRepositories(options);
|
.getRepositories(options);
|
||||||
return response.data;
|
return response.data;
|
||||||
};
|
};
|
||||||
@@ -26,4 +26,3 @@ export const useRepositories = () => {
|
|||||||
const { api } = useApi();
|
const { api } = useApi();
|
||||||
return useQuery(getRepositoriesQuery(api));
|
return useQuery(getRepositoriesQuery(api));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ import { useMutation } from '@tanstack/react-query';
|
|||||||
import { useApi } from 'hooks/useApi';
|
import { useApi } from 'hooks/useApi';
|
||||||
import { queryClient } from 'utils/query/queryClient';
|
import { queryClient } from 'utils/query/queryClient';
|
||||||
import { QueryKey } from './queryKey';
|
import { QueryKey } from './queryKey';
|
||||||
import { getPackageApi } from '@jellyfin/sdk/lib/utils/api/package-api';
|
import { getPluginApi } from '@jellyfin/sdk/lib/utils/api/plugin-api';
|
||||||
import { PackageApiSetRepositoriesRequest } from '@jellyfin/sdk/lib/generated-client/api/package-api';
|
import { PluginApiSetRepositoriesRequest } from '@jellyfin/sdk/lib/generated-client/api/plugin-api';
|
||||||
|
|
||||||
export const useSetRepositories = () => {
|
export const useSetRepositories = () => {
|
||||||
const { api } = useApi();
|
const { api } = useApi();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (params: PackageApiSetRepositoriesRequest) => (
|
mutationFn: (params: PluginApiSetRepositoriesRequest) => (
|
||||||
getPackageApi(api!)
|
getPluginApi(api!)
|
||||||
.setRepositories(params)
|
.setRepositories(params)
|
||||||
),
|
),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { PluginsApiUninstallPluginByVersionRequest } from '@jellyfin/sdk/lib/generated-client/api/plugins-api';
|
import type { PluginApiUninstallPluginByVersionRequest } from '@jellyfin/sdk/lib/generated-client/api/plugin-api';
|
||||||
import { getPluginsApi } from '@jellyfin/sdk/lib/utils/api/plugins-api';
|
import { getPluginApi } from '@jellyfin/sdk/lib/utils/api/plugin-api';
|
||||||
import { useMutation } from '@tanstack/react-query';
|
import { useMutation } from '@tanstack/react-query';
|
||||||
|
|
||||||
import { useApi } from 'hooks/useApi';
|
import { useApi } from 'hooks/useApi';
|
||||||
@@ -10,8 +10,8 @@ import { QueryKey } from './queryKey';
|
|||||||
export const useUninstallPlugin = () => {
|
export const useUninstallPlugin = () => {
|
||||||
const { api } = useApi();
|
const { api } = useApi();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (params: PluginsApiUninstallPluginByVersionRequest) => (
|
mutationFn: (params: PluginApiUninstallPluginByVersionRequest) => (
|
||||||
getPluginsApi(api!)
|
getPluginApi(api!)
|
||||||
.uninstallPluginByVersion(params)
|
.uninstallPluginByVersion(params)
|
||||||
),
|
),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { SessionInfo } from '@jellyfin/sdk/lib/generated-client/models/session-info';
|
import type { SessionInfoDto } from '@jellyfin/sdk/lib/generated-client/models/session-info-dto';
|
||||||
import itemHelper from 'components/itemHelper';
|
import itemHelper from 'components/itemHelper';
|
||||||
import formatDistanceToNow from 'date-fns/formatDistanceToNow';
|
import formatDistanceToNow from 'date-fns/formatDistanceToNow';
|
||||||
import globalize from 'lib/globalize';
|
import globalize from 'lib/globalize';
|
||||||
@@ -11,7 +11,7 @@ type NowPlayingInfo = {
|
|||||||
image?: string;
|
image?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getNowPlayingName = (session: SessionInfo): NowPlayingInfo => {
|
const getNowPlayingName = (session: SessionInfoDto): NowPlayingInfo => {
|
||||||
let imgUrl = '';
|
let imgUrl = '';
|
||||||
const nowPlayingItem = session.NowPlayingItem;
|
const nowPlayingItem = session.NowPlayingItem;
|
||||||
// FIXME: It seems that, sometimes, server sends date in the future, so date-fns displays messages like 'in less than a minute'. We should fix
|
// FIXME: It seems that, sometimes, server sends date in the future, so date-fns displays messages like 'in less than a minute'. We should fix
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import type { SessionInfo } from '@jellyfin/sdk/lib/generated-client/models/session-info';
|
import type { SessionInfoDto } from '@jellyfin/sdk/lib/generated-client/models/session-info-dto';
|
||||||
import playmethodhelper from 'components/playback/playmethodhelper';
|
import playmethodhelper from 'components/playback/playmethodhelper';
|
||||||
import globalize from 'lib/globalize';
|
import globalize from 'lib/globalize';
|
||||||
|
|
||||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
const getSessionNowPlayingStreamInfo = (session: SessionInfo): string => {
|
const getSessionNowPlayingStreamInfo = (session: SessionInfoDto): string => {
|
||||||
let text = '';
|
let text = '';
|
||||||
let showTranscodingInfo = false;
|
let showTranscodingInfo = false;
|
||||||
const displayPlayMethod = playmethodhelper.getDisplayPlayMethod(session);
|
const displayPlayMethod = playmethodhelper.getDisplayPlayMethod(session);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { SessionInfo } from '@jellyfin/sdk/lib/generated-client/models/session-info';
|
import type { SessionInfoDto } from '@jellyfin/sdk/lib/generated-client/models/session-info-dto';
|
||||||
import datetime from 'scripts/datetime';
|
import datetime from 'scripts/datetime';
|
||||||
|
|
||||||
const getSessionNowPlayingTime = (session: SessionInfo) => {
|
const getSessionNowPlayingTime = (session: SessionInfoDto) => {
|
||||||
const nowPlayingItem = session.NowPlayingItem;
|
const nowPlayingItem = session.NowPlayingItem;
|
||||||
|
|
||||||
let start = '0:00';
|
let start = '0:00';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ScheduledTasksApiStartTaskRequest } from '@jellyfin/sdk/lib/generated-client/api/scheduled-tasks-api';
|
import { ScheduledTaskApiStartTaskRequest } from '@jellyfin/sdk/lib/generated-client/api/scheduled-task-api';
|
||||||
import { getScheduledTasksApi } from '@jellyfin/sdk/lib/utils/api/scheduled-tasks-api';
|
import { getScheduledTaskApi } from '@jellyfin/sdk/lib/utils/api/scheduled-task-api';
|
||||||
import { useMutation } from '@tanstack/react-query';
|
import { useMutation } from '@tanstack/react-query';
|
||||||
import { useApi } from 'hooks/useApi';
|
import { useApi } from 'hooks/useApi';
|
||||||
import { queryClient } from 'utils/query/queryClient';
|
import { queryClient } from 'utils/query/queryClient';
|
||||||
@@ -9,8 +9,8 @@ export const useStartTask = () => {
|
|||||||
const { api } = useApi();
|
const { api } = useApi();
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (params: ScheduledTasksApiStartTaskRequest) => (
|
mutationFn: (params: ScheduledTaskApiStartTaskRequest) => (
|
||||||
getScheduledTasksApi(api!)
|
getScheduledTaskApi(api!)
|
||||||
.startTask(params)
|
.startTask(params)
|
||||||
),
|
),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ScheduledTasksApiStartTaskRequest } from '@jellyfin/sdk/lib/generated-client/api/scheduled-tasks-api';
|
import { ScheduledTaskApiStartTaskRequest } from '@jellyfin/sdk/lib/generated-client/api/scheduled-task-api';
|
||||||
import { getScheduledTasksApi } from '@jellyfin/sdk/lib/utils/api/scheduled-tasks-api';
|
import { getScheduledTaskApi } from '@jellyfin/sdk/lib/utils/api/scheduled-task-api';
|
||||||
import { useMutation } from '@tanstack/react-query';
|
import { useMutation } from '@tanstack/react-query';
|
||||||
import { useApi } from 'hooks/useApi';
|
import { useApi } from 'hooks/useApi';
|
||||||
import { queryClient } from 'utils/query/queryClient';
|
import { queryClient } from 'utils/query/queryClient';
|
||||||
@@ -9,8 +9,8 @@ export const useStopTask = () => {
|
|||||||
const { api } = useApi();
|
const { api } = useApi();
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (params: ScheduledTasksApiStartTaskRequest) => (
|
mutationFn: (params: ScheduledTaskApiStartTaskRequest) => (
|
||||||
getScheduledTasksApi(api!)
|
getScheduledTaskApi(api!)
|
||||||
.stopTask(params)
|
.stopTask(params)
|
||||||
),
|
),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { ScheduledTasksApiGetTaskRequest } from '@jellyfin/sdk/lib/generated-client/api/scheduled-tasks-api';
|
import type { ScheduledTaskApiGetTaskRequest } from '@jellyfin/sdk/lib/generated-client/api/scheduled-task-api';
|
||||||
import type { AxiosRequestConfig } from 'axios';
|
import type { AxiosRequestConfig } from 'axios';
|
||||||
import type { Api } from '@jellyfin/sdk';
|
import type { Api } from '@jellyfin/sdk';
|
||||||
import { getScheduledTasksApi } from '@jellyfin/sdk/lib/utils/api/scheduled-tasks-api';
|
import { getScheduledTaskApi } from '@jellyfin/sdk/lib/utils/api/scheduled-task-api';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
import { useApi } from 'hooks/useApi';
|
import { useApi } from 'hooks/useApi';
|
||||||
@@ -9,15 +9,15 @@ import { QUERY_KEY } from './useTasks';
|
|||||||
|
|
||||||
const fetchTask = async (
|
const fetchTask = async (
|
||||||
api: Api,
|
api: Api,
|
||||||
params: ScheduledTasksApiGetTaskRequest,
|
params: ScheduledTaskApiGetTaskRequest,
|
||||||
options?: AxiosRequestConfig
|
options?: AxiosRequestConfig
|
||||||
) => {
|
) => {
|
||||||
const response = await getScheduledTasksApi(api).getTask(params, options);
|
const response = await getScheduledTaskApi(api).getTask(params, options);
|
||||||
|
|
||||||
return response.data;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useTask = (params: ScheduledTasksApiGetTaskRequest) => {
|
export const useTask = (params: ScheduledTaskApiGetTaskRequest) => {
|
||||||
const { api } = useApi();
|
const { api } = useApi();
|
||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { ScheduledTasksApiGetTasksRequest } from '@jellyfin/sdk/lib/generated-client/api/scheduled-tasks-api';
|
import type { ScheduledTaskApiGetTasksRequest } from '@jellyfin/sdk/lib/generated-client/api/scheduled-task-api';
|
||||||
import type { AxiosRequestConfig } from 'axios';
|
import type { AxiosRequestConfig } from 'axios';
|
||||||
import type { Api } from '@jellyfin/sdk';
|
import type { Api } from '@jellyfin/sdk';
|
||||||
import { getScheduledTasksApi } from '@jellyfin/sdk/lib/utils/api/scheduled-tasks-api';
|
import { getScheduledTaskApi } from '@jellyfin/sdk/lib/utils/api/scheduled-task-api';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
import { useApi } from 'hooks/useApi';
|
import { useApi } from 'hooks/useApi';
|
||||||
@@ -10,15 +10,15 @@ export const QUERY_KEY = 'Tasks';
|
|||||||
|
|
||||||
const fetchTasks = async (
|
const fetchTasks = async (
|
||||||
api: Api,
|
api: Api,
|
||||||
params?: ScheduledTasksApiGetTasksRequest,
|
params?: ScheduledTaskApiGetTasksRequest,
|
||||||
options?: AxiosRequestConfig
|
options?: AxiosRequestConfig
|
||||||
) => {
|
) => {
|
||||||
const response = await getScheduledTasksApi(api).getTasks(params, options);
|
const response = await getScheduledTaskApi(api).getTasks(params, options);
|
||||||
|
|
||||||
return response.data;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useTasks = (params?: ScheduledTasksApiGetTasksRequest) => {
|
export const useTasks = (params?: ScheduledTaskApiGetTasksRequest) => {
|
||||||
const { api } = useApi();
|
const { api } = useApi();
|
||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ScheduledTasksApiUpdateTaskRequest } from '@jellyfin/sdk/lib/generated-client/api/scheduled-tasks-api';
|
import { ScheduledTaskApiUpdateTaskRequest } from '@jellyfin/sdk/lib/generated-client/api/scheduled-task-api';
|
||||||
import { getScheduledTasksApi } from '@jellyfin/sdk/lib/utils/api/scheduled-tasks-api';
|
import { getScheduledTaskApi } from '@jellyfin/sdk/lib/utils/api/scheduled-task-api';
|
||||||
import { useMutation } from '@tanstack/react-query';
|
import { useMutation } from '@tanstack/react-query';
|
||||||
import { useApi } from 'hooks/useApi';
|
import { useApi } from 'hooks/useApi';
|
||||||
import { queryClient } from 'utils/query/queryClient';
|
import { queryClient } from 'utils/query/queryClient';
|
||||||
@@ -9,8 +9,8 @@ export const useUpdateTask = () => {
|
|||||||
const { api } = useApi();
|
const { api } = useApi();
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (params: ScheduledTasksApiUpdateTaskRequest) => (
|
mutationFn: (params: ScheduledTaskApiUpdateTaskRequest) => (
|
||||||
getScheduledTasksApi(api!)
|
getScheduledTaskApi(api!)
|
||||||
.updateTask(params)
|
.updateTask(params)
|
||||||
),
|
),
|
||||||
onSuccess: (_data, params) => {
|
onSuccess: (_data, params) => {
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useApi } from 'hooks/useApi';
|
import { useApi } from 'hooks/useApi';
|
||||||
import { QUERY_KEY, useTasks } from '../api/useTasks';
|
import { QUERY_KEY, useTasks } from '../api/useTasks';
|
||||||
import type { ScheduledTasksApiGetTasksRequest } from '@jellyfin/sdk/lib/generated-client/api/scheduled-tasks-api';
|
import type { ScheduledTaskApiGetTasksRequest } from '@jellyfin/sdk/lib/generated-client/api/scheduled-task-api';
|
||||||
import { queryClient } from 'utils/query/queryClient';
|
import { queryClient } from 'utils/query/queryClient';
|
||||||
import { OutboundWebSocketMessageType } from '@jellyfin/sdk/lib/websocket';
|
import { OutboundWebSocketMessageType } from '@jellyfin/sdk/lib/websocket';
|
||||||
|
|
||||||
const useLiveTasks = (params: ScheduledTasksApiGetTasksRequest) => {
|
const useLiveTasks = (params: ScheduledTaskApiGetTasksRequest) => {
|
||||||
const { api } = useApi();
|
const { api } = useApi();
|
||||||
const tasksQuery = useTasks(params);
|
const tasksQuery = useTasks(params);
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import { Api } from '@jellyfin/sdk';
|
import { Api } from '@jellyfin/sdk';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { useApi } from 'hooks/useApi';
|
import { useApi } from 'hooks/useApi';
|
||||||
import { getChannelsApi } from '@jellyfin/sdk/lib/utils/api/channels-api';
|
import { getChannelApi } from '@jellyfin/sdk/lib/utils/api/channel-api';
|
||||||
import { ChannelsApiGetChannelsRequest } from '@jellyfin/sdk/lib/generated-client/api/channels-api';
|
import { ChannelApiGetChannelsRequest } from '@jellyfin/sdk/lib/generated-client/api/channel-api';
|
||||||
import type { AxiosRequestConfig } from 'axios';
|
import type { AxiosRequestConfig } from 'axios';
|
||||||
|
|
||||||
const fetchChannels = async (api: Api, params?: ChannelsApiGetChannelsRequest, options?: AxiosRequestConfig) => {
|
const fetchChannels = async (api: Api, params?: ChannelApiGetChannelsRequest, options?: AxiosRequestConfig) => {
|
||||||
const response = await getChannelsApi(api).getChannels(params, options);
|
const response = await getChannelApi(api).getChannels(params, options);
|
||||||
|
|
||||||
return response.data;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useChannels = (params?: ChannelsApiGetChannelsRequest) => {
|
export const useChannels = (params?: ChannelApiGetChannelsRequest) => {
|
||||||
const { api } = useApi();
|
const { api } = useApi();
|
||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { Api } from '@jellyfin/sdk';
|
import { Api } from '@jellyfin/sdk';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { useApi } from 'hooks/useApi';
|
import { useApi } from 'hooks/useApi';
|
||||||
import { getConfigurationApi } from '@jellyfin/sdk/lib/utils/api/configuration-api';
|
import { getSystemApi } from '@jellyfin/sdk/lib/utils/api/system-api';
|
||||||
import type { AxiosRequestConfig } from 'axios';
|
import type { AxiosRequestConfig } from 'axios';
|
||||||
import type { NetworkConfiguration } from '@jellyfin/sdk/lib/generated-client/models/network-configuration';
|
import type { NetworkConfiguration } from '@jellyfin/sdk/lib/generated-client/models/network-configuration';
|
||||||
|
|
||||||
const fetchNetworkConfig = async (api: Api, options?: AxiosRequestConfig) => {
|
const fetchNetworkConfig = async (api: Api, options?: AxiosRequestConfig) => {
|
||||||
const response = await getConfigurationApi(api).getNamedConfiguration({ key: 'network' }, options);
|
const response = await getSystemApi(api).getNamedConfiguration({ key: 'network' }, options);
|
||||||
|
|
||||||
return response.data as NetworkConfiguration;
|
return response.data as NetworkConfiguration;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { useChannels } from 'apps/dashboard/features/users/api/useChannels';
|
|||||||
import { useUpdateUser } from 'apps/dashboard/features/users/api/useUpdateUser';
|
import { useUpdateUser } from 'apps/dashboard/features/users/api/useUpdateUser';
|
||||||
import { useUpdateUserPolicy } from 'apps/dashboard/features/users/api/useUpdateUserPolicy';
|
import { useUpdateUserPolicy } from 'apps/dashboard/features/users/api/useUpdateUserPolicy';
|
||||||
import { useNetworkConfig } from 'apps/dashboard/features/users/api/useNetworkConfig';
|
import { useNetworkConfig } from 'apps/dashboard/features/users/api/useNetworkConfig';
|
||||||
|
import Toast from 'apps/dashboard/components/Toast';
|
||||||
|
|
||||||
interface ProfileProps {
|
interface ProfileProps {
|
||||||
userDto: UserDto;
|
userDto: UserDto;
|
||||||
@@ -45,6 +46,12 @@ const Profile = ({ userDto }: ProfileProps) => {
|
|||||||
const { data: channels, isSuccess: isChannelsSuccess } = useChannels({ supportsMediaDeletion: true });
|
const { data: channels, isSuccess: isChannelsSuccess } = useChannels({ supportsMediaDeletion: true });
|
||||||
const { data: netConfig, isSuccess: isNetConfigSuccess } = useNetworkConfig();
|
const { data: netConfig, isSuccess: isNetConfigSuccess } = useNetworkConfig();
|
||||||
|
|
||||||
|
const [ isErrorToastOpen, setIsErrorToastOpen ] = useState(false);
|
||||||
|
|
||||||
|
const handleToastClose = useCallback(() => {
|
||||||
|
setIsErrorToastOpen(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const updateUser = useUpdateUser();
|
const updateUser = useUpdateUser();
|
||||||
const updateUserPolicy = useUpdateUserPolicy();
|
const updateUserPolicy = useUpdateUserPolicy();
|
||||||
|
|
||||||
@@ -255,6 +262,10 @@ const Profile = ({ userDto }: ProfileProps) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
loading.hide();
|
||||||
|
setIsErrorToastOpen(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -306,6 +317,11 @@ const Profile = ({ userDto }: ProfileProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={element}>
|
<div ref={element}>
|
||||||
|
<Toast
|
||||||
|
open={isErrorToastOpen}
|
||||||
|
onClose={handleToastClose}
|
||||||
|
message={globalize.translate('ErrorDefault')}
|
||||||
|
/>
|
||||||
<div
|
<div
|
||||||
className='lnkEditUserPreferencesContainer'
|
className='lnkEditUserPreferencesContainer'
|
||||||
style={{ paddingBottom: '1em' }}
|
style={{ paddingBottom: '1em' }}
|
||||||
@@ -332,6 +348,9 @@ const Profile = ({ userDto }: ProfileProps) => {
|
|||||||
label={globalize.translate('LabelName')}
|
label={globalize.translate('LabelName')}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
<div className='fieldDescription'>
|
||||||
|
{globalize.translate('LabelUsernameAllowedCharactersHelp')}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='selectContainer fldSelectLoginProvider hide'>
|
<div className='selectContainer fldSelectLoginProvider hide'>
|
||||||
<SelectElement
|
<SelectElement
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { BrandingOptions } from '@jellyfin/sdk/lib/generated-client/models/branding-options';
|
import type { BrandingOptionsDto } from '@jellyfin/sdk/lib/generated-client/models/branding-options-dto';
|
||||||
import { getConfigurationApi } from '@jellyfin/sdk/lib/utils/api/configuration-api';
|
import { getSystemApi } from '@jellyfin/sdk/lib/utils/api/system-api';
|
||||||
import { getImageApi } from '@jellyfin/sdk/lib/utils/api/image-api';
|
import { getImageApi } from '@jellyfin/sdk/lib/utils/api/image-api';
|
||||||
import Delete from '@mui/icons-material/Delete';
|
import Delete from '@mui/icons-material/Delete';
|
||||||
import Upload from '@mui/icons-material/Upload';
|
import Upload from '@mui/icons-material/Upload';
|
||||||
@@ -39,13 +39,13 @@ export const action = async ({ request }: ActionFunctionArgs) => {
|
|||||||
const formData = await request.formData();
|
const formData = await request.formData();
|
||||||
const data = Object.fromEntries(formData);
|
const data = Object.fromEntries(formData);
|
||||||
|
|
||||||
const brandingOptions: BrandingOptions = {
|
const brandingOptions: BrandingOptionsDto = {
|
||||||
CustomCss: data.CustomCss?.toString(),
|
CustomCss: data.CustomCss?.toString(),
|
||||||
LoginDisclaimer: data.LoginDisclaimer?.toString(),
|
LoginDisclaimer: data.LoginDisclaimer?.toString(),
|
||||||
SplashscreenEnabled: data.SplashscreenEnabled?.toString() === 'on'
|
SplashscreenEnabled: data.SplashscreenEnabled?.toString() === 'on'
|
||||||
};
|
};
|
||||||
|
|
||||||
await getConfigurationApi(api)
|
await getSystemApi(api)
|
||||||
.updateNamedConfiguration({
|
.updateNamedConfiguration({
|
||||||
key: BRANDING_CONFIG_KEY,
|
key: BRANDING_CONFIG_KEY,
|
||||||
body: JSON.stringify(brandingOptions)
|
body: JSON.stringify(brandingOptions)
|
||||||
@@ -150,7 +150,7 @@ export const Component = () => {
|
|||||||
const setSplashscreenEnabled = useCallback(async (_: React.ChangeEvent<HTMLInputElement>, isEnabled: boolean) => {
|
const setSplashscreenEnabled = useCallback(async (_: React.ChangeEvent<HTMLInputElement>, isEnabled: boolean) => {
|
||||||
setIsSplashscreenEnabled(isEnabled);
|
setIsSplashscreenEnabled(isEnabled);
|
||||||
|
|
||||||
await getConfigurationApi(api!)
|
await getSystemApi(api!)
|
||||||
.updateNamedConfiguration({
|
.updateNamedConfiguration({
|
||||||
key: BRANDING_CONFIG_KEY,
|
key: BRANDING_CONFIG_KEY,
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ export const Component = () => {
|
|||||||
const onConfirmDelete = useCallback(() => {
|
const onConfirmDelete = useCallback(() => {
|
||||||
if (pendingDeleteDeviceId) {
|
if (pendingDeleteDeviceId) {
|
||||||
deleteDevice.mutate({
|
deleteDevice.mutate({
|
||||||
id: pendingDeleteDeviceId
|
id: [pendingDeleteDeviceId]
|
||||||
}, {
|
}, {
|
||||||
onSettled: onCloseDeleteConfirmDialog
|
onSettled: onCloseDeleteConfirmDialog
|
||||||
});
|
});
|
||||||
@@ -97,7 +97,7 @@ export const Component = () => {
|
|||||||
Promise
|
Promise
|
||||||
.all(devices.map(item => {
|
.all(devices.map(item => {
|
||||||
if (api && item.Id && api.deviceInfo.id === item.Id) {
|
if (api && item.Id && api.deviceInfo.id === item.Id) {
|
||||||
return deleteDevice.mutateAsync({ id: item.Id });
|
return deleteDevice.mutateAsync({ id: [item.Id] });
|
||||||
}
|
}
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import TextField from '@mui/material/TextField';
|
|||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import Loading from 'components/loading/LoadingComponent';
|
import Loading from 'components/loading/LoadingComponent';
|
||||||
import Page from 'components/Page';
|
import Page from 'components/Page';
|
||||||
import { getConfigurationApi } from '@jellyfin/sdk/lib/utils/api/configuration-api';
|
import { getSystemApi } from '@jellyfin/sdk/lib/utils/api/system-api';
|
||||||
import { QUERY_KEY as CONFIG_QUERY_KEY, useConfiguration } from 'hooks/useConfiguration';
|
import { QUERY_KEY as CONFIG_QUERY_KEY, useConfiguration } from 'hooks/useConfiguration';
|
||||||
import { QUERY_KEY as NAMED_CONFIG_QUERY_KEY, useNamedConfiguration } from 'hooks/useNamedConfiguration';
|
import { QUERY_KEY as NAMED_CONFIG_QUERY_KEY, useNamedConfiguration } from 'hooks/useNamedConfiguration';
|
||||||
import globalize from 'lib/globalize';
|
import globalize from 'lib/globalize';
|
||||||
@@ -31,7 +31,7 @@ export const action = async ({ request }: ActionFunctionArgs) => {
|
|||||||
const formData = await request.formData();
|
const formData = await request.formData();
|
||||||
const data = Object.fromEntries(formData);
|
const data = Object.fromEntries(formData);
|
||||||
|
|
||||||
const { data: config } = await getConfigurationApi(api).getConfiguration();
|
const { data: config } = await getSystemApi(api).getConfiguration();
|
||||||
|
|
||||||
const metadataConfig: MetadataConfiguration = {
|
const metadataConfig: MetadataConfiguration = {
|
||||||
UseFileCreationTimeForDateAdded: data.DateAddedBehavior.toString() === '1'
|
UseFileCreationTimeForDateAdded: data.DateAddedBehavior.toString() === '1'
|
||||||
@@ -43,10 +43,10 @@ export const action = async ({ request }: ActionFunctionArgs) => {
|
|||||||
config.EnableGroupingShowsIntoCollections = data.GroupShowsIntoCollections?.toString() === 'on';
|
config.EnableGroupingShowsIntoCollections = data.GroupShowsIntoCollections?.toString() === 'on';
|
||||||
config.EnableExternalContentInSuggestions = data.EnableExternalContentInSuggestions?.toString() === 'on';
|
config.EnableExternalContentInSuggestions = data.EnableExternalContentInSuggestions?.toString() === 'on';
|
||||||
|
|
||||||
await getConfigurationApi(api)
|
await getSystemApi(api)
|
||||||
.updateConfiguration({ serverConfiguration: config });
|
.updateConfiguration({ serverConfiguration: config });
|
||||||
|
|
||||||
await getConfigurationApi(api)
|
await getSystemApi(api)
|
||||||
.updateNamedConfiguration({ key: CONFIG_KEY, body: metadataConfig });
|
.updateNamedConfiguration({ key: CONFIG_KEY, body: metadataConfig });
|
||||||
|
|
||||||
void queryClient.invalidateQueries({
|
void queryClient.invalidateQueries({
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ImageResolution } from '@jellyfin/sdk/lib/generated-client/models/image-resolution';
|
import { ImageResolution } from '@jellyfin/sdk/lib/generated-client/models/image-resolution';
|
||||||
import { getConfigurationApi } from '@jellyfin/sdk/lib/utils/api/configuration-api';
|
import { getSystemApi } from '@jellyfin/sdk/lib/utils/api/system-api';
|
||||||
import Alert from '@mui/material/Alert';
|
import Alert from '@mui/material/Alert';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
@@ -27,14 +27,14 @@ export const action = async ({ request }: ActionFunctionArgs) => {
|
|||||||
const formData = await request.formData();
|
const formData = await request.formData();
|
||||||
const data = Object.fromEntries(formData);
|
const data = Object.fromEntries(formData);
|
||||||
|
|
||||||
const { data: config } = await getConfigurationApi(api).getConfiguration();
|
const { data: config } = await getSystemApi(api).getConfiguration();
|
||||||
|
|
||||||
config.PreferredMetadataLanguage = data.Language.toString();
|
config.PreferredMetadataLanguage = data.Language.toString();
|
||||||
config.MetadataCountryCode = data.Country.toString();
|
config.MetadataCountryCode = data.Country.toString();
|
||||||
config.DummyChapterDuration = parseInt(data.DummyChapterDuration.toString(), 10);
|
config.DummyChapterDuration = parseInt(data.DummyChapterDuration.toString(), 10);
|
||||||
config.ChapterImageResolution = data.ChapterImageResolution.toString() as ImageResolution;
|
config.ChapterImageResolution = data.ChapterImageResolution.toString() as ImageResolution;
|
||||||
|
|
||||||
await getConfigurationApi(api)
|
await getSystemApi(api)
|
||||||
.updateConfiguration({ serverConfiguration: config });
|
.updateConfiguration({ serverConfiguration: config });
|
||||||
|
|
||||||
void queryClient.invalidateQueries({
|
void queryClient.invalidateQueries({
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { getConfigurationApi } from '@jellyfin/sdk/lib/utils/api/configuration-api';
|
import { getSystemApi } from '@jellyfin/sdk/lib/utils/api/system-api';
|
||||||
import Alert from '@mui/material/Alert';
|
import Alert from '@mui/material/Alert';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
@@ -40,7 +40,7 @@ export const action = async ({ request }: ActionFunctionArgs) => {
|
|||||||
EnableExtraThumbsDuplication: data.EnableExtraThumbsDuplication?.toString() === 'on'
|
EnableExtraThumbsDuplication: data.EnableExtraThumbsDuplication?.toString() === 'on'
|
||||||
};
|
};
|
||||||
|
|
||||||
await getConfigurationApi(api)
|
await getSystemApi(api)
|
||||||
.updateNamedConfiguration({ key: CONFIG_KEY, body: newConfig });
|
.updateNamedConfiguration({ key: CONFIG_KEY, body: newConfig });
|
||||||
|
|
||||||
void queryClient.invalidateQueries({
|
void queryClient.invalidateQueries({
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import Checkbox from '@mui/material/Checkbox';
|
|||||||
import FormHelperText from '@mui/material/FormHelperText';
|
import FormHelperText from '@mui/material/FormHelperText';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||||
import { getConfigurationApi } from '@jellyfin/sdk/lib/utils/api/configuration-api';
|
import { getSystemApi } from '@jellyfin/sdk/lib/utils/api/system-api';
|
||||||
import { queryClient } from 'utils/query/queryClient';
|
import { queryClient } from 'utils/query/queryClient';
|
||||||
import { ActionData } from 'types/actionData';
|
import { ActionData } from 'types/actionData';
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ export const action = async ({ request }: ActionFunctionArgs) => {
|
|||||||
|
|
||||||
const data = await request.json() as LiveTvOptions;
|
const data = await request.json() as LiveTvOptions;
|
||||||
|
|
||||||
await getConfigurationApi(api)
|
await getSystemApi(api)
|
||||||
.updateNamedConfiguration({ key: CONFIG_KEY, body: data });
|
.updateNamedConfiguration({ key: CONFIG_KEY, body: data });
|
||||||
|
|
||||||
void queryClient.invalidateQueries({
|
void queryClient.invalidateQueries({
|
||||||
|
|||||||
@@ -110,7 +110,16 @@ export const Component = () => {
|
|||||||
ref={contentPrimaryRef}
|
ref={contentPrimaryRef}
|
||||||
>
|
>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant='h1'>{fileName}</Typography>
|
<Typography
|
||||||
|
variant='h1'
|
||||||
|
sx={{
|
||||||
|
whiteSpace: 'normal',
|
||||||
|
overflowWrap: 'anywhere',
|
||||||
|
wordBreak: 'break-word'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{fileName}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<Alert
|
<Alert
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { ChangeEvent, useCallback, useEffect, useState } from 'react';
|
import React, { ChangeEvent, useCallback, useEffect, useState } from 'react';
|
||||||
import { getConfigurationApi } from '@jellyfin/sdk/lib/utils/api/configuration-api';
|
import { getSystemApi } from '@jellyfin/sdk/lib/utils/api/system-api';
|
||||||
import Loading from 'components/loading/LoadingComponent';
|
import Loading from 'components/loading/LoadingComponent';
|
||||||
import Page from 'components/Page';
|
import Page from 'components/Page';
|
||||||
import globalize from 'lib/globalize';
|
import globalize from 'lib/globalize';
|
||||||
@@ -24,7 +24,7 @@ export const action = async ({ request }: ActionFunctionArgs) => {
|
|||||||
if (!api) throw new Error('No Api instance available');
|
if (!api) throw new Error('No Api instance available');
|
||||||
|
|
||||||
const formData = await request.formData();
|
const formData = await request.formData();
|
||||||
const { data: config } = await getConfigurationApi(api).getConfiguration();
|
const { data: config } = await getSystemApi(api).getConfiguration();
|
||||||
|
|
||||||
const enableWarningMessage = formData.get('EnableWarningMessage');
|
const enableWarningMessage = formData.get('EnableWarningMessage');
|
||||||
config.EnableSlowResponseWarning = enableWarningMessage === 'on';
|
config.EnableSlowResponseWarning = enableWarningMessage === 'on';
|
||||||
@@ -34,7 +34,7 @@ export const action = async ({ request }: ActionFunctionArgs) => {
|
|||||||
config.SlowResponseThresholdMs = parseInt(responseTime.toString(), 10);
|
config.SlowResponseThresholdMs = parseInt(responseTime.toString(), 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
await getConfigurationApi(api)
|
await getSystemApi(api)
|
||||||
.updateConfiguration({ serverConfiguration: config });
|
.updateConfiguration({ serverConfiguration: config });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import DirectoryBrowser from 'components/directorybrowser/directorybrowser';
|
|||||||
import MenuItem from '@mui/material/MenuItem';
|
import MenuItem from '@mui/material/MenuItem';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||||
import { getConfigurationApi } from '@jellyfin/sdk/lib/utils/api/configuration-api';
|
import { getSystemApi } from '@jellyfin/sdk/lib/utils/api/system-api';
|
||||||
import { QUERY_KEY as CONFIG_QUERY_KEY } from 'hooks/useConfiguration';
|
import { QUERY_KEY as CONFIG_QUERY_KEY } from 'hooks/useConfiguration';
|
||||||
import { queryClient } from 'utils/query/queryClient';
|
import { queryClient } from 'utils/query/queryClient';
|
||||||
import { encodePublishedServerUris, getPublishedServerUris, PublishedServerUris, splitString } from 'apps/dashboard/features/networking/utils';
|
import { encodePublishedServerUris, getPublishedServerUris, PublishedServerUris, splitString } from 'apps/dashboard/features/networking/utils';
|
||||||
@@ -69,7 +69,7 @@ export const action = async ({ request }: ActionFunctionArgs) => {
|
|||||||
|
|
||||||
newConfig.PublishedServerUriBySubnet = encodePublishedServerUris(publishedServerUri);
|
newConfig.PublishedServerUriBySubnet = encodePublishedServerUris(publishedServerUri);
|
||||||
|
|
||||||
await getConfigurationApi(api)
|
await getSystemApi(api)
|
||||||
.updateNamedConfiguration({ key: CONFIG_KEY, body: newConfig });
|
.updateNamedConfiguration({ key: CONFIG_KEY, body: newConfig });
|
||||||
|
|
||||||
void queryClient.invalidateQueries({
|
void queryClient.invalidateQueries({
|
||||||
|
|||||||
@@ -12,14 +12,14 @@ import { type ActionFunctionArgs, Form, useActionData, useNavigation } from 'rea
|
|||||||
import { ActionData } from 'types/actionData';
|
import { ActionData } from 'types/actionData';
|
||||||
import { QUERY_KEY, useConfiguration } from 'hooks/useConfiguration';
|
import { QUERY_KEY, useConfiguration } from 'hooks/useConfiguration';
|
||||||
import Loading from 'components/loading/LoadingComponent';
|
import Loading from 'components/loading/LoadingComponent';
|
||||||
import { getConfigurationApi } from '@jellyfin/sdk/lib/utils/api/configuration-api';
|
import { getSystemApi } from '@jellyfin/sdk/lib/utils/api/system-api';
|
||||||
import { queryClient } from 'utils/query/queryClient';
|
import { queryClient } from 'utils/query/queryClient';
|
||||||
|
|
||||||
export const action = async ({ request }: ActionFunctionArgs) => {
|
export const action = async ({ request }: ActionFunctionArgs) => {
|
||||||
const api = ServerConnections.getApi();
|
const api = ServerConnections.getApi();
|
||||||
if (!api) throw new Error('No Api instance available');
|
if (!api) throw new Error('No Api instance available');
|
||||||
|
|
||||||
const { data: config } = await getConfigurationApi(api).getConfiguration();
|
const { data: config } = await getSystemApi(api).getConfiguration();
|
||||||
const formData = await request.formData();
|
const formData = await request.formData();
|
||||||
|
|
||||||
const minResumePercentage = formData.get('MinResumePercentage')?.toString();
|
const minResumePercentage = formData.get('MinResumePercentage')?.toString();
|
||||||
@@ -34,7 +34,7 @@ export const action = async ({ request }: ActionFunctionArgs) => {
|
|||||||
if (maxAudiobookResume) config.MaxAudiobookResume = parseInt(maxAudiobookResume, 10);
|
if (maxAudiobookResume) config.MaxAudiobookResume = parseInt(maxAudiobookResume, 10);
|
||||||
if (minResumeDuration) config.MinResumeDurationSeconds = parseInt(minResumeDuration, 10);
|
if (minResumeDuration) config.MinResumeDurationSeconds = parseInt(minResumeDuration, 10);
|
||||||
|
|
||||||
await getConfigurationApi(api)
|
await getSystemApi(api)
|
||||||
.updateConfiguration({ serverConfiguration: config });
|
.updateConfiguration({ serverConfiguration: config });
|
||||||
|
|
||||||
void queryClient.invalidateQueries({
|
void queryClient.invalidateQueries({
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import Stack from '@mui/material/Stack';
|
|||||||
import TextField from '@mui/material/TextField';
|
import TextField from '@mui/material/TextField';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import { type ActionFunctionArgs, Form, useActionData, useNavigation } from 'react-router-dom';
|
import { type ActionFunctionArgs, Form, useActionData, useNavigation } from 'react-router-dom';
|
||||||
import { getConfigurationApi } from '@jellyfin/sdk/lib/utils/api/configuration-api';
|
import { getSystemApi } from '@jellyfin/sdk/lib/utils/api/system-api';
|
||||||
import { QUERY_KEY, useConfiguration } from 'hooks/useConfiguration';
|
import { QUERY_KEY, useConfiguration } from 'hooks/useConfiguration';
|
||||||
import Loading from 'components/loading/LoadingComponent';
|
import Loading from 'components/loading/LoadingComponent';
|
||||||
import { ActionData } from 'types/actionData';
|
import { ActionData } from 'types/actionData';
|
||||||
@@ -19,13 +19,13 @@ export const action = async ({ request }: ActionFunctionArgs) => {
|
|||||||
const api = ServerConnections.getApi();
|
const api = ServerConnections.getApi();
|
||||||
if (!api) throw new Error('No Api instance available');
|
if (!api) throw new Error('No Api instance available');
|
||||||
|
|
||||||
const { data: config } = await getConfigurationApi(api).getConfiguration();
|
const { data: config } = await getSystemApi(api).getConfiguration();
|
||||||
const formData = await request.formData();
|
const formData = await request.formData();
|
||||||
|
|
||||||
const bitrateLimit = formData.get('StreamingBitrateLimit')?.toString();
|
const bitrateLimit = formData.get('StreamingBitrateLimit')?.toString();
|
||||||
config.RemoteClientBitrateLimit = Math.trunc(1e6 * parseFloat(bitrateLimit || '0'));
|
config.RemoteClientBitrateLimit = Math.trunc(1e6 * parseFloat(bitrateLimit || '0'));
|
||||||
|
|
||||||
await getConfigurationApi(api)
|
await getSystemApi(api)
|
||||||
.updateConfiguration({ serverConfiguration: config });
|
.updateConfiguration({ serverConfiguration: config });
|
||||||
|
|
||||||
void queryClient.invalidateQueries({
|
void queryClient.invalidateQueries({
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import { QUERY_KEY, useNamedConfiguration } from 'hooks/useNamedConfiguration';
|
|||||||
import type { EncodingOptions } from '@jellyfin/sdk/lib/generated-client/models/encoding-options';
|
import type { EncodingOptions } from '@jellyfin/sdk/lib/generated-client/models/encoding-options';
|
||||||
import { HardwareAccelerationType } from '@jellyfin/sdk/lib/generated-client/models/hardware-acceleration-type';
|
import { HardwareAccelerationType } from '@jellyfin/sdk/lib/generated-client/models/hardware-acceleration-type';
|
||||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||||
import { getConfigurationApi } from '@jellyfin/sdk/lib/utils/api/configuration-api';
|
import { getSystemApi } from '@jellyfin/sdk/lib/utils/api/system-api';
|
||||||
import { queryClient } from 'utils/query/queryClient';
|
import { queryClient } from 'utils/query/queryClient';
|
||||||
import { ActionData } from 'types/actionData';
|
import { ActionData } from 'types/actionData';
|
||||||
import { CODECS, HEVC_REXT_DECODING_TYPES, HEVC_VP9_HW_DECODING_TYPES } from 'apps/dashboard/features/playback/constants/codecs';
|
import { CODECS, HEVC_REXT_DECODING_TYPES, HEVC_VP9_HW_DECODING_TYPES } from 'apps/dashboard/features/playback/constants/codecs';
|
||||||
@@ -38,7 +38,7 @@ export const action = async ({ request }: ActionFunctionArgs) => {
|
|||||||
|
|
||||||
const data = await request.json() as EncodingOptions;
|
const data = await request.json() as EncodingOptions;
|
||||||
|
|
||||||
await getConfigurationApi(api)
|
await getSystemApi(api)
|
||||||
.updateNamedConfiguration({ key: CONFIG_KEY, body: data });
|
.updateNamedConfiguration({ key: CONFIG_KEY, body: data });
|
||||||
|
|
||||||
void queryClient.invalidateQueries({
|
void queryClient.invalidateQueries({
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import MenuItem from '@mui/material/MenuItem';
|
|||||||
import TextField from '@mui/material/TextField';
|
import TextField from '@mui/material/TextField';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
import Alert from '@mui/material/Alert';
|
import Alert from '@mui/material/Alert';
|
||||||
import { getConfigurationApi } from '@jellyfin/sdk/lib/utils/api/configuration-api';
|
import { getSystemApi } from '@jellyfin/sdk/lib/utils/api/system-api';
|
||||||
import { TrickplayScanBehavior } from '@jellyfin/sdk/lib/generated-client/models/trickplay-scan-behavior';
|
import { TrickplayScanBehavior } from '@jellyfin/sdk/lib/generated-client/models/trickplay-scan-behavior';
|
||||||
import { ProcessPriorityClass } from '@jellyfin/sdk/lib/generated-client/models/process-priority-class';
|
import { ProcessPriorityClass } from '@jellyfin/sdk/lib/generated-client/models/process-priority-class';
|
||||||
import { ActionData } from 'types/actionData';
|
import { ActionData } from 'types/actionData';
|
||||||
@@ -30,7 +30,7 @@ export const action = async ({ request }: ActionFunctionArgs) => {
|
|||||||
const formData = await request.formData();
|
const formData = await request.formData();
|
||||||
const data = Object.fromEntries(formData);
|
const data = Object.fromEntries(formData);
|
||||||
|
|
||||||
const { data: config } = await getConfigurationApi(api).getConfiguration();
|
const { data: config } = await getSystemApi(api).getConfiguration();
|
||||||
|
|
||||||
const options = config.TrickplayOptions;
|
const options = config.TrickplayOptions;
|
||||||
if (!options) throw new Error('Unexpected null TrickplayOptions');
|
if (!options) throw new Error('Unexpected null TrickplayOptions');
|
||||||
@@ -48,7 +48,7 @@ export const action = async ({ request }: ActionFunctionArgs) => {
|
|||||||
options.JpegQuality = parseInt(data.JpegQuality.toString() || '90', 10);
|
options.JpegQuality = parseInt(data.JpegQuality.toString() || '90', 10);
|
||||||
options.ProcessThreads = parseInt(data.TrickplayThreads.toString() || '1', 10);
|
options.ProcessThreads = parseInt(data.TrickplayThreads.toString() || '1', 10);
|
||||||
|
|
||||||
await getConfigurationApi(api)
|
await getSystemApi(api)
|
||||||
.updateConfiguration({ serverConfiguration: config });
|
.updateConfiguration({ serverConfiguration: config });
|
||||||
|
|
||||||
void queryClient.invalidateQueries({
|
void queryClient.invalidateQueries({
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import Checkbox from '@mui/material/Checkbox';
|
|||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
import Link from '@mui/material/Link';
|
import Link from '@mui/material/Link';
|
||||||
import DirectoryBrowser from 'components/directorybrowser/directorybrowser';
|
import DirectoryBrowser from 'components/directorybrowser/directorybrowser';
|
||||||
import { getConfigurationApi } from '@jellyfin/sdk/lib/utils/api/configuration-api';
|
import { getSystemApi } from '@jellyfin/sdk/lib/utils/api/system-api';
|
||||||
import { queryClient } from 'utils/query/queryClient';
|
import { queryClient } from 'utils/query/queryClient';
|
||||||
import { ActionData } from 'types/actionData';
|
import { ActionData } from 'types/actionData';
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ export const action = async ({ request }: ActionFunctionArgs) => {
|
|||||||
const api = ServerConnections.getApi();
|
const api = ServerConnections.getApi();
|
||||||
if (!api) throw new Error('No Api instance available');
|
if (!api) throw new Error('No Api instance available');
|
||||||
|
|
||||||
const { data: config } = await getConfigurationApi(api).getConfiguration();
|
const { data: config } = await getSystemApi(api).getConfiguration();
|
||||||
const formData = await request.formData();
|
const formData = await request.formData();
|
||||||
|
|
||||||
config.ServerName = formData.get('ServerName')?.toString();
|
config.ServerName = formData.get('ServerName')?.toString();
|
||||||
@@ -41,7 +41,7 @@ export const action = async ({ request }: ActionFunctionArgs) => {
|
|||||||
config.LibraryScanFanoutConcurrency = parseInt(formData.get('LibraryScanFanoutConcurrency')?.toString() || '0', 10);
|
config.LibraryScanFanoutConcurrency = parseInt(formData.get('LibraryScanFanoutConcurrency')?.toString() || '0', 10);
|
||||||
config.ParallelImageEncodingLimit = parseInt(formData.get('ParallelImageEncodingLimit')?.toString() || '0', 10);
|
config.ParallelImageEncodingLimit = parseInt(formData.get('ParallelImageEncodingLimit')?.toString() || '0', 10);
|
||||||
|
|
||||||
await getConfigurationApi(api)
|
await getSystemApi(api)
|
||||||
.updateConfiguration({ serverConfiguration: config });
|
.updateConfiguration({ serverConfiguration: config });
|
||||||
|
|
||||||
void queryClient.invalidateQueries({
|
void queryClient.invalidateQueries({
|
||||||
|
|||||||
@@ -167,6 +167,10 @@ const UserNew = () => {
|
|||||||
setIsErrorToastOpen(true);
|
setIsErrorToastOpen(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
loading.hide();
|
||||||
|
setIsErrorToastOpen(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -231,6 +235,9 @@ const UserNew = () => {
|
|||||||
label={globalize.translate('LabelName')}
|
label={globalize.translate('LabelName')}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
<div className='fieldDescription'>
|
||||||
|
{globalize.translate('LabelUsernameAllowedCharactersHelp')}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='inputContainer'>
|
<div className='inputContainer'>
|
||||||
<Input
|
<Input
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-ite
|
|||||||
import { ImageType } from '@jellyfin/sdk/lib/generated-client/models/image-type';
|
import { ImageType } from '@jellyfin/sdk/lib/generated-client/models/image-type';
|
||||||
import { ItemFields } from '@jellyfin/sdk/lib/generated-client/models/item-fields';
|
import { ItemFields } from '@jellyfin/sdk/lib/generated-client/models/item-fields';
|
||||||
import { ItemSortBy } from '@jellyfin/sdk/lib/generated-client/models/item-sort-by';
|
import { ItemSortBy } from '@jellyfin/sdk/lib/generated-client/models/item-sort-by';
|
||||||
import { getStudiosApi } from '@jellyfin/sdk/lib/utils/api/studios-api';
|
import { getStudioApi } from '@jellyfin/sdk/lib/utils/api/studio-api';
|
||||||
|
|
||||||
import cardBuilder from 'components/cardbuilder/cardBuilder';
|
import cardBuilder from 'components/cardbuilder/cardBuilder';
|
||||||
import { getBackdropShape, getPortraitShape, getSquareShape } from 'components/cardbuilder/utils/shape';
|
import { getBackdropShape, getPortraitShape, getSquareShape } from 'components/cardbuilder/utils/shape';
|
||||||
@@ -208,7 +208,7 @@ function getFetchDataFn(section) {
|
|||||||
return Promise.resolve(undefined);
|
return Promise.resolve(undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
return getStudiosApi(api)
|
return getStudioApi(api)
|
||||||
.getStudios({
|
.getStudios({
|
||||||
userId,
|
userId,
|
||||||
isFavorite: true,
|
isFavorite: true,
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-ite
|
|||||||
import { ItemFields } from '@jellyfin/sdk/lib/generated-client/models/item-fields';
|
import { ItemFields } from '@jellyfin/sdk/lib/generated-client/models/item-fields';
|
||||||
import { PersonKind } from '@jellyfin/sdk/lib/generated-client/models/person-kind';
|
import { PersonKind } from '@jellyfin/sdk/lib/generated-client/models/person-kind';
|
||||||
import { getLibraryApi } from '@jellyfin/sdk/lib/utils/api/library-api';
|
import { getLibraryApi } from '@jellyfin/sdk/lib/utils/api/library-api';
|
||||||
import { getUserLibraryApi } from '@jellyfin/sdk/lib/utils/api/user-library-api';
|
|
||||||
import { intervalToDuration } from 'date-fns';
|
import { intervalToDuration } from 'date-fns';
|
||||||
import DOMPurify from 'dompurify';
|
import DOMPurify from 'dompurify';
|
||||||
import escapeHtml from 'escape-html';
|
import escapeHtml from 'escape-html';
|
||||||
@@ -1088,7 +1087,7 @@ function renderMoreFromSeason(view, item, apiClient) {
|
|||||||
cardBuilder.buildCards(result.Items, {
|
cardBuilder.buildCards(result.Items, {
|
||||||
parentContainer: section,
|
parentContainer: section,
|
||||||
itemsContainer: itemsContainer,
|
itemsContainer: itemsContainer,
|
||||||
shape: 'autooverflow',
|
shape: 'overflowBackdrop',
|
||||||
sectionTitleTagName: 'h2',
|
sectionTitleTagName: 'h2',
|
||||||
scalable: true,
|
scalable: true,
|
||||||
showTitle: true,
|
showTitle: true,
|
||||||
@@ -2170,7 +2169,7 @@ export default function (view, params) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
getUserLibraryApi(api).getItem({
|
getLibraryApi(api).getItem({
|
||||||
userId: apiClient?.getCurrentUserId(),
|
userId: apiClient?.getCurrentUserId(),
|
||||||
itemId: selectedId
|
itemId: selectedId
|
||||||
}).then(function ({ data: altItem }) {
|
}).then(function ({ data: altItem }) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { getLyricsApi } from '@jellyfin/sdk/lib/utils/api/lyrics-api';
|
import { getLyricApi } from '@jellyfin/sdk/lib/utils/api/lyric-api';
|
||||||
import escapeHtml from 'escape-html';
|
import escapeHtml from 'escape-html';
|
||||||
|
|
||||||
import { AutoScroll } from 'apps/legacy/features/lyrics/constants/autoScroll';
|
import { AutoScroll } from 'apps/legacy/features/lyrics/constants/autoScroll';
|
||||||
@@ -137,7 +137,7 @@ export default function (view) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return getLyricsApi(api).getLyrics({ itemId })
|
return getLyricApi(api).getLyrics({ itemId })
|
||||||
.then(({ data }) => {
|
.then(({ data }) => {
|
||||||
if (!data.Lyrics?.length) {
|
if (!data.Lyrics?.length) {
|
||||||
throw new Error('No lyrics returned');
|
throw new Error('No lyrics returned');
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { Api } from '@jellyfin/sdk/lib/api';
|
import type { Api } from '@jellyfin/sdk/lib/api';
|
||||||
import type { UserLibraryApiGetLatestMediaRequest } from '@jellyfin/sdk/lib/generated-client/api/user-library-api';
|
import type { LibraryApiGetLatestMediaRequest } from '@jellyfin/sdk/lib/generated-client/api/library-api';
|
||||||
import { getUserLibraryApi } from '@jellyfin/sdk/lib/utils/api/user-library-api';
|
import { getLibraryApi } from '@jellyfin/sdk/lib/utils/api/library-api';
|
||||||
import { queryOptions, useQuery } from '@tanstack/react-query';
|
import { queryOptions, useQuery } from '@tanstack/react-query';
|
||||||
import type { AxiosRequestConfig } from 'axios';
|
import type { AxiosRequestConfig } from 'axios';
|
||||||
|
|
||||||
@@ -8,17 +8,17 @@ import { useApi } from 'hooks/useApi';
|
|||||||
|
|
||||||
const fetchLatestMedia = async (
|
const fetchLatestMedia = async (
|
||||||
api: Api,
|
api: Api,
|
||||||
params?: UserLibraryApiGetLatestMediaRequest,
|
params?: LibraryApiGetLatestMediaRequest,
|
||||||
options?: AxiosRequestConfig
|
options?: AxiosRequestConfig
|
||||||
) => {
|
) => {
|
||||||
const response = await getUserLibraryApi(api)
|
const response = await getLibraryApi(api)
|
||||||
.getLatestMedia(params, options);
|
.getLatestMedia(params, options);
|
||||||
return response.data;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getLatestMediaQuery = (
|
export const getLatestMediaQuery = (
|
||||||
api?: Api,
|
api?: Api,
|
||||||
params?: UserLibraryApiGetLatestMediaRequest
|
params?: LibraryApiGetLatestMediaRequest
|
||||||
) => queryOptions({
|
) => queryOptions({
|
||||||
queryKey: [ 'User', params?.userId, 'LatestMedia', params ],
|
queryKey: [ 'User', params?.userId, 'LatestMedia', params ],
|
||||||
queryFn: ({ signal }) => fetchLatestMedia(api!, params, { signal }),
|
queryFn: ({ signal }) => fetchLatestMedia(api!, params, { signal }),
|
||||||
@@ -26,7 +26,7 @@ export const getLatestMediaQuery = (
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const useLatestMedia = (
|
export const useLatestMedia = (
|
||||||
params?: UserLibraryApiGetLatestMediaRequest
|
params?: LibraryApiGetLatestMediaRequest
|
||||||
) => {
|
) => {
|
||||||
const { api, user } = useApi();
|
const { api, user } = useApi();
|
||||||
return useQuery(getLatestMediaQuery(api, {
|
return useQuery(getLatestMediaQuery(api, {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { Api } from '@jellyfin/sdk/lib/api';
|
import type { Api } from '@jellyfin/sdk/lib/api';
|
||||||
import type { TvShowsApiGetNextUpRequest } from '@jellyfin/sdk/lib/generated-client/api/tv-shows-api';
|
import type { ShowApiGetNextUpRequest } from '@jellyfin/sdk/lib/generated-client/api/show-api';
|
||||||
import { getTvShowsApi } from '@jellyfin/sdk/lib/utils/api/tv-shows-api';
|
import { getShowApi } from '@jellyfin/sdk/lib/utils/api/show-api';
|
||||||
import { queryOptions, useQuery } from '@tanstack/react-query';
|
import { queryOptions, useQuery } from '@tanstack/react-query';
|
||||||
import type { AxiosRequestConfig } from 'axios';
|
import type { AxiosRequestConfig } from 'axios';
|
||||||
|
|
||||||
@@ -8,17 +8,17 @@ import { useApi } from 'hooks/useApi';
|
|||||||
|
|
||||||
const fetchNextUp = async (
|
const fetchNextUp = async (
|
||||||
api: Api,
|
api: Api,
|
||||||
params?: TvShowsApiGetNextUpRequest,
|
params?: ShowApiGetNextUpRequest,
|
||||||
options?: AxiosRequestConfig
|
options?: AxiosRequestConfig
|
||||||
) => {
|
) => {
|
||||||
const response = await getTvShowsApi(api)
|
const response = await getShowApi(api)
|
||||||
.getNextUp(params, options);
|
.getNextUp(params, options);
|
||||||
return response.data;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getNextUpQuery = (
|
export const getNextUpQuery = (
|
||||||
api?: Api,
|
api?: Api,
|
||||||
params?: TvShowsApiGetNextUpRequest
|
params?: ShowApiGetNextUpRequest
|
||||||
) => queryOptions({
|
) => queryOptions({
|
||||||
queryKey: [ 'User', params?.userId, 'NextUp', params ],
|
queryKey: [ 'User', params?.userId, 'NextUp', params ],
|
||||||
queryFn: ({ signal }) => fetchNextUp(api!, params, { signal }),
|
queryFn: ({ signal }) => fetchNextUp(api!, params, { signal }),
|
||||||
@@ -26,7 +26,7 @@ export const getNextUpQuery = (
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const useNextUp = (
|
export const useNextUp = (
|
||||||
params?: TvShowsApiGetNextUpRequest
|
params?: ShowApiGetNextUpRequest
|
||||||
) => {
|
) => {
|
||||||
const { api, user } = useApi();
|
const { api, user } = useApi();
|
||||||
return useQuery(getNextUpQuery(api, {
|
return useQuery(getNextUpQuery(api, {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { Api } from '@jellyfin/sdk/lib/api';
|
import type { Api } from '@jellyfin/sdk/lib/api';
|
||||||
import type { ItemsApiGetResumeItemsRequest } from '@jellyfin/sdk/lib/generated-client/api/items-api';
|
import type { LibraryApiGetResumeItemsRequest } from '@jellyfin/sdk/lib/generated-client/api/library-api';
|
||||||
import { getItemsApi } from '@jellyfin/sdk/lib/utils/api/items-api';
|
import { getLibraryApi } from '@jellyfin/sdk/lib/utils/api/library-api';
|
||||||
import { queryOptions, useQuery } from '@tanstack/react-query';
|
import { queryOptions, useQuery } from '@tanstack/react-query';
|
||||||
import type { AxiosRequestConfig } from 'axios';
|
import type { AxiosRequestConfig } from 'axios';
|
||||||
|
|
||||||
@@ -8,17 +8,17 @@ import { useApi } from 'hooks/useApi';
|
|||||||
|
|
||||||
const fetchResumeItems = async (
|
const fetchResumeItems = async (
|
||||||
api: Api,
|
api: Api,
|
||||||
params?: ItemsApiGetResumeItemsRequest,
|
params?: LibraryApiGetResumeItemsRequest,
|
||||||
options?: AxiosRequestConfig
|
options?: AxiosRequestConfig
|
||||||
) => {
|
) => {
|
||||||
const response = await getItemsApi(api)
|
const response = await getLibraryApi(api)
|
||||||
.getResumeItems(params, options);
|
.getResumeItems(params, options);
|
||||||
return response.data;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getResumeItemsQuery = (
|
export const getResumeItemsQuery = (
|
||||||
api?: Api,
|
api?: Api,
|
||||||
params?: ItemsApiGetResumeItemsRequest
|
params?: LibraryApiGetResumeItemsRequest
|
||||||
) => queryOptions({
|
) => queryOptions({
|
||||||
queryKey: [ 'User', params?.userId, 'ResumeItems', params ],
|
queryKey: [ 'User', params?.userId, 'ResumeItems', params ],
|
||||||
queryFn: ({ signal }) => fetchResumeItems(api!, params, { signal }),
|
queryFn: ({ signal }) => fetchResumeItems(api!, params, { signal }),
|
||||||
@@ -26,7 +26,7 @@ export const getResumeItemsQuery = (
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const useResumeItems = (
|
export const useResumeItems = (
|
||||||
params?: ItemsApiGetResumeItemsRequest
|
params?: LibraryApiGetResumeItemsRequest
|
||||||
) => {
|
) => {
|
||||||
const { api, user } = useApi();
|
const { api, user } = useApi();
|
||||||
return useQuery(getResumeItemsQuery(api, {
|
return useQuery(getResumeItemsQuery(api, {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { Api } from '@jellyfin/sdk/lib/api';
|
import type { Api } from '@jellyfin/sdk/lib/api';
|
||||||
import type { MediaSegmentDto } from '@jellyfin/sdk/lib/generated-client/models/media-segment-dto';
|
import type { MediaSegmentDto } from '@jellyfin/sdk/lib/generated-client/models/media-segment-dto';
|
||||||
import { MediaSegmentType } from '@jellyfin/sdk/lib/generated-client/models/media-segment-type';
|
import { MediaSegmentType } from '@jellyfin/sdk/lib/generated-client/models/media-segment-type';
|
||||||
import { getMediaSegmentsApi } from '@jellyfin/sdk/lib/utils/api/media-segments-api';
|
import { getMediaSegmentApi } from '@jellyfin/sdk/lib/utils/api/media-segment-api';
|
||||||
|
|
||||||
import type { PlaybackManager } from 'components/playback/playbackmanager';
|
import type { PlaybackManager } from 'components/playback/playbackmanager';
|
||||||
import { TICKS_PER_MILLISECOND, TICKS_PER_SECOND } from 'constants/time';
|
import { TICKS_PER_MILLISECOND, TICKS_PER_SECOND } from 'constants/time';
|
||||||
@@ -25,7 +25,7 @@ class MediaSegmentManager extends PlaybackSubscriber {
|
|||||||
|
|
||||||
private async fetchMediaSegments(api: Api, itemId: string, includeSegmentTypes: MediaSegmentType[]) {
|
private async fetchMediaSegments(api: Api, itemId: string, includeSegmentTypes: MediaSegmentType[]) {
|
||||||
try {
|
try {
|
||||||
const { data: mediaSegments } = await getMediaSegmentsApi(api)
|
const { data: mediaSegments } = await getMediaSegmentApi(api)
|
||||||
.getItemSegments({ itemId, includeSegmentTypes });
|
.getItemSegments({ itemId, includeSegmentTypes });
|
||||||
this.mediaSegments = mediaSegments.Items || [];
|
this.mediaSegments = mediaSegments.Items || [];
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export interface PlaybackSubscriber {
|
|||||||
onPlayerChange?(e: Event, player: PlayerPlugin, target: PlayTarget, previousPlayer: PlayerPlugin): void
|
onPlayerChange?(e: Event, player: PlayerPlugin, target: PlayTarget, previousPlayer: PlayerPlugin): void
|
||||||
onPromptSkip?(e: Event, mediaSegment: MediaSegmentDto): void
|
onPromptSkip?(e: Event, mediaSegment: MediaSegmentDto): void
|
||||||
onPlayerError?(e: Event, error: PlayerError): void
|
onPlayerError?(e: Event, error: PlayerError): void
|
||||||
onPlayerFullscreenChange?(e: Event): void
|
onPlayerFullscreenChange?(e: Event, fullscreen: boolean): void
|
||||||
onPlayerItemStarted?(e: Event, item?: BaseItemDto, mediaSource?: MediaSourceInfo): void
|
onPlayerItemStarted?(e: Event, item?: BaseItemDto, mediaSource?: MediaSourceInfo): void
|
||||||
onPlayerItemStopped?(e: Event, info: ManagedPlayerStopInfo): void
|
onPlayerItemStopped?(e: Event, info: ManagedPlayerStopInfo): void
|
||||||
onPlayerMediaStreamsChange?(e: Event): void
|
onPlayerMediaStreamsChange?(e: Event): void
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { Api } from '@jellyfin/sdk/lib/api';
|
import { Api } from '@jellyfin/sdk/lib/api';
|
||||||
import { ItemsApiGetItemsRequest } from '@jellyfin/sdk/lib/generated-client/api/items-api';
|
import { LibraryApiGetItemsRequest } from '@jellyfin/sdk/lib/generated-client/api/library-api';
|
||||||
import { getItemsApi } from '@jellyfin/sdk/lib/utils/api/items-api';
|
import { getLibraryApi } from '@jellyfin/sdk/lib/utils/api/library-api';
|
||||||
import { AxiosRequestConfig } from 'axios';
|
import { AxiosRequestConfig } from 'axios';
|
||||||
import { QUERY_OPTIONS } from '../constants/queryOptions';
|
import { QUERY_OPTIONS } from '../constants/queryOptions';
|
||||||
|
|
||||||
export const fetchItemsByType = async (
|
export const fetchItemsByType = async (
|
||||||
api: Api,
|
api: Api,
|
||||||
userId?: string,
|
userId?: string,
|
||||||
params?: ItemsApiGetItemsRequest,
|
params?: LibraryApiGetItemsRequest,
|
||||||
options?: AxiosRequestConfig
|
options?: AxiosRequestConfig
|
||||||
) => {
|
) => {
|
||||||
const response = await getItemsApi(api).getItems(
|
const response = await getLibraryApi(api).getItems(
|
||||||
{
|
{
|
||||||
...QUERY_OPTIONS,
|
...QUERY_OPTIONS,
|
||||||
userId,
|
userId,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Api } from '@jellyfin/sdk';
|
import { Api } from '@jellyfin/sdk';
|
||||||
import { ArtistsApiGetArtistsRequest } from '@jellyfin/sdk/lib/generated-client/api/artists-api';
|
import { ArtistApiGetArtistsRequest } from '@jellyfin/sdk/lib/generated-client/api/artist-api';
|
||||||
import { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type';
|
import { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type';
|
||||||
import { getArtistsApi } from '@jellyfin/sdk/lib/utils/api/artists-api';
|
import { getArtistApi } from '@jellyfin/sdk/lib/utils/api/artist-api';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { AxiosRequestConfig } from 'axios';
|
import { AxiosRequestConfig } from 'axios';
|
||||||
import { useApi } from 'hooks/useApi';
|
import { useApi } from 'hooks/useApi';
|
||||||
@@ -11,10 +11,10 @@ import { isMusic } from '../utils/search';
|
|||||||
const fetchArtists = async (
|
const fetchArtists = async (
|
||||||
api: Api,
|
api: Api,
|
||||||
userId: string,
|
userId: string,
|
||||||
params?: ArtistsApiGetArtistsRequest,
|
params?: ArtistApiGetArtistsRequest,
|
||||||
options?: AxiosRequestConfig
|
options?: AxiosRequestConfig
|
||||||
) => {
|
) => {
|
||||||
const response = await getArtistsApi(api).getArtists(
|
const response = await getArtistApi(api).getArtists(
|
||||||
{
|
{
|
||||||
...QUERY_OPTIONS,
|
...QUERY_OPTIONS,
|
||||||
userId,
|
userId,
|
||||||
|
|||||||
@@ -5,17 +5,17 @@ import { AxiosRequestConfig } from 'axios';
|
|||||||
import { useApi } from 'hooks/useApi';
|
import { useApi } from 'hooks/useApi';
|
||||||
import { QUERY_OPTIONS } from '../constants/queryOptions';
|
import { QUERY_OPTIONS } from '../constants/queryOptions';
|
||||||
import { isMovies, isTVShows } from '../utils/search';
|
import { isMovies, isTVShows } from '../utils/search';
|
||||||
import { PersonsApiGetPersonsRequest } from '@jellyfin/sdk/lib/generated-client/api/persons-api';
|
import { PersonApiGetPersonsRequest } from '@jellyfin/sdk/lib/generated-client/api/person-api';
|
||||||
import { getPersonsApi } from '@jellyfin/sdk/lib/utils/api/persons-api';
|
import { getPersonApi } from '@jellyfin/sdk/lib/utils/api/person-api';
|
||||||
import { PersonKind } from '@jellyfin/sdk/lib/generated-client/models/person-kind';
|
import { PersonKind } from '@jellyfin/sdk/lib/generated-client/models/person-kind';
|
||||||
|
|
||||||
const fetchPeople = async (
|
const fetchPeople = async (
|
||||||
api: Api,
|
api: Api,
|
||||||
userId: string,
|
userId: string,
|
||||||
params?: PersonsApiGetPersonsRequest,
|
params?: PersonApiGetPersonsRequest,
|
||||||
options?: AxiosRequestConfig
|
options?: AxiosRequestConfig
|
||||||
) => {
|
) => {
|
||||||
const response = await getPersonsApi(api).getPersons(
|
const response = await getPersonApi(api).getPersons(
|
||||||
{
|
{
|
||||||
...QUERY_OPTIONS,
|
...QUERY_OPTIONS,
|
||||||
userId,
|
userId,
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ import { useQuery } from '@tanstack/react-query';
|
|||||||
import { AxiosRequestConfig } from 'axios';
|
import { AxiosRequestConfig } from 'axios';
|
||||||
import { useApi } from 'hooks/useApi';
|
import { useApi } from 'hooks/useApi';
|
||||||
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
|
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
|
||||||
import { ItemsApiGetItemsRequest } from '@jellyfin/sdk/lib/generated-client/api/items-api';
|
import { LibraryApiGetItemsRequest } from '@jellyfin/sdk/lib/generated-client/api/library-api';
|
||||||
import { fetchItemsByType } from './fetchItemsByType';
|
import { fetchItemsByType } from './fetchItemsByType';
|
||||||
|
|
||||||
const fetchPrograms = async (
|
const fetchPrograms = async (
|
||||||
api: Api,
|
api: Api,
|
||||||
userId: string,
|
userId: string,
|
||||||
params?: ItemsApiGetItemsRequest,
|
params?: LibraryApiGetItemsRequest,
|
||||||
options?: AxiosRequestConfig
|
options?: AxiosRequestConfig
|
||||||
) => {
|
) => {
|
||||||
const response = await fetchItemsByType(
|
const response = await fetchItemsByType(
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { AxiosRequestConfig } from 'axios';
|
|||||||
import type { Api } from '@jellyfin/sdk';
|
import type { Api } from '@jellyfin/sdk';
|
||||||
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
|
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
|
||||||
import { ItemSortBy } from '@jellyfin/sdk/lib/generated-client/models/item-sort-by';
|
import { ItemSortBy } from '@jellyfin/sdk/lib/generated-client/models/item-sort-by';
|
||||||
import { getItemsApi } from '@jellyfin/sdk/lib/utils/api/items-api';
|
import { getLibraryApi } from '@jellyfin/sdk/lib/utils/api/library-api';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
import { useApi } from 'hooks/useApi';
|
import { useApi } from 'hooks/useApi';
|
||||||
@@ -13,7 +13,7 @@ const fetchGetItems = async (
|
|||||||
parentId?: string,
|
parentId?: string,
|
||||||
options?: AxiosRequestConfig
|
options?: AxiosRequestConfig
|
||||||
) => {
|
) => {
|
||||||
const response = await getItemsApi(api).getItems(
|
const response = await getLibraryApi(api).getItems(
|
||||||
{
|
{
|
||||||
userId,
|
userId,
|
||||||
sortBy: [ItemSortBy.IsFavoriteOrLiked, ItemSortBy.Random],
|
sortBy: [ItemSortBy.IsFavoriteOrLiked, ItemSortBy.Random],
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Api } from '@jellyfin/sdk';
|
import { Api } from '@jellyfin/sdk';
|
||||||
import { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type';
|
import { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type';
|
||||||
import { StudiosApiGetStudiosRequest } from '@jellyfin/sdk/lib/generated-client/api/studios-api';
|
import { StudioApiGetStudiosRequest } from '@jellyfin/sdk/lib/generated-client/api/studio-api';
|
||||||
import { getStudiosApi } from '@jellyfin/sdk/lib/utils/api/studios-api';
|
import { getStudioApi } from '@jellyfin/sdk/lib/utils/api/studio-api';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { AxiosRequestConfig } from 'axios';
|
import { AxiosRequestConfig } from 'axios';
|
||||||
import { useApi } from 'hooks/useApi';
|
import { useApi } from 'hooks/useApi';
|
||||||
@@ -11,10 +11,10 @@ import { isMovies, isTVShows } from '../utils/search';
|
|||||||
const fetchStudios = async (
|
const fetchStudios = async (
|
||||||
api: Api,
|
api: Api,
|
||||||
userId: string,
|
userId: string,
|
||||||
params?: StudiosApiGetStudiosRequest,
|
params?: StudioApiGetStudiosRequest,
|
||||||
options?: AxiosRequestConfig
|
options?: AxiosRequestConfig
|
||||||
) => {
|
) => {
|
||||||
const response = await getStudiosApi(api).getStudios(
|
const response = await getStudioApi(api).getStudios(
|
||||||
{
|
{
|
||||||
...QUERY_OPTIONS,
|
...QUERY_OPTIONS,
|
||||||
userId,
|
userId,
|
||||||
|
|||||||
@@ -4,18 +4,18 @@ import { useQuery } from '@tanstack/react-query';
|
|||||||
import { AxiosRequestConfig } from 'axios';
|
import { AxiosRequestConfig } from 'axios';
|
||||||
import { useApi } from 'hooks/useApi';
|
import { useApi } from 'hooks/useApi';
|
||||||
import { QUERY_OPTIONS } from '../constants/queryOptions';
|
import { QUERY_OPTIONS } from '../constants/queryOptions';
|
||||||
import { getItemsApi } from '@jellyfin/sdk/lib/utils/api/items-api';
|
import { getLibraryApi } from '@jellyfin/sdk/lib/utils/api/library-api';
|
||||||
import { MediaType } from '@jellyfin/sdk/lib/generated-client/models/media-type';
|
import { MediaType } from '@jellyfin/sdk/lib/generated-client/models/media-type';
|
||||||
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
|
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
|
||||||
import { ItemsApiGetItemsRequest } from '@jellyfin/sdk/lib/generated-client/api/items-api';
|
import { LibraryApiGetItemsRequest } from '@jellyfin/sdk/lib/generated-client/api/library-api';
|
||||||
|
|
||||||
const fetchVideos = async (
|
const fetchVideos = async (
|
||||||
api: Api,
|
api: Api,
|
||||||
userId: string,
|
userId: string,
|
||||||
params?: ItemsApiGetItemsRequest,
|
params?: LibraryApiGetItemsRequest,
|
||||||
options?: AxiosRequestConfig
|
options?: AxiosRequestConfig
|
||||||
) => {
|
) => {
|
||||||
const response = await getItemsApi(api).getItems(
|
const response = await getLibraryApi(api).getItems(
|
||||||
{
|
{
|
||||||
...QUERY_OPTIONS,
|
...QUERY_OPTIONS,
|
||||||
userId,
|
userId,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { AsyncRoute } from '../../../../components/router/AsyncRoute';
|
import { AsyncRoute } from 'components/router/AsyncRoute';
|
||||||
|
|
||||||
export const ASYNC_PUBLIC_ROUTES: AsyncRoute[] = [
|
export const ASYNC_PUBLIC_ROUTES: AsyncRoute[] = [
|
||||||
{ path: 'forgotpassword', page: 'session/forgotPassword' }
|
{ path: 'forgotpassword', page: 'session/forgotPassword' }
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { getQuickConnectApi } from '@jellyfin/sdk/lib/utils/api/quick-connect-api';
|
import { getAuthenticationApi } from '@jellyfin/sdk/lib/utils/api/authentication-api';
|
||||||
import React, { FC, FormEvent, useCallback, useState } from 'react';
|
import React, { FC, FormEvent, useCallback, useState } from 'react';
|
||||||
import { Link, useSearchParams } from 'react-router-dom';
|
import { Link, useSearchParams } from 'react-router-dom';
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ const QuickConnectPage: FC = () => {
|
|||||||
const normalizedCode = code.replace(/\s/g, '');
|
const normalizedCode = code.replace(/\s/g, '');
|
||||||
console.log('[QuickConnect] authorizing code %s as user %s', normalizedCode, userId);
|
console.log('[QuickConnect] authorizing code %s as user %s', normalizedCode, userId);
|
||||||
|
|
||||||
getQuickConnectApi(api)
|
getAuthenticationApi(api)
|
||||||
.authorizeQuickConnect({
|
.authorizeQuickConnect({
|
||||||
code: normalizedCode,
|
code: normalizedCode,
|
||||||
userId
|
userId
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export const Component = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<OffsetAppBar dense elevation={4}>
|
<OffsetAppBar dense>
|
||||||
<AppToolbar
|
<AppToolbar
|
||||||
isDrawerAvailable={!isMediumScreen && isDrawerAvailable}
|
isDrawerAvailable={!isMediumScreen && isDrawerAvailable}
|
||||||
isDrawerOpen={isDrawerOpen}
|
isDrawerOpen={isDrawerOpen}
|
||||||
|
|||||||
@@ -24,6 +24,12 @@ $mui-bp-xl: 1536px;
|
|||||||
}
|
}
|
||||||
|
|
||||||
.layout-mobile {
|
.layout-mobile {
|
||||||
|
.itemDetailPage {
|
||||||
|
// Fix horizontal panning on mobile item details page
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-x: clip;
|
||||||
|
}
|
||||||
|
|
||||||
.itemBackdrop {
|
.itemBackdrop {
|
||||||
// Fix backdrop position on mobile item details page
|
// Fix backdrop position on mobile item details page
|
||||||
margin-top: -48px !important;
|
margin-top: -48px !important;
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ const AppToolbar: FC<AppToolbarProps> = ({
|
|||||||
onDrawerButtonClick={onDrawerButtonClick}
|
onDrawerButtonClick={onDrawerButtonClick}
|
||||||
isBackButtonAvailable={isBackButtonAvailable}
|
isBackButtonAvailable={isBackButtonAvailable}
|
||||||
isUserMenuAvailable={!isPublicPath}
|
isUserMenuAvailable={!isPublicPath}
|
||||||
|
className='padded-left padded-right'
|
||||||
>
|
>
|
||||||
{!isDrawerAvailable && (
|
{!isDrawerAvailable && (
|
||||||
<Stack
|
<Stack
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { getDisplayVersion } from '@jellyfin/sdk/lib/utils/versioning';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import ListItemIcon from '@mui/material/ListItemIcon';
|
import ListItemIcon from '@mui/material/ListItemIcon';
|
||||||
import ListItemText from '@mui/material/ListItemText';
|
import ListItemText from '@mui/material/ListItemText';
|
||||||
@@ -5,7 +6,6 @@ import React from 'react';
|
|||||||
|
|
||||||
import { useSystemInfo } from 'hooks/useSystemInfo';
|
import { useSystemInfo } from 'hooks/useSystemInfo';
|
||||||
import ListItemLink from 'components/ListItemLink';
|
import ListItemLink from 'components/ListItemLink';
|
||||||
import { getDisplayVersion } from 'utils/versions';
|
|
||||||
|
|
||||||
import appIcon from '@jellyfin/ux-web/icon-transparent.png';
|
import appIcon from '@jellyfin/ux-web/icon-transparent.png';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { AxiosRequestConfig } from 'axios';
|
import type { AxiosRequestConfig } from 'axios';
|
||||||
import { getUserLibraryApi } from '@jellyfin/sdk/lib/utils/api/user-library-api';
|
import { getLibraryApi } from '@jellyfin/sdk/lib/utils/api/library-api';
|
||||||
import { getLiveTvApi } from '@jellyfin/sdk/lib/utils/api/live-tv-api';
|
import { getLiveTvApi } from '@jellyfin/sdk/lib/utils/api/live-tv-api';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { type JellyfinApiContext, useApi } from 'hooks/useApi';
|
import { type JellyfinApiContext, useApi } from 'hooks/useApi';
|
||||||
@@ -33,7 +33,7 @@ const getItemByType = async (
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
default: {
|
default: {
|
||||||
response = await getUserLibraryApi(api).getItem(
|
response = await getLibraryApi(api).getItem(
|
||||||
{ userId: user.Id, itemId },
|
{ userId: user.Id, itemId },
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ const ItemsView: FC = () => {
|
|||||||
listOptions.showArtist = true;
|
listOptions.showArtist = true;
|
||||||
listOptions.addToListButton = true;
|
listOptions.addToListButton = true;
|
||||||
} else if (viewType === LibraryTab.Albums) {
|
} else if (viewType === LibraryTab.Albums) {
|
||||||
listOptions.sortBy = libraryViewSettings.SortBy;
|
listOptions.sortBy = libraryViewSettings.SortBy[0];
|
||||||
listOptions.addToListButton = true;
|
listOptions.addToListButton = true;
|
||||||
} else if (viewType === LibraryTab.Episodes) {
|
} else if (viewType === LibraryTab.Episodes) {
|
||||||
listOptions.showParentTitle = true;
|
listOptions.showParentTitle = true;
|
||||||
@@ -171,7 +171,7 @@ const ItemsView: FC = () => {
|
|||||||
noItemsMessage
|
noItemsMessage
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const hasSortName = libraryViewSettings.SortBy !== ItemSortBy.Random;
|
const hasSortName = !libraryViewSettings.SortBy.includes(ItemSortBy.Random);
|
||||||
|
|
||||||
const itemsContainerClass = classNames(
|
const itemsContainerClass = classNames(
|
||||||
'padded-left padded-right',
|
'padded-left padded-right',
|
||||||
|
|||||||
@@ -104,61 +104,21 @@ const LibraryToolbar: FC = () => {
|
|||||||
>
|
>
|
||||||
<LibraryViewMenu />
|
<LibraryViewMenu />
|
||||||
|
|
||||||
<Box
|
{!MENU_ONLY_VIEWS.includes(viewType) && (
|
||||||
sx={{
|
<Box
|
||||||
display: 'flex',
|
sx={{
|
||||||
flexGrow: {
|
display: 'flex',
|
||||||
xs: 1,
|
flexGrow: {
|
||||||
sm: 0
|
xs: 1,
|
||||||
},
|
sm: 0
|
||||||
justifyContent: 'flex-end',
|
},
|
||||||
marginLeft: 1
|
justifyContent: 'flex-end',
|
||||||
}}
|
marginLeft: 1
|
||||||
>
|
}}
|
||||||
<ButtonGroup
|
|
||||||
variant='contained'
|
|
||||||
>
|
>
|
||||||
{isBtnPlayAllEnabled && (
|
<Chip label={itemCountDisplay} />
|
||||||
<PlayAllButton
|
</Box>
|
||||||
item={item}
|
)}
|
||||||
items={items}
|
|
||||||
viewType={viewType}
|
|
||||||
collectionType={collectionType}
|
|
||||||
hasFilters={hasFilters}
|
|
||||||
isTextVisible={isSmallScreen}
|
|
||||||
libraryViewSettings={libraryViewSettings}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isBtnShuffleEnabled && (
|
|
||||||
<ShuffleButton
|
|
||||||
item={item}
|
|
||||||
items={items}
|
|
||||||
viewType={viewType}
|
|
||||||
collectionType={collectionType}
|
|
||||||
hasFilters={hasFilters}
|
|
||||||
isTextVisible={isSmallScreen && !isBtnPlayAllEnabled}
|
|
||||||
libraryViewSettings={libraryViewSettings}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isBtnQueueEnabled && item && playbackManager.canQueue(item) && (
|
|
||||||
<QueueButton
|
|
||||||
item={item}
|
|
||||||
items={items}
|
|
||||||
hasFilters={hasFilters}
|
|
||||||
isTextVisible={isSmallScreen && !isBtnPlayAllEnabled && !isBtnShuffleEnabled}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</ButtonGroup>
|
|
||||||
|
|
||||||
{isBtnNewCollectionEnabled && canCreateCollections && (
|
|
||||||
<NewCollectionButton isTextVisible={isSmallScreen} queryKey={allItemsQueryKey} />
|
|
||||||
)}
|
|
||||||
{isBtnNewPlaylistEnabled && (
|
|
||||||
<NewPlaylistButton isTextVisible={isSmallScreen} queryKey={allItemsQueryKey} />
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{!MENU_ONLY_VIEWS.includes(viewType) && (
|
{!MENU_ONLY_VIEWS.includes(viewType) && (
|
||||||
<Stack
|
<Stack
|
||||||
@@ -174,7 +134,10 @@ const LibraryToolbar: FC = () => {
|
|||||||
sm: 'auto'
|
sm: 'auto'
|
||||||
},
|
},
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
marginTop: 0.5,
|
marginTop: {
|
||||||
|
xs: 1,
|
||||||
|
sm: 0.5
|
||||||
|
},
|
||||||
marginBottom: 0.5
|
marginBottom: 0.5
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -188,7 +151,49 @@ const LibraryToolbar: FC = () => {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Chip label={itemCountDisplay} />
|
<ButtonGroup
|
||||||
|
variant='contained'
|
||||||
|
>
|
||||||
|
{isBtnPlayAllEnabled && (
|
||||||
|
<PlayAllButton
|
||||||
|
item={item}
|
||||||
|
items={items}
|
||||||
|
viewType={viewType}
|
||||||
|
collectionType={collectionType}
|
||||||
|
hasFilters={hasFilters}
|
||||||
|
isTextVisible={isSmallScreen}
|
||||||
|
libraryViewSettings={libraryViewSettings}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isBtnShuffleEnabled && (
|
||||||
|
<ShuffleButton
|
||||||
|
item={item}
|
||||||
|
items={items}
|
||||||
|
viewType={viewType}
|
||||||
|
collectionType={collectionType}
|
||||||
|
hasFilters={hasFilters}
|
||||||
|
isTextVisible={isSmallScreen && !isBtnPlayAllEnabled}
|
||||||
|
libraryViewSettings={libraryViewSettings}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isBtnQueueEnabled && item && playbackManager.canQueue(item) && (
|
||||||
|
<QueueButton
|
||||||
|
item={item}
|
||||||
|
items={items}
|
||||||
|
hasFilters={hasFilters}
|
||||||
|
isTextVisible={isSmallScreen && !isBtnPlayAllEnabled && !isBtnShuffleEnabled}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</ButtonGroup>
|
||||||
|
|
||||||
|
{isBtnNewCollectionEnabled && canCreateCollections && (
|
||||||
|
<NewCollectionButton isTextVisible={isSmallScreen} queryKey={allItemsQueryKey} />
|
||||||
|
)}
|
||||||
|
{isBtnNewPlaylistEnabled && (
|
||||||
|
<NewPlaylistButton isTextVisible={isSmallScreen} queryKey={allItemsQueryKey} />
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<ButtonGroup
|
<ButtonGroup
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ const PageTabContent: FC<PageTabContentProps> = ({ parentId, currentTab }) => {
|
|||||||
if (currentTab.viewType === LibraryTab.Programs || currentTab.viewType === LibraryTab.Recordings || currentTab.viewType === LibraryTab.Schedule) {
|
if (currentTab.viewType === LibraryTab.Programs || currentTab.viewType === LibraryTab.Recordings || currentTab.viewType === LibraryTab.Schedule) {
|
||||||
return (
|
return (
|
||||||
<ProgramsSectionView
|
<ProgramsSectionView
|
||||||
parentId={parentId}
|
|
||||||
sectionType={
|
sectionType={
|
||||||
currentTab.sectionsView?.programSections ?? []
|
currentTab.sectionsView?.programSections ?? []
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ const PlayAllButton: FC<PlayAllButtonProps> = ({
|
|||||||
items: [item],
|
items: [item],
|
||||||
autoplay: true,
|
autoplay: true,
|
||||||
queryOptions: {
|
queryOptions: {
|
||||||
SortBy: [libraryViewSettings.SortBy],
|
SortBy: libraryViewSettings.SortBy,
|
||||||
SortOrder: [libraryViewSettings.SortOrder]
|
SortOrder: [libraryViewSettings.SortOrder]
|
||||||
}
|
}
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
@@ -56,7 +56,7 @@ const PlayAllButton: FC<PlayAllButtonProps> = ({
|
|||||||
queryOptions: {
|
queryOptions: {
|
||||||
ParentId: item?.Id ?? undefined,
|
ParentId: item?.Id ?? undefined,
|
||||||
...getFiltersQuery(viewType, libraryViewSettings),
|
...getFiltersQuery(viewType, libraryViewSettings),
|
||||||
SortBy: [libraryViewSettings.SortBy],
|
SortBy: libraryViewSettings.SortBy,
|
||||||
SortOrder: [libraryViewSettings.SortOrder]
|
SortOrder: [libraryViewSettings.SortOrder]
|
||||||
}
|
}
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
|
|||||||
@@ -9,22 +9,19 @@ import { ItemAction } from 'constants/itemAction';
|
|||||||
import { useApi } from 'hooks/useApi';
|
import { useApi } from 'hooks/useApi';
|
||||||
import { useGetProgramsSectionsWithItems, useGetTimers } from 'hooks/useFetchItems';
|
import { useGetProgramsSectionsWithItems, useGetTimers } from 'hooks/useFetchItems';
|
||||||
import globalize from 'lib/globalize';
|
import globalize from 'lib/globalize';
|
||||||
import type { ParentId } from 'types/library';
|
|
||||||
import type { Section, SectionType } from 'types/sections';
|
import type { Section, SectionType } from 'types/sections';
|
||||||
|
|
||||||
interface ProgramsSectionViewProps {
|
interface ProgramsSectionViewProps {
|
||||||
parentId: ParentId;
|
|
||||||
sectionType: SectionType[];
|
sectionType: SectionType[];
|
||||||
isUpcomingRecordingsEnabled: boolean | undefined
|
isUpcomingRecordingsEnabled: boolean | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProgramsSectionView: FC<ProgramsSectionViewProps> = ({
|
const ProgramsSectionView: FC<ProgramsSectionViewProps> = ({
|
||||||
parentId,
|
|
||||||
sectionType,
|
sectionType,
|
||||||
isUpcomingRecordingsEnabled = false
|
isUpcomingRecordingsEnabled = false
|
||||||
}) => {
|
}) => {
|
||||||
const { __legacyApiClient__ } = useApi();
|
const { __legacyApiClient__ } = useApi();
|
||||||
const { isLoading, data: sectionsWithItems, refetch } = useGetProgramsSectionsWithItems(parentId, sectionType);
|
const { isLoading, data: sectionsWithItems, refetch } = useGetProgramsSectionsWithItems(sectionType);
|
||||||
const {
|
const {
|
||||||
isLoading: isUpcomingRecordingsLoading,
|
isLoading: isUpcomingRecordingsLoading,
|
||||||
data: upcomingRecordings
|
data: upcomingRecordings
|
||||||
|
|||||||
@@ -14,52 +14,53 @@ import Popover from '@mui/material/Popover';
|
|||||||
import globalize from 'lib/globalize';
|
import globalize from 'lib/globalize';
|
||||||
import { LibraryViewSettings } from 'types/library';
|
import { LibraryViewSettings } from 'types/library';
|
||||||
import { LibraryTab } from 'types/libraryTab';
|
import { LibraryTab } from 'types/libraryTab';
|
||||||
|
import isEqual from 'lodash-es/isEqual';
|
||||||
|
|
||||||
type SortOption = {
|
type SortOption = {
|
||||||
label: string;
|
label: string;
|
||||||
value: ItemSortBy;
|
value: ItemSortBy[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type SortOptionsMapping = Record<string, SortOption[]>;
|
type SortOptionsMapping = Record<string, SortOption[]>;
|
||||||
|
|
||||||
const collectionMovieOptions: SortOption[] = [
|
const collectionMovieOptions: SortOption[] = [
|
||||||
{ label: 'Name', value: ItemSortBy.SortName },
|
{ label: 'Name', value: [ItemSortBy.SortName] },
|
||||||
{ label: 'OptionRandom', value: ItemSortBy.Random },
|
{ label: 'OptionRandom', value: [ItemSortBy.Random] },
|
||||||
{ label: 'OptionCommunityRating', value: ItemSortBy.CommunityRating },
|
{ label: 'OptionCommunityRating', value: [ItemSortBy.CommunityRating, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionDateAdded', value: ItemSortBy.DateCreated },
|
{ label: 'OptionDateAdded', value: [ItemSortBy.DateCreated, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionParentalRating', value: ItemSortBy.OfficialRating },
|
{ label: 'OptionParentalRating', value: [ItemSortBy.OfficialRating, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionReleaseDate', value: ItemSortBy.PremiereDate }
|
{ label: 'OptionReleaseDate', value: [ItemSortBy.ProductionYear, ItemSortBy.PremiereDate, ItemSortBy.SortName] }
|
||||||
];
|
];
|
||||||
|
|
||||||
const movieOrFavoriteOptions = [
|
const movieOrFavoriteOptions = [
|
||||||
{ label: 'Name', value: ItemSortBy.SortName },
|
{ label: 'Name', value: [ItemSortBy.SortName] },
|
||||||
{ label: 'OptionRandom', value: ItemSortBy.Random },
|
{ label: 'OptionRandom', value: [ItemSortBy.Random] },
|
||||||
{ label: 'OptionCommunityRating', value: ItemSortBy.CommunityRating },
|
{ label: 'OptionCommunityRating', value: [ItemSortBy.CommunityRating, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionCriticRating', value: ItemSortBy.CriticRating },
|
{ label: 'OptionCriticRating', value: [ItemSortBy.CriticRating, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionDateAdded', value: ItemSortBy.DateCreated },
|
{ label: 'OptionDateAdded', value: [ItemSortBy.DateCreated, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionDatePlayed', value: ItemSortBy.DatePlayed },
|
{ label: 'OptionDatePlayed', value: [ItemSortBy.DatePlayed, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionParentalRating', value: ItemSortBy.OfficialRating },
|
{ label: 'OptionParentalRating', value: [ItemSortBy.OfficialRating, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionPlayCount', value: ItemSortBy.PlayCount },
|
{ label: 'OptionPlayCount', value: [ItemSortBy.PlayCount, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionReleaseDate', value: ItemSortBy.PremiereDate },
|
{ label: 'OptionReleaseDate', value: [ItemSortBy.ProductionYear, ItemSortBy.PremiereDate, ItemSortBy.SortName] },
|
||||||
{ label: 'Runtime', value: ItemSortBy.Runtime }
|
{ label: 'Runtime', value: [ItemSortBy.Runtime, ItemSortBy.SortName] }
|
||||||
];
|
];
|
||||||
|
|
||||||
const photosOrPhotoAlbumsOptions = [
|
const photosOrPhotoAlbumsOptions = [
|
||||||
{ label: 'Name', value: ItemSortBy.SortName },
|
{ label: 'Name', value: [ItemSortBy.SortName] },
|
||||||
{ label: 'OptionRandom', value: ItemSortBy.Random },
|
{ label: 'OptionRandom', value: [ItemSortBy.Random] },
|
||||||
{ label: 'OptionDateAdded', value: ItemSortBy.DateCreated }
|
{ label: 'OptionDateAdded', value: [ItemSortBy.DateCreated, ItemSortBy.SortName] }
|
||||||
];
|
];
|
||||||
|
|
||||||
const videoOrMusicVideoOptions = [
|
const videoOrMusicVideoOptions = [
|
||||||
{ label: 'Name', value: ItemSortBy.SortName },
|
{ label: 'Name', value: [ItemSortBy.SortName] },
|
||||||
{ label: 'OptionRandom', value: ItemSortBy.Random },
|
{ label: 'OptionRandom', value: [ItemSortBy.Random] },
|
||||||
{ label: 'OptionCommunityRating', value: ItemSortBy.CommunityRating },
|
{ label: 'OptionCommunityRating', value: [ItemSortBy.CommunityRating, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionDateAdded', value: ItemSortBy.DateCreated },
|
{ label: 'OptionDateAdded', value: [ItemSortBy.DateCreated, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionDatePlayed', value: ItemSortBy.DatePlayed },
|
{ label: 'OptionDatePlayed', value: [ItemSortBy.DatePlayed, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionParentalRating', value: ItemSortBy.OfficialRating },
|
{ label: 'OptionParentalRating', value: [ItemSortBy.OfficialRating, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionPlayCount', value: ItemSortBy.PlayCount },
|
{ label: 'OptionPlayCount', value: [ItemSortBy.PlayCount, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionReleaseDate', value: ItemSortBy.PremiereDate },
|
{ label: 'OptionReleaseDate', value: [ItemSortBy.ProductionYear, ItemSortBy.PremiereDate, ItemSortBy.SortName] },
|
||||||
{ label: 'Runtime', value: ItemSortBy.Runtime }
|
{ label: 'Runtime', value: [ItemSortBy.Runtime, ItemSortBy.SortName] }
|
||||||
];
|
];
|
||||||
|
|
||||||
const sortOptionsMapping: SortOptionsMapping = {
|
const sortOptionsMapping: SortOptionsMapping = {
|
||||||
@@ -67,91 +68,91 @@ const sortOptionsMapping: SortOptionsMapping = {
|
|||||||
[LibraryTab.Collections]: collectionMovieOptions,
|
[LibraryTab.Collections]: collectionMovieOptions,
|
||||||
[LibraryTab.Favorites]: movieOrFavoriteOptions,
|
[LibraryTab.Favorites]: movieOrFavoriteOptions,
|
||||||
[LibraryTab.Series]: [
|
[LibraryTab.Series]: [
|
||||||
{ label: 'Name', value: ItemSortBy.SortName },
|
{ label: 'Name', value: [ItemSortBy.SortName] },
|
||||||
{ label: 'OptionRandom', value: ItemSortBy.Random },
|
{ label: 'OptionRandom', value: [ItemSortBy.Random] },
|
||||||
{ label: 'OptionCommunityRating', value: ItemSortBy.CommunityRating },
|
{ label: 'OptionCommunityRating', value: [ItemSortBy.CommunityRating, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionDateShowAdded', value: ItemSortBy.DateCreated },
|
{ label: 'OptionDateShowAdded', value: [ItemSortBy.DateCreated, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionDateEpisodeAdded', value: ItemSortBy.DateLastContentAdded },
|
{ label: 'OptionDateEpisodeAdded', value: [ItemSortBy.DateLastContentAdded, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionDatePlayed', value: ItemSortBy.SeriesDatePlayed },
|
{ label: 'OptionDatePlayed', value: [ItemSortBy.SeriesDatePlayed, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionParentalRating', value: ItemSortBy.OfficialRating },
|
{ label: 'OptionParentalRating', value: [ItemSortBy.OfficialRating, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionReleaseDate', value: ItemSortBy.PremiereDate }
|
{ label: 'OptionReleaseDate', value: [ItemSortBy.ProductionYear, ItemSortBy.PremiereDate, ItemSortBy.SortName] }
|
||||||
],
|
],
|
||||||
[LibraryTab.Episodes]: [
|
[LibraryTab.Episodes]: [
|
||||||
{ label: 'Name', value: ItemSortBy.SeriesSortName },
|
{ label: 'Name', value: [ItemSortBy.SeriesSortName] },
|
||||||
{ label: 'OptionRandom', value: ItemSortBy.Random },
|
{ label: 'OptionRandom', value: [ItemSortBy.Random] },
|
||||||
{ label: 'OptionCommunityRating', value: ItemSortBy.CommunityRating },
|
{ label: 'OptionCommunityRating', value: [ItemSortBy.CommunityRating, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionDateAdded', value: ItemSortBy.DateCreated },
|
{ label: 'OptionDateAdded', value: [ItemSortBy.DateCreated, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionReleaseDate', value: ItemSortBy.PremiereDate },
|
{ label: 'OptionReleaseDate', value: [ItemSortBy.ProductionYear, ItemSortBy.PremiereDate, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionDatePlayed', value: ItemSortBy.DatePlayed },
|
{ label: 'OptionDatePlayed', value: [ItemSortBy.DatePlayed, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionParentalRating', value: ItemSortBy.OfficialRating },
|
{ label: 'OptionParentalRating', value: [ItemSortBy.OfficialRating, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionPlayCount', value: ItemSortBy.PlayCount },
|
{ label: 'OptionPlayCount', value: [ItemSortBy.PlayCount, ItemSortBy.SortName] },
|
||||||
{ label: 'Runtime', value: ItemSortBy.Runtime }
|
{ label: 'Runtime', value: [ItemSortBy.Runtime, ItemSortBy.SortName] }
|
||||||
],
|
],
|
||||||
[LibraryTab.Albums]: [
|
[LibraryTab.Albums]: [
|
||||||
{ label: 'Name', value: ItemSortBy.SortName },
|
{ label: 'Name', value: [ItemSortBy.SortName] },
|
||||||
{ label: 'OptionRandom', value: ItemSortBy.Random },
|
{ label: 'OptionRandom', value: [ItemSortBy.Random] },
|
||||||
{ label: 'AlbumArtist', value: ItemSortBy.AlbumArtist },
|
{ label: 'AlbumArtist', value: [ItemSortBy.AlbumArtist, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionCommunityRating', value: ItemSortBy.CommunityRating },
|
{ label: 'OptionCommunityRating', value: [ItemSortBy.CommunityRating, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionCriticRating', value: ItemSortBy.CriticRating },
|
{ label: 'OptionCriticRating', value: [ItemSortBy.CriticRating, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionReleaseDate', value: ItemSortBy.ProductionYear },
|
{ label: 'OptionReleaseDate', value: [ItemSortBy.ProductionYear, ItemSortBy.PremiereDate, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionDateAdded', value: ItemSortBy.DateCreated }
|
{ label: 'OptionDateAdded', value: [ItemSortBy.DateCreated, ItemSortBy.SortName] }
|
||||||
],
|
],
|
||||||
[LibraryTab.Books]: [
|
[LibraryTab.Books]: [
|
||||||
{ label: 'Name', value: ItemSortBy.SortName },
|
{ label: 'Name', value: [ItemSortBy.SortName] },
|
||||||
{ label: 'OptionRandom', value: ItemSortBy.Random },
|
{ label: 'OptionRandom', value: [ItemSortBy.Random] },
|
||||||
{ label: 'OptionReleaseDate', value: ItemSortBy.ProductionYear },
|
{ label: 'OptionReleaseDate', value: [ItemSortBy.ProductionYear, ItemSortBy.PremiereDate, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionDateAdded', value: ItemSortBy.DateCreated },
|
{ label: 'OptionDateAdded', value: [ItemSortBy.DateCreated, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionDatePlayed', value: ItemSortBy.DatePlayed },
|
{ label: 'OptionDatePlayed', value: [ItemSortBy.DatePlayed, ItemSortBy.SortName] },
|
||||||
{ label: 'IndexNumber', value: ItemSortBy.IndexNumber }
|
{ label: 'IndexNumber', value: [ItemSortBy.IndexNumber, ItemSortBy.SortName] }
|
||||||
],
|
],
|
||||||
[LibraryTab.Songs]: [
|
[LibraryTab.Songs]: [
|
||||||
{ label: 'Name', value: ItemSortBy.SortName },
|
{ label: 'OptionTrackName', value: [ItemSortBy.Name] },
|
||||||
{ label: 'OptionRandom', value: ItemSortBy.Random },
|
{ label: 'OptionRandom', value: [ItemSortBy.Random] },
|
||||||
{ label: 'Album', value: ItemSortBy.Album },
|
{ label: 'Album', value: [ItemSortBy.Album, ItemSortBy.AlbumArtist, ItemSortBy.SortName] },
|
||||||
{ label: 'AlbumArtist', value: ItemSortBy.AlbumArtist },
|
{ label: 'AlbumArtist', value: [ItemSortBy.AlbumArtist, ItemSortBy.Album, ItemSortBy.SortName] },
|
||||||
{ label: 'Artist', value: ItemSortBy.Artist },
|
{ label: 'Artist', value: [ItemSortBy.Artist, ItemSortBy.Album, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionDateAdded', value: ItemSortBy.DateCreated },
|
{ label: 'OptionDateAdded', value: [ItemSortBy.DateCreated, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionDatePlayed', value: ItemSortBy.DatePlayed },
|
{ label: 'OptionDatePlayed', value: [ItemSortBy.DatePlayed, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionPlayCount', value: ItemSortBy.PlayCount },
|
{ label: 'OptionPlayCount', value: [ItemSortBy.PlayCount, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionReleaseDate', value: ItemSortBy.PremiereDate },
|
{ label: 'OptionReleaseDate', value: [ItemSortBy.ProductionYear, ItemSortBy.PremiereDate, ItemSortBy.AlbumArtist, ItemSortBy.Album, ItemSortBy.SortName] },
|
||||||
{ label: 'Runtime', value: ItemSortBy.Runtime }
|
{ label: 'Runtime', value: [ItemSortBy.Runtime, ItemSortBy.AlbumArtist, ItemSortBy.Album, ItemSortBy.SortName] }
|
||||||
],
|
],
|
||||||
[LibraryTab.Playlists]: [
|
[LibraryTab.Playlists]: [
|
||||||
{ label: 'Name', value: ItemSortBy.SortName },
|
{ label: 'Name', value: [ItemSortBy.SortName] },
|
||||||
{ label: 'OptionRandom', value: ItemSortBy.Random },
|
{ label: 'OptionRandom', value: [ItemSortBy.Random] },
|
||||||
{ label: 'OptionDateAdded', value: ItemSortBy.DateCreated },
|
{ label: 'OptionDateAdded', value: [ItemSortBy.DateCreated, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionDatePlaylistUpdated', value: ItemSortBy.DateLastContentAdded },
|
{ label: 'OptionDatePlaylistUpdated', value: [ItemSortBy.DateLastContentAdded, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionReleaseDate', value: ItemSortBy.PremiereDate },
|
{ label: 'OptionReleaseDate', value: [ItemSortBy.ProductionYear, ItemSortBy.PremiereDate, ItemSortBy.SortName] },
|
||||||
{ label: 'Runtime', value: ItemSortBy.Runtime }
|
{ label: 'Runtime', value: [ItemSortBy.Runtime, ItemSortBy.SortName] }
|
||||||
],
|
],
|
||||||
[LibraryTab.PhotoAlbums]: photosOrPhotoAlbumsOptions,
|
[LibraryTab.PhotoAlbums]: photosOrPhotoAlbumsOptions,
|
||||||
[LibraryTab.Photos]: photosOrPhotoAlbumsOptions,
|
[LibraryTab.Photos]: photosOrPhotoAlbumsOptions,
|
||||||
[LibraryTab.Videos]:videoOrMusicVideoOptions,
|
[LibraryTab.Videos]:videoOrMusicVideoOptions,
|
||||||
[LibraryTab.MusicVideos]:videoOrMusicVideoOptions,
|
[LibraryTab.MusicVideos]:videoOrMusicVideoOptions,
|
||||||
[LibraryTab.Folders]: [
|
[LibraryTab.Folders]: [
|
||||||
{ label: 'Name', value: ItemSortBy.SortName },
|
{ label: 'Name', value: [ItemSortBy.SortName] },
|
||||||
{ label: 'OptionRandom', value: ItemSortBy.Random },
|
{ label: 'OptionRandom', value: [ItemSortBy.Random] },
|
||||||
{ label: 'OptionCommunityRating', value: ItemSortBy.CommunityRating },
|
{ label: 'OptionCommunityRating', value: [ItemSortBy.CommunityRating, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionDateAdded', value: ItemSortBy.DateCreated },
|
{ label: 'OptionDateAdded', value: [ItemSortBy.DateCreated, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionDatePlayed', value: ItemSortBy.DatePlayed },
|
{ label: 'OptionDatePlayed', value: [ItemSortBy.DatePlayed, ItemSortBy.SortName] },
|
||||||
{ label: 'Folders', value: ItemSortBy.IsFolder },
|
{ label: 'Folders', value: [ItemSortBy.IsFolder, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionParentalRating', value: ItemSortBy.OfficialRating },
|
{ label: 'OptionParentalRating', value: [ItemSortBy.OfficialRating, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionPlayCount', value: ItemSortBy.PlayCount },
|
{ label: 'OptionPlayCount', value: [ItemSortBy.PlayCount, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionReleaseDate', value: ItemSortBy.PremiereDate },
|
{ label: 'OptionReleaseDate', value: [ItemSortBy.ProductionYear, ItemSortBy.PremiereDate, ItemSortBy.SortName] },
|
||||||
{ label: 'Runtime', value: ItemSortBy.Runtime }
|
{ label: 'Runtime', value: [ItemSortBy.Runtime, ItemSortBy.SortName] }
|
||||||
],
|
],
|
||||||
[LibraryTab.Mixed]: [
|
[LibraryTab.Mixed]: [
|
||||||
{ label: 'Name', value: ItemSortBy.SortName },
|
{ label: 'Name', value: [ItemSortBy.SortName] },
|
||||||
{ label: 'OptionRandom', value: ItemSortBy.Random },
|
{ label: 'OptionRandom', value: [ItemSortBy.Random] },
|
||||||
{ label: 'OptionCommunityRating', value: ItemSortBy.CommunityRating },
|
{ label: 'OptionCommunityRating', value: [ItemSortBy.CommunityRating, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionCriticRating', value: ItemSortBy.CriticRating },
|
{ label: 'OptionCriticRating', value: [ItemSortBy.CriticRating, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionDateAdded', value: ItemSortBy.DateCreated },
|
{ label: 'OptionDateAdded', value: [ItemSortBy.DateCreated, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionDateEpisodeAdded', value: ItemSortBy.DateLastContentAdded },
|
{ label: 'OptionDateEpisodeAdded', value: [ItemSortBy.DateLastContentAdded, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionDatePlayed', value: ItemSortBy.DatePlayed },
|
{ label: 'OptionDatePlayed', value: [ItemSortBy.DatePlayed, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionParentalRating', value: ItemSortBy.OfficialRating },
|
{ label: 'OptionParentalRating', value: [ItemSortBy.OfficialRating, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionPlayCount', value: ItemSortBy.PlayCount },
|
{ label: 'OptionPlayCount', value: [ItemSortBy.PlayCount, ItemSortBy.SortName] },
|
||||||
{ label: 'OptionReleaseDate', value: ItemSortBy.PremiereDate },
|
{ label: 'OptionReleaseDate', value: [ItemSortBy.ProductionYear, ItemSortBy.PremiereDate, ItemSortBy.SortName] },
|
||||||
{ label: 'Runtime', value: ItemSortBy.Runtime }
|
{ label: 'Runtime', value: [ItemSortBy.Runtime, ItemSortBy.SortName] }
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -186,11 +187,11 @@ const SortButton: FC<SortButtonProps> = ({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onMenuItemClick = useCallback(
|
const onMenuItemClick = useCallback(
|
||||||
(sortBy: ItemSortBy) => {
|
(sortBy: ItemSortBy[]) => {
|
||||||
setLibraryViewSettings((prevState) => {
|
setLibraryViewSettings((prevState) => {
|
||||||
let sortOrder: SortOrder = SortOrder.Ascending;
|
let sortOrder: SortOrder = SortOrder.Ascending;
|
||||||
// If the user clicks the currently selected sort option, toggle the sort order
|
// If the user clicks the currently selected sort option, toggle the sort order
|
||||||
if (prevState.SortBy === sortBy) {
|
if (isEqual(prevState.SortBy, sortBy)) {
|
||||||
sortOrder = prevState.SortOrder === SortOrder.Ascending ? SortOrder.Descending : SortOrder.Ascending;
|
sortOrder = prevState.SortOrder === SortOrder.Ascending ? SortOrder.Descending : SortOrder.Ascending;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,7 +236,7 @@ const SortButton: FC<SortButtonProps> = ({
|
|||||||
{sortMenuOptions
|
{sortMenuOptions
|
||||||
.map((option) => (
|
.map((option) => (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
key={option.value}
|
key={option.value.join(',')}
|
||||||
// eslint-disable-next-line react/jsx-no-bind
|
// eslint-disable-next-line react/jsx-no-bind
|
||||||
onClick={() => onMenuItemClick(option.value)}
|
onClick={() => onMenuItemClick(option.value)}
|
||||||
>
|
>
|
||||||
@@ -243,7 +244,7 @@ const SortButton: FC<SortButtonProps> = ({
|
|||||||
{globalize.translate(option.label)}
|
{globalize.translate(option.label)}
|
||||||
</ListItemText>
|
</ListItemText>
|
||||||
<ListItemIcon sx={{ justifyContent: 'flex-end' }}>
|
<ListItemIcon sx={{ justifyContent: 'flex-end' }}>
|
||||||
{libraryViewSettings.SortBy === option.value && (
|
{isEqual(libraryViewSettings.SortBy, option.value) && (
|
||||||
libraryViewSettings.SortOrder === SortOrder.Ascending ? (
|
libraryViewSettings.SortOrder === SortOrder.Ascending ? (
|
||||||
<ArrowUpward fontSize='small' />
|
<ArrowUpward fontSize='small' />
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
|
import { ItemFields } from '@jellyfin/sdk/lib/generated-client/models/item-fields';
|
||||||
import type { RecommendationDto } from '@jellyfin/sdk/lib/generated-client/models/recommendation-dto';
|
import type { RecommendationDto } from '@jellyfin/sdk/lib/generated-client/models/recommendation-dto';
|
||||||
import { RecommendationType } from '@jellyfin/sdk/lib/generated-client/models/recommendation-type';
|
import { RecommendationType } from '@jellyfin/sdk/lib/generated-client/models/recommendation-type';
|
||||||
import React, { type FC } from 'react';
|
import React, { type FC } from 'react';
|
||||||
|
|
||||||
import { CardShape } from 'components/cardbuilder/utils/shape';
|
import { CardShape } from 'components/cardbuilder/utils/shape';
|
||||||
import { useApi } from 'hooks/useApi';
|
import { useApi } from 'hooks/useApi';
|
||||||
import {
|
import { useGetSuggestionSectionsWithItems } from 'hooks/useFetchItems';
|
||||||
useGetMovieRecommendations,
|
|
||||||
useGetSuggestionSectionsWithItems
|
|
||||||
} from 'hooks/useFetchItems';
|
|
||||||
import { appRouter } from 'components/router/appRouter';
|
import { appRouter } from 'components/router/appRouter';
|
||||||
import globalize from 'lib/globalize';
|
import globalize from 'lib/globalize';
|
||||||
import Loading from 'components/loading/LoadingComponent';
|
import Loading from 'components/loading/LoadingComponent';
|
||||||
@@ -17,6 +15,8 @@ import type { ParentId } from 'types/library';
|
|||||||
import type { Section, SectionType } from 'types/sections';
|
import type { Section, SectionType } from 'types/sections';
|
||||||
import type { ItemDto } from 'types/base/models/item-dto';
|
import type { ItemDto } from 'types/base/models/item-dto';
|
||||||
|
|
||||||
|
import { useMovieRecommendations } from '../hooks/api/useMovieRecommendations';
|
||||||
|
|
||||||
interface SuggestionsSectionViewProps {
|
interface SuggestionsSectionViewProps {
|
||||||
parentId: ParentId;
|
parentId: ParentId;
|
||||||
sectionType: SectionType[];
|
sectionType: SectionType[];
|
||||||
@@ -35,7 +35,15 @@ const SuggestionsSectionView: FC<SuggestionsSectionViewProps> = ({
|
|||||||
const {
|
const {
|
||||||
isLoading: isRecommendationsLoading,
|
isLoading: isRecommendationsLoading,
|
||||||
data: movieRecommendationsItems
|
data: movieRecommendationsItems
|
||||||
} = useGetMovieRecommendations(isMovieRecommendationEnabled, parentId);
|
} = useMovieRecommendations({
|
||||||
|
parentId: parentId || undefined,
|
||||||
|
fields: [
|
||||||
|
ItemFields.PrimaryImageAspectRatio,
|
||||||
|
ItemFields.MediaSourceCount
|
||||||
|
],
|
||||||
|
categoryLimit: 6,
|
||||||
|
itemLimit: 20
|
||||||
|
}, isMovieRecommendationEnabled);
|
||||||
|
|
||||||
if (isLoading || isRecommendationsLoading) {
|
if (isLoading || isRecommendationsLoading) {
|
||||||
return <Loading />;
|
return <Loading />;
|
||||||
|
|||||||
@@ -15,9 +15,10 @@ import Badge from '@mui/material/Badge';
|
|||||||
import { styled } from '@mui/material/styles';
|
import { styled } from '@mui/material/styles';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
|
|
||||||
import { useGetQueryFiltersLegacy, useGetStudios } from 'hooks/useFetchItems';
|
import { useGetQueryFilters, useGetQueryFiltersLegacy, useGetStudios } from 'hooks/useFetchItems';
|
||||||
import globalize from 'lib/globalize';
|
import globalize from 'lib/globalize';
|
||||||
|
|
||||||
|
import FiltersAudioLanguages from './FiltersAudioLanguages';
|
||||||
import FiltersFeatures from './FiltersFeatures';
|
import FiltersFeatures from './FiltersFeatures';
|
||||||
import FiltersGenres from './FiltersGenres';
|
import FiltersGenres from './FiltersGenres';
|
||||||
import FiltersOfficialRatings from './FiltersOfficialRatings';
|
import FiltersOfficialRatings from './FiltersOfficialRatings';
|
||||||
@@ -25,6 +26,7 @@ import FiltersEpisodesStatus from './FiltersEpisodesStatus';
|
|||||||
import FiltersSeriesStatus from './FiltersSeriesStatus';
|
import FiltersSeriesStatus from './FiltersSeriesStatus';
|
||||||
import FiltersStatus from './FiltersStatus';
|
import FiltersStatus from './FiltersStatus';
|
||||||
import FiltersStudios from './FiltersStudios';
|
import FiltersStudios from './FiltersStudios';
|
||||||
|
import FiltersSubtitleLanguages from './FiltersSubtitleLanguages';
|
||||||
import FiltersTags from './FiltersTags';
|
import FiltersTags from './FiltersTags';
|
||||||
import FiltersVideoTypes from './FiltersVideoTypes';
|
import FiltersVideoTypes from './FiltersVideoTypes';
|
||||||
import FiltersYears from './FiltersYears';
|
import FiltersYears from './FiltersYears';
|
||||||
@@ -99,7 +101,8 @@ const FilterButton: FC<FilterButtonProps> = ({
|
|||||||
const open = Boolean(anchorEl);
|
const open = Boolean(anchorEl);
|
||||||
const id = open ? 'filter-popover' : undefined;
|
const id = open ? 'filter-popover' : undefined;
|
||||||
|
|
||||||
const { data } = useGetQueryFiltersLegacy(parentId, itemType);
|
const { data: filtersLegacy } = useGetQueryFiltersLegacy(parentId, itemType);
|
||||||
|
const { data: filters } = useGetQueryFilters(parentId, itemType);
|
||||||
const { data: studios } = useGetStudios(parentId, itemType);
|
const { data: studios } = useGetStudios(parentId, itemType);
|
||||||
|
|
||||||
const handleChange =
|
const handleChange =
|
||||||
@@ -173,6 +176,13 @@ const FilterButton: FC<FilterButtonProps> = ({
|
|||||||
return viewType === LibraryTab.Episodes;
|
return viewType === LibraryTab.Episodes;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isFiltersLanguagesEnabled = () => {
|
||||||
|
return viewType === LibraryTab.Movies
|
||||||
|
|| viewType === LibraryTab.Series
|
||||||
|
|| viewType === LibraryTab.Episodes
|
||||||
|
|| viewType === LibraryTab.Mixed;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
@@ -324,7 +334,7 @@ const FilterButton: FC<FilterButtonProps> = ({
|
|||||||
|
|
||||||
{isFiltersLegacyEnabled() && (
|
{isFiltersLegacyEnabled() && (
|
||||||
<>
|
<>
|
||||||
{data?.Genres && data?.Genres?.length > 0 && (
|
{filtersLegacy?.Genres && filtersLegacy?.Genres?.length > 0 && (
|
||||||
<Accordion
|
<Accordion
|
||||||
expanded={expanded === 'filtersGenres'}
|
expanded={expanded === 'filtersGenres'}
|
||||||
onChange={handleChange('filtersGenres')}
|
onChange={handleChange('filtersGenres')}
|
||||||
@@ -339,7 +349,7 @@ const FilterButton: FC<FilterButtonProps> = ({
|
|||||||
</AccordionSummary>
|
</AccordionSummary>
|
||||||
<AccordionDetails>
|
<AccordionDetails>
|
||||||
<FiltersGenres
|
<FiltersGenres
|
||||||
genresOptions={data.Genres}
|
genresOptions={filtersLegacy.Genres}
|
||||||
libraryViewSettings={
|
libraryViewSettings={
|
||||||
libraryViewSettings
|
libraryViewSettings
|
||||||
}
|
}
|
||||||
@@ -351,8 +361,8 @@ const FilterButton: FC<FilterButtonProps> = ({
|
|||||||
</Accordion>
|
</Accordion>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{data?.OfficialRatings
|
{filtersLegacy?.OfficialRatings
|
||||||
&& data?.OfficialRatings?.length > 0 && (
|
&& filtersLegacy?.OfficialRatings?.length > 0 && (
|
||||||
<Accordion
|
<Accordion
|
||||||
expanded={
|
expanded={
|
||||||
expanded === 'filtersOfficialRatings'
|
expanded === 'filtersOfficialRatings'
|
||||||
@@ -373,7 +383,7 @@ const FilterButton: FC<FilterButtonProps> = ({
|
|||||||
</AccordionSummary>
|
</AccordionSummary>
|
||||||
<AccordionDetails>
|
<AccordionDetails>
|
||||||
<FiltersOfficialRatings
|
<FiltersOfficialRatings
|
||||||
OfficialRatingsOptions={data.OfficialRatings}
|
OfficialRatingsOptions={filtersLegacy.OfficialRatings}
|
||||||
libraryViewSettings={
|
libraryViewSettings={
|
||||||
libraryViewSettings
|
libraryViewSettings
|
||||||
}
|
}
|
||||||
@@ -385,7 +395,7 @@ const FilterButton: FC<FilterButtonProps> = ({
|
|||||||
</Accordion>
|
</Accordion>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{data?.Tags && data?.Tags.length > 0 && (
|
{filtersLegacy?.Tags && filtersLegacy?.Tags.length > 0 && (
|
||||||
<Accordion
|
<Accordion
|
||||||
expanded={expanded === 'filtersTags'}
|
expanded={expanded === 'filtersTags'}
|
||||||
onChange={handleChange('filtersTags')}
|
onChange={handleChange('filtersTags')}
|
||||||
@@ -400,7 +410,7 @@ const FilterButton: FC<FilterButtonProps> = ({
|
|||||||
</AccordionSummary>
|
</AccordionSummary>
|
||||||
<AccordionDetails>
|
<AccordionDetails>
|
||||||
<FiltersTags
|
<FiltersTags
|
||||||
tagsOptions={data.Tags}
|
tagsOptions={filtersLegacy.Tags}
|
||||||
libraryViewSettings={
|
libraryViewSettings={
|
||||||
libraryViewSettings
|
libraryViewSettings
|
||||||
}
|
}
|
||||||
@@ -412,7 +422,7 @@ const FilterButton: FC<FilterButtonProps> = ({
|
|||||||
</Accordion>
|
</Accordion>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{data?.Years && data?.Years?.length > 0 && (
|
{filtersLegacy?.Years && filtersLegacy?.Years?.length > 0 && (
|
||||||
<Accordion
|
<Accordion
|
||||||
expanded={expanded === 'filtersYears'}
|
expanded={expanded === 'filtersYears'}
|
||||||
onChange={handleChange('filtersYears')}
|
onChange={handleChange('filtersYears')}
|
||||||
@@ -427,7 +437,7 @@ const FilterButton: FC<FilterButtonProps> = ({
|
|||||||
</AccordionSummary>
|
</AccordionSummary>
|
||||||
<AccordionDetails>
|
<AccordionDetails>
|
||||||
<FiltersYears
|
<FiltersYears
|
||||||
yearsOptions={data.Years}
|
yearsOptions={filtersLegacy.Years}
|
||||||
libraryViewSettings={
|
libraryViewSettings={
|
||||||
libraryViewSettings
|
libraryViewSettings
|
||||||
}
|
}
|
||||||
@@ -464,6 +474,50 @@ const FilterButton: FC<FilterButtonProps> = ({
|
|||||||
</AccordionDetails>
|
</AccordionDetails>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
)}
|
)}
|
||||||
|
{isFiltersLanguagesEnabled() && !!filters?.AudioLanguages?.length && (
|
||||||
|
<Accordion
|
||||||
|
expanded={expanded === 'filtersAudioLanguages'}
|
||||||
|
onChange={handleChange('filtersAudioLanguages')}
|
||||||
|
>
|
||||||
|
<AccordionSummary
|
||||||
|
aria-controls='audioLanguages-content'
|
||||||
|
id='filtersAudioLanguages-header'
|
||||||
|
>
|
||||||
|
<Typography>
|
||||||
|
{globalize.translate('AudioLanguages')}
|
||||||
|
</Typography>
|
||||||
|
</AccordionSummary>
|
||||||
|
<AccordionDetails>
|
||||||
|
<FiltersAudioLanguages
|
||||||
|
options={filters.AudioLanguages}
|
||||||
|
libraryViewSettings={libraryViewSettings}
|
||||||
|
setLibraryViewSettings={setLibraryViewSettings}
|
||||||
|
/>
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
)}
|
||||||
|
{isFiltersLanguagesEnabled() && !!filters?.SubtitleLanguages?.length && (
|
||||||
|
<Accordion
|
||||||
|
expanded={expanded === 'filtersSubtitleLanguages'}
|
||||||
|
onChange={handleChange('filtersSubtitleLanguages')}
|
||||||
|
>
|
||||||
|
<AccordionSummary
|
||||||
|
aria-controls='subtitleLanguages-content'
|
||||||
|
id='filtersSubtitleLanguages-header'
|
||||||
|
>
|
||||||
|
<Typography>
|
||||||
|
{globalize.translate('SubtitleLanguages')}
|
||||||
|
</Typography>
|
||||||
|
</AccordionSummary>
|
||||||
|
<AccordionDetails>
|
||||||
|
<FiltersSubtitleLanguages
|
||||||
|
options={filters.SubtitleLanguages}
|
||||||
|
libraryViewSettings={libraryViewSettings}
|
||||||
|
setLibraryViewSettings={setLibraryViewSettings}
|
||||||
|
/>
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
)}
|
||||||
<Button
|
<Button
|
||||||
disabled={!hasFilters}
|
disabled={!hasFilters}
|
||||||
title={globalize.translate('ResetFilters')}
|
title={globalize.translate('ResetFilters')}
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import Checkbox from '@mui/material/Checkbox';
|
||||||
|
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||||
|
import FormGroup from '@mui/material/FormGroup';
|
||||||
|
import React, { FC, useCallback } from 'react';
|
||||||
|
import { LibraryViewSettings } from 'types/library';
|
||||||
|
|
||||||
|
interface FiltersAudioLanguagesProps {
|
||||||
|
options: { Name?: string | null, Value?: string | null }[];
|
||||||
|
libraryViewSettings: LibraryViewSettings;
|
||||||
|
setLibraryViewSettings: React.Dispatch<React.SetStateAction<LibraryViewSettings>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FiltersAudioLanguages: FC<FiltersAudioLanguagesProps> = ({
|
||||||
|
options,
|
||||||
|
libraryViewSettings,
|
||||||
|
setLibraryViewSettings
|
||||||
|
}) => {
|
||||||
|
const onFiltersChange = useCallback(
|
||||||
|
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
const value = event.target.value;
|
||||||
|
const existingValues = libraryViewSettings?.Filters?.AudioLanguages ?? [];
|
||||||
|
|
||||||
|
const updatedValues = existingValues.includes(value) ?
|
||||||
|
existingValues.filter((filter) => filter !== value) :
|
||||||
|
[...existingValues, value];
|
||||||
|
|
||||||
|
setLibraryViewSettings((prevState) => ({
|
||||||
|
...prevState,
|
||||||
|
StartIndex: 0,
|
||||||
|
Filters: {
|
||||||
|
...prevState.Filters,
|
||||||
|
AudioLanguages: updatedValues.length ? updatedValues : undefined
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
[setLibraryViewSettings, libraryViewSettings?.Filters?.AudioLanguages]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormGroup>
|
||||||
|
{options.filter((filter) => filter?.Value != null).map((filter) => (
|
||||||
|
<FormControlLabel
|
||||||
|
key={filter.Value}
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={!!libraryViewSettings?.Filters?.AudioLanguages?.includes(filter.Value!)}
|
||||||
|
onChange={onFiltersChange}
|
||||||
|
value={filter.Value}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={filter.Name}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</FormGroup>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FiltersAudioLanguages;
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import Checkbox from '@mui/material/Checkbox';
|
||||||
|
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||||
|
import FormGroup from '@mui/material/FormGroup';
|
||||||
|
import React, { FC, useCallback } from 'react';
|
||||||
|
import { LibraryViewSettings } from 'types/library';
|
||||||
|
|
||||||
|
interface FiltersSubtitleLanguagesProps {
|
||||||
|
options: { Name?: string | null, Value?: string | null }[];
|
||||||
|
libraryViewSettings: LibraryViewSettings;
|
||||||
|
setLibraryViewSettings: React.Dispatch<React.SetStateAction<LibraryViewSettings>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FiltersSubtitleLanguages: FC<FiltersSubtitleLanguagesProps> = ({
|
||||||
|
options,
|
||||||
|
libraryViewSettings,
|
||||||
|
setLibraryViewSettings
|
||||||
|
}) => {
|
||||||
|
const onFiltersChange = useCallback(
|
||||||
|
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
const value = event.target.value;
|
||||||
|
const existingValues = libraryViewSettings?.Filters?.SubtitleLanguages ?? [];
|
||||||
|
|
||||||
|
const updatedValues = existingValues.includes(value) ?
|
||||||
|
existingValues.filter((filter) => filter !== value) :
|
||||||
|
[...existingValues, value];
|
||||||
|
|
||||||
|
setLibraryViewSettings((prevState) => ({
|
||||||
|
...prevState,
|
||||||
|
StartIndex: 0,
|
||||||
|
Filters: {
|
||||||
|
...prevState.Filters,
|
||||||
|
SubtitleLanguages: updatedValues.length ? updatedValues : undefined
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
[setLibraryViewSettings, libraryViewSettings?.Filters?.SubtitleLanguages]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormGroup>
|
||||||
|
{options.filter((filter) => filter?.Value != null).map((filter) => (
|
||||||
|
<FormControlLabel
|
||||||
|
key={filter.Value}
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={!!libraryViewSettings?.Filters?.SubtitleLanguages?.includes(filter.Value!)}
|
||||||
|
onChange={onFiltersChange}
|
||||||
|
value={filter.Value}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={filter.Name}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</FormGroup>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FiltersSubtitleLanguages;
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import type { Api } from '@jellyfin/sdk/lib/api';
|
||||||
|
import type { MovieApiGetMovieRecommendationsRequest } from '@jellyfin/sdk/lib/generated-client/api/movie-api';
|
||||||
|
import { getMovieApi } from '@jellyfin/sdk/lib/utils/api/movie-api';
|
||||||
|
import { queryOptions, useQuery } from '@tanstack/react-query';
|
||||||
|
import type { AxiosRequestConfig } from 'axios';
|
||||||
|
|
||||||
|
import { useApi } from 'hooks/useApi';
|
||||||
|
|
||||||
|
const fetchGetMovieRecommendations = async (
|
||||||
|
api: Api,
|
||||||
|
params: MovieApiGetMovieRecommendationsRequest,
|
||||||
|
options?: AxiosRequestConfig
|
||||||
|
) => {
|
||||||
|
const response = await getMovieApi(api).getMovieRecommendations(params, options);
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Query options for fetching movie recommendations. */
|
||||||
|
export const getMovieRecommendationsQuery = (
|
||||||
|
api?: Api,
|
||||||
|
params: MovieApiGetMovieRecommendationsRequest = {},
|
||||||
|
enabled = true
|
||||||
|
) => queryOptions({
|
||||||
|
queryKey: ['MovieRecommendations', params?.parentId],
|
||||||
|
queryFn: ({ signal }) => fetchGetMovieRecommendations(api!, params, { signal }),
|
||||||
|
enabled: !!api && enabled
|
||||||
|
});
|
||||||
|
|
||||||
|
/** Hook for fetching movie recommendations. */
|
||||||
|
export const useMovieRecommendations = (
|
||||||
|
params?: MovieApiGetMovieRecommendationsRequest,
|
||||||
|
enabled?: boolean
|
||||||
|
) => {
|
||||||
|
const { api, user } = useApi();
|
||||||
|
return useQuery(getMovieRecommendationsQuery(
|
||||||
|
api,
|
||||||
|
{
|
||||||
|
...params,
|
||||||
|
userId: params?.userId || user?.Id
|
||||||
|
},
|
||||||
|
enabled
|
||||||
|
));
|
||||||
|
};
|
||||||
@@ -35,7 +35,7 @@ export const useLibrary = () => useContext(LibraryContext);
|
|||||||
|
|
||||||
export const LibraryProvider: FC<PropsWithChildren<unknown>> = ({ children }) => {
|
export const LibraryProvider: FC<PropsWithChildren<unknown>> = ({ children }) => {
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
const { libraryId, activeTab } = useCurrentTab();
|
const { libraryId, activeTab, settingsKey } = useCurrentTab();
|
||||||
|
|
||||||
const route = useMemo(() => LibraryRoutes.find(({ path }) => path === pathname), [pathname]);
|
const route = useMemo(() => LibraryRoutes.find(({ path }) => path === pathname), [pathname]);
|
||||||
const collectionType = route?.type;
|
const collectionType = route?.type;
|
||||||
@@ -48,7 +48,7 @@ export const LibraryProvider: FC<PropsWithChildren<unknown>> = ({ children }) =>
|
|||||||
// Local storage requires the view type to be known upfront so default to movies if unknown
|
// Local storage requires the view type to be known upfront so default to movies if unknown
|
||||||
const settingsViewType = viewType ?? LibraryTab.Movies;
|
const settingsViewType = viewType ?? LibraryTab.Movies;
|
||||||
const [viewSettings, setViewSettings] = useLocalStorage<LibraryViewSettings>(
|
const [viewSettings, setViewSettings] = useLocalStorage<LibraryViewSettings>(
|
||||||
getSettingsKey(settingsViewType, libraryId),
|
getSettingsKey(settingsViewType, settingsKey),
|
||||||
getDefaultLibraryViewSettings(settingsViewType)
|
getDefaultLibraryViewSettings(settingsViewType)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ import { SortOrder } from '@jellyfin/sdk/lib/generated-client/models/sort-order'
|
|||||||
import { type ParentId, ViewMode, type LibraryViewSettings } from 'types/library';
|
import { type ParentId, ViewMode, type LibraryViewSettings } from 'types/library';
|
||||||
import { LibraryTab } from 'types/libraryTab';
|
import { LibraryTab } from 'types/libraryTab';
|
||||||
|
|
||||||
export const getDefaultSortBy = (viewType: LibraryTab) => {
|
export const getDefaultSortBy = (viewType: LibraryTab): ItemSortBy[] => {
|
||||||
if (viewType === LibraryTab.Episodes) {
|
if (viewType === LibraryTab.Episodes) {
|
||||||
return ItemSortBy.SeriesSortName;
|
return [ItemSortBy.SeriesSortName];
|
||||||
}
|
}
|
||||||
|
|
||||||
return ItemSortBy.SortName;
|
return [ItemSortBy.SortName];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getDefaultLibraryViewSettings = (viewType: LibraryTab): LibraryViewSettings => {
|
export const getDefaultLibraryViewSettings = (viewType: LibraryTab): LibraryViewSettings => {
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
|
export * from './public';
|
||||||
export * from './user';
|
export * from './user';
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { AsyncRoute } from 'components/router/AsyncRoute';
|
||||||
|
|
||||||
|
export const ASYNC_PUBLIC_ROUTES: AsyncRoute[] = [
|
||||||
|
{ path: 'forgotpassword', page: 'session/forgotPassword' }
|
||||||
|
];
|
||||||
@@ -22,13 +22,6 @@ export const LEGACY_PUBLIC_ROUTES: LegacyRoute[] = [
|
|||||||
view: 'session/login/index.html'
|
view: 'session/login/index.html'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: 'forgotpassword',
|
|
||||||
pageProps: {
|
|
||||||
controller: 'session/forgotPassword/index',
|
|
||||||
view: 'session/forgotPassword/index.html'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: 'forgotpasswordpin',
|
path: 'forgotpasswordpin',
|
||||||
pageProps: {
|
pageProps: {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { toViewManagerPageRoute } from 'components/router/LegacyRoute';
|
|||||||
import ErrorBoundary from 'components/router/ErrorBoundary';
|
import ErrorBoundary from 'components/router/ErrorBoundary';
|
||||||
import FallbackRoute from 'components/router/FallbackRoute';
|
import FallbackRoute from 'components/router/FallbackRoute';
|
||||||
|
|
||||||
import { ASYNC_USER_ROUTES } from './asyncRoutes';
|
import { ASYNC_PUBLIC_ROUTES, ASYNC_USER_ROUTES } from './asyncRoutes';
|
||||||
import { LEGACY_PUBLIC_ROUTES, LEGACY_USER_ROUTES } from './legacyRoutes';
|
import { LEGACY_PUBLIC_ROUTES, LEGACY_USER_ROUTES } from './legacyRoutes';
|
||||||
import VideoPage from './video';
|
import VideoPage from './video';
|
||||||
|
|
||||||
@@ -38,6 +38,7 @@ export const APP_ROUTES: RouteObject[] = [
|
|||||||
/* Public routes */
|
/* Public routes */
|
||||||
element: <ConnectionRequired level='public' />,
|
element: <ConnectionRequired level='public' />,
|
||||||
children: [
|
children: [
|
||||||
|
...ASYNC_PUBLIC_ROUTES.map(toAsyncPageRoute),
|
||||||
...LEGACY_PUBLIC_ROUTES.map(toViewManagerPageRoute),
|
...LEGACY_PUBLIC_ROUTES.map(toViewManagerPageRoute),
|
||||||
|
|
||||||
/* Fallback route for invalid paths */
|
/* Fallback route for invalid paths */
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ const VideoPage: FC = () => {
|
|||||||
<RemotePlayButton />
|
<RemotePlayButton />
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
className='padded-left padded-right'
|
||||||
>
|
>
|
||||||
<Typography>{videoTitle}</Typography>
|
<Typography>{videoTitle}</Typography>
|
||||||
</AppToolbar>
|
</AppToolbar>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { type FC } from 'react';
|
import React, { type FC, type PropsWithChildren } from 'react';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import useCardText from './useCardText';
|
import useCardText from './useCardText';
|
||||||
import layoutManager from 'components/layoutManager';
|
import layoutManager from 'components/layoutManager';
|
||||||
@@ -21,6 +21,28 @@ const shouldShowDetailsMenu = (
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface CardFooterWrapperProps {
|
||||||
|
cardOptions: CardOptions;
|
||||||
|
footerClass?: string;
|
||||||
|
isOuterFooter: boolean;
|
||||||
|
logoUrl?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Component that conditionally wraps the footer content based on the card options and presence of a logo. */
|
||||||
|
const CardFooterWrapper: FC<PropsWithChildren<CardFooterWrapperProps>> = ({
|
||||||
|
cardOptions,
|
||||||
|
footerClass,
|
||||||
|
isOuterFooter,
|
||||||
|
logoUrl,
|
||||||
|
children
|
||||||
|
}) => (
|
||||||
|
(!isOuterFooter || logoUrl || cardOptions.cardLayout) ? (
|
||||||
|
<Box className={footerClass}>
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
) : children
|
||||||
|
);
|
||||||
|
|
||||||
interface LogoComponentProps {
|
interface LogoComponentProps {
|
||||||
logoUrl: string;
|
logoUrl: string;
|
||||||
}
|
}
|
||||||
@@ -72,7 +94,12 @@ const CardFooterText: FC<CardFooterTextProps> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box className={footerClass}>
|
<CardFooterWrapper
|
||||||
|
cardOptions={cardOptions}
|
||||||
|
footerClass={footerClass}
|
||||||
|
isOuterFooter={isOuterFooter}
|
||||||
|
logoUrl={logoUrl}
|
||||||
|
>
|
||||||
{logoUrl && <LogoComponent logoUrl={logoUrl} />}
|
{logoUrl && <LogoComponent logoUrl={logoUrl} />}
|
||||||
{shouldShowDetailsMenu(cardOptions, isOuterFooter) && (
|
{shouldShowDetailsMenu(cardOptions, isOuterFooter) && (
|
||||||
<MoreVertIconButton className='itemAction btnCardOptions' />
|
<MoreVertIconButton className='itemAction btnCardOptions' />
|
||||||
@@ -81,7 +108,7 @@ const CardFooterText: FC<CardFooterTextProps> = ({
|
|||||||
{cardTextLines}
|
{cardTextLines}
|
||||||
|
|
||||||
{progressBar}
|
{progressBar}
|
||||||
</Box>
|
</CardFooterWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -299,7 +299,7 @@ button::-moz-focus-inner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cardText {
|
.cardText {
|
||||||
padding: 0.06em 0.5em;
|
padding: 0.06em 2px; // 2px horizontal padding to avoid touching multiselect border
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
@@ -448,6 +448,11 @@ button::-moz-focus-inner {
|
|||||||
z-index: 1;
|
z-index: 1;
|
||||||
padding: 0.75em;
|
padding: 0.75em;
|
||||||
font-size: 88%;
|
font-size: 88%;
|
||||||
|
|
||||||
|
/* The "modern" layout needs additional specificity to prevent MUI styles from overriding */
|
||||||
|
&.paper-icon-button-light {
|
||||||
|
color: rgba(255, 255, 255, 0.76);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.cardOverlayButton-br {
|
.cardOverlayButton-br {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user