diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index 7c7801b866..31f00754f5 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -2,8 +2,7 @@ trigger: batch: true branches: include: - - master - - release-* + - '*' tags: include: - '*' @@ -13,90 +12,94 @@ pr: - '*' jobs: - - job: build - displayName: 'Build' +- job: Build + displayName: 'Build' - pool: - vmImage: 'ubuntu-latest' + strategy: + matrix: + Development: + BuildConfiguration: development + Production: + BuildConfiguration: production + Standalone: + BuildConfiguration: standalone - strategy: - matrix: - Development: - BuildConfiguration: development - Production: - BuildConfiguration: production - Standalone: - BuildConfiguration: standalone - maxParallel: 3 + pool: + vmImage: 'ubuntu-latest' - steps: - - task: NodeTool@0 - displayName: 'Install Node' - inputs: - versionSpec: '12.x' + steps: + - task: NodeTool@0 + displayName: 'Install Node' + inputs: + versionSpec: '12.x' - - task: Cache@2 - displayName: 'Check Cache' - inputs: - key: 'yarn | yarn.lock' - path: 'node_modules' - cacheHitVar: CACHE_RESTORED + - task: Cache@2 + displayName: 'Check Cache' + inputs: + key: 'yarn | yarn.lock' + path: 'node_modules' + cacheHitVar: CACHE_RESTORED - - script: 'yarn install --frozen-lockfile' - displayName: 'Install Dependencies' - condition: ne(variables.CACHE_RESTORED, 'true') + - script: 'yarn install --frozen-lockfile' + displayName: 'Install Dependencies' + condition: ne(variables.CACHE_RESTORED, 'true') - - script: 'yarn build:development' - displayName: 'Build Development' - condition: eq(variables['BuildConfiguration'], 'development') + - script: 'yarn build:development' + displayName: 'Build Development' + condition: eq(variables['BuildConfiguration'], 'development') - - script: 'yarn build:production' - displayName: 'Build Bundle' - condition: eq(variables['BuildConfiguration'], 'production') + - script: 'yarn build:production' + displayName: 'Build Bundle' + condition: eq(variables['BuildConfiguration'], 'production') - - script: 'yarn build:standalone' - displayName: 'Build Standalone' - condition: eq(variables['BuildConfiguration'], 'standalone') + - script: 'yarn build:standalone' + displayName: 'Build Standalone' + condition: eq(variables['BuildConfiguration'], 'standalone') - - script: 'test -d dist' - displayName: 'Check Build' + - script: 'test -d dist' + displayName: 'Check Build' - - script: 'mv dist jellyfin-web' - displayName: 'Rename Directory' - condition: succeeded() + - script: 'mv dist jellyfin-web' + displayName: 'Rename Directory' - - task: PublishPipelineArtifact@1 - displayName: 'Publish Release' - condition: succeeded() - inputs: - targetPath: '$(Build.SourcesDirectory)/jellyfin-web' - artifactName: 'jellyfin-web-$(BuildConfiguration)' + - task: ArchiveFiles@2 + displayName: 'Archive Directory' + inputs: + rootFolderOrFile: 'jellyfin-web' + includeRootFolder: true + archiveFile: 'jellyfin-web-$(BuildConfiguration)' - - job: lint - displayName: 'Lint' + - task: PublishPipelineArtifact@1 + displayName: 'Publish Release' + inputs: + targetPath: '$(Build.SourcesDirectory)/jellyfin-web-$(BuildConfiguration).zip' + artifactName: 'jellyfin-web-$(BuildConfiguration)' - pool: - vmImage: 'ubuntu-latest' +- job: Lint + displayName: 'Lint' - steps: - - task: NodeTool@0 - displayName: 'Install Node' - inputs: - versionSpec: '12.x' + pool: + vmImage: 'ubuntu-latest' - - task: Cache@2 - displayName: 'Check Cache' - inputs: - key: 'yarn | yarn.lock' - path: 'node_modules' - cacheHitVar: CACHE_RESTORED + steps: + - task: NodeTool@0 + displayName: 'Install Node' + inputs: + versionSpec: '12.x' - - script: 'yarn install --frozen-lockfile' - displayName: 'Install Dependencies' - condition: ne(variables.CACHE_RESTORED, 'true') + - task: Cache@2 + displayName: 'Check Cache' + inputs: + key: 'yarn | yarn.lock' + path: 'node_modules' + cacheHitVar: CACHE_RESTORED - - script: 'yarn run lint' - displayName: 'Run ESLint' + - script: 'yarn install --frozen-lockfile' + displayName: 'Install Dependencies' + condition: ne(variables.CACHE_RESTORED, 'true') - - script: 'yarn run stylelint' - displayName: 'Run Stylelint' + - script: 'yarn run lint --quiet' + displayName: 'Run ESLint' + + - script: 'yarn run stylelint' + displayName: 'Run Stylelint' diff --git a/.eslintrc.yml b/.eslintrc.yml index 377716d53c..0b92c0c9b0 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -1,21 +1,32 @@ env: - es6: true - browser: true amd: true + browser: true + es6: true + es2017: true + es2020: true parserOptions: - ecmaVersion: 6 + ecmaVersion: 2020 sourceType: module ecmaFeatures: impliedStrict: true +plugins: + - promise + - import + - eslint-comments + +extends: + - eslint:recommended + - plugin:promise/recommended + - plugin:import/errors + - plugin:import/warnings + - plugin:eslint-comments/recommended + - plugin:compat/recommended + globals: - # New browser globals - DataView: readonly + # Browser globals MediaMetadata: readonly - Promise: readonly - # Deprecated browser globals - DocumentTouch: readonly # Tizen globals tizen: readonly webapis: readonly @@ -24,7 +35,6 @@ globals: # Dependency globals $: readonly jQuery: readonly - queryString: readonly requirejs: readonly # Jellyfin globals ApiClient: writable @@ -40,8 +50,7 @@ globals: getWindowLocationSearch: writable Globalize: writable Hls: writable - humaneDate: writable - humaneElapsed: writable + dfnshelper: writable LibraryMenu: writable LinkParser: writable LiveTvHelpers: writable @@ -52,9 +61,6 @@ globals: UserParentalControlPage: writable Windows: readonly -extends: - - eslint:recommended - rules: block-spacing: ["error"] brace-style: ["error"] @@ -69,9 +75,97 @@ rules: no-multiple-empty-lines: ["error", { "max": 1 }] no-trailing-spaces: ["error"] one-var: ["error", "never"] - semi: ["warn"] + semi: ["error"] space-before-blocks: ["error"] # TODO: Fix warnings and remove these rules no-redeclare: ["warn"] no-unused-vars: ["warn"] no-useless-escape: ["warn"] + promise/catch-or-return: ["warn"] + promise/always-return: ["warn"] + promise/no-return-wrap: ["warn"] + # TODO: Remove after ES6 migration is complete + import/no-unresolved: ["warn"] + +settings: + polyfills: + # Native Promises Only + - Promise + # whatwg-fetch + - fetch + # document-register-element + - document.registerElement + # resize-observer-polyfill + - ResizeObserver + # fast-text-encoding + - TextEncoder + # intersection-observer + - IntersectionObserver + # Core-js + - Object.assign + - Object.is + - Object.setPrototypeOf + - Object.toString + - Object.freeze + - Object.seal + - Object.preventExtensions + - Object.isFrozen + - Object.isSealed + - Object.isExtensible + - Object.getOwnPropertyDescriptor + - Object.getPrototypeOf + - Object.keys + - Object.getOwnPropertyNames + - Function.name + - Function.hasInstance + - Array.from + - Array.arrayOf + - Array.copyWithin + - Array.fill + - Array.find + - Array.findIndex + - Array.iterator + - String.fromCodePoint + - String.raw + - String.iterator + - String.codePointAt + - String.endsWith + - String.includes + - String.repeat + - String.startsWith + - String.trim + - String.anchor + - String.big + - String.blink + - String.bold + - String.fixed + - String.fontcolor + - String.fontsize + - String.italics + - String.link + - String.small + - String.strike + - String.sub + - String.sup + - RegExp + - Number + - Math + - Date + - async + - Symbol + - Map + - Set + - WeakMap + - WeakSet + - ArrayBuffer + - DataView + - Int8Array + - Uint8Array + - Uint8ClampedArray + - Int16Array + - Uint16Array + - Int32Array + - Uint32Array + - Float32Array + - Float64Array + - Reflect diff --git a/babel.config.json b/babel.config.json new file mode 100644 index 0000000000..1320b9a327 --- /dev/null +++ b/babel.config.json @@ -0,0 +1,3 @@ +{ + "presets": ["@babel/preset-env"] +} diff --git a/gulpfile.js b/gulpfile.js index 0eb5593541..4556e71bc8 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -62,7 +62,7 @@ function serve() { port: 8080 }); - let events = ['add', 'change']; + const events = ['add', 'change']; watch(options.javascript.query).on('all', function (event, path) { if (events.includes(event)) { @@ -105,7 +105,7 @@ function clean() { return del(['dist/']); } -let pipelineJavascript = lazypipe() +const pipelineJavascript = lazypipe() .pipe(function () { return mode.development(sourcemaps.init({ loadMaps: true })); }) @@ -140,7 +140,7 @@ function apploader(standalone) { .pipe(pipelineJavascript()) .pipe(dest('dist/')) .pipe(browserSync.stream()); - }; + } task.displayName = 'apploader'; @@ -183,6 +183,12 @@ function copy(query) { .pipe(browserSync.stream()); } +function copyIndex() { + return src(options.injectBundle.query, { base: './src/' }) + .pipe(dest('dist/')) + .pipe(browserSync.stream()); +} + function injectBundle() { return src(options.injectBundle.query, { base: './src/' }) .pipe(inject( @@ -193,9 +199,9 @@ function injectBundle() { } function build(standalone) { - return series(clean, parallel(javascript, apploader(standalone), webpack, css, html, images, copy), injectBundle); + return series(clean, parallel(javascript, apploader(standalone), webpack, css, html, images, copy)); } -exports.default = build(false); -exports.standalone = build(true); +exports.default = series(build(false), copyIndex); +exports.standalone = series(build(true), injectBundle); exports.serve = series(exports.standalone, serve); diff --git a/package.json b/package.json index c0ff158ec9..4e79ea3c39 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,10 @@ "cssnano": "^4.1.10", "del": "^5.1.0", "eslint": "^6.8.0", + "eslint-plugin-compat": "^3.5.1", + "eslint-plugin-eslint-comments": "^3.1.2", + "eslint-plugin-import": "^2.20.2", + "eslint-plugin-promise": "^4.2.1", "file-loader": "^6.0.0", "gulp": "^4.0.2", "gulp-babel": "^8.0.0", @@ -51,23 +55,29 @@ }, "dependencies": { "alameda": "^1.4.0", + "classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz", "core-js": "^3.6.4", + "date-fns": "^2.11.1", "document-register-element": "^1.14.3", + "fast-text-encoding": "^1.0.1", "flv.js": "^1.5.0", + "headroom.js": "^0.11.0", "hls.js": "^0.13.1", "howler": "^2.1.3", + "intersection-observer": "^0.7.0", "jellyfin-noto": "https://github.com/jellyfin/jellyfin-noto", "jquery": "^3.4.1", "jstree": "^3.3.7", - "libass-wasm": "https://github.com/jellyfin/JavascriptSubtitlesOctopus#4.0.0-jf", + "libass-wasm": "https://github.com/jellyfin/JavascriptSubtitlesOctopus#4.0.0-jf-cordova", "material-design-icons-iconfont": "^5.0.1", "native-promise-only": "^0.8.0-a", "page": "^1.11.5", "query-string": "^6.11.1", "resize-observer-polyfill": "^1.5.1", + "screenfull": "^5.0.2", "shaka-player": "^2.5.10", "sortablejs": "^1.10.2", - "swiper": "^5.3.1", + "swiper": "^5.3.7", "webcomponents.js": "^0.7.24", "whatwg-fetch": "^3.0.0" }, @@ -85,8 +95,13 @@ "src/components/filesystem.js", "src/components/input/keyboardnavigation.js", "src/components/sanatizefilename.js", + "src/components/scrollManager.js", + "src/scripts/settings/appSettings.js", + "src/scripts/settings/userSettings.js", "src/scripts/settings/webSettings.js", - "src/components/scrollManager.js" + "src/scripts/dfnshelper.js", + "src/scripts/imagehelper.js", + "src/scripts/inputManager.js" ], "plugins": [ "@babel/plugin-transform-modules-amd" @@ -110,7 +125,7 @@ "Firefox ESR" ], "scripts": { - "serve": "gulp serve", + "serve": "gulp serve --development", "prepare": "gulp --production", "build:development": "gulp --development", "build:production": "gulp --production", diff --git a/src/assets/css/librarybrowser.css b/src/assets/css/librarybrowser.css index e4b5bcf8d6..f7a1160469 100644 --- a/src/assets/css/librarybrowser.css +++ b/src/assets/css/librarybrowser.css @@ -21,7 +21,11 @@ } .libraryPage { - padding-top: 7em; + padding-top: 7em !important; +} + +.layout-mobile .libraryPage { + padding-top: 4em !important; } .itemDetailPage { @@ -128,10 +132,6 @@ margin-top: 0; } -.layout-mobile .pageTitleWithDefaultLogo { - background-image: url(../img/icon-transparent.png); -} - .headerLeft, .skinHeader { display: -webkit-box; @@ -246,6 +246,7 @@ } @media all and (min-width: 40em) { + .dashboardDocument .adminDrawerLogo, .dashboardDocument .mainDrawerButton { display: none !important; } @@ -313,7 +314,7 @@ } .dashboardDocument .mainDrawer-scrollContainer { - margin-top: 4.6em !important; + margin-top: 4.65em !important; } } @@ -516,6 +517,13 @@ .itemName { margin: 0.5em 0; + font-weight: 600; +} + +.nameContainer { + display: flex; + flex-direction: column; + flex-wrap: wrap; } .itemMiscInfo { @@ -533,7 +541,6 @@ .layout-mobile .itemName, .layout-mobile .itemMiscInfo, .layout-mobile .mainDetailButtons { - display: flex; align-items: center; justify-content: center; text-align: center; @@ -575,7 +582,6 @@ .infoText { white-space: nowrap; - overflow: hidden; text-overflow: ellipsis; text-align: left; } @@ -606,12 +612,11 @@ } .detailLogo { - width: 67.25vw; - height: 14.5vh; + width: 30vw; + height: 25vh; position: absolute; - top: 15vh; - right: 0; - -webkit-background-size: contain; + top: 10vh; + right: 20vw; background-size: contain; } @@ -619,26 +624,8 @@ display: none; } -@media all and (max-width: 87.5em) { - .detailLogo { - right: 5%; - } -} - -@media all and (max-width: 75em) { - .detailLogo { - right: 2%; - } -} - @media all and (max-width: 68.75em) { .detailLogo { - width: 14.91em; - height: 3.5em; - right: 5%; - bottom: 5%; - top: auto; - background-position: center right; display: none; } } diff --git a/src/assets/css/site.css b/src/assets/css/site.css index 67416663e7..e59b639f45 100644 --- a/src/assets/css/site.css +++ b/src/assets/css/site.css @@ -96,3 +96,16 @@ div[data-role=page] { margin-right: auto; width: 85%; } + +.headroom { + will-change: transform; + transition: transform 200ms linear; +} + +.headroom--pinned { + transform: translateY(0%); +} + +.headroom--unpinned { + transform: translateY(-100%); +} diff --git a/src/bundle.js b/src/bundle.js index ba5f74b163..8a829103fa 100644 --- a/src/bundle.js +++ b/src/bundle.js @@ -13,7 +13,7 @@ _define("document-register-element", function() { // fetch var fetch = require("whatwg-fetch"); _define("fetch", function() { - return fetch + return fetch; }); // query-string @@ -85,15 +85,15 @@ _define("webcomponents", function() { }); // libass-wasm -var libass_wasm = require("libass-wasm"); +var libassWasm = require("libass-wasm"); _define("JavascriptSubtitlesOctopus", function() { - return libass_wasm; + return libassWasm; }); // material-icons -var material_icons = require("material-design-icons-iconfont/dist/material-design-icons.css"); +var materialIcons = require("material-design-icons-iconfont/dist/material-design-icons.css"); _define("material-icons", function() { - return material_icons; + return materialIcons; }); // noto font @@ -112,3 +112,43 @@ var polyfill = require("@babel/polyfill/dist/polyfill"); _define("polyfill", function () { return polyfill; }); + +// domtokenlist-shim +var classlist = require("classlist.js"); +_define("classlist-polyfill", function () { + return classlist; +}); + +// Date-FNS +var dateFns = require("date-fns"); +_define("date-fns", function () { + return dateFns; +}); + +var dateFnsLocale = require("date-fns/locale"); +_define("date-fns/locale", function () { + return dateFnsLocale; +}); + +var fast_text_encoding = require("fast-text-encoding"); +_define("fast-text-encoding", function () { + return fast_text_encoding; +}); + +// intersection-observer +var intersection_observer = require("intersection-observer"); +_define("intersection-observer", function () { + return intersection_observer; +}); + +// screenfull +var screenfull = require("screenfull"); +_define("screenfull", function () { + return screenfull; +}); + +// headroom.js +var headroom = require("headroom.js/dist/headroom"); +_define("headroom", function () { + return headroom; +}); diff --git a/src/components/activitylog.js b/src/components/activitylog.js index 05971f01b8..62eda74d5f 100644 --- a/src/components/activitylog.js +++ b/src/components/activitylog.js @@ -1,4 +1,4 @@ -define(["events", "globalize", "dom", "datetime", "userSettings", "serverNotifications", "connectionManager", "emby-button", "listViewStyle"], function (events, globalize, dom, datetime, userSettings, serverNotifications, connectionManager) { +define(["events", "globalize", "dom", "date-fns", "dfnshelper", "userSettings", "serverNotifications", "connectionManager", "emby-button", "listViewStyle"], function (events, globalize, dom, datefns, dfnshelper, userSettings, serverNotifications, connectionManager) { "use strict"; function getEntryHtml(entry, apiClient) { @@ -16,7 +16,7 @@ define(["events", "globalize", "dom", "datetime", "userSettings", "serverNotific html += 'dvr" + }) + "');background-repeat:no-repeat;background-position:center center;background-size: cover;\">dvr"; } else { html += '' + icon + ''; } @@ -26,8 +26,7 @@ define(["events", "globalize", "dom", "datetime", "userSettings", "serverNotific html += entry.Name; html += ""; html += '