1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-03-07 13:40:38 -05:00

Compare commits

..

3 Commits

Author SHA1 Message Date
ta264
38c5989695 Only query 5 movies ahead 2020-05-24 22:47:51 +01:00
ta264
9ca03ba450 Revert "Revert "Revert "Revert "Use async api calls for Refresh and NetImportSync""""
This reverts commit 0479bb9cc3.
2020-05-24 22:47:23 +01:00
ta264
d283967523 Revert "Revert "Async HttpClient and list lookup""
This reverts commit 1d6a7a1843.
2020-05-24 22:47:20 +01:00
1509 changed files with 14874 additions and 44075 deletions

View File

@@ -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

View File

@@ -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. -->

View File

@@ -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

View File

@@ -6,7 +6,6 @@ YES | NO
#### Todos
- [ ] Tests
- [ ] Translation Keys
#### Issues Fixed or Closed by this PR

5
.github/stale.yml vendored
View File

@@ -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
View File

@@ -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

View File

@@ -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
View 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.

View File

@@ -1,46 +1,42 @@
# Radarr
[![Build Status](https://dev.azure.com/Radarr/Radarr/_apis/build/status/Radarr.Radarr?branchName=develop)](https://dev.azure.com/Radarr/Radarr/_build/latest?definitionId=1&branchName=develop)
[![Translated](https://translate.servarr.com/widgets/radarr/-/radarr/svg-badge.svg)](https://translate.servarr.com/engage/radarr/?utm_source=widget)
[![Docker Pulls](https://img.shields.io/docker/pulls/linuxserver/radarr.svg)](https://github.com/Radarr/Radarr/wiki/Docker)
![Github Downloads](https://img.shields.io/github/downloads/Radarr/Radarr/total.svg)
[![Backers on Open Collective](https://opencollective.com/Radarr/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/Radarr/sponsors/badge.svg)](#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
[![Installation](https://img.shields.io/badge/wiki-installation-brightgreen.svg?maxAge=60&style=flat-square)](https://github.com/Radarr/Radarr/wiki/Installation)
[![Docker](https://img.shields.io/badge/wiki-docker-1488C6.svg?maxAge=60&style=flat-square)](https://github.com/Radarr/Radarr/wiki/Docker)
[![Docker](https://img.shields.io/badge/wiki-docker-1488C6.svg?maxAge=60&style=flat-square)](https://github.com/Radarr/Radarr/wiki/Docker)
[![Setup Guide](https://img.shields.io/badge/wiki-setup_guide-orange.svg?maxAge=60&style=flat-square)](https://github.com/Radarr/Radarr/wiki/Setup-Guide)
[![FAQ](https://img.shields.io/badge/wiki-FAQ-BF55EC.svg?maxAge=60&style=flat-square)](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 | [![GitHub Releases](https://img.shields.io/badge/downloads-releases-brightgreen.svg?maxAge=60&style=flat-square)](https://github.com/Radarr/Radarr/releases) | [![Azure Build](https://img.shields.io/badge/downloads-Windows_X64-green.svg?maxAge=60&style=flat-square)](https://radarr.servarr.com/v1/update/nightly/updatefile?os=windows&runtime=netcore&arch=x64) <br> [![Azure Build](https://img.shields.io/badge/downloads-Linux_X64-green.svg?maxAge=60&style=flat-square)](https://radarr.servarr.com/v1/update/nightly/updatefile?os=linux&runtime=netcore&arch=x64) <br> [![Azure Build](https://img.shields.io/badge/downloads-Linux_ARM64-green.svg?maxAge=60&style=flat-square)](https://radarr.servarr.com/v1/update/nightly/updatefile?os=linux&runtime=netcore&arch=arm64) [![Azure Build](https://img.shields.io/badge/downloads-Linux_ARM-green.svg?maxAge=60&style=flat-square)](https://radarr.servarr.com/v1/update/nightly/updatefile?os=linux&runtime=netcore&arch=arm) <br> [![Azure Build](https://img.shields.io/badge/downloads-macOS-green.svg?maxAge=60&style=flat-square)](https://radarr.servarr.com/v1/update/nightly/updatefile?os=osx&runtime=netcore&arch=x64)
| Docker - lsio | [![Docker release](https://img.shields.io/badge/linuxserver-radarr:latest-blue.svg?colorB=1488C6&maxAge=60&style=flat-square)](https://hub.docker.com/r/linuxserver/radarr) | [![Docker nightly](https://img.shields.io/badge/linuxserver-radarr:nightly-blue.svg?colorB=1488C6&maxAge=60&style=flat-square)](https://hub.docker.com/r/linuxserver/radarr) |
| Docker - hotio | [![Docker release](https://img.shields.io/badge/hotio-radarr:latest-blue.svg?colorB=1488C6&maxAge=60&style=flat-square)](https://hub.docker.com/r/hotio/radarr) | [![Docker nightly](https://img.shields.io/badge/hotio-radarr:nightly-blue.svg?colorB=1488C6&maxAge=60&style=flat-square)](https://hub.docker.com/r/hotio/radarr) |
| Release Type | Branch: develop (stable) | Branch: nightly (semi-unstable) | Branch: aphrodite (very-unstable) |
|-----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Binary Releases | [![GitHub Releases](https://img.shields.io/badge/downloads-releases-brightgreen.svg?maxAge=60&style=flat-square)](https://github.com/Radarr/Radarr/releases) | [![AppVeyor Builds](https://img.shields.io/badge/downloads-nightly-green.svg?maxAge=60&style=flat-square)](https://ci.appveyor.com/project/galli-leo/radarr-usby1/branch/develop/artifacts) | |
| Docker | [![Docker release](https://img.shields.io/badge/linuxserver-radarr:latest-blue.svg?colorB=1488C6&maxAge=60&style=flat-square)](https://hub.docker.com/r/linuxserver/radarr) | [![Docker nightly](https://img.shields.io/badge/linuxserver-radarr:nightly-blue.svg?colorB=1488C6&maxAge=60&style=flat-square)](https://hub.docker.com/r/linuxserver/radarr) | [![Docker aphrodite](https://img.shields.io/badge/linuxserver-radarr:preview-blue.svg?colorB=1488C6&maxAge=60&style=flat-square)](https://hub.docker.com/r/linuxserver/radarr) |
| Docker | [![Docker release](https://img.shields.io/badge/hotio-radarr:latest-blue.svg?colorB=1488C6&maxAge=60&style=flat-square)](https://hub.docker.com/r/hotio/radarr) | [![Docker nightly](https://img.shields.io/badge/hotio-radarr:unstable-blue.svg?colorB=1488C6&maxAge=60&style=flat-square)](https://hub.docker.com/r/hotio/radarr) | [![Docker aphrodite](https://img.shields.io/badge/hotio-radarr:aphrodite-blue.svg?colorB=1488C6&maxAge=60&style=flat-square)](https://hub.docker.com/r/hotio/radarr) |
## Support
[![Discord](https://img.shields.io/badge/discord-chat-r5wJPt9.svg?maxAge=60&style=flat-square)](https://discord.gg/r5wJPt9)
[![OpenCollective](https://opencollective.com/radarr/tiers/backer/badge.svg)](#backers)
[![OpenCollective](https://opencollective.com/radarr/tiers/flexible-sponsor/badge.svg)](#flexible-sponsors)
[![OpenCollective](https://opencollective.com/radarr/tiers/sponsor/badge.svg)](#sponsors)
[![Discord](https://img.shields.io/badge/discord-chat-7289DA.svg?maxAge=60&style=flat-square)](https://discord.gg/AD3UP37)
[![Reddit](https://img.shields.io/badge/reddit-discussion-FF4500.svg?maxAge=60&style=flat-square)](https://www.reddit.com/r/radarr)
[![Feathub](https://img.shields.io/badge/feathub-requests-lightgrey.svg?maxAge=60&style=flat-square)](http://feathub.com/Radarr/Radarr)
[![GitHub](https://img.shields.io/badge/github-issues-red.svg?maxAge=60&style=flat-square)](https://github.com/Radarr/Radarr/issues)
[![GitHub Wiki](https://img.shields.io/badge/github-wiki-181717.svg?maxAge=60&style=flat-square)](https://github.com/Radarr/Radarr/wiki)
@@ -49,13 +45,23 @@ Each push to the "develop" branch creates a build on "nightly" release channel (
[![GitHub issues](https://img.shields.io/github/issues/radarr/radarr.svg?maxAge=60&style=flat-square)](https://github.com/Radarr/Radarr/issues)
[![GitHub pull requests](https://img.shields.io/github/issues-pr/radarr/radarr.svg?maxAge=60&style=flat-square)](https://github.com/Radarr/Radarr/pulls)
[![GNU GPL v3](https://img.shields.io/badge/license-GNU%20GPL%20v3-blue.svg?maxAge=60&style=flat-square)](http://www.gnu.org/licenses/gpl.html)
[![Copyright 2010-2020](https://img.shields.io/badge/copyright-2020-blue.svg?maxAge=60&style=flat-square)](https://github.com/Radarr/Radarr)
[![Copyright 2010-2017](https://img.shields.io/badge/copyright-2017-blue.svg?maxAge=60&style=flat-square)](https://github.com/Radarr/Radarr)
[![Github Releases](https://img.shields.io/github/downloads/Radarr/Radarr/total.svg?maxAge=60&style=flat-square)](https://github.com/Radarr/Radarr/releases/)
[![Docker Pulls](https://img.shields.io/docker/pulls/linuxserver/radarr.svg?maxAge=60&style=flat-square)](https://hub.docker.com/r/linuxserver/radarr/)
[![Changelog](https://img.shields.io/github/commit-activity/w/radarr/radarr.svg?style=flat-square)](/CHANGELOG.md#unreleased)
| Service | Master | Develop |
|----------|:---------------------------:|:----------------------------:|
| AppVeyor | [![AppVeyor](https://img.shields.io/appveyor/ci/galli-leo/Radarr/master.svg?maxAge=60&style=flat-square)](https://ci.appveyor.com/project/galli-leo/Radarr) | [![AppVeyor](https://img.shields.io/appveyor/ci/galli-leo/Radarr-usby1/develop.svg?maxAge=60&style=flat-square)](https://ci.appveyor.com/project/galli-leo/Radarr-usby1) |
| Travis | [![Travis](https://img.shields.io/travis/Radarr/Radarr/master.svg?maxAge=60&style=flat-square)](https://travis-ci.org/Radarr/Radarr) | [![Travis](https://img.shields.io/travis/Radarr/Radarr/develop.svg?maxAge=60&style=flat-square)](https://travis-ci.org/Radarr/Radarr) |
### [Site and API Status](https://status.radarr.video)
| API | Updates | Sites |
|-------|:----:|:----:|
| [![API V2 (develop)](http://status.radarr.video/component/1/shield?style=flat-square)](https://api.radarr.video/v2/) | [![Update Server](http://status.radarr.video/component/4/shield?style=flat-square)](https://radarr.aeonlucid.com) | [![Radarr Mappings](http://status.radarr.video/component/6/shield?style=flat-square)](https://mappings.radarr.video/)
| [![API Staging (nightly)](http://status.radarr.video/component/2/shield?style=flat-square)](https://staging.api.radarr.video/) | [![Github Updates](http://status.radarr.video/component/5/shield?style=flat-square)](https://api.github.com/v3/) | [![Main Site](http://status.radarr.video/component/7/shield?style=flat-square)](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
View File

@@ -0,0 +1,3 @@
skip_commits:
files:
- '**/**'

View File

@@ -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'))

View File

@@ -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
View 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
}
}

View File

@@ -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$']
]
}
]
}
}
]
};

View File

@@ -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",

View File

@@ -10,7 +10,8 @@ gulp.task('build',
'webpack',
'copyHtml',
'copyFonts',
'copyImages'
'copyImages',
'copyJs'
)
)
);

View File

@@ -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'))

View File

@@ -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)

View File

@@ -1,20 +0,0 @@
{
"compilerOptions": {
"target": "es6",
"checkJs": false,
"baseUrl": "src",
"jsx": "react",
"module": "commonjs",
"moduleResolution": "node",
"paths": {
"*": [
"*"
]
}
},
"include": [
"./src/**/*"
],
"exclude": [
]
}

View File

@@ -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
};

View File

@@ -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,

View File

@@ -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>

View File

@@ -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
};

View File

@@ -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 }));
}
};
}

View File

@@ -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>

View File

@@ -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'
]);
}
);
}

View File

@@ -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>

View File

@@ -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>
);
}

View File

@@ -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() {

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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}

View File

@@ -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() {

View File

@@ -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
};

View File

@@ -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>

View File

@@ -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}

View File

@@ -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;

View File

@@ -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

View File

@@ -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

View File

@@ -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(

View File

@@ -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}>
-

View File

@@ -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'
);

View File

@@ -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;

View 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'
);

View File

@@ -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'
};
}
);

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);

View File

@@ -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';

View 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;

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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);

View 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;
}

View 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;

View File

@@ -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);

View File

@@ -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;

View File

@@ -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);

View File

@@ -0,0 +1,9 @@
.status {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 60px;
}
.statusIcon {
width: 20px !important;
}

View 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;

View File

@@ -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>
);
}

View File

@@ -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);

View File

@@ -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>

View File

@@ -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';

View File

@@ -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;
}
}

View File

@@ -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,

View File

@@ -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() {

View File

@@ -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>
);

View File

@@ -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,

View File

@@ -31,7 +31,3 @@
margin: 0 10px 0 12px;
text-align: left;
}
.importError {
margin-left: 10px;
}

View File

@@ -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
};

View File

@@ -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
};

View File

@@ -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>
);

View File

@@ -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';

View File

@@ -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
}

View File

@@ -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>

View File

@@ -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 {

View File

@@ -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}
/>

View File

@@ -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