diff --git a/.ci/azure-pipelines-package.yml b/.ci/azure-pipelines-package.yml index bf4234ec97..bb2a09f176 100644 --- a/.ci/azure-pipelines-package.yml +++ b/.ci/azure-pipelines-package.yml @@ -59,7 +59,15 @@ jobs: pool: vmImage: 'ubuntu-latest' + variables: + - name: JellyfinVersion + value: 0.0.0 + steps: + - script: echo "##vso[task.setvariable variable=JellyfinVersion]$( awk -F '/' '{ print $NF }' <<<'$(Build.SourceBranch)' | sed 's/^v//' )" + displayName: Set release version (stable) + condition: startsWith(variables['Build.SourceBranch'], 'refs/tags') + - task: Docker@2 displayName: 'Push Unstable Image' condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master') @@ -84,7 +92,7 @@ jobs: containerRegistry: Docker Hub tags: | stable-$(Build.BuildNumber) - stable + $(JellyfinVersion) - job: CollectArtifacts displayName: 'Collect Artifacts' diff --git a/.dependabot/config.yml b/.dependabot/config.yml index 4ee827471a..02dfd18aac 100644 --- a/.dependabot/config.yml +++ b/.dependabot/config.yml @@ -2,4 +2,4 @@ version: 1 update_configs: - package_manager: "javascript" directory: "/" - update_schedule: "live" + update_schedule: "weekly" diff --git a/.editorconfig b/.editorconfig index 92cf9dc590..84ba694073 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,5 +8,5 @@ trim_trailing_whitespace = true insert_final_newline = true end_of_line = lf -[json] +[*.json] indent_size = 2 diff --git a/.eslintignore b/.eslintignore index 8e3aee83fb..74b18ddcf6 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,4 +2,3 @@ node_modules dist .idea .vscode -src/libraries diff --git a/.eslintrc.js b/.eslintrc.js index baf6d0e084..dfb65cb4d4 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,6 +1,9 @@ +const restrictedGlobals = require('confusing-browser-globals'); + module.exports = { root: true, plugins: [ + '@babel', 'promise', 'import', 'eslint-comments' @@ -28,7 +31,7 @@ module.exports = { ], rules: { 'block-spacing': ['error'], - 'brace-style': ['error'], + 'brace-style': ['error', '1tbs', { 'allowSingleLine': true }], 'comma-dangle': ['error', 'never'], 'comma-spacing': ['error'], 'eol-last': ['error'], @@ -38,19 +41,25 @@ module.exports = { 'no-floating-decimal': ['error'], 'no-multi-spaces': ['error'], 'no-multiple-empty-lines': ['error', { 'max': 1 }], + 'no-restricted-globals': ['error'].concat(restrictedGlobals), 'no-trailing-spaces': ['error'], + '@babel/no-unused-expressions': ['error', { 'allowShortCircuit': true, 'allowTernary': true, 'allowTaggedTemplates': true }], + //'no-unused-vars': ['error', { 'vars': 'all', 'args': 'none', 'ignoreRestSiblings': true }], 'one-var': ['error', 'never'], + 'padded-blocks': ['error', 'never'], + //'prefer-const': ['error', {'destructuring': 'all'}], 'quotes': ['error', 'single', { 'avoidEscape': true, 'allowTemplateLiterals': false }], - 'semi': ['error'], + '@babel/semi': ['error'], 'space-before-blocks': ['error'], - 'space-infix-ops': 'error' + 'space-infix-ops': 'error', + 'yoda': 'error' }, overrides: [ { files: [ './src/**/*.js' ], - parser: 'babel-eslint', + parser: '@babel/eslint-parser', env: { node: false, amd: true, @@ -75,7 +84,6 @@ module.exports = { 'ApiClient': 'writable', 'AppInfo': 'writable', 'chrome': 'writable', - 'ConnectionManager': 'writable', 'DlnaProfilePage': 'writable', 'Dashboard': 'writable', 'DashboardPage': 'writable', @@ -98,9 +106,9 @@ module.exports = { }, rules: { // TODO: Fix warnings and remove these rules - 'no-redeclare': ['warn'], - 'no-unused-vars': ['warn'], - 'no-useless-escape': ['warn'], + 'no-redeclare': ['off'], + 'no-useless-escape': ['off'], + 'no-unused-vars': ['off'], // TODO: Remove after ES6 migration is complete 'import/no-unresolved': ['off'] }, diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index a6fd74e3b3..b7c64ef9c1 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -36,7 +36,9 @@ - [MrTimscampi](https://github.com/MrTimscampi) - [ConfusedPolarBear](https://github.com/ConfusedPolarBear) - [Sarab Singh](https://github.com/sarab97) + - [GuilhermeHideki](https://github.com/GuilhermeHideki) - [Andrei Oanca](https://github.com/OancaAndrei) + - [Cromefire_](https://github.com/cromefire) # Emby Contributors diff --git a/README.md b/README.md index f06e461320..ca42965dd9 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ Jellyfin Web is the frontend used for most of the clients available for end user ### Dependencies +- [Node.js](https://nodejs.org/en/download/) - [Yarn 1.22.4](https://classic.yarnpkg.com/en/docs/install) - Gulp-cli diff --git a/build.yaml b/build.yaml index fe1633faec..a73be8ec43 100644 --- a/build.yaml +++ b/build.yaml @@ -1,7 +1,7 @@ --- # We just wrap `build` so this is really it name: "jellyfin-web" -version: "10.6.0" +version: "10.7.0" packages: - debian.all - fedora.all diff --git a/bump_version b/bump_version index bc8288b829..4e6aa6f792 100755 --- a/bump_version +++ b/bump_version @@ -4,6 +4,7 @@ set -o errexit set -o pipefail +set -o xtrace usage() { echo -e "bump_version - increase the shared version and generate changelogs" @@ -23,10 +24,7 @@ build_file="./build.yaml" new_version="$1" # Parse the version from shared version file -old_version="$( - grep "appVersion" ${shared_version_file} | head -1 \ - | sed -E 's/var appVersion = "([0-9\.]+)";/\1/' -)" +old_version="$( grep "appVersion" ${shared_version_file} | head -1 | sed -E "s/var appVersion = '([0-9\.]+)';/\1/" | tr -d '[:space:]' )" echo "Old version in appHost is: $old_version" # Set the shared version to the specified new_version @@ -34,11 +32,8 @@ old_version_sed="$( sed 's/\./\\./g' <<<"${old_version}" )" # Escape the '.' cha new_version_sed="$( cut -f1 -d'-' <<<"${new_version}" )" sed -i "s/${old_version_sed}/${new_version_sed}/g" ${shared_version_file} -old_version="$( - grep "version:" ${build_file} \ - | sed -E 's/version: "([0-9\.]+[-a-z0-9]*)"/\1/' -)" -echo "Old version in ${build_file}: $old_version`" +old_version="$( grep "version:" ${build_file} | sed -E 's/version: "([0-9\.]+[-a-z0-9]*)"/\1/' )" +echo "Old version in ${build_file}: ${old_version}" # Set the build.yaml version to the specified new_version old_version_sed="$( sed 's/\./\\./g' <<<"${old_version}" )" # Escape the '.' chars @@ -54,7 +49,7 @@ fi debian_changelog_file="debian/changelog" debian_changelog_temp="$( mktemp )" # Create new temp file with our changelog -echo -e "jellyfin (${new_version_deb}) unstable; urgency=medium +echo -e "jellyfin-web (${new_version_deb}) unstable; urgency=medium * New upstream version ${new_version}; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v${new_version} @@ -65,15 +60,15 @@ cat ${debian_changelog_file} >> ${debian_changelog_temp} mv ${debian_changelog_temp} ${debian_changelog_file} # Write out a temporary Yum changelog with our new stuff prepended and some templated formatting -fedora_spec_file="fedora/jellyfin.spec" +fedora_spec_file="fedora/jellyfin-web.spec" fedora_changelog_temp="$( mktemp )" fedora_spec_temp_dir="$( mktemp -d )" -fedora_spec_temp="${fedora_spec_temp_dir}/jellyfin.spec.tmp" +fedora_spec_temp="${fedora_spec_temp_dir}/jellyfin-web.spec.tmp" # Make a copy of our spec file for hacking cp ${fedora_spec_file} ${fedora_spec_temp_dir}/ pushd ${fedora_spec_temp_dir} # Split out the stuff before and after changelog -csplit jellyfin.spec "/^%changelog/" # produces xx00 xx01 +csplit jellyfin-web.spec "/^%changelog/" # produces xx00 xx01 # Update the version in xx00 sed -i "s/${old_version_sed}/${new_version_sed}/g" xx00 # Remove the header from xx01 @@ -92,5 +87,5 @@ mv ${fedora_spec_temp} ${fedora_spec_file} rm -rf ${fedora_changelog_temp} ${fedora_spec_temp_dir} # Stage the changed files for commit -git add ${shared_version_file} ${build_file} ${debian_changelog_file} ${fedora_spec_file} Dockerfile* +git add ${shared_version_file} ${build_file} ${debian_changelog_file} ${fedora_spec_file} git status diff --git a/debian/changelog b/debian/changelog index 50966c3a01..ab5e13196d 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +jellyfin-web (10.7.0-1) unstable; urgency=medium + + * Forthcoming stable release + + -- Jellyfin Packaging Team Mon, 27 Jul 2020 19:13:31 -0400 + jellyfin-web (10.6.0-1) unstable; urgency=medium * New upstream version 10.6.0; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.6.0 diff --git a/fedora/jellyfin-web.spec b/fedora/jellyfin-web.spec index dcc9d9d2ab..1d85e5ae6b 100644 --- a/fedora/jellyfin-web.spec +++ b/fedora/jellyfin-web.spec @@ -1,7 +1,7 @@ %global debug_package %{nil} Name: jellyfin-web -Version: 10.6.0 +Version: 10.7.0 Release: 1%{?dist} Summary: The Free Software Media System web client License: GPLv3 @@ -11,6 +11,8 @@ Source0: jellyfin-web-%{version}.tar.gz %if 0%{?centos} BuildRequires: yarn +# sadly the yarn RPM at https://dl.yarnpkg.com/rpm/ uses git but doesn't Requires: it +BuildRequires: git %else BuildRequires: nodejs-yarn %endif @@ -39,5 +41,7 @@ mv dist %{buildroot}%{_datadir}/jellyfin-web %{_datadir}/licenses/jellyfin/LICENSE %changelog +* Mon Jul 27 2020 Jellyfin Packaging Team +- Forthcoming stable release * Mon Mar 23 2020 Jellyfin Packaging Team - Forthcoming stable release diff --git a/package.json b/package.json index 88a361d8ba..d53ef70cb6 100644 --- a/package.json +++ b/package.json @@ -5,26 +5,28 @@ "repository": "https://github.com/jellyfin/jellyfin-web", "license": "GPL-2.0-or-later", "devDependencies": { - "@babel/core": "^7.10.5", + "@babel/core": "^7.11.5", + "@babel/eslint-parser": "^7.11.5", + "@babel/eslint-plugin": "^7.11.5", "@babel/plugin-proposal-class-properties": "^7.10.1", "@babel/plugin-proposal-private-methods": "^7.10.1", "@babel/plugin-transform-modules-amd": "^7.10.5", - "@babel/polyfill": "^7.8.7", - "@babel/preset-env": "^7.10.3", - "autoprefixer": "^9.8.5", - "babel-eslint": "^11.0.0-beta.2", + "@babel/polyfill": "^7.11.5", + "@babel/preset-env": "^7.11.5", + "autoprefixer": "^9.8.6", "babel-loader": "^8.0.6", - "browser-sync": "^2.26.10", + "browser-sync": "^2.26.12", + "confusing-browser-globals": "^1.0.9", "copy-webpack-plugin": "^5.1.1", - "css-loader": "^4.0.0", + "css-loader": "^4.2.2", "cssnano": "^4.1.10", "del": "^5.1.0", - "eslint": "^6.8.0", + "eslint": "^7.8.1", "eslint-plugin-compat": "^3.5.1", "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-import": "^2.21.2", "eslint-plugin-promise": "^4.2.1", - "file-loader": "^6.0.0", + "file-loader": "^6.1.0", "gulp": "^4.0.2", "gulp-babel": "^8.0.0", "gulp-cli": "^2.3.0", @@ -37,50 +39,51 @@ "gulp-postcss": "^8.0.0", "gulp-sass": "^4.0.2", "gulp-sourcemaps": "^2.6.5", - "gulp-terser": "^1.2.1", - "html-webpack-plugin": "^4.3.0", + "gulp-terser": "^1.4.0", + "html-webpack-plugin": "^4.4.1", "lazypipe": "^1.0.2", "node-sass": "^4.13.1", "postcss-loader": "^3.0.0", "postcss-preset-env": "^6.7.0", "style-loader": "^1.1.3", - "stylelint": "^13.6.1", + "stylelint": "^13.7.0", "stylelint-config-rational-order": "^0.1.2", "stylelint-no-browser-hacks": "^1.2.1", "stylelint-order": "^4.1.0", - "webpack": "^4.44.0", + "webpack": "^4.44.1", "webpack-merge": "^4.2.2", - "webpack-stream": "^5.2.1" + "webpack-stream": "^6.1.0", + "worker-plugin": "^5.0.0" }, "dependencies": { "alameda": "^1.4.0", "blurhash": "^1.1.3", "classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz", "core-js": "^3.6.5", - "date-fns": "^2.15.0", + "date-fns": "^2.16.1", "epubjs": "^0.3.85", "fast-text-encoding": "^1.0.3", "flv.js": "^1.5.0", "headroom.js": "^0.11.0", - "hls.js": "^0.14.5", + "hls.js": "^0.14.11", "howler": "^2.2.0", "intersection-observer": "^0.11.0", "jellyfin-apiclient": "^1.4.1", "jellyfin-noto": "https://github.com/jellyfin/jellyfin-noto", "jquery": "^3.5.1", "jstree": "^3.3.10", + "libarchive.js": "^1.3.0", "libass-wasm": "https://github.com/jellyfin/JavascriptSubtitlesOctopus#4.0.0-jf-smarttv", - "material-design-icons-iconfont": "^5.0.1", + "material-design-icons-iconfont": "^6.0.1", "native-promise-only": "^0.8.0-a", "page": "^1.11.6", "query-string": "^6.13.1", "resize-observer-polyfill": "^1.5.1", "screenfull": "^5.0.2", - "shaka-player": "^2.5.13", "sortablejs": "^1.10.2", - "swiper": "^5.4.5", + "swiper": "^6.1.1", "webcomponents.js": "^0.7.24", - "whatwg-fetch": "^3.2.0" + "whatwg-fetch": "^3.4.0" }, "babel": { "presets": [ @@ -95,6 +98,8 @@ "src/components/alert.js", "src/components/alphaPicker/alphaPicker.js", "src/components/appFooter/appFooter.js", + "src/components/apphost.js", + "src/components/appRouter.js", "src/components/autoFocuser.js", "src/components/backdrop/backdrop.js", "src/components/cardbuilder/cardBuilder.js", @@ -107,22 +112,33 @@ "src/components/dialogHelper/dialogHelper.js", "src/components/directorybrowser/directorybrowser.js", "src/components/displaySettings/displaySettings.js", + "src/components/favoriteitems.js", "src/components/fetchhelper.js", "src/components/filterdialog/filterdialog.js", + "src/components/filtermenu/filtermenu.js", + "src/components/focusManager.js", "src/components/groupedcards.js", + "src/components/guide/guide.js", + "src/components/guide/guide-settings.js", "src/components/homeScreenSettings/homeScreenSettings.js", "src/components/homesections/homesections.js", "src/components/htmlMediaHelper.js", "src/components/imageOptionsEditor/imageOptionsEditor.js", "src/components/images/imageLoader.js", + "src/components/imageDownloader/imageDownloader.js", + "src/components/imageeditor/imageeditor.js", "src/components/imageUploader/imageUploader.js", "src/components/indicators/indicators.js", "src/components/itemContextMenu.js", + "src/components/itemHelper.js", "src/components/itemidentifier/itemidentifier.js", "src/components/itemMediaInfo/itemMediaInfo.js", + "src/components/itemsrefresher.js", + "src/components/layoutManager.js", "src/components/lazyLoader/lazyLoaderIntersectionObserver.js", "src/components/libraryoptionseditor/libraryoptionseditor.js", "src/components/listview/listview.js", + "src/components/loading/loading.js", "src/components/maintabsmanager.js", "src/components/mediainfo/mediainfo.js", "src/components/mediaLibraryCreator/mediaLibraryCreator.js", @@ -130,39 +146,77 @@ "src/components/metadataEditor/metadataEditor.js", "src/components/metadataEditor/personEditor.js", "src/components/multiSelect/multiSelect.js", + "src/components/notifications/notifications.js", "src/components/nowPlayingBar/nowPlayingBar.js", + "src/components/packageManager.js", "src/components/playback/brightnessosd.js", "src/components/playback/mediasession.js", "src/components/playback/nowplayinghelper.js", "src/components/playback/playbackorientation.js", + "src/components/playback/playbackmanager.js", "src/components/playback/playerSelectionMenu.js", "src/components/playback/playersettingsmenu.js", "src/components/playback/playmethodhelper.js", + "src/components/playback/playqueuemanager.js", "src/components/playback/remotecontrolautoplay.js", "src/components/playback/volumeosd.js", "src/components/playbackSettings/playbackSettings.js", "src/components/playerstats/playerstats.js", "src/components/playlisteditor/playlisteditor.js", "src/components/playmenu.js", + "src/components/pluginManager.js", "src/components/prompt/prompt.js", + "src/components/qualityOptions.js", "src/components/quickConnectSettings/quickConnectSettings.js", + "src/components/recordingcreator/recordingbutton.js", + "src/components/recordingcreator/recordingcreator.js", + "src/components/recordingcreator/seriesrecordingeditor.js", + "src/components/recordingcreator/recordinghelper.js", + "src/components/refreshdialog/refreshdialog.js", + "src/components/recordingcreator/recordingeditor.js", + "src/components/recordingcreator/recordingfields.js", + "src/components/remotecontrol/remotecontrol.js", "src/components/sanatizefilename.js", "src/components/scrollManager.js", + "src/plugins/experimentalWarnings/plugin.js", + "src/plugins/sessionPlayer/plugin.js", + "src/plugins/htmlAudioPlayer/plugin.js", + "src/plugins/comicsPlayer/plugin.js", + "src/plugins/chromecastPlayer/plugin.js", + "src/components/slideshow/slideshow.js", + "src/components/sortmenu/sortmenu.js", + "src/plugins/htmlVideoPlayer/plugin.js", + "src/plugins/logoScreensaver/plugin.js", + "src/plugins/playAccessValidation/plugin.js", "src/components/search/searchfields.js", "src/components/search/searchresults.js", "src/components/settingshelper.js", "src/components/shortcuts.js", + "src/components/subtitleeditor/subtitleeditor.js", + "src/components/subtitlesync/subtitlesync.js", "src/components/subtitlesettings/subtitleappearancehelper.js", "src/components/subtitlesettings/subtitlesettings.js", "src/components/syncPlay/groupSelectionMenu.js", "src/components/syncPlay/playbackPermissionManager.js", "src/components/syncPlay/syncPlayManager.js", "src/components/syncPlay/timeSyncManager.js", + "src/components/themeMediaPlayer.js", + "src/components/tabbedview/tabbedview.js", + "src/components/viewManager/viewManager.js", + "src/components/tvproviders/schedulesdirect.js", + "src/components/tvproviders/xmltv.js", + "src/components/toast/toast.js", + "src/components/tunerPicker.js", + "src/components/upnextdialog/upnextdialog.js", + "src/components/userdatabuttons/userdatabuttons.js", + "src/components/viewContainer.js", + "src/components/viewSettings/viewSettings.js", + "src/components/castSenderApi.js", "src/controllers/session/addServer/index.js", "src/controllers/session/forgotPassword/index.js", "src/controllers/session/redeemPassword/index.js", "src/controllers/session/login/index.js", - "src/controllers/sessopm/selectServer/index.js", + "src/controllers/session/selectServer/index.js", "src/controllers/dashboard/apikeys.js", "src/controllers/dashboard/dashboard.js", "src/controllers/dashboard/devices/device.js", @@ -174,13 +228,24 @@ "src/controllers/dashboard/general.js", "src/controllers/dashboard/librarydisplay.js", "src/controllers/dashboard/logs.js", - "src/controllers/dashboard/mediaLibrary.js", + "src/controllers/music/musicalbums.js", + "src/controllers/music/musicartists.js", + "src/controllers/music/musicgenres.js", + "src/controllers/music/musicplaylists.js", + "src/controllers/music/musicrecommended.js", + "src/controllers/music/songs.js", + "src/controllers/dashboard/library.js", "src/controllers/dashboard/metadataImages.js", "src/controllers/dashboard/metadatanfo.js", "src/controllers/dashboard/networking.js", + "src/controllers/dashboard/notifications/notification/index.js", + "src/controllers/dashboard/notifications/notifications/index.js", "src/controllers/dashboard/playback.js", - "src/controllers/dashboard/quickconnect.js", + "src/controllers/dashboard/plugins/add/index.js", + "src/controllers/dashboard/plugins/installed/index.js", + "src/controllers/dashboard/plugins/available/index.js", "src/controllers/dashboard/plugins/repositories/index.js", + "src/controllers/dashboard/quickconnect.js", "src/controllers/dashboard/scheduledtasks/scheduledtask.js", "src/controllers/dashboard/scheduledtasks/scheduledtasks.js", "src/controllers/dashboard/serveractivity.js", @@ -191,9 +256,32 @@ "src/controllers/dashboard/users/userparentalcontrol.js", "src/controllers/dashboard/users/userpasswordpage.js", "src/controllers/dashboard/users/userprofilespage.js", + "src/controllers/home.js", + "src/controllers/list.js", + "src/controllers/edititemmetadata.js", + "src/controllers/favorites.js", + "src/controllers/hometab.js", + "src/controllers/movies/moviecollections.js", + "src/controllers/movies/moviegenres.js", + "src/controllers/movies/movies.js", + "src/controllers/movies/moviesrecommended.js", + "src/controllers/movies/movietrailers.js", + "src/controllers/playback/nowplaying.js", + "src/controllers/playback/videoosd.js", + "src/controllers/itemDetails/index.js", "src/controllers/playback/queue/index.js", "src/controllers/playback/video/index.js", "src/controllers/searchpage.js", + "src/controllers/livetv/livetvguide.js", + "src/controllers/livetvtuner.js", + "src/controllers/livetv/livetvsuggested.js", + "src/controllers/livetvstatus.js", + "src/controllers/livetvguideprovider.js", + "src/controllers/livetvsettings.js", + "src/controllers/livetv/livetvrecordings.js", + "src/controllers/livetv/livetvschedule.js", + "src/controllers/livetv/livetvseriestimers.js", + "src/controllers/livetv/livetvchannels.js", "src/controllers/shows/episodes.js", "src/controllers/shows/tvgenres.js", "src/controllers/shows/tvlatest.js", @@ -208,7 +296,6 @@ "src/controllers/user/profile/index.js", "src/controllers/user/quickConnect/index.js", "src/controllers/user/subtitles/index.js", - "src/controllers/user/subtitles/index.js", "src/controllers/wizard/finish/index.js", "src/controllers/wizard/remote/index.js", "src/controllers/wizard/settings/index.js", @@ -234,13 +321,19 @@ "src/elements/emby-tabs/emby-tabs.js", "src/elements/emby-textarea/emby-textarea.js", "src/elements/emby-toggle/emby-toggle.js", + "src/libraries/screensavermanager.js", + "src/libraries/navdrawer/navdrawer.js", + "src/libraries/scroller.js", "src/plugins/backdropScreensaver/plugin.js", "src/plugins/bookPlayer/plugin.js", "src/plugins/bookPlayer/tableOfContents.js", + "src/plugins/chromecastPlayer/chromecastHelper.js", "src/plugins/photoPlayer/plugin.js", "src/plugins/youtubePlayer/plugin.js", "src/scripts/alphanumericshortcuts.js", "src/scripts/autoBackdrops.js", + "src/scripts/browser.js", + "src/scripts/clientUtils.js", "src/scripts/datetime.js", "src/scripts/deleteHelper.js", "src/scripts/dfnshelper.js", @@ -250,12 +343,24 @@ "src/scripts/filesystem.js", "src/scripts/globalize.js", "src/scripts/imagehelper.js", + "src/scripts/itembynamedetailpage.js", "src/scripts/inputManager.js", + "src/scripts/autoThemes.js", + "src/scripts/themeManager.js", "src/scripts/keyboardNavigation.js", + "src/scripts/libraryMenu.js", + "src/scripts/libraryBrowser.js", + "src/scripts/livetvcomponents.js", + "src/scripts/mouseManager.js", + "src/scripts/multiDownload.js", "src/scripts/playlists.js", + "src/scripts/scrollHelper.js", + "src/scripts/serverNotifications.js", + "src/scripts/routes.js", "src/scripts/settings/appSettings.js", "src/scripts/settings/userSettings.js", "src/scripts/settings/webSettings.js", + "src/scripts/shell.js", "src/scripts/taskbutton.js", "src/scripts/themeLoader.js", "src/scripts/touchHelper.js" @@ -273,7 +378,7 @@ "last 2 Chrome versions", "last 2 ChromeAndroid versions", "last 2 Safari versions", - "last 2 iOS versions", + "iOS > 10", "last 2 Edge versions", "Chrome 27", "Chrome 38", @@ -281,9 +386,11 @@ "Chrome 53", "Chrome 56", "Chrome 63", + "Edge 18", "Firefox ESR" ], "scripts": { + "start": "yarn serve", "serve": "gulp serve --development", "prepare": "gulp --production", "build:development": "gulp --development", diff --git a/scripts/duplicates.py b/scripts/duplicates.py new file mode 100644 index 0000000000..2daad94682 --- /dev/null +++ b/scripts/duplicates.py @@ -0,0 +1,33 @@ +import sys +import os +import json + +# load every string in the source language +# print all duplicate values to a file + +cwd = os.getcwd() +source = cwd + '/../src/strings/en-us.json' + +reverse = {} +duplicates = {} + +with open(source) as en: + strings = json.load(en) + for key, value in strings.items(): + if value not in reverse: + reverse[value] = [key] + else: + reverse[value].append(key) + +for key, value in reverse.items(): + if len(value) > 1: + duplicates[key] = value + +print('LENGTH: ' + str(len(duplicates))) +with open('duplicates.txt', 'w') as out: + for item in duplicates: + out.write(json.dumps(item) + ': ') + out.write(json.dumps(duplicates[item]) + '\n') + out.close() + +print('DONE') diff --git a/scripts/scrm.py b/scripts/remove.py similarity index 95% rename from scripts/scrm.py rename to scripts/remove.py index 9bd5bc2a48..dba48537aa 100644 --- a/scripts/scrm.py +++ b/scripts/remove.py @@ -11,7 +11,7 @@ langlst = os.listdir(langdir) keys = [] -with open('scout.txt', 'r') as f: +with open('unused.txt', 'r') as f: for line in f: keys.append(line.strip('\n')) diff --git a/scripts/scdup.py b/scripts/source.py similarity index 100% rename from scripts/scdup.py rename to scripts/source.py diff --git a/scripts/scgen.py b/scripts/unused.py similarity index 87% rename from scripts/scgen.py rename to scripts/unused.py index 12af27320a..abbc399cf6 100644 --- a/scripts/scgen.py +++ b/scripts/unused.py @@ -16,7 +16,7 @@ langlst.append('en-us.json') dep = [] def grep(key): - command = 'grep -r -E "(\(\\\"|\(\'|\{)%s(\\\"|\'|\})" --include=\*.{js,html} --exclude-dir=../src/strings ../src' % key + command = 'grep -r -E "(\\\"|\'|\{)%s(\\\"|\'|\})" --include=\*.{js,html} --exclude-dir=../src/strings ../src' % key p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) output = p.stdout.readlines() if output: diff --git a/src/assets/css/fonts.css b/src/assets/css/fonts.css index cb0da0f80f..6e87f11d9d 100644 --- a/src/assets/css/fonts.css +++ b/src/assets/css/fonts.css @@ -1,7 +1,5 @@ html { font-family: "Noto Sans", sans-serif; - font-size: 93%; - -webkit-text-size-adjust: 100%; text-size-adjust: 100%; -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; @@ -29,7 +27,9 @@ h3 { } .layout-tv { - font-size: 130%; + /* Per WebOS and Tizen guidelines, fonts must be 20px minimum. + This takes the 16px baseline and multiplies it by 1.25 to get 20px. */ + font-size: 125%; } .layout-mobile { diff --git a/src/assets/css/librarybrowser.css b/src/assets/css/librarybrowser.css index 61815a590f..c9ee82c8a0 100644 --- a/src/assets/css/librarybrowser.css +++ b/src/assets/css/librarybrowser.css @@ -28,6 +28,10 @@ padding-top: 0 !important; } +.layout-tv .itemDetailPage { + padding-top: 4.2em !important; +} + .standalonePage { padding-top: 4.5em !important; } @@ -163,6 +167,12 @@ transition: background ease-in-out 0.5s; } +.layout-tv .skinHeader { + /* In TV layout, it makes more sense to keep the top bar at the top of the page + Having it follow the view only makes us lose vertical space, while not being focusable */ + position: relative; +} + .hiddenViewMenuBar .skinHeader { display: none; } @@ -236,12 +246,6 @@ text-align: center; } -.layout-desktop .searchTabButton, -.layout-mobile .searchTabButton, -.layout-tv .headerSearchButton { - display: none !important; -} - .mainDrawer-scrollContainer { padding-bottom: 10vh; } @@ -453,8 +457,7 @@ height: 26.5vh; } -.layout-desktop .itemBackdrop::after, -.layout-tv .itemBackdrop::after { +.layout-desktop .itemBackdrop::after { content: ""; width: 100%; height: 100%; @@ -462,8 +465,8 @@ display: block; } -.layout-desktop .noBackdrop .itemBackdrop, -.layout-tv .noBackdrop .itemBackdrop { +.layout-tv .itemBackdrop, +.layout-desktop .noBackdrop .itemBackdrop { display: none; } @@ -630,6 +633,10 @@ z-index: 2; } +.layout-tv .detailPagePrimaryContainer { + display: block; +} + .layout-mobile .detailPagePrimaryContainer { display: block; position: relative; @@ -643,10 +650,14 @@ padding-left: 32.45vw; } -.layout-desktop .detailRibbon, -.layout-tv .detailRibbon { +.layout-desktop .detailRibbon { margin-top: -7.2em; - height: 7.18em; + height: 7.2em; +} + +.layout-tv .detailRibbon { + margin-top: 0; + height: inherit; } .layout-desktop .noBackdrop .detailRibbon, @@ -754,8 +765,7 @@ div.itemDetailGalleryLink.defaultCardBackground { position: relative; } - .layout-desktop .itemBackdrop, - .layout-tv .itemBackdrop { + .layout-desktop .itemBackdrop { height: 40vh; } @@ -781,13 +791,8 @@ div.itemDetailGalleryLink.defaultCardBackground { } .emby-button.detailFloatingButton { - position: absolute; - background-color: rgba(0, 0, 0, 0.5); - z-index: 3; - top: 100%; - left: 90%; - margin: -2.2em 0 0 -2.2em; - padding: 0.4em; + font-size: 1.4em; + margin-right: 0.5em !important; color: rgba(255, 255, 255, 0.76); } @@ -850,7 +855,7 @@ div.itemDetailGalleryLink.defaultCardBackground { -webkit-align-items: center; align-items: center; margin: 0 !important; - padding: 0.5em 0.7em !important; + padding: 0.7em 0.7em !important; } @media all and (min-width: 29em) { @@ -919,10 +924,6 @@ div.itemDetailGalleryLink.defaultCardBackground { } @media all and (min-width: 100em) { - .detailFloatingButton { - display: none !important; - } - .personBackdrop { display: none !important; } @@ -931,6 +932,11 @@ div.itemDetailGalleryLink.defaultCardBackground { font-size: 108%; margin: 1.25em 0; } + + .layout-tv .mainDetailButtons { + font-size: 108%; + margin: 1em 0 1.25em; + } } @media all and (max-width: 50em) { @@ -1146,13 +1152,13 @@ div:not(.sectionTitleContainer-cards) > .sectionTitle-cards { } .layout-tv .padded-top-focusscale { - padding-top: 1em; - margin-top: -1em; + padding-top: 1.5em; + margin-top: -1.5em; } .layout-tv .padded-bottom-focusscale { - padding-bottom: 1em; - margin-bottom: -1em; + padding-bottom: 1.5em; + margin-bottom: -1.5em; } @media all and (min-height: 31.25em) { diff --git a/src/assets/css/videoosd.css b/src/assets/css/videoosd.css index 50cb41021b..808915e58b 100644 --- a/src/assets/css/videoosd.css +++ b/src/assets/css/videoosd.css @@ -7,7 +7,6 @@ } .osdPoster img, -.pageContainer, .videoOsdBottom { bottom: 0; left: 0; @@ -248,14 +247,7 @@ animation: spin 4s linear infinite; } -.pageContainer { - top: 0; - position: fixed; -} - @media all and (max-width: 30em) { - .btnFastForward, - .btnRewind, .osdMediaInfo, .osdPoster { display: none !important; diff --git a/src/assets/img/devices/edgechromium.svg b/src/assets/img/devices/edgechromium.svg new file mode 100644 index 0000000000..14d68a5d48 --- /dev/null +++ b/src/assets/img/devices/edgechromium.svg @@ -0,0 +1 @@ +Microsoft Edge icon diff --git a/src/bundle.js b/src/bundle.js index dd1ff6548b..25810f58ee 100644 --- a/src/bundle.js +++ b/src/bundle.js @@ -2,165 +2,159 @@ * require.js module definitions bundled by webpack */ // Use define from require.js not webpack's define -var _define = window.define; +const _define = window.define; // fetch -var fetch = require('whatwg-fetch'); +const fetch = require('whatwg-fetch'); _define('fetch', function() { return fetch; }); // Blurhash -var blurhash = require('blurhash'); +const blurhash = require('blurhash'); _define('blurhash', function() { return blurhash; }); // query-string -var query = require('query-string'); +const query = require('query-string'); _define('queryString', function() { return query; }); // flvjs -var flvjs = require('flv.js/dist/flv').default; +const flvjs = require('flv.js/dist/flv').default; _define('flvjs', function() { return flvjs; }); // jstree -var jstree = require('jstree'); +const jstree = require('jstree'); require('jstree/dist/themes/default/style.css'); _define('jstree', function() { return jstree; }); // jquery -var jquery = require('jquery'); +const jquery = require('jquery'); _define('jQuery', function() { return jquery; }); // hlsjs -var hlsjs = require('hls.js'); +const hlsjs = require('hls.js'); _define('hlsjs', function() { return hlsjs; }); // howler -var howler = require('howler'); +const howler = require('howler'); _define('howler', function() { return howler; }); // resize-observer-polyfill -var resize = require('resize-observer-polyfill').default; +const resize = require('resize-observer-polyfill').default; _define('resize-observer-polyfill', function() { return resize; }); // swiper -var swiper = require('swiper/js/swiper'); -require('swiper/css/swiper.min.css'); +const swiper = require('swiper/swiper-bundle'); +require('swiper/swiper-bundle.css'); _define('swiper', function() { return swiper; }); // sortable -var sortable = require('sortablejs').default; +const sortable = require('sortablejs').default; _define('sortable', function() { return sortable; }); // webcomponents -var webcomponents = require('webcomponents.js/webcomponents-lite'); +const webcomponents = require('webcomponents.js/webcomponents-lite'); _define('webcomponents', function() { return webcomponents; }); -// shaka -var shaka = require('shaka-player'); -_define('shaka', function() { - return shaka; -}); - // libass-wasm -var libassWasm = require('libass-wasm'); +const libassWasm = require('libass-wasm'); _define('JavascriptSubtitlesOctopus', function() { return libassWasm; }); // material-icons -var materialIcons = require('material-design-icons-iconfont/dist/material-design-icons.css'); +const materialIcons = require('material-design-icons-iconfont/dist/material-design-icons.css'); _define('material-icons', function() { return materialIcons; }); // noto font -var noto = require('jellyfin-noto'); +const noto = require('jellyfin-noto'); _define('jellyfin-noto', function () { return noto; }); -var epubjs = require('epubjs'); +const epubjs = require('epubjs'); _define('epubjs', function () { return epubjs; }); // page.js -var page = require('page'); +const page = require('page'); _define('page', function() { return page; }); // core-js -var polyfill = require('@babel/polyfill/dist/polyfill'); +const polyfill = require('@babel/polyfill/dist/polyfill'); _define('polyfill', function () { return polyfill; }); // domtokenlist-shim -var classlist = require('classlist.js'); +const classlist = require('classlist.js'); _define('classlist-polyfill', function () { return classlist; }); // Date-FNS -var dateFns = require('date-fns'); +const dateFns = require('date-fns'); _define('date-fns', function () { return dateFns; }); -var dateFnsLocale = require('date-fns/locale'); +const dateFnsLocale = require('date-fns/locale'); _define('date-fns/locale', function () { return dateFnsLocale; }); -var fast_text_encoding = require('fast-text-encoding'); +const fast_text_encoding = require('fast-text-encoding'); _define('fast-text-encoding', function () { return fast_text_encoding; }); // intersection-observer -var intersection_observer = require('intersection-observer'); +const intersection_observer = require('intersection-observer'); _define('intersection-observer', function () { return intersection_observer; }); // screenfull -var screenfull = require('screenfull'); +const screenfull = require('screenfull'); _define('screenfull', function () { return screenfull; }); // headroom.js -var headroom = require('headroom.js/dist/headroom'); +const headroom = require('headroom.js/dist/headroom'); _define('headroom', function () { return headroom; }); // apiclient -var apiclient = require('jellyfin-apiclient'); +const apiclient = require('jellyfin-apiclient'); _define('apiclient', function () { return apiclient.ApiClient; @@ -181,3 +175,9 @@ _define('connectionManagerFactory', function () { _define('appStorage', function () { return apiclient.AppStorage; }); + +// libarchive.js +var libarchive = require('libarchive.js'); +_define('libarchive', function () { + return libarchive; +}); diff --git a/src/components/accessSchedule/accessSchedule.js b/src/components/accessSchedule/accessSchedule.js index 6b96aad1d4..b513766d0b 100644 --- a/src/components/accessSchedule/accessSchedule.js +++ b/src/components/accessSchedule/accessSchedule.js @@ -49,7 +49,7 @@ import 'formDialogStyle'; }; if (parseFloat(updatedSchedule.StartHour) >= parseFloat(updatedSchedule.EndHour)) { - return void alert(globalize.translate('ErrorMessageStartHourGreaterThanEnd')); + return void alert(globalize.translate('ErrorStartHourGreaterThanEnd')); } context.submitted = true; diff --git a/src/components/accessSchedule/accessSchedule.template.html b/src/components/accessSchedule/accessSchedule.template.html index 493150ae5e..c0f83ccec6 100644 --- a/src/components/accessSchedule/accessSchedule.template.html +++ b/src/components/accessSchedule/accessSchedule.template.html @@ -1,5 +1,5 @@
-

@@ -12,13 +12,13 @@
`; html += '
'; if (!readOnlyAttribute) { - html += ``; + html += ``; } html += '

'; if (!readOnlyAttribute) { @@ -166,10 +166,11 @@ import 'emby-button'; return apiClient.ajax({ type: 'POST', url: apiClient.getUrl('Environment/ValidatePath'), - data: { + data: JSON.stringify({ ValidateWriteable: validateWriteable, Path: path - } + }), + contentType: 'application/json' }).catch(response => { if (response) { if (response.status === 404) { diff --git a/src/components/displaySettings/displaySettings.js b/src/components/displaySettings/displaySettings.js index 4e24a3b730..9def27dc8a 100644 --- a/src/components/displaySettings/displaySettings.js +++ b/src/components/displaySettings/displaySettings.js @@ -1,13 +1,11 @@ import browser from 'browser'; import layoutManager from 'layoutManager'; -import appSettings from 'appSettings'; import pluginManager from 'pluginManager'; import appHost from 'apphost'; import focusManager from 'focusManager'; import datetime from 'datetime'; import globalize from 'globalize'; import loading from 'loading'; -import connectionManager from 'connectionManager'; import skinManager from 'skinManager'; import events from 'events'; import 'emby-select'; @@ -16,17 +14,22 @@ import 'emby-button'; /* eslint-disable indent */ - function fillThemes(select, isDashboard) { - select.innerHTML = skinManager.getThemes().map(t => { - let value = t.id; - if (t.isDefault && !isDashboard) { - value = ''; - } else if (t.isDefaultServerDashboard && isDashboard) { - value = ''; - } + function fillThemes(context, userSettings) { + const select = context.querySelector('#selectTheme'); - return ``; - }).join(''); + skinManager.getThemes().then(themes => { + select.innerHTML = themes.map(t => { + return ``; + }).join(''); + + // get default theme + var defaultTheme = themes.find(theme => { + return theme.default; + }); + + // set the current theme + select.value = userSettings.theme() || defaultTheme.id; + }); } function loadScreensavers(context, userSettings) { @@ -46,6 +49,7 @@ import 'emby-button'; selectScreensaver.innerHTML = options.map(o => { return ``; }).join(''); + selectScreensaver.value = userSettings.screensaver(); if (!selectScreensaver.value) { @@ -54,61 +58,7 @@ import 'emby-button'; } } - function loadSoundEffects(context, userSettings) { - - const selectSoundEffects = context.querySelector('.selectSoundEffects'); - const options = pluginManager.ofType('soundeffects').map(plugin => { - return { - name: plugin.name, - value: plugin.id - }; - }); - - options.unshift({ - name: globalize.translate('None'), - value: 'none' - }); - - selectSoundEffects.innerHTML = options.map(o => { - return ``; - }).join(''); - selectSoundEffects.value = userSettings.soundEffects(); - - if (!selectSoundEffects.value) { - // TODO: set the default instead of none - selectSoundEffects.value = 'none'; - } - } - - function loadSkins(context, userSettings) { - - const selectSkin = context.querySelector('.selectSkin'); - - const options = pluginManager.ofType('skin').map(plugin => { - return { - name: plugin.name, - value: plugin.id - }; - }); - - selectSkin.innerHTML = options.map(o => { - return ``; - }).join(''); - selectSkin.value = userSettings.skin(); - - if (!selectSkin.value && options.length) { - selectSkin.value = options[0].value; - } - - if (options.length > 1 && appHost.supports('skins')) { - context.querySelector('.selectSkinContainer').classList.remove('hide'); - } else { - context.querySelector('.selectSkinContainer').classList.add('hide'); - } - } - function showOrHideMissingEpisodesField(context) { - if (browser.tizen || browser.web0s) { context.querySelector('.fldDisplayMissingEpisodes').classList.add('hide'); return; @@ -118,13 +68,6 @@ import 'emby-button'; } function loadForm(context, user, userSettings) { - - if (user.Policy.IsAdministrator) { - context.querySelector('.selectDashboardThemeContainer').classList.remove('hide'); - } else { - context.querySelector('.selectDashboardThemeContainer').classList.add('hide'); - } - if (appHost.supports('displaylanguage')) { context.querySelector('.languageSection').classList.remove('hide'); } else { @@ -143,18 +86,6 @@ import 'emby-button'; context.querySelector('.learnHowToContributeContainer').classList.add('hide'); } - if (appHost.supports('runatstartup')) { - context.querySelector('.fldAutorun').classList.remove('hide'); - } else { - context.querySelector('.fldAutorun').classList.add('hide'); - } - - if (appHost.supports('soundeffects')) { - context.querySelector('.fldSoundEffects').classList.remove('hide'); - } else { - context.querySelector('.fldSoundEffects').classList.add('hide'); - } - if (appHost.supports('screensaver')) { context.querySelector('.selectScreensaverContainer').classList.remove('hide'); } else { @@ -177,16 +108,8 @@ import 'emby-button'; context.querySelector('.fldThemeVideo').classList.add('hide'); } - context.querySelector('.chkRunAtStartup').checked = appSettings.runAtStartup(); - - const selectTheme = context.querySelector('#selectTheme'); - const selectDashboardTheme = context.querySelector('#selectDashboardTheme'); - - fillThemes(selectTheme); - fillThemes(selectDashboardTheme, true); + fillThemes(context, userSettings); loadScreensavers(context, userSettings); - loadSoundEffects(context, userSettings); - loadSkins(context, userSettings); context.querySelector('.chkDisplayMissingEpisodes').checked = user.Configuration.DisplayMissingEpisodes || false; @@ -202,9 +125,6 @@ import 'emby-button'; context.querySelector('#txtLibraryPageSize').value = userSettings.libraryPageSize(); - selectDashboardTheme.value = userSettings.dashboardTheme() || ''; - selectTheme.value = userSettings.theme() || ''; - context.querySelector('.selectLayout').value = layoutManager.getSavedLayout() || ''; showOrHideMissingEpisodesField(context); @@ -213,9 +133,6 @@ import 'emby-button'; } function saveUser(context, user, userSettingsInstance, apiClient) { - - appSettings.runAtStartup(context.querySelector('.chkRunAtStartup').checked); - user.Configuration.DisplayMissingEpisodes = context.querySelector('.chkDisplayMissingEpisodes').checked; if (appHost.supports('displaylanguage')) { @@ -226,15 +143,11 @@ import 'emby-button'; userSettingsInstance.enableThemeSongs(context.querySelector('#chkThemeSong').checked); userSettingsInstance.enableThemeVideos(context.querySelector('#chkThemeVideo').checked); - userSettingsInstance.dashboardTheme(context.querySelector('#selectDashboardTheme').value); userSettingsInstance.theme(context.querySelector('#selectTheme').value); - userSettingsInstance.soundEffects(context.querySelector('.selectSoundEffects').value); userSettingsInstance.screensaver(context.querySelector('.selectScreensaver').value); userSettingsInstance.libraryPageSize(context.querySelector('#txtLibraryPageSize').value); - userSettingsInstance.skin(context.querySelector('.selectSkin').value); - userSettingsInstance.enableFastFadein(context.querySelector('#chkFadein').checked); userSettingsInstance.enableBlurhash(context.querySelector('#chkBlurhash').checked); userSettingsInstance.enableBackdrops(context.querySelector('#chkBackdrops').checked); @@ -268,7 +181,7 @@ import 'emby-button'; function onSubmit(e) { const self = this; - const apiClient = connectionManager.getApiClient(self.options.serverId); + const apiClient = window.connectionManager.getApiClient(self.options.serverId); const userId = self.options.userId; const userSettings = self.options.userSettings; @@ -307,7 +220,7 @@ import 'emby-button'; loading.show(); const userId = self.options.userId; - const apiClient = connectionManager.getApiClient(self.options.serverId); + const apiClient = window.connectionManager.getApiClient(self.options.serverId); const userSettings = self.options.userSettings; return apiClient.getUser(userId).then(user => { diff --git a/src/components/displaySettings/displaySettings.template.html b/src/components/displaySettings/displaySettings.template.html index ab01b4b6ae..1b9bf00376 100644 --- a/src/components/displaySettings/displaySettings.template.html +++ b/src/components/displaySettings/displaySettings.template.html @@ -1,5 +1,4 @@
-

${Display}

@@ -123,26 +122,14 @@
${LabelPleaseRestart}
-
- -
-
-
- -
-
-
- -
-
${LabelLibraryPageSizeHelp}
@@ -159,9 +146,9 @@
-
${EnableBlurhashHelp}
+
${EnableBlurHashHelp}
@@ -175,7 +162,7 @@
${EnableBackdropsHelp}
@@ -183,7 +170,7 @@
${EnableThemeSongsHelp}
@@ -191,18 +178,11 @@
${EnableThemeVideosHelp}
-
- -
-
'; } @@ -258,20 +242,16 @@ import 'emby-playstatebutton'; const channelIdData = item.ChannelId ? (` data-channelid="${item.ChannelId}"`) : ''; if (enableContentWrapper) { - cssClass += ' listItem-withContentWrapper'; } html += `<${outerTagName} class="${cssClass}"${playlistItemId} data-action="${action}" data-isfolder="${item.IsFolder}" data-id="${item.Id}" data-serverid="${item.ServerId}" data-type="${item.Type}"${mediaTypeData}${collectionTypeData}${channelIdData}${positionTicksData}${collectionIdData}${playlistIdData}>`; if (enableContentWrapper) { - html += '
'; } if (!clickEntireItem && options.dragHandle) { - //html += ''; - // Firefox and Edge are not allowing the button to be draggable html += ''; } @@ -319,7 +299,6 @@ import 'emby-playstatebutton'; } if (options.showIndexNumberLeft) { - html += '
'; html += (item.IndexNumber || ' '); html += '
'; @@ -367,9 +346,7 @@ import 'emby-playstatebutton'; } if (options.showParentTitle && options.parentTitleWithTitle) { - if (displayName) { - if (parentTitle) { parentTitle += ' - '; } @@ -387,27 +364,13 @@ import 'emby-playstatebutton'; if (item.IsFolder) { if (options.artist !== false) { - if (item.AlbumArtist && item.Type === 'MusicAlbum') { textlines.push(item.AlbumArtist); } } } else { - - let showArtist = options.artist === true; - const artistItems = item.ArtistItems; - - if (!showArtist && options.artist !== false) { - - if (!artistItems || !artistItems.length) { - showArtist = true; - } else if (artistItems.length > 1 || !containerAlbumArtistIds.includes(artistItems[0].Id)) { - showArtist = true; - } - } - - if (showArtist) { - + if (options.artist) { + const artistItems = item.ArtistItems; if (artistItems && item.Type !== 'MusicAlbum') { textlines.push(artistItems.map(a => { return a.Name; @@ -417,7 +380,6 @@ import 'emby-playstatebutton'; } if (item.Type === 'TvChannel') { - if (item.CurrentProgram) { textlines.push(itemHelper.getDisplayName(item.CurrentProgram)); } @@ -438,7 +400,6 @@ import 'emby-playstatebutton'; if (options.mediaInfo !== false) { if (!enableSideMediaInfo) { - const mediaInfoClass = 'secondary listItemMediaInfo listItemBodyText'; html += `
`; @@ -483,7 +444,6 @@ import 'emby-playstatebutton'; html += '
'; if (!clickEntireItem) { - if (options.addToListButton) { html += ''; } @@ -497,7 +457,6 @@ import 'emby-playstatebutton'; } if (options.enableUserDataButtons !== false) { - const userData = item.UserData || {}; const likes = userData.Likes == null ? '' : userData.Likes; diff --git a/src/components/loading/loading.js b/src/components/loading/loading.js index f36e41f44d..8237611373 100644 --- a/src/components/loading/loading.js +++ b/src/components/loading/loading.js @@ -1,80 +1,74 @@ -define(['css!./loading'], function () { - 'use strict'; +import 'css!./loading'; - var loadingElem; - var layer1; - var layer2; - var layer3; - var layer4; - var circleLefts; - var circleRights; +let loadingElem; +let layer1; +let layer2; +let layer3; +let layer4; +let circleLefts; +let circleRights; - return { - show: function () { - var elem = loadingElem; +export function show() { + let elem = loadingElem; - if (!elem) { + if (!elem) { + elem = document.createElement('div'); + loadingElem = elem; - elem = document.createElement('div'); - loadingElem = elem; + elem.classList.add('docspinner'); + elem.classList.add('mdl-spinner'); - elem.classList.add('docspinner'); - elem.classList.add('mdl-spinner'); + elem.innerHTML = '
'; - elem.innerHTML = '
'; + document.body.appendChild(elem); - document.body.appendChild(elem); + layer1 = elem.querySelector('.mdl-spinner__layer-1'); + layer2 = elem.querySelector('.mdl-spinner__layer-2'); + layer3 = elem.querySelector('.mdl-spinner__layer-3'); + layer4 = elem.querySelector('.mdl-spinner__layer-4'); - layer1 = elem.querySelector('.mdl-spinner__layer-1'); - layer2 = elem.querySelector('.mdl-spinner__layer-2'); - layer3 = elem.querySelector('.mdl-spinner__layer-3'); - layer4 = elem.querySelector('.mdl-spinner__layer-4'); + circleLefts = elem.querySelectorAll('.mdl-spinner__circleLeft'); + circleRights = elem.querySelectorAll('.mdl-spinner__circleRight'); + } - circleLefts = elem.querySelectorAll('.mdl-spinner__circleLeft'); - circleRights = elem.querySelectorAll('.mdl-spinner__circleRight'); - } + elem.classList.add('mdlSpinnerActive'); - elem.classList.add('mdlSpinnerActive'); + layer1.classList.add('mdl-spinner__layer-1-active'); + layer2.classList.add('mdl-spinner__layer-2-active'); + layer3.classList.add('mdl-spinner__layer-3-active'); + layer4.classList.add('mdl-spinner__layer-4-active'); - layer1.classList.add('mdl-spinner__layer-1-active'); - layer2.classList.add('mdl-spinner__layer-2-active'); - layer3.classList.add('mdl-spinner__layer-3-active'); - layer4.classList.add('mdl-spinner__layer-4-active'); + for (let i = 0, length = circleLefts.length; i < length; i++) { + circleLefts[i].classList.add('mdl-spinner__circleLeft-active'); + } - var i; - var length; + for (let i = 0, length = circleRights.length; i < length; i++) { + circleRights[i].classList.add('mdl-spinner__circleRight-active'); + } +} - for (i = 0, length = circleLefts.length; i < length; i++) { - circleLefts[i].classList.add('mdl-spinner__circleLeft-active'); - } +export function hide() { + const elem = loadingElem; - for (i = 0, length = circleRights.length; i < length; i++) { - circleRights[i].classList.add('mdl-spinner__circleRight-active'); - } - }, - hide: function () { - var elem = loadingElem; + if (elem) { + elem.classList.remove('mdlSpinnerActive'); - if (elem) { + elem.classList.remove('mdl-spinner__layer-1-active'); + elem.classList.remove('mdl-spinner__layer-2-active'); + elem.classList.remove('mdl-spinner__layer-3-active'); + elem.classList.remove('mdl-spinner__layer-4-active'); - elem.classList.remove('mdlSpinnerActive'); - - elem.classList.remove('mdl-spinner__layer-1-active'); - elem.classList.remove('mdl-spinner__layer-2-active'); - elem.classList.remove('mdl-spinner__layer-3-active'); - elem.classList.remove('mdl-spinner__layer-4-active'); - - var i; - var length; - - for (i = 0, length = circleLefts.length; i < length; i++) { - circleLefts[i].classList.remove('mdl-spinner__circleLeft-active'); - } - - for (i = 0, length = circleRights.length; i < length; i++) { - circleRights[i].classList.remove('mdl-spinner__circleRight-active'); - } - } + for (let i = 0, length = circleLefts.length; i < length; i++) { + circleLefts[i].classList.remove('mdl-spinner__circleLeft-active'); } - }; -}); + + for (let i = 0, length = circleRights.length; i < length; i++) { + circleRights[i].classList.remove('mdl-spinner__circleRight-active'); + } + } +} + +export default { + show: show, + hide: hide +}; diff --git a/src/components/maintabsmanager.js b/src/components/maintabsmanager.js index 0d31c2958a..1be1cf622f 100644 --- a/src/components/maintabsmanager.js +++ b/src/components/maintabsmanager.js @@ -8,29 +8,10 @@ import 'emby-button'; let tabOwnerView; const queryScope = document.querySelector('.skinHeader'); - let footerTabsContainer; let headerTabsContainer; let tabsElem; - function enableTabsInFooter() { - return false; - } - - function getTabsContainerElem() { - } - - function ensureElements(enableInFooter) { - - if (enableInFooter) { - if (!footerTabsContainer) { - footerTabsContainer = document.createElement('div'); - footerTabsContainer.classList.add('footerTabs'); - footerTabsContainer.classList.add('sectionTabs'); - footerTabsContainer.classList.add('hide'); - //appFooter.add(footerTabsContainer); - } - } - + function ensureElements() { if (!headerTabsContainer) { headerTabsContainer = queryScope.querySelector('.headerTabs'); } @@ -42,9 +23,7 @@ import 'emby-button'; } function allowSwipe(target) { - function allowSwipeOn(elem) { - if (dom.parentWithTag(elem, 'input')) { return false; } @@ -69,13 +48,11 @@ import 'emby-button'; } function configureSwipeTabs(view, tabsElem, getTabContainersFn) { - if (!browser.touch) { return; } // implement without hammer - const pageCount = getTabContainersFn().length; const onSwipeLeft = function (e, target) { if (allowSwipe(target) && view.contains(target)) { tabsElem.selectNext(); @@ -89,7 +66,6 @@ import 'emby-button'; }; import('touchHelper').then(({default: TouchHelper}) => { - const touchHelper = new TouchHelper(view.parentNode.parentNode); events.on(touchHelper, 'swipeleft', onSwipeLeft); @@ -102,28 +78,19 @@ import 'emby-button'; } export function setTabs(view, selectedIndex, getTabsFn, getTabContainersFn, onBeforeTabChange, onTabChange, setSelectedIndex) { - - const enableInFooter = enableTabsInFooter(); - if (!view) { if (tabOwnerView) { - if (!headerTabsContainer) { headerTabsContainer = queryScope.querySelector('.headerTabs'); } - ensureElements(enableInFooter); + ensureElements(); document.body.classList.remove('withSectionTabs'); headerTabsContainer.innerHTML = ''; headerTabsContainer.classList.add('hide'); - if (footerTabsContainer) { - footerTabsContainer.innerHTML = ''; - footerTabsContainer.classList.add('hide'); - } - tabOwnerView = null; } return { @@ -132,21 +99,19 @@ import 'emby-button'; }; } - ensureElements(enableInFooter); + ensureElements(); - const tabsContainerElem = enableInFooter ? footerTabsContainer : headerTabsContainer; + const tabsContainerElem = headerTabsContainer; if (!tabOwnerView) { tabsContainerElem.classList.remove('hide'); } if (tabOwnerView !== view) { - let index = 0; const indexAttribute = selectedIndex == null ? '' : (' data-index="' + selectedIndex + '"'); const tabsHtml = '
' + getTabsFn().map(function (t) { - let tabClass = 'emby-tab-button'; if (t.enabled === false) { @@ -167,7 +132,6 @@ import 'emby-button'; index++; return tabHtml; - }).join('') + '
'; tabsContainerElem.innerHTML = tabsHtml; @@ -181,10 +145,8 @@ import 'emby-button'; configureSwipeTabs(view, tabsElem, getTabContainersFn); tabsElem.addEventListener('beforetabchange', function (e) { - const tabContainers = getTabContainersFn(); if (e.detail.previousIndex != null) { - const previousPanel = tabContainers[e.detail.previousIndex]; if (previousPanel) { previousPanel.classList.remove('is-active'); @@ -193,12 +155,6 @@ import 'emby-button'; const newPanel = tabContainers[e.detail.selectedTabIndex]; - //if (e.detail.previousIndex != null && e.detail.previousIndex != e.detail.selectedTabIndex) { - // if (newPanel.animate && (animateTabs || []).indexOf(e.detail.selectedTabIndex) != -1) { - // fadeInRight(newPanel); - // } - //} - if (newPanel) { newPanel.classList.add('is-active'); } @@ -215,16 +171,11 @@ import 'emby-button'; if (tabsElem.selectedIndex) { tabsElem.selectedIndex(selectedIndex); } else { - tabsElem.readySelectedIndex = selectedIndex; tabsElem.addEventListener('ready', onViewTabsReady); } } - //if (enableSwipe !== false) { - // libraryBrowser.configureSwipeTabs(ownerpage, tabs); - //} - return { tabsContainer: tabsContainerElem, tabs: tabsContainerElem.querySelector('[is="emby-tabs"]'), @@ -247,7 +198,6 @@ import 'emby-button'; } export function selectedTabIndex(index) { - const tabsContainerElem = headerTabsContainer; if (!tabsElem) { diff --git a/src/components/mediaLibraryCreator/mediaLibraryCreator.template.html b/src/components/mediaLibraryCreator/mediaLibraryCreator.template.html index f92a63e403..4d84544d06 100644 --- a/src/components/mediaLibraryCreator/mediaLibraryCreator.template.html +++ b/src/components/mediaLibraryCreator/mediaLibraryCreator.template.html @@ -24,8 +24,8 @@
-

${HeadersFolders}

-
diff --git a/src/components/mediaLibraryEditor/mediaLibraryEditor.js b/src/components/mediaLibraryEditor/mediaLibraryEditor.js index 1cee6984d9..13d264f4c9 100644 --- a/src/components/mediaLibraryEditor/mediaLibraryEditor.js +++ b/src/components/mediaLibraryEditor/mediaLibraryEditor.js @@ -76,7 +76,7 @@ import 'flexStyles'; confirm({ title: globalize.translate('HeaderRemoveMediaLocation'), text: globalize.translate('MessageConfirmRemoveMediaLocation'), - confirmText: globalize.translate('ButtonDelete'), + confirmText: globalize.translate('Delete'), primary: 'delete' }).then(() => { const refreshAfterChange = currentOptions.refresh; @@ -85,7 +85,7 @@ import 'flexStyles'; refreshLibraryFromServer(dom.parentWithClass(button, 'dlg-libraryeditor')); }, () => { import('toast').then(({default: toast}) => { - toast(globalize.translate('DefaultErrorMessage')); + toast(globalize.translate('ErrorDefault')); }); }); }); @@ -98,8 +98,8 @@ import 'flexStyles'; if (listItem) { const index = parseInt(listItem.getAttribute('data-index')); const pathInfos = (currentOptions.library.LibraryOptions || {}).PathInfos || []; - const pathInfo = null == index ? {} : pathInfos[index] || {}; - const originalPath = pathInfo.Path || (null == index ? null : currentOptions.library.Locations[index]); + const pathInfo = index == null ? {} : pathInfos[index] || {}; + const originalPath = pathInfo.Path || (index == null ? null : currentOptions.library.Locations[index]); const btnRemovePath = dom.parentWithClass(e.target, 'btnRemovePath'); if (btnRemovePath) { @@ -171,7 +171,7 @@ import 'flexStyles'; const picker = new directoryBrowser(); picker.show({ enableNetworkSharePath: true, - pathReadOnly: null != originalPath, + pathReadOnly: originalPath != null, path: originalPath, networkSharePath: networkPath, callback: function (path, networkSharePath) { diff --git a/src/components/mediaLibraryEditor/mediaLibraryEditor.template.html b/src/components/mediaLibraryEditor/mediaLibraryEditor.template.html index 6c814cf2dd..1f2581612d 100644 --- a/src/components/mediaLibraryEditor/mediaLibraryEditor.template.html +++ b/src/components/mediaLibraryEditor/mediaLibraryEditor.template.html @@ -18,8 +18,8 @@
-

${HeadersFolders}

-
diff --git a/src/components/mediainfo/mediainfo.js b/src/components/mediainfo/mediainfo.js index 71acc5bbce..1d78d490a2 100644 --- a/src/components/mediainfo/mediainfo.js +++ b/src/components/mediainfo/mediainfo.js @@ -9,25 +9,20 @@ import 'programStyles'; import 'emby-button'; /* eslint-disable indent */ - function getTimerIndicator(item) { - let status; if (item.Type === 'SeriesTimer') { return ''; } else if (item.TimerId || item.SeriesTimerId) { - status = item.Status || 'Cancelled'; } else if (item.Type === 'Timer') { - status = item.Status; } else { return ''; } if (item.SeriesTimerId) { - if (status !== 'Cancelled') { return ''; } @@ -46,9 +41,7 @@ import 'emby-button'; let date; if (item.StartDate && options.programTime !== false) { - try { - text = ''; date = datetime.parseISO8601Date(item.StartDate); @@ -75,7 +68,6 @@ import 'emby-button'; } if (item.ChannelName) { - if (options.interactive && item.ChannelId) { miscInfo.push({ html: `${globalize.translate('AttributeNew')}
` + html: `
${globalize.translate('New')}
` }); } else if (item.IsSeries && item.IsRepeat) { miscInfo.push({ @@ -240,7 +217,6 @@ import 'emby-button'; } if ((item.IsSeries || item.EpisodeTitle) && options.episodeTitle !== false) { - text = itemHelper.getDisplayName(item, { includeIndexNumber: options.episodeTitleIndexNumber }); @@ -251,7 +227,6 @@ import 'emby-button'; } else if (item.IsMovie && item.ProductionYear && options.originalAirDate !== false) { miscInfo.push(item.ProductionYear); } else if (item.PremiereDate && options.originalAirDate !== false) { - try { date = datetime.parseISO8601Date(item.PremiereDate); text = globalize.translate('OriginalAirDateValue', datetime.toLocaleDateString(date)); @@ -266,12 +241,9 @@ import 'emby-button'; if (options.year !== false) { if (item.Type !== 'Series' && item.Type !== 'Episode' && item.Type !== 'Person' && item.MediaType !== 'Photo' && item.Type !== 'Program' && item.Type !== 'Season') { - if (item.ProductionYear) { - miscInfo.push(item.ProductionYear); } else if (item.PremiereDate) { - try { text = datetime.parseISO8601Date(item.PremiereDate).getFullYear(); miscInfo.push(text); @@ -283,11 +255,8 @@ import 'emby-button'; } if (item.RunTimeTicks && item.Type !== 'Series' && item.Type !== 'Program' && item.Type !== 'Book' && !showFolderRuntime && options.runtime !== false) { - if (item.Type === 'Audio') { - miscInfo.push(datetime.getDisplayRunningTime(item.RunTimeTicks)); - } else { minutes = item.RunTimeTicks / 600000000; @@ -327,7 +296,6 @@ import 'emby-button'; } if (item.CriticRating && options.criticRating !== false) { - if (item.CriticRating >= 60) { html += `
${item.CriticRating}
`; } else { @@ -336,7 +304,6 @@ import 'emby-button'; } if (options.endsAt !== false) { - const endsAt = getEndsAt(item); if (endsAt) { html += getMediaInfoItem(endsAt, 'endsAt'); @@ -349,9 +316,7 @@ import 'emby-button'; } export function getEndsAt(item) { - if (item.MediaType === 'Video' && item.RunTimeTicks) { - if (!item.StartDate) { let endDate = new Date().getTime() + (item.RunTimeTicks / 10000); endDate = new Date(endDate); @@ -365,7 +330,6 @@ import 'emby-button'; } export function getEndsAtFromPosition(runtimeTicks, positionTicks, includeText) { - let endDate = new Date().getTime() + ((runtimeTicks - (positionTicks || 0)) / 10000); endDate = new Date(endDate); @@ -378,12 +342,10 @@ import 'emby-button'; } function getMediaInfoItem(m, cssClass) { - cssClass = cssClass ? (`${cssClass} mediaInfoItem`) : 'mediaInfoItem'; let mediaInfoText = m; if (typeof (m) !== 'string' && typeof (m) !== 'number') { - if (m.html) { return m.html; } @@ -408,17 +370,13 @@ import 'emby-button'; } function dynamicEndTime(elem, item) { - const interval = setInterval(() => { - if (!document.body.contains(elem)) { - clearInterval(interval); return; } elem.innerHTML = getEndsAt(item); - }, 60000); } @@ -437,7 +395,6 @@ import 'emby-button'; } function afterFill(elem, item, options) { - if (options.endsAt !== false) { const endsAtElem = elem.querySelector('.endsAt'); if (endsAtElem) { @@ -452,7 +409,6 @@ import 'emby-button'; } function onChannelLinkClick(e) { - const channelId = this.getAttribute('data-id'); const serverId = this.getAttribute('data-serverid'); @@ -463,7 +419,6 @@ import 'emby-button'; } export function getPrimaryMediaInfoHtml(item, options) { - options = options || {}; if (options.interactive == null) { options.interactive = false; @@ -473,7 +428,6 @@ import 'emby-button'; } export function getSecondaryMediaInfoHtml(item, options) { - options = options || {}; if (options.interactive == null) { options.interactive = false; @@ -486,12 +440,10 @@ import 'emby-button'; } export function getResolutionText(i) { - const width = i.Width; const height = i.Height; if (width && height) { - if (width >= 3800 || height >= 2000) { return '4K'; } @@ -514,19 +466,16 @@ import 'emby-button'; return '720p'; } if (width >= 700 || height >= 400) { - if (i.IsInterlaced) { return '480i'; } return '480p'; } - } return null; } function getAudioStreamForDisplay(item) { - if (!item.MediaSources) { return null; } @@ -542,7 +491,6 @@ import 'emby-button'; } export function getMediaInfoStats(item, options) { - options = options || {}; const list = []; @@ -568,10 +516,6 @@ import 'emby-button'; }); } - //if (mediaSource.Container) { - // html += '
' + mediaSource.Container + '
'; - //} - const resolutionText = getResolutionText(videoStream); if (resolutionText) { list.push({ @@ -591,19 +535,12 @@ import 'emby-button'; let channelText; if (channels === 8) { - channelText = '7.1'; - } else if (channels === 7) { - channelText = '6.1'; - } else if (channels === 6) { - channelText = '5.1'; - } else if (channels === 2) { - channelText = '2.0'; } @@ -629,7 +566,6 @@ import 'emby-button'; } if (item.DateCreated && itemHelper.enableDateAddedDisplay(item)) { - const dateCreated = datetime.parseISO8601Date(item.DateCreated); list.push({ diff --git a/src/components/metadataEditor/metadataEditor.js b/src/components/metadataEditor/metadataEditor.js index 9bf2da2003..e2ff2a6b56 100644 --- a/src/components/metadataEditor/metadataEditor.js +++ b/src/components/metadataEditor/metadataEditor.js @@ -1,13 +1,10 @@ -import itemHelper from 'itemHelper'; import dom from 'dom'; import layoutManager from 'layoutManager'; import dialogHelper from 'dialogHelper'; import datetime from 'datetime'; import loading from 'loading'; import focusManager from 'focusManager'; -import connectionManager from 'connectionManager'; import globalize from 'globalize'; -import require from 'require'; import shell from 'shell'; import 'emby-checkbox'; import 'emby-input'; @@ -31,17 +28,14 @@ import 'flexStyles'; } function closeDialog(isSubmitted) { - if (isDialog()) { dialogHelper.close(currentContext); } } function submitUpdatedItem(form, item) { - function afterContentTypeUpdated() { - - require(['toast'], function (toast) { + import('toast').then(({default: toast}) => { toast(globalize.translate('MessageItemSaved')); }); @@ -52,11 +46,9 @@ import 'flexStyles'; const apiClient = getApiClient(); apiClient.updateItem(item).then(function () { - const newContentType = form.querySelector('#selectContentType').value || ''; if ((metadataEditorInfo.ContentType || '') !== newContentType) { - apiClient.ajax({ url: apiClient.getUrl('Items/' + item.Id + '/ContentType', { @@ -68,11 +60,9 @@ import 'flexStyles'; }).then(function () { afterContentTypeUpdated(); }); - } else { afterContentTypeUpdated(); } - }); } @@ -84,13 +74,9 @@ import 'flexStyles'; } function getAlbumArtists(form) { - return form.querySelector('#txtAlbumArtist').value.trim().split(';').filter(function (s) { - return s.length > 0; - }).map(function (a) { - return { Name: a }; @@ -98,13 +84,9 @@ import 'flexStyles'; } function getArtists(form) { - return form.querySelector('#txtArtist').value.trim().split(';').filter(function (s) { - return s.length > 0; - }).map(function (a) { - return { Name: a }; @@ -112,7 +94,6 @@ import 'flexStyles'; } function getDateValue(form, element, property) { - let val = form.querySelector(element).value; if (!val) { @@ -120,14 +101,12 @@ import 'flexStyles'; } if (currentItem[property]) { - const date = datetime.parseISO8601Date(currentItem[property], true); const parts = date.toISOString().split('T'); // If the date is the same, preserve the time if (parts[0].indexOf(val) === 0) { - const iso = parts[1]; val += 'T' + iso; @@ -138,7 +117,6 @@ import 'flexStyles'; } function onSubmit(e) { - loading.show(); const form = this; @@ -199,14 +177,12 @@ import 'flexStyles'; item.PreferredMetadataCountryCode = form.querySelector('#selectCountry').value; if (currentItem.Type === 'Person') { - const placeOfBirth = form.querySelector('#txtPlaceOfBirth').value; item.ProductionLocations = placeOfBirth ? [placeOfBirth] : []; } if (currentItem.Type === 'Series') { - // 600000000 const seriesRuntime = form.querySelector('#txtSeriesRuntime').value; item.RunTimeTicks = seriesRuntime ? (seriesRuntime * 600000000) : null; @@ -249,11 +225,8 @@ import 'flexStyles'; } function editPerson(context, person, index) { - - require(['personEditor'], function (personEditor) { - + import('personEditor').then(({default: personEditor}) => { personEditor.show(person).then(function (updatedPerson) { - const isNew = index === -1; if (isNew) { @@ -271,14 +244,14 @@ import 'flexStyles'; if (parentId) { reload(context, parentId, item.ServerId); } else { - require(['appRouter'], function (appRouter) { + import('appRouter').then(({default: appRouter}) => { appRouter.goHome(); }); } } function showMoreMenu(context, button, user) { - require(['itemContextMenu'], function (itemContextMenu) { + import('itemContextMenu').then(({default: itemContextMenu}) => { var item = currentItem; itemContextMenu.show({ @@ -303,7 +276,6 @@ import 'flexStyles'; } function onEditorClick(e) { - const btnRemoveFromEditorList = dom.parentWithClass(e.target, 'btnRemoveFromEditorList'); if (btnRemoveFromEditorList) { removeElementFromList(btnRemoveFromEditorList); @@ -317,7 +289,7 @@ import 'flexStyles'; } function getApiClient() { - return connectionManager.getApiClient(currentItem.ServerId); + return window.connectionManager.getApiClient(currentItem.ServerId); } function bindAll(elems, eventName, fn) { @@ -376,12 +348,10 @@ import 'flexStyles'; form.addEventListener('submit', onSubmit); context.querySelector('#btnAddPerson').addEventListener('click', function (event, data) { - editPerson(context, {}, -1); }); context.querySelector('#peopleList').addEventListener('click', function (e) { - let index; const btnDeletePerson = dom.parentWithClass(e.target, 'btnDeletePerson'); if (btnDeletePerson) { @@ -399,8 +369,7 @@ import 'flexStyles'; } function getItem(itemId, serverId) { - - const apiClient = connectionManager.getApiClient(serverId); + const apiClient = window.connectionManager.getApiClient(serverId); if (itemId) { return apiClient.getItem(apiClient.getCurrentUserId(), itemId); @@ -410,8 +379,7 @@ import 'flexStyles'; } function getEditorConfig(itemId, serverId) { - - const apiClient = connectionManager.getApiClient(serverId); + const apiClient = window.connectionManager.getApiClient(serverId); if (itemId) { return apiClient.getJSON(apiClient.getUrl('Items/' + itemId + '/MetadataEditor')); @@ -421,13 +389,11 @@ import 'flexStyles'; } function populateCountries(select, allCountries) { - let html = ''; html += ""; for (let i = 0, length = allCountries.length; i < length; i++) { - const culture = allCountries[i]; html += "'; @@ -437,13 +403,11 @@ import 'flexStyles'; } function populateLanguages(select, languages) { - let html = ''; html += ""; for (let i = 0, length = languages.length; i < length; i++) { - const culture = languages[i]; html += "'; @@ -453,7 +417,6 @@ import 'flexStyles'; } function renderContentTypeOptions(context, metadataInfo) { - if (!metadataInfo.ContentTypeOptions.length) { hideElement('#fldContentType', context); } else { @@ -461,9 +424,7 @@ import 'flexStyles'; } const html = metadataInfo.ContentTypeOptions.map(function (i) { - return ''; - }).join(''); const selectEl = context.querySelector('#selectContentType'); @@ -472,13 +433,11 @@ import 'flexStyles'; } function loadExternalIds(context, item, externalIds) { - let html = ''; const providerIds = item.ProviderIds || {}; for (let i = 0, length = externalIds.length; i < length; i++) { - const idInfo = externalIds[i]; const id = 'txt1' + idInfo.Key; @@ -524,7 +483,6 @@ import 'flexStyles'; function hideElement(selector, context, multiple) { context = context || document; if (typeof selector === 'string') { - const elements = multiple ? context.querySelectorAll(selector) : [context.querySelector(selector)]; Array.prototype.forEach.call(elements, function (el) { @@ -543,7 +501,6 @@ import 'flexStyles'; function showElement(selector, context, multiple) { context = context || document; if (typeof selector === 'string') { - const elements = multiple ? context.querySelectorAll(selector) : [context.querySelector(selector)]; Array.prototype.forEach.call(elements, function (el) { @@ -745,7 +702,6 @@ import 'flexStyles'; } function fillItemInfo(context, item, parentalRatingOptions) { - let select = context.querySelector('#selectOfficialRating'); populateRatings(parentalRatingOptions, select, item.OfficialRating); @@ -867,7 +823,6 @@ import 'flexStyles'; context.querySelector('#selectCountry').value = item.PreferredMetadataCountryCode || ''; if (item.RunTimeTicks) { - const minutes = item.RunTimeTicks / 600000000; context.querySelector('#txtSeriesRuntime').value = Math.round(minutes); @@ -877,7 +832,6 @@ import 'flexStyles'; } function populateRatings(allParentalRatings, select, currentValue) { - let html = ''; html += ""; @@ -888,7 +842,6 @@ import 'flexStyles'; let currentValueFound = false; for (let i = 0, length = allParentalRatings.length; i < length; i++) { - rating = allParentalRatings[i]; ratings.push({ Name: rating.Name, Value: rating.Name }); @@ -903,7 +856,6 @@ import 'flexStyles'; } for (let i = 0, length = ratings.length; i < length; i++) { - rating = ratings[i]; html += "'; @@ -922,7 +874,6 @@ import 'flexStyles'; } function populateListView(list, items, sortCallback) { - items = items || []; if (typeof (sortCallback) === 'undefined') { items.sort(function (a, b) { @@ -954,14 +905,12 @@ import 'flexStyles'; } function populatePeople(context, people) { - - let lastType = ''; + const lastType = ''; let html = ''; const elem = context.querySelector('#peopleList'); for (let i = 0, length = people.length; i < length; i++) { - const person = people[i]; html += '
'; @@ -991,10 +940,8 @@ import 'flexStyles'; } function getLockedFieldsHtml(fields, currentFields) { - let html = ''; for (let i = 0; i < fields.length; i++) { - const field = fields[i]; const name = field.name; const value = field.value || field.name; @@ -1041,11 +988,9 @@ import 'flexStyles'; } function reload(context, itemId, serverId) { - loading.show(); Promise.all([getItem(itemId, serverId), getEditorConfig(itemId, serverId)]).then(function (responses) { - const item = responses[0]; metadataEditorInfo = responses[1]; @@ -1122,7 +1067,7 @@ import 'flexStyles'; currentContext = dlg; - init(dlg, connectionManager.getApiClient(serverId)); + init(dlg, window.connectionManager.getApiClient(serverId)); reload(dlg, itemId, serverId); }); @@ -1137,7 +1082,6 @@ import 'flexStyles'; embed: function (elem, itemId, serverId) { return new Promise(function (resolve, reject) { - loading.show(); import('text!./metadataEditor.template.html').then(({default: template}) => { @@ -1150,7 +1094,7 @@ import 'flexStyles'; currentContext = elem; - init(elem, connectionManager.getApiClient(serverId)); + init(elem, window.connectionManager.getApiClient(serverId)); reload(elem, itemId, serverId); focusManager.autoFocus(elem); diff --git a/src/components/metadataEditor/personEditor.js b/src/components/metadataEditor/personEditor.js index 2349f40e7b..8326971247 100644 --- a/src/components/metadataEditor/personEditor.js +++ b/src/components/metadataEditor/personEditor.js @@ -1,7 +1,6 @@ import dialogHelper from 'dialogHelper'; import layoutManager from 'layoutManager'; import globalize from 'globalize'; -import require from 'require'; import 'paper-icon-button-light'; import 'emby-input'; import 'emby-select'; @@ -18,9 +17,7 @@ import 'css!./../formdialog'; function show(person) { return new Promise(function (resolve, reject) { - import('text!./personEditor.template.html').then(({default: template}) => { - const dialogOptions = { removeOnClose: true, scrollY: false @@ -54,7 +51,6 @@ import 'css!./../formdialog'; dialogHelper.open(dlg); dlg.addEventListener('close', function () { - if (layoutManager.tv) { centerFocus(dlg.querySelector('.formDialogContent'), false, false); } @@ -67,7 +63,6 @@ import 'css!./../formdialog'; }); dlg.querySelector('.selectPersonType').addEventListener('change', function (e) { - if (this.value === 'Actor') { dlg.querySelector('.fldRole').classList.remove('hide'); } else { @@ -76,12 +71,10 @@ import 'css!./../formdialog'; }); dlg.querySelector('.btnCancel').addEventListener('click', function (e) { - dialogHelper.close(dlg); }); dlg.querySelector('form').addEventListener('submit', function (e) { - submitted = true; person.Name = dlg.querySelector('.txtPersonName', dlg).value; diff --git a/src/components/multiSelect/multiSelect.js b/src/components/multiSelect/multiSelect.js index 87696909af..e7ce440f06 100644 --- a/src/components/multiSelect/multiSelect.js +++ b/src/components/multiSelect/multiSelect.js @@ -1,7 +1,6 @@ import browser from 'browser'; import appHost from 'apphost'; import loading from 'loading'; -import connectionManager from 'connectionManager'; import globalize from 'globalize'; import dom from 'dom'; import 'css!./multiSelect'; @@ -13,10 +12,8 @@ import 'css!./multiSelect'; let currentSelectionCommandsPanel; function hideSelections() { - const selectionCommandsPanel = currentSelectionCommandsPanel; if (selectionCommandsPanel) { - selectionCommandsPanel.parentNode.removeChild(selectionCommandsPanel); currentSelectionCommandsPanel = null; @@ -24,7 +21,6 @@ import 'css!./multiSelect'; selectedElements = []; const elems = document.querySelectorAll('.itemSelectionPanel'); for (let i = 0, length = elems.length; i < length; i++) { - const parent = elems[i].parentNode; parent.removeChild(elems[i]); parent.classList.remove('withMultiSelect'); @@ -33,13 +29,11 @@ import 'css!./multiSelect'; } function onItemSelectionPanelClick(e, itemSelectionPanel) { - // toggle the checkbox, if it wasn't clicked on if (!dom.parentWithClass(e.target, 'chkItemSelect')) { const chkItemSelect = itemSelectionPanel.querySelector('.chkItemSelect'); if (chkItemSelect) { - if (chkItemSelect.classList.contains('checkedInitial')) { chkItemSelect.classList.remove('checkedInitial'); } else { @@ -56,11 +50,9 @@ import 'css!./multiSelect'; } function updateItemSelection(chkItemSelect, selected) { - const id = dom.parentWithAttribute(chkItemSelect, 'data-id').getAttribute('data-id'); if (selected) { - const current = selectedItems.filter(i => { return i === id; }); @@ -69,7 +61,6 @@ import 'css!./multiSelect'; selectedItems.push(id); selectedElements.push(chkItemSelect); } - } else { selectedItems = selectedItems.filter(i => { return i !== id; @@ -94,11 +85,9 @@ import 'css!./multiSelect'; } function showSelection(item, isChecked) { - let itemSelectionPanel = item.querySelector('.itemSelectionPanel'); if (!itemSelectionPanel) { - itemSelectionPanel = document.createElement('div'); itemSelectionPanel.classList.add('itemSelectionPanel'); @@ -120,11 +109,9 @@ import 'css!./multiSelect'; } function showSelectionCommands() { - let selectionCommandsPanel = currentSelectionCommandsPanel; if (!selectionCommandsPanel) { - selectionCommandsPanel = document.createElement('div'); selectionCommandsPanel.classList.add('selectionCommandsPanel'); @@ -150,9 +137,7 @@ import 'css!./multiSelect'; } function alertText(options) { - return new Promise((resolve, reject) => { - import('alert').then(({default: alert}) => { alert(options).then(resolve, resolve); }); @@ -160,9 +145,7 @@ import 'css!./multiSelect'; } function deleteItems(apiClient, itemIds) { - return new Promise((resolve, reject) => { - let msg = globalize.translate('ConfirmDeleteItem'); let title = globalize.translate('HeaderDeleteItem'); @@ -172,28 +155,23 @@ import 'css!./multiSelect'; } import('confirm').then(({default: confirm}) => { - confirm(msg, title).then(() => { const promises = itemIds.map(itemId => { apiClient.deleteItem(itemId); }); Promise.all(promises).then(resolve, () => { - alertText(globalize.translate('ErrorDeletingItem')).then(reject, reject); }); }, reject); - }); }); } function showMenuForSelectedItems(e) { - - const apiClient = connectionManager.currentApiClient(); + const apiClient = window.connectionManager.currentApiClient(); apiClient.getCurrentUser().then(user => { - const menuItems = []; menuItems.push({ @@ -219,7 +197,7 @@ import 'css!./multiSelect'; if (user.Policy.EnableContentDownloading && appHost.supports('filedownload')) { menuItems.push({ - name: globalize.translate('ButtonDownload'), + name: globalize.translate('Download'), id: 'download', icon: 'file_download' }); @@ -317,17 +295,14 @@ import 'css!./multiSelect'; } } }); - }); }); } function dispatchNeedsRefresh() { - const elems = []; [].forEach.call(selectedElements, i => { - const container = dom.parentWithAttribute(i, 'is', 'emby-itemscontainer'); if (container && !elems.includes(container)) { @@ -341,9 +316,7 @@ import 'css!./multiSelect'; } function combineVersions(apiClient, selection) { - if (selection.length < 2) { - import('alert').then(({default: alert}) => { alert({ @@ -361,7 +334,6 @@ import 'css!./multiSelect'; url: apiClient.getUrl('Videos/MergeVersions', { Ids: selection.join(',') }) }).then(() => { - loading.hide(); hideSelections(); dispatchNeedsRefresh(); @@ -369,7 +341,6 @@ import 'css!./multiSelect'; } function showSelections(initialCard) { - import('emby-checkbox').then(() => { const cards = document.querySelectorAll('.card'); for (let i = 0, length = cards.length; i < length; i++) { @@ -382,11 +353,9 @@ import 'css!./multiSelect'; } function onContainerClick(e) { - const target = e.target; if (selectedItems.length) { - const card = dom.parentWithClass(target, 'card'); if (card) { const itemSelectionPanel = card.querySelector('.itemSelectionPanel'); @@ -404,17 +373,14 @@ import 'css!./multiSelect'; document.addEventListener('viewbeforehide', hideSelections); export default function (options) { - const self = this; const container = options.container; function onTapHold(e) { - const card = dom.parentWithClass(e.target, 'card'); if (card) { - showSelections(card); } @@ -427,7 +393,6 @@ import 'css!./multiSelect'; } function getTouches(e) { - return e.changedTouches || e.targetTouches || e.touches; } @@ -436,7 +401,6 @@ import 'css!./multiSelect'; let touchStartX; let touchStartY; function onTouchStart(e) { - const touch = getTouches(e)[0]; touchTarget = null; touchStartX = 0; @@ -451,7 +415,6 @@ import 'css!./multiSelect'; const card = dom.parentWithClass(element, 'card'); if (card) { - if (touchStartTimeout) { clearTimeout(touchStartTimeout); touchStartTimeout = null; @@ -465,7 +428,6 @@ import 'css!./multiSelect'; } function onTouchMove(e) { - if (touchTarget) { const touch = getTouches(e)[0]; let deltaX; @@ -487,12 +449,10 @@ import 'css!./multiSelect'; } function onTouchEnd(e) { - onMouseOut(e); } function onMouseDown(e) { - if (touchStartTimeout) { clearTimeout(touchStartTimeout); touchStartTimeout = null; @@ -503,7 +463,6 @@ import 'css!./multiSelect'; } function onMouseOut(e) { - if (touchStartTimeout) { clearTimeout(touchStartTimeout); touchStartTimeout = null; @@ -512,7 +471,6 @@ import 'css!./multiSelect'; } function onTouchStartTimerFired() { - if (!touchTarget) { return; } @@ -521,13 +479,11 @@ import 'css!./multiSelect'; touchTarget = null; if (card) { - showSelections(card); } } function initTapHold(element) { - // mobile safari doesn't allow contextmenu override if (browser.touch && !browser.safari) { element.addEventListener('contextmenu', onTapHold); @@ -565,7 +521,6 @@ import 'css!./multiSelect'; self.onContainerClick = onContainerClick; self.destroy = () => { - container.removeEventListener('click', onContainerClick); container.removeEventListener('contextmenu', onTapHold); @@ -580,10 +535,6 @@ import 'css!./multiSelect'; dom.removeEventListener(element, 'touchend', onTouchEnd, { passive: true }); - // this fires in safari due to magnifying class - //dom.removeEventListener(element, "touchcancel", onTouchEnd, { - // passive: true - //}); dom.removeEventListener(element, 'mousedown', onMouseDown, { passive: true }); diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js index c8480e4f15..cf3ae88345 100644 --- a/src/components/notifications/notifications.js +++ b/src/components/notifications/notifications.js @@ -1,192 +1,181 @@ -define(['serverNotifications', 'playbackManager', 'events', 'globalize', 'require'], function (serverNotifications, playbackManager, events, globalize, require) { - 'use strict'; +import serverNotifications from 'serverNotifications'; +import playbackManager from 'playbackManager'; +import events from 'events'; +import globalize from 'globalize'; - function onOneDocumentClick() { - document.removeEventListener('click', onOneDocumentClick); - document.removeEventListener('keydown', onOneDocumentClick); +function onOneDocumentClick() { + document.removeEventListener('click', onOneDocumentClick); + document.removeEventListener('keydown', onOneDocumentClick); - // don't request notification permissions if they're already granted or denied - if (window.Notification && window.Notification.permission === 'default') { - /* eslint-disable-next-line compat/compat */ - Notification.requestPermission(); - } - } - - document.addEventListener('click', onOneDocumentClick); - document.addEventListener('keydown', onOneDocumentClick); - - var serviceWorkerRegistration; - - function closeAfter(notification, timeoutMs) { - setTimeout(function () { - if (notification.close) { - notification.close(); - } else if (notification.cancel) { - notification.cancel(); - } - }, timeoutMs); - } - - function resetRegistration() { + // don't request notification permissions if they're already granted or denied + if (window.Notification && window.Notification.permission === 'default') { /* eslint-disable-next-line compat/compat */ - var serviceWorker = navigator.serviceWorker; - if (serviceWorker) { - serviceWorker.ready.then(function (registration) { - serviceWorkerRegistration = registration; - }); + Notification.requestPermission(); + } +} + +document.addEventListener('click', onOneDocumentClick); +document.addEventListener('keydown', onOneDocumentClick); + +let serviceWorkerRegistration; + +function closeAfter(notification, timeoutMs) { + setTimeout(function () { + if (notification.close) { + notification.close(); + } else if (notification.cancel) { + notification.cancel(); + } + }, timeoutMs); +} + +function resetRegistration() { + /* eslint-disable-next-line compat/compat */ + let serviceWorker = navigator.serviceWorker; + if (serviceWorker) { + serviceWorker.ready.then(function (registration) { + serviceWorkerRegistration = registration; + }); + } +} + +resetRegistration(); + +function showPersistentNotification(title, options, timeoutMs) { + serviceWorkerRegistration.showNotification(title, options); +} + +function showNonPersistentNotification(title, options, timeoutMs) { + try { + let notif = new Notification(title, options); /* eslint-disable-line compat/compat */ + + if (notif.show) { + notif.show(); + } + + if (timeoutMs) { + closeAfter(notif, timeoutMs); + } + } catch (err) { + if (options.actions) { + options.actions = []; + showNonPersistentNotification(title, options, timeoutMs); + } else { + throw err; } } +} + +function showNotification(options, timeoutMs, apiClient) { + let title = options.title; + + options.data = options.data || {}; + options.data.serverId = apiClient.serverInfo().Id; + options.icon = options.icon || getIconUrl(); + options.badge = options.badge || getIconUrl('badge.png'); resetRegistration(); - function showPersistentNotification(title, options, timeoutMs) { - serviceWorkerRegistration.showNotification(title, options); + if (serviceWorkerRegistration) { + showPersistentNotification(title, options, timeoutMs); + return; } - function showNonPersistentNotification(title, options, timeoutMs) { + showNonPersistentNotification(title, options, timeoutMs); +} - try { - var notif = new Notification(title, options); /* eslint-disable-line compat/compat */ - - if (notif.show) { - notif.show(); - } - - if (timeoutMs) { - closeAfter(notif, timeoutMs); - } - } catch (err) { - if (options.actions) { - options.actions = []; - showNonPersistentNotification(title, options, timeoutMs); - } else { - throw err; - } - } +function showNewItemNotification(item, apiClient) { + if (playbackManager.isPlayingLocally(['Video'])) { + return; } - function showNotification(options, timeoutMs, apiClient) { + let body = item.Name; - var title = options.title; - - options.data = options.data || {}; - options.data.serverId = apiClient.serverInfo().Id; - options.icon = options.icon || getIconUrl(); - options.badge = options.badge || getIconUrl('badge.png'); - - resetRegistration(); - - if (serviceWorkerRegistration) { - showPersistentNotification(title, options, timeoutMs); - return; - } - - showNonPersistentNotification(title, options, timeoutMs); + if (item.SeriesName) { + body = item.SeriesName + ' - ' + body; } - function showNewItemNotification(item, apiClient) { + let notification = { + title: 'New ' + item.Type, + body: body, + vibrate: true, + tag: 'newItem' + item.Id, + data: {} + }; - if (playbackManager.isPlayingLocally(['Video'])) { - return; - } + let imageTags = item.ImageTags || {}; - var body = item.Name; - - if (item.SeriesName) { - body = item.SeriesName + ' - ' + body; - } - - var notification = { - title: 'New ' + item.Type, - body: body, - vibrate: true, - tag: 'newItem' + item.Id, - data: { - //options: { - // url: LibraryBrowser.getHref(item) - //} - } - }; - - var imageTags = item.ImageTags || {}; - - if (imageTags.Primary) { - - notification.icon = apiClient.getScaledImageUrl(item.Id, { - width: 80, - tag: imageTags.Primary, - type: 'Primary' - }); - } - - showNotification(notification, 15000, apiClient); - } - - function onLibraryChanged(data, apiClient) { - - var newItems = data.ItemsAdded; - - if (!newItems.length) { - return; - } - - // Don't put a massive number of Id's onto the query string - if (newItems.length > 12) { - newItems.length = 12; - } - - apiClient.getItems(apiClient.getCurrentUserId(), { - - Recursive: true, - Limit: 3, - Filters: 'IsNotFolder', - SortBy: 'DateCreated', - SortOrder: 'Descending', - Ids: newItems.join(','), - MediaTypes: 'Audio,Video', - EnableTotalRecordCount: false - - }).then(function (result) { - - var items = result.Items; - - for (var i = 0, length = items.length ; i < length; i++) { - - showNewItemNotification(items[i], apiClient); - } + if (imageTags.Primary) { + notification.icon = apiClient.getScaledImageUrl(item.Id, { + width: 80, + tag: imageTags.Primary, + type: 'Primary' }); } - function getIconUrl(name) { - name = name || 'notificationicon.png'; - return require.toUrl('.').split('?')[0] + '/' + name; + showNotification(notification, 15000, apiClient); +} + +function onLibraryChanged(data, apiClient) { + let newItems = data.ItemsAdded; + + if (!newItems.length) { + return; } - function showPackageInstallNotification(apiClient, installation, status) { + // Don't put a massive number of Id's onto the query string + if (newItems.length > 12) { + newItems.length = 12; + } - apiClient.getCurrentUser().then(function (user) { + apiClient.getItems(apiClient.getCurrentUserId(), { - if (!user.Policy.IsAdministrator) { - return; - } + Recursive: true, + Limit: 3, + Filters: 'IsNotFolder', + SortBy: 'DateCreated', + SortOrder: 'Descending', + Ids: newItems.join(','), + MediaTypes: 'Audio,Video', + EnableTotalRecordCount: false - var notification = { - tag: 'install' + installation.Id, - data: {} - }; + }).then(function (result) { + let items = result.Items; - if (status === 'completed') { - notification.title = globalize.translate('PackageInstallCompleted', installation.Name, installation.Version); - notification.vibrate = true; - } else if (status === 'cancelled') { - notification.title = globalize.translate('PackageInstallCancelled', installation.Name, installation.Version); - } else if (status === 'failed') { - notification.title = globalize.translate('PackageInstallFailed', installation.Name, installation.Version); - notification.vibrate = true; - } else if (status === 'progress') { - notification.title = globalize.translate('InstallingPackage', installation.Name, installation.Version); + for (const item of items) { + showNewItemNotification(item, apiClient); + } + }); +} - notification.actions = +function getIconUrl(name) { + name = name || 'notificationicon.png'; + return './components/notifications/' + name; +} + +function showPackageInstallNotification(apiClient, installation, status) { + apiClient.getCurrentUser().then(function (user) { + if (!user.Policy.IsAdministrator) { + return; + } + + let notification = { + tag: 'install' + installation.Id, + data: {} + }; + + if (status === 'completed') { + notification.title = globalize.translate('PackageInstallCompleted', installation.Name, installation.Version); + notification.vibrate = true; + } else if (status === 'cancelled') { + notification.title = globalize.translate('PackageInstallCancelled', installation.Name, installation.Version); + } else if (status === 'failed') { + notification.title = globalize.translate('PackageInstallFailed', installation.Name, installation.Version); + notification.vibrate = true; + } else if (status === 'progress') { + notification.title = globalize.translate('InstallingPackage', installation.Name, installation.Version); + + notification.actions = [ { action: 'cancel-install', @@ -195,77 +184,75 @@ define(['serverNotifications', 'playbackManager', 'events', 'globalize', 'requir } ]; - notification.data.id = installation.id; - } + notification.data.id = installation.id; + } - if (status === 'progress') { + if (status === 'progress') { + let percentComplete = Math.round(installation.PercentComplete || 0); - var percentComplete = Math.round(installation.PercentComplete || 0); + notification.body = percentComplete + '% complete.'; + } - notification.body = percentComplete + '% complete.'; - } + let timeout = status === 'cancelled' ? 5000 : 0; - var timeout = status === 'cancelled' ? 5000 : 0; - - showNotification(notification, timeout, apiClient); - }); - } - - events.on(serverNotifications, 'LibraryChanged', function (e, apiClient, data) { - onLibraryChanged(data, apiClient); + showNotification(notification, timeout, apiClient); }); +} - events.on(serverNotifications, 'PackageInstallationCompleted', function (e, apiClient, data) { - showPackageInstallNotification(apiClient, data, 'completed'); - }); +events.on(serverNotifications, 'LibraryChanged', function (e, apiClient, data) { + onLibraryChanged(data, apiClient); +}); - events.on(serverNotifications, 'PackageInstallationFailed', function (e, apiClient, data) { - showPackageInstallNotification(apiClient, data, 'failed'); - }); +events.on(serverNotifications, 'PackageInstallationCompleted', function (e, apiClient, data) { + showPackageInstallNotification(apiClient, data, 'completed'); +}); - events.on(serverNotifications, 'PackageInstallationCancelled', function (e, apiClient, data) { - showPackageInstallNotification(apiClient, data, 'cancelled'); - }); +events.on(serverNotifications, 'PackageInstallationFailed', function (e, apiClient, data) { + showPackageInstallNotification(apiClient, data, 'failed'); +}); - events.on(serverNotifications, 'PackageInstalling', function (e, apiClient, data) { - showPackageInstallNotification(apiClient, data, 'progress'); - }); +events.on(serverNotifications, 'PackageInstallationCancelled', function (e, apiClient, data) { + showPackageInstallNotification(apiClient, data, 'cancelled'); +}); - events.on(serverNotifications, 'ServerShuttingDown', function (e, apiClient, data) { - var serverId = apiClient.serverInfo().Id; - var notification = { - tag: 'restart' + serverId, - title: globalize.translate('ServerNameIsShuttingDown', apiClient.serverInfo().Name) - }; - showNotification(notification, 0, apiClient); - }); +events.on(serverNotifications, 'PackageInstalling', function (e, apiClient, data) { + showPackageInstallNotification(apiClient, data, 'progress'); +}); - events.on(serverNotifications, 'ServerRestarting', function (e, apiClient, data) { - var serverId = apiClient.serverInfo().Id; - var notification = { - tag: 'restart' + serverId, - title: globalize.translate('ServerNameIsRestarting', apiClient.serverInfo().Name) - }; - showNotification(notification, 0, apiClient); - }); +events.on(serverNotifications, 'ServerShuttingDown', function (e, apiClient, data) { + let serverId = apiClient.serverInfo().Id; + let notification = { + tag: 'restart' + serverId, + title: globalize.translate('ServerNameIsShuttingDown', apiClient.serverInfo().Name) + }; + showNotification(notification, 0, apiClient); +}); - events.on(serverNotifications, 'RestartRequired', function (e, apiClient) { +events.on(serverNotifications, 'ServerRestarting', function (e, apiClient, data) { + let serverId = apiClient.serverInfo().Id; + let notification = { + tag: 'restart' + serverId, + title: globalize.translate('ServerNameIsRestarting', apiClient.serverInfo().Name) + }; + showNotification(notification, 0, apiClient); +}); - var serverId = apiClient.serverInfo().Id; - var notification = { - tag: 'restart' + serverId, - title: globalize.translate('PleaseRestartServerName', apiClient.serverInfo().Name) - }; +events.on(serverNotifications, 'RestartRequired', function (e, apiClient) { + let serverId = apiClient.serverInfo().Id; + let notification = { + tag: 'restart' + serverId, + title: globalize.translate('PleaseRestartServerName', apiClient.serverInfo().Name) + }; - notification.actions = + notification.actions = [ { action: 'restart', - title: globalize.translate('ButtonRestart'), + title: globalize.translate('Restart'), icon: getIconUrl() } ]; - showNotification(notification, 0, apiClient); - }); + showNotification(notification, 0, apiClient); }); + diff --git a/src/components/nowPlayingBar/nowPlayingBar.js b/src/components/nowPlayingBar/nowPlayingBar.js index ed46131292..d9521c4c9e 100644 --- a/src/components/nowPlayingBar/nowPlayingBar.js +++ b/src/components/nowPlayingBar/nowPlayingBar.js @@ -1,6 +1,4 @@ -import require from 'require'; import datetime from 'datetime'; -import itemHelper from 'itemHelper'; import events from 'events'; import browser from 'browser'; import imageLoader from 'imageLoader'; @@ -9,7 +7,6 @@ import playbackManager from 'playbackManager'; import nowPlayingHelper from 'nowPlayingHelper'; import appHost from 'apphost'; import dom from 'dom'; -import connectionManager from 'connectionManager'; import itemContextMenu from 'itemContextMenu'; import 'paper-icon-button-light'; import 'emby-ratingbutton'; @@ -39,7 +36,6 @@ import 'emby-ratingbutton'; let isVisibilityAllowed = true; function getNowPlayingBarHtml() { - let html = ''; html += '
'; @@ -99,12 +95,10 @@ import 'emby-ratingbutton'; } function onSlideDownComplete() { - this.classList.add('hide'); } function slideDown(elem) { - // trigger reflow void elem.offsetWidth; @@ -116,7 +110,6 @@ import 'emby-ratingbutton'; } function slideUp(elem) { - dom.removeEventListener(elem, dom.whichTransitionEvent(), onSlideDownComplete, { once: true }); @@ -134,7 +127,6 @@ import 'emby-ratingbutton'; } function bindEvents(elem) { - currentTimeElement = elem.querySelector('.nowPlayingBarCurrentTime'); nowPlayingImageElement = elem.querySelector('.nowPlayingImage'); nowPlayingTextElement = elem.querySelector('.nowPlayingBarText'); @@ -147,15 +139,12 @@ import 'emby-ratingbutton'; volumeSliderContainer = elem.querySelector('.nowPlayingBarVolumeSliderContainer'); muteButton.addEventListener('click', function () { - if (currentPlayer) { playbackManager.toggleMute(currentPlayer); } - }); elem.querySelector('.stopButton').addEventListener('click', function () { - if (currentPlayer) { playbackManager.stop(currentPlayer); } @@ -166,7 +155,6 @@ import 'emby-ratingbutton'; }); elem.querySelector('.nextTrackButton').addEventListener('click', function () { - if (currentPlayer) { playbackManager.nextTrack(currentPlayer); } @@ -226,18 +214,14 @@ import 'emby-ratingbutton'; }); positionSlider.addEventListener('change', function () { - if (currentPlayer) { - const newPercent = parseFloat(this.value); playbackManager.seekPercent(newPercent, currentPlayer); } - }); positionSlider.getBubbleText = function (value) { - const state = lastPlayerState; if (!state || !state.NowPlayingItem || !currentRuntimeTicks) { @@ -252,7 +236,6 @@ import 'emby-ratingbutton'; }; elem.addEventListener('click', function (e) { - if (!dom.parentWithTag(e.target, ['BUTTON', 'INPUT'])) { showRemoteControl(); } @@ -260,7 +243,6 @@ import 'emby-ratingbutton'; } function showRemoteControl() { - import('appRouter').then(({default: appRouter}) => { appRouter.showNowPlaying(); }); @@ -268,7 +250,6 @@ import 'emby-ratingbutton'; let nowPlayingBarElement; function getNowPlayingBar() { - if (nowPlayingBarElement) { return Promise.resolve(nowPlayingBarElement); } @@ -329,7 +310,6 @@ import 'emby-ratingbutton'; } function updatePlayerStateInternal(event, state, player) { - showNowPlayingBar(); lastPlayerState = state; @@ -417,7 +397,6 @@ import 'emby-ratingbutton'; } function updatePlayerVolumeState(isMuted, volumeLevel) { - const supportedCommands = currentPlayerSupportedCommands; let showMuteButton = true; @@ -448,7 +427,6 @@ import 'emby-ratingbutton'; // See bindEvents for why this is necessary if (volumeSlider) { - volumeSliderContainer.classList.toggle('hide', !showVolumeSlider); if (!volumeSlider.dragging) { @@ -458,7 +436,6 @@ import 'emby-ratingbutton'; } function seriesImageUrl(item, options) { - if (!item) { throw new Error('item cannot be null!'); } @@ -471,28 +448,23 @@ import 'emby-ratingbutton'; options.type = options.type || 'Primary'; if (options.type === 'Primary') { - if (item.SeriesPrimaryImageTag) { - options.tag = item.SeriesPrimaryImageTag; - return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); + return window.connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); } } if (options.type === 'Thumb') { - if (item.SeriesThumbImageTag) { - options.tag = item.SeriesThumbImageTag; - return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); + return window.connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); } if (item.ParentThumbImageTag) { - options.tag = item.ParentThumbImageTag; - return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.ParentThumbItemId, options); + return window.connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.ParentThumbItemId, options); } } @@ -500,7 +472,6 @@ import 'emby-ratingbutton'; } function imageUrl(item, options) { - if (!item) { throw new Error('item cannot be null!'); } @@ -509,15 +480,13 @@ import 'emby-ratingbutton'; options.type = options.type || 'Primary'; if (item.ImageTags && item.ImageTags[options.type]) { - options.tag = item.ImageTags[options.type]; - return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.PrimaryImageItemId || item.Id, options); + return window.connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.PrimaryImageItemId || item.Id, options); } if (item.AlbumId && item.AlbumPrimaryImageTag) { - options.tag = item.AlbumPrimaryImageTag; - return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.AlbumId, options); + return window.connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.AlbumId, options); } return null; @@ -525,26 +494,25 @@ import 'emby-ratingbutton'; let currentImgUrl; function updateNowPlayingInfo(state) { - const nowPlayingItem = state.NowPlayingItem; const textLines = nowPlayingItem ? nowPlayingHelper.getNowPlayingNames(nowPlayingItem) : []; nowPlayingTextElement.innerHTML = ''; if (textLines) { - let itemText = document.createElement('div'); - let secondaryText = document.createElement('div'); + const itemText = document.createElement('div'); + const secondaryText = document.createElement('div'); secondaryText.classList.add('nowPlayingBarSecondaryText'); if (textLines.length > 1) { textLines[1].secondary = true; if (textLines[1].text) { - let text = document.createElement('a'); + const text = document.createElement('a'); text.innerHTML = textLines[1].text; secondaryText.appendChild(text); } } if (textLines[0].text) { - let text = document.createElement('a'); + const text = document.createElement('a'); text.innerHTML = textLines[0].text; itemText.appendChild(text); } @@ -579,18 +547,17 @@ import 'emby-ratingbutton'; if (nowPlayingItem.Id) { if (isRefreshing) { - - const apiClient = connectionManager.getApiClient(nowPlayingItem.ServerId); + const apiClient = window.connectionManager.getApiClient(nowPlayingItem.ServerId); apiClient.getItem(apiClient.getCurrentUserId(), nowPlayingItem.Id).then(function (item) { const userData = item.UserData || {}; const likes = userData.Likes == null ? '' : userData.Likes; if (!layoutManager.mobile) { let contextButton = nowPlayingBarElement.querySelector('.btnToggleContextMenu'); // We remove the previous event listener by replacing the item in each update event - let contextButtonClone = contextButton.cloneNode(true); + const contextButtonClone = contextButton.cloneNode(true); contextButton.parentNode.replaceChild(contextButtonClone, contextButton); contextButton = nowPlayingBarElement.querySelector('.btnToggleContextMenu'); - let options = { + const options = { play: false, queue: false, clearQueue: true, @@ -632,10 +599,10 @@ import 'emby-ratingbutton'; return; } - let shuffleMode = playbackManager.getQueueShuffleMode(); - let context = nowPlayingBarElement; + const shuffleMode = playbackManager.getQueueShuffleMode(); + const context = nowPlayingBarElement; const cssClass = 'buttonActive'; - let toggleShuffleButton = context.querySelector('.btnShuffleQueue'); + const toggleShuffleButton = context.querySelector('.btnShuffleQueue'); switch (shuffleMode) { case 'Shuffle': toggleShuffleButton.classList.add(cssClass); @@ -657,7 +624,6 @@ import 'emby-ratingbutton'; } function hideNowPlayingBar() { - isEnabled = false; // Use a timeout to prevent the bar from hiding and showing quickly @@ -666,13 +632,11 @@ import 'emby-ratingbutton'; // Don't call getNowPlayingBar here because we don't want to end up creating it just to hide it const elem = document.getElementsByClassName('nowPlayingBar')[0]; if (elem) { - slideDown(elem); } } function onPlaybackStopped(e, state) { - console.debug('nowplaying event: ' + e.type); const player = this; @@ -688,7 +652,6 @@ import 'emby-ratingbutton'; } function onPlayPauseStateChanged(e) { - if (!isEnabled) { return; } @@ -698,7 +661,6 @@ import 'emby-ratingbutton'; } function onStateChanged(event, state) { - console.debug('nowplaying event: ' + event.type); const player = this; @@ -725,7 +687,6 @@ import 'emby-ratingbutton'; } function onTimeUpdate(e) { - if (!isEnabled) { return; } @@ -733,18 +694,16 @@ import 'emby-ratingbutton'; // Try to avoid hammering the document with changes const now = new Date().getTime(); if ((now - lastUpdateTime) < 700) { - return; } lastUpdateTime = now; const player = this; currentRuntimeTicks = playbackManager.duration(player); - updateTimeDisplay(playbackManager.currentTime(player), currentRuntimeTicks, playbackManager.getBufferedRanges(player)); + updateTimeDisplay(playbackManager.currentTime(player) * 10000, currentRuntimeTicks, playbackManager.getBufferedRanges(player)); } function releaseCurrentPlayer() { - const player = currentPlayer; if (player) { @@ -764,7 +723,6 @@ import 'emby-ratingbutton'; } function onVolumeChanged(e) { - if (!isEnabled) { return; } @@ -775,14 +733,12 @@ import 'emby-ratingbutton'; } function refreshFromPlayer(player) { - const state = playbackManager.getPlayerState(player); onStateChanged.call(player, { type: 'init' }, state); } function bindToPlayer(player) { - if (player === currentPlayer) { return; } @@ -815,16 +771,12 @@ import 'emby-ratingbutton'; bindToPlayer(playbackManager.getCurrentPlayer()); document.addEventListener('viewbeforeshow', function (e) { - if (!e.detail.options.enableMediaControl) { - if (isVisibilityAllowed) { isVisibilityAllowed = false; hideNowPlayingBar(); } - } else if (!isVisibilityAllowed) { - isVisibilityAllowed = true; if (currentPlayer) { refreshFromPlayer(currentPlayer); diff --git a/src/components/packageManager.js b/src/components/packageManager.js index 46f4704522..2a15a14f34 100644 --- a/src/components/packageManager.js +++ b/src/components/packageManager.js @@ -1,152 +1,140 @@ -define(['appSettings', 'pluginManager'], function (appSettings, pluginManager) { - 'use strict'; +import appSettings from 'appSettings'; +import pluginManager from 'pluginManager'; +/* eslint-disable indent */ - var settingsKey = 'installedpackages1'; + class PackageManager { + #packagesList = []; + #settingsKey = 'installedpackages1'; - function addPackage(packageManager, pkg) { + init() { + console.groupCollapsed('loading packages'); + var manifestUrls = JSON.parse(appSettings.get(this.#settingsKey) || '[]'); - packageManager.packagesList = packageManager.packagesList.filter(function (p) { + return Promise.all(manifestUrls.map((url) => { + return this.loadPackage(url); + })) + .then(() => { + console.debug('finished loading packages'); + return Promise.resolve(); + }) + .catch(() => { + return Promise.resolve(); + }).finally(() => { + console.groupEnd('loading packages'); + }); + } - return p.name !== pkg.name; - }); + get packages() { + return this.#packagesList.slice(0); + } - packageManager.packagesList.push(pkg); - } + install(url) { + return this.loadPackage(url, true).then((pkg) => { + var manifestUrls = JSON.parse(appSettings.get(this.#settingsKey) || '[]'); - function removeUrl(url) { - - var manifestUrls = JSON.parse(appSettings.get(settingsKey) || '[]'); - - manifestUrls = manifestUrls.filter(function (i) { - return i !== url; - }); - - appSettings.set(settingsKey, JSON.stringify(manifestUrls)); - } - - function loadPackage(packageManager, url, throwError) { - - return new Promise(function (resolve, reject) { - - var xhr = new XMLHttpRequest(); - var originalUrl = url; - url += url.indexOf('?') === -1 ? '?' : '&'; - url += 't=' + new Date().getTime(); - - xhr.open('GET', url, true); - - var onError = function () { - - if (throwError === true) { - reject(); - } else { - removeUrl(originalUrl); - resolve(); + if (!manifestUrls.includes(url)) { + manifestUrls.push(url); + appSettings.set(this.#settingsKey, JSON.stringify(manifestUrls)); } - }; - xhr.onload = function (e) { - if (this.status < 400) { + return pkg; + }); + } - var pkg = JSON.parse(this.response); - pkg.url = originalUrl; + uninstall(name) { + var pkg = this.#packagesList.filter((p) => { + return p.name === name; + })[0]; - addPackage(packageManager, pkg); + if (pkg) { + this.#packagesList = this.#packagesList.filter((p) => { + return p.name !== name; + }); - var plugins = pkg.plugins || []; - if (pkg.plugin) { - plugins.push(pkg.plugin); - } - var promises = plugins.map(function (pluginUrl) { - return pluginManager.loadPlugin(packageManager.mapPath(pkg, pluginUrl)); - }); - Promise.all(promises).then(resolve, resolve); - - } else { - onError(); - } - }; - - xhr.onerror = onError; - - xhr.send(); - }); - } - - function PackageManager() { - - this.packagesList = []; - } - - PackageManager.prototype.init = function () { - var manifestUrls = JSON.parse(appSettings.get(settingsKey) || '[]'); - - var instance = this; - return Promise.all(manifestUrls.map(function (u) { - - return loadPackage(instance, u); - - })).then(function () { - return Promise.resolve(); - }, function () { - return Promise.resolve(); - }); - }; - - PackageManager.prototype.packages = function () { - return this.packagesList.slice(0); - }; - - PackageManager.prototype.install = function (url) { - - return loadPackage(this, url, true).then(function (pkg) { - - var manifestUrls = JSON.parse(appSettings.get(settingsKey) || '[]'); - - if (manifestUrls.indexOf(url) === -1) { - manifestUrls.push(url); - appSettings.set(settingsKey, JSON.stringify(manifestUrls)); + this.removeUrl(pkg.url); } - return pkg; - }); - }; + return Promise.resolve(); + } - PackageManager.prototype.uninstall = function (name) { + mapPath(pkg, pluginUrl) { + var urlLower = pluginUrl.toLowerCase(); + if (urlLower.startsWith('http:') || urlLower.startsWith('https:') || urlLower.startsWith('file:')) { + return pluginUrl; + } - var pkg = this.packagesList.filter(function (p) { + var packageUrl = pkg.url; + packageUrl = packageUrl.substring(0, packageUrl.lastIndexOf('/')); - return p.name === name; - })[0]; + packageUrl += '/'; + packageUrl += pluginUrl; - if (pkg) { + return packageUrl; + } - this.packagesList = this.packagesList.filter(function (p) { - - return p.name !== name; + addPackage(pkg) { + this.#packagesList = this.#packagesList.filter((p) => { + return p.name !== pkg.name; }); - removeUrl(pkg.url); + this.#packagesList.push(pkg); } - return Promise.resolve(); - }; + removeUrl(url) { + var manifestUrls = JSON.parse(appSettings.get(this.#settingsKey) || '[]'); - PackageManager.prototype.mapPath = function (pkg, pluginUrl) { + manifestUrls = manifestUrls.filter((i) => { + return i !== url; + }); - var urlLower = pluginUrl.toLowerCase(); - if (urlLower.indexOf('http:') === 0 || urlLower.indexOf('https:') === 0 || urlLower.indexOf('file:') === 0) { - return pluginUrl; + appSettings.set(this.#settingsKey, JSON.stringify(manifestUrls)); } - var packageUrl = pkg.url; - packageUrl = packageUrl.substring(0, packageUrl.lastIndexOf('/')); + loadPackage(url, throwError = false) { + return new Promise((resolve, reject) => { + var xhr = new XMLHttpRequest(); + var originalUrl = url; + url += url.indexOf('?') === -1 ? '?' : '&'; + url += 't=' + new Date().getTime(); - packageUrl += '/'; - packageUrl += pluginUrl; + xhr.open('GET', url, true); - return packageUrl; - }; + var onError = () => { + if (throwError === true) { + reject(); + } else { + this.removeUrl(originalUrl); + resolve(); + } + }; - return new PackageManager(); -}); + xhr.onload = () => { + if (this.status < 400) { + var pkg = JSON.parse(this.response); + pkg.url = originalUrl; + + this.addPackage(pkg); + + var plugins = pkg.plugins || []; + if (pkg.plugin) { + plugins.push(pkg.plugin); + } + var promises = plugins.map((pluginUrl) => { + return pluginManager.loadPlugin(this.mapPath(pkg, pluginUrl)); + }); + Promise.all(promises).then(resolve, resolve); + } else { + onError(); + } + }; + + xhr.onerror = onError; + + xhr.send(); + }); + } + } + +/* eslint-enable indent */ + +export default new PackageManager(); diff --git a/src/components/playback/brightnessosd.js b/src/components/playback/brightnessosd.js index 5f3becd64b..9d5db62fe0 100644 --- a/src/components/playback/brightnessosd.js +++ b/src/components/playback/brightnessosd.js @@ -23,10 +23,8 @@ function getOsdElementHtml() { } function ensureOsdElement() { - var elem = osdElement; if (!elem) { - enableAnimation = browser.supportsCssAnimation(); elem = document.createElement('div'); @@ -50,7 +48,6 @@ function onHideComplete() { var hideTimeout; function showOsd() { - clearHideTimeout(); var elem = osdElement; @@ -79,12 +76,10 @@ function clearHideTimeout() { } function hideOsd() { - clearHideTimeout(); var elem = osdElement; if (elem) { - if (enableAnimation) { // trigger reflow void elem.offsetWidth; @@ -108,7 +103,6 @@ function setIcon(iconElement, icon) { } function updateElementsFromPlayer(brightness) { - if (iconElement) { if (brightness >= 80) { setIcon(iconElement, 'brightness_high'); @@ -124,7 +118,6 @@ function updateElementsFromPlayer(brightness) { } function releaseCurrentPlayer() { - var player = currentPlayer; if (player) { @@ -135,7 +128,6 @@ function releaseCurrentPlayer() { } function onBrightnessChanged(e) { - var player = this; ensureOsdElement(); @@ -146,7 +138,6 @@ function onBrightnessChanged(e) { } function bindToPlayer(player) { - if (player === currentPlayer) { return; } diff --git a/src/components/playback/mediasession.js b/src/components/playback/mediasession.js index 0f275c88f7..2478c52d6e 100644 --- a/src/components/playback/mediasession.js +++ b/src/components/playback/mediasession.js @@ -1,7 +1,6 @@ import playbackManager from 'playbackManager'; import nowPlayingHelper from 'nowPlayingHelper'; import events from 'events'; -import connectionManager from 'connectionManager'; /* eslint-disable indent */ // Reports media playback to the device for lock screen control @@ -16,16 +15,16 @@ import connectionManager from 'connectionManager'; } else if (options.type === 'Primary' && item.SeriesPrimaryImageTag) { options.tag = item.SeriesPrimaryImageTag; - return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); + return window.connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); } else if (options.type === 'Thumb') { if (item.SeriesThumbImageTag) { options.tag = item.SeriesThumbImageTag; - return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); + return window.connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); } else if (item.ParentThumbImageTag) { options.tag = item.ParentThumbImageTag; - return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.ParentThumbItemId, options); + return window.connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.ParentThumbItemId, options); } } @@ -38,11 +37,11 @@ import connectionManager from 'connectionManager'; if (item.ImageTags && item.ImageTags[options.type]) { options.tag = item.ImageTags[options.type]; - return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.Id, options); + return window.connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.Id, options); } else if (item.AlbumId && item.AlbumPrimaryImageTag) { options.tag = item.AlbumPrimaryImageTag; - return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.AlbumId, options); + return window.connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.AlbumId, options); } return null; @@ -127,7 +126,7 @@ import connectionManager from 'connectionManager'; artwork: getImageUrls(item) }); } else { - let itemImageUrl = seriesImageUrl(item, { maxHeight: 3000 }) || imageUrl(item, { maxHeight: 3000 }); + const itemImageUrl = seriesImageUrl(item, { maxHeight: 3000 }) || imageUrl(item, { maxHeight: 3000 }); window.NativeShell.updateMediaSession({ action: eventName, @@ -244,10 +243,10 @@ import connectionManager from 'connectionManager'; /* eslint-disable-next-line compat/compat */ navigator.mediaSession.setActionHandler('seekto', function (object) { - let item = playbackManager.getPlayerState(currentPlayer).NowPlayingItem; + const item = playbackManager.getPlayerState(currentPlayer).NowPlayingItem; // Convert to ms - let duration = parseInt(item.RunTimeTicks ? (item.RunTimeTicks / 10000) : 0); - let wantedTime = object.seekTime * 1000; + const duration = parseInt(item.RunTimeTicks ? (item.RunTimeTicks / 10000) : 0); + const wantedTime = object.seekTime * 1000; playbackManager.seekPercent(wantedTime / duration * 100, currentPlayer); }); } diff --git a/src/components/playback/nowplayinghelper.js b/src/components/playback/nowplayinghelper.js index 310edc03c3..d7f9d04865 100644 --- a/src/components/playback/nowplayinghelper.js +++ b/src/components/playback/nowplayinghelper.js @@ -1,5 +1,4 @@ export function getNowPlayingNames(nowPlayingItem, includeNonNameInfo) { - var topItem = nowPlayingItem; var bottomItem = null; var topText = nowPlayingItem.Name; @@ -25,7 +24,6 @@ export function getNowPlayingNames(nowPlayingItem, includeNonNameInfo) { var bottomText = ''; if (nowPlayingItem.ArtistItems && nowPlayingItem.ArtistItems.length) { - bottomItem = { Id: nowPlayingItem.ArtistItems[0].Id, Name: nowPlayingItem.ArtistItems[0].Name, @@ -36,9 +34,7 @@ export function getNowPlayingNames(nowPlayingItem, includeNonNameInfo) { bottomText = nowPlayingItem.ArtistItems.map(function (a) { return a.Name; }).join(', '); - } else if (nowPlayingItem.Artists && nowPlayingItem.Artists.length) { - bottomText = nowPlayingItem.Artists.join(', '); } else if (nowPlayingItem.SeriesName || nowPlayingItem.Album) { bottomText = topText; diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index fe3ff11250..82bce8075e 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -1,792 +1,737 @@ -define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'playQueueManager', 'userSettings', 'globalize', 'connectionManager', 'loading', 'apphost', 'screenfull'], function (events, datetime, appSettings, itemHelper, pluginManager, PlayQueueManager, userSettings, globalize, connectionManager, loading, apphost, screenfull) { - 'use strict'; - - function enableLocalPlaylistManagement(player) { - - if (player.getPlaylist) { - - return false; - } - - if (player.isLocalPlayer) { - - return true; - } +import events from 'events'; +import datetime from 'datetime'; +import appSettings from 'appSettings'; +import itemHelper from 'itemHelper'; +import pluginManager from 'pluginManager'; +import PlayQueueManager from 'playQueueManager'; +import * as userSettings from 'userSettings'; +import globalize from 'globalize'; +import loading from 'loading'; +import appHost from 'apphost'; +import screenfull from 'screenfull'; +function enableLocalPlaylistManagement(player) { + if (player.getPlaylist) { return false; } - function bindToFullscreenChange(player) { - if (screenfull.isEnabled) { - screenfull.on('change', function () { - events.trigger(player, 'fullscreenchange'); - }); - } - } - - function triggerPlayerChange(playbackManagerInstance, newPlayer, newTarget, previousPlayer, previousTargetInfo) { - - if (!newPlayer && !previousPlayer) { - return; - } - - if (newTarget && previousTargetInfo) { - - if (newTarget.id === previousTargetInfo.id) { - return; - } - } - - events.trigger(playbackManagerInstance, 'playerchange', [newPlayer, newTarget, previousPlayer]); - } - - function reportPlayback(playbackManagerInstance, state, player, reportPlaylist, serverId, method, progressEventName) { - - if (!serverId) { - // Not a server item - // We can expand on this later and possibly report them - events.trigger(playbackManagerInstance, 'reportplayback', [false]); - return; - } - - var info = Object.assign({}, state.PlayState); - info.ItemId = state.NowPlayingItem.Id; - - if (progressEventName) { - info.EventName = progressEventName; - } - - if (reportPlaylist) { - addPlaylistToPlaybackReport(playbackManagerInstance, info, player, serverId); - } - - var apiClient = connectionManager.getApiClient(serverId); - var reportPlaybackPromise = apiClient[method](info); - // Notify that report has been sent - reportPlaybackPromise.then(() => { - events.trigger(playbackManagerInstance, 'reportplayback', [true]); - }); - } - - function getPlaylistSync(playbackManagerInstance, player) { - player = player || playbackManagerInstance._currentPlayer; - if (player && !enableLocalPlaylistManagement(player)) { - return player.getPlaylistSync(); - } - - return playbackManagerInstance._playQueueManager.getPlaylist(); - } - - function addPlaylistToPlaybackReport(playbackManagerInstance, info, player, serverId) { - - info.NowPlayingQueue = getPlaylistSync(playbackManagerInstance, player).map(function (i) { - - var itemInfo = { - Id: i.Id, - PlaylistItemId: i.PlaylistItemId - }; - - if (i.ServerId !== serverId) { - itemInfo.ServerId = i.ServerId; - } - - return itemInfo; - }); - } - - function normalizeName(t) { - return t.toLowerCase().replace(' ', ''); - } - - function getItemsForPlayback(serverId, query) { - - var apiClient = connectionManager.getApiClient(serverId); - - if (query.Ids && query.Ids.split(',').length === 1) { - - var itemId = query.Ids.split(','); - - return apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(function (item) { - - return { - Items: [item], - TotalRecordCount: 1 - }; - }); - } else { - - query.Limit = query.Limit || 300; - query.Fields = 'Chapters'; - query.ExcludeLocationTypes = 'Virtual'; - query.EnableTotalRecordCount = false; - query.CollapseBoxSetItems = false; - - return apiClient.getItems(apiClient.getCurrentUserId(), query); - } - } - - function createStreamInfoFromUrlItem(item) { - - // Check item.Path for games - return { - url: item.Url || item.Path, - playMethod: 'DirectPlay', - item: item, - textTracks: [], - mediaType: item.MediaType - }; - } - - function mergePlaybackQueries(obj1, obj2) { - - var query = Object.assign(obj1, obj2); - - var filters = query.Filters ? query.Filters.split(',') : []; - if (filters.indexOf('IsNotFolder') === -1) { - filters.push('IsNotFolder'); - } - query.Filters = filters.join(','); - return query; - } - - function backdropImageUrl(apiClient, item, options) { - - options = options || {}; - options.type = options.type || 'Backdrop'; - - // If not resizing, get the original image - if (!options.maxWidth && !options.width && !options.maxHeight && !options.height) { - options.quality = 100; - } - - if (item.BackdropImageTags && item.BackdropImageTags.length) { - - options.tag = item.BackdropImageTags[0]; - return apiClient.getScaledImageUrl(item.Id, options); - } - - if (item.ParentBackdropImageTags && item.ParentBackdropImageTags.length) { - options.tag = item.ParentBackdropImageTags[0]; - return apiClient.getScaledImageUrl(item.ParentBackdropItemId, options); - } - - return null; - } - - function getMimeType(type, container) { - - container = (container || '').toLowerCase(); - - if (type === 'audio') { - if (container === 'opus') { - return 'audio/ogg'; - } - if (container === 'webma') { - return 'audio/webm'; - } - if (container === 'm4a') { - return 'audio/mp4'; - } - } else if (type === 'video') { - if (container === 'mkv') { - return 'video/x-matroska'; - } - if (container === 'm4v') { - return 'video/mp4'; - } - if (container === 'mov') { - return 'video/quicktime'; - } - if (container === 'mpg') { - return 'video/mpeg'; - } - if (container === 'flv') { - return 'video/x-flv'; - } - } - - return type + '/' + container; - } - - function getParam(name, url) { - name = name.replace(/[\[]/, '\\\[').replace(/[\]]/, '\\\]'); - var regexS = '[\\?&]' + name + '=([^&#]*)'; - var regex = new RegExp(regexS, 'i'); - - var results = regex.exec(url); - if (results == null) { - return ''; - } else { - return decodeURIComponent(results[1].replace(/\+/g, ' ')); - } - } - - function isAutomaticPlayer(player) { - - if (player.isLocalPlayer) { - return true; - } - - return false; - } - - function getAutomaticPlayers(instance, forceLocalPlayer) { - - if (!forceLocalPlayer) { - var player = instance._currentPlayer; - if (player && !isAutomaticPlayer(player)) { - return [player]; - } - } - - return instance.getPlayers().filter(isAutomaticPlayer); - } - - function isServerItem(item) { - if (!item.Id) { - return false; - } + if (player.isLocalPlayer) { return true; } - function enableIntros(item) { + return false; +} - if (item.MediaType !== 'Video') { - return false; - } - if (item.Type === 'TvChannel') { - return false; - } - // disable for in-progress recordings - if (item.Status === 'InProgress') { - return false; - } +function bindToFullscreenChange(player) { + if (screenfull.isEnabled) { + screenfull.on('change', function () { + events.trigger(player, 'fullscreenchange'); + }); + } else { + // iOS Safari + document.addEventListener('webkitfullscreenchange', function () { + events.trigger(player, 'fullscreenchange'); + }, false); + } +} - return isServerItem(item); +function triggerPlayerChange(playbackManagerInstance, newPlayer, newTarget, previousPlayer, previousTargetInfo) { + if (!newPlayer && !previousPlayer) { + return; } - function getIntros(firstItem, apiClient, options) { - - if (options.startPositionTicks || options.startIndex || options.fullscreen === false || !enableIntros(firstItem) || !userSettings.enableCinemaMode()) { - return Promise.resolve({ - Items: [] - }); + if (newTarget && previousTargetInfo) { + if (newTarget.id === previousTargetInfo.id) { + return; } - - return apiClient.getIntros(firstItem.Id).then(function (result) { - - return result; - - }, function (err) { - - return Promise.resolve({ - Items: [] - }); - }); } - function getAudioMaxValues(deviceProfile) { + events.trigger(playbackManagerInstance, 'playerchange', [newPlayer, newTarget, previousPlayer]); +} - // TODO - this could vary per codec and should be done on the server using the entire profile - var maxAudioSampleRate = null; - var maxAudioBitDepth = null; - var maxAudioBitrate = null; +function reportPlayback(playbackManagerInstance, state, player, reportPlaylist, serverId, method, progressEventName) { + if (!serverId) { + // Not a server item + // We can expand on this later and possibly report them + events.trigger(playbackManagerInstance, 'reportplayback', [false]); + return; + } - deviceProfile.CodecProfiles.map(function (codecProfile) { + const info = Object.assign({}, state.PlayState); + info.ItemId = state.NowPlayingItem.Id; - if (codecProfile.Type === 'Audio') { - (codecProfile.Conditions || []).map(function (condition) { - if (condition.Condition === 'LessThanEqual' && condition.Property === 'AudioBitDepth') { - return maxAudioBitDepth = condition.Value; - } else if (condition.Condition === 'LessThanEqual' && condition.Property === 'AudioSampleRate') { - return maxAudioSampleRate = condition.Value; - } else if (condition.Condition === 'LessThanEqual' && condition.Property === 'AudioBitrate') { - return maxAudioBitrate = condition.Value; - } - }); - } - }); + if (progressEventName) { + info.EventName = progressEventName; + } - return { - maxAudioSampleRate: maxAudioSampleRate, - maxAudioBitDepth: maxAudioBitDepth, - maxAudioBitrate: maxAudioBitrate + if (reportPlaylist) { + addPlaylistToPlaybackReport(playbackManagerInstance, info, player, serverId); + } + + const apiClient = window.connectionManager.getApiClient(serverId); + const reportPlaybackPromise = apiClient[method](info); + // Notify that report has been sent + reportPlaybackPromise.then(() => { + events.trigger(playbackManagerInstance, 'reportplayback', [true]); + }); +} + +function getPlaylistSync(playbackManagerInstance, player) { + player = player || playbackManagerInstance._currentPlayer; + if (player && !enableLocalPlaylistManagement(player)) { + return player.getPlaylistSync(); + } + + return playbackManagerInstance._playQueueManager.getPlaylist(); +} + +function addPlaylistToPlaybackReport(playbackManagerInstance, info, player, serverId) { + info.NowPlayingQueue = getPlaylistSync(playbackManagerInstance, player).map(function (i) { + const itemInfo = { + Id: i.Id, + PlaylistItemId: i.PlaylistItemId }; + + if (i.ServerId !== serverId) { + itemInfo.ServerId = i.ServerId; + } + + return itemInfo; + }); +} + +function normalizeName(t) { + return t.toLowerCase().replace(' ', ''); +} + +function getItemsForPlayback(serverId, query) { + const apiClient = window.connectionManager.getApiClient(serverId); + + if (query.Ids && query.Ids.split(',').length === 1) { + const itemId = query.Ids.split(','); + + return apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(function (item) { + return { + Items: [item], + TotalRecordCount: 1 + }; + }); + } else { + query.Limit = query.Limit || 300; + query.Fields = 'Chapters'; + query.ExcludeLocationTypes = 'Virtual'; + query.EnableTotalRecordCount = false; + query.CollapseBoxSetItems = false; + + return apiClient.getItems(apiClient.getCurrentUserId(), query); + } +} + +function createStreamInfoFromUrlItem(item) { + // Check item.Path for games + return { + url: item.Url || item.Path, + playMethod: 'DirectPlay', + item: item, + textTracks: [], + mediaType: item.MediaType + }; +} + +function mergePlaybackQueries(obj1, obj2) { + const query = Object.assign(obj1, obj2); + + const filters = query.Filters ? query.Filters.split(',') : []; + if (filters.indexOf('IsNotFolder') === -1) { + filters.push('IsNotFolder'); + } + query.Filters = filters.join(','); + return query; +} + +function backdropImageUrl(apiClient, item, options) { + options = options || {}; + options.type = options.type || 'Backdrop'; + + // If not resizing, get the original image + if (!options.maxWidth && !options.width && !options.maxHeight && !options.height) { + options.quality = 100; } - var startingPlaySession = new Date().getTime(); - function getAudioStreamUrl(item, transcodingProfile, directPlayContainers, maxBitrate, apiClient, maxAudioSampleRate, maxAudioBitDepth, maxAudioBitrate, startPosition) { + if (item.BackdropImageTags && item.BackdropImageTags.length) { + options.tag = item.BackdropImageTags[0]; + return apiClient.getScaledImageUrl(item.Id, options); + } - var url = 'Audio/' + item.Id + '/universal'; + if (item.ParentBackdropImageTags && item.ParentBackdropImageTags.length) { + options.tag = item.ParentBackdropImageTags[0]; + return apiClient.getScaledImageUrl(item.ParentBackdropItemId, options); + } - startingPlaySession++; - return apiClient.getUrl(url, { - UserId: apiClient.getCurrentUserId(), - DeviceId: apiClient.deviceId(), - MaxStreamingBitrate: maxAudioBitrate || maxBitrate, - Container: directPlayContainers, - TranscodingContainer: transcodingProfile.Container || null, - TranscodingProtocol: transcodingProfile.Protocol || null, - AudioCodec: transcodingProfile.AudioCodec, - MaxAudioSampleRate: maxAudioSampleRate, - MaxAudioBitDepth: maxAudioBitDepth, - api_key: apiClient.accessToken(), - PlaySessionId: startingPlaySession, - StartTimeTicks: startPosition || 0, - EnableRedirection: true, - EnableRemoteMedia: apphost.supports('remoteaudio') + return null; +} + +function getMimeType(type, container) { + container = (container || '').toLowerCase(); + + if (type === 'audio') { + if (container === 'opus') { + return 'audio/ogg'; + } + if (container === 'webma') { + return 'audio/webm'; + } + if (container === 'm4a') { + return 'audio/mp4'; + } + } else if (type === 'video') { + if (container === 'mkv') { + return 'video/x-matroska'; + } + if (container === 'm4v') { + return 'video/mp4'; + } + if (container === 'mov') { + return 'video/quicktime'; + } + if (container === 'mpg') { + return 'video/mpeg'; + } + if (container === 'flv') { + return 'video/x-flv'; + } + } + + return type + '/' + container; +} + +function getParam(name, url) { + name = name.replace(/[\[]/, '\\\[').replace(/[\]]/, '\\\]'); + const regexS = '[\\?&]' + name + '=([^&#]*)'; + const regex = new RegExp(regexS, 'i'); + + const results = regex.exec(url); + if (results == null) { + return ''; + } else { + return decodeURIComponent(results[1].replace(/\+/g, ' ')); + } +} + +function isAutomaticPlayer(player) { + if (player.isLocalPlayer) { + return true; + } + + return false; +} + +function getAutomaticPlayers(instance, forceLocalPlayer) { + if (!forceLocalPlayer) { + const player = instance._currentPlayer; + if (player && !isAutomaticPlayer(player)) { + return [player]; + } + } + + return instance.getPlayers().filter(isAutomaticPlayer); +} + +function isServerItem(item) { + if (!item.Id) { + return false; + } + return true; +} + +function enableIntros(item) { + if (item.MediaType !== 'Video') { + return false; + } + if (item.Type === 'TvChannel') { + return false; + } + // disable for in-progress recordings + if (item.Status === 'InProgress') { + return false; + } + + return isServerItem(item); +} + +function getIntros(firstItem, apiClient, options) { + if (options.startPositionTicks || options.startIndex || options.fullscreen === false || !enableIntros(firstItem) || !userSettings.enableCinemaMode()) { + return Promise.resolve({ + Items: [] }); } - function getAudioStreamUrlFromDeviceProfile(item, deviceProfile, maxBitrate, apiClient, startPosition) { + return apiClient.getIntros(firstItem.Id).then(function (result) { + return result; + }, function (err) { + return Promise.resolve({ + Items: [] + }); + }); +} - var transcodingProfile = deviceProfile.TranscodingProfiles.filter(function (p) { - return p.Type === 'Audio' && p.Context === 'Streaming'; +function getAudioMaxValues(deviceProfile) { + // TODO - this could vary per codec and should be done on the server using the entire profile + let maxAudioSampleRate = null; + let maxAudioBitDepth = null; + let maxAudioBitrate = null; + + deviceProfile.CodecProfiles.map(function (codecProfile) { + if (codecProfile.Type === 'Audio') { + (codecProfile.Conditions || []).map(function (condition) { + if (condition.Condition === 'LessThanEqual' && condition.Property === 'AudioBitDepth') { + return maxAudioBitDepth = condition.Value; + } else if (condition.Condition === 'LessThanEqual' && condition.Property === 'AudioSampleRate') { + return maxAudioSampleRate = condition.Value; + } else if (condition.Condition === 'LessThanEqual' && condition.Property === 'AudioBitrate') { + return maxAudioBitrate = condition.Value; + } + }); + } + }); + + return { + maxAudioSampleRate: maxAudioSampleRate, + maxAudioBitDepth: maxAudioBitDepth, + maxAudioBitrate: maxAudioBitrate + }; +} + +let startingPlaySession = new Date().getTime(); +function getAudioStreamUrl(item, transcodingProfile, directPlayContainers, maxBitrate, apiClient, maxAudioSampleRate, maxAudioBitDepth, maxAudioBitrate, startPosition) { + const url = 'Audio/' + item.Id + '/universal'; + + startingPlaySession++; + return apiClient.getUrl(url, { + UserId: apiClient.getCurrentUserId(), + DeviceId: apiClient.deviceId(), + MaxStreamingBitrate: maxAudioBitrate || maxBitrate, + Container: directPlayContainers, + TranscodingContainer: transcodingProfile.Container || null, + TranscodingProtocol: transcodingProfile.Protocol || null, + AudioCodec: transcodingProfile.AudioCodec, + MaxAudioSampleRate: maxAudioSampleRate, + MaxAudioBitDepth: maxAudioBitDepth, + api_key: apiClient.accessToken(), + PlaySessionId: startingPlaySession, + StartTimeTicks: startPosition || 0, + EnableRedirection: true, + EnableRemoteMedia: appHost.supports('remoteaudio') + }); +} + +function getAudioStreamUrlFromDeviceProfile(item, deviceProfile, maxBitrate, apiClient, startPosition) { + const transcodingProfile = deviceProfile.TranscodingProfiles.filter(function (p) { + return p.Type === 'Audio' && p.Context === 'Streaming'; + })[0]; + + let directPlayContainers = ''; + + deviceProfile.DirectPlayProfiles.map(function (p) { + if (p.Type === 'Audio') { + if (directPlayContainers) { + directPlayContainers += ',' + p.Container; + } else { + directPlayContainers = p.Container; + } + + if (p.AudioCodec) { + directPlayContainers += '|' + p.AudioCodec; + } + } + }); + + const maxValues = getAudioMaxValues(deviceProfile); + + return getAudioStreamUrl(item, transcodingProfile, directPlayContainers, maxBitrate, apiClient, maxValues.maxAudioSampleRate, maxValues.maxAudioBitDepth, maxValues.maxAudioBitrate, startPosition); +} + +function getStreamUrls(items, deviceProfile, maxBitrate, apiClient, startPosition) { + const audioTranscodingProfile = deviceProfile.TranscodingProfiles.filter(function (p) { + return p.Type === 'Audio' && p.Context === 'Streaming'; + })[0]; + + let audioDirectPlayContainers = ''; + + deviceProfile.DirectPlayProfiles.map(function (p) { + if (p.Type === 'Audio') { + if (audioDirectPlayContainers) { + audioDirectPlayContainers += ',' + p.Container; + } else { + audioDirectPlayContainers = p.Container; + } + + if (p.AudioCodec) { + audioDirectPlayContainers += '|' + p.AudioCodec; + } + } + }); + + const maxValues = getAudioMaxValues(deviceProfile); + + const streamUrls = []; + + for (let i = 0, length = items.length; i < length; i++) { + const item = items[i]; + let streamUrl; + + if (item.MediaType === 'Audio' && !itemHelper.isLocalItem(item)) { + streamUrl = getAudioStreamUrl(item, audioTranscodingProfile, audioDirectPlayContainers, maxBitrate, apiClient, maxValues.maxAudioSampleRate, maxValues.maxAudioBitDepth, maxValues.maxAudioBitrate, startPosition); + } + + streamUrls.push(streamUrl || ''); + + if (i === 0) { + startPosition = 0; + } + } + + return Promise.resolve(streamUrls); +} + +function setStreamUrls(items, deviceProfile, maxBitrate, apiClient, startPosition) { + return getStreamUrls(items, deviceProfile, maxBitrate, apiClient, startPosition).then(function (streamUrls) { + for (let i = 0, length = items.length; i < length; i++) { + const item = items[i]; + const streamUrl = streamUrls[i]; + + if (streamUrl) { + item.PresetMediaSource = { + StreamUrl: streamUrl, + Id: item.Id, + MediaStreams: [], + RunTimeTicks: item.RunTimeTicks + }; + } + } + }); +} + +function getPlaybackInfo(player, + apiClient, + item, + deviceProfile, + maxBitrate, + startPosition, + isPlayback, + mediaSourceId, + audioStreamIndex, + subtitleStreamIndex, + liveStreamId, + enableDirectPlay, + enableDirectStream, + allowVideoStreamCopy, + allowAudioStreamCopy) { + if (!itemHelper.isLocalItem(item) && item.MediaType === 'Audio') { + return Promise.resolve({ + MediaSources: [ + { + StreamUrl: getAudioStreamUrlFromDeviceProfile(item, deviceProfile, maxBitrate, apiClient, startPosition), + Id: item.Id, + MediaStreams: [], + RunTimeTicks: item.RunTimeTicks + }] + }); + } + + if (item.PresetMediaSource) { + return Promise.resolve({ + MediaSources: [item.PresetMediaSource] + }); + } + + const itemId = item.Id; + + const query = { + UserId: apiClient.getCurrentUserId(), + StartTimeTicks: startPosition || 0 + }; + + if (isPlayback) { + query.IsPlayback = true; + query.AutoOpenLiveStream = true; + } else { + query.IsPlayback = false; + query.AutoOpenLiveStream = false; + } + + if (audioStreamIndex != null) { + query.AudioStreamIndex = audioStreamIndex; + } + if (subtitleStreamIndex != null) { + query.SubtitleStreamIndex = subtitleStreamIndex; + } + if (enableDirectPlay != null) { + query.EnableDirectPlay = enableDirectPlay; + } + + if (enableDirectStream != null) { + query.EnableDirectStream = enableDirectStream; + } + if (allowVideoStreamCopy != null) { + query.AllowVideoStreamCopy = allowVideoStreamCopy; + } + if (allowAudioStreamCopy != null) { + query.AllowAudioStreamCopy = allowAudioStreamCopy; + } + if (mediaSourceId) { + query.MediaSourceId = mediaSourceId; + } + if (liveStreamId) { + query.LiveStreamId = liveStreamId; + } + if (maxBitrate) { + query.MaxStreamingBitrate = maxBitrate; + } + if (player.enableMediaProbe && !player.enableMediaProbe(item)) { + query.EnableMediaProbe = false; + } + + // lastly, enforce player overrides for special situations + if (query.EnableDirectStream !== false) { + if (player.supportsPlayMethod && !player.supportsPlayMethod('DirectStream', item)) { + query.EnableDirectStream = false; + } + } + + if (player.getDirectPlayProtocols) { + query.DirectPlayProtocols = player.getDirectPlayProtocols(); + } + + return apiClient.getPlaybackInfo(itemId, query, deviceProfile); +} + +function getOptimalMediaSource(apiClient, item, versions) { + const promises = versions.map(function (v) { + return supportsDirectPlay(apiClient, item, v); + }); + + if (!promises.length) { + return Promise.reject(); + } + + return Promise.all(promises).then(function (results) { + for (let i = 0, length = versions.length; i < length; i++) { + versions[i].enableDirectPlay = results[i] || false; + } + let optimalVersion = versions.filter(function (v) { + return v.enableDirectPlay; })[0]; - var directPlayContainers = ''; + if (!optimalVersion) { + optimalVersion = versions.filter(function (v) { + return v.SupportsDirectStream; + })[0]; + } - deviceProfile.DirectPlayProfiles.map(function (p) { - - if (p.Type === 'Audio') { - if (directPlayContainers) { - directPlayContainers += ',' + p.Container; - } else { - directPlayContainers = p.Container; - } - - if (p.AudioCodec) { - directPlayContainers += '|' + p.AudioCodec; - } - } - - }); - - var maxValues = getAudioMaxValues(deviceProfile); - - return getAudioStreamUrl(item, transcodingProfile, directPlayContainers, maxBitrate, apiClient, maxValues.maxAudioSampleRate, maxValues.maxAudioBitDepth, maxValues.maxAudioBitrate, startPosition); - } - - function getStreamUrls(items, deviceProfile, maxBitrate, apiClient, startPosition) { - - var audioTranscodingProfile = deviceProfile.TranscodingProfiles.filter(function (p) { - return p.Type === 'Audio' && p.Context === 'Streaming'; + optimalVersion = optimalVersion || versions.filter(function (s) { + return s.SupportsTranscoding; })[0]; - var audioDirectPlayContainers = ''; + return optimalVersion || versions[0]; + }); +} - deviceProfile.DirectPlayProfiles.map(function (p) { +function getLiveStream(player, apiClient, item, playSessionId, deviceProfile, maxBitrate, startPosition, mediaSource, audioStreamIndex, subtitleStreamIndex) { + const postData = { + DeviceProfile: deviceProfile, + OpenToken: mediaSource.OpenToken + }; - if (p.Type === 'Audio') { - if (audioDirectPlayContainers) { - audioDirectPlayContainers += ',' + p.Container; - } else { - audioDirectPlayContainers = p.Container; - } + const query = { + UserId: apiClient.getCurrentUserId(), + StartTimeTicks: startPosition || 0, + ItemId: item.Id, + PlaySessionId: playSessionId + }; - if (p.AudioCodec) { - audioDirectPlayContainers += '|' + p.AudioCodec; + if (maxBitrate) { + query.MaxStreamingBitrate = maxBitrate; + } + if (audioStreamIndex != null) { + query.AudioStreamIndex = audioStreamIndex; + } + if (subtitleStreamIndex != null) { + query.SubtitleStreamIndex = subtitleStreamIndex; + } + + // lastly, enforce player overrides for special situations + if (query.EnableDirectStream !== false) { + if (player.supportsPlayMethod && !player.supportsPlayMethod('DirectStream', item)) { + query.EnableDirectStream = false; + } + } + + return apiClient.ajax({ + url: apiClient.getUrl('LiveStreams/Open', query), + type: 'POST', + data: JSON.stringify(postData), + contentType: 'application/json', + dataType: 'json' + + }); +} + +function isHostReachable(mediaSource, apiClient) { + if (mediaSource.IsRemote) { + return Promise.resolve(true); + } + + return apiClient.getEndpointInfo().then(function (endpointInfo) { + if (endpointInfo.IsInNetwork) { + if (!endpointInfo.IsLocal) { + const path = (mediaSource.Path || '').toLowerCase(); + if (path.indexOf('localhost') !== -1 || path.indexOf('127.0.0.1') !== -1) { + // This will only work if the app is on the same machine as the server + return Promise.resolve(false); } } - }); - var maxValues = getAudioMaxValues(deviceProfile); - - var streamUrls = []; - - for (var i = 0, length = items.length; i < length; i++) { - - var item = items[i]; - var streamUrl; - - if (item.MediaType === 'Audio' && !itemHelper.isLocalItem(item)) { - streamUrl = getAudioStreamUrl(item, audioTranscodingProfile, audioDirectPlayContainers, maxBitrate, apiClient, maxValues.maxAudioSampleRate, maxValues.maxAudioBitDepth, maxValues.maxAudioBitrate, startPosition); - } - - streamUrls.push(streamUrl || ''); - - if (i === 0) { - startPosition = 0; - } - } - - return Promise.resolve(streamUrls); - } - - function setStreamUrls(items, deviceProfile, maxBitrate, apiClient, startPosition) { - - return getStreamUrls(items, deviceProfile, maxBitrate, apiClient, startPosition).then(function (streamUrls) { - - for (var i = 0, length = items.length; i < length; i++) { - - var item = items[i]; - var streamUrl = streamUrls[i]; - - if (streamUrl) { - item.PresetMediaSource = { - StreamUrl: streamUrl, - Id: item.Id, - MediaStreams: [], - RunTimeTicks: item.RunTimeTicks - }; - } - } - }); - } - - function getPlaybackInfo(player, - apiClient, - item, - deviceProfile, - maxBitrate, - startPosition, - isPlayback, - mediaSourceId, - audioStreamIndex, - subtitleStreamIndex, - liveStreamId, - enableDirectPlay, - enableDirectStream, - allowVideoStreamCopy, - allowAudioStreamCopy) { - - if (!itemHelper.isLocalItem(item) && item.MediaType === 'Audio') { - - return Promise.resolve({ - MediaSources: [ - { - StreamUrl: getAudioStreamUrlFromDeviceProfile(item, deviceProfile, maxBitrate, apiClient, startPosition), - Id: item.Id, - MediaStreams: [], - RunTimeTicks: item.RunTimeTicks - }] - }); - } - - if (item.PresetMediaSource) { - return Promise.resolve({ - MediaSources: [item.PresetMediaSource] - }); - } - - var itemId = item.Id; - - var query = { - UserId: apiClient.getCurrentUserId(), - StartTimeTicks: startPosition || 0 - }; - - if (isPlayback) { - query.IsPlayback = true; - query.AutoOpenLiveStream = true; - } else { - query.IsPlayback = false; - query.AutoOpenLiveStream = false; - } - - if (audioStreamIndex != null) { - query.AudioStreamIndex = audioStreamIndex; - } - if (subtitleStreamIndex != null) { - query.SubtitleStreamIndex = subtitleStreamIndex; - } - if (enableDirectPlay != null) { - query.EnableDirectPlay = enableDirectPlay; - } - - if (enableDirectStream != null) { - query.EnableDirectStream = enableDirectStream; - } - if (allowVideoStreamCopy != null) { - query.AllowVideoStreamCopy = allowVideoStreamCopy; - } - if (allowAudioStreamCopy != null) { - query.AllowAudioStreamCopy = allowAudioStreamCopy; - } - if (mediaSourceId) { - query.MediaSourceId = mediaSourceId; - } - if (liveStreamId) { - query.LiveStreamId = liveStreamId; - } - if (maxBitrate) { - query.MaxStreamingBitrate = maxBitrate; - } - if (player.enableMediaProbe && !player.enableMediaProbe(item)) { - query.EnableMediaProbe = false; - } - - // lastly, enforce player overrides for special situations - if (query.EnableDirectStream !== false) { - if (player.supportsPlayMethod && !player.supportsPlayMethod('DirectStream', item)) { - query.EnableDirectStream = false; - } - } - - if (player.getDirectPlayProtocols) { - query.DirectPlayProtocols = player.getDirectPlayProtocols(); - } - - return apiClient.getPlaybackInfo(itemId, query, deviceProfile); - } - - function getOptimalMediaSource(apiClient, item, versions) { - - var promises = versions.map(function (v) { - return supportsDirectPlay(apiClient, item, v); - }); - - if (!promises.length) { - return Promise.reject(); - } - - return Promise.all(promises).then(function (results) { - - for (var i = 0, length = versions.length; i < length; i++) { - versions[i].enableDirectPlay = results[i] || false; - } - var optimalVersion = versions.filter(function (v) { - - return v.enableDirectPlay; - - })[0]; - - if (!optimalVersion) { - optimalVersion = versions.filter(function (v) { - - return v.SupportsDirectStream; - - })[0]; - } - - optimalVersion = optimalVersion || versions.filter(function (s) { - return s.SupportsTranscoding; - })[0]; - - return optimalVersion || versions[0]; - }); - } - - function getLiveStream(player, apiClient, item, playSessionId, deviceProfile, maxBitrate, startPosition, mediaSource, audioStreamIndex, subtitleStreamIndex) { - - var postData = { - DeviceProfile: deviceProfile, - OpenToken: mediaSource.OpenToken - }; - - var query = { - UserId: apiClient.getCurrentUserId(), - StartTimeTicks: startPosition || 0, - ItemId: item.Id, - PlaySessionId: playSessionId - }; - - if (maxBitrate) { - query.MaxStreamingBitrate = maxBitrate; - } - if (audioStreamIndex != null) { - query.AudioStreamIndex = audioStreamIndex; - } - if (subtitleStreamIndex != null) { - query.SubtitleStreamIndex = subtitleStreamIndex; - } - - // lastly, enforce player overrides for special situations - if (query.EnableDirectStream !== false) { - if (player.supportsPlayMethod && !player.supportsPlayMethod('DirectStream', item)) { - query.EnableDirectStream = false; - } - } - - return apiClient.ajax({ - url: apiClient.getUrl('LiveStreams/Open', query), - type: 'POST', - data: JSON.stringify(postData), - contentType: 'application/json', - dataType: 'json' - - }); - } - - function isHostReachable(mediaSource, apiClient) { - - if (mediaSource.IsRemote) { return Promise.resolve(true); } - return apiClient.getEndpointInfo().then(function (endpointInfo) { + // media source is in network, but connection is out of network + return Promise.resolve(false); + }); +} - if (endpointInfo.IsInNetwork) { +function supportsDirectPlay(apiClient, item, mediaSource) { + // folder rip hacks due to not yet being supported by the stream building engine + const isFolderRip = mediaSource.VideoType === 'BluRay' || mediaSource.VideoType === 'Dvd' || mediaSource.VideoType === 'HdDvd'; - if (!endpointInfo.IsLocal) { - var path = (mediaSource.Path || '').toLowerCase(); - if (path.indexOf('localhost') !== -1 || path.indexOf('127.0.0.1') !== -1) { - // This will only work if the app is on the same machine as the server - return Promise.resolve(false); - } - } - - return Promise.resolve(true); - } - - // media source is in network, but connection is out of network + if (mediaSource.SupportsDirectPlay || isFolderRip) { + if (mediaSource.IsRemote && !appHost.supports('remotevideo')) { return Promise.resolve(false); - }); - } + } - function supportsDirectPlay(apiClient, item, mediaSource) { - - // folder rip hacks due to not yet being supported by the stream building engine - var isFolderRip = mediaSource.VideoType === 'BluRay' || mediaSource.VideoType === 'Dvd' || mediaSource.VideoType === 'HdDvd'; - - if (mediaSource.SupportsDirectPlay || isFolderRip) { - - if (mediaSource.IsRemote && !apphost.supports('remotevideo')) { - return Promise.resolve(false); + if (mediaSource.Protocol === 'Http' && !mediaSource.RequiredHttpHeaders.length) { + // If this is the only way it can be played, then allow it + if (!mediaSource.SupportsDirectStream && !mediaSource.SupportsTranscoding) { + return Promise.resolve(true); + } else { + return isHostReachable(mediaSource, apiClient); } + } else if (mediaSource.Protocol === 'File') { + return new Promise(function (resolve, reject) { + // Determine if the file can be accessed directly + import('filesystem').then((filesystem) => { + const method = isFolderRip ? + 'directoryExists' : + 'fileExists'; - if (mediaSource.Protocol === 'Http' && !mediaSource.RequiredHttpHeaders.length) { - - // If this is the only way it can be played, then allow it - if (!mediaSource.SupportsDirectStream && !mediaSource.SupportsTranscoding) { - return Promise.resolve(true); - } else { - return isHostReachable(mediaSource, apiClient); - } - } else if (mediaSource.Protocol === 'File') { - - return new Promise(function (resolve, reject) { - - // Determine if the file can be accessed directly - require(['filesystem'], function (filesystem) { - - var method = isFolderRip ? - 'directoryExists' : - 'fileExists'; - - filesystem[method](mediaSource.Path).then(function () { - resolve(true); - }, function () { - resolve(false); - }); - + filesystem[method](mediaSource.Path).then(function () { + resolve(true); + }, function () { + resolve(false); }); }); - } - } - - return Promise.resolve(false); - } - - function validatePlaybackInfoResult(instance, result) { - - if (result.ErrorCode) { - - showPlaybackInfoErrorMessage(instance, result.ErrorCode); - return false; - } - - return true; - } - - function showPlaybackInfoErrorMessage(instance, errorCode, playNextTrack) { - - require(['alert'], function (alert) { - alert.default({ - text: globalize.translate('PlaybackError' + errorCode), - title: globalize.translate('HeaderPlaybackError') - }).then(function () { - - if (playNextTrack) { - instance.nextTrack(); - } }); + } + } + + return Promise.resolve(false); +} + +function validatePlaybackInfoResult(instance, result) { + if (result.ErrorCode) { + showPlaybackInfoErrorMessage(instance, 'PlaybackError' + result.ErrorCode); + return false; + } + + return true; +} + +function showPlaybackInfoErrorMessage(instance, errorCode) { + import('alert').then(({ default: alert }) => { + alert({ + text: globalize.translate(errorCode), + title: globalize.translate('HeaderPlaybackError') }); + }); +} + +function normalizePlayOptions(playOptions) { + playOptions.fullscreen = playOptions.fullscreen !== false; +} + +function truncatePlayOptions(playOptions) { + return { + fullscreen: playOptions.fullscreen, + mediaSourceId: playOptions.mediaSourceId, + audioStreamIndex: playOptions.audioStreamIndex, + subtitleStreamIndex: playOptions.subtitleStreamIndex, + startPositionTicks: playOptions.startPositionTicks + }; +} + +function getNowPlayingItemForReporting(player, item, mediaSource) { + const nowPlayingItem = Object.assign({}, item); + + if (mediaSource) { + nowPlayingItem.RunTimeTicks = mediaSource.RunTimeTicks; + nowPlayingItem.MediaStreams = mediaSource.MediaStreams; + + // not needed + nowPlayingItem.MediaSources = null; } - function normalizePlayOptions(playOptions) { - playOptions.fullscreen = playOptions.fullscreen !== false; + nowPlayingItem.RunTimeTicks = nowPlayingItem.RunTimeTicks || player.duration() * 10000; + + return nowPlayingItem; +} + +function displayPlayerIndividually(player) { + return !player.isLocalPlayer; +} + +function createTarget(instance, player) { + return { + name: player.name, + id: player.id, + playerName: player.name, + playableMediaTypes: ['Audio', 'Video', 'Photo', 'Book'].map(player.canPlayMediaType), + isLocalPlayer: player.isLocalPlayer, + supportedCommands: instance.getSupportedCommands(player) + }; +} + +function getPlayerTargets(player) { + if (player.getTargets) { + return player.getTargets(); } - function truncatePlayOptions(playOptions) { + return Promise.resolve([createTarget(player)]); +} - return { - fullscreen: playOptions.fullscreen, - mediaSourceId: playOptions.mediaSourceId, - audioStreamIndex: playOptions.audioStreamIndex, - subtitleStreamIndex: playOptions.subtitleStreamIndex, - startPositionTicks: playOptions.startPositionTicks - }; - } +function sortPlayerTargets(a, b) { + let aVal = a.isLocalPlayer ? 0 : 1; + let bVal = b.isLocalPlayer ? 0 : 1; - function getNowPlayingItemForReporting(player, item, mediaSource) { + aVal = aVal.toString() + a.name; + bVal = bVal.toString() + b.name; - var nowPlayingItem = Object.assign({}, item); + return aVal.localeCompare(bVal); +} - if (mediaSource) { - nowPlayingItem.RunTimeTicks = mediaSource.RunTimeTicks; - nowPlayingItem.MediaStreams = mediaSource.MediaStreams; +class PlaybackManager { + constructor() { + const self = this; - // not needed - nowPlayingItem.MediaSources = null; - } - - nowPlayingItem.RunTimeTicks = nowPlayingItem.RunTimeTicks || player.duration() * 10000; - - return nowPlayingItem; - } - - function displayPlayerIndividually(player) { - - return !player.isLocalPlayer; - } - - function createTarget(instance, player) { - return { - name: player.name, - id: player.id, - playerName: player.name, - playableMediaTypes: ['Audio', 'Video', 'Photo', 'Book'].map(player.canPlayMediaType), - isLocalPlayer: player.isLocalPlayer, - supportedCommands: instance.getSupportedCommands(player) - }; - } - - function getPlayerTargets(player) { - if (player.getTargets) { - return player.getTargets(); - } - - return Promise.resolve([createTarget(player)]); - } - - function sortPlayerTargets(a, b) { - - var aVal = a.isLocalPlayer ? 0 : 1; - var bVal = b.isLocalPlayer ? 0 : 1; - - aVal = aVal.toString() + a.name; - bVal = bVal.toString() + b.name; - - return aVal.localeCompare(bVal); - } - - function PlaybackManager() { - - var self = this; - - var players = []; - var currentTargetInfo; - var lastLocalPlayer; - var currentPairingId = null; + const players = []; + let currentTargetInfo; + let currentPairingId = null; this._playNextAfterEnded = true; - var playerStates = {}; + const playerStates = {}; this._playQueueManager = new PlayQueueManager(); self.currentItem = function (player) { - if (!player) { throw new Error('player cannot be null'); } @@ -795,12 +740,11 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return player.currentItem(); } - var data = getPlayerData(player); + const data = getPlayerData(player); return data.streamInfo ? data.streamInfo.item : null; }; self.currentMediaSource = function (player) { - if (!player) { throw new Error('player cannot be null'); } @@ -809,12 +753,11 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return player.currentMediaSource(); } - var data = getPlayerData(player); + const data = getPlayerData(player); return data.streamInfo ? data.streamInfo.mediaSource : null; }; self.playMethod = function (player) { - if (!player) { throw new Error('player cannot be null'); } @@ -823,12 +766,11 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return player.playMethod(); } - var data = getPlayerData(player); + const data = getPlayerData(player); return data.streamInfo ? data.streamInfo.playMethod : null; }; self.playSessionId = function (player) { - if (!player) { throw new Error('player cannot be null'); } @@ -837,22 +779,20 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return player.playSessionId(); } - var data = getPlayerData(player); + const data = getPlayerData(player); return data.streamInfo ? data.streamInfo.playSessionId : null; }; self.getPlayerInfo = function () { - - var player = self._currentPlayer; + const player = self._currentPlayer; if (!player) { return null; } - var target = currentTargetInfo || {}; + const target = currentTargetInfo || {}; return { - name: player.name, isLocalPlayer: player.isLocalPlayer, id: target.id, @@ -863,7 +803,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla }; self.setActivePlayer = function (player, targetInfo) { - if (player === 'localplayer' || player.name === 'localplayer') { if (self._currentPlayer && self._currentPlayer.isLocalPlayer) { return; @@ -886,7 +825,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla }; self.trySetActivePlayer = function (player, targetInfo) { - if (player === 'localplayer' || player.name === 'localplayer') { if (self._currentPlayer && self._currentPlayer.isLocalPlayer) { return; @@ -910,7 +848,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla currentPairingId = targetInfo.id; - var promise = player.tryPair ? + const promise = player.tryPair ? player.tryPair(targetInfo) : Promise.resolve(); @@ -928,14 +866,11 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla }; self.getTargets = function () { - - var promises = players.filter(displayPlayerIndividually).map(getPlayerTargets); + const promises = players.filter(displayPlayerIndividually).map(getPlayerTargets); return Promise.all(promises).then(function (responses) { - - return connectionManager.currentApiClient().getCurrentUser().then(function (user) { - - var targets = []; + return window.connectionManager.currentApiClient().getCurrentUser().then(function (user) { + const targets = []; targets.push({ name: globalize.translate('HeaderMyDevice'), @@ -949,30 +884,25 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla user: user }); - for (var i = 0; i < responses.length; i++) { - - var subTargets = responses[i]; - - for (var j = 0; j < subTargets.length; j++) { + for (let i = 0; i < responses.length; i++) { + const subTargets = responses[i]; + for (let j = 0; j < subTargets.length; j++) { targets.push(subTargets[j]); } } - targets = targets.sort(sortPlayerTargets); - - return targets; + return targets.sort(sortPlayerTargets); }); }); }; function getCurrentSubtitleStream(player) { - if (!player) { throw new Error('player cannot be null'); } - var index = getPlayerData(player).subtitleStreamIndex; + const index = getPlayerData(player).subtitleStreamIndex; if (index == null || index === -1) { return null; @@ -988,10 +918,8 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } self.getPlaylist = function (player) { - player = player || self._currentPlayer; if (player && !enableLocalPlaylistManagement(player)) { - if (player.getPlaylistSync) { return Promise.resolve(player.getPlaylistSync()); } @@ -1003,7 +931,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla }; function removeCurrentPlayer(player) { - var previousPlayer = self._currentPlayer; + const previousPlayer = self._currentPlayer; if (!previousPlayer || player.id === previousPlayer.id) { setCurrentPlayerInternal(null); @@ -1011,9 +939,8 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } function setCurrentPlayerInternal(player, targetInfo) { - - var previousPlayer = self._currentPlayer; - var previousTargetInfo = currentTargetInfo; + const previousPlayer = self._currentPlayer; + const previousTargetInfo = currentTargetInfo; if (player && !targetInfo && player.isLocalPlayer) { targetInfo = createTarget(self, player); @@ -1031,10 +958,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla console.debug('Active player: ' + JSON.stringify(targetInfo)); } - if (player && player.isLocalPlayer) { - lastLocalPlayer = player; - } - if (previousPlayer) { self.endPlayerUpdates(previousPlayer); } @@ -1047,7 +970,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } self.isPlaying = function (player) { - player = player || self._currentPlayer; if (player) { @@ -1069,7 +991,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } if (self.isPlaying(player)) { - var playerData = getPlayerData(player); + const playerData = getPlayerData(player); return playerData.streamInfo.mediaType === mediaType; } @@ -1078,7 +1000,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla }; self.isPlayingLocally = function (mediaTypes, player) { - player = player || self._currentPlayer; if (!player || !player.isLocalPlayer) { @@ -1086,9 +1007,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } return mediaTypes.filter(function (mediaType) { - return self.isPlayingMediaType(mediaType, player); - }).length > 0; }; @@ -1101,7 +1020,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla }; self.getPlayers = function () { - return players; }; @@ -1112,7 +1030,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } self.canPlay = function (item) { - var itemType = item.Type; + const itemType = item.Type; if (itemType === 'PhotoAlbum' || itemType === 'MusicGenre' || itemType === 'Season' || itemType === 'Series' || itemType === 'BoxSet' || itemType === 'MusicAlbum' || itemType === 'MusicArtist' || itemType === 'Playlist') { return true; @@ -1139,16 +1057,15 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla }; self.toggleAspectRatio = function (player) { - player = player || self._currentPlayer; if (player) { - var current = self.getAspectRatio(player); + const current = self.getAspectRatio(player); - var supported = self.getSupportedAspectRatios(player); + const supported = self.getSupportedAspectRatios(player); - var index = -1; - for (var i = 0, length = supported.length; i < length; i++) { + let index = -1; + for (let i = 0, length = supported.length; i < length; i++) { if (supported[i].id === current) { index = i; break; @@ -1165,17 +1082,14 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla }; self.setAspectRatio = function (val, player) { - player = player || self._currentPlayer; if (player && player.setAspectRatio) { - player.setAspectRatio(val); } }; self.getSupportedAspectRatios = function (player) { - player = player || self._currentPlayer; if (player && player.getSupportedAspectRatios) { @@ -1186,7 +1100,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla }; self.getAspectRatio = function (player) { - player = player || self._currentPlayer; if (player && player.getAspectRatio) { @@ -1194,24 +1107,67 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } }; - var brightnessOsdLoaded; - self.setBrightness = function (val, player) { + self.increasePlaybackRate = function (player) { + player = player || self._currentPlayer; + if (player) { + let current = self.getPlaybackRate(player); + let supported = self.getSupportedPlaybackRates(player); + let index = -1; + for (let i = 0, length = supported.length; i < length; i++) { + if (supported[i].id === current) { + index = i; + break; + } + } + + index = Math.min(index + 1, supported.length - 1); + self.setPlaybackRate(supported[index].id, player); + } + }; + + self.decreasePlaybackRate = function (player) { + player = player || self._currentPlayer; + if (player) { + let current = self.getPlaybackRate(player); + let supported = self.getSupportedPlaybackRates(player); + + let index = -1; + for (let i = 0, length = supported.length; i < length; i++) { + if (supported[i].id === current) { + index = i; + break; + } + } + + index = Math.max(index - 1, 0); + self.setPlaybackRate(supported[index].id, player); + } + }; + + self.getSupportedPlaybackRates = function (player) { + player = player || self._currentPlayer; + if (player && player.getSupportedPlaybackRates) { + return player.getSupportedPlaybackRates(); + } + return []; + }; + + let brightnessOsdLoaded; + self.setBrightness = function (val, player) { player = player || self._currentPlayer; if (player) { - if (!brightnessOsdLoaded) { brightnessOsdLoaded = true; // TODO: Have this trigger an event instead to get the osd out of here - require(['brightnessOsd']); + import('brightnessOsd').then(); } player.setBrightness(val); } }; self.getBrightness = function (player) { - player = player || self._currentPlayer; if (player) { @@ -1220,7 +1176,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla }; self.setVolume = function (val, player) { - player = player || self._currentPlayer; if (player) { @@ -1229,7 +1184,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla }; self.getVolume = function (player) { - player = player || self._currentPlayer; if (player) { @@ -1238,7 +1192,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla }; self.volumeUp = function (player) { - player = player || self._currentPlayer; if (player) { @@ -1247,7 +1200,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla }; self.volumeDown = function (player) { - player = player || self._currentPlayer; if (player) { @@ -1256,7 +1208,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla }; self.changeAudioStream = function (player) { - player = player || self._currentPlayer; if (player && !enableLocalPlaylistManagement(player)) { return player.changeAudioStream(); @@ -1266,11 +1217,9 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return; } - var currentMediaSource = self.currentMediaSource(player); - var mediaStreams = []; - var i; - var length; - for (i = 0, length = currentMediaSource.MediaStreams.length; i < length; i++) { + const currentMediaSource = self.currentMediaSource(player); + const mediaStreams = []; + for (let i = 0, length = currentMediaSource.MediaStreams.length; i < length; i++) { if (currentMediaSource.MediaStreams[i].Type === 'Audio') { mediaStreams.push(currentMediaSource.MediaStreams[i]); } @@ -1281,16 +1230,16 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return; } - var currentStreamIndex = self.getAudioStreamIndex(player); - var indexInList = -1; - for (i = 0, length = mediaStreams.length; i < length; i++) { + const currentStreamIndex = self.getAudioStreamIndex(player); + let indexInList = -1; + for (let i = 0, length = mediaStreams.length; i < length; i++) { if (mediaStreams[i].Index === currentStreamIndex) { indexInList = i; break; } } - var nextIndex = indexInList + 1; + let nextIndex = indexInList + 1; if (nextIndex >= mediaStreams.length) { nextIndex = 0; } @@ -1301,7 +1250,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla }; self.changeSubtitleStream = function (player) { - player = player || self._currentPlayer; if (player && !enableLocalPlaylistManagement(player)) { return player.changeSubtitleStream(); @@ -1311,11 +1259,9 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return; } - var currentMediaSource = self.currentMediaSource(player); - var mediaStreams = []; - var i; - var length; - for (i = 0, length = currentMediaSource.MediaStreams.length; i < length; i++) { + const currentMediaSource = self.currentMediaSource(player); + const mediaStreams = []; + for (let i = 0, length = currentMediaSource.MediaStreams.length; i < length; i++) { if (currentMediaSource.MediaStreams[i].Type === 'Subtitle') { mediaStreams.push(currentMediaSource.MediaStreams[i]); } @@ -1326,16 +1272,16 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return; } - var currentStreamIndex = self.getSubtitleStreamIndex(player); - var indexInList = -1; - for (i = 0, length = mediaStreams.length; i < length; i++) { + const currentStreamIndex = self.getSubtitleStreamIndex(player); + let indexInList = -1; + for (let i = 0, length = mediaStreams.length; i < length; i++) { if (mediaStreams[i].Index === currentStreamIndex) { indexInList = i; break; } } - var nextIndex = indexInList + 1; + let nextIndex = indexInList + 1; if (nextIndex >= mediaStreams.length) { nextIndex = -1; } @@ -1346,7 +1292,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla }; self.getAudioStreamIndex = function (player) { - player = player || self._currentPlayer; if (player && !enableLocalPlaylistManagement(player)) { return player.getAudioStreamIndex(); @@ -1356,13 +1301,10 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla }; function isAudioStreamSupported(mediaSource, index, deviceProfile) { + let mediaStream; + const mediaStreams = mediaSource.MediaStreams; - var mediaStream; - var i; - var length; - var mediaStreams = mediaSource.MediaStreams; - - for (i = 0, length = mediaStreams.length; i < length; i++) { + for (let i = 0, length = mediaStreams.length; i < length; i++) { if (mediaStreams[i].Type === 'Audio' && mediaStreams[i].Index === index) { mediaStream = mediaStreams[i]; break; @@ -1373,18 +1315,16 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return false; } - var codec = (mediaStream.Codec || '').toLowerCase(); + const codec = (mediaStream.Codec || '').toLowerCase(); if (!codec) { return false; } - var profiles = deviceProfile.DirectPlayProfiles || []; + const profiles = deviceProfile.DirectPlayProfiles || []; return profiles.filter(function (p) { - if (p.Type === 'Video') { - if (!p.AudioCodec) { return true; } @@ -1398,27 +1338,21 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } return false; - }).length > 0; } self.setAudioStreamIndex = function (index, player) { - player = player || self._currentPlayer; if (player && !enableLocalPlaylistManagement(player)) { return player.setAudioStreamIndex(index); } if (self.playMethod(player) === 'Transcode' || !player.canSetAudioStreamIndex()) { - changeStream(player, getCurrentTicks(player), { AudioStreamIndex: index }); getPlayerData(player).audioStreamIndex = index; - } else { - // See if the player supports the track without transcoding player.getDeviceProfile(self.currentItem(player)).then(function (profile) { - if (isAudioStreamSupported(self.currentMediaSource(player), index, profile)) { player.setAudioStreamIndex(index); getPlayerData(player).audioStreamIndex = index; @@ -1431,69 +1365,64 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla }; function getSavedMaxStreamingBitrate(apiClient, mediaType) { - if (!apiClient) { // This should hopefully never happen - apiClient = connectionManager.currentApiClient(); + apiClient = window.connectionManager.currentApiClient(); } - var endpointInfo = apiClient.getSavedEndpointInfo() || {}; + const endpointInfo = apiClient.getSavedEndpointInfo() || {}; return appSettings.maxStreamingBitrate(endpointInfo.IsInNetwork, mediaType); } self.getMaxStreamingBitrate = function (player) { - player = player || self._currentPlayer; if (player && player.getMaxStreamingBitrate) { return player.getMaxStreamingBitrate(); } - var playerData = getPlayerData(player); + const playerData = getPlayerData(player); if (playerData.maxStreamingBitrate) { return playerData.maxStreamingBitrate; } - var mediaType = playerData.streamInfo ? playerData.streamInfo.mediaType : null; - var currentItem = self.currentItem(player); + const mediaType = playerData.streamInfo ? playerData.streamInfo.mediaType : null; + const currentItem = self.currentItem(player); - var apiClient = currentItem ? connectionManager.getApiClient(currentItem.ServerId) : connectionManager.currentApiClient(); + const apiClient = currentItem ? window.connectionManager.getApiClient(currentItem.ServerId) : window.connectionManager.currentApiClient(); return getSavedMaxStreamingBitrate(apiClient, mediaType); }; self.enableAutomaticBitrateDetection = function (player) { - player = player || self._currentPlayer; if (player && player.enableAutomaticBitrateDetection) { return player.enableAutomaticBitrateDetection(); } - var playerData = getPlayerData(player); - var mediaType = playerData.streamInfo ? playerData.streamInfo.mediaType : null; - var currentItem = self.currentItem(player); + const playerData = getPlayerData(player); + const mediaType = playerData.streamInfo ? playerData.streamInfo.mediaType : null; + const currentItem = self.currentItem(player); - var apiClient = currentItem ? connectionManager.getApiClient(currentItem.ServerId) : connectionManager.currentApiClient(); - var endpointInfo = apiClient.getSavedEndpointInfo() || {}; + const apiClient = currentItem ? window.connectionManager.getApiClient(currentItem.ServerId) : window.connectionManager.currentApiClient(); + const endpointInfo = apiClient.getSavedEndpointInfo() || {}; return appSettings.enableAutomaticBitrateDetection(endpointInfo.IsInNetwork, mediaType); }; self.setMaxStreamingBitrate = function (options, player) { - player = player || self._currentPlayer; if (player && player.setMaxStreamingBitrate) { return player.setMaxStreamingBitrate(options); } - var apiClient = connectionManager.getApiClient(self.currentItem(player).ServerId); + const apiClient = window.connectionManager.getApiClient(self.currentItem(player).ServerId); apiClient.getEndpointInfo().then(function (endpointInfo) { + const playerData = getPlayerData(player); + const mediaType = playerData.streamInfo ? playerData.streamInfo.mediaType : null; - var playerData = getPlayerData(player); - var mediaType = playerData.streamInfo ? playerData.streamInfo.mediaType : null; - - var promise; + let promise; if (options.enableAutomaticBitrateDetection) { appSettings.enableAutomaticBitrateDetection(endpointInfo.IsInNetwork, mediaType, true); promise = apiClient.detectBitrate(true); @@ -1503,7 +1432,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } promise.then(function (bitrate) { - appSettings.maxStreamingBitrate(endpointInfo.IsInNetwork, mediaType, bitrate); changeStream(player, getCurrentTicks(player), { @@ -1514,24 +1442,37 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla }; self.isFullscreen = function (player) { - player = player || self._currentPlayer; if (!player.isLocalPlayer || player.isFullscreen) { return player.isFullscreen(); } + if (!screenfull.isEnabled) { + // iOS Safari + return document.webkitIsFullScreen; + } + return screenfull.isFullscreen; }; self.toggleFullscreen = function (player) { - player = player || self._currentPlayer; - if (!player.isLocalPlayer || player.toggleFulscreen) { - return player.toggleFulscreen(); + if (!player.isLocalPlayer || player.toggleFullscreen) { + return player.toggleFullscreen(); } if (screenfull.isEnabled) { screenfull.toggle(); + } else { + // iOS Safari + if (document.webkitIsFullScreen && document.webkitCancelFullscreen) { + document.webkitCancelFullscreen(); + } else { + const elem = document.querySelector('video'); + if (elem && elem.webkitEnterFullscreen) { + elem.webkitEnterFullscreen(); + } + } } }; @@ -1546,7 +1487,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla }; self.getSubtitleStreamIndex = function (player) { - player = player || self._currentPlayer; if (player && !enableLocalPlaylistManagement(player)) { @@ -1561,7 +1501,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla }; function getDeliveryMethod(subtitleStream) { - // This will be null for internal subs for local items if (subtitleStream.DeliveryMethod) { return subtitleStream.DeliveryMethod; @@ -1571,44 +1510,38 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } self.setSubtitleStreamIndex = function (index, player) { - player = player || self._currentPlayer; if (player && !enableLocalPlaylistManagement(player)) { return player.setSubtitleStreamIndex(index); } - var currentStream = getCurrentSubtitleStream(player); + const currentStream = getCurrentSubtitleStream(player); - var newStream = getSubtitleStream(player, index); + const newStream = getSubtitleStream(player, index); if (!currentStream && !newStream) { return; } - var selectedTrackElementIndex = -1; + let selectedTrackElementIndex = -1; - var currentPlayMethod = self.playMethod(player); + const currentPlayMethod = self.playMethod(player); if (currentStream && !newStream) { - if (getDeliveryMethod(currentStream) === 'Encode' || (getDeliveryMethod(currentStream) === 'Embed' && currentPlayMethod === 'Transcode')) { - // Need to change the transcoded stream to remove subs changeStream(player, getCurrentTicks(player), { SubtitleStreamIndex: -1 }); } } else if (!currentStream && newStream) { - if (getDeliveryMethod(newStream) === 'External') { selectedTrackElementIndex = index; } else if (getDeliveryMethod(newStream) === 'Embed' && currentPlayMethod !== 'Transcode') { selectedTrackElementIndex = index; } else { - // Need to change the transcoded stream to add subs changeStream(player, getCurrentTicks(player), { SubtitleStreamIndex: index }); } } else if (currentStream && newStream) { - // Switching tracks // We can handle this clientside if the new track is external or the new track is embedded and we're not transcoding if (getDeliveryMethod(newStream) === 'External' || (getDeliveryMethod(newStream) === 'Embed' && currentPlayMethod !== 'Transcode')) { @@ -1619,7 +1552,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla changeStream(player, getCurrentTicks(player), { SubtitleStreamIndex: -1 }); } } else { - // Need to change the transcoded stream to add subs changeStream(player, getCurrentTicks(player), { SubtitleStreamIndex: index }); } @@ -1630,30 +1562,30 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla getPlayerData(player).subtitleStreamIndex = index; }; - self.supportSubtitleOffset = function(player) { + self.supportSubtitleOffset = function (player) { player = player || self._currentPlayer; return player && 'setSubtitleOffset' in player; }; - self.enableShowingSubtitleOffset = function(player) { + self.enableShowingSubtitleOffset = function (player) { player = player || self._currentPlayer; player.enableShowingSubtitleOffset(); }; - self.disableShowingSubtitleOffset = function(player) { + self.disableShowingSubtitleOffset = function (player) { player = player || self._currentPlayer; if (player.disableShowingSubtitleOffset) { player.disableShowingSubtitleOffset(); } }; - self.isShowingSubtitleOffsetEnabled = function(player) { + self.isShowingSubtitleOffsetEnabled = function (player) { player = player || self._currentPlayer; return player.isShowingSubtitleOffsetEnabled(); }; - self.isSubtitleStreamExternal = function(index, player) { - var stream = getSubtitleStream(player, index); + self.isSubtitleStreamExternal = function (index, player) { + const stream = getSubtitleStream(player, index); return stream ? getDeliveryMethod(stream) === 'External' : false; }; @@ -1664,61 +1596,48 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } }; - self.getPlayerSubtitleOffset = function(player) { + self.getPlayerSubtitleOffset = function (player) { player = player || self._currentPlayer; if (player.getSubtitleOffset) { return player.getSubtitleOffset(); } }; - self.canHandleOffsetOnCurrentSubtitle = function(player) { - var index = self.getSubtitleStreamIndex(player); + self.canHandleOffsetOnCurrentSubtitle = function (player) { + const index = self.getSubtitleStreamIndex(player); return index !== -1 && self.isSubtitleStreamExternal(index, player); }; self.seek = function (ticks, player) { - ticks = Math.max(0, ticks); player = player || self._currentPlayer; if (player && !enableLocalPlaylistManagement(player)) { - - if (player.isLocalPlayer) { - return player.seek((ticks || 0) / 10000); - } else { - return player.seek(ticks); - } + return player.seek(ticks); } changeStream(player, ticks); }; self.seekRelative = function (offsetTicks, player) { - player = player || self._currentPlayer; if (player && !enableLocalPlaylistManagement(player) && player.seekRelative) { - - if (player.isLocalPlayer) { - return player.seekRelative((ticks || 0) / 10000); - } else { - return player.seekRelative(ticks); - } + return player.seekRelative(ticks); } - var ticks = getCurrentTicks(player) + offsetTicks; + const ticks = getCurrentTicks(player) + offsetTicks; return this.seek(ticks, player); }; // Returns true if the player can seek using native client-side seeking functions function canPlayerSeek(player) { - if (!player) { throw new Error('player cannot be null'); } - var playerData = getPlayerData(player); + const playerData = getPlayerData(player); - var currentSrc = (playerData.streamInfo.url || '').toLowerCase(); + const currentSrc = (playerData.streamInfo.url || '').toLowerCase(); if (currentSrc.indexOf('.m3u8') !== -1) { return true; @@ -1728,7 +1647,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return player.seekable(); } - var isPlayMethodTranscode = self.playMethod(player) === 'Transcode'; + const isPlayMethodTranscode = self.playMethod(player) === 'Transcode'; if (isPlayMethodTranscode) { return false; @@ -1738,54 +1657,47 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } function changeStream(player, ticks, params) { - if (canPlayerSeek(player) && params == null) { - player.currentTime(parseInt(ticks / 10000)); return; } params = params || {}; - var liveStreamId = getPlayerData(player).streamInfo.liveStreamId; - var lastMediaInfoQuery = getPlayerData(player).streamInfo.lastMediaInfoQuery; + const liveStreamId = getPlayerData(player).streamInfo.liveStreamId; + const lastMediaInfoQuery = getPlayerData(player).streamInfo.lastMediaInfoQuery; - var playSessionId = self.playSessionId(player); + const playSessionId = self.playSessionId(player); - var currentItem = self.currentItem(player); + const currentItem = self.currentItem(player); player.getDeviceProfile(currentItem, { - isRetry: params.EnableDirectPlay === false - }).then(function (deviceProfile) { + const audioStreamIndex = params.AudioStreamIndex == null ? getPlayerData(player).audioStreamIndex : params.AudioStreamIndex; + const subtitleStreamIndex = params.SubtitleStreamIndex == null ? getPlayerData(player).subtitleStreamIndex : params.SubtitleStreamIndex; - var audioStreamIndex = params.AudioStreamIndex == null ? getPlayerData(player).audioStreamIndex : params.AudioStreamIndex; - var subtitleStreamIndex = params.SubtitleStreamIndex == null ? getPlayerData(player).subtitleStreamIndex : params.SubtitleStreamIndex; - - var currentMediaSource = self.currentMediaSource(player); - var apiClient = connectionManager.getApiClient(currentItem.ServerId); + let currentMediaSource = self.currentMediaSource(player); + const apiClient = window.connectionManager.getApiClient(currentItem.ServerId); if (ticks) { ticks = parseInt(ticks); } - var maxBitrate = params.MaxStreamingBitrate || self.getMaxStreamingBitrate(player); + const maxBitrate = params.MaxStreamingBitrate || self.getMaxStreamingBitrate(player); - var currentPlayOptions = currentItem.playOptions || getDefaultPlayOptions(); + const currentPlayOptions = currentItem.playOptions || getDefaultPlayOptions(); getPlaybackInfo(player, apiClient, currentItem, deviceProfile, maxBitrate, ticks, true, currentMediaSource.Id, audioStreamIndex, subtitleStreamIndex, liveStreamId, params.EnableDirectPlay, params.EnableDirectStream, params.AllowVideoStreamCopy, params.AllowAudioStreamCopy).then(function (result) { - if (validatePlaybackInfoResult(self, result)) { - currentMediaSource = result.MediaSources[0]; - var streamInfo = createStreamInfo(apiClient, currentItem.MediaType, currentItem, currentMediaSource, ticks); + const streamInfo = createStreamInfo(apiClient, currentItem.MediaType, currentItem, currentMediaSource, ticks); streamInfo.fullscreen = currentPlayOptions.fullscreen; streamInfo.lastMediaInfoQuery = lastMediaInfoQuery; if (!streamInfo.url) { - showPlaybackInfoErrorMessage(self, 'NoCompatibleStream', true); + showPlaybackInfoErrorMessage(self, 'PlaybackErrorNoCompatibleStream'); return; } @@ -1800,33 +1712,26 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } function changeStreamToUrl(apiClient, player, playSessionId, streamInfo, newPositionTicks) { - - var playerData = getPlayerData(player); + const playerData = getPlayerData(player); playerData.isChangingStream = true; if (playerData.streamInfo && playSessionId) { - apiClient.stopActiveEncodings(playSessionId).then(function () { - // Stop the first transcoding afterwards because the player may still send requests to the original url - var afterSetSrc = function () { - + const afterSetSrc = function () { apiClient.stopActiveEncodings(playSessionId); }; setSrcIntoPlayer(apiClient, player, streamInfo).then(afterSetSrc, afterSetSrc); }); - } else { setSrcIntoPlayer(apiClient, player, streamInfo); } } function setSrcIntoPlayer(apiClient, player, streamInfo) { - return player.play(streamInfo).then(function () { - - var playerData = getPlayerData(player); + const playerData = getPlayerData(player); playerData.isChangingStream = false; playerData.streamInfo = streamInfo; @@ -1835,8 +1740,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla sendProgressUpdate(player, 'timeupdate'); }, function (e) { - - var playerData = getPlayerData(player); + const playerData = getPlayerData(player); playerData.isChangingStream = false; onPlaybackError.call(player, e, { @@ -1854,26 +1758,23 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla }); } - var firstItem = items[0]; - var promise; + const firstItem = items[0]; + let promise; - var serverId = firstItem.ServerId; + const serverId = firstItem.ServerId; - var queryOptions = options.queryOptions || {}; + const queryOptions = options.queryOptions || {}; if (firstItem.Type === 'Program') { - promise = getItemsForPlayback(serverId, { Ids: firstItem.ChannelId }); } else if (firstItem.Type === 'Playlist') { - promise = getItemsForPlayback(serverId, { ParentId: firstItem.Id, SortBy: options.shuffle ? 'Random' : null }); } else if (firstItem.Type === 'MusicArtist') { - promise = getItemsForPlayback(serverId, { ArtistIds: firstItem.Id, Filters: 'IsNotFolder', @@ -1881,9 +1782,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla SortBy: options.shuffle ? 'Random' : 'SortName', MediaTypes: 'Audio' }); - } else if (firstItem.MediaType === 'Photo') { - promise = getItemsForPlayback(serverId, { ParentId: firstItem.ParentId, Filters: 'IsNotFolder', @@ -1892,11 +1791,10 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla SortBy: options.shuffle ? 'Random' : 'SortName', MediaTypes: 'Photo,Video' }).then(function (result) { - var items = result.Items; + const items = result.Items; - var index = items.map(function (i) { + let index = items.map(function (i) { return i.Id; - }).indexOf(firstItem.Id); if (index === -1) { @@ -1906,10 +1804,8 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla options.startIndex = index; return Promise.resolve(result); - }); } else if (firstItem.Type === 'PhotoAlbum') { - promise = getItemsForPlayback(serverId, { ParentId: firstItem.Id, Filters: 'IsNotFolder', @@ -1918,10 +1814,8 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla SortBy: options.shuffle ? 'Random' : 'SortName', MediaTypes: 'Photo,Video', Limit: 1000 - }); } else if (firstItem.Type === 'MusicGenre') { - promise = getItemsForPlayback(serverId, { GenreIds: firstItem.Id, Filters: 'IsNotFolder', @@ -1930,24 +1824,19 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla MediaTypes: 'Audio' }); } else if (firstItem.IsFolder) { - promise = getItemsForPlayback(serverId, mergePlaybackQueries({ - ParentId: firstItem.Id, Filters: 'IsNotFolder', Recursive: true, // These are pre-sorted SortBy: options.shuffle ? 'Random' : (['BoxSet'].indexOf(firstItem.Type) === -1 ? 'SortName' : null), MediaTypes: 'Audio,Video' - }, queryOptions)); } else if (firstItem.Type === 'Episode' && items.length === 1 && getPlayer(firstItem, options).supportsProgress !== false) { - promise = new Promise(function (resolve, reject) { - var apiClient = connectionManager.getApiClient(firstItem.ServerId); + const apiClient = window.connectionManager.getApiClient(firstItem.ServerId); apiClient.getCurrentUser().then(function (user) { - if (!user.Configuration.EnableNextEpisodeAutoPlay || !firstItem.SeriesId) { resolve(null); return; @@ -1958,12 +1847,9 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla IsMissing: false, UserId: apiClient.getCurrentUserId(), Fields: 'Chapters' - }).then(function (episodesResult) { - - var foundItem = false; + let foundItem = false; episodesResult.Items = episodesResult.Items.filter(function (e) { - if (foundItem) { return true; } @@ -1983,7 +1869,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla if (promise) { return promise.then(function (result) { - return result ? result.Items : items; }); } else { @@ -1992,7 +1877,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } self.play = function (options) { - normalizePlayOptions(options); if (self._currentPlayer) { @@ -2010,42 +1894,32 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } if (options.items) { - return translateItemsForPlayback(options.items, options).then(function (items) { - return playWithIntros(items, options); }); - } else { - if (!options.serverId) { throw new Error('serverId required!'); } return getItemsForPlayback(options.serverId, { - Ids: options.ids.join(',') - }).then(function (result) { - return translateItemsForPlayback(result.Items, options).then(function (items) { - return playWithIntros(items, options); }); - }); } }; function getPlayerData(player) { - if (!player) { throw new Error('player cannot be null'); } if (!player.name) { throw new Error('player name cannot be null'); } - var state = playerStates[player.name]; + let state = playerStates[player.name]; if (!state) { playerStates[player.name] = {}; @@ -2056,7 +1930,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } self.getPlayerState = function (player, item, mediaSource) { - player = player || self._currentPlayer; if (!player) { @@ -2070,12 +1943,11 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla item = item || self.currentItem(player); mediaSource = mediaSource || self.currentMediaSource(player); - var state = { + const state = { PlayState: {} }; if (player) { - state.PlayState.VolumeLevel = player.getVolume(); state.PlayState.IsMuted = player.isMuted(); state.PlayState.IsPaused = player.paused(); @@ -2100,7 +1972,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } if (mediaSource) { - state.PlayState.MediaSourceId = mediaSource.Id; state.NowPlayingItem = { @@ -2111,7 +1982,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } if (item) { - state.NowPlayingItem = getNowPlayingItemForReporting(player, item, mediaSource); } @@ -2121,7 +1991,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla }; self.duration = function (player) { - player = player || self._currentPlayer; if (player && !enableLocalPlaylistManagement(player) && !player.isLocalPlayer) { @@ -2132,13 +2001,13 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla throw new Error('player cannot be null'); } - var mediaSource = self.currentMediaSource(player); + const mediaSource = self.currentMediaSource(player); if (mediaSource && mediaSource.RunTimeTicks) { return mediaSource.RunTimeTicks; } - var playerDuration = player.duration(); + let playerDuration = player.duration(); if (playerDuration) { playerDuration *= 10000; @@ -2148,14 +2017,13 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla }; function getCurrentTicks(player) { - if (!player) { throw new Error('player cannot be null'); } - var playerTime = Math.floor(10000 * (player || self._currentPlayer).currentTime()); + let playerTime = Math.floor(10000 * (player).currentTime()); - var streamInfo = getPlayerData(player).streamInfo; + const streamInfo = getPlayerData(player).streamInfo; if (streamInfo) { playerTime += getPlayerData(player).streamInfo.transcodingOffsetTicks || 0; } @@ -2167,9 +2035,8 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla self.getCurrentTicks = getCurrentTicks; function playOther(items, options, user) { - - var playStartIndex = options.startIndex || 0; - var player = getPlayer(items[playStartIndex], options); + const playStartIndex = options.startIndex || 0; + const player = getPlayer(items[playStartIndex], options); loading.hide(); @@ -2179,9 +2046,8 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } function playWithIntros(items, options, user) { - - var playStartIndex = options.startIndex || 0; - var firstItem = items[playStartIndex]; + let playStartIndex = options.startIndex || 0; + let firstItem = items[playStartIndex]; // If index was bad, reset it if (!firstItem) { @@ -2191,30 +2057,26 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla // If it's still null then there's nothing to play if (!firstItem) { - showPlaybackInfoErrorMessage(self, 'NoCompatibleStream', false); + showPlaybackInfoErrorMessage(self, 'PlaybackErrorNoCompatibleStream'); return Promise.reject(); } if (firstItem.MediaType === 'Photo' || firstItem.MediaType === 'Book') { - return playOther(items, options, user); } - var apiClient = connectionManager.getApiClient(firstItem.ServerId); + const apiClient = window.connectionManager.getApiClient(firstItem.ServerId); return getIntros(firstItem, apiClient, options).then(function (introsResult) { - - var introItems = introsResult.Items; - var introPlayOptions; + const introItems = introsResult.Items; + let introPlayOptions; firstItem.playOptions = truncatePlayOptions(options); if (introItems.length) { - introPlayOptions = { fullscreen: firstItem.playOptions.fullscreen }; - } else { introPlayOptions = firstItem.playOptions; } @@ -2226,7 +2088,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla introPlayOptions.startIndex = playStartIndex; return playInternal(items[playStartIndex], introPlayOptions, function () { - self._playQueueManager.setPlaylist(items); setPlaylistState(items[playStartIndex].PlaylistItemId, playStartIndex); @@ -2237,17 +2098,15 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla // Set playlist state. Using a method allows for overloading in derived player implementations function setPlaylistState(playlistItemId, index) { - if (!isNaN(index)) { self._playQueueManager.setPlaylistState(playlistItemId, index); } } function playInternal(item, playOptions, onPlaybackStartedFn) { - if (item.IsPlaceHolder) { loading.hide(); - showPlaybackInfoErrorMessage(self, 'PlaceHolder', true); + showPlaybackInfoErrorMessage(self, 'PlaybackErrorPlaceHolder'); return Promise.reject(); } @@ -2261,47 +2120,38 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } return runInterceptors(item, playOptions).then(function () { - if (playOptions.fullscreen) { loading.show(); } // TODO: This should be the media type requested, not the original media type - var mediaType = item.MediaType; + const mediaType = item.MediaType; - var onBitrateDetectionFailure = function () { - return playAfterBitrateDetect(getSavedMaxStreamingBitrate(connectionManager.getApiClient(item.ServerId), mediaType), item, playOptions, onPlaybackStartedFn); + const onBitrateDetectionFailure = function () { + return playAfterBitrateDetect(getSavedMaxStreamingBitrate(window.connectionManager.getApiClient(item.ServerId), mediaType), item, playOptions, onPlaybackStartedFn); }; if (!isServerItem(item) || itemHelper.isLocalItem(item)) { return onBitrateDetectionFailure(); } - var apiClient = connectionManager.getApiClient(item.ServerId); + const apiClient = window.connectionManager.getApiClient(item.ServerId); apiClient.getEndpointInfo().then(function (endpointInfo) { - if ((mediaType === 'Video' || mediaType === 'Audio') && appSettings.enableAutomaticBitrateDetection(endpointInfo.IsInNetwork, mediaType)) { - return apiClient.detectBitrate().then(function (bitrate) { - appSettings.maxStreamingBitrate(endpointInfo.IsInNetwork, mediaType, bitrate); return playAfterBitrateDetect(bitrate, item, playOptions, onPlaybackStartedFn); - }, onBitrateDetectionFailure); - } else { - onBitrateDetectionFailure(); } - }, onBitrateDetectionFailure); - }, onInterceptorRejection); } function onInterceptorRejection() { - var player = self._currentPlayer; + const player = self._currentPlayer; if (player) { destroyPlayer(player); @@ -2318,10 +2168,8 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } function runInterceptors(item, playOptions) { - return new Promise(function (resolve, reject) { - - var interceptors = pluginManager.ofType('preplayintercept'); + const interceptors = pluginManager.ofType('preplayintercept'); interceptors.sort(function (a, b) { return (a.order || 0) - (b.order || 0); @@ -2334,7 +2182,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla loading.hide(); - var options = Object.assign({}, playOptions); + const options = Object.assign({}, playOptions); options.mediaType = item.MediaType; options.item = item; @@ -2344,25 +2192,20 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } function runNextPrePlay(interceptors, index, options, resolve, reject) { - if (index >= interceptors.length) { resolve(); return; } - var interceptor = interceptors[index]; + const interceptor = interceptors[index]; interceptor.intercept(options).then(function () { - runNextPrePlay(interceptors, index + 1, options, resolve, reject); - }, reject); } function sendPlaybackListToPlayer(player, items, deviceProfile, maxBitrate, apiClient, startPositionTicks, mediaSourceId, audioStreamIndex, subtitleStreamIndex, startIndex) { - return setStreamUrls(items, deviceProfile, maxBitrate, apiClient, startPositionTicks).then(function () { - loading.hide(); return player.play({ @@ -2377,16 +2220,14 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } function playAfterBitrateDetect(maxBitrate, item, playOptions, onPlaybackStartedFn) { + const startPosition = playOptions.startPositionTicks; - var startPosition = playOptions.startPositionTicks; + const player = getPlayer(item, playOptions); + const activePlayer = self._currentPlayer; - var player = getPlayer(item, playOptions); - var activePlayer = self._currentPlayer; - - var promise; + let promise; if (activePlayer) { - // TODO: if changing players within the same playlist, this will cause nextItem to be null self._playNextAfterEnded = false; promise = onPlaybackChanging(activePlayer, player, item); @@ -2396,7 +2237,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla if (!isServerItem(item) || item.MediaType === 'Book') { return promise.then(function () { - var streamInfo = createStreamInfoFromUrlItem(item); + const streamInfo = createStreamInfoFromUrlItem(item); streamInfo.fullscreen = playOptions.fullscreen; getPlayerData(player).isChangingStream = false; return player.play(streamInfo).then(function () { @@ -2411,17 +2252,15 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } return Promise.all([promise, player.getDeviceProfile(item)]).then(function (responses) { + const deviceProfile = responses[1]; - var deviceProfile = responses[1]; + const apiClient = window.connectionManager.getApiClient(item.ServerId); - var apiClient = connectionManager.getApiClient(item.ServerId); - - var mediaSourceId = playOptions.mediaSourceId; - var audioStreamIndex = playOptions.audioStreamIndex; - var subtitleStreamIndex = playOptions.subtitleStreamIndex; + const mediaSourceId = playOptions.mediaSourceId; + const audioStreamIndex = playOptions.audioStreamIndex; + const subtitleStreamIndex = playOptions.subtitleStreamIndex; if (player && !enableLocalPlaylistManagement(player)) { - return sendPlaybackListToPlayer(player, playOptions.items, deviceProfile, maxBitrate, apiClient, startPosition, mediaSourceId, audioStreamIndex, subtitleStreamIndex, playOptions.startIndex); } @@ -2429,8 +2268,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla playOptions.items = null; return getPlaybackMediaSource(player, apiClient, deviceProfile, maxBitrate, item, startPosition, mediaSourceId, audioStreamIndex, subtitleStreamIndex).then(function (mediaSource) { - - var streamInfo = createStreamInfo(apiClient, item.MediaType, item, mediaSource, startPosition); + const streamInfo = createStreamInfo(apiClient, item.MediaType, item, mediaSource, startPosition); streamInfo.fullscreen = playOptions.fullscreen; @@ -2442,7 +2280,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla onPlaybackStartedFn(); onPlaybackStarted(player, playOptions, streamInfo, mediaSource); }, function (err) { - // TODO: Improve this because it will report playback start on a failure onPlaybackStartedFn(); onPlaybackStarted(player, playOptions, streamInfo, mediaSource); @@ -2458,22 +2295,18 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } self.getPlaybackInfo = function (item, options) { - options = options || {}; - var startPosition = options.startPositionTicks || 0; - var mediaType = options.mediaType || item.MediaType; - var player = getPlayer(item, options); - var apiClient = connectionManager.getApiClient(item.ServerId); + const startPosition = options.startPositionTicks || 0; + const mediaType = options.mediaType || item.MediaType; + const player = getPlayer(item, options); + const apiClient = window.connectionManager.getApiClient(item.ServerId); // Call this just to ensure the value is recorded, it is needed with getSavedMaxStreamingBitrate return apiClient.getEndpointInfo().then(function () { - - var maxBitrate = getSavedMaxStreamingBitrate(connectionManager.getApiClient(item.ServerId), mediaType); + const maxBitrate = getSavedMaxStreamingBitrate(window.connectionManager.getApiClient(item.ServerId), mediaType); return player.getDeviceProfile(item).then(function (deviceProfile) { - return getPlaybackMediaSource(player, apiClient, deviceProfile, maxBitrate, item, startPosition, options.mediaSourceId, options.audioStreamIndex, options.subtitleStreamIndex).then(function (mediaSource) { - return createStreamInfo(apiClient, item.MediaType, item, mediaSource, startPosition); }); }); @@ -2481,59 +2314,49 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla }; self.getPlaybackMediaSources = function (item, options) { - options = options || {}; - var startPosition = options.startPositionTicks || 0; - var mediaType = options.mediaType || item.MediaType; + const startPosition = options.startPositionTicks || 0; + const mediaType = options.mediaType || item.MediaType; // TODO: Remove the true forceLocalPlayer hack - var player = getPlayer(item, options, true); - var apiClient = connectionManager.getApiClient(item.ServerId); + const player = getPlayer(item, options, true); + const apiClient = window.connectionManager.getApiClient(item.ServerId); // Call this just to ensure the value is recorded, it is needed with getSavedMaxStreamingBitrate return apiClient.getEndpointInfo().then(function () { - - var maxBitrate = getSavedMaxStreamingBitrate(connectionManager.getApiClient(item.ServerId), mediaType); + const maxBitrate = getSavedMaxStreamingBitrate(window.connectionManager.getApiClient(item.ServerId), mediaType); return player.getDeviceProfile(item).then(function (deviceProfile) { - return getPlaybackInfo(player, apiClient, item, deviceProfile, maxBitrate, startPosition, false, null, null, null, null).then(function (playbackInfoResult) { - return playbackInfoResult.MediaSources; }); }); }); - }; function createStreamInfo(apiClient, type, item, mediaSource, startPosition) { + let mediaUrl; + let contentType; + let transcodingOffsetTicks = 0; + const playerStartPositionTicks = startPosition; + const liveStreamId = mediaSource.LiveStreamId; - var mediaUrl; - var contentType; - var transcodingOffsetTicks = 0; - var playerStartPositionTicks = startPosition; - var liveStreamId = mediaSource.LiveStreamId; + let playMethod = 'Transcode'; - var playMethod = 'Transcode'; - - var mediaSourceContainer = (mediaSource.Container || '').toLowerCase(); - var directOptions; + const mediaSourceContainer = (mediaSource.Container || '').toLowerCase(); + let directOptions; if (type === 'Video' || type === 'Audio') { - contentType = getMimeType(type.toLowerCase(), mediaSourceContainer); if (mediaSource.enableDirectPlay) { mediaUrl = mediaSource.Path; playMethod = 'DirectPlay'; - } else if (mediaSource.StreamUrl) { - // Only used for audio playMethod = 'Transcode'; mediaUrl = mediaSource.StreamUrl; } else if (mediaSource.SupportsDirectStream) { - directOptions = { Static: true, mediaSourceId: mediaSource.Id, @@ -2549,21 +2372,16 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla directOptions.LiveStreamId = mediaSource.LiveStreamId; } - var prefix = type === 'Video' ? 'Videos' : 'Audio'; + const prefix = type === 'Video' ? 'Videos' : 'Audio'; mediaUrl = apiClient.getUrl(prefix + '/' + item.Id + '/stream.' + mediaSourceContainer, directOptions); playMethod = 'DirectStream'; - } else if (mediaSource.SupportsTranscoding) { - mediaUrl = apiClient.getUrl(mediaSource.TranscodingUrl); if (mediaSource.TranscodingSubProtocol === 'hls') { - contentType = 'application/x-mpegURL'; - } else { - contentType = getMimeType(type.toLowerCase(), mediaSource.TranscodingContainer); if (mediaUrl.toLowerCase().indexOf('copytimestamps=true') === -1) { @@ -2571,9 +2389,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } } } - } else { - // All other media types mediaUrl = mediaSource.Path; playMethod = 'DirectPlay'; @@ -2585,7 +2401,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla playMethod = 'DirectPlay'; } - var resultInfo = { + const resultInfo = { url: mediaUrl, mimeType: contentType, transcodingOffsetTicks: transcodingOffsetTicks, @@ -2602,7 +2418,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla title: item.Name }; - var backdropUrl = backdropImageUrl(apiClient, item, {}); + const backdropUrl = backdropImageUrl(apiClient, item, {}); if (backdropUrl) { resultInfo.backdropUrl = backdropUrl; } @@ -2611,21 +2427,19 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } function getTextTracks(apiClient, item, mediaSource) { - - var subtitleStreams = mediaSource.MediaStreams.filter(function (s) { + const subtitleStreams = mediaSource.MediaStreams.filter(function (s) { return s.Type === 'Subtitle'; }); - var textStreams = subtitleStreams.filter(function (s) { + const textStreams = subtitleStreams.filter(function (s) { return s.DeliveryMethod === 'External'; }); - var tracks = []; + const tracks = []; - for (var i = 0, length = textStreams.length; i < length; i++) { - - var textStream = textStreams[i]; - var textStreamUrl; + for (let i = 0, length = textStreams.length; i < length; i++) { + const textStream = textStreams[i]; + let textStreamUrl; if (itemHelper.isLocalItem(item)) { textStreamUrl = textStream.Path; @@ -2646,31 +2460,22 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } function getPlaybackMediaSource(player, apiClient, deviceProfile, maxBitrate, item, startPosition, mediaSourceId, audioStreamIndex, subtitleStreamIndex) { - return getPlaybackInfo(player, apiClient, item, deviceProfile, maxBitrate, startPosition, true, mediaSourceId, audioStreamIndex, subtitleStreamIndex, null).then(function (playbackInfoResult) { - if (validatePlaybackInfoResult(self, playbackInfoResult)) { - return getOptimalMediaSource(apiClient, item, playbackInfoResult.MediaSources).then(function (mediaSource) { if (mediaSource) { - if (mediaSource.RequiresOpening && !mediaSource.LiveStreamId) { - return getLiveStream(player, apiClient, item, playbackInfoResult.PlaySessionId, deviceProfile, maxBitrate, startPosition, mediaSource, null, null).then(function (openLiveStreamResult) { - return supportsDirectPlay(apiClient, item, openLiveStreamResult.MediaSource).then(function (result) { - openLiveStreamResult.MediaSource.enableDirectPlay = result; return openLiveStreamResult.MediaSource; }); - }); - } else { return mediaSource; } } else { - showPlaybackInfoErrorMessage(self, 'NoCompatibleStream'); + showPlaybackInfoErrorMessage(self, 'PlaybackErrorNoCompatibleStream'); return Promise.reject(); } }); @@ -2681,12 +2486,9 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } function getPlayer(item, playOptions, forceLocalPlayers) { - - var serverItem = isServerItem(item); + const serverItem = isServerItem(item); return getAutomaticPlayers(self, forceLocalPlayers).filter(function (p) { - if (p.canPlayMediaType(item.MediaType)) { - if (serverItem) { if (p.canPlayItem) { return p.canPlayItem(item, playOptions); @@ -2698,22 +2500,20 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } return false; - })[0]; } self.setCurrentPlaylistItem = function (playlistItemId, player) { - player = player || self._currentPlayer; if (player && !enableLocalPlaylistManagement(player)) { return player.setCurrentPlaylistItem(playlistItemId); } - var newItem; - var newItemIndex; - var playlist = self._playQueueManager.getPlaylist(); + let newItem; + let newItemIndex; + const playlist = self._playQueueManager.getPlaylist(); - for (var i = 0, length = playlist.length; i < length; i++) { + for (let i = 0, length = playlist.length; i < length; i++) { if (playlist[i].PlaylistItemId === playlistItemId) { newItem = playlist[i]; newItemIndex = i; @@ -2722,8 +2522,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } if (newItem) { - - var newItemPlayOptions = newItem.playOptions || getDefaultPlayOptions(); + const newItemPlayOptions = newItem.playOptions || getDefaultPlayOptions(); playInternal(newItem, newItemPlayOptions, function () { setPlaylistState(newItem.PlaylistItemId, newItemIndex); @@ -2732,7 +2531,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla }; self.removeFromPlaylist = function (playlistItemIds, player) { - if (!playlistItemIds) { throw new Error('Invalid playlistItemIds'); } @@ -2742,21 +2540,21 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return player.removeFromPlaylist(playlistItemIds); } - var removeResult = self._playQueueManager.removeFromPlaylist(playlistItemIds); + const removeResult = self._playQueueManager.removeFromPlaylist(playlistItemIds); if (removeResult.result === 'empty') { return self.stop(player); } - var isCurrentIndex = removeResult.isCurrentIndex; + const isCurrentIndex = removeResult.isCurrentIndex; events.trigger(player, 'playlistitemremove', [ { playlistItemIds: playlistItemIds - }]); + } + ]); if (isCurrentIndex) { - return self.setCurrentPlaylistItem(self._playQueueManager.getPlaylist()[0].PlaylistItemId, player); } @@ -2764,13 +2562,12 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla }; self.movePlaylistItem = function (playlistItemId, newIndex, player) { - player = player || self._currentPlayer; if (player && !enableLocalPlaylistManagement(player)) { return player.movePlaylistItem(playlistItemId, newIndex); } - var moveResult = self._playQueueManager.movePlaylistItem(playlistItemId, newIndex); + const moveResult = self._playQueueManager.movePlaylistItem(playlistItemId, newIndex); if (moveResult.result === 'noop') { return; @@ -2780,11 +2577,11 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla { playlistItemId: moveResult.playlistItemId, newIndex: moveResult.newIndex - }]); + } + ]); }; self.getCurrentPlaylistIndex = function (player) { - player = player || self._currentPlayer; if (player && !enableLocalPlaylistManagement(player)) { return player.getCurrentPlaylistIndex(); @@ -2794,7 +2591,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla }; self.getCurrentPlaylistItemId = function (player) { - player = player || self._currentPlayer; if (player && !enableLocalPlaylistManagement(player)) { return player.getCurrentPlaylistItemId(); @@ -2804,31 +2600,27 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla }; self.channelUp = function (player) { - player = player || self._currentPlayer; return self.nextTrack(player); }; self.channelDown = function (player) { - player = player || self._currentPlayer; return self.previousTrack(player); }; self.nextTrack = function (player) { - player = player || self._currentPlayer; if (player && !enableLocalPlaylistManagement(player)) { return player.nextTrack(); } - var newItemInfo = self._playQueueManager.getNextItemInfo(); + const newItemInfo = self._playQueueManager.getNextItemInfo(); if (newItemInfo) { - console.debug('playing next track'); - var newItemPlayOptions = newItemInfo.item.playOptions || getDefaultPlayOptions(); + const newItemPlayOptions = newItemInfo.item.playOptions || getDefaultPlayOptions(); playInternal(newItemInfo.item, newItemPlayOptions, function () { setPlaylistState(newItemInfo.item.PlaylistItemId, newItemInfo.index); @@ -2837,21 +2629,18 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla }; self.previousTrack = function (player) { - player = player || self._currentPlayer; if (player && !enableLocalPlaylistManagement(player)) { return player.previousTrack(); } - var newIndex = self.getCurrentPlaylistIndex(player) - 1; + const newIndex = self.getCurrentPlaylistIndex(player) - 1; if (newIndex >= 0) { - - var playlist = self._playQueueManager.getPlaylist(); - var newItem = playlist[newIndex]; + const playlist = self._playQueueManager.getPlaylist(); + const newItem = playlist[newIndex]; if (newItem) { - - var newItemPlayOptions = newItem.playOptions || getDefaultPlayOptions(); + const newItemPlayOptions = newItem.playOptions || getDefaultPlayOptions(); newItemPlayOptions.startPositionTicks = 0; playInternal(newItem, newItemPlayOptions, function () { @@ -2870,7 +2659,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla }; function queue(options, mode, player) { - player = player || self._currentPlayer; if (!player) { @@ -2878,38 +2666,27 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } if (options.items) { - return translateItemsForPlayback(options.items, options).then(function (items) { - // TODO: Handle options.startIndex for photos queueAll(items, mode, player); - }); - } else { - if (!options.serverId) { throw new Error('serverId required!'); } return getItemsForPlayback(options.serverId, { - Ids: options.ids.join(',') - }).then(function (result) { - return translateItemsForPlayback(result.Items, options).then(function (items) { - // TODO: Handle options.startIndex for photos queueAll(items, mode, player); - }); }); } } function queueAll(items, mode, player) { - if (!items.length) { return; } @@ -2927,16 +2704,13 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return; } - var queueDirectToPlayer = player && !enableLocalPlaylistManagement(player); + const queueDirectToPlayer = player && !enableLocalPlaylistManagement(player); if (queueDirectToPlayer) { - - var apiClient = connectionManager.getApiClient(items[0].ServerId); + const apiClient = window.connectionManager.getApiClient(items[0].ServerId); player.getDeviceProfile(items[0]).then(function (profile) { - setStreamUrls(items, profile, self.getMaxStreamingBitrate(player), apiClient, 0).then(function () { - if (mode === 'next') { player.queueNext(items); } else { @@ -2957,35 +2731,31 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } function onPlayerProgressInterval() { - var player = this; + const player = this; sendProgressUpdate(player, 'timeupdate'); } function startPlaybackProgressTimer(player) { - stopPlaybackProgressTimer(player); player._progressInterval = setInterval(onPlayerProgressInterval.bind(player), 10000); } function stopPlaybackProgressTimer(player) { - if (player._progressInterval) { - clearInterval(player._progressInterval); player._progressInterval = null; } } function onPlaybackStarted(player, playOptions, streamInfo, mediaSource) { - if (!player) { throw new Error('player cannot be null'); } setCurrentPlayerInternal(player); - var playerData = getPlayerData(player); + const playerData = getPlayerData(player); playerData.streamInfo = streamInfo; @@ -3000,10 +2770,10 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } self._playNextAfterEnded = true; - var isFirstItem = playOptions.isFirstItem; - var fullscreen = playOptions.fullscreen; + const isFirstItem = playOptions.isFirstItem; + const fullscreen = playOptions.fullscreen; - var state = self.getPlayerState(player, streamInfo.item, streamInfo.mediaSource); + const state = self.getPlayerState(player, streamInfo.item, streamInfo.mediaSource); reportPlayback(self, state, player, true, state.NowPlayingItem.ServerId, 'reportPlaybackStart'); @@ -3019,23 +2789,22 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } function onPlaybackStartedFromSelfManagingPlayer(e, item, mediaSource) { - - var player = this; + const player = this; setCurrentPlayerInternal(player); - var playOptions = item.playOptions || getDefaultPlayOptions(); - var isFirstItem = playOptions.isFirstItem; - var fullscreen = playOptions.fullscreen; + const playOptions = item.playOptions || getDefaultPlayOptions(); + const isFirstItem = playOptions.isFirstItem; + const fullscreen = playOptions.fullscreen; playOptions.isFirstItem = false; - var playerData = getPlayerData(player); + const playerData = getPlayerData(player); playerData.streamInfo = {}; - var streamInfo = playerData.streamInfo; + const streamInfo = playerData.streamInfo; streamInfo.playbackStartTimeTicks = new Date().getTime() * 10000; - var state = self.getPlayerState(player, item, mediaSource); + const state = self.getPlayerState(player, item, mediaSource); reportPlayback(self, state, player, true, state.NowPlayingItem.ServerId, 'reportPlaybackStart'); @@ -3051,16 +2820,15 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } function onPlaybackStoppedFromSelfManagingPlayer(e, playerStopInfo) { - - var player = this; + const player = this; stopPlaybackProgressTimer(player); - var state = self.getPlayerState(player, playerStopInfo.item, playerStopInfo.mediaSource); + const state = self.getPlayerState(player, playerStopInfo.item, playerStopInfo.mediaSource); - var nextItem = playerStopInfo.nextItem; - var nextMediaType = playerStopInfo.nextMediaType; + const nextItem = playerStopInfo.nextItem; + const nextMediaType = playerStopInfo.nextMediaType; - var playbackStopInfo = { + const playbackStopInfo = { player: player, state: state, nextItem: (nextItem ? nextItem.item : null), @@ -3069,13 +2837,12 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla state.NextMediaType = nextMediaType; - var streamInfo = getPlayerData(player).streamInfo; + const streamInfo = getPlayerData(player).streamInfo; // only used internally as a safeguard to avoid reporting other events to the server after playback stopped streamInfo.ended = true; if (isServerItem(playerStopInfo.item)) { - state.PlayState.PositionTicks = (playerStopInfo.positionMs || 0) * 10000; reportPlayback(self, state, player, true, playerStopInfo.item.ServerId, 'reportPlaybackStopped'); @@ -3086,8 +2853,8 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla events.trigger(player, 'playbackstop', [state]); events.trigger(self, 'playbackstop', [playbackStopInfo]); - var nextItemPlayOptions = nextItem ? (nextItem.item.playOptions || getDefaultPlayOptions()) : getDefaultPlayOptions(); - var newPlayer = nextItem ? getPlayer(nextItem.item, nextItemPlayOptions) : null; + const nextItemPlayOptions = nextItem ? (nextItem.item.playOptions || getDefaultPlayOptions()) : getDefaultPlayOptions(); + const newPlayer = nextItem ? getPlayer(nextItem.item, nextItemPlayOptions) : null; if (newPlayer !== player) { destroyPlayer(player); @@ -3096,11 +2863,8 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } function enablePlaybackRetryWithTranscoding(streamInfo, errorType, currentlyPreventsVideoStreamCopy, currentlyPreventsAudioStreamCopy) { - // mediadecodeerror, medianotsupported, network, servererror - if (streamInfo.mediaSource.SupportsTranscoding && (!currentlyPreventsVideoStreamCopy || !currentlyPreventsAudioStreamCopy)) { - return true; } @@ -3108,50 +2872,44 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } function onPlaybackError(e, error) { - - var player = this; + const player = this; error = error || {}; // network // mediadecodeerror // medianotsupported - var errorType = error.type; + const errorType = error.type; console.debug('playbackmanager playback error type: ' + (errorType || '')); - var streamInfo = error.streamInfo || getPlayerData(player).streamInfo; + const streamInfo = error.streamInfo || getPlayerData(player).streamInfo; if (streamInfo) { - - var currentlyPreventsVideoStreamCopy = streamInfo.url.toLowerCase().indexOf('allowvideostreamcopy=false') !== -1; - var currentlyPreventsAudioStreamCopy = streamInfo.url.toLowerCase().indexOf('allowaudiostreamcopy=false') !== -1; + const currentlyPreventsVideoStreamCopy = streamInfo.url.toLowerCase().indexOf('allowvideostreamcopy=false') !== -1; + const currentlyPreventsAudioStreamCopy = streamInfo.url.toLowerCase().indexOf('allowaudiostreamcopy=false') !== -1; // Auto switch to transcoding if (enablePlaybackRetryWithTranscoding(streamInfo, errorType, currentlyPreventsVideoStreamCopy, currentlyPreventsAudioStreamCopy)) { - - var startTime = getCurrentTicks(player) || streamInfo.playerStartPositionTicks; + const startTime = getCurrentTicks(player) || streamInfo.playerStartPositionTicks; changeStream(player, startTime, { - // force transcoding EnableDirectPlay: false, EnableDirectStream: false, AllowVideoStreamCopy: false, AllowAudioStreamCopy: currentlyPreventsAudioStreamCopy || currentlyPreventsVideoStreamCopy ? false : null - }); return; } } - var displayErrorCode = 'NoCompatibleStream'; + const displayErrorCode = 'NoCompatibleStream'; onPlaybackStopped.call(player, e, displayErrorCode); } function onPlaybackStopped(e, displayErrorCode) { - - var player = this; + const player = this; if (getPlayerData(player).isChangingStream) { return; @@ -3160,15 +2918,15 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla stopPlaybackProgressTimer(player); // User clicked stop or content ended - var state = self.getPlayerState(player); - var data = getPlayerData(player); - var streamInfo = data.streamInfo; + const state = self.getPlayerState(player); + const data = getPlayerData(player); + const streamInfo = data.streamInfo; - var nextItem = self._playNextAfterEnded ? self._playQueueManager.getNextItemInfo() : null; + const nextItem = self._playNextAfterEnded ? self._playQueueManager.getNextItemInfo() : null; - var nextMediaType = (nextItem ? nextItem.item.MediaType : null); + const nextMediaType = (nextItem ? nextItem.item.MediaType : null); - var playbackStopInfo = { + const playbackStopInfo = { player: player, state: state, nextItem: (nextItem ? nextItem.item : null), @@ -3178,7 +2936,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla state.NextMediaType = nextMediaType; if (isServerItem(streamInfo.item)) { - if (player.supportsProgress === false && state.PlayState && !state.PlayState.PositionTicks) { state.PlayState.PositionTicks = streamInfo.item.RunTimeTicks; } @@ -3198,8 +2955,8 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla events.trigger(player, 'playbackstop', [state]); events.trigger(self, 'playbackstop', [playbackStopInfo]); - var nextItemPlayOptions = nextItem ? (nextItem.item.playOptions || getDefaultPlayOptions()) : getDefaultPlayOptions(); - var newPlayer = nextItem ? getPlayer(nextItem.item, nextItemPlayOptions) : null; + const nextItemPlayOptions = nextItem ? (nextItem.item.playOptions || getDefaultPlayOptions()) : getDefaultPlayOptions(); + const newPlayer = nextItem ? getPlayer(nextItem.item, nextItemPlayOptions) : null; if (newPlayer !== player) { destroyPlayer(player); @@ -3207,7 +2964,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } if (displayErrorCode && typeof (displayErrorCode) === 'string') { - showPlaybackInfoErrorMessage(self, displayErrorCode, nextItem); + showPlaybackInfoErrorMessage(self, 'PlaybackError' + displayErrorCode); } else if (nextItem) { self.nextTrack(); } else { @@ -3217,30 +2974,25 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } function onPlaybackChanging(activePlayer, newPlayer, newItem) { + const state = self.getPlayerState(activePlayer); - var state = self.getPlayerState(activePlayer); - - var serverId = self.currentItem(activePlayer).ServerId; + const serverId = self.currentItem(activePlayer).ServerId; // User started playing something new while existing content is playing - var promise; + let promise; stopPlaybackProgressTimer(activePlayer); unbindStopped(activePlayer); if (activePlayer === newPlayer) { - // If we're staying with the same player, stop it promise = activePlayer.stop(false); - } else { - // If we're switching players, tear down the current one promise = activePlayer.stop(true); } return promise.then(function () { - bindStopped(activePlayer); if (enableLocalPlaylistManagement(activePlayer)) { @@ -3257,7 +3009,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } function bindStopped(player) { - if (enableLocalPlaylistManagement(player)) { events.off(player, 'stopped', onPlaybackStopped); events.on(player, 'stopped', onPlaybackStopped); @@ -3265,52 +3016,51 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } function onPlaybackTimeUpdate(e) { - var player = this; + const player = this; sendProgressUpdate(player, 'timeupdate'); } function onPlaybackPause(e) { - var player = this; + const player = this; sendProgressUpdate(player, 'pause'); } function onPlaybackUnpause(e) { - var player = this; + const player = this; sendProgressUpdate(player, 'unpause'); } function onPlaybackVolumeChange(e) { - var player = this; + const player = this; sendProgressUpdate(player, 'volumechange'); } function onRepeatModeChange(e) { - var player = this; + const player = this; sendProgressUpdate(player, 'repeatmodechange'); } function onShuffleQueueModeChange() { - var player = this; + const player = this; sendProgressUpdate(player, 'shufflequeuemodechange'); } function onPlaylistItemMove(e) { - var player = this; + const player = this; sendProgressUpdate(player, 'playlistitemmove', true); } function onPlaylistItemRemove(e) { - var player = this; + const player = this; sendProgressUpdate(player, 'playlistitemremove', true); } function onPlaylistItemAdd(e) { - var player = this; + const player = this; sendProgressUpdate(player, 'playlistitemadd', true); } function unbindStopped(player) { - events.off(player, 'stopped', onPlaybackStopped); } @@ -3324,10 +3074,8 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } function initMediaPlayer(player) { - players.push(player); players.sort(function (a, b) { - return (a.priority || 0) - (b.priority || 0); }); @@ -3353,7 +3101,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla events.on(player, 'playlistitemremove', onPlaylistItemRemove); events.on(player, 'playlistitemadd', onPlaylistItemAdd); } else if (player.isLocalPlayer) { - events.on(player, 'itemstarted', onPlaybackStartedFromSelfManagingPlayer); events.on(player, 'itemstopped', onPlaybackStoppedFromSelfManagingPlayer); events.on(player, 'timeupdate', onPlaybackTimeUpdate); @@ -3374,9 +3121,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } events.on(pluginManager, 'registered', function (e, plugin) { - if (plugin.type === 'mediaplayer') { - initMediaPlayer(plugin); } }); @@ -3388,19 +3133,18 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla throw new Error('player cannot be null'); } - var state = self.getPlayerState(player); + const state = self.getPlayerState(player); if (state.NowPlayingItem) { - var serverId = state.NowPlayingItem.ServerId; + const serverId = state.NowPlayingItem.ServerId; - var streamInfo = getPlayerData(player).streamInfo; + const streamInfo = getPlayerData(player).streamInfo; if (streamInfo && streamInfo.started && !streamInfo.ended) { reportPlayback(self, state, player, reportPlaylist, serverId, 'reportPlaybackProgress', progressEventName); } if (streamInfo && streamInfo.liveStreamId) { - if (new Date().getTime() - (streamInfo.lastMediaInfoQuery || 0) >= 600000) { getLiveStreamMediaInfo(player, streamInfo, self.currentMediaSource(player), streamInfo.liveStreamId, serverId); } @@ -3409,30 +3153,25 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } function getLiveStreamMediaInfo(player, streamInfo, mediaSource, liveStreamId, serverId) { - console.debug('getLiveStreamMediaInfo'); streamInfo.lastMediaInfoQuery = new Date().getTime(); - var apiClient = connectionManager.getApiClient(serverId); + const apiClient = window.connectionManager.getApiClient(serverId); if (!apiClient.isMinServerVersion('3.2.70.7')) { return; } - connectionManager.getApiClient(serverId).getLiveStreamMediaInfo(liveStreamId).then(function (info) { - + window.connectionManager.getApiClient(serverId).getLiveStreamMediaInfo(liveStreamId).then(function (info) { mediaSource.MediaStreams = info.MediaStreams; events.trigger(player, 'mediastreamschange'); - }, function () { - }); } self.onAppClose = function () { - - var player = this._currentPlayer; + const player = this._currentPlayer; // Try to report playback stopped before the app closes if (player && this.isPlaying(player)) { @@ -3441,135 +3180,110 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } }; - self.playbackStartTime = function (player) { - - player = player || this._currentPlayer; + self.playbackStartTime = function (player = this._currentPlayer) { if (player && !enableLocalPlaylistManagement(player) && !player.isLocalPlayer) { return player.playbackStartTime(); } - var streamInfo = getPlayerData(player).streamInfo; + const streamInfo = getPlayerData(player).streamInfo; return streamInfo ? streamInfo.playbackStartTimeTicks : null; }; - if (apphost.supports('remotecontrol')) { - - require(['serverNotifications'], function (serverNotifications) { + if (appHost.supports('remotecontrol')) { + import('serverNotifications').then(({ default: serverNotifications }) => { events.on(serverNotifications, 'ServerShuttingDown', self.setDefaultPlayerActive.bind(self)); events.on(serverNotifications, 'ServerRestarting', self.setDefaultPlayerActive.bind(self)); }); } } - PlaybackManager.prototype.getCurrentPlayer = function () { + getCurrentPlayer() { return this._currentPlayer; - }; + } - PlaybackManager.prototype.currentTime = function (player) { - - player = player || this._currentPlayer; + currentTime(player = this._currentPlayer) { if (player && !enableLocalPlaylistManagement(player) && !player.isLocalPlayer) { return player.currentTime(); } - return this.getCurrentTicks(player); - }; - - PlaybackManager.prototype.nextItem = function (player) { - - player = player || this._currentPlayer; + return this.getCurrentTicks(player) / 10000; + } + nextItem(player = this._currentPlayer) { if (player && !enableLocalPlaylistManagement(player)) { return player.nextItem(); } - var nextItem = this._playQueueManager.getNextItemInfo(); + const nextItem = this._playQueueManager.getNextItemInfo(); if (!nextItem || !nextItem.item) { return Promise.reject(); } - var apiClient = connectionManager.getApiClient(nextItem.item.ServerId); + const apiClient = window.connectionManager.getApiClient(nextItem.item.ServerId); return apiClient.getItem(apiClient.getCurrentUserId(), nextItem.item.Id); - }; - - PlaybackManager.prototype.canQueue = function (item) { + } + canQueue(item) { if (item.Type === 'MusicAlbum' || item.Type === 'MusicArtist' || item.Type === 'MusicGenre') { return this.canQueueMediaType('Audio'); } return this.canQueueMediaType(item.MediaType); - }; - - PlaybackManager.prototype.canQueueMediaType = function (mediaType) { + } + canQueueMediaType(mediaType) { if (this._currentPlayer) { return this._currentPlayer.canPlayMediaType(mediaType); } return false; - }; - - PlaybackManager.prototype.isMuted = function (player) { - - player = player || this._currentPlayer; + } + isMuted(player = this._currentPlayer) { if (player) { return player.isMuted(); } return false; - }; - - PlaybackManager.prototype.setMute = function (mute, player) { - - player = player || this._currentPlayer; + } + setMute(mute, player = this._currentPlayer) { if (player) { player.setMute(mute); } - }; + } - PlaybackManager.prototype.toggleMute = function (mute, player) { - - player = player || this._currentPlayer; + toggleMute(mute, player = this._currentPlayer) { if (player) { - if (player.toggleMute) { player.toggleMute(); } else { player.setMute(!player.isMuted()); } } - }; + } - PlaybackManager.prototype.toggleDisplayMirroring = function () { + toggleDisplayMirroring() { this.enableDisplayMirroring(!this.enableDisplayMirroring()); - }; - - PlaybackManager.prototype.enableDisplayMirroring = function (enabled) { + } + enableDisplayMirroring(enabled) { if (enabled != null) { - - var val = enabled ? '1' : '0'; + const val = enabled ? '1' : '0'; appSettings.set('displaymirror', val); return; } return (appSettings.get('displaymirror') || '') !== '0'; - }; + } - PlaybackManager.prototype.nextChapter = function (player) { + nextChapter(player = this._currentPlayer) { + const item = this.currentItem(player); - player = player || this._currentPlayer; - var item = this.currentItem(player); - - var ticks = this.getCurrentTicks(player); - - var nextChapter = (item.Chapters || []).filter(function (i) { + const ticks = this.getCurrentTicks(player); + const nextChapter = (item.Chapters || []).filter(function (i) { return i.StartPositionTicks > ticks; - })[0]; if (nextChapter) { @@ -3577,14 +3291,12 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } else { this.nextTrack(player); } - }; + } - PlaybackManager.prototype.previousChapter = function (player) { + previousChapter(player = this._currentPlayer) { + const item = this.currentItem(player); - player = player || this._currentPlayer; - var item = this.currentItem(player); - - var ticks = this.getCurrentTicks(player); + let ticks = this.getCurrentTicks(player); // Go back 10 seconds ticks -= 100000000; @@ -3594,8 +3306,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla ticks = Math.max(ticks, 0); } - var previousChapters = (item.Chapters || []).filter(function (i) { - + const previousChapters = (item.Chapters || []).filter(function (i) { return i.StartPositionTicks <= ticks; }); @@ -3604,68 +3315,55 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } else { this.previousTrack(player); } - }; - - PlaybackManager.prototype.fastForward = function (player) { - - player = player || this._currentPlayer; + } + fastForward(player = this._currentPlayer) { if (player.fastForward != null) { player.fastForward(userSettings.skipForwardLength()); return; } // Go back 15 seconds - var offsetTicks = userSettings.skipForwardLength() * 10000; + const offsetTicks = userSettings.skipForwardLength() * 10000; this.seekRelative(offsetTicks, player); - }; - - PlaybackManager.prototype.rewind = function (player) { - - player = player || this._currentPlayer; + } + rewind(player = this._currentPlayer) { if (player.rewind != null) { player.rewind(userSettings.skipBackLength()); return; } // Go back 15 seconds - var offsetTicks = 0 - (userSettings.skipBackLength() * 10000); + const offsetTicks = 0 - (userSettings.skipBackLength() * 10000); this.seekRelative(offsetTicks, player); - }; + } - PlaybackManager.prototype.seekPercent = function (percent, player) { - - player = player || this._currentPlayer; - - var ticks = this.duration(player) || 0; + seekPercent(percent, player = this._currentPlayer) { + let ticks = this.duration(player) || 0; percent /= 100; ticks *= percent; this.seek(parseInt(ticks), player); - }; + } - PlaybackManager.prototype.seekMs = function (ms, player) { - - player = player || this._currentPlayer; - - var ticks = ms * 10000; + seekMs(ms, player = this._currentPlayer) { + const ticks = ms * 10000; this.seek(ticks, player); - }; + } - PlaybackManager.prototype.playTrailers = function (item) { - - var player = this._currentPlayer; + playTrailers(item) { + const player = this._currentPlayer; if (player && player.playTrailers) { return player.playTrailers(item); } - var apiClient = connectionManager.getApiClient(item.ServerId); + const apiClient = window.connectionManager.getApiClient(item.ServerId); - var instance = this; + const instance = this; if (item.LocalTrailerCount) { return apiClient.getLocalTrailers(apiClient.getCurrentUserId(), item.Id).then(function (result) { @@ -3674,7 +3372,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla }); }); } else { - var remoteTrailers = item.RemoteTrailers || []; + const remoteTrailers = item.RemoteTrailers || []; if (!remoteTrailers.length) { return Promise.reject(); @@ -3692,20 +3390,17 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla }) }); } - }; + } - PlaybackManager.prototype.getSubtitleUrl = function (textStream, serverId) { + getSubtitleUrl(textStream, serverId) { + const apiClient = window.connectionManager.getApiClient(serverId); - var apiClient = connectionManager.getApiClient(serverId); - var textStreamUrl = !textStream.IsExternalUrl ? apiClient.getUrl(textStream.DeliveryUrl) : textStream.DeliveryUrl; - return textStreamUrl; - }; + return !textStream.IsExternalUrl ? apiClient.getUrl(textStream.DeliveryUrl) : textStream.DeliveryUrl; + } - PlaybackManager.prototype.stop = function (player) { + stop(player) { player = player || this._currentPlayer; - if (player) { - if (enableLocalPlaylistManagement(player)) { this._playNextAfterEnded = false; } @@ -3715,28 +3410,20 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } return Promise.resolve(); - }; - - PlaybackManager.prototype.getBufferedRanges = function (player) { - - player = player || this._currentPlayer; + } + getBufferedRanges(player = this._currentPlayer) { if (player) { - if (player.getBufferedRanges) { return player.getBufferedRanges(); } } return []; - }; - - PlaybackManager.prototype.playPause = function (player) { - - player = player || this._currentPlayer; + } + playPause(player = this._currentPlayer) { if (player) { - if (player.playPause) { return player.playPause(); } @@ -3747,121 +3434,105 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return this.pause(player); } } - }; - - PlaybackManager.prototype.paused = function (player) { - - player = player || this._currentPlayer; + } + paused(player = this._currentPlayer) { if (player) { return player.paused(); } - }; - - PlaybackManager.prototype.pause = function (player) { - player = player || this._currentPlayer; + } + pause(player = this._currentPlayer) { if (player) { player.pause(); } - }; - - PlaybackManager.prototype.unpause = function (player) { - player = player || this._currentPlayer; + } + unpause(player = this._currentPlayer) { if (player) { player.unpause(); } - }; + } - PlaybackManager.prototype.setPlaybackRate = function (value, player = this._currentPlayer) { + setPlaybackRate(value, player = this._currentPlayer) { if (player && player.setPlaybackRate) { player.setPlaybackRate(value); } - }; + } - PlaybackManager.prototype.getPlaybackRate = function (player = this._currentPlayer) { + getPlaybackRate(player = this._currentPlayer) { if (player && player.getPlaybackRate) { return player.getPlaybackRate(); } return null; - }; + } - PlaybackManager.prototype.instantMix = function (item, player) { - - player = player || this._currentPlayer; + instantMix(item, player = this._currentPlayer) { if (player && player.instantMix) { return player.instantMix(item); } - var apiClient = connectionManager.getApiClient(item.ServerId); + const apiClient = window.connectionManager.getApiClient(item.ServerId); - var options = {}; + const options = {}; options.UserId = apiClient.getCurrentUserId(); options.Limit = 200; - var instance = this; + const instance = this; apiClient.getInstantMixFromItem(item.Id, options).then(function (result) { instance.play({ items: result.Items }); }); - }; + } - PlaybackManager.prototype.shuffle = function (shuffleItem, player) { - - player = player || this._currentPlayer; + shuffle(shuffleItem, player = this._currentPlayer) { if (player && player.shuffle) { return player.shuffle(shuffleItem); } return this.play({ items: [shuffleItem], shuffle: true }); - }; + } - PlaybackManager.prototype.audioTracks = function (player) { - - player = player || this._currentPlayer; + audioTracks(player = this._currentPlayer) { if (player.audioTracks) { - var result = player.audioTracks(); + const result = player.audioTracks(); if (result) { return result; } } - var mediaSource = this.currentMediaSource(player); + const mediaSource = this.currentMediaSource(player); - var mediaStreams = (mediaSource || {}).MediaStreams || []; + const mediaStreams = (mediaSource || {}).MediaStreams || []; return mediaStreams.filter(function (s) { return s.Type === 'Audio'; }); - }; + } - PlaybackManager.prototype.subtitleTracks = function (player) { - - player = player || this._currentPlayer; + subtitleTracks(player = this._currentPlayer) { if (player.subtitleTracks) { - var result = player.subtitleTracks(); + const result = player.subtitleTracks(); if (result) { return result; } } - var mediaSource = this.currentMediaSource(player); + const mediaSource = this.currentMediaSource(player); - var mediaStreams = (mediaSource || {}).MediaStreams || []; + const mediaStreams = (mediaSource || {}).MediaStreams || []; return mediaStreams.filter(function (s) { return s.Type === 'Subtitle'; }); - }; - - PlaybackManager.prototype.getSupportedCommands = function (player) { + } + getSupportedCommands(player) { player = player || this._currentPlayer || { isLocalPlayer: true }; if (player.isLocalPlayer) { - var list = [ + const list = [ 'GoHome', 'GoToSettings', 'VolumeUp', @@ -3882,7 +3553,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla 'PlayTrailers' ]; - if (apphost.supports('fullscreenchange')) { + if (appHost.supports('fullscreenchange')) { list.push('ToggleFullscreen'); } @@ -3907,45 +3578,45 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return list; } - var info = this.getPlayerInfo(); + const info = this.getPlayerInfo(); return info ? info.supportedCommands : []; - }; + } - PlaybackManager.prototype.setRepeatMode = function (value, player = this._currentPlayer) { + setRepeatMode(value, player = this._currentPlayer) { if (player && !enableLocalPlaylistManagement(player)) { return player.setRepeatMode(value); } this._playQueueManager.setRepeatMode(value); events.trigger(player, 'repeatmodechange'); - }; + } - PlaybackManager.prototype.getRepeatMode = function (player = this._currentPlayer) { + getRepeatMode(player = this._currentPlayer) { if (player && !enableLocalPlaylistManagement(player)) { return player.getRepeatMode(); } return this._playQueueManager.getRepeatMode(); - }; + } - PlaybackManager.prototype.setQueueShuffleMode = function (value, player = this._currentPlayer) { + setQueueShuffleMode(value, player = this._currentPlayer) { if (player && !enableLocalPlaylistManagement(player)) { return player.setQueueShuffleMode(value); } this._playQueueManager.setShuffleMode(value); events.trigger(player, 'shufflequeuemodechange'); - }; + } - PlaybackManager.prototype.getQueueShuffleMode = function (player = this._currentPlayer) { + getQueueShuffleMode(player = this._currentPlayer) { if (player && !enableLocalPlaylistManagement(player)) { return player.getQueueShuffleMode(); } return this._playQueueManager.getShuffleMode(); - }; + } - PlaybackManager.prototype.toggleQueueShuffleMode = function (player = this._currentPlayer) { + toggleQueueShuffleMode(player = this._currentPlayer) { let currentvalue; if (player && !enableLocalPlaylistManagement(player)) { currentvalue = player.getQueueShuffleMode(); @@ -3963,80 +3634,73 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla this._playQueueManager.toggleShuffleMode(); } events.trigger(player, 'shufflequeuemodechange'); - }; + } - PlaybackManager.prototype.clearQueue = function (clearCurrentItem = false, player = this._currentPlayer) { + clearQueue(clearCurrentItem = false, player = this._currentPlayer) { if (player && !enableLocalPlaylistManagement(player)) { return player.clearQueue(clearCurrentItem); } this._playQueueManager.clearPlaylist(clearCurrentItem); events.trigger(player, 'playlistitemremove'); - }; - - PlaybackManager.prototype.trySetActiveDeviceName = function (name) { + } + trySetActiveDeviceName(name) { name = normalizeName(name); - var instance = this; + const instance = this; instance.getTargets().then(function (result) { - - var target = result.filter(function (p) { + const target = result.filter(function (p) { return normalizeName(p.name) === name; })[0]; if (target) { instance.trySetActivePlayer(target.playerName, target); } - }); - }; + } - PlaybackManager.prototype.displayContent = function (options, player) { - player = player || this._currentPlayer; + displayContent(options, player = this._currentPlayer) { if (player && player.displayContent) { player.displayContent(options); } - }; + } - PlaybackManager.prototype.beginPlayerUpdates = function (player) { + beginPlayerUpdates(player) { if (player.beginPlayerUpdates) { player.beginPlayerUpdates(); } - }; + } - PlaybackManager.prototype.endPlayerUpdates = function (player) { + endPlayerUpdates(player) { if (player.endPlayerUpdates) { player.endPlayerUpdates(); } - }; - - PlaybackManager.prototype.setDefaultPlayerActive = function () { + } + setDefaultPlayerActive() { this.setActivePlayer('localplayer'); - }; + } - PlaybackManager.prototype.removeActivePlayer = function (name) { - - var playerInfo = this.getPlayerInfo(); + removeActivePlayer(name) { + const playerInfo = this.getPlayerInfo(); if (playerInfo) { if (playerInfo.name === name) { this.setDefaultPlayerActive(); } } - }; + } - PlaybackManager.prototype.removeActiveTarget = function (id) { - - var playerInfo = this.getPlayerInfo(); + removeActiveTarget(id) { + const playerInfo = this.getPlayerInfo(); if (playerInfo) { if (playerInfo.id === id) { this.setDefaultPlayerActive(); } } - }; + } - PlaybackManager.prototype.sendCommand = function (cmd, player) { + sendCommand(cmd, player) { console.debug('MediaController received command: ' + cmd.Name); switch (cmd.Name) { case 'SetRepeatMode': @@ -4066,6 +3730,9 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla case 'SetAspectRatio': this.setAspectRatio(cmd.Arguments.AspectRatio, player); break; + case 'PlaybackRate': + this.setPlaybackRate(cmd.Arguments.PlaybackRate, player); + break; case 'SetBrightness': this.setBrightness(cmd.Arguments.Brightness, player); break; @@ -4076,8 +3743,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla this.setSubtitleStreamIndex(parseInt(cmd.Arguments.Index), player); break; case 'SetMaxStreamingBitrate': - // todo - //this.setMaxStreamingBitrate(parseInt(cmd.Arguments.Bitrate), player); + this.setMaxStreamingBitrate(parseInt(cmd.Arguments.Bitrate), player); break; case 'ToggleFullscreen': this.toggleFullscreen(player); @@ -4088,7 +3754,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } break; } - }; + } +} - return new PlaybackManager(); -}); +export default new PlaybackManager(); diff --git a/src/components/playback/playbackorientation.js b/src/components/playback/playbackorientation.js index 2078c6f6a8..b73377bade 100644 --- a/src/components/playback/playbackorientation.js +++ b/src/components/playback/playbackorientation.js @@ -14,15 +14,13 @@ function onOrientationChangeError(err) { } events.on(playbackManager, 'playbackstart', function (e, player, state) { - var isLocalVideo = player.isLocalPlayer && !player.isExternalPlayer && playbackManager.isPlayingVideo(player); if (isLocalVideo && layoutManager.mobile) { /* eslint-disable-next-line compat/compat */ - var lockOrientation = screen.lockOrientation || screen.mozLockOrientation || screen.msLockOrientation || (screen.orientation && screen.orientation.lock); + var lockOrientation = window.screen.lockOrientation || window.screen.mozLockOrientation || window.screen.msLockOrientation || (window.screen.orientation && window.screen.orientation.lock); if (lockOrientation) { - try { var promise = lockOrientation('landscape'); if (promise.then) { @@ -39,11 +37,9 @@ events.on(playbackManager, 'playbackstart', function (e, player, state) { }); events.on(playbackManager, 'playbackstop', function (e, playbackStopInfo) { - if (orientationLocked && !playbackStopInfo.nextMediaType) { - /* eslint-disable-next-line compat/compat */ - var unlockOrientation = screen.unlockOrientation || screen.mozUnlockOrientation || screen.msUnlockOrientation || (screen.orientation && screen.orientation.unlock); + var unlockOrientation = window.screen.unlockOrientation || window.screen.mozUnlockOrientation || window.screen.msUnlockOrientation || (window.screen.orientation && window.screen.orientation.unlock); if (unlockOrientation) { try { diff --git a/src/components/playback/playerSelectionMenu.js b/src/components/playback/playerSelectionMenu.js index 298ae0b07e..7799613400 100644 --- a/src/components/playback/playerSelectionMenu.js +++ b/src/components/playback/playerSelectionMenu.js @@ -8,7 +8,6 @@ import globalize from 'globalize'; import appHost from 'apphost'; function mirrorItem(info, player) { - var item = info.item; playbackManager.displayContent({ @@ -21,9 +20,7 @@ function mirrorItem(info, player) { } function mirrorIfEnabled(info) { - if (info && playbackManager.enableDisplayMirroring()) { - var getPlayerInfo = playbackManager.getPlayerInfo(); if (getPlayerInfo) { @@ -39,9 +36,7 @@ function emptyCallback() { } function getTargetSecondaryText(target) { - if (target.user) { - return target.user.Name; } @@ -49,7 +44,6 @@ function getTargetSecondaryText(target) { } function getIcon(target) { - var deviceType = target.deviceType; if (!deviceType && target.isLocalPlayer) { @@ -67,7 +61,6 @@ function getIcon(target) { } switch (deviceType) { - case 'smartphone': return 'smartphone'; case 'tablet': @@ -84,7 +77,6 @@ function getIcon(target) { } export function show(button) { - var currentPlayerInfo = playbackManager.getPlayerInfo(); if (currentPlayerInfo) { @@ -99,9 +91,7 @@ export function show(button) { loading.show(); playbackManager.getTargets().then(function (targets) { - var menuItems = targets.map(function (t) { - var name = t.name; if (t.appName && t.appName !== t.name) { @@ -115,11 +105,9 @@ export function show(button) { secondaryText: getTargetSecondaryText(t), icon: getIcon(t) }; - }); import('actionsheet').then(({default: actionsheet}) => { - loading.hide(); var menuOptions = { @@ -133,12 +121,11 @@ export function show(button) { // Unfortunately we can't allow the url to change or chromecast will throw a security error // Might be able to solve this in the future by moving the dialogs to hashbangs - if (!(!browser.chrome || appHost.supports('castmenuhashchange'))) { + if (!(!browser.chrome && !browser.edgeChromium || appHost.supports('castmenuhashchange'))) { menuOptions.enableHistory = false; } actionsheet.show(menuOptions).then(function (id) { - var target = targets.filter(function (t) { return t.id === id; })[0]; @@ -146,7 +133,6 @@ export function show(button) { playbackManager.trySetActivePlayer(target.playerName, target); mirrorIfEnabled(); - }, emptyCallback); }); }); @@ -164,11 +150,8 @@ function showActivePlayerMenu(playerInfo) { } function disconnectFromPlayer(currentDeviceName) { - if (playbackManager.getSupportedCommands().indexOf('EndSession') !== -1) { - import('dialog').then(({default: dialog}) => { - var menuItems = []; menuItems.push({ @@ -182,12 +165,10 @@ function disconnectFromPlayer(currentDeviceName) { dialog({ buttons: menuItems, - //positionTo: positionTo, text: globalize.translate('ConfirmEndPlayerSession', currentDeviceName) }).then(function (id) { switch (id) { - case 'yes': playbackManager.getCurrentPlayer().endSession(); playbackManager.setDefaultPlayerActive(); @@ -199,17 +180,13 @@ function disconnectFromPlayer(currentDeviceName) { break; } }); - }); - } else { - playbackManager.setDefaultPlayerActive(); } } function showActivePlayerMenuInternal(dialogHelper, playerInfo) { - var html = ''; var dialogOptions = { @@ -235,7 +212,6 @@ function showActivePlayerMenuInternal(dialogHelper, playerInfo) { html += '
'; if (playerInfo.supportedCommands.indexOf('DisplayContent') !== -1) { - html += '
diff --git a/src/components/playerstats/playerstats.js b/src/components/playerstats/playerstats.js index fce37c1507..28181756a3 100644 --- a/src/components/playerstats/playerstats.js +++ b/src/components/playerstats/playerstats.js @@ -1,18 +1,15 @@ import events from 'events'; import globalize from 'globalize'; import playbackManager from 'playbackManager'; -import connectionManager from 'connectionManager'; import syncPlayManager from 'syncPlayManager'; import playMethodHelper from 'playMethodHelper'; import layoutManager from 'layoutManager'; -import serverNotifications from 'serverNotifications'; import 'paper-icon-button-light'; import 'css!./playerstats'; /* eslint-disable indent */ function init(instance) { - const parent = document.createElement('div'); parent.classList.add('playerStats'); @@ -51,9 +48,7 @@ import 'css!./playerstats'; } function renderStats(elem, categories) { - elem.querySelector('.playerStats-stats').innerHTML = categories.map(function (category) { - let categoryHtml = ''; const stats = category.stats; @@ -73,7 +68,6 @@ import 'css!./playerstats'; } for (let i = 0, length = stats.length; i < length; i++) { - categoryHtml += '
'; const stat = stats[i]; @@ -90,36 +84,31 @@ import 'css!./playerstats'; } return categoryHtml; - }).join(''); } function getSession(instance, player) { - const now = new Date().getTime(); if ((now - (instance.lastSessionTime || 0)) < 10000) { return Promise.resolve(instance.lastSession); } - const apiClient = connectionManager.getApiClient(playbackManager.currentItem(player).ServerId); + const apiClient = window.connectionManager.getApiClient(playbackManager.currentItem(player).ServerId); return apiClient.getSessions({ deviceId: apiClient.deviceId() }).then(function (sessions) { - instance.lastSession = sessions[0] || {}; instance.lastSessionTime = new Date().getTime(); return Promise.resolve(instance.lastSession); - }, function () { return Promise.resolve({}); }); } function translateReason(reason) { - return globalize.translate('' + reason); } @@ -132,7 +121,6 @@ import 'css!./playerstats'; let audioChannels; if (session.TranscodingInfo) { - videoCodec = session.TranscodingInfo.VideoCodec; audioCodec = session.TranscodingInfo.AudioCodec; totalBitrate = session.TranscodingInfo.Bitrate; @@ -140,7 +128,6 @@ import 'css!./playerstats'; } if (videoCodec) { - sessionStats.push({ label: globalize.translate('LabelVideoCodec'), value: session.TranscodingInfo.IsVideoDirect ? (videoCodec.toUpperCase() + ' (direct)') : videoCodec.toUpperCase() @@ -148,45 +135,39 @@ import 'css!./playerstats'; } if (audioCodec) { - sessionStats.push({ label: globalize.translate('LabelAudioCodec'), value: session.TranscodingInfo.IsAudioDirect ? (audioCodec.toUpperCase() + ' (direct)') : audioCodec.toUpperCase() }); } - //if (audioChannels) { - - // sessionStats.push({ - // label: 'Audio channels:', - // value: audioChannels - // }); - //} + if (audioChannels) { + sessionStats.push({ + label: globalize.translate('LabelAudioChannels'), + value: audioChannels + }); + } if (displayPlayMethod === 'Transcode') { if (totalBitrate) { - sessionStats.push({ label: globalize.translate('LabelBitrate'), value: getDisplayBitrate(totalBitrate) }); } if (session.TranscodingInfo.CompletionPercentage) { - sessionStats.push({ label: globalize.translate('LabelTranscodingProgress'), value: session.TranscodingInfo.CompletionPercentage.toFixed(1) + '%' }); } if (session.TranscodingInfo.Framerate) { - sessionStats.push({ label: globalize.translate('LabelTranscodingFramerate'), value: session.TranscodingInfo.Framerate + ' fps' }); } if (session.TranscodingInfo.TranscodeReasons && session.TranscodingInfo.TranscodeReasons.length) { - sessionStats.push({ label: globalize.translate('LabelReasonForTranscoding'), value: session.TranscodingInfo.TranscodeReasons.map(translateReason).join('
') @@ -198,7 +179,6 @@ import 'css!./playerstats'; } function getDisplayBitrate(bitrate) { - if (bitrate > 1000000) { return (bitrate / 1000000).toFixed(1) + ' Mbps'; } else { @@ -217,7 +197,6 @@ import 'css!./playerstats'; } function getMediaSourceStats(session, player, displayPlayMethod) { - const sessionStats = []; const mediaSource = playbackManager.currentMediaSource(player) || {}; @@ -239,7 +218,6 @@ import 'css!./playerstats'; } if (totalBitrate) { - sessionStats.push({ label: globalize.translate('LabelBitrate'), value: getDisplayBitrate(totalBitrate) @@ -248,18 +226,14 @@ import 'css!./playerstats'; const mediaStreams = mediaSource.MediaStreams || []; const videoStream = mediaStreams.filter(function (s) { - return s.Type === 'Video'; - })[0] || {}; const videoCodec = videoStream.Codec; const audioStreamIndex = playbackManager.getAudioStreamIndex(player); const audioStream = playbackManager.audioTracks(player).filter(function (s) { - return s.Type === 'Audio' && s.Index === audioStreamIndex; - })[0] || {}; const audioCodec = audioStream.Codec; @@ -360,12 +334,10 @@ import 'css!./playerstats'; } function getStats(instance, player) { - const statsPromise = player.getStats ? player.getStats() : Promise.resolve({}); const sessionPromise = getSession(instance, player); return Promise.all([statsPromise, sessionPromise]).then(function (responses) { - const playerStatsResult = responses[0]; const playerStats = playerStatsResult.categories || []; const session = responses[1]; @@ -392,7 +364,6 @@ import 'css!./playerstats'; categories.push(baseCategory); for (let i = 0, length = playerStats.length; i < length; i++) { - const category = playerStats[i]; if (category.type === 'audio') { category.name = 'Audio Info'; @@ -403,7 +374,6 @@ import 'css!./playerstats'; } if (session.TranscodingInfo) { - categories.push({ stats: getTranscodingStats(session, player, displayPlayMethod), name: displayPlayMethod === 'Transcode' ? 'Transcoding Info' : 'Direct Stream Info' @@ -415,7 +385,7 @@ import 'css!./playerstats'; name: 'Original Media Info' }); - var apiClient = connectionManager.getApiClient(playbackManager.currentItem(player).ServerId); + var apiClient = window.connectionManager.getApiClient(playbackManager.currentItem(player).ServerId); if (syncPlayManager.isSyncPlayEnabled() && apiClient.isMinServerVersion('10.6.0')) { categories.push({ stats: getSyncPlayStats(), @@ -428,7 +398,6 @@ import 'css!./playerstats'; } function renderPlayerStats(instance, player) { - const now = new Date().getTime(); if ((now - (instance.lastRender || 0)) < 700) { @@ -438,7 +407,6 @@ import 'css!./playerstats'; instance.lastRender = now; getStats(instance, player).then(function (stats) { - const elem = instance.element; if (!elem) { return; @@ -449,7 +417,6 @@ import 'css!./playerstats'; } function bindEvents(instance, player) { - const localOnTimeUpdate = function () { renderPlayerStats(instance, player); }; @@ -459,7 +426,6 @@ import 'css!./playerstats'; } function unbindEvents(instance, player) { - const localOnTimeUpdate = instance.onTimeUpdate; if (localOnTimeUpdate) { @@ -469,7 +435,6 @@ import 'css!./playerstats'; class PlayerStats { constructor(options) { - this.options = options; init(this); @@ -478,7 +443,6 @@ class PlayerStats { } enabled(enabled) { - if (enabled == null) { return this._enabled; } @@ -504,11 +468,9 @@ class PlayerStats { } destroy() { - const options = this.options; if (options) { - this.options = null; unbindEvents(this, options.player); } diff --git a/src/components/playlisteditor/playlisteditor.js b/src/components/playlisteditor/playlisteditor.js index 7b1e915e1f..dda9436a29 100644 --- a/src/components/playlisteditor/playlisteditor.js +++ b/src/components/playlisteditor/playlisteditor.js @@ -3,7 +3,6 @@ import dialogHelper from 'dialogHelper'; import loading from 'loading'; import layoutManager from 'layoutManager'; import playbackManager from 'playbackManager'; -import connectionManager from 'connectionManager'; import * as userSettings from 'userSettings'; import appRouter from 'appRouter'; import globalize from 'globalize'; @@ -22,7 +21,7 @@ import 'emby-button'; const panel = dom.parentWithClass(this, 'dialog'); const playlistId = panel.querySelector('#selectPlaylistToAddTo').value; - const apiClient = connectionManager.getApiClient(currentServerId); + const apiClient = window.connectionManager.getApiClient(currentServerId); if (playlistId) { userSettings.set('playlisteditor-lastplaylistid', playlistId); @@ -113,7 +112,7 @@ import 'emby-button'; EnableTotalRecordCount: false }; - const apiClient = connectionManager.getApiClient(currentServerId); + const apiClient = window.connectionManager.getApiClient(currentServerId); apiClient.getItems(apiClient.getCurrentUserId(), options).then(result => { let html = ''; @@ -210,7 +209,7 @@ import 'emby-button'; } function centerFocus(elem, horiz, on) { - import('scrollHelper').then(scrollHelper => { + import('scrollHelper').then((scrollHelper) => { const fn = on ? 'on' : 'off'; scrollHelper.centerFocus[fn](elem, horiz); }); diff --git a/src/components/playmenu.js b/src/components/playmenu.js index 50c2a7b31f..c537993ef6 100644 --- a/src/components/playmenu.js +++ b/src/components/playmenu.js @@ -4,7 +4,6 @@ import playbackManager from 'playbackManager'; import globalize from 'globalize'; export function show(options) { - var item = options.item; var resumePositionTicks = item.UserData ? item.UserData.PlaybackPositionTicks : null; @@ -38,7 +37,6 @@ export function show(options) { }).then(function (id) { switch (id) { - case 'play': playbackManager.play({ ids: [playableItemId], diff --git a/src/components/pluginManager.js b/src/components/pluginManager.js index fd35d344bf..55a5c230ff 100644 --- a/src/components/pluginManager.js +++ b/src/components/pluginManager.js @@ -1,50 +1,47 @@ -define(['events', 'globalize'], function (events, globalize) { - 'use strict'; +import events from 'events'; +import globalize from 'globalize'; +/* eslint-disable indent */ // TODO: replace with each plugin version var cacheParam = new Date().getTime(); - function loadStrings(plugin) { - var strings = plugin.getTranslations ? plugin.getTranslations() : []; - return globalize.loadStrings({ - name: plugin.id || plugin.packageName, - strings: strings - }); - } + class PluginManager { + pluginsList = []; - function definePluginRoute(pluginManager, route, plugin) { + get plugins() { + return this.pluginsList; + } - route.contentPath = pluginManager.mapPath(plugin, route.path); - route.path = pluginManager.mapRoute(plugin, route); + #loadStrings(plugin) { + var strings = plugin.getTranslations ? plugin.getTranslations() : []; + return globalize.loadStrings({ + name: plugin.id || plugin.packageName, + strings: strings + }); + } - Emby.App.defineRoute(route, plugin.id); - } + #definePluginRoute(route, plugin) { + route.contentPath = this.mapPath(plugin, route.path); + route.path = this.#mapRoute(plugin, route); - function PluginManager() { + Emby.App.defineRoute(route, plugin.id); + } - this.pluginsList = []; - } - - PluginManager.prototype.loadPlugin = function(pluginSpec) { - - var instance = this; - - function registerPlugin(plugin) { - instance.register(plugin); + #registerPlugin(plugin) { + this.#register(plugin); if (plugin.getRoutes) { - plugin.getRoutes().forEach(function (route) { - definePluginRoute(instance, route, plugin); + plugin.getRoutes().forEach((route) => { + this.#definePluginRoute(route, plugin); }); } if (plugin.type === 'skin') { - // translations won't be loaded for skins until needed return Promise.resolve(plugin); } else { return new Promise((resolve, reject) => { - loadStrings(plugin) + this.#loadStrings(plugin) .then(function () { resolve(plugin); }) @@ -53,107 +50,102 @@ define(['events', 'globalize'], function (events, globalize) { } } - if (typeof pluginSpec === 'string') { - console.debug('Loading plugin (via deprecated requirejs method): ' + pluginSpec); + loadPlugin(pluginSpec) { + if (typeof pluginSpec === 'string') { + console.debug('Loading plugin (via deprecated requirejs method): ' + pluginSpec); - return new Promise(function (resolve, reject) { - require([pluginSpec], (pluginFactory) => { - var plugin = pluginFactory.default ? new pluginFactory.default() : new pluginFactory(); + return new Promise((resolve, reject) => { + require([pluginSpec], (pluginFactory) => { + var plugin = pluginFactory.default ? new pluginFactory.default() : new pluginFactory(); - // See if it's already installed - var existing = instance.pluginsList.filter(function (p) { - return p.id === plugin.id; - })[0]; + // See if it's already installed + var existing = this.pluginsList.filter(function (p) { + return p.id === plugin.id; + })[0]; - if (existing) { - resolve(pluginSpec); - } + if (existing) { + resolve(pluginSpec); + } - plugin.installUrl = pluginSpec; + plugin.installUrl = pluginSpec; - var separatorIndex = Math.max(pluginSpec.lastIndexOf('/'), pluginSpec.lastIndexOf('\\')); - plugin.baseUrl = pluginSpec.substring(0, separatorIndex); + var separatorIndex = Math.max(pluginSpec.lastIndexOf('/'), pluginSpec.lastIndexOf('\\')); + plugin.baseUrl = pluginSpec.substring(0, separatorIndex); - var paths = {}; - paths[plugin.id] = plugin.baseUrl; + var paths = {}; + paths[plugin.id] = plugin.baseUrl; - requirejs.config({ - waitSeconds: 0, - paths: paths + requirejs.config({ + waitSeconds: 0, + paths: paths + }); + + this.#registerPlugin(plugin).then(resolve).catch(reject); }); - - registerPlugin(plugin).then(resolve).catch(reject); }); + } else if (pluginSpec.then) { + return pluginSpec.then(pluginBuilder => { + return pluginBuilder(); + }).then((plugin) => { + console.debug(`Plugin loaded: ${plugin.id}`); + return this.#registerPlugin(plugin); + }); + } else { + const err = new TypeError('Plugins have to be a Promise that resolves to a plugin builder function or a RequireJS url (deprecated)'); + console.error(err); + return Promise.reject(err); + } + } + + // In lieu of automatic discovery, plugins will register dynamic objects + // Each object will have the following properties: + // name + // type (skin, screensaver, etc) + #register(obj) { + this.pluginsList.push(obj); + events.trigger(this, 'registered', [obj]); + } + + ofType(type) { + return this.pluginsList.filter((o) => { + return o.type === type; }); - } else if (pluginSpec.then) { - return pluginSpec.then(pluginBuilder => { - return pluginBuilder(); - }).then(plugin => { - console.debug(`Plugin loaded: ${plugin.id}`); - return registerPlugin(plugin); - }); - } else { - const err = new Error('Plugins have to be a Promise that resolves to a plugin builder function or a requirejs urls (deprecated)'); - console.error(err); - return Promise.reject(err); - } - }; - - // In lieu of automatic discovery, plugins will register dynamic objects - // Each object will have the following properties: - // name - // type (skin, screensaver, etc) - PluginManager.prototype.register = function (obj) { - - this.pluginsList.push(obj); - events.trigger(this, 'registered', [obj]); - }; - - PluginManager.prototype.ofType = function (type) { - - return this.pluginsList.filter(function (o) { - return o.type === type; - }); - }; - - PluginManager.prototype.plugins = function () { - return this.pluginsList; - }; - - PluginManager.prototype.mapRoute = function (plugin, route) { - - if (typeof plugin === 'string') { - plugin = this.pluginsList.filter(function (p) { - return (p.id || p.packageName) === plugin; - })[0]; } - route = route.path || route; + #mapRoute(plugin, route) { + if (typeof plugin === 'string') { + plugin = this.pluginsList.filter((p) => { + return (p.id || p.packageName) === plugin; + })[0]; + } - if (route.toLowerCase().indexOf('http') === 0) { - return route; + route = route.path || route; + + if (route.toLowerCase().startsWith('http')) { + return route; + } + + return '/plugins/' + plugin.id + '/' + route; } - return '/plugins/' + plugin.id + '/' + route; - }; + mapPath(plugin, path, addCacheParam) { + if (typeof plugin === 'string') { + plugin = this.pluginsList.filter((p) => { + return (p.id || p.packageName) === plugin; + })[0]; + } - PluginManager.prototype.mapPath = function (plugin, path, addCacheParam) { + var url = plugin.baseUrl + '/' + path; - if (typeof plugin === 'string') { - plugin = this.pluginsList.filter(function (p) { - return (p.id || p.packageName) === plugin; - })[0]; + if (addCacheParam) { + url += url.includes('?') ? '&' : '?'; + url += 'v=' + cacheParam; + } + + return url; } + } - var url = plugin.baseUrl + '/' + path; +/* eslint-enable indent */ - if (addCacheParam) { - url += url.indexOf('?') === -1 ? '?' : '&'; - url += 'v=' + cacheParam; - } - - return url; - }; - - return new PluginManager(); -}); +export default new PluginManager(); diff --git a/src/components/prompt/prompt.js b/src/components/prompt/prompt.js index f3bf72f8f2..868f1d865c 100644 --- a/src/components/prompt/prompt.js +++ b/src/components/prompt/prompt.js @@ -12,7 +12,6 @@ import 'formDialogStyle'; /* eslint-disable indent */ export default (() => { - function replaceAll(str, find, replace) { return str.split(find).join(replace); } @@ -68,7 +67,6 @@ export default (() => { let submitValue; dlg.querySelector('form').addEventListener('submit', e => { - submitValue = dlg.querySelector('#txtInput').value; e.preventDefault(); e.stopPropagation(); diff --git a/src/components/qualityOptions.js b/src/components/qualityOptions.js index 221e13d4ef..63d9557c7b 100644 --- a/src/components/qualityOptions.js +++ b/src/components/qualityOptions.js @@ -1,170 +1,158 @@ -define(['globalize'], function (globalize) { - 'use strict'; +import globalize from 'globalize'; - function getVideoQualityOptions(options) { +export function getVideoQualityOptions(options) { + var maxStreamingBitrate = options.currentMaxBitrate; + var videoWidth = options.videoWidth; + var videoHeight = options.videoHeight; - var maxStreamingBitrate = options.currentMaxBitrate; - var videoWidth = options.videoWidth; - var videoHeight = options.videoHeight; - - // If the aspect ratio is less than 16/9 (1.77), set the width as if it were pillarboxed. - // 4:3 1440x1080 -> 1920x1080 - if (videoWidth / videoHeight < 16 / 9) { - videoWidth = videoHeight * (16 / 9); - } - - var maxAllowedWidth = videoWidth || 4096; - //var maxAllowedHeight = videoHeight || 2304; - - var qualityOptions = []; - - if (maxAllowedWidth >= 3800) { - qualityOptions.push({ name: '4K - 120 Mbps', maxHeight: 2160, bitrate: 120000000 }); - qualityOptions.push({ name: '4K - 100 Mbps', maxHeight: 2160, bitrate: 100000000 }); - qualityOptions.push({ name: '4K - 80 Mbps', maxHeight: 2160, bitrate: 80000000 }); - } - - // Some 1080- videos are reported as 1912? - if (maxAllowedWidth >= 1900) { - - qualityOptions.push({ name: '1080p - 60 Mbps', maxHeight: 1080, bitrate: 60000000 }); - qualityOptions.push({ name: '1080p - 50 Mbps', maxHeight: 1080, bitrate: 50000000 }); - qualityOptions.push({ name: '1080p - 40 Mbps', maxHeight: 1080, bitrate: 40000000 }); - qualityOptions.push({ name: '1080p - 30 Mbps', maxHeight: 1080, bitrate: 30000000 }); - qualityOptions.push({ name: '1080p - 25 Mbps', maxHeight: 1080, bitrate: 25000000 }); - qualityOptions.push({ name: '1080p - 20 Mbps', maxHeight: 1080, bitrate: 20000000 }); - qualityOptions.push({ name: '1080p - 15 Mbps', maxHeight: 1080, bitrate: 15000000 }); - qualityOptions.push({ name: '1080p - 10 Mbps', maxHeight: 1080, bitrate: 10000001 }); - qualityOptions.push({ name: '1080p - 8 Mbps', maxHeight: 1080, bitrate: 8000001 }); - qualityOptions.push({ name: '1080p - 6 Mbps', maxHeight: 1080, bitrate: 6000001 }); - qualityOptions.push({ name: '1080p - 5 Mbps', maxHeight: 1080, bitrate: 5000001 }); - qualityOptions.push({ name: '1080p - 4 Mbps', maxHeight: 1080, bitrate: 4000002 }); - - } else if (maxAllowedWidth >= 1260) { - qualityOptions.push({ name: '720p - 10 Mbps', maxHeight: 720, bitrate: 10000000 }); - qualityOptions.push({ name: '720p - 8 Mbps', maxHeight: 720, bitrate: 8000000 }); - qualityOptions.push({ name: '720p - 6 Mbps', maxHeight: 720, bitrate: 6000000 }); - qualityOptions.push({ name: '720p - 5 Mbps', maxHeight: 720, bitrate: 5000000 }); - - } else if (maxAllowedWidth >= 620) { - qualityOptions.push({ name: '480p - 4 Mbps', maxHeight: 480, bitrate: 4000001 }); - qualityOptions.push({ name: '480p - 3 Mbps', maxHeight: 480, bitrate: 3000001 }); - qualityOptions.push({ name: '480p - 2.5 Mbps', maxHeight: 480, bitrate: 2500000 }); - qualityOptions.push({ name: '480p - 2 Mbps', maxHeight: 480, bitrate: 2000001 }); - qualityOptions.push({ name: '480p - 1.5 Mbps', maxHeight: 480, bitrate: 1500001 }); - } - - if (maxAllowedWidth >= 1260) { - qualityOptions.push({ name: '720p - 4 Mbps', maxHeight: 720, bitrate: 4000000 }); - qualityOptions.push({ name: '720p - 3 Mbps', maxHeight: 720, bitrate: 3000000 }); - qualityOptions.push({ name: '720p - 2 Mbps', maxHeight: 720, bitrate: 2000000 }); - - // The extra 1 is because they're keyed off the bitrate value - qualityOptions.push({ name: '720p - 1.5 Mbps', maxHeight: 720, bitrate: 1500000 }); - qualityOptions.push({ name: '720p - 1 Mbps', maxHeight: 720, bitrate: 1000001 }); - } - - qualityOptions.push({ name: '480p - 1 Mbps', maxHeight: 480, bitrate: 1000000 }); - qualityOptions.push({ name: '480p - 720 kbps', maxHeight: 480, bitrate: 720000 }); - qualityOptions.push({ name: '480p - 420 kbps', maxHeight: 480, bitrate: 420000 }); - qualityOptions.push({ name: '360p', maxHeight: 360, bitrate: 400000 }); - qualityOptions.push({ name: '240p', maxHeight: 240, bitrate: 320000 }); - qualityOptions.push({ name: '144p', maxHeight: 144, bitrate: 192000 }); - - var autoQualityOption = { - name: globalize.translate('Auto'), - bitrate: 0, - selected: options.isAutomaticBitrateEnabled - }; - - if (options.enableAuto) { - qualityOptions.push(autoQualityOption); - } - - if (maxStreamingBitrate) { - var selectedIndex = -1; - for (var i = 0, length = qualityOptions.length; i < length; i++) { - - var option = qualityOptions[i]; - - if (selectedIndex === -1 && option.bitrate <= maxStreamingBitrate) { - selectedIndex = i; - } - } - - if (selectedIndex === -1) { - - selectedIndex = qualityOptions.length - 1; - } - - var currentQualityOption = qualityOptions[selectedIndex]; - - if (!options.isAutomaticBitrateEnabled) { - currentQualityOption.selected = true; - } else { - autoQualityOption.autoText = currentQualityOption.name; - } - } - - return qualityOptions; + // If the aspect ratio is less than 16/9 (1.77), set the width as if it were pillarboxed. + // 4:3 1440x1080 -> 1920x1080 + if (videoWidth / videoHeight < 16 / 9) { + videoWidth = videoHeight * (16 / 9); } - function getAudioQualityOptions(options) { + var maxAllowedWidth = videoWidth || 4096; - var maxStreamingBitrate = options.currentMaxBitrate; + var qualityOptions = []; - var qualityOptions = []; - - qualityOptions.push({ name: '2 Mbps', bitrate: 2000000 }); - qualityOptions.push({ name: '1.5 Mbps', bitrate: 1500000 }); - qualityOptions.push({ name: '1 Mbps', bitrate: 1000000 }); - qualityOptions.push({ name: '320 kbps', bitrate: 320000 }); - qualityOptions.push({ name: '256 kbps', bitrate: 256000 }); - qualityOptions.push({ name: '192 kbps', bitrate: 192000 }); - qualityOptions.push({ name: '128 kbps', bitrate: 128000 }); - qualityOptions.push({ name: '96 kbps', bitrate: 96000 }); - qualityOptions.push({ name: '64 kbps', bitrate: 64000 }); - - var autoQualityOption = { - name: globalize.translate('Auto'), - bitrate: 0, - selected: options.isAutomaticBitrateEnabled - }; - - if (options.enableAuto) { - qualityOptions.push(autoQualityOption); - } - - if (maxStreamingBitrate) { - var selectedIndex = -1; - for (var i = 0, length = qualityOptions.length; i < length; i++) { - - var option = qualityOptions[i]; - - if (selectedIndex === -1 && option.bitrate <= maxStreamingBitrate) { - selectedIndex = i; - } - } - - if (selectedIndex === -1) { - - selectedIndex = qualityOptions.length - 1; - } - - var currentQualityOption = qualityOptions[selectedIndex]; - - if (!options.isAutomaticBitrateEnabled) { - currentQualityOption.selected = true; - } else { - autoQualityOption.autoText = currentQualityOption.name; - } - } - - return qualityOptions; + if (maxAllowedWidth >= 3800) { + qualityOptions.push({ name: '4K - 120 Mbps', maxHeight: 2160, bitrate: 120000000 }); + qualityOptions.push({ name: '4K - 100 Mbps', maxHeight: 2160, bitrate: 100000000 }); + qualityOptions.push({ name: '4K - 80 Mbps', maxHeight: 2160, bitrate: 80000000 }); } - return { - getVideoQualityOptions: getVideoQualityOptions, - getAudioQualityOptions: getAudioQualityOptions + // Some 1080- videos are reported as 1912? + if (maxAllowedWidth >= 1900) { + qualityOptions.push({ name: '1080p - 60 Mbps', maxHeight: 1080, bitrate: 60000000 }); + qualityOptions.push({ name: '1080p - 50 Mbps', maxHeight: 1080, bitrate: 50000000 }); + qualityOptions.push({ name: '1080p - 40 Mbps', maxHeight: 1080, bitrate: 40000000 }); + qualityOptions.push({ name: '1080p - 30 Mbps', maxHeight: 1080, bitrate: 30000000 }); + qualityOptions.push({ name: '1080p - 25 Mbps', maxHeight: 1080, bitrate: 25000000 }); + qualityOptions.push({ name: '1080p - 20 Mbps', maxHeight: 1080, bitrate: 20000000 }); + qualityOptions.push({ name: '1080p - 15 Mbps', maxHeight: 1080, bitrate: 15000000 }); + qualityOptions.push({ name: '1080p - 10 Mbps', maxHeight: 1080, bitrate: 10000001 }); + qualityOptions.push({ name: '1080p - 8 Mbps', maxHeight: 1080, bitrate: 8000001 }); + qualityOptions.push({ name: '1080p - 6 Mbps', maxHeight: 1080, bitrate: 6000001 }); + qualityOptions.push({ name: '1080p - 5 Mbps', maxHeight: 1080, bitrate: 5000001 }); + qualityOptions.push({ name: '1080p - 4 Mbps', maxHeight: 1080, bitrate: 4000002 }); + } else if (maxAllowedWidth >= 1260) { + qualityOptions.push({ name: '720p - 10 Mbps', maxHeight: 720, bitrate: 10000000 }); + qualityOptions.push({ name: '720p - 8 Mbps', maxHeight: 720, bitrate: 8000000 }); + qualityOptions.push({ name: '720p - 6 Mbps', maxHeight: 720, bitrate: 6000000 }); + qualityOptions.push({ name: '720p - 5 Mbps', maxHeight: 720, bitrate: 5000000 }); + } else if (maxAllowedWidth >= 620) { + qualityOptions.push({ name: '480p - 4 Mbps', maxHeight: 480, bitrate: 4000001 }); + qualityOptions.push({ name: '480p - 3 Mbps', maxHeight: 480, bitrate: 3000001 }); + qualityOptions.push({ name: '480p - 2.5 Mbps', maxHeight: 480, bitrate: 2500000 }); + qualityOptions.push({ name: '480p - 2 Mbps', maxHeight: 480, bitrate: 2000001 }); + qualityOptions.push({ name: '480p - 1.5 Mbps', maxHeight: 480, bitrate: 1500001 }); + } + + if (maxAllowedWidth >= 1260) { + qualityOptions.push({ name: '720p - 4 Mbps', maxHeight: 720, bitrate: 4000000 }); + qualityOptions.push({ name: '720p - 3 Mbps', maxHeight: 720, bitrate: 3000000 }); + qualityOptions.push({ name: '720p - 2 Mbps', maxHeight: 720, bitrate: 2000000 }); + + // The extra 1 is because they're keyed off the bitrate value + qualityOptions.push({ name: '720p - 1.5 Mbps', maxHeight: 720, bitrate: 1500000 }); + qualityOptions.push({ name: '720p - 1 Mbps', maxHeight: 720, bitrate: 1000001 }); + } + + qualityOptions.push({ name: '480p - 1 Mbps', maxHeight: 480, bitrate: 1000000 }); + qualityOptions.push({ name: '480p - 720 kbps', maxHeight: 480, bitrate: 720000 }); + qualityOptions.push({ name: '480p - 420 kbps', maxHeight: 480, bitrate: 420000 }); + qualityOptions.push({ name: '360p', maxHeight: 360, bitrate: 400000 }); + qualityOptions.push({ name: '240p', maxHeight: 240, bitrate: 320000 }); + qualityOptions.push({ name: '144p', maxHeight: 144, bitrate: 192000 }); + + var autoQualityOption = { + name: globalize.translate('Auto'), + bitrate: 0, + selected: options.isAutomaticBitrateEnabled }; -}); + + if (options.enableAuto) { + qualityOptions.push(autoQualityOption); + } + + if (maxStreamingBitrate) { + var selectedIndex = -1; + for (var i = 0, length = qualityOptions.length; i < length; i++) { + var option = qualityOptions[i]; + + if (selectedIndex === -1 && option.bitrate <= maxStreamingBitrate) { + selectedIndex = i; + } + } + + if (selectedIndex === -1) { + selectedIndex = qualityOptions.length - 1; + } + + var currentQualityOption = qualityOptions[selectedIndex]; + + if (!options.isAutomaticBitrateEnabled) { + currentQualityOption.selected = true; + } else { + autoQualityOption.autoText = currentQualityOption.name; + } + } + + return qualityOptions; +} + +export function getAudioQualityOptions(options) { + var maxStreamingBitrate = options.currentMaxBitrate; + + var qualityOptions = []; + + qualityOptions.push({ name: '2 Mbps', bitrate: 2000000 }); + qualityOptions.push({ name: '1.5 Mbps', bitrate: 1500000 }); + qualityOptions.push({ name: '1 Mbps', bitrate: 1000000 }); + qualityOptions.push({ name: '320 kbps', bitrate: 320000 }); + qualityOptions.push({ name: '256 kbps', bitrate: 256000 }); + qualityOptions.push({ name: '192 kbps', bitrate: 192000 }); + qualityOptions.push({ name: '128 kbps', bitrate: 128000 }); + qualityOptions.push({ name: '96 kbps', bitrate: 96000 }); + qualityOptions.push({ name: '64 kbps', bitrate: 64000 }); + + var autoQualityOption = { + name: globalize.translate('Auto'), + bitrate: 0, + selected: options.isAutomaticBitrateEnabled + }; + + if (options.enableAuto) { + qualityOptions.push(autoQualityOption); + } + + if (maxStreamingBitrate) { + var selectedIndex = -1; + for (var i = 0, length = qualityOptions.length; i < length; i++) { + var option = qualityOptions[i]; + + if (selectedIndex === -1 && option.bitrate <= maxStreamingBitrate) { + selectedIndex = i; + } + } + + if (selectedIndex === -1) { + selectedIndex = qualityOptions.length - 1; + } + + var currentQualityOption = qualityOptions[selectedIndex]; + + if (!options.isAutomaticBitrateEnabled) { + currentQualityOption.selected = true; + } else { + autoQualityOption.autoText = currentQualityOption.name; + } + } + + return qualityOptions; +} + +export default { + getVideoQualityOptions, + getAudioQualityOptions +}; diff --git a/src/components/recordingcreator/recordingbutton.js b/src/components/recordingcreator/recordingbutton.js index c4bfc43010..dc7da836da 100644 --- a/src/components/recordingcreator/recordingbutton.js +++ b/src/components/recordingcreator/recordingbutton.js @@ -1,37 +1,39 @@ -define(['globalize', 'connectionManager', 'require', 'loading', 'apphost', 'dom', 'recordingHelper', 'events', 'paper-icon-button-light', 'emby-button', 'css!./recordingfields'], function (globalize, connectionManager, require, loading, appHost, dom, recordingHelper, events) { - 'use strict'; +import dom from 'dom'; +import recordingHelper from 'recordingHelper'; +import 'paper-icon-button-light'; +import 'emby-button'; +import 'css!./recordingfields'; - function onRecordingButtonClick(e) { +function onRecordingButtonClick(e) { + const item = this.item; - var item = this.item; + if (item) { + const serverId = item.ServerId; + const programId = item.Id; + const timerId = item.TimerId; + const timerStatus = item.Status; + const seriesTimerId = item.SeriesTimerId; - if (item) { + const instance = this; - var serverId = item.ServerId; - var programId = item.Id; - var timerId = item.TimerId; - var timerStatus = item.Status; - var seriesTimerId = item.SeriesTimerId; - - var instance = this; - - recordingHelper.toggleRecording(serverId, programId, timerId, timerStatus, seriesTimerId).then(function () { - instance.refresh(serverId, programId); - }); - } + recordingHelper.toggleRecording(serverId, programId, timerId, timerStatus, seriesTimerId).then(function () { + instance.refresh(serverId, programId); + }); } +} - function setButtonIcon(button, icon) { - var inner = button.querySelector('.material-icons'); - inner.classList.remove('fiber_smart_record'); - inner.classList.remove('fiber_manual_record'); - inner.classList.add(icon); - } +function setButtonIcon(button, icon) { + const inner = button.querySelector('.material-icons'); + inner.classList.remove('fiber_smart_record'); + inner.classList.remove('fiber_manual_record'); + inner.classList.add(icon); +} - function RecordingButton(options) { +class RecordingButton { + constructor(options) { this.options = options; - var button = options.button; + const button = options.button; setButtonIcon(button, 'fiber_manual_record'); @@ -41,7 +43,7 @@ define(['globalize', 'connectionManager', 'require', 'loading', 'apphost', 'dom' this.refresh(options.itemId, options.serverId); } - var clickFn = onRecordingButtonClick.bind(this); + const clickFn = onRecordingButtonClick.bind(this); this.clickFn = clickFn; dom.addEventListener(button, 'click', clickFn, { @@ -49,45 +51,17 @@ define(['globalize', 'connectionManager', 'require', 'loading', 'apphost', 'dom' }); } - function getIndicatorIcon(item) { - - var status; - - if (item.Type === 'SeriesTimer') { - return 'fiber_smart_record'; - } else if (item.TimerId || item.SeriesTimerId) { - - status = item.Status || 'Cancelled'; - } else if (item.Type === 'Timer') { - - status = item.Status; - } else { - return 'fiber_manual_record'; - } - - if (item.SeriesTimerId) { - - if (status !== 'Cancelled') { - return 'fiber_smart_record'; - } - } - - return 'fiber_manual_record'; - } - - RecordingButton.prototype.refresh = function (serverId, itemId) { - - var apiClient = connectionManager.getApiClient(serverId); - var self = this; + refresh(serverId, itemId) { + const apiClient = window.connectionManager.getApiClient(serverId); + const self = this; apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(function (item) { self.refreshItem(item); }); - }; + } - RecordingButton.prototype.refreshItem = function (item) { - - var options = this.options; - var button = options.button; + refreshItem(item) { + const options = this.options; + const button = options.button; this.item = item; setButtonIcon(button, getIndicatorIcon(item)); @@ -96,16 +70,15 @@ define(['globalize', 'connectionManager', 'require', 'loading', 'apphost', 'dom' } else { button.classList.remove('recordingIcon-active'); } - }; + } - RecordingButton.prototype.destroy = function () { - - var options = this.options; + destroy() { + const options = this.options; if (options) { - var button = options.button; + const button = options.button; - var clickFn = this.clickFn; + const clickFn = this.clickFn; if (clickFn) { dom.removeEventListener(button, 'click', clickFn, { @@ -116,7 +89,29 @@ define(['globalize', 'connectionManager', 'require', 'loading', 'apphost', 'dom' this.options = null; this.item = null; - }; + } +} - return RecordingButton; -}); +function getIndicatorIcon(item) { + let status; + + if (item.Type === 'SeriesTimer') { + return 'fiber_smart_record'; + } else if (item.TimerId || item.SeriesTimerId) { + status = item.Status || 'Cancelled'; + } else if (item.Type === 'Timer') { + status = item.Status; + } else { + return 'fiber_manual_record'; + } + + if (item.SeriesTimerId) { + if (status !== 'Cancelled') { + return 'fiber_smart_record'; + } + } + + return 'fiber_manual_record'; +} + +export default RecordingButton; diff --git a/src/components/recordingcreator/recordingcreator.js b/src/components/recordingcreator/recordingcreator.js index d18d5fcb6c..27ad0584d5 100644 --- a/src/components/recordingcreator/recordingcreator.js +++ b/src/components/recordingcreator/recordingcreator.js @@ -1,205 +1,203 @@ -define(['dialogHelper', 'globalize', 'layoutManager', 'mediaInfo', 'apphost', 'connectionManager', 'require', 'loading', 'scrollHelper', 'datetime', 'imageLoader', 'recordingFields', 'events', 'emby-checkbox', 'emby-button', 'emby-collapse', 'emby-input', 'paper-icon-button-light', 'css!./../formdialog', 'css!./recordingcreator', 'material-icons'], function (dialogHelper, globalize, layoutManager, mediaInfo, appHost, connectionManager, require, loading, scrollHelper, datetime, imageLoader, recordingFields, events) { - 'use strict'; +import dialogHelper from 'dialogHelper'; +import globalize from 'globalize'; +import layoutManager from 'layoutManager'; +import mediaInfo from 'mediaInfo'; +import require from 'require'; +import loading from 'loading'; +import scrollHelper from 'scrollHelper'; +import datetime from 'datetime'; +import imageLoader from 'imageLoader'; +import recordingFields from 'recordingFields'; +import events from 'events'; +import 'emby-checkbox'; +import 'emby-button'; +import 'emby-collapse'; +import 'emby-input'; +import 'paper-icon-button-light'; +import 'css!./../formdialog'; +import 'css!./recordingcreator'; +import 'material-icons'; - var currentDialog; - var closeAction; - var currentRecordingFields; +let currentDialog; +let closeAction; +let currentRecordingFields; - function closeDialog() { +function closeDialog() { + dialogHelper.close(currentDialog); +} - dialogHelper.close(currentDialog); +function init(context) { + context.querySelector('.btnPlay').addEventListener('click', function () { + closeAction = 'play'; + closeDialog(); + }); + + context.querySelector('.btnCancel').addEventListener('click', function () { + closeAction = null; + closeDialog(); + }); +} + +function getImageUrl(item, apiClient, imageHeight) { + const imageTags = item.ImageTags || {}; + + if (item.PrimaryImageTag) { + imageTags.Primary = item.PrimaryImageTag; } - function init(context) { - - context.querySelector('.btnPlay').addEventListener('click', function () { - - closeAction = 'play'; - closeDialog(); + if (imageTags.Primary) { + return apiClient.getScaledImageUrl(item.Id, { + type: 'Primary', + maxHeight: imageHeight, + tag: item.ImageTags.Primary }); - - context.querySelector('.btnCancel').addEventListener('click', function () { - - closeAction = null; - closeDialog(); + } else if (imageTags.Thumb) { + return apiClient.getScaledImageUrl(item.Id, { + type: 'Thumb', + maxHeight: imageHeight, + tag: item.ImageTags.Thumb }); } - function getImageUrl(item, apiClient, imageHeight) { + return null; +} - var imageTags = item.ImageTags || {}; +function renderRecording(context, defaultTimer, program, apiClient, refreshRecordingStateOnly) { + if (!refreshRecordingStateOnly) { + const imgUrl = getImageUrl(program, apiClient, 200); + const imageContainer = context.querySelector('.recordingDialog-imageContainer'); - if (item.PrimaryImageTag) { - imageTags.Primary = item.PrimaryImageTag; + if (imgUrl) { + imageContainer.innerHTML = ''; + imageContainer.classList.remove('hide'); + + imageLoader.lazyChildren(imageContainer); + } else { + imageContainer.innerHTML = ''; + imageContainer.classList.add('hide'); } - if (imageTags.Primary) { + context.querySelector('.recordingDialog-itemName').innerHTML = program.Name; + context.querySelector('.formDialogHeaderTitle').innerHTML = program.Name; + context.querySelector('.itemGenres').innerHTML = (program.Genres || []).join(' / '); + context.querySelector('.itemOverview').innerHTML = program.Overview || ''; - return apiClient.getScaledImageUrl(item.Id, { - type: 'Primary', - maxHeight: imageHeight, - tag: item.ImageTags.Primary + const formDialogFooter = context.querySelector('.formDialogFooter'); + const now = new Date(); + if (now >= datetime.parseISO8601Date(program.StartDate, true) && now < datetime.parseISO8601Date(program.EndDate, true)) { + formDialogFooter.classList.remove('hide'); + } else { + formDialogFooter.classList.add('hide'); + } + + context.querySelector('.itemMiscInfoPrimary').innerHTML = mediaInfo.getPrimaryMediaInfoHtml(program); + } + + context.querySelector('.itemMiscInfoSecondary').innerHTML = mediaInfo.getSecondaryMediaInfoHtml(program, { + }); + + loading.hide(); +} + +function reload(context, programId, serverId, refreshRecordingStateOnly) { + loading.show(); + + const apiClient = window.connectionManager.getApiClient(serverId); + + const promise1 = apiClient.getNewLiveTvTimerDefaults({ programId: programId }); + const promise2 = apiClient.getLiveTvProgram(programId, apiClient.getCurrentUserId()); + + Promise.all([promise1, promise2]).then(function (responses) { + const defaults = responses[0]; + const program = responses[1]; + + renderRecording(context, defaults, program, apiClient, refreshRecordingStateOnly); + }); +} + +function executeCloseAction(action, programId, serverId) { + if (action === 'play') { + import('playbackManager').then(({ default: playbackManager }) => { + const apiClient = window.connectionManager.getApiClient(serverId); + + apiClient.getLiveTvProgram(programId, apiClient.getCurrentUserId()).then(function (item) { + playbackManager.play({ + ids: [item.ChannelId], + serverId: serverId + }); }); - } else if (imageTags.Thumb) { - - return apiClient.getScaledImageUrl(item.Id, { - type: 'Thumb', - maxHeight: imageHeight, - tag: item.ImageTags.Thumb - }); - } - - return null; - } - - function renderRecording(context, defaultTimer, program, apiClient, refreshRecordingStateOnly) { - - if (!refreshRecordingStateOnly) { - var imgUrl = getImageUrl(program, apiClient, 200); - var imageContainer = context.querySelector('.recordingDialog-imageContainer'); - - if (imgUrl) { - imageContainer.innerHTML = ''; - imageContainer.classList.remove('hide'); - - imageLoader.lazyChildren(imageContainer); - } else { - imageContainer.innerHTML = ''; - imageContainer.classList.add('hide'); - } - - context.querySelector('.recordingDialog-itemName').innerHTML = program.Name; - context.querySelector('.formDialogHeaderTitle').innerHTML = program.Name; - context.querySelector('.itemGenres').innerHTML = (program.Genres || []).join(' / '); - context.querySelector('.itemOverview').innerHTML = program.Overview || ''; - - var formDialogFooter = context.querySelector('.formDialogFooter'); - var now = new Date(); - if (now >= datetime.parseISO8601Date(program.StartDate, true) && now < datetime.parseISO8601Date(program.EndDate, true)) { - formDialogFooter.classList.remove('hide'); - } else { - formDialogFooter.classList.add('hide'); - } - - context.querySelector('.itemMiscInfoPrimary').innerHTML = mediaInfo.getPrimaryMediaInfoHtml(program); - } - - context.querySelector('.itemMiscInfoSecondary').innerHTML = mediaInfo.getSecondaryMediaInfoHtml(program, { }); - - loading.hide(); + return; } +} - function reload(context, programId, serverId, refreshRecordingStateOnly) { +function showEditor(itemId, serverId) { + return new Promise(function (resolve, reject) { + closeAction = null; loading.show(); - var apiClient = connectionManager.getApiClient(serverId); + import('text!./recordingcreator.template.html').then(({ default: template }) => { + const dialogOptions = { + removeOnClose: true, + scrollY: false + }; - var promise1 = apiClient.getNewLiveTvTimerDefaults({ programId: programId }); - var promise2 = apiClient.getLiveTvProgram(programId, apiClient.getCurrentUserId()); + if (layoutManager.tv) { + dialogOptions.size = 'fullscreen'; + } else { + dialogOptions.size = 'small'; + } - Promise.all([promise1, promise2]).then(function (responses) { + const dlg = dialogHelper.createDialog(dialogOptions); - var defaults = responses[0]; - var program = responses[1]; + dlg.classList.add('formDialog'); + dlg.classList.add('recordingDialog'); - renderRecording(context, defaults, program, apiClient, refreshRecordingStateOnly); - }); - } + let html = ''; - function executeCloseAction(action, programId, serverId) { + html += globalize.translateHtml(template, 'core'); - if (action === 'play') { + dlg.innerHTML = html; - require(['playbackManager'], function (playbackManager) { + currentDialog = dlg; - var apiClient = connectionManager.getApiClient(serverId); + function onRecordingChanged() { + reload(dlg, itemId, serverId, true); + } - apiClient.getLiveTvProgram(programId, apiClient.getCurrentUserId()).then(function (item) { + dlg.addEventListener('close', function () { + events.off(currentRecordingFields, 'recordingchanged', onRecordingChanged); + executeCloseAction(closeAction, itemId, serverId); - playbackManager.play({ - ids: [item.ChannelId], - serverId: serverId - }); - }); - }); - return; - } - } - - function showEditor(itemId, serverId) { - - return new Promise(function (resolve, reject) { - - closeAction = null; - - loading.show(); - - require(['text!./recordingcreator.template.html'], function (template) { - - var dialogOptions = { - removeOnClose: true, - scrollY: false - }; - - if (layoutManager.tv) { - dialogOptions.size = 'fullscreen'; + if (currentRecordingFields && currentRecordingFields.hasChanged()) { + resolve(); } else { - dialogOptions.size = 'small'; + reject(); } - - var dlg = dialogHelper.createDialog(dialogOptions); - - dlg.classList.add('formDialog'); - dlg.classList.add('recordingDialog'); - - var html = ''; - - html += globalize.translateHtml(template, 'core'); - - dlg.innerHTML = html; - - currentDialog = dlg; - - function onRecordingChanged() { - reload(dlg, itemId, serverId, true); - } - - dlg.addEventListener('close', function () { - - events.off(currentRecordingFields, 'recordingchanged', onRecordingChanged); - executeCloseAction(closeAction, itemId, serverId); - - if (currentRecordingFields && currentRecordingFields.hasChanged()) { - resolve(); - } else { - reject(); - } - }); - - if (layoutManager.tv) { - scrollHelper.centerFocus.on(dlg.querySelector('.formDialogContent'), false); - } - - init(dlg); - - reload(dlg, itemId, serverId); - - currentRecordingFields = new recordingFields({ - parent: dlg.querySelector('.recordingFields'), - programId: itemId, - serverId: serverId - }); - - events.on(currentRecordingFields, 'recordingchanged', onRecordingChanged); - - dialogHelper.open(dlg); }); - }); - } - return { - show: showEditor - }; -}); + if (layoutManager.tv) { + scrollHelper.centerFocus.on(dlg.querySelector('.formDialogContent'), false); + } + + init(dlg); + + reload(dlg, itemId, serverId); + + currentRecordingFields = new recordingFields({ + parent: dlg.querySelector('.recordingFields'), + programId: itemId, + serverId: serverId + }); + + events.on(currentRecordingFields, 'recordingchanged', onRecordingChanged); + + dialogHelper.open(dlg); + }); + }); +} + +export default { + show: showEditor +}; diff --git a/src/components/recordingcreator/recordingeditor.js b/src/components/recordingcreator/recordingeditor.js index c3f40fcddb..37b55e4eec 100644 --- a/src/components/recordingcreator/recordingeditor.js +++ b/src/components/recordingcreator/recordingeditor.js @@ -1,162 +1,154 @@ -define(['dialogHelper', 'globalize', 'layoutManager', 'mediaInfo', 'apphost', 'connectionManager', 'require', 'loading', 'scrollHelper', 'imageLoader', 'scrollStyles', 'emby-button', 'emby-collapse', 'emby-input', 'paper-icon-button-light', 'css!./../formdialog', 'css!./recordingcreator', 'material-icons', 'flexStyles'], function (dialogHelper, globalize, layoutManager, mediaInfo, appHost, connectionManager, require, loading, scrollHelper, imageLoader) { - 'use strict'; +import dialogHelper from 'dialogHelper'; +import globalize from 'globalize'; +import layoutManager from 'layoutManager'; +import loading from 'loading'; +import scrollHelper from 'scrollHelper'; +import 'scrollStyles'; +import 'emby-button'; +import 'emby-collapse'; +import 'emby-input'; +import 'paper-icon-button-light'; +import 'css!./../formdialog'; +import 'css!./recordingcreator'; +import 'material-icons'; +import 'flexStyles'; - var currentDialog; - var recordingDeleted = false; - var currentItemId; - var currentServerId; - var currentResolve; +let currentDialog; +let recordingDeleted = false; +let currentItemId; +let currentServerId; +let currentResolve; - function deleteTimer(apiClient, timerId) { +function deleteTimer(apiClient, timerId) { + return import('recordingHelper').then(({ default: recordingHelper }) => { + recordingHelper.cancelTimerWithConfirmation(timerId, apiClient.serverId()); + }); +} - return new Promise(function (resolve, reject) { +function renderTimer(context, item, apiClient) { + context.querySelector('#txtPrePaddingMinutes').value = item.PrePaddingSeconds / 60; + context.querySelector('#txtPostPaddingMinutes').value = item.PostPaddingSeconds / 60; - require(['recordingHelper'], function (recordingHelper) { + loading.hide(); +} - recordingHelper.cancelTimerWithConfirmation(timerId, apiClient.serverId()).then(resolve, reject); - }); +function closeDialog(isDeleted) { + recordingDeleted = isDeleted; + dialogHelper.close(currentDialog); +} + +function onSubmit(e) { + const form = this; + + const apiClient = window.connectionManager.getApiClient(currentServerId); + + apiClient.getLiveTvTimer(currentItemId).then(function (item) { + item.PrePaddingSeconds = form.querySelector('#txtPrePaddingMinutes').value * 60; + item.PostPaddingSeconds = form.querySelector('#txtPostPaddingMinutes').value * 60; + apiClient.updateLiveTvTimer(item).then(currentResolve); + }); + + e.preventDefault(); + + // Disable default form submission + return false; +} + +function init(context) { + context.querySelector('.btnCancel').addEventListener('click', function () { + closeDialog(false); + }); + + context.querySelector('.btnCancelRecording').addEventListener('click', function () { + const apiClient = window.connectionManager.getApiClient(currentServerId); + + deleteTimer(apiClient, currentItemId).then(function () { + closeDialog(true); }); - } + }); - function renderTimer(context, item, apiClient) { + context.querySelector('form').addEventListener('submit', onSubmit); +} - var program = item.ProgramInfo || {}; - - context.querySelector('#txtPrePaddingMinutes').value = item.PrePaddingSeconds / 60; - context.querySelector('#txtPostPaddingMinutes').value = item.PostPaddingSeconds / 60; +function reload(context, id) { + loading.show(); + currentItemId = id; + const apiClient = window.connectionManager.getApiClient(currentServerId); + apiClient.getLiveTvTimer(id).then(function (result) { + renderTimer(context, result, apiClient); loading.hide(); - } - - function closeDialog(isDeleted) { - - recordingDeleted = isDeleted; - - dialogHelper.close(currentDialog); - } - - function onSubmit(e) { - - var form = this; - - var apiClient = connectionManager.getApiClient(currentServerId); - - apiClient.getLiveTvTimer(currentItemId).then(function (item) { - item.PrePaddingSeconds = form.querySelector('#txtPrePaddingMinutes').value * 60; - item.PostPaddingSeconds = form.querySelector('#txtPostPaddingMinutes').value * 60; - apiClient.updateLiveTvTimer(item).then(currentResolve); - }); - - e.preventDefault(); - - // Disable default form submission - return false; - } - - function init(context) { - - context.querySelector('.btnCancel').addEventListener('click', function () { - - closeDialog(false); - }); - - context.querySelector('.btnCancelRecording').addEventListener('click', function () { - - var apiClient = connectionManager.getApiClient(currentServerId); - deleteTimer(apiClient, currentItemId).then(function () { - closeDialog(true); - }); - }); - - context.querySelector('form').addEventListener('submit', onSubmit); - } - - function reload(context, id) { + }); +} +function showEditor(itemId, serverId, options) { + return new Promise(function (resolve, reject) { + recordingDeleted = false; + currentServerId = serverId; loading.show(); - currentItemId = id; + options = options || {}; + currentResolve = resolve; - var apiClient = connectionManager.getApiClient(currentServerId); - apiClient.getLiveTvTimer(id).then(function (result) { + import('text!./recordingeditor.template.html').then(({default: template}) => { + const dialogOptions = { + removeOnClose: true, + scrollY: false + }; - renderTimer(context, result, apiClient); - loading.hide(); - }); - } + if (layoutManager.tv) { + dialogOptions.size = 'fullscreen'; + } - function showEditor(itemId, serverId, options) { + const dlg = dialogHelper.createDialog(dialogOptions); - return new Promise(function (resolve, reject) { + dlg.classList.add('formDialog'); + dlg.classList.add('recordingDialog'); - recordingDeleted = false; - currentServerId = serverId; - loading.show(); - options = options || {}; - currentResolve = resolve; + if (!layoutManager.tv) { + dlg.style['min-width'] = '20%'; + dlg.classList.add('dialog-fullscreen-lowres'); + } - require(['text!./recordingeditor.template.html'], function (template) { - var dialogOptions = { - removeOnClose: true, - scrollY: false - }; + let html = ''; - if (layoutManager.tv) { - dialogOptions.size = 'fullscreen'; + html += globalize.translateHtml(template, 'core'); + + dlg.innerHTML = html; + + if (options.enableCancel === false) { + dlg.querySelector('.formDialogFooter').classList.add('hide'); + } + + currentDialog = dlg; + + dlg.addEventListener('closing', function () { + if (!recordingDeleted) { + dlg.querySelector('.btnSubmit').click(); } - - var dlg = dialogHelper.createDialog(dialogOptions); - - dlg.classList.add('formDialog'); - dlg.classList.add('recordingDialog'); - - if (!layoutManager.tv) { - dlg.style['min-width'] = '20%'; - dlg.classList.add('dialog-fullscreen-lowres'); - } - - var html = ''; - - html += globalize.translateHtml(template, 'core'); - - dlg.innerHTML = html; - - if (options.enableCancel === false) { - dlg.querySelector('.formDialogFooter').classList.add('hide'); - } - - currentDialog = dlg; - - dlg.addEventListener('closing', function () { - - if (!recordingDeleted) { - dlg.querySelector('.btnSubmit').click(); - } - }); - - dlg.addEventListener('close', function () { - - if (recordingDeleted) { - resolve({ - updated: true, - deleted: true - }); - } - }); - - if (layoutManager.tv) { - scrollHelper.centerFocus.on(dlg.querySelector('.formDialogContent'), false); - } - - init(dlg); - - reload(dlg, itemId); - - dialogHelper.open(dlg); }); - }); - } - return { - show: showEditor - }; -}); + dlg.addEventListener('close', function () { + if (recordingDeleted) { + resolve({ + updated: true, + deleted: true + }); + } + }); + + if (layoutManager.tv) { + scrollHelper.centerFocus.on(dlg.querySelector('.formDialogContent'), false); + } + + init(dlg); + + reload(dlg, itemId); + + dialogHelper.open(dlg); + }); + }); +} + +export default { + show: showEditor +}; diff --git a/src/components/recordingcreator/recordingfields.js b/src/components/recordingcreator/recordingfields.js index 84348fcfbc..9b3f0d16a3 100644 --- a/src/components/recordingcreator/recordingfields.js +++ b/src/components/recordingcreator/recordingfields.js @@ -1,224 +1,125 @@ -define(['globalize', 'connectionManager', 'serverNotifications', 'require', 'loading', 'apphost', 'dom', 'recordingHelper', 'events', 'paper-icon-button-light', 'emby-button', 'css!./recordingfields', 'flexStyles'], function (globalize, connectionManager, serverNotifications, require, loading, appHost, dom, recordingHelper, events) { - 'use strict'; +import globalize from 'globalize'; +import serverNotifications from 'serverNotifications'; +import loading from 'loading'; +import dom from 'dom'; +import recordingHelper from 'recordingHelper'; +import events from 'events'; +import 'paper-icon-button-light'; +import 'emby-button'; +import 'css!./recordingfields'; +import 'flexStyles'; - function loadData(parent, program, apiClient) { - if (program.IsSeries) { - parent.querySelector('.recordSeriesContainer').classList.remove('hide'); +/*eslint prefer-const: "error"*/ + +function loadData(parent, program, apiClient) { + if (program.IsSeries) { + parent.querySelector('.recordSeriesContainer').classList.remove('hide'); + } else { + parent.querySelector('.recordSeriesContainer').classList.add('hide'); + } + + if (program.SeriesTimerId) { + parent.querySelector('.btnManageSeriesRecording').classList.remove('hide'); + parent.querySelector('.seriesRecordingButton .recordingIcon').classList.add('recordingIcon-active'); + parent.querySelector('.seriesRecordingButton .buttonText').innerHTML = globalize.translate('CancelSeries'); + } else { + parent.querySelector('.btnManageSeriesRecording').classList.add('hide'); + parent.querySelector('.seriesRecordingButton .recordingIcon').classList.remove('recordingIcon-active'); + parent.querySelector('.seriesRecordingButton .buttonText').innerHTML = globalize.translate('RecordSeries'); + } + + if (program.TimerId && program.Status !== 'Cancelled') { + parent.querySelector('.btnManageRecording').classList.remove('hide'); + parent.querySelector('.singleRecordingButton .recordingIcon').classList.add('recordingIcon-active'); + if (program.Status === 'InProgress') { + parent.querySelector('.singleRecordingButton .buttonText').innerHTML = globalize.translate('StopRecording'); } else { - parent.querySelector('.recordSeriesContainer').classList.add('hide'); + parent.querySelector('.singleRecordingButton .buttonText').innerHTML = globalize.translate('DoNotRecord'); } + } else { + parent.querySelector('.btnManageRecording').classList.add('hide'); + parent.querySelector('.singleRecordingButton .recordingIcon').classList.remove('recordingIcon-active'); + parent.querySelector('.singleRecordingButton .buttonText').innerHTML = globalize.translate('Record'); + } +} - if (program.SeriesTimerId) { - parent.querySelector('.btnManageSeriesRecording').classList.remove('hide'); - parent.querySelector('.seriesRecordingButton .recordingIcon').classList.add('recordingIcon-active'); - parent.querySelector('.seriesRecordingButton .buttonText').innerHTML = globalize.translate('CancelSeries'); - } else { - parent.querySelector('.btnManageSeriesRecording').classList.add('hide'); - parent.querySelector('.seriesRecordingButton .recordingIcon').classList.remove('recordingIcon-active'); - parent.querySelector('.seriesRecordingButton .buttonText').innerHTML = globalize.translate('RecordSeries'); +function fetchData(instance) { + const options = instance.options; + const apiClient = window.connectionManager.getApiClient(options.serverId); + + options.parent.querySelector('.recordingFields').classList.remove('hide'); + return apiClient.getLiveTvProgram(options.programId, apiClient.getCurrentUserId()).then(function (program) { + instance.TimerId = program.TimerId; + instance.Status = program.Status; + instance.SeriesTimerId = program.SeriesTimerId; + loadData(options.parent, program, apiClient); + }); +} + +function onTimerChangedExternally(e, apiClient, data) { + const options = this.options; + let refresh = false; + + if (data.Id) { + if (this.TimerId === data.Id) { + refresh = true; } - - if (program.TimerId && program.Status !== 'Cancelled') { - parent.querySelector('.btnManageRecording').classList.remove('hide'); - parent.querySelector('.singleRecordingButton .recordingIcon').classList.add('recordingIcon-active'); - if (program.Status === 'InProgress') { - parent.querySelector('.singleRecordingButton .buttonText').innerHTML = globalize.translate('StopRecording'); - } else { - parent.querySelector('.singleRecordingButton .buttonText').innerHTML = globalize.translate('DoNotRecord'); - } - } else { - parent.querySelector('.btnManageRecording').classList.add('hide'); - parent.querySelector('.singleRecordingButton .recordingIcon').classList.remove('recordingIcon-active'); - parent.querySelector('.singleRecordingButton .buttonText').innerHTML = globalize.translate('Record'); + } + if (data.ProgramId && options) { + if (options.programId === data.ProgramId) { + refresh = true; } } - function fetchData(instance) { - - var options = instance.options; - var apiClient = connectionManager.getApiClient(options.serverId); - - options.parent.querySelector('.recordingFields').classList.remove('hide'); - return apiClient.getLiveTvProgram(options.programId, apiClient.getCurrentUserId()).then(function (program) { - instance.TimerId = program.TimerId; - instance.Status = program.Status; - instance.SeriesTimerId = program.SeriesTimerId; - loadData(options.parent, program, apiClient); - }); + if (refresh) { + this.refresh(); } +} - function onTimerChangedExternally(e, apiClient, data) { - var options = this.options; - var refresh = false; +function onSeriesTimerChangedExternally(e, apiClient, data) { + const options = this.options; + let refresh = false; - if (data.Id) { - if (this.TimerId === data.Id) { - refresh = true; - } + if (data.Id) { + if (this.SeriesTimerId === data.Id) { + refresh = true; } - if (data.ProgramId && options) { - if (options.programId === data.ProgramId) { - refresh = true; - } - } - - if (refresh) { - this.refresh(); + } + if (data.ProgramId && options) { + if (options.programId === data.ProgramId) { + refresh = true; } } - function onSeriesTimerChangedExternally(e, apiClient, data) { - var options = this.options; - var refresh = false; - - if (data.Id) { - if (this.SeriesTimerId === data.Id) { - refresh = true; - } - } - if (data.ProgramId && options) { - if (options.programId === data.ProgramId) { - refresh = true; - } - } - - if (refresh) { - this.refresh(); - } + if (refresh) { + this.refresh(); } +} - function RecordingEditor(options) { +class RecordingEditor { + constructor(options) { this.options = options; this.embed(); - var timerChangedHandler = onTimerChangedExternally.bind(this); + const timerChangedHandler = onTimerChangedExternally.bind(this); this.timerChangedHandler = timerChangedHandler; events.on(serverNotifications, 'TimerCreated', timerChangedHandler); events.on(serverNotifications, 'TimerCancelled', timerChangedHandler); - var seriesTimerChangedHandler = onSeriesTimerChangedExternally.bind(this); + const seriesTimerChangedHandler = onSeriesTimerChangedExternally.bind(this); this.seriesTimerChangedHandler = seriesTimerChangedHandler; events.on(serverNotifications, 'SeriesTimerCreated', seriesTimerChangedHandler); events.on(serverNotifications, 'SeriesTimerCancelled', seriesTimerChangedHandler); } - function onManageRecordingClick(e) { - var options = this.options; - if (!this.TimerId || this.Status === 'Cancelled') { - return; - } - - var self = this; - require(['recordingEditor'], function (recordingEditor) { - recordingEditor.show(self.TimerId, options.serverId, { - enableCancel: false - }).then(function () { - self.changed = true; - }); - }); - } - - function onManageSeriesRecordingClick(e) { - - var options = this.options; - - if (!this.SeriesTimerId) { - return; - } - - var self = this; - - require(['seriesRecordingEditor'], function (seriesRecordingEditor) { - - seriesRecordingEditor.show(self.SeriesTimerId, options.serverId, { - - enableCancel: false - - }).then(function () { - self.changed = true; - }); - }); - } - - function onRecordChange(e) { - - this.changed = true; - - var self = this; - var options = this.options; - var apiClient = connectionManager.getApiClient(options.serverId); - - var button = dom.parentWithTag(e.target, 'BUTTON'); - var isChecked = !button.querySelector('.material-icons').classList.contains('recordingIcon-active'); - - var hasEnabledTimer = this.TimerId && this.Status !== 'Cancelled'; - - if (isChecked) { - if (!hasEnabledTimer) { - loading.show(); - recordingHelper.createRecording(apiClient, options.programId, false).then(function () { - events.trigger(self, 'recordingchanged'); - fetchData(self); - loading.hide(); - }); - } - } else { - if (hasEnabledTimer) { - loading.show(); - recordingHelper.cancelTimer(apiClient, this.TimerId, true).then(function () { - events.trigger(self, 'recordingchanged'); - fetchData(self); - loading.hide(); - }); - } - } - } - - function sendToast(msg) { - require(['toast'], function (toast) { - toast(msg); - }); - } - - function onRecordSeriesChange(e) { - - this.changed = true; - - var self = this; - var options = this.options; - var apiClient = connectionManager.getApiClient(options.serverId); - - var button = dom.parentWithTag(e.target, 'BUTTON'); - var isChecked = !button.querySelector('.material-icons').classList.contains('recordingIcon-active'); - - if (isChecked) { - options.parent.querySelector('.recordSeriesContainer').classList.remove('hide'); - if (!this.SeriesTimerId) { - var promise = this.TimerId ? - recordingHelper.changeRecordingToSeries(apiClient, this.TimerId, options.programId) : - recordingHelper.createRecording(apiClient, options.programId, true); - promise.then(function () { - fetchData(self); - }); - } - } else { - if (this.SeriesTimerId) { - apiClient.cancelLiveTvSeriesTimer(this.SeriesTimerId).then(function () { - sendToast(globalize.translate('RecordingCancelled')); - fetchData(self); - }); - } - } - } - - RecordingEditor.prototype.embed = function () { - var self = this; + embed() { + const self = this; return new Promise(function (resolve, reject) { - require(['text!./recordingfields.template.html'], function (template) { - var options = self.options; - var context = options.parent; + import('text!./recordingfields.template.html').then(({default: template}) => { + const options = self.options; + const context = options.parent; context.innerHTML = globalize.translateHtml(template, 'core'); context.querySelector('.singleRecordingButton').addEventListener('click', onRecordChange.bind(self)); @@ -229,29 +130,134 @@ define(['globalize', 'connectionManager', 'serverNotifications', 'require', 'loa fetchData(self).then(resolve); }); }); - }; + } - RecordingEditor.prototype.hasChanged = function () { + hasChanged() { return this.changed; - }; + } - RecordingEditor.prototype.refresh = function () { + refresh() { fetchData(this); - }; + } - RecordingEditor.prototype.destroy = function () { - var timerChangedHandler = this.timerChangedHandler; + destroy() { + const timerChangedHandler = this.timerChangedHandler; this.timerChangedHandler = null; events.off(serverNotifications, 'TimerCreated', timerChangedHandler); events.off(serverNotifications, 'TimerCancelled', timerChangedHandler); - var seriesTimerChangedHandler = this.seriesTimerChangedHandler; + const seriesTimerChangedHandler = this.seriesTimerChangedHandler; this.seriesTimerChangedHandler = null; events.off(serverNotifications, 'SeriesTimerCreated', seriesTimerChangedHandler); events.off(serverNotifications, 'SeriesTimerCancelled', seriesTimerChangedHandler); - }; + } +} - return RecordingEditor; -}); +function onManageRecordingClick(e) { + const options = this.options; + if (!this.TimerId || this.Status === 'Cancelled') { + return; + } + + const self = this; + import('recordingEditor').then(({default: recordingEditor}) => { + recordingEditor.show(self.TimerId, options.serverId, { + enableCancel: false + }).then(function () { + self.changed = true; + }); + }); +} + +function onManageSeriesRecordingClick(e) { + const options = this.options; + + if (!this.SeriesTimerId) { + return; + } + + const self = this; + + import('seriesRecordingEditor').then(({default: seriesRecordingEditor}) => { + seriesRecordingEditor.show(self.SeriesTimerId, options.serverId, { + + enableCancel: false + + }).then(function () { + self.changed = true; + }); + }); +} + +function onRecordChange(e) { + this.changed = true; + + const self = this; + const options = this.options; + const apiClient = window.connectionManager.getApiClient(options.serverId); + + const button = dom.parentWithTag(e.target, 'BUTTON'); + const isChecked = !button.querySelector('.material-icons').classList.contains('recordingIcon-active'); + + const hasEnabledTimer = this.TimerId && this.Status !== 'Cancelled'; + + if (isChecked) { + if (!hasEnabledTimer) { + loading.show(); + recordingHelper.createRecording(apiClient, options.programId, false).then(function () { + events.trigger(self, 'recordingchanged'); + fetchData(self); + loading.hide(); + }); + } + } else { + if (hasEnabledTimer) { + loading.show(); + recordingHelper.cancelTimer(apiClient, this.TimerId, true).then(function () { + events.trigger(self, 'recordingchanged'); + fetchData(self); + loading.hide(); + }); + } + } +} + +function sendToast(msg) { + import('toast').then(({default: toast}) => { + toast(msg); + }); +} + +function onRecordSeriesChange(e) { + this.changed = true; + + const self = this; + const options = this.options; + const apiClient = window.connectionManager.getApiClient(options.serverId); + + const button = dom.parentWithTag(e.target, 'BUTTON'); + const isChecked = !button.querySelector('.material-icons').classList.contains('recordingIcon-active'); + + if (isChecked) { + options.parent.querySelector('.recordSeriesContainer').classList.remove('hide'); + if (!this.SeriesTimerId) { + const promise = this.TimerId ? + recordingHelper.changeRecordingToSeries(apiClient, this.TimerId, options.programId) : + recordingHelper.createRecording(apiClient, options.programId, true); + promise.then(function () { + fetchData(self); + }); + } + } else { + if (this.SeriesTimerId) { + apiClient.cancelLiveTvSeriesTimer(this.SeriesTimerId).then(function () { + sendToast(globalize.translate('RecordingCancelled')); + fetchData(self); + }); + } + } +} + +export default RecordingEditor; diff --git a/src/components/recordingcreator/recordinghelper.js b/src/components/recordingcreator/recordinghelper.js index c773368f2d..495378106c 100644 --- a/src/components/recordingcreator/recordinghelper.js +++ b/src/components/recordingcreator/recordinghelper.js @@ -1,217 +1,194 @@ -define(['globalize', 'loading', 'connectionManager'], function (globalize, loading, connectionManager) { - 'use strict'; +import globalize from 'globalize'; +import loading from 'loading'; - function changeRecordingToSeries(apiClient, timerId, programId, confirmTimerCancellation) { +/*eslint prefer-const: "error"*/ - loading.show(); +function changeRecordingToSeries(apiClient, timerId, programId, confirmTimerCancellation) { + loading.show(); - return apiClient.getItem(apiClient.getCurrentUserId(), programId).then(function (item) { + return apiClient.getItem(apiClient.getCurrentUserId(), programId).then(function (item) { + if (item.IsSeries) { + // create series + return apiClient.getNewLiveTvTimerDefaults({ programId: programId }).then(function (timerDefaults) { + return apiClient.createLiveTvSeriesTimer(timerDefaults).then(function () { + loading.hide(); + sendToast(globalize.translate('SeriesRecordingScheduled')); + }); + }); + } else { + // cancel + if (confirmTimerCancellation) { + return cancelTimerWithConfirmation(timerId, apiClient.serverId()); + } - if (item.IsSeries) { - // create series - return apiClient.getNewLiveTvTimerDefaults({ programId: programId }).then(function (timerDefaults) { + return cancelTimer(apiClient.serverId(), timerId, true); + } + }); +} - return apiClient.createLiveTvSeriesTimer(timerDefaults).then(function () { +function cancelTimerWithConfirmation(timerId, serverId) { + return new Promise(function (resolve, reject) { + import('confirm').then(({ default: confirm }) => { + confirm.default({ - loading.hide(); - sendToast(globalize.translate('SeriesRecordingScheduled')); + text: globalize.translate('MessageConfirmRecordingCancellation'), + primary: 'delete', + confirmText: globalize.translate('HeaderCancelRecording'), + cancelText: globalize.translate('HeaderKeepRecording') + + }).then(function () { + loading.show(); + + const apiClient = window.connectionManager.getApiClient(serverId); + cancelTimer(apiClient, timerId, true).then(resolve, reject); + }, reject); + }); + }); +} + +function cancelSeriesTimerWithConfirmation(timerId, serverId) { + return new Promise(function (resolve, reject) { + import('confirm').then(({ default: confirm }) => { + confirm.default({ + + text: globalize.translate('MessageConfirmRecordingCancellation'), + primary: 'delete', + confirmText: globalize.translate('HeaderCancelSeries'), + cancelText: globalize.translate('HeaderKeepSeries') + + }).then(function () { + loading.show(); + + const apiClient = window.connectionManager.getApiClient(serverId); + apiClient.cancelLiveTvSeriesTimer(timerId).then(function () { + import('toast').then(({default: toast}) => { + toast(globalize.translate('SeriesCancelled')); }); + + loading.hide(); + resolve(); + }, reject); + }, reject); + }); + }); +} + +function cancelTimer(apiClient, timerId, hideLoading) { + loading.show(); + return apiClient.cancelLiveTvTimer(timerId).then(function () { + if (hideLoading !== false) { + loading.hide(); + sendToast(globalize.translate('RecordingCancelled')); + } + }); +} + +function createRecording(apiClient, programId, isSeries) { + loading.show(); + return apiClient.getNewLiveTvTimerDefaults({ programId: programId }).then(function (item) { + const promise = isSeries ? + apiClient.createLiveTvSeriesTimer(item) : + apiClient.createLiveTvTimer(item); + + return promise.then(function () { + loading.hide(); + sendToast(globalize.translate('RecordingScheduled')); + }); + }); +} + +function sendToast(msg) { + import('toast').then(({ default: toast }) => { + toast(msg); + }); +} + +function showMultiCancellationPrompt(serverId, programId, timerId, timerStatus, seriesTimerId) { + return new Promise(function (resolve, reject) { + import('dialog').then(({ default: dialog }) => { + const items = []; + + items.push({ + name: globalize.translate('HeaderKeepRecording'), + id: 'cancel', + type: 'submit' + }); + + if (timerStatus === 'InProgress') { + items.push({ + name: globalize.translate('HeaderStopRecording'), + id: 'canceltimer', + type: 'cancel' }); } else { - // cancel - if (confirmTimerCancellation) { - return cancelTimerWithConfirmation(timerId, apiClient.serverId()); - } - - return cancelTimer(apiClient.serverId(), timerId, true); + items.push({ + name: globalize.translate('HeaderCancelRecording'), + id: 'canceltimer', + type: 'cancel' + }); } - }); - } - function cancelTimerWithConfirmation(timerId, serverId) { - - return new Promise(function (resolve, reject) { - - require(['confirm'], function (confirm) { - - confirm.default({ - - text: globalize.translate('MessageConfirmRecordingCancellation'), - primary: 'delete', - confirmText: globalize.translate('HeaderCancelRecording'), - cancelText: globalize.translate('HeaderKeepRecording') - - }).then(function () { - - loading.show(); - - var apiClient = connectionManager.getApiClient(serverId); - cancelTimer(apiClient, timerId, true).then(resolve, reject); - - }, reject); + items.push({ + name: globalize.translate('HeaderCancelSeries'), + id: 'cancelseriestimer', + type: 'cancel' }); - }); - } - function cancelSeriesTimerWithConfirmation(timerId, serverId) { + dialog({ - return new Promise(function (resolve, reject) { + text: globalize.translate('MessageConfirmRecordingCancellation'), + buttons: items - require(['confirm'], function (confirm) { - - confirm.default({ - - text: globalize.translate('MessageConfirmRecordingCancellation'), - primary: 'delete', - confirmText: globalize.translate('HeaderCancelSeries'), - cancelText: globalize.translate('HeaderKeepSeries') - - }).then(function () { + }).then(function (result) { + const apiClient = window.connectionManager.getApiClient(serverId); + if (result === 'canceltimer') { loading.show(); - var apiClient = connectionManager.getApiClient(serverId); - apiClient.cancelLiveTvSeriesTimer(timerId).then(function () { + cancelTimer(apiClient, timerId, true).then(resolve, reject); + } else if (result === 'cancelseriestimer') { + loading.show(); - require(['toast'], function (toast) { + apiClient.cancelLiveTvSeriesTimer(seriesTimerId).then(function () { + import('toast').then(({ default: toast }) => { toast(globalize.translate('SeriesCancelled')); }); loading.hide(); resolve(); }, reject); - - }, reject); - }); - }); - } - - function cancelTimer(apiClient, timerId, hideLoading) { - loading.show(); - return apiClient.cancelLiveTvTimer(timerId).then(function () { - - if (hideLoading !== false) { - loading.hide(); - sendToast(globalize.translate('RecordingCancelled')); - } - }); - } - - function createRecording(apiClient, programId, isSeries) { - - loading.show(); - return apiClient.getNewLiveTvTimerDefaults({ programId: programId }).then(function (item) { - - var promise = isSeries ? - apiClient.createLiveTvSeriesTimer(item) : - apiClient.createLiveTvTimer(item); - - return promise.then(function () { - - loading.hide(); - sendToast(globalize.translate('RecordingScheduled')); - }); - }); - } - - function sendToast(msg) { - require(['toast'], function (toast) { - toast(msg); - }); - } - - function showMultiCancellationPrompt(serverId, programId, timerId, timerStatus, seriesTimerId) { - return new Promise(function (resolve, reject) { - - require(['dialog'], function (dialog) { - - var items = []; - - items.push({ - name: globalize.translate('HeaderKeepRecording'), - id: 'cancel', - type: 'submit' - }); - - if (timerStatus === 'InProgress') { - items.push({ - name: globalize.translate('HeaderStopRecording'), - id: 'canceltimer', - type: 'cancel' - }); } else { - items.push({ - name: globalize.translate('HeaderCancelRecording'), - id: 'canceltimer', - type: 'cancel' - }); + resolve(); } - - items.push({ - name: globalize.translate('HeaderCancelSeries'), - id: 'cancelseriestimer', - type: 'cancel' - }); - - dialog({ - - text: globalize.translate('MessageConfirmRecordingCancellation'), - buttons: items - - }).then(function (result) { - - var apiClient = connectionManager.getApiClient(serverId); - - if (result === 'canceltimer') { - loading.show(); - - cancelTimer(apiClient, timerId, true).then(resolve, reject); - } else if (result === 'cancelseriestimer') { - - loading.show(); - - apiClient.cancelLiveTvSeriesTimer(seriesTimerId).then(function () { - - require(['toast'], function (toast) { - toast(globalize.translate('SeriesCancelled')); - }); - - loading.hide(); - resolve(); - }, reject); - } else { - resolve(); - } - - }, reject); - }); + }, reject); }); - } + }); +} - function toggleRecording(serverId, programId, timerId, timerStatus, seriesTimerId) { - var apiClient = connectionManager.getApiClient(serverId); - var hasTimer = timerId && timerStatus !== 'Cancelled'; - if (seriesTimerId && hasTimer) { - // cancel - return showMultiCancellationPrompt(serverId, programId, timerId, timerStatus, seriesTimerId); - } else if (hasTimer && programId) { - // change to series recording, if possible - // otherwise cancel individual recording - return changeRecordingToSeries(apiClient, timerId, programId, true); - } else if (programId) { - // schedule recording - return createRecording(apiClient, programId); - } else { - return Promise.reject(); - } +function toggleRecording(serverId, programId, timerId, timerStatus, seriesTimerId) { + const apiClient = window.connectionManager.getApiClient(serverId); + const hasTimer = timerId && timerStatus !== 'Cancelled'; + if (seriesTimerId && hasTimer) { + // cancel + return showMultiCancellationPrompt(serverId, programId, timerId, timerStatus, seriesTimerId); + } else if (hasTimer && programId) { + // change to series recording, if possible + // otherwise cancel individual recording + return changeRecordingToSeries(apiClient, timerId, programId, true); + } else if (programId) { + // schedule recording + return createRecording(apiClient, programId); + } else { + return Promise.reject(); } +} + +export default { + cancelTimer: cancelTimer, + createRecording: createRecording, + changeRecordingToSeries: changeRecordingToSeries, + toggleRecording: toggleRecording, + cancelTimerWithConfirmation: cancelTimerWithConfirmation, + cancelSeriesTimerWithConfirmation: cancelSeriesTimerWithConfirmation +}; - return { - cancelTimer: cancelTimer, - createRecording: createRecording, - changeRecordingToSeries: changeRecordingToSeries, - toggleRecording: toggleRecording, - cancelTimerWithConfirmation: cancelTimerWithConfirmation, - cancelSeriesTimerWithConfirmation: cancelSeriesTimerWithConfirmation - }; -}); diff --git a/src/components/recordingcreator/seriesrecordingeditor.js b/src/components/recordingcreator/seriesrecordingeditor.js index a101ce53ec..e3a6ae6853 100644 --- a/src/components/recordingcreator/seriesrecordingeditor.js +++ b/src/components/recordingcreator/seriesrecordingeditor.js @@ -1,160 +1,199 @@ -define(['dialogHelper', 'globalize', 'layoutManager', 'mediaInfo', 'apphost', 'connectionManager', 'require', 'loading', 'scrollHelper', 'imageLoader', 'datetime', 'scrollStyles', 'emby-button', 'emby-checkbox', 'emby-input', 'emby-select', 'paper-icon-button-light', 'css!./../formdialog', 'css!./recordingcreator', 'material-icons', 'flexStyles'], function (dialogHelper, globalize, layoutManager, mediaInfo, appHost, connectionManager, require, loading, scrollHelper, imageLoader, datetime) { - 'use strict'; +import dialogHelper from 'dialogHelper'; +import globalize from 'globalize'; +import layoutManager from 'layoutManager'; +import loading from 'loading'; +import scrollHelper from 'scrollHelper'; +import datetime from 'datetime'; +import 'scrollStyles'; +import 'emby-button'; +import 'emby-checkbox'; +import 'emby-input'; +import 'emby-select'; +import 'paper-icon-button-light'; +import 'css!./../formdialog'; +import 'css!./recordingcreator'; +import 'material-icons'; +import 'flexStyles'; - var currentDialog; - var recordingUpdated = false; - var recordingDeleted = false; - var currentItemId; - var currentServerId; +/*eslint prefer-const: "error"*/ - function deleteTimer(apiClient, timerId) { +let currentDialog; +let recordingUpdated = false; +let recordingDeleted = false; +let currentItemId; +let currentServerId; - return new Promise(function (resolve, reject) { - - require(['recordingHelper'], function (recordingHelper) { - - recordingHelper.cancelSeriesTimerWithConfirmation(timerId, apiClient.serverId()).then(resolve, reject); - }); +function deleteTimer(apiClient, timerId) { + return new Promise(function (resolve, reject) { + import('recordingHelper').then(({ default: recordingHelper }) => { + recordingHelper.cancelSeriesTimerWithConfirmation(timerId, apiClient.serverId()).then(resolve, reject); }); + }); +} + +function renderTimer(context, item) { + context.querySelector('#txtPrePaddingMinutes').value = item.PrePaddingSeconds / 60; + context.querySelector('#txtPostPaddingMinutes').value = item.PostPaddingSeconds / 60; + + context.querySelector('.selectChannels').value = item.RecordAnyChannel ? 'all' : 'one'; + context.querySelector('.selectAirTime').value = item.RecordAnyTime ? 'any' : 'original'; + + context.querySelector('.selectShowType').value = item.RecordNewOnly ? 'new' : 'all'; + context.querySelector('.chkSkipEpisodesInLibrary').checked = item.SkipEpisodesInLibrary; + context.querySelector('.selectKeepUpTo').value = item.KeepUpTo || 0; + + if (item.ChannelName || item.ChannelNumber) { + context.querySelector('.optionChannelOnly').innerHTML = globalize.translate('ChannelNameOnly', item.ChannelName || item.ChannelNumber); + } else { + context.querySelector('.optionChannelOnly').innerHTML = globalize.translate('OneChannel'); } - function renderTimer(context, item, apiClient) { + context.querySelector('.optionAroundTime').innerHTML = globalize.translate('AroundTime', datetime.getDisplayTime(datetime.parseISO8601Date(item.StartDate))); - var program = item.ProgramInfo || {}; + loading.hide(); +} - context.querySelector('#txtPrePaddingMinutes').value = item.PrePaddingSeconds / 60; - context.querySelector('#txtPostPaddingMinutes').value = item.PostPaddingSeconds / 60; +function closeDialog(isDeleted) { + recordingUpdated = true; + recordingDeleted = isDeleted; - context.querySelector('.selectChannels').value = item.RecordAnyChannel ? 'all' : 'one'; - context.querySelector('.selectAirTime').value = item.RecordAnyTime ? 'any' : 'original'; + dialogHelper.close(currentDialog); +} - context.querySelector('.selectShowType').value = item.RecordNewOnly ? 'new' : 'all'; - context.querySelector('.chkSkipEpisodesInLibrary').checked = item.SkipEpisodesInLibrary; - context.querySelector('.selectKeepUpTo').value = item.KeepUpTo || 0; +function onSubmit(e) { + const form = this; - if (item.ChannelName || item.ChannelNumber) { - context.querySelector('.optionChannelOnly').innerHTML = globalize.translate('ChannelNameOnly', item.ChannelName || item.ChannelNumber); - } else { - context.querySelector('.optionChannelOnly').innerHTML = globalize.translate('OneChannel'); - } + const apiClient = window.connectionManager.getApiClient(currentServerId); - context.querySelector('.optionAroundTime').innerHTML = globalize.translate('AroundTime', datetime.getDisplayTime(datetime.parseISO8601Date(item.StartDate))); + apiClient.getLiveTvSeriesTimer(currentItemId).then(function (item) { + item.PrePaddingSeconds = form.querySelector('#txtPrePaddingMinutes').value * 60; + item.PostPaddingSeconds = form.querySelector('#txtPostPaddingMinutes').value * 60; + item.RecordAnyChannel = form.querySelector('.selectChannels').value === 'all'; + item.RecordAnyTime = form.querySelector('.selectAirTime').value === 'any'; + item.RecordNewOnly = form.querySelector('.selectShowType').value === 'new'; + item.SkipEpisodesInLibrary = form.querySelector('.chkSkipEpisodesInLibrary').checked; + item.KeepUpTo = form.querySelector('.selectKeepUpTo').value; + apiClient.updateLiveTvSeriesTimer(item); + }); + + e.preventDefault(); + + // Disable default form submission + return false; +} + +function init(context) { + fillKeepUpTo(context); + + context.querySelector('.btnCancel').addEventListener('click', function () { + closeDialog(false); + }); + + context.querySelector('.btnCancelRecording').addEventListener('click', function () { + const apiClient = window.connectionManager.getApiClient(currentServerId); + deleteTimer(apiClient, currentItemId).then(function () { + closeDialog(true); + }); + }); + + context.querySelector('form').addEventListener('submit', onSubmit); +} + +function reload(context, id) { + const apiClient = window.connectionManager.getApiClient(currentServerId); + + loading.show(); + if (typeof id === 'string') { + currentItemId = id; + + apiClient.getLiveTvSeriesTimer(id).then(function (result) { + renderTimer(context, result); + loading.hide(); + }); + } else if (id) { + currentItemId = id.Id; + + renderTimer(context, id); loading.hide(); } +} - function closeDialog(isDeleted) { +function fillKeepUpTo(context) { + let html = ''; - recordingUpdated = true; - recordingDeleted = isDeleted; + for (let i = 0; i <= 50; i++) { + let text; - dialogHelper.close(currentDialog); - } - - function onSubmit(e) { - - var form = this; - - var apiClient = connectionManager.getApiClient(currentServerId); - - apiClient.getLiveTvSeriesTimer(currentItemId).then(function (item) { - - item.PrePaddingSeconds = form.querySelector('#txtPrePaddingMinutes').value * 60; - item.PostPaddingSeconds = form.querySelector('#txtPostPaddingMinutes').value * 60; - item.RecordAnyChannel = form.querySelector('.selectChannels').value === 'all'; - item.RecordAnyTime = form.querySelector('.selectAirTime').value === 'any'; - item.RecordNewOnly = form.querySelector('.selectShowType').value === 'new'; - item.SkipEpisodesInLibrary = form.querySelector('.chkSkipEpisodesInLibrary').checked; - item.KeepUpTo = form.querySelector('.selectKeepUpTo').value; - - apiClient.updateLiveTvSeriesTimer(item); - }); - - e.preventDefault(); - - // Disable default form submission - return false; - } - - function init(context) { - - fillKeepUpTo(context); - - context.querySelector('.btnCancel').addEventListener('click', function () { - - closeDialog(false); - }); - - context.querySelector('.btnCancelRecording').addEventListener('click', function () { - - var apiClient = connectionManager.getApiClient(currentServerId); - deleteTimer(apiClient, currentItemId).then(function () { - closeDialog(true); - }); - }); - - context.querySelector('form').addEventListener('submit', onSubmit); - } - - function reload(context, id) { - - var apiClient = connectionManager.getApiClient(currentServerId); - - loading.show(); - if (typeof id === 'string') { - currentItemId = id; - - apiClient.getLiveTvSeriesTimer(id).then(function (result) { - - renderTimer(context, result, apiClient); - loading.hide(); - }); - } else if (id) { - - currentItemId = id.Id; - - renderTimer(context, id, apiClient); - loading.hide(); - } - } - - function fillKeepUpTo(context) { - - var html = ''; - - for (var i = 0; i <= 50; i++) { - - var text; - - if (i === 0) { - text = globalize.translate('AsManyAsPossible'); - } else if (i === 1) { - text = globalize.translate('ValueOneEpisode'); - } else { - text = globalize.translate('ValueEpisodeCount', i); - } - - html += ''; + if (i === 0) { + text = globalize.translate('AsManyAsPossible'); + } else if (i === 1) { + text = globalize.translate('ValueOneEpisode'); + } else { + text = globalize.translate('ValueEpisodeCount', i); } - context.querySelector('.selectKeepUpTo').innerHTML = html; + html += ''; } - function onFieldChange(e) { - this.querySelector('.btnSubmit').click(); - } + context.querySelector('.selectKeepUpTo').innerHTML = html; +} - function embed(itemId, serverId, options) { +function onFieldChange() { + this.querySelector('.btnSubmit').click(); +} +function embed(itemId, serverId, options) { + recordingUpdated = false; + recordingDeleted = false; + currentServerId = serverId; + loading.show(); + options = options || {}; + + import('text!./seriesrecordingeditor.template.html').then(({ default: template }) => { + const dialogOptions = { + removeOnClose: true, + scrollY: false + }; + + if (layoutManager.tv) { + dialogOptions.size = 'fullscreen'; + } else { + dialogOptions.size = 'small'; + } + + const dlg = options.context; + + dlg.classList.add('hide'); + dlg.innerHTML = globalize.translateHtml(template, 'core'); + + dlg.querySelector('.formDialogHeader').classList.add('hide'); + dlg.querySelector('.formDialogFooter').classList.add('hide'); + dlg.querySelector('.formDialogContent').className = ''; + dlg.querySelector('.dialogContentInner').className = ''; + dlg.classList.remove('hide'); + + dlg.removeEventListener('change', onFieldChange); + dlg.addEventListener('change', onFieldChange); + + currentDialog = dlg; + + init(dlg); + + reload(dlg, itemId); + }); +} + +function showEditor(itemId, serverId, options) { + return new Promise(function (resolve, reject) { recordingUpdated = false; recordingDeleted = false; currentServerId = serverId; loading.show(); options = options || {}; - require(['text!./seriesrecordingeditor.template.html'], function (template) { - - var dialogOptions = { + import('text!./seriesrecordingeditor.template.html').then(({ default: template }) => { + const dialogOptions = { removeOnClose: true, scrollY: false }; @@ -165,106 +204,58 @@ define(['dialogHelper', 'globalize', 'layoutManager', 'mediaInfo', 'apphost', 'c dialogOptions.size = 'small'; } - var dlg = options.context; + const dlg = dialogHelper.createDialog(dialogOptions); - dlg.classList.add('hide'); - dlg.innerHTML = globalize.translateHtml(template, 'core'); + dlg.classList.add('formDialog'); + dlg.classList.add('recordingDialog'); - dlg.querySelector('.formDialogHeader').classList.add('hide'); - dlg.querySelector('.formDialogFooter').classList.add('hide'); - dlg.querySelector('.formDialogContent').className = ''; - dlg.querySelector('.dialogContentInner').className = ''; - dlg.classList.remove('hide'); + if (!layoutManager.tv) { + dlg.style['min-width'] = '20%'; + } - dlg.removeEventListener('change', onFieldChange); - dlg.addEventListener('change', onFieldChange); + let html = ''; + + html += globalize.translateHtml(template, 'core'); + + dlg.innerHTML = html; + + if (options.enableCancel === false) { + dlg.querySelector('.formDialogFooter').classList.add('hide'); + } currentDialog = dlg; + dlg.addEventListener('closing', function () { + if (!recordingDeleted) { + this.querySelector('.btnSubmit').click(); + } + }); + + dlg.addEventListener('close', function () { + if (recordingUpdated) { + resolve({ + updated: true, + deleted: recordingDeleted + }); + } else { + reject(); + } + }); + + if (layoutManager.tv) { + scrollHelper.centerFocus.on(dlg.querySelector('.formDialogContent'), false); + } + init(dlg); reload(dlg, itemId); + + dialogHelper.open(dlg); }); - } + }); +} - function showEditor(itemId, serverId, options) { - - return new Promise(function (resolve, reject) { - - recordingUpdated = false; - recordingDeleted = false; - currentServerId = serverId; - loading.show(); - options = options || {}; - - require(['text!./seriesrecordingeditor.template.html'], function (template) { - - var dialogOptions = { - removeOnClose: true, - scrollY: false - }; - - if (layoutManager.tv) { - dialogOptions.size = 'fullscreen'; - } else { - dialogOptions.size = 'small'; - } - - var dlg = dialogHelper.createDialog(dialogOptions); - - dlg.classList.add('formDialog'); - dlg.classList.add('recordingDialog'); - - if (!layoutManager.tv) { - dlg.style['min-width'] = '20%'; - } - - var html = ''; - - html += globalize.translateHtml(template, 'core'); - - dlg.innerHTML = html; - - if (options.enableCancel === false) { - dlg.querySelector('.formDialogFooter').classList.add('hide'); - } - - currentDialog = dlg; - - dlg.addEventListener('closing', function () { - - if (!recordingDeleted) { - this.querySelector('.btnSubmit').click(); - } - }); - - dlg.addEventListener('close', function () { - - if (recordingUpdated) { - resolve({ - updated: true, - deleted: recordingDeleted - }); - } else { - reject(); - } - }); - - if (layoutManager.tv) { - scrollHelper.centerFocus.on(dlg.querySelector('.formDialogContent'), false); - } - - init(dlg); - - reload(dlg, itemId); - - dialogHelper.open(dlg); - }); - }); - } - - return { - show: showEditor, - embed: embed - }; -}); +export default { + show: showEditor, + embed: embed +}; diff --git a/src/components/refreshdialog/refreshdialog.js b/src/components/refreshdialog/refreshdialog.js index 3edb725c4a..e5ceb1e6e0 100644 --- a/src/components/refreshdialog/refreshdialog.js +++ b/src/components/refreshdialog/refreshdialog.js @@ -1,97 +1,107 @@ -define(['dom', 'shell', 'dialogHelper', 'loading', 'layoutManager', 'connectionManager', 'appRouter', 'globalize', 'emby-input', 'emby-checkbox', 'paper-icon-button-light', 'emby-select', 'material-icons', 'css!./../formdialog', 'emby-button'], function (dom, shell, dialogHelper, loading, layoutManager, connectionManager, appRouter, globalize) { - 'use strict'; +import dom from 'dom'; +import dialogHelper from 'dialogHelper'; +import loading from 'loading'; +import layoutManager from 'layoutManager'; +import globalize from 'globalize'; +import 'emby-input'; +import 'emby-checkbox'; +import 'paper-icon-button-light'; +import 'emby-select'; +import 'material-icons'; +import 'css!./../formdialog'; +import 'emby-button'; - function getEditorHtml() { +/*eslint prefer-const: "error"*/ - var html = ''; +function getEditorHtml() { + let html = ''; - html += '
'; - html += '
'; - html += '
'; + html += '
'; + html += '
'; + html += ''; - html += '
'; - html += ''; - html += '
'; + html += '
'; + html += ''; + html += '
'; - html += ''; + html += ''; - html += '
'; - html += globalize.translate('RefreshDialogHelp'); - html += '
'; + html += '
'; + html += globalize.translate('RefreshDialogHelp'); + html += '
'; - html += ''; + html += ''; - html += '
'; - html += '
'; - html += ''; - html += '
'; + html += '
'; + html += '
'; + html += ''; + html += '
'; - html += ''; - html += '
'; - html += '
'; + html += ''; + html += '
'; + html += '
'; - return html; - } + return html; +} - function centerFocus(elem, horiz, on) { - require(['scrollHelper'], function (scrollHelper) { - var fn = on ? 'on' : 'off'; - scrollHelper.centerFocus[fn](elem, horiz); +function centerFocus(elem, horiz, on) { + import('scrollHelper').then(({default: scrollHelper}) => { + const fn = on ? 'on' : 'off'; + scrollHelper.centerFocus[fn](elem, horiz); + }); +} + +function onSubmit(e) { + loading.show(); + + const instance = this; + const dlg = dom.parentWithClass(e.target, 'dialog'); + const options = instance.options; + + const apiClient = window.connectionManager.getApiClient(options.serverId); + + const replaceAllMetadata = dlg.querySelector('#selectMetadataRefreshMode').value === 'all'; + + const mode = dlg.querySelector('#selectMetadataRefreshMode').value === 'scan' ? 'Default' : 'FullRefresh'; + const replaceAllImages = mode === 'FullRefresh' && dlg.querySelector('.chkReplaceImages').checked; + + options.itemIds.forEach(function (itemId) { + apiClient.refreshItem(itemId, { + + Recursive: true, + ImageRefreshMode: mode, + MetadataRefreshMode: mode, + ReplaceAllImages: replaceAllImages, + ReplaceAllMetadata: replaceAllMetadata }); - } + }); - function onSubmit(e) { + dialogHelper.close(dlg); - loading.show(); + import('toast').then(({default: toast}) => { + toast(globalize.translate('RefreshQueued')); + }); - var instance = this; - var dlg = dom.parentWithClass(e.target, 'dialog'); - var options = instance.options; + loading.hide(); - var apiClient = connectionManager.getApiClient(options.serverId); + e.preventDefault(); + return false; +} - var replaceAllMetadata = dlg.querySelector('#selectMetadataRefreshMode').value === 'all'; - - var mode = dlg.querySelector('#selectMetadataRefreshMode').value === 'scan' ? 'Default' : 'FullRefresh'; - var replaceAllImages = mode === 'FullRefresh' && dlg.querySelector('.chkReplaceImages').checked; - - options.itemIds.forEach(function (itemId) { - apiClient.refreshItem(itemId, { - - Recursive: true, - ImageRefreshMode: mode, - MetadataRefreshMode: mode, - ReplaceAllImages: replaceAllImages, - ReplaceAllMetadata: replaceAllMetadata - }); - }); - - dialogHelper.close(dlg); - - require(['toast'], function (toast) { - toast(globalize.translate('RefreshQueued')); - }); - - loading.hide(); - - e.preventDefault(); - return false; - } - - function RefreshDialog(options) { +class RefreshDialog { + constructor(options) { this.options = options; } - RefreshDialog.prototype.show = function () { - - var dialogOptions = { + show() { + const dialogOptions = { removeOnClose: true, scrollY: false }; @@ -102,12 +112,12 @@ define(['dom', 'shell', 'dialogHelper', 'loading', 'layoutManager', 'connectionM dialogOptions.size = 'small'; } - var dlg = dialogHelper.createDialog(dialogOptions); + const dlg = dialogHelper.createDialog(dialogOptions); dlg.classList.add('formDialog'); - var html = ''; - var title = globalize.translate('RefreshMetadata'); + let html = ''; + const title = globalize.translate('RefreshMetadata'); html += '
'; html += ''; @@ -124,7 +134,6 @@ define(['dom', 'shell', 'dialogHelper', 'loading', 'layoutManager', 'connectionM dlg.querySelector('form').addEventListener('submit', onSubmit.bind(this)); dlg.querySelector('#selectMetadataRefreshMode').addEventListener('change', function () { - if (this.value === 'scan') { dlg.querySelector('.fldReplaceExistingImages').classList.add('hide'); } else { @@ -139,7 +148,6 @@ define(['dom', 'shell', 'dialogHelper', 'loading', 'layoutManager', 'connectionM dlg.querySelector('#selectMetadataRefreshMode').dispatchEvent(new CustomEvent('change')); dlg.querySelector('.btnCancel').addEventListener('click', function () { - dialogHelper.close(dlg); }); @@ -148,7 +156,6 @@ define(['dom', 'shell', 'dialogHelper', 'loading', 'layoutManager', 'connectionM } return new Promise(function (resolve, reject) { - if (layoutManager.tv) { centerFocus(dlg.querySelector('.formDialogContent'), false, false); } @@ -156,7 +163,7 @@ define(['dom', 'shell', 'dialogHelper', 'loading', 'layoutManager', 'connectionM dlg.addEventListener('close', resolve); dialogHelper.open(dlg); }); - }; + } +} - return RefreshDialog; -}); +export default RefreshDialog; diff --git a/src/components/remotecontrol/remotecontrol.css b/src/components/remotecontrol/remotecontrol.css index c260799585..1c31b4382b 100644 --- a/src/components/remotecontrol/remotecontrol.css +++ b/src/components/remotecontrol/remotecontrol.css @@ -222,18 +222,10 @@ margin: 0; } -.layout-mobile .nowPlayingSecondaryButtons .btnShuffleQueue { - display: none; -} - .layout-mobile .nowPlayingSecondaryButtons .volumecontrol { display: none; } -.layout-mobile .nowPlayingSecondaryButtons .btnRepeat { - display: none; -} - .layout-desktop .nowPlayingInfoButtons .btnRepeat, .layout-tv .nowPlayingInfoButtons .btnRepeat { display: none; @@ -362,7 +354,8 @@ border-radius: 0; } - .nowPlayingInfoButtons .btnRepeat { + .nowPlayingInfoButtons .btnRepeat, + .nowPlayingInfoButtons .btnRewind { position: absolute; left: 0; margin-left: 0; @@ -370,7 +363,8 @@ font-size: smaller; } - .nowPlayingInfoButtons .btnShuffleQueue { + .nowPlayingInfoButtons .btnShuffleQueue, + .nowPlayingInfoButtons .btnFastForward { position: absolute; right: 0; margin-right: 0; @@ -468,7 +462,6 @@ } @media all and (max-width: 63em) { - .nowPlayingSecondaryButtons .repeatToggleButton, .nowPlayingInfoButtons .playlist .listItemMediaInfo, .nowPlayingInfoButtons .btnStop { display: none !important; diff --git a/src/components/remotecontrol/remotecontrol.js b/src/components/remotecontrol/remotecontrol.js index 874137155c..ac9ceaae00 100644 --- a/src/components/remotecontrol/remotecontrol.js +++ b/src/components/remotecontrol/remotecontrol.js @@ -1,937 +1,957 @@ -define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageLoader', 'playbackManager', 'nowPlayingHelper', 'events', 'connectionManager', 'apphost', 'globalize', 'layoutManager', 'userSettings', 'cardBuilder', 'itemContextMenu', 'cardStyle', 'emby-itemscontainer', 'css!./remotecontrol.css', 'emby-ratingbutton'], function (browser, datetime, backdrop, libraryBrowser, listView, imageLoader, playbackManager, nowPlayingHelper, events, connectionManager, appHost, globalize, layoutManager, userSettings, cardBuilder, itemContextMenu) { - 'use strict'; - var showMuteButton = true; - var showVolumeSlider = true; +import datetime from 'datetime'; +import backdrop from 'backdrop'; +import listView from 'listView'; +import imageLoader from 'imageLoader'; +import playbackManager from 'playbackManager'; +import nowPlayingHelper from 'nowPlayingHelper'; +import events from 'events'; +import appHost from 'apphost'; +import globalize from 'globalize'; +import layoutManager from 'layoutManager'; +import * as userSettings from 'userSettings'; +import cardBuilder from 'cardBuilder'; +import itemContextMenu from 'itemContextMenu'; +import 'cardStyle'; +import 'emby-itemscontainer'; +import 'css!./remotecontrol.css'; +import 'emby-ratingbutton'; - function showAudioMenu(context, player, button, item) { - var currentIndex = playbackManager.getAudioStreamIndex(player); - var streams = playbackManager.audioTracks(player); - var menuItems = streams.map(function (s) { - var menuItem = { - name: s.DisplayTitle, - id: s.Index - }; +/*eslint prefer-const: "error"*/ - if (s.Index == currentIndex) { - menuItem.selected = true; - } +let showMuteButton = true; +let showVolumeSlider = true; - return menuItem; - }); +function showAudioMenu(context, player, button, item) { + const currentIndex = playbackManager.getAudioStreamIndex(player); + const streams = playbackManager.audioTracks(player); + const menuItems = streams.map(function (s) { + const menuItem = { + name: s.DisplayTitle, + id: s.Index + }; - require(['actionsheet'], function (actionsheet) { - actionsheet.show({ - items: menuItems, - positionTo: button, - callback: function (id) { - playbackManager.setAudioStreamIndex(parseInt(id), player); - } - }); - }); - } - - function showSubtitleMenu(context, player, button, item) { - var currentIndex = playbackManager.getSubtitleStreamIndex(player); - var streams = playbackManager.subtitleTracks(player); - var menuItems = streams.map(function (s) { - var menuItem = { - name: s.DisplayTitle, - id: s.Index - }; - - if (s.Index == currentIndex) { - menuItem.selected = true; - } - - return menuItem; - }); - menuItems.unshift({ - id: -1, - name: globalize.translate('ButtonOff'), - selected: null == currentIndex - }); - - require(['actionsheet'], function (actionsheet) { - actionsheet.show({ - items: menuItems, - positionTo: button, - callback: function (id) { - playbackManager.setSubtitleStreamIndex(parseInt(id), player); - } - }); - }); - } - - function getNowPlayingNameHtml(nowPlayingItem, includeNonNameInfo) { - return nowPlayingHelper.getNowPlayingNames(nowPlayingItem, includeNonNameInfo).map(function (i) { - return i.text; - }).join('
'); - } - - function seriesImageUrl(item, options) { - if ('Episode' !== item.Type) { - return null; + if (s.Index == currentIndex) { + menuItem.selected = true; } - options = options || {}; - options.type = options.type || 'Primary'; - if ('Primary' === options.type && item.SeriesPrimaryImageTag) { - options.tag = item.SeriesPrimaryImageTag; - return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); + return menuItem; + }); + + import('actionsheet').then(({ default: actionsheet }) => { + actionsheet.show({ + items: menuItems, + positionTo: button, + callback: function (id) { + playbackManager.setAudioStreamIndex(parseInt(id), player); + } + }); + }); +} + +function showSubtitleMenu(context, player, button, item) { + const currentIndex = playbackManager.getSubtitleStreamIndex(player); + const streams = playbackManager.subtitleTracks(player); + const menuItems = streams.map(function (s) { + const menuItem = { + name: s.DisplayTitle, + id: s.Index + }; + + if (s.Index == currentIndex) { + menuItem.selected = true; } - if ('Thumb' === options.type) { - if (item.SeriesThumbImageTag) { - options.tag = item.SeriesThumbImageTag; - return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); - } + return menuItem; + }); + menuItems.unshift({ + id: -1, + name: globalize.translate('Off'), + selected: currentIndex == null + }); - if (item.ParentThumbImageTag) { - options.tag = item.ParentThumbImageTag; - return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.ParentThumbItemId, options); + import('actionsheet').then(({ default: actionsheet }) => { + actionsheet.show({ + items: menuItems, + positionTo: button, + callback: function (id) { + playbackManager.setSubtitleStreamIndex(parseInt(id), player); } - } + }); + }); +} +function getNowPlayingNameHtml(nowPlayingItem, includeNonNameInfo) { + return nowPlayingHelper.getNowPlayingNames(nowPlayingItem, includeNonNameInfo).map(function (i) { + return i.text; + }).join('
'); +} + +function seriesImageUrl(item, options) { + if (item.Type !== 'Episode') { return null; } - function imageUrl(item, options) { - options = options || {}; - options.type = options.type || 'Primary'; - - if (item.ImageTags && item.ImageTags[options.type]) { - options.tag = item.ImageTags[options.type]; - return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.PrimaryImageItemId || item.Id, options); - } - - if (item.AlbumId && item.AlbumPrimaryImageTag) { - options.tag = item.AlbumPrimaryImageTag; - return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.AlbumId, options); - } - - return null; + options = options || {}; + options.type = options.type || 'Primary'; + if (options.type === 'Primary' && item.SeriesPrimaryImageTag) { + options.tag = item.SeriesPrimaryImageTag; + return window.connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); } - function updateNowPlayingInfo(context, state, serverId) { - var item = state.NowPlayingItem; - var displayName = item ? getNowPlayingNameHtml(item).replace('
', ' - ') : ''; - if (typeof item !== 'undefined') { - var nowPlayingServerId = (item.ServerId || serverId); - if (item.Type == 'Audio' || item.MediaStreams[0].Type == 'Audio') { - var songName = item.Name; - if (item.Album != null && item.Artists != null) { - var artistsSeries = ''; - var albumName = item.Album; - if (item.ArtistItems != null) { - for (const artist of item.ArtistItems) { - let artistName = artist.Name; - let artistId = artist.Id; - artistsSeries += `
${artistName}`; - if (artist !== item.ArtistItems.slice(-1)[0]) { - artistsSeries += ', '; - } - } - } else if (item.Artists) { - // For some reason, Chromecast Player doesn't return a item.ArtistItems object, so we need to fallback - // to normal item.Artists item. - // TODO: Normalise fields returned by all the players - for (const artist of item.Artists) { - artistsSeries += `${artist}`; - if (artist !== item.Artists.slice(-1)[0]) { - artistsSeries += ', '; - } + if (options.type === 'Thumb') { + if (item.SeriesThumbImageTag) { + options.tag = item.SeriesThumbImageTag; + return window.connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); + } + + if (item.ParentThumbImageTag) { + options.tag = item.ParentThumbImageTag; + return window.connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.ParentThumbItemId, options); + } + } + + return null; +} + +function imageUrl(item, options) { + options = options || {}; + options.type = options.type || 'Primary'; + + if (item.ImageTags && item.ImageTags[options.type]) { + options.tag = item.ImageTags[options.type]; + return window.connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.PrimaryImageItemId || item.Id, options); + } + + if (item.AlbumId && item.AlbumPrimaryImageTag) { + options.tag = item.AlbumPrimaryImageTag; + return window.connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.AlbumId, options); + } + + return null; +} + +function updateNowPlayingInfo(context, state, serverId) { + const item = state.NowPlayingItem; + const displayName = item ? getNowPlayingNameHtml(item).replace('
', ' - ') : ''; + if (item) { + const nowPlayingServerId = (item.ServerId || serverId); + if (item.Type == 'Audio' || item.MediaStreams[0].Type == 'Audio') { + const songName = item.Name; + let artistsSeries = ''; + let albumName = ''; + if (item.Artists != null) { + if (item.ArtistItems != null) { + for (const artist of item.ArtistItems) { + const artistName = artist.Name; + const artistId = artist.Id; + artistsSeries += `${artistName}`; + if (artist !== item.ArtistItems.slice(-1)[0]) { + artistsSeries += ', '; } } - context.querySelector('.nowPlayingArtist').innerHTML = artistsSeries; - context.querySelector('.nowPlayingAlbum').innerHTML = '${albumName}`; - } - context.querySelector('.nowPlayingSongName').innerHTML = songName; - } else if (item.Type == 'Episode') { - if (item.SeasonName != null) { - var seasonName = item.SeasonName; - context.querySelector('.nowPlayingSeason').innerHTML = '${seasonName}`; - } - if (item.SeriesName != null) { - var seriesName = item.SeriesName; - if (item.SeriesId != null) { - context.querySelector('.nowPlayingSerie').innerHTML = '${seriesName}`; - } else { - context.querySelector('.nowPlayingSerie').innerHTML = seriesName; - } - } - context.querySelector('.nowPlayingEpisode').innerHTML = item.Name; - } else { - context.querySelector('.nowPlayingPageTitle').innerHTML = displayName; - } - - if (displayName.length > 0 && item.Type != 'Audio' && item.Type != 'Episode') { - context.querySelector('.nowPlayingPageTitle').classList.remove('hide'); - } else { - context.querySelector('.nowPlayingPageTitle').classList.add('hide'); - } - - var url = item ? seriesImageUrl(item, { - maxHeight: 300 * 2 - }) || imageUrl(item, { - maxHeight: 300 * 2 - }) : null; - - let contextButton = context.querySelector('.btnToggleContextMenu'); - // We remove the previous event listener by replacing the item in each update event - const autoFocusContextButton = document.activeElement === contextButton; - let contextButtonClone = contextButton.cloneNode(true); - contextButton.parentNode.replaceChild(contextButtonClone, contextButton); - contextButton = context.querySelector('.btnToggleContextMenu'); - if (autoFocusContextButton) { - contextButton.focus(); - } - const stopPlayback = !!layoutManager.mobile; - var options = { - play: false, - queue: false, - stopPlayback: stopPlayback, - clearQueue: true, - openAlbum: false, - positionTo: contextButton - }; - var apiClient = connectionManager.getApiClient(item.ServerId); - apiClient.getItem(apiClient.getCurrentUserId(), item.Id).then(function (fullItem) { - apiClient.getCurrentUser().then(function (user) { - contextButton.addEventListener('click', function () { - itemContextMenu.show(Object.assign({ - item: fullItem, - user: user - }, options)); - }); - }); - }); - setImageUrl(context, state, url); - if (item) { - backdrop.setBackdrops([item]); - apiClient.getItem(apiClient.getCurrentUserId(), item.Id).then(function (fullItem) { - var userData = fullItem.UserData || {}; - var likes = null == userData.Likes ? '' : userData.Likes; - context.querySelector('.nowPlayingPageUserDataButtonsTitle').innerHTML = ''; - context.querySelector('.nowPlayingPageUserDataButtons').innerHTML = ''; - }); - } else { - backdrop.clearBackdrop(); - context.querySelector('.nowPlayingPageUserDataButtons').innerHTML = ''; - } - } - } - - function setImageUrl(context, state, url) { - currentImgUrl = url; - var item = state.NowPlayingItem; - var imgContainer = context.querySelector('.nowPlayingPageImageContainer'); - - if (url) { - imgContainer.innerHTML = ''; - if (item.Type == 'Audio') { - context.querySelector('.nowPlayingPageImage').classList.add('nowPlayingPageImageAudio'); - context.querySelector('.nowPlayingPageImageContainer').classList.remove('nowPlayingPageImageAudio'); - } else { - context.querySelector('.nowPlayingPageImageContainer').classList.add('nowPlayingPageImagePoster'); - context.querySelector('.nowPlayingPageImage').classList.remove('nowPlayingPageImageAudio'); - } - } else { - imgContainer.innerHTML = '
'; - } - } - - function buttonVisible(btn, enabled) { - if (enabled) { - btn.classList.remove('hide'); - } else { - btn.classList.add('hide'); - } - } - - function updateSupportedCommands(context, commands) { - var all = context.querySelectorAll('.btnCommand'); - - for (var i = 0, length = all.length; i < length; i++) { - var enableButton = -1 !== commands.indexOf(all[i].getAttribute('data-command')); - all[i].disabled = !enableButton; - } - } - - var currentImgUrl; - return function () { - function toggleRepeat() { - switch (playbackManager.getRepeatMode()) { - case 'RepeatAll': - playbackManager.setRepeatMode('RepeatOne'); - break; - case 'RepeatOne': - playbackManager.setRepeatMode('RepeatNone'); - break; - case 'RepeatNone': - playbackManager.setRepeatMode('RepeatAll'); - } - } - - function updatePlayerState(player, context, state) { - lastPlayerState = state; - var item = state.NowPlayingItem; - var playerInfo = playbackManager.getPlayerInfo(); - var supportedCommands = playerInfo.supportedCommands; - currentPlayerSupportedCommands = supportedCommands; - var playState = state.PlayState || {}; - var isSupportedCommands = supportedCommands.includes('DisplayMessage') || supportedCommands.includes('SendString') || supportedCommands.includes('Select'); - buttonVisible(context.querySelector('.btnToggleFullscreen'), item && 'Video' == item.MediaType && supportedCommands.includes('ToggleFullscreen')); - updateAudioTracksDisplay(player, context); - updateSubtitleTracksDisplay(player, context); - - if (supportedCommands.includes('DisplayMessage') && !currentPlayer.isLocalPlayer) { - context.querySelector('.sendMessageSection').classList.remove('hide'); - } else { - context.querySelector('.sendMessageSection').classList.add('hide'); - } - - if (supportedCommands.includes('SendString') && !currentPlayer.isLocalPlayer) { - context.querySelector('.sendTextSection').classList.remove('hide'); - } else { - context.querySelector('.sendTextSection').classList.add('hide'); - } - - if (supportedCommands.includes('Select') && !currentPlayer.isLocalPlayer) { - context.querySelector('.navigationSection').classList.remove('hide'); - } else { - context.querySelector('.navigationSection').classList.add('hide'); - } - - if (isSupportedCommands && !currentPlayer.isLocalPlayer) { - context.querySelector('.remoteControlSection').classList.remove('hide'); - } else { - context.querySelector('.remoteControlSection').classList.add('hide'); - } - - buttonVisible(context.querySelector('.btnStop'), null != item); - buttonVisible(context.querySelector('.btnNextTrack'), null != item); - buttonVisible(context.querySelector('.btnPreviousTrack'), null != item); - if (layoutManager.mobile) { - buttonVisible(context.querySelector('.btnRewind'), false); - buttonVisible(context.querySelector('.btnFastForward'), false); - } else { - buttonVisible(context.querySelector('.btnRewind'), null != item); - buttonVisible(context.querySelector('.btnFastForward'), null != item); - } - var positionSlider = context.querySelector('.nowPlayingPositionSlider'); - - if (positionSlider && item && item.RunTimeTicks) { - positionSlider.setKeyboardSteps(userSettings.skipBackLength() * 1000000 / item.RunTimeTicks, - userSettings.skipForwardLength() * 1000000 / item.RunTimeTicks); - } - - if (positionSlider && !positionSlider.dragging) { - positionSlider.disabled = !playState.CanSeek; - var isProgressClear = state.MediaSource && null == state.MediaSource.RunTimeTicks; - positionSlider.setIsClear(isProgressClear); - } - - updatePlayPauseState(playState.IsPaused, null != item); - updateTimeDisplay(playState.PositionTicks, item ? item.RunTimeTicks : null); - updatePlayerVolumeState(context, playState.IsMuted, playState.VolumeLevel); - - if (item && 'Video' == item.MediaType) { - context.classList.remove('hideVideoButtons'); - } else { - context.classList.add('hideVideoButtons'); - } - - updateRepeatModeDisplay(playbackManager.getRepeatMode()); - onShuffleQueueModeChange(false); - updateNowPlayingInfo(context, state); - } - - function updateAudioTracksDisplay(player, context) { - var supportedCommands = currentPlayerSupportedCommands; - buttonVisible(context.querySelector('.btnAudioTracks'), playbackManager.audioTracks(player).length > 1 && -1 != supportedCommands.indexOf('SetAudioStreamIndex')); - } - - function updateSubtitleTracksDisplay(player, context) { - var supportedCommands = currentPlayerSupportedCommands; - buttonVisible(context.querySelector('.btnSubtitles'), playbackManager.subtitleTracks(player).length && -1 != supportedCommands.indexOf('SetSubtitleStreamIndex')); - } - - function updateRepeatModeDisplay(repeatMode) { - var context = dlg; - let toggleRepeatButtons = context.querySelectorAll('.repeatToggleButton'); - const cssClass = 'buttonActive'; - let innHtml = ''; - let repeatOn = true; - - switch (repeatMode) { - case 'RepeatAll': - break; - case 'RepeatOne': - innHtml = ''; - break; - case 'RepeatNone': - default: - repeatOn = false; - break; - } - - for (const toggleRepeatButton of toggleRepeatButtons) { - toggleRepeatButton.classList.toggle(cssClass, repeatOn); - toggleRepeatButton.innerHTML = innHtml; - } - } - - function updatePlayerVolumeState(context, isMuted, volumeLevel) { - var view = context; - var supportedCommands = currentPlayerSupportedCommands; - - if (-1 === supportedCommands.indexOf('Mute')) { - showMuteButton = false; - } - - if (-1 === supportedCommands.indexOf('SetVolume')) { - showVolumeSlider = false; - } - - if (currentPlayer.isLocalPlayer && appHost.supports('physicalvolumecontrol')) { - showMuteButton = false; - showVolumeSlider = false; - } - - const buttonMute = view.querySelector('.buttonMute'); - const buttonMuteIcon = buttonMute.querySelector('.material-icons'); - - buttonMuteIcon.classList.remove('volume_off', 'volume_up'); - - if (isMuted) { - buttonMute.setAttribute('title', globalize.translate('Unmute')); - buttonMuteIcon.classList.add('volume_off'); - } else { - buttonMute.setAttribute('title', globalize.translate('Mute')); - buttonMuteIcon.classList.add('volume_up'); - } - - if (!showMuteButton && !showVolumeSlider) { - context.querySelector('.volumecontrol').classList.add('hide'); - } else { - buttonMute.classList.toggle('hide', !showMuteButton); - - var nowPlayingVolumeSlider = context.querySelector('.nowPlayingVolumeSlider'); - var nowPlayingVolumeSliderContainer = context.querySelector('.nowPlayingVolumeSliderContainer'); - - if (nowPlayingVolumeSlider) { - - nowPlayingVolumeSliderContainer.classList.toggle('hide', !showVolumeSlider); - - if (!nowPlayingVolumeSlider.dragging) { - nowPlayingVolumeSlider.value = volumeLevel || 0; + } else if (item.Artists) { + // For some reason, Chromecast Player doesn't return a item.ArtistItems object, so we need to fallback + // to normal item.Artists item. + // TODO: Normalise fields returned by all the players + for (const artist of item.Artists) { + artistsSeries += `${artist}`; + if (artist !== item.Artists.slice(-1)[0]) { + artistsSeries += ', '; + } } } } - } - - function updatePlayPauseState(isPaused, isActive) { - var context = dlg; - var btnPlayPause = context.querySelector('.btnPlayPause'); - const btnPlayPauseIcon = btnPlayPause.querySelector('.material-icons'); - - btnPlayPauseIcon.classList.remove('play_circle_filled', 'pause_circle_filled'); - btnPlayPauseIcon.classList.add(isPaused ? 'play_circle_filled' : 'pause_circle_filled'); - - buttonVisible(btnPlayPause, isActive); - } - - function updateTimeDisplay(positionTicks, runtimeTicks) { - var context = dlg; - var positionSlider = context.querySelector('.nowPlayingPositionSlider'); - - if (positionSlider && !positionSlider.dragging) { - if (runtimeTicks) { - var pct = positionTicks / runtimeTicks; - pct *= 100; - positionSlider.value = pct; + if (item.Album != null) { + albumName = '` + item.Album + ''; + } + context.querySelector('.nowPlayingAlbum').innerHTML = albumName; + context.querySelector('.nowPlayingArtist').innerHTML = artistsSeries; + context.querySelector('.nowPlayingSongName').innerHTML = songName; + } else if (item.Type == 'Episode') { + if (item.SeasonName != null) { + const seasonName = item.SeasonName; + context.querySelector('.nowPlayingSeason').innerHTML = '${seasonName}`; + } + if (item.SeriesName != null) { + const seriesName = item.SeriesName; + if (item.SeriesId != null) { + context.querySelector('.nowPlayingSerie').innerHTML = '${seriesName}`; } else { - positionSlider.value = 0; + context.querySelector('.nowPlayingSerie').innerHTML = seriesName; } } - - context.querySelector('.positionTime').innerHTML = null == positionTicks ? '--:--' : datetime.getDisplayRunningTime(positionTicks); - context.querySelector('.runtime').innerHTML = null != runtimeTicks ? datetime.getDisplayRunningTime(runtimeTicks) : '--:--'; + context.querySelector('.nowPlayingEpisode').innerHTML = item.Name; + } else { + context.querySelector('.nowPlayingPageTitle').innerHTML = displayName; } - function getPlaylistItems(player) { - return playbackManager.getPlaylist(player); + if (displayName.length > 0 && item.Type != 'Audio' && item.Type != 'Episode') { + context.querySelector('.nowPlayingPageTitle').classList.remove('hide'); + } else { + context.querySelector('.nowPlayingPageTitle').classList.add('hide'); } - function loadPlaylist(context, player) { - getPlaylistItems(player).then(function (items) { - var html = ''; - let favoritesEnabled = true; - if (layoutManager.mobile) { - if (items.length > 0) { - context.querySelector('.btnTogglePlaylist').classList.remove('hide'); - } else { - context.querySelector('.btnTogglePlaylist').classList.add('hide'); - } - favoritesEnabled = false; - } + const url = seriesImageUrl(item, { + maxHeight: 300 + }) || imageUrl(item, { + maxHeight: 300 + }); - html += listView.getListViewHtml({ - items: items, - smallIcon: true, - action: 'setplaylistindex', - enableUserDataButtons: favoritesEnabled, - rightButtons: [{ - icon: 'remove_circle_outline', - title: globalize.translate('ButtonRemove'), - id: 'remove' - }], - dragHandle: true + let contextButton = context.querySelector('.btnToggleContextMenu'); + // We remove the previous event listener by replacing the item in each update event + const autoFocusContextButton = document.activeElement === contextButton; + const contextButtonClone = contextButton.cloneNode(true); + contextButton.parentNode.replaceChild(contextButtonClone, contextButton); + contextButton = context.querySelector('.btnToggleContextMenu'); + if (autoFocusContextButton) { + contextButton.focus(); + } + const stopPlayback = !!layoutManager.mobile; + const options = { + play: false, + queue: false, + stopPlayback: stopPlayback, + clearQueue: true, + openAlbum: false, + positionTo: contextButton + }; + const apiClient = window.connectionManager.getApiClient(item.ServerId); + apiClient.getItem(apiClient.getCurrentUserId(), item.Id).then(function (fullItem) { + apiClient.getCurrentUser().then(function (user) { + contextButton.addEventListener('click', function () { + itemContextMenu.show(Object.assign({ + item: fullItem, + user: user + }, options)); }); - - var itemsContainer = context.querySelector('.playlist'); - let focusedItemPlaylistId = itemsContainer.querySelector('button:focus'); - itemsContainer.innerHTML = html; - if (focusedItemPlaylistId !== null) { - focusedItemPlaylistId = focusedItemPlaylistId.getAttribute('data-playlistitemid'); - const newFocusedItem = itemsContainer.querySelector(`button[data-playlistitemid="${focusedItemPlaylistId}"]`); - if (newFocusedItem !== null) { - newFocusedItem.focus(); - } - } - - var playlistItemId = playbackManager.getCurrentPlaylistItemId(player); - - if (playlistItemId) { - var img = itemsContainer.querySelector(`.listItem[data-playlistItemId="${playlistItemId}"] .listItemImage`); - - if (img) { - img.classList.remove('lazy'); - img.classList.add('playlistIndexIndicatorImage'); - } - } - - imageLoader.lazyChildren(itemsContainer); }); + }); + setImageUrl(context, state, url); + backdrop.setBackdrops([item]); + apiClient.getItem(apiClient.getCurrentUserId(), item.Id).then(function (fullItem) { + const userData = fullItem.UserData || {}; + const likes = userData.Likes == null ? '' : userData.Likes; + context.querySelector('.nowPlayingPageUserDataButtonsTitle').innerHTML = ''; + context.querySelector('.nowPlayingPageUserDataButtons').innerHTML = ''; + }); + } else { + backdrop.clearBackdrop(); + context.querySelector('.nowPlayingPageUserDataButtons').innerHTML = ''; + } +} + +function setImageUrl(context, state, url) { + const item = state.NowPlayingItem; + const imgContainer = context.querySelector('.nowPlayingPageImageContainer'); + + if (url) { + imgContainer.innerHTML = ''; + if (item.Type == 'Audio') { + context.querySelector('.nowPlayingPageImage').classList.add('nowPlayingPageImageAudio'); + context.querySelector('.nowPlayingPageImageContainer').classList.remove('nowPlayingPageImageAudio'); + } else { + context.querySelector('.nowPlayingPageImageContainer').classList.add('nowPlayingPageImagePoster'); + context.querySelector('.nowPlayingPageImage').classList.remove('nowPlayingPageImageAudio'); + } + } else { + imgContainer.innerHTML = '
'; + } +} + +function buttonVisible(btn, enabled) { + if (enabled) { + btn.classList.remove('hide'); + } else { + btn.classList.add('hide'); + } +} + +function updateSupportedCommands(context, commands) { + const all = context.querySelectorAll('.btnCommand'); + + for (let i = 0, length = all.length; i < length; i++) { + const enableButton = commands.indexOf(all[i].getAttribute('data-command')) !== -1; + all[i].disabled = !enableButton; + } +} + +export default function () { + function toggleRepeat() { + switch (playbackManager.getRepeatMode()) { + case 'RepeatAll': + playbackManager.setRepeatMode('RepeatOne'); + break; + case 'RepeatOne': + playbackManager.setRepeatMode('RepeatNone'); + break; + case 'RepeatNone': + playbackManager.setRepeatMode('RepeatAll'); + } + } + + function updatePlayerState(player, context, state) { + lastPlayerState = state; + const item = state.NowPlayingItem; + const playerInfo = playbackManager.getPlayerInfo(); + const supportedCommands = playerInfo.supportedCommands; + currentPlayerSupportedCommands = supportedCommands; + const playState = state.PlayState || {}; + const isSupportedCommands = supportedCommands.includes('DisplayMessage') || supportedCommands.includes('SendString') || supportedCommands.includes('Select'); + buttonVisible(context.querySelector('.btnToggleFullscreen'), item && item.MediaType == 'Video' && supportedCommands.includes('ToggleFullscreen')); + updateAudioTracksDisplay(player, context); + updateSubtitleTracksDisplay(player, context); + + if (supportedCommands.includes('DisplayMessage') && !currentPlayer.isLocalPlayer) { + context.querySelector('.sendMessageSection').classList.remove('hide'); + } else { + context.querySelector('.sendMessageSection').classList.add('hide'); } - function onPlaybackStart(e, state) { - console.debug('remotecontrol event: ' + e.type); - var player = this; - onStateChanged.call(player, e, state); + if (supportedCommands.includes('SendString') && !currentPlayer.isLocalPlayer) { + context.querySelector('.sendTextSection').classList.remove('hide'); + } else { + context.querySelector('.sendTextSection').classList.add('hide'); } - function onRepeatModeChange() { - updateRepeatModeDisplay(playbackManager.getRepeatMode()); + if (supportedCommands.includes('Select') && !currentPlayer.isLocalPlayer) { + context.querySelector('.navigationSection').classList.remove('hide'); + } else { + context.querySelector('.navigationSection').classList.add('hide'); } - function onShuffleQueueModeChange(updateView = true) { - let shuffleMode = playbackManager.getQueueShuffleMode(this); - let context = dlg; - const cssClass = 'buttonActive'; - let shuffleButtons = context.querySelectorAll('.btnShuffleQueue'); + if (isSupportedCommands && !currentPlayer.isLocalPlayer) { + context.querySelector('.remoteControlSection').classList.remove('hide'); + } else { + context.querySelector('.remoteControlSection').classList.add('hide'); + } - for (let shuffleButton of shuffleButtons) { - switch (shuffleMode) { - case 'Shuffle': - shuffleButton.classList.add(cssClass); - break; - case 'Sorted': - default: - shuffleButton.classList.remove(cssClass); - break; + buttonVisible(context.querySelector('.btnStop'), item != null); + buttonVisible(context.querySelector('.btnNextTrack'), item != null); + buttonVisible(context.querySelector('.btnPreviousTrack'), item != null); + if (layoutManager.mobile) { + const playingVideo = playbackManager.isPlayingVideo() && item !== null; + const playingAudio = !playbackManager.isPlayingVideo() && item !== null; + buttonVisible(context.querySelector('.btnRepeat'), playingAudio); + buttonVisible(context.querySelector('.btnShuffleQueue'), playingAudio); + buttonVisible(context.querySelector('.btnRewind'), playingVideo); + buttonVisible(context.querySelector('.btnFastForward'), playingVideo); + buttonVisible(context.querySelector('.nowPlayingSecondaryButtons .btnShuffleQueue'), playingVideo); + buttonVisible(context.querySelector('.nowPlayingSecondaryButtons .btnRepeat'), playingVideo); + } else { + buttonVisible(context.querySelector('.btnRewind'), item != null); + buttonVisible(context.querySelector('.btnFastForward'), item != null); + } + const positionSlider = context.querySelector('.nowPlayingPositionSlider'); + + if (positionSlider && item && item.RunTimeTicks) { + positionSlider.setKeyboardSteps(userSettings.skipBackLength() * 1000000 / item.RunTimeTicks, + userSettings.skipForwardLength() * 1000000 / item.RunTimeTicks); + } + + if (positionSlider && !positionSlider.dragging) { + positionSlider.disabled = !playState.CanSeek; + const isProgressClear = state.MediaSource && state.MediaSource.RunTimeTicks == null; + positionSlider.setIsClear(isProgressClear); + } + + updatePlayPauseState(playState.IsPaused, item != null); + updateTimeDisplay(playState.PositionTicks, item ? item.RunTimeTicks : null); + updatePlayerVolumeState(context, playState.IsMuted, playState.VolumeLevel); + + if (item && item.MediaType == 'Video') { + context.classList.remove('hideVideoButtons'); + } else { + context.classList.add('hideVideoButtons'); + } + + updateRepeatModeDisplay(playbackManager.getRepeatMode()); + onShuffleQueueModeChange(false); + updateNowPlayingInfo(context, state); + } + + function updateAudioTracksDisplay(player, context) { + const supportedCommands = currentPlayerSupportedCommands; + buttonVisible(context.querySelector('.btnAudioTracks'), playbackManager.audioTracks(player).length > 1 && supportedCommands.indexOf('SetAudioStreamIndex') != -1); + } + + function updateSubtitleTracksDisplay(player, context) { + const supportedCommands = currentPlayerSupportedCommands; + buttonVisible(context.querySelector('.btnSubtitles'), playbackManager.subtitleTracks(player).length && supportedCommands.indexOf('SetSubtitleStreamIndex') != -1); + } + + function updateRepeatModeDisplay(repeatMode) { + const context = dlg; + const toggleRepeatButtons = context.querySelectorAll('.repeatToggleButton'); + const cssClass = 'buttonActive'; + let innHtml = ''; + let repeatOn = true; + + switch (repeatMode) { + case 'RepeatAll': + break; + case 'RepeatOne': + innHtml = ''; + break; + case 'RepeatNone': + default: + repeatOn = false; + break; + } + + for (const toggleRepeatButton of toggleRepeatButtons) { + toggleRepeatButton.classList.toggle(cssClass, repeatOn); + toggleRepeatButton.innerHTML = innHtml; + } + } + + function updatePlayerVolumeState(context, isMuted, volumeLevel) { + const view = context; + const supportedCommands = currentPlayerSupportedCommands; + + if (supportedCommands.indexOf('Mute') === -1) { + showMuteButton = false; + } + + if (supportedCommands.indexOf('SetVolume') === -1) { + showVolumeSlider = false; + } + + if (currentPlayer.isLocalPlayer && appHost.supports('physicalvolumecontrol')) { + showMuteButton = false; + showVolumeSlider = false; + } + + const buttonMute = view.querySelector('.buttonMute'); + const buttonMuteIcon = buttonMute.querySelector('.material-icons'); + + buttonMuteIcon.classList.remove('volume_off', 'volume_up'); + + if (isMuted) { + buttonMute.setAttribute('title', globalize.translate('Unmute')); + buttonMuteIcon.classList.add('volume_off'); + } else { + buttonMute.setAttribute('title', globalize.translate('Mute')); + buttonMuteIcon.classList.add('volume_up'); + } + + if (!showMuteButton && !showVolumeSlider) { + context.querySelector('.volumecontrol').classList.add('hide'); + } else { + buttonMute.classList.toggle('hide', !showMuteButton); + + const nowPlayingVolumeSlider = context.querySelector('.nowPlayingVolumeSlider'); + const nowPlayingVolumeSliderContainer = context.querySelector('.nowPlayingVolumeSliderContainer'); + + if (nowPlayingVolumeSlider) { + nowPlayingVolumeSliderContainer.classList.toggle('hide', !showVolumeSlider); + + if (!nowPlayingVolumeSlider.dragging) { + nowPlayingVolumeSlider.value = volumeLevel || 0; } } - - if (updateView) { - onPlaylistUpdate(); - } } + } - function onPlaylistUpdate(e) { - loadPlaylist(dlg, this); - } + function updatePlayPauseState(isPaused, isActive) { + const context = dlg; + const btnPlayPause = context.querySelector('.btnPlayPause'); + const btnPlayPauseIcon = btnPlayPause.querySelector('.material-icons'); - function onPlaylistItemRemoved(e, info) { - var context = dlg; - if (info !== undefined) { - var playlistItemIds = info.playlistItemIds; + btnPlayPauseIcon.classList.remove('play_circle_filled', 'pause_circle_filled'); + btnPlayPauseIcon.classList.add(isPaused ? 'play_circle_filled' : 'pause_circle_filled'); - for (var i = 0, length = playlistItemIds.length; i < length; i++) { - var listItem = context.querySelector('.listItem[data-playlistItemId="' + playlistItemIds[i] + '"]'); + buttonVisible(btnPlayPause, isActive); + } - if (listItem) { - listItem.parentNode.removeChild(listItem); - } - } + function updateTimeDisplay(positionTicks, runtimeTicks) { + const context = dlg; + const positionSlider = context.querySelector('.nowPlayingPositionSlider'); + + if (positionSlider && !positionSlider.dragging) { + if (runtimeTicks) { + let pct = positionTicks / runtimeTicks; + pct *= 100; + positionSlider.value = pct; } else { - onPlaylistUpdate(); + positionSlider.value = 0; } } - function onPlaybackStopped(e, state) { - console.debug('remotecontrol event: ' + e.type); - var player = this; + context.querySelector('.positionTime').innerHTML = positionTicks == null ? '--:--' : datetime.getDisplayRunningTime(positionTicks); + context.querySelector('.runtime').innerHTML = runtimeTicks != null ? datetime.getDisplayRunningTime(runtimeTicks) : '--:--'; + } - if (!state.NextMediaType) { - updatePlayerState(player, dlg, {}); - Emby.Page.back(); + function getPlaylistItems(player) { + return playbackManager.getPlaylist(player); + } + + function loadPlaylist(context, player) { + getPlaylistItems(player).then(function (items) { + let html = ''; + let favoritesEnabled = true; + if (layoutManager.mobile) { + if (items.length > 0) { + context.querySelector('.btnTogglePlaylist').classList.remove('hide'); + } else { + context.querySelector('.btnTogglePlaylist').classList.add('hide'); + } + favoritesEnabled = false; + } + + html += listView.getListViewHtml({ + items: items, + smallIcon: true, + action: 'setplaylistindex', + enableUserDataButtons: favoritesEnabled, + rightButtons: [{ + icon: 'remove_circle_outline', + title: globalize.translate('ButtonRemove'), + id: 'remove' + }], + dragHandle: true + }); + + const itemsContainer = context.querySelector('.playlist'); + let focusedItemPlaylistId = itemsContainer.querySelector('button:focus'); + itemsContainer.innerHTML = html; + if (focusedItemPlaylistId !== null) { + focusedItemPlaylistId = focusedItemPlaylistId.getAttribute('data-playlistitemid'); + const newFocusedItem = itemsContainer.querySelector(`button[data-playlistitemid="${focusedItemPlaylistId}"]`); + if (newFocusedItem !== null) { + newFocusedItem.focus(); + } + } + + const playlistItemId = playbackManager.getCurrentPlaylistItemId(player); + + if (playlistItemId) { + const img = itemsContainer.querySelector(`.listItem[data-playlistItemId="${playlistItemId}"] .listItemImage`); + + if (img) { + img.classList.remove('lazy'); + img.classList.add('playlistIndexIndicatorImage'); + } + } + + imageLoader.lazyChildren(itemsContainer); + }); + } + + function onPlaybackStart(e, state) { + console.debug('remotecontrol event: ' + e.type); + const player = this; + onStateChanged.call(player, e, state); + } + + function onRepeatModeChange() { + updateRepeatModeDisplay(playbackManager.getRepeatMode()); + } + + function onShuffleQueueModeChange(updateView = true) { + const shuffleMode = playbackManager.getQueueShuffleMode(this); + const context = dlg; + const cssClass = 'buttonActive'; + const shuffleButtons = context.querySelectorAll('.btnShuffleQueue'); + + for (const shuffleButton of shuffleButtons) { + switch (shuffleMode) { + case 'Shuffle': + shuffleButton.classList.add(cssClass); + break; + case 'Sorted': + default: + shuffleButton.classList.remove(cssClass); + break; } } - function onPlayPauseStateChanged(e) { - updatePlayPauseState(this.paused(), true); - } - - function onStateChanged(event, state) { - var player = this; - updatePlayerState(player, dlg, state); + if (updateView) { onPlaylistUpdate(); } + } - function onTimeUpdate(e) { - var now = new Date().getTime(); + function onPlaylistUpdate(e) { + loadPlaylist(dlg, this); + } - if (!(now - lastUpdateTime < 700)) { - lastUpdateTime = now; - var player = this; - currentRuntimeTicks = playbackManager.duration(player); - updateTimeDisplay(playbackManager.currentTime(player), currentRuntimeTicks); + function onPlaylistItemRemoved(e, info) { + const context = dlg; + if (info !== undefined) { + const playlistItemIds = info.playlistItemIds; + + for (let i = 0, length = playlistItemIds.length; i < length; i++) { + const listItem = context.querySelector('.listItem[data-playlistItemId="' + playlistItemIds[i] + '"]'); + + if (listItem) { + listItem.parentNode.removeChild(listItem); + } + } + } else { + onPlaylistUpdate(); + } + } + + function onPlaybackStopped(e, state) { + console.debug('remotecontrol event: ' + e.type); + const player = this; + + if (!state.NextMediaType) { + updatePlayerState(player, dlg, {}); + Emby.Page.back(); + } + } + + function onPlayPauseStateChanged(e) { + updatePlayPauseState(this.paused(), true); + } + + function onStateChanged(event, state) { + const player = this; + updatePlayerState(player, dlg, state); + onPlaylistUpdate(); + } + + function onTimeUpdate(e) { + const now = new Date().getTime(); + + if (!(now - lastUpdateTime < 700)) { + lastUpdateTime = now; + const player = this; + currentRuntimeTicks = playbackManager.duration(player); + updateTimeDisplay(playbackManager.currentTime(player) * 10000, currentRuntimeTicks); + } + } + + function onVolumeChanged(e) { + const player = this; + updatePlayerVolumeState(dlg, player.isMuted(), player.getVolume()); + } + + function releaseCurrentPlayer() { + const player = currentPlayer; + + if (player) { + events.off(player, 'playbackstart', onPlaybackStart); + events.off(player, 'statechange', onStateChanged); + events.off(player, 'repeatmodechange', onRepeatModeChange); + events.off(player, 'shufflequeuemodechange', onShuffleQueueModeChange); + events.off(player, 'playlistitemremove', onPlaylistItemRemoved); + events.off(player, 'playlistitemmove', onPlaylistUpdate); + events.off(player, 'playlistitemadd', onPlaylistUpdate); + events.off(player, 'playbackstop', onPlaybackStopped); + events.off(player, 'volumechange', onVolumeChanged); + events.off(player, 'pause', onPlayPauseStateChanged); + events.off(player, 'unpause', onPlayPauseStateChanged); + events.off(player, 'timeupdate', onTimeUpdate); + currentPlayer = null; + } + } + + function bindToPlayer(context, player) { + if (releaseCurrentPlayer(), currentPlayer = player, player) { + const state = playbackManager.getPlayerState(player); + onStateChanged.call(player, { + type: 'init' + }, state); + events.on(player, 'playbackstart', onPlaybackStart); + events.on(player, 'statechange', onStateChanged); + events.on(player, 'repeatmodechange', onRepeatModeChange); + events.on(player, 'shufflequeuemodechange', onShuffleQueueModeChange); + events.on(player, 'playlistitemremove', onPlaylistItemRemoved); + events.on(player, 'playlistitemmove', onPlaylistUpdate); + events.on(player, 'playlistitemadd', onPlaylistUpdate); + events.on(player, 'playbackstop', onPlaybackStopped); + events.on(player, 'volumechange', onVolumeChanged); + events.on(player, 'pause', onPlayPauseStateChanged); + events.on(player, 'unpause', onPlayPauseStateChanged); + events.on(player, 'timeupdate', onTimeUpdate); + const playerInfo = playbackManager.getPlayerInfo(); + const supportedCommands = playerInfo.supportedCommands; + currentPlayerSupportedCommands = supportedCommands; + updateSupportedCommands(context, supportedCommands); + } + } + + function onBtnCommandClick() { + if (currentPlayer) { + if (this.classList.contains('repeatToggleButton')) { + toggleRepeat(); + } else { + playbackManager.sendCommand({ + Name: this.getAttribute('data-command') + }, currentPlayer); } } + } - function onVolumeChanged(e) { - var player = this; - updatePlayerVolumeState(dlg, player.isMuted(), player.getVolume()); + function getSaveablePlaylistItems() { + return getPlaylistItems(currentPlayer).then(function (items) { + return items.filter(function (i) { + return i.Id && i.ServerId; + }); + }); + } + + function savePlaylist() { + import('playlistEditor').then(({ default: playlistEditor }) => { + getSaveablePlaylistItems().then(function (items) { + const serverId = items.length ? items[0].ServerId : ApiClient.serverId(); + new playlistEditor({ + items: items.map(function (i) { + return i.Id; + }), + serverId: serverId, + enableAddToPlayQueue: false, + defaultValue: 'new' + }); + }); + }); + } + + function bindEvents(context) { + const btnCommand = context.querySelectorAll('.btnCommand'); + const positionSlider = context.querySelector('.nowPlayingPositionSlider'); + + for (let i = 0, length = btnCommand.length; i < length; i++) { + btnCommand[i].addEventListener('click', onBtnCommandClick); } - function releaseCurrentPlayer() { - var player = currentPlayer; - - if (player) { - events.off(player, 'playbackstart', onPlaybackStart); - events.off(player, 'statechange', onStateChanged); - events.off(player, 'repeatmodechange', onRepeatModeChange); - events.off(player, 'shufflequeuemodechange', onShuffleQueueModeChange); - events.off(player, 'playlistitemremove', onPlaylistItemRemoved); - events.off(player, 'playlistitemmove', onPlaylistUpdate); - events.off(player, 'playlistitemadd', onPlaylistUpdate); - events.off(player, 'playbackstop', onPlaybackStopped); - events.off(player, 'volumechange', onVolumeChanged); - events.off(player, 'pause', onPlayPauseStateChanged); - events.off(player, 'unpause', onPlayPauseStateChanged); - events.off(player, 'timeupdate', onTimeUpdate); - currentPlayer = null; - } - } - - function bindToPlayer(context, player) { - if (releaseCurrentPlayer(), currentPlayer = player, player) { - var state = playbackManager.getPlayerState(player); - onStateChanged.call(player, { - type: 'init' - }, state); - events.on(player, 'playbackstart', onPlaybackStart); - events.on(player, 'statechange', onStateChanged); - events.on(player, 'repeatmodechange', onRepeatModeChange); - events.on(player, 'shufflequeuemodechange', onShuffleQueueModeChange); - events.on(player, 'playlistitemremove', onPlaylistItemRemoved); - events.on(player, 'playlistitemmove', onPlaylistUpdate); - events.on(player, 'playlistitemadd', onPlaylistUpdate); - events.on(player, 'playbackstop', onPlaybackStopped); - events.on(player, 'volumechange', onVolumeChanged); - events.on(player, 'pause', onPlayPauseStateChanged); - events.on(player, 'unpause', onPlayPauseStateChanged); - events.on(player, 'timeupdate', onTimeUpdate); - var playerInfo = playbackManager.getPlayerInfo(); - var supportedCommands = playerInfo.supportedCommands; - currentPlayerSupportedCommands = supportedCommands; - updateSupportedCommands(context, supportedCommands); - } - } - - function onBtnCommandClick() { + context.querySelector('.btnToggleFullscreen').addEventListener('click', function (e) { if (currentPlayer) { - if (this.classList.contains('repeatToggleButton')) { - toggleRepeat(); + playbackManager.sendCommand({ + Name: e.target.getAttribute('data-command') + }, currentPlayer); + } + }); + context.querySelector('.btnAudioTracks').addEventListener('click', function (e) { + if (currentPlayer && lastPlayerState && lastPlayerState.NowPlayingItem) { + showAudioMenu(context, currentPlayer, e.target, lastPlayerState.NowPlayingItem); + } + }); + context.querySelector('.btnSubtitles').addEventListener('click', function (e) { + if (currentPlayer && lastPlayerState && lastPlayerState.NowPlayingItem) { + showSubtitleMenu(context, currentPlayer, e.target, lastPlayerState.NowPlayingItem); + } + }); + context.querySelector('.btnStop').addEventListener('click', function () { + if (currentPlayer) { + playbackManager.stop(currentPlayer); + } + }); + context.querySelector('.btnPlayPause').addEventListener('click', function () { + if (currentPlayer) { + playbackManager.playPause(currentPlayer); + } + }); + context.querySelector('.btnNextTrack').addEventListener('click', function () { + if (currentPlayer) { + playbackManager.nextTrack(currentPlayer); + } + }); + context.querySelector('.btnRewind').addEventListener('click', function () { + if (currentPlayer) { + playbackManager.rewind(currentPlayer); + } + }); + context.querySelector('.btnFastForward').addEventListener('click', function () { + if (currentPlayer) { + playbackManager.fastForward(currentPlayer); + } + }); + for (const shuffleButton of context.querySelectorAll('.btnShuffleQueue')) { + shuffleButton.addEventListener('click', function () { + if (currentPlayer) { + playbackManager.toggleQueueShuffleMode(currentPlayer); + } + }); + } + + context.querySelector('.btnPreviousTrack').addEventListener('click', function (e) { + if (currentPlayer) { + if (lastPlayerState.NowPlayingItem.MediaType === 'Audio' && (currentPlayer._currentTime >= 5 || !playbackManager.previousTrack(currentPlayer))) { + // Cancel this event if doubleclick is fired + if (e.detail > 1 && playbackManager.previousTrack(currentPlayer)) { + return; + } + playbackManager.seekPercent(0, currentPlayer); + // This is done automatically by playbackManager. However, setting this here gives instant visual feedback. + // TODO: Check why seekPercentage doesn't reflect the changes inmmediately, so we can remove this workaround. + positionSlider.value = 0; } else { - playbackManager.sendCommand({ - Name: this.getAttribute('data-command') - }, currentPlayer); - } - } - } - - function getSaveablePlaylistItems() { - return getPlaylistItems(currentPlayer).then(function (items) { - return items.filter(function (i) { - return i.Id && i.ServerId; - }); - }); - } - - function savePlaylist() { - require(['playlistEditor'], function (playlistEditor) { - getSaveablePlaylistItems().then(function (items) { - var serverId = items.length ? items[0].ServerId : ApiClient.serverId(); - new playlistEditor.showEditor({ - items: items.map(function (i) { - return i.Id; - }), - serverId: serverId, - enableAddToPlayQueue: false, - defaultValue: 'new' - }); - }); - }); - } - - function bindEvents(context) { - var btnCommand = context.querySelectorAll('.btnCommand'); - var positionSlider = context.querySelector('.nowPlayingPositionSlider'); - - for (var i = 0, length = btnCommand.length; i < length; i++) { - btnCommand[i].addEventListener('click', onBtnCommandClick); - } - - context.querySelector('.btnToggleFullscreen').addEventListener('click', function (e) { - if (currentPlayer) { - playbackManager.sendCommand({ - Name: e.target.getAttribute('data-command') - }, currentPlayer); - } - }); - context.querySelector('.btnAudioTracks').addEventListener('click', function (e) { - if (currentPlayer && lastPlayerState && lastPlayerState.NowPlayingItem) { - showAudioMenu(context, currentPlayer, e.target, lastPlayerState.NowPlayingItem); - } - }); - context.querySelector('.btnSubtitles').addEventListener('click', function (e) { - if (currentPlayer && lastPlayerState && lastPlayerState.NowPlayingItem) { - showSubtitleMenu(context, currentPlayer, e.target, lastPlayerState.NowPlayingItem); - } - }); - context.querySelector('.btnStop').addEventListener('click', function () { - if (currentPlayer) { - playbackManager.stop(currentPlayer); - } - }); - context.querySelector('.btnPlayPause').addEventListener('click', function () { - if (currentPlayer) { - playbackManager.playPause(currentPlayer); - } - }); - context.querySelector('.btnNextTrack').addEventListener('click', function () { - if (currentPlayer) { - playbackManager.nextTrack(currentPlayer); - } - }); - context.querySelector('.btnRewind').addEventListener('click', function () { - if (currentPlayer) { - playbackManager.rewind(currentPlayer); - } - }); - context.querySelector('.btnFastForward').addEventListener('click', function () { - if (currentPlayer) { - playbackManager.fastForward(currentPlayer); - } - }); - for (const shuffleButton of context.querySelectorAll('.btnShuffleQueue')) { - shuffleButton.addEventListener('click', function () { - if (currentPlayer) { - playbackManager.toggleQueueShuffleMode(currentPlayer); - } - }); - } - - context.querySelector('.btnPreviousTrack').addEventListener('click', function (e) { - if (currentPlayer) { - if (lastPlayerState.NowPlayingItem.MediaType === 'Audio' && (currentPlayer._currentTime >= 5 || !playbackManager.previousTrack(currentPlayer))) { - // Cancel this event if doubleclick is fired - if (e.detail > 1 && playbackManager.previousTrack(currentPlayer)) { - return; - } - playbackManager.seekPercent(0, currentPlayer); - // This is done automatically by playbackManager. However, setting this here gives instant visual feedback. - // TODO: Check why seekPercentage doesn't reflect the changes inmmediately, so we can remove this workaround. - positionSlider.value = 0; - } else { - playbackManager.previousTrack(currentPlayer); - } - } - }); - - context.querySelector('.btnPreviousTrack').addEventListener('dblclick', function () { - if (currentPlayer) { playbackManager.previousTrack(currentPlayer); } - }); - positionSlider.addEventListener('change', function () { - var value = this.value; + } + }); - if (currentPlayer) { - var newPercent = parseFloat(value); - playbackManager.seekPercent(newPercent, currentPlayer); - } - }); + context.querySelector('.btnPreviousTrack').addEventListener('dblclick', function () { + if (currentPlayer) { + playbackManager.previousTrack(currentPlayer); + } + }); + positionSlider.addEventListener('change', function () { + const value = this.value; - positionSlider.getBubbleText = function (value) { - var state = lastPlayerState; + if (currentPlayer) { + const newPercent = parseFloat(value); + playbackManager.seekPercent(newPercent, currentPlayer); + } + }); - if (!state || !state.NowPlayingItem || !currentRuntimeTicks) { - return '--:--'; - } + positionSlider.getBubbleText = function (value) { + const state = lastPlayerState; - var ticks = currentRuntimeTicks; - ticks /= 100; - ticks *= value; - return datetime.getDisplayRunningTime(ticks); - }; + if (!state || !state.NowPlayingItem || !currentRuntimeTicks) { + return '--:--'; + } - context.querySelector('.nowPlayingVolumeSlider').addEventListener('input', (e) => { - playbackManager.setVolume(e.target.value, currentPlayer); - }); + let ticks = currentRuntimeTicks; + ticks /= 100; + ticks *= value; + return datetime.getDisplayRunningTime(ticks); + }; - context.querySelector('.buttonMute').addEventListener('click', function () { - playbackManager.toggleMute(currentPlayer); - }); - var playlistContainer = context.querySelector('.playlist'); - playlistContainer.addEventListener('action-remove', function (e) { - playbackManager.removeFromPlaylist([e.detail.playlistItemId], currentPlayer); - }); - playlistContainer.addEventListener('itemdrop', function (e) { - var newIndex = e.detail.newIndex; - var playlistItemId = e.detail.playlistItemId; - playbackManager.movePlaylistItem(playlistItemId, newIndex, currentPlayer); - }); - context.querySelector('.btnSavePlaylist').addEventListener('click', savePlaylist); - context.querySelector('.btnTogglePlaylist').addEventListener('click', function () { - if (context.querySelector('.playlist').classList.contains('hide')) { - context.querySelector('.playlist').classList.remove('hide'); - context.querySelector('.btnSavePlaylist').classList.remove('hide'); - context.querySelector('.volumecontrol').classList.add('hide'); - if (layoutManager.mobile) { - context.querySelector('.playlistSectionButton').classList.remove('playlistSectionButtonTransparent'); - } - } else { - context.querySelector('.playlist').classList.add('hide'); - context.querySelector('.btnSavePlaylist').classList.add('hide'); - if (showMuteButton || showVolumeSlider) { - context.querySelector('.volumecontrol').classList.remove('hide'); - } - if (layoutManager.mobile) { - context.querySelector('.playlistSectionButton').classList.add('playlistSectionButtonTransparent'); - } - } - }); - } + context.querySelector('.nowPlayingVolumeSlider').addEventListener('input', (e) => { + playbackManager.setVolume(e.target.value, currentPlayer); + }); - function onPlayerChange() { - bindToPlayer(dlg, playbackManager.getCurrentPlayer()); - } - - function onMessageSubmit(e) { - var form = e.target; - playbackManager.sendCommand({ - Name: 'DisplayMessage', - Arguments: { - Header: form.querySelector('#txtMessageTitle').value, - Text: form.querySelector('#txtMessageText', form).value - } - }, currentPlayer); - form.querySelector('input').value = ''; - - require(['toast'], function (toast) { - toast('Message sent.'); - }); - - e.preventDefault(); - e.stopPropagation(); - return false; - } - - function onSendStringSubmit(e) { - var form = e.target; - playbackManager.sendCommand({ - Name: 'SendString', - Arguments: { - String: form.querySelector('#txtTypeText', form).value - } - }, currentPlayer); - form.querySelector('input').value = ''; - - require(['toast'], function (toast) { - toast('Text sent.'); - }); - - e.preventDefault(); - e.stopPropagation(); - return false; - } - - function init(ownerView, context) { - let volumecontrolHtml = '
'; - volumecontrolHtml += ``; - volumecontrolHtml += '
'; - volumecontrolHtml += '
'; - let optionsSection = context.querySelector('.playlistSectionButton'); - if (!layoutManager.mobile) { - context.querySelector('.nowPlayingSecondaryButtons').insertAdjacentHTML('beforeend', volumecontrolHtml); - optionsSection.classList.remove('align-items-center', 'justify-content-center'); - optionsSection.classList.add('align-items-right', 'justify-content-flex-end'); + context.querySelector('.buttonMute').addEventListener('click', function () { + playbackManager.toggleMute(currentPlayer); + }); + const playlistContainer = context.querySelector('.playlist'); + playlistContainer.addEventListener('action-remove', function (e) { + playbackManager.removeFromPlaylist([e.detail.playlistItemId], currentPlayer); + }); + playlistContainer.addEventListener('itemdrop', function (e) { + const newIndex = e.detail.newIndex; + const playlistItemId = e.detail.playlistItemId; + playbackManager.movePlaylistItem(playlistItemId, newIndex, currentPlayer); + }); + context.querySelector('.btnSavePlaylist').addEventListener('click', savePlaylist); + context.querySelector('.btnTogglePlaylist').addEventListener('click', function () { + if (context.querySelector('.playlist').classList.contains('hide')) { context.querySelector('.playlist').classList.remove('hide'); context.querySelector('.btnSavePlaylist').classList.remove('hide'); - context.classList.add('padded-bottom'); + context.querySelector('.volumecontrol').classList.add('hide'); + if (layoutManager.mobile) { + context.querySelector('.playlistSectionButton').classList.remove('playlistSectionButtonTransparent'); + } } else { - optionsSection.querySelector('.btnTogglePlaylist').insertAdjacentHTML('afterend', volumecontrolHtml); - optionsSection.classList.add('playlistSectionButtonTransparent'); - context.querySelector('.btnTogglePlaylist').classList.remove('hide'); - context.querySelector('.playlistSectionButton').classList.remove('justify-content-center'); - context.querySelector('.playlistSectionButton').classList.add('justify-content-space-between'); + context.querySelector('.playlist').classList.add('hide'); + context.querySelector('.btnSavePlaylist').classList.add('hide'); + if (showMuteButton || showVolumeSlider) { + context.querySelector('.volumecontrol').classList.remove('hide'); + } + if (layoutManager.mobile) { + context.querySelector('.playlistSectionButton').classList.add('playlistSectionButtonTransparent'); + } } + }); + } - bindEvents(context); - context.querySelector('.sendMessageForm').addEventListener('submit', onMessageSubmit); - context.querySelector('.typeTextForm').addEventListener('submit', onSendStringSubmit); - events.on(playbackManager, 'playerchange', onPlayerChange); + function onPlayerChange() { + bindToPlayer(dlg, playbackManager.getCurrentPlayer()); + } - if (layoutManager.tv) { - var positionSlider = context.querySelector('.nowPlayingPositionSlider'); - positionSlider.classList.add('focusable'); - positionSlider.enableKeyboardDragging(); + function onMessageSubmit(e) { + const form = e.target; + playbackManager.sendCommand({ + Name: 'DisplayMessage', + Arguments: { + Header: form.querySelector('#txtMessageTitle').value, + Text: form.querySelector('#txtMessageText', form).value } + }, currentPlayer); + form.querySelector('input').value = ''; + + import('toast').then(({ default: toast }) => { + toast('Message sent.'); + }); + + e.preventDefault(); + e.stopPropagation(); + return false; + } + + function onSendStringSubmit(e) { + const form = e.target; + playbackManager.sendCommand({ + Name: 'SendString', + Arguments: { + String: form.querySelector('#txtTypeText', form).value + } + }, currentPlayer); + form.querySelector('input').value = ''; + + import('toast').then(({ default: toast }) => { + toast('Text sent.'); + }); + + e.preventDefault(); + e.stopPropagation(); + return false; + } + + function init(ownerView, context) { + let volumecontrolHtml = '
'; + volumecontrolHtml += ``; + volumecontrolHtml += '
'; + volumecontrolHtml += '
'; + const optionsSection = context.querySelector('.playlistSectionButton'); + if (!layoutManager.mobile) { + context.querySelector('.nowPlayingSecondaryButtons').insertAdjacentHTML('beforeend', volumecontrolHtml); + optionsSection.classList.remove('align-items-center', 'justify-content-center'); + optionsSection.classList.add('align-items-right', 'justify-content-flex-end'); + context.querySelector('.playlist').classList.remove('hide'); + context.querySelector('.btnSavePlaylist').classList.remove('hide'); + context.classList.add('padded-bottom'); + } else { + optionsSection.querySelector('.btnTogglePlaylist').insertAdjacentHTML('afterend', volumecontrolHtml); + optionsSection.classList.add('playlistSectionButtonTransparent'); + context.querySelector('.btnTogglePlaylist').classList.remove('hide'); + context.querySelector('.playlistSectionButton').classList.remove('justify-content-center'); + context.querySelector('.playlistSectionButton').classList.add('justify-content-space-between'); } - function onDialogClosed(e) { - releaseCurrentPlayer(); - events.off(playbackManager, 'playerchange', onPlayerChange); - lastPlayerState = null; + bindEvents(context); + context.querySelector('.sendMessageForm').addEventListener('submit', onMessageSubmit); + context.querySelector('.typeTextForm').addEventListener('submit', onSendStringSubmit); + events.on(playbackManager, 'playerchange', onPlayerChange); + + if (layoutManager.tv) { + const positionSlider = context.querySelector('.nowPlayingPositionSlider'); + positionSlider.classList.add('focusable'); + positionSlider.enableKeyboardDragging(); } + } - function onShow(context, tab) { - currentImgUrl = null; - bindToPlayer(context, playbackManager.getCurrentPlayer()); - } + function onDialogClosed(e) { + releaseCurrentPlayer(); + events.off(playbackManager, 'playerchange', onPlayerChange); + lastPlayerState = null; + } - var dlg; - var currentPlayer; - var lastPlayerState; - var currentPlayerSupportedCommands = []; - var lastUpdateTime = 0; - var currentRuntimeTicks = 0; - var self = this; + function onShow(context, tab) { + bindToPlayer(context, playbackManager.getCurrentPlayer()); + } - self.init = function (ownerView, context) { - dlg = context; - init(ownerView, dlg); - }; + let dlg; + let currentPlayer; + let lastPlayerState; + let currentPlayerSupportedCommands = []; + let lastUpdateTime = 0; + let currentRuntimeTicks = 0; + const self = this; - self.onShow = function () { - onShow(dlg, window.location.hash); - }; - - self.destroy = function () { - onDialogClosed(); - }; + self.init = function (ownerView, context) { + dlg = context; + init(ownerView, dlg); }; -}); + + self.onShow = function () { + onShow(dlg, window.location.hash); + }; + + self.destroy = function () { + onDialogClosed(); + }; +} diff --git a/src/components/require/requirecss.js b/src/components/require/requirecss.js index 78e5af2a08..8aaa04d689 100644 --- a/src/components/require/requirecss.js +++ b/src/components/require/requirecss.js @@ -18,7 +18,6 @@ define(function () { } function removeFromLoadHistory(url) { - url = url.toLowerCase(); importedCss = importedCss.filter(function (c) { @@ -27,7 +26,6 @@ define(function () { } requireCss.load = function (cssId, req, load, config) { - // Somehow if the url starts with /css, require will get all screwed up since this extension is also called css var srch = 'components/require/requirecss'; var index = cssId.indexOf(srch); @@ -65,7 +63,6 @@ define(function () { window.requireCss = { removeStylesheet: function (stylesheet) { - stylesheet.parentNode.removeChild(stylesheet); removeFromLoadHistory(stylesheet.href); } diff --git a/src/components/require/requiretext.js b/src/components/require/requiretext.js index ac508f95be..28ddeb21c8 100644 --- a/src/components/require/requiretext.js +++ b/src/components/require/requiretext.js @@ -2,12 +2,11 @@ define(function () { 'use strict'; // hack to work around the server's auto-redirection feature - var addRedirectPrevention = self.dashboardVersion != null && self.Dashboard && !self.AppInfo.isNativeApp; + var addRedirectPrevention = window.dashboardVersion != null && window.Dashboard && !window.AppInfo.isNativeApp; return { load: function (url, req, load, config) { - if (url.indexOf('://') === -1) { url = config.baseUrl + url; } diff --git a/src/components/sanitizeFilename.js b/src/components/sanitizeFilename.js index de7b1a0782..ffea2a0a6c 100644 --- a/src/components/sanitizeFilename.js +++ b/src/components/sanitizeFilename.js @@ -1,3 +1,4 @@ +// TODO: Check if needed and move to external dependency // From https://github.com/parshap/node-sanitize-filename const illegalRe = /[\/\?<>\\:\*\|":]/g; diff --git a/src/components/scrollManager.js b/src/components/scrollManager.js index 3b01cf1ad1..549cb9445c 100644 --- a/src/components/scrollManager.js +++ b/src/components/scrollManager.js @@ -251,7 +251,7 @@ import layoutManager from 'layoutManager'; * @return {ScrollerData} Scroller data. */ function getScrollerData(scroller, vertical) { - let data = {}; + const data = {}; if (!vertical) { data.scrollPos = scroller.scrollLeft; @@ -376,7 +376,6 @@ import layoutManager from 'layoutManager'; * @param {number} scrollY - Vertical coordinate. */ function animateScroll(xScroller, scrollX, yScroller, scrollY) { - const ox = xScroller.scrollLeft; const oy = yScroller.scrollTop; const dx = scrollX - ox; @@ -389,7 +388,6 @@ import layoutManager from 'layoutManager'; let start; function scrollAnim(currentTimestamp) { - start = start || currentTimestamp; let k = Math.min(1, (currentTimestamp - start) / ScrollTime); @@ -423,7 +421,6 @@ import layoutManager from 'layoutManager'; * @param {boolean} smooth - Smooth scrolling. */ function doScroll(xScroller, scrollX, yScroller, scrollY, smooth) { - resetScrollTimer(); if (smooth && useAnimatedScroll()) { @@ -437,7 +434,6 @@ import layoutManager from 'layoutManager'; * Returns true if smooth scroll must be used. */ function useSmoothScroll() { - if (browser.tizen) { return true; } @@ -469,7 +465,6 @@ import layoutManager from 'layoutManager'; * @param {boolean} [smooth=false] - Smooth scrolling. */ export function scrollTo(scrollX, scrollY, smooth) { - smooth = !!smooth; // Scroller is document itself by default @@ -491,7 +486,6 @@ import layoutManager from 'layoutManager'; * @param {boolean} [smooth=false] - Smooth scrolling. */ export function scrollToElement(element, smooth) { - smooth = !!smooth; let scrollCenterX = true; diff --git a/src/components/search/searchfields.js b/src/components/search/searchfields.js index 5cc38eda9a..b3cb3cf4c4 100644 --- a/src/components/search/searchfields.js +++ b/src/components/search/searchfields.js @@ -11,7 +11,6 @@ import 'css!./searchfields'; /* eslint-disable indent */ function onSearchTimeout() { - const instance = this; let value = instance.nextSearchValue; @@ -20,7 +19,6 @@ import 'css!./searchfields'; } function triggerSearch(instance, value) { - if (instance.searchTimeout) { clearTimeout(instance.searchTimeout); } @@ -30,17 +28,14 @@ import 'css!./searchfields'; } function onAlphaValueClicked(e) { - const value = e.detail.value; const searchFieldsInstance = this; const txtSearch = searchFieldsInstance.options.element.querySelector('.searchfields-txtSearch'); if (value === 'backspace') { - const val = txtSearch.value; txtSearch.value = val.length ? val.substring(0, val.length - 1) : ''; - } else { txtSearch.value += value; } @@ -51,7 +46,6 @@ import 'css!./searchfields'; } function initAlphaPicker(alphaPickerElement, instance) { - instance.alphaPicker = new AlphaPicker({ element: alphaPickerElement, mode: 'keyboard' @@ -61,16 +55,13 @@ import 'css!./searchfields'; } function onSearchInput(e) { - const value = e.target.value; const searchFieldsInstance = this; triggerSearch(searchFieldsInstance, value); } function embed(elem, instance, options) { - import('text!./searchfields.template.html').then(({default: template}) => { - let html = globalize.translateHtml(template, 'core'); if (browser.tizen || browser.orsay) { @@ -98,16 +89,13 @@ import 'css!./searchfields'; class SearchFields { constructor(options) { - this.options = options; embed(options.element, this, options); } focus() { - this.options.element.querySelector('.searchfields-txtSearch').focus(); } destroy() { - const options = this.options; if (options) { options.element.classList.remove('searchFields'); diff --git a/src/components/search/searchresults.js b/src/components/search/searchresults.js index e16b6a1cd4..d35868d433 100644 --- a/src/components/search/searchresults.js +++ b/src/components/search/searchresults.js @@ -1,8 +1,5 @@ import layoutManager from 'layoutManager'; import globalize from 'globalize'; -import require from 'require'; -import events from 'events'; -import connectionManager from 'connectionManager'; import cardBuilder from 'cardBuilder'; import appRouter from 'appRouter'; import 'emby-scroller'; @@ -12,7 +9,6 @@ import 'emby-button'; /* eslint-disable indent */ function loadSuggestions(instance, context, apiClient) { - const options = { SortBy: 'IsFavoriteOrLiked,Random', @@ -26,20 +22,17 @@ import 'emby-button'; }; apiClient.getItems(apiClient.getCurrentUserId(), options).then(function (result) { - if (instance.mode !== 'suggestions') { result.Items = []; } const html = result.Items.map(function (i) { - const href = appRouter.getRouteUrl(i); let itemHtml = ''; return itemHtml; - }).join(''); const searchSuggestions = context.querySelector('.searchSuggestions'); @@ -52,7 +45,6 @@ import 'emby-button'; } function getSearchHints(instance, apiClient, query) { - if (!query.searchTerm) { return Promise.resolve({ SearchHints: [] @@ -131,7 +123,6 @@ import 'emby-button'; // Convert the search hint query to a regular item query if (apiClient.isMinServerVersion('3.4.1.31')) { - query.Fields = 'PrimaryImageAspectRatio,CanDelete,BasicSyncInfo,MediaSourceCount'; query.Recursive = true; query.EnableTotalRecordCount = false; @@ -142,7 +133,6 @@ import 'emby-button'; if (!query.IncludeMedia) { if (query.IncludePeople) { methodName = 'getPeople'; - } else if (query.IncludeArtists) { methodName = 'getArtists'; } @@ -157,7 +147,6 @@ import 'emby-button'; } function search(instance, apiClient, context, value) { - if (value || layoutManager.tv) { instance.mode = 'search'; context.querySelector('.searchSuggestions').classList.add('hide'); @@ -167,7 +156,6 @@ import 'emby-button'; } if (instance.options.collectionType === 'livetv') { - searchType(instance, apiClient, { searchTerm: value, IncludePeople: false, @@ -196,7 +184,6 @@ import 'emby-button'; showChannelName: true }); } else { - searchType(instance, apiClient, { searchTerm: value, IncludePeople: false, @@ -233,7 +220,6 @@ import 'emby-button'; }); if (instance.options.collectionType === 'livetv') { - searchType(instance, apiClient, { searchTerm: value, IncludePeople: false, @@ -262,9 +248,7 @@ import 'emby-button'; showAirDateTime: true, showChannelName: true }); - } else { - searchType(instance, apiClient, { searchTerm: value, IncludePeople: false, @@ -562,18 +546,15 @@ import 'emby-button'; } function searchType(instance, apiClient, query, context, section, cardOptions) { - query.Limit = enableScrollX() ? 24 : 16; query.ParentId = instance.options.parentId; getSearchHints(instance, apiClient, query).then(function (result) { - populateResults(result, context, section, cardOptions); }); } function populateResults(result, context, section, cardOptions) { - section = context.querySelector(section); const items = result.Items || result.SearchHints; @@ -603,9 +584,7 @@ import 'emby-button'; } function embed(elem, instance, options) { - import('text!./searchresults.template.html').then(({default: template}) => { - if (!enableScrollX()) { template = replaceAll(template, 'data-horizontal="true"', 'data-horizontal="false"'); template = replaceAll(template, 'itemsContainer scrollSlider', 'itemsContainer scrollSlider vertical-wrap'); @@ -622,24 +601,20 @@ import 'emby-button'; class SearchResults { constructor(options) { - this.options = options; embed(options.element, this, options); } search(value) { - - const apiClient = connectionManager.getApiClient(this.options.serverId); + const apiClient = window.connectionManager.getApiClient(this.options.serverId); search(this, apiClient, this.options.element, value); } destroy() { - const options = this.options; if (options) { options.element.classList.remove('searchFields'); } this.options = null; - } } diff --git a/src/components/serviceworker/notifications.js b/src/components/serviceworker/notifications.js index 5f96d01a4d..9b50553244 100644 --- a/src/components/serviceworker/notifications.js +++ b/src/components/serviceworker/notifications.js @@ -26,6 +26,7 @@ }); } + /* eslint-disable-next-line no-restricted-globals -- self is valid in a serviceworker environment */ self.addEventListener('notificationclick', function (event) { var notification = event.notification; notification.close(); diff --git a/src/components/shortcuts.js b/src/components/shortcuts.js index f105d6c599..2959fd3372 100644 --- a/src/components/shortcuts.js +++ b/src/components/shortcuts.js @@ -7,14 +7,12 @@ import playbackManager from 'playbackManager'; import inputManager from 'inputManager'; -import connectionManager from 'connectionManager'; import appRouter from 'appRouter'; import globalize from 'globalize'; import dom from 'dom'; import recordingHelper from 'recordingHelper'; function playAllFromHere(card, serverId, queue) { - const parent = card.parentNode; const className = card.classList.length ? (`.${card.classList[0]}`) : ''; const cards = parent.querySelectorAll(`${className}[data-id]`); @@ -36,17 +34,14 @@ import recordingHelper from 'recordingHelper'; const itemsContainer = dom.parentWithClass(card, 'itemsContainer'); if (itemsContainer && itemsContainer.fetchData) { - const queryOptions = queue ? { StartIndex: startIndex } : {}; return itemsContainer.fetchData(queryOptions).then(result => { - if (queue) { return playbackManager.queue({ items: result.Items }); } else { - return playbackManager.play({ items: result.Items, startIndex: startIndex @@ -65,7 +60,6 @@ import recordingHelper from 'recordingHelper'; serverId: serverId }); } else { - return playbackManager.play({ ids: ids, serverId: serverId, @@ -75,21 +69,18 @@ import recordingHelper from 'recordingHelper'; } function showProgramDialog(item) { - import('recordingCreator').then(({default:recordingCreator}) => { - recordingCreator.show(item.Id, item.ServerId); }); } function getItem(button) { - button = dom.parentWithAttribute(button, 'data-id'); const serverId = button.getAttribute('data-serverid'); const id = button.getAttribute('data-id'); const type = button.getAttribute('data-type'); - const apiClient = connectionManager.getApiClient(serverId); + const apiClient = window.connectionManager.getApiClient(serverId); if (type === 'Timer') { return apiClient.getLiveTvTimer(id); @@ -101,7 +92,6 @@ import recordingHelper from 'recordingHelper'; } function notifyRefreshNeeded(childElement, itemsContainer) { - itemsContainer = itemsContainer || dom.parentWithAttribute(childElement, 'is', 'emby-itemscontainer'); if (itemsContainer) { @@ -110,9 +100,7 @@ import recordingHelper from 'recordingHelper'; } function showContextMenu(card, options) { - getItem(card).then(item => { - const playlistId = card.getAttribute('data-playlistid'); const collectionId = card.getAttribute('data-collectionid'); @@ -122,8 +110,7 @@ import recordingHelper from 'recordingHelper'; } import('itemContextMenu').then(({default: itemContextMenu}) => { - - connectionManager.getApiClient(item.ServerId).getCurrentUser().then(user => { + window.connectionManager.getApiClient(item.ServerId).getCurrentUser().then(user => { itemContextMenu.show(Object.assign({ item: item, play: true, @@ -135,7 +122,6 @@ import recordingHelper from 'recordingHelper'; user: user }, options || {})).then(result => { - if (result.command === 'playallfromhere' || result.command === 'queueallfromhere') { executeAction(card, options.positionTo, result.command); } else if (result.updated || result.deleted) { @@ -148,7 +134,6 @@ import recordingHelper from 'recordingHelper'; } function getItemInfoFromCard(card) { - return { Type: card.getAttribute('data-type'), Id: card.getAttribute('data-id'), @@ -166,11 +151,9 @@ import recordingHelper from 'recordingHelper'; } function showPlayMenu(card, target) { - const item = getItemInfoFromCard(card); import('playMenu').then(({default: playMenu}) => { - playMenu.show({ item: item, @@ -186,7 +169,6 @@ import recordingHelper from 'recordingHelper'; } function executeAction(card, target, action) { - target = target || card; let id = card.getAttribute('data-id'); @@ -208,13 +190,11 @@ import recordingHelper from 'recordingHelper'; } if (action === 'link') { - appRouter.showItem(item, { context: card.getAttribute('data-context'), parentId: card.getAttribute('data-parentid') }); } else if (action === 'programdialog') { - showProgramDialog(item); } else if (action === 'instantmix') { playbackManager.instantMix({ @@ -222,7 +202,6 @@ import recordingHelper from 'recordingHelper'; ServerId: serverId }); } else if (action === 'play' || action === 'resume') { - const startPositionTicks = parseInt(card.getAttribute('data-positionticks') || '0'); playbackManager.play({ @@ -231,7 +210,6 @@ import recordingHelper from 'recordingHelper'; serverId: serverId }); } else if (action === 'queue') { - if (playbackManager.isPlaying()) { playbackManager.queue({ ids: [playableItemId], @@ -253,7 +231,6 @@ import recordingHelper from 'recordingHelper'; } else if (action === 'record') { onRecordCommand(serverId, id, type, card.getAttribute('data-timerid'), card.getAttribute('data-seriestimerid')); } else if (action === 'menu') { - const options = target.getAttribute('data-playoptions') === 'false' ? { shuffle: false, @@ -279,7 +256,6 @@ import recordingHelper from 'recordingHelper'; } else if (action === 'addtoplaylist') { getItem(target).then(addToPlaylist); } else if (action === 'custom') { - const customAction = target.getAttribute('data-customaction'); card.dispatchEvent(new CustomEvent(`action-${customAction}`, { @@ -294,7 +270,6 @@ import recordingHelper from 'recordingHelper'; function addToPlaylist(item) { import('playlistEditor').then(({default: playlistEditor}) => { - new playlistEditor().show({ items: [item.Id], serverId: item.ServerId @@ -304,8 +279,7 @@ import recordingHelper from 'recordingHelper'; } function playTrailer(item) { - - const apiClient = connectionManager.getApiClient(item.ServerId); + const apiClient = window.connectionManager.getApiClient(item.ServerId); apiClient.getLocalTrailers(apiClient.getCurrentUserId(), item.Id).then(trailers => { playbackManager.play({ items: trailers }); @@ -313,28 +287,23 @@ import recordingHelper from 'recordingHelper'; } function editItem(item, serverId) { - - const apiClient = connectionManager.getApiClient(serverId); + const apiClient = window.connectionManager.getApiClient(serverId); return new Promise((resolve, reject) => { - const serverId = apiClient.serverInfo().Id; if (item.Type === 'Timer') { if (item.ProgramId) { import('recordingCreator').then(({default: recordingCreator}) => { - recordingCreator.show(item.ProgramId, serverId).then(resolve, reject); }); } else { import('recordingEditor').then(({default: recordingEditor}) => { - recordingEditor.show(item.Id, serverId).then(resolve, reject); }); } } else { import('metadataEditor').then(({default: metadataEditor}) => { - metadataEditor.show(item.Id, serverId).then(resolve, reject); }); } @@ -342,20 +311,16 @@ import recordingHelper from 'recordingHelper'; } function onRecordCommand(serverId, id, type, timerId, seriesTimerId) { - if (type === 'Program' || timerId || seriesTimerId) { - const programId = type === 'Program' ? id : null; recordingHelper.toggleRecording(serverId, programId, timerId, seriesTimerId); } } export function onClick(e) { - const card = dom.parentWithClass(e.target, 'itemAction'); if (card) { - let actionElement = card; let action = actionElement.getAttribute('data-action'); @@ -377,11 +342,9 @@ import recordingHelper from 'recordingHelper'; } function onCommand(e) { - const cmd = e.detail.command; if (cmd === 'play' || cmd === 'resume' || cmd === 'record' || cmd === 'menu' || cmd === 'info') { - const target = e.target; const card = dom.parentWithClass(target, 'itemAction') || dom.parentWithAttribute(target, 'data-id'); @@ -394,7 +357,6 @@ import recordingHelper from 'recordingHelper'; } export function on(context, options) { - options = options || {}; if (options.click !== false) { @@ -417,7 +379,6 @@ import recordingHelper from 'recordingHelper'; } export function getShortcutAttributesHtml(item, serverId) { - let html = `data-id="${item.Id}" data-serverid="${serverId || item.ServerId}" data-type="${item.Type}" data-mediatype="${item.MediaType}" data-channelid="${item.ChannelId}" data-isfolder="${item.IsFolder}"`; const collectionType = item.CollectionType; diff --git a/src/components/skinManager.js b/src/components/skinManager.js deleted file mode 100644 index faa1a1f306..0000000000 --- a/src/components/skinManager.js +++ /dev/null @@ -1,184 +0,0 @@ -define(['apphost', 'userSettings', 'browser', 'events', 'backdrop', 'globalize', 'require', 'appSettings'], function (appHost, userSettings, browser, events, backdrop, globalize, require, appSettings) { - 'use strict'; - - var themeStyleElement; - var currentThemeId; - - function unloadTheme() { - var elem = themeStyleElement; - if (elem) { - elem.parentNode.removeChild(elem); - themeStyleElement = null; - currentThemeId = null; - } - } - - function loadUserSkin(options) { - options = options || {}; - if (options.start) { - Emby.Page.invokeShortcut(options.start); - } else { - Emby.Page.goHome(); - } - } - - function getThemes() { - return [{ - name: 'Apple TV', - id: 'appletv' - }, { - name: 'Blue Radiance', - id: 'blueradiance' - }, { - name: 'Dark', - id: 'dark', - isDefault: true, - isDefaultServerDashboard: true - }, { - name: 'Light', - id: 'light' - }, { - name: 'Purple Haze', - id: 'purplehaze' - }, { - name: 'Windows Media Center', - id: 'wmc' - }]; - } - - var skinManager = { - getThemes: getThemes, - loadUserSkin: loadUserSkin - }; - - function getThemeStylesheetInfo(id, isDefaultProperty) { - var themes = skinManager.getThemes(); - var defaultTheme; - var selectedTheme; - - for (var i = 0, length = themes.length; i < length; i++) { - var theme = themes[i]; - if (theme[isDefaultProperty]) { - defaultTheme = theme; - } - if (id === theme.id) { - selectedTheme = theme; - } - } - - selectedTheme = selectedTheme || defaultTheme; - return { - stylesheetPath: require.toUrl('themes/' + selectedTheme.id + '/theme.css'), - themeId: selectedTheme.id - }; - } - - var themeResources = {}; - var lastSound = 0; - var currentSound; - - function loadThemeResources(id) { - lastSound = 0; - if (currentSound) { - currentSound.stop(); - currentSound = null; - } - - backdrop.clearBackdrop(); - } - - function onThemeLoaded() { - document.documentElement.classList.remove('preload'); - try { - var color = getComputedStyle(document.querySelector('.skinHeader')).getPropertyValue('background-color'); - if (color) { - appHost.setThemeColor(color); - } - } catch (err) { - console.error('error setting theme color: ' + err); - } - } - - skinManager.setTheme = function (id, context) { - return new Promise(function (resolve, reject) { - if (currentThemeId && currentThemeId === id) { - resolve(); - return; - } - - var isDefaultProperty = context === 'serverdashboard' ? 'isDefaultServerDashboard' : 'isDefault'; - var info = getThemeStylesheetInfo(id, isDefaultProperty); - if (currentThemeId && currentThemeId === info.themeId) { - resolve(); - return; - } - - var linkUrl = info.stylesheetPath; - unloadTheme(); - - var link = document.createElement('link'); - link.setAttribute('rel', 'stylesheet'); - link.setAttribute('type', 'text/css'); - link.onload = function () { - onThemeLoaded(); - resolve(); - }; - - link.setAttribute('href', linkUrl); - document.head.appendChild(link); - themeStyleElement = link; - currentThemeId = info.themeId; - loadThemeResources(info.themeId); - - onViewBeforeShow({}); - }); - }; - - function onViewBeforeShow(e) { - if (e.detail && e.detail.type === 'video-osd') { - // This removes the space that the scrollbar takes while playing a video - document.body.classList.remove('force-scroll'); - return; - } - - if (themeResources.backdrop) { - backdrop.setBackdrop(themeResources.backdrop); - } - - if (!browser.mobile && userSettings.enableThemeSongs()) { - if (lastSound === 0) { - if (themeResources.themeSong) { - playSound(themeResources.themeSong); - } - } else if ((new Date().getTime() - lastSound) > 30000) { - if (themeResources.effect) { - playSound(themeResources.effect); - } - } - } - // This keeps the scrollbar always present in all pages, so we avoid clipping while switching between pages - // that need the scrollbar and pages that don't. - document.body.classList.add('force-scroll'); - } - - document.addEventListener('viewshow', onViewBeforeShow); - - function playSound(path, volume) { - lastSound = new Date().getTime(); - require(['howler'], function (howler) { - /* globals Howl */ - try { - var sound = new Howl({ - src: [path], - volume: volume || 0.1 - }); - sound.play(); - currentSound = sound; - } catch (err) { - console.error('error playing sound: ' + err); - } - }); - } - - return skinManager; -}); diff --git a/src/components/slideshow/slideshow.js b/src/components/slideshow/slideshow.js index f7026a007e..028c21b221 100644 --- a/src/components/slideshow/slideshow.js +++ b/src/components/slideshow/slideshow.js @@ -2,676 +2,671 @@ * Image viewer component * @module components/slideshow/slideshow */ -define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'focusManager', 'browser', 'apphost', 'dom', 'css!./style', 'material-icons', 'paper-icon-button-light'], function (dialogHelper, inputManager, connectionManager, layoutManager, focusManager, browser, appHost, dom) { - 'use strict'; +import dialogHelper from 'dialogHelper'; +import inputManager from 'inputManager'; +import layoutManager from 'layoutManager'; +import focusManager from 'focusManager'; +import browser from 'browser'; +import appHost from 'apphost'; +import dom from 'dom'; +import 'css!./style'; +import 'material-icons'; +import 'paper-icon-button-light'; - /** - * Name of transition event. - */ - const transitionEndEventName = dom.whichTransitionEvent(); +/** + * Name of transition event. + */ +const transitionEndEventName = dom.whichTransitionEvent(); - /** - * Flag to use fake image to fix blurry zoomed image. - * At least WebKit doesn't restore quality for zoomed images. - */ - const useFakeZoomImage = browser.safari; +/** + * Flag to use fake image to fix blurry zoomed image. + * At least WebKit doesn't restore quality for zoomed images. + */ +const useFakeZoomImage = browser.safari; - /** - * Retrieves an item's image URL from the API. - * @param {object|string} item - Item used to generate the image URL. - * @param {object} options - Options of the image. - * @param {object} apiClient - API client instance used to retrieve the image. - * @returns {null|string} URL of the item's image. - */ - function getImageUrl(item, options, apiClient) { - options = options || {}; - options.type = options.type || 'Primary'; +/** + * Retrieves an item's image URL from the API. + * @param {object|string} item - Item used to generate the image URL. + * @param {object} options - Options of the image. + * @param {object} apiClient - API client instance used to retrieve the image. + * @returns {null|string} URL of the item's image. + */ +function getImageUrl(item, options, apiClient) { + options = options || {}; + options.type = options.type || 'Primary'; - if (typeof (item) === 'string') { - return apiClient.getScaledImageUrl(item, options); + if (typeof (item) === 'string') { + return apiClient.getScaledImageUrl(item, options); + } + + if (item.ImageTags && item.ImageTags[options.type]) { + options.tag = item.ImageTags[options.type]; + return apiClient.getScaledImageUrl(item.Id, options); + } + + if (options.type === 'Primary') { + if (item.AlbumId && item.AlbumPrimaryImageTag) { + options.tag = item.AlbumPrimaryImageTag; + return apiClient.getScaledImageUrl(item.AlbumId, options); } + } - if (item.ImageTags && item.ImageTags[options.type]) { - options.tag = item.ImageTags[options.type]; - return apiClient.getScaledImageUrl(item.Id, options); + return null; +} + +/** + * Retrieves a backdrop's image URL from the API. + * @param {object} item - Item used to generate the image URL. + * @param {object} options - Options of the image. + * @param {object} apiClient - API client instance used to retrieve the image. + * @returns {null|string} URL of the item's backdrop. + */ +function getBackdropImageUrl(item, options, apiClient) { + options = options || {}; + options.type = options.type || 'Backdrop'; + + // If not resizing, get the original image + if (!options.maxWidth && !options.width && !options.maxHeight && !options.height) { + options.quality = 100; + } + + if (item.BackdropImageTags && item.BackdropImageTags.length) { + options.tag = item.BackdropImageTags[0]; + return apiClient.getScaledImageUrl(item.Id, options); + } + + return null; +} + +/** + * Dispatches a request for an item's image to its respective handler. + * @param {object} item - Item used to generate the image URL. + * @returns {string} URL of the item's image. + */ +function getImgUrl(item, user) { + const apiClient = window.connectionManager.getApiClient(item.ServerId); + const imageOptions = {}; + + if (item.BackdropImageTags && item.BackdropImageTags.length) { + return getBackdropImageUrl(item, imageOptions, apiClient); + } else { + if (item.MediaType === 'Photo' && user && user.Policy.EnableContentDownloading) { + return apiClient.getItemDownloadUrl(item.Id); } + imageOptions.type = 'Primary'; + return getImageUrl(item, imageOptions, apiClient); + } +} - if (options.type === 'Primary') { - if (item.AlbumId && item.AlbumPrimaryImageTag) { +/** + * Generates a button using the specified icon, classes and properties. + * @param {string} icon - Name of the material icon on the button + * @param {string} cssClass - CSS classes to assign to the button + * @param {boolean} canFocus - Flag to set the tabindex attribute on the button to -1. + * @param {boolean} autoFocus - Flag to set the autofocus attribute on the button. + * @returns {string} The HTML markup of the button. + */ +function getIcon(icon, cssClass, canFocus, autoFocus) { + const tabIndex = canFocus ? '' : ' tabindex="-1"'; + autoFocus = autoFocus ? ' autofocus' : ''; + return ''; +} - options.tag = item.AlbumPrimaryImageTag; - return apiClient.getScaledImageUrl(item.AlbumId, options); +/** + * Sets the viewport meta tag to enable or disable scaling by the user. + * @param {boolean} scalable - Flag to set the scalability of the viewport. + */ +function setUserScalable(scalable) { + try { + appHost.setUserScalable(scalable); + } catch (err) { + console.error('error in appHost.setUserScalable: ' + err); + } +} + +export default function (options) { + const self = this; + /** Initialized instance of Swiper. */ + let swiperInstance; + /** Initialized instance of the dialog containing the Swiper instance. */ + let dialog; + /** Options of the slideshow components */ + let currentOptions; + /** ID of the timeout used to hide the OSD. */ + let hideTimeout; + /** Last coordinates of the mouse pointer. */ + let lastMouseMoveData; + + /** + * Creates the HTML markup for the dialog and the OSD. + * @param {Object} options - Options used to create the dialog and slideshow. + */ + function createElements(options) { + currentOptions = options; + + dialog = dialogHelper.createDialog({ + exitAnimationDuration: options.interactive ? 400 : 800, + size: 'fullscreen', + autoFocus: false, + scrollY: false, + exitAnimation: 'fadeout', + removeOnClose: true + }); + + dialog.classList.add('slideshowDialog'); + + let html = ''; + + html += '
'; + + if (options.interactive && !layoutManager.tv) { + const actionButtonsOnTop = layoutManager.mobile; + + html += getIcon('keyboard_arrow_left', 'btnSlideshowPrevious slideshowButton hide-mouse-idle-tv', false); + html += getIcon('keyboard_arrow_right', 'btnSlideshowNext slideshowButton hide-mouse-idle-tv', false); + + html += '
'; + if (actionButtonsOnTop) { + if (appHost.supports('filedownload') && options.user && options.user.Policy.EnableContentDownloading) { + html += getIcon('file_download', 'btnDownload slideshowButton', true); + } + if (appHost.supports('sharing')) { + html += getIcon('share', 'btnShare slideshowButton', true); + } } - } + html += getIcon('close', 'slideshowButton btnSlideshowExit hide-mouse-idle-tv', false); + html += '
'; - return null; - } + if (!actionButtonsOnTop) { + html += '
'; - /** - * Retrieves a backdrop's image URL from the API. - * @param {object} item - Item used to generate the image URL. - * @param {object} options - Options of the image. - * @param {object} apiClient - API client instance used to retrieve the image. - * @returns {null|string} URL of the item's backdrop. - */ - function getBackdropImageUrl(item, options, apiClient) { - options = options || {}; - options.type = options.type || 'Backdrop'; + html += getIcon('play_arrow', 'btnSlideshowPause slideshowButton', true, true); + if (appHost.supports('filedownload') && options.user && options.user.Policy.EnableContentDownloading) { + html += getIcon('file_download', 'btnDownload slideshowButton', true); + } + if (appHost.supports('sharing')) { + html += getIcon('share', 'btnShare slideshowButton', true); + } - // If not resizing, get the original image - if (!options.maxWidth && !options.width && !options.maxHeight && !options.height) { - options.quality = 100; - } - - if (item.BackdropImageTags && item.BackdropImageTags.length) { - - options.tag = item.BackdropImageTags[0]; - return apiClient.getScaledImageUrl(item.Id, options); - } - - return null; - } - - /** - * Dispatches a request for an item's image to its respective handler. - * @param {object} item - Item used to generate the image URL. - * @returns {string} URL of the item's image. - */ - function getImgUrl(item, user) { - var apiClient = connectionManager.getApiClient(item.ServerId); - var imageOptions = {}; - - if (item.BackdropImageTags && item.BackdropImageTags.length) { - return getBackdropImageUrl(item, imageOptions, apiClient); + html += '
'; + } } else { - if (item.MediaType === 'Photo' && user && user.Policy.EnableContentDownloading) { - return apiClient.getItemDownloadUrl(item.Id); - } - imageOptions.type = 'Primary'; - return getImageUrl(item, imageOptions, apiClient); - } - } - - /** - * Generates a button using the specified icon, classes and properties. - * @param {string} icon - Name of the material icon on the button - * @param {string} cssClass - CSS classes to assign to the button - * @param {boolean} canFocus - Flag to set the tabindex attribute on the button to -1. - * @param {boolean} autoFocus - Flag to set the autofocus attribute on the button. - * @returns {string} The HTML markup of the button. - */ - function getIcon(icon, cssClass, canFocus, autoFocus) { - var tabIndex = canFocus ? '' : ' tabindex="-1"'; - autoFocus = autoFocus ? ' autofocus' : ''; - return ''; - } - - /** - * Sets the viewport meta tag to enable or disable scaling by the user. - * @param {boolean} scalable - Flag to set the scalability of the viewport. - */ - function setUserScalable(scalable) { - try { - appHost.setUserScalable(scalable); - } catch (err) { - console.error('error in appHost.setUserScalable: ' + err); - } - } - - return function (options) { - var self = this; - /** Initialized instance of Swiper. */ - var swiperInstance; - /** Initialized instance of the dialog containing the Swiper instance. */ - var dialog; - /** Options of the slideshow components */ - var currentOptions; - /** ID of the timeout used to hide the OSD. */ - var hideTimeout; - /** Last coordinates of the mouse pointer. */ - var lastMouseMoveData; - /** Visibility status of the OSD. */ - var _osdOpen = false; - - // Use autoplay on Chromecast since it is non-interactive. - if (browser.chromecast) options.interactive = false; - - /** - * Creates the HTML markup for the dialog and the OSD. - * @param {Object} options - Options used to create the dialog and slideshow. - */ - function createElements(options) { - currentOptions = options; - - dialog = dialogHelper.createDialog({ - exitAnimationDuration: options.interactive ? 400 : 800, - size: 'fullscreen', - autoFocus: false, - scrollY: false, - exitAnimation: 'fadeout', - removeOnClose: true - }); - - dialog.classList.add('slideshowDialog'); - - var html = ''; - - html += '
'; - - if (options.interactive && !layoutManager.tv) { - var actionButtonsOnTop = layoutManager.mobile; - - html += getIcon('keyboard_arrow_left', 'btnSlideshowPrevious slideshowButton hide-mouse-idle-tv', false); - html += getIcon('keyboard_arrow_right', 'btnSlideshowNext slideshowButton hide-mouse-idle-tv', false); - - html += '
'; - if (actionButtonsOnTop) { - if (appHost.supports('filedownload') && options.user && options.user.Policy.EnableContentDownloading) { - html += getIcon('file_download', 'btnDownload slideshowButton', true); - } - if (appHost.supports('sharing')) { - html += getIcon('share', 'btnShare slideshowButton', true); - } - } - html += getIcon('close', 'slideshowButton btnSlideshowExit hide-mouse-idle-tv', false); - html += '
'; - - if (!actionButtonsOnTop) { - html += '
'; - - html += getIcon('play_arrow', 'btnSlideshowPause slideshowButton', true, true); - if (appHost.supports('filedownload') && options.user && options.user.Policy.EnableContentDownloading) { - html += getIcon('file_download', 'btnDownload slideshowButton', true); - } - if (appHost.supports('sharing')) { - html += getIcon('share', 'btnShare slideshowButton', true); - } - - html += '
'; - } - - } else { - html += '

'; - } - - dialog.innerHTML = html; - - if (options.interactive && !layoutManager.tv) { - dialog.querySelector('.btnSlideshowExit').addEventListener('click', function (e) { - dialogHelper.close(dialog); - }); - - var btnPause = dialog.querySelector('.btnSlideshowPause'); - if (btnPause) { - btnPause.addEventListener('click', playPause); - } - - var btnDownload = dialog.querySelector('.btnDownload'); - if (btnDownload) { - btnDownload.addEventListener('click', download); - } - - var btnShare = dialog.querySelector('.btnShare'); - if (btnShare) { - btnShare.addEventListener('click', share); - } - } - - setUserScalable(true); - - dialogHelper.open(dialog).then(function () { - setUserScalable(false); - }); - - inputManager.on(window, onInputCommand); - /* eslint-disable-next-line compat/compat */ - document.addEventListener((window.PointerEvent ? 'pointermove' : 'mousemove'), onPointerMove); - - dialog.addEventListener('close', onDialogClosed); - - loadSwiper(dialog, options); + html += '

'; } - /** - * Handles OSD changes when the autoplay is started. - */ - function onAutoplayStart() { - var btnSlideshowPause = dialog.querySelector('.btnSlideshowPause .material-icons'); - if (btnSlideshowPause) { - btnSlideshowPause.classList.replace('play_arrow', 'pause'); - } - } + dialog.innerHTML = html; - /** - * Handles OSD changes when the autoplay is stopped. - */ - function onAutoplayStop() { - var btnSlideshowPause = dialog.querySelector('.btnSlideshowPause .material-icons'); - if (btnSlideshowPause) { - btnSlideshowPause.classList.replace('pause', 'play_arrow'); - } - } - - /** - * Handles zoom changes. - */ - function onZoomChange(scale, imageEl, slideEl) { - const zoomImage = slideEl.querySelector('.swiper-zoom-fakeimg'); - - if (zoomImage) { - zoomImage.style.width = zoomImage.style.height = scale * 100 + '%'; - - if (scale > 1) { - if (zoomImage.classList.contains('swiper-zoom-fakeimg-hidden')) { - // Await for Swiper style changes - setTimeout(() => { - const callback = () => { - imageEl.removeEventListener(transitionEndEventName, callback); - zoomImage.classList.remove('swiper-zoom-fakeimg-hidden'); - }; - - // Swiper set 'transition-duration: 300ms' for auto zoom - // and 'transition-duration: 0s' for touch zoom - const transitionDuration = parseFloat(imageEl.style.transitionDuration.replace(/[a-z]/i, '')); - - if (transitionDuration > 0) { - imageEl.addEventListener(transitionEndEventName, callback); - } else { - callback(); - } - }, 0); - } - } else { - zoomImage.classList.add('swiper-zoom-fakeimg-hidden'); - } - } - } - - /** - * Initializes the Swiper instance and binds the relevant events. - * @param {HTMLElement} dialog - Element containing the dialog. - * @param {Object} options - Options used to initialize the Swiper instance. - */ - function loadSwiper(dialog, options) { - var slides; - if (currentOptions.slides) { - slides = currentOptions.slides; - } else { - slides = currentOptions.items; - } - - require(['swiper'], function (Swiper) { - swiperInstance = new Swiper(dialog.querySelector('.slideshowSwiperContainer'), { - direction: 'horizontal', - // Loop is disabled due to the virtual slides option not supporting it. - loop: false, - zoom: { - minRatio: 1, - toggle: true - }, - autoplay: !options.interactive, - keyboard: { - enabled: true - }, - preloadImages: true, - slidesPerView: 1, - slidesPerColumn: 1, - initialSlide: options.startIndex || 0, - speed: 240, - navigation: { - nextEl: '.btnSlideshowNext', - prevEl: '.btnSlideshowPrevious' - }, - // Virtual slides reduce memory consumption for large libraries while allowing preloading of images; - virtual: { - slides: slides, - cache: true, - renderSlide: getSwiperSlideHtml, - addSlidesBefore: 1, - addSlidesAfter: 1 - } - }); - - swiperInstance.on('autoplayStart', onAutoplayStart); - swiperInstance.on('autoplayStop', onAutoplayStop); - - if (useFakeZoomImage) { - swiperInstance.on('zoomChange', onZoomChange); - } - }); - } - - /** - * Renders the HTML markup of a slide for an item or a slide. - * @param {Object} item - The item used to render the slide. - * @param {number} index - The index of the item in the Swiper instance. - * @returns {string} The HTML markup of the slide. - */ - function getSwiperSlideHtml(item, index) { - if (currentOptions.slides) { - return getSwiperSlideHtmlFromSlide(item); - } else { - return getSwiperSlideHtmlFromItem(item); - } - } - - /** - * Renders the HTML markup of a slide for an item. - * @param {Object} item - Item used to generate the slide. - * @returns {string} The HTML markup of the slide. - */ - function getSwiperSlideHtmlFromItem(item) { - return getSwiperSlideHtmlFromSlide({ - originalImage: getImgUrl(item, currentOptions.user), - //title: item.Name, - //description: item.Overview - Id: item.Id, - ServerId: item.ServerId - }); - } - - /** - * Renders the HTML markup of a slide for a slide object. - * @param {Object} item - Slide object used to generate the slide. - * @returns {string} The HTML markup of the slide. - */ - function getSwiperSlideHtmlFromSlide(item) { - var html = ''; - html += '
'; - html += '
'; - if (useFakeZoomImage) { - html += `
`; - } - html += ''; - html += '
'; - if (item.title || item.subtitle) { - html += '
'; - html += '
'; - if (item.title) { - html += '

'; - html += item.title; - html += '

'; - } - if (item.description) { - html += '
'; - html += item.description; - html += '
'; - } - html += '
'; - html += '
'; - } - html += '
'; - - return html; - } - - /** - * Fetches the information of the currently displayed slide. - * @returns {null|{itemId: string, shareUrl: string, serverId: string, url: string}} Object containing the information of the currently displayed slide. - */ - function getCurrentImageInfo() { - if (swiperInstance) { - var slide = document.querySelector('.swiper-slide-active'); - - if (slide) { - return { - url: slide.getAttribute('data-original'), - shareUrl: slide.getAttribute('data-original'), - itemId: slide.getAttribute('data-itemid'), - serverId: slide.getAttribute('data-serverid') - }; - } - return null; - } else { - return null; - } - } - - /** - * Starts a download for the currently displayed slide. - */ - function download() { - var imageInfo = getCurrentImageInfo(); - - require(['fileDownloader'], function (fileDownloader) { - fileDownloader.download([imageInfo]); - }); - } - - /** - * Shares the currently displayed slide using the browser's built-in sharing feature. - */ - function share() { - var imageInfo = getCurrentImageInfo(); - - navigator.share({ - url: imageInfo.shareUrl - }); - } - - /** - * Starts the autoplay feature of the Swiper instance. - */ - function play() { - if (swiperInstance.autoplay) { - swiperInstance.autoplay.start(); - } - } - - /** - * Pauses the autoplay feature of the Swiper instance; - */ - function pause() { - if (swiperInstance.autoplay) { - swiperInstance.autoplay.stop(); - } - } - - /** - * Toggles the autoplay feature of the Swiper instance. - */ - function playPause() { - var paused = !dialog.querySelector('.btnSlideshowPause .material-icons').classList.contains('pause'); - if (paused) { - play(); - } else { - pause(); - } - } - - /** - * Closes the dialog and destroys the Swiper instance. - */ - function onDialogClosed() { - var swiper = swiperInstance; - if (swiper) { - swiper.destroy(true, true); - swiperInstance = null; - } - - inputManager.off(window, onInputCommand); - /* eslint-disable-next-line compat/compat */ - document.removeEventListener((window.PointerEvent ? 'pointermove' : 'mousemove'), onPointerMove); - // Shows page scrollbar - document.body.classList.remove('hide-scroll'); - document.body.classList.add('force-scroll'); - } - - /** - * Shows the OSD. - */ - function showOsd() { - var bottom = dialog.querySelector('.slideshowBottomBar'); - if (bottom) { - slideUpToShow(bottom); - startHideTimer(); - } - } - - /** - * Hides the OSD. - */ - function hideOsd() { - var bottom = dialog.querySelector('.slideshowBottomBar'); - if (bottom) { - slideDownToHide(bottom); - } - } - - /** - * Starts the timer used to automatically hide the OSD. - */ - function startHideTimer() { - stopHideTimer(); - hideTimeout = setTimeout(hideOsd, 3000); - } - - /** - * Stops the timer used to automatically hide the OSD. - */ - function stopHideTimer() { - if (hideTimeout) { - clearTimeout(hideTimeout); - hideTimeout = null; - } - } - - /** - * Shows the OSD by sliding it into view. - * @param {HTMLElement} element - Element containing the OSD. - */ - function slideUpToShow(element) { - if (!element.classList.contains('hide')) { - return; - } - - _osdOpen = true; - element.classList.remove('hide'); - - var onFinish = function () { - focusManager.focus(element.querySelector('.btnSlideshowPause')); - }; - - if (!element.animate) { - onFinish(); - return; - } - - requestAnimationFrame(function () { - var keyframes = [ - { transform: 'translate3d(0,' + element.offsetHeight + 'px,0)', opacity: '.3', offset: 0 }, - { transform: 'translate3d(0,0,0)', opacity: '1', offset: 1 } - ]; - var timing = { duration: 300, iterations: 1, easing: 'ease-out' }; - element.animate(keyframes, timing).onfinish = onFinish; - }); - } - - /** - * Hides the OSD by sliding it out of view. - * @param {HTMLElement} element - Element containing the OSD. - */ - function slideDownToHide(element) { - if (element.classList.contains('hide')) { - return; - } - - var onFinish = function () { - element.classList.add('hide'); - _osdOpen = false; - }; - - if (!element.animate) { - onFinish(); - return; - } - - requestAnimationFrame(function () { - var keyframes = [ - { transform: 'translate3d(0,0,0)', opacity: '1', offset: 0 }, - { transform: 'translate3d(0,' + element.offsetHeight + 'px,0)', opacity: '.3', offset: 1 } - ]; - var timing = { duration: 300, iterations: 1, easing: 'ease-out' }; - element.animate(keyframes, timing).onfinish = onFinish; - }); - } - - /** - * Shows the OSD when moving the mouse pointer or touching the screen. - * @param {Event} event - Pointer movement event. - */ - function onPointerMove(event) { - var pointerType = event.pointerType || (layoutManager.mobile ? 'touch' : 'mouse'); - - if (pointerType === 'mouse') { - var eventX = event.screenX || 0; - var eventY = event.screenY || 0; - - var obj = lastMouseMoveData; - if (!obj) { - lastMouseMoveData = { - x: eventX, - y: eventY - }; - return; - } - - // if coord are same, it didn't move - if (Math.abs(eventX - obj.x) < 10 && Math.abs(eventY - obj.y) < 10) { - return; - } - - obj.x = eventX; - obj.y = eventY; - - showOsd(); - } - } - - /** - * Dispatches keyboard inputs to their proper handlers. - * @param {Event} event - Keyboard input event. - */ - function onInputCommand(event) { - switch (event.detail.command) { - case 'up': - case 'down': - case 'select': - case 'menu': - case 'info': - showOsd(); - break; - case 'play': - play(); - break; - case 'pause': - pause(); - break; - case 'playpause': - playPause(); - break; - default: - break; - } - } - - /** - * Shows the slideshow component. - */ - self.show = function () { - createElements(options); - // Hides page scrollbar - document.body.classList.remove('force-scroll'); - document.body.classList.add('hide-scroll'); - }; - - /** - * Hides the slideshow element. - */ - self.hide = function () { - if (dialog) { + if (options.interactive && !layoutManager.tv) { + dialog.querySelector('.btnSlideshowExit').addEventListener('click', function (e) { dialogHelper.close(dialog); + }); + + const btnPause = dialog.querySelector('.btnSlideshowPause'); + if (btnPause) { + btnPause.addEventListener('click', playPause); } + + const btnDownload = dialog.querySelector('.btnDownload'); + if (btnDownload) { + btnDownload.addEventListener('click', download); + } + + const btnShare = dialog.querySelector('.btnShare'); + if (btnShare) { + btnShare.addEventListener('click', share); + } + } + + setUserScalable(true); + + dialogHelper.open(dialog).then(function () { + setUserScalable(false); + }); + + inputManager.on(window, onInputCommand); + /* eslint-disable-next-line compat/compat */ + document.addEventListener((window.PointerEvent ? 'pointermove' : 'mousemove'), onPointerMove); + + dialog.addEventListener('close', onDialogClosed); + + loadSwiper(dialog, options); + } + + /** + * Handles OSD changes when the autoplay is started. + */ + function onAutoplayStart() { + const btnSlideshowPause = dialog.querySelector('.btnSlideshowPause .material-icons'); + if (btnSlideshowPause) { + btnSlideshowPause.classList.replace('play_arrow', 'pause'); + } + } + + /** + * Handles OSD changes when the autoplay is stopped. + */ + function onAutoplayStop() { + const btnSlideshowPause = dialog.querySelector('.btnSlideshowPause .material-icons'); + if (btnSlideshowPause) { + btnSlideshowPause.classList.replace('pause', 'play_arrow'); + } + } + + /** + * Handles zoom changes. + */ + function onZoomChange(swiper, scale, imageEl, slideEl) { + const zoomImage = slideEl.querySelector('.swiper-zoom-fakeimg'); + + if (zoomImage) { + zoomImage.style.width = zoomImage.style.height = scale * 100 + '%'; + + if (scale > 1) { + if (zoomImage.classList.contains('swiper-zoom-fakeimg-hidden')) { + // Await for Swiper style changes + setTimeout(() => { + const callback = () => { + imageEl.removeEventListener(transitionEndEventName, callback); + zoomImage.classList.remove('swiper-zoom-fakeimg-hidden'); + }; + + // Swiper set 'transition-duration: 300ms' for auto zoom + // and 'transition-duration: 0s' for touch zoom + const transitionDuration = parseFloat(imageEl.style.transitionDuration.replace(/[a-z]/i, '')); + + if (transitionDuration > 0) { + imageEl.addEventListener(transitionEndEventName, callback); + } else { + callback(); + } + }, 0); + } + } else { + zoomImage.classList.add('swiper-zoom-fakeimg-hidden'); + } + } + } + + /** + * Initializes the Swiper instance and binds the relevant events. + * @param {HTMLElement} dialog - Element containing the dialog. + * @param {Object} options - Options used to initialize the Swiper instance. + */ + function loadSwiper(dialog, options) { + let slides; + if (currentOptions.slides) { + slides = currentOptions.slides; + } else { + slides = currentOptions.items; + } + + import('swiper').then(({default: Swiper}) => { + swiperInstance = new Swiper(dialog.querySelector('.slideshowSwiperContainer'), { + direction: 'horizontal', + // Loop is disabled due to the virtual slides option not supporting it. + loop: false, + zoom: { + minRatio: 1, + toggle: true + }, + autoplay: !options.interactive, + keyboard: { + enabled: true + }, + preloadImages: true, + slidesPerView: 1, + slidesPerColumn: 1, + initialSlide: options.startIndex || 0, + speed: 240, + navigation: { + nextEl: '.btnSlideshowNext', + prevEl: '.btnSlideshowPrevious' + }, + // Virtual slides reduce memory consumption for large libraries while allowing preloading of images; + virtual: { + slides: slides, + cache: true, + renderSlide: getSwiperSlideHtml, + addSlidesBefore: 1, + addSlidesAfter: 1 + } + }); + + swiperInstance.on('autoplayStart', onAutoplayStart); + swiperInstance.on('autoplayStop', onAutoplayStop); + + if (useFakeZoomImage) { + swiperInstance.on('zoomChange', onZoomChange); + } + }); + } + + /** + * Renders the HTML markup of a slide for an item or a slide. + * @param {Object} item - The item used to render the slide. + * @param {number} index - The index of the item in the Swiper instance. + * @returns {string} The HTML markup of the slide. + */ + function getSwiperSlideHtml(item, index) { + if (currentOptions.slides) { + return getSwiperSlideHtmlFromSlide(item); + } else { + return getSwiperSlideHtmlFromItem(item); + } + } + + /** + * Renders the HTML markup of a slide for an item. + * @param {Object} item - Item used to generate the slide. + * @returns {string} The HTML markup of the slide. + */ + function getSwiperSlideHtmlFromItem(item) { + return getSwiperSlideHtmlFromSlide({ + originalImage: getImgUrl(item, currentOptions.user), + Id: item.Id, + ServerId: item.ServerId + }); + } + + /** + * Renders the HTML markup of a slide for a slide object. + * @param {Object} item - Slide object used to generate the slide. + * @returns {string} The HTML markup of the slide. + */ + function getSwiperSlideHtmlFromSlide(item) { + let html = ''; + html += '
'; + html += '
'; + if (useFakeZoomImage) { + html += `
`; + } + html += ''; + html += '
'; + if (item.title || item.subtitle) { + html += '
'; + html += '
'; + if (item.title) { + html += '

'; + html += item.title; + html += '

'; + } + if (item.description) { + html += '
'; + html += item.description; + html += '
'; + } + html += '
'; + html += '
'; + } + html += '
'; + + return html; + } + + /** + * Fetches the information of the currently displayed slide. + * @returns {null|{itemId: string, shareUrl: string, serverId: string, url: string}} Object containing the information of the currently displayed slide. + */ + function getCurrentImageInfo() { + if (swiperInstance) { + const slide = document.querySelector('.swiper-slide-active'); + + if (slide) { + return { + url: slide.getAttribute('data-original'), + shareUrl: slide.getAttribute('data-original'), + itemId: slide.getAttribute('data-itemid'), + serverId: slide.getAttribute('data-serverid') + }; + } + return null; + } else { + return null; + } + } + + /** + * Starts a download for the currently displayed slide. + */ + function download() { + const imageInfo = getCurrentImageInfo(); + + import('fileDownloader').then(({default: fileDownloader}) => { + fileDownloader.download([imageInfo]); + }); + } + + /** + * Shares the currently displayed slide using the browser's built-in sharing feature. + */ + function share() { + const imageInfo = getCurrentImageInfo(); + + navigator.share({ + url: imageInfo.shareUrl + }); + } + + /** + * Starts the autoplay feature of the Swiper instance. + */ + function play() { + if (swiperInstance.autoplay) { + swiperInstance.autoplay.start(); + } + } + + /** + * Pauses the autoplay feature of the Swiper instance; + */ + function pause() { + if (swiperInstance.autoplay) { + swiperInstance.autoplay.stop(); + } + } + + /** + * Toggles the autoplay feature of the Swiper instance. + */ + function playPause() { + const paused = !dialog.querySelector('.btnSlideshowPause .material-icons').classList.contains('pause'); + if (paused) { + play(); + } else { + pause(); + } + } + + /** + * Closes the dialog and destroys the Swiper instance. + */ + function onDialogClosed() { + const swiper = swiperInstance; + if (swiper) { + swiper.destroy(true, true); + swiperInstance = null; + } + + inputManager.off(window, onInputCommand); + /* eslint-disable-next-line compat/compat */ + document.removeEventListener((window.PointerEvent ? 'pointermove' : 'mousemove'), onPointerMove); + // Shows page scrollbar + document.body.classList.remove('hide-scroll'); + document.body.classList.add('force-scroll'); + } + + /** + * Shows the OSD. + */ + function showOsd() { + const bottom = dialog.querySelector('.slideshowBottomBar'); + if (bottom) { + slideUpToShow(bottom); + startHideTimer(); + } + } + + /** + * Hides the OSD. + */ + function hideOsd() { + const bottom = dialog.querySelector('.slideshowBottomBar'); + if (bottom) { + slideDownToHide(bottom); + } + } + + /** + * Starts the timer used to automatically hide the OSD. + */ + function startHideTimer() { + stopHideTimer(); + hideTimeout = setTimeout(hideOsd, 3000); + } + + /** + * Stops the timer used to automatically hide the OSD. + */ + function stopHideTimer() { + if (hideTimeout) { + clearTimeout(hideTimeout); + hideTimeout = null; + } + } + + /** + * Shows the OSD by sliding it into view. + * @param {HTMLElement} element - Element containing the OSD. + */ + function slideUpToShow(element) { + if (!element.classList.contains('hide')) { + return; + } + + element.classList.remove('hide'); + + const onFinish = function () { + focusManager.focus(element.querySelector('.btnSlideshowPause')); }; + + if (!element.animate) { + onFinish(); + return; + } + + requestAnimationFrame(function () { + const keyframes = [ + { transform: 'translate3d(0,' + element.offsetHeight + 'px,0)', opacity: '.3', offset: 0 }, + { transform: 'translate3d(0,0,0)', opacity: '1', offset: 1 } + ]; + const timing = { duration: 300, iterations: 1, easing: 'ease-out' }; + element.animate(keyframes, timing).onfinish = onFinish; + }); + } + + /** + * Hides the OSD by sliding it out of view. + * @param {HTMLElement} element - Element containing the OSD. + */ + function slideDownToHide(element) { + if (element.classList.contains('hide')) { + return; + } + + const onFinish = function () { + element.classList.add('hide'); + }; + + if (!element.animate) { + onFinish(); + return; + } + + requestAnimationFrame(function () { + const keyframes = [ + { transform: 'translate3d(0,0,0)', opacity: '1', offset: 0 }, + { transform: 'translate3d(0,' + element.offsetHeight + 'px,0)', opacity: '.3', offset: 1 } + ]; + const timing = { duration: 300, iterations: 1, easing: 'ease-out' }; + element.animate(keyframes, timing).onfinish = onFinish; + }); + } + + /** + * Shows the OSD when moving the mouse pointer or touching the screen. + * @param {Event} event - Pointer movement event. + */ + function onPointerMove(event) { + const pointerType = event.pointerType || (layoutManager.mobile ? 'touch' : 'mouse'); + + if (pointerType === 'mouse') { + const eventX = event.screenX || 0; + const eventY = event.screenY || 0; + + const obj = lastMouseMoveData; + if (!obj) { + lastMouseMoveData = { + x: eventX, + y: eventY + }; + return; + } + + // if coord are same, it didn't move + if (Math.abs(eventX - obj.x) < 10 && Math.abs(eventY - obj.y) < 10) { + return; + } + + obj.x = eventX; + obj.y = eventY; + + showOsd(); + } + } + + /** + * Dispatches keyboard inputs to their proper handlers. + * @param {Event} event - Keyboard input event. + */ + function onInputCommand(event) { + switch (event.detail.command) { + case 'up': + case 'down': + case 'select': + case 'menu': + case 'info': + showOsd(); + break; + case 'play': + play(); + break; + case 'pause': + pause(); + break; + case 'playpause': + playPause(); + break; + default: + break; + } + } + + /** + * Shows the slideshow component. + */ + self.show = function () { + createElements(options); + // Hides page scrollbar + document.body.classList.remove('force-scroll'); + document.body.classList.add('hide-scroll'); }; -}); + + /** + * Hides the slideshow element. + */ + self.hide = function () { + if (dialog) { + dialogHelper.close(dialog); + } + }; +} diff --git a/src/components/sortmenu/sortmenu.js b/src/components/sortmenu/sortmenu.js index dbf7ef1a7f..d38d98c090 100644 --- a/src/components/sortmenu/sortmenu.js +++ b/src/components/sortmenu/sortmenu.js @@ -1,54 +1,51 @@ -define(['require', 'dom', 'focusManager', 'dialogHelper', 'loading', 'layoutManager', 'connectionManager', 'globalize', 'userSettings', 'emby-select', 'paper-icon-button-light', 'material-icons', 'css!./../formdialog', 'emby-button', 'flexStyles'], function (require, dom, focusManager, dialogHelper, loading, layoutManager, connectionManager, globalize, userSettings) { - 'use strict'; +import dialogHelper from 'dialogHelper'; +import layoutManager from 'layoutManager'; +import globalize from 'globalize'; +import * as userSettings from 'userSettings'; +import 'emby-select'; +import 'paper-icon-button-light'; +import 'material-icons'; +import 'css!./../formdialog'; +import 'emby-button'; +import 'flexStyles'; - function onSubmit(e) { +function onSubmit(e) { + e.preventDefault(); + return false; +} - e.preventDefault(); - return false; - } +function initEditor(context, settings) { + context.querySelector('form').addEventListener('submit', onSubmit); - function initEditor(context, settings) { + context.querySelector('.selectSortOrder').value = settings.sortOrder; + context.querySelector('.selectSortBy').value = settings.sortBy; +} - context.querySelector('form').addEventListener('submit', onSubmit); +function centerFocus(elem, horiz, on) { + import('scrollHelper').then(({default: scrollHelper}) => { + const fn = on ? 'on' : 'off'; + scrollHelper.centerFocus[fn](elem, horiz); + }); +} - context.querySelector('.selectSortOrder').value = settings.sortOrder; - context.querySelector('.selectSortBy').value = settings.sortBy; - } +function fillSortBy(context, options) { + const selectSortBy = context.querySelector('.selectSortBy'); - function centerFocus(elem, horiz, on) { - require(['scrollHelper'], function (scrollHelper) { - var fn = on ? 'on' : 'off'; - scrollHelper.centerFocus[fn](elem, horiz); - }); - } + selectSortBy.innerHTML = options.map(function (o) { + return ''; + }).join(''); +} - function fillSortBy(context, options) { - var selectSortBy = context.querySelector('.selectSortBy'); - - selectSortBy.innerHTML = options.map(function (o) { - - return ''; - - }).join(''); - } - - function saveValues(context, settings, settingsKey) { - - userSettings.setFilter(settingsKey + '-sortorder', context.querySelector('.selectSortOrder').value); - userSettings.setFilter(settingsKey + '-sortby', context.querySelector('.selectSortBy').value); - } - - function SortMenu() { - - } - - SortMenu.prototype.show = function (options) { +function saveValues(context, settingsKey) { + userSettings.setFilter(settingsKey + '-sortorder', context.querySelector('.selectSortOrder').value); + userSettings.setFilter(settingsKey + '-sortby', context.querySelector('.selectSortBy').value); +} +class SortMenu { + show(options) { return new Promise(function (resolve, reject) { - - require(['text!./sortmenu.template.html'], function (template) { - - var dialogOptions = { + import('text!./sortmenu.template.html').then(({default: template}) => { + const dialogOptions = { removeOnClose: true, scrollY: false }; @@ -59,11 +56,11 @@ define(['require', 'dom', 'focusManager', 'dialogHelper', 'loading', 'layoutMana dialogOptions.size = 'small'; } - var dlg = dialogHelper.createDialog(dialogOptions); + const dlg = dialogHelper.createDialog(dialogOptions); dlg.classList.add('formDialog'); - var html = ''; + let html = ''; html += '
'; html += ''; @@ -79,7 +76,6 @@ define(['require', 'dom', 'focusManager', 'dialogHelper', 'loading', 'layoutMana initEditor(dlg, options.settings); dlg.querySelector('.btnCancel').addEventListener('click', function () { - dialogHelper.close(dlg); }); @@ -87,30 +83,20 @@ define(['require', 'dom', 'focusManager', 'dialogHelper', 'loading', 'layoutMana centerFocus(dlg.querySelector('.formDialogContent'), false, true); } - var submitted; + let submitted; dlg.querySelector('form').addEventListener('change', function () { - submitted = true; - //if (options.onChange) { - // saveValues(dlg, options.settings, options.settingsKey); - // options.onChange(); - //} - }, true); dialogHelper.open(dlg).then(function () { - if (layoutManager.tv) { centerFocus(dlg.querySelector('.formDialogContent'), false, false); } if (submitted) { - - //if (!options.onChange) { - saveValues(dlg, options.settings, options.settingsKey); + saveValues(dlg, options.settingsKey); resolve(); - //} return; } @@ -118,7 +104,7 @@ define(['require', 'dom', 'focusManager', 'dialogHelper', 'loading', 'layoutMana }); }); }); - }; + } +} - return SortMenu; -}); +export default SortMenu; diff --git a/src/components/subtitleeditor/subtitleeditor.js b/src/components/subtitleeditor/subtitleeditor.js index 9c0992c53c..2ade9966cb 100644 --- a/src/components/subtitleeditor/subtitleeditor.js +++ b/src/components/subtitleeditor/subtitleeditor.js @@ -1,520 +1,436 @@ -define(['dialogHelper', 'require', 'layoutManager', 'globalize', 'userSettings', 'connectionManager', 'loading', 'focusManager', 'dom', 'apphost', 'emby-select', 'listViewStyle', 'paper-icon-button-light', 'css!./../formdialog', 'material-icons', 'css!./subtitleeditor', 'emby-button', 'flexStyles'], function (dialogHelper, require, layoutManager, globalize, userSettings, connectionManager, loading, focusManager, dom, appHost) { - 'use strict'; +import dialogHelper from 'dialogHelper'; +import layoutManager from 'layoutManager'; +import globalize from 'globalize'; +import * as userSettings from 'userSettings'; +import loading from 'loading'; +import focusManager from 'focusManager'; +import dom from 'dom'; +import 'emby-select'; +import 'listViewStyle'; +import 'paper-icon-button-light'; +import 'css!./../formdialog'; +import 'material-icons'; +import 'css!./subtitleeditor'; +import 'emby-button'; +import 'flexStyles'; - var currentItem; - var hasChanges; +let currentItem; +let hasChanges; - function showLocalSubtitles(context, index) { +function downloadRemoteSubtitles(context, id) { + let url = 'Items/' + currentItem.Id + '/RemoteSearch/Subtitles/' + id; - loading.show(); + let apiClient = window.connectionManager.getApiClient(currentItem.ServerId); + apiClient.ajax({ - var subtitleContent = context.querySelector('.subtitleContent'); - subtitleContent.innerHTML = ''; + type: 'POST', + url: apiClient.getUrl(url) - var apiClient = connectionManager.getApiClient(currentItem.ServerId); - var url = 'Videos/' + currentItem.Id + '/Subtitles/' + index; + }).then(function () { + hasChanges = true; - apiClient.ajax({ - - type: 'GET', - url: url - - }).then(function (result) { - - subtitleContent.innerHTML = result; - - loading.hide(); + import('toast').then(({default: toast}) => { + toast(globalize.translate('MessageDownloadQueued')); }); - } - function showRemoteSubtitles(context, id) { + focusManager.autoFocus(context); + }); +} - loading.show(); +function deleteLocalSubtitle(context, index) { + let msg = globalize.translate('MessageAreYouSureDeleteSubtitles'); - var url = 'Providers/Subtitles/Subtitles/' + id; + import('confirm').then(({default: confirm}) => { + confirm({ - ApiClient.get(ApiClient.getUrl(url)).then(function (result) { - - // show result - - loading.hide(); - }); - } - - function downloadRemoteSubtitles(context, id) { - - var url = 'Items/' + currentItem.Id + '/RemoteSearch/Subtitles/' + id; - - var apiClient = connectionManager.getApiClient(currentItem.ServerId); - apiClient.ajax({ - - type: 'POST', - url: apiClient.getUrl(url) + title: globalize.translate('ConfirmDeletion'), + text: msg, + confirmText: globalize.translate('Delete'), + primary: 'delete' }).then(function () { + loading.show(); - hasChanges = true; + let itemId = currentItem.Id; + let url = 'Videos/' + itemId + '/Subtitles/' + index; - require(['toast'], function (toast) { - toast(globalize.translate('MessageDownloadQueued')); - }); + let apiClient = window.connectionManager.getApiClient(currentItem.ServerId); - focusManager.autoFocus(context); - }); - } + apiClient.ajax({ - function deleteLocalSubtitle(context, index) { - - var msg = globalize.translate('MessageAreYouSureDeleteSubtitles'); - - require(['confirm'], function (confirm) { - - confirm.default({ - - title: globalize.translate('ConfirmDeletion'), - text: msg, - confirmText: globalize.translate('Delete'), - primary: 'delete' + type: 'DELETE', + url: apiClient.getUrl(url) }).then(function () { - - loading.show(); - - var itemId = currentItem.Id; - var url = 'Videos/' + itemId + '/Subtitles/' + index; - - var apiClient = connectionManager.getApiClient(currentItem.ServerId); - - apiClient.ajax({ - - type: 'DELETE', - url: apiClient.getUrl(url) - - }).then(function () { - - hasChanges = true; - reload(context, apiClient, itemId); - - }); + hasChanges = true; + reload(context, apiClient, itemId); }); }); - } + }); +} - function fillSubtitleList(context, item) { +function fillSubtitleList(context, item) { + let streams = item.MediaStreams || []; - var streams = item.MediaStreams || []; + let subs = streams.filter(function (s) { + return s.Type === 'Subtitle'; + }); - var subs = streams.filter(function (s) { + let html = ''; - return s.Type === 'Subtitle'; - }); + if (subs.length) { + html += '

' + globalize.translate('MySubtitles') + '

'; - var html = ''; + html += '
'; - if (subs.length) { + html += subs.map(function (s) { + let itemHtml = ''; - html += '

' + globalize.translate('MySubtitles') + '

'; + let tagName = layoutManager.tv ? 'button' : 'div'; + let className = layoutManager.tv && s.Path ? 'listItem listItem-border btnDelete' : 'listItem listItem-border'; - html += '
'; - - html += subs.map(function (s) { - - var itemHtml = ''; - - var tagName = layoutManager.tv ? 'button' : 'div'; - var className = layoutManager.tv && s.Path ? 'listItem listItem-border btnDelete' : 'listItem listItem-border'; - - if (layoutManager.tv) { - className += ' listItem-focusscale listItem-button'; - } - - className += ' listItem-noborder'; - - itemHtml += '<' + tagName + ' class="' + className + '" data-index="' + s.Index + '">'; - - itemHtml += ''; - - itemHtml += '
'; - - itemHtml += '
'; - itemHtml += s.DisplayTitle || ''; - itemHtml += '
'; - - if (s.Path) { - itemHtml += '
' + (s.Path) + '
'; - } - - itemHtml += ''; - itemHtml += '
'; - - if (!layoutManager.tv) { - if (s.Path) { - itemHtml += ''; - } - } - - itemHtml += ''; - - return itemHtml; - - }).join(''); - - html += '
'; - } - - var elem = context.querySelector('.subtitleList'); - - if (subs.length) { - elem.classList.remove('hide'); - } else { - elem.classList.add('hide'); - } - elem.innerHTML = html; - - //('.btnViewSubtitles', elem).on('click', function () { - - // var index = this.getAttribute('data-index'); - - // showLocalSubtitles(context, index); - - //}); - } - - function fillLanguages(context, apiClient, languages) { - - var selectLanguage = context.querySelector('#selectLanguage'); - - selectLanguage.innerHTML = languages.map(function (l) { - - return ''; - }); - - var lastLanguage = userSettings.get('subtitleeditor-language'); - if (lastLanguage) { - selectLanguage.value = lastLanguage; - } else { - - apiClient.getCurrentUser().then(function (user) { - - var lang = user.Configuration.SubtitleLanguagePreference; - - if (lang) { - selectLanguage.value = lang; - } - }); - } - } - - function renderSearchResults(context, results) { - - var lastProvider = ''; - var html = ''; - - if (!results.length) { - - context.querySelector('.noSearchResults').classList.remove('hide'); - context.querySelector('.subtitleResults').innerHTML = ''; - loading.hide(); - return; - } - - context.querySelector('.noSearchResults').classList.add('hide'); - - for (var i = 0, length = results.length; i < length; i++) { - - var result = results[i]; - - var provider = result.ProviderName; - - if (provider !== lastProvider) { - - if (i > 0) { - html += '
'; - } - html += '

' + provider + '

'; - html += '
'; - lastProvider = provider; - } - - var tagName = layoutManager.tv ? 'button' : 'div'; - var className = layoutManager.tv ? 'listItem listItem-border btnOptions' : 'listItem listItem-border'; if (layoutManager.tv) { className += ' listItem-focusscale listItem-button'; } - html += '<' + tagName + ' class="' + className + '" data-subid="' + result.Id + '">'; + className += ' listItem-noborder'; - html += ''; + itemHtml += '<' + tagName + ' class="' + className + '" data-index="' + s.Index + '">'; - var bodyClass = result.Comment || result.IsHashMatch ? 'three-line' : 'two-line'; + itemHtml += ''; - html += ''; if (!layoutManager.tv) { - html += ''; + if (s.Path) { + itemHtml += ''; + } } - html += ''; + itemHtml += ''; + + return itemHtml; + }).join(''); + + html += '
'; + } + + let elem = context.querySelector('.subtitleList'); + + if (subs.length) { + elem.classList.remove('hide'); + } else { + elem.classList.add('hide'); + } + elem.innerHTML = html; +} + +function fillLanguages(context, apiClient, languages) { + let selectLanguage = context.querySelector('#selectLanguage'); + + selectLanguage.innerHTML = languages.map(function (l) { + return ''; + }); + + let lastLanguage = userSettings.get('subtitleeditor-language'); + if (lastLanguage) { + selectLanguage.value = lastLanguage; + } else { + apiClient.getCurrentUser().then(function (user) { + let lang = user.Configuration.SubtitleLanguagePreference; + + if (lang) { + selectLanguage.value = lang; + } + }); + } +} + +function renderSearchResults(context, results) { + let lastProvider = ''; + let html = ''; + + if (!results.length) { + context.querySelector('.noSearchResults').classList.remove('hide'); + context.querySelector('.subtitleResults').innerHTML = ''; + loading.hide(); + return; + } + + context.querySelector('.noSearchResults').classList.add('hide'); + + for (let i = 0, length = results.length; i < length; i++) { + let result = results[i]; + + let provider = result.ProviderName; + + if (provider !== lastProvider) { + if (i > 0) { + html += '
'; + } + html += '

' + provider + '

'; + html += '
'; + lastProvider = provider; } - if (results.length) { - html += '
'; + let tagName = layoutManager.tv ? 'button' : 'div'; + let className = layoutManager.tv ? 'listItem listItem-border btnOptions' : 'listItem listItem-border'; + if (layoutManager.tv) { + className += ' listItem-focusscale listItem-button'; } - var elem = context.querySelector('.subtitleResults'); - elem.innerHTML = html; + html += '<' + tagName + ' class="' + className + '" data-subid="' + result.Id + '">'; - //('.btnViewSubtitle', elem).on('click', function () { + html += ''; - // var id = this.getAttribute('data-subid'); - // showRemoteSubtitles(context, id); - //}); + let bodyClass = result.Comment || result.IsHashMatch ? 'three-line' : 'two-line'; + + html += '
'; + + html += '
' + (result.Name) + '
'; + html += '
'; + + if (result.Format) { + html += '' + globalize.translate('FormatValue', result.Format) + ''; + } + + if (result.DownloadCount != null) { + html += '' + globalize.translate('DownloadsValue', result.DownloadCount) + ''; + } + html += '
'; + + if (result.Comment) { + html += '
' + (result.Comment) + '
'; + } + + if (result.IsHashMatch) { + html += '
' + globalize.translate('PerfectMatch') + '
'; + } + + html += '
'; + + if (!layoutManager.tv) { + html += ''; + } + + html += ''; + } + + if (results.length) { + html += '
'; + } + + let elem = context.querySelector('.subtitleResults'); + elem.innerHTML = html; + + loading.hide(); +} + +function searchForSubtitles(context, language) { + userSettings.set('subtitleeditor-language', language); + + loading.show(); + + let apiClient = window.connectionManager.getApiClient(currentItem.ServerId); + let url = apiClient.getUrl('Items/' + currentItem.Id + '/RemoteSearch/Subtitles/' + language); + + apiClient.getJSON(url).then(function (results) { + renderSearchResults(context, results); + }); +} + +function reload(context, apiClient, itemId) { + context.querySelector('.noSearchResults').classList.add('hide'); + + function onGetItem(item) { + currentItem = item; + + fillSubtitleList(context, item); + let file = item.Path || ''; + let index = Math.max(file.lastIndexOf('/'), file.lastIndexOf('\\')); + if (index > -1) { + file = file.substring(index + 1); + } + + if (file) { + context.querySelector('.pathValue').innerHTML = file; + context.querySelector('.originalFile').classList.remove('hide'); + } else { + context.querySelector('.pathValue').innerHTML = ''; + context.querySelector('.originalFile').classList.add('hide'); + } loading.hide(); } - function searchForSubtitles(context, language) { + if (typeof itemId === 'string') { + apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(onGetItem); + } else { + onGetItem(itemId); + } +} - userSettings.set('subtitleeditor-language', language); +function onSearchSubmit(e) { + let form = this; - loading.show(); + let lang = form.querySelector('#selectLanguage', form).value; - var apiClient = connectionManager.getApiClient(currentItem.ServerId); - var url = apiClient.getUrl('Items/' + currentItem.Id + '/RemoteSearch/Subtitles/' + language); + searchForSubtitles(dom.parentWithClass(form, 'formDialogContent'), lang); - apiClient.getJSON(url).then(function (results) { + e.preventDefault(); + return false; +} - renderSearchResults(context, results); - }); +function onSubtitleListClick(e) { + let btnDelete = dom.parentWithClass(e.target, 'btnDelete'); + if (btnDelete) { + let index = btnDelete.getAttribute('data-index'); + let context = dom.parentWithClass(btnDelete, 'subtitleEditorDialog'); + deleteLocalSubtitle(context, index); + } +} + +function onSubtitleResultsClick(e) { + let subtitleId; + let context; + + let btnOptions = dom.parentWithClass(e.target, 'btnOptions'); + if (btnOptions) { + subtitleId = btnOptions.getAttribute('data-subid'); + context = dom.parentWithClass(btnOptions, 'subtitleEditorDialog'); + showDownloadOptions(btnOptions, context, subtitleId); } - function reload(context, apiClient, itemId) { + let btnDownload = dom.parentWithClass(e.target, 'btnDownload'); + if (btnDownload) { + subtitleId = btnDownload.getAttribute('data-subid'); + context = dom.parentWithClass(btnDownload, 'subtitleEditorDialog'); + downloadRemoteSubtitles(context, subtitleId); + } +} - context.querySelector('.noSearchResults').classList.add('hide'); +function showDownloadOptions(button, context, subtitleId) { + let items = []; - function onGetItem(item) { + items.push({ + name: globalize.translate('Download'), + id: 'download' + }); - currentItem = item; + import('actionsheet').then(({default: actionsheet}) => { + actionsheet.show({ + items: items, + positionTo: button - fillSubtitleList(context, item); - var file = item.Path || ''; - var index = Math.max(file.lastIndexOf('/'), file.lastIndexOf('\\')); - if (index > -1) { - file = file.substring(index + 1); + }).then(function (id) { + switch (id) { + case 'download': + downloadRemoteSubtitles(context, subtitleId); + break; + default: + break; } + }); + }); +} - if (file) { - context.querySelector('.pathValue').innerHTML = file; - context.querySelector('.originalFile').classList.remove('hide'); - } else { - context.querySelector('.pathValue').innerHTML = ''; - context.querySelector('.originalFile').classList.add('hide'); - } +function centerFocus(elem, horiz, on) { + import('scrollHelper').then(({default: scrollHelper}) => { + let fn = on ? 'on' : 'off'; + scrollHelper.centerFocus[fn](elem, horiz); + }); +} - loading.hide(); - } +function showEditorInternal(itemId, serverId, template) { + hasChanges = false; - if (typeof itemId === 'string') { - apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(onGetItem); + let apiClient = window.connectionManager.getApiClient(serverId); + return apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(function (item) { + let dialogOptions = { + removeOnClose: true, + scrollY: false + }; + + if (layoutManager.tv) { + dialogOptions.size = 'fullscreen'; } else { - onGetItem(itemId); - } - } - - function onSearchSubmit(e) { - var form = this; - - var lang = form.querySelector('#selectLanguage', form).value; - - searchForSubtitles(dom.parentWithClass(form, 'formDialogContent'), lang); - - e.preventDefault(); - return false; - } - - function onSubtitleListClick(e) { - - var btnDelete = dom.parentWithClass(e.target, 'btnDelete'); - if (btnDelete) { - var index = btnDelete.getAttribute('data-index'); - var context = dom.parentWithClass(btnDelete, 'subtitleEditorDialog'); - deleteLocalSubtitle(context, index); - } - } - - function onSubtitleResultsClick(e) { - - var btnOptions = dom.parentWithClass(e.target, 'btnOptions'); - var subtitleId; - var context; - - if (btnOptions) { - subtitleId = btnOptions.getAttribute('data-subid'); - context = dom.parentWithClass(btnOptions, 'subtitleEditorDialog'); - showDownloadOptions(btnOptions, context, subtitleId); + dialogOptions.size = 'small'; } - var btnDownload = dom.parentWithClass(e.target, 'btnDownload'); - if (btnDownload) { - subtitleId = btnDownload.getAttribute('data-subid'); - context = dom.parentWithClass(btnDownload, 'subtitleEditorDialog'); - downloadRemoteSubtitles(context, subtitleId); + let dlg = dialogHelper.createDialog(dialogOptions); + + dlg.classList.add('formDialog'); + dlg.classList.add('subtitleEditorDialog'); + + dlg.innerHTML = globalize.translateHtml(template, 'core'); + + dlg.querySelector('.originalSubtitleFileLabel').innerHTML = globalize.translate('File'); + + dlg.querySelector('.subtitleSearchForm').addEventListener('submit', onSearchSubmit); + + let btnSubmit = dlg.querySelector('.btnSubmit'); + + if (layoutManager.tv) { + centerFocus(dlg.querySelector('.formDialogContent'), false, true); + dlg.querySelector('.btnSearchSubtitles').classList.add('hide'); + } else { + btnSubmit.classList.add('hide'); } - } - function showDownloadOptions(button, context, subtitleId) { + let editorContent = dlg.querySelector('.formDialogContent'); - var items = []; + dlg.querySelector('.subtitleList').addEventListener('click', onSubtitleListClick); + dlg.querySelector('.subtitleResults').addEventListener('click', onSubtitleResultsClick); - items.push({ - name: globalize.translate('Download'), - id: 'download' + apiClient.getCultures().then(function (languages) { + fillLanguages(editorContent, apiClient, languages); }); - require(['actionsheet'], function (actionsheet) { + dlg.querySelector('.btnCancel').addEventListener('click', function () { + dialogHelper.close(dlg); + }); - actionsheet.show({ - items: items, - positionTo: button + return new Promise(function (resolve, reject) { + dlg.addEventListener('close', function () { + if (layoutManager.tv) { + centerFocus(dlg.querySelector('.formDialogContent'), false, false); + } - }).then(function (id) { - - switch (id) { - - case 'download': - downloadRemoteSubtitles(context, subtitleId); - break; - default: - break; + if (hasChanges) { + resolve(); + } else { + reject(); } }); + dialogHelper.open(dlg); + + reload(editorContent, apiClient, item); }); - } + }); +} - function centerFocus(elem, horiz, on) { - require(['scrollHelper'], function (scrollHelper) { - var fn = on ? 'on' : 'off'; - scrollHelper.centerFocus[fn](elem, horiz); +function showEditor(itemId, serverId) { + loading.show(); + + return new Promise(function (resolve, reject) { + import('text!./subtitleeditor.template.html').then(({default: template}) => { + showEditorInternal(itemId, serverId, template).then(resolve, reject); }); - } + }); +} - function showEditorInternal(itemId, serverId, template) { - - hasChanges = false; - - var apiClient = connectionManager.getApiClient(serverId); - return apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(function (item) { - - var dialogOptions = { - removeOnClose: true, - scrollY: false - }; - - if (layoutManager.tv) { - dialogOptions.size = 'fullscreen'; - } else { - dialogOptions.size = 'small'; - } - - var dlg = dialogHelper.createDialog(dialogOptions); - - dlg.classList.add('formDialog'); - dlg.classList.add('subtitleEditorDialog'); - - dlg.innerHTML = globalize.translateHtml(template, 'core'); - - dlg.querySelector('.originalSubtitleFileLabel').innerHTML = globalize.translate('File'); - - dlg.querySelector('.subtitleSearchForm').addEventListener('submit', onSearchSubmit); - - var btnSubmit = dlg.querySelector('.btnSubmit'); - - if (layoutManager.tv) { - centerFocus(dlg.querySelector('.formDialogContent'), false, true); - dlg.querySelector('.btnSearchSubtitles').classList.add('hide'); - } else { - btnSubmit.classList.add('hide'); - } - - var editorContent = dlg.querySelector('.formDialogContent'); - - dlg.querySelector('.subtitleList').addEventListener('click', onSubtitleListClick); - dlg.querySelector('.subtitleResults').addEventListener('click', onSubtitleResultsClick); - - apiClient.getCultures().then(function (languages) { - - fillLanguages(editorContent, apiClient, languages); - }); - - dlg.querySelector('.btnCancel').addEventListener('click', function () { - - dialogHelper.close(dlg); - }); - - return new Promise(function (resolve, reject) { - - dlg.addEventListener('close', function () { - - if (layoutManager.tv) { - centerFocus(dlg.querySelector('.formDialogContent'), false, false); - } - - if (hasChanges) { - resolve(); - } else { - reject(); - } - }); - - dialogHelper.open(dlg); - - reload(editorContent, apiClient, item); - }); - }); - } - - function showEditor(itemId, serverId) { - - loading.show(); - - return new Promise(function (resolve, reject) { - - require(['text!./subtitleeditor.template.html'], function (template) { - - showEditorInternal(itemId, serverId, template).then(resolve, reject); - }); - }); - - } - - return { - show: showEditor - }; -}); +export default { + show: showEditor +}; diff --git a/src/components/subtitlesettings/subtitleappearancehelper.js b/src/components/subtitlesettings/subtitleappearancehelper.js index f710751376..1834cafa17 100644 --- a/src/components/subtitlesettings/subtitleappearancehelper.js +++ b/src/components/subtitlesettings/subtitleappearancehelper.js @@ -3,59 +3,32 @@ * @module components/subtitleSettings/subtitleAppearanceHelper */ -function getTextStyles(settings, isCue) { +function getTextStyles(settings, preview) { + const list = []; - let list = []; - - if (isCue) { - switch (settings.textSize || '') { - - case 'smaller': - list.push({ name: 'font-size', value: '.5em' }); - break; - case 'small': - list.push({ name: 'font-size', value: '.7em' }); - break; - case 'large': - list.push({ name: 'font-size', value: '1.3em' }); - break; - case 'larger': - list.push({ name: 'font-size', value: '1.72em' }); - break; - case 'extralarge': - list.push({ name: 'font-size', value: '2em' }); - break; - default: - case 'medium': - break; - } - } else { - switch (settings.textSize || '') { - - case 'smaller': - list.push({ name: 'font-size', value: '.8em' }); - break; - case 'small': - list.push({ name: 'font-size', value: 'inherit' }); - break; - case 'larger': - list.push({ name: 'font-size', value: '2em' }); - break; - case 'extralarge': - list.push({ name: 'font-size', value: '2.2em' }); - break; - case 'large': - list.push({ name: 'font-size', value: '1.72em' }); - break; - default: - case 'medium': - list.push({ name: 'font-size', value: '1.36em' }); - break; - } + switch (settings.textSize || '') { + case 'smaller': + list.push({ name: 'font-size', value: '.8em' }); + break; + case 'small': + list.push({ name: 'font-size', value: 'inherit' }); + break; + case 'larger': + list.push({ name: 'font-size', value: '2em' }); + break; + case 'extralarge': + list.push({ name: 'font-size', value: '2.2em' }); + break; + case 'large': + list.push({ name: 'font-size', value: '1.72em' }); + break; + default: + case 'medium': + list.push({ name: 'font-size', value: '1.36em' }); + break; } switch (settings.dropShadow || '') { - case 'raised': list.push({ name: 'text-shadow', value: '-1px -1px white, 0px -1px white, -1px 0px white, 1px 1px black, 0px 1px black, 1px 0px black' }); break; @@ -85,7 +58,6 @@ function getTextStyles(settings, isCue) { } switch (settings.font || '') { - case 'typewriter': list.push({ name: 'font-family', value: '"Courier New",monospace' }); list.push({ name: 'font-variant', value: 'none' }); @@ -116,30 +88,56 @@ function getTextStyles(settings, isCue) { break; } + if (!preview) { + const pos = parseInt(settings.verticalPosition, 10); + const lineHeight = 1.35; // FIXME: It is better to read this value from element + const line = Math.abs(pos * lineHeight); + if (pos < 0) { + list.push({ name: 'min-height', value: `${line}em` }); + list.push({ name: 'margin-top', value: '' }); + } else { + list.push({ name: 'min-height', value: '' }); + list.push({ name: 'margin-top', value: `${line}em` }); + } + } + return list; } -export function getStyles(settings, isCue) { +function getWindowStyles(settings, preview) { + const list = []; + if (!preview) { + const pos = parseInt(settings.verticalPosition, 10); + if (pos < 0) { + list.push({ name: 'top', value: '' }); + list.push({ name: 'bottom', value: '0' }); + } else { + list.push({ name: 'top', value: '0' }); + list.push({ name: 'bottom', value: '' }); + } + } + + return list; +} + +export function getStyles(settings, preview) { return { - text: getTextStyles(settings, isCue), - window: [] + text: getTextStyles(settings, preview), + window: getWindowStyles(settings, preview) }; } function applyStyleList(styles, elem) { - for (let i = 0, length = styles.length; i < length; i++) { - - let style = styles[i]; + const style = styles[i]; elem.style[style.name] = style.value; } } export function applyStyles(elements, appearanceSettings) { - - let styles = getStyles(appearanceSettings); + const styles = getStyles(appearanceSettings, !!elements.preview); if (elements.text) { applyStyleList(styles.text, elements.text); diff --git a/src/components/subtitlesettings/subtitlesettings.css b/src/components/subtitlesettings/subtitlesettings.css new file mode 100644 index 0000000000..204757f10f --- /dev/null +++ b/src/components/subtitlesettings/subtitlesettings.css @@ -0,0 +1,26 @@ +.subtitleappearance-fullpreview { + position: fixed; + width: 100%; + height: 100%; + top: 0; + left: 0; + z-index: 1000; + pointer-events: none; + transition: 0.2s; +} + +.subtitleappearance-fullpreview-hide { + opacity: 0; +} + +.subtitleappearance-fullpreview-window { + position: absolute; + width: 100%; + font-size: 170%; + text-align: center; +} + +.subtitleappearance-fullpreview-text { + display: inline-block; + max-width: 70%; +} diff --git a/src/components/subtitlesettings/subtitlesettings.js b/src/components/subtitlesettings/subtitlesettings.js index d2ae852bd5..0e2c4e3661 100644 --- a/src/components/subtitlesettings/subtitlesettings.js +++ b/src/components/subtitlesettings/subtitlesettings.js @@ -1,19 +1,20 @@ -import require from 'require'; import globalize from 'globalize'; import appHost from 'apphost'; import appSettings from 'appSettings'; import focusManager from 'focusManager'; +import layoutManager from 'layoutManager'; import loading from 'loading'; -import connectionManager from 'connectionManager'; import subtitleAppearanceHelper from 'subtitleAppearanceHelper'; import settingsHelper from 'settingsHelper'; import dom from 'dom'; import events from 'events'; import 'listViewStyle'; import 'emby-select'; +import 'emby-slider'; import 'emby-input'; import 'emby-checkbox'; import 'flexStyles'; +import 'css!./subtitlesettings'; /** * Subtitle settings. @@ -21,26 +22,25 @@ import 'flexStyles'; */ function getSubtitleAppearanceObject(context) { - let appearanceSettings = {}; + const appearanceSettings = {}; appearanceSettings.textSize = context.querySelector('#selectTextSize').value; appearanceSettings.dropShadow = context.querySelector('#selectDropShadow').value; appearanceSettings.font = context.querySelector('#selectFont').value; appearanceSettings.textBackground = context.querySelector('#inputTextBackground').value; appearanceSettings.textColor = context.querySelector('#inputTextColor').value; + appearanceSettings.verticalPosition = context.querySelector('#sliderVerticalPosition').value; return appearanceSettings; } function loadForm(context, user, userSettings, appearanceSettings, apiClient) { - apiClient.getCultures().then(function (allCultures) { - if (appHost.supports('subtitleburnsettings') && user.Policy.EnableVideoPlaybackTranscoding) { context.querySelector('.fldBurnIn').classList.remove('hide'); } - let selectSubtitleLanguage = context.querySelector('#selectSubtitleLanguage'); + const selectSubtitleLanguage = context.querySelector('#selectSubtitleLanguage'); settingsHelper.populateLanguages(selectSubtitleLanguage, allCultures); @@ -54,6 +54,7 @@ function loadForm(context, user, userSettings, appearanceSettings, apiClient) { context.querySelector('#inputTextBackground').value = appearanceSettings.textBackground || 'transparent'; context.querySelector('#inputTextColor').value = appearanceSettings.textColor || '#ffffff'; context.querySelector('#selectFont').value = appearanceSettings.font || ''; + context.querySelector('#sliderVerticalPosition').value = appearanceSettings.verticalPosition; context.querySelector('#selectSubtitleBurnIn').value = appSettings.get('subtitleburnin') || ''; @@ -66,7 +67,6 @@ function loadForm(context, user, userSettings, appearanceSettings, apiClient) { } function saveUser(context, user, userSettingsInstance, appearanceKey, apiClient) { - let appearanceSettings = userSettingsInstance.getSubtitleAppearanceSettings(appearanceKey); appearanceSettings = Object.assign(appearanceSettings, getSubtitleAppearanceObject(context)); @@ -79,15 +79,12 @@ function saveUser(context, user, userSettingsInstance, appearanceKey, apiClient) } function save(instance, context, userId, userSettings, apiClient, enableSaveConfirmation) { - loading.show(); appSettings.set('subtitleburnin', context.querySelector('#selectSubtitleBurnIn').value); apiClient.getUser(userId).then(function (user) { - saveUser(context, user, userSettings, instance.appearanceKey, apiClient).then(function () { - loading.hide(); if (enableSaveConfirmation) { import('toast').then(({default: toast}) => { @@ -96,7 +93,6 @@ function save(instance, context, userId, userSettings, apiClient, enableSaveConf } events.trigger(instance, 'saved'); - }, function () { loading.hide(); }); @@ -104,10 +100,9 @@ function save(instance, context, userId, userSettings, apiClient, enableSaveConf } function onSubtitleModeChange(e) { + const view = dom.parentWithClass(e.target, 'subtitlesettings'); - let view = dom.parentWithClass(e.target, 'subtitlesettings'); - - let subtitlesHelp = view.querySelectorAll('.subtitlesHelp'); + const subtitlesHelp = view.querySelectorAll('.subtitlesHelp'); for (let i = 0, length = subtitlesHelp.length; i < length; i++) { subtitlesHelp[i].classList.add('hide'); } @@ -115,23 +110,55 @@ function onSubtitleModeChange(e) { } function onAppearanceFieldChange(e) { + const view = dom.parentWithClass(e.target, 'subtitlesettings'); - let view = dom.parentWithClass(e.target, 'subtitlesettings'); + const appearanceSettings = getSubtitleAppearanceObject(view); - let appearanceSettings = getSubtitleAppearanceObject(view); - - let elements = { + const elements = { window: view.querySelector('.subtitleappearance-preview-window'), - text: view.querySelector('.subtitleappearance-preview-text') + text: view.querySelector('.subtitleappearance-preview-text'), + preview: true }; subtitleAppearanceHelper.applyStyles(elements, appearanceSettings); + + subtitleAppearanceHelper.applyStyles({ + window: view.querySelector('.subtitleappearance-fullpreview-window'), + text: view.querySelector('.subtitleappearance-fullpreview-text') + }, appearanceSettings); +} + +const subtitlePreviewDelay = 1000; +let subtitlePreviewTimer; + +function showSubtitlePreview(persistent) { + clearTimeout(subtitlePreviewTimer); + + this._fullPreview.classList.remove('subtitleappearance-fullpreview-hide'); + + if (persistent) { + this._refFullPreview++; + } + + if (this._refFullPreview === 0) { + subtitlePreviewTimer = setTimeout(hideSubtitlePreview.bind(this), subtitlePreviewDelay); + } +} + +function hideSubtitlePreview(persistent) { + clearTimeout(subtitlePreviewTimer); + + if (persistent) { + this._refFullPreview--; + } + + if (this._refFullPreview === 0) { + this._fullPreview.classList.add('subtitleappearance-fullpreview-hide'); + } } function embed(options, self) { - import('text!./subtitlesettings.template.html').then(({default: template}) => { - options.element.classList.add('subtitlesettings'); options.element.innerHTML = globalize.translateHtml(template, 'core'); @@ -150,6 +177,36 @@ function embed(options, self) { if (appHost.supports('subtitleappearancesettings')) { options.element.querySelector('.subtitleAppearanceSection').classList.remove('hide'); + + self._fullPreview = options.element.querySelector('.subtitleappearance-fullpreview'); + self._refFullPreview = 0; + + const sliderVerticalPosition = options.element.querySelector('#sliderVerticalPosition'); + sliderVerticalPosition.addEventListener('input', onAppearanceFieldChange); + sliderVerticalPosition.addEventListener('input', () => showSubtitlePreview.call(self)); + + const eventPrefix = window.PointerEvent ? 'pointer' : 'mouse'; + sliderVerticalPosition.addEventListener(`${eventPrefix}enter`, () => showSubtitlePreview.call(self, true)); + sliderVerticalPosition.addEventListener(`${eventPrefix}leave`, () => hideSubtitlePreview.call(self, true)); + + if (layoutManager.tv) { + sliderVerticalPosition.addEventListener('focus', () => showSubtitlePreview.call(self, true)); + sliderVerticalPosition.addEventListener('blur', () => hideSubtitlePreview.call(self, true)); + + // Give CustomElements time to attach + setTimeout(() => { + sliderVerticalPosition.classList.add('focusable'); + sliderVerticalPosition.enableKeyboardDragging(); + }, 0); + } + + options.element.querySelector('.chkPreview').addEventListener('change', (e) => { + if (e.target.checked) { + showSubtitlePreview.call(self, true); + } else { + hideSubtitlePreview.call(self, true); + } + }); } self.loadData(); @@ -161,29 +218,27 @@ function embed(options, self) { } export class SubtitleSettings { - constructor(options) { - this.options = options; embed(options, this); } loadData() { - let self = this; - let context = self.options.element; + const self = this; + const context = self.options.element; loading.show(); - let userId = self.options.userId; - let apiClient = connectionManager.getApiClient(self.options.serverId); - let userSettings = self.options.userSettings; + const userId = self.options.userId; + const apiClient = window.connectionManager.getApiClient(self.options.serverId); + const userSettings = self.options.userSettings; apiClient.getUser(userId).then(function (user) { userSettings.setUserInfo(userId, apiClient).then(function () { self.dataLoaded = true; - let appearanceSettings = userSettings.getSubtitleAppearanceSettings(self.options.appearanceKey); + const appearanceSettings = userSettings.getSubtitleAppearanceSettings(self.options.appearanceKey); loadForm(context, user, userSettings, appearanceSettings, apiClient); }); @@ -200,13 +255,12 @@ export class SubtitleSettings { onSubmit(e) { const self = this; - let apiClient = connectionManager.getApiClient(self.options.serverId); - let userId = self.options.userId; - let userSettings = self.options.userSettings; + const apiClient = window.connectionManager.getApiClient(self.options.serverId); + const userId = self.options.userId; + const userSettings = self.options.userSettings; userSettings.setUserInfo(userId, apiClient).then(function () { - - let enableSaveConfirmation = self.options.enableSaveConfirmation; + const enableSaveConfirmation = self.options.enableSaveConfirmation; save(self, self.options.element, userId, userSettings, apiClient, enableSaveConfirmation); }); diff --git a/src/components/subtitlesettings/subtitlesettings.template.html b/src/components/subtitlesettings/subtitlesettings.template.html index 716296a257..2884b26efd 100644 --- a/src/components/subtitlesettings/subtitlesettings.template.html +++ b/src/components/subtitlesettings/subtitlesettings.template.html @@ -14,7 +14,7 @@ - +
${DefaultSubtitlesHelp}
${SmartSubtitlesHelp}
@@ -38,6 +38,16 @@ ${HeaderSubtitleAppearance} +
+
+
+ ${HeaderSubtitleAppearance} +
+ ${TheseSettingsAffectSubtitlesOnThisDevice} +
+
+
+
@@ -89,6 +99,20 @@
+ +
+
+ +
+
${SubtitleVerticalPositionHelp}
+
+ +
+ +
'; - } - - function getTunerName(providerId) { - switch (providerId = providerId.toLowerCase()) { - case 'm3u': - return 'M3U'; - - case 'hdhomerun': - return 'HDHomerun'; - - case 'hauppauge': - return 'Hauppauge'; - - case 'satip': - return 'DVB'; - - default: - return 'Unknown'; + if (enableFocusTransform) { + cssClass += ' show-animation'; } } - function renderDevices(view, devices) { - var i; - var length; - var html = ''; + html += ''; +} - for (i = 0, length = devices.length; i < length; i++) { - html += getDeviceHtml(devices[i]); - } +function getTunerName(providerId) { + switch (providerId = providerId.toLowerCase()) { + case 'm3u': + return 'M3U'; - if (devices.length) { - view.querySelector('.devicesHeader').classList.remove('hide'); - } else { - html = '


' + globalize.translate('NoNewDevicesFound') + '

'; - view.querySelector('.devicesHeader').classList.add('hide'); - } + case 'hdhomerun': + return 'HDHomerun'; - var elem = view.querySelector('.results'); - elem.innerHTML = html; + case 'hauppauge': + return 'Hauppauge'; - if (layoutManager.tv) { - focusManager.autoFocus(elem); - } + case 'satip': + return 'DVB'; + + default: + return 'Unknown'; + } +} + +function renderDevices(view, devices) { + let html = ''; + + for (let i = 0, length = devices.length; i < length; i++) { + html += getDeviceHtml(devices[i]); } - function discoverDevices(view, apiClient) { - loading.show(); - view.querySelector('.loadingContent').classList.remove('hide'); - return ApiClient.getJSON(ApiClient.getUrl('LiveTv/Tuners/Discvover', { - NewDevicesOnly: true - })).then(function (devices) { - currentDevices = devices; - renderDevices(view, devices); - view.querySelector('.loadingContent').classList.add('hide'); - loading.hide(); - }); + if (devices.length) { + view.querySelector('.devicesHeader').classList.remove('hide'); + } else { + html = '


' + globalize.translate('NoNewDevicesFound') + '

'; + view.querySelector('.devicesHeader').classList.add('hide'); } - function tunerPicker() { - this.show = function (options) { - var dialogOptions = { - removeOnClose: true, - scrollY: false - }; + const elem = view.querySelector('.results'); + elem.innerHTML = html; - if (layoutManager.tv) { - dialogOptions.size = 'fullscreen'; - } else { - dialogOptions.size = 'small'; - } + if (layoutManager.tv) { + focusManager.autoFocus(elem); + } +} - var dlg = dialogHelper.createDialog(dialogOptions); - dlg.classList.add('formDialog'); - var html = ''; - html += '
'; - html += ''; - html += '

'; - html += globalize.translate('HeaderLiveTvTunerSetup'); - html += '

'; - html += '
'; - html += getEditorHtml(); - dlg.innerHTML = html; - dlg.querySelector('.btnCancel').addEventListener('click', function () { - dialogHelper.close(dlg); - }); - var deviceResult; - dlg.querySelector('.results').addEventListener('click', function (e) { - var tunerCard = dom.parentWithClass(e.target, 'card'); +function discoverDevices(view, apiClient) { + loading.show(); + view.querySelector('.loadingContent').classList.remove('hide'); + return ApiClient.getJSON(ApiClient.getUrl('LiveTv/Tuners/Discvover', { + NewDevicesOnly: true + })).then(function (devices) { + currentDevices = devices; + renderDevices(view, devices); + view.querySelector('.loadingContent').classList.add('hide'); + loading.hide(); + }); +} - if (tunerCard) { - var deviceId = tunerCard.getAttribute('data-id'); - deviceResult = currentDevices.filter(function (d) { - return d.DeviceId === deviceId; - })[0]; - dialogHelper.close(dlg); - } - }); - - if (layoutManager.tv) { - scrollHelper.centerFocus.on(dlg.querySelector('.formDialogContent'), false); - } - - var apiClient = connectionManager.getApiClient(options.serverId); - discoverDevices(dlg, apiClient); - - if (layoutManager.tv) { - scrollHelper.centerFocus.off(dlg.querySelector('.formDialogContent'), false); - } - - return dialogHelper.open(dlg).then(function () { - if (deviceResult) { - return Promise.resolve(deviceResult); - } - - return Promise.reject(); - }); +function tunerPicker() { + this.show = function (options) { + const dialogOptions = { + removeOnClose: true, + scrollY: false }; - } - var currentDevices = []; - return tunerPicker; -}); + if (layoutManager.tv) { + dialogOptions.size = 'fullscreen'; + } else { + dialogOptions.size = 'small'; + } + + const dlg = dialogHelper.createDialog(dialogOptions); + dlg.classList.add('formDialog'); + let html = ''; + html += '
'; + html += ''; + html += '

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

'; + html += '
'; + html += getEditorHtml(); + dlg.innerHTML = html; + dlg.querySelector('.btnCancel').addEventListener('click', function () { + dialogHelper.close(dlg); + }); + let deviceResult; + dlg.querySelector('.results').addEventListener('click', function (e) { + const tunerCard = dom.parentWithClass(e.target, 'card'); + + if (tunerCard) { + const deviceId = tunerCard.getAttribute('data-id'); + deviceResult = currentDevices.filter(function (d) { + return d.DeviceId === deviceId; + })[0]; + dialogHelper.close(dlg); + } + }); + + if (layoutManager.tv) { + scrollHelper.centerFocus.on(dlg.querySelector('.formDialogContent'), false); + } + + const apiClient = window.connectionManager.getApiClient(options.serverId); + discoverDevices(dlg, apiClient); + + if (layoutManager.tv) { + scrollHelper.centerFocus.off(dlg.querySelector('.formDialogContent'), false); + } + + return dialogHelper.open(dlg).then(function () { + if (deviceResult) { + return Promise.resolve(deviceResult); + } + + return Promise.reject(); + }); + }; +} + +let currentDevices = []; + +export default tunerPicker; diff --git a/src/components/tvproviders/schedulesdirect.js b/src/components/tvproviders/schedulesdirect.js index be1cdf575b..de469e1845 100644 --- a/src/components/tvproviders/schedulesdirect.js +++ b/src/components/tvproviders/schedulesdirect.js @@ -1,297 +1,304 @@ -define(['jQuery', 'loading', 'globalize', 'emby-checkbox', 'listViewStyle', 'emby-input', 'emby-select', 'emby-button', 'flexStyles'], function ($, loading, globalize) { - 'use strict'; +import $ from 'jQuery'; +import loading from 'loading'; +import globalize from 'globalize'; +import 'emby-checkbox'; +import 'emby-input'; +import 'listViewStyle'; +import 'paper-icon-button-light'; +import 'emby-select'; +import 'emby-button'; +import 'flexStyles'; - return function (page, providerId, options) { - function reload() { - loading.show(); - ApiClient.getNamedConfiguration('livetv').then(function (config) { - var info = config.ListingProviders.filter(function (i) { - return i.Id === providerId; - })[0] || {}; - listingsId = info.ListingsId; - $('#selectListing', page).val(info.ListingsId || ''); - page.querySelector('.txtUser').value = info.Username || ''; - page.querySelector('.txtPass').value = ''; - page.querySelector('.txtZipCode').value = info.ZipCode || ''; +export default function (page, providerId, options) { + function reload() { + loading.show(); + ApiClient.getNamedConfiguration('livetv').then(function (config) { + const info = config.ListingProviders.filter(function (i) { + return i.Id === providerId; + })[0] || {}; + listingsId = info.ListingsId; + $('#selectListing', page).val(info.ListingsId || ''); + page.querySelector('.txtUser').value = info.Username || ''; + page.querySelector('.txtPass').value = ''; + page.querySelector('.txtZipCode').value = info.ZipCode || ''; - if (info.Username && info.Password) { - page.querySelector('.listingsSection').classList.remove('hide'); - } else { - page.querySelector('.listingsSection').classList.add('hide'); - } - - page.querySelector('.chkAllTuners').checked = info.EnableAllTuners; - - if (info.EnableAllTuners) { - page.querySelector('.selectTunersSection').classList.add('hide'); - } else { - page.querySelector('.selectTunersSection').classList.remove('hide'); - } - - setCountry(info); - refreshTunerDevices(page, info, config.TunerHosts); - }); - } - - function setCountry(info) { - ApiClient.getJSON(ApiClient.getUrl('LiveTv/ListingProviders/SchedulesDirect/Countries')).then(function (result) { - var i; - var length; - var countryList = []; - - for (var region in result) { - var countries = result[region]; - - if (countries.length && 'ZZZ' !== region) { - for (i = 0, length = countries.length; i < length; i++) { - countryList.push({ - name: countries[i].fullName, - value: countries[i].shortName - }); - } - } - } - - countryList.sort(function (a, b) { - if (a.name > b.name) { - return 1; - } - - if (a.name < b.name) { - return -1; - } - - return 0; - }); - $('#selectCountry', page).html(countryList.map(function (c) { - return ''; - }).join('')).val(info.Country || ''); - $(page.querySelector('.txtZipCode')).trigger('change'); - }, function () { // ApiClient.getJSON() error handler - Dashboard.alert({ - message: globalize.translate('ErrorGettingTvLineups') - }); - }); - loading.hide(); - } - - function sha256(str) { - if (!self.TextEncoder) { - return Promise.resolve(''); + if (info.Username && info.Password) { + page.querySelector('.listingsSection').classList.remove('hide'); + } else { + page.querySelector('.listingsSection').classList.add('hide'); } - var buffer = new TextEncoder('utf-8').encode(str); - return crypto.subtle.digest('SHA-256', buffer).then(function (hash) { - return hex(hash); - }); - } + page.querySelector('.chkAllTuners').checked = info.EnableAllTuners; - function hex(buffer) { - var hexCodes = []; - var view = new DataView(buffer); - - for (var i = 0; i < view.byteLength; i += 4) { - var value = view.getUint32(i); - var stringValue = value.toString(16); - var paddedValue = ('00000000' + stringValue).slice(-'00000000'.length); - hexCodes.push(paddedValue); + if (info.EnableAllTuners) { + page.querySelector('.selectTunersSection').classList.add('hide'); + } else { + page.querySelector('.selectTunersSection').classList.remove('hide'); } - return hexCodes.join(''); - } + setCountry(info); + refreshTunerDevices(page, info, config.TunerHosts); + }); + } - function submitLoginForm() { - loading.show(); - sha256(page.querySelector('.txtPass').value).then(function (passwordHash) { - var info = { - Type: 'SchedulesDirect', - Username: page.querySelector('.txtUser').value, - EnableAllTuners: true, - Password: passwordHash, - Pw: page.querySelector('.txtPass').value - }; - var id = providerId; + function setCountry(info) { + ApiClient.getJSON(ApiClient.getUrl('LiveTv/ListingProviders/SchedulesDirect/Countries')).then(function (result) { + let i; + let length; + const countryList = []; - if (id) { - info.Id = id; + for (const region in result) { + const countries = result[region]; + + if (countries.length && region !== 'ZZZ') { + for (i = 0, length = countries.length; i < length; i++) { + countryList.push({ + name: countries[i].fullName, + value: countries[i].shortName + }); + } + } + } + + countryList.sort(function (a, b) { + if (a.name > b.name) { + return 1; } - ApiClient.ajax({ - type: 'POST', - url: ApiClient.getUrl('LiveTv/ListingProviders', { - ValidateLogin: true - }), - data: JSON.stringify(info), - contentType: 'application/json', - dataType: 'json' - }).then(function (result) { - Dashboard.processServerConfigurationUpdateResult(); - providerId = result.Id; - reload(); - }, function () { - Dashboard.alert({ // ApiClient.ajax() error handler - message: globalize.translate('ErrorSavingTvProvider') - }); - }); + if (a.name < b.name) { + return -1; + } + + return 0; }); + $('#selectCountry', page).html(countryList.map(function (c) { + return ''; + }).join('')).val(info.Country || ''); + $(page.querySelector('.txtZipCode')).trigger('change'); + }, function () { // ApiClient.getJSON() error handler + Dashboard.alert({ + message: globalize.translate('ErrorGettingTvLineups') + }); + }); + loading.hide(); + } + + function sha256(str) { + if (!self.TextEncoder) { + return Promise.resolve(''); } - function submitListingsForm() { - var selectedListingsId = $('#selectListing', page).val(); + const buffer = new TextEncoder('utf-8').encode(str); + return crypto.subtle.digest('SHA-256', buffer).then(function (hash) { + return hex(hash); + }); + } - if (!selectedListingsId) { - return void Dashboard.alert({ - message: globalize.translate('ErrorPleaseSelectLineup') - }); - } + function hex(buffer) { + const hexCodes = []; + const view = new DataView(buffer); - loading.show(); - var id = providerId; - ApiClient.getNamedConfiguration('livetv').then(function (config) { - var info = config.ListingProviders.filter(function (i) { - return i.Id === id; - })[0]; - info.ZipCode = page.querySelector('.txtZipCode').value; - info.Country = $('#selectCountry', page).val(); - info.ListingsId = selectedListingsId; - info.EnableAllTuners = page.querySelector('.chkAllTuners').checked; - info.EnabledTuners = info.EnableAllTuners ? [] : $('.chkTuner', page).get().filter(function (i) { - return i.checked; - }).map(function (i) { - return i.getAttribute('data-id'); - }); - ApiClient.ajax({ - type: 'POST', - url: ApiClient.getUrl('LiveTv/ListingProviders', { - ValidateListings: true - }), - data: JSON.stringify(info), - contentType: 'application/json' - }).then(function (result) { - loading.hide(); - - if (options.showConfirmation) { - Dashboard.processServerConfigurationUpdateResult(); - } - - Events.trigger(self, 'submitted'); - }, function () { - loading.hide(); - Dashboard.alert({ - message: globalize.translate('ErrorAddingListingsToSchedulesDirect') - }); - }); - }); + for (let i = 0; i < view.byteLength; i += 4) { + const value = view.getUint32(i); + const stringValue = value.toString(16); + const paddedValue = ('00000000' + stringValue).slice(-'00000000'.length); + hexCodes.push(paddedValue); } - function refreshListings(value) { - if (!value) { - return void $('#selectListing', page).html(''); + return hexCodes.join(''); + } + + function submitLoginForm() { + loading.show(); + sha256(page.querySelector('.txtPass').value).then(function (passwordHash) { + const info = { + Type: 'SchedulesDirect', + Username: page.querySelector('.txtUser').value, + EnableAllTuners: true, + Password: passwordHash, + Pw: page.querySelector('.txtPass').value + }; + const id = providerId; + + if (id) { + info.Id = id; } - loading.show(); ApiClient.ajax({ - type: 'GET', - url: ApiClient.getUrl('LiveTv/ListingProviders/Lineups', { - Id: providerId, - Location: value, - Country: $('#selectCountry', page).val() + type: 'POST', + url: ApiClient.getUrl('LiveTv/ListingProviders', { + ValidateLogin: true }), + data: JSON.stringify(info), + contentType: 'application/json', dataType: 'json' }).then(function (result) { - $('#selectListing', page).html(result.map(function (o) { - return ''; - })); - - if (listingsId) { - $('#selectListing', page).val(listingsId); - } - - loading.hide(); - }, function (result) { - Dashboard.alert({ - message: globalize.translate('ErrorGettingTvLineups') + Dashboard.processServerConfigurationUpdateResult(); + providerId = result.Id; + reload(); + }, function () { + Dashboard.alert({ // ApiClient.ajax() error handler + message: globalize.translate('ErrorSavingTvProvider') }); - refreshListings(''); + }); + }); + } + + function submitListingsForm() { + const selectedListingsId = $('#selectListing', page).val(); + + if (!selectedListingsId) { + return void Dashboard.alert({ + message: globalize.translate('ErrorPleaseSelectLineup') + }); + } + + loading.show(); + const id = providerId; + ApiClient.getNamedConfiguration('livetv').then(function (config) { + const info = config.ListingProviders.filter(function (i) { + return i.Id === id; + })[0]; + info.ZipCode = page.querySelector('.txtZipCode').value; + info.Country = $('#selectCountry', page).val(); + info.ListingsId = selectedListingsId; + info.EnableAllTuners = page.querySelector('.chkAllTuners').checked; + info.EnabledTuners = info.EnableAllTuners ? [] : $('.chkTuner', page).get().filter(function (i) { + return i.checked; + }).map(function (i) { + return i.getAttribute('data-id'); + }); + ApiClient.ajax({ + type: 'POST', + url: ApiClient.getUrl('LiveTv/ListingProviders', { + ValidateListings: true + }), + data: JSON.stringify(info), + contentType: 'application/json' + }).then(function (result) { loading.hide(); - }); - } - function getTunerName(providerId) { - switch (providerId = providerId.toLowerCase()) { - case 'm3u': - return 'M3U Playlist'; - case 'hdhomerun': - return 'HDHomerun'; - case 'satip': - return 'DVB'; - default: - return 'Unknown'; - } - } - - function refreshTunerDevices(page, providerInfo, devices) { - var html = ''; - - for (var i = 0, length = devices.length; i < length; i++) { - var device = devices[i]; - html += '
'; - var enabledTuners = providerInfo.EnabledTuners || []; - var isChecked = providerInfo.EnableAllTuners || -1 !== enabledTuners.indexOf(device.Id); - var checkedAttribute = isChecked ? ' checked' : ''; - html += ''; - html += '
'; - html += '
'; - html += device.FriendlyName || getTunerName(device.Type); - html += '
'; - html += '
'; - html += device.Url; - html += '
'; - html += '
'; - html += '
'; - } - - page.querySelector('.tunerList').innerHTML = html; - } - - var listingsId; - var self = this; - - self.submit = function () { - page.querySelector('.btnSubmitListingsContainer').click(); - }; - - self.init = function () { - options = options || {}; - - // Only hide the buttons if explicitly set to false; default to showing if undefined or null - // FIXME: rename this option to clarify logic - var hideCancelButton = options.showCancelButton === false; - page.querySelector('.btnCancel').classList.toggle('hide', hideCancelButton); - - var hideSubmitButton = options.showSubmitButton === false; - page.querySelector('.btnSubmitListings').classList.toggle('hide', hideSubmitButton); - - $('.formLogin', page).on('submit', function () { - submitLoginForm(); - return false; - }); - $('.formListings', page).on('submit', function () { - submitListingsForm(); - return false; - }); - $('.txtZipCode', page).on('change', function () { - refreshListings(this.value); - }); - page.querySelector('.chkAllTuners').addEventListener('change', function (e) { - if (e.target.checked) { - page.querySelector('.selectTunersSection').classList.add('hide'); - } else { - page.querySelector('.selectTunersSection').classList.remove('hide'); + if (options.showConfirmation) { + Dashboard.processServerConfigurationUpdateResult(); } + + Events.trigger(self, 'submitted'); + }, function () { + loading.hide(); + Dashboard.alert({ + message: globalize.translate('ErrorAddingListingsToSchedulesDirect') + }); }); - $('.createAccountHelp', page).html(globalize.translate('MessageCreateAccountAt', 'http://www.schedulesdirect.org')); - reload(); - }; + }); + } + + function refreshListings(value) { + if (!value) { + return void $('#selectListing', page).html(''); + } + + loading.show(); + ApiClient.ajax({ + type: 'GET', + url: ApiClient.getUrl('LiveTv/ListingProviders/Lineups', { + Id: providerId, + Location: value, + Country: $('#selectCountry', page).val() + }), + dataType: 'json' + }).then(function (result) { + $('#selectListing', page).html(result.map(function (o) { + return ''; + })); + + if (listingsId) { + $('#selectListing', page).val(listingsId); + } + + loading.hide(); + }, function (result) { + Dashboard.alert({ + message: globalize.translate('ErrorGettingTvLineups') + }); + refreshListings(''); + loading.hide(); + }); + } + + function getTunerName(providerId) { + switch (providerId = providerId.toLowerCase()) { + case 'm3u': + return 'M3U Playlist'; + case 'hdhomerun': + return 'HDHomerun'; + case 'satip': + return 'DVB'; + default: + return 'Unknown'; + } + } + + function refreshTunerDevices(page, providerInfo, devices) { + let html = ''; + + for (let i = 0, length = devices.length; i < length; i++) { + const device = devices[i]; + html += '
'; + const enabledTuners = providerInfo.EnabledTuners || []; + const isChecked = providerInfo.EnableAllTuners || enabledTuners.indexOf(device.Id) !== -1; + const checkedAttribute = isChecked ? ' checked' : ''; + html += ''; + html += '
'; + html += '
'; + html += device.FriendlyName || getTunerName(device.Type); + html += '
'; + html += '
'; + html += device.Url; + html += '
'; + html += '
'; + html += '
'; + } + + page.querySelector('.tunerList').innerHTML = html; + } + + let listingsId; + const self = this; + + self.submit = function () { + page.querySelector('.btnSubmitListingsContainer').click(); }; -}); + + self.init = function () { + options = options || {}; + + // Only hide the buttons if explicitly set to false; default to showing if undefined or null + // FIXME: rename this option to clarify logic + const hideCancelButton = options.showCancelButton === false; + page.querySelector('.btnCancel').classList.toggle('hide', hideCancelButton); + + const hideSubmitButton = options.showSubmitButton === false; + page.querySelector('.btnSubmitListings').classList.toggle('hide', hideSubmitButton); + + $('.formLogin', page).on('submit', function () { + submitLoginForm(); + return false; + }); + $('.formListings', page).on('submit', function () { + submitListingsForm(); + return false; + }); + $('.txtZipCode', page).on('change', function () { + refreshListings(this.value); + }); + page.querySelector('.chkAllTuners').addEventListener('change', function (e) { + if (e.target.checked) { + page.querySelector('.selectTunersSection').classList.add('hide'); + } else { + page.querySelector('.selectTunersSection').classList.remove('hide'); + } + }); + $('.createAccountHelp', page).html(globalize.translate('MessageCreateAccountAt', 'http://www.schedulesdirect.org')); + reload(); + }; +} diff --git a/src/components/tvproviders/schedulesdirect.template.html b/src/components/tvproviders/schedulesdirect.template.html index abe19b50f5..8783987875 100644 --- a/src/components/tvproviders/schedulesdirect.template.html +++ b/src/components/tvproviders/schedulesdirect.template.html @@ -27,7 +27,7 @@
- +
@@ -65,7 +65,7 @@

- +
diff --git a/src/components/tvproviders/xmltv.js b/src/components/tvproviders/xmltv.js index 054c5b640d..a75b29eeb3 100644 --- a/src/components/tvproviders/xmltv.js +++ b/src/components/tvproviders/xmltv.js @@ -1,189 +1,193 @@ -define(['jQuery', 'loading', 'globalize', 'emby-checkbox', 'emby-input', 'listViewStyle', 'paper-icon-button-light'], function ($, loading, globalize) { - 'use strict'; +import $ from 'jQuery'; +import loading from 'loading'; +import globalize from 'globalize'; +import 'emby-checkbox'; +import 'emby-input'; +import 'listViewStyle'; +import 'paper-icon-button-light'; - return function (page, providerId, options) { - function getListingProvider(config, id) { - if (config && id) { - var result = config.ListingProviders.filter(function (provider) { - return provider.Id === id; - })[0]; +export default function (page, providerId, options) { + function getListingProvider(config, id) { + if (config && id) { + const result = config.ListingProviders.filter(function (provider) { + return provider.Id === id; + })[0]; - if (result) { - return Promise.resolve(result); - } - - return getListingProvider(); + if (result) { + return Promise.resolve(result); } - return ApiClient.getJSON(ApiClient.getUrl('LiveTv/ListingProviders/Default')); + return getListingProvider(); } - function reload() { - loading.show(); - ApiClient.getNamedConfiguration('livetv').then(function (config) { - getListingProvider(config, providerId).then(function (info) { - page.querySelector('.txtPath').value = info.Path || ''; - page.querySelector('.txtKids').value = (info.KidsCategories || []).join('|'); - page.querySelector('.txtNews').value = (info.NewsCategories || []).join('|'); - page.querySelector('.txtSports').value = (info.SportsCategories || []).join('|'); - page.querySelector('.txtMovies').value = (info.MovieCategories || []).join('|'); - page.querySelector('.txtMoviePrefix').value = info.MoviePrefix || ''; - page.querySelector('.txtUserAgent').value = info.UserAgent || ''; - page.querySelector('.chkAllTuners').checked = info.EnableAllTuners; + return ApiClient.getJSON(ApiClient.getUrl('LiveTv/ListingProviders/Default')); + } - if (page.querySelector('.chkAllTuners').checked) { - page.querySelector('.selectTunersSection').classList.add('hide'); - } else { - page.querySelector('.selectTunersSection').classList.remove('hide'); - } + function reload() { + loading.show(); + ApiClient.getNamedConfiguration('livetv').then(function (config) { + getListingProvider(config, providerId).then(function (info) { + page.querySelector('.txtPath').value = info.Path || ''; + page.querySelector('.txtKids').value = (info.KidsCategories || []).join('|'); + page.querySelector('.txtNews').value = (info.NewsCategories || []).join('|'); + page.querySelector('.txtSports').value = (info.SportsCategories || []).join('|'); + page.querySelector('.txtMovies').value = (info.MovieCategories || []).join('|'); + page.querySelector('.txtMoviePrefix').value = info.MoviePrefix || ''; + page.querySelector('.txtUserAgent').value = info.UserAgent || ''; + page.querySelector('.chkAllTuners').checked = info.EnableAllTuners; - refreshTunerDevices(page, info, config.TunerHosts); - loading.hide(); - }); - }); - } - - function getCategories(txtInput) { - var value = txtInput.value; - - if (value) { - return value.split('|'); - } - - return []; - } - - function submitListingsForm() { - loading.show(); - var id = providerId; - ApiClient.getNamedConfiguration('livetv').then(function (config) { - var info = config.ListingProviders.filter(function (provider) { - return provider.Id === id; - })[0] || {}; - info.Type = 'xmltv'; - info.Path = page.querySelector('.txtPath').value; - info.MoviePrefix = page.querySelector('.txtMoviePrefix').value || null; - info.UserAgent = page.querySelector('.txtUserAgent').value || null; - info.MovieCategories = getCategories(page.querySelector('.txtMovies')); - info.KidsCategories = getCategories(page.querySelector('.txtKids')); - info.NewsCategories = getCategories(page.querySelector('.txtNews')); - info.SportsCategories = getCategories(page.querySelector('.txtSports')); - info.EnableAllTuners = page.querySelector('.chkAllTuners').checked; - info.EnabledTuners = info.EnableAllTuners ? [] : $('.chkTuner', page).get().filter(function (tuner) { - return tuner.checked; - }).map(function (tuner) { - return tuner.getAttribute('data-id'); - }); - ApiClient.ajax({ - type: 'POST', - url: ApiClient.getUrl('LiveTv/ListingProviders', { - ValidateListings: true - }), - data: JSON.stringify(info), - contentType: 'application/json' - }).then(function (result) { - loading.hide(); - - if (false !== options.showConfirmation) { - Dashboard.processServerConfigurationUpdateResult(); - } - - Events.trigger(self, 'submitted'); - }, function () { - loading.hide(); - Dashboard.alert({ - message: globalize.translate('ErrorAddingXmlTvFile') - }); - }); - }); - } - - function getTunerName(providerId) { - switch (providerId = providerId.toLowerCase()) { - case 'm3u': - return 'M3U Playlist'; - case 'hdhomerun': - return 'HDHomerun'; - case 'satip': - return 'DVB'; - default: - return 'Unknown'; - } - } - - function refreshTunerDevices(page, providerInfo, devices) { - var html = ''; - - for (var i = 0, length = devices.length; i < length; i++) { - var device = devices[i]; - html += '
'; - var enabledTuners = providerInfo.EnabledTuners || []; - var isChecked = providerInfo.EnableAllTuners || -1 !== enabledTuners.indexOf(device.Id); - var checkedAttribute = isChecked ? ' checked' : ''; - html += ''; - html += '
'; - html += '
'; - html += device.FriendlyName || getTunerName(device.Type); - html += '
'; - html += '
'; - html += device.Url; - html += '
'; - html += '
'; - html += '
'; - } - - page.querySelector('.tunerList').innerHTML = html; - } - - function onSelectPathClick(e) { - var page = $(e.target).parents('.xmltvForm')[0]; - - require(['directorybrowser'], function (directoryBrowser) { - var picker = new directoryBrowser.default(); - picker.show({ - includeFiles: true, - callback: function (path) { - if (path) { - var txtPath = page.querySelector('.txtPath'); - txtPath.value = path; - txtPath.focus(); - } - picker.close(); - } - }); - }); - } - - var self = this; - - self.submit = function () { - page.querySelector('.btnSubmitListings').click(); - }; - - self.init = function () { - options = options || {}; - - // Only hide the buttons if explicitly set to false; default to showing if undefined or null - // FIXME: rename this option to clarify logic - var hideCancelButton = options.showCancelButton === false; - page.querySelector('.btnCancel').classList.toggle('hide', hideCancelButton); - - var hideSubmitButton = options.showSubmitButton === false; - page.querySelector('.btnSubmitListings').classList.toggle('hide', hideSubmitButton); - - $('form', page).on('submit', function () { - submitListingsForm(); - return false; - }); - page.querySelector('#btnSelectPath').addEventListener('click', onSelectPathClick); - page.querySelector('.chkAllTuners').addEventListener('change', function (evt) { - if (evt.target.checked) { + if (page.querySelector('.chkAllTuners').checked) { page.querySelector('.selectTunersSection').classList.add('hide'); } else { page.querySelector('.selectTunersSection').classList.remove('hide'); } + + refreshTunerDevices(page, info, config.TunerHosts); + loading.hide(); }); - reload(); - }; + }); + } + + function getCategories(txtInput) { + const value = txtInput.value; + + if (value) { + return value.split('|'); + } + + return []; + } + + function submitListingsForm() { + loading.show(); + const id = providerId; + ApiClient.getNamedConfiguration('livetv').then(function (config) { + const info = config.ListingProviders.filter(function (provider) { + return provider.Id === id; + })[0] || {}; + info.Type = 'xmltv'; + info.Path = page.querySelector('.txtPath').value; + info.MoviePrefix = page.querySelector('.txtMoviePrefix').value || null; + info.UserAgent = page.querySelector('.txtUserAgent').value || null; + info.MovieCategories = getCategories(page.querySelector('.txtMovies')); + info.KidsCategories = getCategories(page.querySelector('.txtKids')); + info.NewsCategories = getCategories(page.querySelector('.txtNews')); + info.SportsCategories = getCategories(page.querySelector('.txtSports')); + info.EnableAllTuners = page.querySelector('.chkAllTuners').checked; + info.EnabledTuners = info.EnableAllTuners ? [] : $('.chkTuner', page).get().filter(function (tuner) { + return tuner.checked; + }).map(function (tuner) { + return tuner.getAttribute('data-id'); + }); + ApiClient.ajax({ + type: 'POST', + url: ApiClient.getUrl('LiveTv/ListingProviders', { + ValidateListings: true + }), + data: JSON.stringify(info), + contentType: 'application/json' + }).then(function (result) { + loading.hide(); + + if (options.showConfirmation !== false) { + Dashboard.processServerConfigurationUpdateResult(); + } + + Events.trigger(self, 'submitted'); + }, function () { + loading.hide(); + Dashboard.alert({ + message: globalize.translate('ErrorAddingXmlTvFile') + }); + }); + }); + } + + function getTunerName(providerId) { + switch (providerId = providerId.toLowerCase()) { + case 'm3u': + return 'M3U Playlist'; + case 'hdhomerun': + return 'HDHomerun'; + case 'satip': + return 'DVB'; + default: + return 'Unknown'; + } + } + + function refreshTunerDevices(page, providerInfo, devices) { + let html = ''; + + for (let i = 0, length = devices.length; i < length; i++) { + const device = devices[i]; + html += '
'; + const enabledTuners = providerInfo.EnabledTuners || []; + const isChecked = providerInfo.EnableAllTuners || enabledTuners.indexOf(device.Id) !== -1; + const checkedAttribute = isChecked ? ' checked' : ''; + html += ''; + html += '
'; + html += '
'; + html += device.FriendlyName || getTunerName(device.Type); + html += '
'; + html += '
'; + html += device.Url; + html += '
'; + html += '
'; + html += '
'; + } + + page.querySelector('.tunerList').innerHTML = html; + } + + function onSelectPathClick(e) { + const page = $(e.target).parents('.xmltvForm')[0]; + + import('directorybrowser').then(({default: directoryBrowser}) => { + const picker = new directoryBrowser(); + picker.show({ + includeFiles: true, + callback: function (path) { + if (path) { + const txtPath = page.querySelector('.txtPath'); + txtPath.value = path; + txtPath.focus(); + } + picker.close(); + } + }); + }); + } + + const self = this; + + self.submit = function () { + page.querySelector('.btnSubmitListings').click(); }; -}); + + self.init = function () { + options = options || {}; + + // Only hide the buttons if explicitly set to false; default to showing if undefined or null + // FIXME: rename this option to clarify logic + const hideCancelButton = options.showCancelButton === false; + page.querySelector('.btnCancel').classList.toggle('hide', hideCancelButton); + + const hideSubmitButton = options.showSubmitButton === false; + page.querySelector('.btnSubmitListings').classList.toggle('hide', hideSubmitButton); + + $('form', page).on('submit', function () { + submitListingsForm(); + return false; + }); + page.querySelector('#btnSelectPath').addEventListener('click', onSelectPathClick); + page.querySelector('.chkAllTuners').addEventListener('change', function (evt) { + if (evt.target.checked) { + page.querySelector('.selectTunersSection').classList.add('hide'); + } else { + page.querySelector('.selectTunersSection').classList.remove('hide'); + } + }); + reload(); + }; +} diff --git a/src/components/tvproviders/xmltv.template.html b/src/components/tvproviders/xmltv.template.html index 72c29904b3..010e66ea0d 100644 --- a/src/components/tvproviders/xmltv.template.html +++ b/src/components/tvproviders/xmltv.template.html @@ -56,7 +56,7 @@
- +
diff --git a/src/components/upnextdialog/upnextdialog.js b/src/components/upnextdialog/upnextdialog.js index 3e9c9f9c5e..985e4fb02f 100644 --- a/src/components/upnextdialog/upnextdialog.js +++ b/src/components/upnextdialog/upnextdialog.js @@ -1,10 +1,20 @@ -define(['dom', 'playbackManager', 'connectionManager', 'events', 'mediaInfo', 'layoutManager', 'focusManager', 'globalize', 'itemHelper', 'css!./upnextdialog', 'emby-button', 'flexStyles'], function (dom, playbackManager, connectionManager, events, mediaInfo, layoutManager, focusManager, globalize, itemHelper) { - 'use strict'; +import dom from 'dom'; +import playbackManager from 'playbackManager'; +import events from 'events'; +import mediaInfo from 'mediaInfo'; +import layoutManager from 'layoutManager'; +import focusManager from 'focusManager'; +import globalize from 'globalize'; +import itemHelper from 'itemHelper'; +import 'css!./upnextdialog'; +import 'emby-button'; +import 'flexStyles'; - var transitionEndEventName = dom.whichTransitionEvent(); +/* eslint-disable indent */ + + const transitionEndEventName = dom.whichTransitionEvent(); function seriesImageUrl(item, options) { - if (item.Type !== 'Episode') { return null; } @@ -13,28 +23,23 @@ define(['dom', 'playbackManager', 'connectionManager', 'events', 'mediaInfo', 'l options.type = options.type || 'Primary'; if (options.type === 'Primary') { - if (item.SeriesPrimaryImageTag) { - options.tag = item.SeriesPrimaryImageTag; - return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); + return window.connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); } } if (options.type === 'Thumb') { - if (item.SeriesThumbImageTag) { - options.tag = item.SeriesThumbImageTag; - return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); + return window.connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); } if (item.ParentThumbImageTag) { - options.tag = item.ParentThumbImageTag; - return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.ParentThumbItemId, options); + return window.connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.ParentThumbItemId, options); } } @@ -42,21 +47,18 @@ define(['dom', 'playbackManager', 'connectionManager', 'events', 'mediaInfo', 'l } function imageUrl(item, options) { - options = options || {}; options.type = options.type || 'Primary'; if (item.ImageTags && item.ImageTags[options.type]) { - options.tag = item.ImageTags[options.type]; - return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.PrimaryImageItemId || item.Id, options); + return window.connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.PrimaryImageItemId || item.Id, options); } if (options.type === 'Primary') { if (item.AlbumId && item.AlbumPrimaryImageTag) { - options.tag = item.AlbumPrimaryImageTag; - return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.AlbumId, options); + return window.connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.AlbumId, options); } } @@ -64,10 +66,8 @@ define(['dom', 'playbackManager', 'connectionManager', 'events', 'mediaInfo', 'l } function setPoster(osdPoster, item, secondaryItem) { - if (item) { - - var imgUrl = seriesImageUrl(item, { type: 'Primary' }) || + let imgUrl = seriesImageUrl(item, { type: 'Primary' }) || seriesImageUrl(item, { type: 'Thumb' }) || imageUrl(item, { type: 'Primary' }); @@ -87,8 +87,7 @@ define(['dom', 'playbackManager', 'connectionManager', 'events', 'mediaInfo', 'l } function getHtml() { - - var html = ''; + let html = ''; html += '
'; html += '
'; @@ -124,18 +123,17 @@ define(['dom', 'playbackManager', 'connectionManager', 'events', 'mediaInfo', 'l } function setNextVideoText() { + const instance = this; - var instance = this; + const elem = instance.options.parent; - var elem = instance.options.parent; - - var secondsRemaining = Math.max(Math.round(getTimeRemainingMs(instance) / 1000), 0); + const secondsRemaining = Math.max(Math.round(getTimeRemainingMs(instance) / 1000), 0); console.debug('up next seconds remaining: ' + secondsRemaining); - var timeText = '' + globalize.translate('HeaderSecondsValue', secondsRemaining) + ''; + const timeText = '' + globalize.translate('HeaderSecondsValue', secondsRemaining) + ''; - var nextVideoText = instance.itemType === 'Episode' ? + const nextVideoText = instance.itemType === 'Episode' ? globalize.translate('HeaderNextEpisodePlayingInValue', timeText) : globalize.translate('HeaderNextVideoPlayingInValue', timeText); @@ -143,10 +141,9 @@ define(['dom', 'playbackManager', 'connectionManager', 'events', 'mediaInfo', 'l } function fillItem(item) { + const instance = this; - var instance = this; - - var elem = instance.options.parent; + const elem = instance.options.parent; setPoster(elem.querySelector('.upNextDialog-poster'), item); @@ -155,7 +152,7 @@ define(['dom', 'playbackManager', 'connectionManager', 'events', 'mediaInfo', 'l elem.querySelector('.upNextDialog-mediainfo').innerHTML = mediaInfo.getPrimaryMediaInfoHtml(item, { }); - var title = itemHelper.getDisplayName(item); + let title = itemHelper.getDisplayName(item); if (item.SeriesName) { title = item.SeriesName + ' - ' + title; } @@ -175,12 +172,10 @@ define(['dom', 'playbackManager', 'connectionManager', 'events', 'mediaInfo', 'l } function onStartNowClick() { - - var options = this.options; + const options = this.options; if (options) { - - var player = options.player; + const player = options.player; this.hide(); @@ -189,7 +184,6 @@ define(['dom', 'playbackManager', 'connectionManager', 'events', 'mediaInfo', 'l } function init(instance, options) { - options.parent.innerHTML = getHtml(); options.parent.classList.add('hide'); @@ -203,8 +197,7 @@ define(['dom', 'playbackManager', 'connectionManager', 'events', 'mediaInfo', 'l } function clearHideAnimationEventListeners(instance, elem) { - - var fn = instance._onHideAnimationComplete; + const fn = instance._onHideAnimationComplete; if (fn) { dom.removeEventListener(elem, transitionEndEventName, fn, { @@ -214,9 +207,8 @@ define(['dom', 'playbackManager', 'connectionManager', 'events', 'mediaInfo', 'l } function onHideAnimationComplete(e) { - - var instance = this; - var elem = e.target; + const instance = this; + const elem = e.target; elem.classList.add('hide'); @@ -225,15 +217,14 @@ define(['dom', 'playbackManager', 'connectionManager', 'events', 'mediaInfo', 'l } function hideComingUpNext() { - - var instance = this; + const instance = this; clearCountdownTextTimeout(this); if (!instance.options) { return; } - var elem = instance.options.parent; + const elem = instance.options.parent; if (!elem) { return; @@ -250,7 +241,7 @@ define(['dom', 'playbackManager', 'connectionManager', 'events', 'mediaInfo', 'l elem.classList.add('upNextDialog-hidden'); - var fn = onHideAnimationComplete.bind(instance); + const fn = onHideAnimationComplete.bind(instance); instance._onHideAnimationComplete = fn; dom.addEventListener(elem, transitionEndEventName, fn, { @@ -259,14 +250,12 @@ define(['dom', 'playbackManager', 'connectionManager', 'events', 'mediaInfo', 'l } function getTimeRemainingMs(instance) { - - var options = instance.options; + const options = instance.options; if (options) { - - var runtimeTicks = playbackManager.duration(options.player); + const runtimeTicks = playbackManager.duration(options.player); if (runtimeTicks) { - var timeRemainingTicks = runtimeTicks - playbackManager.currentTime(options.player); + const timeRemainingTicks = runtimeTicks - playbackManager.currentTime(options.player) * 10000; return Math.round(timeRemainingTicks / 10000); } @@ -276,8 +265,7 @@ define(['dom', 'playbackManager', 'connectionManager', 'events', 'mediaInfo', 'l } function startComingUpNextHideTimer(instance) { - - var timeRemainingMs = getTimeRemainingMs(instance); + const timeRemainingMs = getTimeRemainingMs(instance); if (timeRemainingMs <= 0) { return; @@ -289,16 +277,14 @@ define(['dom', 'playbackManager', 'connectionManager', 'events', 'mediaInfo', 'l instance._countdownTextTimeout = setInterval(setNextVideoText.bind(instance), 400); } - function UpNextDialog(options) { - +class UpNextDialog { + constructor(options) { this.options = options; init(this, options); } - - UpNextDialog.prototype.show = function () { - - var elem = this.options.parent; + show() { + const elem = this.options.parent; clearHideAnimationEventListeners(this, elem); @@ -316,20 +302,18 @@ define(['dom', 'playbackManager', 'connectionManager', 'events', 'mediaInfo', 'l } startComingUpNextHideTimer(this); - }; - - UpNextDialog.prototype.hide = function () { - + } + hide() { hideComingUpNext.call(this); - }; - - UpNextDialog.prototype.destroy = function () { - + } + destroy() { hideComingUpNext.call(this); this.options = null; this.itemType = null; - }; + } +} - return UpNextDialog; -}); +export default UpNextDialog; + +/* eslint-enable indent */ diff --git a/src/components/userdatabuttons/userdatabuttons.js b/src/components/userdatabuttons/userdatabuttons.js index 86b9902133..6c0cbb8a29 100644 --- a/src/components/userdatabuttons/userdatabuttons.js +++ b/src/components/userdatabuttons/userdatabuttons.js @@ -1,251 +1,218 @@ -define(['connectionManager', 'globalize', 'dom', 'itemHelper', 'paper-icon-button-light', 'material-icons', 'emby-button', 'css!./userdatabuttons'], function (connectionManager, globalize, dom, itemHelper) { - 'use strict'; +import globalize from 'globalize'; +import dom from 'dom'; +import itemHelper from 'itemHelper'; +import 'paper-icon-button-light'; +import 'material-icons'; +import 'emby-button'; +import 'css!./userdatabuttons'; - var userDataMethods = { - markPlayed: markPlayed, - markDislike: markDislike, - markLike: markLike, - markFavorite: markFavorite - }; +const userDataMethods = { + markPlayed: markPlayed, + markDislike: markDislike, + markLike: markLike, + markFavorite: markFavorite +}; - function getUserDataButtonHtml(method, itemId, serverId, buttonCssClass, iconCssClass, icon, tooltip, style) { - - if (style === 'fab-mini') { - style = 'fab'; - buttonCssClass = buttonCssClass ? (buttonCssClass + ' mini') : 'mini'; - } - - var is = style === 'fab' ? 'emby-button' : 'paper-icon-button-light'; - var className = style === 'fab' ? 'autoSize fab' : 'autoSize'; - - if (buttonCssClass) { - className += ' ' + buttonCssClass; - } - - if (iconCssClass) { - iconCssClass += ' '; - } else { - iconCssClass = ''; - } - - iconCssClass += 'material-icons'; - - return ''; +function getUserDataButtonHtml(method, itemId, serverId, buttonCssClass, iconCssClass, icon, tooltip, style) { + if (style === 'fab-mini') { + style = 'fab'; + buttonCssClass = buttonCssClass ? (buttonCssClass + ' mini') : 'mini'; } - function onContainerClick(e) { + const is = style === 'fab' ? 'emby-button' : 'paper-icon-button-light'; + let className = style === 'fab' ? 'autoSize fab' : 'autoSize'; - var btnUserData = dom.parentWithClass(e.target, 'btnUserData'); - - if (!btnUserData) { - return; - } - - var method = btnUserData.getAttribute('data-method'); - userDataMethods[method](btnUserData); + if (buttonCssClass) { + className += ' ' + buttonCssClass; } - function fill(options) { - - var html = getIconsHtml(options); - - if (options.fillMode === 'insertAdjacent') { - options.element.insertAdjacentHTML(options.insertLocation || 'beforeend', html); - } else { - options.element.innerHTML = html; - } - - dom.removeEventListener(options.element, 'click', onContainerClick, { - passive: true - }); - - dom.addEventListener(options.element, 'click', onContainerClick, { - passive: true - }); + if (iconCssClass) { + iconCssClass += ' '; + } else { + iconCssClass = ''; } - function destroy(options) { + iconCssClass += 'material-icons'; - options.element.innerHTML = ''; + return ''; +} - dom.removeEventListener(options.element, 'click', onContainerClick, { - passive: true - }); +function onContainerClick(e) { + const btnUserData = dom.parentWithClass(e.target, 'btnUserData'); + + if (!btnUserData) { + return; } - function getIconsHtml(options) { + const method = btnUserData.getAttribute('data-method'); + userDataMethods[method](btnUserData); +} - var item = options.item; - var includePlayed = options.includePlayed; - var cssClass = options.cssClass; - var style = options.style; +function fill(options) { + const html = getIconsHtml(options); - var html = ''; + if (options.fillMode === 'insertAdjacent') { + options.element.insertAdjacentHTML(options.insertLocation || 'beforeend', html); + } else { + options.element.innerHTML = html; + } - var userData = item.UserData || {}; + dom.removeEventListener(options.element, 'click', onContainerClick, { + passive: true + }); - var itemId = item.Id; + dom.addEventListener(options.element, 'click', onContainerClick, { + passive: true + }); +} - if (itemHelper.isLocalItem(item)) { - return html; - } +function destroy(options) { + options.element.innerHTML = ''; - var btnCssClass = 'btnUserData'; + dom.removeEventListener(options.element, 'click', onContainerClick, { + passive: true + }); +} - if (cssClass) { - btnCssClass += ' ' + cssClass; - } +function getIconsHtml(options) { + const item = options.item; + const includePlayed = options.includePlayed; + const cssClass = options.cssClass; + const style = options.style; - var iconCssClass = options.iconCssClass; + let html = ''; - var serverId = item.ServerId; + const userData = item.UserData || {}; - if (includePlayed !== false) { - var tooltipPlayed = globalize.translate('MarkPlayed'); - - if (itemHelper.canMarkPlayed(item)) { - if (userData.Played) { - html += getUserDataButtonHtml('markPlayed', itemId, serverId, btnCssClass + ' btnUserDataOn', iconCssClass, 'check', tooltipPlayed, style); - } else { - html += getUserDataButtonHtml('markPlayed', itemId, serverId, btnCssClass, iconCssClass, 'check', tooltipPlayed, style); - } - } - } - - //var tooltipLike = globalize.translate('Like'); - //var tooltipDislike = globalize.translate('Dislike'); - - //if (typeof userData.Likes == "undefined") { - // html += getUserDataButtonHtml('markDislike', itemId, serverId, btnCssClass + ' btnUserData btnDislike', 'thumb-down', tooltipDislike); - // html += getUserDataButtonHtml('markLike', itemId, serverId, btnCssClass + ' btnUserData btnLike', 'thumb-up', tooltipLike); - //} - //else if (userData.Likes) { - // html += getUserDataButtonHtml('markDislike', itemId, serverId, btnCssClass + ' btnUserData btnDislike', 'thumb-down', tooltipDislike); - // html += getUserDataButtonHtml('markLike', itemId, serverId, btnCssClass + ' btnUserData btnLike btnUserDataOn', 'thumb-up', tooltipLike); - //} - //else { - // html += getUserDataButtonHtml('markDislike', itemId, serverId, btnCssClass + ' btnUserData btnDislike btnUserDataOn', 'thumb-down', tooltipDislike); - // html += getUserDataButtonHtml('markLike', itemId, serverId, btnCssClass + ' btnUserData btnLike', 'thumb-up', tooltipLike); - //} - - var tooltipFavorite = globalize.translate('Favorite'); - if (userData.IsFavorite) { - - html += getUserDataButtonHtml('markFavorite', itemId, serverId, btnCssClass + ' btnUserData btnUserDataOn', iconCssClass, 'favorite', tooltipFavorite, style); - } else { - html += getUserDataButtonHtml('markFavorite', itemId, serverId, btnCssClass + ' btnUserData', iconCssClass, 'favorite', tooltipFavorite, style); - } + const itemId = item.Id; + if (itemHelper.isLocalItem(item)) { return html; } - function markFavorite(link) { + let btnCssClass = 'btnUserData'; - var id = link.getAttribute('data-itemid'); - var serverId = link.getAttribute('data-serverid'); + if (cssClass) { + btnCssClass += ' ' + cssClass; + } - var markAsFavorite = !link.classList.contains('btnUserDataOn'); + const iconCssClass = options.iconCssClass; - favorite(id, serverId, markAsFavorite); + const serverId = item.ServerId; - if (markAsFavorite) { - link.classList.add('btnUserDataOn'); - } else { - link.classList.remove('btnUserDataOn'); + if (includePlayed !== false) { + const tooltipPlayed = globalize.translate('MarkPlayed'); + + if (itemHelper.canMarkPlayed(item)) { + if (userData.Played) { + html += getUserDataButtonHtml('markPlayed', itemId, serverId, btnCssClass + ' btnUserDataOn', iconCssClass, 'check', tooltipPlayed, style); + } else { + html += getUserDataButtonHtml('markPlayed', itemId, serverId, btnCssClass, iconCssClass, 'check', tooltipPlayed, style); + } } } - function markLike(link) { - - var id = link.getAttribute('data-itemid'); - var serverId = link.getAttribute('data-serverid'); - - if (!link.classList.contains('btnUserDataOn')) { - - likes(id, serverId, true); - - link.classList.add('btnUserDataOn'); - - } else { - - clearLike(id, serverId); - - link.classList.remove('btnUserDataOn'); - } - - link.parentNode.querySelector('.btnDislike').classList.remove('btnUserDataOn'); + const tooltipFavorite = globalize.translate('Favorite'); + if (userData.IsFavorite) { + html += getUserDataButtonHtml('markFavorite', itemId, serverId, btnCssClass + ' btnUserData btnUserDataOn', iconCssClass, 'favorite', tooltipFavorite, style); + } else { + html += getUserDataButtonHtml('markFavorite', itemId, serverId, btnCssClass + ' btnUserData', iconCssClass, 'favorite', tooltipFavorite, style); } - function markDislike(link) { + return html; +} - var id = link.getAttribute('data-itemid'); - var serverId = link.getAttribute('data-serverid'); +function markFavorite(link) { + const id = link.getAttribute('data-itemid'); + const serverId = link.getAttribute('data-serverid'); - if (!link.classList.contains('btnUserDataOn')) { + const markAsFavorite = !link.classList.contains('btnUserDataOn'); - likes(id, serverId, false); + favorite(id, serverId, markAsFavorite); - link.classList.add('btnUserDataOn'); + if (markAsFavorite) { + link.classList.add('btnUserDataOn'); + } else { + link.classList.remove('btnUserDataOn'); + } +} - } else { +function markLike(link) { + const id = link.getAttribute('data-itemid'); + const serverId = link.getAttribute('data-serverid'); - clearLike(id, serverId); + if (!link.classList.contains('btnUserDataOn')) { + likes(id, serverId, true); - link.classList.remove('btnUserDataOn'); - } + link.classList.add('btnUserDataOn'); + } else { + clearLike(id, serverId); - link.parentNode.querySelector('.btnLike').classList.remove('btnUserDataOn'); + link.classList.remove('btnUserDataOn'); } - function markPlayed(link) { + link.parentNode.querySelector('.btnDislike').classList.remove('btnUserDataOn'); +} - var id = link.getAttribute('data-itemid'); - var serverId = link.getAttribute('data-serverid'); +function markDislike(link) { + const id = link.getAttribute('data-itemid'); + const serverId = link.getAttribute('data-serverid'); - if (!link.classList.contains('btnUserDataOn')) { + if (!link.classList.contains('btnUserDataOn')) { + likes(id, serverId, false); - played(id, serverId, true); + link.classList.add('btnUserDataOn'); + } else { + clearLike(id, serverId); - link.classList.add('btnUserDataOn'); - - } else { - - played(id, serverId, false); - - link.classList.remove('btnUserDataOn'); - } + link.classList.remove('btnUserDataOn'); } - function likes(id, serverId, isLiked) { - var apiClient = connectionManager.getApiClient(serverId); - return apiClient.updateUserItemRating(apiClient.getCurrentUserId(), id, isLiked); + link.parentNode.querySelector('.btnLike').classList.remove('btnUserDataOn'); +} + +function markPlayed(link) { + const id = link.getAttribute('data-itemid'); + const serverId = link.getAttribute('data-serverid'); + + if (!link.classList.contains('btnUserDataOn')) { + played(id, serverId, true); + + link.classList.add('btnUserDataOn'); + } else { + played(id, serverId, false); + + link.classList.remove('btnUserDataOn'); } +} - function played(id, serverId, isPlayed) { - var apiClient = connectionManager.getApiClient(serverId); +function likes(id, serverId, isLiked) { + const apiClient = window.connectionManager.getApiClient(serverId); + return apiClient.updateUserItemRating(apiClient.getCurrentUserId(), id, isLiked); +} - var method = isPlayed ? 'markPlayed' : 'markUnplayed'; +function played(id, serverId, isPlayed) { + const apiClient = window.connectionManager.getApiClient(serverId); - return apiClient[method](apiClient.getCurrentUserId(), id, new Date()); - } + const method = isPlayed ? 'markPlayed' : 'markUnplayed'; - function favorite(id, serverId, isFavorite) { - var apiClient = connectionManager.getApiClient(serverId); + return apiClient[method](apiClient.getCurrentUserId(), id, new Date()); +} - return apiClient.updateFavoriteStatus(apiClient.getCurrentUserId(), id, isFavorite); - } +function favorite(id, serverId, isFavorite) { + const apiClient = window.connectionManager.getApiClient(serverId); - function clearLike(id, serverId) { + return apiClient.updateFavoriteStatus(apiClient.getCurrentUserId(), id, isFavorite); +} - var apiClient = connectionManager.getApiClient(serverId); +function clearLike(id, serverId) { + const apiClient = window.connectionManager.getApiClient(serverId); - return apiClient.clearUserItemRating(apiClient.getCurrentUserId(), id); - } + return apiClient.clearUserItemRating(apiClient.getCurrentUserId(), id); +} - return { - fill: fill, - destroy: destroy, - getIconsHtml: getIconsHtml - }; - -}); +export default { + fill: fill, + destroy: destroy, + getIconsHtml: getIconsHtml +}; diff --git a/src/components/viewContainer.js b/src/components/viewContainer.js index dbc493bc91..c39ad2ba84 100644 --- a/src/components/viewContainer.js +++ b/src/components/viewContainer.js @@ -1,20 +1,21 @@ -define(['browser', 'dom', 'layoutManager', 'css!components/viewManager/viewContainer'], function (browser, dom, layoutManager) { - 'use strict'; +import 'css!components/viewManager/viewContainer'; +/* eslint-disable indent */ function setControllerClass(view, options) { if (options.controllerFactory) { return Promise.resolve(); } - var controllerUrl = view.getAttribute('data-controller'); + let controllerUrl = view.getAttribute('data-controller'); if (controllerUrl) { - if (0 === controllerUrl.indexOf('__plugin/')) { + if (controllerUrl.indexOf('__plugin/') === 0) { controllerUrl = controllerUrl.substring('__plugin/'.length); } - controllerUrl = Dashboard.getConfigurationResourceUrl(controllerUrl); - return getRequirePromise([controllerUrl]).then(function (ControllerFactory) { + controllerUrl = Dashboard.getPluginUrl(controllerUrl); + let apiUrl = ApiClient.getUrl('/web/' + controllerUrl); + return import(apiUrl).then((ControllerFactory) => { options.controllerFactory = ControllerFactory; }); } @@ -22,94 +23,85 @@ define(['browser', 'dom', 'layoutManager', 'css!components/viewManager/viewConta return Promise.resolve(); } - function getRequirePromise(deps) { - return new Promise(function (resolve, reject) { - require(deps, resolve); - }); - } - - function loadView(options) { + export function loadView(options) { if (!options.cancel) { - var selected = selectedPageIndex; - var previousAnimatable = -1 === selected ? null : allPages[selected]; - var pageIndex = selected + 1; + const selected = selectedPageIndex; + const previousAnimatable = selected === -1 ? null : allPages[selected]; + let pageIndex = selected + 1; if (pageIndex >= pageContainerCount) { pageIndex = 0; } - var isPluginpage = -1 !== options.url.toLowerCase().indexOf('/configurationpage'); - var newViewInfo = normalizeNewView(options, isPluginpage); - var newView = newViewInfo.elem; - var modulesToLoad = []; + const isPluginpage = options.url.includes('configurationpage'); + const newViewInfo = normalizeNewView(options, isPluginpage); + const newView = newViewInfo.elem; - return new Promise(function (resolve) { - require(modulesToLoad, function () { - var currentPage = allPages[pageIndex]; + return new Promise((resolve) => { + const currentPage = allPages[pageIndex]; - if (currentPage) { - triggerDestroy(currentPage); - } + if (currentPage) { + triggerDestroy(currentPage); + } - var view = newView; + let view = newView; - if ('string' == typeof view) { - view = document.createElement('div'); - view.innerHTML = newView; - } + if (typeof view == 'string') { + view = document.createElement('div'); + view.innerHTML = newView; + } - view.classList.add('mainAnimatedPage'); + view.classList.add('mainAnimatedPage'); - if (currentPage) { - if (newViewInfo.hasScript && window.$) { - view = $(view).appendTo(mainAnimatedPages)[0]; - mainAnimatedPages.removeChild(currentPage); - } else { - mainAnimatedPages.replaceChild(view, currentPage); - } + if (currentPage) { + if (newViewInfo.hasScript && window.$) { + mainAnimatedPages.removeChild(currentPage); + view = $(view).appendTo(mainAnimatedPages)[0]; } else { - if (newViewInfo.hasScript && window.$) { - view = $(view).appendTo(mainAnimatedPages)[0]; - } else { - mainAnimatedPages.appendChild(view); - } + mainAnimatedPages.replaceChild(view, currentPage); + } + } else { + if (newViewInfo.hasScript && window.$) { + view = $(view).appendTo(mainAnimatedPages)[0]; + } else { + mainAnimatedPages.appendChild(view); + } + } + + if (options.type) { + view.setAttribute('data-type', options.type); + } + + const properties = []; + + if (options.fullscreen) { + properties.push('fullscreen'); + } + + if (properties.length) { + view.setAttribute('data-properties', properties.join(',')); + } + + allPages[pageIndex] = view; + setControllerClass(view, options).then(() => { + if (onBeforeChange) { + onBeforeChange(view, false, options); } - if (options.type) { - view.setAttribute('data-type', options.type); + beforeAnimate(allPages, pageIndex, selected); + selectedPageIndex = pageIndex; + currentUrls[pageIndex] = options.url; + + if (!options.cancel && previousAnimatable) { + afterAnimate(allPages, pageIndex); } - var properties = []; - - if (options.fullscreen) { - properties.push('fullscreen'); + if (window.$) { + $.mobile = $.mobile || {}; + $.mobile.activePage = view; } - if (properties.length) { - view.setAttribute('data-properties', properties.join(',')); - } - - allPages[pageIndex] = view; - setControllerClass(view, options).then(function () { - if (onBeforeChange) { - onBeforeChange(view, false, options); - } - - beforeAnimate(allPages, pageIndex, selected); - selectedPageIndex = pageIndex; - currentUrls[pageIndex] = options.url; - - if (!options.cancel && previousAnimatable) { - afterAnimate(allPages, pageIndex); - } - - if (window.$) { - $.mobile = $.mobile || {}; - $.mobile.activePage = view; - } - - resolve(view); - }); + resolve(view); }); }); } @@ -125,33 +117,33 @@ define(['browser', 'dom', 'layoutManager', 'css!components/viewManager/viewConta html = replaceAll(html, '<\/script>--\x3e', '<\/script>'); } - var wrapper = document.createElement('div'); + const wrapper = document.createElement('div'); wrapper.innerHTML = html; return wrapper.querySelector('div[data-role="page"]'); } function normalizeNewView(options, isPluginpage) { - var viewHtml = options.view; + const viewHtml = options.view; - if (-1 === viewHtml.indexOf('data-role="page"')) { + if (viewHtml.indexOf('data-role="page"') === -1) { return viewHtml; } - var hasScript = -1 !== viewHtml.indexOf(' { if (onBeforeChange) { onBeforeChange(view, true, options); } @@ -228,25 +220,27 @@ define(['browser', 'dom', 'layoutManager', 'css!components/viewManager/viewConta view.dispatchEvent(new CustomEvent('viewdestroy', {})); } - function reset() { + export function reset() { allPages = []; currentUrls = []; mainAnimatedPages.innerHTML = ''; selectedPageIndex = -1; } - var onBeforeChange; - var mainAnimatedPages = document.querySelector('.mainAnimatedPages'); - var allPages = []; - var currentUrls = []; - var pageContainerCount = 3; - var selectedPageIndex = -1; + let onBeforeChange; + const mainAnimatedPages = document.querySelector('.mainAnimatedPages'); + let allPages = []; + let currentUrls = []; + const pageContainerCount = 3; + let selectedPageIndex = -1; reset(); mainAnimatedPages.classList.remove('hide'); - return { - loadView: loadView, - tryRestoreView: tryRestoreView, - reset: reset, - setOnBeforeChange: setOnBeforeChange - }; -}); + +/* eslint-enable indent */ + +export default { + loadView: loadView, + tryRestoreView: tryRestoreView, + reset: reset, + setOnBeforeChange: setOnBeforeChange +}; diff --git a/src/components/viewManager/viewManager.js b/src/components/viewManager/viewManager.js index 8e17264f9f..00c3018590 100644 --- a/src/components/viewManager/viewManager.js +++ b/src/components/viewManager/viewManager.js @@ -1,141 +1,134 @@ -define(['viewContainer', 'focusManager', 'queryString', 'layoutManager'], function (viewContainer, focusManager, queryString, layoutManager) { - 'use strict'; +import viewContainer from 'viewContainer'; +import focusManager from 'focusManager'; +import queryString from 'queryString'; +import layoutManager from 'layoutManager'; - var currentView; - var dispatchPageEvents; +let currentView; +let dispatchPageEvents; - viewContainer.setOnBeforeChange(function (newView, isRestored, options) { +viewContainer.setOnBeforeChange(function (newView, isRestored, options) { + const lastView = currentView; + if (lastView) { + const beforeHideResult = dispatchViewEvent(lastView, null, 'viewbeforehide', true); - var lastView = currentView; - if (lastView) { - - var beforeHideResult = dispatchViewEvent(lastView, null, 'viewbeforehide', true); - - if (!beforeHideResult) { - // todo: cancel - } - } - - var eventDetail = getViewEventDetail(newView, options, isRestored); - - if (!newView.initComplete) { - newView.initComplete = true; - - var controller; - if (typeof options.controllerFactory === 'function') { - controller = new options.controllerFactory(newView, eventDetail.detail.params); - } else if (options.controllerFactory && typeof options.controllerFactory.default === 'function') { - controller = new options.controllerFactory.default(newView, eventDetail.detail.params); - } - - if (!options.controllerFactory || dispatchPageEvents) { - dispatchViewEvent(newView, eventDetail, 'viewinit'); - } - } - - dispatchViewEvent(newView, eventDetail, 'viewbeforeshow'); - }); - - function onViewChange(view, options, isRestore) { - - var lastView = currentView; - if (lastView) { - dispatchViewEvent(lastView, null, 'viewhide'); - } - - currentView = view; - - var eventDetail = getViewEventDetail(view, options, isRestore); - - if (!isRestore) { - if (options.autoFocus !== false) { - focusManager.autoFocus(view); - } - } else if (!layoutManager.mobile) { - if (view.activeElement && document.body.contains(view.activeElement) && focusManager.isCurrentlyFocusable(view.activeElement)) { - focusManager.focus(view.activeElement); - } else { - focusManager.autoFocus(view); - } - } - - view.dispatchEvent(new CustomEvent('viewshow', eventDetail)); - - if (dispatchPageEvents) { - view.dispatchEvent(new CustomEvent('pageshow', eventDetail)); + if (!beforeHideResult) { + // todo: cancel } } - function getProperties(view) { - var props = view.getAttribute('data-properties'); + const eventDetail = getViewEventDetail(newView, options, isRestored); - if (props) { - return props.split(','); + if (!newView.initComplete) { + newView.initComplete = true; + + if (typeof options.controllerFactory === 'function') { + new options.controllerFactory(newView, eventDetail.detail.params); + } else if (options.controllerFactory && typeof options.controllerFactory.default === 'function') { + new options.controllerFactory.default(newView, eventDetail.detail.params); } - return []; + if (!options.controllerFactory || dispatchPageEvents) { + dispatchViewEvent(newView, eventDetail, 'viewinit'); + } } - function dispatchViewEvent(view, eventInfo, eventName, isCancellable) { + dispatchViewEvent(newView, eventDetail, 'viewbeforeshow'); +}); - if (!eventInfo) { - eventInfo = { - detail: { - type: view.getAttribute('data-type'), - properties: getProperties(view) - }, - bubbles: true, - cancelable: isCancellable - }; - } - - eventInfo.cancelable = isCancellable || false; - - var eventResult = view.dispatchEvent(new CustomEvent(eventName, eventInfo)); - - if (dispatchPageEvents) { - eventInfo.cancelable = false; - view.dispatchEvent(new CustomEvent(eventName.replace('view', 'page'), eventInfo)); - } - - return eventResult; +function onViewChange(view, options, isRestore) { + const lastView = currentView; + if (lastView) { + dispatchViewEvent(lastView, null, 'viewhide'); } - function getViewEventDetail(view, options, isRestore) { + currentView = view; - var url = options.url; - var index = url.indexOf('?'); - var params = index === -1 ? {} : queryString.parse(url.substring(index + 1)); + const eventDetail = getViewEventDetail(view, options, isRestore); - return { + if (!isRestore) { + if (options.autoFocus !== false) { + focusManager.autoFocus(view); + } + } else if (!layoutManager.mobile) { + if (view.activeElement && document.body.contains(view.activeElement) && focusManager.isCurrentlyFocusable(view.activeElement)) { + focusManager.focus(view.activeElement); + } else { + focusManager.autoFocus(view); + } + } + + view.dispatchEvent(new CustomEvent('viewshow', eventDetail)); + + if (dispatchPageEvents) { + view.dispatchEvent(new CustomEvent('pageshow', eventDetail)); + } +} + +function getProperties(view) { + const props = view.getAttribute('data-properties'); + + if (props) { + return props.split(','); + } + + return []; +} + +function dispatchViewEvent(view, eventInfo, eventName, isCancellable) { + if (!eventInfo) { + eventInfo = { detail: { type: view.getAttribute('data-type'), - properties: getProperties(view), - params: params, - isRestored: isRestore, - state: options.state, - - // The route options - options: options.options || {} + properties: getProperties(view) }, bubbles: true, - cancelable: false + cancelable: isCancellable }; } - function resetCachedViews() { - // Reset all cached views whenever the skin changes - viewContainer.reset(); + eventInfo.cancelable = isCancellable || false; + + const eventResult = view.dispatchEvent(new CustomEvent(eventName, eventInfo)); + + if (dispatchPageEvents) { + eventInfo.cancelable = false; + view.dispatchEvent(new CustomEvent(eventName.replace('view', 'page'), eventInfo)); } - document.addEventListener('skinunload', resetCachedViews); + return eventResult; +} - function ViewManager() { - } +function getViewEventDetail(view, options, isRestore) { + const url = options.url; + const index = url.indexOf('?'); + const params = index === -1 ? {} : queryString.parse(url.substring(index + 1)); - ViewManager.prototype.loadView = function (options) { + return { + detail: { + type: view.getAttribute('data-type'), + properties: getProperties(view), + params: params, + isRestored: isRestore, + state: options.state, - var lastView = currentView; + // The route options + options: options.options || {} + }, + bubbles: true, + cancelable: false + }; +} + +function resetCachedViews() { + // Reset all cached views whenever the skin changes + viewContainer.reset(); +} + +document.addEventListener('skinunload', resetCachedViews); + +class ViewManager { + loadView(options) { + const lastView = currentView; // Record the element that has focus if (lastView) { @@ -147,13 +140,11 @@ define(['viewContainer', 'focusManager', 'queryString', 'layoutManager'], functi } viewContainer.loadView(options).then(function (view) { - onViewChange(view, options); }); - }; - - ViewManager.prototype.tryRestoreView = function (options, onViewChanging) { + } + tryRestoreView(options, onViewChanging) { if (options.cancel) { return Promise.reject({ cancelled: true }); } @@ -164,20 +155,18 @@ define(['viewContainer', 'focusManager', 'queryString', 'layoutManager'], functi } return viewContainer.tryRestoreView(options).then(function (view) { - onViewChanging(); onViewChange(view, options, true); - }); - }; + } - ViewManager.prototype.currentView = function () { + currentView() { return currentView; - }; + } - ViewManager.prototype.dispatchPageEvents = function (value) { + dispatchPageEvents(value) { dispatchPageEvents = value; - }; + } +} - return new ViewManager(); -}); +export default new ViewManager(); diff --git a/src/components/viewSettings/viewSettings.js b/src/components/viewSettings/viewSettings.js index 28a9854c34..fd5b5c3f9e 100644 --- a/src/components/viewSettings/viewSettings.js +++ b/src/components/viewSettings/viewSettings.js @@ -1,65 +1,66 @@ -define(['require', 'dialogHelper', 'loading', 'apphost', 'layoutManager', 'connectionManager', 'appRouter', 'globalize', 'userSettings', 'emby-checkbox', 'emby-input', 'paper-icon-button-light', 'emby-select', 'material-icons', 'css!./../formdialog', 'emby-button', 'flexStyles'], function (require, dialogHelper, loading, appHost, layoutManager, connectionManager, appRouter, globalize, userSettings) { - 'use strict'; +import dialogHelper from 'dialogHelper'; +import layoutManager from 'layoutManager'; +import globalize from 'globalize'; +import * as userSettings from 'userSettings'; +import 'emby-checkbox'; +import 'emby-input'; +import 'paper-icon-button-light'; +import 'emby-select'; +import 'material-icons'; +import 'css!./../formdialog'; +import 'emby-button'; +import 'flexStyles'; - function onSubmit(e) { +function onSubmit(e) { + e.preventDefault(); + return false; +} - e.preventDefault(); - return false; +function initEditor(context, settings) { + context.querySelector('form').addEventListener('submit', onSubmit); + + const elems = context.querySelectorAll('.viewSetting-checkboxContainer'); + + for (const elem of elems) { + elem.querySelector('input').checked = settings[elem.getAttribute('data-settingname')] || false; } - function initEditor(context, settings) { + context.querySelector('.selectImageType').value = settings.imageType || 'primary'; +} - context.querySelector('form').addEventListener('submit', onSubmit); - - var elems = context.querySelectorAll('.viewSetting-checkboxContainer'); - - for (var i = 0, length = elems.length; i < length; i++) { - - elems[i].querySelector('input').checked = settings[elems[i].getAttribute('data-settingname')] || false; - } - - context.querySelector('.selectImageType').value = settings.imageType || 'primary'; +function saveValues(context, settings, settingsKey) { + const elems = context.querySelectorAll('.viewSetting-checkboxContainer'); + for (const elem of elems) { + userSettings.set(settingsKey + '-' + elem.getAttribute('data-settingname'), elem.querySelector('input').checked); } - function saveValues(context, settings, settingsKey) { + userSettings.set(settingsKey + '-imageType', context.querySelector('.selectImageType').value); +} - var elems = context.querySelectorAll('.viewSetting-checkboxContainer'); - for (var i = 0, length = elems.length; i < length; i++) { - userSettings.set(settingsKey + '-' + elems[i].getAttribute('data-settingname'), elems[i].querySelector('input').checked); - } +function centerFocus(elem, horiz, on) { + import('scrollHelper').then(({default: scrollHelper}) => { + const fn = on ? 'on' : 'off'; + scrollHelper.centerFocus[fn](elem, horiz); + }); +} - userSettings.set(settingsKey + '-imageType', context.querySelector('.selectImageType').value); +function showIfAllowed(context, selector, visible) { + const elem = context.querySelector(selector); + + if (visible && !elem.classList.contains('hiddenFromViewSettings')) { + elem.classList.remove('hide'); + } else { + elem.classList.add('hide'); } +} - function centerFocus(elem, horiz, on) { - require(['scrollHelper'], function (scrollHelper) { - var fn = on ? 'on' : 'off'; - scrollHelper.centerFocus[fn](elem, horiz); - }); +class ViewSettings { + constructor() { } - - function showIfAllowed(context, selector, visible) { - - var elem = context.querySelector(selector); - - if (visible && !elem.classList.contains('hiddenFromViewSettings')) { - elem.classList.remove('hide'); - } else { - elem.classList.add('hide'); - } - } - - function ViewSettings() { - - } - - ViewSettings.prototype.show = function (options) { - + show(options) { return new Promise(function (resolve, reject) { - - require(['text!./viewSettings.template.html'], function (template) { - - var dialogOptions = { + import('text!./viewSettings.template.html').then(({default: template}) => { + const dialogOptions = { removeOnClose: true, scrollY: false }; @@ -70,11 +71,11 @@ define(['require', 'dialogHelper', 'loading', 'apphost', 'layoutManager', 'conne dialogOptions.size = 'small'; } - var dlg = dialogHelper.createDialog(dialogOptions); + const dlg = dialogHelper.createDialog(dialogOptions); dlg.classList.add('formDialog'); - var html = ''; + let html = ''; html += '
'; html += ''; @@ -86,27 +87,25 @@ define(['require', 'dialogHelper', 'loading', 'apphost', 'layoutManager', 'conne dlg.innerHTML = globalize.translateHtml(html, 'core'); - var settingElements = dlg.querySelectorAll('.viewSetting'); - for (var i = 0, length = settingElements.length; i < length; i++) { - if (options.visibleSettings.indexOf(settingElements[i].getAttribute('data-settingname')) === -1) { - settingElements[i].classList.add('hide'); - settingElements[i].classList.add('hiddenFromViewSettings'); + const settingElements = dlg.querySelectorAll('.viewSetting'); + for (const settingElement of settingElements) { + if (options.visibleSettings.indexOf(settingElement.getAttribute('data-settingname')) === -1) { + settingElement.classList.add('hide'); + settingElement.classList.add('hiddenFromViewSettings'); } else { - settingElements[i].classList.remove('hide'); - settingElements[i].classList.remove('hiddenFromViewSettings'); + settingElement.classList.remove('hide'); + settingElement.classList.remove('hiddenFromViewSettings'); } } initEditor(dlg, options.settings); dlg.querySelector('.selectImageType').addEventListener('change', function () { - showIfAllowed(dlg, '.chkTitleContainer', this.value !== 'list'); showIfAllowed(dlg, '.chkYearContainer', this.value !== 'list'); }); dlg.querySelector('.btnCancel').addEventListener('click', function () { - dialogHelper.close(dlg); }); @@ -114,18 +113,15 @@ define(['require', 'dialogHelper', 'loading', 'apphost', 'layoutManager', 'conne centerFocus(dlg.querySelector('.formDialogContent'), false, true); } - var submitted; + let submitted; dlg.querySelector('.selectImageType').dispatchEvent(new CustomEvent('change', {})); dlg.querySelector('form').addEventListener('change', function () { - submitted = true; - }, true); dialogHelper.open(dlg).then(function () { - if (layoutManager.tv) { centerFocus(dlg.querySelector('.formDialogContent'), false, false); } @@ -140,7 +136,7 @@ define(['require', 'dialogHelper', 'loading', 'apphost', 'layoutManager', 'conne }); }); }); - }; + } +} - return ViewSettings; -}); +export default ViewSettings; diff --git a/src/config.template.json b/src/config.template.json index 4b22a699d6..0f308ccc11 100644 --- a/src/config.template.json +++ b/src/config.template.json @@ -1,3 +1,39 @@ { - "multiserver": false + "multiserver": false, + "themes": [ + { + "name": "Apple TV", + "id": "appletv" + }, { + "name": "Blue Radiance", + "id": "blueradiance" + }, { + "name": "Dark", + "id": "dark", + "default": true + }, { + "name": "Light", + "id": "light" + }, { + "name": "Purple Haze", + "id": "purplehaze" + }, { + "name": "WMC", + "id": "wmc" + } + ], + "plugins": [ + "plugins/playAccessValidation/plugin", + "plugins/experimentalWarnings/plugin", + "plugins/htmlAudioPlayer/plugin", + "plugins/htmlVideoPlayer/plugin", + "plugins/photoPlayer/plugin", + "plugins/comicsPlayer/plugin", + "plugins/bookPlayer/plugin", + "plugins/youtubePlayer/plugin", + "plugins/backdropScreensaver/plugin", + "plugins/logoScreensaver/plugin", + "plugins/sessionPlayer/plugin", + "plugins/chromecastPlayer/plugin" + ] } diff --git a/src/apikeys.html b/src/controllers/dashboard/apikeys.html similarity index 94% rename from src/apikeys.html rename to src/controllers/dashboard/apikeys.html index 3cb7cd6de1..400a3e5f60 100644 --- a/src/apikeys.html +++ b/src/controllers/dashboard/apikeys.html @@ -3,7 +3,7 @@

${HeaderApiKeys}

-
diff --git a/src/controllers/dashboard/apikeys.js b/src/controllers/dashboard/apikeys.js index 0e122c486d..2b526aa8cc 100644 --- a/src/controllers/dashboard/apikeys.js +++ b/src/controllers/dashboard/apikeys.js @@ -1,6 +1,5 @@ import datetime from 'datetime'; import loading from 'loading'; -import libraryMenu from 'libraryMenu'; import dom from 'dom'; import globalize from 'globalize'; import 'emby-button'; diff --git a/src/dashboard.html b/src/controllers/dashboard/dashboard.html similarity index 99% rename from src/dashboard.html rename to src/controllers/dashboard/dashboard.html index b7af3c8c03..ec0d60a74d 100644 --- a/src/dashboard.html +++ b/src/controllers/dashboard/dashboard.html @@ -17,7 +17,7 @@
'; - btnCssClass = session.ServerId && -1 !== session.SupportedCommands.indexOf('DisplayMessage') && session.DeviceId !== connectionManager.deviceId() ? '' : ' hide'; + btnCssClass = session.ServerId && session.SupportedCommands.indexOf('DisplayMessage') !== -1 && session.DeviceId !== window.connectionManager.deviceId() ? '' : ' hide'; html += ''; html += '
'; @@ -346,7 +345,7 @@ import 'emby-itemscontainer'; function renderRunningTasks(view, tasks) { let html = ''; tasks = tasks.filter(function (task) { - if ('Idle' != task.State) { + if (task.State != 'Idle') { return !task.IsHidden; } @@ -551,7 +550,7 @@ import 'emby-itemscontainer'; row.classList.remove('playingSession'); } - if (session.ServerId && -1 !== session.SupportedCommands.indexOf('DisplayMessage') && session.DeviceId !== connectionManager.deviceId()) { + if (session.ServerId && session.SupportedCommands.indexOf('DisplayMessage') !== -1 && session.DeviceId !== window.connectionManager.deviceId()) { row.querySelector('.btnSessionSendMessage').classList.remove('hide'); } else { row.querySelector('.btnSessionSendMessage').classList.add('hide'); @@ -565,7 +564,7 @@ import 'emby-itemscontainer'; const btnSessionPlayPause = row.querySelector('.btnSessionPlayPause'); - if (session.ServerId && nowPlayingItem && session.SupportsRemoteControl && session.DeviceId !== connectionManager.deviceId()) { + if (session.ServerId && nowPlayingItem && session.SupportsRemoteControl && session.DeviceId !== window.connectionManager.deviceId()) { btnSessionPlayPause.classList.remove('hide'); row.querySelector('.btnSessionStop').classList.remove('hide'); } else { @@ -722,9 +721,9 @@ import 'emby-itemscontainer'; restart: function (btn) { import('confirm').then(({default: confirm}) => { confirm({ - title: globalize.translate('HeaderRestart'), + title: globalize.translate('Restart'), text: globalize.translate('MessageConfirmRestart'), - confirmText: globalize.translate('ButtonRestart'), + confirmText: globalize.translate('Restart'), primary: 'delete' }).then(function () { const page = dom.parentWithClass(btn, 'page'); @@ -737,7 +736,7 @@ import 'emby-itemscontainer'; shutdown: function (btn) { import('confirm').then(({default: confirm}) => { confirm({ - title: globalize.translate('HeaderShutdown'), + title: globalize.translate('ButtonShutdown'), text: globalize.translate('MessageConfirmShutdown'), confirmText: globalize.translate('ButtonShutdown'), primary: 'delete' diff --git a/src/device.html b/src/controllers/dashboard/devices/device.html similarity index 91% rename from src/device.html rename to src/controllers/dashboard/devices/device.html index 093b311208..aec73db008 100644 --- a/src/device.html +++ b/src/controllers/dashboard/devices/device.html @@ -9,13 +9,13 @@
- +
${LabelCustomDeviceDisplayNameHelp}
diff --git a/src/controllers/dashboard/devices/device.js b/src/controllers/dashboard/devices/device.js index 7faaee2494..17e28b9bdb 100644 --- a/src/controllers/dashboard/devices/device.js +++ b/src/controllers/dashboard/devices/device.js @@ -1,5 +1,4 @@ import loading from 'loading'; -import libraryMenu from 'libraryMenu'; import dom from 'dom'; import 'emby-input'; import 'emby-button'; diff --git a/src/devices.html b/src/controllers/dashboard/devices/devices.html similarity index 97% rename from src/devices.html rename to src/controllers/dashboard/devices/devices.html index 55f51d7e23..63c348c900 100644 --- a/src/devices.html +++ b/src/controllers/dashboard/devices/devices.html @@ -3,7 +3,7 @@
-

${TabDevices}

+

${HeaderDevices}

${Help}
diff --git a/src/controllers/dashboard/devices/devices.js b/src/controllers/dashboard/devices/devices.js index 9f05d84713..1178a0f1bd 100644 --- a/src/controllers/dashboard/devices/devices.js +++ b/src/controllers/dashboard/devices/devices.js @@ -1,6 +1,5 @@ import loading from 'loading'; import dom from 'dom'; -import libraryMenu from 'libraryMenu'; import globalize from 'globalize'; import imageHelper from 'scripts/imagehelper'; import * as datefns from 'date-fns'; @@ -22,7 +21,7 @@ import 'cardStyle'; confirm({ text: msg, title: globalize.translate('HeaderDeleteDevice'), - confirmText: globalize.translate('ButtonDelete'), + confirmText: globalize.translate('Delete'), primary: 'delete' }).then(function () { loading.show(); @@ -39,7 +38,7 @@ import 'cardStyle'; } function showDeviceMenu(view, btn, deviceId) { - let menuItems = []; + const menuItems = []; if (canEdit) { menuItems.push({ diff --git a/src/dlnaprofile.html b/src/controllers/dashboard/dlna/profile.html similarity index 93% rename from src/dlnaprofile.html rename to src/controllers/dashboard/dlna/profile.html index e960d97a0f..22edffed99 100644 --- a/src/dlnaprofile.html +++ b/src/controllers/dashboard/dlna/profile.html @@ -5,13 +5,12 @@

${HeaderProfileInformation}

- ${Help}
- ${TabInfo} + ${ButtonInfo} ${TabDirectPlay} - ${TabTranscoding} + ${Transcoding} ${TabContainers} ${TabCodecs} ${TabResponses} @@ -30,15 +29,15 @@
@@ -97,14 +96,14 @@

${HeaderHttpHeaders}

-
-
+

@@ -145,19 +144,19 @@
-
${LabelAlbumArtMaxWidthHelp}
+
${LabelAlbumArtMaxResHelp}
-
${LabelAlbumArtMaxHeightHelp}
+
${LabelAlbumArtMaxResHelp}
-
${LabelIconMaxWidthHelp}
+
${LabelIconMaxResHelp}
-
${LabelIconMaxHeightHelp}
+
${LabelIconMaxResHelp}
@@ -210,7 +209,7 @@

${HeaderSubtitleProfilesHelp}


@@ -220,7 +219,7 @@

${HeaderXmlDocumentAttributes}

-
@@ -232,38 +231,38 @@

${HeaderDirectPlayProfileHelp}

- +

${HeaderTranscodingProfileHelp}

- +

${HeaderContainerProfileHelp}

- +

${HeaderCodecProfileHelp}

- +

${HeaderResponseProfileHelp}

- +

'; + if (profile.Type == 'User') { + html += ''; } html += '
'; @@ -81,7 +81,7 @@ import 'emby-button'; function getTabs() { return [{ href: 'dlnasettings.html', - name: globalize.translate('TabSettings') + name: globalize.translate('Settings') }, { href: 'dlnaprofiles.html', name: globalize.translate('TabProfiles') diff --git a/src/dlnasettings.html b/src/controllers/dashboard/dlna/settings.html similarity index 96% rename from src/dlnasettings.html rename to src/controllers/dashboard/dlna/settings.html index 703dd66a9f..765bf2d8a8 100644 --- a/src/dlnasettings.html +++ b/src/controllers/dashboard/dlna/settings.html @@ -7,7 +7,7 @@
-

${TabSettings}

+

${Settings}

${Help}
@@ -59,7 +59,7 @@
diff --git a/src/controllers/dashboard/dlna/settings.js b/src/controllers/dashboard/dlna/settings.js index 9e7d78d799..fb93441a55 100644 --- a/src/controllers/dashboard/dlna/settings.js +++ b/src/controllers/dashboard/dlna/settings.js @@ -38,7 +38,7 @@ import globalize from 'globalize'; function getTabs() { return [{ href: 'dlnasettings.html', - name: globalize.translate('TabSettings') + name: globalize.translate('Settings') }, { href: 'dlnaprofiles.html', name: globalize.translate('TabProfiles') diff --git a/src/encodingsettings.html b/src/controllers/dashboard/encodingsettings.html similarity index 97% rename from src/encodingsettings.html rename to src/controllers/dashboard/encodingsettings.html index 858375b145..fb89fd298a 100644 --- a/src/encodingsettings.html +++ b/src/controllers/dashboard/encodingsettings.html @@ -4,14 +4,14 @@
-

${TabTranscoding}

+

${Transcoding}

${Help}
- + @@ -132,7 +132,7 @@
-
${LabelPreferredDisplayLanguageHelp}
+
${LabelDisplayLanguageHelp}
@@ -63,7 +63,7 @@
diff --git a/src/controllers/dashboard/general.js b/src/controllers/dashboard/general.js index 8e30749aba..eb819dc415 100644 --- a/src/controllers/dashboard/general.js +++ b/src/controllers/dashboard/general.js @@ -52,7 +52,7 @@ import 'emby-button'; }); }, function () { import('alert').then(({default: alert}) => { - alert(globalize.translate('DefaultErrorMessage')); + alert(globalize.translate('ErrorDefault')); }); Dashboard.processServerConfigurationUpdateResult(); diff --git a/src/library.html b/src/controllers/dashboard/library.html similarity index 100% rename from src/library.html rename to src/controllers/dashboard/library.html diff --git a/src/controllers/dashboard/mediaLibrary.js b/src/controllers/dashboard/library.js similarity index 94% rename from src/controllers/dashboard/mediaLibrary.js rename to src/controllers/dashboard/library.js index b958e69d22..0abda33b6e 100644 --- a/src/controllers/dashboard/mediaLibrary.js +++ b/src/controllers/dashboard/library.js @@ -1,11 +1,9 @@ import $ from 'jQuery'; -import appHost from 'apphost'; import taskButton from 'scripts/taskbutton'; import loading from 'loading'; import libraryMenu from 'libraryMenu'; import globalize from 'globalize'; import dom from 'dom'; -import indicators from 'indicators'; import imageHelper from 'scripts/imagehelper'; import 'cardStyle'; import 'emby-itemrefreshindicator'; @@ -95,7 +93,7 @@ import 'emby-itemrefreshindicator'; const virtualFolder = virtualFolders[index]; const menuItems = []; menuItems.push({ - name: globalize.translate('ButtonEditImages'), + name: globalize.translate('EditImages'), id: 'editimages', icon: 'photo' }); @@ -158,7 +156,7 @@ import 'emby-itemrefreshindicator'; } function shouldRefreshLibraryAfterChanges(page) { - return 'mediaLibraryPage' === page.id; + return page.id === 'mediaLibraryPage'; } function reloadVirtualFolders(page, virtualFolders) { @@ -220,29 +218,29 @@ import 'emby-itemrefreshindicator'; name: '', value: '' }, { - name: globalize.translate('FolderTypeMovies'), + name: globalize.translate('Movies'), value: 'movies', message: getLink('MovieLibraryHelp', 'https://docs.jellyfin.org/general/server/media/movies.html') }, { - name: globalize.translate('FolderTypeMusic'), + name: globalize.translate('TabMusic'), value: 'music', message: getLink('MusicLibraryHelp', 'https://docs.jellyfin.org/general/server/media/music.html') }, { - name: globalize.translate('FolderTypeTvShows'), + name: globalize.translate('Shows'), value: 'tvshows', message: getLink('TvLibraryHelp', 'https://docs.jellyfin.org/general/server/media/shows.html') }, { - name: globalize.translate('FolderTypeBooks'), + name: globalize.translate('Books'), value: 'books', message: getLink('BookLibraryHelp', 'https://docs.jellyfin.org/general/server/media/books.html') }, { - name: globalize.translate('OptionHomeVideos'), + name: globalize.translate('Photos'), value: 'homevideos' }, { - name: globalize.translate('FolderTypeMusicVideos'), + name: globalize.translate('MusicVideos'), value: 'musicvideos' }, { - name: globalize.translate('FolderTypeUnset'), + name: globalize.translate('Other'), value: 'mixed', message: globalize.translate('MessageUnsetContentHelp') }]; @@ -288,7 +286,7 @@ import 'emby-itemrefreshindicator'; if (hasCardImageContainer) { html += '
'; - html += '
'; + html += '
'; html += '
'; html += '
'; } @@ -328,7 +326,7 @@ import 'emby-itemrefreshindicator'; let typeName = getCollectionTypeOptions().filter(function (t) { return t.value == virtualFolder.CollectionType; })[0]; - typeName = typeName ? typeName.name : globalize.translate('FolderTypeUnset'); + typeName = typeName ? typeName.name : globalize.translate('Other'); html += "
"; if (virtualFolder.showType === false) { @@ -365,10 +363,10 @@ import 'emby-itemrefreshindicator'; name: globalize.translate('HeaderLibraries') }, { href: 'librarydisplay.html', - name: globalize.translate('TabDisplay') + name: globalize.translate('Display') }, { href: 'metadataimages.html', - name: globalize.translate('TabMetadata') + name: globalize.translate('Metadata') }, { href: 'metadatanfo.html', name: globalize.translate('TabNfoSettings') @@ -395,7 +393,6 @@ import 'emby-itemrefreshindicator'; }); }); pageIdOn('pagebeforehide', 'mediaLibraryPage', function () { - const page = this; taskButton({ mode: 'off', diff --git a/src/librarydisplay.html b/src/controllers/dashboard/librarydisplay.html similarity index 98% rename from src/librarydisplay.html rename to src/controllers/dashboard/librarydisplay.html index 25dce48223..04a8554823 100644 --- a/src/librarydisplay.html +++ b/src/controllers/dashboard/librarydisplay.html @@ -49,7 +49,7 @@
diff --git a/src/controllers/dashboard/librarydisplay.js b/src/controllers/dashboard/librarydisplay.js index 2269e19c44..06e366b988 100644 --- a/src/controllers/dashboard/librarydisplay.js +++ b/src/controllers/dashboard/librarydisplay.js @@ -12,10 +12,10 @@ import 'emby-button'; name: globalize.translate('HeaderLibraries') }, { href: 'librarydisplay.html', - name: globalize.translate('TabDisplay') + name: globalize.translate('Display') }, { href: 'metadataimages.html', - name: globalize.translate('TabMetadata') + name: globalize.translate('Metadata') }, { href: 'metadatanfo.html', name: globalize.translate('TabNfoSettings') @@ -48,7 +48,7 @@ import 'emby-button'; ApiClient.updateServerConfiguration(config).then(Dashboard.processServerConfigurationUpdateResult); }); ApiClient.getNamedConfiguration('metadata').then(function(config) { - config.UseFileCreationTimeForDateAdded = '1' === $('#selectDateAdded', form).val(); + config.UseFileCreationTimeForDateAdded = $('#selectDateAdded', form).val() === '1'; ApiClient.updateNamedConfiguration('metadata', config); }); @@ -61,7 +61,7 @@ import 'emby-button'; libraryMenu.setTabs('librarysetup', 1, getTabs); loadData(); ApiClient.getSystemInfo().then(function(info) { - if ('Windows' === info.OperatingSystem) { + if (info.OperatingSystem === 'Windows') { view.querySelector('.fldSaveMetadataHidden').classList.remove('hide'); } else { view.querySelector('.fldSaveMetadataHidden').classList.add('hide'); diff --git a/src/log.html b/src/controllers/dashboard/logs.html similarity index 100% rename from src/log.html rename to src/controllers/dashboard/logs.html diff --git a/src/controllers/dashboard/metadataImages.js b/src/controllers/dashboard/metadataImages.js index cbfe05419c..649ca9ac31 100644 --- a/src/controllers/dashboard/metadataImages.js +++ b/src/controllers/dashboard/metadataImages.js @@ -1,5 +1,4 @@ import $ from 'jQuery'; -import dom from 'dom'; import loading from 'loading'; import libraryMenu from 'libraryMenu'; import globalize from 'globalize'; @@ -56,10 +55,10 @@ import 'listViewStyle'; name: globalize.translate('HeaderLibraries') }, { href: 'librarydisplay.html', - name: globalize.translate('TabDisplay') + name: globalize.translate('Display') }, { href: 'metadataimages.html', - name: globalize.translate('TabMetadata') + name: globalize.translate('Metadata') }, { href: 'metadatanfo.html', name: globalize.translate('TabNfoSettings') diff --git a/src/metadataimages.html b/src/controllers/dashboard/metadataimages.html similarity index 92% rename from src/metadataimages.html rename to src/controllers/dashboard/metadataimages.html index 8ad129256c..48a9977054 100644 --- a/src/metadataimages.html +++ b/src/controllers/dashboard/metadataimages.html @@ -17,7 +17,7 @@

-
+
diff --git a/src/metadatanfo.html b/src/controllers/dashboard/metadatanfo.html similarity index 97% rename from src/metadatanfo.html rename to src/controllers/dashboard/metadatanfo.html index 4005c74f67..44d1cc06f6 100644 --- a/src/metadatanfo.html +++ b/src/controllers/dashboard/metadatanfo.html @@ -41,7 +41,7 @@
${LabelKodiMetadataEnableExtraThumbsHelp}
-
+
diff --git a/src/controllers/dashboard/metadatanfo.js b/src/controllers/dashboard/metadatanfo.js index f1b768d18b..16e1018e44 100644 --- a/src/controllers/dashboard/metadatanfo.js +++ b/src/controllers/dashboard/metadatanfo.js @@ -6,7 +6,7 @@ import globalize from 'globalize'; /* eslint-disable indent */ function loadPage(page, config, users) { - let html = ''; + let html = ''; html += users.map(function (user) { return ''; }).join(''); @@ -52,10 +52,10 @@ import globalize from 'globalize'; name: globalize.translate('HeaderLibraries') }, { href: 'librarydisplay.html', - name: globalize.translate('TabDisplay') + name: globalize.translate('Display') }, { href: 'metadataimages.html', - name: globalize.translate('TabMetadata') + name: globalize.translate('Metadata') }, { href: 'metadatanfo.html', name: globalize.translate('TabNfoSettings') diff --git a/src/networking.html b/src/controllers/dashboard/networking.html similarity index 99% rename from src/networking.html rename to src/controllers/dashboard/networking.html index 74ddefb87b..899d0976b7 100644 --- a/src/networking.html +++ b/src/controllers/dashboard/networking.html @@ -112,7 +112,7 @@
diff --git a/src/controllers/dashboard/networking.js b/src/controllers/dashboard/networking.js index 367ab8335c..a7f3076056 100644 --- a/src/controllers/dashboard/networking.js +++ b/src/controllers/dashboard/networking.js @@ -1,5 +1,4 @@ import loading from 'loading'; -import libraryMenu from 'libraryMenu'; import globalize from 'globalize'; import 'emby-checkbox'; import 'emby-select'; @@ -31,7 +30,7 @@ import 'emby-select'; }).filter(function (s) { return s.length > 0; }); - config.IsRemoteIPFilterBlacklist = 'blacklist' === form.querySelector('#selectExternalAddressFilterMode').value; + config.IsRemoteIPFilterBlacklist = form.querySelector('#selectExternalAddressFilterMode').value === 'blacklist'; config.PublicPort = form.querySelector('#txtPublicPort').value; config.PublicHttpsPort = form.querySelector('#txtPublicHttpsPort').value; config.HttpServerPortNumber = form.querySelector('#txtPortNumber').value; @@ -111,7 +110,7 @@ import 'emby-select'; page.querySelector('#txtLanNetworks').value = (config.LocalNetworkSubnets || []).join(', '); page.querySelector('#txtExternalAddressFilter').value = (config.RemoteIPFilter || []).join(', '); page.querySelector('#selectExternalAddressFilterMode').value = config.IsRemoteIPFilterBlacklist ? 'blacklist' : 'whitelist'; - page.querySelector('#chkRemoteAccess').checked = null == config.EnableRemoteAccess || config.EnableRemoteAccess; + page.querySelector('#chkRemoteAccess').checked = config.EnableRemoteAccess == null || config.EnableRemoteAccess; page.querySelector('#txtHttpsPort').value = config.HttpsPortNumber; page.querySelector('#chkEnableHttps').checked = config.EnableHttps; page.querySelector('#chkRequireHttps').checked = config.RequireHttps; diff --git a/src/controllers/dashboard/notifications/notification/index.html b/src/controllers/dashboard/notifications/notification/index.html index 67b35981f1..99bed59887 100644 --- a/src/controllers/dashboard/notifications/notification/index.html +++ b/src/controllers/dashboard/notifications/notification/index.html @@ -54,7 +54,7 @@

'; - itemHtml += ''; - } - itemHtml += '
'; - itemHtml += '
'; - itemHtml += '

'; - itemHtml += notification.Category; - itemHtml += '

'; - if (showHelp) { - showHelp = false; - itemHtml += ''; - itemHtml += globalize.translate('Help'); - itemHtml += ''; - } +function reload(page) { + loading.show(); + ApiClient.getJSON(ApiClient.getUrl('Notifications/Types')).then(function (list) { + let html = ''; + let lastCategory = ''; + let showHelp = true; + html += list.map(function (notification) { + let itemHtml = ''; + if (notification.Category !== lastCategory) { + lastCategory = notification.Category; + if (lastCategory) { + itemHtml += '
'; itemHtml += '
'; - itemHtml += '
'; } - itemHtml += ''; - if (notification.Enabled) { - itemHtml += ''; - } else { - itemHtml += ''; + itemHtml += '
'; + itemHtml += '
'; + itemHtml += '

'; + itemHtml += notification.Category; + itemHtml += '

'; + if (showHelp) { + showHelp = false; + itemHtml += '
'; + itemHtml += globalize.translate('Help'); + itemHtml += ''; } - itemHtml += '
'; - itemHtml += '
' + notification.Name + '
'; itemHtml += '
'; - itemHtml += ''; - itemHtml += ''; - return itemHtml; - }).join(''); - - if (list.length) { - html += '
'; - html += '
'; + itemHtml += '
'; } - page.querySelector('.notificationList').innerHTML = html; - loading.hide(); - }); - } + itemHtml += ''; + if (notification.Enabled) { + itemHtml += ''; + } else { + itemHtml += ''; + } + itemHtml += '
'; + itemHtml += '
' + notification.Name + '
'; + itemHtml += '
'; + itemHtml += ''; + itemHtml += '
'; + return itemHtml; + }).join(''); - return function(view, params) { - view.addEventListener('viewshow', function() { - reload(view); - }); - }; -}); + if (list.length) { + html += '
'; + html += '
'; + } + page.querySelector('.notificationList').innerHTML = html; + loading.hide(); + }); +} + +export default function (view, params) { + view.addEventListener('viewshow', function () { + reload(view); + }); +} diff --git a/src/playbackconfiguration.html b/src/controllers/dashboard/playback.html similarity index 91% rename from src/playbackconfiguration.html rename to src/controllers/dashboard/playback.html index 9deeeebbcd..e5f392ead4 100644 --- a/src/playbackconfiguration.html +++ b/src/controllers/dashboard/playback.html @@ -3,7 +3,7 @@
-

${TabResumeSettings}

+

${ButtonResume}

@@ -23,7 +23,7 @@ ${LabelMinResumeDurationHelp}
-
+
diff --git a/src/controllers/dashboard/playback.js b/src/controllers/dashboard/playback.js index 0406c21926..101c3ac0a2 100644 --- a/src/controllers/dashboard/playback.js +++ b/src/controllers/dashboard/playback.js @@ -32,7 +32,7 @@ import globalize from 'globalize'; name: globalize.translate('Transcoding') }, { href: 'playbackconfiguration.html', - name: globalize.translate('TabResumeSettings') + name: globalize.translate('ButtonResume') }, { href: 'streamingsettings.html', name: globalize.translate('TabStreaming') diff --git a/src/controllers/dashboard/plugins/add/index.js b/src/controllers/dashboard/plugins/add/index.js index 375a0c0ec7..5cc1dd3215 100644 --- a/src/controllers/dashboard/plugins/add/index.js +++ b/src/controllers/dashboard/plugins/add/index.js @@ -1,143 +1,146 @@ -define(['jQuery', 'loading', 'libraryMenu', 'globalize', 'connectionManager', 'emby-button'], function ($, loading, libraryMenu, globalize, connectionManager) { - 'use strict'; +import $ from 'jQuery'; +import loading from 'loading'; +import globalize from 'globalize'; +import 'emby-button'; - function populateHistory(packageInfo, page) { - var html = ''; - var length = Math.min(packageInfo.versions.length, 10); +function populateHistory(packageInfo, page) { + let html = ''; + const length = Math.min(packageInfo.versions.length, 10); - for (var i = 0; i < length; i++) { - var version = packageInfo.versions[i]; - html += '

' + version.version + '

'; - html += '
' + version.changelog + '
'; - } - - $('#revisionHistory', page).html(html); + for (let i = 0; i < length; i++) { + const version = packageInfo.versions[i]; + html += '

' + version.version + '

'; + html += '
' + version.changelog + '
'; } - function populateVersions(packageInfo, page, installedPlugin) { - var html = ''; + $('#revisionHistory', page).html(html); +} - for (var i = 0; i < packageInfo.versions.length; i++) { - var version = packageInfo.versions[i]; - html += ''; - } +function populateVersions(packageInfo, page, installedPlugin) { + let html = ''; - var selectmenu = $('#selectVersion', page).html(html); - - if (!installedPlugin) { - $('#pCurrentVersion', page).hide().html(''); - } - - var packageVersion = packageInfo.versions[0]; - if (packageVersion) { - selectmenu.val(packageVersion.version); - } + for (let i = 0; i < packageInfo.versions.length; i++) { + const version = packageInfo.versions[i]; + html += ''; } - function renderPackage(pkg, installedPlugins, page) { - var installedPlugin = installedPlugins.filter(function (ip) { - return ip.Name == pkg.name; - })[0]; + const selectmenu = $('#selectVersion', page).html(html); - populateVersions(pkg, page, installedPlugin); - populateHistory(pkg, page); - - $('.pluginName', page).html(pkg.name); - $('#btnInstallDiv', page).removeClass('hide'); - $('#pSelectVersion', page).removeClass('hide'); - - if (pkg.overview) { - $('#overview', page).show().html(pkg.overview); - } else { - $('#overview', page).hide(); - } - - $('#description', page).html(pkg.description); - $('#developer', page).html(pkg.owner); - - if (installedPlugin) { - var currentVersionText = globalize.translate('MessageYouHaveVersionInstalled', '' + installedPlugin.Version + ''); - $('#pCurrentVersion', page).show().html(currentVersionText); - } else { - $('#pCurrentVersion', page).hide().html(''); - } - - loading.hide(); + if (!installedPlugin) { + $('#pCurrentVersion', page).hide().html(''); } - function alertText(options) { - require(['alert'], function (alert) { - alert(options); - }); + const packageVersion = packageInfo.versions[0]; + if (packageVersion) { + selectmenu.val(packageVersion.version); + } +} + +function renderPackage(pkg, installedPlugins, page) { + const installedPlugin = installedPlugins.filter(function (ip) { + return ip.Name == pkg.name; + })[0]; + + populateVersions(pkg, page, installedPlugin); + populateHistory(pkg, page); + + $('.pluginName', page).html(pkg.name); + $('#btnInstallDiv', page).removeClass('hide'); + $('#pSelectVersion', page).removeClass('hide'); + + if (pkg.overview) { + $('#overview', page).show().html(pkg.overview); + } else { + $('#overview', page).hide(); } - function performInstallation(page, name, guid, version) { - var developer = $('#developer', page).html().toLowerCase(); + $('#description', page).html(pkg.description); + $('#developer', page).html(pkg.owner); - var alertCallback = function () { - loading.show(); - page.querySelector('#btnInstall').disabled = true; - ApiClient.installPlugin(name, guid, version).then(function () { - loading.hide(); - alertText(globalize.translate('PluginInstalledMessage')); - }); - }; + if (installedPlugin) { + const currentVersionText = globalize.translate('MessageYouHaveVersionInstalled', '' + installedPlugin.Version + ''); + $('#pCurrentVersion', page).show().html(currentVersionText); + } else { + $('#pCurrentVersion', page).hide().html(''); + } - if (developer !== 'jellyfin') { + loading.hide(); +} + +function alertText(options) { + import('alert').then(({default: alert}) => { + alert(options); + }); +} + +function performInstallation(page, name, guid, version) { + const developer = $('#developer', page).html().toLowerCase(); + + const alertCallback = function () { + loading.show(); + page.querySelector('#btnInstall').disabled = true; + ApiClient.installPlugin(name, guid, version).then(() => { loading.hide(); - var msg = globalize.translate('MessagePluginInstallDisclaimer'); - msg += '
'; - msg += '
'; - msg += globalize.translate('PleaseConfirmPluginInstallation'); - - require(['confirm'], function (confirm) { - confirm.default(msg, globalize.translate('HeaderConfirmPluginInstallation')).then(function () { - alertCallback(); - }, function () { - console.debug('plugin not installed'); - }); - }); - } else { - alertCallback(); - } - } - - return function (view, params) { - $('.addPluginForm', view).on('submit', function () { - loading.show(); - var page = $(this).parents('#addPluginPage')[0]; - var name = params.name; - var guid = params.guid; - ApiClient.getInstalledPlugins().then(function (plugins) { - var installedPlugin = plugins.filter(function (plugin) { - return plugin.Name == name; - })[0]; - - var version = $('#selectVersion', page).val(); - if (installedPlugin) { - if (installedPlugin.Version === version) { - loading.hide(); - Dashboard.alert({ - message: globalize.translate('MessageAlreadyInstalled'), - title: globalize.translate('HeaderPluginInstallation') - }); - } - } else { - performInstallation(page, name, guid, version); - } - }); - return false; - }); - view.addEventListener('viewshow', function () { - var page = this; - loading.show(); - var name = params.name; - var guid = params.guid; - var promise1 = ApiClient.getPackageInfo(name, guid); - var promise2 = ApiClient.getInstalledPlugins(); - Promise.all([promise1, promise2]).then(function (responses) { - renderPackage(responses[0], responses[1], page); - }); + alertText(globalize.translate('MessagePluginInstalled')); + }).catch(() => { + alertText(globalize.translate('MessagePluginInstallError')); }); }; -}); + + if (developer !== 'jellyfin') { + loading.hide(); + let msg = globalize.translate('MessagePluginInstallDisclaimer'); + msg += '
'; + msg += '
'; + msg += globalize.translate('PleaseConfirmPluginInstallation'); + + import('confirm').then(({default: confirm}) => { + confirm(msg, globalize.translate('HeaderConfirmPluginInstallation')).then(function () { + alertCallback(); + }).catch(() => { + console.debug('plugin not installed'); + }); + }); + } else { + alertCallback(); + } +} + +export default function(view, params) { + $('.addPluginForm', view).on('submit', function () { + loading.show(); + const page = $(this).parents('#addPluginPage')[0]; + const name = params.name; + const guid = params.guid; + ApiClient.getInstalledPlugins().then(function (plugins) { + const installedPlugin = plugins.filter(function (plugin) { + return plugin.Name == name; + })[0]; + + const version = $('#selectVersion', page).val(); + if (installedPlugin && installedPlugin.Version === version) { + loading.hide(); + Dashboard.alert({ + message: globalize.translate('MessageAlreadyInstalled'), + title: globalize.translate('HeaderPluginInstallation') + }); + } else { + performInstallation(page, name, guid, version); + } + }).catch(() => { + alertText(globalize.translate('MessageGetInstalledPluginsError')); + }); + return false; + }); + view.addEventListener('viewshow', function () { + const page = this; + loading.show(); + const name = params.name; + const guid = params.guid; + const promise1 = ApiClient.getPackageInfo(name, guid); + const promise2 = ApiClient.getInstalledPlugins(); + Promise.all([promise1, promise2]).then(function (responses) { + renderPackage(responses[0], responses[1], page); + }); + }); +} diff --git a/src/controllers/dashboard/plugins/available/index.js b/src/controllers/dashboard/plugins/available/index.js index 37df8801b5..b7c3505aa1 100644 --- a/src/controllers/dashboard/plugins/available/index.js +++ b/src/controllers/dashboard/plugins/available/index.js @@ -1,142 +1,142 @@ -define(['loading', 'libraryMenu', 'globalize', 'cardStyle', 'emby-button', 'emby-checkbox', 'emby-select'], function (loading, libraryMenu, globalize) { - 'use strict'; +import loading from 'loading'; +import libraryMenu from 'libraryMenu'; +import globalize from 'globalize'; +import 'cardStyle'; +import 'emby-button'; +import 'emby-checkbox'; +import 'emby-select'; - function reloadList(page) { - loading.show(); - var promise1 = ApiClient.getAvailablePlugins(); - var promise2 = ApiClient.getInstalledPlugins(); - Promise.all([promise1, promise2]).then(function (responses) { - populateList({ - catalogElement: page.querySelector('#pluginTiles'), - noItemsElement: page.querySelector('#noPlugins'), - availablePlugins: responses[0], - installedPlugins: responses[1] - }); +function reloadList(page) { + loading.show(); + const promise1 = ApiClient.getAvailablePlugins(); + const promise2 = ApiClient.getInstalledPlugins(); + Promise.all([promise1, promise2]).then(function (responses) { + populateList({ + catalogElement: page.querySelector('#pluginTiles'), + noItemsElement: page.querySelector('#noPlugins'), + availablePlugins: responses[0], + installedPlugins: responses[1] }); + }); +} + +function getHeaderText(category) { + category = category.replace(' ', ''); + // TODO: Replace with switch + if (category === 'Channel') { + category = 'Channels'; + } else if (category === 'Theme') { + category = 'Themes'; + } else if (category === 'LiveTV') { + category = 'LiveTV'; + } else if (category === 'ScreenSaver') { + category = 'HeaderScreenSavers'; } - function getHeaderText(category) { - category = category.replace(' ', ''); - if ('Channel' === category) { - category = 'Channels'; - } else if ('Theme' === category) { - category = 'Themes'; - } else if ('LiveTV' === category) { - category = 'HeaderLiveTV'; - } else if ('ScreenSaver' === category) { - category = 'HeaderScreenSavers'; + return globalize.translate(category); +} + +function populateList(options) { + const availablePlugins = options.availablePlugins; + const installedPlugins = options.installedPlugins; + + availablePlugins.forEach(function (plugin, index, array) { + plugin.category = plugin.category || 'General'; + plugin.categoryDisplayName = getHeaderText(plugin.category); + array[index] = plugin; + }); + + availablePlugins.sort(function (a, b) { + if (a.category > b.category) { + return 1; + } else if (b.category > a.category) { + return -1; } + if (a.name > b.name) { + return 1; + } else if (b.name > a.name) { + return -1; + } + return 0; + }); - return globalize.translate(category); - } + let currentCategory = null; + let html = ''; - function populateList(options) { - var availablePlugins = options.availablePlugins; - var installedPlugins = options.installedPlugins; - - var categories = []; - availablePlugins.forEach(function (plugin, index, array) { - plugin.category = plugin.category || 'General'; - plugin.categoryDisplayName = getHeaderText(plugin.category); - array[index] = plugin; - }); - - availablePlugins.sort(function (a, b) { - if (a.category > b.category) { - return 1; - } else if (b.category > a.category) { - return -1; + for (let i = 0; i < availablePlugins.length; i++) { + const plugin = availablePlugins[i]; + const category = plugin.categoryDisplayName; + if (category != currentCategory) { + if (currentCategory) { + html += ''; + html += ''; } - if (a.name > b.name) { - return 1; - } else if (b.name > a.name) { - return -1; - } - return 0; - }); - - var currentCategory = null; - var html = ''; - - for (var i = 0; i < availablePlugins.length; i++) { - var plugin = availablePlugins[i]; - var category = plugin.categoryDisplayName; - if (category != currentCategory) { - if (currentCategory) { - html += ''; - html += ''; - } - html += '
'; - html += '

' + category + '

'; - html += '
'; - currentCategory = category; - } - html += getPluginHtml(plugin, options, installedPlugins); + html += '
'; + html += '

' + category + '

'; + html += '
'; + currentCategory = category; } - html += '
'; - html += '
'; + html += getPluginHtml(plugin, options, installedPlugins); + } + html += '
'; + html += '
'; - if (!availablePlugins.length && options.noItemsElement) { - options.noItemsElement.classList.remove('hide'); - } - - options.catalogElement.innerHTML = html; - loading.hide(); + if (!availablePlugins.length && options.noItemsElement) { + options.noItemsElement.classList.remove('hide'); } - function getPluginHtml(plugin, options, installedPlugins) { - var html = ''; - var href = plugin.externalUrl ? plugin.externalUrl : 'addplugin.html?name=' + encodeURIComponent(plugin.name) + '&guid=' + plugin.guid; + options.catalogElement.innerHTML = html; + loading.hide(); +} - if (options.context) { - href += '&context=' + options.context; - } +function getPluginHtml(plugin, options, installedPlugins) { + let html = ''; + let href = plugin.externalUrl ? plugin.externalUrl : 'addplugin.html?name=' + encodeURIComponent(plugin.name) + '&guid=' + plugin.guid; - var target = plugin.externalUrl ? ' target="_blank"' : ''; - html += "
"; - html += '
'; - html += '
'; - html += '
'; - html += ''; - html += ''; - html += ''; - html += '
'; - html += '
'; - html += "
"; - html += plugin.name; - html += '
'; - var installedPlugin = installedPlugins.filter(function (ip) { - return ip.Id == plugin.guid; - })[0]; - html += "
"; - html += installedPlugin ? globalize.translate('LabelVersionInstalled', installedPlugin.Version) : ' '; - html += '
'; - html += '
'; - html += '
'; - return html += '
'; + if (options.context) { + href += '&context=' + options.context; } - function getTabs() { - return [{ - href: 'installedplugins.html', - name: globalize.translate('TabMyPlugins') - }, { - href: 'availableplugins.html', - name: globalize.translate('TabCatalog') - }, { - href: 'repositories.html', - name: globalize.translate('TabRepositories') - }]; - } + const target = plugin.externalUrl ? ' target="_blank"' : ''; + html += "
"; + html += '
'; + html += '
'; + html += '
'; + html += ''; + html += ''; + html += ''; + html += '
'; + html += '
'; + html += "
"; + html += plugin.name; + html += '
'; + const installedPlugin = installedPlugins.filter(function (ip) { + return ip.Id == plugin.guid; + })[0]; + html += "
"; + html += installedPlugin ? globalize.translate('LabelVersionInstalled', installedPlugin.Version) : ' '; + html += '
'; + html += '
'; + html += '
'; + return html += '
'; +} - window.PluginCatalog = { - renderCatalog: populateList - }; +function getTabs() { + return [{ + href: 'installedplugins.html', + name: globalize.translate('TabMyPlugins') + }, { + href: 'availableplugins.html', + name: globalize.translate('TabCatalog') + }, { + href: 'repositories.html', + name: globalize.translate('TabRepositories') + }]; +} - return function (view, params) { - view.addEventListener('viewshow', function () { - libraryMenu.setTabs('plugins', 1, getTabs); - reloadList(this); - }); - }; -}); +export default function (view) { + view.addEventListener('viewshow', function () { + libraryMenu.setTabs('plugins', 1, getTabs); + reloadList(this); + }); +} diff --git a/src/controllers/dashboard/plugins/installed/index.js b/src/controllers/dashboard/plugins/installed/index.js index d28a04cb2b..cdf21d6a3c 100644 --- a/src/controllers/dashboard/plugins/installed/index.js +++ b/src/controllers/dashboard/plugins/installed/index.js @@ -1,190 +1,193 @@ -define(['loading', 'libraryMenu', 'dom', 'globalize', 'cardStyle', 'emby-button'], function (loading, libraryMenu, dom, globalize) { - 'use strict'; +import loading from 'loading'; +import libraryMenu from 'libraryMenu'; +import dom from 'dom'; +import globalize from 'globalize'; +import 'cardStyle'; +import 'emby-button'; - function deletePlugin(page, uniqueid, name) { - var msg = globalize.translate('UninstallPluginConfirmation', name); +function deletePlugin(page, uniqueid, name) { + const msg = globalize.translate('UninstallPluginConfirmation', name); - require(['confirm'], function (confirm) { - confirm.default({ - title: globalize.translate('UninstallPluginHeader'), - text: msg, - primary: 'delete', - confirmText: globalize.translate('UninstallPluginHeader') - }).then(function () { - loading.show(); - ApiClient.uninstallPlugin(uniqueid).then(function () { - reloadList(page); - }); + import('confirm').then(({default: confirm}) => { + confirm.default({ + title: globalize.translate('HeaderUninstallPlugin'), + text: msg, + primary: 'delete', + confirmText: globalize.translate('HeaderUninstallPlugin') + }).then(function () { + loading.show(); + ApiClient.uninstallPlugin(uniqueid).then(function () { + reloadList(page); }); }); - } + }); +} - function showNoConfigurationMessage() { - Dashboard.alert({ - message: globalize.translate('NoPluginConfigurationMessage') - }); - } +function showNoConfigurationMessage() { + Dashboard.alert({ + message: globalize.translate('MessageNoPluginConfiguration') + }); +} - function showConnectMessage() { - Dashboard.alert({ - message: globalize.translate('MessagePluginConfigurationRequiresLocalAccess') - }); - } +function showConnectMessage() { + Dashboard.alert({ + message: globalize.translate('MessagePluginConfigurationRequiresLocalAccess') + }); +} - function getPluginCardHtml(plugin, pluginConfigurationPages) { - var configPage = pluginConfigurationPages.filter(function (pluginConfigurationPage) { - return pluginConfigurationPage.PluginId == plugin.Id; - })[0]; - var configPageUrl = configPage ? Dashboard.getConfigurationPageUrl(configPage.Name) : null; - var html = ''; - html += "
"; - html += '
'; - html += '
'; - html += '
'; - html += configPageUrl ? '' : ''; +function getPluginCardHtml(plugin, pluginConfigurationPages) { + const configPage = pluginConfigurationPages.filter(function (pluginConfigurationPage) { + return pluginConfigurationPage.PluginId == plugin.Id; + })[0]; + const configPageUrl = configPage ? Dashboard.getPluginUrl(configPage.Name) : null; + let html = ''; + html += "
"; + html += '
'; + html += '
'; + html += '
'; + html += configPageUrl ? '' : ''; + html += '
'; + html += '
'; + + if (configPage || plugin.CanUninstall) { + html += '
'; + html += ''; html += '
'; - html += '
'; + } - if (configPage || plugin.CanUninstall) { - html += '
'; - html += ''; - html += '
'; + html += "
"; + html += configPage && configPage.DisplayName ? configPage.DisplayName : plugin.Name; + html += '
'; + html += "
"; + html += plugin.Version; + html += '
'; + html += '
'; + html += '
'; + html += '
'; + return html; +} + +function renderPlugins(page, plugins) { + ApiClient.getJSON(ApiClient.getUrl('web/configurationpages') + '?pageType=PluginConfiguration').then(function (configPages) { + populateList(page, plugins, configPages); + }); +} + +function populateList(page, plugins, pluginConfigurationPages) { + plugins = plugins.sort(function (plugin1, plugin2) { + if (plugin1.Name > plugin2.Name) { + return 1; } - html += "
"; - html += configPage && configPage.DisplayName ? configPage.DisplayName : plugin.Name; - html += '
'; - html += "
"; - html += plugin.Version; - html += '
'; - html += '
'; - html += '
'; - html += '
'; - return html; - } - - function renderPlugins(page, plugins) { - ApiClient.getJSON(ApiClient.getUrl('web/configurationpages') + '?pageType=PluginConfiguration').then(function (configPages) { - populateList(page, plugins, configPages); - }); - } - - function populateList(page, plugins, pluginConfigurationPages) { - plugins = plugins.sort(function (plugin1, plugin2) { - if (plugin1.Name > plugin2.Name) { - return 1; - } - - return -1; - }); - - var html = plugins.map(function (p) { - return getPluginCardHtml(p, pluginConfigurationPages); - }).join(''); - - var installedPluginsElement = page.querySelector('.installedPlugins'); - installedPluginsElement.removeEventListener('click', onInstalledPluginsClick); - installedPluginsElement.addEventListener('click', onInstalledPluginsClick); - - if (plugins.length) { - installedPluginsElement.classList.add('itemsContainer'); - installedPluginsElement.classList.add('vertical-wrap'); - } else { - html += '
'; - html += '

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

'; - html += '

'; - html += globalize.translate('BrowsePluginCatalogMessage'); - html += '

'; - html += '
'; - } - - installedPluginsElement.innerHTML = html; - loading.hide(); - } - - function showPluginMenu(page, elem) { - var card = dom.parentWithClass(elem, 'card'); - var id = card.getAttribute('data-id'); - var name = card.getAttribute('data-name'); - var removable = card.getAttribute('data-removable'); - var configHref = card.querySelector('.cardContent').getAttribute('href'); - var menuItems = []; - - if (configHref) { - menuItems.push({ - name: globalize.translate('ButtonSettings'), - id: 'open', - icon: 'mode_edit' - }); - } - - if (removable === 'true') { - menuItems.push({ - name: globalize.translate('ButtonUninstall'), - id: 'delete', - icon: 'delete' - }); - } - - require(['actionsheet'], function (actionsheet) { - actionsheet.show({ - items: menuItems, - positionTo: elem, - callback: function (resultId) { - switch (resultId) { - case 'open': - Dashboard.navigate(configHref); - break; - case 'delete': - deletePlugin(page, id, name); - break; - } - } - }); - }); - } - - function reloadList(page) { - loading.show(); - ApiClient.getInstalledPlugins().then(function (plugins) { - renderPlugins(page, plugins); - }); - } - - function getTabs() { - return [{ - href: 'installedplugins.html', - name: globalize.translate('TabMyPlugins') - }, { - href: 'availableplugins.html', - name: globalize.translate('TabCatalog') - }, { - href: 'repositories.html', - name: globalize.translate('TabRepositories') - }]; - } - - function onInstalledPluginsClick(e) { - if (dom.parentWithClass(e.target, 'noConfigPluginCard')) { - showNoConfigurationMessage(); - } else if (dom.parentWithClass(e.target, 'connectModePluginCard')) { - showConnectMessage(); - } else { - var btnCardMenu = dom.parentWithClass(e.target, 'btnCardMenu'); - if (btnCardMenu) { - showPluginMenu(dom.parentWithClass(btnCardMenu, 'page'), btnCardMenu); - } - } - } - - pageIdOn('pageshow', 'pluginsPage', function () { - libraryMenu.setTabs('plugins', 0, getTabs); - reloadList(this); + return -1; }); - window.PluginsPage = { - renderPlugins: renderPlugins - }; + let html = plugins.map(function (p) { + return getPluginCardHtml(p, pluginConfigurationPages); + }).join(''); + + const installedPluginsElement = page.querySelector('.installedPlugins'); + installedPluginsElement.removeEventListener('click', onInstalledPluginsClick); + installedPluginsElement.addEventListener('click', onInstalledPluginsClick); + + if (plugins.length) { + installedPluginsElement.classList.add('itemsContainer'); + installedPluginsElement.classList.add('vertical-wrap'); + } else { + html += '
'; + html += '

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

'; + html += '

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

'; + html += '
'; + } + + installedPluginsElement.innerHTML = html; + loading.hide(); +} + +function showPluginMenu(page, elem) { + const card = dom.parentWithClass(elem, 'card'); + const id = card.getAttribute('data-id'); + const name = card.getAttribute('data-name'); + const removable = card.getAttribute('data-removable'); + const configHref = card.querySelector('.cardContent').getAttribute('href'); + const menuItems = []; + + if (configHref) { + menuItems.push({ + name: globalize.translate('Settings'), + id: 'open', + icon: 'mode_edit' + }); + } + + if (removable === 'true') { + menuItems.push({ + name: globalize.translate('ButtonUninstall'), + id: 'delete', + icon: 'delete' + }); + } + + import('actionsheet').then(({default: actionsheet}) => { + actionsheet.show({ + items: menuItems, + positionTo: elem, + callback: function (resultId) { + switch (resultId) { + case 'open': + Dashboard.navigate(configHref); + break; + case 'delete': + deletePlugin(page, id, name); + break; + } + } + }); + }); +} + +function reloadList(page) { + loading.show(); + ApiClient.getInstalledPlugins().then(function (plugins) { + renderPlugins(page, plugins); + }); +} + +function getTabs() { + return [{ + href: 'installedplugins.html', + name: globalize.translate('TabMyPlugins') + }, { + href: 'availableplugins.html', + name: globalize.translate('TabCatalog') + }, { + href: 'repositories.html', + name: globalize.translate('TabRepositories') + }]; +} + +function onInstalledPluginsClick(e) { + if (dom.parentWithClass(e.target, 'noConfigPluginCard')) { + showNoConfigurationMessage(); + } else if (dom.parentWithClass(e.target, 'connectModePluginCard')) { + showConnectMessage(); + } else { + const btnCardMenu = dom.parentWithClass(e.target, 'btnCardMenu'); + if (btnCardMenu) { + showPluginMenu(dom.parentWithClass(btnCardMenu, 'page'), btnCardMenu); + } + } +} + +pageIdOn('pageshow', 'pluginsPage', function () { + libraryMenu.setTabs('plugins', 0, getTabs); + reloadList(this); }); + +window.PluginsPage = { + renderPlugins: renderPlugins +}; diff --git a/src/controllers/dashboard/plugins/repositories/index.html b/src/controllers/dashboard/plugins/repositories/index.html index ff3406fb95..cea20d5a4a 100644 --- a/src/controllers/dashboard/plugins/repositories/index.html +++ b/src/controllers/dashboard/plugins/repositories/index.html @@ -3,7 +3,7 @@

${TabRepositories}

-
diff --git a/src/controllers/dashboard/plugins/repositories/index.js b/src/controllers/dashboard/plugins/repositories/index.js index 3087cdd927..8d1cbf3164 100644 --- a/src/controllers/dashboard/plugins/repositories/index.js +++ b/src/controllers/dashboard/plugins/repositories/index.js @@ -69,7 +69,7 @@ function getRepositoryHtml(repository) { html += `

${repository.Name}

`; html += `
${repository.Url}
`; html += '
'; - html += ``; + html += ``; html += '
'; return html; @@ -105,7 +105,7 @@ export default function(view, params) { }); view.querySelector('.btnNewRepository').addEventListener('click', () => { - let dialog = dialogHelper.createDialog({ + const dialog = dialogHelper.createDialog({ scrollY: false, size: 'large', modal: false, @@ -127,7 +127,7 @@ export default function(view, params) { html += ``; html += `
${globalize.translate('LabelRepositoryUrlHelp')}
`; html += ''; - html += ``; + html += ``; html += ''; html += ''; diff --git a/src/scheduledtask.html b/src/controllers/dashboard/scheduledtasks/scheduledtask.html similarity index 86% rename from src/scheduledtask.html rename to src/controllers/dashboard/scheduledtasks/scheduledtask.html index c7e82594ce..8d0b2e24e0 100644 --- a/src/scheduledtask.html +++ b/src/controllers/dashboard/scheduledtasks/scheduledtask.html @@ -23,7 +23,7 @@
-

${HeaderAddScheduledTaskTrigger}

+

${ButtonAddScheduledTaskTrigger}

@@ -31,18 +31,18 @@ - +
@@ -73,7 +73,7 @@
'; - html += ''; + html += ''; html += '
'; } @@ -85,21 +84,20 @@ import 'emby-select'; }, // TODO: Replace this mess with date-fns and remove datetime completely getTriggerFriendlyName: function (trigger) { - if ('DailyTrigger' == trigger.Type) { + if (trigger.Type == 'DailyTrigger') { return globalize.translate('DailyAt', ScheduledTaskPage.getDisplayTime(trigger.TimeOfDayTicks)); } - if ('WeeklyTrigger' == trigger.Type) { + if (trigger.Type == 'WeeklyTrigger') { // TODO: The day of week isn't localised as well return globalize.translate('WeeklyAt', trigger.DayOfWeek, ScheduledTaskPage.getDisplayTime(trigger.TimeOfDayTicks)); } - if ('SystemEventTrigger' == trigger.Type && 'WakeFromSleep' == trigger.SystemEvent) { + if (trigger.Type == 'SystemEventTrigger' && trigger.SystemEvent == 'WakeFromSleep') { return globalize.translate('OnWakeFromSleep'); } if (trigger.Type == 'IntervalTrigger') { - const hours = trigger.IntervalTicks / 36e9; if (hours == 0.25) { @@ -138,14 +136,14 @@ import 'emby-select'; }, confirmDeleteTrigger: function (view, index) { import('confirm').then(({default: confirm}) => { - confirm.default(globalize.translate('MessageDeleteTaskTrigger'), globalize.translate('HeaderDeleteTaskTrigger')).then(function () { + confirm(globalize.translate('MessageDeleteTaskTrigger'), globalize.translate('HeaderDeleteTaskTrigger')).then(function () { ScheduledTaskPage.deleteTrigger(view, index); }); }); }, deleteTrigger: function (view, index) { loading.show(); - let id = getParameterByName('id'); + const id = getParameterByName('id'); ApiClient.getScheduledTask(id).then(function (task) { task.Triggers.remove(index); ApiClient.updateScheduledTaskTriggers(task.Id, task.Triggers).then(function () { @@ -213,7 +211,7 @@ import 'emby-select'; export default function (view, params) { function onSubmit(e) { loading.show(); - let id = getParameterByName('id'); + const id = getParameterByName('id'); ApiClient.getScheduledTask(id).then(function (task) { task.Triggers.push(ScheduledTaskPage.getTriggerToAdd(view)); ApiClient.updateScheduledTaskTriggers(task.Id, task.Triggers).then(function () { diff --git a/src/scheduledtasks.html b/src/controllers/dashboard/scheduledtasks/scheduledtasks.html similarity index 100% rename from src/scheduledtasks.html rename to src/controllers/dashboard/scheduledtasks/scheduledtasks.html diff --git a/src/controllers/dashboard/scheduledtasks/scheduledtasks.js b/src/controllers/dashboard/scheduledtasks/scheduledtasks.js index af96f34c15..81a34d4fa6 100644 --- a/src/controllers/dashboard/scheduledtasks/scheduledtasks.js +++ b/src/controllers/dashboard/scheduledtasks/scheduledtasks.js @@ -103,7 +103,7 @@ import 'emby-button'; } function setTaskButtonIcon(button, icon) { - let inner = button.querySelector('.material-icons'); + const inner = button.querySelector('.material-icons'); inner.classList.remove('stop', 'play_arrow'); inner.classList.add(icon); } @@ -160,7 +160,7 @@ import 'emby-button'; $('.divScheduledTasks', view).on('click', '.btnStartTask', function() { const button = this; - let id = button.getAttribute('data-taskid'); + const id = button.getAttribute('data-taskid'); ApiClient.startScheduledTask(id).then(function() { updateTaskButton(button, 'Running'); reloadList(view); @@ -169,7 +169,7 @@ import 'emby-button'; $('.divScheduledTasks', view).on('click', '.btnStopTask', function() { const button = this; - let id = button.getAttribute('data-taskid'); + const id = button.getAttribute('data-taskid'); ApiClient.stopScheduledTask(id).then(function() { updateTaskButton(button, ''); reloadList(view); diff --git a/src/serveractivity.html b/src/controllers/dashboard/serveractivity.html similarity index 100% rename from src/serveractivity.html rename to src/controllers/dashboard/serveractivity.html diff --git a/src/streamingsettings.html b/src/controllers/dashboard/streaming.html similarity index 95% rename from src/streamingsettings.html rename to src/controllers/dashboard/streaming.html index 2d51d9ee77..c3e7e783f2 100644 --- a/src/streamingsettings.html +++ b/src/controllers/dashboard/streaming.html @@ -12,7 +12,7 @@
diff --git a/src/controllers/dashboard/streaming.js b/src/controllers/dashboard/streaming.js index c54fbea472..5db888dfc1 100644 --- a/src/controllers/dashboard/streaming.js +++ b/src/controllers/dashboard/streaming.js @@ -27,7 +27,7 @@ import globalize from 'globalize'; name: globalize.translate('Transcoding') }, { href: 'playbackconfiguration.html', - name: globalize.translate('TabResumeSettings') + name: globalize.translate('ButtonResume') }, { href: 'streamingsettings.html', name: globalize.translate('TabStreaming') diff --git a/src/useredit.html b/src/controllers/dashboard/users/useredit.html similarity index 98% rename from src/useredit.html rename to src/controllers/dashboard/users/useredit.html index c3a613bed4..f4dc8c096b 100644 --- a/src/useredit.html +++ b/src/controllers/dashboard/users/useredit.html @@ -11,10 +11,10 @@

${ButtonEditOtherUserPreferences} @@ -192,7 +192,7 @@

@@ -59,7 +59,7 @@
diff --git a/src/controllers/dashboard/users/userlibraryaccess.js b/src/controllers/dashboard/users/userlibraryaccess.js index df42733f0c..146777a0db 100644 --- a/src/controllers/dashboard/users/userlibraryaccess.js +++ b/src/controllers/dashboard/users/userlibraryaccess.js @@ -18,7 +18,7 @@ import globalize from 'globalize'; for (let i = 0, length = mediaFolders.length; i < length; i++) { const folder = mediaFolders[i]; - const isChecked = user.Policy.EnableAllFolders || -1 != user.Policy.EnabledFolders.indexOf(folder.Id); + const isChecked = user.Policy.EnableAllFolders || user.Policy.EnabledFolders.indexOf(folder.Id) != -1; const checkedAttribute = isChecked ? ' checked="checked"' : ''; html += ''; } @@ -32,12 +32,12 @@ import globalize from 'globalize'; function loadChannels(page, user, channels) { let html = ''; - html += '

' + globalize.translate('HeaderChannels') + '

'; + html += '

' + globalize.translate('Channels') + '

'; html += '
'; for (let i = 0, length = channels.length; i < length; i++) { const folder = channels[i]; - const isChecked = user.Policy.EnableAllChannels || -1 != user.Policy.EnabledChannels.indexOf(folder.Id); + const isChecked = user.Policy.EnableAllChannels || user.Policy.EnabledChannels.indexOf(folder.Id) != -1; const checkedAttribute = isChecked ? ' checked="checked"' : ''; html += ''; } @@ -61,7 +61,7 @@ import globalize from 'globalize'; for (let i = 0, length = devices.length; i < length; i++) { const device = devices[i]; - const checkedAttribute = user.Policy.EnableAllDevices || -1 != user.Policy.EnabledDevices.indexOf(device.Id) ? ' checked="checked"' : ''; + const checkedAttribute = user.Policy.EnableAllDevices || user.Policy.EnabledDevices.indexOf(device.Id) != -1 ? ' checked="checked"' : ''; html += ''; } diff --git a/src/usernew.html b/src/controllers/dashboard/users/usernew.html similarity index 96% rename from src/usernew.html rename to src/controllers/dashboard/users/usernew.html index fac036aa8d..67f1f61ebc 100644 --- a/src/usernew.html +++ b/src/controllers/dashboard/users/usernew.html @@ -4,7 +4,7 @@
-

${HeaderAddUser}

+

${ButtonAddUser}

${Help}
@@ -49,7 +49,7 @@
@@ -40,7 +40,7 @@

${HeaderAccessSchedule}

-
@@ -51,7 +51,7 @@
diff --git a/src/controllers/dashboard/users/userparentalcontrol.js b/src/controllers/dashboard/users/userparentalcontrol.js index 3fba3fc2b2..efe00aec0b 100644 --- a/src/controllers/dashboard/users/userparentalcontrol.js +++ b/src/controllers/dashboard/users/userparentalcontrol.js @@ -40,25 +40,25 @@ import 'paper-icon-button-light'; function loadUnratedItems(page, user) { const items = [{ - name: globalize.translate('OptionBlockBooks'), + name: globalize.translate('Books'), value: 'Book' }, { - name: globalize.translate('OptionBlockChannelContent'), + name: globalize.translate('Channels'), value: 'ChannelContent' }, { - name: globalize.translate('OptionBlockLiveTvChannels'), + name: globalize.translate('LiveTV'), value: 'LiveTvChannel' }, { - name: globalize.translate('OptionBlockMovies'), + name: globalize.translate('Movies'), value: 'Movie' }, { - name: globalize.translate('OptionBlockMusic'), + name: globalize.translate('Music'), value: 'Music' }, { - name: globalize.translate('OptionBlockTrailers'), + name: globalize.translate('Trailers'), value: 'Trailer' }, { - name: globalize.translate('OptionBlockTvShows'), + name: globalize.translate('Shows'), value: 'Series' }]; let html = ''; @@ -67,7 +67,7 @@ import 'paper-icon-button-light'; for (let i = 0, length = items.length; i < length; i++) { const item = items[i]; - const checkedAttribute = -1 != user.Policy.BlockUnratedItems.indexOf(item.value) ? ' checked="checked"' : ''; + const checkedAttribute = user.Policy.BlockUnratedItems.indexOf(item.value) != -1 ? ' checked="checked"' : ''; html += ''; } @@ -201,7 +201,7 @@ import 'paper-icon-button-light'; }).then(function (updatedSchedule) { const schedules = getSchedulesFromPage(page); - if (-1 == index) { + if (index == -1) { index = schedules.length; } @@ -228,14 +228,13 @@ import 'paper-icon-button-light'; } function showBlockedTagPopup(page) { - import('prompt').then(({default: prompt}) => { prompt({ label: globalize.translate('LabelTag') }).then(function (value) { const tags = getBlockedTagsFromPage(page); - if (-1 == tags.indexOf(value)) { + if (tags.indexOf(value) == -1) { tags.push(value); loadBlockedTags(page, tags); } diff --git a/src/userpassword.html b/src/controllers/dashboard/users/userpassword.html similarity index 93% rename from src/userpassword.html rename to src/controllers/dashboard/users/userpassword.html index 119a0212de..285533cc4c 100644 --- a/src/userpassword.html +++ b/src/controllers/dashboard/users/userpassword.html @@ -9,10 +9,10 @@
@@ -29,9 +29,9 @@

- +
@@ -58,7 +58,7 @@
diff --git a/src/controllers/dashboard/users/userprofilespage.js b/src/controllers/dashboard/users/userprofilespage.js index dbdec4f752..bfd8d96d92 100644 --- a/src/controllers/dashboard/users/userprofilespage.js +++ b/src/controllers/dashboard/users/userprofilespage.js @@ -18,7 +18,7 @@ import 'flexStyles'; confirm({ title: globalize.translate('DeleteUser'), text: msg, - confirmText: globalize.translate('ButtonDelete'), + confirmText: globalize.translate('Delete'), primary: 'delete' }).then(function () { loading.show(); @@ -50,7 +50,7 @@ import 'flexStyles'; icon: 'person' }); menuItems.push({ - name: globalize.translate('ButtonDelete'), + name: globalize.translate('Delete'), id: 'delete', icon: 'delete' }); @@ -129,7 +129,7 @@ import 'flexStyles'; html += ''; html += '
'; const lastSeen = getLastSeenText(user.LastActivityDate); - html += '' != lastSeen ? lastSeen : ' '; + html += lastSeen != '' ? lastSeen : ' '; html += '
'; html += ''; html += ''; @@ -155,102 +155,12 @@ import 'flexStyles'; page.querySelector('.localUsers').innerHTML = getUserSectionHtml(users, true); } - function showPendingUserMenu(elem) { - const menuItems = []; - menuItems.push({ - name: globalize.translate('ButtonCancel'), - id: 'delete', - icon: 'delete' - }); - - import('actionsheet').then(({default: actionsheet}) => { - const card = dom.parentWithClass(elem, 'card'); - const page = dom.parentWithClass(card, 'page'); - const id = card.getAttribute('data-id'); - actionsheet.show({ - items: menuItems, - positionTo: card, - callback: function (menuItemId) { - switch (menuItemId) { - case 'delete': - cancelAuthorization(page, id); - } - } - }); - }); - } - - function getPendingUserHtml(user) { - let html = ''; - html += "
"; - html += '
'; - html += ''; - html += '
'; - html += '
'; - html += ''; - html += '
'; - html += '
'; - html += user.UserName; - html += '
'; - html += '
'; - html += '
'; - return html + '
'; - } - - function renderPendingGuests(page, users) { - if (users.length) { - page.querySelector('.sectionPendingGuests').classList.remove('hide'); - } else { - page.querySelector('.sectionPendingGuests').classList.add('hide'); - } - - page.querySelector('.pending').innerHTML = users.map(getPendingUserHtml).join(''); - } - - // TODO cvium: maybe reuse for invitation system - function cancelAuthorization(page, id) { - loading.show(); - ApiClient.ajax({ - type: 'DELETE', - url: ApiClient.getUrl('Connect/Pending', { - Id: id - }) - }).then(function () { - loadData(page); - }); - } - function loadData(page) { loading.show(); ApiClient.getUsers().then(function (users) { renderUsers(page, users); loading.hide(); }); - // TODO cvium - renderPendingGuests(page, []); - // ApiClient.getJSON(ApiClient.getUrl("Connect/Pending")).then(function (pending) { - // - // }); - } - - function showInvitePopup(page) { - import('components/guestinviter/guestinviter').then(({default: guestinviter}) => { - guestinviter.show().then(function () { - loadData(page); - }); - }); } pageIdOn('pageinit', 'userProfilesPage', function () { @@ -265,14 +175,8 @@ import 'flexStyles'; showUserMenu(btnUserMenu); } }); - page.querySelector('.pending').addEventListener('click', function (e__r) { - const btnUserMenu = dom.parentWithClass(e__r.target, 'btnUserMenu'); - - if (btnUserMenu) { - showPendingUserMenu(btnUserMenu); - } - }); }); + pageIdOn('pagebeforeshow', 'userProfilesPage', function () { loadData(this); }); diff --git a/src/edititemmetadata.html b/src/controllers/edititemmetadata.html similarity index 100% rename from src/edititemmetadata.html rename to src/controllers/edititemmetadata.html diff --git a/src/controllers/edititemmetadata.js b/src/controllers/edititemmetadata.js index 1a3647a758..57c72dda28 100644 --- a/src/controllers/edititemmetadata.js +++ b/src/controllers/edititemmetadata.js @@ -1,31 +1,30 @@ -define(['loading', 'scripts/editorsidebar'], function (loading) { - 'use strict'; +import loading from 'loading'; +import 'scripts/editorsidebar'; - function reload(context, itemId) { - loading.show(); +function reload(context, itemId) { + loading.show(); - if (itemId) { - require(['metadataEditor'], function ({default: metadataEditor}) { - metadataEditor.embed(context.querySelector('.editPageInnerContent'), itemId, ApiClient.serverInfo().Id); - }); - } else { - context.querySelector('.editPageInnerContent').innerHTML = ''; - loading.hide(); - } + if (itemId) { + import('metadataEditor').then(({ default: metadataEditor }) => { + metadataEditor.embed(context.querySelector('.editPageInnerContent'), itemId, ApiClient.serverInfo().Id); + }); + } else { + context.querySelector('.editPageInnerContent').innerHTML = ''; + loading.hide(); } +} - return function (view, params) { - view.addEventListener('viewshow', function () { - reload(this, MetadataEditor.getCurrentItemId()); - }); - MetadataEditor.setCurrentItemId(null); - view.querySelector('.libraryTree').addEventListener('itemclicked', function (event) { - var data = event.detail; +export default function (view, params) { + view.addEventListener('viewshow', function () { + reload(this, MetadataEditor.getCurrentItemId()); + }); + MetadataEditor.setCurrentItemId(null); + view.querySelector('.libraryTree').addEventListener('itemclicked', function (event) { + var data = event.detail; - if (data.id != MetadataEditor.getCurrentItemId()) { - MetadataEditor.setCurrentItemId(data.id); - reload(view, data.id); - } - }); - }; -}); + if (data.id != MetadataEditor.getCurrentItemId()) { + MetadataEditor.setCurrentItemId(data.id); + reload(view, data.id); + } + }); +} diff --git a/src/controllers/favorites.js b/src/controllers/favorites.js index b4c7936239..fc37f4eef4 100644 --- a/src/controllers/favorites.js +++ b/src/controllers/favorites.js @@ -1,5 +1,14 @@ -define(['appRouter', 'cardBuilder', 'dom', 'globalize', 'connectionManager', 'apphost', 'layoutManager', 'focusManager', 'emby-itemscontainer', 'emby-scroller'], function (appRouter, cardBuilder, dom, globalize, connectionManager, appHost, layoutManager, focusManager) { - 'use strict'; +import appRouter from 'appRouter'; +import cardBuilder from 'cardBuilder'; +import dom from 'dom'; +import globalize from 'globalize'; +import appHost from 'apphost'; +import layoutManager from 'layoutManager'; +import focusManager from 'focusManager'; +import 'emby-itemscontainer'; +import 'emby-scroller'; + +/* eslint-disable indent */ function enableScrollX() { return true; @@ -19,7 +28,7 @@ define(['appRouter', 'cardBuilder', 'dom', 'globalize', 'connectionManager', 'ap function getSections() { return [{ - name: 'HeaderFavoriteMovies', + name: 'Movies', types: 'Movie', shape: getPosterShape(), showTitle: true, @@ -28,7 +37,7 @@ define(['appRouter', 'cardBuilder', 'dom', 'globalize', 'connectionManager', 'ap overlayText: false, centerText: true }, { - name: 'HeaderFavoriteShows', + name: 'Shows', types: 'Series', shape: getPosterShape(), showTitle: true, @@ -37,7 +46,7 @@ define(['appRouter', 'cardBuilder', 'dom', 'globalize', 'connectionManager', 'ap overlayText: false, centerText: true }, { - name: 'HeaderFavoriteEpisodes', + name: 'Episodes', types: 'Episode', shape: getThumbShape(), preferThumb: false, @@ -47,7 +56,7 @@ define(['appRouter', 'cardBuilder', 'dom', 'globalize', 'connectionManager', 'ap overlayText: false, centerText: true }, { - name: 'HeaderFavoriteVideos', + name: 'Videos', types: 'Video', shape: getThumbShape(), preferThumb: true, @@ -56,7 +65,7 @@ define(['appRouter', 'cardBuilder', 'dom', 'globalize', 'connectionManager', 'ap overlayText: false, centerText: true }, { - name: 'HeaderFavoriteCollections', + name: 'Collections', types: 'BoxSet', shape: getPosterShape(), showTitle: true, @@ -64,7 +73,7 @@ define(['appRouter', 'cardBuilder', 'dom', 'globalize', 'connectionManager', 'ap overlayText: false, centerText: true }, { - name: 'HeaderFavoritePlaylists', + name: 'Playlists', types: 'Playlist', shape: getSquareShape(), preferThumb: false, @@ -75,7 +84,7 @@ define(['appRouter', 'cardBuilder', 'dom', 'globalize', 'connectionManager', 'ap overlayPlayButton: true, coverImage: true }, { - name: 'HeaderFavoritePeople', + name: 'People', types: 'Person', shape: getPosterShape(), preferThumb: false, @@ -86,7 +95,7 @@ define(['appRouter', 'cardBuilder', 'dom', 'globalize', 'connectionManager', 'ap overlayPlayButton: true, coverImage: true }, { - name: 'HeaderFavoriteArtists', + name: 'Artists', types: 'MusicArtist', shape: getSquareShape(), preferThumb: false, @@ -97,7 +106,7 @@ define(['appRouter', 'cardBuilder', 'dom', 'globalize', 'connectionManager', 'ap overlayPlayButton: true, coverImage: true }, { - name: 'HeaderFavoriteAlbums', + name: 'Albums', types: 'MusicAlbum', shape: getSquareShape(), preferThumb: false, @@ -108,7 +117,7 @@ define(['appRouter', 'cardBuilder', 'dom', 'globalize', 'connectionManager', 'ap overlayPlayButton: true, coverImage: true }, { - name: 'HeaderFavoriteSongs', + name: 'Songs', types: 'Audio', shape: getSquareShape(), preferThumb: false, @@ -120,7 +129,7 @@ define(['appRouter', 'cardBuilder', 'dom', 'globalize', 'connectionManager', 'ap action: 'instantmix', coverImage: true }, { - name: 'HeaderFavoriteBooks', + name: 'Books', types: 'Book', shape: getPosterShape(), showTitle: true, @@ -133,8 +142,8 @@ define(['appRouter', 'cardBuilder', 'dom', 'globalize', 'connectionManager', 'ap function getFetchDataFn(section) { return function () { - var apiClient = this.apiClient; - var options = { + const apiClient = this.apiClient; + const options = { SortBy: (section.types, 'SeriesName,SortName'), SortOrder: 'Ascending', Filters: 'IsFavorite', @@ -145,13 +154,13 @@ define(['appRouter', 'cardBuilder', 'dom', 'globalize', 'connectionManager', 'ap EnableTotalRecordCount: false }; options.Limit = 20; - var userId = apiClient.getCurrentUserId(); + const userId = apiClient.getCurrentUserId(); - if ('MusicArtist' === section.types) { + if (section.types === 'MusicArtist') { return apiClient.getArtists(userId, options); } - if ('Person' === section.types) { + if (section.types === 'Person') { return apiClient.getPeople(userId, options); } @@ -170,17 +179,16 @@ define(['appRouter', 'cardBuilder', 'dom', 'globalize', 'connectionManager', 'ap function getItemsHtmlFn(section) { return function (items) { - var supportsImageAnalysis = appHost.supports('imageanalysis'); - var cardLayout = (appHost.preferVisualCards || supportsImageAnalysis) && section.autoCardLayout && section.showTitle; + let cardLayout = appHost.preferVisualCards && section.autoCardLayout && section.showTitle; cardLayout = false; - var serverId = this.apiClient.serverId(); - var leadingButtons = layoutManager.tv ? [{ + const serverId = this.apiClient.serverId(); + const leadingButtons = layoutManager.tv ? [{ name: globalize.translate('All'), id: 'more', icon: 'favorite', routeUrl: getRouteUrl(section, serverId) }] : null; - var lines = 0; + let lines = 0; if (section.showTitle) { lines++; @@ -199,7 +207,7 @@ define(['appRouter', 'cardBuilder', 'dom', 'globalize', 'connectionManager', 'ap preferThumb: section.preferThumb, shape: section.shape, centerText: section.centerText && !cardLayout, - overlayText: false !== section.overlayText, + overlayText: section.overlayText !== false, showTitle: section.showTitle, showYear: section.showYear, showParentTitle: section.showParentTitle, @@ -216,23 +224,12 @@ define(['appRouter', 'cardBuilder', 'dom', 'globalize', 'connectionManager', 'ap }; } - function FavoritesTab(view, params) { - this.view = view; - this.params = params; - this.apiClient = connectionManager.currentApiClient(); - this.sectionsContainer = view.querySelector('.sections'); - createSections(this, this.sectionsContainer, this.apiClient); - } - function createSections(instance, elem, apiClient) { - var i; - var length; - var sections = getSections(); - var html = ''; + const sections = getSections(); + let html = ''; - for (i = 0, length = sections.length; i < length; i++) { - var section = sections[i]; - var sectionClass = 'verticalSection'; + for (const section of sections) { + let sectionClass = 'verticalSection'; if (!section.showTitle) { sectionClass += ' verticalSection-extrabottompadding'; @@ -258,23 +255,32 @@ define(['appRouter', 'cardBuilder', 'dom', 'globalize', 'connectionManager', 'ap } elem.innerHTML = html; - var elems = elem.querySelectorAll('.itemsContainer'); + const elems = elem.querySelectorAll('.itemsContainer'); - for (i = 0, length = elems.length; i < length; i++) { - var itemsContainer = elems[i]; + for (let i = 0, length = elems.length; i < length; i++) { + const itemsContainer = elems[i]; itemsContainer.fetchData = getFetchDataFn(sections[i]).bind(instance); itemsContainer.getItemsHtml = getItemsHtmlFn(sections[i]).bind(instance); itemsContainer.parentContainer = dom.parentWithClass(itemsContainer, 'verticalSection'); } } - FavoritesTab.prototype.onResume = function (options) { - var promises = (this.apiClient, []); - var view = this.view; - var elems = this.sectionsContainer.querySelectorAll('.itemsContainer'); +class FavoritesTab { + constructor(view, params) { + this.view = view; + this.params = params; + this.apiClient = window.connectionManager.currentApiClient(); + this.sectionsContainer = view.querySelector('.sections'); + createSections(this, this.sectionsContainer, this.apiClient); + } - for (var i = 0, length = elems.length; i < length; i++) { - promises.push(elems[i].resume(options)); + onResume(options) { + const promises = (this.apiClient, []); + const view = this.view; + const elems = this.sectionsContainer.querySelectorAll('.itemsContainer'); + + for (const elem of elems) { + promises.push(elem.resume(options)); } Promise.all(promises).then(function () { @@ -282,30 +288,32 @@ define(['appRouter', 'cardBuilder', 'dom', 'globalize', 'connectionManager', 'ap focusManager.autoFocus(view); } }); - }; + } - FavoritesTab.prototype.onPause = function () { - var elems = this.sectionsContainer.querySelectorAll('.itemsContainer'); + onPause() { + const elems = this.sectionsContainer.querySelectorAll('.itemsContainer'); - for (var i = 0, length = elems.length; i < length; i++) { - elems[i].pause(); + for (const elem of elems) { + elem.pause(); } - }; + } - FavoritesTab.prototype.destroy = function () { + destroy() { this.view = null; this.params = null; this.apiClient = null; - var elems = this.sectionsContainer.querySelectorAll('.itemsContainer'); + const elems = this.sectionsContainer.querySelectorAll('.itemsContainer'); - for (var i = 0, length = elems.length; i < length; i++) { - elems[i].fetchData = null; - elems[i].getItemsHtml = null; - elems[i].parentContainer = null; + for (const elem of elems) { + elem.fetchData = null; + elem.getItemsHtml = null; + elem.parentContainer = null; } this.sectionsContainer = null; - }; + } +} - return FavoritesTab; -}); +export default FavoritesTab; + +/* eslint-enable indent */ diff --git a/src/home.html b/src/controllers/home.html similarity index 100% rename from src/home.html rename to src/controllers/home.html diff --git a/src/controllers/home.js b/src/controllers/home.js index 9a4cea2227..72e326e46b 100644 --- a/src/controllers/home.js +++ b/src/controllers/home.js @@ -1,7 +1,33 @@ -define(['tabbedView', 'globalize', 'require', 'emby-tabs', 'emby-button', 'emby-scroller'], function (TabbedView, globalize, require) { - 'use strict'; +import TabbedView from 'tabbedView'; +import globalize from 'globalize'; +import 'emby-tabs'; +import 'emby-button'; +import 'emby-scroller'; - function getTabs() { +class HomeView extends TabbedView { + constructor(view, params) { + super(view, params); + } + + setTitle() { + Emby.Page.setTitle(null); + } + + onPause() { + super.onPause(this); + document.querySelector('.skinHeader').classList.remove('noHomeButtonHeader'); + } + + onResume(options) { + super.onResume(this, options); + document.querySelector('.skinHeader').classList.add('noHomeButtonHeader'); + } + + getDefaultTabIndex() { + return 0; + } + + getTabs() { return [{ name: globalize.translate('Home') }, { @@ -9,35 +35,25 @@ define(['tabbedView', 'globalize', 'require', 'emby-tabs', 'emby-button', 'emby- }]; } - function getDefaultTabIndex() { - return 0; - } - - function getRequirePromise(deps) { - return new Promise(function (resolve, reject) { - require(deps, resolve); - }); - } - - function getTabController(index) { - if (null == index) { + getTabController(index) { + if (index == null) { throw new Error('index cannot be null'); } - var depends = []; + let depends = ''; switch (index) { case 0: - depends.push('controllers/hometab'); + depends = 'controllers/hometab'; break; case 1: - depends.push('controllers/favorites'); + depends = 'controllers/favorites'; } - var instance = this; - return getRequirePromise(depends).then(function (controllerFactory) { - var controller = instance.tabControllers[index]; + const instance = this; + return import(depends).then(({ default: controllerFactory }) => { + let controller = instance.tabControllers[index]; if (!controller) { controller = new controllerFactory(instance.view.querySelector(".tabContent[data-index='" + index + "']"), instance.params); @@ -47,29 +63,6 @@ define(['tabbedView', 'globalize', 'require', 'emby-tabs', 'emby-button', 'emby- return controller; }); } +} - function HomeView(view, params) { - TabbedView.call(this, view, params); - } - - Object.assign(HomeView.prototype, TabbedView.prototype); - HomeView.prototype.getTabs = getTabs; - HomeView.prototype.getDefaultTabIndex = getDefaultTabIndex; - HomeView.prototype.getTabController = getTabController; - - HomeView.prototype.setTitle = function () { - Emby.Page.setTitle(null); - }; - - HomeView.prototype.onPause = function () { - TabbedView.prototype.onPause.call(this); - document.querySelector('.skinHeader').classList.remove('noHomeButtonHeader'); - }; - - HomeView.prototype.onResume = function (options) { - TabbedView.prototype.onResume.call(this, options); - document.querySelector('.skinHeader').classList.add('noHomeButtonHeader'); - }; - - return HomeView; -}); +export default HomeView; diff --git a/src/controllers/hometab.js b/src/controllers/hometab.js index 8e2a1f92e7..ff56e08d14 100644 --- a/src/controllers/hometab.js +++ b/src/controllers/hometab.js @@ -1,27 +1,20 @@ -define(['userSettings', 'loading', 'connectionManager', 'apphost', 'layoutManager', 'focusManager', 'homeSections', 'emby-itemscontainer'], function (userSettings, loading, connectionManager, appHost, layoutManager, focusManager, homeSections) { - 'use strict'; +import * as userSettings from 'userSettings'; +import loading from 'loading'; +import focusManager from 'focusManager'; +import homeSections from 'homeSections'; +import 'emby-itemscontainer'; - function HomeTab(view, params) { +class HomeTab { + constructor(view, params) { this.view = view; this.params = params; - this.apiClient = connectionManager.currentApiClient(); + this.apiClient = window.connectionManager.currentApiClient(); this.sectionsContainer = view.querySelector('.sections'); view.querySelector('.sections').addEventListener('settingschange', onHomeScreenSettingsChanged.bind(this)); } - - function onHomeScreenSettingsChanged() { - this.sectionsRendered = false; - - if (!this.paused) { - this.onResume({ - refresh: true - }); - } - } - - HomeTab.prototype.onResume = function (options) { + onResume(options) { if (this.sectionsRendered) { - var sectionsContainer = this.sectionsContainer; + const sectionsContainer = this.sectionsContainer; if (sectionsContainer) { return homeSections.resume(sectionsContainer, options); @@ -31,8 +24,8 @@ define(['userSettings', 'loading', 'connectionManager', 'apphost', 'layoutManage } loading.show(); - var view = this.view; - var apiClient = this.apiClient; + const view = this.view; + const apiClient = this.apiClient; this.destroyHomeSections(); this.sectionsRendered = true; return apiClient.getCurrentUser().then(function (user) { @@ -44,31 +37,38 @@ define(['userSettings', 'loading', 'connectionManager', 'apphost', 'layoutManage loading.hide(); }); }); - }; - - HomeTab.prototype.onPause = function () { - var sectionsContainer = this.sectionsContainer; + } + onPause() { + const sectionsContainer = this.sectionsContainer; if (sectionsContainer) { homeSections.pause(sectionsContainer); } - }; - - HomeTab.prototype.destroy = function () { + } + destroy() { this.view = null; this.params = null; this.apiClient = null; this.destroyHomeSections(); this.sectionsContainer = null; - }; - - HomeTab.prototype.destroyHomeSections = function () { - var sectionsContainer = this.sectionsContainer; + } + destroyHomeSections() { + const sectionsContainer = this.sectionsContainer; if (sectionsContainer) { homeSections.destroySections(sectionsContainer); } - }; + } +} - return HomeTab; -}); +function onHomeScreenSettingsChanged() { + this.sectionsRendered = false; + + if (!this.paused) { + this.onResume({ + refresh: true + }); + } +} + +export default HomeTab; diff --git a/src/controllers/itemDetails/index.html b/src/controllers/itemDetails/index.html index bc6bc5f5a4..b2604e0341 100644 --- a/src/controllers/itemDetails/index.html +++ b/src/controllers/itemDetails/index.html @@ -7,24 +7,24 @@
-
-
+
+
-
+
- -
-
-
+
@@ -139,22 +139,22 @@

-
- +
+
-
-

${HeaderSchedule}

+
+

${Schedule}

-

${HeaderNextUp}

+

${NextUp}

@@ -193,7 +193,7 @@
-

${HeaderCastCrew}

+

${HeaderCastAndCrew}

@@ -205,14 +205,14 @@
-

${HeaderSpecialFeatures}

+

${SpecialFeatures}

-

${HeaderMusicVideos}

+

${MusicVideos}

diff --git a/src/controllers/itemDetails/index.js b/src/controllers/itemDetails/index.js index 011bc4ac92..c68fe15feb 100644 --- a/src/controllers/itemDetails/index.js +++ b/src/controllers/itemDetails/index.js @@ -1,1919 +1,1970 @@ -define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSettings', 'cardBuilder', 'datetime', 'mediaInfo', 'backdrop', 'listView', 'itemContextMenu', 'itemHelper', 'dom', 'indicators', 'imageLoader', 'libraryMenu', 'globalize', 'browser', 'events', 'playbackManager', 'scrollStyles', 'emby-itemscontainer', 'emby-checkbox', 'emby-button', 'emby-playstatebutton', 'emby-ratingbutton', 'emby-scroller', 'emby-select'], function (loading, appRouter, layoutManager, connectionManager, userSettings, cardBuilder, datetime, mediaInfo, backdrop, listView, itemContextMenu, itemHelper, dom, indicators, imageLoader, libraryMenu, globalize, browser, events, playbackManager) { - 'use strict'; +import appHost from 'apphost'; +import loading from 'loading'; +import appRouter from 'appRouter'; +import itemShortcuts from 'itemShortcuts'; +import layoutManager from 'layoutManager'; +import * as userSettings from 'userSettings'; +import cardBuilder from 'cardBuilder'; +import datetime from 'datetime'; +import mediaInfo from 'mediaInfo'; +import backdrop from 'backdrop'; +import listView from 'listView'; +import itemContextMenu from 'itemContextMenu'; +import itemHelper from 'itemHelper'; +import dom from 'dom'; +import indicators from 'indicators'; +import imageLoader from 'imageLoader'; +import libraryMenu from 'libraryMenu'; +import globalize from 'globalize'; +import browser from 'browser'; +import events from 'events'; +import playbackManager from 'playbackManager'; +import 'scrollStyles'; +import 'emby-itemscontainer'; +import 'emby-checkbox'; +import 'emby-button'; +import 'emby-playstatebutton'; +import 'emby-ratingbutton'; +import 'emby-scroller'; +import 'emby-select'; - function getPromise(apiClient, params) { - var id = params.id; +function getPromise(apiClient, params) { + const id = params.id; - if (id) { - return apiClient.getItem(apiClient.getCurrentUserId(), id); - } - - if (params.seriesTimerId) { - return apiClient.getLiveTvSeriesTimer(params.seriesTimerId); - } - - if (params.genre) { - return apiClient.getGenre(params.genre, apiClient.getCurrentUserId()); - } - - if (params.musicgenre) { - return apiClient.getMusicGenre(params.musicgenre, apiClient.getCurrentUserId()); - } - - if (params.musicartist) { - return apiClient.getArtist(params.musicartist, apiClient.getCurrentUserId()); - } - - throw new Error('Invalid request'); + if (id) { + return apiClient.getItem(apiClient.getCurrentUserId(), id); } - function hideAll(page, className, show) { - for (const elem of page.querySelectorAll('.' + className)) { - if (show) { - elem.classList.remove('hide'); - } else { - elem.classList.add('hide'); - } + if (params.seriesTimerId) { + return apiClient.getLiveTvSeriesTimer(params.seriesTimerId); + } + + if (params.genre) { + return apiClient.getGenre(params.genre, apiClient.getCurrentUserId()); + } + + if (params.musicgenre) { + return apiClient.getMusicGenre(params.musicgenre, apiClient.getCurrentUserId()); + } + + if (params.musicartist) { + return apiClient.getArtist(params.musicartist, apiClient.getCurrentUserId()); + } + + throw new Error('Invalid request'); +} + +function hideAll(page, className, show) { + for (const elem of page.querySelectorAll('.' + className)) { + if (show) { + elem.classList.remove('hide'); + } else { + elem.classList.add('hide'); } } +} - function getContextMenuOptions(item, user, button) { - var options = { - item: item, - open: false, - play: false, - playAllFromHere: false, - queueAllFromHere: false, - positionTo: button, - cancelTimer: false, - record: false, - deleteItem: item.CanDelete === true, - shuffle: false, - instantMix: false, - user: user, - share: true - }; +function getContextMenuOptions(item, user, button) { + return { + item: item, + open: false, + play: false, + playAllFromHere: false, + queueAllFromHere: false, + positionTo: button, + cancelTimer: false, + record: false, + deleteItem: item.CanDelete === true, + shuffle: false, + instantMix: false, + user: user, + share: true + }; +} - return options; - } +function getProgramScheduleHtml(items) { + let html = ''; - function getProgramScheduleHtml(items) { - var html = ''; + html += '
'; + html += listView.getListViewHtml({ + items: items, + enableUserDataButtons: false, + image: true, + imageSource: 'channel', + showProgramDateTime: true, + showChannel: false, + mediaInfo: false, + action: 'none', + moreButton: false, + recordButton: false + }); - html += '
'; - html += listView.getListViewHtml({ - items: items, - enableUserDataButtons: false, - image: true, - imageSource: 'channel', - showProgramDateTime: true, - showChannel: false, - mediaInfo: false, - action: 'none', - moreButton: false, - recordButton: false - }); + html += '
'; - html += '
'; + return html; +} - return html; - } - - function renderSeriesTimerSchedule(page, apiClient, seriesTimerId) { - apiClient.getLiveTvTimers({ - UserId: apiClient.getCurrentUserId(), - ImageTypeLimit: 1, - EnableImageTypes: 'Primary,Backdrop,Thumb', - SortBy: 'StartDate', - EnableTotalRecordCount: false, - EnableUserData: false, - SeriesTimerId: seriesTimerId, - Fields: 'ChannelInfo,ChannelImage' - }).then(function (result) { - if (result.Items.length && result.Items[0].SeriesTimerId != seriesTimerId) { - result.Items = []; - } - - var html = getProgramScheduleHtml(result.Items); - var scheduleTab = page.querySelector('.seriesTimerSchedule'); - scheduleTab.innerHTML = html; - imageLoader.lazyChildren(scheduleTab); - }); - } - - function renderTimerEditor(page, item, apiClient, user) { - if ('Recording' !== item.Type || !user.Policy.EnableLiveTvManagement || !item.TimerId || 'InProgress' !== item.Status) { - return void hideAll(page, 'btnCancelTimer'); +function renderSeriesTimerSchedule(page, apiClient, seriesTimerId) { + apiClient.getLiveTvTimers({ + UserId: apiClient.getCurrentUserId(), + ImageTypeLimit: 1, + EnableImageTypes: 'Primary,Backdrop,Thumb', + SortBy: 'StartDate', + EnableTotalRecordCount: false, + EnableUserData: false, + SeriesTimerId: seriesTimerId, + Fields: 'ChannelInfo,ChannelImage' + }).then(function (result) { + if (result.Items.length && result.Items[0].SeriesTimerId != seriesTimerId) { + result.Items = []; } - hideAll(page, 'btnCancelTimer', true); + const html = getProgramScheduleHtml(result.Items); + const scheduleTab = page.querySelector('.seriesTimerSchedule'); + scheduleTab.innerHTML = html; + imageLoader.lazyChildren(scheduleTab); + }); +} + +function renderTimerEditor(page, item, apiClient, user) { + if (item.Type !== 'Recording' || !user.Policy.EnableLiveTvManagement || !item.TimerId || item.Status !== 'InProgress') { + return void hideAll(page, 'btnCancelTimer'); } - function renderSeriesTimerEditor(page, item, apiClient, user) { - if ('SeriesTimer' !== item.Type) { - return void hideAll(page, 'btnCancelSeriesTimer'); - } + hideAll(page, 'btnCancelTimer', true); +} - if (user.Policy.EnableLiveTvManagement) { - require(['seriesRecordingEditor'], function (seriesRecordingEditor) { - seriesRecordingEditor.embed(item, apiClient.serverId(), { - context: page.querySelector('.seriesRecordingEditor') - }); - }); - - page.querySelector('.seriesTimerScheduleSection').classList.remove('hide'); - hideAll(page, 'btnCancelSeriesTimer', true); - return void renderSeriesTimerSchedule(page, apiClient, item.Id); - } - - page.querySelector('.seriesTimerScheduleSection').classList.add('hide'); +function renderSeriesTimerEditor(page, item, apiClient, user) { + if (item.Type !== 'SeriesTimer') { return void hideAll(page, 'btnCancelSeriesTimer'); } - function renderTrackSelections(page, instance, item, forceReload) { - var select = page.querySelector('.selectSource'); - - if (!item.MediaSources || !itemHelper.supportsMediaSourceSelection(item) || -1 === playbackManager.getSupportedCommands().indexOf('PlayMediaSource') || !playbackManager.canPlay(item)) { - page.querySelector('.trackSelections').classList.add('hide'); - select.innerHTML = ''; - page.querySelector('.selectVideo').innerHTML = ''; - page.querySelector('.selectAudio').innerHTML = ''; - page.querySelector('.selectSubtitles').innerHTML = ''; - return; - } - - var mediaSources = item.MediaSources; - instance._currentPlaybackMediaSources = mediaSources; - - page.querySelector('.trackSelections').classList.remove('hide'); - select.setLabel(globalize.translate('LabelVersion')); - - var currentValue = select.value; - - var selectedId = mediaSources[0].Id; - select.innerHTML = mediaSources.map(function (v) { - var selected = v.Id === selectedId ? ' selected' : ''; - return ''; - }).join(''); - - if (mediaSources.length > 1) { - page.querySelector('.selectSourceContainer').classList.remove('hide'); - } else { - page.querySelector('.selectSourceContainer').classList.add('hide'); - } - - if (select.value !== currentValue || forceReload) { - renderVideoSelections(page, mediaSources); - renderAudioSelections(page, mediaSources); - renderSubtitleSelections(page, mediaSources); - } - } - - function renderVideoSelections(page, mediaSources) { - var mediaSourceId = page.querySelector('.selectSource').value; - var mediaSource = mediaSources.filter(function (m) { - return m.Id === mediaSourceId; - })[0]; - - var tracks = mediaSource.MediaStreams.filter(function (m) { - return m.Type === 'Video'; + if (user.Policy.EnableLiveTvManagement) { + import('seriesRecordingEditor').then(({ default: seriesRecordingEditor }) => { + seriesRecordingEditor.embed(item, apiClient.serverId(), { + context: page.querySelector('.seriesRecordingEditor') + }); }); - var select = page.querySelector('.selectVideo'); - select.setLabel(globalize.translate('LabelVideo')); - var selectedId = tracks.length ? tracks[0].Index : -1; - select.innerHTML = tracks.map(function (v) { - var selected = v.Index === selectedId ? ' selected' : ''; - var titleParts = []; - var resolutionText = mediaInfo.getResolutionText(v); + page.querySelector('.seriesTimerScheduleSection').classList.remove('hide'); + hideAll(page, 'btnCancelSeriesTimer', true); + return void renderSeriesTimerSchedule(page, apiClient, item.Id); + } - if (resolutionText) { - titleParts.push(resolutionText); - } + page.querySelector('.seriesTimerScheduleSection').classList.add('hide'); + return void hideAll(page, 'btnCancelSeriesTimer'); +} - if (v.Codec) { - titleParts.push(v.Codec.toUpperCase()); - } +function renderTrackSelections(page, instance, item, forceReload) { + const select = page.querySelector('.selectSource'); - return ''; - }).join(''); + if (!item.MediaSources || !itemHelper.supportsMediaSourceSelection(item) || playbackManager.getSupportedCommands().indexOf('PlayMediaSource') === -1 || !playbackManager.canPlay(item)) { + page.querySelector('.trackSelections').classList.add('hide'); + select.innerHTML = ''; + page.querySelector('.selectVideo').innerHTML = ''; + page.querySelector('.selectAudio').innerHTML = ''; + page.querySelector('.selectSubtitles').innerHTML = ''; + return; + } + + const mediaSources = item.MediaSources; + instance._currentPlaybackMediaSources = mediaSources; + + page.querySelector('.trackSelections').classList.remove('hide'); + select.setLabel(globalize.translate('LabelVersion')); + + const currentValue = select.value; + + const selectedId = mediaSources[0].Id; + select.innerHTML = mediaSources.map(function (v) { + const selected = v.Id === selectedId ? ' selected' : ''; + return ''; + }).join(''); + + if (mediaSources.length > 1) { + page.querySelector('.selectSourceContainer').classList.remove('hide'); + } else { + page.querySelector('.selectSourceContainer').classList.add('hide'); + } + + if (select.value !== currentValue || forceReload) { + renderVideoSelections(page, mediaSources); + renderAudioSelections(page, mediaSources); + renderSubtitleSelections(page, mediaSources); + } +} + +function renderVideoSelections(page, mediaSources) { + const mediaSourceId = page.querySelector('.selectSource').value; + const mediaSource = mediaSources.filter(function (m) { + return m.Id === mediaSourceId; + })[0]; + + const tracks = mediaSource.MediaStreams.filter(function (m) { + return m.Type === 'Video'; + }); + + const select = page.querySelector('.selectVideo'); + select.setLabel(globalize.translate('Video')); + const selectedId = tracks.length ? tracks[0].Index : -1; + select.innerHTML = tracks.map(function (v) { + const selected = v.Index === selectedId ? ' selected' : ''; + const titleParts = []; + const resolutionText = mediaInfo.getResolutionText(v); + + if (resolutionText) { + titleParts.push(resolutionText); + } + + if (v.Codec) { + titleParts.push(v.Codec.toUpperCase()); + } + + return ''; + }).join(''); + select.setAttribute('disabled', 'disabled'); + + if (tracks.length) { + page.querySelector('.selectVideoContainer').classList.remove('hide'); + } else { + page.querySelector('.selectVideoContainer').classList.add('hide'); + } +} + +function renderAudioSelections(page, mediaSources) { + const mediaSourceId = page.querySelector('.selectSource').value; + const mediaSource = mediaSources.filter(function (m) { + return m.Id === mediaSourceId; + })[0]; + const tracks = mediaSource.MediaStreams.filter(function (m) { + return m.Type === 'Audio'; + }); + const select = page.querySelector('.selectAudio'); + select.setLabel(globalize.translate('Audio')); + const selectedId = mediaSource.DefaultAudioStreamIndex; + select.innerHTML = tracks.map(function (v) { + const selected = v.Index === selectedId ? ' selected' : ''; + return ''; + }).join(''); + + if (tracks.length > 1) { + select.removeAttribute('disabled'); + } else { select.setAttribute('disabled', 'disabled'); - - if (tracks.length) { - page.querySelector('.selectVideoContainer').classList.remove('hide'); - } else { - page.querySelector('.selectVideoContainer').classList.add('hide'); - } } - function renderAudioSelections(page, mediaSources) { - var mediaSourceId = page.querySelector('.selectSource').value; - var mediaSource = mediaSources.filter(function (m) { - return m.Id === mediaSourceId; - })[0]; - var tracks = mediaSource.MediaStreams.filter(function (m) { - return 'Audio' === m.Type; - }); - var select = page.querySelector('.selectAudio'); - select.setLabel(globalize.translate('LabelAudio')); - var selectedId = mediaSource.DefaultAudioStreamIndex; - select.innerHTML = tracks.map(function (v) { - var selected = v.Index === selectedId ? ' selected' : ''; + if (tracks.length) { + page.querySelector('.selectAudioContainer').classList.remove('hide'); + } else { + page.querySelector('.selectAudioContainer').classList.add('hide'); + } +} + +function renderSubtitleSelections(page, mediaSources) { + const mediaSourceId = page.querySelector('.selectSource').value; + const mediaSource = mediaSources.filter(function (m) { + return m.Id === mediaSourceId; + })[0]; + const tracks = mediaSource.MediaStreams.filter(function (m) { + return m.Type === 'Subtitle'; + }); + const select = page.querySelector('.selectSubtitles'); + select.setLabel(globalize.translate('Subtitles')); + const selectedId = mediaSource.DefaultSubtitleStreamIndex == null ? -1 : mediaSource.DefaultSubtitleStreamIndex; + + const videoTracks = mediaSource.MediaStreams.filter(function (m) { + return m.Type === 'Video'; + }); + + // This only makes sense on Video items + if (videoTracks.length) { + let selected = selectedId === -1 ? ' selected' : ''; + select.innerHTML = '' + tracks.map(function (v) { + selected = v.Index === selectedId ? ' selected' : ''; return ''; }).join(''); - if (tracks.length > 1) { + if (tracks.length > 0) { select.removeAttribute('disabled'); } else { select.setAttribute('disabled', 'disabled'); } - if (tracks.length) { - page.querySelector('.selectAudioContainer').classList.remove('hide'); - } else { - page.querySelector('.selectAudioContainer').classList.add('hide'); - } + page.querySelector('.selectSubtitlesContainer').classList.remove('hide'); + } else { + select.innerHTML = ''; + page.querySelector('.selectSubtitlesContainer').classList.add('hide'); } +} - function renderSubtitleSelections(page, mediaSources) { - var mediaSourceId = page.querySelector('.selectSource').value; - var mediaSource = mediaSources.filter(function (m) { - return m.Id === mediaSourceId; - })[0]; - var tracks = mediaSource.MediaStreams.filter(function (m) { - return 'Subtitle' === m.Type; - }); - var select = page.querySelector('.selectSubtitles'); - select.setLabel(globalize.translate('LabelSubtitles')); - var selectedId = null == mediaSource.DefaultSubtitleStreamIndex ? -1 : mediaSource.DefaultSubtitleStreamIndex; +function reloadPlayButtons(page, item) { + let canPlay = false; - var videoTracks = mediaSource.MediaStreams.filter(function (m) { - return 'Video' === m.Type; - }); + if (item.Type == 'Program') { + const now = new Date(); - // This only makes sense on Video items - if (videoTracks.length) { - var selected = -1 === selectedId ? ' selected' : ''; - select.innerHTML = '' + tracks.map(function (v) { - selected = v.Index === selectedId ? ' selected' : ''; - return ''; - }).join(''); - - if (tracks.length > 0) { - select.removeAttribute('disabled'); - } else { - select.setAttribute('disabled', 'disabled'); - } - - page.querySelector('.selectSubtitlesContainer').classList.remove('hide'); - } else { - select.innerHTML = ''; - page.querySelector('.selectSubtitlesContainer').classList.add('hide'); - } - } - - function reloadPlayButtons(page, item) { - var canPlay = false; - - if ('Program' == item.Type) { - var now = new Date(); - - if (now >= datetime.parseISO8601Date(item.StartDate, true) && now < datetime.parseISO8601Date(item.EndDate, true)) { - hideAll(page, 'btnPlay', true); - canPlay = true; - } else { - hideAll(page, 'btnPlay'); - } - - hideAll(page, 'btnResume'); - hideAll(page, 'btnInstantMix'); - hideAll(page, 'btnShuffle'); - } else if (playbackManager.canPlay(item)) { + if (now >= datetime.parseISO8601Date(item.StartDate, true) && now < datetime.parseISO8601Date(item.EndDate, true)) { hideAll(page, 'btnPlay', true); - var enableInstantMix = -1 !== ['Audio', 'MusicAlbum', 'MusicGenre', 'MusicArtist'].indexOf(item.Type); - hideAll(page, 'btnInstantMix', enableInstantMix); - var enableShuffle = item.IsFolder || -1 !== ['MusicAlbum', 'MusicGenre', 'MusicArtist'].indexOf(item.Type); - hideAll(page, 'btnShuffle', enableShuffle); canPlay = true; - - const isResumable = item.UserData && item.UserData.PlaybackPositionTicks > 0; - hideAll(page, 'btnResume', isResumable); - - if (isResumable) { - for (const elem of page.querySelectorAll('.btnPlay')) { - elem.querySelector('.detailButton-icon').classList.replace('play_arrow', 'replay'); - } - } } else { hideAll(page, 'btnPlay'); - hideAll(page, 'btnResume'); - hideAll(page, 'btnInstantMix'); - hideAll(page, 'btnShuffle'); } - return canPlay; + hideAll(page, 'btnResume'); + hideAll(page, 'btnInstantMix'); + hideAll(page, 'btnShuffle'); + } else if (playbackManager.canPlay(item)) { + hideAll(page, 'btnPlay', true); + const enableInstantMix = ['Audio', 'MusicAlbum', 'MusicGenre', 'MusicArtist'].indexOf(item.Type) !== -1; + hideAll(page, 'btnInstantMix', enableInstantMix); + const enableShuffle = item.IsFolder || ['MusicAlbum', 'MusicGenre', 'MusicArtist'].indexOf(item.Type) !== -1; + hideAll(page, 'btnShuffle', enableShuffle); + canPlay = true; + + const isResumable = item.UserData && item.UserData.PlaybackPositionTicks > 0; + hideAll(page, 'btnResume', isResumable); + + if (isResumable) { + for (const elem of page.querySelectorAll('.btnPlay')) { + elem.querySelector('.detailButton-icon').classList.replace('play_arrow', 'replay'); + } + } + } else { + hideAll(page, 'btnPlay'); + hideAll(page, 'btnResume'); + hideAll(page, 'btnInstantMix'); + hideAll(page, 'btnShuffle'); } - function reloadUserDataButtons(page, item) { - var i; - var length; - var btnPlaystates = page.querySelectorAll('.btnPlaystate'); + return canPlay; +} - for (i = 0, length = btnPlaystates.length; i < length; i++) { - var btnPlaystate = btnPlaystates[i]; +function reloadUserDataButtons(page, item) { + let i; + let length; + const btnPlaystates = page.querySelectorAll('.btnPlaystate'); - if (itemHelper.canMarkPlayed(item)) { - btnPlaystate.classList.remove('hide'); - btnPlaystate.setItem(item); - } else { - btnPlaystate.classList.add('hide'); - btnPlaystate.setItem(null); - } - } + for (i = 0, length = btnPlaystates.length; i < length; i++) { + const btnPlaystate = btnPlaystates[i]; - var btnUserRatings = page.querySelectorAll('.btnUserRating'); - - for (i = 0, length = btnUserRatings.length; i < length; i++) { - var btnUserRating = btnUserRatings[i]; - - if (itemHelper.canRate(item)) { - btnUserRating.classList.remove('hide'); - btnUserRating.setItem(item); - } else { - btnUserRating.classList.add('hide'); - btnUserRating.setItem(null); - } + if (itemHelper.canMarkPlayed(item)) { + btnPlaystate.classList.remove('hide'); + btnPlaystate.setItem(item); + } else { + btnPlaystate.classList.add('hide'); + btnPlaystate.setItem(null); } } - function getArtistLinksHtml(artists, serverId, context) { - var html = []; + const btnUserRatings = page.querySelectorAll('.btnUserRating'); - for (const artist of artists) { - var href = appRouter.getRouteUrl(artist, { - context: context, - itemType: 'MusicArtist', - serverId: serverId - }); - html.push('' + artist.Name + ''); + for (i = 0, length = btnUserRatings.length; i < length; i++) { + const btnUserRating = btnUserRatings[i]; + + if (itemHelper.canRate(item)) { + btnUserRating.classList.remove('hide'); + btnUserRating.setItem(item); + } else { + btnUserRating.classList.add('hide'); + btnUserRating.setItem(null); } - html = html.join(' / '); - - return html; } +} - /** - * Renders the item's name block - * @param {Object} item - Item used to render the name. - * @param {HTMLDivElement} container - Container to render the information into. - * @param {Object} context - Application context. - */ - function renderName(item, container, context) { - var parentRoute; - var parentNameHtml = []; - var parentNameLast = false; +function getArtistLinksHtml(artists, serverId, context) { + const html = []; - if (item.AlbumArtists) { - parentNameHtml.push(getArtistLinksHtml(item.AlbumArtists, item.ServerId, context)); - parentNameLast = true; - } else if (item.ArtistItems && item.ArtistItems.length && 'MusicVideo' === item.Type) { - parentNameHtml.push(getArtistLinksHtml(item.ArtistItems, item.ServerId, context)); - parentNameLast = true; - } else if (item.SeriesName && 'Episode' === item.Type) { - parentRoute = appRouter.getRouteUrl({ - Id: item.SeriesId, - Name: item.SeriesName, - Type: 'Series', - IsFolder: true, - ServerId: item.ServerId - }, { - context: context - }); - parentNameHtml.push('' + item.SeriesName + ''); - } else if (item.IsSeries || item.EpisodeTitle) { - parentNameHtml.push(item.Name); - } - - if (item.SeriesName && 'Season' === item.Type) { - parentRoute = appRouter.getRouteUrl({ - Id: item.SeriesId, - Name: item.SeriesName, - Type: 'Series', - IsFolder: true, - ServerId: item.ServerId - }, { - context: context - }); - parentNameHtml.push('' + item.SeriesName + ''); - } else if (null != item.ParentIndexNumber && 'Episode' === item.Type) { - parentRoute = appRouter.getRouteUrl({ - Id: item.SeasonId, - Name: item.SeasonName, - Type: 'Season', - IsFolder: true, - ServerId: item.ServerId - }, { - context: context - }); - parentNameHtml.push('' + item.SeasonName + ''); - } else if (null != item.ParentIndexNumber && item.IsSeries) { - parentNameHtml.push(item.SeasonName || 'S' + item.ParentIndexNumber); - } else if (item.Album && item.AlbumId && ('MusicVideo' === item.Type || 'Audio' === item.Type)) { - parentRoute = appRouter.getRouteUrl({ - Id: item.AlbumId, - Name: item.Album, - Type: 'MusicAlbum', - IsFolder: true, - ServerId: item.ServerId - }, { - context: context - }); - parentNameHtml.push('' + item.Album + ''); - } else if (item.Album) { - parentNameHtml.push(item.Album); - } - - // FIXME: This whole section needs some refactoring, so it becames easier to scale across all form factors. See GH #1022 - var html = ''; - var tvShowHtml = parentNameHtml[0]; - var tvSeasonHtml = parentNameHtml[1]; - - if (parentNameHtml.length) { - if (parentNameLast) { - // Music - if (layoutManager.mobile) { - html = '

' + parentNameHtml.join('
') + '

'; - } else { - html = '

' + parentNameHtml.join(' - ') + '

'; - } - } else { - html = '

' + tvShowHtml + '

'; - } - } - - var name = itemHelper.getDisplayName(item, { - includeParentInfo: false + for (const artist of artists) { + const href = appRouter.getRouteUrl(artist, { + context: context, + itemType: 'MusicArtist', + serverId: serverId }); + html.push('' + artist.Name + ''); + } - if (html && !parentNameLast) { - if (tvSeasonHtml) { - html += '

' + tvSeasonHtml + ' - ' + name + '

'; + return html.join(' / '); +} + +/** + * Renders the item's name block + * @param {Object} item - Item used to render the name. + * @param {HTMLDivElement} container - Container to render the information into. + * @param {Object} context - Application context. + */ +function renderName(item, container, context) { + let parentRoute; + const parentNameHtml = []; + let parentNameLast = false; + + if (item.AlbumArtists) { + parentNameHtml.push(getArtistLinksHtml(item.AlbumArtists, item.ServerId, context)); + parentNameLast = true; + } else if (item.ArtistItems && item.ArtistItems.length && item.Type === 'MusicVideo') { + parentNameHtml.push(getArtistLinksHtml(item.ArtistItems, item.ServerId, context)); + parentNameLast = true; + } else if (item.SeriesName && item.Type === 'Episode') { + parentRoute = appRouter.getRouteUrl({ + Id: item.SeriesId, + Name: item.SeriesName, + Type: 'Series', + IsFolder: true, + ServerId: item.ServerId + }, { + context: context + }); + parentNameHtml.push('' + item.SeriesName + ''); + } else if (item.IsSeries || item.EpisodeTitle) { + parentNameHtml.push(item.Name); + } + + if (item.SeriesName && item.Type === 'Season') { + parentRoute = appRouter.getRouteUrl({ + Id: item.SeriesId, + Name: item.SeriesName, + Type: 'Series', + IsFolder: true, + ServerId: item.ServerId + }, { + context: context + }); + parentNameHtml.push('' + item.SeriesName + ''); + } else if (item.ParentIndexNumber != null && item.Type === 'Episode') { + parentRoute = appRouter.getRouteUrl({ + Id: item.SeasonId, + Name: item.SeasonName, + Type: 'Season', + IsFolder: true, + ServerId: item.ServerId + }, { + context: context + }); + parentNameHtml.push('' + item.SeasonName + ''); + } else if (item.ParentIndexNumber != null && item.IsSeries) { + parentNameHtml.push(item.SeasonName || 'S' + item.ParentIndexNumber); + } else if (item.Album && item.AlbumId && (item.Type === 'MusicVideo' || item.Type === 'Audio')) { + parentRoute = appRouter.getRouteUrl({ + Id: item.AlbumId, + Name: item.Album, + Type: 'MusicAlbum', + IsFolder: true, + ServerId: item.ServerId + }, { + context: context + }); + parentNameHtml.push('' + item.Album + ''); + } else if (item.Album) { + parentNameHtml.push(item.Album); + } + + // FIXME: This whole section needs some refactoring, so it becames easier to scale across all form factors. See GH #1022 + let html = ''; + const tvShowHtml = parentNameHtml[0]; + const tvSeasonHtml = parentNameHtml[1]; + + if (parentNameHtml.length) { + if (parentNameLast) { + // Music + if (layoutManager.mobile) { + html = '

' + parentNameHtml.join('
') + '

'; } else { - html += '

' + name + '

'; + html = '

' + parentNameHtml.join(' - ') + '

'; } - } else if (item.OriginalTitle && item.OriginalTitle != item.Name) { - html = '

' + name + '

' + html; } else { - html = '

' + name + '

' + html; - } - - if (item.OriginalTitle && item.OriginalTitle != item.Name) { - html += '

' + item.OriginalTitle + '

'; - } - - container.innerHTML = html; - - if (html.length) { - container.classList.remove('hide'); - } else { - container.classList.add('hide'); + html = '

' + tvShowHtml + '

'; } } - function setTrailerButtonVisibility(page, item) { - if ((item.LocalTrailerCount || item.RemoteTrailers && item.RemoteTrailers.length) && -1 !== playbackManager.getSupportedCommands().indexOf('PlayTrailers')) { - hideAll(page, 'btnPlayTrailer', true); + const name = itemHelper.getDisplayName(item, { + includeParentInfo: false + }); + + if (html && !parentNameLast) { + if (tvSeasonHtml) { + html += '

' + tvSeasonHtml + ' - ' + name + '

'; } else { - hideAll(page, 'btnPlayTrailer'); + html += '

' + name + '

'; } + } else if (item.OriginalTitle && item.OriginalTitle != item.Name) { + html = '

' + name + '

' + html; + } else { + html = '

' + name + '

' + html; } - function renderBackdrop(item) { - if (dom.getWindowSize().innerWidth >= 1000) { - backdrop.setBackdrops([item]); - } else { - backdrop.clearBackdrop(); - } + if (item.OriginalTitle && item.OriginalTitle != item.Name) { + html += '

' + item.OriginalTitle + '

'; } - function renderDetailPageBackdrop(page, item, apiClient) { - var imgUrl; - var hasbackdrop = false; - var itemBackdropElement = page.querySelector('#itemBackdrop'); + container.innerHTML = html; - if (!layoutManager.mobile && !userSettings.detailsBanner()) { - return false; - } + if (html.length) { + container.classList.remove('hide'); + } else { + container.classList.add('hide'); + } +} - if (item.BackdropImageTags && item.BackdropImageTags.length) { - imgUrl = apiClient.getScaledImageUrl(item.Id, { - type: 'Backdrop', - maxWidth: dom.getScreenWidth(), - index: 0, - tag: item.BackdropImageTags[0] - }); - imageLoader.lazyImage(itemBackdropElement, imgUrl); - hasbackdrop = true; - } else if (item.ParentBackdropItemId && item.ParentBackdropImageTags && item.ParentBackdropImageTags.length) { - imgUrl = apiClient.getScaledImageUrl(item.ParentBackdropItemId, { - type: 'Backdrop', - maxWidth: dom.getScreenWidth(), - index: 0, - tag: item.ParentBackdropImageTags[0] - }); - imageLoader.lazyImage(itemBackdropElement, imgUrl); - hasbackdrop = true; - } else { - itemBackdropElement.style.backgroundImage = ''; - } +function setTrailerButtonVisibility(page, item) { + if ((item.LocalTrailerCount || item.RemoteTrailers && item.RemoteTrailers.length) && playbackManager.getSupportedCommands().indexOf('PlayTrailers') !== -1) { + hideAll(page, 'btnPlayTrailer', true); + } else { + hideAll(page, 'btnPlayTrailer'); + } +} - return hasbackdrop; +function renderBackdrop(item) { + if (dom.getWindowSize().innerWidth >= 1000) { + backdrop.setBackdrops([item]); + } else { + backdrop.clearBackdrop(); + } +} + +function renderDetailPageBackdrop(page, item, apiClient) { + let imgUrl; + let hasbackdrop = false; + const itemBackdropElement = page.querySelector('#itemBackdrop'); + + if (!layoutManager.mobile && !userSettings.detailsBanner()) { + return false; } - function reloadFromItem(instance, page, params, item, user) { - const apiClient = connectionManager.getApiClient(item.ServerId); + if (item.BackdropImageTags && item.BackdropImageTags.length) { + imgUrl = apiClient.getScaledImageUrl(item.Id, { + type: 'Backdrop', + maxWidth: dom.getScreenWidth(), + index: 0, + tag: item.BackdropImageTags[0] + }); + imageLoader.lazyImage(itemBackdropElement, imgUrl); + hasbackdrop = true; + } else if (item.ParentBackdropItemId && item.ParentBackdropImageTags && item.ParentBackdropImageTags.length) { + imgUrl = apiClient.getScaledImageUrl(item.ParentBackdropItemId, { + type: 'Backdrop', + maxWidth: dom.getScreenWidth(), + index: 0, + tag: item.ParentBackdropImageTags[0] + }); + imageLoader.lazyImage(itemBackdropElement, imgUrl); + hasbackdrop = true; + } else if (item.ImageTags && item.ImageTags.Primary) { + imgUrl = apiClient.getScaledImageUrl(item.Id, { + type: 'Primary', + maxWidth: dom.getScreenWidth(), + tag: item.ImageTags.Primary + }); + imageLoader.lazyImage(itemBackdropElement, imgUrl); + hasbackdrop = true; + } else { + itemBackdropElement.style.backgroundImage = ''; + } - Emby.Page.setTitle(''); + return hasbackdrop; +} - // Start rendering the artwork first - renderImage(page, item); +function reloadFromItem(instance, page, params, item, user) { + const apiClient = window.connectionManager.getApiClient(item.ServerId); + + Emby.Page.setTitle(''); + + // Start rendering the artwork first + renderImage(page, item); + // Save some screen real estate in TV mode + if (!layoutManager.tv) { renderLogo(page, item, apiClient); - renderBackdrop(item); renderDetailPageBackdrop(page, item, apiClient); + } + renderBackdrop(item); - // Render the main information for the item - page.querySelector('.detailPagePrimaryContainer').classList.add('detailRibbon'); - renderName(item, page.querySelector('.nameContainer'), params.context); - renderDetails(page, item, apiClient, params.context); - renderTrackSelections(page, instance, item); + // Render the main information for the item + page.querySelector('.detailPagePrimaryContainer').classList.add('detailRibbon'); + renderName(item, page.querySelector('.nameContainer'), params.context); + renderDetails(page, item, apiClient, params.context); + renderTrackSelections(page, instance, item); - renderSeriesTimerEditor(page, item, apiClient, user); - renderTimerEditor(page, item, apiClient, user); - setInitialCollapsibleState(page, item, apiClient, params.context, user); - var canPlay = reloadPlayButtons(page, item); + renderSeriesTimerEditor(page, item, apiClient, user); + renderTimerEditor(page, item, apiClient, user); + setInitialCollapsibleState(page, item, apiClient, params.context, user); + const canPlay = reloadPlayButtons(page, item); - if ((item.LocalTrailerCount || item.RemoteTrailers && item.RemoteTrailers.length) && -1 !== playbackManager.getSupportedCommands().indexOf('PlayTrailers')) { - hideAll(page, 'btnPlayTrailer', true); - } else { - hideAll(page, 'btnPlayTrailer'); - } + if ((item.LocalTrailerCount || item.RemoteTrailers && item.RemoteTrailers.length) && playbackManager.getSupportedCommands().indexOf('PlayTrailers') !== -1) { + hideAll(page, 'btnPlayTrailer', true); + } else { + hideAll(page, 'btnPlayTrailer'); + } - setTrailerButtonVisibility(page, item); + setTrailerButtonVisibility(page, item); - if ('Program' !== item.Type || canPlay) { - hideAll(page, 'mainDetailButtons', true); - } else { - hideAll(page, 'mainDetailButtons'); - } + if (item.Type !== 'Program' || canPlay) { + hideAll(page, 'mainDetailButtons', true); + } else { + hideAll(page, 'mainDetailButtons'); + } - showRecordingFields(instance, page, item, user); - var groupedVersions = (item.MediaSources || []).filter(function (g) { - return 'Grouping' == g.Type; - }); + showRecordingFields(instance, page, item, user); + const groupedVersions = (item.MediaSources || []).filter(function (g) { + return g.Type == 'Grouping'; + }); - if (user.Policy.IsAdministrator && groupedVersions.length) { - page.querySelector('.btnSplitVersions').classList.remove('hide'); - } else { - page.querySelector('.btnSplitVersions').classList.add('hide'); - } + if (user.Policy.IsAdministrator && groupedVersions.length) { + page.querySelector('.btnSplitVersions').classList.remove('hide'); + } else { + page.querySelector('.btnSplitVersions').classList.add('hide'); + } - if (itemContextMenu.getCommands(getContextMenuOptions(item, user)).length) { - hideAll(page, 'btnMoreCommands', true); - } else { - hideAll(page, 'btnMoreCommands'); - } + if (itemContextMenu.getCommands(getContextMenuOptions(item, user)).length) { + hideAll(page, 'btnMoreCommands', true); + } else { + hideAll(page, 'btnMoreCommands'); + } - var itemBirthday = page.querySelector('#itemBirthday'); + const itemBirthday = page.querySelector('#itemBirthday'); - if ('Person' == item.Type && item.PremiereDate) { - try { - var birthday = datetime.parseISO8601Date(item.PremiereDate, true).toDateString(); - itemBirthday.classList.remove('hide'); - itemBirthday.innerHTML = globalize.translate('BirthDateValue', birthday); - } catch (err) { - itemBirthday.classList.add('hide'); - } - } else { + if (item.Type == 'Person' && item.PremiereDate) { + try { + const birthday = datetime.parseISO8601Date(item.PremiereDate, true).toDateString(); + itemBirthday.classList.remove('hide'); + itemBirthday.innerHTML = globalize.translate('BirthDateValue', birthday); + } catch (err) { itemBirthday.classList.add('hide'); } + } else { + itemBirthday.classList.add('hide'); + } - var itemDeathDate = page.querySelector('#itemDeathDate'); + const itemDeathDate = page.querySelector('#itemDeathDate'); - if ('Person' == item.Type && item.EndDate) { - try { - var deathday = datetime.parseISO8601Date(item.EndDate, true).toDateString(); - itemDeathDate.classList.remove('hide'); - itemDeathDate.innerHTML = globalize.translate('DeathDateValue', deathday); - } catch (err) { - itemDeathDate.classList.add('hide'); - } - } else { + if (item.Type == 'Person' && item.EndDate) { + try { + const deathday = datetime.parseISO8601Date(item.EndDate, true).toDateString(); + itemDeathDate.classList.remove('hide'); + itemDeathDate.innerHTML = globalize.translate('DeathDateValue', deathday); + } catch (err) { itemDeathDate.classList.add('hide'); } - - var itemBirthLocation = page.querySelector('#itemBirthLocation'); - - if ('Person' == item.Type && item.ProductionLocations && item.ProductionLocations.length) { - var gmap = '' + item.ProductionLocations[0] + ''; - itemBirthLocation.classList.remove('hide'); - itemBirthLocation.innerHTML = globalize.translate('BirthPlaceValue', gmap); - } else { - itemBirthLocation.classList.add('hide'); - } - - setPeopleHeader(page, item); - loading.hide(); - - if (item.Type === 'Book') { - hideAll(page, 'btnDownload', true); - } - - require(['autoFocuser'], function (autoFocuser) { - autoFocuser.autoFocus(page); - }); + } else { + itemDeathDate.classList.add('hide'); } - function logoImageUrl(item, apiClient, options) { - options = options || {}; - options.type = 'Logo'; + const itemBirthLocation = page.querySelector('#itemBirthLocation'); - if (item.ImageTags && item.ImageTags.Logo) { - options.tag = item.ImageTags.Logo; - return apiClient.getScaledImageUrl(item.Id, options); - } - - if (item.ParentLogoImageTag) { - options.tag = item.ParentLogoImageTag; - return apiClient.getScaledImageUrl(item.ParentLogoItemId, options); - } - - return null; + if (item.Type == 'Person' && item.ProductionLocations && item.ProductionLocations.length) { + const gmap = '' + item.ProductionLocations[0] + ''; + itemBirthLocation.classList.remove('hide'); + itemBirthLocation.innerHTML = globalize.translate('BirthPlaceValue', gmap); + } else { + itemBirthLocation.classList.add('hide'); } - function renderLogo(page, item, apiClient) { - var detailLogo = page.querySelector('.detailLogo'); + setPeopleHeader(page, item); + loading.hide(); - var url = logoImageUrl(item, apiClient, {}); - - if (!layoutManager.mobile && !userSettings.enableBackdrops()) { - detailLogo.classList.add('hide'); - } else if (url) { - detailLogo.classList.remove('hide'); - imageLoader.setLazyImage(detailLogo, url); - } else { - detailLogo.classList.add('hide'); - } + if (item.Type === 'Book' && item.CanDownload && appHost.supports('filedownload')) { + hideAll(page, 'btnDownload', true); } - function showRecordingFields(instance, page, item, user) { - if (!instance.currentRecordingFields) { - var recordingFieldsElement = page.querySelector('.recordingFields'); + import('autoFocuser').then(({ default: autoFocuser }) => { + autoFocuser.autoFocus(page); + }); +} - if ('Program' == item.Type && user.Policy.EnableLiveTvManagement) { - require(['recordingFields'], function (recordingFields) { - instance.currentRecordingFields = new recordingFields({ - parent: recordingFieldsElement, - programId: item.Id, - serverId: item.ServerId - }); - recordingFieldsElement.classList.remove('hide'); +function logoImageUrl(item, apiClient, options) { + options = options || {}; + options.type = 'Logo'; + + if (item.ImageTags && item.ImageTags.Logo) { + options.tag = item.ImageTags.Logo; + return apiClient.getScaledImageUrl(item.Id, options); + } + + if (item.ParentLogoImageTag) { + options.tag = item.ParentLogoImageTag; + return apiClient.getScaledImageUrl(item.ParentLogoItemId, options); + } + + return null; +} + +function renderLogo(page, item, apiClient) { + const detailLogo = page.querySelector('.detailLogo'); + + const url = logoImageUrl(item, apiClient, {}); + + if (!layoutManager.mobile && !userSettings.enableBackdrops()) { + detailLogo.classList.add('hide'); + } else if (url) { + detailLogo.classList.remove('hide'); + imageLoader.setLazyImage(detailLogo, url); + } else { + detailLogo.classList.add('hide'); + } +} + +function showRecordingFields(instance, page, item, user) { + if (!instance.currentRecordingFields) { + const recordingFieldsElement = page.querySelector('.recordingFields'); + + if (item.Type == 'Program' && user.Policy.EnableLiveTvManagement) { + import('recordingFields').then(({ default: recordingFields }) => { + instance.currentRecordingFields = new recordingFields({ + parent: recordingFieldsElement, + programId: item.Id, + serverId: item.ServerId }); - } else { - recordingFieldsElement.classList.add('hide'); - recordingFieldsElement.innerHTML = ''; - } - } - } - - function renderLinks(page, item) { - var externalLinksElem = page.querySelector('.itemExternalLinks'); - - var links = []; - - if (!layoutManager.tv && item.HomePageUrl) { - links.push(`${globalize.translate('ButtonWebsite')}`); - } - - if (item.ExternalUrls) { - for (const url of item.ExternalUrls) { - links.push(`${url.Name}`); - } - } - - var html = []; - if (links.length) { - html.push(links.join(', ')); - } - - externalLinksElem.innerHTML = html.join(', '); - - if (html.length) { - externalLinksElem.classList.remove('hide'); - } else { - externalLinksElem.classList.add('hide'); - } - } - - function renderDetailImage(elem, item, imageLoader) { - const itemArray = []; - itemArray.push(item); - const cardHtml = cardBuilder.getCardsHtml(itemArray, { - shape: 'auto', - showTitle: false, - centerText: true, - overlayText: false, - transition: false, - disableIndicators: true, - disableHoverMenu: true, - overlayPlayButton: true, - width: dom.getWindowSize().innerWidth * 0.5 - }); - - elem.innerHTML = cardHtml; - imageLoader.lazyChildren(elem); - } - - function renderImage(page, item) { - renderDetailImage( - page.querySelector('.detailImageContainer'), - item, - imageLoader - ); - } - - function refreshDetailImageUserData(elem, item) { - elem.querySelector('.detailImageProgressContainer').innerHTML = indicators.getProgressBarHtml(item); - } - - function refreshImage(page, item) { - refreshDetailImageUserData(page.querySelector('.detailImageContainer'), item); - } - - function setPeopleHeader(page, item) { - if ('Audio' == item.MediaType || 'MusicAlbum' == item.Type || 'Book' == item.MediaType || 'Photo' == item.MediaType) { - page.querySelector('#peopleHeader').innerHTML = globalize.translate('HeaderPeople'); - } else { - page.querySelector('#peopleHeader').innerHTML = globalize.translate('HeaderCastAndCrew'); - } - } - - function renderNextUp(page, item, user) { - var section = page.querySelector('.nextUpSection'); - - if ('Series' != item.Type) { - return void section.classList.add('hide'); - } - - connectionManager.getApiClient(item.ServerId).getNextUpEpisodes({ - SeriesId: item.Id, - UserId: user.Id - }).then(function (result) { - if (result.Items.length) { - section.classList.remove('hide'); - } else { - section.classList.add('hide'); - } - - var html = cardBuilder.getCardsHtml({ - items: result.Items, - shape: 'overflowBackdrop', - showTitle: true, - displayAsSpecial: 'Season' == item.Type && item.IndexNumber, - overlayText: false, - centerText: true, - overlayPlayButton: true + recordingFieldsElement.classList.remove('hide'); }); - var itemsContainer = section.querySelector('.nextUpItems'); - itemsContainer.innerHTML = html; - imageLoader.lazyChildren(itemsContainer); - }); + } else { + recordingFieldsElement.classList.add('hide'); + recordingFieldsElement.innerHTML = ''; + } + } +} + +function renderLinks(page, item) { + const externalLinksElem = page.querySelector('.itemExternalLinks'); + + const links = []; + + if (!layoutManager.tv && item.HomePageUrl) { + links.push(`${globalize.translate('ButtonWebsite')}`); } - function setInitialCollapsibleState(page, item, apiClient, context, user) { - page.querySelector('.collectionItems').innerHTML = ''; + if (item.ExternalUrls) { + for (const url of item.ExternalUrls) { + links.push(`${url.Name}`); + } + } - if ('Playlist' == item.Type) { - page.querySelector('#childrenCollapsible').classList.remove('hide'); - renderPlaylistItems(page, item); - } else if ('Studio' == item.Type || 'Person' == item.Type || 'Genre' == item.Type || 'MusicGenre' == item.Type || 'MusicArtist' == item.Type) { - page.querySelector('#childrenCollapsible').classList.remove('hide'); - renderItemsByName(page, item); - } else if (item.IsFolder) { - if ('BoxSet' == item.Type) { - page.querySelector('#childrenCollapsible').classList.add('hide'); - } + const html = []; + if (links.length) { + html.push(links.join(', ')); + } - renderChildren(page, item); + externalLinksElem.innerHTML = html.join(', '); + + if (html.length) { + externalLinksElem.classList.remove('hide'); + } else { + externalLinksElem.classList.add('hide'); + } +} + +function renderDetailImage(elem, item, imageLoader) { + const itemArray = []; + itemArray.push(item); + const cardHtml = cardBuilder.getCardsHtml(itemArray, { + shape: 'auto', + showTitle: false, + centerText: true, + overlayText: false, + transition: false, + disableIndicators: true, + overlayPlayButton: true, + action: 'play', + width: dom.getWindowSize().innerWidth * 0.25 + }); + + elem.innerHTML = cardHtml; + imageLoader.lazyChildren(elem); + + // Avoid breaking the design by preventing focus of the poster using the keyboard. + elem.querySelector('button').tabIndex = -1; +} + +function renderImage(page, item) { + renderDetailImage( + page.querySelector('.detailImageContainer'), + item, + imageLoader + ); +} + +function refreshDetailImageUserData(elem, item) { + elem.querySelector('.detailImageProgressContainer').innerHTML = indicators.getProgressBarHtml(item); +} + +function refreshImage(page, item) { + refreshDetailImageUserData(page.querySelector('.detailImageContainer'), item); +} + +function setPeopleHeader(page, item) { + if (item.MediaType == 'Audio' || item.Type == 'MusicAlbum' || item.MediaType == 'Book' || item.MediaType == 'Photo') { + page.querySelector('#peopleHeader').innerHTML = globalize.translate('People'); + } else { + page.querySelector('#peopleHeader').innerHTML = globalize.translate('HeaderCastAndCrew'); + } +} + +function renderNextUp(page, item, user) { + const section = page.querySelector('.nextUpSection'); + + if (item.Type != 'Series') { + return void section.classList.add('hide'); + } + + window.connectionManager.getApiClient(item.ServerId).getNextUpEpisodes({ + SeriesId: item.Id, + UserId: user.Id + }).then(function (result) { + if (result.Items.length) { + section.classList.remove('hide'); } else { + section.classList.add('hide'); + } + + const html = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'overflowBackdrop', + showTitle: true, + displayAsSpecial: item.Type == 'Season' && item.IndexNumber, + overlayText: false, + centerText: true, + overlayPlayButton: true + }); + const itemsContainer = section.querySelector('.nextUpItems'); + itemsContainer.innerHTML = html; + imageLoader.lazyChildren(itemsContainer); + }); +} + +function setInitialCollapsibleState(page, item, apiClient, context, user) { + page.querySelector('.collectionItems').innerHTML = ''; + + if (item.Type == 'Playlist') { + page.querySelector('#childrenCollapsible').classList.remove('hide'); + renderPlaylistItems(page, item); + } else if (item.Type == 'Studio' || item.Type == 'Person' || item.Type == 'Genre' || item.Type == 'MusicGenre' || item.Type == 'MusicArtist') { + page.querySelector('#childrenCollapsible').classList.remove('hide'); + renderItemsByName(page, item); + } else if (item.IsFolder) { + if (item.Type == 'BoxSet') { page.querySelector('#childrenCollapsible').classList.add('hide'); } - if ('Series' == item.Type) { - renderSeriesSchedule(page, item); - renderNextUp(page, item, user); - } else { - page.querySelector('.nextUpSection').classList.add('hide'); - } - - renderScenes(page, item); - - if (item.SpecialFeatureCount && 0 != item.SpecialFeatureCount && 'Series' != item.Type) { - page.querySelector('#specialsCollapsible').classList.remove('hide'); - renderSpecials(page, item, user); - } else { - page.querySelector('#specialsCollapsible').classList.add('hide'); - } - - renderCast(page, item); - - if (item.PartCount && item.PartCount > 1) { - page.querySelector('#additionalPartsCollapsible').classList.remove('hide'); - renderAdditionalParts(page, item, user); - } else { - page.querySelector('#additionalPartsCollapsible').classList.add('hide'); - } - - if ('MusicAlbum' == item.Type) { - renderMusicVideos(page, item, user); - } else { - page.querySelector('#musicVideosCollapsible').classList.add('hide'); - } + renderChildren(page, item); + } else { + page.querySelector('#childrenCollapsible').classList.add('hide'); } - function toggleLineClamp(clampTarget, e) { - var expandButton = e.target; - var clampClassName = 'detail-clamp-text'; - - if (clampTarget.classList.contains(clampClassName)) { - clampTarget.classList.remove(clampClassName); - expandButton.innerHTML = globalize.translate('ShowLess'); - } else { - clampTarget.classList.add(clampClassName); - expandButton.innerHTML = globalize.translate('ShowMore'); - } + if (item.Type == 'Series') { + renderSeriesSchedule(page, item); + renderNextUp(page, item, user); + } else { + page.querySelector('.nextUpSection').classList.add('hide'); } - function renderOverview(page, item) { - for (const overviewElemnt of page.querySelectorAll('.overview')) { - var overview = item.Overview || ''; + renderScenes(page, item); - if (overview) { - overviewElemnt.innerHTML = overview; - overviewElemnt.classList.remove('hide'); - overviewElemnt.classList.add('detail-clamp-text'); + if (item.SpecialFeatureCount && item.SpecialFeatureCount != 0 && item.Type != 'Series') { + page.querySelector('#specialsCollapsible').classList.remove('hide'); + renderSpecials(page, item, user); + } else { + page.querySelector('#specialsCollapsible').classList.add('hide'); + } - // Grab the sibling element to control the expand state - var expandButton = overviewElemnt.parentElement.querySelector('.overview-expand'); + renderCast(page, item); - // Detect if we have overflow of text. Based on this StackOverflow answer - // https://stackoverflow.com/a/35157976 - if (Math.abs(overviewElemnt.scrollHeight - overviewElemnt.offsetHeight) > 2) { - expandButton.classList.remove('hide'); - } else { - expandButton.classList.add('hide'); - } + if (item.PartCount && item.PartCount > 1) { + page.querySelector('#additionalPartsCollapsible').classList.remove('hide'); + renderAdditionalParts(page, item, user); + } else { + page.querySelector('#additionalPartsCollapsible').classList.add('hide'); + } - expandButton.addEventListener('click', toggleLineClamp.bind(null, overviewElemnt)); + if (item.Type == 'MusicAlbum') { + renderMusicVideos(page, item, user); + } else { + page.querySelector('#musicVideosCollapsible').classList.add('hide'); + } +} - for (const anchor of overviewElemnt.querySelectorAll('a')) { - anchor.setAttribute('target', '_blank'); - } +function toggleLineClamp(clampTarget, e) { + const expandButton = e.target; + const clampClassName = 'detail-clamp-text'; + + if (clampTarget.classList.contains(clampClassName)) { + clampTarget.classList.remove(clampClassName); + expandButton.innerHTML = globalize.translate('ShowLess'); + } else { + clampTarget.classList.add(clampClassName); + expandButton.innerHTML = globalize.translate('ShowMore'); + } +} + +function renderOverview(page, item) { + for (const overviewElemnt of page.querySelectorAll('.overview')) { + const overview = item.Overview || ''; + + if (overview) { + overviewElemnt.innerHTML = overview; + overviewElemnt.classList.remove('hide'); + overviewElemnt.classList.add('detail-clamp-text'); + + // Grab the sibling element to control the expand state + const expandButton = overviewElemnt.parentElement.querySelector('.overview-expand'); + + // Detect if we have overflow of text. Based on this StackOverflow answer + // https://stackoverflow.com/a/35157976 + if (Math.abs(overviewElemnt.scrollHeight - overviewElemnt.offsetHeight) > 2) { + expandButton.classList.remove('hide'); } else { - overviewElemnt.innerHTML = ''; - overviewElemnt.classList.add('hide'); + expandButton.classList.add('hide'); } - } - } - function renderGenres(page, item, context = inferContext(item)) { - var genres = item.GenreItems || []; - var type = context === 'music' ? 'MusicGenre' : 'Genre'; + expandButton.addEventListener('click', toggleLineClamp.bind(null, overviewElemnt)); - var html = genres.map(function (p) { - return '' + p.Name + ''; - }).join(', '); - - var genresLabel = page.querySelector('.genresLabel'); - genresLabel.innerHTML = globalize.translate(genres.length > 1 ? 'Genres' : 'Genre'); - var genresValue = page.querySelector('.genres'); - genresValue.innerHTML = html; - - var genresGroup = page.querySelector('.genresGroup'); - if (genres.length) { - genresGroup.classList.remove('hide'); + for (const anchor of overviewElemnt.querySelectorAll('a')) { + anchor.setAttribute('target', '_blank'); + } } else { - genresGroup.classList.add('hide'); + overviewElemnt.innerHTML = ''; + overviewElemnt.classList.add('hide'); } } +} - function renderWriter(page, item, context) { - var writers = (item.People || []).filter(function (person) { - return person.Type === 'Writer'; +function renderGenres(page, item, context = inferContext(item)) { + const genres = item.GenreItems || []; + const type = context === 'music' ? 'MusicGenre' : 'Genre'; + + const html = genres.map(function (p) { + return '' + p.Name + ''; + }).join(', '); + + const genresLabel = page.querySelector('.genresLabel'); + genresLabel.innerHTML = globalize.translate(genres.length > 1 ? 'Genres' : 'Genre'); + const genresValue = page.querySelector('.genres'); + genresValue.innerHTML = html; + + const genresGroup = page.querySelector('.genresGroup'); + if (genres.length) { + genresGroup.classList.remove('hide'); + } else { + genresGroup.classList.add('hide'); + } +} + +function renderWriter(page, item, context) { + const writers = (item.People || []).filter(function (person) { + return person.Type === 'Writer'; + }); + + const html = writers.map(function (person) { + return '' + person.Name + ''; + }).join(', '); + + const writersLabel = page.querySelector('.writersLabel'); + writersLabel.innerHTML = globalize.translate(writers.length > 1 ? 'Writers' : 'Writer'); + const writersValue = page.querySelector('.writers'); + writersValue.innerHTML = html; + + const writersGroup = page.querySelector('.writersGroup'); + if (writers.length) { + writersGroup.classList.remove('hide'); + } else { + writersGroup.classList.add('hide'); + } +} + +function renderDirector(page, item, context) { + const directors = (item.People || []).filter(function (person) { + return person.Type === 'Director'; + }); + + const html = directors.map(function (person) { + return '' + person.Name + ''; + }).join(', '); + + const directorsLabel = page.querySelector('.directorsLabel'); + directorsLabel.innerHTML = globalize.translate(directors.length > 1 ? 'Directors' : 'Director'); + const directorsValue = page.querySelector('.directors'); + directorsValue.innerHTML = html; + + const directorsGroup = page.querySelector('.directorsGroup'); + if (directors.length) { + directorsGroup.classList.remove('hide'); + } else { + directorsGroup.classList.add('hide'); + } +} + +function renderMiscInfo(page, item) { + const primaryItemMiscInfo = page.querySelectorAll('.itemMiscInfo-primary'); + + for (const miscInfo of primaryItemMiscInfo) { + mediaInfo.fillPrimaryMediaInfo(miscInfo, item, { + interactive: true, + episodeTitle: false, + subtitles: false }); - var html = writers.map(function (person) { - return '' + person.Name + ''; - }).join(', '); - - var writersLabel = page.querySelector('.writersLabel'); - writersLabel.innerHTML = globalize.translate(writers.length > 1 ? 'Writers' : 'Writer'); - var writersValue = page.querySelector('.writers'); - writersValue.innerHTML = html; - - var writersGroup = page.querySelector('.writersGroup'); - if (writers.length) { - writersGroup.classList.remove('hide'); + if (miscInfo.innerHTML && item.Type !== 'SeriesTimer') { + miscInfo.classList.remove('hide'); } else { - writersGroup.classList.add('hide'); + miscInfo.classList.add('hide'); } } - function renderDirector(page, item, context) { - var directors = (item.People || []).filter(function (person) { - return person.Type === 'Director'; + const secondaryItemMiscInfo = page.querySelectorAll('.itemMiscInfo-secondary'); + + for (const miscInfo of secondaryItemMiscInfo) { + mediaInfo.fillSecondaryMediaInfo(miscInfo, item, { + interactive: true }); - var html = directors.map(function (person) { - return '' + person.Name + ''; - }).join(', '); - - var directorsLabel = page.querySelector('.directorsLabel'); - directorsLabel.innerHTML = globalize.translate(directors.length > 1 ? 'Directors' : 'Director'); - var directorsValue = page.querySelector('.directors'); - directorsValue.innerHTML = html; - - var directorsGroup = page.querySelector('.directorsGroup'); - if (directors.length) { - directorsGroup.classList.remove('hide'); + if (miscInfo.innerHTML && item.Type !== 'SeriesTimer') { + miscInfo.classList.remove('hide'); } else { - directorsGroup.classList.add('hide'); + miscInfo.classList.add('hide'); } } +} - function renderMiscInfo(page, item) { - const primaryItemMiscInfo = page.querySelectorAll('.itemMiscInfo-primary'); +function renderTagline(page, item) { + const taglineElement = page.querySelector('.tagline'); - for (const miscInfo of primaryItemMiscInfo) { - mediaInfo.fillPrimaryMediaInfo(miscInfo, item, { - interactive: true, - episodeTitle: false, - subtitles: false - }); - - if (miscInfo.innerHTML && 'SeriesTimer' !== item.Type) { - miscInfo.classList.remove('hide'); - } else { - miscInfo.classList.add('hide'); - } - } - - const secondaryItemMiscInfo = page.querySelectorAll('.itemMiscInfo-secondary'); - - for (const miscInfo of secondaryItemMiscInfo) { - mediaInfo.fillSecondaryMediaInfo(miscInfo, item, { - interactive: true - }); - - if (miscInfo.innerHTML && 'SeriesTimer' !== item.Type) { - miscInfo.classList.remove('hide'); - } else { - miscInfo.classList.add('hide'); - } - } + if (item.Taglines && item.Taglines.length) { + taglineElement.classList.remove('hide'); + taglineElement.innerHTML = item.Taglines[0]; + } else { + taglineElement.classList.add('hide'); } +} - function renderTagline(page, item) { - var taglineElement = page.querySelector('.tagline'); +function renderDetails(page, item, apiClient, context, isStatic) { + renderSimilarItems(page, item, context); + renderMoreFromSeason(page, item, apiClient); + renderMoreFromArtist(page, item, apiClient); + renderDirector(page, item, context); + renderWriter(page, item, context); + renderGenres(page, item, context); + renderChannelGuide(page, apiClient, item); + renderTagline(page, item); + renderOverview(page, item); + renderMiscInfo(page, item); + reloadUserDataButtons(page, item); - if (item.Taglines && item.Taglines.length) { - taglineElement.classList.remove('hide'); - taglineElement.innerHTML = item.Taglines[0]; - } else { - taglineElement.classList.add('hide'); - } - } - - function renderDetails(page, item, apiClient, context, isStatic) { - renderSimilarItems(page, item, context); - renderMoreFromSeason(page, item, apiClient); - renderMoreFromArtist(page, item, apiClient); - renderDirector(page, item, context); - renderWriter(page, item, context); - renderGenres(page, item, context); - renderChannelGuide(page, apiClient, item); - renderTagline(page, item); - renderOverview(page, item); - renderMiscInfo(page, item); - reloadUserDataButtons(page, item); + // Don't allow redirection to other websites from the TV layout + if (!layoutManager.tv) { renderLinks(page, item); - renderTags(page, item); - renderSeriesAirTime(page, item, isStatic); } - function enableScrollX() { - return browser.mobile && screen.availWidth <= 1000; + renderTags(page, item); + renderSeriesAirTime(page, item, isStatic); +} + +function enableScrollX() { + return browser.mobile && window.screen.availWidth <= 1000; +} + +function getPortraitShape(scrollX) { + if (scrollX == null) { + scrollX = enableScrollX(); } - function getPortraitShape(scrollX) { - if (null == scrollX) { - scrollX = enableScrollX(); + return scrollX ? 'overflowPortrait' : 'portrait'; +} + +function getSquareShape(scrollX) { + if (scrollX == null) { + scrollX = enableScrollX(); + } + + return scrollX ? 'overflowSquare' : 'square'; +} + +function renderMoreFromSeason(view, item, apiClient) { + const section = view.querySelector('.moreFromSeasonSection'); + + if (section) { + if (item.Type !== 'Episode' || !item.SeasonId || !item.SeriesId) { + return void section.classList.add('hide'); } - return scrollX ? 'overflowPortrait' : 'portrait'; - } - - function getSquareShape(scrollX) { - if (null == scrollX) { - scrollX = enableScrollX(); - } - - return scrollX ? 'overflowSquare' : 'square'; - } - - function renderMoreFromSeason(view, item, apiClient) { - var section = view.querySelector('.moreFromSeasonSection'); - - if (section) { - if ('Episode' !== item.Type || !item.SeasonId || !item.SeriesId) { + const userId = apiClient.getCurrentUserId(); + apiClient.getEpisodes(item.SeriesId, { + SeasonId: item.SeasonId, + UserId: userId, + Fields: 'ItemCounts,PrimaryImageAspectRatio,BasicSyncInfo,CanDelete,MediaSourceCount' + }).then(function (result) { + if (result.Items.length < 2) { return void section.classList.add('hide'); } - var userId = apiClient.getCurrentUserId(); - apiClient.getEpisodes(item.SeriesId, { - SeasonId: item.SeasonId, - UserId: userId, - Fields: 'ItemCounts,PrimaryImageAspectRatio,BasicSyncInfo,CanDelete,MediaSourceCount' - }).then(function (result) { - if (result.Items.length < 2) { - return void section.classList.add('hide'); - } - - section.classList.remove('hide'); - section.querySelector('h2').innerHTML = globalize.translate('MoreFromValue', item.SeasonName); - var itemsContainer = section.querySelector('.itemsContainer'); - cardBuilder.buildCards(result.Items, { - parentContainer: section, - itemsContainer: itemsContainer, - shape: 'autooverflow', - sectionTitleTagName: 'h2', - scalable: true, - showTitle: true, - overlayText: false, - centerText: true, - includeParentInfoInTitle: false, - allowBottomPadding: false - }); - var card = itemsContainer.querySelector('.card[data-id="' + item.Id + '"]'); - - if (card) { - setTimeout(function () { - section.querySelector('.emby-scroller').toStart(card.previousSibling || card, true); - }, 100); - } + section.classList.remove('hide'); + section.querySelector('h2').innerHTML = globalize.translate('MoreFromValue', item.SeasonName); + const itemsContainer = section.querySelector('.itemsContainer'); + cardBuilder.buildCards(result.Items, { + parentContainer: section, + itemsContainer: itemsContainer, + shape: 'autooverflow', + sectionTitleTagName: 'h2', + scalable: true, + showTitle: true, + overlayText: false, + centerText: true, + includeParentInfoInTitle: false, + allowBottomPadding: false }); - } + const card = itemsContainer.querySelector('.card[data-id="' + item.Id + '"]'); + + if (card) { + setTimeout(function () { + section.querySelector('.emby-scroller').toStart(card.previousSibling || card, true); + }, 100); + } + }); } +} - function renderMoreFromArtist(view, item, apiClient) { - var section = view.querySelector('.moreFromArtistSection'); +function renderMoreFromArtist(view, item, apiClient) { + const section = view.querySelector('.moreFromArtistSection'); - if (section) { - if ('MusicArtist' === item.Type) { - if (!apiClient.isMinServerVersion('3.4.1.19')) { - return void section.classList.add('hide'); - } - } else if ('MusicAlbum' !== item.Type || !item.AlbumArtists || !item.AlbumArtists.length) { + if (section) { + if (item.Type === 'MusicArtist') { + if (!apiClient.isMinServerVersion('3.4.1.19')) { + return void section.classList.add('hide'); + } + } else if (item.Type !== 'MusicAlbum' || !item.AlbumArtists || !item.AlbumArtists.length) { + return void section.classList.add('hide'); + } + + const query = { + IncludeItemTypes: 'MusicAlbum', + Recursive: true, + ExcludeItemIds: item.Id, + SortBy: 'ProductionYear,SortName', + SortOrder: 'Descending' + }; + + if (item.Type === 'MusicArtist') { + query.ContributingArtistIds = item.Id; + } else if (apiClient.isMinServerVersion('3.4.1.18')) { + query.AlbumArtistIds = item.AlbumArtists[0].Id; + } else { + query.ArtistIds = item.AlbumArtists[0].Id; + } + + apiClient.getItems(apiClient.getCurrentUserId(), query).then(function (result) { + if (!result.Items.length) { return void section.classList.add('hide'); } - var query = { - IncludeItemTypes: 'MusicAlbum', - Recursive: true, - ExcludeItemIds: item.Id, - SortBy: 'ProductionYear,SortName', - SortOrder: 'Descending' - }; + section.classList.remove('hide'); - if ('MusicArtist' === item.Type) { - query.ContributingArtistIds = item.Id; - } else if (apiClient.isMinServerVersion('3.4.1.18')) { - query.AlbumArtistIds = item.AlbumArtists[0].Id; + if (item.Type === 'MusicArtist') { + section.querySelector('h2').innerHTML = globalize.translate('HeaderAppearsOn'); } else { - query.ArtistIds = item.AlbumArtists[0].Id; + section.querySelector('h2').innerHTML = globalize.translate('MoreFromValue', item.AlbumArtists[0].Name); } - apiClient.getItems(apiClient.getCurrentUserId(), query).then(function (result) { - if (!result.Items.length) { - return void section.classList.add('hide'); - } - - section.classList.remove('hide'); - - if ('MusicArtist' === item.Type) { - section.querySelector('h2').innerHTML = globalize.translate('HeaderAppearsOn'); - } else { - section.querySelector('h2').innerHTML = globalize.translate('MoreFromValue', item.AlbumArtists[0].Name); - } - - cardBuilder.buildCards(result.Items, { - parentContainer: section, - itemsContainer: section.querySelector('.itemsContainer'), - shape: 'autooverflow', - sectionTitleTagName: 'h2', - scalable: true, - coverImage: 'MusicArtist' === item.Type || 'MusicAlbum' === item.Type, - showTitle: true, - showParentTitle: false, - centerText: true, - overlayText: false, - overlayPlayButton: true, - showYear: true - }); + cardBuilder.buildCards(result.Items, { + parentContainer: section, + itemsContainer: section.querySelector('.itemsContainer'), + shape: 'autooverflow', + sectionTitleTagName: 'h2', + scalable: true, + coverImage: item.Type === 'MusicArtist' || item.Type === 'MusicAlbum', + showTitle: true, + showParentTitle: false, + centerText: true, + overlayText: false, + overlayPlayButton: true, + showYear: true }); - } + }); } +} - function renderSimilarItems(page, item, context) { - var similarCollapsible = page.querySelector('#similarCollapsible'); +function renderSimilarItems(page, item, context) { + const similarCollapsible = page.querySelector('#similarCollapsible'); - if (similarCollapsible) { - if ('Movie' != item.Type && 'Trailer' != item.Type && 'Series' != item.Type && 'Program' != item.Type && 'Recording' != item.Type && 'MusicAlbum' != item.Type && 'MusicArtist' != item.Type && 'Playlist' != item.Type) { + if (similarCollapsible) { + if (item.Type != 'Movie' && item.Type != 'Trailer' && item.Type != 'Series' && item.Type != 'Program' && item.Type != 'Recording' && item.Type != 'MusicAlbum' && item.Type != 'MusicArtist' && item.Type != 'Playlist') { + return void similarCollapsible.classList.add('hide'); + } + + similarCollapsible.classList.remove('hide'); + const apiClient = window.connectionManager.getApiClient(item.ServerId); + const options = { + userId: apiClient.getCurrentUserId(), + limit: 12, + fields: 'PrimaryImageAspectRatio,UserData,CanDelete' + }; + + if (item.Type == 'MusicAlbum' && item.AlbumArtists && item.AlbumArtists.length) { + options.ExcludeArtistIds = item.AlbumArtists[0].Id; + } + + apiClient.getSimilarItems(item.Id, options).then(function (result) { + if (!result.Items.length) { return void similarCollapsible.classList.add('hide'); } similarCollapsible.classList.remove('hide'); - var apiClient = connectionManager.getApiClient(item.ServerId); - var options = { - userId: apiClient.getCurrentUserId(), - limit: 12, - fields: 'PrimaryImageAspectRatio,UserData,CanDelete' - }; - - if ('MusicAlbum' == item.Type && item.AlbumArtists && item.AlbumArtists.length) { - options.ExcludeArtistIds = item.AlbumArtists[0].Id; - } - - apiClient.getSimilarItems(item.Id, options).then(function (result) { - if (!result.Items.length) { - return void similarCollapsible.classList.add('hide'); - } - - similarCollapsible.classList.remove('hide'); - var html = ''; - html += cardBuilder.getCardsHtml({ - items: result.Items, - shape: 'autooverflow', - showParentTitle: 'MusicAlbum' == item.Type, - centerText: true, - showTitle: true, - context: context, - lazy: true, - showDetailsMenu: true, - coverImage: 'MusicAlbum' == item.Type || 'MusicArtist' == item.Type, - overlayPlayButton: true, - overlayText: false, - showYear: 'Movie' === item.Type || 'Trailer' === item.Type || 'Series' === item.Type - }); - var similarContent = similarCollapsible.querySelector('.similarContent'); - similarContent.innerHTML = html; - imageLoader.lazyChildren(similarContent); + let html = ''; + html += cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'autooverflow', + showParentTitle: item.Type == 'MusicAlbum', + centerText: true, + showTitle: true, + context: context, + lazy: true, + showDetailsMenu: true, + coverImage: item.Type == 'MusicAlbum' || item.Type == 'MusicArtist', + overlayPlayButton: true, + overlayText: false, + showYear: item.Type === 'Movie' || item.Type === 'Trailer' || item.Type === 'Series' }); - } + const similarContent = similarCollapsible.querySelector('.similarContent'); + similarContent.innerHTML = html; + imageLoader.lazyChildren(similarContent); + }); } +} - function renderSeriesAirTime(page, item, isStatic) { - var seriesAirTime = page.querySelector('#seriesAirTime'); - if ('Series' != item.Type) { - seriesAirTime.classList.add('hide'); - return; - } - var html = ''; - if (item.AirDays && item.AirDays.length) { - if (7 == item.AirDays.length) { - html += 'daily'; - } else { - html += item.AirDays.map(function (a) { - return a + 's'; - }).join(','); - } - } - if (item.AirTime) { - html += ' at ' + item.AirTime; - } - if (item.Studios.length) { - if (isStatic) { - html += ' on ' + item.Studios[0].Name; - } else { - var context = inferContext(item); - var href = appRouter.getRouteUrl(item.Studios[0], { - context: context, - itemType: 'Studio', - serverId: item.ServerId - }); - html += ' on ' + item.Studios[0].Name + ''; - } - } - if (html) { - html = ('Ended' == item.Status ? 'Aired ' : 'Airs ') + html; - seriesAirTime.innerHTML = html; - seriesAirTime.classList.remove('hide'); +function renderSeriesAirTime(page, item, isStatic) { + const seriesAirTime = page.querySelector('#seriesAirTime'); + if (item.Type != 'Series') { + seriesAirTime.classList.add('hide'); + return; + } + let html = ''; + if (item.AirDays && item.AirDays.length) { + if (item.AirDays.length == 7) { + html += 'daily'; } else { - seriesAirTime.classList.add('hide'); + html += item.AirDays.map(function (a) { + return a + 's'; + }).join(','); } } - - function renderTags(page, item) { - var itemTags = page.querySelector('.itemTags'); - var tagElements = []; - var tags = item.Tags || []; - - if ('Program' === item.Type) { - tags = []; - } - - for (var i = 0, length = tags.length; i < length; i++) { - tagElements.push(tags[i]); - } - - if (tagElements.length) { - itemTags.innerHTML = globalize.translate('TagsValue', tagElements.join(', ')); - itemTags.classList.remove('hide'); + if (item.AirTime) { + html += ' at ' + item.AirTime; + } + if (item.Studios.length) { + if (isStatic) { + html += ' on ' + item.Studios[0].Name; } else { - itemTags.innerHTML = ''; - itemTags.classList.add('hide'); + const context = inferContext(item); + const href = appRouter.getRouteUrl(item.Studios[0], { + context: context, + itemType: 'Studio', + serverId: item.ServerId + }); + html += ' on ' + item.Studios[0].Name + ''; } } + if (html) { + html = (item.Status == 'Ended' ? 'Aired ' : 'Airs ') + html; + seriesAirTime.innerHTML = html; + seriesAirTime.classList.remove('hide'); + } else { + seriesAirTime.classList.add('hide'); + } +} - function renderChildren(page, item) { - var fields = 'ItemCounts,PrimaryImageAspectRatio,BasicSyncInfo,CanDelete,MediaSourceCount'; - var query = { - ParentId: item.Id, +function renderTags(page, item) { + const itemTags = page.querySelector('.itemTags'); + const tagElements = []; + let tags = item.Tags || []; + + if (item.Type === 'Program') { + tags = []; + } + + for (let i = 0, length = tags.length; i < length; i++) { + tagElements.push(tags[i]); + } + + if (tagElements.length) { + itemTags.innerHTML = globalize.translate('TagsValue', tagElements.join(', ')); + itemTags.classList.remove('hide'); + } else { + itemTags.innerHTML = ''; + itemTags.classList.add('hide'); + } +} + +function renderChildren(page, item) { + let fields = 'ItemCounts,PrimaryImageAspectRatio,BasicSyncInfo,CanDelete,MediaSourceCount'; + const query = { + ParentId: item.Id, + Fields: fields + }; + + if (item.Type !== 'BoxSet') { + query.SortBy = 'SortName'; + } + + let promise; + const apiClient = window.connectionManager.getApiClient(item.ServerId); + const userId = apiClient.getCurrentUserId(); + + if (item.Type == 'Series') { + promise = apiClient.getSeasons(item.Id, { + userId: userId, Fields: fields - }; + }); + } else if (item.Type == 'Season') { + fields += ',Overview'; + promise = apiClient.getEpisodes(item.SeriesId, { + seasonId: item.Id, + userId: userId, + Fields: fields + }); + } else if (item.Type == 'MusicArtist') { + query.SortBy = 'ProductionYear,SortName'; + } - if ('BoxSet' !== item.Type) { - query.SortBy = 'SortName'; - } + promise = promise || apiClient.getItems(apiClient.getCurrentUserId(), query); + promise.then(function (result) { + let html = ''; + let scrollX = false; + let isList = false; + const childrenItemsContainer = page.querySelector('.childrenItemsContainer'); - var promise; - var apiClient = connectionManager.getApiClient(item.ServerId); - var userId = apiClient.getCurrentUserId(); - - if ('Series' == item.Type) { - promise = apiClient.getSeasons(item.Id, { - userId: userId, - Fields: fields + if (item.Type == 'MusicAlbum') { + const equalSet = (arr1, arr2) => arr1.every(x => arr2.indexOf(x) !== -1) && arr1.length === arr2.length; + let showArtist = false; + for (const track of result.Items) { + if (!equalSet(track.ArtistItems.map(x => x.Id), track.AlbumArtists.map(x => x.Id))) { + showArtist = true; + break; + } + } + const discNumbers = result.Items.map(x => x.ParentIndexNumber); + html = listView.getListViewHtml({ + items: result.Items, + smallIcon: true, + showIndex: new Set(discNumbers).size > 1 || (discNumbers.length >= 1 && discNumbers[0] > 1), + index: 'disc', + showIndexNumberLeft: true, + playFromHere: true, + action: 'playallfromhere', + image: false, + artist: showArtist, + containerAlbumArtists: item.AlbumArtists }); - } else if ('Season' == item.Type) { - fields += ',Overview'; - promise = apiClient.getEpisodes(item.SeriesId, { - seasonId: item.Id, - userId: userId, - Fields: fields + isList = true; + } else if (item.Type == 'Series') { + scrollX = enableScrollX(); + html = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'overflowPortrait', + showTitle: true, + centerText: true, + lazy: true, + overlayPlayButton: true, + allowBottomPadding: !scrollX }); - } else if ('MusicArtist' == item.Type) { - query.SortBy = 'ProductionYear,SortName'; - } - - promise = promise || apiClient.getItems(apiClient.getCurrentUserId(), query); - promise.then(function (result) { - var html = ''; - var scrollX = false; - var isList = false; - var childrenItemsContainer = page.querySelector('.childrenItemsContainer'); - - if ('MusicAlbum' == item.Type) { - html = listView.getListViewHtml({ - items: result.Items, - smallIcon: true, - showIndex: true, - index: 'disc', - showIndexNumberLeft: true, - playFromHere: true, - action: 'playallfromhere', - image: false, - artist: 'auto', - containerAlbumArtists: item.AlbumArtists - }); + } else if (item.Type == 'Season' || item.Type == 'Episode') { + if (item.Type !== 'Episode') { isList = true; - } else if ('Series' == item.Type) { - scrollX = enableScrollX(); + } + scrollX = item.Type == 'Episode'; + if (result.Items.length < 2 && item.Type === 'Episode') { + return; + } + + if (item.Type === 'Episode') { html = cardBuilder.getCardsHtml({ items: result.Items, - shape: 'overflowPortrait', + shape: 'overflowBackdrop', showTitle: true, - centerText: true, + displayAsSpecial: item.Type == 'Season' && item.IndexNumber, + playFromHere: true, + overlayText: true, lazy: true, + showDetailsMenu: true, overlayPlayButton: true, - allowBottomPadding: !scrollX + allowBottomPadding: !scrollX, + includeParentInfoInTitle: false + }); + } else if (item.Type === 'Season') { + html = listView.getListViewHtml({ + items: result.Items, + showIndexNumber: false, + enableOverview: true, + enablePlayedButton: layoutManager.mobile ? false : true, + infoButton: layoutManager.mobile ? false : true, + imageSize: 'large', + enableSideMediaInfo: false, + highlight: false, + action: !layoutManager.desktop ? 'link' : 'none', + imagePlayButton: true, + includeParentInfoInTitle: false }); - } else if ('Season' == item.Type || 'Episode' == item.Type) { - if ('Episode' !== item.Type) { - isList = true; - } - scrollX = 'Episode' == item.Type; - if (result.Items.length < 2 && 'Episode' === item.Type) { - return; - } - - if ('Episode' === item.Type) { - html = cardBuilder.getCardsHtml({ - items: result.Items, - shape: 'overflowBackdrop', - showTitle: true, - displayAsSpecial: 'Season' == item.Type && item.IndexNumber, - playFromHere: true, - overlayText: true, - lazy: true, - showDetailsMenu: true, - overlayPlayButton: true, - allowBottomPadding: !scrollX, - includeParentInfoInTitle: false - }); - } else if ('Season' === item.Type) { - html = listView.getListViewHtml({ - items: result.Items, - showIndexNumber: false, - enableOverview: true, - enablePlayedButton: layoutManager.mobile ? false : true, - infoButton: layoutManager.mobile ? false : true, - imageSize: 'large', - enableSideMediaInfo: false, - highlight: false, - action: !layoutManager.desktop ? 'link' : 'none', - imagePlayButton: true, - includeParentInfoInTitle: false - }); - } } + } - if ('BoxSet' !== item.Type) { - page.querySelector('#childrenCollapsible').classList.remove('hide'); - } - if (scrollX) { - childrenItemsContainer.classList.add('scrollX'); - childrenItemsContainer.classList.add('hiddenScrollX'); + if (item.Type !== 'BoxSet') { + page.querySelector('#childrenCollapsible').classList.remove('hide'); + } + if (scrollX) { + childrenItemsContainer.classList.add('scrollX'); + childrenItemsContainer.classList.add('hiddenScrollX'); + childrenItemsContainer.classList.remove('vertical-wrap'); + childrenItemsContainer.classList.remove('vertical-list'); + } else { + childrenItemsContainer.classList.remove('scrollX'); + childrenItemsContainer.classList.remove('hiddenScrollX'); + childrenItemsContainer.classList.remove('smoothScrollX'); + if (isList) { + childrenItemsContainer.classList.add('vertical-list'); childrenItemsContainer.classList.remove('vertical-wrap'); - childrenItemsContainer.classList.remove('vertical-list'); } else { - childrenItemsContainer.classList.remove('scrollX'); - childrenItemsContainer.classList.remove('hiddenScrollX'); - childrenItemsContainer.classList.remove('smoothScrollX'); - if (isList) { - childrenItemsContainer.classList.add('vertical-list'); - childrenItemsContainer.classList.remove('vertical-wrap'); - } else { - childrenItemsContainer.classList.add('vertical-wrap'); - childrenItemsContainer.classList.remove('vertical-list'); - } + childrenItemsContainer.classList.add('vertical-wrap'); + childrenItemsContainer.classList.remove('vertical-list'); } - if (layoutManager.mobile) { - childrenItemsContainer.classList.remove('padded-right'); - } - childrenItemsContainer.innerHTML = html; - imageLoader.lazyChildren(childrenItemsContainer); - if ('BoxSet' == item.Type) { - var collectionItemTypes = [{ - name: globalize.translate('HeaderVideos'), - mediaType: 'Video' - }, { - name: globalize.translate('HeaderSeries'), - type: 'Series' - }, { - name: globalize.translate('HeaderAlbums'), - type: 'MusicAlbum' - }, { - name: globalize.translate('HeaderBooks'), - type: 'Book' - }]; - renderCollectionItems(page, item, collectionItemTypes, result.Items); - } - }); - - if ('Season' == item.Type) { - page.querySelector('#childrenTitle').innerHTML = globalize.translate('HeaderEpisodes'); - } else if ('Series' == item.Type) { - page.querySelector('#childrenTitle').innerHTML = globalize.translate('HeaderSeasons'); - } else if ('MusicAlbum' == item.Type) { - page.querySelector('#childrenTitle').innerHTML = globalize.translate('HeaderTracks'); - } else { - page.querySelector('#childrenTitle').innerHTML = globalize.translate('HeaderItems'); } - - if ('MusicAlbum' == item.Type || 'Season' == item.Type) { - page.querySelector('.childrenSectionHeader').classList.add('hide'); - page.querySelector('#childrenCollapsible').classList.add('verticalSection-extrabottompadding'); - } else { - page.querySelector('.childrenSectionHeader').classList.remove('hide'); + if (layoutManager.mobile) { + childrenItemsContainer.classList.remove('padded-right'); } + childrenItemsContainer.innerHTML = html; + imageLoader.lazyChildren(childrenItemsContainer); + if (item.Type == 'BoxSet') { + const collectionItemTypes = [{ + name: globalize.translate('HeaderVideos'), + mediaType: 'Video' + }, { + name: globalize.translate('Series'), + type: 'Series' + }, { + name: globalize.translate('Albums'), + type: 'MusicAlbum' + }, { + name: globalize.translate('Books'), + type: 'Book' + }]; + renderCollectionItems(page, item, collectionItemTypes, result.Items); + } + }); + + if (item.Type == 'Season') { + page.querySelector('#childrenTitle').innerHTML = globalize.translate('Episodes'); + } else if (item.Type == 'Series') { + page.querySelector('#childrenTitle').innerHTML = globalize.translate('HeaderSeasons'); + } else if (item.Type == 'MusicAlbum') { + page.querySelector('#childrenTitle').innerHTML = globalize.translate('HeaderTracks'); + } else { + page.querySelector('#childrenTitle').innerHTML = globalize.translate('Items'); } - function renderItemsByName(page, item) { - require('scripts/itembynamedetailpage'.split(','), function () { - window.ItemsByName.renderItems(page, item); - }); + if (item.Type == 'MusicAlbum' || item.Type == 'Season') { + page.querySelector('.childrenSectionHeader').classList.add('hide'); + page.querySelector('#childrenCollapsible').classList.add('verticalSection-extrabottompadding'); + } else { + page.querySelector('.childrenSectionHeader').classList.remove('hide'); } +} - function renderPlaylistItems(page, item) { - require('scripts/playlistedit'.split(','), function () { - PlaylistViewer.render(page, item); - }); - } +function renderItemsByName(page, item) { + import('scripts/itembynamedetailpage').then(() => { + window.ItemsByName.renderItems(page, item); + }); +} - function renderProgramsForChannel(page, result) { - var html = ''; - var currentItems = []; - var currentStartDate = null; +function renderPlaylistItems(page, item) { + import('scripts/playlistedit').then(() => { + PlaylistViewer.render(page, item); + }); +} - for (var i = 0, length = result.Items.length; i < length; i++) { - var item = result.Items[i]; - var itemStartDate = datetime.parseISO8601Date(item.StartDate); +function renderProgramsForChannel(page, result) { + let html = ''; + let currentItems = []; + let currentStartDate = null; - if (!(currentStartDate && currentStartDate.toDateString() === itemStartDate.toDateString())) { - if (currentItems.length) { - html += '
'; - html += '

' + datetime.toLocaleDateString(currentStartDate, { - weekday: 'long', - month: 'long', - day: 'numeric' - }) + '

'; - html += '
' + listView.getListViewHtml({ - items: currentItems, - enableUserDataButtons: false, - showParentTitle: true, - image: false, - showProgramTime: true, - mediaInfo: false, - parentTitleWithTitle: true - }) + '
'; - } + for (let i = 0, length = result.Items.length; i < length; i++) { + const item = result.Items[i]; + const itemStartDate = datetime.parseISO8601Date(item.StartDate); - currentStartDate = itemStartDate; - currentItems = []; + if (!(currentStartDate && currentStartDate.toDateString() === itemStartDate.toDateString())) { + if (currentItems.length) { + html += '
'; + html += '

' + datetime.toLocaleDateString(currentStartDate, { + weekday: 'long', + month: 'long', + day: 'numeric' + }) + '

'; + html += '
' + listView.getListViewHtml({ + items: currentItems, + enableUserDataButtons: false, + showParentTitle: true, + image: false, + showProgramTime: true, + mediaInfo: false, + parentTitleWithTitle: true + }) + '
'; } - currentItems.push(item); + currentStartDate = itemStartDate; + currentItems = []; } - if (currentItems.length) { - html += '
'; - html += '

' + datetime.toLocaleDateString(currentStartDate, { - weekday: 'long', - month: 'long', - day: 'numeric' - }) + '

'; - html += '
' + listView.getListViewHtml({ - items: currentItems, - enableUserDataButtons: false, - showParentTitle: true, - image: false, - showProgramTime: true, - mediaInfo: false, - parentTitleWithTitle: true - }) + '
'; - } - - page.querySelector('.programGuide').innerHTML = html; + currentItems.push(item); } - function renderChannelGuide(page, apiClient, item) { - if ('TvChannel' === item.Type) { - page.querySelector('.programGuideSection').classList.remove('hide'); - apiClient.getLiveTvPrograms({ - ChannelIds: item.Id, - UserId: apiClient.getCurrentUserId(), - HasAired: false, - SortBy: 'StartDate', - EnableTotalRecordCount: false, - EnableImages: false, - ImageTypeLimit: 0, - EnableUserData: false - }).then(function (result) { - renderProgramsForChannel(page, result); - }); - } + if (currentItems.length) { + html += '
'; + html += '

' + datetime.toLocaleDateString(currentStartDate, { + weekday: 'long', + month: 'long', + day: 'numeric' + }) + '

'; + html += '
' + listView.getListViewHtml({ + items: currentItems, + enableUserDataButtons: false, + showParentTitle: true, + image: false, + showProgramTime: true, + mediaInfo: false, + parentTitleWithTitle: true + }) + '
'; } - function renderSeriesSchedule(page, item) { - var apiClient = connectionManager.getApiClient(item.ServerId); + page.querySelector('.programGuide').innerHTML = html; +} + +function renderChannelGuide(page, apiClient, item) { + if (item.Type === 'TvChannel') { + page.querySelector('.programGuideSection').classList.remove('hide'); apiClient.getLiveTvPrograms({ + ChannelIds: item.Id, UserId: apiClient.getCurrentUserId(), HasAired: false, SortBy: 'StartDate', EnableTotalRecordCount: false, EnableImages: false, ImageTypeLimit: 0, - Limit: 50, - EnableUserData: false, - LibrarySeriesId: item.Id + EnableUserData: false }).then(function (result) { - if (result.Items.length) { - page.querySelector('#seriesScheduleSection').classList.remove('hide'); - } else { - page.querySelector('#seriesScheduleSection').classList.add('hide'); - } - - page.querySelector('#seriesScheduleList').innerHTML = listView.getListViewHtml({ - items: result.Items, - enableUserDataButtons: false, - showParentTitle: false, - image: false, - showProgramDateTime: true, - mediaInfo: false, - showTitle: true, - moreButton: false, - action: 'programdialog' - }); - loading.hide(); + renderProgramsForChannel(page, result); }); } +} - function inferContext(item) { - if ('Movie' === item.Type || 'BoxSet' === item.Type) { - return 'movies'; - } - - if ('Series' === item.Type || 'Season' === item.Type || 'Episode' === item.Type) { - return 'tvshows'; - } - - if ('MusicArtist' === item.Type || 'MusicAlbum' === item.Type || 'Audio' === item.Type || 'AudioBook' === item.Type) { - return 'music'; - } - - if ('Program' === item.Type) { - return 'livetv'; - } - - return null; - } - - function filterItemsByCollectionItemType(items, typeInfo) { - return items.filter(function (item) { - if (typeInfo.mediaType) { - return item.MediaType == typeInfo.mediaType; - } - - return item.Type == typeInfo.type; - }); - } - - function canPlaySomeItemInCollection(items) { - var i = 0; - - for (var length = items.length; i < length; i++) { - if (playbackManager.canPlay(items[i])) { - return true; - } - } - - return false; - } - - function renderCollectionItems(page, parentItem, types, items) { - page.querySelector('.collectionItems').classList.remove('hide'); - page.querySelector('.collectionItems').innerHTML = ''; - - for (const type of types) { - var typeItems = filterItemsByCollectionItemType(items, type); - - if (typeItems.length) { - renderCollectionItemType(page, parentItem, type, typeItems); - } - } - - var otherType = { - name: globalize.translate('HeaderOtherItems') - }; - var otherTypeItems = items.filter(function (curr) { - return !types.filter(function (t) { - return filterItemsByCollectionItemType([curr], t).length > 0; - }).length; - }); - - if (otherTypeItems.length) { - renderCollectionItemType(page, parentItem, otherType, otherTypeItems); - } - - if (!items.length) { - renderCollectionItemType(page, parentItem, { - name: globalize.translate('HeaderItems') - }, items); - } - - var containers = page.querySelectorAll('.collectionItemsContainer'); - - var notifyRefreshNeeded = function () { - renderChildren(page, parentItem); - }; - - for (const container of containers) { - container.notifyRefreshNeeded = notifyRefreshNeeded; - } - - // if nothing in the collection can be played hide play and shuffle buttons - if (!canPlaySomeItemInCollection(items)) { - hideAll(page, 'btnPlay', false); - hideAll(page, 'btnShuffle', false); - } - - // HACK: Call autoFocuser again because btnPlay may be hidden, but focused by reloadFromItem - // FIXME: Sometimes focus does not move until all (?) sections are loaded - require(['autoFocuser'], function (autoFocuser) { - autoFocuser.autoFocus(page); - }); - } - - function renderCollectionItemType(page, parentItem, type, items) { - var html = ''; - html += '
'; - html += '
'; - html += '

'; - html += '' + type.name + ''; - html += '

'; - html += ''; - html += '
'; - html += '
'; - var shape = 'MusicAlbum' == type.type ? getSquareShape(false) : getPortraitShape(false); - html += cardBuilder.getCardsHtml({ - items: items, - shape: shape, - showTitle: true, - showYear: 'Video' === type.mediaType || 'Series' === type.type, - centerText: true, - lazy: true, - showDetailsMenu: true, - overlayMoreButton: true, - showAddToCollection: false, - showRemoveFromCollection: true, - collectionId: parentItem.Id - }); - html += '
'; - html += '
'; - var collectionItems = page.querySelector('.collectionItems'); - collectionItems.insertAdjacentHTML('beforeend', html); - imageLoader.lazyChildren(collectionItems); - collectionItems.querySelector('.btnAddToCollection').addEventListener('click', function () { - require(['alert'], function (alert) { - alert.default({ - text: globalize.translate('AddItemToCollectionHelp'), - html: globalize.translate('AddItemToCollectionHelp') + '

' + globalize.translate('ButtonLearnMore') + '' - }); - }); - }); - } - - function renderMusicVideos(page, item, user) { - connectionManager.getApiClient(item.ServerId).getItems(user.Id, { - SortBy: 'SortName', - SortOrder: 'Ascending', - IncludeItemTypes: 'MusicVideo', - Recursive: true, - Fields: 'PrimaryImageAspectRatio,BasicSyncInfo,CanDelete,MediaSourceCount', - AlbumIds: item.Id - }).then(function (result) { - if (result.Items.length) { - page.querySelector('#musicVideosCollapsible').classList.remove('hide'); - var musicVideosContent = page.querySelector('.musicVideosContent'); - musicVideosContent.innerHTML = getVideosHtml(result.Items); - imageLoader.lazyChildren(musicVideosContent); - } else { - page.querySelector('#musicVideosCollapsible').classList.add('hide'); - } - }); - } - - function renderAdditionalParts(page, item, user) { - connectionManager.getApiClient(item.ServerId).getAdditionalVideoParts(user.Id, item.Id).then(function (result) { - if (result.Items.length) { - page.querySelector('#additionalPartsCollapsible').classList.remove('hide'); - var additionalPartsContent = page.querySelector('#additionalPartsContent'); - additionalPartsContent.innerHTML = getVideosHtml(result.Items); - imageLoader.lazyChildren(additionalPartsContent); - } else { - page.querySelector('#additionalPartsCollapsible').classList.add('hide'); - } - }); - } - - function renderScenes(page, item) { - var chapters = item.Chapters || []; - - if (chapters.length && !chapters[0].ImageTag && (chapters = []), chapters.length) { - page.querySelector('#scenesCollapsible').classList.remove('hide'); - var scenesContent = page.querySelector('#scenesContent'); - - require(['chaptercardbuilder'], function (chaptercardbuilder) { - chaptercardbuilder.buildChapterCards(item, chapters, { - itemsContainer: scenesContent, - backdropShape: 'overflowBackdrop', - squareShape: 'overflowSquare', - imageBlurhashes: item.ImageBlurHashes - }); - }); +function renderSeriesSchedule(page, item) { + const apiClient = window.connectionManager.getApiClient(item.ServerId); + apiClient.getLiveTvPrograms({ + UserId: apiClient.getCurrentUserId(), + HasAired: false, + SortBy: 'StartDate', + EnableTotalRecordCount: false, + EnableImages: false, + ImageTypeLimit: 0, + Limit: 50, + EnableUserData: false, + LibrarySeriesId: item.Id + }).then(function (result) { + if (result.Items.length) { + page.querySelector('#seriesScheduleSection').classList.remove('hide'); } else { - page.querySelector('#scenesCollapsible').classList.add('hide'); + page.querySelector('#seriesScheduleSection').classList.add('hide'); } - } - function getVideosHtml(items) { - var html = cardBuilder.getCardsHtml({ - items: items, - shape: 'autooverflow', + page.querySelector('#seriesScheduleList').innerHTML = listView.getListViewHtml({ + items: result.Items, + enableUserDataButtons: false, + showParentTitle: false, + image: false, + showProgramDateTime: true, + mediaInfo: false, showTitle: true, - action: 'play', - overlayText: false, - centerText: true, - showRuntime: true + moreButton: false, + action: 'programdialog' }); + loading.hide(); + }); +} - return html; +function inferContext(item) { + if (item.Type === 'Movie' || item.Type === 'BoxSet') { + return 'movies'; } - function renderSpecials(page, item, user) { - connectionManager.getApiClient(item.ServerId).getSpecialFeatures(user.Id, item.Id).then(function (specials) { - var specialsContent = page.querySelector('#specialsContent'); - specialsContent.innerHTML = getVideosHtml(specials); - imageLoader.lazyChildren(specialsContent); - }); + if (item.Type === 'Series' || item.Type === 'Season' || item.Type === 'Episode') { + return 'tvshows'; } - function renderCast(page, item) { - var people = (item.People || []).filter(function (p) { - return p.Type === 'Actor'; - }); + if (item.Type === 'MusicArtist' || item.Type === 'MusicAlbum' || item.Type === 'Audio' || item.Type === 'AudioBook') { + return 'music'; + } - if (!people.length) { - return void page.querySelector('#castCollapsible').classList.add('hide'); + if (item.Type === 'Program') { + return 'livetv'; + } + + return null; +} + +function filterItemsByCollectionItemType(items, typeInfo) { + return items.filter(function (item) { + if (typeInfo.mediaType) { + return item.MediaType == typeInfo.mediaType; } - page.querySelector('#castCollapsible').classList.remove('hide'); - var castContent = page.querySelector('#castContent'); + return item.Type == typeInfo.type; + }); +} - require(['peoplecardbuilder'], function (peoplecardbuilder) { - peoplecardbuilder.buildPeopleCards(people, { - itemsContainer: castContent, - coverImage: true, - serverId: item.ServerId, - shape: 'overflowPortrait', +function canPlaySomeItemInCollection(items) { + let i = 0; + + for (let length = items.length; i < length; i++) { + if (playbackManager.canPlay(items[i])) { + return true; + } + } + + return false; +} + +function renderCollectionItems(page, parentItem, types, items) { + page.querySelector('.collectionItems').classList.remove('hide'); + page.querySelector('.collectionItems').innerHTML = ''; + + for (const type of types) { + const typeItems = filterItemsByCollectionItemType(items, type); + + if (typeItems.length) { + renderCollectionItemType(page, parentItem, type, typeItems); + } + } + + const otherType = { + name: globalize.translate('HeaderOtherItems') + }; + const otherTypeItems = items.filter(function (curr) { + return !types.filter(function (t) { + return filterItemsByCollectionItemType([curr], t).length > 0; + }).length; + }); + + if (otherTypeItems.length) { + renderCollectionItemType(page, parentItem, otherType, otherTypeItems); + } + + if (!items.length) { + renderCollectionItemType(page, parentItem, { + name: globalize.translate('Items') + }, items); + } + + const containers = page.querySelectorAll('.collectionItemsContainer'); + + const notifyRefreshNeeded = function () { + renderChildren(page, parentItem); + }; + + for (const container of containers) { + container.notifyRefreshNeeded = notifyRefreshNeeded; + } + + // if nothing in the collection can be played hide play and shuffle buttons + if (!canPlaySomeItemInCollection(items)) { + hideAll(page, 'btnPlay', false); + hideAll(page, 'btnShuffle', false); + } + + // HACK: Call autoFocuser again because btnPlay may be hidden, but focused by reloadFromItem + // FIXME: Sometimes focus does not move until all (?) sections are loaded + import('autoFocuser').then(({ default: autoFocuser }) => { + autoFocuser.autoFocus(page); + }); +} + +function renderCollectionItemType(page, parentItem, type, items) { + let html = ''; + html += '
'; + html += '
'; + html += '

'; + html += '' + type.name + ''; + html += '

'; + html += '
'; + html += '
'; + const shape = type.type == 'MusicAlbum' ? getSquareShape(false) : getPortraitShape(false); + html += cardBuilder.getCardsHtml({ + items: items, + shape: shape, + showTitle: true, + showYear: type.mediaType === 'Video' || type.type === 'Series', + centerText: true, + lazy: true, + showDetailsMenu: true, + overlayMoreButton: true, + showAddToCollection: false, + showRemoveFromCollection: true, + collectionId: parentItem.Id + }); + html += '
'; + html += '
'; + const collectionItems = page.querySelector('.collectionItems'); + collectionItems.insertAdjacentHTML('beforeend', html); + imageLoader.lazyChildren(collectionItems); +} + +function renderMusicVideos(page, item, user) { + window.connectionManager.getApiClient(item.ServerId).getItems(user.Id, { + SortBy: 'SortName', + SortOrder: 'Ascending', + IncludeItemTypes: 'MusicVideo', + Recursive: true, + Fields: 'PrimaryImageAspectRatio,BasicSyncInfo,CanDelete,MediaSourceCount', + AlbumIds: item.Id + }).then(function (result) { + if (result.Items.length) { + page.querySelector('#musicVideosCollapsible').classList.remove('hide'); + const musicVideosContent = page.querySelector('.musicVideosContent'); + musicVideosContent.innerHTML = getVideosHtml(result.Items); + imageLoader.lazyChildren(musicVideosContent); + } else { + page.querySelector('#musicVideosCollapsible').classList.add('hide'); + } + }); +} + +function renderAdditionalParts(page, item, user) { + window.connectionManager.getApiClient(item.ServerId).getAdditionalVideoParts(user.Id, item.Id).then(function (result) { + if (result.Items.length) { + page.querySelector('#additionalPartsCollapsible').classList.remove('hide'); + const additionalPartsContent = page.querySelector('#additionalPartsContent'); + additionalPartsContent.innerHTML = getVideosHtml(result.Items); + imageLoader.lazyChildren(additionalPartsContent); + } else { + page.querySelector('#additionalPartsCollapsible').classList.add('hide'); + } + }); +} + +function renderScenes(page, item) { + let chapters = item.Chapters || []; + + if (chapters.length && !chapters[0].ImageTag && (chapters = []), chapters.length) { + page.querySelector('#scenesCollapsible').classList.remove('hide'); + const scenesContent = page.querySelector('#scenesContent'); + + import('chaptercardbuilder').then(({ default: chaptercardbuilder }) => { + chaptercardbuilder.buildChapterCards(item, chapters, { + itemsContainer: scenesContent, + backdropShape: 'overflowBackdrop', + squareShape: 'overflowSquare', imageBlurhashes: item.ImageBlurHashes }); }); + } else { + page.querySelector('#scenesCollapsible').classList.add('hide'); + } +} + +function getVideosHtml(items) { + return cardBuilder.getCardsHtml({ + items: items, + shape: 'autooverflow', + showTitle: true, + action: 'play', + overlayText: false, + centerText: true, + showRuntime: true + }); +} + +function renderSpecials(page, item, user) { + window.connectionManager.getApiClient(item.ServerId).getSpecialFeatures(user.Id, item.Id).then(function (specials) { + const specialsContent = page.querySelector('#specialsContent'); + specialsContent.innerHTML = getVideosHtml(specials); + imageLoader.lazyChildren(specialsContent); + }); +} + +function renderCast(page, item) { + const people = (item.People || []).filter(function (p) { + return p.Type === 'Actor'; + }); + + if (!people.length) { + return void page.querySelector('#castCollapsible').classList.add('hide'); } - function itemDetailPage() { - var self = this; - self.setInitialCollapsibleState = setInitialCollapsibleState; - self.renderDetails = renderDetails; - self.renderCast = renderCast; + page.querySelector('#castCollapsible').classList.remove('hide'); + const castContent = page.querySelector('#castContent'); + + import('peoplecardbuilder').then(({ default: peoplecardbuilder }) => { + peoplecardbuilder.buildPeopleCards(people, { + itemsContainer: castContent, + coverImage: true, + serverId: item.ServerId, + shape: 'overflowPortrait', + imageBlurhashes: item.ImageBlurHashes + }); + }); +} + +function itemDetailPage() { + const self = this; + self.setInitialCollapsibleState = setInitialCollapsibleState; + self.renderDetails = renderDetails; + self.renderCast = renderCast; +} + +function bindAll(view, selector, eventName, fn) { + const elems = view.querySelectorAll(selector); + + for (const elem of elems) { + elem.addEventListener(eventName, fn); + } +} + +function onTrackSelectionsSubmit(e) { + e.preventDefault(); + return false; +} + +window.ItemDetailPage = new itemDetailPage(); + +export default function (view, params) { + function reload(instance, page, params) { + loading.show(); + + const apiClient = params.serverId ? window.connectionManager.getApiClient(params.serverId) : ApiClient; + + Promise.all([getPromise(apiClient, params), apiClient.getCurrentUser()]).then(([item, user]) => { + currentItem = item; + reloadFromItem(instance, page, params, item, user); + }).catch((error) => { + console.error('failed to get item or current user: ', error); + }); } - function bindAll(view, selector, eventName, fn) { - var elems = view.querySelectorAll(selector); - - for (const elem of elems) { - elem.addEventListener(eventName, fn); - } - } - - function onTrackSelectionsSubmit(e) { - e.preventDefault(); - return false; - } - - window.ItemDetailPage = new itemDetailPage(); - return function (view, params) { - function reload(instance, page, params) { - loading.show(); - - var apiClient = params.serverId ? connectionManager.getApiClient(params.serverId) : ApiClient; - - Promise.all([getPromise(apiClient, params), apiClient.getCurrentUser()]).then(([item, user]) => { - currentItem = item; - reloadFromItem(instance, page, params, item, user); - }).catch((error) => { - console.error('failed to get item or current user: ', error); + function splitVersions(instance, page, apiClient, params) { + import('confirm').then(({ default: confirm }) => { + confirm('Are you sure you wish to split the media sources into separate items?', 'Split Media Apart').then(function () { + loading.show(); + apiClient.ajax({ + type: 'DELETE', + url: apiClient.getUrl('Videos/' + params.id + '/AlternateSources') + }).then(function () { + loading.hide(); + reload(instance, page, params); + }); }); - } + }); + } - function splitVersions(instance, page, apiClient, params) { - require(['confirm'], function (confirm) { - confirm.default('Are you sure you wish to split the media sources into separate items?', 'Split Media Apart').then(function () { - loading.show(); - apiClient.ajax({ - type: 'DELETE', - url: apiClient.getUrl('Videos/' + params.id + '/AlternateSources') - }).then(function () { - loading.hide(); - reload(instance, page, params); - }); + function getPlayOptions(startPosition) { + const audioStreamIndex = view.querySelector('.selectAudio').value || null; + return { + startPositionTicks: startPosition, + mediaSourceId: view.querySelector('.selectSource').value, + audioStreamIndex: audioStreamIndex, + subtitleStreamIndex: view.querySelector('.selectSubtitles').value + }; + } + + function playItem(item, startPosition) { + const playOptions = getPlayOptions(startPosition); + playOptions.items = [item]; + playbackManager.play(playOptions); + } + + function playTrailer() { + playbackManager.playTrailers(currentItem); + } + + function playCurrentItem(button, mode) { + const item = currentItem; + + if (item.Type === 'Program') { + const apiClient = window.connectionManager.getApiClient(item.ServerId); + return void apiClient.getLiveTvChannel(item.ChannelId, apiClient.getCurrentUserId()).then(function (channel) { + playbackManager.play({ + items: [channel] }); }); } - function getPlayOptions(startPosition) { - var audioStreamIndex = view.querySelector('.selectAudio').value || null; - return { - startPositionTicks: startPosition, - mediaSourceId: view.querySelector('.selectSource').value, - audioStreamIndex: audioStreamIndex, - subtitleStreamIndex: view.querySelector('.selectSubtitles').value - }; - } + playItem(item, item.UserData && mode === 'resume' ? item.UserData.PlaybackPositionTicks : 0); + } - function playItem(item, startPosition) { - var playOptions = getPlayOptions(startPosition); - playOptions.items = [item]; - playbackManager.play(playOptions); - } + function onPlayClick() { + playCurrentItem(this, this.getAttribute('data-mode')); + } - function playTrailer() { - playbackManager.playTrailers(currentItem); - } + function onPosterClick(e) { + itemShortcuts.onClick.call(view.querySelector('.detailImageContainer'), e); + } - function playCurrentItem(button, mode) { - var item = currentItem; + function onInstantMixClick() { + playbackManager.instantMix(currentItem); + } - if ('Program' === item.Type) { - var apiClient = connectionManager.getApiClient(item.ServerId); - return void apiClient.getLiveTvChannel(item.ChannelId, apiClient.getCurrentUserId()).then(function (channel) { - playbackManager.play({ - items: [channel] - }); - }); - } + function onShuffleClick() { + playbackManager.shuffle(currentItem); + } - playItem(item, item.UserData && mode === 'resume' ? item.UserData.PlaybackPositionTicks : 0); - } - - function onPlayClick() { - playCurrentItem(this, this.getAttribute('data-mode')); - } - - function onInstantMixClick() { - playbackManager.instantMix(currentItem); - } - - function onShuffleClick() { - playbackManager.shuffle(currentItem); - } - - function onCancelSeriesTimerClick() { - require(['recordingHelper'], function (recordingHelper) { - recordingHelper.cancelSeriesTimerWithConfirmation(currentItem.Id, currentItem.ServerId).then(function () { - Dashboard.navigate('livetv.html'); - }); + function onCancelSeriesTimerClick() { + import('recordingHelper').then(({ default: recordingHelper }) => { + recordingHelper.cancelSeriesTimerWithConfirmation(currentItem.Id, currentItem.ServerId).then(function () { + Dashboard.navigate('livetv.html'); }); - } + }); + } - function onCancelTimerClick() { - require(['recordingHelper'], function (recordingHelper) { - recordingHelper.cancelTimer(connectionManager.getApiClient(currentItem.ServerId), currentItem.TimerId).then(function () { - reload(self, view, params); - }); + function onCancelTimerClick() { + import('recordingHelper').then(({ default: recordingHelper }) => { + recordingHelper.cancelTimer(window.connectionManager.getApiClient(currentItem.ServerId), currentItem.TimerId).then(function () { + reload(self, view, params); }); - } + }); + } - function onPlayTrailerClick() { - playTrailer(); - } + function onPlayTrailerClick() { + playTrailer(); + } - function onDownloadClick() { - require(['fileDownloader'], function (fileDownloader) { - var downloadHref = apiClient.getItemDownloadUrl(currentItem.Id); - fileDownloader.download([{ - url: downloadHref, - itemId: currentItem.Id, - serverId: currentItem.serverId - }]); - }); - } + function onDownloadClick() { + import('fileDownloader').then(({ default: fileDownloader }) => { + const downloadHref = apiClient.getItemDownloadUrl(currentItem.Id); + fileDownloader.download([{ + url: downloadHref, + itemId: currentItem.Id, + serverId: currentItem.serverId + }]); + }); + } + + function onMoreCommandsClick() { + const button = this; + let selectedItem = view.querySelector('.selectSource').value || currentItem.Id; + + apiClient.getItem(apiClient.getCurrentUserId(), selectedItem).then(function (item) { + selectedItem = item; - function onMoreCommandsClick() { - var button = this; apiClient.getCurrentUser().then(function (user) { - itemContextMenu.show(getContextMenuOptions(currentItem, user, button)).then(function (result) { + itemContextMenu.show(getContextMenuOptions(selectedItem, user, button)).then(function (result) { if (result.deleted) { appRouter.goHome(); } else if (result.updated) { @@ -1921,78 +1972,90 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti } }); }); - } + }); + } - function onPlayerChange() { - renderTrackSelections(view, self, currentItem); - setTrailerButtonVisibility(view, currentItem); - } + function onPlayerChange() { + renderTrackSelections(view, self, currentItem); + setTrailerButtonVisibility(view, currentItem); + } - function onWebSocketMessage(e, data) { - var msg = data; + function onWebSocketMessage(e, data) { + const msg = data; - if ('UserDataChanged' === msg.MessageType && currentItem && msg.Data.UserId == apiClient.getCurrentUserId()) { - var key = currentItem.UserData.Key; - var userData = msg.Data.UserDataList.filter(function (u) { - return u.Key == key; - })[0]; + if (msg.MessageType === 'UserDataChanged' && currentItem && msg.Data.UserId == apiClient.getCurrentUserId()) { + const key = currentItem.UserData.Key; + const userData = msg.Data.UserDataList.filter(function (u) { + return u.Key == key; + })[0]; - if (userData) { - currentItem.UserData = userData; - reloadPlayButtons(view, currentItem); - refreshImage(view, currentItem); - } + if (userData) { + currentItem.UserData = userData; + reloadPlayButtons(view, currentItem); + refreshImage(view, currentItem); } } + } - var currentItem; - var self = this; - var apiClient = params.serverId ? connectionManager.getApiClient(params.serverId) : ApiClient; - view.querySelectorAll('.btnPlay'); - bindAll(view, '.btnPlay', 'click', onPlayClick); - bindAll(view, '.btnResume', 'click', onPlayClick); - bindAll(view, '.btnInstantMix', 'click', onInstantMixClick); - bindAll(view, '.btnShuffle', 'click', onShuffleClick); - bindAll(view, '.btnPlayTrailer', 'click', onPlayTrailerClick); - bindAll(view, '.btnCancelSeriesTimer', 'click', onCancelSeriesTimerClick); - bindAll(view, '.btnCancelTimer', 'click', onCancelTimerClick); - bindAll(view, '.btnDownload', 'click', onDownloadClick); - view.querySelector('.trackSelections').addEventListener('submit', onTrackSelectionsSubmit); - view.querySelector('.btnSplitVersions').addEventListener('click', function () { - splitVersions(self, view, apiClient, params); - }); - bindAll(view, '.btnMoreCommands', 'click', onMoreCommandsClick); - view.querySelector('.selectSource').addEventListener('change', function () { - renderVideoSelections(view, self._currentPlaybackMediaSources); - renderAudioSelections(view, self._currentPlaybackMediaSources); - renderSubtitleSelections(view, self._currentPlaybackMediaSources); - }); - view.addEventListener('viewshow', function (e) { - var page = this; + let currentItem; + const self = this; + const apiClient = params.serverId ? window.connectionManager.getApiClient(params.serverId) : ApiClient; - libraryMenu.setTransparentMenu(true); + const btnResume = view.querySelector('.mainDetailButtons .btnResume'); + const btnPlay = view.querySelector('.mainDetailButtons .btnPlay'); + if (layoutManager.tv && !btnResume.classList.contains('hide')) { + btnResume.classList.add('fab'); + btnResume.classList.add('detailFloatingButton'); + } else if (layoutManager.tv && btnResume.classList.contains('hide')) { + btnPlay.classList.add('fab'); + btnPlay.classList.add('detailFloatingButton'); + } - if (e.detail.isRestored) { - if (currentItem) { - Emby.Page.setTitle(''); - renderTrackSelections(page, self, currentItem, true); - } - } else { - reload(self, page, params); + view.querySelectorAll('.btnPlay'); + bindAll(view, '.btnPlay', 'click', onPlayClick); + bindAll(view, '.btnResume', 'click', onPlayClick); + bindAll(view, '.btnInstantMix', 'click', onInstantMixClick); + bindAll(view, '.btnShuffle', 'click', onShuffleClick); + bindAll(view, '.btnPlayTrailer', 'click', onPlayTrailerClick); + bindAll(view, '.btnCancelSeriesTimer', 'click', onCancelSeriesTimerClick); + bindAll(view, '.btnCancelTimer', 'click', onCancelTimerClick); + bindAll(view, '.btnDownload', 'click', onDownloadClick); + view.querySelector('.detailImageContainer').addEventListener('click', onPosterClick); + view.querySelector('.trackSelections').addEventListener('submit', onTrackSelectionsSubmit); + view.querySelector('.btnSplitVersions').addEventListener('click', function () { + splitVersions(self, view, apiClient, params); + }); + bindAll(view, '.btnMoreCommands', 'click', onMoreCommandsClick); + view.querySelector('.selectSource').addEventListener('change', function () { + renderVideoSelections(view, self._currentPlaybackMediaSources); + renderAudioSelections(view, self._currentPlaybackMediaSources); + renderSubtitleSelections(view, self._currentPlaybackMediaSources); + }); + view.addEventListener('viewshow', function (e) { + const page = this; + + libraryMenu.setTransparentMenu(true); + + if (e.detail.isRestored) { + if (currentItem) { + Emby.Page.setTitle(''); + renderTrackSelections(page, self, currentItem, true); } + } else { + reload(self, page, params); + } - events.on(apiClient, 'message', onWebSocketMessage); - events.on(playbackManager, 'playerchange', onPlayerChange); - }); - view.addEventListener('viewbeforehide', function () { - events.off(apiClient, 'message', onWebSocketMessage); - events.off(playbackManager, 'playerchange', onPlayerChange); - libraryMenu.setTransparentMenu(false); - }); - view.addEventListener('viewdestroy', function () { - currentItem = null; - self._currentPlaybackMediaSources = null; - self.currentRecordingFields = null; - }); - }; -}); + events.on(apiClient, 'message', onWebSocketMessage); + events.on(playbackManager, 'playerchange', onPlayerChange); + }); + view.addEventListener('viewbeforehide', function () { + events.off(apiClient, 'message', onWebSocketMessage); + events.off(playbackManager, 'playerchange', onPlayerChange); + libraryMenu.setTransparentMenu(false); + }); + view.addEventListener('viewdestroy', function () { + currentItem = null; + self._currentPlaybackMediaSources = null; + self.currentRecordingFields = null; + }); +} diff --git a/src/list.html b/src/controllers/list.html similarity index 100% rename from src/list.html rename to src/controllers/list.html diff --git a/src/controllers/list.js b/src/controllers/list.js index e550b535f4..fe04fc18e8 100644 --- a/src/controllers/list.js +++ b/src/controllers/list.js @@ -1,15 +1,27 @@ -define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager', 'cardBuilder', 'loading', 'connectionManager', 'alphaNumericShortcuts', 'scroller', 'playbackManager', 'alphaPicker', 'emby-itemscontainer', 'emby-scroller'], function (globalize, listView, layoutManager, userSettings, focusManager, cardBuilder, loading, connectionManager, AlphaNumericShortcuts, scroller, playbackManager, AlphaPicker) { - 'use strict'; +import globalize from 'globalize'; +import listView from 'listView'; +import layoutManager from 'layoutManager'; +import * as userSettings from 'userSettings'; +import focusManager from 'focusManager'; +import cardBuilder from 'cardBuilder'; +import loading from 'loading'; +import AlphaNumericShortcuts from 'alphaNumericShortcuts'; +import playbackManager from 'playbackManager'; +import AlphaPicker from 'alphaPicker'; +import 'emby-itemscontainer'; +import 'emby-scroller'; + +/* eslint-disable indent */ function getInitialLiveTvQuery(instance, params) { - var query = { - UserId: connectionManager.getApiClient(params.serverId).getCurrentUserId(), + const query = { + UserId: window.connectionManager.getApiClient(params.serverId).getCurrentUserId(), StartIndex: 0, Fields: 'ChannelInfo,PrimaryImageAspectRatio', Limit: 300 }; - if ('Recordings' === params.type) { + if (params.type === 'Recordings') { query.IsInProgress = false; } else { query.HasAired = false; @@ -19,39 +31,39 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' query.GenreIds = params.genreId; } - if ('true' === params.IsMovie) { + if (params.IsMovie === 'true') { query.IsMovie = true; - } else if ('false' === params.IsMovie) { + } else if (params.IsMovie === 'false') { query.IsMovie = false; } - if ('true' === params.IsSeries) { + if (params.IsSeries === 'true') { query.IsSeries = true; - } else if ('false' === params.IsSeries) { + } else if (params.IsSeries === 'false') { query.IsSeries = false; } - if ('true' === params.IsNews) { + if (params.IsNews === 'true') { query.IsNews = true; - } else if ('false' === params.IsNews) { + } else if (params.IsNews === 'false') { query.IsNews = false; } - if ('true' === params.IsSports) { + if (params.IsSports === 'true') { query.IsSports = true; - } else if ('false' === params.IsSports) { + } else if (params.IsSports === 'false') { query.IsSports = false; } - if ('true' === params.IsKids) { + if (params.IsKids === 'true') { query.IsKids = true; - } else if ('false' === params.IsKids) { + } else if (params.IsKids === 'false') { query.IsKids = false; } - if ('true' === params.IsAiring) { + if (params.IsAiring === 'true') { query.IsAiring = true; - } else if ('false' === params.IsAiring) { + } else if (params.IsAiring === 'false') { query.IsAiring = false; } @@ -59,7 +71,7 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' } function modifyQueryWithFilters(instance, query) { - var sortValues = instance.getSortValues(); + const sortValues = instance.getSortValues(); if (!query.SortBy) { query.SortBy = sortValues.sortBy; @@ -68,9 +80,9 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' query.Fields = query.Fields ? query.Fields + ',PrimaryImageAspectRatio' : 'PrimaryImageAspectRatio'; query.ImageTypeLimit = 1; - var hasFilters; - var queryFilters = []; - var filters = instance.getFilters(); + let hasFilters; + const queryFilters = []; + const filters = instance.getFilters(); if (filters.IsPlayed) { queryFilters.push('IsPlayed'); @@ -164,30 +176,30 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' } function updateSortText(instance) { - var btnSortText = instance.btnSortText; + const btnSortText = instance.btnSortText; if (btnSortText) { - var options = instance.getSortMenuOptions(); - var values = instance.getSortValues(); - var sortBy = values.sortBy; + const options = instance.getSortMenuOptions(); + const values = instance.getSortValues(); + const sortBy = values.sortBy; - for (var i = 0, length = options.length; i < length; i++) { - if (sortBy === options[i].value) { - btnSortText.innerHTML = globalize.translate('SortByValue', options[i].name); + for (const option of options) { + if (sortBy === option.value) { + btnSortText.innerHTML = globalize.translate('SortByValue', option.name); break; } } - var btnSortIcon = instance.btnSortIcon; + const btnSortIcon = instance.btnSortIcon; if (btnSortIcon) { - setSortButtonIcon(btnSortIcon, 'Descending' === values.sortOrder ? 'arrow_downward' : 'arrow_upward'); + setSortButtonIcon(btnSortIcon, values.sortOrder === 'Descending' ? 'arrow_downward' : 'arrow_upward'); } } } function updateItemsContainerForViewType(instance) { - if ('list' === instance.getViewSettings().imageType) { + if (instance.getViewSettings().imageType === 'list') { instance.itemsContainer.classList.remove('vertical-wrap'); instance.itemsContainer.classList.add('vertical-list'); } else { @@ -198,16 +210,16 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' function updateAlphaPickerState(instance, numItems) { if (instance.alphaPicker) { - var alphaPicker = instance.alphaPickerElement; + const alphaPicker = instance.alphaPickerElement; if (alphaPicker) { - var values = instance.getSortValues(); + const values = instance.getSortValues(); - if (null == numItems) { + if (numItems == null) { numItems = 100; } - if ('SortName' === values.sortBy && 'Ascending' === values.sortOrder && numItems > 40) { + if (values.sortBy === 'SortName' && values.sortOrder === 'Ascending' && numItems > 40) { alphaPicker.classList.remove('hide'); instance.itemsContainer.parentNode.classList.add('padded-right-withalphapicker'); } else { @@ -219,22 +231,22 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' } function getItems(instance, params, item, sortBy, startIndex, limit) { - var apiClient = connectionManager.getApiClient(params.serverId); + const apiClient = window.connectionManager.getApiClient(params.serverId); instance.queryRecursive = false; - if ('Recordings' === params.type) { + if (params.type === 'Recordings') { return apiClient.getLiveTvRecordings(getInitialLiveTvQuery(instance, params)); } - if ('Programs' === params.type) { - if ('true' === params.IsAiring) { + if (params.type === 'Programs') { + if (params.IsAiring === 'true') { return apiClient.getLiveTvRecommendedPrograms(getInitialLiveTvQuery(instance, params)); } return apiClient.getLiveTvPrograms(getInitialLiveTvQuery(instance, params)); } - if ('nextup' === params.type) { + if (params.type === 'nextup') { return apiClient.getNextUpEpisodes(modifyQueryWithFilters(instance, { Limit: limit, Fields: 'PrimaryImageAspectRatio,SeriesInfo,DateCreated,BasicSyncInfo', @@ -248,11 +260,11 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' if (!item) { instance.queryRecursive = true; - var method = 'getItems'; + let method = 'getItems'; - if ('MusicArtist' === params.type) { + if (params.type === 'MusicArtist') { method = 'getArtists'; - } else if ('Person' === params.type) { + } else if (params.type === 'Person') { method = 'getPeople'; } @@ -261,17 +273,17 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' Limit: limit, Fields: 'PrimaryImageAspectRatio,SortName', ImageTypeLimit: 1, - IncludeItemTypes: 'MusicArtist' === params.type || 'Person' === params.type ? null : params.type, + IncludeItemTypes: params.type === 'MusicArtist' || params.type === 'Person' ? null : params.type, Recursive: true, - IsFavorite: 'true' === params.IsFavorite || null, + IsFavorite: params.IsFavorite === 'true' || null, ArtistIds: params.artistId || null, SortBy: sortBy })); } - if ('Genre' === item.Type || 'MusicGenre' === item.Type || 'Studio' === item.Type || 'Person' === item.Type) { + if (item.Type === 'Genre' || item.Type === 'MusicGenre' || item.Type === 'Studio' || item.Type === 'Person') { instance.queryRecursive = true; - var query = { + const query = { StartIndex: startIndex, Limit: limit, Fields: 'PrimaryImageAspectRatio,SortName', @@ -280,25 +292,25 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' SortBy: sortBy }; - if ('Studio' === item.Type) { + if (item.Type === 'Studio') { query.StudioIds = item.Id; - } else if ('Genre' === item.Type || 'MusicGenre' === item.Type) { + } else if (item.Type === 'Genre' || item.Type === 'MusicGenre') { query.GenreIds = item.Id; - } else if ('Person' === item.Type) { + } else if (item.Type === 'Person') { query.PersonIds = item.Id; } - if ('MusicGenre' === item.Type) { + if (item.Type === 'MusicGenre') { query.IncludeItemTypes = 'MusicAlbum'; - } else if ('GameGenre' === item.Type) { + } else if (item.Type === 'GameGenre') { query.IncludeItemTypes = 'Game'; - } else if ('movies' === item.CollectionType) { + } else if (item.CollectionType === 'movies') { query.IncludeItemTypes = 'Movie'; - } else if ('tvshows' === item.CollectionType) { + } else if (item.CollectionType === 'tvshows') { query.IncludeItemTypes = 'Series'; - } else if ('Genre' === item.Type) { + } else if (item.Type === 'Genre') { query.IncludeItemTypes = 'Movie,Series,Video'; - } else if ('Person' === item.Type) { + } else if (item.Type === 'Person') { query.IncludeItemTypes = params.type; } @@ -316,12 +328,12 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' } function getItem(params) { - if ('Recordings' === params.type || 'Programs' === params.type || 'nextup' === params.type) { + if (params.type === 'Recordings' || params.type === 'Programs' || params.type === 'nextup') { return Promise.resolve(null); } - var apiClient = connectionManager.getApiClient(params.serverId); - var itemId = params.genreId || params.musicGenreId || params.studioId || params.personId || params.parentId; + const apiClient = window.connectionManager.getApiClient(params.serverId); + const itemId = params.genreId || params.musicGenreId || params.studioId || params.personId || params.parentId; if (itemId) { return apiClient.getItem(apiClient.getCurrentUserId(), itemId); @@ -331,9 +343,9 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' } function showViewSettingsMenu() { - var instance = this; + const instance = this; - require(['viewSettings'], function (ViewSettings) { + import('viewSettings').then(({default: ViewSettings}) => { new ViewSettings().show({ settingsKey: instance.getSettingsKey(), settings: instance.getViewSettings(), @@ -346,9 +358,9 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' } function showFilterMenu() { - var instance = this; + const instance = this; - require(['filterMenu'], function (FilterMenu) { + import('filterMenu').then(({default: FilterMenu}) => { new FilterMenu().show({ settingsKey: instance.getSettingsKey(), settings: instance.getFilters(), @@ -365,9 +377,9 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' } function showSortMenu() { - var instance = this; + const instance = this; - require(['sortMenu'], function (SortMenu) { + import('sortMenu').then(({default: SortMenu}) => { new SortMenu().show({ settingsKey: instance.getSettingsKey(), settings: instance.getSortValues(), @@ -383,10 +395,10 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' } function onNewItemClick() { - var instance = this; + const instance = this; - require(['playlistEditor'], function (playlistEditor) { - new playlistEditor.showEditor({ + import('playlistEditor').then(({default: playlistEditor}) => { + new playlistEditor({ items: [], serverId: instance.params.serverId }); @@ -394,25 +406,26 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' } function hideOrShowAll(elems, hide) { - for (var i = 0, length = elems.length; i < length; i++) { + for (const elem of elems) { if (hide) { - elems[i].classList.add('hide'); + elem.classList.add('hide'); } else { - elems[i].classList.remove('hide'); + elem.classList.remove('hide'); } } } function bindAll(elems, eventName, fn) { - for (var i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener(eventName, fn); + for (const elem of elems) { + elem.addEventListener(eventName, fn); } } - function ItemsView(view, params) { +class ItemsView { + constructor(view, params) { function fetchData() { return getItems(self, params, self.currentItem).then(function (result) { - if (null == self.totalItemCount) { + if (self.totalItemCount == null) { self.totalItemCount = result.Items ? result.Items.length : result.length; } @@ -422,45 +435,45 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' } function getItemsHtml(items) { - var settings = self.getViewSettings(); + const settings = self.getViewSettings(); - if ('list' === settings.imageType) { + if (settings.imageType === 'list') { return listView.getListViewHtml({ items: items }); } - var shape; - var preferThumb; - var preferDisc; - var preferLogo; - var defaultShape; - var item = self.currentItem; - var lines = settings.showTitle ? 2 : 0; + let shape; + let preferThumb; + let preferDisc; + let preferLogo; + let defaultShape; + const item = self.currentItem; + let lines = settings.showTitle ? 2 : 0; - if ('banner' === settings.imageType) { + if (settings.imageType === 'banner') { shape = 'banner'; - } else if ('disc' === settings.imageType) { + } else if (settings.imageType === 'disc') { shape = 'square'; preferDisc = true; - } else if ('logo' === settings.imageType) { + } else if (settings.imageType === 'logo') { shape = 'backdrop'; preferLogo = true; - } else if ('thumb' === settings.imageType) { + } else if (settings.imageType === 'thumb') { shape = 'backdrop'; preferThumb = true; - } else if ('nextup' === params.type) { + } else if (params.type === 'nextup') { shape = 'backdrop'; - preferThumb = 'thumb' === settings.imageType; - } else if ('Programs' === params.type || 'Recordings' === params.type) { - shape = 'true' === params.IsMovie ? 'portrait' : 'autoVertical'; - preferThumb = 'true' !== params.IsMovie ? 'auto' : false; - defaultShape = 'true' === params.IsMovie ? 'portrait' : 'backdrop'; + preferThumb = settings.imageType === 'thumb'; + } else if (params.type === 'Programs' || params.type === 'Recordings') { + shape = params.IsMovie === 'true' ? 'portrait' : 'autoVertical'; + preferThumb = params.IsMovie !== 'true' ? 'auto' : false; + defaultShape = params.IsMovie === 'true' ? 'portrait' : 'backdrop'; } else { shape = 'autoVertical'; } - var posterOptions = { + let posterOptions = { shape: shape, showTitle: settings.showTitle, showYear: settings.showTitle, @@ -473,46 +486,46 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' overlayMoreButton: true, overlayText: !settings.showTitle, defaultShape: defaultShape, - action: 'Audio' === params.type ? 'playallfromhere' : null + action: params.type === 'Audio' ? 'playallfromhere' : null }; - if ('nextup' === params.type) { + if (params.type === 'nextup') { posterOptions.showParentTitle = settings.showTitle; - } else if ('Person' === params.type) { + } else if (params.type === 'Person') { posterOptions.showYear = false; posterOptions.showParentTitle = false; lines = 1; - } else if ('Audio' === params.type) { + } else if (params.type === 'Audio') { posterOptions.showParentTitle = settings.showTitle; - } else if ('MusicAlbum' === params.type) { + } else if (params.type === 'MusicAlbum') { posterOptions.showParentTitle = settings.showTitle; - } else if ('Episode' === params.type) { + } else if (params.type === 'Episode') { posterOptions.showParentTitle = settings.showTitle; - } else if ('MusicArtist' === params.type) { + } else if (params.type === 'MusicArtist') { posterOptions.showYear = false; lines = 1; - } else if ('Programs' === params.type) { + } else if (params.type === 'Programs') { lines = settings.showTitle ? 1 : 0; - var showParentTitle = settings.showTitle && 'true' !== params.IsMovie; + const showParentTitle = settings.showTitle && params.IsMovie !== 'true'; if (showParentTitle) { lines++; } - var showAirTime = settings.showTitle && 'Recordings' !== params.type; + const showAirTime = settings.showTitle && params.type !== 'Recordings'; if (showAirTime) { lines++; } - var showYear = settings.showTitle && 'true' === params.IsMovie && 'Recordings' === params.type; + const showYear = settings.showTitle && params.IsMovie === 'true' && params.type === 'Recordings'; if (showYear) { lines++; } posterOptions = Object.assign(posterOptions, { - inheritThumb: 'Recordings' === params.type, + inheritThumb: params.type === 'Recordings', context: 'livetv', showParentTitle: showParentTitle, showAirTime: showAirTime, @@ -529,7 +542,7 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' posterOptions.lines = lines; posterOptions.items = items; - if (item && 'folders' === item.CollectionType) { + if (item && item.CollectionType === 'folders') { posterOptions.context = 'folders'; } @@ -538,13 +551,13 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' function initAlphaPicker() { self.scroller = view.querySelector('.scrollFrameY'); - var alphaPickerElement = self.alphaPickerElement; + const alphaPickerElement = self.alphaPickerElement; alphaPickerElement.classList.add('alphaPicker-fixed-right'); alphaPickerElement.classList.add('focuscontainer-right'); self.itemsContainer.parentNode.classList.add('padded-right-withalphapicker'); - self.alphaPicker = new AlphaPicker.default({ + self.alphaPicker = new AlphaPicker({ element: alphaPickerElement, itemsContainer: layoutManager.tv ? self.itemsContainer : null, itemClass: 'card', @@ -561,7 +574,7 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' function setTitle(item) { Emby.Page.setTitle(getTitle(item) || ''); - if (item && 'playlists' === item.CollectionType) { + if (item && item.CollectionType === 'playlists') { hideOrShowAll(view.querySelectorAll('.btnNewItem'), false); } else { hideOrShowAll(view.querySelectorAll('.btnNewItem'), true); @@ -569,43 +582,43 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' } function getTitle(item) { - if ('Recordings' === params.type) { + if (params.type === 'Recordings') { return globalize.translate('Recordings'); } - if ('Programs' === params.type) { - if ('true' === params.IsMovie) { + if (params.type === 'Programs') { + if (params.IsMovie === 'true') { return globalize.translate('Movies'); } - if ('true' === params.IsSports) { + if (params.IsSports === 'true') { return globalize.translate('Sports'); } - if ('true' === params.IsKids) { + if (params.IsKids === 'true') { return globalize.translate('HeaderForKids'); } - if ('true' === params.IsAiring) { + if (params.IsAiring === 'true') { return globalize.translate('HeaderOnNow'); } - if ('true' === params.IsSeries) { + if (params.IsSeries === 'true') { return globalize.translate('Shows'); } - if ('true' === params.IsNews) { + if (params.IsNews === 'true') { return globalize.translate('News'); } return globalize.translate('Programs'); } - if ('nextup' === params.type) { + if (params.type === 'nextup') { return globalize.translate('NextUp'); } - if ('favoritemovies' === params.type) { + if (params.type === 'favoritemovies') { return globalize.translate('FavoriteMovies'); } @@ -613,35 +626,35 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' return item.Name; } - if ('Movie' === params.type) { + if (params.type === 'Movie') { return globalize.translate('Movies'); } - if ('Series' === params.type) { + if (params.type === 'Series') { return globalize.translate('Shows'); } - if ('Season' === params.type) { + if (params.type === 'Season') { return globalize.translate('Seasons'); } - if ('Episode' === params.type) { + if (params.type === 'Episode') { return globalize.translate('Episodes'); } - if ('MusicArtist' === params.type) { + if (params.type === 'MusicArtist') { return globalize.translate('Artists'); } - if ('MusicAlbum' === params.type) { + if (params.type === 'MusicAlbum') { return globalize.translate('Albums'); } - if ('Audio' === params.type) { + if (params.type === 'Audio') { return globalize.translate('Songs'); } - if ('Video' === params.type) { + if (params.type === 'Video') { return globalize.translate('Videos'); } @@ -649,7 +662,7 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' } function play() { - var currentItem = self.currentItem; + const currentItem = self.currentItem; if (currentItem && !self.hasFilters) { playbackManager.play({ @@ -665,7 +678,7 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' } function queue() { - var currentItem = self.currentItem; + const currentItem = self.currentItem; if (currentItem && !self.hasFilters) { playbackManager.queue({ @@ -681,7 +694,7 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' } function shuffle() { - var currentItem = self.currentItem; + const currentItem = self.currentItem; if (currentItem && !self.hasFilters) { playbackManager.shuffle(currentItem); @@ -694,34 +707,31 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' } } - var self = this; + const self = this; self.params = params; this.itemsContainer = view.querySelector('.itemsContainer'); if (params.parentId) { this.itemsContainer.setAttribute('data-parentid', params.parentId); - } else if ('nextup' === params.type) { + } else if (params.type === 'nextup') { this.itemsContainer.setAttribute('data-monitor', 'videoplayback'); - } else if ('favoritemovies' === params.type) { + } else if (params.type === 'favoritemovies') { this.itemsContainer.setAttribute('data-monitor', 'markfavorite'); - } else if ('Programs' === params.type) { + } else if (params.type === 'Programs') { this.itemsContainer.setAttribute('data-refreshinterval', '300000'); } - var i; - var length; - var btnViewSettings = view.querySelectorAll('.btnViewSettings'); + const btnViewSettings = view.querySelectorAll('.btnViewSettings'); - for (i = 0, length = btnViewSettings.length; i < length; i++) { - btnViewSettings[i].addEventListener('click', showViewSettingsMenu.bind(this)); + for (const btnViewSetting of btnViewSettings) { + btnViewSetting.addEventListener('click', showViewSettingsMenu.bind(this)); } - var filterButtons = view.querySelectorAll('.btnFilter'); + const filterButtons = view.querySelectorAll('.btnFilter'); this.filterButtons = filterButtons; - var hasVisibleFilters = this.getVisibleFilters().length; + const hasVisibleFilters = this.getVisibleFilters().length; - for (i = 0, length = filterButtons.length; i < length; i++) { - var btnFilter = filterButtons[i]; + for (const btnFilter of filterButtons) { btnFilter.addEventListener('click', showFilterMenu.bind(this)); if (hasVisibleFilters) { @@ -731,13 +741,13 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' } } - var sortButtons = view.querySelectorAll('.btnSort'); + const sortButtons = view.querySelectorAll('.btnSort'); - for (this.sortButtons = sortButtons, i = 0, length = sortButtons.length; i < length; i++) { - var sortButton = sortButtons[i]; + this.sortButtons = sortButtons; + for (const sortButton of sortButtons) { sortButton.addEventListener('click', showSortMenu.bind(this)); - if ('nextup' !== params.type) { + if (params.type !== 'nextup') { sortButton.classList.remove('hide'); } } @@ -749,7 +759,7 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' self.itemsContainer.fetchData = fetchData; self.itemsContainer.getItemsHtml = getItemsHtml; view.addEventListener('viewshow', function (e) { - var isRestored = e.detail.isRestored; + const isRestored = e.detail.isRestored; if (!isRestored) { loading.show(); @@ -761,7 +771,7 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' getItem(params).then(function (item) { setTitle(item); self.currentItem = item; - var refresh = !isRestored; + const refresh = !isRestored; self.itemsContainer.resume({ refresh: refresh }).then(function () { @@ -772,19 +782,19 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' } }); - if (!isRestored && item && 'PhotoAlbum' !== item.Type) { + if (!isRestored && item && item.Type !== 'PhotoAlbum') { initAlphaPicker(); } - var itemType = item ? item.Type : null; + const itemType = item ? item.Type : null; - if ('MusicGenre' === itemType || 'Programs' !== params.type && 'Channel' !== itemType) { + if (itemType === 'MusicGenre' || params.type !== 'Programs' && itemType !== 'Channel') { hideOrShowAll(view.querySelectorAll('.btnPlay'), false); } else { hideOrShowAll(view.querySelectorAll('.btnPlay'), true); } - if ('MusicGenre' === itemType || 'Programs' !== params.type && 'nextup' !== params.type && 'Channel' !== itemType) { + if (itemType === 'MusicGenre' || params.type !== 'Programs' && params.type !== 'nextup' && itemType !== 'Channel') { hideOrShowAll(view.querySelectorAll('.btnShuffle'), false); } else { hideOrShowAll(view.querySelectorAll('.btnShuffle'), true); @@ -803,18 +813,18 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' bindAll(view.querySelectorAll('.btnShuffle'), 'click', shuffle); } - self.alphaNumericShortcuts = new AlphaNumericShortcuts.default({ + self.alphaNumericShortcuts = new AlphaNumericShortcuts({ itemsContainer: self.itemsContainer }); }); view.addEventListener('viewhide', function (e) { - var itemsContainer = self.itemsContainer; + const itemsContainer = self.itemsContainer; if (itemsContainer) { itemsContainer.pause(); } - var alphaNumericShortcuts = self.alphaNumericShortcuts; + const alphaNumericShortcuts = self.alphaNumericShortcuts; if (alphaNumericShortcuts) { alphaNumericShortcuts.destroy(); @@ -842,17 +852,17 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' }); } - ItemsView.prototype.getFilters = function () { - var basekey = this.getSettingsKey(); + getFilters() { + const basekey = this.getSettingsKey(); return { - IsPlayed: 'true' === userSettings.getFilter(basekey + '-filter-IsPlayed'), - IsUnplayed: 'true' === userSettings.getFilter(basekey + '-filter-IsUnplayed'), - IsFavorite: 'true' === userSettings.getFilter(basekey + '-filter-IsFavorite'), - IsResumable: 'true' === userSettings.getFilter(basekey + '-filter-IsResumable'), - Is4K: 'true' === userSettings.getFilter(basekey + '-filter-Is4K'), - IsHD: 'true' === userSettings.getFilter(basekey + '-filter-IsHD'), - IsSD: 'true' === userSettings.getFilter(basekey + '-filter-IsSD'), - Is3D: 'true' === userSettings.getFilter(basekey + '-filter-Is3D'), + IsPlayed: userSettings.getFilter(basekey + '-filter-IsPlayed') === 'true', + IsUnplayed: userSettings.getFilter(basekey + '-filter-IsUnplayed') === 'true', + IsFavorite: userSettings.getFilter(basekey + '-filter-IsFavorite') === 'true', + IsResumable: userSettings.getFilter(basekey + '-filter-IsResumable') === 'true', + Is4K: userSettings.getFilter(basekey + '-filter-Is4K') === 'true', + IsHD: userSettings.getFilter(basekey + '-filter-IsHD') === 'true', + IsSD: userSettings.getFilter(basekey + '-filter-IsSD') === 'true', + Is3D: userSettings.getFilter(basekey + '-filter-Is3D') === 'true', VideoTypes: userSettings.getFilter(basekey + '-filter-VideoTypes'), SeriesStatus: userSettings.getFilter(basekey + '-filter-SeriesStatus'), HasSubtitles: userSettings.getFilter(basekey + '-filter-HasSubtitles'), @@ -862,39 +872,37 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' HasThemeVideo: userSettings.getFilter(basekey + '-filter-HasThemeVideo'), GenreIds: userSettings.getFilter(basekey + '-filter-GenreIds') }; - }; + } - ItemsView.prototype.getSortValues = function () { - var basekey = this.getSettingsKey(); + getSortValues() { + const basekey = this.getSettingsKey(); return { sortBy: userSettings.getFilter(basekey + '-sortby') || this.getDefaultSortBy(), - sortOrder: 'Descending' === userSettings.getFilter(basekey + '-sortorder') ? 'Descending' : 'Ascending' + sortOrder: userSettings.getFilter(basekey + '-sortorder') === 'Descending' ? 'Descending' : 'Ascending' }; - }; + } - ItemsView.prototype.getDefaultSortBy = function () { - var params = this.params; - var sortNameOption = this.getNameSortOption(params); + getDefaultSortBy() { + const sortNameOption = this.getNameSortOption(this.params); - if (params.type) { + if (this.params.type) { return sortNameOption.value; } return 'IsFolder,' + sortNameOption.value; - }; + } - ItemsView.prototype.getSortMenuOptions = function () { - var sortBy = []; - var params = this.params; + getSortMenuOptions() { + const sortBy = []; - if ('Programs' === params.type) { + if (this.params.type === 'Programs') { sortBy.push({ name: globalize.translate('AirDate'), value: 'StartDate,SortName' }); } - var option = this.getNameSortOption(params); + let option = this.getNameSortOption(this.params); if (option) { sortBy.push(option); @@ -912,7 +920,7 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' sortBy.push(option); } - if ('Programs' !== params.type) { + if (this.params.type !== 'Programs') { sortBy.push({ name: globalize.translate('DateAdded'), value: 'DateCreated,SortName' @@ -925,8 +933,8 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' sortBy.push(option); } - if (!params.type) { - option = this.getNameSortOption(params); + if (!this.params.type) { + option = this.getNameSortOption(this.params); sortBy.push({ name: globalize.translate('Folders'), value: 'IsFolder,' + option.value @@ -952,10 +960,10 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' value: 'Runtime,SortName' }); return sortBy; - }; + } - ItemsView.prototype.getNameSortOption = function (params) { - if ('Episode' === params.type) { + getNameSortOption(params) { + if (params.type === 'Episode') { return { name: globalize.translate('Name'), value: 'SeriesName,SortName' @@ -966,10 +974,10 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' name: globalize.translate('Name'), value: 'SortName' }; - }; + } - ItemsView.prototype.getPlayCountSortOption = function () { - if ('Programs' === this.params.type) { + getPlayCountSortOption() { + if (this.params.type === 'Programs') { return null; } @@ -977,10 +985,10 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' name: globalize.translate('PlayCount'), value: 'PlayCount,SortName' }; - }; + } - ItemsView.prototype.getDatePlayedSortOption = function () { - if ('Programs' === this.params.type) { + getDatePlayedSortOption() { + if (this.params.type === 'Programs') { return null; } @@ -988,10 +996,10 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' name: globalize.translate('DatePlayed'), value: 'DatePlayed,SortName' }; - }; + } - ItemsView.prototype.getCriticRatingSortOption = function () { - if ('Programs' === this.params.type) { + getCriticRatingSortOption() { + if (this.params.type === 'Programs') { return null; } @@ -999,21 +1007,21 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' name: globalize.translate('CriticRating'), value: 'CriticRating,SortName' }; - }; + } - ItemsView.prototype.getCommunityRatingSortOption = function () { + getCommunityRatingSortOption() { return { name: globalize.translate('CommunityRating'), value: 'CommunityRating,SortName' }; - }; + } - ItemsView.prototype.getVisibleFilters = function () { - var filters = []; - var params = this.params; + getVisibleFilters() { + const filters = []; + const params = this.params; - if (!('nextup' === params.type)) { - if ('Programs' === params.type) { + if (!(params.type === 'nextup')) { + if (params.type === 'Programs') { filters.push('Genres'); } else { filters.push('IsUnplayed'); @@ -1034,16 +1042,15 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' } return filters; - }; + } - ItemsView.prototype.setFilterStatus = function (hasFilters) { + setFilterStatus(hasFilters) { this.hasFilters = hasFilters; - var filterButtons = this.filterButtons; + const filterButtons = this.filterButtons; if (filterButtons.length) { - for (var i = 0, length = filterButtons.length; i < length; i++) { - var btnFilter = filterButtons[i]; - var bubble = btnFilter.querySelector('.filterButtonBubble'); + for (const btnFilter of filterButtons) { + let bubble = btnFilter.querySelector('.filterButtonBubble'); if (!bubble) { if (!hasFilters) { @@ -1062,10 +1069,10 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' } } } - }; + } - ItemsView.prototype.getFilterMenuOptions = function () { - var params = this.params; + getFilterMenuOptions() { + const params = this.params; return { IsAiring: params.IsAiring, IsMovie: params.IsMovie, @@ -1075,68 +1082,68 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' IsSeries: params.IsSeries, Recursive: this.queryRecursive }; - }; + } - ItemsView.prototype.getVisibleViewSettings = function () { - var item = (this.params, this.currentItem); - var fields = ['showTitle']; + getVisibleViewSettings() { + const item = (this.params, this.currentItem); + const fields = ['showTitle']; - if (!item || 'PhotoAlbum' !== item.Type && 'ChannelFolderItem' !== item.Type) { + if (!item || item.Type !== 'PhotoAlbum' && item.Type !== 'ChannelFolderItem') { fields.push('imageType'); } fields.push('viewType'); return fields; - }; + } - ItemsView.prototype.getViewSettings = function () { - var basekey = this.getSettingsKey(); - var params = this.params; - var item = this.currentItem; - var showTitle = userSettings.get(basekey + '-showTitle'); + getViewSettings() { + const basekey = this.getSettingsKey(); + const params = this.params; + const item = this.currentItem; + let showTitle = userSettings.get(basekey + '-showTitle'); - if ('true' === showTitle) { + if (showTitle === 'true') { showTitle = true; - } else if ('false' === showTitle) { + } else if (showTitle === 'false') { showTitle = false; - } else if ('Programs' === params.type || 'Recordings' === params.type || 'Person' === params.type || 'nextup' === params.type || 'Audio' === params.type || 'MusicAlbum' === params.type || 'MusicArtist' === params.type) { + } else if (params.type === 'Programs' || params.type === 'Recordings' || params.type === 'Person' || params.type === 'nextup' || params.type === 'Audio' || params.type === 'MusicAlbum' || params.type === 'MusicArtist') { showTitle = true; - } else if (item && 'PhotoAlbum' !== item.Type) { + } else if (item && item.Type !== 'PhotoAlbum') { showTitle = true; } - var imageType = userSettings.get(basekey + '-imageType'); + let imageType = userSettings.get(basekey + '-imageType'); - if (!imageType && 'nextup' === params.type) { + if (!imageType && params.type === 'nextup') { imageType = 'thumb'; } return { showTitle: showTitle, - showYear: 'false' !== userSettings.get(basekey + '-showYear'), + showYear: userSettings.get(basekey + '-showYear') !== 'false', imageType: imageType || 'primary', viewType: userSettings.get(basekey + '-viewType') || 'images' }; - }; + } - ItemsView.prototype.getItemTypes = function () { - var params = this.params; + getItemTypes() { + const params = this.params; - if ('nextup' === params.type) { + if (params.type === 'nextup') { return ['Episode']; } - if ('Programs' === params.type) { + if (params.type === 'Programs') { return ['Program']; } return []; - }; + } - ItemsView.prototype.getSettingsKey = function () { - var values = []; + getSettingsKey() { + const values = []; values.push('items'); - var params = this.params; + const params = this.params; if (params.type) { values.push(params.type); @@ -1193,7 +1200,9 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' } return values.join('-'); - }; + } +} - return ItemsView; -}); +export default ItemsView; + +/* eslint-enable indent */ diff --git a/src/livetv.html b/src/controllers/livetv.html similarity index 95% rename from src/livetv.html rename to src/controllers/livetv.html index fdf19bd915..fa6c3125d5 100644 --- a/src/livetv.html +++ b/src/controllers/livetv.html @@ -1,4 +1,4 @@ -
+
@@ -15,7 +15,7 @@
@@ -24,7 +24,7 @@
@@ -63,7 +63,7 @@
- +
diff --git a/src/controllers/livetv/livetvchannels.js b/src/controllers/livetv/livetvchannels.js index 62906d9d21..278200c634 100644 --- a/src/controllers/livetv/livetvchannels.js +++ b/src/controllers/livetv/livetvchannels.js @@ -1,130 +1,134 @@ -define(['cardBuilder', 'imageLoader', 'libraryBrowser', 'loading', 'events', 'userSettings', 'emby-itemscontainer'], function (cardBuilder, imageLoader, libraryBrowser, loading, events, userSettings) { - 'use strict'; +import cardBuilder from 'cardBuilder'; +import imageLoader from 'imageLoader'; +import libraryBrowser from 'libraryBrowser'; +import loading from 'loading'; +import events from 'events'; +import * as userSettings from 'userSettings'; +import 'emby-itemscontainer'; - return function (view, params, tabContent) { - function getPageData() { - if (!pageData) { - pageData = { - query: { - StartIndex: 0, - Fields: 'PrimaryImageAspectRatio' - } - }; +export default function (view, params, tabContent) { + function getPageData() { + if (!pageData) { + pageData = { + query: { + StartIndex: 0, + Fields: 'PrimaryImageAspectRatio' + } + }; + } + + if (userSettings.libraryPageSize() > 0) { + pageData.query['Limit'] = userSettings.libraryPageSize(); + } + + return pageData; + } + + function getQuery() { + return getPageData().query; + } + + function getChannelsHtml(channels) { + return cardBuilder.getCardsHtml({ + items: channels, + shape: 'square', + showTitle: true, + lazy: true, + cardLayout: true, + showDetailsMenu: true, + showCurrentProgram: true, + showCurrentProgramTime: true + }); + } + + function renderChannels(context, result) { + function onNextPageClick() { + if (isLoading) { + return; } if (userSettings.libraryPageSize() > 0) { - pageData.query['Limit'] = userSettings.libraryPageSize(); + query.StartIndex += query.Limit; + } + reloadItems(context); + } + + function onPreviousPageClick() { + if (isLoading) { + return; } - return pageData; - } - - function getQuery() { - return getPageData().query; - } - - function getChannelsHtml(channels) { - return cardBuilder.getCardsHtml({ - items: channels, - shape: 'square', - showTitle: true, - lazy: true, - cardLayout: true, - showDetailsMenu: true, - showCurrentProgram: true, - showCurrentProgramTime: true - }); - } - - function renderChannels(context, result) { - function onNextPageClick() { - if (isLoading) { - return; - } - - if (userSettings.libraryPageSize() > 0) { - query.StartIndex += query.Limit; - } - reloadItems(context); - } - - function onPreviousPageClick() { - if (isLoading) { - return; - } - - if (userSettings.libraryPageSize() > 0) { - query.StartIndex = Math.max(0, query.StartIndex - query.Limit); - } - reloadItems(context); - } - - var query = getQuery(); - context.querySelector('.paging').innerHTML = libraryBrowser.getQueryPagingHtml({ - startIndex: query.StartIndex, - limit: query.Limit, - totalRecordCount: result.TotalRecordCount, - showLimit: false, - updatePageSizeSetting: false, - filterButton: false - }); - var html = getChannelsHtml(result.Items); - var elem = context.querySelector('#items'); - elem.innerHTML = html; - imageLoader.lazyChildren(elem); - var i; - var length; - var elems; - - for (elems = context.querySelectorAll('.btnNextPage'), i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener('click', onNextPageClick); - } - - for (elems = context.querySelectorAll('.btnPreviousPage'), i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener('click', onPreviousPageClick); + if (userSettings.libraryPageSize() > 0) { + query.StartIndex = Math.max(0, query.StartIndex - query.Limit); } + reloadItems(context); } - function showFilterMenu(context) { - require(['components/filterdialog/filterdialog'], function ({default: filterDialogFactory}) { - var filterDialog = new filterDialogFactory({ - query: getQuery(), - mode: 'livetvchannels', - serverId: ApiClient.serverId() - }); - events.on(filterDialog, 'filterchange', function () { - reloadItems(context); - }); - filterDialog.show(); - }); - } - - function reloadItems(context, save) { - loading.show(); - isLoading = true; - var query = getQuery(); - var apiClient = ApiClient; - query.UserId = apiClient.getCurrentUserId(); - apiClient.getLiveTvChannels(query).then(function (result) { - renderChannels(context, result); - loading.hide(); - isLoading = false; - - require(['autoFocuser'], function (autoFocuser) { - autoFocuser.autoFocus(view); - }); - }); - } - - var pageData; - var self = this; - var isLoading = false; - tabContent.querySelector('.btnFilter').addEventListener('click', function () { - showFilterMenu(tabContent); + const query = getQuery(); + context.querySelector('.paging').innerHTML = libraryBrowser.getQueryPagingHtml({ + startIndex: query.StartIndex, + limit: query.Limit, + totalRecordCount: result.TotalRecordCount, + showLimit: false, + updatePageSizeSetting: false, + filterButton: false }); + const html = getChannelsHtml(result.Items); + const elem = context.querySelector('#items'); + elem.innerHTML = html; + imageLoader.lazyChildren(elem); + let i; + let length; + let elems; - self.renderTab = function () { - reloadItems(tabContent); - }; + for (elems = context.querySelectorAll('.btnNextPage'), i = 0, length = elems.length; i < length; i++) { + elems[i].addEventListener('click', onNextPageClick); + } + + for (elems = context.querySelectorAll('.btnPreviousPage'), i = 0, length = elems.length; i < length; i++) { + elems[i].addEventListener('click', onPreviousPageClick); + } + } + + function showFilterMenu(context) { + import(['components/filterdialog/filterdialog']).then(({default: FilterDialog}) => { + const filterDialog = new FilterDialog({ + query: getQuery(), + mode: 'livetvchannels', + serverId: ApiClient.serverId() + }); + events.on(filterDialog, 'filterchange', function () { + reloadItems(context); + }); + filterDialog.show(); + }); + } + + function reloadItems(context, save) { + loading.show(); + isLoading = true; + const query = getQuery(); + const apiClient = ApiClient; + query.UserId = apiClient.getCurrentUserId(); + apiClient.getLiveTvChannels(query).then(function (result) { + renderChannels(context, result); + loading.hide(); + isLoading = false; + + import('autoFocuser').then(({default: autoFocuser}) => { + autoFocuser.autoFocus(view); + }); + }); + } + + let pageData; + const self = this; + let isLoading = false; + tabContent.querySelector('.btnFilter').addEventListener('click', function () { + showFilterMenu(tabContent); + }); + + self.renderTab = function () { + reloadItems(tabContent); }; -}); +} diff --git a/src/controllers/livetv/livetvguide.js b/src/controllers/livetv/livetvguide.js index ec7a7a3f81..f8b49bd22a 100644 --- a/src/controllers/livetv/livetvguide.js +++ b/src/controllers/livetv/livetvguide.js @@ -1,29 +1,27 @@ -define(['tvguide'], function (tvguide) { - 'use strict'; +import tvguide from 'tvguide'; - return function (view, params, tabContent) { - var guideInstance; - var self = this; +export default function (view, params, tabContent) { + let guideInstance; + const self = this; - self.renderTab = function () { - if (!guideInstance) { - guideInstance = new tvguide({ - element: tabContent, - serverId: ApiClient.serverId() - }); - } - }; - - self.onShow = function () { - if (guideInstance) { - guideInstance.resume(); - } - }; - - self.onHide = function () { - if (guideInstance) { - guideInstance.pause(); - } - }; + self.renderTab = function () { + if (!guideInstance) { + guideInstance = new tvguide({ + element: tabContent, + serverId: ApiClient.serverId() + }); + } }; -}); + + self.onShow = function () { + if (guideInstance) { + guideInstance.resume(); + } + }; + + self.onHide = function () { + if (guideInstance) { + guideInstance.pause(); + } + }; +} diff --git a/src/controllers/livetv/livetvrecordings.js b/src/controllers/livetv/livetvrecordings.js index d5cfe66672..ec2f57e14f 100644 --- a/src/controllers/livetv/livetvrecordings.js +++ b/src/controllers/livetv/livetvrecordings.js @@ -1,110 +1,112 @@ -define(['layoutManager', 'loading', 'cardBuilder', 'apphost', 'imageLoader', 'scripts/livetvcomponents', 'listViewStyle', 'emby-itemscontainer'], function (layoutManager, loading, cardBuilder, appHost, imageLoader) { - 'use strict'; +import loading from 'loading'; +import cardBuilder from 'cardBuilder'; +import imageLoader from 'imageLoader'; +import 'scripts/livetvcomponents'; +import 'listViewStyle'; +import 'emby-itemscontainer'; - function renderRecordings(elem, recordings, cardOptions, scrollX) { - if (!elem) { - return; - } - - if (recordings.length) { - elem.classList.remove('hide'); - } else { - elem.classList.add('hide'); - } - - var recordingItems = elem.querySelector('.recordingItems'); - - if (scrollX) { - recordingItems.classList.add('scrollX'); - recordingItems.classList.add('hiddenScrollX'); - recordingItems.classList.remove('vertical-wrap'); - } else { - recordingItems.classList.remove('scrollX'); - recordingItems.classList.remove('hiddenScrollX'); - recordingItems.classList.add('vertical-wrap'); - } - - appHost.supports('imageanalysis'); - recordingItems.innerHTML = cardBuilder.getCardsHtml(Object.assign({ - items: recordings, - shape: scrollX ? 'autooverflow' : 'auto', - defaultShape: scrollX ? 'overflowBackdrop' : 'backdrop', - showTitle: true, - showParentTitle: true, - coverImage: true, - cardLayout: false, - centerText: true, - allowBottomPadding: !scrollX, - preferThumb: 'auto', - overlayText: false - }, cardOptions || {})); - imageLoader.lazyChildren(recordingItems); +function renderRecordings(elem, recordings, cardOptions, scrollX) { + if (!elem) { + return; } - function renderLatestRecordings(context, promise) { - promise.then(function (result) { - renderRecordings(context.querySelector('#latestRecordings'), result.Items, { - showYear: true, - lines: 2 - }, false); - loading.hide(); - }); + if (recordings.length) { + elem.classList.remove('hide'); + } else { + elem.classList.add('hide'); } - function renderRecordingFolders(context, promise) { - promise.then(function (result) { - renderRecordings(context.querySelector('#recordingFolders'), result.Items, { - showYear: false, - showParentTitle: false - }, false); - }); + const recordingItems = elem.querySelector('.recordingItems'); + + if (scrollX) { + recordingItems.classList.add('scrollX'); + recordingItems.classList.add('hiddenScrollX'); + recordingItems.classList.remove('vertical-wrap'); + } else { + recordingItems.classList.remove('scrollX'); + recordingItems.classList.remove('hiddenScrollX'); + recordingItems.classList.add('vertical-wrap'); } - function onMoreClick(e) { - var type = this.getAttribute('data-type'); - var serverId = ApiClient.serverId(); + recordingItems.innerHTML = cardBuilder.getCardsHtml(Object.assign({ + items: recordings, + shape: scrollX ? 'autooverflow' : 'auto', + defaultShape: scrollX ? 'overflowBackdrop' : 'backdrop', + showTitle: true, + showParentTitle: true, + coverImage: true, + cardLayout: false, + centerText: true, + allowBottomPadding: !scrollX, + preferThumb: 'auto', + overlayText: false + }, cardOptions || {})); + imageLoader.lazyChildren(recordingItems); +} - switch (type) { - case 'latest': - Dashboard.navigate('list.html?type=Recordings&serverId=' + serverId); - } +function renderLatestRecordings(context, promise) { + promise.then(function (result) { + renderRecordings(context.querySelector('#latestRecordings'), result.Items, { + showYear: true, + lines: 2 + }, false); + loading.hide(); + }); +} + +function renderRecordingFolders(context, promise) { + promise.then(function (result) { + renderRecordings(context.querySelector('#recordingFolders'), result.Items, { + showYear: false, + showParentTitle: false + }, false); + }); +} + +function onMoreClick(e) { + const type = this.getAttribute('data-type'); + const serverId = ApiClient.serverId(); + + switch (type) { + case 'latest': + Dashboard.navigate('list.html?type=Recordings&serverId=' + serverId); + } +} + +export default function (view, params, tabContent) { + function enableFullRender() { + return new Date().getTime() - lastFullRender > 300000; } - return function (view, params, tabContent) { - function enableFullRender() { - return new Date().getTime() - lastFullRender > 300000; + let foldersPromise; + let latestPromise; + const self = this; + let lastFullRender = 0; + const moreButtons = tabContent.querySelectorAll('.more'); + + for (let i = 0, length = moreButtons.length; i < length; i++) { + moreButtons[i].addEventListener('click', onMoreClick); + } + + self.preRender = function () { + if (enableFullRender()) { + latestPromise = ApiClient.getLiveTvRecordings({ + UserId: Dashboard.getCurrentUserId(), + Limit: 12, + Fields: 'CanDelete,PrimaryImageAspectRatio,BasicSyncInfo', + EnableTotalRecordCount: false, + EnableImageTypes: 'Primary,Thumb,Backdrop' + }); + foldersPromise = ApiClient.getRecordingFolders(Dashboard.getCurrentUserId()); } - - var foldersPromise; - var latestPromise; - var self = this; - var lastFullRender = 0; - var moreButtons = tabContent.querySelectorAll('.more'); - - for (var i = 0, length = moreButtons.length; i < length; i++) { - moreButtons[i].addEventListener('click', onMoreClick); - } - - self.preRender = function () { - if (enableFullRender()) { - latestPromise = ApiClient.getLiveTvRecordings({ - UserId: Dashboard.getCurrentUserId(), - Limit: 12, - Fields: 'CanDelete,PrimaryImageAspectRatio,BasicSyncInfo', - EnableTotalRecordCount: false, - EnableImageTypes: 'Primary,Thumb,Backdrop' - }); - foldersPromise = ApiClient.getRecordingFolders(Dashboard.getCurrentUserId()); - } - }; - - self.renderTab = function () { - if (enableFullRender()) { - loading.show(); - renderLatestRecordings(tabContent, latestPromise); - renderRecordingFolders(tabContent, foldersPromise); - lastFullRender = new Date().getTime(); - } - }; }; -}); + + self.renderTab = function () { + if (enableFullRender()) { + loading.show(); + renderLatestRecordings(tabContent, latestPromise); + renderRecordingFolders(tabContent, foldersPromise); + lastFullRender = new Date().getTime(); + } + }; +} diff --git a/src/controllers/livetv/livetvschedule.js b/src/controllers/livetv/livetvschedule.js index a6f509c6f1..d7bfbad059 100644 --- a/src/controllers/livetv/livetvschedule.js +++ b/src/controllers/livetv/livetvschedule.js @@ -1,123 +1,124 @@ -define(['layoutManager', 'cardBuilder', 'apphost', 'imageLoader', 'loading', 'scripts/livetvcomponents', 'emby-button', 'emby-itemscontainer'], function (layoutManager, cardBuilder, appHost, imageLoader, loading) { - 'use strict'; +import layoutManager from 'layoutManager'; +import cardBuilder from 'cardBuilder'; +import imageLoader from 'imageLoader'; +import loading from 'loading'; +import 'scripts/livetvcomponents'; +import 'emby-button'; +import 'emby-itemscontainer'; - function enableScrollX() { - return !layoutManager.desktop; +function enableScrollX() { + return !layoutManager.desktop; +} + +function renderRecordings(elem, recordings, cardOptions) { + if (recordings.length) { + elem.classList.remove('hide'); + } else { + elem.classList.add('hide'); } - function renderRecordings(elem, recordings, cardOptions) { - if (recordings.length) { + const recordingItems = elem.querySelector('.recordingItems'); + + if (enableScrollX()) { + recordingItems.classList.add('scrollX'); + + if (layoutManager.tv) { + recordingItems.classList.add('smoothScrollX'); + } + + recordingItems.classList.add('hiddenScrollX'); + recordingItems.classList.remove('vertical-wrap'); + } else { + recordingItems.classList.remove('scrollX'); + recordingItems.classList.remove('smoothScrollX'); + recordingItems.classList.remove('hiddenScrollX'); + recordingItems.classList.add('vertical-wrap'); + } + + recordingItems.innerHTML = cardBuilder.getCardsHtml(Object.assign({ + items: recordings, + shape: enableScrollX() ? 'autooverflow' : 'auto', + showTitle: true, + showParentTitle: true, + coverImage: true, + cardLayout: false, + centerText: true, + allowBottomPadding: !enableScrollX(), + preferThumb: 'auto' + }, cardOptions || {})); + imageLoader.lazyChildren(recordingItems); +} + +function getBackdropShape() { + return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; +} + +function renderActiveRecordings(context, promise) { + promise.then(function (result) { + renderRecordings(context.querySelector('#activeRecordings'), result.Items, { + shape: enableScrollX() ? 'autooverflow' : 'auto', + defaultShape: getBackdropShape(), + showParentTitle: false, + showParentTitleOrTitle: true, + showTitle: false, + showAirTime: true, + showAirEndTime: true, + showChannelName: true, + coverImage: true, + overlayText: false, + overlayMoreButton: true + }); + }); +} + +function renderTimers(context, timers, options) { + LiveTvHelpers.getTimersHtml(timers, options).then(function (html) { + const elem = context; + + if (html) { elem.classList.remove('hide'); } else { elem.classList.add('hide'); } - var recordingItems = elem.querySelector('.recordingItems'); + elem.querySelector('.recordingItems').innerHTML = html; + imageLoader.lazyChildren(elem); + }); +} - if (enableScrollX()) { - recordingItems.classList.add('scrollX'); +function renderUpcomingRecordings(context, promise) { + promise.then(function (result) { + renderTimers(context.querySelector('#upcomingRecordings'), result.Items); + loading.hide(); + }); +} - if (layoutManager.tv) { - recordingItems.classList.add('smoothScrollX'); - } +export default function (view, params, tabContent) { + let activeRecordingsPromise; + let upcomingRecordingsPromise; + const self = this; + tabContent.querySelector('#upcomingRecordings .recordingItems').addEventListener('timercancelled', function () { + self.preRender(); + self.renderTab(); + }); - recordingItems.classList.add('hiddenScrollX'); - recordingItems.classList.remove('vertical-wrap'); - } else { - recordingItems.classList.remove('scrollX'); - recordingItems.classList.remove('smoothScrollX'); - recordingItems.classList.remove('hiddenScrollX'); - recordingItems.classList.add('vertical-wrap'); - } - - var supportsImageAnalysis = appHost.supports('imageanalysis'); - var cardLayout = appHost.preferVisualCards || supportsImageAnalysis; - cardLayout = false; - recordingItems.innerHTML = cardBuilder.getCardsHtml(Object.assign({ - items: recordings, - shape: enableScrollX() ? 'autooverflow' : 'auto', - showTitle: true, - showParentTitle: true, - coverImage: true, - cardLayout: cardLayout, - centerText: !cardLayout, - allowBottomPadding: !enableScrollX(), - preferThumb: 'auto' - }, cardOptions || {})); - imageLoader.lazyChildren(recordingItems); - } - - function getBackdropShape() { - return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; - } - - function renderActiveRecordings(context, promise) { - promise.then(function (result) { - renderRecordings(context.querySelector('#activeRecordings'), result.Items, { - shape: enableScrollX() ? 'autooverflow' : 'auto', - defaultShape: getBackdropShape(), - showParentTitle: false, - showParentTitleOrTitle: true, - showTitle: false, - showAirTime: true, - showAirEndTime: true, - showChannelName: true, - coverImage: true, - overlayText: false, - overlayMoreButton: true - }); + self.preRender = function () { + activeRecordingsPromise = ApiClient.getLiveTvRecordings({ + UserId: Dashboard.getCurrentUserId(), + IsInProgress: true, + Fields: 'CanDelete,PrimaryImageAspectRatio,BasicSyncInfo', + EnableTotalRecordCount: false, + EnableImageTypes: 'Primary,Thumb,Backdrop' }); - } - - function renderTimers(context, timers, options) { - LiveTvHelpers.getTimersHtml(timers, options).then(function (html) { - var elem = context; - - if (html) { - elem.classList.remove('hide'); - } else { - elem.classList.add('hide'); - } - - elem.querySelector('.recordingItems').innerHTML = html; - imageLoader.lazyChildren(elem); + upcomingRecordingsPromise = ApiClient.getLiveTvTimers({ + IsActive: false, + IsScheduled: true }); - } - - function renderUpcomingRecordings(context, promise) { - promise.then(function (result) { - renderTimers(context.querySelector('#upcomingRecordings'), result.Items); - loading.hide(); - }); - } - - return function (view, params, tabContent) { - var activeRecordingsPromise; - var upcomingRecordingsPromise; - var self = this; - tabContent.querySelector('#upcomingRecordings .recordingItems').addEventListener('timercancelled', function () { - self.preRender(); - self.renderTab(); - }); - - self.preRender = function () { - activeRecordingsPromise = ApiClient.getLiveTvRecordings({ - UserId: Dashboard.getCurrentUserId(), - IsInProgress: true, - Fields: 'CanDelete,PrimaryImageAspectRatio,BasicSyncInfo', - EnableTotalRecordCount: false, - EnableImageTypes: 'Primary,Thumb,Backdrop' - }); - upcomingRecordingsPromise = ApiClient.getLiveTvTimers({ - IsActive: false, - IsScheduled: true - }); - }; - - self.renderTab = function () { - loading.show(); - renderActiveRecordings(tabContent, activeRecordingsPromise); - renderUpcomingRecordings(tabContent, upcomingRecordingsPromise); - }; }; -}); + + self.renderTab = function () { + loading.show(); + renderActiveRecordings(tabContent, activeRecordingsPromise); + renderUpcomingRecordings(tabContent, upcomingRecordingsPromise); + }; +} diff --git a/src/controllers/livetv/livetvseriestimers.js b/src/controllers/livetv/livetvseriestimers.js index 27daca1983..4f6bfaaa6a 100644 --- a/src/controllers/livetv/livetvseriestimers.js +++ b/src/controllers/livetv/livetvseriestimers.js @@ -1,51 +1,52 @@ -define(['datetime', 'cardBuilder', 'imageLoader', 'apphost', 'loading', 'paper-icon-button-light', 'emby-button'], function (datetime, cardBuilder, imageLoader, appHost, loading) { - 'use strict'; +import cardBuilder from 'cardBuilder'; +import imageLoader from 'imageLoader'; +import loading from 'loading'; +import 'paper-icon-button-light'; +import 'emby-button'; - function renderTimers(context, timers) { - var html = ''; - appHost.supports('imageanalysis'); - html += cardBuilder.getCardsHtml({ - items: timers, - shape: 'auto', - defaultShape: 'portrait', - showTitle: true, - cardLayout: false, - preferThumb: 'auto', - coverImage: true, - overlayText: false, - showSeriesTimerTime: true, - showSeriesTimerChannel: true, - centerText: true, - overlayMoreButton: true, - lines: 3 - }); - var elem = context.querySelector('#items'); - elem.innerHTML = html; - imageLoader.lazyChildren(elem); - loading.hide(); - } +function renderTimers(context, timers) { + const html = cardBuilder.getCardsHtml({ + items: timers, + shape: 'auto', + defaultShape: 'portrait', + showTitle: true, + cardLayout: false, + preferThumb: 'auto', + coverImage: true, + overlayText: false, + showSeriesTimerTime: true, + showSeriesTimerChannel: true, + centerText: true, + overlayMoreButton: true, + lines: 3 + }); + const elem = context.querySelector('#items'); + elem.innerHTML = html; + imageLoader.lazyChildren(elem); + loading.hide(); +} - function reload(context, promise) { - loading.show(); - promise.then(function (result) { - renderTimers(context, result.Items); - }); - } +function reload(context, promise) { + loading.show(); + promise.then(function (result) { + renderTimers(context, result.Items); + }); +} - var query = { - SortBy: 'SortName', - SortOrder: 'Ascending' +const query = { + SortBy: 'SortName', + SortOrder: 'Ascending' +}; + +export default function (view, params, tabContent) { + let timersPromise; + const self = this; + + self.preRender = function () { + timersPromise = ApiClient.getLiveTvSeriesTimers(query); }; - return function (view, params, tabContent) { - var timersPromise; - var self = this; - self.preRender = function () { - timersPromise = ApiClient.getLiveTvSeriesTimers(query); - }; - - self.renderTab = function () { - reload(tabContent, timersPromise); - }; + self.renderTab = function () { + reload(tabContent, timersPromise); }; -}); +} diff --git a/src/controllers/livetv/livetvsuggested.js b/src/controllers/livetv/livetvsuggested.js index 036eee9fc6..346630012a 100644 --- a/src/controllers/livetv/livetvsuggested.js +++ b/src/controllers/livetv/livetvsuggested.js @@ -1,393 +1,397 @@ -define(['layoutManager', 'userSettings', 'inputManager', 'loading', 'globalize', 'libraryBrowser', 'mainTabsManager', 'cardBuilder', 'apphost', 'imageLoader', 'scrollStyles', 'emby-itemscontainer', 'emby-tabs', 'emby-button'], function (layoutManager, userSettings, inputManager, loading, globalize, libraryBrowser, mainTabsManager, cardBuilder, appHost, imageLoader) { - 'use strict'; +import layoutManager from 'layoutManager'; +import * as userSettings from 'userSettings'; +import inputManager from 'inputManager'; +import loading from 'loading'; +import globalize from 'globalize'; +import * as mainTabsManager from 'mainTabsManager'; +import cardBuilder from 'cardBuilder'; +import imageLoader from 'imageLoader'; +import 'scrollStyles'; +import 'emby-itemscontainer'; +import 'emby-tabs'; +import 'emby-button'; - function enableScrollX() { - return !layoutManager.desktop; +function enableScrollX() { + return !layoutManager.desktop; +} + +function getBackdropShape() { + if (enableScrollX()) { + return 'overflowBackdrop'; + } + return 'backdrop'; +} + +function getPortraitShape() { + if (enableScrollX()) { + return 'overflowPortrait'; + } + return 'portrait'; +} + +function getLimit() { + if (enableScrollX()) { + return 12; } - function getBackdropShape() { - if (enableScrollX()) { - return 'overflowBackdrop'; - } - return 'backdrop'; + return 9; +} + +function loadRecommendedPrograms(page) { + loading.show(); + let limit = getLimit(); + + if (enableScrollX()) { + limit *= 2; } - function getPortraitShape() { - if (enableScrollX()) { - return 'overflowPortrait'; - } - return 'portrait'; - } + ApiClient.getLiveTvRecommendedPrograms({ + userId: Dashboard.getCurrentUserId(), + IsAiring: true, + limit: limit, + ImageTypeLimit: 1, + EnableImageTypes: 'Primary,Thumb,Backdrop', + EnableTotalRecordCount: false, + Fields: 'ChannelInfo,PrimaryImageAspectRatio' + }).then(function (result) { + renderItems(page, result.Items, 'activeProgramItems', 'play', { + showAirDateTime: false, + showAirEndTime: true + }); + loading.hide(); - function getLimit() { - if (enableScrollX()) { - return 12; - } + import('autoFocuser').then(({default: autoFocuser}) => { + autoFocuser.autoFocus(page); + }); + }); +} - return 9; - } - - function loadRecommendedPrograms(page) { - loading.show(); - var limit = getLimit(); - - if (enableScrollX()) { - limit *= 2; - } - - ApiClient.getLiveTvRecommendedPrograms({ +function reload(page, enableFullRender) { + if (enableFullRender) { + loadRecommendedPrograms(page); + ApiClient.getLiveTvPrograms({ userId: Dashboard.getCurrentUserId(), - IsAiring: true, - limit: limit, - ImageTypeLimit: 1, - EnableImageTypes: 'Primary,Thumb,Backdrop', + HasAired: false, + limit: getLimit(), + IsMovie: false, + IsSports: false, + IsKids: false, + IsNews: false, + IsSeries: true, EnableTotalRecordCount: false, - Fields: 'ChannelInfo,PrimaryImageAspectRatio' + Fields: 'ChannelInfo,PrimaryImageAspectRatio', + EnableImageTypes: 'Primary,Thumb' }).then(function (result) { - renderItems(page, result.Items, 'activeProgramItems', 'play', { - showAirDateTime: false, - showAirEndTime: true + renderItems(page, result.Items, 'upcomingEpisodeItems'); + }); + ApiClient.getLiveTvPrograms({ + userId: Dashboard.getCurrentUserId(), + HasAired: false, + limit: getLimit(), + IsMovie: true, + EnableTotalRecordCount: false, + Fields: 'ChannelInfo', + EnableImageTypes: 'Primary,Thumb' + }).then(function (result) { + renderItems(page, result.Items, 'upcomingTvMovieItems', null, { + shape: getPortraitShape(), + preferThumb: null, + showParentTitle: false }); - loading.hide(); - - require(['autoFocuser'], function (autoFocuser) { - autoFocuser.autoFocus(page); + }); + ApiClient.getLiveTvPrograms({ + userId: Dashboard.getCurrentUserId(), + HasAired: false, + limit: getLimit(), + IsSports: true, + EnableTotalRecordCount: false, + Fields: 'ChannelInfo,PrimaryImageAspectRatio', + EnableImageTypes: 'Primary,Thumb' + }).then(function (result) { + renderItems(page, result.Items, 'upcomingSportsItems'); + }); + ApiClient.getLiveTvPrograms({ + userId: Dashboard.getCurrentUserId(), + HasAired: false, + limit: getLimit(), + IsKids: true, + EnableTotalRecordCount: false, + Fields: 'ChannelInfo,PrimaryImageAspectRatio', + EnableImageTypes: 'Primary,Thumb' + }).then(function (result) { + renderItems(page, result.Items, 'upcomingKidsItems'); + }); + ApiClient.getLiveTvPrograms({ + userId: Dashboard.getCurrentUserId(), + HasAired: false, + limit: getLimit(), + IsNews: true, + EnableTotalRecordCount: false, + Fields: 'ChannelInfo,PrimaryImageAspectRatio', + EnableImageTypes: 'Primary,Thumb' + }).then(function (result) { + renderItems(page, result.Items, 'upcomingNewsItems', null, { + showParentTitleOrTitle: true, + showTitle: false, + showParentTitle: false }); }); } +} - function reload(page, enableFullRender) { - if (enableFullRender) { - loadRecommendedPrograms(page); - ApiClient.getLiveTvPrograms({ - userId: Dashboard.getCurrentUserId(), - HasAired: false, - limit: getLimit(), - IsMovie: false, - IsSports: false, - IsKids: false, - IsNews: false, - IsSeries: true, - EnableTotalRecordCount: false, - Fields: 'ChannelInfo,PrimaryImageAspectRatio', - EnableImageTypes: 'Primary,Thumb' - }).then(function (result) { - renderItems(page, result.Items, 'upcomingEpisodeItems'); - }); - ApiClient.getLiveTvPrograms({ - userId: Dashboard.getCurrentUserId(), - HasAired: false, - limit: getLimit(), - IsMovie: true, - EnableTotalRecordCount: false, - Fields: 'ChannelInfo', - EnableImageTypes: 'Primary,Thumb' - }).then(function (result) { - renderItems(page, result.Items, 'upcomingTvMovieItems', null, { - shape: getPortraitShape(), - preferThumb: null, - showParentTitle: false - }); - }); - ApiClient.getLiveTvPrograms({ - userId: Dashboard.getCurrentUserId(), - HasAired: false, - limit: getLimit(), - IsSports: true, - EnableTotalRecordCount: false, - Fields: 'ChannelInfo,PrimaryImageAspectRatio', - EnableImageTypes: 'Primary,Thumb' - }).then(function (result) { - renderItems(page, result.Items, 'upcomingSportsItems'); - }); - ApiClient.getLiveTvPrograms({ - userId: Dashboard.getCurrentUserId(), - HasAired: false, - limit: getLimit(), - IsKids: true, - EnableTotalRecordCount: false, - Fields: 'ChannelInfo,PrimaryImageAspectRatio', - EnableImageTypes: 'Primary,Thumb' - }).then(function (result) { - renderItems(page, result.Items, 'upcomingKidsItems'); - }); - ApiClient.getLiveTvPrograms({ - userId: Dashboard.getCurrentUserId(), - HasAired: false, - limit: getLimit(), - IsNews: true, - EnableTotalRecordCount: false, - Fields: 'ChannelInfo,PrimaryImageAspectRatio', - EnableImageTypes: 'Primary,Thumb' - }).then(function (result) { - renderItems(page, result.Items, 'upcomingNewsItems', null, { - showParentTitleOrTitle: true, - showTitle: false, - showParentTitle: false - }); - }); +function renderItems(page, items, sectionClass, overlayButton, cardOptions) { + const html = cardBuilder.getCardsHtml(Object.assign({ + items: items, + preferThumb: 'auto', + inheritThumb: false, + shape: enableScrollX() ? 'autooverflow' : 'auto', + defaultShape: getBackdropShape(), + showParentTitle: true, + showTitle: true, + centerText: true, + coverImage: true, + overlayText: false, + lazy: true, + overlayPlayButton: overlayButton === 'play', + overlayMoreButton: overlayButton === 'more', + overlayInfoButton: overlayButton === 'info', + allowBottomPadding: !enableScrollX(), + showAirTime: true, + showAirDateTime: true + }, cardOptions || {})); + const elem = page.querySelector('.' + sectionClass); + elem.innerHTML = html; + imageLoader.lazyChildren(elem); +} + +function getTabs() { + return [{ + name: globalize.translate('Programs') + }, { + name: globalize.translate('Guide') + }, { + name: globalize.translate('Channels') + }, { + name: globalize.translate('Recordings') + }, { + name: globalize.translate('Schedule') + }, { + name: globalize.translate('Series') + }]; +} + +function setScrollClasses(elem, scrollX) { + if (scrollX) { + elem.classList.add('hiddenScrollX'); + + if (layoutManager.tv) { + elem.classList.add('smoothScrollX'); } + + elem.classList.add('scrollX'); + elem.classList.remove('vertical-wrap'); + } else { + elem.classList.remove('hiddenScrollX'); + elem.classList.remove('smoothScrollX'); + elem.classList.remove('scrollX'); + elem.classList.add('vertical-wrap'); + } +} + +function getDefaultTabIndex(folderId) { + if (userSettings.get('landing-' + folderId) === 'guide') { + return 1; } - function renderItems(page, items, sectionClass, overlayButton, cardOptions) { - var html = cardBuilder.getCardsHtml(Object.assign({ - items: items, - preferThumb: 'auto', - inheritThumb: false, - shape: enableScrollX() ? 'autooverflow' : 'auto', - defaultShape: getBackdropShape(), - showParentTitle: true, - showTitle: true, - centerText: true, - coverImage: true, - overlayText: false, - lazy: true, - overlayPlayButton: 'play' === overlayButton, - overlayMoreButton: 'more' === overlayButton, - overlayInfoButton: 'info' === overlayButton, - allowBottomPadding: !enableScrollX(), - showAirTime: true, - showAirDateTime: true - }, cardOptions || {})); - var elem = page.querySelector('.' + sectionClass); - elem.innerHTML = html; - imageLoader.lazyChildren(elem); + return 0; +} + +export default function (view, params) { + function enableFullRender() { + return new Date().getTime() - lastFullRender > 3e5; } - function getTabs() { - return [{ - name: globalize.translate('Programs') - }, { - name: globalize.translate('TabGuide') - }, { - name: globalize.translate('TabChannels') - }, { - name: globalize.translate('TabRecordings') - }, { - name: globalize.translate('HeaderSchedule') - }, { - name: globalize.translate('TabSeries') - }, { - name: globalize.translate('ButtonSearch'), - cssClass: 'searchTabButton' - }]; + function onBeforeTabChange(evt) { + preLoadTab(view, parseInt(evt.detail.selectedTabIndex)); } - function setScrollClasses(elem, scrollX) { - if (scrollX) { - elem.classList.add('hiddenScrollX'); + function onTabChange(evt) { + const previousTabController = tabControllers[parseInt(evt.detail.previousIndex)]; - if (layoutManager.tv) { - elem.classList.add('smoothScrollX'); + if (previousTabController && previousTabController.onHide) { + previousTabController.onHide(); + } + + loadTab(view, parseInt(evt.detail.selectedTabIndex)); + } + + function getTabContainers() { + return view.querySelectorAll('.pageTabContent'); + } + + function initTabs() { + mainTabsManager.setTabs(view, currentTabIndex, getTabs, getTabContainers, onBeforeTabChange, onTabChange); + } + + function getTabController(page, index, callback) { + let depends; + + // TODO int is a little hard to read + switch (index) { + case 0: + depends = 'controllers/livetv/livetvsuggested'; + break; + + case 1: + depends = 'controllers/livetv/livetvguide'; + break; + + case 2: + depends = 'controllers/livetv/livetvchannels'; + break; + + case 3: + depends = 'controllers/livetv/livetvrecordings'; + break; + + case 4: + depends = 'controllers/livetv/livetvschedule'; + break; + + case 5: + depends = 'controllers/livetv/livetvseriestimers'; + break; + } + + import(depends).then(({default: controllerFactory}) => { + let tabContent; + + if (index === 0) { + tabContent = view.querySelector(`.pageTabContent[data-index="${index}"]`); + self.tabContent = tabContent; } - elem.classList.add('scrollX'); - elem.classList.remove('vertical-wrap'); - } else { - elem.classList.remove('hiddenScrollX'); - elem.classList.remove('smoothScrollX'); - elem.classList.remove('scrollX'); - elem.classList.add('vertical-wrap'); - } - } + let controller = tabControllers[index]; - function getDefaultTabIndex(folderId) { - if (userSettings.get('landing-' + folderId) === 'guide') { - return 1; - } + if (!controller) { + tabContent = view.querySelector(`.pageTabContent[data-index="${index}"]`); - return 0; - } - - return function (view, params) { - function enableFullRender() { - return new Date().getTime() - lastFullRender > 3e5; - } - - function onBeforeTabChange(evt) { - preLoadTab(view, parseInt(evt.detail.selectedTabIndex)); - } - - function onTabChange(evt) { - var previousTabController = tabControllers[parseInt(evt.detail.previousIndex)]; - - if (previousTabController && previousTabController.onHide) { - previousTabController.onHide(); - } - - loadTab(view, parseInt(evt.detail.selectedTabIndex)); - } - - function getTabContainers() { - return view.querySelectorAll('.pageTabContent'); - } - - function initTabs() { - mainTabsManager.setTabs(view, currentTabIndex, getTabs, getTabContainers, onBeforeTabChange, onTabChange); - } - - function getTabController(page, index, callback) { - var depends = []; - - // TODO int is a little hard to read - switch (index) { - case 0: - break; - - case 1: - depends.push('controllers/livetv/livetvguide'); - break; - - case 2: - depends.push('controllers/livetv/livetvchannels'); - break; - - case 3: - depends.push('controllers/livetv/livetvrecordings'); - break; - - case 4: - depends.push('controllers/livetv/livetvschedule'); - break; - - case 5: - depends.push('controllers/livetv/livetvseriestimers'); - break; - - case 6: - depends.push('scripts/searchtab'); - } - - require(depends, function (controllerFactory) { - var tabContent; - - if (0 == index) { - tabContent = view.querySelector(".pageTabContent[data-index='" + index + "']"); - self.tabContent = tabContent; + if (index === 0) { + controller = self; + } else if (index === 6) { + controller = new controllerFactory(view, tabContent, { + collectionType: 'livetv' + }); + } else { + controller = new controllerFactory(view, params, tabContent); } - var controller = tabControllers[index]; + tabControllers[index] = controller; - if (!controller) { - tabContent = view.querySelector(".pageTabContent[data-index='" + index + "']"); + if (controller.initTab) { + controller.initTab(); + } + } - if (0 === index) { - controller = self; - } else if (6 === index) { - controller = new controllerFactory(view, tabContent, { - collectionType: 'livetv' - }); - } else { - controller = new controllerFactory(view, params, tabContent); - } + callback(controller); + }); + } - tabControllers[index] = controller; + function preLoadTab(page, index) { + getTabController(page, index, function (controller) { + if (renderedTabs.indexOf(index) === -1 && controller.preRender) { + controller.preRender(); + } + }); + } - if (controller.initTab) { - controller.initTab(); - } + function loadTab(page, index) { + currentTabIndex = index; + getTabController(page, index, function (controller) { + initialTabIndex = null; + + if (renderedTabs.indexOf(index) === -1) { + if (index === 1) { + renderedTabs.push(index); } - callback(controller); - }); + controller.renderTab(); + } else if (controller.onShow) { + controller.onShow(); + } + + currentTabController = controller; + }); + } + + function onInputCommand(evt) { + if (evt.detail.command === 'search') { + evt.preventDefault(); + Dashboard.navigate('search.html?collectionType=livetv'); } + } - function preLoadTab(page, index) { - getTabController(page, index, function (controller) { - if (renderedTabs.indexOf(index) === -1 && controller.preRender) { - controller.preRender(); - } - }); + let isViewRestored; + const self = this; + let currentTabIndex = parseInt(params.tab || getDefaultTabIndex('livetv')); + let initialTabIndex = currentTabIndex; + let lastFullRender = 0; + [].forEach.call(view.querySelectorAll('.sectionTitleTextButton-programs'), function (link) { + const href = link.href; + + if (href) { + link.href = href + '&serverId=' + ApiClient.serverId(); } + }); - function loadTab(page, index) { - currentTabIndex = index; - getTabController(page, index, function (controller) { - initialTabIndex = null; + self.initTab = function () { + const tabContent = view.querySelector('.pageTabContent[data-index="0"]'); + const containers = tabContent.querySelectorAll('.itemsContainer'); - if (-1 == renderedTabs.indexOf(index)) { - if (1 === index) { - renderedTabs.push(index); - } - - controller.renderTab(); - } else if (controller.onShow) { - controller.onShow(); - } - - currentTabController = controller; - }); + for (let i = 0, length = containers.length; i < length; i++) { + setScrollClasses(containers[i], enableScrollX()); } - - function onInputCommand(evt) { - if (evt.detail.command === 'search') { - evt.preventDefault(); - Dashboard.navigate('search.html?collectionType=livetv'); - } - } - - var isViewRestored; - var self = this; - var currentTabIndex = parseInt(params.tab || getDefaultTabIndex('livetv')); - var initialTabIndex = currentTabIndex; - var lastFullRender = 0; - [].forEach.call(view.querySelectorAll('.sectionTitleTextButton-programs'), function (link) { - var href = link.href; - - if (href) { - link.href = href + '&serverId=' + ApiClient.serverId(); - } - }); - - self.initTab = function () { - var tabContent = view.querySelector(".pageTabContent[data-index='0']"); - var containers = tabContent.querySelectorAll('.itemsContainer'); - - for (var i = 0, length = containers.length; i < length; i++) { - setScrollClasses(containers[i], enableScrollX()); - } - }; - - self.renderTab = function () { - var tabContent = view.querySelector(".pageTabContent[data-index='0']"); - - if (enableFullRender()) { - reload(tabContent, true); - lastFullRender = new Date().getTime(); - } else { - reload(tabContent); - } - }; - - var currentTabController; - var tabControllers = []; - var renderedTabs = []; - view.addEventListener('viewbeforeshow', function (evt) { - isViewRestored = evt.detail.isRestored; - initTabs(); - }); - view.addEventListener('viewshow', function (evt) { - isViewRestored = evt.detail.isRestored; - - if (!isViewRestored) { - mainTabsManager.selectedTabIndex(initialTabIndex); - } - - inputManager.on(window, onInputCommand); - }); - view.addEventListener('viewbeforehide', function (e) { - if (currentTabController && currentTabController.onHide) { - currentTabController.onHide(); - } - - inputManager.off(window, onInputCommand); - }); - view.addEventListener('viewdestroy', function (evt) { - tabControllers.forEach(function (tabController) { - if (tabController.destroy) { - tabController.destroy(); - } - }); - }); }; -}); + + self.renderTab = function () { + const tabContent = view.querySelector('.pageTabContent[data-index="0"]'); + + if (enableFullRender()) { + reload(tabContent, true); + lastFullRender = new Date().getTime(); + } else { + reload(tabContent); + } + }; + + let currentTabController; + const tabControllers = []; + const renderedTabs = []; + view.addEventListener('viewbeforeshow', function (evt) { + isViewRestored = evt.detail.isRestored; + initTabs(); + }); + view.addEventListener('viewshow', function (evt) { + isViewRestored = evt.detail.isRestored; + + if (!isViewRestored) { + mainTabsManager.selectedTabIndex(initialTabIndex); + } + + inputManager.on(window, onInputCommand); + }); + view.addEventListener('viewbeforehide', function () { + if (currentTabController && currentTabController.onHide) { + currentTabController.onHide(); + } + + inputManager.off(window, onInputCommand); + }); + view.addEventListener('viewdestroy', function () { + tabControllers.forEach(function (tabController) { + if (tabController.destroy) { + tabController.destroy(); + } + }); + }); +} diff --git a/src/livetvguideprovider.html b/src/controllers/livetvguideprovider.html similarity index 100% rename from src/livetvguideprovider.html rename to src/controllers/livetvguideprovider.html diff --git a/src/controllers/livetvguideprovider.js b/src/controllers/livetvguideprovider.js index 8f8b9f91c7..6ab195a088 100644 --- a/src/controllers/livetvguideprovider.js +++ b/src/controllers/livetvguideprovider.js @@ -1,30 +1,30 @@ -define(['events', 'loading', 'globalize'], function (events, loading, globalize) { - 'use strict'; +import events from 'events'; +import loading from 'loading'; +import globalize from 'globalize'; - function onListingsSubmitted() { - Dashboard.navigate('livetvstatus.html'); - } +function onListingsSubmitted() { + Dashboard.navigate('livetvstatus.html'); +} - function init(page, type, providerId) { - var url = 'components/tvproviders/' + type + '.js'; +function init(page, type, providerId) { + const url = 'components/tvproviders/' + type + '.js'; - require([url], function (factory) { - var instance = new factory(page, providerId, {}); - events.on(instance, 'submitted', onListingsSubmitted); - instance.init(); - }); - } - - function loadTemplate(page, type, providerId) { - require(['text!./components/tvproviders/' + type + '.template.html'], function (html) { - page.querySelector('.providerTemplate').innerHTML = globalize.translateHtml(html); - init(page, type, providerId); - }); - } - - pageIdOn('pageshow', 'liveTvGuideProviderPage', function () { - loading.show(); - var providerId = getParameterByName('id'); - loadTemplate(this, getParameterByName('type'), providerId); + import(url).then(({default: factory}) => { + const instance = new factory(page, providerId, {}); + events.on(instance, 'submitted', onListingsSubmitted); + instance.init(); }); +} + +function loadTemplate(page, type, providerId) { + import('text!./../components/tvproviders/' + type + '.template.html').then(({default: html}) => { + page.querySelector('.providerTemplate').innerHTML = globalize.translateHtml(html); + init(page, type, providerId); + }); +} + +pageIdOn('pageshow', 'liveTvGuideProviderPage', function () { + loading.show(); + const providerId = getParameterByName('id'); + loadTemplate(this, getParameterByName('type'), providerId); }); diff --git a/src/livetvsettings.html b/src/controllers/livetvsettings.html similarity index 97% rename from src/livetvsettings.html rename to src/controllers/livetvsettings.html index 02b960f08d..374ab9a4a0 100644 --- a/src/livetvsettings.html +++ b/src/controllers/livetvsettings.html @@ -11,7 +11,7 @@
'; } - var itemsContainer = tabContent.querySelector('.itemsContainer'); + const itemsContainer = tabContent.querySelector('.itemsContainer'); itemsContainer.innerHTML = html; imageLoader.lazyChildren(itemsContainer); libraryBrowser.saveQueryValues(getSavedQueryKey(page), query); loading.hide(); isLoading = false; - require(['autoFocuser'], function (autoFocuser) { + import('autoFocuser').then(({default: autoFocuser}) => { autoFocuser.autoFocus(page); }); }); - } + }; - var self = this; - var data = {}; - var isLoading = false; + const data = {}; + let isLoading = false; - self.getCurrentViewStyle = function () { + this.getCurrentViewStyle = function () { return getPageData(tabContent).view; }; - function initPage(tabContent) { + const initPage = (tabContent) => { tabContent.querySelector('.btnSort').addEventListener('click', function (e) { libraryBrowser.showSortMenu({ items: [{ - name: globalize.translate('OptionNameSort'), + name: globalize.translate('Name'), id: 'SortName' }, { name: globalize.translate('OptionImdbRating'), @@ -227,36 +233,37 @@ define(['loading', 'events', 'libraryBrowser', 'imageLoader', 'listView', 'cardB button: e.target }); }); - var btnSelectView = tabContent.querySelector('.btnSelectView'); + const btnSelectView = tabContent.querySelector('.btnSelectView'); btnSelectView.addEventListener('click', function (e) { - libraryBrowser.showLayoutMenu(e.target, self.getCurrentViewStyle(), 'List,Poster,PosterCard,Thumb,ThumbCard'.split(',')); + libraryBrowser.showLayoutMenu(e.target, this.getCurrentViewStyle(), 'List,Poster,PosterCard,Thumb,ThumbCard'.split(',')); }); btnSelectView.addEventListener('layoutchange', function (e) { - var viewStyle = e.detail.viewStyle; + const viewStyle = e.detail.viewStyle; getPageData(tabContent).view = viewStyle; libraryBrowser.saveViewSetting(getSavedQueryKey(tabContent), viewStyle); getQuery(tabContent).StartIndex = 0; onViewStyleChange(); reloadItems(tabContent); }); - tabContent.querySelector('.btnNewCollection').addEventListener('click', function () { - require(['collectionEditor'], function (collectionEditor) { - var serverId = ApiClient.serverInfo().Id; + tabContent.querySelector('.btnNewCollection').addEventListener('click', () => { + import('collectionEditor').then(({default: collectionEditor}) => { + const serverId = ApiClient.serverInfo().Id; new collectionEditor.showEditor({ items: [], serverId: serverId }); }); }); - } + }; initPage(tabContent); onViewStyleChange(); - self.renderTab = function () { + this.renderTab = function () { reloadItems(tabContent); }; - self.destroy = function () {}; - }; -}); + this.destroy = function () {}; + } + +/* eslint-enable indent */ diff --git a/src/controllers/movies/moviegenres.js b/src/controllers/movies/moviegenres.js index ab410c1bd4..ca02ede36d 100644 --- a/src/controllers/movies/moviegenres.js +++ b/src/controllers/movies/moviegenres.js @@ -1,10 +1,18 @@ -define(['layoutManager', 'loading', 'libraryBrowser', 'cardBuilder', 'lazyLoader', 'apphost', 'globalize', 'appRouter', 'dom', 'emby-button'], function (layoutManager, loading, libraryBrowser, cardBuilder, lazyLoader, appHost, globalize, appRouter, dom) { - 'use strict'; +import layoutManager from 'layoutManager'; +import loading from 'loading'; +import libraryBrowser from 'libraryBrowser'; +import cardBuilder from 'cardBuilder'; +import lazyLoader from 'lazyLoader'; +import globalize from 'globalize'; +import appRouter from 'appRouter'; +import 'emby-button'; - return function (view, params, tabContent) { +/* eslint-disable indent */ + + export default function (view, params, tabContent) { function getPageData() { - var key = getSavedQueryKey(); - var pageData = data[key]; + const key = getSavedQueryKey(); + let pageData = data[key]; if (!pageData) { pageData = data[key] = { @@ -34,7 +42,7 @@ define(['layoutManager', 'loading', 'libraryBrowser', 'cardBuilder', 'lazyLoader function getPromise() { loading.show(); - var query = getQuery(); + const query = getQuery(); return ApiClient.getGenres(ApiClient.getCurrentUserId(), query); } @@ -50,18 +58,18 @@ define(['layoutManager', 'loading', 'libraryBrowser', 'cardBuilder', 'lazyLoader return enableScrollX() ? 'overflowPortrait' : 'portrait'; } - function fillItemsContainer(entry) { - var elem = entry.target; - var id = elem.getAttribute('data-id'); - var viewStyle = self.getCurrentViewStyle(); - var limit = 'Thumb' == viewStyle || 'ThumbCard' == viewStyle ? 5 : 9; + const fillItemsContainer = (entry) => { + const elem = entry.target; + const id = elem.getAttribute('data-id'); + const viewStyle = this.getCurrentViewStyle(); + let limit = viewStyle == 'Thumb' || viewStyle == 'ThumbCard' ? 5 : 9; if (enableScrollX()) { limit = 10; } - var enableImageTypes = 'Thumb' == viewStyle || 'ThumbCard' == viewStyle ? 'Primary,Backdrop,Thumb' : 'Primary'; - var query = { + const enableImageTypes = viewStyle == 'Thumb' || viewStyle == 'ThumbCard' ? 'Primary,Backdrop,Thumb' : 'Primary'; + const query = { SortBy: 'SortName', SortOrder: 'Ascending', IncludeItemTypes: 'Movie', @@ -75,8 +83,6 @@ define(['layoutManager', 'loading', 'libraryBrowser', 'cardBuilder', 'lazyLoader ParentId: params.topParentId }; ApiClient.getItems(ApiClient.getCurrentUserId(), query).then(function (result) { - var supportsImageAnalysis = appHost.supports('imageanalysis'); - if (viewStyle == 'Thumb') { cardBuilder.buildCards(result.Items, { itemsContainer: elem, @@ -125,17 +131,17 @@ define(['layoutManager', 'loading', 'libraryBrowser', 'cardBuilder', 'lazyLoader tabContent.querySelector('.btnMoreFromGenre' + id + ' .material-icons').classList.remove('hide'); } }); - } + }; function reloadItems(context, promise) { - var query = getQuery(); + const query = getQuery(); promise.then(function (result) { - var elem = context.querySelector('#items'); - var html = ''; - var items = result.Items; + const elem = context.querySelector('#items'); + let html = ''; + const items = result.Items; - for (var i = 0, length = items.length; i < length; i++) { - var item = items[i]; + for (let i = 0, length = items.length; i < length; i++) { + const item = items[i]; html += '
'; html += '
'; @@ -150,7 +156,7 @@ define(['layoutManager', 'loading', 'libraryBrowser', 'cardBuilder', 'lazyLoader html += ''; html += '
'; if (enableScrollX()) { - var scrollXClass = 'scrollX hiddenScrollX'; + let scrollXClass = 'scrollX hiddenScrollX'; if (layoutManager.tv) { scrollXClass += 'smoothScrollX padded-top-focusscale padded-bottom-focusscale'; @@ -181,37 +187,37 @@ define(['layoutManager', 'loading', 'libraryBrowser', 'cardBuilder', 'lazyLoader }); } - function fullyReload() { - self.preRender(); - self.renderTab(); - } + const fullyReload = () => { + this.preRender(); + this.renderTab(); + }; - var self = this; - var data = {}; + const data = {}; - self.getViewStyles = function () { + this.getViewStyles = function () { return 'Poster,PosterCard,Thumb,ThumbCard'.split(','); }; - self.getCurrentViewStyle = function () { + this.getCurrentViewStyle = function () { return getPageData().view; }; - self.setCurrentViewStyle = function (viewStyle) { + this.setCurrentViewStyle = function (viewStyle) { getPageData().view = viewStyle; libraryBrowser.saveViewSetting(getSavedQueryKey(), viewStyle); fullyReload(); }; - self.enableViewSelection = true; - var promise; + this.enableViewSelection = true; + let promise; - self.preRender = function () { + this.preRender = function () { promise = getPromise(); }; - self.renderTab = function () { + this.renderTab = function () { reloadItems(tabContent, promise); }; - }; -}); + } + +/* eslint-enable indent */ diff --git a/src/movies.html b/src/controllers/movies/movies.html similarity index 91% rename from src/movies.html rename to src/controllers/movies/movies.html index a2221c510d..dddda3f7f9 100644 --- a/src/movies.html +++ b/src/controllers/movies/movies.html @@ -4,8 +4,8 @@
- - + +
@@ -46,8 +46,8 @@
- - + +
@@ -75,7 +75,7 @@
- +
diff --git a/src/controllers/movies/movies.js b/src/controllers/movies/movies.js index 82b162e2ad..ab5aff9ecd 100644 --- a/src/controllers/movies/movies.js +++ b/src/controllers/movies/movies.js @@ -1,9 +1,18 @@ -define(['loading', 'layoutManager', 'userSettings', 'events', 'libraryBrowser', 'alphaPicker', 'listView', 'cardBuilder', 'globalize', 'emby-itemscontainer'], function (loading, layoutManager, userSettings, events, libraryBrowser, AlphaPicker, listView, cardBuilder, globalize) { - 'use strict'; +import loading from 'loading'; +import * as userSettings from 'userSettings'; +import events from 'events'; +import libraryBrowser from 'libraryBrowser'; +import AlphaPicker from 'alphaPicker'; +import listView from 'listView'; +import cardBuilder from 'cardBuilder'; +import globalize from 'globalize'; +import 'emby-itemscontainer'; - return function (view, params, tabContent, options) { - function onViewStyleChange() { - if (self.getCurrentViewStyle() == 'List') { +/* eslint-disable indent */ + + export default function (view, params, tabContent, options) { + const onViewStyleChange = () => { + if (this.getCurrentViewStyle() == 'List') { itemsContainer.classList.add('vertical-list'); itemsContainer.classList.remove('vertical-wrap'); } else { @@ -12,13 +21,13 @@ define(['loading', 'layoutManager', 'userSettings', 'events', 'libraryBrowser', } itemsContainer.innerHTML = ''; - } + }; - function updateFilterControls() { - if (self.alphaPicker) { - self.alphaPicker.value(query.NameStartsWithOrGreater); + const updateFilterControls = () => { + if (this.alphaPicker) { + this.alphaPicker.value(query.NameStartsWithOrGreater); } - } + }; function fetchData() { isLoading = true; @@ -51,7 +60,7 @@ define(['loading', 'layoutManager', 'userSettings', 'events', 'libraryBrowser', window.scrollTo(0, 0); updateFilterControls(); - var pagingHtml = libraryBrowser.getQueryPagingHtml({ + const pagingHtml = libraryBrowser.getQueryPagingHtml({ startIndex: query.StartIndex, limit: query.Limit, totalRecordCount: result.TotalRecordCount, @@ -61,35 +70,30 @@ define(['loading', 'layoutManager', 'userSettings', 'events', 'libraryBrowser', sortButton: false, filterButton: false }); - var i; - var length; - var elems = tabContent.querySelectorAll('.paging'); - for (i = 0, length = elems.length; i < length; i++) { - elems[i].innerHTML = pagingHtml; + for (const elem of tabContent.querySelectorAll('.paging')) { + elem.innerHTML = pagingHtml; } - elems = tabContent.querySelectorAll('.btnNextPage'); - for (i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener('click', onNextPageClick); + for (const elem of tabContent.querySelectorAll('.btnNextPage')) { + elem.addEventListener('click', onNextPageClick); } - elems = tabContent.querySelectorAll('.btnPreviousPage'); - for (i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener('click', onPreviousPageClick); + for (const elem of tabContent.querySelectorAll('.btnPreviousPage')) { + elem.addEventListener('click', onPreviousPageClick); } isLoading = false; loading.hide(); - require(['autoFocuser'], function (autoFocuser) { + import('autoFocuser').then(({default: autoFocuser}) => { autoFocuser.autoFocus(tabContent); }); } - function getItemsHtml(items) { - var html; - var viewStyle = self.getCurrentViewStyle(); + const getItemsHtml = (items) => { + let html; + const viewStyle = this.getCurrentViewStyle(); if (viewStyle == 'Thumb') { html = cardBuilder.getCardsHtml({ @@ -153,22 +157,22 @@ define(['loading', 'layoutManager', 'userSettings', 'events', 'libraryBrowser', } return html; - } + }; - function initPage(tabContent) { + const initPage = (tabContent) => { itemsContainer.fetchData = fetchData; itemsContainer.getItemsHtml = getItemsHtml; itemsContainer.afterRefresh = afterRefresh; - var alphaPickerElement = tabContent.querySelector('.alphaPicker'); + let alphaPickerElement = tabContent.querySelector('.alphaPicker'); if (alphaPickerElement) { alphaPickerElement.addEventListener('alphavaluechanged', function (e) { - var newValue = e.detail.value; + let newValue = e.detail.value; query.NameStartsWithOrGreater = newValue; query.StartIndex = 0; itemsContainer.refreshItems(); }); - self.alphaPicker = new AlphaPicker.default({ + this.alphaPicker = new AlphaPicker({ element: alphaPickerElement, valueChangeEvent: 'click' }); @@ -178,20 +182,20 @@ define(['loading', 'layoutManager', 'userSettings', 'events', 'libraryBrowser', itemsContainer.classList.add('padded-right-withalphapicker'); } - var btnFilter = tabContent.querySelector('.btnFilter'); + const btnFilter = tabContent.querySelector('.btnFilter'); if (btnFilter) { - btnFilter.addEventListener('click', function () { - self.showFilterMenu(); + btnFilter.addEventListener('click', () => { + this.showFilterMenu(); }); } - var btnSort = tabContent.querySelector('.btnSort'); + const btnSort = tabContent.querySelector('.btnSort'); if (btnSort) { btnSort.addEventListener('click', function (e) { libraryBrowser.showSortMenu({ items: [{ - name: globalize.translate('OptionNameSort'), + name: globalize.translate('Name'), id: 'SortName,ProductionYear' }, { name: globalize.translate('OptionImdbRating'), @@ -215,7 +219,7 @@ define(['loading', 'layoutManager', 'userSettings', 'events', 'libraryBrowser', name: globalize.translate('OptionReleaseDate'), id: 'PremiereDate,SortName,ProductionYear' }, { - name: globalize.translate('OptionRuntime'), + name: globalize.translate('Runtime'), id: 'Runtime,SortName,ProductionYear' }], callback: function () { @@ -228,24 +232,23 @@ define(['loading', 'layoutManager', 'userSettings', 'events', 'libraryBrowser', }); }); } - var btnSelectView = tabContent.querySelector('.btnSelectView'); + const btnSelectView = tabContent.querySelector('.btnSelectView'); btnSelectView.addEventListener('click', function (e) { - libraryBrowser.showLayoutMenu(e.target, self.getCurrentViewStyle(), 'Banner,List,Poster,PosterCard,Thumb,ThumbCard'.split(',')); + libraryBrowser.showLayoutMenu(e.target, this.getCurrentViewStyle, 'Banner,List,Poster,PosterCard,Thumb,ThumbCard'.split(',')); }); btnSelectView.addEventListener('layoutchange', function (e) { - var viewStyle = e.detail.viewStyle; + let viewStyle = e.detail.viewStyle; userSettings.set(savedViewKey, viewStyle); query.StartIndex = 0; onViewStyleChange(); itemsContainer.refreshItems(); }); - } + }; - var self = this; - var itemsContainer = tabContent.querySelector('.itemsContainer'); - var savedQueryKey = params.topParentId + '-' + options.mode; - var savedViewKey = savedQueryKey + '-view'; - var query = { + let itemsContainer = tabContent.querySelector('.itemsContainer'); + const savedQueryKey = params.topParentId + '-' + options.mode; + const savedViewKey = savedQueryKey + '-view'; + let query = { SortBy: 'SortName,ProductionYear', SortOrder: 'Ascending', IncludeItemTypes: 'Movie', @@ -261,7 +264,7 @@ define(['loading', 'layoutManager', 'userSettings', 'events', 'libraryBrowser', query['Limit'] = userSettings.libraryPageSize(); } - var isLoading = false; + let isLoading = false; if (options.mode === 'favorites') { query.IsFavorite = true; @@ -269,14 +272,14 @@ define(['loading', 'layoutManager', 'userSettings', 'events', 'libraryBrowser', query = userSettings.loadQuerySettings(savedQueryKey, query); - self.showFilterMenu = function () { - require(['components/filterdialog/filterdialog'], function ({default: filterDialogFactory}) { - var filterDialog = new filterDialogFactory({ + this.showFilterMenu = function () { + import('components/filterdialog/filterdialog').then(({default: filterDialogFactory}) => { + let filterDialog = new filterDialogFactory({ query: query, mode: 'movies', serverId: ApiClient.serverId() }); - events.on(filterDialog, 'filterchange', function () { + events.on(filterDialog, 'filterchange', () => { query.StartIndex = 0; itemsContainer.refreshItems(); }); @@ -284,22 +287,23 @@ define(['loading', 'layoutManager', 'userSettings', 'events', 'libraryBrowser', }); }; - self.getCurrentViewStyle = function () { + this.getCurrentViewStyle = function () { return userSettings.get(savedViewKey) || 'Poster'; }; - self.initTab = function () { + this.initTab = function () { initPage(tabContent); onViewStyleChange(); }; - self.renderTab = function () { + this.renderTab = function () { itemsContainer.refreshItems(); updateFilterControls(); }; - self.destroy = function () { + this.destroy = function () { itemsContainer = null; }; - }; -}); + } + +/* eslint-enable indent */ diff --git a/src/controllers/movies/moviesrecommended.js b/src/controllers/movies/moviesrecommended.js index d948c1cef7..5b85fbadae 100644 --- a/src/controllers/movies/moviesrecommended.js +++ b/src/controllers/movies/moviesrecommended.js @@ -1,5 +1,20 @@ -define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu', 'mainTabsManager', 'cardBuilder', 'dom', 'imageLoader', 'playbackManager', 'globalize', 'emby-scroller', 'emby-itemscontainer', 'emby-tabs', 'emby-button'], function (events, layoutManager, inputManager, userSettings, libraryMenu, mainTabsManager, cardBuilder, dom, imageLoader, playbackManager, globalize) { - 'use strict'; +import events from 'events'; +import layoutManager from 'layoutManager'; +import inputManager from 'inputManager'; +import * as userSettings from 'userSettings'; +import libraryMenu from 'libraryMenu'; +import * as mainTabsManager from 'mainTabsManager'; +import cardBuilder from 'cardBuilder'; +import dom from 'dom'; +import imageLoader from 'imageLoader'; +import playbackManager from 'playbackManager'; +import globalize from 'globalize'; +import 'emby-scroller'; +import 'emby-itemscontainer'; +import 'emby-tabs'; +import 'emby-button'; + +/* eslint-disable indent */ function enableScrollX() { return !layoutManager.desktop; @@ -14,7 +29,7 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu' } function loadLatest(page, userId, parentId) { - var options = { + const options = { IncludeItemTypes: 'Movie', Limit: 18, Fields: 'PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo', @@ -24,8 +39,8 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu' EnableTotalRecordCount: false }; ApiClient.getJSON(ApiClient.getUrl('Users/' + userId + '/Items/Latest', options)).then(function (items) { - var allowBottomPadding = !enableScrollX(); - var container = page.querySelector('#recentlyAddedItems'); + const allowBottomPadding = !enableScrollX(); + const container = page.querySelector('#recentlyAddedItems'); cardBuilder.buildCards(items, { itemsContainer: container, shape: getPortraitShape(), @@ -43,8 +58,8 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu' } function loadResume(page, userId, parentId) { - var screenWidth = dom.getWindowSize().innerWidth; - var options = { + let screenWidth = dom.getWindowSize().innerWidth; + const options = { SortBy: 'DatePlayed', SortOrder: 'Descending', IncludeItemTypes: 'Movie', @@ -65,8 +80,8 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu' page.querySelector('#resumableSection').classList.add('hide'); } - var allowBottomPadding = !enableScrollX(); - var container = page.querySelector('#resumableItems'); + const allowBottomPadding = !enableScrollX(); + const container = page.querySelector('#resumableItems'); cardBuilder.buildCards(result.Items, { itemsContainer: container, preferThumb: true, @@ -86,8 +101,8 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu' } function getRecommendationHtml(recommendation) { - var html = ''; - var title = ''; + let html = ''; + let title = ''; switch (recommendation.RecommendationType) { case 'SimilarToRecentlyPlayed': @@ -111,7 +126,7 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu' html += '
'; html += '

' + title + '

'; - var allowBottomPadding = true; + const allowBottomPadding = true; if (enableScrollX()) { html += '
'; @@ -139,8 +154,8 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu' } function loadSuggestions(page, userId, parentId) { - var screenWidth = dom.getWindowSize().innerWidth; - var url = ApiClient.getUrl('Movies/Recommendations', { + let screenWidth = dom.getWindowSize().innerWidth; + let url = ApiClient.getUrl('Movies/Recommendations', { userId: userId, categoryLimit: 6, ItemLimit: screenWidth >= 1920 ? 8 : screenWidth >= 1600 ? 8 : screenWidth >= 1200 ? 6 : 5, @@ -155,9 +170,9 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu' return; } - var html = recommendations.map(getRecommendationHtml).join(''); + const html = recommendations.map(getRecommendationHtml).join(''); page.querySelector('.noItemsMessage').classList.add('hide'); - var recs = page.querySelector('.recommendations'); + let recs = page.querySelector('.recommendations'); recs.innerHTML = html; imageLoader.lazyChildren(recs); @@ -167,7 +182,7 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu' } function autoFocus(page) { - require(['autoFocuser'], function (autoFocuser) { + import('autoFocuser').then(({default: autoFocuser}) => { autoFocuser.autoFocus(page); }); } @@ -193,17 +208,16 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu' } function initSuggestedTab(page, tabContent) { - var containers = tabContent.querySelectorAll('.itemsContainer'); + const containers = tabContent.querySelectorAll('.itemsContainer'); - for (var i = 0, length = containers.length; i < length; i++) { - setScrollClasses(containers[i], enableScrollX()); + for (const container of containers) { + setScrollClasses(container, enableScrollX()); } } function loadSuggestionsTab(view, params, tabContent) { - var parentId = params.topParentId; - var userId = ApiClient.getCurrentUserId(); - console.debug('loadSuggestionsTab'); + const parentId = params.topParentId; + const userId = ApiClient.getCurrentUserId(); loadResume(tabContent, userId, parentId); loadLatest(tabContent, userId, parentId); loadSuggestions(tabContent, userId, parentId); @@ -213,18 +227,15 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu' return [{ name: globalize.translate('Movies') }, { - name: globalize.translate('TabSuggestions') + name: globalize.translate('Suggestions') }, { - name: globalize.translate('TabTrailers') + name: globalize.translate('Trailers') }, { - name: globalize.translate('TabFavorites') + name: globalize.translate('Favorites') }, { - name: globalize.translate('TabCollections') + name: globalize.translate('Collections') }, { - name: globalize.translate('TabGenres') - }, { - name: globalize.translate('ButtonSearch'), - cssClass: 'searchTabButton' + name: globalize.translate('Genres') }]; } @@ -247,13 +258,13 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu' } } - return function (view, params) { + export default function (view, params) { function onBeforeTabChange(e) { preLoadTab(view, parseInt(e.detail.selectedTabIndex)); } function onTabChange(e) { - var newIndex = parseInt(e.detail.selectedTabIndex); + const newIndex = parseInt(e.detail.selectedTabIndex); loadTab(view, newIndex); } @@ -265,52 +276,50 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu' mainTabsManager.setTabs(view, currentTabIndex, getTabs, getTabContainers, onBeforeTabChange, onTabChange); } - function getTabController(page, index, callback) { - var depends = []; + const getTabController = (page, index, callback) => { + let depends = ''; switch (index) { case 0: - depends.push('controllers/movies/movies'); + depends = 'controllers/movies/movies'; break; case 1: + depends = 'controllers/movies/moviesrecommended.js'; break; case 2: - depends.push('controllers/movies/movietrailers'); + depends = 'controllers/movies/movietrailers'; break; case 3: - depends.push('controllers/movies/movies'); + depends = 'controllers/movies/movies'; break; case 4: - depends.push('controllers/movies/moviecollections'); + depends = 'controllers/movies/moviecollections'; break; case 5: - depends.push('controllers/movies/moviegenres'); + depends = 'controllers/movies/moviegenres'; break; - - case 6: - depends.push('scripts/searchtab'); } - require(depends, function (controllerFactory) { - var tabContent; + import(depends).then(({default: controllerFactory}) => { + let tabContent; if (index === suggestionsTabIndex) { tabContent = view.querySelector(".pageTabContent[data-index='" + index + "']"); - self.tabContent = tabContent; + this.tabContent = tabContent; } - var controller = tabControllers[index]; + let controller = tabControllers[index]; if (!controller) { tabContent = view.querySelector(".pageTabContent[data-index='" + index + "']"); if (index === suggestionsTabIndex) { - controller = self; + controller = this; } else if (index === 6) { controller = new controllerFactory(view, tabContent, { collectionType: 'movies', @@ -333,7 +342,7 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu' callback(controller); }); - } + }; function preLoadTab(page, index) { getTabController(page, index, function (controller) { @@ -345,14 +354,12 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu' function loadTab(page, index) { currentTabIndex = index; - getTabController(page, index, function (controller) { - initialTabIndex = null; - + getTabController(page, index, ((controller) => { if (renderedTabs.indexOf(index) == -1) { renderedTabs.push(index); controller.renderTab(); } - }); + })); } function onPlaybackStop(e, state) { @@ -370,26 +377,24 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu' } } - var isViewRestored; - var self = this; - var currentTabIndex = parseInt(params.tab || getDefaultTabIndex(params.topParentId)); - var initialTabIndex = currentTabIndex; - var suggestionsTabIndex = 1; + let currentTabIndex = parseInt(params.tab || getDefaultTabIndex(params.topParentId)); + const suggestionsTabIndex = 1; - self.initTab = function () { - var tabContent = view.querySelector(".pageTabContent[data-index='" + suggestionsTabIndex + "']"); + this.initTab = function () { + let tabContent = view.querySelector(".pageTabContent[data-index='" + suggestionsTabIndex + "']"); initSuggestedTab(view, tabContent); }; - self.renderTab = function () { - var tabContent = view.querySelector(".pageTabContent[data-index='" + suggestionsTabIndex + "']"); + this.renderTab = function () { + let tabContent = view.querySelector(".pageTabContent[data-index='" + suggestionsTabIndex + "']"); loadSuggestionsTab(view, params, tabContent); }; - var tabControllers = []; - var renderedTabs = []; + let tabControllers = []; + let renderedTabs = []; view.addEventListener('viewshow', function (e) { - if (isViewRestored = e.detail.isRestored, initTabs(), !view.getAttribute('data-title')) { + initTabs(); + if (!view.getAttribute('data-title')) { var parentId = params.topParentId; if (parentId) { @@ -398,23 +403,22 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu' libraryMenu.setTitle(item.Name); }); } else { - view.setAttribute('data-title', globalize.translate('TabMovies')); - libraryMenu.setTitle(globalize.translate('TabMovies')); + view.setAttribute('data-title', globalize.translate('Movies')); + libraryMenu.setTitle(globalize.translate('Movies')); } } events.on(playbackManager, 'playbackstop', onPlaybackStop); inputManager.on(window, onInputCommand); }); - view.addEventListener('viewbeforehide', function (e) { + view.addEventListener('viewbeforehide', function () { inputManager.off(window, onInputCommand); }); - view.addEventListener('viewdestroy', function (e) { - tabControllers.forEach(function (t) { - if (t.destroy) { - t.destroy(); - } - }); - }); - }; -}); + for (const tabController of tabControllers) { + if (tabController.destroy) { + tabController.destroy(); + } + } + } + +/* eslint-enable indent */ diff --git a/src/controllers/movies/movietrailers.js b/src/controllers/movies/movietrailers.js index a9db0abf49..f2c7feada7 100644 --- a/src/controllers/movies/movietrailers.js +++ b/src/controllers/movies/movietrailers.js @@ -1,10 +1,20 @@ -define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', 'alphaPicker', 'listView', 'cardBuilder', 'userSettings', 'globalize', 'emby-itemscontainer'], function (layoutManager, loading, events, libraryBrowser, imageLoader, AlphaPicker, listView, cardBuilder, userSettings, globalize) { - 'use strict'; +import loading from 'loading'; +import events from 'events'; +import libraryBrowser from 'libraryBrowser'; +import imageLoader from 'imageLoader'; +import AlphaPicker from 'alphaPicker'; +import listView from 'listView'; +import cardBuilder from 'cardBuilder'; +import * as userSettings from 'userSettings'; +import globalize from 'globalize'; +import 'emby-itemscontainer'; - return function (view, params, tabContent) { +/* eslint-disable indent */ + + export default function (view, params, tabContent) { function getPageData(context) { - var key = getSavedQueryKey(context); - var pageData = data[key]; + const key = getSavedQueryKey(context); + let pageData = data[key]; if (!pageData) { pageData = data[key] = { @@ -43,11 +53,11 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', ' return context.savedQueryKey; } - function reloadItems() { + const reloadItems = () => { loading.show(); isLoading = true; - var query = getQuery(tabContent); - ApiClient.getItems(ApiClient.getCurrentUserId(), query).then(function (result) { + const query = getQuery(tabContent); + ApiClient.getItems(ApiClient.getCurrentUserId(), query).then((result) => { function onNextPageClick() { if (isLoading) { return; @@ -72,7 +82,7 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', ' window.scrollTo(0, 0); updateFilterControls(tabContent); - var pagingHtml = libraryBrowser.getQueryPagingHtml({ + const pagingHtml = libraryBrowser.getQueryPagingHtml({ startIndex: query.StartIndex, limit: query.Limit, totalRecordCount: result.TotalRecordCount, @@ -82,8 +92,8 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', ' sortButton: false, filterButton: false }); - var html; - var viewStyle = self.getCurrentViewStyle(); + let html; + const viewStyle = this.getCurrentViewStyle(); if (viewStyle == 'Thumb') { html = cardBuilder.getCardsHtml({ @@ -139,22 +149,20 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', ' }); } - var i; - var length; - var elems = tabContent.querySelectorAll('.paging'); + let elems = tabContent.querySelectorAll('.paging'); - for (i = 0, length = elems.length; i < length; i++) { - elems[i].innerHTML = pagingHtml; + for (const elem of elems) { + elem.innerHTML = pagingHtml; } elems = tabContent.querySelectorAll('.btnNextPage'); - for (i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener('click', onNextPageClick); + for (const elem of elems) { + elem.addEventListener('click', onNextPageClick); } elems = tabContent.querySelectorAll('.btnPreviousPage'); - for (i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener('click', onPreviousPageClick); + for (const elem of elems) { + elem.addEventListener('click', onPreviousPageClick); } if (!result.Items.length) { @@ -166,27 +174,26 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', ' html += '
'; } - var itemsContainer = tabContent.querySelector('.itemsContainer'); + const itemsContainer = tabContent.querySelector('.itemsContainer'); itemsContainer.innerHTML = html; imageLoader.lazyChildren(itemsContainer); libraryBrowser.saveQueryValues(getSavedQueryKey(tabContent), query); loading.hide(); isLoading = false; }); - } + }; - function updateFilterControls(tabContent) { - var query = getQuery(tabContent); - self.alphaPicker.value(query.NameStartsWithOrGreater); - } + const updateFilterControls = (tabContent) => { + const query = getQuery(tabContent); + this.alphaPicker.value(query.NameStartsWithOrGreater); + }; - var self = this; - var data = {}; - var isLoading = false; + const data = {}; + let isLoading = false; - self.showFilterMenu = function () { - require(['components/filterdialog/filterdialog'], function ({default: filterDialogFactory}) { - var filterDialog = new filterDialogFactory({ + this.showFilterMenu = function () { + import('components/filterdialog/filterdialog').then(({default: filterDialogFactory}) => { + const filterDialog = new filterDialogFactory({ query: getQuery(tabContent), mode: 'movies', serverId: ApiClient.serverId() @@ -199,21 +206,21 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', ' }); }; - self.getCurrentViewStyle = function () { + this.getCurrentViewStyle = function () { return getPageData(tabContent).view; }; - function initPage(tabContent) { - var alphaPickerElement = tabContent.querySelector('.alphaPicker'); - var itemsContainer = tabContent.querySelector('.itemsContainer'); + const initPage = (tabContent) => { + const alphaPickerElement = tabContent.querySelector('.alphaPicker'); + const itemsContainer = tabContent.querySelector('.itemsContainer'); alphaPickerElement.addEventListener('alphavaluechanged', function (e) { - var newValue = e.detail.value; - var query = getQuery(tabContent); + const newValue = e.detail.value; + const query = getQuery(tabContent); query.NameStartsWithOrGreater = newValue; query.StartIndex = 0; reloadItems(); }); - self.alphaPicker = new AlphaPicker.default({ + this.alphaPicker = new AlphaPicker({ element: alphaPickerElement, valueChangeEvent: 'click' }); @@ -223,12 +230,12 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', ' itemsContainer.classList.add('padded-right-withalphapicker'); tabContent.querySelector('.btnFilter').addEventListener('click', function () { - self.showFilterMenu(); + this.showFilterMenu(); }); tabContent.querySelector('.btnSort').addEventListener('click', function (e) { libraryBrowser.showSortMenu({ items: [{ - name: globalize.translate('OptionNameSort'), + name: globalize.translate('Name'), id: 'SortName' }, { name: globalize.translate('OptionImdbRating'), @@ -257,15 +264,16 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', ' button: e.target }); }); - } + }; initPage(tabContent); - self.renderTab = function () { + this.renderTab = function () { reloadItems(); updateFilterControls(tabContent); }; - self.destroy = function () {}; - }; -}); + this.destroy = function () {}; + } + +/* eslint-enable indent */ diff --git a/src/music.html b/src/controllers/music/music.html similarity index 89% rename from src/music.html rename to src/controllers/music/music.html index 1e22ae9f3a..84d324857d 100644 --- a/src/music.html +++ b/src/controllers/music/music.html @@ -38,10 +38,10 @@
- + - - + +
@@ -57,7 +57,7 @@
- +
@@ -73,7 +73,7 @@
- +
@@ -92,8 +92,8 @@
- - + +
diff --git a/src/controllers/music/musicalbums.js b/src/controllers/music/musicalbums.js index 3630e0b9f9..7947aa1376 100644 --- a/src/controllers/music/musicalbums.js +++ b/src/controllers/music/musicalbums.js @@ -1,7 +1,18 @@ -define(['layoutManager', 'playbackManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', 'alphaPicker', 'listView', 'cardBuilder', 'userSettings', 'globalize', 'emby-itemscontainer'], function (layoutManager, playbackManager, loading, events, libraryBrowser, imageLoader, AlphaPicker, listView, cardBuilder, userSettings, globalize) { - 'use strict'; +import playbackManager from 'playbackManager'; +import loading from 'loading'; +import events from 'events'; +import libraryBrowser from 'libraryBrowser'; +import imageLoader from 'imageLoader'; +import AlphaPicker from 'alphaPicker'; +import listView from 'listView'; +import cardBuilder from 'cardBuilder'; +import * as userSettings from 'userSettings'; +import globalize from 'globalize'; +import 'emby-itemscontainer'; - return function (view, params, tabContent) { +/* eslint-disable indent */ + + export default function (view, params, tabContent) { function playAll() { ApiClient.getItem(ApiClient.getCurrentUserId(), params.topParentId).then(function (item) { playbackManager.play({ @@ -18,7 +29,7 @@ define(['layoutManager', 'playbackManager', 'loading', 'events', 'libraryBrowser } function getPageData() { - var key = getSavedQueryKey(); + const key = getSavedQueryKey(); if (!pageData) { pageData = { @@ -58,11 +69,11 @@ define(['layoutManager', 'playbackManager', 'loading', 'events', 'libraryBrowser return savedQueryKey; } - function onViewStyleChange() { - var viewStyle = self.getCurrentViewStyle(); - var itemsContainer = tabContent.querySelector('.itemsContainer'); + const onViewStyleChange = () => { + const viewStyle = this.getCurrentViewStyle(); + const itemsContainer = tabContent.querySelector('.itemsContainer'); - if ('List' == viewStyle) { + if (viewStyle == 'List') { itemsContainer.classList.add('vertical-list'); itemsContainer.classList.remove('vertical-wrap'); } else { @@ -71,13 +82,13 @@ define(['layoutManager', 'playbackManager', 'loading', 'events', 'libraryBrowser } itemsContainer.innerHTML = ''; - } + }; - function reloadItems(page) { + const reloadItems = (page) => { loading.show(); isLoading = true; - var query = getQuery(); - ApiClient.getItems(ApiClient.getCurrentUserId(), query).then(function (result) { + const query = getQuery(); + ApiClient.getItems(ApiClient.getCurrentUserId(), query).then((result) => { function onNextPageClick() { if (isLoading) { return; @@ -102,8 +113,8 @@ define(['layoutManager', 'playbackManager', 'loading', 'events', 'libraryBrowser window.scrollTo(0, 0); updateFilterControls(page); - var html; - var pagingHtml = libraryBrowser.getQueryPagingHtml({ + let html; + const pagingHtml = libraryBrowser.getQueryPagingHtml({ startIndex: query.StartIndex, limit: query.Limit, totalRecordCount: result.TotalRecordCount, @@ -113,7 +124,7 @@ define(['layoutManager', 'playbackManager', 'loading', 'events', 'libraryBrowser sortButton: false, filterButton: false }); - var viewStyle = self.getCurrentViewStyle(); + const viewStyle = this.getCurrentViewStyle(); if (viewStyle == 'List') { html = listView.getListViewHtml({ items: result.Items, @@ -144,50 +155,47 @@ define(['layoutManager', 'playbackManager', 'loading', 'events', 'libraryBrowser overlayPlayButton: true }); } - var i; - var length; - var elems = tabContent.querySelectorAll('.paging'); + let elems = tabContent.querySelectorAll('.paging'); - for (i = 0, length = elems.length; i < length; i++) { + for (let i = 0, length = elems.length; i < length; i++) { elems[i].innerHTML = pagingHtml; } elems = tabContent.querySelectorAll('.btnNextPage'); - for (i = 0, length = elems.length; i < length; i++) { + for (let i = 0, length = elems.length; i < length; i++) { elems[i].addEventListener('click', onNextPageClick); } elems = tabContent.querySelectorAll('.btnPreviousPage'); - for (i = 0, length = elems.length; i < length; i++) { + for (let i = 0, length = elems.length; i < length; i++) { elems[i].addEventListener('click', onPreviousPageClick); } - var itemsContainer = tabContent.querySelector('.itemsContainer'); + const itemsContainer = tabContent.querySelector('.itemsContainer'); itemsContainer.innerHTML = html; imageLoader.lazyChildren(itemsContainer); libraryBrowser.saveQueryValues(getSavedQueryKey(), query); loading.hide(); isLoading = false; - require(['autoFocuser'], function (autoFocuser) { + import('autoFocuser').then(({default: autoFocuser}) => { autoFocuser.autoFocus(tabContent); }); }); - } + }; - function updateFilterControls(tabContent) { - var query = getQuery(); - self.alphaPicker.value(query.NameStartsWithOrGreater); - } + const updateFilterControls = (tabContent) => { + const query = getQuery(); + this.alphaPicker.value(query.NameStartsWithOrGreater); + }; - var savedQueryKey; - var pageData; - var self = this; - var isLoading = false; + let savedQueryKey; + let pageData; + let isLoading = false; - self.showFilterMenu = function () { - require(['components/filterdialog/filterdialog'], function ({default: filterDialogFactory}) { - var filterDialog = new filterDialogFactory({ + this.showFilterMenu = function () { + import('components/filterdialog/filterdialog').then(({default: filterDialogFactory}) => { + const filterDialog = new filterDialogFactory({ query: getQuery(), mode: 'albums', serverId: ApiClient.serverId() @@ -200,22 +208,22 @@ define(['layoutManager', 'playbackManager', 'loading', 'events', 'libraryBrowser }); }; - self.getCurrentViewStyle = function () { + this.getCurrentViewStyle = function () { return getPageData().view; }; - function initPage(tabContent) { - var alphaPickerElement = tabContent.querySelector('.alphaPicker'); - var itemsContainer = tabContent.querySelector('.itemsContainer'); + const initPage = (tabContent) => { + const alphaPickerElement = tabContent.querySelector('.alphaPicker'); + const itemsContainer = tabContent.querySelector('.itemsContainer'); alphaPickerElement.addEventListener('alphavaluechanged', function (e) { - var newValue = e.detail.value; - var query = getQuery(); + const newValue = e.detail.value; + const query = getQuery(); query.NameStartsWithOrGreater = newValue; query.StartIndex = 0; reloadItems(tabContent); }); - self.alphaPicker = new AlphaPicker.default({ + this.alphaPicker = new AlphaPicker({ element: alphaPickerElement, valueChangeEvent: 'click' }); @@ -224,16 +232,16 @@ define(['layoutManager', 'playbackManager', 'loading', 'events', 'libraryBrowser alphaPickerElement.classList.add('alphaPicker-fixed-right'); itemsContainer.classList.add('padded-right-withalphapicker'); - tabContent.querySelector('.btnFilter').addEventListener('click', function () { - self.showFilterMenu(); + tabContent.querySelector('.btnFilter').addEventListener('click', () => { + this.showFilterMenu(); }); - tabContent.querySelector('.btnSort').addEventListener('click', function (e) { + tabContent.querySelector('.btnSort').addEventListener('click', (e) => { libraryBrowser.showSortMenu({ items: [{ - name: globalize.translate('OptionNameSort'), + name: globalize.translate('Name'), id: 'SortName' }, { - name: globalize.translate('OptionAlbumArtist'), + name: globalize.translate('AlbumArtist'), id: 'AlbumArtist,SortName' }, { name: globalize.translate('OptionCommunityRating'), @@ -259,12 +267,12 @@ define(['layoutManager', 'playbackManager', 'loading', 'events', 'libraryBrowser button: e.target }); }); - var btnSelectView = tabContent.querySelector('.btnSelectView'); - btnSelectView.addEventListener('click', function (e) { - libraryBrowser.showLayoutMenu(e.target, self.getCurrentViewStyle(), 'List,Poster,PosterCard'.split(',')); + const btnSelectView = tabContent.querySelector('.btnSelectView'); + btnSelectView.addEventListener('click', (e) => { + libraryBrowser.showLayoutMenu(e.target, this.getCurrentViewStyle(), 'List,Poster,PosterCard'.split(',')); }); btnSelectView.addEventListener('layoutchange', function (e) { - var viewStyle = e.detail.viewStyle; + const viewStyle = e.detail.viewStyle; getPageData().view = viewStyle; libraryBrowser.saveViewSetting(getSavedQueryKey(), viewStyle); getQuery().StartIndex = 0; @@ -273,16 +281,17 @@ define(['layoutManager', 'playbackManager', 'loading', 'events', 'libraryBrowser }); tabContent.querySelector('.btnPlayAll').addEventListener('click', playAll); tabContent.querySelector('.btnShuffle').addEventListener('click', shuffle); - } + }; initPage(tabContent); onViewStyleChange(); - self.renderTab = function () { + this.renderTab = function () { reloadItems(tabContent); updateFilterControls(tabContent); }; - self.destroy = function () {}; - }; -}); + this.destroy = function () {}; + } + +/* eslint-enable indent */ diff --git a/src/controllers/music/musicartists.js b/src/controllers/music/musicartists.js index c9a0b06b77..32e0a22a07 100644 --- a/src/controllers/music/musicartists.js +++ b/src/controllers/music/musicartists.js @@ -1,13 +1,22 @@ -define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', 'alphaPicker', 'listView', 'cardBuilder', 'apphost', 'userSettings', 'emby-itemscontainer'], function (layoutManager, loading, events, libraryBrowser, imageLoader, AlphaPicker, listView, cardBuilder, appHost, userSettings) { - 'use strict'; +import loading from 'loading'; +import events from 'events'; +import libraryBrowser from 'libraryBrowser'; +import imageLoader from 'imageLoader'; +import AlphaPicker from 'alphaPicker'; +import listView from 'listView'; +import cardBuilder from 'cardBuilder'; +import * as userSettings from 'userSettings'; +import 'emby-itemscontainer'; - return function (view, params, tabContent) { +/* eslint-disable indent */ + + export default function (view, params, tabContent) { function getPageData(context) { - var key = getSavedQueryKey(context); - var pageData = data[key]; + const key = getSavedQueryKey(context); + let pageData = data[key]; if (!pageData) { - var queryValues = { + const queryValues = { SortBy: 'SortName', SortOrder: 'Ascending', Recursive: true, @@ -36,19 +45,19 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', ' return getPageData(context).query; } - function getSavedQueryKey(context) { + const getSavedQueryKey = (context) => { if (!context.savedQueryKey) { - context.savedQueryKey = libraryBrowser.getSavedQueryKey(self.mode); + context.savedQueryKey = libraryBrowser.getSavedQueryKey(this.mode); } return context.savedQueryKey; - } + }; - function onViewStyleChange() { - var viewStyle = self.getCurrentViewStyle(); - var itemsContainer = tabContent.querySelector('.itemsContainer'); + const onViewStyleChange = () => { + const viewStyle = this.getCurrentViewStyle(); + const itemsContainer = tabContent.querySelector('.itemsContainer'); - if ('List' == viewStyle) { + if (viewStyle == 'List') { itemsContainer.classList.add('vertical-list'); itemsContainer.classList.remove('vertical-wrap'); } else { @@ -57,16 +66,16 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', ' } itemsContainer.innerHTML = ''; - } + }; - function reloadItems(page) { + const reloadItems = (page) => { loading.show(); isLoading = true; - var query = getQuery(page); - var promise = self.mode == 'albumartists' ? + const query = getQuery(page); + const promise = this.mode == 'albumartists' ? ApiClient.getAlbumArtists(ApiClient.getCurrentUserId(), query) : ApiClient.getArtists(ApiClient.getCurrentUserId(), query); - promise.then(function (result) { + promise.then((result) => { function onNextPageClick() { if (isLoading) { return; @@ -91,8 +100,8 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', ' window.scrollTo(0, 0); updateFilterControls(page); - var html; - var pagingHtml = libraryBrowser.getQueryPagingHtml({ + let html; + const pagingHtml = libraryBrowser.getQueryPagingHtml({ startIndex: query.StartIndex, limit: query.Limit, totalRecordCount: result.TotalRecordCount, @@ -102,7 +111,7 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', ' sortButton: false, filterButton: false }); - var viewStyle = self.getCurrentViewStyle(); + const viewStyle = this.getCurrentViewStyle(); if (viewStyle == 'List') { html = listView.getListViewHtml({ items: result.Items, @@ -129,51 +138,48 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', ' overlayPlayButton: true }); } - var i; - var length; - var elems = tabContent.querySelectorAll('.paging'); + let elems = tabContent.querySelectorAll('.paging'); - for (i = 0, length = elems.length; i < length; i++) { + for (let i = 0, length = elems.length; i < length; i++) { elems[i].innerHTML = pagingHtml; } elems = tabContent.querySelectorAll('.btnNextPage'); - for (i = 0, length = elems.length; i < length; i++) { + for (let i = 0, length = elems.length; i < length; i++) { elems[i].addEventListener('click', onNextPageClick); } elems = tabContent.querySelectorAll('.btnPreviousPage'); - for (i = 0, length = elems.length; i < length; i++) { + for (let i = 0, length = elems.length; i < length; i++) { elems[i].addEventListener('click', onPreviousPageClick); } - var itemsContainer = tabContent.querySelector('.itemsContainer'); + const itemsContainer = tabContent.querySelector('.itemsContainer'); itemsContainer.innerHTML = html; imageLoader.lazyChildren(itemsContainer); libraryBrowser.saveQueryValues(getSavedQueryKey(page), query); loading.hide(); isLoading = false; - require(['autoFocuser'], function (autoFocuser) { + import('autoFocuser').then(({default: autoFocuser}) => { autoFocuser.autoFocus(tabContent); }); }); - } + }; - function updateFilterControls(tabContent) { - var query = getQuery(tabContent); - self.alphaPicker.value(query.NameStartsWithOrGreater); - } + const updateFilterControls = (tabContent) => { + const query = getQuery(tabContent); + this.alphaPicker.value(query.NameStartsWithOrGreater); + }; - var self = this; - var data = {}; - var isLoading = false; + const data = {}; + let isLoading = false; - self.showFilterMenu = function () { - require(['components/filterdialog/filterdialog'], function ({default: filterDialogFactory}) { - var filterDialog = new filterDialogFactory({ + this.showFilterMenu = function () { + import('components/filterdialog/filterdialog').then(({default: filterDialogFactory}) => { + const filterDialog = new filterDialogFactory({ query: getQuery(tabContent), - mode: self.mode, + mode: this.mode, serverId: ApiClient.serverId() }); events.on(filterDialog, 'filterchange', function () { @@ -184,22 +190,22 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', ' }); }; - self.getCurrentViewStyle = function () { + this.getCurrentViewStyle = function () { return getPageData(tabContent).view; }; - function initPage(tabContent) { - var alphaPickerElement = tabContent.querySelector('.alphaPicker'); - var itemsContainer = tabContent.querySelector('.itemsContainer'); + const initPage = (tabContent) => { + const alphaPickerElement = tabContent.querySelector('.alphaPicker'); + const itemsContainer = tabContent.querySelector('.itemsContainer'); alphaPickerElement.addEventListener('alphavaluechanged', function (e) { - var newValue = e.detail.value; - var query = getQuery(tabContent); + const newValue = e.detail.value; + const query = getQuery(tabContent); query.NameStartsWithOrGreater = newValue; query.StartIndex = 0; reloadItems(tabContent); }); - self.alphaPicker = new AlphaPicker.default({ + this.alphaPicker = new AlphaPicker({ element: alphaPickerElement, valueChangeEvent: 'click' }); @@ -208,31 +214,32 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', ' alphaPickerElement.classList.add('alphaPicker-fixed-right'); itemsContainer.classList.add('padded-right-withalphapicker'); - tabContent.querySelector('.btnFilter').addEventListener('click', function () { - self.showFilterMenu(); + tabContent.querySelector('.btnFilter').addEventListener('click', () => { + this.showFilterMenu(); }); - var btnSelectView = tabContent.querySelector('.btnSelectView'); + const btnSelectView = tabContent.querySelector('.btnSelectView'); btnSelectView.addEventListener('click', function (e) { - libraryBrowser.showLayoutMenu(e.target, self.getCurrentViewStyle(), 'List,Poster,PosterCard'.split(',')); + libraryBrowser.showLayoutMenu(e.target, this.getCurrentViewStyle(), 'List,Poster,PosterCard'.split(',')); }); btnSelectView.addEventListener('layoutchange', function (e) { - var viewStyle = e.detail.viewStyle; + const viewStyle = e.detail.viewStyle; getPageData(tabContent).view = viewStyle; libraryBrowser.saveViewSetting(getSavedQueryKey(tabContent), viewStyle); getQuery(tabContent).StartIndex = 0; onViewStyleChange(); reloadItems(tabContent); }); - } + }; initPage(tabContent); onViewStyleChange(); - self.renderTab = function () { + this.renderTab = function () { reloadItems(tabContent); updateFilterControls(tabContent); }; - self.destroy = function () {}; - }; -}); + this.destroy = function () {}; + } + +/* eslint-enable indent */ diff --git a/src/controllers/music/musicgenres.js b/src/controllers/music/musicgenres.js index 82f2eba574..2cd9e2114b 100644 --- a/src/controllers/music/musicgenres.js +++ b/src/controllers/music/musicgenres.js @@ -1,10 +1,14 @@ -define(['libraryBrowser', 'cardBuilder', 'apphost', 'imageLoader', 'loading'], function (libraryBrowser, cardBuilder, appHost, imageLoader, loading) { - 'use strict'; +import libraryBrowser from 'libraryBrowser'; +import cardBuilder from 'cardBuilder'; +import imageLoader from 'imageLoader'; +import loading from 'loading'; - return function (view, params, tabContent) { +/* eslint-disable indent */ + + export default function (view, params, tabContent) { function getPageData() { - var key = getSavedQueryKey(); - var pageData = data[key]; + const key = getSavedQueryKey(); + let pageData = data[key]; if (!pageData) { pageData = data[key] = { @@ -34,15 +38,15 @@ define(['libraryBrowser', 'cardBuilder', 'apphost', 'imageLoader', 'loading'], f function getPromise() { loading.show(); - var query = getQuery(); + const query = getQuery(); return ApiClient.getGenres(ApiClient.getCurrentUserId(), query); } - function reloadItems(context, promise) { - var query = getQuery(); - promise.then(function (result) { - var html = ''; - var viewStyle = self.getCurrentViewStyle(); + const reloadItems = (context, promise) => { + const query = getQuery(); + promise.then((result) => { + let html = ''; + const viewStyle = this.getCurrentViewStyle(); if (viewStyle == 'Thumb') { html = cardBuilder.getCardsHtml({ @@ -82,49 +86,49 @@ define(['libraryBrowser', 'cardBuilder', 'apphost', 'imageLoader', 'loading'], f }); } - var elem = context.querySelector('#items'); + const elem = context.querySelector('#items'); elem.innerHTML = html; imageLoader.lazyChildren(elem); libraryBrowser.saveQueryValues(getSavedQueryKey(), query); loading.hide(); - require(['autoFocuser'], function (autoFocuser) { + import('autoFocuser').then(({default: autoFocuser}) => { autoFocuser.autoFocus(context); }); }); - } + }; function fullyReload() { - self.preRender(); - self.renderTab(); + this.preRender(); + this.renderTab(); } - var self = this; - var data = {}; + const data = {}; - self.getViewStyles = function () { + this.getViewStyles = function () { return 'Poster,PosterCard,Thumb,ThumbCard'.split(','); }; - self.getCurrentViewStyle = function () { + this.getCurrentViewStyle = function () { return getPageData().view; }; - self.setCurrentViewStyle = function (viewStyle) { + this.setCurrentViewStyle = function (viewStyle) { getPageData().view = viewStyle; libraryBrowser.saveViewSetting(getSavedQueryKey(), viewStyle); fullyReload(); }; - self.enableViewSelection = true; - var promise; + this.enableViewSelection = true; + let promise; - self.preRender = function () { + this.preRender = function () { promise = getPromise(); }; - self.renderTab = function () { + this.renderTab = function () { reloadItems(tabContent, promise); }; - }; -}); + } + +/* eslint-enable indent */ diff --git a/src/controllers/music/musicplaylists.js b/src/controllers/music/musicplaylists.js index f508489216..67e6a959eb 100644 --- a/src/controllers/music/musicplaylists.js +++ b/src/controllers/music/musicplaylists.js @@ -1,10 +1,14 @@ -define(['libraryBrowser', 'cardBuilder', 'apphost', 'imageLoader', 'loading'], function (libraryBrowser, cardBuilder, appHost, imageLoader, loading) { - 'use strict'; +import libraryBrowser from 'libraryBrowser'; +import cardBuilder from 'cardBuilder'; +import imageLoader from 'imageLoader'; +import loading from 'loading'; - return function (view, params, tabContent) { +/* eslint-disable indent */ + + export default function (view, params, tabContent) { function getPageData() { - var key = getSavedQueryKey(); - var pageData = data[key]; + const key = getSavedQueryKey(); + let pageData = data[key]; if (!pageData) { pageData = data[key] = { @@ -35,14 +39,14 @@ define(['libraryBrowser', 'cardBuilder', 'apphost', 'imageLoader', 'loading'], f function getPromise() { loading.show(); - var query = getQuery(); + const query = getQuery(); return ApiClient.getItems(ApiClient.getCurrentUserId(), query); } function reloadItems(context, promise) { - var query = getQuery(); + const query = getQuery(); promise.then(function (result) { - var html = ''; + let html = ''; html = cardBuilder.getCardsHtml({ items: result.Items, shape: 'square', @@ -53,33 +57,33 @@ define(['libraryBrowser', 'cardBuilder', 'apphost', 'imageLoader', 'loading'], f allowBottomPadding: true, cardLayout: false }); - var elem = context.querySelector('#items'); + const elem = context.querySelector('#items'); elem.innerHTML = html; imageLoader.lazyChildren(elem); libraryBrowser.saveQueryValues(getSavedQueryKey(), query); loading.hide(); - require(['autoFocuser'], function (autoFocuser) { + import('autoFocuser').then(({default: autoFocuser}) => { autoFocuser.autoFocus(context); }); }); } - var self = this; - var data = {}; + const data = {}; - self.getCurrentViewStyle = function () { + this.getCurrentViewStyle = function () { return getPageData().view; }; - var promise; + let promise; - self.preRender = function () { + this.preRender = function () { promise = getPromise(); }; - self.renderTab = function () { + this.renderTab = function () { reloadItems(tabContent, promise); }; - }; -}); + } + +/* eslint-enable indent */ diff --git a/src/controllers/music/musicrecommended.js b/src/controllers/music/musicrecommended.js index 3f025799f6..a93bfea4e3 100644 --- a/src/controllers/music/musicrecommended.js +++ b/src/controllers/music/musicrecommended.js @@ -1,8 +1,24 @@ -define(['browser', 'layoutManager', 'userSettings', 'inputManager', 'loading', 'cardBuilder', 'dom', 'apphost', 'imageLoader', 'libraryMenu', 'playbackManager', 'mainTabsManager', 'globalize', 'scrollStyles', 'emby-itemscontainer', 'emby-tabs', 'emby-button', 'flexStyles'], function (browser, layoutManager, userSettings, inputManager, loading, cardBuilder, dom, appHost, imageLoader, libraryMenu, playbackManager, mainTabsManager, globalize) { - 'use strict'; +import browser from 'browser'; +import layoutManager from 'layoutManager'; +import * as userSettings from 'userSettings'; +import inputManager from 'inputManager'; +import loading from 'loading'; +import cardBuilder from 'cardBuilder'; +import dom from 'dom'; +import imageLoader from 'imageLoader'; +import libraryMenu from 'libraryMenu'; +import * as mainTabsManager from 'mainTabsManager'; +import globalize from 'globalize'; +import 'scrollStyles'; +import 'emby-itemscontainer'; +import 'emby-tabs'; +import 'emby-button'; +import 'flexStyles'; + +/* eslint-disable indent */ function itemsPerRow() { - var screenWidth = dom.getWindowSize().innerWidth; + const screenWidth = dom.getWindowSize().innerWidth; if (screenWidth >= 1920) { return 9; @@ -29,8 +45,8 @@ define(['browser', 'layoutManager', 'userSettings', 'inputManager', 'loading', ' function loadLatest(page, parentId) { loading.show(); - var userId = ApiClient.getCurrentUserId(); - var options = { + const userId = ApiClient.getCurrentUserId(); + const options = { IncludeItemTypes: 'Audio', Limit: enableScrollX() ? 3 * itemsPerRow() : 2 * itemsPerRow(), Fields: 'PrimaryImageAspectRatio,BasicSyncInfo', @@ -41,8 +57,6 @@ define(['browser', 'layoutManager', 'userSettings', 'inputManager', 'loading', ' }; ApiClient.getJSON(ApiClient.getUrl('Users/' + userId + '/Items/Latest', options)).then(function (items) { var elem = page.querySelector('#recentlyAddedSongs'); - var supportsImageAnalysis = appHost.supports('imageanalysis'); - supportsImageAnalysis = false; elem.innerHTML = cardBuilder.getCardsHtml({ items: items, showUnplayedIndicator: false, @@ -51,23 +65,23 @@ define(['browser', 'layoutManager', 'userSettings', 'inputManager', 'loading', ' showTitle: true, showParentTitle: true, lazy: true, - centerText: !supportsImageAnalysis, - overlayPlayButton: !supportsImageAnalysis, + centerText: true, + overlayPlayButton: true, allowBottomPadding: !enableScrollX(), - cardLayout: supportsImageAnalysis, + cardLayout: false, coverImage: true }); imageLoader.lazyChildren(elem); loading.hide(); - require(['autoFocuser'], function (autoFocuser) { + import('autoFocuser').then(({default: autoFocuser}) => { autoFocuser.autoFocus(page); }); }); } function loadRecentlyPlayed(page, parentId) { - var options = { + const options = { SortBy: 'DatePlayed', SortOrder: 'Descending', IncludeItemTypes: 'Audio', @@ -81,7 +95,7 @@ define(['browser', 'layoutManager', 'userSettings', 'inputManager', 'loading', ' EnableTotalRecordCount: false }; ApiClient.getItems(ApiClient.getCurrentUserId(), options).then(function (result) { - var elem = page.querySelector('#recentlyPlayed'); + const elem = page.querySelector('#recentlyPlayed'); if (result.Items.length) { elem.classList.remove('hide'); @@ -90,8 +104,6 @@ define(['browser', 'layoutManager', 'userSettings', 'inputManager', 'loading', ' } var itemsContainer = elem.querySelector('.itemsContainer'); - var supportsImageAnalysis = appHost.supports('imageanalysis'); - supportsImageAnalysis = false; itemsContainer.innerHTML = cardBuilder.getCardsHtml({ items: result.Items, showUnplayedIndicator: false, @@ -100,10 +112,10 @@ define(['browser', 'layoutManager', 'userSettings', 'inputManager', 'loading', ' showParentTitle: true, action: 'instantmix', lazy: true, - centerText: !supportsImageAnalysis, - overlayMoreButton: !supportsImageAnalysis, + centerText: true, + overlayMoreButton: true, allowBottomPadding: !enableScrollX(), - cardLayout: supportsImageAnalysis, + cardLayout: false, coverImage: true }); imageLoader.lazyChildren(itemsContainer); @@ -111,7 +123,7 @@ define(['browser', 'layoutManager', 'userSettings', 'inputManager', 'loading', ' } function loadFrequentlyPlayed(page, parentId) { - var options = { + const options = { SortBy: 'PlayCount', SortOrder: 'Descending', IncludeItemTypes: 'Audio', @@ -125,7 +137,7 @@ define(['browser', 'layoutManager', 'userSettings', 'inputManager', 'loading', ' EnableTotalRecordCount: false }; ApiClient.getItems(ApiClient.getCurrentUserId(), options).then(function (result) { - var elem = page.querySelector('#topPlayed'); + const elem = page.querySelector('#topPlayed'); if (result.Items.length) { elem.classList.remove('hide'); @@ -134,8 +146,6 @@ define(['browser', 'layoutManager', 'userSettings', 'inputManager', 'loading', ' } var itemsContainer = elem.querySelector('.itemsContainer'); - var supportsImageAnalysis = appHost.supports('imageanalysis'); - supportsImageAnalysis = false; itemsContainer.innerHTML = cardBuilder.getCardsHtml({ items: result.Items, showUnplayedIndicator: false, @@ -144,10 +154,10 @@ define(['browser', 'layoutManager', 'userSettings', 'inputManager', 'loading', ' showParentTitle: true, action: 'instantmix', lazy: true, - centerText: !supportsImageAnalysis, - overlayMoreButton: !supportsImageAnalysis, + centerText: true, + overlayMoreButton: true, allowBottomPadding: !enableScrollX(), - cardLayout: supportsImageAnalysis, + cardLayout: false, coverImage: true }); imageLoader.lazyChildren(itemsContainer); @@ -160,29 +170,26 @@ define(['browser', 'layoutManager', 'userSettings', 'inputManager', 'loading', ' loadRecentlyPlayed(tabContent, parentId); loadFrequentlyPlayed(tabContent, parentId); - require(['components/favoriteitems'], function (favoriteItems) { + import('components/favoriteitems').then(({default: favoriteItems}) => { favoriteItems.render(tabContent, ApiClient.getCurrentUserId(), parentId, ['favoriteArtists', 'favoriteAlbums', 'favoriteSongs']); }); } function getTabs() { return [{ - name: globalize.translate('TabSuggestions') + name: globalize.translate('Suggestions') }, { - name: globalize.translate('TabAlbums') + name: globalize.translate('Albums') }, { - name: globalize.translate('TabAlbumArtists') + name: globalize.translate('HeaderAlbumArtists') }, { - name: globalize.translate('TabArtists') + name: globalize.translate('Artists') }, { - name: globalize.translate('TabPlaylists') + name: globalize.translate('Playlists') }, { - name: globalize.translate('TabSongs') + name: globalize.translate('Songs') }, { - name: globalize.translate('TabGenres') - }, { - name: globalize.translate('ButtonSearch'), - cssClass: 'searchTabButton' + name: globalize.translate('Genres') }]; } @@ -211,10 +218,10 @@ define(['browser', 'layoutManager', 'userSettings', 'inputManager', 'loading', ' } } - return function (view, params) { + export default function (view, params) { function reload() { loading.show(); - var tabContent = view.querySelector(".pageTabContent[data-index='0']"); + const tabContent = view.querySelector(".pageTabContent[data-index='0']"); loadSuggestionsTab(view, tabContent, params.topParentId); } @@ -256,53 +263,51 @@ define(['browser', 'layoutManager', 'userSettings', 'inputManager', 'loading', ' mainTabsManager.setTabs(view, currentTabIndex, getTabs, getTabContainers, onBeforeTabChange, onTabChange); } - function getTabController(page, index, callback) { - var depends = []; + const getTabController = (page, index, callback) => { + let depends; switch (index) { case 0: + depends = 'controllers/music/musicrecommended'; break; case 1: - depends.push('controllers/music/musicalbums'); + depends = 'controllers/music/musicalbums'; break; case 2: case 3: - depends.push('controllers/music/musicartists'); + depends = 'controllers/music/musicartists'; break; case 4: - depends.push('controllers/music/musicplaylists'); + depends = 'controllers/music/musicplaylists'; break; case 5: - depends.push('controllers/music/songs'); + depends = 'controllers/music/songs'; break; case 6: - depends.push('controllers/music/musicgenres'); + depends = 'controllers/music/musicgenres'; break; - - case 7: - depends.push('scripts/searchtab'); } - require(depends, function (controllerFactory) { - var tabContent; + import(depends).then(({default: controllerFactory}) => { + let tabContent; - if (0 == index) { + if (index == 0) { tabContent = view.querySelector(".pageTabContent[data-index='" + index + "']"); - self.tabContent = tabContent; + this.tabContent = tabContent; } - var controller = tabControllers[index]; + let controller = tabControllers[index]; if (!controller) { tabContent = view.querySelector(".pageTabContent[data-index='" + index + "']"); if (index === 0) { - controller = self; + controller = this; } else if (index === 7) { controller = new controllerFactory(view, tabContent, { collectionType: 'music', @@ -326,7 +331,7 @@ define(['browser', 'layoutManager', 'userSettings', 'inputManager', 'loading', ' callback(controller); }); - } + }; function preLoadTab(page, index) { getTabController(page, index, function (controller) { @@ -339,8 +344,6 @@ define(['browser', 'layoutManager', 'userSettings', 'inputManager', 'loading', ' function loadTab(page, index) { currentTabIndex = index; getTabController(page, index, function (controller) { - initialTabIndex = null; - if (renderedTabs.indexOf(index) == -1) { renderedTabs.push(index); controller.renderTab(); @@ -356,31 +359,27 @@ define(['browser', 'layoutManager', 'userSettings', 'inputManager', 'loading', ' } } - var isViewRestored; - var self = this; - var currentTabIndex = parseInt(params.tab || getDefaultTabIndex(params.topParentId)); - var initialTabIndex = currentTabIndex; + let currentTabIndex = parseInt(params.tab || getDefaultTabIndex(params.topParentId)); - self.initTab = function () { - var tabContent = view.querySelector(".pageTabContent[data-index='0']"); - var containers = tabContent.querySelectorAll('.itemsContainer'); + this.initTab = function () { + const tabContent = view.querySelector(".pageTabContent[data-index='0']"); + const containers = tabContent.querySelectorAll('.itemsContainer'); - for (var i = 0, length = containers.length; i < length; i++) { + for (let i = 0, length = containers.length; i < length; i++) { setScrollClasses(containers[i], enableScrollX()); } }; - self.renderTab = function () { + this.renderTab = function () { reload(); }; - var tabControllers = []; - var renderedTabs = []; + const tabControllers = []; + const renderedTabs = []; view.addEventListener('viewshow', function (e) { - isViewRestored = e.detail.isRestored; initTabs(); if (!view.getAttribute('data-title')) { - var parentId = params.topParentId; + const parentId = params.topParentId; if (parentId) { ApiClient.getItem(ApiClient.getCurrentUserId(), parentId).then(function (item) { @@ -405,5 +404,6 @@ define(['browser', 'layoutManager', 'userSettings', 'inputManager', 'loading', ' } }); }); - }; -}); + } + +/* eslint-enable indent */ diff --git a/src/controllers/music/songs.js b/src/controllers/music/songs.js index aa63ec51fe..d30c74deb8 100644 --- a/src/controllers/music/songs.js +++ b/src/controllers/music/songs.js @@ -1,10 +1,18 @@ -define(['events', 'libraryBrowser', 'imageLoader', 'listView', 'loading', 'userSettings', 'globalize', 'emby-itemscontainer'], function (events, libraryBrowser, imageLoader, listView, loading, userSettings, globalize) { - 'use strict'; +import events from 'events'; +import libraryBrowser from 'libraryBrowser'; +import imageLoader from 'imageLoader'; +import listView from 'listView'; +import loading from 'loading'; +import * as userSettings from 'userSettings'; +import globalize from 'globalize'; +import 'emby-itemscontainer'; - return function (view, params, tabContent) { +/* eslint-disable indent */ + + export default function (view, params, tabContent) { function getPageData(context) { - var key = getSavedQueryKey(context); - var pageData = data[key]; + const key = getSavedQueryKey(context); + let pageData = data[key]; if (!pageData) { pageData = data[key] = { @@ -46,7 +54,7 @@ define(['events', 'libraryBrowser', 'imageLoader', 'listView', 'loading', 'userS function reloadItems(page) { loading.show(); isLoading = true; - var query = getQuery(page); + const query = getQuery(page); ApiClient.getItems(Dashboard.getCurrentUserId(), query).then(function (result) { function onNextPageClick() { if (isLoading) { @@ -71,9 +79,7 @@ define(['events', 'libraryBrowser', 'imageLoader', 'listView', 'loading', 'userS } window.scrollTo(0, 0); - var i; - var length; - var pagingHtml = libraryBrowser.getQueryPagingHtml({ + const pagingHtml = libraryBrowser.getQueryPagingHtml({ startIndex: query.StartIndex, limit: query.Limit, totalRecordCount: result.TotalRecordCount, @@ -83,49 +89,49 @@ define(['events', 'libraryBrowser', 'imageLoader', 'listView', 'loading', 'userS sortButton: false, filterButton: false }); - var html = listView.getListViewHtml({ + const html = listView.getListViewHtml({ items: result.Items, action: 'playallfromhere', smallIcon: true, artist: true, addToListButton: true }); - var elems = tabContent.querySelectorAll('.paging'); + let elems = tabContent.querySelectorAll('.paging'); - for (i = 0, length = elems.length; i < length; i++) { + for (let i = 0, length = elems.length; i < length; i++) { elems[i].innerHTML = pagingHtml; } elems = tabContent.querySelectorAll('.btnNextPage'); - for (i = 0, length = elems.length; i < length; i++) { + for (let i = 0, length = elems.length; i < length; i++) { elems[i].addEventListener('click', onNextPageClick); } elems = tabContent.querySelectorAll('.btnPreviousPage'); - for (i = 0, length = elems.length; i < length; i++) { + for (let i = 0, length = elems.length; i < length; i++) { elems[i].addEventListener('click', onPreviousPageClick); } - var itemsContainer = tabContent.querySelector('.itemsContainer'); + const itemsContainer = tabContent.querySelector('.itemsContainer'); itemsContainer.innerHTML = html; imageLoader.lazyChildren(itemsContainer); libraryBrowser.saveQueryValues(getSavedQueryKey(page), query); loading.hide(); isLoading = false; - require(['autoFocuser'], function (autoFocuser) { + import('autoFocuser').then(({default: autoFocuser}) => { autoFocuser.autoFocus(page); }); }); } - var self = this; - var data = {}; - var isLoading = false; + const self = this; + const data = {}; + let isLoading = false; self.showFilterMenu = function () { - require(['components/filterdialog/filterdialog'], function ({default: filterDialogFactory}) { - var filterDialog = new filterDialogFactory({ + import('components/filterdialog/filterdialog').then(({default: filterDialogFactory}) => { + const filterDialog = new filterDialogFactory({ query: getQuery(tabContent), mode: 'songs', serverId: ApiClient.serverId() @@ -152,13 +158,13 @@ define(['events', 'libraryBrowser', 'imageLoader', 'listView', 'loading', 'userS name: globalize.translate('OptionTrackName'), id: 'Name' }, { - name: globalize.translate('OptionAlbum'), + name: globalize.translate('Album'), id: 'Album,SortName' }, { - name: globalize.translate('OptionAlbumArtist'), + name: globalize.translate('AlbumArtist'), id: 'AlbumArtist,Album,SortName' }, { - name: globalize.translate('OptionArtist'), + name: globalize.translate('Artist'), id: 'Artist,Album,SortName' }, { name: globalize.translate('OptionDateAdded'), @@ -173,7 +179,7 @@ define(['events', 'libraryBrowser', 'imageLoader', 'listView', 'loading', 'userS name: globalize.translate('OptionReleaseDate'), id: 'PremiereDate,AlbumArtist,Album,SortName' }, { - name: globalize.translate('OptionRuntime'), + name: globalize.translate('Runtime'), id: 'Runtime,AlbumArtist,Album,SortName' }], callback: function () { @@ -193,5 +199,6 @@ define(['events', 'libraryBrowser', 'imageLoader', 'listView', 'loading', 'userS }; self.destroy = function () {}; - }; -}); + } + +/* eslint-enable indent */ diff --git a/src/controllers/playback/queue/index.html b/src/controllers/playback/queue/index.html index 592807737f..f38f9cd010 100644 --- a/src/controllers/playback/queue/index.html +++ b/src/controllers/playback/queue/index.html @@ -30,7 +30,7 @@
- @@ -59,7 +59,7 @@ - @@ -71,7 +71,7 @@ - @@ -81,11 +81,11 @@ - - @@ -99,7 +99,7 @@
-
@@ -120,7 +120,7 @@ -

- - -
@@ -180,10 +180,10 @@ - -
diff --git a/src/controllers/playback/video/index.html b/src/controllers/playback/video/index.html index 452c8a9af8..77274230e4 100644 --- a/src/controllers/playback/video/index.html +++ b/src/controllers/playback/video/index.html @@ -1,5 +1,4 @@
-
@@ -28,7 +27,7 @@ - @@ -44,7 +43,7 @@ - diff --git a/src/controllers/playback/video/index.js b/src/controllers/playback/video/index.js index e821c2393e..a5c270bf6d 100644 --- a/src/controllers/playback/video/index.js +++ b/src/controllers/playback/video/index.js @@ -1,12 +1,12 @@ import playbackManager from 'playbackManager'; import dom from 'dom'; import inputManager from 'inputManager'; +import mouseManager from 'mouseManager'; import datetime from 'datetime'; import itemHelper from 'itemHelper'; import mediaInfo from 'mediaInfo'; import focusManager from 'focusManager'; import events from 'events'; -import connectionManager from 'connectionManager'; import browser from 'browser'; import globalize from 'globalize'; import appHost from 'apphost'; @@ -21,26 +21,26 @@ import 'css!assets/css/videoosd'; /* eslint-disable indent */ function seriesImageUrl(item, options) { - if ('Episode' !== item.Type) { + if (item.Type !== 'Episode') { return null; } options = options || {}; options.type = options.type || 'Primary'; - if ('Primary' === options.type && item.SeriesPrimaryImageTag) { + if (options.type === 'Primary' && item.SeriesPrimaryImageTag) { options.tag = item.SeriesPrimaryImageTag; - return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); + return window.connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); } - if ('Thumb' === options.type) { + if (options.type === 'Thumb') { if (item.SeriesThumbImageTag) { options.tag = item.SeriesThumbImageTag; - return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); + return window.connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); } if (item.ParentThumbImageTag) { options.tag = item.ParentThumbImageTag; - return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.ParentThumbItemId, options); + return window.connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.ParentThumbItemId, options); } } @@ -53,12 +53,12 @@ import 'css!assets/css/videoosd'; if (item.ImageTags && item.ImageTags[options.type]) { options.tag = item.ImageTags[options.type]; - return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.PrimaryImageItemId || item.Id, options); + return window.connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.PrimaryImageItemId || item.Id, options); } - if ('Primary' === options.type && item.AlbumId && item.AlbumPrimaryImageTag) { + if (options.type === 'Primary' && item.AlbumId && item.AlbumPrimaryImageTag) { options.tag = item.AlbumPrimaryImageTag; - return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.AlbumId, options); + return window.connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.AlbumId, options); } return null; @@ -103,7 +103,7 @@ import 'css!assets/css/videoosd'; function onDoubleClick(e) { const clientX = e.clientX; - if (null != clientX) { + if (clientX != null) { if (clientX < dom.getWindowSize().innerWidth / 2) { playbackManager.rewind(currentPlayer); } else { @@ -116,8 +116,8 @@ import 'css!assets/css/videoosd'; } function getDisplayItem(item) { - if ('TvChannel' === item.Type) { - const apiClient = connectionManager.getApiClient(item.ServerId); + if (item.Type === 'TvChannel') { + const apiClient = window.connectionManager.getApiClient(item.ServerId); return apiClient.getItem(apiClient.getCurrentUserId(), item.Id).then(function (refreshedItem) { return { originalItem: refreshedItem, @@ -132,7 +132,7 @@ import 'css!assets/css/videoosd'; } function updateRecordingButton(item) { - if (!item || 'Program' !== item.Type) { + if (!item || item.Type !== 'Program') { if (recordingButtonManager) { recordingButtonManager.destroy(); recordingButtonManager = null; @@ -141,7 +141,7 @@ import 'css!assets/css/videoosd'; return void view.querySelector('.btnRecord').classList.add('hide'); } - connectionManager.getApiClient(item.ServerId).getCurrentUser().then(function (user) { + window.connectionManager.getApiClient(item.ServerId).getCurrentUser().then(function (user) { if (user.Policy.EnableLiveTvManagement) { import('recordingButton').then(({default: RecordingButton}) => { if (recordingButtonManager) { @@ -171,12 +171,10 @@ import 'css!assets/css/videoosd'; } setTitle(displayItem, parentName); - let titleElement; - const osdTitle = view.querySelector('.osdTitle'); - titleElement = osdTitle; + const titleElement = view.querySelector('.osdTitle'); let displayName = itemHelper.getDisplayName(displayItem, { - includeParentInfo: 'Program' !== displayItem.Type, - includeIndexNumber: 'Program' !== displayItem.Type + includeParentInfo: displayItem.Type !== 'Program', + includeIndexNumber: displayItem.Type !== 'Program' }); if (!displayName) { @@ -197,8 +195,8 @@ import 'css!assets/css/videoosd'; tomatoes: false, endsAt: false, episodeTitle: false, - originalAirDate: 'Program' !== displayItem.Type, - episodeTitleIndexNumber: 'Program' !== displayItem.Type, + originalAirDate: displayItem.Type !== 'Program', + episodeTitleIndexNumber: displayItem.Type !== 'Program', programIndicator: false }); const osdMediaInfo = view.querySelector('.osdMediaInfo'); @@ -270,7 +268,7 @@ import 'css!assets/css/videoosd'; } function shouldEnableProgressByTimeOfDay(item) { - return !('TvChannel' !== item.Type || !item.CurrentProgram); + return !(item.Type !== 'TvChannel' || !item.CurrentProgram); } function updateNowPlayingInfo(player, state) { @@ -329,24 +327,24 @@ import 'css!assets/css/videoosd'; if (item) { let imgUrl = seriesImageUrl(item, { - maxWidth: osdPoster.clientWidth * 2, + maxWidth: osdPoster.clientWidth, type: 'Primary' }) || seriesImageUrl(item, { - maxWidth: osdPoster.clientWidth * 2, + maxWidth: osdPoster.clientWidth, type: 'Thumb' }) || imageUrl(item, { - maxWidth: osdPoster.clientWidth * 2, + maxWidth: osdPoster.clientWidth, type: 'Primary' }); if (!imgUrl && secondaryItem && (imgUrl = seriesImageUrl(secondaryItem, { - maxWidth: osdPoster.clientWidth * 2, + maxWidth: osdPoster.clientWidth, type: 'Primary' }) || seriesImageUrl(secondaryItem, { - maxWidth: osdPoster.clientWidth * 2, + maxWidth: osdPoster.clientWidth, type: 'Thumb' }) || imageUrl(secondaryItem, { - maxWidth: osdPoster.clientWidth * 2, + maxWidth: osdPoster.clientWidth, type: 'Primary' })), imgUrl) { return void (osdPoster.innerHTML = ''); @@ -367,10 +365,11 @@ import 'css!assets/css/videoosd'; function hideOsd() { slideUpToHide(headerElement); hideMainOsdControls(); + mouseManager.hideCursor(); } function toggleOsd() { - if ('osd' === currentVisibleMenu) { + if (currentVisibleMenu === 'osd') { hideOsd(); } else if (!currentVisibleMenu) { showOsd(); @@ -431,10 +430,11 @@ import 'css!assets/css/videoosd'; } function hideMainOsdControls() { - if ('osd' === currentVisibleMenu) { + if (currentVisibleMenu === 'osd') { const elem = osdBottomElement; clearHideAnimationEventListeners(elem); elem.classList.add('videoOsdBottom-hidden'); + dom.addEventListener(elem, transitionEndEventName, onHideAnimationComplete, { once: true }); @@ -460,7 +460,7 @@ import 'css!assets/css/videoosd'; } function onPointerMove(e) { - if ('mouse' === (e.pointerType || (layoutManager.mobile ? 'touch' : 'mouse'))) { + if ((e.pointerType || (layoutManager.mobile ? 'touch' : 'mouse')) === 'mouse') { const eventX = e.screenX || 0; const eventY = e.screenY || 0; const obj = lastPointerMoveData; @@ -488,7 +488,7 @@ import 'css!assets/css/videoosd'; switch (e.detail.command) { case 'left': - if ('osd' === currentVisibleMenu) { + if (currentVisibleMenu === 'osd') { showOsd(); } else { if (!currentVisibleMenu) { @@ -500,7 +500,7 @@ import 'css!assets/css/videoosd'; break; case 'right': - if ('osd' === currentVisibleMenu) { + if (currentVisibleMenu === 'osd') { showOsd(); } else if (!currentVisibleMenu) { e.preventDefault(); @@ -615,7 +615,7 @@ import 'css!assets/css/videoosd'; resetUpNextDialog(); console.debug('nowplaying event: ' + e.type); - if ('Video' !== state.NextMediaType) { + if (state.NextMediaType !== 'Video') { view.removeEventListener('viewbeforehide', onViewHideStopPlayback); Emby.Page.back(); } @@ -692,7 +692,7 @@ import 'css!assets/css/videoosd'; lastUpdateTime = now; const player = this; currentRuntimeTicks = playbackManager.duration(player); - const currentTime = playbackManager.currentTime(player); + const currentTime = playbackManager.currentTime(player) * 10000; updateTimeDisplay(currentTime, currentRuntimeTicks, playbackManager.playbackStartTime(player), playbackManager.getBufferedRanges(player)); const item = currentItem; refreshProgramInfoIfNeeded(player, item); @@ -702,7 +702,7 @@ import 'css!assets/css/videoosd'; } function showComingUpNextIfNeeded(player, currentItem, currentTimeTicks, runtimeTicks) { - if (runtimeTicks && currentTimeTicks && !comingUpNextDisplayed && !currentVisibleMenu && 'Episode' === currentItem.Type && userSettings.enableNextVideoInfoOverlay()) { + if (runtimeTicks && currentTimeTicks && !comingUpNextDisplayed && !currentVisibleMenu && currentItem.Type === 'Episode' && userSettings.enableNextVideoInfoOverlay()) { const showAtSecondsLeft = runtimeTicks >= 3e10 ? 40 : runtimeTicks >= 24e9 ? 35 : 30; const showAtTicks = runtimeTicks - 1e3 * showAtSecondsLeft * 1e4; const timeRemainingTicks = runtimeTicks - currentTimeTicks; @@ -714,7 +714,7 @@ import 'css!assets/css/videoosd'; } function onUpNextHidden() { - if ('upnext' === currentVisibleMenu) { + if (currentVisibleMenu === 'upnext') { currentVisibleMenu = null; } } @@ -737,7 +737,7 @@ import 'css!assets/css/videoosd'; } function refreshProgramInfoIfNeeded(player, item) { - if ('TvChannel' === item.Type) { + if (item.Type === 'TvChannel') { const program = item.CurrentProgram; if (program && program.EndDate) { @@ -766,7 +766,7 @@ import 'css!assets/css/videoosd'; if (isPaused) { btnPlayPauseIcon.classList.add('play_arrow'); - btnPlayPause.setAttribute('title', globalize.translate('ButtonPlay') + ' (k)'); + btnPlayPause.setAttribute('title', globalize.translate('Play') + ' (k)'); } else { btnPlayPauseIcon.classList.add('pause'); btnPlayPause.setAttribute('title', globalize.translate('ButtonPause') + ' (k)'); @@ -778,7 +778,7 @@ import 'css!assets/css/videoosd'; updatePlayPauseState(playState.IsPaused); const supportedCommands = playbackManager.getSupportedCommands(player); currentPlayerSupportedCommands = supportedCommands; - supportsBrightnessChange = -1 !== supportedCommands.indexOf('SetBrightness'); + supportsBrightnessChange = supportedCommands.indexOf('SetBrightness') !== -1; updatePlayerVolumeState(player, playState.IsMuted, playState.VolumeLevel); if (nowPlayingPositionSlider && !nowPlayingPositionSlider.dragging) { @@ -792,13 +792,13 @@ import 'css!assets/css/videoosd'; updateTimeDisplay(playState.PositionTicks, nowPlayingItem.RunTimeTicks, playState.PlaybackStartTimeTicks, playState.BufferedRanges || []); updateNowPlayingInfo(player, state); - if (state.MediaSource && state.MediaSource.SupportsTranscoding && -1 !== supportedCommands.indexOf('SetMaxStreamingBitrate')) { + if (state.MediaSource && state.MediaSource.SupportsTranscoding && supportedCommands.indexOf('SetMaxStreamingBitrate') !== -1) { view.querySelector('.btnVideoOsdSettings').classList.remove('hide'); } else { view.querySelector('.btnVideoOsdSettings').classList.add('hide'); } - const isProgressClear = state.MediaSource && null == state.MediaSource.RunTimeTicks; + const isProgressClear = state.MediaSource && state.MediaSource.RunTimeTicks == null; nowPlayingPositionSlider.setIsClear(isProgressClear); if (nowPlayingItem.RunTimeTicks) { @@ -806,19 +806,19 @@ import 'css!assets/css/videoosd'; userSettings.skipForwardLength() * 1000000 / nowPlayingItem.RunTimeTicks); } - if (-1 === supportedCommands.indexOf('ToggleFullscreen') || player.isLocalPlayer && layoutManager.tv && playbackManager.isFullscreen(player)) { + if (supportedCommands.indexOf('ToggleFullscreen') === -1 || player.isLocalPlayer && layoutManager.tv && playbackManager.isFullscreen(player)) { view.querySelector('.btnFullscreen').classList.add('hide'); } else { view.querySelector('.btnFullscreen').classList.remove('hide'); } - if (-1 === supportedCommands.indexOf('PictureInPicture')) { + if (supportedCommands.indexOf('PictureInPicture') === -1) { view.querySelector('.btnPip').classList.add('hide'); } else { view.querySelector('.btnPip').classList.remove('hide'); } - if (-1 === supportedCommands.indexOf('AirPlay')) { + if (supportedCommands.indexOf('AirPlay') === -1) { view.querySelector('.btnAirPlay').classList.add('hide'); } else { view.querySelector('.btnAirPlay').classList.remove('hide'); @@ -866,7 +866,7 @@ import 'css!assets/css/videoosd'; nowPlayingPositionSlider.value = 0; } - if (runtimeTicks && null != positionTicks && currentRuntimeTicks && !enableProgressByTimeOfDay && currentItem.RunTimeTicks && 'Recording' !== currentItem.Type) { + if (runtimeTicks && positionTicks != null && currentRuntimeTicks && !enableProgressByTimeOfDay && currentItem.RunTimeTicks && currentItem.Type !== 'Recording') { endsAtText.innerHTML = '  -  ' + mediaInfo.getEndsAtFromPosition(runtimeTicks, positionTicks, true); } else { endsAtText.innerHTML = ''; @@ -887,11 +887,11 @@ import 'css!assets/css/videoosd'; let showMuteButton = true; let showVolumeSlider = true; - if (-1 === supportedCommands.indexOf('Mute')) { + if (supportedCommands.indexOf('Mute') === -1) { showMuteButton = false; } - if (-1 === supportedCommands.indexOf('SetVolume')) { + if (supportedCommands.indexOf('SetVolume') === -1) { showVolumeSlider = false; } @@ -942,7 +942,7 @@ import 'css!assets/css/videoosd'; } function updateTimeText(elem, ticks, divider) { - if (null == ticks) { + if (ticks == null) { elem.innerHTML = ''; return; } @@ -963,7 +963,6 @@ import 'css!assets/css/videoosd'; const player = currentPlayer; if (player) { - // show subtitle offset feature only if player and media support it const showSubOffset = playbackManager.supportSubtitleOffset(player) && playbackManager.canHandleOffsetOnCurrentSubtitle(player); @@ -985,9 +984,9 @@ import 'css!assets/css/videoosd'; } function onSettingsOption(selectedOption) { - if ('stats' === selectedOption) { + if (selectedOption === 'stats') { toggleStats(); - } else if ('suboffset' === selectedOption) { + } else if (selectedOption === 'suboffset') { const player = currentPlayer; if (player) { playbackManager.enableShowingSubtitleOffset(player); @@ -1061,7 +1060,7 @@ import 'css!assets/css/videoosd'; const streams = playbackManager.subtitleTracks(player); let currentIndex = playbackManager.getSubtitleStreamIndex(player); - if (null == currentIndex) { + if (currentIndex == null) { currentIndex = -1; } @@ -1132,8 +1131,9 @@ import 'css!assets/css/videoosd'; clickedElement = e.target; const key = keyboardnavigation.getKeyName(e); + const isKeyModified = e.ctrlKey || e.altKey || e.metaKey; - if (!currentVisibleMenu && 32 === e.keyCode) { + if (!currentVisibleMenu && e.keyCode === 32) { playbackManager.playPause(currentPlayer); showOsd(); return; @@ -1236,10 +1236,18 @@ import 'css!assets/css/videoosd'; case '7': case '8': case '9': { - const percent = parseInt(key, 10) * 10; - playbackManager.seekPercent(percent, currentPlayer); + if (!isKeyModified) { + const percent = parseInt(key, 10) * 10; + playbackManager.seekPercent(percent, currentPlayer); + } break; } + case '>': + playbackManager.increasePlaybackRate(currentPlayer); + break; + case '<': + playbackManager.decreasePlaybackRate(currentPlayer); + break; } } @@ -1614,7 +1622,7 @@ import 'css!assets/css/videoosd'; const item = currentItem; if (item && item.Chapters && item.Chapters.length && item.Chapters[0].ImageTag) { - let html = getChapterBubbleHtml(connectionManager.getApiClient(item.ServerId), item, item.Chapters, ticks); + const html = getChapterBubbleHtml(window.connectionManager.getApiClient(item.ServerId), item, item.Chapters, ticks); if (html) { return html; diff --git a/src/search.html b/src/controllers/search.html similarity index 70% rename from src/search.html rename to src/controllers/search.html index acf65e7317..b0277aa7e0 100644 --- a/src/search.html +++ b/src/controllers/search.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/controllers/searchpage.js b/src/controllers/searchpage.js index 8ce3e8afe8..ffb7fbac0b 100644 --- a/src/controllers/searchpage.js +++ b/src/controllers/searchpage.js @@ -1,4 +1,3 @@ -import focusManager from 'focusManager'; import SearchFields from 'searchFields'; import SearchResults from 'searchResults'; import events from 'events'; diff --git a/src/controllers/session/addServer/index.html b/src/controllers/session/addServer/index.html index d25a8eef98..31dd66b33c 100644 --- a/src/controllers/session/addServer/index.html +++ b/src/controllers/session/addServer/index.html @@ -8,7 +8,7 @@

- - + +
@@ -26,12 +26,12 @@
-

${HeaderNextUp}

+

${NextUp}

- +
@@ -60,8 +60,8 @@
- - + +
diff --git a/src/controllers/shows/tvrecommended.js b/src/controllers/shows/tvrecommended.js index 7edd2ab501..c58a2faad3 100644 --- a/src/controllers/shows/tvrecommended.js +++ b/src/controllers/shows/tvrecommended.js @@ -17,22 +17,19 @@ import 'emby-button'; function getTabs() { return [{ - name: globalize.translate('TabShows') + name: globalize.translate('Shows') }, { - name: globalize.translate('TabSuggestions') + name: globalize.translate('Suggestions') }, { name: globalize.translate('TabLatest') }, { name: globalize.translate('TabUpcoming') }, { - name: globalize.translate('TabGenres') + name: globalize.translate('Genres') }, { name: globalize.translate('TabNetworks') }, { - name: globalize.translate('TabEpisodes') - }, { - name: globalize.translate('ButtonSearch'), - cssClass: 'searchTabButton' + name: globalize.translate('Episodes') }]; } @@ -217,10 +214,6 @@ import 'emby-button'; case 6: depends = 'controllers/shows/episodes'; break; - - case 7: - depends = 'scripts/searchtab'; - break; } import(depends).then(({default: controllerFactory}) => { @@ -269,8 +262,6 @@ import 'emby-button'; function loadTab(page, index) { currentTabIndex = index; getTabController(page, index, function (controller) { - initialTabIndex = null; - if (renderedTabs.indexOf(index) == -1) { renderedTabs.push(index); controller.renderTab(); @@ -301,10 +292,8 @@ import 'emby-button'; } } - let isViewRestored; const self = this; let currentTabIndex = parseInt(params.tab || getDefaultTabIndex(params.topParentId)); - let initialTabIndex = currentTabIndex; self.initTab = function () { const tabContent = self.tabContent; @@ -319,7 +308,6 @@ import 'emby-button'; let renderedTabs = []; setScrollClasses(view.querySelector('#resumableItems'), enableScrollX()); view.addEventListener('viewshow', function (e) { - isViewRestored = e.detail.isRestored; initTabs(); if (!view.getAttribute('data-title')) { const parentId = params.topParentId; @@ -330,8 +318,8 @@ import 'emby-button'; libraryMenu.setTitle(item.Name); }); } else { - view.setAttribute('data-title', globalize.translate('TabShows')); - libraryMenu.setTitle(globalize.translate('TabShows')); + view.setAttribute('data-title', globalize.translate('Shows')); + libraryMenu.setTitle(globalize.translate('Shows')); } } diff --git a/src/controllers/shows/tvshows.js b/src/controllers/shows/tvshows.js index cde5ae5058..ef45eba0f7 100644 --- a/src/controllers/shows/tvshows.js +++ b/src/controllers/shows/tvshows.js @@ -1,4 +1,3 @@ -import layoutManager from 'layoutManager'; import loading from 'loading'; import events from 'events'; import libraryBrowser from 'libraryBrowser'; @@ -59,7 +58,7 @@ import 'emby-itemscontainer'; const viewStyle = self.getCurrentViewStyle(); const itemsContainer = tabContent.querySelector('.itemsContainer'); - if ('List' == viewStyle) { + if (viewStyle == 'List') { itemsContainer.classList.add('vertical-list'); itemsContainer.classList.remove('vertical-wrap'); } else { @@ -251,7 +250,7 @@ import 'emby-itemscontainer'; tabContent.querySelector('.btnSort').addEventListener('click', function (e) { libraryBrowser.showSortMenu({ items: [{ - name: globalize.translate('OptionNameSort'), + name: globalize.translate('Name'), id: 'SortName' }, { name: globalize.translate('OptionImdbRating'), diff --git a/src/controllers/shows/tvstudios.js b/src/controllers/shows/tvstudios.js index ba7fdfaf78..4be717fb7f 100644 --- a/src/controllers/shows/tvstudios.js +++ b/src/controllers/shows/tvstudios.js @@ -1,7 +1,6 @@ import loading from 'loading'; import libraryBrowser from 'libraryBrowser'; import cardBuilder from 'cardBuilder'; -import appHost from 'apphost'; /* eslint-disable indent */ diff --git a/src/controllers/shows/tvupcoming.js b/src/controllers/shows/tvupcoming.js index a2016279b8..f9df3df343 100644 --- a/src/controllers/shows/tvupcoming.js +++ b/src/controllers/shows/tvupcoming.js @@ -1,9 +1,7 @@ import layoutManager from 'layoutManager'; import loading from 'loading'; import datetime from 'datetime'; -import libraryBrowser from 'libraryBrowser'; import cardBuilder from 'cardBuilder'; -import appHost from 'apphost'; import imageLoader from 'imageLoader'; import globalize from 'globalize'; import 'scrollStyles'; @@ -106,8 +104,6 @@ import 'emby-itemscontainer'; html += '
'; } - let supportsImageAnalysis = appHost.supports('imageanalysis'); - supportsImageAnalysis = false; html += cardBuilder.getCardsHtml({ items: group.items, showLocationTypeIndicator: false, @@ -116,11 +112,11 @@ import 'emby-itemscontainer'; preferThumb: true, lazy: true, showDetailsMenu: true, - centerText: !supportsImageAnalysis, + centerText: true, showParentTitle: true, overlayText: false, allowBottomPadding: allowBottomPadding, - cardLayout: supportsImageAnalysis, + cardLayout: false, overlayMoreButton: true, missingIndicator: false }); diff --git a/src/controllers/user/display/index.html b/src/controllers/user/display/index.html index bee49754af..5482b62fe8 100644 --- a/src/controllers/user/display/index.html +++ b/src/controllers/user/display/index.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/controllers/user/display/index.js b/src/controllers/user/display/index.js index a400c50ccf..54f71ad571 100644 --- a/src/controllers/user/display/index.js +++ b/src/controllers/user/display/index.js @@ -8,19 +8,13 @@ import autoFocuser from 'autoFocuser'; const UserSettings = userSettings.UserSettings; export default function (view, params) { - function onBeforeUnload(e) { - if (hasChanges) { - e.returnValue = 'You currently have unsaved changes. Are you sure you wish to leave?'; - } - } - let settingsInstance; let hasChanges; + const userId = params.userId || ApiClient.getCurrentUserId(); const currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new UserSettings(); - view.addEventListener('viewshow', function () { - window.addEventListener('beforeunload', onBeforeUnload); + view.addEventListener('viewshow', function () { if (settingsInstance) { settingsInstance.loadData(); } else { diff --git a/src/controllers/user/home/index.html b/src/controllers/user/home/index.html index 79c5ccc4bd..f98f373cb0 100644 --- a/src/controllers/user/home/index.html +++ b/src/controllers/user/home/index.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/controllers/user/home/index.js b/src/controllers/user/home/index.js index e7058fd3ac..539365ff97 100644 --- a/src/controllers/user/home/index.js +++ b/src/controllers/user/home/index.js @@ -1,7 +1,4 @@ import HomescreenSettings from 'homescreenSettings'; -import dom from 'dom'; -import globalize from 'globalize'; -import loading from 'loading'; import * as userSettings from 'userSettings'; import autoFocuser from 'autoFocuser'; import 'listViewStyle'; @@ -12,19 +9,13 @@ import 'listViewStyle'; const UserSettings = userSettings.UserSettings; export default function (view, params) { - function onBeforeUnload(e) { - if (hasChanges) { - e.returnValue = 'You currently have unsaved changes. Are you sure you wish to leave?'; - } - } - let homescreenSettingsInstance; let hasChanges; + const userId = params.userId || ApiClient.getCurrentUserId(); const currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new UserSettings(); - view.addEventListener('viewshow', function () { - window.addEventListener('beforeunload', onBeforeUnload); + view.addEventListener('viewshow', function () { if (homescreenSettingsInstance) { homescreenSettingsInstance.loadData(); } else { diff --git a/src/controllers/user/menu/index.html b/src/controllers/user/menu/index.html index 17f376a286..fc856e6219 100644 --- a/src/controllers/user/menu/index.html +++ b/src/controllers/user/menu/index.html @@ -1,4 +1,4 @@ -
+
@@ -7,7 +7,7 @@
-
${ButtonProfile}
+
${Profile}
@@ -16,7 +16,7 @@
-
${HeaderDisplay}
+
${Display}
@@ -25,7 +25,7 @@
-
${HeaderHome}
+
${Home}
@@ -67,7 +67,7 @@
-
+

${HeaderAdmin}

@@ -92,7 +92,7 @@
-
${HeaderSelectServer}
+
${SelectServer}
diff --git a/src/controllers/user/menu/index.js b/src/controllers/user/menu/index.js index 6d0f5f8021..88cf28a216 100644 --- a/src/controllers/user/menu/index.js +++ b/src/controllers/user/menu/index.js @@ -1,5 +1,4 @@ import appHost from 'apphost'; -import connectionManager from 'connectionManager'; import layoutManager from 'layoutManager'; import 'listViewStyle'; import 'emby-button'; @@ -41,19 +40,19 @@ export default function (view, params) { page.querySelector('.selectServer').classList.add('hide'); } - // hide the actions if user preferences are being edited for a different user + ApiClient.getUser(userId).then(function (user) { + page.querySelector('.headerUsername').innerHTML = user.Name; + if (user.Policy.IsAdministrator && !layoutManager.tv) { + page.querySelector('.adminSection').classList.remove('hide'); + } + }); + + // Hide the actions if user preferences are being edited for a different user if (params.userId && params.userId !== Dashboard.getCurrentUserId) { page.querySelector('.userSection').classList.add('hide'); page.querySelector('.adminSection').classList.add('hide'); } - ApiClient.getUser(userId).then(function (user) { - page.querySelector('.headerUsername').innerHTML = user.Name; - if (!user.Policy.IsAdministrator) { - page.querySelector('.adminSection').classList.add('hide'); - } - }); - import('autoFocuser').then(({default: autoFocuser}) => { autoFocuser.autoFocus(view); }); diff --git a/src/controllers/user/playback/index.js b/src/controllers/user/playback/index.js index 5bcf055cd3..34a5ae0b1d 100644 --- a/src/controllers/user/playback/index.js +++ b/src/controllers/user/playback/index.js @@ -1,7 +1,4 @@ import PlaybackSettings from 'playbackSettings'; -import dom from 'dom'; -import globalize from 'globalize'; -import loading from 'loading'; import * as userSettings from 'userSettings'; import autoFocuser from 'autoFocuser'; import 'listViewStyle'; @@ -12,19 +9,13 @@ import 'listViewStyle'; const UserSettings = userSettings.UserSettings; export default function (view, params) { - function onBeforeUnload(e) { - if (hasChanges) { - e.returnValue = 'You currently have unsaved changes. Are you sure you wish to leave?'; - } - } - let settingsInstance; let hasChanges; + const userId = params.userId || ApiClient.getCurrentUserId(); const currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new UserSettings(); - view.addEventListener('viewshow', function () { - window.addEventListener('beforeunload', onBeforeUnload); + view.addEventListener('viewshow', function () { if (settingsInstance) { settingsInstance.loadData(); } else { diff --git a/src/controllers/user/profile/index.html b/src/controllers/user/profile/index.html index 3765ac5e75..3eaa2f7299 100644 --- a/src/controllers/user/profile/index.html +++ b/src/controllers/user/profile/index.html @@ -1,4 +1,4 @@ -
+
@@ -12,7 +12,7 @@ ${ButtonAddImage}
@@ -32,10 +32,10 @@
@@ -57,7 +57,7 @@
+

@@ -12,10 +12,10 @@
diff --git a/src/controllers/wizard/remote/index.html b/src/controllers/wizard/remote/index.html index 0718c2dc37..0334f584c5 100644 --- a/src/controllers/wizard/remote/index.html +++ b/src/controllers/wizard/remote/index.html @@ -22,10 +22,10 @@
diff --git a/src/controllers/wizard/remote/index.js b/src/controllers/wizard/remote/index.js index 3c482607de..b967d668ad 100644 --- a/src/controllers/wizard/remote/index.js +++ b/src/controllers/wizard/remote/index.js @@ -11,8 +11,9 @@ function save(page) { config.EnableAutomaticPortMapping = page.querySelector('#chkEnableUpnp').checked; apiClient.ajax({ type: 'POST', - data: config, - url: apiClient.getUrl('Startup/RemoteAccess') + data: JSON.stringify(config), + url: apiClient.getUrl('Startup/RemoteAccess'), + contentType: 'application/json' }).then(function () { loading.hide(); navigateToNextPage(); diff --git a/src/controllers/wizard/settings/index.html b/src/controllers/wizard/settings/index.html index d4f537cf98..d1f557d8f1 100644 --- a/src/controllers/wizard/settings/index.html +++ b/src/controllers/wizard/settings/index.html @@ -17,10 +17,10 @@
diff --git a/src/controllers/wizard/settings/index.js b/src/controllers/wizard/settings/index.js index a1c3c11267..6e3a82cd9b 100644 --- a/src/controllers/wizard/settings/index.js +++ b/src/controllers/wizard/settings/index.js @@ -11,8 +11,9 @@ function save(page) { config.MetadataCountryCode = page.querySelector('#selectCountry').value; apiClient.ajax({ type: 'POST', - data: config, - url: apiClient.getUrl('Startup/Configuration') + data: JSON.stringify(config), + url: apiClient.getUrl('Startup/Configuration'), + contentType: 'application/json' }).then(function () { loading.hide(); navigateToNextPage(); diff --git a/src/controllers/wizard/start/index.html b/src/controllers/wizard/start/index.html index 05e282bee3..5306d2ca73 100644 --- a/src/controllers/wizard/start/index.html +++ b/src/controllers/wizard/start/index.html @@ -19,7 +19,7 @@
diff --git a/src/controllers/wizard/start/index.js b/src/controllers/wizard/start/index.js index cec93446db..3cd53b4ceb 100644 --- a/src/controllers/wizard/start/index.js +++ b/src/controllers/wizard/start/index.js @@ -17,8 +17,9 @@ function save(page) { config.UICulture = $('#selectLocalizationLanguage', page).val(); apiClient.ajax({ type: 'POST', - data: config, - url: apiClient.getUrl('Startup/Configuration') + data: JSON.stringify(config), + url: apiClient.getUrl('Startup/Configuration'), + contentType: 'application/json' }).then(function () { Dashboard.navigate('wizarduser.html'); }); diff --git a/src/controllers/wizard/user/index.html b/src/controllers/wizard/user/index.html index 3ce0b3ba74..24429d043a 100644 --- a/src/controllers/wizard/user/index.html +++ b/src/controllers/wizard/user/index.html @@ -23,10 +23,10 @@
diff --git a/src/controllers/wizard/user/index.js b/src/controllers/wizard/user/index.js index 855086a3a4..ec587fec8e 100644 --- a/src/controllers/wizard/user/index.js +++ b/src/controllers/wizard/user/index.js @@ -23,11 +23,12 @@ function submit(form) { const apiClient = getApiClient(); apiClient.ajax({ type: 'POST', - data: { + data: JSON.stringify({ Name: form.querySelector('#txtUsername').value, Password: form.querySelector('#txtManualPassword').value - }, - url: apiClient.getUrl('Startup/User') + }), + url: apiClient.getUrl('Startup/User'), + contentType: 'application/json' }).then(onUpdateUserComplete); } diff --git a/src/elements/emby-checkbox/emby-checkbox.js b/src/elements/emby-checkbox/emby-checkbox.js index d3f24d6f82..d44c58ed48 100644 --- a/src/elements/emby-checkbox/emby-checkbox.js +++ b/src/elements/emby-checkbox/emby-checkbox.js @@ -5,12 +5,12 @@ import 'webcomponents'; /* eslint-disable indent */ - let EmbyCheckboxPrototype = Object.create(HTMLInputElement.prototype); + const EmbyCheckboxPrototype = Object.create(HTMLInputElement.prototype); function onKeyDown(e) { // Don't submit form on enter // Real (non-emulator) Tizen does nothing on Space - if (e.keyCode === 13 || e.keyCode === 32) { + if (e.keyCode === 13 || (e.keyCode === 32 && browser.tizen)) { e.preventDefault(); this.checked = !this.checked; @@ -26,7 +26,7 @@ import 'webcomponents'; const enableRefreshHack = browser.tizen || browser.orsay || browser.operaTv || browser.web0s ? true : false; function forceRefresh(loading) { - let elem = this.parentNode; + const elem = this.parentNode; elem.style.webkitAnimationName = 'repaintChrome'; elem.style.webkitAnimationDelay = (loading === true ? '500ms' : ''); diff --git a/src/elements/emby-collapse/emby-collapse.js b/src/elements/emby-collapse/emby-collapse.js index f980f19af0..c87e73d48f 100644 --- a/src/elements/emby-collapse/emby-collapse.js +++ b/src/elements/emby-collapse/emby-collapse.js @@ -7,55 +7,54 @@ import 'emby-button'; const EmbyButtonPrototype = Object.create(HTMLDivElement.prototype); function slideDownToShow(button, elem) { - - elem.classList.remove('hide'); - elem.classList.add('expanded'); - elem.style.height = 'auto'; - const height = elem.offsetHeight + 'px'; - elem.style.height = '0'; - - // trigger reflow - const newHeight = elem.offsetHeight; - elem.style.height = height; - - setTimeout(function () { - if (elem.classList.contains('expanded')) { - elem.classList.remove('hide'); - } else { - elem.classList.add('hide'); - } + requestAnimationFrame(() => { + elem.classList.remove('hide'); + elem.classList.add('expanded'); elem.style.height = 'auto'; - }, 300); + const height = elem.offsetHeight + 'px'; + elem.style.height = '0'; + // trigger reflow + // TODO: Find a better way to do this + const newHeight = elem.offsetHeight; /* eslint-disable-line no-unused-vars */ + elem.style.height = height; - const icon = button.querySelector('.material-icons'); - //icon.innerHTML = 'expand_less'; - icon.classList.add('emby-collapse-expandIconExpanded'); + setTimeout(function () { + if (elem.classList.contains('expanded')) { + elem.classList.remove('hide'); + } else { + elem.classList.add('hide'); + } + elem.style.height = 'auto'; + }, 300); + + const icon = button.querySelector('.material-icons'); + icon.classList.add('emby-collapse-expandIconExpanded'); + }); } function slideUpToHide(button, elem) { + requestAnimationFrame(() => { + elem.style.height = elem.offsetHeight + 'px'; + // trigger reflow + // TODO: Find a better way to do this + const newHeight = elem.offsetHeight; /* eslint-disable-line no-unused-vars */ + elem.classList.remove('expanded'); + elem.style.height = '0'; - elem.style.height = elem.offsetHeight + 'px'; - // trigger reflow - const newHeight = elem.offsetHeight; + setTimeout(function () { + if (elem.classList.contains('expanded')) { + elem.classList.remove('hide'); + } else { + elem.classList.add('hide'); + } + }, 300); - elem.classList.remove('expanded'); - elem.style.height = '0'; - - setTimeout(function () { - if (elem.classList.contains('expanded')) { - elem.classList.remove('hide'); - } else { - elem.classList.add('hide'); - } - }, 300); - - const icon = button.querySelector('.material-icons'); - //icon.innerHTML = 'expand_more'; - icon.classList.remove('emby-collapse-expandIconExpanded'); + const icon = button.querySelector('.material-icons'); + icon.classList.remove('emby-collapse-expandIconExpanded'); + }); } function onButtonClick(e) { - const button = this; const collapseContent = button.parentNode.querySelector('.collapseContent'); @@ -69,7 +68,6 @@ import 'emby-button'; } EmbyButtonPrototype.attachedCallback = function () { - if (this.classList.contains('emby-collapse')) { return; } diff --git a/src/elements/emby-input/emby-input.js b/src/elements/emby-input/emby-input.js index d27cc71564..3a71e29a6f 100644 --- a/src/elements/emby-input/emby-input.js +++ b/src/elements/emby-input/emby-input.js @@ -11,7 +11,6 @@ import 'webcomponents'; let supportsFloatingLabel = false; if (Object.getOwnPropertyDescriptor && Object.defineProperty) { - const descriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value'); // descriptor returning null in webos @@ -94,16 +93,13 @@ import 'webcomponents'; } } } - }; function onChange() { - const label = this.labelElement; if (this.value) { label.classList.remove('inputLabel-float'); } else { - const instanceSupportsFloat = supportsFloatingLabel && this.type !== 'date' && this.type !== 'time'; if (instanceSupportsFloat) { diff --git a/src/elements/emby-itemrefreshindicator/emby-itemrefreshindicator.js b/src/elements/emby-itemrefreshindicator/emby-itemrefreshindicator.js index b1f8c4f4cf..51f3fc5be9 100644 --- a/src/elements/emby-itemrefreshindicator/emby-itemrefreshindicator.js +++ b/src/elements/emby-itemrefreshindicator/emby-itemrefreshindicator.js @@ -7,14 +7,12 @@ import 'webcomponents'; /* eslint-disable indent */ function addNotificationEvent(instance, name, handler) { - const localHandler = handler.bind(instance); events.on(serverNotifications, name, localHandler); instance[name] = localHandler; } function removeNotificationEvent(instance, name) { - const handler = instance[name]; if (handler) { events.off(serverNotifications, name, handler); @@ -23,7 +21,6 @@ import 'webcomponents'; } function onRefreshProgress(e, apiClient, info) { - const indicator = this; if (!indicator.itemId) { @@ -31,7 +28,6 @@ import 'webcomponents'; } if (info.ItemId === indicator.itemId) { - const progress = parseFloat(info.Progress); if (progress && progress < 100) { @@ -40,14 +36,13 @@ import 'webcomponents'; this.classList.add('hide'); } - this.setProgress(progress); + this.setAttribute('data-progress', progress); } } - let EmbyItemRefreshIndicatorPrototype = Object.create(EmbyProgressRing); + const EmbyItemRefreshIndicatorPrototype = Object.create(EmbyProgressRing); EmbyItemRefreshIndicatorPrototype.createdCallback = function () { - // base method if (EmbyProgressRing.createdCallback) { EmbyProgressRing.createdCallback.call(this); @@ -57,7 +52,6 @@ import 'webcomponents'; }; EmbyItemRefreshIndicatorPrototype.attachedCallback = function () { - // base method if (EmbyProgressRing.attachedCallback) { EmbyProgressRing.attachedCallback.call(this); @@ -65,7 +59,6 @@ import 'webcomponents'; }; EmbyItemRefreshIndicatorPrototype.detachedCallback = function () { - // base method if (EmbyProgressRing.detachedCallback) { EmbyProgressRing.detachedCallback.call(this); diff --git a/src/elements/emby-itemscontainer/emby-itemscontainer.js b/src/elements/emby-itemscontainer/emby-itemscontainer.js index 1d4a67717a..7d8f941603 100644 --- a/src/elements/emby-itemscontainer/emby-itemscontainer.js +++ b/src/elements/emby-itemscontainer/emby-itemscontainer.js @@ -1,6 +1,5 @@ import itemShortcuts from 'itemShortcuts'; import inputManager from 'inputManager'; -import connectionManager from 'connectionManager'; import playbackManager from 'playbackManager'; import imageLoader from 'imageLoader'; import layoutManager from 'layoutManager'; @@ -18,8 +17,7 @@ import 'webcomponents'; function onClick(e) { const itemsContainer = this; - const target = e.target; - let multiSelect = itemsContainer.multiSelect; + const multiSelect = itemsContainer.multiSelect; if (multiSelect) { if (multiSelect.onContainerClick.call(itemsContainer, e) === false) { @@ -104,7 +102,7 @@ import 'webcomponents'; } const serverId = el.getAttribute('data-serverid'); - const apiClient = connectionManager.getApiClient(serverId); + const apiClient = window.connectionManager.getApiClient(serverId); loading.show(); @@ -148,7 +146,6 @@ import 'webcomponents'; }; function onUserDataChanged(e, apiClient, userData) { - const itemsContainer = this; import('cardBuilder').then(({default: cardBuilder}) => { @@ -166,7 +163,7 @@ import 'webcomponents'; } function getEventsToMonitor(itemsContainer) { - let monitor = itemsContainer.getAttribute('data-monitor'); + const monitor = itemsContainer.getAttribute('data-monitor'); if (monitor) { return monitor.split(','); } @@ -175,7 +172,6 @@ import 'webcomponents'; } function onTimerCreated(e, apiClient, data) { - const itemsContainer = this; if (getEventsToMonitor(itemsContainer).indexOf('timers') !== -1) { @@ -359,9 +355,8 @@ import 'webcomponents'; ItemsContainerPrototype.resume = function (options) { this.paused = false; - let refreshIntervalEndTime = this.refreshIntervalEndTime; + const refreshIntervalEndTime = this.refreshIntervalEndTime; if (refreshIntervalEndTime) { - const remainingMs = refreshIntervalEndTime - new Date().getTime(); if (remainingMs > 0 && !this.needsRefresh) { resetRefreshInterval(this, remainingMs); @@ -399,7 +394,7 @@ import 'webcomponents'; return; } - let timeout = this.refreshTimeout; + const timeout = this.refreshTimeout; if (timeout) { clearTimeout(timeout); } @@ -438,7 +433,7 @@ import 'webcomponents'; function onDataFetched(result) { const items = result.Items || result; - let parentContainer = this.parentContainer; + const parentContainer = this.parentContainer; if (parentContainer) { if (items.length) { parentContainer.classList.remove('hide'); diff --git a/src/elements/emby-playstatebutton/emby-playstatebutton.js b/src/elements/emby-playstatebutton/emby-playstatebutton.js index e1b34318b8..8d17ddf9ff 100644 --- a/src/elements/emby-playstatebutton/emby-playstatebutton.js +++ b/src/elements/emby-playstatebutton/emby-playstatebutton.js @@ -1,4 +1,3 @@ -import connectionManager from 'connectionManager'; import serverNotifications from 'serverNotifications'; import events from 'events'; import globalize from 'globalize'; @@ -21,11 +20,10 @@ import EmbyButtonPrototype from 'emby-button'; } function onClick(e) { - const button = this; const id = button.getAttribute('data-id'); const serverId = button.getAttribute('data-serverid'); - const apiClient = connectionManager.getApiClient(serverId); + const apiClient = window.connectionManager.getApiClient(serverId); if (!button.classList.contains('playstatebutton-played')) { apiClient.markPlayed(apiClient.getCurrentUserId(), id, new Date()); @@ -70,27 +68,24 @@ import EmbyButtonPrototype from 'emby-button'; } function setTitle(button, itemType) { - if (itemType !== 'AudioBook' && itemType !== 'AudioPodcast') { button.title = globalize.translate('Watched'); } else { button.title = globalize.translate('Played'); } - let text = button.querySelector('.button-text'); + const text = button.querySelector('.button-text'); if (text) { text.innerHTML = button.title; } } function clearEvents(button) { - button.removeEventListener('click', onClick); removeNotificationEvent(button, 'UserDataChanged'); } function bindEvents(button) { - clearEvents(button); button.addEventListener('click', onClick); @@ -100,7 +95,6 @@ import EmbyButtonPrototype from 'emby-button'; const EmbyPlaystateButtonPrototype = Object.create(EmbyButtonPrototype); EmbyPlaystateButtonPrototype.createdCallback = function () { - // base method if (EmbyButtonPrototype.createdCallback) { EmbyButtonPrototype.createdCallback.call(this); @@ -108,7 +102,6 @@ import EmbyButtonPrototype from 'emby-button'; }; EmbyPlaystateButtonPrototype.attachedCallback = function () { - // base method if (EmbyButtonPrototype.attachedCallback) { EmbyButtonPrototype.attachedCallback.call(this); @@ -117,7 +110,6 @@ import EmbyButtonPrototype from 'emby-button'; const itemId = this.getAttribute('data-id'); const serverId = this.getAttribute('data-serverid'); if (itemId && serverId) { - setState(this, this.getAttribute('data-played') === 'true', false); bindEvents(this); setTitle(this, this.getAttribute('data-type')); @@ -125,7 +117,6 @@ import EmbyButtonPrototype from 'emby-button'; }; EmbyPlaystateButtonPrototype.detachedCallback = function () { - // base method if (EmbyButtonPrototype.detachedCallback) { EmbyButtonPrototype.detachedCallback.call(this); @@ -136,9 +127,7 @@ import EmbyButtonPrototype from 'emby-button'; }; EmbyPlaystateButtonPrototype.setItem = function (item) { - if (item) { - this.setAttribute('data-id', item.Id); this.setAttribute('data-serverid', item.ServerId); @@ -147,9 +136,7 @@ import EmbyButtonPrototype from 'emby-button'; bindEvents(this); setTitle(this, item.Type); - } else { - this.removeAttribute('data-id'); this.removeAttribute('data-serverid'); this.removeAttribute('data-played'); diff --git a/src/elements/emby-progressbar/emby-progressbar.js b/src/elements/emby-progressbar/emby-progressbar.js index 54fcb1999e..e232bbcde0 100644 --- a/src/elements/emby-progressbar/emby-progressbar.js +++ b/src/elements/emby-progressbar/emby-progressbar.js @@ -1,6 +1,6 @@ /* eslint-disable indent */ - let ProgressBarPrototype = Object.create(HTMLDivElement.prototype); + const ProgressBarPrototype = Object.create(HTMLDivElement.prototype); function onAutoTimeProgress() { const start = parseInt(this.getAttribute('data-starttime')); diff --git a/src/elements/emby-progressring/emby-progressring.js b/src/elements/emby-progressring/emby-progressring.js index 062f64d78b..929b80a573 100644 --- a/src/elements/emby-progressring/emby-progressring.js +++ b/src/elements/emby-progressring/emby-progressring.js @@ -3,40 +3,37 @@ import 'webcomponents'; /* eslint-disable indent */ - let EmbyProgressRing = Object.create(HTMLDivElement.prototype); + const EmbyProgressRing = Object.create(HTMLDivElement.prototype); EmbyProgressRing.createdCallback = function () { - this.classList.add('progressring'); const instance = this; import('text!./emby-progressring.template.html').then(({default: template}) => { instance.innerHTML = template; - //if (window.MutationObserver) { - // // create an observer instance - // var observer = new MutationObserver(function (mutations) { - // mutations.forEach(function (mutation) { + if (window.MutationObserver) { + // create an observer instance + var observer = new MutationObserver(function (mutations) { + mutations.forEach(function (mutation) { + instance.setProgress(parseFloat(instance.getAttribute('data-progress') || '0')); + }); + }); - // instance.setProgress(parseFloat(instance.getAttribute('data-progress') || '0')); - // }); - // }); + // configuration of the observer: + var config = { attributes: true, childList: false, characterData: false }; - // // configuration of the observer: - // var config = { attributes: true, childList: false, characterData: false }; + // pass in the target node, as well as the observer options + observer.observe(instance, config); - // // pass in the target node, as well as the observer options - // observer.observe(instance, config); - - // instance.observer = observer; - //} + instance.observer = observer; + } instance.setProgress(parseFloat(instance.getAttribute('data-progress') || '0')); }); }; EmbyProgressRing.setProgress = function (progress) { - progress = Math.floor(progress); let angle; @@ -50,7 +47,6 @@ import 'webcomponents'; this.querySelector('.animate-50-75-b').style.transform = 'rotate(-90deg)'; this.querySelector('.animate-75-100-b').style.transform = 'rotate(-90deg)'; } else if (progress >= 25 && progress < 50) { - angle = -90 + ((progress - 25) / 100) * 360; this.querySelector('.animate-0-25-b').style.transform = 'none'; @@ -83,8 +79,7 @@ import 'webcomponents'; }; EmbyProgressRing.detachedCallback = function () { - - let observer = this.observer; + const observer = this.observer; if (observer) { // later, you can stop observing diff --git a/src/elements/emby-radio/emby-radio.js b/src/elements/emby-radio/emby-radio.js index c8437d77e3..7c468a84a6 100644 --- a/src/elements/emby-radio/emby-radio.js +++ b/src/elements/emby-radio/emby-radio.js @@ -1,16 +1,16 @@ import layoutManager from 'layoutManager'; import 'css!./emby-radio'; import 'webcomponents'; +import browser from 'browser'; /* eslint-disable indent */ - let EmbyRadioPrototype = Object.create(HTMLInputElement.prototype); + const EmbyRadioPrototype = Object.create(HTMLInputElement.prototype); function onKeyDown(e) { - // Don't submit form on enter // Real (non-emulator) Tizen does nothing on Space - if (e.keyCode === 13 || e.keyCode === 32) { + if (e.keyCode === 13 || (e.keyCode === 32 && browser.tizen)) { e.preventDefault(); if (!this.checked) { @@ -36,8 +36,7 @@ import 'webcomponents'; this.classList.add('mdl-radio__button'); - let labelElement = this.parentNode; - //labelElement.classList.add('"mdl-radio mdl-js-radio mdl-js-ripple-effect'); + const labelElement = this.parentNode; labelElement.classList.add('mdl-radio'); labelElement.classList.add('mdl-js-radio'); labelElement.classList.add('mdl-js-ripple-effect'); @@ -45,7 +44,7 @@ import 'webcomponents'; labelElement.classList.add('show-focus'); } - let labelTextElement = labelElement.querySelector('span'); + const labelTextElement = labelElement.querySelector('span'); labelTextElement.classList.add('radioButtonLabel'); labelTextElement.classList.add('mdl-radio__label'); diff --git a/src/elements/emby-ratingbutton/emby-ratingbutton.js b/src/elements/emby-ratingbutton/emby-ratingbutton.js index b455495a2e..865d918b45 100644 --- a/src/elements/emby-ratingbutton/emby-ratingbutton.js +++ b/src/elements/emby-ratingbutton/emby-ratingbutton.js @@ -1,4 +1,3 @@ -import connectionManager from 'connectionManager'; import serverNotifications from 'serverNotifications'; import events from 'events'; import globalize from 'globalize'; @@ -7,14 +6,12 @@ import EmbyButtonPrototype from 'emby-button'; /* eslint-disable indent */ function addNotificationEvent(instance, name, handler) { - const localHandler = handler.bind(instance); events.on(serverNotifications, name, localHandler); instance[name] = localHandler; } function removeNotificationEvent(instance, name) { - const handler = instance[name]; if (handler) { events.off(serverNotifications, name, handler); @@ -23,16 +20,14 @@ import EmbyButtonPrototype from 'emby-button'; } function showPicker(button, apiClient, itemId, likes, isFavorite) { - return apiClient.updateFavoriteStatus(apiClient.getCurrentUserId(), itemId, !isFavorite); } function onClick(e) { - const button = this; const id = button.getAttribute('data-id'); const serverId = button.getAttribute('data-serverid'); - const apiClient = connectionManager.getApiClient(serverId); + const apiClient = window.connectionManager.getApiClient(serverId); let likes = this.getAttribute('data-likes'); const isFavorite = this.getAttribute('data-isfavorite') === 'true'; @@ -45,58 +40,44 @@ import EmbyButtonPrototype from 'emby-button'; } showPicker(button, apiClient, id, likes, isFavorite).then(function (userData) { - setState(button, userData.Likes, userData.IsFavorite); }); } function onUserDataChanged(e, apiClient, userData) { - const button = this; if (userData.ItemId === button.getAttribute('data-id')) { - setState(button, userData.Likes, userData.IsFavorite); } } function setState(button, likes, isFavorite, updateAttribute) { - const icon = button.querySelector('.material-icons'); if (isFavorite) { - if (icon) { icon.classList.add('favorite'); icon.classList.add('ratingbutton-icon-withrating'); } button.classList.add('ratingbutton-withrating'); - } else if (likes) { - if (icon) { icon.classList.add('favorite'); icon.classList.remove('ratingbutton-icon-withrating'); - //icon.innerHTML = 'thumb_up'; } button.classList.remove('ratingbutton-withrating'); - } else if (likes === false) { - if (icon) { icon.classList.add('favorite'); icon.classList.remove('ratingbutton-icon-withrating'); - //icon.innerHTML = 'thumb_down'; } button.classList.remove('ratingbutton-withrating'); - } else { - if (icon) { icon.classList.add('favorite'); icon.classList.remove('ratingbutton-icon-withrating'); - //icon.innerHTML = 'thumbs_up_down'; } button.classList.remove('ratingbutton-withrating'); } @@ -118,13 +99,11 @@ import EmbyButtonPrototype from 'emby-button'; } function clearEvents(button) { - button.removeEventListener('click', onClick); removeNotificationEvent(button, 'UserDataChanged'); } function bindEvents(button) { - clearEvents(button); button.addEventListener('click', onClick); @@ -134,7 +113,6 @@ import EmbyButtonPrototype from 'emby-button'; const EmbyRatingButtonPrototype = Object.create(EmbyButtonPrototype); EmbyRatingButtonPrototype.createdCallback = function () { - // base method if (EmbyButtonPrototype.createdCallback) { EmbyButtonPrototype.createdCallback.call(this); @@ -142,7 +120,6 @@ import EmbyButtonPrototype from 'emby-button'; }; EmbyRatingButtonPrototype.attachedCallback = function () { - // base method if (EmbyButtonPrototype.attachedCallback) { EmbyButtonPrototype.attachedCallback.call(this); @@ -151,7 +128,6 @@ import EmbyButtonPrototype from 'emby-button'; const itemId = this.getAttribute('data-id'); const serverId = this.getAttribute('data-serverid'); if (itemId && serverId) { - let likes = this.getAttribute('data-likes'); const isFavorite = this.getAttribute('data-isfavorite') === 'true'; if (likes === 'true') { @@ -170,7 +146,6 @@ import EmbyButtonPrototype from 'emby-button'; }; EmbyRatingButtonPrototype.detachedCallback = function () { - // base method if (EmbyButtonPrototype.detachedCallback) { EmbyButtonPrototype.detachedCallback.call(this); @@ -180,18 +155,14 @@ import EmbyButtonPrototype from 'emby-button'; }; EmbyRatingButtonPrototype.setItem = function (item) { - if (item) { - this.setAttribute('data-id', item.Id); this.setAttribute('data-serverid', item.ServerId); const userData = item.UserData || {}; setState(this, userData.Likes, userData.IsFavorite); bindEvents(this); - } else { - this.removeAttribute('data-id'); this.removeAttribute('data-serverid'); this.removeAttribute('data-likes'); diff --git a/src/elements/emby-scrollbuttons/emby-scrollbuttons.js b/src/elements/emby-scrollbuttons/emby-scrollbuttons.js index 239fbcfcda..f7665c0618 100644 --- a/src/elements/emby-scrollbuttons/emby-scrollbuttons.js +++ b/src/elements/emby-scrollbuttons/emby-scrollbuttons.js @@ -36,6 +36,7 @@ const EmbyScrollButtonsPrototype = Object.create(HTMLDivElement.prototype); } function updateScrollButtons(scrollButtons, scrollSize, scrollPos, scrollWidth) { + // TODO: Check if hack is really needed // hack alert add twenty for rounding errors if (scrollWidth <= scrollSize + 20) { scrollButtons.scrollButtonsLeft.classList.add('hide'); @@ -117,12 +118,11 @@ const EmbyScrollButtonsPrototype = Object.create(HTMLDivElement.prototype); } function onScrollButtonClick(e) { - let scroller = this.parentNode.nextSibling; + const scroller = this.parentNode.nextSibling; const direction = this.getAttribute('data-direction'); const scrollSize = getScrollSize(scroller); const scrollPos = getScrollPosition(scroller); - const scrollWidth = getScrollWidth(scroller); let newPos; if (direction === 'left') { @@ -161,7 +161,7 @@ const EmbyScrollButtonsPrototype = Object.create(HTMLDivElement.prototype); const parent = this.scroller; this.scroller = null; - let scrollHandler = this.scrollHandler; + const scrollHandler = this.scrollHandler; if (parent && scrollHandler) { parent.removeScrollEventListener(scrollHandler, { capture: false, diff --git a/src/elements/emby-scroller/emby-scroller.js b/src/elements/emby-scroller/emby-scroller.js index fb903d839c..d7133e317a 100644 --- a/src/elements/emby-scroller/emby-scroller.js +++ b/src/elements/emby-scroller/emby-scroller.js @@ -9,7 +9,7 @@ import 'css!./emby-scroller'; /* eslint-disable indent */ - let ScrollerPrototype = Object.create(HTMLDivElement.prototype); + const ScrollerPrototype = Object.create(HTMLDivElement.prototype); ScrollerPrototype.createdCallback = function () { this.classList.add('emby-scroller'); diff --git a/src/elements/emby-select/emby-select.js b/src/elements/emby-select/emby-select.js index d45e7ef7a7..0629a74e52 100644 --- a/src/elements/emby-select/emby-select.js +++ b/src/elements/emby-select/emby-select.js @@ -9,7 +9,6 @@ import 'webcomponents'; const EmbySelectPrototype = Object.create(HTMLSelectElement.prototype); function enableNativeMenu() { - if (browser.edgeUwp || browser.xboxOne) { return true; } @@ -38,12 +37,10 @@ import 'webcomponents'; } function setValue(select, value) { - select.value = value; } function showActionSheet(select) { - const labelElem = getLabel(select); const title = labelElem ? (labelElem.textContent || labelElem.innerText) : null; @@ -112,7 +109,6 @@ import 'webcomponents'; let inputId = 0; EmbySelectPrototype.createdCallback = function () { - if (!this.id) { this.id = 'embyselect' + inputId; inputId++; @@ -132,7 +128,6 @@ import 'webcomponents'; }; EmbySelectPrototype.attachedCallback = function () { - if (this.classList.contains('emby-select')) { return; } @@ -151,7 +146,6 @@ import 'webcomponents'; }; EmbySelectPrototype.setLabel = function (text) { - const label = this.parentNode.querySelector('label'); label.innerHTML = text; diff --git a/src/elements/emby-slider/emby-slider.css b/src/elements/emby-slider/emby-slider.css index 7661895f15..01221b6cae 100644 --- a/src/elements/emby-slider/emby-slider.css +++ b/src/elements/emby-slider/emby-slider.css @@ -230,3 +230,18 @@ margin: 0; padding: 0.5em 0.75em; } + +/* FIXME: 'sliderContainer' is used to wrap slider's pieces */ +.sliderContainer-settings { + margin-bottom: 1.8em; + position: relative; +} + +.sliderContainer-settings .mdl-slider-container { + height: 2.83em; /* similar to emby-input with its 110% font-size */ +} + +.sliderLabel { + display: block; + margin-bottom: 0.25em; +} diff --git a/src/elements/emby-slider/emby-slider.js b/src/elements/emby-slider/emby-slider.js index e872f6c78b..40d2e69bbb 100644 --- a/src/elements/emby-slider/emby-slider.js +++ b/src/elements/emby-slider/emby-slider.js @@ -8,14 +8,11 @@ import 'emby-input'; /* eslint-disable indent */ - let EmbySliderPrototype = Object.create(HTMLInputElement.prototype); + const EmbySliderPrototype = Object.create(HTMLInputElement.prototype); let supportsValueSetOverride = false; - let enableWidthWithTransform; - if (Object.getOwnPropertyDescriptor && Object.defineProperty) { - const descriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value'); // descriptor returning null in webos if (descriptor && descriptor.configurable) { @@ -85,7 +82,6 @@ import 'emby-input'; * @param {boolean} [isValueSet] update by 'valueset' event or by timer */ function updateValues(isValueSet) { - // Do not update values by 'valueset' in case of soft-implemented dragging if (!!isValueSet && (!!this.keyboardDragging || !!this.touched)) { return; @@ -98,24 +94,18 @@ import 'emby-input'; // Keep only one per slider frame request cancelAnimationFrame(range.updateValuesFrame); range.updateValuesFrame = requestAnimationFrame(function () { - - let backgroundLower = range.backgroundLower; + const backgroundLower = range.backgroundLower; if (backgroundLower) { let fraction = (value - range.min) / (range.max - range.min); - if (enableWidthWithTransform) { - backgroundLower.style.transform = 'scaleX(' + (fraction) + ')'; - } else { - fraction *= 100; - backgroundLower.style.width = fraction + '%'; - } + fraction *= 100; + backgroundLower.style.width = fraction + '%'; } }); } function updateBubble(range, value, bubble, bubbleText) { - requestAnimationFrame(function () { const bubbleTrackRect = range.sliderBubbleTrack.getBoundingClientRect(); const bubbleRect = bubble.getBoundingClientRect(); @@ -141,15 +131,10 @@ import 'emby-input'; } EmbySliderPrototype.attachedCallback = function () { - if (this.getAttribute('data-embyslider') === 'true') { return; } - if (enableWidthWithTransform == null) { - //enableWidthWithTransform = browser.supportsCssAnimation(); - } - this.setAttribute('data-embyslider', 'true'); this.classList.add('mdl-slider'); @@ -165,6 +150,16 @@ import 'emby-input'; this.classList.add('show-focus'); } + const topContainer = dom.parentWithClass(this, 'sliderContainer-settings'); + + if (topContainer && this.getAttribute('label')) { + const label = this.ownerDocument.createElement('label'); + label.innerHTML = this.getAttribute('label'); + label.classList.add('sliderLabel'); + label.htmlFor = this.id; + topContainer.insertBefore(label, topContainer.firstChild); + } + const containerElement = this.parentNode; containerElement.classList.add('mdl-slider-container'); @@ -177,11 +172,7 @@ import 'emby-input'; // the more of these, the more ranges we can display htmlToInsert += '
'; - if (enableWidthWithTransform) { - htmlToInsert += '
'; - } else { - htmlToInsert += '
'; - } + htmlToInsert += '
'; htmlToInsert += '
'; htmlToInsert += '
'; @@ -225,14 +216,12 @@ import 'emby-input'; sliderBubble.classList.add('hide'); hasHideClass = true; - }, { passive: true }); /* eslint-disable-next-line compat/compat */ dom.addEventListener(this, (window.PointerEvent ? 'pointermove' : 'mousemove'), function (e) { - if (!this.dragging) { const bubbleValue = mapClientToFraction(this, e.clientX) * 100; @@ -243,7 +232,6 @@ import 'emby-input'; hasHideClass = false; } } - }, { passive: true }); @@ -422,7 +410,6 @@ import 'emby-input'; }; function setRange(elem, startPercent, endPercent) { - const style = elem.style; style.left = Math.max(startPercent, 0) + '%'; @@ -431,13 +418,11 @@ import 'emby-input'; } function mapRangesFromRuntimeToPercent(ranges, runtime) { - if (!runtime) { return []; } return ranges.map(function (r) { - return { start: (r.start / runtime) * 100, end: (r.end / runtime) * 100 @@ -446,7 +431,6 @@ import 'emby-input'; } EmbySliderPrototype.setBufferedRanges = function (ranges, runtime, position) { - const elem = this.backgroundUpper; if (!elem) { return; @@ -459,7 +443,6 @@ import 'emby-input'; } for (const range in ranges) { - if (position != null) { if (position >= range.end) { continue; @@ -474,7 +457,6 @@ import 'emby-input'; }; EmbySliderPrototype.setIsClear = function (isClear) { - const backgroundLower = this.backgroundLower; if (backgroundLower) { if (isClear) { @@ -494,7 +476,6 @@ import 'emby-input'; } EmbySliderPrototype.detachedCallback = function () { - const interval = this.interval; if (interval) { clearInterval(interval); diff --git a/src/elements/emby-tabs/emby-tabs.js b/src/elements/emby-tabs/emby-tabs.js index f3cd9d38cc..7e16e31dd4 100644 --- a/src/elements/emby-tabs/emby-tabs.js +++ b/src/elements/emby-tabs/emby-tabs.js @@ -8,36 +8,26 @@ import 'scrollStyles'; /* eslint-disable indent */ - let EmbyTabs = Object.create(HTMLDivElement.prototype); + const EmbyTabs = Object.create(HTMLDivElement.prototype); const buttonClass = 'emby-tab-button'; const activeButtonClass = buttonClass + '-active'; - function setActiveTabButton(tabs, newButton, oldButton, animate) { - + function setActiveTabButton(newButton) { newButton.classList.add(activeButtonClass); } function getTabPanel(tabs, index) { - return null; } function removeActivePanelClass(tabs, index) { - let tabPanel = getTabPanel(tabs, index); + const tabPanel = getTabPanel(tabs, index); if (tabPanel) { tabPanel.classList.remove('is-active'); } } - function addActivePanelClass(tabs, index) { - let tabPanel = getTabPanel(tabs, index); - if (tabPanel) { - tabPanel.classList.add('is-active'); - } - } - function fadeInRight(elem) { - const pct = browser.mobile ? '4%' : '0.5%'; const keyframes = [ @@ -52,7 +42,6 @@ import 'scrollStyles'; } function triggerBeforeTabChange(tabs, index, previousIndex) { - tabs.dispatchEvent(new CustomEvent('beforetabchange', { detail: { selectedTabIndex: index, @@ -63,7 +52,7 @@ import 'scrollStyles'; removeActivePanelClass(tabs, previousIndex); } - let newPanel = getTabPanel(tabs, index); + const newPanel = getTabPanel(tabs, index); if (newPanel) { // animate new panel ? @@ -76,21 +65,19 @@ import 'scrollStyles'; } function onClick(e) { - const tabs = this; const current = tabs.querySelector('.' + activeButtonClass); const tabButton = dom.parentWithClass(e.target, buttonClass); if (tabButton && tabButton !== current) { - if (current) { current.classList.remove(activeButtonClass); } const previousIndex = current ? parseInt(current.getAttribute('data-index')) : null; - setActiveTabButton(tabs, tabButton, current, true); + setActiveTabButton(tabButton); const index = parseInt(tabButton.getAttribute('data-index')); @@ -98,7 +85,6 @@ import 'scrollStyles'; // If toCenter is called syncronously within the click event, it sometimes ends up canceling it setTimeout(function () { - tabs.selectedTabIndex = index; tabs.dispatchEvent(new CustomEvent('tabchange', { @@ -112,12 +98,19 @@ import 'scrollStyles'; if (tabs.scroller) { tabs.scroller.toCenter(tabButton, false); } - } } - function initScroller(tabs) { + function onFocusOut(e) { + const parentContainer = e.target.parentNode; + const previousFocus = parentContainer.querySelector('.lastFocused'); + if (previousFocus) { + previousFocus.classList.remove('lastFocused'); + } + e.target.classList.add('lastFocused'); + } + function initScroller(tabs) { if (tabs.scroller) { return; } @@ -153,7 +146,6 @@ import 'scrollStyles'; } EmbyTabs.createdCallback = function () { - if (this.classList.contains('emby-tabs')) { return; } @@ -163,35 +155,36 @@ import 'scrollStyles'; dom.addEventListener(this, 'click', onClick, { passive: true }); + + dom.addEventListener(this, 'focusout', onFocusOut); }; - EmbyTabs.focus = function () { + EmbyTabs.focus = function onFocusIn() { + const selectedTab = this.querySelector('.' + activeButtonClass); + const lastFocused = this.querySelector('.lastFocused'); - const selected = this.querySelector('.' + activeButtonClass); - - if (selected) { - focusManager.focus(selected); + if (lastFocused) { + focusManager.focus(lastFocused); + } else if (selectedTab) { + focusManager.focus(selectedTab); } else { focusManager.autoFocus(this); } }; EmbyTabs.refresh = function () { - if (this.scroller) { this.scroller.reload(); } }; EmbyTabs.attachedCallback = function () { - initScroller(this); const current = this.querySelector('.' + activeButtonClass); const currentIndex = current ? parseInt(current.getAttribute('data-index')) : parseInt(this.getAttribute('data-index') || '0'); if (currentIndex !== -1) { - this.selectedTabIndex = currentIndex; const tabButtons = this.querySelectorAll('.' + buttonClass); @@ -199,7 +192,7 @@ import 'scrollStyles'; const newTabButton = tabButtons[currentIndex]; if (newTabButton) { - setActiveTabButton(this, newTabButton, current, false); + setActiveTabButton(newTabButton); } } @@ -210,7 +203,6 @@ import 'scrollStyles'; }; EmbyTabs.detachedCallback = function () { - if (this.scroller) { this.scroller.destroy(); this.scroller = null; @@ -222,16 +214,13 @@ import 'scrollStyles'; }; function getSelectedTabButton(elem) { - return elem.querySelector('.' + activeButtonClass); } EmbyTabs.selectedIndex = function (selected, triggerEvent) { - const tabs = this; if (selected == null) { - return tabs.selectedTabIndex || 0; } @@ -242,7 +231,6 @@ import 'scrollStyles'; const tabButtons = tabs.querySelectorAll('.' + buttonClass); if (current === selected || triggerEvent === false) { - triggerBeforeTabChange(tabs, selected, current); tabs.dispatchEvent(new CustomEvent('tabchange', { @@ -251,29 +239,24 @@ import 'scrollStyles'; } })); - let currentTabButton = tabButtons[current]; - setActiveTabButton(tabs, tabButtons[selected], currentTabButton, false); + const currentTabButton = tabButtons[current]; + setActiveTabButton(tabButtons[selected]); if (current !== selected && currentTabButton) { currentTabButton.classList.remove(activeButtonClass); } - } else { - onClick.call(tabs, { target: tabButtons[selected] }); - //tabButtons[selected].click(); } }; function getSibling(elem, method) { - let sibling = elem[method]; while (sibling) { if (sibling.classList.contains(buttonClass)) { - if (!sibling.classList.contains('hide')) { return sibling; } @@ -286,7 +269,6 @@ import 'scrollStyles'; } EmbyTabs.selectNext = function () { - const current = getSelectedTabButton(this); const sibling = getSibling(current, 'nextSibling'); @@ -299,7 +281,6 @@ import 'scrollStyles'; }; EmbyTabs.selectPrevious = function () { - const current = getSelectedTabButton(this); const sibling = getSibling(current, 'previousSibling'); @@ -312,14 +293,12 @@ import 'scrollStyles'; }; EmbyTabs.triggerBeforeTabChange = function (selected) { - const tabs = this; triggerBeforeTabChange(tabs, tabs.selectedIndex()); }; EmbyTabs.triggerTabChange = function (selected) { - const tabs = this; tabs.dispatchEvent(new CustomEvent('tabchange', { @@ -330,8 +309,6 @@ import 'scrollStyles'; }; EmbyTabs.setTabEnabled = function (index, enabled) { - - const tabs = this; const btn = this.querySelector('.emby-tab-button[data-index="' + index + '"]'); if (enabled) { diff --git a/src/elements/emby-textarea/emby-textarea.js b/src/elements/emby-textarea/emby-textarea.js index d98cc8c07e..c14724346a 100644 --- a/src/elements/emby-textarea/emby-textarea.js +++ b/src/elements/emby-textarea/emby-textarea.js @@ -47,7 +47,6 @@ import 'emby-input'; return; } let newHeight = 0; - let hasGrown = false; if ((textarea.scrollHeight - offset) > self.maxAllowedHeight) { textarea.style.overflowY = 'scroll'; @@ -56,7 +55,6 @@ import 'emby-input'; textarea.style.overflowY = 'hidden'; textarea.style.height = 'auto'; newHeight = textarea.scrollHeight/* - offset*/; - hasGrown = true; } $('.customCssContainer').css('height', newHeight + 'px'); textarea.style.height = newHeight + 'px'; @@ -75,7 +73,6 @@ import 'emby-input'; let elementId = 0; if (Object.getOwnPropertyDescriptor && Object.defineProperty) { - const descriptor = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value'); // descriptor returning null in webos @@ -95,7 +92,6 @@ import 'emby-input'; } EmbyTextAreaPrototype.createdCallback = function () { - if (!this.id) { this.id = 'embytextarea' + elementId; elementId++; @@ -103,7 +99,6 @@ import 'emby-input'; }; EmbyTextAreaPrototype.attachedCallback = function () { - if (this.classList.contains('emby-textarea')) { return; } diff --git a/src/elements/emby-toggle/emby-toggle.js b/src/elements/emby-toggle/emby-toggle.js index 8e82160806..5e78b38dd3 100644 --- a/src/elements/emby-toggle/emby-toggle.js +++ b/src/elements/emby-toggle/emby-toggle.js @@ -6,7 +6,6 @@ import 'webcomponents'; const EmbyTogglePrototype = Object.create(HTMLInputElement.prototype); function onKeyDown(e) { - // Don't submit form on enter if (e.keyCode === 13) { e.preventDefault(); @@ -22,7 +21,6 @@ import 'webcomponents'; } EmbyTogglePrototype.attachedCallback = function () { - if (this.getAttribute('data-embytoggle') === 'true') { return; } diff --git a/src/index.html b/src/index.html index f8d867cb1a..c689a42f30 100644 --- a/src/index.html +++ b/src/index.html @@ -1,13 +1,11 @@ - - @@ -15,10 +13,9 @@ - - + + - @@ -64,7 +61,6 @@ - Jellyfin @@ -129,7 +125,7 @@ animation: fadein 0.5s; width: 30%; height: 30%; - background-image: url(assets/img/banner-light.png); + background-image: url(assets/img/icon-transparent.png); background-position: center center; background-repeat: no-repeat; background-size: contain; @@ -139,6 +135,13 @@ -webkit-transform: translate(-50%, -50%); transform: translate(-50%, -50%); } + + @media screen + and (min-device-width: 992px) { + .splashLogo { + background-image: url(assets/img/banner-light.png); + } + } diff --git a/src/legacy/vendorStyles.js b/src/legacy/vendorStyles.js new file mode 100644 index 0000000000..74a90ba04d --- /dev/null +++ b/src/legacy/vendorStyles.js @@ -0,0 +1,34 @@ +// Polyfill for vendor prefixed style properties + +(function () { + const vendorProperties = { + 'transform': ['webkitTransform'], + 'transition': ['webkitTransition'] + }; + + const elem = document.createElement('div'); + + function polyfillProperty(name) { + if (!(name in elem.style)) { + (vendorProperties[name] || []).every((vendorName) => { + if (vendorName in elem.style) { + console.debug(`polyfill '${name}' with '${vendorName}'`); + + Object.defineProperty(CSSStyleDeclaration.prototype, name, { + get: function () { return this[vendorName]; }, + set: function (val) { this[vendorName] = val; } + }); + + return false; + } + + return true; + }); + } + } + + if (elem.style instanceof CSSStyleDeclaration) { + polyfillProperty('transform'); + polyfillProperty('transition'); + } +})(); diff --git a/src/libraries/navdrawer/navdrawer.js b/src/libraries/navdrawer/navdrawer.js index d9c246b406..965b68aee4 100644 --- a/src/libraries/navdrawer/navdrawer.js +++ b/src/libraries/navdrawer/navdrawer.js @@ -1,352 +1,357 @@ -define(["browser", "dom", "css!./navdrawer", "scrollStyles"], function (browser, dom) { - "use strict"; +/* Cleaning this file properly is not neecessary, since it's an outdated library + * and will be replaced soon by a Vue component. + */ - return function (options) { - function getTouches(e) { - return e.changedTouches || e.targetTouches || e.touches; +import browser from 'browser'; +import dom from 'dom'; +import 'css!./navdrawer'; +import 'scrollStyles'; + +export default function (options) { + function getTouches(e) { + return e.changedTouches || e.targetTouches || e.touches; + } + + function onMenuTouchStart(e) { + options.target.classList.remove('transition'); + var touches = getTouches(e); + var touch = touches[0] || {}; + menuTouchStartX = touch.clientX; + menuTouchStartY = touch.clientY; + menuTouchStartTime = new Date().getTime(); + } + + function setVelocity(deltaX) { + var time = new Date().getTime() - (menuTouchStartTime || 0); + velocity = Math.abs(deltaX) / time; + } + + function onMenuTouchMove(e) { + var isOpen = self.visible; + var touches = getTouches(e); + var touch = touches[0] || {}; + var endX = touch.clientX || 0; + var endY = touch.clientY || 0; + var deltaX = endX - (menuTouchStartX || 0); + var deltaY = endY - (menuTouchStartY || 0); + setVelocity(deltaX); + + if (isOpen && dragMode !== 1 && deltaX > 0) { + dragMode = 2; } - function onMenuTouchStart(e) { - options.target.classList.remove("transition"); - var touches = getTouches(e); - var touch = touches[0] || {}; - menuTouchStartX = touch.clientX; - menuTouchStartY = touch.clientY; - menuTouchStartTime = new Date().getTime(); + if (dragMode === 0 && (!isOpen || Math.abs(deltaX) >= 10) && Math.abs(deltaY) < 5) { + dragMode = 1; + scrollContainer.addEventListener('scroll', disableEvent); + self.showMask(); + } else if (dragMode === 0 && Math.abs(deltaY) >= 5) { + dragMode = 2; } - function setVelocity(deltaX) { - var time = new Date().getTime() - (menuTouchStartTime || 0); - velocity = Math.abs(deltaX) / time; + if (dragMode === 1) { + newPos = currentPos + deltaX; + self.changeMenuPos(); } + } - function onMenuTouchMove(e) { - var isOpen = self.visible; - var touches = getTouches(e); - var touch = touches[0] || {}; - var endX = touch.clientX || 0; - var endY = touch.clientY || 0; - var deltaX = endX - (menuTouchStartX || 0); - var deltaY = endY - (menuTouchStartY || 0); - setVelocity(deltaX); + function onMenuTouchEnd(e) { + options.target.classList.add('transition'); + scrollContainer.removeEventListener('scroll', disableEvent); + dragMode = 0; + var touches = getTouches(e); + var touch = touches[0] || {}; + var endX = touch.clientX || 0; + var endY = touch.clientY || 0; + var deltaX = endX - (menuTouchStartX || 0); + var deltaY = endY - (menuTouchStartY || 0); + currentPos = deltaX; + self.checkMenuState(deltaX, deltaY); + } - if (isOpen && 1 !== dragMode && deltaX > 0) { - dragMode = 2; - } + function onEdgeTouchStart(e) { + if (isPeeking) { + onMenuTouchMove(e); + } else { + if (((getTouches(e)[0] || {}).clientX || 0) <= options.handleSize) { + isPeeking = true; - if (0 === dragMode && (!isOpen || Math.abs(deltaX) >= 10) && Math.abs(deltaY) < 5) { - dragMode = 1; - scrollContainer.addEventListener("scroll", disableEvent); - self.showMask(); - } else if (0 === dragMode && Math.abs(deltaY) >= 5) { - dragMode = 2; - } - - if (1 === dragMode) { - newPos = currentPos + deltaX; - self.changeMenuPos(); - } - } - - function onMenuTouchEnd(e) { - options.target.classList.add("transition"); - scrollContainer.removeEventListener("scroll", disableEvent); - dragMode = 0; - var touches = getTouches(e); - var touch = touches[0] || {}; - var endX = touch.clientX || 0; - var endY = touch.clientY || 0; - var deltaX = endX - (menuTouchStartX || 0); - var deltaY = endY - (menuTouchStartY || 0); - currentPos = deltaX; - self.checkMenuState(deltaX, deltaY); - } - - function onEdgeTouchStart(e) { - if (isPeeking) { - onMenuTouchMove(e); - } else { - if (((getTouches(e)[0] || {}).clientX || 0) <= options.handleSize) { - isPeeking = true; - - if (e.type === "touchstart") { - dom.removeEventListener(edgeContainer, "touchmove", onEdgeTouchMove, {}); - dom.addEventListener(edgeContainer, "touchmove", onEdgeTouchMove, {}); - } - - onMenuTouchStart(e); + if (e.type === 'touchstart') { + dom.removeEventListener(edgeContainer, 'touchmove', onEdgeTouchMove, {}); + dom.addEventListener(edgeContainer, 'touchmove', onEdgeTouchMove, {}); } + + onMenuTouchStart(e); } } + } - function onEdgeTouchMove(e) { - e.preventDefault(); - e.stopPropagation(); - onEdgeTouchStart(e); + function onEdgeTouchMove(e) { + e.preventDefault(); + e.stopPropagation(); + onEdgeTouchStart(e); + } + + function onEdgeTouchEnd(e) { + if (isPeeking) { + isPeeking = false; + dom.removeEventListener(edgeContainer, 'touchmove', onEdgeTouchMove, {}); + onMenuTouchEnd(e); } + } - function onEdgeTouchEnd(e) { - if (isPeeking) { - isPeeking = false; - dom.removeEventListener(edgeContainer, "touchmove", onEdgeTouchMove, {}); - onMenuTouchEnd(e); - } - } + function disableEvent(e) { + e.preventDefault(); + e.stopPropagation(); + } - function disableEvent(e) { - e.preventDefault(); - e.stopPropagation(); - } + function onBackgroundTouchStart(e) { + var touches = getTouches(e); + var touch = touches[0] || {}; + backgroundTouchStartX = touch.clientX; + backgroundTouchStartTime = new Date().getTime(); + } - function onBackgroundTouchStart(e) { - var touches = getTouches(e); - var touch = touches[0] || {}; - backgroundTouchStartX = touch.clientX; - backgroundTouchStartTime = new Date().getTime(); - } + function onBackgroundTouchMove(e) { + var touches = getTouches(e); + var touch = touches[0] || {}; + var endX = touch.clientX || 0; - function onBackgroundTouchMove(e) { - var touches = getTouches(e); - var touch = touches[0] || {}; - var endX = touch.clientX || 0; - - if (endX <= options.width && self.isVisible) { - countStart++; - var deltaX = endX - (backgroundTouchStartX || 0); - - if (countStart == 1) { - startPoint = deltaX; - } - if (deltaX < 0 && dragMode !== 2) { - dragMode = 1; - newPos = deltaX - startPoint + options.width; - self.changeMenuPos(); - var time = new Date().getTime() - (backgroundTouchStartTime || 0); - velocity = Math.abs(deltaX) / time; - } - } - - e.preventDefault(); - e.stopPropagation(); - } - - function onBackgroundTouchEnd(e) { - var touches = getTouches(e); - var touch = touches[0] || {}; - var endX = touch.clientX || 0; + if (endX <= options.width && self.isVisible) { + countStart++; var deltaX = endX - (backgroundTouchStartX || 0); - self.checkMenuState(deltaX); - countStart = 0; - } - function onMaskTransitionEnd() { - var classList = mask.classList; - - if (!classList.contains("backdrop")) { - classList.add("hide"); + if (countStart == 1) { + startPoint = deltaX; + } + if (deltaX < 0 && dragMode !== 2) { + dragMode = 1; + newPos = deltaX - startPoint + options.width; + self.changeMenuPos(); + var time = new Date().getTime() - (backgroundTouchStartTime || 0); + velocity = Math.abs(deltaX) / time; } } - var self; - var defaults; - var mask; - var newPos = 0; - var currentPos = 0; - var startPoint = 0; - var countStart = 0; - var velocity = 0; - options.target.classList.add("transition"); - var dragMode = 0; - var scrollContainer = options.target.querySelector(".mainDrawer-scrollContainer"); - scrollContainer.classList.add("scrollY"); + e.preventDefault(); + e.stopPropagation(); + } - var TouchMenuLA = function () { - self = this; - defaults = { - width: 260, - handleSize: 10, - disableMask: false, - maxMaskOpacity: 0.5 - }; - this.isVisible = false; - this.initialize(); + function onBackgroundTouchEnd(e) { + var touches = getTouches(e); + var touch = touches[0] || {}; + var endX = touch.clientX || 0; + var deltaX = endX - (backgroundTouchStartX || 0); + self.checkMenuState(deltaX); + countStart = 0; + } + + function onMaskTransitionEnd() { + var classList = mask.classList; + + if (!classList.contains('backdrop')) { + classList.add('hide'); + } + } + + var self; + var defaults; + var mask; + var newPos = 0; + var currentPos = 0; + var startPoint = 0; + var countStart = 0; + var velocity = 0; + options.target.classList.add('transition'); + var dragMode = 0; + var scrollContainer = options.target.querySelector('.mainDrawer-scrollContainer'); + scrollContainer.classList.add('scrollY'); + + var TouchMenuLA = function () { + self = this; + defaults = { + width: 260, + handleSize: 10, + disableMask: false, + maxMaskOpacity: 0.5 }; + this.isVisible = false; + this.initialize(); + }; - TouchMenuLA.prototype.initElements = function () { - options.target.classList.add("touch-menu-la"); - options.target.style.width = options.width + "px"; - options.target.style.left = -options.width + "px"; + TouchMenuLA.prototype.initElements = function () { + options.target.classList.add('touch-menu-la'); + options.target.style.width = options.width + 'px'; + options.target.style.left = -options.width + 'px'; - if (!options.disableMask) { - mask = document.createElement("div"); - mask.className = "tmla-mask hide"; - document.body.appendChild(mask); - dom.addEventListener(mask, dom.whichTransitionEvent(), onMaskTransitionEnd, { - passive: true - }); - } - }; - - var menuTouchStartX; - var menuTouchStartY; - var menuTouchStartTime; - var edgeContainer = document.querySelector(".mainDrawerHandle"); - var isPeeking = false; - - TouchMenuLA.prototype.animateToPosition = function (pos) { - requestAnimationFrame(function () { - options.target.style.transform = pos ? "translateX(" + pos + "px)" : "none"; + if (!options.disableMask) { + mask = document.createElement('div'); + mask.className = 'tmla-mask hide'; + document.body.appendChild(mask); + dom.addEventListener(mask, dom.whichTransitionEvent(), onMaskTransitionEnd, { + passive: true }); - }; + } + }; - TouchMenuLA.prototype.changeMenuPos = function () { - if (newPos <= options.width) { - this.animateToPosition(newPos); - } - }; + var menuTouchStartX; + var menuTouchStartY; + var menuTouchStartTime; + var edgeContainer = document.querySelector('.mainDrawerHandle'); + var isPeeking = false; - TouchMenuLA.prototype.clickMaskClose = function () { - mask.addEventListener("click", function () { + TouchMenuLA.prototype.animateToPosition = function (pos) { + requestAnimationFrame(function () { + options.target.style.transform = pos ? 'translateX(' + pos + 'px)' : 'none'; + }); + }; + + TouchMenuLA.prototype.changeMenuPos = function () { + if (newPos <= options.width) { + this.animateToPosition(newPos); + } + }; + + TouchMenuLA.prototype.clickMaskClose = function () { + mask.addEventListener('click', function () { + self.close(); + }); + }; + + TouchMenuLA.prototype.checkMenuState = function (deltaX, deltaY) { + if (velocity >= 0.4) { + if (deltaX >= 0 || Math.abs(deltaY || 0) >= 70) { + self.open(); + } else { self.close(); - }); - }; - - TouchMenuLA.prototype.checkMenuState = function (deltaX, deltaY) { - if (velocity >= 0.4) { - if (deltaX >= 0 || Math.abs(deltaY || 0) >= 70) { - self.open(); - } else { + } + } else { + if (newPos >= 100) { + self.open(); + } else { + if (newPos) { self.close(); } - } else { - if (newPos >= 100) { - self.open(); - } else { - if (newPos) { - self.close(); - } - } } - }; - - TouchMenuLA.prototype.open = function () { - this.animateToPosition(options.width); - currentPos = options.width; - this.isVisible = true; - options.target.classList.add("drawer-open"); - self.showMask(); - self.invoke(options.onChange); - }; - - TouchMenuLA.prototype.close = function () { - this.animateToPosition(0); - currentPos = 0; - self.isVisible = false; - options.target.classList.remove("drawer-open"); - self.hideMask(); - self.invoke(options.onChange); - }; - - TouchMenuLA.prototype.toggle = function () { - if (self.isVisible) { - self.close(); - } else { - self.open(); - } - }; - - var backgroundTouchStartX; - var backgroundTouchStartTime; - - TouchMenuLA.prototype.showMask = function () { - mask.classList.remove("hide"); - mask.classList.add("backdrop"); - }; - - TouchMenuLA.prototype.hideMask = function () { - mask.classList.add("hide"); - mask.classList.remove("backdrop"); - }; - - TouchMenuLA.prototype.invoke = function (fn) { - if (fn) { - fn.apply(self); - } - }; - - var _edgeSwipeEnabled; - - TouchMenuLA.prototype.setEdgeSwipeEnabled = function (enabled) { - if (!options.disableEdgeSwipe) { - if (browser.touch) { - if (enabled) { - if (!_edgeSwipeEnabled) { - _edgeSwipeEnabled = true; - dom.addEventListener(edgeContainer, "touchstart", onEdgeTouchStart, { - passive: true - }); - dom.addEventListener(edgeContainer, "touchend", onEdgeTouchEnd, { - passive: true - }); - dom.addEventListener(edgeContainer, "touchcancel", onEdgeTouchEnd, { - passive: true - }); - } - } else { - if (_edgeSwipeEnabled) { - _edgeSwipeEnabled = false; - dom.removeEventListener(edgeContainer, "touchstart", onEdgeTouchStart, { - passive: true - }); - dom.removeEventListener(edgeContainer, "touchend", onEdgeTouchEnd, { - passive: true - }); - dom.removeEventListener(edgeContainer, "touchcancel", onEdgeTouchEnd, { - passive: true - }); - } - } - } - } - }; - - TouchMenuLA.prototype.initialize = function () { - options = Object.assign(defaults, options || {}); - - if (browser.edge) { - options.disableEdgeSwipe = true; - } - - self.initElements(); - - if (browser.touch) { - dom.addEventListener(options.target, "touchstart", onMenuTouchStart, { - passive: true - }); - dom.addEventListener(options.target, "touchmove", onMenuTouchMove, { - passive: true - }); - dom.addEventListener(options.target, "touchend", onMenuTouchEnd, { - passive: true - }); - dom.addEventListener(options.target, "touchcancel", onMenuTouchEnd, { - passive: true - }); - dom.addEventListener(mask, "touchstart", onBackgroundTouchStart, { - passive: true - }); - dom.addEventListener(mask, "touchmove", onBackgroundTouchMove, {}); - dom.addEventListener(mask, "touchend", onBackgroundTouchEnd, { - passive: true - }); - dom.addEventListener(mask, "touchcancel", onBackgroundTouchEnd, { - passive: true - }); - } - - self.clickMaskClose(); - }; - - return new TouchMenuLA(); + } }; -}); + + TouchMenuLA.prototype.open = function () { + this.animateToPosition(options.width); + currentPos = options.width; + this.isVisible = true; + options.target.classList.add('drawer-open'); + self.showMask(); + self.invoke(options.onChange); + }; + + TouchMenuLA.prototype.close = function () { + this.animateToPosition(0); + currentPos = 0; + self.isVisible = false; + options.target.classList.remove('drawer-open'); + self.hideMask(); + self.invoke(options.onChange); + }; + + TouchMenuLA.prototype.toggle = function () { + if (self.isVisible) { + self.close(); + } else { + self.open(); + } + }; + + var backgroundTouchStartX; + var backgroundTouchStartTime; + + TouchMenuLA.prototype.showMask = function () { + mask.classList.remove('hide'); + mask.classList.add('backdrop'); + }; + + TouchMenuLA.prototype.hideMask = function () { + mask.classList.add('hide'); + mask.classList.remove('backdrop'); + }; + + TouchMenuLA.prototype.invoke = function (fn) { + if (fn) { + fn.apply(self); + } + }; + + var _edgeSwipeEnabled; + + TouchMenuLA.prototype.setEdgeSwipeEnabled = function (enabled) { + if (!options.disableEdgeSwipe) { + if (browser.touch) { + if (enabled) { + if (!_edgeSwipeEnabled) { + _edgeSwipeEnabled = true; + dom.addEventListener(edgeContainer, 'touchstart', onEdgeTouchStart, { + passive: true + }); + dom.addEventListener(edgeContainer, 'touchend', onEdgeTouchEnd, { + passive: true + }); + dom.addEventListener(edgeContainer, 'touchcancel', onEdgeTouchEnd, { + passive: true + }); + } + } else { + if (_edgeSwipeEnabled) { + _edgeSwipeEnabled = false; + dom.removeEventListener(edgeContainer, 'touchstart', onEdgeTouchStart, { + passive: true + }); + dom.removeEventListener(edgeContainer, 'touchend', onEdgeTouchEnd, { + passive: true + }); + dom.removeEventListener(edgeContainer, 'touchcancel', onEdgeTouchEnd, { + passive: true + }); + } + } + } + } + }; + + TouchMenuLA.prototype.initialize = function () { + options = Object.assign(defaults, options || {}); + + if (browser.edge) { + options.disableEdgeSwipe = true; + } + + self.initElements(); + + if (browser.touch) { + dom.addEventListener(options.target, 'touchstart', onMenuTouchStart, { + passive: true + }); + dom.addEventListener(options.target, 'touchmove', onMenuTouchMove, { + passive: true + }); + dom.addEventListener(options.target, 'touchend', onMenuTouchEnd, { + passive: true + }); + dom.addEventListener(options.target, 'touchcancel', onMenuTouchEnd, { + passive: true + }); + dom.addEventListener(mask, 'touchstart', onBackgroundTouchStart, { + passive: true + }); + dom.addEventListener(mask, 'touchmove', onBackgroundTouchMove, {}); + dom.addEventListener(mask, 'touchend', onBackgroundTouchEnd, { + passive: true + }); + dom.addEventListener(mask, 'touchcancel', onBackgroundTouchEnd, { + passive: true + }); + } + + self.clickMaskClose(); + }; + + return new TouchMenuLA(); +} diff --git a/src/libraries/screensavermanager.js b/src/libraries/screensavermanager.js index b9d7082850..68a7dda73b 100644 --- a/src/libraries/screensavermanager.js +++ b/src/libraries/screensavermanager.js @@ -1,132 +1,127 @@ -define(["events", "playbackManager", "pluginManager", "inputManager", "connectionManager", "userSettings"], function (events, playbackManager, pluginManager, inputManager, connectionManager, userSettings) { - "use strict"; +import events from 'events'; +import playbackManager from 'playbackManager'; +import pluginManager from 'pluginManager'; +import inputManager from 'inputManager'; +import * as userSettings from 'userSettings'; - function getMinIdleTime() { - // Returns the minimum amount of idle time required before the screen saver can be displayed - //time units used Millisecond - return 180000; +function getMinIdleTime() { + // Returns the minimum amount of idle time required before the screen saver can be displayed + //time units used Millisecond + return 180000; +} + +let lastFunctionalEvent = 0; + +function getFunctionalEventIdleTime() { + return new Date().getTime() - lastFunctionalEvent; +} + +events.on(playbackManager, 'playbackstop', function (e, stopInfo) { + const state = stopInfo.state; + if (state.NowPlayingItem && state.NowPlayingItem.MediaType == 'Video') { + lastFunctionalEvent = new Date().getTime(); } - - var lastFunctionalEvent = 0; - - function getFunctionalEventIdleTime() { - return new Date().getTime() - lastFunctionalEvent; - } - - events.on(playbackManager, "playbackstop", function (e, stopInfo) { - var state = stopInfo.state; - if (state.NowPlayingItem && state.NowPlayingItem.MediaType == "Video") { - lastFunctionalEvent = new Date().getTime(); - } - }); - - function getScreensaverPlugin(isLoggedIn) { - - var option; - try { - option = userSettings.get("screensaver", false); - } catch (err) { - option = isLoggedIn ? "backdropscreensaver" : "logoscreensaver"; - } - - var plugins = pluginManager.ofType("screensaver"); - - for (var i = 0, length = plugins.length; i < length; i++) { - var plugin = plugins[i]; - - if (plugin.id === option) { - return plugin; - } - } - - return null; - } - - function ScreenSaverManager() { - - var self = this; - var activeScreenSaver; - - function showScreenSaver(screensaver) { - - if (activeScreenSaver) { - throw new Error("An existing screensaver is already active."); - } - - console.debug("Showing screensaver " + screensaver.name); - - screensaver.show(); - activeScreenSaver = screensaver; - - if (screensaver.hideOnClick !== false) { - window.addEventListener("click", hide, true); - } - if (screensaver.hideOnMouse !== false) { - window.addEventListener("mousemove", hide, true); - } - if (screensaver.hideOnKey !== false) { - window.addEventListener("keydown", hide, true); - } - } - - function hide() { - if (activeScreenSaver) { - console.debug("Hiding screensaver"); - activeScreenSaver.hide(); - activeScreenSaver = null; - } - - window.removeEventListener("click", hide, true); - window.removeEventListener("mousemove", hide, true); - window.removeEventListener("keydown", hide, true); - } - - self.isShowing = function () { - return activeScreenSaver != null; - }; - - self.show = function () { - var isLoggedIn; - var apiClient = connectionManager.currentApiClient(); - - if (apiClient && apiClient.isLoggedIn()) { - isLoggedIn = true; - } - - var screensaver = getScreensaverPlugin(isLoggedIn); - - if (screensaver) { - showScreenSaver(screensaver); - } - }; - - self.hide = function () { - hide(); - }; - - function onInterval() { - - if (self.isShowing()) { - return; - } - - if (inputManager.idleTime() < getMinIdleTime()) { - return; - } - - if (getFunctionalEventIdleTime < getMinIdleTime()) { - return; - } - - if (playbackManager.isPlayingVideo()) { - return; - } - - self.show(); - } - - setInterval(onInterval, 10000); - } - - return new ScreenSaverManager(); }); + +function getScreensaverPlugin(isLoggedIn) { + let option; + try { + option = userSettings.get('screensaver', false); + } catch (err) { + option = isLoggedIn ? 'backdropscreensaver' : 'logoscreensaver'; + } + + const plugins = pluginManager.ofType('screensaver'); + + for (const plugin of plugins) { + if (plugin.id === option) { + return plugin; + } + } + + return null; +} + +function ScreenSaverManager() { + let activeScreenSaver; + + function showScreenSaver(screensaver) { + if (activeScreenSaver) { + throw new Error('An existing screensaver is already active.'); + } + + console.debug('Showing screensaver ' + screensaver.name); + + screensaver.show(); + activeScreenSaver = screensaver; + + if (screensaver.hideOnClick !== false) { + window.addEventListener('click', hide, true); + } + if (screensaver.hideOnMouse !== false) { + window.addEventListener('mousemove', hide, true); + } + if (screensaver.hideOnKey !== false) { + window.addEventListener('keydown', hide, true); + } + } + + function hide() { + if (activeScreenSaver) { + console.debug('Hiding screensaver'); + activeScreenSaver.hide(); + activeScreenSaver = null; + } + + window.removeEventListener('click', hide, true); + window.removeEventListener('mousemove', hide, true); + window.removeEventListener('keydown', hide, true); + } + + this.isShowing = () => { + return activeScreenSaver != null; + }; + + this.show = function () { + let isLoggedIn; + const apiClient = window.connectionManager.currentApiClient(); + + if (apiClient && apiClient.isLoggedIn()) { + isLoggedIn = true; + } + + const screensaver = getScreensaverPlugin(isLoggedIn); + + if (screensaver) { + showScreenSaver(screensaver); + } + }; + + this.hide = function () { + hide(); + }; + + const onInterval = () => { + if (this.isShowing()) { + return; + } + + if (inputManager.idleTime() < getMinIdleTime()) { + return; + } + + if (getFunctionalEventIdleTime < getMinIdleTime()) { + return; + } + + if (playbackManager.isPlayingVideo()) { + return; + } + + this.show(); + }; + + setInterval(onInterval, 10000); +} + +export default new ScreenSaverManager; diff --git a/src/libraries/scroller.js b/src/libraries/scroller.js index 8c67127eb3..dbb3de16e3 100644 --- a/src/libraries/scroller.js +++ b/src/libraries/scroller.js @@ -1,932 +1,890 @@ -define(['browser', 'layoutManager', 'dom', 'focusManager', 'ResizeObserver', 'scrollStyles'], function (browser, layoutManager, dom, focusManager, ResizeObserver) { - 'use strict'; +/* Cleaning this file properly is not neecessary, since it's an outdated library + * and will be replaced soon by a Vue component. + */ - /** +import browser from 'browser'; +import layoutManager from 'layoutManager'; +import dom from 'dom'; +import focusManager from 'focusManager'; +import ResizeObserver from 'ResizeObserver'; +import 'scrollStyles'; + +/** * Return type of the value. * * @param {Mixed} value * * @return {String} */ - function type(value) { - if (value == null) { - return String(value); - } - - if (typeof value === 'object' || typeof value === 'function') { - return Object.prototype.toString.call(value).match(/\s([a-z]+)/i)[1].toLowerCase() || 'object'; - } - - return typeof value; +function type(value) { + if (value == null) { + return String(value); } - /** - * Disables an event it was triggered on and unbinds itself. - * - * @param {Event} event - * - * @return {Void} - */ - function disableOneEvent(event) { - /*jshint validthis:true */ - event.preventDefault(); - event.stopPropagation(); - this.removeEventListener(event.type, disableOneEvent); + if (typeof value === 'object' || typeof value === 'function') { + return Object.prototype.toString.call(value).match(/\s([a-z]+)/i)[1].toLowerCase() || 'object'; } - /** - * Make sure that number is within the limits. - * - * @param {Number} number - * @param {Number} min - * @param {Number} max - * - * @return {Number} - */ - function within(number, min, max) { - return number < min ? min : number > max ? max : number; + return typeof value; +} + +/** + * Disables an event it was triggered on and unbinds itself. + * + * @param {Event} event + * + * @return {Void} + */ +function disableOneEvent(event) { + /*jshint validthis:true */ + event.preventDefault(); + event.stopPropagation(); + this.removeEventListener(event.type, disableOneEvent); +} + +/** + * Make sure that number is within the limits. + * + * @param {Number} number + * @param {Number} min + * @param {Number} max + * + * @return {Number} + */ +function within(number, min, max) { + return number < min ? min : number > max ? max : number; +} + +// Other global values +var dragMouseEvents = ['mousemove', 'mouseup']; +var dragTouchEvents = ['touchmove', 'touchend']; +var wheelEvent = (document.implementation.hasFeature('Event.wheel', '3.0') ? 'wheel' : 'mousewheel'); +var interactiveElements = ['INPUT', 'SELECT', 'TEXTAREA']; + +var scrollerFactory = function (frame, options) { + // Extend options + var o = Object.assign({}, { + slidee: null, // Selector, DOM element, or jQuery object with DOM element representing SLIDEE. + horizontal: false, // Switch to horizontal mode. + + // Scrolling + mouseWheel: true, + scrollBy: 0, // Pixels or items to move per one mouse scroll. 0 to disable scrolling + + // Dragging + dragSource: null, // Selector or DOM element for catching dragging events. Default is FRAME. + mouseDragging: 1, // Enable navigation by dragging the SLIDEE with mouse cursor. + touchDragging: 1, // Enable navigation by dragging the SLIDEE with touch events. + dragThreshold: 3, // Distance in pixels before Sly recognizes dragging. + intervactive: null, // Selector for special interactive elements. + + // Mixed options + speed: 0 // Animations speed in milliseconds. 0 to disable animations. + + }, options); + + var isSmoothScrollSupported = 'scrollBehavior' in document.documentElement.style; + + // native scroll is a must with touch input + // also use native scroll when scrolling vertically in desktop mode - excluding horizontal because the mouse wheel support is choppy at the moment + // in cases with firefox, if the smooth scroll api is supported then use that because their implementation is very good + if (options.allowNativeScroll === false) { + options.enableNativeScroll = false; + } else if (isSmoothScrollSupported && ((browser.firefox && !layoutManager.tv) || options.allowNativeSmoothScroll)) { + // native smooth scroll + options.enableNativeScroll = true; + } else if (options.requireAnimation && (browser.animate || browser.supportsCssAnimation())) { + // transform is the only way to guarantee animation + options.enableNativeScroll = false; + } else if (!layoutManager.tv || !browser.animate) { + options.enableNativeScroll = true; } - // Other global values - var dragMouseEvents = ['mousemove', 'mouseup']; - var dragTouchEvents = ['touchmove', 'touchend']; - var wheelEvent = (document.implementation.hasFeature('Event.wheel', '3.0') ? 'wheel' : 'mousewheel'); - var interactiveElements = ['INPUT', 'SELECT', 'TEXTAREA']; - var tmpArray = []; - var time; - - // Math shorthands - var abs = Math.abs; - var sqrt = Math.sqrt; - var pow = Math.pow; - var round = Math.round; - var max = Math.max; - var min = Math.min; - - var scrollerFactory = function (frame, options) { - - // Extend options - var o = Object.assign({}, { - slidee: null, // Selector, DOM element, or jQuery object with DOM element representing SLIDEE. - horizontal: false, // Switch to horizontal mode. - - // Scrolling - mouseWheel: true, - scrollBy: 0, // Pixels or items to move per one mouse scroll. 0 to disable scrolling - - // Dragging - dragSource: null, // Selector or DOM element for catching dragging events. Default is FRAME. - mouseDragging: 1, // Enable navigation by dragging the SLIDEE with mouse cursor. - touchDragging: 1, // Enable navigation by dragging the SLIDEE with touch events. - dragThreshold: 3, // Distance in pixels before Sly recognizes dragging. - intervactive: null, // Selector for special interactive elements. - - // Mixed options - speed: 0 // Animations speed in milliseconds. 0 to disable animations. - - }, options); - - var isSmoothScrollSupported = 'scrollBehavior' in document.documentElement.style; - - // native scroll is a must with touch input - // also use native scroll when scrolling vertically in desktop mode - excluding horizontal because the mouse wheel support is choppy at the moment - // in cases with firefox, if the smooth scroll api is supported then use that because their implementation is very good - if (options.allowNativeScroll === false) { - options.enableNativeScroll = false; - } else if (isSmoothScrollSupported && ((browser.firefox && !layoutManager.tv) || options.allowNativeSmoothScroll)) { - // native smooth scroll - options.enableNativeScroll = true; - } else if (options.requireAnimation && (browser.animate || browser.supportsCssAnimation())) { - - // transform is the only way to guarantee animation - options.enableNativeScroll = false; - } else if (!layoutManager.tv || !browser.animate) { - - options.enableNativeScroll = true; - } - - // Need this for the magic wheel. With the animated scroll the magic wheel will run off of the screen - if (browser.web0s) { - options.enableNativeScroll = true; - } - - // Private variables - var self = this; - self.options = o; - - // Frame - var slideeElement = o.slidee ? o.slidee : sibling(frame.firstChild)[0]; - self._pos = { - start: 0, - center: 0, - end: 0, - cur: 0, - dest: 0 - }; - - var transform = !options.enableNativeScroll; - - // Miscellaneous - var scrollSource = frame; - var dragSourceElement = o.dragSource ? o.dragSource : frame; - var dragging = { - released: 1 - }; - var scrolling = { - last: 0, - delta: 0, - resetTime: 200 - }; - - // Expose properties - self.initialized = 0; - self.slidee = slideeElement; - self.options = o; - self.dragging = dragging; - - var nativeScrollElement = frame; - - function sibling(n, elem) { - var matched = []; - - for (; n; n = n.nextSibling) { - if (n.nodeType === 1 && n !== elem) { - matched.push(n); - } - } - return matched; - } - - var requiresReflow = true; - - var frameSize = 0; - var slideeSize = 0; - function ensureSizeInfo() { - - if (requiresReflow) { - - requiresReflow = false; - - // Reset global variables - frameSize = o.horizontal ? (frame).offsetWidth : (frame).offsetHeight; - - slideeSize = o.scrollWidth || Math.max(slideeElement[o.horizontal ? 'offsetWidth' : 'offsetHeight'], slideeElement[o.horizontal ? 'scrollWidth' : 'scrollHeight']); - - // Set position limits & relativess - self._pos.end = max(slideeSize - frameSize, 0); - } - } - - /** - * Loading function. - * - * Populate arrays, set sizes, bind events, ... - * - * @param {Boolean} [isInit] Whether load is called from within self.init(). - * @return {Void} - */ - function load(isInit) { - - requiresReflow = true; - - if (!isInit) { - - ensureSizeInfo(); - - // Fix possible overflowing - var pos = self._pos; - self.slideTo(within(pos.dest, pos.start, pos.end)); - } - } - - function initFrameResizeObserver() { - - var observerOptions = {}; - - self.frameResizeObserver = new ResizeObserver(onResize, observerOptions); - - self.frameResizeObserver.observe(frame); - } - - self.reload = function () { - load(); - }; - - self.getScrollEventName = function () { - return transform ? 'scrollanimate' : 'scroll'; - }; - - self.getScrollSlider = function () { - return slideeElement; - }; - - self.getScrollFrame = function () { - return frame; - }; - - function nativeScrollTo(container, pos, immediate) { - - if (container.scroll) { - if (o.horizontal) { - - container.scroll({ - left: pos, - behavior: immediate ? 'instant' : 'smooth' - }); - } else { - - container.scroll({ - top: pos, - behavior: immediate ? 'instant' : 'smooth' - }); - } - } else if (!immediate && container.scrollTo) { - if (o.horizontal) { - container.scrollTo(Math.round(pos), 0); - } else { - container.scrollTo(0, Math.round(pos)); - } - } else { - if (o.horizontal) { - container.scrollLeft = Math.round(pos); - } else { - container.scrollTop = Math.round(pos); - } - } - } - - var lastAnimate; - - /** - * Animate to a position. - * - * @param {Int} newPos New position. - * @param {Bool} immediate Reposition immediately without an animation. - * - * @return {Void} - */ - self.slideTo = function (newPos, immediate, fullItemPos) { - - ensureSizeInfo(); - var pos = self._pos; - - newPos = within(newPos, pos.start, pos.end); - - if (!transform) { - - nativeScrollTo(nativeScrollElement, newPos, immediate); - return; - } - - // Update the animation object - var from = pos.cur; - immediate = immediate || dragging.init || !o.speed; - - var now = new Date().getTime(); - - if (o.autoImmediate) { - if (!immediate && (now - (lastAnimate || 0)) <= 50) { - immediate = true; - } - } - - if (!immediate && o.skipSlideToWhenVisible && fullItemPos && fullItemPos.isVisible) { - - return; - } - - // Start animation rendering - // NOTE the dependency was modified here to fix a scrollbutton issue - pos.dest = newPos; - renderAnimateWithTransform(from, newPos, immediate); - lastAnimate = now; - }; - - function setStyleProperty(elem, name, value, speed, resetTransition) { - - var style = elem.style; - - if (resetTransition || browser.edge) { - style.transition = 'none'; - void elem.offsetWidth; - } - - style.transition = 'transform ' + speed + 'ms ease-out'; - style[name] = value; - } - - function dispatchScrollEventIfNeeded() { - if (o.dispatchScrollEvent) { - frame.dispatchEvent(new CustomEvent(self.getScrollEventName(), { - bubbles: true, - cancelable: false - })); - } - } - - function renderAnimateWithTransform(fromPosition, toPosition, immediate) { - - var speed = o.speed; - - if (immediate) { - speed = o.immediateSpeed || 50; - } - - if (o.horizontal) { - setStyleProperty(slideeElement, 'transform', 'translateX(' + (-round(toPosition)) + 'px)', speed); - } else { - setStyleProperty(slideeElement, 'transform', 'translateY(' + (-round(toPosition)) + 'px)', speed); - } - self._pos.cur = toPosition; - - dispatchScrollEventIfNeeded(); - } - - function getBoundingClientRect(elem) { - - // Support: BlackBerry 5, iOS 3 (original iPhone) - // If we don't have gBCR, just use 0,0 rather than error - if (elem.getBoundingClientRect) { - return elem.getBoundingClientRect(); - } else { - return { top: 0, left: 0 }; - } - } - - /** - * Returns the position object. - * - * @param {Mixed} item - * - * @return {Object} - */ - self.getPos = function (item) { - - var scrollElement = transform ? slideeElement : nativeScrollElement; - var slideeOffset = getBoundingClientRect(scrollElement); - var itemOffset = getBoundingClientRect(item); - - var slideeStartPos = o.horizontal ? slideeOffset.left : slideeOffset.top; - var slideeEndPos = o.horizontal ? slideeOffset.right : slideeOffset.bottom; - - var offset = o.horizontal ? itemOffset.left - slideeOffset.left : itemOffset.top - slideeOffset.top; - - var size = o.horizontal ? itemOffset.width : itemOffset.height; - if (!size && size !== 0) { - size = item[o.horizontal ? 'offsetWidth' : 'offsetHeight']; - } - - var centerOffset = o.centerOffset || 0; - - if (!transform) { - centerOffset = 0; - if (o.horizontal) { - offset += nativeScrollElement.scrollLeft; - } else { - offset += nativeScrollElement.scrollTop; - } - } - - ensureSizeInfo(); - - var currentStart = self._pos.cur; - var currentEnd = currentStart + frameSize; - - console.debug('offset:' + offset + ' currentStart:' + currentStart + ' currentEnd:' + currentEnd); - var isVisible = offset >= currentStart && (offset + size) <= currentEnd; - - return { - start: offset, - center: offset + centerOffset - (frameSize / 2) + (size / 2), - end: offset - frameSize + size, - size: size, - isVisible: isVisible - }; - }; - - self.getCenterPosition = function (item) { - - ensureSizeInfo(); - - var pos = self.getPos(item); - return within(pos.center, pos.start, pos.end); - }; - - function dragInitSlidee(event) { - var isTouch = event.type === 'touchstart'; - - // Ignore when already in progress, or interactive element in non-touch navivagion - if (dragging.init || !isTouch && isInteractive(event.target)) { - return; - } - - // SLIDEE dragging conditions - if (!(isTouch ? o.touchDragging : o.mouseDragging && event.which < 2)) { - return; - } - - if (!isTouch) { - // prevents native image dragging in Firefox - event.preventDefault(); - } - - // Reset dragging object - dragging.released = 0; - - // Properties used in dragHandler - dragging.init = 0; - dragging.source = event.target; - dragging.touch = isTouch; - var pointer = isTouch ? event.touches[0] : event; - dragging.initX = pointer.pageX; - dragging.initY = pointer.pageY; - dragging.initPos = self._pos.cur; - dragging.start = +new Date(); - dragging.time = 0; - dragging.path = 0; - dragging.delta = 0; - dragging.locked = 0; - dragging.pathToLock = isTouch ? 30 : 10; - - // Bind dragging events - if (transform) { - - if (isTouch) { - dragTouchEvents.forEach(function (eventName) { - dom.addEventListener(document, eventName, dragHandler, { - passive: true - }); - }); - } else { - dragMouseEvents.forEach(function (eventName) { - dom.addEventListener(document, eventName, dragHandler, { - passive: true - }); - }); - } - } - } - - /** - * Handler for dragging scrollbar handle or SLIDEE. - * - * @param {Event} event - * - * @return {Void} - */ - function dragHandler(event) { - dragging.released = event.type === 'mouseup' || event.type === 'touchend'; - var pointer = dragging.touch ? event[dragging.released ? 'changedTouches' : 'touches'][0] : event; - dragging.pathX = pointer.pageX - dragging.initX; - dragging.pathY = pointer.pageY - dragging.initY; - dragging.path = sqrt(pow(dragging.pathX, 2) + pow(dragging.pathY, 2)); - dragging.delta = o.horizontal ? dragging.pathX : dragging.pathY; - - if (!dragging.released && dragging.path < 1) { - return; - } - - // We haven't decided whether this is a drag or not... - if (!dragging.init) { - // If the drag path was very short, maybe it's not a drag? - if (dragging.path < o.dragThreshold) { - // If the pointer was released, the path will not become longer and it's - // definitely not a drag. If not released yet, decide on next iteration - return dragging.released ? dragEnd() : undefined; - } else { - // If dragging path is sufficiently long we can confidently start a drag - // if drag is in different direction than scroll, ignore it - if (o.horizontal ? abs(dragging.pathX) > abs(dragging.pathY) : abs(dragging.pathX) < abs(dragging.pathY)) { - dragging.init = 1; - } else { - return dragEnd(); - } - } - } - - //event.preventDefault(); - - // Disable click on a source element, as it is unwelcome when dragging - if (!dragging.locked && dragging.path > dragging.pathToLock) { - dragging.locked = 1; - dragging.source.addEventListener('click', disableOneEvent); - } - - // Cancel dragging on release - if (dragging.released) { - dragEnd(); - } - - self.slideTo(round(dragging.initPos - dragging.delta)); - } - - /** - * Stops dragging and cleans up after it. - * - * @return {Void} - */ - function dragEnd() { - dragging.released = true; - - dragTouchEvents.forEach(function (eventName) { - dom.removeEventListener(document, eventName, dragHandler, { - passive: true - }); - }); - - dragMouseEvents.forEach(function (eventName) { - dom.removeEventListener(document, eventName, dragHandler, { - passive: true - }); - }); - - // Make sure that disableOneEvent is not active in next tick. - setTimeout(function () { - dragging.source.removeEventListener('click', disableOneEvent); - }); - - dragging.init = 0; - } - - /** - * Check whether element is interactive. - * - * @return {Boolean} - */ - function isInteractive(element) { - - while (element) { - - if (interactiveElements.indexOf(element.tagName) !== -1) { - return true; - } - - element = element.parentNode; - } - return false; - } - - /** - * Mouse wheel delta normalization. - * - * @param {Event} event - * - * @return {Int} - */ - function normalizeWheelDelta(event) { - // JELLYFIN MOD: Only use deltaX for horizontal scroll and remove IE8 support - scrolling.curDelta = o.horizontal ? event.deltaX : event.deltaY; - // END JELLYFIN MOD - - if (transform) { - scrolling.curDelta /= event.deltaMode === 1 ? 3 : 100; - } - return scrolling.curDelta; - } - - /** - * Mouse scrolling handler. - * - * @param {Event} event - * - * @return {Void} - */ - function scrollHandler(event) { - - ensureSizeInfo(); - var pos = self._pos; - // Ignore if there is no scrolling to be done - if (!o.scrollBy || pos.start === pos.end) { - return; - } - var delta = normalizeWheelDelta(event); - - if (transform) { - // Trap scrolling only when necessary and/or requested - if (delta > 0 && pos.dest < pos.end || delta < 0 && pos.dest > pos.start) { - //stopDefault(event, 1); - } - - self.slideBy(o.scrollBy * delta); - } else { - - if (isSmoothScrollSupported) { - delta *= 12; - } - - if (o.horizontal) { - nativeScrollElement.scrollLeft += delta; - } else { - nativeScrollElement.scrollTop += delta; - } - } - } - - /** - * Destroys instance and everything it created. - * - * @return {Void} - */ - self.destroy = function () { - - if (self.frameResizeObserver) { - self.frameResizeObserver.disconnect(); - self.frameResizeObserver = null; - } - - // Reset native FRAME element scroll - dom.removeEventListener(frame, 'scroll', resetScroll, { - passive: true - }); - - dom.removeEventListener(scrollSource, wheelEvent, scrollHandler, { - passive: true - }); - - dom.removeEventListener(dragSourceElement, 'touchstart', dragInitSlidee, { - passive: true - }); - - dom.removeEventListener(frame, 'click', onFrameClick, { - passive: true, - capture: true - }); - - dom.removeEventListener(dragSourceElement, 'mousedown', dragInitSlidee, { - //passive: true - }); - - // Reset initialized status and return the instance - self.initialized = 0; - return self; - }; - - var contentRect = {}; - - function onResize(entries) { - - var entry = entries[0]; - - if (entry) { - - var newRect = entry.contentRect; - - // handle element being hidden - if (newRect.width === 0 || newRect.height === 0) { - return; - } - - if (newRect.width !== contentRect.width || newRect.height !== contentRect.height) { - - contentRect = newRect; - - load(false); - } - } - } - - function resetScroll() { - if (o.horizontal) { - this.scrollLeft = 0; - } else { - this.scrollTop = 0; - } - } - - function onFrameClick(e) { - if (e.which === 1) { - var focusableParent = focusManager.focusableParent(e.target); - if (focusableParent && focusableParent !== document.activeElement) { - focusableParent.focus(); - } - } - } - - self.getScrollPosition = function () { - - if (transform) { - return self._pos.cur; - } - - if (o.horizontal) { - return nativeScrollElement.scrollLeft; - } else { - return nativeScrollElement.scrollTop; - } - }; - - self.getScrollSize = function () { - - if (transform) { - return slideeSize; - } - - if (o.horizontal) { - return nativeScrollElement.scrollWidth; - } else { - return nativeScrollElement.scrollHeight; - } - }; - - /** - * Initialize. - * - * @return {Object} - */ - self.init = function () { - if (self.initialized) { - return; - } - - if (!transform) { - if (o.horizontal) { - if (layoutManager.desktop && !o.hideScrollbar) { - nativeScrollElement.classList.add('scrollX'); - } else { - nativeScrollElement.classList.add('scrollX'); - nativeScrollElement.classList.add('hiddenScrollX'); - - if (layoutManager.tv && o.allowNativeSmoothScroll !== false) { - nativeScrollElement.classList.add('smoothScrollX'); - } - } - - if (o.forceHideScrollbars) { - nativeScrollElement.classList.add('hiddenScrollX-forced'); - } - } else { - if (layoutManager.desktop && !o.hideScrollbar) { - nativeScrollElement.classList.add('scrollY'); - } else { - nativeScrollElement.classList.add('scrollY'); - nativeScrollElement.classList.add('hiddenScrollY'); - - if (layoutManager.tv && o.allowNativeSmoothScroll !== false) { - nativeScrollElement.classList.add('smoothScrollY'); - } - } - - if (o.forceHideScrollbars) { - nativeScrollElement.classList.add('hiddenScrollY-forced'); - } - } - } else { - frame.style.overflow = 'hidden'; - slideeElement.style['will-change'] = 'transform'; - slideeElement.style.transition = 'transform ' + o.speed + 'ms ease-out'; - - if (o.horizontal) { - slideeElement.classList.add('animatedScrollX'); - } else { - slideeElement.classList.add('animatedScrollY'); - } - } - - if (transform || layoutManager.tv) { - // This can prevent others from being able to listen to mouse events - dom.addEventListener(dragSourceElement, 'mousedown', dragInitSlidee, { - //passive: true - }); - } - - initFrameResizeObserver(); - - if (transform) { - - dom.addEventListener(dragSourceElement, 'touchstart', dragInitSlidee, { - passive: true - }); - - if (!o.horizontal) { - dom.addEventListener(frame, 'scroll', resetScroll, { - passive: true - }); - } - - if (o.mouseWheel) { - // Scrolling navigation - dom.addEventListener(scrollSource, wheelEvent, scrollHandler, { - passive: true - }); - } - - } else if (o.horizontal) { - - // Don't bind to mouse events with vertical scroll since the mouse wheel can handle this natively - - if (o.mouseWheel) { - // Scrolling navigation - dom.addEventListener(scrollSource, wheelEvent, scrollHandler, { - passive: true - }); - } - } - - dom.addEventListener(frame, 'click', onFrameClick, { - passive: true, - capture: true - }); - - // Mark instance as initialized - self.initialized = 1; - - // Load - load(true); - - // Return instance - return self; - }; + // Need this for the magic wheel. With the animated scroll the magic wheel will run off of the screen + if (browser.web0s) { + options.enableNativeScroll = true; + } + + // Private variables + var self = this; + self.options = o; + + // Frame + var slideeElement = o.slidee ? o.slidee : sibling(frame.firstChild)[0]; + self._pos = { + start: 0, + center: 0, + end: 0, + cur: 0, + dest: 0 }; + var transform = !options.enableNativeScroll; + + // Miscellaneous + var scrollSource = frame; + var dragSourceElement = o.dragSource ? o.dragSource : frame; + var dragging = { + released: 1 + }; + var scrolling = { + last: 0, + delta: 0, + resetTime: 200 + }; + + // Expose properties + self.initialized = 0; + self.slidee = slideeElement; + self.options = o; + self.dragging = dragging; + + var nativeScrollElement = frame; + + function sibling(n, elem) { + var matched = []; + + for (; n; n = n.nextSibling) { + if (n.nodeType === 1 && n !== elem) { + matched.push(n); + } + } + return matched; + } + + var requiresReflow = true; + + var frameSize = 0; + var slideeSize = 0; + function ensureSizeInfo() { + if (requiresReflow) { + requiresReflow = false; + + // Reset global variables + frameSize = o.horizontal ? (frame).offsetWidth : (frame).offsetHeight; + + slideeSize = o.scrollWidth || Math.max(slideeElement[o.horizontal ? 'offsetWidth' : 'offsetHeight'], slideeElement[o.horizontal ? 'scrollWidth' : 'scrollHeight']); + + // Set position limits & relativess + self._pos.end = Math.max(slideeSize - frameSize, 0); + } + } + /** - * Slide SLIDEE by amount of pixels. + * Loading function. * - * @param {Int} delta Pixels/Items. Positive means forward, negative means backward. - * @param {Bool} immediate Reposition immediately without an animation. + * Populate arrays, set sizes, bind events, ... * + * @param {Boolean} [isInit] Whether load is called from within self.init(). * @return {Void} */ - scrollerFactory.prototype.slideBy = function (delta, immediate) { - if (!delta) { + function load(isInit) { + requiresReflow = true; + + if (!isInit) { + ensureSizeInfo(); + + // Fix possible overflowing + var pos = self._pos; + self.slideTo(within(pos.dest, pos.start, pos.end)); + } + } + + function initFrameResizeObserver() { + var observerOptions = {}; + + self.frameResizeObserver = new ResizeObserver(onResize, observerOptions); + + self.frameResizeObserver.observe(frame); + } + + self.reload = function () { + load(); + }; + + self.getScrollEventName = function () { + return transform ? 'scrollanimate' : 'scroll'; + }; + + self.getScrollSlider = function () { + return slideeElement; + }; + + self.getScrollFrame = function () { + return frame; + }; + + function nativeScrollTo(container, pos, immediate) { + if (container.scroll) { + if (o.horizontal) { + container.scroll({ + left: pos, + behavior: immediate ? 'instant' : 'smooth' + }); + } else { + container.scroll({ + top: pos, + behavior: immediate ? 'instant' : 'smooth' + }); + } + } else if (!immediate && container.scrollTo) { + if (o.horizontal) { + container.scrollTo(Math.round(pos), 0); + } else { + container.scrollTo(0, Math.round(pos)); + } + } else { + if (o.horizontal) { + container.scrollLeft = Math.round(pos); + } else { + container.scrollTop = Math.round(pos); + } + } + } + + var lastAnimate; + + /** + * Animate to a position. + * + * @param {Int} newPos New position. + * @param {Bool} immediate Reposition immediately without an animation. + * + * @return {Void} + */ + self.slideTo = function (newPos, immediate, fullItemPos) { + ensureSizeInfo(); + var pos = self._pos; + + if (layoutManager.tv) { + newPos = within(newPos, pos.start); + } else { + newPos = within(newPos, pos.start, pos.end); + } + + if (!transform) { + nativeScrollTo(nativeScrollElement, newPos, immediate); return; } - this.slideTo(this._pos.dest + delta, immediate); - }; - /** - * Core method for handling `toLocation` methods. - * - * @param {String} location - * @param {Mixed} item - * @param {Bool} immediate - * - * @return {Void} - */ - scrollerFactory.prototype.to = function (location, item, immediate) { - // Optional arguments logic - if (type(item) === 'boolean') { - immediate = item; - item = undefined; - } + // Update the animation object + var from = pos.cur; + immediate = immediate || dragging.init || !o.speed; - if (item === undefined) { - this.slideTo(this._pos[location], immediate); - } else { + var now = new Date().getTime(); - //if (!transform) { - - // item.scrollIntoView(); - // return; - //} - - var itemPos = this.getPos(item); - - if (itemPos) { - this.slideTo(itemPos[location], immediate, itemPos); + if (o.autoImmediate) { + if (!immediate && (now - (lastAnimate || 0)) <= 50) { + immediate = true; } } + + if (!immediate && o.skipSlideToWhenVisible && fullItemPos && fullItemPos.isVisible) { + return; + } + + // Start animation rendering + // NOTE the dependency was modified here to fix a scrollbutton issue + pos.dest = newPos; + renderAnimateWithTransform(from, newPos, immediate); + lastAnimate = now; }; + function setStyleProperty(elem, name, value, speed, resetTransition) { + var style = elem.style; + + if (resetTransition || browser.edge) { + style.transition = 'none'; + void elem.offsetWidth; + } + + style.transition = 'transform ' + speed + 'ms ease-out'; + style[name] = value; + } + + function dispatchScrollEventIfNeeded() { + if (o.dispatchScrollEvent) { + frame.dispatchEvent(new CustomEvent(self.getScrollEventName(), { + bubbles: true, + cancelable: false + })); + } + } + + function renderAnimateWithTransform(fromPosition, toPosition, immediate) { + var speed = o.speed; + + if (immediate) { + speed = o.immediateSpeed || 50; + } + + if (o.horizontal) { + setStyleProperty(slideeElement, 'transform', 'translateX(' + (-Math.round(toPosition)) + 'px)', speed); + } else { + setStyleProperty(slideeElement, 'transform', 'translateY(' + (-Math.round(toPosition)) + 'px)', speed); + } + self._pos.cur = toPosition; + + dispatchScrollEventIfNeeded(); + } + + function getBoundingClientRect(elem) { + // Support: BlackBerry 5, iOS 3 (original iPhone) + // If we don't have gBCR, just use 0,0 rather than error + if (elem.getBoundingClientRect) { + return elem.getBoundingClientRect(); + } else { + return { top: 0, left: 0 }; + } + } + /** - * Animate element or the whole SLIDEE to the start of the frame. + * Returns the position object. * - * @param {Mixed} item Item DOM element, or index starting at 0. Omitting will animate SLIDEE. - * @param {Bool} immediate Reposition immediately without an animation. + * @param {Mixed} item + * + * @return {Object} + */ + self.getPos = function (item) { + var scrollElement = transform ? slideeElement : nativeScrollElement; + var slideeOffset = getBoundingClientRect(scrollElement); + var itemOffset = getBoundingClientRect(item); + + var offset = o.horizontal ? itemOffset.left - slideeOffset.left : itemOffset.top - slideeOffset.top; + + var size = o.horizontal ? itemOffset.width : itemOffset.height; + if (!size && size !== 0) { + size = item[o.horizontal ? 'offsetWidth' : 'offsetHeight']; + } + + var centerOffset = o.centerOffset || 0; + + if (!transform) { + centerOffset = 0; + if (o.horizontal) { + offset += nativeScrollElement.scrollLeft; + } else { + offset += nativeScrollElement.scrollTop; + } + } + + ensureSizeInfo(); + + var currentStart = self._pos.cur; + var currentEnd = currentStart + frameSize; + + console.debug('offset:' + offset + ' currentStart:' + currentStart + ' currentEnd:' + currentEnd); + var isVisible = offset >= currentStart && (offset + size) <= currentEnd; + + return { + start: offset, + center: offset + centerOffset - (frameSize / 2) + (size / 2), + end: offset - frameSize + size, + size: size, + isVisible: isVisible + }; + }; + + self.getCenterPosition = function (item) { + ensureSizeInfo(); + + var pos = self.getPos(item); + return within(pos.center, pos.start, pos.end); + }; + + function dragInitSlidee(event) { + var isTouch = event.type === 'touchstart'; + + // Ignore when already in progress, or interactive element in non-touch navivagion + if (dragging.init || !isTouch && isInteractive(event.target)) { + return; + } + + // SLIDEE dragging conditions + if (!(isTouch ? o.touchDragging : o.mouseDragging && event.which < 2)) { + return; + } + + if (!isTouch) { + // prevents native image dragging in Firefox + event.preventDefault(); + } + + // Reset dragging object + dragging.released = 0; + + // Properties used in dragHandler + dragging.init = 0; + dragging.source = event.target; + dragging.touch = isTouch; + var pointer = isTouch ? event.touches[0] : event; + dragging.initX = pointer.pageX; + dragging.initY = pointer.pageY; + dragging.initPos = self._pos.cur; + dragging.start = +new Date(); + dragging.time = 0; + dragging.path = 0; + dragging.delta = 0; + dragging.locked = 0; + dragging.pathToLock = isTouch ? 30 : 10; + + // Bind dragging events + if (transform) { + if (isTouch) { + dragTouchEvents.forEach(function (eventName) { + dom.addEventListener(document, eventName, dragHandler, { + passive: true + }); + }); + } else { + dragMouseEvents.forEach(function (eventName) { + dom.addEventListener(document, eventName, dragHandler, { + passive: true + }); + }); + } + } + } + + /** + * Handler for dragging scrollbar handle or SLIDEE. + * + * @param {Event} event * * @return {Void} */ - scrollerFactory.prototype.toStart = function (item, immediate) { - this.to('start', item, immediate); - }; + function dragHandler(event) { + dragging.released = event.type === 'mouseup' || event.type === 'touchend'; + var pointer = dragging.touch ? event[dragging.released ? 'changedTouches' : 'touches'][0] : event; + dragging.pathX = pointer.pageX - dragging.initX; + dragging.pathY = pointer.pageY - dragging.initY; + dragging.path = Math.sqrt(Math.pow(dragging.pathX, 2) + Math.pow(dragging.pathY, 2)); + dragging.delta = o.horizontal ? dragging.pathX : dragging.pathY; + + if (!dragging.released && dragging.path < 1) { + return; + } + + // We haven't decided whether this is a drag or not... + if (!dragging.init) { + // If the drag path was very short, maybe it's not a drag? + if (dragging.path < o.dragThreshold) { + // If the pointer was released, the path will not become longer and it's + // definitely not a drag. If not released yet, decide on next iteration + return dragging.released ? dragEnd() : undefined; + } else { + // If dragging path is sufficiently long we can confidently start a drag + // if drag is in different direction than scroll, ignore it + if (o.horizontal ? Math.abs(dragging.pathX) > Math.abs(dragging.pathY) : Math.abs(dragging.pathX) < Math.abs(dragging.pathY)) { + dragging.init = 1; + } else { + return dragEnd(); + } + } + } + + //event.preventDefault(); + + // Disable click on a source element, as it is unwelcome when dragging + if (!dragging.locked && dragging.path > dragging.pathToLock) { + dragging.locked = 1; + dragging.source.addEventListener('click', disableOneEvent); + } + + // Cancel dragging on release + if (dragging.released) { + dragEnd(); + } + + self.slideTo(Math.round(dragging.initPos - dragging.delta)); + } /** - * Animate element or the whole SLIDEE to the end of the frame. - * - * @param {Mixed} item Item DOM element, or index starting at 0. Omitting will animate SLIDEE. - * @param {Bool} immediate Reposition immediately without an animation. + * Stops dragging and cleans up after it. * * @return {Void} */ - scrollerFactory.prototype.toEnd = function (item, immediate) { - this.to('end', item, immediate); - }; + function dragEnd() { + dragging.released = true; + + dragTouchEvents.forEach(function (eventName) { + dom.removeEventListener(document, eventName, dragHandler, { + passive: true + }); + }); + + dragMouseEvents.forEach(function (eventName) { + dom.removeEventListener(document, eventName, dragHandler, { + passive: true + }); + }); + + // Make sure that disableOneEvent is not active in next tick. + setTimeout(function () { + dragging.source.removeEventListener('click', disableOneEvent); + }); + + dragging.init = 0; + } /** - * Animate element or the whole SLIDEE to the center of the frame. + * Check whether element is interactive. * - * @param {Mixed} item Item DOM element, or index starting at 0. Omitting will animate SLIDEE. - * @param {Bool} immediate Reposition immediately without an animation. + * @return {Boolean} + */ + function isInteractive(element) { + while (element) { + if (interactiveElements.indexOf(element.tagName) !== -1) { + return true; + } + + element = element.parentNode; + } + return false; + } + + /** + * Mouse wheel delta normalization. + * + * @param {Event} event + * + * @return {Int} + */ + function normalizeWheelDelta(event) { + // JELLYFIN MOD: Only use deltaX for horizontal scroll and remove IE8 support + scrolling.curDelta = o.horizontal ? event.deltaX : event.deltaY; + // END JELLYFIN MOD + + if (transform) { + scrolling.curDelta /= event.deltaMode === 1 ? 3 : 100; + } + return scrolling.curDelta; + } + + /** + * Mouse scrolling handler. + * + * @param {Event} event * * @return {Void} */ - scrollerFactory.prototype.toCenter = function (item, immediate) { - this.to('center', item, immediate); + function scrollHandler(event) { + ensureSizeInfo(); + var pos = self._pos; + // Ignore if there is no scrolling to be done + if (!o.scrollBy || pos.start === pos.end) { + return; + } + var delta = normalizeWheelDelta(event); + + if (transform) { + // Trap scrolling only when necessary and/or requested + if (delta > 0 && pos.dest < pos.end || delta < 0 && pos.dest > pos.start) { + //stopDefault(event, 1); + } + + self.slideBy(o.scrollBy * delta); + } else { + if (isSmoothScrollSupported) { + delta *= 12; + } + + if (o.horizontal) { + nativeScrollElement.scrollLeft += delta; + } else { + nativeScrollElement.scrollTop += delta; + } + } + } + + /** + * Destroys instance and everything it created. + * + * @return {Void} + */ + self.destroy = function () { + if (self.frameResizeObserver) { + self.frameResizeObserver.disconnect(); + self.frameResizeObserver = null; + } + + // Reset native FRAME element scroll + dom.removeEventListener(frame, 'scroll', resetScroll, { + passive: true + }); + + dom.removeEventListener(scrollSource, wheelEvent, scrollHandler, { + passive: true + }); + + dom.removeEventListener(dragSourceElement, 'touchstart', dragInitSlidee, { + passive: true + }); + + dom.removeEventListener(frame, 'click', onFrameClick, { + passive: true, + capture: true + }); + + dom.removeEventListener(dragSourceElement, 'mousedown', dragInitSlidee, { + //passive: true + }); + + // Reset initialized status and return the instance + self.initialized = 0; + return self; }; - scrollerFactory.create = function (frame, options) { - var instance = new scrollerFactory(frame, options); - return Promise.resolve(instance); + var contentRect = {}; + + function onResize(entries) { + var entry = entries[0]; + + if (entry) { + var newRect = entry.contentRect; + + // handle element being hidden + if (newRect.width === 0 || newRect.height === 0) { + return; + } + + if (newRect.width !== contentRect.width || newRect.height !== contentRect.height) { + contentRect = newRect; + + load(false); + } + } + } + + function resetScroll() { + if (o.horizontal) { + this.scrollLeft = 0; + } else { + this.scrollTop = 0; + } + } + + function onFrameClick(e) { + if (e.which === 1) { + var focusableParent = focusManager.focusableParent(e.target); + if (focusableParent && focusableParent !== document.activeElement) { + focusableParent.focus(); + } + } + } + + self.getScrollPosition = function () { + if (transform) { + return self._pos.cur; + } + + if (o.horizontal) { + return nativeScrollElement.scrollLeft; + } else { + return nativeScrollElement.scrollTop; + } }; - return scrollerFactory; -}); + self.getScrollSize = function () { + if (transform) { + return slideeSize; + } + + if (o.horizontal) { + return nativeScrollElement.scrollWidth; + } else { + return nativeScrollElement.scrollHeight; + } + }; + + /** + * Initialize. + * + * @return {Object} + */ + self.init = function () { + if (self.initialized) { + return; + } + + if (!transform) { + if (o.horizontal) { + if (layoutManager.desktop && !o.hideScrollbar) { + nativeScrollElement.classList.add('scrollX'); + } else { + nativeScrollElement.classList.add('scrollX'); + nativeScrollElement.classList.add('hiddenScrollX'); + + if (layoutManager.tv && o.allowNativeSmoothScroll !== false) { + nativeScrollElement.classList.add('smoothScrollX'); + } + } + + if (o.forceHideScrollbars) { + nativeScrollElement.classList.add('hiddenScrollX-forced'); + } + } else { + if (layoutManager.desktop && !o.hideScrollbar) { + nativeScrollElement.classList.add('scrollY'); + } else { + nativeScrollElement.classList.add('scrollY'); + nativeScrollElement.classList.add('hiddenScrollY'); + + if (layoutManager.tv && o.allowNativeSmoothScroll !== false) { + nativeScrollElement.classList.add('smoothScrollY'); + } + } + + if (o.forceHideScrollbars) { + nativeScrollElement.classList.add('hiddenScrollY-forced'); + } + } + } else { + frame.style.overflow = 'hidden'; + slideeElement.style['will-change'] = 'transform'; + slideeElement.style.transition = 'transform ' + o.speed + 'ms ease-out'; + + if (o.horizontal) { + slideeElement.classList.add('animatedScrollX'); + } else { + slideeElement.classList.add('animatedScrollY'); + } + } + + if (transform || layoutManager.tv) { + // This can prevent others from being able to listen to mouse events + dom.addEventListener(dragSourceElement, 'mousedown', dragInitSlidee, { + //passive: true + }); + } + + initFrameResizeObserver(); + + if (transform) { + dom.addEventListener(dragSourceElement, 'touchstart', dragInitSlidee, { + passive: true + }); + + if (!o.horizontal) { + dom.addEventListener(frame, 'scroll', resetScroll, { + passive: true + }); + } + + if (o.mouseWheel) { + // Scrolling navigation + dom.addEventListener(scrollSource, wheelEvent, scrollHandler, { + passive: true + }); + } + } else if (o.horizontal) { + // Don't bind to mouse events with vertical scroll since the mouse wheel can handle this natively + + if (o.mouseWheel) { + // Scrolling navigation + dom.addEventListener(scrollSource, wheelEvent, scrollHandler, { + passive: true + }); + } + } + + dom.addEventListener(frame, 'click', onFrameClick, { + passive: true, + capture: true + }); + + // Mark instance as initialized + self.initialized = 1; + + // Load + load(true); + + // Return instance + return self; + }; +}; + +/** + * Slide SLIDEE by amount of pixels. + * + * @param {Int} delta Pixels/Items. Positive means forward, negative means backward. + * @param {Bool} immediate Reposition immediately without an animation. + * + * @return {Void} + */ +scrollerFactory.prototype.slideBy = function (delta, immediate) { + if (!delta) { + return; + } + this.slideTo(this._pos.dest + delta, immediate); +}; + +/** + * Core method for handling `toLocation` methods. + * + * @param {String} location + * @param {Mixed} item + * @param {Bool} immediate + * + * @return {Void} + */ +scrollerFactory.prototype.to = function (location, item, immediate) { + // Optional arguments logic + if (type(item) === 'boolean') { + immediate = item; + item = undefined; + } + + if (item === undefined) { + this.slideTo(this._pos[location], immediate); + } else { + var itemPos = this.getPos(item); + + if (itemPos) { + this.slideTo(itemPos[location], immediate, itemPos); + } + } +}; + +/** + * Animate element or the whole SLIDEE to the start of the frame. + * + * @param {Mixed} item Item DOM element, or index starting at 0. Omitting will animate SLIDEE. + * @param {Bool} immediate Reposition immediately without an animation. + * + * @return {Void} + */ +scrollerFactory.prototype.toStart = function (item, immediate) { + this.to('start', item, immediate); +}; + +/** + * Animate element or the whole SLIDEE to the end of the frame. + * + * @param {Mixed} item Item DOM element, or index starting at 0. Omitting will animate SLIDEE. + * @param {Bool} immediate Reposition immediately without an animation. + * + * @return {Void} + */ +scrollerFactory.prototype.toEnd = function (item, immediate) { + this.to('end', item, immediate); +}; + +/** + * Animate element or the whole SLIDEE to the center of the frame. + * + * @param {Mixed} item Item DOM element, or index starting at 0. Omitting will animate SLIDEE. + * @param {Bool} immediate Reposition immediately without an animation. + * + * @return {Void} + */ +scrollerFactory.prototype.toCenter = function (item, immediate) { + this.to('center', item, immediate); +}; + +scrollerFactory.create = function (frame, options) { + var instance = new scrollerFactory(frame, options); + return Promise.resolve(instance); +}; + +export default scrollerFactory; diff --git a/src/manifest.json b/src/manifest.json index fed1177e24..5b5ecd5516 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -1,6 +1,6 @@ { "name": "Jellyfin", - "description": "Jellyfin: the Free Software Media System.", + "description": "The Free Software Media System", "lang": "en-US", "short_name": "Jellyfin", "start_url": "index.html#!/home.html", diff --git a/src/plugins/backdropScreensaver/plugin.js b/src/plugins/backdropScreensaver/plugin.js index 88bfa1f4b7..917d8f48a3 100644 --- a/src/plugins/backdropScreensaver/plugin.js +++ b/src/plugins/backdropScreensaver/plugin.js @@ -1,5 +1,4 @@ /* eslint-disable indent */ -import connectionManager from 'connectionManager'; class BackdropScreensaver { constructor() { @@ -21,11 +20,9 @@ class BackdropScreensaver { Limit: 200 }; - const apiClient = connectionManager.currentApiClient(); + const apiClient = window.connectionManager.currentApiClient(); apiClient.getItems(apiClient.getCurrentUserId(), query).then((result) => { - if (result.Items.length) { - import('slideshow').then(({default: Slideshow}) => { const newSlideShow = new Slideshow({ showTitle: true, diff --git a/src/plugins/bookPlayer/plugin.js b/src/plugins/bookPlayer/plugin.js index db6c344dad..0303275be2 100644 --- a/src/plugins/bookPlayer/plugin.js +++ b/src/plugins/bookPlayer/plugin.js @@ -1,7 +1,7 @@ -import connectionManager from 'connectionManager'; import loading from 'loading'; import keyboardnavigation from 'keyboardnavigation'; import dialogHelper from 'dialogHelper'; +import dom from 'dom'; import events from 'events'; import 'css!./style'; import 'material-icons'; @@ -26,16 +26,16 @@ export class BookPlayer { this._loaded = false; loading.show(); - let elem = this.createMediaElement(); + const elem = this.createMediaElement(); return this.setCurrentSrc(elem, options); } stop() { this.unbindEvents(); - let elem = this._mediaElement; - let tocElement = this._tocElement; - let rendition = this._rendition; + const elem = this._mediaElement; + const tocElement = this._tocElement; + const rendition = this._rendition; if (elem) { dialogHelper.close(elem); @@ -92,24 +92,23 @@ export class BookPlayer { } onWindowKeyUp(e) { - let key = keyboardnavigation.getKeyName(e); - let rendition = this._rendition; - let book = rendition.book; + const key = keyboardnavigation.getKeyName(e); + // TODO: depending on the event this can be the document or the rendition itself + const rendition = this._rendition || this; + const book = rendition.book; + + if (this._loaded === false) return; switch (key) { case 'l': case 'ArrowRight': case 'Right': - if (this._loaded) { - book.package.metadata.direction === 'rtl' ? rendition.prev() : rendition.next(); - } + book.package.metadata.direction === 'rtl' ? rendition.prev() : rendition.next(); break; case 'j': case 'ArrowLeft': case 'Left': - if (this._loaded) { - book.package.metadata.direction === 'rtl' ? rendition.next() : rendition.prev(); - } + book.package.metadata.direction === 'rtl' ? rendition.next() : rendition.prev(); break; case 'Escape': if (this._tocElement) { @@ -123,12 +122,32 @@ export class BookPlayer { } } + onTouchStart(e) { + // TODO: depending on the event this can be the document or the rendition itself + const rendition = this._rendition || this; + const book = rendition.book; + + // check that the event is from the book or the document + if (!book || this._loaded === false) return; + + // epubjs stores pages off the screen or something for preloading + // get the modulus of the touch event to account for the increased width + if (!e.touches || e.touches.length === 0) return; + + const touch = e.touches[0].clientX % dom.getWindowSize().innerWidth; + if (touch < dom.getWindowSize().innerWidth / 2) { + book.package.metadata.direction === 'rtl' ? rendition.next() : rendition.prev(); + } else { + book.package.metadata.direction === 'rtl' ? rendition.prev() : rendition.next(); + } + } + onDialogClosed() { this.stop(); } bindMediaElementEvents() { - let elem = this._mediaElement; + const elem = this._mediaElement; elem.addEventListener('close', this.onDialogClosed, {once: true}); elem.querySelector('.btnBookplayerExit').addEventListener('click', this.onDialogClosed, {once: true}); @@ -139,12 +158,15 @@ export class BookPlayer { this.bindMediaElementEvents(); document.addEventListener('keyup', this.onWindowKeyUp); + document.addEventListener('touchstart', this.onTouchStart); + // FIXME: I don't really get why document keyup event is not triggered when epub is in focus this._rendition.on('keyup', this.onWindowKeyUp); + this._rendition.on('touchstart', this.onTouchStart); } unbindMediaElementEvents() { - let elem = this._mediaElement; + const elem = this._mediaElement; elem.removeEventListener('close', this.onDialogClosed); elem.querySelector('.btnBookplayerExit').removeEventListener('click', this.onDialogClosed); @@ -155,9 +177,13 @@ export class BookPlayer { if (this._mediaElement) { this.unbindMediaElementEvents(); } + document.removeEventListener('keyup', this.onWindowKeyUp); + document.removeEventListener('touchstart', this.onTouchStart); + if (this._rendition) { this._rendition.off('keyup', this.onWindowKeyUp); + this._rendition.off('touchstart', this.onTouchStart); } } @@ -169,13 +195,11 @@ export class BookPlayer { createMediaElement() { let elem = this._mediaElement; - if (elem) { return elem; } elem = document.getElementById('bookPlayer'); - if (!elem) { elem = dialogHelper.createDialog({ exitAnimationDuration: 400, @@ -185,6 +209,7 @@ export class BookPlayer { exitAnimation: 'fadeout', removeOnClose: true }); + elem.id = 'bookPlayer'; let html = ''; @@ -206,7 +231,7 @@ export class BookPlayer { } setCurrentSrc(elem, options) { - let item = options.items[0]; + const item = options.items[0]; this._currentItem = item; this.streamInfo = { started: true, @@ -216,24 +241,25 @@ export class BookPlayer { } }; - let serverId = item.ServerId; - let apiClient = connectionManager.getApiClient(serverId); + const serverId = item.ServerId; + const apiClient = window.connectionManager.getApiClient(serverId); return new Promise((resolve, reject) => { import('epubjs').then(({default: epubjs}) => { - let downloadHref = apiClient.getItemDownloadUrl(item.Id); - let book = epubjs.default(downloadHref, {openAs: 'epub'}); - let rendition = book.renderTo(elem, {width: '100%', height: '97%'}); + const downloadHref = apiClient.getItemDownloadUrl(item.Id); + const book = epubjs(downloadHref, {openAs: 'epub'}); + const rendition = book.renderTo(elem, {width: '100%', height: '97%'}); this._currentSrc = downloadHref; this._rendition = rendition; - let cancellationToken = { + const cancellationToken = { shouldCancel: false }; + this._cancellationToken = cancellationToken; return rendition.display().then(() => { - let epubElem = document.querySelector('.epub-container'); + const epubElem = document.querySelector('.epub-container'); epubElem.style.display = 'none'; this.bindEvents(); @@ -253,7 +279,6 @@ export class BookPlayer { epubElem.style.display = 'block'; rendition.on('relocated', (locations) => { this._progress = book.locations.percentageFromCfi(locations.start.cfi); - events.trigger(this, 'timeupdate'); }); @@ -262,7 +287,7 @@ export class BookPlayer { return resolve(); }); }, () => { - console.error('Failed to display epub'); + console.error('failed to display epub'); return reject(); }); }); diff --git a/src/plugins/bookPlayer/tableOfContents.js b/src/plugins/bookPlayer/tableOfContents.js index 23e288aff5..a1c5d8f220 100644 --- a/src/plugins/bookPlayer/tableOfContents.js +++ b/src/plugins/bookPlayer/tableOfContents.js @@ -11,7 +11,7 @@ export default class TableOfContents { } destroy() { - let elem = this._elem; + const elem = this._elem; if (elem) { this.unbindEvents(); dialogHelper.close(elem); @@ -21,14 +21,14 @@ export default class TableOfContents { } bindEvents() { - let elem = this._elem; + const elem = this._elem; elem.addEventListener('close', this.onDialogClosed, {once: true}); elem.querySelector('.btnBookplayerTocClose').addEventListener('click', this.onDialogClosed, {once: true}); } unbindEvents() { - let elem = this._elem; + const elem = this._elem; elem.removeEventListener('close', this.onDialogClosed); elem.querySelector('.btnBookplayerTocClose').removeEventListener('click', this.onDialogClosed); @@ -39,10 +39,10 @@ export default class TableOfContents { } replaceLinks(contents, f) { - let links = contents.querySelectorAll('a[href]'); + const links = contents.querySelectorAll('a[href]'); links.forEach((link) => { - let href = link.getAttribute('href'); + const href = link.getAttribute('href'); link.onclick = () => { f(href); @@ -52,9 +52,9 @@ export default class TableOfContents { } createMediaElement() { - let rendition = this._rendition; + const rendition = this._rendition; - let elem = dialogHelper.createDialog({ + const elem = dialogHelper.createDialog({ size: 'small', autoFocus: false, removeOnClose: true @@ -69,7 +69,7 @@ export default class TableOfContents { rendition.book.navigation.forEach((chapter) => { tocHtml += '
  • '; // Remove '../' from href - let link = chapter.href.startsWith('../') ? chapter.href.substr(3) : chapter.href; + const link = chapter.href.startsWith('../') ? chapter.href.substr(3) : chapter.href; tocHtml += `${chapter.label}`; tocHtml += '
  • '; }); @@ -78,7 +78,7 @@ export default class TableOfContents { elem.innerHTML = tocHtml; this.replaceLinks(elem, (href) => { - let relative = rendition.book.path.relative(href); + const relative = rendition.book.path.relative(href); rendition.display(relative); this.destroy(); }); diff --git a/src/plugins/chromecastPlayer/chromecastHelper.js b/src/plugins/chromecastPlayer/chromecastHelper.js new file mode 100644 index 0000000000..e92fa4471b --- /dev/null +++ b/src/plugins/chromecastPlayer/chromecastHelper.js @@ -0,0 +1,229 @@ +import events from 'events'; + +// LinkParser +// +// https://github.com/ravisorg/LinkParser +// +// Locate and extract almost any URL within a string. Handles protocol-less domains, IPv4 and +// IPv6, unrecognised TLDs, and more. +// +// This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +// http://creativecommons.org/licenses/by-sa/4.0/ +(function () { + // Original URL regex from the Android android.text.util.Linkify function, found here: + // http://stackoverflow.com/a/19696443 + // + // However there were problems with it, most probably related to the fact it was + // written in 2007, and it's been highly modified. + // + // 1) I didn't like the fact that it was tied to specific TLDs, since new ones + // are being added all the time it wouldn't be reasonable to expect developer to + // be continually updating their regular expressions. + // + // 2) It didn't allow unicode characters in the domains which are now allowed in + // many languages, (including some IDN TLDs). Again these are constantly being + // added to and it doesn't seem reasonable to hard-code them. Note this ended up + // not being possible in standard JS due to the way it handles multibyte strings. + // It is possible using XRegExp, however a big performance hit results. Disabled + // for now. + // + // 3) It didn't allow for IPv6 hostnames + // IPv6 regex from http://stackoverflow.com/a/17871737 + // + // 4) It was very poorly commented + // + // 5) It wasn't as smart as it could have been about what should be part of a + // URL and what should be part of human language. + + const protocols = '(?:(?:http|https|rtsp|ftp):\\/\\/)'; + const credentials = "(?:(?:[a-z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-f0-9]{2})){1,64}" // username (1-64 normal or url escaped characters) + + "(?:\\:(?:[a-z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-f0-9]{2})){1,25})?" // followed by optional password (: + 1-25 normal or url escaped characters) + + '\\@)'; + + // IPv6 Regex http://forums.intermapper.com/viewtopic.php?t=452 + // by Dartware, LLC is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License + // http://intermapper.com/ + const ipv6 = '(' + + '(([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))' + + '|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))' + + '|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))' + + '|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))' + + '|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))' + + '|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))' + + '|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))' + + '|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))' + + ')(%.+)?'; + + const ipv4 = '(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.' + + '(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.' + + '(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.' + + '(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[0-9])'; + + // This would have been a lot cleaner if JS RegExp supported conditionals... + const linkRegExpString = + + // begin match for protocol / username / password / host + '(?:' + + // ============================ + // If we have a recognized protocol at the beginning of the URL, we're + // more relaxed about what we accept, because we assume the user wants + // this to be a URL, and we're not accidentally matching human language + + protocols + '?' + + // optional username:password@ + + credentials + '?' + + // IP address (both v4 and v6) + + '(?:' + + // IPv6 + + ipv6 + + // IPv4 + + '|' + ipv4 + + + ')' + + // end match for protocol / username / password / host + + ')' + + // optional port number + + '(?:\\:\\d{1,5})?' + + // plus optional path and query params (no unicode allowed here?) + + '(?:' + + '\\/(?:' + // some characters we'll accept because it's unlikely human language + // would use them after a URL unless they were part of the url + + '(?:[a-z0-9\\/\\@\\&\\#\\~\\*\\_\\-\\+])' + + '|(?:\\%[a-f0-9]{2})' + // some characters are much more likely to be used AFTER a url and + // were not intended to be included in the url itself. Mostly end + // of sentence type things. It's also likely that the URL would + // still work if any of these characters were missing from the end + // because we parsed it incorrectly. For these characters to be accepted + // they must be followed by another character that we're reasonably + // sure is part of the url + + "|(?:[\\;\\?\\:\\.\\!\\'\\(\\)\\,\\=]+(?=(?:[a-z0-9\\/\\@\\&\\#\\~\\*\\_\\-\\+])|(?:\\%[a-f0-9]{2})))" + + ')*' + + '|\\b|\$' + + ')'; + + // regex = XRegExp(regex,'gi'); + const linkRegExp = RegExp(linkRegExpString, 'gi'); + + const protocolRegExp = RegExp('^' + protocols, 'i'); + + // if url doesn't begin with a known protocol, add http by default + function ensureProtocol(url) { + if (!url.match(protocolRegExp)) { + url = 'http://' + url; + } + return url; + } + + // look for links in the text + const LinkParser = { + parse: function (text) { + const links = []; + let match; + + // eslint-disable-next-line no-cond-assign + while (match = linkRegExp.exec(text)) { + console.debug(match); + const txt = match[0]; + const pos = match.index; + const len = txt.length; + const url = ensureProtocol(text); + links.push({ 'pos': pos, 'text': txt, 'len': len, 'url': url }); + } + + return links; + } + + }; + + window.LinkParser = LinkParser; +})(); + +let cache = {}; + +// TODO: Replace with isIP (https://www.npmjs.com/package/is-ip) +function isValidIpAddress(address) { + const links = LinkParser.parse(address); + + return links.length == 1; +} + +// TODO: Add IPv6 support. Potentially replace with isLocalhost (https://www.npmjs.com/package/is-localhost-ip) +function isLocalIpAddress(address) { + address = address.toLowerCase(); + + if (address.includes('127.0.0.1')) { + return true; + } + if (address.includes('localhost')) { + return true; + } + + return false; +} + +export function getServerAddress(apiClient) { + const serverAddress = apiClient.serverAddress(); + + if (isValidIpAddress(serverAddress) && !isLocalIpAddress(serverAddress)) { + return Promise.resolve(serverAddress); + } + + const cachedValue = getCachedValue(serverAddress); + if (cachedValue) { + return Promise.resolve(cachedValue); + } + + return apiClient.getEndpointInfo().then(function (endpoint) { + if (endpoint.IsInNetwork) { + return apiClient.getPublicSystemInfo().then(function (info) { + let localAddress = info.LocalAddress; + if (!localAddress) { + console.debug('No valid local address returned, defaulting to external one'); + localAddress = serverAddress; + } + addToCache(serverAddress, localAddress); + return localAddress; + }); + } else { + addToCache(serverAddress, serverAddress); + return serverAddress; + } + }); +} + +function clearCache() { + cache = {}; +} + +function addToCache(key, value) { + cache[key] = { + value: value, + time: new Date().getTime() + }; +} + +function getCachedValue(key) { + const obj = cache[key]; + + if (obj && (new Date().getTime() - obj.time) < 180000) { + return obj.value; + } + + return null; +} + +events.on(window.connectionManager, 'localusersignedin', clearCache); +events.on(window.connectionManager, 'localusersignedout', clearCache); + +export default { + getServerAddress: getServerAddress +}; diff --git a/src/plugins/chromecastPlayer/chromecastHelpers.js b/src/plugins/chromecastPlayer/chromecastHelpers.js deleted file mode 100644 index ca2d27c977..0000000000 --- a/src/plugins/chromecastPlayer/chromecastHelpers.js +++ /dev/null @@ -1,234 +0,0 @@ -define(['events'], function (events) { - 'use strict'; - - // LinkParser - // - // https://github.com/ravisorg/LinkParser - // - // Locate and extract almost any URL within a string. Handles protocol-less domains, IPv4 and - // IPv6, unrecognised TLDs, and more. - // - // This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. - // http://creativecommons.org/licenses/by-sa/4.0/ - (function () { - - // Original URL regex from the Android android.text.util.Linkify function, found here: - // http://stackoverflow.com/a/19696443 - // - // However there were problems with it, most probably related to the fact it was - // written in 2007, and it's been highly modified. - // - // 1) I didn't like the fact that it was tied to specific TLDs, since new ones - // are being added all the time it wouldn't be reasonable to expect developer to - // be continually updating their regular expressions. - // - // 2) It didn't allow unicode characters in the domains which are now allowed in - // many languages, (including some IDN TLDs). Again these are constantly being - // added to and it doesn't seem reasonable to hard-code them. Note this ended up - // not being possible in standard JS due to the way it handles multibyte strings. - // It is possible using XRegExp, however a big performance hit results. Disabled - // for now. - // - // 3) It didn't allow for IPv6 hostnames - // IPv6 regex from http://stackoverflow.com/a/17871737 - // - // 4) It was very poorly commented - // - // 5) It wasn't as smart as it could have been about what should be part of a - // URL and what should be part of human language. - - var protocols = '(?:(?:http|https|rtsp|ftp):\\/\\/)'; - var credentials = "(?:(?:[a-z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-f0-9]{2})){1,64}" // username (1-64 normal or url escaped characters) - + "(?:\\:(?:[a-z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-f0-9]{2})){1,25})?" // followed by optional password (: + 1-25 normal or url escaped characters) - + '\\@)'; - - // IPv6 Regex http://forums.intermapper.com/viewtopic.php?t=452 - // by Dartware, LLC is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License - // http://intermapper.com/ - var ipv6 = '(' - + '(([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))' - + '|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))' - + '|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))' - + '|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))' - + '|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))' - + '|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))' - + '|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))' - + '|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))' - + ')(%.+)?'; - - var ipv4 = '(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.' - + '(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.' - + '(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.' - + '(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[0-9])'; - - // This would have been a lot cleaner if JS RegExp supported conditionals... - var linkRegExpString = - - // begin match for protocol / username / password / host - '(?:' - - // ============================ - // If we have a recognized protocol at the beginning of the URL, we're - // more relaxed about what we accept, because we assume the user wants - // this to be a URL, and we're not accidentally matching human language - + protocols + '?' - - // optional username:password@ - + credentials + '?' - - // IP address (both v4 and v6) - + '(?:' - - // IPv6 - + ipv6 - - // IPv4 - + '|' + ipv4 - - + ')' - - // end match for protocol / username / password / host - + ')' - - // optional port number - + '(?:\\:\\d{1,5})?' - - // plus optional path and query params (no unicode allowed here?) - + '(?:' - + '\\/(?:' - // some characters we'll accept because it's unlikely human language - // would use them after a URL unless they were part of the url - + '(?:[a-z0-9\\/\\@\\&\\#\\~\\*\\_\\-\\+])' - + '|(?:\\%[a-f0-9]{2})' - // some characters are much more likely to be used AFTER a url and - // were not intended to be included in the url itself. Mostly end - // of sentence type things. It's also likely that the URL would - // still work if any of these characters were missing from the end - // because we parsed it incorrectly. For these characters to be accepted - // they must be followed by another character that we're reasonably - // sure is part of the url - + "|(?:[\\;\\?\\:\\.\\!\\'\\(\\)\\,\\=]+(?=(?:[a-z0-9\\/\\@\\&\\#\\~\\*\\_\\-\\+])|(?:\\%[a-f0-9]{2})))" - + ')*' - + '|\\b|\$' - + ')'; - - // regex = XRegExp(regex,'gi'); - var linkRegExp = RegExp(linkRegExpString, 'gi'); - - var protocolRegExp = RegExp('^' + protocols, 'i'); - - // if url doesn't begin with a known protocol, add http by default - function ensureProtocol(url) { - if (!url.match(protocolRegExp)) { - url = 'http://' + url; - } - return url; - } - - // look for links in the text - var LinkParser = { - parse: function (text) { - var links = []; - var match; - - // eslint-disable-next-line no-cond-assign - while (match = linkRegExp.exec(text)) { - console.debug(match); - var txt = match[0]; - var pos = match.index; - var len = txt.length; - var url = ensureProtocol(text); - links.push({ 'pos': pos, 'text': txt, 'len': len, 'url': url }); - } - - return links; - } - - }; - - window.LinkParser = LinkParser; - })(); - - var cache = {}; - - function isValidIpAddress(address) { - - var links = LinkParser.parse(address); - - return links.length == 1; - } - - function isLocalIpAddress(address) { - - address = address.toLowerCase(); - - if (address.indexOf('127.0.0.1') !== -1) { - return true; - } - if (address.indexOf('localhost') !== -1) { - return true; - } - - return false; - } - - function getServerAddress(apiClient) { - - var serverAddress = apiClient.serverAddress(); - - if (isValidIpAddress(serverAddress) && !isLocalIpAddress(serverAddress)) { - return Promise.resolve(serverAddress); - } - - var cachedValue = getCachedValue(serverAddress); - if (cachedValue) { - return Promise.resolve(cachedValue); - } - - return apiClient.getEndpointInfo().then(function (endpoint) { - if (endpoint.IsInNetwork) { - return apiClient.getPublicSystemInfo().then(function (info) { - var localAddress = info.LocalAddress; - if (!localAddress) { - console.debug('No valid local address returned, defaulting to external one'); - localAddress = serverAddress; - } - addToCache(serverAddress, localAddress); - return localAddress; - }); - } else { - addToCache(serverAddress, serverAddress); - return serverAddress; - } - }); - } - - function clearCache() { - cache = {}; - } - - function addToCache(key, value) { - cache[key] = { - value: value, - time: new Date().getTime() - }; - } - - function getCachedValue(key) { - - var obj = cache[key]; - - if (obj && (new Date().getTime() - obj.time) < 180000) { - return obj.value; - } - - return null; - } - - events.on(ConnectionManager, 'localusersignedin', clearCache); - events.on(ConnectionManager, 'localusersignedout', clearCache); - - return { - getServerAddress: getServerAddress - }; -}); diff --git a/src/plugins/chromecastPlayer/plugin.js b/src/plugins/chromecastPlayer/plugin.js index 3d4f293cc8..f61a0055af 100644 --- a/src/plugins/chromecastPlayer/plugin.js +++ b/src/plugins/chromecastPlayer/plugin.js @@ -1,66 +1,70 @@ -define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', 'globalize', 'events', 'require', 'castSenderApiLoader'], function (appSettings, userSettings, playbackManager, connectionManager, globalize, events, require, castSenderApiLoader) { - 'use strict'; +import appSettings from 'appSettings'; +import * as userSettings from 'userSettings'; +import playbackManager from 'playbackManager'; +import globalize from 'globalize'; +import events from 'events'; +import castSenderApiLoader from 'castSenderApiLoader'; - // Based on https://github.com/googlecast/CastVideos-chrome/blob/master/CastVideos.js - var currentResolve; - var currentReject; +// Based on https://github.com/googlecast/CastVideos-chrome/blob/master/CastVideos.js - var PlayerName = 'Google Cast'; +let currentResolve; +let currentReject; - function sendConnectionResult(isOk) { +const PlayerName = 'Google Cast'; - var resolve = currentResolve; - var reject = currentReject; +function sendConnectionResult(isOk) { + const resolve = currentResolve; + const reject = currentReject; - currentResolve = null; - currentReject = null; + currentResolve = null; + currentReject = null; - if (isOk) { - if (resolve) { - resolve(); - } + if (isOk) { + if (resolve) { + resolve(); + } + } else { + if (reject) { + reject(); } else { - if (reject) { - reject(); - } else { - playbackManager.removeActivePlayer(PlayerName); - } + playbackManager.removeActivePlayer(PlayerName); } } +} - /** - * Constants of states for Chromecast device - **/ - var DEVICE_STATE = { - 'IDLE': 0, - 'ACTIVE': 1, - 'WARNING': 2, - 'ERROR': 3 - }; +/** + * Constants of states for Chromecast device + **/ +const DEVICE_STATE = { + 'IDLE': 0, + 'ACTIVE': 1, + 'WARNING': 2, + 'ERROR': 3 +}; - /** - * Constants of states for CastPlayer - **/ - var PLAYER_STATE = { - 'IDLE': 'IDLE', - 'LOADING': 'LOADING', - 'LOADED': 'LOADED', - 'PLAYING': 'PLAYING', - 'PAUSED': 'PAUSED', - 'STOPPED': 'STOPPED', - 'SEEKING': 'SEEKING', - 'ERROR': 'ERROR' - }; +/** + * Constants of states for CastPlayer + **/ +const PLAYER_STATE = { + 'IDLE': 'IDLE', + 'LOADING': 'LOADING', + 'LOADED': 'LOADED', + 'PLAYING': 'PLAYING', + 'PAUSED': 'PAUSED', + 'STOPPED': 'STOPPED', + 'SEEKING': 'SEEKING', + 'ERROR': 'ERROR' +}; - // production version registered with google - // replace this value if you want to test changes on another instance - var applicationStable = 'F007D354'; - var applicationNightly = '6F511C87'; +// production version registered with google +// replace this value if you want to test changes on another instance +const applicationStable = 'F007D354'; +const applicationUnstable = '6F511C87'; - var messageNamespace = 'urn:x-cast:com.connectsdk'; - - var CastPlayer = function () { +const messageNamespace = 'urn:x-cast:com.connectsdk'; +class CastPlayer { + constructor() { /* device variables */ // @type {DEVICE_STATE} A state for device this.deviceState = DEVICE_STATE.IDLE; @@ -81,7 +85,7 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' this.mediaStatusUpdateHandler = this.onMediaStatusUpdate.bind(this); this.initializeCastPlayer(); - }; + } /** * Initialize Cast media player @@ -89,8 +93,8 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' * invoked once the API has finished initialization. The sessionListener and * receiverListener may be invoked at any time afterwards, and possibly more than once. */ - CastPlayer.prototype.initializeCastPlayer = function () { - var chrome = window.chrome; + initializeCastPlayer() { + const chrome = window.chrome; if (!chrome) { return; } @@ -100,35 +104,35 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' return; } - var applicationID = applicationStable; - if (userSettings.chromecastVersion() === 'nightly') { - applicationID = applicationNightly; + let applicationID = applicationStable; + if (userSettings.chromecastVersion() === 'unstable') { + applicationID = applicationUnstable; } // request session - var sessionRequest = new chrome.cast.SessionRequest(applicationID); - var apiConfig = new chrome.cast.ApiConfig(sessionRequest, + const sessionRequest = new chrome.cast.SessionRequest(applicationID); + const apiConfig = new chrome.cast.ApiConfig(sessionRequest, this.sessionListener.bind(this), this.receiverListener.bind(this)); console.debug('chromecast.initialize'); chrome.cast.initialize(apiConfig, this.onInitSuccess.bind(this), this.errorHandler); - }; + } /** * Callback function for init success */ - CastPlayer.prototype.onInitSuccess = function () { + onInitSuccess() { this.isInitialized = true; console.debug('chromecast init success'); - }; + } /** * Generic error callback function */ - CastPlayer.prototype.onError = function () { + onError() { console.debug('chromecast error'); - }; + } /** * @param {!Object} e A new session @@ -137,7 +141,7 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' * join existing session and occur in Cast mode and media * status gets synced up with current media of the session */ - CastPlayer.prototype.sessionListener = function (e) { + sessionListener(e) { this.session = e; if (this.session) { if (this.session.media[0]) { @@ -146,24 +150,15 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' this.onSessionConnected(e); } - }; - - function alertText(text, title) { - require(['alert'], function (alert) { - alert.default({ - text: text, - title: title - }); - }); } - CastPlayer.prototype.messageListener = function (namespace, message) { + messageListener(namespace, message) { if (typeof (message) === 'string') { message = JSON.parse(message); } if (message.type === 'playbackerror') { - var errorCode = message.data; + const errorCode = message.data; setTimeout(function () { alertText(globalize.translate('MessagePlaybackError' + errorCode), globalize.translate('HeaderPlaybackError')); }, 300); @@ -174,14 +169,14 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' } else if (message.type) { events.trigger(this, message.type, [message.data]); } - }; + } /** * @param {string} e Receiver availability * This indicates availability of receivers but * does not provide a list of device IDs */ - CastPlayer.prototype.receiverListener = function (e) { + receiverListener(e) { if (e === 'available') { console.debug('chromecast receiver found'); this.hasReceivers = true; @@ -189,12 +184,12 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' console.debug('chromecast receiver list empty'); this.hasReceivers = false; } - }; + } /** * session update listener */ - CastPlayer.prototype.sessionUpdateListener = function (isAlive) { + sessionUpdateListener(isAlive) { if (isAlive) { console.debug('sessionUpdateListener: already alive'); } else { @@ -209,28 +204,28 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' sendConnectionResult(false); } - }; + } /** * Requests that a receiver application session be created or joined. By default, the SessionRequest * passed to the API at initialization time is used; this may be overridden by passing a different * session request in opt_sessionRequest. */ - CastPlayer.prototype.launchApp = function () { + launchApp() { console.debug('chromecast launching app...'); chrome.cast.requestSession(this.onRequestSessionSuccess.bind(this), this.onLaunchError.bind(this)); - }; + } /** * Callback function for request session success * @param {Object} e A chrome.cast.Session object */ - CastPlayer.prototype.onRequestSessionSuccess = function (e) { + onRequestSessionSuccess(e) { console.debug('chromecast session success: ' + e.sessionId); this.onSessionConnected(e); - }; + } - CastPlayer.prototype.onSessionConnected = function (session) { + onSessionConnected(session) { this.session = session; this.deviceState = DEVICE_STATE.ACTIVE; @@ -246,46 +241,38 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' options: {}, command: 'Identify' }); - }; - - function onVolumeUpKeyDown() { - playbackManager.volumeUp(); - } - - function onVolumeDownKeyDown() { - playbackManager.volumeDown(); } /** * session update listener */ - CastPlayer.prototype.sessionMediaListener = function (e) { + sessionMediaListener(e) { this.currentMediaSession = e; this.currentMediaSession.addUpdateListener(this.mediaStatusUpdateHandler); - }; + } /** * Callback function for launch error */ - CastPlayer.prototype.onLaunchError = function () { + onLaunchError() { console.debug('chromecast launch error'); this.deviceState = DEVICE_STATE.ERROR; sendConnectionResult(false); - }; + } /** * Stops the running receiver application associated with the session. */ - CastPlayer.prototype.stopApp = function () { + stopApp() { if (this.session) { this.session.stop(this.onStopAppSuccess.bind(this, 'Session stopped'), this.errorHandler); } - }; + } /** * Callback function for stop app success */ - CastPlayer.prototype.onStopAppSuccess = function (message) { + onStopAppSuccess(message) { console.debug(message); this.deviceState = DEVICE_STATE.IDLE; @@ -294,13 +281,13 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' document.removeEventListener('volumedownbutton', onVolumeDownKeyDown, false); this.currentMediaSession = null; - }; + } /** * Loads media into a running receiver application * @param {Number} mediaIndex An index number to indicate current media content */ - CastPlayer.prototype.loadMedia = function (options, command) { + loadMedia(options, command) { if (!this.session) { console.debug('no session'); return Promise.reject(); @@ -322,27 +309,26 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' options: options, command: command }); - }; + } - CastPlayer.prototype.sendMessage = function (message) { + sendMessage(message) { + const player = this; - var player = this; + let receiverName = null; - var receiverName = null; - - var session = player.session; + const session = player.session; if (session && session.receiver && session.receiver.friendlyName) { receiverName = session.receiver.friendlyName; } - var apiClient; + let apiClient; if (message.options && message.options.ServerId) { - apiClient = connectionManager.getApiClient(message.options.ServerId); + apiClient = window.connectionManager.getApiClient(message.options.ServerId); } else if (message.options && message.options.items && message.options.items.length) { - apiClient = connectionManager.getApiClient(message.options.items[0].ServerId); + apiClient = window.connectionManager.getApiClient(message.options.items[0].ServerId); } else { - apiClient = connectionManager.currentApiClient(); + apiClient = window.connectionManager.currentApiClient(); } message = Object.assign(message, { @@ -355,7 +341,7 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' receiverName: receiverName }); - var bitrateSetting = appSettings.maxChromecastBitrate(); + const bitrateSetting = appSettings.maxChromecastBitrate(); if (bitrateSetting) { message.maxBitrate = bitrateSetting; } @@ -366,32 +352,31 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' } return new Promise(function (resolve, reject) { - require(['chromecastHelper'], function (chromecastHelper) { + import('./chromecastHelper').then(({ default: chromecastHelper }) => { chromecastHelper.getServerAddress(apiClient).then(function (serverAddress) { message.serverAddress = serverAddress; player.sendMessageInternal(message).then(resolve, reject); }, reject); }); }); - }; + } - CastPlayer.prototype.sendMessageInternal = function (message) { + sendMessageInternal(message) { message = JSON.stringify(message); this.session.sendMessage(messageNamespace, message, this.onPlayCommandSuccess.bind(this), this.errorHandler); return Promise.resolve(); - }; + } - CastPlayer.prototype.onPlayCommandSuccess = function () { + onPlayCommandSuccess() { console.debug('Message was sent to receiver ok.'); - }; + } /** * Callback function for loadMedia success * @param {Object} mediaSession A new media object. */ - CastPlayer.prototype.onMediaDiscovered = function (how, mediaSession) { - + onMediaDiscovered(how, mediaSession) { console.debug('chromecast new media session ID:' + mediaSession.mediaSessionId + ' (' + how + ')'); this.currentMediaSession = mediaSession; @@ -404,24 +389,24 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' } this.currentMediaSession.addUpdateListener(this.mediaStatusUpdateHandler); - }; + } /** * Callback function for media status update from receiver * @param {!Boolean} e true/false */ - CastPlayer.prototype.onMediaStatusUpdate = function (e) { + onMediaStatusUpdate(e) { console.debug('chromecast updating media: ' + e); if (e === false) { this.castPlayerState = PLAYER_STATE.IDLE; } - }; + } /** * Set media volume in Cast mode * @param {Boolean} mute A boolean */ - CastPlayer.prototype.setReceiverVolume = function (mute, vol) { + setReceiverVolume(mute, vol) { if (!this.currentMediaSession) { console.debug('this.currentMediaSession is null'); return; @@ -436,155 +421,161 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' this.mediaCommandSuccessCallback.bind(this), this.errorHandler); } - }; + } /** * Mute CC */ - CastPlayer.prototype.mute = function () { + mute() { this.setReceiverVolume(true); - }; + } /** * Callback function for media command success */ - CastPlayer.prototype.mediaCommandSuccessCallback = function (info, e) { + mediaCommandSuccessCallback(info, e) { console.debug(info); - }; + } +} - function normalizeImages(state) { +function alertText(text, title) { + import('alert').then(({default: alert}) => { + alert({ + text: text, + title: title + }); + }); +} - if (state && state.NowPlayingItem) { +function onVolumeUpKeyDown() { + playbackManager.volumeUp(); +} - var item = state.NowPlayingItem; +function onVolumeDownKeyDown() { + playbackManager.volumeDown(); +} - if (!item.ImageTags || !item.ImageTags.Primary) { - if (item.PrimaryImageTag) { - item.ImageTags = item.ImageTags || {}; - item.ImageTags.Primary = item.PrimaryImageTag; - } - } - if (item.BackdropImageTag && item.BackdropItemId === item.Id) { - item.BackdropImageTags = [item.BackdropImageTag]; - } - if (item.BackdropImageTag && item.BackdropItemId !== item.Id) { - item.ParentBackdropImageTags = [item.BackdropImageTag]; - item.ParentBackdropItemId = item.BackdropItemId; +function normalizeImages(state) { + if (state && state.NowPlayingItem) { + const item = state.NowPlayingItem; + + if (!item.ImageTags || !item.ImageTags.Primary) { + if (item.PrimaryImageTag) { + item.ImageTags = item.ImageTags || {}; + item.ImageTags.Primary = item.PrimaryImageTag; } } + if (item.BackdropImageTag && item.BackdropItemId === item.Id) { + item.BackdropImageTags = [item.BackdropImageTag]; + } + if (item.BackdropImageTag && item.BackdropItemId !== item.Id) { + item.ParentBackdropImageTags = [item.BackdropImageTag]; + item.ParentBackdropItemId = item.BackdropItemId; + } } +} - function getItemsForPlayback(apiClient, query) { +function getItemsForPlayback(apiClient, query) { + const userId = apiClient.getCurrentUserId(); - var userId = apiClient.getCurrentUserId(); + if (query.Ids && query.Ids.split(',').length === 1) { + return apiClient.getItem(userId, query.Ids.split(',')).then(function (item) { + return { + Items: [item], + TotalRecordCount: 1 + }; + }); + } else { + query.Limit = query.Limit || 100; + query.ExcludeLocationTypes = 'Virtual'; + query.EnableTotalRecordCount = false; - if (query.Ids && query.Ids.split(',').length === 1) { - return apiClient.getItem(userId, query.Ids.split(',')).then(function (item) { - return { - Items: [item], - TotalRecordCount: 1 - }; - }); + return apiClient.getItems(userId, query); + } +} + +function bindEventForRelay(instance, eventName) { + events.on(instance._castPlayer, eventName, function (e, data) { + console.debug('cc: ' + eventName); + const state = instance.getPlayerStateInternal(data); + + events.trigger(instance, eventName, [state]); + }); +} + +function initializeChromecast() { + const instance = this; + instance._castPlayer = new CastPlayer(); + + // To allow the native android app to override + document.dispatchEvent(new CustomEvent('chromecastloaded', { + detail: { + player: instance + } + })); + + events.on(instance._castPlayer, 'connect', function (e) { + if (currentResolve) { + sendConnectionResult(true); } else { - - query.Limit = query.Limit || 100; - query.ExcludeLocationTypes = 'Virtual'; - query.EnableTotalRecordCount = false; - - return apiClient.getItems(userId, query); + playbackManager.setActivePlayer(PlayerName, instance.getCurrentTargetInfo()); } - } - function bindEventForRelay(instance, eventName) { + console.debug('cc: connect'); + // Reset this so that statechange will fire + instance.lastPlayerData = null; + }); - events.on(instance._castPlayer, eventName, function (e, data) { + events.on(instance._castPlayer, 'playbackstart', function (e, data) { + console.debug('cc: playbackstart'); - console.debug('cc: ' + eventName); - var state = instance.getPlayerStateInternal(data); + instance._castPlayer.initializeCastPlayer(); - events.trigger(instance, eventName, [state]); - }); - } + const state = instance.getPlayerStateInternal(data); + events.trigger(instance, 'playbackstart', [state]); + }); - function initializeChromecast() { + events.on(instance._castPlayer, 'playbackstop', function (e, data) { + console.debug('cc: playbackstop'); + let state = instance.getPlayerStateInternal(data); - var instance = this; - instance._castPlayer = new CastPlayer(); + events.trigger(instance, 'playbackstop', [state]); - // To allow the native android app to override - document.dispatchEvent(new CustomEvent('chromecastloaded', { - detail: { - player: instance - } - })); + state = instance.lastPlayerData.PlayState || {}; + const volume = state.VolumeLevel || 0.5; + const mute = state.IsMuted || false; - events.on(instance._castPlayer, 'connect', function (e) { + // Reset this so the next query doesn't make it appear like content is playing. + instance.lastPlayerData = {}; + instance.lastPlayerData.PlayState = {}; + instance.lastPlayerData.PlayState.VolumeLevel = volume; + instance.lastPlayerData.PlayState.IsMuted = mute; + }); - if (currentResolve) { - sendConnectionResult(true); - } else { - playbackManager.setActivePlayer(PlayerName, instance.getCurrentTargetInfo()); - } + events.on(instance._castPlayer, 'playbackprogress', function (e, data) { + console.debug('cc: positionchange'); + const state = instance.getPlayerStateInternal(data); - console.debug('cc: connect'); - // Reset this so that statechange will fire - instance.lastPlayerData = null; - }); + events.trigger(instance, 'timeupdate', [state]); + }); - events.on(instance._castPlayer, 'playbackstart', function (e, data) { + bindEventForRelay(instance, 'timeupdate'); + bindEventForRelay(instance, 'pause'); + bindEventForRelay(instance, 'unpause'); + bindEventForRelay(instance, 'volumechange'); + bindEventForRelay(instance, 'repeatmodechange'); + bindEventForRelay(instance, 'shufflequeuemodechange'); - console.debug('cc: playbackstart'); + events.on(instance._castPlayer, 'playstatechange', function (e, data) { + console.debug('cc: playstatechange'); + const state = instance.getPlayerStateInternal(data); - instance._castPlayer.initializeCastPlayer(); - - var state = instance.getPlayerStateInternal(data); - events.trigger(instance, 'playbackstart', [state]); - }); - - events.on(instance._castPlayer, 'playbackstop', function (e, data) { - - console.debug('cc: playbackstop'); - var state = instance.getPlayerStateInternal(data); - - events.trigger(instance, 'playbackstop', [state]); - - state = instance.lastPlayerData.PlayState || {}; - var volume = state.VolumeLevel || 0.5; - var mute = state.IsMuted || false; - - // Reset this so the next query doesn't make it appear like content is playing. - instance.lastPlayerData = {}; - instance.lastPlayerData.PlayState = {}; - instance.lastPlayerData.PlayState.VolumeLevel = volume; - instance.lastPlayerData.PlayState.IsMuted = mute; - }); - - events.on(instance._castPlayer, 'playbackprogress', function (e, data) { - - console.debug('cc: positionchange'); - var state = instance.getPlayerStateInternal(data); - - events.trigger(instance, 'timeupdate', [state]); - }); - - bindEventForRelay(instance, 'timeupdate'); - bindEventForRelay(instance, 'pause'); - bindEventForRelay(instance, 'unpause'); - bindEventForRelay(instance, 'volumechange'); - bindEventForRelay(instance, 'repeatmodechange'); - bindEventForRelay(instance, 'shufflequeuemodechange'); - - events.on(instance._castPlayer, 'playstatechange', function (e, data) { - - console.debug('cc: playstatechange'); - var state = instance.getPlayerStateInternal(data); - - events.trigger(instance, 'pause', [state]); - }); - } - - function ChromecastPlayer() { + events.trigger(instance, 'pause', [state]); + }); +} +class ChromecastPlayer { + constructor() { // playbackManager needs this this.name = PlayerName; this.type = 'mediaplayer'; @@ -592,11 +583,11 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' this.isLocalPlayer = false; this.lastPlayerData = {}; - castSenderApiLoader.load().then(initializeChromecast.bind(this)); + new castSenderApiLoader().load().then(initializeChromecast.bind(this)); } - ChromecastPlayer.prototype.tryPair = function (target) { - var castPlayer = this._castPlayer; + tryPair(target) { + const castPlayer = this._castPlayer; if (castPlayer.deviceState !== DEVICE_STATE.ACTIVE && castPlayer.isInitialized) { return new Promise(function (resolve, reject) { @@ -610,24 +601,23 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' return Promise.reject(); } - }; + } - ChromecastPlayer.prototype.getTargets = function () { - var targets = []; + getTargets() { + const targets = []; if (this._castPlayer && this._castPlayer.hasReceivers) { targets.push(this.getCurrentTargetInfo()); } return Promise.resolve(targets); - }; + } // This is a privately used method - ChromecastPlayer.prototype.getCurrentTargetInfo = function () { + getCurrentTargetInfo() { + let appName = null; - var appName = null; - - var castPlayer = this._castPlayer; + const castPlayer = this._castPlayer; if (castPlayer.session && castPlayer.session.receiver && castPlayer.session.receiver.friendlyName) { appName = castPlayer.session.receiver.friendlyName; @@ -658,11 +648,10 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' 'PlayTrailers' ] }; - }; + } - ChromecastPlayer.prototype.getPlayerStateInternal = function (data) { - - var triggerStateChange = false; + getPlayerStateInternal(data) { + let triggerStateChange = false; if (data && !this.lastPlayerData) { triggerStateChange = true; } @@ -679,16 +668,14 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' } return data; - }; - - ChromecastPlayer.prototype.playWithCommand = function (options, command) { + } + playWithCommand(options, command) { if (!options.items) { - var apiClient = connectionManager.getApiClient(options.serverId); - var instance = this; + const apiClient = window.connectionManager.getApiClient(options.serverId); + const instance = this; return apiClient.getItem(apiClient.getCurrentUserId(), options.ids[0]).then(function (item) { - options.items = [item]; return instance.playWithCommand(options, command); }); @@ -702,10 +689,9 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' } return this._castPlayer.loadMedia(options, command); - }; - - ChromecastPlayer.prototype.seek = function (position) { + } + seek(position) { position = parseInt(position); position = position / 10000000; @@ -716,56 +702,55 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' }, command: 'Seek' }); - }; + } - ChromecastPlayer.prototype.setAudioStreamIndex = function (index) { + setAudioStreamIndex(index) { this._castPlayer.sendMessage({ options: { index: index }, command: 'SetAudioStreamIndex' }); - }; + } - ChromecastPlayer.prototype.setSubtitleStreamIndex = function (index) { + setSubtitleStreamIndex(index) { this._castPlayer.sendMessage({ options: { index: index }, command: 'SetSubtitleStreamIndex' }); - }; - - ChromecastPlayer.prototype.setMaxStreamingBitrate = function (options) { + } + setMaxStreamingBitrate(options) { this._castPlayer.sendMessage({ options: options, command: 'SetMaxStreamingBitrate' }); - }; + } - ChromecastPlayer.prototype.isFullscreen = function () { - var state = this.lastPlayerData || {}; + isFullscreen() { + let state = this.lastPlayerData || {}; state = state.PlayState || {}; return state.IsFullscreen; - }; + } - ChromecastPlayer.prototype.nextTrack = function () { + nextTrack() { this._castPlayer.sendMessage({ options: {}, command: 'NextTrack' }); - }; + } - ChromecastPlayer.prototype.previousTrack = function () { + previousTrack() { this._castPlayer.sendMessage({ options: {}, command: 'PreviousTrack' }); - }; + } - ChromecastPlayer.prototype.volumeDown = function () { - var vol = this._castPlayer.session.receiver.volume.level; + volumeDown() { + let vol = this._castPlayer.session.receiver.volume.level; if (vol == null) { vol = 0.5; } @@ -773,22 +758,20 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' vol = Math.max(vol, 0); this._castPlayer.session.setReceiverVolumeLevel(vol); + } - }; - - ChromecastPlayer.prototype.endSession = function () { - - var instance = this; + endSession() { + const instance = this; this.stop().then(function () { setTimeout(function () { instance._castPlayer.stopApp(); }, 1000); }); - }; + } - ChromecastPlayer.prototype.volumeUp = function () { - var vol = this._castPlayer.session.receiver.volume.level; + volumeUp() { + let vol = this._castPlayer.session.receiver.volume.level; if (vol == null) { vol = 0.5; } @@ -796,56 +779,53 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' vol = Math.min(vol, 1); this._castPlayer.session.setReceiverVolumeLevel(vol); - }; - - ChromecastPlayer.prototype.setVolume = function (vol) { + } + setVolume(vol) { vol = Math.min(vol, 100); vol = Math.max(vol, 0); vol = vol / 100; this._castPlayer.session.setReceiverVolumeLevel(vol); - }; + } - ChromecastPlayer.prototype.unpause = function () { + unpause() { this._castPlayer.sendMessage({ options: {}, command: 'Unpause' }); - }; + } - ChromecastPlayer.prototype.playPause = function () { + playPause() { this._castPlayer.sendMessage({ options: {}, command: 'PlayPause' }); - }; + } - ChromecastPlayer.prototype.pause = function () { + pause() { this._castPlayer.sendMessage({ options: {}, command: 'Pause' }); - }; + } - ChromecastPlayer.prototype.stop = function () { + stop() { return this._castPlayer.sendMessage({ options: {}, command: 'Stop' }); - }; - - ChromecastPlayer.prototype.displayContent = function (options) { + } + displayContent(options) { this._castPlayer.sendMessage({ options: options, command: 'DisplayContent' }); - }; + } - ChromecastPlayer.prototype.setMute = function (isMuted) { - - var castPlayer = this._castPlayer; + setMute(isMuted) { + const castPlayer = this._castPlayer; if (isMuted) { castPlayer.sendMessage({ @@ -858,22 +838,21 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' command: 'Unmute' }); } - }; + } - ChromecastPlayer.prototype.getRepeatMode = function () { - var state = this.lastPlayerData || {}; + getRepeatMode() { + let state = this.lastPlayerData || {}; state = state.PlayState || {}; return state.RepeatMode; - }; + } - ChromecastPlayer.prototype.getQueueShuffleMode = function () { - var state = this.lastPlayerData || {}; + getQueueShuffleMode() { + let state = this.lastPlayerData || {}; state = state.PlayState || {}; return state.ShuffleMode; - }; - - ChromecastPlayer.prototype.playTrailers = function (item) { + } + playTrailers(item) { this._castPlayer.sendMessage({ options: { ItemId: item.Id, @@ -881,247 +860,222 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' }, command: 'PlayTrailers' }); - }; + } - ChromecastPlayer.prototype.setRepeatMode = function (mode) { + setRepeatMode(mode) { this._castPlayer.sendMessage({ options: { RepeatMode: mode }, command: 'SetRepeatMode' }); - }; + } - ChromecastPlayer.prototype.setQueueShuffleMode = function (value) { + setQueueShuffleMode(value) { this._castPlayer.sendMessage({ options: { ShuffleMode: value }, command: 'SetShuffleQueue' }); - }; - - ChromecastPlayer.prototype.toggleMute = function () { + } + toggleMute() { this._castPlayer.sendMessage({ options: {}, command: 'ToggleMute' }); - }; + } - ChromecastPlayer.prototype.audioTracks = function () { - var state = this.lastPlayerData || {}; + audioTracks() { + let state = this.lastPlayerData || {}; state = state.NowPlayingItem || {}; - var streams = state.MediaStreams || []; + const streams = state.MediaStreams || []; return streams.filter(function (s) { return s.Type === 'Audio'; }); - }; + } - ChromecastPlayer.prototype.getAudioStreamIndex = function () { - var state = this.lastPlayerData || {}; + getAudioStreamIndex() { + let state = this.lastPlayerData || {}; state = state.PlayState || {}; return state.AudioStreamIndex; - }; + } - ChromecastPlayer.prototype.subtitleTracks = function () { - var state = this.lastPlayerData || {}; + subtitleTracks() { + let state = this.lastPlayerData || {}; state = state.NowPlayingItem || {}; - var streams = state.MediaStreams || []; + const streams = state.MediaStreams || []; return streams.filter(function (s) { return s.Type === 'Subtitle'; }); - }; + } - ChromecastPlayer.prototype.getSubtitleStreamIndex = function () { - var state = this.lastPlayerData || {}; + getSubtitleStreamIndex() { + let state = this.lastPlayerData || {}; state = state.PlayState || {}; return state.SubtitleStreamIndex; - }; + } - ChromecastPlayer.prototype.getMaxStreamingBitrate = function () { - var state = this.lastPlayerData || {}; + getMaxStreamingBitrate() { + let state = this.lastPlayerData || {}; state = state.PlayState || {}; return state.MaxStreamingBitrate; - }; + } - ChromecastPlayer.prototype.getVolume = function () { - - var state = this.lastPlayerData || {}; + getVolume() { + let state = this.lastPlayerData || {}; state = state.PlayState || {}; return state.VolumeLevel == null ? 100 : state.VolumeLevel; - }; + } - ChromecastPlayer.prototype.isPlaying = function () { - var state = this.lastPlayerData || {}; - return state.NowPlayingItem != null; - }; + isPlaying(mediaType) { + const state = this.lastPlayerData || {}; + return state.NowPlayingItem != null && (state.NowPlayingItem.MediaType === mediaType || !mediaType); + } - ChromecastPlayer.prototype.isPlayingVideo = function () { - var state = this.lastPlayerData || {}; + isPlayingVideo() { + let state = this.lastPlayerData || {}; state = state.NowPlayingItem || {}; return state.MediaType === 'Video'; - }; + } - ChromecastPlayer.prototype.isPlayingAudio = function () { - var state = this.lastPlayerData || {}; + isPlayingAudio() { + let state = this.lastPlayerData || {}; state = state.NowPlayingItem || {}; return state.MediaType === 'Audio'; - }; - - ChromecastPlayer.prototype.currentTime = function (val) { + } + currentTime(val) { if (val != null) { - return this.seek(val); + return this.seek(val * 10000); } - var state = this.lastPlayerData || {}; + let state = this.lastPlayerData || {}; state = state.PlayState || {}; - return state.PositionTicks; - }; + return state.PositionTicks / 10000; + } - ChromecastPlayer.prototype.duration = function () { - var state = this.lastPlayerData || {}; + duration() { + let state = this.lastPlayerData || {}; state = state.NowPlayingItem || {}; return state.RunTimeTicks; - }; + } - ChromecastPlayer.prototype.getBufferedRanges = function () { - var state = this.lastPlayerData || {}; + getBufferedRanges() { + let state = this.lastPlayerData || {}; state = state.PlayState || {}; return state.BufferedRanges || []; - }; + } - ChromecastPlayer.prototype.paused = function () { - var state = this.lastPlayerData || {}; + paused() { + let state = this.lastPlayerData || {}; state = state.PlayState || {}; return state.IsPaused; - }; + } - ChromecastPlayer.prototype.isMuted = function () { - var state = this.lastPlayerData || {}; + isMuted() { + let state = this.lastPlayerData || {}; state = state.PlayState || {}; return state.IsMuted; - }; + } - ChromecastPlayer.prototype.shuffle = function (item) { + shuffle(item) { + const apiClient = window.connectionManager.getApiClient(item.ServerId); + const userId = apiClient.getCurrentUserId(); - var apiClient = connectionManager.getApiClient(item.ServerId); - var userId = apiClient.getCurrentUserId(); - - var instance = this; + const instance = this; apiClient.getItem(userId, item.Id).then(function (item) { - instance.playWithCommand({ - items: [item] - }, 'Shuffle'); - }); + } - }; + instantMix(item) { + const apiClient = window.connectionManager.getApiClient(item.ServerId); + const userId = apiClient.getCurrentUserId(); - ChromecastPlayer.prototype.instantMix = function (item) { - - var apiClient = connectionManager.getApiClient(item.ServerId); - var userId = apiClient.getCurrentUserId(); - - var instance = this; + const instance = this; apiClient.getItem(userId, item.Id).then(function (item) { - instance.playWithCommand({ - items: [item] - }, 'InstantMix'); - }); + } - }; - - ChromecastPlayer.prototype.canPlayMediaType = function (mediaType) { - + canPlayMediaType(mediaType) { mediaType = (mediaType || '').toLowerCase(); return mediaType === 'audio' || mediaType === 'video'; - }; + } - ChromecastPlayer.prototype.canQueueMediaType = function (mediaType) { + canQueueMediaType(mediaType) { return this.canPlayMediaType(mediaType); - }; + } - ChromecastPlayer.prototype.queue = function (options) { + queue(options) { this.playWithCommand(options, 'PlayLast'); - }; + } - ChromecastPlayer.prototype.queueNext = function (options) { + queueNext(options) { this.playWithCommand(options, 'PlayNext'); - }; - - ChromecastPlayer.prototype.play = function (options) { + } + play(options) { if (options.items) { - return this.playWithCommand(options, 'PlayNow'); - } else { - if (!options.serverId) { throw new Error('serverId required!'); } - var instance = this; - var apiClient = connectionManager.getApiClient(options.serverId); + const instance = this; + const apiClient = window.connectionManager.getApiClient(options.serverId); return getItemsForPlayback(apiClient, { - Ids: options.ids.join(',') - }).then(function (result) { - options.items = result.Items; return instance.playWithCommand(options, 'PlayNow'); - }); } - }; + } - ChromecastPlayer.prototype.toggleFullscreen = function () { + toggleFullscreen() { // not supported - }; + } - ChromecastPlayer.prototype.beginPlayerUpdates = function () { + beginPlayerUpdates() { // Setup polling here - }; + } - ChromecastPlayer.prototype.endPlayerUpdates = function () { + endPlayerUpdates() { // Stop polling here - }; + } - ChromecastPlayer.prototype.getPlaylist = function () { + getPlaylist() { return Promise.resolve([]); - }; + } - ChromecastPlayer.prototype.getCurrentPlaylistItemId = function () { - }; + getCurrentPlaylistItemId() { + } - ChromecastPlayer.prototype.setCurrentPlaylistItem = function (playlistItemId) { + setCurrentPlaylistItem(playlistItemId) { return Promise.resolve(); - }; + } - ChromecastPlayer.prototype.removeFromPlaylist = function (playlistItemIds) { + removeFromPlaylist(playlistItemIds) { return Promise.resolve(); - }; - - ChromecastPlayer.prototype.getPlayerState = function () { + } + getPlayerState() { return this.getPlayerStateInternal() || {}; - }; + } +} - return ChromecastPlayer; -}); +export default ChromecastPlayer; diff --git a/src/plugins/comicsPlayer/plugin.js b/src/plugins/comicsPlayer/plugin.js new file mode 100644 index 0000000000..7d0ac92df9 --- /dev/null +++ b/src/plugins/comicsPlayer/plugin.js @@ -0,0 +1,213 @@ +import loading from 'loading'; +import dialogHelper from 'dialogHelper'; +import keyboardnavigation from 'keyboardnavigation'; +import appRouter from 'appRouter'; +import * as libarchive from 'libarchive'; + +export class ComicsPlayer { + constructor() { + this.name = 'Comics Player'; + this.type = 'mediaplayer'; + this.id = 'comicsplayer'; + this.priority = 1; + this.imageMap = new Map(); + + this.onDialogClosed = this.onDialogClosed.bind(this); + this.onWindowKeyUp = this.onWindowKeyUp.bind(this); + } + + play(options) { + this.progress = 0; + + let elem = this.createMediaElement(); + return this.setCurrentSrc(elem, options); + } + + stop() { + this.unbindEvents(); + + let elem = this.mediaElement; + if (elem) { + dialogHelper.close(elem); + this.mediaElement = null; + } + + loading.hide(); + } + + onDialogClosed() { + this.stop(); + } + + onWindowKeyUp(e) { + let key = keyboardnavigation.getKeyName(e); + switch (key) { + case 'Escape': + this.stop(); + break; + } + } + + bindEvents() { + document.addEventListener('keyup', this.onWindowKeyUp); + } + + unbindEvents() { + document.removeEventListener('keyup', this.onWindowKeyUp); + } + + createMediaElement() { + let elem = this.mediaElement; + if (elem) { + return elem; + } + + elem = document.getElementById('comicsPlayer'); + if (!elem) { + elem = dialogHelper.createDialog({ + exitAnimationDuration: 400, + size: 'fullscreen', + autoFocus: false, + scrollY: false, + exitAnimation: 'fadeout', + removeOnClose: true + }); + + elem.id = 'comicsPlayer'; + elem.classList.add('slideshowDialog'); + + elem.innerHTML = '
    '; + + this.bindEvents(); + dialogHelper.open(elem); + } + + this.mediaElement = elem; + return elem; + } + + setCurrentSrc(elem, options) { + let item = options.items[0]; + this.currentItem = item; + + loading.show(); + + let serverId = item.ServerId; + let apiClient = window.connectionManager.getApiClient(serverId); + + libarchive.Archive.init({ + workerUrl: appRouter.baseUrl() + '/libraries/worker-bundle.js' + }); + + return new Promise((resolve, reject) => { + let downloadUrl = apiClient.getItemDownloadUrl(item.Id); + const archiveSource = new ArchiveSource(downloadUrl); + + var instance = this; + import('swiper').then(({default: Swiper}) => { + archiveSource.load().then(() => { + loading.hide(); + this.swiperInstance = new Swiper(elem.querySelector('.slideshowSwiperContainer'), { + direction: 'horizontal', + // loop is disabled due to the lack of support in virtual slides + loop: false, + zoom: { + minRatio: 1, + toggle: true, + containerClass: 'slider-zoom-container' + }, + autoplay: false, + keyboard: { + enabled: true + }, + preloadImages: true, + slidesPerView: 1, + slidesPerColumn: 1, + initialSlide: 0, + // reduces memory consumption for large libraries while allowing preloading of images + virtual: { + slides: archiveSource.urls, + cache: true, + renderSlide: instance.getImgFromUrl, + addSlidesBefore: 1, + addSlidesAfter: 1 + } + }); + }); + }); + }); + } + + getImgFromUrl(url) { + return `
    +
    + +
    +
    `; + } + + canPlayMediaType(mediaType) { + return (mediaType || '').toLowerCase() === 'book'; + } + + canPlayItem(item) { + if (item.Path && (item.Path.endsWith('cbz') || item.Path.endsWith('cbr'))) { + return true; + } + + return false; + } +} + +class ArchiveSource { + constructor(url) { + this.url = url; + this.files = []; + this.urls = []; + this.loadPromise = this.load(); + this.itemsLoaded = 0; + } + + async load() { + let res = await fetch(this.url); + if (!res.ok) { + return; + } + + let blob = await res.blob(); + this.archive = await libarchive.Archive.open(blob); + this.raw = await this.archive.getFilesArray(); + this.numberOfFiles = this.raw.length; + await this.archive.extractFiles(); + + let files = await this.archive.getFilesArray(); + files.sort((a, b) => { + if (a.file.name < b.file.name) { + return -1; + } else { + return 1; + } + }); + + for (let file of files) { + /* eslint-disable-next-line compat/compat */ + let url = URL.createObjectURL(file.file); + this.urls.push(url); + } + } + + getLength() { + return this.raw.length; + } + + async item(index) { + if (this.urls[index]) { + return this.urls[index]; + } + + await this.loadPromise; + return this.urls[index]; + } +} + +export default ComicsPlayer; diff --git a/src/plugins/experimentalWarnings/plugin.js b/src/plugins/experimentalWarnings/plugin.js index 632e38208c..bc301f01af 100644 --- a/src/plugins/experimentalWarnings/plugin.js +++ b/src/plugins/experimentalWarnings/plugin.js @@ -1,70 +1,61 @@ -define(['connectionManager', 'globalize', 'userSettings', 'apphost'], function (connectionManager, globalize, userSettings, appHost) { - 'use strict'; +import globalize from 'globalize'; +import * as userSettings from 'userSettings'; +import appHost from 'apphost'; - function getRequirePromise(deps) { +// TODO: Replace with date-fns +// https://stackoverflow.com/questions/6117814/get-week-of-year-in-javascript-like-in-php +function getWeek(date) { + const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())); + const dayNum = d.getUTCDay() || 7; + d.setUTCDate(d.getUTCDate() + 4 - dayNum); + const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1)); + return Math.ceil((((d - yearStart) / 86400000) + 1) / 7); +} - return new Promise(function (resolve, reject) { +function showMessage(text, userSettingsKey, appHostFeature) { + if (appHost.supports(appHostFeature)) { + return Promise.resolve(); + } - require(deps, resolve); + const now = new Date(); + + // TODO: Use date-fns + userSettingsKey += now.getFullYear() + '-w' + getWeek(now); + + if (userSettings.get(userSettingsKey, false) === '1') { + return Promise.resolve(); + } + + return new Promise(function (resolve, reject) { + userSettings.set(userSettingsKey, '1', false); + + import('alert').then(({default: alert}) => { + return alert(text).then(resolve, resolve); }); - } + }); +} - // https://stackoverflow.com/questions/6117814/get-week-of-year-in-javascript-like-in-php - function getWeek(date) { - var d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())); - var dayNum = d.getUTCDay() || 7; - d.setUTCDate(d.getUTCDate() + 4 - dayNum); - var yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1)); - return Math.ceil((((d - yearStart) / 86400000) + 1) / 7); - } +function showBlurayMessage() { + return showMessage(globalize.translate('UnsupportedPlayback'), 'blurayexpirementalinfo', 'nativeblurayplayback'); +} - function showMessage(text, userSettingsKey, appHostFeature) { +function showDvdMessage() { + return showMessage(globalize.translate('UnsupportedPlayback'), 'dvdexpirementalinfo', 'nativedvdplayback'); +} - if (appHost.supports(appHostFeature)) { - return Promise.resolve(); - } - - var now = new Date(); - - userSettingsKey += now.getFullYear() + '-w' + getWeek(now); - - if (userSettings.get(userSettingsKey, false) === '1') { - return Promise.resolve(); - } - - return new Promise(function (resolve, reject) { - - userSettings.set(userSettingsKey, '1', false); - - require(['alert'], function (alert) { - - return alert(text).then(resolve, resolve); - }); - }); - } - - function showBlurayMessage() { - return showMessage(globalize.translate('UnsupportedPlayback'), 'blurayexpirementalinfo', 'nativeblurayplayback'); - } - - function showDvdMessage() { - return showMessage(globalize.translate('UnsupportedPlayback'), 'dvdexpirementalinfo', 'nativedvdplayback'); - } - - function showIsoMessage() { - return showMessage(globalize.translate('UnsupportedPlayback'), 'isoexpirementalinfo', 'nativeisoplayback'); - } - - function ExpirementalPlaybackWarnings() { +function showIsoMessage() { + return showMessage(globalize.translate('UnsupportedPlayback'), 'isoexpirementalinfo', 'nativeisoplayback'); +} +class ExpirementalPlaybackWarnings { + constructor() { this.name = 'Experimental playback warnings'; this.type = 'preplayintercept'; this.id = 'expirementalplaybackwarnings'; } - ExpirementalPlaybackWarnings.prototype.intercept = function (options) { - - var item = options.item; + intercept(options) { + const item = options.item; if (!item) { return Promise.resolve(); } @@ -82,7 +73,7 @@ define(['connectionManager', 'globalize', 'userSettings', 'apphost'], function ( } return Promise.resolve(); - }; + } +} - return ExpirementalPlaybackWarnings; -}); +export default ExpirementalPlaybackWarnings; diff --git a/src/plugins/htmlAudioPlayer/plugin.js b/src/plugins/htmlAudioPlayer/plugin.js index 8265987e28..6f413fac50 100644 --- a/src/plugins/htmlAudioPlayer/plugin.js +++ b/src/plugins/htmlAudioPlayer/plugin.js @@ -1,92 +1,92 @@ -define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelper'], function (events, browser, require, appHost, appSettings, htmlMediaHelper) { - 'use strict'; +import events from 'events'; +import browser from 'browser'; +import appHost from 'apphost'; +import * as htmlMediaHelper from 'htmlMediaHelper'; - function getDefaultProfile() { - return new Promise(function (resolve, reject) { - require(['browserdeviceprofile'], function (profileBuilder) { - resolve(profileBuilder({})); - }); +function getDefaultProfile() { + return import('browserdeviceprofile').then(({ default: profileBuilder }) => { + return profileBuilder({}); + }); +} + +let fadeTimeout; +function fade(instance, elem, startingVolume) { + instance._isFadingOut = true; + + // Need to record the starting volume on each pass rather than querying elem.volume + // This is due to iOS safari not allowing volume changes and always returning the system volume value + const newVolume = Math.max(0, startingVolume - 0.15); + console.debug('fading volume to ' + newVolume); + elem.volume = newVolume; + + if (newVolume <= 0) { + instance._isFadingOut = false; + return Promise.resolve(); + } + + return new Promise(function (resolve, reject) { + cancelFadeTimeout(); + fadeTimeout = setTimeout(function () { + fade(instance, elem, newVolume).then(resolve, reject); + }, 100); + }); +} + +function cancelFadeTimeout() { + const timeout = fadeTimeout; + if (timeout) { + clearTimeout(timeout); + fadeTimeout = null; + } +} + +function supportsFade() { + if (browser.tv) { + // Not working on tizen. + // We could possibly enable on other tv's, but all smart tv browsers tend to be pretty primitive + return false; + } + + return true; +} + +function requireHlsPlayer(callback) { + import('hlsjs').then(({ default: hls }) => { + window.Hls = hls; + callback(); + }); +} + +function enableHlsPlayer(url, item, mediaSource, mediaType) { + if (!htmlMediaHelper.enableHlsJsPlayer(mediaSource.RunTimeTicks, mediaType)) { + return Promise.reject(); + } + + if (url.indexOf('.m3u8') !== -1) { + return Promise.resolve(); + } + + // issue head request to get content type + return new Promise(function (resolve, reject) { + import('fetchHelper').then((fetchHelper) => { + fetchHelper.ajax({ + url: url, + type: 'HEAD' + }).then(function (response) { + const contentType = (response.headers.get('Content-Type') || '').toLowerCase(); + if (contentType === 'application/x-mpegurl') { + resolve(); + } else { + reject(); + } + }, reject); }); - } + }); +} - var fadeTimeout; - function fade(instance, elem, startingVolume) { - instance._isFadingOut = true; - - // Need to record the starting volume on each pass rather than querying elem.volume - // This is due to iOS safari not allowing volume changes and always returning the system volume value - var newVolume = Math.max(0, startingVolume - 0.15); - console.debug('fading volume to ' + newVolume); - elem.volume = newVolume; - - if (newVolume <= 0) { - instance._isFadingOut = false; - return Promise.resolve(); - } - - return new Promise(function (resolve, reject) { - cancelFadeTimeout(); - fadeTimeout = setTimeout(function () { - fade(instance, elem, newVolume).then(resolve, reject); - }, 100); - }); - } - - function cancelFadeTimeout() { - var timeout = fadeTimeout; - if (timeout) { - clearTimeout(timeout); - fadeTimeout = null; - } - } - - function supportsFade() { - if (browser.tv) { - // Not working on tizen. - // We could possibly enable on other tv's, but all smart tv browsers tend to be pretty primitive - return false; - } - - return true; - } - - function requireHlsPlayer(callback) { - require(['hlsjs'], function (hls) { - window.Hls = hls; - callback(); - }); - } - - function enableHlsPlayer(url, item, mediaSource, mediaType) { - if (!htmlMediaHelper.enableHlsJsPlayer(mediaSource.RunTimeTicks, mediaType)) { - return Promise.reject(); - } - - if (url.indexOf('.m3u8') !== -1) { - return Promise.resolve(); - } - - // issue head request to get content type - return new Promise(function (resolve, reject) { - - require(['fetchHelper'], function (fetchHelper) { - fetchHelper.ajax({ - url: url, - type: 'HEAD' - }).then(function (response) { - var contentType = (response.headers.get('Content-Type') || '').toLowerCase(); - if (contentType === 'application/x-mpegurl') { - resolve(); - } else { - reject(); - } - }, reject); - }); - }); - } - - function HtmlAudioPlayer() { - var self = this; +class HtmlAudioPlayer { + constructor() { + const self = this; self.name = 'Html Audio Player'; self.type = 'mediaplayer'; @@ -96,27 +96,25 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp self.priority = 1; self.play = function (options) { - self._started = false; self._timeUpdated = false; self._currentTime = null; - var elem = createMediaElement(); + const elem = createMediaElement(); return setCurrentSrc(elem, options); }; function setCurrentSrc(elem, options) { - elem.removeEventListener('error', onError); unBindEvents(elem); bindEvents(elem); - var val = options.url; + let val = options.url; console.debug('playing url: ' + val); // Convert to seconds - var seconds = (options.playerStartPositionTicks || 0) / 10000000; + const seconds = (options.playerStartPositionTicks || 0) / 10000000; if (seconds) { val += '#t=' + seconds; } @@ -125,23 +123,16 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp self._currentPlayOptions = options; - var crossOrigin = htmlMediaHelper.getCrossOriginValue(options.mediaSource); + const crossOrigin = htmlMediaHelper.getCrossOriginValue(options.mediaSource); if (crossOrigin) { elem.crossOrigin = crossOrigin; } return enableHlsPlayer(val, options.item, options.mediaSource, 'Audio').then(function () { - return new Promise(function (resolve, reject) { - requireHlsPlayer(function () { - var hls = new Hls({ - manifestLoadingTimeOut: 20000, - xhrSetup: function(xhr, url) { - xhr.withCredentials = true; - } - //appendErrorMaxRetry: 6, - //debug: true + const hls = new Hls({ + manifestLoadingTimeOut: 20000 }); hls.loadSource(val); hls.attachMedia(elem); @@ -153,16 +144,13 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp self._currentSrc = val; }); }); - }, function () { - elem.autoplay = true; // Safari will not send cookies without this elem.crossOrigin = 'use-credentials'; return htmlMediaHelper.applySrc(elem, val, options).then(function () { - self._currentSrc = val; return htmlMediaHelper.playWithPromise(elem, onError); @@ -191,16 +179,13 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp } self.stop = function (destroyPlayer) { - cancelFadeTimeout(); - var elem = self._mediaElement; - var src = self._currentSrc; + const elem = self._mediaElement; + const src = self._currentSrc; if (elem && src) { - if (!destroyPlayer || !supportsFade()) { - elem.pause(); htmlMediaHelper.onEndedInternal(self, elem, onError); @@ -211,10 +196,9 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp return Promise.resolve(); } - var originalVolume = elem.volume; + const originalVolume = elem.volume; return fade(self, elem, elem.volume).then(function () { - elem.pause(); elem.volume = originalVolume; @@ -233,8 +217,7 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp }; function createMediaElement() { - - var elem = self._mediaElement; + let elem = self._mediaElement; if (elem) { return elem; @@ -258,14 +241,12 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp } function onEnded() { - htmlMediaHelper.onEndedInternal(self, this, onError); } function onTimeUpdate() { - // Get the player position + the transcoding offset - var time = this.currentTime; + const time = this.currentTime; // Don't trigger events after user stop if (!self._isFadingOut) { @@ -275,7 +256,6 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp } function onVolumeChange() { - if (!self._isFadingOut) { htmlMediaHelper.saveVolume(this.volume); events.trigger(self, 'volumechange'); @@ -283,7 +263,6 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp } function onPlaying(e) { - if (!self._started) { self._started = true; this.removeAttribute('controls'); @@ -294,7 +273,6 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp } function onPlay(e) { - events.trigger(self, 'unpause'); } @@ -307,12 +285,11 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp } function onError() { - - var errorCode = this.error ? (this.error.code || 0) : 0; - var errorMessage = this.error ? (this.error.message || '') : ''; + const errorCode = this.error ? (this.error.code || 0) : 0; + const errorMessage = this.error ? (this.error.message || '') : ''; console.error('media element error: ' + errorCode.toString() + ' ' + errorMessage); - var type; + let type; switch (errorCode) { case 1: @@ -346,65 +323,59 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp } } - HtmlAudioPlayer.prototype.currentSrc = function () { + currentSrc() { return this._currentSrc; - }; - - HtmlAudioPlayer.prototype.canPlayMediaType = function (mediaType) { + } + canPlayMediaType(mediaType) { return (mediaType || '').toLowerCase() === 'audio'; - }; - - HtmlAudioPlayer.prototype.getDeviceProfile = function (item) { + } + getDeviceProfile(item) { if (appHost.getDeviceProfile) { return appHost.getDeviceProfile(item); } return getDefaultProfile(); - }; + } // Save this for when playback stops, because querying the time at that point might return 0 - HtmlAudioPlayer.prototype.currentTime = function (val) { - - var mediaElement = this._mediaElement; + currentTime(val) { + const mediaElement = this._mediaElement; if (mediaElement) { if (val != null) { mediaElement.currentTime = val / 1000; return; } - var currentTime = this._currentTime; + const currentTime = this._currentTime; if (currentTime) { return currentTime * 1000; } return (mediaElement.currentTime || 0) * 1000; } - }; + } - HtmlAudioPlayer.prototype.duration = function (val) { - - var mediaElement = this._mediaElement; + duration(val) { + const mediaElement = this._mediaElement; if (mediaElement) { - var duration = mediaElement.duration; + const duration = mediaElement.duration; if (htmlMediaHelper.isValidDuration(duration)) { return duration * 1000; } } return null; - }; + } - HtmlAudioPlayer.prototype.seekable = function () { - var mediaElement = this._mediaElement; + seekable() { + const mediaElement = this._mediaElement; if (mediaElement) { - - var seekable = mediaElement.seekable; + const seekable = mediaElement.seekable; if (seekable && seekable.length) { - - var start = seekable.start(0); - var end = seekable.end(0); + let start = seekable.start(0); + let end = seekable.end(0); if (!htmlMediaHelper.isValidDuration(start)) { start = 0; @@ -418,128 +389,120 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp return false; } - }; + } - HtmlAudioPlayer.prototype.getBufferedRanges = function () { - var mediaElement = this._mediaElement; + getBufferedRanges() { + const mediaElement = this._mediaElement; if (mediaElement) { - return htmlMediaHelper.getBufferedRanges(this, mediaElement); } return []; - }; + } - HtmlAudioPlayer.prototype.pause = function () { - var mediaElement = this._mediaElement; + pause() { + const mediaElement = this._mediaElement; if (mediaElement) { mediaElement.pause(); } - }; + } // This is a retry after error - HtmlAudioPlayer.prototype.resume = function () { - var mediaElement = this._mediaElement; + resume() { + const mediaElement = this._mediaElement; if (mediaElement) { mediaElement.play(); } - }; + } - HtmlAudioPlayer.prototype.unpause = function () { - var mediaElement = this._mediaElement; + unpause() { + const mediaElement = this._mediaElement; if (mediaElement) { mediaElement.play(); } - }; + } - HtmlAudioPlayer.prototype.paused = function () { - - var mediaElement = this._mediaElement; + paused() { + const mediaElement = this._mediaElement; if (mediaElement) { return mediaElement.paused; } return false; - }; + } - HtmlAudioPlayer.prototype.setPlaybackRate = function (value) { - var mediaElement = this._mediaElement; + setPlaybackRate(value) { + const mediaElement = this._mediaElement; if (mediaElement) { mediaElement.playbackRate = value; } - }; + } - HtmlAudioPlayer.prototype.getPlaybackRate = function () { - var mediaElement = this._mediaElement; + getPlaybackRate() { + const mediaElement = this._mediaElement; if (mediaElement) { return mediaElement.playbackRate; } return null; - }; + } - HtmlAudioPlayer.prototype.setVolume = function (val) { - var mediaElement = this._mediaElement; + setVolume(val) { + const mediaElement = this._mediaElement; if (mediaElement) { mediaElement.volume = val / 100; } - }; + } - HtmlAudioPlayer.prototype.getVolume = function () { - var mediaElement = this._mediaElement; + getVolume() { + const mediaElement = this._mediaElement; if (mediaElement) { - return Math.min(Math.round(mediaElement.volume * 100), 100); } - }; + } - HtmlAudioPlayer.prototype.volumeUp = function () { + volumeUp() { this.setVolume(Math.min(this.getVolume() + 2, 100)); - }; + } - HtmlAudioPlayer.prototype.volumeDown = function () { + volumeDown() { this.setVolume(Math.max(this.getVolume() - 2, 0)); - }; + } - HtmlAudioPlayer.prototype.setMute = function (mute) { - - var mediaElement = this._mediaElement; + setMute(mute) { + const mediaElement = this._mediaElement; if (mediaElement) { mediaElement.muted = mute; } - }; + } - HtmlAudioPlayer.prototype.isMuted = function () { - var mediaElement = this._mediaElement; + isMuted() { + const mediaElement = this._mediaElement; if (mediaElement) { return mediaElement.muted; } return false; - }; - - HtmlAudioPlayer.prototype.destroy = function () { - - }; - - var supportedFeatures; - - function getSupportedFeatures() { - var list = []; - var audio = document.createElement('audio'); - - if (typeof audio.playbackRate === 'number') { - list.push('PlaybackRate'); - } - - return list; } - HtmlAudioPlayer.prototype.supports = function (feature) { + supports(feature) { if (!supportedFeatures) { supportedFeatures = getSupportedFeatures(); } return supportedFeatures.indexOf(feature) !== -1; - }; + } +} - return HtmlAudioPlayer; -}); +let supportedFeatures; + +function getSupportedFeatures() { + const list = []; + const audio = document.createElement('audio'); + + if (typeof audio.playbackRate === 'number') { + list.push('PlaybackRate'); + } + + return list; +} + +export default HtmlAudioPlayer; diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js index cc312bb956..d07e6aae58 100644 --- a/src/plugins/htmlVideoPlayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -1,43 +1,47 @@ -define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackManager', 'appRouter', 'appSettings', 'connectionManager', 'htmlMediaHelper', 'itemHelper', 'screenfull', 'globalize'], function (browser, require, events, appHost, loading, dom, playbackManager, appRouter, appSettings, connectionManager, htmlMediaHelper, itemHelper, screenfull, globalize) { - 'use strict'; - /* globals cast */ +import browser from 'browser'; +import events from 'events'; +import appHost from 'apphost'; +import loading from 'loading'; +import dom from 'dom'; +import playbackManager from 'playbackManager'; +import appRouter from 'appRouter'; +import { + bindEventsToHlsPlayer, + destroyHlsPlayer, + destroyFlvPlayer, + destroyCastPlayer, + getCrossOriginValue, + enableHlsJsPlayer, + applySrc, + playWithPromise, + onEndedInternal, + saveVolume, + seekOnPlaybackStart, + onErrorInternal, + handleHlsJsMediaError, + getSavedVolume, + isValidDuration, + getBufferedRanges +} from 'htmlMediaHelper'; +import itemHelper from 'itemHelper'; +import screenfull from 'screenfull'; +import globalize from 'globalize'; - var mediaManager; +/* eslint-disable indent */ - function tryRemoveElement(elem) { - var parentNode = elem.parentNode; +function tryRemoveElement(elem) { + const parentNode = elem.parentNode; if (parentNode) { - // Seeing crashes in edge webview try { parentNode.removeChild(elem); } catch (err) { - console.error('error removing dialog element: ' + err); + console.error(`error removing dialog element: ${err}`); } } } - var _supportsTextTracks; - function supportsTextTracks() { - - if (_supportsTextTracks == null) { - _supportsTextTracks = document.createElement('video').textTracks != null; - } - - // For now, until ready - return _supportsTextTracks; - } - - function supportsCanvas() { - return !!document.createElement('canvas').getContext; - } - - function supportsWebWorkers() { - return !!window.Worker; - } - function enableNativeTrackSupport(currentSrc, track) { - if (track) { if (track.DeliveryMethod === 'Embed') { return true; @@ -45,14 +49,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa } if (browser.firefox) { - if ((currentSrc || '').toLowerCase().indexOf('.m3u8') !== -1) { - return false; - } - } - - // subs getting blocked due to CORS - if (browser.chromecast) { - if ((currentSrc || '').toLowerCase().indexOf('.m3u8') !== -1) { + if ((currentSrc || '').toLowerCase().includes('.m3u8')) { return false; } } @@ -78,7 +75,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa } if (track) { - var format = (track.Codec || '').toLowerCase(); + const format = (track.Codec || '').toLowerCase(); if (format === 'ssa' || format === 'ass') { return false; } @@ -88,7 +85,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa } function requireHlsPlayer(callback) { - require(['hlsjs'], function (hls) { + import('hlsjs').then(({default: hls}) => { window.Hls = hls; callback(); }); @@ -107,16 +104,16 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa } function hidePrePlaybackPage() { - let animatedPage = document.querySelector('.page:not(.hide)'); + const animatedPage = document.querySelector('.page:not(.hide)'); animatedPage.classList.add('hide'); // At this point, we must hide the scrollbar placeholder, so it's not being displayed while the item is being loaded document.body.classList.remove('force-scroll'); } function zoomIn(elem) { - return new Promise(function (resolve, reject) { - var duration = 240; - elem.style.animation = 'htmlvideoplayer-zoomin ' + duration + 'ms ease-in normal'; + return new Promise(resolve => { + const duration = 240; + elem.style.animation = `htmlvideoplayer-zoomin ${duration}ms ease-in normal`; hidePrePlaybackPage(); dom.addEventListener(elem, dom.whichAnimationEvent(), resolve, { once: true @@ -125,22 +122,16 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa } function normalizeTrackEventText(text, useHtml) { - var result = text.replace(/\\N/gi, '\n').replace(/\r/gi, ''); + const result = text.replace(/\\N/gi, '\n').replace(/\r/gi, ''); return useHtml ? result.replace(/\n/gi, '
    ') : result; } - function setTracks(elem, tracks, item, mediaSource) { - - elem.innerHTML = getTracksHtml(tracks, item, mediaSource); - } - function getTextTrackUrl(track, item, format) { - if (itemHelper.isLocalItem(item) && track.Path) { return track.Path; } - var url = playbackManager.getSubtitleUrl(track, item.ServerId); + let url = playbackManager.getSubtitleUrl(track, item.ServerId); if (format) { url = url.replace('.vtt', format); } @@ -148,161 +139,232 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa return url; } - function getTracksHtml(tracks, item, mediaSource) { - return tracks.map(function (t) { - - if (t.DeliveryMethod !== 'External') { - return ''; - } - - var defaultAttribute = mediaSource.DefaultSubtitleStreamIndex === t.Index ? ' default' : ''; - - var language = t.Language || 'und'; - var label = t.Language || 'und'; - return ''; - - }).join(''); - } - function getDefaultProfile() { - - return new Promise(function (resolve, reject) { - - require(['browserdeviceprofile'], function (profileBuilder) { - - resolve(profileBuilder({})); - }); + return import('browserdeviceprofile').then(({default: profileBuilder}) => { + return profileBuilder({}); }); } - function HtmlVideoPlayer() { + export class HtmlVideoPlayer { + /** + * @type {string} + */ + name; + /** + * @type {string} + */ + type = 'mediaplayer'; + /** + * @type {string} + */ + id = 'htmlvideoplayer'; + /** + * Let any players created by plugins take priority + * + * @type {number} + */ + priority = 1; + /** + * @type {boolean} + */ + isFetching = false; - if (browser.edgeUwp) { - this.name = 'Windows Video Player'; - } else { - this.name = 'Html Video Player'; - } + /** + * @type {HTMLDivElement | null | undefined} + */ + #videoDialog; + /** + * @type {number | undefined} + */ + #subtitleTrackIndexToSetOnPlaying; + /** + * @type {number | null} + */ + #audioTrackIndexToSetOnPlaying; + /** + * @type {null | undefined} + */ + #currentClock; + /** + * @type {any | null | undefined} + */ + #currentSubtitlesOctopus; + /** + * @type {null | undefined} + */ + #currentAssRenderer; + /** + * @type {number | undefined} + */ + #customTrackIndex; + /** + * @type {boolean | undefined} + */ + #showTrackOffset; + /** + * @type {number | undefined} + */ + #currentTrackOffset; + /** + * @type {HTMLElement | null | undefined} + */ + #videoSubtitlesElem; + /** + * @type {any | null | undefined} + */ + #currentTrackEvents; + /** + * @type {string[] | undefined} + */ + #supportedFeatures; + /** + * @type {HTMLVideoElement | null | undefined} + */ + #mediaElement; + /** + * @type {number} + */ + #fetchQueue = 0; + /** + * @type {string | undefined} + */ + #currentSrc; + /** + * @type {boolean | undefined} + */ + #started; + /** + * @type {boolean | undefined} + */ + #timeUpdated; + /** + * @type {number | null | undefined} + */ + #currentTime; + /** + * @type {any | undefined} + */ + #flvPlayer; + /** + * @private (used in other files) + * @type {any | undefined} + */ + _hlsPlayer; + /** + * @private (used in other files) + * @type {any | null | undefined} + */ + _castPlayer; + /** + * @private (used in other files) + * @type {any | undefined} + */ + _currentPlayOptions; + /** + * @type {any | undefined} + */ + #lastProfile; + /** + * @type {MutationObserver | IntersectionObserver | undefined} (Unclear observer typing) + */ + #resizeObserver; - this.type = 'mediaplayer'; - this.id = 'htmlvideoplayer'; - - // Let any players created by plugins take priority - this.priority = 1; - - var videoDialog; - - var winJsPlaybackItem; - - var subtitleTrackIndexToSetOnPlaying; - var audioTrackIndexToSetOnPlaying; - - var lastCustomTrackMs = 0; - var currentClock; - var currentSubtitlesOctopus; - var currentAssRenderer; - var customTrackIndex = -1; - - var showTrackOffset; - var currentTrackOffset; - - var videoSubtitlesElem; - var currentTrackEvents; - - var self = this; - - self.currentSrc = function () { - return self._currentSrc; - }; - - self._fetchQueue = 0; - self.isFetching = false; - - function incrementFetchQueue() { - if (self._fetchQueue <= 0) { - self.isFetching = true; - events.trigger(self, 'beginFetch'); - } - - self._fetchQueue++; - } - - function decrementFetchQueue() { - self._fetchQueue--; - - if (self._fetchQueue <= 0) { - self.isFetching = false; - events.trigger(self, 'endFetch'); + constructor() { + if (browser.edgeUwp) { + this.name = 'Windows Video Player'; + } else { + this.name = 'Html Video Player'; } } - function updateVideoUrl(streamInfo) { + currentSrc() { + return this.#currentSrc; + } - var isHls = streamInfo.url.toLowerCase().indexOf('.m3u8') !== -1; + /** + * @private + */ + incrementFetchQueue() { + if (this.#fetchQueue <= 0) { + this.isFetching = true; + events.trigger(this, 'beginFetch'); + } - var mediaSource = streamInfo.mediaSource; - var item = streamInfo.item; + this.#fetchQueue++; + } + + /** + * @private + */ + decrementFetchQueue() { + this.#fetchQueue--; + + if (this.#fetchQueue <= 0) { + this.isFetching = false; + events.trigger(this, 'endFetch'); + } + } + + /** + * @private + */ + updateVideoUrl(streamInfo) { + const isHls = streamInfo.url.toLowerCase().includes('.m3u8'); + + const mediaSource = streamInfo.mediaSource; + const item = streamInfo.item; // Huge hack alert. Safari doesn't seem to like if the segments aren't available right away when playback starts // This will start the transcoding process before actually feeding the video url into the player // Edit: Also seeing stalls from hls.js if (mediaSource && item && !mediaSource.RunTimeTicks && isHls && streamInfo.playMethod === 'Transcode' && (browser.iOS || browser.osx)) { - - var hlsPlaylistUrl = streamInfo.url.replace('master.m3u8', 'live.m3u8'); + const hlsPlaylistUrl = streamInfo.url.replace('master.m3u8', 'live.m3u8'); loading.show(); - console.debug('prefetching hls playlist: ' + hlsPlaylistUrl); + console.debug(`prefetching hls playlist: ${hlsPlaylistUrl}`); - return connectionManager.getApiClient(item.ServerId).ajax({ + return window.connectionManager.getApiClient(item.ServerId).ajax({ type: 'GET', url: hlsPlaylistUrl }).then(function () { - - console.debug('completed prefetching hls playlist: ' + hlsPlaylistUrl); + console.debug(`completed prefetching hls playlist: ${hlsPlaylistUrl}`); loading.hide(); streamInfo.url = hlsPlaylistUrl; - - return Promise.resolve(); - }, function () { - - console.error('error prefetching hls playlist: ' + hlsPlaylistUrl); + console.error(`error prefetching hls playlist: ${hlsPlaylistUrl}`); loading.hide(); - return Promise.resolve(); }); - } else { return Promise.resolve(); } } - self.play = function (options) { - self._started = false; - self._timeUpdated = false; + play(options) { + this.#started = false; + this.#timeUpdated = false; - self._currentTime = null; + this.#currentTime = null; - self.resetSubtitleOffset(); + this.resetSubtitleOffset(); - return createMediaElement(options).then(function (elem) { - - return updateVideoUrl(options).then(function () { - return setCurrentSrc(elem, options); + return this.createMediaElement(options).then(elem => { + return this.updateVideoUrl(options).then(() => { + return this.setCurrentSrc(elem, options); }); }); - }; + } - function setSrcWithFlvJs(instance, elem, options, url) { - - return new Promise(function (resolve, reject) { - - require(['flvjs'], function (flvjs) { - - var flvPlayer = flvjs.createPlayer({ + /** + * @private + */ + setSrcWithFlvJs(elem, options, url) { + return import('flvjs').then(({default: flvjs}) => { + const flvPlayer = flvjs.createPlayer({ type: 'flv', url: url }, @@ -311,288 +373,123 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa lazyLoad: false }); - flvPlayer.attachMediaElement(elem); - flvPlayer.load(); + flvPlayer.attachMediaElement(elem); + flvPlayer.load(); - flvPlayer.play().then(resolve, reject); - instance._flvPlayer = flvPlayer; + this.#flvPlayer = flvPlayer; - // This is needed in setCurrentTrackElement - self._currentSrc = url; - }); + // This is needed in setCurrentTrackElement + this.#currentSrc = url; + + return flvPlayer.play(); }); } - function setSrcWithHlsJs(instance, elem, options, url) { - - return new Promise(function (resolve, reject) { - - requireHlsPlayer(function () { - - var hls = new Hls({ - manifestLoadingTimeOut: 20000, - xhrSetup: function(xhr, xhr_url) { - xhr.withCredentials = true; - } - //appendErrorMaxRetry: 6, - //debug: true + /** + * @private + */ + setSrcWithHlsJs(elem, options, url) { + return new Promise((resolve, reject) => { + requireHlsPlayer(() => { + const hls = new Hls({ + manifestLoadingTimeOut: 20000 }); hls.loadSource(url); hls.attachMedia(elem); - htmlMediaHelper.bindEventsToHlsPlayer(self, hls, elem, onError, resolve, reject); + bindEventsToHlsPlayer(this, hls, elem, this.onError, resolve, reject); - self._hlsPlayer = hls; + this._hlsPlayer = hls; // This is needed in setCurrentTrackElement - self._currentSrc = url; + this.#currentSrc = url; }); }); } - function onShakaError(event) { + /** + * @private + */ + setCurrentSrc(elem, options) { + elem.removeEventListener('error', this.onError); - var error = event.detail; - console.error('Error code', error.code, 'object', error); - } - - function setSrcWithShakaPlayer(instance, elem, options, url) { - - return new Promise(function (resolve, reject) { - - require(['shaka'], function () { - /* globals shaka */ - - var player = new shaka.Player(elem); - - //player.configure({ - // abr: { - // enabled: false - // }, - // streaming: { - - // failureCallback: function () { - // alert(2); - // } - // } - //}); - - //shaka.log.setLevel(6); - - // Listen for error events. - player.addEventListener('error', onShakaError); - - // Try to load a manifest. - // This is an asynchronous process. - player.load(url).then(function () { - - // This runs if the asynchronous load is successful. - resolve(); - - }, reject); - - self._shakaPlayer = player; - - // This is needed in setCurrentTrackElement - self._currentSrc = url; - }); - }); - } - - function setCurrentSrcChromecast(instance, elem, options, url) { - - elem.autoplay = true; - - var lrd = new cast.receiver.MediaManager.LoadRequestData(); - lrd.currentTime = (options.playerStartPositionTicks || 0) / 10000000; - lrd.autoplay = true; - lrd.media = new cast.receiver.media.MediaInformation(); - - lrd.media.contentId = url; - lrd.media.contentType = options.mimeType; - lrd.media.streamType = cast.receiver.media.StreamType.OTHER; - lrd.media.customData = options; - - console.debug('loading media url into media manager'); - - try { - mediaManager.load(lrd); - // This is needed in setCurrentTrackElement - self._currentSrc = url; - - return Promise.resolve(); - } catch (err) { - - console.debug('media manager error: ' + err); - return Promise.reject(); - } - } - - // Adapted from : https://github.com/googlecast/CastReferencePlayer/blob/master/player.js - function onMediaManagerLoadMedia(event) { - - if (self._castPlayer) { - self._castPlayer.unload(); // Must unload before starting again. - } - self._castPlayer = null; - - var data = event.data; - - var media = event.data.media || {}; - var url = media.contentId; - var contentType = media.contentType.toLowerCase(); - var options = media.customData; - - var protocol; - var ext = 'm3u8'; - - var mediaElement = self._mediaElement; - - var host = new cast.player.api.Host({ - 'url': url, - 'mediaElement': mediaElement - }); - - if (ext === 'm3u8' || - contentType === 'application/x-mpegurl' || - contentType === 'application/vnd.apple.mpegurl') { - protocol = cast.player.api.CreateHlsStreamingProtocol(host); - } else if (ext === 'mpd' || - contentType === 'application/dash+xml') { - protocol = cast.player.api.CreateDashStreamingProtocol(host); - } else if (url.indexOf('.ism') > -1 || - contentType === 'application/vnd.ms-sstr+xml') { - protocol = cast.player.api.CreateSmoothStreamingProtocol(host); - } - - console.debug('loading playback url: ' + url); - console.debug('content type: ' + contentType); - - host.onError = function (errorCode) { - console.error('fatal Error - ' + errorCode); - }; - - mediaElement.autoplay = false; - - self._castPlayer = new cast.player.api.Player(host); - - self._castPlayer.load(protocol, data.currentTime || 0); - - self._castPlayer.playWhenHaveEnoughData(); - } - - function initMediaManager() { - - mediaManager.defaultOnLoad = mediaManager.onLoad.bind(mediaManager); - mediaManager.onLoad = onMediaManagerLoadMedia.bind(self); - - //mediaManager.defaultOnPlay = mediaManager.onPlay.bind(mediaManager); - //mediaManager.onPlay = function (event) { - // // TODO ??? - // mediaManager.defaultOnPlay(event); - //}; - - mediaManager.defaultOnStop = mediaManager.onStop.bind(mediaManager); - mediaManager.onStop = function (event) { - playbackManager.stop(); - mediaManager.defaultOnStop(event); - }; - } - - function setCurrentSrc(elem, options) { - - elem.removeEventListener('error', onError); - - var val = options.url; - console.debug('playing url: ' + val); + let val = options.url; + console.debug(`playing url: ${val}`); // Convert to seconds - var seconds = (options.playerStartPositionTicks || 0) / 10000000; + const seconds = (options.playerStartPositionTicks || 0) / 10000000; if (seconds) { - val += '#t=' + seconds; + val += `#t=${seconds}`; } - htmlMediaHelper.destroyHlsPlayer(self); - htmlMediaHelper.destroyFlvPlayer(self); - htmlMediaHelper.destroyCastPlayer(self); + destroyHlsPlayer(this); + destroyFlvPlayer(this); + destroyCastPlayer(this); - var tracks = getMediaStreamTextTracks(options.mediaSource); - - subtitleTrackIndexToSetOnPlaying = options.mediaSource.DefaultSubtitleStreamIndex == null ? -1 : options.mediaSource.DefaultSubtitleStreamIndex; - if (subtitleTrackIndexToSetOnPlaying != null && subtitleTrackIndexToSetOnPlaying >= 0) { - var initialSubtitleStream = options.mediaSource.MediaStreams[subtitleTrackIndexToSetOnPlaying]; + this.#subtitleTrackIndexToSetOnPlaying = options.mediaSource.DefaultSubtitleStreamIndex == null ? -1 : options.mediaSource.DefaultSubtitleStreamIndex; + if (this.#subtitleTrackIndexToSetOnPlaying != null && this.#subtitleTrackIndexToSetOnPlaying >= 0) { + const initialSubtitleStream = options.mediaSource.MediaStreams[this.#subtitleTrackIndexToSetOnPlaying]; if (!initialSubtitleStream || initialSubtitleStream.DeliveryMethod === 'Encode') { - subtitleTrackIndexToSetOnPlaying = -1; + this.#subtitleTrackIndexToSetOnPlaying = -1; } } - audioTrackIndexToSetOnPlaying = options.playMethod === 'Transcode' ? null : options.mediaSource.DefaultAudioStreamIndex; + this.#audioTrackIndexToSetOnPlaying = options.playMethod === 'Transcode' ? null : options.mediaSource.DefaultAudioStreamIndex; - self._currentPlayOptions = options; + this._currentPlayOptions = options; - var crossOrigin = htmlMediaHelper.getCrossOriginValue(options.mediaSource); + const crossOrigin = getCrossOriginValue(options.mediaSource); if (crossOrigin) { elem.crossOrigin = crossOrigin; } - /*if (htmlMediaHelper.enableHlsShakaPlayer(options.item, options.mediaSource, 'Video') && val.indexOf('.m3u8') !== -1) { - - setTracks(elem, tracks, options.item, options.mediaSource); - - return setSrcWithShakaPlayer(self, elem, options, val); - - } else*/ if (browser.chromecast && val.indexOf('.m3u8') !== -1 && options.mediaSource.RunTimeTicks) { - - return setCurrentSrcChromecast(self, elem, options, val); - } else if (htmlMediaHelper.enableHlsJsPlayer(options.mediaSource.RunTimeTicks, 'Video') && val.indexOf('.m3u8') !== -1) { - return setSrcWithHlsJs(self, elem, options, val); + if (enableHlsJsPlayer(options.mediaSource.RunTimeTicks, 'Video') && val.includes('.m3u8')) { + return this.setSrcWithHlsJs(elem, options, val); } else if (options.playMethod !== 'Transcode' && options.mediaSource.Container === 'flv') { - - return setSrcWithFlvJs(self, elem, options, val); - + return this.setSrcWithFlvJs(elem, options, val); } else { - elem.autoplay = true; // Safari will not send cookies without this elem.crossOrigin = 'use-credentials'; - return htmlMediaHelper.applySrc(elem, val, options).then(function () { + return applySrc(elem, val, options).then(() => { + this.#currentSrc = val; - self._currentSrc = val; - - return htmlMediaHelper.playWithPromise(elem, onError); + return playWithPromise(elem, this.onError); }); } } - self.setSubtitleStreamIndex = function (index) { + setSubtitleStreamIndex(index) { + this.setCurrentTrackElement(index); + } - setCurrentTrackElement(index); - }; + resetSubtitleOffset() { + this.#currentTrackOffset = 0; + this.#showTrackOffset = false; + } - self.resetSubtitleOffset = function() { - currentTrackOffset = 0; - showTrackOffset = false; - }; + enableShowingSubtitleOffset() { + this.#showTrackOffset = true; + } - self.enableShowingSubtitleOffset = function() { - showTrackOffset = true; - }; + disableShowingSubtitleOffset() { + this.#showTrackOffset = false; + } - self.disableShowingSubtitleOffset = function() { - showTrackOffset = false; - }; + isShowingSubtitleOffsetEnabled() { + return this.#showTrackOffset; + } - self.isShowingSubtitleOffsetEnabled = function() { - return showTrackOffset; - }; - - function getTextTrack() { - var videoElement = self._mediaElement; + /** + * @private + */ + getTextTrack() { + const videoElement = this.#mediaElement; if (videoElement) { return Array.from(videoElement.textTracks) - .find(function(trackElement) { + .find(function (trackElement) { // get showing .vtt textTack return trackElement.mode === 'showing'; }); @@ -601,69 +498,79 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa } } - self.setSubtitleOffset = function(offset) { - - var offsetValue = parseFloat(offset); + /** + * @private + */ + setSubtitleOffset(offset) { + const offsetValue = parseFloat(offset); // if .ass currently rendering - if (currentSubtitlesOctopus) { - updateCurrentTrackOffset(offsetValue); - currentSubtitlesOctopus.timeOffset = (self._currentPlayOptions.transcodingOffsetTicks || 0) / 10000000 + offsetValue; + if (this.#currentSubtitlesOctopus) { + this.updateCurrentTrackOffset(offsetValue); + this.#currentSubtitlesOctopus.timeOffset = (this._currentPlayOptions.transcodingOffsetTicks || 0) / 10000000 + offsetValue; } else { - var trackElement = getTextTrack(); + const trackElement = this.getTextTrack(); // if .vtt currently rendering if (trackElement) { - setTextTrackSubtitleOffset(trackElement, offsetValue); - } else if (currentTrackEvents) { - setTrackEventsSubtitleOffset(currentTrackEvents, offsetValue); + this.setTextTrackSubtitleOffset(trackElement, offsetValue); + } else if (this.#currentTrackEvents) { + this.setTrackEventsSubtitleOffset(this.#currentTrackEvents, offsetValue); } else { console.debug('No available track, cannot apply offset: ', offsetValue); } } - }; + } - function updateCurrentTrackOffset(offsetValue) { - - var relativeOffset = offsetValue; - var newTrackOffset = offsetValue; - if (currentTrackOffset) { - relativeOffset -= currentTrackOffset; + /** + * @private + */ + updateCurrentTrackOffset(offsetValue) { + let relativeOffset = offsetValue; + const newTrackOffset = offsetValue; + if (this.#currentTrackOffset) { + relativeOffset -= this.#currentTrackOffset; } - currentTrackOffset = newTrackOffset; + this.#currentTrackOffset = newTrackOffset; // relative to currentTrackOffset return relativeOffset; } - function setTextTrackSubtitleOffset(currentTrack, offsetValue) { - + /** + * @private + */ + setTextTrackSubtitleOffset(currentTrack, offsetValue) { if (currentTrack.cues) { - offsetValue = updateCurrentTrackOffset(offsetValue); + offsetValue = this.updateCurrentTrackOffset(offsetValue); Array.from(currentTrack.cues) - .forEach(function(cue) { + .forEach(function (cue) { cue.startTime -= offsetValue; cue.endTime -= offsetValue; }); } } - function setTrackEventsSubtitleOffset(trackEvents, offsetValue) { - + /** + * @private + */ + setTrackEventsSubtitleOffset(trackEvents, offsetValue) { if (Array.isArray(trackEvents)) { - offsetValue = updateCurrentTrackOffset(offsetValue) * 1e7; // ticks - trackEvents.forEach(function(trackEvent) { + offsetValue = this.updateCurrentTrackOffset(offsetValue) * 1e7; // ticks + trackEvents.forEach(function (trackEvent) { trackEvent.StartPositionTicks -= offsetValue; trackEvent.EndPositionTicks -= offsetValue; }); } } - self.getSubtitleOffset = function() { - return currentTrackOffset; - }; + getSubtitleOffset() { + return this.#currentTrackOffset; + } - function isAudioStreamSupported(stream, deviceProfile) { - - var codec = (stream.Codec || '').toLowerCase(); + /** + * @private + */ + isAudioStreamSupported(stream, deviceProfile) { + const codec = (stream.Codec || '').toLowerCase(); if (!codec) { return true; @@ -674,49 +581,43 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa return true; } - var profiles = deviceProfile.DirectPlayProfiles || []; + const profiles = deviceProfile.DirectPlayProfiles || []; return profiles.filter(function (p) { - if (p.Type === 'Video') { - if (!p.AudioCodec) { return true; } - return p.AudioCodec.toLowerCase().indexOf(codec) !== -1; + return p.AudioCodec.toLowerCase().includes(codec); } return false; - }).length > 0; } - function getSupportedAudioStreams() { - var profile = self._lastProfile; + /** + * @private + */ + getSupportedAudioStreams() { + const profile = this.#lastProfile; - return getMediaStreamAudioTracks(self._currentPlayOptions.mediaSource).filter(function (stream) { - return isAudioStreamSupported(stream, profile); + return getMediaStreamAudioTracks(this._currentPlayOptions.mediaSource).filter((stream) => { + return this.isAudioStreamSupported(stream, profile); }); } - self.setAudioStreamIndex = function (index) { - - var streams = getSupportedAudioStreams(); + setAudioStreamIndex(index) { + const streams = this.getSupportedAudioStreams(); if (streams.length < 2) { // If there's only one supported stream then trust that the player will handle it on it's own return; } - var audioIndex = -1; - var i; - var length; - var stream; - - for (i = 0, length = streams.length; i < length; i++) { - stream = streams[i]; + let audioIndex = -1; + for (const stream of streams) { audioIndex++; if (stream.Index === index) { @@ -728,208 +629,272 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa return; } - var elem = self._mediaElement; + const elem = this.#mediaElement; if (!elem) { return; } - // https://msdn.microsoft.com/en-us/library/hh772507(v=vs.85).aspx + // https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/audioTracks - var elemAudioTracks = elem.audioTracks || []; - console.debug('found ' + elemAudioTracks.length + ' audio tracks'); - - for (i = 0, length = elemAudioTracks.length; i < length; i++) { + /** + * @type {ArrayLike|any[]} + */ + const elemAudioTracks = elem.audioTracks || []; + console.debug(`found ${elemAudioTracks.length} audio tracks`); + for (const [i, audioTrack] of Array.from(elemAudioTracks).entries()) { if (audioIndex === i) { - console.debug('setting audio track ' + i + ' to enabled'); - elemAudioTracks[i].enabled = true; + console.debug(`setting audio track ${i} to enabled`); + audioTrack.enabled = true; } else { - console.debug('setting audio track ' + i + ' to disabled'); - elemAudioTracks[i].enabled = false; + console.debug(`setting audio track ${i} to disabled`); + audioTrack.enabled = false; } } - }; + } - self.stop = function (destroyPlayer) { - var elem = self._mediaElement; - var src = self._currentSrc; + stop(destroyPlayer) { + const elem = this.#mediaElement; + const src = this.#currentSrc; if (elem) { if (src) { elem.pause(); } - htmlMediaHelper.onEndedInternal(self, elem, onError); + onEndedInternal(this, elem, this.onError); if (destroyPlayer) { - self.destroy(); + this.destroy(); } } - destroyCustomTrack(elem); + this.destroyCustomTrack(elem); return Promise.resolve(); - }; + } - self.destroy = function () { - htmlMediaHelper.destroyHlsPlayer(self); - htmlMediaHelper.destroyFlvPlayer(self); + destroy() { + destroyHlsPlayer(this); + destroyFlvPlayer(this); appRouter.setTransparency('none'); - var videoElement = self._mediaElement; + const videoElement = this.#mediaElement; if (videoElement) { - self._mediaElement = null; + this.#mediaElement = null; - destroyCustomTrack(videoElement); - videoElement.removeEventListener('timeupdate', onTimeUpdate); - videoElement.removeEventListener('ended', onEnded); - videoElement.removeEventListener('volumechange', onVolumeChange); - videoElement.removeEventListener('pause', onPause); - videoElement.removeEventListener('playing', onPlaying); - videoElement.removeEventListener('play', onPlay); - videoElement.removeEventListener('click', onClick); - videoElement.removeEventListener('dblclick', onDblClick); - videoElement.removeEventListener('waiting', onWaiting); + this.destroyCustomTrack(videoElement); + videoElement.removeEventListener('timeupdate', this.onTimeUpdate); + videoElement.removeEventListener('ended', this.onEnded); + videoElement.removeEventListener('volumechange', this.onVolumeChange); + videoElement.removeEventListener('pause', this.onPause); + videoElement.removeEventListener('playing', this.onPlaying); + videoElement.removeEventListener('play', this.onPlay); + videoElement.removeEventListener('click', this.onClick); + videoElement.removeEventListener('dblclick', this.onDblClick); + videoElement.removeEventListener('waiting', this.onWaiting); videoElement.parentNode.removeChild(videoElement); } - var dlg = videoDialog; + const dlg = this.#videoDialog; if (dlg) { - videoDialog = null; + this.#videoDialog = null; dlg.parentNode.removeChild(dlg); } if (screenfull.isEnabled) { screenfull.exit(); + } else { + // iOS Safari + if (document.webkitIsFullScreen && document.webkitCancelFullscreen) { + document.webkitCancelFullscreen(); + } } + } + + /** + * @private + * @param e {Event} The event received from the `