diff --git a/.devcontainer/Lidarr.code-workspace b/.devcontainer/Lidarr.code-workspace deleted file mode 100644 index a46158e44..000000000 --- a/.devcontainer/Lidarr.code-workspace +++ /dev/null @@ -1,13 +0,0 @@ -// This file is used to open the backend and frontend in the same workspace, which is necessary as -// the frontend has vscode settings that are distinct from the backend -{ - "folders": [ - { - "path": ".." - }, - { - "path": "../frontend" - } - ], - "settings": {} -} diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index d0fa03d5f..000000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,19 +0,0 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the -// README at: https://github.com/devcontainers/templates/tree/main/src/dotnet -{ - "name": "Lidarr", - "image": "mcr.microsoft.com/devcontainers/dotnet:1-6.0", - "features": { - "ghcr.io/devcontainers/features/node:1": { - "nodeGypDependencies": true, - "version": "20", - "nvmVersion": "latest" - } - }, - "forwardPorts": [8686], - "customizations": { - "vscode": { - "extensions": ["esbenp.prettier-vscode"] - } - } -} diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 491815370..31f001e52 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -60,7 +60,6 @@ body: - Master - Develop - Nightly - - Plugins (experimental) - Other (This issue will be closed) validations: required: true diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index f33a02cd1..000000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,12 +0,0 @@ -# To get started with Dependabot version updates, you'll need to specify which -# package ecosystems to update and where the package manifests are located. -# Please see the documentation for more information: -# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates -# https://containers.dev/guide/dependabot - -version: 2 -updates: - - package-ecosystem: "devcontainers" - directory: "/" - schedule: - interval: weekly diff --git a/.github/workflows/label-actions.yml b/.github/workflows/label-actions.yml index a6246a6b3..a7fc89446 100644 --- a/.github/workflows/label-actions.yml +++ b/.github/workflows/label-actions.yml @@ -12,6 +12,6 @@ jobs: action: runs-on: ubuntu-latest steps: - - uses: dessant/label-actions@v4 + - uses: dessant/label-actions@v3 with: process-only: 'issues' diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml index 1d50cb1f1..cf38066c5 100644 --- a/.github/workflows/lock.yml +++ b/.github/workflows/lock.yml @@ -9,7 +9,7 @@ jobs: lock: runs-on: ubuntu-latest steps: - - uses: dessant/lock-threads@v5 + - uses: dessant/lock-threads@v4 with: github-token: ${{ github.token }} issue-inactive-days: '90' diff --git a/.gitignore b/.gitignore index a5d6bb7c8..d2dc01467 100644 --- a/.gitignore +++ b/.gitignore @@ -121,13 +121,11 @@ _artifacts _rawPackage/ _dotTrace* _tests/ -_temp* *.Result.xml coverage*.xml coverage*.json setup/Output/ *.~is -.mono # VS outout folders bin @@ -140,6 +138,12 @@ project.fragment.lock.json artifacts/ **/Properties/launchSettings.json +#VS outout folders +bin +obj +output/* + + # macOS metadata files ._* .DS_Store @@ -158,12 +162,34 @@ Thumbs.db /tools/Addins/* packages.config.md5sum + +# Common IntelliJ Platform excludes + +# User specific +**/.idea/**/workspace.xml +**/.idea/**/tasks.xml +**/.idea/shelf/* +**/.idea/dictionaries +**/.idea/.idea.Radarr.Posix +**/.idea/.idea.Radarr.Windows + +# Sensitive or high-churn files +**/.idea/**/dataSources/ +**/.idea/**/dataSources.ids +**/.idea/**/dataSources.xml +**/.idea/**/dataSources.local.xml +**/.idea/**/sqlDataSources.xml +**/.idea/**/dynamic.xml + +# Rider +# Rider auto-generates .iml files, and contentModel.xml +**/.idea/**/*.iml +**/.idea/**/contentModel.xml +**/.idea/**/modules.xml + # ignore node_modules symlink node_modules node_modules.nosync # API doc generation .config/ - -# Ignore Jetbrains IntelliJ Workspace Directories -.idea/ diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index 7a36fefe1..000000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "recommendations": [ - "esbenp.prettier-vscode", - "ms-dotnettools.csdevkit", - "ms-vscode-remote.remote-containers" - ] -} diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 74b8d418b..000000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - // Use IntelliSense to find out which attributes exist for C# debugging - // Use hover for the description of the existing attributes - // For further information visit https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md - "name": "Run Lidarr", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build dotnet", - // If you have changed target frameworks, make sure to update the program path. - "program": "${workspaceFolder}/_output/net6.0/Lidarr", - "args": [], - "cwd": "${workspaceFolder}", - // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console - "console": "integratedTerminal", - "stopAtEntry": false - }, - { - "name": ".NET Core Attach", - "type": "coreclr", - "request": "attach" - } - ] -} diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index 4b3b00b89..000000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "version": "2.0.0", - "tasks": [ - { - "label": "build dotnet", - "command": "dotnet", - "type": "process", - "args": [ - "msbuild", - "-restore", - "${workspaceFolder}/src/Lidarr.sln", - "-p:GenerateFullPaths=true", - "-p:Configuration=Debug", - "-p:Platform=Posix", - "-consoleloggerparameters:NoSummary;ForceNoAlign" - ], - "problemMatcher": "$msCompile" - }, - { - "label": "publish", - "command": "dotnet", - "type": "process", - "args": [ - "publish", - "${workspaceFolder}/src/Lidarr.sln", - "-property:GenerateFullPaths=true", - "-consoleloggerparameters:NoSummary;ForceNoAlign" - ], - "problemMatcher": "$msCompile" - }, - { - "label": "watch", - "command": "dotnet", - "type": "process", - "args": [ - "watch", - "run", - "--project", - "${workspaceFolder}/src/Lidarr.sln" - ], - "problemMatcher": "$msCompile" - } - ] -} diff --git a/README.md b/README.md index 6e643760f..f5c8cdf84 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # Lidarr [![Build Status](https://dev.azure.com/Lidarr/Lidarr/_apis/build/status/lidarr.Lidarr?branchName=develop)](https://dev.azure.com/Lidarr/Lidarr/_build/latest?definitionId=1&branchName=develop) -[![Translation status](https://translate.servarr.com/widget/servarr/lidarr/svg-badge.svg)](https://translate.servarr.com/engage/servarr/?utm_source=widget) [![Docker Pulls](https://img.shields.io/docker/pulls/linuxserver/lidarr.svg)](https://wiki.servarr.com/lidarr/installation#docker) ![Github Downloads](https://img.shields.io/github/downloads/lidarr/lidarr/total.svg) [![Backers on Open Collective](https://opencollective.com/lidarr/backers/badge.svg)](#backers) @@ -9,9 +8,6 @@ Lidarr is a music collection manager for Usenet and BitTorrent users. It can monitor multiple RSS feeds for new tracks from your favorite artists and will grab, sort and rename them. It can also be configured to automatically upgrade the quality of files already downloaded when a better quality format becomes available. -> [!WARNING] -> NOTICE - The Lidarr Metadata Server is currently down impacting adding artists, etc. Please follow [GHI 5498](https://github.com/Lidarr/Lidarr/issues/5498) or see Discord for detaila. - ## Major Features Include: * Support for major platforms: Windows, Linux, macOS, Raspberry Pi, etc. diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 85d13499a..fbde1ee5e 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -9,18 +9,18 @@ variables: testsFolder: './_tests' yarnCacheFolder: $(Pipeline.Workspace)/.yarn nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages - majorVersion: '2.13.3' + majorVersion: '2.2.1' minorVersion: $[counter('minorVersion', 1076)] lidarrVersion: '$(majorVersion).$(minorVersion)' buildName: '$(Build.SourceBranchName).$(lidarrVersion)' sentryOrg: 'servarr' sentryUrl: 'https://sentry.servarr.com' - dotnetVersion: '6.0.427' + dotnetVersion: '6.0.417' nodeVersion: '20.X' innoVersion: '6.2.0' windowsImage: 'windows-2022' - linuxImage: 'ubuntu-22.04' - macImage: 'macOS-13' + linuxImage: 'ubuntu-20.04' + macImage: 'macOS-11' trigger: branches: @@ -166,10 +166,10 @@ stages: pool: vmImage: $(imageName) steps: - - task: UseNode@1 + - task: NodeTool@0 displayName: Set Node.js version inputs: - version: $(nodeVersion) + versionSpec: $(nodeVersion) - checkout: self submodules: true fetchDepth: 1 @@ -1093,10 +1093,10 @@ stages: pool: vmImage: $(imageName) steps: - - task: UseNode@1 + - task: NodeTool@0 displayName: Set Node.js version inputs: - version: $(nodeVersion) + versionSpec: $(nodeVersion) - checkout: self submodules: true fetchDepth: 1 @@ -1120,19 +1120,19 @@ stages: vmImage: ${{ variables.windowsImage }} steps: - checkout: self # Need history for Sonar analysis - - task: SonarCloudPrepare@3 + - task: SonarCloudPrepare@1 env: SONAR_SCANNER_OPTS: '' inputs: SonarCloud: 'SonarCloud' organization: 'lidarr' - scannerMode: 'cli' + scannerMode: 'CLI' configMode: 'manual' cliProjectKey: 'lidarr_Lidarr.UI' cliProjectName: 'LidarrUI' cliProjectVersion: '$(lidarrVersion)' cliSources: './frontend' - - task: SonarCloudAnalyze@3 + - task: SonarCloudAnalyze@1 - job: Api_Docs displayName: API Docs @@ -1208,12 +1208,12 @@ stages: submodules: true - powershell: Set-Service SCardSvr -StartupType Manual displayName: Enable Windows Test Service - - task: SonarCloudPrepare@3 + - task: SonarCloudPrepare@1 condition: eq(variables['System.PullRequest.IsFork'], 'False') inputs: SonarCloud: 'SonarCloud' organization: 'lidarr' - scannerMode: 'dotnet' + scannerMode: 'MSBuild' projectKey: 'lidarr_Lidarr' projectName: 'Lidarr' projectVersion: '$(lidarrVersion)' @@ -1226,16 +1226,21 @@ stages: ./build.sh --backend -f net6.0 -r win-x64 TEST_DIR=_tests/net6.0/win-x64/publish/ ./test.sh Windows Unit Coverage displayName: Coverage Unit Tests - - task: SonarCloudAnalyze@3 + - task: SonarCloudAnalyze@1 condition: eq(variables['System.PullRequest.IsFork'], 'False') displayName: Publish SonarCloud Results - - task: reportgenerator@5.3.11 + - task: reportgenerator@4 displayName: Generate Coverage Report inputs: reports: '$(Build.SourcesDirectory)/CoverageResults/**/coverage.opencover.xml' targetdir: '$(Build.SourcesDirectory)/CoverageResults/combined' reporttypes: 'HtmlInline_AzurePipelines;Cobertura;Badges' - publishCodeCoverageResults: true + - task: PublishCodeCoverageResults@1 + displayName: Publish Coverage Report + inputs: + codeCoverageTool: 'cobertura' + summaryFileLocation: './CoverageResults/combined/Cobertura.xml' + reportDirectory: './CoverageResults/combined/' - stage: Report_Out dependsOn: diff --git a/distribution/debian/install.sh b/distribution/debian/install.sh index b71eb20c9..6eff79eaa 100644 --- a/distribution/debian/install.sh +++ b/distribution/debian/install.sh @@ -59,7 +59,7 @@ app_guid=$(echo "$app_guid" | tr -d ' ') app_guid=${app_guid:-media} echo "This will install [${app^}] to [$bindir] and use [$datadir] for the AppData Directory" -echo "${app^} will run as the user [$app_uid] and group [$app_guid]. By continuing, you've confirmed that the selected user and group will have READ and WRITE access to your Media Library and Download Client Completed Download directories" +echo "${app^} will run as the user [$app_uid] and group [$app_guid]. By continuing, you've confirmed that that user and group will have READ and WRITE access to your Media Library and Download Client Completed Download directories" read -n 1 -r -s -p $'Press enter to continue or ctrl+c to exit...\n' < /dev/tty # Create User / Group as needed @@ -114,7 +114,7 @@ case "$ARCH" in esac echo "" echo "Removing previous tarballs" -# -f to Force so we fail if it doesn't exist +# -f to Force so we fail if it doesnt exist rm -f "${app^}".*.tar.gz echo "" echo "Downloading..." diff --git a/docs.sh b/docs.sh index a44dc90ce..9cbb02756 100644 --- a/docs.sh +++ b/docs.sh @@ -1,18 +1,13 @@ -#!/bin/bash -set -e - -FRAMEWORK="net6.0" PLATFORM=$1 -ARCHITECTURE="${2:-x64}" if [ "$PLATFORM" = "Windows" ]; then - RUNTIME="win-$ARCHITECTURE" + RUNTIME="win-x64" elif [ "$PLATFORM" = "Linux" ]; then - RUNTIME="linux-$ARCHITECTURE" + RUNTIME="linux-x64" elif [ "$PLATFORM" = "Mac" ]; then - RUNTIME="osx-$ARCHITECTURE" + RUNTIME="osx-x64" else - echo "Platform must be provided as first argument: Windows, Linux or Mac" + echo "Platform must be provided as first arguement: Windows, Linux or Mac" exit 1 fi @@ -26,21 +21,15 @@ slnFile=src/Lidarr.sln platform=Posix -if [ "$PLATFORM" = "Windows" ]; then - application=Lidarr.Console.dll -else - application=Lidarr.dll -fi - dotnet clean $slnFile -c Debug dotnet clean $slnFile -c Release dotnet msbuild -restore $slnFile -p:Configuration=Debug -p:Platform=$platform -p:RuntimeIdentifiers=$RUNTIME -t:PublishAllRids dotnet new tool-manifest -dotnet tool install --version 6.6.2 Swashbuckle.AspNetCore.Cli +dotnet tool install --version 6.5.0 Swashbuckle.AspNetCore.Cli -dotnet tool run swagger tofile --output ./src/Lidarr.Api.V1/openapi.json "$outputFolder/$FRAMEWORK/$RUNTIME/$application" v1 & +dotnet tool run swagger tofile --output ./src/Lidarr.Api.V1/openapi.json "$outputFolder/net6.0/$RUNTIME/lidarr.console.dll" v1 & sleep 45 diff --git a/frontend/.eslintrc.js b/frontend/.eslintrc.js index cc26a2633..603b20a48 100644 --- a/frontend/.eslintrc.js +++ b/frontend/.eslintrc.js @@ -28,8 +28,7 @@ module.exports = { globals: { expect: false, chai: false, - sinon: false, - JSX: true + sinon: false }, parserOptions: { diff --git a/frontend/.vscode/settings.json b/frontend/.vscode/settings.json index 8da95337f..edb88e0e7 100644 --- a/frontend/.vscode/settings.json +++ b/frontend/.vscode/settings.json @@ -9,7 +9,7 @@ "editor.formatOnSave": false, "editor.codeActionsOnSave": { - "source.fixAll": "explicit" + "source.fixAll": true }, "typescript.preferences.quoteStyle": "single", diff --git a/frontend/build/webpack.config.js b/frontend/build/webpack.config.js index d1873380e..e0ec27c27 100644 --- a/frontend/build/webpack.config.js +++ b/frontend/build/webpack.config.js @@ -26,7 +26,6 @@ module.exports = (env) => { const config = { mode: isProduction ? 'production' : 'development', devtool: isProduction ? 'source-map' : 'eval-source-map', - target: 'web', stats: { children: false @@ -68,7 +67,7 @@ module.exports = (env) => { output: { path: distFolder, publicPath: '/', - filename: isProduction ? '[name]-[contenthash].js' : '[name].js', + filename: '[name]-[contenthash].js', sourceMapFilename: '[file].map' }, @@ -93,7 +92,7 @@ module.exports = (env) => { new MiniCssExtractPlugin({ filename: 'Content/styles.css', - chunkFilename: isProduction ? 'Content/[id]-[chunkhash].css' : 'Content/[id].css' + chunkFilename: 'Content/[id]-[chunkhash].css' }), new HtmlWebpackPlugin({ @@ -135,12 +134,6 @@ module.exports = (env) => { { source: 'frontend/src/Content/robots.txt', destination: path.join(distFolder, 'Content/robots.txt') - }, - - // manifest.json and browserconfig.xml - { - source: 'frontend/src/Content/*.(json|xml)', - destination: path.join(distFolder, 'Content') } ] } @@ -188,7 +181,7 @@ module.exports = (env) => { loose: true, debug: false, useBuiltIns: 'entry', - corejs: '3.41' + corejs: 3 } ] ] @@ -209,7 +202,7 @@ module.exports = (env) => { options: { importLoaders: 1, modules: { - localIdentName: isProduction ? '[name]/[local]/[hash:base64:5]' : '[name]/[local]' + localIdentName: '[name]/[local]/[hash:base64:5]' } } }, diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js index 89db00f8c..f657adf28 100644 --- a/frontend/postcss.config.js +++ b/frontend/postcss.config.js @@ -16,7 +16,6 @@ const mixinsFiles = [ module.exports = { plugins: [ - 'autoprefixer', ['postcss-mixins', { mixinsFiles }], diff --git a/frontend/src/Activity/History/Details/HistoryDetails.js b/frontend/src/Activity/History/Details/HistoryDetails.js index 84aa3e0f2..b90a64f47 100644 --- a/frontend/src/Activity/History/Details/HistoryDetails.js +++ b/frontend/src/Activity/History/Details/HistoryDetails.js @@ -172,8 +172,7 @@ function HistoryDetails(props) { if (eventType === 'downloadFailed') { const { - message, - indexer + message } = data; return ( @@ -193,14 +192,6 @@ function HistoryDetails(props) { null } - { - indexer ? ( - - ) : null} - { message ? { diff --git a/frontend/src/AddArtist/ArtistMonitorNewItemsOptionsPopoverContent.js b/frontend/src/AddArtist/ArtistMonitorNewItemsOptionsPopoverContent.js index cda224e2f..5a837b1eb 100644 --- a/frontend/src/AddArtist/ArtistMonitorNewItemsOptionsPopoverContent.js +++ b/frontend/src/AddArtist/ArtistMonitorNewItemsOptionsPopoverContent.js @@ -8,17 +8,17 @@ function ArtistMonitorNewItemsOptionsPopoverContent() { ); diff --git a/frontend/src/Album/Album.ts b/frontend/src/Album/Album.ts index 86f1ed5fe..c9f10a87c 100644 --- a/frontend/src/Album/Album.ts +++ b/frontend/src/Album/Album.ts @@ -10,7 +10,6 @@ export interface Statistics { } interface Album extends ModelBase { - artistId: number; artist: Artist; foreignAlbumId: string; title: string; @@ -20,7 +19,6 @@ interface Album extends ModelBase { monitored: boolean; releaseDate: string; statistics: Statistics; - lastSearchTime?: string; isSaving?: boolean; } diff --git a/frontend/src/Album/AlbumTitleLink.js b/frontend/src/Album/AlbumTitleLink.js index e55fadfc0..8b4dfe212 100644 --- a/frontend/src/Album/AlbumTitleLink.js +++ b/frontend/src/Album/AlbumTitleLink.js @@ -4,11 +4,10 @@ import Link from 'Components/Link/Link'; function AlbumTitleLink({ foreignAlbumId, title, disambiguation }) { const link = `/album/${foreignAlbumId}`; - const albumTitle = `${title}${disambiguation ? ` (${disambiguation})` : ''}`; return ( - - {albumTitle} + + {title}{disambiguation ? ` (${disambiguation})` : ''} ); } diff --git a/frontend/src/Album/Delete/DeleteAlbumModalContent.js b/frontend/src/Album/Delete/DeleteAlbumModalContent.js index 28505ea75..e45985c97 100644 --- a/frontend/src/Album/Delete/DeleteAlbumModalContent.js +++ b/frontend/src/Album/Delete/DeleteAlbumModalContent.js @@ -53,7 +53,7 @@ class DeleteAlbumModalContent extends Component { render() { const { title, - statistics = {}, + statistics, onModalClose } = this.props; diff --git a/frontend/src/Album/Details/AlbumDetails.css b/frontend/src/Album/Details/AlbumDetails.css index a676ae574..d87920074 100644 --- a/frontend/src/Album/Details/AlbumDetails.css +++ b/frontend/src/Album/Details/AlbumDetails.css @@ -121,8 +121,6 @@ .releaseDate, .sizeOnDisk, -.albumType, -.secondaryTypes, .qualityProfileName, .links, .tags { diff --git a/frontend/src/Album/Details/AlbumDetails.css.d.ts b/frontend/src/Album/Details/AlbumDetails.css.d.ts index 1d14a0ccf..4c126c8b5 100644 --- a/frontend/src/Album/Details/AlbumDetails.css.d.ts +++ b/frontend/src/Album/Details/AlbumDetails.css.d.ts @@ -3,7 +3,6 @@ interface CssExports { 'albumNavigationButton': string; 'albumNavigationButtons': string; - 'albumType': string; 'alternateTitlesIconContainer': string; 'backdrop': string; 'backdropOverlay': string; @@ -21,7 +20,6 @@ interface CssExports { 'overview': string; 'qualityProfileName': string; 'releaseDate': string; - 'secondaryTypes': string; 'sizeOnDisk': string; 'tags': string; 'title': string; diff --git a/frontend/src/Album/Details/AlbumDetails.js b/frontend/src/Album/Details/AlbumDetails.js index fe007e168..783216a61 100644 --- a/frontend/src/Album/Details/AlbumDetails.js +++ b/frontend/src/Album/Details/AlbumDetails.js @@ -192,7 +192,6 @@ class AlbumDetails extends Component { duration, overview, albumType, - secondaryTypes, statistics = {}, monitored, releaseDate, @@ -205,7 +204,6 @@ class AlbumDetails extends Component { isFetching, isPopulated, albumsError, - tracksError, trackFilesError, hasTrackFiles, shortDateFormat, @@ -398,11 +396,10 @@ class AlbumDetails extends Component {
{ - duration ? + !!duration && {formatDuration(duration)} - : - null + } -
- - - {moment(releaseDate).format(shortDateFormat)} - -
+ + + + {moment(releaseDate).format(shortDateFormat)} + -
- - - {formatBytes(sizeOnDisk)} - -
+ + + + { + formatBytes(sizeOnDisk || 0) + } + } tooltip={ @@ -462,55 +459,32 @@ class AlbumDetails extends Component { className={styles.detailsLabel} size={sizes.LARGE} > -
- - - {monitored ? translate('Monitored') : translate('Unmonitored')} - -
+ + + + {monitored ? translate('Monitored') : translate('Unmonitored')} + { - albumType ? + !!albumType && : - null - } + - { - secondaryTypes.length ? - : - null + + {albumType} + + } -
- - - {translate('Links')} - -
+ + + + {translate('Links')} + } tooltip={ @@ -553,9 +526,8 @@ class AlbumDetails extends Component {
{ - !isPopulated && !albumsError && !tracksError && !trackFilesError ? - : - null + !isPopulated && !albumsError && !trackFilesError && + } { @@ -566,14 +538,6 @@ class AlbumDetails extends Component { null } - { - !isFetching && tracksError ? - - {translate('TracksLoadError')} - : - null - } - { !isFetching && trackFilesError ? @@ -602,14 +566,6 @@ class AlbumDetails extends Component {
} - { - isPopulated && !media.length ? - - {translate('NoMediumInformation')} - : - null - } -
{ if (track.trackFileId) { + trackCount++; trackFileCount++; + } else { + trackCount++; } totalTrackCount++; diff --git a/frontend/src/Album/Details/TrackRow.css b/frontend/src/Album/Details/TrackRow.css index 912c00101..11ebb64fa 100644 --- a/frontend/src/Album/Details/TrackRow.css +++ b/frontend/src/Album/Details/TrackRow.css @@ -35,9 +35,3 @@ width: 55px; } - -.indexerFlags { - composes: cell from '~Components/Table/Cells/TableRowCell.css'; - - width: 50px; -} diff --git a/frontend/src/Album/Details/TrackRow.css.d.ts b/frontend/src/Album/Details/TrackRow.css.d.ts index 79bbdaf43..c5644a2d4 100644 --- a/frontend/src/Album/Details/TrackRow.css.d.ts +++ b/frontend/src/Album/Details/TrackRow.css.d.ts @@ -4,7 +4,6 @@ interface CssExports { 'audio': string; 'customFormatScore': string; 'duration': string; - 'indexerFlags': string; 'monitored': string; 'size': string; 'status': string; diff --git a/frontend/src/Album/Details/TrackRow.js b/frontend/src/Album/Details/TrackRow.js index db128d493..5f60df882 100644 --- a/frontend/src/Album/Details/TrackRow.js +++ b/frontend/src/Album/Details/TrackRow.js @@ -2,19 +2,15 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import AlbumFormats from 'Album/AlbumFormats'; import EpisodeStatusConnector from 'Album/EpisodeStatusConnector'; -import IndexerFlags from 'Album/IndexerFlags'; -import Icon from 'Components/Icon'; import TableRowCell from 'Components/Table/Cells/TableRowCell'; import TableRow from 'Components/Table/TableRow'; -import Popover from 'Components/Tooltip/Popover'; import Tooltip from 'Components/Tooltip/Tooltip'; -import { icons, kinds, tooltipPositions } from 'Helpers/Props'; +import { tooltipPositions } from 'Helpers/Props'; import MediaInfoConnector from 'TrackFile/MediaInfoConnector'; import * as mediaInfoTypes from 'TrackFile/mediaInfoTypes'; import formatTimeSpan from 'Utilities/Date/formatTimeSpan'; import formatBytes from 'Utilities/Number/formatBytes'; import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore'; -import translate from 'Utilities/String/translate'; import TrackActionsCell from './TrackActionsCell'; import styles from './TrackRow.css'; @@ -36,7 +32,6 @@ class TrackRow extends Component { trackFileSize, customFormats, customFormatScore, - indexerFlags, columns, deleteTrackFile } = this.props; @@ -146,30 +141,12 @@ class TrackRow extends Component { customFormats.length )} tooltip={} - position={tooltipPositions.LEFT} + position={tooltipPositions.BOTTOM} /> ); } - if (name === 'indexerFlags') { - return ( - - {indexerFlags ? ( - } - title={translate('IndexerFlags')} - body={} - position={tooltipPositions.LEFT} - /> - ) : null} - - ); - } - if (name === 'size') { return ( (indexerFlags & item.id) === item.id - ); - - return flags.length ? ( -
    - {flags.map((flag, index) => { - return
  • {flag.name}
  • ; - })} -
- ) : null; -} - -export default IndexerFlags; diff --git a/frontend/src/Album/Search/AlbumInteractiveSearchModal.js b/frontend/src/Album/Search/AlbumInteractiveSearchModal.js index 6ce488615..5049658a0 100644 --- a/frontend/src/Album/Search/AlbumInteractiveSearchModal.js +++ b/frontend/src/Album/Search/AlbumInteractiveSearchModal.js @@ -15,7 +15,7 @@ function AlbumInteractiveSearchModal(props) { return ( diff --git a/frontend/src/Album/Search/AlbumInteractiveSearchModalContent.js b/frontend/src/Album/Search/AlbumInteractiveSearchModalContent.js index 370f67ab1..97261ee35 100644 --- a/frontend/src/Album/Search/AlbumInteractiveSearchModalContent.js +++ b/frontend/src/Album/Search/AlbumInteractiveSearchModalContent.js @@ -7,7 +7,6 @@ import ModalFooter from 'Components/Modal/ModalFooter'; import ModalHeader from 'Components/Modal/ModalHeader'; import { scrollDirections } from 'Helpers/Props'; import InteractiveSearchConnector from 'InteractiveSearch/InteractiveSearchConnector'; -import translate from 'Utilities/String/translate'; function AlbumInteractiveSearchModalContent(props) { const { @@ -19,10 +18,7 @@ function AlbumInteractiveSearchModalContent(props) { return ( - {albumTitle === undefined ? - translate('InteractiveSearchModalHeader') : - translate('InteractiveSearchModalHeaderTitle', { title: albumTitle }) - } + Interactive Search {albumId != null && `- ${albumTitle}`} @@ -36,7 +32,7 @@ function AlbumInteractiveSearchModalContent(props) { diff --git a/frontend/src/App/App.js b/frontend/src/App/App.js index 9e8d508ac..3871b14e9 100644 --- a/frontend/src/App/App.js +++ b/frontend/src/App/App.js @@ -12,10 +12,11 @@ function App({ store, history }) { - - - - + + + + + diff --git a/frontend/src/App/AppRoutes.js b/frontend/src/App/AppRoutes.js index c1004d36d..0af990f43 100644 --- a/frontend/src/App/AppRoutes.js +++ b/frontend/src/App/AppRoutes.js @@ -11,7 +11,7 @@ import CalendarPageConnector from 'Calendar/CalendarPageConnector'; import NotFound from 'Components/NotFound'; import Switch from 'Components/Router/Switch'; import AddNewItemConnector from 'Search/AddNewItemConnector'; -import CustomFormatSettingsPage from 'Settings/CustomFormats/CustomFormatSettingsPage'; +import CustomFormatSettingsConnector from 'Settings/CustomFormats/CustomFormatSettingsConnector'; import DownloadClientSettingsConnector from 'Settings/DownloadClients/DownloadClientSettingsConnector'; import GeneralSettingsConnector from 'Settings/General/GeneralSettingsConnector'; import ImportListSettingsConnector from 'Settings/ImportLists/ImportListSettingsConnector'; @@ -29,7 +29,7 @@ import LogsTableConnector from 'System/Events/LogsTableConnector'; import Logs from 'System/Logs/Logs'; import Status from 'System/Status/Status'; import Tasks from 'System/Tasks/Tasks'; -import Updates from 'System/Updates/Updates'; +import UpdatesConnector from 'System/Updates/UpdatesConnector'; import UnmappedFilesTableConnector from 'UnmappedFiles/UnmappedFilesTableConnector'; import getPathWithUrlBase from 'Utilities/getPathWithUrlBase'; import CutoffUnmetConnector from 'Wanted/CutoffUnmet/CutoffUnmetConnector'; @@ -184,7 +184,7 @@ function AppRoutes(props) { state.settings.ui.item.theme || window.Lidarr.theme, + ( + theme + ) => { + return { + theme + }; + } + ); +} + +function ApplyTheme({ theme, children }) { + // Update the CSS Variables + + const updateCSSVariables = useCallback(() => { + const arrayOfVariableKeys = Object.keys(themes[theme]); + const arrayOfVariableValues = Object.values(themes[theme]); + + // Loop through each array key and set the CSS Variables + arrayOfVariableKeys.forEach((cssVariableKey, index) => { + // Based on our snippet from MDN + document.documentElement.style.setProperty( + `--${cssVariableKey}`, + arrayOfVariableValues[index] + ); + }); + }, [theme]); + + // On Component Mount and Component Update + useEffect(() => { + updateCSSVariables(theme); + }, [updateCSSVariables, theme]); + + return {children}; +} + +ApplyTheme.propTypes = { + theme: PropTypes.string.isRequired, + children: PropTypes.object.isRequired +}; + +export default connect(createMapStateToProps)(ApplyTheme); diff --git a/frontend/src/App/ApplyTheme.tsx b/frontend/src/App/ApplyTheme.tsx deleted file mode 100644 index e04dda8c4..000000000 --- a/frontend/src/App/ApplyTheme.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React, { Fragment, ReactNode, useCallback, useEffect } from 'react'; -import { useSelector } from 'react-redux'; -import { createSelector } from 'reselect'; -import themes from 'Styles/Themes'; -import AppState from './State/AppState'; - -interface ApplyThemeProps { - children: ReactNode; -} - -function createThemeSelector() { - return createSelector( - (state: AppState) => state.settings.ui.item.theme || window.Lidarr.theme, - (theme) => { - return theme; - } - ); -} - -function ApplyTheme({ children }: ApplyThemeProps) { - const theme = useSelector(createThemeSelector()); - - const updateCSSVariables = useCallback(() => { - Object.entries(themes[theme]).forEach(([key, value]) => { - document.documentElement.style.setProperty(`--${key}`, value); - }); - }, [theme]); - - // On Component Mount and Component Update - useEffect(() => { - updateCSSVariables(); - }, [updateCSSVariables, theme]); - - return {children}; -} - -export default ApplyTheme; diff --git a/frontend/src/App/State/AppState.ts b/frontend/src/App/State/AppState.ts index cb8da5987..a8850b16f 100644 --- a/frontend/src/App/State/AppState.ts +++ b/frontend/src/App/State/AppState.ts @@ -1,12 +1,9 @@ -import ParseAppState from 'App/State/ParseAppState'; import AlbumAppState from './AlbumAppState'; import ArtistAppState, { ArtistIndexAppState } from './ArtistAppState'; import CalendarAppState from './CalendarAppState'; -import CommandAppState from './CommandAppState'; import HistoryAppState from './HistoryAppState'; import QueueAppState from './QueueAppState'; import SettingsAppState from './SettingsAppState'; -import SystemAppState from './SystemAppState'; import TagsAppState from './TagsAppState'; import TrackFilesAppState from './TrackFilesAppState'; import TracksAppState from './TracksAppState'; @@ -44,7 +41,6 @@ export interface CustomFilter { } export interface AppSectionState { - version: string; dimensions: { isSmallScreen: boolean; width: number; @@ -58,15 +54,12 @@ interface AppState { artist: ArtistAppState; artistIndex: ArtistIndexAppState; calendar: CalendarAppState; - commands: CommandAppState; history: HistoryAppState; - parse: ParseAppState; queue: QueueAppState; settings: SettingsAppState; tags: TagsAppState; trackFiles: TrackFilesAppState; tracksSelection: TracksAppState; - system: SystemAppState; } export default AppState; diff --git a/frontend/src/App/State/CommandAppState.ts b/frontend/src/App/State/CommandAppState.ts deleted file mode 100644 index 1bde37371..000000000 --- a/frontend/src/App/State/CommandAppState.ts +++ /dev/null @@ -1,6 +0,0 @@ -import AppSectionState from 'App/State/AppSectionState'; -import Command from 'Commands/Command'; - -export type CommandAppState = AppSectionState; - -export default CommandAppState; diff --git a/frontend/src/App/State/ParseAppState.ts b/frontend/src/App/State/ParseAppState.ts deleted file mode 100644 index 827d5b1a7..000000000 --- a/frontend/src/App/State/ParseAppState.ts +++ /dev/null @@ -1,35 +0,0 @@ -import Album from 'Album/Album'; -import ModelBase from 'App/ModelBase'; -import { AppSectionItemState } from 'App/State/AppSectionState'; -import Artist from 'Artist/Artist'; -import { QualityModel } from 'Quality/Quality'; -import CustomFormat from 'typings/CustomFormat'; - -export interface ArtistTitleInfo { - title: string; -} - -export interface ParsedAlbumInfo { - albumTitle: string; - artistName: string; - artistTitleInfo: ArtistTitleInfo; - discography: boolean; - quality: QualityModel; - releaseGroup?: string; - releaseHash: string; - releaseTitle: string; - releaseTokens: string; -} - -export interface ParseModel extends ModelBase { - title: string; - parsedAlbumInfo: ParsedAlbumInfo; - artist?: Artist; - albums: Album[]; - customFormats?: CustomFormat[]; - customFormatScore?: number; -} - -type ParseAppState = AppSectionItemState; - -export default ParseAppState; diff --git a/frontend/src/App/State/SettingsAppState.ts b/frontend/src/App/State/SettingsAppState.ts index b387e13fd..8511b5e2b 100644 --- a/frontend/src/App/State/SettingsAppState.ts +++ b/frontend/src/App/State/SettingsAppState.ts @@ -1,28 +1,22 @@ import AppSectionState, { AppSectionDeleteState, - AppSectionItemState, AppSectionSaveState, AppSectionSchemaState, } from 'App/State/AppSectionState'; -import CustomFormat from 'typings/CustomFormat'; import DownloadClient from 'typings/DownloadClient'; import ImportList from 'typings/ImportList'; import Indexer from 'typings/Indexer'; -import IndexerFlag from 'typings/IndexerFlag'; import MetadataProfile from 'typings/MetadataProfile'; import Notification from 'typings/Notification'; import QualityProfile from 'typings/QualityProfile'; import RootFolder from 'typings/RootFolder'; -import General from 'typings/Settings/General'; -import UiSettings from 'typings/Settings/UiSettings'; +import { UiSettings } from 'typings/UiSettings'; export interface DownloadClientAppState extends AppSectionState, AppSectionDeleteState, AppSectionSaveState {} -export type GeneralAppState = AppSectionItemState; - export interface ImportListAppState extends AppSectionState, AppSectionDeleteState, @@ -45,32 +39,22 @@ export interface MetadataProfilesAppState extends AppSectionState, AppSectionSchemaState {} -export interface CustomFormatAppState - extends AppSectionState, - AppSectionDeleteState, - AppSectionSaveState {} - export interface RootFolderAppState extends AppSectionState, AppSectionDeleteState, AppSectionSaveState {} -export type IndexerFlagSettingsAppState = AppSectionState; -export type UiSettingsAppState = AppSectionItemState; +export type UiSettingsAppState = AppSectionState; interface SettingsAppState { - advancedSettings: boolean; - customFormats: CustomFormatAppState; downloadClients: DownloadClientAppState; - general: GeneralAppState; importLists: ImportListAppState; - indexerFlags: IndexerFlagSettingsAppState; indexers: IndexerAppState; metadataProfiles: MetadataProfilesAppState; notifications: NotificationAppState; qualityProfiles: QualityProfilesAppState; rootFolders: RootFolderAppState; - ui: UiSettingsAppState; + uiSettings: UiSettingsAppState; } export default SettingsAppState; diff --git a/frontend/src/App/State/SystemAppState.ts b/frontend/src/App/State/SystemAppState.ts deleted file mode 100644 index 3c150fcfb..000000000 --- a/frontend/src/App/State/SystemAppState.ts +++ /dev/null @@ -1,13 +0,0 @@ -import SystemStatus from 'typings/SystemStatus'; -import Update from 'typings/Update'; -import AppSectionState, { AppSectionItemState } from './AppSectionState'; - -export type SystemStatusAppState = AppSectionItemState; -export type UpdateAppState = AppSectionState; - -interface SystemAppState { - updates: UpdateAppState; - status: SystemStatusAppState; -} - -export default SystemAppState; diff --git a/frontend/src/App/State/TagsAppState.ts b/frontend/src/App/State/TagsAppState.ts index edaf3a158..d1f1d5a2f 100644 --- a/frontend/src/App/State/TagsAppState.ts +++ b/frontend/src/App/State/TagsAppState.ts @@ -1,32 +1,12 @@ import ModelBase from 'App/ModelBase'; import AppSectionState, { AppSectionDeleteState, - AppSectionSaveState, } from 'App/State/AppSectionState'; export interface Tag extends ModelBase { label: string; } -export interface TagDetail extends ModelBase { - label: string; - autoTagIds: number[]; - delayProfileIds: number[]; - downloadClientIds: []; - importListIds: number[]; - indexerIds: number[]; - notificationIds: number[]; - restrictionIds: number[]; - artistIds: number[]; -} - -export interface TagDetailAppState - extends AppSectionState, - AppSectionDeleteState, - AppSectionSaveState {} - -interface TagsAppState extends AppSectionState, AppSectionDeleteState { - details: TagDetailAppState; -} +interface TagsAppState extends AppSectionState, AppSectionDeleteState {} export default TagsAppState; diff --git a/frontend/src/Artist/Artist.ts b/frontend/src/Artist/Artist.ts index 813dbea08..d89e32f34 100644 --- a/frontend/src/Artist/Artist.ts +++ b/frontend/src/Artist/Artist.ts @@ -23,6 +23,7 @@ export interface Ratings { interface Artist extends ModelBase { added: string; + artistMetadataId: string; foreignArtistId: string; cleanName: string; ended: boolean; diff --git a/frontend/src/Artist/Delete/DeleteArtistModalContent.js b/frontend/src/Artist/Delete/DeleteArtistModalContent.js index ac1e2b041..0542f718b 100644 --- a/frontend/src/Artist/Delete/DeleteArtistModalContent.js +++ b/frontend/src/Artist/Delete/DeleteArtistModalContent.js @@ -135,14 +135,14 @@ class DeleteArtistModalContent extends Component { @@ -161,7 +161,9 @@ DeleteArtistModalContent.propTypes = { }; DeleteArtistModalContent.defaultProps = { - statistics: {} + statistics: { + trackFileCount: 0 + } }; export default DeleteArtistModalContent; diff --git a/frontend/src/Artist/Details/AlbumGroupInfo.js b/frontend/src/Artist/Details/AlbumGroupInfo.js index 139cd7765..0fb62d4a3 100644 --- a/frontend/src/Artist/Details/AlbumGroupInfo.js +++ b/frontend/src/Artist/Details/AlbumGroupInfo.js @@ -10,7 +10,6 @@ function AlbumGroupInfo(props) { const { totalAlbumCount, monitoredAlbumCount, - albumFileCount, trackFileCount, sizeOnDisk } = props; @@ -31,13 +30,6 @@ function AlbumGroupInfo(props) { data={monitoredAlbumCount} /> - - - {secondaryTypes.join(', ')} + { + secondaryTypes + }
); } @@ -158,7 +160,7 @@ class AlbumRow extends Component { return ( { - totalTrackCount + statistics.totalTrackCount } ); diff --git a/frontend/src/Artist/Details/ArtistDetails.js b/frontend/src/Artist/Details/ArtistDetails.js index 1bfa767c3..c6fb445b7 100644 --- a/frontend/src/Artist/Details/ArtistDetails.js +++ b/frontend/src/Artist/Details/ArtistDetails.js @@ -192,7 +192,7 @@ class ArtistDetails extends Component { artistName, ratings, path, - statistics = {}, + statistics, qualityProfileId, monitored, genres, diff --git a/frontend/src/Artist/Details/ArtistDetailsSeason.js b/frontend/src/Artist/Details/ArtistDetailsSeason.js index 004613e30..37c85aa66 100644 --- a/frontend/src/Artist/Details/ArtistDetailsSeason.js +++ b/frontend/src/Artist/Details/ArtistDetailsSeason.js @@ -22,43 +22,32 @@ import styles from './ArtistDetailsSeason.css'; function getAlbumStatistics(albums) { let albumCount = 0; - let albumFileCount = 0; let trackFileCount = 0; let totalAlbumCount = 0; let monitoredAlbumCount = 0; let hasMonitoredAlbums = false; let sizeOnDisk = 0; - albums.forEach(({ monitored, releaseDate, statistics = {} }) => { - const { - trackFileCount: albumTrackFileCount = 0, - totalTrackCount: albumTotalTrackCount = 0, - sizeOnDisk: albumSizeOnDisk = 0 - } = statistics; + albums.forEach((album) => { + if (album.statistics) { + sizeOnDisk = sizeOnDisk + album.statistics.sizeOnDisk; + trackFileCount = trackFileCount + album.statistics.trackFileCount; - const hasFiles = albumTrackFileCount > 0 && albumTrackFileCount === albumTotalTrackCount; - - if (hasFiles || (monitored && isBefore(releaseDate))) { - albumCount++; + if (album.statistics.trackFileCount === album.statistics.totalTrackCount || (album.monitored && isBefore(album.airDateUtc))) { + albumCount++; + } } - if (hasFiles) { - albumFileCount++; - } - - if (monitored) { + if (album.monitored) { monitoredAlbumCount++; hasMonitoredAlbums = true; } totalAlbumCount++; - trackFileCount = trackFileCount + albumTrackFileCount; - sizeOnDisk = sizeOnDisk + albumSizeOnDisk; }); return { albumCount, - albumFileCount, totalAlbumCount, trackFileCount, monitoredAlbumCount, @@ -67,8 +56,8 @@ function getAlbumStatistics(albums) { }; } -function getAlbumCountKind(monitored, albumCount, albumFileCount) { - if (albumCount === albumFileCount && albumFileCount > 0) { +function getAlbumCountKind(monitored, albumCount, monitoredAlbumCount) { + if (albumCount === monitoredAlbumCount && monitoredAlbumCount > 0) { return kinds.SUCCESS; } @@ -203,7 +192,6 @@ class ArtistDetailsSeason extends Component { const { albumCount, - albumFileCount, totalAlbumCount, trackFileCount, monitoredAlbumCount, @@ -238,9 +226,9 @@ class ArtistDetailsSeason extends Component { anchor={ } title={translate('GroupInformation')} @@ -249,7 +237,6 @@ class ArtistDetailsSeason extends Component { diff --git a/frontend/src/Artist/Details/ArtistTagsConnector.js b/frontend/src/Artist/Details/ArtistTagsConnector.js index 1d24a5755..33ced5f0d 100644 --- a/frontend/src/Artist/Details/ArtistTagsConnector.js +++ b/frontend/src/Artist/Details/ArtistTagsConnector.js @@ -2,7 +2,6 @@ import { connect } from 'react-redux'; import { createSelector } from 'reselect'; import createArtistSelector from 'Store/Selectors/createArtistSelector'; import createTagsSelector from 'Store/Selectors/createTagsSelector'; -import sortByProp from 'Utilities/Array/sortByProp'; import ArtistTags from './ArtistTags'; function createMapStateToProps() { @@ -13,8 +12,8 @@ function createMapStateToProps() { const tags = artist.tags .map((tagId) => tagList.find((tag) => tag.id === tagId)) .filter((tag) => !!tag) - .sort(sortByProp('label')) - .map((tag) => tag.label); + .map((tag) => tag.label) + .sort((a, b) => a.localeCompare(b)); return { tags diff --git a/frontend/src/Artist/Edit/EditArtistModalContent.js b/frontend/src/Artist/Edit/EditArtistModalContent.js index bca6e3ea6..82a390d84 100644 --- a/frontend/src/Artist/Edit/EditArtistModalContent.js +++ b/frontend/src/Artist/Edit/EditArtistModalContent.js @@ -15,7 +15,7 @@ import ModalContent from 'Components/Modal/ModalContent'; import ModalFooter from 'Components/Modal/ModalFooter'; import ModalHeader from 'Components/Modal/ModalHeader'; import Popover from 'Components/Tooltip/Popover'; -import { icons, inputTypes, kinds, sizes, tooltipPositions } from 'Helpers/Props'; +import { icons, inputTypes, kinds, tooltipPositions } from 'Helpers/Props'; import translate from 'Utilities/String/translate'; import styles from './EditArtistModalContent.css'; @@ -93,7 +93,7 @@ class EditArtistModalContent extends Component {
- + {translate('Monitored')} @@ -107,10 +107,9 @@ class EditArtistModalContent extends Component { /> - + {translate('MonitorNewItems')} - - + {translate('QualityProfile')} @@ -147,10 +146,10 @@ class EditArtistModalContent extends Component { { - showMetadataProfile ? - + showMetadataProfile && + - {translate('MetadataProfile')} + Metadata Profile - : - null + } - + {translate('Path')} @@ -191,7 +189,7 @@ class EditArtistModalContent extends Component { /> - + {translate('Tags')} @@ -211,7 +209,7 @@ class EditArtistModalContent extends Component { kind={kinds.DANGER} onPress={onDeleteArtistPress} > - {translate('Delete')} + Delete
diff --git a/frontend/src/Components/Page/Header/PageHeaderActionsMenu.js b/frontend/src/Components/Page/Header/PageHeaderActionsMenu.js new file mode 100644 index 000000000..c14617b29 --- /dev/null +++ b/frontend/src/Components/Page/Header/PageHeaderActionsMenu.js @@ -0,0 +1,90 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import Icon from 'Components/Icon'; +import Menu from 'Components/Menu/Menu'; +import MenuButton from 'Components/Menu/MenuButton'; +import MenuContent from 'Components/Menu/MenuContent'; +import MenuItem from 'Components/Menu/MenuItem'; +import MenuItemSeparator from 'Components/Menu/MenuItemSeparator'; +import { align, icons, kinds } from 'Helpers/Props'; +import translate from 'Utilities/String/translate'; +import styles from './PageHeaderActionsMenu.css'; + +function PageHeaderActionsMenu(props) { + const { + formsAuth, + onKeyboardShortcutsPress, + onRestartPress, + onShutdownPress + } = props; + + return ( +
+ + + + + + + + + {translate('KeyboardShortcuts')} + + + + + + + {translate('Restart')} + + + + + Shutdown + + + { + formsAuth && +
+ } + + { + formsAuth && + + + {translate('Logout')} + + } + +
+
+ ); +} + +PageHeaderActionsMenu.propTypes = { + formsAuth: PropTypes.bool.isRequired, + onKeyboardShortcutsPress: PropTypes.func.isRequired, + onRestartPress: PropTypes.func.isRequired, + onShutdownPress: PropTypes.func.isRequired +}; + +export default PageHeaderActionsMenu; diff --git a/frontend/src/Components/Page/Header/PageHeaderActionsMenu.tsx b/frontend/src/Components/Page/Header/PageHeaderActionsMenu.tsx deleted file mode 100644 index 7a0c35c1c..000000000 --- a/frontend/src/Components/Page/Header/PageHeaderActionsMenu.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import React, { useCallback } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import AppState from 'App/State/AppState'; -import Icon from 'Components/Icon'; -import Menu from 'Components/Menu/Menu'; -import MenuButton from 'Components/Menu/MenuButton'; -import MenuContent from 'Components/Menu/MenuContent'; -import MenuItem from 'Components/Menu/MenuItem'; -import MenuItemSeparator from 'Components/Menu/MenuItemSeparator'; -import { align, icons, kinds } from 'Helpers/Props'; -import { restart, shutdown } from 'Store/Actions/systemActions'; -import translate from 'Utilities/String/translate'; -import styles from './PageHeaderActionsMenu.css'; - -interface PageHeaderActionsMenuProps { - onKeyboardShortcutsPress(): void; -} - -function PageHeaderActionsMenu(props: PageHeaderActionsMenuProps) { - const { onKeyboardShortcutsPress } = props; - - const dispatch = useDispatch(); - - const { authentication, isDocker } = useSelector( - (state: AppState) => state.system.status.item - ); - - const formsAuth = authentication === 'forms'; - - const handleRestartPress = useCallback(() => { - dispatch(restart()); - }, [dispatch]); - - const handleShutdownPress = useCallback(() => { - dispatch(shutdown()); - }, [dispatch]); - - return ( -
- - - - - - - - - {translate('KeyboardShortcuts')} - - - {isDocker ? null : ( - <> - - - - - {translate('Restart')} - - - - - {translate('Shutdown')} - - - )} - - {formsAuth ? ( - <> - - - - - {translate('Logout')} - - - ) : null} - - -
- ); -} - -export default PageHeaderActionsMenu; diff --git a/frontend/src/Components/Page/Header/PageHeaderActionsMenuConnector.js b/frontend/src/Components/Page/Header/PageHeaderActionsMenuConnector.js new file mode 100644 index 000000000..3aba95065 --- /dev/null +++ b/frontend/src/Components/Page/Header/PageHeaderActionsMenuConnector.js @@ -0,0 +1,56 @@ +import PropTypes from 'prop-types'; +import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import { createSelector } from 'reselect'; +import { restart, shutdown } from 'Store/Actions/systemActions'; +import PageHeaderActionsMenu from './PageHeaderActionsMenu'; + +function createMapStateToProps() { + return createSelector( + (state) => state.system.status, + (status) => { + return { + formsAuth: status.item.authentication === 'forms' + }; + } + ); +} + +const mapDispatchToProps = { + restart, + shutdown +}; + +class PageHeaderActionsMenuConnector extends Component { + + // + // Listeners + + onRestartPress = () => { + this.props.restart(); + }; + + onShutdownPress = () => { + this.props.shutdown(); + }; + + // + // Render + + render() { + return ( + + ); + } +} + +PageHeaderActionsMenuConnector.propTypes = { + restart: PropTypes.func.isRequired, + shutdown: PropTypes.func.isRequired +}; + +export default connect(createMapStateToProps, mapDispatchToProps)(PageHeaderActionsMenuConnector); diff --git a/frontend/src/Components/Page/PageConnector.js b/frontend/src/Components/Page/PageConnector.js index c84099a5e..070be9b42 100644 --- a/frontend/src/Components/Page/PageConnector.js +++ b/frontend/src/Components/Page/PageConnector.js @@ -6,14 +6,7 @@ import { createSelector } from 'reselect'; import { fetchTranslations, saveDimensions, setIsSidebarVisible } from 'Store/Actions/appActions'; import { fetchArtist } from 'Store/Actions/artistActions'; import { fetchCustomFilters } from 'Store/Actions/customFilterActions'; -import { - fetchImportLists, - fetchIndexerFlags, - fetchLanguages, - fetchMetadataProfiles, - fetchQualityProfiles, - fetchUISettings -} from 'Store/Actions/settingsActions'; +import { fetchImportLists, fetchLanguages, fetchMetadataProfiles, fetchQualityProfiles, fetchUISettings } from 'Store/Actions/settingsActions'; import { fetchStatus } from 'Store/Actions/systemActions'; import { fetchTags } from 'Store/Actions/tagActions'; import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector'; @@ -51,7 +44,6 @@ const selectAppProps = createSelector( ); const selectIsPopulated = createSelector( - (state) => state.artist.isPopulated, (state) => state.customFilters.isPopulated, (state) => state.tags.isPopulated, (state) => state.settings.ui.isPopulated, @@ -59,11 +51,9 @@ const selectIsPopulated = createSelector( (state) => state.settings.qualityProfiles.isPopulated, (state) => state.settings.metadataProfiles.isPopulated, (state) => state.settings.importLists.isPopulated, - (state) => state.settings.indexerFlags.isPopulated, (state) => state.system.status.isPopulated, (state) => state.app.translations.isPopulated, ( - artistsIsPopulated, customFiltersIsPopulated, tagsIsPopulated, uiSettingsIsPopulated, @@ -71,12 +61,10 @@ const selectIsPopulated = createSelector( qualityProfilesIsPopulated, metadataProfilesIsPopulated, importListsIsPopulated, - indexerFlagsIsPopulated, systemStatusIsPopulated, translationsIsPopulated ) => { return ( - artistsIsPopulated && customFiltersIsPopulated && tagsIsPopulated && uiSettingsIsPopulated && @@ -84,7 +72,6 @@ const selectIsPopulated = createSelector( qualityProfilesIsPopulated && metadataProfilesIsPopulated && importListsIsPopulated && - indexerFlagsIsPopulated && systemStatusIsPopulated && translationsIsPopulated ); @@ -92,7 +79,6 @@ const selectIsPopulated = createSelector( ); const selectErrors = createSelector( - (state) => state.artist.error, (state) => state.customFilters.error, (state) => state.tags.error, (state) => state.settings.ui.error, @@ -100,11 +86,9 @@ const selectErrors = createSelector( (state) => state.settings.qualityProfiles.error, (state) => state.settings.metadataProfiles.error, (state) => state.settings.importLists.error, - (state) => state.settings.indexerFlags.error, (state) => state.system.status.error, (state) => state.app.translations.error, ( - artistsError, customFiltersError, tagsError, uiSettingsError, @@ -112,12 +96,10 @@ const selectErrors = createSelector( qualityProfilesError, metadataProfilesError, importListsError, - indexerFlagsError, systemStatusError, translationsError ) => { const hasError = !!( - artistsError || customFiltersError || tagsError || uiSettingsError || @@ -125,7 +107,6 @@ const selectErrors = createSelector( qualityProfilesError || metadataProfilesError || importListsError || - indexerFlagsError || systemStatusError || translationsError ); @@ -139,7 +120,6 @@ const selectErrors = createSelector( qualityProfilesError, metadataProfilesError, importListsError, - indexerFlagsError, systemStatusError, translationsError }; @@ -197,9 +177,6 @@ function createMapDispatchToProps(dispatch, props) { dispatchFetchImportLists() { dispatch(fetchImportLists()); }, - dispatchFetchIndexerFlags() { - dispatch(fetchIndexerFlags()); - }, dispatchFetchUISettings() { dispatch(fetchUISettings()); }, @@ -240,7 +217,6 @@ class PageConnector extends Component { this.props.dispatchFetchQualityProfiles(); this.props.dispatchFetchMetadataProfiles(); this.props.dispatchFetchImportLists(); - this.props.dispatchFetchIndexerFlags(); this.props.dispatchFetchUISettings(); this.props.dispatchFetchStatus(); this.props.dispatchFetchTranslations(); @@ -267,7 +243,6 @@ class PageConnector extends Component { dispatchFetchQualityProfiles, dispatchFetchMetadataProfiles, dispatchFetchImportLists, - dispatchFetchIndexerFlags, dispatchFetchUISettings, dispatchFetchStatus, dispatchFetchTranslations, @@ -309,7 +284,6 @@ PageConnector.propTypes = { dispatchFetchQualityProfiles: PropTypes.func.isRequired, dispatchFetchMetadataProfiles: PropTypes.func.isRequired, dispatchFetchImportLists: PropTypes.func.isRequired, - dispatchFetchIndexerFlags: PropTypes.func.isRequired, dispatchFetchUISettings: PropTypes.func.isRequired, dispatchFetchStatus: PropTypes.func.isRequired, dispatchFetchTranslations: PropTypes.func.isRequired, diff --git a/frontend/src/Components/SignalRConnector.js b/frontend/src/Components/SignalRConnector.js index 365827a2b..c970589c7 100644 --- a/frontend/src/Components/SignalRConnector.js +++ b/frontend/src/Components/SignalRConnector.js @@ -172,7 +172,7 @@ class SignalRConnector extends Component { const status = resource.status; // Both successful and failed commands need to be - // completed, otherwise they spin until they time out. + // completed, otherwise they spin until they timeout. if (status === 'completed' || status === 'failed') { this.props.dispatchFinishCommand(resource); @@ -224,58 +224,10 @@ class SignalRConnector extends Component { repopulatePage('trackFileUpdated'); }; - handleDownloadclient = ({ action, resource }) => { - const section = 'settings.downloadClients'; - - if (action === 'created' || action === 'updated') { - this.props.dispatchUpdateItem({ section, ...resource }); - } else if (action === 'deleted') { - this.props.dispatchRemoveItem({ section, id: resource.id }); - } - }; - handleHealth = () => { this.props.dispatchFetchHealth(); }; - handleImportlist = ({ action, resource }) => { - const section = 'settings.importLists'; - - if (action === 'created' || action === 'updated') { - this.props.dispatchUpdateItem({ section, ...resource }); - } else if (action === 'deleted') { - this.props.dispatchRemoveItem({ section, id: resource.id }); - } - }; - - handleIndexer = ({ action, resource }) => { - const section = 'settings.indexers'; - - if (action === 'created' || action === 'updated') { - this.props.dispatchUpdateItem({ section, ...resource }); - } else if (action === 'deleted') { - this.props.dispatchRemoveItem({ section, id: resource.id }); - } - }; - - handleMetadata = ({ action, resource }) => { - const section = 'settings.metadata'; - - if (action === 'updated') { - this.props.dispatchUpdateItem({ section, ...resource }); - } - }; - - handleNotification = ({ action, resource }) => { - const section = 'settings.notifications'; - - if (action === 'created' || action === 'updated') { - this.props.dispatchUpdateItem({ section, ...resource }); - } else if (action === 'deleted') { - this.props.dispatchRemoveItem({ section, id: resource.id }); - } - }; - handleArtist = (body) => { const action = body.action; const section = 'artist'; @@ -314,7 +266,7 @@ class SignalRConnector extends Component { handleWantedCutoff = (body) => { if (body.action === 'updated') { this.props.dispatchUpdateItem({ - section: 'wanted.cutoffUnmet', + section: 'cutoffUnmet', updateOnly: true, ...body.resource }); @@ -324,7 +276,7 @@ class SignalRConnector extends Component { handleWantedMissing = (body) => { if (body.action === 'updated') { this.props.dispatchUpdateItem({ - section: 'wanted.missing', + section: 'missing', updateOnly: true, ...body.resource }); diff --git a/frontend/src/Components/Table/Cells/TableRowCell.css b/frontend/src/Components/Table/Cells/TableRowCell.css index 7e3353c25..47ce0d22e 100644 --- a/frontend/src/Components/Table/Cells/TableRowCell.css +++ b/frontend/src/Components/Table/Cells/TableRowCell.css @@ -4,7 +4,7 @@ line-height: 1.52857143; } -@media only screen and (max-width: $breakpointMedium) { +@media only screen and (max-width: $breakpointSmall) { .cell { white-space: nowrap; } diff --git a/frontend/src/Components/Table/Cells/VirtualTableRowCell.css b/frontend/src/Components/Table/Cells/VirtualTableRowCell.css index f7f3b9306..2501b7c84 100644 --- a/frontend/src/Components/Table/Cells/VirtualTableRowCell.css +++ b/frontend/src/Components/Table/Cells/VirtualTableRowCell.css @@ -7,7 +7,7 @@ white-space: nowrap; } -@media only screen and (max-width: $breakpointMedium) { +@media only screen and (max-width: $breakpointSmall) { .cell { white-space: nowrap; } diff --git a/frontend/src/Components/Table/Table.css b/frontend/src/Components/Table/Table.css index d0507be6b..bdfdec641 100644 --- a/frontend/src/Components/Table/Table.css +++ b/frontend/src/Components/Table/Table.css @@ -10,7 +10,7 @@ border-collapse: collapse; } -@media only screen and (max-width: $breakpointMedium) { +@media only screen and (max-width: $breakpointSmall) { .tableContainer { min-width: 100%; width: fit-content; diff --git a/frontend/src/Components/Table/TableHeaderCell.css b/frontend/src/Components/Table/TableHeaderCell.css index eded9c95b..c2c4f58c8 100644 --- a/frontend/src/Components/Table/TableHeaderCell.css +++ b/frontend/src/Components/Table/TableHeaderCell.css @@ -9,7 +9,7 @@ margin-left: 10px; } -@media only screen and (max-width: $breakpointMedium) { +@media only screen and (max-width: $breakpointSmall) { .headerCell { white-space: nowrap; } diff --git a/frontend/src/Components/Table/TablePager.css b/frontend/src/Components/Table/TablePager.css index 6d184196e..d73a0d0c0 100644 --- a/frontend/src/Components/Table/TablePager.css +++ b/frontend/src/Components/Table/TablePager.css @@ -60,7 +60,7 @@ height: 25px; } -@media only screen and (max-width: $breakpointMedium) { +@media only screen and (max-width: $breakpointSmall) { .pager { flex-wrap: wrap; } diff --git a/frontend/src/Components/Table/VirtualTableHeaderCell.css b/frontend/src/Components/Table/VirtualTableHeaderCell.css index eded9c95b..c2c4f58c8 100644 --- a/frontend/src/Components/Table/VirtualTableHeaderCell.css +++ b/frontend/src/Components/Table/VirtualTableHeaderCell.css @@ -9,7 +9,7 @@ margin-left: 10px; } -@media only screen and (max-width: $breakpointMedium) { +@media only screen and (max-width: $breakpointSmall) { .headerCell { white-space: nowrap; } diff --git a/frontend/src/Components/TagList.js b/frontend/src/Components/TagList.js index fe700b8fe..6da96849c 100644 --- a/frontend/src/Components/TagList.js +++ b/frontend/src/Components/TagList.js @@ -1,7 +1,6 @@ import PropTypes from 'prop-types'; import React from 'react'; import { kinds } from 'Helpers/Props'; -import sortByProp from 'Utilities/Array/sortByProp'; import Label from './Label'; import styles from './TagList.css'; @@ -9,7 +8,7 @@ function TagList({ tags, tagList }) { const sortedTags = tags .map((tagId) => tagList.find((tag) => tag.id === tagId)) .filter((tag) => !!tag) - .sort(sortByProp('label')); + .sort((a, b) => a.label.localeCompare(b.label)); return (
diff --git a/frontend/src/Content/Fonts/fonts.css b/frontend/src/Content/Fonts/fonts.css index e0f1bf5dc..bf31501dd 100644 --- a/frontend/src/Content/Fonts/fonts.css +++ b/frontend/src/Content/Fonts/fonts.css @@ -25,3 +25,14 @@ font-family: 'Ubuntu Mono'; src: url('UbuntuMono-Regular.eot?#iefix&v=1.3.0') format('embedded-opentype'), url('UbuntuMono-Regular.woff?v=1.3.0') format('woff'), url('UbuntuMono-Regular.ttf?v=1.3.0') format('truetype'); } + +/* + * text-security-disc + */ + +@font-face { + font-weight: normal; + font-style: normal; + font-family: 'text-security-disc'; + src: url('text-security-disc.woff?v=1.3.0') format('woff'), url('text-security-disc.ttf?v=1.3.0') format('truetype'); +} diff --git a/frontend/src/Content/Fonts/text-security-disc.ttf b/frontend/src/Content/Fonts/text-security-disc.ttf new file mode 100644 index 000000000..86038dba8 Binary files /dev/null and b/frontend/src/Content/Fonts/text-security-disc.ttf differ diff --git a/frontend/src/Content/Fonts/text-security-disc.woff b/frontend/src/Content/Fonts/text-security-disc.woff new file mode 100644 index 000000000..bc4cc324b Binary files /dev/null and b/frontend/src/Content/Fonts/text-security-disc.woff differ diff --git a/frontend/src/Content/Images/Icons/browserconfig.xml b/frontend/src/Content/Images/Icons/browserconfig.xml new file mode 100644 index 000000000..993924968 --- /dev/null +++ b/frontend/src/Content/Images/Icons/browserconfig.xml @@ -0,0 +1,9 @@ + + + + + + #00ccff + + + diff --git a/frontend/src/Content/Images/Icons/manifest.json b/frontend/src/Content/Images/Icons/manifest.json new file mode 100644 index 000000000..a7ffa6777 --- /dev/null +++ b/frontend/src/Content/Images/Icons/manifest.json @@ -0,0 +1,19 @@ +{ + "name": "Lidarr", + "icons": [ + { + "src": "android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "start_url": "../../../../", + "theme_color": "#3a3f51", + "background_color": "#3a3f51", + "display": "minimal-ui" +} diff --git a/frontend/src/Content/browserconfig.xml b/frontend/src/Content/browserconfig.xml deleted file mode 100644 index 646112d06..000000000 --- a/frontend/src/Content/browserconfig.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - #00ccff - - - - diff --git a/frontend/src/Content/manifest.json b/frontend/src/Content/manifest.json deleted file mode 100644 index 5c2b3d59d..000000000 --- a/frontend/src/Content/manifest.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "__INSTANCE_NAME__", - "icons": [ - { - "src": "__URL_BASE__/Content/Images/Icons/android-chrome-192x192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "__URL_BASE__/Content/Images/Icons/android-chrome-512x512.png", - "sizes": "512x512", - "type": "image/png" - } - ], - "start_url": "__URL_BASE__/", - "theme_color": "#3a3f51", - "background_color": "#3a3f51", - "display": "standalone" -} diff --git a/frontend/src/Helpers/Hooks/useModalOpenState.ts b/frontend/src/Helpers/Hooks/useModalOpenState.ts deleted file mode 100644 index f5b5a96f0..000000000 --- a/frontend/src/Helpers/Hooks/useModalOpenState.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { useCallback, useState } from 'react'; - -export default function useModalOpenState( - initialState: boolean -): [boolean, () => void, () => void] { - const [isOpen, setOpen] = useState(initialState); - - const setModalOpen = useCallback(() => { - setOpen(true); - }, [setOpen]); - - const setModalClosed = useCallback(() => { - setOpen(false); - }, [setOpen]); - - return [isOpen, setModalOpen, setModalClosed]; -} diff --git a/frontend/src/Helpers/Props/icons.js b/frontend/src/Helpers/Props/icons.js index aa9c23145..77803e56e 100644 --- a/frontend/src/Helpers/Props/icons.js +++ b/frontend/src/Helpers/Props/icons.js @@ -32,7 +32,6 @@ import { faBookReader as fasBookReader, faBroadcastTower as fasBroadcastTower, faBug as fasBug, - faCalculator as fasCalculator, faCalendarAlt as fasCalendarAlt, faCaretDown as fasCaretDown, faCheck as fasCheck, @@ -61,7 +60,6 @@ import { faFileImport as fasFileImport, faFileInvoice as farFileInvoice, faFilter as fasFilter, - faFlag as fasFlag, faFolderOpen as fasFolderOpen, faForward as fasForward, faHeart as fasHeart, @@ -160,7 +158,6 @@ export const FILE = farFile; export const FILE_IMPORT = fasFileImport; export const FILE_MISSING = fasFileCircleQuestion; export const FILTER = fasFilter; -export const FLAG = fasFlag; export const FOLDER = farFolder; export const FOLDER_OPEN = fasFolderOpen; export const GROUP = farObjectGroup; @@ -188,7 +185,6 @@ export const PAGE_PREVIOUS = fasBackward; export const PAGE_NEXT = fasForward; export const PAGE_LAST = fasFastForward; export const PARENT = fasLevelUpAlt; -export const PARSE = fasCalculator; export const PAUSED = fasPause; export const PENDING = farClock; export const PROFILE = fasUser; diff --git a/frontend/src/Helpers/Props/inputTypes.js b/frontend/src/Helpers/Props/inputTypes.js index 44115c787..8ebbd540b 100644 --- a/frontend/src/Helpers/Props/inputTypes.js +++ b/frontend/src/Helpers/Props/inputTypes.js @@ -2,8 +2,8 @@ export const AUTO_COMPLETE = 'autoComplete'; export const CAPTCHA = 'captcha'; export const CHECK = 'check'; export const DEVICE = 'device'; -export const KEY_VALUE_LIST = 'keyValueList'; export const PLAYLIST = 'playlist'; +export const KEY_VALUE_LIST = 'keyValueList'; export const MONITOR_ALBUMS_SELECT = 'monitorAlbumsSelect'; export const MONITOR_NEW_ITEMS_SELECT = 'monitorNewItemsSelect'; export const FLOAT = 'float'; @@ -15,12 +15,10 @@ export const QUALITY_PROFILE_SELECT = 'qualityProfileSelect'; export const METADATA_PROFILE_SELECT = 'metadataProfileSelect'; export const ALBUM_RELEASE_SELECT = 'albumReleaseSelect'; export const INDEXER_SELECT = 'indexerSelect'; -export const INDEXER_FLAGS_SELECT = 'indexerFlagsSelect'; export const DOWNLOAD_CLIENT_SELECT = 'downloadClientSelect'; export const ROOT_FOLDER_SELECT = 'rootFolderSelect'; export const SELECT = 'select'; export const SERIES_TYPE_SELECT = 'artistTypeSelect'; -export const ARTIST_TAG = 'artistTag'; export const DYNAMIC_SELECT = 'dynamicSelect'; export const TAG = 'tag'; export const TAG_SELECT = 'tagSelect'; @@ -34,8 +32,8 @@ export const all = [ CAPTCHA, CHECK, DEVICE, - KEY_VALUE_LIST, PLAYLIST, + KEY_VALUE_LIST, MONITOR_ALBUMS_SELECT, MONITOR_NEW_ITEMS_SELECT, FLOAT, @@ -50,7 +48,6 @@ export const all = [ DOWNLOAD_CLIENT_SELECT, ROOT_FOLDER_SELECT, SELECT, - ARTIST_TAG, DYNAMIC_SELECT, SERIES_TYPE_SELECT, TAG, diff --git a/frontend/src/Helpers/Props/sizes.js b/frontend/src/Helpers/Props/sizes.js index 6ac15f3bd..d7f85df5e 100644 --- a/frontend/src/Helpers/Props/sizes.js +++ b/frontend/src/Helpers/Props/sizes.js @@ -3,5 +3,5 @@ export const SMALL = 'small'; export const MEDIUM = 'medium'; export const LARGE = 'large'; export const EXTRA_LARGE = 'extraLarge'; -export const EXTRA_EXTRA_LARGE = 'extraExtraLarge'; -export const all = [EXTRA_SMALL, SMALL, MEDIUM, LARGE, EXTRA_LARGE, EXTRA_EXTRA_LARGE]; + +export const all = [EXTRA_SMALL, SMALL, MEDIUM, LARGE, EXTRA_LARGE]; diff --git a/frontend/src/InteractiveImport/Album/SelectAlbumModalContent.js b/frontend/src/InteractiveImport/Album/SelectAlbumModalContent.js index f8c84e54b..5f2cc9696 100644 --- a/frontend/src/InteractiveImport/Album/SelectAlbumModalContent.js +++ b/frontend/src/InteractiveImport/Album/SelectAlbumModalContent.js @@ -11,7 +11,6 @@ import Scroller from 'Components/Scroller/Scroller'; import Table from 'Components/Table/Table'; import TableBody from 'Components/Table/TableBody'; import { scrollDirections } from 'Helpers/Props'; -import getErrorMessage from 'Utilities/Object/getErrorMessage'; import translate from 'Utilities/String/translate'; import SelectAlbumRow from './SelectAlbumRow'; import styles from './SelectAlbumModalContent.css'; @@ -20,7 +19,6 @@ const columns = [ { name: 'title', label: () => translate('AlbumTitle'), - isSortable: true, isVisible: true }, { @@ -31,7 +29,6 @@ const columns = [ { name: 'releaseDate', label: () => translate('ReleaseDate'), - isSortable: true, isVisible: true }, { @@ -66,22 +63,16 @@ class SelectAlbumModalContent extends Component { render() { const { - isFetching, - isPopulated, - error, items, - sortKey, - sortDirection, - onSortPress, onAlbumSelect, - onModalClose + onModalClose, + isFetching, + ...otherProps } = this.props; const filter = this.state.filter; const filterLower = filter.toLowerCase(); - const errorMessage = getErrorMessage(error, 'Unable to load albums'); - return ( @@ -92,29 +83,27 @@ class SelectAlbumModalContent extends Component { className={styles.modalBody} scrollDirection={scrollDirections.NONE} > + { + isFetching && + + } + + - {isFetching ? : null} - - {error ?
{errorMessage}
: null} - - - - {isPopulated && !!items.length ? ( + { { @@ -133,7 +122,7 @@ class SelectAlbumModalContent extends Component { }
- ) : null} + }
@@ -148,13 +137,8 @@ class SelectAlbumModalContent extends Component { } SelectAlbumModalContent.propTypes = { - isFetching: PropTypes.bool.isRequired, - isPopulated: PropTypes.bool.isRequired, - error: PropTypes.object, items: PropTypes.arrayOf(PropTypes.object).isRequired, - sortKey: PropTypes.string, - sortDirection: PropTypes.string, - onSortPress: PropTypes.func.isRequired, + isFetching: PropTypes.bool.isRequired, onAlbumSelect: PropTypes.func.isRequired, onModalClose: PropTypes.func.isRequired }; diff --git a/frontend/src/InteractiveImport/Album/SelectAlbumModalContentConnector.js b/frontend/src/InteractiveImport/Album/SelectAlbumModalContentConnector.js index d09da0fca..12cd88e53 100644 --- a/frontend/src/InteractiveImport/Album/SelectAlbumModalContentConnector.js +++ b/frontend/src/InteractiveImport/Album/SelectAlbumModalContentConnector.js @@ -3,14 +3,18 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; -import { clearAlbums, fetchAlbums, setAlbumsSort } from 'Store/Actions/albumSelectionActions'; -import { saveInteractiveImportItem, updateInteractiveImportItem } from 'Store/Actions/interactiveImportActions'; +import { + clearInteractiveImportAlbums, + fetchInteractiveImportAlbums, + saveInteractiveImportItem, + setInteractiveImportAlbumsSort, + updateInteractiveImportItem } from 'Store/Actions/interactiveImportActions'; import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector'; import SelectAlbumModalContent from './SelectAlbumModalContent'; function createMapStateToProps() { return createSelector( - createClientSideCollectionSelector('albumSelection'), + createClientSideCollectionSelector('interactiveImport.albums'), (albums) => { return albums; } @@ -18,9 +22,9 @@ function createMapStateToProps() { } const mapDispatchToProps = { - fetchAlbums, - setAlbumsSort, - clearAlbums, + fetchInteractiveImportAlbums, + setInteractiveImportAlbumsSort, + clearInteractiveImportAlbums, updateInteractiveImportItem, saveInteractiveImportItem }; @@ -35,20 +39,20 @@ class SelectAlbumModalContentConnector extends Component { artistId } = this.props; - this.props.fetchAlbums({ artistId }); + this.props.fetchInteractiveImportAlbums({ artistId }); } componentWillUnmount() { // This clears the albums for the queue and hides the queue // We'll need another place to store albums for manual import - this.props.clearAlbums(); + this.props.clearInteractiveImportAlbums(); } // // Listeners onSortPress = (sortKey, sortDirection) => { - this.props.setAlbumsSort({ sortKey, sortDirection }); + this.props.setInteractiveImportAlbumsSort({ sortKey, sortDirection }); }; onAlbumSelect = (albumId) => { @@ -78,7 +82,6 @@ class SelectAlbumModalContentConnector extends Component { return ( ); @@ -89,9 +92,9 @@ SelectAlbumModalContentConnector.propTypes = { ids: PropTypes.arrayOf(PropTypes.number).isRequired, artistId: PropTypes.number.isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired, - fetchAlbums: PropTypes.func.isRequired, - setAlbumsSort: PropTypes.func.isRequired, - clearAlbums: PropTypes.func.isRequired, + fetchInteractiveImportAlbums: PropTypes.func.isRequired, + setInteractiveImportAlbumsSort: PropTypes.func.isRequired, + clearInteractiveImportAlbums: PropTypes.func.isRequired, saveInteractiveImportItem: PropTypes.func.isRequired, updateInteractiveImportItem: PropTypes.func.isRequired, onModalClose: PropTypes.func.isRequired diff --git a/frontend/src/InteractiveImport/AlbumRelease/SelectAlbumReleaseRow.js b/frontend/src/InteractiveImport/AlbumRelease/SelectAlbumReleaseRow.js index 26fa7282a..d9301cdfa 100644 --- a/frontend/src/InteractiveImport/AlbumRelease/SelectAlbumReleaseRow.js +++ b/frontend/src/InteractiveImport/AlbumRelease/SelectAlbumReleaseRow.js @@ -1,9 +1,10 @@ import _ from 'lodash'; import PropTypes from 'prop-types'; import React, { Component } from 'react'; -import SelectInput from 'Components/Form/SelectInput'; +import FormInputGroup from 'Components/Form/FormInputGroup'; import TableRowCell from 'Components/Table/Cells/TableRowCell'; import TableRow from 'Components/Table/TableRow'; +import { inputTypes } from 'Helpers/Props'; import shortenList from 'Utilities/String/shortenList'; import titleCase from 'Utilities/String/titleCase'; @@ -55,7 +56,8 @@ class SelectAlbumReleaseRow extends Component { if (name === 'release') { return ( - ({ key: r.id, diff --git a/frontend/src/InteractiveImport/IndexerFlags/SelectIndexerFlagsModal.js b/frontend/src/InteractiveImport/IndexerFlags/SelectIndexerFlagsModal.js deleted file mode 100644 index 04359b96a..000000000 --- a/frontend/src/InteractiveImport/IndexerFlags/SelectIndexerFlagsModal.js +++ /dev/null @@ -1,37 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import Modal from 'Components/Modal/Modal'; -import SelectIndexerFlagsModalContentConnector from './SelectIndexerFlagsModalContentConnector'; - -class SelectIndexerFlagsModal extends Component { - - // - // Render - - render() { - const { - isOpen, - onModalClose, - ...otherProps - } = this.props; - - return ( - - - - ); - } -} - -SelectIndexerFlagsModal.propTypes = { - isOpen: PropTypes.bool.isRequired, - onModalClose: PropTypes.func.isRequired -}; - -export default SelectIndexerFlagsModal; diff --git a/frontend/src/InteractiveImport/IndexerFlags/SelectIndexerFlagsModalContent.css b/frontend/src/InteractiveImport/IndexerFlags/SelectIndexerFlagsModalContent.css deleted file mode 100644 index 72dfb1cb6..000000000 --- a/frontend/src/InteractiveImport/IndexerFlags/SelectIndexerFlagsModalContent.css +++ /dev/null @@ -1,7 +0,0 @@ -.modalBody { - composes: modalBody from '~Components/Modal/ModalBody.css'; - - display: flex; - flex: 1 1 auto; - flex-direction: column; -} diff --git a/frontend/src/InteractiveImport/IndexerFlags/SelectIndexerFlagsModalContent.js b/frontend/src/InteractiveImport/IndexerFlags/SelectIndexerFlagsModalContent.js deleted file mode 100644 index b30f76775..000000000 --- a/frontend/src/InteractiveImport/IndexerFlags/SelectIndexerFlagsModalContent.js +++ /dev/null @@ -1,106 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import Form from 'Components/Form/Form'; -import FormGroup from 'Components/Form/FormGroup'; -import FormInputGroup from 'Components/Form/FormInputGroup'; -import FormLabel from 'Components/Form/FormLabel'; -import Button from 'Components/Link/Button'; -import ModalBody from 'Components/Modal/ModalBody'; -import ModalContent from 'Components/Modal/ModalContent'; -import ModalFooter from 'Components/Modal/ModalFooter'; -import ModalHeader from 'Components/Modal/ModalHeader'; -import { inputTypes, kinds, scrollDirections } from 'Helpers/Props'; -import translate from 'Utilities/String/translate'; -import styles from './SelectIndexerFlagsModalContent.css'; - -class SelectIndexerFlagsModalContent extends Component { - - // - // Lifecycle - - constructor(props, context) { - super(props, context); - - const { - indexerFlags - } = props; - - this.state = { - indexerFlags - }; - } - - // - // Listeners - - onIndexerFlagsChange = ({ value }) => { - this.setState({ indexerFlags: value }); - }; - - onIndexerFlagsSelect = () => { - this.props.onIndexerFlagsSelect(this.state); - }; - - // - // Render - - render() { - const { - onModalClose - } = this.props; - - const { - indexerFlags - } = this.state; - - return ( - - - Manual Import - Set indexer Flags - - - - - - - {translate('IndexerFlags')} - - - - - - - - - - - - - - ); - } -} - -SelectIndexerFlagsModalContent.propTypes = { - indexerFlags: PropTypes.number.isRequired, - onIndexerFlagsSelect: PropTypes.func.isRequired, - onModalClose: PropTypes.func.isRequired -}; - -export default SelectIndexerFlagsModalContent; diff --git a/frontend/src/InteractiveImport/IndexerFlags/SelectIndexerFlagsModalContentConnector.js b/frontend/src/InteractiveImport/IndexerFlags/SelectIndexerFlagsModalContentConnector.js deleted file mode 100644 index 7a9af7353..000000000 --- a/frontend/src/InteractiveImport/IndexerFlags/SelectIndexerFlagsModalContentConnector.js +++ /dev/null @@ -1,54 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import { saveInteractiveImportItem, updateInteractiveImportItems } from 'Store/Actions/interactiveImportActions'; -import SelectIndexerFlagsModalContent from './SelectIndexerFlagsModalContent'; - -const mapDispatchToProps = { - dispatchUpdateInteractiveImportItems: updateInteractiveImportItems, - dispatchSaveInteractiveImportItems: saveInteractiveImportItem -}; - -class SelectIndexerFlagsModalContentConnector extends Component { - - // - // Listeners - - onIndexerFlagsSelect = ({ indexerFlags }) => { - const { - ids, - dispatchUpdateInteractiveImportItems, - dispatchSaveInteractiveImportItems - } = this.props; - - dispatchUpdateInteractiveImportItems({ - ids, - indexerFlags - }); - - dispatchSaveInteractiveImportItems({ ids }); - - this.props.onModalClose(true); - }; - - // - // Render - - render() { - return ( - - ); - } -} - -SelectIndexerFlagsModalContentConnector.propTypes = { - ids: PropTypes.arrayOf(PropTypes.number).isRequired, - dispatchUpdateInteractiveImportItems: PropTypes.func.isRequired, - dispatchSaveInteractiveImportItems: PropTypes.func.isRequired, - onModalClose: PropTypes.func.isRequired -}; - -export default connect(null, mapDispatchToProps)(SelectIndexerFlagsModalContentConnector); diff --git a/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.css b/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.css index 93d815c9c..573b16667 100644 --- a/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.css +++ b/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.css @@ -18,17 +18,12 @@ .leftButtons, .rightButtons { display: flex; + flex: 1 0 50%; flex-wrap: wrap; - min-width: 0; -} - -.leftButtons { - flex: 0 1 auto; } .rightButtons { justify-content: flex-end; - flex: 1 1 50%; } .importMode, @@ -36,7 +31,6 @@ composes: select from '~Components/Form/SelectInput.css'; margin-right: 10px; - max-width: 100%; width: auto; } @@ -49,12 +43,10 @@ .leftButtons, .rightButtons { flex-direction: column; - gap: 3px; } .leftButtons { align-items: flex-start; - max-width: fit-content; } .rightButtons { diff --git a/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.js b/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.js index d980e77ce..d1361a785 100644 --- a/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.js +++ b/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.js @@ -20,7 +20,6 @@ import SelectAlbumModal from 'InteractiveImport/Album/SelectAlbumModal'; import SelectAlbumReleaseModal from 'InteractiveImport/AlbumRelease/SelectAlbumReleaseModal'; import SelectArtistModal from 'InteractiveImport/Artist/SelectArtistModal'; import ConfirmImportModal from 'InteractiveImport/Confirmation/ConfirmImportModal'; -import SelectIndexerFlagsModal from 'InteractiveImport/IndexerFlags/SelectIndexerFlagsModal'; import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal'; import SelectReleaseGroupModal from 'InteractiveImport/ReleaseGroup/SelectReleaseGroupModal'; import getErrorMessage from 'Utilities/Object/getErrorMessage'; @@ -31,7 +30,7 @@ import toggleSelected from 'Utilities/Table/toggleSelected'; import InteractiveImportRow from './InteractiveImportRow'; import styles from './InteractiveImportModalContent.css'; -const COLUMNS = [ +const columns = [ { name: 'path', label: () => translate('Path'), @@ -80,21 +79,11 @@ const COLUMNS = [ isSortable: true, isVisible: true }, - { - name: 'indexerFlags', - label: React.createElement(Icon, { - name: icons.FLAG, - title: () => translate('IndexerFlags') - }), - isSortable: true, - isVisible: true - }, { name: 'rejections', label: React.createElement(Icon, { name: icons.DANGER, - kind: kinds.DANGER, - title: () => translate('Rejections') + kind: kinds.DANGER }), isSortable: true, isVisible: true @@ -118,7 +107,6 @@ const ALBUM = 'album'; const ALBUM_RELEASE = 'albumRelease'; const RELEASE_GROUP = 'releaseGroup'; const QUALITY = 'quality'; -const INDEXER_FLAGS = 'indexerFlags'; const replaceExistingFilesOptions = { COMBINE: 'combine', @@ -313,21 +301,6 @@ class InteractiveImportModalContent extends Component { inconsistentAlbumReleases } = this.state; - const allColumns = _.cloneDeep(COLUMNS); - const columns = allColumns.map((column) => { - const showIndexerFlags = items.some((item) => item.indexerFlags); - - if (!showIndexerFlags) { - const indexerFlagsColumn = allColumns.find((c) => c.name === 'indexerFlags'); - - if (indexerFlagsColumn) { - indexerFlagsColumn.isVisible = false; - } - } - - return column; - }); - const selectedIds = this.getSelectedIds(); const selectedItem = selectedIds.length ? _.find(items, { id: selectedIds[0] }) : null; const errorMessage = getErrorMessage(error, 'Unable to load manual import items'); @@ -337,8 +310,7 @@ class InteractiveImportModalContent extends Component { { key: ALBUM, value: translate('SelectAlbum') }, { key: ALBUM_RELEASE, value: translate('SelectAlbumRelease') }, { key: QUALITY, value: translate('SelectQuality') }, - { key: RELEASE_GROUP, value: translate('SelectReleaseGroup') }, - { key: INDEXER_FLAGS, value: translate('SelectIndexerFlags') } + { key: RELEASE_GROUP, value: translate('SelectReleaseGroup') } ]; if (allowArtistChange) { @@ -461,7 +433,6 @@ class InteractiveImportModalContent extends Component { isSaving={isSaving} {...item} allowArtistChange={allowArtistChange} - columns={columns} onSelectedChange={this.onSelectedChange} onValidRowChange={this.onValidRowChange} /> @@ -576,13 +547,6 @@ class InteractiveImportModalContent extends Component { onModalClose={this.onSelectModalClose} /> - - 0 + quality ) { this.props.onSelectedChange({ id, value: true }); } @@ -135,10 +130,6 @@ class InteractiveImportRow extends Component { this.setState({ isSelectQualityModalOpen: true }); }; - onSelectIndexerFlagsPress = () => { - this.setState({ isSelectIndexerFlagsModalOpen: true }); - }; - onSelectArtistModalClose = (changed) => { this.setState({ isSelectArtistModalOpen: false }); this.selectRowAfterChange(changed); @@ -164,11 +155,6 @@ class InteractiveImportRow extends Component { this.selectRowAfterChange(changed); }; - onSelectIndexerFlagsModalClose = (changed) => { - this.setState({ isSelectIndexerFlagsModalOpen: false }); - this.selectRowAfterChange(changed); - }; - // // Render @@ -185,9 +171,7 @@ class InteractiveImportRow extends Component { releaseGroup, size, customFormats, - indexerFlags, rejections, - columns, isReprocessing, audioTags, additionalFile, @@ -200,8 +184,7 @@ class InteractiveImportRow extends Component { isSelectAlbumModalOpen, isSelectTrackModalOpen, isSelectReleaseGroupModalOpen, - isSelectQualityModalOpen, - isSelectIndexerFlagsModalOpen + isSelectQualityModalOpen } = this.state; const artistName = artist ? artist.artistName : ''; @@ -221,7 +204,6 @@ class InteractiveImportRow extends Component { const showTrackNumbersLoading = isReprocessing && !tracks.length; const showReleaseGroupPlaceholder = isSelected && !releaseGroup; const showQualityPlaceholder = isSelected && !quality; - const showIndexerFlagsPlaceholder = isSelected && !indexerFlags; const pathCellContents = (
@@ -237,8 +219,6 @@ class InteractiveImportRow extends Component { /> ) : pathCellContents; - const isIndexerFlagsColumnVisible = columns.find((c) => c.name === 'indexerFlags')?.isVisible ?? false; - return ( - {isIndexerFlagsColumnVisible ? ( - - {showIndexerFlagsPlaceholder ? ( - - ) : ( - <> - {indexerFlags ? ( - } - title={translate('IndexerFlags')} - body={} - position={tooltipPositions.LEFT} - /> - ) : null} - - )} - - ) : null} - { rejections.length ? @@ -437,13 +395,6 @@ class InteractiveImportRow extends Component { real={quality ? quality.revision.real > 0 : false} onModalClose={this.onSelectQualityModalClose} /> - - ); } @@ -462,9 +413,7 @@ InteractiveImportRow.propTypes = { quality: PropTypes.object, size: PropTypes.number.isRequired, customFormats: PropTypes.arrayOf(PropTypes.object), - indexerFlags: PropTypes.number.isRequired, rejections: PropTypes.arrayOf(PropTypes.object).isRequired, - columns: PropTypes.arrayOf(PropTypes.object).isRequired, audioTags: PropTypes.object.isRequired, additionalFile: PropTypes.bool.isRequired, isReprocessing: PropTypes.bool, diff --git a/frontend/src/InteractiveImport/InteractiveImportModal.js b/frontend/src/InteractiveImport/InteractiveImportModal.js index 4d0e9f2f1..dda75152f 100644 --- a/frontend/src/InteractiveImport/InteractiveImportModal.js +++ b/frontend/src/InteractiveImport/InteractiveImportModal.js @@ -48,7 +48,7 @@ class InteractiveImportModal extends Component { return ( diff --git a/frontend/src/InteractiveSearch/InteractiveSearch.js b/frontend/src/InteractiveSearch/InteractiveSearch.js index 64d1ce730..6e74695b0 100644 --- a/frontend/src/InteractiveSearch/InteractiveSearch.js +++ b/frontend/src/InteractiveSearch/InteractiveSearch.js @@ -65,15 +65,6 @@ const columns = [ isSortable: true, isVisible: true }, - { - name: 'indexerFlags', - label: React.createElement(Icon, { - name: icons.FLAG, - title: () => translate('IndexerFlags') - }), - isSortable: true, - isVisible: true - }, { name: 'rejections', label: React.createElement(Icon, { diff --git a/frontend/src/InteractiveSearch/InteractiveSearchRow.css b/frontend/src/InteractiveSearch/InteractiveSearchRow.css index dad7242c8..ffea82600 100644 --- a/frontend/src/InteractiveSearch/InteractiveSearchRow.css +++ b/frontend/src/InteractiveSearch/InteractiveSearchRow.css @@ -35,7 +35,6 @@ } .rejected, -.indexerFlags, .download { composes: cell from '~Components/Table/Cells/TableRowCell.css'; diff --git a/frontend/src/InteractiveSearch/InteractiveSearchRow.css.d.ts b/frontend/src/InteractiveSearch/InteractiveSearchRow.css.d.ts index bec6dcf78..ca01c5ee6 100644 --- a/frontend/src/InteractiveSearch/InteractiveSearchRow.css.d.ts +++ b/frontend/src/InteractiveSearch/InteractiveSearchRow.css.d.ts @@ -5,7 +5,6 @@ interface CssExports { 'customFormatScore': string; 'download': string; 'indexer': string; - 'indexerFlags': string; 'peers': string; 'protocol': string; 'quality': string; diff --git a/frontend/src/InteractiveSearch/InteractiveSearchRow.js b/frontend/src/InteractiveSearch/InteractiveSearchRow.js index a139f8085..3029d2d2f 100644 --- a/frontend/src/InteractiveSearch/InteractiveSearchRow.js +++ b/frontend/src/InteractiveSearch/InteractiveSearchRow.js @@ -2,7 +2,6 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import ProtocolLabel from 'Activity/Queue/ProtocolLabel'; import AlbumFormats from 'Album/AlbumFormats'; -import IndexerFlags from 'Album/IndexerFlags'; import TrackQuality from 'Album/TrackQuality'; import Icon from 'Components/Icon'; import Link from 'Components/Link/Link'; @@ -49,12 +48,12 @@ function getDownloadTooltip(isGrabbing, isGrabbed, grabError) { if (isGrabbing) { return ''; } else if (isGrabbed) { - return translate('AddedToDownloadQueue'); + return 'Added to downloaded queue'; } else if (grabError) { return grabError; } - return translate('AddToDownloadQueue'); + return 'Add to downloaded queue'; } class InteractiveSearchRow extends Component { @@ -130,7 +129,6 @@ class InteractiveSearchRow extends Component { quality, customFormatScore, customFormats, - indexerFlags = 0, rejections, downloadAllowed, isGrabbing, @@ -189,21 +187,10 @@ class InteractiveSearchRow extends Component { formatCustomFormatScore(customFormatScore, customFormats.length) } tooltip={} - position={tooltipPositions.LEFT} + position={tooltipPositions.BOTTOM} /> - - {indexerFlags ? ( - } - title={translate('IndexerFlags')} - body={} - position={tooltipPositions.LEFT} - /> - ) : null} - - { !!rejections.length && @@ -249,9 +236,7 @@ class InteractiveSearchRow extends Component { isOpen={this.state.isConfirmGrabModalOpen} kind={kinds.WARNING} title={translate('GrabRelease')} - message={translate('GrabReleaseUnknownArtistOrAlbumMessageText', { - title - })} + message={translate('GrabReleaseMessageText', [title])} confirmLabel={translate('Grab')} onConfirm={this.onGrabConfirm} onCancel={this.onGrabCancel} @@ -278,7 +263,6 @@ InteractiveSearchRow.propTypes = { quality: PropTypes.object.isRequired, customFormats: PropTypes.arrayOf(PropTypes.object), customFormatScore: PropTypes.number.isRequired, - indexerFlags: PropTypes.number.isRequired, rejections: PropTypes.arrayOf(PropTypes.string).isRequired, downloadAllowed: PropTypes.bool.isRequired, isGrabbing: PropTypes.bool.isRequired, @@ -291,7 +275,6 @@ InteractiveSearchRow.propTypes = { }; InteractiveSearchRow.defaultProps = { - indexerFlags: 0, rejections: [], isGrabbing: false, isGrabbed: false diff --git a/frontend/src/Parse/Parse.css b/frontend/src/Parse/Parse.css deleted file mode 100644 index 43536452c..000000000 --- a/frontend/src/Parse/Parse.css +++ /dev/null @@ -1,45 +0,0 @@ -.inputContainer { - display: flex; - margin-bottom: 10px; -} - -.inputIconContainer { - width: 58px; - height: 46px; - border: 1px solid var(--inputBorderColor); - border-right: none; - border-radius: 4px; - border-top-right-radius: 0; - border-bottom-right-radius: 0; - background-color: var(--inputIconContainerBackgroundColor); - text-align: center; - line-height: 46px; -} - -.input { - composes: input from '~Components/Form/TextInput.css'; - - height: 46px; - border-radius: 0; - font-size: 18px; -} - -.clearButton { - border: 1px solid var(--inputBorderColor); - border-left: none; - border-top-right-radius: 4px; - border-bottom-right-radius: 4px; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); -} - -.message { - margin-top: 30px; - text-align: center; - font-weight: 300; - font-size: $largeFontSize; -} - -.helpText { - margin-bottom: 10px; - font-size: 24px; -} diff --git a/frontend/src/Parse/Parse.css.d.ts b/frontend/src/Parse/Parse.css.d.ts deleted file mode 100644 index 4a4def577..000000000 --- a/frontend/src/Parse/Parse.css.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -// This file is automatically generated. -// Please do not change this file! -interface CssExports { - 'clearButton': string; - 'helpText': string; - 'input': string; - 'inputContainer': string; - 'inputIconContainer': string; - 'message': string; -} -export const cssExports: CssExports; -export default cssExports; diff --git a/frontend/src/Parse/Parse.tsx b/frontend/src/Parse/Parse.tsx deleted file mode 100644 index 15a0deb47..000000000 --- a/frontend/src/Parse/Parse.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import React, { useCallback, useEffect, useState } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import TextInput from 'Components/Form/TextInput'; -import Icon from 'Components/Icon'; -import Button from 'Components/Link/Button'; -import LoadingIndicator from 'Components/Loading/LoadingIndicator'; -import PageContent from 'Components/Page/PageContent'; -import PageContentBody from 'Components/Page/PageContentBody'; -import { icons } from 'Helpers/Props'; -import { clear, fetch } from 'Store/Actions/parseActions'; -import getErrorMessage from 'Utilities/Object/getErrorMessage'; -import translate from 'Utilities/String/translate'; -import ParseResult from './ParseResult'; -import parseStateSelector from './parseStateSelector'; -import styles from './Parse.css'; - -function Parse() { - const { isFetching, error, item } = useSelector(parseStateSelector()); - - const [title, setTitle] = useState(''); - const dispatch = useDispatch(); - - const onInputChange = useCallback( - ({ value }: { value: string }) => { - const trimmedValue = value.trim(); - - setTitle(value); - - if (trimmedValue === '') { - dispatch(clear()); - } else { - dispatch(fetch({ title: trimmedValue })); - } - }, - [setTitle, dispatch] - ); - - const onClearPress = useCallback(() => { - setTitle(''); - dispatch(clear()); - }, [setTitle, dispatch]); - - useEffect( - () => { - return () => { - dispatch(clear()); - }; - }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [] - ); - - return ( - - -
-
- -
- - - - -
- - {isFetching ? : null} - - {!isFetching && !!error ? ( -
-
- {translate('ParseModalErrorParsing')} -
-
{getErrorMessage(error)}
-
- ) : null} - - {!isFetching && title && !error && !item.parsedAlbumInfo ? ( -
- {translate('ParseModalUnableToParse')} -
- ) : null} - - {!isFetching && !error && item.parsedAlbumInfo ? ( - - ) : null} - - {title ? null : ( -
-
- {translate('ParseModalHelpText')} -
-
{translate('ParseModalHelpTextDetails')}
-
- )} -
-
- ); -} - -export default Parse; diff --git a/frontend/src/Parse/ParseModal.tsx b/frontend/src/Parse/ParseModal.tsx deleted file mode 100644 index 0ee455bf0..000000000 --- a/frontend/src/Parse/ParseModal.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react'; -import Modal from 'Components/Modal/Modal'; -import ParseModalContent from './ParseModalContent'; - -interface ParseModalProps { - isOpen: boolean; - onModalClose: () => void; -} - -function ParseModal(props: ParseModalProps) { - const { isOpen, onModalClose } = props; - - return ( - - - - ); -} - -export default ParseModal; diff --git a/frontend/src/Parse/ParseModalContent.css b/frontend/src/Parse/ParseModalContent.css deleted file mode 100644 index 43536452c..000000000 --- a/frontend/src/Parse/ParseModalContent.css +++ /dev/null @@ -1,45 +0,0 @@ -.inputContainer { - display: flex; - margin-bottom: 10px; -} - -.inputIconContainer { - width: 58px; - height: 46px; - border: 1px solid var(--inputBorderColor); - border-right: none; - border-radius: 4px; - border-top-right-radius: 0; - border-bottom-right-radius: 0; - background-color: var(--inputIconContainerBackgroundColor); - text-align: center; - line-height: 46px; -} - -.input { - composes: input from '~Components/Form/TextInput.css'; - - height: 46px; - border-radius: 0; - font-size: 18px; -} - -.clearButton { - border: 1px solid var(--inputBorderColor); - border-left: none; - border-top-right-radius: 4px; - border-bottom-right-radius: 4px; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); -} - -.message { - margin-top: 30px; - text-align: center; - font-weight: 300; - font-size: $largeFontSize; -} - -.helpText { - margin-bottom: 10px; - font-size: 24px; -} diff --git a/frontend/src/Parse/ParseModalContent.css.d.ts b/frontend/src/Parse/ParseModalContent.css.d.ts deleted file mode 100644 index 4a4def577..000000000 --- a/frontend/src/Parse/ParseModalContent.css.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -// This file is automatically generated. -// Please do not change this file! -interface CssExports { - 'clearButton': string; - 'helpText': string; - 'input': string; - 'inputContainer': string; - 'inputIconContainer': string; - 'message': string; -} -export const cssExports: CssExports; -export default cssExports; diff --git a/frontend/src/Parse/ParseModalContent.tsx b/frontend/src/Parse/ParseModalContent.tsx deleted file mode 100644 index d5ae93759..000000000 --- a/frontend/src/Parse/ParseModalContent.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import React, { useCallback, useEffect, useState } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import TextInput from 'Components/Form/TextInput'; -import Icon from 'Components/Icon'; -import Button from 'Components/Link/Button'; -import LoadingIndicator from 'Components/Loading/LoadingIndicator'; -import ModalBody from 'Components/Modal/ModalBody'; -import ModalContent from 'Components/Modal/ModalContent'; -import ModalFooter from 'Components/Modal/ModalFooter'; -import ModalHeader from 'Components/Modal/ModalHeader'; -import { icons } from 'Helpers/Props'; -import { clear, fetch } from 'Store/Actions/parseActions'; -import getErrorMessage from 'Utilities/Object/getErrorMessage'; -import translate from 'Utilities/String/translate'; -import ParseResult from './ParseResult'; -import parseStateSelector from './parseStateSelector'; -import styles from './ParseModalContent.css'; - -interface ParseModalContentProps { - onModalClose: () => void; -} - -function ParseModalContent(props: ParseModalContentProps) { - const { onModalClose } = props; - const { isFetching, error, item } = useSelector(parseStateSelector()); - - const [title, setTitle] = useState(''); - const dispatch = useDispatch(); - - const onInputChange = useCallback( - ({ value }: { value: string }) => { - const trimmedValue = value.trim(); - - setTitle(value); - - if (trimmedValue === '') { - dispatch(clear()); - } else { - dispatch(fetch({ title: trimmedValue })); - } - }, - [setTitle, dispatch] - ); - - const onClearPress = useCallback(() => { - setTitle(''); - dispatch(clear()); - }, [setTitle, dispatch]); - - useEffect( - () => { - return () => { - dispatch(clear()); - }; - }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [] - ); - - return ( - - {translate('TestParsing')} - - -
-
- -
- - - - -
- - {isFetching ? : null} - - {!isFetching && !!error ? ( -
-
- {translate('ParseModalErrorParsing')} -
-
{getErrorMessage(error)}
-
- ) : null} - - {!isFetching && title && !error && !item.parsedAlbumInfo ? ( -
- {translate('ParseModalUnableToParse')} -
- ) : null} - - {!isFetching && !error && item.parsedAlbumInfo ? ( - - ) : null} - - {title ? null : ( -
-
- {translate('ParseModalHelpText')} -
-
{translate('ParseModalHelpTextDetails')}
-
- )} -
- - - - -
- ); -} - -export default ParseModalContent; diff --git a/frontend/src/Parse/ParseResult.css b/frontend/src/Parse/ParseResult.css deleted file mode 100644 index c49c4e3fa..000000000 --- a/frontend/src/Parse/ParseResult.css +++ /dev/null @@ -1,8 +0,0 @@ -.container { - display: flex; - flex-wrap: wrap; -} - -.column { - flex: 0 0 50%; -} diff --git a/frontend/src/Parse/ParseResult.css.d.ts b/frontend/src/Parse/ParseResult.css.d.ts deleted file mode 100644 index 653368e06..000000000 --- a/frontend/src/Parse/ParseResult.css.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -// This file is automatically generated. -// Please do not change this file! -interface CssExports { - 'column': string; - 'container': string; -} -export const cssExports: CssExports; -export default cssExports; diff --git a/frontend/src/Parse/ParseResult.tsx b/frontend/src/Parse/ParseResult.tsx deleted file mode 100644 index 7e6c40d92..000000000 --- a/frontend/src/Parse/ParseResult.tsx +++ /dev/null @@ -1,160 +0,0 @@ -import _ from 'lodash'; -import moment from 'moment'; -import React from 'react'; -import AlbumFormats from 'Album/AlbumFormats'; -import AlbumTitleLink from 'Album/AlbumTitleLink'; -import { ParseModel } from 'App/State/ParseAppState'; -import ArtistNameLink from 'Artist/ArtistNameLink'; -import FieldSet from 'Components/FieldSet'; -import translate from 'Utilities/String/translate'; -import ParseResultItem from './ParseResultItem'; -import styles from './ParseResult.css'; - -interface ParseResultProps { - item: ParseModel; -} - -function ParseResult(props: ParseResultProps) { - const { item } = props; - const { customFormats, customFormatScore, albums, parsedAlbumInfo, artist } = - item; - - const { - releaseTitle, - artistName, - albumTitle, - releaseGroup, - discography, - quality, - } = parsedAlbumInfo; - - const sortedAlbums = _.sortBy(albums, (item) => - moment(item.releaseDate).unix() - ); - - return ( -
-
- - - - - - - -
- -
-
-
- -
-
-
- -
-
-
- - 1 && !quality.revision.isRepack - ? translate('True') - : '-' - } - /> - - -
- -
- 1 ? quality.revision.version : '-' - } - /> - - -
-
-
- -
- - ) : ( - '-' - ) - } - /> - - - {sortedAlbums.map((album) => { - return ( -
- -
- ); - })} -
- ) : ( - '-' - ) - } - /> - - - ) : ( - '-' - ) - } - /> - - - -
- ); -} - -export default ParseResult; diff --git a/frontend/src/Parse/ParseResultItem.css b/frontend/src/Parse/ParseResultItem.css deleted file mode 100644 index 275fe7e1f..000000000 --- a/frontend/src/Parse/ParseResultItem.css +++ /dev/null @@ -1,21 +0,0 @@ -.item { - display: flex; -} - -.title { - margin-right: 20px; - width: 250px; - text-align: right; - font-weight: bold; -} - -@media (max-width: $breakpointSmall) { - .item { - display: block; - margin-bottom: 10px; - } - - .title { - text-align: left; - } -} diff --git a/frontend/src/Parse/ParseResultItem.css.d.ts b/frontend/src/Parse/ParseResultItem.css.d.ts deleted file mode 100644 index bcf268e50..000000000 --- a/frontend/src/Parse/ParseResultItem.css.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -// This file is automatically generated. -// Please do not change this file! -interface CssExports { - 'item': string; - 'title': string; -} -export const cssExports: CssExports; -export default cssExports; diff --git a/frontend/src/Parse/ParseResultItem.tsx b/frontend/src/Parse/ParseResultItem.tsx deleted file mode 100644 index 661af448d..000000000 --- a/frontend/src/Parse/ParseResultItem.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React, { ReactNode } from 'react'; -import styles from './ParseResultItem.css'; - -interface ParseResultItemProps { - title: string; - data: string | number | ReactNode; -} - -function ParseResultItem(props: ParseResultItemProps) { - const { title, data } = props; - - return ( -
-
{title}
-
{data}
-
- ); -} - -export default ParseResultItem; diff --git a/frontend/src/Parse/ParseToolbarButton.tsx b/frontend/src/Parse/ParseToolbarButton.tsx deleted file mode 100644 index 43b8b959f..000000000 --- a/frontend/src/Parse/ParseToolbarButton.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import React, { Fragment, useCallback, useState } from 'react'; -import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton'; -import { icons } from 'Helpers/Props'; -import ParseModal from 'Parse/ParseModal'; -import translate from 'Utilities/String/translate'; - -function ParseToolbarButton() { - const [isParseModalOpen, setIsParseModalOpen] = useState(false); - - const onOpenParseModalPress = useCallback(() => { - setIsParseModalOpen(true); - }, [setIsParseModalOpen]); - - const onParseModalClose = useCallback(() => { - setIsParseModalOpen(false); - }, [setIsParseModalOpen]); - - return ( - - - - - - ); -} - -export default ParseToolbarButton; diff --git a/frontend/src/Parse/parseStateSelector.ts b/frontend/src/Parse/parseStateSelector.ts deleted file mode 100644 index 7abcfeca1..000000000 --- a/frontend/src/Parse/parseStateSelector.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { createSelector } from 'reselect'; -import AppState from 'App/State/AppState'; -import ParseAppState from 'App/State/ParseAppState'; - -export default function parseStateSelector() { - return createSelector( - (state: AppState) => state.parse, - (parse: ParseAppState) => { - return parse; - } - ); -} diff --git a/frontend/src/Search/AddNewItem.js b/frontend/src/Search/AddNewItem.js index e335ef4c2..5ec065149 100644 --- a/frontend/src/Search/AddNewItem.js +++ b/frontend/src/Search/AddNewItem.js @@ -1,6 +1,5 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; -import Alert from 'Components/Alert'; import TextInput from 'Components/Form/TextInput'; import Icon from 'Components/Icon'; import Button from 'Components/Link/Button'; @@ -131,8 +130,7 @@ class AddNewItem extends Component {
{translate('FailedLoadingSearchResults')}
- - {getErrorMessage(error)} +
{getErrorMessage(error)}
: null } diff --git a/frontend/src/Settings/CustomFormats/CustomFormatSettingsConnector.js b/frontend/src/Settings/CustomFormats/CustomFormatSettingsConnector.js new file mode 100644 index 000000000..342df29d2 --- /dev/null +++ b/frontend/src/Settings/CustomFormats/CustomFormatSettingsConnector.js @@ -0,0 +1,33 @@ +import React, { Component } from 'react'; +import { DndProvider } from 'react-dnd'; +import { HTML5Backend } from 'react-dnd-html5-backend'; +import PageContent from 'Components/Page/PageContent'; +import PageContentBody from 'Components/Page/PageContentBody'; +import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector'; +import translate from 'Utilities/String/translate'; +import CustomFormatsConnector from './CustomFormats/CustomFormatsConnector'; + +class CustomFormatSettingsConnector extends Component { + + // + // Render + + render() { + return ( + + + + + + + + + + ); + } +} + +export default CustomFormatSettingsConnector; + diff --git a/frontend/src/Settings/CustomFormats/CustomFormatSettingsPage.tsx b/frontend/src/Settings/CustomFormats/CustomFormatSettingsPage.tsx deleted file mode 100644 index 66c208f9a..000000000 --- a/frontend/src/Settings/CustomFormats/CustomFormatSettingsPage.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import React from 'react'; -import { DndProvider } from 'react-dnd'; -import { HTML5Backend } from 'react-dnd-html5-backend'; -import PageContent from 'Components/Page/PageContent'; -import PageContentBody from 'Components/Page/PageContentBody'; -import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator'; -import ParseToolbarButton from 'Parse/ParseToolbarButton'; -import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector'; -import translate from 'Utilities/String/translate'; -import CustomFormatsConnector from './CustomFormats/CustomFormatsConnector'; -import ManageCustomFormatsToolbarButton from './CustomFormats/Manage/ManageCustomFormatsToolbarButton'; - -function CustomFormatSettingsPage() { - return ( - - - - - - - - - } - /> - - - {/* TODO: Upgrade react-dnd to get typings, we're 2 major versions behind */} - {/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */} - {/* @ts-ignore */} - - {/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */} - {/* @ts-ignore */} - - - - - ); -} - -export default CustomFormatSettingsPage; diff --git a/frontend/src/Settings/CustomFormats/CustomFormats/CustomFormatsConnector.js b/frontend/src/Settings/CustomFormats/CustomFormats/CustomFormatsConnector.js index 0417d9b21..8e828620b 100644 --- a/frontend/src/Settings/CustomFormats/CustomFormats/CustomFormatsConnector.js +++ b/frontend/src/Settings/CustomFormats/CustomFormats/CustomFormatsConnector.js @@ -4,12 +4,12 @@ import { connect } from 'react-redux'; import { createSelector } from 'reselect'; import { cloneCustomFormat, deleteCustomFormat, fetchCustomFormats } from 'Store/Actions/settingsActions'; import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector'; -import sortByProp from 'Utilities/Array/sortByProp'; +import sortByName from 'Utilities/Array/sortByName'; import CustomFormats from './CustomFormats'; function createMapStateToProps() { return createSelector( - createSortedSectionSelector('settings.customFormats', sortByProp('name')), + createSortedSectionSelector('settings.customFormats', sortByName), (customFormats) => customFormats ); } diff --git a/frontend/src/Settings/CustomFormats/CustomFormats/EditCustomFormatModalConnector.js b/frontend/src/Settings/CustomFormats/CustomFormats/EditCustomFormatModalConnector.js index 3e79425cd..52b2f09f6 100644 --- a/frontend/src/Settings/CustomFormats/CustomFormats/EditCustomFormatModalConnector.js +++ b/frontend/src/Settings/CustomFormats/CustomFormats/EditCustomFormatModalConnector.js @@ -3,7 +3,6 @@ import React, { Component } from 'react'; import { connect } from 'react-redux'; import { clearPendingChanges } from 'Store/Actions/baseActions'; import EditCustomFormatModal from './EditCustomFormatModal'; -import EditCustomFormatModalContentConnector from './EditCustomFormatModalContentConnector'; function mapStateToProps() { return {}; @@ -37,7 +36,6 @@ class EditCustomFormatModalConnector extends Component { } EditCustomFormatModalConnector.propTypes = { - ...EditCustomFormatModalContentConnector.propTypes, onModalClose: PropTypes.func.isRequired, clearPendingChanges: PropTypes.func.isRequired }; diff --git a/frontend/src/Settings/CustomFormats/CustomFormats/EditCustomFormatModalContent.css b/frontend/src/Settings/CustomFormats/CustomFormats/EditCustomFormatModalContent.css index 24830ef42..b7d3da255 100644 --- a/frontend/src/Settings/CustomFormats/CustomFormats/EditCustomFormatModalContent.css +++ b/frontend/src/Settings/CustomFormats/CustomFormats/EditCustomFormatModalContent.css @@ -25,8 +25,3 @@ border-radius: 4px; background-color: var(--cardCenterBackgroundColor); } - -.customFormats { - display: flex; - flex-wrap: wrap; -} diff --git a/frontend/src/Settings/CustomFormats/CustomFormats/EditCustomFormatModalContent.css.d.ts b/frontend/src/Settings/CustomFormats/CustomFormats/EditCustomFormatModalContent.css.d.ts index 1aab6062e..1339caf02 100644 --- a/frontend/src/Settings/CustomFormats/CustomFormats/EditCustomFormatModalContent.css.d.ts +++ b/frontend/src/Settings/CustomFormats/CustomFormats/EditCustomFormatModalContent.css.d.ts @@ -3,7 +3,6 @@ interface CssExports { 'addSpecification': string; 'center': string; - 'customFormats': string; 'deleteButton': string; 'rightButtons': string; } diff --git a/frontend/src/Settings/CustomFormats/CustomFormats/EditCustomFormatModalContent.js b/frontend/src/Settings/CustomFormats/CustomFormats/EditCustomFormatModalContent.js index 8b06deb4b..4d8f0fd4b 100644 --- a/frontend/src/Settings/CustomFormats/CustomFormats/EditCustomFormatModalContent.js +++ b/frontend/src/Settings/CustomFormats/CustomFormats/EditCustomFormatModalContent.js @@ -1,6 +1,5 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; -import Alert from 'Components/Alert'; import Card from 'Components/Card'; import FieldSet from 'Components/FieldSet'; import Form from 'Components/Form/Form'; @@ -151,11 +150,6 @@ class EditCustomFormatModalContent extends Component {
- -
- {translate('CustomFormatsSettingsTriggerInfo')} -
-
{ specifications.map((tag) => { diff --git a/frontend/src/Settings/CustomFormats/CustomFormats/Manage/Edit/ManageCustomFormatsEditModal.tsx b/frontend/src/Settings/CustomFormats/CustomFormats/Manage/Edit/ManageCustomFormatsEditModal.tsx deleted file mode 100644 index 3ff5cfa37..000000000 --- a/frontend/src/Settings/CustomFormats/CustomFormats/Manage/Edit/ManageCustomFormatsEditModal.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react'; -import Modal from 'Components/Modal/Modal'; -import ManageCustomFormatsEditModalContent from './ManageCustomFormatsEditModalContent'; - -interface ManageCustomFormatsEditModalProps { - isOpen: boolean; - customFormatIds: number[]; - onSavePress(payload: object): void; - onModalClose(): void; -} - -function ManageCustomFormatsEditModal( - props: ManageCustomFormatsEditModalProps -) { - const { isOpen, customFormatIds, onSavePress, onModalClose } = props; - - return ( - - - - ); -} - -export default ManageCustomFormatsEditModal; diff --git a/frontend/src/Settings/CustomFormats/CustomFormats/Manage/Edit/ManageCustomFormatsEditModalContent.css b/frontend/src/Settings/CustomFormats/CustomFormats/Manage/Edit/ManageCustomFormatsEditModalContent.css deleted file mode 100644 index ea406894e..000000000 --- a/frontend/src/Settings/CustomFormats/CustomFormats/Manage/Edit/ManageCustomFormatsEditModalContent.css +++ /dev/null @@ -1,16 +0,0 @@ -.modalFooter { - composes: modalFooter from '~Components/Modal/ModalFooter.css'; - - justify-content: space-between; -} - -.selected { - font-weight: bold; -} - -@media only screen and (max-width: $breakpointExtraSmall) { - .modalFooter { - flex-direction: column; - gap: 10px; - } -} diff --git a/frontend/src/Settings/CustomFormats/CustomFormats/Manage/Edit/ManageCustomFormatsEditModalContent.css.d.ts b/frontend/src/Settings/CustomFormats/CustomFormats/Manage/Edit/ManageCustomFormatsEditModalContent.css.d.ts deleted file mode 100644 index cbf2d6328..000000000 --- a/frontend/src/Settings/CustomFormats/CustomFormats/Manage/Edit/ManageCustomFormatsEditModalContent.css.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -// This file is automatically generated. -// Please do not change this file! -interface CssExports { - 'modalFooter': string; - 'selected': string; -} -export const cssExports: CssExports; -export default cssExports; diff --git a/frontend/src/Settings/CustomFormats/CustomFormats/Manage/Edit/ManageCustomFormatsEditModalContent.tsx b/frontend/src/Settings/CustomFormats/CustomFormats/Manage/Edit/ManageCustomFormatsEditModalContent.tsx deleted file mode 100644 index 25a2f85c2..000000000 --- a/frontend/src/Settings/CustomFormats/CustomFormats/Manage/Edit/ManageCustomFormatsEditModalContent.tsx +++ /dev/null @@ -1,125 +0,0 @@ -import React, { useCallback, useState } from 'react'; -import FormGroup from 'Components/Form/FormGroup'; -import FormInputGroup from 'Components/Form/FormInputGroup'; -import FormLabel from 'Components/Form/FormLabel'; -import Button from 'Components/Link/Button'; -import ModalBody from 'Components/Modal/ModalBody'; -import ModalContent from 'Components/Modal/ModalContent'; -import ModalFooter from 'Components/Modal/ModalFooter'; -import ModalHeader from 'Components/Modal/ModalHeader'; -import { inputTypes } from 'Helpers/Props'; -import translate from 'Utilities/String/translate'; -import styles from './ManageCustomFormatsEditModalContent.css'; - -interface SavePayload { - includeCustomFormatWhenRenaming?: boolean; -} - -interface ManageCustomFormatsEditModalContentProps { - customFormatIds: number[]; - onSavePress(payload: object): void; - onModalClose(): void; -} - -const NO_CHANGE = 'noChange'; - -const enableOptions = [ - { - key: NO_CHANGE, - get value() { - return translate('NoChange'); - }, - isDisabled: true, - }, - { - key: 'enabled', - get value() { - return translate('Enabled'); - }, - }, - { - key: 'disabled', - get value() { - return translate('Disabled'); - }, - }, -]; - -function ManageCustomFormatsEditModalContent( - props: ManageCustomFormatsEditModalContentProps -) { - const { customFormatIds, onSavePress, onModalClose } = props; - - const [includeCustomFormatWhenRenaming, setIncludeCustomFormatWhenRenaming] = - useState(NO_CHANGE); - - const save = useCallback(() => { - let hasChanges = false; - const payload: SavePayload = {}; - - if (includeCustomFormatWhenRenaming !== NO_CHANGE) { - hasChanges = true; - payload.includeCustomFormatWhenRenaming = - includeCustomFormatWhenRenaming === 'enabled'; - } - - if (hasChanges) { - onSavePress(payload); - } - - onModalClose(); - }, [includeCustomFormatWhenRenaming, onSavePress, onModalClose]); - - const onInputChange = useCallback( - ({ name, value }: { name: string; value: string }) => { - switch (name) { - case 'includeCustomFormatWhenRenaming': - setIncludeCustomFormatWhenRenaming(value); - break; - default: - console.warn( - `EditCustomFormatsModalContent Unknown Input: '${name}'` - ); - } - }, - [] - ); - - const selectedCount = customFormatIds.length; - - return ( - - {translate('EditSelectedCustomFormats')} - - - - {translate('IncludeCustomFormatWhenRenaming')} - - - - - - -
- {translate('CountCustomFormatsSelected', { - count: selectedCount, - })} -
- -
- - - -
-
-
- ); -} - -export default ManageCustomFormatsEditModalContent; diff --git a/frontend/src/Settings/CustomFormats/CustomFormats/Manage/ManageCustomFormatsModal.tsx b/frontend/src/Settings/CustomFormats/CustomFormats/Manage/ManageCustomFormatsModal.tsx deleted file mode 100644 index dd3456437..000000000 --- a/frontend/src/Settings/CustomFormats/CustomFormats/Manage/ManageCustomFormatsModal.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react'; -import Modal from 'Components/Modal/Modal'; -import ManageCustomFormatsModalContent from './ManageCustomFormatsModalContent'; - -interface ManageCustomFormatsModalProps { - isOpen: boolean; - onModalClose(): void; -} - -function ManageCustomFormatsModal(props: ManageCustomFormatsModalProps) { - const { isOpen, onModalClose } = props; - - return ( - - - - ); -} - -export default ManageCustomFormatsModal; diff --git a/frontend/src/Settings/CustomFormats/CustomFormats/Manage/ManageCustomFormatsModalContent.css b/frontend/src/Settings/CustomFormats/CustomFormats/Manage/ManageCustomFormatsModalContent.css deleted file mode 100644 index 6ea04a0c8..000000000 --- a/frontend/src/Settings/CustomFormats/CustomFormats/Manage/ManageCustomFormatsModalContent.css +++ /dev/null @@ -1,16 +0,0 @@ -.leftButtons, -.rightButtons { - display: flex; - flex: 1 0 50%; - flex-wrap: wrap; -} - -.rightButtons { - justify-content: flex-end; -} - -.deleteButton { - composes: button from '~Components/Link/Button.css'; - - margin-right: 10px; -} diff --git a/frontend/src/Settings/CustomFormats/CustomFormats/Manage/ManageCustomFormatsModalContent.css.d.ts b/frontend/src/Settings/CustomFormats/CustomFormats/Manage/ManageCustomFormatsModalContent.css.d.ts deleted file mode 100644 index 7b392fff9..000000000 --- a/frontend/src/Settings/CustomFormats/CustomFormats/Manage/ManageCustomFormatsModalContent.css.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -// This file is automatically generated. -// Please do not change this file! -interface CssExports { - 'deleteButton': string; - 'leftButtons': string; - 'rightButtons': string; -} -export const cssExports: CssExports; -export default cssExports; diff --git a/frontend/src/Settings/CustomFormats/CustomFormats/Manage/ManageCustomFormatsModalContent.tsx b/frontend/src/Settings/CustomFormats/CustomFormats/Manage/ManageCustomFormatsModalContent.tsx deleted file mode 100644 index aabaf67c1..000000000 --- a/frontend/src/Settings/CustomFormats/CustomFormats/Manage/ManageCustomFormatsModalContent.tsx +++ /dev/null @@ -1,244 +0,0 @@ -import React, { useCallback, useMemo, useState } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { CustomFormatAppState } from 'App/State/SettingsAppState'; -import Alert from 'Components/Alert'; -import Button from 'Components/Link/Button'; -import SpinnerButton from 'Components/Link/SpinnerButton'; -import LoadingIndicator from 'Components/Loading/LoadingIndicator'; -import ConfirmModal from 'Components/Modal/ConfirmModal'; -import ModalBody from 'Components/Modal/ModalBody'; -import ModalContent from 'Components/Modal/ModalContent'; -import ModalFooter from 'Components/Modal/ModalFooter'; -import ModalHeader from 'Components/Modal/ModalHeader'; -import Column from 'Components/Table/Column'; -import Table from 'Components/Table/Table'; -import TableBody from 'Components/Table/TableBody'; -import useSelectState from 'Helpers/Hooks/useSelectState'; -import { kinds } from 'Helpers/Props'; -import { - bulkDeleteCustomFormats, - bulkEditCustomFormats, - setManageCustomFormatsSort, -} from 'Store/Actions/settingsActions'; -import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector'; -import { SelectStateInputProps } from 'typings/props'; -import getErrorMessage from 'Utilities/Object/getErrorMessage'; -import translate from 'Utilities/String/translate'; -import getSelectedIds from 'Utilities/Table/getSelectedIds'; -import ManageCustomFormatsEditModal from './Edit/ManageCustomFormatsEditModal'; -import ManageCustomFormatsModalRow from './ManageCustomFormatsModalRow'; -import styles from './ManageCustomFormatsModalContent.css'; - -// TODO: This feels janky to do, but not sure of a better way currently -type OnSelectedChangeCallback = React.ComponentProps< - typeof ManageCustomFormatsModalRow ->['onSelectedChange']; - -const COLUMNS: Column[] = [ - { - name: 'name', - label: () => translate('Name'), - isSortable: true, - isVisible: true, - }, - { - name: 'includeCustomFormatWhenRenaming', - label: () => translate('IncludeCustomFormatWhenRenaming'), - isSortable: true, - isVisible: true, - }, - { - name: 'actions', - label: '', - isVisible: true, - }, -]; - -interface ManageCustomFormatsModalContentProps { - onModalClose(): void; -} - -function ManageCustomFormatsModalContent( - props: ManageCustomFormatsModalContentProps -) { - const { onModalClose } = props; - - const { - isFetching, - isPopulated, - isDeleting, - isSaving, - error, - items, - sortKey, - sortDirection, - }: CustomFormatAppState = useSelector( - createClientSideCollectionSelector('settings.customFormats') - ); - const dispatch = useDispatch(); - - const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); - const [isEditModalOpen, setIsEditModalOpen] = useState(false); - - const [selectState, setSelectState] = useSelectState(); - - const { allSelected, allUnselected, selectedState } = selectState; - - const selectedIds: number[] = useMemo(() => { - return getSelectedIds(selectedState); - }, [selectedState]); - - const selectedCount = selectedIds.length; - - const onSortPress = useCallback( - (value: string) => { - dispatch(setManageCustomFormatsSort({ sortKey: value })); - }, - [dispatch] - ); - - const onDeletePress = useCallback(() => { - setIsDeleteModalOpen(true); - }, [setIsDeleteModalOpen]); - - const onDeleteModalClose = useCallback(() => { - setIsDeleteModalOpen(false); - }, [setIsDeleteModalOpen]); - - const onEditPress = useCallback(() => { - setIsEditModalOpen(true); - }, [setIsEditModalOpen]); - - const onEditModalClose = useCallback(() => { - setIsEditModalOpen(false); - }, [setIsEditModalOpen]); - - const onConfirmDelete = useCallback(() => { - dispatch(bulkDeleteCustomFormats({ ids: selectedIds })); - setIsDeleteModalOpen(false); - }, [selectedIds, dispatch]); - - const onSavePress = useCallback( - (payload: object) => { - setIsEditModalOpen(false); - - dispatch( - bulkEditCustomFormats({ - ids: selectedIds, - ...payload, - }) - ); - }, - [selectedIds, dispatch] - ); - - const onSelectAllChange = useCallback( - ({ value }: SelectStateInputProps) => { - setSelectState({ type: value ? 'selectAll' : 'unselectAll', items }); - }, - [items, setSelectState] - ); - - const onSelectedChange = useCallback( - ({ id, value, shiftKey = false }) => { - setSelectState({ - type: 'toggleSelected', - items, - id, - isSelected: value, - shiftKey, - }); - }, - [items, setSelectState] - ); - - const errorMessage = getErrorMessage(error, 'Unable to load custom formats.'); - const anySelected = selectedCount > 0; - - return ( - - {translate('ManageCustomFormats')} - - {isFetching ? : null} - - {error ?
{errorMessage}
: null} - - {isPopulated && !error && !items.length ? ( - {translate('NoCustomFormatsFound')} - ) : null} - - {isPopulated && !!items.length && !isFetching && !isFetching ? ( - - - {items.map((item) => { - return ( - - ); - })} - -
- ) : null} -
- - -
- - {translate('Delete')} - - - - {translate('Edit')} - -
- - -
- - - - -
- ); -} - -export default ManageCustomFormatsModalContent; diff --git a/frontend/src/Settings/CustomFormats/CustomFormats/Manage/ManageCustomFormatsModalRow.css b/frontend/src/Settings/CustomFormats/CustomFormats/Manage/ManageCustomFormatsModalRow.css deleted file mode 100644 index 355c70378..000000000 --- a/frontend/src/Settings/CustomFormats/CustomFormats/Manage/ManageCustomFormatsModalRow.css +++ /dev/null @@ -1,12 +0,0 @@ -.name, -.includeCustomFormatWhenRenaming { - composes: cell from '~Components/Table/Cells/TableRowCell.css'; - - word-break: break-all; -} - -.actions { - composes: cell from '~Components/Table/Cells/TableRowCell.css'; - - width: 40px; -} diff --git a/frontend/src/Settings/CustomFormats/CustomFormats/Manage/ManageCustomFormatsModalRow.css.d.ts b/frontend/src/Settings/CustomFormats/CustomFormats/Manage/ManageCustomFormatsModalRow.css.d.ts deleted file mode 100644 index d1719edd8..000000000 --- a/frontend/src/Settings/CustomFormats/CustomFormats/Manage/ManageCustomFormatsModalRow.css.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -// This file is automatically generated. -// Please do not change this file! -interface CssExports { - 'actions': string; - 'includeCustomFormatWhenRenaming': string; - 'name': string; -} -export const cssExports: CssExports; -export default cssExports; diff --git a/frontend/src/Settings/CustomFormats/CustomFormats/Manage/ManageCustomFormatsModalRow.tsx b/frontend/src/Settings/CustomFormats/CustomFormats/Manage/ManageCustomFormatsModalRow.tsx deleted file mode 100644 index 57bb7fda0..000000000 --- a/frontend/src/Settings/CustomFormats/CustomFormats/Manage/ManageCustomFormatsModalRow.tsx +++ /dev/null @@ -1,126 +0,0 @@ -import React, { useCallback, useState } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { createSelector } from 'reselect'; -import AppState from 'App/State/AppState'; -import IconButton from 'Components/Link/IconButton'; -import ConfirmModal from 'Components/Modal/ConfirmModal'; -import TableRowCell from 'Components/Table/Cells/TableRowCell'; -import TableSelectCell from 'Components/Table/Cells/TableSelectCell'; -import Column from 'Components/Table/Column'; -import TableRow from 'Components/Table/TableRow'; -import { icons } from 'Helpers/Props'; -import { deleteCustomFormat } from 'Store/Actions/settingsActions'; -import { SelectStateInputProps } from 'typings/props'; -import translate from 'Utilities/String/translate'; -import EditCustomFormatModalConnector from '../EditCustomFormatModalConnector'; -import styles from './ManageCustomFormatsModalRow.css'; - -interface ManageCustomFormatsModalRowProps { - id: number; - name: string; - includeCustomFormatWhenRenaming: boolean; - columns: Column[]; - isSelected?: boolean; - onSelectedChange(result: SelectStateInputProps): void; -} - -function isDeletingSelector() { - return createSelector( - (state: AppState) => state.settings.customFormats.isDeleting, - (isDeleting) => { - return isDeleting; - } - ); -} - -function ManageCustomFormatsModalRow(props: ManageCustomFormatsModalRowProps) { - const { - id, - isSelected, - name, - includeCustomFormatWhenRenaming, - onSelectedChange, - } = props; - - const dispatch = useDispatch(); - const isDeleting = useSelector(isDeletingSelector()); - - const [isEditCustomFormatModalOpen, setIsEditCustomFormatModalOpen] = - useState(false); - - const [isDeleteCustomFormatModalOpen, setIsDeleteCustomFormatModalOpen] = - useState(false); - - const handlelectedChange = useCallback( - (result: SelectStateInputProps) => { - onSelectedChange({ - ...result, - }); - }, - [onSelectedChange] - ); - - const handleEditCustomFormatModalOpen = useCallback(() => { - setIsEditCustomFormatModalOpen(true); - }, [setIsEditCustomFormatModalOpen]); - - const handleEditCustomFormatModalClose = useCallback(() => { - setIsEditCustomFormatModalOpen(false); - }, [setIsEditCustomFormatModalOpen]); - - const handleDeleteCustomFormatPress = useCallback(() => { - setIsEditCustomFormatModalOpen(false); - setIsDeleteCustomFormatModalOpen(true); - }, [setIsEditCustomFormatModalOpen, setIsDeleteCustomFormatModalOpen]); - - const handleDeleteCustomFormatModalClose = useCallback(() => { - setIsDeleteCustomFormatModalOpen(false); - }, [setIsDeleteCustomFormatModalOpen]); - - const handleConfirmDeleteCustomFormat = useCallback(() => { - dispatch(deleteCustomFormat({ id })); - }, [id, dispatch]); - - return ( - - - - {name} - - - {includeCustomFormatWhenRenaming ? translate('Yes') : translate('No')} - - - - - - - - - - - ); -} - -export default ManageCustomFormatsModalRow; diff --git a/frontend/src/Settings/CustomFormats/CustomFormats/Manage/ManageCustomFormatsToolbarButton.tsx b/frontend/src/Settings/CustomFormats/CustomFormats/Manage/ManageCustomFormatsToolbarButton.tsx deleted file mode 100644 index 91f41dc44..000000000 --- a/frontend/src/Settings/CustomFormats/CustomFormats/Manage/ManageCustomFormatsToolbarButton.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react'; -import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton'; -import useModalOpenState from 'Helpers/Hooks/useModalOpenState'; -import { icons } from 'Helpers/Props'; -import translate from 'Utilities/String/translate'; -import ManageCustomFormatsModal from './ManageCustomFormatsModal'; - -function ManageCustomFormatsToolbarButton() { - const [isManageModalOpen, openManageModal, closeManageModal] = - useModalOpenState(false); - - return ( - <> - - - - - ); -} - -export default ManageCustomFormatsToolbarButton; diff --git a/frontend/src/Settings/CustomFormats/CustomFormats/Specifications/EditSpecificationModalContent.js b/frontend/src/Settings/CustomFormats/CustomFormats/Specifications/EditSpecificationModalContent.js index 19aae4694..02a31eda7 100644 --- a/frontend/src/Settings/CustomFormats/CustomFormats/Specifications/EditSpecificationModalContent.js +++ b/frontend/src/Settings/CustomFormats/CustomFormats/Specifications/EditSpecificationModalContent.js @@ -7,8 +7,8 @@ import FormInputGroup from 'Components/Form/FormInputGroup'; import FormLabel from 'Components/Form/FormLabel'; import ProviderFieldFormGroup from 'Components/Form/ProviderFieldFormGroup'; import Button from 'Components/Link/Button'; +import Link from 'Components/Link/Link'; import SpinnerErrorButton from 'Components/Link/SpinnerErrorButton'; -import InlineMarkdown from 'Components/Markdown/InlineMarkdown'; import ModalBody from 'Components/Modal/ModalBody'; import ModalContent from 'Components/Modal/ModalContent'; import ModalFooter from 'Components/Modal/ModalFooter'; @@ -52,13 +52,12 @@ function EditSpecificationModalContent(props) { fields && fields.some((x) => x.label === translate('CustomFormatsSpecificationRegularExpression')) &&
- +
\\^$.|?*+()[{ have special meanings and need escaping with a \\' }} /> + {'More details'} {'Here'}
- -
-
- + {'Regular expressions can be tested '} + Here
} diff --git a/frontend/src/Settings/DownloadClients/DownloadClients/DownloadClientsConnector.js b/frontend/src/Settings/DownloadClients/DownloadClients/DownloadClientsConnector.js index 0dc410fcb..d9e543469 100644 --- a/frontend/src/Settings/DownloadClients/DownloadClients/DownloadClientsConnector.js +++ b/frontend/src/Settings/DownloadClients/DownloadClients/DownloadClientsConnector.js @@ -5,12 +5,12 @@ import { createSelector } from 'reselect'; import { deleteDownloadClient, fetchDownloadClients } from 'Store/Actions/settingsActions'; import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector'; import createTagsSelector from 'Store/Selectors/createTagsSelector'; -import sortByProp from 'Utilities/Array/sortByProp'; +import sortByName from 'Utilities/Array/sortByName'; import DownloadClients from './DownloadClients'; function createMapStateToProps() { return createSelector( - createSortedSectionSelector('settings.downloadClients', sortByProp('name')), + createSortedSectionSelector('settings.downloadClients', sortByName), createTagsSelector(), (downloadClients, tagList) => { return { diff --git a/frontend/src/Settings/DownloadClients/DownloadClients/EditDownloadClientModalContent.js b/frontend/src/Settings/DownloadClients/DownloadClients/EditDownloadClientModalContent.js index 8d7d994f7..d3d51c7fc 100644 --- a/frontend/src/Settings/DownloadClients/DownloadClients/EditDownloadClientModalContent.js +++ b/frontend/src/Settings/DownloadClients/DownloadClients/EditDownloadClientModalContent.js @@ -15,7 +15,6 @@ import ModalContent from 'Components/Modal/ModalContent'; import ModalFooter from 'Components/Modal/ModalFooter'; import ModalHeader from 'Components/Modal/ModalHeader'; import { inputTypes, kinds, sizes } from 'Helpers/Props'; -import AdvancedSettingsButton from 'Settings/AdvancedSettingsButton'; import translate from 'Utilities/String/translate'; import styles from './EditDownloadClientModalContent.css'; @@ -38,7 +37,6 @@ class EditDownloadClientModalContent extends Component { onModalClose, onSavePress, onTestPress, - onAdvancedSettingsPress, onDeleteDownloadClientPress, ...otherProps } = this.props; @@ -208,12 +206,6 @@ class EditDownloadClientModalContent extends Component { } - - { - this.props.toggleAdvancedSettings(); - }; - // // Render @@ -76,7 +65,6 @@ class EditDownloadClientModalContentConnector extends Component { {...this.props} onSavePress={this.onSavePress} onTestPress={this.onTestPress} - onAdvancedSettingsPress={this.onAdvancedSettingsPress} onInputChange={this.onInputChange} onFieldChange={this.onFieldChange} /> @@ -94,7 +82,6 @@ EditDownloadClientModalContentConnector.propTypes = { setDownloadClientFieldValue: PropTypes.func.isRequired, saveDownloadClient: PropTypes.func.isRequired, testDownloadClient: PropTypes.func.isRequired, - toggleAdvancedSettings: PropTypes.func.isRequired, onModalClose: PropTypes.func.isRequired }; diff --git a/frontend/src/Settings/DownloadClients/DownloadClients/Manage/Edit/ManageDownloadClientsEditModalContent.tsx b/frontend/src/Settings/DownloadClients/DownloadClients/Manage/Edit/ManageDownloadClientsEditModalContent.tsx index 7599cb9b0..18ae5170a 100644 --- a/frontend/src/Settings/DownloadClients/DownloadClients/Manage/Edit/ManageDownloadClientsEditModalContent.tsx +++ b/frontend/src/Settings/DownloadClients/DownloadClients/Manage/Edit/ManageDownloadClientsEditModalContent.tsx @@ -32,7 +32,7 @@ const enableOptions = [ get value() { return translate('NoChange'); }, - isDisabled: true, + disabled: true, }, { key: 'enabled', diff --git a/frontend/src/Settings/DownloadClients/DownloadClients/Manage/ManageDownloadClientsModalContent.css b/frontend/src/Settings/DownloadClients/DownloadClients/Manage/ManageDownloadClientsModalContent.css index 6ea04a0c8..c106388ab 100644 --- a/frontend/src/Settings/DownloadClients/DownloadClients/Manage/ManageDownloadClientsModalContent.css +++ b/frontend/src/Settings/DownloadClients/DownloadClients/Manage/ManageDownloadClientsModalContent.css @@ -13,4 +13,4 @@ composes: button from '~Components/Link/Button.css'; margin-right: 10px; -} +} \ No newline at end of file diff --git a/frontend/src/Settings/DownloadClients/DownloadClients/Manage/ManageDownloadClientsModalContent.tsx b/frontend/src/Settings/DownloadClients/DownloadClients/Manage/ManageDownloadClientsModalContent.tsx index b2c1208cb..2722f02fa 100644 --- a/frontend/src/Settings/DownloadClients/DownloadClients/Manage/ManageDownloadClientsModalContent.tsx +++ b/frontend/src/Settings/DownloadClients/DownloadClients/Manage/ManageDownloadClientsModalContent.tsx @@ -10,11 +10,11 @@ import ModalBody from 'Components/Modal/ModalBody'; import ModalContent from 'Components/Modal/ModalContent'; import ModalFooter from 'Components/Modal/ModalFooter'; import ModalHeader from 'Components/Modal/ModalHeader'; -import Column from 'Components/Table/Column'; import Table from 'Components/Table/Table'; import TableBody from 'Components/Table/TableBody'; import useSelectState from 'Helpers/Hooks/useSelectState'; import { kinds } from 'Helpers/Props'; +import SortDirection from 'Helpers/Props/SortDirection'; import { bulkDeleteDownloadClients, bulkEditDownloadClients, @@ -35,7 +35,7 @@ type OnSelectedChangeCallback = React.ComponentProps< typeof ManageDownloadClientsModalRow >['onSelectedChange']; -const COLUMNS: Column[] = [ +const COLUMNS = [ { name: 'name', label: () => translate('Name'), @@ -82,6 +82,8 @@ const COLUMNS: Column[] = [ interface ManageDownloadClientsModalContentProps { onModalClose(): void; + sortKey?: string; + sortDirection?: SortDirection; } function ManageDownloadClientsModalContent( @@ -218,9 +220,9 @@ function ManageDownloadClientsModalContent( {error ?
{errorMessage}
: null} - {isPopulated && !error && !items.length ? ( + {isPopulated && !error && !items.length && ( {translate('NoDownloadClientsFound')} - ) : null} + )} {isPopulated && !!items.length && !isFetching && !isFetching ? ( diff --git a/frontend/src/Settings/General/LoggingSettings.js b/frontend/src/Settings/General/LoggingSettings.js index 043867853..93918a3d2 100644 --- a/frontend/src/Settings/General/LoggingSettings.js +++ b/frontend/src/Settings/General/LoggingSettings.js @@ -15,14 +15,12 @@ const logLevelOptions = [ function LoggingSettings(props) { const { - advancedSettings, settings, onInputChange } = props; const { - logLevel, - logSizeLimit + logLevel } = settings; return ( @@ -41,30 +39,11 @@ function LoggingSettings(props) { {...logLevel} /> - - - {translate('LogSizeLimit')} - - - ); } LoggingSettings.propTypes = { - advancedSettings: PropTypes.bool.isRequired, settings: PropTypes.object.isRequired, onInputChange: PropTypes.func.isRequired }; diff --git a/frontend/src/Settings/General/UpdateSettings.js b/frontend/src/Settings/General/UpdateSettings.js index a151423e5..081f5dda2 100644 --- a/frontend/src/Settings/General/UpdateSettings.js +++ b/frontend/src/Settings/General/UpdateSettings.js @@ -18,6 +18,7 @@ function UpdateSettings(props) { const { advancedSettings, settings, + isWindows, packageUpdateMechanism, onInputChange } = props; @@ -43,10 +44,10 @@ function UpdateSettings(props) { value: titleCase(packageUpdateMechanism) }); } else { - updateOptions.push({ key: 'builtIn', value: translate('BuiltIn') }); + updateOptions.push({ key: 'builtIn', value: 'Built-In' }); } - updateOptions.push({ key: 'script', value: translate('Script') }); + updateOptions.push({ key: 'script', value: 'Script' }); return (
@@ -68,59 +69,62 @@ function UpdateSettings(props) { /> -
- - {translate('Automatic')} + { + !isWindows && +
+ + {translate('Automatic')} - - + + - - {translate('Mechanism')} - - - - - { - updateMechanism.value === 'script' && - {translate('ScriptPath')} + {translate('Mechanism')} - } -
+ + { + updateMechanism.value === 'script' && + + {translate('ScriptPath')} + + + + } +
+ }
); } diff --git a/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js b/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js index d50fb2385..ed9582e91 100644 --- a/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js +++ b/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js @@ -20,7 +20,6 @@ import ModalFooter from 'Components/Modal/ModalFooter'; import ModalHeader from 'Components/Modal/ModalHeader'; import Popover from 'Components/Tooltip/Popover'; import { icons, inputTypes, kinds, tooltipPositions } from 'Helpers/Props'; -import AdvancedSettingsButton from 'Settings/AdvancedSettingsButton'; import formatShortTimeSpan from 'Utilities/Date/formatShortTimeSpan'; import translate from 'Utilities/String/translate'; import styles from './EditImportListModalContent.css'; @@ -67,7 +66,6 @@ function EditImportListModalContent(props) { onModalClose, onSavePress, onTestPress, - onAdvancedSettingsPress, onDeleteImportListPress, showMetadataProfile, ...otherProps @@ -292,7 +290,7 @@ function EditImportListModalContent(props) { @@ -335,12 +333,6 @@ function EditImportListModalContent(props) { } - - { - this.props.toggleAdvancedSettings(); - }; - // // Render @@ -78,7 +67,6 @@ class EditImportListModalContentConnector extends Component { {...this.props} onSavePress={this.onSavePress} onTestPress={this.onTestPress} - onAdvancedSettingsPress={this.onAdvancedSettingsPress} onInputChange={this.onInputChange} onFieldChange={this.onFieldChange} /> @@ -96,7 +84,6 @@ EditImportListModalContentConnector.propTypes = { setImportListFieldValue: PropTypes.func.isRequired, saveImportList: PropTypes.func.isRequired, testImportList: PropTypes.func.isRequired, - toggleAdvancedSettings: PropTypes.func.isRequired, onModalClose: PropTypes.func.isRequired }; diff --git a/frontend/src/Settings/ImportLists/ImportLists/ImportListsConnector.js b/frontend/src/Settings/ImportLists/ImportLists/ImportListsConnector.js index 5eb47068d..5c6bad8e7 100644 --- a/frontend/src/Settings/ImportLists/ImportLists/ImportListsConnector.js +++ b/frontend/src/Settings/ImportLists/ImportLists/ImportListsConnector.js @@ -4,12 +4,12 @@ import { connect } from 'react-redux'; import { createSelector } from 'reselect'; import { deleteImportList, fetchImportLists, fetchRootFolders } from 'Store/Actions/settingsActions'; import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector'; -import sortByProp from 'Utilities/Array/sortByProp'; +import sortByName from 'Utilities/Array/sortByName'; import ImportLists from './ImportLists'; function createMapStateToProps() { return createSelector( - createSortedSectionSelector('settings.importLists', sortByProp('name')), + createSortedSectionSelector('settings.importLists', sortByName), (importLists) => importLists ); } diff --git a/frontend/src/Settings/ImportLists/ImportLists/Manage/Edit/ManageImportListsEditModalContent.tsx b/frontend/src/Settings/ImportLists/ImportLists/Manage/Edit/ManageImportListsEditModalContent.tsx index 82f7d309c..5a651ba28 100644 --- a/frontend/src/Settings/ImportLists/ImportLists/Manage/Edit/ManageImportListsEditModalContent.tsx +++ b/frontend/src/Settings/ImportLists/ImportLists/Manage/Edit/ManageImportListsEditModalContent.tsx @@ -31,7 +31,7 @@ const autoAddOptions = [ get value() { return translate('NoChange'); }, - isDisabled: true, + disabled: true, }, { key: 'enabled', diff --git a/frontend/src/Settings/ImportLists/ImportLists/Manage/ManageImportListsModalContent.css b/frontend/src/Settings/ImportLists/ImportLists/Manage/ManageImportListsModalContent.css index 6ea04a0c8..c106388ab 100644 --- a/frontend/src/Settings/ImportLists/ImportLists/Manage/ManageImportListsModalContent.css +++ b/frontend/src/Settings/ImportLists/ImportLists/Manage/ManageImportListsModalContent.css @@ -13,4 +13,4 @@ composes: button from '~Components/Link/Button.css'; margin-right: 10px; -} +} \ No newline at end of file diff --git a/frontend/src/Settings/ImportLists/ImportLists/Manage/ManageImportListsModalContent.tsx b/frontend/src/Settings/ImportLists/ImportLists/Manage/ManageImportListsModalContent.tsx index 4fee485c9..60619c662 100644 --- a/frontend/src/Settings/ImportLists/ImportLists/Manage/ManageImportListsModalContent.tsx +++ b/frontend/src/Settings/ImportLists/ImportLists/Manage/ManageImportListsModalContent.tsx @@ -198,9 +198,9 @@ function ManageImportListsModalContent( {error ?
{errorMessage}
: null} - {isPopulated && !error && !items.length ? ( + {isPopulated && !error && !items.length && ( {translate('NoImportListsFound')} - ) : null} + )} {isPopulated && !!items.length && !isFetching && !isFetching ? (
- {qualityProfile?.name ?? translate('None')} + {qualityProfile?.name ?? 'None'} @@ -72,7 +71,7 @@ function ManageImportListsModalRow(props: ManageImportListsModalRowProps) { - {enableAutomaticAdd ? translate('Yes') : translate('No')} + {enableAutomaticAdd ? 'Yes' : 'No'} diff --git a/frontend/src/Settings/Indexers/Indexers/EditIndexerModalContent.js b/frontend/src/Settings/Indexers/Indexers/EditIndexerModalContent.js index bda52ac42..55235c9da 100644 --- a/frontend/src/Settings/Indexers/Indexers/EditIndexerModalContent.js +++ b/frontend/src/Settings/Indexers/Indexers/EditIndexerModalContent.js @@ -160,7 +160,7 @@ function EditIndexerModalContent(props) { { return { diff --git a/frontend/src/Settings/Indexers/Indexers/Manage/Edit/ManageIndexersEditModalContent.tsx b/frontend/src/Settings/Indexers/Indexers/Manage/Edit/ManageIndexersEditModalContent.tsx index 69ad5a988..f9b051986 100644 --- a/frontend/src/Settings/Indexers/Indexers/Manage/Edit/ManageIndexersEditModalContent.tsx +++ b/frontend/src/Settings/Indexers/Indexers/Manage/Edit/ManageIndexersEditModalContent.tsx @@ -32,7 +32,7 @@ const enableOptions = [ get value() { return translate('NoChange'); }, - isDisabled: true, + disabled: true, }, { key: 'enabled', diff --git a/frontend/src/Settings/Indexers/Indexers/Manage/ManageIndexersModalContent.css b/frontend/src/Settings/Indexers/Indexers/Manage/ManageIndexersModalContent.css index 6ea04a0c8..c106388ab 100644 --- a/frontend/src/Settings/Indexers/Indexers/Manage/ManageIndexersModalContent.css +++ b/frontend/src/Settings/Indexers/Indexers/Manage/ManageIndexersModalContent.css @@ -13,4 +13,4 @@ composes: button from '~Components/Link/Button.css'; margin-right: 10px; -} +} \ No newline at end of file diff --git a/frontend/src/Settings/Indexers/Indexers/Manage/ManageIndexersModalContent.tsx b/frontend/src/Settings/Indexers/Indexers/Manage/ManageIndexersModalContent.tsx index 997d1b566..8137111f5 100644 --- a/frontend/src/Settings/Indexers/Indexers/Manage/ManageIndexersModalContent.tsx +++ b/frontend/src/Settings/Indexers/Indexers/Manage/ManageIndexersModalContent.tsx @@ -10,11 +10,11 @@ import ModalBody from 'Components/Modal/ModalBody'; import ModalContent from 'Components/Modal/ModalContent'; import ModalFooter from 'Components/Modal/ModalFooter'; import ModalHeader from 'Components/Modal/ModalHeader'; -import Column from 'Components/Table/Column'; import Table from 'Components/Table/Table'; import TableBody from 'Components/Table/TableBody'; import useSelectState from 'Helpers/Hooks/useSelectState'; import { kinds } from 'Helpers/Props'; +import SortDirection from 'Helpers/Props/SortDirection'; import { bulkDeleteIndexers, bulkEditIndexers, @@ -35,7 +35,7 @@ type OnSelectedChangeCallback = React.ComponentProps< typeof ManageIndexersModalRow >['onSelectedChange']; -const COLUMNS: Column[] = [ +const COLUMNS = [ { name: 'name', label: () => translate('Name'), @@ -82,6 +82,8 @@ const COLUMNS: Column[] = [ interface ManageIndexersModalContentProps { onModalClose(): void; + sortKey?: string; + sortDirection?: SortDirection; } function ManageIndexersModalContent(props: ManageIndexersModalContentProps) { @@ -213,9 +215,9 @@ function ManageIndexersModalContent(props: ManageIndexersModalContentProps) { {error ?
{errorMessage}
: null} - {isPopulated && !error && !items.length ? ( + {isPopulated && !error && !items.length && ( {translate('NoIndexersFound')} - ) : null} + )} {isPopulated && !!items.length && !isFetching && !isFetching ? (
- - {translate('SkipFreeSpaceCheck')} + { + !isWindows && + + + {translate('SkipFreeSpaceCheck')} + - - + + + } state.settings.advancedSettings, (state) => state.settings.namingExamples, createSettingsSectionSelector(SECTION), - (advancedSettings, namingExamples, sectionSettings) => { + (advancedSettings, examples, sectionSettings) => { return { advancedSettings, - examples: namingExamples.item, - examplesPopulated: namingExamples.isPopulated, + examples: examples.item, + examplesPopulated: !_.isEmpty(examples.item), ...sectionSettings }; } diff --git a/frontend/src/Settings/MediaManagement/Naming/NamingOption.css b/frontend/src/Settings/MediaManagement/Naming/NamingOption.css index 204c93d0e..b692362fb 100644 --- a/frontend/src/Settings/MediaManagement/Naming/NamingOption.css +++ b/frontend/src/Settings/MediaManagement/Naming/NamingOption.css @@ -1,6 +1,6 @@ .option { display: flex; - align-items: stretch; + align-items: center; flex-wrap: wrap; margin: 3px; border: 1px solid var(--borderColor); @@ -17,7 +17,7 @@ } .small { - width: 490px; + width: 460px; } .large { @@ -26,7 +26,7 @@ .token { flex: 0 0 50%; - padding: 6px; + padding: 6px 16px; background-color: var(--popoverTitleBackgroundColor); font-family: $monoSpaceFontFamily; } @@ -34,9 +34,9 @@ .example { display: flex; align-items: center; - justify-content: space-between; + align-self: stretch; flex: 0 0 50%; - padding: 6px; + padding: 6px 16px; background-color: var(--popoverBodyBackgroundColor); .footNote { diff --git a/frontend/src/Settings/MediaManagement/RootFolder/RootFolder.js b/frontend/src/Settings/MediaManagement/RootFolder/RootFolder.js index dc91e4622..20cefc53f 100644 --- a/frontend/src/Settings/MediaManagement/RootFolder/RootFolder.js +++ b/frontend/src/Settings/MediaManagement/RootFolder/RootFolder.js @@ -75,12 +75,12 @@ class RootFolder extends Component { {path} -
+ + { + items.map((item) => { + return ( + + ); + }) + } + +
+ } +
+ ); +} + +QueuedTasks.propTypes = { + isFetching: PropTypes.bool.isRequired, + isPopulated: PropTypes.bool.isRequired, + items: PropTypes.array.isRequired +}; + +export default QueuedTasks; diff --git a/frontend/src/System/Tasks/Queued/QueuedTasks.tsx b/frontend/src/System/Tasks/Queued/QueuedTasks.tsx deleted file mode 100644 index e79deed7c..000000000 --- a/frontend/src/System/Tasks/Queued/QueuedTasks.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import React, { useEffect } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import AppState from 'App/State/AppState'; -import FieldSet from 'Components/FieldSet'; -import LoadingIndicator from 'Components/Loading/LoadingIndicator'; -import Table from 'Components/Table/Table'; -import TableBody from 'Components/Table/TableBody'; -import { fetchCommands } from 'Store/Actions/commandActions'; -import translate from 'Utilities/String/translate'; -import QueuedTaskRow from './QueuedTaskRow'; - -const columns = [ - { - name: 'trigger', - label: '', - isVisible: true, - }, - { - name: 'commandName', - label: () => translate('Name'), - isVisible: true, - }, - { - name: 'queued', - label: () => translate('Queued'), - isVisible: true, - }, - { - name: 'started', - label: () => translate('Started'), - isVisible: true, - }, - { - name: 'ended', - label: () => translate('Ended'), - isVisible: true, - }, - { - name: 'duration', - label: () => translate('Duration'), - isVisible: true, - }, - { - name: 'actions', - isVisible: true, - }, -]; - -export default function QueuedTasks() { - const dispatch = useDispatch(); - const { isFetching, isPopulated, items } = useSelector( - (state: AppState) => state.commands - ); - - useEffect(() => { - dispatch(fetchCommands()); - }, [dispatch]); - - return ( -
- {isFetching && !isPopulated && } - - {isPopulated && ( - - - {items.map((item) => { - return ; - })} - -
- )} -
- ); -} diff --git a/frontend/src/System/Tasks/Queued/QueuedTasksConnector.js b/frontend/src/System/Tasks/Queued/QueuedTasksConnector.js new file mode 100644 index 000000000..5fa4d9ead --- /dev/null +++ b/frontend/src/System/Tasks/Queued/QueuedTasksConnector.js @@ -0,0 +1,46 @@ +import PropTypes from 'prop-types'; +import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import { createSelector } from 'reselect'; +import { fetchCommands } from 'Store/Actions/commandActions'; +import QueuedTasks from './QueuedTasks'; + +function createMapStateToProps() { + return createSelector( + (state) => state.commands, + (commands) => { + return commands; + } + ); +} + +const mapDispatchToProps = { + dispatchFetchCommands: fetchCommands +}; + +class QueuedTasksConnector extends Component { + + // + // Lifecycle + + componentDidMount() { + this.props.dispatchFetchCommands(); + } + + // + // Render + + render() { + return ( + + ); + } +} + +QueuedTasksConnector.propTypes = { + dispatchFetchCommands: PropTypes.func.isRequired +}; + +export default connect(createMapStateToProps, mapDispatchToProps)(QueuedTasksConnector); diff --git a/frontend/src/System/Tasks/Tasks.js b/frontend/src/System/Tasks/Tasks.js index 03a3b6ce4..032dbede8 100644 --- a/frontend/src/System/Tasks/Tasks.js +++ b/frontend/src/System/Tasks/Tasks.js @@ -2,7 +2,7 @@ import React from 'react'; import PageContent from 'Components/Page/PageContent'; import PageContentBody from 'Components/Page/PageContentBody'; import translate from 'Utilities/String/translate'; -import QueuedTasks from './Queued/QueuedTasks'; +import QueuedTasksConnector from './Queued/QueuedTasksConnector'; import ScheduledTasksConnector from './Scheduled/ScheduledTasksConnector'; function Tasks() { @@ -10,7 +10,7 @@ function Tasks() { - + ); diff --git a/frontend/src/System/Updates/UpdateChanges.js b/frontend/src/System/Updates/UpdateChanges.js new file mode 100644 index 000000000..3588069a0 --- /dev/null +++ b/frontend/src/System/Updates/UpdateChanges.js @@ -0,0 +1,46 @@ +import PropTypes from 'prop-types'; +import React, { Component } from 'react'; +import InlineMarkdown from 'Components/Markdown/InlineMarkdown'; +import styles from './UpdateChanges.css'; + +class UpdateChanges extends Component { + + // + // Render + + render() { + const { + title, + changes + } = this.props; + + if (changes.length === 0) { + return null; + } + + return ( +
+
{title}
+
    + { + changes.map((change, index) => { + return ( +
  • + +
  • + ); + }) + } +
+
+ ); + } + +} + +UpdateChanges.propTypes = { + title: PropTypes.string.isRequired, + changes: PropTypes.arrayOf(PropTypes.string) +}; + +export default UpdateChanges; diff --git a/frontend/src/System/Updates/UpdateChanges.tsx b/frontend/src/System/Updates/UpdateChanges.tsx deleted file mode 100644 index 3e5ba1c9b..000000000 --- a/frontend/src/System/Updates/UpdateChanges.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import React from 'react'; -import InlineMarkdown from 'Components/Markdown/InlineMarkdown'; -import styles from './UpdateChanges.css'; - -interface UpdateChangesProps { - title: string; - changes: string[]; -} - -function UpdateChanges(props: UpdateChangesProps) { - const { title, changes } = props; - - if (changes.length === 0) { - return null; - } - - const uniqueChanges = [...new Set(changes)]; - - return ( -
-
{title}
-
    - {uniqueChanges.map((change, index) => { - const checkChange = change.replace( - /#\d{4,5}\b/g, - (match) => - `[${match}](https://github.com/Lidarr/Lidarr/issues/${match.substring( - 1 - )})` - ); - - return ( -
  • - -
  • - ); - })} -
-
- ); -} - -export default UpdateChanges; diff --git a/frontend/src/System/Updates/Updates.js b/frontend/src/System/Updates/Updates.js new file mode 100644 index 000000000..528441cbe --- /dev/null +++ b/frontend/src/System/Updates/Updates.js @@ -0,0 +1,249 @@ +import _ from 'lodash'; +import PropTypes from 'prop-types'; +import React, { Component, Fragment } from 'react'; +import Alert from 'Components/Alert'; +import Icon from 'Components/Icon'; +import Label from 'Components/Label'; +import SpinnerButton from 'Components/Link/SpinnerButton'; +import LoadingIndicator from 'Components/Loading/LoadingIndicator'; +import InlineMarkdown from 'Components/Markdown/InlineMarkdown'; +import PageContent from 'Components/Page/PageContent'; +import PageContentBody from 'Components/Page/PageContentBody'; +import { icons, kinds } from 'Helpers/Props'; +import formatDate from 'Utilities/Date/formatDate'; +import formatDateTime from 'Utilities/Date/formatDateTime'; +import translate from 'Utilities/String/translate'; +import UpdateChanges from './UpdateChanges'; +import styles from './Updates.css'; + +class Updates extends Component { + + // + // Render + + render() { + const { + currentVersion, + isFetching, + isPopulated, + updatesError, + generalSettingsError, + items, + isInstallingUpdate, + updateMechanism, + updateMechanismMessage, + shortDateFormat, + longDateFormat, + timeFormat, + onInstallLatestPress + } = this.props; + + const hasError = !!(updatesError || generalSettingsError); + const hasUpdates = isPopulated && !hasError && items.length > 0; + const noUpdates = isPopulated && !hasError && !items.length; + const hasUpdateToInstall = hasUpdates && _.some(items, { installable: true, latest: true }); + const noUpdateToInstall = hasUpdates && !hasUpdateToInstall; + + const externalUpdaterPrefix = 'Unable to update Lidarr directly,'; + const externalUpdaterMessages = { + external: 'Lidarr is configured to use an external update mechanism', + apt: 'use apt to install the update', + docker: 'update the docker container to receive the update' + }; + + return ( + + + { + !isPopulated && !hasError && + + } + + { + noUpdates && + + {translate('NoUpdatesAreAvailable')} + + } + + { + hasUpdateToInstall && +
+ { + updateMechanism === 'builtIn' || updateMechanism === 'script' ? + + Install Latest + : + + + + +
+ {externalUpdaterPrefix} +
+
+ } + + { + isFetching && + + } +
+ } + + { + noUpdateToInstall && +
+ +
+ The latest version of Lidarr is already installed +
+ + { + isFetching && + + } +
+ } + + { + hasUpdates && +
+ { + items.map((update) => { + const hasChanges = !!update.changes; + + return ( +
+
+
{update.version}
+
+
+ {formatDate(update.releaseDate, shortDateFormat)} +
+ + { + update.branch === 'master' ? + null : + + } + + { + update.version === currentVersion ? + : + null + } + + { + update.version !== currentVersion && update.installedOn ? + : + null + } +
+ + { + !hasChanges && +
+ {translate('MaintenanceRelease')} +
+ } + + { + hasChanges && +
+ + + +
+ } +
+ ); + }) + } +
+ } + + { + !!updatesError && +
+ Failed to fetch updates +
+ } + + { + !!generalSettingsError && +
+ Failed to update settings +
+ } +
+
+ ); + } + +} + +Updates.propTypes = { + currentVersion: PropTypes.string.isRequired, + isFetching: PropTypes.bool.isRequired, + isPopulated: PropTypes.bool.isRequired, + updatesError: PropTypes.object, + generalSettingsError: PropTypes.object, + items: PropTypes.array.isRequired, + isInstallingUpdate: PropTypes.bool.isRequired, + updateMechanism: PropTypes.string, + updateMechanismMessage: PropTypes.string, + shortDateFormat: PropTypes.string.isRequired, + longDateFormat: PropTypes.string.isRequired, + timeFormat: PropTypes.string.isRequired, + onInstallLatestPress: PropTypes.func.isRequired +}; + +export default Updates; diff --git a/frontend/src/System/Updates/Updates.tsx b/frontend/src/System/Updates/Updates.tsx deleted file mode 100644 index 300ab1f99..000000000 --- a/frontend/src/System/Updates/Updates.tsx +++ /dev/null @@ -1,303 +0,0 @@ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { createSelector } from 'reselect'; -import AppState from 'App/State/AppState'; -import * as commandNames from 'Commands/commandNames'; -import Alert from 'Components/Alert'; -import Icon from 'Components/Icon'; -import Label from 'Components/Label'; -import SpinnerButton from 'Components/Link/SpinnerButton'; -import LoadingIndicator from 'Components/Loading/LoadingIndicator'; -import InlineMarkdown from 'Components/Markdown/InlineMarkdown'; -import ConfirmModal from 'Components/Modal/ConfirmModal'; -import PageContent from 'Components/Page/PageContent'; -import PageContentBody from 'Components/Page/PageContentBody'; -import { icons, kinds } from 'Helpers/Props'; -import { executeCommand } from 'Store/Actions/commandActions'; -import { fetchGeneralSettings } from 'Store/Actions/settingsActions'; -import { fetchUpdates } from 'Store/Actions/systemActions'; -import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector'; -import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector'; -import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector'; -import { UpdateMechanism } from 'typings/Settings/General'; -import formatDate from 'Utilities/Date/formatDate'; -import formatDateTime from 'Utilities/Date/formatDateTime'; -import translate from 'Utilities/String/translate'; -import UpdateChanges from './UpdateChanges'; -import styles from './Updates.css'; - -const VERSION_REGEX = /\d+\.\d+\.\d+\.\d+/i; - -function createUpdatesSelector() { - return createSelector( - (state: AppState) => state.system.updates, - (state: AppState) => state.settings.general, - (updates, generalSettings) => { - const { error: updatesError, items } = updates; - - const isFetching = updates.isFetching || generalSettings.isFetching; - const isPopulated = updates.isPopulated && generalSettings.isPopulated; - - return { - isFetching, - isPopulated, - updatesError, - generalSettingsError: generalSettings.error, - items, - updateMechanism: generalSettings.item.updateMechanism, - }; - } - ); -} - -function Updates() { - const currentVersion = useSelector((state: AppState) => state.app.version); - const { packageUpdateMechanismMessage } = useSelector( - createSystemStatusSelector() - ); - const { shortDateFormat, longDateFormat, timeFormat } = useSelector( - createUISettingsSelector() - ); - const isInstallingUpdate = useSelector( - createCommandExecutingSelector(commandNames.APPLICATION_UPDATE) - ); - - const { - isFetching, - isPopulated, - updatesError, - generalSettingsError, - items, - updateMechanism, - } = useSelector(createUpdatesSelector()); - - const dispatch = useDispatch(); - const [isMajorUpdateModalOpen, setIsMajorUpdateModalOpen] = useState(false); - const hasError = !!(updatesError || generalSettingsError); - const hasUpdates = isPopulated && !hasError && items.length > 0; - const noUpdates = isPopulated && !hasError && !items.length; - - const externalUpdaterPrefix = translate('UpdateAppDirectlyLoadError'); - const externalUpdaterMessages: Partial> = { - external: translate('ExternalUpdater'), - apt: translate('AptUpdater'), - docker: translate('DockerUpdater'), - }; - - const { isMajorUpdate, hasUpdateToInstall } = useMemo(() => { - const majorVersion = parseInt( - currentVersion.match(VERSION_REGEX)?.[0] ?? '0' - ); - - const latestVersion = items[0]?.version; - const latestMajorVersion = parseInt( - latestVersion?.match(VERSION_REGEX)?.[0] ?? '0' - ); - - return { - isMajorUpdate: latestMajorVersion > majorVersion, - hasUpdateToInstall: items.some( - (update) => update.installable && update.latest - ), - }; - }, [currentVersion, items]); - - const noUpdateToInstall = hasUpdates && !hasUpdateToInstall; - - const handleInstallLatestPress = useCallback(() => { - if (isMajorUpdate) { - setIsMajorUpdateModalOpen(true); - } else { - dispatch(executeCommand({ name: commandNames.APPLICATION_UPDATE })); - } - }, [isMajorUpdate, setIsMajorUpdateModalOpen, dispatch]); - - const handleInstallLatestMajorVersionPress = useCallback(() => { - setIsMajorUpdateModalOpen(false); - - dispatch( - executeCommand({ - name: commandNames.APPLICATION_UPDATE, - installMajorUpdate: true, - }) - ); - }, [setIsMajorUpdateModalOpen, dispatch]); - - const handleCancelMajorVersionPress = useCallback(() => { - setIsMajorUpdateModalOpen(false); - }, [setIsMajorUpdateModalOpen]); - - useEffect(() => { - dispatch(fetchUpdates()); - dispatch(fetchGeneralSettings()); - }, [dispatch]); - - return ( - - - {isPopulated || hasError ? null : } - - {noUpdates ? ( - {translate('NoUpdatesAreAvailable')} - ) : null} - - {hasUpdateToInstall ? ( -
- {updateMechanism === 'builtIn' || updateMechanism === 'script' ? ( - - {translate('InstallLatest')} - - ) : ( - <> - - -
- {externalUpdaterPrefix}{' '} - -
- - )} - - {isFetching ? ( - - ) : null} -
- ) : null} - - {noUpdateToInstall && ( -
- -
{translate('OnLatestVersion')}
- - {isFetching && ( - - )} -
- )} - - {hasUpdates && ( -
- {items.map((update) => { - return ( -
-
-
{update.version}
-
-
- {formatDate(update.releaseDate, shortDateFormat)} -
- - {update.branch === 'master' ? null : ( - - )} - - {update.version === currentVersion ? ( - - ) : null} - - {update.version !== currentVersion && update.installedOn ? ( - - ) : null} -
- - {update.changes ? ( -
- - - -
- ) : ( -
{translate('MaintenanceRelease')}
- )} -
- ); - })} -
- )} - - {updatesError ? ( - - {translate('FailedToFetchUpdates')} - - ) : null} - - {generalSettingsError ? ( - - {translate('FailedToFetchSettings')} - - ) : null} - - -
{translate('InstallMajorVersionUpdateMessage')}
-
- -
- - } - confirmLabel={translate('Install')} - onConfirm={handleInstallLatestMajorVersionPress} - onCancel={handleCancelMajorVersionPress} - /> -
-
- ); -} - -export default Updates; diff --git a/frontend/src/System/Updates/UpdatesConnector.js b/frontend/src/System/Updates/UpdatesConnector.js new file mode 100644 index 000000000..77d75dbda --- /dev/null +++ b/frontend/src/System/Updates/UpdatesConnector.js @@ -0,0 +1,98 @@ +import PropTypes from 'prop-types'; +import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import { createSelector } from 'reselect'; +import * as commandNames from 'Commands/commandNames'; +import { executeCommand } from 'Store/Actions/commandActions'; +import { fetchGeneralSettings } from 'Store/Actions/settingsActions'; +import { fetchUpdates } from 'Store/Actions/systemActions'; +import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector'; +import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector'; +import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector'; +import Updates from './Updates'; + +function createMapStateToProps() { + return createSelector( + (state) => state.app.version, + createSystemStatusSelector(), + (state) => state.system.updates, + (state) => state.settings.general, + createUISettingsSelector(), + createCommandExecutingSelector(commandNames.APPLICATION_UPDATE), + ( + currentVersion, + status, + updates, + generalSettings, + uiSettings, + isInstallingUpdate + ) => { + const { + error: updatesError, + items + } = updates; + + const isFetching = updates.isFetching || generalSettings.isFetching; + const isPopulated = updates.isPopulated && generalSettings.isPopulated; + + return { + currentVersion, + isFetching, + isPopulated, + updatesError, + generalSettingsError: generalSettings.error, + items, + isInstallingUpdate, + updateMechanism: generalSettings.item.updateMechanism, + updateMechanismMessage: status.packageUpdateMechanismMessage, + shortDateFormat: uiSettings.shortDateFormat, + longDateFormat: uiSettings.longDateFormat, + timeFormat: uiSettings.timeFormat + }; + } + ); +} + +const mapDispatchToProps = { + dispatchFetchUpdates: fetchUpdates, + dispatchFetchGeneralSettings: fetchGeneralSettings, + dispatchExecuteCommand: executeCommand +}; + +class UpdatesConnector extends Component { + + // + // Lifecycle + + componentDidMount() { + this.props.dispatchFetchUpdates(); + this.props.dispatchFetchGeneralSettings(); + } + + // + // Listeners + + onInstallLatestPress = () => { + this.props.dispatchExecuteCommand({ name: commandNames.APPLICATION_UPDATE }); + }; + + // + // Render + + render() { + return ( + + ); + } +} + +UpdatesConnector.propTypes = { + dispatchFetchUpdates: PropTypes.func.isRequired, + dispatchFetchGeneralSettings: PropTypes.func.isRequired, + dispatchExecuteCommand: PropTypes.func.isRequired +}; + +export default connect(createMapStateToProps, mapDispatchToProps)(UpdatesConnector); diff --git a/frontend/src/TrackFile/Editor/TrackFileEditorModalContent.js b/frontend/src/TrackFile/Editor/TrackFileEditorModalContent.js index 0e387f39f..aa59e866f 100644 --- a/frontend/src/TrackFile/Editor/TrackFileEditorModalContent.js +++ b/frontend/src/TrackFile/Editor/TrackFileEditorModalContent.js @@ -146,7 +146,7 @@ class TrackFileEditorModalContent extends Component { }); return acc; - }, [{ key: 'selectQuality', value: translate('SelectQuality'), isDisabled: true }]); + }, [{ key: 'selectQuality', value: 'Select Quality', disabled: true }]); const hasSelectedFiles = this.getSelectedIds().length > 0; diff --git a/frontend/src/TrackFile/TrackFile.ts b/frontend/src/TrackFile/TrackFile.ts index ef4dc65f3..ce9379816 100644 --- a/frontend/src/TrackFile/TrackFile.ts +++ b/frontend/src/TrackFile/TrackFile.ts @@ -13,7 +13,6 @@ export interface TrackFile extends ModelBase { releaseGroup: string; quality: QualityModel; customFormats: CustomFormat[]; - indexerFlags: number; mediaInfo: MediaInfo; qualityCutoffNotMet: boolean; } diff --git a/frontend/src/Utilities/Array/sortByName.js b/frontend/src/Utilities/Array/sortByName.js new file mode 100644 index 000000000..1956d3bac --- /dev/null +++ b/frontend/src/Utilities/Array/sortByName.js @@ -0,0 +1,5 @@ +function sortByName(a, b) { + return a.name.localeCompare(b.name); +} + +export default sortByName; diff --git a/frontend/src/Utilities/Array/sortByProp.ts b/frontend/src/Utilities/Array/sortByProp.ts deleted file mode 100644 index 8fbde08c9..000000000 --- a/frontend/src/Utilities/Array/sortByProp.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { StringKey } from 'typings/Helpers/KeysMatching'; - -export function sortByProp< - // eslint-disable-next-line no-use-before-define - T extends Record, - K extends StringKey ->(sortKey: K) { - return (a: T, b: T) => { - return a[sortKey].localeCompare(b[sortKey], undefined, { numeric: true }); - }; -} - -export default sortByProp; diff --git a/frontend/src/Utilities/Date/getRelativeDate.ts b/frontend/src/Utilities/Date/getRelativeDate.js similarity index 65% rename from frontend/src/Utilities/Date/getRelativeDate.ts rename to frontend/src/Utilities/Date/getRelativeDate.js index 178d14fb7..812064272 100644 --- a/frontend/src/Utilities/Date/getRelativeDate.ts +++ b/frontend/src/Utilities/Date/getRelativeDate.js @@ -6,33 +6,15 @@ import isTomorrow from 'Utilities/Date/isTomorrow'; import isYesterday from 'Utilities/Date/isYesterday'; import translate from 'Utilities/String/translate'; -interface GetRelativeDateOptions { - timeFormat?: string; - includeSeconds?: boolean; - timeForToday?: boolean; -} - -function getRelativeDate( - date: string | undefined, - shortDateFormat: string, - showRelativeDates: boolean, - { - timeFormat, - includeSeconds = false, - timeForToday = false, - }: GetRelativeDateOptions = {} -) { +function getRelativeDate(date, shortDateFormat, showRelativeDates, { timeFormat, includeSeconds = false, timeForToday = false } = {}) { if (!date) { - return ''; + return null; } const isTodayDate = isToday(date); if (isTodayDate && timeForToday && timeFormat) { - return formatTime(date, timeFormat, { - includeMinuteZero: true, - includeSeconds, - }); + return formatTime(date, timeFormat, { includeMinuteZero: true, includeSeconds }); } if (!showRelativeDates) { diff --git a/frontend/src/Utilities/String/translate.ts b/frontend/src/Utilities/String/translate.ts index 5571ef6b0..98a0418ad 100644 --- a/frontend/src/Utilities/String/translate.ts +++ b/frontend/src/Utilities/String/translate.ts @@ -17,7 +17,7 @@ export async function fetchTranslations(): Promise { translations = data.strings; resolve(true); - } catch { + } catch (error) { resolve(false); } }); @@ -27,12 +27,6 @@ export default function translate( key: string, tokens: Record = {} ) { - const { isProduction = true } = window.Lidarr; - - if (!isProduction && !(key in translations)) { - console.warn(`Missing translation for key: ${key}`); - } - const translation = translations[key] || key; tokens.appName = 'Lidarr'; diff --git a/frontend/src/Wanted/CutoffUnmet/CutoffUnmetConnector.js b/frontend/src/Wanted/CutoffUnmet/CutoffUnmetConnector.js index 1dd9870d1..dbb4f2235 100644 --- a/frontend/src/Wanted/CutoffUnmet/CutoffUnmetConnector.js +++ b/frontend/src/Wanted/CutoffUnmet/CutoffUnmetConnector.js @@ -131,15 +131,13 @@ class CutoffUnmetConnector extends Component { onSearchSelectedPress = (selected) => { this.props.executeCommand({ name: commandNames.ALBUM_SEARCH, - albumIds: selected, - commandFinished: this.repopulate + albumIds: selected }); }; onSearchAllCutoffUnmetPress = () => { this.props.executeCommand({ - name: commandNames.CUTOFF_UNMET_ALBUM_SEARCH, - commandFinished: this.repopulate + name: commandNames.CUTOFF_UNMET_ALBUM_SEARCH }); }; diff --git a/frontend/src/Wanted/CutoffUnmet/CutoffUnmetRow.js b/frontend/src/Wanted/CutoffUnmet/CutoffUnmetRow.js index 452e2947a..785b9b1c1 100644 --- a/frontend/src/Wanted/CutoffUnmet/CutoffUnmetRow.js +++ b/frontend/src/Wanted/CutoffUnmet/CutoffUnmetRow.js @@ -20,7 +20,6 @@ function CutoffUnmetRow(props) { foreignAlbumId, albumType, title, - lastSearchTime, disambiguation, isSelected, columns, @@ -90,15 +89,6 @@ function CutoffUnmetRow(props) { ); } - if (name === 'albums.lastSearchTime') { - return ( - - ); - } - if (name === 'status') { return ( { this.props.executeCommand({ name: commandNames.ALBUM_SEARCH, - albumIds: selected, - commandFinished: this.repopulate + albumIds: selected }); }; onSearchAllMissingPress = () => { this.props.executeCommand({ - name: commandNames.MISSING_ALBUM_SEARCH, - commandFinished: this.repopulate + name: commandNames.MISSING_ALBUM_SEARCH }); }; diff --git a/frontend/src/Wanted/Missing/MissingRow.js b/frontend/src/Wanted/Missing/MissingRow.js index 6c0b5a0c6..0eb1a0452 100644 --- a/frontend/src/Wanted/Missing/MissingRow.js +++ b/frontend/src/Wanted/Missing/MissingRow.js @@ -17,7 +17,6 @@ function MissingRow(props) { albumType, foreignAlbumId, title, - lastSearchTime, disambiguation, isSelected, columns, @@ -87,15 +86,6 @@ function MissingRow(props) { ); } - if (name === 'albums.lastSearchTime') { - return ( - - ); - } - if (name === 'actions') { return ( ); + render( + , + document.getElementById('root') + ); } diff --git a/frontend/src/index.ejs b/frontend/src/index.ejs index a893149d5..b99a39a0d 100644 --- a/frontend/src/index.ejs +++ b/frontend/src/index.ejs @@ -3,16 +3,13 @@ + + - - - - - @@ -33,7 +30,7 @@ sizes="16x16" href="/Content/Images/Icons/favicon-16x16.png" /> - + diff --git a/frontend/src/index.ts b/frontend/src/index.ts index 37e780919..36aed4c4b 100644 --- a/frontend/src/index.ts +++ b/frontend/src/index.ts @@ -14,32 +14,6 @@ window.Lidarr = await response.json(); __webpack_public_path__ = `${window.Lidarr.urlBase}/`; /* eslint-enable no-undef, @typescript-eslint/ban-ts-comment */ -const error = console.error; - -// Monkey patch console.error to filter out some warnings from React -// TODO: Remove this after the great TypeScript migration - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function logError(...parameters: any[]) { - const filter = parameters.find((parameter) => { - return ( - typeof parameter === 'string' && - (parameter.includes( - 'Support for defaultProps will be removed from function components in a future major release' - ) || - parameter.includes( - 'findDOMNode is deprecated and will be removed in the next major release' - )) - ); - }); - - if (!filter) { - error(...parameters); - } -} - -console.error = logError; - const { bootstrap } = await import('./bootstrap'); await bootstrap(); diff --git a/frontend/src/login.html b/frontend/src/login.html index 24d086959..a65106664 100644 --- a/frontend/src/login.html +++ b/frontend/src/login.html @@ -3,19 +3,13 @@ + + - - - - - @@ -36,11 +30,7 @@ sizes="16x16" href="/Content/Images/Icons/favicon-16x16.png" /> - + - + @@ -61,9 +54,9 @@