From 721945a8c706be229262dc596a22aa46ba07e8f6 Mon Sep 17 00:00:00 2001 From: p0psicles Date: Wed, 9 Mar 2022 15:13:51 +0100 Subject: [PATCH 1/9] Add keep-alive to vue router router --- themes-default/slim/src/components/app.vue | 4 +++- themes-default/slim/src/router/routes.js | 6 ++++-- themes/dark/assets/js/medusa-runtime.js | 6 +++--- themes/light/assets/js/medusa-runtime.js | 6 +++--- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/themes-default/slim/src/components/app.vue b/themes-default/slim/src/components/app.vue index 7da47022c2..0d468f12f1 100644 --- a/themes-default/slim/src/components/app.vue +++ b/themes-default/slim/src/components/app.vue @@ -2,7 +2,9 @@

{{ $route.meta.header }}

- + + +
diff --git a/themes-default/slim/src/router/routes.js b/themes-default/slim/src/router/routes.js index e2bc962605..2360025d4f 100644 --- a/themes-default/slim/src/router/routes.js +++ b/themes-default/slim/src/router/routes.js @@ -249,7 +249,8 @@ const addShowRoutes = [ title: 'Add Existing Shows', header: 'Add Existing Shows', topMenu: 'home', - converted: true + converted: true, + nocache: true }, component: () => import('../components/new-shows-existing.vue') }, @@ -260,7 +261,8 @@ const addShowRoutes = [ title: 'Add New Show', header: 'Add New Show', topMenu: 'home', - converted: true + converted: true, + nocache: true }, props: route => ({ ...route.params }), component: () => import('../components/new-show.vue') diff --git a/themes/dark/assets/js/medusa-runtime.js b/themes/dark/assets/js/medusa-runtime.js index 0cb0f27cb3..b14e04e07a 100644 --- a/themes/dark/assets/js/medusa-runtime.js +++ b/themes/dark/assets/js/medusa-runtime.js @@ -70,7 +70,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n//\n//\n//\n//\n//\n//\n//\n//\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({\n name: 'app'\n});\n\n//# sourceURL=webpack://slim/./src/components/app.vue?./node_modules/babel-loader/lib/index.js??clonedRuleSet-1%5B0%5D.rules%5B0%5D!./node_modules/vue-loader/lib/index.js??vue-loader-options"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({\n name: 'app'\n});\n\n//# sourceURL=webpack://slim/./src/components/app.vue?./node_modules/babel-loader/lib/index.js??clonedRuleSet-1%5B0%5D.rules%5B0%5D!./node_modules/vue-loader/lib/index.js??vue-loader-options"); /***/ }), @@ -1324,7 +1324,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _sub_menus__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./sub-menus */ \"./src/router/sub-menus.js\");\n\n/** @type {import('.').Route[]} */\n\nconst homeRoutes = [{\n path: '/home',\n name: 'home',\n meta: {\n title: 'Home',\n topMenu: 'home',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/home.vue */ \"./src/components/home.vue\"))\n}, {\n path: '/home/editShow',\n name: 'editShow',\n meta: {\n topMenu: 'home',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.showSubMenu,\n converted: true,\n nocache: true // Use this flag, to have the router-view use :key=\"$route.fullPath\"\n\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/edit-show.vue */ \"./src/components/edit-show.vue\"))\n}, {\n path: '/home/displayShow',\n name: 'show',\n meta: {\n topMenu: 'home',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.showSubMenu,\n converted: true,\n nocache: true // Use this flag, to have the router-view use :key=\"$route.fullPath\"\n\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/display-show.vue */ \"./src/components/display-show.vue\"))\n}, {\n path: '/home/snatchSelection',\n name: 'snatchSelection',\n meta: {\n topMenu: 'home',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.showSubMenu,\n converted: true,\n nocache: true // Use this flag, to have the router-view use :key=\"$route.fullPath\"\n\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/snatch-selection.vue */ \"./src/components/snatch-selection.vue\"))\n}, {\n path: '/home/testRename',\n name: 'testRename',\n meta: {\n topMenu: 'home',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.showSubMenu,\n title: 'Preview Rename',\n header: 'Preview Rename',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/test-rename.vue */ \"./src/components/test-rename.vue\"))\n}, {\n path: '/home/postprocess',\n name: 'postprocess',\n meta: {\n title: 'Manual Post-Processing',\n header: 'Manual Post-Processing',\n topMenu: 'home',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/manual-post-process.vue */ \"./src/components/manual-post-process.vue\"))\n}, {\n path: '/home/status',\n name: 'status',\n meta: {\n title: 'Status',\n topMenu: 'system',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/status.vue */ \"./src/components/status.vue\"))\n}, {\n path: '/home/restart',\n name: 'restart',\n meta: {\n title: 'Restarting...',\n header: 'Performing Restart',\n topMenu: 'system',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/restart.vue */ \"./src/components/restart.vue\"))\n}, {\n path: '/home/shutdown',\n name: 'shutdown',\n meta: {\n header: 'Shutting down',\n topMenu: 'system',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/restart.vue */ \"./src/components/restart.vue\")),\n props: {\n shutdown: true\n }\n}, {\n path: '/home/update',\n name: 'update',\n meta: {\n header: 'Update Medusa',\n topMenu: 'system',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/update.vue */ \"./src/components/update.vue\"))\n}];\n/** @type {import('.').Route[]} */\n\nconst configRoutes = [{\n path: '/config',\n name: 'config',\n meta: {\n title: 'Help & Info',\n header: 'Medusa Configuration',\n topMenu: 'config',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.configSubMenu,\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/config.vue */ \"./src/components/config.vue\"))\n}, {\n path: '/config/anime',\n name: 'configAnime',\n meta: {\n title: 'Config - Anime',\n header: 'Anime',\n topMenu: 'config',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.configSubMenu,\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/config-anime.vue */ \"./src/components/config-anime.vue\"))\n}, {\n path: '/config/backuprestore',\n name: 'configBackupRestore',\n meta: {\n title: 'Config - Backup/Restore',\n header: 'Backup/Restore',\n topMenu: 'config',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.configSubMenu,\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/config-backup-restore.vue */ \"./src/components/config-backup-restore.vue\"))\n}, {\n path: '/config/general',\n name: 'configGeneral',\n meta: {\n title: 'Config - General',\n header: 'General Configuration',\n topMenu: 'config',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.configSubMenu,\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/config-general.vue */ \"./src/components/config-general.vue\"))\n}, {\n path: '/config/notifications',\n name: 'configNotifications',\n meta: {\n title: 'Config - Notifications',\n header: 'Notifications',\n topMenu: 'config',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.configSubMenu,\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/config-notifications.vue */ \"./src/components/config-notifications.vue\"))\n}, {\n path: '/config/postProcessing',\n name: 'configPostProcessing',\n meta: {\n title: 'Config - Post-Processing',\n header: 'Post-Processing',\n topMenu: 'config',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.configSubMenu,\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/config-post-processing.vue */ \"./src/components/config-post-processing.vue\"))\n}, {\n path: '/config/providers',\n name: 'configSearchProviders',\n meta: {\n title: 'Config - Providers',\n header: 'Search Providers',\n topMenu: 'config',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.configSubMenu,\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/config-providers.vue */ \"./src/components/config-providers.vue\"))\n}, {\n path: '/config/search',\n name: 'configSearchSettings',\n meta: {\n title: 'Config - Episode Search',\n header: 'Search Settings',\n topMenu: 'config',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.configSubMenu,\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/config-search.vue */ \"./src/components/config-search.vue\"))\n}, {\n path: '/config/subtitles',\n name: 'configSubtitles',\n meta: {\n title: 'Config - Subtitles',\n header: 'Subtitles',\n topMenu: 'config',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.configSubMenu,\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/config-subtitles.vue */ \"./src/components/config-subtitles.vue\"))\n}];\n/** @type {import('.').Route[]} */\n\nconst addShowRoutes = [{\n path: '/addShows',\n name: 'addShows',\n meta: {\n title: 'Add Shows',\n header: 'Add Shows',\n topMenu: 'home',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/add-shows.vue */ \"./src/components/add-shows.vue\"))\n}, {\n path: '/addShows/existingShows',\n name: 'addExistingShows',\n meta: {\n title: 'Add Existing Shows',\n header: 'Add Existing Shows',\n topMenu: 'home',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/new-shows-existing.vue */ \"./src/components/new-shows-existing.vue\"))\n}, {\n path: '/addShows/newShow',\n name: 'addNewShow',\n meta: {\n title: 'Add New Show',\n header: 'Add New Show',\n topMenu: 'home',\n converted: true\n },\n props: route => ({ ...route.params\n }),\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/new-show.vue */ \"./src/components/new-show.vue\"))\n}];\n/** @type {import('.').Route} */\n\nconst loginRoute = {\n path: '/login',\n name: 'login',\n meta: {\n title: 'Login'\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/login.vue */ \"./src/components/login.vue\"))\n};\n/** @type {import('.').Route} */\n\nconst addRecommendedRoute = {\n path: '/addRecommended',\n name: 'addRecommended',\n meta: {\n title: 'Add Recommended Shows',\n header: 'Add Recommended Shows',\n topMenu: 'home',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/recommended.vue */ \"./src/components/recommended.vue\"))\n};\n/** @type {import('.').Route} */\n\nconst scheduleRoute = {\n path: '/schedule',\n name: 'schedule',\n meta: {\n title: 'Schedule',\n header: 'Schedule',\n topMenu: 'schedule',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/schedule.vue */ \"./src/components/schedule.vue\"))\n};\n/** @type {import('.').Route} */\n\nconst historyRoute = {\n path: '/history',\n name: 'history',\n meta: {\n title: 'History',\n header: 'History',\n topMenu: 'history',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.historySubMenu,\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/history.vue */ \"./src/components/history.vue\"))\n};\n/** @type {import('.').Route} */\n\nconst downloadsRoute = {\n path: '/downloads',\n name: 'downloads',\n meta: {\n title: 'Downloads',\n header: 'Downloads',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/current-downloads.vue */ \"./src/components/current-downloads.vue\"))\n};\n/** @type {import('.').Route[]} */\n\nconst manageRoutes = [{\n path: '/manage',\n name: 'manage',\n meta: {\n title: 'Mass Update',\n topMenu: 'manage',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/manage-mass-update.vue */ \"./src/components/manage-mass-update.vue\")),\n props: true\n}, {\n path: '/manage/changeIndexer',\n name: 'manageChangeIndexer',\n meta: {\n title: 'Change show indexer',\n header: 'Change show indexer',\n topMenu: 'manage',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/change-indexer.vue */ \"./src/components/change-indexer.vue\"))\n}, {\n path: '/manage/backlogOverview',\n name: 'manageBacklogOverview',\n meta: {\n title: 'Backlog Overview',\n header: 'Backlog Overview',\n topMenu: 'manage',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/manage-backlog.vue */ \"./src/components/manage-backlog.vue\"))\n}, {\n path: '/manage/episodeStatuses',\n name: 'manageEpisodeOverview',\n meta: {\n title: 'Episode Overview',\n header: 'Episode Overview',\n topMenu: 'manage',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/manage-episode-status.vue */ \"./src/components/manage-episode-status.vue\"))\n}, {\n path: '/manage/failedDownloads',\n name: 'manageFailedDownloads',\n meta: {\n title: 'Failed Downloads',\n header: 'Failed Downloads',\n topMenu: 'manage',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/manage-failed-downloads.vue */ \"./src/components/manage-failed-downloads.vue\"))\n}, {\n path: '/manage/manageSearches',\n name: 'manageManageSearches',\n meta: {\n title: 'Manage Searches',\n header: 'Manage Searches',\n topMenu: 'manage',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/manage-searches.vue */ \"./src/components/manage-searches.vue\"))\n}, {\n path: '/manage/massEdit',\n name: 'manageMassEdit',\n meta: {\n title: 'Mass Edit',\n topMenu: 'manage',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/manage-mass-edit.vue */ \"./src/components/manage-mass-edit.vue\")),\n props: true\n}, {\n path: '/manage/subtitleMissed',\n name: 'manageSubtitleMissed',\n meta: {\n title: 'Missing Subtitles',\n header: 'Missing Subtitles',\n topMenu: 'manage',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/manage-missing-subtitles.vue */ \"./src/components/manage-missing-subtitles.vue\"))\n}];\n/** @type {import('.').Route[]} */\n\nconst errorLogsRoutes = [{\n path: '/errorlogs',\n name: 'errorlogs',\n meta: {\n title: 'Logs & Errors',\n topMenu: 'system',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.errorlogsSubMenu,\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/log-reporter.vue */ \"./src/components/log-reporter.vue\")),\n props: true\n}, {\n path: '/errorlogs/viewlog',\n name: 'viewlog',\n meta: {\n title: 'Logs',\n header: 'Log File',\n topMenu: 'system',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/logs.vue */ \"./src/components/logs.vue\"))\n}];\n/** @type {import('.').Route} */\n\nconst newsRoute = {\n path: '/news',\n name: 'news',\n meta: {\n title: 'News',\n header: 'News',\n topMenu: 'system'\n }\n};\n/** @type {import('.').Route} */\n\nconst changesRoute = {\n path: '/changes',\n name: 'changes',\n meta: {\n title: 'Changelog',\n header: 'Changelog',\n topMenu: 'system'\n }\n};\n/** @type {import('.').Route} */\n\nconst ircRoute = {\n path: '/IRC',\n name: 'IRC',\n meta: {\n title: 'IRC',\n topMenu: 'system',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/irc.vue */ \"./src/components/irc.vue\"))\n};\n/** @type {import('.').Route} */\n\nconst notFoundRoute = {\n path: '/not-found',\n name: 'not-found',\n meta: {\n title: '404',\n header: '404 - page not found'\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/http/404.vue */ \"./src/components/http/404.vue\"))\n}; // @NOTE: Redirect can only be added once all routes are vue\n\n/*\n/** @type {import('.').Route} *-/\nconst notFoundRedirect = {\n path: '*',\n redirect: '/not-found'\n};\n*/\n\n/** @type {import('.').Route[]} */\n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ([...homeRoutes, ...configRoutes, ...addShowRoutes, loginRoute, addRecommendedRoute, scheduleRoute, historyRoute, downloadsRoute, ...manageRoutes, ...errorLogsRoutes, newsRoute, changesRoute, ircRoute, notFoundRoute]);\n\n//# sourceURL=webpack://slim/./src/router/routes.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _sub_menus__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./sub-menus */ \"./src/router/sub-menus.js\");\n\n/** @type {import('.').Route[]} */\n\nconst homeRoutes = [{\n path: '/home',\n name: 'home',\n meta: {\n title: 'Home',\n topMenu: 'home',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/home.vue */ \"./src/components/home.vue\"))\n}, {\n path: '/home/editShow',\n name: 'editShow',\n meta: {\n topMenu: 'home',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.showSubMenu,\n converted: true,\n nocache: true // Use this flag, to have the router-view use :key=\"$route.fullPath\"\n\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/edit-show.vue */ \"./src/components/edit-show.vue\"))\n}, {\n path: '/home/displayShow',\n name: 'show',\n meta: {\n topMenu: 'home',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.showSubMenu,\n converted: true,\n nocache: true // Use this flag, to have the router-view use :key=\"$route.fullPath\"\n\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/display-show.vue */ \"./src/components/display-show.vue\"))\n}, {\n path: '/home/snatchSelection',\n name: 'snatchSelection',\n meta: {\n topMenu: 'home',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.showSubMenu,\n converted: true,\n nocache: true // Use this flag, to have the router-view use :key=\"$route.fullPath\"\n\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/snatch-selection.vue */ \"./src/components/snatch-selection.vue\"))\n}, {\n path: '/home/testRename',\n name: 'testRename',\n meta: {\n topMenu: 'home',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.showSubMenu,\n title: 'Preview Rename',\n header: 'Preview Rename',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/test-rename.vue */ \"./src/components/test-rename.vue\"))\n}, {\n path: '/home/postprocess',\n name: 'postprocess',\n meta: {\n title: 'Manual Post-Processing',\n header: 'Manual Post-Processing',\n topMenu: 'home',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/manual-post-process.vue */ \"./src/components/manual-post-process.vue\"))\n}, {\n path: '/home/status',\n name: 'status',\n meta: {\n title: 'Status',\n topMenu: 'system',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/status.vue */ \"./src/components/status.vue\"))\n}, {\n path: '/home/restart',\n name: 'restart',\n meta: {\n title: 'Restarting...',\n header: 'Performing Restart',\n topMenu: 'system',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/restart.vue */ \"./src/components/restart.vue\"))\n}, {\n path: '/home/shutdown',\n name: 'shutdown',\n meta: {\n header: 'Shutting down',\n topMenu: 'system',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/restart.vue */ \"./src/components/restart.vue\")),\n props: {\n shutdown: true\n }\n}, {\n path: '/home/update',\n name: 'update',\n meta: {\n header: 'Update Medusa',\n topMenu: 'system',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/update.vue */ \"./src/components/update.vue\"))\n}];\n/** @type {import('.').Route[]} */\n\nconst configRoutes = [{\n path: '/config',\n name: 'config',\n meta: {\n title: 'Help & Info',\n header: 'Medusa Configuration',\n topMenu: 'config',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.configSubMenu,\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/config.vue */ \"./src/components/config.vue\"))\n}, {\n path: '/config/anime',\n name: 'configAnime',\n meta: {\n title: 'Config - Anime',\n header: 'Anime',\n topMenu: 'config',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.configSubMenu,\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/config-anime.vue */ \"./src/components/config-anime.vue\"))\n}, {\n path: '/config/backuprestore',\n name: 'configBackupRestore',\n meta: {\n title: 'Config - Backup/Restore',\n header: 'Backup/Restore',\n topMenu: 'config',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.configSubMenu,\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/config-backup-restore.vue */ \"./src/components/config-backup-restore.vue\"))\n}, {\n path: '/config/general',\n name: 'configGeneral',\n meta: {\n title: 'Config - General',\n header: 'General Configuration',\n topMenu: 'config',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.configSubMenu,\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/config-general.vue */ \"./src/components/config-general.vue\"))\n}, {\n path: '/config/notifications',\n name: 'configNotifications',\n meta: {\n title: 'Config - Notifications',\n header: 'Notifications',\n topMenu: 'config',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.configSubMenu,\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/config-notifications.vue */ \"./src/components/config-notifications.vue\"))\n}, {\n path: '/config/postProcessing',\n name: 'configPostProcessing',\n meta: {\n title: 'Config - Post-Processing',\n header: 'Post-Processing',\n topMenu: 'config',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.configSubMenu,\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/config-post-processing.vue */ \"./src/components/config-post-processing.vue\"))\n}, {\n path: '/config/providers',\n name: 'configSearchProviders',\n meta: {\n title: 'Config - Providers',\n header: 'Search Providers',\n topMenu: 'config',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.configSubMenu,\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/config-providers.vue */ \"./src/components/config-providers.vue\"))\n}, {\n path: '/config/search',\n name: 'configSearchSettings',\n meta: {\n title: 'Config - Episode Search',\n header: 'Search Settings',\n topMenu: 'config',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.configSubMenu,\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/config-search.vue */ \"./src/components/config-search.vue\"))\n}, {\n path: '/config/subtitles',\n name: 'configSubtitles',\n meta: {\n title: 'Config - Subtitles',\n header: 'Subtitles',\n topMenu: 'config',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.configSubMenu,\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/config-subtitles.vue */ \"./src/components/config-subtitles.vue\"))\n}];\n/** @type {import('.').Route[]} */\n\nconst addShowRoutes = [{\n path: '/addShows',\n name: 'addShows',\n meta: {\n title: 'Add Shows',\n header: 'Add Shows',\n topMenu: 'home',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/add-shows.vue */ \"./src/components/add-shows.vue\"))\n}, {\n path: '/addShows/existingShows',\n name: 'addExistingShows',\n meta: {\n title: 'Add Existing Shows',\n header: 'Add Existing Shows',\n topMenu: 'home',\n converted: true,\n nocache: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/new-shows-existing.vue */ \"./src/components/new-shows-existing.vue\"))\n}, {\n path: '/addShows/newShow',\n name: 'addNewShow',\n meta: {\n title: 'Add New Show',\n header: 'Add New Show',\n topMenu: 'home',\n converted: true,\n nocache: true\n },\n props: route => ({ ...route.params\n }),\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/new-show.vue */ \"./src/components/new-show.vue\"))\n}];\n/** @type {import('.').Route} */\n\nconst loginRoute = {\n path: '/login',\n name: 'login',\n meta: {\n title: 'Login'\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/login.vue */ \"./src/components/login.vue\"))\n};\n/** @type {import('.').Route} */\n\nconst addRecommendedRoute = {\n path: '/addRecommended',\n name: 'addRecommended',\n meta: {\n title: 'Add Recommended Shows',\n header: 'Add Recommended Shows',\n topMenu: 'home',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/recommended.vue */ \"./src/components/recommended.vue\"))\n};\n/** @type {import('.').Route} */\n\nconst scheduleRoute = {\n path: '/schedule',\n name: 'schedule',\n meta: {\n title: 'Schedule',\n header: 'Schedule',\n topMenu: 'schedule',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/schedule.vue */ \"./src/components/schedule.vue\"))\n};\n/** @type {import('.').Route} */\n\nconst historyRoute = {\n path: '/history',\n name: 'history',\n meta: {\n title: 'History',\n header: 'History',\n topMenu: 'history',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.historySubMenu,\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/history.vue */ \"./src/components/history.vue\"))\n};\n/** @type {import('.').Route} */\n\nconst downloadsRoute = {\n path: '/downloads',\n name: 'downloads',\n meta: {\n title: 'Downloads',\n header: 'Downloads',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/current-downloads.vue */ \"./src/components/current-downloads.vue\"))\n};\n/** @type {import('.').Route[]} */\n\nconst manageRoutes = [{\n path: '/manage',\n name: 'manage',\n meta: {\n title: 'Mass Update',\n topMenu: 'manage',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/manage-mass-update.vue */ \"./src/components/manage-mass-update.vue\")),\n props: true\n}, {\n path: '/manage/changeIndexer',\n name: 'manageChangeIndexer',\n meta: {\n title: 'Change show indexer',\n header: 'Change show indexer',\n topMenu: 'manage',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/change-indexer.vue */ \"./src/components/change-indexer.vue\"))\n}, {\n path: '/manage/backlogOverview',\n name: 'manageBacklogOverview',\n meta: {\n title: 'Backlog Overview',\n header: 'Backlog Overview',\n topMenu: 'manage',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/manage-backlog.vue */ \"./src/components/manage-backlog.vue\"))\n}, {\n path: '/manage/episodeStatuses',\n name: 'manageEpisodeOverview',\n meta: {\n title: 'Episode Overview',\n header: 'Episode Overview',\n topMenu: 'manage',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/manage-episode-status.vue */ \"./src/components/manage-episode-status.vue\"))\n}, {\n path: '/manage/failedDownloads',\n name: 'manageFailedDownloads',\n meta: {\n title: 'Failed Downloads',\n header: 'Failed Downloads',\n topMenu: 'manage',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/manage-failed-downloads.vue */ \"./src/components/manage-failed-downloads.vue\"))\n}, {\n path: '/manage/manageSearches',\n name: 'manageManageSearches',\n meta: {\n title: 'Manage Searches',\n header: 'Manage Searches',\n topMenu: 'manage',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/manage-searches.vue */ \"./src/components/manage-searches.vue\"))\n}, {\n path: '/manage/massEdit',\n name: 'manageMassEdit',\n meta: {\n title: 'Mass Edit',\n topMenu: 'manage',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/manage-mass-edit.vue */ \"./src/components/manage-mass-edit.vue\")),\n props: true\n}, {\n path: '/manage/subtitleMissed',\n name: 'manageSubtitleMissed',\n meta: {\n title: 'Missing Subtitles',\n header: 'Missing Subtitles',\n topMenu: 'manage',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/manage-missing-subtitles.vue */ \"./src/components/manage-missing-subtitles.vue\"))\n}];\n/** @type {import('.').Route[]} */\n\nconst errorLogsRoutes = [{\n path: '/errorlogs',\n name: 'errorlogs',\n meta: {\n title: 'Logs & Errors',\n topMenu: 'system',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.errorlogsSubMenu,\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/log-reporter.vue */ \"./src/components/log-reporter.vue\")),\n props: true\n}, {\n path: '/errorlogs/viewlog',\n name: 'viewlog',\n meta: {\n title: 'Logs',\n header: 'Log File',\n topMenu: 'system',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/logs.vue */ \"./src/components/logs.vue\"))\n}];\n/** @type {import('.').Route} */\n\nconst newsRoute = {\n path: '/news',\n name: 'news',\n meta: {\n title: 'News',\n header: 'News',\n topMenu: 'system'\n }\n};\n/** @type {import('.').Route} */\n\nconst changesRoute = {\n path: '/changes',\n name: 'changes',\n meta: {\n title: 'Changelog',\n header: 'Changelog',\n topMenu: 'system'\n }\n};\n/** @type {import('.').Route} */\n\nconst ircRoute = {\n path: '/IRC',\n name: 'IRC',\n meta: {\n title: 'IRC',\n topMenu: 'system',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/irc.vue */ \"./src/components/irc.vue\"))\n};\n/** @type {import('.').Route} */\n\nconst notFoundRoute = {\n path: '/not-found',\n name: 'not-found',\n meta: {\n title: '404',\n header: '404 - page not found'\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/http/404.vue */ \"./src/components/http/404.vue\"))\n}; // @NOTE: Redirect can only be added once all routes are vue\n\n/*\n/** @type {import('.').Route} *-/\nconst notFoundRedirect = {\n path: '*',\n redirect: '/not-found'\n};\n*/\n\n/** @type {import('.').Route[]} */\n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ([...homeRoutes, ...configRoutes, ...addShowRoutes, loginRoute, addRecommendedRoute, scheduleRoute, historyRoute, downloadsRoute, ...manageRoutes, ...errorLogsRoutes, newsRoute, changesRoute, ircRoute, notFoundRoute]);\n\n//# sourceURL=webpack://slim/./src/router/routes.js?"); /***/ }), @@ -7209,7 +7209,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"render\": () => (/* binding */ render),\n/* harmony export */ \"staticRenderFns\": () => (/* binding */ staticRenderFns)\n/* harmony export */ });\nvar render = function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\n \"div\",\n { attrs: { id: \"app\" } },\n [\n _c(\"vue-snotify\"),\n _vm._v(\" \"),\n _vm.$route.meta.header\n ? _c(\"h1\", { staticClass: \"header\" }, [\n _vm._v(_vm._s(_vm.$route.meta.header))\n ])\n : _vm._e(),\n _vm._v(\" \"),\n _c(\"router-view\", {\n key: _vm.$route.meta.nocache ? _vm.$route.fullPath : _vm.$route.name\n })\n ],\n 1\n )\n}\nvar staticRenderFns = []\nrender._withStripped = true\n\n\n\n//# sourceURL=webpack://slim/./src/components/app.vue?./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/vue-loader/lib/index.js??vue-loader-options"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"render\": () => (/* binding */ render),\n/* harmony export */ \"staticRenderFns\": () => (/* binding */ staticRenderFns)\n/* harmony export */ });\nvar render = function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\n \"div\",\n { attrs: { id: \"app\" } },\n [\n _c(\"vue-snotify\"),\n _vm._v(\" \"),\n _vm.$route.meta.header\n ? _c(\"h1\", { staticClass: \"header\" }, [\n _vm._v(_vm._s(_vm.$route.meta.header))\n ])\n : _vm._e(),\n _vm._v(\" \"),\n _c(\n \"keep-alive\",\n [\n _c(\"router-view\", {\n key: _vm.$route.meta.nocache\n ? _vm.$route.fullPath + \"#\" + Date.now()\n : _vm.$route.name\n })\n ],\n 1\n )\n ],\n 1\n )\n}\nvar staticRenderFns = []\nrender._withStripped = true\n\n\n\n//# sourceURL=webpack://slim/./src/components/app.vue?./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/vue-loader/lib/index.js??vue-loader-options"); /***/ }), diff --git a/themes/light/assets/js/medusa-runtime.js b/themes/light/assets/js/medusa-runtime.js index 0cb0f27cb3..b14e04e07a 100644 --- a/themes/light/assets/js/medusa-runtime.js +++ b/themes/light/assets/js/medusa-runtime.js @@ -70,7 +70,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n//\n//\n//\n//\n//\n//\n//\n//\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({\n name: 'app'\n});\n\n//# sourceURL=webpack://slim/./src/components/app.vue?./node_modules/babel-loader/lib/index.js??clonedRuleSet-1%5B0%5D.rules%5B0%5D!./node_modules/vue-loader/lib/index.js??vue-loader-options"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({\n name: 'app'\n});\n\n//# sourceURL=webpack://slim/./src/components/app.vue?./node_modules/babel-loader/lib/index.js??clonedRuleSet-1%5B0%5D.rules%5B0%5D!./node_modules/vue-loader/lib/index.js??vue-loader-options"); /***/ }), @@ -1324,7 +1324,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _sub_menus__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./sub-menus */ \"./src/router/sub-menus.js\");\n\n/** @type {import('.').Route[]} */\n\nconst homeRoutes = [{\n path: '/home',\n name: 'home',\n meta: {\n title: 'Home',\n topMenu: 'home',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/home.vue */ \"./src/components/home.vue\"))\n}, {\n path: '/home/editShow',\n name: 'editShow',\n meta: {\n topMenu: 'home',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.showSubMenu,\n converted: true,\n nocache: true // Use this flag, to have the router-view use :key=\"$route.fullPath\"\n\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/edit-show.vue */ \"./src/components/edit-show.vue\"))\n}, {\n path: '/home/displayShow',\n name: 'show',\n meta: {\n topMenu: 'home',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.showSubMenu,\n converted: true,\n nocache: true // Use this flag, to have the router-view use :key=\"$route.fullPath\"\n\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/display-show.vue */ \"./src/components/display-show.vue\"))\n}, {\n path: '/home/snatchSelection',\n name: 'snatchSelection',\n meta: {\n topMenu: 'home',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.showSubMenu,\n converted: true,\n nocache: true // Use this flag, to have the router-view use :key=\"$route.fullPath\"\n\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/snatch-selection.vue */ \"./src/components/snatch-selection.vue\"))\n}, {\n path: '/home/testRename',\n name: 'testRename',\n meta: {\n topMenu: 'home',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.showSubMenu,\n title: 'Preview Rename',\n header: 'Preview Rename',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/test-rename.vue */ \"./src/components/test-rename.vue\"))\n}, {\n path: '/home/postprocess',\n name: 'postprocess',\n meta: {\n title: 'Manual Post-Processing',\n header: 'Manual Post-Processing',\n topMenu: 'home',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/manual-post-process.vue */ \"./src/components/manual-post-process.vue\"))\n}, {\n path: '/home/status',\n name: 'status',\n meta: {\n title: 'Status',\n topMenu: 'system',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/status.vue */ \"./src/components/status.vue\"))\n}, {\n path: '/home/restart',\n name: 'restart',\n meta: {\n title: 'Restarting...',\n header: 'Performing Restart',\n topMenu: 'system',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/restart.vue */ \"./src/components/restart.vue\"))\n}, {\n path: '/home/shutdown',\n name: 'shutdown',\n meta: {\n header: 'Shutting down',\n topMenu: 'system',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/restart.vue */ \"./src/components/restart.vue\")),\n props: {\n shutdown: true\n }\n}, {\n path: '/home/update',\n name: 'update',\n meta: {\n header: 'Update Medusa',\n topMenu: 'system',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/update.vue */ \"./src/components/update.vue\"))\n}];\n/** @type {import('.').Route[]} */\n\nconst configRoutes = [{\n path: '/config',\n name: 'config',\n meta: {\n title: 'Help & Info',\n header: 'Medusa Configuration',\n topMenu: 'config',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.configSubMenu,\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/config.vue */ \"./src/components/config.vue\"))\n}, {\n path: '/config/anime',\n name: 'configAnime',\n meta: {\n title: 'Config - Anime',\n header: 'Anime',\n topMenu: 'config',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.configSubMenu,\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/config-anime.vue */ \"./src/components/config-anime.vue\"))\n}, {\n path: '/config/backuprestore',\n name: 'configBackupRestore',\n meta: {\n title: 'Config - Backup/Restore',\n header: 'Backup/Restore',\n topMenu: 'config',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.configSubMenu,\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/config-backup-restore.vue */ \"./src/components/config-backup-restore.vue\"))\n}, {\n path: '/config/general',\n name: 'configGeneral',\n meta: {\n title: 'Config - General',\n header: 'General Configuration',\n topMenu: 'config',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.configSubMenu,\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/config-general.vue */ \"./src/components/config-general.vue\"))\n}, {\n path: '/config/notifications',\n name: 'configNotifications',\n meta: {\n title: 'Config - Notifications',\n header: 'Notifications',\n topMenu: 'config',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.configSubMenu,\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/config-notifications.vue */ \"./src/components/config-notifications.vue\"))\n}, {\n path: '/config/postProcessing',\n name: 'configPostProcessing',\n meta: {\n title: 'Config - Post-Processing',\n header: 'Post-Processing',\n topMenu: 'config',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.configSubMenu,\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/config-post-processing.vue */ \"./src/components/config-post-processing.vue\"))\n}, {\n path: '/config/providers',\n name: 'configSearchProviders',\n meta: {\n title: 'Config - Providers',\n header: 'Search Providers',\n topMenu: 'config',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.configSubMenu,\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/config-providers.vue */ \"./src/components/config-providers.vue\"))\n}, {\n path: '/config/search',\n name: 'configSearchSettings',\n meta: {\n title: 'Config - Episode Search',\n header: 'Search Settings',\n topMenu: 'config',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.configSubMenu,\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/config-search.vue */ \"./src/components/config-search.vue\"))\n}, {\n path: '/config/subtitles',\n name: 'configSubtitles',\n meta: {\n title: 'Config - Subtitles',\n header: 'Subtitles',\n topMenu: 'config',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.configSubMenu,\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/config-subtitles.vue */ \"./src/components/config-subtitles.vue\"))\n}];\n/** @type {import('.').Route[]} */\n\nconst addShowRoutes = [{\n path: '/addShows',\n name: 'addShows',\n meta: {\n title: 'Add Shows',\n header: 'Add Shows',\n topMenu: 'home',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/add-shows.vue */ \"./src/components/add-shows.vue\"))\n}, {\n path: '/addShows/existingShows',\n name: 'addExistingShows',\n meta: {\n title: 'Add Existing Shows',\n header: 'Add Existing Shows',\n topMenu: 'home',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/new-shows-existing.vue */ \"./src/components/new-shows-existing.vue\"))\n}, {\n path: '/addShows/newShow',\n name: 'addNewShow',\n meta: {\n title: 'Add New Show',\n header: 'Add New Show',\n topMenu: 'home',\n converted: true\n },\n props: route => ({ ...route.params\n }),\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/new-show.vue */ \"./src/components/new-show.vue\"))\n}];\n/** @type {import('.').Route} */\n\nconst loginRoute = {\n path: '/login',\n name: 'login',\n meta: {\n title: 'Login'\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/login.vue */ \"./src/components/login.vue\"))\n};\n/** @type {import('.').Route} */\n\nconst addRecommendedRoute = {\n path: '/addRecommended',\n name: 'addRecommended',\n meta: {\n title: 'Add Recommended Shows',\n header: 'Add Recommended Shows',\n topMenu: 'home',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/recommended.vue */ \"./src/components/recommended.vue\"))\n};\n/** @type {import('.').Route} */\n\nconst scheduleRoute = {\n path: '/schedule',\n name: 'schedule',\n meta: {\n title: 'Schedule',\n header: 'Schedule',\n topMenu: 'schedule',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/schedule.vue */ \"./src/components/schedule.vue\"))\n};\n/** @type {import('.').Route} */\n\nconst historyRoute = {\n path: '/history',\n name: 'history',\n meta: {\n title: 'History',\n header: 'History',\n topMenu: 'history',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.historySubMenu,\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/history.vue */ \"./src/components/history.vue\"))\n};\n/** @type {import('.').Route} */\n\nconst downloadsRoute = {\n path: '/downloads',\n name: 'downloads',\n meta: {\n title: 'Downloads',\n header: 'Downloads',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/current-downloads.vue */ \"./src/components/current-downloads.vue\"))\n};\n/** @type {import('.').Route[]} */\n\nconst manageRoutes = [{\n path: '/manage',\n name: 'manage',\n meta: {\n title: 'Mass Update',\n topMenu: 'manage',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/manage-mass-update.vue */ \"./src/components/manage-mass-update.vue\")),\n props: true\n}, {\n path: '/manage/changeIndexer',\n name: 'manageChangeIndexer',\n meta: {\n title: 'Change show indexer',\n header: 'Change show indexer',\n topMenu: 'manage',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/change-indexer.vue */ \"./src/components/change-indexer.vue\"))\n}, {\n path: '/manage/backlogOverview',\n name: 'manageBacklogOverview',\n meta: {\n title: 'Backlog Overview',\n header: 'Backlog Overview',\n topMenu: 'manage',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/manage-backlog.vue */ \"./src/components/manage-backlog.vue\"))\n}, {\n path: '/manage/episodeStatuses',\n name: 'manageEpisodeOverview',\n meta: {\n title: 'Episode Overview',\n header: 'Episode Overview',\n topMenu: 'manage',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/manage-episode-status.vue */ \"./src/components/manage-episode-status.vue\"))\n}, {\n path: '/manage/failedDownloads',\n name: 'manageFailedDownloads',\n meta: {\n title: 'Failed Downloads',\n header: 'Failed Downloads',\n topMenu: 'manage',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/manage-failed-downloads.vue */ \"./src/components/manage-failed-downloads.vue\"))\n}, {\n path: '/manage/manageSearches',\n name: 'manageManageSearches',\n meta: {\n title: 'Manage Searches',\n header: 'Manage Searches',\n topMenu: 'manage',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/manage-searches.vue */ \"./src/components/manage-searches.vue\"))\n}, {\n path: '/manage/massEdit',\n name: 'manageMassEdit',\n meta: {\n title: 'Mass Edit',\n topMenu: 'manage',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/manage-mass-edit.vue */ \"./src/components/manage-mass-edit.vue\")),\n props: true\n}, {\n path: '/manage/subtitleMissed',\n name: 'manageSubtitleMissed',\n meta: {\n title: 'Missing Subtitles',\n header: 'Missing Subtitles',\n topMenu: 'manage',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/manage-missing-subtitles.vue */ \"./src/components/manage-missing-subtitles.vue\"))\n}];\n/** @type {import('.').Route[]} */\n\nconst errorLogsRoutes = [{\n path: '/errorlogs',\n name: 'errorlogs',\n meta: {\n title: 'Logs & Errors',\n topMenu: 'system',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.errorlogsSubMenu,\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/log-reporter.vue */ \"./src/components/log-reporter.vue\")),\n props: true\n}, {\n path: '/errorlogs/viewlog',\n name: 'viewlog',\n meta: {\n title: 'Logs',\n header: 'Log File',\n topMenu: 'system',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/logs.vue */ \"./src/components/logs.vue\"))\n}];\n/** @type {import('.').Route} */\n\nconst newsRoute = {\n path: '/news',\n name: 'news',\n meta: {\n title: 'News',\n header: 'News',\n topMenu: 'system'\n }\n};\n/** @type {import('.').Route} */\n\nconst changesRoute = {\n path: '/changes',\n name: 'changes',\n meta: {\n title: 'Changelog',\n header: 'Changelog',\n topMenu: 'system'\n }\n};\n/** @type {import('.').Route} */\n\nconst ircRoute = {\n path: '/IRC',\n name: 'IRC',\n meta: {\n title: 'IRC',\n topMenu: 'system',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/irc.vue */ \"./src/components/irc.vue\"))\n};\n/** @type {import('.').Route} */\n\nconst notFoundRoute = {\n path: '/not-found',\n name: 'not-found',\n meta: {\n title: '404',\n header: '404 - page not found'\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/http/404.vue */ \"./src/components/http/404.vue\"))\n}; // @NOTE: Redirect can only be added once all routes are vue\n\n/*\n/** @type {import('.').Route} *-/\nconst notFoundRedirect = {\n path: '*',\n redirect: '/not-found'\n};\n*/\n\n/** @type {import('.').Route[]} */\n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ([...homeRoutes, ...configRoutes, ...addShowRoutes, loginRoute, addRecommendedRoute, scheduleRoute, historyRoute, downloadsRoute, ...manageRoutes, ...errorLogsRoutes, newsRoute, changesRoute, ircRoute, notFoundRoute]);\n\n//# sourceURL=webpack://slim/./src/router/routes.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _sub_menus__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./sub-menus */ \"./src/router/sub-menus.js\");\n\n/** @type {import('.').Route[]} */\n\nconst homeRoutes = [{\n path: '/home',\n name: 'home',\n meta: {\n title: 'Home',\n topMenu: 'home',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/home.vue */ \"./src/components/home.vue\"))\n}, {\n path: '/home/editShow',\n name: 'editShow',\n meta: {\n topMenu: 'home',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.showSubMenu,\n converted: true,\n nocache: true // Use this flag, to have the router-view use :key=\"$route.fullPath\"\n\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/edit-show.vue */ \"./src/components/edit-show.vue\"))\n}, {\n path: '/home/displayShow',\n name: 'show',\n meta: {\n topMenu: 'home',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.showSubMenu,\n converted: true,\n nocache: true // Use this flag, to have the router-view use :key=\"$route.fullPath\"\n\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/display-show.vue */ \"./src/components/display-show.vue\"))\n}, {\n path: '/home/snatchSelection',\n name: 'snatchSelection',\n meta: {\n topMenu: 'home',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.showSubMenu,\n converted: true,\n nocache: true // Use this flag, to have the router-view use :key=\"$route.fullPath\"\n\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/snatch-selection.vue */ \"./src/components/snatch-selection.vue\"))\n}, {\n path: '/home/testRename',\n name: 'testRename',\n meta: {\n topMenu: 'home',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.showSubMenu,\n title: 'Preview Rename',\n header: 'Preview Rename',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/test-rename.vue */ \"./src/components/test-rename.vue\"))\n}, {\n path: '/home/postprocess',\n name: 'postprocess',\n meta: {\n title: 'Manual Post-Processing',\n header: 'Manual Post-Processing',\n topMenu: 'home',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/manual-post-process.vue */ \"./src/components/manual-post-process.vue\"))\n}, {\n path: '/home/status',\n name: 'status',\n meta: {\n title: 'Status',\n topMenu: 'system',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/status.vue */ \"./src/components/status.vue\"))\n}, {\n path: '/home/restart',\n name: 'restart',\n meta: {\n title: 'Restarting...',\n header: 'Performing Restart',\n topMenu: 'system',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/restart.vue */ \"./src/components/restart.vue\"))\n}, {\n path: '/home/shutdown',\n name: 'shutdown',\n meta: {\n header: 'Shutting down',\n topMenu: 'system',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/restart.vue */ \"./src/components/restart.vue\")),\n props: {\n shutdown: true\n }\n}, {\n path: '/home/update',\n name: 'update',\n meta: {\n header: 'Update Medusa',\n topMenu: 'system',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/update.vue */ \"./src/components/update.vue\"))\n}];\n/** @type {import('.').Route[]} */\n\nconst configRoutes = [{\n path: '/config',\n name: 'config',\n meta: {\n title: 'Help & Info',\n header: 'Medusa Configuration',\n topMenu: 'config',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.configSubMenu,\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/config.vue */ \"./src/components/config.vue\"))\n}, {\n path: '/config/anime',\n name: 'configAnime',\n meta: {\n title: 'Config - Anime',\n header: 'Anime',\n topMenu: 'config',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.configSubMenu,\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/config-anime.vue */ \"./src/components/config-anime.vue\"))\n}, {\n path: '/config/backuprestore',\n name: 'configBackupRestore',\n meta: {\n title: 'Config - Backup/Restore',\n header: 'Backup/Restore',\n topMenu: 'config',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.configSubMenu,\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/config-backup-restore.vue */ \"./src/components/config-backup-restore.vue\"))\n}, {\n path: '/config/general',\n name: 'configGeneral',\n meta: {\n title: 'Config - General',\n header: 'General Configuration',\n topMenu: 'config',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.configSubMenu,\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/config-general.vue */ \"./src/components/config-general.vue\"))\n}, {\n path: '/config/notifications',\n name: 'configNotifications',\n meta: {\n title: 'Config - Notifications',\n header: 'Notifications',\n topMenu: 'config',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.configSubMenu,\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/config-notifications.vue */ \"./src/components/config-notifications.vue\"))\n}, {\n path: '/config/postProcessing',\n name: 'configPostProcessing',\n meta: {\n title: 'Config - Post-Processing',\n header: 'Post-Processing',\n topMenu: 'config',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.configSubMenu,\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/config-post-processing.vue */ \"./src/components/config-post-processing.vue\"))\n}, {\n path: '/config/providers',\n name: 'configSearchProviders',\n meta: {\n title: 'Config - Providers',\n header: 'Search Providers',\n topMenu: 'config',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.configSubMenu,\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/config-providers.vue */ \"./src/components/config-providers.vue\"))\n}, {\n path: '/config/search',\n name: 'configSearchSettings',\n meta: {\n title: 'Config - Episode Search',\n header: 'Search Settings',\n topMenu: 'config',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.configSubMenu,\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/config-search.vue */ \"./src/components/config-search.vue\"))\n}, {\n path: '/config/subtitles',\n name: 'configSubtitles',\n meta: {\n title: 'Config - Subtitles',\n header: 'Subtitles',\n topMenu: 'config',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.configSubMenu,\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/config-subtitles.vue */ \"./src/components/config-subtitles.vue\"))\n}];\n/** @type {import('.').Route[]} */\n\nconst addShowRoutes = [{\n path: '/addShows',\n name: 'addShows',\n meta: {\n title: 'Add Shows',\n header: 'Add Shows',\n topMenu: 'home',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/add-shows.vue */ \"./src/components/add-shows.vue\"))\n}, {\n path: '/addShows/existingShows',\n name: 'addExistingShows',\n meta: {\n title: 'Add Existing Shows',\n header: 'Add Existing Shows',\n topMenu: 'home',\n converted: true,\n nocache: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/new-shows-existing.vue */ \"./src/components/new-shows-existing.vue\"))\n}, {\n path: '/addShows/newShow',\n name: 'addNewShow',\n meta: {\n title: 'Add New Show',\n header: 'Add New Show',\n topMenu: 'home',\n converted: true,\n nocache: true\n },\n props: route => ({ ...route.params\n }),\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/new-show.vue */ \"./src/components/new-show.vue\"))\n}];\n/** @type {import('.').Route} */\n\nconst loginRoute = {\n path: '/login',\n name: 'login',\n meta: {\n title: 'Login'\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/login.vue */ \"./src/components/login.vue\"))\n};\n/** @type {import('.').Route} */\n\nconst addRecommendedRoute = {\n path: '/addRecommended',\n name: 'addRecommended',\n meta: {\n title: 'Add Recommended Shows',\n header: 'Add Recommended Shows',\n topMenu: 'home',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/recommended.vue */ \"./src/components/recommended.vue\"))\n};\n/** @type {import('.').Route} */\n\nconst scheduleRoute = {\n path: '/schedule',\n name: 'schedule',\n meta: {\n title: 'Schedule',\n header: 'Schedule',\n topMenu: 'schedule',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/schedule.vue */ \"./src/components/schedule.vue\"))\n};\n/** @type {import('.').Route} */\n\nconst historyRoute = {\n path: '/history',\n name: 'history',\n meta: {\n title: 'History',\n header: 'History',\n topMenu: 'history',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.historySubMenu,\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/history.vue */ \"./src/components/history.vue\"))\n};\n/** @type {import('.').Route} */\n\nconst downloadsRoute = {\n path: '/downloads',\n name: 'downloads',\n meta: {\n title: 'Downloads',\n header: 'Downloads',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/current-downloads.vue */ \"./src/components/current-downloads.vue\"))\n};\n/** @type {import('.').Route[]} */\n\nconst manageRoutes = [{\n path: '/manage',\n name: 'manage',\n meta: {\n title: 'Mass Update',\n topMenu: 'manage',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/manage-mass-update.vue */ \"./src/components/manage-mass-update.vue\")),\n props: true\n}, {\n path: '/manage/changeIndexer',\n name: 'manageChangeIndexer',\n meta: {\n title: 'Change show indexer',\n header: 'Change show indexer',\n topMenu: 'manage',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/change-indexer.vue */ \"./src/components/change-indexer.vue\"))\n}, {\n path: '/manage/backlogOverview',\n name: 'manageBacklogOverview',\n meta: {\n title: 'Backlog Overview',\n header: 'Backlog Overview',\n topMenu: 'manage',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/manage-backlog.vue */ \"./src/components/manage-backlog.vue\"))\n}, {\n path: '/manage/episodeStatuses',\n name: 'manageEpisodeOverview',\n meta: {\n title: 'Episode Overview',\n header: 'Episode Overview',\n topMenu: 'manage',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/manage-episode-status.vue */ \"./src/components/manage-episode-status.vue\"))\n}, {\n path: '/manage/failedDownloads',\n name: 'manageFailedDownloads',\n meta: {\n title: 'Failed Downloads',\n header: 'Failed Downloads',\n topMenu: 'manage',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/manage-failed-downloads.vue */ \"./src/components/manage-failed-downloads.vue\"))\n}, {\n path: '/manage/manageSearches',\n name: 'manageManageSearches',\n meta: {\n title: 'Manage Searches',\n header: 'Manage Searches',\n topMenu: 'manage',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/manage-searches.vue */ \"./src/components/manage-searches.vue\"))\n}, {\n path: '/manage/massEdit',\n name: 'manageMassEdit',\n meta: {\n title: 'Mass Edit',\n topMenu: 'manage',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/manage-mass-edit.vue */ \"./src/components/manage-mass-edit.vue\")),\n props: true\n}, {\n path: '/manage/subtitleMissed',\n name: 'manageSubtitleMissed',\n meta: {\n title: 'Missing Subtitles',\n header: 'Missing Subtitles',\n topMenu: 'manage',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/manage-missing-subtitles.vue */ \"./src/components/manage-missing-subtitles.vue\"))\n}];\n/** @type {import('.').Route[]} */\n\nconst errorLogsRoutes = [{\n path: '/errorlogs',\n name: 'errorlogs',\n meta: {\n title: 'Logs & Errors',\n topMenu: 'system',\n subMenu: _sub_menus__WEBPACK_IMPORTED_MODULE_0__.errorlogsSubMenu,\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/log-reporter.vue */ \"./src/components/log-reporter.vue\")),\n props: true\n}, {\n path: '/errorlogs/viewlog',\n name: 'viewlog',\n meta: {\n title: 'Logs',\n header: 'Log File',\n topMenu: 'system',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/logs.vue */ \"./src/components/logs.vue\"))\n}];\n/** @type {import('.').Route} */\n\nconst newsRoute = {\n path: '/news',\n name: 'news',\n meta: {\n title: 'News',\n header: 'News',\n topMenu: 'system'\n }\n};\n/** @type {import('.').Route} */\n\nconst changesRoute = {\n path: '/changes',\n name: 'changes',\n meta: {\n title: 'Changelog',\n header: 'Changelog',\n topMenu: 'system'\n }\n};\n/** @type {import('.').Route} */\n\nconst ircRoute = {\n path: '/IRC',\n name: 'IRC',\n meta: {\n title: 'IRC',\n topMenu: 'system',\n converted: true\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/irc.vue */ \"./src/components/irc.vue\"))\n};\n/** @type {import('.').Route} */\n\nconst notFoundRoute = {\n path: '/not-found',\n name: 'not-found',\n meta: {\n title: '404',\n header: '404 - page not found'\n },\n component: () => Promise.resolve(/*! import() */).then(__webpack_require__.bind(__webpack_require__, /*! ../components/http/404.vue */ \"./src/components/http/404.vue\"))\n}; // @NOTE: Redirect can only be added once all routes are vue\n\n/*\n/** @type {import('.').Route} *-/\nconst notFoundRedirect = {\n path: '*',\n redirect: '/not-found'\n};\n*/\n\n/** @type {import('.').Route[]} */\n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ([...homeRoutes, ...configRoutes, ...addShowRoutes, loginRoute, addRecommendedRoute, scheduleRoute, historyRoute, downloadsRoute, ...manageRoutes, ...errorLogsRoutes, newsRoute, changesRoute, ircRoute, notFoundRoute]);\n\n//# sourceURL=webpack://slim/./src/router/routes.js?"); /***/ }), @@ -7209,7 +7209,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"render\": () => (/* binding */ render),\n/* harmony export */ \"staticRenderFns\": () => (/* binding */ staticRenderFns)\n/* harmony export */ });\nvar render = function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\n \"div\",\n { attrs: { id: \"app\" } },\n [\n _c(\"vue-snotify\"),\n _vm._v(\" \"),\n _vm.$route.meta.header\n ? _c(\"h1\", { staticClass: \"header\" }, [\n _vm._v(_vm._s(_vm.$route.meta.header))\n ])\n : _vm._e(),\n _vm._v(\" \"),\n _c(\"router-view\", {\n key: _vm.$route.meta.nocache ? _vm.$route.fullPath : _vm.$route.name\n })\n ],\n 1\n )\n}\nvar staticRenderFns = []\nrender._withStripped = true\n\n\n\n//# sourceURL=webpack://slim/./src/components/app.vue?./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/vue-loader/lib/index.js??vue-loader-options"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"render\": () => (/* binding */ render),\n/* harmony export */ \"staticRenderFns\": () => (/* binding */ staticRenderFns)\n/* harmony export */ });\nvar render = function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\n \"div\",\n { attrs: { id: \"app\" } },\n [\n _c(\"vue-snotify\"),\n _vm._v(\" \"),\n _vm.$route.meta.header\n ? _c(\"h1\", { staticClass: \"header\" }, [\n _vm._v(_vm._s(_vm.$route.meta.header))\n ])\n : _vm._e(),\n _vm._v(\" \"),\n _c(\n \"keep-alive\",\n [\n _c(\"router-view\", {\n key: _vm.$route.meta.nocache\n ? _vm.$route.fullPath + \"#\" + Date.now()\n : _vm.$route.name\n })\n ],\n 1\n )\n ],\n 1\n )\n}\nvar staticRenderFns = []\nrender._withStripped = true\n\n\n\n//# sourceURL=webpack://slim/./src/components/app.vue?./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/vue-loader/lib/index.js??vue-loader-options"); /***/ }), From fa7ec5c7413f95151e449539cffc3d5a223063c7 Mon Sep 17 00:00:00 2001 From: p0psicles Date: Wed, 9 Mar 2022 15:58:58 +0100 Subject: [PATCH 2/9] Fix router-links with webroot --- themes-default/slim/src/components/helpers/app-link.vue | 2 +- themes/dark/assets/js/medusa-runtime.js | 2 +- themes/light/assets/js/medusa-runtime.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/themes-default/slim/src/components/helpers/app-link.vue b/themes-default/slim/src/components/helpers/app-link.vue index d9c16c5bc2..596969bdc3 100644 --- a/themes-default/slim/src/components/helpers/app-link.vue +++ b/themes-default/slim/src/components/helpers/app-link.vue @@ -94,7 +94,7 @@ export default { return; } - const { route } = router.resolve(routerBase + computedHref); + const { route } = router.resolve('/' + computedHref); if (!route.name) { return; } diff --git a/themes/dark/assets/js/medusa-runtime.js b/themes/dark/assets/js/medusa-runtime.js index b14e04e07a..70e6dafefe 100644 --- a/themes/dark/assets/js/medusa-runtime.js +++ b/themes/dark/assets/js/medusa-runtime.js @@ -235,7 +235,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var vuex__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! vuex */ \"./node_modules/vuex/dist/vuex.esm.js\");\n/* harmony import */ var _router__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../router */ \"./src/router/index.js\");\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n\n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({\n name: 'app-link',\n props: {\n to: [String, Object],\n href: String,\n indexerId: {\n type: String\n },\n placeholder: {\n type: String,\n default: 'indexer-to-name'\n }\n },\n computed: { ...(0,vuex__WEBPACK_IMPORTED_MODULE_1__.mapState)({\n general: state => state.config.general\n }),\n ...(0,vuex__WEBPACK_IMPORTED_MODULE_1__.mapGetters)(['indexerIdToName']),\n\n indexerName() {\n // Returns `undefined` if not found\n const {\n indexerId,\n indexerIdToName\n } = this;\n return indexerIdToName(indexerId);\n },\n\n computedBase() {\n return document.querySelector('base').getAttribute('href');\n },\n\n computedHref() {\n const {\n href,\n indexerId,\n placeholder,\n indexerName\n } = this;\n\n if (indexerId && placeholder) {\n return href.replace(placeholder, indexerName);\n }\n\n return href;\n },\n\n isIRC() {\n if (!this.computedHref) {\n return;\n }\n\n return this.computedHref.startsWith('irc://');\n },\n\n isAbsolute() {\n const href = this.computedHref;\n\n if (!href) {\n return;\n }\n\n return /^[a-z][\\d+.a-z-]*:/.test(href);\n },\n\n isExternal() {\n const base = this.computedBase;\n const href = this.computedHref;\n\n if (!href) {\n return;\n }\n\n return !href.startsWith(base) && !href.startsWith('webcal://');\n },\n\n isHashPath() {\n if (!this.computedHref) {\n return;\n }\n\n return this.computedHref.startsWith('#');\n },\n\n /**\n * Not used for now as derefer.org is down and there are no suitable replacements.\n * @returns {string} - de-refer service'd url.\n */\n anonymisedHref() {\n const {\n anonRedirect\n } = this.general;\n const href = this.computedHref;\n\n if (!href) {\n return;\n }\n\n return anonRedirect ? anonRedirect + href : href;\n },\n\n matchingVueRoute() {\n const {\n isAbsolute,\n isExternal,\n computedHref\n } = this;\n\n if (isAbsolute && isExternal) {\n return;\n }\n\n const {\n route\n } = _router__WEBPACK_IMPORTED_MODULE_0__[\"default\"].resolve(_router__WEBPACK_IMPORTED_MODULE_0__.base + computedHref);\n\n if (!route.name) {\n return;\n }\n\n return route;\n },\n\n linkProperties() {\n const {\n to,\n isIRC,\n isAbsolute,\n isExternal,\n isHashPath,\n matchingVueRoute\n } = this;\n const base = this.computedBase;\n const href = this.computedHref; // Return normal router-link\n\n if (to) {\n return {\n is: 'router-link',\n to\n };\n } // Just return a boring link with other attrs\n // @NOTE: This is for scroll anchors as it uses the id\n\n\n if (!href) {\n return {\n is: 'a',\n // Only tag this as a \"false-link\" if we passed a name in the props\n falseLink: Boolean(this.$attrs.name) || undefined\n };\n } // If current page and next page are both vue routes return router-link\n\n\n if (matchingVueRoute && this.$route && matchingVueRoute.meta.converted && this.$route.meta.converted) {\n // Allows us to skip when we're in a test\n if (window.loadMainApp) {\n return {\n is: 'router-link',\n to: matchingVueRoute.fullPath,\n // Add a `href` attribute to enable native mouse navigation (middle click, ctrl+click, etc.)\n href: new URL(matchingVueRoute.fullPath, base).href\n };\n }\n }\n\n return {\n is: 'a',\n target: isAbsolute && isExternal ? '_blank' : '_self',\n href: (() => {\n if (isHashPath) {\n const {\n location\n } = window;\n\n if (location.hash.length === 0) {\n // Current location might be `url#`\n const newHash = location.href.endsWith('#') ? href.slice(1) : href;\n return location.href + newHash;\n }\n\n return location.href.replace(location.hash, '') + href;\n }\n\n if (isIRC || isAbsolute) {\n return href;\n }\n\n return new URL(href, base).href;\n })(),\n rel: isAbsolute && isExternal ? 'noreferrer noopener' : undefined\n };\n }\n\n }\n});\n\n//# sourceURL=webpack://slim/./src/components/helpers/app-link.vue?./node_modules/babel-loader/lib/index.js??clonedRuleSet-1%5B0%5D.rules%5B0%5D!./node_modules/vue-loader/lib/index.js??vue-loader-options"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var vuex__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! vuex */ \"./node_modules/vuex/dist/vuex.esm.js\");\n/* harmony import */ var _router__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../router */ \"./src/router/index.js\");\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n\n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({\n name: 'app-link',\n props: {\n to: [String, Object],\n href: String,\n indexerId: {\n type: String\n },\n placeholder: {\n type: String,\n default: 'indexer-to-name'\n }\n },\n computed: { ...(0,vuex__WEBPACK_IMPORTED_MODULE_1__.mapState)({\n general: state => state.config.general\n }),\n ...(0,vuex__WEBPACK_IMPORTED_MODULE_1__.mapGetters)(['indexerIdToName']),\n\n indexerName() {\n // Returns `undefined` if not found\n const {\n indexerId,\n indexerIdToName\n } = this;\n return indexerIdToName(indexerId);\n },\n\n computedBase() {\n return document.querySelector('base').getAttribute('href');\n },\n\n computedHref() {\n const {\n href,\n indexerId,\n placeholder,\n indexerName\n } = this;\n\n if (indexerId && placeholder) {\n return href.replace(placeholder, indexerName);\n }\n\n return href;\n },\n\n isIRC() {\n if (!this.computedHref) {\n return;\n }\n\n return this.computedHref.startsWith('irc://');\n },\n\n isAbsolute() {\n const href = this.computedHref;\n\n if (!href) {\n return;\n }\n\n return /^[a-z][\\d+.a-z-]*:/.test(href);\n },\n\n isExternal() {\n const base = this.computedBase;\n const href = this.computedHref;\n\n if (!href) {\n return;\n }\n\n return !href.startsWith(base) && !href.startsWith('webcal://');\n },\n\n isHashPath() {\n if (!this.computedHref) {\n return;\n }\n\n return this.computedHref.startsWith('#');\n },\n\n /**\n * Not used for now as derefer.org is down and there are no suitable replacements.\n * @returns {string} - de-refer service'd url.\n */\n anonymisedHref() {\n const {\n anonRedirect\n } = this.general;\n const href = this.computedHref;\n\n if (!href) {\n return;\n }\n\n return anonRedirect ? anonRedirect + href : href;\n },\n\n matchingVueRoute() {\n const {\n isAbsolute,\n isExternal,\n computedHref\n } = this;\n\n if (isAbsolute && isExternal) {\n return;\n }\n\n const {\n route\n } = _router__WEBPACK_IMPORTED_MODULE_0__[\"default\"].resolve('/' + computedHref);\n\n if (!route.name) {\n return;\n }\n\n return route;\n },\n\n linkProperties() {\n const {\n to,\n isIRC,\n isAbsolute,\n isExternal,\n isHashPath,\n matchingVueRoute\n } = this;\n const base = this.computedBase;\n const href = this.computedHref; // Return normal router-link\n\n if (to) {\n return {\n is: 'router-link',\n to\n };\n } // Just return a boring link with other attrs\n // @NOTE: This is for scroll anchors as it uses the id\n\n\n if (!href) {\n return {\n is: 'a',\n // Only tag this as a \"false-link\" if we passed a name in the props\n falseLink: Boolean(this.$attrs.name) || undefined\n };\n } // If current page and next page are both vue routes return router-link\n\n\n if (matchingVueRoute && this.$route && matchingVueRoute.meta.converted && this.$route.meta.converted) {\n // Allows us to skip when we're in a test\n if (window.loadMainApp) {\n return {\n is: 'router-link',\n to: matchingVueRoute.fullPath,\n // Add a `href` attribute to enable native mouse navigation (middle click, ctrl+click, etc.)\n href: new URL(matchingVueRoute.fullPath, base).href\n };\n }\n }\n\n return {\n is: 'a',\n target: isAbsolute && isExternal ? '_blank' : '_self',\n href: (() => {\n if (isHashPath) {\n const {\n location\n } = window;\n\n if (location.hash.length === 0) {\n // Current location might be `url#`\n const newHash = location.href.endsWith('#') ? href.slice(1) : href;\n return location.href + newHash;\n }\n\n return location.href.replace(location.hash, '') + href;\n }\n\n if (isIRC || isAbsolute) {\n return href;\n }\n\n return new URL(href, base).href;\n })(),\n rel: isAbsolute && isExternal ? 'noreferrer noopener' : undefined\n };\n }\n\n }\n});\n\n//# sourceURL=webpack://slim/./src/components/helpers/app-link.vue?./node_modules/babel-loader/lib/index.js??clonedRuleSet-1%5B0%5D.rules%5B0%5D!./node_modules/vue-loader/lib/index.js??vue-loader-options"); /***/ }), diff --git a/themes/light/assets/js/medusa-runtime.js b/themes/light/assets/js/medusa-runtime.js index b14e04e07a..70e6dafefe 100644 --- a/themes/light/assets/js/medusa-runtime.js +++ b/themes/light/assets/js/medusa-runtime.js @@ -235,7 +235,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var vuex__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! vuex */ \"./node_modules/vuex/dist/vuex.esm.js\");\n/* harmony import */ var _router__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../router */ \"./src/router/index.js\");\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n\n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({\n name: 'app-link',\n props: {\n to: [String, Object],\n href: String,\n indexerId: {\n type: String\n },\n placeholder: {\n type: String,\n default: 'indexer-to-name'\n }\n },\n computed: { ...(0,vuex__WEBPACK_IMPORTED_MODULE_1__.mapState)({\n general: state => state.config.general\n }),\n ...(0,vuex__WEBPACK_IMPORTED_MODULE_1__.mapGetters)(['indexerIdToName']),\n\n indexerName() {\n // Returns `undefined` if not found\n const {\n indexerId,\n indexerIdToName\n } = this;\n return indexerIdToName(indexerId);\n },\n\n computedBase() {\n return document.querySelector('base').getAttribute('href');\n },\n\n computedHref() {\n const {\n href,\n indexerId,\n placeholder,\n indexerName\n } = this;\n\n if (indexerId && placeholder) {\n return href.replace(placeholder, indexerName);\n }\n\n return href;\n },\n\n isIRC() {\n if (!this.computedHref) {\n return;\n }\n\n return this.computedHref.startsWith('irc://');\n },\n\n isAbsolute() {\n const href = this.computedHref;\n\n if (!href) {\n return;\n }\n\n return /^[a-z][\\d+.a-z-]*:/.test(href);\n },\n\n isExternal() {\n const base = this.computedBase;\n const href = this.computedHref;\n\n if (!href) {\n return;\n }\n\n return !href.startsWith(base) && !href.startsWith('webcal://');\n },\n\n isHashPath() {\n if (!this.computedHref) {\n return;\n }\n\n return this.computedHref.startsWith('#');\n },\n\n /**\n * Not used for now as derefer.org is down and there are no suitable replacements.\n * @returns {string} - de-refer service'd url.\n */\n anonymisedHref() {\n const {\n anonRedirect\n } = this.general;\n const href = this.computedHref;\n\n if (!href) {\n return;\n }\n\n return anonRedirect ? anonRedirect + href : href;\n },\n\n matchingVueRoute() {\n const {\n isAbsolute,\n isExternal,\n computedHref\n } = this;\n\n if (isAbsolute && isExternal) {\n return;\n }\n\n const {\n route\n } = _router__WEBPACK_IMPORTED_MODULE_0__[\"default\"].resolve(_router__WEBPACK_IMPORTED_MODULE_0__.base + computedHref);\n\n if (!route.name) {\n return;\n }\n\n return route;\n },\n\n linkProperties() {\n const {\n to,\n isIRC,\n isAbsolute,\n isExternal,\n isHashPath,\n matchingVueRoute\n } = this;\n const base = this.computedBase;\n const href = this.computedHref; // Return normal router-link\n\n if (to) {\n return {\n is: 'router-link',\n to\n };\n } // Just return a boring link with other attrs\n // @NOTE: This is for scroll anchors as it uses the id\n\n\n if (!href) {\n return {\n is: 'a',\n // Only tag this as a \"false-link\" if we passed a name in the props\n falseLink: Boolean(this.$attrs.name) || undefined\n };\n } // If current page and next page are both vue routes return router-link\n\n\n if (matchingVueRoute && this.$route && matchingVueRoute.meta.converted && this.$route.meta.converted) {\n // Allows us to skip when we're in a test\n if (window.loadMainApp) {\n return {\n is: 'router-link',\n to: matchingVueRoute.fullPath,\n // Add a `href` attribute to enable native mouse navigation (middle click, ctrl+click, etc.)\n href: new URL(matchingVueRoute.fullPath, base).href\n };\n }\n }\n\n return {\n is: 'a',\n target: isAbsolute && isExternal ? '_blank' : '_self',\n href: (() => {\n if (isHashPath) {\n const {\n location\n } = window;\n\n if (location.hash.length === 0) {\n // Current location might be `url#`\n const newHash = location.href.endsWith('#') ? href.slice(1) : href;\n return location.href + newHash;\n }\n\n return location.href.replace(location.hash, '') + href;\n }\n\n if (isIRC || isAbsolute) {\n return href;\n }\n\n return new URL(href, base).href;\n })(),\n rel: isAbsolute && isExternal ? 'noreferrer noopener' : undefined\n };\n }\n\n }\n});\n\n//# sourceURL=webpack://slim/./src/components/helpers/app-link.vue?./node_modules/babel-loader/lib/index.js??clonedRuleSet-1%5B0%5D.rules%5B0%5D!./node_modules/vue-loader/lib/index.js??vue-loader-options"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var vuex__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! vuex */ \"./node_modules/vuex/dist/vuex.esm.js\");\n/* harmony import */ var _router__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../router */ \"./src/router/index.js\");\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n\n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({\n name: 'app-link',\n props: {\n to: [String, Object],\n href: String,\n indexerId: {\n type: String\n },\n placeholder: {\n type: String,\n default: 'indexer-to-name'\n }\n },\n computed: { ...(0,vuex__WEBPACK_IMPORTED_MODULE_1__.mapState)({\n general: state => state.config.general\n }),\n ...(0,vuex__WEBPACK_IMPORTED_MODULE_1__.mapGetters)(['indexerIdToName']),\n\n indexerName() {\n // Returns `undefined` if not found\n const {\n indexerId,\n indexerIdToName\n } = this;\n return indexerIdToName(indexerId);\n },\n\n computedBase() {\n return document.querySelector('base').getAttribute('href');\n },\n\n computedHref() {\n const {\n href,\n indexerId,\n placeholder,\n indexerName\n } = this;\n\n if (indexerId && placeholder) {\n return href.replace(placeholder, indexerName);\n }\n\n return href;\n },\n\n isIRC() {\n if (!this.computedHref) {\n return;\n }\n\n return this.computedHref.startsWith('irc://');\n },\n\n isAbsolute() {\n const href = this.computedHref;\n\n if (!href) {\n return;\n }\n\n return /^[a-z][\\d+.a-z-]*:/.test(href);\n },\n\n isExternal() {\n const base = this.computedBase;\n const href = this.computedHref;\n\n if (!href) {\n return;\n }\n\n return !href.startsWith(base) && !href.startsWith('webcal://');\n },\n\n isHashPath() {\n if (!this.computedHref) {\n return;\n }\n\n return this.computedHref.startsWith('#');\n },\n\n /**\n * Not used for now as derefer.org is down and there are no suitable replacements.\n * @returns {string} - de-refer service'd url.\n */\n anonymisedHref() {\n const {\n anonRedirect\n } = this.general;\n const href = this.computedHref;\n\n if (!href) {\n return;\n }\n\n return anonRedirect ? anonRedirect + href : href;\n },\n\n matchingVueRoute() {\n const {\n isAbsolute,\n isExternal,\n computedHref\n } = this;\n\n if (isAbsolute && isExternal) {\n return;\n }\n\n const {\n route\n } = _router__WEBPACK_IMPORTED_MODULE_0__[\"default\"].resolve('/' + computedHref);\n\n if (!route.name) {\n return;\n }\n\n return route;\n },\n\n linkProperties() {\n const {\n to,\n isIRC,\n isAbsolute,\n isExternal,\n isHashPath,\n matchingVueRoute\n } = this;\n const base = this.computedBase;\n const href = this.computedHref; // Return normal router-link\n\n if (to) {\n return {\n is: 'router-link',\n to\n };\n } // Just return a boring link with other attrs\n // @NOTE: This is for scroll anchors as it uses the id\n\n\n if (!href) {\n return {\n is: 'a',\n // Only tag this as a \"false-link\" if we passed a name in the props\n falseLink: Boolean(this.$attrs.name) || undefined\n };\n } // If current page and next page are both vue routes return router-link\n\n\n if (matchingVueRoute && this.$route && matchingVueRoute.meta.converted && this.$route.meta.converted) {\n // Allows us to skip when we're in a test\n if (window.loadMainApp) {\n return {\n is: 'router-link',\n to: matchingVueRoute.fullPath,\n // Add a `href` attribute to enable native mouse navigation (middle click, ctrl+click, etc.)\n href: new URL(matchingVueRoute.fullPath, base).href\n };\n }\n }\n\n return {\n is: 'a',\n target: isAbsolute && isExternal ? '_blank' : '_self',\n href: (() => {\n if (isHashPath) {\n const {\n location\n } = window;\n\n if (location.hash.length === 0) {\n // Current location might be `url#`\n const newHash = location.href.endsWith('#') ? href.slice(1) : href;\n return location.href + newHash;\n }\n\n return location.href.replace(location.hash, '') + href;\n }\n\n if (isIRC || isAbsolute) {\n return href;\n }\n\n return new URL(href, base).href;\n })(),\n rel: isAbsolute && isExternal ? 'noreferrer noopener' : undefined\n };\n }\n\n }\n});\n\n//# sourceURL=webpack://slim/./src/components/helpers/app-link.vue?./node_modules/babel-loader/lib/index.js??clonedRuleSet-1%5B0%5D.rules%5B0%5D!./node_modules/vue-loader/lib/index.js??vue-loader-options"); /***/ }), From 6400a1d09252ef4f6d8684d6794ee8102d3dfa0f Mon Sep 17 00:00:00 2001 From: p0psicles Date: Wed, 9 Mar 2022 18:27:05 +0100 Subject: [PATCH 3/9] Remove the render loop --- themes-default/slim/src/components/display-show.vue | 6 ++++-- themes-default/slim/src/store/modules/shows.js | 5 ++++- themes/dark/assets/js/medusa-runtime.js | 4 ++-- themes/light/assets/js/medusa-runtime.js | 4 ++-- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/themes-default/slim/src/components/display-show.vue b/themes-default/slim/src/components/display-show.vue index 014a91c771..767d1add92 100644 --- a/themes-default/slim/src/components/display-show.vue +++ b/themes-default/slim/src/components/display-show.vue @@ -554,7 +554,9 @@ export default { return []; } - let sortedSeasons = show.seasons.sort((a, b) => a.season - b.season).filter(season => season.season !== 0); + const seasons = show.seasons.slice(); + + let sortedSeasons = seasons.sort((a, b) => a.season - b.season).filter(season => season.season !== 0); // Use the filterOverviewStatus to filter the data based on what's checked in the show-header. if (filterByOverviewStatus && filterByOverviewStatus.filter(status => status.checked).length < filterByOverviewStatus.length) { @@ -574,7 +576,7 @@ export default { } if (invertTable) { - return sortedSeasons.reverse(); + return sortedSeasons.slice().reverse(); } return sortedSeasons; diff --git a/themes-default/slim/src/store/modules/shows.js b/themes-default/slim/src/store/modules/shows.js index 20badaafa8..c6d7123735 100644 --- a/themes-default/slim/src/store/modules/shows.js +++ b/themes-default/slim/src/store/modules/shows.js @@ -117,9 +117,12 @@ const mutations = { // Recreate an Array with season objects, with each season having an episodes array. // This format is used by vue-good-table (displayShow). episodes.forEach(episode => { - const existingSeason = newShow.seasons.find(season => season.season === episode.season); + let existingSeason = newShow.seasons.find(season => season.season === episode.season); if (existingSeason) { + // Shallow copy + existingSeason = { ...existingSeason }; + const foundIndex = existingSeason.children.findIndex(element => element.slug === episode.slug); if (foundIndex === -1) { existingSeason.children.push(episode); diff --git a/themes/dark/assets/js/medusa-runtime.js b/themes/dark/assets/js/medusa-runtime.js index 70e6dafefe..a7e166317d 100644 --- a/themes/dark/assets/js/medusa-runtime.js +++ b/themes/dark/assets/js/medusa-runtime.js @@ -213,7 +213,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var lodash_debounce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! lodash/debounce */ \"./node_modules/lodash/debounce.js\");\n/* harmony import */ var lodash_debounce__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(lodash_debounce__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var vuex__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! vuex */ \"./node_modules/vuex/dist/vuex.esm.js\");\n/* harmony import */ var _helpers__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./helpers */ \"./src/components/helpers/index.js\");\n/* harmony import */ var _utils_core__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../utils/core */ \"./src/utils/core.js\");\n/* harmony import */ var _mixins_manage_cookie__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../mixins/manage-cookie */ \"./src/mixins/manage-cookie.js\");\n/* harmony import */ var _utils_jquery__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../utils/jquery */ \"./src/utils/jquery.js\");\n/* harmony import */ var vue_good_table__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! vue-good-table */ \"./node_modules/vue-good-table/dist/vue-good-table.esm.js\");\n/* harmony import */ var _backstretch_vue__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./backstretch.vue */ \"./src/components/backstretch.vue\");\n/* harmony import */ var _show_header_vue__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./show-header.vue */ \"./src/components/show-header.vue\");\n/* harmony import */ var _subtitle_search_vue__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./subtitle-search.vue */ \"./src/components/subtitle-search.vue\");\n/* harmony import */ var _helpers_quality_pill_vue__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./helpers/quality-pill.vue */ \"./src/components/helpers/quality-pill.vue\");\n/* provided dependency */ var $ = __webpack_require__(/*! jquery */ \"./node_modules/jquery/dist/jquery.js\");\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n\n\n\n\n\n\n\n\n\n\n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({\n name: 'show',\n components: {\n AppLink: _helpers__WEBPACK_IMPORTED_MODULE_1__.AppLink,\n Backstretch: _backstretch_vue__WEBPACK_IMPORTED_MODULE_5__[\"default\"],\n PlotInfo: _helpers__WEBPACK_IMPORTED_MODULE_1__.PlotInfo,\n QualityPill: _helpers_quality_pill_vue__WEBPACK_IMPORTED_MODULE_8__[\"default\"],\n SceneNumberInput: _helpers__WEBPACK_IMPORTED_MODULE_1__.SceneNumberInput,\n SceneNumberAnimeInput: _helpers__WEBPACK_IMPORTED_MODULE_1__.SceneNumberAnimeInput,\n ShowHeader: _show_header_vue__WEBPACK_IMPORTED_MODULE_6__[\"default\"],\n VueGoodTable: vue_good_table__WEBPACK_IMPORTED_MODULE_9__.VueGoodTable\n },\n mixins: [(0,_mixins_manage_cookie__WEBPACK_IMPORTED_MODULE_3__.manageCookieMixin)('displayShow')],\n\n metaInfo() {\n if (!this.show || !this.show.title) {\n return {\n title: 'Medusa'\n };\n }\n\n const {\n title\n } = this.show;\n return {\n title,\n titleTemplate: '%s | Medusa'\n };\n },\n\n props: {\n /**\n * Show Slug\n */\n slug: {\n type: String\n }\n },\n\n data() {\n const {\n getCookie\n } = this;\n const perPageDropdown = [25, 50, 100, 250, 500];\n\n const getPaginationPerPage = () => {\n const rows = getCookie('pagination-perPage');\n\n if (!rows) {\n return 50;\n }\n\n if (!perPageDropdown.includes(rows)) {\n return 500;\n }\n\n return rows;\n };\n\n return {\n invertTable: true,\n subtitleSearchComponents: [],\n columns: [{\n label: 'NFO',\n field: 'content.hasNfo',\n type: 'boolean',\n sortable: false,\n hidden: getCookie('NFO')\n }, {\n label: 'TBN',\n field: 'content.hasTbn',\n type: 'boolean',\n sortable: false,\n hidden: getCookie('TBN')\n }, {\n label: 'Episode',\n field: 'episode',\n type: 'number',\n hidden: getCookie('Episode')\n }, {\n label: 'Abs. #',\n field: 'absoluteNumber',\n type: 'number',\n hidden: getCookie('Abs. #')\n }, {\n label: 'Scene',\n field: 'scene',\n sortable: false,\n hidden: getCookie('Scene')\n }, {\n label: 'Scene Abs. #',\n field: 'sceneAbsolute',\n type: 'number',\n hidden: getCookie('Scene Abs. #')\n }, {\n label: 'Title',\n field: 'title',\n hidden: getCookie('Title')\n }, {\n label: 'File',\n field: 'file.location',\n hidden: getCookie('File')\n }, {\n label: 'Size',\n field: 'file.size',\n type: 'number',\n formatFn: _utils_core__WEBPACK_IMPORTED_MODULE_2__.humanFileSize,\n hidden: getCookie('Size')\n }, {\n // For now i'm using a custom function the parse it. As the type: date, isn't working for us.\n // But the goal is to have this user formatted (as configured in backend)\n label: 'Air date',\n field: this.parseDateFn,\n tdClass: 'align-center',\n sortable: false,\n hidden: getCookie('Air date')\n }, {\n label: 'Download',\n field: 'download',\n sortable: false,\n hidden: getCookie('Download')\n }, {\n label: 'Subtitles',\n field: 'subtitles',\n sortable: false,\n hidden: getCookie('Subtitles')\n }, {\n label: 'Status',\n field: 'status',\n hidden: getCookie('Status')\n }, {\n label: 'Search',\n field: 'search',\n sortable: false,\n hidden: getCookie('Search')\n }],\n perPageDropdown,\n paginationPerPage: getPaginationPerPage(),\n selectedEpisodes: [],\n // We need to keep track of which episode where trying to search, for the vue-modal\n failedSearchEpisodes: [],\n backlogSearchEpisodes: [],\n filterByOverviewStatus: false,\n selectedSearch: 'search action'\n };\n },\n\n computed: { ...(0,vuex__WEBPACK_IMPORTED_MODULE_10__.mapState)({\n shows: state => state.shows.shows,\n subtitles: state => state.config.subtitles,\n configLoaded: state => state.config.layout.fanartBackground !== null,\n layout: state => state.config.layout,\n stateSearch: state => state.config.search\n }),\n ...(0,vuex__WEBPACK_IMPORTED_MODULE_10__.mapGetters)({\n show: 'getCurrentShow',\n getOverviewStatus: 'getOverviewStatus',\n fuzzyParseDateTime: 'fuzzyParseDateTime'\n }),\n\n showSlug() {\n const {\n slug\n } = this;\n return slug || this.$route.query.showslug;\n },\n\n theme() {\n const {\n layout\n } = this;\n const {\n themeName\n } = layout;\n return themeName || 'light';\n },\n\n orderedSeasons() {\n const {\n filterByOverviewStatus,\n invertTable,\n show\n } = this;\n\n if (!show.seasons) {\n return [];\n }\n\n let sortedSeasons = show.seasons.sort((a, b) => a.season - b.season).filter(season => season.season !== 0); // Use the filterOverviewStatus to filter the data based on what's checked in the show-header.\n\n if (filterByOverviewStatus && filterByOverviewStatus.filter(status => status.checked).length < filterByOverviewStatus.length) {\n const filteredSortedSeasons = [];\n\n for (const season of sortedSeasons) {\n const {\n children,\n ...res\n } = season;\n const filteredEpisodes = children.filter(episode => {\n const episodeOverviewStatus = this.getOverviewStatus(episode.status, episode.quality, show.config.qualities);\n const filteredStatus = filterByOverviewStatus.find(overviewStatus => overviewStatus.name === episodeOverviewStatus);\n return !filteredStatus || filteredStatus.checked;\n });\n filteredSortedSeasons.push(Object.assign({\n children: filteredEpisodes\n }, res));\n }\n\n sortedSeasons = filteredSortedSeasons;\n }\n\n if (invertTable) {\n return sortedSeasons.reverse();\n }\n\n return sortedSeasons;\n },\n\n specials() {\n const {\n show\n } = this;\n\n if (!show.seasons) {\n return [];\n }\n\n return show.seasons.filter(season => season.season === 0);\n }\n\n },\n\n mounted() {\n this.loadShow();\n ['load', 'resize'].map(event => {\n return window.addEventListener(event, () => {\n this.reflowLayout();\n });\n });\n $(document.body).on('click', '.seasonCheck', event => {\n const seasCheck = event.currentTarget;\n const seasNo = $(seasCheck).attr('id');\n $('#collapseSeason-' + seasNo).collapse('show');\n const seasonIdentifier = 's' + seasNo;\n $('.epCheck:visible').each((index, element) => {\n const epParts = $(element).attr('id').split('e');\n\n if (epParts[0] === seasonIdentifier) {\n element.checked = seasCheck.checked;\n }\n });\n });\n },\n\n methods: {\n humanFileSize: _utils_core__WEBPACK_IMPORTED_MODULE_2__.humanFileSize,\n ...(0,vuex__WEBPACK_IMPORTED_MODULE_10__.mapActions)({\n getShow: 'getShow',\n // Map `this.getShow()` to `this.$store.dispatch('getShow')`\n getEpisodes: 'getEpisodes',\n setCurrentShow: 'setCurrentShow',\n setRecentShow: 'setRecentShow'\n }),\n\n async loadShow() {\n const {\n setCurrentShow,\n showSlug,\n initializeEpisodes,\n getShow\n } = this; // We need detailed info for the xem / scene exceptions, so let's get it.\n\n await getShow({\n showSlug,\n detailed: true\n }); // Let's tell the store which show we currently want as current.\n // Run this after getShow(), as it will trigger the initializeEpisodes() method.\n\n setCurrentShow(showSlug); // Load all episodes\n\n initializeEpisodes();\n },\n\n statusQualityUpdate(event) {\n const {\n selectedEpisodes,\n setStatus,\n setQuality\n } = this;\n\n if (event.newQuality !== null && event.newQuality !== 'Change quality to:') {\n setQuality(event.newQuality, selectedEpisodes);\n }\n\n if (event.newStatus !== null && event.newStatus !== 'Change status to:') {\n setStatus(event.newStatus, selectedEpisodes);\n }\n },\n\n setQuality(quality, episodes) {\n const {\n showSlug,\n getEpisodes,\n show\n } = this;\n const patchData = {};\n episodes.forEach(episode => {\n patchData[episode.slug] = {\n quality: Number.parseInt(quality, 10)\n };\n });\n api.patch(`series/${show.id.slug}/episodes`, patchData) // eslint-disable-line no-undef\n .then(_ => {\n console.info(`patched show ${show.id.slug} with quality ${quality}`);\n [...new Set(episodes.map(episode => episode.season))].forEach(season => {\n getEpisodes({\n showSlug,\n season\n });\n });\n }).catch(error => {\n console.error(String(error));\n });\n },\n\n setStatus(status, episodes) {\n const {\n showSlug,\n getEpisodes\n } = this;\n const patchData = {};\n episodes.forEach(episode => {\n patchData[episode.slug] = {\n status\n };\n });\n api.patch(`series/${showSlug}/episodes`, patchData) // eslint-disable-line no-undef\n .then(_ => {\n console.info(`patched show ${showSlug} with status ${status}`);\n [...new Set(episodes.map(episode => episode.season))].forEach(season => {\n getEpisodes({\n showSlug,\n season\n });\n });\n }).catch(error => {\n console.error(String(error));\n }); // New status Wanted\n\n if (status === 3) {\n this.$modal.show('query-start-backlog-search', {\n episodes\n });\n } // New status Failed\n\n\n if (status === 11) {\n this.$modal.show('query-mark-failed-and-search', {\n episodes\n });\n }\n },\n\n parseDateFn(row) {\n const {\n fuzzyParseDateTime\n } = this;\n return fuzzyParseDateTime(row.airDate);\n },\n\n rowStyleClassFn(row) {\n const {\n getOverviewStatus,\n show\n } = this;\n\n if (Object.keys(row).includes('vgt_header_id')) {\n return;\n }\n\n const overview = getOverviewStatus(row.status, row.quality, show.config.qualities).toLowerCase().trim();\n return overview;\n },\n\n /**\n * Add (reduce) the total episodes filesize.\n * @param {object} headerRow header row object.\n * @returns {string} - Human readable file size.\n */\n addFileSize(headerRow) {\n return (0,_utils_core__WEBPACK_IMPORTED_MODULE_2__.humanFileSize)(headerRow.children.reduce((a, b) => a + (b.file.size || 0), 0));\n },\n\n searchSubtitle(event, episode, lang) {\n const {\n showSlug,\n getEpisodes,\n show,\n subtitleSearchComponents\n } = this;\n const SubtitleSearchClass = Vue.extend(_subtitle_search_vue__WEBPACK_IMPORTED_MODULE_7__[\"default\"]); // eslint-disable-line no-undef\n\n const instance = new SubtitleSearchClass({\n propsData: {\n show,\n episode,\n key: episode.originalIndex,\n lang\n },\n parent: this\n }); // Update the show, as we downloaded new subtitle(s)\n\n instance.$on('update', event => {\n // This could be replaced by the generic websocket updates in future.\n if (event.reason === 'new subtitles found') {\n getEpisodes({\n showSlug,\n season: episode.season\n });\n }\n });\n const node = document.createElement('div');\n const tableRef = episode.season === 0 ? 'table-specials' : 'table-seasons';\n this.$refs[tableRef].$refs[`row-${episode.originalIndex}`][0].after(node);\n instance.$mount(node);\n subtitleSearchComponents.push(instance);\n },\n\n /**\n * Attaches IMDB tooltip\n */\n reflowLayout: lodash_debounce__WEBPACK_IMPORTED_MODULE_0___default()(() => {\n console.debug('Reflowing layout');\n (0,_utils_jquery__WEBPACK_IMPORTED_MODULE_4__.addQTip)(); // eslint-disable-line no-undef\n }, 1000),\n\n /**\n * Check if any of the episodes in this season does not have the status \"unaired\".\n * If that's the case we want to manual season search icon.\n * @param {object} season - A season object.\n * @returns {Boolean} - true if one of the seasons episodes has a status 'unaired'.\n */\n anyEpisodeNotUnaired(season) {\n return season.children.filter(ep => ep.status !== 'Unaired').length > 0;\n },\n\n episodesInverse(season) {\n const {\n invertTable\n } = this;\n\n if (!season.children) {\n return [];\n }\n\n if (invertTable) {\n return season.children.slice().reverse();\n }\n\n return season.children;\n },\n\n /**\n * Vue-js-modal requires a method, to pass an event to.\n * The event then can be used to assign the value of the episode.\n * @param {Object} event - vue js modal event\n */\n beforeBacklogSearchModalClose(event) {\n this.backlogSearchEpisodes = event.params.episodes;\n },\n\n /**\n * Vue-js-modal requires a method, to pass an event to.\n * The event then can be used to assign the value of the episode.\n * @param {Object} event - vue js modal event\n */\n beforeFailedSearchModalClose(event) {\n this.failedSearchEpisodes = event.params.episodes;\n },\n\n retryDownload(episode) {\n const {\n stateSearch\n } = this;\n return stateSearch.general.failedDownloads.enabled && ['Snatched', 'Snatched (Proper)', 'Snatched (Best)', 'Downloaded'].includes(episode.status);\n },\n\n search(episodes, searchType) {\n const {\n show\n } = this;\n let data = {};\n\n if (episodes) {\n data = {\n showSlug: show.id.slug,\n episodes: [],\n options: {}\n };\n episodes.forEach(episode => {\n data.episodes.push(episode.slug);\n this.$refs[`search-${episode.slug}`].src = 'images/loading16-dark.gif';\n });\n }\n\n api.put(`search/${searchType}`, data) // eslint-disable-line no-undef\n .then(_ => {\n if (episodes.length === 1) {\n console.info(`started search for show: ${show.id.slug} episode: ${episodes[0].slug}`);\n this.$refs[`search-${episodes[0].slug}`].src = 'images/queued.png';\n this.$refs[`search-${episodes[0].slug}`].disabled = true;\n } else {\n console.info('started a full backlog search');\n }\n }).catch(error => {\n console.error(String(error));\n episodes.forEach(episode => {\n data.episodes.push(episode.slug);\n this.$refs[`search-${episodes[0].slug}`].src = 'images/no16.png';\n });\n }).finally(() => {\n this.failedSearchEpisodes = [];\n this.backlogSearchEpisodes = [];\n });\n },\n\n /**\n * Start a backlog search or failed search for the specific episode.\n * A failed search is started depending on the current episodes status.\n * @param {Object} episode - Episode object. If no episode object is passed, a backlog search is started.\n */\n queueSearch(episode) {\n const {\n $modal,\n search,\n retryDownload\n } = this;\n const episodeIdentifier = episode.slug;\n\n if (episode) {\n if (this.$refs[`search-${episodeIdentifier}`].disabled === true) {\n return;\n }\n\n if (retryDownload(episode)) {\n $modal.show('query-mark-failed-and-search', {\n episode\n });\n } else {\n search([episode], 'backlog');\n }\n }\n },\n\n showSubtitleButton(episode) {\n const {\n subtitles,\n show\n } = this;\n return episode.season !== 0 && subtitles.enabled && show.config.subtitlesEnabled && !['Snatched', 'Snatched (Proper)', 'Snatched (Best)', 'Downloaded'].includes(episode.status);\n },\n\n totalSeasonEpisodeSize(season) {\n return season.children.filter(x => x.file && x.file.size > 0).reduce((a, b) => a + b.file.size, 0);\n },\n\n getSeasonExceptions(season) {\n const {\n show\n } = this;\n const {\n config\n } = show;\n const {\n aliases\n } = config;\n let bindData = {\n class: 'display: none'\n }; // Map the indexer season to a xem mapped season.\n // check if the season exception also exists in the xem numbering table\n\n let xemSeasons = [];\n let foundInXem = false;\n\n if (show.xemNumbering.length > 0) {\n const xemResult = show.xemNumbering.filter(x => x.source.season === season); // Create an array with unique seasons\n\n xemSeasons = [...new Set(xemResult.map(item => item.destination.season))];\n foundInXem = Boolean(xemSeasons.length);\n } // Check if there is a season exception for this season\n\n\n if (aliases.find(x => x.season === season)) {\n // If there is not a match on the xem table, display it as a medusa scene exception\n bindData = {\n id: `xem-exception-season-${foundInXem ? xemSeasons[0] : season}`,\n alt: foundInXem ? '[xem]' : '[medusa]',\n src: foundInXem ? 'images/xem.png' : 'images/ico/favicon-16.png',\n title: foundInXem ? xemSeasons.reduce((a, b) => {\n return a.concat(aliases.filter(alias => alias.season === b).map(alias => alias.title));\n }, []).join(', ') : aliases.filter(alias => alias.season === season).map(alias => alias.title).join(', ')\n };\n }\n\n return bindData;\n },\n\n updateEpisodeWatched(episode, watched) {\n const {\n showSlug,\n getEpisodes,\n show\n } = this;\n const patchData = {};\n patchData[episode.slug] = {\n watched\n };\n api.patch(`series/${show.id.slug}/episodes`, patchData) // eslint-disable-line no-undef\n .then(_ => {\n console.info(`patched episode ${episode.slug} with watched set to ${watched}`);\n getEpisodes({\n showSlug,\n season: episode.season\n });\n }).catch(error => {\n console.error(String(error));\n });\n episode.watched = watched;\n },\n\n updatePaginationPerPage(rows) {\n const {\n setCookie\n } = this;\n this.paginationPerPage = rows;\n setCookie('pagination-perPage', rows);\n },\n\n onPageChange(params) {\n this.loadEpisodes(params.currentPage);\n },\n\n neededSeasons(page) {\n const {\n layout,\n paginationPerPage,\n show\n } = this;\n const {\n seasonCount\n } = show;\n\n if (!seasonCount || seasonCount.length === 0) {\n return [];\n }\n\n if (!layout.show.pagination.enable) {\n return seasonCount.filter(season => season.season !== 0).map(season => season.season).reverse();\n }\n\n const seasons = show.seasonCount.length - 1;\n let pagesCount = 1;\n let episodeCount = 0;\n const pages = {};\n\n for (let i = seasons; i >= 0; i--) {\n const {\n season\n } = show.seasonCount[i]; // Exclude specials\n\n if (season === 0) {\n break;\n }\n\n if (pagesCount in pages) {\n pages[pagesCount].push(season);\n } else {\n pages[pagesCount] = [season];\n }\n\n episodeCount += show.seasonCount[i].episodeCount;\n\n if (episodeCount / paginationPerPage > pagesCount) {\n pagesCount++;\n pages[pagesCount] = [season];\n }\n\n if (pagesCount > page) {\n break;\n }\n }\n\n return pages[page] || [];\n },\n\n loadEpisodes(page) {\n const {\n showSlug,\n getEpisodes\n } = this; // Wrap getEpisodes into an async/await function, so we can wait for the season to have been committed\n // before going on to the next one.\n\n const _getEpisodes = async showSlug => {\n for (const season of this.neededSeasons(page)) {\n // We're waiting for the results by design, to give vue the chance to update the dom.\n // If we fire all the promises at once for, for example 25 seasons. We'll overload medusa's app\n // and chance is high a number of requests will timeout.\n await getEpisodes({\n showSlug,\n season\n }); // eslint-disable-line no-await-in-loop\n }\n };\n\n _getEpisodes(showSlug);\n },\n\n initializeEpisodes() {\n const {\n getEpisodes,\n showSlug,\n setRecentShow,\n show\n } = this;\n\n if (!show.seasons && show.seasonCount) {\n // Load episodes for the first page.\n this.loadEpisodes(1); // Always get special episodes if available.\n\n if (show.seasonCount.length > 0 && show.seasonCount[0].season === 0) {\n getEpisodes({\n showSlug,\n season: 0\n });\n }\n }\n\n if (show.id.slug) {\n // For now i'm dumping this here\n setRecentShow({\n showSlug: show.id.slug,\n name: show.title\n });\n }\n },\n\n mobileSelectSearch(event, episode) {\n const {\n $snotify,\n $router,\n queueSearch,\n searchSubtitle,\n show\n } = this;\n\n if (event.target.value === 'forced') {\n queueSearch(episode);\n $snotify.success(`Search started for S${episode.season} E${episode.episode}`);\n }\n\n if (event.target.value === 'manual') {\n // Use the router to navigate to snatchSelection.\n $router.push({\n name: 'snatchSelection',\n query: {\n showslug: show.id.slug,\n season: episode.season,\n episode: episode.episode\n }\n });\n }\n\n if (event.target.value === 'subtitle') {\n searchSubtitle(event, episode);\n }\n\n setTimeout(() => {\n event.target.value = 'search action';\n }, 2000);\n }\n\n },\n filters: {\n sceneObjectToString(value) {\n return `${value.season}x${value.episode}`;\n }\n\n },\n watch: {\n 'show.id.slug': function (slug) {\n // eslint-disable-line object-shorthand\n // Show's slug has changed, meaning the show's page has finished loading.\n if (slug) {\n // This is still technically jQuery. Meaning whe're still letting jQuery do its thing on the entire dom.\n (0,_utils_jquery__WEBPACK_IMPORTED_MODULE_4__.updateSearchIcons)(slug, this);\n }\n }\n }\n});\n\n//# sourceURL=webpack://slim/./src/components/display-show.vue?./node_modules/babel-loader/lib/index.js??clonedRuleSet-1%5B0%5D.rules%5B0%5D!./node_modules/vue-loader/lib/index.js??vue-loader-options"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var lodash_debounce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! lodash/debounce */ \"./node_modules/lodash/debounce.js\");\n/* harmony import */ var lodash_debounce__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(lodash_debounce__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var vuex__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! vuex */ \"./node_modules/vuex/dist/vuex.esm.js\");\n/* harmony import */ var _helpers__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./helpers */ \"./src/components/helpers/index.js\");\n/* harmony import */ var _utils_core__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../utils/core */ \"./src/utils/core.js\");\n/* harmony import */ var _mixins_manage_cookie__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../mixins/manage-cookie */ \"./src/mixins/manage-cookie.js\");\n/* harmony import */ var _utils_jquery__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../utils/jquery */ \"./src/utils/jquery.js\");\n/* harmony import */ var vue_good_table__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! vue-good-table */ \"./node_modules/vue-good-table/dist/vue-good-table.esm.js\");\n/* harmony import */ var _backstretch_vue__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./backstretch.vue */ \"./src/components/backstretch.vue\");\n/* harmony import */ var _show_header_vue__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./show-header.vue */ \"./src/components/show-header.vue\");\n/* harmony import */ var _subtitle_search_vue__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./subtitle-search.vue */ \"./src/components/subtitle-search.vue\");\n/* harmony import */ var _helpers_quality_pill_vue__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./helpers/quality-pill.vue */ \"./src/components/helpers/quality-pill.vue\");\n/* provided dependency */ var $ = __webpack_require__(/*! jquery */ \"./node_modules/jquery/dist/jquery.js\");\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n\n\n\n\n\n\n\n\n\n\n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({\n name: 'show',\n components: {\n AppLink: _helpers__WEBPACK_IMPORTED_MODULE_1__.AppLink,\n Backstretch: _backstretch_vue__WEBPACK_IMPORTED_MODULE_5__[\"default\"],\n PlotInfo: _helpers__WEBPACK_IMPORTED_MODULE_1__.PlotInfo,\n QualityPill: _helpers_quality_pill_vue__WEBPACK_IMPORTED_MODULE_8__[\"default\"],\n SceneNumberInput: _helpers__WEBPACK_IMPORTED_MODULE_1__.SceneNumberInput,\n SceneNumberAnimeInput: _helpers__WEBPACK_IMPORTED_MODULE_1__.SceneNumberAnimeInput,\n ShowHeader: _show_header_vue__WEBPACK_IMPORTED_MODULE_6__[\"default\"],\n VueGoodTable: vue_good_table__WEBPACK_IMPORTED_MODULE_9__.VueGoodTable\n },\n mixins: [(0,_mixins_manage_cookie__WEBPACK_IMPORTED_MODULE_3__.manageCookieMixin)('displayShow')],\n\n metaInfo() {\n if (!this.show || !this.show.title) {\n return {\n title: 'Medusa'\n };\n }\n\n const {\n title\n } = this.show;\n return {\n title,\n titleTemplate: '%s | Medusa'\n };\n },\n\n props: {\n /**\n * Show Slug\n */\n slug: {\n type: String\n }\n },\n\n data() {\n const {\n getCookie\n } = this;\n const perPageDropdown = [25, 50, 100, 250, 500];\n\n const getPaginationPerPage = () => {\n const rows = getCookie('pagination-perPage');\n\n if (!rows) {\n return 50;\n }\n\n if (!perPageDropdown.includes(rows)) {\n return 500;\n }\n\n return rows;\n };\n\n return {\n invertTable: true,\n subtitleSearchComponents: [],\n columns: [{\n label: 'NFO',\n field: 'content.hasNfo',\n type: 'boolean',\n sortable: false,\n hidden: getCookie('NFO')\n }, {\n label: 'TBN',\n field: 'content.hasTbn',\n type: 'boolean',\n sortable: false,\n hidden: getCookie('TBN')\n }, {\n label: 'Episode',\n field: 'episode',\n type: 'number',\n hidden: getCookie('Episode')\n }, {\n label: 'Abs. #',\n field: 'absoluteNumber',\n type: 'number',\n hidden: getCookie('Abs. #')\n }, {\n label: 'Scene',\n field: 'scene',\n sortable: false,\n hidden: getCookie('Scene')\n }, {\n label: 'Scene Abs. #',\n field: 'sceneAbsolute',\n type: 'number',\n hidden: getCookie('Scene Abs. #')\n }, {\n label: 'Title',\n field: 'title',\n hidden: getCookie('Title')\n }, {\n label: 'File',\n field: 'file.location',\n hidden: getCookie('File')\n }, {\n label: 'Size',\n field: 'file.size',\n type: 'number',\n formatFn: _utils_core__WEBPACK_IMPORTED_MODULE_2__.humanFileSize,\n hidden: getCookie('Size')\n }, {\n // For now i'm using a custom function the parse it. As the type: date, isn't working for us.\n // But the goal is to have this user formatted (as configured in backend)\n label: 'Air date',\n field: this.parseDateFn,\n tdClass: 'align-center',\n sortable: false,\n hidden: getCookie('Air date')\n }, {\n label: 'Download',\n field: 'download',\n sortable: false,\n hidden: getCookie('Download')\n }, {\n label: 'Subtitles',\n field: 'subtitles',\n sortable: false,\n hidden: getCookie('Subtitles')\n }, {\n label: 'Status',\n field: 'status',\n hidden: getCookie('Status')\n }, {\n label: 'Search',\n field: 'search',\n sortable: false,\n hidden: getCookie('Search')\n }],\n perPageDropdown,\n paginationPerPage: getPaginationPerPage(),\n selectedEpisodes: [],\n // We need to keep track of which episode where trying to search, for the vue-modal\n failedSearchEpisodes: [],\n backlogSearchEpisodes: [],\n filterByOverviewStatus: false,\n selectedSearch: 'search action'\n };\n },\n\n computed: { ...(0,vuex__WEBPACK_IMPORTED_MODULE_10__.mapState)({\n shows: state => state.shows.shows,\n subtitles: state => state.config.subtitles,\n configLoaded: state => state.config.layout.fanartBackground !== null,\n layout: state => state.config.layout,\n stateSearch: state => state.config.search\n }),\n ...(0,vuex__WEBPACK_IMPORTED_MODULE_10__.mapGetters)({\n show: 'getCurrentShow',\n getOverviewStatus: 'getOverviewStatus',\n fuzzyParseDateTime: 'fuzzyParseDateTime'\n }),\n\n showSlug() {\n const {\n slug\n } = this;\n return slug || this.$route.query.showslug;\n },\n\n theme() {\n const {\n layout\n } = this;\n const {\n themeName\n } = layout;\n return themeName || 'light';\n },\n\n orderedSeasons() {\n const {\n filterByOverviewStatus,\n invertTable,\n show\n } = this;\n\n if (!show.seasons) {\n return [];\n }\n\n const seasons = show.seasons.slice();\n let sortedSeasons = seasons.sort((a, b) => a.season - b.season).filter(season => season.season !== 0); // Use the filterOverviewStatus to filter the data based on what's checked in the show-header.\n\n if (filterByOverviewStatus && filterByOverviewStatus.filter(status => status.checked).length < filterByOverviewStatus.length) {\n const filteredSortedSeasons = [];\n\n for (const season of sortedSeasons) {\n const {\n children,\n ...res\n } = season;\n const filteredEpisodes = children.filter(episode => {\n const episodeOverviewStatus = this.getOverviewStatus(episode.status, episode.quality, show.config.qualities);\n const filteredStatus = filterByOverviewStatus.find(overviewStatus => overviewStatus.name === episodeOverviewStatus);\n return !filteredStatus || filteredStatus.checked;\n });\n filteredSortedSeasons.push(Object.assign({\n children: filteredEpisodes\n }, res));\n }\n\n sortedSeasons = filteredSortedSeasons;\n }\n\n if (invertTable) {\n return sortedSeasons.slice().reverse();\n }\n\n return sortedSeasons;\n },\n\n specials() {\n const {\n show\n } = this;\n\n if (!show.seasons) {\n return [];\n }\n\n return show.seasons.filter(season => season.season === 0);\n }\n\n },\n\n mounted() {\n this.loadShow();\n ['load', 'resize'].map(event => {\n return window.addEventListener(event, () => {\n this.reflowLayout();\n });\n });\n $(document.body).on('click', '.seasonCheck', event => {\n const seasCheck = event.currentTarget;\n const seasNo = $(seasCheck).attr('id');\n $('#collapseSeason-' + seasNo).collapse('show');\n const seasonIdentifier = 's' + seasNo;\n $('.epCheck:visible').each((index, element) => {\n const epParts = $(element).attr('id').split('e');\n\n if (epParts[0] === seasonIdentifier) {\n element.checked = seasCheck.checked;\n }\n });\n });\n },\n\n methods: {\n humanFileSize: _utils_core__WEBPACK_IMPORTED_MODULE_2__.humanFileSize,\n ...(0,vuex__WEBPACK_IMPORTED_MODULE_10__.mapActions)({\n getShow: 'getShow',\n // Map `this.getShow()` to `this.$store.dispatch('getShow')`\n getEpisodes: 'getEpisodes',\n setCurrentShow: 'setCurrentShow',\n setRecentShow: 'setRecentShow'\n }),\n\n async loadShow() {\n const {\n setCurrentShow,\n showSlug,\n initializeEpisodes,\n getShow\n } = this; // We need detailed info for the xem / scene exceptions, so let's get it.\n\n await getShow({\n showSlug,\n detailed: true\n }); // Let's tell the store which show we currently want as current.\n // Run this after getShow(), as it will trigger the initializeEpisodes() method.\n\n setCurrentShow(showSlug); // Load all episodes\n\n initializeEpisodes();\n },\n\n statusQualityUpdate(event) {\n const {\n selectedEpisodes,\n setStatus,\n setQuality\n } = this;\n\n if (event.newQuality !== null && event.newQuality !== 'Change quality to:') {\n setQuality(event.newQuality, selectedEpisodes);\n }\n\n if (event.newStatus !== null && event.newStatus !== 'Change status to:') {\n setStatus(event.newStatus, selectedEpisodes);\n }\n },\n\n setQuality(quality, episodes) {\n const {\n showSlug,\n getEpisodes,\n show\n } = this;\n const patchData = {};\n episodes.forEach(episode => {\n patchData[episode.slug] = {\n quality: Number.parseInt(quality, 10)\n };\n });\n api.patch(`series/${show.id.slug}/episodes`, patchData) // eslint-disable-line no-undef\n .then(_ => {\n console.info(`patched show ${show.id.slug} with quality ${quality}`);\n [...new Set(episodes.map(episode => episode.season))].forEach(season => {\n getEpisodes({\n showSlug,\n season\n });\n });\n }).catch(error => {\n console.error(String(error));\n });\n },\n\n setStatus(status, episodes) {\n const {\n showSlug,\n getEpisodes\n } = this;\n const patchData = {};\n episodes.forEach(episode => {\n patchData[episode.slug] = {\n status\n };\n });\n api.patch(`series/${showSlug}/episodes`, patchData) // eslint-disable-line no-undef\n .then(_ => {\n console.info(`patched show ${showSlug} with status ${status}`);\n [...new Set(episodes.map(episode => episode.season))].forEach(season => {\n getEpisodes({\n showSlug,\n season\n });\n });\n }).catch(error => {\n console.error(String(error));\n }); // New status Wanted\n\n if (status === 3) {\n this.$modal.show('query-start-backlog-search', {\n episodes\n });\n } // New status Failed\n\n\n if (status === 11) {\n this.$modal.show('query-mark-failed-and-search', {\n episodes\n });\n }\n },\n\n parseDateFn(row) {\n const {\n fuzzyParseDateTime\n } = this;\n return fuzzyParseDateTime(row.airDate);\n },\n\n rowStyleClassFn(row) {\n const {\n getOverviewStatus,\n show\n } = this;\n\n if (Object.keys(row).includes('vgt_header_id')) {\n return;\n }\n\n const overview = getOverviewStatus(row.status, row.quality, show.config.qualities).toLowerCase().trim();\n return overview;\n },\n\n /**\n * Add (reduce) the total episodes filesize.\n * @param {object} headerRow header row object.\n * @returns {string} - Human readable file size.\n */\n addFileSize(headerRow) {\n return (0,_utils_core__WEBPACK_IMPORTED_MODULE_2__.humanFileSize)(headerRow.children.reduce((a, b) => a + (b.file.size || 0), 0));\n },\n\n searchSubtitle(event, episode, lang) {\n const {\n showSlug,\n getEpisodes,\n show,\n subtitleSearchComponents\n } = this;\n const SubtitleSearchClass = Vue.extend(_subtitle_search_vue__WEBPACK_IMPORTED_MODULE_7__[\"default\"]); // eslint-disable-line no-undef\n\n const instance = new SubtitleSearchClass({\n propsData: {\n show,\n episode,\n key: episode.originalIndex,\n lang\n },\n parent: this\n }); // Update the show, as we downloaded new subtitle(s)\n\n instance.$on('update', event => {\n // This could be replaced by the generic websocket updates in future.\n if (event.reason === 'new subtitles found') {\n getEpisodes({\n showSlug,\n season: episode.season\n });\n }\n });\n const node = document.createElement('div');\n const tableRef = episode.season === 0 ? 'table-specials' : 'table-seasons';\n this.$refs[tableRef].$refs[`row-${episode.originalIndex}`][0].after(node);\n instance.$mount(node);\n subtitleSearchComponents.push(instance);\n },\n\n /**\n * Attaches IMDB tooltip\n */\n reflowLayout: lodash_debounce__WEBPACK_IMPORTED_MODULE_0___default()(() => {\n console.debug('Reflowing layout');\n (0,_utils_jquery__WEBPACK_IMPORTED_MODULE_4__.addQTip)(); // eslint-disable-line no-undef\n }, 1000),\n\n /**\n * Check if any of the episodes in this season does not have the status \"unaired\".\n * If that's the case we want to manual season search icon.\n * @param {object} season - A season object.\n * @returns {Boolean} - true if one of the seasons episodes has a status 'unaired'.\n */\n anyEpisodeNotUnaired(season) {\n return season.children.filter(ep => ep.status !== 'Unaired').length > 0;\n },\n\n episodesInverse(season) {\n const {\n invertTable\n } = this;\n\n if (!season.children) {\n return [];\n }\n\n if (invertTable) {\n return season.children.slice().reverse();\n }\n\n return season.children;\n },\n\n /**\n * Vue-js-modal requires a method, to pass an event to.\n * The event then can be used to assign the value of the episode.\n * @param {Object} event - vue js modal event\n */\n beforeBacklogSearchModalClose(event) {\n this.backlogSearchEpisodes = event.params.episodes;\n },\n\n /**\n * Vue-js-modal requires a method, to pass an event to.\n * The event then can be used to assign the value of the episode.\n * @param {Object} event - vue js modal event\n */\n beforeFailedSearchModalClose(event) {\n this.failedSearchEpisodes = event.params.episodes;\n },\n\n retryDownload(episode) {\n const {\n stateSearch\n } = this;\n return stateSearch.general.failedDownloads.enabled && ['Snatched', 'Snatched (Proper)', 'Snatched (Best)', 'Downloaded'].includes(episode.status);\n },\n\n search(episodes, searchType) {\n const {\n show\n } = this;\n let data = {};\n\n if (episodes) {\n data = {\n showSlug: show.id.slug,\n episodes: [],\n options: {}\n };\n episodes.forEach(episode => {\n data.episodes.push(episode.slug);\n this.$refs[`search-${episode.slug}`].src = 'images/loading16-dark.gif';\n });\n }\n\n api.put(`search/${searchType}`, data) // eslint-disable-line no-undef\n .then(_ => {\n if (episodes.length === 1) {\n console.info(`started search for show: ${show.id.slug} episode: ${episodes[0].slug}`);\n this.$refs[`search-${episodes[0].slug}`].src = 'images/queued.png';\n this.$refs[`search-${episodes[0].slug}`].disabled = true;\n } else {\n console.info('started a full backlog search');\n }\n }).catch(error => {\n console.error(String(error));\n episodes.forEach(episode => {\n data.episodes.push(episode.slug);\n this.$refs[`search-${episodes[0].slug}`].src = 'images/no16.png';\n });\n }).finally(() => {\n this.failedSearchEpisodes = [];\n this.backlogSearchEpisodes = [];\n });\n },\n\n /**\n * Start a backlog search or failed search for the specific episode.\n * A failed search is started depending on the current episodes status.\n * @param {Object} episode - Episode object. If no episode object is passed, a backlog search is started.\n */\n queueSearch(episode) {\n const {\n $modal,\n search,\n retryDownload\n } = this;\n const episodeIdentifier = episode.slug;\n\n if (episode) {\n if (this.$refs[`search-${episodeIdentifier}`].disabled === true) {\n return;\n }\n\n if (retryDownload(episode)) {\n $modal.show('query-mark-failed-and-search', {\n episode\n });\n } else {\n search([episode], 'backlog');\n }\n }\n },\n\n showSubtitleButton(episode) {\n const {\n subtitles,\n show\n } = this;\n return episode.season !== 0 && subtitles.enabled && show.config.subtitlesEnabled && !['Snatched', 'Snatched (Proper)', 'Snatched (Best)', 'Downloaded'].includes(episode.status);\n },\n\n totalSeasonEpisodeSize(season) {\n return season.children.filter(x => x.file && x.file.size > 0).reduce((a, b) => a + b.file.size, 0);\n },\n\n getSeasonExceptions(season) {\n const {\n show\n } = this;\n const {\n config\n } = show;\n const {\n aliases\n } = config;\n let bindData = {\n class: 'display: none'\n }; // Map the indexer season to a xem mapped season.\n // check if the season exception also exists in the xem numbering table\n\n let xemSeasons = [];\n let foundInXem = false;\n\n if (show.xemNumbering.length > 0) {\n const xemResult = show.xemNumbering.filter(x => x.source.season === season); // Create an array with unique seasons\n\n xemSeasons = [...new Set(xemResult.map(item => item.destination.season))];\n foundInXem = Boolean(xemSeasons.length);\n } // Check if there is a season exception for this season\n\n\n if (aliases.find(x => x.season === season)) {\n // If there is not a match on the xem table, display it as a medusa scene exception\n bindData = {\n id: `xem-exception-season-${foundInXem ? xemSeasons[0] : season}`,\n alt: foundInXem ? '[xem]' : '[medusa]',\n src: foundInXem ? 'images/xem.png' : 'images/ico/favicon-16.png',\n title: foundInXem ? xemSeasons.reduce((a, b) => {\n return a.concat(aliases.filter(alias => alias.season === b).map(alias => alias.title));\n }, []).join(', ') : aliases.filter(alias => alias.season === season).map(alias => alias.title).join(', ')\n };\n }\n\n return bindData;\n },\n\n updateEpisodeWatched(episode, watched) {\n const {\n showSlug,\n getEpisodes,\n show\n } = this;\n const patchData = {};\n patchData[episode.slug] = {\n watched\n };\n api.patch(`series/${show.id.slug}/episodes`, patchData) // eslint-disable-line no-undef\n .then(_ => {\n console.info(`patched episode ${episode.slug} with watched set to ${watched}`);\n getEpisodes({\n showSlug,\n season: episode.season\n });\n }).catch(error => {\n console.error(String(error));\n });\n episode.watched = watched;\n },\n\n updatePaginationPerPage(rows) {\n const {\n setCookie\n } = this;\n this.paginationPerPage = rows;\n setCookie('pagination-perPage', rows);\n },\n\n onPageChange(params) {\n this.loadEpisodes(params.currentPage);\n },\n\n neededSeasons(page) {\n const {\n layout,\n paginationPerPage,\n show\n } = this;\n const {\n seasonCount\n } = show;\n\n if (!seasonCount || seasonCount.length === 0) {\n return [];\n }\n\n if (!layout.show.pagination.enable) {\n return seasonCount.filter(season => season.season !== 0).map(season => season.season).reverse();\n }\n\n const seasons = show.seasonCount.length - 1;\n let pagesCount = 1;\n let episodeCount = 0;\n const pages = {};\n\n for (let i = seasons; i >= 0; i--) {\n const {\n season\n } = show.seasonCount[i]; // Exclude specials\n\n if (season === 0) {\n break;\n }\n\n if (pagesCount in pages) {\n pages[pagesCount].push(season);\n } else {\n pages[pagesCount] = [season];\n }\n\n episodeCount += show.seasonCount[i].episodeCount;\n\n if (episodeCount / paginationPerPage > pagesCount) {\n pagesCount++;\n pages[pagesCount] = [season];\n }\n\n if (pagesCount > page) {\n break;\n }\n }\n\n return pages[page] || [];\n },\n\n loadEpisodes(page) {\n const {\n showSlug,\n getEpisodes\n } = this; // Wrap getEpisodes into an async/await function, so we can wait for the season to have been committed\n // before going on to the next one.\n\n const _getEpisodes = async showSlug => {\n for (const season of this.neededSeasons(page)) {\n // We're waiting for the results by design, to give vue the chance to update the dom.\n // If we fire all the promises at once for, for example 25 seasons. We'll overload medusa's app\n // and chance is high a number of requests will timeout.\n await getEpisodes({\n showSlug,\n season\n }); // eslint-disable-line no-await-in-loop\n }\n };\n\n _getEpisodes(showSlug);\n },\n\n initializeEpisodes() {\n const {\n getEpisodes,\n showSlug,\n setRecentShow,\n show\n } = this;\n\n if (!show.seasons && show.seasonCount) {\n // Load episodes for the first page.\n this.loadEpisodes(1); // Always get special episodes if available.\n\n if (show.seasonCount.length > 0 && show.seasonCount[0].season === 0) {\n getEpisodes({\n showSlug,\n season: 0\n });\n }\n }\n\n if (show.id.slug) {\n // For now i'm dumping this here\n setRecentShow({\n showSlug: show.id.slug,\n name: show.title\n });\n }\n },\n\n mobileSelectSearch(event, episode) {\n const {\n $snotify,\n $router,\n queueSearch,\n searchSubtitle,\n show\n } = this;\n\n if (event.target.value === 'forced') {\n queueSearch(episode);\n $snotify.success(`Search started for S${episode.season} E${episode.episode}`);\n }\n\n if (event.target.value === 'manual') {\n // Use the router to navigate to snatchSelection.\n $router.push({\n name: 'snatchSelection',\n query: {\n showslug: show.id.slug,\n season: episode.season,\n episode: episode.episode\n }\n });\n }\n\n if (event.target.value === 'subtitle') {\n searchSubtitle(event, episode);\n }\n\n setTimeout(() => {\n event.target.value = 'search action';\n }, 2000);\n }\n\n },\n filters: {\n sceneObjectToString(value) {\n return `${value.season}x${value.episode}`;\n }\n\n },\n watch: {\n 'show.id.slug': function (slug) {\n // eslint-disable-line object-shorthand\n // Show's slug has changed, meaning the show's page has finished loading.\n if (slug) {\n // This is still technically jQuery. Meaning whe're still letting jQuery do its thing on the entire dom.\n (0,_utils_jquery__WEBPACK_IMPORTED_MODULE_4__.updateSearchIcons)(slug, this);\n }\n }\n }\n});\n\n//# sourceURL=webpack://slim/./src/components/display-show.vue?./node_modules/babel-loader/lib/index.js??clonedRuleSet-1%5B0%5D.rules%5B0%5D!./node_modules/vue-loader/lib/index.js??vue-loader-options"); /***/ }), @@ -1852,7 +1852,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.js\");\n/* harmony import */ var _api__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../api */ \"./src/api.js\");\n/* harmony import */ var _mutation_types__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../mutation-types */ \"./src/store/mutation-types.js\");\n\n\n\n/**\n * @typedef {object} ShowIdentifier\n * @property {string} indexer The indexer name (e.g. `tvdb`)\n * @property {number} id The show ID on the indexer (e.g. `12345`)\n */\n\nconst state = {\n shows: [],\n currentShow: {\n showSlug: null\n },\n loading: {\n total: null,\n current: null,\n display: false,\n finished: false\n },\n queueitems: []\n};\nconst mutations = {\n [_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW](state, show) {\n const existingShow = state.shows.find(_ref => {\n let {\n id,\n indexer\n } = _ref;\n return Number(show.id[show.indexer]) === Number(id[indexer]);\n });\n\n if (!existingShow) {\n console.debug(`Adding ${show.title || show.indexer + String(show.id)} as it wasn't found in the shows array`, show);\n state.shows.push(show);\n return;\n } // Merge new show object over old one\n // this allows detailed queries to update the record\n // without the non-detailed removing the extra data\n\n\n console.debug(`Found ${show.title || show.indexer + String(show.id)} in shows array attempting merge`);\n const newShow = { ...existingShow,\n ...show\n }; // Repair the searchTemplates\n\n newShow.config.searchTemplates = show.config.searchTemplates ? show.config.searchTemplates : existingShow.config.searchTemplates; // Update state\n\n vue__WEBPACK_IMPORTED_MODULE_2__[\"default\"].set(state.shows, state.shows.indexOf(existingShow), newShow);\n console.debug(`Merged ${newShow.title || newShow.indexer + String(newShow.id)}`, newShow);\n },\n\n [_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOWS](state, shows) {\n // If the show is already available, we only want to merge values\n const mergedShows = [];\n\n for (const newShow of shows) {\n const existing = state.shows.find(stateShow => stateShow.id.slug === newShow.id.slug);\n\n if (existing) {\n const {\n sceneAbsoluteNumbering,\n xemAbsoluteNumbering,\n sceneNumbering,\n ...showWithoutDetailed\n } = newShow; // Repair searchTemplates.\n\n const mergedShow = { ...existing,\n ...showWithoutDetailed\n };\n mergedShow.config.searchTemplates = showWithoutDetailed.config.searchTemplates ? showWithoutDetailed.config.searchTemplates : existing.config.searchTemplates;\n mergedShows.push(mergedShow);\n } else {\n mergedShows.push(newShow);\n }\n }\n\n state.shows = mergedShows;\n console.debug(`Added ${shows.length} shows to store`);\n },\n\n [_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW_CONFIG](state, _ref2) {\n let {\n show,\n config\n } = _ref2;\n const existingShow = state.shows.find(_ref3 => {\n let {\n id,\n indexer\n } = _ref3;\n return Number(show.id[show.indexer]) === Number(id[indexer]);\n });\n existingShow.config = { ...existingShow.config,\n ...config\n };\n },\n\n currentShow(state, showSlug) {\n state.currentShow.showSlug = showSlug;\n },\n\n setLoadingTotal(state, total) {\n state.loading.total = total;\n },\n\n setLoadingCurrent(state, current) {\n state.loading.current = current;\n },\n\n updateLoadingCurrent(state, current) {\n state.loading.current += current;\n },\n\n setLoadingDisplay(state, display) {\n state.loading.display = display;\n },\n\n setLoadingFinished(state, finished) {\n state.loading.finished = finished;\n },\n\n [_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW_EPISODE](state, _ref4) {\n let {\n show,\n episodes\n } = _ref4;\n // Creating a new show object (from the state one) as we want to trigger a store update\n const newShow = Object.assign({}, state.shows.find(_ref5 => {\n let {\n id,\n indexer\n } = _ref5;\n return Number(show.id[show.indexer]) === Number(id[indexer]);\n }));\n\n if (!newShow.seasons) {\n newShow.seasons = [];\n } // Recreate an Array with season objects, with each season having an episodes array.\n // This format is used by vue-good-table (displayShow).\n\n\n episodes.forEach(episode => {\n const existingSeason = newShow.seasons.find(season => season.season === episode.season);\n\n if (existingSeason) {\n const foundIndex = existingSeason.children.findIndex(element => element.slug === episode.slug);\n\n if (foundIndex === -1) {\n existingSeason.children.push(episode);\n } else {\n existingSeason.children.splice(foundIndex, 1, episode);\n }\n } else {\n const newSeason = {\n season: episode.season,\n children: [],\n html: false,\n mode: 'span',\n label: 1\n };\n newShow.seasons.push(newSeason);\n newSeason.children.push(episode);\n }\n }); // Update state\n\n const existingShow = state.shows.find(_ref6 => {\n let {\n id,\n indexer\n } = _ref6;\n return Number(show.id[show.indexer]) === Number(id[indexer]);\n });\n vue__WEBPACK_IMPORTED_MODULE_2__[\"default\"].set(state.shows, state.shows.indexOf(existingShow), newShow);\n console.log(`Storing episodes for show ${newShow.title} seasons: ${[...new Set(episodes.map(episode => episode.season))].join(', ')}`);\n },\n\n [_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW_SCENE_EXCEPTION](state, _ref7) {\n let {\n show,\n exception\n } = _ref7;\n // Get current show object\n const currentShow = Object.assign({}, state.shows.find(_ref8 => {\n let {\n id,\n indexer\n } = _ref8;\n return Number(show.id[show.indexer]) === Number(id[indexer]);\n }));\n\n if (currentShow.config.aliases.find(e => e.title === exception.title && e.season === exception.season)) {\n console.warn(`Can't add exception ${exception.title} with season ${exception.season} to show ${currentShow.title} as it already exists.`);\n return;\n }\n\n currentShow.config.aliases.push(exception);\n },\n\n [_mutation_types__WEBPACK_IMPORTED_MODULE_1__.REMOVE_SHOW_SCENE_EXCEPTION](state, _ref9) {\n let {\n show,\n exception\n } = _ref9;\n // Get current show object\n const currentShow = Object.assign({}, state.shows.find(_ref10 => {\n let {\n id,\n indexer\n } = _ref10;\n return Number(show.id[show.indexer]) === Number(id[indexer]);\n }));\n\n if (!currentShow.config.aliases.find(e => e.title === exception.title && e.season === exception.season)) {\n console.warn(`Can't remove exception ${exception.title} with season ${exception.season} to show ${currentShow.title} as it does not exist.`);\n return;\n }\n\n currentShow.config.aliases.splice(currentShow.config.aliases.indexOf(exception), 1);\n },\n\n [_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW_QUEUE_ITEM](state, queueItem) {\n const existingQueueItem = state.queueitems.find(item => item.identifier === queueItem.identifier);\n\n if (existingQueueItem) {\n vue__WEBPACK_IMPORTED_MODULE_2__[\"default\"].set(state.queueitems, state.queueitems.indexOf(existingQueueItem), { ...existingQueueItem,\n ...queueItem\n });\n } else {\n vue__WEBPACK_IMPORTED_MODULE_2__[\"default\"].set(state.queueitems, state.queueitems.length, queueItem);\n }\n },\n\n [_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW_CONFIG_TEMPLATE](state, _ref11) {\n let {\n show,\n template\n } = _ref11;\n // Get current show object\n const currentShow = Object.assign({}, state.shows.find(_ref12 => {\n let {\n id,\n indexer\n } = _ref12;\n return Number(show.id[show.indexer]) === Number(id[indexer]);\n }));\n\n if (currentShow.config.searchTemplates.find(t => t.template === template.pattern)) {\n console.warn(`Can't add template (${template.pattern} to show ${currentShow.title} as it already exists.`);\n return;\n }\n\n currentShow.config.searchTemplates.push(template);\n },\n\n [_mutation_types__WEBPACK_IMPORTED_MODULE_1__.REMOVE_SHOW_CONFIG_TEMPLATE](state, _ref13) {\n let {\n show,\n template\n } = _ref13;\n // Get current show object\n const currentShow = Object.assign({}, state.shows.find(_ref14 => {\n let {\n id,\n indexer\n } = _ref14;\n return Number(show.id[show.indexer]) === Number(id[indexer]);\n }));\n\n if (template.id) {\n currentShow.config.searchTemplates = currentShow.config.searchTemplates.filter(t => t.id !== template.id);\n return;\n }\n\n currentShow.config.searchTemplates = currentShow.config.searchTemplates.filter(t => !(t.title === template.title && t.season === template.season && t.template === template.template));\n },\n\n [_mutation_types__WEBPACK_IMPORTED_MODULE_1__.REMOVE_SHOW](state, removedShow) {\n state.shows = state.shows.filter(existingShow => removedShow.id.slug !== existingShow.id.slug);\n },\n\n initShowsFromStore(state) {\n // Check if the ID exists\n if (localStorage.getItem('shows')) {\n vue__WEBPACK_IMPORTED_MODULE_2__[\"default\"].set(state, 'shows', JSON.parse(localStorage.getItem('shows')));\n }\n }\n\n};\nconst getters = {\n getShowById: state => {\n /**\n * Get a show from the loaded shows state, identified by show slug.\n *\n * @param {string} showSlug Show identifier.\n * @returns {object|undefined} Show object or undefined if not found.\n */\n const getShowById = showSlug => state.shows.find(show => show.id.slug === showSlug);\n\n return getShowById;\n },\n getShowByTitle: state => title => state.shows.find(show => show.title === title),\n getSeason: state => _ref15 => {\n let {\n showSlug,\n season\n } = _ref15;\n const show = state.shows.find(show => show.id.slug === showSlug);\n return show && show.seasons ? show.seasons[season] : undefined;\n },\n getEpisode: state => _ref16 => {\n let {\n showSlug,\n season,\n episode\n } = _ref16;\n const show = state.shows.find(show => show.id.slug === showSlug);\n return show && show.seasons && show.seasons.find(s => s.season === season) ? show.seasons.find(s => s.season === season).children.find(ep => ep.episode === episode) : undefined;\n },\n getCurrentShow: (state, _, rootState) => {\n return state.shows.find(show => show.id.slug === state.currentShow.showSlug) || rootState.defaults.show;\n },\n getShowIndexerUrl: (state, getters, rootState) => show => {\n const indexerConfig = rootState.config.indexers.indexers;\n\n if (!show.indexer || !indexerConfig[show.indexer]) {\n return;\n }\n\n const id = show.id[show.indexer];\n const indexerUrl = indexerConfig[show.indexer].showUrl;\n\n if (show.indexer === 'imdb') {\n return `${indexerUrl}${String(id).padStart(7, '0')}`;\n }\n\n return `${indexerUrl}${id}`;\n },\n showsWithStats: (state, getters, rootState) => {\n if (!state.shows) {\n return [];\n }\n\n return state.shows.map(show => {\n let showStats = rootState.stats.show.stats.find(stat => stat.indexerId === getters.indexerNameToId(show.indexer) && stat.seriesId === show.id[show.indexer]);\n const newLine = '\\u000D';\n let text = 'Unaired';\n let title = '';\n\n if (!showStats) {\n showStats = {\n epDownloaded: 0,\n epSnatched: 0,\n epTotal: 0,\n seriesSize: 0\n };\n }\n\n if (showStats.epTotal >= 1) {\n text = showStats.epDownloaded;\n title = `Downloaded: ${showStats.epDownloaded}`;\n\n if (showStats.epSnatched) {\n text += `+${showStats.epSnatched}`;\n title += `${newLine}Snatched: ${showStats.epSnatched}`;\n }\n\n text += ` / ${showStats.epTotal}`;\n title += `${newLine}Total: ${showStats.epTotal}`;\n }\n\n show.stats = {\n episodes: {\n total: showStats.epTotal,\n snatched: showStats.epSnatched,\n downloaded: showStats.epDownloaded,\n size: showStats.seriesSize\n },\n tooltip: {\n text,\n title,\n percentage: showStats.epDownloaded * 100 / (showStats.epTotal || 1)\n }\n };\n return show;\n });\n },\n showsInLists: (state, getters, rootState) => {\n const {\n layout,\n general\n } = rootState.config;\n const {\n show\n } = layout;\n const {\n showListOrder\n } = show;\n const {\n rootDirs\n } = general;\n const {\n selectedRootIndex,\n local\n } = layout;\n const {\n showFilterByName\n } = local;\n const {\n showsWithStats\n } = getters;\n let shows = null; // Filter root dirs\n\n shows = showsWithStats.filter(show => selectedRootIndex === -1 || show.config.location.includes(rootDirs.slice(1)[selectedRootIndex])); // Filter by text for the banner, simple and smallposter layouts.\n // The Poster layout uses vue-isotope and this does not respond well to changes to the `list` property.\n\n if (layout.home !== 'poster') {\n shows = shows.filter(show => show.title.toLowerCase().includes(showFilterByName.toLowerCase()));\n }\n\n const categorizedShows = showListOrder.filter(listTitle => shows.filter(show => show.config.showLists.map(list => list.toLowerCase()).includes(listTitle.toLowerCase())).length > 0).map(listTitle => ({\n listTitle,\n shows: shows.filter(show => show.config.showLists.map(list => list.toLowerCase()).includes(listTitle.toLowerCase()))\n })); // Check for shows that are not in any category anymore\n\n const uncategorizedShows = shows.filter(show => {\n return show.config.showLists.map(item => {\n return showListOrder.map(list => list.toLowerCase()).includes(item.toLowerCase());\n }).every(item => !item);\n });\n\n if (uncategorizedShows.length > 0) {\n categorizedShows.push({\n listTitle: 'uncategorized',\n shows: uncategorizedShows\n });\n }\n\n if (categorizedShows.length === 0 && uncategorizedShows.length === 0) {\n categorizedShows.push({\n listTitle: 'Series',\n shows: []\n });\n }\n\n return categorizedShows;\n }\n};\n/**\n * An object representing request parameters for getting a show from the API.\n *\n * @typedef {object} ShowGetParameters\n * @property {boolean} detailed Fetch detailed information? (e.g. scene/xem numbering)\n * @property {boolean} episodes Fetch seasons & episodes?\n */\n\nconst actions = {\n /**\n * Get show from API and commit it to the store.\n *\n * @param {*} context The store context.\n * @param {ShowIdentifier&ShowGetParameters} parameters Request parameters.\n * @returns {Promise} The API response.\n */\n getShow(context, _ref17) {\n let {\n showSlug,\n detailed,\n episodes\n } = _ref17;\n return new Promise((resolve, reject) => {\n const {\n commit\n } = context;\n const params = {};\n let timeout = 30000;\n\n if (detailed !== undefined) {\n params.detailed = detailed;\n timeout = 60000;\n timeout = 60000;\n }\n\n if (episodes !== undefined) {\n params.episodes = episodes;\n timeout = 60000;\n }\n\n _api__WEBPACK_IMPORTED_MODULE_0__.api.get(`/series/${showSlug}`, {\n params,\n timeout\n }).then(res => {\n commit(_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW, res.data);\n resolve(res.data);\n }).catch(error => {\n reject(error);\n });\n });\n },\n\n /**\n * Get episdoes for a specified show from API and commit it to the store.\n *\n * @param {*} context - The store context.\n * @param {ShowParameteres} parameters - Request parameters.\n * @returns {Promise} The API response.\n */\n getEpisodes(_ref18, _ref19) {\n let {\n commit,\n getters\n } = _ref18;\n let {\n showSlug,\n season\n } = _ref19;\n return new Promise((resolve, reject) => {\n const {\n getShowById\n } = getters;\n const show = getShowById(showSlug);\n const limit = 1000;\n const params = {\n limit\n };\n\n if (season !== undefined) {\n params.season = season;\n } // Get episodes\n\n\n _api__WEBPACK_IMPORTED_MODULE_0__.api.get(`/series/${showSlug}/episodes`, {\n params\n }).then(response => {\n commit(_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW_EPISODE, {\n show,\n episodes: response.data\n });\n resolve();\n }).catch(error => {\n console.log(`Could not retrieve a episodes for show ${showSlug}, error: ${error}`);\n reject(error);\n });\n });\n },\n\n /**\n * Get shows from API and commit them to the store.\n *\n * @param {*} context - The store context.\n * @param {(ShowIdentifier&ShowGetParameters)[]} shows Shows to get. If not provided, gets the first 1k shows.\n * @returns {undefined|Promise} undefined if `shows` was provided or the API response if not.\n */\n getShows(context, shows) {\n const {\n commit,\n dispatch,\n state,\n rootState\n } = context; // If no shows are provided get the first 1000\n\n if (shows) {\n // Return a specific show list. (not used afaik).\n return shows.forEach(show => dispatch('getShow', show));\n }\n\n return new Promise((resolve, _) => {\n // Loading new shows, commit show loading information to state.\n commit('setLoadingTotal', 0);\n commit('setLoadingCurrent', 0);\n commit('setLoadingDisplay', true);\n const limit = 1000;\n const page = 1;\n const params = {\n limit,\n page\n };\n const pageRequests = [];\n const newShows = []; // Get first page\n\n pageRequests.push(_api__WEBPACK_IMPORTED_MODULE_0__.api.get('/series', {\n params\n }).then(response => {\n commit('setLoadingTotal', Number(response.headers['x-pagination-count']));\n const totalPages = Number(response.headers['x-pagination-total']);\n newShows.push(...response.data);\n commit('updateLoadingCurrent', response.data.length); // Optionally get additional pages\n\n for (let page = 2; page <= totalPages; page++) {\n pageRequests.push(new Promise((resolve, reject) => {\n const newPage = {\n page\n };\n newPage.limit = params.limit;\n return _api__WEBPACK_IMPORTED_MODULE_0__.api.get('/series', {\n params: newPage\n }).then(response => {\n newShows.push(...response.data);\n commit('updateLoadingCurrent', response.data.length);\n resolve();\n }).catch(error => {\n reject(error);\n });\n }));\n }\n }).catch(() => {\n console.log('Could not retrieve a list of shows');\n }).finally(() => {\n Promise.all(pageRequests).then(() => {\n // Commit all the found shows to store.\n commit(_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOWS, newShows); // Update (namespaced) localStorage\n\n const namespace = rootState.config.system.webRoot ? `${rootState.config.system.webRoot}_` : '';\n\n try {\n localStorage.setItem(`${namespace}shows`, JSON.stringify(state.shows));\n } catch (error) {\n console.warn(error);\n }\n\n resolve();\n });\n }));\n });\n },\n\n setShow(_, _ref20) {\n let {\n showSlug,\n data\n } = _ref20;\n // Update show, updated show will arrive over a WebSocket message\n return _api__WEBPACK_IMPORTED_MODULE_0__.api.patch(`series/${showSlug}`, data);\n },\n\n updateShow(context, show) {\n // Update local store\n const {\n commit\n } = context;\n return commit(_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW, show);\n },\n\n addSceneException(context, _ref21) {\n let {\n show,\n exception\n } = _ref21;\n const {\n commit\n } = context;\n commit(_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW_SCENE_EXCEPTION, {\n show,\n exception\n });\n },\n\n removeSceneException(context, _ref22) {\n let {\n show,\n exception\n } = _ref22;\n const {\n commit\n } = context;\n commit(_mutation_types__WEBPACK_IMPORTED_MODULE_1__.REMOVE_SHOW_SCENE_EXCEPTION, {\n show,\n exception\n });\n },\n\n setCurrentShow(context, showSlug) {\n return new Promise(resolve => {\n // Set current show\n const {\n commit\n } = context;\n commit('currentShow', showSlug);\n resolve();\n });\n },\n\n setShowConfig(context, _ref23) {\n let {\n show,\n config\n } = _ref23;\n const {\n commit\n } = context;\n commit(_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW_CONFIG, {\n show,\n config\n });\n },\n\n removeShow(_ref24, show) {\n let {\n commit,\n rootState,\n state\n } = _ref24;\n // Remove the show from store and localStorage (provided through websocket)\n commit(_mutation_types__WEBPACK_IMPORTED_MODULE_1__.REMOVE_SHOW, show); // Update recentShows.\n\n rootState.config.general.recentShows = rootState.config.general.recentShows.filter(recentShow => recentShow.showSlug !== show.id.slug);\n const config = {\n recentShows: rootState.config.general.recentShows\n };\n _api__WEBPACK_IMPORTED_MODULE_0__.api.patch('config/main', config); // Update (namespaced) localStorage\n\n const namespace = rootState.config.system.webRoot ? `${rootState.config.system.webRoot}_` : '';\n localStorage.setItem(`${namespace}shows`, JSON.stringify(state.shows));\n },\n\n updateShowQueueItem(context, queueItem) {\n // Update store's search queue item. (provided through websocket)\n const {\n commit\n } = context;\n return commit(_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW_QUEUE_ITEM, queueItem);\n },\n\n addSearchTemplate(context, _ref25) {\n let {\n show,\n template\n } = _ref25;\n const {\n commit\n } = context;\n commit(_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW_CONFIG_TEMPLATE, {\n show,\n template\n });\n const data = {\n config: {\n searchTemplates: context.getters.getCurrentShow.config.searchTemplates\n }\n };\n return _api__WEBPACK_IMPORTED_MODULE_0__.api.patch(`series/${show.indexer}${show.id[show.indexer]}`, data);\n },\n\n removeSearchTemplate(context, _ref26) {\n let {\n show,\n template\n } = _ref26;\n const {\n commit\n } = context;\n commit(_mutation_types__WEBPACK_IMPORTED_MODULE_1__.REMOVE_SHOW_CONFIG_TEMPLATE, {\n show,\n template\n });\n const data = {\n config: {\n searchTemplates: context.getters.getCurrentShow.config.searchTemplates\n }\n };\n return _api__WEBPACK_IMPORTED_MODULE_0__.api.patch(`series/${show.indexer}${show.id[show.indexer]}`, data);\n }\n\n};\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({\n state,\n mutations,\n getters,\n actions\n});\n\n//# sourceURL=webpack://slim/./src/store/modules/shows.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.js\");\n/* harmony import */ var _api__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../api */ \"./src/api.js\");\n/* harmony import */ var _mutation_types__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../mutation-types */ \"./src/store/mutation-types.js\");\n\n\n\n/**\n * @typedef {object} ShowIdentifier\n * @property {string} indexer The indexer name (e.g. `tvdb`)\n * @property {number} id The show ID on the indexer (e.g. `12345`)\n */\n\nconst state = {\n shows: [],\n currentShow: {\n showSlug: null\n },\n loading: {\n total: null,\n current: null,\n display: false,\n finished: false\n },\n queueitems: []\n};\nconst mutations = {\n [_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW](state, show) {\n const existingShow = state.shows.find(_ref => {\n let {\n id,\n indexer\n } = _ref;\n return Number(show.id[show.indexer]) === Number(id[indexer]);\n });\n\n if (!existingShow) {\n console.debug(`Adding ${show.title || show.indexer + String(show.id)} as it wasn't found in the shows array`, show);\n state.shows.push(show);\n return;\n } // Merge new show object over old one\n // this allows detailed queries to update the record\n // without the non-detailed removing the extra data\n\n\n console.debug(`Found ${show.title || show.indexer + String(show.id)} in shows array attempting merge`);\n const newShow = { ...existingShow,\n ...show\n }; // Repair the searchTemplates\n\n newShow.config.searchTemplates = show.config.searchTemplates ? show.config.searchTemplates : existingShow.config.searchTemplates; // Update state\n\n vue__WEBPACK_IMPORTED_MODULE_2__[\"default\"].set(state.shows, state.shows.indexOf(existingShow), newShow);\n console.debug(`Merged ${newShow.title || newShow.indexer + String(newShow.id)}`, newShow);\n },\n\n [_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOWS](state, shows) {\n // If the show is already available, we only want to merge values\n const mergedShows = [];\n\n for (const newShow of shows) {\n const existing = state.shows.find(stateShow => stateShow.id.slug === newShow.id.slug);\n\n if (existing) {\n const {\n sceneAbsoluteNumbering,\n xemAbsoluteNumbering,\n sceneNumbering,\n ...showWithoutDetailed\n } = newShow; // Repair searchTemplates.\n\n const mergedShow = { ...existing,\n ...showWithoutDetailed\n };\n mergedShow.config.searchTemplates = showWithoutDetailed.config.searchTemplates ? showWithoutDetailed.config.searchTemplates : existing.config.searchTemplates;\n mergedShows.push(mergedShow);\n } else {\n mergedShows.push(newShow);\n }\n }\n\n state.shows = mergedShows;\n console.debug(`Added ${shows.length} shows to store`);\n },\n\n [_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW_CONFIG](state, _ref2) {\n let {\n show,\n config\n } = _ref2;\n const existingShow = state.shows.find(_ref3 => {\n let {\n id,\n indexer\n } = _ref3;\n return Number(show.id[show.indexer]) === Number(id[indexer]);\n });\n existingShow.config = { ...existingShow.config,\n ...config\n };\n },\n\n currentShow(state, showSlug) {\n state.currentShow.showSlug = showSlug;\n },\n\n setLoadingTotal(state, total) {\n state.loading.total = total;\n },\n\n setLoadingCurrent(state, current) {\n state.loading.current = current;\n },\n\n updateLoadingCurrent(state, current) {\n state.loading.current += current;\n },\n\n setLoadingDisplay(state, display) {\n state.loading.display = display;\n },\n\n setLoadingFinished(state, finished) {\n state.loading.finished = finished;\n },\n\n [_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW_EPISODE](state, _ref4) {\n let {\n show,\n episodes\n } = _ref4;\n // Creating a new show object (from the state one) as we want to trigger a store update\n const newShow = Object.assign({}, state.shows.find(_ref5 => {\n let {\n id,\n indexer\n } = _ref5;\n return Number(show.id[show.indexer]) === Number(id[indexer]);\n }));\n\n if (!newShow.seasons) {\n newShow.seasons = [];\n } // Recreate an Array with season objects, with each season having an episodes array.\n // This format is used by vue-good-table (displayShow).\n\n\n episodes.forEach(episode => {\n let existingSeason = newShow.seasons.find(season => season.season === episode.season);\n\n if (existingSeason) {\n // Shallow copy\n existingSeason = { ...existingSeason\n };\n const foundIndex = existingSeason.children.findIndex(element => element.slug === episode.slug);\n\n if (foundIndex === -1) {\n existingSeason.children.push(episode);\n } else {\n existingSeason.children.splice(foundIndex, 1, episode);\n }\n } else {\n const newSeason = {\n season: episode.season,\n children: [],\n html: false,\n mode: 'span',\n label: 1\n };\n newShow.seasons.push(newSeason);\n newSeason.children.push(episode);\n }\n }); // Update state\n\n const existingShow = state.shows.find(_ref6 => {\n let {\n id,\n indexer\n } = _ref6;\n return Number(show.id[show.indexer]) === Number(id[indexer]);\n });\n vue__WEBPACK_IMPORTED_MODULE_2__[\"default\"].set(state.shows, state.shows.indexOf(existingShow), newShow);\n console.log(`Storing episodes for show ${newShow.title} seasons: ${[...new Set(episodes.map(episode => episode.season))].join(', ')}`);\n },\n\n [_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW_SCENE_EXCEPTION](state, _ref7) {\n let {\n show,\n exception\n } = _ref7;\n // Get current show object\n const currentShow = Object.assign({}, state.shows.find(_ref8 => {\n let {\n id,\n indexer\n } = _ref8;\n return Number(show.id[show.indexer]) === Number(id[indexer]);\n }));\n\n if (currentShow.config.aliases.find(e => e.title === exception.title && e.season === exception.season)) {\n console.warn(`Can't add exception ${exception.title} with season ${exception.season} to show ${currentShow.title} as it already exists.`);\n return;\n }\n\n currentShow.config.aliases.push(exception);\n },\n\n [_mutation_types__WEBPACK_IMPORTED_MODULE_1__.REMOVE_SHOW_SCENE_EXCEPTION](state, _ref9) {\n let {\n show,\n exception\n } = _ref9;\n // Get current show object\n const currentShow = Object.assign({}, state.shows.find(_ref10 => {\n let {\n id,\n indexer\n } = _ref10;\n return Number(show.id[show.indexer]) === Number(id[indexer]);\n }));\n\n if (!currentShow.config.aliases.find(e => e.title === exception.title && e.season === exception.season)) {\n console.warn(`Can't remove exception ${exception.title} with season ${exception.season} to show ${currentShow.title} as it does not exist.`);\n return;\n }\n\n currentShow.config.aliases.splice(currentShow.config.aliases.indexOf(exception), 1);\n },\n\n [_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW_QUEUE_ITEM](state, queueItem) {\n const existingQueueItem = state.queueitems.find(item => item.identifier === queueItem.identifier);\n\n if (existingQueueItem) {\n vue__WEBPACK_IMPORTED_MODULE_2__[\"default\"].set(state.queueitems, state.queueitems.indexOf(existingQueueItem), { ...existingQueueItem,\n ...queueItem\n });\n } else {\n vue__WEBPACK_IMPORTED_MODULE_2__[\"default\"].set(state.queueitems, state.queueitems.length, queueItem);\n }\n },\n\n [_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW_CONFIG_TEMPLATE](state, _ref11) {\n let {\n show,\n template\n } = _ref11;\n // Get current show object\n const currentShow = Object.assign({}, state.shows.find(_ref12 => {\n let {\n id,\n indexer\n } = _ref12;\n return Number(show.id[show.indexer]) === Number(id[indexer]);\n }));\n\n if (currentShow.config.searchTemplates.find(t => t.template === template.pattern)) {\n console.warn(`Can't add template (${template.pattern} to show ${currentShow.title} as it already exists.`);\n return;\n }\n\n currentShow.config.searchTemplates.push(template);\n },\n\n [_mutation_types__WEBPACK_IMPORTED_MODULE_1__.REMOVE_SHOW_CONFIG_TEMPLATE](state, _ref13) {\n let {\n show,\n template\n } = _ref13;\n // Get current show object\n const currentShow = Object.assign({}, state.shows.find(_ref14 => {\n let {\n id,\n indexer\n } = _ref14;\n return Number(show.id[show.indexer]) === Number(id[indexer]);\n }));\n\n if (template.id) {\n currentShow.config.searchTemplates = currentShow.config.searchTemplates.filter(t => t.id !== template.id);\n return;\n }\n\n currentShow.config.searchTemplates = currentShow.config.searchTemplates.filter(t => !(t.title === template.title && t.season === template.season && t.template === template.template));\n },\n\n [_mutation_types__WEBPACK_IMPORTED_MODULE_1__.REMOVE_SHOW](state, removedShow) {\n state.shows = state.shows.filter(existingShow => removedShow.id.slug !== existingShow.id.slug);\n },\n\n initShowsFromStore(state) {\n // Check if the ID exists\n if (localStorage.getItem('shows')) {\n vue__WEBPACK_IMPORTED_MODULE_2__[\"default\"].set(state, 'shows', JSON.parse(localStorage.getItem('shows')));\n }\n }\n\n};\nconst getters = {\n getShowById: state => {\n /**\n * Get a show from the loaded shows state, identified by show slug.\n *\n * @param {string} showSlug Show identifier.\n * @returns {object|undefined} Show object or undefined if not found.\n */\n const getShowById = showSlug => state.shows.find(show => show.id.slug === showSlug);\n\n return getShowById;\n },\n getShowByTitle: state => title => state.shows.find(show => show.title === title),\n getSeason: state => _ref15 => {\n let {\n showSlug,\n season\n } = _ref15;\n const show = state.shows.find(show => show.id.slug === showSlug);\n return show && show.seasons ? show.seasons[season] : undefined;\n },\n getEpisode: state => _ref16 => {\n let {\n showSlug,\n season,\n episode\n } = _ref16;\n const show = state.shows.find(show => show.id.slug === showSlug);\n return show && show.seasons && show.seasons.find(s => s.season === season) ? show.seasons.find(s => s.season === season).children.find(ep => ep.episode === episode) : undefined;\n },\n getCurrentShow: (state, _, rootState) => {\n return state.shows.find(show => show.id.slug === state.currentShow.showSlug) || rootState.defaults.show;\n },\n getShowIndexerUrl: (state, getters, rootState) => show => {\n const indexerConfig = rootState.config.indexers.indexers;\n\n if (!show.indexer || !indexerConfig[show.indexer]) {\n return;\n }\n\n const id = show.id[show.indexer];\n const indexerUrl = indexerConfig[show.indexer].showUrl;\n\n if (show.indexer === 'imdb') {\n return `${indexerUrl}${String(id).padStart(7, '0')}`;\n }\n\n return `${indexerUrl}${id}`;\n },\n showsWithStats: (state, getters, rootState) => {\n if (!state.shows) {\n return [];\n }\n\n return state.shows.map(show => {\n let showStats = rootState.stats.show.stats.find(stat => stat.indexerId === getters.indexerNameToId(show.indexer) && stat.seriesId === show.id[show.indexer]);\n const newLine = '\\u000D';\n let text = 'Unaired';\n let title = '';\n\n if (!showStats) {\n showStats = {\n epDownloaded: 0,\n epSnatched: 0,\n epTotal: 0,\n seriesSize: 0\n };\n }\n\n if (showStats.epTotal >= 1) {\n text = showStats.epDownloaded;\n title = `Downloaded: ${showStats.epDownloaded}`;\n\n if (showStats.epSnatched) {\n text += `+${showStats.epSnatched}`;\n title += `${newLine}Snatched: ${showStats.epSnatched}`;\n }\n\n text += ` / ${showStats.epTotal}`;\n title += `${newLine}Total: ${showStats.epTotal}`;\n }\n\n show.stats = {\n episodes: {\n total: showStats.epTotal,\n snatched: showStats.epSnatched,\n downloaded: showStats.epDownloaded,\n size: showStats.seriesSize\n },\n tooltip: {\n text,\n title,\n percentage: showStats.epDownloaded * 100 / (showStats.epTotal || 1)\n }\n };\n return show;\n });\n },\n showsInLists: (state, getters, rootState) => {\n const {\n layout,\n general\n } = rootState.config;\n const {\n show\n } = layout;\n const {\n showListOrder\n } = show;\n const {\n rootDirs\n } = general;\n const {\n selectedRootIndex,\n local\n } = layout;\n const {\n showFilterByName\n } = local;\n const {\n showsWithStats\n } = getters;\n let shows = null; // Filter root dirs\n\n shows = showsWithStats.filter(show => selectedRootIndex === -1 || show.config.location.includes(rootDirs.slice(1)[selectedRootIndex])); // Filter by text for the banner, simple and smallposter layouts.\n // The Poster layout uses vue-isotope and this does not respond well to changes to the `list` property.\n\n if (layout.home !== 'poster') {\n shows = shows.filter(show => show.title.toLowerCase().includes(showFilterByName.toLowerCase()));\n }\n\n const categorizedShows = showListOrder.filter(listTitle => shows.filter(show => show.config.showLists.map(list => list.toLowerCase()).includes(listTitle.toLowerCase())).length > 0).map(listTitle => ({\n listTitle,\n shows: shows.filter(show => show.config.showLists.map(list => list.toLowerCase()).includes(listTitle.toLowerCase()))\n })); // Check for shows that are not in any category anymore\n\n const uncategorizedShows = shows.filter(show => {\n return show.config.showLists.map(item => {\n return showListOrder.map(list => list.toLowerCase()).includes(item.toLowerCase());\n }).every(item => !item);\n });\n\n if (uncategorizedShows.length > 0) {\n categorizedShows.push({\n listTitle: 'uncategorized',\n shows: uncategorizedShows\n });\n }\n\n if (categorizedShows.length === 0 && uncategorizedShows.length === 0) {\n categorizedShows.push({\n listTitle: 'Series',\n shows: []\n });\n }\n\n return categorizedShows;\n }\n};\n/**\n * An object representing request parameters for getting a show from the API.\n *\n * @typedef {object} ShowGetParameters\n * @property {boolean} detailed Fetch detailed information? (e.g. scene/xem numbering)\n * @property {boolean} episodes Fetch seasons & episodes?\n */\n\nconst actions = {\n /**\n * Get show from API and commit it to the store.\n *\n * @param {*} context The store context.\n * @param {ShowIdentifier&ShowGetParameters} parameters Request parameters.\n * @returns {Promise} The API response.\n */\n getShow(context, _ref17) {\n let {\n showSlug,\n detailed,\n episodes\n } = _ref17;\n return new Promise((resolve, reject) => {\n const {\n commit\n } = context;\n const params = {};\n let timeout = 30000;\n\n if (detailed !== undefined) {\n params.detailed = detailed;\n timeout = 60000;\n timeout = 60000;\n }\n\n if (episodes !== undefined) {\n params.episodes = episodes;\n timeout = 60000;\n }\n\n _api__WEBPACK_IMPORTED_MODULE_0__.api.get(`/series/${showSlug}`, {\n params,\n timeout\n }).then(res => {\n commit(_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW, res.data);\n resolve(res.data);\n }).catch(error => {\n reject(error);\n });\n });\n },\n\n /**\n * Get episdoes for a specified show from API and commit it to the store.\n *\n * @param {*} context - The store context.\n * @param {ShowParameteres} parameters - Request parameters.\n * @returns {Promise} The API response.\n */\n getEpisodes(_ref18, _ref19) {\n let {\n commit,\n getters\n } = _ref18;\n let {\n showSlug,\n season\n } = _ref19;\n return new Promise((resolve, reject) => {\n const {\n getShowById\n } = getters;\n const show = getShowById(showSlug);\n const limit = 1000;\n const params = {\n limit\n };\n\n if (season !== undefined) {\n params.season = season;\n } // Get episodes\n\n\n _api__WEBPACK_IMPORTED_MODULE_0__.api.get(`/series/${showSlug}/episodes`, {\n params\n }).then(response => {\n commit(_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW_EPISODE, {\n show,\n episodes: response.data\n });\n resolve();\n }).catch(error => {\n console.log(`Could not retrieve a episodes for show ${showSlug}, error: ${error}`);\n reject(error);\n });\n });\n },\n\n /**\n * Get shows from API and commit them to the store.\n *\n * @param {*} context - The store context.\n * @param {(ShowIdentifier&ShowGetParameters)[]} shows Shows to get. If not provided, gets the first 1k shows.\n * @returns {undefined|Promise} undefined if `shows` was provided or the API response if not.\n */\n getShows(context, shows) {\n const {\n commit,\n dispatch,\n state,\n rootState\n } = context; // If no shows are provided get the first 1000\n\n if (shows) {\n // Return a specific show list. (not used afaik).\n return shows.forEach(show => dispatch('getShow', show));\n }\n\n return new Promise((resolve, _) => {\n // Loading new shows, commit show loading information to state.\n commit('setLoadingTotal', 0);\n commit('setLoadingCurrent', 0);\n commit('setLoadingDisplay', true);\n const limit = 1000;\n const page = 1;\n const params = {\n limit,\n page\n };\n const pageRequests = [];\n const newShows = []; // Get first page\n\n pageRequests.push(_api__WEBPACK_IMPORTED_MODULE_0__.api.get('/series', {\n params\n }).then(response => {\n commit('setLoadingTotal', Number(response.headers['x-pagination-count']));\n const totalPages = Number(response.headers['x-pagination-total']);\n newShows.push(...response.data);\n commit('updateLoadingCurrent', response.data.length); // Optionally get additional pages\n\n for (let page = 2; page <= totalPages; page++) {\n pageRequests.push(new Promise((resolve, reject) => {\n const newPage = {\n page\n };\n newPage.limit = params.limit;\n return _api__WEBPACK_IMPORTED_MODULE_0__.api.get('/series', {\n params: newPage\n }).then(response => {\n newShows.push(...response.data);\n commit('updateLoadingCurrent', response.data.length);\n resolve();\n }).catch(error => {\n reject(error);\n });\n }));\n }\n }).catch(() => {\n console.log('Could not retrieve a list of shows');\n }).finally(() => {\n Promise.all(pageRequests).then(() => {\n // Commit all the found shows to store.\n commit(_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOWS, newShows); // Update (namespaced) localStorage\n\n const namespace = rootState.config.system.webRoot ? `${rootState.config.system.webRoot}_` : '';\n\n try {\n localStorage.setItem(`${namespace}shows`, JSON.stringify(state.shows));\n } catch (error) {\n console.warn(error);\n }\n\n resolve();\n });\n }));\n });\n },\n\n setShow(_, _ref20) {\n let {\n showSlug,\n data\n } = _ref20;\n // Update show, updated show will arrive over a WebSocket message\n return _api__WEBPACK_IMPORTED_MODULE_0__.api.patch(`series/${showSlug}`, data);\n },\n\n updateShow(context, show) {\n // Update local store\n const {\n commit\n } = context;\n return commit(_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW, show);\n },\n\n addSceneException(context, _ref21) {\n let {\n show,\n exception\n } = _ref21;\n const {\n commit\n } = context;\n commit(_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW_SCENE_EXCEPTION, {\n show,\n exception\n });\n },\n\n removeSceneException(context, _ref22) {\n let {\n show,\n exception\n } = _ref22;\n const {\n commit\n } = context;\n commit(_mutation_types__WEBPACK_IMPORTED_MODULE_1__.REMOVE_SHOW_SCENE_EXCEPTION, {\n show,\n exception\n });\n },\n\n setCurrentShow(context, showSlug) {\n return new Promise(resolve => {\n // Set current show\n const {\n commit\n } = context;\n commit('currentShow', showSlug);\n resolve();\n });\n },\n\n setShowConfig(context, _ref23) {\n let {\n show,\n config\n } = _ref23;\n const {\n commit\n } = context;\n commit(_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW_CONFIG, {\n show,\n config\n });\n },\n\n removeShow(_ref24, show) {\n let {\n commit,\n rootState,\n state\n } = _ref24;\n // Remove the show from store and localStorage (provided through websocket)\n commit(_mutation_types__WEBPACK_IMPORTED_MODULE_1__.REMOVE_SHOW, show); // Update recentShows.\n\n rootState.config.general.recentShows = rootState.config.general.recentShows.filter(recentShow => recentShow.showSlug !== show.id.slug);\n const config = {\n recentShows: rootState.config.general.recentShows\n };\n _api__WEBPACK_IMPORTED_MODULE_0__.api.patch('config/main', config); // Update (namespaced) localStorage\n\n const namespace = rootState.config.system.webRoot ? `${rootState.config.system.webRoot}_` : '';\n localStorage.setItem(`${namespace}shows`, JSON.stringify(state.shows));\n },\n\n updateShowQueueItem(context, queueItem) {\n // Update store's search queue item. (provided through websocket)\n const {\n commit\n } = context;\n return commit(_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW_QUEUE_ITEM, queueItem);\n },\n\n addSearchTemplate(context, _ref25) {\n let {\n show,\n template\n } = _ref25;\n const {\n commit\n } = context;\n commit(_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW_CONFIG_TEMPLATE, {\n show,\n template\n });\n const data = {\n config: {\n searchTemplates: context.getters.getCurrentShow.config.searchTemplates\n }\n };\n return _api__WEBPACK_IMPORTED_MODULE_0__.api.patch(`series/${show.indexer}${show.id[show.indexer]}`, data);\n },\n\n removeSearchTemplate(context, _ref26) {\n let {\n show,\n template\n } = _ref26;\n const {\n commit\n } = context;\n commit(_mutation_types__WEBPACK_IMPORTED_MODULE_1__.REMOVE_SHOW_CONFIG_TEMPLATE, {\n show,\n template\n });\n const data = {\n config: {\n searchTemplates: context.getters.getCurrentShow.config.searchTemplates\n }\n };\n return _api__WEBPACK_IMPORTED_MODULE_0__.api.patch(`series/${show.indexer}${show.id[show.indexer]}`, data);\n }\n\n};\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({\n state,\n mutations,\n getters,\n actions\n});\n\n//# sourceURL=webpack://slim/./src/store/modules/shows.js?"); /***/ }), diff --git a/themes/light/assets/js/medusa-runtime.js b/themes/light/assets/js/medusa-runtime.js index 70e6dafefe..a7e166317d 100644 --- a/themes/light/assets/js/medusa-runtime.js +++ b/themes/light/assets/js/medusa-runtime.js @@ -213,7 +213,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var lodash_debounce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! lodash/debounce */ \"./node_modules/lodash/debounce.js\");\n/* harmony import */ var lodash_debounce__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(lodash_debounce__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var vuex__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! vuex */ \"./node_modules/vuex/dist/vuex.esm.js\");\n/* harmony import */ var _helpers__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./helpers */ \"./src/components/helpers/index.js\");\n/* harmony import */ var _utils_core__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../utils/core */ \"./src/utils/core.js\");\n/* harmony import */ var _mixins_manage_cookie__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../mixins/manage-cookie */ \"./src/mixins/manage-cookie.js\");\n/* harmony import */ var _utils_jquery__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../utils/jquery */ \"./src/utils/jquery.js\");\n/* harmony import */ var vue_good_table__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! vue-good-table */ \"./node_modules/vue-good-table/dist/vue-good-table.esm.js\");\n/* harmony import */ var _backstretch_vue__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./backstretch.vue */ \"./src/components/backstretch.vue\");\n/* harmony import */ var _show_header_vue__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./show-header.vue */ \"./src/components/show-header.vue\");\n/* harmony import */ var _subtitle_search_vue__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./subtitle-search.vue */ \"./src/components/subtitle-search.vue\");\n/* harmony import */ var _helpers_quality_pill_vue__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./helpers/quality-pill.vue */ \"./src/components/helpers/quality-pill.vue\");\n/* provided dependency */ var $ = __webpack_require__(/*! jquery */ \"./node_modules/jquery/dist/jquery.js\");\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n\n\n\n\n\n\n\n\n\n\n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({\n name: 'show',\n components: {\n AppLink: _helpers__WEBPACK_IMPORTED_MODULE_1__.AppLink,\n Backstretch: _backstretch_vue__WEBPACK_IMPORTED_MODULE_5__[\"default\"],\n PlotInfo: _helpers__WEBPACK_IMPORTED_MODULE_1__.PlotInfo,\n QualityPill: _helpers_quality_pill_vue__WEBPACK_IMPORTED_MODULE_8__[\"default\"],\n SceneNumberInput: _helpers__WEBPACK_IMPORTED_MODULE_1__.SceneNumberInput,\n SceneNumberAnimeInput: _helpers__WEBPACK_IMPORTED_MODULE_1__.SceneNumberAnimeInput,\n ShowHeader: _show_header_vue__WEBPACK_IMPORTED_MODULE_6__[\"default\"],\n VueGoodTable: vue_good_table__WEBPACK_IMPORTED_MODULE_9__.VueGoodTable\n },\n mixins: [(0,_mixins_manage_cookie__WEBPACK_IMPORTED_MODULE_3__.manageCookieMixin)('displayShow')],\n\n metaInfo() {\n if (!this.show || !this.show.title) {\n return {\n title: 'Medusa'\n };\n }\n\n const {\n title\n } = this.show;\n return {\n title,\n titleTemplate: '%s | Medusa'\n };\n },\n\n props: {\n /**\n * Show Slug\n */\n slug: {\n type: String\n }\n },\n\n data() {\n const {\n getCookie\n } = this;\n const perPageDropdown = [25, 50, 100, 250, 500];\n\n const getPaginationPerPage = () => {\n const rows = getCookie('pagination-perPage');\n\n if (!rows) {\n return 50;\n }\n\n if (!perPageDropdown.includes(rows)) {\n return 500;\n }\n\n return rows;\n };\n\n return {\n invertTable: true,\n subtitleSearchComponents: [],\n columns: [{\n label: 'NFO',\n field: 'content.hasNfo',\n type: 'boolean',\n sortable: false,\n hidden: getCookie('NFO')\n }, {\n label: 'TBN',\n field: 'content.hasTbn',\n type: 'boolean',\n sortable: false,\n hidden: getCookie('TBN')\n }, {\n label: 'Episode',\n field: 'episode',\n type: 'number',\n hidden: getCookie('Episode')\n }, {\n label: 'Abs. #',\n field: 'absoluteNumber',\n type: 'number',\n hidden: getCookie('Abs. #')\n }, {\n label: 'Scene',\n field: 'scene',\n sortable: false,\n hidden: getCookie('Scene')\n }, {\n label: 'Scene Abs. #',\n field: 'sceneAbsolute',\n type: 'number',\n hidden: getCookie('Scene Abs. #')\n }, {\n label: 'Title',\n field: 'title',\n hidden: getCookie('Title')\n }, {\n label: 'File',\n field: 'file.location',\n hidden: getCookie('File')\n }, {\n label: 'Size',\n field: 'file.size',\n type: 'number',\n formatFn: _utils_core__WEBPACK_IMPORTED_MODULE_2__.humanFileSize,\n hidden: getCookie('Size')\n }, {\n // For now i'm using a custom function the parse it. As the type: date, isn't working for us.\n // But the goal is to have this user formatted (as configured in backend)\n label: 'Air date',\n field: this.parseDateFn,\n tdClass: 'align-center',\n sortable: false,\n hidden: getCookie('Air date')\n }, {\n label: 'Download',\n field: 'download',\n sortable: false,\n hidden: getCookie('Download')\n }, {\n label: 'Subtitles',\n field: 'subtitles',\n sortable: false,\n hidden: getCookie('Subtitles')\n }, {\n label: 'Status',\n field: 'status',\n hidden: getCookie('Status')\n }, {\n label: 'Search',\n field: 'search',\n sortable: false,\n hidden: getCookie('Search')\n }],\n perPageDropdown,\n paginationPerPage: getPaginationPerPage(),\n selectedEpisodes: [],\n // We need to keep track of which episode where trying to search, for the vue-modal\n failedSearchEpisodes: [],\n backlogSearchEpisodes: [],\n filterByOverviewStatus: false,\n selectedSearch: 'search action'\n };\n },\n\n computed: { ...(0,vuex__WEBPACK_IMPORTED_MODULE_10__.mapState)({\n shows: state => state.shows.shows,\n subtitles: state => state.config.subtitles,\n configLoaded: state => state.config.layout.fanartBackground !== null,\n layout: state => state.config.layout,\n stateSearch: state => state.config.search\n }),\n ...(0,vuex__WEBPACK_IMPORTED_MODULE_10__.mapGetters)({\n show: 'getCurrentShow',\n getOverviewStatus: 'getOverviewStatus',\n fuzzyParseDateTime: 'fuzzyParseDateTime'\n }),\n\n showSlug() {\n const {\n slug\n } = this;\n return slug || this.$route.query.showslug;\n },\n\n theme() {\n const {\n layout\n } = this;\n const {\n themeName\n } = layout;\n return themeName || 'light';\n },\n\n orderedSeasons() {\n const {\n filterByOverviewStatus,\n invertTable,\n show\n } = this;\n\n if (!show.seasons) {\n return [];\n }\n\n let sortedSeasons = show.seasons.sort((a, b) => a.season - b.season).filter(season => season.season !== 0); // Use the filterOverviewStatus to filter the data based on what's checked in the show-header.\n\n if (filterByOverviewStatus && filterByOverviewStatus.filter(status => status.checked).length < filterByOverviewStatus.length) {\n const filteredSortedSeasons = [];\n\n for (const season of sortedSeasons) {\n const {\n children,\n ...res\n } = season;\n const filteredEpisodes = children.filter(episode => {\n const episodeOverviewStatus = this.getOverviewStatus(episode.status, episode.quality, show.config.qualities);\n const filteredStatus = filterByOverviewStatus.find(overviewStatus => overviewStatus.name === episodeOverviewStatus);\n return !filteredStatus || filteredStatus.checked;\n });\n filteredSortedSeasons.push(Object.assign({\n children: filteredEpisodes\n }, res));\n }\n\n sortedSeasons = filteredSortedSeasons;\n }\n\n if (invertTable) {\n return sortedSeasons.reverse();\n }\n\n return sortedSeasons;\n },\n\n specials() {\n const {\n show\n } = this;\n\n if (!show.seasons) {\n return [];\n }\n\n return show.seasons.filter(season => season.season === 0);\n }\n\n },\n\n mounted() {\n this.loadShow();\n ['load', 'resize'].map(event => {\n return window.addEventListener(event, () => {\n this.reflowLayout();\n });\n });\n $(document.body).on('click', '.seasonCheck', event => {\n const seasCheck = event.currentTarget;\n const seasNo = $(seasCheck).attr('id');\n $('#collapseSeason-' + seasNo).collapse('show');\n const seasonIdentifier = 's' + seasNo;\n $('.epCheck:visible').each((index, element) => {\n const epParts = $(element).attr('id').split('e');\n\n if (epParts[0] === seasonIdentifier) {\n element.checked = seasCheck.checked;\n }\n });\n });\n },\n\n methods: {\n humanFileSize: _utils_core__WEBPACK_IMPORTED_MODULE_2__.humanFileSize,\n ...(0,vuex__WEBPACK_IMPORTED_MODULE_10__.mapActions)({\n getShow: 'getShow',\n // Map `this.getShow()` to `this.$store.dispatch('getShow')`\n getEpisodes: 'getEpisodes',\n setCurrentShow: 'setCurrentShow',\n setRecentShow: 'setRecentShow'\n }),\n\n async loadShow() {\n const {\n setCurrentShow,\n showSlug,\n initializeEpisodes,\n getShow\n } = this; // We need detailed info for the xem / scene exceptions, so let's get it.\n\n await getShow({\n showSlug,\n detailed: true\n }); // Let's tell the store which show we currently want as current.\n // Run this after getShow(), as it will trigger the initializeEpisodes() method.\n\n setCurrentShow(showSlug); // Load all episodes\n\n initializeEpisodes();\n },\n\n statusQualityUpdate(event) {\n const {\n selectedEpisodes,\n setStatus,\n setQuality\n } = this;\n\n if (event.newQuality !== null && event.newQuality !== 'Change quality to:') {\n setQuality(event.newQuality, selectedEpisodes);\n }\n\n if (event.newStatus !== null && event.newStatus !== 'Change status to:') {\n setStatus(event.newStatus, selectedEpisodes);\n }\n },\n\n setQuality(quality, episodes) {\n const {\n showSlug,\n getEpisodes,\n show\n } = this;\n const patchData = {};\n episodes.forEach(episode => {\n patchData[episode.slug] = {\n quality: Number.parseInt(quality, 10)\n };\n });\n api.patch(`series/${show.id.slug}/episodes`, patchData) // eslint-disable-line no-undef\n .then(_ => {\n console.info(`patched show ${show.id.slug} with quality ${quality}`);\n [...new Set(episodes.map(episode => episode.season))].forEach(season => {\n getEpisodes({\n showSlug,\n season\n });\n });\n }).catch(error => {\n console.error(String(error));\n });\n },\n\n setStatus(status, episodes) {\n const {\n showSlug,\n getEpisodes\n } = this;\n const patchData = {};\n episodes.forEach(episode => {\n patchData[episode.slug] = {\n status\n };\n });\n api.patch(`series/${showSlug}/episodes`, patchData) // eslint-disable-line no-undef\n .then(_ => {\n console.info(`patched show ${showSlug} with status ${status}`);\n [...new Set(episodes.map(episode => episode.season))].forEach(season => {\n getEpisodes({\n showSlug,\n season\n });\n });\n }).catch(error => {\n console.error(String(error));\n }); // New status Wanted\n\n if (status === 3) {\n this.$modal.show('query-start-backlog-search', {\n episodes\n });\n } // New status Failed\n\n\n if (status === 11) {\n this.$modal.show('query-mark-failed-and-search', {\n episodes\n });\n }\n },\n\n parseDateFn(row) {\n const {\n fuzzyParseDateTime\n } = this;\n return fuzzyParseDateTime(row.airDate);\n },\n\n rowStyleClassFn(row) {\n const {\n getOverviewStatus,\n show\n } = this;\n\n if (Object.keys(row).includes('vgt_header_id')) {\n return;\n }\n\n const overview = getOverviewStatus(row.status, row.quality, show.config.qualities).toLowerCase().trim();\n return overview;\n },\n\n /**\n * Add (reduce) the total episodes filesize.\n * @param {object} headerRow header row object.\n * @returns {string} - Human readable file size.\n */\n addFileSize(headerRow) {\n return (0,_utils_core__WEBPACK_IMPORTED_MODULE_2__.humanFileSize)(headerRow.children.reduce((a, b) => a + (b.file.size || 0), 0));\n },\n\n searchSubtitle(event, episode, lang) {\n const {\n showSlug,\n getEpisodes,\n show,\n subtitleSearchComponents\n } = this;\n const SubtitleSearchClass = Vue.extend(_subtitle_search_vue__WEBPACK_IMPORTED_MODULE_7__[\"default\"]); // eslint-disable-line no-undef\n\n const instance = new SubtitleSearchClass({\n propsData: {\n show,\n episode,\n key: episode.originalIndex,\n lang\n },\n parent: this\n }); // Update the show, as we downloaded new subtitle(s)\n\n instance.$on('update', event => {\n // This could be replaced by the generic websocket updates in future.\n if (event.reason === 'new subtitles found') {\n getEpisodes({\n showSlug,\n season: episode.season\n });\n }\n });\n const node = document.createElement('div');\n const tableRef = episode.season === 0 ? 'table-specials' : 'table-seasons';\n this.$refs[tableRef].$refs[`row-${episode.originalIndex}`][0].after(node);\n instance.$mount(node);\n subtitleSearchComponents.push(instance);\n },\n\n /**\n * Attaches IMDB tooltip\n */\n reflowLayout: lodash_debounce__WEBPACK_IMPORTED_MODULE_0___default()(() => {\n console.debug('Reflowing layout');\n (0,_utils_jquery__WEBPACK_IMPORTED_MODULE_4__.addQTip)(); // eslint-disable-line no-undef\n }, 1000),\n\n /**\n * Check if any of the episodes in this season does not have the status \"unaired\".\n * If that's the case we want to manual season search icon.\n * @param {object} season - A season object.\n * @returns {Boolean} - true if one of the seasons episodes has a status 'unaired'.\n */\n anyEpisodeNotUnaired(season) {\n return season.children.filter(ep => ep.status !== 'Unaired').length > 0;\n },\n\n episodesInverse(season) {\n const {\n invertTable\n } = this;\n\n if (!season.children) {\n return [];\n }\n\n if (invertTable) {\n return season.children.slice().reverse();\n }\n\n return season.children;\n },\n\n /**\n * Vue-js-modal requires a method, to pass an event to.\n * The event then can be used to assign the value of the episode.\n * @param {Object} event - vue js modal event\n */\n beforeBacklogSearchModalClose(event) {\n this.backlogSearchEpisodes = event.params.episodes;\n },\n\n /**\n * Vue-js-modal requires a method, to pass an event to.\n * The event then can be used to assign the value of the episode.\n * @param {Object} event - vue js modal event\n */\n beforeFailedSearchModalClose(event) {\n this.failedSearchEpisodes = event.params.episodes;\n },\n\n retryDownload(episode) {\n const {\n stateSearch\n } = this;\n return stateSearch.general.failedDownloads.enabled && ['Snatched', 'Snatched (Proper)', 'Snatched (Best)', 'Downloaded'].includes(episode.status);\n },\n\n search(episodes, searchType) {\n const {\n show\n } = this;\n let data = {};\n\n if (episodes) {\n data = {\n showSlug: show.id.slug,\n episodes: [],\n options: {}\n };\n episodes.forEach(episode => {\n data.episodes.push(episode.slug);\n this.$refs[`search-${episode.slug}`].src = 'images/loading16-dark.gif';\n });\n }\n\n api.put(`search/${searchType}`, data) // eslint-disable-line no-undef\n .then(_ => {\n if (episodes.length === 1) {\n console.info(`started search for show: ${show.id.slug} episode: ${episodes[0].slug}`);\n this.$refs[`search-${episodes[0].slug}`].src = 'images/queued.png';\n this.$refs[`search-${episodes[0].slug}`].disabled = true;\n } else {\n console.info('started a full backlog search');\n }\n }).catch(error => {\n console.error(String(error));\n episodes.forEach(episode => {\n data.episodes.push(episode.slug);\n this.$refs[`search-${episodes[0].slug}`].src = 'images/no16.png';\n });\n }).finally(() => {\n this.failedSearchEpisodes = [];\n this.backlogSearchEpisodes = [];\n });\n },\n\n /**\n * Start a backlog search or failed search for the specific episode.\n * A failed search is started depending on the current episodes status.\n * @param {Object} episode - Episode object. If no episode object is passed, a backlog search is started.\n */\n queueSearch(episode) {\n const {\n $modal,\n search,\n retryDownload\n } = this;\n const episodeIdentifier = episode.slug;\n\n if (episode) {\n if (this.$refs[`search-${episodeIdentifier}`].disabled === true) {\n return;\n }\n\n if (retryDownload(episode)) {\n $modal.show('query-mark-failed-and-search', {\n episode\n });\n } else {\n search([episode], 'backlog');\n }\n }\n },\n\n showSubtitleButton(episode) {\n const {\n subtitles,\n show\n } = this;\n return episode.season !== 0 && subtitles.enabled && show.config.subtitlesEnabled && !['Snatched', 'Snatched (Proper)', 'Snatched (Best)', 'Downloaded'].includes(episode.status);\n },\n\n totalSeasonEpisodeSize(season) {\n return season.children.filter(x => x.file && x.file.size > 0).reduce((a, b) => a + b.file.size, 0);\n },\n\n getSeasonExceptions(season) {\n const {\n show\n } = this;\n const {\n config\n } = show;\n const {\n aliases\n } = config;\n let bindData = {\n class: 'display: none'\n }; // Map the indexer season to a xem mapped season.\n // check if the season exception also exists in the xem numbering table\n\n let xemSeasons = [];\n let foundInXem = false;\n\n if (show.xemNumbering.length > 0) {\n const xemResult = show.xemNumbering.filter(x => x.source.season === season); // Create an array with unique seasons\n\n xemSeasons = [...new Set(xemResult.map(item => item.destination.season))];\n foundInXem = Boolean(xemSeasons.length);\n } // Check if there is a season exception for this season\n\n\n if (aliases.find(x => x.season === season)) {\n // If there is not a match on the xem table, display it as a medusa scene exception\n bindData = {\n id: `xem-exception-season-${foundInXem ? xemSeasons[0] : season}`,\n alt: foundInXem ? '[xem]' : '[medusa]',\n src: foundInXem ? 'images/xem.png' : 'images/ico/favicon-16.png',\n title: foundInXem ? xemSeasons.reduce((a, b) => {\n return a.concat(aliases.filter(alias => alias.season === b).map(alias => alias.title));\n }, []).join(', ') : aliases.filter(alias => alias.season === season).map(alias => alias.title).join(', ')\n };\n }\n\n return bindData;\n },\n\n updateEpisodeWatched(episode, watched) {\n const {\n showSlug,\n getEpisodes,\n show\n } = this;\n const patchData = {};\n patchData[episode.slug] = {\n watched\n };\n api.patch(`series/${show.id.slug}/episodes`, patchData) // eslint-disable-line no-undef\n .then(_ => {\n console.info(`patched episode ${episode.slug} with watched set to ${watched}`);\n getEpisodes({\n showSlug,\n season: episode.season\n });\n }).catch(error => {\n console.error(String(error));\n });\n episode.watched = watched;\n },\n\n updatePaginationPerPage(rows) {\n const {\n setCookie\n } = this;\n this.paginationPerPage = rows;\n setCookie('pagination-perPage', rows);\n },\n\n onPageChange(params) {\n this.loadEpisodes(params.currentPage);\n },\n\n neededSeasons(page) {\n const {\n layout,\n paginationPerPage,\n show\n } = this;\n const {\n seasonCount\n } = show;\n\n if (!seasonCount || seasonCount.length === 0) {\n return [];\n }\n\n if (!layout.show.pagination.enable) {\n return seasonCount.filter(season => season.season !== 0).map(season => season.season).reverse();\n }\n\n const seasons = show.seasonCount.length - 1;\n let pagesCount = 1;\n let episodeCount = 0;\n const pages = {};\n\n for (let i = seasons; i >= 0; i--) {\n const {\n season\n } = show.seasonCount[i]; // Exclude specials\n\n if (season === 0) {\n break;\n }\n\n if (pagesCount in pages) {\n pages[pagesCount].push(season);\n } else {\n pages[pagesCount] = [season];\n }\n\n episodeCount += show.seasonCount[i].episodeCount;\n\n if (episodeCount / paginationPerPage > pagesCount) {\n pagesCount++;\n pages[pagesCount] = [season];\n }\n\n if (pagesCount > page) {\n break;\n }\n }\n\n return pages[page] || [];\n },\n\n loadEpisodes(page) {\n const {\n showSlug,\n getEpisodes\n } = this; // Wrap getEpisodes into an async/await function, so we can wait for the season to have been committed\n // before going on to the next one.\n\n const _getEpisodes = async showSlug => {\n for (const season of this.neededSeasons(page)) {\n // We're waiting for the results by design, to give vue the chance to update the dom.\n // If we fire all the promises at once for, for example 25 seasons. We'll overload medusa's app\n // and chance is high a number of requests will timeout.\n await getEpisodes({\n showSlug,\n season\n }); // eslint-disable-line no-await-in-loop\n }\n };\n\n _getEpisodes(showSlug);\n },\n\n initializeEpisodes() {\n const {\n getEpisodes,\n showSlug,\n setRecentShow,\n show\n } = this;\n\n if (!show.seasons && show.seasonCount) {\n // Load episodes for the first page.\n this.loadEpisodes(1); // Always get special episodes if available.\n\n if (show.seasonCount.length > 0 && show.seasonCount[0].season === 0) {\n getEpisodes({\n showSlug,\n season: 0\n });\n }\n }\n\n if (show.id.slug) {\n // For now i'm dumping this here\n setRecentShow({\n showSlug: show.id.slug,\n name: show.title\n });\n }\n },\n\n mobileSelectSearch(event, episode) {\n const {\n $snotify,\n $router,\n queueSearch,\n searchSubtitle,\n show\n } = this;\n\n if (event.target.value === 'forced') {\n queueSearch(episode);\n $snotify.success(`Search started for S${episode.season} E${episode.episode}`);\n }\n\n if (event.target.value === 'manual') {\n // Use the router to navigate to snatchSelection.\n $router.push({\n name: 'snatchSelection',\n query: {\n showslug: show.id.slug,\n season: episode.season,\n episode: episode.episode\n }\n });\n }\n\n if (event.target.value === 'subtitle') {\n searchSubtitle(event, episode);\n }\n\n setTimeout(() => {\n event.target.value = 'search action';\n }, 2000);\n }\n\n },\n filters: {\n sceneObjectToString(value) {\n return `${value.season}x${value.episode}`;\n }\n\n },\n watch: {\n 'show.id.slug': function (slug) {\n // eslint-disable-line object-shorthand\n // Show's slug has changed, meaning the show's page has finished loading.\n if (slug) {\n // This is still technically jQuery. Meaning whe're still letting jQuery do its thing on the entire dom.\n (0,_utils_jquery__WEBPACK_IMPORTED_MODULE_4__.updateSearchIcons)(slug, this);\n }\n }\n }\n});\n\n//# sourceURL=webpack://slim/./src/components/display-show.vue?./node_modules/babel-loader/lib/index.js??clonedRuleSet-1%5B0%5D.rules%5B0%5D!./node_modules/vue-loader/lib/index.js??vue-loader-options"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var lodash_debounce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! lodash/debounce */ \"./node_modules/lodash/debounce.js\");\n/* harmony import */ var lodash_debounce__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(lodash_debounce__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var vuex__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! vuex */ \"./node_modules/vuex/dist/vuex.esm.js\");\n/* harmony import */ var _helpers__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./helpers */ \"./src/components/helpers/index.js\");\n/* harmony import */ var _utils_core__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../utils/core */ \"./src/utils/core.js\");\n/* harmony import */ var _mixins_manage_cookie__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../mixins/manage-cookie */ \"./src/mixins/manage-cookie.js\");\n/* harmony import */ var _utils_jquery__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../utils/jquery */ \"./src/utils/jquery.js\");\n/* harmony import */ var vue_good_table__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! vue-good-table */ \"./node_modules/vue-good-table/dist/vue-good-table.esm.js\");\n/* harmony import */ var _backstretch_vue__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./backstretch.vue */ \"./src/components/backstretch.vue\");\n/* harmony import */ var _show_header_vue__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./show-header.vue */ \"./src/components/show-header.vue\");\n/* harmony import */ var _subtitle_search_vue__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./subtitle-search.vue */ \"./src/components/subtitle-search.vue\");\n/* harmony import */ var _helpers_quality_pill_vue__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./helpers/quality-pill.vue */ \"./src/components/helpers/quality-pill.vue\");\n/* provided dependency */ var $ = __webpack_require__(/*! jquery */ \"./node_modules/jquery/dist/jquery.js\");\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n\n\n\n\n\n\n\n\n\n\n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({\n name: 'show',\n components: {\n AppLink: _helpers__WEBPACK_IMPORTED_MODULE_1__.AppLink,\n Backstretch: _backstretch_vue__WEBPACK_IMPORTED_MODULE_5__[\"default\"],\n PlotInfo: _helpers__WEBPACK_IMPORTED_MODULE_1__.PlotInfo,\n QualityPill: _helpers_quality_pill_vue__WEBPACK_IMPORTED_MODULE_8__[\"default\"],\n SceneNumberInput: _helpers__WEBPACK_IMPORTED_MODULE_1__.SceneNumberInput,\n SceneNumberAnimeInput: _helpers__WEBPACK_IMPORTED_MODULE_1__.SceneNumberAnimeInput,\n ShowHeader: _show_header_vue__WEBPACK_IMPORTED_MODULE_6__[\"default\"],\n VueGoodTable: vue_good_table__WEBPACK_IMPORTED_MODULE_9__.VueGoodTable\n },\n mixins: [(0,_mixins_manage_cookie__WEBPACK_IMPORTED_MODULE_3__.manageCookieMixin)('displayShow')],\n\n metaInfo() {\n if (!this.show || !this.show.title) {\n return {\n title: 'Medusa'\n };\n }\n\n const {\n title\n } = this.show;\n return {\n title,\n titleTemplate: '%s | Medusa'\n };\n },\n\n props: {\n /**\n * Show Slug\n */\n slug: {\n type: String\n }\n },\n\n data() {\n const {\n getCookie\n } = this;\n const perPageDropdown = [25, 50, 100, 250, 500];\n\n const getPaginationPerPage = () => {\n const rows = getCookie('pagination-perPage');\n\n if (!rows) {\n return 50;\n }\n\n if (!perPageDropdown.includes(rows)) {\n return 500;\n }\n\n return rows;\n };\n\n return {\n invertTable: true,\n subtitleSearchComponents: [],\n columns: [{\n label: 'NFO',\n field: 'content.hasNfo',\n type: 'boolean',\n sortable: false,\n hidden: getCookie('NFO')\n }, {\n label: 'TBN',\n field: 'content.hasTbn',\n type: 'boolean',\n sortable: false,\n hidden: getCookie('TBN')\n }, {\n label: 'Episode',\n field: 'episode',\n type: 'number',\n hidden: getCookie('Episode')\n }, {\n label: 'Abs. #',\n field: 'absoluteNumber',\n type: 'number',\n hidden: getCookie('Abs. #')\n }, {\n label: 'Scene',\n field: 'scene',\n sortable: false,\n hidden: getCookie('Scene')\n }, {\n label: 'Scene Abs. #',\n field: 'sceneAbsolute',\n type: 'number',\n hidden: getCookie('Scene Abs. #')\n }, {\n label: 'Title',\n field: 'title',\n hidden: getCookie('Title')\n }, {\n label: 'File',\n field: 'file.location',\n hidden: getCookie('File')\n }, {\n label: 'Size',\n field: 'file.size',\n type: 'number',\n formatFn: _utils_core__WEBPACK_IMPORTED_MODULE_2__.humanFileSize,\n hidden: getCookie('Size')\n }, {\n // For now i'm using a custom function the parse it. As the type: date, isn't working for us.\n // But the goal is to have this user formatted (as configured in backend)\n label: 'Air date',\n field: this.parseDateFn,\n tdClass: 'align-center',\n sortable: false,\n hidden: getCookie('Air date')\n }, {\n label: 'Download',\n field: 'download',\n sortable: false,\n hidden: getCookie('Download')\n }, {\n label: 'Subtitles',\n field: 'subtitles',\n sortable: false,\n hidden: getCookie('Subtitles')\n }, {\n label: 'Status',\n field: 'status',\n hidden: getCookie('Status')\n }, {\n label: 'Search',\n field: 'search',\n sortable: false,\n hidden: getCookie('Search')\n }],\n perPageDropdown,\n paginationPerPage: getPaginationPerPage(),\n selectedEpisodes: [],\n // We need to keep track of which episode where trying to search, for the vue-modal\n failedSearchEpisodes: [],\n backlogSearchEpisodes: [],\n filterByOverviewStatus: false,\n selectedSearch: 'search action'\n };\n },\n\n computed: { ...(0,vuex__WEBPACK_IMPORTED_MODULE_10__.mapState)({\n shows: state => state.shows.shows,\n subtitles: state => state.config.subtitles,\n configLoaded: state => state.config.layout.fanartBackground !== null,\n layout: state => state.config.layout,\n stateSearch: state => state.config.search\n }),\n ...(0,vuex__WEBPACK_IMPORTED_MODULE_10__.mapGetters)({\n show: 'getCurrentShow',\n getOverviewStatus: 'getOverviewStatus',\n fuzzyParseDateTime: 'fuzzyParseDateTime'\n }),\n\n showSlug() {\n const {\n slug\n } = this;\n return slug || this.$route.query.showslug;\n },\n\n theme() {\n const {\n layout\n } = this;\n const {\n themeName\n } = layout;\n return themeName || 'light';\n },\n\n orderedSeasons() {\n const {\n filterByOverviewStatus,\n invertTable,\n show\n } = this;\n\n if (!show.seasons) {\n return [];\n }\n\n const seasons = show.seasons.slice();\n let sortedSeasons = seasons.sort((a, b) => a.season - b.season).filter(season => season.season !== 0); // Use the filterOverviewStatus to filter the data based on what's checked in the show-header.\n\n if (filterByOverviewStatus && filterByOverviewStatus.filter(status => status.checked).length < filterByOverviewStatus.length) {\n const filteredSortedSeasons = [];\n\n for (const season of sortedSeasons) {\n const {\n children,\n ...res\n } = season;\n const filteredEpisodes = children.filter(episode => {\n const episodeOverviewStatus = this.getOverviewStatus(episode.status, episode.quality, show.config.qualities);\n const filteredStatus = filterByOverviewStatus.find(overviewStatus => overviewStatus.name === episodeOverviewStatus);\n return !filteredStatus || filteredStatus.checked;\n });\n filteredSortedSeasons.push(Object.assign({\n children: filteredEpisodes\n }, res));\n }\n\n sortedSeasons = filteredSortedSeasons;\n }\n\n if (invertTable) {\n return sortedSeasons.slice().reverse();\n }\n\n return sortedSeasons;\n },\n\n specials() {\n const {\n show\n } = this;\n\n if (!show.seasons) {\n return [];\n }\n\n return show.seasons.filter(season => season.season === 0);\n }\n\n },\n\n mounted() {\n this.loadShow();\n ['load', 'resize'].map(event => {\n return window.addEventListener(event, () => {\n this.reflowLayout();\n });\n });\n $(document.body).on('click', '.seasonCheck', event => {\n const seasCheck = event.currentTarget;\n const seasNo = $(seasCheck).attr('id');\n $('#collapseSeason-' + seasNo).collapse('show');\n const seasonIdentifier = 's' + seasNo;\n $('.epCheck:visible').each((index, element) => {\n const epParts = $(element).attr('id').split('e');\n\n if (epParts[0] === seasonIdentifier) {\n element.checked = seasCheck.checked;\n }\n });\n });\n },\n\n methods: {\n humanFileSize: _utils_core__WEBPACK_IMPORTED_MODULE_2__.humanFileSize,\n ...(0,vuex__WEBPACK_IMPORTED_MODULE_10__.mapActions)({\n getShow: 'getShow',\n // Map `this.getShow()` to `this.$store.dispatch('getShow')`\n getEpisodes: 'getEpisodes',\n setCurrentShow: 'setCurrentShow',\n setRecentShow: 'setRecentShow'\n }),\n\n async loadShow() {\n const {\n setCurrentShow,\n showSlug,\n initializeEpisodes,\n getShow\n } = this; // We need detailed info for the xem / scene exceptions, so let's get it.\n\n await getShow({\n showSlug,\n detailed: true\n }); // Let's tell the store which show we currently want as current.\n // Run this after getShow(), as it will trigger the initializeEpisodes() method.\n\n setCurrentShow(showSlug); // Load all episodes\n\n initializeEpisodes();\n },\n\n statusQualityUpdate(event) {\n const {\n selectedEpisodes,\n setStatus,\n setQuality\n } = this;\n\n if (event.newQuality !== null && event.newQuality !== 'Change quality to:') {\n setQuality(event.newQuality, selectedEpisodes);\n }\n\n if (event.newStatus !== null && event.newStatus !== 'Change status to:') {\n setStatus(event.newStatus, selectedEpisodes);\n }\n },\n\n setQuality(quality, episodes) {\n const {\n showSlug,\n getEpisodes,\n show\n } = this;\n const patchData = {};\n episodes.forEach(episode => {\n patchData[episode.slug] = {\n quality: Number.parseInt(quality, 10)\n };\n });\n api.patch(`series/${show.id.slug}/episodes`, patchData) // eslint-disable-line no-undef\n .then(_ => {\n console.info(`patched show ${show.id.slug} with quality ${quality}`);\n [...new Set(episodes.map(episode => episode.season))].forEach(season => {\n getEpisodes({\n showSlug,\n season\n });\n });\n }).catch(error => {\n console.error(String(error));\n });\n },\n\n setStatus(status, episodes) {\n const {\n showSlug,\n getEpisodes\n } = this;\n const patchData = {};\n episodes.forEach(episode => {\n patchData[episode.slug] = {\n status\n };\n });\n api.patch(`series/${showSlug}/episodes`, patchData) // eslint-disable-line no-undef\n .then(_ => {\n console.info(`patched show ${showSlug} with status ${status}`);\n [...new Set(episodes.map(episode => episode.season))].forEach(season => {\n getEpisodes({\n showSlug,\n season\n });\n });\n }).catch(error => {\n console.error(String(error));\n }); // New status Wanted\n\n if (status === 3) {\n this.$modal.show('query-start-backlog-search', {\n episodes\n });\n } // New status Failed\n\n\n if (status === 11) {\n this.$modal.show('query-mark-failed-and-search', {\n episodes\n });\n }\n },\n\n parseDateFn(row) {\n const {\n fuzzyParseDateTime\n } = this;\n return fuzzyParseDateTime(row.airDate);\n },\n\n rowStyleClassFn(row) {\n const {\n getOverviewStatus,\n show\n } = this;\n\n if (Object.keys(row).includes('vgt_header_id')) {\n return;\n }\n\n const overview = getOverviewStatus(row.status, row.quality, show.config.qualities).toLowerCase().trim();\n return overview;\n },\n\n /**\n * Add (reduce) the total episodes filesize.\n * @param {object} headerRow header row object.\n * @returns {string} - Human readable file size.\n */\n addFileSize(headerRow) {\n return (0,_utils_core__WEBPACK_IMPORTED_MODULE_2__.humanFileSize)(headerRow.children.reduce((a, b) => a + (b.file.size || 0), 0));\n },\n\n searchSubtitle(event, episode, lang) {\n const {\n showSlug,\n getEpisodes,\n show,\n subtitleSearchComponents\n } = this;\n const SubtitleSearchClass = Vue.extend(_subtitle_search_vue__WEBPACK_IMPORTED_MODULE_7__[\"default\"]); // eslint-disable-line no-undef\n\n const instance = new SubtitleSearchClass({\n propsData: {\n show,\n episode,\n key: episode.originalIndex,\n lang\n },\n parent: this\n }); // Update the show, as we downloaded new subtitle(s)\n\n instance.$on('update', event => {\n // This could be replaced by the generic websocket updates in future.\n if (event.reason === 'new subtitles found') {\n getEpisodes({\n showSlug,\n season: episode.season\n });\n }\n });\n const node = document.createElement('div');\n const tableRef = episode.season === 0 ? 'table-specials' : 'table-seasons';\n this.$refs[tableRef].$refs[`row-${episode.originalIndex}`][0].after(node);\n instance.$mount(node);\n subtitleSearchComponents.push(instance);\n },\n\n /**\n * Attaches IMDB tooltip\n */\n reflowLayout: lodash_debounce__WEBPACK_IMPORTED_MODULE_0___default()(() => {\n console.debug('Reflowing layout');\n (0,_utils_jquery__WEBPACK_IMPORTED_MODULE_4__.addQTip)(); // eslint-disable-line no-undef\n }, 1000),\n\n /**\n * Check if any of the episodes in this season does not have the status \"unaired\".\n * If that's the case we want to manual season search icon.\n * @param {object} season - A season object.\n * @returns {Boolean} - true if one of the seasons episodes has a status 'unaired'.\n */\n anyEpisodeNotUnaired(season) {\n return season.children.filter(ep => ep.status !== 'Unaired').length > 0;\n },\n\n episodesInverse(season) {\n const {\n invertTable\n } = this;\n\n if (!season.children) {\n return [];\n }\n\n if (invertTable) {\n return season.children.slice().reverse();\n }\n\n return season.children;\n },\n\n /**\n * Vue-js-modal requires a method, to pass an event to.\n * The event then can be used to assign the value of the episode.\n * @param {Object} event - vue js modal event\n */\n beforeBacklogSearchModalClose(event) {\n this.backlogSearchEpisodes = event.params.episodes;\n },\n\n /**\n * Vue-js-modal requires a method, to pass an event to.\n * The event then can be used to assign the value of the episode.\n * @param {Object} event - vue js modal event\n */\n beforeFailedSearchModalClose(event) {\n this.failedSearchEpisodes = event.params.episodes;\n },\n\n retryDownload(episode) {\n const {\n stateSearch\n } = this;\n return stateSearch.general.failedDownloads.enabled && ['Snatched', 'Snatched (Proper)', 'Snatched (Best)', 'Downloaded'].includes(episode.status);\n },\n\n search(episodes, searchType) {\n const {\n show\n } = this;\n let data = {};\n\n if (episodes) {\n data = {\n showSlug: show.id.slug,\n episodes: [],\n options: {}\n };\n episodes.forEach(episode => {\n data.episodes.push(episode.slug);\n this.$refs[`search-${episode.slug}`].src = 'images/loading16-dark.gif';\n });\n }\n\n api.put(`search/${searchType}`, data) // eslint-disable-line no-undef\n .then(_ => {\n if (episodes.length === 1) {\n console.info(`started search for show: ${show.id.slug} episode: ${episodes[0].slug}`);\n this.$refs[`search-${episodes[0].slug}`].src = 'images/queued.png';\n this.$refs[`search-${episodes[0].slug}`].disabled = true;\n } else {\n console.info('started a full backlog search');\n }\n }).catch(error => {\n console.error(String(error));\n episodes.forEach(episode => {\n data.episodes.push(episode.slug);\n this.$refs[`search-${episodes[0].slug}`].src = 'images/no16.png';\n });\n }).finally(() => {\n this.failedSearchEpisodes = [];\n this.backlogSearchEpisodes = [];\n });\n },\n\n /**\n * Start a backlog search or failed search for the specific episode.\n * A failed search is started depending on the current episodes status.\n * @param {Object} episode - Episode object. If no episode object is passed, a backlog search is started.\n */\n queueSearch(episode) {\n const {\n $modal,\n search,\n retryDownload\n } = this;\n const episodeIdentifier = episode.slug;\n\n if (episode) {\n if (this.$refs[`search-${episodeIdentifier}`].disabled === true) {\n return;\n }\n\n if (retryDownload(episode)) {\n $modal.show('query-mark-failed-and-search', {\n episode\n });\n } else {\n search([episode], 'backlog');\n }\n }\n },\n\n showSubtitleButton(episode) {\n const {\n subtitles,\n show\n } = this;\n return episode.season !== 0 && subtitles.enabled && show.config.subtitlesEnabled && !['Snatched', 'Snatched (Proper)', 'Snatched (Best)', 'Downloaded'].includes(episode.status);\n },\n\n totalSeasonEpisodeSize(season) {\n return season.children.filter(x => x.file && x.file.size > 0).reduce((a, b) => a + b.file.size, 0);\n },\n\n getSeasonExceptions(season) {\n const {\n show\n } = this;\n const {\n config\n } = show;\n const {\n aliases\n } = config;\n let bindData = {\n class: 'display: none'\n }; // Map the indexer season to a xem mapped season.\n // check if the season exception also exists in the xem numbering table\n\n let xemSeasons = [];\n let foundInXem = false;\n\n if (show.xemNumbering.length > 0) {\n const xemResult = show.xemNumbering.filter(x => x.source.season === season); // Create an array with unique seasons\n\n xemSeasons = [...new Set(xemResult.map(item => item.destination.season))];\n foundInXem = Boolean(xemSeasons.length);\n } // Check if there is a season exception for this season\n\n\n if (aliases.find(x => x.season === season)) {\n // If there is not a match on the xem table, display it as a medusa scene exception\n bindData = {\n id: `xem-exception-season-${foundInXem ? xemSeasons[0] : season}`,\n alt: foundInXem ? '[xem]' : '[medusa]',\n src: foundInXem ? 'images/xem.png' : 'images/ico/favicon-16.png',\n title: foundInXem ? xemSeasons.reduce((a, b) => {\n return a.concat(aliases.filter(alias => alias.season === b).map(alias => alias.title));\n }, []).join(', ') : aliases.filter(alias => alias.season === season).map(alias => alias.title).join(', ')\n };\n }\n\n return bindData;\n },\n\n updateEpisodeWatched(episode, watched) {\n const {\n showSlug,\n getEpisodes,\n show\n } = this;\n const patchData = {};\n patchData[episode.slug] = {\n watched\n };\n api.patch(`series/${show.id.slug}/episodes`, patchData) // eslint-disable-line no-undef\n .then(_ => {\n console.info(`patched episode ${episode.slug} with watched set to ${watched}`);\n getEpisodes({\n showSlug,\n season: episode.season\n });\n }).catch(error => {\n console.error(String(error));\n });\n episode.watched = watched;\n },\n\n updatePaginationPerPage(rows) {\n const {\n setCookie\n } = this;\n this.paginationPerPage = rows;\n setCookie('pagination-perPage', rows);\n },\n\n onPageChange(params) {\n this.loadEpisodes(params.currentPage);\n },\n\n neededSeasons(page) {\n const {\n layout,\n paginationPerPage,\n show\n } = this;\n const {\n seasonCount\n } = show;\n\n if (!seasonCount || seasonCount.length === 0) {\n return [];\n }\n\n if (!layout.show.pagination.enable) {\n return seasonCount.filter(season => season.season !== 0).map(season => season.season).reverse();\n }\n\n const seasons = show.seasonCount.length - 1;\n let pagesCount = 1;\n let episodeCount = 0;\n const pages = {};\n\n for (let i = seasons; i >= 0; i--) {\n const {\n season\n } = show.seasonCount[i]; // Exclude specials\n\n if (season === 0) {\n break;\n }\n\n if (pagesCount in pages) {\n pages[pagesCount].push(season);\n } else {\n pages[pagesCount] = [season];\n }\n\n episodeCount += show.seasonCount[i].episodeCount;\n\n if (episodeCount / paginationPerPage > pagesCount) {\n pagesCount++;\n pages[pagesCount] = [season];\n }\n\n if (pagesCount > page) {\n break;\n }\n }\n\n return pages[page] || [];\n },\n\n loadEpisodes(page) {\n const {\n showSlug,\n getEpisodes\n } = this; // Wrap getEpisodes into an async/await function, so we can wait for the season to have been committed\n // before going on to the next one.\n\n const _getEpisodes = async showSlug => {\n for (const season of this.neededSeasons(page)) {\n // We're waiting for the results by design, to give vue the chance to update the dom.\n // If we fire all the promises at once for, for example 25 seasons. We'll overload medusa's app\n // and chance is high a number of requests will timeout.\n await getEpisodes({\n showSlug,\n season\n }); // eslint-disable-line no-await-in-loop\n }\n };\n\n _getEpisodes(showSlug);\n },\n\n initializeEpisodes() {\n const {\n getEpisodes,\n showSlug,\n setRecentShow,\n show\n } = this;\n\n if (!show.seasons && show.seasonCount) {\n // Load episodes for the first page.\n this.loadEpisodes(1); // Always get special episodes if available.\n\n if (show.seasonCount.length > 0 && show.seasonCount[0].season === 0) {\n getEpisodes({\n showSlug,\n season: 0\n });\n }\n }\n\n if (show.id.slug) {\n // For now i'm dumping this here\n setRecentShow({\n showSlug: show.id.slug,\n name: show.title\n });\n }\n },\n\n mobileSelectSearch(event, episode) {\n const {\n $snotify,\n $router,\n queueSearch,\n searchSubtitle,\n show\n } = this;\n\n if (event.target.value === 'forced') {\n queueSearch(episode);\n $snotify.success(`Search started for S${episode.season} E${episode.episode}`);\n }\n\n if (event.target.value === 'manual') {\n // Use the router to navigate to snatchSelection.\n $router.push({\n name: 'snatchSelection',\n query: {\n showslug: show.id.slug,\n season: episode.season,\n episode: episode.episode\n }\n });\n }\n\n if (event.target.value === 'subtitle') {\n searchSubtitle(event, episode);\n }\n\n setTimeout(() => {\n event.target.value = 'search action';\n }, 2000);\n }\n\n },\n filters: {\n sceneObjectToString(value) {\n return `${value.season}x${value.episode}`;\n }\n\n },\n watch: {\n 'show.id.slug': function (slug) {\n // eslint-disable-line object-shorthand\n // Show's slug has changed, meaning the show's page has finished loading.\n if (slug) {\n // This is still technically jQuery. Meaning whe're still letting jQuery do its thing on the entire dom.\n (0,_utils_jquery__WEBPACK_IMPORTED_MODULE_4__.updateSearchIcons)(slug, this);\n }\n }\n }\n});\n\n//# sourceURL=webpack://slim/./src/components/display-show.vue?./node_modules/babel-loader/lib/index.js??clonedRuleSet-1%5B0%5D.rules%5B0%5D!./node_modules/vue-loader/lib/index.js??vue-loader-options"); /***/ }), @@ -1852,7 +1852,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.js\");\n/* harmony import */ var _api__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../api */ \"./src/api.js\");\n/* harmony import */ var _mutation_types__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../mutation-types */ \"./src/store/mutation-types.js\");\n\n\n\n/**\n * @typedef {object} ShowIdentifier\n * @property {string} indexer The indexer name (e.g. `tvdb`)\n * @property {number} id The show ID on the indexer (e.g. `12345`)\n */\n\nconst state = {\n shows: [],\n currentShow: {\n showSlug: null\n },\n loading: {\n total: null,\n current: null,\n display: false,\n finished: false\n },\n queueitems: []\n};\nconst mutations = {\n [_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW](state, show) {\n const existingShow = state.shows.find(_ref => {\n let {\n id,\n indexer\n } = _ref;\n return Number(show.id[show.indexer]) === Number(id[indexer]);\n });\n\n if (!existingShow) {\n console.debug(`Adding ${show.title || show.indexer + String(show.id)} as it wasn't found in the shows array`, show);\n state.shows.push(show);\n return;\n } // Merge new show object over old one\n // this allows detailed queries to update the record\n // without the non-detailed removing the extra data\n\n\n console.debug(`Found ${show.title || show.indexer + String(show.id)} in shows array attempting merge`);\n const newShow = { ...existingShow,\n ...show\n }; // Repair the searchTemplates\n\n newShow.config.searchTemplates = show.config.searchTemplates ? show.config.searchTemplates : existingShow.config.searchTemplates; // Update state\n\n vue__WEBPACK_IMPORTED_MODULE_2__[\"default\"].set(state.shows, state.shows.indexOf(existingShow), newShow);\n console.debug(`Merged ${newShow.title || newShow.indexer + String(newShow.id)}`, newShow);\n },\n\n [_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOWS](state, shows) {\n // If the show is already available, we only want to merge values\n const mergedShows = [];\n\n for (const newShow of shows) {\n const existing = state.shows.find(stateShow => stateShow.id.slug === newShow.id.slug);\n\n if (existing) {\n const {\n sceneAbsoluteNumbering,\n xemAbsoluteNumbering,\n sceneNumbering,\n ...showWithoutDetailed\n } = newShow; // Repair searchTemplates.\n\n const mergedShow = { ...existing,\n ...showWithoutDetailed\n };\n mergedShow.config.searchTemplates = showWithoutDetailed.config.searchTemplates ? showWithoutDetailed.config.searchTemplates : existing.config.searchTemplates;\n mergedShows.push(mergedShow);\n } else {\n mergedShows.push(newShow);\n }\n }\n\n state.shows = mergedShows;\n console.debug(`Added ${shows.length} shows to store`);\n },\n\n [_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW_CONFIG](state, _ref2) {\n let {\n show,\n config\n } = _ref2;\n const existingShow = state.shows.find(_ref3 => {\n let {\n id,\n indexer\n } = _ref3;\n return Number(show.id[show.indexer]) === Number(id[indexer]);\n });\n existingShow.config = { ...existingShow.config,\n ...config\n };\n },\n\n currentShow(state, showSlug) {\n state.currentShow.showSlug = showSlug;\n },\n\n setLoadingTotal(state, total) {\n state.loading.total = total;\n },\n\n setLoadingCurrent(state, current) {\n state.loading.current = current;\n },\n\n updateLoadingCurrent(state, current) {\n state.loading.current += current;\n },\n\n setLoadingDisplay(state, display) {\n state.loading.display = display;\n },\n\n setLoadingFinished(state, finished) {\n state.loading.finished = finished;\n },\n\n [_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW_EPISODE](state, _ref4) {\n let {\n show,\n episodes\n } = _ref4;\n // Creating a new show object (from the state one) as we want to trigger a store update\n const newShow = Object.assign({}, state.shows.find(_ref5 => {\n let {\n id,\n indexer\n } = _ref5;\n return Number(show.id[show.indexer]) === Number(id[indexer]);\n }));\n\n if (!newShow.seasons) {\n newShow.seasons = [];\n } // Recreate an Array with season objects, with each season having an episodes array.\n // This format is used by vue-good-table (displayShow).\n\n\n episodes.forEach(episode => {\n const existingSeason = newShow.seasons.find(season => season.season === episode.season);\n\n if (existingSeason) {\n const foundIndex = existingSeason.children.findIndex(element => element.slug === episode.slug);\n\n if (foundIndex === -1) {\n existingSeason.children.push(episode);\n } else {\n existingSeason.children.splice(foundIndex, 1, episode);\n }\n } else {\n const newSeason = {\n season: episode.season,\n children: [],\n html: false,\n mode: 'span',\n label: 1\n };\n newShow.seasons.push(newSeason);\n newSeason.children.push(episode);\n }\n }); // Update state\n\n const existingShow = state.shows.find(_ref6 => {\n let {\n id,\n indexer\n } = _ref6;\n return Number(show.id[show.indexer]) === Number(id[indexer]);\n });\n vue__WEBPACK_IMPORTED_MODULE_2__[\"default\"].set(state.shows, state.shows.indexOf(existingShow), newShow);\n console.log(`Storing episodes for show ${newShow.title} seasons: ${[...new Set(episodes.map(episode => episode.season))].join(', ')}`);\n },\n\n [_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW_SCENE_EXCEPTION](state, _ref7) {\n let {\n show,\n exception\n } = _ref7;\n // Get current show object\n const currentShow = Object.assign({}, state.shows.find(_ref8 => {\n let {\n id,\n indexer\n } = _ref8;\n return Number(show.id[show.indexer]) === Number(id[indexer]);\n }));\n\n if (currentShow.config.aliases.find(e => e.title === exception.title && e.season === exception.season)) {\n console.warn(`Can't add exception ${exception.title} with season ${exception.season} to show ${currentShow.title} as it already exists.`);\n return;\n }\n\n currentShow.config.aliases.push(exception);\n },\n\n [_mutation_types__WEBPACK_IMPORTED_MODULE_1__.REMOVE_SHOW_SCENE_EXCEPTION](state, _ref9) {\n let {\n show,\n exception\n } = _ref9;\n // Get current show object\n const currentShow = Object.assign({}, state.shows.find(_ref10 => {\n let {\n id,\n indexer\n } = _ref10;\n return Number(show.id[show.indexer]) === Number(id[indexer]);\n }));\n\n if (!currentShow.config.aliases.find(e => e.title === exception.title && e.season === exception.season)) {\n console.warn(`Can't remove exception ${exception.title} with season ${exception.season} to show ${currentShow.title} as it does not exist.`);\n return;\n }\n\n currentShow.config.aliases.splice(currentShow.config.aliases.indexOf(exception), 1);\n },\n\n [_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW_QUEUE_ITEM](state, queueItem) {\n const existingQueueItem = state.queueitems.find(item => item.identifier === queueItem.identifier);\n\n if (existingQueueItem) {\n vue__WEBPACK_IMPORTED_MODULE_2__[\"default\"].set(state.queueitems, state.queueitems.indexOf(existingQueueItem), { ...existingQueueItem,\n ...queueItem\n });\n } else {\n vue__WEBPACK_IMPORTED_MODULE_2__[\"default\"].set(state.queueitems, state.queueitems.length, queueItem);\n }\n },\n\n [_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW_CONFIG_TEMPLATE](state, _ref11) {\n let {\n show,\n template\n } = _ref11;\n // Get current show object\n const currentShow = Object.assign({}, state.shows.find(_ref12 => {\n let {\n id,\n indexer\n } = _ref12;\n return Number(show.id[show.indexer]) === Number(id[indexer]);\n }));\n\n if (currentShow.config.searchTemplates.find(t => t.template === template.pattern)) {\n console.warn(`Can't add template (${template.pattern} to show ${currentShow.title} as it already exists.`);\n return;\n }\n\n currentShow.config.searchTemplates.push(template);\n },\n\n [_mutation_types__WEBPACK_IMPORTED_MODULE_1__.REMOVE_SHOW_CONFIG_TEMPLATE](state, _ref13) {\n let {\n show,\n template\n } = _ref13;\n // Get current show object\n const currentShow = Object.assign({}, state.shows.find(_ref14 => {\n let {\n id,\n indexer\n } = _ref14;\n return Number(show.id[show.indexer]) === Number(id[indexer]);\n }));\n\n if (template.id) {\n currentShow.config.searchTemplates = currentShow.config.searchTemplates.filter(t => t.id !== template.id);\n return;\n }\n\n currentShow.config.searchTemplates = currentShow.config.searchTemplates.filter(t => !(t.title === template.title && t.season === template.season && t.template === template.template));\n },\n\n [_mutation_types__WEBPACK_IMPORTED_MODULE_1__.REMOVE_SHOW](state, removedShow) {\n state.shows = state.shows.filter(existingShow => removedShow.id.slug !== existingShow.id.slug);\n },\n\n initShowsFromStore(state) {\n // Check if the ID exists\n if (localStorage.getItem('shows')) {\n vue__WEBPACK_IMPORTED_MODULE_2__[\"default\"].set(state, 'shows', JSON.parse(localStorage.getItem('shows')));\n }\n }\n\n};\nconst getters = {\n getShowById: state => {\n /**\n * Get a show from the loaded shows state, identified by show slug.\n *\n * @param {string} showSlug Show identifier.\n * @returns {object|undefined} Show object or undefined if not found.\n */\n const getShowById = showSlug => state.shows.find(show => show.id.slug === showSlug);\n\n return getShowById;\n },\n getShowByTitle: state => title => state.shows.find(show => show.title === title),\n getSeason: state => _ref15 => {\n let {\n showSlug,\n season\n } = _ref15;\n const show = state.shows.find(show => show.id.slug === showSlug);\n return show && show.seasons ? show.seasons[season] : undefined;\n },\n getEpisode: state => _ref16 => {\n let {\n showSlug,\n season,\n episode\n } = _ref16;\n const show = state.shows.find(show => show.id.slug === showSlug);\n return show && show.seasons && show.seasons.find(s => s.season === season) ? show.seasons.find(s => s.season === season).children.find(ep => ep.episode === episode) : undefined;\n },\n getCurrentShow: (state, _, rootState) => {\n return state.shows.find(show => show.id.slug === state.currentShow.showSlug) || rootState.defaults.show;\n },\n getShowIndexerUrl: (state, getters, rootState) => show => {\n const indexerConfig = rootState.config.indexers.indexers;\n\n if (!show.indexer || !indexerConfig[show.indexer]) {\n return;\n }\n\n const id = show.id[show.indexer];\n const indexerUrl = indexerConfig[show.indexer].showUrl;\n\n if (show.indexer === 'imdb') {\n return `${indexerUrl}${String(id).padStart(7, '0')}`;\n }\n\n return `${indexerUrl}${id}`;\n },\n showsWithStats: (state, getters, rootState) => {\n if (!state.shows) {\n return [];\n }\n\n return state.shows.map(show => {\n let showStats = rootState.stats.show.stats.find(stat => stat.indexerId === getters.indexerNameToId(show.indexer) && stat.seriesId === show.id[show.indexer]);\n const newLine = '\\u000D';\n let text = 'Unaired';\n let title = '';\n\n if (!showStats) {\n showStats = {\n epDownloaded: 0,\n epSnatched: 0,\n epTotal: 0,\n seriesSize: 0\n };\n }\n\n if (showStats.epTotal >= 1) {\n text = showStats.epDownloaded;\n title = `Downloaded: ${showStats.epDownloaded}`;\n\n if (showStats.epSnatched) {\n text += `+${showStats.epSnatched}`;\n title += `${newLine}Snatched: ${showStats.epSnatched}`;\n }\n\n text += ` / ${showStats.epTotal}`;\n title += `${newLine}Total: ${showStats.epTotal}`;\n }\n\n show.stats = {\n episodes: {\n total: showStats.epTotal,\n snatched: showStats.epSnatched,\n downloaded: showStats.epDownloaded,\n size: showStats.seriesSize\n },\n tooltip: {\n text,\n title,\n percentage: showStats.epDownloaded * 100 / (showStats.epTotal || 1)\n }\n };\n return show;\n });\n },\n showsInLists: (state, getters, rootState) => {\n const {\n layout,\n general\n } = rootState.config;\n const {\n show\n } = layout;\n const {\n showListOrder\n } = show;\n const {\n rootDirs\n } = general;\n const {\n selectedRootIndex,\n local\n } = layout;\n const {\n showFilterByName\n } = local;\n const {\n showsWithStats\n } = getters;\n let shows = null; // Filter root dirs\n\n shows = showsWithStats.filter(show => selectedRootIndex === -1 || show.config.location.includes(rootDirs.slice(1)[selectedRootIndex])); // Filter by text for the banner, simple and smallposter layouts.\n // The Poster layout uses vue-isotope and this does not respond well to changes to the `list` property.\n\n if (layout.home !== 'poster') {\n shows = shows.filter(show => show.title.toLowerCase().includes(showFilterByName.toLowerCase()));\n }\n\n const categorizedShows = showListOrder.filter(listTitle => shows.filter(show => show.config.showLists.map(list => list.toLowerCase()).includes(listTitle.toLowerCase())).length > 0).map(listTitle => ({\n listTitle,\n shows: shows.filter(show => show.config.showLists.map(list => list.toLowerCase()).includes(listTitle.toLowerCase()))\n })); // Check for shows that are not in any category anymore\n\n const uncategorizedShows = shows.filter(show => {\n return show.config.showLists.map(item => {\n return showListOrder.map(list => list.toLowerCase()).includes(item.toLowerCase());\n }).every(item => !item);\n });\n\n if (uncategorizedShows.length > 0) {\n categorizedShows.push({\n listTitle: 'uncategorized',\n shows: uncategorizedShows\n });\n }\n\n if (categorizedShows.length === 0 && uncategorizedShows.length === 0) {\n categorizedShows.push({\n listTitle: 'Series',\n shows: []\n });\n }\n\n return categorizedShows;\n }\n};\n/**\n * An object representing request parameters for getting a show from the API.\n *\n * @typedef {object} ShowGetParameters\n * @property {boolean} detailed Fetch detailed information? (e.g. scene/xem numbering)\n * @property {boolean} episodes Fetch seasons & episodes?\n */\n\nconst actions = {\n /**\n * Get show from API and commit it to the store.\n *\n * @param {*} context The store context.\n * @param {ShowIdentifier&ShowGetParameters} parameters Request parameters.\n * @returns {Promise} The API response.\n */\n getShow(context, _ref17) {\n let {\n showSlug,\n detailed,\n episodes\n } = _ref17;\n return new Promise((resolve, reject) => {\n const {\n commit\n } = context;\n const params = {};\n let timeout = 30000;\n\n if (detailed !== undefined) {\n params.detailed = detailed;\n timeout = 60000;\n timeout = 60000;\n }\n\n if (episodes !== undefined) {\n params.episodes = episodes;\n timeout = 60000;\n }\n\n _api__WEBPACK_IMPORTED_MODULE_0__.api.get(`/series/${showSlug}`, {\n params,\n timeout\n }).then(res => {\n commit(_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW, res.data);\n resolve(res.data);\n }).catch(error => {\n reject(error);\n });\n });\n },\n\n /**\n * Get episdoes for a specified show from API and commit it to the store.\n *\n * @param {*} context - The store context.\n * @param {ShowParameteres} parameters - Request parameters.\n * @returns {Promise} The API response.\n */\n getEpisodes(_ref18, _ref19) {\n let {\n commit,\n getters\n } = _ref18;\n let {\n showSlug,\n season\n } = _ref19;\n return new Promise((resolve, reject) => {\n const {\n getShowById\n } = getters;\n const show = getShowById(showSlug);\n const limit = 1000;\n const params = {\n limit\n };\n\n if (season !== undefined) {\n params.season = season;\n } // Get episodes\n\n\n _api__WEBPACK_IMPORTED_MODULE_0__.api.get(`/series/${showSlug}/episodes`, {\n params\n }).then(response => {\n commit(_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW_EPISODE, {\n show,\n episodes: response.data\n });\n resolve();\n }).catch(error => {\n console.log(`Could not retrieve a episodes for show ${showSlug}, error: ${error}`);\n reject(error);\n });\n });\n },\n\n /**\n * Get shows from API and commit them to the store.\n *\n * @param {*} context - The store context.\n * @param {(ShowIdentifier&ShowGetParameters)[]} shows Shows to get. If not provided, gets the first 1k shows.\n * @returns {undefined|Promise} undefined if `shows` was provided or the API response if not.\n */\n getShows(context, shows) {\n const {\n commit,\n dispatch,\n state,\n rootState\n } = context; // If no shows are provided get the first 1000\n\n if (shows) {\n // Return a specific show list. (not used afaik).\n return shows.forEach(show => dispatch('getShow', show));\n }\n\n return new Promise((resolve, _) => {\n // Loading new shows, commit show loading information to state.\n commit('setLoadingTotal', 0);\n commit('setLoadingCurrent', 0);\n commit('setLoadingDisplay', true);\n const limit = 1000;\n const page = 1;\n const params = {\n limit,\n page\n };\n const pageRequests = [];\n const newShows = []; // Get first page\n\n pageRequests.push(_api__WEBPACK_IMPORTED_MODULE_0__.api.get('/series', {\n params\n }).then(response => {\n commit('setLoadingTotal', Number(response.headers['x-pagination-count']));\n const totalPages = Number(response.headers['x-pagination-total']);\n newShows.push(...response.data);\n commit('updateLoadingCurrent', response.data.length); // Optionally get additional pages\n\n for (let page = 2; page <= totalPages; page++) {\n pageRequests.push(new Promise((resolve, reject) => {\n const newPage = {\n page\n };\n newPage.limit = params.limit;\n return _api__WEBPACK_IMPORTED_MODULE_0__.api.get('/series', {\n params: newPage\n }).then(response => {\n newShows.push(...response.data);\n commit('updateLoadingCurrent', response.data.length);\n resolve();\n }).catch(error => {\n reject(error);\n });\n }));\n }\n }).catch(() => {\n console.log('Could not retrieve a list of shows');\n }).finally(() => {\n Promise.all(pageRequests).then(() => {\n // Commit all the found shows to store.\n commit(_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOWS, newShows); // Update (namespaced) localStorage\n\n const namespace = rootState.config.system.webRoot ? `${rootState.config.system.webRoot}_` : '';\n\n try {\n localStorage.setItem(`${namespace}shows`, JSON.stringify(state.shows));\n } catch (error) {\n console.warn(error);\n }\n\n resolve();\n });\n }));\n });\n },\n\n setShow(_, _ref20) {\n let {\n showSlug,\n data\n } = _ref20;\n // Update show, updated show will arrive over a WebSocket message\n return _api__WEBPACK_IMPORTED_MODULE_0__.api.patch(`series/${showSlug}`, data);\n },\n\n updateShow(context, show) {\n // Update local store\n const {\n commit\n } = context;\n return commit(_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW, show);\n },\n\n addSceneException(context, _ref21) {\n let {\n show,\n exception\n } = _ref21;\n const {\n commit\n } = context;\n commit(_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW_SCENE_EXCEPTION, {\n show,\n exception\n });\n },\n\n removeSceneException(context, _ref22) {\n let {\n show,\n exception\n } = _ref22;\n const {\n commit\n } = context;\n commit(_mutation_types__WEBPACK_IMPORTED_MODULE_1__.REMOVE_SHOW_SCENE_EXCEPTION, {\n show,\n exception\n });\n },\n\n setCurrentShow(context, showSlug) {\n return new Promise(resolve => {\n // Set current show\n const {\n commit\n } = context;\n commit('currentShow', showSlug);\n resolve();\n });\n },\n\n setShowConfig(context, _ref23) {\n let {\n show,\n config\n } = _ref23;\n const {\n commit\n } = context;\n commit(_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW_CONFIG, {\n show,\n config\n });\n },\n\n removeShow(_ref24, show) {\n let {\n commit,\n rootState,\n state\n } = _ref24;\n // Remove the show from store and localStorage (provided through websocket)\n commit(_mutation_types__WEBPACK_IMPORTED_MODULE_1__.REMOVE_SHOW, show); // Update recentShows.\n\n rootState.config.general.recentShows = rootState.config.general.recentShows.filter(recentShow => recentShow.showSlug !== show.id.slug);\n const config = {\n recentShows: rootState.config.general.recentShows\n };\n _api__WEBPACK_IMPORTED_MODULE_0__.api.patch('config/main', config); // Update (namespaced) localStorage\n\n const namespace = rootState.config.system.webRoot ? `${rootState.config.system.webRoot}_` : '';\n localStorage.setItem(`${namespace}shows`, JSON.stringify(state.shows));\n },\n\n updateShowQueueItem(context, queueItem) {\n // Update store's search queue item. (provided through websocket)\n const {\n commit\n } = context;\n return commit(_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW_QUEUE_ITEM, queueItem);\n },\n\n addSearchTemplate(context, _ref25) {\n let {\n show,\n template\n } = _ref25;\n const {\n commit\n } = context;\n commit(_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW_CONFIG_TEMPLATE, {\n show,\n template\n });\n const data = {\n config: {\n searchTemplates: context.getters.getCurrentShow.config.searchTemplates\n }\n };\n return _api__WEBPACK_IMPORTED_MODULE_0__.api.patch(`series/${show.indexer}${show.id[show.indexer]}`, data);\n },\n\n removeSearchTemplate(context, _ref26) {\n let {\n show,\n template\n } = _ref26;\n const {\n commit\n } = context;\n commit(_mutation_types__WEBPACK_IMPORTED_MODULE_1__.REMOVE_SHOW_CONFIG_TEMPLATE, {\n show,\n template\n });\n const data = {\n config: {\n searchTemplates: context.getters.getCurrentShow.config.searchTemplates\n }\n };\n return _api__WEBPACK_IMPORTED_MODULE_0__.api.patch(`series/${show.indexer}${show.id[show.indexer]}`, data);\n }\n\n};\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({\n state,\n mutations,\n getters,\n actions\n});\n\n//# sourceURL=webpack://slim/./src/store/modules/shows.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.js\");\n/* harmony import */ var _api__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../api */ \"./src/api.js\");\n/* harmony import */ var _mutation_types__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../mutation-types */ \"./src/store/mutation-types.js\");\n\n\n\n/**\n * @typedef {object} ShowIdentifier\n * @property {string} indexer The indexer name (e.g. `tvdb`)\n * @property {number} id The show ID on the indexer (e.g. `12345`)\n */\n\nconst state = {\n shows: [],\n currentShow: {\n showSlug: null\n },\n loading: {\n total: null,\n current: null,\n display: false,\n finished: false\n },\n queueitems: []\n};\nconst mutations = {\n [_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW](state, show) {\n const existingShow = state.shows.find(_ref => {\n let {\n id,\n indexer\n } = _ref;\n return Number(show.id[show.indexer]) === Number(id[indexer]);\n });\n\n if (!existingShow) {\n console.debug(`Adding ${show.title || show.indexer + String(show.id)} as it wasn't found in the shows array`, show);\n state.shows.push(show);\n return;\n } // Merge new show object over old one\n // this allows detailed queries to update the record\n // without the non-detailed removing the extra data\n\n\n console.debug(`Found ${show.title || show.indexer + String(show.id)} in shows array attempting merge`);\n const newShow = { ...existingShow,\n ...show\n }; // Repair the searchTemplates\n\n newShow.config.searchTemplates = show.config.searchTemplates ? show.config.searchTemplates : existingShow.config.searchTemplates; // Update state\n\n vue__WEBPACK_IMPORTED_MODULE_2__[\"default\"].set(state.shows, state.shows.indexOf(existingShow), newShow);\n console.debug(`Merged ${newShow.title || newShow.indexer + String(newShow.id)}`, newShow);\n },\n\n [_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOWS](state, shows) {\n // If the show is already available, we only want to merge values\n const mergedShows = [];\n\n for (const newShow of shows) {\n const existing = state.shows.find(stateShow => stateShow.id.slug === newShow.id.slug);\n\n if (existing) {\n const {\n sceneAbsoluteNumbering,\n xemAbsoluteNumbering,\n sceneNumbering,\n ...showWithoutDetailed\n } = newShow; // Repair searchTemplates.\n\n const mergedShow = { ...existing,\n ...showWithoutDetailed\n };\n mergedShow.config.searchTemplates = showWithoutDetailed.config.searchTemplates ? showWithoutDetailed.config.searchTemplates : existing.config.searchTemplates;\n mergedShows.push(mergedShow);\n } else {\n mergedShows.push(newShow);\n }\n }\n\n state.shows = mergedShows;\n console.debug(`Added ${shows.length} shows to store`);\n },\n\n [_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW_CONFIG](state, _ref2) {\n let {\n show,\n config\n } = _ref2;\n const existingShow = state.shows.find(_ref3 => {\n let {\n id,\n indexer\n } = _ref3;\n return Number(show.id[show.indexer]) === Number(id[indexer]);\n });\n existingShow.config = { ...existingShow.config,\n ...config\n };\n },\n\n currentShow(state, showSlug) {\n state.currentShow.showSlug = showSlug;\n },\n\n setLoadingTotal(state, total) {\n state.loading.total = total;\n },\n\n setLoadingCurrent(state, current) {\n state.loading.current = current;\n },\n\n updateLoadingCurrent(state, current) {\n state.loading.current += current;\n },\n\n setLoadingDisplay(state, display) {\n state.loading.display = display;\n },\n\n setLoadingFinished(state, finished) {\n state.loading.finished = finished;\n },\n\n [_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW_EPISODE](state, _ref4) {\n let {\n show,\n episodes\n } = _ref4;\n // Creating a new show object (from the state one) as we want to trigger a store update\n const newShow = Object.assign({}, state.shows.find(_ref5 => {\n let {\n id,\n indexer\n } = _ref5;\n return Number(show.id[show.indexer]) === Number(id[indexer]);\n }));\n\n if (!newShow.seasons) {\n newShow.seasons = [];\n } // Recreate an Array with season objects, with each season having an episodes array.\n // This format is used by vue-good-table (displayShow).\n\n\n episodes.forEach(episode => {\n let existingSeason = newShow.seasons.find(season => season.season === episode.season);\n\n if (existingSeason) {\n // Shallow copy\n existingSeason = { ...existingSeason\n };\n const foundIndex = existingSeason.children.findIndex(element => element.slug === episode.slug);\n\n if (foundIndex === -1) {\n existingSeason.children.push(episode);\n } else {\n existingSeason.children.splice(foundIndex, 1, episode);\n }\n } else {\n const newSeason = {\n season: episode.season,\n children: [],\n html: false,\n mode: 'span',\n label: 1\n };\n newShow.seasons.push(newSeason);\n newSeason.children.push(episode);\n }\n }); // Update state\n\n const existingShow = state.shows.find(_ref6 => {\n let {\n id,\n indexer\n } = _ref6;\n return Number(show.id[show.indexer]) === Number(id[indexer]);\n });\n vue__WEBPACK_IMPORTED_MODULE_2__[\"default\"].set(state.shows, state.shows.indexOf(existingShow), newShow);\n console.log(`Storing episodes for show ${newShow.title} seasons: ${[...new Set(episodes.map(episode => episode.season))].join(', ')}`);\n },\n\n [_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW_SCENE_EXCEPTION](state, _ref7) {\n let {\n show,\n exception\n } = _ref7;\n // Get current show object\n const currentShow = Object.assign({}, state.shows.find(_ref8 => {\n let {\n id,\n indexer\n } = _ref8;\n return Number(show.id[show.indexer]) === Number(id[indexer]);\n }));\n\n if (currentShow.config.aliases.find(e => e.title === exception.title && e.season === exception.season)) {\n console.warn(`Can't add exception ${exception.title} with season ${exception.season} to show ${currentShow.title} as it already exists.`);\n return;\n }\n\n currentShow.config.aliases.push(exception);\n },\n\n [_mutation_types__WEBPACK_IMPORTED_MODULE_1__.REMOVE_SHOW_SCENE_EXCEPTION](state, _ref9) {\n let {\n show,\n exception\n } = _ref9;\n // Get current show object\n const currentShow = Object.assign({}, state.shows.find(_ref10 => {\n let {\n id,\n indexer\n } = _ref10;\n return Number(show.id[show.indexer]) === Number(id[indexer]);\n }));\n\n if (!currentShow.config.aliases.find(e => e.title === exception.title && e.season === exception.season)) {\n console.warn(`Can't remove exception ${exception.title} with season ${exception.season} to show ${currentShow.title} as it does not exist.`);\n return;\n }\n\n currentShow.config.aliases.splice(currentShow.config.aliases.indexOf(exception), 1);\n },\n\n [_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW_QUEUE_ITEM](state, queueItem) {\n const existingQueueItem = state.queueitems.find(item => item.identifier === queueItem.identifier);\n\n if (existingQueueItem) {\n vue__WEBPACK_IMPORTED_MODULE_2__[\"default\"].set(state.queueitems, state.queueitems.indexOf(existingQueueItem), { ...existingQueueItem,\n ...queueItem\n });\n } else {\n vue__WEBPACK_IMPORTED_MODULE_2__[\"default\"].set(state.queueitems, state.queueitems.length, queueItem);\n }\n },\n\n [_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW_CONFIG_TEMPLATE](state, _ref11) {\n let {\n show,\n template\n } = _ref11;\n // Get current show object\n const currentShow = Object.assign({}, state.shows.find(_ref12 => {\n let {\n id,\n indexer\n } = _ref12;\n return Number(show.id[show.indexer]) === Number(id[indexer]);\n }));\n\n if (currentShow.config.searchTemplates.find(t => t.template === template.pattern)) {\n console.warn(`Can't add template (${template.pattern} to show ${currentShow.title} as it already exists.`);\n return;\n }\n\n currentShow.config.searchTemplates.push(template);\n },\n\n [_mutation_types__WEBPACK_IMPORTED_MODULE_1__.REMOVE_SHOW_CONFIG_TEMPLATE](state, _ref13) {\n let {\n show,\n template\n } = _ref13;\n // Get current show object\n const currentShow = Object.assign({}, state.shows.find(_ref14 => {\n let {\n id,\n indexer\n } = _ref14;\n return Number(show.id[show.indexer]) === Number(id[indexer]);\n }));\n\n if (template.id) {\n currentShow.config.searchTemplates = currentShow.config.searchTemplates.filter(t => t.id !== template.id);\n return;\n }\n\n currentShow.config.searchTemplates = currentShow.config.searchTemplates.filter(t => !(t.title === template.title && t.season === template.season && t.template === template.template));\n },\n\n [_mutation_types__WEBPACK_IMPORTED_MODULE_1__.REMOVE_SHOW](state, removedShow) {\n state.shows = state.shows.filter(existingShow => removedShow.id.slug !== existingShow.id.slug);\n },\n\n initShowsFromStore(state) {\n // Check if the ID exists\n if (localStorage.getItem('shows')) {\n vue__WEBPACK_IMPORTED_MODULE_2__[\"default\"].set(state, 'shows', JSON.parse(localStorage.getItem('shows')));\n }\n }\n\n};\nconst getters = {\n getShowById: state => {\n /**\n * Get a show from the loaded shows state, identified by show slug.\n *\n * @param {string} showSlug Show identifier.\n * @returns {object|undefined} Show object or undefined if not found.\n */\n const getShowById = showSlug => state.shows.find(show => show.id.slug === showSlug);\n\n return getShowById;\n },\n getShowByTitle: state => title => state.shows.find(show => show.title === title),\n getSeason: state => _ref15 => {\n let {\n showSlug,\n season\n } = _ref15;\n const show = state.shows.find(show => show.id.slug === showSlug);\n return show && show.seasons ? show.seasons[season] : undefined;\n },\n getEpisode: state => _ref16 => {\n let {\n showSlug,\n season,\n episode\n } = _ref16;\n const show = state.shows.find(show => show.id.slug === showSlug);\n return show && show.seasons && show.seasons.find(s => s.season === season) ? show.seasons.find(s => s.season === season).children.find(ep => ep.episode === episode) : undefined;\n },\n getCurrentShow: (state, _, rootState) => {\n return state.shows.find(show => show.id.slug === state.currentShow.showSlug) || rootState.defaults.show;\n },\n getShowIndexerUrl: (state, getters, rootState) => show => {\n const indexerConfig = rootState.config.indexers.indexers;\n\n if (!show.indexer || !indexerConfig[show.indexer]) {\n return;\n }\n\n const id = show.id[show.indexer];\n const indexerUrl = indexerConfig[show.indexer].showUrl;\n\n if (show.indexer === 'imdb') {\n return `${indexerUrl}${String(id).padStart(7, '0')}`;\n }\n\n return `${indexerUrl}${id}`;\n },\n showsWithStats: (state, getters, rootState) => {\n if (!state.shows) {\n return [];\n }\n\n return state.shows.map(show => {\n let showStats = rootState.stats.show.stats.find(stat => stat.indexerId === getters.indexerNameToId(show.indexer) && stat.seriesId === show.id[show.indexer]);\n const newLine = '\\u000D';\n let text = 'Unaired';\n let title = '';\n\n if (!showStats) {\n showStats = {\n epDownloaded: 0,\n epSnatched: 0,\n epTotal: 0,\n seriesSize: 0\n };\n }\n\n if (showStats.epTotal >= 1) {\n text = showStats.epDownloaded;\n title = `Downloaded: ${showStats.epDownloaded}`;\n\n if (showStats.epSnatched) {\n text += `+${showStats.epSnatched}`;\n title += `${newLine}Snatched: ${showStats.epSnatched}`;\n }\n\n text += ` / ${showStats.epTotal}`;\n title += `${newLine}Total: ${showStats.epTotal}`;\n }\n\n show.stats = {\n episodes: {\n total: showStats.epTotal,\n snatched: showStats.epSnatched,\n downloaded: showStats.epDownloaded,\n size: showStats.seriesSize\n },\n tooltip: {\n text,\n title,\n percentage: showStats.epDownloaded * 100 / (showStats.epTotal || 1)\n }\n };\n return show;\n });\n },\n showsInLists: (state, getters, rootState) => {\n const {\n layout,\n general\n } = rootState.config;\n const {\n show\n } = layout;\n const {\n showListOrder\n } = show;\n const {\n rootDirs\n } = general;\n const {\n selectedRootIndex,\n local\n } = layout;\n const {\n showFilterByName\n } = local;\n const {\n showsWithStats\n } = getters;\n let shows = null; // Filter root dirs\n\n shows = showsWithStats.filter(show => selectedRootIndex === -1 || show.config.location.includes(rootDirs.slice(1)[selectedRootIndex])); // Filter by text for the banner, simple and smallposter layouts.\n // The Poster layout uses vue-isotope and this does not respond well to changes to the `list` property.\n\n if (layout.home !== 'poster') {\n shows = shows.filter(show => show.title.toLowerCase().includes(showFilterByName.toLowerCase()));\n }\n\n const categorizedShows = showListOrder.filter(listTitle => shows.filter(show => show.config.showLists.map(list => list.toLowerCase()).includes(listTitle.toLowerCase())).length > 0).map(listTitle => ({\n listTitle,\n shows: shows.filter(show => show.config.showLists.map(list => list.toLowerCase()).includes(listTitle.toLowerCase()))\n })); // Check for shows that are not in any category anymore\n\n const uncategorizedShows = shows.filter(show => {\n return show.config.showLists.map(item => {\n return showListOrder.map(list => list.toLowerCase()).includes(item.toLowerCase());\n }).every(item => !item);\n });\n\n if (uncategorizedShows.length > 0) {\n categorizedShows.push({\n listTitle: 'uncategorized',\n shows: uncategorizedShows\n });\n }\n\n if (categorizedShows.length === 0 && uncategorizedShows.length === 0) {\n categorizedShows.push({\n listTitle: 'Series',\n shows: []\n });\n }\n\n return categorizedShows;\n }\n};\n/**\n * An object representing request parameters for getting a show from the API.\n *\n * @typedef {object} ShowGetParameters\n * @property {boolean} detailed Fetch detailed information? (e.g. scene/xem numbering)\n * @property {boolean} episodes Fetch seasons & episodes?\n */\n\nconst actions = {\n /**\n * Get show from API and commit it to the store.\n *\n * @param {*} context The store context.\n * @param {ShowIdentifier&ShowGetParameters} parameters Request parameters.\n * @returns {Promise} The API response.\n */\n getShow(context, _ref17) {\n let {\n showSlug,\n detailed,\n episodes\n } = _ref17;\n return new Promise((resolve, reject) => {\n const {\n commit\n } = context;\n const params = {};\n let timeout = 30000;\n\n if (detailed !== undefined) {\n params.detailed = detailed;\n timeout = 60000;\n timeout = 60000;\n }\n\n if (episodes !== undefined) {\n params.episodes = episodes;\n timeout = 60000;\n }\n\n _api__WEBPACK_IMPORTED_MODULE_0__.api.get(`/series/${showSlug}`, {\n params,\n timeout\n }).then(res => {\n commit(_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW, res.data);\n resolve(res.data);\n }).catch(error => {\n reject(error);\n });\n });\n },\n\n /**\n * Get episdoes for a specified show from API and commit it to the store.\n *\n * @param {*} context - The store context.\n * @param {ShowParameteres} parameters - Request parameters.\n * @returns {Promise} The API response.\n */\n getEpisodes(_ref18, _ref19) {\n let {\n commit,\n getters\n } = _ref18;\n let {\n showSlug,\n season\n } = _ref19;\n return new Promise((resolve, reject) => {\n const {\n getShowById\n } = getters;\n const show = getShowById(showSlug);\n const limit = 1000;\n const params = {\n limit\n };\n\n if (season !== undefined) {\n params.season = season;\n } // Get episodes\n\n\n _api__WEBPACK_IMPORTED_MODULE_0__.api.get(`/series/${showSlug}/episodes`, {\n params\n }).then(response => {\n commit(_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW_EPISODE, {\n show,\n episodes: response.data\n });\n resolve();\n }).catch(error => {\n console.log(`Could not retrieve a episodes for show ${showSlug}, error: ${error}`);\n reject(error);\n });\n });\n },\n\n /**\n * Get shows from API and commit them to the store.\n *\n * @param {*} context - The store context.\n * @param {(ShowIdentifier&ShowGetParameters)[]} shows Shows to get. If not provided, gets the first 1k shows.\n * @returns {undefined|Promise} undefined if `shows` was provided or the API response if not.\n */\n getShows(context, shows) {\n const {\n commit,\n dispatch,\n state,\n rootState\n } = context; // If no shows are provided get the first 1000\n\n if (shows) {\n // Return a specific show list. (not used afaik).\n return shows.forEach(show => dispatch('getShow', show));\n }\n\n return new Promise((resolve, _) => {\n // Loading new shows, commit show loading information to state.\n commit('setLoadingTotal', 0);\n commit('setLoadingCurrent', 0);\n commit('setLoadingDisplay', true);\n const limit = 1000;\n const page = 1;\n const params = {\n limit,\n page\n };\n const pageRequests = [];\n const newShows = []; // Get first page\n\n pageRequests.push(_api__WEBPACK_IMPORTED_MODULE_0__.api.get('/series', {\n params\n }).then(response => {\n commit('setLoadingTotal', Number(response.headers['x-pagination-count']));\n const totalPages = Number(response.headers['x-pagination-total']);\n newShows.push(...response.data);\n commit('updateLoadingCurrent', response.data.length); // Optionally get additional pages\n\n for (let page = 2; page <= totalPages; page++) {\n pageRequests.push(new Promise((resolve, reject) => {\n const newPage = {\n page\n };\n newPage.limit = params.limit;\n return _api__WEBPACK_IMPORTED_MODULE_0__.api.get('/series', {\n params: newPage\n }).then(response => {\n newShows.push(...response.data);\n commit('updateLoadingCurrent', response.data.length);\n resolve();\n }).catch(error => {\n reject(error);\n });\n }));\n }\n }).catch(() => {\n console.log('Could not retrieve a list of shows');\n }).finally(() => {\n Promise.all(pageRequests).then(() => {\n // Commit all the found shows to store.\n commit(_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOWS, newShows); // Update (namespaced) localStorage\n\n const namespace = rootState.config.system.webRoot ? `${rootState.config.system.webRoot}_` : '';\n\n try {\n localStorage.setItem(`${namespace}shows`, JSON.stringify(state.shows));\n } catch (error) {\n console.warn(error);\n }\n\n resolve();\n });\n }));\n });\n },\n\n setShow(_, _ref20) {\n let {\n showSlug,\n data\n } = _ref20;\n // Update show, updated show will arrive over a WebSocket message\n return _api__WEBPACK_IMPORTED_MODULE_0__.api.patch(`series/${showSlug}`, data);\n },\n\n updateShow(context, show) {\n // Update local store\n const {\n commit\n } = context;\n return commit(_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW, show);\n },\n\n addSceneException(context, _ref21) {\n let {\n show,\n exception\n } = _ref21;\n const {\n commit\n } = context;\n commit(_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW_SCENE_EXCEPTION, {\n show,\n exception\n });\n },\n\n removeSceneException(context, _ref22) {\n let {\n show,\n exception\n } = _ref22;\n const {\n commit\n } = context;\n commit(_mutation_types__WEBPACK_IMPORTED_MODULE_1__.REMOVE_SHOW_SCENE_EXCEPTION, {\n show,\n exception\n });\n },\n\n setCurrentShow(context, showSlug) {\n return new Promise(resolve => {\n // Set current show\n const {\n commit\n } = context;\n commit('currentShow', showSlug);\n resolve();\n });\n },\n\n setShowConfig(context, _ref23) {\n let {\n show,\n config\n } = _ref23;\n const {\n commit\n } = context;\n commit(_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW_CONFIG, {\n show,\n config\n });\n },\n\n removeShow(_ref24, show) {\n let {\n commit,\n rootState,\n state\n } = _ref24;\n // Remove the show from store and localStorage (provided through websocket)\n commit(_mutation_types__WEBPACK_IMPORTED_MODULE_1__.REMOVE_SHOW, show); // Update recentShows.\n\n rootState.config.general.recentShows = rootState.config.general.recentShows.filter(recentShow => recentShow.showSlug !== show.id.slug);\n const config = {\n recentShows: rootState.config.general.recentShows\n };\n _api__WEBPACK_IMPORTED_MODULE_0__.api.patch('config/main', config); // Update (namespaced) localStorage\n\n const namespace = rootState.config.system.webRoot ? `${rootState.config.system.webRoot}_` : '';\n localStorage.setItem(`${namespace}shows`, JSON.stringify(state.shows));\n },\n\n updateShowQueueItem(context, queueItem) {\n // Update store's search queue item. (provided through websocket)\n const {\n commit\n } = context;\n return commit(_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW_QUEUE_ITEM, queueItem);\n },\n\n addSearchTemplate(context, _ref25) {\n let {\n show,\n template\n } = _ref25;\n const {\n commit\n } = context;\n commit(_mutation_types__WEBPACK_IMPORTED_MODULE_1__.ADD_SHOW_CONFIG_TEMPLATE, {\n show,\n template\n });\n const data = {\n config: {\n searchTemplates: context.getters.getCurrentShow.config.searchTemplates\n }\n };\n return _api__WEBPACK_IMPORTED_MODULE_0__.api.patch(`series/${show.indexer}${show.id[show.indexer]}`, data);\n },\n\n removeSearchTemplate(context, _ref26) {\n let {\n show,\n template\n } = _ref26;\n const {\n commit\n } = context;\n commit(_mutation_types__WEBPACK_IMPORTED_MODULE_1__.REMOVE_SHOW_CONFIG_TEMPLATE, {\n show,\n template\n });\n const data = {\n config: {\n searchTemplates: context.getters.getCurrentShow.config.searchTemplates\n }\n };\n return _api__WEBPACK_IMPORTED_MODULE_0__.api.patch(`series/${show.indexer}${show.id[show.indexer]}`, data);\n }\n\n};\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({\n state,\n mutations,\n getters,\n actions\n});\n\n//# sourceURL=webpack://slim/./src/store/modules/shows.js?"); /***/ }), From a3dce9e80ce0b322d737e4388d1c01bbd89c2b7b Mon Sep 17 00:00:00 2001 From: p0psicles Date: Fri, 11 Mar 2022 15:54:19 +0100 Subject: [PATCH 4/9] Prevent infinite loop in show-results.vue --- themes-default/slim/src/components/show-results.vue | 2 +- themes/dark/assets/js/medusa-runtime.js | 2 +- themes/light/assets/js/medusa-runtime.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/themes-default/slim/src/components/show-results.vue b/themes-default/slim/src/components/show-results.vue index eff1e46a63..f3c847e8b9 100644 --- a/themes-default/slim/src/components/show-results.vue +++ b/themes-default/slim/src/components/show-results.vue @@ -264,7 +264,7 @@ export default { let results = []; const getLastHistoryStatus = result => { - const sortedHistory = episodeHistory.sort(item => item.actionDate).reverse(); + const sortedHistory = episodeHistory.splice().sort(item => item.actionDate).reverse(); for (const historyRow of sortedHistory) { if (historyRow.resource === result.release && historyRow.size === result.size) { return historyRow.statusName.toLocaleLowerCase(); diff --git a/themes/dark/assets/js/medusa-runtime.js b/themes/dark/assets/js/medusa-runtime.js index a7e166317d..95ca5a215d 100644 --- a/themes/dark/assets/js/medusa-runtime.js +++ b/themes/dark/assets/js/medusa-runtime.js @@ -1137,7 +1137,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _api__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../api */ \"./src/api.js\");\n/* harmony import */ var vuex__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! vuex */ \"./node_modules/vuex/dist/vuex.esm.js\");\n/* harmony import */ var vue_good_table__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! vue-good-table */ \"./node_modules/vue-good-table/dist/vue-good-table.esm.js\");\n/* harmony import */ var _mixins_manage_cookie__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../mixins/manage-cookie */ \"./src/mixins/manage-cookie.js\");\n/* harmony import */ var _helpers__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./helpers */ \"./src/components/helpers/index.js\");\n/* harmony import */ var _helpers_quality_pill_vue__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./helpers/quality-pill.vue */ \"./src/components/helpers/quality-pill.vue\");\n/* harmony import */ var _utils_core__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../utils/core */ \"./src/utils/core.js\");\n/* harmony import */ var v_tooltip__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! v-tooltip */ \"./node_modules/v-tooltip/dist/v-tooltip.esm.js\");\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n\n\n\n\n\n\n\n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({\n name: 'show-results',\n components: {\n VueGoodTable: vue_good_table__WEBPACK_IMPORTED_MODULE_6__.VueGoodTable,\n StateSwitch: _helpers__WEBPACK_IMPORTED_MODULE_2__.StateSwitch,\n QualityPill: _helpers_quality_pill_vue__WEBPACK_IMPORTED_MODULE_3__[\"default\"]\n },\n directives: {\n tooltip: v_tooltip__WEBPACK_IMPORTED_MODULE_5__.VTooltip\n },\n mixins: [(0,_mixins_manage_cookie__WEBPACK_IMPORTED_MODULE_1__.manageCookieMixin)('showResults')],\n props: {\n show: {\n type: Object,\n required: true\n },\n season: {\n type: Number,\n required: true\n },\n episode: {\n type: Number,\n required: false\n },\n searchType: {\n type: String,\n default: 'episode'\n }\n },\n\n data() {\n const {\n getCookie\n } = this;\n const perPageDropdown = [25, 50, 100, 250, 500];\n\n const getPaginationPerPage = () => {\n const rows = getCookie('pagination-perPage');\n\n if (!rows) {\n return 50;\n }\n\n if (!perPageDropdown.includes(rows)) {\n return 500;\n }\n\n return rows;\n };\n\n return {\n columns: [{\n label: 'Release',\n field: 'release',\n tdClass: 'release',\n filterOptions: {\n enabled: true\n },\n hidden: getCookie('Release')\n }, {\n label: 'Group',\n field: 'releaseGroup',\n filterOptions: {\n enabled: true\n },\n hidden: getCookie('Group')\n }, {\n label: 'Provider',\n field: 'provider.name',\n filterOptions: {\n enabled: true\n },\n hidden: getCookie('Provider')\n }, {\n label: 'Quality',\n field: 'quality',\n type: 'number',\n filterOptions: {\n customFilter: true\n },\n hidden: getCookie('Quality')\n }, {\n label: 'Seeds',\n field: 'seeders',\n type: 'number',\n hidden: getCookie('Seeds')\n }, {\n label: 'Peers',\n field: 'leechers',\n type: 'number',\n hidden: getCookie('Peers')\n }, {\n label: 'Size',\n field: 'size',\n formatFn: _utils_core__WEBPACK_IMPORTED_MODULE_4__.humanFileSize,\n type: 'number',\n hidden: getCookie('Size')\n }, {\n label: 'Added',\n field: 'dateAdded',\n type: 'date',\n sortable: true,\n dateInputFormat: 'yyyy-MM-dd\\'T\\'HH:mm:ss',\n // E.g. 07-09-2017 19:16:25\n dateOutputFormat: 'yyyy/MM/dd HH:mm:ss',\n hidden: getCookie('Added')\n }, {\n label: 'Published',\n field: 'pubdate',\n type: 'date',\n sortable: true,\n dateInputFormat: 'yyyy-MM-dd\\'T\\'HH:mm:ssXXX',\n dateOutputFormat: 'yyyy-MM-dd HH:mm:ss',\n hidden: getCookie('Published')\n }, {\n label: 'Updated',\n field: 'time',\n type: 'date',\n sortable: true,\n dateInputFormat: 'yyyy-MM-dd\\'T\\'HH:mm:ss',\n dateOutputFormat: 'yyyy/MM/dd HH:mm:ss',\n hidden: getCookie('Updated')\n }, {\n label: 'Snatch',\n field: 'snatch',\n sortable: false\n }],\n loading: false,\n loadingMessage: '',\n perPageDropdown,\n getPaginationPerPage\n };\n },\n\n async mounted() {\n const {\n forceSearch,\n getProviders,\n getProviderCacheResults,\n show,\n season,\n episode\n } = this;\n await getProviders();\n const result = await getProviderCacheResults({\n showSlug: show.id.slug,\n season,\n episode\n }); // TODO: put a modal in between\n\n if (result.providersSearched > 0 && result.totalSearchResults.length === 0) {\n forceSearch();\n }\n },\n\n computed: { ...(0,vuex__WEBPACK_IMPORTED_MODULE_7__.mapState)({\n config: state => state.config.general,\n layout: state => state.config.layout,\n search: state => state.config.search,\n providers: state => state.provider.providers,\n queueitems: state => state.queue.queueitems,\n history: state => state.history.episodeHistory\n }),\n ...(0,vuex__WEBPACK_IMPORTED_MODULE_7__.mapGetters)({\n fuzzyParseDateTime: 'fuzzyParseDateTime',\n effectiveIgnored: 'effectiveIgnored',\n effectiveRequired: 'effectiveRequired'\n }),\n\n combinedResults() {\n const {\n episode,\n episodeHistory,\n providers,\n season,\n show\n } = this;\n let results = [];\n\n const getLastHistoryStatus = result => {\n const sortedHistory = episodeHistory.sort(item => item.actionDate).reverse();\n\n for (const historyRow of sortedHistory) {\n if (historyRow.resource === result.release && historyRow.size === result.size) {\n return historyRow.statusName.toLocaleLowerCase();\n }\n }\n\n return 'skipped';\n };\n\n for (const provider of Object.values(providers).filter(provider => provider.config.enabled)) {\n if (provider.cache && provider.cache.length > 0) {\n results = [...results, ...provider.cache.filter(searchResult => {\n if (episode) {\n return searchResult.showSlug === show.id.slug && searchResult.season === season && searchResult.episodes.includes(episode);\n }\n\n return searchResult.showSlug === show.id.slug && searchResult.season === season && searchResult.seasonPack;\n }).map(result => {\n return { ...result,\n status: getLastHistoryStatus(result)\n };\n })];\n }\n }\n\n return results;\n },\n\n /**\n * Helper to get the current episode or season slug.\n * @returns {string} episode slug.\n */\n episodeSlug() {\n const {\n season,\n episode\n } = this;\n return (0,_utils_core__WEBPACK_IMPORTED_MODULE_4__.episodeToSlug)(season, episode);\n },\n\n /**\n * Helper to check if showSlug and season/episode slug exist.\n * @returns {array} history for episode or empty array.\n */\n episodeHistory() {\n const {\n episodeSlug,\n history,\n show\n } = this;\n\n if (!history[show.id.slug] || !history[show.id.slug][episodeSlug]) {\n return [];\n }\n\n return history[show.id.slug][episodeSlug];\n }\n\n },\n methods: {\n humanFileSize: _utils_core__WEBPACK_IMPORTED_MODULE_4__.humanFileSize,\n ...(0,vuex__WEBPACK_IMPORTED_MODULE_7__.mapActions)({\n getProviders: 'getProviders',\n getProviderCacheResults: 'getProviderCacheResults',\n getShowEpisodeHistory: 'getShowEpisodeHistory'\n }),\n\n close() {\n this.$emit('close'); // Destroy the vue listeners, etc\n\n this.$destroy(); // Remove the element from the DOM\n\n this.$el.remove();\n },\n\n getProviderResults() {\n const {\n episode,\n getProviderCacheResults,\n season,\n show\n } = this;\n getProviderCacheResults({\n showSlug: show.id.slug,\n season,\n episode\n });\n },\n\n forceSearch() {\n const {\n episode,\n episodeSlug,\n season,\n show\n } = this;\n let data = {};\n data = {\n showSlug: show.id.slug,\n options: {},\n [episode ? 'episodes' : 'season']: [episodeSlug]\n };\n this.loading = true;\n this.loadingMessage = 'Queue search...';\n api.put('search/manual', data) // eslint-disable-line no-undef\n .then(() => {\n console.info(`Queued search for show: ${show.id.slug} season: ${season}, episode: ${episode}`);\n this.loadingMessage = 'Queued search...';\n }).catch(error => {\n console.error(String(error));\n });\n },\n\n rowStyleClassFn(row) {\n const {\n effectiveIgnored,\n effectiveRequired,\n search,\n show\n } = this;\n const classes = [row.status || 'skipped'];\n\n const getReleaseNameClasses = name => {\n const classes = [];\n\n if (effectiveIgnored(show).map(word => {\n return name.toLowerCase().includes(word.toLowerCase());\n }).filter(x => x === true).length > 0) {\n classes.push('global-ignored');\n }\n\n if (effectiveRequired(show).map(word => {\n return name.toLowerCase().includes(word.toLowerCase());\n }).filter(x => x === true).length > 0) {\n classes.push('global-required');\n }\n\n if (search.filters.undesired.map(word => {\n return name.toLowerCase().includes(word.toLowerCase());\n }).filter(x => x === true).length > 0) {\n classes.push('global-undesired');\n }\n\n return classes;\n };\n\n return [...classes, ...getReleaseNameClasses(row.release)].join(' ');\n },\n\n async snatchResult(evt, result) {\n const {\n layout\n } = this;\n evt.target.src = `images/loading16-${layout.themeName}.gif`;\n\n try {\n const response = await (0,_api__WEBPACK_IMPORTED_MODULE_0__.apiRoute)('home/pickManualSearch', {\n params: {\n provider: result.provider.id,\n identifier: result.identifier\n }\n });\n\n if (response.data.result === 'success') {\n evt.target.src = 'images/save.png';\n } else {\n evt.target.src = 'images/no16.png';\n }\n } catch (error) {\n console.error(String(error));\n evt.target.src = 'images/no16.png';\n }\n },\n\n updatePaginationPerPage(rows) {\n const {\n setCookie\n } = this;\n this.paginationPerPage = rows;\n setCookie('pagination-perPage', rows);\n }\n\n },\n watch: {\n queueitems: {\n async handler(queue) {\n // Check for manual searches\n const queueForThisEpisode = queue.filter(q => ['MANUAL-SEARCH'].includes(q.name) && q.segment.length && q.segment.find(ep => ep.season === this.season && ep.episode === this.episode));\n const [last] = queueForThisEpisode.slice(-1);\n\n if (last) {\n const searchStatus = last.success === null ? 'running' : 'finished';\n\n if (searchStatus === 'running') {\n this.loading = true;\n this.loadingMessage = 'Started searching providers...';\n } else {\n this.loadingMessage = 'Finished manual search';\n setTimeout(() => {\n this.loading = false;\n this.loadingMessage = '';\n }, 5000);\n }\n } // Check for snach selection\n\n\n const snatchedForThisEpisode = queue.filter(q => q.name === 'SNATCH-RESULT' && q.segment.length && q.segment.find(ep => ep.season === this.season && ep.episode === this.episode));\n const [lastSnatch] = snatchedForThisEpisode.slice(-1);\n\n if (lastSnatch && lastSnatch.success === true) {\n const {\n getProviderCacheResults,\n getShowEpisodeHistory,\n show,\n season,\n episode,\n episodeSlug\n } = this; // Something changed, let's get a new batch of provider results.\n\n await getShowEpisodeHistory({\n showSlug: show.id.slug,\n episodeSlug\n });\n await getProviderCacheResults({\n showSlug: show.id.slug,\n season,\n episode\n });\n }\n },\n\n deep: true,\n immediate: false\n }\n }\n});\n\n//# sourceURL=webpack://slim/./src/components/show-results.vue?./node_modules/babel-loader/lib/index.js??clonedRuleSet-1%5B0%5D.rules%5B0%5D!./node_modules/vue-loader/lib/index.js??vue-loader-options"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _api__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../api */ \"./src/api.js\");\n/* harmony import */ var vuex__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! vuex */ \"./node_modules/vuex/dist/vuex.esm.js\");\n/* harmony import */ var vue_good_table__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! vue-good-table */ \"./node_modules/vue-good-table/dist/vue-good-table.esm.js\");\n/* harmony import */ var _mixins_manage_cookie__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../mixins/manage-cookie */ \"./src/mixins/manage-cookie.js\");\n/* harmony import */ var _helpers__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./helpers */ \"./src/components/helpers/index.js\");\n/* harmony import */ var _helpers_quality_pill_vue__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./helpers/quality-pill.vue */ \"./src/components/helpers/quality-pill.vue\");\n/* harmony import */ var _utils_core__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../utils/core */ \"./src/utils/core.js\");\n/* harmony import */ var v_tooltip__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! v-tooltip */ \"./node_modules/v-tooltip/dist/v-tooltip.esm.js\");\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n\n\n\n\n\n\n\n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({\n name: 'show-results',\n components: {\n VueGoodTable: vue_good_table__WEBPACK_IMPORTED_MODULE_6__.VueGoodTable,\n StateSwitch: _helpers__WEBPACK_IMPORTED_MODULE_2__.StateSwitch,\n QualityPill: _helpers_quality_pill_vue__WEBPACK_IMPORTED_MODULE_3__[\"default\"]\n },\n directives: {\n tooltip: v_tooltip__WEBPACK_IMPORTED_MODULE_5__.VTooltip\n },\n mixins: [(0,_mixins_manage_cookie__WEBPACK_IMPORTED_MODULE_1__.manageCookieMixin)('showResults')],\n props: {\n show: {\n type: Object,\n required: true\n },\n season: {\n type: Number,\n required: true\n },\n episode: {\n type: Number,\n required: false\n },\n searchType: {\n type: String,\n default: 'episode'\n }\n },\n\n data() {\n const {\n getCookie\n } = this;\n const perPageDropdown = [25, 50, 100, 250, 500];\n\n const getPaginationPerPage = () => {\n const rows = getCookie('pagination-perPage');\n\n if (!rows) {\n return 50;\n }\n\n if (!perPageDropdown.includes(rows)) {\n return 500;\n }\n\n return rows;\n };\n\n return {\n columns: [{\n label: 'Release',\n field: 'release',\n tdClass: 'release',\n filterOptions: {\n enabled: true\n },\n hidden: getCookie('Release')\n }, {\n label: 'Group',\n field: 'releaseGroup',\n filterOptions: {\n enabled: true\n },\n hidden: getCookie('Group')\n }, {\n label: 'Provider',\n field: 'provider.name',\n filterOptions: {\n enabled: true\n },\n hidden: getCookie('Provider')\n }, {\n label: 'Quality',\n field: 'quality',\n type: 'number',\n filterOptions: {\n customFilter: true\n },\n hidden: getCookie('Quality')\n }, {\n label: 'Seeds',\n field: 'seeders',\n type: 'number',\n hidden: getCookie('Seeds')\n }, {\n label: 'Peers',\n field: 'leechers',\n type: 'number',\n hidden: getCookie('Peers')\n }, {\n label: 'Size',\n field: 'size',\n formatFn: _utils_core__WEBPACK_IMPORTED_MODULE_4__.humanFileSize,\n type: 'number',\n hidden: getCookie('Size')\n }, {\n label: 'Added',\n field: 'dateAdded',\n type: 'date',\n sortable: true,\n dateInputFormat: 'yyyy-MM-dd\\'T\\'HH:mm:ss',\n // E.g. 07-09-2017 19:16:25\n dateOutputFormat: 'yyyy/MM/dd HH:mm:ss',\n hidden: getCookie('Added')\n }, {\n label: 'Published',\n field: 'pubdate',\n type: 'date',\n sortable: true,\n dateInputFormat: 'yyyy-MM-dd\\'T\\'HH:mm:ssXXX',\n dateOutputFormat: 'yyyy-MM-dd HH:mm:ss',\n hidden: getCookie('Published')\n }, {\n label: 'Updated',\n field: 'time',\n type: 'date',\n sortable: true,\n dateInputFormat: 'yyyy-MM-dd\\'T\\'HH:mm:ss',\n dateOutputFormat: 'yyyy/MM/dd HH:mm:ss',\n hidden: getCookie('Updated')\n }, {\n label: 'Snatch',\n field: 'snatch',\n sortable: false\n }],\n loading: false,\n loadingMessage: '',\n perPageDropdown,\n getPaginationPerPage\n };\n },\n\n async mounted() {\n const {\n forceSearch,\n getProviders,\n getProviderCacheResults,\n show,\n season,\n episode\n } = this;\n await getProviders();\n const result = await getProviderCacheResults({\n showSlug: show.id.slug,\n season,\n episode\n }); // TODO: put a modal in between\n\n if (result.providersSearched > 0 && result.totalSearchResults.length === 0) {\n forceSearch();\n }\n },\n\n computed: { ...(0,vuex__WEBPACK_IMPORTED_MODULE_7__.mapState)({\n config: state => state.config.general,\n layout: state => state.config.layout,\n search: state => state.config.search,\n providers: state => state.provider.providers,\n queueitems: state => state.queue.queueitems,\n history: state => state.history.episodeHistory\n }),\n ...(0,vuex__WEBPACK_IMPORTED_MODULE_7__.mapGetters)({\n fuzzyParseDateTime: 'fuzzyParseDateTime',\n effectiveIgnored: 'effectiveIgnored',\n effectiveRequired: 'effectiveRequired'\n }),\n\n combinedResults() {\n const {\n episode,\n episodeHistory,\n providers,\n season,\n show\n } = this;\n let results = [];\n\n const getLastHistoryStatus = result => {\n const sortedHistory = episodeHistory.splice().sort(item => item.actionDate).reverse();\n\n for (const historyRow of sortedHistory) {\n if (historyRow.resource === result.release && historyRow.size === result.size) {\n return historyRow.statusName.toLocaleLowerCase();\n }\n }\n\n return 'skipped';\n };\n\n for (const provider of Object.values(providers).filter(provider => provider.config.enabled)) {\n if (provider.cache && provider.cache.length > 0) {\n results = [...results, ...provider.cache.filter(searchResult => {\n if (episode) {\n return searchResult.showSlug === show.id.slug && searchResult.season === season && searchResult.episodes.includes(episode);\n }\n\n return searchResult.showSlug === show.id.slug && searchResult.season === season && searchResult.seasonPack;\n }).map(result => {\n return { ...result,\n status: getLastHistoryStatus(result)\n };\n })];\n }\n }\n\n return results;\n },\n\n /**\n * Helper to get the current episode or season slug.\n * @returns {string} episode slug.\n */\n episodeSlug() {\n const {\n season,\n episode\n } = this;\n return (0,_utils_core__WEBPACK_IMPORTED_MODULE_4__.episodeToSlug)(season, episode);\n },\n\n /**\n * Helper to check if showSlug and season/episode slug exist.\n * @returns {array} history for episode or empty array.\n */\n episodeHistory() {\n const {\n episodeSlug,\n history,\n show\n } = this;\n\n if (!history[show.id.slug] || !history[show.id.slug][episodeSlug]) {\n return [];\n }\n\n return history[show.id.slug][episodeSlug];\n }\n\n },\n methods: {\n humanFileSize: _utils_core__WEBPACK_IMPORTED_MODULE_4__.humanFileSize,\n ...(0,vuex__WEBPACK_IMPORTED_MODULE_7__.mapActions)({\n getProviders: 'getProviders',\n getProviderCacheResults: 'getProviderCacheResults',\n getShowEpisodeHistory: 'getShowEpisodeHistory'\n }),\n\n close() {\n this.$emit('close'); // Destroy the vue listeners, etc\n\n this.$destroy(); // Remove the element from the DOM\n\n this.$el.remove();\n },\n\n getProviderResults() {\n const {\n episode,\n getProviderCacheResults,\n season,\n show\n } = this;\n getProviderCacheResults({\n showSlug: show.id.slug,\n season,\n episode\n });\n },\n\n forceSearch() {\n const {\n episode,\n episodeSlug,\n season,\n show\n } = this;\n let data = {};\n data = {\n showSlug: show.id.slug,\n options: {},\n [episode ? 'episodes' : 'season']: [episodeSlug]\n };\n this.loading = true;\n this.loadingMessage = 'Queue search...';\n api.put('search/manual', data) // eslint-disable-line no-undef\n .then(() => {\n console.info(`Queued search for show: ${show.id.slug} season: ${season}, episode: ${episode}`);\n this.loadingMessage = 'Queued search...';\n }).catch(error => {\n console.error(String(error));\n });\n },\n\n rowStyleClassFn(row) {\n const {\n effectiveIgnored,\n effectiveRequired,\n search,\n show\n } = this;\n const classes = [row.status || 'skipped'];\n\n const getReleaseNameClasses = name => {\n const classes = [];\n\n if (effectiveIgnored(show).map(word => {\n return name.toLowerCase().includes(word.toLowerCase());\n }).filter(x => x === true).length > 0) {\n classes.push('global-ignored');\n }\n\n if (effectiveRequired(show).map(word => {\n return name.toLowerCase().includes(word.toLowerCase());\n }).filter(x => x === true).length > 0) {\n classes.push('global-required');\n }\n\n if (search.filters.undesired.map(word => {\n return name.toLowerCase().includes(word.toLowerCase());\n }).filter(x => x === true).length > 0) {\n classes.push('global-undesired');\n }\n\n return classes;\n };\n\n return [...classes, ...getReleaseNameClasses(row.release)].join(' ');\n },\n\n async snatchResult(evt, result) {\n const {\n layout\n } = this;\n evt.target.src = `images/loading16-${layout.themeName}.gif`;\n\n try {\n const response = await (0,_api__WEBPACK_IMPORTED_MODULE_0__.apiRoute)('home/pickManualSearch', {\n params: {\n provider: result.provider.id,\n identifier: result.identifier\n }\n });\n\n if (response.data.result === 'success') {\n evt.target.src = 'images/save.png';\n } else {\n evt.target.src = 'images/no16.png';\n }\n } catch (error) {\n console.error(String(error));\n evt.target.src = 'images/no16.png';\n }\n },\n\n updatePaginationPerPage(rows) {\n const {\n setCookie\n } = this;\n this.paginationPerPage = rows;\n setCookie('pagination-perPage', rows);\n }\n\n },\n watch: {\n queueitems: {\n async handler(queue) {\n // Check for manual searches\n const queueForThisEpisode = queue.filter(q => ['MANUAL-SEARCH'].includes(q.name) && q.segment.length && q.segment.find(ep => ep.season === this.season && ep.episode === this.episode));\n const [last] = queueForThisEpisode.slice(-1);\n\n if (last) {\n const searchStatus = last.success === null ? 'running' : 'finished';\n\n if (searchStatus === 'running') {\n this.loading = true;\n this.loadingMessage = 'Started searching providers...';\n } else {\n this.loadingMessage = 'Finished manual search';\n setTimeout(() => {\n this.loading = false;\n this.loadingMessage = '';\n }, 5000);\n }\n } // Check for snach selection\n\n\n const snatchedForThisEpisode = queue.filter(q => q.name === 'SNATCH-RESULT' && q.segment.length && q.segment.find(ep => ep.season === this.season && ep.episode === this.episode));\n const [lastSnatch] = snatchedForThisEpisode.slice(-1);\n\n if (lastSnatch && lastSnatch.success === true) {\n const {\n getProviderCacheResults,\n getShowEpisodeHistory,\n show,\n season,\n episode,\n episodeSlug\n } = this; // Something changed, let's get a new batch of provider results.\n\n await getShowEpisodeHistory({\n showSlug: show.id.slug,\n episodeSlug\n });\n await getProviderCacheResults({\n showSlug: show.id.slug,\n season,\n episode\n });\n }\n },\n\n deep: true,\n immediate: false\n }\n }\n});\n\n//# sourceURL=webpack://slim/./src/components/show-results.vue?./node_modules/babel-loader/lib/index.js??clonedRuleSet-1%5B0%5D.rules%5B0%5D!./node_modules/vue-loader/lib/index.js??vue-loader-options"); /***/ }), diff --git a/themes/light/assets/js/medusa-runtime.js b/themes/light/assets/js/medusa-runtime.js index a7e166317d..95ca5a215d 100644 --- a/themes/light/assets/js/medusa-runtime.js +++ b/themes/light/assets/js/medusa-runtime.js @@ -1137,7 +1137,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _api__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../api */ \"./src/api.js\");\n/* harmony import */ var vuex__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! vuex */ \"./node_modules/vuex/dist/vuex.esm.js\");\n/* harmony import */ var vue_good_table__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! vue-good-table */ \"./node_modules/vue-good-table/dist/vue-good-table.esm.js\");\n/* harmony import */ var _mixins_manage_cookie__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../mixins/manage-cookie */ \"./src/mixins/manage-cookie.js\");\n/* harmony import */ var _helpers__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./helpers */ \"./src/components/helpers/index.js\");\n/* harmony import */ var _helpers_quality_pill_vue__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./helpers/quality-pill.vue */ \"./src/components/helpers/quality-pill.vue\");\n/* harmony import */ var _utils_core__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../utils/core */ \"./src/utils/core.js\");\n/* harmony import */ var v_tooltip__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! v-tooltip */ \"./node_modules/v-tooltip/dist/v-tooltip.esm.js\");\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n\n\n\n\n\n\n\n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({\n name: 'show-results',\n components: {\n VueGoodTable: vue_good_table__WEBPACK_IMPORTED_MODULE_6__.VueGoodTable,\n StateSwitch: _helpers__WEBPACK_IMPORTED_MODULE_2__.StateSwitch,\n QualityPill: _helpers_quality_pill_vue__WEBPACK_IMPORTED_MODULE_3__[\"default\"]\n },\n directives: {\n tooltip: v_tooltip__WEBPACK_IMPORTED_MODULE_5__.VTooltip\n },\n mixins: [(0,_mixins_manage_cookie__WEBPACK_IMPORTED_MODULE_1__.manageCookieMixin)('showResults')],\n props: {\n show: {\n type: Object,\n required: true\n },\n season: {\n type: Number,\n required: true\n },\n episode: {\n type: Number,\n required: false\n },\n searchType: {\n type: String,\n default: 'episode'\n }\n },\n\n data() {\n const {\n getCookie\n } = this;\n const perPageDropdown = [25, 50, 100, 250, 500];\n\n const getPaginationPerPage = () => {\n const rows = getCookie('pagination-perPage');\n\n if (!rows) {\n return 50;\n }\n\n if (!perPageDropdown.includes(rows)) {\n return 500;\n }\n\n return rows;\n };\n\n return {\n columns: [{\n label: 'Release',\n field: 'release',\n tdClass: 'release',\n filterOptions: {\n enabled: true\n },\n hidden: getCookie('Release')\n }, {\n label: 'Group',\n field: 'releaseGroup',\n filterOptions: {\n enabled: true\n },\n hidden: getCookie('Group')\n }, {\n label: 'Provider',\n field: 'provider.name',\n filterOptions: {\n enabled: true\n },\n hidden: getCookie('Provider')\n }, {\n label: 'Quality',\n field: 'quality',\n type: 'number',\n filterOptions: {\n customFilter: true\n },\n hidden: getCookie('Quality')\n }, {\n label: 'Seeds',\n field: 'seeders',\n type: 'number',\n hidden: getCookie('Seeds')\n }, {\n label: 'Peers',\n field: 'leechers',\n type: 'number',\n hidden: getCookie('Peers')\n }, {\n label: 'Size',\n field: 'size',\n formatFn: _utils_core__WEBPACK_IMPORTED_MODULE_4__.humanFileSize,\n type: 'number',\n hidden: getCookie('Size')\n }, {\n label: 'Added',\n field: 'dateAdded',\n type: 'date',\n sortable: true,\n dateInputFormat: 'yyyy-MM-dd\\'T\\'HH:mm:ss',\n // E.g. 07-09-2017 19:16:25\n dateOutputFormat: 'yyyy/MM/dd HH:mm:ss',\n hidden: getCookie('Added')\n }, {\n label: 'Published',\n field: 'pubdate',\n type: 'date',\n sortable: true,\n dateInputFormat: 'yyyy-MM-dd\\'T\\'HH:mm:ssXXX',\n dateOutputFormat: 'yyyy-MM-dd HH:mm:ss',\n hidden: getCookie('Published')\n }, {\n label: 'Updated',\n field: 'time',\n type: 'date',\n sortable: true,\n dateInputFormat: 'yyyy-MM-dd\\'T\\'HH:mm:ss',\n dateOutputFormat: 'yyyy/MM/dd HH:mm:ss',\n hidden: getCookie('Updated')\n }, {\n label: 'Snatch',\n field: 'snatch',\n sortable: false\n }],\n loading: false,\n loadingMessage: '',\n perPageDropdown,\n getPaginationPerPage\n };\n },\n\n async mounted() {\n const {\n forceSearch,\n getProviders,\n getProviderCacheResults,\n show,\n season,\n episode\n } = this;\n await getProviders();\n const result = await getProviderCacheResults({\n showSlug: show.id.slug,\n season,\n episode\n }); // TODO: put a modal in between\n\n if (result.providersSearched > 0 && result.totalSearchResults.length === 0) {\n forceSearch();\n }\n },\n\n computed: { ...(0,vuex__WEBPACK_IMPORTED_MODULE_7__.mapState)({\n config: state => state.config.general,\n layout: state => state.config.layout,\n search: state => state.config.search,\n providers: state => state.provider.providers,\n queueitems: state => state.queue.queueitems,\n history: state => state.history.episodeHistory\n }),\n ...(0,vuex__WEBPACK_IMPORTED_MODULE_7__.mapGetters)({\n fuzzyParseDateTime: 'fuzzyParseDateTime',\n effectiveIgnored: 'effectiveIgnored',\n effectiveRequired: 'effectiveRequired'\n }),\n\n combinedResults() {\n const {\n episode,\n episodeHistory,\n providers,\n season,\n show\n } = this;\n let results = [];\n\n const getLastHistoryStatus = result => {\n const sortedHistory = episodeHistory.sort(item => item.actionDate).reverse();\n\n for (const historyRow of sortedHistory) {\n if (historyRow.resource === result.release && historyRow.size === result.size) {\n return historyRow.statusName.toLocaleLowerCase();\n }\n }\n\n return 'skipped';\n };\n\n for (const provider of Object.values(providers).filter(provider => provider.config.enabled)) {\n if (provider.cache && provider.cache.length > 0) {\n results = [...results, ...provider.cache.filter(searchResult => {\n if (episode) {\n return searchResult.showSlug === show.id.slug && searchResult.season === season && searchResult.episodes.includes(episode);\n }\n\n return searchResult.showSlug === show.id.slug && searchResult.season === season && searchResult.seasonPack;\n }).map(result => {\n return { ...result,\n status: getLastHistoryStatus(result)\n };\n })];\n }\n }\n\n return results;\n },\n\n /**\n * Helper to get the current episode or season slug.\n * @returns {string} episode slug.\n */\n episodeSlug() {\n const {\n season,\n episode\n } = this;\n return (0,_utils_core__WEBPACK_IMPORTED_MODULE_4__.episodeToSlug)(season, episode);\n },\n\n /**\n * Helper to check if showSlug and season/episode slug exist.\n * @returns {array} history for episode or empty array.\n */\n episodeHistory() {\n const {\n episodeSlug,\n history,\n show\n } = this;\n\n if (!history[show.id.slug] || !history[show.id.slug][episodeSlug]) {\n return [];\n }\n\n return history[show.id.slug][episodeSlug];\n }\n\n },\n methods: {\n humanFileSize: _utils_core__WEBPACK_IMPORTED_MODULE_4__.humanFileSize,\n ...(0,vuex__WEBPACK_IMPORTED_MODULE_7__.mapActions)({\n getProviders: 'getProviders',\n getProviderCacheResults: 'getProviderCacheResults',\n getShowEpisodeHistory: 'getShowEpisodeHistory'\n }),\n\n close() {\n this.$emit('close'); // Destroy the vue listeners, etc\n\n this.$destroy(); // Remove the element from the DOM\n\n this.$el.remove();\n },\n\n getProviderResults() {\n const {\n episode,\n getProviderCacheResults,\n season,\n show\n } = this;\n getProviderCacheResults({\n showSlug: show.id.slug,\n season,\n episode\n });\n },\n\n forceSearch() {\n const {\n episode,\n episodeSlug,\n season,\n show\n } = this;\n let data = {};\n data = {\n showSlug: show.id.slug,\n options: {},\n [episode ? 'episodes' : 'season']: [episodeSlug]\n };\n this.loading = true;\n this.loadingMessage = 'Queue search...';\n api.put('search/manual', data) // eslint-disable-line no-undef\n .then(() => {\n console.info(`Queued search for show: ${show.id.slug} season: ${season}, episode: ${episode}`);\n this.loadingMessage = 'Queued search...';\n }).catch(error => {\n console.error(String(error));\n });\n },\n\n rowStyleClassFn(row) {\n const {\n effectiveIgnored,\n effectiveRequired,\n search,\n show\n } = this;\n const classes = [row.status || 'skipped'];\n\n const getReleaseNameClasses = name => {\n const classes = [];\n\n if (effectiveIgnored(show).map(word => {\n return name.toLowerCase().includes(word.toLowerCase());\n }).filter(x => x === true).length > 0) {\n classes.push('global-ignored');\n }\n\n if (effectiveRequired(show).map(word => {\n return name.toLowerCase().includes(word.toLowerCase());\n }).filter(x => x === true).length > 0) {\n classes.push('global-required');\n }\n\n if (search.filters.undesired.map(word => {\n return name.toLowerCase().includes(word.toLowerCase());\n }).filter(x => x === true).length > 0) {\n classes.push('global-undesired');\n }\n\n return classes;\n };\n\n return [...classes, ...getReleaseNameClasses(row.release)].join(' ');\n },\n\n async snatchResult(evt, result) {\n const {\n layout\n } = this;\n evt.target.src = `images/loading16-${layout.themeName}.gif`;\n\n try {\n const response = await (0,_api__WEBPACK_IMPORTED_MODULE_0__.apiRoute)('home/pickManualSearch', {\n params: {\n provider: result.provider.id,\n identifier: result.identifier\n }\n });\n\n if (response.data.result === 'success') {\n evt.target.src = 'images/save.png';\n } else {\n evt.target.src = 'images/no16.png';\n }\n } catch (error) {\n console.error(String(error));\n evt.target.src = 'images/no16.png';\n }\n },\n\n updatePaginationPerPage(rows) {\n const {\n setCookie\n } = this;\n this.paginationPerPage = rows;\n setCookie('pagination-perPage', rows);\n }\n\n },\n watch: {\n queueitems: {\n async handler(queue) {\n // Check for manual searches\n const queueForThisEpisode = queue.filter(q => ['MANUAL-SEARCH'].includes(q.name) && q.segment.length && q.segment.find(ep => ep.season === this.season && ep.episode === this.episode));\n const [last] = queueForThisEpisode.slice(-1);\n\n if (last) {\n const searchStatus = last.success === null ? 'running' : 'finished';\n\n if (searchStatus === 'running') {\n this.loading = true;\n this.loadingMessage = 'Started searching providers...';\n } else {\n this.loadingMessage = 'Finished manual search';\n setTimeout(() => {\n this.loading = false;\n this.loadingMessage = '';\n }, 5000);\n }\n } // Check for snach selection\n\n\n const snatchedForThisEpisode = queue.filter(q => q.name === 'SNATCH-RESULT' && q.segment.length && q.segment.find(ep => ep.season === this.season && ep.episode === this.episode));\n const [lastSnatch] = snatchedForThisEpisode.slice(-1);\n\n if (lastSnatch && lastSnatch.success === true) {\n const {\n getProviderCacheResults,\n getShowEpisodeHistory,\n show,\n season,\n episode,\n episodeSlug\n } = this; // Something changed, let's get a new batch of provider results.\n\n await getShowEpisodeHistory({\n showSlug: show.id.slug,\n episodeSlug\n });\n await getProviderCacheResults({\n showSlug: show.id.slug,\n season,\n episode\n });\n }\n },\n\n deep: true,\n immediate: false\n }\n }\n});\n\n//# sourceURL=webpack://slim/./src/components/show-results.vue?./node_modules/babel-loader/lib/index.js??clonedRuleSet-1%5B0%5D.rules%5B0%5D!./node_modules/vue-loader/lib/index.js??vue-loader-options"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _api__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../api */ \"./src/api.js\");\n/* harmony import */ var vuex__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! vuex */ \"./node_modules/vuex/dist/vuex.esm.js\");\n/* harmony import */ var vue_good_table__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! vue-good-table */ \"./node_modules/vue-good-table/dist/vue-good-table.esm.js\");\n/* harmony import */ var _mixins_manage_cookie__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../mixins/manage-cookie */ \"./src/mixins/manage-cookie.js\");\n/* harmony import */ var _helpers__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./helpers */ \"./src/components/helpers/index.js\");\n/* harmony import */ var _helpers_quality_pill_vue__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./helpers/quality-pill.vue */ \"./src/components/helpers/quality-pill.vue\");\n/* harmony import */ var _utils_core__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../utils/core */ \"./src/utils/core.js\");\n/* harmony import */ var v_tooltip__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! v-tooltip */ \"./node_modules/v-tooltip/dist/v-tooltip.esm.js\");\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n\n\n\n\n\n\n\n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({\n name: 'show-results',\n components: {\n VueGoodTable: vue_good_table__WEBPACK_IMPORTED_MODULE_6__.VueGoodTable,\n StateSwitch: _helpers__WEBPACK_IMPORTED_MODULE_2__.StateSwitch,\n QualityPill: _helpers_quality_pill_vue__WEBPACK_IMPORTED_MODULE_3__[\"default\"]\n },\n directives: {\n tooltip: v_tooltip__WEBPACK_IMPORTED_MODULE_5__.VTooltip\n },\n mixins: [(0,_mixins_manage_cookie__WEBPACK_IMPORTED_MODULE_1__.manageCookieMixin)('showResults')],\n props: {\n show: {\n type: Object,\n required: true\n },\n season: {\n type: Number,\n required: true\n },\n episode: {\n type: Number,\n required: false\n },\n searchType: {\n type: String,\n default: 'episode'\n }\n },\n\n data() {\n const {\n getCookie\n } = this;\n const perPageDropdown = [25, 50, 100, 250, 500];\n\n const getPaginationPerPage = () => {\n const rows = getCookie('pagination-perPage');\n\n if (!rows) {\n return 50;\n }\n\n if (!perPageDropdown.includes(rows)) {\n return 500;\n }\n\n return rows;\n };\n\n return {\n columns: [{\n label: 'Release',\n field: 'release',\n tdClass: 'release',\n filterOptions: {\n enabled: true\n },\n hidden: getCookie('Release')\n }, {\n label: 'Group',\n field: 'releaseGroup',\n filterOptions: {\n enabled: true\n },\n hidden: getCookie('Group')\n }, {\n label: 'Provider',\n field: 'provider.name',\n filterOptions: {\n enabled: true\n },\n hidden: getCookie('Provider')\n }, {\n label: 'Quality',\n field: 'quality',\n type: 'number',\n filterOptions: {\n customFilter: true\n },\n hidden: getCookie('Quality')\n }, {\n label: 'Seeds',\n field: 'seeders',\n type: 'number',\n hidden: getCookie('Seeds')\n }, {\n label: 'Peers',\n field: 'leechers',\n type: 'number',\n hidden: getCookie('Peers')\n }, {\n label: 'Size',\n field: 'size',\n formatFn: _utils_core__WEBPACK_IMPORTED_MODULE_4__.humanFileSize,\n type: 'number',\n hidden: getCookie('Size')\n }, {\n label: 'Added',\n field: 'dateAdded',\n type: 'date',\n sortable: true,\n dateInputFormat: 'yyyy-MM-dd\\'T\\'HH:mm:ss',\n // E.g. 07-09-2017 19:16:25\n dateOutputFormat: 'yyyy/MM/dd HH:mm:ss',\n hidden: getCookie('Added')\n }, {\n label: 'Published',\n field: 'pubdate',\n type: 'date',\n sortable: true,\n dateInputFormat: 'yyyy-MM-dd\\'T\\'HH:mm:ssXXX',\n dateOutputFormat: 'yyyy-MM-dd HH:mm:ss',\n hidden: getCookie('Published')\n }, {\n label: 'Updated',\n field: 'time',\n type: 'date',\n sortable: true,\n dateInputFormat: 'yyyy-MM-dd\\'T\\'HH:mm:ss',\n dateOutputFormat: 'yyyy/MM/dd HH:mm:ss',\n hidden: getCookie('Updated')\n }, {\n label: 'Snatch',\n field: 'snatch',\n sortable: false\n }],\n loading: false,\n loadingMessage: '',\n perPageDropdown,\n getPaginationPerPage\n };\n },\n\n async mounted() {\n const {\n forceSearch,\n getProviders,\n getProviderCacheResults,\n show,\n season,\n episode\n } = this;\n await getProviders();\n const result = await getProviderCacheResults({\n showSlug: show.id.slug,\n season,\n episode\n }); // TODO: put a modal in between\n\n if (result.providersSearched > 0 && result.totalSearchResults.length === 0) {\n forceSearch();\n }\n },\n\n computed: { ...(0,vuex__WEBPACK_IMPORTED_MODULE_7__.mapState)({\n config: state => state.config.general,\n layout: state => state.config.layout,\n search: state => state.config.search,\n providers: state => state.provider.providers,\n queueitems: state => state.queue.queueitems,\n history: state => state.history.episodeHistory\n }),\n ...(0,vuex__WEBPACK_IMPORTED_MODULE_7__.mapGetters)({\n fuzzyParseDateTime: 'fuzzyParseDateTime',\n effectiveIgnored: 'effectiveIgnored',\n effectiveRequired: 'effectiveRequired'\n }),\n\n combinedResults() {\n const {\n episode,\n episodeHistory,\n providers,\n season,\n show\n } = this;\n let results = [];\n\n const getLastHistoryStatus = result => {\n const sortedHistory = episodeHistory.splice().sort(item => item.actionDate).reverse();\n\n for (const historyRow of sortedHistory) {\n if (historyRow.resource === result.release && historyRow.size === result.size) {\n return historyRow.statusName.toLocaleLowerCase();\n }\n }\n\n return 'skipped';\n };\n\n for (const provider of Object.values(providers).filter(provider => provider.config.enabled)) {\n if (provider.cache && provider.cache.length > 0) {\n results = [...results, ...provider.cache.filter(searchResult => {\n if (episode) {\n return searchResult.showSlug === show.id.slug && searchResult.season === season && searchResult.episodes.includes(episode);\n }\n\n return searchResult.showSlug === show.id.slug && searchResult.season === season && searchResult.seasonPack;\n }).map(result => {\n return { ...result,\n status: getLastHistoryStatus(result)\n };\n })];\n }\n }\n\n return results;\n },\n\n /**\n * Helper to get the current episode or season slug.\n * @returns {string} episode slug.\n */\n episodeSlug() {\n const {\n season,\n episode\n } = this;\n return (0,_utils_core__WEBPACK_IMPORTED_MODULE_4__.episodeToSlug)(season, episode);\n },\n\n /**\n * Helper to check if showSlug and season/episode slug exist.\n * @returns {array} history for episode or empty array.\n */\n episodeHistory() {\n const {\n episodeSlug,\n history,\n show\n } = this;\n\n if (!history[show.id.slug] || !history[show.id.slug][episodeSlug]) {\n return [];\n }\n\n return history[show.id.slug][episodeSlug];\n }\n\n },\n methods: {\n humanFileSize: _utils_core__WEBPACK_IMPORTED_MODULE_4__.humanFileSize,\n ...(0,vuex__WEBPACK_IMPORTED_MODULE_7__.mapActions)({\n getProviders: 'getProviders',\n getProviderCacheResults: 'getProviderCacheResults',\n getShowEpisodeHistory: 'getShowEpisodeHistory'\n }),\n\n close() {\n this.$emit('close'); // Destroy the vue listeners, etc\n\n this.$destroy(); // Remove the element from the DOM\n\n this.$el.remove();\n },\n\n getProviderResults() {\n const {\n episode,\n getProviderCacheResults,\n season,\n show\n } = this;\n getProviderCacheResults({\n showSlug: show.id.slug,\n season,\n episode\n });\n },\n\n forceSearch() {\n const {\n episode,\n episodeSlug,\n season,\n show\n } = this;\n let data = {};\n data = {\n showSlug: show.id.slug,\n options: {},\n [episode ? 'episodes' : 'season']: [episodeSlug]\n };\n this.loading = true;\n this.loadingMessage = 'Queue search...';\n api.put('search/manual', data) // eslint-disable-line no-undef\n .then(() => {\n console.info(`Queued search for show: ${show.id.slug} season: ${season}, episode: ${episode}`);\n this.loadingMessage = 'Queued search...';\n }).catch(error => {\n console.error(String(error));\n });\n },\n\n rowStyleClassFn(row) {\n const {\n effectiveIgnored,\n effectiveRequired,\n search,\n show\n } = this;\n const classes = [row.status || 'skipped'];\n\n const getReleaseNameClasses = name => {\n const classes = [];\n\n if (effectiveIgnored(show).map(word => {\n return name.toLowerCase().includes(word.toLowerCase());\n }).filter(x => x === true).length > 0) {\n classes.push('global-ignored');\n }\n\n if (effectiveRequired(show).map(word => {\n return name.toLowerCase().includes(word.toLowerCase());\n }).filter(x => x === true).length > 0) {\n classes.push('global-required');\n }\n\n if (search.filters.undesired.map(word => {\n return name.toLowerCase().includes(word.toLowerCase());\n }).filter(x => x === true).length > 0) {\n classes.push('global-undesired');\n }\n\n return classes;\n };\n\n return [...classes, ...getReleaseNameClasses(row.release)].join(' ');\n },\n\n async snatchResult(evt, result) {\n const {\n layout\n } = this;\n evt.target.src = `images/loading16-${layout.themeName}.gif`;\n\n try {\n const response = await (0,_api__WEBPACK_IMPORTED_MODULE_0__.apiRoute)('home/pickManualSearch', {\n params: {\n provider: result.provider.id,\n identifier: result.identifier\n }\n });\n\n if (response.data.result === 'success') {\n evt.target.src = 'images/save.png';\n } else {\n evt.target.src = 'images/no16.png';\n }\n } catch (error) {\n console.error(String(error));\n evt.target.src = 'images/no16.png';\n }\n },\n\n updatePaginationPerPage(rows) {\n const {\n setCookie\n } = this;\n this.paginationPerPage = rows;\n setCookie('pagination-perPage', rows);\n }\n\n },\n watch: {\n queueitems: {\n async handler(queue) {\n // Check for manual searches\n const queueForThisEpisode = queue.filter(q => ['MANUAL-SEARCH'].includes(q.name) && q.segment.length && q.segment.find(ep => ep.season === this.season && ep.episode === this.episode));\n const [last] = queueForThisEpisode.slice(-1);\n\n if (last) {\n const searchStatus = last.success === null ? 'running' : 'finished';\n\n if (searchStatus === 'running') {\n this.loading = true;\n this.loadingMessage = 'Started searching providers...';\n } else {\n this.loadingMessage = 'Finished manual search';\n setTimeout(() => {\n this.loading = false;\n this.loadingMessage = '';\n }, 5000);\n }\n } // Check for snach selection\n\n\n const snatchedForThisEpisode = queue.filter(q => q.name === 'SNATCH-RESULT' && q.segment.length && q.segment.find(ep => ep.season === this.season && ep.episode === this.episode));\n const [lastSnatch] = snatchedForThisEpisode.slice(-1);\n\n if (lastSnatch && lastSnatch.success === true) {\n const {\n getProviderCacheResults,\n getShowEpisodeHistory,\n show,\n season,\n episode,\n episodeSlug\n } = this; // Something changed, let's get a new batch of provider results.\n\n await getShowEpisodeHistory({\n showSlug: show.id.slug,\n episodeSlug\n });\n await getProviderCacheResults({\n showSlug: show.id.slug,\n season,\n episode\n });\n }\n },\n\n deep: true,\n immediate: false\n }\n }\n});\n\n//# sourceURL=webpack://slim/./src/components/show-results.vue?./node_modules/babel-loader/lib/index.js??clonedRuleSet-1%5B0%5D.rules%5B0%5D!./node_modules/vue-loader/lib/index.js??vue-loader-options"); /***/ }), From 413703e63c7453e8eed544c7363cf85690cb0139 Mon Sep 17 00:00:00 2001 From: p0psicles Date: Fri, 11 Mar 2022 16:11:02 +0100 Subject: [PATCH 5/9] Fix creating urls --- themes-default/slim/src/components/helpers/app-link.vue | 2 +- themes/dark/assets/js/medusa-runtime.js | 2 +- themes/light/assets/js/medusa-runtime.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/themes-default/slim/src/components/helpers/app-link.vue b/themes-default/slim/src/components/helpers/app-link.vue index 596969bdc3..8eef735218 100644 --- a/themes-default/slim/src/components/helpers/app-link.vue +++ b/themes-default/slim/src/components/helpers/app-link.vue @@ -132,7 +132,7 @@ export default { is: 'router-link', to: matchingVueRoute.fullPath, // Add a `href` attribute to enable native mouse navigation (middle click, ctrl+click, etc.) - href: new URL(matchingVueRoute.fullPath, base).href + href: new URL(matchingVueRoute.fullPath.replace(/^(\/)/, ''), base).href }; } } diff --git a/themes/dark/assets/js/medusa-runtime.js b/themes/dark/assets/js/medusa-runtime.js index 95ca5a215d..be38541233 100644 --- a/themes/dark/assets/js/medusa-runtime.js +++ b/themes/dark/assets/js/medusa-runtime.js @@ -235,7 +235,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var vuex__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! vuex */ \"./node_modules/vuex/dist/vuex.esm.js\");\n/* harmony import */ var _router__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../router */ \"./src/router/index.js\");\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n\n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({\n name: 'app-link',\n props: {\n to: [String, Object],\n href: String,\n indexerId: {\n type: String\n },\n placeholder: {\n type: String,\n default: 'indexer-to-name'\n }\n },\n computed: { ...(0,vuex__WEBPACK_IMPORTED_MODULE_1__.mapState)({\n general: state => state.config.general\n }),\n ...(0,vuex__WEBPACK_IMPORTED_MODULE_1__.mapGetters)(['indexerIdToName']),\n\n indexerName() {\n // Returns `undefined` if not found\n const {\n indexerId,\n indexerIdToName\n } = this;\n return indexerIdToName(indexerId);\n },\n\n computedBase() {\n return document.querySelector('base').getAttribute('href');\n },\n\n computedHref() {\n const {\n href,\n indexerId,\n placeholder,\n indexerName\n } = this;\n\n if (indexerId && placeholder) {\n return href.replace(placeholder, indexerName);\n }\n\n return href;\n },\n\n isIRC() {\n if (!this.computedHref) {\n return;\n }\n\n return this.computedHref.startsWith('irc://');\n },\n\n isAbsolute() {\n const href = this.computedHref;\n\n if (!href) {\n return;\n }\n\n return /^[a-z][\\d+.a-z-]*:/.test(href);\n },\n\n isExternal() {\n const base = this.computedBase;\n const href = this.computedHref;\n\n if (!href) {\n return;\n }\n\n return !href.startsWith(base) && !href.startsWith('webcal://');\n },\n\n isHashPath() {\n if (!this.computedHref) {\n return;\n }\n\n return this.computedHref.startsWith('#');\n },\n\n /**\n * Not used for now as derefer.org is down and there are no suitable replacements.\n * @returns {string} - de-refer service'd url.\n */\n anonymisedHref() {\n const {\n anonRedirect\n } = this.general;\n const href = this.computedHref;\n\n if (!href) {\n return;\n }\n\n return anonRedirect ? anonRedirect + href : href;\n },\n\n matchingVueRoute() {\n const {\n isAbsolute,\n isExternal,\n computedHref\n } = this;\n\n if (isAbsolute && isExternal) {\n return;\n }\n\n const {\n route\n } = _router__WEBPACK_IMPORTED_MODULE_0__[\"default\"].resolve('/' + computedHref);\n\n if (!route.name) {\n return;\n }\n\n return route;\n },\n\n linkProperties() {\n const {\n to,\n isIRC,\n isAbsolute,\n isExternal,\n isHashPath,\n matchingVueRoute\n } = this;\n const base = this.computedBase;\n const href = this.computedHref; // Return normal router-link\n\n if (to) {\n return {\n is: 'router-link',\n to\n };\n } // Just return a boring link with other attrs\n // @NOTE: This is for scroll anchors as it uses the id\n\n\n if (!href) {\n return {\n is: 'a',\n // Only tag this as a \"false-link\" if we passed a name in the props\n falseLink: Boolean(this.$attrs.name) || undefined\n };\n } // If current page and next page are both vue routes return router-link\n\n\n if (matchingVueRoute && this.$route && matchingVueRoute.meta.converted && this.$route.meta.converted) {\n // Allows us to skip when we're in a test\n if (window.loadMainApp) {\n return {\n is: 'router-link',\n to: matchingVueRoute.fullPath,\n // Add a `href` attribute to enable native mouse navigation (middle click, ctrl+click, etc.)\n href: new URL(matchingVueRoute.fullPath, base).href\n };\n }\n }\n\n return {\n is: 'a',\n target: isAbsolute && isExternal ? '_blank' : '_self',\n href: (() => {\n if (isHashPath) {\n const {\n location\n } = window;\n\n if (location.hash.length === 0) {\n // Current location might be `url#`\n const newHash = location.href.endsWith('#') ? href.slice(1) : href;\n return location.href + newHash;\n }\n\n return location.href.replace(location.hash, '') + href;\n }\n\n if (isIRC || isAbsolute) {\n return href;\n }\n\n return new URL(href, base).href;\n })(),\n rel: isAbsolute && isExternal ? 'noreferrer noopener' : undefined\n };\n }\n\n }\n});\n\n//# sourceURL=webpack://slim/./src/components/helpers/app-link.vue?./node_modules/babel-loader/lib/index.js??clonedRuleSet-1%5B0%5D.rules%5B0%5D!./node_modules/vue-loader/lib/index.js??vue-loader-options"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var vuex__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! vuex */ \"./node_modules/vuex/dist/vuex.esm.js\");\n/* harmony import */ var _router__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../router */ \"./src/router/index.js\");\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n\n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({\n name: 'app-link',\n props: {\n to: [String, Object],\n href: String,\n indexerId: {\n type: String\n },\n placeholder: {\n type: String,\n default: 'indexer-to-name'\n }\n },\n computed: { ...(0,vuex__WEBPACK_IMPORTED_MODULE_1__.mapState)({\n general: state => state.config.general\n }),\n ...(0,vuex__WEBPACK_IMPORTED_MODULE_1__.mapGetters)(['indexerIdToName']),\n\n indexerName() {\n // Returns `undefined` if not found\n const {\n indexerId,\n indexerIdToName\n } = this;\n return indexerIdToName(indexerId);\n },\n\n computedBase() {\n return document.querySelector('base').getAttribute('href');\n },\n\n computedHref() {\n const {\n href,\n indexerId,\n placeholder,\n indexerName\n } = this;\n\n if (indexerId && placeholder) {\n return href.replace(placeholder, indexerName);\n }\n\n return href;\n },\n\n isIRC() {\n if (!this.computedHref) {\n return;\n }\n\n return this.computedHref.startsWith('irc://');\n },\n\n isAbsolute() {\n const href = this.computedHref;\n\n if (!href) {\n return;\n }\n\n return /^[a-z][\\d+.a-z-]*:/.test(href);\n },\n\n isExternal() {\n const base = this.computedBase;\n const href = this.computedHref;\n\n if (!href) {\n return;\n }\n\n return !href.startsWith(base) && !href.startsWith('webcal://');\n },\n\n isHashPath() {\n if (!this.computedHref) {\n return;\n }\n\n return this.computedHref.startsWith('#');\n },\n\n /**\n * Not used for now as derefer.org is down and there are no suitable replacements.\n * @returns {string} - de-refer service'd url.\n */\n anonymisedHref() {\n const {\n anonRedirect\n } = this.general;\n const href = this.computedHref;\n\n if (!href) {\n return;\n }\n\n return anonRedirect ? anonRedirect + href : href;\n },\n\n matchingVueRoute() {\n const {\n isAbsolute,\n isExternal,\n computedHref\n } = this;\n\n if (isAbsolute && isExternal) {\n return;\n }\n\n const {\n route\n } = _router__WEBPACK_IMPORTED_MODULE_0__[\"default\"].resolve('/' + computedHref);\n\n if (!route.name) {\n return;\n }\n\n return route;\n },\n\n linkProperties() {\n const {\n to,\n isIRC,\n isAbsolute,\n isExternal,\n isHashPath,\n matchingVueRoute\n } = this;\n const base = this.computedBase;\n const href = this.computedHref; // Return normal router-link\n\n if (to) {\n return {\n is: 'router-link',\n to\n };\n } // Just return a boring link with other attrs\n // @NOTE: This is for scroll anchors as it uses the id\n\n\n if (!href) {\n return {\n is: 'a',\n // Only tag this as a \"false-link\" if we passed a name in the props\n falseLink: Boolean(this.$attrs.name) || undefined\n };\n } // If current page and next page are both vue routes return router-link\n\n\n if (matchingVueRoute && this.$route && matchingVueRoute.meta.converted && this.$route.meta.converted) {\n // Allows us to skip when we're in a test\n if (window.loadMainApp) {\n return {\n is: 'router-link',\n to: matchingVueRoute.fullPath,\n // Add a `href` attribute to enable native mouse navigation (middle click, ctrl+click, etc.)\n href: new URL(matchingVueRoute.fullPath.replace(/^(\\/)/, ''), base).href\n };\n }\n }\n\n return {\n is: 'a',\n target: isAbsolute && isExternal ? '_blank' : '_self',\n href: (() => {\n if (isHashPath) {\n const {\n location\n } = window;\n\n if (location.hash.length === 0) {\n // Current location might be `url#`\n const newHash = location.href.endsWith('#') ? href.slice(1) : href;\n return location.href + newHash;\n }\n\n return location.href.replace(location.hash, '') + href;\n }\n\n if (isIRC || isAbsolute) {\n return href;\n }\n\n return new URL(href, base).href;\n })(),\n rel: isAbsolute && isExternal ? 'noreferrer noopener' : undefined\n };\n }\n\n }\n});\n\n//# sourceURL=webpack://slim/./src/components/helpers/app-link.vue?./node_modules/babel-loader/lib/index.js??clonedRuleSet-1%5B0%5D.rules%5B0%5D!./node_modules/vue-loader/lib/index.js??vue-loader-options"); /***/ }), diff --git a/themes/light/assets/js/medusa-runtime.js b/themes/light/assets/js/medusa-runtime.js index 95ca5a215d..be38541233 100644 --- a/themes/light/assets/js/medusa-runtime.js +++ b/themes/light/assets/js/medusa-runtime.js @@ -235,7 +235,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var vuex__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! vuex */ \"./node_modules/vuex/dist/vuex.esm.js\");\n/* harmony import */ var _router__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../router */ \"./src/router/index.js\");\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n\n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({\n name: 'app-link',\n props: {\n to: [String, Object],\n href: String,\n indexerId: {\n type: String\n },\n placeholder: {\n type: String,\n default: 'indexer-to-name'\n }\n },\n computed: { ...(0,vuex__WEBPACK_IMPORTED_MODULE_1__.mapState)({\n general: state => state.config.general\n }),\n ...(0,vuex__WEBPACK_IMPORTED_MODULE_1__.mapGetters)(['indexerIdToName']),\n\n indexerName() {\n // Returns `undefined` if not found\n const {\n indexerId,\n indexerIdToName\n } = this;\n return indexerIdToName(indexerId);\n },\n\n computedBase() {\n return document.querySelector('base').getAttribute('href');\n },\n\n computedHref() {\n const {\n href,\n indexerId,\n placeholder,\n indexerName\n } = this;\n\n if (indexerId && placeholder) {\n return href.replace(placeholder, indexerName);\n }\n\n return href;\n },\n\n isIRC() {\n if (!this.computedHref) {\n return;\n }\n\n return this.computedHref.startsWith('irc://');\n },\n\n isAbsolute() {\n const href = this.computedHref;\n\n if (!href) {\n return;\n }\n\n return /^[a-z][\\d+.a-z-]*:/.test(href);\n },\n\n isExternal() {\n const base = this.computedBase;\n const href = this.computedHref;\n\n if (!href) {\n return;\n }\n\n return !href.startsWith(base) && !href.startsWith('webcal://');\n },\n\n isHashPath() {\n if (!this.computedHref) {\n return;\n }\n\n return this.computedHref.startsWith('#');\n },\n\n /**\n * Not used for now as derefer.org is down and there are no suitable replacements.\n * @returns {string} - de-refer service'd url.\n */\n anonymisedHref() {\n const {\n anonRedirect\n } = this.general;\n const href = this.computedHref;\n\n if (!href) {\n return;\n }\n\n return anonRedirect ? anonRedirect + href : href;\n },\n\n matchingVueRoute() {\n const {\n isAbsolute,\n isExternal,\n computedHref\n } = this;\n\n if (isAbsolute && isExternal) {\n return;\n }\n\n const {\n route\n } = _router__WEBPACK_IMPORTED_MODULE_0__[\"default\"].resolve('/' + computedHref);\n\n if (!route.name) {\n return;\n }\n\n return route;\n },\n\n linkProperties() {\n const {\n to,\n isIRC,\n isAbsolute,\n isExternal,\n isHashPath,\n matchingVueRoute\n } = this;\n const base = this.computedBase;\n const href = this.computedHref; // Return normal router-link\n\n if (to) {\n return {\n is: 'router-link',\n to\n };\n } // Just return a boring link with other attrs\n // @NOTE: This is for scroll anchors as it uses the id\n\n\n if (!href) {\n return {\n is: 'a',\n // Only tag this as a \"false-link\" if we passed a name in the props\n falseLink: Boolean(this.$attrs.name) || undefined\n };\n } // If current page and next page are both vue routes return router-link\n\n\n if (matchingVueRoute && this.$route && matchingVueRoute.meta.converted && this.$route.meta.converted) {\n // Allows us to skip when we're in a test\n if (window.loadMainApp) {\n return {\n is: 'router-link',\n to: matchingVueRoute.fullPath,\n // Add a `href` attribute to enable native mouse navigation (middle click, ctrl+click, etc.)\n href: new URL(matchingVueRoute.fullPath, base).href\n };\n }\n }\n\n return {\n is: 'a',\n target: isAbsolute && isExternal ? '_blank' : '_self',\n href: (() => {\n if (isHashPath) {\n const {\n location\n } = window;\n\n if (location.hash.length === 0) {\n // Current location might be `url#`\n const newHash = location.href.endsWith('#') ? href.slice(1) : href;\n return location.href + newHash;\n }\n\n return location.href.replace(location.hash, '') + href;\n }\n\n if (isIRC || isAbsolute) {\n return href;\n }\n\n return new URL(href, base).href;\n })(),\n rel: isAbsolute && isExternal ? 'noreferrer noopener' : undefined\n };\n }\n\n }\n});\n\n//# sourceURL=webpack://slim/./src/components/helpers/app-link.vue?./node_modules/babel-loader/lib/index.js??clonedRuleSet-1%5B0%5D.rules%5B0%5D!./node_modules/vue-loader/lib/index.js??vue-loader-options"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var vuex__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! vuex */ \"./node_modules/vuex/dist/vuex.esm.js\");\n/* harmony import */ var _router__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../router */ \"./src/router/index.js\");\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n\n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({\n name: 'app-link',\n props: {\n to: [String, Object],\n href: String,\n indexerId: {\n type: String\n },\n placeholder: {\n type: String,\n default: 'indexer-to-name'\n }\n },\n computed: { ...(0,vuex__WEBPACK_IMPORTED_MODULE_1__.mapState)({\n general: state => state.config.general\n }),\n ...(0,vuex__WEBPACK_IMPORTED_MODULE_1__.mapGetters)(['indexerIdToName']),\n\n indexerName() {\n // Returns `undefined` if not found\n const {\n indexerId,\n indexerIdToName\n } = this;\n return indexerIdToName(indexerId);\n },\n\n computedBase() {\n return document.querySelector('base').getAttribute('href');\n },\n\n computedHref() {\n const {\n href,\n indexerId,\n placeholder,\n indexerName\n } = this;\n\n if (indexerId && placeholder) {\n return href.replace(placeholder, indexerName);\n }\n\n return href;\n },\n\n isIRC() {\n if (!this.computedHref) {\n return;\n }\n\n return this.computedHref.startsWith('irc://');\n },\n\n isAbsolute() {\n const href = this.computedHref;\n\n if (!href) {\n return;\n }\n\n return /^[a-z][\\d+.a-z-]*:/.test(href);\n },\n\n isExternal() {\n const base = this.computedBase;\n const href = this.computedHref;\n\n if (!href) {\n return;\n }\n\n return !href.startsWith(base) && !href.startsWith('webcal://');\n },\n\n isHashPath() {\n if (!this.computedHref) {\n return;\n }\n\n return this.computedHref.startsWith('#');\n },\n\n /**\n * Not used for now as derefer.org is down and there are no suitable replacements.\n * @returns {string} - de-refer service'd url.\n */\n anonymisedHref() {\n const {\n anonRedirect\n } = this.general;\n const href = this.computedHref;\n\n if (!href) {\n return;\n }\n\n return anonRedirect ? anonRedirect + href : href;\n },\n\n matchingVueRoute() {\n const {\n isAbsolute,\n isExternal,\n computedHref\n } = this;\n\n if (isAbsolute && isExternal) {\n return;\n }\n\n const {\n route\n } = _router__WEBPACK_IMPORTED_MODULE_0__[\"default\"].resolve('/' + computedHref);\n\n if (!route.name) {\n return;\n }\n\n return route;\n },\n\n linkProperties() {\n const {\n to,\n isIRC,\n isAbsolute,\n isExternal,\n isHashPath,\n matchingVueRoute\n } = this;\n const base = this.computedBase;\n const href = this.computedHref; // Return normal router-link\n\n if (to) {\n return {\n is: 'router-link',\n to\n };\n } // Just return a boring link with other attrs\n // @NOTE: This is for scroll anchors as it uses the id\n\n\n if (!href) {\n return {\n is: 'a',\n // Only tag this as a \"false-link\" if we passed a name in the props\n falseLink: Boolean(this.$attrs.name) || undefined\n };\n } // If current page and next page are both vue routes return router-link\n\n\n if (matchingVueRoute && this.$route && matchingVueRoute.meta.converted && this.$route.meta.converted) {\n // Allows us to skip when we're in a test\n if (window.loadMainApp) {\n return {\n is: 'router-link',\n to: matchingVueRoute.fullPath,\n // Add a `href` attribute to enable native mouse navigation (middle click, ctrl+click, etc.)\n href: new URL(matchingVueRoute.fullPath.replace(/^(\\/)/, ''), base).href\n };\n }\n }\n\n return {\n is: 'a',\n target: isAbsolute && isExternal ? '_blank' : '_self',\n href: (() => {\n if (isHashPath) {\n const {\n location\n } = window;\n\n if (location.hash.length === 0) {\n // Current location might be `url#`\n const newHash = location.href.endsWith('#') ? href.slice(1) : href;\n return location.href + newHash;\n }\n\n return location.href.replace(location.hash, '') + href;\n }\n\n if (isIRC || isAbsolute) {\n return href;\n }\n\n return new URL(href, base).href;\n })(),\n rel: isAbsolute && isExternal ? 'noreferrer noopener' : undefined\n };\n }\n\n }\n});\n\n//# sourceURL=webpack://slim/./src/components/helpers/app-link.vue?./node_modules/babel-loader/lib/index.js??clonedRuleSet-1%5B0%5D.rules%5B0%5D!./node_modules/vue-loader/lib/index.js??vue-loader-options"); /***/ }), From 9d6e9f9d2f625fcc7c9190925886de4f07440cff Mon Sep 17 00:00:00 2001 From: p0psicles Date: Mon, 14 Mar 2022 19:53:31 +0100 Subject: [PATCH 6/9] Update layout when switching routes to 'home' --- themes-default/slim/src/components/show-list/poster.vue | 5 +++++ themes/dark/assets/js/medusa-runtime.js | 2 +- themes/light/assets/js/medusa-runtime.js | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/themes-default/slim/src/components/show-list/poster.vue b/themes-default/slim/src/components/show-list/poster.vue index d5b8da2a1d..8c31da7a9a 100644 --- a/themes-default/slim/src/components/show-list/poster.vue +++ b/themes-default/slim/src/components/show-list/poster.vue @@ -287,6 +287,11 @@ export default { container.classList.add('hide'); } } + }, + $route(to) { + if (to.name === 'home') { + this.updateLayout(); + } } } }; diff --git a/themes/dark/assets/js/medusa-runtime.js b/themes/dark/assets/js/medusa-runtime.js index be38541233..4e3231d0e0 100644 --- a/themes/dark/assets/js/medusa-runtime.js +++ b/themes/dark/assets/js/medusa-runtime.js @@ -1104,7 +1104,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var vuex__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! vuex */ \"./node_modules/vuex/dist/vuex.esm.js\");\n/* harmony import */ var pretty_bytes__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! pretty-bytes */ \"./node_modules/pretty-bytes/index.js\");\n/* harmony import */ var pretty_bytes__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(pretty_bytes__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _helpers__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../helpers */ \"./src/components/helpers/index.js\");\n/* harmony import */ var vueisotope__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! vueisotope */ \"./node_modules/vueisotope/dist/vue_isotope.min.js\");\n/* harmony import */ var vueisotope__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(vueisotope__WEBPACK_IMPORTED_MODULE_2__);\n/* harmony import */ var vue_images_loaded__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! vue-images-loaded */ \"./node_modules/vue-images-loaded/dist/vueimagesloaded.js\");\n/* harmony import */ var vue_images_loaded__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(vue_images_loaded__WEBPACK_IMPORTED_MODULE_3__);\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n\n\n\n\n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({\n name: 'poster',\n directives: {\n imagesLoaded: (vue_images_loaded__WEBPACK_IMPORTED_MODULE_3___default())\n },\n components: {\n Asset: _helpers__WEBPACK_IMPORTED_MODULE_1__.Asset,\n AppLink: _helpers__WEBPACK_IMPORTED_MODULE_1__.AppLink,\n ProgressBar: _helpers__WEBPACK_IMPORTED_MODULE_1__.ProgressBar,\n QualityPill: _helpers__WEBPACK_IMPORTED_MODULE_1__.QualityPill,\n Isotope: (vueisotope__WEBPACK_IMPORTED_MODULE_2___default())\n },\n props: {\n layout: {\n validator: val => val === null || typeof val === 'string',\n required: true\n },\n shows: {\n type: Array,\n required: true\n },\n listTitle: {\n type: String\n },\n header: {\n type: Boolean\n }\n },\n\n data() {\n return {\n // Isotope stuff\n itemSelector: '.show-container',\n selected: null,\n option: {\n getSortData: {\n id: row => row.id.slug,\n name: row => {\n const {\n stateLayout\n } = this;\n const {\n sortArticle\n } = stateLayout;\n\n if (sortArticle) {\n return row.title;\n }\n\n return row.title.replace(/^((?:a(?!\\s+to)n?)|the)\\s/i, '').toLowerCase();\n },\n date: row => {\n const {\n maxNextAirDate\n } = this;\n\n if (row.nextAirDate && Date.parse(row.nextAirDate) > Date.now()) {\n return Date.parse(row.nextAirDate) - Date.now();\n }\n\n if (row.prevAirDate) {\n return maxNextAirDate + Date.now() - Date.parse(row.prevAirDate);\n }\n\n return Date.now();\n },\n network: 'network',\n progress: row => {\n if (!row.stats) {\n return 0;\n }\n\n return Math.round(row.stats.episodes.downloaded / row.stats.episodes.total * 100);\n },\n indexer: row => {\n const {\n indexers\n } = this;\n return indexers.indexers[row.indexer].id;\n }\n },\n sortBy: () => this.posterSortBy,\n layoutMode: 'fitRows',\n sortAscending: () => this.posterSortDir\n },\n fontSize: null,\n logoWidth: null,\n borderRadius: null,\n borderWidth: null,\n isotopeLoaded: false,\n imgLazyLoad: null,\n filterByTitle: ''\n };\n },\n\n computed: { ...(0,vuex__WEBPACK_IMPORTED_MODULE_4__.mapState)({\n config: state => state.config.general,\n stateLayout: state => state.config.layout,\n indexers: state => state.config.indexers,\n // Need to map these computed, as we need them in the $watch.\n posterSortBy: state => state.config.layout.posterSortby,\n posterSortDir: state => state.config.layout.posterSortdir,\n posterSize: state => state.config.layout.local.posterSize,\n currentShowTab: state => state.config.layout.local.currentShowTab,\n showFilterByName: state => state.config.layout.local.showFilterByName\n }),\n ...(0,vuex__WEBPACK_IMPORTED_MODULE_4__.mapGetters)({\n fuzzyParseDateTime: 'fuzzyParseDateTime'\n }),\n\n showsReady() {\n const {\n shows,\n maxNextAirDate\n } = this;\n\n if (shows.length === 0 || !maxNextAirDate) {\n return [];\n }\n\n return shows;\n },\n\n showContainerStyle() {\n const {\n posterSize,\n borderWidth,\n borderRadius\n } = this;\n return {\n width: posterSize + 'px',\n borderWidth: borderWidth + 'px',\n borderRadius: borderRadius + 'px'\n };\n },\n\n maxNextAirDate() {\n const {\n shows\n } = this;\n return Math.max(...shows.filter(show => show.nextAirDate).map(show => Date.parse(show.nextAirDate)));\n }\n\n },\n methods: {\n prettyBytes: bytes => pretty_bytes__WEBPACK_IMPORTED_MODULE_0___default()(bytes),\n\n parsePrevDateFn(row) {\n const {\n fuzzyParseDateTime\n } = this;\n\n if (row.prevAirDate) {\n console.log(`Calculating time for show ${row.title} prev date: ${row.prevAirDate}`);\n return fuzzyParseDateTime(row.prevAirDate);\n }\n\n return '';\n },\n\n parseNextDateFn(row) {\n const {\n fuzzyParseDateTime\n } = this;\n\n if (row.nextAirDate) {\n console.log(`Calculating time for show ${row.title} next date: ${row.nextAirDate}`);\n return fuzzyParseDateTime(row.nextAirDate);\n }\n\n return '';\n },\n\n calculateSize() {\n const {\n posterSize\n } = this;\n\n if (posterSize < 125) {\n // Small\n this.fontSize = null;\n this.borderRadius = 3;\n this.borderWidth = 4;\n } else if (posterSize < 175) {\n // Medium\n this.fontSize = 9;\n this.logoWidth = 40;\n this.borderRadius = 4;\n this.borderWidth = 5;\n } else {\n // Large\n this.fontSize = 11;\n this.logoWidth = 50;\n this.borderRadius = 6;\n this.borderWidth = 6;\n }\n },\n\n updateLayout() {\n const {\n calculateSize,\n listTitle,\n posterSortBy,\n posterSortDir\n } = this;\n this.isotopeLoaded = true;\n calculateSize(); // If we can't find a layout, bail out, as there is nothing to arrange.\n\n if (this.$refs[`isotope-${listTitle}`] === undefined) {\n return;\n } // Render layout (for sizing)\n\n\n this.$refs[`isotope-${listTitle}`].layout(); // Arrange & Sort\n\n this.$refs[`isotope-${listTitle}`].arrange({\n sortBy: posterSortBy,\n sortAscending: posterSortDir\n });\n console.log('isotope Layout loaded');\n },\n\n dateOrStatus(show) {\n if (show.nextAirDate) {\n const {\n fuzzyParseDateTime\n } = this;\n return fuzzyParseDateTime(show.nextAirDate);\n }\n\n if (!show.status.includes('nded') && show.config.paused) {\n return 'Paused';\n }\n\n return show.status;\n }\n\n },\n watch: {\n posterSortBy(key) {\n const {\n listTitle\n } = this;\n this.$refs[`isotope-${listTitle}`].sort(key);\n },\n\n posterSortDir(value) {\n const {\n listTitle,\n posterSortBy\n } = this;\n this.$refs[`isotope-${listTitle}`].arrange({\n sortBy: posterSortBy,\n sortAscending: value\n });\n },\n\n posterSize(oldSize, newSize) {\n const {\n calculateSize,\n isotopeLoaded,\n listTitle\n } = this;\n\n if (!isotopeLoaded || oldSize === newSize) {\n return;\n }\n\n calculateSize();\n this.$nextTick(() => {\n this.$refs[`isotope-${listTitle}`].arrange();\n });\n },\n\n currentShowTab() {\n const {\n isotopeLoaded,\n listTitle\n } = this;\n\n if (!isotopeLoaded) {\n return;\n }\n\n this.$nextTick(() => {\n this.$refs[`isotope-${listTitle}`].arrange();\n });\n },\n\n showFilterByName(value) {\n const {\n $refs,\n listTitle\n } = this;\n const allContainers = $refs[`isotope-${listTitle}`].$el.querySelectorAll('.show-container');\n\n for (const container of allContainers) {\n if (container.textContent.toLowerCase().includes(value.toLowerCase())) {\n container.classList.remove('hide');\n } else {\n container.classList.add('hide');\n }\n }\n }\n\n }\n});\n\n//# sourceURL=webpack://slim/./src/components/show-list/poster.vue?./node_modules/babel-loader/lib/index.js??clonedRuleSet-1%5B0%5D.rules%5B0%5D!./node_modules/vue-loader/lib/index.js??vue-loader-options"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var vuex__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! vuex */ \"./node_modules/vuex/dist/vuex.esm.js\");\n/* harmony import */ var pretty_bytes__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! pretty-bytes */ \"./node_modules/pretty-bytes/index.js\");\n/* harmony import */ var pretty_bytes__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(pretty_bytes__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _helpers__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../helpers */ \"./src/components/helpers/index.js\");\n/* harmony import */ var vueisotope__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! vueisotope */ \"./node_modules/vueisotope/dist/vue_isotope.min.js\");\n/* harmony import */ var vueisotope__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(vueisotope__WEBPACK_IMPORTED_MODULE_2__);\n/* harmony import */ var vue_images_loaded__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! vue-images-loaded */ \"./node_modules/vue-images-loaded/dist/vueimagesloaded.js\");\n/* harmony import */ var vue_images_loaded__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(vue_images_loaded__WEBPACK_IMPORTED_MODULE_3__);\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n\n\n\n\n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({\n name: 'poster',\n directives: {\n imagesLoaded: (vue_images_loaded__WEBPACK_IMPORTED_MODULE_3___default())\n },\n components: {\n Asset: _helpers__WEBPACK_IMPORTED_MODULE_1__.Asset,\n AppLink: _helpers__WEBPACK_IMPORTED_MODULE_1__.AppLink,\n ProgressBar: _helpers__WEBPACK_IMPORTED_MODULE_1__.ProgressBar,\n QualityPill: _helpers__WEBPACK_IMPORTED_MODULE_1__.QualityPill,\n Isotope: (vueisotope__WEBPACK_IMPORTED_MODULE_2___default())\n },\n props: {\n layout: {\n validator: val => val === null || typeof val === 'string',\n required: true\n },\n shows: {\n type: Array,\n required: true\n },\n listTitle: {\n type: String\n },\n header: {\n type: Boolean\n }\n },\n\n data() {\n return {\n // Isotope stuff\n itemSelector: '.show-container',\n selected: null,\n option: {\n getSortData: {\n id: row => row.id.slug,\n name: row => {\n const {\n stateLayout\n } = this;\n const {\n sortArticle\n } = stateLayout;\n\n if (sortArticle) {\n return row.title;\n }\n\n return row.title.replace(/^((?:a(?!\\s+to)n?)|the)\\s/i, '').toLowerCase();\n },\n date: row => {\n const {\n maxNextAirDate\n } = this;\n\n if (row.nextAirDate && Date.parse(row.nextAirDate) > Date.now()) {\n return Date.parse(row.nextAirDate) - Date.now();\n }\n\n if (row.prevAirDate) {\n return maxNextAirDate + Date.now() - Date.parse(row.prevAirDate);\n }\n\n return Date.now();\n },\n network: 'network',\n progress: row => {\n if (!row.stats) {\n return 0;\n }\n\n return Math.round(row.stats.episodes.downloaded / row.stats.episodes.total * 100);\n },\n indexer: row => {\n const {\n indexers\n } = this;\n return indexers.indexers[row.indexer].id;\n }\n },\n sortBy: () => this.posterSortBy,\n layoutMode: 'fitRows',\n sortAscending: () => this.posterSortDir\n },\n fontSize: null,\n logoWidth: null,\n borderRadius: null,\n borderWidth: null,\n isotopeLoaded: false,\n imgLazyLoad: null,\n filterByTitle: ''\n };\n },\n\n computed: { ...(0,vuex__WEBPACK_IMPORTED_MODULE_4__.mapState)({\n config: state => state.config.general,\n stateLayout: state => state.config.layout,\n indexers: state => state.config.indexers,\n // Need to map these computed, as we need them in the $watch.\n posterSortBy: state => state.config.layout.posterSortby,\n posterSortDir: state => state.config.layout.posterSortdir,\n posterSize: state => state.config.layout.local.posterSize,\n currentShowTab: state => state.config.layout.local.currentShowTab,\n showFilterByName: state => state.config.layout.local.showFilterByName\n }),\n ...(0,vuex__WEBPACK_IMPORTED_MODULE_4__.mapGetters)({\n fuzzyParseDateTime: 'fuzzyParseDateTime'\n }),\n\n showsReady() {\n const {\n shows,\n maxNextAirDate\n } = this;\n\n if (shows.length === 0 || !maxNextAirDate) {\n return [];\n }\n\n return shows;\n },\n\n showContainerStyle() {\n const {\n posterSize,\n borderWidth,\n borderRadius\n } = this;\n return {\n width: posterSize + 'px',\n borderWidth: borderWidth + 'px',\n borderRadius: borderRadius + 'px'\n };\n },\n\n maxNextAirDate() {\n const {\n shows\n } = this;\n return Math.max(...shows.filter(show => show.nextAirDate).map(show => Date.parse(show.nextAirDate)));\n }\n\n },\n methods: {\n prettyBytes: bytes => pretty_bytes__WEBPACK_IMPORTED_MODULE_0___default()(bytes),\n\n parsePrevDateFn(row) {\n const {\n fuzzyParseDateTime\n } = this;\n\n if (row.prevAirDate) {\n console.log(`Calculating time for show ${row.title} prev date: ${row.prevAirDate}`);\n return fuzzyParseDateTime(row.prevAirDate);\n }\n\n return '';\n },\n\n parseNextDateFn(row) {\n const {\n fuzzyParseDateTime\n } = this;\n\n if (row.nextAirDate) {\n console.log(`Calculating time for show ${row.title} next date: ${row.nextAirDate}`);\n return fuzzyParseDateTime(row.nextAirDate);\n }\n\n return '';\n },\n\n calculateSize() {\n const {\n posterSize\n } = this;\n\n if (posterSize < 125) {\n // Small\n this.fontSize = null;\n this.borderRadius = 3;\n this.borderWidth = 4;\n } else if (posterSize < 175) {\n // Medium\n this.fontSize = 9;\n this.logoWidth = 40;\n this.borderRadius = 4;\n this.borderWidth = 5;\n } else {\n // Large\n this.fontSize = 11;\n this.logoWidth = 50;\n this.borderRadius = 6;\n this.borderWidth = 6;\n }\n },\n\n updateLayout() {\n const {\n calculateSize,\n listTitle,\n posterSortBy,\n posterSortDir\n } = this;\n this.isotopeLoaded = true;\n calculateSize(); // If we can't find a layout, bail out, as there is nothing to arrange.\n\n if (this.$refs[`isotope-${listTitle}`] === undefined) {\n return;\n } // Render layout (for sizing)\n\n\n this.$refs[`isotope-${listTitle}`].layout(); // Arrange & Sort\n\n this.$refs[`isotope-${listTitle}`].arrange({\n sortBy: posterSortBy,\n sortAscending: posterSortDir\n });\n console.log('isotope Layout loaded');\n },\n\n dateOrStatus(show) {\n if (show.nextAirDate) {\n const {\n fuzzyParseDateTime\n } = this;\n return fuzzyParseDateTime(show.nextAirDate);\n }\n\n if (!show.status.includes('nded') && show.config.paused) {\n return 'Paused';\n }\n\n return show.status;\n }\n\n },\n watch: {\n posterSortBy(key) {\n const {\n listTitle\n } = this;\n this.$refs[`isotope-${listTitle}`].sort(key);\n },\n\n posterSortDir(value) {\n const {\n listTitle,\n posterSortBy\n } = this;\n this.$refs[`isotope-${listTitle}`].arrange({\n sortBy: posterSortBy,\n sortAscending: value\n });\n },\n\n posterSize(oldSize, newSize) {\n const {\n calculateSize,\n isotopeLoaded,\n listTitle\n } = this;\n\n if (!isotopeLoaded || oldSize === newSize) {\n return;\n }\n\n calculateSize();\n this.$nextTick(() => {\n this.$refs[`isotope-${listTitle}`].arrange();\n });\n },\n\n currentShowTab() {\n const {\n isotopeLoaded,\n listTitle\n } = this;\n\n if (!isotopeLoaded) {\n return;\n }\n\n this.$nextTick(() => {\n this.$refs[`isotope-${listTitle}`].arrange();\n });\n },\n\n showFilterByName(value) {\n const {\n $refs,\n listTitle\n } = this;\n const allContainers = $refs[`isotope-${listTitle}`].$el.querySelectorAll('.show-container');\n\n for (const container of allContainers) {\n if (container.textContent.toLowerCase().includes(value.toLowerCase())) {\n container.classList.remove('hide');\n } else {\n container.classList.add('hide');\n }\n }\n },\n\n $route(to) {\n if (to.name === 'home') {\n this.updateLayout();\n }\n }\n\n }\n});\n\n//# sourceURL=webpack://slim/./src/components/show-list/poster.vue?./node_modules/babel-loader/lib/index.js??clonedRuleSet-1%5B0%5D.rules%5B0%5D!./node_modules/vue-loader/lib/index.js??vue-loader-options"); /***/ }), diff --git a/themes/light/assets/js/medusa-runtime.js b/themes/light/assets/js/medusa-runtime.js index be38541233..4e3231d0e0 100644 --- a/themes/light/assets/js/medusa-runtime.js +++ b/themes/light/assets/js/medusa-runtime.js @@ -1104,7 +1104,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var vuex__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! vuex */ \"./node_modules/vuex/dist/vuex.esm.js\");\n/* harmony import */ var pretty_bytes__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! pretty-bytes */ \"./node_modules/pretty-bytes/index.js\");\n/* harmony import */ var pretty_bytes__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(pretty_bytes__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _helpers__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../helpers */ \"./src/components/helpers/index.js\");\n/* harmony import */ var vueisotope__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! vueisotope */ \"./node_modules/vueisotope/dist/vue_isotope.min.js\");\n/* harmony import */ var vueisotope__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(vueisotope__WEBPACK_IMPORTED_MODULE_2__);\n/* harmony import */ var vue_images_loaded__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! vue-images-loaded */ \"./node_modules/vue-images-loaded/dist/vueimagesloaded.js\");\n/* harmony import */ var vue_images_loaded__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(vue_images_loaded__WEBPACK_IMPORTED_MODULE_3__);\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n\n\n\n\n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({\n name: 'poster',\n directives: {\n imagesLoaded: (vue_images_loaded__WEBPACK_IMPORTED_MODULE_3___default())\n },\n components: {\n Asset: _helpers__WEBPACK_IMPORTED_MODULE_1__.Asset,\n AppLink: _helpers__WEBPACK_IMPORTED_MODULE_1__.AppLink,\n ProgressBar: _helpers__WEBPACK_IMPORTED_MODULE_1__.ProgressBar,\n QualityPill: _helpers__WEBPACK_IMPORTED_MODULE_1__.QualityPill,\n Isotope: (vueisotope__WEBPACK_IMPORTED_MODULE_2___default())\n },\n props: {\n layout: {\n validator: val => val === null || typeof val === 'string',\n required: true\n },\n shows: {\n type: Array,\n required: true\n },\n listTitle: {\n type: String\n },\n header: {\n type: Boolean\n }\n },\n\n data() {\n return {\n // Isotope stuff\n itemSelector: '.show-container',\n selected: null,\n option: {\n getSortData: {\n id: row => row.id.slug,\n name: row => {\n const {\n stateLayout\n } = this;\n const {\n sortArticle\n } = stateLayout;\n\n if (sortArticle) {\n return row.title;\n }\n\n return row.title.replace(/^((?:a(?!\\s+to)n?)|the)\\s/i, '').toLowerCase();\n },\n date: row => {\n const {\n maxNextAirDate\n } = this;\n\n if (row.nextAirDate && Date.parse(row.nextAirDate) > Date.now()) {\n return Date.parse(row.nextAirDate) - Date.now();\n }\n\n if (row.prevAirDate) {\n return maxNextAirDate + Date.now() - Date.parse(row.prevAirDate);\n }\n\n return Date.now();\n },\n network: 'network',\n progress: row => {\n if (!row.stats) {\n return 0;\n }\n\n return Math.round(row.stats.episodes.downloaded / row.stats.episodes.total * 100);\n },\n indexer: row => {\n const {\n indexers\n } = this;\n return indexers.indexers[row.indexer].id;\n }\n },\n sortBy: () => this.posterSortBy,\n layoutMode: 'fitRows',\n sortAscending: () => this.posterSortDir\n },\n fontSize: null,\n logoWidth: null,\n borderRadius: null,\n borderWidth: null,\n isotopeLoaded: false,\n imgLazyLoad: null,\n filterByTitle: ''\n };\n },\n\n computed: { ...(0,vuex__WEBPACK_IMPORTED_MODULE_4__.mapState)({\n config: state => state.config.general,\n stateLayout: state => state.config.layout,\n indexers: state => state.config.indexers,\n // Need to map these computed, as we need them in the $watch.\n posterSortBy: state => state.config.layout.posterSortby,\n posterSortDir: state => state.config.layout.posterSortdir,\n posterSize: state => state.config.layout.local.posterSize,\n currentShowTab: state => state.config.layout.local.currentShowTab,\n showFilterByName: state => state.config.layout.local.showFilterByName\n }),\n ...(0,vuex__WEBPACK_IMPORTED_MODULE_4__.mapGetters)({\n fuzzyParseDateTime: 'fuzzyParseDateTime'\n }),\n\n showsReady() {\n const {\n shows,\n maxNextAirDate\n } = this;\n\n if (shows.length === 0 || !maxNextAirDate) {\n return [];\n }\n\n return shows;\n },\n\n showContainerStyle() {\n const {\n posterSize,\n borderWidth,\n borderRadius\n } = this;\n return {\n width: posterSize + 'px',\n borderWidth: borderWidth + 'px',\n borderRadius: borderRadius + 'px'\n };\n },\n\n maxNextAirDate() {\n const {\n shows\n } = this;\n return Math.max(...shows.filter(show => show.nextAirDate).map(show => Date.parse(show.nextAirDate)));\n }\n\n },\n methods: {\n prettyBytes: bytes => pretty_bytes__WEBPACK_IMPORTED_MODULE_0___default()(bytes),\n\n parsePrevDateFn(row) {\n const {\n fuzzyParseDateTime\n } = this;\n\n if (row.prevAirDate) {\n console.log(`Calculating time for show ${row.title} prev date: ${row.prevAirDate}`);\n return fuzzyParseDateTime(row.prevAirDate);\n }\n\n return '';\n },\n\n parseNextDateFn(row) {\n const {\n fuzzyParseDateTime\n } = this;\n\n if (row.nextAirDate) {\n console.log(`Calculating time for show ${row.title} next date: ${row.nextAirDate}`);\n return fuzzyParseDateTime(row.nextAirDate);\n }\n\n return '';\n },\n\n calculateSize() {\n const {\n posterSize\n } = this;\n\n if (posterSize < 125) {\n // Small\n this.fontSize = null;\n this.borderRadius = 3;\n this.borderWidth = 4;\n } else if (posterSize < 175) {\n // Medium\n this.fontSize = 9;\n this.logoWidth = 40;\n this.borderRadius = 4;\n this.borderWidth = 5;\n } else {\n // Large\n this.fontSize = 11;\n this.logoWidth = 50;\n this.borderRadius = 6;\n this.borderWidth = 6;\n }\n },\n\n updateLayout() {\n const {\n calculateSize,\n listTitle,\n posterSortBy,\n posterSortDir\n } = this;\n this.isotopeLoaded = true;\n calculateSize(); // If we can't find a layout, bail out, as there is nothing to arrange.\n\n if (this.$refs[`isotope-${listTitle}`] === undefined) {\n return;\n } // Render layout (for sizing)\n\n\n this.$refs[`isotope-${listTitle}`].layout(); // Arrange & Sort\n\n this.$refs[`isotope-${listTitle}`].arrange({\n sortBy: posterSortBy,\n sortAscending: posterSortDir\n });\n console.log('isotope Layout loaded');\n },\n\n dateOrStatus(show) {\n if (show.nextAirDate) {\n const {\n fuzzyParseDateTime\n } = this;\n return fuzzyParseDateTime(show.nextAirDate);\n }\n\n if (!show.status.includes('nded') && show.config.paused) {\n return 'Paused';\n }\n\n return show.status;\n }\n\n },\n watch: {\n posterSortBy(key) {\n const {\n listTitle\n } = this;\n this.$refs[`isotope-${listTitle}`].sort(key);\n },\n\n posterSortDir(value) {\n const {\n listTitle,\n posterSortBy\n } = this;\n this.$refs[`isotope-${listTitle}`].arrange({\n sortBy: posterSortBy,\n sortAscending: value\n });\n },\n\n posterSize(oldSize, newSize) {\n const {\n calculateSize,\n isotopeLoaded,\n listTitle\n } = this;\n\n if (!isotopeLoaded || oldSize === newSize) {\n return;\n }\n\n calculateSize();\n this.$nextTick(() => {\n this.$refs[`isotope-${listTitle}`].arrange();\n });\n },\n\n currentShowTab() {\n const {\n isotopeLoaded,\n listTitle\n } = this;\n\n if (!isotopeLoaded) {\n return;\n }\n\n this.$nextTick(() => {\n this.$refs[`isotope-${listTitle}`].arrange();\n });\n },\n\n showFilterByName(value) {\n const {\n $refs,\n listTitle\n } = this;\n const allContainers = $refs[`isotope-${listTitle}`].$el.querySelectorAll('.show-container');\n\n for (const container of allContainers) {\n if (container.textContent.toLowerCase().includes(value.toLowerCase())) {\n container.classList.remove('hide');\n } else {\n container.classList.add('hide');\n }\n }\n }\n\n }\n});\n\n//# sourceURL=webpack://slim/./src/components/show-list/poster.vue?./node_modules/babel-loader/lib/index.js??clonedRuleSet-1%5B0%5D.rules%5B0%5D!./node_modules/vue-loader/lib/index.js??vue-loader-options"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var vuex__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! vuex */ \"./node_modules/vuex/dist/vuex.esm.js\");\n/* harmony import */ var pretty_bytes__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! pretty-bytes */ \"./node_modules/pretty-bytes/index.js\");\n/* harmony import */ var pretty_bytes__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(pretty_bytes__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _helpers__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../helpers */ \"./src/components/helpers/index.js\");\n/* harmony import */ var vueisotope__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! vueisotope */ \"./node_modules/vueisotope/dist/vue_isotope.min.js\");\n/* harmony import */ var vueisotope__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(vueisotope__WEBPACK_IMPORTED_MODULE_2__);\n/* harmony import */ var vue_images_loaded__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! vue-images-loaded */ \"./node_modules/vue-images-loaded/dist/vueimagesloaded.js\");\n/* harmony import */ var vue_images_loaded__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(vue_images_loaded__WEBPACK_IMPORTED_MODULE_3__);\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n\n\n\n\n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({\n name: 'poster',\n directives: {\n imagesLoaded: (vue_images_loaded__WEBPACK_IMPORTED_MODULE_3___default())\n },\n components: {\n Asset: _helpers__WEBPACK_IMPORTED_MODULE_1__.Asset,\n AppLink: _helpers__WEBPACK_IMPORTED_MODULE_1__.AppLink,\n ProgressBar: _helpers__WEBPACK_IMPORTED_MODULE_1__.ProgressBar,\n QualityPill: _helpers__WEBPACK_IMPORTED_MODULE_1__.QualityPill,\n Isotope: (vueisotope__WEBPACK_IMPORTED_MODULE_2___default())\n },\n props: {\n layout: {\n validator: val => val === null || typeof val === 'string',\n required: true\n },\n shows: {\n type: Array,\n required: true\n },\n listTitle: {\n type: String\n },\n header: {\n type: Boolean\n }\n },\n\n data() {\n return {\n // Isotope stuff\n itemSelector: '.show-container',\n selected: null,\n option: {\n getSortData: {\n id: row => row.id.slug,\n name: row => {\n const {\n stateLayout\n } = this;\n const {\n sortArticle\n } = stateLayout;\n\n if (sortArticle) {\n return row.title;\n }\n\n return row.title.replace(/^((?:a(?!\\s+to)n?)|the)\\s/i, '').toLowerCase();\n },\n date: row => {\n const {\n maxNextAirDate\n } = this;\n\n if (row.nextAirDate && Date.parse(row.nextAirDate) > Date.now()) {\n return Date.parse(row.nextAirDate) - Date.now();\n }\n\n if (row.prevAirDate) {\n return maxNextAirDate + Date.now() - Date.parse(row.prevAirDate);\n }\n\n return Date.now();\n },\n network: 'network',\n progress: row => {\n if (!row.stats) {\n return 0;\n }\n\n return Math.round(row.stats.episodes.downloaded / row.stats.episodes.total * 100);\n },\n indexer: row => {\n const {\n indexers\n } = this;\n return indexers.indexers[row.indexer].id;\n }\n },\n sortBy: () => this.posterSortBy,\n layoutMode: 'fitRows',\n sortAscending: () => this.posterSortDir\n },\n fontSize: null,\n logoWidth: null,\n borderRadius: null,\n borderWidth: null,\n isotopeLoaded: false,\n imgLazyLoad: null,\n filterByTitle: ''\n };\n },\n\n computed: { ...(0,vuex__WEBPACK_IMPORTED_MODULE_4__.mapState)({\n config: state => state.config.general,\n stateLayout: state => state.config.layout,\n indexers: state => state.config.indexers,\n // Need to map these computed, as we need them in the $watch.\n posterSortBy: state => state.config.layout.posterSortby,\n posterSortDir: state => state.config.layout.posterSortdir,\n posterSize: state => state.config.layout.local.posterSize,\n currentShowTab: state => state.config.layout.local.currentShowTab,\n showFilterByName: state => state.config.layout.local.showFilterByName\n }),\n ...(0,vuex__WEBPACK_IMPORTED_MODULE_4__.mapGetters)({\n fuzzyParseDateTime: 'fuzzyParseDateTime'\n }),\n\n showsReady() {\n const {\n shows,\n maxNextAirDate\n } = this;\n\n if (shows.length === 0 || !maxNextAirDate) {\n return [];\n }\n\n return shows;\n },\n\n showContainerStyle() {\n const {\n posterSize,\n borderWidth,\n borderRadius\n } = this;\n return {\n width: posterSize + 'px',\n borderWidth: borderWidth + 'px',\n borderRadius: borderRadius + 'px'\n };\n },\n\n maxNextAirDate() {\n const {\n shows\n } = this;\n return Math.max(...shows.filter(show => show.nextAirDate).map(show => Date.parse(show.nextAirDate)));\n }\n\n },\n methods: {\n prettyBytes: bytes => pretty_bytes__WEBPACK_IMPORTED_MODULE_0___default()(bytes),\n\n parsePrevDateFn(row) {\n const {\n fuzzyParseDateTime\n } = this;\n\n if (row.prevAirDate) {\n console.log(`Calculating time for show ${row.title} prev date: ${row.prevAirDate}`);\n return fuzzyParseDateTime(row.prevAirDate);\n }\n\n return '';\n },\n\n parseNextDateFn(row) {\n const {\n fuzzyParseDateTime\n } = this;\n\n if (row.nextAirDate) {\n console.log(`Calculating time for show ${row.title} next date: ${row.nextAirDate}`);\n return fuzzyParseDateTime(row.nextAirDate);\n }\n\n return '';\n },\n\n calculateSize() {\n const {\n posterSize\n } = this;\n\n if (posterSize < 125) {\n // Small\n this.fontSize = null;\n this.borderRadius = 3;\n this.borderWidth = 4;\n } else if (posterSize < 175) {\n // Medium\n this.fontSize = 9;\n this.logoWidth = 40;\n this.borderRadius = 4;\n this.borderWidth = 5;\n } else {\n // Large\n this.fontSize = 11;\n this.logoWidth = 50;\n this.borderRadius = 6;\n this.borderWidth = 6;\n }\n },\n\n updateLayout() {\n const {\n calculateSize,\n listTitle,\n posterSortBy,\n posterSortDir\n } = this;\n this.isotopeLoaded = true;\n calculateSize(); // If we can't find a layout, bail out, as there is nothing to arrange.\n\n if (this.$refs[`isotope-${listTitle}`] === undefined) {\n return;\n } // Render layout (for sizing)\n\n\n this.$refs[`isotope-${listTitle}`].layout(); // Arrange & Sort\n\n this.$refs[`isotope-${listTitle}`].arrange({\n sortBy: posterSortBy,\n sortAscending: posterSortDir\n });\n console.log('isotope Layout loaded');\n },\n\n dateOrStatus(show) {\n if (show.nextAirDate) {\n const {\n fuzzyParseDateTime\n } = this;\n return fuzzyParseDateTime(show.nextAirDate);\n }\n\n if (!show.status.includes('nded') && show.config.paused) {\n return 'Paused';\n }\n\n return show.status;\n }\n\n },\n watch: {\n posterSortBy(key) {\n const {\n listTitle\n } = this;\n this.$refs[`isotope-${listTitle}`].sort(key);\n },\n\n posterSortDir(value) {\n const {\n listTitle,\n posterSortBy\n } = this;\n this.$refs[`isotope-${listTitle}`].arrange({\n sortBy: posterSortBy,\n sortAscending: value\n });\n },\n\n posterSize(oldSize, newSize) {\n const {\n calculateSize,\n isotopeLoaded,\n listTitle\n } = this;\n\n if (!isotopeLoaded || oldSize === newSize) {\n return;\n }\n\n calculateSize();\n this.$nextTick(() => {\n this.$refs[`isotope-${listTitle}`].arrange();\n });\n },\n\n currentShowTab() {\n const {\n isotopeLoaded,\n listTitle\n } = this;\n\n if (!isotopeLoaded) {\n return;\n }\n\n this.$nextTick(() => {\n this.$refs[`isotope-${listTitle}`].arrange();\n });\n },\n\n showFilterByName(value) {\n const {\n $refs,\n listTitle\n } = this;\n const allContainers = $refs[`isotope-${listTitle}`].$el.querySelectorAll('.show-container');\n\n for (const container of allContainers) {\n if (container.textContent.toLowerCase().includes(value.toLowerCase())) {\n container.classList.remove('hide');\n } else {\n container.classList.add('hide');\n }\n }\n },\n\n $route(to) {\n if (to.name === 'home') {\n this.updateLayout();\n }\n }\n\n }\n});\n\n//# sourceURL=webpack://slim/./src/components/show-list/poster.vue?./node_modules/babel-loader/lib/index.js??clonedRuleSet-1%5B0%5D.rules%5B0%5D!./node_modules/vue-loader/lib/index.js??vue-loader-options"); /***/ }), From 6a7d826616282b83a864592253adfb70668ca532 Mon Sep 17 00:00:00 2001 From: p0psicles Date: Tue, 15 Mar 2022 08:20:23 +0100 Subject: [PATCH 7/9] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa644b7ced..47c14afcf9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ #### New Features #### Improvements +- UI now behaves as a singel page app. ([10408](https://github.com/pymedusa/Medusa/pull/10408)) #### Fixes From 88e90cb88af9ef578c551ab44b75c161b02852aa Mon Sep 17 00:00:00 2001 From: p0psicles Date: Tue, 15 Mar 2022 08:22:48 +0100 Subject: [PATCH 8/9] improve note. --- themes-default/slim/src/components/manage-searches.vue | 4 ++-- themes/dark/assets/js/medusa-runtime.js | 2 +- themes/light/assets/js/medusa-runtime.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/themes-default/slim/src/components/manage-searches.vue b/themes-default/slim/src/components/manage-searches.vue index e4f029ccca..a134e364d8 100644 --- a/themes-default/slim/src/components/manage-searches.vue +++ b/themes-default/slim/src/components/manage-searches.vue @@ -87,8 +87,8 @@

Force refresh recommended list:

Note! Syncing shows with a recommended list may take a while. - The action will be queued. For example staring a sync with Trakt and directly after Imdb. - You will won't see results for Imdb after the sync of Trakt has fully completed. + The action will be queued. For example, starting syncs for Trakt and Imdb. + You will not see results for Imdb until after the sync of Trakt has finished.