diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index d0fa03d5f..6f027453c 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -6,7 +6,7 @@ "features": { "ghcr.io/devcontainers/features/node:1": { "nodeGypDependencies": true, - "version": "20", + "version": "16", "nvmVersion": "latest" } }, 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/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..05531517e 100644 --- a/.gitignore +++ b/.gitignore @@ -121,7 +121,6 @@ _artifacts _rawPackage/ _dotTrace* _tests/ -_temp* *.Result.xml coverage*.xml coverage*.json @@ -140,6 +139,12 @@ project.fragment.lock.json artifacts/ **/Properties/launchSettings.json +#VS outout folders +bin +obj +output/* + + # macOS metadata files ._* .DS_Store @@ -158,12 +163,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/README.md b/README.md index 4aa47575c..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) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index ba0800fee..f0fd6957f 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.1' + majorVersion: '2.3.2' 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.421' 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: @@ -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/.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/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/Edit/EditAlbumModalContent.js b/frontend/src/Album/Edit/EditAlbumModalContent.js index dafc0312d..b924f937b 100644 --- a/frontend/src/Album/Edit/EditAlbumModalContent.js +++ b/frontend/src/Album/Edit/EditAlbumModalContent.js @@ -35,7 +35,7 @@ class EditAlbumModalContent extends Component { title, artistName, albumType, - statistics = {}, + statistics, item, isSaving, onInputChange, 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..979785f3a 100644 --- a/frontend/src/App/State/AppState.ts +++ b/frontend/src/App/State/AppState.ts @@ -1,4 +1,3 @@ -import ParseAppState from 'App/State/ParseAppState'; import AlbumAppState from './AlbumAppState'; import ArtistAppState, { ArtistIndexAppState } from './ArtistAppState'; import CalendarAppState from './CalendarAppState'; @@ -44,7 +43,6 @@ export interface CustomFilter { } export interface AppSectionState { - version: string; dimensions: { isSmallScreen: boolean; width: number; @@ -60,7 +58,6 @@ interface AppState { calendar: CalendarAppState; commands: CommandAppState; history: HistoryAppState; - parse: ParseAppState; queue: QueueAppState; settings: SettingsAppState; tags: TagsAppState; 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..547f353cd 100644 --- a/frontend/src/App/State/SettingsAppState.ts +++ b/frontend/src/App/State/SettingsAppState.ts @@ -4,7 +4,6 @@ import AppSectionState, { 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'; @@ -13,16 +12,13 @@ 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,11 +41,6 @@ export interface MetadataProfilesAppState extends AppSectionState, AppSectionSchemaState {} -export interface CustomFormatAppState - extends AppSectionState, - AppSectionDeleteState, - AppSectionSaveState {} - export interface RootFolderAppState extends AppSectionState, AppSectionDeleteState, @@ -59,10 +50,7 @@ export type IndexerFlagSettingsAppState = AppSectionState; export type UiSettingsAppState = AppSectionItemState; interface SettingsAppState { - advancedSettings: boolean; - customFormats: CustomFormatAppState; downloadClients: DownloadClientAppState; - general: GeneralAppState; importLists: ImportListAppState; indexerFlags: IndexerFlagSettingsAppState; indexers: IndexerAppState; diff --git a/frontend/src/App/State/SystemAppState.ts b/frontend/src/App/State/SystemAppState.ts index 3c150fcfb..d43c1d0ee 100644 --- a/frontend/src/App/State/SystemAppState.ts +++ b/frontend/src/App/State/SystemAppState.ts @@ -1,12 +1,9 @@ import SystemStatus from 'typings/SystemStatus'; -import Update from 'typings/Update'; -import AppSectionState, { AppSectionItemState } from './AppSectionState'; +import { AppSectionItemState } from './AppSectionState'; export type SystemStatusAppState = AppSectionItemState; -export type UpdateAppState = AppSectionState; interface SystemAppState { - updates: UpdateAppState; status: SystemStatusAppState; } 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/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..cff971235 --- /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": "standalone" +} 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/Props/icons.js b/frontend/src/Helpers/Props/icons.js index aa9c23145..ccb8b90e9 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, @@ -188,7 +187,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..1d08c762f 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'; @@ -34,8 +34,8 @@ export const all = [ CAPTCHA, CHECK, DEVICE, - KEY_VALUE_LIST, PLAYLIST, + KEY_VALUE_LIST, MONITOR_ALBUMS_SELECT, MONITOR_NEW_ITEMS_SELECT, FLOAT, 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/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/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.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/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/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..2799af7d8 100644 --- a/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js +++ b/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js @@ -292,7 +292,7 @@ function EditImportListModalContent(props) { 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/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/IndexersConnector.js b/frontend/src/Settings/Indexers/Indexers/IndexersConnector.js index 88c571a60..cb6e830fd 100644 --- a/frontend/src/Settings/Indexers/Indexers/IndexersConnector.js +++ b/frontend/src/Settings/Indexers/Indexers/IndexersConnector.js @@ -5,12 +5,12 @@ import { createSelector } from 'reselect'; import { cloneIndexer, deleteIndexer, fetchIndexers } 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 Indexers from './Indexers'; function createMapStateToProps() { return createSelector( - createSortedSectionSelector('settings.indexers', sortByProp('name')), + createSortedSectionSelector('settings.indexers', sortByName), createTagsSelector(), (indexers, tagList) => { return { 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/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} -