mirror of
https://github.com/Sonarr/Sonarr.git
synced 2026-03-05 13:20:20 -05:00
Compare commits
1 Commits
3.0.10.156
...
phantom-ba
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8502f523e6 |
41
.github/ISSUE_TEMPLATE.md
vendored
Normal file
41
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
<!--
|
||||
Before opening a new issue, please ensure:
|
||||
- You use the forums for support/questions
|
||||
- You search for existing bugs/feature requests
|
||||
- Remove extraneous template details
|
||||
- Do not prefix title with type of issue (Feature Request, Bug, etc.) The appropriate labels will be added during triage.
|
||||
-->
|
||||
|
||||
## Support / Questions
|
||||
|
||||
Please use https://forums.sonarr.tv/ for support. Support requests or questions will be redirected to the forums and the issue will be closed.
|
||||
|
||||
<!--
|
||||
Remove if not opening a bug report
|
||||
-->
|
||||
|
||||
## Bug Report
|
||||
|
||||
### System Information/Logs
|
||||
|
||||
**Sonarr Version:**
|
||||
|
||||
**Operating System:**
|
||||
|
||||
**.net Framework (Windows) or mono (macOS/Linux) Version:**
|
||||
|
||||
**Link to Log Files (debug or trace):**
|
||||
|
||||
**Browser (for UI bugs):**
|
||||
|
||||
### Additional Information
|
||||
|
||||
<!--
|
||||
Remove if not opening a feature request
|
||||
-->
|
||||
|
||||
## Feature Request
|
||||
|
||||
### What problem are you looking to solve?
|
||||
|
||||
### Other Information
|
||||
28
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
28
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve Sonarr
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Logs**
|
||||
Link to debug or trace log files.
|
||||
|
||||
**System Information**
|
||||
|
||||
- Sonarr Version: [e.g. 2.0.0.1]
|
||||
- Operating System: [e.g. Windows 10]
|
||||
- .net Framework (Windows) or mono (macOS/Linux) Version: [e.g. 4.5 or 5.12]
|
||||
|
||||
**UI Bugs:**
|
||||
- OS: [e.g. Windows]
|
||||
- Browser: [e.g. chrome, firefox]
|
||||
- Version: [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
82
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
82
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -1,82 +0,0 @@
|
||||
name: Bug Report
|
||||
description: 'Support Requests will be closed immediately, if you are not 100% certain this is a bug please go to our Reddit, Discord, Forums, or IRC first. Exceptions do not mean you found a bug!'
|
||||
labels: ['needs-triage']
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is there an existing issue for this?
|
||||
description: Please search to see if an issue already exists for the bug you encountered.
|
||||
options:
|
||||
- label: I have searched the existing issues
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Current Behavior
|
||||
description: A concise description of what you're experiencing.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Expected Behavior
|
||||
description: A concise description of what you expected to happen.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps To Reproduce
|
||||
description: Steps to reproduce the behavior.
|
||||
placeholder: |
|
||||
1. In this environment...
|
||||
2. With this config...
|
||||
3. Run '...'
|
||||
4. See error...
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Environment
|
||||
description: |
|
||||
examples:
|
||||
- **OS**: Ubuntu 20.04
|
||||
- **Sonarr**: Sonarr 3.0.6.1265
|
||||
- **Docker Install**: Yes
|
||||
- **Using Reverse Proxy**: No
|
||||
- **Browser**: Firefox 90 (If UI related)
|
||||
value: |
|
||||
- OS:
|
||||
- Sonarr:
|
||||
- Docker Install:
|
||||
- Using Reverse Proxy:
|
||||
- Browser:
|
||||
render: markdown
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: What branch are you running?
|
||||
options:
|
||||
- Main
|
||||
- Develop
|
||||
- Other (This issue will be closed)
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Trace Logs?
|
||||
description: |
|
||||
Trace Logs (https://wiki.servarr.com/sonarr/troubleshooting#logging-and-log-files)
|
||||
***Generally speaking, all bug reports must have trace logs provided.***
|
||||
*** Info Logs are not trace logs. If the logs do not say trace and are not from a file like `*.trace.*.txt` they are not trace logs.***
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Anything else?
|
||||
description: |
|
||||
Links? Screenshots? References? Anything that will give us more context about the issue you are encountering!
|
||||
***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.
|
||||
validations:
|
||||
required: true
|
||||
required: true
|
||||
14
.github/ISSUE_TEMPLATE/config.yml
vendored
14
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,14 +0,0 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Support via Discord
|
||||
url: https://discord.gg/M6BvZn5
|
||||
about: Chat with users and devs on support and setup related topics.
|
||||
- name: Support via Reddit
|
||||
url: https://reddit.com/r/Sonarr
|
||||
about: Discuss and search through support topics.
|
||||
- name: Support via Forums
|
||||
url: https://forums.sonarr.tv/
|
||||
about: Discuss and search through support topics.
|
||||
- name: Support via IRC
|
||||
url: https://web.libera.chat/?channels=#sonarr
|
||||
about: Chat with users and devs on support and setup related topics.
|
||||
14
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
14
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for Sonarr
|
||||
|
||||
---
|
||||
|
||||
**Describe the problem**
|
||||
A clear and concise description of the problem you're looking to solve.
|
||||
|
||||
**Describe any solutions you think might work**
|
||||
A clear and concise description of any solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
38
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
38
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -1,38 +0,0 @@
|
||||
name: Feature Request
|
||||
description: 'Suggest an idea for Sonarr'
|
||||
labels: ['needs-triage']
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is there an existing issue for this?
|
||||
description: Please search to see if an issue already exists for the feature you are requesting.
|
||||
options:
|
||||
- label: I have searched the existing issues
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Is your feature request related to a problem? Please describe
|
||||
description: A clear and concise description of what the problem is.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the solution you'd like
|
||||
description: A clear and concise description of what you want to happen.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe alternatives you've considered
|
||||
description: A clear and concise description of any alternative solutions or features you've considered.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Anything else?
|
||||
description: |
|
||||
Links? References? Mockups? Anything that will give us more context about the feature you are encountering!
|
||||
|
||||
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
|
||||
validations:
|
||||
required: true
|
||||
7
.github/ISSUE_TEMPLATE/other-issues.md
vendored
Normal file
7
.github/ISSUE_TEMPLATE/other-issues.md
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
name: Other issues
|
||||
about: How to get support or ask questions
|
||||
|
||||
---
|
||||
|
||||
Please use https://forums.sonarr.tv/ for support. Support requests or questions will be redirected to the forums and the issue will be closed.
|
||||
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -6,7 +6,7 @@ A few sentences describing the overall goals of the pull request's commits.
|
||||
|
||||
#### Todos
|
||||
- [ ] Tests
|
||||
- [ ] Wiki Updates
|
||||
- [ ] Documentation
|
||||
|
||||
|
||||
#### Issues Fixed or Closed by this PR
|
||||
|
||||
6
.github/SUPPORT.md
vendored
6
.github/SUPPORT.md
vendored
@@ -1,7 +1,7 @@
|
||||
## Support
|
||||
|
||||
There are a number of frequently asked questions that have been answered in our [FAQ](https://wiki.servarr.com/sonarr/faq)
|
||||
There are a number of frequently asked questions that have been answered in our [FAQ](https://github.com/Sonarr/Sonarr/wiki/FAQ)
|
||||
|
||||
The [wiki](https://wiki.servarr.com/sonarr) contains other information and guides
|
||||
The [wiki](https://github.com/Sonarr/Sonarr/wiki) contains other information and guides
|
||||
|
||||
Please use one of the support channels: [forums](https://forums.sonarr.tv/), [subreddit](https://www.reddit.com/r/sonarr/), [discord ](https://discord.gg/M6BvZn5), or [IRC](https://web.libera.chat/?channels=#sonarr)for support/questions.
|
||||
If you have a support question, please use the [support forums](https://forums.sonarr.tv/).
|
||||
|
||||
21
.github/workflows/lock.yml
vendored
21
.github/workflows/lock.yml
vendored
@@ -1,21 +0,0 @@
|
||||
name: 'Lock threads'
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
|
||||
jobs:
|
||||
lock:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v2
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
issue-lock-inactive-days: '90'
|
||||
issue-exclude-created-before: ''
|
||||
issue-exclude-labels: 'one-day-maybe'
|
||||
issue-lock-labels: ''
|
||||
issue-lock-comment: ''
|
||||
issue-lock-reason: 'resolved'
|
||||
process-only: ''
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -141,4 +141,3 @@ output/*
|
||||
_start
|
||||
|
||||
src/.idea/
|
||||
/distribution/windows/setup/output/*
|
||||
|
||||
@@ -3,40 +3,25 @@
|
||||
We're always looking for people to help make Sonarr even better, there are a number of ways to contribute.
|
||||
|
||||
## Documentation ##
|
||||
Setup guides, [FAQ](https://wiki.servarr.com/sonarr/faq), the more information we have on the [wiki](https://wiki.servarr.com/sonarr) the better.
|
||||
Setup guides, FAQ, the more information we have on the wiki the better.
|
||||
|
||||
## Development ##
|
||||
|
||||
### Tools required ###
|
||||
- Visual Studio 2019 or higher (https://www.visualstudio.com/vs/). The community version is free and works (https://www.visualstudio.com/downloads/).
|
||||
- HTML/Javascript editor of choice (VS Code/Sublime Text/Webstorm/Atom/etc)
|
||||
- [Git](https://git-scm.com/downloads)
|
||||
- [NodeJS](https://nodejs.org/en/download/) (Node 10.X.X or higher)
|
||||
- [Yarn](https://yarnpkg.com/)
|
||||
|
||||
### Getting started ###
|
||||
|
||||
1. Fork Sonarr
|
||||
2. Clone the repository into your development machine. [*info*](https://docs.github.com/en/get-started/quickstart/fork-a-repo)
|
||||
3. Install the required Node Packages `yarn install`
|
||||
4. Start webpack to monitor your dev environment for any frontend changes that need post processing using `yarn start` command.
|
||||
5. Build the project in Visual Studio, Setting startup project to `Sonarr.Console` and framework to `x86`
|
||||
6. Debug the project in Visual Studio
|
||||
7. Open http://localhost:8989
|
||||
See the readme for information on setting up your development environment.
|
||||
|
||||
### Contributing Code ###
|
||||
- If you're adding a new, already requested feature, please comment on [Github Issues](https://github.com/Sonarr/Sonarr/issues "Github Issues") so work is not duplicated (If you want to add something not already on there, please talk to us first)
|
||||
- Rebase from Sonarr's `develop` branch, don't merge
|
||||
- Rebase from Sonarr's develop branch, don't merge
|
||||
- Make meaningful commits, or squash them
|
||||
- Feel free to make a pull request before work is complete, this will let us see where its at and make comments/suggest improvements
|
||||
- Reach out to us on our [forums](https://forums.sonarr.tv/), [subreddit](https://www.reddit.com/r/sonarr/), [discord](https://discord.gg/Ex7FmFK), or [IRC](https://web.libera.chat/?channels=#sonarr) if you have any questions
|
||||
- Reach out to us on the forums or on IRC if you have any questions
|
||||
- Add tests (unit/integration)
|
||||
- Commit with *nix line endings for consistency (We checkout Windows and commit *nix)
|
||||
- One feature/bug fix per pull request to keep things clean and easy to understand
|
||||
- Use 4 spaces instead of tabs, this should be the default for VS 2019 and WebStorm
|
||||
- Use 4 spaces instead of tabs, this is the default for VS 2012 and WebStorm (to my knowledge)
|
||||
|
||||
### Pull Requesting ###
|
||||
- Only make pull requests to develop (currently `develop`), never `main`, if you make a PR to master we'll comment on it and close it
|
||||
- Only make pull requests to develop, never master, if you make a PR to master we'll comment on it and close it
|
||||
- You're probably going to get some comments or questions from us, they will be to ensure consistency and maintainability
|
||||
- We'll try to respond to pull requests as soon as possible, if its been a day or two, please reach out to us, we may have missed it
|
||||
- Each PR should come from its own [feature branch](http://martinfowler.com/bliki/FeatureBranch.html) not develop in your fork, it should have a meaningful branch name (what is being added/fixed)
|
||||
|
||||
72
README.md
72
README.md
@@ -4,23 +4,17 @@ Sonarr is a PVR for Usenet and BitTorrent users. It can monitor multiple RSS fee
|
||||
|
||||
## Getting Started
|
||||
|
||||
- [Download/Installation](https://sonarr.tv/#downloads-v3)
|
||||
- [FAQ](https://wiki.servarr.com/sonarr/faq)
|
||||
- [Wiki](https://wiki.servarr.com/Sonarr)
|
||||
- [(WIP) API Documentation](https://github.com/Sonarr/Sonarr/wiki/API)
|
||||
- [Donate](https://sonarr.tv/donate)
|
||||
- [Download](https://sonarr.tv/#download) (Linux, MacOS, Windows, Docker, etc.)
|
||||
- [Installation](https://github.com/Sonarr/Sonarr/wiki/Installation)
|
||||
- [FAQ](https://github.com/Sonarr/Sonarr/wiki/FAQ)
|
||||
- [Wiki](https://github.com/Sonarr/Sonarr/wiki)
|
||||
- [API Documentation](https://github.com/Sonarr/Sonarr/wiki/API)
|
||||
|
||||
## Support
|
||||
Note: GitHub Issues are for Bugs and Feature Requests Only
|
||||
|
||||
- [Forums](https://forums.sonarr.tv/)
|
||||
- [Donate](https://sonarr.tv/donate)
|
||||
- [Discord](https://discord.gg/M6BvZn5)
|
||||
- [GitHub - Bugs and Feature Requests Only](https://github.com/Sonarr/Sonarr/issues)
|
||||
- [IRC](https://web.libera.chat/?channels=#sonarr)
|
||||
- [Reddit](https://www.reddit.com/r/sonarr)
|
||||
- [Wiki](https://wiki.servarr.com/sonarr)
|
||||
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
@@ -38,29 +32,55 @@ Note: GitHub Issues are for Bugs and Feature Requests Only
|
||||
- Full support for specials and multi-episode releases
|
||||
- And a beautiful UI
|
||||
|
||||
## Contributing
|
||||
## Configuring Development Environment:
|
||||
|
||||
### Development
|
||||
This project exists thanks to all the people who contribute. [Contribute](CONTRIBUTING.md).
|
||||
### Requirements
|
||||
|
||||
<a href="https://github.com/Sonarr/Sonarr/graphs/contributors"><img src="https://opencollective.com/Sonarr/contributors.svg?width=890&button=false" /></a>
|
||||
- [Visual Studio 2017](https://www.visualstudio.com/vs)
|
||||
- [Git](https://git-scm.com/downloads)
|
||||
- [NodeJS](https://nodejs.org/en/download)
|
||||
- [Yarn](https://yarnpkg.com)
|
||||
|
||||
### Setup
|
||||
|
||||
- Make sure all the required software mentioned above are installed
|
||||
- Clone the repository recursively to get Sonarr and it's submodules
|
||||
- You can do this by running `git clone --recursive https://github.com/Sonarr/Sonarr.git`
|
||||
- Install the required Node Packages using `yarn`
|
||||
|
||||
### Backend Development
|
||||
|
||||
- Run `yarn build` to build the UI
|
||||
- Open `Sonarr.sln` in Visual Studio
|
||||
- Make sure `Sonarr.Console` is set as the startup project
|
||||
- Build `Sonarr.Windows` and `Sonarr.Mono` projects
|
||||
- Build Solution
|
||||
|
||||
### UI Development
|
||||
|
||||
- Run `yarn watch` to build UI and rebuild automatically when changes are detected
|
||||
- Run Sonarr.Console.exe (or debug in Visual Studio)
|
||||
|
||||
### Licenses
|
||||
|
||||
- [GNU GPL v3](http://www.gnu.org/licenses/gpl.html)
|
||||
- Copyright 2010-2020
|
||||
|
||||
### Supporters
|
||||
|
||||
This project would not be possible without the support of our users and software providers.
|
||||
[**Become a sponsor or backer**](https://opencollective.com/sonarr) to help us out!
|
||||
|
||||
#### Mega Sponsors
|
||||
|
||||
[](https://opencollective.com/sonarr/contribute/mega-sponsor-21443/checkout)
|
||||
This project would not be possible without the support of our users and software providers. [**Become a sponsor or backer**](https://opencollective.com/sonarr) to help us out!
|
||||
|
||||
#### Sponsors
|
||||
|
||||
[](https://opencollective.com/sonarr/contribute/sponsor-21457/checkout)
|
||||
[](https://opencollective.com/sonarr/contribute/sponsor-21443/checkout)
|
||||
|
||||
#### Flexible Sponsors
|
||||
|
||||
[](https://opencollective.com/sonarr/contribute/flexible-sponsor-21457/checkout)
|
||||
|
||||
#### Backers
|
||||
|
||||
[](https://opencollective.com/sonarr/contribute/backer-21442/checkout)
|
||||
[](https://opencollective.com/sonarr/contribute/backer-21442/checkout)
|
||||
|
||||
#### JetBrains
|
||||
|
||||
@@ -70,7 +90,3 @@ Thank you to [<img src="/Logo/Jetbrains/jetbrains.svg" alt="JetBrains" width="32
|
||||
* [<img src="/Logo/Jetbrains/resharper.svg" alt="ReSharper" width="32"> ReSharper](http://www.jetbrains.com/resharper/)
|
||||
* [<img src="/Logo/Jetbrains/dottrace.svg" alt="dotTrace" width="32"> dotTrace](http://www.jetbrains.com/dottrace/)
|
||||
|
||||
### Licenses
|
||||
|
||||
- [GNU GPL v3](http://www.gnu.org/licenses/gpl.html)
|
||||
- Copyright 2010-2021
|
||||
|
||||
66
build.sh
66
build.sh
@@ -12,11 +12,8 @@ sourceFolder='./src'
|
||||
slnFile=$sourceFolder/Sonarr.sln
|
||||
updateSubFolder=Sonarr.Update
|
||||
|
||||
sqlitePackageDir="$HOME/.nuget/packages/system.data.sqlite.core.servarr/1.0.115.5-18"
|
||||
|
||||
nuget='tools/nuget/nuget.exe';
|
||||
vswhere='tools/vswhere/vswhere.exe';
|
||||
macho='tools/macho/MachOConverter.exe';
|
||||
|
||||
. ./version.sh
|
||||
|
||||
@@ -138,16 +135,13 @@ Build()
|
||||
|
||||
CleanFolder $outputFolder false
|
||||
|
||||
echo "Removing Sonarr.Update/sqlite3.dll"
|
||||
rm $outputFolder/Sonarr.Update/sqlite3.dll
|
||||
|
||||
echo "Removing Mono.Posix.dll"
|
||||
rm $outputFolder/Mono.Posix.dll
|
||||
|
||||
ProgressEnd 'Build'
|
||||
}
|
||||
|
||||
RunWebpack()
|
||||
RunGulp()
|
||||
{
|
||||
ProgressStart 'yarn install'
|
||||
yarn install
|
||||
@@ -155,9 +149,9 @@ RunWebpack()
|
||||
|
||||
LintUI
|
||||
|
||||
ProgressStart 'Running webpack'
|
||||
CheckExitCode yarn run build --env production
|
||||
ProgressEnd 'Running webpack'
|
||||
ProgressStart 'Running gulp'
|
||||
CheckExitCode yarn run build --production
|
||||
ProgressEnd 'Running gulp'
|
||||
}
|
||||
|
||||
CreateMdbs()
|
||||
@@ -240,7 +234,6 @@ PackageMono()
|
||||
|
||||
echo "Removing native windows binaries Sqlite, MediaInfo"
|
||||
rm -f $outputFolderLinux/sqlite3.*
|
||||
rm -f $outputFolderLinux/Sonarr.Update/sqlite3.*
|
||||
rm -f $outputFolderLinux/MediaInfo.*
|
||||
|
||||
PatchMono $outputFolderLinux
|
||||
@@ -285,22 +278,17 @@ PackageMacOS()
|
||||
chmod +x $outputFolderMacOS/Sonarr
|
||||
|
||||
echo "Adding Sonarr.Update Launcher"
|
||||
CheckExitCode cp ./distribution/osx/Launcher/dist/Launcher $outputFolderMacOS/Sonarr.Update/
|
||||
CheckExitCode mv $outputFolderMacOS/Sonarr.Update/Sonarr.Update.exe $outputFolderMacOS/Sonarr.Update/Sonarr.Update.exe.bak
|
||||
CheckExitCode mv $outputFolderMacOS/Sonarr.Update/Launcher $outputFolderMacOS/Sonarr.Update/Sonarr.Update
|
||||
CheckExitCode mv $outputFolderMacOS/Sonarr.Update/Sonarr.Update.exe.bak $outputFolderMacOS/Sonarr.Update/Sonarr.Update.exe
|
||||
cp ./distribution/osx/Launcher/dist/Launcher $outputFolderMacOS/Sonarr.Update/
|
||||
mv $outputFolderMacOS/Sonarr.Update/Sonarr.Update.exe $outputFolderMacOS/Sonarr.Update/Sonarr.Update.exe.bak
|
||||
mv $outputFolderMacOS/Sonarr.Update/Launcher $outputFolderMacOS/Sonarr.Update/Sonarr.Update
|
||||
mv $outputFolderMacOS/Sonarr.Update/Sonarr.Update.exe.bak $outputFolderMacOS/Sonarr.Update/Sonarr.Update.exe
|
||||
chmod +x $outputFolderMacOS/Sonarr.Update/Sonarr.Update
|
||||
|
||||
echo "Adding sqlite dylib"
|
||||
CheckExitCode cp "$sqlitePackageDir/runtimes/osx-x64/native/net46"/*.config $outputFolderMacOS/
|
||||
if [ $runtime = "dotnet" ] ; then
|
||||
CheckExitCode $macho merge $outputFolderMacOS "$sqlitePackageDir/runtimes/osx-x64/native/net46"/*.dylib "$sqlitePackageDir/runtimes/osx-arm64/native/net46"/*.dylib
|
||||
else
|
||||
CheckExitCode mono $macho merge $outputFolderMacOS "$sqlitePackageDir/runtimes/osx-x64/native/net46"/*.dylib "$sqlitePackageDir/runtimes/osx-arm64/native/net46"/*.dylib
|
||||
fi
|
||||
echo "Adding sqlite dylibs"
|
||||
cp $sourceFolder/Libraries/Sqlite/*.dylib $outputFolderMacOS
|
||||
|
||||
echo "Adding MediaInfo dylib"
|
||||
CheckExitCode cp $sourceFolder/Libraries/MediaInfo/x64/*.dylib $outputFolderMacOS/
|
||||
cp $sourceFolder/Libraries/MediaInfo/*.dylib $outputFolderMacOS
|
||||
|
||||
ProgressEnd 'Creating MacOS Package'
|
||||
}
|
||||
@@ -309,37 +297,29 @@ PackageMacOSApp()
|
||||
{
|
||||
ProgressStart 'Creating macOS App Package'
|
||||
|
||||
outputFolderMacOSAppBase=$outputFolderMacOSApp/Sonarr.app/Contents/MacOS
|
||||
outputFolderMacOSAppBin=$outputFolderMacOSAppBase/bin
|
||||
|
||||
rm -rf $outputFolderMacOSApp
|
||||
mkdir $outputFolderMacOSApp
|
||||
cp -r ./distribution/osx/Sonarr.app $outputFolderMacOSApp
|
||||
mkdir -p $outputFolderMacOSAppBase
|
||||
mkdir -p $outputFolderMacOSApp/Sonarr.app/Contents/MacOS
|
||||
|
||||
echo "Adding Sonarr Launcher"
|
||||
CheckExitCode cp ./distribution/osx/Launcher/dist/Launcher $outputFolderMacOSAppBase/
|
||||
CheckExitCode mv $outputFolderMacOSAppBase/Launcher $outputFolderMacOSAppBase/Sonarr
|
||||
chmod +x $outputFolderMacOSAppBase/Sonarr
|
||||
cp ./distribution/osx/Launcher/dist/Launcher $outputFolderMacOSApp/Sonarr.app/Contents/MacOS/
|
||||
mv $outputFolderMacOSApp/Sonarr.app/Contents/MacOS/Launcher $outputFolderMacOSApp/Sonarr.app/Contents/MacOS/Sonarr
|
||||
chmod +x $outputFolderMacOSApp/Sonarr.app/Contents/MacOS/Sonarr
|
||||
|
||||
echo "Copying Binaries"
|
||||
mkdir -p $outputFolderMacOSAppBin
|
||||
CheckExitCode cp -r $outputFolderLinux/* $outputFolderMacOSAppBin
|
||||
|
||||
echo "Adding sqlite dylib"
|
||||
if [ $runtime = "dotnet" ] ; then
|
||||
CheckExitCode $macho merge $outputFolderMacOSAppBin "$sqlitePackageDir/runtimes/osx-x64/native/net46" "$sqlitePackageDir/runtimes/osx-arm64/native/net46"
|
||||
else
|
||||
CheckExitCode mono $macho merge $outputFolderMacOSAppBin "$sqlitePackageDir/runtimes/osx-x64/native/net46" "$sqlitePackageDir/runtimes/osx-arm64/native/net46"
|
||||
fi
|
||||
mkdir -p $outputFolderMacOSApp/Sonarr.app/Contents/MacOS/bin
|
||||
cp -r $outputFolderLinux/* $outputFolderMacOSApp/Sonarr.app/Contents/MacOS/bin/
|
||||
|
||||
echo "Adding sqlite dylibs"
|
||||
cp $sourceFolder/Libraries/Sqlite/*.dylib $outputFolderMacOSApp/Sonarr.app/Contents/MacOS/bin/
|
||||
|
||||
echo "Adding MediaInfo dylib"
|
||||
CheckExitCode cp $sourceFolder/Libraries/MediaInfo/x64/*.dylib $outputFolderMacOSAppBin/
|
||||
cp $sourceFolder/Libraries/MediaInfo/*.dylib $outputFolderMacOSApp/Sonarr.app/Contents/MacOS/bin/
|
||||
|
||||
echo "Removing Update Folder"
|
||||
rm -r $outputFolderMacOSAppBin/Sonarr.Update
|
||||
echo "# Do Not Edit\nPackageVersion=${BUILD_NUMBER}\nPackageAuthor=[Team Sonarr](https://sonarr.tv)\nReleaseVersion=${BUILD_NUMBER}\nUpdateMethod=$PackageUpdater\nBranch=${Branch:-master}" > $outputFolderMacOSAppBase/package_info
|
||||
rm -r $outputFolderMacOSApp/Sonarr.app/Contents/MacOS/bin/Sonarr.Update
|
||||
echo "# Do Not Edit\nPackageVersion=${BUILD_NUMBER}\nPackageAuthor=[Team Sonarr](https://sonarr.tv)\nReleaseVersion=${BUILD_NUMBER}\nUpdateMethod=$PackageUpdater\nBranch=${Branch:-master}" > $outputFolderMacOSApp/Sonarr.app/Contents/MacOS/package_info
|
||||
|
||||
ProgressEnd 'Creating macOS App Package'
|
||||
}
|
||||
@@ -467,7 +447,7 @@ esac
|
||||
UpdateVersionNumber
|
||||
Build
|
||||
CreateReleaseInfo
|
||||
RunWebpack
|
||||
RunGulp
|
||||
PackageMono
|
||||
PackageMacOS
|
||||
PackageMacOSApp
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
fromdos ./debian/*
|
||||
chmod ugo-x ./debian/*
|
||||
cp -r ./debian ./debian_backup
|
||||
|
||||
BuildVersion=${dependent_build_number:-3.10.0.999}
|
||||
|
||||
@@ -1 +1 @@
|
||||
10
|
||||
8
|
||||
|
||||
@@ -8,10 +8,7 @@ db_input high sonarr/owning_group || true
|
||||
db_endblock
|
||||
db_go
|
||||
|
||||
db_beginblock
|
||||
db_input low sonarr/owning_umask || true
|
||||
db_input low sonarr/config_directory || true
|
||||
db_endblock
|
||||
db_go
|
||||
|
||||
exit 0
|
||||
|
||||
@@ -9,8 +9,6 @@ db_get sonarr/owning_user
|
||||
USER="$RET"
|
||||
db_get sonarr/owning_group
|
||||
GROUP="$RET"
|
||||
db_get sonarr/owning_umask
|
||||
UMASK="$RET"
|
||||
db_get sonarr/config_directory
|
||||
CONFDIR="$RET"
|
||||
|
||||
@@ -66,11 +64,9 @@ fi
|
||||
# Create data directory
|
||||
if [ ! -d "$CONFDIR" ]; then
|
||||
mkdir -p "$CONFDIR"
|
||||
chown -R $USER:$GROUP "$CONFDIR"
|
||||
fi
|
||||
|
||||
# Set permissions on data directory (always do this instead only on creation in case user was changed via dpkg-reconfigure)
|
||||
chown -R $USER:$GROUP "$CONFDIR"
|
||||
|
||||
#BEGIN BUILTIN UPDATER
|
||||
# Apply patch if present
|
||||
if [ "$UPDATER" = "BuiltIn" ] && [ -f /usr/lib/sonarr/bin_patch/release_info ]; then
|
||||
@@ -96,7 +92,7 @@ fi
|
||||
chown -R $USER:$GROUP /usr/lib/sonarr
|
||||
|
||||
# Update sonarr.service file
|
||||
sed -i "s:User=\w*:User=$USER:g; s:Group=\w*:Group=$GROUP:g; s:UMask=[0-9]*:UMask=$UMASK:g; s:-data=.*$:-data=$CONFDIR:g" /lib/systemd/system/sonarr.service
|
||||
sed -i "s:User=sonarr:User=$USER:g; s:Group=sonarr:Group=$GROUP:g; s:-data=/var/lib/sonarr:-data=$CONFDIR:g" /lib/systemd/system/sonarr.service
|
||||
|
||||
#BEGIN BUILTIN UPDATER
|
||||
if [ "$UPDATER" = "BuiltIn" ]; then
|
||||
|
||||
@@ -9,7 +9,7 @@ UPDATER={updater}
|
||||
# Existing nzbdrone packages do not have startup scripts and the process might still be running.
|
||||
# If the user manually installed nzbdrone then the process might still be running too.
|
||||
if [ $1 = "install" ]; then
|
||||
psNzbDrone=`ps ax -o'user:20,pid,ppid,unit,args' | grep mono.*NzbDrone\\\\.exe || true`
|
||||
psNzbDrone=`ps ax -o'user,pid,ppid,unit,args' | grep mono.*NzbDrone\\\\.exe || true`
|
||||
if [ ! -z "$psNzbDrone" ]; then
|
||||
# Get the user and optional systemd unit
|
||||
psNzbDroneUser=`echo "$psNzbDrone" | tr -s ' ' | cut -d ' ' -f 1`
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
ignores msbuild
|
||||
ignores libmediainfo0v5
|
||||
ignores libc6
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
# This file is owned by the sonarr package, DO NOT MODIFY MANUALLY
|
||||
# Instead use 'dpkg-reconfigure -plow sonarr' to modify User/Group/UMask/-data
|
||||
# Or use systemd built-in override functionality using 'systemctl edit sonarr'
|
||||
[Unit]
|
||||
Description=Sonarr Daemon
|
||||
After=network.target
|
||||
|
||||
@@ -14,12 +14,6 @@ Description: Sonarr group:
|
||||
Any media files created by Sonarr will be writeable by this group.
|
||||
It's advisable to keep the group the same between download client, Sonarr and media centers.
|
||||
|
||||
Template: sonarr/owning_umask
|
||||
Type: string
|
||||
Default: 0002
|
||||
Description: Sonarr umask:
|
||||
Specifies the umask of the files created by Sonarr. 0002 means the files will be created with 664 as permissions.
|
||||
|
||||
Template: sonarr/config_directory
|
||||
Type: string
|
||||
Default: /var/lib/sonarr
|
||||
|
||||
@@ -1,25 +1,16 @@
|
||||
FROM ubuntu:focal AS builder
|
||||
FROM ubuntu:xenial AS builder
|
||||
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
ENV MONO_VERSION 5.18
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get -y -o Dpkg::Options::="--force-confold" install --no-install-recommends \
|
||||
apt-transport-https \
|
||||
wget dirmngr gpg gpg-agent \
|
||||
# add-apt-repository for PPAs
|
||||
software-properties-common && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF && \
|
||||
echo "deb http://download.mono-project.com/repo/debian stable-focal main" > /etc/apt/sources.list.d/mono-official-stable.list && \
|
||||
echo "deb http://download.mono-project.com/repo/debian stable-xenial/snapshots/$MONO_VERSION main" > /etc/apt/sources.list.d/mono-official-stable.list && \
|
||||
apt-get update && apt-get install -y \
|
||||
devscripts build-essential tofrodos \
|
||||
dh-make dh-systemd \
|
||||
cli-common-dev \
|
||||
mono-complete \
|
||||
sqlite3 libcurl4 mediainfo
|
||||
RUN apt-get upgrade -y
|
||||
sqlite3 libcurl3 mediainfo
|
||||
|
||||
RUN apt-cache policy mono-complete
|
||||
RUN apt-cache policy cli-common-dev
|
||||
|
||||
@@ -37,7 +37,6 @@ Compression=lzma2/normal
|
||||
AppContact={#ForumsURL}
|
||||
VersionInfoVersion={#BuildNumber}
|
||||
SetupLogging=yes
|
||||
OutputDir=output
|
||||
|
||||
[Languages]
|
||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||
@@ -60,12 +59,11 @@ Name: "{userstartup}\{#AppName}"; Filename: "{app}\Sonarr.exe"; WorkingDir: "{ap
|
||||
|
||||
[InstallDelete]
|
||||
Name: "{commonappdata}\NzbDrone\bin"; Type: filesandordirs
|
||||
Name: "{app}"; Type: filesandordirs
|
||||
|
||||
[Run]
|
||||
Filename: "{app}\Sonarr.Console.exe"; StatusMsg: "Removing previous Windows Service"; Parameters: "/u /exitimmediately"; Flags: runhidden waituntilterminated;
|
||||
Filename: "{app}\Sonarr.Console.exe"; Description: "Enable Access from Other Devices"; StatusMsg: "Enabling Remote access"; Parameters: "/registerurl /exitimmediately"; Flags: postinstall runascurrentuser runhidden waituntilterminated; Tasks: startupShortcut none;
|
||||
Filename: "{app}\Sonarr.Console.exe"; StatusMsg: "Installing Windows Service"; Parameters: "/i /exitimmediately"; Flags: runhidden waituntilterminated; Tasks: windowsService
|
||||
Filename: "{app}\Sonarr.Console.exe"; StatusMsg: "Removing previous Windows Service"; Parameters: "/u"; Flags: runhidden waituntilterminated;
|
||||
Filename: "{app}\Sonarr.Console.exe"; Description: "Enable Access from Other Devices"; StatusMsg: "Enabling Remote access"; Parameters: "/registerurl"; Flags: postinstall runascurrentuser runhidden waituntilterminated; Tasks: startupShortcut none;
|
||||
Filename: "{app}\Sonarr.Console.exe"; StatusMsg: "Installing Windows Service"; Parameters: "/i"; Flags: runhidden waituntilterminated; Tasks: windowsService
|
||||
Filename: "{app}\Sonarr.exe"; Description: "Open Sonarr Web UI"; Flags: postinstall skipifsilent nowait; Tasks: windowsService;
|
||||
Filename: "{app}\Sonarr.exe"; Description: "Start Sonarr"; Flags: postinstall skipifsilent nowait; Tasks: startupShortcut none;
|
||||
|
||||
|
||||
@@ -17,9 +17,6 @@ RUN fromdos /startup.sh
|
||||
|
||||
WORKDIR /data/
|
||||
VOLUME ["/data/_tests_linux", "/data/_output_linux", "/data/_tests_results"]
|
||||
|
||||
RUN groupadd sonarrtst -g 4020 && useradd sonarrtst -u 4021 -g 4020 -m -s /bin/bash
|
||||
USER sonarrtst
|
||||
|
||||
CMD bash /startup.sh
|
||||
|
||||
|
||||
@@ -25,8 +25,5 @@ RUN fromdos /startup.sh
|
||||
WORKDIR /data/
|
||||
VOLUME ["/data/_tests_linux", "/data/_output_linux", "/data/_tests_results"]
|
||||
|
||||
RUN groupadd sonarrtst -g 4020 && useradd sonarrtst -u 4021 -g 4020 -m -s /bin/bash
|
||||
USER sonarrtst
|
||||
|
||||
CMD bash /startup.sh
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
"function-parentheses-newline-inside": "never-multi-line",
|
||||
"function-parentheses-space-inside": "never",
|
||||
"function-url-quotes": "always",
|
||||
"function-url-scheme-disallowed-list": [
|
||||
"function-url-scheme-blacklist": [
|
||||
"data"
|
||||
],
|
||||
"function-whitespace-after": "always",
|
||||
|
||||
@@ -1,263 +0,0 @@
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const FileManagerPlugin = require('filemanager-webpack-plugin');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const LiveReloadPlugin = require('webpack-livereload-plugin');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const TerserPlugin = require('terser-webpack-plugin');
|
||||
|
||||
module.exports = (env) => {
|
||||
const uiFolder = 'UI';
|
||||
const frontendFolder = path.join(__dirname, '..');
|
||||
const srcFolder = path.join(frontendFolder, 'src');
|
||||
const isProduction = !!env.production;
|
||||
const isProfiling = isProduction && !!env.profile;
|
||||
const inlineWebWorkers = 'no-fallback';
|
||||
|
||||
const distFolder = path.resolve(frontendFolder, '..', '_output', uiFolder);
|
||||
|
||||
console.log('Source Folder:', srcFolder);
|
||||
console.log('Output Folder:', distFolder);
|
||||
console.log('isProduction:', isProduction);
|
||||
console.log('isProfiling:', isProfiling);
|
||||
|
||||
const config = {
|
||||
mode: isProduction ? 'production' : 'development',
|
||||
devtool: isProduction ? 'source-map' : 'eval-source-map',
|
||||
|
||||
stats: {
|
||||
children: false
|
||||
},
|
||||
|
||||
watchOptions: {
|
||||
ignored: /node_modules/
|
||||
},
|
||||
|
||||
entry: {
|
||||
index: 'index.js'
|
||||
},
|
||||
|
||||
resolve: {
|
||||
modules: [
|
||||
srcFolder,
|
||||
path.join(srcFolder, 'Shims'),
|
||||
'node_modules'
|
||||
],
|
||||
alias: {
|
||||
jquery: 'jquery/src/jquery',
|
||||
'react-middle-truncate': 'react-middle-truncate/lib/react-middle-truncate'
|
||||
}
|
||||
},
|
||||
|
||||
output: {
|
||||
path: distFolder,
|
||||
publicPath: '/',
|
||||
filename: '[name].js',
|
||||
sourceMapFilename: '[file].map'
|
||||
},
|
||||
|
||||
optimization: {
|
||||
moduleIds: 'deterministic',
|
||||
chunkIds: 'named',
|
||||
splitChunks: {
|
||||
chunks: 'initial',
|
||||
name: 'vendors'
|
||||
}
|
||||
},
|
||||
|
||||
performance: {
|
||||
hints: false
|
||||
},
|
||||
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
__DEV__: !isProduction,
|
||||
'process.env.NODE_ENV': isProduction ? JSON.stringify('production') : JSON.stringify('development')
|
||||
}),
|
||||
|
||||
new MiniCssExtractPlugin({
|
||||
filename: 'Content/styles.css'
|
||||
}),
|
||||
|
||||
new HtmlWebpackPlugin({
|
||||
template: 'frontend/src/index.ejs',
|
||||
filename: 'index.html',
|
||||
publicPath: '/'
|
||||
}),
|
||||
|
||||
new FileManagerPlugin({
|
||||
events: {
|
||||
onEnd: {
|
||||
copy: [
|
||||
// HTML
|
||||
{
|
||||
source: 'frontend/src/*.html',
|
||||
destination: distFolder
|
||||
},
|
||||
|
||||
// Fonts
|
||||
{
|
||||
source: 'frontend/src/Content/Fonts/*.*',
|
||||
destination: path.join(distFolder, 'Content/Fonts')
|
||||
},
|
||||
|
||||
// Icon Images
|
||||
{
|
||||
source: 'frontend/src/Content/Images/Icons/*.*',
|
||||
destination: path.join(distFolder, 'Content/Images/Icons')
|
||||
},
|
||||
|
||||
// Images
|
||||
{
|
||||
source: 'frontend/src/Content/Images/*.*',
|
||||
destination: path.join(distFolder, 'Content/Images')
|
||||
},
|
||||
|
||||
// Robots
|
||||
{
|
||||
source: 'frontend/src/Content/robots.txt',
|
||||
destination: path.join(distFolder, 'Content/robots.txt')
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
||||
new LiveReloadPlugin()
|
||||
],
|
||||
|
||||
resolveLoader: {
|
||||
modules: [
|
||||
'node_modules',
|
||||
'frontend/build/webpack/'
|
||||
]
|
||||
},
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.worker\.js$/,
|
||||
use: {
|
||||
loader: 'worker-loader',
|
||||
options: {
|
||||
filename: '[name].js',
|
||||
inline: inlineWebWorkers
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.js?$/,
|
||||
exclude: /(node_modules|JsLibraries)/,
|
||||
use: [
|
||||
{
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
configFile: `${frontendFolder}/babel.config.js`,
|
||||
envName: isProduction ? 'production' : 'development',
|
||||
presets: [
|
||||
[
|
||||
'@babel/preset-env',
|
||||
{
|
||||
modules: false,
|
||||
loose: true,
|
||||
debug: false,
|
||||
useBuiltIns: 'entry',
|
||||
corejs: 3
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// CSS Modules
|
||||
{
|
||||
test: /\.css$/,
|
||||
exclude: /(node_modules|globals.css)/,
|
||||
use: [
|
||||
{ loader: MiniCssExtractPlugin.loader },
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
importLoaders: 1,
|
||||
modules: {
|
||||
localIdentName: '[name]/[local]/[hash:base64:5]'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
loader: 'postcss-loader',
|
||||
options: {
|
||||
postcssOptions: {
|
||||
config: 'frontend/postcss.config.js'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// Global styles
|
||||
{
|
||||
test: /\.css$/,
|
||||
include: /(node_modules|globals.css)/,
|
||||
use: [
|
||||
'style-loader',
|
||||
{
|
||||
loader: 'css-loader'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// Fonts
|
||||
{
|
||||
test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 10240,
|
||||
mimetype: 'application/font-woff',
|
||||
emitFile: false,
|
||||
name: 'Content/Fonts/[name].[ext]'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
test: /\.(ttf|eot|eot?#iefix|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
emitFile: false,
|
||||
name: 'Content/Fonts/[name].[ext]'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
if (isProfiling) {
|
||||
config.resolve.alias['react-dom$'] = 'react-dom/profiling';
|
||||
config.resolve.alias['scheduler/tracing'] = 'scheduler/tracing-profiling';
|
||||
|
||||
config.optimization.minimizer = [
|
||||
new TerserPlugin({
|
||||
cache: true,
|
||||
parallel: true,
|
||||
sourceMap: true, // Must be set to true if using source-maps in production
|
||||
terserOptions: {
|
||||
mangle: false,
|
||||
keep_classnames: true,
|
||||
keep_fnames: true
|
||||
}
|
||||
})
|
||||
];
|
||||
}
|
||||
|
||||
return config;
|
||||
};
|
||||
17
frontend/gulp/build.js
Normal file
17
frontend/gulp/build.js
Normal file
@@ -0,0 +1,17 @@
|
||||
const gulp = require('gulp');
|
||||
|
||||
require('./clean');
|
||||
require('./copy');
|
||||
require('./webpack');
|
||||
|
||||
gulp.task('build',
|
||||
gulp.series('clean',
|
||||
gulp.parallel(
|
||||
'webpack',
|
||||
'copyHtml',
|
||||
'copyFonts',
|
||||
'copyImages'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
8
frontend/gulp/clean.js
Normal file
8
frontend/gulp/clean.js
Normal file
@@ -0,0 +1,8 @@
|
||||
const gulp = require('gulp');
|
||||
const del = require('del');
|
||||
|
||||
const paths = require('./helpers/paths');
|
||||
|
||||
gulp.task('clean', () => {
|
||||
return del([paths.dest.root]);
|
||||
});
|
||||
34
frontend/gulp/copy.js
Normal file
34
frontend/gulp/copy.js
Normal file
@@ -0,0 +1,34 @@
|
||||
const path = require('path');
|
||||
const gulp = require('gulp');
|
||||
const print = require('gulp-print').default;
|
||||
const cache = require('gulp-cached');
|
||||
const livereload = require('gulp-livereload');
|
||||
const paths = require('./helpers/paths.js');
|
||||
|
||||
gulp.task('copyHtml', () => {
|
||||
return gulp.src(paths.src.html, { base: paths.src.root })
|
||||
.pipe(cache('copyHtml'))
|
||||
.pipe(print())
|
||||
.pipe(gulp.dest(paths.dest.root))
|
||||
.pipe(livereload());
|
||||
});
|
||||
|
||||
gulp.task('copyFonts', () => {
|
||||
return gulp.src(
|
||||
path.join(paths.src.fonts, '**', '*.*'), { base: paths.src.root }
|
||||
)
|
||||
.pipe(cache('copyFonts'))
|
||||
.pipe(print())
|
||||
.pipe(gulp.dest(paths.dest.root))
|
||||
.pipe(livereload());
|
||||
});
|
||||
|
||||
gulp.task('copyImages', () => {
|
||||
return gulp.src(
|
||||
path.join(paths.src.images, '**', '*.*'), { base: paths.src.root }
|
||||
)
|
||||
.pipe(cache('copyImages'))
|
||||
.pipe(print())
|
||||
.pipe(gulp.dest(paths.dest.root))
|
||||
.pipe(livereload());
|
||||
});
|
||||
5
frontend/gulp/gulpFile.js
Normal file
5
frontend/gulp/gulpFile.js
Normal file
@@ -0,0 +1,5 @@
|
||||
require('./build.js');
|
||||
require('./clean.js');
|
||||
require('./copy.js');
|
||||
require('./watch.js');
|
||||
require('./webpack.js');
|
||||
6
frontend/gulp/helpers/errorHandler.js
Normal file
6
frontend/gulp/helpers/errorHandler.js
Normal file
@@ -0,0 +1,6 @@
|
||||
const colors = require('ansi-colors');
|
||||
|
||||
module.exports = function errorHandler(error) {
|
||||
console.log(colors.red(`Error (${error.plugin}): ${error.message}`));
|
||||
this.emit('end');
|
||||
};
|
||||
23
frontend/gulp/helpers/paths.js
Normal file
23
frontend/gulp/helpers/paths.js
Normal file
@@ -0,0 +1,23 @@
|
||||
const root = './frontend/src';
|
||||
|
||||
const paths = {
|
||||
src: {
|
||||
root,
|
||||
html: `${root}/*.html`,
|
||||
scripts: `${root}/**/*.js`,
|
||||
content: `${root}/Content/`,
|
||||
fonts: `${root}/Content/Fonts/`,
|
||||
images: `${root}/Content/Images/`,
|
||||
exclude: {
|
||||
libs: `!${root}/JsLibraries/**`
|
||||
}
|
||||
},
|
||||
dest: {
|
||||
root: './_output/UI/',
|
||||
content: './_output/UI/Content/',
|
||||
fonts: './_output/UI/Content/Fonts/',
|
||||
images: './_output/UI/Content/Images/'
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = paths;
|
||||
18
frontend/gulp/watch.js
Normal file
18
frontend/gulp/watch.js
Normal file
@@ -0,0 +1,18 @@
|
||||
const gulp = require('gulp');
|
||||
const livereload = require('gulp-livereload');
|
||||
const gulpWatch = require('gulp-watch');
|
||||
const paths = require('./helpers/paths.js');
|
||||
|
||||
require('./copy.js');
|
||||
require('./webpack.js');
|
||||
|
||||
function watch() {
|
||||
livereload.listen({ start: true });
|
||||
|
||||
gulp.task('webpackWatch')();
|
||||
gulpWatch(paths.src.html, gulp.series('copyHtml'));
|
||||
gulpWatch(`${paths.src.fonts}**/*.*`, gulp.series('copyFonts'));
|
||||
gulpWatch(`${paths.src.images}**/*.*`, gulp.series('copyImages'));
|
||||
}
|
||||
|
||||
gulp.task('watch', gulp.series('build', watch));
|
||||
268
frontend/gulp/webpack.js
Normal file
268
frontend/gulp/webpack.js
Normal file
@@ -0,0 +1,268 @@
|
||||
const gulp = require('gulp');
|
||||
const webpackStream = require('webpack-stream');
|
||||
const livereload = require('gulp-livereload');
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const errorHandler = require('./helpers/errorHandler');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const HtmlWebpackPluginHtmlTags = require('html-webpack-plugin/lib/html-tags');
|
||||
const TerserPlugin = require('terser-webpack-plugin');
|
||||
|
||||
const uiFolder = 'UI';
|
||||
const frontendFolder = path.join(__dirname, '..');
|
||||
const srcFolder = path.join(frontendFolder, 'src');
|
||||
const isProduction = process.argv.indexOf('--production') > -1;
|
||||
const isProfiling = isProduction && process.argv.indexOf('--profile') > -1;
|
||||
const inlineWebWorkers = 'no-fallback';
|
||||
|
||||
const distFolder = path.resolve(frontendFolder, '..', '_output', uiFolder);
|
||||
|
||||
console.log('Source Folder:', srcFolder);
|
||||
console.log('Output Folder:', distFolder);
|
||||
console.log('isProduction:', isProduction);
|
||||
console.log('isProfiling:', isProfiling);
|
||||
|
||||
const cssVarsFiles = [
|
||||
'../src/Styles/Variables/colors',
|
||||
'../src/Styles/Variables/dimensions',
|
||||
'../src/Styles/Variables/fonts',
|
||||
'../src/Styles/Variables/animations',
|
||||
'../src/Styles/Variables/zIndexes'
|
||||
].map(require.resolve);
|
||||
|
||||
// Override the way HtmlWebpackPlugin injects the scripts
|
||||
// TODO: Find a better way to get these paths without
|
||||
HtmlWebpackPlugin.prototype.injectAssetsIntoHtml = function(html, assets, assetTags) {
|
||||
const head = assetTags.headTags.map((v) => {
|
||||
const href = v.attributes.href
|
||||
.replace('\\', '/')
|
||||
.replace('%5C', '/');
|
||||
|
||||
v.attributes = { rel: 'stylesheet', type: 'text/css', href: `/${href}` };
|
||||
return HtmlWebpackPluginHtmlTags.htmlTagObjectToString(v, this.options.xhtml);
|
||||
});
|
||||
const body = assetTags.bodyTags.map((v) => {
|
||||
v.attributes = { src: `/${v.attributes.src}` };
|
||||
return HtmlWebpackPluginHtmlTags.htmlTagObjectToString(v, this.options.xhtml);
|
||||
});
|
||||
|
||||
return html
|
||||
.replace('<!-- webpack bundles head -->', head.join('\r\n '))
|
||||
.replace('<!-- webpack bundles body -->', body.join('\r\n '));
|
||||
};
|
||||
|
||||
const plugins = [
|
||||
new webpack.DefinePlugin({
|
||||
__DEV__: !isProduction,
|
||||
'process.env.NODE_ENV': isProduction ? JSON.stringify('production') : JSON.stringify('development')
|
||||
}),
|
||||
|
||||
new MiniCssExtractPlugin({
|
||||
filename: path.join('Content', 'styles.css')
|
||||
}),
|
||||
|
||||
new HtmlWebpackPlugin({
|
||||
template: 'frontend/src/index.html',
|
||||
filename: 'index.html'
|
||||
})
|
||||
];
|
||||
|
||||
const config = {
|
||||
mode: isProduction ? 'production' : 'development',
|
||||
devtool: '#source-map',
|
||||
|
||||
stats: {
|
||||
children: false
|
||||
},
|
||||
|
||||
watchOptions: {
|
||||
ignored: /node_modules/
|
||||
},
|
||||
|
||||
entry: {
|
||||
index: 'index.js'
|
||||
},
|
||||
|
||||
resolve: {
|
||||
modules: [
|
||||
srcFolder,
|
||||
path.join(srcFolder, 'Shims'),
|
||||
'node_modules'
|
||||
],
|
||||
alias: {
|
||||
jquery: 'jquery/src/jquery'
|
||||
}
|
||||
},
|
||||
|
||||
output: {
|
||||
path: distFolder,
|
||||
filename: '[name].js',
|
||||
sourceMapFilename: '[file].map'
|
||||
},
|
||||
|
||||
optimization: {
|
||||
chunkIds: 'named',
|
||||
splitChunks: {
|
||||
chunks: 'initial'
|
||||
}
|
||||
},
|
||||
|
||||
performance: {
|
||||
hints: false
|
||||
},
|
||||
|
||||
plugins,
|
||||
|
||||
resolveLoader: {
|
||||
modules: [
|
||||
'node_modules',
|
||||
'frontend/gulp/webpack/'
|
||||
]
|
||||
},
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.worker\.js$/,
|
||||
use: {
|
||||
loader: 'worker-loader',
|
||||
options: {
|
||||
filename: '[name].js',
|
||||
inline: inlineWebWorkers
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.js?$/,
|
||||
exclude: /(node_modules|JsLibraries)/,
|
||||
use: [
|
||||
{
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
configFile: `${frontendFolder}/babel.config.js`,
|
||||
envName: isProduction ? 'production' : 'development',
|
||||
presets: [
|
||||
[
|
||||
'@babel/preset-env',
|
||||
{
|
||||
modules: false,
|
||||
loose: true,
|
||||
debug: false,
|
||||
useBuiltIns: 'entry',
|
||||
corejs: 3
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// CSS Modules
|
||||
{
|
||||
test: /\.css$/,
|
||||
exclude: /(node_modules|globals.css)/,
|
||||
use: [
|
||||
{ loader: MiniCssExtractPlugin.loader },
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
importLoaders: 1,
|
||||
modules: {
|
||||
localIdentName: '[name]/[local]/[hash:base64:5]'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
loader: 'postcss-loader',
|
||||
options: {
|
||||
ident: 'postcss',
|
||||
config: {
|
||||
ctx: {
|
||||
cssVarsFiles
|
||||
},
|
||||
path: 'frontend/postcss.config.js'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// Global styles
|
||||
{
|
||||
test: /\.css$/,
|
||||
include: /(node_modules|globals.css)/,
|
||||
use: [
|
||||
'style-loader',
|
||||
{
|
||||
loader: 'css-loader'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// Fonts
|
||||
{
|
||||
test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 10240,
|
||||
mimetype: 'application/font-woff',
|
||||
emitFile: false,
|
||||
name: 'Content/Fonts/[name].[ext]'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
test: /\.(ttf|eot|eot?#iefix|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
emitFile: false,
|
||||
name: 'Content/Fonts/[name].[ext]'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
if (isProfiling) {
|
||||
config.resolve.alias['react-dom$'] = 'react-dom/profiling';
|
||||
config.resolve.alias['scheduler/tracing'] = 'scheduler/tracing-profiling';
|
||||
|
||||
config.optimization.minimizer = [
|
||||
new TerserPlugin({
|
||||
cache: true,
|
||||
parallel: true,
|
||||
sourceMap: true, // Must be set to true if using source-maps in production
|
||||
terserOptions: {
|
||||
mangle: false,
|
||||
keep_classnames: true,
|
||||
keep_fnames: true
|
||||
}
|
||||
})
|
||||
];
|
||||
}
|
||||
|
||||
gulp.task('webpack', () => {
|
||||
return webpackStream(config)
|
||||
.pipe(gulp.dest('_output/UI'));
|
||||
});
|
||||
|
||||
gulp.task('webpackWatch', () => {
|
||||
config.watch = true;
|
||||
|
||||
return webpackStream(config)
|
||||
.on('error', errorHandler)
|
||||
.pipe(gulp.dest('_output/UI'))
|
||||
.on('error', errorHandler)
|
||||
.pipe(livereload())
|
||||
.on('error', errorHandler);
|
||||
});
|
||||
@@ -1,32 +1,23 @@
|
||||
const reload = require('require-nocache')(module);
|
||||
|
||||
const cssVarsFiles = [
|
||||
'./src/Styles/Variables/colors',
|
||||
'./src/Styles/Variables/dimensions',
|
||||
'./src/Styles/Variables/fonts',
|
||||
'./src/Styles/Variables/animations',
|
||||
'./src/Styles/Variables/zIndexes'
|
||||
].map(require.resolve);
|
||||
module.exports = (ctx, configPath, options) => {
|
||||
const config = {
|
||||
plugins: {
|
||||
'postcss-mixins': {
|
||||
mixinsDir: [
|
||||
'frontend/src/Styles/Mixins'
|
||||
]
|
||||
},
|
||||
'postcss-simple-vars': {
|
||||
variables: () =>
|
||||
ctx.options.cssVarsFiles.reduce((acc, vars) => {
|
||||
return Object.assign(acc, reload(vars));
|
||||
}, {})
|
||||
},
|
||||
'postcss-color-function': {},
|
||||
'postcss-nested': {}
|
||||
}
|
||||
};
|
||||
|
||||
const mixinsFiles = [
|
||||
'frontend/src/Styles/Mixins/cover.css',
|
||||
'frontend/src/Styles/Mixins/linkOverlay.css',
|
||||
'frontend/src/Styles/Mixins/scroller.css',
|
||||
'frontend/src/Styles/Mixins/truncate.css'
|
||||
];
|
||||
|
||||
module.exports = {
|
||||
plugins: [
|
||||
['postcss-mixins', {
|
||||
mixinsFiles
|
||||
}],
|
||||
['postcss-simple-vars', {
|
||||
variables: () =>
|
||||
cssVarsFiles.reduce((acc, vars) => {
|
||||
return Object.assign(acc, reload(vars));
|
||||
}, {})
|
||||
}],
|
||||
'postcss-color-function',
|
||||
'postcss-nested'
|
||||
]
|
||||
return config;
|
||||
};
|
||||
|
||||
123
frontend/src/Activity/Blacklist/Blacklist.js
Normal file
123
frontend/src/Activity/Blacklist/Blacklist.js
Normal file
@@ -0,0 +1,123 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { align, icons } from 'Helpers/Props';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
||||
import TablePager from 'Components/Table/TablePager';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
|
||||
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
||||
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||
import BlacklistRowConnector from './BlacklistRowConnector';
|
||||
|
||||
class Blacklist extends Component {
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
isFetching,
|
||||
isPopulated,
|
||||
error,
|
||||
items,
|
||||
columns,
|
||||
totalRecords,
|
||||
isClearingBlacklistExecuting,
|
||||
onClearBlacklistPress,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<PageContent title="Blacklist">
|
||||
<PageToolbar>
|
||||
<PageToolbarSection>
|
||||
<PageToolbarButton
|
||||
label="Clear"
|
||||
iconName={icons.CLEAR}
|
||||
isSpinning={isClearingBlacklistExecuting}
|
||||
onPress={onClearBlacklistPress}
|
||||
/>
|
||||
</PageToolbarSection>
|
||||
|
||||
<PageToolbarSection alignContent={align.RIGHT}>
|
||||
<TableOptionsModalWrapper
|
||||
{...otherProps}
|
||||
columns={columns}
|
||||
>
|
||||
<PageToolbarButton
|
||||
label="Options"
|
||||
iconName={icons.TABLE}
|
||||
/>
|
||||
</TableOptionsModalWrapper>
|
||||
</PageToolbarSection>
|
||||
</PageToolbar>
|
||||
|
||||
<PageContentBody>
|
||||
{
|
||||
isFetching && !isPopulated &&
|
||||
<LoadingIndicator />
|
||||
}
|
||||
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
<div>Unable to load blacklist</div>
|
||||
}
|
||||
|
||||
{
|
||||
isPopulated && !error && !items.length &&
|
||||
<div>
|
||||
No history blacklist
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
isPopulated && !error && !!items.length &&
|
||||
<div>
|
||||
<Table
|
||||
columns={columns}
|
||||
{...otherProps}
|
||||
>
|
||||
<TableBody>
|
||||
{
|
||||
items.map((item) => {
|
||||
return (
|
||||
<BlacklistRowConnector
|
||||
key={item.id}
|
||||
columns={columns}
|
||||
{...item}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
</TableBody>
|
||||
</Table>
|
||||
|
||||
<TablePager
|
||||
totalRecords={totalRecords}
|
||||
isFetching={isFetching}
|
||||
{...otherProps}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</PageContentBody>
|
||||
</PageContent>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Blacklist.propTypes = {
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
isPopulated: PropTypes.bool.isRequired,
|
||||
error: PropTypes.object,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
totalRecords: PropTypes.number,
|
||||
isClearingBlacklistExecuting: PropTypes.bool.isRequired,
|
||||
onClearBlacklistPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default Blacklist;
|
||||
154
frontend/src/Activity/Blacklist/BlacklistConnector.js
Normal file
154
frontend/src/Activity/Blacklist/BlacklistConnector.js
Normal file
@@ -0,0 +1,154 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
|
||||
import withCurrentPage from 'Components/withCurrentPage';
|
||||
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
|
||||
import * as blacklistActions from 'Store/Actions/blacklistActions';
|
||||
import { executeCommand } from 'Store/Actions/commandActions';
|
||||
import * as commandNames from 'Commands/commandNames';
|
||||
import Blacklist from './Blacklist';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.blacklist,
|
||||
createCommandExecutingSelector(commandNames.CLEAR_BLACKLIST),
|
||||
(blacklist, isClearingBlacklistExecuting) => {
|
||||
return {
|
||||
isClearingBlacklistExecuting,
|
||||
...blacklist
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
...blacklistActions,
|
||||
executeCommand
|
||||
};
|
||||
|
||||
class BlacklistConnector extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
componentDidMount() {
|
||||
const {
|
||||
useCurrentPage,
|
||||
fetchBlacklist,
|
||||
gotoBlacklistFirstPage
|
||||
} = this.props;
|
||||
|
||||
registerPagePopulator(this.repopulate);
|
||||
|
||||
if (useCurrentPage) {
|
||||
fetchBlacklist();
|
||||
} else {
|
||||
gotoBlacklistFirstPage();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.isClearingBlacklistExecuting && !this.props.isClearingBlacklistExecuting) {
|
||||
this.props.gotoBlacklistFirstPage();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.clearBlacklist();
|
||||
unregisterPagePopulator(this.repopulate);
|
||||
}
|
||||
|
||||
//
|
||||
// Control
|
||||
|
||||
repopulate = () => {
|
||||
this.props.fetchBlacklist();
|
||||
}
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onFirstPagePress = () => {
|
||||
this.props.gotoBlacklistFirstPage();
|
||||
}
|
||||
|
||||
onPreviousPagePress = () => {
|
||||
this.props.gotoBlacklistPreviousPage();
|
||||
}
|
||||
|
||||
onNextPagePress = () => {
|
||||
this.props.gotoBlacklistNextPage();
|
||||
}
|
||||
|
||||
onLastPagePress = () => {
|
||||
this.props.gotoBlacklistLastPage();
|
||||
}
|
||||
|
||||
onPageSelect = (page) => {
|
||||
this.props.gotoBlacklistPage({ page });
|
||||
}
|
||||
|
||||
onSortPress = (sortKey) => {
|
||||
this.props.setBlacklistSort({ sortKey });
|
||||
}
|
||||
|
||||
onTableOptionChange = (payload) => {
|
||||
this.props.setBlacklistTableOption(payload);
|
||||
|
||||
if (payload.pageSize) {
|
||||
this.props.gotoBlacklistFirstPage();
|
||||
}
|
||||
}
|
||||
|
||||
onClearBlacklistPress = () => {
|
||||
this.props.executeCommand({ name: commandNames.CLEAR_BLACKLIST });
|
||||
}
|
||||
|
||||
onTableOptionChange = (payload) => {
|
||||
this.props.setBlacklistTableOption(payload);
|
||||
|
||||
if (payload.pageSize) {
|
||||
this.props.gotoBlacklistFirstPage();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Blacklist
|
||||
onFirstPagePress={this.onFirstPagePress}
|
||||
onPreviousPagePress={this.onPreviousPagePress}
|
||||
onNextPagePress={this.onNextPagePress}
|
||||
onLastPagePress={this.onLastPagePress}
|
||||
onPageSelect={this.onPageSelect}
|
||||
onSortPress={this.onSortPress}
|
||||
onTableOptionChange={this.onTableOptionChange}
|
||||
onClearBlacklistPress={this.onClearBlacklistPress}
|
||||
{...this.props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
BlacklistConnector.propTypes = {
|
||||
useCurrentPage: PropTypes.bool.isRequired,
|
||||
isClearingBlacklistExecuting: PropTypes.bool.isRequired,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
fetchBlacklist: PropTypes.func.isRequired,
|
||||
gotoBlacklistFirstPage: PropTypes.func.isRequired,
|
||||
gotoBlacklistPreviousPage: PropTypes.func.isRequired,
|
||||
gotoBlacklistNextPage: PropTypes.func.isRequired,
|
||||
gotoBlacklistLastPage: PropTypes.func.isRequired,
|
||||
gotoBlacklistPage: PropTypes.func.isRequired,
|
||||
setBlacklistSort: PropTypes.func.isRequired,
|
||||
setBlacklistTableOption: PropTypes.func.isRequired,
|
||||
clearBlacklist: PropTypes.func.isRequired,
|
||||
executeCommand: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default withCurrentPage(
|
||||
connect(createMapStateToProps, mapDispatchToProps)(BlacklistConnector)
|
||||
);
|
||||
@@ -9,7 +9,7 @@ import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
|
||||
class BlocklistDetailsModal extends Component {
|
||||
class BlacklistDetailsModal extends Component {
|
||||
|
||||
//
|
||||
// Render
|
||||
@@ -77,7 +77,7 @@ class BlocklistDetailsModal extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
BlocklistDetailsModal.propTypes = {
|
||||
BlacklistDetailsModal.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
sourceTitle: PropTypes.string.isRequired,
|
||||
protocol: PropTypes.string.isRequired,
|
||||
@@ -86,4 +86,4 @@ BlocklistDetailsModal.propTypes = {
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default BlocklistDetailsModal;
|
||||
export default BlacklistDetailsModal;
|
||||
@@ -2,17 +2,16 @@ import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { icons, kinds } from 'Helpers/Props';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
|
||||
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
||||
import TableRow from 'Components/Table/TableRow';
|
||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import EpisodeLanguage from 'Episode/EpisodeLanguage';
|
||||
import EpisodeQuality from 'Episode/EpisodeQuality';
|
||||
import SeriesTitleLink from 'Series/SeriesTitleLink';
|
||||
import BlocklistDetailsModal from './BlocklistDetailsModal';
|
||||
import styles from './BlocklistRow.css';
|
||||
import BlacklistDetailsModal from './BlacklistDetailsModal';
|
||||
import styles from './BlacklistRow.css';
|
||||
|
||||
class BlocklistRow extends Component {
|
||||
class BlacklistRow extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
@@ -41,7 +40,6 @@ class BlocklistRow extends Component {
|
||||
|
||||
render() {
|
||||
const {
|
||||
id,
|
||||
series,
|
||||
sourceTitle,
|
||||
language,
|
||||
@@ -50,20 +48,12 @@ class BlocklistRow extends Component {
|
||||
protocol,
|
||||
indexer,
|
||||
message,
|
||||
isSelected,
|
||||
columns,
|
||||
onSelectedChange,
|
||||
onRemovePress
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<TableRow>
|
||||
<TableSelectCell
|
||||
id={id}
|
||||
isSelected={isSelected}
|
||||
onSelectedChange={onSelectedChange}
|
||||
/>
|
||||
|
||||
{
|
||||
columns.map((column) => {
|
||||
const {
|
||||
@@ -152,7 +142,7 @@ class BlocklistRow extends Component {
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
title="Remove from blocklist"
|
||||
title="Remove from blacklist"
|
||||
name={icons.REMOVE}
|
||||
kind={kinds.DANGER}
|
||||
onPress={onRemovePress}
|
||||
@@ -165,7 +155,7 @@ class BlocklistRow extends Component {
|
||||
})
|
||||
}
|
||||
|
||||
<BlocklistDetailsModal
|
||||
<BlacklistDetailsModal
|
||||
isOpen={this.state.isDetailsModalOpen}
|
||||
sourceTitle={sourceTitle}
|
||||
protocol={protocol}
|
||||
@@ -179,7 +169,7 @@ class BlocklistRow extends Component {
|
||||
|
||||
}
|
||||
|
||||
BlocklistRow.propTypes = {
|
||||
BlacklistRow.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
series: PropTypes.object.isRequired,
|
||||
sourceTitle: PropTypes.string.isRequired,
|
||||
@@ -189,10 +179,8 @@ BlocklistRow.propTypes = {
|
||||
protocol: PropTypes.string.isRequired,
|
||||
indexer: PropTypes.string,
|
||||
message: PropTypes.string,
|
||||
isSelected: PropTypes.bool.isRequired,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
onSelectedChange: PropTypes.func.isRequired,
|
||||
onRemovePress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default BlocklistRow;
|
||||
export default BlacklistRow;
|
||||
@@ -1,8 +1,8 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { removeBlocklistItem } from 'Store/Actions/blocklistActions';
|
||||
import { removeFromBlacklist } from 'Store/Actions/blacklistActions';
|
||||
import createSeriesSelector from 'Store/Selectors/createSeriesSelector';
|
||||
import BlocklistRow from './BlocklistRow';
|
||||
import BlacklistRow from './BlacklistRow';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
@@ -18,9 +18,9 @@ function createMapStateToProps() {
|
||||
function createMapDispatchToProps(dispatch, props) {
|
||||
return {
|
||||
onRemovePress() {
|
||||
dispatch(removeBlocklistItem({ id: props.id }));
|
||||
dispatch(removeFromBlacklist({ id: props.id }));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(createMapStateToProps, createMapDispatchToProps)(BlocklistRow);
|
||||
export default connect(createMapStateToProps, createMapDispatchToProps)(BlacklistRow);
|
||||
@@ -1,232 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import getRemovedItems from 'Utilities/Object/getRemovedItems';
|
||||
import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
|
||||
import getSelectedIds from 'Utilities/Table/getSelectedIds';
|
||||
import removeOldSelectedState from 'Utilities/Table/removeOldSelectedState';
|
||||
import selectAll from 'Utilities/Table/selectAll';
|
||||
import toggleSelected from 'Utilities/Table/toggleSelected';
|
||||
import { align, icons, kinds } from 'Helpers/Props';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||
import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
||||
import TablePager from 'Components/Table/TablePager';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
|
||||
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
||||
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||
import BlocklistRowConnector from './BlocklistRowConnector';
|
||||
|
||||
class Blocklist extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
allSelected: false,
|
||||
allUnselected: false,
|
||||
lastToggled: null,
|
||||
selectedState: {},
|
||||
isConfirmRemoveModalOpen: false,
|
||||
items: props.items
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const {
|
||||
items
|
||||
} = this.props;
|
||||
|
||||
if (hasDifferentItems(prevProps.items, items)) {
|
||||
this.setState((state) => {
|
||||
return {
|
||||
...removeOldSelectedState(state, getRemovedItems(prevProps.items, items)),
|
||||
items
|
||||
};
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Control
|
||||
|
||||
getSelectedIds = () => {
|
||||
return getSelectedIds(this.state.selectedState);
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onSelectAllChange = ({ value }) => {
|
||||
this.setState(selectAll(this.state.selectedState, value));
|
||||
}
|
||||
|
||||
onSelectedChange = ({ id, value, shiftKey = false }) => {
|
||||
this.setState((state) => {
|
||||
return toggleSelected(state, this.props.items, id, value, shiftKey);
|
||||
});
|
||||
}
|
||||
|
||||
onRemoveSelectedPress = () => {
|
||||
this.setState({ isConfirmRemoveModalOpen: true });
|
||||
}
|
||||
|
||||
onRemoveSelectedConfirmed = () => {
|
||||
this.props.onRemoveSelected(this.getSelectedIds());
|
||||
this.setState({ isConfirmRemoveModalOpen: false });
|
||||
}
|
||||
|
||||
onConfirmRemoveModalClose = () => {
|
||||
this.setState({ isConfirmRemoveModalOpen: false });
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
isFetching,
|
||||
isPopulated,
|
||||
error,
|
||||
items,
|
||||
columns,
|
||||
totalRecords,
|
||||
isRemoving,
|
||||
isClearingBlocklistExecuting,
|
||||
onClearBlocklistPress,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
allSelected,
|
||||
allUnselected,
|
||||
selectedState,
|
||||
isConfirmRemoveModalOpen
|
||||
} = this.state;
|
||||
|
||||
const selectedIds = this.getSelectedIds();
|
||||
|
||||
return (
|
||||
<PageContent title="Blocklist">
|
||||
<PageToolbar>
|
||||
<PageToolbarSection>
|
||||
<PageToolbarButton
|
||||
label="Remove Selected"
|
||||
iconName={icons.REMOVE}
|
||||
isDisabled={!selectedIds.length}
|
||||
isSpinning={isRemoving}
|
||||
onPress={this.onRemoveSelectedPress}
|
||||
/>
|
||||
|
||||
<PageToolbarButton
|
||||
label="Clear"
|
||||
iconName={icons.CLEAR}
|
||||
isSpinning={isClearingBlocklistExecuting}
|
||||
onPress={onClearBlocklistPress}
|
||||
/>
|
||||
</PageToolbarSection>
|
||||
|
||||
<PageToolbarSection alignContent={align.RIGHT}>
|
||||
<TableOptionsModalWrapper
|
||||
{...otherProps}
|
||||
columns={columns}
|
||||
>
|
||||
<PageToolbarButton
|
||||
label="Options"
|
||||
iconName={icons.TABLE}
|
||||
/>
|
||||
</TableOptionsModalWrapper>
|
||||
</PageToolbarSection>
|
||||
</PageToolbar>
|
||||
|
||||
<PageContentBody>
|
||||
{
|
||||
isFetching && !isPopulated &&
|
||||
<LoadingIndicator />
|
||||
}
|
||||
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
<div>Unable to load blocklist</div>
|
||||
}
|
||||
|
||||
{
|
||||
isPopulated && !error && !items.length &&
|
||||
<div>
|
||||
No history blocklist
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
isPopulated && !error && !!items.length &&
|
||||
<div>
|
||||
<Table
|
||||
selectAll={true}
|
||||
allSelected={allSelected}
|
||||
allUnselected={allUnselected}
|
||||
columns={columns}
|
||||
{...otherProps}
|
||||
onSelectAllChange={this.onSelectAllChange}
|
||||
>
|
||||
<TableBody>
|
||||
{
|
||||
items.map((item) => {
|
||||
return (
|
||||
<BlocklistRowConnector
|
||||
key={item.id}
|
||||
isSelected={selectedState[item.id] || false}
|
||||
columns={columns}
|
||||
{...item}
|
||||
onSelectedChange={this.onSelectedChange}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
</TableBody>
|
||||
</Table>
|
||||
|
||||
<TablePager
|
||||
totalRecords={totalRecords}
|
||||
isFetching={isFetching}
|
||||
{...otherProps}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</PageContentBody>
|
||||
|
||||
<ConfirmModal
|
||||
isOpen={isConfirmRemoveModalOpen}
|
||||
kind={kinds.DANGER}
|
||||
title="Remove Selected"
|
||||
message={'Are you sure you want to remove the selected items from the blocklist?'}
|
||||
confirmLabel="Remove Selected"
|
||||
onConfirm={this.onRemoveSelectedConfirmed}
|
||||
onCancel={this.onConfirmRemoveModalClose}
|
||||
/>
|
||||
</PageContent>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Blocklist.propTypes = {
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
isPopulated: PropTypes.bool.isRequired,
|
||||
error: PropTypes.object,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
totalRecords: PropTypes.number,
|
||||
isRemoving: PropTypes.bool.isRequired,
|
||||
isClearingBlocklistExecuting: PropTypes.bool.isRequired,
|
||||
onRemoveSelected: PropTypes.func.isRequired,
|
||||
onClearBlocklistPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default Blocklist;
|
||||
@@ -1,160 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
|
||||
import withCurrentPage from 'Components/withCurrentPage';
|
||||
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
|
||||
import * as blocklistActions from 'Store/Actions/blocklistActions';
|
||||
import { executeCommand } from 'Store/Actions/commandActions';
|
||||
import * as commandNames from 'Commands/commandNames';
|
||||
import Blocklist from './Blocklist';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.blocklist,
|
||||
createCommandExecutingSelector(commandNames.CLEAR_BLOCKLIST),
|
||||
(blocklist, isClearingBlocklistExecuting) => {
|
||||
return {
|
||||
isClearingBlocklistExecuting,
|
||||
...blocklist
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
...blocklistActions,
|
||||
executeCommand
|
||||
};
|
||||
|
||||
class BlocklistConnector extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
componentDidMount() {
|
||||
const {
|
||||
useCurrentPage,
|
||||
fetchBlocklist,
|
||||
gotoBlocklistFirstPage
|
||||
} = this.props;
|
||||
|
||||
registerPagePopulator(this.repopulate);
|
||||
|
||||
if (useCurrentPage) {
|
||||
fetchBlocklist();
|
||||
} else {
|
||||
gotoBlocklistFirstPage();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.isClearingBlocklistExecuting && !this.props.isClearingBlocklistExecuting) {
|
||||
this.props.gotoBlocklistFirstPage();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.clearBlocklist();
|
||||
unregisterPagePopulator(this.repopulate);
|
||||
}
|
||||
|
||||
//
|
||||
// Control
|
||||
|
||||
repopulate = () => {
|
||||
this.props.fetchBlocklist();
|
||||
}
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onFirstPagePress = () => {
|
||||
this.props.gotoBlocklistFirstPage();
|
||||
}
|
||||
|
||||
onPreviousPagePress = () => {
|
||||
this.props.gotoBlocklistPreviousPage();
|
||||
}
|
||||
|
||||
onNextPagePress = () => {
|
||||
this.props.gotoBlocklistNextPage();
|
||||
}
|
||||
|
||||
onLastPagePress = () => {
|
||||
this.props.gotoBlocklistLastPage();
|
||||
}
|
||||
|
||||
onPageSelect = (page) => {
|
||||
this.props.gotoBlocklistPage({ page });
|
||||
}
|
||||
|
||||
onRemoveSelected = (ids) => {
|
||||
this.props.removeBlocklistItems({ ids });
|
||||
}
|
||||
|
||||
onSortPress = (sortKey) => {
|
||||
this.props.setBlocklistSort({ sortKey });
|
||||
}
|
||||
|
||||
onTableOptionChange = (payload) => {
|
||||
this.props.setBlocklistTableOption(payload);
|
||||
|
||||
if (payload.pageSize) {
|
||||
this.props.gotoBlocklistFirstPage();
|
||||
}
|
||||
}
|
||||
|
||||
onClearBlocklistPress = () => {
|
||||
this.props.executeCommand({ name: commandNames.CLEAR_BLOCKLIST });
|
||||
}
|
||||
|
||||
onTableOptionChange = (payload) => {
|
||||
this.props.setBlocklistTableOption(payload);
|
||||
|
||||
if (payload.pageSize) {
|
||||
this.props.gotoBlocklistFirstPage();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Blocklist
|
||||
onFirstPagePress={this.onFirstPagePress}
|
||||
onPreviousPagePress={this.onPreviousPagePress}
|
||||
onNextPagePress={this.onNextPagePress}
|
||||
onLastPagePress={this.onLastPagePress}
|
||||
onPageSelect={this.onPageSelect}
|
||||
onRemoveSelected={this.onRemoveSelected}
|
||||
onSortPress={this.onSortPress}
|
||||
onTableOptionChange={this.onTableOptionChange}
|
||||
onClearBlocklistPress={this.onClearBlocklistPress}
|
||||
{...this.props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
BlocklistConnector.propTypes = {
|
||||
useCurrentPage: PropTypes.bool.isRequired,
|
||||
isClearingBlocklistExecuting: PropTypes.bool.isRequired,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
fetchBlocklist: PropTypes.func.isRequired,
|
||||
gotoBlocklistFirstPage: PropTypes.func.isRequired,
|
||||
gotoBlocklistPreviousPage: PropTypes.func.isRequired,
|
||||
gotoBlocklistNextPage: PropTypes.func.isRequired,
|
||||
gotoBlocklistLastPage: PropTypes.func.isRequired,
|
||||
gotoBlocklistPage: PropTypes.func.isRequired,
|
||||
removeBlocklistItems: PropTypes.func.isRequired,
|
||||
setBlocklistSort: PropTypes.func.isRequired,
|
||||
setBlocklistTableOption: PropTypes.func.isRequired,
|
||||
clearBlocklist: PropTypes.func.isRequired,
|
||||
executeCommand: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default withCurrentPage(
|
||||
connect(createMapStateToProps, mapDispatchToProps)(BlocklistConnector)
|
||||
);
|
||||
@@ -1,8 +1,7 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import formatAge from 'Utilities/Number/formatAge';
|
||||
import formatDateTime from 'Utilities/Date/formatDateTime';
|
||||
import formatPreferredWordScore from 'Utilities/Number/formatPreferredWordScore';
|
||||
import formatAge from 'Utilities/Number/formatAge';
|
||||
import Link from 'Components/Link/Link';
|
||||
import DescriptionList from 'Components/DescriptionList/DescriptionList';
|
||||
import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem';
|
||||
@@ -23,11 +22,8 @@ function HistoryDetails(props) {
|
||||
const {
|
||||
indexer,
|
||||
releaseGroup,
|
||||
preferredWordScore,
|
||||
seriesMatchType,
|
||||
nzbInfoUrl,
|
||||
downloadClient,
|
||||
downloadClientName,
|
||||
downloadId,
|
||||
age,
|
||||
ageHours,
|
||||
@@ -35,8 +31,6 @@ function HistoryDetails(props) {
|
||||
publishedDate
|
||||
} = data;
|
||||
|
||||
const downloadClientNameInfo = downloadClientName ?? downloadClient;
|
||||
|
||||
return (
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
@@ -46,45 +40,24 @@ function HistoryDetails(props) {
|
||||
/>
|
||||
|
||||
{
|
||||
indexer ?
|
||||
!!indexer &&
|
||||
<DescriptionListItem
|
||||
title="Indexer"
|
||||
data={indexer}
|
||||
/> :
|
||||
null
|
||||
/>
|
||||
}
|
||||
|
||||
{
|
||||
releaseGroup ?
|
||||
!!releaseGroup &&
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title="Release Group"
|
||||
data={releaseGroup}
|
||||
/> :
|
||||
null
|
||||
/>
|
||||
}
|
||||
|
||||
{
|
||||
preferredWordScore && preferredWordScore !== '0' ?
|
||||
<DescriptionListItem
|
||||
title="Preferred Word Score"
|
||||
data={formatPreferredWordScore(preferredWordScore)}
|
||||
/> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
seriesMatchType ?
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title="Series Match Type"
|
||||
data={seriesMatchType}
|
||||
/> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
nzbInfoUrl ?
|
||||
!!nzbInfoUrl &&
|
||||
<span>
|
||||
<DescriptionListItemTitle>
|
||||
Info URL
|
||||
@@ -93,44 +66,39 @@ function HistoryDetails(props) {
|
||||
<DescriptionListItemDescription>
|
||||
<Link to={nzbInfoUrl}>{nzbInfoUrl}</Link>
|
||||
</DescriptionListItemDescription>
|
||||
</span> :
|
||||
null
|
||||
</span>
|
||||
}
|
||||
|
||||
{
|
||||
downloadClientNameInfo ?
|
||||
!!downloadClient &&
|
||||
<DescriptionListItem
|
||||
title="Download Client"
|
||||
data={downloadClientNameInfo}
|
||||
/> :
|
||||
null
|
||||
data={downloadClient}
|
||||
/>
|
||||
}
|
||||
|
||||
{
|
||||
downloadId ?
|
||||
!!downloadId &&
|
||||
<DescriptionListItem
|
||||
title="Grab ID"
|
||||
data={downloadId}
|
||||
/> :
|
||||
null
|
||||
/>
|
||||
}
|
||||
|
||||
{
|
||||
age || ageHours || ageMinutes ?
|
||||
!!indexer &&
|
||||
<DescriptionListItem
|
||||
title="Age (when grabbed)"
|
||||
data={formatAge(age, ageHours, ageMinutes)}
|
||||
/> :
|
||||
null
|
||||
/>
|
||||
}
|
||||
|
||||
{
|
||||
publishedDate ?
|
||||
!!publishedDate &&
|
||||
<DescriptionListItem
|
||||
title="Published Date"
|
||||
data={formatDateTime(publishedDate, shortDateFormat, timeFormat, { includeSeconds: true })}
|
||||
/> :
|
||||
null
|
||||
/>
|
||||
}
|
||||
</DescriptionList>
|
||||
);
|
||||
@@ -150,12 +118,11 @@ function HistoryDetails(props) {
|
||||
/>
|
||||
|
||||
{
|
||||
message ?
|
||||
!!message &&
|
||||
<DescriptionListItem
|
||||
title="Message"
|
||||
data={message}
|
||||
/> :
|
||||
null
|
||||
/>
|
||||
}
|
||||
</DescriptionList>
|
||||
);
|
||||
@@ -163,7 +130,6 @@ function HistoryDetails(props) {
|
||||
|
||||
if (eventType === 'downloadFolderImported') {
|
||||
const {
|
||||
preferredWordScore,
|
||||
droppedPath,
|
||||
importedPath
|
||||
} = data;
|
||||
@@ -177,32 +143,21 @@ function HistoryDetails(props) {
|
||||
/>
|
||||
|
||||
{
|
||||
droppedPath ?
|
||||
!!droppedPath &&
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title="Source"
|
||||
data={droppedPath}
|
||||
/> :
|
||||
null
|
||||
/>
|
||||
}
|
||||
|
||||
{
|
||||
importedPath ?
|
||||
!!importedPath &&
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title="Imported To"
|
||||
data={importedPath}
|
||||
/> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
preferredWordScore && preferredWordScore !== '0' ?
|
||||
<DescriptionListItem
|
||||
title="Preferred Word Score"
|
||||
data={formatPreferredWordScore(preferredWordScore)}
|
||||
/> :
|
||||
null
|
||||
/>
|
||||
}
|
||||
</DescriptionList>
|
||||
);
|
||||
@@ -210,8 +165,7 @@ function HistoryDetails(props) {
|
||||
|
||||
if (eventType === 'episodeFileDeleted') {
|
||||
const {
|
||||
reason,
|
||||
preferredWordScore
|
||||
reason
|
||||
} = data;
|
||||
|
||||
let reasonMessage = '';
|
||||
@@ -221,7 +175,7 @@ function HistoryDetails(props) {
|
||||
reasonMessage = 'File was deleted by via UI';
|
||||
break;
|
||||
case 'MissingFromDisk':
|
||||
reasonMessage = 'Sonarr was unable to find the file on disk so the file was unlinked from the episode in the database';
|
||||
reasonMessage = 'Sonarr was unable to find the file on disk so it was removed';
|
||||
break;
|
||||
case 'Upgrade':
|
||||
reasonMessage = 'File was deleted to import an upgrade';
|
||||
@@ -241,15 +195,6 @@ function HistoryDetails(props) {
|
||||
title="Reason"
|
||||
data={reasonMessage}
|
||||
/>
|
||||
|
||||
{
|
||||
preferredWordScore && preferredWordScore !== '0' ?
|
||||
<DescriptionListItem
|
||||
title="Preferred Word Score"
|
||||
data={formatPreferredWordScore(preferredWordScore)}
|
||||
/> :
|
||||
null
|
||||
}
|
||||
</DescriptionList>
|
||||
);
|
||||
}
|
||||
@@ -301,12 +246,11 @@ function HistoryDetails(props) {
|
||||
/>
|
||||
|
||||
{
|
||||
message ?
|
||||
!!message &&
|
||||
<DescriptionListItem
|
||||
title="Message"
|
||||
data={message}
|
||||
/> :
|
||||
null
|
||||
/>
|
||||
}
|
||||
</DescriptionList>
|
||||
);
|
||||
|
||||
@@ -10,12 +10,6 @@
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.preferredWordScore {
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 55px;
|
||||
}
|
||||
|
||||
.releaseGroup {
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import formatPreferredWordScore from 'Utilities/Number/formatPreferredWordScore';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
||||
@@ -195,17 +194,6 @@ class HistoryRow extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'preferredWordScore') {
|
||||
return (
|
||||
<TableRowCell
|
||||
key={name}
|
||||
className={styles.preferredWordScore}
|
||||
>
|
||||
{formatPreferredWordScore(data.preferredWordScore)}
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'releaseGroup') {
|
||||
return (
|
||||
<TableRowCell
|
||||
@@ -217,16 +205,6 @@ class HistoryRow extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'sourceTitle') {
|
||||
return (
|
||||
<TableRowCell
|
||||
key={name}
|
||||
>
|
||||
{sourceTitle}
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'details') {
|
||||
return (
|
||||
<TableRowCell
|
||||
|
||||
@@ -31,8 +31,6 @@ class Queue extends Component {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this._shouldBlockRefresh = false;
|
||||
|
||||
this.state = {
|
||||
allSelected: false,
|
||||
allUnselected: false,
|
||||
@@ -44,14 +42,6 @@ class Queue extends Component {
|
||||
};
|
||||
}
|
||||
|
||||
shouldComponentUpdate() {
|
||||
if (this._shouldBlockRefresh) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const {
|
||||
items,
|
||||
@@ -92,10 +82,6 @@ class Queue extends Component {
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onQueueRowModalOpenOrClose = (isOpen) => {
|
||||
this._shouldBlockRefresh = isOpen;
|
||||
}
|
||||
|
||||
onSelectAllChange = ({ value }) => {
|
||||
this.setState(selectAll(this.state.selectedState, value));
|
||||
}
|
||||
@@ -111,19 +97,15 @@ class Queue extends Component {
|
||||
}
|
||||
|
||||
onRemoveSelectedPress = () => {
|
||||
this.setState({ isConfirmRemoveModalOpen: true }, () => {
|
||||
this._shouldBlockRefresh = true;
|
||||
});
|
||||
this.setState({ isConfirmRemoveModalOpen: true });
|
||||
}
|
||||
|
||||
onRemoveSelectedConfirmed = (payload) => {
|
||||
this._shouldBlockRefresh = false;
|
||||
this.props.onRemoveSelectedPress({ ids: this.getSelectedIds(), ...payload });
|
||||
this.setState({ isConfirmRemoveModalOpen: false });
|
||||
}
|
||||
|
||||
onConfirmRemoveModalClose = () => {
|
||||
this._shouldBlockRefresh = false;
|
||||
this.setState({ isConfirmRemoveModalOpen: false });
|
||||
}
|
||||
|
||||
@@ -223,7 +205,7 @@ class Queue extends Component {
|
||||
}
|
||||
|
||||
{
|
||||
isAllPopulated && !hasError && !items.length &&
|
||||
isPopulated && !hasError && !items.length &&
|
||||
<div>
|
||||
Queue is empty
|
||||
</div>
|
||||
@@ -252,7 +234,6 @@ class Queue extends Component {
|
||||
columns={columns}
|
||||
{...item}
|
||||
onSelectedChange={this.onSelectedChange}
|
||||
onQueueRowModalOpenOrClose={this.onQueueRowModalOpenOrClose}
|
||||
/>
|
||||
);
|
||||
})
|
||||
@@ -279,17 +260,6 @@ class Queue extends Component {
|
||||
return !!(item && item.seriesId && item.episodeId);
|
||||
})
|
||||
)}
|
||||
allPending={isConfirmRemoveModalOpen && (
|
||||
selectedIds.every((id) => {
|
||||
const item = items.find((i) => i.id === id);
|
||||
|
||||
if (!item) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return item.status === 'delay' || item.status === 'downloadClientUnavailable';
|
||||
})
|
||||
)}
|
||||
onRemovePress={this.onRemoveSelectedConfirmed}
|
||||
onModalClose={this.onConfirmRemoveModalClose}
|
||||
/>
|
||||
|
||||
@@ -48,7 +48,6 @@ class QueueConnector extends Component {
|
||||
const {
|
||||
useCurrentPage,
|
||||
fetchQueue,
|
||||
fetchQueueStatus,
|
||||
gotoQueueFirstPage
|
||||
} = this.props;
|
||||
|
||||
@@ -59,8 +58,6 @@ class QueueConnector extends Component {
|
||||
} else {
|
||||
gotoQueueFirstPage();
|
||||
}
|
||||
|
||||
fetchQueueStatus();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
@@ -171,7 +168,6 @@ QueueConnector.propTypes = {
|
||||
useCurrentPage: PropTypes.bool.isRequired,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
fetchQueue: PropTypes.func.isRequired,
|
||||
fetchQueueStatus: PropTypes.func.isRequired,
|
||||
gotoQueueFirstPage: PropTypes.func.isRequired,
|
||||
gotoQueuePreviousPage: PropTypes.func.isRequired,
|
||||
gotoQueueNextPage: PropTypes.func.isRequired,
|
||||
|
||||
@@ -19,7 +19,6 @@ import QueueStatusCell from './QueueStatusCell';
|
||||
import TimeleftCell from './TimeleftCell';
|
||||
import RemoveQueueItemModal from './RemoveQueueItemModal';
|
||||
import styles from './QueueRow.css';
|
||||
import formatBytes from 'Utilities/Number/formatBytes';
|
||||
|
||||
class QueueRow extends Component {
|
||||
|
||||
@@ -42,33 +41,20 @@ class QueueRow extends Component {
|
||||
this.setState({ isRemoveQueueItemModalOpen: true });
|
||||
}
|
||||
|
||||
onRemoveQueueItemModalConfirmed = (blocklist) => {
|
||||
const {
|
||||
onRemoveQueueItemPress,
|
||||
onQueueRowModalOpenOrClose
|
||||
} = this.props;
|
||||
|
||||
onQueueRowModalOpenOrClose(false);
|
||||
onRemoveQueueItemPress(blocklist);
|
||||
|
||||
onRemoveQueueItemModalConfirmed = (blacklist) => {
|
||||
this.props.onRemoveQueueItemPress(blacklist);
|
||||
this.setState({ isRemoveQueueItemModalOpen: false });
|
||||
}
|
||||
|
||||
onRemoveQueueItemModalClose = () => {
|
||||
this.props.onQueueRowModalOpenOrClose(false);
|
||||
|
||||
this.setState({ isRemoveQueueItemModalOpen: false });
|
||||
}
|
||||
|
||||
onInteractiveImportPress = () => {
|
||||
this.props.onQueueRowModalOpenOrClose(true);
|
||||
|
||||
this.setState({ isInteractiveImportModalOpen: true });
|
||||
}
|
||||
|
||||
onInteractiveImportModalClose = () => {
|
||||
this.props.onQueueRowModalOpenOrClose(false);
|
||||
|
||||
this.setState({ isInteractiveImportModalOpen: false });
|
||||
}
|
||||
|
||||
@@ -281,12 +267,6 @@ class QueueRow extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'size') {
|
||||
return (
|
||||
<TableRowCell key={name}>{formatBytes(size)}</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'outputPath') {
|
||||
return (
|
||||
<TableRowCell key={name}>
|
||||
@@ -377,7 +357,6 @@ class QueueRow extends Component {
|
||||
isOpen={isRemoveQueueItemModalOpen}
|
||||
sourceTitle={title}
|
||||
canIgnore={!!series}
|
||||
isPending={isPending}
|
||||
onRemovePress={this.onRemoveQueueItemModalConfirmed}
|
||||
onModalClose={this.onRemoveQueueItemModalClose}
|
||||
/>
|
||||
@@ -418,8 +397,7 @@ QueueRow.propTypes = {
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
onSelectedChange: PropTypes.func.isRequired,
|
||||
onGrabPress: PropTypes.func.isRequired,
|
||||
onRemoveQueueItemPress: PropTypes.func.isRequired,
|
||||
onQueueRowModalOpenOrClose: PropTypes.func.isRequired
|
||||
onRemoveQueueItemPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
QueueRow.defaultProps = {
|
||||
|
||||
@@ -3,7 +3,3 @@
|
||||
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
.noMessages {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
@@ -12,10 +12,7 @@ function getDetailedPopoverBody(statusMessages) {
|
||||
{
|
||||
statusMessages.map(({ title, messages }) => {
|
||||
return (
|
||||
<div
|
||||
key={title}
|
||||
className={messages.length ? undefined: styles.noMessages}
|
||||
>
|
||||
<div key={title}>
|
||||
{title}
|
||||
<ul>
|
||||
{
|
||||
|
||||
@@ -21,7 +21,7 @@ class RemoveQueueItemModal extends Component {
|
||||
|
||||
this.state = {
|
||||
remove: true,
|
||||
blocklist: false
|
||||
blacklist: false
|
||||
};
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ class RemoveQueueItemModal extends Component {
|
||||
resetState = function() {
|
||||
this.setState({
|
||||
remove: true,
|
||||
blocklist: false
|
||||
blacklist: false
|
||||
});
|
||||
}
|
||||
|
||||
@@ -42,8 +42,8 @@ class RemoveQueueItemModal extends Component {
|
||||
this.setState({ remove: value });
|
||||
}
|
||||
|
||||
onBlocklistChange = ({ value }) => {
|
||||
this.setState({ blocklist: value });
|
||||
onBlacklistChange = ({ value }) => {
|
||||
this.setState({ blacklist: value });
|
||||
}
|
||||
|
||||
onRemoveConfirmed = () => {
|
||||
@@ -65,11 +65,10 @@ class RemoveQueueItemModal extends Component {
|
||||
const {
|
||||
isOpen,
|
||||
sourceTitle,
|
||||
canIgnore,
|
||||
isPending
|
||||
canIgnore
|
||||
} = this.props;
|
||||
|
||||
const { remove, blocklist } = this.state;
|
||||
const { remove, blacklist } = this.state;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
@@ -89,32 +88,28 @@ class RemoveQueueItemModal extends Component {
|
||||
Are you sure you want to remove '{sourceTitle}' from the queue?
|
||||
</div>
|
||||
|
||||
{
|
||||
isPending ?
|
||||
null :
|
||||
<FormGroup>
|
||||
<FormLabel>Remove From Download Client</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="remove"
|
||||
value={remove}
|
||||
helpTextWarning="Removing will remove the download and the file(s) from the download client."
|
||||
isDisabled={!canIgnore}
|
||||
onChange={this.onRemoveChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
}
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>Add Release To Blocklist</FormLabel>
|
||||
<FormLabel>Remove From Download Client</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="blocklist"
|
||||
value={blocklist}
|
||||
name="remove"
|
||||
value={remove}
|
||||
helpTextWarning="Removing will remove the download and the file(s) from the download client."
|
||||
isDisabled={!canIgnore}
|
||||
onChange={this.onRemoveChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>Blacklist Release</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="blacklist"
|
||||
value={blacklist}
|
||||
helpText="Starts a search for this episode again and prevents this release from being grabbed again"
|
||||
onChange={this.onBlocklistChange}
|
||||
onChange={this.onBlacklistChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
@@ -142,7 +137,6 @@ RemoveQueueItemModal.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
sourceTitle: PropTypes.string.isRequired,
|
||||
canIgnore: PropTypes.bool.isRequired,
|
||||
isPending: PropTypes.bool.isRequired,
|
||||
onRemovePress: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
@@ -22,7 +22,7 @@ class RemoveQueueItemsModal extends Component {
|
||||
|
||||
this.state = {
|
||||
remove: true,
|
||||
blocklist: false
|
||||
blacklist: false
|
||||
};
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ class RemoveQueueItemsModal extends Component {
|
||||
resetState = function() {
|
||||
this.setState({
|
||||
remove: true,
|
||||
blocklist: false
|
||||
blacklist: false
|
||||
});
|
||||
}
|
||||
|
||||
@@ -43,8 +43,8 @@ class RemoveQueueItemsModal extends Component {
|
||||
this.setState({ remove: value });
|
||||
}
|
||||
|
||||
onBlocklistChange = ({ value }) => {
|
||||
this.setState({ blocklist: value });
|
||||
onBlacklistChange = ({ value }) => {
|
||||
this.setState({ blacklist: value });
|
||||
}
|
||||
|
||||
onRemoveConfirmed = () => {
|
||||
@@ -66,11 +66,10 @@ class RemoveQueueItemsModal extends Component {
|
||||
const {
|
||||
isOpen,
|
||||
selectedCount,
|
||||
canIgnore,
|
||||
allPending
|
||||
canIgnore
|
||||
} = this.props;
|
||||
|
||||
const { remove, blocklist } = this.state;
|
||||
const { remove, blacklist } = this.state;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
@@ -90,34 +89,30 @@ class RemoveQueueItemsModal extends Component {
|
||||
Are you sure you want to remove {selectedCount} item{selectedCount > 1 ? 's' : ''} from the queue?
|
||||
</div>
|
||||
|
||||
{
|
||||
allPending ?
|
||||
null :
|
||||
<FormGroup>
|
||||
<FormLabel>Remove From Download Client</FormLabel>
|
||||
<FormGroup>
|
||||
<FormLabel>Remove From Download Client</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="remove"
|
||||
value={remove}
|
||||
helpTextWarning="Removing will remove the download and the file(s) from the download client."
|
||||
isDisabled={!canIgnore}
|
||||
onChange={this.onRemoveChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
}
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="remove"
|
||||
value={remove}
|
||||
helpTextWarning="Removing will remove the download and the file(s) from the download client."
|
||||
isDisabled={!canIgnore}
|
||||
onChange={this.onRemoveChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>
|
||||
Add Release{selectedCount > 1 ? 's' : ''} To Blocklist
|
||||
Blacklist Release{selectedCount > 1 ? 's' : ''}
|
||||
</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="blocklist"
|
||||
value={blocklist}
|
||||
name="blacklist"
|
||||
value={blacklist}
|
||||
helpText="Prevents Sonarr from automatically grabbing this episode again"
|
||||
onChange={this.onBlocklistChange}
|
||||
onChange={this.onBlacklistChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
@@ -145,7 +140,6 @@ RemoveQueueItemsModal.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
selectedCount: PropTypes.number.isRequired,
|
||||
canIgnore: PropTypes.bool.isRequired,
|
||||
allPending: PropTypes.bool.isRequired,
|
||||
onRemovePress: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
@@ -154,7 +154,7 @@ class AddNewSeries extends Component {
|
||||
<div className={styles.noResults}>Couldn't find any results for '{term}'</div>
|
||||
<div>You can also search using TVDB ID of a show. eg. tvdb:71663</div>
|
||||
<div>
|
||||
<Link to="https://wiki.servarr.com/sonarr/faq#why-cant-i-add-a-new-series-when-i-know-the-tvdb-id">
|
||||
<Link to="https://github.com/Sonarr/Sonarr/wiki/FAQ#why-cant-i-add-a-new-series-when-i-know-the-tvdb-id">
|
||||
Why can't I find my show?
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@@ -30,7 +30,9 @@ class AddNewSeriesModalContent extends Component {
|
||||
this.state = {
|
||||
seriesType: props.initialSeriesType === seriesTypes.STANDARD ?
|
||||
props.seriesType.value :
|
||||
props.initialSeriesType
|
||||
props.initialSeriesType,
|
||||
searchForMissingEpisodes: false,
|
||||
searchForCutoffUnmetEpisodes: false
|
||||
};
|
||||
}
|
||||
|
||||
@@ -43,6 +45,14 @@ class AddNewSeriesModalContent extends Component {
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onSearchForMissingEpisodesChange = ({ value }) => {
|
||||
this.setState({ searchForMissingEpisodes: value });
|
||||
}
|
||||
|
||||
onSearchForCutoffUnmetEpisodesChange = ({ value }) => {
|
||||
this.setState({ searchForCutoffUnmetEpisodes: value });
|
||||
}
|
||||
|
||||
onQualityProfileIdChange = ({ value }) => {
|
||||
this.props.onInputChange({ name: 'qualityProfileId', value: parseInt(value) });
|
||||
}
|
||||
@@ -53,10 +63,14 @@ class AddNewSeriesModalContent extends Component {
|
||||
|
||||
onAddSeriesPress = () => {
|
||||
const {
|
||||
searchForMissingEpisodes,
|
||||
searchForCutoffUnmetEpisodes,
|
||||
seriesType
|
||||
} = this.state;
|
||||
|
||||
this.props.onAddSeriesPress(
|
||||
searchForMissingEpisodes,
|
||||
searchForCutoffUnmetEpisodes,
|
||||
seriesType
|
||||
);
|
||||
}
|
||||
@@ -77,8 +91,6 @@ class AddNewSeriesModalContent extends Component {
|
||||
languageProfileId,
|
||||
seriesType,
|
||||
seasonFolder,
|
||||
searchForMissingEpisodes,
|
||||
searchForCutoffUnmetEpisodes,
|
||||
folder,
|
||||
tags,
|
||||
showLanguageProfile,
|
||||
@@ -89,6 +101,11 @@ class AddNewSeriesModalContent extends Component {
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
searchForMissingEpisodes,
|
||||
searchForCutoffUnmetEpisodes
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
@@ -254,8 +271,8 @@ class AddNewSeriesModalContent extends Component {
|
||||
containerClassName={styles.searchInputContainer}
|
||||
className={styles.searchInput}
|
||||
name="searchForMissingEpisodes"
|
||||
onChange={onInputChange}
|
||||
{...searchForMissingEpisodes}
|
||||
value={searchForMissingEpisodes}
|
||||
onChange={this.onSearchForMissingEpisodesChange}
|
||||
/>
|
||||
</label>
|
||||
|
||||
@@ -268,8 +285,8 @@ class AddNewSeriesModalContent extends Component {
|
||||
containerClassName={styles.searchInputContainer}
|
||||
className={styles.searchInput}
|
||||
name="searchForCutoffUnmetEpisodes"
|
||||
onChange={onInputChange}
|
||||
{...searchForCutoffUnmetEpisodes}
|
||||
value={searchForCutoffUnmetEpisodes}
|
||||
onChange={this.onSearchForCutoffUnmetEpisodesChange}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
@@ -302,8 +319,6 @@ AddNewSeriesModalContent.propTypes = {
|
||||
languageProfileId: PropTypes.object,
|
||||
seriesType: PropTypes.object.isRequired,
|
||||
seasonFolder: PropTypes.object.isRequired,
|
||||
searchForMissingEpisodes: PropTypes.object.isRequired,
|
||||
searchForCutoffUnmetEpisodes: PropTypes.object.isRequired,
|
||||
folder: PropTypes.string.isRequired,
|
||||
tags: PropTypes.object.isRequired,
|
||||
showLanguageProfile: PropTypes.bool.isRequired,
|
||||
|
||||
@@ -55,7 +55,7 @@ class AddNewSeriesModalContentConnector extends Component {
|
||||
this.props.setAddSeriesDefault({ [name]: value });
|
||||
}
|
||||
|
||||
onAddSeriesPress = (seriesType) => {
|
||||
onAddSeriesPress = (searchForMissingEpisodes, searchForCutoffUnmetEpisodes, seriesType) => {
|
||||
const {
|
||||
tvdbId,
|
||||
rootFolderPath,
|
||||
@@ -63,8 +63,6 @@ class AddNewSeriesModalContentConnector extends Component {
|
||||
qualityProfileId,
|
||||
languageProfileId,
|
||||
seasonFolder,
|
||||
searchForMissingEpisodes,
|
||||
searchForCutoffUnmetEpisodes,
|
||||
tags
|
||||
} = this.props;
|
||||
|
||||
@@ -76,9 +74,9 @@ class AddNewSeriesModalContentConnector extends Component {
|
||||
languageProfileId: languageProfileId.value,
|
||||
seriesType,
|
||||
seasonFolder: seasonFolder.value,
|
||||
searchForMissingEpisodes: searchForMissingEpisodes.value,
|
||||
searchForCutoffUnmetEpisodes: searchForCutoffUnmetEpisodes.value,
|
||||
tags: tags.value
|
||||
tags: tags.value,
|
||||
searchForMissingEpisodes,
|
||||
searchForCutoffUnmetEpisodes
|
||||
});
|
||||
}
|
||||
|
||||
@@ -104,8 +102,6 @@ AddNewSeriesModalContentConnector.propTypes = {
|
||||
languageProfileId: PropTypes.object,
|
||||
seriesType: PropTypes.object.isRequired,
|
||||
seasonFolder: PropTypes.object.isRequired,
|
||||
searchForMissingEpisodes: PropTypes.object.isRequired,
|
||||
searchForCutoffUnmetEpisodes: PropTypes.object.isRequired,
|
||||
tags: PropTypes.object.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired,
|
||||
setAddSeriesDefault: PropTypes.func.isRequired,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
.container {
|
||||
display: flex;
|
||||
.series {
|
||||
padding: 10px 20px;
|
||||
width: 100%;
|
||||
|
||||
@@ -7,19 +6,3 @@
|
||||
background-color: $menuItemHoverBackgroundColor;
|
||||
}
|
||||
}
|
||||
|
||||
.series {
|
||||
flex: 1 0 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tvdbLink {
|
||||
composes: link from '~Components/Link/Link.css';
|
||||
|
||||
margin-left: auto;
|
||||
color: $textColor;
|
||||
}
|
||||
|
||||
.tvdbLinkIcon {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
@@ -1,28 +1,33 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { useCallback } from 'react';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import React, { Component } from 'react';
|
||||
import Link from 'Components/Link/Link';
|
||||
import Icon from 'Components/Icon';
|
||||
import ImportSeriesTitle from './ImportSeriesTitle';
|
||||
import styles from './ImportSeriesSearchResult.css';
|
||||
|
||||
function ImportSeriesSearchResult(props) {
|
||||
const {
|
||||
tvdbId,
|
||||
title,
|
||||
year,
|
||||
network,
|
||||
isExistingSeries,
|
||||
onPress
|
||||
} = props;
|
||||
class ImportSeriesSearchResult extends Component {
|
||||
|
||||
const onPressCallback = useCallback(() => onPress(tvdbId), [tvdbId, onPress]);
|
||||
//
|
||||
// Listeners
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
onPress = () => {
|
||||
this.props.onPress(this.props.tvdbId);
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
title,
|
||||
year,
|
||||
network,
|
||||
isExistingSeries
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Link
|
||||
className={styles.series}
|
||||
onPress={onPressCallback}
|
||||
onPress={this.onPress}
|
||||
>
|
||||
<ImportSeriesTitle
|
||||
title={title}
|
||||
@@ -31,19 +36,8 @@ function ImportSeriesSearchResult(props) {
|
||||
isExistingSeries={isExistingSeries}
|
||||
/>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
className={styles.tvdbLink}
|
||||
to={`http://www.thetvdb.com/?tab=series&id=${tvdbId}`}
|
||||
>
|
||||
<Icon
|
||||
className={styles.tvdbLinkIcon}
|
||||
name={icons.EXTERNAL_LINK}
|
||||
size={16}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ImportSeriesSearchResult.propTypes = {
|
||||
|
||||
@@ -20,27 +20,24 @@ function ImportSeriesTitle(props) {
|
||||
|
||||
{
|
||||
!title.contains(year) &&
|
||||
year > 0 ?
|
||||
year > 0 &&
|
||||
<span className={styles.year}>
|
||||
({year})
|
||||
</span> :
|
||||
null
|
||||
</span>
|
||||
}
|
||||
|
||||
{
|
||||
network ?
|
||||
<Label>{network}</Label> :
|
||||
null
|
||||
!!network &&
|
||||
<Label>{network}</Label>
|
||||
}
|
||||
|
||||
{
|
||||
isExistingSeries ?
|
||||
isExistingSeries &&
|
||||
<Label
|
||||
kind={kinds.WARNING}
|
||||
>
|
||||
Existing
|
||||
</Label> :
|
||||
null
|
||||
</Label>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -30,9 +30,3 @@
|
||||
.importButtonIcon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.addErrorAlert {
|
||||
composes: alert from '~Components/Alert.css';
|
||||
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { icons, kinds, sizes } from 'Helpers/Props';
|
||||
import Button from 'Components/Link/Button';
|
||||
import Alert from 'Components/Alert';
|
||||
import Icon from 'Components/Icon';
|
||||
import FieldSet from 'Components/FieldSet';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
@@ -48,27 +47,21 @@ class ImportSeriesSelectFolder extends Component {
|
||||
isWindows,
|
||||
isFetching,
|
||||
isPopulated,
|
||||
isSaving,
|
||||
error,
|
||||
saveError,
|
||||
items
|
||||
} = this.props;
|
||||
|
||||
const hasRootFolders = items.length > 0;
|
||||
|
||||
return (
|
||||
<PageContent title="Import Series">
|
||||
<PageContentBody>
|
||||
{
|
||||
isFetching && !isPopulated ?
|
||||
<LoadingIndicator /> :
|
||||
null
|
||||
isFetching && !isPopulated &&
|
||||
<LoadingIndicator />
|
||||
}
|
||||
|
||||
{
|
||||
!isFetching && error ?
|
||||
<div>Unable to load root folders</div> :
|
||||
null
|
||||
!isFetching && !!error &&
|
||||
<div>Unable to load root folders</div>
|
||||
}
|
||||
|
||||
{
|
||||
@@ -85,16 +78,13 @@ class ImportSeriesSelectFolder extends Component {
|
||||
Make sure that your files include the quality in their filenames. eg. <span className={styles.code}>episode.s02e15.bluray.mkv</span>
|
||||
</li>
|
||||
<li className={styles.tip}>
|
||||
Point Sonarr to the folder containing all of your tv shows, not a specific one. eg. <span className={styles.code}>"{isWindows ? 'C:\\tv shows' : '/tv shows'}"</span> and not <span className={styles.code}>"{isWindows ? 'C:\\tv shows\\the simpsons' : '/tv shows/the simpsons'}"</span> Additionally, each series must be in its own folder within the root/library folder.
|
||||
</li>
|
||||
<li className={styles.tip}>
|
||||
Do not use for importing downloads from your download client, this is only for existing organized libraries, not unsorted files.
|
||||
Point Sonarr to the folder containing all of your tv shows, not a specific one. eg. <span className={styles.code}>"{isWindows ? 'C:\\tv shows' : '/tv shows'}"</span> and not <span className={styles.code}>"{isWindows ? 'C:\\tv shows\\the simpsons' : '/tv shows/the simpsons'}"</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{
|
||||
hasRootFolders ?
|
||||
items.length > 0 ?
|
||||
<div className={styles.recentFolders}>
|
||||
<FieldSet legend="Root Folders">
|
||||
<RootFolders
|
||||
@@ -104,51 +94,35 @@ class ImportSeriesSelectFolder extends Component {
|
||||
items={items}
|
||||
/>
|
||||
</FieldSet>
|
||||
|
||||
<Button
|
||||
kind={kinds.PRIMARY}
|
||||
size={sizes.LARGE}
|
||||
onPress={this.onAddNewRootFolderPress}
|
||||
>
|
||||
<Icon
|
||||
className={styles.importButtonIcon}
|
||||
name={icons.DRIVE}
|
||||
/>
|
||||
Choose another folder
|
||||
</Button>
|
||||
</div> :
|
||||
null
|
||||
|
||||
<div className={styles.startImport}>
|
||||
<Button
|
||||
kind={kinds.PRIMARY}
|
||||
size={sizes.LARGE}
|
||||
onPress={this.onAddNewRootFolderPress}
|
||||
>
|
||||
<Icon
|
||||
className={styles.importButtonIcon}
|
||||
name={icons.DRIVE}
|
||||
/>
|
||||
Start Import
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
!isSaving && saveError ?
|
||||
<Alert
|
||||
className={styles.addErrorAlert}
|
||||
kind={kinds.DANGER}
|
||||
>
|
||||
Unable to add root folder
|
||||
|
||||
<ul>
|
||||
{
|
||||
saveError.responseJSON.map((e, index) => {
|
||||
return (
|
||||
<li key={index}>
|
||||
{e.errorMessage}
|
||||
</li>
|
||||
);
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
</Alert> :
|
||||
null
|
||||
}
|
||||
|
||||
<div className={hasRootFolders ? undefined : styles.startImport}>
|
||||
<Button
|
||||
kind={kinds.PRIMARY}
|
||||
size={sizes.LARGE}
|
||||
onPress={this.onAddNewRootFolderPress}
|
||||
>
|
||||
<Icon
|
||||
className={styles.importButtonIcon}
|
||||
name={icons.DRIVE}
|
||||
/>
|
||||
{
|
||||
hasRootFolders ?
|
||||
'Choose another folder' :
|
||||
'Start Import'
|
||||
}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<FileBrowserModal
|
||||
isOpen={this.state.isAddNewRootFolderModalOpen}
|
||||
name="rootFolderPath"
|
||||
@@ -168,9 +142,7 @@ ImportSeriesSelectFolder.propTypes = {
|
||||
isWindows: PropTypes.bool.isRequired,
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
isPopulated: PropTypes.bool.isRequired,
|
||||
isSaving: PropTypes.bool.isRequired,
|
||||
error: PropTypes.object,
|
||||
saveError: PropTypes.object,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
onNewRootFolderSelect: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
@@ -8,7 +8,7 @@ import AppRoutes from './AppRoutes';
|
||||
|
||||
function App({ store, history }) {
|
||||
return (
|
||||
<DocumentTitle title={window.Sonarr.instanceName}>
|
||||
<DocumentTitle title="Sonarr">
|
||||
<Provider store={store}>
|
||||
<ConnectedRouter history={history}>
|
||||
<PageConnector>
|
||||
|
||||
@@ -13,13 +13,13 @@ import SeriesDetailsPageConnector from 'Series/Details/SeriesDetailsPageConnecto
|
||||
import CalendarPageConnector from 'Calendar/CalendarPageConnector';
|
||||
import HistoryConnector from 'Activity/History/HistoryConnector';
|
||||
import QueueConnector from 'Activity/Queue/QueueConnector';
|
||||
import BlocklistConnector from 'Activity/Blocklist/BlocklistConnector';
|
||||
import BlacklistConnector from 'Activity/Blacklist/BlacklistConnector';
|
||||
import MissingConnector from 'Wanted/Missing/MissingConnector';
|
||||
import CutoffUnmetConnector from 'Wanted/CutoffUnmet/CutoffUnmetConnector';
|
||||
import Settings from 'Settings/Settings';
|
||||
import MediaManagementConnector from 'Settings/MediaManagement/MediaManagementConnector';
|
||||
import Profiles from 'Settings/Profiles/Profiles';
|
||||
import QualityConnector from 'Settings/Quality/QualityConnector';
|
||||
import Quality from 'Settings/Quality/Quality';
|
||||
import IndexerSettingsConnector from 'Settings/Indexers/IndexerSettingsConnector';
|
||||
import ImportListSettingsConnector from 'Settings/ImportLists/ImportListSettingsConnector';
|
||||
import DownloadClientSettingsConnector from 'Settings/DownloadClients/DownloadClientSettingsConnector';
|
||||
@@ -118,8 +118,8 @@ function AppRoutes(props) {
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="/activity/blocklist"
|
||||
component={BlocklistConnector}
|
||||
path="/activity/blacklist"
|
||||
component={BlacklistConnector}
|
||||
/>
|
||||
|
||||
{/*
|
||||
@@ -158,7 +158,7 @@ function AppRoutes(props) {
|
||||
|
||||
<Route
|
||||
path="/settings/quality"
|
||||
component={QualityConnector}
|
||||
component={Quality}
|
||||
/>
|
||||
|
||||
<Route
|
||||
|
||||
@@ -27,7 +27,7 @@ function ConnectionLostModal(props) {
|
||||
|
||||
<ModalBody>
|
||||
<div>
|
||||
Sonarr has lost its connection to the backend and will need to be reloaded to restore functionality.
|
||||
Sonarr has lost it's connection to the backend and will need to be reloaded to restore functionality.
|
||||
</div>
|
||||
|
||||
<div className={styles.automatic}>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
}
|
||||
|
||||
.time {
|
||||
flex: 0 0 125px;
|
||||
flex: 0 0 120px;
|
||||
margin-right: 10px;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
@@ -50,7 +50,6 @@ class AgendaEvent extends Component {
|
||||
absoluteEpisodeNumber,
|
||||
airDateUtc,
|
||||
monitored,
|
||||
unverifiedSceneNumbering,
|
||||
hasFile,
|
||||
grabbed,
|
||||
queueItem,
|
||||
@@ -71,7 +70,7 @@ class AgendaEvent extends Component {
|
||||
const statusStyle = getStatusStyle(hasFile, downloading, startTime, endTime, isMonitored);
|
||||
const missingAbsoluteNumber = series.seriesType === 'anime' && seasonNumber > 0 && !absoluteEpisodeNumber;
|
||||
const season = series.seasons.find((s) => s.seasonNumber === seasonNumber);
|
||||
const seasonStatistics = season?.statistics || {};
|
||||
const seasonStatistics = season.statistics || {};
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -132,16 +131,6 @@ class AgendaEvent extends Component {
|
||||
/>
|
||||
}
|
||||
|
||||
{
|
||||
unverifiedSceneNumbering && !missingAbsoluteNumber ?
|
||||
<Icon
|
||||
className={styles.statusIcon}
|
||||
name={icons.WARNING}
|
||||
title="Scene number hasn't been verified yet"
|
||||
/> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
!!queueItem &&
|
||||
<span className={styles.statusIcon}>
|
||||
@@ -248,7 +237,6 @@ AgendaEvent.propTypes = {
|
||||
absoluteEpisodeNumber: PropTypes.number,
|
||||
airDateUtc: PropTypes.string.isRequired,
|
||||
monitored: PropTypes.bool.isRequired,
|
||||
unverifiedSceneNumbering: PropTypes.bool,
|
||||
hasFile: PropTypes.bool.isRequired,
|
||||
grabbed: PropTypes.bool,
|
||||
queueItem: PropTypes.object,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
$fullColorGradient: rgba(244, 245, 246, 0.2);
|
||||
|
||||
.event {
|
||||
overflow-x: hidden;
|
||||
margin: 4px 2px;
|
||||
@@ -38,11 +36,6 @@ $fullColorGradient: rgba(244, 245, 246, 0.2);
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
.statusContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.statusIcon {
|
||||
margin-left: 3px;
|
||||
}
|
||||
@@ -58,10 +51,6 @@ $fullColorGradient: rgba(244, 245, 246, 0.2);
|
||||
.downloaded {
|
||||
border-left-color: $successColor !important;
|
||||
|
||||
&:global(.fullColor) {
|
||||
background-color: rgba(39, 194, 76, 0.4) !important;
|
||||
}
|
||||
|
||||
&:global(.colorImpaired) {
|
||||
border-left-color: color($successColor, saturation(+15%)) !important;
|
||||
}
|
||||
@@ -69,73 +58,37 @@ $fullColorGradient: rgba(244, 245, 246, 0.2);
|
||||
|
||||
.downloading {
|
||||
border-left-color: $purple !important;
|
||||
|
||||
&:global(.fullColor) {
|
||||
background-color: rgba(122, 67, 182, 0.4) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.unmonitored {
|
||||
border-left-color: $gray !important;
|
||||
|
||||
&:global(.fullColor) {
|
||||
background-color: rgba(173, 173, 173, 0.5) !important;
|
||||
}
|
||||
|
||||
&:global(.colorImpaired) {
|
||||
background: repeating-linear-gradient(45deg, $colorImpairedGradientDark, $colorImpairedGradientDark 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
|
||||
}
|
||||
|
||||
&:global(.fullColor.colorImpaired) {
|
||||
background: repeating-linear-gradient(45deg, $fullColorGradient, $fullColorGradient 5px, transparent 5px, transparent 10px);
|
||||
}
|
||||
}
|
||||
|
||||
.onAir {
|
||||
border-left-color: $warningColor !important;
|
||||
|
||||
&:global(.fullColor) {
|
||||
background-color: rgba(255, 165, 0, 0.6) !important;
|
||||
}
|
||||
|
||||
&:global(.colorImpaired) {
|
||||
background: repeating-linear-gradient(90deg, $colorImpairedGradientDark, $colorImpairedGradientDark 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
|
||||
}
|
||||
|
||||
&:global(.fullColor.colorImpaired) {
|
||||
background: repeating-linear-gradient(90deg, $fullColorGradient, $fullColorGradient 5px, transparent 5px, transparent 10px);
|
||||
}
|
||||
}
|
||||
|
||||
.missing {
|
||||
border-left-color: $dangerColor !important;
|
||||
|
||||
&:global(.fullColor) {
|
||||
background-color: rgba(240, 80, 80, 0.6) !important;
|
||||
}
|
||||
|
||||
&:global(.colorImpaired) {
|
||||
border-left-color: color($dangerColor saturation(+15%)) !important;
|
||||
background: repeating-linear-gradient(90deg, $colorImpairedGradientDark, $colorImpairedGradientDark 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
|
||||
}
|
||||
|
||||
&:global(.fullColor.colorImpaired) {
|
||||
background: repeating-linear-gradient(90deg, $fullColorGradient, $fullColorGradient 5px, transparent 5px, transparent 10px);
|
||||
}
|
||||
}
|
||||
|
||||
.unaired {
|
||||
border-left-color: $primaryColor !important;
|
||||
|
||||
&:global(.fullColor) {
|
||||
background-color: rgba(93, 156, 236, 0.4) !important;
|
||||
}
|
||||
|
||||
&:global(.colorImpaired) {
|
||||
background: repeating-linear-gradient(90deg, $colorImpairedGradientDark, $colorImpairedGradientDark 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
|
||||
}
|
||||
|
||||
&:global(.fullColor.colorImpaired) {
|
||||
background: repeating-linear-gradient(90deg, $fullColorGradient, $fullColorGradient 5px, transparent 5px, transparent 10px);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import moment from 'moment';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import React, { Component } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { icons, kinds } from 'Helpers/Props';
|
||||
import formatTime from 'Utilities/Date/formatTime';
|
||||
@@ -55,7 +55,6 @@ class CalendarEvent extends Component {
|
||||
absoluteEpisodeNumber,
|
||||
airDateUtc,
|
||||
monitored,
|
||||
unverifiedSceneNumbering,
|
||||
hasFile,
|
||||
grabbed,
|
||||
queueItem,
|
||||
@@ -63,7 +62,6 @@ class CalendarEvent extends Component {
|
||||
showFinaleIcon,
|
||||
showSpecialIcon,
|
||||
showCutoffUnmetIcon,
|
||||
fullColorEvents,
|
||||
timeFormat,
|
||||
colorImpairedMode
|
||||
} = this.props;
|
||||
@@ -79,16 +77,15 @@ class CalendarEvent extends Component {
|
||||
const statusStyle = getStatusStyle(hasFile, isDownloading, startTime, endTime, isMonitored);
|
||||
const missingAbsoluteNumber = series.seriesType === 'anime' && seasonNumber > 0 && !absoluteEpisodeNumber;
|
||||
const season = series.seasons.find((s) => s.seasonNumber === seasonNumber);
|
||||
const seasonStatistics = season?.statistics || {};
|
||||
const seasonStatistics = season.statistics || {};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div>
|
||||
<Link
|
||||
className={classNames(
|
||||
styles.event,
|
||||
styles[statusStyle],
|
||||
colorImpairedMode && 'colorImpaired',
|
||||
fullColorEvents && 'fullColor'
|
||||
colorImpairedMode && 'colorImpaired'
|
||||
)}
|
||||
component="div"
|
||||
onPress={this.onPress}
|
||||
@@ -98,117 +95,95 @@ class CalendarEvent extends Component {
|
||||
{series.title}
|
||||
</div>
|
||||
|
||||
<div className={styles.statusContainer}>
|
||||
{
|
||||
missingAbsoluteNumber ?
|
||||
<Icon
|
||||
className={styles.statusIcon}
|
||||
name={icons.WARNING}
|
||||
title="Episode does not have an absolute episode number"
|
||||
/> :
|
||||
null
|
||||
}
|
||||
{
|
||||
missingAbsoluteNumber &&
|
||||
<Icon
|
||||
className={styles.statusIcon}
|
||||
name={icons.WARNING}
|
||||
title="Episode does not have an absolute episode number"
|
||||
/>
|
||||
}
|
||||
|
||||
{
|
||||
unverifiedSceneNumbering && !missingAbsoluteNumber ?
|
||||
<Icon
|
||||
className={styles.statusIcon}
|
||||
name={icons.WARNING}
|
||||
title="Scene number hasn't been verified yet"
|
||||
/> :
|
||||
null
|
||||
}
|
||||
{
|
||||
!!queueItem &&
|
||||
<span className={styles.statusIcon}>
|
||||
<CalendarEventQueueDetails
|
||||
{...queueItem}
|
||||
/>
|
||||
</span>
|
||||
}
|
||||
|
||||
{
|
||||
queueItem ?
|
||||
<span className={styles.statusIcon}>
|
||||
<CalendarEventQueueDetails
|
||||
{...queueItem}
|
||||
/>
|
||||
</span> :
|
||||
null
|
||||
}
|
||||
{
|
||||
!queueItem && grabbed &&
|
||||
<Icon
|
||||
className={styles.statusIcon}
|
||||
name={icons.DOWNLOADING}
|
||||
title="Episode is downloading"
|
||||
/>
|
||||
}
|
||||
|
||||
{
|
||||
!queueItem && grabbed ?
|
||||
<Icon
|
||||
className={styles.statusIcon}
|
||||
name={icons.DOWNLOADING}
|
||||
title="Episode is downloading"
|
||||
/> :
|
||||
null
|
||||
}
|
||||
{
|
||||
showCutoffUnmetIcon &&
|
||||
!!episodeFile &&
|
||||
episodeFile.qualityCutoffNotMet &&
|
||||
<Icon
|
||||
className={styles.statusIcon}
|
||||
name={icons.EPISODE_FILE}
|
||||
kind={kinds.WARNING}
|
||||
title="Quality cutoff has not been met"
|
||||
/>
|
||||
}
|
||||
|
||||
{
|
||||
showCutoffUnmetIcon &&
|
||||
!!episodeFile &&
|
||||
episodeFile.qualityCutoffNotMet ?
|
||||
<Icon
|
||||
className={styles.statusIcon}
|
||||
name={icons.EPISODE_FILE}
|
||||
kind={fullColorEvents ? kinds.DEFAULT : kinds.WARNING}
|
||||
title="Quality cutoff has not been met"
|
||||
/> :
|
||||
null
|
||||
}
|
||||
{
|
||||
showCutoffUnmetIcon &&
|
||||
!!episodeFile &&
|
||||
episodeFile.languageCutoffNotMet &&
|
||||
!episodeFile.qualityCutoffNotMet &&
|
||||
<Icon
|
||||
className={styles.statusIcon}
|
||||
name={icons.EPISODE_FILE}
|
||||
kind={kinds.WARNING}
|
||||
title="Language cutoff has not been met"
|
||||
/>
|
||||
}
|
||||
|
||||
{
|
||||
showCutoffUnmetIcon &&
|
||||
!!episodeFile &&
|
||||
episodeFile.languageCutoffNotMet &&
|
||||
!episodeFile.qualityCutoffNotMet ?
|
||||
<Icon
|
||||
className={styles.statusIcon}
|
||||
name={icons.EPISODE_FILE}
|
||||
kind={fullColorEvents ? kinds.DEFAULT : kinds.WARNING}
|
||||
title="Language cutoff has not been met"
|
||||
/> :
|
||||
null
|
||||
}
|
||||
{
|
||||
episodeNumber === 1 && seasonNumber > 0 &&
|
||||
<Icon
|
||||
className={styles.statusIcon}
|
||||
name={icons.INFO}
|
||||
kind={kinds.INFO}
|
||||
title={seasonNumber === 1 ? 'Series premiere' : 'Season premiere'}
|
||||
/>
|
||||
}
|
||||
|
||||
{
|
||||
episodeNumber === 1 && seasonNumber > 0 ?
|
||||
<Icon
|
||||
className={styles.statusIcon}
|
||||
name={icons.INFO}
|
||||
kind={kinds.INFO}
|
||||
darken={fullColorEvents}
|
||||
title={seasonNumber === 1 ? 'Series premiere' : 'Season premiere'}
|
||||
/> :
|
||||
null
|
||||
}
|
||||
{
|
||||
showFinaleIcon &&
|
||||
episodeNumber !== 1 &&
|
||||
seasonNumber > 0 &&
|
||||
episodeNumber === seasonStatistics.totalEpisodeCount &&
|
||||
<Icon
|
||||
className={styles.statusIcon}
|
||||
name={icons.INFO}
|
||||
kind={kinds.WARNING}
|
||||
title={series.status === 'ended' ? 'Series finale' : 'Season finale'}
|
||||
/>
|
||||
}
|
||||
|
||||
{
|
||||
showFinaleIcon &&
|
||||
episodeNumber !== 1 &&
|
||||
seasonNumber > 0 &&
|
||||
episodeNumber === seasonStatistics.totalEpisodeCount ?
|
||||
<Icon
|
||||
className={styles.statusIcon}
|
||||
name={icons.INFO}
|
||||
kind={fullColorEvents ? kinds.DEFAULT : kinds.WARNING}
|
||||
title={series.status === 'ended' ? 'Series finale' : 'Season finale'}
|
||||
/> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
showSpecialIcon &&
|
||||
(episodeNumber === 0 || seasonNumber === 0) ?
|
||||
<Icon
|
||||
className={styles.statusIcon}
|
||||
name={icons.INFO}
|
||||
kind={kinds.PINK}
|
||||
darken={fullColorEvents}
|
||||
title="Special"
|
||||
/> :
|
||||
null
|
||||
}
|
||||
</div>
|
||||
{
|
||||
showSpecialIcon &&
|
||||
(episodeNumber === 0 || seasonNumber === 0) &&
|
||||
<Icon
|
||||
className={styles.statusIcon}
|
||||
name={icons.INFO}
|
||||
kind={kinds.PINK}
|
||||
title="Special"
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
|
||||
{
|
||||
showEpisodeInformation ?
|
||||
showEpisodeInformation &&
|
||||
<div className={styles.episodeInfo}>
|
||||
<div className={styles.episodeTitle}>
|
||||
{title}
|
||||
@@ -218,12 +193,11 @@ class CalendarEvent extends Component {
|
||||
{seasonNumber}x{padNumber(episodeNumber, 2)}
|
||||
|
||||
{
|
||||
series.seriesType === 'anime' && absoluteEpisodeNumber ?
|
||||
<span className={styles.absoluteEpisodeNumber}>({absoluteEpisodeNumber})</span> : null
|
||||
series.seriesType === 'anime' && absoluteEpisodeNumber &&
|
||||
<span className={styles.absoluteEpisodeNumber}>({absoluteEpisodeNumber})</span>
|
||||
}
|
||||
</div>
|
||||
</div> :
|
||||
null
|
||||
</div>
|
||||
}
|
||||
|
||||
<div className={styles.airTime}>
|
||||
@@ -240,7 +214,7 @@ class CalendarEvent extends Component {
|
||||
showOpenSeriesButton={true}
|
||||
onModalClose={this.onDetailsModalClose}
|
||||
/>
|
||||
</Fragment>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -255,7 +229,6 @@ CalendarEvent.propTypes = {
|
||||
absoluteEpisodeNumber: PropTypes.number,
|
||||
airDateUtc: PropTypes.string.isRequired,
|
||||
monitored: PropTypes.bool.isRequired,
|
||||
unverifiedSceneNumbering: PropTypes.bool,
|
||||
hasFile: PropTypes.bool.isRequired,
|
||||
grabbed: PropTypes.bool,
|
||||
queueItem: PropTypes.object,
|
||||
@@ -263,7 +236,6 @@ CalendarEvent.propTypes = {
|
||||
showFinaleIcon: PropTypes.bool.isRequired,
|
||||
showSpecialIcon: PropTypes.bool.isRequired,
|
||||
showCutoffUnmetIcon: PropTypes.bool.isRequired,
|
||||
fullColorEvents: PropTypes.bool.isRequired,
|
||||
timeFormat: PropTypes.string.isRequired,
|
||||
colorImpairedMode: PropTypes.bool.isRequired,
|
||||
onEventModalOpenToggle: PropTypes.func.isRequired
|
||||
|
||||
@@ -74,7 +74,6 @@ class CalendarEventGroup extends Component {
|
||||
showEpisodeInformation,
|
||||
showFinaleIcon,
|
||||
timeFormat,
|
||||
fullColorEvents,
|
||||
colorImpairedMode,
|
||||
onEventModalOpenToggle
|
||||
} = this.props;
|
||||
@@ -134,8 +133,7 @@ class CalendarEventGroup extends Component {
|
||||
className={classNames(
|
||||
styles.eventGroup,
|
||||
styles[statusStyle],
|
||||
colorImpairedMode && 'colorImpaired',
|
||||
fullColorEvents && 'fullColor'
|
||||
colorImpairedMode && 'colorImpaired'
|
||||
)}
|
||||
>
|
||||
<div className={styles.info}>
|
||||
@@ -146,7 +144,7 @@ class CalendarEventGroup extends Component {
|
||||
{
|
||||
isMissingAbsoluteNumber &&
|
||||
<Icon
|
||||
containerClassName={styles.statusIcon}
|
||||
className={styles.statusIcon}
|
||||
name={icons.WARNING}
|
||||
title="Episode does not have an absolute episode number"
|
||||
/>
|
||||
@@ -155,7 +153,7 @@ class CalendarEventGroup extends Component {
|
||||
{
|
||||
anyDownloading &&
|
||||
<Icon
|
||||
containerClassName={styles.statusIcon}
|
||||
className={styles.statusIcon}
|
||||
name={icons.DOWNLOADING}
|
||||
title="An episode is downloading"
|
||||
/>
|
||||
@@ -164,10 +162,9 @@ class CalendarEventGroup extends Component {
|
||||
{
|
||||
firstEpisode.episodeNumber === 1 && seasonNumber > 0 &&
|
||||
<Icon
|
||||
containerClassName={styles.statusIcon}
|
||||
className={styles.statusIcon}
|
||||
name={icons.INFO}
|
||||
kind={kinds.INFO}
|
||||
darken={fullColorEvents}
|
||||
title={seasonNumber === 1 ? 'Series Premiere' : 'Season Premiere'}
|
||||
/>
|
||||
}
|
||||
@@ -178,9 +175,9 @@ class CalendarEventGroup extends Component {
|
||||
seasonNumber > 0 &&
|
||||
lastEpisode.episodeNumber === series.seasons.find((season) => season.seasonNumber === seasonNumber).statistics.totalEpisodeCount &&
|
||||
<Icon
|
||||
containerClassName={styles.statusIcon}
|
||||
className={styles.statusIcon}
|
||||
name={icons.INFO}
|
||||
kind={fullColorEvents ? kinds.DEFAULT : kinds.WARNING}
|
||||
kind={kinds.WARNING}
|
||||
title={series.status === 'ended' ? 'Series finale' : 'Season finale'}
|
||||
/>
|
||||
}
|
||||
@@ -240,7 +237,6 @@ CalendarEventGroup.propTypes = {
|
||||
isDownloading: PropTypes.bool.isRequired,
|
||||
showEpisodeInformation: PropTypes.bool.isRequired,
|
||||
showFinaleIcon: PropTypes.bool.isRequired,
|
||||
fullColorEvents: PropTypes.bool.isRequired,
|
||||
timeFormat: PropTypes.string.isRequired,
|
||||
colorImpairedMode: PropTypes.bool.isRequired,
|
||||
onEventModalOpenToggle: PropTypes.func.isRequired
|
||||
|
||||
@@ -7,23 +7,19 @@ import styles from './Legend.css';
|
||||
|
||||
function Legend(props) {
|
||||
const {
|
||||
view,
|
||||
showFinaleIcon,
|
||||
showSpecialIcon,
|
||||
showCutoffUnmetIcon,
|
||||
fullColorEvents,
|
||||
colorImpairedMode
|
||||
} = props;
|
||||
|
||||
const iconsToShow = [];
|
||||
const isAgendaView = view === 'agenda';
|
||||
|
||||
if (showFinaleIcon) {
|
||||
iconsToShow.push(
|
||||
<LegendIconItem
|
||||
name="Finale"
|
||||
icon={icons.INFO}
|
||||
kind={fullColorEvents ? kinds.DEFAULT : kinds.WARNING}
|
||||
kind={kinds.WARNING}
|
||||
tooltip="Series or season finale"
|
||||
/>
|
||||
);
|
||||
@@ -35,7 +31,6 @@ function Legend(props) {
|
||||
name="Special"
|
||||
icon={icons.INFO}
|
||||
kind={kinds.PINK}
|
||||
darken={fullColorEvents}
|
||||
tooltip="Special episode"
|
||||
/>
|
||||
);
|
||||
@@ -46,7 +41,7 @@ function Legend(props) {
|
||||
<LegendIconItem
|
||||
name="Cutoff Not Met"
|
||||
icon={icons.EPISODE_FILE}
|
||||
kind={fullColorEvents ? kinds.DEFAULT : kinds.WARNING}
|
||||
kind={kinds.WARNING}
|
||||
tooltip="Quality or language cutoff has not been met"
|
||||
/>
|
||||
);
|
||||
@@ -58,16 +53,12 @@ function Legend(props) {
|
||||
<LegendItem
|
||||
status="unaired"
|
||||
tooltip="Episode hasn't aired yet"
|
||||
isAgendaView={isAgendaView}
|
||||
fullColorEvents={fullColorEvents}
|
||||
colorImpairedMode={colorImpairedMode}
|
||||
/>
|
||||
|
||||
<LegendItem
|
||||
status="unmonitored"
|
||||
tooltip="Episode is unmonitored"
|
||||
isAgendaView={isAgendaView}
|
||||
fullColorEvents={fullColorEvents}
|
||||
colorImpairedMode={colorImpairedMode}
|
||||
/>
|
||||
</div>
|
||||
@@ -77,16 +68,12 @@ function Legend(props) {
|
||||
status="onAir"
|
||||
name="On Air"
|
||||
tooltip="Episode is currently airing"
|
||||
isAgendaView={isAgendaView}
|
||||
fullColorEvents={fullColorEvents}
|
||||
colorImpairedMode={colorImpairedMode}
|
||||
/>
|
||||
|
||||
<LegendItem
|
||||
status="missing"
|
||||
tooltip="Episode has aired and is missing from disk"
|
||||
isAgendaView={isAgendaView}
|
||||
fullColorEvents={fullColorEvents}
|
||||
colorImpairedMode={colorImpairedMode}
|
||||
/>
|
||||
</div>
|
||||
@@ -95,16 +82,12 @@ function Legend(props) {
|
||||
<LegendItem
|
||||
status="downloading"
|
||||
tooltip="Episode is currently downloading"
|
||||
isAgendaView={isAgendaView}
|
||||
fullColorEvents={fullColorEvents}
|
||||
colorImpairedMode={colorImpairedMode}
|
||||
/>
|
||||
|
||||
<LegendItem
|
||||
status="downloaded"
|
||||
tooltip="Episode was downloaded and sorted"
|
||||
isAgendaView={isAgendaView}
|
||||
fullColorEvents={fullColorEvents}
|
||||
colorImpairedMode={colorImpairedMode}
|
||||
/>
|
||||
</div>
|
||||
@@ -114,7 +97,6 @@ function Legend(props) {
|
||||
name="Premiere"
|
||||
icon={icons.INFO}
|
||||
kind={kinds.INFO}
|
||||
darken={true}
|
||||
tooltip="Series or season premiere"
|
||||
/>
|
||||
|
||||
@@ -133,11 +115,9 @@ function Legend(props) {
|
||||
}
|
||||
|
||||
Legend.propTypes = {
|
||||
view: PropTypes.string.isRequired,
|
||||
showFinaleIcon: PropTypes.bool.isRequired,
|
||||
showSpecialIcon: PropTypes.bool.isRequired,
|
||||
showCutoffUnmetIcon: PropTypes.bool.isRequired,
|
||||
fullColorEvents: PropTypes.bool.isRequired,
|
||||
colorImpairedMode: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
|
||||
@@ -6,12 +6,10 @@ import Legend from './Legend';
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.calendar.options,
|
||||
(state) => state.calendar.view,
|
||||
createUISettingsSelector(),
|
||||
(calendarOptions, view, uiSettings) => {
|
||||
(calendarOptions, uiSettings) => {
|
||||
return {
|
||||
...calendarOptions,
|
||||
view,
|
||||
colorImpairedMode: uiSettings.enableColorImpairedMode
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ function LegendIconItem(props) {
|
||||
name,
|
||||
icon,
|
||||
kind,
|
||||
darken,
|
||||
tooltip
|
||||
} = props;
|
||||
|
||||
@@ -20,7 +19,6 @@ function LegendIconItem(props) {
|
||||
<Icon
|
||||
className={styles.icon}
|
||||
name={icon}
|
||||
darken={darken}
|
||||
kind={kind}
|
||||
/>
|
||||
|
||||
@@ -33,12 +31,7 @@ LegendIconItem.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
icon: PropTypes.object.isRequired,
|
||||
kind: PropTypes.string.isRequired,
|
||||
darken: PropTypes.bool.isRequired,
|
||||
tooltip: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
LegendIconItem.defaultProps = {
|
||||
darken: false
|
||||
};
|
||||
|
||||
export default LegendIconItem;
|
||||
|
||||
@@ -9,8 +9,6 @@ function LegendItem(props) {
|
||||
name,
|
||||
status,
|
||||
tooltip,
|
||||
isAgendaView,
|
||||
fullColorEvents,
|
||||
colorImpairedMode
|
||||
} = props;
|
||||
|
||||
@@ -19,8 +17,7 @@ function LegendItem(props) {
|
||||
className={classNames(
|
||||
styles.legendItem,
|
||||
styles[status],
|
||||
colorImpairedMode && 'colorImpaired',
|
||||
fullColorEvents && !isAgendaView && 'fullColor'
|
||||
colorImpairedMode && 'colorImpaired'
|
||||
)}
|
||||
title={tooltip}
|
||||
>
|
||||
@@ -33,8 +30,6 @@ LegendItem.propTypes = {
|
||||
name: PropTypes.string,
|
||||
status: PropTypes.string.isRequired,
|
||||
tooltip: PropTypes.string.isRequired,
|
||||
isAgendaView: PropTypes.bool.isRequired,
|
||||
fullColorEvents: PropTypes.bool.isRequired,
|
||||
colorImpairedMode: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
|
||||
@@ -25,16 +25,14 @@ class CalendarOptionsModalContent extends Component {
|
||||
firstDayOfWeek,
|
||||
calendarWeekColumnHeader,
|
||||
timeFormat,
|
||||
enableColorImpairedMode,
|
||||
fullColorEvents
|
||||
enableColorImpairedMode
|
||||
} = props;
|
||||
|
||||
this.state = {
|
||||
firstDayOfWeek,
|
||||
calendarWeekColumnHeader,
|
||||
timeFormat,
|
||||
enableColorImpairedMode,
|
||||
fullColorEvents
|
||||
enableColorImpairedMode
|
||||
};
|
||||
}
|
||||
|
||||
@@ -98,7 +96,6 @@ class CalendarOptionsModalContent extends Component {
|
||||
showFinaleIcon,
|
||||
showSpecialIcon,
|
||||
showCutoffUnmetIcon,
|
||||
fullColorEvents,
|
||||
onModalClose
|
||||
} = this.props;
|
||||
|
||||
@@ -177,18 +174,6 @@ class CalendarOptionsModalContent extends Component {
|
||||
onChange={this.onOptionInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>Full Color Events</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="fullColorEvents"
|
||||
value={fullColorEvents}
|
||||
helpText="Altered style to color the entire event with the status color, instead of just the left edge. Does not apply to Agenda"
|
||||
onChange={this.onOptionInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
</FieldSet>
|
||||
|
||||
@@ -229,9 +214,7 @@ class CalendarOptionsModalContent extends Component {
|
||||
value={timeFormat}
|
||||
onChange={this.onGlobalInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
</FormGroup><FormGroup>
|
||||
<FormLabel>Enable Color-Impaired Mode</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
@@ -242,6 +225,7 @@ class CalendarOptionsModalContent extends Component {
|
||||
onChange={this.onGlobalInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
</Form>
|
||||
</FieldSet>
|
||||
</ModalBody>
|
||||
@@ -266,7 +250,6 @@ CalendarOptionsModalContent.propTypes = {
|
||||
calendarWeekColumnHeader: PropTypes.string.isRequired,
|
||||
timeFormat: PropTypes.string.isRequired,
|
||||
enableColorImpairedMode: PropTypes.bool.isRequired,
|
||||
fullColorEvents: PropTypes.bool.isRequired,
|
||||
dispatchSetCalendarOption: PropTypes.func.isRequired,
|
||||
dispatchSaveUISettings: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export const APPLICATION_UPDATE = 'ApplicationUpdate';
|
||||
export const BACKUP = 'Backup';
|
||||
export const REFRESH_MONITORED_DOWNLOADS = 'RefreshMonitoredDownloads';
|
||||
export const CLEAR_BLOCKLIST = 'ClearBlocklist';
|
||||
export const CLEAR_BLACKLIST = 'ClearBlacklist';
|
||||
export const CLEAR_LOGS = 'ClearLog';
|
||||
export const CUTOFF_UNMET_EPISODE_SEARCH = 'CutoffUnmetEpisodeSearch';
|
||||
export const DELETE_LOG_FILES = 'DeleteLogFiles';
|
||||
@@ -15,7 +15,6 @@ export const REFRESH_SERIES = 'RefreshSeries';
|
||||
export const RENAME_FILES = 'RenameFiles';
|
||||
export const RENAME_SERIES = 'RenameSeries';
|
||||
export const RESET_API_KEY = 'ResetApiKey';
|
||||
export const RESET_QUALITY_DEFINITIONS = 'ResetQualityDefinitions';
|
||||
export const RSS_SYNC = 'RssSync';
|
||||
export const SEASON_SEARCH = 'SeasonSearch';
|
||||
export const SERIES_SEARCH = 'SeriesSearch';
|
||||
|
||||
@@ -16,9 +16,4 @@
|
||||
color: #3a3f51;
|
||||
font-size: 21px;
|
||||
line-height: inherit;
|
||||
|
||||
&.small {
|
||||
color: #909293;
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { sizes } from 'Helpers/Props';
|
||||
import styles from './FieldSet.css';
|
||||
|
||||
class FieldSet extends Component {
|
||||
@@ -11,14 +9,13 @@ class FieldSet extends Component {
|
||||
|
||||
render() {
|
||||
const {
|
||||
size,
|
||||
legend,
|
||||
children
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<fieldset className={styles.fieldSet}>
|
||||
<legend className={classNames(styles.legend, (size === sizes.SMALL) && styles.small)}>
|
||||
<legend className={styles.legend}>
|
||||
{legend}
|
||||
</legend>
|
||||
{children}
|
||||
@@ -29,13 +26,8 @@ class FieldSet extends Component {
|
||||
}
|
||||
|
||||
FieldSet.propTypes = {
|
||||
size: PropTypes.oneOf(sizes.all).isRequired,
|
||||
legend: PropTypes.oneOfType([PropTypes.node, PropTypes.string]),
|
||||
children: PropTypes.node
|
||||
};
|
||||
|
||||
FieldSet.defaultProps = {
|
||||
size: sizes.MEDIUM
|
||||
};
|
||||
|
||||
export default FieldSet;
|
||||
|
||||
@@ -128,7 +128,7 @@ class FileBrowserModalContent extends Component {
|
||||
className={styles.mappedDrivesWarning}
|
||||
kind={kinds.WARNING}
|
||||
>
|
||||
Mapped network drives are not available when running as a Windows Service, see the <Link className={styles.faqLink} to="https://wiki.servarr.com/sonarr/faq#why-cant-sonarr-see-my-files-on-a-remote-server">FAQ</Link> for more information.
|
||||
Mapped network drives are not available when running as a Windows Service, see the <Link className={styles.faqLink} to="https://github.com/Sonarr/Sonarr/wiki/FAQ">FAQ</Link> for more information.
|
||||
</Alert>
|
||||
}
|
||||
|
||||
|
||||
@@ -160,7 +160,6 @@ class DateFilterBuilderRowValue extends Component {
|
||||
<TextInput
|
||||
name={NAME}
|
||||
value={filterValue}
|
||||
type="date"
|
||||
placeholder="yyyy-mm-dd"
|
||||
onChange={this.onValueChange}
|
||||
/>
|
||||
|
||||
@@ -12,7 +12,6 @@ import ProtocolFilterBuilderRowValue from './ProtocolFilterBuilderRowValue';
|
||||
import QualityFilterBuilderRowValueConnector from './QualityFilterBuilderRowValueConnector';
|
||||
import QualityProfileFilterBuilderRowValueConnector from './QualityProfileFilterBuilderRowValueConnector';
|
||||
import SeriesStatusFilterBuilderRowValue from './SeriesStatusFilterBuilderRowValue';
|
||||
import SeriesTypeFilterBuilderRowValue from './SeriesTypeFilterBuilderRowValue';
|
||||
import TagFilterBuilderRowValueConnector from './TagFilterBuilderRowValueConnector';
|
||||
import styles from './FilterBuilderRow.css';
|
||||
|
||||
@@ -76,9 +75,6 @@ function getRowValueConnector(selectedFilterBuilderProp) {
|
||||
case filterBuilderValueTypes.SERIES_STATUS:
|
||||
return SeriesStatusFilterBuilderRowValue;
|
||||
|
||||
case filterBuilderValueTypes.SERIES_TYPES:
|
||||
return SeriesTypeFilterBuilderRowValue;
|
||||
|
||||
case filterBuilderValueTypes.TAG:
|
||||
return TagFilterBuilderRowValueConnector;
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ function createTagListSelector() {
|
||||
(selectedFilterBuilderProp.type === filterBuilderTypes.NUMBER ||
|
||||
selectedFilterBuilderProp.type === filterBuilderTypes.STRING) &&
|
||||
filterType !== filterTypes.EQUAL &&
|
||||
filterType !== filterTypes.NOT_EQUAL ||
|
||||
filterType !== filterBuilderTypes.NOT_EQUAL ||
|
||||
!selectedFilterBuilderProp.optionsSelector
|
||||
) {
|
||||
return [];
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.tag {
|
||||
display: flex;
|
||||
height: 21px;
|
||||
|
||||
&.isLastTag {
|
||||
.or {
|
||||
@@ -18,5 +18,4 @@
|
||||
.or {
|
||||
margin: 0 3px;
|
||||
color: $themeDarkColor;
|
||||
line-height: 31px;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import styles from './FilterBuilderRowValueTag.css';
|
||||
|
||||
function FilterBuilderRowValueTag(props) {
|
||||
return (
|
||||
<div
|
||||
<span
|
||||
className={styles.tag}
|
||||
>
|
||||
<TagInputTag
|
||||
@@ -15,13 +15,12 @@ function FilterBuilderRowValueTag(props) {
|
||||
/>
|
||||
|
||||
{
|
||||
props.isLastTag ?
|
||||
null :
|
||||
<div className={styles.or}>
|
||||
!props.isLastTag &&
|
||||
<span className={styles.or}>
|
||||
or
|
||||
</div>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import React from 'react';
|
||||
import FilterBuilderRowValue from './FilterBuilderRowValue';
|
||||
|
||||
const seriesTypeList = [
|
||||
{ id: 'anime', name: 'Anime' },
|
||||
{ id: 'daily', name: 'Daily' },
|
||||
{ id: 'standard', name: 'Standard' }
|
||||
];
|
||||
|
||||
function SeriesTypeFilterBuilderRowValue(props) {
|
||||
return (
|
||||
<FilterBuilderRowValue
|
||||
tagList={seriesTypeList}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default SeriesTypeFilterBuilderRowValue;
|
||||
@@ -1,100 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { fetchDownloadClients } from 'Store/Actions/settingsActions';
|
||||
import sortByName from 'Utilities/Array/sortByName';
|
||||
import EnhancedSelectInput from './EnhancedSelectInput';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.settings.downloadClients,
|
||||
(state, { includeAny }) => includeAny,
|
||||
(state, { protocol }) => protocol,
|
||||
(downloadClients, includeAny, protocolFilter) => {
|
||||
const {
|
||||
isFetching,
|
||||
isPopulated,
|
||||
error,
|
||||
items
|
||||
} = downloadClients;
|
||||
|
||||
const filteredItems = items.filter((item) => item.protocol === protocolFilter);
|
||||
|
||||
const values = _.map(filteredItems.sort(sortByName), (downloadClient) => {
|
||||
return {
|
||||
key: downloadClient.id,
|
||||
value: downloadClient.name
|
||||
};
|
||||
});
|
||||
|
||||
if (includeAny) {
|
||||
values.unshift({
|
||||
key: 0,
|
||||
value: '(Any)'
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
isFetching,
|
||||
isPopulated,
|
||||
error,
|
||||
values
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
dispatchFetchDownloadClients: fetchDownloadClients
|
||||
};
|
||||
|
||||
class DownloadClientSelectInputConnector extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
componentDidMount() {
|
||||
if (!this.props.isPopulated) {
|
||||
this.props.dispatchFetchDownloadClients();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onChange = ({ name, value }) => {
|
||||
this.props.onChange({ name, value: parseInt(value) });
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
return (
|
||||
<EnhancedSelectInput
|
||||
{...this.props}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DownloadClientSelectInputConnector.propTypes = {
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
isPopulated: PropTypes.bool.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
|
||||
values: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
includeAny: PropTypes.bool.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
dispatchFetchDownloadClients: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
DownloadClientSelectInputConnector.defaultProps = {
|
||||
includeAny: false,
|
||||
protocol: 'torrent'
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(DownloadClientSelectInputConnector);
|
||||
@@ -5,10 +5,6 @@
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.editableContainer {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.hasError {
|
||||
composes: hasError from '~Components/Form/Input.css';
|
||||
}
|
||||
@@ -26,16 +22,6 @@
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.dropdownArrowContainerEditable {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
padding-right: 17px;
|
||||
width: 30%;
|
||||
height: 35px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.dropdownArrowContainerDisabled {
|
||||
composes: dropdownArrowContainer;
|
||||
|
||||
@@ -85,21 +71,3 @@
|
||||
display: inline-block;
|
||||
margin: 5px -5px 5px 0;
|
||||
}
|
||||
|
||||
.mobileCloseButtonContainer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
height: 40px;
|
||||
border-bottom: 1px solid $borderColor;
|
||||
}
|
||||
|
||||
.mobileCloseButton {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
text-align: center;
|
||||
line-height: 40px;
|
||||
|
||||
&:hover {
|
||||
color: $modalCloseButtonHoverColor;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import React, { Component } from 'react';
|
||||
import { Manager, Popper, Reference } from 'react-popper';
|
||||
import classNames from 'classnames';
|
||||
import getUniqueElememtId from 'Utilities/getUniqueElementId';
|
||||
import { isMobile as isMobileUtil } from 'Utilities/browser';
|
||||
import { isMobile as isMobileUtil } from 'Utilities/mobile';
|
||||
import * as keyCodes from 'Utilities/Constants/keyCodes';
|
||||
import { icons, sizes, scrollDirections } from 'Helpers/Props';
|
||||
import Icon from 'Components/Icon';
|
||||
@@ -15,7 +15,6 @@ import Measure from 'Components/Measure';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import Scroller from 'Components/Scroller/Scroller';
|
||||
import TextInput from './TextInput';
|
||||
import HintedSelectInputSelectedValue from './HintedSelectInputSelectedValue';
|
||||
import HintedSelectInputOption from './HintedSelectInputOption';
|
||||
import styles from './EnhancedSelectInput.css';
|
||||
@@ -170,21 +169,11 @@ class EnhancedSelectInput extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
onFocus = () => {
|
||||
if (this.state.isOpen) {
|
||||
this._removeListener();
|
||||
this.setState({ isOpen: false });
|
||||
}
|
||||
}
|
||||
|
||||
onBlur = () => {
|
||||
if (!this.props.isEditable) {
|
||||
// Calling setState without this check prevents the click event from being properly handled on Chrome (it is on firefox)
|
||||
const origIndex = getSelectedIndex(this.props);
|
||||
|
||||
if (origIndex !== this.state.selectedIndex) {
|
||||
this.setState({ selectedIndex: origIndex });
|
||||
}
|
||||
// Calling setState without this check prevents the click event from being properly handled on Chrome (it is on firefox)
|
||||
const origIndex = getSelectedIndex(this.props);
|
||||
if (origIndex !== this.state.selectedIndex) {
|
||||
this.setState({ selectedIndex: origIndex });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,19 +297,16 @@ class EnhancedSelectInput extends Component {
|
||||
const {
|
||||
className,
|
||||
disabledClassName,
|
||||
name,
|
||||
value,
|
||||
values,
|
||||
isDisabled,
|
||||
isEditable,
|
||||
isFetching,
|
||||
hasError,
|
||||
hasWarning,
|
||||
valueOptions,
|
||||
selectedValueOptions,
|
||||
selectedValueComponent: SelectedValueComponent,
|
||||
optionComponent: OptionComponent,
|
||||
onChange
|
||||
optionComponent: OptionComponent
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
@@ -346,94 +332,52 @@ class EnhancedSelectInput extends Component {
|
||||
whitelist={['width']}
|
||||
onMeasure={this.onMeasure}
|
||||
>
|
||||
{
|
||||
isEditable ?
|
||||
<div
|
||||
className={styles.editableContainer}
|
||||
>
|
||||
<TextInput
|
||||
className={className}
|
||||
name={name}
|
||||
value={value}
|
||||
readOnly={isDisabled}
|
||||
hasError={hasError}
|
||||
hasWarning={hasWarning}
|
||||
onFocus={this.onFocus}
|
||||
onBlur={this.onBlur}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<Link
|
||||
className={classNames(
|
||||
styles.dropdownArrowContainerEditable,
|
||||
isDisabled ?
|
||||
styles.dropdownArrowContainerDisabled :
|
||||
styles.dropdownArrowContainer)
|
||||
}
|
||||
onPress={this.onPress}
|
||||
>
|
||||
{
|
||||
isFetching &&
|
||||
<LoadingIndicator
|
||||
className={styles.loading}
|
||||
size={20}
|
||||
/>
|
||||
}
|
||||
<Link
|
||||
className={classNames(
|
||||
className,
|
||||
hasError && styles.hasError,
|
||||
hasWarning && styles.hasWarning,
|
||||
isDisabled && disabledClassName
|
||||
)}
|
||||
isDisabled={isDisabled}
|
||||
onBlur={this.onBlur}
|
||||
onKeyDown={this.onKeyDown}
|
||||
onPress={this.onPress}
|
||||
>
|
||||
<SelectedValueComponent
|
||||
value={value}
|
||||
values={values}
|
||||
{...selectedValueOptions}
|
||||
{...selectedOption}
|
||||
isDisabled={isDisabled}
|
||||
isMultiSelect={isMultiSelect}
|
||||
>
|
||||
{selectedOption ? selectedOption.value : null}
|
||||
</SelectedValueComponent>
|
||||
|
||||
{
|
||||
!isFetching &&
|
||||
<Icon
|
||||
name={icons.CARET_DOWN}
|
||||
/>
|
||||
}
|
||||
</Link>
|
||||
</div> :
|
||||
<Link
|
||||
className={classNames(
|
||||
className,
|
||||
hasError && styles.hasError,
|
||||
hasWarning && styles.hasWarning,
|
||||
isDisabled && disabledClassName
|
||||
)}
|
||||
isDisabled={isDisabled}
|
||||
onBlur={this.onBlur}
|
||||
onKeyDown={this.onKeyDown}
|
||||
onPress={this.onPress}
|
||||
>
|
||||
<SelectedValueComponent
|
||||
value={value}
|
||||
values={values}
|
||||
{...selectedValueOptions}
|
||||
{...selectedOption}
|
||||
isDisabled={isDisabled}
|
||||
isMultiSelect={isMultiSelect}
|
||||
>
|
||||
{selectedOption ? selectedOption.value : null}
|
||||
</SelectedValueComponent>
|
||||
<div
|
||||
className={isDisabled ?
|
||||
styles.dropdownArrowContainerDisabled :
|
||||
styles.dropdownArrowContainer
|
||||
}
|
||||
>
|
||||
|
||||
<div
|
||||
className={isDisabled ?
|
||||
styles.dropdownArrowContainerDisabled :
|
||||
styles.dropdownArrowContainer
|
||||
}
|
||||
>
|
||||
{
|
||||
isFetching &&
|
||||
<LoadingIndicator
|
||||
className={styles.loading}
|
||||
size={20}
|
||||
/>
|
||||
}
|
||||
|
||||
{
|
||||
isFetching &&
|
||||
<LoadingIndicator
|
||||
className={styles.loading}
|
||||
size={20}
|
||||
/>
|
||||
}
|
||||
|
||||
{
|
||||
!isFetching &&
|
||||
<Icon
|
||||
name={icons.CARET_DOWN}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</Link>
|
||||
}
|
||||
{
|
||||
!isFetching &&
|
||||
<Icon
|
||||
name={icons.CARET_DOWN}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</Link>
|
||||
</Measure>
|
||||
</div>
|
||||
)}
|
||||
@@ -518,18 +462,6 @@ class EnhancedSelectInput extends Component {
|
||||
scrollDirection={scrollDirections.NONE}
|
||||
>
|
||||
<Scroller className={styles.optionsModalScroller}>
|
||||
<div className={styles.mobileCloseButtonContainer}>
|
||||
<Link
|
||||
className={styles.mobileCloseButton}
|
||||
onPress={this.onOptionsModalClose}
|
||||
>
|
||||
<Icon
|
||||
name={icons.CLOSE}
|
||||
size={18}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{
|
||||
values.map((v, index) => {
|
||||
const hasParent = v.parentKey !== undefined;
|
||||
@@ -570,7 +502,6 @@ EnhancedSelectInput.propTypes = {
|
||||
values: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
isDisabled: PropTypes.bool.isRequired,
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
isEditable: PropTypes.bool.isRequired,
|
||||
hasError: PropTypes.bool,
|
||||
hasWarning: PropTypes.bool,
|
||||
valueOptions: PropTypes.object.isRequired,
|
||||
@@ -586,7 +517,6 @@ EnhancedSelectInput.defaultProps = {
|
||||
disabledClassName: styles.isDisabled,
|
||||
isDisabled: false,
|
||||
isFetching: false,
|
||||
isEditable: false,
|
||||
valueOptions: {},
|
||||
selectedValueOptions: {},
|
||||
selectedValueComponent: HintedSelectInputSelectedValue,
|
||||
|
||||
@@ -54,8 +54,4 @@
|
||||
&:last-child {
|
||||
border: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: unset;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,7 @@ class EnhancedSelectInputOption extends Component {
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onPress = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
onPress = () => {
|
||||
const {
|
||||
id,
|
||||
onSelect
|
||||
|
||||
@@ -6,7 +6,6 @@ import AutoCompleteInput from './AutoCompleteInput';
|
||||
import CaptchaInputConnector from './CaptchaInputConnector';
|
||||
import CheckInput from './CheckInput';
|
||||
import DeviceInputConnector from './DeviceInputConnector';
|
||||
import DownloadClientSelectInputConnector from './DownloadClientSelectInputConnector';
|
||||
import KeyValueListInput from './KeyValueListInput';
|
||||
import MonitorEpisodesSelectInput from './MonitorEpisodesSelectInput';
|
||||
import NumberInput from './NumberInput';
|
||||
@@ -21,10 +20,8 @@ import SeriesTypeSelectInput from './SeriesTypeSelectInput';
|
||||
import EnhancedSelectInput from './EnhancedSelectInput';
|
||||
import EnhancedSelectInputConnector from './EnhancedSelectInputConnector';
|
||||
import TagInputConnector from './TagInputConnector';
|
||||
import TagSelectInputConnector from './TagSelectInputConnector';
|
||||
import TextTagInputConnector from './TextTagInputConnector';
|
||||
import TextInput from './TextInput';
|
||||
import UMaskInput from './UMaskInput';
|
||||
import FormInputHelpText from './FormInputHelpText';
|
||||
import styles from './FormInputGroup.css';
|
||||
|
||||
@@ -69,9 +66,6 @@ function getComponent(type) {
|
||||
case inputTypes.INDEXER_SELECT:
|
||||
return IndexerSelectInputConnector;
|
||||
|
||||
case inputTypes.DOWNLOAD_CLIENT_SELECT:
|
||||
return DownloadClientSelectInputConnector;
|
||||
|
||||
case inputTypes.ROOT_FOLDER_SELECT:
|
||||
return RootFolderSelectInputConnector;
|
||||
|
||||
@@ -90,12 +84,6 @@ function getComponent(type) {
|
||||
case inputTypes.TEXT_TAG:
|
||||
return TextTagInputConnector;
|
||||
|
||||
case inputTypes.TAG_SELECT:
|
||||
return TagSelectInputConnector;
|
||||
|
||||
case inputTypes.UMASK:
|
||||
return UMaskInput;
|
||||
|
||||
default:
|
||||
return TextInput;
|
||||
}
|
||||
@@ -203,7 +191,7 @@ function FormInputGroup(props) {
|
||||
}
|
||||
|
||||
{
|
||||
(!checkInput || helpText) && helpTextWarning &&
|
||||
!checkInput && helpTextWarning &&
|
||||
<FormInputHelpText
|
||||
text={helpTextWarning}
|
||||
isWarning={true}
|
||||
|
||||
@@ -8,13 +8,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
.keyInputWrapper {
|
||||
flex: 6 0 0;
|
||||
}
|
||||
|
||||
.valueInputWrapper {
|
||||
.inputWrapper {
|
||||
flex: 1 0 0;
|
||||
min-width: 40px;
|
||||
}
|
||||
|
||||
.buttonWrapper {
|
||||
|
||||
@@ -63,7 +63,7 @@ class KeyValueListInputItem extends Component {
|
||||
|
||||
return (
|
||||
<div className={styles.itemContainer}>
|
||||
<div className={styles.keyInputWrapper}>
|
||||
<div className={styles.inputWrapper}>
|
||||
<TextInput
|
||||
className={styles.keyInput}
|
||||
name="key"
|
||||
@@ -75,7 +75,7 @@ class KeyValueListInputItem extends Component {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.valueInputWrapper}>
|
||||
<div className={styles.inputWrapper}>
|
||||
<TextInput
|
||||
className={styles.valueInput}
|
||||
name="value"
|
||||
|
||||
@@ -3,17 +3,10 @@ import React from 'react';
|
||||
import TextInput from './TextInput';
|
||||
import styles from './PasswordInput.css';
|
||||
|
||||
// Prevent a user from copying (or cutting) the password from the input
|
||||
function onCopy(e) {
|
||||
e.preventDefault();
|
||||
e.nativeEvent.stopImmediatePropagation();
|
||||
}
|
||||
|
||||
function PasswordInput(props) {
|
||||
return (
|
||||
<TextInput
|
||||
{...props}
|
||||
onCopy={onCopy}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -29,8 +29,6 @@ function getType({ type, selectOptionsProviderAction }) {
|
||||
return inputTypes.SELECT;
|
||||
case 'tag':
|
||||
return inputTypes.TEXT_TAG;
|
||||
case 'tagSelect':
|
||||
return inputTypes.TAG_SELECT;
|
||||
case 'textbox':
|
||||
return inputTypes.TEXT;
|
||||
case 'oAuth':
|
||||
|
||||
@@ -10,16 +10,13 @@ const ADD_NEW_KEY = 'addNew';
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.rootFolders,
|
||||
(state, { value }) => value,
|
||||
(state, { includeMissingValue }) => includeMissingValue,
|
||||
(state, { includeNoChange }) => includeNoChange,
|
||||
(rootFolders, value, includeMissingValue, includeNoChange) => {
|
||||
(rootFolders, includeNoChange) => {
|
||||
const values = rootFolders.items.map((rootFolder) => {
|
||||
return {
|
||||
key: rootFolder.path,
|
||||
value: rootFolder.path,
|
||||
freeSpace: rootFolder.freeSpace,
|
||||
isMissing: false
|
||||
freeSpace: rootFolder.freeSpace
|
||||
};
|
||||
});
|
||||
|
||||
@@ -27,8 +24,7 @@ function createMapStateToProps() {
|
||||
values.unshift({
|
||||
key: 'noChange',
|
||||
value: 'No Change',
|
||||
isDisabled: true,
|
||||
isMissing: false
|
||||
isDisabled: true
|
||||
});
|
||||
}
|
||||
|
||||
@@ -41,15 +37,6 @@ function createMapStateToProps() {
|
||||
});
|
||||
}
|
||||
|
||||
if (includeMissingValue && !values.find((v) => v.key === value)) {
|
||||
values.push({
|
||||
key: value,
|
||||
value,
|
||||
isMissing: true,
|
||||
isDisabled: true
|
||||
});
|
||||
}
|
||||
|
||||
values.push({
|
||||
key: ADD_NEW_KEY,
|
||||
value: 'Add a new path'
|
||||
|
||||
@@ -27,9 +27,3 @@
|
||||
color: $darkGray;
|
||||
font-size: $smallFontSize;
|
||||
}
|
||||
|
||||
.isMissing {
|
||||
margin-left: 15px;
|
||||
color: $dangerColor;
|
||||
font-size: $smallFontSize;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ function RootFolderSelectInputOption(props) {
|
||||
id,
|
||||
value,
|
||||
freeSpace,
|
||||
isMissing,
|
||||
seriesFolder,
|
||||
isMobile,
|
||||
isWindows,
|
||||
@@ -44,20 +43,11 @@ function RootFolderSelectInputOption(props) {
|
||||
</div>
|
||||
|
||||
{
|
||||
freeSpace == null ?
|
||||
null :
|
||||
freeSpace != null &&
|
||||
<div className={styles.freeSpace}>
|
||||
{formatBytes(freeSpace)} Free
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
isMissing ?
|
||||
<div className={styles.isMissing}>
|
||||
Missing
|
||||
</div> :
|
||||
null
|
||||
}
|
||||
</div>
|
||||
</EnhancedSelectInputOption>
|
||||
);
|
||||
@@ -67,7 +57,6 @@ RootFolderSelectInputOption.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
value: PropTypes.string.isRequired,
|
||||
freeSpace: PropTypes.number,
|
||||
isMissing: PropTypes.boolean,
|
||||
seriesFolder: PropTypes.string,
|
||||
isMobile: PropTypes.bool.isRequired,
|
||||
isWindows: PropTypes.bool
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user