mirror of
https://github.com/Radarr/Radarr.git
synced 2026-03-07 13:40:38 -05:00
Compare commits
3 Commits
new-exclus
...
async-test
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
38c5989695 | ||
|
|
9ca03ba450 | ||
|
|
d283967523 |
213
.editorconfig
213
.editorconfig
@@ -2,12 +2,6 @@
|
||||
# editorconfig.org
|
||||
root = true
|
||||
|
||||
# NOTE: Requires **VS2019 16.3** or later
|
||||
|
||||
# Stylecop.ruleset
|
||||
# Description: Rules for Radarr
|
||||
|
||||
# Code files
|
||||
[*.cs]
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
@@ -44,213 +38,6 @@ csharp_style_var_for_built_in_types = true:suggestion
|
||||
csharp_style_var_when_type_is_apparent = true:suggestion
|
||||
csharp_style_var_elsewhere = true:suggestion
|
||||
|
||||
# Stylecop Rules
|
||||
dotnet_diagnostic.SA0001.severity = none
|
||||
dotnet_diagnostic.SA1005.severity = none
|
||||
dotnet_diagnostic.SA1025.severity = none
|
||||
dotnet_diagnostic.SA1101.severity = none
|
||||
dotnet_diagnostic.SA1116.severity = none
|
||||
dotnet_diagnostic.SA1118.severity = none
|
||||
dotnet_diagnostic.SA1122.severity = none
|
||||
dotnet_diagnostic.SA1201.severity = suggestion
|
||||
dotnet_diagnostic.SA1202.severity = suggestion
|
||||
dotnet_diagnostic.SA1204.severity = suggestion
|
||||
dotnet_diagnostic.SA1300.severity = none
|
||||
dotnet_diagnostic.SA1303.severity = none
|
||||
dotnet_diagnostic.SA1304.severity = none
|
||||
dotnet_diagnostic.SA1306.severity = none
|
||||
dotnet_diagnostic.SA1309.severity = none
|
||||
dotnet_diagnostic.SA1310.severity = none
|
||||
dotnet_diagnostic.SA1401.severity = none
|
||||
dotnet_diagnostic.SA1402.severity = none
|
||||
dotnet_diagnostic.SA1404.severity = suggestion
|
||||
dotnet_diagnostic.SA1405.severity = suggestion
|
||||
dotnet_diagnostic.SA1406.severity = suggestion
|
||||
dotnet_diagnostic.SA1410.severity = suggestion
|
||||
dotnet_diagnostic.SA1411.severity = suggestion
|
||||
dotnet_diagnostic.SA1413.severity = none
|
||||
dotnet_diagnostic.SA1516.severity = none
|
||||
dotnet_diagnostic.SA1600.severity = none
|
||||
dotnet_diagnostic.SA1601.severity = none
|
||||
dotnet_diagnostic.SA1602.severity = none
|
||||
dotnet_diagnostic.SA1604.severity = none
|
||||
dotnet_diagnostic.SA1605.severity = none
|
||||
dotnet_diagnostic.SA1606.severity = none
|
||||
dotnet_diagnostic.SA1607.severity = none
|
||||
dotnet_diagnostic.SA1608.severity = none
|
||||
dotnet_diagnostic.SA1610.severity = none
|
||||
dotnet_diagnostic.SA1611.severity = none
|
||||
dotnet_diagnostic.SA1612.severity = none
|
||||
dotnet_diagnostic.SA1613.severity = none
|
||||
dotnet_diagnostic.SA1614.severity = none
|
||||
dotnet_diagnostic.SA1615.severity = none
|
||||
dotnet_diagnostic.SA1616.severity = none
|
||||
dotnet_diagnostic.SA1617.severity = none
|
||||
dotnet_diagnostic.SA1618.severity = none
|
||||
dotnet_diagnostic.SA1619.severity = none
|
||||
dotnet_diagnostic.SA1620.severity = none
|
||||
dotnet_diagnostic.SA1621.severity = none
|
||||
dotnet_diagnostic.SA1622.severity = none
|
||||
dotnet_diagnostic.SA1623.severity = none
|
||||
dotnet_diagnostic.SA1624.severity = none
|
||||
dotnet_diagnostic.SA1625.severity = none
|
||||
dotnet_diagnostic.SA1626.severity = none
|
||||
dotnet_diagnostic.SA1627.severity = none
|
||||
dotnet_diagnostic.SA1629.severity = none
|
||||
dotnet_diagnostic.SA1633.severity = none
|
||||
dotnet_diagnostic.SA1634.severity = none
|
||||
dotnet_diagnostic.SA1635.severity = none
|
||||
dotnet_diagnostic.SA1636.severity = none
|
||||
dotnet_diagnostic.SA1637.severity = none
|
||||
dotnet_diagnostic.SA1638.severity = none
|
||||
dotnet_diagnostic.SA1640.severity = none
|
||||
dotnet_diagnostic.SA1641.severity = none
|
||||
dotnet_diagnostic.SA1642.severity = none
|
||||
dotnet_diagnostic.SA1643.severity = none
|
||||
dotnet_diagnostic.SA1648.severity = none
|
||||
dotnet_diagnostic.SA1649.severity = none
|
||||
dotnet_diagnostic.SA1651.severity = none
|
||||
dotnet_diagnostic.SX1101.severity = warning
|
||||
dotnet_diagnostic.SX1309.severity = warning
|
||||
|
||||
# Microsoft Analyzers that fail and need to be sorted thru
|
||||
dotnet_diagnostic.ASP0000.severity = suggestion
|
||||
dotnet_diagnostic.CA1000.severity = suggestion
|
||||
dotnet_diagnostic.CA1001.severity = suggestion
|
||||
dotnet_diagnostic.CA1003.severity = suggestion
|
||||
dotnet_diagnostic.CA1008.severity = suggestion
|
||||
dotnet_diagnostic.CA1010.severity = suggestion
|
||||
dotnet_diagnostic.CA1012.severity = suggestion
|
||||
dotnet_diagnostic.CA1014.severity = suggestion
|
||||
dotnet_diagnostic.CA1016.severity = suggestion
|
||||
dotnet_diagnostic.CA1017.severity = suggestion
|
||||
dotnet_diagnostic.CA1018.severity = suggestion
|
||||
dotnet_diagnostic.CA1019.severity = suggestion
|
||||
dotnet_diagnostic.CA1021.severity = suggestion
|
||||
dotnet_diagnostic.CA1024.severity = suggestion
|
||||
dotnet_diagnostic.CA1027.severity = suggestion
|
||||
dotnet_diagnostic.CA1028.severity = suggestion
|
||||
dotnet_diagnostic.CA1030.severity = suggestion
|
||||
dotnet_diagnostic.CA1031.severity = suggestion
|
||||
dotnet_diagnostic.CA1032.severity = suggestion
|
||||
dotnet_diagnostic.CA1033.severity = suggestion
|
||||
dotnet_diagnostic.CA1034.severity = suggestion
|
||||
dotnet_diagnostic.CA1036.severity = suggestion
|
||||
dotnet_diagnostic.CA1040.severity = suggestion
|
||||
dotnet_diagnostic.CA1041.severity = suggestion
|
||||
dotnet_diagnostic.CA1043.severity = suggestion
|
||||
dotnet_diagnostic.CA1044.severity = suggestion
|
||||
dotnet_diagnostic.CA1050.severity = suggestion
|
||||
dotnet_diagnostic.CA1051.severity = suggestion
|
||||
dotnet_diagnostic.CA1052.severity = suggestion
|
||||
dotnet_diagnostic.CA1054.severity = suggestion
|
||||
dotnet_diagnostic.CA1055.severity = suggestion
|
||||
dotnet_diagnostic.CA1056.severity = suggestion
|
||||
dotnet_diagnostic.CA1058.severity = suggestion
|
||||
dotnet_diagnostic.CA1060.severity = suggestion
|
||||
dotnet_diagnostic.CA1061.severity = suggestion
|
||||
dotnet_diagnostic.CA1062.severity = suggestion
|
||||
dotnet_diagnostic.CA1063.severity = suggestion
|
||||
dotnet_diagnostic.CA1064.severity = suggestion
|
||||
dotnet_diagnostic.CA1065.severity = suggestion
|
||||
dotnet_diagnostic.CA1066.severity = suggestion
|
||||
dotnet_diagnostic.CA1067.severity = suggestion
|
||||
dotnet_diagnostic.CA1068.severity = suggestion
|
||||
dotnet_diagnostic.CA1069.severity = suggestion
|
||||
dotnet_diagnostic.CA1200.severity = suggestion
|
||||
dotnet_diagnostic.CA1303.severity = suggestion
|
||||
dotnet_diagnostic.CA1304.severity = suggestion
|
||||
dotnet_diagnostic.CA1305.severity = suggestion
|
||||
dotnet_diagnostic.CA1307.severity = suggestion
|
||||
dotnet_diagnostic.CA1308.severity = suggestion
|
||||
dotnet_diagnostic.CA1401.severity = suggestion
|
||||
dotnet_diagnostic.CA1507.severity = suggestion
|
||||
dotnet_diagnostic.CA1707.severity = suggestion
|
||||
dotnet_diagnostic.CA1710.severity = suggestion
|
||||
dotnet_diagnostic.CA1712.severity = suggestion
|
||||
dotnet_diagnostic.CA1714.severity = suggestion
|
||||
dotnet_diagnostic.CA1715.severity = suggestion
|
||||
dotnet_diagnostic.CA1716.severity = suggestion
|
||||
dotnet_diagnostic.CA1717.severity = suggestion
|
||||
dotnet_diagnostic.CA1720.severity = suggestion
|
||||
dotnet_diagnostic.CA1721.severity = suggestion
|
||||
dotnet_diagnostic.CA1724.severity = suggestion
|
||||
dotnet_diagnostic.CA1801.severity = suggestion
|
||||
dotnet_diagnostic.CA1802.severity = suggestion
|
||||
dotnet_diagnostic.CA1805.severity = suggestion
|
||||
dotnet_diagnostic.CA1806.severity = suggestion
|
||||
dotnet_diagnostic.CA1810.severity = suggestion
|
||||
dotnet_diagnostic.CA1812.severity = suggestion
|
||||
dotnet_diagnostic.CA1814.severity = suggestion
|
||||
dotnet_diagnostic.CA1815.severity = suggestion
|
||||
dotnet_diagnostic.CA1816.severity = suggestion
|
||||
dotnet_diagnostic.CA1819.severity = suggestion
|
||||
dotnet_diagnostic.CA1822.severity = suggestion
|
||||
dotnet_diagnostic.CA1823.severity = suggestion
|
||||
dotnet_diagnostic.CA1824.severity = suggestion
|
||||
dotnet_diagnostic.CA2000.severity = suggestion
|
||||
dotnet_diagnostic.CA2002.severity = suggestion
|
||||
dotnet_diagnostic.CA2007.severity = suggestion
|
||||
dotnet_diagnostic.CA2008.severity = suggestion
|
||||
dotnet_diagnostic.CA2009.severity = suggestion
|
||||
dotnet_diagnostic.CA2010.severity = suggestion
|
||||
dotnet_diagnostic.CA2011.severity = suggestion
|
||||
dotnet_diagnostic.CA2012.severity = suggestion
|
||||
dotnet_diagnostic.CA2013.severity = suggestion
|
||||
dotnet_diagnostic.CA2100.severity = suggestion
|
||||
dotnet_diagnostic.CA2101.severity = suggestion
|
||||
dotnet_diagnostic.CA2119.severity = suggestion
|
||||
dotnet_diagnostic.CA2153.severity = suggestion
|
||||
dotnet_diagnostic.CA2200.severity = suggestion
|
||||
dotnet_diagnostic.CA2207.severity = suggestion
|
||||
dotnet_diagnostic.CA2208.severity = suggestion
|
||||
dotnet_diagnostic.CA2211.severity = suggestion
|
||||
dotnet_diagnostic.CA2213.severity = suggestion
|
||||
dotnet_diagnostic.CA2214.severity = suggestion
|
||||
dotnet_diagnostic.CA2215.severity = suggestion
|
||||
dotnet_diagnostic.CA2216.severity = suggestion
|
||||
dotnet_diagnostic.CA2219.severity = suggestion
|
||||
dotnet_diagnostic.CA2225.severity = suggestion
|
||||
dotnet_diagnostic.CA2226.severity = suggestion
|
||||
dotnet_diagnostic.CA2227.severity = suggestion
|
||||
dotnet_diagnostic.CA2229.severity = suggestion
|
||||
dotnet_diagnostic.CA2231.severity = suggestion
|
||||
dotnet_diagnostic.CA2234.severity = suggestion
|
||||
dotnet_diagnostic.CA2235.severity = suggestion
|
||||
dotnet_diagnostic.CA2237.severity = suggestion
|
||||
dotnet_diagnostic.CA2241.severity = suggestion
|
||||
dotnet_diagnostic.CA2242.severity = suggestion
|
||||
dotnet_diagnostic.CA2243.severity = suggestion
|
||||
dotnet_diagnostic.CA2244.severity = suggestion
|
||||
dotnet_diagnostic.CA2245.severity = suggestion
|
||||
dotnet_diagnostic.CA2246.severity = suggestion
|
||||
dotnet_diagnostic.CA3061.severity = suggestion
|
||||
dotnet_diagnostic.CA3075.severity = suggestion
|
||||
dotnet_diagnostic.CA3076.severity = suggestion
|
||||
dotnet_diagnostic.CA3077.severity = suggestion
|
||||
dotnet_diagnostic.CA3147.severity = suggestion
|
||||
dotnet_diagnostic.CA5350.severity = suggestion
|
||||
dotnet_diagnostic.CA5351.severity = suggestion
|
||||
dotnet_diagnostic.CA5359.severity = suggestion
|
||||
dotnet_diagnostic.CA5360.severity = suggestion
|
||||
dotnet_diagnostic.CA5363.severity = suggestion
|
||||
dotnet_diagnostic.CA5364.severity = suggestion
|
||||
dotnet_diagnostic.CA5365.severity = suggestion
|
||||
dotnet_diagnostic.CA5366.severity = suggestion
|
||||
dotnet_diagnostic.CA5368.severity = suggestion
|
||||
dotnet_diagnostic.CA5369.severity = suggestion
|
||||
dotnet_diagnostic.CA5370.severity = suggestion
|
||||
dotnet_diagnostic.CA5371.severity = suggestion
|
||||
dotnet_diagnostic.CA5372.severity = suggestion
|
||||
dotnet_diagnostic.CA5373.severity = suggestion
|
||||
dotnet_diagnostic.CA5374.severity = suggestion
|
||||
dotnet_diagnostic.CA5379.severity = suggestion
|
||||
dotnet_diagnostic.CA5384.severity = suggestion
|
||||
dotnet_diagnostic.CA5385.severity = suggestion
|
||||
dotnet_diagnostic.CA5397.severity = suggestion
|
||||
|
||||
|
||||
|
||||
[*.{js,html,js,hbs,less,css}]
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
@@ -6,7 +6,7 @@
|
||||
|
||||
**Just because you receive an exception in your logs, doesn't mean it's a bug and should be reported here. Often it's something else, such as a permission error. If you are unsure ask on the Discord or Subreddit first.**
|
||||
|
||||
Visit our [Discord server](https://discord.gg/r5wJPt9) or [Subreddit](https://reddit.com/r/radarr) for support or longer discussions. Support questions posed on here will be closed immediately.
|
||||
Visit our [Discord server](https://discord.gg/NWYch8M) or [Subreddit](https://reddit.com/r/radarr) for support or longer discussions. Support questions posed on here will be closed immediately.
|
||||
|
||||
Provide a description of the feature request or bug here, the more details the better.
|
||||
Please also include the following if you are reporting a bug. If you do not include it, the issue will probably be closed as we cannot help you. -->
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,7 +1,7 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Support via Discord
|
||||
url: https://discord.gg/r5wJPt9
|
||||
url: https://discord.gg/AD3UP37
|
||||
about: Chat with users and devs on support and setup related topics.
|
||||
- name: Support via Reddit
|
||||
url: https://reddit.com/r/radarr
|
||||
|
||||
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -6,7 +6,6 @@ YES | NO
|
||||
|
||||
#### Todos
|
||||
- [ ] Tests
|
||||
- [ ] Translation Keys
|
||||
|
||||
#### Issues Fixed or Closed by this PR
|
||||
|
||||
|
||||
5
.github/stale.yml
vendored
5
.github/stale.yml
vendored
@@ -7,10 +7,7 @@ exemptLabels:
|
||||
- feature request
|
||||
- parser
|
||||
- confirmed
|
||||
- sonarr-pull
|
||||
- lidarr-pull
|
||||
- readarr-pull
|
||||
- v3
|
||||
- aphrodite
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: stale
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
|
||||
2
.github/support.yml
vendored
2
.github/support.yml
vendored
@@ -6,7 +6,7 @@ supportLabel: support
|
||||
# to a support page, or set to `false` to disable
|
||||
supportComment: >
|
||||
We use the issue tracker exclusively for bug reports and feature requests.
|
||||
However, this issue appears to be a support request. Please hop over onto our [Discord](https://discord.gg/r5wJPt9) or [Subreddit](https://reddit.com/r/radarr)
|
||||
However, this issue appears to be a support request. Please hop over onto our [Discord](https://discord.gg/ZDmT7qb) or [Subreddit](https://reddit.com/r/radarr)
|
||||
# Whether to close issues marked as support requests
|
||||
close: true
|
||||
# Whether to lock issues marked as support requests
|
||||
|
||||
@@ -14,7 +14,7 @@ See the readme for information on setting up your development environment.
|
||||
- Rebase from Radarr'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 the discord 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
|
||||
|
||||
18
DEVELOPMENT.md
Normal file
18
DEVELOPMENT.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# New UI Development
|
||||
|
||||
This document should provide an overview of current UI development, progress and blockers.
|
||||
|
||||
## Current Focus
|
||||
|
||||
Our current focus is creating a foundation for the UI, so that everything can be built upon it.
|
||||
|
||||
We are trialing the Sonarr V3 UI as our foundation. So far it has been working great and we already have a working build running.
|
||||
|
||||
## Performance Issues
|
||||
|
||||
You can download a database with 40k movies here: https://radarr.video/dev/radarr.db (Version where the next refresh movie scan is in a year. The refresh movie scan will lag the UI and other stuff. https://radarr.video/dev/radarr_no_scan.db). Just place it in your AppData Directory while Radarr is not running and make sure it's named radarr.db (https://github.com/Radarr/Radarr/wiki/AppData-Directory).
|
||||
You will have to message me (@galli-leo) via Discord or Reddit for the username and password (just as a precaution).
|
||||
|
||||
## Tasks
|
||||
|
||||
The actual tasks that are not related to the foundation of the new UI are all listed here https://github.com/Radarr/Radarr/projects/4. They are sorted according to different priorities. Some issues are also issues that shouldn't need anything extra, just a correct implementation in the new UI.
|
||||
63
README.md
63
README.md
@@ -1,46 +1,42 @@
|
||||
# Radarr
|
||||
|
||||
[](https://dev.azure.com/Radarr/Radarr/_build/latest?definitionId=1&branchName=develop)
|
||||
[](https://translate.servarr.com/engage/radarr/?utm_source=widget)
|
||||
[](https://github.com/Radarr/Radarr/wiki/Docker)
|
||||

|
||||
[](#backers) [](#sponsors)
|
||||
**New UI Development:** For an overview of the new UI development see [DEVELOPMENT.md](https://github.com/Radarr/Radarr/blob/aphrodite/DEVELOPMENT.md).
|
||||
|
||||
Radarr is an __independent__ fork of [Sonarr](https://github.com/Sonarr/Sonarr) reworked for automatically downloading movies via Usenet and BitTorrent.
|
||||
|
||||
The project was inspired by other Usenet/BitTorrent movie downloaders such as CouchPotato.
|
||||
|
||||
See the [Roadmap blogpost](https://blog.radarr.video/development/update/2018/11/11/roadmap-update.html) for an overview of planned features.
|
||||
|
||||
## Getting Started
|
||||
|
||||
[](https://github.com/Radarr/Radarr/wiki/Installation)
|
||||
[](https://github.com/Radarr/Radarr/wiki/Docker)
|
||||
[](https://github.com/Radarr/Radarr/wiki/Docker)
|
||||
[](https://github.com/Radarr/Radarr/wiki/Setup-Guide)
|
||||
[](https://github.com/Radarr/Radarr/wiki/FAQ)
|
||||
|
||||
|
||||
If you are using Docker please ensure your Docker paths are setup correctly using [this guide to facilitate](https://old.reddit.com/r/usenet/wiki/docker) hardlinks and minimize permissions issues.
|
||||
|
||||
* [Install Radarr for your desired OS](https://github.com/Radarr/Radarr/wiki/Installation) *or* use [Docker](https://github.com/Radarr/Radarr/wiki/Docker)
|
||||
* *For Linux users*, run `radarr` and *optionally* have [Radarr start automatically](https://github.com/Radarr/Radarr/wiki/Autostart-on-Linux)
|
||||
* Connect to the UI through <http://localhost:7878> or <http://your-ip:7878> in your web browser
|
||||
* See the [Setup Guide](https://github.com/Radarr/Radarr/wiki/Setup-Guide) for further configuration
|
||||
|
||||
## Downloads
|
||||
Please note that v0.2 will only have critical bugs resolved as of August 2020. Any additional development or features will be soley in V3.
|
||||
|
||||
Each push to the "develop" branch creates a build on "nightly" release channel (release channel is the "branch" within radarr's settings), once we push a build to Github it will show up on "develop" release channel.
|
||||
|
||||
|
||||
| Release Channel Type | Branch: develop (stable) (v0.2) | Branch: nightly (semi-unstable) (v3.0) |
|
||||
|-----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Binary Releases | [](https://github.com/Radarr/Radarr/releases) | [](https://radarr.servarr.com/v1/update/nightly/updatefile?os=windows&runtime=netcore&arch=x64) <br> [](https://radarr.servarr.com/v1/update/nightly/updatefile?os=linux&runtime=netcore&arch=x64) <br> [](https://radarr.servarr.com/v1/update/nightly/updatefile?os=linux&runtime=netcore&arch=arm64) [](https://radarr.servarr.com/v1/update/nightly/updatefile?os=linux&runtime=netcore&arch=arm) <br> [](https://radarr.servarr.com/v1/update/nightly/updatefile?os=osx&runtime=netcore&arch=x64)
|
||||
| Docker - lsio | [](https://hub.docker.com/r/linuxserver/radarr) | [](https://hub.docker.com/r/linuxserver/radarr) |
|
||||
| Docker - hotio | [](https://hub.docker.com/r/hotio/radarr) | [](https://hub.docker.com/r/hotio/radarr) |
|
||||
| Release Type | Branch: develop (stable) | Branch: nightly (semi-unstable) | Branch: aphrodite (very-unstable) |
|
||||
|-----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Binary Releases | [](https://github.com/Radarr/Radarr/releases) | [](https://ci.appveyor.com/project/galli-leo/radarr-usby1/branch/develop/artifacts) | |
|
||||
| Docker | [](https://hub.docker.com/r/linuxserver/radarr) | [](https://hub.docker.com/r/linuxserver/radarr) | [](https://hub.docker.com/r/linuxserver/radarr) |
|
||||
| Docker | [](https://hub.docker.com/r/hotio/radarr) | [](https://hub.docker.com/r/hotio/radarr) | [](https://hub.docker.com/r/hotio/radarr) |
|
||||
|
||||
## Support
|
||||
|
||||
[](https://discord.gg/r5wJPt9)
|
||||
[](#backers)
|
||||
[](#flexible-sponsors)
|
||||
[](#sponsors)
|
||||
|
||||
[](https://discord.gg/AD3UP37)
|
||||
[](https://www.reddit.com/r/radarr)
|
||||
[](http://feathub.com/Radarr/Radarr)
|
||||
[](https://github.com/Radarr/Radarr/issues)
|
||||
[](https://github.com/Radarr/Radarr/wiki)
|
||||
|
||||
@@ -49,13 +45,23 @@ Each push to the "develop" branch creates a build on "nightly" release channel (
|
||||
[](https://github.com/Radarr/Radarr/issues)
|
||||
[](https://github.com/Radarr/Radarr/pulls)
|
||||
[](http://www.gnu.org/licenses/gpl.html)
|
||||
[](https://github.com/Radarr/Radarr)
|
||||
[](https://github.com/Radarr/Radarr)
|
||||
[](https://github.com/Radarr/Radarr/releases/)
|
||||
[](https://hub.docker.com/r/linuxserver/radarr/)
|
||||
[](/CHANGELOG.md#unreleased)
|
||||
|
||||
| Service | Master | Develop |
|
||||
|----------|:---------------------------:|:----------------------------:|
|
||||
| AppVeyor | [](https://ci.appveyor.com/project/galli-leo/Radarr) | [](https://ci.appveyor.com/project/galli-leo/Radarr-usby1) |
|
||||
| Travis | [](https://travis-ci.org/Radarr/Radarr) | [](https://travis-ci.org/Radarr/Radarr) |
|
||||
|
||||
### [Site and API Status](https://status.radarr.video)
|
||||
|
||||
| API | Updates | Sites |
|
||||
|-------|:----:|:----:|
|
||||
| [](https://api.radarr.video/v2/) | [](https://radarr.aeonlucid.com) | [](https://mappings.radarr.video/)
|
||||
| [](https://staging.api.radarr.video/) | [](https://api.github.com/v3/) | [](https://radarr.video/)
|
||||
|
||||
Radarr is currently undergoing rapid development and pull requests are actively added into the repository.
|
||||
|
||||
## Features
|
||||
@@ -78,14 +84,18 @@ Radarr is currently undergoing rapid development and pull requests are actively
|
||||
* New TorrentPotato Indexer
|
||||
* Torznab Indexer now supports Movies (Works well with [Jackett](https://github.com/Jackett/Jackett))
|
||||
* Scanning PreDB to know when a new release is available
|
||||
* Importing movies from various online sources, such as IMDb Watchlists or Trakt (v3) (A complete list can be found [here](https://github.com/Radarr/Radarr/issues/114))
|
||||
* Importing movies from various online sources, such as IMDb Watchlists (A complete list can be found [here](https://github.com/Radarr/Radarr/issues/114))
|
||||
* Full integration with Kodi, Plex (notification, library update)
|
||||
* And a new beautiful UI (v3)
|
||||
* And a beautiful UI
|
||||
* Importing Metadata such as trailers or subtitles
|
||||
* Adding metadata such as posters and information for Kodi and others to use
|
||||
* Advanced customization for profiles, such that Radarr will always download the copy you want
|
||||
|
||||
#### [Feature Requests](https://github.com/Radarr/Radarr/issues/new?assignees=&labels=feature+request&template=feature_request.md&title=)
|
||||
### Planned Features
|
||||
|
||||
See the [Roadmap blogpost](https://blog.radarr.video/development/update/2018/11/11/roadmap-update.html) for an overview of planned features.
|
||||
|
||||
#### [Feature Requests](http://feathub.com/Radarr/Radarr)
|
||||
|
||||
## Configuring the Development Environment
|
||||
|
||||
@@ -100,6 +110,7 @@ Radarr is currently undergoing rapid development and pull requests are actively
|
||||
|
||||
* Make sure all the required software mentioned above are installed
|
||||
* Clone the repository into your development machine ([*info*](https://help.github.com/desktop/guides/contributing/working-with-your-remote-repository-on-github-or-github-enterprise))
|
||||
* Grab the submodules `git submodule init && git submodule update`
|
||||
* Install the required Node Packages `yarn install`
|
||||
* Start gulp to monitor your dev environment for any changes that need post processing using `yarn start` command.
|
||||
|
||||
@@ -114,9 +125,9 @@ Radarr is currently undergoing rapid development and pull requests are actively
|
||||
|
||||
### Development
|
||||
|
||||
* Open `Radarr.sln` in Visual Studio 2019 or run the build.sh script, if Mono is installed. Alternatively you can use Jetbrains Rider, since it works on all Platforms.
|
||||
* Open `Radarr.sln` in Visual Studio 2017 or run the build.sh script, if Mono is installed. Alternatively you can use Jetbrains Rider, since it works on all Platforms.
|
||||
* Make sure `NzbDrone.Console` is set as the startup project
|
||||
* Run `build.sh` before running, or build in VS
|
||||
* Run `build.sh` before running
|
||||
|
||||
## Supporters
|
||||
|
||||
@@ -146,4 +157,4 @@ Thank you to [<img src="/Logo/jetbrains.svg" alt="JetBrains" width="32"> JetBrai
|
||||
## License
|
||||
|
||||
* [GNU GPL v3](http://www.gnu.org/licenses/gpl.html)
|
||||
* Copyright 2010-2020
|
||||
* Copyright 2010-2019
|
||||
|
||||
3
appveyor.yml
Normal file
3
appveyor.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
skip_commits:
|
||||
files:
|
||||
- '**/**'
|
||||
@@ -13,17 +13,17 @@ variables:
|
||||
buildName: '$(Build.SourceBranchName).$(radarrVersion)'
|
||||
sentryOrg: 'servarr'
|
||||
sentryUrl: 'https://sentry.servarr.com'
|
||||
dotnetVersion: '3.1.401'
|
||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||
dotnetVersion: '3.1.300'
|
||||
|
||||
trigger:
|
||||
branches:
|
||||
include:
|
||||
- develop
|
||||
- master
|
||||
- aphrodite
|
||||
|
||||
pr:
|
||||
- develop
|
||||
- aphrodite
|
||||
|
||||
stages:
|
||||
- stage: Setup
|
||||
@@ -39,7 +39,7 @@ stages:
|
||||
displayName: Set Build Name
|
||||
- bash: |
|
||||
if [[ $BUILD_REASON == "PullRequest" ]]; then
|
||||
git diff origin/develop...HEAD --name-only | grep -E "^(src/|azure-pipelines.yml)"
|
||||
git diff origin/aphrodite...HEAD --name-only | grep -E "^(src/|azure-pipelines.yml)"
|
||||
echo $? > not_backend_update
|
||||
else
|
||||
echo 0 > not_backend_update
|
||||
@@ -68,9 +68,6 @@ stages:
|
||||
|
||||
pool:
|
||||
vmImage: $(imageName)
|
||||
variables:
|
||||
# Disable stylecop here - linting errors get caught by the analyze task
|
||||
EnableAnalyzers: 'false'
|
||||
steps:
|
||||
- checkout: self
|
||||
submodules: true
|
||||
@@ -104,10 +101,6 @@ stages:
|
||||
artifact: LinuxCoreTests
|
||||
displayName: Publish Linux Test Package
|
||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||
- publish: '$(testsFolder)/netcoreapp3.1/linux-musl-x64/publish'
|
||||
artifact: LinuxMuslCoreTests
|
||||
displayName: Publish Linux Musl Test Package
|
||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||
- publish: '$(testsFolder)/netcoreapp3.1/osx-x64/publish'
|
||||
artifact: MacCoreTests
|
||||
displayName: Publish MacOS Test Package
|
||||
@@ -139,19 +132,10 @@ stages:
|
||||
- checkout: self
|
||||
submodules: true
|
||||
fetchDepth: 1
|
||||
- task: Cache@2
|
||||
inputs:
|
||||
key: 'yarn | "$(osName)" | yarn.lock'
|
||||
restoreKeys: |
|
||||
yarn | "$(osName)"
|
||||
yarn
|
||||
path: $(yarnCacheFolder)
|
||||
displayName: Cache Yarn packages
|
||||
- bash: ./build.sh --frontend
|
||||
displayName: Build Radarr Frontend
|
||||
env:
|
||||
FORCE_COLOR: 0
|
||||
YARN_CACHE_FOLDER: $(yarnCacheFolder)
|
||||
- publish: $(outputFolder)
|
||||
artifact: '$(osName)Frontend'
|
||||
displayName: Publish Frontend
|
||||
@@ -259,14 +243,6 @@ stages:
|
||||
tarCompression: 'gz'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-x64/netcoreapp3.1
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create Linux Musl Core tar
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).linux-musl-core-x64.tar.gz'
|
||||
archiveType: 'tar'
|
||||
tarCompression: 'gz'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-musl-x64/netcoreapp3.1
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create ARM32 Linux Core tar
|
||||
inputs:
|
||||
@@ -283,14 +259,6 @@ stages:
|
||||
tarCompression: 'gz'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-arm64/netcoreapp3.1
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create ARM64 Linux Musl Core tar
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).linux-musl-core-arm64.tar.gz'
|
||||
archiveType: 'tar'
|
||||
tarCompression: 'gz'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-musl-arm64/netcoreapp3.1
|
||||
- publish: $(Build.ArtifactStagingDirectory)
|
||||
artifact: 'Packages'
|
||||
displayName: Publish Packages
|
||||
@@ -301,22 +269,14 @@ stages:
|
||||
sentry-cli releases new --finalize -p radarr -p radarr-ui -p radarr-update "${RELEASENAME}"
|
||||
sentry-cli releases -p radarr-ui files "${RELEASENAME}" upload-sourcemaps _output/UI/ --rewrite
|
||||
sentry-cli releases set-commits --auto "${RELEASENAME}"
|
||||
if [[ ${BUILD_SOURCEBRANCH} == "refs/heads/develop" ]]; then
|
||||
sentry-cli releases deploys "${RELEASENAME}" new -e nightly
|
||||
else
|
||||
sentry-cli releases deploys "${RELEASENAME}" new -e production
|
||||
fi
|
||||
sentry-cli releases deploys "${RELEASENAME}" new -e aphrodite
|
||||
if [ $? -gt 0 ]; then
|
||||
echo "##vso[task.logissue type=warning]Error uploading source maps."
|
||||
fi
|
||||
exit 0
|
||||
displayName: Publish Sentry Source Maps
|
||||
condition: |
|
||||
or
|
||||
(
|
||||
and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/develop')),
|
||||
and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master'))
|
||||
)
|
||||
continueOnError: true
|
||||
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/aphrodite'))
|
||||
env:
|
||||
SENTRY_AUTH_TOKEN: $(sentryAuthTokenServarr)
|
||||
SENTRY_ORG: $(sentryOrg)
|
||||
@@ -386,6 +346,11 @@ stages:
|
||||
- powershell: Set-Service SCardSvr -StartupType Manual
|
||||
displayName: Enable Windows Test Service
|
||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||
- bash: |
|
||||
wget https://github.com/acoustid/chromaprint/releases/download/v1.4.3/chromaprint-fpcalc-1.4.3-linux-x86_64.tar.gz
|
||||
sudo tar xf chromaprint-fpcalc-1.4.3-linux-x86_64.tar.gz --strip-components=1 --directory /usr/bin
|
||||
displayName: Install fpcalc
|
||||
condition: and(succeeded(), eq(variables['osName'], 'Linux'))
|
||||
- bash: |
|
||||
SYMLINK=6_6_0
|
||||
MONOPREFIX=/Library/Frameworks/Mono.framework/Versions/$SYMLINK
|
||||
@@ -417,22 +382,18 @@ stages:
|
||||
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||
strategy:
|
||||
matrix:
|
||||
mono510:
|
||||
testName: 'Mono 5.10'
|
||||
containerImage: servarr/testimages:mono-5.10
|
||||
mono520:
|
||||
testName: 'Mono 5.20'
|
||||
artifactName: LinuxTests
|
||||
containerImage: servarr/testimages:mono-5.20
|
||||
mono608:
|
||||
testName: 'Mono 6.8'
|
||||
containerImage: servarr/testimages:mono-6.8
|
||||
mono610:
|
||||
testName: 'Mono 6.10'
|
||||
artifactName: LinuxTests
|
||||
containerImage: servarr/testimages:mono-6.10
|
||||
mono612:
|
||||
testName: 'Mono 6.12'
|
||||
artifactName: LinuxTests
|
||||
containerImage: servarr/testimages:mono-6.12
|
||||
alpine:
|
||||
testName: 'Musl Net Core'
|
||||
artifactName: LinuxMuslCoreTests
|
||||
containerImage: servarr/testimages:alpine
|
||||
|
||||
pool:
|
||||
vmImage: 'ubuntu-18.04'
|
||||
@@ -442,6 +403,8 @@ stages:
|
||||
timeoutInMinutes: 10
|
||||
|
||||
steps:
|
||||
- bash: mono --version
|
||||
displayName: Check Mono version
|
||||
- task: UseDotNet@2
|
||||
displayName: 'Install .net core'
|
||||
inputs:
|
||||
@@ -451,14 +414,10 @@ stages:
|
||||
displayName: Download Test Artifact
|
||||
inputs:
|
||||
buildType: 'current'
|
||||
artifactName: $(artifactName)
|
||||
artifactName: LinuxTests
|
||||
targetPath: $(testsFolder)
|
||||
- bash: find ${TESTSFOLDER} -name "Radarr.Test.Dummy" -exec chmod a+x {} \;
|
||||
displayName: Make Test Dummy Executable
|
||||
condition: and(succeeded(), ne(variables['osName'], 'Windows'))
|
||||
- bash: |
|
||||
chmod a+x ${TESTSFOLDER}/test.sh
|
||||
ls -lR ${TESTSFOLDER}
|
||||
${TESTSFOLDER}/test.sh Linux Unit Test
|
||||
displayName: Run Tests
|
||||
- task: PublishTestResults@2
|
||||
@@ -566,26 +525,22 @@ stages:
|
||||
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||
strategy:
|
||||
matrix:
|
||||
mono510:
|
||||
testName: 'Mono 5.10'
|
||||
containerImage: servarr/testimages:mono-5.10
|
||||
mono520:
|
||||
testName: 'Mono 5.20'
|
||||
artifactName: LinuxTests
|
||||
containerImage: servarr/testimages:mono-5.20
|
||||
pattern: 'Radarr.**.linux.tar.gz'
|
||||
mono608:
|
||||
testName: 'Mono 6.8'
|
||||
containerImage: servarr/testimages:mono-6.8
|
||||
mono610:
|
||||
testName: 'Mono 6.10'
|
||||
artifactName: LinuxTests
|
||||
containerImage: servarr/testimages:mono-6.10
|
||||
pattern: 'Radarr.**.linux.tar.gz'
|
||||
mono612:
|
||||
testName: 'Mono 6.12'
|
||||
artifactName: LinuxTests
|
||||
containerImage: servarr/testimages:mono-6.12
|
||||
pattern: 'Radarr.**.linux.tar.gz'
|
||||
alpine:
|
||||
testName: 'Musl Net Core'
|
||||
artifactName: LinuxMuslCoreTests
|
||||
containerImage: servarr/testimages:alpine
|
||||
pattern: 'Radarr.**.linux-musl-core-x64.tar.gz'
|
||||
|
||||
variables:
|
||||
pattern: 'Radarr.**.linux.tar.gz'
|
||||
|
||||
pool:
|
||||
vmImage: 'ubuntu-18.04'
|
||||
|
||||
@@ -594,6 +549,8 @@ stages:
|
||||
timeoutInMinutes: 15
|
||||
|
||||
steps:
|
||||
- bash: mono --version
|
||||
displayName: Check Mono version
|
||||
- task: UseDotNet@2
|
||||
displayName: 'Install .net core'
|
||||
inputs:
|
||||
@@ -603,7 +560,7 @@ stages:
|
||||
displayName: Download Test Artifact
|
||||
inputs:
|
||||
buildType: 'current'
|
||||
artifactName: $(artifactName)
|
||||
artifactName: LinuxTests
|
||||
targetPath: $(testsFolder)
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download Build Artifact
|
||||
@@ -648,14 +605,14 @@ stages:
|
||||
failBuild: true
|
||||
Mac:
|
||||
osName: 'Mac'
|
||||
imageName: 'macos-10.14'
|
||||
imageName: 'macos-10.14' # Fails due to firefox not being installed on image
|
||||
pattern: 'Radarr.**.osx-core-x64.tar.gz'
|
||||
failBuild: true
|
||||
failBuild: false
|
||||
Windows:
|
||||
osName: 'Windows'
|
||||
imageName: 'windows-2019'
|
||||
pattern: 'Radarr.**.windows-core-x64.zip'
|
||||
failBuild: true
|
||||
failBuild: $(failOnAutomationFailure)
|
||||
|
||||
pool:
|
||||
vmImage: $(imageName)
|
||||
@@ -688,21 +645,24 @@ stages:
|
||||
mkdir -p ./bin/
|
||||
cp -r -v ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin/Radarr/. ./bin/
|
||||
displayName: Move Package Contents
|
||||
- bash: |
|
||||
if [[ $OSNAME == "Mac" ]]; then
|
||||
url=https://github.com/mozilla/geckodriver/releases/download/v0.26.0/geckodriver-v0.26.0-macos.tar.gz
|
||||
elif [[ $OSNAME == "Linux" ]]; then
|
||||
url=https://github.com/mozilla/geckodriver/releases/download/v0.26.0/geckodriver-v0.26.0-linux64.tar.gz
|
||||
else
|
||||
echo "Unhandled OS"
|
||||
exit 1
|
||||
fi
|
||||
curl -s -L "$url" | tar -xz
|
||||
chmod +x geckodriver
|
||||
mv geckodriver _tests
|
||||
displayName: Install Gecko Driver
|
||||
condition: and(succeeded(), ne(variables['osName'], 'Windows'))
|
||||
- bash: |
|
||||
chmod a+x ${TESTSFOLDER}/test.sh
|
||||
${TESTSFOLDER}/test.sh ${OSNAME} Automation Test
|
||||
displayName: Run Automation Tests
|
||||
- task: CopyFiles@2
|
||||
displayName: 'Copy Screenshot to: $(Build.ArtifactStagingDirectory)'
|
||||
inputs:
|
||||
SourceFolder: '$(Build.SourcesDirectory)'
|
||||
Contents: |
|
||||
**/*_test_screenshot.png
|
||||
TargetFolder: '$(Build.ArtifactStagingDirectory)/screenshots'
|
||||
- publish: $(Build.ArtifactStagingDirectory)/screenshots
|
||||
artifact: '$(osName)AutomationScreenshots'
|
||||
displayName: Publish Screenshot Bundle
|
||||
condition: and(succeeded(), eq(variables['System.JobAttempt'], '1'))
|
||||
displayName: Run Integration Tests
|
||||
- task: PublishTestResults@2
|
||||
inputs:
|
||||
testResultsFormat: 'NUnit'
|
||||
@@ -750,19 +710,10 @@ stages:
|
||||
- checkout: self
|
||||
submodules: true
|
||||
fetchDepth: 1
|
||||
- task: Cache@2
|
||||
inputs:
|
||||
key: 'yarn | "$(osName)" | yarn.lock'
|
||||
restoreKeys: |
|
||||
yarn | "$(osName)"
|
||||
yarn
|
||||
path: $(yarnCacheFolder)
|
||||
displayName: Cache Yarn packages
|
||||
- bash: ./build.sh --lint
|
||||
displayName: Lint Radarr Frontend
|
||||
env:
|
||||
FORCE_COLOR: 0
|
||||
YARN_CACHE_FOLDER: $(yarnCacheFolder)
|
||||
|
||||
- job: Analyze_Frontend
|
||||
displayName: Frontend
|
||||
@@ -851,15 +802,8 @@ stages:
|
||||
- job:
|
||||
displayName: Discord Notification
|
||||
pool:
|
||||
vmImage: 'windows-2019'
|
||||
vmImage: 'ubuntu-18.04'
|
||||
steps:
|
||||
- task: DownloadPipelineArtifact@2
|
||||
continueOnError: true
|
||||
displayName: Download Screenshot Artifact
|
||||
inputs:
|
||||
buildType: 'current'
|
||||
artifactName: 'WindowsAutomationScreenshots'
|
||||
targetPath: $(Build.SourcesDirectory)
|
||||
- checkout: none
|
||||
- powershell: |
|
||||
iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/Servarr/AzureDiscordNotify/master/DiscordNotify.ps1'))
|
||||
|
||||
7
build.sh
7
build.sh
@@ -71,7 +71,7 @@ Build()
|
||||
YarnInstall()
|
||||
{
|
||||
ProgressStart 'yarn install'
|
||||
yarn install --frozen-lockfile --network-timeout 120000
|
||||
yarn install --frozen-lockfile
|
||||
ProgressEnd 'yarn install'
|
||||
}
|
||||
|
||||
@@ -235,7 +235,7 @@ PackageTests()
|
||||
# geckodriver.exe isn't copied by dotnet publish
|
||||
if [ "$runtime" = "win-x64" ];
|
||||
then
|
||||
curl -Lso gecko.zip "https://github.com/mozilla/geckodriver/releases/download/v0.27.0/geckodriver-v0.27.0-win64.zip"
|
||||
curl -Lso gecko.zip "https://github.com/mozilla/geckodriver/releases/download/v0.26.0/geckodriver-v0.26.0-win64.zip"
|
||||
unzip -o gecko.zip
|
||||
cp geckodriver.exe "$testPackageFolder/$framework/win-x64/publish"
|
||||
fi
|
||||
@@ -319,7 +319,6 @@ then
|
||||
then
|
||||
PackageTests "netcoreapp3.1" "win-x64"
|
||||
PackageTests "netcoreapp3.1" "linux-x64"
|
||||
PackageTests "netcoreapp3.1" "linux-musl-x64"
|
||||
PackageTests "netcoreapp3.1" "osx-x64"
|
||||
PackageTests "net462" "linux-x64"
|
||||
else
|
||||
@@ -351,9 +350,7 @@ then
|
||||
then
|
||||
Package "netcoreapp3.1" "win-x64"
|
||||
Package "netcoreapp3.1" "linux-x64"
|
||||
Package "netcoreapp3.1" "linux-musl-x64"
|
||||
Package "netcoreapp3.1" "linux-arm64"
|
||||
Package "netcoreapp3.1" "linux-musl-arm64"
|
||||
Package "netcoreapp3.1" "linux-arm"
|
||||
Package "netcoreapp3.1" "osx-x64"
|
||||
Package "net462" "linux-x64"
|
||||
|
||||
293
frontend/.eslintrc
Normal file
293
frontend/.eslintrc
Normal file
@@ -0,0 +1,293 @@
|
||||
{
|
||||
"parser": "babel-eslint",
|
||||
|
||||
"env": {
|
||||
"browser": true,
|
||||
"commonjs": true,
|
||||
"node": true,
|
||||
"es6": true
|
||||
},
|
||||
|
||||
"globals": {
|
||||
"expect": false,
|
||||
"chai": false,
|
||||
"sinon": false
|
||||
},
|
||||
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6,
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"modules": true,
|
||||
"impliedStrict": true
|
||||
}
|
||||
},
|
||||
|
||||
"plugins": [
|
||||
"filenames",
|
||||
"react"
|
||||
],
|
||||
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "detect"
|
||||
}
|
||||
},
|
||||
|
||||
"rules": {
|
||||
"filenames/match-exported": ["error"],
|
||||
|
||||
# ECMAScript 6
|
||||
|
||||
"arrow-body-style": [0],
|
||||
"arrow-parens": ["error", "always"],
|
||||
"arrow-spacing": ["error", { "before": true, "after": true }],
|
||||
"constructor-super": "error",
|
||||
"generator-star-spacing": "off",
|
||||
"no-class-assign": "error",
|
||||
"no-confusing-arrow": "error",
|
||||
"no-const-assign": "error",
|
||||
"no-dupe-class-members": "error",
|
||||
"no-duplicate-imports": "error",
|
||||
"no-new-symbol": "error",
|
||||
"no-this-before-super": "error",
|
||||
"no-useless-escape": "error",
|
||||
"no-useless-computed-key": "error",
|
||||
"no-useless-constructor": "error",
|
||||
"no-var": "warn",
|
||||
"object-shorthand": ["error", "properties"],
|
||||
"prefer-arrow-callback": "error",
|
||||
"prefer-const": "warn",
|
||||
"prefer-reflect": "off",
|
||||
"prefer-rest-params": "off",
|
||||
"prefer-spread": "warn",
|
||||
"prefer-template": "error",
|
||||
"require-yield": "off",
|
||||
"template-curly-spacing": ["error", "never"],
|
||||
"yield-star-spacing": "off",
|
||||
|
||||
# Possible Errors
|
||||
|
||||
"comma-dangle": "error",
|
||||
"no-cond-assign": "error",
|
||||
"no-console": "off",
|
||||
"no-constant-condition": "warn",
|
||||
"no-control-regex": "error",
|
||||
"no-debugger": "off",
|
||||
"no-dupe-args": "error",
|
||||
"no-dupe-keys": "error",
|
||||
"no-duplicate-case": "error",
|
||||
"no-empty": "warn",
|
||||
"no-empty-character-class": "error",
|
||||
"no-ex-assign": "error",
|
||||
"no-extra-boolean-cast": "error",
|
||||
"no-extra-parens": ["error", "functions"],
|
||||
"no-extra-semi": "error",
|
||||
"no-func-assign": "error",
|
||||
"no-inner-declarations": "error",
|
||||
"no-invalid-regexp": "error",
|
||||
"no-irregular-whitespace": "error",
|
||||
"no-negated-in-lhs": "error",
|
||||
"no-obj-calls": "error",
|
||||
"no-regex-spaces": "error",
|
||||
"no-sparse-arrays": "error",
|
||||
"no-unexpected-multiline": "error",
|
||||
"no-unreachable": "warn",
|
||||
"no-unsafe-finally": "error",
|
||||
"use-isnan": "error",
|
||||
"valid-jsdoc": "off",
|
||||
"valid-typeof": "error",
|
||||
|
||||
# Best Practices
|
||||
|
||||
"accessor-pairs": "off",
|
||||
"array-callback-return": "warn",
|
||||
"block-scoped-var": "warn",
|
||||
"consistent-return": "off",
|
||||
"curly": "error",
|
||||
"default-case": "error",
|
||||
"dot-location": ["error", "property"],
|
||||
"dot-notation": "error",
|
||||
"eqeqeq": ["error", "smart"],
|
||||
"guard-for-in": "error",
|
||||
"no-alert": "warn",
|
||||
"no-caller": "error",
|
||||
"no-case-declarations": "error",
|
||||
"no-div-regex": "error",
|
||||
"no-else-return": "error",
|
||||
"no-empty-function": ["error", {"allow": ["arrowFunctions"]}],
|
||||
"no-empty-pattern": "error",
|
||||
"no-eval": "error",
|
||||
"no-extend-native": "error",
|
||||
"no-extra-bind": "error",
|
||||
"no-fallthrough": "error",
|
||||
"no-floating-decimal": "error",
|
||||
"no-implicit-coercion": ["error", {
|
||||
"boolean": false,
|
||||
"number": true,
|
||||
"string": true,
|
||||
"allow": [/* "!!", "~", "*", "+" */]
|
||||
}],
|
||||
"no-implicit-globals": "error",
|
||||
"no-implied-eval": "error",
|
||||
"no-invalid-this": "off",
|
||||
"no-iterator": "error",
|
||||
"no-labels": "error",
|
||||
"no-lone-blocks": "error",
|
||||
"no-loop-func": "error",
|
||||
"no-magic-numbers": ["off", {"ignoreArrayIndexes": true, "ignore": [0, 1] }],
|
||||
"no-multi-spaces": "error",
|
||||
"no-multi-str": "error",
|
||||
"no-native-reassign": ["error", {"exceptions": ["console"]}],
|
||||
"no-new": "off",
|
||||
"no-new-func": "error",
|
||||
"no-new-wrappers": "error",
|
||||
"no-octal": "error",
|
||||
"no-octal-escape": "error",
|
||||
"no-param-reassign": "off",
|
||||
"no-process-env": "off",
|
||||
"no-proto": "error",
|
||||
"no-redeclare": "error",
|
||||
"no-return-assign": "warn",
|
||||
"no-script-url": "error",
|
||||
"no-self-assign": "error",
|
||||
"no-self-compare": "error",
|
||||
"no-sequences": "error",
|
||||
"no-throw-literal": "error",
|
||||
"no-unmodified-loop-condition": "error",
|
||||
"no-unused-expressions": "error",
|
||||
"no-unused-labels": "error",
|
||||
"no-useless-call": "error",
|
||||
"no-useless-concat": "error",
|
||||
"no-void": "error",
|
||||
"no-warning-comments": "off",
|
||||
"no-with": "error",
|
||||
"radix": ["error", "as-needed"],
|
||||
"vars-on-top": "off",
|
||||
"wrap-iife": ["error", "inside"],
|
||||
"yoda": "error",
|
||||
|
||||
# Strict Mode
|
||||
|
||||
"strict": ["error", "never"],
|
||||
|
||||
# Variables
|
||||
|
||||
"init-declarations": ["error", "always"],
|
||||
"no-catch-shadow": "error",
|
||||
"no-delete-var": "error",
|
||||
"no-label-var": "error",
|
||||
"no-restricted-globals": "off",
|
||||
"no-shadow": "error",
|
||||
"no-shadow-restricted-names": "error",
|
||||
"no-undef": "error",
|
||||
"no-undef-init": "off",
|
||||
"no-undefined": "off",
|
||||
"no-unused-vars": ["error", { "args": "none", "ignoreRestSiblings": true }],
|
||||
"no-use-before-define": "error",
|
||||
|
||||
# Node.js and CommonJS
|
||||
|
||||
"callback-return": "warn",
|
||||
"global-require": "error",
|
||||
"handle-callback-err": "warn",
|
||||
"no-mixed-requires": "error",
|
||||
"no-new-require": "error",
|
||||
"no-path-concat": "error",
|
||||
"no-process-exit": "error",
|
||||
|
||||
# Stylistic Issues
|
||||
|
||||
"array-bracket-spacing": ["error", "never"],
|
||||
"block-spacing": ["error", "always"],
|
||||
"brace-style": ["error", "1tbs", { "allowSingleLine": false }],
|
||||
"camelcase": "off",
|
||||
"comma-spacing": ["error", {"before": false, "after": true}],
|
||||
"comma-style": ["error", "last"],
|
||||
"computed-property-spacing": ["error", "never"],
|
||||
"consistent-this": ["error", "self"],
|
||||
"eol-last": "error",
|
||||
"func-names": "off",
|
||||
"func-style": ["error", "declaration"],
|
||||
"indent": ["error", 2, {"SwitchCase": 1}],
|
||||
"key-spacing": ["error", {"beforeColon": false, "afterColon": true}],
|
||||
"keyword-spacing": ["error", { "before": true, "after": true}],
|
||||
"lines-around-comment": ["error", { "beforeBlockComment": true, "afterBlockComment": false }],
|
||||
"max-depth": ["error", {"maximum": 5}],
|
||||
"max-nested-callbacks": ["error", 4],
|
||||
"max-statements": "off",
|
||||
"max-statements-per-line": ["error", { "max": 1 }],
|
||||
"new-cap": ["error", {"capIsNewExceptions": ["$.Deferred", "DragDropContext", "DragLayer", "DragSource", "DropTarget"]}],
|
||||
"new-parens": "error",
|
||||
"newline-after-var": "off",
|
||||
"newline-before-return": "off",
|
||||
"newline-per-chained-call": "off",
|
||||
"no-array-constructor": "error",
|
||||
"no-bitwise": "error",
|
||||
"no-continue": "error",
|
||||
"no-inline-comments": "off",
|
||||
"no-lonely-if": "warn",
|
||||
"no-mixed-spaces-and-tabs": "error",
|
||||
"no-multiple-empty-lines": ["error", { "max": 1 }],
|
||||
"no-negated-condition": "warn",
|
||||
"no-nested-ternary": "error",
|
||||
"no-new-object": "error",
|
||||
"no-plusplus": "off",
|
||||
"no-restricted-syntax": "off",
|
||||
"no-spaced-func": "error",
|
||||
"no-ternary": "off",
|
||||
"no-trailing-spaces": "error",
|
||||
"no-underscore-dangle": ["error", { "allowAfterThis": true }],
|
||||
"no-unneeded-ternary": "error",
|
||||
"no-whitespace-before-property": "error",
|
||||
"object-curly-spacing": ["error", "always"],
|
||||
"one-var": ["error", "never"],
|
||||
"one-var-declaration-per-line": ["error", "always"],
|
||||
"operator-assignment": ["off", "never"],
|
||||
"operator-linebreak": ["error", "after"],
|
||||
"quote-props": ["error", "as-needed"],
|
||||
"quotes": ["error", "single"],
|
||||
"require-jsdoc": "off",
|
||||
"semi": "error",
|
||||
"semi-spacing": ["error", { "before": false, "after": true }],
|
||||
"sort-vars": "off",
|
||||
"space-before-blocks": ["error", "always"],
|
||||
"space-before-function-paren": ["error", "never"],
|
||||
"space-in-parens": "off",
|
||||
"space-infix-ops": "off",
|
||||
"space-unary-ops": "off",
|
||||
"spaced-comment": "error",
|
||||
"wrap-regex": "error",
|
||||
|
||||
# React
|
||||
|
||||
"react/jsx-boolean-value": [2, "always"],
|
||||
"react/jsx-uses-vars": 2,
|
||||
"react/jsx-closing-bracket-location": 2,
|
||||
"react/jsx-tag-spacing": ["error"],
|
||||
"react/jsx-curly-spacing": [2, "never"],
|
||||
"react/jsx-equals-spacing": [2, "never"],
|
||||
"react/jsx-indent-props": [2, 2],
|
||||
"react/jsx-indent": [2, 2, { "indentLogicalExpressions": true }],
|
||||
"react/jsx-key": 2,
|
||||
"react/jsx-no-bind": [2, { "allowArrowFunctions": true }],
|
||||
"react/jsx-no-duplicate-props": [2, { "ignoreCase": true }],
|
||||
"react/jsx-max-props-per-line": [2, { "maximum": 2 }],
|
||||
"react/jsx-handler-names": [2, { "eventHandlerPrefix": "(on|dispatch)", "eventHandlerPropPrefix": "on" }],
|
||||
"react/jsx-no-undef": 2,
|
||||
"react/jsx-pascal-case": 2,
|
||||
"react/jsx-uses-react": 2,
|
||||
// Explicitly disabled in case we want to enable them again
|
||||
"react/no-did-mount-set-state": 0,
|
||||
"react/no-did-update-set-state": 0,
|
||||
"react/no-direct-mutation-state": 2,
|
||||
"react/no-multi-comp": [2, { "ignoreStateless": true }],
|
||||
"react/no-unknown-property": 2,
|
||||
"react/prefer-es6-class": 2,
|
||||
"react/prop-types": 2,
|
||||
"react/react-in-jsx-scope": 2,
|
||||
"react/self-closing-comp": 2,
|
||||
"react/sort-comp": 2,
|
||||
"react/jsx-wrap-multilines": 2
|
||||
}
|
||||
}
|
||||
@@ -1,327 +0,0 @@
|
||||
const fs = require('fs');
|
||||
|
||||
const dirs = fs
|
||||
.readdirSync('frontend/src', { withFileTypes: true })
|
||||
.filter((dirent) => dirent.isDirectory())
|
||||
.map((dirent) => dirent.name)
|
||||
.join('|');
|
||||
|
||||
module.exports = {
|
||||
parser: 'babel-eslint',
|
||||
|
||||
env: {
|
||||
browser: true,
|
||||
commonjs: true,
|
||||
node: true,
|
||||
es6: true
|
||||
},
|
||||
|
||||
globals: {
|
||||
expect: false,
|
||||
chai: false,
|
||||
sinon: false
|
||||
},
|
||||
|
||||
parserOptions: {
|
||||
ecmaVersion: 6,
|
||||
sourceType: 'module',
|
||||
ecmaFeatures: {
|
||||
modules: true,
|
||||
impliedStrict: true
|
||||
}
|
||||
},
|
||||
|
||||
plugins: [
|
||||
'filenames',
|
||||
'react',
|
||||
'simple-import-sort',
|
||||
'import'
|
||||
],
|
||||
|
||||
settings: {
|
||||
react: {
|
||||
version: 'detect'
|
||||
}
|
||||
},
|
||||
|
||||
rules: {
|
||||
'filenames/match-exported': ['error'],
|
||||
|
||||
// ECMAScript 6
|
||||
|
||||
'arrow-body-style': [0],
|
||||
'arrow-parens': ['error', 'always'],
|
||||
'arrow-spacing': ['error', { before: true, after: true }],
|
||||
'constructor-super': 'error',
|
||||
'generator-star-spacing': 'off',
|
||||
'no-class-assign': 'error',
|
||||
'no-confusing-arrow': 'error',
|
||||
'no-const-assign': 'error',
|
||||
'no-dupe-class-members': 'error',
|
||||
'no-duplicate-imports': 'error',
|
||||
'no-new-symbol': 'error',
|
||||
'no-this-before-super': 'error',
|
||||
'no-useless-escape': 'error',
|
||||
'no-useless-computed-key': 'error',
|
||||
'no-useless-constructor': 'error',
|
||||
'no-var': 'warn',
|
||||
'object-shorthand': ['error', 'properties'],
|
||||
'prefer-arrow-callback': 'error',
|
||||
'prefer-const': 'warn',
|
||||
'prefer-reflect': 'off',
|
||||
'prefer-rest-params': 'off',
|
||||
'prefer-spread': 'warn',
|
||||
'prefer-template': 'error',
|
||||
'require-yield': 'off',
|
||||
'template-curly-spacing': ['error', 'never'],
|
||||
'yield-star-spacing': 'off',
|
||||
|
||||
// Possible Errors
|
||||
|
||||
'comma-dangle': 'error',
|
||||
'no-cond-assign': 'error',
|
||||
'no-console': 'off',
|
||||
'no-constant-condition': 'warn',
|
||||
'no-control-regex': 'error',
|
||||
'no-debugger': 'off',
|
||||
'no-dupe-args': 'error',
|
||||
'no-dupe-keys': 'error',
|
||||
'no-duplicate-case': 'error',
|
||||
'no-empty': 'warn',
|
||||
'no-empty-character-class': 'error',
|
||||
'no-ex-assign': 'error',
|
||||
'no-extra-boolean-cast': 'error',
|
||||
'no-extra-parens': ['error', 'functions'],
|
||||
'no-extra-semi': 'error',
|
||||
'no-func-assign': 'error',
|
||||
'no-inner-declarations': 'error',
|
||||
'no-invalid-regexp': 'error',
|
||||
'no-irregular-whitespace': 'error',
|
||||
'no-negated-in-lhs': 'error',
|
||||
'no-obj-calls': 'error',
|
||||
'no-regex-spaces': 'error',
|
||||
'no-sparse-arrays': 'error',
|
||||
'no-unexpected-multiline': 'error',
|
||||
'no-unreachable': 'warn',
|
||||
'no-unsafe-finally': 'error',
|
||||
'use-isnan': 'error',
|
||||
'valid-jsdoc': 'off',
|
||||
'valid-typeof': 'error',
|
||||
|
||||
// Best Practices
|
||||
|
||||
'accessor-pairs': 'off',
|
||||
'array-callback-return': 'warn',
|
||||
'block-scoped-var': 'warn',
|
||||
'consistent-return': 'off',
|
||||
curly: 'error',
|
||||
'default-case': 'error',
|
||||
'dot-location': ['error', 'property'],
|
||||
'dot-notation': 'error',
|
||||
eqeqeq: ['error', 'smart'],
|
||||
'guard-for-in': 'error',
|
||||
'no-alert': 'warn',
|
||||
'no-caller': 'error',
|
||||
'no-case-declarations': 'error',
|
||||
'no-div-regex': 'error',
|
||||
'no-else-return': 'error',
|
||||
'no-empty-function': ['error', { allow: ['arrowFunctions'] }],
|
||||
'no-empty-pattern': 'error',
|
||||
'no-eval': 'error',
|
||||
'no-extend-native': 'error',
|
||||
'no-extra-bind': 'error',
|
||||
'no-fallthrough': 'error',
|
||||
'no-floating-decimal': 'error',
|
||||
'no-implicit-coercion': ['error', {
|
||||
boolean: false,
|
||||
number: true,
|
||||
string: true,
|
||||
allow: [/* "!!", "~", "*", "+" */]
|
||||
}],
|
||||
'no-implicit-globals': 'error',
|
||||
'no-implied-eval': 'error',
|
||||
'no-invalid-this': 'off',
|
||||
'no-iterator': 'error',
|
||||
'no-labels': 'error',
|
||||
'no-lone-blocks': 'error',
|
||||
'no-loop-func': 'error',
|
||||
'no-magic-numbers': ['off', { ignoreArrayIndexes: true, ignore: [0, 1] }],
|
||||
'no-multi-spaces': 'error',
|
||||
'no-multi-str': 'error',
|
||||
'no-native-reassign': ['error', { exceptions: ['console'] }],
|
||||
'no-new': 'off',
|
||||
'no-new-func': 'error',
|
||||
'no-new-wrappers': 'error',
|
||||
'no-octal': 'error',
|
||||
'no-octal-escape': 'error',
|
||||
'no-param-reassign': 'off',
|
||||
'no-process-env': 'off',
|
||||
'no-proto': 'error',
|
||||
'no-redeclare': 'error',
|
||||
'no-return-assign': 'warn',
|
||||
'no-script-url': 'error',
|
||||
'no-self-assign': 'error',
|
||||
'no-self-compare': 'error',
|
||||
'no-sequences': 'error',
|
||||
'no-throw-literal': 'error',
|
||||
'no-unmodified-loop-condition': 'error',
|
||||
'no-unused-expressions': 'error',
|
||||
'no-unused-labels': 'error',
|
||||
'no-useless-call': 'error',
|
||||
'no-useless-concat': 'error',
|
||||
'no-void': 'error',
|
||||
'no-warning-comments': 'off',
|
||||
'no-with': 'error',
|
||||
radix: ['error', 'as-needed'],
|
||||
'vars-on-top': 'off',
|
||||
'wrap-iife': ['error', 'inside'],
|
||||
yoda: 'error',
|
||||
|
||||
// Strict Mode
|
||||
|
||||
strict: ['error', 'never'],
|
||||
|
||||
// Variables
|
||||
|
||||
'init-declarations': ['error', 'always'],
|
||||
'no-catch-shadow': 'error',
|
||||
'no-delete-var': 'error',
|
||||
'no-label-var': 'error',
|
||||
'no-restricted-globals': 'off',
|
||||
'no-shadow': 'error',
|
||||
'no-shadow-restricted-names': 'error',
|
||||
'no-undef': 'error',
|
||||
'no-undef-init': 'off',
|
||||
'no-undefined': 'off',
|
||||
'no-unused-vars': ['error', { args: 'none', ignoreRestSiblings: true }],
|
||||
'no-use-before-define': 'error',
|
||||
|
||||
// Node.js and CommonJS
|
||||
|
||||
'callback-return': 'warn',
|
||||
'global-require': 'error',
|
||||
'handle-callback-err': 'warn',
|
||||
'no-mixed-requires': 'error',
|
||||
'no-new-require': 'error',
|
||||
'no-path-concat': 'error',
|
||||
'no-process-exit': 'error',
|
||||
|
||||
// Stylistic Issues
|
||||
|
||||
'array-bracket-spacing': ['error', 'never'],
|
||||
'block-spacing': ['error', 'always'],
|
||||
'brace-style': ['error', '1tbs', { allowSingleLine: false }],
|
||||
camelcase: 'off',
|
||||
'comma-spacing': ['error', { before: false, after: true }],
|
||||
'comma-style': ['error', 'last'],
|
||||
'computed-property-spacing': ['error', 'never'],
|
||||
'consistent-this': ['error', 'self'],
|
||||
'eol-last': 'error',
|
||||
'func-names': 'off',
|
||||
'func-style': ['error', 'declaration'],
|
||||
indent: ['error', 2, { SwitchCase: 1 }],
|
||||
'key-spacing': ['error', { beforeColon: false, afterColon: true }],
|
||||
'keyword-spacing': ['error', { before: true, after: true }],
|
||||
'lines-around-comment': ['error', { beforeBlockComment: true, afterBlockComment: false }],
|
||||
'max-depth': ['error', { maximum: 5 }],
|
||||
'max-nested-callbacks': ['error', 4],
|
||||
'max-statements': 'off',
|
||||
'max-statements-per-line': ['error', { max: 1 }],
|
||||
'new-cap': ['error', { capIsNewExceptions: ['$.Deferred', 'DragDropContext', 'DragLayer', 'DragSource', 'DropTarget'] }],
|
||||
'new-parens': 'error',
|
||||
'newline-after-var': 'off',
|
||||
'newline-before-return': 'off',
|
||||
'newline-per-chained-call': 'off',
|
||||
'no-array-constructor': 'error',
|
||||
'no-bitwise': 'error',
|
||||
'no-continue': 'error',
|
||||
'no-inline-comments': 'off',
|
||||
'no-lonely-if': 'warn',
|
||||
'no-mixed-spaces-and-tabs': 'error',
|
||||
'no-multiple-empty-lines': ['error', { max: 1 }],
|
||||
'no-negated-condition': 'warn',
|
||||
'no-nested-ternary': 'error',
|
||||
'no-new-object': 'error',
|
||||
'no-plusplus': 'off',
|
||||
'no-restricted-syntax': 'off',
|
||||
'no-spaced-func': 'error',
|
||||
'no-ternary': 'off',
|
||||
'no-trailing-spaces': 'error',
|
||||
'no-underscore-dangle': ['error', { allowAfterThis: true }],
|
||||
'no-unneeded-ternary': 'error',
|
||||
'no-whitespace-before-property': 'error',
|
||||
'object-curly-spacing': ['error', 'always'],
|
||||
'one-var': ['error', 'never'],
|
||||
'one-var-declaration-per-line': ['error', 'always'],
|
||||
'operator-assignment': ['off', 'never'],
|
||||
'operator-linebreak': ['error', 'after'],
|
||||
'quote-props': ['error', 'as-needed'],
|
||||
quotes: ['error', 'single'],
|
||||
'require-jsdoc': 'off',
|
||||
semi: 'error',
|
||||
'semi-spacing': ['error', { before: false, after: true }],
|
||||
'sort-vars': 'off',
|
||||
'space-before-blocks': ['error', 'always'],
|
||||
'space-before-function-paren': ['error', 'never'],
|
||||
'space-in-parens': 'off',
|
||||
'space-infix-ops': 'off',
|
||||
'space-unary-ops': 'off',
|
||||
'spaced-comment': 'error',
|
||||
'wrap-regex': 'error',
|
||||
|
||||
// ImportSort
|
||||
|
||||
'simple-import-sort/sort': 'error',
|
||||
'import/newline-after-import': 'error',
|
||||
|
||||
// React
|
||||
|
||||
'react/jsx-boolean-value': [2, 'always'],
|
||||
'react/jsx-uses-vars': 2,
|
||||
'react/jsx-closing-bracket-location': 2,
|
||||
'react/jsx-tag-spacing': ['error'],
|
||||
'react/jsx-curly-spacing': [2, 'never'],
|
||||
'react/jsx-equals-spacing': [2, 'never'],
|
||||
'react/jsx-indent-props': [2, 2],
|
||||
'react/jsx-indent': [2, 2, { indentLogicalExpressions: true }],
|
||||
'react/jsx-key': 2,
|
||||
'react/jsx-no-bind': [2, { allowArrowFunctions: true }],
|
||||
'react/jsx-no-duplicate-props': [2, { ignoreCase: true }],
|
||||
'react/jsx-max-props-per-line': [2, { maximum: 2 }],
|
||||
'react/jsx-handler-names': [2, { eventHandlerPrefix: '(on|dispatch)', eventHandlerPropPrefix: 'on' }],
|
||||
'react/jsx-no-undef': 2,
|
||||
'react/jsx-pascal-case': 2,
|
||||
'react/jsx-uses-react': 2,
|
||||
// Explicitly disabled in case we want to enable them again
|
||||
'react/no-did-mount-set-state': 0,
|
||||
'react/no-did-update-set-state': 0,
|
||||
'react/no-direct-mutation-state': 2,
|
||||
'react/no-multi-comp': [2, { ignoreStateless: true }],
|
||||
'react/no-unknown-property': 2,
|
||||
'react/prefer-es6-class': 2,
|
||||
'react/prop-types': 2,
|
||||
'react/react-in-jsx-scope': 2,
|
||||
'react/self-closing-comp': 2,
|
||||
'react/sort-comp': 2,
|
||||
'react/jsx-wrap-multilines': 2
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.js'],
|
||||
rules: {
|
||||
'simple-import-sort/sort': [
|
||||
'error',
|
||||
{
|
||||
groups: [
|
||||
// Packages
|
||||
// Absolute Paths
|
||||
// Relative Paths
|
||||
// Css
|
||||
['^@?\\w', `^(${dirs})(/.*|$)`, '^\\.', '^\\..*css$']
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -24,7 +24,7 @@
|
||||
"ignoreAtRules": [
|
||||
"/^add\\-mixin$/",
|
||||
"/^define\\-mixin$/"
|
||||
]
|
||||
]
|
||||
}
|
||||
],
|
||||
"at-rule-no-vendor-prefix": true,
|
||||
@@ -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",
|
||||
|
||||
@@ -10,7 +10,8 @@ gulp.task('build',
|
||||
'webpack',
|
||||
'copyHtml',
|
||||
'copyFonts',
|
||||
'copyImages'
|
||||
'copyImages',
|
||||
'copyJs'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
@@ -5,6 +5,17 @@ const cache = require('gulp-cached');
|
||||
const livereload = require('gulp-livereload');
|
||||
const paths = require('./helpers/paths.js');
|
||||
|
||||
gulp.task('copyJs', () => {
|
||||
return gulp.src(
|
||||
[
|
||||
path.join(paths.src.root, 'polyfills.js')
|
||||
], { base: paths.src.root })
|
||||
.pipe(cache('copyJs'))
|
||||
.pipe(print())
|
||||
.pipe(gulp.dest(paths.dest.root))
|
||||
.pipe(livereload());
|
||||
});
|
||||
|
||||
gulp.task('copyHtml', () => {
|
||||
return gulp.src(paths.src.html, { base: paths.src.root })
|
||||
.pipe(cache('copyHtml'))
|
||||
|
||||
@@ -4,10 +4,8 @@ const livereload = require('gulp-livereload');
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const errorHandler = require('./helpers/errorHandler');
|
||||
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
|
||||
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';
|
||||
@@ -15,7 +13,6 @@ 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);
|
||||
|
||||
@@ -33,19 +30,14 @@ const cssVarsFiles = [
|
||||
].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 head = assetTags.head.map((v) => {
|
||||
v.attributes = { rel: 'stylesheet', type: 'text/css', href: `/${v.attributes.href.replace('\\', '/')}` };
|
||||
return this.createHtmlTag(v);
|
||||
});
|
||||
const body = assetTags.bodyTags.map((v) => {
|
||||
const body = assetTags.body.map((v) => {
|
||||
v.attributes = { src: `/${v.attributes.src}` };
|
||||
return HtmlWebpackPluginHtmlTags.htmlTagObjectToString(v, this.options.xhtml);
|
||||
return this.createHtmlTag(v);
|
||||
});
|
||||
|
||||
return html
|
||||
@@ -54,8 +46,6 @@ HtmlWebpackPlugin.prototype.injectAssetsIntoHtml = function(html, assets, assetT
|
||||
};
|
||||
|
||||
const plugins = [
|
||||
new OptimizeCssAssetsPlugin({}),
|
||||
|
||||
new webpack.DefinePlugin({
|
||||
__DEV__: !isProduction,
|
||||
'process.env.NODE_ENV': isProduction ? JSON.stringify('production') : JSON.stringify('development')
|
||||
@@ -131,8 +121,7 @@ const config = {
|
||||
use: {
|
||||
loader: 'worker-loader',
|
||||
options: {
|
||||
filename: '[name].js',
|
||||
inline: inlineWebWorkers
|
||||
name: '[name].js'
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -262,7 +251,7 @@ gulp.task('webpack', () => {
|
||||
gulp.task('webpackWatch', () => {
|
||||
config.watch = true;
|
||||
|
||||
return webpackStream(config, webpack)
|
||||
return webpackStream(config)
|
||||
.on('error', errorHandler)
|
||||
.pipe(gulp.dest('_output/UI'))
|
||||
.on('error', errorHandler)
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es6",
|
||||
"checkJs": false,
|
||||
"baseUrl": "src",
|
||||
"jsx": "react",
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"paths": {
|
||||
"*": [
|
||||
"*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"./src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
]
|
||||
}
|
||||
@@ -1,94 +1,20 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { align, icons } from 'Helpers/Props';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
|
||||
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
||||
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 { align, icons, kinds } from 'Helpers/Props';
|
||||
import getRemovedItems from 'Utilities/Object/getRemovedItems';
|
||||
import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
|
||||
import translate from 'Utilities/String/translate';
|
||||
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 PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBodyConnector from 'Components/Page/PageContentBodyConnector';
|
||||
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 {
|
||||
|
||||
//
|
||||
// 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
|
||||
|
||||
@@ -100,35 +26,17 @@ class Blacklist extends Component {
|
||||
items,
|
||||
columns,
|
||||
totalRecords,
|
||||
isRemoving,
|
||||
isClearingBlacklistExecuting,
|
||||
onClearBlacklistPress,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
allSelected,
|
||||
allUnselected,
|
||||
selectedState,
|
||||
isConfirmRemoveModalOpen
|
||||
} = this.state;
|
||||
|
||||
const selectedIds = this.getSelectedIds();
|
||||
|
||||
return (
|
||||
<PageContent title={translate('Blacklist')}>
|
||||
<PageContent title="Blacklist">
|
||||
<PageToolbar>
|
||||
<PageToolbarSection>
|
||||
<PageToolbarButton
|
||||
label="Remove Selected"
|
||||
iconName={icons.REMOVE}
|
||||
isDisabled={!selectedIds.length}
|
||||
isSpinning={isRemoving}
|
||||
onPress={this.onRemoveSelectedPress}
|
||||
/>
|
||||
|
||||
<PageToolbarButton
|
||||
label={translate('Clear')}
|
||||
label="Clear"
|
||||
iconName={icons.CLEAR}
|
||||
isSpinning={isClearingBlacklistExecuting}
|
||||
onPress={onClearBlacklistPress}
|
||||
@@ -141,14 +49,14 @@ class Blacklist extends Component {
|
||||
columns={columns}
|
||||
>
|
||||
<PageToolbarButton
|
||||
label={translate('Options')}
|
||||
label="Options"
|
||||
iconName={icons.TABLE}
|
||||
/>
|
||||
</TableOptionsModalWrapper>
|
||||
</PageToolbarSection>
|
||||
</PageToolbar>
|
||||
|
||||
<PageContentBody>
|
||||
<PageContentBodyConnector>
|
||||
{
|
||||
isFetching && !isPopulated &&
|
||||
<LoadingIndicator />
|
||||
@@ -156,15 +64,13 @@ class Blacklist extends Component {
|
||||
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
<div>
|
||||
{translate('UnableToLoadBlacklist')}
|
||||
</div>
|
||||
<div>Unable to load blacklist</div>
|
||||
}
|
||||
|
||||
{
|
||||
isPopulated && !error && !items.length &&
|
||||
<div>
|
||||
{translate('NoHistory')}
|
||||
No history blacklist
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -172,12 +78,8 @@ class Blacklist extends Component {
|
||||
isPopulated && !error && !!items.length &&
|
||||
<div>
|
||||
<Table
|
||||
selectAll={true}
|
||||
allSelected={allSelected}
|
||||
allUnselected={allUnselected}
|
||||
columns={columns}
|
||||
{...otherProps}
|
||||
onSelectAllChange={this.onSelectAllChange}
|
||||
>
|
||||
<TableBody>
|
||||
{
|
||||
@@ -185,10 +87,8 @@ class Blacklist extends Component {
|
||||
return (
|
||||
<BlacklistRowConnector
|
||||
key={item.id}
|
||||
isSelected={selectedState[item.id] || false}
|
||||
columns={columns}
|
||||
{...item}
|
||||
onSelectedChange={this.onSelectedChange}
|
||||
/>
|
||||
);
|
||||
})
|
||||
@@ -203,17 +103,7 @@ class Blacklist extends Component {
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</PageContentBody>
|
||||
|
||||
<ConfirmModal
|
||||
isOpen={isConfirmRemoveModalOpen}
|
||||
kind={kinds.DANGER}
|
||||
title="Remove Selected"
|
||||
message={'Are you sure you want to remove the selected items from the blacklist?'}
|
||||
confirmLabel="Remove Selected"
|
||||
onConfirm={this.onRemoveSelectedConfirmed}
|
||||
onCancel={this.onConfirmRemoveModalClose}
|
||||
/>
|
||||
</PageContentBodyConnector>
|
||||
</PageContent>
|
||||
);
|
||||
}
|
||||
@@ -226,9 +116,7 @@ Blacklist.propTypes = {
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
totalRecords: PropTypes.number,
|
||||
isRemoving: PropTypes.bool.isRequired,
|
||||
isClearingBlacklistExecuting: PropTypes.bool.isRequired,
|
||||
onRemoveSelected: PropTypes.func.isRequired,
|
||||
onClearBlacklistPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
|
||||
@@ -2,12 +2,12 @@ import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import * as commandNames from 'Commands/commandNames';
|
||||
import { 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 createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
|
||||
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
|
||||
import * as commandNames from 'Commands/commandNames';
|
||||
import Blacklist from './Blacklist';
|
||||
|
||||
function createMapStateToProps() {
|
||||
@@ -89,10 +89,6 @@ class BlacklistConnector extends Component {
|
||||
this.props.gotoBlacklistPage({ page });
|
||||
}
|
||||
|
||||
onRemoveSelected = (ids) => {
|
||||
this.props.removeBlacklistItems({ ids });
|
||||
}
|
||||
|
||||
onSortPress = (sortKey) => {
|
||||
this.props.setBlacklistSort({ sortKey });
|
||||
}
|
||||
@@ -128,7 +124,6 @@ class BlacklistConnector extends Component {
|
||||
onNextPagePress={this.onNextPagePress}
|
||||
onLastPagePress={this.onLastPagePress}
|
||||
onPageSelect={this.onPageSelect}
|
||||
onRemoveSelected={this.onRemoveSelected}
|
||||
onSortPress={this.onSortPress}
|
||||
onTableOptionChange={this.onTableOptionChange}
|
||||
onClearBlacklistPress={this.onClearBlacklistPress}
|
||||
@@ -148,7 +143,6 @@ BlacklistConnector.propTypes = {
|
||||
gotoBlacklistNextPage: PropTypes.func.isRequired,
|
||||
gotoBlacklistLastPage: PropTypes.func.isRequired,
|
||||
gotoBlacklistPage: PropTypes.func.isRequired,
|
||||
removeBlacklistItems: PropTypes.func.isRequired,
|
||||
setBlacklistSort: PropTypes.func.isRequired,
|
||||
setBlacklistTableOption: PropTypes.func.isRequired,
|
||||
clearBlacklist: PropTypes.func.isRequired,
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Button from 'Components/Link/Button';
|
||||
import DescriptionList from 'Components/DescriptionList/DescriptionList';
|
||||
import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem';
|
||||
import Button from 'Components/Link/Button';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
|
||||
class BlacklistDetailsModal extends Component {
|
||||
|
||||
@@ -40,19 +39,19 @@ class BlacklistDetailsModal extends Component {
|
||||
<ModalBody>
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
title={translate('Name')}
|
||||
title="Name"
|
||||
data={sourceTitle}
|
||||
/>
|
||||
|
||||
<DescriptionListItem
|
||||
title={translate('Protocol')}
|
||||
title="Protocol"
|
||||
data={protocol}
|
||||
/>
|
||||
|
||||
{
|
||||
!!message &&
|
||||
<DescriptionListItem
|
||||
title={translate('Indexer')}
|
||||
title="Indexer"
|
||||
data={indexer}
|
||||
/>
|
||||
}
|
||||
@@ -60,7 +59,7 @@ class BlacklistDetailsModal extends Component {
|
||||
{
|
||||
!!message &&
|
||||
<DescriptionListItem
|
||||
title={translate('Message')}
|
||||
title="Message"
|
||||
data={message}
|
||||
/>
|
||||
}
|
||||
@@ -69,7 +68,7 @@ class BlacklistDetailsModal extends Component {
|
||||
|
||||
<ModalFooter>
|
||||
<Button onPress={onModalClose}>
|
||||
{translate('Close')}
|
||||
Close
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { icons, kinds } from 'Helpers/Props';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
|
||||
import TableRow from 'Components/Table/TableRow';
|
||||
import { icons, kinds } from 'Helpers/Props';
|
||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import MovieQuality from 'Movie/MovieQuality';
|
||||
import MovieFormats from 'Movie/MovieFormats';
|
||||
import MovieLanguage from 'Movie/MovieLanguage';
|
||||
import MovieQuality from 'Movie/MovieQuality';
|
||||
import MovieTitleLink from 'Movie/MovieTitleLink';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import BlacklistDetailsModal from './BlacklistDetailsModal';
|
||||
import styles from './BlacklistRow.css';
|
||||
|
||||
@@ -43,7 +41,6 @@ class BlacklistRow extends Component {
|
||||
|
||||
render() {
|
||||
const {
|
||||
id,
|
||||
movie,
|
||||
sourceTitle,
|
||||
quality,
|
||||
@@ -53,9 +50,7 @@ class BlacklistRow extends Component {
|
||||
protocol,
|
||||
indexer,
|
||||
message,
|
||||
isSelected,
|
||||
columns,
|
||||
onSelectedChange,
|
||||
onRemovePress
|
||||
} = this.props;
|
||||
|
||||
@@ -65,12 +60,6 @@ class BlacklistRow extends Component {
|
||||
|
||||
return (
|
||||
<TableRow>
|
||||
<TableSelectCell
|
||||
id={id}
|
||||
isSelected={isSelected}
|
||||
onSelectedChange={onSelectedChange}
|
||||
/>
|
||||
|
||||
{
|
||||
columns.map((column) => {
|
||||
const {
|
||||
@@ -166,7 +155,7 @@ class BlacklistRow extends Component {
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
title={translate('RemoveFromBlacklist')}
|
||||
title="Remove from blacklist"
|
||||
name={icons.REMOVE}
|
||||
kind={kinds.DANGER}
|
||||
onPress={onRemovePress}
|
||||
@@ -204,9 +193,7 @@ BlacklistRow.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
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { removeBlacklistItem } from 'Store/Actions/blacklistActions';
|
||||
import { removeFromBlacklist } from 'Store/Actions/blacklistActions';
|
||||
import createMovieSelector from 'Store/Selectors/createMovieSelector';
|
||||
import BlacklistRow from './BlacklistRow';
|
||||
|
||||
@@ -18,7 +18,7 @@ function createMapStateToProps() {
|
||||
function createMapDispatchToProps(dispatch, props) {
|
||||
return {
|
||||
onRemovePress() {
|
||||
dispatch(removeBlacklistItem({ id: props.id }));
|
||||
dispatch(removeFromBlacklist({ id: props.id }));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import DescriptionList from 'Components/DescriptionList/DescriptionList';
|
||||
import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem';
|
||||
import DescriptionListItemDescription from 'Components/DescriptionList/DescriptionListItemDescription';
|
||||
import DescriptionListItemTitle from 'Components/DescriptionList/DescriptionListItemTitle';
|
||||
import Link from 'Components/Link/Link';
|
||||
import formatDateTime from 'Utilities/Date/formatDateTime';
|
||||
import formatAge from 'Utilities/Number/formatAge';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import Link from 'Components/Link/Link';
|
||||
import DescriptionList from 'Components/DescriptionList/DescriptionList';
|
||||
import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem';
|
||||
import DescriptionListItemTitle from 'Components/DescriptionList/DescriptionListItemTitle';
|
||||
import DescriptionListItemDescription from 'Components/DescriptionList/DescriptionListItemDescription';
|
||||
import styles from './HistoryDetails.css';
|
||||
|
||||
function HistoryDetails(props) {
|
||||
@@ -36,14 +35,14 @@ function HistoryDetails(props) {
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title={translate('Name')}
|
||||
title="Name"
|
||||
data={sourceTitle}
|
||||
/>
|
||||
|
||||
{
|
||||
!!indexer &&
|
||||
<DescriptionListItem
|
||||
title={translate('Indexer')}
|
||||
title="Indexer"
|
||||
data={indexer}
|
||||
/>
|
||||
}
|
||||
@@ -52,7 +51,7 @@ function HistoryDetails(props) {
|
||||
!!releaseGroup &&
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title={translate('ReleaseGroup')}
|
||||
title="Release Group"
|
||||
data={releaseGroup}
|
||||
/>
|
||||
}
|
||||
@@ -73,7 +72,7 @@ function HistoryDetails(props) {
|
||||
{
|
||||
!!downloadClient &&
|
||||
<DescriptionListItem
|
||||
title={translate('DownloadClient')}
|
||||
title="Download Client"
|
||||
data={downloadClient}
|
||||
/>
|
||||
}
|
||||
@@ -81,7 +80,7 @@ function HistoryDetails(props) {
|
||||
{
|
||||
!!downloadId &&
|
||||
<DescriptionListItem
|
||||
title={translate('GrabID')}
|
||||
title="Grab ID"
|
||||
data={downloadId}
|
||||
/>
|
||||
}
|
||||
@@ -89,7 +88,7 @@ function HistoryDetails(props) {
|
||||
{
|
||||
!!indexer &&
|
||||
<DescriptionListItem
|
||||
title={translate('AgeWhenGrabbed')}
|
||||
title="Age (when grabbed)"
|
||||
data={formatAge(age, ageHours, ageMinutes)}
|
||||
/>
|
||||
}
|
||||
@@ -97,7 +96,7 @@ function HistoryDetails(props) {
|
||||
{
|
||||
!!publishedDate &&
|
||||
<DescriptionListItem
|
||||
title={translate('PublishedDate')}
|
||||
title="Published Date"
|
||||
data={formatDateTime(publishedDate, shortDateFormat, timeFormat, { includeSeconds: true })}
|
||||
/>
|
||||
}
|
||||
@@ -114,14 +113,14 @@ function HistoryDetails(props) {
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title={translate('Name')}
|
||||
title="Name"
|
||||
data={sourceTitle}
|
||||
/>
|
||||
|
||||
{
|
||||
!!message &&
|
||||
<DescriptionListItem
|
||||
title={translate('Message')}
|
||||
title="Message"
|
||||
data={message}
|
||||
/>
|
||||
}
|
||||
@@ -139,7 +138,7 @@ function HistoryDetails(props) {
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title={translate('Name')}
|
||||
title="Name"
|
||||
data={sourceTitle}
|
||||
/>
|
||||
|
||||
@@ -147,7 +146,7 @@ function HistoryDetails(props) {
|
||||
!!droppedPath &&
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title={translate('Source')}
|
||||
title="Source"
|
||||
data={droppedPath}
|
||||
/>
|
||||
}
|
||||
@@ -156,7 +155,7 @@ function HistoryDetails(props) {
|
||||
!!importedPath &&
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title={translate('ImportedTo')}
|
||||
title="Imported To"
|
||||
data={importedPath}
|
||||
/>
|
||||
}
|
||||
@@ -188,12 +187,12 @@ function HistoryDetails(props) {
|
||||
return (
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
title={translate('Name')}
|
||||
title="Name"
|
||||
data={sourceTitle}
|
||||
/>
|
||||
|
||||
<DescriptionListItem
|
||||
title={translate('Reason')}
|
||||
title="Reason"
|
||||
data={reasonMessage}
|
||||
/>
|
||||
</DescriptionList>
|
||||
@@ -211,22 +210,22 @@ function HistoryDetails(props) {
|
||||
return (
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
title={translate('SourcePath')}
|
||||
title="Source Path"
|
||||
data={sourcePath}
|
||||
/>
|
||||
|
||||
<DescriptionListItem
|
||||
title={translate('SourceRelativePath')}
|
||||
title="Source Relative Path"
|
||||
data={sourceRelativePath}
|
||||
/>
|
||||
|
||||
<DescriptionListItem
|
||||
title={translate('DestinationPath')}
|
||||
title="Destination Path"
|
||||
data={path}
|
||||
/>
|
||||
|
||||
<DescriptionListItem
|
||||
title={translate('DestinationRelativePath')}
|
||||
title="Destination Relative Path"
|
||||
data={relativePath}
|
||||
/>
|
||||
</DescriptionList>
|
||||
@@ -242,14 +241,14 @@ function HistoryDetails(props) {
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title={translate('Name')}
|
||||
title="Name"
|
||||
data={sourceTitle}
|
||||
/>
|
||||
|
||||
{
|
||||
!!message &&
|
||||
<DescriptionListItem
|
||||
title={translate('Message')}
|
||||
title="Message"
|
||||
data={message}
|
||||
/>
|
||||
}
|
||||
@@ -261,7 +260,7 @@ function HistoryDetails(props) {
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title={translate('Name')}
|
||||
title="Name"
|
||||
data={sourceTitle}
|
||||
/>
|
||||
</DescriptionList>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import _ from 'lodash';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||
@@ -7,10 +8,10 @@ function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createUISettingsSelector(),
|
||||
(uiSettings) => {
|
||||
return {
|
||||
shortDateFormat: uiSettings.shortDateFormat,
|
||||
timeFormat: uiSettings.timeFormat
|
||||
};
|
||||
return _.pick(uiSettings, [
|
||||
'shortDateFormat',
|
||||
'timeFormat'
|
||||
]);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
import Button from 'Components/Link/Button';
|
||||
import SpinnerButton from 'Components/Link/SpinnerButton';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import HistoryDetails from './HistoryDetails';
|
||||
import styles from './HistoryDetailsModal.css';
|
||||
|
||||
@@ -80,7 +79,7 @@ function HistoryDetailsModal(props) {
|
||||
<Button
|
||||
onPress={onModalClose}
|
||||
>
|
||||
{translate('Close')}
|
||||
Close
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { align, icons } from 'Helpers/Props';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import FilterMenu from 'Components/Menu/FilterMenu';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
|
||||
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
||||
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 { align, icons } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBodyConnector from 'Components/Page/PageContentBodyConnector';
|
||||
import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
|
||||
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
||||
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||
import FilterMenu from 'Components/Menu/FilterMenu';
|
||||
import HistoryRowConnector from './HistoryRowConnector';
|
||||
|
||||
class History extends Component {
|
||||
@@ -43,11 +42,11 @@ class History extends Component {
|
||||
const hasError = error || moviesError;
|
||||
|
||||
return (
|
||||
<PageContent title={translate('History')}>
|
||||
<PageContent title="History">
|
||||
<PageToolbar>
|
||||
<PageToolbarSection>
|
||||
<PageToolbarButton
|
||||
label={translate('Refresh')}
|
||||
label="Refresh"
|
||||
iconName={icons.REFRESH}
|
||||
isSpinning={isFetching}
|
||||
onPress={onFirstPagePress}
|
||||
@@ -60,7 +59,7 @@ class History extends Component {
|
||||
columns={columns}
|
||||
>
|
||||
<PageToolbarButton
|
||||
label={translate('Options')}
|
||||
label="Options"
|
||||
iconName={icons.TABLE}
|
||||
/>
|
||||
</TableOptionsModalWrapper>
|
||||
@@ -75,7 +74,7 @@ class History extends Component {
|
||||
</PageToolbarSection>
|
||||
</PageToolbar>
|
||||
|
||||
<PageContentBody>
|
||||
<PageContentBodyConnector>
|
||||
{
|
||||
isFetchingAny && !isAllPopulated &&
|
||||
<LoadingIndicator />
|
||||
@@ -83,9 +82,7 @@ class History extends Component {
|
||||
|
||||
{
|
||||
!isFetchingAny && hasError &&
|
||||
<div>
|
||||
{translate('UnableToLoadHistory')}
|
||||
</div>
|
||||
<div>Unable to load history</div>
|
||||
}
|
||||
|
||||
{
|
||||
@@ -128,7 +125,7 @@ class History extends Component {
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</PageContentBody>
|
||||
</PageContentBodyConnector>
|
||||
</PageContent>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ 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 * as historyActions from 'Store/Actions/historyActions';
|
||||
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
|
||||
import History from './History';
|
||||
|
||||
function createMapStateToProps() {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { icons, kinds } from 'Helpers/Props';
|
||||
import Icon from 'Components/Icon';
|
||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import { icons, kinds } from 'Helpers/Props';
|
||||
import styles from './HistoryEventTypeCell.css';
|
||||
|
||||
function getIconName(eventType) {
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import TableRow from 'Components/Table/TableRow';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import MovieQuality from 'Movie/MovieQuality';
|
||||
import MovieFormats from 'Movie/MovieFormats';
|
||||
import MovieLanguage from 'Movie/MovieLanguage';
|
||||
import MovieQuality from 'Movie/MovieQuality';
|
||||
import MovieTitleLink from 'Movie/MovieTitleLink';
|
||||
import HistoryDetailsModal from './Details/HistoryDetailsModal';
|
||||
import HistoryEventTypeCell from './HistoryEventTypeCell';
|
||||
import HistoryDetailsModal from './Details/HistoryDetailsModal';
|
||||
import styles from './HistoryRow.css';
|
||||
|
||||
class HistoryRow extends Component {
|
||||
|
||||
@@ -1,28 +1,27 @@
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
|
||||
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
||||
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
|
||||
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 { align, icons } from 'Helpers/Props';
|
||||
import getRemovedItems from 'Utilities/Object/getRemovedItems';
|
||||
import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
|
||||
import translate from 'Utilities/String/translate';
|
||||
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 } from 'Helpers/Props';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import TablePager from 'Components/Table/TablePager';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBodyConnector from 'Components/Page/PageContentBodyConnector';
|
||||
import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
|
||||
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
||||
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
|
||||
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
||||
import RemoveQueueItemsModal from './RemoveQueueItemsModal';
|
||||
import QueueOptionsConnector from './QueueOptionsConnector';
|
||||
import QueueRowConnector from './QueueRowConnector';
|
||||
import RemoveQueueItemsModal from './RemoveQueueItemsModal';
|
||||
|
||||
class Queue extends Component {
|
||||
|
||||
@@ -149,11 +148,11 @@ class Queue extends Component {
|
||||
const disableSelectedActions = selectedCount === 0;
|
||||
|
||||
return (
|
||||
<PageContent title={translate('Queue')}>
|
||||
<PageContent title="Queue">
|
||||
<PageToolbar>
|
||||
<PageToolbarSection>
|
||||
<PageToolbarButton
|
||||
label={translate('Refresh')}
|
||||
label="Refresh"
|
||||
iconName={icons.REFRESH}
|
||||
isSpinning={isRefreshing}
|
||||
onPress={onRefreshPress}
|
||||
@@ -162,7 +161,7 @@ class Queue extends Component {
|
||||
<PageToolbarSeparator />
|
||||
|
||||
<PageToolbarButton
|
||||
label={translate('GrabSelected')}
|
||||
label="Grab Selected"
|
||||
iconName={icons.DOWNLOAD}
|
||||
isDisabled={disableSelectedActions || !isPendingSelected}
|
||||
isSpinning={isGrabbing}
|
||||
@@ -170,7 +169,7 @@ class Queue extends Component {
|
||||
/>
|
||||
|
||||
<PageToolbarButton
|
||||
label={translate('RemoveSelected')}
|
||||
label="Remove Selected"
|
||||
iconName={icons.REMOVE}
|
||||
isDisabled={disableSelectedActions}
|
||||
isSpinning={isRemoving}
|
||||
@@ -187,14 +186,14 @@ class Queue extends Component {
|
||||
optionsComponent={QueueOptionsConnector}
|
||||
>
|
||||
<PageToolbarButton
|
||||
label={translate('Options')}
|
||||
label="Options"
|
||||
iconName={icons.TABLE}
|
||||
/>
|
||||
</TableOptionsModalWrapper>
|
||||
</PageToolbarSection>
|
||||
</PageToolbar>
|
||||
|
||||
<PageContentBody>
|
||||
<PageContentBodyConnector>
|
||||
{
|
||||
isRefreshing && !isAllPopulated &&
|
||||
<LoadingIndicator />
|
||||
@@ -251,7 +250,7 @@ class Queue extends Component {
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</PageContentBody>
|
||||
</PageContentBodyConnector>
|
||||
|
||||
<RemoveQueueItemsModal
|
||||
isOpen={isConfirmRemoveModalOpen}
|
||||
|
||||
@@ -2,12 +2,12 @@ import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import * as commandNames from 'Commands/commandNames';
|
||||
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
|
||||
import withCurrentPage from 'Components/withCurrentPage';
|
||||
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
|
||||
import { executeCommand } from 'Store/Actions/commandActions';
|
||||
import * as queueActions from 'Store/Actions/queueActions';
|
||||
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
|
||||
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
|
||||
import * as commandNames from 'Commands/commandNames';
|
||||
import Queue from './Queue';
|
||||
|
||||
function createMapStateToProps() {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import moment from 'moment';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Icon from 'Components/Icon';
|
||||
import { icons, kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import Icon from 'Components/Icon';
|
||||
|
||||
function QueueDetails(props) {
|
||||
const {
|
||||
@@ -11,20 +10,20 @@ function QueueDetails(props) {
|
||||
size,
|
||||
sizeleft,
|
||||
estimatedCompletionTime,
|
||||
status,
|
||||
trackedDownloadState,
|
||||
trackedDownloadStatus,
|
||||
status: queueStatus,
|
||||
errorMessage,
|
||||
progressBar
|
||||
} = props;
|
||||
|
||||
const progress = size ? (100 - sizeleft / size * 100) : 0;
|
||||
const status = queueStatus.toLowerCase();
|
||||
|
||||
const progress = (100 - sizeleft / size * 100);
|
||||
|
||||
if (status === 'pending') {
|
||||
return (
|
||||
<Icon
|
||||
name={icons.PENDING}
|
||||
title={translate('ReleaseWillBeProcessedInterp', [moment(estimatedCompletionTime).fromNow()])}
|
||||
title={`Release will be processed ${moment(estimatedCompletionTime).fromNow()}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -35,40 +34,12 @@ function QueueDetails(props) {
|
||||
<Icon
|
||||
name={icons.DOWNLOAD}
|
||||
kind={kinds.DANGER}
|
||||
title={translate('ImportFailedInterp', [errorMessage])}
|
||||
title={`Import failed: ${errorMessage}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (trackedDownloadStatus === 'warning') {
|
||||
return (
|
||||
<Icon
|
||||
name={icons.DOWNLOAD}
|
||||
kind={kinds.WARNING}
|
||||
title={'Downloaded - Unable to Import: check logs for details'}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (trackedDownloadState === 'importPending') {
|
||||
return (
|
||||
<Icon
|
||||
name={icons.DOWNLOAD}
|
||||
kind={kinds.PURPLE}
|
||||
title={`${translate('Downloaded')} - ${translate('WaitingToImport')}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (trackedDownloadState === 'importing') {
|
||||
return (
|
||||
<Icon
|
||||
name={icons.DOWNLOAD}
|
||||
kind={kinds.PURPLE}
|
||||
title={`${translate('Downloaded')} - ${translate('Importing')}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
// TODO: show an icon when download is complete, but not imported yet?
|
||||
}
|
||||
|
||||
if (errorMessage) {
|
||||
@@ -76,7 +47,7 @@ function QueueDetails(props) {
|
||||
<Icon
|
||||
name={icons.DOWNLOADING}
|
||||
kind={kinds.DANGER}
|
||||
title={translate('DownloadFailedInterp', [errorMessage])}
|
||||
title={`Download failed: ${errorMessage}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -86,7 +57,7 @@ function QueueDetails(props) {
|
||||
<Icon
|
||||
name={icons.DOWNLOADING}
|
||||
kind={kinds.DANGER}
|
||||
title={translate('DownloadFailedCheckDownloadClientForMoreDetails')}
|
||||
title="Download failed: check download client for more details"
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -96,7 +67,7 @@ function QueueDetails(props) {
|
||||
<Icon
|
||||
name={icons.DOWNLOADING}
|
||||
kind={kinds.WARNING}
|
||||
title={translate('DownloadWarningCheckDownloadClientForMoreDetails')}
|
||||
title="Download warning: check download client for more details"
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -105,7 +76,7 @@ function QueueDetails(props) {
|
||||
return (
|
||||
<Icon
|
||||
name={icons.DOWNLOADING}
|
||||
title={translate('MovieIsDownloadingInterp', [progress.toFixed(1), title])}
|
||||
title={`Movie is downloading - ${progress.toFixed(1)}% ${title}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -119,8 +90,6 @@ QueueDetails.propTypes = {
|
||||
sizeleft: PropTypes.number.isRequired,
|
||||
estimatedCompletionTime: PropTypes.string,
|
||||
status: PropTypes.string.isRequired,
|
||||
trackedDownloadState: PropTypes.string.isRequired,
|
||||
trackedDownloadStatus: PropTypes.string.isRequired,
|
||||
errorMessage: PropTypes.string,
|
||||
progressBar: PropTypes.node.isRequired
|
||||
};
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import FormLabel from 'Components/Form/FormLabel';
|
||||
import { inputTypes } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormLabel from 'Components/Form/FormLabel';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
|
||||
class QueueOptions extends Component {
|
||||
|
||||
@@ -55,13 +54,13 @@ class QueueOptions extends Component {
|
||||
return (
|
||||
<Fragment>
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('ShowUnknownMovieItems')}</FormLabel>
|
||||
<FormLabel>Show Unknown Movie Items</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="includeUnknownMovieItems"
|
||||
value={includeUnknownMovieItems}
|
||||
helpText={translate('IncludeUnknownMovieItemsHelpText')}
|
||||
helpText="Show items without a movie in the queue, this could include removed movie, movies or anything else in Radarr's category"
|
||||
onChange={this.onOptionChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import ProtocolLabel from 'Activity/Queue/ProtocolLabel';
|
||||
import formatBytes from 'Utilities/Number/formatBytes';
|
||||
import { icons, kinds } from 'Helpers/Props';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
||||
import ProgressBar from 'Components/ProgressBar';
|
||||
import TableRow from 'Components/Table/TableRow';
|
||||
// import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
|
||||
import TableRow from 'Components/Table/TableRow';
|
||||
import { icons, kinds } from 'Helpers/Props';
|
||||
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
|
||||
import ProtocolLabel from 'Activity/Queue/ProtocolLabel';
|
||||
import MovieQuality from 'Movie/MovieQuality';
|
||||
import MovieFormats from 'Movie/MovieFormats';
|
||||
import MovieLanguage from 'Movie/MovieLanguage';
|
||||
import MovieQuality from 'Movie/MovieQuality';
|
||||
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
|
||||
import MovieTitleLink from 'Movie/MovieTitleLink';
|
||||
import formatBytes from 'Utilities/Number/formatBytes';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import QueueStatusCell from './QueueStatusCell';
|
||||
import RemoveQueueItemModal from './RemoveQueueItemModal';
|
||||
import TimeleftCell from './TimeleftCell';
|
||||
import RemoveQueueItemModal from './RemoveQueueItemModal';
|
||||
import styles from './QueueRow.css';
|
||||
|
||||
class QueueRow extends Component {
|
||||
@@ -295,7 +294,7 @@ class QueueRow extends Component {
|
||||
}
|
||||
|
||||
<SpinnerIconButton
|
||||
title={translate('RemoveFromQueue')}
|
||||
title="Remove from queue"
|
||||
name={icons.REMOVE}
|
||||
isSpinning={isRemoving}
|
||||
onPress={this.onRemoveQueueItemPress}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
|
||||
import Icon from 'Components/Icon';
|
||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import Popover from 'Components/Tooltip/Popover';
|
||||
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './QueueStatusCell.css';
|
||||
|
||||
function getDetailedPopoverBody(statusMessages) {
|
||||
@@ -50,75 +49,71 @@ function QueueStatusCell(props) {
|
||||
// status === 'downloading'
|
||||
let iconName = icons.DOWNLOADING;
|
||||
let iconKind = kinds.DEFAULT;
|
||||
let title = translate('Downloading');
|
||||
|
||||
if (status === 'paused') {
|
||||
iconName = icons.PAUSED;
|
||||
title = translate('Paused');
|
||||
}
|
||||
|
||||
if (status === 'queued') {
|
||||
iconName = icons.QUEUED;
|
||||
title = translate('Queued');
|
||||
}
|
||||
|
||||
if (status === 'completed') {
|
||||
iconName = icons.DOWNLOADED;
|
||||
title = translate('Downloaded');
|
||||
|
||||
if (trackedDownloadState === 'importPending') {
|
||||
title += ` - ${translate('WaitingToImport')}`;
|
||||
iconKind = kinds.PURPLE;
|
||||
}
|
||||
|
||||
if (trackedDownloadState === 'importing') {
|
||||
title += ` - ${translate('Importing')}`;
|
||||
iconKind = kinds.PURPLE;
|
||||
}
|
||||
|
||||
if (trackedDownloadState === 'failedPending') {
|
||||
title += ` - ${translate('WaitingToProcess')}`;
|
||||
iconKind = kinds.DANGER;
|
||||
}
|
||||
}
|
||||
let title = 'Downloading';
|
||||
|
||||
if (hasWarning) {
|
||||
iconKind = kinds.WARNING;
|
||||
}
|
||||
|
||||
if (status === 'paused') {
|
||||
iconName = icons.PAUSED;
|
||||
title = 'Paused';
|
||||
}
|
||||
|
||||
if (status === 'queued') {
|
||||
iconName = icons.QUEUED;
|
||||
title = 'Queued';
|
||||
}
|
||||
|
||||
if (status === 'completed') {
|
||||
iconName = icons.DOWNLOADED;
|
||||
title = 'Downloaded';
|
||||
|
||||
if (trackedDownloadState === 'importPending') {
|
||||
title += ' - Waiting to Import';
|
||||
}
|
||||
|
||||
if (trackedDownloadState === 'importing') {
|
||||
title += ' - Importing';
|
||||
}
|
||||
|
||||
if (trackedDownloadState === 'failedPending') {
|
||||
title += ' - Waiting to Process';
|
||||
}
|
||||
}
|
||||
|
||||
if (status === 'delay') {
|
||||
iconName = icons.PENDING;
|
||||
title = translate('Pending');
|
||||
title = 'Pending';
|
||||
}
|
||||
|
||||
if (status === 'DownloadClientUnavailable') {
|
||||
iconName = icons.PENDING;
|
||||
iconKind = kinds.WARNING;
|
||||
title = `${translate('Pending')} - ${translate('DownloadClientUnavailable')}`;
|
||||
title = 'Pending - Download client is unavailable';
|
||||
}
|
||||
|
||||
if (status === 'failed') {
|
||||
iconName = icons.DOWNLOADING;
|
||||
iconKind = kinds.DANGER;
|
||||
title = translate('DownloadFailed');
|
||||
title = 'Download failed';
|
||||
}
|
||||
|
||||
if (status === 'warning') {
|
||||
iconName = icons.DOWNLOADING;
|
||||
iconKind = kinds.WARNING;
|
||||
const warningMessage = errorMessage || translate('CheckDownloadClientForDetails');
|
||||
title = translate('DownloadWarning', [warningMessage]);
|
||||
title = `Download warning: ${errorMessage || 'check download client for more details'}`;
|
||||
}
|
||||
|
||||
if (hasError) {
|
||||
if (status === 'completed') {
|
||||
iconName = icons.DOWNLOAD;
|
||||
iconKind = kinds.DANGER;
|
||||
title = translate('ImportFailed', [sourceTitle]);
|
||||
title = `Import failed: ${sourceTitle}`;
|
||||
} else {
|
||||
iconName = icons.DOWNLOADING;
|
||||
iconKind = kinds.DANGER;
|
||||
title = translate('DownloadFailed');
|
||||
title = 'Download failed';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,8 +145,8 @@ QueueStatusCell.propTypes = {
|
||||
};
|
||||
|
||||
QueueStatusCell.defaultProps = {
|
||||
trackedDownloadStatus: translate('Ok'),
|
||||
trackedDownloadState: translate('Downloading')
|
||||
trackedDownloadStatus: 'Ok',
|
||||
trackedDownloadState: 'Downloading'
|
||||
};
|
||||
|
||||
export default QueueStatusCell;
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import FormLabel from 'Components/Form/FormLabel';
|
||||
import { inputTypes, kinds, sizes } from 'Helpers/Props';
|
||||
import Button from 'Components/Link/Button';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormLabel from 'Components/Form/FormLabel';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { inputTypes, kinds, sizes } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
|
||||
class RemoveQueueItemModal extends Component {
|
||||
|
||||
@@ -90,25 +89,25 @@ class RemoveQueueItemModal extends Component {
|
||||
</div>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('RemoveFromDownloadClient')}</FormLabel>
|
||||
<FormLabel>Remove From Download Client</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="remove"
|
||||
value={remove}
|
||||
helpTextWarning={translate('RemoveHelpTextWarning')}
|
||||
helpTextWarning="Removing will remove the download and the file(s) from the download client."
|
||||
isDisabled={!canIgnore}
|
||||
onChange={this.onRemoveChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('BlacklistRelease')}</FormLabel>
|
||||
<FormLabel>Blacklist Release</FormLabel>
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="blacklist"
|
||||
value={blacklist}
|
||||
helpText={translate('BlacklistHelpText')}
|
||||
helpText="Starts a search for this movie again and prevents this release from being grabbed again"
|
||||
onChange={this.onBlacklistChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
@@ -117,7 +116,7 @@ class RemoveQueueItemModal extends Component {
|
||||
|
||||
<ModalFooter>
|
||||
<Button onPress={this.onModalClose}>
|
||||
{translate('Close')}
|
||||
Close
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import FormLabel from 'Components/Form/FormLabel';
|
||||
import { inputTypes, kinds, sizes } from 'Helpers/Props';
|
||||
import Button from 'Components/Link/Button';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormLabel from 'Components/Form/FormLabel';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { inputTypes, kinds, sizes } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import styles from './RemoveQueueItemsModal.css';
|
||||
|
||||
class RemoveQueueItemsModal extends Component {
|
||||
@@ -91,13 +90,13 @@ class RemoveQueueItemsModal extends Component {
|
||||
</div>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('RemoveFromDownloadClient')}</FormLabel>
|
||||
<FormLabel>Remove From Download Client</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="remove"
|
||||
value={remove}
|
||||
helpTextWarning={translate('RemoveHelpTextWarning')}
|
||||
helpTextWarning="Removing will remove the download and the file(s) from the download client."
|
||||
isDisabled={!canIgnore}
|
||||
onChange={this.onRemoveChange}
|
||||
/>
|
||||
@@ -112,7 +111,7 @@ class RemoveQueueItemsModal extends Component {
|
||||
type={inputTypes.CHECK}
|
||||
name="blacklist"
|
||||
value={blacklist}
|
||||
helpText={translate('BlacklistHelpText')}
|
||||
helpText="Prevents Radarr from automatically grabbing this movie again"
|
||||
onChange={this.onBlacklistChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
@@ -121,7 +120,7 @@ class RemoveQueueItemsModal extends Component {
|
||||
|
||||
<ModalFooter>
|
||||
<Button onPress={this.onModalClose}>
|
||||
{translate('Close')}
|
||||
Close
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
|
||||
@@ -2,8 +2,8 @@ import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import PageSidebarStatus from 'Components/Page/Sidebar/PageSidebarStatus';
|
||||
import { fetchQueueStatus } from 'Store/Actions/queueActions';
|
||||
import PageSidebarStatus from 'Components/Page/Sidebar/PageSidebarStatus';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import formatTime from 'Utilities/Date/formatTime';
|
||||
import formatTimeSpan from 'Utilities/Date/formatTimeSpan';
|
||||
import getRelativeDate from 'Utilities/Date/getRelativeDate';
|
||||
import formatBytes from 'Utilities/Number/formatBytes';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import styles from './TimeleftCell.css';
|
||||
|
||||
function TimeleftCell(props) {
|
||||
@@ -20,35 +19,35 @@ function TimeleftCell(props) {
|
||||
timeFormat
|
||||
} = props;
|
||||
|
||||
if (status === 'delay') {
|
||||
if (status === 'Delay') {
|
||||
const date = getRelativeDate(estimatedCompletionTime, shortDateFormat, showRelativeDates);
|
||||
const time = formatTime(estimatedCompletionTime, timeFormat, { includeMinuteZero: true });
|
||||
|
||||
return (
|
||||
<TableRowCell
|
||||
className={styles.timeleft}
|
||||
title={translate('DelayingDownloadUntilInterp', [date, time])}
|
||||
title={`Delaying download until ${date} at ${time}`}
|
||||
>
|
||||
-
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (status === 'downloadClientUnavailable') {
|
||||
if (status === 'DownloadClientUnavailable') {
|
||||
const date = getRelativeDate(estimatedCompletionTime, shortDateFormat, showRelativeDates);
|
||||
const time = formatTime(estimatedCompletionTime, timeFormat, { includeMinuteZero: true });
|
||||
|
||||
return (
|
||||
<TableRowCell
|
||||
className={styles.timeleft}
|
||||
title={translate('RetryingDownloadInterp', [date, time])}
|
||||
title={`Retrying download ${date} at ${time}`}
|
||||
>
|
||||
-
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (!timeleft || status === 'completed' || status === 'failed') {
|
||||
if (!timeleft) {
|
||||
return (
|
||||
<TableRowCell className={styles.timeleft}>
|
||||
-
|
||||
|
||||
@@ -2,32 +2,25 @@ import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import * as commandNames from 'Commands/commandNames';
|
||||
import withScrollPosition from 'Components/withScrollPosition';
|
||||
import { executeCommand } from 'Store/Actions/commandActions';
|
||||
import { addImportExclusions, addMovies, clearAddMovie, fetchDiscoverMovies, setListMovieFilter, setListMovieSort, setListMovieTableOption, setListMovieView } from 'Store/Actions/discoverMovieActions';
|
||||
import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
|
||||
import { fetchImportExclusions } from 'Store/Actions/Settings/importExclusions';
|
||||
import scrollPositions from 'Store/scrollPositions';
|
||||
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
|
||||
import createAddMovieClientSideCollectionItemsSelector from 'Store/Selectors/createAddMovieClientSideCollectionItemsSelector';
|
||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||
import createDiscoverMovieClientSideCollectionItemsSelector from 'Store/Selectors/createDiscoverMovieClientSideCollectionItemsSelector';
|
||||
import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
|
||||
import { fetchDiscoverMovies, clearAddMovie, setListMovieSort, setListMovieFilter, setListMovieView, setListMovieTableOption } from 'Store/Actions/addMovieActions';
|
||||
import scrollPositions from 'Store/scrollPositions';
|
||||
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
|
||||
import DiscoverMovie from './DiscoverMovie';
|
||||
import withScrollPosition from 'Components/withScrollPosition';
|
||||
import AddListMovie from './AddListMovie';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createDiscoverMovieClientSideCollectionItemsSelector('discoverMovie'),
|
||||
createCommandExecutingSelector(commandNames.IMPORT_LIST_SYNC),
|
||||
createAddMovieClientSideCollectionItemsSelector('addMovie'),
|
||||
createDimensionsSelector(),
|
||||
(
|
||||
movies,
|
||||
isSyncingLists,
|
||||
dimensionsState
|
||||
) => {
|
||||
return {
|
||||
...movies,
|
||||
isSyncingLists,
|
||||
isSmallScreen: dimensionsState.isSmallScreen
|
||||
};
|
||||
}
|
||||
@@ -40,10 +33,6 @@ function createMapDispatchToProps(dispatch, props) {
|
||||
dispatch(fetchRootFolders());
|
||||
},
|
||||
|
||||
dispatchFetchImportExclusions() {
|
||||
dispatch(fetchImportExclusions());
|
||||
},
|
||||
|
||||
dispatchClearListMovie() {
|
||||
dispatch(clearAddMovie());
|
||||
},
|
||||
@@ -66,25 +55,11 @@ function createMapDispatchToProps(dispatch, props) {
|
||||
|
||||
dispatchSetListMovieView(view) {
|
||||
dispatch(setListMovieView({ view }));
|
||||
},
|
||||
|
||||
dispatchAddMovies(ids, addOptions) {
|
||||
dispatch(addMovies({ ids, addOptions }));
|
||||
},
|
||||
|
||||
dispatchAddImportExclusions(exclusions) {
|
||||
dispatch(addImportExclusions(exclusions));
|
||||
},
|
||||
|
||||
onImportListSyncPress() {
|
||||
dispatch(executeCommand({
|
||||
name: commandNames.IMPORT_LIST_SYNC
|
||||
}));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class DiscoverMovieConnector extends Component {
|
||||
class AddDiscoverMovieConnector extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
@@ -92,7 +67,6 @@ class DiscoverMovieConnector extends Component {
|
||||
componentDidMount() {
|
||||
registerPagePopulator(this.repopulate);
|
||||
this.props.dispatchFetchRootFolders();
|
||||
this.props.dispatchFetchImportExclusions();
|
||||
this.props.dispatchFetchListMovies();
|
||||
}
|
||||
|
||||
@@ -109,15 +83,7 @@ class DiscoverMovieConnector extends Component {
|
||||
}
|
||||
|
||||
onScroll = ({ scrollTop }) => {
|
||||
scrollPositions.discoverMovie = scrollTop;
|
||||
}
|
||||
|
||||
onAddMoviesPress = ({ ids, addOptions }) => {
|
||||
this.props.dispatchAddMovies(ids, addOptions);
|
||||
}
|
||||
|
||||
onExcludeMoviesPress =({ ids }) => {
|
||||
this.props.dispatchAddImportExclusions({ ids });
|
||||
scrollPositions.addMovie = scrollTop;
|
||||
}
|
||||
|
||||
//
|
||||
@@ -125,31 +91,26 @@ class DiscoverMovieConnector extends Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<DiscoverMovie
|
||||
<AddListMovie
|
||||
{...this.props}
|
||||
onViewSelect={this.onViewSelect}
|
||||
onScroll={this.onScroll}
|
||||
onAddMoviesPress={this.onAddMoviesPress}
|
||||
onExcludeMoviesPress={this.onExcludeMoviesPress}
|
||||
onSyncListsPress={this.onSyncListsPress}
|
||||
onSaveSelected={this.onSaveSelected}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DiscoverMovieConnector.propTypes = {
|
||||
AddDiscoverMovieConnector.propTypes = {
|
||||
isSmallScreen: PropTypes.bool.isRequired,
|
||||
view: PropTypes.string.isRequired,
|
||||
dispatchFetchImportExclusions: PropTypes.func.isRequired,
|
||||
dispatchFetchRootFolders: PropTypes.func.isRequired,
|
||||
dispatchFetchListMovies: PropTypes.func.isRequired,
|
||||
dispatchClearListMovie: PropTypes.func.isRequired,
|
||||
dispatchSetListMovieView: PropTypes.func.isRequired,
|
||||
dispatchAddMovies: PropTypes.func.isRequired,
|
||||
dispatchAddImportExclusions: PropTypes.func.isRequired
|
||||
dispatchSetListMovieView: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default withScrollPosition(
|
||||
connect(createMapStateToProps, createMapDispatchToProps)(DiscoverMovieConnector),
|
||||
'discoverMovie'
|
||||
connect(createMapStateToProps, createMapDispatchToProps)(AddDiscoverMovieConnector),
|
||||
'addMovie'
|
||||
);
|
||||
@@ -1,47 +1,40 @@
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItems';
|
||||
import { align, icons, sortDirections } from 'Helpers/Props';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
import PageContentBodyConnector from 'Components/Page/PageContentBodyConnector';
|
||||
import PageJumpBar from 'Components/Page/PageJumpBar';
|
||||
import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
|
||||
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
||||
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
|
||||
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
||||
import { align, icons, sortDirections } from 'Helpers/Props';
|
||||
import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
|
||||
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
|
||||
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
||||
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||
import AddListMovieTableConnector from './Table/AddListMovieTableConnector';
|
||||
import AddListMoviePosterOptionsModal from './Posters/Options/AddListMoviePosterOptionsModal';
|
||||
import AddListMoviePostersConnector from './Posters/AddListMoviePostersConnector';
|
||||
import AddListMovieOverviewOptionsModal from './Overview/Options/AddListMovieOverviewOptionsModal';
|
||||
import AddListMovieOverviewsConnector from './Overview/AddListMovieOverviewsConnector';
|
||||
import AddListMovieFilterMenu from 'AddMovie/AddListMovie/Menus/AddListMovieFilterMenu';
|
||||
import AddListMovieSortMenu from 'AddMovie/AddListMovie/Menus/AddListMovieSortMenu';
|
||||
import AddListMovieViewMenu from 'AddMovie/AddListMovie/Menus/AddListMovieViewMenu';
|
||||
import styles from 'Movie/Index/MovieIndex.css';
|
||||
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import getSelectedIds from 'Utilities/Table/getSelectedIds';
|
||||
import selectAll from 'Utilities/Table/selectAll';
|
||||
import toggleSelected from 'Utilities/Table/toggleSelected';
|
||||
import DiscoverMovieFooterConnector from './DiscoverMovieFooterConnector';
|
||||
import DiscoverMovieFilterMenu from './Menus/DiscoverMovieFilterMenu';
|
||||
import DiscoverMovieSortMenu from './Menus/DiscoverMovieSortMenu';
|
||||
import DiscoverMovieViewMenu from './Menus/DiscoverMovieViewMenu';
|
||||
import NoDiscoverMovie from './NoDiscoverMovie';
|
||||
import DiscoverMovieOverviewsConnector from './Overview/DiscoverMovieOverviewsConnector';
|
||||
import DiscoverMovieOverviewOptionsModal from './Overview/Options/DiscoverMovieOverviewOptionsModal';
|
||||
import DiscoverMoviePostersConnector from './Posters/DiscoverMoviePostersConnector';
|
||||
import DiscoverMoviePosterOptionsModal from './Posters/Options/DiscoverMoviePosterOptionsModal';
|
||||
import DiscoverMovieTableConnector from './Table/DiscoverMovieTableConnector';
|
||||
import DiscoverMovieTableOptionsConnector from './Table/DiscoverMovieTableOptionsConnector';
|
||||
|
||||
function getViewComponent(view) {
|
||||
if (view === 'posters') {
|
||||
return DiscoverMoviePostersConnector;
|
||||
return AddListMoviePostersConnector;
|
||||
}
|
||||
|
||||
if (view === 'overview') {
|
||||
return DiscoverMovieOverviewsConnector;
|
||||
return AddListMovieOverviewsConnector;
|
||||
}
|
||||
|
||||
return DiscoverMovieTableConnector;
|
||||
return AddListMovieTableConnector;
|
||||
}
|
||||
|
||||
class DiscoverMovie extends Component {
|
||||
class AddListMovie extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
@@ -57,16 +50,12 @@ class DiscoverMovie extends Component {
|
||||
isOverviewOptionsModalOpen: false,
|
||||
isConfirmSearchModalOpen: false,
|
||||
searchType: null,
|
||||
allSelected: false,
|
||||
allUnselected: false,
|
||||
lastToggled: null,
|
||||
selectedState: {}
|
||||
lastToggled: null
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.setJumpBarItems();
|
||||
this.setSelectedState();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
@@ -81,7 +70,6 @@ class DiscoverMovie extends Component {
|
||||
hasDifferentItemsOrOrder(prevProps.items, items)
|
||||
) {
|
||||
this.setJumpBarItems();
|
||||
this.setSelectedState();
|
||||
}
|
||||
|
||||
if (this.state.jumpToCharacter != null) {
|
||||
@@ -96,48 +84,6 @@ class DiscoverMovie extends Component {
|
||||
this.setState({ scroller: ref });
|
||||
}
|
||||
|
||||
getSelectedIds = () => {
|
||||
if (this.state.allUnselected) {
|
||||
return [];
|
||||
}
|
||||
return getSelectedIds(this.state.selectedState);
|
||||
}
|
||||
|
||||
setSelectedState() {
|
||||
const {
|
||||
items
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
selectedState
|
||||
} = this.state;
|
||||
|
||||
const newSelectedState = {};
|
||||
|
||||
items.forEach((movie) => {
|
||||
const isItemSelected = selectedState[movie.tmdbId];
|
||||
|
||||
if (isItemSelected) {
|
||||
newSelectedState[movie.tmdbId] = isItemSelected;
|
||||
} else {
|
||||
newSelectedState[movie.tmdbId] = false;
|
||||
}
|
||||
});
|
||||
|
||||
const selectedCount = getSelectedIds(newSelectedState).length;
|
||||
const newStateCount = Object.keys(newSelectedState).length;
|
||||
let isAllSelected = false;
|
||||
let isAllUnselected = false;
|
||||
|
||||
if (selectedCount === 0) {
|
||||
isAllUnselected = true;
|
||||
} else if (selectedCount === newStateCount) {
|
||||
isAllSelected = true;
|
||||
}
|
||||
|
||||
this.setState({ selectedState: newSelectedState, allSelected: isAllSelected, allUnselected: isAllUnselected });
|
||||
}
|
||||
|
||||
setJumpBarItems() {
|
||||
const {
|
||||
items,
|
||||
@@ -205,32 +151,6 @@ class DiscoverMovie extends Component {
|
||||
this.setState({ jumpToCharacter });
|
||||
}
|
||||
|
||||
onSelectAllChange = ({ value }) => {
|
||||
this.setState(selectAll(this.state.selectedState, value));
|
||||
}
|
||||
|
||||
onSelectAllPress = () => {
|
||||
this.onSelectAllChange({ value: !this.state.allSelected });
|
||||
}
|
||||
|
||||
onImportListSyncPress = () => {
|
||||
this.props.onImportListSyncPress();
|
||||
}
|
||||
|
||||
onSelectedChange = ({ id, value, shiftKey = false }) => {
|
||||
this.setState((state) => {
|
||||
return toggleSelected(state, this.props.items, id, value, shiftKey, 'tmdbId');
|
||||
});
|
||||
}
|
||||
|
||||
onAddMoviesPress = ({ addOptions }) => {
|
||||
this.props.onAddMoviesPress({ ids: this.getSelectedIds(), addOptions });
|
||||
}
|
||||
|
||||
onExcludeMoviesPress = () => {
|
||||
this.props.onExcludeMoviesPress({ ids: this.getSelectedIds() });
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
@@ -252,8 +172,6 @@ class DiscoverMovie extends Component {
|
||||
onFilterSelect,
|
||||
onViewSelect,
|
||||
onScroll,
|
||||
onAddMoviesPress,
|
||||
isSyncingLists,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
@@ -262,14 +180,9 @@ class DiscoverMovie extends Component {
|
||||
jumpBarItems,
|
||||
jumpToCharacter,
|
||||
isPosterOptionsModalOpen,
|
||||
isOverviewOptionsModalOpen,
|
||||
selectedState,
|
||||
allSelected,
|
||||
allUnselected
|
||||
isOverviewOptionsModalOpen
|
||||
} = this.state;
|
||||
|
||||
const selectedMovieIds = this.getSelectedIds();
|
||||
|
||||
const ViewComponent = getViewComponent(view);
|
||||
const isLoaded = !!(!error && isPopulated && items.length && scroller);
|
||||
const hasNoMovie = !totalItems;
|
||||
@@ -277,22 +190,6 @@ class DiscoverMovie extends Component {
|
||||
return (
|
||||
<PageContent>
|
||||
<PageToolbar>
|
||||
<PageToolbarSection>
|
||||
<PageToolbarButton
|
||||
label='Refresh Lists'
|
||||
iconName={icons.REFRESH}
|
||||
isSpinning={isSyncingLists}
|
||||
isDisabled={hasNoMovie}
|
||||
onPress={this.onImportListSyncPress}
|
||||
/>
|
||||
<PageToolbarButton
|
||||
label={allSelected ? translate('UnselectAll') : translate('SelectAll')}
|
||||
iconName={icons.CHECK_SQUARE}
|
||||
isDisabled={hasNoMovie}
|
||||
onPress={this.onSelectAllPress}
|
||||
/>
|
||||
</PageToolbarSection>
|
||||
|
||||
<PageToolbarSection
|
||||
alignContent={align.RIGHT}
|
||||
collapseButtons={false}
|
||||
@@ -302,10 +199,9 @@ class DiscoverMovie extends Component {
|
||||
<TableOptionsModalWrapper
|
||||
{...otherProps}
|
||||
columns={columns}
|
||||
optionsComponent={DiscoverMovieTableOptionsConnector}
|
||||
>
|
||||
<PageToolbarButton
|
||||
label={translate('Options')}
|
||||
label="Options"
|
||||
iconName={icons.TABLE}
|
||||
/>
|
||||
</TableOptionsModalWrapper> :
|
||||
@@ -315,8 +211,9 @@ class DiscoverMovie extends Component {
|
||||
{
|
||||
view === 'posters' ?
|
||||
<PageToolbarButton
|
||||
label={translate('Options')}
|
||||
label="Options"
|
||||
iconName={icons.POSTER}
|
||||
isDisabled={hasNoMovie}
|
||||
onPress={this.onPosterOptionsPress}
|
||||
/> :
|
||||
null
|
||||
@@ -325,8 +222,9 @@ class DiscoverMovie extends Component {
|
||||
{
|
||||
view === 'overview' ?
|
||||
<PageToolbarButton
|
||||
label={translate('Options')}
|
||||
label="Options"
|
||||
iconName={icons.OVERVIEW}
|
||||
isDisabled={hasNoMovie}
|
||||
onPress={this.onOverviewOptionsPress}
|
||||
/> :
|
||||
null
|
||||
@@ -337,20 +235,20 @@ class DiscoverMovie extends Component {
|
||||
<PageToolbarSeparator />
|
||||
}
|
||||
|
||||
<DiscoverMovieViewMenu
|
||||
<AddListMovieViewMenu
|
||||
view={view}
|
||||
isDisabled={hasNoMovie}
|
||||
onViewSelect={onViewSelect}
|
||||
/>
|
||||
|
||||
<DiscoverMovieSortMenu
|
||||
<AddListMovieSortMenu
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
isDisabled={hasNoMovie}
|
||||
onSortSelect={onSortSelect}
|
||||
/>
|
||||
|
||||
<DiscoverMovieFilterMenu
|
||||
<AddListMovieFilterMenu
|
||||
selectedFilterKey={selectedFilterKey}
|
||||
filters={filters}
|
||||
customFilters={customFilters}
|
||||
@@ -361,7 +259,7 @@ class DiscoverMovie extends Component {
|
||||
</PageToolbar>
|
||||
|
||||
<div className={styles.pageContentBodyWrapper}>
|
||||
<PageContentBody
|
||||
<PageContentBodyConnector
|
||||
registerScroller={this.setScrollerRef}
|
||||
className={styles.contentBody}
|
||||
innerClassName={styles[`${view}InnerContentBody`]}
|
||||
@@ -374,9 +272,7 @@ class DiscoverMovie extends Component {
|
||||
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
<div>
|
||||
{translate('UnableToLoadMovies')}
|
||||
</div>
|
||||
<div>Unable to load movies</div>
|
||||
}
|
||||
|
||||
{
|
||||
@@ -389,11 +285,6 @@ class DiscoverMovie extends Component {
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
jumpToCharacter={jumpToCharacter}
|
||||
allSelected={allSelected}
|
||||
allUnselected={allUnselected}
|
||||
onSelectedChange={this.onSelectedChange}
|
||||
onSelectAllChange={this.onSelectAllChange}
|
||||
selectedState={selectedState}
|
||||
{...otherProps}
|
||||
/>
|
||||
</div>
|
||||
@@ -401,9 +292,11 @@ class DiscoverMovie extends Component {
|
||||
|
||||
{
|
||||
!error && isPopulated && !items.length &&
|
||||
<NoDiscoverMovie totalItems={totalItems} />
|
||||
<div className={styles.message}>
|
||||
<div className={styles.noResults}>Couldn't find any results</div>
|
||||
</div>
|
||||
}
|
||||
</PageContentBody>
|
||||
</PageContentBodyConnector>
|
||||
|
||||
{
|
||||
isLoaded && !!jumpBarItems.order.length &&
|
||||
@@ -414,21 +307,12 @@ class DiscoverMovie extends Component {
|
||||
}
|
||||
</div>
|
||||
|
||||
{
|
||||
isLoaded &&
|
||||
<DiscoverMovieFooterConnector
|
||||
selectedIds={selectedMovieIds}
|
||||
onAddMoviesPress={this.onAddMoviesPress}
|
||||
onExcludeMoviesPress={this.onExcludeMoviesPress}
|
||||
/>
|
||||
}
|
||||
|
||||
<DiscoverMoviePosterOptionsModal
|
||||
<AddListMoviePosterOptionsModal
|
||||
isOpen={isPosterOptionsModalOpen}
|
||||
onModalClose={this.onPosterOptionsModalClose}
|
||||
/>
|
||||
|
||||
<DiscoverMovieOverviewOptionsModal
|
||||
<AddListMovieOverviewOptionsModal
|
||||
isOpen={isOverviewOptionsModalOpen}
|
||||
onModalClose={this.onOverviewOptionsModalClose}
|
||||
/>
|
||||
@@ -437,7 +321,7 @@ class DiscoverMovie extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
DiscoverMovie.propTypes = {
|
||||
AddListMovie.propTypes = {
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
isPopulated: PropTypes.bool.isRequired,
|
||||
error: PropTypes.object,
|
||||
@@ -450,15 +334,11 @@ DiscoverMovie.propTypes = {
|
||||
sortKey: PropTypes.string,
|
||||
sortDirection: PropTypes.oneOf(sortDirections.all),
|
||||
view: PropTypes.string.isRequired,
|
||||
isSyncingLists: PropTypes.bool.isRequired,
|
||||
isSmallScreen: PropTypes.bool.isRequired,
|
||||
onSortSelect: PropTypes.func.isRequired,
|
||||
onFilterSelect: PropTypes.func.isRequired,
|
||||
onViewSelect: PropTypes.func.isRequired,
|
||||
onScroll: PropTypes.func.isRequired,
|
||||
onAddMoviesPress: PropTypes.func.isRequired,
|
||||
onExcludeMoviesPress: PropTypes.func.isRequired,
|
||||
onImportListSyncPress: PropTypes.func.isRequired
|
||||
onScroll: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default DiscoverMovie;
|
||||
export default AddListMovie;
|
||||
113
frontend/src/AddMovie/AddListMovie/AddListMovieConnector.js
Normal file
113
frontend/src/AddMovie/AddListMovie/AddListMovieConnector.js
Normal file
@@ -0,0 +1,113 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import createAddMovieClientSideCollectionItemsSelector from 'Store/Selectors/createAddMovieClientSideCollectionItemsSelector';
|
||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||
import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
|
||||
import { fetchListMovies, clearAddMovie, setListMovieSort, setListMovieFilter, setListMovieView, setListMovieTableOption } from 'Store/Actions/addMovieActions';
|
||||
import scrollPositions from 'Store/scrollPositions';
|
||||
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
|
||||
import withScrollPosition from 'Components/withScrollPosition';
|
||||
import AddListMovie from './AddListMovie';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createAddMovieClientSideCollectionItemsSelector('addMovie'),
|
||||
createDimensionsSelector(),
|
||||
(
|
||||
movies,
|
||||
dimensionsState
|
||||
) => {
|
||||
return {
|
||||
...movies,
|
||||
isSmallScreen: dimensionsState.isSmallScreen
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function createMapDispatchToProps(dispatch, props) {
|
||||
return {
|
||||
dispatchFetchRootFolders() {
|
||||
dispatch(fetchRootFolders());
|
||||
},
|
||||
|
||||
dispatchFetchListMovies() {
|
||||
dispatch(fetchListMovies());
|
||||
},
|
||||
|
||||
onTableOptionChange(payload) {
|
||||
dispatch(setListMovieTableOption(payload));
|
||||
},
|
||||
|
||||
onSortSelect(sortKey) {
|
||||
dispatch(setListMovieSort({ sortKey }));
|
||||
},
|
||||
|
||||
onFilterSelect(selectedFilterKey) {
|
||||
dispatch(setListMovieFilter({ selectedFilterKey }));
|
||||
},
|
||||
|
||||
dispatchSetListMovieView(view) {
|
||||
dispatch(setListMovieView({ view }));
|
||||
},
|
||||
|
||||
dispatchClearListMovie() {
|
||||
dispatch(clearAddMovie());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class AddListMovieConnector extends Component {
|
||||
|
||||
componentDidMount() {
|
||||
registerPagePopulator(this.repopulate);
|
||||
this.props.dispatchFetchRootFolders();
|
||||
this.props.dispatchFetchListMovies();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.dispatchClearListMovie();
|
||||
unregisterPagePopulator(this.repopulate);
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onViewSelect = (view) => {
|
||||
this.props.dispatchSetListMovieView(view);
|
||||
}
|
||||
|
||||
onScroll = ({ scrollTop }) => {
|
||||
scrollPositions.addMovie = scrollTop;
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
return (
|
||||
<AddListMovie
|
||||
{...this.props}
|
||||
onViewSelect={this.onViewSelect}
|
||||
onScroll={this.onScroll}
|
||||
onSaveSelected={this.onSaveSelected}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AddListMovieConnector.propTypes = {
|
||||
isSmallScreen: PropTypes.bool.isRequired,
|
||||
view: PropTypes.string.isRequired,
|
||||
dispatchFetchRootFolders: PropTypes.func.isRequired,
|
||||
dispatchFetchListMovies: PropTypes.func.isRequired,
|
||||
dispatchClearListMovie: PropTypes.func.isRequired,
|
||||
dispatchSetListMovieView: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default withScrollPosition(
|
||||
connect(createMapStateToProps, createMapDispatchToProps)(AddListMovieConnector),
|
||||
'addMovie'
|
||||
);
|
||||
@@ -1,17 +1,17 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { setListMovieFilter } from 'Store/Actions/addMovieActions';
|
||||
import FilterModal from 'Components/Filter/FilterModal';
|
||||
import { setListMovieFilter } from 'Store/Actions/discoverMovieActions';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.discoverMovie.items,
|
||||
(state) => state.discoverMovie.filterBuilderProps,
|
||||
(state) => state.addMovie.items,
|
||||
(state) => state.addMovie.filterBuilderProps,
|
||||
(sectionItems, filterBuilderProps) => {
|
||||
return {
|
||||
sectionItems,
|
||||
filterBuilderProps,
|
||||
customFilterType: 'discoverMovie'
|
||||
customFilterType: 'addMovie'
|
||||
};
|
||||
}
|
||||
);
|
||||
@@ -2,13 +2,16 @@ import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import createDiscoverMovieSelector from 'Store/Selectors/createDiscoverMovieSelector';
|
||||
import createAddListMovieSelector from 'Store/Selectors/createAddListMovieSelector';
|
||||
import createMovieQualityProfileSelector from 'Store/Selectors/createMovieQualityProfileSelector';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createDiscoverMovieSelector(),
|
||||
createAddListMovieSelector(),
|
||||
createMovieQualityProfileSelector(),
|
||||
(
|
||||
movie
|
||||
movie,
|
||||
qualityProfile
|
||||
) => {
|
||||
|
||||
// If a movie is deleted this selector may fire before the parent
|
||||
@@ -21,13 +24,17 @@ function createMapStateToProps() {
|
||||
}
|
||||
|
||||
return {
|
||||
...movie
|
||||
...movie,
|
||||
qualityProfile
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
class DiscoverMovieItemConnector extends Component {
|
||||
const mapDispatchToProps = {
|
||||
};
|
||||
|
||||
class AddListMovieItemConnector extends Component {
|
||||
|
||||
//
|
||||
// Render
|
||||
@@ -52,9 +59,9 @@ class DiscoverMovieItemConnector extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
DiscoverMovieItemConnector.propTypes = {
|
||||
AddListMovieItemConnector.propTypes = {
|
||||
tmdbId: PropTypes.number,
|
||||
component: PropTypes.elementType.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps)(DiscoverMovieItemConnector);
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(AddListMovieItemConnector);
|
||||
@@ -1,10 +1,10 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import FilterMenu from 'Components/Menu/FilterMenu';
|
||||
import DiscoverMovieFilterModalConnector from 'DiscoverMovie/DiscoverMovieFilterModalConnector';
|
||||
import { align } from 'Helpers/Props';
|
||||
import FilterMenu from 'Components/Menu/FilterMenu';
|
||||
import AddListMovieFilterModalConnector from 'AddMovie/AddListMovie/AddListMovieFilterModalConnector';
|
||||
|
||||
function DiscoverMovieFilterMenu(props) {
|
||||
function AddListMovieFilterMenu(props) {
|
||||
const {
|
||||
selectedFilterKey,
|
||||
filters,
|
||||
@@ -20,13 +20,13 @@ function DiscoverMovieFilterMenu(props) {
|
||||
selectedFilterKey={selectedFilterKey}
|
||||
filters={filters}
|
||||
customFilters={customFilters}
|
||||
filterModalConnectorComponent={DiscoverMovieFilterModalConnector}
|
||||
filterModalConnectorComponent={AddListMovieFilterModalConnector}
|
||||
onFilterSelect={onFilterSelect}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
DiscoverMovieFilterMenu.propTypes = {
|
||||
AddListMovieFilterMenu.propTypes = {
|
||||
selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
||||
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
@@ -34,8 +34,8 @@ DiscoverMovieFilterMenu.propTypes = {
|
||||
onFilterSelect: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
DiscoverMovieFilterMenu.defaultProps = {
|
||||
AddListMovieFilterMenu.defaultProps = {
|
||||
showCustomFilters: false
|
||||
};
|
||||
|
||||
export default DiscoverMovieFilterMenu;
|
||||
export default AddListMovieFilterMenu;
|
||||
@@ -1,11 +1,11 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import MenuContent from 'Components/Menu/MenuContent';
|
||||
import SortMenu from 'Components/Menu/SortMenu';
|
||||
import SortMenuItem from 'Components/Menu/SortMenuItem';
|
||||
import { align, sortDirections } from 'Helpers/Props';
|
||||
import SortMenu from 'Components/Menu/SortMenu';
|
||||
import MenuContent from 'Components/Menu/MenuContent';
|
||||
import SortMenuItem from 'Components/Menu/SortMenuItem';
|
||||
|
||||
function DiscoverMovieSortMenu(props) {
|
||||
function AddListMovieSortMenu(props) {
|
||||
const {
|
||||
sortKey,
|
||||
sortDirection,
|
||||
@@ -63,52 +63,16 @@ function DiscoverMovieSortMenu(props) {
|
||||
>
|
||||
Physical Release
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
name="digitalRelease"
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
Digital Release
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
name="runtime"
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
Runtime
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
name="ratings"
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
Rating
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
name="certification"
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
Certification
|
||||
</SortMenuItem>
|
||||
</MenuContent>
|
||||
</SortMenu>
|
||||
);
|
||||
}
|
||||
|
||||
DiscoverMovieSortMenu.propTypes = {
|
||||
AddListMovieSortMenu.propTypes = {
|
||||
sortKey: PropTypes.string,
|
||||
sortDirection: PropTypes.oneOf(sortDirections.all),
|
||||
isDisabled: PropTypes.bool.isRequired,
|
||||
onSortSelect: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default DiscoverMovieSortMenu;
|
||||
export default AddListMovieSortMenu;
|
||||
@@ -1,11 +1,11 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import MenuContent from 'Components/Menu/MenuContent';
|
||||
import ViewMenu from 'Components/Menu/ViewMenu';
|
||||
import ViewMenuItem from 'Components/Menu/ViewMenuItem';
|
||||
import { align } from 'Helpers/Props';
|
||||
import ViewMenu from 'Components/Menu/ViewMenu';
|
||||
import MenuContent from 'Components/Menu/MenuContent';
|
||||
import ViewMenuItem from 'Components/Menu/ViewMenuItem';
|
||||
|
||||
function DiscoverMovieViewMenu(props) {
|
||||
function AddListMovieViewMenu(props) {
|
||||
const {
|
||||
view,
|
||||
isDisabled,
|
||||
@@ -46,10 +46,10 @@ function DiscoverMovieViewMenu(props) {
|
||||
);
|
||||
}
|
||||
|
||||
DiscoverMovieViewMenu.propTypes = {
|
||||
AddListMovieViewMenu.propTypes = {
|
||||
view: PropTypes.string.isRequired,
|
||||
isDisabled: PropTypes.bool.isRequired,
|
||||
onViewSelect: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default DiscoverMovieViewMenu;
|
||||
export default AddListMovieViewMenu;
|
||||
@@ -1,5 +1,13 @@
|
||||
$hoverScale: 1.05;
|
||||
|
||||
.container {
|
||||
&:hover {
|
||||
.content {
|
||||
background-color: $tableRowHoverBackgroundColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
@@ -9,13 +17,6 @@ $hoverScale: 1.05;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.editorSelect {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 5px;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.posterContainer {
|
||||
position: relative;
|
||||
}
|
||||
@@ -32,16 +33,17 @@ $hoverScale: 1.05;
|
||||
}
|
||||
}
|
||||
|
||||
.alreadyExistsIcon {
|
||||
margin-left: 10px;
|
||||
color: #37bc9b;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.exclusionIcon {
|
||||
margin-left: 10px;
|
||||
color: $dangerColor;
|
||||
pointer-events: all;
|
||||
.ended {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-width: 0 25px 25px 0;
|
||||
border-style: solid;
|
||||
border-color: transparent $dangerColor transparent transparent;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.info {
|
||||
@@ -56,15 +58,12 @@ $hoverScale: 1.05;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex: 0 0 auto;
|
||||
margin-bottom: 5px;
|
||||
margin-bottom: 10px;
|
||||
font-weight: 300;
|
||||
font-size: 30px;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
.lists {
|
||||
display: flex;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.title {
|
||||
@add-mixin truncate;
|
||||
composes: link;
|
||||
@@ -72,7 +71,6 @@ $hoverScale: 1.05;
|
||||
flex: 1 0 1px;
|
||||
font-weight: 300;
|
||||
font-size: 30px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.actions {
|
||||
@@ -0,0 +1,156 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import TextTruncate from 'react-text-truncate';
|
||||
import dimensions from 'Styles/Variables/dimensions';
|
||||
import fonts from 'Styles/Variables/fonts';
|
||||
import MoviePoster from 'Movie/MoviePoster';
|
||||
import Link from 'Components/Link/Link';
|
||||
import AddNewMovieModal from 'AddMovie/AddNewMovie/AddNewMovieModal';
|
||||
import styles from './AddListMovieOverview.css';
|
||||
|
||||
const columnPadding = parseInt(dimensions.movieIndexColumnPadding);
|
||||
const columnPaddingSmallScreen = parseInt(dimensions.movieIndexColumnPaddingSmallScreen);
|
||||
const defaultFontSize = parseInt(fonts.defaultFontSize);
|
||||
const lineHeight = parseFloat(fonts.lineHeight);
|
||||
|
||||
// Hardcoded height beased on line-height of 32 + bottom margin of 10.
|
||||
// Less side-effecty than using react-measure.
|
||||
const titleRowHeight = 42;
|
||||
|
||||
function getContentHeight(rowHeight, isSmallScreen) {
|
||||
const padding = isSmallScreen ? columnPaddingSmallScreen : columnPadding;
|
||||
|
||||
return rowHeight - padding;
|
||||
}
|
||||
|
||||
class AddListMovieOverview extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
isNewAddMovieModalOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onPress = () => {
|
||||
this.setState({ isNewAddMovieModalOpen: true });
|
||||
}
|
||||
|
||||
onAddMovieModalClose = () => {
|
||||
this.setState({ isNewAddMovieModalOpen: false });
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
tmdbId,
|
||||
title,
|
||||
titleSlug,
|
||||
folder,
|
||||
year,
|
||||
overview,
|
||||
images,
|
||||
posterWidth,
|
||||
posterHeight,
|
||||
rowHeight,
|
||||
isSmallScreen,
|
||||
isExistingMovie
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
isNewAddMovieModalOpen
|
||||
} = this.state;
|
||||
|
||||
const elementStyle = {
|
||||
width: `${posterWidth}px`,
|
||||
height: `${posterHeight}px`
|
||||
};
|
||||
|
||||
const linkProps = isExistingMovie ? { to: `/movie/${titleSlug}` } : { onPress: this.onPress };
|
||||
|
||||
const contentHeight = getContentHeight(rowHeight, isSmallScreen);
|
||||
const overviewHeight = contentHeight - titleRowHeight;
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<Link
|
||||
className={styles.content}
|
||||
{...linkProps}
|
||||
>
|
||||
<div className={styles.poster}>
|
||||
<div className={styles.posterContainer}>
|
||||
|
||||
<MoviePoster
|
||||
className={styles.poster}
|
||||
style={elementStyle}
|
||||
images={images}
|
||||
size={250}
|
||||
lazy={false}
|
||||
overflow={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.info} style={{ maxHeight: contentHeight }}>
|
||||
<div className={styles.titleRow}>
|
||||
{title} ({year})
|
||||
</div>
|
||||
|
||||
<div className={styles.details}>
|
||||
<TextTruncate
|
||||
line={Math.floor(overviewHeight / (defaultFontSize * lineHeight))}
|
||||
text={overview}
|
||||
/>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<AddNewMovieModal
|
||||
isOpen={isNewAddMovieModalOpen && !isExistingMovie}
|
||||
tmdbId={tmdbId}
|
||||
title={title}
|
||||
year={year}
|
||||
overview={overview}
|
||||
folder={folder}
|
||||
images={images}
|
||||
onModalClose={this.onAddMovieModalClose}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AddListMovieOverview.propTypes = {
|
||||
tmdbId: PropTypes.number.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
folder: PropTypes.string.isRequired,
|
||||
year: PropTypes.number.isRequired,
|
||||
overview: PropTypes.string.isRequired,
|
||||
monitored: PropTypes.bool.isRequired,
|
||||
status: PropTypes.string.isRequired,
|
||||
titleSlug: PropTypes.string.isRequired,
|
||||
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
posterWidth: PropTypes.number.isRequired,
|
||||
posterHeight: PropTypes.number.isRequired,
|
||||
rowHeight: PropTypes.number.isRequired,
|
||||
overviewOptions: PropTypes.object.isRequired,
|
||||
showRelativeDates: PropTypes.bool.isRequired,
|
||||
shortDateFormat: PropTypes.string.isRequired,
|
||||
longDateFormat: PropTypes.string.isRequired,
|
||||
timeFormat: PropTypes.string.isRequired,
|
||||
isSmallScreen: PropTypes.bool.isRequired,
|
||||
isExistingMovie: PropTypes.bool.isRequired,
|
||||
isExclusionMovie: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
export default AddListMovieOverview;
|
||||
@@ -0,0 +1,23 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import createExistingMovieSelector from 'Store/Selectors/createExistingMovieSelector';
|
||||
import createExclusionMovieSelector from 'Store/Selectors/createExclusionMovieSelector';
|
||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||
import AddListMovieOverview from './AddListMovieOverview';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createExistingMovieSelector(),
|
||||
createExclusionMovieSelector(),
|
||||
createDimensionsSelector(),
|
||||
(isExistingMovie, isExclusionMovie, dimensions) => {
|
||||
return {
|
||||
isExistingMovie,
|
||||
isExclusionMovie,
|
||||
isSmallScreen: dimensions.isSmallScreen
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(createMapStateToProps)(AddListMovieOverview);
|
||||
@@ -1,13 +1,13 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { Grid, WindowScroller } from 'react-virtualized';
|
||||
import Measure from 'Components/Measure';
|
||||
import DiscoverMovieItemConnector from 'DiscoverMovie/DiscoverMovieItemConnector';
|
||||
import dimensions from 'Styles/Variables/dimensions';
|
||||
import getIndexOfFirstCharacter from 'Utilities/Array/getIndexOfFirstCharacter';
|
||||
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
|
||||
import DiscoverMovieOverviewConnector from './DiscoverMovieOverviewConnector';
|
||||
import styles from './DiscoverMovieOverviews.css';
|
||||
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItems';
|
||||
import dimensions from 'Styles/Variables/dimensions';
|
||||
import Measure from 'Components/Measure';
|
||||
import AddListMovieItemConnector from 'AddMovie/AddListMovie/AddListMovieItemConnector';
|
||||
import AddListMovieOverviewConnector from './AddListMovieOverviewConnector';
|
||||
import styles from './AddListMovieOverviews.css';
|
||||
|
||||
// Poster container dimensions
|
||||
const columnPadding = parseInt(dimensions.movieIndexColumnPadding);
|
||||
@@ -41,7 +41,7 @@ function calculatePosterHeight(posterWidth) {
|
||||
return Math.ceil((250 / 170) * posterWidth);
|
||||
}
|
||||
|
||||
class DiscoverMovieOverviews extends Component {
|
||||
class AddListMovieOverviews extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
@@ -81,7 +81,7 @@ class DiscoverMovieOverviews extends Component {
|
||||
if (this._grid &&
|
||||
(prevState.width !== width ||
|
||||
prevState.rowHeight !== rowHeight ||
|
||||
hasDifferentItemsOrOrder(prevProps.items, items, 'tmdbId'))) {
|
||||
hasDifferentItemsOrOrder(prevProps.items, items))) {
|
||||
// recomputeGridSize also forces Grid to discard its cache of rendered cells
|
||||
this._grid.recomputeGridSize();
|
||||
}
|
||||
@@ -133,9 +133,7 @@ class DiscoverMovieOverviews extends Component {
|
||||
shortDateFormat,
|
||||
longDateFormat,
|
||||
timeFormat,
|
||||
isSmallScreen,
|
||||
selectedState,
|
||||
onSelectedChange
|
||||
isSmallScreen
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
@@ -156,9 +154,9 @@ class DiscoverMovieOverviews extends Component {
|
||||
key={key}
|
||||
style={style}
|
||||
>
|
||||
<DiscoverMovieItemConnector
|
||||
key={movie.tmdbId}
|
||||
component={DiscoverMovieOverviewConnector}
|
||||
<AddListMovieItemConnector
|
||||
key={movie.id}
|
||||
component={AddListMovieOverviewConnector}
|
||||
sortKey={sortKey}
|
||||
posterWidth={posterWidth}
|
||||
posterHeight={posterHeight}
|
||||
@@ -170,8 +168,6 @@ class DiscoverMovieOverviews extends Component {
|
||||
timeFormat={timeFormat}
|
||||
isSmallScreen={isSmallScreen}
|
||||
movieId={movie.tmdbId}
|
||||
isSelected={selectedState[movie.tmdbId]}
|
||||
onSelectedChange={onSelectedChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -191,8 +187,7 @@ class DiscoverMovieOverviews extends Component {
|
||||
const {
|
||||
isSmallScreen,
|
||||
scroller,
|
||||
items,
|
||||
selectedState
|
||||
items
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
@@ -229,7 +224,6 @@ class DiscoverMovieOverviews extends Component {
|
||||
scrollTop={scrollTop}
|
||||
overscanRowCount={2}
|
||||
cellRenderer={this.cellRenderer}
|
||||
selectedState={selectedState}
|
||||
scrollToAlignment={'start'}
|
||||
isScrollingOptout={true}
|
||||
/>
|
||||
@@ -243,7 +237,7 @@ class DiscoverMovieOverviews extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
DiscoverMovieOverviews.propTypes = {
|
||||
AddListMovieOverviews.propTypes = {
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
sortKey: PropTypes.string,
|
||||
overviewOptions: PropTypes.object.isRequired,
|
||||
@@ -253,9 +247,7 @@ DiscoverMovieOverviews.propTypes = {
|
||||
shortDateFormat: PropTypes.string.isRequired,
|
||||
longDateFormat: PropTypes.string.isRequired,
|
||||
isSmallScreen: PropTypes.bool.isRequired,
|
||||
timeFormat: PropTypes.string.isRequired,
|
||||
selectedState: PropTypes.object.isRequired,
|
||||
onSelectedChange: PropTypes.func.isRequired
|
||||
timeFormat: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
export default DiscoverMovieOverviews;
|
||||
export default AddListMovieOverviews;
|
||||
@@ -1,12 +1,12 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||
import DiscoverMovieOverviews from './DiscoverMovieOverviews';
|
||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||
import AddListMovieOverviews from './AddListMovieOverviews';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.discoverMovie.overviewOptions,
|
||||
(state) => state.addMovie.overviewOptions,
|
||||
createUISettingsSelector(),
|
||||
createDimensionsSelector(),
|
||||
(overviewOptions, uiSettings, dimensions) => {
|
||||
@@ -22,4 +22,4 @@ function createMapStateToProps() {
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(createMapStateToProps)(DiscoverMovieOverviews);
|
||||
export default connect(createMapStateToProps)(AddListMovieOverviews);
|
||||
@@ -1,15 +1,15 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import DiscoverMoviePosterOptionsModalContentConnector from './DiscoverMoviePosterOptionsModalContentConnector';
|
||||
import AddListMovieOverviewOptionsModalContentConnector from './AddListMovieOverviewOptionsModalContentConnector';
|
||||
|
||||
function DiscoverMoviePosterOptionsModal({ isOpen, onModalClose, ...otherProps }) {
|
||||
function AddListMovieOverviewOptionsModal({ isOpen, onModalClose, ...otherProps }) {
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onModalClose={onModalClose}
|
||||
>
|
||||
<DiscoverMoviePosterOptionsModalContentConnector
|
||||
<AddListMovieOverviewOptionsModalContentConnector
|
||||
{...otherProps}
|
||||
onModalClose={onModalClose}
|
||||
/>
|
||||
@@ -17,9 +17,9 @@ function DiscoverMoviePosterOptionsModal({ isOpen, onModalClose, ...otherProps }
|
||||
);
|
||||
}
|
||||
|
||||
DiscoverMoviePosterOptionsModal.propTypes = {
|
||||
AddListMovieOverviewOptionsModal.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default DiscoverMoviePosterOptionsModal;
|
||||
export default AddListMovieOverviewOptionsModal;
|
||||
@@ -0,0 +1,132 @@
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { inputTypes } from 'Helpers/Props';
|
||||
import Button from 'Components/Link/Button';
|
||||
import Form from 'Components/Form/Form';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormLabel from 'Components/Form/FormLabel';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
|
||||
const posterSizeOptions = [
|
||||
{ key: 'small', value: 'Small' },
|
||||
{ key: 'medium', value: 'Medium' },
|
||||
{ key: 'large', value: 'Large' }
|
||||
];
|
||||
|
||||
class AddListMovieOverviewOptionsModalContent extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
size: props.size,
|
||||
showStudio: props.showStudio
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const {
|
||||
size,
|
||||
showStudio
|
||||
} = this.props;
|
||||
|
||||
const state = {};
|
||||
|
||||
if (size !== prevProps.size) {
|
||||
state.size = size;
|
||||
}
|
||||
|
||||
if (showStudio !== prevProps.showStudio) {
|
||||
state.showStudio = showStudio;
|
||||
}
|
||||
|
||||
if (!_.isEmpty(state)) {
|
||||
this.setState(state);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onChangeOverviewOption = ({ name, value }) => {
|
||||
this.setState({
|
||||
[name]: value
|
||||
}, () => {
|
||||
this.props.onChangeOverviewOption({ [name]: value });
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
onModalClose
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
size,
|
||||
showStudio
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
Overview Options
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<Form>
|
||||
<FormGroup>
|
||||
<FormLabel>Poster Size</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.SELECT}
|
||||
name="size"
|
||||
value={size}
|
||||
values={posterSizeOptions}
|
||||
onChange={this.onChangeOverviewOption}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>Show Studio</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="showStudio"
|
||||
value={showStudio}
|
||||
onChange={this.onChangeOverviewOption}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button
|
||||
onPress={onModalClose}
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AddListMovieOverviewOptionsModalContent.propTypes = {
|
||||
size: PropTypes.string.isRequired,
|
||||
showStudio: PropTypes.bool.isRequired,
|
||||
onChangeOverviewOption: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default AddListMovieOverviewOptionsModalContent;
|
||||
@@ -0,0 +1,23 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { setListMovieOverviewOption } from 'Store/Actions/addMovieActions';
|
||||
import AddListMovieOverviewOptionsModalContent from './AddListMovieOverviewOptionsModalContent';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.addMovie,
|
||||
(addMovie) => {
|
||||
return addMovie.overviewOptions;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function createMapDispatchToProps(dispatch, props) {
|
||||
return {
|
||||
onChangeOverviewOption(payload) {
|
||||
dispatch(setListMovieOverviewOption(payload));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(createMapStateToProps, createMapDispatchToProps)(AddListMovieOverviewOptionsModalContent);
|
||||
@@ -1,5 +1,9 @@
|
||||
$hoverScale: 1.05;
|
||||
|
||||
.container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.content {
|
||||
transition: all 200ms ease-in;
|
||||
|
||||
@@ -27,11 +31,6 @@ $hoverScale: 1.05;
|
||||
background-color: $defaultColor;
|
||||
}
|
||||
|
||||
.externalLinks {
|
||||
margin-right: 0.5em;
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
.overlayTitle {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@@ -55,11 +54,10 @@ $hoverScale: 1.05;
|
||||
font-size: $smallFontSize;
|
||||
}
|
||||
|
||||
.excluded {
|
||||
.ended {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-width: 0 25px 25px 0;
|
||||
@@ -81,13 +79,6 @@ $hoverScale: 1.05;
|
||||
transition: opacity 0;
|
||||
}
|
||||
|
||||
.editorSelect {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.action {
|
||||
composes: button from '~Components/Link/IconButton.css';
|
||||
|
||||
159
frontend/src/AddMovie/AddListMovie/Posters/AddListMoviePoster.js
Normal file
159
frontend/src/AddMovie/AddListMovie/Posters/AddListMoviePoster.js
Normal file
@@ -0,0 +1,159 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Link from 'Components/Link/Link';
|
||||
import MoviePoster from 'Movie/MoviePoster';
|
||||
import AddNewMovieModal from 'AddMovie/AddNewMovie/AddNewMovieModal';
|
||||
import styles from './AddListMoviePoster.css';
|
||||
|
||||
class AddListMoviePoster extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
hasPosterError: false,
|
||||
isNewAddMovieModalOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onPress = () => {
|
||||
this.setState({ isNewAddMovieModalOpen: true });
|
||||
}
|
||||
|
||||
onAddMovieModalClose = () => {
|
||||
this.setState({ isNewAddMovieModalOpen: false });
|
||||
}
|
||||
|
||||
onPosterLoad = () => {
|
||||
if (this.state.hasPosterError) {
|
||||
this.setState({ hasPosterError: false });
|
||||
}
|
||||
}
|
||||
|
||||
onPosterLoadError = () => {
|
||||
if (!this.state.hasPosterError) {
|
||||
this.setState({ hasPosterError: true });
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
tmdbId,
|
||||
title,
|
||||
year,
|
||||
overview,
|
||||
folder,
|
||||
status,
|
||||
titleSlug,
|
||||
images,
|
||||
posterWidth,
|
||||
posterHeight,
|
||||
showTitle,
|
||||
isExistingMovie
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
hasPosterError,
|
||||
isNewAddMovieModalOpen
|
||||
} = this.state;
|
||||
|
||||
const linkProps = isExistingMovie ? { to: `/movie/${titleSlug}` } : { onPress: this.onPress };
|
||||
|
||||
const elementStyle = {
|
||||
width: `${posterWidth}px`,
|
||||
height: `${posterHeight}px`
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.content}>
|
||||
<div className={styles.posterContainer}>
|
||||
{
|
||||
status === 'ended' &&
|
||||
<div
|
||||
className={styles.ended}
|
||||
title="Ended"
|
||||
/>
|
||||
}
|
||||
|
||||
<Link
|
||||
className={styles.link}
|
||||
style={elementStyle}
|
||||
{...linkProps}
|
||||
>
|
||||
<MoviePoster
|
||||
className={styles.poster}
|
||||
style={elementStyle}
|
||||
images={images}
|
||||
size={250}
|
||||
lazy={false}
|
||||
overflow={true}
|
||||
onError={this.onPosterLoadError}
|
||||
onLoad={this.onPosterLoad}
|
||||
/>
|
||||
|
||||
{
|
||||
hasPosterError &&
|
||||
<div className={styles.overlayTitle}>
|
||||
{title}
|
||||
</div>
|
||||
}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{
|
||||
showTitle &&
|
||||
<div className={styles.title}>
|
||||
{title}
|
||||
</div>
|
||||
}
|
||||
|
||||
<AddNewMovieModal
|
||||
isOpen={isNewAddMovieModalOpen && !isExistingMovie}
|
||||
tmdbId={tmdbId}
|
||||
title={title}
|
||||
year={year}
|
||||
overview={overview}
|
||||
folder={folder}
|
||||
images={images}
|
||||
onModalClose={this.onAddMovieModalClose}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AddListMoviePoster.propTypes = {
|
||||
tmdbId: PropTypes.number.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
year: PropTypes.number.isRequired,
|
||||
overview: PropTypes.string.isRequired,
|
||||
folder: PropTypes.string.isRequired,
|
||||
status: PropTypes.string.isRequired,
|
||||
titleSlug: PropTypes.string.isRequired,
|
||||
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
posterWidth: PropTypes.number.isRequired,
|
||||
posterHeight: PropTypes.number.isRequired,
|
||||
showTitle: PropTypes.bool.isRequired,
|
||||
showRelativeDates: PropTypes.bool.isRequired,
|
||||
shortDateFormat: PropTypes.string.isRequired,
|
||||
timeFormat: PropTypes.string.isRequired,
|
||||
isExistingMovie: PropTypes.bool.isRequired,
|
||||
isExclusionMovie: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
AddListMoviePoster.defaultProps = {
|
||||
statistics: {
|
||||
movieFileCount: 0
|
||||
}
|
||||
};
|
||||
|
||||
export default AddListMoviePoster;
|
||||
@@ -0,0 +1,23 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import createExistingMovieSelector from 'Store/Selectors/createExistingMovieSelector';
|
||||
import createExclusionMovieSelector from 'Store/Selectors/createExclusionMovieSelector';
|
||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||
import AddListMoviePoster from './AddListMoviePoster';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createExistingMovieSelector(),
|
||||
createExclusionMovieSelector(),
|
||||
createDimensionsSelector(),
|
||||
(isExistingMovie, isExclusionMovie, dimensions) => {
|
||||
return {
|
||||
isExistingMovie,
|
||||
isExclusionMovie,
|
||||
isSmallScreen: dimensions.isSmallScreen
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(createMapStateToProps)(AddListMoviePoster);
|
||||
@@ -0,0 +1,30 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import styles from './AddListMoviePosterInfo.css';
|
||||
|
||||
function AddListMoviePosterInfo(props) {
|
||||
const {
|
||||
studio,
|
||||
sortKey
|
||||
} = props;
|
||||
|
||||
if (sortKey === 'studio' && studio) {
|
||||
return (
|
||||
<div className={styles.info}>
|
||||
{studio}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
AddListMoviePosterInfo.propTypes = {
|
||||
studio: PropTypes.string,
|
||||
sortKey: PropTypes.string.isRequired,
|
||||
showRelativeDates: PropTypes.bool.isRequired,
|
||||
shortDateFormat: PropTypes.string.isRequired,
|
||||
timeFormat: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
export default AddListMoviePosterInfo;
|
||||
@@ -1,3 +1,3 @@
|
||||
.lists {
|
||||
.grid {
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { Grid, WindowScroller } from 'react-virtualized';
|
||||
import Measure from 'Components/Measure';
|
||||
import DiscoverMovieItemConnector from 'DiscoverMovie/DiscoverMovieItemConnector';
|
||||
import dimensions from 'Styles/Variables/dimensions';
|
||||
import getIndexOfFirstCharacter from 'Utilities/Array/getIndexOfFirstCharacter';
|
||||
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
|
||||
import DiscoverMoviePosterConnector from './DiscoverMoviePosterConnector';
|
||||
import styles from './DiscoverMoviePosters.css';
|
||||
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItems';
|
||||
import dimensions from 'Styles/Variables/dimensions';
|
||||
import Measure from 'Components/Measure';
|
||||
import AddListMovieItemConnector from 'AddMovie/AddListMovie/AddListMovieItemConnector';
|
||||
import AddListMoviePosterConnector from './AddListMoviePosterConnector';
|
||||
import styles from './AddListMoviePosters.css';
|
||||
|
||||
// Poster container dimensions
|
||||
const columnPadding = parseInt(dimensions.movieIndexColumnPadding);
|
||||
@@ -36,7 +36,9 @@ function calculateColumnWidth(width, posterSize, isSmallScreen) {
|
||||
function calculateRowHeight(posterHeight, sortKey, isSmallScreen, posterOptions) {
|
||||
const {
|
||||
detailedProgressBar,
|
||||
showTitle
|
||||
showTitle,
|
||||
showMonitored,
|
||||
showQualityProfile
|
||||
} = posterOptions;
|
||||
|
||||
const nextAiringHeight = 19;
|
||||
@@ -52,6 +54,14 @@ function calculateRowHeight(posterHeight, sortKey, isSmallScreen, posterOptions)
|
||||
heights.push(19);
|
||||
}
|
||||
|
||||
if (showMonitored) {
|
||||
heights.push(19);
|
||||
}
|
||||
|
||||
if (showQualityProfile) {
|
||||
heights.push(19);
|
||||
}
|
||||
|
||||
switch (sortKey) {
|
||||
case 'studio':
|
||||
default:
|
||||
@@ -65,7 +75,7 @@ function calculatePosterHeight(posterWidth) {
|
||||
return Math.ceil((250 / 170) * posterWidth);
|
||||
}
|
||||
|
||||
class DiscoverMoviePosters extends Component {
|
||||
class AddListMoviePosters extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
@@ -84,7 +94,6 @@ class DiscoverMoviePosters extends Component {
|
||||
|
||||
this._isInitialized = false;
|
||||
this._grid = null;
|
||||
this._padding = props.isSmallScreen ? columnPaddingSmallScreen : columnPadding;
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
@@ -92,9 +101,7 @@ class DiscoverMoviePosters extends Component {
|
||||
items,
|
||||
sortKey,
|
||||
posterOptions,
|
||||
jumpToCharacter,
|
||||
isSmallScreen,
|
||||
selectedState
|
||||
jumpToCharacter
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
@@ -106,7 +113,7 @@ class DiscoverMoviePosters extends Component {
|
||||
|
||||
if (prevProps.sortKey !== sortKey ||
|
||||
prevProps.posterOptions !== posterOptions) {
|
||||
this.calculateGrid(width, isSmallScreen);
|
||||
this.calculateGrid();
|
||||
}
|
||||
|
||||
if (this._grid &&
|
||||
@@ -114,8 +121,7 @@ class DiscoverMoviePosters extends Component {
|
||||
prevState.columnWidth !== columnWidth ||
|
||||
prevState.columnCount !== columnCount ||
|
||||
prevState.rowHeight !== rowHeight ||
|
||||
prevProps.selectedState !== selectedState ||
|
||||
hasDifferentItemsOrOrder(prevProps.items, items, 'tmdbId'))) {
|
||||
hasDifferentItemsOrOrder(prevProps.items, items))) {
|
||||
// recomputeGridSize also forces Grid to discard its cache of rendered cells
|
||||
this._grid.recomputeGridSize();
|
||||
}
|
||||
@@ -147,9 +153,10 @@ class DiscoverMoviePosters extends Component {
|
||||
posterOptions
|
||||
} = this.props;
|
||||
|
||||
const padding = isSmallScreen ? columnPaddingSmallScreen : columnPadding;
|
||||
const columnWidth = calculateColumnWidth(width, posterOptions.size, isSmallScreen);
|
||||
const columnCount = Math.max(Math.floor(width / columnWidth), 1);
|
||||
const posterWidth = columnWidth - this._padding * 2;
|
||||
const posterWidth = columnWidth - padding;
|
||||
const posterHeight = calculatePosterHeight(posterWidth);
|
||||
const rowHeight = calculateRowHeight(posterHeight, sortKey, isSmallScreen, posterOptions);
|
||||
|
||||
@@ -170,9 +177,7 @@ class DiscoverMoviePosters extends Component {
|
||||
posterOptions,
|
||||
showRelativeDates,
|
||||
shortDateFormat,
|
||||
timeFormat,
|
||||
selectedState,
|
||||
onSelectedChange
|
||||
timeFormat
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
@@ -185,8 +190,7 @@ class DiscoverMoviePosters extends Component {
|
||||
showTitle
|
||||
} = posterOptions;
|
||||
|
||||
const movieIdx = rowIndex * columnCount + columnIndex;
|
||||
const movie = items[movieIdx];
|
||||
const movie = items[rowIndex * columnCount + columnIndex];
|
||||
|
||||
if (!movie) {
|
||||
return null;
|
||||
@@ -194,16 +198,12 @@ class DiscoverMoviePosters extends Component {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.container}
|
||||
key={key}
|
||||
style={{
|
||||
...style,
|
||||
padding: this._padding
|
||||
}}
|
||||
style={style}
|
||||
>
|
||||
<DiscoverMovieItemConnector
|
||||
key={movie.tmdbId}
|
||||
component={DiscoverMoviePosterConnector}
|
||||
<AddListMovieItemConnector
|
||||
key={movie.id}
|
||||
component={AddListMoviePosterConnector}
|
||||
sortKey={sortKey}
|
||||
posterWidth={posterWidth}
|
||||
posterHeight={posterHeight}
|
||||
@@ -212,8 +212,6 @@ class DiscoverMoviePosters extends Component {
|
||||
shortDateFormat={shortDateFormat}
|
||||
timeFormat={timeFormat}
|
||||
movieId={movie.tmdbId}
|
||||
isSelected={selectedState[movie.tmdbId]}
|
||||
onSelectedChange={onSelectedChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -233,8 +231,7 @@ class DiscoverMoviePosters extends Component {
|
||||
const {
|
||||
isSmallScreen,
|
||||
scroller,
|
||||
items,
|
||||
selectedState
|
||||
items
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
@@ -275,7 +272,6 @@ class DiscoverMoviePosters extends Component {
|
||||
scrollTop={scrollTop}
|
||||
overscanRowCount={2}
|
||||
cellRenderer={this.cellRenderer}
|
||||
selectedState={selectedState}
|
||||
scrollToAlignment={'start'}
|
||||
isScrollingOptOut={true}
|
||||
/>
|
||||
@@ -289,7 +285,7 @@ class DiscoverMoviePosters extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
DiscoverMoviePosters.propTypes = {
|
||||
AddListMoviePosters.propTypes = {
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
sortKey: PropTypes.string,
|
||||
posterOptions: PropTypes.object.isRequired,
|
||||
@@ -298,9 +294,7 @@ DiscoverMoviePosters.propTypes = {
|
||||
showRelativeDates: PropTypes.bool.isRequired,
|
||||
shortDateFormat: PropTypes.string.isRequired,
|
||||
isSmallScreen: PropTypes.bool.isRequired,
|
||||
timeFormat: PropTypes.string.isRequired,
|
||||
selectedState: PropTypes.object.isRequired,
|
||||
onSelectedChange: PropTypes.func.isRequired
|
||||
timeFormat: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
export default DiscoverMoviePosters;
|
||||
export default AddListMoviePosters;
|
||||
@@ -1,12 +1,12 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||
import DiscoverMoviePosters from './DiscoverMoviePosters';
|
||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||
import AddListMoviePosters from './AddListMoviePosters';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.discoverMovie.posterOptions,
|
||||
(state) => state.addMovie.posterOptions,
|
||||
createUISettingsSelector(),
|
||||
createDimensionsSelector(),
|
||||
(posterOptions, uiSettings, dimensions) => {
|
||||
@@ -21,4 +21,4 @@ function createMapStateToProps() {
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(createMapStateToProps)(DiscoverMoviePosters);
|
||||
export default connect(createMapStateToProps)(AddListMoviePosters);
|
||||
@@ -1,21 +1,15 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import AddNewDiscoverMovieModalContentConnector from './AddNewDiscoverMovieModalContentConnector';
|
||||
|
||||
function AddNewDiscoverMovieModal(props) {
|
||||
const {
|
||||
isOpen,
|
||||
onModalClose,
|
||||
...otherProps
|
||||
} = props;
|
||||
import AddListMoviePosterOptionsModalContentConnector from './AddListMoviePosterOptionsModalContentConnector';
|
||||
|
||||
function AddListMoviePosterOptionsModal({ isOpen, onModalClose, ...otherProps }) {
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onModalClose={onModalClose}
|
||||
>
|
||||
<AddNewDiscoverMovieModalContentConnector
|
||||
<AddListMoviePosterOptionsModalContentConnector
|
||||
{...otherProps}
|
||||
onModalClose={onModalClose}
|
||||
/>
|
||||
@@ -23,9 +17,9 @@ function AddNewDiscoverMovieModal(props) {
|
||||
);
|
||||
}
|
||||
|
||||
AddNewDiscoverMovieModal.propTypes = {
|
||||
AddListMoviePosterOptionsModal.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default AddNewDiscoverMovieModal;
|
||||
export default AddListMoviePosterOptionsModal;
|
||||
@@ -1,17 +1,16 @@
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { inputTypes } from 'Helpers/Props';
|
||||
import Button from 'Components/Link/Button';
|
||||
import Form from 'Components/Form/Form';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import FormLabel from 'Components/Form/FormLabel';
|
||||
import Button from 'Components/Link/Button';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { inputTypes } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
|
||||
const posterSizeOptions = [
|
||||
{ key: 'small', value: 'Small' },
|
||||
@@ -19,7 +18,7 @@ const posterSizeOptions = [
|
||||
{ key: 'large', value: 'Large' }
|
||||
];
|
||||
|
||||
class DiscoverMoviePosterOptionsModalContent extends Component {
|
||||
class AddListMoviePosterOptionsModalContent extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
@@ -29,16 +28,14 @@ class DiscoverMoviePosterOptionsModalContent extends Component {
|
||||
|
||||
this.state = {
|
||||
size: props.size,
|
||||
showTitle: props.showTitle,
|
||||
includeRecommendations: props.includeRecommendations
|
||||
showTitle: props.showTitle
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const {
|
||||
size,
|
||||
showTitle,
|
||||
includeRecommendations
|
||||
showTitle
|
||||
} = this.props;
|
||||
|
||||
const state = {};
|
||||
@@ -51,10 +48,6 @@ class DiscoverMoviePosterOptionsModalContent extends Component {
|
||||
state.showTitle = showTitle;
|
||||
}
|
||||
|
||||
if (includeRecommendations !== prevProps.includeRecommendations) {
|
||||
state.includeRecommendations = includeRecommendations;
|
||||
}
|
||||
|
||||
if (!_.isEmpty(state)) {
|
||||
this.setState(state);
|
||||
}
|
||||
@@ -71,16 +64,6 @@ class DiscoverMoviePosterOptionsModalContent extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
onChangeOption = ({ name, value }) => {
|
||||
this.setState({
|
||||
[name]: value
|
||||
}, () => {
|
||||
this.props.onChangeOption({
|
||||
[name]: value
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
@@ -91,8 +74,7 @@ class DiscoverMoviePosterOptionsModalContent extends Component {
|
||||
|
||||
const {
|
||||
size,
|
||||
showTitle,
|
||||
includeRecommendations
|
||||
showTitle
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
@@ -104,19 +86,7 @@ class DiscoverMoviePosterOptionsModalContent extends Component {
|
||||
<ModalBody>
|
||||
<Form>
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('IncludeRadarrRecommendations')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="includeRecommendations"
|
||||
value={includeRecommendations}
|
||||
helpText={translate('IncludeRecommendationsHelpText')}
|
||||
onChange={this.onChangeOption}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('PosterSize')}</FormLabel>
|
||||
<FormLabel>Poster Size</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.SELECT}
|
||||
@@ -128,13 +98,13 @@ class DiscoverMoviePosterOptionsModalContent extends Component {
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('ShowTitle')}</FormLabel>
|
||||
<FormLabel>Show Title</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="showTitle"
|
||||
value={showTitle}
|
||||
helpText={translate('ShowTitleHelpText')}
|
||||
helpText="Show movie title under poster"
|
||||
onChange={this.onChangePosterOption}
|
||||
/>
|
||||
</FormGroup>
|
||||
@@ -145,7 +115,7 @@ class DiscoverMoviePosterOptionsModalContent extends Component {
|
||||
<Button
|
||||
onPress={onModalClose}
|
||||
>
|
||||
{translate('Close')}
|
||||
Close
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
@@ -153,13 +123,11 @@ class DiscoverMoviePosterOptionsModalContent extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
DiscoverMoviePosterOptionsModalContent.propTypes = {
|
||||
AddListMoviePosterOptionsModalContent.propTypes = {
|
||||
size: PropTypes.string.isRequired,
|
||||
showTitle: PropTypes.bool.isRequired,
|
||||
includeRecommendations: PropTypes.bool.isRequired,
|
||||
onChangePosterOption: PropTypes.func.isRequired,
|
||||
onChangeOption: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default DiscoverMoviePosterOptionsModalContent;
|
||||
export default AddListMoviePosterOptionsModalContent;
|
||||
@@ -0,0 +1,23 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { setListMoviePosterOption } from 'Store/Actions/addMovieActions';
|
||||
import AddListMoviePosterOptionsModalContent from './AddListMoviePosterOptionsModalContent';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.addMovie,
|
||||
(addMovie) => {
|
||||
return addMovie.posterOptions;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function createMapDispatchToProps(dispatch, props) {
|
||||
return {
|
||||
onChangePosterOption(payload) {
|
||||
dispatch(setListMoviePosterOption(payload));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(createMapStateToProps, createMapDispatchToProps)(AddListMoviePosterOptionsModalContent);
|
||||
@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
|
||||
|
||||
class DiscoverMovieActionsCell extends Component {
|
||||
class AddListMovieActionsCell extends Component {
|
||||
|
||||
//
|
||||
// Render
|
||||
@@ -47,8 +47,8 @@ class DiscoverMovieActionsCell extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
DiscoverMovieActionsCell.propTypes = {
|
||||
AddListMovieActionsCell.propTypes = {
|
||||
id: PropTypes.number.isRequired
|
||||
};
|
||||
|
||||
export default DiscoverMovieActionsCell;
|
||||
export default AddListMovieActionsCell;
|
||||
@@ -1,10 +1,9 @@
|
||||
.status {
|
||||
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
|
||||
|
||||
flex: 0 0 30px;
|
||||
flex: 0 0 60px;
|
||||
}
|
||||
|
||||
.collection,
|
||||
.sortTitle {
|
||||
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
|
||||
|
||||
@@ -19,36 +18,23 @@
|
||||
|
||||
.inCinemas,
|
||||
.physicalRelease,
|
||||
.digitalRelease,
|
||||
.genres {
|
||||
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
|
||||
|
||||
flex: 0 0 180px;
|
||||
}
|
||||
|
||||
.ratings,
|
||||
.runtime {
|
||||
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
|
||||
|
||||
flex: 0 0 90px;
|
||||
}
|
||||
|
||||
.lists {
|
||||
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
|
||||
|
||||
flex: 1 0 110px;
|
||||
}
|
||||
|
||||
.movieStatus,
|
||||
.certification {
|
||||
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
|
||||
|
||||
flex: 0 0 115px;
|
||||
flex: 0 0 100px;
|
||||
}
|
||||
|
||||
.isRecommendation {
|
||||
.ratings {
|
||||
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
|
||||
|
||||
flex: 0 0 50px;
|
||||
flex: 0 0 80px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
@@ -1,16 +1,13 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Icon from 'Components/Icon';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import TableOptionsModal from 'Components/Table/TableOptions/TableOptionsModal';
|
||||
import VirtualTableHeader from 'Components/Table/VirtualTableHeader';
|
||||
import VirtualTableHeaderCell from 'Components/Table/VirtualTableHeaderCell';
|
||||
import VirtualTableSelectAllHeaderCell from 'Components/Table/VirtualTableSelectAllHeaderCell';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import DiscoverMovieTableOptionsConnector from './DiscoverMovieTableOptionsConnector';
|
||||
import styles from './DiscoverMovieHeader.css';
|
||||
import TableOptionsModal from 'Components/Table/TableOptions/TableOptionsModal';
|
||||
import styles from './AddListMovieHeader.css';
|
||||
|
||||
class DiscoverMovieHeader extends Component {
|
||||
class AddListMovieHeader extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
@@ -41,21 +38,11 @@ class DiscoverMovieHeader extends Component {
|
||||
const {
|
||||
columns,
|
||||
onTableOptionChange,
|
||||
allSelected,
|
||||
allUnselected,
|
||||
onSelectAllChange,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<VirtualTableHeader>
|
||||
<VirtualTableSelectAllHeaderCell
|
||||
key={name}
|
||||
allSelected={allSelected}
|
||||
allUnselected={allUnselected}
|
||||
onSelectAllChange={onSelectAllChange}
|
||||
/>
|
||||
|
||||
{
|
||||
columns.map((column) => {
|
||||
const {
|
||||
@@ -86,23 +73,6 @@ class DiscoverMovieHeader extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'isRecommendation') {
|
||||
return (
|
||||
<VirtualTableHeaderCell
|
||||
key={name}
|
||||
className={styles[name]}
|
||||
name={name}
|
||||
isSortable={true}
|
||||
{...otherProps}
|
||||
>
|
||||
<Icon
|
||||
name={icons.RECOMMENDED}
|
||||
size={12}
|
||||
/>
|
||||
</VirtualTableHeaderCell>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<VirtualTableHeaderCell
|
||||
key={name}
|
||||
@@ -121,7 +91,6 @@ class DiscoverMovieHeader extends Component {
|
||||
isOpen={this.state.isTableOptionsModalOpen}
|
||||
columns={columns}
|
||||
onTableOptionChange={onTableOptionChange}
|
||||
optionsComponent={DiscoverMovieTableOptionsConnector}
|
||||
onModalClose={this.onTableOptionsModalClose}
|
||||
/>
|
||||
</VirtualTableHeader>
|
||||
@@ -129,12 +98,9 @@ class DiscoverMovieHeader extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
DiscoverMovieHeader.propTypes = {
|
||||
AddListMovieHeader.propTypes = {
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
onTableOptionChange: PropTypes.func.isRequired,
|
||||
allSelected: PropTypes.bool.isRequired,
|
||||
allUnselected: PropTypes.bool.isRequired,
|
||||
onSelectAllChange: PropTypes.func.isRequired
|
||||
onTableOptionChange: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default DiscoverMovieHeader;
|
||||
export default AddListMovieHeader;
|
||||
@@ -0,0 +1,13 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { setListMovieTableOption } from 'Store/Actions/addMovieActions';
|
||||
import AddListMovieHeader from './AddListMovieHeader';
|
||||
|
||||
function createMapDispatchToProps(dispatch, props) {
|
||||
return {
|
||||
onTableOptionChange(payload) {
|
||||
dispatch(setListMovieTableOption(payload));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(undefined, createMapDispatchToProps)(AddListMovieHeader);
|
||||
56
frontend/src/AddMovie/AddListMovie/Table/AddListMovieRow.css
Normal file
56
frontend/src/AddMovie/AddListMovie/Table/AddListMovieRow.css
Normal file
@@ -0,0 +1,56 @@
|
||||
.status {
|
||||
composes: cell from '~Components/Table/Cells/VirtualTableRowCell.css';
|
||||
|
||||
flex: 0 0 60px;
|
||||
}
|
||||
|
||||
.sortTitle {
|
||||
composes: cell from '~Components/Table/Cells/VirtualTableRowCell.css';
|
||||
|
||||
flex: 4 0 110px;
|
||||
}
|
||||
|
||||
.studio {
|
||||
composes: cell from '~Components/Table/Cells/VirtualTableRowCell.css';
|
||||
|
||||
flex: 2 0 90px;
|
||||
}
|
||||
|
||||
.inCinemas,
|
||||
.physicalRelease,
|
||||
.genres {
|
||||
composes: cell from '~Components/Table/Cells/VirtualTableRowCell.css';
|
||||
|
||||
flex: 0 0 180px;
|
||||
}
|
||||
|
||||
.movieStatus,
|
||||
.certification {
|
||||
composes: cell from '~Components/Table/Cells/VirtualTableRowCell.css';
|
||||
|
||||
flex: 0 0 100px;
|
||||
}
|
||||
|
||||
.ratings {
|
||||
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
|
||||
|
||||
flex: 0 0 80px;
|
||||
}
|
||||
|
||||
.tags {
|
||||
composes: cell from '~Components/Table/Cells/VirtualTableRowCell.css';
|
||||
|
||||
flex: 1 0 60px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
composes: cell from '~Components/Table/Cells/VirtualTableRowCell.css';
|
||||
|
||||
flex: 0 1 90px;
|
||||
}
|
||||
|
||||
.checkInput {
|
||||
composes: input from '~Components/Form/CheckInput.css';
|
||||
|
||||
margin-top: 0;
|
||||
}
|
||||
218
frontend/src/AddMovie/AddListMovie/Table/AddListMovieRow.js
Normal file
218
frontend/src/AddMovie/AddListMovie/Table/AddListMovieRow.js
Normal file
@@ -0,0 +1,218 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import HeartRating from 'Components/HeartRating';
|
||||
import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
|
||||
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
||||
import MovieStatusCell from './MovieStatusCell';
|
||||
import Link from 'Components/Link/Link';
|
||||
import AddNewMovieModal from 'AddMovie/AddNewMovie/AddNewMovieModal';
|
||||
import styles from './AddListMovieRow.css';
|
||||
|
||||
class AddListMovieRow extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
isNewAddMovieModalOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onPress = () => {
|
||||
this.setState({ isNewAddMovieModalOpen: true });
|
||||
}
|
||||
|
||||
onAddMovieModalClose = () => {
|
||||
this.setState({ isNewAddMovieModalOpen: false });
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
status,
|
||||
tmdbId,
|
||||
title,
|
||||
titleSlug,
|
||||
studio,
|
||||
inCinemas,
|
||||
physicalRelease,
|
||||
year,
|
||||
overview,
|
||||
folder,
|
||||
images,
|
||||
genres,
|
||||
ratings,
|
||||
certification,
|
||||
columns,
|
||||
isExistingMovie
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
isNewAddMovieModalOpen
|
||||
} = this.state;
|
||||
|
||||
const linkProps = isExistingMovie ? { to: `/movie/${titleSlug}` } : { onPress: this.onPress };
|
||||
|
||||
return (
|
||||
<>
|
||||
{
|
||||
columns.map((column) => {
|
||||
const {
|
||||
name,
|
||||
isVisible
|
||||
} = column;
|
||||
|
||||
if (!isVisible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (name === 'status') {
|
||||
return (
|
||||
<MovieStatusCell
|
||||
key={name}
|
||||
className={styles[name]}
|
||||
status={status}
|
||||
component={VirtualTableRowCell}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'sortTitle') {
|
||||
return (
|
||||
<VirtualTableRowCell
|
||||
key={name}
|
||||
className={styles[name]}
|
||||
>
|
||||
<Link
|
||||
{...linkProps}
|
||||
>
|
||||
{title}
|
||||
</Link>
|
||||
</VirtualTableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'studio') {
|
||||
return (
|
||||
<VirtualTableRowCell
|
||||
key={name}
|
||||
className={styles[name]}
|
||||
>
|
||||
{studio}
|
||||
</VirtualTableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'inCinemas') {
|
||||
return (
|
||||
<RelativeDateCellConnector
|
||||
key={name}
|
||||
className={styles[name]}
|
||||
date={inCinemas}
|
||||
component={VirtualTableRowCell}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'physicalRelease') {
|
||||
return (
|
||||
<RelativeDateCellConnector
|
||||
key={name}
|
||||
className={styles[name]}
|
||||
date={physicalRelease}
|
||||
component={VirtualTableRowCell}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'genres') {
|
||||
const joinedGenres = genres.join(', ');
|
||||
|
||||
return (
|
||||
<VirtualTableRowCell
|
||||
key={name}
|
||||
className={styles[name]}
|
||||
>
|
||||
<span title={joinedGenres}>
|
||||
{joinedGenres}
|
||||
</span>
|
||||
</VirtualTableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'ratings') {
|
||||
return (
|
||||
<VirtualTableRowCell
|
||||
key={name}
|
||||
className={styles[name]}
|
||||
>
|
||||
<HeartRating
|
||||
rating={ratings.value}
|
||||
/>
|
||||
</VirtualTableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'certification') {
|
||||
return (
|
||||
<VirtualTableRowCell
|
||||
key={name}
|
||||
className={styles[name]}
|
||||
>
|
||||
{certification}
|
||||
</VirtualTableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
}
|
||||
|
||||
<AddNewMovieModal
|
||||
isOpen={isNewAddMovieModalOpen && !isExistingMovie}
|
||||
tmdbId={tmdbId}
|
||||
title={title}
|
||||
year={year}
|
||||
overview={overview}
|
||||
folder={folder}
|
||||
images={images}
|
||||
onModalClose={this.onAddMovieModalClose}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AddListMovieRow.propTypes = {
|
||||
tmdbId: PropTypes.number.isRequired,
|
||||
status: PropTypes.string.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
year: PropTypes.number.isRequired,
|
||||
overview: PropTypes.string.isRequired,
|
||||
folder: PropTypes.string.isRequired,
|
||||
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
titleSlug: PropTypes.string.isRequired,
|
||||
studio: PropTypes.string,
|
||||
inCinemas: PropTypes.string,
|
||||
physicalRelease: PropTypes.string,
|
||||
genres: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
ratings: PropTypes.object.isRequired,
|
||||
certification: PropTypes.string,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
isExistingMovie: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
AddListMovieRow.defaultProps = {
|
||||
genres: [],
|
||||
tags: []
|
||||
};
|
||||
|
||||
export default AddListMovieRow;
|
||||
@@ -0,0 +1,23 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import createExistingMovieSelector from 'Store/Selectors/createExistingMovieSelector';
|
||||
import createExclusionMovieSelector from 'Store/Selectors/createExclusionMovieSelector';
|
||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||
import AddListMovieRow from './AddListMovieRow';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createExistingMovieSelector(),
|
||||
createExclusionMovieSelector(),
|
||||
createDimensionsSelector(),
|
||||
(isExistingMovie, isExclusionMovie, dimensions) => {
|
||||
return {
|
||||
isExistingMovie,
|
||||
isExclusionMovie,
|
||||
isSmallScreen: dimensions.isSmallScreen
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(createMapStateToProps)(AddListMovieRow);
|
||||
@@ -1,15 +1,15 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import getIndexOfFirstCharacter from 'Utilities/Array/getIndexOfFirstCharacter';
|
||||
import { sortDirections } from 'Helpers/Props';
|
||||
import VirtualTable from 'Components/Table/VirtualTable';
|
||||
import VirtualTableRow from 'Components/Table/VirtualTableRow';
|
||||
import DiscoverMovieItemConnector from 'DiscoverMovie/DiscoverMovieItemConnector';
|
||||
import { sortDirections } from 'Helpers/Props';
|
||||
import getIndexOfFirstCharacter from 'Utilities/Array/getIndexOfFirstCharacter';
|
||||
import DiscoverMovieHeaderConnector from './DiscoverMovieHeaderConnector';
|
||||
import DiscoverMovieRowConnector from './DiscoverMovieRowConnector';
|
||||
import styles from './DiscoverMovieTable.css';
|
||||
import AddListMovieItemConnector from 'AddMovie/AddListMovie/AddListMovieItemConnector';
|
||||
import AddListMovieHeaderConnector from './AddListMovieHeaderConnector';
|
||||
import AddListMovieRowConnector from './AddListMovieRowConnector';
|
||||
import styles from './AddListMovieTable.css';
|
||||
|
||||
class DiscoverMovieTable extends Component {
|
||||
class AddListMovieTable extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
@@ -46,9 +46,7 @@ class DiscoverMovieTable extends Component {
|
||||
rowRenderer = ({ key, rowIndex, style }) => {
|
||||
const {
|
||||
items,
|
||||
columns,
|
||||
selectedState,
|
||||
onSelectedChange
|
||||
columns
|
||||
} = this.props;
|
||||
|
||||
const movie = items[rowIndex];
|
||||
@@ -58,13 +56,11 @@ class DiscoverMovieTable extends Component {
|
||||
key={key}
|
||||
style={style}
|
||||
>
|
||||
<DiscoverMovieItemConnector
|
||||
key={movie.tmdbId}
|
||||
component={DiscoverMovieRowConnector}
|
||||
<AddListMovieItemConnector
|
||||
key={movie.id}
|
||||
component={AddListMovieRowConnector}
|
||||
columns={columns}
|
||||
movieId={movie.tmdbId}
|
||||
isSelected={selectedState[movie.tmdbId]}
|
||||
onSelectedChange={onSelectedChange}
|
||||
/>
|
||||
</VirtualTableRow>
|
||||
);
|
||||
@@ -79,13 +75,8 @@ class DiscoverMovieTable extends Component {
|
||||
columns,
|
||||
sortKey,
|
||||
sortDirection,
|
||||
isSmallScreen,
|
||||
onSortPress,
|
||||
scroller,
|
||||
allSelected,
|
||||
allUnselected,
|
||||
onSelectAllChange,
|
||||
selectedState
|
||||
onSortPress
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
@@ -93,43 +84,32 @@ class DiscoverMovieTable extends Component {
|
||||
className={styles.tableContainer}
|
||||
items={items}
|
||||
scrollIndex={this.state.scrollIndex}
|
||||
isSmallScreen={isSmallScreen}
|
||||
scroller={scroller}
|
||||
rowHeight={38}
|
||||
overscanRowCount={2}
|
||||
rowRenderer={this.rowRenderer}
|
||||
header={
|
||||
<DiscoverMovieHeaderConnector
|
||||
<AddListMovieHeaderConnector
|
||||
columns={columns}
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onSortPress={onSortPress}
|
||||
allSelected={allSelected}
|
||||
allUnselected={allUnselected}
|
||||
onSelectAllChange={onSelectAllChange}
|
||||
/>
|
||||
}
|
||||
selectedState={selectedState}
|
||||
columns={columns}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DiscoverMovieTable.propTypes = {
|
||||
AddListMovieTable.propTypes = {
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
sortKey: PropTypes.string,
|
||||
sortDirection: PropTypes.oneOf(sortDirections.all),
|
||||
jumpToCharacter: PropTypes.string,
|
||||
isSmallScreen: PropTypes.bool.isRequired,
|
||||
scroller: PropTypes.instanceOf(Element).isRequired,
|
||||
onSortPress: PropTypes.func.isRequired,
|
||||
allSelected: PropTypes.bool.isRequired,
|
||||
allUnselected: PropTypes.bool.isRequired,
|
||||
selectedState: PropTypes.object.isRequired,
|
||||
onSelectedChange: PropTypes.func.isRequired,
|
||||
onSelectAllChange: PropTypes.func.isRequired
|
||||
onSortPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default DiscoverMovieTable;
|
||||
export default AddListMovieTable;
|
||||
@@ -1,12 +1,12 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { setListMovieSort } from 'Store/Actions/discoverMovieActions';
|
||||
import DiscoverMovieTable from './DiscoverMovieTable';
|
||||
import { setListMovieSort } from 'Store/Actions/addMovieActions';
|
||||
import AddListMovieTable from './AddListMovieTable';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.app.dimensions,
|
||||
(state) => state.discoverMovie.columns,
|
||||
(state) => state.addMovie.columns,
|
||||
(dimensions, columns) => {
|
||||
return {
|
||||
isSmallScreen: dimensions.isSmallScreen,
|
||||
@@ -24,4 +24,4 @@ function createMapDispatchToProps(dispatch, props) {
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(createMapStateToProps, createMapDispatchToProps)(DiscoverMovieTable);
|
||||
export default connect(createMapStateToProps, createMapDispatchToProps)(AddListMovieTable);
|
||||
@@ -0,0 +1,9 @@
|
||||
.status {
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.statusIcon {
|
||||
width: 20px !important;
|
||||
}
|
||||
62
frontend/src/AddMovie/AddListMovie/Table/MovieStatusCell.js
Normal file
62
frontend/src/AddMovie/AddListMovie/Table/MovieStatusCell.js
Normal file
@@ -0,0 +1,62 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import Icon from 'Components/Icon';
|
||||
import VirtualTableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import styles from './MovieStatusCell.css';
|
||||
|
||||
function MovieStatusCell(props) {
|
||||
const {
|
||||
className,
|
||||
status,
|
||||
component: Component,
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<Component
|
||||
className={className}
|
||||
{...otherProps}
|
||||
>
|
||||
{
|
||||
status === 'announced' ?
|
||||
<Icon
|
||||
className={styles.statusIcon}
|
||||
name={icons.ANNOUNCED}
|
||||
title={'Movie is announced'}
|
||||
/> : null
|
||||
}
|
||||
|
||||
{
|
||||
status === 'inCinemas' ?
|
||||
<Icon
|
||||
className={styles.statusIcon}
|
||||
name={icons.IN_CINEMAS}
|
||||
title={'Movie is in Cinemas'}
|
||||
/> : null
|
||||
}
|
||||
|
||||
{
|
||||
status === 'released' ?
|
||||
<Icon
|
||||
className={styles.statusIcon}
|
||||
name={icons.MOVIE_FILE}
|
||||
title={'Movie is released'}
|
||||
/> : null
|
||||
}
|
||||
</Component>
|
||||
);
|
||||
}
|
||||
|
||||
MovieStatusCell.propTypes = {
|
||||
className: PropTypes.string.isRequired,
|
||||
status: PropTypes.string.isRequired,
|
||||
component: PropTypes.elementType
|
||||
};
|
||||
|
||||
MovieStatusCell.defaultProps = {
|
||||
className: styles.status,
|
||||
component: VirtualTableRowCell
|
||||
};
|
||||
|
||||
export default MovieStatusCell;
|
||||
@@ -1,15 +1,14 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import TextInput from 'Components/Form/TextInput';
|
||||
import Icon from 'Components/Icon';
|
||||
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
||||
import { icons, kinds } from 'Helpers/Props';
|
||||
import Button from 'Components/Link/Button';
|
||||
import Link from 'Components/Link/Link';
|
||||
import Icon from 'Components/Icon';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import TextInput from 'Components/Form/TextInput';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
import { icons, kinds } from 'Helpers/Props';
|
||||
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import PageContentBodyConnector from 'Components/Page/PageContentBodyConnector';
|
||||
import AddNewMovieSearchResultConnector from './AddNewMovieSearchResultConnector';
|
||||
import styles from './AddNewMovie.css';
|
||||
|
||||
@@ -88,8 +87,8 @@ class AddNewMovie extends Component {
|
||||
const isFetching = this.state.isFetching;
|
||||
|
||||
return (
|
||||
<PageContent title={translate('AddNewMovie')}>
|
||||
<PageContentBody>
|
||||
<PageContent title="Add New Movie">
|
||||
<PageContentBodyConnector>
|
||||
<div className={styles.searchContainer}>
|
||||
<div className={styles.searchIconContainer}>
|
||||
<Icon
|
||||
@@ -127,7 +126,7 @@ class AddNewMovie extends Component {
|
||||
!isFetching && !!error ?
|
||||
<div className={styles.message}>
|
||||
<div className={styles.helpText}>
|
||||
{translate('FailedLoadingSearchResults')}
|
||||
Failed to load search results, please try again.
|
||||
</div>
|
||||
<div>{getErrorMessage(error)}</div>
|
||||
</div> : null
|
||||
@@ -152,15 +151,11 @@ class AddNewMovie extends Component {
|
||||
{
|
||||
!isFetching && !error && !items.length && !!term &&
|
||||
<div className={styles.message}>
|
||||
<div className={styles.noResults}>
|
||||
{translate('CouldNotFindResults', [term])}
|
||||
</div>
|
||||
<div>
|
||||
{translate('YouCanAlsoSearch')}
|
||||
</div>
|
||||
<div className={styles.noResults}>Couldn't find any results for '{term}'</div>
|
||||
<div>You can also search using TMDB ID or IMDB ID of a movie. eg. tmdb:71663</div>
|
||||
<div>
|
||||
<Link to="https://github.com/Radarr/Radarr/wiki/FAQ#why-cant-i-add-a-new-movie-when-i-know-the-tmdb-id">
|
||||
{translate('CantFindMovie')}
|
||||
Why can't I find my movie?
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
@@ -171,11 +166,9 @@ class AddNewMovie extends Component {
|
||||
null :
|
||||
<div className={styles.message}>
|
||||
<div className={styles.helpText}>
|
||||
{translate('AddNewMessage')}
|
||||
</div>
|
||||
<div>
|
||||
{translate('AddNewTmdbIdMessage')}
|
||||
It's easy to add a new movie, just start typing the name the movie you want to add.
|
||||
</div>
|
||||
<div>You can also search using TMDB ID of a movie. eg. tmdb:71663</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -183,14 +176,14 @@ class AddNewMovie extends Component {
|
||||
!term && !hasExistingMovies ?
|
||||
<div className={styles.message}>
|
||||
<div className={styles.noMoviesText}>
|
||||
{translate('HaveNotAddedMovies')}
|
||||
You haven't added any movies yet, do you want to import some or all of your movies first?
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
to="/add/import"
|
||||
kind={kinds.PRIMARY}
|
||||
>
|
||||
{translate('ImportExistingMovies')}
|
||||
Import Existing Movies
|
||||
</Button>
|
||||
</div>
|
||||
</div> :
|
||||
@@ -198,7 +191,7 @@ class AddNewMovie extends Component {
|
||||
}
|
||||
|
||||
<div />
|
||||
</PageContentBody>
|
||||
</PageContentBodyConnector>
|
||||
</PageContent>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@ import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { clearAddMovie, lookupMovie } from 'Store/Actions/addMovieActions';
|
||||
import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
|
||||
import { fetchImportExclusions } from 'Store/Actions/Settings/importExclusions';
|
||||
import parseUrl from 'Utilities/String/parseUrl';
|
||||
import { lookupMovie, clearAddMovie } from 'Store/Actions/addMovieActions';
|
||||
import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
|
||||
import { fetchNetImportExclusions } from 'Store/Actions/Settings/netImportExclusions';
|
||||
import AddNewMovie from './AddNewMovie';
|
||||
|
||||
function createMapStateToProps() {
|
||||
@@ -29,7 +29,7 @@ const mapDispatchToProps = {
|
||||
lookupMovie,
|
||||
clearAddMovie,
|
||||
fetchRootFolders,
|
||||
fetchImportExclusions
|
||||
fetchNetImportExclusions
|
||||
};
|
||||
|
||||
class AddNewMovieConnector extends Component {
|
||||
@@ -45,7 +45,7 @@ class AddNewMovieConnector extends Component {
|
||||
|
||||
componentDidMount() {
|
||||
this.props.fetchRootFolders();
|
||||
this.props.fetchImportExclusions();
|
||||
this.props.fetchNetImportExclusions();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
@@ -102,7 +102,7 @@ AddNewMovieConnector.propTypes = {
|
||||
lookupMovie: PropTypes.func.isRequired,
|
||||
clearAddMovie: PropTypes.func.isRequired,
|
||||
fetchRootFolders: PropTypes.func.isRequired,
|
||||
fetchImportExclusions: PropTypes.func.isRequired
|
||||
fetchNetImportExclusions: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(AddNewMovieConnector);
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import CheckInput from 'Components/Form/CheckInput';
|
||||
import { kinds, inputTypes } from 'Helpers/Props';
|
||||
import SpinnerButton from 'Components/Link/SpinnerButton';
|
||||
import Form from 'Components/Form/Form';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import FormLabel from 'Components/Form/FormLabel';
|
||||
import SpinnerButton from 'Components/Link/SpinnerButton';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import CheckInput from 'Components/Form/CheckInput';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { inputTypes, kinds } from 'Helpers/Props';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import MoviePoster from 'Movie/MoviePoster';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './AddNewMovieModalContent.css';
|
||||
|
||||
class AddNewMovieModalContent extends Component {
|
||||
@@ -96,7 +95,7 @@ class AddNewMovieModalContent extends Component {
|
||||
|
||||
<Form>
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('RootFolder')}</FormLabel>
|
||||
<FormLabel>Root Folder</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.ROOT_FOLDER_SELECT}
|
||||
@@ -109,7 +108,7 @@ class AddNewMovieModalContent extends Component {
|
||||
movieFolder: folder,
|
||||
isWindows
|
||||
}}
|
||||
helpText={translate('SubfolderWillBeCreatedAutomaticallyInterp', [folder])}
|
||||
helpText={`'${folder}' subfolder will be created automatically`}
|
||||
onChange={onInputChange}
|
||||
{...rootFolderPath}
|
||||
/>
|
||||
@@ -117,7 +116,7 @@ class AddNewMovieModalContent extends Component {
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>
|
||||
{translate('Monitor')}
|
||||
Monitor
|
||||
</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
@@ -129,7 +128,7 @@ class AddNewMovieModalContent extends Component {
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('MinimumAvailability')}</FormLabel>
|
||||
<FormLabel>Minimum Availability</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.AVAILABILITY_SELECT}
|
||||
@@ -140,7 +139,7 @@ class AddNewMovieModalContent extends Component {
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('QualityProfile')}</FormLabel>
|
||||
<FormLabel>Quality Profile</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.QUALITY_PROFILE_SELECT}
|
||||
@@ -151,7 +150,7 @@ class AddNewMovieModalContent extends Component {
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('Tags')}</FormLabel>
|
||||
<FormLabel>Tags</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.TAG}
|
||||
@@ -168,7 +167,7 @@ class AddNewMovieModalContent extends Component {
|
||||
<ModalFooter className={styles.modalFooter}>
|
||||
<label className={styles.searchForMissingMovieLabelContainer}>
|
||||
<span className={styles.searchForMissingMovieLabel}>
|
||||
{translate('StartSearchForMissingMovie')}
|
||||
Start search for missing movie
|
||||
</span>
|
||||
|
||||
<CheckInput
|
||||
@@ -186,7 +185,7 @@ class AddNewMovieModalContent extends Component {
|
||||
isSpinning={isAdding}
|
||||
onPress={this.onAddMoviePress}
|
||||
>
|
||||
{translate('AddMovie')}
|
||||
Add {title}
|
||||
</SpinnerButton>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
|
||||
@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { addMovie, setAddMovieDefault } from 'Store/Actions/addMovieActions';
|
||||
import { setAddMovieDefault, addMovie } from 'Store/Actions/addMovieActions';
|
||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||
import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector';
|
||||
import selectSettings from 'Store/Selectors/selectSettings';
|
||||
|
||||
@@ -34,20 +34,10 @@
|
||||
|
||||
.content {
|
||||
flex: 0 1 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.titleRow {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.titleContainer {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
font-weight: 300;
|
||||
font-size: 36px;
|
||||
}
|
||||
@@ -57,12 +47,16 @@
|
||||
color: $disabledColor;
|
||||
}
|
||||
|
||||
.icons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex: 1 0 auto;
|
||||
height: 55px;
|
||||
.tmdbLink {
|
||||
composes: link from '~Components/Link/Link.css';
|
||||
|
||||
margin-top: -4px;
|
||||
margin-left: auto;
|
||||
color: $textColor;
|
||||
}
|
||||
|
||||
.tmdbLinkIcon {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.alreadyExistsIcon {
|
||||
@@ -73,22 +67,10 @@
|
||||
|
||||
.exclusionIcon {
|
||||
margin-left: 10px;
|
||||
color: $dangerColor;
|
||||
color: #bc3737;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.overview {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.links {
|
||||
margin-left: 8px;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $breakpointMedium) {
|
||||
.titleRow {
|
||||
justify-content: space-between;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { icons, kinds, sizes } from 'Helpers/Props';
|
||||
import HeartRating from 'Components/HeartRating';
|
||||
import Icon from 'Components/Icon';
|
||||
import Label from 'Components/Label';
|
||||
import Link from 'Components/Link/Link';
|
||||
import Tooltip from 'Components/Tooltip/Tooltip';
|
||||
import { icons, kinds, sizes, tooltipPositions } from 'Helpers/Props';
|
||||
import MovieDetailsLinks from 'Movie/Details/MovieDetailsLinks';
|
||||
import MoviePoster from 'Movie/MoviePoster';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import AddNewMovieModal from './AddNewMovieModal';
|
||||
import styles from './AddNewMovieSearchResult.css';
|
||||
|
||||
@@ -42,7 +39,7 @@ class AddNewMovieSearchResult extends Component {
|
||||
this.setState({ isNewAddMovieModalOpen: false });
|
||||
}
|
||||
|
||||
onExternalLinkPress = (event) => {
|
||||
onTMDBLinkPress = (event) => {
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
@@ -52,8 +49,6 @@ class AddNewMovieSearchResult extends Component {
|
||||
render() {
|
||||
const {
|
||||
tmdbId,
|
||||
imdbId,
|
||||
youTubeTrailerId,
|
||||
title,
|
||||
titleSlug,
|
||||
year,
|
||||
@@ -94,43 +89,45 @@ class AddNewMovieSearchResult extends Component {
|
||||
}
|
||||
|
||||
<div className={styles.content}>
|
||||
<div className={styles.titleRow}>
|
||||
<div className={styles.titleContainer}>
|
||||
<div className={styles.title}>
|
||||
{title}
|
||||
<div className={styles.title}>
|
||||
{title}
|
||||
|
||||
{
|
||||
!title.contains(year) && !!year ?
|
||||
<span className={styles.year}>
|
||||
({year})
|
||||
</span> :
|
||||
null
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
!title.contains(year) && !!year &&
|
||||
<span className={styles.year}>({year})</span>
|
||||
}
|
||||
|
||||
<div className={styles.icons}>
|
||||
{
|
||||
isExistingMovie &&
|
||||
<Icon
|
||||
className={styles.alreadyExistsIcon}
|
||||
name={icons.CHECK_CIRCLE}
|
||||
size={36}
|
||||
title="Already in your library"
|
||||
/>
|
||||
}
|
||||
|
||||
{
|
||||
isExistingMovie &&
|
||||
<Icon
|
||||
className={styles.alreadyExistsIcon}
|
||||
name={icons.CHECK_CIRCLE}
|
||||
size={36}
|
||||
title={translate('AlreadyInYourLibrary')}
|
||||
/>
|
||||
}
|
||||
{
|
||||
isExclusionMovie &&
|
||||
<Icon
|
||||
className={styles.exclusionIcon}
|
||||
name={icons.DANGER}
|
||||
size={36}
|
||||
title="Movie is on Net Import Exclusion List"
|
||||
/>
|
||||
}
|
||||
|
||||
{
|
||||
isExclusionMovie &&
|
||||
<Icon
|
||||
className={styles.exclusionIcon}
|
||||
name={icons.DANGER}
|
||||
size={36}
|
||||
title={translate('MovieIsOnImportExclusionList')}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
<Link
|
||||
className={styles.tmdbLink}
|
||||
to={`https://www.themoviedb.org/movie/${tmdbId}`}
|
||||
onPress={this.onTMDBLinkPress}
|
||||
>
|
||||
<Icon
|
||||
className={styles.tmdbLinkIcon}
|
||||
name={icons.EXTERNAL_LINK}
|
||||
size={28}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@@ -148,33 +145,6 @@ class AddNewMovieSearchResult extends Component {
|
||||
</Label>
|
||||
}
|
||||
|
||||
<Tooltip
|
||||
anchor={
|
||||
<Label
|
||||
size={sizes.LARGE}
|
||||
>
|
||||
<Icon
|
||||
name={icons.EXTERNAL_LINK}
|
||||
size={13}
|
||||
/>
|
||||
|
||||
<span className={styles.links}>
|
||||
Links
|
||||
</span>
|
||||
</Label>
|
||||
}
|
||||
tooltip={
|
||||
<MovieDetailsLinks
|
||||
tmdbId={tmdbId}
|
||||
youTubeTrailerId={youTubeTrailerId}
|
||||
imdbId={imdbId}
|
||||
/>
|
||||
}
|
||||
canFlip={true}
|
||||
kind={kinds.INVERSE}
|
||||
position={tooltipPositions.BOTTOM}
|
||||
/>
|
||||
|
||||
{
|
||||
status === 'ended' &&
|
||||
<Label
|
||||
@@ -209,8 +179,6 @@ class AddNewMovieSearchResult extends Component {
|
||||
|
||||
AddNewMovieSearchResult.propTypes = {
|
||||
tmdbId: PropTypes.number.isRequired,
|
||||
imdbId: PropTypes.string,
|
||||
youTubeTrailerId: PropTypes.string,
|
||||
title: PropTypes.string.isRequired,
|
||||
titleSlug: PropTypes.string.isRequired,
|
||||
year: PropTypes.number.isRequired,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||
import createExclusionMovieSelector from 'Store/Selectors/createExclusionMovieSelector';
|
||||
import createExistingMovieSelector from 'Store/Selectors/createExistingMovieSelector';
|
||||
import createExclusionMovieSelector from 'Store/Selectors/createExclusionMovieSelector';
|
||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||
import AddNewMovieSearchResult from './AddNewMovieSearchResult';
|
||||
|
||||
function createMapStateToProps() {
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import getSelectedIds from 'Utilities/Table/getSelectedIds';
|
||||
import selectAll from 'Utilities/Table/selectAll';
|
||||
import toggleSelected from 'Utilities/Table/toggleSelected';
|
||||
import ImportMovieFooterConnector from './ImportMovieFooterConnector';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBodyConnector from 'Components/Page/PageContentBodyConnector';
|
||||
import ImportMovieTableConnector from './ImportMovieTableConnector';
|
||||
import ImportMovieFooterConnector from './ImportMovieFooterConnector';
|
||||
|
||||
class ImportMovie extends Component {
|
||||
|
||||
@@ -80,8 +79,8 @@ class ImportMovie extends Component {
|
||||
rootFolderId,
|
||||
path,
|
||||
rootFoldersFetching,
|
||||
rootFoldersError,
|
||||
rootFoldersPopulated,
|
||||
rootFoldersError,
|
||||
unmappedFolders
|
||||
} = this.props;
|
||||
|
||||
@@ -93,40 +92,30 @@ class ImportMovie extends Component {
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
<PageContent title={translate('ImportMovies')}>
|
||||
<PageContentBody
|
||||
<PageContent title="Import Movies">
|
||||
<PageContentBodyConnector
|
||||
registerScroller={this.setScrollerRef}
|
||||
onScroll={this.onScroll}
|
||||
>
|
||||
{
|
||||
rootFoldersFetching ? <LoadingIndicator /> : null
|
||||
rootFoldersFetching && !rootFoldersPopulated &&
|
||||
<LoadingIndicator />
|
||||
}
|
||||
|
||||
{
|
||||
!rootFoldersFetching && !!rootFoldersError ?
|
||||
<div>
|
||||
{translate('UnableToLoadRootFolders')}
|
||||
</div> :
|
||||
null
|
||||
!rootFoldersFetching && !!rootFoldersError &&
|
||||
<div>Unable to load root folders</div>
|
||||
}
|
||||
|
||||
{
|
||||
!rootFoldersError &&
|
||||
!rootFoldersFetching &&
|
||||
rootFoldersPopulated &&
|
||||
!unmappedFolders.length ?
|
||||
!rootFoldersError && rootFoldersPopulated && !unmappedFolders.length &&
|
||||
<div>
|
||||
All movies in {path} have been imported
|
||||
</div> :
|
||||
null
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
!rootFoldersError &&
|
||||
!rootFoldersFetching &&
|
||||
rootFoldersPopulated &&
|
||||
!!unmappedFolders.length &&
|
||||
scroller ?
|
||||
!rootFoldersError && rootFoldersPopulated && !!unmappedFolders.length && scroller &&
|
||||
<ImportMovieTableConnector
|
||||
rootFolderId={rootFolderId}
|
||||
unmappedFolders={unmappedFolders}
|
||||
@@ -137,21 +126,17 @@ class ImportMovie extends Component {
|
||||
onSelectAllChange={this.onSelectAllChange}
|
||||
onSelectedChange={this.onSelectedChange}
|
||||
onRemoveSelectedStateItem={this.onRemoveSelectedStateItem}
|
||||
/> :
|
||||
null
|
||||
/>
|
||||
}
|
||||
</PageContentBody>
|
||||
</PageContentBodyConnector>
|
||||
|
||||
{
|
||||
!rootFoldersError &&
|
||||
!rootFoldersFetching &&
|
||||
!!unmappedFolders.length ?
|
||||
!rootFoldersError && rootFoldersPopulated && !!unmappedFolders.length &&
|
||||
<ImportMovieFooterConnector
|
||||
selectedIds={this.getSelectedIds()}
|
||||
onInputChange={this.onInputChange}
|
||||
onImportPress={this.onImportPress}
|
||||
/> :
|
||||
null
|
||||
/>
|
||||
}
|
||||
</PageContent>
|
||||
);
|
||||
|
||||
@@ -3,10 +3,10 @@ import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import createRouteMatchShape from 'Helpers/Props/Shapes/createRouteMatchShape';
|
||||
import { setAddMovieDefault } from 'Store/Actions/addMovieActions';
|
||||
import { clearImportMovie, importMovie, setImportMovieValue } from 'Store/Actions/importMovieActions';
|
||||
import { setImportMovieValue, importMovie, clearImportMovie } from 'Store/Actions/importMovieActions';
|
||||
import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
|
||||
import { setAddMovieDefault } from 'Store/Actions/addMovieActions';
|
||||
import createRouteMatchShape from 'Helpers/Props/Shapes/createRouteMatchShape';
|
||||
import ImportMovie from './ImportMovie';
|
||||
|
||||
function createMapStateToProps() {
|
||||
@@ -71,14 +71,15 @@ class ImportMovieConnector extends Component {
|
||||
|
||||
componentDidMount() {
|
||||
const {
|
||||
rootFolderId,
|
||||
qualityProfiles,
|
||||
defaultQualityProfileId,
|
||||
dispatchFetchRootFolders,
|
||||
dispatchSetAddMovieDefault
|
||||
} = this.props;
|
||||
|
||||
dispatchFetchRootFolders({ id: rootFolderId, timeout: false });
|
||||
if (!this.props.rootFoldersPopulated) {
|
||||
dispatchFetchRootFolders();
|
||||
}
|
||||
|
||||
let setDefaults = false;
|
||||
const setDefaultPayload = {};
|
||||
@@ -138,8 +139,6 @@ const routeMatchShape = createRouteMatchShape({
|
||||
|
||||
ImportMovieConnector.propTypes = {
|
||||
match: routeMatchShape.isRequired,
|
||||
rootFolderId: PropTypes.number.isRequired,
|
||||
rootFoldersFetching: PropTypes.bool.isRequired,
|
||||
rootFoldersPopulated: PropTypes.bool.isRequired,
|
||||
qualityProfiles: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
defaultQualityProfileId: PropTypes.number.isRequired,
|
||||
|
||||
@@ -31,7 +31,3 @@
|
||||
margin: 0 10px 0 12px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.importError {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
// import CheckInput from 'Components/Form/CheckInput';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import Icon from 'Components/Icon';
|
||||
import { inputTypes, kinds } from 'Helpers/Props';
|
||||
import Button from 'Components/Link/Button';
|
||||
import SpinnerButton from 'Components/Link/SpinnerButton';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
// import CheckInput from 'Components/Form/CheckInput';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import PageContentFooter from 'Components/Page/PageContentFooter';
|
||||
import Popover from 'Components/Tooltip/Popover';
|
||||
import { icons, inputTypes, kinds, tooltipPositions } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './ImportMovieFooter.css';
|
||||
|
||||
const MIXED = 'mixed';
|
||||
@@ -96,10 +93,7 @@ class ImportMovieFooter extends Component {
|
||||
isMonitorMixed,
|
||||
isQualityProfileIdMixed,
|
||||
isMinimumAvailabilityMixed,
|
||||
hasUnsearchedItems,
|
||||
importError,
|
||||
onImportPress,
|
||||
onLookupPress,
|
||||
onCancelLookupPress
|
||||
} = this.props;
|
||||
|
||||
@@ -113,7 +107,7 @@ class ImportMovieFooter extends Component {
|
||||
<PageContentFooter>
|
||||
<div className={styles.inputContainer}>
|
||||
<div className={styles.label}>
|
||||
{translate('Monitor')}
|
||||
Monitor
|
||||
</div>
|
||||
|
||||
<FormInputGroup
|
||||
@@ -128,7 +122,7 @@ class ImportMovieFooter extends Component {
|
||||
|
||||
<div className={styles.inputContainer}>
|
||||
<div className={styles.label}>
|
||||
{translate('MinimumAvailability')}
|
||||
Minimum Availability
|
||||
</div>
|
||||
|
||||
<FormInputGroup
|
||||
@@ -143,7 +137,7 @@ class ImportMovieFooter extends Component {
|
||||
|
||||
<div className={styles.inputContainer}>
|
||||
<div className={styles.label}>
|
||||
{translate('QualityProfile')}
|
||||
Quality Profile
|
||||
</div>
|
||||
|
||||
<FormInputGroup
|
||||
@@ -169,75 +163,31 @@ class ImportMovieFooter extends Component {
|
||||
isDisabled={!selectedCount || isLookingUpMovie}
|
||||
onPress={onImportPress}
|
||||
>
|
||||
{translate('Import')} {selectedCount} {selectedCount > 1 ? translate('Movies') : translate('Movie')}
|
||||
Import {selectedCount} {selectedCount > 1 ? 'Movies' : 'Movie'}
|
||||
</SpinnerButton>
|
||||
|
||||
{
|
||||
isLookingUpMovie ?
|
||||
isLookingUpMovie &&
|
||||
<Button
|
||||
className={styles.loadingButton}
|
||||
kind={kinds.WARNING}
|
||||
onPress={onCancelLookupPress}
|
||||
>
|
||||
{translate('CancelProcessing')}
|
||||
</Button> :
|
||||
null
|
||||
Cancel Processing
|
||||
</Button>
|
||||
}
|
||||
|
||||
{
|
||||
hasUnsearchedItems ?
|
||||
<Button
|
||||
className={styles.loadingButton}
|
||||
kind={kinds.SUCCESS}
|
||||
onPress={onLookupPress}
|
||||
>
|
||||
{translate('StartProcessing')}
|
||||
</Button> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
isLookingUpMovie ?
|
||||
isLookingUpMovie &&
|
||||
<LoadingIndicator
|
||||
className={styles.loading}
|
||||
size={24}
|
||||
/> :
|
||||
null
|
||||
/>
|
||||
}
|
||||
|
||||
{
|
||||
isLookingUpMovie ?
|
||||
translate('ProcessingFolders') :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
importError ?
|
||||
<Popover
|
||||
anchor={
|
||||
<Icon
|
||||
className={styles.importError}
|
||||
name={icons.WARNING}
|
||||
kind={kinds.WARNING}
|
||||
/>
|
||||
}
|
||||
title={translate('ImportErrors')}
|
||||
body={
|
||||
<ul>
|
||||
{
|
||||
importError.responseJSON.map((error, index) => {
|
||||
return (
|
||||
<li key={index}>
|
||||
{error.errorMessage}
|
||||
</li>
|
||||
);
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
position={tooltipPositions.RIGHT}
|
||||
/> :
|
||||
null
|
||||
isLookingUpMovie &&
|
||||
'Processing Folders'
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -256,11 +206,8 @@ ImportMovieFooter.propTypes = {
|
||||
isMonitorMixed: PropTypes.bool.isRequired,
|
||||
isQualityProfileIdMixed: PropTypes.bool.isRequired,
|
||||
isMinimumAvailabilityMixed: PropTypes.bool.isRequired,
|
||||
hasUnsearchedItems: PropTypes.bool.isRequired,
|
||||
importError: PropTypes.object,
|
||||
onInputChange: PropTypes.func.isRequired,
|
||||
onImportPress: PropTypes.func.isRequired,
|
||||
onLookupPress: PropTypes.func.isRequired,
|
||||
onCancelLookupPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import _ from 'lodash';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { cancelLookupMovie, lookupUnsearchedMovies } from 'Store/Actions/importMovieActions';
|
||||
import { cancelLookupMovie } from 'Store/Actions/importMovieActions';
|
||||
import ImportMovieFooter from './ImportMovieFooter';
|
||||
|
||||
function isMixed(items, selectedIds, defaultValue, key) {
|
||||
@@ -25,14 +25,12 @@ function createMapStateToProps() {
|
||||
const {
|
||||
isLookingUpMovie,
|
||||
isImporting,
|
||||
items,
|
||||
importError
|
||||
items
|
||||
} = importMovie;
|
||||
|
||||
const isMonitorMixed = isMixed(items, selectedIds, defaultMonitor, 'monitor');
|
||||
const isQualityProfileIdMixed = isMixed(items, selectedIds, defaultQualityProfileId, 'qualityProfileId');
|
||||
const isMinimumAvailabilityMixed = isMixed(items, selectedIds, defaultMinimumAvailability, 'minimumAvailability');
|
||||
const hasUnsearchedItems = !isLookingUpMovie && items.some((item) => !item.isPopulated);
|
||||
|
||||
return {
|
||||
selectedCount: selectedIds.length,
|
||||
@@ -43,16 +41,13 @@ function createMapStateToProps() {
|
||||
defaultMinimumAvailability,
|
||||
isMonitorMixed,
|
||||
isQualityProfileIdMixed,
|
||||
isMinimumAvailabilityMixed,
|
||||
importError,
|
||||
hasUnsearchedItems
|
||||
isMinimumAvailabilityMixed
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
onLookupPress: lookupUnsearchedMovies,
|
||||
onCancelLookupPress: cancelLookupMovie
|
||||
};
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import React from 'react';
|
||||
import VirtualTableHeader from 'Components/Table/VirtualTableHeader';
|
||||
import VirtualTableHeaderCell from 'Components/Table/VirtualTableHeaderCell';
|
||||
import VirtualTableSelectAllHeaderCell from 'Components/Table/VirtualTableSelectAllHeaderCell';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './ImportMovieHeader.css';
|
||||
|
||||
function ImportMovieHeader(props) {
|
||||
@@ -25,35 +24,35 @@ function ImportMovieHeader(props) {
|
||||
className={styles.folder}
|
||||
name="folder"
|
||||
>
|
||||
{translate('Folder')}
|
||||
Folder
|
||||
</VirtualTableHeaderCell>
|
||||
|
||||
<VirtualTableHeaderCell
|
||||
className={styles.monitor}
|
||||
name="monitor"
|
||||
>
|
||||
{translate('Monitor')}
|
||||
Monitor
|
||||
</VirtualTableHeaderCell>
|
||||
|
||||
<VirtualTableHeaderCell
|
||||
className={styles.minimumAvailability}
|
||||
name="minimumAvailability"
|
||||
>
|
||||
{translate('MinAvailability')}
|
||||
Min Availability
|
||||
</VirtualTableHeaderCell>
|
||||
|
||||
<VirtualTableHeaderCell
|
||||
className={styles.qualityProfile}
|
||||
name="qualityProfileId"
|
||||
>
|
||||
{translate('QualityProfile')}
|
||||
Quality Profile
|
||||
</VirtualTableHeaderCell>
|
||||
|
||||
<VirtualTableHeaderCell
|
||||
className={styles.movie}
|
||||
name="movie"
|
||||
>
|
||||
{translate('Movie')}
|
||||
Movie
|
||||
</VirtualTableHeaderCell>
|
||||
</VirtualTableHeader>
|
||||
);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { inputTypes } from 'Helpers/Props';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
|
||||
import VirtualTableSelectCell from 'Components/Table/Cells/VirtualTableSelectCell';
|
||||
import { inputTypes } from 'Helpers/Props';
|
||||
import ImportMovieSelectMovieConnector from './SelectMovie/ImportMovieSelectMovieConnector';
|
||||
import styles from './ImportMovieRow.css';
|
||||
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { Manager, Popper, Reference } from 'react-popper';
|
||||
import FormInputButton from 'Components/Form/FormInputButton';
|
||||
import TextInput from 'Components/Form/TextInput';
|
||||
import getUniqueElememtId from 'Utilities/getUniqueElementId';
|
||||
import { icons, kinds } from 'Helpers/Props';
|
||||
import Icon from 'Components/Icon';
|
||||
import Portal from 'Components/Portal';
|
||||
import FormInputButton from 'Components/Form/FormInputButton';
|
||||
import Link from 'Components/Link/Link';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import Portal from 'Components/Portal';
|
||||
import { icons, kinds } from 'Helpers/Props';
|
||||
import getUniqueElememtId from 'Utilities/getUniqueElementId';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import TextInput from 'Components/Form/TextInput';
|
||||
import ImportMovieSearchResultConnector from './ImportMovieSearchResultConnector';
|
||||
import ImportMovieTitle from './ImportMovieTitle';
|
||||
import styles from './ImportMovieSelectMovie.css';
|
||||
@@ -175,7 +174,7 @@ class ImportMovieSelectMovie extends Component {
|
||||
kind={kinds.WARNING}
|
||||
/>
|
||||
|
||||
{translate('NoMatchFound')}
|
||||
No match found!
|
||||
</div> :
|
||||
null
|
||||
}
|
||||
@@ -190,7 +189,7 @@ class ImportMovieSelectMovie extends Component {
|
||||
kind={kinds.WARNING}
|
||||
/>
|
||||
|
||||
{translate('SearchFailedPleaseTryAgainLater')}
|
||||
Search failed, please try again later.
|
||||
</div> :
|
||||
null
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Label from 'Components/Label';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import Label from 'Components/Label';
|
||||
import styles from './ImportMovieTitle.css';
|
||||
|
||||
function ImportMovieTitle(props) {
|
||||
@@ -34,7 +33,7 @@ function ImportMovieTitle(props) {
|
||||
<Label
|
||||
kind={kinds.WARNING}
|
||||
>
|
||||
{translate('Existing')}
|
||||
Existing
|
||||
</Label>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Route } from 'react-router-dom';
|
||||
import ImportMovieConnector from 'AddMovie/ImportMovie/Import/ImportMovieConnector';
|
||||
import ImportMovieSelectFolderConnector from 'AddMovie/ImportMovie/SelectFolder/ImportMovieSelectFolderConnector';
|
||||
import Switch from 'Components/Router/Switch';
|
||||
import ImportMovieSelectFolderConnector from 'AddMovie/ImportMovie/SelectFolder/ImportMovieSelectFolderConnector';
|
||||
import ImportMovieConnector from 'AddMovie/ImportMovie/Import/ImportMovieConnector';
|
||||
|
||||
class ImportMovies extends Component {
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import formatBytes from 'Utilities/Number/formatBytes';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import Link from 'Components/Link/Link';
|
||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import TableRow from 'Components/Table/TableRow';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import formatBytes from 'Utilities/Number/formatBytes';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import styles from './ImportMovieRootFolderRow.css';
|
||||
|
||||
function ImportMovieRootFolderRow(props) {
|
||||
@@ -41,7 +40,7 @@ function ImportMovieRootFolderRow(props) {
|
||||
|
||||
<TableRowCell className={styles.actions}>
|
||||
<IconButton
|
||||
title={translate('RemoveRootFolder')}
|
||||
title="Remove root folder"
|
||||
name={icons.REMOVE}
|
||||
onPress={onDeletePress}
|
||||
/>
|
||||
|
||||
@@ -1,33 +1,32 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import FieldSet from 'Components/FieldSet';
|
||||
import FileBrowserModal from 'Components/FileBrowser/FileBrowserModal';
|
||||
import Icon from 'Components/Icon';
|
||||
import { icons, kinds, sizes } from 'Helpers/Props';
|
||||
import Button from 'Components/Link/Button';
|
||||
import FieldSet from 'Components/FieldSet';
|
||||
import Icon from 'Components/Icon';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import FileBrowserModal from 'Components/FileBrowser/FileBrowserModal';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
import PageContentBodyConnector from 'Components/Page/PageContentBodyConnector';
|
||||
import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import { icons, kinds, sizes } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import ImportMovieRootFolderRowConnector from './ImportMovieRootFolderRowConnector';
|
||||
import styles from './ImportMovieSelectFolder.css';
|
||||
|
||||
const rootFolderColumns = [
|
||||
{
|
||||
name: 'path',
|
||||
label: translate('Path'),
|
||||
label: 'Path',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'freeSpace',
|
||||
label: translate('FreeSpace'),
|
||||
label: 'Free Space',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'unmappedFolders',
|
||||
label: translate('UnmappedFolders'),
|
||||
label: 'Unmapped Folders',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
@@ -77,8 +76,8 @@ class ImportMovieSelectFolder extends Component {
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<PageContent title={translate('ImportMovies')}>
|
||||
<PageContentBody>
|
||||
<PageContent title="Import Movies">
|
||||
<PageContentBodyConnector>
|
||||
{
|
||||
isFetching && !isPopulated &&
|
||||
<LoadingIndicator />
|
||||
@@ -86,30 +85,32 @@ class ImportMovieSelectFolder extends Component {
|
||||
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
<div>
|
||||
{translate('UnableToLoadRootFolders')}
|
||||
</div>
|
||||
<div>Unable to load root folders</div>
|
||||
}
|
||||
|
||||
{
|
||||
!error && isPopulated &&
|
||||
<div>
|
||||
<div className={styles.header}>
|
||||
{translate('ImportHeader')}
|
||||
Import movies you already have
|
||||
</div>
|
||||
|
||||
<div className={styles.tips}>
|
||||
{translate('ImportTipsMessage')}
|
||||
Some tips to ensure the import goes smoothly:
|
||||
<ul>
|
||||
<li className={styles.tip} dangerouslySetInnerHTML={{ __html: translate('ImportIncludeQuality', ['<code>movie.2008.bluray.mkv</code>']) }} />
|
||||
<li className={styles.tip} dangerouslySetInnerHTML={{ __html: translate('ImportRootPath', [`<code>${isWindows ? 'C:\\movies' : '/movies'}</code>`, `<code>${isWindows ? 'C:\\movies\\the matrix' : '/movies/the matrix'}</code>`]) }} />
|
||||
<li className={styles.tip}>
|
||||
Make sure that your files include the quality in their filenames. eg. <span className={styles.code}>movie.2008.bluray.mkv</span>
|
||||
</li>
|
||||
<li className={styles.tip}>
|
||||
Point Radarr to the folder containing all of your movies, not a specific one. eg. <span className={styles.code}>"{isWindows ? 'C:\\movies' : '/movies'}"</span> and not <span className={styles.code}>"{isWindows ? 'C:\\movies\\the matrix' : '/movies/the matrix'}"</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{
|
||||
items.length > 0 ?
|
||||
<div className={styles.recentFolders}>
|
||||
<FieldSet legend={translate('RecentFolders')}>
|
||||
<FieldSet legend="Recent Folders">
|
||||
<Table
|
||||
columns={rootFolderColumns}
|
||||
>
|
||||
@@ -140,7 +141,7 @@ class ImportMovieSelectFolder extends Component {
|
||||
className={styles.importButtonIcon}
|
||||
name={icons.DRIVE}
|
||||
/>
|
||||
{translate('ChooseAnotherFolder')}
|
||||
Choose another folder
|
||||
</Button>
|
||||
</div> :
|
||||
|
||||
@@ -154,7 +155,7 @@ class ImportMovieSelectFolder extends Component {
|
||||
className={styles.importButtonIcon}
|
||||
name={icons.DRIVE}
|
||||
/>
|
||||
{translate('StartImport')}
|
||||
Start Import
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
@@ -168,7 +169,7 @@ class ImportMovieSelectFolder extends Component {
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</PageContentBody>
|
||||
</PageContentBodyConnector>
|
||||
</PageContent>
|
||||
);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user