mirror of
https://github.com/Radarr/Radarr.git
synced 2026-03-05 13:21:25 -05:00
Compare commits
83 Commits
v4.7.2.768
...
v4.7.5.780
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e8e54fdf99 | ||
|
|
c3b856401e | ||
|
|
25f6f3ec6d | ||
|
|
d28eb47a1a | ||
|
|
431bc14e76 | ||
|
|
efe5c3beb7 | ||
|
|
d61ce6112b | ||
|
|
531e948687 | ||
|
|
7ad4411e4d | ||
|
|
e8e23e41dc | ||
|
|
0c1fc49d69 | ||
|
|
83632f91e6 | ||
|
|
1bbd08a5a0 | ||
|
|
298077940e | ||
|
|
4fb632e4fc | ||
|
|
7bcb492572 | ||
|
|
a673535417 | ||
|
|
e0d70dc341 | ||
|
|
aa98b2bac9 | ||
|
|
145f67d14b | ||
|
|
caea810908 | ||
|
|
9a567b93d0 | ||
|
|
6ecd41bc5a | ||
|
|
d5b4f0efa9 | ||
|
|
b337f62a34 | ||
|
|
c42fc6094d | ||
|
|
a6f61b2722 | ||
|
|
54bb267e17 | ||
|
|
00e2933052 | ||
|
|
b8f06eb97d | ||
|
|
bd49a4ee8b | ||
|
|
247ca9b22a | ||
|
|
779b65fa2e | ||
|
|
002cbdb864 | ||
|
|
e36715d359 | ||
|
|
69b621b13a | ||
|
|
385c7971bb | ||
|
|
1129d3901c | ||
|
|
d057d15ac7 | ||
|
|
722c20a5dc | ||
|
|
43a0e75acf | ||
|
|
abad6a9f18 | ||
|
|
835a539275 | ||
|
|
cd2d81a5aa | ||
|
|
5aee804bc0 | ||
|
|
12fcd3f9b9 | ||
|
|
47360d4d38 | ||
|
|
788782d009 | ||
|
|
847d6244aa | ||
|
|
8fd8128641 | ||
|
|
136075d233 | ||
|
|
02cec5312c | ||
|
|
e5f728352c | ||
|
|
2cc1333e5c | ||
|
|
a79980aae5 | ||
|
|
f2bbef75dd | ||
|
|
d5c1f58839 | ||
|
|
430ea81937 | ||
|
|
80099dcacb | ||
|
|
938b69b240 | ||
|
|
9839b482b2 | ||
|
|
4dbd962fca | ||
|
|
856c4fa4bb | ||
|
|
45f5ce5f29 | ||
|
|
9d3e7f62ca | ||
|
|
594ed666e1 | ||
|
|
36338310df | ||
|
|
ffde07e4d6 | ||
|
|
90a1e1dbb3 | ||
|
|
8b5f305462 | ||
|
|
2fe6847eb3 | ||
|
|
bf0f681d46 | ||
|
|
f9cb4c1abd | ||
|
|
1190bf791c | ||
|
|
53eb88d9a9 | ||
|
|
ed5c063127 | ||
|
|
e691253419 | ||
|
|
2959f72a10 | ||
|
|
78ae059f3d | ||
|
|
7226cab3d8 | ||
|
|
622162c5f6 | ||
|
|
e612d8c485 | ||
|
|
b20e15855c |
8
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
8
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -65,18 +65,18 @@ body:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Trace Logs?
|
||||
label: Trace Logs? **Not Optional**
|
||||
description: |
|
||||
Trace Logs (https://wiki.servarr.com/radarr/troubleshooting#logging-and-log-files)
|
||||
***Generally speaking, all bug reports must have trace logs provided.***
|
||||
***Generally speaking, all bug reports MUST have trace logs provided.***
|
||||
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
|
||||
Additionally, any additional info? Screenshots? References? Anything that will give us more context about the issue you are encountering!
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Trace Logs have been provided as applicable. Reports may be closed if the required logs are not provided.
|
||||
description: Trace logs are generally required for all bug reports and contain `trace`. Info logs are invalid for bug reports and do not contain `debug` nor `trace`
|
||||
label: Trace Logs have been provided as applicable. Reports will be closed if the required logs are not provided.
|
||||
description: Trace logs are **generally required** and are not optional for all bug reports and contain `trace`. Info logs are invalid for bug reports and do not contain `debug` nor `trace`
|
||||
options:
|
||||
- label: I have read and followed the steps in the wiki link above and provided the required trace logs - the logs contain `trace` - that are relevant and show this issue.
|
||||
required: true
|
||||
|
||||
@@ -9,7 +9,7 @@ variables:
|
||||
testsFolder: './_tests'
|
||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||
majorVersion: '4.7.2'
|
||||
majorVersion: '4.7.5'
|
||||
minorVersion: $[counter('minorVersion', 2000)]
|
||||
radarrVersion: '$(majorVersion).$(minorVersion)'
|
||||
buildName: '$(Build.SourceBranchName).$(radarrVersion)'
|
||||
@@ -536,8 +536,8 @@ stages:
|
||||
testRunTitle: '$(testName) Unit Tests'
|
||||
failTaskOnFailedTests: true
|
||||
|
||||
- job: Unit_LinuxCore_Postgres
|
||||
displayName: Unit Native LinuxCore with Postgres Database
|
||||
- job: Unit_LinuxCore_Postgres14
|
||||
displayName: Unit Native LinuxCore with Postgres14 Database
|
||||
dependsOn: Prepare
|
||||
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||
variables:
|
||||
@@ -589,7 +589,63 @@ stages:
|
||||
inputs:
|
||||
testResultsFormat: 'NUnit'
|
||||
testResultsFiles: '**/TestResult.xml'
|
||||
testRunTitle: 'LinuxCore Postgres Unit Tests'
|
||||
testRunTitle: 'LinuxCore Postgres14 Unit Tests'
|
||||
failTaskOnFailedTests: true
|
||||
|
||||
- job: Unit_LinuxCore_Postgres15
|
||||
displayName: Unit Native LinuxCore with Postgres15 Database
|
||||
dependsOn: Prepare
|
||||
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||
variables:
|
||||
pattern: 'Radarr.*.linux-core-x64.tar.gz'
|
||||
artifactName: linux-x64-tests
|
||||
Radarr__Postgres__Host: 'localhost'
|
||||
Radarr__Postgres__Port: '5432'
|
||||
Radarr__Postgres__User: 'radarr'
|
||||
Radarr__Postgres__Password: 'radarr'
|
||||
|
||||
pool:
|
||||
vmImage: ${{ variables.linuxImage }}
|
||||
|
||||
timeoutInMinutes: 10
|
||||
|
||||
steps:
|
||||
- task: UseDotNet@2
|
||||
displayName: 'Install .net core'
|
||||
inputs:
|
||||
version: $(dotnetVersion)
|
||||
- checkout: none
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download Test Artifact
|
||||
inputs:
|
||||
buildType: 'current'
|
||||
artifactName: $(artifactName)
|
||||
targetPath: $(testsFolder)
|
||||
- bash: |
|
||||
chmod a+x _tests/ffprobe
|
||||
displayName: Make ffprobe Executable
|
||||
- bash: find ${TESTSFOLDER} -name "Radarr.Test.Dummy" -exec chmod a+x {} \;
|
||||
displayName: Make Test Dummy Executable
|
||||
condition: and(succeeded(), ne(variables['osName'], 'Windows'))
|
||||
- bash: |
|
||||
docker run -d --name=postgres15 \
|
||||
-e POSTGRES_PASSWORD=radarr \
|
||||
-e POSTGRES_USER=radarr \
|
||||
-p 5432:5432/tcp \
|
||||
-v /usr/share/zoneinfo/America/Chicago:/etc/localtime:ro \
|
||||
postgres:15
|
||||
displayName: Start postgres
|
||||
- bash: |
|
||||
chmod a+x ${TESTSFOLDER}/test.sh
|
||||
ls -lR ${TESTSFOLDER}
|
||||
${TESTSFOLDER}/test.sh Linux Unit Test
|
||||
displayName: Run Tests
|
||||
- task: PublishTestResults@2
|
||||
displayName: Publish Test Results
|
||||
inputs:
|
||||
testResultsFormat: 'NUnit'
|
||||
testResultsFiles: '**/TestResult.xml'
|
||||
testRunTitle: 'LinuxCore Postgres15 Unit Tests'
|
||||
failTaskOnFailedTests: true
|
||||
|
||||
- stage: Integration
|
||||
@@ -675,8 +731,8 @@ stages:
|
||||
failTaskOnFailedTests: true
|
||||
displayName: Publish Test Results
|
||||
|
||||
- job: Integration_LinuxCore_Postgres
|
||||
displayName: Integration Native LinuxCore with Postgres Database
|
||||
- job: Integration_LinuxCore_Postgres14
|
||||
displayName: Integration Native LinuxCore with Postgres14 Database
|
||||
dependsOn: Prepare
|
||||
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||
variables:
|
||||
@@ -733,7 +789,70 @@ stages:
|
||||
inputs:
|
||||
testResultsFormat: 'NUnit'
|
||||
testResultsFiles: '**/TestResult.xml'
|
||||
testRunTitle: 'Integration LinuxCore Postgres Database Integration Tests'
|
||||
testRunTitle: 'Integration LinuxCore Postgres14 Database Integration Tests'
|
||||
failTaskOnFailedTests: true
|
||||
displayName: Publish Test Results
|
||||
|
||||
|
||||
- job: Integration_LinuxCore_Postgres15
|
||||
displayName: Integration Native LinuxCore with Postgres Database
|
||||
dependsOn: Prepare
|
||||
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||
variables:
|
||||
pattern: 'Radarr.*.linux-core-x64.tar.gz'
|
||||
Radarr__Postgres__Host: 'localhost'
|
||||
Radarr__Postgres__Port: '5432'
|
||||
Radarr__Postgres__User: 'radarr'
|
||||
Radarr__Postgres__Password: 'radarr'
|
||||
|
||||
pool:
|
||||
vmImage: ${{ variables.linuxImage }}
|
||||
|
||||
steps:
|
||||
- task: UseDotNet@2
|
||||
displayName: 'Install .net core'
|
||||
inputs:
|
||||
version: $(dotnetVersion)
|
||||
- checkout: none
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download Test Artifact
|
||||
inputs:
|
||||
buildType: 'current'
|
||||
artifactName: 'linux-x64-tests'
|
||||
targetPath: $(testsFolder)
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download Build Artifact
|
||||
inputs:
|
||||
buildType: 'current'
|
||||
artifactName: Packages
|
||||
itemPattern: '**/$(pattern)'
|
||||
targetPath: $(Build.ArtifactStagingDirectory)
|
||||
- task: ExtractFiles@1
|
||||
inputs:
|
||||
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
|
||||
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
|
||||
displayName: Extract Package
|
||||
- bash: |
|
||||
mkdir -p ./bin/
|
||||
cp -r -v ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin/Radarr/. ./bin/
|
||||
displayName: Move Package Contents
|
||||
- bash: |
|
||||
docker run -d --name=postgres15 \
|
||||
-e POSTGRES_PASSWORD=radarr \
|
||||
-e POSTGRES_USER=radarr \
|
||||
-p 5432:5432/tcp \
|
||||
-v /usr/share/zoneinfo/America/Chicago:/etc/localtime:ro \
|
||||
postgres:15
|
||||
displayName: Start postgres
|
||||
- bash: |
|
||||
chmod a+x ${TESTSFOLDER}/test.sh
|
||||
${TESTSFOLDER}/test.sh Linux Integration Test
|
||||
displayName: Run Integration Tests
|
||||
- task: PublishTestResults@2
|
||||
inputs:
|
||||
testResultsFormat: 'NUnit'
|
||||
testResultsFiles: '**/TestResult.xml'
|
||||
testRunTitle: 'Integration LinuxCore Postgres15 Database Integration Tests'
|
||||
failTaskOnFailedTests: true
|
||||
displayName: Publish Test Results
|
||||
|
||||
|
||||
13
build.sh
13
build.sh
@@ -392,22 +392,21 @@ then
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$FRONTEND" = "YES" ];
|
||||
if [[ "$LINT" = "YES" || "$FRONTEND" = "YES" ]];
|
||||
then
|
||||
YarnInstall
|
||||
RunWebpack
|
||||
fi
|
||||
|
||||
if [ "$LINT" = "YES" ];
|
||||
then
|
||||
if [ -z "$FRONTEND" ];
|
||||
then
|
||||
YarnInstall
|
||||
fi
|
||||
|
||||
LintUI
|
||||
fi
|
||||
|
||||
if [ "$FRONTEND" = "YES" ];
|
||||
then
|
||||
RunWebpack
|
||||
fi
|
||||
|
||||
if [ "$PACKAGES" = "YES" ];
|
||||
then
|
||||
UpdateVersionNumber
|
||||
|
||||
@@ -36,7 +36,7 @@ module.exports = (env) => {
|
||||
},
|
||||
|
||||
entry: {
|
||||
index: 'index.js'
|
||||
index: 'index.ts'
|
||||
},
|
||||
|
||||
resolve: {
|
||||
@@ -97,7 +97,8 @@ module.exports = (env) => {
|
||||
new HtmlWebpackPlugin({
|
||||
template: 'frontend/src/index.ejs',
|
||||
filename: 'index.html',
|
||||
publicPath: '/'
|
||||
publicPath: '/',
|
||||
inject: false
|
||||
}),
|
||||
|
||||
new FileManagerPlugin({
|
||||
|
||||
@@ -18,17 +18,17 @@ import styles from './ImportMovieSelectFolder.css';
|
||||
const rootFolderColumns = [
|
||||
{
|
||||
name: 'path',
|
||||
label: translate('Path'),
|
||||
label: () => translate('Path'),
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'freeSpace',
|
||||
label: translate('FreeSpace'),
|
||||
label: () => translate('FreeSpace'),
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'unmappedFolders',
|
||||
label: translate('UnmappedFolders'),
|
||||
label: () => translate('UnmappedFolders'),
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
|
||||
@@ -7,13 +7,13 @@ import PageConnector from 'Components/Page/PageConnector';
|
||||
import ApplyTheme from './ApplyTheme';
|
||||
import AppRoutes from './AppRoutes';
|
||||
|
||||
function App({ store, history, hasTranslationsError }) {
|
||||
function App({ store, history }) {
|
||||
return (
|
||||
<DocumentTitle title={window.Radarr.instanceName}>
|
||||
<Provider store={store}>
|
||||
<ConnectedRouter history={history}>
|
||||
<ApplyTheme>
|
||||
<PageConnector hasTranslationsError={hasTranslationsError}>
|
||||
<PageConnector>
|
||||
<AppRoutes app={App} />
|
||||
</PageConnector>
|
||||
</ApplyTheme>
|
||||
@@ -25,8 +25,7 @@ function App({ store, history, hasTranslationsError }) {
|
||||
|
||||
App.propTypes = {
|
||||
store: PropTypes.object.isRequired,
|
||||
history: PropTypes.object.isRequired,
|
||||
hasTranslationsError: PropTypes.bool.isRequired
|
||||
history: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default App;
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import InteractiveImportAppState from 'App/State/InteractiveImportAppState';
|
||||
import CommandAppState from './CommandAppState';
|
||||
import MovieCollectionAppState from './MovieCollectionAppState';
|
||||
import MovieFilesAppState from './MovieFilesAppState';
|
||||
import MoviesAppState, { MovieIndexAppState } from './MoviesAppState';
|
||||
import ParseAppState from './ParseAppState';
|
||||
import QueueAppState from './QueueAppState';
|
||||
import RootFolderAppState from './RootFolderAppState';
|
||||
import SettingsAppState from './SettingsAppState';
|
||||
import SystemAppState from './SystemAppState';
|
||||
import TagsAppState from './TagsAppState';
|
||||
|
||||
interface FilterBuilderPropOption {
|
||||
@@ -39,14 +43,18 @@ export interface CustomFilter {
|
||||
}
|
||||
|
||||
interface AppState {
|
||||
movieFiles: MovieFilesAppState;
|
||||
commands: CommandAppState;
|
||||
interactiveImport: InteractiveImportAppState;
|
||||
movieCollections: MovieCollectionAppState;
|
||||
movieFiles: MovieFilesAppState;
|
||||
movieIndex: MovieIndexAppState;
|
||||
parse: ParseAppState;
|
||||
settings: SettingsAppState;
|
||||
movies: MoviesAppState;
|
||||
tags: TagsAppState;
|
||||
parse: ParseAppState;
|
||||
queue: QueueAppState;
|
||||
rootFolders: RootFolderAppState;
|
||||
settings: SettingsAppState;
|
||||
system: SystemAppState;
|
||||
tags: TagsAppState;
|
||||
}
|
||||
|
||||
export default AppState;
|
||||
|
||||
6
frontend/src/App/State/CommandAppState.ts
Normal file
6
frontend/src/App/State/CommandAppState.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import AppSectionState from 'App/State/AppSectionState';
|
||||
import Command from 'Commands/Command';
|
||||
|
||||
export type CommandAppState = AppSectionState<Command>;
|
||||
|
||||
export default CommandAppState;
|
||||
6
frontend/src/App/State/MovieCollectionAppState.ts
Normal file
6
frontend/src/App/State/MovieCollectionAppState.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import AppSectionState from 'App/State/AppSectionState';
|
||||
import MovieCollection from 'typings/MovieCollection';
|
||||
|
||||
type MovieCollectionAppState = AppSectionState<MovieCollection>;
|
||||
|
||||
export default MovieCollectionAppState;
|
||||
@@ -22,6 +22,9 @@ export interface MovieIndexAppState {
|
||||
showQualityProfile: boolean;
|
||||
showReleaseDate: boolean;
|
||||
showCinemaRelease: boolean;
|
||||
showTmdbRating: boolean;
|
||||
showImdbRating: boolean;
|
||||
showRottenTomatoesRating: boolean;
|
||||
showSearchAction: boolean;
|
||||
};
|
||||
|
||||
|
||||
12
frontend/src/App/State/RootFolderAppState.ts
Normal file
12
frontend/src/App/State/RootFolderAppState.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import AppSectionState, {
|
||||
AppSectionDeleteState,
|
||||
AppSectionSaveState,
|
||||
} from 'App/State/AppSectionState';
|
||||
import RootFolder from 'typings/RootFolder';
|
||||
|
||||
interface RootFolderAppState
|
||||
extends AppSectionState<RootFolder>,
|
||||
AppSectionDeleteState,
|
||||
AppSectionSaveState {}
|
||||
|
||||
export default RootFolderAppState;
|
||||
@@ -1,5 +1,6 @@
|
||||
import AppSectionState, {
|
||||
AppSectionDeleteState,
|
||||
AppSectionItemState,
|
||||
AppSectionSaveState,
|
||||
AppSectionSchemaState,
|
||||
} from 'App/State/AppSectionState';
|
||||
@@ -35,16 +36,16 @@ export interface QualityProfilesAppState
|
||||
AppSectionSchemaState<QualityProfile> {}
|
||||
|
||||
export type LanguageSettingsAppState = AppSectionState<Language>;
|
||||
export type UiSettingsAppState = AppSectionState<UiSettings>;
|
||||
export type UiSettingsAppState = AppSectionItemState<UiSettings>;
|
||||
|
||||
interface SettingsAppState {
|
||||
downloadClients: DownloadClientAppState;
|
||||
importLists: ImportListAppState;
|
||||
indexers: IndexerAppState;
|
||||
languages: LanguageSettingsAppState;
|
||||
notifications: NotificationAppState;
|
||||
language: LanguageSettingsAppState;
|
||||
uiSettings: UiSettingsAppState;
|
||||
qualityProfiles: QualityProfilesAppState;
|
||||
ui: UiSettingsAppState;
|
||||
}
|
||||
|
||||
export default SettingsAppState;
|
||||
|
||||
10
frontend/src/App/State/SystemAppState.ts
Normal file
10
frontend/src/App/State/SystemAppState.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import SystemStatus from 'typings/SystemStatus';
|
||||
import { AppSectionItemState } from './AppSectionState';
|
||||
|
||||
export type SystemStatusAppState = AppSectionItemState<SystemStatus>;
|
||||
|
||||
interface SystemAppState {
|
||||
status: SystemStatusAppState;
|
||||
}
|
||||
|
||||
export default SystemAppState;
|
||||
@@ -1,12 +1,32 @@
|
||||
import ModelBase from 'App/ModelBase';
|
||||
import AppSectionState, {
|
||||
AppSectionDeleteState,
|
||||
AppSectionSaveState,
|
||||
} from 'App/State/AppSectionState';
|
||||
|
||||
export interface Tag extends ModelBase {
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface TagsAppState extends AppSectionState<Tag>, AppSectionDeleteState {}
|
||||
export interface TagDetail extends ModelBase {
|
||||
label: string;
|
||||
autoTagIds: number[];
|
||||
delayProfileIds: number[];
|
||||
downloadClientIds: number[];
|
||||
importListIds: number[];
|
||||
indexerIds: number[];
|
||||
movieIds: number[];
|
||||
notificationIds: number[];
|
||||
restrictionIds: number[];
|
||||
}
|
||||
|
||||
export interface TagDetailAppState
|
||||
extends AppSectionState<TagDetail>,
|
||||
AppSectionDeleteState,
|
||||
AppSectionSaveState {}
|
||||
|
||||
interface TagsAppState extends AppSectionState<Tag>, AppSectionDeleteState {
|
||||
details: TagDetailAppState;
|
||||
}
|
||||
|
||||
export default TagsAppState;
|
||||
|
||||
@@ -258,7 +258,7 @@ CollectionOverviews.propTypes = {
|
||||
sortKey: PropTypes.string,
|
||||
overviewOptions: PropTypes.object.isRequired,
|
||||
jumpToCharacter: PropTypes.string,
|
||||
scrollTop: PropTypes.number.isRequired,
|
||||
scrollTop: PropTypes.number,
|
||||
scroller: PropTypes.instanceOf(Element).isRequired,
|
||||
showRelativeDates: PropTypes.bool.isRequired,
|
||||
shortDateFormat: PropTypes.string.isRequired,
|
||||
|
||||
@@ -14,9 +14,24 @@ import { inputTypes } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
||||
const posterSizeOptions = [
|
||||
{ key: 'small', value: translate('Small') },
|
||||
{ key: 'medium', value: translate('Medium') },
|
||||
{ key: 'large', value: translate('Large') }
|
||||
{
|
||||
key: 'small',
|
||||
get value() {
|
||||
return translate('Small');
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'medium',
|
||||
get value() {
|
||||
return translate('Medium');
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'large',
|
||||
get value() {
|
||||
return translate('Large');
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
class CollectionOverviewOptionsModalContent extends Component {
|
||||
|
||||
@@ -20,12 +20,12 @@ import styles from './FileBrowserModalContent.css';
|
||||
const columns = [
|
||||
{
|
||||
name: 'type',
|
||||
label: translate('Type'),
|
||||
label: () => translate('Type'),
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
label: translate('Name'),
|
||||
label: () => translate('Name'),
|
||||
isVisible: true
|
||||
}
|
||||
];
|
||||
|
||||
@@ -10,12 +10,42 @@ import { NAME } from './FilterBuilderRowValue';
|
||||
import styles from './DateFilterBuilderRowValue.css';
|
||||
|
||||
const timeOptions = [
|
||||
{ key: 'seconds', value: translate('Seconds') },
|
||||
{ key: 'minutes', value: translate('Minutes') },
|
||||
{ key: 'hours', value: translate('Hours') },
|
||||
{ key: 'days', value: translate('Days') },
|
||||
{ key: 'weeks', value: translate('Weeks') },
|
||||
{ key: 'months', value: translate('Months') }
|
||||
{
|
||||
key: 'seconds',
|
||||
get value() {
|
||||
return translate('Seconds');
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'minutes',
|
||||
get value() {
|
||||
return translate('Minutes');
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'hours',
|
||||
get value() {
|
||||
return translate('Hours');
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'days',
|
||||
get value() {
|
||||
return translate('Days');
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'weeks',
|
||||
get value() {
|
||||
return translate('Weeks');
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'months',
|
||||
get value() {
|
||||
return translate('Months');
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
function isInFilter(filterType) {
|
||||
|
||||
@@ -210,11 +210,13 @@ class FilterBuilderRow extends Component {
|
||||
const selectedFilterBuilderProp = this.selectedFilterBuilderProp;
|
||||
|
||||
const keyOptions = filterBuilderProps.map((availablePropFilter) => {
|
||||
const { name, label } = availablePropFilter;
|
||||
|
||||
return {
|
||||
key: availablePropFilter.name,
|
||||
value: availablePropFilter.label
|
||||
key: name,
|
||||
value: typeof label === 'function' ? label() : label
|
||||
};
|
||||
});
|
||||
}).sort((a, b) => a.value.localeCompare(b.value));
|
||||
|
||||
const ValueComponent = getRowValueConnector(selectedFilterBuilderProp);
|
||||
|
||||
|
||||
@@ -3,9 +3,24 @@ import translate from 'Utilities/String/translate';
|
||||
import FilterBuilderRowValue from './FilterBuilderRowValue';
|
||||
|
||||
const protocols = [
|
||||
{ id: 'announced', name: translate('Announced') },
|
||||
{ id: 'inCinemas', name: translate('InCinemas') },
|
||||
{ id: 'released', name: translate('Released') }
|
||||
{
|
||||
id: 'announced',
|
||||
get name() {
|
||||
return translate('Announced');
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'inCinemas',
|
||||
get name() {
|
||||
return translate('InCinemas');
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'released',
|
||||
get name() {
|
||||
return translate('Released');
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
function MinimumAvailabilityFilterBuilderRowValue(props) {
|
||||
|
||||
@@ -4,10 +4,30 @@ import FilterBuilderRowValue from './FilterBuilderRowValue';
|
||||
|
||||
const protocols = [
|
||||
{ id: 'tba', name: 'TBA' },
|
||||
{ id: 'announced', name: translate('Announced') },
|
||||
{ id: 'inCinemas', name: translate('InCinemas') },
|
||||
{ id: 'released', name: translate('Released') },
|
||||
{ id: 'deleted', name: translate('Deleted') }
|
||||
{
|
||||
id: 'announced',
|
||||
get name() {
|
||||
return translate('Announced');
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'inCinemas',
|
||||
get name() {
|
||||
return translate('InCinemas');
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'released',
|
||||
get name() {
|
||||
return translate('Released');
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'deleted',
|
||||
get name() {
|
||||
return translate('Deleted');
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
function ReleaseStatusFilterBuilderRowValue(props) {
|
||||
|
||||
@@ -4,9 +4,24 @@ import translate from 'Utilities/String/translate';
|
||||
import EnhancedSelectInput from './EnhancedSelectInput';
|
||||
|
||||
const availabilityOptions = [
|
||||
{ key: 'announced', value: translate('Announced') },
|
||||
{ key: 'inCinemas', value: translate('InCinemas') },
|
||||
{ key: 'released', value: translate('Released') }
|
||||
{
|
||||
key: 'announced',
|
||||
get value() {
|
||||
return translate('Announced');
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'inCinemas',
|
||||
get value() {
|
||||
return translate('InCinemas');
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'released',
|
||||
get value() {
|
||||
return translate('Released');
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
function AvailabilitySelectInput(props) {
|
||||
@@ -20,7 +35,7 @@ function AvailabilitySelectInput(props) {
|
||||
if (includeNoChange) {
|
||||
values.unshift({
|
||||
key: 'noChange',
|
||||
value: 'No Change',
|
||||
value: translate('NoChange'),
|
||||
disabled: true
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import monitorOptions from 'Utilities/Movie/monitorOptions';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import SelectInput from './SelectInput';
|
||||
|
||||
function MovieMonitoredSelectInput(props) {
|
||||
@@ -14,7 +15,7 @@ function MovieMonitoredSelectInput(props) {
|
||||
if (includeNoChange) {
|
||||
values.unshift({
|
||||
key: 'noChange',
|
||||
value: 'No Change',
|
||||
value: translate('NoChange'),
|
||||
disabled: true
|
||||
});
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@ function getType({ type, selectOptionsProviderAction }) {
|
||||
return inputTypes.TEXT;
|
||||
case 'oAuth':
|
||||
return inputTypes.OAUTH;
|
||||
case 'rootFolder':
|
||||
return inputTypes.ROOT_FOLDER_SELECT;
|
||||
default:
|
||||
return inputTypes.TEXT;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
|
||||
import sortByName from 'Utilities/Array/sortByName';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import EnhancedSelectInput from './EnhancedSelectInput';
|
||||
|
||||
function createMapStateToProps() {
|
||||
@@ -24,7 +25,7 @@ function createMapStateToProps() {
|
||||
if (includeNoChange) {
|
||||
values.unshift({
|
||||
key: 'noChange',
|
||||
value: 'No Change',
|
||||
value: translate('NoChange'),
|
||||
disabled: includeNoChangeDisabled
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { addRootFolder } from 'Store/Actions/rootFolderActions';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import RootFolderSelectInput from './RootFolderSelectInput';
|
||||
|
||||
const ADD_NEW_KEY = 'addNew';
|
||||
@@ -27,7 +28,7 @@ function createMapStateToProps() {
|
||||
if (includeNoChange) {
|
||||
values.unshift({
|
||||
key: 'noChange',
|
||||
value: 'No Change',
|
||||
value: translate('NoChange'),
|
||||
isDisabled: includeNoChangeDisabled,
|
||||
isMissing: false
|
||||
});
|
||||
|
||||
@@ -61,7 +61,7 @@ class SelectInput extends Component {
|
||||
value={key}
|
||||
{...otherOptionProps}
|
||||
>
|
||||
{optionValue}
|
||||
{typeof optionValue === 'function' ? optionValue() : optionValue}
|
||||
</option>
|
||||
);
|
||||
})
|
||||
@@ -75,7 +75,7 @@ SelectInput.propTypes = {
|
||||
className: PropTypes.string,
|
||||
disabledClassName: PropTypes.string,
|
||||
name: PropTypes.string.isRequired,
|
||||
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
||||
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.func]).isRequired,
|
||||
values: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
isDisabled: PropTypes.bool,
|
||||
hasError: PropTypes.bool,
|
||||
|
||||
@@ -75,6 +75,18 @@ class TagInput extends Component {
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onTagEdit = ({ value, ...otherProps }) => {
|
||||
const currentValue = this.state.value;
|
||||
|
||||
if (currentValue && this.props.onTagReplace) {
|
||||
this.props.onTagReplace(otherProps, { name: currentValue });
|
||||
} else {
|
||||
this.props.onTagDelete(otherProps);
|
||||
}
|
||||
|
||||
this.setState({ value });
|
||||
};
|
||||
|
||||
onInputContainerPress = () => {
|
||||
this._autosuggestRef.input.focus();
|
||||
};
|
||||
@@ -188,6 +200,7 @@ class TagInput extends Component {
|
||||
const {
|
||||
tags,
|
||||
kind,
|
||||
canEdit,
|
||||
tagComponent,
|
||||
onTagDelete
|
||||
} = this.props;
|
||||
@@ -199,8 +212,10 @@ class TagInput extends Component {
|
||||
kind={kind}
|
||||
inputProps={inputProps}
|
||||
isFocused={this.state.isFocused}
|
||||
canEdit={canEdit}
|
||||
tagComponent={tagComponent}
|
||||
onTagDelete={onTagDelete}
|
||||
onTagEdit={this.onTagEdit}
|
||||
onInputContainerPress={this.onInputContainerPress}
|
||||
/>
|
||||
);
|
||||
@@ -223,7 +238,7 @@ class TagInput extends Component {
|
||||
<AutoSuggestInput
|
||||
{...otherProps}
|
||||
forwardedRef={this._setAutosuggestRef}
|
||||
className={styles.internalInput}
|
||||
className={className}
|
||||
inputContainerClassName={classNames(
|
||||
inputContainerClassName,
|
||||
isFocused && styles.isFocused
|
||||
@@ -258,11 +273,13 @@ TagInput.propTypes = {
|
||||
placeholder: PropTypes.string.isRequired,
|
||||
delimiters: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
minQueryLength: PropTypes.number.isRequired,
|
||||
canEdit: PropTypes.bool,
|
||||
hasError: PropTypes.bool,
|
||||
hasWarning: PropTypes.bool,
|
||||
tagComponent: PropTypes.elementType.isRequired,
|
||||
onTagAdd: PropTypes.func.isRequired,
|
||||
onTagDelete: PropTypes.func.isRequired
|
||||
onTagDelete: PropTypes.func.isRequired,
|
||||
onTagReplace: PropTypes.func
|
||||
};
|
||||
|
||||
TagInput.defaultProps = {
|
||||
@@ -273,6 +290,7 @@ TagInput.defaultProps = {
|
||||
placeholder: '',
|
||||
delimiters: ['Tab', 'Enter', ' ', ','],
|
||||
minQueryLength: 1,
|
||||
canEdit: false,
|
||||
tagComponent: TagInputTag
|
||||
};
|
||||
|
||||
|
||||
@@ -138,6 +138,7 @@ class TagInputConnector extends Component {
|
||||
<TagInput
|
||||
onTagAdd={this.onTagAdd}
|
||||
onTagDelete={this.onTagDelete}
|
||||
onTagReplace={this.onTagReplace}
|
||||
{...this.props}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -28,8 +28,10 @@ class TagInputInput extends Component {
|
||||
tags,
|
||||
inputProps,
|
||||
kind,
|
||||
canEdit,
|
||||
tagComponent: TagComponent,
|
||||
onTagDelete
|
||||
onTagDelete,
|
||||
onTagEdit
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
@@ -46,8 +48,10 @@ class TagInputInput extends Component {
|
||||
index={index}
|
||||
tag={tag}
|
||||
kind={kind}
|
||||
canEdit={canEdit}
|
||||
isLastTag={index === tags.length - 1}
|
||||
onDelete={onTagDelete}
|
||||
onEdit={onTagEdit}
|
||||
/>
|
||||
);
|
||||
})
|
||||
@@ -66,8 +70,10 @@ TagInputInput.propTypes = {
|
||||
inputProps: PropTypes.object.isRequired,
|
||||
kind: PropTypes.oneOf(kinds.all).isRequired,
|
||||
isFocused: PropTypes.bool.isRequired,
|
||||
canEdit: PropTypes.bool.isRequired,
|
||||
tagComponent: PropTypes.elementType.isRequired,
|
||||
onTagDelete: PropTypes.func.isRequired,
|
||||
onTagEdit: PropTypes.func.isRequired,
|
||||
onInputContainerPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,40 @@
|
||||
.tag {
|
||||
composes: link from '~Components/Link/Link.css';
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
max-width: 100%;
|
||||
height: 31px;
|
||||
}
|
||||
|
||||
.link {
|
||||
composes: link from '~Components/Link/Link.css';
|
||||
|
||||
max-width: 100%;
|
||||
line-height: 1px;
|
||||
}
|
||||
|
||||
.linkWithEdit {
|
||||
composes: link from '~Components/Link/Link.css';
|
||||
|
||||
max-width: calc(100% - 9px - 4px - 2px);
|
||||
line-height: 1px;
|
||||
}
|
||||
|
||||
.editContainer {
|
||||
display: inline-block;
|
||||
margin-left: 4px;
|
||||
padding-left: 2px;
|
||||
border-left: 1px solid #eee;
|
||||
}
|
||||
|
||||
.editButton {
|
||||
composes: button from '~Components/Link/IconButton.css';
|
||||
|
||||
width: 9px;
|
||||
}
|
||||
|
||||
.label {
|
||||
composes: label from '~Components/Label.css';
|
||||
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
// This file is automatically generated.
|
||||
// Please do not change this file!
|
||||
interface CssExports {
|
||||
'editButton': string;
|
||||
'editContainer': string;
|
||||
'label': string;
|
||||
'link': string;
|
||||
'linkWithEdit': string;
|
||||
'tag': string;
|
||||
}
|
||||
export const cssExports: CssExports;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Label from 'Components/Label';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import Link from 'Components/Link/Link';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
import { icons, kinds } from 'Helpers/Props';
|
||||
import tagShape from 'Helpers/Props/Shapes/tagShape';
|
||||
import styles from './TagInputTag.css';
|
||||
|
||||
@@ -24,24 +25,61 @@ class TagInputTag extends Component {
|
||||
});
|
||||
};
|
||||
|
||||
onEdit = () => {
|
||||
const {
|
||||
index,
|
||||
tag,
|
||||
onEdit
|
||||
} = this.props;
|
||||
|
||||
onEdit({
|
||||
index,
|
||||
id: tag.id,
|
||||
value: tag.name
|
||||
});
|
||||
};
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
tag,
|
||||
kind
|
||||
kind,
|
||||
canEdit
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Link
|
||||
<div
|
||||
className={styles.tag}
|
||||
tabIndex={-1}
|
||||
onPress={this.onDelete}
|
||||
>
|
||||
<Label kind={kind}>
|
||||
{tag.name}
|
||||
<Label
|
||||
className={styles.label}
|
||||
kind={kind}
|
||||
>
|
||||
<Link
|
||||
className={canEdit ? styles.linkWithEdit : styles.link}
|
||||
tabIndex={-1}
|
||||
onPress={this.onDelete}
|
||||
>
|
||||
{tag.name}
|
||||
</Link>
|
||||
|
||||
{
|
||||
canEdit ?
|
||||
<div className={styles.editContainer}>
|
||||
<IconButton
|
||||
className={styles.editButton}
|
||||
name={icons.EDIT}
|
||||
size={9}
|
||||
onPress={this.onEdit}
|
||||
/>
|
||||
</div> :
|
||||
null
|
||||
}
|
||||
</Label>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -50,7 +88,9 @@ TagInputTag.propTypes = {
|
||||
index: PropTypes.number.isRequired,
|
||||
tag: PropTypes.shape(tagShape),
|
||||
kind: PropTypes.oneOf(kinds.all).isRequired,
|
||||
onDelete: PropTypes.func.isRequired
|
||||
canEdit: PropTypes.bool.isRequired,
|
||||
onDelete: PropTypes.func.isRequired,
|
||||
onEdit: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default TagInputTag;
|
||||
|
||||
@@ -71,6 +71,20 @@ class TextTagInputConnector extends Component {
|
||||
});
|
||||
};
|
||||
|
||||
onTagReplace = (tagToReplace, newTag) => {
|
||||
const {
|
||||
name,
|
||||
valueArray,
|
||||
onChange
|
||||
} = this.props;
|
||||
|
||||
const newValue = [...valueArray];
|
||||
newValue.splice(tagToReplace.index, 1);
|
||||
newValue.push(newTag.name.trim());
|
||||
|
||||
onChange({ name, value: newValue });
|
||||
};
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
@@ -80,6 +94,7 @@ class TextTagInputConnector extends Component {
|
||||
tagList={[]}
|
||||
onTagAdd={this.onTagAdd}
|
||||
onTagDelete={this.onTagDelete}
|
||||
onTagReplace={this.onTagReplace}
|
||||
{...this.props}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -41,7 +41,7 @@ class Icon extends PureComponent {
|
||||
return (
|
||||
<span
|
||||
className={containerClassName}
|
||||
title={title}
|
||||
title={typeof title === 'function' ? title() : title}
|
||||
>
|
||||
{icon}
|
||||
</span>
|
||||
@@ -58,7 +58,7 @@ Icon.propTypes = {
|
||||
name: PropTypes.object.isRequired,
|
||||
kind: PropTypes.string.isRequired,
|
||||
size: PropTypes.number.isRequired,
|
||||
title: PropTypes.string,
|
||||
title: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
|
||||
isSpinning: PropTypes.bool.isRequired,
|
||||
fixedWidth: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
@@ -13,24 +13,51 @@ class InlineMarkdown extends Component {
|
||||
data
|
||||
} = this.props;
|
||||
|
||||
// For now only replace links
|
||||
// For now only replace links or code blocks (not both)
|
||||
const markdownBlocks = [];
|
||||
if (data) {
|
||||
const regex = RegExp(/\[(.+?)\]\((.+?)\)/g);
|
||||
const linkRegex = RegExp(/\[(.+?)\]\((.+?)\)/g);
|
||||
|
||||
let endIndex = 0;
|
||||
let match = null;
|
||||
while ((match = regex.exec(data)) !== null) {
|
||||
|
||||
while ((match = linkRegex.exec(data)) !== null) {
|
||||
if (match.index > endIndex) {
|
||||
markdownBlocks.push(data.substr(endIndex, match.index - endIndex));
|
||||
}
|
||||
|
||||
markdownBlocks.push(<Link key={match.index} to={match[2]}>{match[1]}</Link>);
|
||||
endIndex = match.index + match[0].length;
|
||||
}
|
||||
|
||||
if (endIndex !== data.length) {
|
||||
if (endIndex !== data.length && markdownBlocks.length > 0) {
|
||||
markdownBlocks.push(data.substr(endIndex, data.length - endIndex));
|
||||
}
|
||||
|
||||
const codeRegex = RegExp(/(?=`)`(?!`)[^`]*(?=`)`(?!`)/g);
|
||||
|
||||
endIndex = 0;
|
||||
match = null;
|
||||
let matchedCode = false;
|
||||
|
||||
while ((match = codeRegex.exec(data)) !== null) {
|
||||
matchedCode = true;
|
||||
|
||||
if (match.index > endIndex) {
|
||||
markdownBlocks.push(data.substr(endIndex, match.index - endIndex));
|
||||
}
|
||||
|
||||
markdownBlocks.push(<code key={`code-${match.index}`}>{match[0].substring(1, match[0].length - 1)}</code>);
|
||||
endIndex = match.index + match[0].length;
|
||||
}
|
||||
|
||||
if (endIndex !== data.length && markdownBlocks.length > 0 && matchedCode) {
|
||||
markdownBlocks.push(data.substr(endIndex, data.length - endIndex));
|
||||
}
|
||||
|
||||
if (markdownBlocks.length === 0) {
|
||||
markdownBlocks.push(data);
|
||||
}
|
||||
}
|
||||
|
||||
return <span className={className}>{markdownBlocks}</span>;
|
||||
|
||||
@@ -33,7 +33,7 @@ class FilterMenuContent extends Component {
|
||||
selectedFilterKey={selectedFilterKey}
|
||||
onPress={onFilterSelect}
|
||||
>
|
||||
{filter.label}
|
||||
{typeof filter.label === 'function' ? filter.label() : filter.label}
|
||||
</FilterMenuItem>
|
||||
);
|
||||
})
|
||||
|
||||
@@ -7,7 +7,7 @@ function ErrorPage(props) {
|
||||
const {
|
||||
version,
|
||||
isLocalStorageSupported,
|
||||
hasTranslationsError,
|
||||
translationsError,
|
||||
moviesError,
|
||||
customFiltersError,
|
||||
tagsError,
|
||||
@@ -21,8 +21,8 @@ function ErrorPage(props) {
|
||||
|
||||
if (!isLocalStorageSupported) {
|
||||
errorMessage = 'Local Storage is not supported or disabled. A plugin or private browsing may have disabled it.';
|
||||
} else if (hasTranslationsError) {
|
||||
errorMessage = 'Failed to load translations from API';
|
||||
} else if (translationsError) {
|
||||
errorMessage = getErrorMessage(translationsError, 'Failed to load translations from API');
|
||||
} else if (moviesError) {
|
||||
errorMessage = getErrorMessage(moviesError, 'Failed to load movie from API');
|
||||
} else if (customFiltersError) {
|
||||
@@ -55,7 +55,7 @@ function ErrorPage(props) {
|
||||
ErrorPage.propTypes = {
|
||||
version: PropTypes.string.isRequired,
|
||||
isLocalStorageSupported: PropTypes.bool.isRequired,
|
||||
hasTranslationsError: PropTypes.bool.isRequired,
|
||||
translationsError: PropTypes.object,
|
||||
moviesError: PropTypes.object,
|
||||
customFiltersError: PropTypes.object,
|
||||
tagsError: PropTypes.object,
|
||||
|
||||
@@ -3,7 +3,7 @@ import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { createSelector } from 'reselect';
|
||||
import { saveDimensions, setIsSidebarVisible } from 'Store/Actions/appActions';
|
||||
import { fetchTranslations, saveDimensions, setIsSidebarVisible } from 'Store/Actions/appActions';
|
||||
import { fetchCustomFilters } from 'Store/Actions/customFilterActions';
|
||||
import { fetchMovies } from 'Store/Actions/movieActions';
|
||||
import { fetchMovieCollections } from 'Store/Actions/movieCollectionActions';
|
||||
@@ -53,6 +53,7 @@ const selectIsPopulated = createSelector(
|
||||
(state) => state.settings.importLists.isPopulated,
|
||||
(state) => state.system.status.isPopulated,
|
||||
(state) => state.movieCollections.isPopulated,
|
||||
(state) => state.app.translations.isPopulated,
|
||||
(
|
||||
customFiltersIsPopulated,
|
||||
tagsIsPopulated,
|
||||
@@ -62,7 +63,8 @@ const selectIsPopulated = createSelector(
|
||||
indexerFlagsIsPopulated,
|
||||
importListsIsPopulated,
|
||||
systemStatusIsPopulated,
|
||||
movieCollectionsIsPopulated
|
||||
movieCollectionsIsPopulated,
|
||||
translationsIsPopulated
|
||||
) => {
|
||||
return (
|
||||
customFiltersIsPopulated &&
|
||||
@@ -73,7 +75,8 @@ const selectIsPopulated = createSelector(
|
||||
indexerFlagsIsPopulated &&
|
||||
importListsIsPopulated &&
|
||||
systemStatusIsPopulated &&
|
||||
movieCollectionsIsPopulated
|
||||
movieCollectionsIsPopulated &&
|
||||
translationsIsPopulated
|
||||
);
|
||||
}
|
||||
);
|
||||
@@ -88,6 +91,7 @@ const selectErrors = createSelector(
|
||||
(state) => state.settings.importLists.error,
|
||||
(state) => state.system.status.error,
|
||||
(state) => state.movieCollections.error,
|
||||
(state) => state.app.translations.error,
|
||||
(
|
||||
customFiltersError,
|
||||
tagsError,
|
||||
@@ -97,7 +101,8 @@ const selectErrors = createSelector(
|
||||
indexerFlagsError,
|
||||
importListsError,
|
||||
systemStatusError,
|
||||
movieCollectionsError
|
||||
movieCollectionsError,
|
||||
translationsError
|
||||
) => {
|
||||
const hasError = !!(
|
||||
customFiltersError ||
|
||||
@@ -108,7 +113,8 @@ const selectErrors = createSelector(
|
||||
indexerFlagsError ||
|
||||
importListsError ||
|
||||
systemStatusError ||
|
||||
movieCollectionsError
|
||||
movieCollectionsError ||
|
||||
translationsError
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -121,7 +127,8 @@ const selectErrors = createSelector(
|
||||
indexerFlagsError,
|
||||
importListsError,
|
||||
systemStatusError,
|
||||
movieCollectionsError
|
||||
movieCollectionsError,
|
||||
translationsError
|
||||
};
|
||||
}
|
||||
);
|
||||
@@ -183,6 +190,9 @@ function createMapDispatchToProps(dispatch, props) {
|
||||
dispatchFetchStatus() {
|
||||
dispatch(fetchStatus());
|
||||
},
|
||||
dispatchFetchTranslations() {
|
||||
dispatch(fetchTranslations());
|
||||
},
|
||||
onResize(dimensions) {
|
||||
dispatch(saveDimensions(dimensions));
|
||||
},
|
||||
@@ -217,6 +227,7 @@ class PageConnector extends Component {
|
||||
this.props.dispatchFetchImportLists();
|
||||
this.props.dispatchFetchUISettings();
|
||||
this.props.dispatchFetchStatus();
|
||||
this.props.dispatchFetchTranslations();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,7 +243,6 @@ class PageConnector extends Component {
|
||||
|
||||
render() {
|
||||
const {
|
||||
hasTranslationsError,
|
||||
isPopulated,
|
||||
hasError,
|
||||
dispatchFetchMovies,
|
||||
@@ -244,15 +254,15 @@ class PageConnector extends Component {
|
||||
dispatchFetchImportLists,
|
||||
dispatchFetchUISettings,
|
||||
dispatchFetchStatus,
|
||||
dispatchFetchTranslations,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
if (hasTranslationsError || hasError || !this.state.isLocalStorageSupported) {
|
||||
if (hasError || !this.state.isLocalStorageSupported) {
|
||||
return (
|
||||
<ErrorPage
|
||||
{...this.state}
|
||||
{...otherProps}
|
||||
hasTranslationsError={hasTranslationsError}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -273,7 +283,6 @@ class PageConnector extends Component {
|
||||
}
|
||||
|
||||
PageConnector.propTypes = {
|
||||
hasTranslationsError: PropTypes.bool.isRequired,
|
||||
isPopulated: PropTypes.bool.isRequired,
|
||||
hasError: PropTypes.bool.isRequired,
|
||||
isSidebarVisible: PropTypes.bool.isRequired,
|
||||
@@ -287,6 +296,7 @@ PageConnector.propTypes = {
|
||||
dispatchFetchImportLists: PropTypes.func.isRequired,
|
||||
dispatchFetchUISettings: PropTypes.func.isRequired,
|
||||
dispatchFetchStatus: PropTypes.func.isRequired,
|
||||
dispatchFetchTranslations: PropTypes.func.isRequired,
|
||||
onSidebarVisibleChange: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
|
||||
@@ -21,24 +21,24 @@ const SIDEBAR_WIDTH = parseInt(dimensions.sidebarWidth);
|
||||
const links = [
|
||||
{
|
||||
iconName: icons.MOVIE_CONTINUING,
|
||||
title: translate('Movies'),
|
||||
title: () => translate('Movies'),
|
||||
to: '/',
|
||||
alias: '/movies',
|
||||
children: [
|
||||
{
|
||||
title: translate('AddNew'),
|
||||
title: () => translate('AddNew'),
|
||||
to: '/add/new'
|
||||
},
|
||||
{
|
||||
title: translate('ImportLibrary'),
|
||||
title: () => translate('ImportLibrary'),
|
||||
to: '/add/import'
|
||||
},
|
||||
{
|
||||
title: translate('Collections'),
|
||||
title: () => translate('Collections'),
|
||||
to: '/collections'
|
||||
},
|
||||
{
|
||||
title: translate('Discover'),
|
||||
title: () => translate('Discover'),
|
||||
to: '/add/discover'
|
||||
}
|
||||
]
|
||||
@@ -46,26 +46,26 @@ const links = [
|
||||
|
||||
{
|
||||
iconName: icons.CALENDAR,
|
||||
title: translate('Calendar'),
|
||||
title: () => translate('Calendar'),
|
||||
to: '/calendar'
|
||||
},
|
||||
|
||||
{
|
||||
iconName: icons.ACTIVITY,
|
||||
title: translate('Activity'),
|
||||
title: () => translate('Activity'),
|
||||
to: '/activity/queue',
|
||||
children: [
|
||||
{
|
||||
title: translate('Queue'),
|
||||
title: () => translate('Queue'),
|
||||
to: '/activity/queue',
|
||||
statusComponent: QueueStatusConnector
|
||||
},
|
||||
{
|
||||
title: translate('History'),
|
||||
title: () => translate('History'),
|
||||
to: '/activity/history'
|
||||
},
|
||||
{
|
||||
title: translate('Blocklist'),
|
||||
title: () => translate('Blocklist'),
|
||||
to: '/activity/blocklist'
|
||||
}
|
||||
]
|
||||
@@ -73,55 +73,55 @@ const links = [
|
||||
|
||||
{
|
||||
iconName: icons.SETTINGS,
|
||||
title: translate('Settings'),
|
||||
title: () => translate('Settings'),
|
||||
to: '/settings',
|
||||
children: [
|
||||
{
|
||||
title: translate('MediaManagement'),
|
||||
title: () => translate('MediaManagement'),
|
||||
to: '/settings/mediamanagement'
|
||||
},
|
||||
{
|
||||
title: translate('Profiles'),
|
||||
title: () => translate('Profiles'),
|
||||
to: '/settings/profiles'
|
||||
},
|
||||
{
|
||||
title: translate('Quality'),
|
||||
title: () => translate('Quality'),
|
||||
to: '/settings/quality'
|
||||
},
|
||||
{
|
||||
title: translate('CustomFormats'),
|
||||
title: () => translate('CustomFormats'),
|
||||
to: '/settings/customformats'
|
||||
},
|
||||
{
|
||||
title: translate('Indexers'),
|
||||
title: () => translate('Indexers'),
|
||||
to: '/settings/indexers'
|
||||
},
|
||||
{
|
||||
title: translate('DownloadClients'),
|
||||
title: () => translate('DownloadClients'),
|
||||
to: '/settings/downloadclients'
|
||||
},
|
||||
{
|
||||
title: translate('Lists'),
|
||||
title: () => translate('Lists'),
|
||||
to: '/settings/importlists'
|
||||
},
|
||||
{
|
||||
title: translate('Connect'),
|
||||
title: () => translate('Connect'),
|
||||
to: '/settings/connect'
|
||||
},
|
||||
{
|
||||
title: translate('Metadata'),
|
||||
title: () => translate('Metadata'),
|
||||
to: '/settings/metadata'
|
||||
},
|
||||
{
|
||||
title: translate('Tags'),
|
||||
title: () => translate('Tags'),
|
||||
to: '/settings/tags'
|
||||
},
|
||||
{
|
||||
title: translate('General'),
|
||||
title: () => translate('General'),
|
||||
to: '/settings/general'
|
||||
},
|
||||
{
|
||||
title: translate('UI'),
|
||||
title: () => translate('UI'),
|
||||
to: '/settings/ui'
|
||||
}
|
||||
]
|
||||
@@ -129,32 +129,32 @@ const links = [
|
||||
|
||||
{
|
||||
iconName: icons.SYSTEM,
|
||||
title: translate('System'),
|
||||
title: () => translate('System'),
|
||||
to: '/system/status',
|
||||
children: [
|
||||
{
|
||||
title: translate('Status'),
|
||||
title: () => translate('Status'),
|
||||
to: '/system/status',
|
||||
statusComponent: HealthStatusConnector
|
||||
},
|
||||
{
|
||||
title: translate('Tasks'),
|
||||
title: () => translate('Tasks'),
|
||||
to: '/system/tasks'
|
||||
},
|
||||
{
|
||||
title: translate('Backup'),
|
||||
title: () => translate('Backup'),
|
||||
to: '/system/backup'
|
||||
},
|
||||
{
|
||||
title: translate('Updates'),
|
||||
title: () => translate('Updates'),
|
||||
to: '/system/updates'
|
||||
},
|
||||
{
|
||||
title: translate('Events'),
|
||||
title: () => translate('Events'),
|
||||
to: '/system/events'
|
||||
},
|
||||
{
|
||||
title: translate('LogFiles'),
|
||||
title: () => translate('LogFiles'),
|
||||
to: '/system/logs/files'
|
||||
}
|
||||
]
|
||||
|
||||
@@ -64,7 +64,7 @@ class PageSidebarItem extends Component {
|
||||
}
|
||||
|
||||
<span className={isChildItem ? styles.noIcon : null}>
|
||||
{title}
|
||||
{typeof title === 'function' ? title() : title}
|
||||
</span>
|
||||
|
||||
{
|
||||
@@ -88,7 +88,7 @@ class PageSidebarItem extends Component {
|
||||
|
||||
PageSidebarItem.propTypes = {
|
||||
iconName: PropTypes.object,
|
||||
title: PropTypes.string.isRequired,
|
||||
title: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired,
|
||||
to: PropTypes.string.isRequired,
|
||||
isActive: PropTypes.bool,
|
||||
isActiveParent: PropTypes.bool,
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import React from 'react';
|
||||
|
||||
type PropertyFunction<T> = () => T;
|
||||
|
||||
interface Column {
|
||||
name: string;
|
||||
label: string | React.ReactNode;
|
||||
label: string | PropertyFunction<string> | React.ReactNode;
|
||||
columnLabel?: string;
|
||||
isSortable?: boolean;
|
||||
isVisible: boolean;
|
||||
|
||||
@@ -107,7 +107,7 @@ function Table(props) {
|
||||
{...getTableHeaderCellProps(otherProps)}
|
||||
{...column}
|
||||
>
|
||||
{column.label}
|
||||
{typeof column.label === 'function' ? column.label() : column.label}
|
||||
</TableHeaderCell>
|
||||
);
|
||||
})
|
||||
|
||||
@@ -30,6 +30,7 @@ class TableHeaderCell extends Component {
|
||||
const {
|
||||
className,
|
||||
name,
|
||||
label,
|
||||
columnLabel,
|
||||
isSortable,
|
||||
isVisible,
|
||||
@@ -53,7 +54,8 @@ class TableHeaderCell extends Component {
|
||||
{...otherProps}
|
||||
component="th"
|
||||
className={className}
|
||||
title={columnLabel}
|
||||
label={typeof label === 'function' ? label() : label}
|
||||
title={typeof columnLabel === 'function' ? columnLabel() : columnLabel}
|
||||
onPress={this.onPress}
|
||||
>
|
||||
{children}
|
||||
@@ -77,7 +79,8 @@ class TableHeaderCell extends Component {
|
||||
TableHeaderCell.propTypes = {
|
||||
className: PropTypes.string,
|
||||
name: PropTypes.string.isRequired,
|
||||
columnLabel: PropTypes.string,
|
||||
label: PropTypes.oneOfType([PropTypes.string, PropTypes.func, PropTypes.node]),
|
||||
columnLabel: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
|
||||
isSortable: PropTypes.bool,
|
||||
isVisible: PropTypes.bool,
|
||||
isModifiable: PropTypes.bool,
|
||||
|
||||
@@ -35,7 +35,7 @@ function TableOptionsColumn(props) {
|
||||
isDisabled={isModifiable === false}
|
||||
onChange={onVisibleChange}
|
||||
/>
|
||||
{label}
|
||||
{typeof label === 'function' ? label() : label}
|
||||
</label>
|
||||
|
||||
{
|
||||
@@ -56,7 +56,7 @@ function TableOptionsColumn(props) {
|
||||
|
||||
TableOptionsColumn.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
label: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired,
|
||||
isVisible: PropTypes.bool.isRequired,
|
||||
isModifiable: PropTypes.bool.isRequired,
|
||||
index: PropTypes.number.isRequired,
|
||||
|
||||
@@ -112,7 +112,7 @@ class TableOptionsColumnDragSource extends Component {
|
||||
|
||||
<TableOptionsColumn
|
||||
name={name}
|
||||
label={label}
|
||||
label={typeof label === 'function' ? label() : label}
|
||||
isVisible={isVisible}
|
||||
isModifiable={isModifiable}
|
||||
index={index}
|
||||
@@ -138,7 +138,7 @@ class TableOptionsColumnDragSource extends Component {
|
||||
|
||||
TableOptionsColumnDragSource.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
label: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired,
|
||||
isVisible: PropTypes.bool.isRequired,
|
||||
isModifiable: PropTypes.bool.isRequired,
|
||||
index: PropTypes.number.isRequired,
|
||||
|
||||
@@ -6,47 +6,65 @@ import translate from 'Utilities/String/translate';
|
||||
export const shortcuts = {
|
||||
OPEN_KEYBOARD_SHORTCUTS_MODAL: {
|
||||
key: '?',
|
||||
name: translate('OpenThisModal')
|
||||
get name() {
|
||||
return translate('OpenThisModal');
|
||||
}
|
||||
},
|
||||
|
||||
CLOSE_MODAL: {
|
||||
key: 'Esc',
|
||||
name: translate('CloseCurrentModal')
|
||||
get name() {
|
||||
return translate('CloseCurrentModal');
|
||||
}
|
||||
},
|
||||
|
||||
ACCEPT_CONFIRM_MODAL: {
|
||||
key: 'Enter',
|
||||
name: translate('AcceptConfirmationModal')
|
||||
get name() {
|
||||
return translate('AcceptConfirmationModal');
|
||||
}
|
||||
},
|
||||
|
||||
MOVIE_SEARCH_INPUT: {
|
||||
key: 's',
|
||||
name: translate('FocusSearchBox')
|
||||
get name() {
|
||||
return translate('FocusSearchBox');
|
||||
}
|
||||
},
|
||||
|
||||
SAVE_SETTINGS: {
|
||||
key: 'mod+s',
|
||||
name: translate('SaveSettings')
|
||||
get name() {
|
||||
return translate('SaveSettings');
|
||||
}
|
||||
},
|
||||
|
||||
SCROLL_TOP: {
|
||||
key: 'mod+home',
|
||||
name: translate('MovieIndexScrollTop')
|
||||
get name() {
|
||||
return translate('MovieIndexScrollTop');
|
||||
}
|
||||
},
|
||||
|
||||
SCROLL_BOTTOM: {
|
||||
key: 'mod+end',
|
||||
name: translate('MovieIndexScrollBottom')
|
||||
get name() {
|
||||
return translate('MovieIndexScrollBottom');
|
||||
}
|
||||
},
|
||||
|
||||
DETAILS_NEXT: {
|
||||
key: '→',
|
||||
name: translate('MovieDetailsNextMovie')
|
||||
get name() {
|
||||
return translate('MovieDetailsNextMovie');
|
||||
}
|
||||
},
|
||||
|
||||
DETAILS_PREVIOUS: {
|
||||
key: '←',
|
||||
name: translate('MovieDetailsPreviousMovie')
|
||||
get name() {
|
||||
return translate('MovieDetailsPreviousMovie');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -14,9 +14,24 @@ import { inputTypes } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
||||
const posterSizeOptions = [
|
||||
{ key: 'small', value: translate('Small') },
|
||||
{ key: 'medium', value: translate('Medium') },
|
||||
{ key: 'large', value: translate('Large') }
|
||||
{
|
||||
key: 'small',
|
||||
get value() {
|
||||
return translate('Small');
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'medium',
|
||||
get value() {
|
||||
return translate('Medium');
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'large',
|
||||
get value() {
|
||||
return translate('Large');
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
class DiscoverMovieOverviewOptionsModalContent extends Component {
|
||||
|
||||
@@ -14,9 +14,24 @@ import { inputTypes } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
||||
const posterSizeOptions = [
|
||||
{ key: 'small', value: translate('Small') },
|
||||
{ key: 'medium', value: translate('Medium') },
|
||||
{ key: 'large', value: translate('Large') }
|
||||
{
|
||||
key: 'small',
|
||||
get value() {
|
||||
return translate('Small');
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'medium',
|
||||
get value() {
|
||||
return translate('Medium');
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'large',
|
||||
get value() {
|
||||
return translate('Large');
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
class DiscoverMoviePosterOptionsModalContent extends Component {
|
||||
|
||||
@@ -111,7 +111,7 @@ class DiscoverMovieHeader extends Component {
|
||||
isSortable={isSortable}
|
||||
{...otherProps}
|
||||
>
|
||||
{label}
|
||||
{typeof label === 'function' ? label() : label}
|
||||
</VirtualTableHeaderCell>
|
||||
);
|
||||
})
|
||||
|
||||
@@ -25,11 +25,11 @@ import styles from './InteractiveImportSelectFolderModalContent.css';
|
||||
const recentFoldersColumns = [
|
||||
{
|
||||
name: 'folder',
|
||||
label: translate('Folder'),
|
||||
label: () => translate('Folder'),
|
||||
},
|
||||
{
|
||||
name: 'lastUsed',
|
||||
label: translate('LastUsed'),
|
||||
label: () => translate('LastUsed'),
|
||||
},
|
||||
{
|
||||
name: 'actions',
|
||||
|
||||
@@ -71,36 +71,36 @@ type OnSelectedChangeCallback = React.ComponentProps<
|
||||
const COLUMNS = [
|
||||
{
|
||||
name: 'relativePath',
|
||||
label: translate('RelativePath'),
|
||||
label: () => translate('RelativePath'),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'movie',
|
||||
label: translate('Movie'),
|
||||
label: () => translate('Movie'),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'releaseGroup',
|
||||
label: translate('ReleaseGroup'),
|
||||
label: () => translate('ReleaseGroup'),
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'quality',
|
||||
label: translate('Quality'),
|
||||
label: () => translate('Quality'),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'languages',
|
||||
label: translate('Languages'),
|
||||
label: () => translate('Languages'),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'size',
|
||||
label: translate('Size'),
|
||||
label: () => translate('Size'),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
},
|
||||
@@ -108,7 +108,7 @@ const COLUMNS = [
|
||||
name: 'customFormats',
|
||||
label: React.createElement(Icon, {
|
||||
name: icons.INTERACTIVE,
|
||||
title: translate('CustomFormat'),
|
||||
title: () => translate('CustomFormat'),
|
||||
}),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
@@ -127,11 +127,17 @@ const COLUMNS = [
|
||||
const importModeOptions = [
|
||||
{
|
||||
key: 'chooseImportMode',
|
||||
value: translate('ChooseImportMode'),
|
||||
value: () => translate('ChooseImportMode'),
|
||||
disabled: true,
|
||||
},
|
||||
{ key: 'move', value: translate('MoveFiles') },
|
||||
{ key: 'copy', value: translate('HardlinkCopyFiles') },
|
||||
{
|
||||
key: 'move',
|
||||
value: () => translate('MoveFiles'),
|
||||
},
|
||||
{
|
||||
key: 'copy',
|
||||
value: () => translate('HardlinkCopyFiles'),
|
||||
},
|
||||
];
|
||||
|
||||
function isSameMovieFile(
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
import { SelectStateInputProps } from 'typings/props';
|
||||
import Rejection from 'typings/Rejection';
|
||||
import formatBytes from 'Utilities/Number/formatBytes';
|
||||
import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import InteractiveImportRowCellPlaceholder from './InteractiveImportRowCellPlaceholder';
|
||||
import styles from './InteractiveImportRow.css';
|
||||
@@ -45,6 +46,7 @@ interface InteractiveImportRowProps {
|
||||
languages?: Language[];
|
||||
size: number;
|
||||
customFormats?: object[];
|
||||
customFormatScore?: number;
|
||||
rejections: Rejection[];
|
||||
columns: Column[];
|
||||
movieFileId?: number;
|
||||
@@ -66,6 +68,7 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
|
||||
releaseGroup,
|
||||
size,
|
||||
customFormats,
|
||||
customFormatScore,
|
||||
rejections,
|
||||
isSelected,
|
||||
modalTitle,
|
||||
@@ -293,8 +296,11 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
|
||||
<TableRowCell>
|
||||
{customFormats?.length ? (
|
||||
<Popover
|
||||
anchor={<Icon name={icons.INTERACTIVE} />}
|
||||
title={translate('Formats')}
|
||||
anchor={formatCustomFormatScore(
|
||||
customFormatScore,
|
||||
customFormats.length
|
||||
)}
|
||||
title={translate('CustomFormats')}
|
||||
body={
|
||||
<div className={styles.customFormatTooltip}>
|
||||
<MovieFormats formats={customFormats} />
|
||||
|
||||
@@ -29,22 +29,22 @@ import styles from './SelectMovieModalContent.css';
|
||||
const columns = [
|
||||
{
|
||||
name: 'title',
|
||||
label: translate('Title'),
|
||||
label: () => translate('Title'),
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'year',
|
||||
label: translate('Year'),
|
||||
label: () => translate('Year'),
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'imdbId',
|
||||
label: translate('ImdbId'),
|
||||
label: () => translate('ImdbId'),
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'tmdbId',
|
||||
label: translate('TmdbId'),
|
||||
label: () => translate('TmdbId'),
|
||||
isVisible: true,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -30,7 +30,7 @@ function SelectMovieModalTableHeader(props: SelectMovieModalTableHeaderProps) {
|
||||
}
|
||||
name={name}
|
||||
>
|
||||
{label}
|
||||
{typeof label === 'function' ? label() : label}
|
||||
</VirtualTableHeaderCell>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -13,13 +13,13 @@ import styles from './InteractiveSearchContent.css';
|
||||
const columns = [
|
||||
{
|
||||
name: 'protocol',
|
||||
label: translate('Source'),
|
||||
label: () => translate('Source'),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'age',
|
||||
label: translate('Age'),
|
||||
label: () => translate('Age'),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
@@ -39,50 +39,50 @@ const columns = [
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
label: translate('Title'),
|
||||
label: () => translate('Title'),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'indexer',
|
||||
label: translate('Indexer'),
|
||||
label: () => translate('Indexer'),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'history',
|
||||
label: translate('History'),
|
||||
label: () => translate('History'),
|
||||
isSortable: true,
|
||||
fixedSortDirection: sortDirections.ASCENDING,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'size',
|
||||
label: translate('Size'),
|
||||
label: () => translate('Size'),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'peers',
|
||||
label: translate('Peers'),
|
||||
label: () => translate('Peers'),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'languages',
|
||||
label: translate('Language'),
|
||||
label: () => translate('Language'),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'qualityWeight',
|
||||
label: translate('Quality'),
|
||||
label: () => translate('Quality'),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'customFormat',
|
||||
label: translate('Formats'),
|
||||
label: () => translate('Formats'),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
@@ -90,7 +90,7 @@ const columns = [
|
||||
name: 'customFormatScore',
|
||||
label: React.createElement(Icon, {
|
||||
name: icons.SCORE,
|
||||
title: translate('CustomFormatScore')
|
||||
title: () => translate('CustomFormatScore')
|
||||
}),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
|
||||
@@ -31,6 +31,18 @@ function getDownloadIcon(isGrabbing, isGrabbed, grabError) {
|
||||
return icons.DOWNLOAD;
|
||||
}
|
||||
|
||||
function getDownloadKind(isGrabbed, grabError) {
|
||||
if (isGrabbed) {
|
||||
return kinds.SUCCESS;
|
||||
}
|
||||
|
||||
if (grabError) {
|
||||
return kinds.DANGER;
|
||||
}
|
||||
|
||||
return kinds.DEFAULT;
|
||||
}
|
||||
|
||||
function getDownloadTooltip(isGrabbing, isGrabbed, grabError) {
|
||||
if (isGrabbing) {
|
||||
return '';
|
||||
@@ -148,7 +160,7 @@ class InteractiveSearchRow extends Component {
|
||||
<TableRowCell className={styles.download}>
|
||||
<SpinnerIconButton
|
||||
name={getDownloadIcon(isGrabbing, isGrabbed, grabError)}
|
||||
kind={grabError ? kinds.DANGER : kinds.DEFAULT}
|
||||
kind={getDownloadKind(isGrabbed, grabError)}
|
||||
title={getDownloadTooltip(isGrabbing, isGrabbed, grabError)}
|
||||
isDisabled={isGrabbed}
|
||||
isSpinning={isGrabbing}
|
||||
|
||||
@@ -10,17 +10,17 @@ import styles from './MovieTitlesTableContent.css';
|
||||
const columns = [
|
||||
{
|
||||
name: 'altTitle',
|
||||
label: translate('AlternativeTitle'),
|
||||
label: () => translate('AlternativeTitle'),
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'language',
|
||||
label: translate('Language'),
|
||||
label: () => translate('Language'),
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'sourceType',
|
||||
label: translate('Type'),
|
||||
label: () => translate('Type'),
|
||||
isVisible: true
|
||||
}
|
||||
];
|
||||
|
||||
@@ -3,3 +3,9 @@
|
||||
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.tagInternalInput {
|
||||
composes: internalInput from '~Components/Form/TagInput.css';
|
||||
|
||||
flex: 0 0 100%;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Please do not change this file!
|
||||
interface CssExports {
|
||||
'deleteButton': string;
|
||||
'tagInternalInput': string;
|
||||
}
|
||||
export const cssExports: CssExports;
|
||||
export default cssExports;
|
||||
|
||||
@@ -9,3 +9,9 @@
|
||||
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.customFormatScore {
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 55px;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Please do not change this file!
|
||||
interface CssExports {
|
||||
'actions': string;
|
||||
'customFormatScore': string;
|
||||
'sourceTitle': string;
|
||||
}
|
||||
export const cssExports: CssExports;
|
||||
|
||||
@@ -7,7 +7,8 @@ import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import TableRow from 'Components/Table/TableRow';
|
||||
import { icons, kinds } from 'Helpers/Props';
|
||||
import Tooltip from 'Components/Tooltip/Tooltip';
|
||||
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
|
||||
import MovieFormats from 'Movie/MovieFormats';
|
||||
import MovieLanguage from 'Movie/MovieLanguage';
|
||||
import MovieQuality from 'Movie/MovieQuality';
|
||||
@@ -100,14 +101,21 @@ class MovieHistoryRow extends Component {
|
||||
/>
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell key={name}>
|
||||
<TableRowCell>
|
||||
<MovieFormats
|
||||
formats={customFormats}
|
||||
/>
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell key={name}>
|
||||
{formatCustomFormatScore(customFormatScore)}
|
||||
<TableRowCell className={styles.customFormatScore}>
|
||||
<Tooltip
|
||||
anchor={formatCustomFormatScore(
|
||||
customFormatScore,
|
||||
customFormats.length
|
||||
)}
|
||||
tooltip={<MovieFormats formats={customFormats} />}
|
||||
position={tooltipPositions.TOP}
|
||||
/>
|
||||
</TableRowCell>
|
||||
|
||||
<RelativeDateCellConnector
|
||||
|
||||
@@ -17,22 +17,22 @@ const columns = [
|
||||
},
|
||||
{
|
||||
name: 'sourceTitle',
|
||||
label: translate('SourceTitle'),
|
||||
label: () => translate('SourceTitle'),
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'languages',
|
||||
label: translate('Languages'),
|
||||
label: () => translate('Languages'),
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'quality',
|
||||
label: translate('Quality'),
|
||||
label: () => translate('Quality'),
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'customFormats',
|
||||
label: translate('CustomFormats'),
|
||||
label: () => translate('CustomFormats'),
|
||||
isSortable: false,
|
||||
isVisible: true
|
||||
},
|
||||
@@ -47,7 +47,7 @@ const columns = [
|
||||
},
|
||||
{
|
||||
name: 'date',
|
||||
label: translate('Date'),
|
||||
label: () => translate('Date'),
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
|
||||
@@ -100,6 +100,15 @@ function MovieIndexSortMenu(props: MovieIndexSortMenuProps) {
|
||||
{translate('DigitalRelease')}
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
name="tmdbRating"
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
{translate('TmdbRating')}
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
name="imdbRating"
|
||||
sortKey={sortKey}
|
||||
@@ -110,12 +119,12 @@ function MovieIndexSortMenu(props: MovieIndexSortMenuProps) {
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
name="tmdbRating"
|
||||
name="rottenTomatoesRating"
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
{translate('TmdbRating')}
|
||||
{translate('RottenTomatoesRating')}
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useSelector } from 'react-redux';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||
import dimensions from 'Styles/Variables/dimensions';
|
||||
import QualityProfile from 'typings/QualityProfile';
|
||||
import { UiSettings } from 'typings/UiSettings';
|
||||
import formatDateTime from 'Utilities/Date/formatDateTime';
|
||||
import getRelativeDate from 'Utilities/Date/getRelativeDate';
|
||||
@@ -33,7 +34,7 @@ interface MovieIndexOverviewInfoProps {
|
||||
showSizeOnDisk: boolean;
|
||||
monitored: boolean;
|
||||
studio?: string;
|
||||
qualityProfile: object;
|
||||
qualityProfile?: QualityProfile;
|
||||
added?: string;
|
||||
path: string;
|
||||
sizeOnDisk?: number;
|
||||
@@ -100,13 +101,10 @@ function getInfoRowProps(
|
||||
};
|
||||
}
|
||||
|
||||
if (name === 'qualityProfileId') {
|
||||
if (name === 'qualityProfileId' && !!props.qualityProfile?.name) {
|
||||
return {
|
||||
title: 'Quality Profile',
|
||||
iconName: icons.PROFILE,
|
||||
// TODO: Type QualityProfile
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore ts(2339)
|
||||
label: props.qualityProfile.name,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -15,9 +15,24 @@ import translate from 'Utilities/String/translate';
|
||||
import selectOverviewOptions from '../selectOverviewOptions';
|
||||
|
||||
const posterSizeOptions = [
|
||||
{ key: 'small', value: translate('Small') },
|
||||
{ key: 'medium', value: translate('Medium') },
|
||||
{ key: 'large', value: translate('Large') },
|
||||
{
|
||||
key: 'small',
|
||||
get value() {
|
||||
return translate('Small');
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'medium',
|
||||
get value() {
|
||||
return translate('Medium');
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'large',
|
||||
get value() {
|
||||
return translate('Large');
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
interface MovieIndexOverviewOptionsModalContentProps {
|
||||
|
||||
@@ -2,10 +2,13 @@ import React, { useCallback, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { MOVIE_SEARCH, REFRESH_MOVIE } from 'Commands/commandNames';
|
||||
import Icon from 'Components/Icon';
|
||||
import ImdbRating from 'Components/ImdbRating';
|
||||
import Label from 'Components/Label';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import Link from 'Components/Link/Link';
|
||||
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
||||
import RottenTomatoRating from 'Components/RottenTomatoRating';
|
||||
import TmdbRating from 'Components/TmdbRating';
|
||||
import Popover from 'Components/Tooltip/Popover';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import DeleteMovieModal from 'Movie/Delete/DeleteMovieModal';
|
||||
@@ -44,6 +47,9 @@ function MovieIndexPoster(props: MovieIndexPosterProps) {
|
||||
showQualityProfile,
|
||||
showCinemaRelease,
|
||||
showReleaseDate,
|
||||
showTmdbRating,
|
||||
showImdbRating,
|
||||
showRottenTomatoesRating,
|
||||
showSearchAction,
|
||||
} = useSelector(selectPosterOptions);
|
||||
|
||||
@@ -229,7 +235,7 @@ function MovieIndexPoster(props: MovieIndexPosterProps) {
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{showQualityProfile ? (
|
||||
{showQualityProfile && !!qualityProfile?.name ? (
|
||||
<div className={styles.title} title={translate('QualityProfile')}>
|
||||
{qualityProfile.name}
|
||||
</div>
|
||||
@@ -257,6 +263,24 @@ function MovieIndexPoster(props: MovieIndexPosterProps) {
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{showTmdbRating && !!ratings.tmdb ? (
|
||||
<div className={styles.title}>
|
||||
<TmdbRating ratings={ratings} iconSize={12} />
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{showImdbRating && !!ratings.imdb ? (
|
||||
<div className={styles.title}>
|
||||
<ImdbRating ratings={ratings} iconSize={12} />
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{showRottenTomatoesRating && !!ratings.rottenTomatoes ? (
|
||||
<div className={styles.title}>
|
||||
<RottenTomatoRating ratings={ratings} iconSize={12} />
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<MovieIndexPosterInfo
|
||||
studio={studio}
|
||||
qualityProfile={qualityProfile}
|
||||
@@ -279,6 +303,9 @@ function MovieIndexPoster(props: MovieIndexPosterProps) {
|
||||
certification={certification}
|
||||
originalTitle={originalTitle}
|
||||
originalLanguage={originalLanguage}
|
||||
showTmdbRating={showTmdbRating}
|
||||
showImdbRating={showImdbRating}
|
||||
showRottenTomatoesRating={showRottenTomatoesRating}
|
||||
/>
|
||||
|
||||
<EditMovieModalConnector
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import Icon from 'Components/Icon';
|
||||
import ImdbRating from 'Components/ImdbRating';
|
||||
import RottenTomatoRating from 'Components/RottenTomatoRating';
|
||||
import TmdbRating from 'Components/TmdbRating';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import { Language, Ratings } from 'Movie/Movie';
|
||||
@@ -14,7 +15,7 @@ import styles from './MovieIndexPosterInfo.css';
|
||||
interface MovieIndexPosterInfoProps {
|
||||
studio?: string;
|
||||
showQualityProfile: boolean;
|
||||
qualityProfile: QualityProfile;
|
||||
qualityProfile?: QualityProfile;
|
||||
added?: string;
|
||||
year: number;
|
||||
inCinemas?: string;
|
||||
@@ -33,6 +34,9 @@ interface MovieIndexPosterInfoProps {
|
||||
shortDateFormat: string;
|
||||
longDateFormat: string;
|
||||
timeFormat: string;
|
||||
showTmdbRating: boolean;
|
||||
showImdbRating: boolean;
|
||||
showRottenTomatoesRating: boolean;
|
||||
}
|
||||
|
||||
function MovieIndexPosterInfo(props: MovieIndexPosterInfoProps) {
|
||||
@@ -58,6 +62,9 @@ function MovieIndexPosterInfo(props: MovieIndexPosterInfoProps) {
|
||||
shortDateFormat,
|
||||
longDateFormat,
|
||||
timeFormat,
|
||||
showTmdbRating,
|
||||
showImdbRating,
|
||||
showRottenTomatoesRating,
|
||||
} = props;
|
||||
|
||||
if (sortKey === 'studio' && studio) {
|
||||
@@ -68,7 +75,11 @@ function MovieIndexPosterInfo(props: MovieIndexPosterInfoProps) {
|
||||
);
|
||||
}
|
||||
|
||||
if (sortKey === 'qualityProfileId' && !showQualityProfile) {
|
||||
if (
|
||||
sortKey === 'qualityProfileId' &&
|
||||
!showQualityProfile &&
|
||||
!!qualityProfile?.name
|
||||
) {
|
||||
return (
|
||||
<div className={styles.info} title={translate('QualityProfile')}>
|
||||
{qualityProfile.name}
|
||||
@@ -159,7 +170,15 @@ function MovieIndexPosterInfo(props: MovieIndexPosterInfoProps) {
|
||||
);
|
||||
}
|
||||
|
||||
if (sortKey === 'imdbRating' && !!ratings.imdb) {
|
||||
if (!showTmdbRating && sortKey === 'tmdbRating' && !!ratings.tmdb) {
|
||||
return (
|
||||
<div className={styles.info}>
|
||||
<TmdbRating ratings={ratings} iconSize={12} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!showImdbRating && sortKey === 'imdbRating' && !!ratings.imdb) {
|
||||
return (
|
||||
<div className={styles.info}>
|
||||
<ImdbRating ratings={ratings} iconSize={12} />
|
||||
@@ -167,10 +186,14 @@ function MovieIndexPosterInfo(props: MovieIndexPosterInfoProps) {
|
||||
);
|
||||
}
|
||||
|
||||
if (sortKey === 'tmdbRating' && !!ratings.tmdb) {
|
||||
if (
|
||||
!showRottenTomatoesRating &&
|
||||
sortKey === 'rottenTomatoesRating' &&
|
||||
!!ratings.rottenTomatoes
|
||||
) {
|
||||
return (
|
||||
<div className={styles.info}>
|
||||
<TmdbRating ratings={ratings} iconSize={12} />
|
||||
<RottenTomatoRating ratings={ratings} iconSize={12} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -145,6 +145,9 @@ export default function MovieIndexPosters(props: MovieIndexPostersProps) {
|
||||
showQualityProfile,
|
||||
showCinemaRelease,
|
||||
showReleaseDate,
|
||||
showTmdbRating,
|
||||
showImdbRating,
|
||||
showRottenTomatoesRating,
|
||||
} = posterOptions;
|
||||
|
||||
const nextAiringHeight = 19;
|
||||
@@ -176,12 +179,22 @@ export default function MovieIndexPosters(props: MovieIndexPostersProps) {
|
||||
heights.push(19);
|
||||
}
|
||||
|
||||
if (showTmdbRating) {
|
||||
heights.push(19);
|
||||
}
|
||||
|
||||
if (showImdbRating) {
|
||||
heights.push(19);
|
||||
}
|
||||
|
||||
if (showRottenTomatoesRating) {
|
||||
heights.push(19);
|
||||
}
|
||||
|
||||
switch (sortKey) {
|
||||
case 'studio':
|
||||
case 'added':
|
||||
case 'year':
|
||||
case 'imdbRating':
|
||||
case 'tmdbRating':
|
||||
case 'path':
|
||||
case 'sizeOnDisk':
|
||||
case 'originalTitle':
|
||||
@@ -204,6 +217,21 @@ export default function MovieIndexPosters(props: MovieIndexPostersProps) {
|
||||
heights.push(19);
|
||||
}
|
||||
break;
|
||||
case 'imdbRating':
|
||||
if (!showImdbRating) {
|
||||
heights.push(19);
|
||||
}
|
||||
break;
|
||||
case 'tmdbRating':
|
||||
if (!showTmdbRating) {
|
||||
heights.push(19);
|
||||
}
|
||||
break;
|
||||
case 'rottenTomatoesRating':
|
||||
if (!showRottenTomatoesRating) {
|
||||
heights.push(19);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// No need to add a height of 0
|
||||
}
|
||||
|
||||
@@ -15,9 +15,24 @@ import translate from 'Utilities/String/translate';
|
||||
import selectPosterOptions from '../selectPosterOptions';
|
||||
|
||||
const posterSizeOptions = [
|
||||
{ key: 'small', value: translate('Small') },
|
||||
{ key: 'medium', value: translate('Medium') },
|
||||
{ key: 'large', value: translate('Large') },
|
||||
{
|
||||
key: 'small',
|
||||
get value() {
|
||||
return translate('Small');
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'medium',
|
||||
get value() {
|
||||
return translate('Medium');
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'large',
|
||||
get value() {
|
||||
return translate('Large');
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
interface MovieIndexPosterOptionsModalContentProps {
|
||||
@@ -39,6 +54,9 @@ function MovieIndexPosterOptionsModalContent(
|
||||
showQualityProfile,
|
||||
showCinemaRelease,
|
||||
showReleaseDate,
|
||||
showTmdbRating,
|
||||
showImdbRating,
|
||||
showRottenTomatoesRating,
|
||||
showSearchAction,
|
||||
} = posterOptions;
|
||||
|
||||
@@ -141,6 +159,42 @@ function MovieIndexPosterOptionsModalContent(
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('ShowTmdbRating')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="showTmdbRating"
|
||||
value={showTmdbRating}
|
||||
helpText={translate('ShowTmdbRatingHelpText')}
|
||||
onChange={onPosterOptionChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('ShowImdbRating')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="showImdbRating"
|
||||
value={showImdbRating}
|
||||
helpText={translate('ShowImdbRatingHelpText')}
|
||||
onChange={onPosterOptionChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('ShowRottenTomatoesRating')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="showRottenTomatoesRating"
|
||||
value={showRottenTomatoesRating}
|
||||
helpText={translate('ShowRottenTomatoesRatingHelpText')}
|
||||
onChange={onPosterOptionChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('ShowSearch')}</FormLabel>
|
||||
|
||||
|
||||
@@ -29,9 +29,25 @@ interface EditMoviesModalContentProps {
|
||||
const NO_CHANGE = 'noChange';
|
||||
|
||||
const monitoredOptions = [
|
||||
{ key: NO_CHANGE, value: 'No Change', disabled: true },
|
||||
{ key: 'monitored', value: 'Monitored' },
|
||||
{ key: 'unmonitored', value: 'Unmonitored' },
|
||||
{
|
||||
key: NO_CHANGE,
|
||||
get value() {
|
||||
return translate('NoChange');
|
||||
},
|
||||
disabled: true,
|
||||
},
|
||||
{
|
||||
key: 'monitored',
|
||||
get value() {
|
||||
return translate('Monitored');
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'unmonitored',
|
||||
get value() {
|
||||
return translate('Unmonitored');
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
function EditMoviesModalContent(props: EditMoviesModalContentProps) {
|
||||
|
||||
@@ -166,7 +166,7 @@ function MovieIndexSelectFooter() {
|
||||
isDisabled={!anySelected || isOrganizingMovies}
|
||||
onPress={onOrganizePress}
|
||||
>
|
||||
{translate('Rename Files')}
|
||||
{translate('RenameFiles')}
|
||||
</SpinnerButton>
|
||||
|
||||
<SpinnerButton
|
||||
|
||||
@@ -210,7 +210,7 @@ function MovieIndexRow(props: MovieIndexRowProps) {
|
||||
if (name === 'qualityProfileId') {
|
||||
return (
|
||||
<VirtualTableRowCell key={name} className={styles[name]}>
|
||||
{qualityProfile.name}
|
||||
{qualityProfile?.name ?? ''}
|
||||
</VirtualTableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ function MovieIndexTableHeader(props: MovieIndexTableHeaderProps) {
|
||||
isSortable={isSortable}
|
||||
onSortPress={onSortPress}
|
||||
>
|
||||
{label}
|
||||
{typeof label === 'function' ? label() : label}
|
||||
</VirtualTableHeaderCell>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -35,7 +35,7 @@ interface Movie extends ModelBase {
|
||||
titleSlug: string;
|
||||
collection: Collection;
|
||||
studio: string;
|
||||
qualityProfile: object;
|
||||
qualityProfileId: number;
|
||||
added: string;
|
||||
year: number;
|
||||
inCinemas: string;
|
||||
|
||||
@@ -23,6 +23,7 @@ MoviePoster.propTypes = {
|
||||
};
|
||||
|
||||
MoviePoster.defaultProps = {
|
||||
...MovieImage.defaultProps,
|
||||
size: 250
|
||||
};
|
||||
|
||||
|
||||
@@ -39,3 +39,9 @@
|
||||
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.customFormatScore {
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 55px;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
interface CssExports {
|
||||
'actions': string;
|
||||
'age': string;
|
||||
'customFormatScore': string;
|
||||
'download': string;
|
||||
'formats': string;
|
||||
'language': string;
|
||||
|
||||
@@ -4,7 +4,8 @@ import IconButton from 'Components/Link/IconButton';
|
||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import TableRow from 'Components/Table/TableRow';
|
||||
import { icons, kinds } from 'Helpers/Props';
|
||||
import Tooltip from 'Components/Tooltip/Tooltip';
|
||||
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
|
||||
import MovieFormats from 'Movie/MovieFormats';
|
||||
import MovieLanguage from 'Movie/MovieLanguage';
|
||||
import MovieQuality from 'Movie/MovieQuality';
|
||||
@@ -12,6 +13,7 @@ import FileEditModal from 'MovieFile/Edit/FileEditModal';
|
||||
import MediaInfoConnector from 'MovieFile/MediaInfoConnector';
|
||||
import * as mediaInfoTypes from 'MovieFile/mediaInfoTypes';
|
||||
import formatBytes from 'Utilities/Number/formatBytes';
|
||||
import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import FileDetailsModal from '../FileDetailsModal';
|
||||
import MovieFileRowCellPlaceholder from './MovieFileRowCellPlaceholder';
|
||||
@@ -78,6 +80,7 @@ class MovieFileEditorRow extends Component {
|
||||
quality,
|
||||
qualityCutoffNotMet,
|
||||
customFormats,
|
||||
customFormatScore,
|
||||
languages
|
||||
} = this.props;
|
||||
|
||||
@@ -170,6 +173,19 @@ class MovieFileEditorRow extends Component {
|
||||
/>
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell
|
||||
className={styles.customFormatScore}
|
||||
>
|
||||
<Tooltip
|
||||
anchor={formatCustomFormatScore(
|
||||
customFormatScore,
|
||||
customFormats.length
|
||||
)}
|
||||
tooltip={<MovieFormats formats={customFormats} />}
|
||||
position={tooltipPositions.TOP}
|
||||
/>
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell className={styles.actions}>
|
||||
<IconButton
|
||||
title={translate('EditMovieFile')}
|
||||
@@ -225,6 +241,7 @@ MovieFileEditorRow.propTypes = {
|
||||
quality: PropTypes.object.isRequired,
|
||||
releaseGroup: PropTypes.string,
|
||||
customFormats: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
customFormatScore: PropTypes.number.isRequired,
|
||||
qualityCutoffNotMet: PropTypes.bool.isRequired,
|
||||
languages: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
mediaInfo: PropTypes.object,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Icon from 'Components/Icon';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
@@ -11,42 +12,50 @@ import styles from './MovieFileEditorTableContent.css';
|
||||
const columns = [
|
||||
{
|
||||
name: 'title',
|
||||
label: translate('RelativePath'),
|
||||
label: () => translate('RelativePath'),
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'videoCodec',
|
||||
label: translate('VideoCodec'),
|
||||
label: () => translate('VideoCodec'),
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'audioInfo',
|
||||
label: translate('AudioInfo'),
|
||||
label: () => translate('AudioInfo'),
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'size',
|
||||
label: translate('Size'),
|
||||
label: () => translate('Size'),
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'languages',
|
||||
label: translate('Languages'),
|
||||
label: () => translate('Languages'),
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'quality',
|
||||
label: translate('Quality'),
|
||||
label: () => translate('Quality'),
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'releaseGroup',
|
||||
label: translate('ReleaseGroup'),
|
||||
label: () => translate('ReleaseGroup'),
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'quality.customFormats',
|
||||
label: translate('Formats'),
|
||||
name: 'customFormats',
|
||||
label: () => translate('Formats'),
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'customFormatScore',
|
||||
label: React.createElement(Icon, {
|
||||
name: icons.SCORE,
|
||||
title: () => translate('CustomFormatScore')
|
||||
}),
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
|
||||
@@ -11,17 +11,17 @@ import styles from './ExtraFileTableContent.css';
|
||||
const columns = [
|
||||
{
|
||||
name: 'relativePath',
|
||||
label: translate('RelativePath'),
|
||||
label: () => translate('RelativePath'),
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'extension',
|
||||
label: translate('Extension'),
|
||||
label: () => translate('Extension'),
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'type',
|
||||
label: translate('Type'),
|
||||
label: () => translate('Type'),
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
|
||||
95
frontend/src/RootFolder/RootFolderRow.tsx
Normal file
95
frontend/src/RootFolder/RootFolderRow.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import Label from 'Components/Label';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import Link from 'Components/Link/Link';
|
||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import TableRow from 'Components/Table/TableRow';
|
||||
import { icons, kinds } from 'Helpers/Props';
|
||||
import { deleteRootFolder } from 'Store/Actions/rootFolderActions';
|
||||
import formatBytes from 'Utilities/Number/formatBytes';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './RootFolderRow.css';
|
||||
|
||||
interface RootFolderRowProps {
|
||||
id: number;
|
||||
path: string;
|
||||
accessible: boolean;
|
||||
freeSpace?: number;
|
||||
unmappedFolders: object[];
|
||||
}
|
||||
|
||||
function RootFolderRow(props: RootFolderRowProps) {
|
||||
const { id, path, accessible, freeSpace, unmappedFolders = [] } = props;
|
||||
|
||||
const isUnavailable = !accessible;
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
|
||||
const onDeletePress = useCallback(() => {
|
||||
setIsDeleteModalOpen(true);
|
||||
}, [setIsDeleteModalOpen]);
|
||||
|
||||
const onDeleteModalClose = useCallback(() => {
|
||||
setIsDeleteModalOpen(false);
|
||||
}, [setIsDeleteModalOpen]);
|
||||
|
||||
const onConfirmDelete = useCallback(() => {
|
||||
dispatch(deleteRootFolder({ id }));
|
||||
|
||||
setIsDeleteModalOpen(false);
|
||||
}, [dispatch, id]);
|
||||
|
||||
return (
|
||||
<TableRow>
|
||||
<TableRowCell>
|
||||
{isUnavailable ? (
|
||||
<div className={styles.unavailablePath}>
|
||||
{path}
|
||||
|
||||
<Label className={styles.unavailableLabel} kind={kinds.DANGER}>
|
||||
{translate('Unavailable')}
|
||||
</Label>
|
||||
</div>
|
||||
) : (
|
||||
<Link className={styles.link} to={`/add/import/${id}`}>
|
||||
{path}
|
||||
</Link>
|
||||
)}
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell className={styles.freeSpace}>
|
||||
{isUnavailable || isNaN(Number(freeSpace))
|
||||
? '-'
|
||||
: formatBytes(freeSpace)}
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell className={styles.unmappedFolders}>
|
||||
{isUnavailable ? '-' : unmappedFolders.length}
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell className={styles.actions}>
|
||||
<IconButton
|
||||
title={translate('RemoveRootFolder')}
|
||||
name={icons.REMOVE}
|
||||
onPress={onDeletePress}
|
||||
/>
|
||||
</TableRowCell>
|
||||
|
||||
<ConfirmModal
|
||||
isOpen={isDeleteModalOpen}
|
||||
kind={kinds.DANGER}
|
||||
title={translate('DeleteRootFolder')}
|
||||
message={translate('DeleteRootFolderMessageText', [path])}
|
||||
confirmLabel={translate('Delete')}
|
||||
onConfirm={onConfirmDelete}
|
||||
onCancel={onDeleteModalClose}
|
||||
/>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
|
||||
export default RootFolderRow;
|
||||
@@ -1,13 +0,0 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { deleteRootFolder } from 'Store/Actions/rootFolderActions';
|
||||
import RootFolderRow from './RootFolderRow';
|
||||
|
||||
function createMapDispatchToProps(dispatch, props) {
|
||||
return {
|
||||
onDeletePress() {
|
||||
dispatch(deleteRootFolder({ id: props.id }));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(null, createMapDispatchToProps)(RootFolderRow);
|
||||
@@ -11,17 +11,23 @@ import RootFolderRowConnector from './RootFolderRowConnector';
|
||||
const rootFolderColumns = [
|
||||
{
|
||||
name: 'path',
|
||||
label: translate('Path'),
|
||||
get label() {
|
||||
return translate('Path');
|
||||
},
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'freeSpace',
|
||||
label: translate('FreeSpace'),
|
||||
get label() {
|
||||
return translate('FreeSpace');
|
||||
},
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'unmappedFolders',
|
||||
label: translate('UnmappedFolders'),
|
||||
get label() {
|
||||
return translate('UnmappedFolders');
|
||||
},
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
|
||||
76
frontend/src/RootFolder/RootFolders.tsx
Normal file
76
frontend/src/RootFolder/RootFolders.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import Alert from 'Components/Alert';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
|
||||
import createRootFoldersSelector from 'Store/Selectors/createRootFoldersSelector';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import RootFolderRow from './RootFolderRow';
|
||||
|
||||
const rootFolderColumns = [
|
||||
{
|
||||
name: 'path',
|
||||
label: () => translate('Path'),
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'freeSpace',
|
||||
label: () => translate('FreeSpace'),
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'unmappedFolders',
|
||||
label: () => translate('UnmappedFolders'),
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'actions',
|
||||
isVisible: true,
|
||||
},
|
||||
];
|
||||
|
||||
function RootFolders() {
|
||||
const { isFetching, isPopulated, error, items } = useSelector(
|
||||
createRootFoldersSelector()
|
||||
);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchRootFolders());
|
||||
}, [dispatch]);
|
||||
|
||||
if (isFetching && !isPopulated) {
|
||||
return <LoadingIndicator />;
|
||||
}
|
||||
|
||||
if (!isFetching && !!error) {
|
||||
return (
|
||||
<Alert kind={kinds.DANGER}>{translate('UnableToLoadRootFolders')}</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Table columns={rootFolderColumns}>
|
||||
<TableBody>
|
||||
{items.map((rootFolder) => {
|
||||
return (
|
||||
<RootFolderRow
|
||||
key={rootFolder.id}
|
||||
id={rootFolder.id}
|
||||
path={rootFolder.path}
|
||||
accessible={rootFolder.accessible}
|
||||
freeSpace={rootFolder.freeSpace}
|
||||
unmappedFolders={rootFolder.unmappedFolders}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
}
|
||||
|
||||
export default RootFolders;
|
||||
@@ -1,46 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
|
||||
import RootFolders from './RootFolders';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.rootFolders,
|
||||
(rootFolders) => {
|
||||
return rootFolders;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
dispatchFetchRootFolders: fetchRootFolders
|
||||
};
|
||||
|
||||
class RootFoldersConnector extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
componentDidMount() {
|
||||
this.props.dispatchFetchRootFolders();
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
return (
|
||||
<RootFolders
|
||||
{...this.props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
RootFoldersConnector.propTypes = {
|
||||
dispatchFetchRootFolders: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(RootFoldersConnector);
|
||||
@@ -27,9 +27,25 @@ interface ManageDownloadClientsEditModalContentProps {
|
||||
const NO_CHANGE = 'noChange';
|
||||
|
||||
const enableOptions = [
|
||||
{ key: NO_CHANGE, value: translate('NoChange'), disabled: true },
|
||||
{ key: 'enabled', value: translate('Enabled') },
|
||||
{ key: 'disabled', value: translate('Disabled') },
|
||||
{
|
||||
key: NO_CHANGE,
|
||||
get value() {
|
||||
return translate('NoChange');
|
||||
},
|
||||
disabled: true,
|
||||
},
|
||||
{
|
||||
key: 'enabled',
|
||||
get value() {
|
||||
return translate('Enabled');
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'disabled',
|
||||
get value() {
|
||||
return translate('Disabled');
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
function ManageDownloadClientsEditModalContent(
|
||||
|
||||
@@ -36,37 +36,37 @@ type OnSelectedChangeCallback = React.ComponentProps<
|
||||
const COLUMNS = [
|
||||
{
|
||||
name: 'name',
|
||||
label: translate('Name'),
|
||||
label: () => translate('Name'),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'implementation',
|
||||
label: translate('Implementation'),
|
||||
label: () => translate('Implementation'),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'enable',
|
||||
label: translate('Enabled'),
|
||||
label: () => translate('Enabled'),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'priority',
|
||||
label: translate('Priority'),
|
||||
label: () => translate('Priority'),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'removeCompletedDownloads',
|
||||
label: translate('RemoveCompleted'),
|
||||
label: () => translate('RemoveCompleted'),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'removeFailedDownloads',
|
||||
label: translate('RemoveFailed'),
|
||||
label: () => translate('RemoveFailed'),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
},
|
||||
|
||||
@@ -72,9 +72,24 @@ function TagsModalContent(props: TagsModalContentProps) {
|
||||
}, [tags, applyTags, onApplyTagsPress]);
|
||||
|
||||
const applyTagsOptions = [
|
||||
{ key: 'add', value: translate('Add') },
|
||||
{ key: 'remove', value: translate('Remove') },
|
||||
{ key: 'replace', value: translate('Replace') },
|
||||
{
|
||||
key: 'add',
|
||||
get value() {
|
||||
return translate('Add');
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'remove',
|
||||
get value() {
|
||||
return translate('Remove');
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'replace',
|
||||
get value() {
|
||||
return translate('Replace');
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
|
||||
@@ -8,9 +8,24 @@ import { inputTypes } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
||||
const logLevelOptions = [
|
||||
{ key: 'info', value: translate('Info') },
|
||||
{ key: 'debug', value: translate('Debug') },
|
||||
{ key: 'trace', value: translate('Trace') }
|
||||
{
|
||||
key: 'info',
|
||||
get value() {
|
||||
return translate('Info');
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'debug',
|
||||
get value() {
|
||||
return translate('Debug');
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'trace',
|
||||
get value() {
|
||||
return translate('Trace');
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
function LoggingSettings(props) {
|
||||
|
||||
@@ -12,15 +12,45 @@ import { icons, inputTypes, kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
||||
const authenticationMethodOptions = [
|
||||
{ key: 'none', value: translate('None') },
|
||||
{ key: 'basic', value: translate('AuthBasic') },
|
||||
{ key: 'forms', value: translate('AuthForm') }
|
||||
{
|
||||
key: 'none',
|
||||
get value() {
|
||||
return translate('None');
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'basic',
|
||||
get value() {
|
||||
return translate('AuthBasic');
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'forms',
|
||||
get value() {
|
||||
return translate('AuthForm');
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const certificateValidationOptions = [
|
||||
{ key: 'enabled', value: translate('Enabled') },
|
||||
{ key: 'disabledForLocalAddresses', value: translate('CertValidationNoLocal') },
|
||||
{ key: 'disabled', value: translate('Disabled') }
|
||||
{
|
||||
key: 'enabled',
|
||||
get value() {
|
||||
return translate('Enabled');
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'disabledForLocalAddresses',
|
||||
get value() {
|
||||
return translate('CertValidationNoLocal');
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'disabled',
|
||||
get value() {
|
||||
return translate('Disabled');
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
class SecuritySettings extends Component {
|
||||
|
||||
@@ -26,9 +26,25 @@ interface ManageImportListsEditModalContentProps {
|
||||
const NO_CHANGE = 'noChange';
|
||||
|
||||
const autoAddOptions = [
|
||||
{ key: NO_CHANGE, value: translate('NoChange'), disabled: true },
|
||||
{ key: 'enabled', value: translate('Enabled') },
|
||||
{ key: 'disabled', value: translate('Disabled') },
|
||||
{
|
||||
key: NO_CHANGE,
|
||||
get value() {
|
||||
return translate('NoChange');
|
||||
},
|
||||
disabled: true,
|
||||
},
|
||||
{
|
||||
key: 'enabled',
|
||||
get value() {
|
||||
return translate('Enabled');
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'disabled',
|
||||
get value() {
|
||||
return translate('Disabled');
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
function ManageImportListsEditModalContent(
|
||||
|
||||
@@ -36,37 +36,37 @@ type OnSelectedChangeCallback = React.ComponentProps<
|
||||
const COLUMNS = [
|
||||
{
|
||||
name: 'name',
|
||||
label: translate('Name'),
|
||||
label: () => translate('Name'),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'implementation',
|
||||
label: translate('Implementation'),
|
||||
label: () => translate('Implementation'),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'qualityProfileId',
|
||||
label: translate('QualityProfile'),
|
||||
label: () => translate('QualityProfile'),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'rootFolderPath',
|
||||
label: translate('RootFolder'),
|
||||
label: () => translate('RootFolder'),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'enableAuto',
|
||||
label: translate('AutomaticAdd'),
|
||||
label: () => translate('AutomaticAdd'),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'tags',
|
||||
label: translate('Tags'),
|
||||
label: () => translate('Tags'),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
},
|
||||
|
||||
@@ -70,9 +70,24 @@ function TagsModalContent(props: TagsModalContentProps) {
|
||||
}, [tags, applyTags, onApplyTagsPress]);
|
||||
|
||||
const applyTagsOptions = [
|
||||
{ key: 'add', value: translate('Add') },
|
||||
{ key: 'remove', value: translate('Remove') },
|
||||
{ key: 'replace', value: translate('Replace') },
|
||||
{
|
||||
key: 'add',
|
||||
get value() {
|
||||
return translate('Add');
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'remove',
|
||||
get value() {
|
||||
return translate('Remove');
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'replace',
|
||||
get value() {
|
||||
return translate('Replace');
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
|
||||
@@ -27,9 +27,25 @@ interface ManageIndexersEditModalContentProps {
|
||||
const NO_CHANGE = 'noChange';
|
||||
|
||||
const enableOptions = [
|
||||
{ key: NO_CHANGE, value: translate('NoChange'), disabled: true },
|
||||
{ key: 'enabled', value: translate('Enabled') },
|
||||
{ key: 'disabled', value: translate('Disabled') },
|
||||
{
|
||||
key: NO_CHANGE,
|
||||
get value() {
|
||||
return translate('NoChange');
|
||||
},
|
||||
disabled: true,
|
||||
},
|
||||
{
|
||||
key: 'enabled',
|
||||
get value() {
|
||||
return translate('Enabled');
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'disabled',
|
||||
get value() {
|
||||
return translate('Disabled');
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
function ManageIndexersEditModalContent(
|
||||
|
||||
@@ -36,43 +36,43 @@ type OnSelectedChangeCallback = React.ComponentProps<
|
||||
const COLUMNS = [
|
||||
{
|
||||
name: 'name',
|
||||
label: translate('Name'),
|
||||
label: () => translate('Name'),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'implementation',
|
||||
label: translate('Implementation'),
|
||||
label: () => translate('Implementation'),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'enableRss',
|
||||
label: translate('EnableRSS'),
|
||||
label: () => translate('EnableRSS'),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'enableAutomaticSearch',
|
||||
label: translate('EnableAutomaticSearch'),
|
||||
label: () => translate('EnableAutomaticSearch'),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'enableInteractiveSearch',
|
||||
label: translate('EnableInteractiveSearch'),
|
||||
label: () => translate('EnableInteractiveSearch'),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'priority',
|
||||
label: translate('Priority'),
|
||||
label: () => translate('Priority'),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'tags',
|
||||
label: translate('Tags'),
|
||||
label: () => translate('Tags'),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
},
|
||||
|
||||
@@ -70,9 +70,24 @@ function TagsModalContent(props: TagsModalContentProps) {
|
||||
}, [tags, applyTags, onApplyTagsPress]);
|
||||
|
||||
const applyTagsOptions = [
|
||||
{ key: 'add', value: translate('Add') },
|
||||
{ key: 'remove', value: translate('Remove') },
|
||||
{ key: 'replace', value: translate('Replace') },
|
||||
{
|
||||
key: 'add',
|
||||
get value() {
|
||||
return translate('Add');
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'remove',
|
||||
get value() {
|
||||
return translate('Remove');
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'replace',
|
||||
get value() {
|
||||
return translate('Replace');
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
|
||||
@@ -47,12 +47,14 @@ function EditRestrictionModalContent(props) {
|
||||
<FormLabel>{translate('MustContain')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
{...required}
|
||||
inputClassName={styles.tagInternalInput}
|
||||
type={inputTypes.TEXT_TAG}
|
||||
name="required"
|
||||
helpText={translate('RequiredRestrictionHelpText')}
|
||||
kind={kinds.SUCCESS}
|
||||
placeholder={translate('RequiredRestrictionPlaceHolder')}
|
||||
{...required}
|
||||
canEdit={true}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
@@ -61,12 +63,14 @@ function EditRestrictionModalContent(props) {
|
||||
<FormLabel>{translate('MustNotContain')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
{...ignored}
|
||||
inputClassName={styles.tagInternalInput}
|
||||
type={inputTypes.TEXT_TAG}
|
||||
name="ignored"
|
||||
helpText={translate('IgnoredHelpText')}
|
||||
kind={kinds.DANGER}
|
||||
placeholder={translate('IgnoredPlaceHolder')}
|
||||
{...ignored}
|
||||
canEdit={true}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
@@ -9,3 +9,9 @@
|
||||
flex-wrap: wrap;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.label {
|
||||
composes: label from '~Components/Label.css';
|
||||
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user