mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Merge branch 'master' into additional-episode-orders
This commit is contained in:
commit
0e606ed69d
275 changed files with 6941 additions and 4435 deletions
|
@ -42,6 +42,7 @@ module.exports = {
|
|||
'jsx-quotes': ['error', 'prefer-single'],
|
||||
'keyword-spacing': ['error'],
|
||||
'max-statements-per-line': ['error'],
|
||||
'no-empty-function': ['error'],
|
||||
'no-floating-decimal': ['error'],
|
||||
'no-multi-spaces': ['error'],
|
||||
'no-multiple-empty-lines': ['error', { 'max': 1 }],
|
||||
|
|
16
.github/dependabot.yaml
vendored
16
.github/dependabot.yaml
vendored
|
@ -1,16 +0,0 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: npm
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
open-pull-requests-limit: 10
|
||||
ignore:
|
||||
- dependency-name: hls.js
|
||||
update-types: [ version-update:semver-major ]
|
||||
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
open-pull-requests-limit: 10
|
42
.github/renovate.json
vendored
Normal file
42
.github/renovate.json
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"packageRules": [
|
||||
{
|
||||
"matchManagers": ["npm"],
|
||||
"addLabels": ["javascript"]
|
||||
},
|
||||
{
|
||||
"matchDepTypes": ["devDependencies"],
|
||||
"groupName": "development dependencies",
|
||||
"groupSlug": "dev-deps"
|
||||
},
|
||||
{
|
||||
"matchDepTypes": ["dependencies"],
|
||||
"groupName": "dependencies",
|
||||
"groupSlug": "deps"
|
||||
},
|
||||
{
|
||||
"matchDepTypes": ["action"],
|
||||
"addLabels": ["github_actions"],
|
||||
"groupName": "CI dependencies",
|
||||
"groupSlug": "ci-deps"
|
||||
},
|
||||
{
|
||||
"matchPackageNames": ["hls.js"],
|
||||
"matchUpdateTypes": "major",
|
||||
"enabled": false
|
||||
}
|
||||
],
|
||||
"vulnerabilityAlerts": {
|
||||
"addLabels": ["security"]
|
||||
},
|
||||
"dependencyDashboard": false,
|
||||
"ignoreDeps": ["npm", "node"],
|
||||
"lockFileMaintenance": {
|
||||
"enabled": false
|
||||
},
|
||||
"enabledManagers": ["npm", "github-actions"],
|
||||
"labels": ["dependencies"],
|
||||
"prHourlyLimit": 2,
|
||||
"rebaseWhen": "conflicted",
|
||||
"rangeStrategy": "pin"
|
||||
}
|
27
.github/stale.yml
vendored
27
.github/stale.yml
vendored
|
@ -1,27 +0,0 @@
|
|||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 120
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 21
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- regression
|
||||
- security
|
||||
- roadmap
|
||||
- future
|
||||
- feature
|
||||
- enhancement
|
||||
- confirmed
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: stale
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has gone 120 days without comment. To avoid abandoned issues, it will be closed in 21 days if there are no new comments.
|
||||
|
||||
If you're the original submitter of this issue, please comment confirming if this issue still affects you in the latest release or nightlies, or close the issue if it has been fixed. If you're another user also affected by this bug, please comment confirming so. Either action will remove the stale label.
|
||||
This bot exists to prevent issues from becoming stale and forgotten. Jellyfin is always moving forward, and bugs are often fixed as side effects of other changes. We therefore ask that bug report authors remain vigilant about their issues to ensure they are closed if fixed, or re-confirmed - perhaps with fresh logs or reproduction examples - regularly. If you have any questions you can reach us on [Matrix or Social Media](https://docs.jellyfin.org/general/getting-help.html).
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: false
|
||||
|
||||
# Disable automatic closing of pull requests
|
||||
pulls:
|
||||
daysUntilClose: false
|
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
|
@ -19,7 +19,7 @@ jobs:
|
|||
language: [ 'javascript' ]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
|
|
2
.github/workflows/commands.yml
vendored
2
.github/workflows/commands.yml
vendored
|
@ -18,7 +18,7 @@ jobs:
|
|||
comment-id: ${{ github.event.comment.id }}
|
||||
reactions: '+1'
|
||||
- name: Checkout the latest code
|
||||
uses: actions/checkout@v2.3.4
|
||||
uses: actions/checkout@v3.0.0
|
||||
with:
|
||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
fetch-depth: 0
|
||||
|
|
12
.github/workflows/lint.yml
vendored
12
.github/workflows/lint.yml
vendored
|
@ -13,10 +13,10 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup node environment
|
||||
uses: actions/setup-node@v2.5.1
|
||||
uses: actions/setup-node@v3.0.0
|
||||
with:
|
||||
node-version: 12
|
||||
check-latest: true
|
||||
|
@ -48,10 +48,10 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup node environment
|
||||
uses: actions/setup-node@v2.5.1
|
||||
uses: actions/setup-node@v3.0.0
|
||||
with:
|
||||
node-version: 12
|
||||
check-latest: true
|
||||
|
@ -86,10 +86,10 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup node environment
|
||||
uses: actions/setup-node@v2.5.1
|
||||
uses: actions/setup-node@v3.0.0
|
||||
with:
|
||||
node-version: 12
|
||||
check-latest: true
|
||||
|
|
27
.github/workflows/repo-stale.yaml
vendored
Normal file
27
.github/workflows/repo-stale.yaml
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
name: Issue Stale Check
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '30 1 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ contains(github.repository, 'jellyfin/') }}
|
||||
steps:
|
||||
- uses: actions/stale@v5.0.0
|
||||
with:
|
||||
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
days-before-stale: 120
|
||||
days-before-pr-stale: -1
|
||||
days-before-close: 21
|
||||
days-before-pr-close: -1
|
||||
exempt-issue-labels: regression,security,roadmap,future,feature,enhancement,confirmed
|
||||
stale-issue-label: stale
|
||||
stale-issue-message: |-
|
||||
This issue has gone 120 days without comment. To avoid abandoned issues, it will be closed in 21 days if there are no new comments.
|
||||
|
||||
If you're the original submitter of this issue, please comment confirming if this issue still affects you in the latest release or master branch, or close the issue if it has been fixed. If you're another user also affected by this bug, please comment confirming so. Either action will remove the stale label.
|
||||
|
||||
This bot exists to prevent issues from becoming stale and forgotten. Jellyfin is always moving forward, and bugs are often fixed as side effects of other changes. We therefore ask that bug report authors remain vigilant about their issues to ensure they are closed if fixed, or re-confirmed - perhaps with fresh logs or reproduction examples - regularly. If you have any questions you can reach us on [Matrix or Social Media](https://docs.jellyfin.org/general/getting-help.html).
|
1
.npmrc
1
.npmrc
|
@ -1 +1,2 @@
|
|||
fund=false
|
||||
save-exact=true
|
||||
|
|
|
@ -50,6 +50,7 @@
|
|||
- [Keegan Dahm](https://github.com/keegandahm)
|
||||
- [GodTamIt](https://github.com/GodTamIt)
|
||||
- [MinecraftPlaye](https://github.com/MinecraftPlaye)
|
||||
- [Matthew Jones](https://github.com/matthew-jones-uk)
|
||||
|
||||
# Emby Contributors
|
||||
|
||||
|
|
3963
package-lock.json
generated
3963
package-lock.json
generated
File diff suppressed because it is too large
Load diff
178
package.json
178
package.json
|
@ -5,98 +5,102 @@
|
|||
"repository": "https://github.com/jellyfin/jellyfin-web",
|
||||
"license": "GPL-2.0-or-later",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.16.7",
|
||||
"@babel/eslint-parser": "^7.16.5",
|
||||
"@babel/eslint-plugin": "^7.16.5",
|
||||
"@babel/plugin-proposal-class-properties": "^7.16.7",
|
||||
"@babel/plugin-proposal-private-methods": "^7.16.7",
|
||||
"@babel/plugin-transform-modules-umd": "^7.16.7",
|
||||
"@babel/preset-env": "^7.16.7",
|
||||
"@babel/preset-react": "^7.16.7",
|
||||
"@babel/preset-typescript": "^7.16.7",
|
||||
"@typescript-eslint/eslint-plugin": "^4.33.0",
|
||||
"@typescript-eslint/parser": "^4.33.0",
|
||||
"@uupaa/dynamic-import-polyfill": "^1.0.2",
|
||||
"autoprefixer": "^10.4.1",
|
||||
"babel-loader": "^8.2.3",
|
||||
"babel-plugin-dynamic-import-polyfill": "^1.0.0",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"confusing-browser-globals": "^1.0.11",
|
||||
"copy-webpack-plugin": "^10.2.0",
|
||||
"css-loader": "^6.5.1",
|
||||
"cssnano": "^5.0.14",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-plugin-compat": "^4.0.0",
|
||||
"eslint-plugin-eslint-comments": "^3.2.0",
|
||||
"eslint-plugin-import": "^2.25.4",
|
||||
"eslint-plugin-jsx-a11y": "^6.5.1",
|
||||
"eslint-plugin-promise": "^6.0.0",
|
||||
"eslint-plugin-react": "^7.28.0",
|
||||
"eslint-plugin-react-hooks": "^4.3.0",
|
||||
"expose-loader": "^3.1.0",
|
||||
"html-loader": "^3.0.1",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"postcss": "^8.4.5",
|
||||
"postcss-loader": "^6.2.1",
|
||||
"postcss-preset-env": "^7.2.0",
|
||||
"postcss-scss": "^4.0.2",
|
||||
"sass": "^1.45.2",
|
||||
"sass-loader": "^12.4.0",
|
||||
"source-map-loader": "^3.0.1",
|
||||
"style-loader": "^3.3.1",
|
||||
"stylelint": "^14.2.0",
|
||||
"stylelint-config-rational-order": "^0.1.2",
|
||||
"stylelint-no-browser-hacks": "^1.2.1",
|
||||
"stylelint-order": "^5.0.0",
|
||||
"stylelint-scss": "^4.1.0",
|
||||
"ts-loader": "^9.2.6",
|
||||
"typescript": "^4.5.4",
|
||||
"webpack": "^5.65.0",
|
||||
"webpack-cli": "^4.9.1",
|
||||
"webpack-dev-server": "^4.7.2",
|
||||
"webpack-merge": "^5.8.0",
|
||||
"workbox-webpack-plugin": "^6.2.4",
|
||||
"worker-loader": "^3.0.8"
|
||||
"@babel/core": "7.17.5",
|
||||
"@babel/eslint-parser": "7.17.0",
|
||||
"@babel/eslint-plugin": "7.16.5",
|
||||
"@babel/plugin-proposal-class-properties": "7.16.7",
|
||||
"@babel/plugin-proposal-private-methods": "7.16.11",
|
||||
"@babel/plugin-transform-modules-umd": "7.16.7",
|
||||
"@babel/preset-env": "7.16.11",
|
||||
"@babel/preset-react": "7.16.7",
|
||||
"@babel/preset-typescript": "7.16.7",
|
||||
"@thornbill/jellyfin-sdk": "0.4.1",
|
||||
"@types/lodash-es": "4.17.6",
|
||||
"@types/react": "17.0.39",
|
||||
"@types/react-dom": "17.0.12",
|
||||
"@typescript-eslint/eslint-plugin": "5.13.0",
|
||||
"@typescript-eslint/parser": "5.13.0",
|
||||
"@uupaa/dynamic-import-polyfill": "1.0.2",
|
||||
"autoprefixer": "10.4.2",
|
||||
"babel-loader": "8.2.3",
|
||||
"babel-plugin-dynamic-import-polyfill": "1.0.0",
|
||||
"clean-webpack-plugin": "4.0.0",
|
||||
"confusing-browser-globals": "1.0.11",
|
||||
"copy-webpack-plugin": "10.2.4",
|
||||
"css-loader": "6.6.0",
|
||||
"cssnano": "5.1.0",
|
||||
"eslint": "8.10.0",
|
||||
"eslint-plugin-compat": "4.0.2",
|
||||
"eslint-plugin-eslint-comments": "3.2.0",
|
||||
"eslint-plugin-import": "2.25.4",
|
||||
"eslint-plugin-jsx-a11y": "6.5.1",
|
||||
"eslint-plugin-promise": "6.0.0",
|
||||
"eslint-plugin-react": "7.29.2",
|
||||
"eslint-plugin-react-hooks": "4.3.0",
|
||||
"expose-loader": "3.1.0",
|
||||
"html-loader": "3.1.0",
|
||||
"html-webpack-plugin": "5.5.0",
|
||||
"postcss": "8.4.7",
|
||||
"postcss-loader": "6.2.1",
|
||||
"postcss-preset-env": "7.4.1",
|
||||
"postcss-scss": "4.0.3",
|
||||
"sass": "1.49.9",
|
||||
"sass-loader": "12.6.0",
|
||||
"source-map-loader": "3.0.1",
|
||||
"style-loader": "3.3.1",
|
||||
"stylelint": "14.5.3",
|
||||
"stylelint-config-rational-order": "0.1.2",
|
||||
"stylelint-no-browser-hacks": "1.2.1",
|
||||
"stylelint-order": "5.0.0",
|
||||
"stylelint-scss": "4.1.0",
|
||||
"ts-loader": "9.2.7",
|
||||
"typescript": "4.6.2",
|
||||
"webpack": "5.69.1",
|
||||
"webpack-cli": "4.9.2",
|
||||
"webpack-dev-server": "4.7.4",
|
||||
"webpack-merge": "5.8.0",
|
||||
"workbox-webpack-plugin": "6.5.0",
|
||||
"worker-loader": "3.0.8"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/noto-sans": "^4.5.1",
|
||||
"@fontsource/noto-sans-hk": "^4.5.2",
|
||||
"@fontsource/noto-sans-jp": "^4.5.2",
|
||||
"@fontsource/noto-sans-kr": "^4.5.2",
|
||||
"@fontsource/noto-sans-sc": "^4.5.2",
|
||||
"blurhash": "^1.1.4",
|
||||
"@fontsource/noto-sans": "4.5.1",
|
||||
"@fontsource/noto-sans-hk": "4.5.2",
|
||||
"@fontsource/noto-sans-jp": "4.5.2",
|
||||
"@fontsource/noto-sans-kr": "4.5.2",
|
||||
"@fontsource/noto-sans-sc": "4.5.2",
|
||||
"blurhash": "1.1.4",
|
||||
"classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz",
|
||||
"classnames": "^2.3.1",
|
||||
"core-js": "^3.20.2",
|
||||
"date-fns": "^2.28.0",
|
||||
"dompurify": "^2.3.4",
|
||||
"epubjs": "^0.3.90",
|
||||
"fast-text-encoding": "^1.0.3",
|
||||
"flv.js": "^1.6.2",
|
||||
"headroom.js": "^0.12.0",
|
||||
"hls.js": "^0.14.17",
|
||||
"intersection-observer": "^0.12.0",
|
||||
"jellyfin-apiclient": "^1.10.0",
|
||||
"jquery": "^3.5.1",
|
||||
"jstree": "^3.3.12",
|
||||
"libarchive.js": "^1.3.0",
|
||||
"classnames": "2.3.1",
|
||||
"core-js": "3.20.2",
|
||||
"date-fns": "2.28.0",
|
||||
"dompurify": "2.3.4",
|
||||
"epubjs": "0.3.90",
|
||||
"fast-text-encoding": "1.0.3",
|
||||
"flv.js": "1.6.2",
|
||||
"headroom.js": "0.12.0",
|
||||
"hls.js": "0.14.17",
|
||||
"intersection-observer": "0.12.0",
|
||||
"jellyfin-apiclient": "1.10.0",
|
||||
"jquery": "3.6.0",
|
||||
"jstree": "3.3.12",
|
||||
"libarchive.js": "1.3.0",
|
||||
"libass-wasm": "git+https://github.com/jellyfin/JavascriptSubtitlesOctopus.git#4.0.0-jf-4",
|
||||
"lodash-es": "^4.17.21",
|
||||
"marked": "^4.0.8",
|
||||
"material-design-icons-iconfont": "^6.1.1",
|
||||
"native-promise-only": "^0.8.0-a",
|
||||
"page": "^1.11.6",
|
||||
"lodash-es": "4.17.21",
|
||||
"marked": "4.0.10",
|
||||
"material-design-icons-iconfont": "6.1.1",
|
||||
"native-promise-only": "0.8.1",
|
||||
"page": "1.11.6",
|
||||
"pdfjs-dist": "2.12.313",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"screenfull": "^6.0.0",
|
||||
"sortablejs": "^1.14.0",
|
||||
"swiper": "^6.8.4",
|
||||
"webcomponents.js": "^0.7.24",
|
||||
"whatwg-fetch": "^3.6.2",
|
||||
"workbox-core": "^6.2.4",
|
||||
"workbox-precaching": "^6.2.4"
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2",
|
||||
"resize-observer-polyfill": "1.5.1",
|
||||
"screenfull": "6.0.0",
|
||||
"sortablejs": "1.14.0",
|
||||
"swiper": "6.8.4",
|
||||
"webcomponents.js": "0.7.24",
|
||||
"whatwg-fetch": "3.6.2",
|
||||
"workbox-core": "6.2.4",
|
||||
"workbox-precaching": "6.2.4"
|
||||
},
|
||||
"browserslist": [
|
||||
"last 2 Firefox versions",
|
||||
|
|
352
src/apiclient.d.ts
vendored
Normal file
352
src/apiclient.d.ts
vendored
Normal file
|
@ -0,0 +1,352 @@
|
|||
// TODO: Move to jellyfin-apiclient
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
declare module 'jellyfin-apiclient' {
|
||||
import {
|
||||
AllThemeMediaResult,
|
||||
AuthenticationResult,
|
||||
BaseItemDto,
|
||||
BaseItemDtoQueryResult,
|
||||
BufferRequestDto,
|
||||
ClientCapabilities,
|
||||
CountryInfo,
|
||||
CultureDto,
|
||||
DeviceOptions,
|
||||
DisplayPreferencesDto,
|
||||
EndPointInfo,
|
||||
FileSystemEntryInfo,
|
||||
GeneralCommand,
|
||||
GroupInfoDto,
|
||||
GuideInfo,
|
||||
IgnoreWaitRequestDto,
|
||||
ImageInfo,
|
||||
ImageProviderInfo,
|
||||
ImageType,
|
||||
ItemCounts,
|
||||
LiveTvInfo,
|
||||
MovePlaylistItemRequestDto,
|
||||
NewGroupRequestDto,
|
||||
NextItemRequestDto,
|
||||
NotificationResultDto,
|
||||
NotificationsSummaryDto,
|
||||
ParentalRating,
|
||||
PingRequestDto,
|
||||
PlaybackInfoResponse,
|
||||
PlaybackProgressInfo,
|
||||
PlaybackStartInfo,
|
||||
PlaybackStopInfo,
|
||||
PlayCommand,
|
||||
PlaystateCommand,
|
||||
PluginInfo,
|
||||
PluginSecurityInfo,
|
||||
PreviousItemRequestDto,
|
||||
QueryFiltersLegacy,
|
||||
QueueRequestDto,
|
||||
QuickConnectResult,
|
||||
QuickConnectState,
|
||||
ReadyRequestDto,
|
||||
RecommendationDto,
|
||||
RemoteImageResult,
|
||||
RemoveFromPlaylistRequestDto,
|
||||
SearchHintResult,
|
||||
SeekRequestDto,
|
||||
SeriesTimerInfoDto,
|
||||
SeriesTimerInfoDtoQueryResult,
|
||||
ServerConfiguration,
|
||||
SessionInfo,
|
||||
SetPlaylistItemRequestDto,
|
||||
SetRepeatModeRequestDto,
|
||||
SetShuffleModeRequestDto,
|
||||
SystemInfo,
|
||||
TaskInfo,
|
||||
TaskTriggerInfo,
|
||||
TimerInfoDto,
|
||||
TimerInfoDtoQueryResult,
|
||||
UserConfiguration,
|
||||
UserDto,
|
||||
UserItemDataDto,
|
||||
UserPolicy,
|
||||
UtcTimeResponse,
|
||||
VirtualFolderInfo
|
||||
} from '@thornbill/jellyfin-sdk/dist/generated-client';
|
||||
|
||||
class ApiClient {
|
||||
constructor(serverAddress: string, appName: string, appVersion: string, deviceName: string, deviceId: string);
|
||||
|
||||
accessToken(): string;
|
||||
addMediaPath(virtualFolderName: string, mediaPath: string, networkSharePath: string, refreshLibrary?: boolean): Promise<void>;
|
||||
addVirtualFolder(name: string, type?: string, refreshLibrary?: boolean, libraryOptions?: any): Promise<void>;
|
||||
appName(): string;
|
||||
appVersion(): string;
|
||||
authenticateUserByName(name: string, password: string): Promise<AuthenticationResult>;
|
||||
cancelLiveTvSeriesTimer(id: string): Promise<void>;
|
||||
cancelLiveTvTimer(id: string): Promise<void>;
|
||||
cancelSyncItems(itemIds: string[], targetId?: string): Promise<void>;
|
||||
clearAuthenticationInfo(): void;
|
||||
clearUserItemRating(userId: string, itemId: string): Promise<UserItemDataDto>;
|
||||
closeWebSocket(): void;
|
||||
createLiveTvSeriesTimer(item: string): Promise<void>;
|
||||
createLiveTvTimer(item: string): Promise<void>;
|
||||
createPackageReview(review: any): Promise<any>;
|
||||
createSyncPlayGroup(options?: NewGroupRequestDto): Promise<void>;
|
||||
createUser(user: UserDto): Promise<UserDto>;
|
||||
deleteDevice(deviceId: string): Promise<void>;
|
||||
deleteItemImage(itemId: string, imageType: ImageType, imageIndex?: number): Promise<void>;
|
||||
deleteItem(itemId: string): Promise<void>;
|
||||
deleteLiveTvRecording(id: string): Promise<void>;
|
||||
deleteUserImage(userId: string, imageType: ImageType, imageIndex?: number): Promise<void>;
|
||||
deleteUser(userId: string): Promise<void>;
|
||||
detectBitrate(force: boolean): Promise<number>;
|
||||
deviceId(): string;
|
||||
deviceName(): string;
|
||||
disablePlugin(id: string, version: string): Promise<void>;
|
||||
downloadRemoteImage(options: any): Promise<void>;
|
||||
enablePlugin(id: string, version: string): Promise<void>;
|
||||
encodeName(name: string): string;
|
||||
ensureWebSocket(): void;
|
||||
fetch(request: any, includeAuthorization?: boolean): Promise<any>;
|
||||
fetchWithFailover(request: any, enableReconnection?: boolean): Promise<any>;
|
||||
getAdditionalVideoParts(userId?: string, itemId: string): Promise<BaseItemDtoQueryResult>;
|
||||
getAlbumArtists(userId: string, options?: any): Promise<BaseItemDtoQueryResult>;
|
||||
getAncestorItems(itemId: string, userId?: string): Promise<BaseItemDto[]>;
|
||||
getArtist(name: string, userId?: string): Promise<BaseItemDto>;
|
||||
getArtists(userId: string, options?: any): Promise<BaseItemDtoQueryResult>;
|
||||
getAvailablePlugins(options?: any): Promise<PluginInfo[]>;
|
||||
getAvailableRemoteImages(options: any): Promise<RemoteImageResult>;
|
||||
getContentUploadHistory(): Promise<any>;
|
||||
getCountries(): Promise<CountryInfo[]>;
|
||||
getCriticReviews(itemId: string, options?: any): Promise<BaseItemDtoQueryResult>;
|
||||
getCultures(): Promise<CultureDto[]>;
|
||||
getCurrentUserId(): string;
|
||||
getDateParamValue(date: Date): string;
|
||||
getDefaultImageQuality(imageType: ImageType): number;
|
||||
getDevicesOptions(): Promise<DeviceOptions>;
|
||||
getDirectoryContents(path: string, options?: any): Promise<FileSystemEntryInfo[]>;
|
||||
getDisplayPreferences(id: string, userId: string, app: string): Promise<DisplayPreferencesDto>;
|
||||
getDownloadSpeed(byteSize: number): Promise<number>;
|
||||
getDrives(): Promise<FileSystemEntryInfo[]>;
|
||||
getEndpointInfo(): Promise<EndPointInfo>;
|
||||
getEpisodes(itemId: string, options?: any): Promise<BaseItemDtoQueryResult>;
|
||||
getFilters(options?: any): Promise<QueryFiltersLegacy>;
|
||||
getGenre(name: string, userId?: string): Promise<BaseItemDto>;
|
||||
getGenres(userId: string, options?: any): Promise<BaseItemDtoQueryResult>;
|
||||
getImageUrl(itemId: string, options?: any): string;
|
||||
getInstalledPlugins(): Promise<PluginInfo[]>;
|
||||
getInstantMixFromItem(itemId: string, options?: any): Promise<BaseItemDtoQueryResult>;
|
||||
getIntros(itemId: string): Promise<BaseItemDtoQueryResult>;
|
||||
getItemCounts(userId?: string): Promise<ItemCounts>;
|
||||
getItemDownloadUrl(itemId: string): string;
|
||||
getItemImageInfos(itemId: string): Promise<ImageInfo[]>;
|
||||
getItems(userId: string, options?: any): Promise<BaseItemDtoQueryResult>;
|
||||
getItem(userId: string, itemId: string): Promise<BaseItemDto>;
|
||||
getJSON(url: string, includeAuthorization?: boolean): Promise<any>;
|
||||
getLatestItems(options?: any): Promise<BaseItemDto[]>;
|
||||
getLiveStreamMediaInfo(liveStreamId: string): Promise<any>;
|
||||
getLiveTvChannel(id: string, userId?: string): Promise<BaseItemDto>;
|
||||
getLiveTvChannels(options?: any): Promise<BaseItemDtoQueryResult>;
|
||||
getLiveTvGuideInfo(userId: string): Promise<GuideInfo>;
|
||||
getLiveTvInfo(userId: string): Promise<LiveTvInfo>;
|
||||
getLiveTvProgram(id: string, userId?: string): Promise<BaseItemDto>;
|
||||
getLiveTvPrograms(options?: any): Promise<BaseItemDtoQueryResult>;
|
||||
getLiveTvRecommendedPrograms(options?: any): Promise<BaseItemDtoQueryResult>;
|
||||
getLiveTvRecordingGroup(id: string): Promise<BaseItemDto>;
|
||||
getLiveTvRecordingGroups(options?: any): Promise<BaseItemDtoQueryResult>;
|
||||
getLiveTvRecording(id: string, userId?: string): Promise<BaseItemDto>;
|
||||
getLiveTvRecordingSeries(options?: any): Promise<BaseItemDtoQueryResult>;
|
||||
getLiveTvRecordings(options?: any): Promise<BaseItemDtoQueryResult>;
|
||||
getLiveTvSeriesTimer(id: string): Promise<SeriesTimerInfoDto>;
|
||||
getLiveTvSeriesTimers(options?: any): Promise<SeriesTimerInfoDtoQueryResult>;
|
||||
getLiveTvTimer(id: string): Promise<TimerInfoDto>;
|
||||
getLiveTvTimers(options?: any): Promise<TimerInfoDtoQueryResult>;
|
||||
getLocalTrailers(userId: string, itemId: string): Promise<BaseItemDto[]>;
|
||||
getMovieRecommendations(options?: any): Promise<RecommendationDto[]>;
|
||||
getMusicGenre(name: string, userId?: string): Promise<BaseItemDto>;
|
||||
getMusicGenres(userId: string, options?: any): Promise<BaseItemDtoQueryResult>;
|
||||
getNamedConfiguration(name: string): Promise<any>;
|
||||
getNetworkDevices(): Promise<any>;
|
||||
getNetworkShares(path: string): Promise<FileSystemEntryInfo[]>;
|
||||
getNewLiveTvTimerDefaults(options?: any): Promise<SeriesTimerInfoDto>;
|
||||
getNextUpEpisodes(options?: any): Promise<BaseItemDtoQueryResult>;
|
||||
getNotificationSummary(userId: string): Promise<NotificationsSummaryDto>;
|
||||
getNotifications(userId: string, options?: any): Promise<NotificationResultDto>;
|
||||
getPackageInfo(name: string, guid: string): Promise<PackageInfo>;
|
||||
getPackageReviews(packageId: string, minRating?: string, maxRating?: string, limit?: string): Promise<any>;
|
||||
getParentalRatings(): Promise<ParentalRating[]>;
|
||||
getParentPath(path: string): Promise<string>;
|
||||
getPeople(userId: string, options?: any): Promise<BaseItemDtoQueryResult>;
|
||||
getPerson(name: string, userId?: string): Promise<BaseItemDto>;
|
||||
getPhysicalPaths(): Promise<string[]>;
|
||||
getPlaybackInfo(itemId: string, options: any, deviceProfile: any): Promise<PlaybackInfoResponse>;
|
||||
getPluginConfiguration(id: string): Promise<any>;
|
||||
getPublicSystemInfo(): Promise<PublicSystemInfo>;
|
||||
getPublicUsers(): Promise<UserDto[]>;
|
||||
getQuickConnect(verb: string): Promise<void|boolean|number|QuickConnectResult|QuickConnectState>;
|
||||
getReadySyncItems(deviceId: string): Promise<any>;
|
||||
getRecordingFolders(userId: string): Promise<BaseItemDtoQueryResult>;
|
||||
getRegistrationInfo(feature: string): Promise<any>;
|
||||
getRemoteImageProviders(options: any): Promise<ImageProviderInfo[]>;
|
||||
getResumableItems(userId: string, options?: any): Promise<BaseItemDtoQueryResult>;
|
||||
getRootFolder(userId: string): Promise<BaseItemDto>;
|
||||
getSavedEndpointInfo(): EndPointInfo;
|
||||
getScaledImageUrl(itemId: string, options?: any): string;
|
||||
getScheduledTask(id: string): Promise<TaskInfo>;
|
||||
getScheduledTasks(options?: any): Promise<TaskInfo[]>;
|
||||
getSearchHints(options?: any): Promise<SearchHintResult>;
|
||||
getSeasons(itemId: string, options?: any): Promise<BaseItemDtoQueryResult>;
|
||||
getServerConfiguration(): Promise<ServerConfiguration>;
|
||||
getServerTime(): Promise<UtcTimeResponse>;
|
||||
getSessions(options?: any): Promise<SessionInfo[]>;
|
||||
getSimilarItems(itemId: string, options?: any): Promise<BaseItemDtoQueryResult>;
|
||||
getSpecialFeatures(userId: string, itemId: string): Promise<BaseItemDto[]>;
|
||||
getStudio(name: string, userId?: string): Promise<BaseItemDto>;
|
||||
getStudios(userId: string, options?: any): Promise<BaseItemDtoQueryResult>;
|
||||
getSyncPlayGroups(): Promise<GroupInfoDto[]>;
|
||||
getSyncStatus(itemId: string): Promise<any>;
|
||||
getSystemInfo(): Promise<SystemInfo>;
|
||||
getThemeMedia(userId?: string, itemId: string, inherit?: boolean): Promise<AllThemeMediaResult>;
|
||||
getThumbImageUrl(item: BaseItemDto, options?: any): string;
|
||||
getUpcomingEpisodes(options?: any): Promise<BaseItemDtoQueryResult>;
|
||||
getUrl(name: string, params?: any, serverAddress?: string): string;
|
||||
get(url: string): Promise<any>;
|
||||
getUserImageUrl(userId: string, options?: any): string;
|
||||
getUsers(options?: any): Promise<UserDto[]>;
|
||||
getUser(userId: string): Promise<UserDto>;
|
||||
getUserViews(options?: any, userId: string): Promise<BaseItemDtoQueryResult>;
|
||||
getVirtualFolders(): Promise<VirtualFolderInfo[]>;
|
||||
handleMessageReceived(msg: any): void;
|
||||
installPlugin(name: string, guid: string, version?: string): Promise<void>;
|
||||
isLoggedIn(): boolean;
|
||||
isMessageChannelOpen(): boolean;
|
||||
isMinServerVersion(version: string): boolean;
|
||||
isWebSocketOpen(): boolean;
|
||||
isWebSocketOpenOrConnecting(): boolean;
|
||||
isWebSocketSupported(): boolean;
|
||||
joinSyncPlayGroup(options?: any): Promise<void>;
|
||||
leaveSyncPlayGroup(): Promise<void>;
|
||||
logout(): Promise<void>;
|
||||
markNotificationsRead(userId: string, idList: string[], isRead: boolean): Promise<void>;
|
||||
markPlayed(userId: string, itemId: string, date: Date): Promise<UserItemDataDto>;
|
||||
markUnplayed(userId: string, itemId: string, date: Date): Promise<UserItemDataDto>;
|
||||
openWebSocket(): void;
|
||||
quickConnect(secret: string): Promise<AuthenticationResult>;
|
||||
refreshItem(itemId: string, options?: any): Promise<void>;
|
||||
removeMediaPath(virtualFolderName: string, mediaPath: string, refreshLibrary?: boolean): Promise<void>;
|
||||
removeVirtualFolder(name: string, refreshLibrary?: boolean): Promise<void>;
|
||||
renameVirtualFolder(name: string, newName: string, refreshLibrary?: boolean): Promise<void>;
|
||||
reportCapabilities(capabilities: ClientCapabilities): Promise<void>;
|
||||
reportOfflineActions(actions: any): Promise<any>;
|
||||
reportPlaybackProgress(options: PlaybackProgressInfo): Promise<void>;
|
||||
reportPlaybackStart(options: PlaybackStartInfo): Promise<void>;
|
||||
reportPlaybackStopped(options: PlaybackStopInfo): Promise<void>;
|
||||
reportSyncJobItemTransferred(syncJobItemId: string): Promise<any>;
|
||||
requestSyncPlayBuffering(options?: BufferRequestDto): Promise<void>;
|
||||
requestSyncPlayMovePlaylistItem(options?: MovePlaylistItemRequestDto): Promise<void>;
|
||||
requestSyncPlayNextItem(options?: NextItemRequestDto): Promise<void>;
|
||||
requestSyncPlayPause(): Promise<void>;
|
||||
requestSyncPlayPreviousItem(options?: PreviousItemRequestDto): Promise<void>;
|
||||
requestSyncPlayQueue(options?: QueueRequestDto): Promise<void>;
|
||||
requestSyncPlayReady(options?: ReadyRequestDto): Promise<void>;
|
||||
requestSyncPlayRemoveFromPlaylist(options?: RemoveFromPlaylistRequestDto): Promise<void>;
|
||||
requestSyncPlaySeek(options?: SeekRequestDto): Promise<void>;
|
||||
requestSyncPlaySetIgnoreWait(options?: IgnoreWaitRequestDto): Promise<void>;
|
||||
requestSyncPlaySetNewQueue(options?: NewGroupRequestDto): Promise<void>;
|
||||
requestSyncPlaySetPlaylistItem(options?: SetPlaylistItemRequestDto): Promise<void>;
|
||||
requestSyncPlaySetRepeatMode(options?: SetRepeatModeRequestDto): Promise<void>;
|
||||
requestSyncPlaySetShuffleMode(options?: SetShuffleModeRequestDto): Promise<void>;
|
||||
requestSyncPlayUnpause(): Promise<void>;
|
||||
resetEasyPassword(userId: string): Promise<void>;
|
||||
resetLiveTvTuner(id: string): Promise<void>;
|
||||
resetUserPassword(userId: string): Promise<void>;
|
||||
restartServer(): Promise<void>;
|
||||
sendCommand(sessionId: string, command: any): Promise<void>;
|
||||
sendMessageCommand(sessionId: string, options: GeneralCommand): Promise<void>;
|
||||
sendMessage(name: string, data: any): void;
|
||||
sendPlayCommand(sessionId: string, options: PlayCommand): Promise<void>;
|
||||
sendPlayStateCommand(sessionId: string, command: PlaystateCommand, options?: any): Promise<void>;
|
||||
sendSyncPlayPing(options?: PingRequestDto): Promise<void>;
|
||||
sendWebSocketMessage(name: string, data: any): void;
|
||||
serverAddress(val?: string): string;
|
||||
serverId(): string;
|
||||
serverVersion(): string
|
||||
setAuthenticationInfo(accessKey?: string, userId?: string): void;
|
||||
setRequestHeaders(headers: any): void;
|
||||
setSystemInfo(info: SystemInfo): void;
|
||||
shutdownServer(): Promise<void>;
|
||||
startScheduledTask(id: string): Promise<void>;
|
||||
stopActiveEncodings(playSessionId: string): Promise<void>;
|
||||
stopScheduledTask(id: string): Promise<void>;
|
||||
syncData(data: any): Promise<any>;
|
||||
uninstallPluginByVersion(id: string, version: string): Promise<void>;
|
||||
uninstallPlugin(id: string): Promise<void>;
|
||||
updateDisplayPreferences(id: string, obj: DisplayPreferencesDto, userId: string, app: string): Promise<void>;
|
||||
updateEasyPassword(userId: string, newPassword: string): Promise<void>;
|
||||
updateFavoriteStatus(userId: string, itemId: string, isFavorite: boolean): Promise<UserItemDataDto>;
|
||||
updateItemImageIndex(itemId: string, imageType: ImageType, imageIndex: number, newIndex: number): Promise<any>;
|
||||
updateItem(item: BaseItemDto): Promise<void>;
|
||||
updateLiveTvSeriesTimer(item: SeriesTimerInfoDto): Promise<void>;
|
||||
updateLiveTvTimer(item: TimerInfoDto): Promise<void>;
|
||||
updateMediaPath(virtualFolderName: string, pathInfo: any): Promise<void>;
|
||||
updateNamedConfiguration(name: string, configuration: any): Promise<void>;
|
||||
updatePluginConfiguration(id: string, configuration: any): Promise<void>;
|
||||
updatePluginSecurityInfo(info: PluginSecurityInfo): Promise<void>;
|
||||
updateScheduledTaskTriggers(id: string, triggers: TaskTriggerInfo[]): Promise<void>;
|
||||
updateServerConfiguration(configuration: ServerConfiguration): Promise<void>;
|
||||
updateServerInfo(server: any, serverUrl: string): void;
|
||||
updateUserConfiguration(userId: string, configuration: UserConfiguration): Promise<void>;
|
||||
updateUserItemRating(userId: string, itemId: string, likes: boolean): Promise<UserItemDataDto>;
|
||||
updateUserPassword(userId: string, currentPassword: string, newPassword: string): Promise<void>;
|
||||
updateUserPolicy(userId: string, policy: UserPolicy): Promise<void>;
|
||||
updateUser(user: UserDto): Promise<void>;
|
||||
updateVirtualFolderOptions(id: string, libraryOptions?: any): Promise<void>;
|
||||
uploadItemImage(itemId: string, imageType: ImageType, file: File): Promise<void>;
|
||||
uploadItemSubtitle(itemId: string, language: string, isForced: boolean, file: File): Promise<void>;
|
||||
uploadUserImage(userId: string, imageType: ImageType, file: File): Promise<void>;
|
||||
}
|
||||
|
||||
class AppStore {
|
||||
constructor();
|
||||
|
||||
getItem(name: string): string|null;
|
||||
removeItem(name: string): void;
|
||||
setItem(name: string, value: string): void;
|
||||
}
|
||||
|
||||
class ConnectionManager {
|
||||
constructor(credentialProvider: Credentials, appName: string, appVersion: string, deviceName: string, deviceId: string, capabilities: ClientCapabilities);
|
||||
|
||||
addApiClient(apiClient: ApiClient): void;
|
||||
clearData(): void;
|
||||
connect(options?: any): Promise<any>;
|
||||
connectToAddress(address: string, options?: any): Promise<any>;
|
||||
connectToServer(server: any, options?: any): Promise<any>;
|
||||
connectToServers(servers: any[], options?: any): Promise<any>;
|
||||
deleteServer(serverId: string): Promise<void>;
|
||||
getApiClient(item: BaseItemDto|string): ApiClient;
|
||||
getApiClients(): ApiClient[];
|
||||
getAvailableServers(): any[];
|
||||
getOrCreateApiClient(serverId: string): ApiClient;
|
||||
getSavedServers(): any[];
|
||||
handleMessageReceived(msg: any): void;
|
||||
logout(): Promise<void>;
|
||||
minServerVersion(val?: string): string;
|
||||
user(apiClient: ApiClient): Promise<any>;
|
||||
}
|
||||
|
||||
class Credentials {
|
||||
constructor(key?: string);
|
||||
|
||||
addOrUpdateServer(list: any[], server: any): any;
|
||||
clear(): void;
|
||||
credentials(data?: any): any;
|
||||
}
|
||||
|
||||
interface Event {
|
||||
type: string;
|
||||
}
|
||||
|
||||
const Events: {
|
||||
off(obj: any, eventName: string, fn: (e: Event, ...args: any[]) => void): void;
|
||||
on(obj: any, eventName: string, fn: (e: Event, ...args: any[]) => void): void;
|
||||
trigger(obj: any, eventName: string, ...args: any[]): void;
|
||||
};
|
||||
}
|
||||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
@ -627,10 +627,15 @@
|
|||
|
||||
.subtitle {
|
||||
margin: 0.15em 0 0.2em;
|
||||
|
||||
// Leave room for a focused button
|
||||
margin-left: -1em;
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
.layout-mobile .subtitle {
|
||||
margin: 0.2em 0 0.2em;
|
||||
padding-left: 0; // Reset padding for focused button since 'margin-left' is 0
|
||||
}
|
||||
|
||||
.detailPagePrimaryContainer {
|
||||
|
@ -987,6 +992,10 @@ div.itemDetailGalleryLink.defaultCardBackground {
|
|||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.mediaInfoContent .btnCopy .material-icons {
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
.mediaInfoStream {
|
||||
margin: 0 3em 0 0;
|
||||
display: inline-block;
|
||||
|
@ -995,6 +1004,10 @@ div.itemDetailGalleryLink.defaultCardBackground {
|
|||
|
||||
.mediaInfoStreamType {
|
||||
display: block;
|
||||
margin: 0.622em 0; /* copy button height compensation */
|
||||
}
|
||||
|
||||
.layout-tv .mediaInfoStreamType {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -142,7 +142,7 @@ export function show(options) {
|
|||
|
||||
if (layoutManager.tv) {
|
||||
html += `<button is="paper-icon-button-light" class="btnCloseActionSheet hide-mouse-idle-tv" tabindex="-1">
|
||||
<span class="material-icons arrow_back"></span>
|
||||
<span class="material-icons arrow_back" aria-hidden="true"></span>
|
||||
</button>`;
|
||||
}
|
||||
|
||||
|
@ -204,9 +204,9 @@ export function show(options) {
|
|||
itemIcon = icons[i];
|
||||
|
||||
if (itemIcon) {
|
||||
html += `<span class="actionsheetMenuItemIcon listItemIcon listItemIcon-transparent material-icons ${itemIcon}"></span>`;
|
||||
html += `<span class="actionsheetMenuItemIcon listItemIcon listItemIcon-transparent material-icons ${itemIcon}" aria-hidden="true"></span>`;
|
||||
} else if (renderIcon && !center) {
|
||||
html += '<span class="actionsheetMenuItemIcon listItemIcon listItemIcon-transparent material-icons check" style="visibility:hidden;"></span>';
|
||||
html += '<span class="actionsheetMenuItemIcon listItemIcon listItemIcon-transparent material-icons check" aria-hidden="true" style="visibility:hidden;"></span>';
|
||||
}
|
||||
|
||||
html += '<div class="listItemBody actionsheetListItemBody">';
|
||||
|
|
|
@ -90,12 +90,13 @@
|
|||
|
||||
.actionSheetTitle {
|
||||
margin: 0.6em 0 0.7em !important;
|
||||
padding: 0 0.9em;
|
||||
padding: 0 0.75rem;
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
.actionSheetText {
|
||||
padding: 0 1em;
|
||||
margin-top: 0;
|
||||
padding: 0 0.75rem;
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -23,12 +23,12 @@ import alert from './alert';
|
|||
}
|
||||
|
||||
if (entry.UserId && entry.UserPrimaryImageTag) {
|
||||
html += '<span class="listItemIcon material-icons dvr" style="width:2em!important;height:2em!important;padding:0;color:transparent;background-color:' + color + ";background-image:url('" + apiClient.getUserImageUrl(entry.UserId, {
|
||||
html += '<span class="listItemIcon material-icons dvr" aria-hidden="true" style="width:2em!important;height:2em!important;padding:0;color:transparent;background-color:' + color + ";background-image:url('" + apiClient.getUserImageUrl(entry.UserId, {
|
||||
type: 'Primary',
|
||||
tag: entry.UserPrimaryImageTag
|
||||
}) + "');background-repeat:no-repeat;background-position:center center;background-size: cover;\"></span>";
|
||||
} else {
|
||||
html += '<span class="listItemIcon material-icons ' + icon + '" style="background-color:' + color + '"></span>';
|
||||
html += '<span class="listItemIcon material-icons ' + icon + '" aria-hidden="true" style="background-color:' + color + '"></span>';
|
||||
}
|
||||
|
||||
html += '<div class="listItemBody three-line">';
|
||||
|
@ -45,7 +45,7 @@ import alert from './alert';
|
|||
|
||||
if (entry.Overview) {
|
||||
html += `<button type="button" is="paper-icon-button-light" class="btnEntryInfo" data-id="${entry.Id}" title="${globalize.translate('Info')}">
|
||||
<span class="material-icons info"></span>
|
||||
<span class="material-icons info" aria-hidden="true"></span>
|
||||
</button>`;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,14 +3,14 @@ import React, { FunctionComponent, useEffect, useRef, useState } from 'react';
|
|||
import AlphaPicker from './alphaPicker';
|
||||
|
||||
type AlphaPickerProps = {
|
||||
onAlphaPicked?: () => void
|
||||
onAlphaPicked?: (e: Event) => void
|
||||
};
|
||||
|
||||
// React compatibility wrapper component for alphaPicker.js
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
const AlphaPickerComponent: FunctionComponent<AlphaPickerProps> = ({ onAlphaPicked = () => {} }: AlphaPickerProps) => {
|
||||
const [ alphaPicker, setAlphaPicker ] = useState(null);
|
||||
const element = useRef(null);
|
||||
const [ alphaPicker, setAlphaPicker ] = useState<AlphaPicker>();
|
||||
const element = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setAlphaPicker(new AlphaPicker({
|
||||
|
|
|
@ -75,7 +75,7 @@ import 'material-design-icons-iconfont';
|
|||
|
||||
html += `<div class="${rowClassName}">`;
|
||||
if (options.mode === 'keyboard') {
|
||||
html += `<button data-value=" " is="paper-icon-button-light" class="${alphaPickerButtonClassName}"><span class="material-icons alphaPickerButtonIcon space_bar"></span></button>`;
|
||||
html += `<button data-value=" " is="paper-icon-button-light" class="${alphaPickerButtonClassName}"><span class="material-icons alphaPickerButtonIcon space_bar" aria-hidden="true"></span></button>`;
|
||||
} else {
|
||||
letters = ['#'];
|
||||
html += mapLetters(letters, vertical).join('');
|
||||
|
@ -85,7 +85,7 @@ import 'material-design-icons-iconfont';
|
|||
html += mapLetters(letters, vertical).join('');
|
||||
|
||||
if (options.mode === 'keyboard') {
|
||||
html += `<button data-value="backspace" is="paper-icon-button-light" class="${alphaPickerButtonClassName}"><span class="material-icons alphaPickerButtonIcon backspace"></span></button>`;
|
||||
html += `<button data-value="backspace" is="paper-icon-button-light" class="${alphaPickerButtonClassName}"><span class="material-icons alphaPickerButtonIcon backspace" aria-hidden="true"></span></button>`;
|
||||
html += '</div>';
|
||||
|
||||
letters = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
||||
|
@ -287,7 +287,7 @@ import 'material-design-icons-iconfont';
|
|||
this.value(query.NameStartsWith);
|
||||
}
|
||||
|
||||
this.visible(query.SortBy.indexOf('SortName') === 0);
|
||||
this.visible(query.SortBy.indexOf('SortName') !== -1);
|
||||
}
|
||||
|
||||
visible(visible) {
|
||||
|
|
|
@ -34,16 +34,21 @@ class AppRouter {
|
|||
|
||||
constructor() {
|
||||
// WebKit fires a popstate event on document load
|
||||
// Skip it using timeout
|
||||
// Skip it using boolean
|
||||
// For Tizen 2.x
|
||||
// https://stackoverflow.com/a/12214354
|
||||
// See `page` node module
|
||||
let loaded = document.readyState === 'complete';
|
||||
if (!loaded) {
|
||||
window.addEventListener('load', () => {
|
||||
setTimeout(() => {
|
||||
window.addEventListener('popstate', () => {
|
||||
this.popstateOccurred = true;
|
||||
});
|
||||
loaded = true;
|
||||
}, 0);
|
||||
});
|
||||
}
|
||||
window.addEventListener('popstate', () => {
|
||||
if (!loaded) return;
|
||||
this.popstateOccurred = true;
|
||||
});
|
||||
|
||||
document.addEventListener('viewshow', () => this.onViewShow());
|
||||
|
||||
|
@ -752,7 +757,13 @@ class AppRouter {
|
|||
}
|
||||
|
||||
if (item === 'nextup') {
|
||||
return '#!/list.html?type=nextup&serverId=' + options.serverId;
|
||||
url = '#!/list.html?type=nextup&serverId=' + options.serverId;
|
||||
|
||||
if (options.rewatching) {
|
||||
url += '&rewatching=' + options.rewatching;
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
if (item === 'list') {
|
||||
|
|
|
@ -199,7 +199,6 @@ const supportedFeatures = function () {
|
|||
if (browser.operaTv || browser.tizen || browser.orsay || browser.web0s) {
|
||||
features.push('exit');
|
||||
} else {
|
||||
features.push('exitmenu');
|
||||
features.push('plugins');
|
||||
}
|
||||
|
||||
|
|
|
@ -64,7 +64,6 @@ import layoutManager from './layoutManager';
|
|||
candidates.push(activeElement);
|
||||
}
|
||||
|
||||
candidates = candidates.concat(Array.from(container.querySelectorAll('.btnResume')));
|
||||
candidates = candidates.concat(Array.from(container.querySelectorAll('.btnPlay')));
|
||||
|
||||
let focusedElement;
|
||||
|
|
|
@ -227,6 +227,10 @@ button::-moz-focus-inner {
|
|||
background-color: transparent;
|
||||
}
|
||||
|
||||
.cardPadder {
|
||||
position: relative; // For centering the cardImageIcon
|
||||
}
|
||||
|
||||
.cardBox:not(.visualCardBox) .cardPadder {
|
||||
border-radius: 0.2em;
|
||||
background-color: #242424;
|
||||
|
@ -377,6 +381,18 @@ button::-moz-focus-inner {
|
|||
color: inherit;
|
||||
}
|
||||
|
||||
.cardPadder .cardImageIcon {
|
||||
color: #111;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.cardImageContainer .cardImageIcon {
|
||||
margin: auto; /* 'justify-content: center' doesn't work in Safari 10 */
|
||||
}
|
||||
|
||||
.cardIndicators {
|
||||
right: 0.225em;
|
||||
top: 0.225em;
|
||||
|
|
|
@ -145,7 +145,7 @@ import ServerConnections from '../ServerConnections';
|
|||
return 100 / 14.2857142857;
|
||||
}
|
||||
if (screenWidth >= 1200) {
|
||||
return 100 / 16.666666666666666666;
|
||||
return 100 / 16.66666667;
|
||||
}
|
||||
if (screenWidth >= 1000) {
|
||||
return 5;
|
||||
|
@ -481,12 +481,20 @@ import ServerConnections from '../ServerConnections';
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} CardImageUrl
|
||||
* @property {string} imgUrl - Image URL.
|
||||
* @property {string} blurhash - Image blurhash.
|
||||
* @property {boolean} forceName - Force name.
|
||||
* @property {boolean} coverImage - Use cover style.
|
||||
*/
|
||||
|
||||
/** Get the URL of the card's image.
|
||||
* @param {Object} item - Item for which to generate a card.
|
||||
* @param {Object} apiClient - API client object.
|
||||
* @param {Object} options - Options of the card.
|
||||
* @param {string} shape - Shape of the desired image.
|
||||
* @returns {Object} Object representing the URL of the card's image.
|
||||
* @returns {CardImageUrl} Object representing the URL of the card's image.
|
||||
*/
|
||||
function getCardImageUrl(item, apiClient, options, shape) {
|
||||
item = item.ProgramInfo || item;
|
||||
|
@ -639,7 +647,7 @@ import ServerConnections from '../ServerConnections';
|
|||
|
||||
/**
|
||||
* Generates an index used to select the default color of a card based on a string.
|
||||
* @param {string} str - String to use for generating the index.
|
||||
* @param {?string} [str] - String to use for generating the index.
|
||||
* @returns {number} Index of the color.
|
||||
*/
|
||||
function getDefaultColorIndex(str) {
|
||||
|
@ -726,8 +734,8 @@ import ServerConnections from '../ServerConnections';
|
|||
/**
|
||||
* Returns the air time text for the item based on the given times.
|
||||
* @param {object} item - Item used to generate the air time text.
|
||||
* @param {string} showAirDateTime - ISO8601 date for the start of the show.
|
||||
* @param {string} showAirEndTime - ISO8601 date for the end of the show.
|
||||
* @param {boolean} showAirDateTime - ISO8601 date for the start of the show.
|
||||
* @param {boolean} showAirEndTime - ISO8601 date for the end of the show.
|
||||
* @returns {string} The air time text for the item based on the given dates.
|
||||
*/
|
||||
function getAirTimeText(item, showAirDateTime, showAirEndTime) {
|
||||
|
@ -782,7 +790,7 @@ import ServerConnections from '../ServerConnections';
|
|||
|
||||
if (isOuterFooter && options.cardLayout && layoutManager.mobile) {
|
||||
if (options.cardFooterAside !== 'none') {
|
||||
html += '<button is="paper-icon-button-light" class="itemAction btnCardOptions cardText-secondary" data-action="menu"><span class="material-icons more_vert"></span></button>';
|
||||
html += '<button is="paper-icon-button-light" class="itemAction btnCardOptions cardText-secondary" data-action="menu"><span class="material-icons more_vert" aria-hidden="true"></span></button>';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -856,6 +864,10 @@ import ServerConnections from '../ServerConnections';
|
|||
}
|
||||
}
|
||||
|
||||
if (item.ExtraType && item.ExtraType !== 'Unknown') {
|
||||
lines.push(globalize.translate(item.ExtraType));
|
||||
}
|
||||
|
||||
if (options.showItemCounts) {
|
||||
lines.push(getItemCountsHtml(options, item));
|
||||
}
|
||||
|
@ -1125,7 +1137,7 @@ import ServerConnections from '../ServerConnections';
|
|||
|
||||
/**
|
||||
* Returns the default background class for a card based on a string.
|
||||
* @param {string} str - Text used to generate the background class.
|
||||
* @param {?string} [str] - Text used to generate the background class.
|
||||
* @returns {string} CSS classes for default card backgrounds.
|
||||
*/
|
||||
export function getDefaultBackgroundClass(str) {
|
||||
|
@ -1297,15 +1309,15 @@ import ServerConnections from '../ServerConnections';
|
|||
const btnCssClass = 'cardOverlayButton cardOverlayButton-br itemAction';
|
||||
|
||||
if (options.centerPlayButton) {
|
||||
overlayButtons += '<button is="paper-icon-button-light" class="' + btnCssClass + ' cardOverlayButton-centered" data-action="play"><span class="material-icons cardOverlayButtonIcon play_arrow"></span></button>';
|
||||
overlayButtons += '<button is="paper-icon-button-light" class="' + btnCssClass + ' cardOverlayButton-centered" data-action="play"><span class="material-icons cardOverlayButtonIcon play_arrow" aria-hidden="true"></span></button>';
|
||||
}
|
||||
|
||||
if (overlayPlayButton && !item.IsPlaceHolder && (item.LocationType !== 'Virtual' || !item.MediaType || item.Type === 'Program') && item.Type !== 'Person') {
|
||||
overlayButtons += '<button is="paper-icon-button-light" class="' + btnCssClass + '" data-action="play"><span class="material-icons cardOverlayButtonIcon play_arrow"></span></button>';
|
||||
overlayButtons += '<button is="paper-icon-button-light" class="' + btnCssClass + '" data-action="play"><span class="material-icons cardOverlayButtonIcon play_arrow" aria-hidden="true"></span></button>';
|
||||
}
|
||||
|
||||
if (options.overlayMoreButton) {
|
||||
overlayButtons += '<button is="paper-icon-button-light" class="' + btnCssClass + '" data-action="menu"><span class="material-icons cardOverlayButtonIcon more_vert"></span></button>';
|
||||
overlayButtons += '<button is="paper-icon-button-light" class="' + btnCssClass + '" data-action="menu"><span class="material-icons cardOverlayButtonIcon more_vert" aria-hidden="true"></span></button>';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1340,7 +1352,18 @@ import ServerConnections from '../ServerConnections';
|
|||
|
||||
const cardScalableClass = 'cardScalable';
|
||||
|
||||
cardImageContainerOpen = '<div class="' + cardBoxClass + '"><div class="' + cardScalableClass + '"><div class="cardPadder cardPadder-' + shape + '"></div>' + cardImageContainerOpen;
|
||||
let cardPadderIcon = '';
|
||||
|
||||
// TV Channel logos are transparent so skip the placeholder to avoid overlapping
|
||||
if (imgUrl && item.Type !== 'TvChannel') {
|
||||
cardPadderIcon = getDefaultText(item, {
|
||||
// Always use an icon
|
||||
defaultCardImageIcon: 'folder',
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
cardImageContainerOpen = `<div class="${cardBoxClass}"><div class="${cardScalableClass}"><div class="cardPadder cardPadder-${shape}">${cardPadderIcon}</div>${cardImageContainerOpen}`;
|
||||
cardBoxClose = '</div>';
|
||||
cardScalableClose = '</div>';
|
||||
|
||||
|
@ -1444,7 +1467,7 @@ import ServerConnections from '../ServerConnections';
|
|||
const btnCssClass = 'cardOverlayButton cardOverlayButton-hover itemAction paper-icon-button-light';
|
||||
|
||||
if (playbackManager.canPlay(item)) {
|
||||
html += '<button is="paper-icon-button-light" class="' + btnCssClass + ' cardOverlayFab-primary" data-action="resume"><span class="material-icons cardOverlayButtonIcon cardOverlayButtonIcon-hover play_arrow"></span></button>';
|
||||
html += '<button is="paper-icon-button-light" class="' + btnCssClass + ' cardOverlayFab-primary" data-action="resume"><span class="material-icons cardOverlayButtonIcon cardOverlayButtonIcon-hover play_arrow" aria-hidden="true"></span></button>';
|
||||
}
|
||||
|
||||
html += '<div class="cardOverlayButton-br flex">';
|
||||
|
@ -1454,7 +1477,7 @@ import ServerConnections from '../ServerConnections';
|
|||
if (itemHelper.canMarkPlayed(item)) {
|
||||
/* eslint-disable-next-line @babel/no-unused-expressions */
|
||||
import('../../elements/emby-playstatebutton/emby-playstatebutton');
|
||||
html += '<button is="emby-playstatebutton" type="button" data-action="none" class="' + btnCssClass + '" data-id="' + item.Id + '" data-serverid="' + item.ServerId + '" data-itemtype="' + item.Type + '" data-played="' + (userData.Played) + '"><span class="material-icons cardOverlayButtonIcon cardOverlayButtonIcon-hover check"></span></button>';
|
||||
html += '<button is="emby-playstatebutton" type="button" data-action="none" class="' + btnCssClass + '" data-id="' + item.Id + '" data-serverid="' + item.ServerId + '" data-itemtype="' + item.Type + '" data-played="' + (userData.Played) + '"><span class="material-icons cardOverlayButtonIcon cardOverlayButtonIcon-hover check" aria-hidden="true"></span></button>';
|
||||
}
|
||||
|
||||
if (itemHelper.canRate(item)) {
|
||||
|
@ -1462,10 +1485,10 @@ import ServerConnections from '../ServerConnections';
|
|||
|
||||
/* eslint-disable-next-line @babel/no-unused-expressions */
|
||||
import('../../elements/emby-ratingbutton/emby-ratingbutton');
|
||||
html += '<button is="emby-ratingbutton" type="button" data-action="none" class="' + btnCssClass + '" data-id="' + item.Id + '" data-serverid="' + item.ServerId + '" data-itemtype="' + item.Type + '" data-likes="' + likes + '" data-isfavorite="' + (userData.IsFavorite) + '"><span class="material-icons cardOverlayButtonIcon cardOverlayButtonIcon-hover favorite"></span></button>';
|
||||
html += '<button is="emby-ratingbutton" type="button" data-action="none" class="' + btnCssClass + '" data-id="' + item.Id + '" data-serverid="' + item.ServerId + '" data-itemtype="' + item.Type + '" data-likes="' + likes + '" data-isfavorite="' + (userData.IsFavorite) + '"><span class="material-icons cardOverlayButtonIcon cardOverlayButtonIcon-hover favorite" aria-hidden="true"></span></button>';
|
||||
}
|
||||
|
||||
html += '<button is="paper-icon-button-light" class="' + btnCssClass + '" data-action="menu"><span class="material-icons cardOverlayButtonIcon cardOverlayButtonIcon-hover more_vert"></span></button>';
|
||||
html += '<button is="paper-icon-button-light" class="' + btnCssClass + '" data-action="menu"><span class="material-icons cardOverlayButtonIcon cardOverlayButtonIcon-hover more_vert" aria-hidden="true"></span></button>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
|
||||
|
@ -1480,35 +1503,40 @@ import ServerConnections from '../ServerConnections';
|
|||
*/
|
||||
export function getDefaultText(item, options) {
|
||||
if (item.CollectionType) {
|
||||
return '<span class="cardImageIcon material-icons ' + imageHelper.getLibraryIcon(item.CollectionType) + '"></span>';
|
||||
return '<span class="cardImageIcon material-icons ' + imageHelper.getLibraryIcon(item.CollectionType) + '" aria-hidden="true"></span>';
|
||||
}
|
||||
|
||||
switch (item.Type) {
|
||||
case 'MusicAlbum':
|
||||
return '<span class="cardImageIcon material-icons album"></span>';
|
||||
return '<span class="cardImageIcon material-icons album" aria-hidden="true"></span>';
|
||||
case 'MusicArtist':
|
||||
case 'Person':
|
||||
return '<span class="cardImageIcon material-icons person"></span>';
|
||||
return '<span class="cardImageIcon material-icons person" aria-hidden="true"></span>';
|
||||
case 'Audio':
|
||||
return '<span class="cardImageIcon material-icons audiotrack"></span>';
|
||||
return '<span class="cardImageIcon material-icons audiotrack" aria-hidden="true"></span>';
|
||||
case 'Movie':
|
||||
return '<span class="cardImageIcon material-icons movie"></span>';
|
||||
return '<span class="cardImageIcon material-icons movie" aria-hidden="true"></span>';
|
||||
case 'Episode':
|
||||
case 'Series':
|
||||
return '<span class="cardImageIcon material-icons tv"></span>';
|
||||
return '<span class="cardImageIcon material-icons tv" aria-hidden="true"></span>';
|
||||
case 'Program':
|
||||
return '<span class="cardImageIcon material-icons live_tv" aria-hidden="true"></span>';
|
||||
case 'Book':
|
||||
return '<span class="cardImageIcon material-icons book"></span>';
|
||||
return '<span class="cardImageIcon material-icons book" aria-hidden="true"></span>';
|
||||
case 'Folder':
|
||||
return '<span class="cardImageIcon material-icons folder"></span>';
|
||||
return '<span class="cardImageIcon material-icons folder" aria-hidden="true"></span>';
|
||||
case 'BoxSet':
|
||||
return '<span class="cardImageIcon material-icons collections"></span>';
|
||||
return '<span class="cardImageIcon material-icons collections" aria-hidden="true"></span>';
|
||||
case 'Playlist':
|
||||
return '<span class="cardImageIcon material-icons view_list"></span>';
|
||||
return '<span class="cardImageIcon material-icons view_list" aria-hidden="true"></span>';
|
||||
case 'Photo':
|
||||
return '<span class="cardImageIcon material-icons photo" aria-hidden="true"></span>';
|
||||
case 'PhotoAlbum':
|
||||
return '<span class="cardImageIcon material-icons photo_album"></span>';
|
||||
return '<span class="cardImageIcon material-icons photo_album" aria-hidden="true"></span>';
|
||||
}
|
||||
|
||||
if (options && options.defaultCardImageIcon) {
|
||||
return '<span class="cardImageIcon material-icons ' + options.defaultCardImageIcon + '"></span>';
|
||||
if (options?.defaultCardImageIcon) {
|
||||
return '<span class="cardImageIcon material-icons ' + options.defaultCardImageIcon + '" aria-hidden="true"></span>';
|
||||
}
|
||||
|
||||
const defaultName = isUsingLiveTvNaming(item) ? item.Name : itemHelper.getDisplayName(item);
|
||||
|
@ -1605,7 +1633,7 @@ import ServerConnections from '../ServerConnections';
|
|||
indicatorsElem = ensureIndicators(card, indicatorsElem);
|
||||
indicatorsElem.appendChild(playedIndicator);
|
||||
}
|
||||
playedIndicator.innerHTML = '<span class="material-icons indicatorIcon check"></span>';
|
||||
playedIndicator.innerHTML = '<span class="material-icons indicatorIcon check" aria-hidden="true"></span>';
|
||||
} else {
|
||||
playedIndicator = card.querySelector('.playedIndicator');
|
||||
if (playedIndicator) {
|
||||
|
@ -1688,7 +1716,7 @@ import ServerConnections from '../ServerConnections';
|
|||
const icon = cell.querySelector('.timerIndicator');
|
||||
if (!icon) {
|
||||
const indicatorsElem = ensureIndicators(cell);
|
||||
indicatorsElem.insertAdjacentHTML('beforeend', '<span class="material-icons timerIndicator indicatorIcon fiber_manual_record"></span>');
|
||||
indicatorsElem.insertAdjacentHTML('beforeend', '<span class="material-icons timerIndicator indicatorIcon fiber_manual_record" aria-hidden="true"></span>');
|
||||
}
|
||||
cell.setAttribute('data-timerid', newTimerId);
|
||||
}
|
||||
|
|
|
@ -94,7 +94,7 @@ import ServerConnections from '../ServerConnections';
|
|||
let cardImageContainer = imgUrl ? (`<div class="${cardImageContainerClass} lazy" data-src="${imgUrl}">`) : (`<div class="${cardImageContainerClass}">`);
|
||||
|
||||
if (!imgUrl) {
|
||||
cardImageContainer += '<span class="material-icons cardImageIcon local_movies"></span>';
|
||||
cardImageContainer += '<span class="material-icons cardImageIcon local_movies" aria-hidden="true"></span>';
|
||||
}
|
||||
|
||||
let nameHtml = '';
|
||||
|
|
|
@ -72,7 +72,7 @@ export default class channelMapper {
|
|||
function getTunerChannelHtml(channel, providerName) {
|
||||
let html = '';
|
||||
html += '<div class="listItem">';
|
||||
html += '<span class="material-icons listItemIcon dvr"></span>';
|
||||
html += '<span class="material-icons listItemIcon dvr" aria-hidden="true"></span>';
|
||||
html += '<div class="listItemBody two-line">';
|
||||
html += '<h3 class="listItemBodyText">';
|
||||
html += channel.Name;
|
||||
|
@ -85,7 +85,7 @@ export default class channelMapper {
|
|||
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
html += `<button class="btnMap autoSize" is="paper-icon-button-light" type="button" data-id="${channel.Id}" data-providerid="${channel.ProviderChannelId}"><span class="material-icons mode_edit"></span></button>`;
|
||||
html += `<button class="btnMap autoSize" is="paper-icon-button-light" type="button" data-id="${channel.Id}" data-providerid="${channel.ProviderChannelId}"><span class="material-icons mode_edit" aria-hidden="true"></span></button>`;
|
||||
return html += '</div>';
|
||||
}
|
||||
|
||||
|
@ -127,7 +127,7 @@ export default class channelMapper {
|
|||
let html = '';
|
||||
const title = globalize.translate('MapChannels');
|
||||
html += '<div class="formDialogHeader">';
|
||||
html += '<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><span class="material-icons arrow_back"></span></button>';
|
||||
html += '<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><span class="material-icons arrow_back" aria-hidden="true"></span></button>';
|
||||
html += '<h3 class="formDialogHeaderTitle">';
|
||||
html += title;
|
||||
html += '</h3>';
|
||||
|
|
|
@ -229,7 +229,7 @@ import toast from '../toast/toast';
|
|||
const title = items.length ? globalize.translate('HeaderAddToCollection') : globalize.translate('NewCollection');
|
||||
|
||||
html += '<div class="formDialogHeader">';
|
||||
html += '<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><span class="material-icons arrow_back"></span></button>';
|
||||
html += '<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><span class="material-icons arrow_back" aria-hidden="true"></span></button>';
|
||||
html += '<h3 class="formDialogHeaderTitle">';
|
||||
html += title;
|
||||
html += '</h3>';
|
||||
|
|
58
src/components/dashboard/users/AccessScheduleList.tsx
Normal file
58
src/components/dashboard/users/AccessScheduleList.tsx
Normal file
|
@ -0,0 +1,58 @@
|
|||
import React, { FunctionComponent } from 'react';
|
||||
import datetime from '../../../scripts/datetime';
|
||||
import globalize from '../../../scripts/globalize';
|
||||
|
||||
const createButtonElement = (index: number) => ({
|
||||
__html: `<button
|
||||
type='button'
|
||||
is='paper-icon-button-light'
|
||||
class='btnDelete listItemButton'
|
||||
data-index='${index}'
|
||||
>
|
||||
<span class='material-icons delete' aria-hidden='true' />
|
||||
</button>`
|
||||
});
|
||||
|
||||
type IProps = {
|
||||
index: number;
|
||||
Id: number;
|
||||
DayOfWeek?: string;
|
||||
StartHour?: number ;
|
||||
EndHour?: number;
|
||||
}
|
||||
|
||||
function getDisplayTime(hours = 0) {
|
||||
let minutes = 0;
|
||||
const pct = hours % 1;
|
||||
|
||||
if (pct) {
|
||||
minutes = Math.floor(60 * pct);
|
||||
}
|
||||
|
||||
return datetime.getDisplayTime(new Date(2000, 1, 1, hours, minutes, 0, 0));
|
||||
}
|
||||
|
||||
const AccessScheduleList: FunctionComponent<IProps> = ({index, DayOfWeek, StartHour, EndHour}: IProps) => {
|
||||
return (
|
||||
<div
|
||||
className='liSchedule listItem'
|
||||
data-day={ DayOfWeek}
|
||||
data-start={ StartHour}
|
||||
data-end={ EndHour}
|
||||
>
|
||||
<div className='listItemBody two-line'>
|
||||
<h3 className='listItemBodyText'>
|
||||
{globalize.translate(DayOfWeek)}
|
||||
</h3>
|
||||
<div className='listItemBodyText secondary'>
|
||||
{getDisplayTime(StartHour) + ' - ' + getDisplayTime(EndHour)}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
dangerouslySetInnerHTML={createButtonElement(index)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AccessScheduleList;
|
36
src/components/dashboard/users/BlockedTagList.tsx
Normal file
36
src/components/dashboard/users/BlockedTagList.tsx
Normal file
|
@ -0,0 +1,36 @@
|
|||
import React, { FunctionComponent } from 'react';
|
||||
|
||||
const createButtonElement = (tag?: string) => ({
|
||||
__html: `<button
|
||||
type='button'
|
||||
is='paper-icon-button-light'
|
||||
class='blockedTag btnDeleteTag listItemButton'
|
||||
data-tag='${tag}'
|
||||
>
|
||||
<span class='material-icons delete' aria-hidden='true' />
|
||||
</button>`
|
||||
});
|
||||
|
||||
type IProps = {
|
||||
tag?: string;
|
||||
}
|
||||
|
||||
const BlockedTagList: FunctionComponent<IProps> = ({tag}: IProps) => {
|
||||
return (
|
||||
<div className='paperList'>
|
||||
<div className='listItem'>
|
||||
<div className='listItemBody'>
|
||||
<h3 className='listItemBodyText'>
|
||||
{tag}
|
||||
</h3>
|
||||
</div>
|
||||
<div
|
||||
dangerouslySetInnerHTML={createButtonElement(tag)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BlockedTagList;
|
|
@ -1,7 +1,7 @@
|
|||
import React, { FunctionComponent } from 'react';
|
||||
import globalize from '../../../scripts/globalize';
|
||||
|
||||
const createButtonElement = ({ type, className, title }) => ({
|
||||
const createButtonElement = ({ type, className, title }: { type?: string, className?: string, title?: string }) => ({
|
||||
__html: `<button
|
||||
is="emby-button"
|
||||
type="${type}"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { FunctionComponent } from 'react';
|
||||
import globalize from '../../../scripts/globalize';
|
||||
|
||||
const createCheckBoxElement = ({ labelClassName, type, className, title }) => ({
|
||||
const createCheckBoxElement = ({ labelClassName, type, className, title }: { labelClassName?: string, type?: string, className?: string, title?: string }) => ({
|
||||
__html: `<label class="${labelClassName}">
|
||||
<input
|
||||
is="emby-checkbox"
|
||||
|
|
|
@ -4,30 +4,31 @@ type IProps = {
|
|||
className?: string;
|
||||
Name?: string;
|
||||
Id?: string;
|
||||
ItemType?: string;
|
||||
AppName?: string;
|
||||
checkedAttribute?: string;
|
||||
}
|
||||
|
||||
const createCheckBoxElement = ({className, Name, Id, AppName, checkedAttribute}) => ({
|
||||
const createCheckBoxElement = ({className, Name, dataAttributes, AppName, checkedAttribute}: {className?: string, Name?: string, dataAttributes?: string, AppName?: string, checkedAttribute?: string}) => ({
|
||||
__html: `<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
is="emby-checkbox"
|
||||
class="${className}"
|
||||
data-id="${Id}" ${checkedAttribute}
|
||||
${dataAttributes} ${checkedAttribute}
|
||||
/>
|
||||
<span>${Name} ${AppName}</span>
|
||||
</label>`
|
||||
});
|
||||
|
||||
const CheckBoxListItem: FunctionComponent<IProps> = ({className, Name, Id, AppName, checkedAttribute}: IProps) => {
|
||||
const CheckBoxListItem: FunctionComponent<IProps> = ({className, Name, Id, ItemType, AppName, checkedAttribute}: IProps) => {
|
||||
return (
|
||||
<div
|
||||
className='sectioncheckbox'
|
||||
dangerouslySetInnerHTML={createCheckBoxElement({
|
||||
className: className,
|
||||
Name: Name,
|
||||
Id: Id,
|
||||
dataAttributes: ItemType ? `data-itemtype='${ItemType}'` : `data-id='${Id}'`,
|
||||
AppName: AppName ? `- ${AppName}` : '',
|
||||
checkedAttribute: checkedAttribute
|
||||
})}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { FunctionComponent } from 'react';
|
||||
import globalize from '../../../scripts/globalize';
|
||||
|
||||
const createInputElement = ({ type, id, label, options }) => ({
|
||||
const createInputElement = ({ type, id, label, options }: { type?: string, id?: string, label?: string, options?: string }) => ({
|
||||
__html: `<input
|
||||
is="emby-input"
|
||||
type="${type}"
|
||||
|
|
|
@ -6,7 +6,7 @@ type IProps = {
|
|||
className?: string;
|
||||
}
|
||||
|
||||
const createLinkElement = ({ className, title }) => ({
|
||||
const createLinkElement = ({ className, title }: IProps) => ({
|
||||
__html: `<a
|
||||
is="emby-linkbutton"
|
||||
class="${className}"
|
||||
|
|
|
@ -5,7 +5,7 @@ type IProps = {
|
|||
activeTab: string;
|
||||
}
|
||||
|
||||
const createLinkElement = ({ activeTab }) => ({
|
||||
const createLinkElement = (activeTab: string) => ({
|
||||
__html: `<a href="#"
|
||||
is="emby-linkbutton"
|
||||
data-role="button"
|
||||
|
@ -42,9 +42,7 @@ const SectionTabs: FunctionComponent<IProps> = ({activeTab}: IProps) => {
|
|||
data-role='controlgroup'
|
||||
data-type='horizontal'
|
||||
className='localnav'
|
||||
dangerouslySetInnerHTML={createLinkElement({
|
||||
activeTab: activeTab
|
||||
})}
|
||||
dangerouslySetInnerHTML={createLinkElement(activeTab)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,23 +1,24 @@
|
|||
import React, { FunctionComponent } from 'react';
|
||||
import globalize from '../../../scripts/globalize';
|
||||
|
||||
const createButtonElement = ({ className, title, icon }) => ({
|
||||
type IProps = {
|
||||
title: string;
|
||||
className?: string;
|
||||
icon: string,
|
||||
}
|
||||
|
||||
const createButtonElement = ({ className, title, icon }: { className?: string, title: string, icon: string }) => ({
|
||||
__html: `<button
|
||||
is="emby-button"
|
||||
type="button"
|
||||
class="${className}"
|
||||
style="margin-left:1em;"
|
||||
title="${title}">
|
||||
<span class="material-icons ${icon}"></span>
|
||||
title="${title}"
|
||||
>
|
||||
<span class="material-icons ${icon}" aria-hidden="true"></span>
|
||||
</button>`
|
||||
});
|
||||
|
||||
type IProps = {
|
||||
title?: string;
|
||||
className?: string;
|
||||
icon?: string,
|
||||
}
|
||||
|
||||
const SectionTitleButtonElement: FunctionComponent<IProps> = ({ className, title, icon }: IProps) => {
|
||||
return (
|
||||
<div
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { FunctionComponent } from 'react';
|
||||
import globalize from '../../../scripts/globalize';
|
||||
|
||||
const createLinkElement = ({ className, title, href }) => ({
|
||||
const createLinkElement = ({ className, title, href }: { className?: string, title?: string, href?: string }) => ({
|
||||
__html: `<a
|
||||
is="emby-linkbutton"
|
||||
rel="noopener noreferrer"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { FunctionComponent } from 'react';
|
||||
import globalize from '../../../scripts/globalize';
|
||||
|
||||
const createSelectElement = ({ className, label, option }) => ({
|
||||
const createSelectElement = ({ className, label, option }: { className?: string, label: string, option: string[] }) => ({
|
||||
__html: `<select
|
||||
class="${className}"
|
||||
is="emby-select"
|
||||
|
|
46
src/components/dashboard/users/SelectMaxParentalRating.tsx
Normal file
46
src/components/dashboard/users/SelectMaxParentalRating.tsx
Normal file
|
@ -0,0 +1,46 @@
|
|||
import React, { FunctionComponent } from 'react';
|
||||
import globalize from '../../../scripts/globalize';
|
||||
|
||||
const createSelectElement = ({ className, label, option }: { className?: string, label: string, option: string }) => ({
|
||||
__html: `<select
|
||||
class="${className}"
|
||||
is="emby-select"
|
||||
label="${label}"
|
||||
>
|
||||
<option value=''></option>
|
||||
${option}
|
||||
</select>`
|
||||
});
|
||||
|
||||
type RatingsArr = {
|
||||
Name: string;
|
||||
Value: number;
|
||||
}
|
||||
|
||||
type IProps = {
|
||||
className?: string;
|
||||
label?: string;
|
||||
parentalRatings: RatingsArr[];
|
||||
}
|
||||
|
||||
const SelectMaxParentalRating: FunctionComponent<IProps> = ({ className, label, parentalRatings }: IProps) => {
|
||||
const renderOption = () => {
|
||||
let content = '';
|
||||
for (const rating of parentalRatings) {
|
||||
content += `<option value='${rating.Value}'>${rating.Name}</option>`;
|
||||
}
|
||||
return content;
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
dangerouslySetInnerHTML={createSelectElement({
|
||||
className: className,
|
||||
label: globalize.translate(label),
|
||||
option: renderOption()
|
||||
})}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default SelectMaxParentalRating;
|
|
@ -1,9 +1,9 @@
|
|||
import React, { FunctionComponent } from 'react';
|
||||
import globalize from '../../../scripts/globalize';
|
||||
|
||||
const createSelectElement = ({ className, id, label }) => ({
|
||||
const createSelectElement = ({ className, id, label }: { className?: string, id?: string, label: string }) => ({
|
||||
__html: `<select
|
||||
className="${className}"
|
||||
class="${className}"
|
||||
is="emby-select"
|
||||
id="${id}"
|
||||
label="${label}"
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import { UserDto } from '@thornbill/jellyfin-sdk/dist/generated-client';
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import { localeWithSuffix } from '../../../scripts/dfnshelper';
|
||||
import globalize from '../../../scripts/globalize';
|
||||
import cardBuilder from '../../cardbuilder/cardBuilder';
|
||||
|
||||
const createLinkElement = ({ user, renderImgUrl }) => ({
|
||||
const createLinkElement = ({ user, renderImgUrl }: { user: UserDto, renderImgUrl: string }) => ({
|
||||
__html: `<a
|
||||
is="emby-linkbutton"
|
||||
class="cardContent"
|
||||
|
@ -20,15 +21,15 @@ const createButtonElement = () => ({
|
|||
type="button"
|
||||
class="btnUserMenu flex-shrink-zero"
|
||||
>
|
||||
<span class="material-icons more_vert"></span>
|
||||
<span class="material-icons more_vert" aria-hidden="true"></span>
|
||||
</button>`
|
||||
});
|
||||
|
||||
type IProps = {
|
||||
user?: Record<string, any>;
|
||||
user?: UserDto;
|
||||
}
|
||||
|
||||
const getLastSeenText = (lastActivityDate) => {
|
||||
const getLastSeenText = (lastActivityDate?: string | null) => {
|
||||
if (lastActivityDate) {
|
||||
return globalize.translate('LastSeen', formatDistanceToNow(Date.parse(lastActivityDate), localeWithSuffix));
|
||||
}
|
||||
|
@ -36,16 +37,16 @@ const getLastSeenText = (lastActivityDate) => {
|
|||
return '';
|
||||
};
|
||||
|
||||
const UserCardBox: FunctionComponent<IProps> = ({ user = [] }: IProps) => {
|
||||
const UserCardBox: FunctionComponent<IProps> = ({ user = {} }: IProps) => {
|
||||
let cssClass = 'card squareCard scalableCard squareCard-scalable';
|
||||
|
||||
if (user.Policy.IsDisabled) {
|
||||
if (user.Policy?.IsDisabled) {
|
||||
cssClass += ' grayscale';
|
||||
}
|
||||
|
||||
let imgUrl;
|
||||
|
||||
if (user.PrimaryImageTag) {
|
||||
if (user.PrimaryImageTag && user.Id) {
|
||||
imgUrl = window.ApiClient.getUserImageUrl(user.Id, {
|
||||
width: 300,
|
||||
tag: user.PrimaryImageTag,
|
||||
|
@ -55,7 +56,7 @@ const UserCardBox: FunctionComponent<IProps> = ({ user = [] }: IProps) => {
|
|||
|
||||
let imageClass = 'cardImage';
|
||||
|
||||
if (user.Policy.IsDisabled) {
|
||||
if (user.Policy?.IsDisabled) {
|
||||
imageClass += ' disabledUser';
|
||||
}
|
||||
|
||||
|
@ -64,7 +65,7 @@ const UserCardBox: FunctionComponent<IProps> = ({ user = [] }: IProps) => {
|
|||
const renderImgUrl = imgUrl ?
|
||||
`<div class='${imageClass}' style='background-image:url(${imgUrl})'></div>` :
|
||||
`<div class='${imageClass} ${cardBuilder.getDefaultBackgroundClass(user.Name)} flex align-items-center justify-content-center'>
|
||||
<span class='material-icons cardImageIcon person'></span>
|
||||
<span class='material-icons cardImageIcon person' aria-hidden='true'></span>
|
||||
</div>`;
|
||||
|
||||
return (
|
||||
|
|
|
@ -77,7 +77,7 @@ function getItem(cssClass, type, path, name) {
|
|||
html += name;
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
html += '<span class="material-icons arrow_forward" style="font-size:inherit;"></span>';
|
||||
html += '<span class="material-icons arrow_forward" aria-hidden="true" style="font-size:inherit;"></span>';
|
||||
html += '</div>';
|
||||
return html;
|
||||
}
|
||||
|
@ -116,7 +116,7 @@ function getEditorHtml(options, systemInfo) {
|
|||
html += `<input is="emby-input" id="txtDirectoryPickerPath" type="text" required="required" ${readOnlyAttribute} label="${globalize.translate(labelKey)}"/>`;
|
||||
html += '</div>';
|
||||
if (!readOnlyAttribute) {
|
||||
html += `<button type="button" is="paper-icon-button-light" class="btnRefreshDirectories emby-input-iconbutton" title="${globalize.translate('Refresh')}"><span class="material-icons search"></span></button>`;
|
||||
html += `<button type="button" is="paper-icon-button-light" class="btnRefreshDirectories emby-input-iconbutton" title="${globalize.translate('Refresh')}"><span class="material-icons search" aria-hidden="true"></span></button>`;
|
||||
}
|
||||
html += '</div>';
|
||||
if (!readOnlyAttribute) {
|
||||
|
@ -264,7 +264,7 @@ class DirectoryBrowser {
|
|||
|
||||
let html = '';
|
||||
html += '<div class="formDialogHeader">';
|
||||
html += '<button is="paper-icon-button-light" class="btnCloseDialog autoSize" tabindex="-1"><span class="material-icons arrow_back"></span></button>';
|
||||
html += '<button is="paper-icon-button-light" class="btnCloseDialog autoSize" tabindex="-1"><span class="material-icons arrow_back" aria-hidden="true"></span></button>';
|
||||
html += '<h3 class="formDialogHeaderTitle">';
|
||||
html += options.header || globalize.translate('HeaderSelectPath');
|
||||
html += '</h3>';
|
||||
|
|
|
@ -100,16 +100,6 @@ import template from './displaySettings.template.html';
|
|||
context.querySelector('.fldDateTimeLocale').classList.add('hide');
|
||||
}
|
||||
|
||||
if (!browser.tizen && !browser.web0s) {
|
||||
context.querySelector('.fldBackdrops').classList.remove('hide');
|
||||
context.querySelector('.fldThemeSong').classList.remove('hide');
|
||||
context.querySelector('.fldThemeVideo').classList.remove('hide');
|
||||
} else {
|
||||
context.querySelector('.fldBackdrops').classList.add('hide');
|
||||
context.querySelector('.fldThemeSong').classList.add('hide');
|
||||
context.querySelector('.fldThemeVideo').classList.add('hide');
|
||||
}
|
||||
|
||||
fillThemes(context.querySelector('#selectTheme'), userSettings.theme());
|
||||
fillThemes(context.querySelector('#selectDashboardTheme'), userSettings.dashboardTheme());
|
||||
|
||||
|
|
|
@ -231,7 +231,7 @@
|
|||
<div class="fieldDescription checkboxFieldDescription">${EnableDetailsBannerHelp}</div>
|
||||
</div>
|
||||
|
||||
<div class="checkboxContainer checkboxContainer-withDescription fldBackdrops hide">
|
||||
<div class="checkboxContainer checkboxContainer-withDescription fldBackdrops">
|
||||
<label>
|
||||
<input type="checkbox" is="emby-checkbox" id="chkBackdrops" />
|
||||
<span>${Backdrops}</span>
|
||||
|
@ -239,7 +239,7 @@
|
|||
<div class="fieldDescription checkboxFieldDescription">${EnableBackdropsHelp}</div>
|
||||
</div>
|
||||
|
||||
<div class="checkboxContainer checkboxContainer-withDescription fldThemeSong hide">
|
||||
<div class="checkboxContainer checkboxContainer-withDescription fldThemeSong">
|
||||
<label>
|
||||
<input type="checkbox" is="emby-checkbox" id="chkThemeSong" />
|
||||
<span>${ThemeSongs}</span>
|
||||
|
@ -247,7 +247,7 @@
|
|||
<div class="fieldDescription checkboxFieldDescription">${EnableThemeSongsHelp}</div>
|
||||
</div>
|
||||
|
||||
<div class="checkboxContainer checkboxContainer-withDescription fldThemeVideo hide">
|
||||
<div class="checkboxContainer checkboxContainer-withDescription fldThemeVideo">
|
||||
<label>
|
||||
<input type="checkbox" is="emby-checkbox" id="chkThemeVideo" />
|
||||
<span>${ThemeVideos}</span>
|
||||
|
|
|
@ -145,7 +145,7 @@ import '../elements/emby-itemscontainer/emby-itemscontainer';
|
|||
html += '<h2 class="sectionTitle sectionTitle-cards">';
|
||||
html += globalize.translate(section.name);
|
||||
html += '</h2>';
|
||||
html += '<span class="material-icons chevron_right"></span>';
|
||||
html += '<span class="material-icons chevron_right" aria-hidden="true"></span>';
|
||||
html += '</a>';
|
||||
} else {
|
||||
html += '<h2 class="sectionTitle sectionTitle-cards">' + globalize.translate(section.name) + '</h2>';
|
||||
|
|
|
@ -228,7 +228,7 @@ class FilterMenu {
|
|||
let html = '';
|
||||
|
||||
html += '<div class="formDialogHeader">';
|
||||
html += '<button is="paper-icon-button-light" class="btnCancel hide-mouse-idle-tv" tabindex="-1"><span class="material-icons arrow_back"></span></button>';
|
||||
html += '<button is="paper-icon-button-light" class="btnCancel hide-mouse-idle-tv" tabindex="-1"><span class="material-icons arrow_back" aria-hidden="true"></span></button>';
|
||||
html += '<h3 class="formDialogHeaderTitle">${Filters}</h3>';
|
||||
|
||||
html += '</div>';
|
||||
|
|
|
@ -166,7 +166,6 @@ function Guide(options) {
|
|||
stopAutoRefresh();
|
||||
|
||||
Events.off(serverNotifications, 'TimerCreated', onTimerCreated);
|
||||
Events.off(serverNotifications, 'SeriesTimerCreated', onSeriesTimerCreated);
|
||||
Events.off(serverNotifications, 'TimerCancelled', onTimerCancelled);
|
||||
Events.off(serverNotifications, 'SeriesTimerCancelled', onSeriesTimerCancelled);
|
||||
|
||||
|
@ -409,7 +408,7 @@ function Guide(options) {
|
|||
let status;
|
||||
|
||||
if (item.Type === 'SeriesTimer') {
|
||||
return '<span class="material-icons programIcon seriesTimerIcon fiber_smart_record"></span>';
|
||||
return '<span class="material-icons programIcon seriesTimerIcon fiber_smart_record" aria-hidden="true"></span>';
|
||||
} else if (item.TimerId || item.SeriesTimerId) {
|
||||
status = item.Status || 'Cancelled';
|
||||
} else if (item.Type === 'Timer') {
|
||||
|
@ -420,13 +419,13 @@ function Guide(options) {
|
|||
|
||||
if (item.SeriesTimerId) {
|
||||
if (status !== 'Cancelled') {
|
||||
return '<span class="material-icons programIcon seriesTimerIcon fiber_smart_record"></span>';
|
||||
return '<span class="material-icons programIcon seriesTimerIcon fiber_smart_record" aria-hidden="true"></span>';
|
||||
}
|
||||
|
||||
return '<span class="material-icons programIcon seriesTimerIcon seriesTimerIcon-inactive fiber_smart_record"></span>';
|
||||
return '<span class="material-icons programIcon seriesTimerIcon seriesTimerIcon-inactive fiber_smart_record" aria-hidden="true"></span>';
|
||||
}
|
||||
|
||||
return '<span class="material-icons programIcon timerIcon fiber_manual_record"></span>';
|
||||
return '<span class="material-icons programIcon timerIcon fiber_manual_record" aria-hidden="true"></span>';
|
||||
}
|
||||
|
||||
function getChannelProgramsHtml(context, date, channel, programs, options, listInfo) {
|
||||
|
@ -537,7 +536,7 @@ function Guide(options) {
|
|||
|
||||
html += '<div class="' + guideProgramNameClass + '">';
|
||||
|
||||
html += '<div class="guide-programNameCaret hide"><span class="guideProgramNameCaretIcon material-icons keyboard_arrow_left"></span></div>';
|
||||
html += '<div class="guide-programNameCaret hide"><span class="guideProgramNameCaretIcon material-icons keyboard_arrow_left" aria-hidden="true"></span></div>';
|
||||
|
||||
html += '<div class="guideProgramNameText">' + program.Name;
|
||||
|
||||
|
@ -1057,9 +1056,6 @@ function Guide(options) {
|
|||
}
|
||||
}
|
||||
|
||||
function onSeriesTimerCreated() {
|
||||
}
|
||||
|
||||
function onTimerCancelled(e, apiClient, data) {
|
||||
const id = data.Id;
|
||||
// find guide cells by timer id, remove timer icon
|
||||
|
@ -1186,7 +1182,6 @@ function Guide(options) {
|
|||
Events.trigger(self, 'load');
|
||||
|
||||
Events.on(serverNotifications, 'TimerCreated', onTimerCreated);
|
||||
Events.on(serverNotifications, 'SeriesTimerCreated', onSeriesTimerCreated);
|
||||
Events.on(serverNotifications, 'TimerCancelled', onTimerCancelled);
|
||||
Events.on(serverNotifications, 'SeriesTimerCancelled', onSeriesTimerCancelled);
|
||||
|
||||
|
|
|
@ -177,7 +177,7 @@ import template from './homeScreenSettings.template.html';
|
|||
|
||||
currentHtml += `<div class="listItem viewItem" data-viewid="${view.Id}">`;
|
||||
|
||||
currentHtml += '<span class="material-icons listItemIcon folder_open"></span>';
|
||||
currentHtml += '<span class="material-icons listItemIcon folder_open" aria-hidden="true"></span>';
|
||||
|
||||
currentHtml += '<div class="listItemBody">';
|
||||
|
||||
|
@ -187,8 +187,8 @@ import template from './homeScreenSettings.template.html';
|
|||
|
||||
currentHtml += '</div>';
|
||||
|
||||
currentHtml += `<button type="button" is="paper-icon-button-light" class="btnViewItemUp btnViewItemMove autoSize" title="${globalize.translate('Up')}"><span class="material-icons keyboard_arrow_up"></span></button>`;
|
||||
currentHtml += `<button type="button" is="paper-icon-button-light" class="btnViewItemDown btnViewItemMove autoSize" title="${globalize.translate('Down')}"><span class="material-icons keyboard_arrow_down"></span></button>`;
|
||||
currentHtml += `<button type="button" is="paper-icon-button-light" class="btnViewItemUp btnViewItemMove autoSize" title="${globalize.translate('Up')}"><span class="material-icons keyboard_arrow_up" aria-hidden="true"></span></button>`;
|
||||
currentHtml += `<button type="button" is="paper-icon-button-light" class="btnViewItemDown btnViewItemMove autoSize" title="${globalize.translate('Down')}"><span class="material-icons keyboard_arrow_down" aria-hidden="true"></span></button>`;
|
||||
|
||||
currentHtml += '</div>';
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
<option value="resumebook">${HeaderContinueReading}</option>
|
||||
<option value="latestmedia">${HeaderLatestMedia}</option>
|
||||
<option value="nextup">${NextUp}</option>
|
||||
<option value="rewatching">${NextUpRewatching}</option>
|
||||
<option value="livetv">${LiveTV}</option>
|
||||
<option value="none">${None}</option>
|
||||
</select>
|
||||
|
@ -41,6 +42,7 @@
|
|||
<option value="resumebook">${HeaderContinueReading}</option>
|
||||
<option value="latestmedia">${HeaderLatestMedia}</option>
|
||||
<option value="nextup">${NextUp}</option>
|
||||
<option value="rewatching">${NextUpRewatching}</option>
|
||||
<option value="livetv">${LiveTV}</option>
|
||||
<option value="none">${None}</option>
|
||||
</select>
|
||||
|
@ -55,6 +57,7 @@
|
|||
<option value="resumebook">${HeaderContinueReading}</option>
|
||||
<option value="latestmedia">${HeaderLatestMedia}</option>
|
||||
<option value="nextup">${NextUp}</option>
|
||||
<option value="rewatching">${NextUpRewatching}</option>
|
||||
<option value="livetv">${LiveTV}</option>
|
||||
<option value="none">${None}</option>
|
||||
</select>
|
||||
|
@ -69,6 +72,7 @@
|
|||
<option value="resumebook">${HeaderContinueReading}</option>
|
||||
<option value="latestmedia">${HeaderLatestMedia}</option>
|
||||
<option value="nextup">${NextUp}</option>
|
||||
<option value="rewatching">${NextUpRewatching}</option>
|
||||
<option value="livetv">${LiveTV}</option>
|
||||
<option value="none">${None}</option>
|
||||
</select>
|
||||
|
@ -83,6 +87,7 @@
|
|||
<option value="resumebook">${HeaderContinueReading}</option>
|
||||
<option value="latestmedia">${HeaderLatestMedia}</option>
|
||||
<option value="nextup">${NextUp}</option>
|
||||
<option value="rewatching">${NextUpRewatching}</option>
|
||||
<option value="livetv">${LiveTV}</option>
|
||||
<option value="none">${None}</option>
|
||||
</select>
|
||||
|
@ -97,6 +102,7 @@
|
|||
<option value="resumebook">${HeaderContinueReading}</option>
|
||||
<option value="latestmedia">${HeaderLatestMedia}</option>
|
||||
<option value="nextup">${NextUp}</option>
|
||||
<option value="rewatching">${NextUpRewatching}</option>
|
||||
<option value="livetv">${LiveTV}</option>
|
||||
<option value="none">${None}</option>
|
||||
</select>
|
||||
|
@ -111,6 +117,7 @@
|
|||
<option value="resumebook">${HeaderContinueReading}</option>
|
||||
<option value="latestmedia">${HeaderLatestMedia}</option>
|
||||
<option value="nextup">${NextUp}</option>
|
||||
<option value="rewatching">${NextUpRewatching}</option>
|
||||
<option value="livetv">${LiveTV}</option>
|
||||
<option value="none">${None}</option>
|
||||
</select>
|
||||
|
|
|
@ -73,8 +73,7 @@ import ServerConnections from '../ServerConnections';
|
|||
|
||||
return Promise.all(promises).then(function () {
|
||||
return resume(elem, {
|
||||
refresh: true,
|
||||
returnPromise: false
|
||||
refresh: true
|
||||
});
|
||||
});
|
||||
} else {
|
||||
|
@ -127,10 +126,7 @@ import ServerConnections from '../ServerConnections';
|
|||
promises.push(elems[i].resume(options));
|
||||
}
|
||||
|
||||
const promise = Promise.all(promises);
|
||||
if (!options || options.returnPromise !== false) {
|
||||
return promise;
|
||||
}
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
function loadSection(page, apiClient, user, userSettings, userViews, allSections, index) {
|
||||
|
@ -151,6 +147,8 @@ import ServerConnections from '../ServerConnections';
|
|||
loadLatestLiveTvRecordings(elem, true, apiClient);
|
||||
} else if (section === 'nextup') {
|
||||
loadNextUp(elem, apiClient, userSettings);
|
||||
} else if (section === 'rewatching') {
|
||||
loadNextUp(elem, apiClient, userSettings, true);
|
||||
} else if (section === 'onnow' || section === 'livetv') {
|
||||
return loadOnNow(elem, apiClient, user);
|
||||
} else if (section === 'resumebook') {
|
||||
|
@ -196,7 +194,7 @@ import ServerConnections from '../ServerConnections';
|
|||
for (let i = 0, length = items.length; i < length; i++) {
|
||||
const item = items[i];
|
||||
const icon = imageHelper.getLibraryIcon(item.CollectionType);
|
||||
html += '<a is="emby-linkbutton" href="' + appRouter.getRouteUrl(item) + '" class="raised homeLibraryButton"><span class="material-icons homeLibraryIcon ' + icon + '"></span><span class="homeLibraryText">' + item.Name + '</span></a>';
|
||||
html += '<a is="emby-linkbutton" href="' + appRouter.getRouteUrl(item) + '" class="raised homeLibraryButton"><span class="material-icons homeLibraryIcon ' + icon + '" aria-hidden="true"></span><span class="homeLibraryText">' + item.Name + '</span></a>';
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
|
@ -287,7 +285,7 @@ import ServerConnections from '../ServerConnections';
|
|||
html += '<h2 class="sectionTitle sectionTitle-cards">';
|
||||
html += globalize.translate('LatestFromLibrary', parent.Name);
|
||||
html += '</h2>';
|
||||
html += '<span class="material-icons chevron_right"></span>';
|
||||
html += '<span class="material-icons chevron_right" aria-hidden="true"></span>';
|
||||
html += '</a>';
|
||||
} else {
|
||||
html += '<h2 class="sectionTitle sectionTitle-cards">' + globalize.translate('LatestFromLibrary', parent.Name) + '</h2>';
|
||||
|
@ -569,7 +567,7 @@ import ServerConnections from '../ServerConnections';
|
|||
html += '<h2 class="sectionTitle sectionTitle-cards">';
|
||||
html += globalize.translate('HeaderOnNow');
|
||||
html += '</h2>';
|
||||
html += '<span class="material-icons chevron_right"></span>';
|
||||
html += '<span class="material-icons chevron_right" aria-hidden="true"></span>';
|
||||
html += '</a>';
|
||||
} else {
|
||||
html += '<h2 class="sectionTitle sectionTitle-cards">' + globalize.translate('HeaderOnNow') + '</h2>';
|
||||
|
@ -600,20 +598,21 @@ import ServerConnections from '../ServerConnections';
|
|||
});
|
||||
}
|
||||
|
||||
function getNextUpFetchFn(serverId, userSettings) {
|
||||
function getNextUpFetchFn(serverId, userSettings, rewatching) {
|
||||
return function () {
|
||||
const apiClient = ServerConnections.getApiClient(serverId);
|
||||
const oldestDateForNextUp = new Date();
|
||||
oldestDateForNextUp.setDate(oldestDateForNextUp.getDate() - userSettings.maxDaysForNextUp());
|
||||
return apiClient.getNextUpEpisodes({
|
||||
Limit: enableScrollX() ? 24 : 15,
|
||||
Fields: 'PrimaryImageAspectRatio,DateCreated,BasicSyncInfo,Path',
|
||||
Fields: 'PrimaryImageAspectRatio,DateCreated,BasicSyncInfo,Path,MediaSourceCount',
|
||||
UserId: apiClient.getCurrentUserId(),
|
||||
ImageTypeLimit: 1,
|
||||
EnableImageTypes: 'Primary,Backdrop,Banner,Thumb',
|
||||
EnableTotalRecordCount: false,
|
||||
DisableFirstEpisode: false,
|
||||
NextUpDateCutoff: oldestDateForNextUp.toISOString()
|
||||
NextUpDateCutoff: oldestDateForNextUp.toISOString(),
|
||||
Rewatching: rewatching
|
||||
});
|
||||
};
|
||||
}
|
||||
|
@ -639,21 +638,32 @@ import ServerConnections from '../ServerConnections';
|
|||
};
|
||||
}
|
||||
|
||||
function loadNextUp(elem, apiClient, userSettings) {
|
||||
function loadNextUp(elem, apiClient, userSettings, rewatching = false) {
|
||||
let html = '';
|
||||
|
||||
html += '<div class="sectionTitleContainer sectionTitleContainer-cards padded-left">';
|
||||
if (!layoutManager.tv) {
|
||||
html += '<a is="emby-linkbutton" href="' + appRouter.getRouteUrl('nextup', {
|
||||
serverId: apiClient.serverId()
|
||||
serverId: apiClient.serverId(),
|
||||
rewatching: rewatching
|
||||
}) + '" class="button-flat button-flat-mini sectionTitleTextButton">';
|
||||
html += '<h2 class="sectionTitle sectionTitle-cards">';
|
||||
if (rewatching) {
|
||||
html += globalize.translate('NextUpRewatching');
|
||||
} else {
|
||||
html += globalize.translate('NextUp');
|
||||
}
|
||||
html += '</h2>';
|
||||
html += '<span class="material-icons chevron_right"></span>';
|
||||
html += '<span class="material-icons chevron_right" aria-hidden="true"></span>';
|
||||
html += '</a>';
|
||||
} else {
|
||||
html += '<h2 class="sectionTitle sectionTitle-cards">' + globalize.translate('NextUp') + '</h2>';
|
||||
html += '<h2 class="sectionTitle sectionTitle-cards">';
|
||||
if (rewatching) {
|
||||
html += globalize.translate('NextUpRewatching');
|
||||
} else {
|
||||
html += globalize.translate('NextUp');
|
||||
}
|
||||
html += '</h2>';
|
||||
}
|
||||
html += '</div>';
|
||||
|
||||
|
@ -673,7 +683,7 @@ import ServerConnections from '../ServerConnections';
|
|||
elem.innerHTML = html;
|
||||
|
||||
const itemsContainer = elem.querySelector('.itemsContainer');
|
||||
itemsContainer.fetchData = getNextUpFetchFn(apiClient.serverId(), userSettings);
|
||||
itemsContainer.fetchData = getNextUpFetchFn(apiClient.serverId(), userSettings, rewatching);
|
||||
itemsContainer.getItemsHtml = getNextUpItemsHtmlFn(userSettings.useEpisodeImagesInNextUpAndResume());
|
||||
itemsContainer.parentContainer = elem;
|
||||
}
|
||||
|
|
|
@ -197,24 +197,21 @@ import { Events } from 'jellyfin-apiclient';
|
|||
|
||||
export function playWithPromise(elem, onErrorFn) {
|
||||
try {
|
||||
const promise = elem.play();
|
||||
if (promise && promise.then) {
|
||||
// Chrome now returns a promise
|
||||
return promise.catch(function (e) {
|
||||
return elem.play()
|
||||
.catch((e) => {
|
||||
const errorName = (e.name || '').toLowerCase();
|
||||
// safari uses aborterror
|
||||
if (errorName === 'notallowederror' ||
|
||||
errorName === 'aborterror') {
|
||||
// swallow this error because the user can still click the play button on the video element
|
||||
onSuccessfulPlay(elem, onErrorFn);
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject();
|
||||
});
|
||||
} else {
|
||||
})
|
||||
.then(() => {
|
||||
onSuccessfulPlay(elem, onErrorFn);
|
||||
return Promise.resolve();
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('error calling video.play: ' + err);
|
||||
return Promise.reject();
|
||||
|
|
|
@ -31,11 +31,16 @@ import template from './imageDownloader.template.html';
|
|||
let browsableImageStartIndex = 0;
|
||||
let browsableImageType = 'Primary';
|
||||
let selectedProvider;
|
||||
let browsableParentId;
|
||||
|
||||
function getBaseRemoteOptions() {
|
||||
function getBaseRemoteOptions(page) {
|
||||
const options = {};
|
||||
|
||||
if (page.querySelector('#chkShowParentImages').checked && browsableParentId) {
|
||||
options.itemId = browsableParentId;
|
||||
} else {
|
||||
options.itemId = currentItemId;
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
@ -43,7 +48,7 @@ import template from './imageDownloader.template.html';
|
|||
function reloadBrowsableImages(page, apiClient) {
|
||||
loading.show();
|
||||
|
||||
const options = getBaseRemoteOptions();
|
||||
const options = getBaseRemoteOptions(page);
|
||||
|
||||
options.type = browsableImageType;
|
||||
options.startIndex = browsableImageStartIndex;
|
||||
|
@ -124,8 +129,8 @@ import template from './imageDownloader.template.html';
|
|||
if (showControls) {
|
||||
html += '<div data-role="controlgroup" data-type="horizontal" style="display:inline-block;">';
|
||||
|
||||
html += '<button is="paper-icon-button-light" title="' + globalize.translate('Previous') + '" class="btnPreviousPage autoSize" ' + (startIndex ? '' : 'disabled') + '><span class="material-icons arrow_back"></span></button>';
|
||||
html += '<button is="paper-icon-button-light" title="' + globalize.translate('Next') + '" class="btnNextPage autoSize" ' + (startIndex + limit >= totalRecordCount ? 'disabled' : '') + '><span class="material-icons arrow_forward"></span></button>';
|
||||
html += `<button is="paper-icon-button-light" title="${globalize.translate('Previous')}" class="btnPreviousPage autoSize" ${(startIndex ? '' : 'disabled')}><span class="material-icons arrow_back" aria-hidden="true"></span></button>`;
|
||||
html += `<button is="paper-icon-button-light" title="${globalize.translate('Next')}" class="btnNextPage autoSize" ${(startIndex + limit >= totalRecordCount ? 'disabled' : '')}><span class="material-icons arrow_forward" aria-hidden="true"></span></button>`;
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
|
@ -135,7 +140,7 @@ import template from './imageDownloader.template.html';
|
|||
}
|
||||
|
||||
function downloadRemoteImage(page, apiClient, url, type, provider) {
|
||||
const options = getBaseRemoteOptions();
|
||||
const options = getBaseRemoteOptions(page);
|
||||
|
||||
options.Type = type;
|
||||
options.ImageUrl = url;
|
||||
|
@ -259,7 +264,7 @@ import template from './imageDownloader.template.html';
|
|||
if (enableFooterButtons) {
|
||||
html += '<div class="cardText cardTextCentered">';
|
||||
|
||||
html += '<button is="paper-icon-button-light" class="btnDownloadRemoteImage autoSize" raised" title="' + globalize.translate('Download') + '"><span class="material-icons cloud_download"></span></button>';
|
||||
html += `<button is="paper-icon-button-light" class="btnDownloadRemoteImage autoSize" raised" title="${globalize.translate('Download')}"><span class="material-icons cloud_download" aria-hidden="true"></span></button>`;
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
|
@ -273,26 +278,31 @@ import template from './imageDownloader.template.html';
|
|||
return html;
|
||||
}
|
||||
|
||||
function reloadBrowsableImagesFirstPage(page, apiClient) {
|
||||
browsableImageStartIndex = 0;
|
||||
reloadBrowsableImages(page, apiClient);
|
||||
}
|
||||
|
||||
function initEditor(page, apiClient) {
|
||||
page.querySelector('#selectBrowsableImageType').addEventListener('change', function () {
|
||||
browsableImageType = this.value;
|
||||
browsableImageStartIndex = 0;
|
||||
selectedProvider = null;
|
||||
|
||||
reloadBrowsableImages(page, apiClient);
|
||||
reloadBrowsableImagesFirstPage(page, apiClient);
|
||||
});
|
||||
|
||||
page.querySelector('#selectImageProvider').addEventListener('change', function () {
|
||||
browsableImageStartIndex = 0;
|
||||
selectedProvider = this.value;
|
||||
|
||||
reloadBrowsableImages(page, apiClient);
|
||||
reloadBrowsableImagesFirstPage(page, apiClient);
|
||||
});
|
||||
|
||||
page.querySelector('#chkAllLanguages').addEventListener('change', function () {
|
||||
browsableImageStartIndex = 0;
|
||||
reloadBrowsableImagesFirstPage(page, apiClient);
|
||||
});
|
||||
|
||||
reloadBrowsableImages(page, apiClient);
|
||||
page.querySelector('#chkShowParentImages').addEventListener('change', function () {
|
||||
reloadBrowsableImagesFirstPage(page, apiClient);
|
||||
});
|
||||
|
||||
page.addEventListener('click', function (e) {
|
||||
|
@ -336,6 +346,10 @@ import template from './imageDownloader.template.html';
|
|||
scrollHelper.centerFocus.on(dlg, false);
|
||||
}
|
||||
|
||||
if (browsableParentId) {
|
||||
dlg.querySelector('#lblShowParentImages').classList.remove('hide');
|
||||
}
|
||||
|
||||
// Has to be assigned a z-index after the call to .open()
|
||||
dlg.addEventListener('close', onDialogClosed);
|
||||
|
||||
|
@ -366,7 +380,7 @@ import template from './imageDownloader.template.html';
|
|||
}
|
||||
}
|
||||
|
||||
export function show(itemId, serverId, itemType, imageType) {
|
||||
export function show(itemId, serverId, itemType, imageType, parentId) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
currentResolve = resolve;
|
||||
currentReject = reject;
|
||||
|
@ -374,6 +388,7 @@ export function show(itemId, serverId, itemType, imageType) {
|
|||
browsableImageStartIndex = 0;
|
||||
browsableImageType = imageType || 'Primary';
|
||||
selectedProvider = null;
|
||||
browsableParentId = parentId;
|
||||
showEditor(itemId, serverId, itemType);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div class="formDialogHeader">
|
||||
<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><span class="material-icons arrow_back"></span></button>
|
||||
<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><span class="material-icons arrow_back" aria-hidden="true"></span></button>
|
||||
<h3 class="formDialogHeaderTitle">
|
||||
${Search}
|
||||
</h3>
|
||||
|
@ -39,6 +39,10 @@
|
|||
<input id="chkAllLanguages" type="checkbox" is="emby-checkbox" />
|
||||
<span>${AllLanguages}</span>
|
||||
</label>
|
||||
<label id="lblShowParentImages" class="hide" style="margin: 0 0 0 1em;width:auto;">
|
||||
<input id="chkShowParentImages" type="checkbox" is="emby-checkbox" />
|
||||
<span>${ShowParentImages}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="availableImagesList vertical-wrap centered"></div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div class="formDialogHeader">
|
||||
<button type="button" is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><span class="material-icons arrow_back"></span></button>
|
||||
<button type="button" is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><span class="material-icons arrow_back" aria-hidden="true"></span></button>
|
||||
<h3 class="formDialogHeaderTitle">
|
||||
${HeaderImageOptions}
|
||||
</h3>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div class="formDialogHeader">
|
||||
<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><span class="material-icons arrow_back"></span></button>
|
||||
<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><span class="material-icons arrow_back" aria-hidden="true"></span></button>
|
||||
<h3 class="formDialogHeaderTitle">
|
||||
${HeaderUploadImage}
|
||||
</h3>
|
||||
|
@ -14,7 +14,7 @@
|
|||
<h2 style="margin:0;">${HeaderAddUpdateImage}</h2>
|
||||
|
||||
<button is="emby-button" type="button" class="raised raised-mini btnBrowse" style="margin-left:1.5em;">
|
||||
<span class="material-icons folder"></span>
|
||||
<span class="material-icons folder" aria-hidden="true"></span>
|
||||
<span>${Browse}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -165,21 +165,21 @@ import template from './imageeditor.template.html';
|
|||
if (index > 0) {
|
||||
html += '<button type="button" is="paper-icon-button-light" class="btnMoveImage autoSize" data-imagetype="' + image.ImageType + '" data-index="' + image.ImageIndex + '" data-newindex="' + (image.ImageIndex - 1) + '" title="' + globalize.translate('MoveLeft') + '"><span class="material-icons chevron_left"></span></button>';
|
||||
} else {
|
||||
html += '<button type="button" is="paper-icon-button-light" class="autoSize" disabled title="' + globalize.translate('MoveLeft') + '"><span class="material-icons chevron_left"></span></button>';
|
||||
html += '<button type="button" is="paper-icon-button-light" class="autoSize" disabled title="' + globalize.translate('MoveLeft') + '"><span class="material-icons chevron_left" aria-hidden="true"></span></button>';
|
||||
}
|
||||
|
||||
if (index < numImages - 1) {
|
||||
html += '<button type="button" is="paper-icon-button-light" class="btnMoveImage autoSize" data-imagetype="' + image.ImageType + '" data-index="' + image.ImageIndex + '" data-newindex="' + (image.ImageIndex + 1) + '" title="' + globalize.translate('MoveRight') + '"><span class="material-icons chevron_right"></span></button>';
|
||||
html += '<button type="button" is="paper-icon-button-light" class="btnMoveImage autoSize" data-imagetype="' + image.ImageType + '" data-index="' + image.ImageIndex + '" data-newindex="' + (image.ImageIndex + 1) + '" title="' + globalize.translate('MoveRight') + '"><span class="material-icons chevron_right" aria-hidden="true"></span></button>';
|
||||
} else {
|
||||
html += '<button type="button" is="paper-icon-button-light" class="autoSize" disabled title="' + globalize.translate('MoveRight') + '"><span class="material-icons chevron_right"></span></button>';
|
||||
html += '<button type="button" is="paper-icon-button-light" class="autoSize" disabled title="' + globalize.translate('MoveRight') + '"><span class="material-icons chevron_right" aria-hidden="true"></span></button>';
|
||||
}
|
||||
} else {
|
||||
if (imageProviders.length) {
|
||||
html += '<button type="button" is="paper-icon-button-light" data-imagetype="' + image.ImageType + '" class="btnSearchImages autoSize" title="' + globalize.translate('Search') + '"><span class="material-icons search"></span></button>';
|
||||
html += '<button type="button" is="paper-icon-button-light" data-imagetype="' + image.ImageType + '" class="btnSearchImages autoSize" title="' + globalize.translate('Search') + '"><span class="material-icons search" aria-hidden="true"></span></button>';
|
||||
}
|
||||
}
|
||||
|
||||
html += '<button type="button" is="paper-icon-button-light" data-imagetype="' + image.ImageType + '" data-index="' + (image.ImageIndex != null ? image.ImageIndex : 'null') + '" class="btnDeleteImage autoSize" title="' + globalize.translate('Delete') + '"><span class="material-icons delete"></span></button>';
|
||||
html += '<button type="button" is="paper-icon-button-light" data-imagetype="' + image.ImageType + '" data-index="' + (image.ImageIndex != null ? image.ImageIndex : 'null') + '" class="btnDeleteImage autoSize" title="' + globalize.translate('Delete') + '"><span class="material-icons delete" aria-hidden="true"></span></button>';
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
|
@ -280,7 +280,13 @@ import template from './imageeditor.template.html';
|
|||
|
||||
function showImageDownloader(page, imageType) {
|
||||
import('../imageDownloader/imageDownloader').then((ImageDownloader) => {
|
||||
ImageDownloader.show(currentItem.Id, currentItem.ServerId, currentItem.Type, imageType).then(function () {
|
||||
ImageDownloader.show(
|
||||
currentItem.Id,
|
||||
currentItem.ServerId,
|
||||
currentItem.Type,
|
||||
imageType,
|
||||
currentItem.Type == 'Season' ? currentItem.ParentId : null
|
||||
).then(function () {
|
||||
hasChanges = true;
|
||||
reload(page);
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div class="formDialogHeader">
|
||||
<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><span class="material-icons arrow_back"></span></button>
|
||||
<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><span class="material-icons arrow_back" aria-hidden="true"></span></button>
|
||||
<h3 class="formDialogHeaderTitle">
|
||||
${HeaderEditImages}
|
||||
</h3>
|
||||
|
@ -12,10 +12,10 @@
|
|||
<div class="imageEditor-buttons first-imageEditor-buttons">
|
||||
<h2 style="margin:0;">${Images}</h2>
|
||||
<button type="button" is="emby-button" class="btnBrowseAllImages fab mini autoSize" style="margin-left: 1em;">
|
||||
<span class="material-icons search"></span>
|
||||
<span class="material-icons search" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button type="button" is="emby-button" class="btnOpenUploadMenu fab mini hide" style="margin-left: .5em;">
|
||||
<span class="material-icons add"></span>
|
||||
<span class="material-icons add" aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div id="images" class="itemsContainer vertical-wrap">
|
||||
|
@ -27,10 +27,10 @@
|
|||
<div class="imageEditor-buttons">
|
||||
<h2 style="margin:0;">${Backdrops}</h2>
|
||||
<button type="button" is="emby-button" class="btnBrowseAllImages fab mini autoSize" style="margin-left: 1em;" data-imagetype="Backdrop">
|
||||
<span class="material-icons search"></span>
|
||||
<span class="material-icons search" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button type="button" is="emby-button" class="btnOpenUploadMenu fab mini hide" style="margin-left: .5em;" data-imagetype="Backdrop">
|
||||
<span class="material-icons add"></span>
|
||||
<span class="material-icons add" aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div id="backdrops" class="itemsContainer vertical-wrap">
|
||||
|
@ -42,10 +42,10 @@
|
|||
<div class="imageEditor-buttons">
|
||||
<h2 style="margin: 0;">${Screenshots}</h2>
|
||||
<button type="button" is="emby-button" class="btnBrowseAllImages fab mini autoSize" style="margin-left: 1em;" data-imagetype="Screenshot">
|
||||
<span class="material-icons search"></span>
|
||||
<span class="material-icons search" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button type="button" is="emby-button" class="btnOpenUploadMenu fab mini hide" style="margin-left: .5em;" data-imagetype="Screenshot">
|
||||
<span class="material-icons add"></span>
|
||||
<span class="material-icons add" aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div id="screenshots" class="itemsContainer vertical-wrap">
|
||||
|
|
|
@ -95,9 +95,12 @@ worker.addEventListener(
|
|||
const elem = event.target;
|
||||
requestAnimationFrame(() => {
|
||||
const canvas = elem.previousSibling;
|
||||
if (elem.classList.contains('blurhashed') && canvas && canvas.tagName === 'CANVAS') {
|
||||
if (elem.classList.contains('blurhashed') && canvas?.tagName === 'CANVAS') {
|
||||
canvas.classList.add('lazy-hidden');
|
||||
}
|
||||
|
||||
// HACK: Hide the content of the card padder
|
||||
elem.parentNode?.querySelector('.cardPadder')?.classList.add('lazy-hidden-children');
|
||||
});
|
||||
elem.removeEventListener('animationend', onAnimationEnd);
|
||||
}
|
||||
|
@ -135,10 +138,13 @@ worker.addEventListener(
|
|||
function emptyImageElement(elem) {
|
||||
elem.removeEventListener('animationend', onAnimationEnd);
|
||||
const canvas = elem.previousSibling;
|
||||
if (canvas && canvas.tagName === 'CANVAS') {
|
||||
if (canvas?.tagName === 'CANVAS') {
|
||||
canvas.classList.remove('lazy-hidden');
|
||||
}
|
||||
|
||||
// HACK: Unhide the content of the card padder
|
||||
elem.parentNode?.querySelector('.cardPadder')?.classList.remove('lazy-hidden-children');
|
||||
|
||||
let url;
|
||||
|
||||
if (elem.tagName !== 'IMG') {
|
||||
|
|
|
@ -18,7 +18,8 @@
|
|||
animation: fadein 0.1s;
|
||||
}
|
||||
|
||||
.lazy-hidden {
|
||||
.lazy-hidden,
|
||||
.lazy-hidden-children * {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -88,7 +88,7 @@ export function getPlayedIndicatorHtml(item) {
|
|||
}
|
||||
|
||||
if (userData.PlayedPercentage && userData.PlayedPercentage >= 100 || (userData.Played)) {
|
||||
return '<div class="playedIndicator indicator"><span class="material-icons indicatorIcon check"></span></div>';
|
||||
return '<div class="playedIndicator indicator"><span class="material-icons indicatorIcon check" aria-hidden="true"></span></div>';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -109,7 +109,7 @@ export function getTimerIndicator(item) {
|
|||
let status;
|
||||
|
||||
if (item.Type === 'SeriesTimer') {
|
||||
return '<span class="material-icons timerIndicator indicatorIcon fiber_smart_record"></span>';
|
||||
return '<span class="material-icons timerIndicator indicatorIcon fiber_smart_record" aria-hidden="true"></span>';
|
||||
} else if (item.TimerId || item.SeriesTimerId) {
|
||||
status = item.Status || 'Cancelled';
|
||||
} else if (item.Type === 'Timer') {
|
||||
|
@ -120,20 +120,20 @@ export function getTimerIndicator(item) {
|
|||
|
||||
if (item.SeriesTimerId) {
|
||||
if (status !== 'Cancelled') {
|
||||
return '<span class="material-icons timerIndicator indicatorIcon fiber_smart_record"></span>';
|
||||
return '<span class="material-icons timerIndicator indicatorIcon fiber_smart_record" aria-hidden="true"></span>';
|
||||
}
|
||||
|
||||
return '<span class="material-icons timerIndicator timerIndicator-inactive indicatorIcon fiber_smart_record"></span>';
|
||||
return '<span class="material-icons timerIndicator timerIndicator-inactive indicatorIcon fiber_smart_record" aria-hidden="true"></span>';
|
||||
}
|
||||
|
||||
return '<span class="material-icons timerIndicator indicatorIcon fiber_manual_record"></span>';
|
||||
return '<span class="material-icons timerIndicator indicatorIcon fiber_manual_record" aria-hidden="true"></span>';
|
||||
}
|
||||
|
||||
export function getSyncIndicator(item) {
|
||||
if (item.SyncPercent === 100) {
|
||||
return '<div class="syncIndicator indicator fullSyncIndicator"><span class="material-icons indicatorIcon file_download"></span></div>';
|
||||
return '<div class="syncIndicator indicator fullSyncIndicator"><span class="material-icons indicatorIcon file_download" aria-hidden="true"></span></div>';
|
||||
} else if (item.SyncPercent != null) {
|
||||
return '<div class="syncIndicator indicator emptySyncIndicator"><span class="material-icons indicatorIcon file_download"></span></div>';
|
||||
return '<div class="syncIndicator indicator emptySyncIndicator"><span class="material-icons indicatorIcon file_download" aria-hidden="true"></span></div>';
|
||||
}
|
||||
|
||||
return '';
|
||||
|
@ -148,7 +148,7 @@ export function getTypeIndicator(item) {
|
|||
};
|
||||
|
||||
const icon = iconT[item.Type];
|
||||
return icon ? '<div class="indicator videoIndicator"><span class="material-icons indicatorIcon ' + icon + '"></span></div>' : '';
|
||||
return icon ? '<div class="indicator videoIndicator"><span class="material-icons indicatorIcon ' + icon + '" aria-hidden="true"></span></div>' : '';
|
||||
}
|
||||
|
||||
export function getMissingIndicator(item) {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import browser from '../scripts/browser';
|
||||
import { copy } from '../scripts/clipboard';
|
||||
import globalize from '../scripts/globalize';
|
||||
import actionsheet from './actionSheet/actionSheet';
|
||||
import { appHost } from './apphost';
|
||||
|
@ -366,32 +367,11 @@ import toast from './toast/toast';
|
|||
break;
|
||||
case 'copy-stream': {
|
||||
const downloadHref = apiClient.getItemDownloadUrl(itemId);
|
||||
const textAreaCopy = function () {
|
||||
const textArea = document.createElement('textarea');
|
||||
textArea.value = downloadHref;
|
||||
document.body.appendChild(textArea);
|
||||
textArea.focus();
|
||||
textArea.select();
|
||||
|
||||
if (document.execCommand('copy')) {
|
||||
copy(downloadHref).then(() => {
|
||||
toast(globalize.translate('CopyStreamURLSuccess'));
|
||||
} else {
|
||||
}).catch(() => {
|
||||
prompt(globalize.translate('CopyStreamURL'), downloadHref);
|
||||
}
|
||||
document.body.removeChild(textArea);
|
||||
};
|
||||
|
||||
/* eslint-disable-next-line compat/compat */
|
||||
if (navigator.clipboard === undefined) {
|
||||
textAreaCopy();
|
||||
} else {
|
||||
/* eslint-disable-next-line compat/compat */
|
||||
navigator.clipboard.writeText(downloadHref).then(function () {
|
||||
toast(globalize.translate('CopyStreamURLSuccess'));
|
||||
}).catch(function () {
|
||||
textAreaCopy();
|
||||
});
|
||||
}
|
||||
getResolveFunction(resolve, id)();
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -7,6 +7,9 @@
|
|||
|
||||
import dialogHelper from '../dialogHelper/dialogHelper';
|
||||
import layoutManager from '../layoutManager';
|
||||
import toast from '../toast/toast';
|
||||
import { copy } from '../../scripts/clipboard';
|
||||
import dom from '../../scripts/dom';
|
||||
import globalize from '../../scripts/globalize';
|
||||
import loading from '../loading/loading';
|
||||
import '../../elements/emby-select/emby-select';
|
||||
|
@ -19,6 +22,12 @@ import '../../assets/css/flexstyles.scss';
|
|||
import ServerConnections from '../ServerConnections';
|
||||
import template from './itemMediaInfo.template.html';
|
||||
|
||||
// Do not add extra spaces between tags - they will be copied into the result
|
||||
const copyButtonHtml = layoutManager.tv ? '' :
|
||||
`<button is="paper-icon-button-light" class="btnCopy" title="${globalize.translate('Copy')}" aria-label="${globalize.translate('Copy')}"
|
||||
><span class="material-icons content_copy" aria-hidden="true"></span></button>`;
|
||||
const attributeDelimiterHtml = layoutManager.tv ? '' : '<span class="hide">: </span>';
|
||||
|
||||
function setMediaInfo(user, page, item) {
|
||||
let html = item.MediaSources.map(version => {
|
||||
return getMediaSourceHtml(user, item, version);
|
||||
|
@ -28,12 +37,25 @@ import template from './itemMediaInfo.template.html';
|
|||
}
|
||||
const mediaInfoContent = page.querySelector('#mediaInfoContent');
|
||||
mediaInfoContent.innerHTML = html;
|
||||
|
||||
for (const btn of mediaInfoContent.querySelectorAll('.btnCopy')) {
|
||||
btn.addEventListener('click', () => {
|
||||
const infoBlock = dom.parentWithClass(btn, 'mediaInfoStream') || dom.parentWithClass(btn, 'mediaInfoSource') || mediaInfoContent;
|
||||
|
||||
copy(infoBlock.textContent).then(() => {
|
||||
toast(globalize.translate('Copied'));
|
||||
}).catch(() => {
|
||||
console.error('Could not copy text');
|
||||
toast(globalize.translate('CopyFailed'));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getMediaSourceHtml(user, item, version) {
|
||||
let html = '';
|
||||
let html = '<div class="mediaInfoSource">';
|
||||
if (version.Name) {
|
||||
html += `<div><h2 class="mediaInfoStreamType">${version.Name}</h2></div>`;
|
||||
html += `<div><h2 class="mediaInfoStreamType">${version.Name}${copyButtonHtml}</h2></div>\n`;
|
||||
}
|
||||
if (version.Container) {
|
||||
html += `${createAttribute(globalize.translate('MediaInfoContainer'), version.Container)}<br/>`;
|
||||
|
@ -69,7 +91,7 @@ import template from './itemMediaInfo.template.html';
|
|||
}
|
||||
|
||||
const displayType = globalize.translate(translateString);
|
||||
html += `<h2 class="mediaInfoStreamType">${displayType}</h2>`;
|
||||
html += `\n<h2 class="mediaInfoStreamType">${displayType}${copyButtonHtml}</h2>\n`;
|
||||
const attributes = [];
|
||||
if (stream.DisplayTitle) {
|
||||
attributes.push(createAttribute(globalize.translate('MediaInfoTitle'), stream.DisplayTitle));
|
||||
|
@ -143,10 +165,8 @@ import template from './itemMediaInfo.template.html';
|
|||
if (stream.NalLengthSize) {
|
||||
attributes.push(createAttribute('NAL', stream.NalLengthSize));
|
||||
}
|
||||
if (stream.Type !== 'Video') {
|
||||
if (stream.Type === 'Subtitle' || stream.Type === 'Audio') {
|
||||
attributes.push(createAttribute(globalize.translate('MediaInfoDefault'), (stream.IsDefault ? 'Yes' : 'No')));
|
||||
}
|
||||
if (stream.Type === 'Subtitle') {
|
||||
attributes.push(createAttribute(globalize.translate('MediaInfoForced'), (stream.IsForced ? 'Yes' : 'No')));
|
||||
attributes.push(createAttribute(globalize.translate('MediaInfoExternal'), (stream.IsExternal ? 'Yes' : 'No')));
|
||||
}
|
||||
|
@ -156,11 +176,12 @@ import template from './itemMediaInfo.template.html';
|
|||
html += attributes.join('<br/>');
|
||||
html += '</div>';
|
||||
}
|
||||
html += '</div>';
|
||||
return html;
|
||||
}
|
||||
|
||||
function createAttribute(label, value) {
|
||||
return `<span class="mediaInfoLabel">${label}</span><span class="mediaInfoAttribute">${value}</span>`;
|
||||
return `<span class="mediaInfoLabel">${label}</span>${attributeDelimiterHtml}<span class="mediaInfoAttribute">${value}</span>\n`;
|
||||
}
|
||||
|
||||
function loadMediaInfo(itemId, serverId) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<div class="formDialogHeader">
|
||||
<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1">
|
||||
<span class="material-icons arrow_back"></span>
|
||||
<span class="material-icons arrow_back" aria-hidden="true"></span>
|
||||
</button>
|
||||
<h3 class="formDialogHeaderTitle">${MoreMediaInfo}</h3>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<div class="formDialogHeader">
|
||||
<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1">
|
||||
<span class="material-icons arrow_back"></span>
|
||||
<span class="material-icons arrow_back" aria-hidden="true"></span>
|
||||
</button>
|
||||
<h3 class="formDialogHeaderTitle">${Identify}</h3>
|
||||
</div>
|
||||
|
|
|
@ -69,16 +69,16 @@ import template from './libraryoptionseditor.template.html';
|
|||
for (let i = 0; i < plugins.length; i++) {
|
||||
const plugin = plugins[i];
|
||||
html += `<div class="listItem localReaderOption sortableOption" data-pluginname="${plugin.Name}">`;
|
||||
html += '<span class="listItemIcon material-icons live_tv"></span>';
|
||||
html += '<span class="listItemIcon material-icons live_tv" aria-hidden="true"></span>';
|
||||
html += '<div class="listItemBody">';
|
||||
html += '<h3 class="listItemBodyText">';
|
||||
html += plugin.Name;
|
||||
html += '</h3>';
|
||||
html += '</div>';
|
||||
if (i > 0) {
|
||||
html += `<button type="button" is="paper-icon-button-light" title="${globalize.translate('Up')}" class="btnSortableMoveUp btnSortable" data-pluginindex="${i}"><span class="material-icons keyboard_arrow_up"></span></button>`;
|
||||
html += `<button type="button" is="paper-icon-button-light" title="${globalize.translate('Up')}" class="btnSortableMoveUp btnSortable" data-pluginindex="${i}"><span class="material-icons keyboard_arrow_up" aria-hidden="true"></span></button>`;
|
||||
} else if (plugins.length > 1) {
|
||||
html += `<button type="button" is="paper-icon-button-light" title="${globalize.translate('Down')}" class="btnSortableMoveDown btnSortable" data-pluginindex="${i}"><span class="material-icons keyboard_arrow_down"></span></button>`;
|
||||
html += `<button type="button" is="paper-icon-button-light" title="${globalize.translate('Down')}" class="btnSortableMoveDown btnSortable" data-pluginindex="${i}"><span class="material-icons keyboard_arrow_down" aria-hidden="true"></span></button>`;
|
||||
}
|
||||
html += '</div>';
|
||||
}
|
||||
|
@ -132,9 +132,9 @@ import template from './libraryoptionseditor.template.html';
|
|||
html += '</h3>';
|
||||
html += '</div>';
|
||||
if (index > 0) {
|
||||
html += '<button type="button" is="paper-icon-button-light" title="' + globalize.translate('Up') + '" class="btnSortableMoveUp btnSortable" data-pluginindex="' + index + '"><span class="material-icons keyboard_arrow_up"></span></button>';
|
||||
html += '<button type="button" is="paper-icon-button-light" title="' + globalize.translate('Up') + '" class="btnSortableMoveUp btnSortable" data-pluginindex="' + index + '"><span class="material-icons keyboard_arrow_up" aria-hidden="true"></span></button>';
|
||||
} else if (plugins.length > 1) {
|
||||
html += '<button type="button" is="paper-icon-button-light" title="' + globalize.translate('Down') + '" class="btnSortableMoveDown btnSortable" data-pluginindex="' + index + '"><span class="material-icons keyboard_arrow_down"></span></button>';
|
||||
html += '<button type="button" is="paper-icon-button-light" title="' + globalize.translate('Down') + '" class="btnSortableMoveDown btnSortable" data-pluginindex="' + index + '"><span class="material-icons keyboard_arrow_down" aria-hidden="true"></span></button>';
|
||||
}
|
||||
html += '</div>';
|
||||
});
|
||||
|
@ -198,9 +198,9 @@ import template from './libraryoptionseditor.template.html';
|
|||
html += '</h3>';
|
||||
html += '</div>';
|
||||
if (i > 0) {
|
||||
html += `<button type="button" is="paper-icon-button-light" title="${globalize.translate('Up')}" class="btnSortableMoveUp btnSortable" data-pluginindex="${i}"><span class="material-icons keyboard_arrow_up"></span></button>`;
|
||||
html += `<button type="button" is="paper-icon-button-light" title="${globalize.translate('Up')}" class="btnSortableMoveUp btnSortable" data-pluginindex="${i}"><span class="material-icons keyboard_arrow_up" aria-hidden="true"></span></button>`;
|
||||
} else if (plugins.length > 1) {
|
||||
html += `<button type="button" is="paper-icon-button-light" title="${globalize.translate('Down')}" class="btnSortableMoveDown btnSortable" data-pluginindex="${i}"><span class="material-icons keyboard_arrow_down"></span></button>`;
|
||||
html += `<button type="button" is="paper-icon-button-light" title="${globalize.translate('Down')}" class="btnSortableMoveDown btnSortable" data-pluginindex="${i}"><span class="material-icons keyboard_arrow_down" aria-hidden="true"></span></button>`;
|
||||
}
|
||||
html += '</div>';
|
||||
}
|
||||
|
@ -237,9 +237,9 @@ import template from './libraryoptionseditor.template.html';
|
|||
html += '</h3>';
|
||||
html += '</div>';
|
||||
if (i > 0) {
|
||||
html += '<button type="button" is="paper-icon-button-light" title="' + globalize.translate('Up') + '" class="btnSortableMoveUp btnSortable" data-pluginindex="' + i + '"><span class="material-icons keyboard_arrow_up"></span></button>';
|
||||
html += '<button type="button" is="paper-icon-button-light" title="' + globalize.translate('Up') + '" class="btnSortableMoveUp btnSortable" data-pluginindex="' + i + '"><span class="material-icons keyboard_arrow_up" aria-hidden="true"></span></button>';
|
||||
} else if (plugins.length > 1) {
|
||||
html += '<button type="button" is="paper-icon-button-light" title="' + globalize.translate('Down') + '" class="btnSortableMoveDown btnSortable" data-pluginindex="' + i + '"><span class="material-icons keyboard_arrow_down"></span></button>';
|
||||
html += '<button type="button" is="paper-icon-button-light" title="' + globalize.translate('Down') + '" class="btnSortableMoveDown btnSortable" data-pluginindex="' + i + '"><span class="material-icons keyboard_arrow_down" aria-hidden="true"></span></button>';
|
||||
}
|
||||
html += '</div>';
|
||||
}
|
||||
|
@ -411,7 +411,13 @@ import template from './libraryoptionseditor.template.html';
|
|||
parent.querySelector('.chkEnableEmbeddedEpisodeInfosContainer').classList.add('hide');
|
||||
}
|
||||
|
||||
parent.querySelector('.chkAutomaticallyAddToCollectionContainer').classList.toggle('hide', contentType !== 'movies');
|
||||
if (contentType === 'tvshows' || contentType === 'movies' || contentType === 'musicvideos' || contentType === 'mixed') {
|
||||
parent.querySelector('.fldAllowEmbeddedSubtitlesContainer').classList.remove('hide');
|
||||
} else {
|
||||
parent.querySelector('.fldAllowEmbeddedSubtitlesContainer').classList.add('hide');
|
||||
}
|
||||
|
||||
parent.querySelector('.chkAutomaticallyAddToCollectionContainer').classList.toggle('hide', contentType !== 'movies' && contentType !== 'mixed');
|
||||
|
||||
return populateMetadataSettings(parent, contentType);
|
||||
}
|
||||
|
@ -509,6 +515,7 @@ import template from './libraryoptionseditor.template.html';
|
|||
AutomaticRefreshIntervalDays: parseInt(parent.querySelector('#selectAutoRefreshInterval').value),
|
||||
EnableEmbeddedTitles: parent.querySelector('#chkEnableEmbeddedTitles').checked,
|
||||
EnableEmbeddedEpisodeInfos: parent.querySelector('#chkEnableEmbeddedEpisodeInfos').checked,
|
||||
AllowEmbeddedSubtitles: parent.querySelector('#selectAllowEmbeddedSubtitles').value,
|
||||
SkipSubtitlesIfEmbeddedSubtitlesPresent: parent.querySelector('#chkSkipIfGraphicalSubsPresent').checked,
|
||||
SkipSubtitlesIfAudioTrackMatches: parent.querySelector('#chkSkipIfAudioTrackPresent').checked,
|
||||
SaveSubtitlesWithMedia: parent.querySelector('#chkSaveSubtitlesLocally').checked,
|
||||
|
@ -560,7 +567,8 @@ import template from './libraryoptionseditor.template.html';
|
|||
parent.querySelector('#chkSaveLocal').checked = options.SaveLocalMetadata;
|
||||
parent.querySelector('.chkAutomaticallyGroupSeries').checked = options.EnableAutomaticSeriesGrouping;
|
||||
parent.querySelector('#chkEnableEmbeddedTitles').checked = options.EnableEmbeddedTitles;
|
||||
parent.querySelector('#chkEnableEmbeddedEpisodeInfos').checked = options.EnableEmbeddedEpisodeInfos;
|
||||
parent.querySelector('#chkEnableEmbeddedEpisodeInfos').value = options.EnableEmbeddedEpisodeInfos;
|
||||
parent.querySelector('#selectAllowEmbeddedSubtitles').value = options.AllowEmbeddedSubtitles;
|
||||
parent.querySelector('#chkSkipIfGraphicalSubsPresent').checked = options.SkipSubtitlesIfEmbeddedSubtitlesPresent;
|
||||
parent.querySelector('#chkSaveSubtitlesLocally').checked = options.SaveSubtitlesWithMedia;
|
||||
parent.querySelector('#chkSkipIfAudioTrackPresent').checked = options.SkipSubtitlesIfAudioTrackMatches;
|
||||
|
|
|
@ -30,6 +30,15 @@
|
|||
</label>
|
||||
<div class="fieldDescription checkboxFieldDescription">${PreferEmbeddedEpisodeInfosOverFileNamesHelp}</div>
|
||||
</div>
|
||||
<div class="selectContainer fldAllowEmbeddedSubtitlesContainer hide advanced" style="margin: 2em 0;">
|
||||
<select is="emby-select" id="selectAllowEmbeddedSubtitles" label="${AllowEmbeddedSubtitles}">
|
||||
<option value="AllowAll">${AllowEmbeddedSubtitlesAllowAllOption}</option>
|
||||
<option value="AllowText">${AllowEmbeddedSubtitlesAllowTextOption}</option>
|
||||
<option value="AllowImage">${AllowEmbeddedSubtitlesAllowImageOption}</option>
|
||||
<option value="AllowNone">${AllowEmbeddedSubtitlesAllowNoneOption}</option>
|
||||
</select>
|
||||
<div class="fieldDescription">${AllowEmbeddedSubtitlesHelp}</div>
|
||||
</div>
|
||||
|
||||
<div class="checkboxContainer checkboxContainer-withDescription advanced">
|
||||
<label>
|
||||
|
|
|
@ -166,7 +166,7 @@ import ServerConnections from '../ServerConnections';
|
|||
for (let i = 0, length = options.rightButtons.length; i < length; i++) {
|
||||
const button = options.rightButtons[i];
|
||||
|
||||
html += `<button is="paper-icon-button-light" class="listItemButton itemAction" data-action="custom" data-customaction="${button.id}" title="${button.title}"><span class="material-icons ${button.icon}"></span></button>`;
|
||||
html += `<button is="paper-icon-button-light" class="listItemButton itemAction" data-action="custom" data-customaction="${button.id}" title="${button.title}"><span class="material-icons ${button.icon}" aria-hidden="true"></span></button>`;
|
||||
}
|
||||
|
||||
return html;
|
||||
|
@ -257,7 +257,7 @@ import ServerConnections from '../ServerConnections';
|
|||
}
|
||||
|
||||
if (!clickEntireItem && options.dragHandle) {
|
||||
html += '<span class="listViewDragHandle material-icons listItemIcon listItemIcon-transparent drag_handle"></span>';
|
||||
html += '<span class="listViewDragHandle material-icons listItemIcon listItemIcon-transparent drag_handle" aria-hidden="true"></span>';
|
||||
}
|
||||
|
||||
if (options.image !== false) {
|
||||
|
@ -282,6 +282,11 @@ import ServerConnections from '../ServerConnections';
|
|||
html += '<div class="' + imageClass + ' cardImageContainer ' + cardBuilder.getDefaultBackgroundClass(item.Name) + '">' + cardBuilder.getDefaultText(item, options);
|
||||
}
|
||||
|
||||
const mediaSourceCount = item.MediaSourceCount || 1;
|
||||
if (mediaSourceCount > 1 && options.disableIndicators !== true) {
|
||||
html += '<div class="mediaSourceIndicator">' + mediaSourceCount + '</div>';
|
||||
}
|
||||
|
||||
let indicatorsHtml = '';
|
||||
indicatorsHtml += indicators.getPlayedIndicatorHtml(item);
|
||||
|
||||
|
@ -290,7 +295,7 @@ import ServerConnections from '../ServerConnections';
|
|||
}
|
||||
|
||||
if (playOnImageClick) {
|
||||
html += '<button is="paper-icon-button-light" class="listItemImageButton itemAction" data-action="resume"><span class="material-icons listItemImageButton-icon play_arrow"></span></button>';
|
||||
html += '<button is="paper-icon-button-light" class="listItemImageButton itemAction" data-action="resume"><span class="material-icons listItemImageButton-icon play_arrow" aria-hidden="true"></span></button>';
|
||||
}
|
||||
|
||||
const progressHtml = indicators.getProgressBarHtml(item, {
|
||||
|
@ -450,11 +455,11 @@ import ServerConnections from '../ServerConnections';
|
|||
|
||||
if (!clickEntireItem) {
|
||||
if (options.addToListButton) {
|
||||
html += '<button is="paper-icon-button-light" class="listItemButton itemAction" data-action="addtoplaylist"><span class="material-icons playlist_add"></span></button>';
|
||||
html += '<button is="paper-icon-button-light" class="listItemButton itemAction" data-action="addtoplaylist"><span class="material-icons playlist_add" aria-hidden="true"></span></button>';
|
||||
}
|
||||
|
||||
if (options.infoButton) {
|
||||
html += '<button is="paper-icon-button-light" class="listItemButton itemAction" data-action="link"><span class="material-icons info_outline"></span></button>';
|
||||
html += '<button is="paper-icon-button-light" class="listItemButton itemAction" data-action="link"><span class="material-icons info_outline" aria-hidden="true"></span></button>';
|
||||
}
|
||||
|
||||
if (options.rightButtons) {
|
||||
|
@ -466,16 +471,16 @@ import ServerConnections from '../ServerConnections';
|
|||
const likes = userData.Likes == null ? '' : userData.Likes;
|
||||
|
||||
if (itemHelper.canMarkPlayed(item) && options.enablePlayedButton !== false) {
|
||||
html += '<button is="emby-playstatebutton" type="button" class="listItemButton paper-icon-button-light" data-id="' + item.Id + '" data-serverid="' + item.ServerId + '" data-itemtype="' + item.Type + '" data-played="' + (userData.Played) + '"><span class="material-icons check"></span></button>';
|
||||
html += '<button is="emby-playstatebutton" type="button" class="listItemButton paper-icon-button-light" data-id="' + item.Id + '" data-serverid="' + item.ServerId + '" data-itemtype="' + item.Type + '" data-played="' + (userData.Played) + '"><span class="material-icons check" aria-hidden="true"></span></button>';
|
||||
}
|
||||
|
||||
if (itemHelper.canRate(item) && options.enableRatingButton !== false) {
|
||||
html += '<button is="emby-ratingbutton" type="button" class="listItemButton paper-icon-button-light" data-id="' + item.Id + '" data-serverid="' + item.ServerId + '" data-itemtype="' + item.Type + '" data-likes="' + likes + '" data-isfavorite="' + (userData.IsFavorite) + '"><span class="material-icons favorite"></span></button>';
|
||||
html += '<button is="emby-ratingbutton" type="button" class="listItemButton paper-icon-button-light" data-id="' + item.Id + '" data-serverid="' + item.ServerId + '" data-itemtype="' + item.Type + '" data-likes="' + likes + '" data-isfavorite="' + (userData.IsFavorite) + '"><span class="material-icons favorite" aria-hidden="true"></span></button>';
|
||||
}
|
||||
}
|
||||
|
||||
if (options.moreButton !== false) {
|
||||
html += '<button is="paper-icon-button-light" class="listItemButton itemAction" data-action="menu"><span class="material-icons more_vert"></span></button>';
|
||||
html += '<button is="paper-icon-button-light" class="listItemButton itemAction" data-action="menu"><span class="material-icons more_vert" aria-hidden="true"></span></button>';
|
||||
}
|
||||
}
|
||||
html += '</div>';
|
||||
|
|
|
@ -164,6 +164,10 @@
|
|||
padding: 0.2em;
|
||||
}
|
||||
|
||||
.listItemImage .cardImageIcon {
|
||||
font-size: 3em;
|
||||
}
|
||||
|
||||
@media all and (max-width: 64em) {
|
||||
.listItemImage-large {
|
||||
width: 22vw;
|
||||
|
|
|
@ -73,7 +73,7 @@ import template from './mediaLibraryCreator.template.html';
|
|||
$('#selectCollectionType', page).html(getCollectionTypeOptionsHtml(collectionTypeOptions)).val('').on('change', function () {
|
||||
const value = this.value;
|
||||
const dlg = $(this).parents('.dialog')[0];
|
||||
libraryoptionseditor.setContentType(dlg.querySelector('.libraryOptions'), value == 'mixed' ? '' : value);
|
||||
libraryoptionseditor.setContentType(dlg.querySelector('.libraryOptions'), value);
|
||||
|
||||
if (value) {
|
||||
dlg.querySelector('.libraryOptions').classList.remove('hide');
|
||||
|
@ -87,12 +87,11 @@ import template from './mediaLibraryCreator.template.html';
|
|||
if (index != -1) {
|
||||
const name = this.options[index].innerHTML.replace('*', '').replace('&', '&');
|
||||
$('#txtValue', dlg).val(name);
|
||||
const folderOption = collectionTypeOptions.filter(i => {
|
||||
return i.value == value;
|
||||
})[0];
|
||||
$('.collectionTypeFieldDescription', dlg).html(folderOption.message || '');
|
||||
}
|
||||
}
|
||||
|
||||
const folderOption = collectionTypeOptions.find(i => i.value === value);
|
||||
$('.collectionTypeFieldDescription', dlg).html(folderOption?.message || '');
|
||||
});
|
||||
page.querySelector('.btnAddFolder').addEventListener('click', onAddButtonClick);
|
||||
page.querySelector('.btnSubmit').addEventListener('click', onAddLibrary);
|
||||
|
@ -128,7 +127,7 @@ import template from './mediaLibraryCreator.template.html';
|
|||
}
|
||||
|
||||
html += '</div>';
|
||||
html += `<button type="button" is="paper-icon-button-light"" class="listItemButton btnRemovePath" data-index="${index}"><span class="material-icons remove_circle"></span></button>`;
|
||||
html += `<button type="button" is="paper-icon-button-light"" class="listItemButton btnRemovePath" data-index="${index}"><span class="material-icons remove_circle" aria-hidden="true"></span></button>`;
|
||||
html += '</div>';
|
||||
return html;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div class="formDialogHeader">
|
||||
<button type="button" is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><span class="material-icons arrow_back"></span></button>
|
||||
<button type="button" is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><span class="material-icons arrow_back" aria-hidden="true"></span></button>
|
||||
<h3 class="formDialogHeaderTitle">${ButtonAddMediaLibrary}</h3>
|
||||
</div>
|
||||
|
||||
|
@ -20,7 +20,7 @@
|
|||
<div style="display: flex; align-items: center;">
|
||||
<h1 style="margin: .5em 0;">${Folders}</h1>
|
||||
<button is="emby-button" type="button" class="fab btnAddFolder submit" style="margin-left:1em;" title="${Add}">
|
||||
<span class="material-icons add"></span>
|
||||
<span class="material-icons add" aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="paperList folderList hide" style="margin-bottom:2em;"></div>
|
||||
|
|
|
@ -119,7 +119,7 @@ import template from './mediaLibraryEditor.template.html';
|
|||
}
|
||||
|
||||
html += '</div>';
|
||||
html += `<button type="button" is="paper-icon-button-light" class="listItemButton btnRemovePath" data-index="${index}"><span class="material-icons remove_circle"></span></button>`;
|
||||
html += `<button type="button" is="paper-icon-button-light" class="listItemButton btnRemovePath" data-index="${index}"><span class="material-icons remove_circle" aria-hidden="true"></span></button>`;
|
||||
html += '</div>';
|
||||
return html;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div class="formDialogHeader">
|
||||
<button type="button" is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><span class="material-icons arrow_back"></span></button>
|
||||
<button type="button" is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><span class="material-icons arrow_back" aria-hidden="true"></span></button>
|
||||
<h3 class="formDialogHeaderTitle"></h3>
|
||||
</div>
|
||||
|
||||
|
@ -13,7 +13,7 @@
|
|||
<div style="display: flex; align-items: center;">
|
||||
<h1 style="margin: .5em 0;">${Folders}</h1>
|
||||
<button is="emby-button" type="button" class="fab btnAddFolder submit" style="margin-left:1em;" title="${Add}">
|
||||
<span class="material-icons add"></span>
|
||||
<span class="material-icons add" aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="paperList folderList" style="margin-bottom:2em;"></div>
|
||||
|
|
|
@ -13,7 +13,7 @@ import '../../elements/emby-button/emby-button';
|
|||
let status;
|
||||
|
||||
if (item.Type === 'SeriesTimer') {
|
||||
return '<span class="material-icons mediaInfoItem mediaInfoIconItem mediaInfoTimerIcon fiber_smart_record"></span>';
|
||||
return '<span class="material-icons mediaInfoItem mediaInfoIconItem mediaInfoTimerIcon fiber_smart_record" aria-hidden="true"></span>';
|
||||
} else if (item.TimerId || item.SeriesTimerId) {
|
||||
status = item.Status || 'Cancelled';
|
||||
} else if (item.Type === 'Timer') {
|
||||
|
@ -24,13 +24,13 @@ import '../../elements/emby-button/emby-button';
|
|||
|
||||
if (item.SeriesTimerId) {
|
||||
if (status !== 'Cancelled') {
|
||||
return '<span class="material-icons mediaInfoItem mediaInfoIconItem mediaInfoTimerIcon fiber_smart_record"></span>';
|
||||
return '<span class="material-icons mediaInfoItem mediaInfoIconItem mediaInfoTimerIcon fiber_smart_record" aria-hidden="true"></span>';
|
||||
}
|
||||
|
||||
return '<span class="material-icons mediaInfoItem mediaInfoIconItem fiber_smart_record"></span>';
|
||||
return '<span class="material-icons mediaInfoItem mediaInfoIconItem fiber_smart_record" aria-hidden="true"></span>';
|
||||
}
|
||||
|
||||
return '<span class="material-icons mediaInfoItem mediaInfoIconItem mediaInfoTimerIcon fiber_manual_record"></span>';
|
||||
return '<span class="material-icons mediaInfoItem mediaInfoIconItem mediaInfoTimerIcon fiber_manual_record" aria-hidden="true"></span>';
|
||||
}
|
||||
|
||||
function getProgramInfoHtml(item, options) {
|
||||
|
@ -358,7 +358,7 @@ import '../../elements/emby-button/emby-button';
|
|||
if (item.CommunityRating) {
|
||||
html += '<div class="starRatingContainer mediaInfoItem">';
|
||||
|
||||
html += '<span class="material-icons starIcon star"></span>';
|
||||
html += '<span class="material-icons starIcon star" aria-hidden="true"></span>';
|
||||
html += item.CommunityRating.toFixed(1);
|
||||
html += '</div>';
|
||||
}
|
||||
|
|
|
@ -460,7 +460,7 @@ import template from './metadataEditor.template.html';
|
|||
html += '</div>';
|
||||
|
||||
if (formatString) {
|
||||
html += '<button type="button" is="paper-icon-button-light" class="btnOpenExternalId align-self-flex-end" data-fieldid="' + id + '"><span class="material-icons open_in_browser"></span></button>';
|
||||
html += '<button type="button" is="paper-icon-button-light" class="btnOpenExternalId align-self-flex-end" data-fieldid="' + id + '"><span class="material-icons open_in_browser" aria-hidden="true"></span></button>';
|
||||
}
|
||||
html += '</div>';
|
||||
|
||||
|
@ -898,7 +898,7 @@ import template from './metadataEditor.template.html';
|
|||
for (let i = 0; i < items.length; i++) {
|
||||
html += '<div class="listItem">';
|
||||
|
||||
html += '<span class="material-icons listItemIcon live_tv" style="background-color:#333;"></span>';
|
||||
html += '<span class="material-icons listItemIcon live_tv" aria-hidden="true" style="background-color:#333;"></span>';
|
||||
|
||||
html += '<div class="listItemBody">';
|
||||
|
||||
|
@ -908,7 +908,7 @@ import template from './metadataEditor.template.html';
|
|||
|
||||
html += '</div>';
|
||||
|
||||
html += '<button type="button" is="paper-icon-button-light" data-index="' + i + '" class="btnRemoveFromEditorList autoSize"><span class="material-icons delete"></span></button>';
|
||||
html += '<button type="button" is="paper-icon-button-light" data-index="' + i + '" class="btnRemoveFromEditorList autoSize"><span class="material-icons delete" aria-hidden="true"></span></button>';
|
||||
|
||||
html += '</div>';
|
||||
}
|
||||
|
@ -945,7 +945,7 @@ import template from './metadataEditor.template.html';
|
|||
html += '</button>';
|
||||
html += '</div>';
|
||||
|
||||
html += '<button type="button" is="paper-icon-button-light" data-index="' + i + '" class="btnDeletePerson autoSize"><span class="material-icons delete"></span></button>';
|
||||
html += '<button type="button" is="paper-icon-button-light" data-index="' + i + '" class="btnDeletePerson autoSize"><span class="material-icons delete" aria-hidden="true"></span></button>';
|
||||
|
||||
html += '</div>';
|
||||
}
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
<div class="formDialogHeader">
|
||||
<button is="paper-icon-button-light" class="btnCancel btnBack autoSize hide" tabindex="-1"><span class="material-icons arrow_back"></span></button>
|
||||
<button is="paper-icon-button-light" class="btnCancel btnBack autoSize hide" tabindex="-1"><span class="material-icons arrow_back" aria-hidden="true"></span></button>
|
||||
<h3 class="formDialogHeaderTitle">
|
||||
${Edit}
|
||||
</h3>
|
||||
<div style="margin-left: auto;" class="flex align-items-center justify-content-center">
|
||||
<button is="emby-button" type="button" class="btnHeaderSave button-accent-flat button-flat hide" tabindex="-1">
|
||||
<span class="material-icons check"></span>
|
||||
<span class="material-icons check" aria-hidden="true"></span>
|
||||
<span>${Save}</span>
|
||||
</button>
|
||||
<button is="paper-icon-button-light" class="btnMore autoSize" tabindex="-1">
|
||||
<span class="material-icons more_vert"></span>
|
||||
<span class="material-icons more_vert" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button is="paper-icon-button-light" class="btnCancel btnClose autoSize" tabindex="-1">
|
||||
<span class="material-icons close"></span>
|
||||
<span class="material-icons close" aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -195,7 +195,7 @@
|
|||
${Genres}
|
||||
</h2>
|
||||
<button is="emby-button" type="button" class="fab btnAddTextItem submit" style="margin-left:1em;" title="${Add}">
|
||||
<span class="material-icons add"></span>
|
||||
<span class="material-icons add" aria-hidden="true"></span>
|
||||
</button>
|
||||
<div class="paperList" id="listGenres"></div>
|
||||
</div>
|
||||
|
@ -204,7 +204,7 @@
|
|||
${People}
|
||||
</h2>
|
||||
<button is="emby-button" type="button" id="btnAddPerson" class="fab btnAddPerson" style="margin-left:1em;" title="${Add}">
|
||||
<span class="material-icons add"></span>
|
||||
<span class="material-icons add" aria-hidden="true"></span>
|
||||
</button>
|
||||
<div id="peopleList" class="paperList">
|
||||
</div>
|
||||
|
@ -214,7 +214,7 @@
|
|||
${Studios}
|
||||
</h2>
|
||||
<button is="emby-button" type="button" class="fab btnAddTextItem submit" style="margin-left:1em;" title="${Add}">
|
||||
<span class="material-icons add"></span>
|
||||
<span class="material-icons add" aria-hidden="true"></span>
|
||||
</button>
|
||||
<div class="paperList" id="listStudios"></div>
|
||||
</div>
|
||||
|
@ -223,7 +223,7 @@
|
|||
${Tags}
|
||||
</h2>
|
||||
<button is="emby-button" type="button" class="fab btnAddTextItem submit" style="margin-left:1em;" title="${Add}">
|
||||
<span class="material-icons add"></span>
|
||||
<span class="material-icons add" aria-hidden="true"></span>
|
||||
</button>
|
||||
<div class="paperList" id="listTags"></div>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div class="formDialogHeader">
|
||||
<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><span class="material-icons arrow_back"></span></button>
|
||||
<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><span class="material-icons arrow_back" aria-hidden="true"></span></button>
|
||||
<h3 class="formDialogHeaderTitle">
|
||||
${Edit}
|
||||
</h3>
|
||||
|
|
|
@ -125,11 +125,11 @@ import itemHelper from '../itemHelper';
|
|||
|
||||
let html = '';
|
||||
|
||||
html += '<button is="paper-icon-button-light" class="btnCloseSelectionPanel autoSize"><span class="material-icons close"></span></button>';
|
||||
html += '<button is="paper-icon-button-light" class="btnCloseSelectionPanel autoSize"><span class="material-icons close" aria-hidden="true"></span></button>';
|
||||
html += '<h1 class="itemSelectionCount"></h1>';
|
||||
|
||||
const moreIcon = 'more_vert';
|
||||
html += `<button is="paper-icon-button-light" class="btnSelectionPanelOptions autoSize" style="margin-left:auto;"><span class="material-icons ${moreIcon}"></span></button>`;
|
||||
html += `<button is="paper-icon-button-light" class="btnSelectionPanelOptions autoSize" style="margin-left:auto;"><span class="material-icons ${moreIcon}" aria-hidden="true"></span></button>`;
|
||||
|
||||
selectionCommandsPanel.innerHTML = html;
|
||||
|
||||
|
|
|
@ -59,13 +59,13 @@ import { appRouter } from '../appRouter';
|
|||
// The onclicks are needed due to the return false above
|
||||
html += '<div class="nowPlayingBarCenter">';
|
||||
|
||||
html += '<button is="paper-icon-button-light" class="previousTrackButton mediaButton"><span class="material-icons skip_previous"></span></button>';
|
||||
html += '<button is="paper-icon-button-light" class="previousTrackButton mediaButton"><span class="material-icons skip_previous" aria-hidden="true"></span></button>';
|
||||
|
||||
html += '<button is="paper-icon-button-light" class="playPauseButton mediaButton"><span class="material-icons pause"></span></button>';
|
||||
html += '<button is="paper-icon-button-light" class="playPauseButton mediaButton"><span class="material-icons pause" aria-hidden="true"></span></button>';
|
||||
|
||||
html += '<button is="paper-icon-button-light" class="stopButton mediaButton"><span class="material-icons stop"></span></button>';
|
||||
html += '<button is="paper-icon-button-light" class="stopButton mediaButton"><span class="material-icons stop" aria-hidden="true"></span></button>';
|
||||
if (!layoutManager.mobile) {
|
||||
html += '<button is="paper-icon-button-light" class="nextTrackButton mediaButton"><span class="material-icons skip_next"></span></button>';
|
||||
html += '<button is="paper-icon-button-light" class="nextTrackButton mediaButton"><span class="material-icons skip_next" aria-hidden="true"></span></button>';
|
||||
}
|
||||
|
||||
html += '<div class="nowPlayingBarCurrentTime"></div>';
|
||||
|
@ -73,23 +73,23 @@ import { appRouter } from '../appRouter';
|
|||
|
||||
html += '<div class="nowPlayingBarRight">';
|
||||
|
||||
html += '<button is="paper-icon-button-light" class="muteButton mediaButton"><span class="material-icons volume_up"></span></button>';
|
||||
html += '<button is="paper-icon-button-light" class="muteButton mediaButton"><span class="material-icons volume_up" aria-hidden="true"></span></button>';
|
||||
|
||||
html += '<div class="sliderContainer nowPlayingBarVolumeSliderContainer hide" style="width:9em;vertical-align:middle;display:inline-flex;">';
|
||||
html += '<input type="range" is="emby-slider" pin step="1" min="0" max="100" value="0" class="slider-medium-thumb nowPlayingBarVolumeSlider"/>';
|
||||
html += '</div>';
|
||||
|
||||
html += '<button is="paper-icon-button-light" class="toggleRepeatButton mediaButton"><span class="material-icons repeat"></span></button>';
|
||||
html += '<button is="paper-icon-button-light" class="btnShuffleQueue mediaButton"><span class="material-icons shuffle"></span></button>';
|
||||
html += '<button is="paper-icon-button-light" class="toggleRepeatButton mediaButton"><span class="material-icons repeat" aria-hidden="true"></span></button>';
|
||||
html += '<button is="paper-icon-button-light" class="btnShuffleQueue mediaButton"><span class="material-icons shuffle" aria-hidden="true"></span></button>';
|
||||
|
||||
html += '<div class="nowPlayingBarUserDataButtons">';
|
||||
html += '</div>';
|
||||
|
||||
html += '<button is="paper-icon-button-light" class="playPauseButton mediaButton"><span class="material-icons pause"></span></button>';
|
||||
html += '<button is="paper-icon-button-light" class="playPauseButton mediaButton"><span class="material-icons pause" aria-hidden="true"></span></button>';
|
||||
if (layoutManager.mobile) {
|
||||
html += '<button is="paper-icon-button-light" class="nextTrackButton mediaButton"><span class="material-icons skip_next"></span></button>';
|
||||
html += '<button is="paper-icon-button-light" class="nextTrackButton mediaButton"><span class="material-icons skip_next" aria-hidden="true"></span></button>';
|
||||
} else {
|
||||
html += '<button is="paper-icon-button-light" class="btnToggleContextMenu mediaButton"><span class="material-icons more_vert"></span></button>';
|
||||
html += '<button is="paper-icon-button-light" class="btnToggleContextMenu mediaButton"><span class="material-icons more_vert" aria-hidden="true"></span></button>';
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
|
@ -569,7 +569,7 @@ import { appRouter } from '../appRouter';
|
|||
});
|
||||
});
|
||||
}
|
||||
nowPlayingUserData.innerHTML = '<button is="emby-ratingbutton" type="button" class="listItemButton mediaButton paper-icon-button-light" data-id="' + item.Id + '" data-serverid="' + item.ServerId + '" data-itemtype="' + item.Type + '" data-likes="' + likes + '" data-isfavorite="' + (userData.IsFavorite) + '"><span class="material-icons favorite"></span></button>';
|
||||
nowPlayingUserData.innerHTML = '<button is="emby-ratingbutton" type="button" class="listItemButton mediaButton paper-icon-button-light" data-id="' + item.Id + '" data-serverid="' + item.ServerId + '" data-itemtype="' + item.Type + '" data-likes="' + likes + '" data-isfavorite="' + (userData.IsFavorite) + '"><span class="material-icons favorite" aria-hidden="true"></span></button>';
|
||||
});
|
||||
}
|
||||
} else {
|
||||
|
@ -658,6 +658,11 @@ import { appRouter } from '../appRouter';
|
|||
}
|
||||
|
||||
function onStateChanged(event, state) {
|
||||
if (event.type === 'init') {
|
||||
// skip non-ready state
|
||||
return;
|
||||
}
|
||||
|
||||
console.debug('nowplaying event: ' + event.type);
|
||||
const player = this;
|
||||
|
||||
|
@ -728,10 +733,10 @@ import { appRouter } from '../appRouter';
|
|||
updatePlayerVolumeState(player.isMuted(), player.getVolume());
|
||||
}
|
||||
|
||||
function refreshFromPlayer(player) {
|
||||
function refreshFromPlayer(player, type) {
|
||||
const state = playbackManager.getPlayerState(player);
|
||||
|
||||
onStateChanged.call(player, { type: 'init' }, state);
|
||||
onStateChanged.call(player, { type }, state);
|
||||
}
|
||||
|
||||
function bindToPlayer(player) {
|
||||
|
@ -747,7 +752,7 @@ import { appRouter } from '../appRouter';
|
|||
return;
|
||||
}
|
||||
|
||||
refreshFromPlayer(player);
|
||||
refreshFromPlayer(player, 'init');
|
||||
|
||||
Events.on(player, 'playbackstart', onPlaybackStart);
|
||||
Events.on(player, 'statechange', onPlaybackStart);
|
||||
|
@ -775,7 +780,7 @@ import { appRouter } from '../appRouter';
|
|||
} else if (!isVisibilityAllowed) {
|
||||
isVisibilityAllowed = true;
|
||||
if (currentPlayer) {
|
||||
refreshFromPlayer(currentPlayer);
|
||||
refreshFromPlayer(currentPlayer, 'refresh');
|
||||
} else {
|
||||
hideNowPlayingBar();
|
||||
}
|
||||
|
|
|
@ -22,9 +22,9 @@ type ItemsArr = {
|
|||
}
|
||||
|
||||
const NewUserPage: FunctionComponent = () => {
|
||||
const [ channelsItems, setChannelsItems ] = useState([]);
|
||||
const [ mediaFoldersItems, setMediaFoldersItems ] = useState([]);
|
||||
const element = useRef(null);
|
||||
const [ channelsItems, setChannelsItems ] = useState<ItemsArr[]>([]);
|
||||
const [ mediaFoldersItems, setMediaFoldersItems ] = useState<ItemsArr[]>([]);
|
||||
const element = useRef<HTMLDivElement>(null);
|
||||
|
||||
const getItemsResult = (items: ItemsArr[]) => {
|
||||
return items.map(item =>
|
||||
|
@ -36,33 +36,54 @@ const NewUserPage: FunctionComponent = () => {
|
|||
};
|
||||
|
||||
const loadMediaFolders = useCallback((result) => {
|
||||
const page = element.current;
|
||||
|
||||
if (!page) {
|
||||
console.error('Unexpected null reference');
|
||||
return;
|
||||
}
|
||||
|
||||
const mediaFolders = getItemsResult(result);
|
||||
|
||||
setMediaFoldersItems(mediaFolders);
|
||||
|
||||
const folderAccess = element?.current?.querySelector('.folderAccess');
|
||||
const folderAccess = page.querySelector('.folderAccess') as HTMLDivElement;
|
||||
folderAccess.dispatchEvent(new CustomEvent('create'));
|
||||
|
||||
element.current.querySelector('.chkEnableAllFolders').checked = false;
|
||||
(page.querySelector('.chkEnableAllFolders') as HTMLInputElement).checked = false;
|
||||
}, []);
|
||||
|
||||
const loadChannels = useCallback((result) => {
|
||||
const page = element.current;
|
||||
|
||||
if (!page) {
|
||||
console.error('Unexpected null reference');
|
||||
return;
|
||||
}
|
||||
|
||||
const channels = getItemsResult(result);
|
||||
|
||||
setChannelsItems(channels);
|
||||
|
||||
const channelAccess = element?.current?.querySelector('.channelAccess');
|
||||
const channelAccess = page.querySelector('.channelAccess') as HTMLDivElement;
|
||||
channelAccess.dispatchEvent(new CustomEvent('create'));
|
||||
|
||||
const channelAccessContainer = element?.current?.querySelector('.channelAccessContainer');
|
||||
const channelAccessContainer = page.querySelector('.channelAccessContainer') as HTMLDivElement;
|
||||
channels.length ? channelAccessContainer.classList.remove('hide') : channelAccessContainer.classList.add('hide');
|
||||
|
||||
element.current.querySelector('.chkEnableAllChannels').checked = false;
|
||||
(page.querySelector('.chkEnableAllChannels') as HTMLInputElement).checked = false;
|
||||
}, []);
|
||||
|
||||
const loadUser = useCallback(() => {
|
||||
element.current.querySelector('#txtUsername').value = '';
|
||||
element.current.querySelector('#txtPassword').value = '';
|
||||
const page = element.current;
|
||||
|
||||
if (!page) {
|
||||
console.error('Unexpected null reference');
|
||||
return;
|
||||
}
|
||||
|
||||
(page.querySelector('#txtUsername') as HTMLInputElement).value = '';
|
||||
(page.querySelector('#txtPassword') as HTMLInputElement).value = '';
|
||||
loading.show();
|
||||
const promiseFolders = window.ApiClient.getJSON(window.ApiClient.getUrl('Library/MediaFolders', {
|
||||
IsHidden: false
|
||||
|
@ -76,29 +97,44 @@ const NewUserPage: FunctionComponent = () => {
|
|||
}, [loadChannels, loadMediaFolders]);
|
||||
|
||||
useEffect(() => {
|
||||
const page = element.current;
|
||||
|
||||
if (!page) {
|
||||
console.error('Unexpected null reference');
|
||||
return;
|
||||
}
|
||||
|
||||
loadUser();
|
||||
|
||||
const saveUser = () => {
|
||||
const userInput: userInput = {};
|
||||
userInput.Name = element?.current?.querySelector('#txtUsername').value;
|
||||
userInput.Password = element?.current?.querySelector('#txtPassword').value;
|
||||
userInput.Name = (page.querySelector('#txtUsername') as HTMLInputElement).value;
|
||||
userInput.Password = (page.querySelector('#txtPassword') as HTMLInputElement).value;
|
||||
window.ApiClient.createUser(userInput).then(function (user) {
|
||||
user.Policy.EnableAllFolders = element?.current?.querySelector('.chkEnableAllFolders').checked;
|
||||
if (!user.Id) {
|
||||
throw new Error('Unexpected null user.Id');
|
||||
}
|
||||
|
||||
if (!user.Policy) {
|
||||
throw new Error('Unexpected null user.Policy');
|
||||
}
|
||||
|
||||
user.Policy.EnableAllFolders = (page.querySelector('.chkEnableAllFolders') as HTMLInputElement).checked;
|
||||
user.Policy.EnabledFolders = [];
|
||||
|
||||
if (!user.Policy.EnableAllFolders) {
|
||||
user.Policy.EnabledFolders = Array.prototype.filter.call(element?.current?.querySelectorAll('.chkFolder'), function (i) {
|
||||
user.Policy.EnabledFolders = Array.prototype.filter.call(page.querySelectorAll('.chkFolder'), function (i) {
|
||||
return i.checked;
|
||||
}).map(function (i) {
|
||||
return i.getAttribute('data-id');
|
||||
});
|
||||
}
|
||||
|
||||
user.Policy.EnableAllChannels = element?.current?.querySelector('.chkEnableAllChannels').checked;
|
||||
user.Policy.EnableAllChannels = (page.querySelector('.chkEnableAllChannels') as HTMLInputElement).checked;
|
||||
user.Policy.EnabledChannels = [];
|
||||
|
||||
if (!user.Policy.EnableAllChannels) {
|
||||
user.Policy.EnabledChannels = Array.prototype.filter.call(element?.current?.querySelectorAll('.chkChannel'), function (i) {
|
||||
user.Policy.EnabledChannels = Array.prototype.filter.call(page.querySelectorAll('.chkChannel'), function (i) {
|
||||
return i.checked;
|
||||
}).map(function (i) {
|
||||
return i.getAttribute('data-id');
|
||||
|
@ -114,7 +150,7 @@ const NewUserPage: FunctionComponent = () => {
|
|||
});
|
||||
};
|
||||
|
||||
const onSubmit = (e) => {
|
||||
const onSubmit = (e: Event) => {
|
||||
loading.show();
|
||||
saveUser();
|
||||
e.preventDefault();
|
||||
|
@ -122,19 +158,19 @@ const NewUserPage: FunctionComponent = () => {
|
|||
return false;
|
||||
};
|
||||
|
||||
element?.current?.querySelector('.chkEnableAllChannels').addEventListener('change', function (this: HTMLInputElement) {
|
||||
const channelAccessListContainer = element?.current?.querySelector('.channelAccessListContainer');
|
||||
(page.querySelector('.chkEnableAllChannels') as HTMLInputElement).addEventListener('change', function (this: HTMLInputElement) {
|
||||
const channelAccessListContainer = page.querySelector('.channelAccessListContainer') as HTMLDivElement;
|
||||
this.checked ? channelAccessListContainer.classList.add('hide') : channelAccessListContainer.classList.remove('hide');
|
||||
});
|
||||
|
||||
element?.current?.querySelector('.chkEnableAllFolders').addEventListener('change', function (this: HTMLInputElement) {
|
||||
const folderAccessListContainer = element?.current?.querySelector('.folderAccessListContainer');
|
||||
(page.querySelector('.chkEnableAllFolders') as HTMLInputElement).addEventListener('change', function (this: HTMLInputElement) {
|
||||
const folderAccessListContainer = page.querySelector('.folderAccessListContainer') as HTMLDivElement;
|
||||
this.checked ? folderAccessListContainer.classList.add('hide') : folderAccessListContainer.classList.remove('hide');
|
||||
});
|
||||
|
||||
element?.current?.querySelector('.newUserProfileForm').addEventListener('submit', onSubmit);
|
||||
(page.querySelector('.newUserProfileForm') as HTMLFormElement).addEventListener('submit', onSubmit);
|
||||
|
||||
element?.current?.querySelector('.button-cancel').addEventListener('click', function() {
|
||||
(page.querySelector('.button-cancel') as HTMLButtonElement).addEventListener('click', function() {
|
||||
window.history.back();
|
||||
});
|
||||
}, [loadUser]);
|
||||
|
|
|
@ -12,7 +12,7 @@ type SearchProps = {
|
|||
};
|
||||
|
||||
const SearchPage: FunctionComponent<SearchProps> = ({ serverId, parentId, collectionType }: SearchProps) => {
|
||||
const [ query, setQuery ] = useState(null);
|
||||
const [ query, setQuery ] = useState<string>();
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { SyncPlayUserAccessType, UserDto } from '@thornbill/jellyfin-sdk/dist/generated-client';
|
||||
import React, { FunctionComponent, useCallback, useEffect, useState, useRef } from 'react';
|
||||
import Dashboard from '../../scripts/clientUtils';
|
||||
import globalize from '../../scripts/globalize';
|
||||
|
@ -23,16 +24,16 @@ type ItemsArr = {
|
|||
|
||||
const UserEditPage: FunctionComponent = () => {
|
||||
const [ userName, setUserName ] = useState('');
|
||||
const [ deleteFoldersAccess, setDeleteFoldersAccess ] = useState([]);
|
||||
const [ deleteFoldersAccess, setDeleteFoldersAccess ] = useState<ItemsArr[]>([]);
|
||||
const [ authProviders, setAuthProviders ] = useState([]);
|
||||
const [ passwordResetProviders, setPasswordResetProviders ] = useState([]);
|
||||
|
||||
const [ authenticationProviderId, setAuthenticationProviderId ] = useState('');
|
||||
const [ passwordResetProviderId, setPasswordResetProviderId ] = useState('');
|
||||
|
||||
const element = useRef(null);
|
||||
const element = useRef<HTMLDivElement>(null);
|
||||
|
||||
const triggerChange = (select) => {
|
||||
const triggerChange = (select: HTMLInputElement) => {
|
||||
const evt = document.createEvent('HTMLEvents');
|
||||
evt.initEvent('change', false, true);
|
||||
select.dispatchEvent(evt);
|
||||
|
@ -44,7 +45,14 @@ const UserEditPage: FunctionComponent = () => {
|
|||
};
|
||||
|
||||
const loadAuthProviders = useCallback((user, providers) => {
|
||||
const fldSelectLoginProvider = element?.current?.querySelector('.fldSelectLoginProvider');
|
||||
const page = element.current;
|
||||
|
||||
if (!page) {
|
||||
console.error('Unexpected null reference');
|
||||
return;
|
||||
}
|
||||
|
||||
const fldSelectLoginProvider = page.querySelector('.fldSelectLoginProvider') as HTMLDivElement;
|
||||
providers.length > 1 ? fldSelectLoginProvider.classList.remove('hide') : fldSelectLoginProvider.classList.add('hide');
|
||||
|
||||
setAuthProviders(providers);
|
||||
|
@ -54,7 +62,14 @@ const UserEditPage: FunctionComponent = () => {
|
|||
}, []);
|
||||
|
||||
const loadPasswordResetProviders = useCallback((user, providers) => {
|
||||
const fldSelectPasswordResetProvider = element?.current?.querySelector('.fldSelectPasswordResetProvider');
|
||||
const page = element.current;
|
||||
|
||||
if (!page) {
|
||||
console.error('Unexpected null reference');
|
||||
return;
|
||||
}
|
||||
|
||||
const fldSelectPasswordResetProvider = page.querySelector('.fldSelectPasswordResetProvider') as HTMLDivElement;
|
||||
providers.length > 1 ? fldSelectPasswordResetProvider.classList.remove('hide') : fldSelectPasswordResetProvider.classList.add('hide');
|
||||
|
||||
setPasswordResetProviders(providers);
|
||||
|
@ -64,6 +79,13 @@ const UserEditPage: FunctionComponent = () => {
|
|||
}, []);
|
||||
|
||||
const loadDeleteFolders = useCallback((user, mediaFolders) => {
|
||||
const page = element.current;
|
||||
|
||||
if (!page) {
|
||||
console.error('Unexpected null reference');
|
||||
return;
|
||||
}
|
||||
|
||||
window.ApiClient.getJSON(window.ApiClient.getUrl('Channels', {
|
||||
SupportsMediaDeletion: true
|
||||
})).then(function (channelsResult) {
|
||||
|
@ -93,13 +115,20 @@ const UserEditPage: FunctionComponent = () => {
|
|||
|
||||
setDeleteFoldersAccess(itemsArr);
|
||||
|
||||
const chkEnableDeleteAllFolders = element.current.querySelector('.chkEnableDeleteAllFolders');
|
||||
const chkEnableDeleteAllFolders = page.querySelector('.chkEnableDeleteAllFolders') as HTMLInputElement;
|
||||
chkEnableDeleteAllFolders.checked = user.Policy.EnableContentDeletion;
|
||||
triggerChange(chkEnableDeleteAllFolders);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const loadUser = useCallback((user) => {
|
||||
const page = element.current;
|
||||
|
||||
if (!page) {
|
||||
console.error('Unexpected null reference');
|
||||
return;
|
||||
}
|
||||
|
||||
window.ApiClient.getJSON(window.ApiClient.getUrl('Auth/Providers')).then(function (providers) {
|
||||
loadAuthProviders(user, providers);
|
||||
});
|
||||
|
@ -112,37 +141,38 @@ const UserEditPage: FunctionComponent = () => {
|
|||
loadDeleteFolders(user, folders.Items);
|
||||
});
|
||||
|
||||
const disabledUserBanner = element?.current?.querySelector('.disabledUserBanner');
|
||||
const disabledUserBanner = page.querySelector('.disabledUserBanner') as HTMLDivElement;
|
||||
user.Policy.IsDisabled ? disabledUserBanner.classList.remove('hide') : disabledUserBanner.classList.add('hide');
|
||||
|
||||
const txtUserName = element?.current?.querySelector('#txtUserName');
|
||||
txtUserName.disabled = '';
|
||||
const txtUserName = page.querySelector('#txtUserName') as HTMLInputElement;
|
||||
txtUserName.disabled = false;
|
||||
txtUserName.removeAttribute('disabled');
|
||||
|
||||
const lnkEditUserPreferences = element?.current?.querySelector('.lnkEditUserPreferences');
|
||||
const lnkEditUserPreferences = page.querySelector('.lnkEditUserPreferences') as HTMLDivElement;
|
||||
lnkEditUserPreferences.setAttribute('href', 'mypreferencesmenu.html?userId=' + user.Id);
|
||||
LibraryMenu.setTitle(user.Name);
|
||||
setUserName(user.Name);
|
||||
element.current.querySelector('#txtUserName').value = user.Name;
|
||||
element.current.querySelector('.chkIsAdmin').checked = user.Policy.IsAdministrator;
|
||||
element.current.querySelector('.chkDisabled').checked = user.Policy.IsDisabled;
|
||||
element.current.querySelector('.chkIsHidden').checked = user.Policy.IsHidden;
|
||||
element.current.querySelector('.chkRemoteControlSharedDevices').checked = user.Policy.EnableSharedDeviceControl;
|
||||
element.current.querySelector('.chkEnableRemoteControlOtherUsers').checked = user.Policy.EnableRemoteControlOfOtherUsers;
|
||||
element.current.querySelector('.chkEnableDownloading').checked = user.Policy.EnableContentDownloading;
|
||||
element.current.querySelector('.chkManageLiveTv').checked = user.Policy.EnableLiveTvManagement;
|
||||
element.current.querySelector('.chkEnableLiveTvAccess').checked = user.Policy.EnableLiveTvAccess;
|
||||
element.current.querySelector('.chkEnableMediaPlayback').checked = user.Policy.EnableMediaPlayback;
|
||||
element.current.querySelector('.chkEnableAudioPlaybackTranscoding').checked = user.Policy.EnableAudioPlaybackTranscoding;
|
||||
element.current.querySelector('.chkEnableVideoPlaybackTranscoding').checked = user.Policy.EnableVideoPlaybackTranscoding;
|
||||
element.current.querySelector('.chkEnableVideoPlaybackRemuxing').checked = user.Policy.EnablePlaybackRemuxing;
|
||||
element.current.querySelector('.chkForceRemoteSourceTranscoding').checked = user.Policy.ForceRemoteSourceTranscoding;
|
||||
element.current.querySelector('.chkRemoteAccess').checked = user.Policy.EnableRemoteAccess == null || user.Policy.EnableRemoteAccess;
|
||||
element.current.querySelector('#txtRemoteClientBitrateLimit').value = user.Policy.RemoteClientBitrateLimit / 1e6 || '';
|
||||
element.current.querySelector('#txtLoginAttemptsBeforeLockout').value = user.Policy.LoginAttemptsBeforeLockout || '0';
|
||||
element.current.querySelector('#txtMaxActiveSessions').value = user.Policy.MaxActiveSessions || '0';
|
||||
(page.querySelector('#txtUserName') as HTMLInputElement).value = user.Name;
|
||||
(page.querySelector('.chkIsAdmin') as HTMLInputElement).checked = user.Policy.IsAdministrator;
|
||||
(page.querySelector('.chkDisabled') as HTMLInputElement).checked = user.Policy.IsDisabled;
|
||||
(page.querySelector('.chkIsHidden') as HTMLInputElement).checked = user.Policy.IsHidden;
|
||||
(page.querySelector('.chkRemoteControlSharedDevices') as HTMLInputElement).checked = user.Policy.EnableSharedDeviceControl;
|
||||
(page.querySelector('.chkEnableRemoteControlOtherUsers') as HTMLInputElement).checked = user.Policy.EnableRemoteControlOfOtherUsers;
|
||||
(page.querySelector('.chkEnableDownloading') as HTMLInputElement).checked = user.Policy.EnableContentDownloading;
|
||||
(page.querySelector('.chkManageLiveTv') as HTMLInputElement).checked = user.Policy.EnableLiveTvManagement;
|
||||
(page.querySelector('.chkEnableLiveTvAccess') as HTMLInputElement).checked = user.Policy.EnableLiveTvAccess;
|
||||
(page.querySelector('.chkEnableMediaPlayback') as HTMLInputElement).checked = user.Policy.EnableMediaPlayback;
|
||||
(page.querySelector('.chkEnableAudioPlaybackTranscoding') as HTMLInputElement).checked = user.Policy.EnableAudioPlaybackTranscoding;
|
||||
(page.querySelector('.chkEnableVideoPlaybackTranscoding') as HTMLInputElement).checked = user.Policy.EnableVideoPlaybackTranscoding;
|
||||
(page.querySelector('.chkEnableVideoPlaybackRemuxing') as HTMLInputElement).checked = user.Policy.EnablePlaybackRemuxing;
|
||||
(page.querySelector('.chkForceRemoteSourceTranscoding') as HTMLInputElement).checked = user.Policy.ForceRemoteSourceTranscoding;
|
||||
(page.querySelector('.chkRemoteAccess') as HTMLInputElement).checked = user.Policy.EnableRemoteAccess == null || user.Policy.EnableRemoteAccess;
|
||||
(page.querySelector('#txtRemoteClientBitrateLimit') as HTMLInputElement).value = user.Policy.RemoteClientBitrateLimit > 0 ?
|
||||
(user.Policy.RemoteClientBitrateLimit / 1e6).toLocaleString(undefined, {maximumFractionDigits: 6}) : '';
|
||||
(page.querySelector('#txtLoginAttemptsBeforeLockout') as HTMLInputElement).value = user.Policy.LoginAttemptsBeforeLockout || '0';
|
||||
(page.querySelector('#txtMaxActiveSessions') as HTMLInputElement).value = user.Policy.MaxActiveSessions || '0';
|
||||
if (window.ApiClient.isMinServerVersion('10.6.0')) {
|
||||
element.current.querySelector('#selectSyncPlayAccess').value = user.Policy.SyncPlayAccess;
|
||||
(page.querySelector('#selectSyncPlayAccess') as HTMLInputElement).value = user.Policy.SyncPlayAccess;
|
||||
}
|
||||
loading.hide();
|
||||
}, [loadAuthProviders, loadPasswordResetProviders, loadDeleteFolders ]);
|
||||
|
@ -155,6 +185,13 @@ const UserEditPage: FunctionComponent = () => {
|
|||
}, [loadUser]);
|
||||
|
||||
useEffect(() => {
|
||||
const page = element.current;
|
||||
|
||||
if (!page) {
|
||||
console.error('Unexpected null reference');
|
||||
return;
|
||||
}
|
||||
|
||||
loadData();
|
||||
|
||||
function onSaveComplete() {
|
||||
|
@ -163,44 +200,52 @@ const UserEditPage: FunctionComponent = () => {
|
|||
toast(globalize.translate('SettingsSaved'));
|
||||
}
|
||||
|
||||
const saveUser = (user) => {
|
||||
user.Name = element?.current?.querySelector('#txtUserName').value;
|
||||
user.Policy.IsAdministrator = element?.current?.querySelector('.chkIsAdmin').checked;
|
||||
user.Policy.IsHidden = element?.current?.querySelector('.chkIsHidden').checked;
|
||||
user.Policy.IsDisabled = element?.current?.querySelector('.chkDisabled').checked;
|
||||
user.Policy.EnableRemoteControlOfOtherUsers = element?.current?.querySelector('.chkEnableRemoteControlOtherUsers').checked;
|
||||
user.Policy.EnableLiveTvManagement = element?.current?.querySelector('.chkManageLiveTv').checked;
|
||||
user.Policy.EnableLiveTvAccess = element?.current?.querySelector('.chkEnableLiveTvAccess').checked;
|
||||
user.Policy.EnableSharedDeviceControl = element?.current?.querySelector('.chkRemoteControlSharedDevices').checked;
|
||||
user.Policy.EnableMediaPlayback = element?.current?.querySelector('.chkEnableMediaPlayback').checked;
|
||||
user.Policy.EnableAudioPlaybackTranscoding = element?.current?.querySelector('.chkEnableAudioPlaybackTranscoding').checked;
|
||||
user.Policy.EnableVideoPlaybackTranscoding = element?.current?.querySelector('.chkEnableVideoPlaybackTranscoding').checked;
|
||||
user.Policy.EnablePlaybackRemuxing = element?.current?.querySelector('.chkEnableVideoPlaybackRemuxing').checked;
|
||||
user.Policy.ForceRemoteSourceTranscoding = element?.current?.querySelector('.chkForceRemoteSourceTranscoding').checked;
|
||||
user.Policy.EnableContentDownloading = element?.current?.querySelector('.chkEnableDownloading').checked;
|
||||
user.Policy.EnableRemoteAccess = element?.current?.querySelector('.chkRemoteAccess').checked;
|
||||
user.Policy.RemoteClientBitrateLimit = Math.floor(1e6 * parseFloat(element?.current?.querySelector('#txtRemoteClientBitrateLimit').value || '0'));
|
||||
user.Policy.LoginAttemptsBeforeLockout = parseInt(element?.current?.querySelector('#txtLoginAttemptsBeforeLockout').value || '0');
|
||||
user.Policy.MaxActiveSessions = parseInt(element?.current?.querySelector('#txtMaxActiveSessions').value || '0');
|
||||
user.Policy.AuthenticationProviderId = element?.current?.querySelector('.selectLoginProvider').value;
|
||||
user.Policy.PasswordResetProviderId = element?.current?.querySelector('.selectPasswordResetProvider').value;
|
||||
user.Policy.EnableContentDeletion = element?.current?.querySelector('.chkEnableDeleteAllFolders').checked;
|
||||
user.Policy.EnableContentDeletionFromFolders = user.Policy.EnableContentDeletion ? [] : Array.prototype.filter.call(element?.current?.querySelectorAll('.chkFolder'), function (c) {
|
||||
const saveUser = (user: UserDto) => {
|
||||
if (!user.Id) {
|
||||
throw new Error('Unexpected null user.Id');
|
||||
}
|
||||
|
||||
if (!user.Policy) {
|
||||
throw new Error('Unexpected null user.Policy');
|
||||
}
|
||||
|
||||
user.Name = (page.querySelector('#txtUserName') as HTMLInputElement).value;
|
||||
user.Policy.IsAdministrator = (page.querySelector('.chkIsAdmin') as HTMLInputElement).checked;
|
||||
user.Policy.IsHidden = (page.querySelector('.chkIsHidden') as HTMLInputElement).checked;
|
||||
user.Policy.IsDisabled = (page.querySelector('.chkDisabled') as HTMLInputElement).checked;
|
||||
user.Policy.EnableRemoteControlOfOtherUsers = (page.querySelector('.chkEnableRemoteControlOtherUsers') as HTMLInputElement).checked;
|
||||
user.Policy.EnableLiveTvManagement = (page.querySelector('.chkManageLiveTv') as HTMLInputElement).checked;
|
||||
user.Policy.EnableLiveTvAccess = (page.querySelector('.chkEnableLiveTvAccess') as HTMLInputElement).checked;
|
||||
user.Policy.EnableSharedDeviceControl = (page.querySelector('.chkRemoteControlSharedDevices') as HTMLInputElement).checked;
|
||||
user.Policy.EnableMediaPlayback = (page.querySelector('.chkEnableMediaPlayback') as HTMLInputElement).checked;
|
||||
user.Policy.EnableAudioPlaybackTranscoding = (page.querySelector('.chkEnableAudioPlaybackTranscoding') as HTMLInputElement).checked;
|
||||
user.Policy.EnableVideoPlaybackTranscoding = (page.querySelector('.chkEnableVideoPlaybackTranscoding') as HTMLInputElement).checked;
|
||||
user.Policy.EnablePlaybackRemuxing = (page.querySelector('.chkEnableVideoPlaybackRemuxing') as HTMLInputElement).checked;
|
||||
user.Policy.ForceRemoteSourceTranscoding = (page.querySelector('.chkForceRemoteSourceTranscoding') as HTMLInputElement).checked;
|
||||
user.Policy.EnableContentDownloading = (page.querySelector('.chkEnableDownloading') as HTMLInputElement).checked;
|
||||
user.Policy.EnableRemoteAccess = (page.querySelector('.chkRemoteAccess') as HTMLInputElement).checked;
|
||||
user.Policy.RemoteClientBitrateLimit = Math.floor(1e6 * parseFloat((page.querySelector('#txtRemoteClientBitrateLimit') as HTMLInputElement).value || '0'));
|
||||
user.Policy.LoginAttemptsBeforeLockout = parseInt((page.querySelector('#txtLoginAttemptsBeforeLockout') as HTMLInputElement).value || '0');
|
||||
user.Policy.MaxActiveSessions = parseInt((page.querySelector('#txtMaxActiveSessions') as HTMLInputElement).value || '0');
|
||||
user.Policy.AuthenticationProviderId = (page.querySelector('.selectLoginProvider') as HTMLInputElement).value;
|
||||
user.Policy.PasswordResetProviderId = (page.querySelector('.selectPasswordResetProvider') as HTMLInputElement).value;
|
||||
user.Policy.EnableContentDeletion = (page.querySelector('.chkEnableDeleteAllFolders') as HTMLInputElement).checked;
|
||||
user.Policy.EnableContentDeletionFromFolders = user.Policy.EnableContentDeletion ? [] : Array.prototype.filter.call(page.querySelectorAll('.chkFolder'), function (c) {
|
||||
return c.checked;
|
||||
}).map(function (c) {
|
||||
return c.getAttribute('data-id');
|
||||
});
|
||||
if (window.ApiClient.isMinServerVersion('10.6.0')) {
|
||||
user.Policy.SyncPlayAccess = element?.current?.querySelector('#selectSyncPlayAccess').value;
|
||||
user.Policy.SyncPlayAccess = (page.querySelector('#selectSyncPlayAccess') as HTMLInputElement).value as SyncPlayUserAccessType;
|
||||
}
|
||||
window.ApiClient.updateUser(user).then(function () {
|
||||
window.ApiClient.updateUserPolicy(user.Id, user.Policy).then(function () {
|
||||
window.ApiClient.updateUserPolicy(user.Id || '', user.Policy || {}).then(function () {
|
||||
onSaveComplete();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const onSubmit = (e) => {
|
||||
const onSubmit = (e: Event) => {
|
||||
loading.show();
|
||||
getUser().then(function (result) {
|
||||
saveUser(result);
|
||||
|
@ -210,22 +255,22 @@ const UserEditPage: FunctionComponent = () => {
|
|||
return false;
|
||||
};
|
||||
|
||||
element?.current?.querySelector('.chkEnableDeleteAllFolders').addEventListener('change', function (this: HTMLInputElement) {
|
||||
(page.querySelector('.chkEnableDeleteAllFolders') as HTMLInputElement).addEventListener('change', function (this: HTMLInputElement) {
|
||||
if (this.checked) {
|
||||
element?.current?.querySelector('.deleteAccess').classList.add('hide');
|
||||
(page.querySelector('.deleteAccess') as HTMLDivElement).classList.add('hide');
|
||||
} else {
|
||||
element?.current?.querySelector('.deleteAccess').classList.remove('hide');
|
||||
(page.querySelector('.deleteAccess') as HTMLDivElement).classList.remove('hide');
|
||||
}
|
||||
});
|
||||
|
||||
window.ApiClient.getServerConfiguration().then(function (config) {
|
||||
const fldRemoteAccess = element?.current?.querySelector('.fldRemoteAccess');
|
||||
const fldRemoteAccess = page.querySelector('.fldRemoteAccess') as HTMLDivElement;
|
||||
config.EnableRemoteAccess ? fldRemoteAccess.classList.remove('hide') : fldRemoteAccess.classList.add('hide');
|
||||
});
|
||||
|
||||
element?.current?.querySelector('.editUserProfileForm').addEventListener('submit', onSubmit);
|
||||
(page.querySelector('.editUserProfileForm') as HTMLFormElement).addEventListener('submit', onSubmit);
|
||||
|
||||
element?.current?.querySelector('.button-cancel').addEventListener('click', function() {
|
||||
(page.querySelector('.button-cancel') as HTMLButtonElement).addEventListener('click', function() {
|
||||
window.history.back();
|
||||
});
|
||||
}, [loadData]);
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { UserDto } from '@thornbill/jellyfin-sdk/dist/generated-client';
|
||||
import React, { FunctionComponent, useCallback, useEffect, useState, useRef } from 'react';
|
||||
|
||||
import loading from '../loading/loading';
|
||||
|
@ -20,19 +21,26 @@ type ItemsArr = {
|
|||
|
||||
const UserLibraryAccessPage: FunctionComponent = () => {
|
||||
const [ userName, setUserName ] = useState('');
|
||||
const [channelsItems, setChannelsItems] = useState([]);
|
||||
const [mediaFoldersItems, setMediaFoldersItems] = useState([]);
|
||||
const [devicesItems, setDevicesItems] = useState([]);
|
||||
const [channelsItems, setChannelsItems] = useState<ItemsArr[]>([]);
|
||||
const [mediaFoldersItems, setMediaFoldersItems] = useState<ItemsArr[]>([]);
|
||||
const [devicesItems, setDevicesItems] = useState<ItemsArr[]>([]);
|
||||
|
||||
const element = useRef(null);
|
||||
const element = useRef<HTMLDivElement>(null);
|
||||
|
||||
const triggerChange = (select) => {
|
||||
const triggerChange = (select: HTMLInputElement) => {
|
||||
const evt = document.createEvent('HTMLEvents');
|
||||
evt.initEvent('change', false, true);
|
||||
select.dispatchEvent(evt);
|
||||
};
|
||||
|
||||
const loadMediaFolders = useCallback((user, mediaFolders) => {
|
||||
const page = element.current;
|
||||
|
||||
if (!page) {
|
||||
console.error('Unexpected null reference');
|
||||
return;
|
||||
}
|
||||
|
||||
const itemsArr: ItemsArr[] = [];
|
||||
|
||||
for (const folder of mediaFolders) {
|
||||
|
@ -47,12 +55,19 @@ const UserLibraryAccessPage: FunctionComponent = () => {
|
|||
|
||||
setMediaFoldersItems(itemsArr);
|
||||
|
||||
const chkEnableAllFolders = element.current.querySelector('.chkEnableAllFolders');
|
||||
const chkEnableAllFolders = page.querySelector('.chkEnableAllFolders') as HTMLInputElement;
|
||||
chkEnableAllFolders.checked = user.Policy.EnableAllFolders;
|
||||
triggerChange(chkEnableAllFolders);
|
||||
}, []);
|
||||
|
||||
const loadChannels = useCallback((user, channels) => {
|
||||
const page = element.current;
|
||||
|
||||
if (!page) {
|
||||
console.error('Unexpected null reference');
|
||||
return;
|
||||
}
|
||||
|
||||
const itemsArr: ItemsArr[] = [];
|
||||
|
||||
for (const folder of channels) {
|
||||
|
@ -68,17 +83,24 @@ const UserLibraryAccessPage: FunctionComponent = () => {
|
|||
setChannelsItems(itemsArr);
|
||||
|
||||
if (channels.length) {
|
||||
element?.current?.querySelector('.channelAccessContainer').classList.remove('hide');
|
||||
(page.querySelector('.channelAccessContainer') as HTMLDivElement).classList.remove('hide');
|
||||
} else {
|
||||
element?.current?.querySelector('.channelAccessContainer').classList.add('hide');
|
||||
(page.querySelector('.channelAccessContainer') as HTMLDivElement).classList.add('hide');
|
||||
}
|
||||
|
||||
const chkEnableAllChannels = element.current.querySelector('.chkEnableAllChannels');
|
||||
const chkEnableAllChannels = page.querySelector('.chkEnableAllChannels') as HTMLInputElement;
|
||||
chkEnableAllChannels.checked = user.Policy.EnableAllChannels;
|
||||
triggerChange(chkEnableAllChannels);
|
||||
}, []);
|
||||
|
||||
const loadDevices = useCallback((user, devices) => {
|
||||
const page = element.current;
|
||||
|
||||
if (!page) {
|
||||
console.error('Unexpected null reference');
|
||||
return;
|
||||
}
|
||||
|
||||
const itemsArr: ItemsArr[] = [];
|
||||
|
||||
for (const device of devices) {
|
||||
|
@ -94,14 +116,14 @@ const UserLibraryAccessPage: FunctionComponent = () => {
|
|||
|
||||
setDevicesItems(itemsArr);
|
||||
|
||||
const chkEnableAllDevices = element.current.querySelector('.chkEnableAllDevices');
|
||||
const chkEnableAllDevices = page.querySelector('.chkEnableAllDevices') as HTMLInputElement;
|
||||
chkEnableAllDevices.checked = user.Policy.EnableAllDevices;
|
||||
triggerChange(chkEnableAllDevices);
|
||||
|
||||
if (user.Policy.IsAdministrator) {
|
||||
element?.current?.querySelector('.deviceAccessContainer').classList.add('hide');
|
||||
(page.querySelector('.deviceAccessContainer') as HTMLDivElement).classList.add('hide');
|
||||
} else {
|
||||
element?.current?.querySelector('.deviceAccessContainer').classList.remove('hide');
|
||||
(page.querySelector('.deviceAccessContainer') as HTMLDivElement).classList.remove('hide');
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
@ -129,9 +151,16 @@ const UserLibraryAccessPage: FunctionComponent = () => {
|
|||
}, [loadUser]);
|
||||
|
||||
useEffect(() => {
|
||||
const page = element.current;
|
||||
|
||||
if (!page) {
|
||||
console.error('Unexpected null reference');
|
||||
return;
|
||||
}
|
||||
|
||||
loadData();
|
||||
|
||||
const onSubmit = (e) => {
|
||||
const onSubmit = (e: Event) => {
|
||||
loading.show();
|
||||
const userId = appRouter.param('userId');
|
||||
window.ApiClient.getUser(userId).then(function (result) {
|
||||
|
@ -142,21 +171,29 @@ const UserLibraryAccessPage: FunctionComponent = () => {
|
|||
return false;
|
||||
};
|
||||
|
||||
const saveUser = (user) => {
|
||||
user.Policy.EnableAllFolders = element?.current?.querySelector('.chkEnableAllFolders').checked;
|
||||
user.Policy.EnabledFolders = user.Policy.EnableAllFolders ? [] : Array.prototype.filter.call(element?.current?.querySelectorAll('.chkFolder'), function (c) {
|
||||
const saveUser = (user: UserDto) => {
|
||||
if (!user.Id) {
|
||||
throw new Error('Unexpected null user.Id');
|
||||
}
|
||||
|
||||
if (!user.Policy) {
|
||||
throw new Error('Unexpected null user.Policy');
|
||||
}
|
||||
|
||||
user.Policy.EnableAllFolders = (page.querySelector('.chkEnableAllFolders') as HTMLInputElement).checked;
|
||||
user.Policy.EnabledFolders = user.Policy.EnableAllFolders ? [] : Array.prototype.filter.call(page.querySelectorAll('.chkFolder'), function (c) {
|
||||
return c.checked;
|
||||
}).map(function (c) {
|
||||
return c.getAttribute('data-id');
|
||||
});
|
||||
user.Policy.EnableAllChannels = element?.current?.querySelector('.chkEnableAllChannels').checked;
|
||||
user.Policy.EnabledChannels = user.Policy.EnableAllChannels ? [] : Array.prototype.filter.call(element?.current?.querySelectorAll('.chkChannel'), function (c) {
|
||||
user.Policy.EnableAllChannels = (page.querySelector('.chkEnableAllChannels') as HTMLInputElement).checked;
|
||||
user.Policy.EnabledChannels = user.Policy.EnableAllChannels ? [] : Array.prototype.filter.call(page.querySelectorAll('.chkChannel'), function (c) {
|
||||
return c.checked;
|
||||
}).map(function (c) {
|
||||
return c.getAttribute('data-id');
|
||||
});
|
||||
user.Policy.EnableAllDevices = element?.current?.querySelector('.chkEnableAllDevices').checked;
|
||||
user.Policy.EnabledDevices = user.Policy.EnableAllDevices ? [] : Array.prototype.filter.call(element?.current?.querySelectorAll('.chkDevice'), function (c) {
|
||||
user.Policy.EnableAllDevices = (page.querySelector('.chkEnableAllDevices') as HTMLInputElement).checked;
|
||||
user.Policy.EnabledDevices = user.Policy.EnableAllDevices ? [] : Array.prototype.filter.call(page.querySelectorAll('.chkDevice'), function (c) {
|
||||
return c.checked;
|
||||
}).map(function (c) {
|
||||
return c.getAttribute('data-id');
|
||||
|
@ -173,19 +210,19 @@ const UserLibraryAccessPage: FunctionComponent = () => {
|
|||
toast(globalize.translate('SettingsSaved'));
|
||||
};
|
||||
|
||||
element?.current?.querySelector('.chkEnableAllDevices').addEventListener('change', function (this: HTMLInputElement) {
|
||||
element?.current?.querySelector('.deviceAccessListContainer').classList.toggle('hide', this.checked);
|
||||
(page.querySelector('.chkEnableAllDevices') as HTMLInputElement).addEventListener('change', function (this: HTMLInputElement) {
|
||||
(page.querySelector('.deviceAccessListContainer') as HTMLDivElement).classList.toggle('hide', this.checked);
|
||||
});
|
||||
|
||||
element?.current?.querySelector('.chkEnableAllChannels').addEventListener('change', function (this: HTMLInputElement) {
|
||||
element?.current?.querySelector('.channelAccessListContainer').classList.toggle('hide', this.checked);
|
||||
(page.querySelector('.chkEnableAllChannels') as HTMLInputElement).addEventListener('change', function (this: HTMLInputElement) {
|
||||
(page.querySelector('.channelAccessListContainer') as HTMLDivElement).classList.toggle('hide', this.checked);
|
||||
});
|
||||
|
||||
element?.current?.querySelector('.chkEnableAllFolders').addEventListener('change', function (this: HTMLInputElement) {
|
||||
element?.current?.querySelector('.folderAccessListContainer').classList.toggle('hide', this.checked);
|
||||
(page.querySelector('.chkEnableAllFolders') as HTMLInputElement).addEventListener('change', function (this: HTMLInputElement) {
|
||||
(page.querySelector('.folderAccessListContainer') as HTMLDivElement).classList.toggle('hide', this.checked);
|
||||
});
|
||||
|
||||
element?.current?.querySelector('.userLibraryAccessForm').addEventListener('submit', onSubmit);
|
||||
(page.querySelector('.userLibraryAccessForm') as HTMLFormElement).addEventListener('submit', onSubmit);
|
||||
}, [loadData]);
|
||||
|
||||
return (
|
||||
|
|
429
src/components/pages/UserParentalControl.tsx
Normal file
429
src/components/pages/UserParentalControl.tsx
Normal file
|
@ -0,0 +1,429 @@
|
|||
import { AccessSchedule, DynamicDayOfWeek, UserDto } from '@thornbill/jellyfin-sdk/dist/generated-client';
|
||||
import React, { FunctionComponent, useCallback, useEffect, useState, useRef } from 'react';
|
||||
import globalize from '../../scripts/globalize';
|
||||
import LibraryMenu from '../../scripts/libraryMenu';
|
||||
import { appRouter } from '../appRouter';
|
||||
import AccessScheduleList from '../dashboard/users/AccessScheduleList';
|
||||
import BlockedTagList from '../dashboard/users/BlockedTagList';
|
||||
import ButtonElement from '../dashboard/users/ButtonElement';
|
||||
import CheckBoxListItem from '../dashboard/users/CheckBoxListItem';
|
||||
import SectionTitleButtonElement from '../dashboard/users/SectionTitleButtonElement';
|
||||
import SectionTitleLinkElement from '../dashboard/users/SectionTitleLinkElement';
|
||||
import SelectMaxParentalRating from '../dashboard/users/SelectMaxParentalRating';
|
||||
import SectionTabs from '../dashboard/users/SectionTabs';
|
||||
import loading from '../loading/loading';
|
||||
import toast from '../toast/toast';
|
||||
|
||||
type RatingsArr = {
|
||||
Name: string;
|
||||
Value: number;
|
||||
}
|
||||
|
||||
type ItemsArr = {
|
||||
name: string;
|
||||
value: string;
|
||||
checkedAttribute: string
|
||||
}
|
||||
|
||||
const UserParentalControl: FunctionComponent = () => {
|
||||
const [ userName, setUserName ] = useState('');
|
||||
const [ parentalRatings, setParentalRatings ] = useState<RatingsArr[]>([]);
|
||||
const [ unratedItems, setUnratedItems ] = useState<ItemsArr[]>([]);
|
||||
const [ accessSchedules, setAccessSchedules ] = useState<AccessSchedule[]>([]);
|
||||
const [ blockedTags, setBlockedTags ] = useState([]);
|
||||
|
||||
const element = useRef<HTMLDivElement>(null);
|
||||
|
||||
const populateRatings = useCallback((allParentalRatings) => {
|
||||
let rating;
|
||||
const ratings: RatingsArr[] = [];
|
||||
|
||||
for (let i = 0, length = allParentalRatings.length; i < length; i++) {
|
||||
rating = allParentalRatings[i];
|
||||
|
||||
if (ratings.length) {
|
||||
const lastRating = ratings[ratings.length - 1];
|
||||
|
||||
if (lastRating.Value === rating.Value) {
|
||||
lastRating.Name += '/' + rating.Name;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
ratings.push({
|
||||
Name: rating.Name,
|
||||
Value: rating.Value
|
||||
});
|
||||
}
|
||||
|
||||
setParentalRatings(ratings);
|
||||
}, []);
|
||||
|
||||
const loadUnratedItems = useCallback((user) => {
|
||||
const page = element.current;
|
||||
|
||||
if (!page) {
|
||||
console.error('Unexpected null reference');
|
||||
return;
|
||||
}
|
||||
|
||||
const items = [{
|
||||
name: globalize.translate('Books'),
|
||||
value: 'Book'
|
||||
}, {
|
||||
name: globalize.translate('Channels'),
|
||||
value: 'ChannelContent'
|
||||
}, {
|
||||
name: globalize.translate('LiveTV'),
|
||||
value: 'LiveTvChannel'
|
||||
}, {
|
||||
name: globalize.translate('Movies'),
|
||||
value: 'Movie'
|
||||
}, {
|
||||
name: globalize.translate('Music'),
|
||||
value: 'Music'
|
||||
}, {
|
||||
name: globalize.translate('Trailers'),
|
||||
value: 'Trailer'
|
||||
}, {
|
||||
name: globalize.translate('Shows'),
|
||||
value: 'Series'
|
||||
}];
|
||||
|
||||
const itemsArr: ItemsArr[] = [];
|
||||
|
||||
for (const item of items) {
|
||||
const isChecked = user.Policy.BlockUnratedItems.indexOf(item.value) != -1;
|
||||
const checkedAttribute = isChecked ? ' checked="checked"' : '';
|
||||
itemsArr.push({
|
||||
value: item.value,
|
||||
name: item.name,
|
||||
checkedAttribute: checkedAttribute
|
||||
});
|
||||
}
|
||||
|
||||
setUnratedItems(itemsArr);
|
||||
|
||||
const blockUnratedItems = page.querySelector('.blockUnratedItems') as HTMLDivElement;
|
||||
blockUnratedItems.dispatchEvent(new CustomEvent('create'));
|
||||
}, []);
|
||||
|
||||
const loadBlockedTags = useCallback((tags) => {
|
||||
const page = element.current;
|
||||
|
||||
if (!page) {
|
||||
console.error('Unexpected null reference');
|
||||
return;
|
||||
}
|
||||
|
||||
setBlockedTags(tags);
|
||||
|
||||
const blockedTagsElem = page.querySelector('.blockedTags') as HTMLDivElement;
|
||||
|
||||
for (const btnDeleteTag of blockedTagsElem.querySelectorAll('.btnDeleteTag')) {
|
||||
btnDeleteTag.addEventListener('click', function () {
|
||||
const tag = btnDeleteTag.getAttribute('data-tag');
|
||||
const newTags = tags.filter(function (t: string) {
|
||||
return t != tag;
|
||||
});
|
||||
loadBlockedTags(newTags);
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
const renderAccessSchedule = useCallback((schedules) => {
|
||||
const page = element.current;
|
||||
|
||||
if (!page) {
|
||||
console.error('Unexpected null reference');
|
||||
return;
|
||||
}
|
||||
|
||||
setAccessSchedules(schedules);
|
||||
|
||||
const accessScheduleList = page.querySelector('.accessScheduleList') as HTMLDivElement;
|
||||
|
||||
for (const btnDelete of accessScheduleList.querySelectorAll('.btnDelete')) {
|
||||
btnDelete.addEventListener('click', function () {
|
||||
const index = parseInt(btnDelete.getAttribute('data-index') || '0', 10);
|
||||
schedules.splice(index, 1);
|
||||
const newindex = schedules.filter(function (i: number) {
|
||||
return i != index;
|
||||
});
|
||||
renderAccessSchedule(newindex);
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
const loadUser = useCallback((user, allParentalRatings) => {
|
||||
const page = element.current;
|
||||
|
||||
if (!page) {
|
||||
console.error('Unexpected null reference');
|
||||
return;
|
||||
}
|
||||
|
||||
setUserName(user.Name);
|
||||
LibraryMenu.setTitle(user.Name);
|
||||
loadUnratedItems(user);
|
||||
|
||||
loadBlockedTags(user.Policy.BlockedTags);
|
||||
populateRatings(allParentalRatings);
|
||||
let ratingValue = '';
|
||||
|
||||
if (user.Policy.MaxParentalRating) {
|
||||
for (let i = 0, length = allParentalRatings.length; i < length; i++) {
|
||||
const rating = allParentalRatings[i];
|
||||
|
||||
if (user.Policy.MaxParentalRating >= rating.Value) {
|
||||
ratingValue = rating.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(page.querySelector('.selectMaxParentalRating') as HTMLInputElement).value = ratingValue;
|
||||
|
||||
if (user.Policy.IsAdministrator) {
|
||||
(page.querySelector('.accessScheduleSection') as HTMLDivElement).classList.add('hide');
|
||||
} else {
|
||||
(page.querySelector('.accessScheduleSection') as HTMLDivElement).classList.remove('hide');
|
||||
}
|
||||
renderAccessSchedule(user.Policy.AccessSchedules || []);
|
||||
loading.hide();
|
||||
}, [loadBlockedTags, loadUnratedItems, populateRatings, renderAccessSchedule]);
|
||||
|
||||
const loadData = useCallback(() => {
|
||||
loading.show();
|
||||
const userId = appRouter.param('userId');
|
||||
const promise1 = window.ApiClient.getUser(userId);
|
||||
const promise2 = window.ApiClient.getParentalRatings();
|
||||
Promise.all([promise1, promise2]).then(function (responses) {
|
||||
loadUser(responses[0], responses[1]);
|
||||
});
|
||||
}, [loadUser]);
|
||||
|
||||
useEffect(() => {
|
||||
const page = element.current;
|
||||
|
||||
if (!page) {
|
||||
console.error('Unexpected null reference');
|
||||
return;
|
||||
}
|
||||
|
||||
loadData();
|
||||
|
||||
const onSaveComplete = () => {
|
||||
loading.hide();
|
||||
toast(globalize.translate('SettingsSaved'));
|
||||
};
|
||||
|
||||
const saveUser = (user: UserDto) => {
|
||||
if (!user.Id) {
|
||||
throw new Error('Unexpected null user.Id');
|
||||
}
|
||||
|
||||
if (!user.Policy) {
|
||||
throw new Error('Unexpected null user.Policy');
|
||||
}
|
||||
|
||||
user.Policy.MaxParentalRating = parseInt((page.querySelector('.selectMaxParentalRating') as HTMLInputElement).value || '0', 10) || null;
|
||||
user.Policy.BlockUnratedItems = Array.prototype.filter.call(page.querySelectorAll('.chkUnratedItem'), function (i) {
|
||||
return i.checked;
|
||||
}).map(function (i) {
|
||||
return i.getAttribute('data-itemtype');
|
||||
});
|
||||
user.Policy.AccessSchedules = getSchedulesFromPage();
|
||||
user.Policy.BlockedTags = getBlockedTagsFromPage();
|
||||
window.ApiClient.updateUserPolicy(user.Id, user.Policy).then(function () {
|
||||
onSaveComplete();
|
||||
});
|
||||
};
|
||||
|
||||
const showSchedulePopup = (schedule: AccessSchedule, index: number) => {
|
||||
schedule = schedule || {};
|
||||
import('../../components/accessSchedule/accessSchedule').then(({default: accessschedule}) => {
|
||||
accessschedule.show({
|
||||
schedule: schedule
|
||||
}).then(function (updatedSchedule) {
|
||||
const schedules = getSchedulesFromPage();
|
||||
|
||||
if (index == -1) {
|
||||
index = schedules.length;
|
||||
}
|
||||
|
||||
schedules[index] = updatedSchedule;
|
||||
renderAccessSchedule(schedules);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const getSchedulesFromPage = () => {
|
||||
return Array.prototype.map.call(page.querySelectorAll('.liSchedule'), function (elem) {
|
||||
return {
|
||||
DayOfWeek: elem.getAttribute('data-day'),
|
||||
StartHour: elem.getAttribute('data-start'),
|
||||
EndHour: elem.getAttribute('data-end')
|
||||
};
|
||||
}) as AccessSchedule[];
|
||||
};
|
||||
|
||||
const getBlockedTagsFromPage = () => {
|
||||
return Array.prototype.map.call(page.querySelectorAll('.blockedTag'), function (elem) {
|
||||
return elem.getAttribute('data-tag');
|
||||
}) as string[];
|
||||
};
|
||||
|
||||
const showBlockedTagPopup = () => {
|
||||
import('../../components/prompt/prompt').then(({default: prompt}) => {
|
||||
prompt({
|
||||
label: globalize.translate('LabelTag')
|
||||
}).then(function (value) {
|
||||
const tags = getBlockedTagsFromPage();
|
||||
|
||||
if (tags.indexOf(value) == -1) {
|
||||
tags.push(value);
|
||||
loadBlockedTags(tags);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const onSubmit = (e: Event) => {
|
||||
loading.show();
|
||||
const userId = appRouter.param('userId');
|
||||
window.ApiClient.getUser(userId).then(function (result) {
|
||||
saveUser(result);
|
||||
});
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return false;
|
||||
};
|
||||
|
||||
(page.querySelector('.btnAddSchedule') as HTMLButtonElement).addEventListener('click', function () {
|
||||
showSchedulePopup({
|
||||
Id: 0,
|
||||
UserId: '',
|
||||
DayOfWeek: DynamicDayOfWeek.Sunday,
|
||||
StartHour: 0,
|
||||
EndHour: 0
|
||||
}, -1);
|
||||
});
|
||||
|
||||
(page.querySelector('.btnAddBlockedTag') as HTMLButtonElement).addEventListener('click', function () {
|
||||
showBlockedTagPopup();
|
||||
});
|
||||
|
||||
(page.querySelector('.userParentalControlForm') as HTMLFormElement).addEventListener('submit', onSubmit);
|
||||
}, [loadBlockedTags, loadData, renderAccessSchedule]);
|
||||
|
||||
return (
|
||||
<div ref={element}>
|
||||
<div className='content-primary'>
|
||||
<div className='verticalSection'>
|
||||
<div className='sectionTitleContainer flex align-items-center'>
|
||||
<h2 className='sectionTitle username'>
|
||||
{userName}
|
||||
</h2>
|
||||
<SectionTitleLinkElement
|
||||
className='raised button-alt headerHelpButton'
|
||||
title='Help'
|
||||
url='https://docs.jellyfin.org/general/server/users/'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<SectionTabs activeTab='userparentalcontrol'/>
|
||||
<form className='userParentalControlForm'>
|
||||
<div className='selectContainer'>
|
||||
<SelectMaxParentalRating
|
||||
className= 'selectMaxParentalRating'
|
||||
label= 'LabelMaxParentalRating'
|
||||
parentalRatings={parentalRatings}
|
||||
/>
|
||||
<div className='fieldDescription'>
|
||||
{globalize.translate('MaxParentalRatingHelp')}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className='blockUnratedItems'>
|
||||
<h3 className='checkboxListLabel'>
|
||||
{globalize.translate('HeaderBlockItemsWithNoRating')}
|
||||
</h3>
|
||||
<div className='checkboxList paperList' style={{ padding: '.5em 1em' }}>
|
||||
{unratedItems.map(Item => {
|
||||
return <CheckBoxListItem
|
||||
key={Item.value}
|
||||
className='chkUnratedItem'
|
||||
ItemType={Item.value}
|
||||
Name={Item.name}
|
||||
checkedAttribute={Item.checkedAttribute}
|
||||
/>;
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<div className='verticalSection' style={{marginBottom: '2em'}}>
|
||||
<div
|
||||
className='detailSectionHeader sectionTitleContainer'
|
||||
style={{display: 'flex', alignItems: 'center', paddingBottom: '1em'}}
|
||||
>
|
||||
<h2 className='sectionTitle'>
|
||||
{globalize.translate('LabelBlockContentWithTags')}
|
||||
</h2>
|
||||
<SectionTitleButtonElement
|
||||
className='fab btnAddBlockedTag submit'
|
||||
title='Add'
|
||||
icon='add'
|
||||
/>
|
||||
</div>
|
||||
<div className='blockedTags' style={{marginTop: '.5em'}}>
|
||||
{blockedTags.map((tag, index) => {
|
||||
return <BlockedTagList
|
||||
key={index}
|
||||
tag={tag}
|
||||
/>;
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className='accessScheduleSection verticalSection' style={{marginBottom: '2em'}}>
|
||||
<div
|
||||
className='sectionTitleContainer'
|
||||
style={{display: 'flex', alignItems: 'center', paddingBottom: '1em'}}
|
||||
>
|
||||
<h2 className='sectionTitle'>
|
||||
{globalize.translate('HeaderAccessSchedule')}
|
||||
</h2>
|
||||
<SectionTitleButtonElement
|
||||
className='fab btnAddSchedule submit'
|
||||
title='Add'
|
||||
icon='add'
|
||||
/>
|
||||
</div>
|
||||
<p>{globalize.translate('HeaderAccessScheduleHelp')}</p>
|
||||
<div className='accessScheduleList paperList'>
|
||||
{accessSchedules.map((accessSchedule, index) => {
|
||||
return <AccessScheduleList
|
||||
key={index}
|
||||
index={index}
|
||||
Id={accessSchedule.Id}
|
||||
DayOfWeek={accessSchedule.DayOfWeek}
|
||||
StartHour={accessSchedule.StartHour}
|
||||
EndHour={accessSchedule.EndHour}
|
||||
/>;
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<ButtonElement
|
||||
type='submit'
|
||||
className='raised button-submit block'
|
||||
title='Save'
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserParentalControl;
|
|
@ -1,4 +1,4 @@
|
|||
|
||||
import { UserDto } from '@thornbill/jellyfin-sdk/dist/generated-client';
|
||||
import React, {FunctionComponent, useEffect, useState, useRef} from 'react';
|
||||
import Dashboard from '../../scripts/clientUtils';
|
||||
import globalize from '../../scripts/globalize';
|
||||
|
@ -21,9 +21,9 @@ type MenuEntry = {
|
|||
}
|
||||
|
||||
const UserProfilesPage: FunctionComponent = () => {
|
||||
const [ users, setUsers ] = useState([]);
|
||||
const [ users, setUsers ] = useState<UserDto[]>([]);
|
||||
|
||||
const element = useRef(null);
|
||||
const element = useRef<HTMLDivElement>(null);
|
||||
|
||||
const loadData = () => {
|
||||
loading.show();
|
||||
|
@ -34,12 +34,24 @@ const UserProfilesPage: FunctionComponent = () => {
|
|||
};
|
||||
|
||||
useEffect(() => {
|
||||
const page = element.current;
|
||||
|
||||
if (!page) {
|
||||
console.error('Unexpected null reference');
|
||||
return;
|
||||
}
|
||||
|
||||
loadData();
|
||||
|
||||
const showUserMenu = (elem) => {
|
||||
const showUserMenu = (elem: HTMLElement) => {
|
||||
const card = dom.parentWithClass(elem, 'card');
|
||||
const userId = card.getAttribute('data-userid');
|
||||
|
||||
if (!userId) {
|
||||
console.error('Unexpected null user id');
|
||||
return;
|
||||
}
|
||||
|
||||
const menuItems: MenuEntry[] = [];
|
||||
|
||||
menuItems.push({
|
||||
|
@ -67,7 +79,7 @@ const UserProfilesPage: FunctionComponent = () => {
|
|||
actionsheet.show({
|
||||
items: menuItems,
|
||||
positionTo: card,
|
||||
callback: function (id) {
|
||||
callback: function (id: string) {
|
||||
switch (id) {
|
||||
case 'open':
|
||||
Dashboard.navigate('useredit.html?userId=' + userId);
|
||||
|
@ -89,7 +101,7 @@ const UserProfilesPage: FunctionComponent = () => {
|
|||
});
|
||||
};
|
||||
|
||||
const deleteUser = (id) => {
|
||||
const deleteUser = (id: string) => {
|
||||
const msg = globalize.translate('DeleteUserConfirmation');
|
||||
|
||||
confirm({
|
||||
|
@ -105,15 +117,15 @@ const UserProfilesPage: FunctionComponent = () => {
|
|||
});
|
||||
};
|
||||
|
||||
element?.current?.addEventListener('click', function (e) {
|
||||
const btnUserMenu = dom.parentWithClass(e.target, 'btnUserMenu');
|
||||
page.addEventListener('click', function (e) {
|
||||
const btnUserMenu = dom.parentWithClass(e.target as HTMLElement, 'btnUserMenu');
|
||||
|
||||
if (btnUserMenu) {
|
||||
showUserMenu(btnUserMenu);
|
||||
}
|
||||
});
|
||||
|
||||
element?.current?.querySelector('.btnAddUser').addEventListener('click', function() {
|
||||
(page.querySelector('.btnAddUser') as HTMLButtonElement).addEventListener('click', function() {
|
||||
Dashboard.navigate('usernew.html');
|
||||
});
|
||||
}, []);
|
||||
|
|
|
@ -15,7 +15,7 @@ let enableAnimation;
|
|||
function getOsdElementHtml() {
|
||||
let html = '';
|
||||
|
||||
html += '<span class="material-icons iconOsdIcon brightness_high"></span>';
|
||||
html += '<span class="material-icons iconOsdIcon brightness_high" aria-hidden="true"></span>';
|
||||
|
||||
html += '<div class="iconOsdProgressOuter"><div class="iconOsdProgressInner brightnessOsdProgressInner"></div></div>';
|
||||
|
||||
|
|
|
@ -12,6 +12,8 @@ import Screenfull from 'screenfull';
|
|||
import ServerConnections from '../ServerConnections';
|
||||
import alert from '../alert';
|
||||
|
||||
const UNLIMITED_ITEMS = -1;
|
||||
|
||||
function enableLocalPlaylistManagement(player) {
|
||||
if (player.getPlaylist) {
|
||||
return false;
|
||||
|
@ -118,8 +120,12 @@ function getItemsForPlayback(serverId, query) {
|
|||
TotalRecordCount: 1
|
||||
};
|
||||
});
|
||||
} else {
|
||||
if (query.Limit === UNLIMITED_ITEMS) {
|
||||
delete query.Limit;
|
||||
} else {
|
||||
query.Limit = query.Limit || 300;
|
||||
}
|
||||
query.Fields = 'Chapters';
|
||||
query.ExcludeLocationTypes = 'Virtual';
|
||||
query.EnableTotalRecordCount = false;
|
||||
|
@ -1774,7 +1780,8 @@ class PlaybackManager {
|
|||
// Setting this to true may cause some incorrect sorting
|
||||
Recursive: false,
|
||||
SortBy: options.shuffle ? 'Random' : 'SortName',
|
||||
MediaTypes: 'Photo,Video'
|
||||
MediaTypes: 'Photo,Video',
|
||||
Limit: UNLIMITED_ITEMS
|
||||
}).then(function (result) {
|
||||
const items = result.Items;
|
||||
|
||||
|
@ -1799,7 +1806,7 @@ class PlaybackManager {
|
|||
SortBy: options.shuffle ? 'Random' : 'SortName',
|
||||
// Only include Photos because we do not handle mixed queues currently
|
||||
MediaTypes: 'Photo',
|
||||
Limit: 1000
|
||||
Limit: UNLIMITED_ITEMS
|
||||
});
|
||||
} else if (firstItem.Type === 'MusicGenre') {
|
||||
promise = getItemsForPlayback(serverId, {
|
||||
|
@ -1817,7 +1824,7 @@ class PlaybackManager {
|
|||
SortBy: options.shuffle ? 'Random' : 'SortName',
|
||||
// Only include Photos because we do not handle mixed queues currently
|
||||
MediaTypes: 'Photo',
|
||||
Limit: 1000
|
||||
Limit: UNLIMITED_ITEMS
|
||||
}, queryOptions));
|
||||
} else if (firstItem.IsFolder) {
|
||||
promise = getItemsForPlayback(serverId, mergePlaybackQueries({
|
||||
|
@ -2385,8 +2392,11 @@ class PlaybackManager {
|
|||
|
||||
streamInfo.fullscreen = playOptions.fullscreen;
|
||||
|
||||
getPlayerData(player).isChangingStream = false;
|
||||
getPlayerData(player).maxStreamingBitrate = maxBitrate;
|
||||
const playerData = getPlayerData(player);
|
||||
|
||||
playerData.isChangingStream = false;
|
||||
playerData.maxStreamingBitrate = maxBitrate;
|
||||
playerData.streamInfo = streamInfo;
|
||||
|
||||
return player.play(streamInfo).then(function () {
|
||||
loading.hide();
|
||||
|
@ -3312,6 +3322,7 @@ class PlaybackManager {
|
|||
mediaSource.MediaStreams = info.MediaStreams;
|
||||
Events.trigger(player, 'mediastreamschange');
|
||||
}, function () {
|
||||
// Swallow errors
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import { Events } from 'jellyfin-apiclient';
|
|||
import browser from '../../scripts/browser';
|
||||
import loading from '../loading/loading';
|
||||
import { playbackManager } from '../playback/playbackmanager';
|
||||
import { pluginManager } from '../pluginManager';
|
||||
import { appRouter } from '../appRouter';
|
||||
import globalize from '../../scripts/globalize';
|
||||
import { appHost } from '../apphost';
|
||||
|
@ -130,6 +131,13 @@ export function show(button) {
|
|||
menuOptions.enableHistory = false;
|
||||
}
|
||||
|
||||
// Add message when Google Cast is not supported
|
||||
const isChromecastPluginLoaded = !!pluginManager.plugins.find(plugin => plugin.id === 'chromecast');
|
||||
// TODO: Add other checks for support (Android app, secure context, etc)
|
||||
if (!isChromecastPluginLoaded) {
|
||||
menuOptions.text = `(${globalize.translate('GoogleCastUnsupported')})`;
|
||||
}
|
||||
|
||||
actionsheet.show(menuOptions).then(function (id) {
|
||||
const target = targets.filter(function (t) {
|
||||
return t.id === id;
|
||||
|
|
|
@ -16,7 +16,7 @@ let enableAnimation;
|
|||
function getOsdElementHtml() {
|
||||
let html = '';
|
||||
|
||||
html += '<span class="material-icons iconOsdIcon volume_up"></span>';
|
||||
html += '<span class="material-icons iconOsdIcon volume_up" aria-hidden="true"></span>';
|
||||
|
||||
html += '<div class="iconOsdProgressOuter"><div class="iconOsdProgressInner"></div></div>';
|
||||
|
||||
|
|
|
@ -176,12 +176,6 @@ import template from './playbackSettings.template.html';
|
|||
context.querySelector('.fldChromecastQuality').classList.add('hide');
|
||||
}
|
||||
|
||||
if (browser.tizen || browser.web0s) {
|
||||
context.querySelector('.fldEnableNextVideoOverlay').classList.add('hide');
|
||||
} else {
|
||||
context.querySelector('.fldEnableNextVideoOverlay').classList.remove('hide');
|
||||
}
|
||||
|
||||
context.querySelector('.chkPlayDefaultAudioTrack').checked = user.Configuration.PlayDefaultAudioTrack || false;
|
||||
context.querySelector('.chkPreferFmp4HlsContainer').checked = userSettings.preferFmp4HlsContainer();
|
||||
context.querySelector('.chkEnableCinemaMode').checked = userSettings.enableCinemaMode();
|
||||
|
|
|
@ -90,7 +90,7 @@
|
|||
<div class="fieldDescription checkboxFieldDescription">${SetUsingLastTracksHelp}</div>
|
||||
</div>
|
||||
|
||||
<div class="checkboxContainer checkboxContainer-withDescription fldEnableNextVideoOverlay hide">
|
||||
<div class="checkboxContainer checkboxContainer-withDescription fldEnableNextVideoOverlay">
|
||||
<label>
|
||||
<input type="checkbox" is="emby-checkbox" class="chkEnableNextVideoOverlay" />
|
||||
<span>${EnableNextVideoInfoOverlay}</span>
|
||||
|
|
|
@ -26,7 +26,7 @@ import ServerConnections from '../ServerConnections';
|
|||
if (layoutManager.tv) {
|
||||
button = '';
|
||||
} else {
|
||||
button = '<button type="button" is="paper-icon-button-light" class="playerStats-closeButton"><span class="material-icons close"></span></button>';
|
||||
button = '<button type="button" is="paper-icon-button-light" class="playerStats-closeButton"><span class="material-icons close" aria-hidden="true"></span></button>';
|
||||
}
|
||||
|
||||
const contentClass = layoutManager.tv ? 'playerStats-content playerStats-content-tv' : 'playerStats-content';
|
||||
|
|
|
@ -242,7 +242,7 @@ import ServerConnections from '../ServerConnections';
|
|||
const title = globalize.translate('HeaderAddToPlaylist');
|
||||
|
||||
html += '<div class="formDialogHeader">';
|
||||
html += '<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><span class="material-icons arrow_back"></span></button>';
|
||||
html += '<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><span class="material-icons arrow_back" aria-hidden="true"></span></button>';
|
||||
html += '<h3 class="formDialogHeaderTitle">';
|
||||
html += title;
|
||||
html += '</h3>';
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<div class="formDialogHeader">
|
||||
<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1">
|
||||
<span class="material-icons arrow_back"></span>
|
||||
<span class="material-icons arrow_back" aria-hidden="true"></span>
|
||||
</button>
|
||||
|
||||
<h3 class="formDialogHeaderTitle"></h3>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div class="formDialogHeader">
|
||||
<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><span class="material-icons arrow_back"></span></button>
|
||||
<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><span class="material-icons arrow_back" aria-hidden="true"></span></button>
|
||||
<h3 class="formDialogHeaderTitle"></h3>
|
||||
</div>
|
||||
<div class="formDialogContent smoothScrollY">
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div class="formDialogHeader">
|
||||
<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><span class="material-icons arrow_back"></span></button>
|
||||
<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><span class="material-icons arrow_back" aria-hidden="true"></span></button>
|
||||
<h3 class="formDialogHeaderTitle">
|
||||
${HeaderRecordingOptions}
|
||||
</h3>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<div class="recordSeriesContainer recordingFields-buttons flex align-items-center hide">
|
||||
<div>
|
||||
<button is="emby-button" type="button" class="raised recordingButton seriesRecordingButton">
|
||||
<span class="material-icons recordingIcon fiber_smart_record"></span>
|
||||
<span class="material-icons recordingIcon fiber_smart_record" aria-hidden="true"></span>
|
||||
<span class="buttonText">${RecordSeries}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -14,7 +14,7 @@
|
|||
<div class="recordingFields-buttons flex align-items-center">
|
||||
<div>
|
||||
<button is="emby-button" type="button" class="raised recordingButton singleRecordingButton">
|
||||
<span class="material-icons recordingIcon fiber_manual_record"></span>
|
||||
<span class="material-icons recordingIcon fiber_manual_record" aria-hidden="true"></span>
|
||||
<span class="buttonText">${Record}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div class="formDialogHeader">
|
||||
<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><span class="material-icons arrow_back"></span></button>
|
||||
<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><span class="material-icons arrow_back" aria-hidden="true"></span></button>
|
||||
<h3 class="formDialogHeaderTitle">
|
||||
${HeaderSeriesOptions}
|
||||
</h3>
|
||||
|
|
|
@ -120,7 +120,7 @@ class RefreshDialog {
|
|||
const title = globalize.translate('RefreshMetadata');
|
||||
|
||||
html += '<div class="formDialogHeader">';
|
||||
html += '<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><span class="material-icons arrow_back"></span></button>';
|
||||
html += '<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><span class="material-icons arrow_back" aria-hidden="true"></span></button>';
|
||||
html += '<h3 class="formDialogHeaderTitle">';
|
||||
html += title;
|
||||
html += '</h3>';
|
||||
|
|
|
@ -233,8 +233,8 @@ function updateNowPlayingInfo(context, state, serverId) {
|
|||
apiClient.getItem(apiClient.getCurrentUserId(), item.Id).then(function (fullItem) {
|
||||
const userData = fullItem.UserData || {};
|
||||
const likes = userData.Likes == null ? '' : userData.Likes;
|
||||
context.querySelector('.nowPlayingPageUserDataButtonsTitle').innerHTML = '<button is="emby-ratingbutton" type="button" class="listItemButton paper-icon-button-light" data-id="' + fullItem.Id + '" data-serverid="' + fullItem.ServerId + '" data-itemtype="' + fullItem.Type + '" data-likes="' + likes + '" data-isfavorite="' + userData.IsFavorite + '"><span class="material-icons favorite"></span></button>';
|
||||
context.querySelector('.nowPlayingPageUserDataButtons').innerHTML = '<button is="emby-ratingbutton" type="button" class="listItemButton paper-icon-button-light" data-id="' + fullItem.Id + '" data-serverid="' + fullItem.ServerId + '" data-itemtype="' + fullItem.Type + '" data-likes="' + likes + '" data-isfavorite="' + userData.IsFavorite + '"><span class="material-icons favorite"></span></button>';
|
||||
context.querySelector('.nowPlayingPageUserDataButtonsTitle').innerHTML = '<button is="emby-ratingbutton" type="button" class="listItemButton paper-icon-button-light" data-id="' + fullItem.Id + '" data-serverid="' + fullItem.ServerId + '" data-itemtype="' + fullItem.Type + '" data-likes="' + likes + '" data-isfavorite="' + userData.IsFavorite + '"><span class="material-icons favorite" aria-hidden="true"></span></button>';
|
||||
context.querySelector('.nowPlayingPageUserDataButtons').innerHTML = '<button is="emby-ratingbutton" type="button" class="listItemButton paper-icon-button-light" data-id="' + fullItem.Id + '" data-serverid="' + fullItem.ServerId + '" data-itemtype="' + fullItem.Type + '" data-likes="' + likes + '" data-isfavorite="' + userData.IsFavorite + '"><span class="material-icons favorite" aria-hidden="true"></span></button>';
|
||||
});
|
||||
} else {
|
||||
backdrop.clearBackdrop();
|
||||
|
@ -248,15 +248,11 @@ function setImageUrl(context, state, url) {
|
|||
|
||||
if (url) {
|
||||
imgContainer.innerHTML = '<img class="nowPlayingPageImage" src="' + url + '" />';
|
||||
if (item.Type == 'Audio') {
|
||||
context.querySelector('.nowPlayingPageImage').classList.add('nowPlayingPageImageAudio');
|
||||
context.querySelector('.nowPlayingPageImageContainer').classList.remove('nowPlayingPageImageAudio');
|
||||
|
||||
context.querySelector('.nowPlayingPageImage').classList.toggle('nowPlayingPageImageAudio', item.Type === 'Audio');
|
||||
context.querySelector('.nowPlayingPageImage').classList.toggle('nowPlayingPageImagePoster', item.Type !== 'Audio');
|
||||
} else {
|
||||
context.querySelector('.nowPlayingPageImageContainer').classList.add('nowPlayingPageImagePoster');
|
||||
context.querySelector('.nowPlayingPageImage').classList.remove('nowPlayingPageImageAudio');
|
||||
}
|
||||
} else {
|
||||
imgContainer.innerHTML = '<div class="nowPlayingPageImageContainerNoAlbum"><button data-action="link" class="cardImageContainer coveredImage ' + cardBuilder.getDefaultBackgroundClass(item.Name) + ' cardContent cardContent-shadow itemAction"><span class="cardImageIcon material-icons album"></span></button></div>';
|
||||
imgContainer.innerHTML = '<div class="nowPlayingPageImageContainerNoAlbum"><button data-action="link" class="cardImageContainer coveredImage ' + cardBuilder.getDefaultBackgroundClass(item.Name) + ' cardContent cardContent-shadow itemAction"><span class="cardImageIcon material-icons album" aria-hidden="true"></span></button></div>';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -385,14 +381,14 @@ export default function () {
|
|||
const context = dlg;
|
||||
const toggleRepeatButtons = context.querySelectorAll('.repeatToggleButton');
|
||||
const cssClass = 'buttonActive';
|
||||
let innHtml = '<span class="material-icons repeat"></span>';
|
||||
let innHtml = '<span class="material-icons repeat" aria-hidden="true"></span>';
|
||||
let repeatOn = true;
|
||||
|
||||
switch (repeatMode) {
|
||||
case 'RepeatAll':
|
||||
break;
|
||||
case 'RepeatOne':
|
||||
innHtml = '<span class="material-icons repeat_one"></span>';
|
||||
innHtml = '<span class="material-icons repeat_one" aria-hidden="true"></span>';
|
||||
break;
|
||||
case 'RepeatNone':
|
||||
default:
|
||||
|
@ -889,7 +885,7 @@ export default function () {
|
|||
|
||||
function init(ownerView, context) {
|
||||
let volumecontrolHtml = '<div class="volumecontrol flex align-items-center flex-wrap-wrap justify-content-center">';
|
||||
volumecontrolHtml += `<button is="paper-icon-button-light" class="buttonMute autoSize" title=${globalize.translate('Mute')}><span class="xlargePaperIconButton material-icons volume_up"></span></button>`;
|
||||
volumecontrolHtml += `<button is="paper-icon-button-light" class="buttonMute autoSize" title=${globalize.translate('Mute')}><span class="xlargePaperIconButton material-icons volume_up" aria-hidden="true"></span></button>`;
|
||||
volumecontrolHtml += '<div class="sliderContainer nowPlayingVolumeSliderContainer"><input is="emby-slider" type="range" step="1" min="0" max="100" value="0" class="nowPlayingVolumeSlider"/></div>';
|
||||
volumecontrolHtml += '</div>';
|
||||
const optionsSection = context.querySelector('.playlistSectionButton');
|
||||
|
|
|
@ -81,8 +81,6 @@
|
|||
-webkit-box-align: center;
|
||||
-webkit-align-items: center;
|
||||
align-items: center;
|
||||
-webkit-flex-wrap: wrap;
|
||||
flex-wrap: wrap;
|
||||
-webkit-flex-shrink: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
@ -145,7 +143,7 @@
|
|||
width: 100%;
|
||||
-webkit-box-shadow: 0 0 1.9vh #000;
|
||||
box-shadow: 0 0 1.9vh #000;
|
||||
border: 0.1em solid #222;
|
||||
border-radius: 0.2em;
|
||||
user-select: none;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
|
@ -203,6 +201,7 @@
|
|||
.layout-desktop .playlistSectionButton,
|
||||
.layout-tv .playlistSectionButton {
|
||||
background: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.layout-desktop .nowPlayingPlaylist,
|
||||
|
@ -274,9 +273,7 @@
|
|||
.remoteControlContent {
|
||||
padding-left: 7.3% !important;
|
||||
padding-right: 7.3% !important;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.layout-desktop .nowPlayingPageUserDataButtons {
|
||||
|
@ -296,7 +293,6 @@
|
|||
}
|
||||
|
||||
.nowPlayingPageTitle {
|
||||
/* text-align: center; */
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
@ -317,17 +313,24 @@
|
|||
}
|
||||
|
||||
.nowPlayingInfoButtons {
|
||||
/* margin: 1.5em 0 0 0; */
|
||||
-webkit-box-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
justify-content: center;
|
||||
font-size: 1.5em;
|
||||
height: 100%;
|
||||
margin-left: -0.5em;
|
||||
margin-right: -0.5em;
|
||||
}
|
||||
|
||||
.nowPlayingPageImageContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
margin: auto auto 0.5em;
|
||||
min-height: 0;
|
||||
margin: 0 auto 0.5em;
|
||||
}
|
||||
|
||||
.nowPlayingPageImageContainerNoAlbum .cardImageContainer .cardImageIcon {
|
||||
|
@ -336,7 +339,9 @@
|
|||
}
|
||||
|
||||
.nowPlayingInfoControls {
|
||||
margin: 0.5em 0 1em 0;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 1;
|
||||
margin: 0.5em 0 0;
|
||||
width: 100%;
|
||||
-webkit-box-pack: start !important;
|
||||
-webkit-justify-content: start !important;
|
||||
|
@ -365,19 +370,15 @@
|
|||
|
||||
.nowPlayingInfoButtons .btnRepeat,
|
||||
.nowPlayingInfoButtons .btnRewind {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
margin-left: 0;
|
||||
padding-left: 7.3%;
|
||||
margin-right: auto;
|
||||
font-size: smaller;
|
||||
}
|
||||
|
||||
.nowPlayingInfoButtons .btnShuffleQueue,
|
||||
.nowPlayingInfoButtons .btnFastForward {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
margin-left: auto;
|
||||
margin-right: 0;
|
||||
padding-right: 7.3%;
|
||||
font-size: smaller;
|
||||
}
|
||||
|
||||
|
@ -391,25 +392,11 @@
|
|||
font-size: 2em;
|
||||
}
|
||||
|
||||
.nowPlayingPageImage {
|
||||
/* width: inherit; */
|
||||
overflow-y: hidden;
|
||||
overflow: hidden;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.nowPlayingPageImage.nowPlayingPageImageAudio {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.nowPlayingPageImageContainer.nowPlayingPageImagePoster {
|
||||
height: 50%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.nowPlayingPageImageContainer.nowPlayingPageImagePoster img {
|
||||
height: 100%;
|
||||
.nowPlayingPageImage.nowPlayingPageImageAudio,
|
||||
.nowPlayingPageImage.nowPlayingPageImagePoster {
|
||||
width: auto;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.playlistSectionButton .volumecontrol {
|
||||
|
@ -423,7 +410,6 @@
|
|||
|
||||
.nowPlayingButtonsContainer {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue