mirror of
https://github.com/Radarr/Radarr.git
synced 2026-03-05 13:21:25 -05:00
Compare commits
384 Commits
v3.0.2.436
...
v3.1.0.489
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d17f0b1db | ||
|
|
2e62aad279 | ||
|
|
f2c9fcde7c | ||
|
|
2716bd6ae1 | ||
|
|
b67f6b98ae | ||
|
|
7de2c8d41f | ||
|
|
5dabec5cc8 | ||
|
|
fc61687e82 | ||
|
|
398fc4dca2 | ||
|
|
f883cab6db | ||
|
|
0a18898e36 | ||
|
|
19d533b50a | ||
|
|
4ce98d689d | ||
|
|
ef9c1bf0a4 | ||
|
|
bec8312d61 | ||
|
|
ac5736c05b | ||
|
|
35abd99b3f | ||
|
|
0fb8aabb41 | ||
|
|
cbc14a1a2c | ||
|
|
627ab64fd0 | ||
|
|
7fdfd0cfb7 | ||
|
|
4052e7160c | ||
|
|
f5d3defa52 | ||
|
|
4cb87309b2 | ||
|
|
c6ec9c316d | ||
|
|
fc26f78558 | ||
|
|
4eaad6ca4b | ||
|
|
7abbe2c8c0 | ||
|
|
40839424be | ||
|
|
3234cff09c | ||
|
|
d715bb4437 | ||
|
|
df88d9a8bf | ||
|
|
a3f4695442 | ||
|
|
321ce87d4d | ||
|
|
30e9a7a1b9 | ||
|
|
f26b00444f | ||
|
|
e088a7258e | ||
|
|
f705692fb7 | ||
|
|
92feeb49a6 | ||
|
|
0d7d647291 | ||
|
|
2ddaef5c50 | ||
|
|
0aaf0f702b | ||
|
|
97e9d64397 | ||
|
|
f1583dcb13 | ||
|
|
dc4dbf4610 | ||
|
|
bb21798c42 | ||
|
|
07b2890f30 | ||
|
|
d9534a857f | ||
|
|
2b17b5310e | ||
|
|
b330a15431 | ||
|
|
ad79250783 | ||
|
|
845b96508f | ||
|
|
cff95eb251 | ||
|
|
d8542f4689 | ||
|
|
99642f15fe | ||
|
|
5d1c600062 | ||
|
|
5cee22f207 | ||
|
|
bb3871e9ea | ||
|
|
434c095c54 | ||
|
|
4808fb1429 | ||
|
|
7dd6805512 | ||
|
|
c31070ab91 | ||
|
|
e6974eb99e | ||
|
|
a72e3d78d7 | ||
|
|
7c707babff | ||
|
|
3b254fd54f | ||
|
|
d9e9df6c26 | ||
|
|
c6b969cd61 | ||
|
|
5ccee34107 | ||
|
|
b8724c384a | ||
|
|
acd82e36c5 | ||
|
|
afd2b7a29e | ||
|
|
74a26b02db | ||
|
|
356926a445 | ||
|
|
19128e70e7 | ||
|
|
aa75bc241d | ||
|
|
88780f33a4 | ||
|
|
1ed30713f5 | ||
|
|
3b3dbfec60 | ||
|
|
e011614570 | ||
|
|
2a1b2187ba | ||
|
|
ea3a6171fa | ||
|
|
c9477d4b5a | ||
|
|
cc466b9e5b | ||
|
|
e9ece8a319 | ||
|
|
22a5304ad8 | ||
|
|
bcb16d484a | ||
|
|
50ebd283dd | ||
|
|
53b9332675 | ||
|
|
bfc969c45c | ||
|
|
31770cd889 | ||
|
|
6267497fc0 | ||
|
|
efeb216383 | ||
|
|
8eb0f33718 | ||
|
|
176bf7ce32 | ||
|
|
4340b6b922 | ||
|
|
ec2c7ec5af | ||
|
|
f3fb67a62e | ||
|
|
ec8d1c4ae6 | ||
|
|
ea3e523f8d | ||
|
|
e50550bb33 | ||
|
|
e0957bfd20 | ||
|
|
582322adfb | ||
|
|
7a3964c42a | ||
|
|
b2f6c4dcdc | ||
|
|
45f8700689 | ||
|
|
7c5fe561e0 | ||
|
|
5145a4a211 | ||
|
|
2aa2164763 | ||
|
|
05bde70b14 | ||
|
|
d8c138694d | ||
|
|
ad8504fa89 | ||
|
|
2f26a955d1 | ||
|
|
3d7808745b | ||
|
|
1d7082593d | ||
|
|
82420d6956 | ||
|
|
77b84be8d1 | ||
|
|
24330beca9 | ||
|
|
dd50f1c564 | ||
|
|
4f63015a3e | ||
|
|
568185c908 | ||
|
|
b56525de27 | ||
|
|
4673736b67 | ||
|
|
ffae7d6b31 | ||
|
|
42146fc366 | ||
|
|
de6a465370 | ||
|
|
bef12f10a8 | ||
|
|
8064a43dee | ||
|
|
3c2e127601 | ||
|
|
6ea370870d | ||
|
|
f6f8934ce3 | ||
|
|
3c59b6aac0 | ||
|
|
2e0d603f4a | ||
|
|
e8972f2273 | ||
|
|
578ce25166 | ||
|
|
fc12770495 | ||
|
|
5d5e66f0d7 | ||
|
|
fe2af13fae | ||
|
|
10205da1c3 | ||
|
|
914d3764dc | ||
|
|
59f8a4d2e2 | ||
|
|
024000275d | ||
|
|
a3723b5ad7 | ||
|
|
e9e034d193 | ||
|
|
52dbcfe852 | ||
|
|
f488fe75e0 | ||
|
|
47e85e6388 | ||
|
|
3264de3a96 | ||
|
|
1c04aad92e | ||
|
|
3b2cf55788 | ||
|
|
b66ce95139 | ||
|
|
f6753bf2c2 | ||
|
|
2a981c3bbc | ||
|
|
82004b6535 | ||
|
|
eb36a1c134 | ||
|
|
02109437e4 | ||
|
|
38872ffc0a | ||
|
|
5e940330b4 | ||
|
|
53b3b58477 | ||
|
|
d6101259ad | ||
|
|
cf94178623 | ||
|
|
999f6cbcd3 | ||
|
|
f0191525a1 | ||
|
|
596803cf0b | ||
|
|
e90465c4ae | ||
|
|
0c21467411 | ||
|
|
d9d1d8ecad | ||
|
|
d9faeb11c2 | ||
|
|
7b13ec0dbd | ||
|
|
6ffc57c106 | ||
|
|
ee1bcb20dc | ||
|
|
19d8604022 | ||
|
|
5926ca5d8a | ||
|
|
24de3fdee6 | ||
|
|
be40a0d738 | ||
|
|
2b97352661 | ||
|
|
88bc44f7ba | ||
|
|
95527218af | ||
|
|
8a062d1cba | ||
|
|
4acd885f19 | ||
|
|
a19c292779 | ||
|
|
aa9880b8dd | ||
|
|
abe8a06c11 | ||
|
|
7ea749e93c | ||
|
|
f85fc5e578 | ||
|
|
20e3a357a5 | ||
|
|
60ac9d3ad7 | ||
|
|
e5fc423f29 | ||
|
|
3f5ff23210 | ||
|
|
a9792973ee | ||
|
|
90289de22c | ||
|
|
a68011988e | ||
|
|
2fd030bd5c | ||
|
|
33cd9f555e | ||
|
|
94df76b6e6 | ||
|
|
065846464d | ||
|
|
108609d064 | ||
|
|
0fcad533eb | ||
|
|
c7b5a42bea | ||
|
|
fa6b7ad287 | ||
|
|
b51ce06e04 | ||
|
|
63cf10c29a | ||
|
|
6b5e743583 | ||
|
|
743946b929 | ||
|
|
cf89533b37 | ||
|
|
7031a8fe44 | ||
|
|
d8c962a911 | ||
|
|
6702c7d21b | ||
|
|
e64dd799e6 | ||
|
|
47c2a15b70 | ||
|
|
4bf726cc66 | ||
|
|
dd61480d60 | ||
|
|
b0753ab153 | ||
|
|
281afe7d9d | ||
|
|
bd0fe16b52 | ||
|
|
901723b8e3 | ||
|
|
3a146ea667 | ||
|
|
88b9a47c79 | ||
|
|
fe5445ba31 | ||
|
|
39f08b6283 | ||
|
|
8831fd7b50 | ||
|
|
70e4324a7c | ||
|
|
35a53c5627 | ||
|
|
9b85f328a6 | ||
|
|
012fe53acc | ||
|
|
566c1405c2 | ||
|
|
83c637e8cb | ||
|
|
fd612f9258 | ||
|
|
621edba82d | ||
|
|
f03dfda2f0 | ||
|
|
e623efefd3 | ||
|
|
7742a81782 | ||
|
|
8588d2e2a3 | ||
|
|
86c5f06c5b | ||
|
|
6d7fb3de25 | ||
|
|
a0d2af54e8 | ||
|
|
ad3ddd11cf | ||
|
|
1372abecc0 | ||
|
|
3ddbd30604 | ||
|
|
eff2e11652 | ||
|
|
aa95781d5d | ||
|
|
6e46720d7b | ||
|
|
7e70166b62 | ||
|
|
444305aa65 | ||
|
|
8fd1f75e21 | ||
|
|
625cdab04a | ||
|
|
325a9c34e3 | ||
|
|
a7e4078c53 | ||
|
|
d2bfddc182 | ||
|
|
a6777015f7 | ||
|
|
f8b211c561 | ||
|
|
fce86db9c5 | ||
|
|
2ff07a1368 | ||
|
|
208b17e9f5 | ||
|
|
e0ce7b3294 | ||
|
|
7af75da694 | ||
|
|
86213d91a2 | ||
|
|
eed4a4ec68 | ||
|
|
24d4390550 | ||
|
|
4983cb9745 | ||
|
|
e71e386cb1 | ||
|
|
d03a37ed60 | ||
|
|
5ea88f6bcd | ||
|
|
d74415a3d7 | ||
|
|
f4deefd21e | ||
|
|
6296356e42 | ||
|
|
a777880a91 | ||
|
|
eb311f33ef | ||
|
|
34ba436204 | ||
|
|
45bb108127 | ||
|
|
0443cc34c6 | ||
|
|
6ad6bf270f | ||
|
|
2237624333 | ||
|
|
39d11b4669 | ||
|
|
70faa123ee | ||
|
|
f4869e6bc9 | ||
|
|
2a3fc81b8a | ||
|
|
a2182e2fca | ||
|
|
fe23c985a2 | ||
|
|
4ffd0d2ddb | ||
|
|
efd3aad6b0 | ||
|
|
11b4967629 | ||
|
|
b6e46ad8fd | ||
|
|
c28b62da2a | ||
|
|
600c7e3391 | ||
|
|
7107aeaddc | ||
|
|
913766f9af | ||
|
|
9e7ea6f55b | ||
|
|
c2c4186965 | ||
|
|
1a755bd3c0 | ||
|
|
e5f66da087 | ||
|
|
2a93686360 | ||
|
|
f33f004aa9 | ||
|
|
30293bc7cc | ||
|
|
a1db7a8f1e | ||
|
|
539fcb91c9 | ||
|
|
970848acc3 | ||
|
|
2b7a383886 | ||
|
|
4dc67c027b | ||
|
|
a12395b468 | ||
|
|
19dceb35fa | ||
|
|
b56043504b | ||
|
|
f2efdd8b6d | ||
|
|
13b14f4d38 | ||
|
|
ecc2996669 | ||
|
|
1df31f9059 | ||
|
|
79d4e0da80 | ||
|
|
0dcb07f170 | ||
|
|
e8040a0819 | ||
|
|
553a8f2a0a | ||
|
|
c892b827af | ||
|
|
1b36b512d7 | ||
|
|
560dc1ffed | ||
|
|
14d43e6d6b | ||
|
|
bc1e131e49 | ||
|
|
61509b53d1 | ||
|
|
b7f680ac98 | ||
|
|
c9526b7dfd | ||
|
|
9f6bca0810 | ||
|
|
114549ca7b | ||
|
|
481863ca1f | ||
|
|
13e55f7c37 | ||
|
|
397442f32e | ||
|
|
71ae27fc0c | ||
|
|
286468e6ab | ||
|
|
aafd82ba73 | ||
|
|
cff5c03af3 | ||
|
|
c394fcc085 | ||
|
|
a6f237bd70 | ||
|
|
013915be62 | ||
|
|
9bec7d2ad0 | ||
|
|
808797f403 | ||
|
|
230a3df3f3 | ||
|
|
26f2b6a3d9 | ||
|
|
e039dd81df | ||
|
|
cd6dd33359 | ||
|
|
8803ef4dbd | ||
|
|
5f0b2c91a1 | ||
|
|
1684fbb119 | ||
|
|
326bcdc18c | ||
|
|
ac0845eedf | ||
|
|
aa00959010 | ||
|
|
986f8d43c0 | ||
|
|
07f5312d43 | ||
|
|
7754589353 | ||
|
|
8428de7a00 | ||
|
|
d210082dad | ||
|
|
4f9df6a114 | ||
|
|
a5726cd65e | ||
|
|
3bfb08c3ff | ||
|
|
ceeea9f7c6 | ||
|
|
83b1e037d5 | ||
|
|
9cc5343547 | ||
|
|
e3cc4f4cb1 | ||
|
|
f0bb614360 | ||
|
|
9241f4a40d | ||
|
|
8e6157074d | ||
|
|
188b02e5c7 | ||
|
|
acb8e1d3c8 | ||
|
|
ab31cccfb2 | ||
|
|
42afaa17ee | ||
|
|
c6123a076f | ||
|
|
0f0aaeef4b | ||
|
|
2983e27dcd | ||
|
|
a18036eed4 | ||
|
|
2013e1f580 | ||
|
|
1e396ead5e | ||
|
|
5ea3ad45cb | ||
|
|
26a04c9fd1 | ||
|
|
7f29af6d4d | ||
|
|
92f3a18e17 | ||
|
|
7836246b05 | ||
|
|
e2b2061ee1 | ||
|
|
8d651e6f7a | ||
|
|
25c0ed1cd0 | ||
|
|
638db3d8d7 | ||
|
|
3a5d18f52d | ||
|
|
c8d0a6a4ea | ||
|
|
553b8b1945 | ||
|
|
e263d066da | ||
|
|
cdaf88efa1 | ||
|
|
ea801665e4 | ||
|
|
a81b6f41af | ||
|
|
177d77db69 |
@@ -110,13 +110,13 @@ 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.CA1002.severity = suggestion
|
||||
dotnet_diagnostic.CA1003.severity = suggestion
|
||||
dotnet_diagnostic.CA1008.severity = suggestion
|
||||
dotnet_diagnostic.CA1010.severity = suggestion
|
||||
@@ -163,10 +163,16 @@ dotnet_diagnostic.CA1304.severity = suggestion
|
||||
dotnet_diagnostic.CA1305.severity = suggestion
|
||||
dotnet_diagnostic.CA1307.severity = suggestion
|
||||
dotnet_diagnostic.CA1308.severity = suggestion
|
||||
dotnet_diagnostic.CA1309.severity = suggestion
|
||||
dotnet_diagnostic.CA1310.severity = suggestion
|
||||
dotnet_diagnostic.CA1401.severity = suggestion
|
||||
dotnet_diagnostic.CA1416.severity = suggestion
|
||||
dotnet_diagnostic.CA1507.severity = suggestion
|
||||
dotnet_diagnostic.CA1508.severity = suggestion
|
||||
dotnet_diagnostic.CA1707.severity = suggestion
|
||||
dotnet_diagnostic.CA1708.severity = suggestion
|
||||
dotnet_diagnostic.CA1710.severity = suggestion
|
||||
dotnet_diagnostic.CA1711.severity = suggestion
|
||||
dotnet_diagnostic.CA1712.severity = suggestion
|
||||
dotnet_diagnostic.CA1714.severity = suggestion
|
||||
dotnet_diagnostic.CA1715.severity = suggestion
|
||||
@@ -175,12 +181,14 @@ dotnet_diagnostic.CA1717.severity = suggestion
|
||||
dotnet_diagnostic.CA1720.severity = suggestion
|
||||
dotnet_diagnostic.CA1721.severity = suggestion
|
||||
dotnet_diagnostic.CA1724.severity = suggestion
|
||||
dotnet_diagnostic.CA1725.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.CA1813.severity = suggestion
|
||||
dotnet_diagnostic.CA1814.severity = suggestion
|
||||
dotnet_diagnostic.CA1815.severity = suggestion
|
||||
dotnet_diagnostic.CA1816.severity = suggestion
|
||||
@@ -202,6 +210,7 @@ dotnet_diagnostic.CA2101.severity = suggestion
|
||||
dotnet_diagnostic.CA2119.severity = suggestion
|
||||
dotnet_diagnostic.CA2153.severity = suggestion
|
||||
dotnet_diagnostic.CA2200.severity = suggestion
|
||||
dotnet_diagnostic.CA2201.severity = suggestion
|
||||
dotnet_diagnostic.CA2207.severity = suggestion
|
||||
dotnet_diagnostic.CA2208.severity = suggestion
|
||||
dotnet_diagnostic.CA2211.severity = suggestion
|
||||
@@ -247,6 +256,8 @@ dotnet_diagnostic.CA5374.severity = suggestion
|
||||
dotnet_diagnostic.CA5379.severity = suggestion
|
||||
dotnet_diagnostic.CA5384.severity = suggestion
|
||||
dotnet_diagnostic.CA5385.severity = suggestion
|
||||
dotnet_diagnostic.CA5392.severity = suggestion
|
||||
dotnet_diagnostic.CA5394.severity = suggestion
|
||||
dotnet_diagnostic.CA5397.severity = suggestion
|
||||
|
||||
|
||||
|
||||
32
.github/ISSUE_TEMPLATE/bug_report.md
vendored
32
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,35 +1,37 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Support Requests will be closed immediately, if you are unsure go to our Reddit or Discord first. Exceptions do not mean you found a bug!
|
||||
about: Support Requests will be closed immediately, if you are not 100% certain this is a bug please go to our Reddit or Discord first. Exceptions do not mean you found a bug!
|
||||
title: ''
|
||||
labels: bug
|
||||
labels: 'Type: Bug'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- Support Requests will be closed immediately, if you are unsure go to our Reddit or Discord first. Exceptions do not mean you found a bug! -->
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
<!-- A clear and concise description of what the bug is. -->
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
<!-- Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
4. See error -->
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
<!-- A clear and concise description of what you expected to happen.-->
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
<!-- If applicable, add screenshots to help explain your problem.-->
|
||||
|
||||
**Platform Information (please complete the following information):**
|
||||
- OS: [e.g. Windows 10 2004 / Ubuntu 20.10]
|
||||
- Docker: [Yes/No]
|
||||
- Mono or.NET Core Version: [e.g. Mono 5.8 or .Net Core 3.1.10] (found under System -> Status)
|
||||
- Browser and Version [e.g. chrome 86.0.4240.198] (Only needed for UI issues)
|
||||
- Radarr Version [e.g. 3.0.0.2956]
|
||||
- Radarr Branch [e.g. master]
|
||||
- OS: <!-- [e.g. Windows 10 2004 / Ubuntu 20.04] -->
|
||||
- Docker: <!-- [Yes/No] -->
|
||||
- Mono or .NET Version (System -> Status): <!--[e.g. Mono 5.8 or .Net Core 3.1.10 or .NET 5.0.1] -->
|
||||
- Browser and Version (Only needed for UI issues): <!--[e.g. chrome 86.0.4240.198] -->
|
||||
- Radarr Version: <!--[e.g. 3.0.1.4259, 3.0.2.4369]-->
|
||||
- Radarr Branch: <!--[e.g. master, develop]-->
|
||||
|
||||
**Trace Logs**
|
||||
Turn on Trace logs under Settings -> General and wait for the bug to occur again. **Upload the full log file here (or another site (e.g. pastebin) and link it). Issues will be closed, if they do not include this!**
|
||||
Turn on Trace logs under Settings -> General and wait for the bug to occur again.
|
||||
**Upload the full log file here (or another site (e.g. pastebin) and link it). Issues will be closed, if they do not include this!**
|
||||
<!-- Trace logs are named Radarr.trace.txt or Radarr.trace.#.txt and will contain "trace" in them-->
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,7 +1,7 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Support via Discord
|
||||
url: https://discord.gg/r5wJPt9
|
||||
url: https://radarr.video/discord
|
||||
about: Chat with users and devs on support and setup related topics.
|
||||
- name: Support via Reddit
|
||||
url: https://reddit.com/r/radarr
|
||||
|
||||
8
.github/ISSUE_TEMPLATE/feature_request.md
vendored
8
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -8,13 +8,13 @@ assignees: ''
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
<!-- A clear and concise description of what you want to happen. -->
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
<!-- Add any other context or screenshots about the feature request here. -->
|
||||
|
||||
4
.github/stale.yml
vendored
4
.github/stale.yml
vendored
@@ -5,12 +5,10 @@ daysUntilClose: 7
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- feature request
|
||||
- parser
|
||||
- confirmed
|
||||
- 'Status: Confirmed'
|
||||
- sonarr-pull
|
||||
- lidarr-pull
|
||||
- readarr-pull
|
||||
- v3
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: stale
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
|
||||
2
.github/support.yml
vendored
2
.github/support.yml
vendored
@@ -1,7 +1,7 @@
|
||||
# Configuration for support-requests - https://github.com/dessant/support-requests
|
||||
|
||||
# Label used to mark issues as support requests
|
||||
supportLabel: support
|
||||
supportLabel: 'Type: Support'
|
||||
# Comment to post on issues marked as support requests. Add a link
|
||||
# to a support page, or set to `false` to disable
|
||||
supportComment: >
|
||||
|
||||
@@ -11,17 +11,17 @@ Setup guides, FAQ, the more information we have on the [wiki](https://wiki.serva
|
||||
- Visual Studio 2019 or higher (https://www.visualstudio.com/vs/). The community version is free and works (https://www.visualstudio.com/downloads/).
|
||||
- HTML/Javascript editor of choice (VS Code/Sublime Text/Webstorm/Atom/etc)
|
||||
- [Git](https://git-scm.com/downloads)
|
||||
- [NodeJS](https://nodejs.org/en/download/) (Node 10.X.X or higher)
|
||||
- [NodeJS](https://nodejs.org/en/download/) (Node 12.X.X or higher)
|
||||
- [Yarn](https://yarnpkg.com/)
|
||||
- .NET Core 3.1.
|
||||
- .NET Core 5.0.
|
||||
|
||||
### Getting started ###
|
||||
|
||||
1. Fork Radarr
|
||||
2. Clone the repository into your development machine. [*info*](https://help.github.com/articles/working-with-repositories)
|
||||
2. Clone the repository into your development machine. [*info*](https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository-from-github)
|
||||
3. Install the required Node Packages `yarn install`
|
||||
4. Start gulp to monitor your dev environment for any changes that need post processing using `yarn start` command.
|
||||
5. Build the project in Visual Studio, Setting startup project to `Radarr.Console` and framework to `netcoreapp31`
|
||||
5. Build the project in Visual Studio, Setting startup project to `Radarr.Console` and framework to `net5.0`
|
||||
6. Debug the project in Visual Studio
|
||||
7. Open http://localhost:7878
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ Radarr is a movie collection manager for Usenet and BitTorrent users. It can mon
|
||||
## Support
|
||||
Note: GitHub Issues are for Bugs and Feature Requests Only
|
||||
|
||||
[](https://discord.gg/r5wJPt9)
|
||||
[](https://radarr.video/discord)
|
||||
[](https://www.reddit.com/r/Radarr)
|
||||
[](https://github.com/Radarr/Radarr/issues)
|
||||
[](https://wiki.servarr.com/Radarr)
|
||||
|
||||
@@ -7,13 +7,13 @@ variables:
|
||||
outputFolder: './_output'
|
||||
artifactsFolder: './_artifacts'
|
||||
testsFolder: './_tests'
|
||||
majorVersion: '3.0.2'
|
||||
majorVersion: '3.1.0'
|
||||
minorVersion: $[counter('minorVersion', 2000)]
|
||||
radarrVersion: '$(majorVersion).$(minorVersion)'
|
||||
buildName: '$(Build.SourceBranchName).$(radarrVersion)'
|
||||
sentryOrg: 'servarr'
|
||||
sentryUrl: 'https://sentry.servarr.com'
|
||||
dotnetVersion: '3.1.404'
|
||||
dotnetVersion: '5.0.202'
|
||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||
|
||||
trigger:
|
||||
@@ -59,18 +59,21 @@ stages:
|
||||
Linux:
|
||||
osName: 'Linux'
|
||||
imageName: 'ubuntu-18.04'
|
||||
enableAnalysis: 'true'
|
||||
Mac:
|
||||
osName: 'Mac'
|
||||
imageName: 'macos-10.14'
|
||||
enableAnalysis: 'false'
|
||||
Windows:
|
||||
osName: 'Windows'
|
||||
imageName: 'windows-2019'
|
||||
enableAnalysis: 'false'
|
||||
|
||||
pool:
|
||||
vmImage: $(imageName)
|
||||
variables:
|
||||
# Disable stylecop here - linting errors get caught by the analyze task
|
||||
EnableAnalyzers: 'false'
|
||||
EnableAnalyzers: $(enableAnalysis)
|
||||
steps:
|
||||
- checkout: self
|
||||
submodules: true
|
||||
@@ -79,7 +82,18 @@ stages:
|
||||
displayName: 'Install .net core'
|
||||
inputs:
|
||||
version: $(dotnetVersion)
|
||||
- bash: ./build.sh --backend
|
||||
- bash: |
|
||||
BUNDLEDVERSIONS=${AGENT_TOOLSDIRECTORY}/dotnet/sdk/${DOTNETVERSION}/Microsoft.NETCoreSdk.BundledVersions.props
|
||||
echo $BUNDLEDVERSIONS
|
||||
grep osx-x64 $BUNDLEDVERSIONS
|
||||
if grep -q freebsd-x64 $BUNDLEDVERSIONS; then
|
||||
echo "BSD already enabled"
|
||||
else
|
||||
echo "Enabling BSD support"
|
||||
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64/' $BUNDLEDVERSIONS
|
||||
fi
|
||||
displayName: Enable FreeBSD Support
|
||||
- bash: ./build.sh --backend --enable-bsd
|
||||
displayName: Build Radarr Backend
|
||||
- bash: |
|
||||
find ${OUTPUTFOLDER} -type f ! -path "*/publish/*" -exec rm -rf {} \;
|
||||
@@ -92,23 +106,27 @@ stages:
|
||||
artifact: '$(osName)Backend'
|
||||
displayName: Publish Backend
|
||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||
- publish: '$(testsFolder)/netcoreapp3.1/win-x64/publish'
|
||||
- publish: '$(testsFolder)/net5.0/win-x64/publish'
|
||||
artifact: WindowsCoreTests
|
||||
displayName: Publish Windows Test Package
|
||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||
- publish: '$(testsFolder)/net462/linux-x64/publish'
|
||||
- publish: '$(testsFolder)/net472/linux-x64/publish'
|
||||
artifact: LinuxTests
|
||||
displayName: Publish Linux Mono Test Package
|
||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||
- publish: '$(testsFolder)/netcoreapp3.1/linux-x64/publish'
|
||||
- publish: '$(testsFolder)/net5.0/linux-x64/publish'
|
||||
artifact: LinuxCoreTests
|
||||
displayName: Publish Linux Test Package
|
||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||
- publish: '$(testsFolder)/netcoreapp3.1/linux-musl-x64/publish'
|
||||
- publish: '$(testsFolder)/net5.0/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'
|
||||
- publish: '$(testsFolder)/net5.0/freebsd-x64/publish'
|
||||
artifact: FreebsdCoreTests
|
||||
displayName: Publish FreeBSD Test Package
|
||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||
- publish: '$(testsFolder)/net5.0/osx-x64/publish'
|
||||
artifact: MacCoreTests
|
||||
displayName: Publish MacOS Test Package
|
||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||
@@ -135,7 +153,7 @@ stages:
|
||||
- task: NodeTool@0
|
||||
displayName: Set Node.js version
|
||||
inputs:
|
||||
versionSpec: '10.x'
|
||||
versionSpec: '12.x'
|
||||
- checkout: self
|
||||
submodules: true
|
||||
fetchDepth: 1
|
||||
@@ -184,12 +202,12 @@ stages:
|
||||
- bash: ./build.sh --packages
|
||||
displayName: Create Packages
|
||||
- bash: |
|
||||
setup/inno/ISCC.exe setup/radarr.iss //DFramework=netcoreapp3.1 //DRuntime=win-x86
|
||||
cp setup/output/Radarr.*windows.netcoreapp3.1.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Radarr.${BUILDNAME}.windows-core-x86-installer.exe
|
||||
setup/inno/ISCC.exe setup/radarr.iss //DFramework=net5.0 //DRuntime=win-x86
|
||||
cp setup/output/Radarr.*windows.net5.0.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Radarr.${BUILDNAME}.windows-core-x86-installer.exe
|
||||
displayName: Create .NET Core Windows installer
|
||||
- bash: |
|
||||
setup/inno/ISCC.exe setup/radarr.iss //DFramework=netcoreapp3.1 //DRuntime=win-x64
|
||||
cp setup/output/Radarr.*windows.netcoreapp3.1.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Radarr.${BUILDNAME}.windows-core-x64-installer.exe
|
||||
setup/inno/ISCC.exe setup/radarr.iss //DFramework=net5.0 //DRuntime=win-x64
|
||||
cp setup/output/Radarr.*windows.net5.0.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Radarr.${BUILDNAME}.windows-core-x64-installer.exe
|
||||
displayName: Create .NET Core Windows installer
|
||||
- publish: $(Build.ArtifactStagingDirectory)
|
||||
artifact: 'WindowsInstaller'
|
||||
@@ -219,7 +237,7 @@ stages:
|
||||
artifactName: WindowsFrontend
|
||||
targetPath: _output
|
||||
displayName: Fetch Frontend
|
||||
- bash: ./build.sh --packages
|
||||
- bash: ./build.sh --packages --enable-bsd
|
||||
displayName: Create Packages
|
||||
- bash: |
|
||||
find . -name "Radarr" -exec chmod a+x {} \;
|
||||
@@ -231,21 +249,21 @@ stages:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).windows-core-x64.zip'
|
||||
archiveType: 'zip'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/win-x64/netcoreapp3.1
|
||||
rootFolderOrFile: $(artifactsFolder)/win-x64/net5.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create Windows x86 Core zip
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).windows-core-x86.zip'
|
||||
archiveType: 'zip'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/win-x86/netcoreapp3.1
|
||||
rootFolderOrFile: $(artifactsFolder)/win-x86/net5.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create MacOS Core app
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).osx-app-core-x64.zip'
|
||||
archiveType: 'zip'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/macos-app/netcoreapp3.1
|
||||
rootFolderOrFile: $(artifactsFolder)/macos-app/net5.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create MacOS Core tar
|
||||
inputs:
|
||||
@@ -253,7 +271,7 @@ stages:
|
||||
archiveType: 'tar'
|
||||
tarCompression: 'gz'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/macos/netcoreapp3.1
|
||||
rootFolderOrFile: $(artifactsFolder)/macos/net5.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create Linux Mono tar
|
||||
inputs:
|
||||
@@ -261,7 +279,7 @@ stages:
|
||||
archiveType: 'tar'
|
||||
tarCompression: 'gz'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-x64/net462
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-x64/net472
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create Linux Core tar
|
||||
inputs:
|
||||
@@ -269,7 +287,7 @@ stages:
|
||||
archiveType: 'tar'
|
||||
tarCompression: 'gz'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-x64/netcoreapp3.1
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-x64/net5.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create Linux Musl Core tar
|
||||
inputs:
|
||||
@@ -277,7 +295,7 @@ stages:
|
||||
archiveType: 'tar'
|
||||
tarCompression: 'gz'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-musl-x64/netcoreapp3.1
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-musl-x64/net5.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create ARM32 Linux Core tar
|
||||
inputs:
|
||||
@@ -285,7 +303,7 @@ stages:
|
||||
archiveType: 'tar'
|
||||
tarCompression: 'gz'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-arm/netcoreapp3.1
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-arm/net5.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create ARM64 Linux Core tar
|
||||
inputs:
|
||||
@@ -293,7 +311,7 @@ stages:
|
||||
archiveType: 'tar'
|
||||
tarCompression: 'gz'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-arm64/netcoreapp3.1
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-arm64/net5.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create ARM64 Linux Musl Core tar
|
||||
inputs:
|
||||
@@ -301,7 +319,15 @@ stages:
|
||||
archiveType: 'tar'
|
||||
tarCompression: 'gz'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-musl-arm64/netcoreapp3.1
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-musl-arm64/net5.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create FreeBSD Core Core tar
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).freebsd-core-x64.tar.gz'
|
||||
archiveType: 'tar'
|
||||
tarCompression: 'gz'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/freebsd-x64/net5.0
|
||||
- publish: $(Build.ArtifactStagingDirectory)
|
||||
artifact: 'Packages'
|
||||
displayName: Publish Packages
|
||||
@@ -355,24 +381,34 @@ stages:
|
||||
displayName: Unit Native
|
||||
dependsOn: Prepare
|
||||
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||
workspace:
|
||||
clean: all
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
MacCore:
|
||||
osName: 'Mac'
|
||||
testName: 'MacCore'
|
||||
poolName: 'Azure Pipelines'
|
||||
imageName: 'macos-10.14'
|
||||
WindowsCore:
|
||||
osName: 'Windows'
|
||||
testName: 'WindowsCore'
|
||||
poolName: 'Azure Pipelines'
|
||||
imageName: 'windows-2019'
|
||||
LinuxCore:
|
||||
osName: 'Linux'
|
||||
testName: 'LinuxCore'
|
||||
poolName: 'Azure Pipelines'
|
||||
imageName: 'ubuntu-18.04'
|
||||
pattern: 'Radarr.**.linux-core-x64.tar.gz'
|
||||
FreebsdCore:
|
||||
osName: 'Linux'
|
||||
testName: 'FreebsdCore'
|
||||
poolName: 'FreeBSD'
|
||||
imageName:
|
||||
|
||||
pool:
|
||||
name: $(poolName)
|
||||
vmImage: $(imageName)
|
||||
|
||||
steps:
|
||||
@@ -381,6 +417,7 @@ stages:
|
||||
displayName: 'Install .net core'
|
||||
inputs:
|
||||
version: $(dotnetVersion)
|
||||
condition: ne(variables['poolName'], 'FreeBSD')
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download Test Artifact
|
||||
inputs:
|
||||
@@ -393,7 +430,7 @@ stages:
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --allow-unauthenticated libmediainfo-dev libmediainfo0v5 mediainfo
|
||||
displayName: Install mediainfo
|
||||
condition: and(succeeded(), eq(variables['osName'], 'Linux'))
|
||||
condition: and(succeeded(), eq(variables['testName'], 'LinuxCore'))
|
||||
- powershell: Set-Service SCardSvr -StartupType Manual
|
||||
displayName: Enable Windows Test Service
|
||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||
@@ -431,19 +468,19 @@ stages:
|
||||
mono520:
|
||||
testName: 'Mono 5.20'
|
||||
artifactName: LinuxTests
|
||||
containerImage: servarr/testimages:mono-5.20
|
||||
containerImage: ghcr.io/servarr/testimages:mono-5.20
|
||||
mono610:
|
||||
testName: 'Mono 6.10'
|
||||
artifactName: LinuxTests
|
||||
containerImage: servarr/testimages:mono-6.10
|
||||
containerImage: ghcr.io/servarr/testimages:mono-6.10
|
||||
mono612:
|
||||
testName: 'Mono 6.12'
|
||||
artifactName: LinuxTests
|
||||
containerImage: servarr/testimages:mono-6.12
|
||||
containerImage: ghcr.io/servarr/testimages:mono-6.12
|
||||
alpine:
|
||||
testName: 'Musl Net Core'
|
||||
artifactName: LinuxMuslCoreTests
|
||||
containerImage: servarr/testimages:alpine
|
||||
containerImage: ghcr.io/servarr/testimages:alpine
|
||||
|
||||
pool:
|
||||
vmImage: 'ubuntu-18.04'
|
||||
@@ -508,17 +545,17 @@ stages:
|
||||
osName: 'Mac'
|
||||
testName: 'MacCore'
|
||||
imageName: 'macos-10.14'
|
||||
pattern: 'Radarr.**.osx-core-x64.tar.gz'
|
||||
pattern: 'Radarr.*.osx-core-x64.tar.gz'
|
||||
WindowsCore:
|
||||
osName: 'Windows'
|
||||
testName: 'WindowsCore'
|
||||
imageName: 'windows-2019'
|
||||
pattern: 'Radarr.**.windows-core-x64.zip'
|
||||
pattern: 'Radarr.*.windows-core-x64.zip'
|
||||
LinuxCore:
|
||||
osName: 'Linux'
|
||||
testName: 'LinuxCore'
|
||||
imageName: 'ubuntu-18.04'
|
||||
pattern: 'Radarr.**.linux-core-x64.tar.gz'
|
||||
pattern: 'Radarr.*.linux-core-x64.tar.gz'
|
||||
|
||||
pool:
|
||||
vmImage: $(imageName)
|
||||
@@ -571,6 +608,52 @@ stages:
|
||||
failTaskOnFailedTests: true
|
||||
displayName: Publish Test Results
|
||||
|
||||
- job: Integration_FreeBSD
|
||||
displayName: Integration Native FreeBSD
|
||||
dependsOn: Prepare
|
||||
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||
workspace:
|
||||
clean: all
|
||||
variables:
|
||||
pattern: 'Radarr.*.freebsd-core-x64.tar.gz'
|
||||
pool:
|
||||
name: 'FreeBSD'
|
||||
|
||||
steps:
|
||||
- checkout: none
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download Test Artifact
|
||||
inputs:
|
||||
buildType: 'current'
|
||||
artifactName: 'FreebsdCoreTests'
|
||||
targetPath: $(testsFolder)
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download Build Artifact
|
||||
inputs:
|
||||
buildType: 'current'
|
||||
artifactName: Packages
|
||||
itemPattern: '/$(pattern)'
|
||||
targetPath: $(Build.ArtifactStagingDirectory)
|
||||
- bash: |
|
||||
mkdir -p ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin
|
||||
tar xf ${BUILD_ARTIFACTSTAGINGDIRECTORY}/$(pattern) -C ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin
|
||||
displayName: Extract Package
|
||||
- bash: |
|
||||
mkdir -p ./bin/
|
||||
cp -r -v ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin/Radarr/. ./bin/
|
||||
displayName: Move Package Contents
|
||||
- bash: |
|
||||
chmod a+x ${TESTSFOLDER}/test.sh
|
||||
${TESTSFOLDER}/test.sh Linux Integration Test
|
||||
displayName: Run Integration Tests
|
||||
- task: PublishTestResults@2
|
||||
inputs:
|
||||
testResultsFormat: 'NUnit'
|
||||
testResultsFiles: '**/TestResult.xml'
|
||||
testRunTitle: 'FreeBSD Integration Tests'
|
||||
failTaskOnFailedTests: false
|
||||
displayName: Publish Test Results
|
||||
|
||||
- job: Integration_Docker
|
||||
displayName: Integration Docker
|
||||
dependsOn: Prepare
|
||||
@@ -580,23 +663,23 @@ stages:
|
||||
mono520:
|
||||
testName: 'Mono 5.20'
|
||||
artifactName: LinuxTests
|
||||
containerImage: servarr/testimages:mono-5.20
|
||||
pattern: 'Radarr.**.linux.tar.gz'
|
||||
containerImage: ghcr.io/servarr/testimages:mono-5.20
|
||||
pattern: 'Radarr.*.linux.tar.gz'
|
||||
mono610:
|
||||
testName: 'Mono 6.10'
|
||||
artifactName: LinuxTests
|
||||
containerImage: servarr/testimages:mono-6.10
|
||||
pattern: 'Radarr.**.linux.tar.gz'
|
||||
containerImage: ghcr.io/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'
|
||||
containerImage: ghcr.io/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'
|
||||
containerImage: ghcr.io/servarr/testimages:alpine
|
||||
pattern: 'Radarr.*.linux-musl-core-x64.tar.gz'
|
||||
pool:
|
||||
vmImage: 'ubuntu-18.04'
|
||||
|
||||
@@ -655,17 +738,17 @@ stages:
|
||||
Linux:
|
||||
osName: 'Linux'
|
||||
imageName: 'ubuntu-18.04'
|
||||
pattern: 'Radarr.**.linux-core-x64.tar.gz'
|
||||
pattern: 'Radarr.*.linux-core-x64.tar.gz'
|
||||
failBuild: true
|
||||
Mac:
|
||||
osName: 'Mac'
|
||||
imageName: 'macos-10.14'
|
||||
pattern: 'Radarr.**.osx-core-x64.tar.gz'
|
||||
pattern: 'Radarr.*.osx-core-x64.tar.gz'
|
||||
failBuild: true
|
||||
Windows:
|
||||
osName: 'Windows'
|
||||
imageName: 'windows-2019'
|
||||
pattern: 'Radarr.**.windows-core-x64.zip'
|
||||
pattern: 'Radarr.*.windows-core-x64.zip'
|
||||
failBuild: true
|
||||
|
||||
pool:
|
||||
@@ -757,7 +840,7 @@ stages:
|
||||
- task: NodeTool@0
|
||||
displayName: Set Node.js version
|
||||
inputs:
|
||||
versionSpec: '10.x'
|
||||
versionSpec: '12.x'
|
||||
- checkout: self
|
||||
submodules: true
|
||||
fetchDepth: 1
|
||||
@@ -803,6 +886,7 @@ stages:
|
||||
|
||||
variables:
|
||||
disable.coverage.autogenerate: 'true'
|
||||
EnableAnalyzers: 'false'
|
||||
|
||||
pool:
|
||||
vmImage: windows-2019
|
||||
@@ -831,8 +915,8 @@ stages:
|
||||
sonar.cs.opencover.reportsPaths=$(Build.SourcesDirectory)/CoverageResults/**/coverage.opencover.xml
|
||||
sonar.cs.nunit.reportsPaths=$(Build.SourcesDirectory)/TestResult.xml
|
||||
- bash: |
|
||||
./build.sh --backend -f netcoreapp3.1 -r win-x64
|
||||
TEST_DIR=_tests/netcoreapp3.1/win-x64/publish/ ./test.sh Windows Unit Coverage
|
||||
./build.sh --backend -f net5.0 -r win-x64
|
||||
TEST_DIR=_tests/net5.0/win-x64/publish/ ./test.sh Windows Unit Coverage
|
||||
displayName: Coverage Unit Tests
|
||||
- task: SonarCloudAnalyze@1
|
||||
condition: eq(variables['System.PullRequest.IsFork'], 'False')
|
||||
|
||||
70
build.sh
70
build.sh
@@ -1,4 +1,4 @@
|
||||
#! /bin/bash
|
||||
#! /usr/bin/env bash
|
||||
set -e
|
||||
|
||||
outputFolder='_output'
|
||||
@@ -25,6 +25,18 @@ UpdateVersionNumber()
|
||||
fi
|
||||
}
|
||||
|
||||
EnableBsdSupport()
|
||||
{
|
||||
#todo enable sdk with
|
||||
#SDK_PATH=$(dotnet --list-sdks | grep -P '5\.\d\.\d+' | head -1 | sed 's/\(5\.[0-9]*\.[0-9]*\).*\[\(.*\)\]/\2\/\1/g')
|
||||
# BUNDLED_VERSIONS="${SDK_PATH}/Microsoft.NETCoreSdk.BundledVersions.props"
|
||||
|
||||
if grep -qv freebsd-x64 src/Directory.Build.props; then
|
||||
sed -i'' -e "s^<RuntimeIdentifiers>\(.*\)</RuntimeIdentifiers>^<RuntimeIdentifiers>\1;freebsd-x64</RuntimeIdentifiers>^g" src/Directory.Build.props
|
||||
sed -i'' -e "s^<ExcludedRuntimeFrameworkPairs>\(.*\)</ExcludedRuntimeFrameworkPairs>^<ExcludedRuntimeFrameworkPairs>\1;freebsd-x64:net472</ExcludedRuntimeFrameworkPairs>^g" src/Directory.Build.props
|
||||
fi
|
||||
}
|
||||
|
||||
LintUI()
|
||||
{
|
||||
ProgressStart 'ESLint'
|
||||
@@ -118,7 +130,7 @@ PackageLinux()
|
||||
|
||||
echo "Adding Radarr.Mono to UpdatePackage"
|
||||
cp $folder/Radarr.Mono.* $folder/Radarr.Update
|
||||
if [ "$framework" = "netcoreapp3.1" ]; then
|
||||
if [ "$framework" = "net5.0" ]; then
|
||||
cp $folder/Mono.Posix.NETStandard.* $folder/Radarr.Update
|
||||
cp $folder/libMonoPosixHelper.* $folder/Radarr.Update
|
||||
fi
|
||||
@@ -136,7 +148,7 @@ PackageMacOS()
|
||||
|
||||
PackageFiles "$folder" "$framework" "osx-x64"
|
||||
|
||||
if [ "$framework" = "net462" ]; then
|
||||
if [ "$framework" = "net472" ]; then
|
||||
echo "Adding Startup script"
|
||||
cp macOS/Radarr $folder
|
||||
fi
|
||||
@@ -150,7 +162,7 @@ PackageMacOS()
|
||||
|
||||
echo "Adding Radarr.Mono to UpdatePackage"
|
||||
cp $folder/Radarr.Mono.* $folder/Radarr.Update
|
||||
if [ "$framework" = "netcoreapp3.1" ]; then
|
||||
if [ "$framework" = "net5.0" ]; then
|
||||
cp $folder/Mono.Posix.NETStandard.* $folder/Radarr.Update
|
||||
cp $folder/libMonoPosixHelper.* $folder/Radarr.Update
|
||||
fi
|
||||
@@ -190,6 +202,7 @@ PackageWindows()
|
||||
local folder=$artifactsFolder/$runtime/$framework/Radarr
|
||||
|
||||
PackageFiles "$folder" "$framework" "$runtime"
|
||||
cp -r $outputFolder/$framework-windows/$runtime/publish/* $folder
|
||||
|
||||
echo "Removing Radarr.Mono"
|
||||
rm -f $folder/Radarr.Mono.*
|
||||
@@ -211,7 +224,7 @@ Package()
|
||||
IFS='-' read -ra SPLIT <<< "$runtime"
|
||||
|
||||
case "${SPLIT[0]}" in
|
||||
linux)
|
||||
linux|freebsd*)
|
||||
PackageLinux "$framework" "$runtime"
|
||||
;;
|
||||
win)
|
||||
@@ -256,6 +269,7 @@ if [ $# -eq 0 ]; then
|
||||
FRONTEND=YES
|
||||
PACKAGES=YES
|
||||
LINT=YES
|
||||
ENABLE_BSD=NO
|
||||
fi
|
||||
|
||||
while [[ $# -gt 0 ]]
|
||||
@@ -267,6 +281,10 @@ case $key in
|
||||
BACKEND=YES
|
||||
shift # past argument
|
||||
;;
|
||||
--enable-bsd)
|
||||
ENABLE_BSD=YES
|
||||
shift # past argument
|
||||
;;
|
||||
-r|--runtime)
|
||||
RID="$2"
|
||||
shift # past argument
|
||||
@@ -307,15 +325,23 @@ set -- "${POSITIONAL[@]}" # restore positional parameters
|
||||
if [ "$BACKEND" = "YES" ];
|
||||
then
|
||||
UpdateVersionNumber
|
||||
if [ "$ENABLE_BSD" = "YES" ];
|
||||
then
|
||||
EnableBsdSupport
|
||||
fi
|
||||
Build
|
||||
if [[ -z "$RID" || -z "$FRAMEWORK" ]];
|
||||
then
|
||||
PackageTests "netcoreapp3.1" "win-x64"
|
||||
PackageTests "netcoreapp3.1" "win-x86"
|
||||
PackageTests "netcoreapp3.1" "linux-x64"
|
||||
PackageTests "netcoreapp3.1" "linux-musl-x64"
|
||||
PackageTests "netcoreapp3.1" "osx-x64"
|
||||
PackageTests "net462" "linux-x64"
|
||||
PackageTests "net5.0" "win-x64"
|
||||
PackageTests "net5.0" "win-x86"
|
||||
PackageTests "net5.0" "linux-x64"
|
||||
PackageTests "net5.0" "linux-musl-x64"
|
||||
PackageTests "net5.0" "osx-x64"
|
||||
PackageTests "net472" "linux-x64"
|
||||
if [ "$ENABLE_BSD" = "YES" ];
|
||||
then
|
||||
PackageTests "net5.0" "freebsd-x64"
|
||||
fi
|
||||
else
|
||||
PackageTests "$FRAMEWORK" "$RID"
|
||||
fi
|
||||
@@ -343,15 +369,19 @@ then
|
||||
|
||||
if [[ -z "$RID" || -z "$FRAMEWORK" ]];
|
||||
then
|
||||
Package "netcoreapp3.1" "win-x64"
|
||||
Package "netcoreapp3.1" "win-x86"
|
||||
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"
|
||||
Package "net5.0" "win-x64"
|
||||
Package "net5.0" "win-x86"
|
||||
Package "net5.0" "linux-x64"
|
||||
Package "net5.0" "linux-musl-x64"
|
||||
Package "net5.0" "linux-arm64"
|
||||
Package "net5.0" "linux-musl-arm64"
|
||||
Package "net5.0" "linux-arm"
|
||||
Package "net5.0" "osx-x64"
|
||||
Package "net472" "linux-x64"
|
||||
if [ "$ENABLE_BSD" = "YES" ];
|
||||
then
|
||||
Package "net5.0" "freebsd-x64"
|
||||
fi
|
||||
else
|
||||
Package "$FRAMEWORK" "$RID"
|
||||
fi
|
||||
|
||||
@@ -54,6 +54,10 @@ HtmlWebpackPlugin.prototype.injectAssetsIntoHtml = function(html, assets, assetT
|
||||
};
|
||||
|
||||
const plugins = [
|
||||
new webpack.IgnorePlugin({
|
||||
resourceRegExp: /(fetch-cookie|node-fetch|tough-cookie)/
|
||||
}),
|
||||
|
||||
new OptimizeCssAssetsPlugin({}),
|
||||
|
||||
new webpack.DefinePlugin({
|
||||
|
||||
@@ -32,6 +32,8 @@ class Queue extends Component {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this._shouldBlockRefresh = false;
|
||||
|
||||
this.state = {
|
||||
allSelected: false,
|
||||
allUnselected: false,
|
||||
@@ -43,6 +45,14 @@ class Queue extends Component {
|
||||
};
|
||||
}
|
||||
|
||||
shouldComponentUpdate() {
|
||||
if (this._shouldBlockRefresh) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const {
|
||||
items,
|
||||
@@ -85,6 +95,10 @@ class Queue extends Component {
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onQueueRowModalOpenOrClose = (isOpen) => {
|
||||
this._shouldBlockRefresh = isOpen;
|
||||
}
|
||||
|
||||
onSelectAllChange = ({ value }) => {
|
||||
this.setState(selectAll(this.state.selectedState, value));
|
||||
}
|
||||
@@ -100,15 +114,19 @@ class Queue extends Component {
|
||||
}
|
||||
|
||||
onRemoveSelectedPress = () => {
|
||||
this.setState({ isConfirmRemoveModalOpen: true });
|
||||
this.setState({ isConfirmRemoveModalOpen: true }, () => {
|
||||
this._shouldBlockRefresh = true;
|
||||
});
|
||||
}
|
||||
|
||||
onRemoveSelectedConfirmed = (payload) => {
|
||||
this._shouldBlockRefresh = false;
|
||||
this.props.onRemoveSelectedPress({ ids: this.getSelectedIds(), ...payload });
|
||||
this.setState({ isConfirmRemoveModalOpen: false });
|
||||
}
|
||||
|
||||
onConfirmRemoveModalClose = () => {
|
||||
this._shouldBlockRefresh = false;
|
||||
this.setState({ isConfirmRemoveModalOpen: false });
|
||||
}
|
||||
|
||||
@@ -208,7 +226,7 @@ class Queue extends Component {
|
||||
}
|
||||
|
||||
{
|
||||
isPopulated && !hasError && !items.length &&
|
||||
isAllPopulated && !hasError && !items.length &&
|
||||
<div>
|
||||
{translate('QueueIsEmpty')}
|
||||
</div>
|
||||
@@ -237,6 +255,7 @@ class Queue extends Component {
|
||||
columns={columns}
|
||||
{...item}
|
||||
onSelectedChange={this.onSelectedChange}
|
||||
onQueueRowModalOpenOrClose={this.onQueueRowModalOpenOrClose}
|
||||
/>
|
||||
);
|
||||
})
|
||||
|
||||
@@ -43,19 +43,32 @@ class QueueRow extends Component {
|
||||
}
|
||||
|
||||
onRemoveQueueItemModalConfirmed = (blacklist) => {
|
||||
this.props.onRemoveQueueItemPress(blacklist);
|
||||
const {
|
||||
onRemoveQueueItemPress,
|
||||
onQueueRowModalOpenOrClose
|
||||
} = this.props;
|
||||
|
||||
onQueueRowModalOpenOrClose(false);
|
||||
onRemoveQueueItemPress(blacklist);
|
||||
|
||||
this.setState({ isRemoveQueueItemModalOpen: false });
|
||||
}
|
||||
|
||||
onRemoveQueueItemModalClose = () => {
|
||||
this.props.onQueueRowModalOpenOrClose(false);
|
||||
|
||||
this.setState({ isRemoveQueueItemModalOpen: false });
|
||||
}
|
||||
|
||||
onInteractiveImportPress = () => {
|
||||
this.props.onQueueRowModalOpenOrClose(true);
|
||||
|
||||
this.setState({ isInteractiveImportModalOpen: true });
|
||||
}
|
||||
|
||||
onInteractiveImportModalClose = () => {
|
||||
this.props.onQueueRowModalOpenOrClose(false);
|
||||
|
||||
this.setState({ isInteractiveImportModalOpen: false });
|
||||
}
|
||||
|
||||
@@ -359,7 +372,8 @@ QueueRow.propTypes = {
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
onSelectedChange: PropTypes.func.isRequired,
|
||||
onGrabPress: PropTypes.func.isRequired,
|
||||
onRemoveQueueItemPress: PropTypes.func.isRequired
|
||||
onRemoveQueueItemPress: PropTypes.func.isRequired,
|
||||
onQueueRowModalOpenOrClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
QueueRow.defaultProps = {
|
||||
|
||||
@@ -81,7 +81,8 @@ class AddNewMovie extends Component {
|
||||
const {
|
||||
error,
|
||||
items,
|
||||
hasExistingMovies
|
||||
hasExistingMovies,
|
||||
colorImpairedMode
|
||||
} = this.props;
|
||||
|
||||
const term = this.state.term;
|
||||
@@ -141,6 +142,7 @@ class AddNewMovie extends Component {
|
||||
return (
|
||||
<AddNewMovieSearchResultConnector
|
||||
key={item.tmdbId}
|
||||
colorImpairedMode={colorImpairedMode}
|
||||
{...item}
|
||||
/>
|
||||
);
|
||||
@@ -213,7 +215,8 @@ AddNewMovie.propTypes = {
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
hasExistingMovies: PropTypes.bool.isRequired,
|
||||
onMovieLookupChange: PropTypes.func.isRequired,
|
||||
onClearMovieLookup: PropTypes.func.isRequired
|
||||
onClearMovieLookup: PropTypes.func.isRequired,
|
||||
colorImpairedMode: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
export default AddNewMovie;
|
||||
|
||||
@@ -3,8 +3,10 @@ import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { clearAddMovie, lookupMovie } from 'Store/Actions/addMovieActions';
|
||||
import { clearQueueDetails, fetchQueueDetails } from 'Store/Actions/queueActions';
|
||||
import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
|
||||
import { fetchImportExclusions } from 'Store/Actions/Settings/importExclusions';
|
||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||
import parseUrl from 'Utilities/String/parseUrl';
|
||||
import AddNewMovie from './AddNewMovie';
|
||||
|
||||
@@ -13,13 +15,15 @@ function createMapStateToProps() {
|
||||
(state) => state.addMovie,
|
||||
(state) => state.movies.items.length,
|
||||
(state) => state.router.location,
|
||||
(addMovie, existingMoviesCount, location) => {
|
||||
createUISettingsSelector(),
|
||||
(addMovie, existingMoviesCount, location, uiSettings) => {
|
||||
const { params } = parseUrl(location.search);
|
||||
|
||||
return {
|
||||
...addMovie,
|
||||
term: params.term,
|
||||
hasExistingMovies: existingMoviesCount > 0
|
||||
hasExistingMovies: existingMoviesCount > 0,
|
||||
colorImpairedMode: uiSettings.enableColorImpairedMode
|
||||
};
|
||||
}
|
||||
);
|
||||
@@ -29,7 +33,9 @@ const mapDispatchToProps = {
|
||||
lookupMovie,
|
||||
clearAddMovie,
|
||||
fetchRootFolders,
|
||||
fetchImportExclusions
|
||||
fetchImportExclusions,
|
||||
fetchQueueDetails,
|
||||
clearQueueDetails
|
||||
};
|
||||
|
||||
class AddNewMovieConnector extends Component {
|
||||
@@ -46,6 +52,7 @@ class AddNewMovieConnector extends Component {
|
||||
componentDidMount() {
|
||||
this.props.fetchRootFolders();
|
||||
this.props.fetchImportExclusions();
|
||||
this.props.fetchQueueDetails();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
@@ -54,6 +61,7 @@ class AddNewMovieConnector extends Component {
|
||||
}
|
||||
|
||||
this.props.clearAddMovie();
|
||||
this.props.clearQueueDetails();
|
||||
}
|
||||
|
||||
//
|
||||
@@ -102,7 +110,9 @@ AddNewMovieConnector.propTypes = {
|
||||
lookupMovie: PropTypes.func.isRequired,
|
||||
clearAddMovie: PropTypes.func.isRequired,
|
||||
fetchRootFolders: PropTypes.func.isRequired,
|
||||
fetchImportExclusions: PropTypes.func.isRequired
|
||||
fetchImportExclusions: PropTypes.func.isRequired,
|
||||
fetchQueueDetails: PropTypes.func.isRequired,
|
||||
clearQueueDetails: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(AddNewMovieConnector);
|
||||
|
||||
@@ -27,9 +27,11 @@
|
||||
}
|
||||
|
||||
.poster {
|
||||
flex: 0 0 170px;
|
||||
position: relative;
|
||||
display: block;
|
||||
margin-right: 20px;
|
||||
height: 250px;
|
||||
background-color: $defaultColor;
|
||||
}
|
||||
|
||||
.content {
|
||||
@@ -86,9 +88,25 @@
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.posterContainer {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.statusContainer {
|
||||
margin-right: 22px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $breakpointMedium) {
|
||||
.titleRow {
|
||||
justify-content: space-between;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.certification {
|
||||
margin-right: 5px;
|
||||
padding: 0 5px;
|
||||
border: 1px solid;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,10 @@ 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 MovieStatusLabel from 'Movie/Details/MovieStatusLabel';
|
||||
import MovieIndexProgressBar from 'Movie/Index/ProgressBar/MovieIndexProgressBar';
|
||||
import MoviePoster from 'Movie/MoviePoster';
|
||||
import formatRuntime from 'Utilities/Date/formatRuntime';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import AddNewMovieModal from './AddNewMovieModal';
|
||||
import styles from './AddNewMovieSearchResult.css';
|
||||
@@ -65,7 +68,17 @@ class AddNewMovieSearchResult extends Component {
|
||||
images,
|
||||
isExistingMovie,
|
||||
isExclusionMovie,
|
||||
isSmallScreen
|
||||
isSmallScreen,
|
||||
colorImpairedMode,
|
||||
id,
|
||||
monitored,
|
||||
hasFile,
|
||||
isAvailable,
|
||||
queueStatus,
|
||||
queueState,
|
||||
runtime,
|
||||
movieRuntimeFormat,
|
||||
certification
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
@@ -85,12 +98,30 @@ class AddNewMovieSearchResult extends Component {
|
||||
{
|
||||
isSmallScreen ?
|
||||
null :
|
||||
<MoviePoster
|
||||
className={styles.poster}
|
||||
images={images}
|
||||
size={250}
|
||||
overflow={true}
|
||||
/>
|
||||
<div>
|
||||
<div className={styles.posterContainer}>
|
||||
<MoviePoster
|
||||
className={styles.poster}
|
||||
images={images}
|
||||
size={250}
|
||||
overflow={true}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{
|
||||
isExistingMovie &&
|
||||
<MovieIndexProgressBar
|
||||
monitored={monitored}
|
||||
hasFile={hasFile}
|
||||
status={status}
|
||||
posterWidth={167}
|
||||
detailedProgressBar={true}
|
||||
queueStatus={queueStatus}
|
||||
queueState={queueState}
|
||||
isAvailable={isAvailable}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<div className={styles.content}>
|
||||
@@ -133,6 +164,22 @@ class AddNewMovieSearchResult extends Component {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{
|
||||
!!certification &&
|
||||
<span className={styles.certification}>
|
||||
{certification}
|
||||
</span>
|
||||
}
|
||||
|
||||
{
|
||||
!!runtime &&
|
||||
<span>
|
||||
{formatRuntime(runtime, movieRuntimeFormat)}
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label size={sizes.LARGE}>
|
||||
<HeartRating
|
||||
@@ -176,13 +223,15 @@ class AddNewMovieSearchResult extends Component {
|
||||
/>
|
||||
|
||||
{
|
||||
status === 'ended' &&
|
||||
<Label
|
||||
kind={kinds.DANGER}
|
||||
size={sizes.LARGE}
|
||||
>
|
||||
Ended
|
||||
</Label>
|
||||
isExistingMovie && isSmallScreen &&
|
||||
<MovieStatusLabel
|
||||
hasMovieFiles={hasFile}
|
||||
monitored={monitored}
|
||||
isAvailable={isAvailable}
|
||||
id={id}
|
||||
useLabel={true}
|
||||
colorImpairedMode={colorImpairedMode}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -222,7 +271,18 @@ AddNewMovieSearchResult.propTypes = {
|
||||
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
isExistingMovie: PropTypes.bool.isRequired,
|
||||
isExclusionMovie: PropTypes.bool.isRequired,
|
||||
isSmallScreen: PropTypes.bool.isRequired
|
||||
isSmallScreen: PropTypes.bool.isRequired,
|
||||
id: PropTypes.number,
|
||||
queueItems: PropTypes.arrayOf(PropTypes.object),
|
||||
monitored: PropTypes.bool.isRequired,
|
||||
hasFile: PropTypes.bool.isRequired,
|
||||
isAvailable: PropTypes.bool.isRequired,
|
||||
colorImpairedMode: PropTypes.bool,
|
||||
queueStatus: PropTypes.string,
|
||||
queueState: PropTypes.string,
|
||||
runtime: PropTypes.number.isRequired,
|
||||
movieRuntimeFormat: PropTypes.string.isRequired,
|
||||
certification: PropTypes.string
|
||||
};
|
||||
|
||||
export default AddNewMovieSearchResult;
|
||||
|
||||
@@ -10,11 +10,17 @@ function createMapStateToProps() {
|
||||
createExistingMovieSelector(),
|
||||
createExclusionMovieSelector(),
|
||||
createDimensionsSelector(),
|
||||
(isExistingMovie, isExclusionMovie, dimensions) => {
|
||||
(state) => state.queue.details.items,
|
||||
(state, { internalId }) => internalId,
|
||||
(isExistingMovie, isExclusionMovie, dimensions, queueItems, internalId) => {
|
||||
const firstQueueItem = queueItems.find((q) => q.movieId === internalId && internalId > 0);
|
||||
|
||||
return {
|
||||
isExistingMovie,
|
||||
isExclusionMovie,
|
||||
isSmallScreen: dimensions.isSmallScreen
|
||||
isSmallScreen: dimensions.isSmallScreen,
|
||||
queueStatus: firstQueueItem ? firstQueueItem.status : null,
|
||||
queueState: firstQueueItem ? firstQueueItem.trackedDownloadState : null
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
@@ -11,9 +11,47 @@ import UpdateChanges from 'System/Updates/UpdateChanges';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './AppUpdatedModalContent.css';
|
||||
|
||||
function mergeUpdates(items, version, prevVersion) {
|
||||
let installedIndex = items.findIndex((u) => u.version === version);
|
||||
let installedPreviouslyIndex = items.findIndex((u) => u.version === prevVersion);
|
||||
|
||||
if (installedIndex === -1) {
|
||||
installedIndex = 0;
|
||||
}
|
||||
|
||||
if (installedPreviouslyIndex === -1) {
|
||||
installedPreviouslyIndex = items.length;
|
||||
} else if (installedPreviouslyIndex === installedIndex && items.length) {
|
||||
installedPreviouslyIndex += 1;
|
||||
}
|
||||
|
||||
const appliedUpdates = items.slice(installedIndex, installedPreviouslyIndex);
|
||||
|
||||
if (!appliedUpdates.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const appliedChanges = { new: [], fixed: [] };
|
||||
appliedUpdates.forEach((u) => {
|
||||
if (u.changes) {
|
||||
appliedChanges.new.push(... u.changes.new);
|
||||
appliedChanges.fixed.push(... u.changes.fixed);
|
||||
}
|
||||
});
|
||||
|
||||
const mergedUpdate = Object.assign({}, appliedUpdates[0], { changes: appliedChanges });
|
||||
|
||||
if (!appliedChanges.new.length && !appliedChanges.fixed.length) {
|
||||
mergedUpdate.changes = null;
|
||||
}
|
||||
|
||||
return mergedUpdate;
|
||||
}
|
||||
|
||||
function AppUpdatedModalContent(props) {
|
||||
const {
|
||||
version,
|
||||
prevVersion,
|
||||
isPopulated,
|
||||
error,
|
||||
items,
|
||||
@@ -21,7 +59,7 @@ function AppUpdatedModalContent(props) {
|
||||
onModalClose
|
||||
} = props;
|
||||
|
||||
const update = items[0];
|
||||
const update = mergeUpdates(items, version, prevVersion);
|
||||
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
@@ -89,6 +127,7 @@ function AppUpdatedModalContent(props) {
|
||||
|
||||
AppUpdatedModalContent.propTypes = {
|
||||
version: PropTypes.string.isRequired,
|
||||
prevVersion: PropTypes.string,
|
||||
isPopulated: PropTypes.bool.isRequired,
|
||||
error: PropTypes.object,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
|
||||
@@ -8,8 +8,9 @@ import AppUpdatedModalContent from './AppUpdatedModalContent';
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.app.version,
|
||||
(state) => state.app.prevVersion,
|
||||
(state) => state.system.updates,
|
||||
(version, updates) => {
|
||||
(version, prevVersion, updates) => {
|
||||
const {
|
||||
isPopulated,
|
||||
error,
|
||||
@@ -18,6 +19,7 @@ function createMapStateToProps() {
|
||||
|
||||
return {
|
||||
version,
|
||||
prevVersion,
|
||||
isPopulated,
|
||||
error,
|
||||
items
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
}
|
||||
|
||||
.time {
|
||||
flex: 0 0 120px;
|
||||
flex: 0 0 125px;
|
||||
margin-right: 10px;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ function createMissingMovieIdsSelector() {
|
||||
const inCinemas = movie.inCinemas;
|
||||
|
||||
if (
|
||||
!movie.movieFileId &&
|
||||
!movie.hasFile &&
|
||||
moment(inCinemas).isAfter(start) &&
|
||||
moment(inCinemas).isBefore(end) &&
|
||||
isBefore(movie.inCinemas) &&
|
||||
|
||||
@@ -22,7 +22,7 @@ function getUrls(state) {
|
||||
tags
|
||||
} = state;
|
||||
|
||||
let icalUrl = `${window.location.host}${window.Radarr.urlBase}/feed/calendar/Radarr.ics?`;
|
||||
let icalUrl = `${window.location.host}${window.Radarr.urlBase}/feed/v3/calendar/Radarr.ics?`;
|
||||
|
||||
if (unmonitored) {
|
||||
icalUrl += 'unmonitored=true&';
|
||||
|
||||
@@ -85,3 +85,21 @@
|
||||
display: inline-block;
|
||||
margin: 5px -5px 5px 0;
|
||||
}
|
||||
|
||||
.mobileCloseButtonContainer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
height: 40px;
|
||||
border-bottom: 1px solid $borderColor;
|
||||
}
|
||||
|
||||
.mobileCloseButton {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
text-align: center;
|
||||
line-height: 40px;
|
||||
|
||||
&:hover {
|
||||
color: $modalCloseButtonHoverColor;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -518,6 +518,18 @@ class EnhancedSelectInput extends Component {
|
||||
scrollDirection={scrollDirections.NONE}
|
||||
>
|
||||
<Scroller className={styles.optionsModalScroller}>
|
||||
<div className={styles.mobileCloseButtonContainer}>
|
||||
<Link
|
||||
className={styles.mobileCloseButton}
|
||||
onPress={this.onOptionsModalClose}
|
||||
>
|
||||
<Icon
|
||||
name={icons.CLOSE}
|
||||
size={18}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{
|
||||
values.map((v, index) => {
|
||||
const hasParent = v.parentKey !== undefined;
|
||||
|
||||
@@ -54,4 +54,8 @@
|
||||
&:last-child {
|
||||
border: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: unset;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,9 @@ class EnhancedSelectInputOption extends Component {
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onPress = () => {
|
||||
onPress = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const {
|
||||
id,
|
||||
onSelect
|
||||
|
||||
@@ -10,13 +10,16 @@ const ADD_NEW_KEY = 'addNew';
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.rootFolders,
|
||||
(state, { value }) => value,
|
||||
(state, { includeMissingValue }) => includeMissingValue,
|
||||
(state, { includeNoChange }) => includeNoChange,
|
||||
(rootFolders, includeNoChange) => {
|
||||
(rootFolders, value, includeMissingValue, includeNoChange) => {
|
||||
const values = rootFolders.items.map((rootFolder) => {
|
||||
return {
|
||||
key: rootFolder.path,
|
||||
value: rootFolder.path,
|
||||
freeSpace: rootFolder.freeSpace
|
||||
freeSpace: rootFolder.freeSpace,
|
||||
isMissing: false
|
||||
};
|
||||
});
|
||||
|
||||
@@ -24,7 +27,8 @@ function createMapStateToProps() {
|
||||
values.unshift({
|
||||
key: 'noChange',
|
||||
value: 'No Change',
|
||||
isDisabled: true
|
||||
isDisabled: true,
|
||||
isMissing: false
|
||||
});
|
||||
}
|
||||
|
||||
@@ -37,6 +41,15 @@ function createMapStateToProps() {
|
||||
});
|
||||
}
|
||||
|
||||
if (includeMissingValue && !values.find((v) => v.key === value)) {
|
||||
values.push({
|
||||
key: value,
|
||||
value,
|
||||
isMissing: true,
|
||||
isDisabled: true
|
||||
});
|
||||
}
|
||||
|
||||
values.push({
|
||||
key: ADD_NEW_KEY,
|
||||
value: 'Add a new path'
|
||||
@@ -151,7 +164,8 @@ RootFolderSelectInputConnector.propTypes = {
|
||||
};
|
||||
|
||||
RootFolderSelectInputConnector.defaultProps = {
|
||||
includeNoChange: false
|
||||
includeNoChange: false,
|
||||
value: ''
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, createMapDispatchToProps)(RootFolderSelectInputConnector);
|
||||
|
||||
@@ -29,3 +29,9 @@
|
||||
color: $darkGray;
|
||||
font-size: $smallFontSize;
|
||||
}
|
||||
|
||||
.isMissing {
|
||||
margin-left: 15px;
|
||||
color: $dangerColor;
|
||||
font-size: $smallFontSize;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ function RootFolderSelectInputOption(props) {
|
||||
id,
|
||||
value,
|
||||
freeSpace,
|
||||
isMissing,
|
||||
movieFolder,
|
||||
isMobile,
|
||||
isWindows,
|
||||
@@ -43,11 +44,20 @@ function RootFolderSelectInputOption(props) {
|
||||
</div>
|
||||
|
||||
{
|
||||
freeSpace != null &&
|
||||
freeSpace == null ?
|
||||
null :
|
||||
<div className={styles.freeSpace}>
|
||||
{formatBytes(freeSpace)} Free
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
isMissing ?
|
||||
<div className={styles.isMissing}>
|
||||
Missing
|
||||
</div> :
|
||||
null
|
||||
}
|
||||
</div>
|
||||
</EnhancedSelectInputOption>
|
||||
);
|
||||
@@ -58,6 +68,7 @@ RootFolderSelectInputOption.propTypes = {
|
||||
value: PropTypes.string.isRequired,
|
||||
freeSpace: PropTypes.number,
|
||||
movieFolder: PropTypes.string,
|
||||
isMissing: PropTypes.boolean,
|
||||
isMobile: PropTypes.bool.isRequired,
|
||||
isWindows: PropTypes.bool
|
||||
};
|
||||
|
||||
@@ -19,6 +19,10 @@
|
||||
&.outline {
|
||||
color: $dangerColor;
|
||||
}
|
||||
|
||||
&:global(.colorImpaired) {
|
||||
background: repeating-linear-gradient(90deg, color($dangerColor shade(5%)), color($dangerColor shade(5%)) 5px, color($dangerColor shade(15%)) 5px, color($dangerColor shade(15%)) 10px);
|
||||
}
|
||||
}
|
||||
|
||||
.default {
|
||||
@@ -85,6 +89,10 @@
|
||||
&.outline {
|
||||
color: $warningColor;
|
||||
}
|
||||
|
||||
&:global(.colorImpaired) {
|
||||
background: repeating-linear-gradient(45deg, $warningColor, $warningColor 5px, color($warningColor tint(15%)) 5px, color($warningColor tint(15%)) 10px);
|
||||
}
|
||||
}
|
||||
|
||||
.queue {
|
||||
|
||||
@@ -11,6 +11,7 @@ function Label(props) {
|
||||
size,
|
||||
outline,
|
||||
children,
|
||||
colorImpairedMode,
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
@@ -20,7 +21,8 @@ function Label(props) {
|
||||
className,
|
||||
styles[kind],
|
||||
styles[size],
|
||||
outline && styles.outline
|
||||
outline && styles.outline,
|
||||
colorImpairedMode && 'colorImpaired'
|
||||
)}
|
||||
{...otherProps}
|
||||
>
|
||||
@@ -34,14 +36,16 @@ Label.propTypes = {
|
||||
kind: PropTypes.oneOf(kinds.all).isRequired,
|
||||
size: PropTypes.oneOf(sizes.all).isRequired,
|
||||
outline: PropTypes.bool.isRequired,
|
||||
children: PropTypes.node.isRequired
|
||||
children: PropTypes.node.isRequired,
|
||||
colorImpairedMode: PropTypes.bool
|
||||
};
|
||||
|
||||
Label.defaultProps = {
|
||||
className: styles.label,
|
||||
kind: kinds.DEFAULT,
|
||||
size: sizes.SMALL,
|
||||
outline: false
|
||||
outline: false,
|
||||
colorImpairedMode: false
|
||||
};
|
||||
|
||||
export default Label;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.loadingMessage {
|
||||
margin: 50px 10px 0;
|
||||
margin: 10px 10px 0;
|
||||
text-align: center;
|
||||
font-weight: 300;
|
||||
font-size: 36px;
|
||||
|
||||
@@ -12,7 +12,7 @@ const messages = [
|
||||
'Hum something loud while others stare',
|
||||
'Loading humorous message... Please Wait',
|
||||
'I could\'ve been faster in Python',
|
||||
'Don\'t forget to rewind your tracks',
|
||||
'Don\'t forget to rewind your movies',
|
||||
'Congratulations! you are the 1000th visitor.',
|
||||
'HELP! I\'m being held hostage and forced to write these stupid lines!',
|
||||
'RE-calibrating the internet...',
|
||||
|
||||
@@ -30,10 +30,10 @@ function ConfirmModal(props) {
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
bindShortcut('enter', onConfirm);
|
||||
} else {
|
||||
unbindShortcut('enter', onConfirm);
|
||||
|
||||
return () => unbindShortcut('enter', onConfirm);
|
||||
}
|
||||
}, [onConfirm]);
|
||||
}, [isOpen, onConfirm]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
.page {
|
||||
composes: page from '~./Page.css';
|
||||
}
|
||||
|
||||
.logoFull {
|
||||
margin-top: 50px;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
width: 120px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -10,6 +10,7 @@ import SpinnerIcon from 'Components/SpinnerIcon';
|
||||
import { forEach } from 'Helpers/elementChildren';
|
||||
import { align, icons } from 'Helpers/Props';
|
||||
import dimensions from 'Styles/Variables/dimensions';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './PageToolbarSection.css';
|
||||
|
||||
const BUTTON_WIDTH = parseInt(dimensions.toolbarButtonWidth);
|
||||
@@ -161,7 +162,7 @@ class PageToolbarSection extends Component {
|
||||
<ToolbarMenuButton
|
||||
className={styles.overflowMenuButton}
|
||||
iconName={icons.OVERFLOW}
|
||||
text="More"
|
||||
text={translate('More')}
|
||||
/>
|
||||
|
||||
<MenuContent>
|
||||
|
||||
@@ -77,8 +77,10 @@ function keyboardShortcuts(WrappedComponent) {
|
||||
}
|
||||
|
||||
unbindShortcut = (key) => {
|
||||
delete this._mousetrapBindings[key];
|
||||
this._mousetrap.unbind(key);
|
||||
if (this._mousetrap != null) {
|
||||
delete this._mousetrapBindings[key];
|
||||
this._mousetrap.unbind(key);
|
||||
}
|
||||
}
|
||||
|
||||
unbindAllShortcuts = () => {
|
||||
|
||||
BIN
frontend/src/Content/Images/Icons/logo-lidarr.png
Normal file
BIN
frontend/src/Content/Images/Icons/logo-lidarr.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
BIN
frontend/src/Content/Images/Icons/logo-radarr.png
Normal file
BIN
frontend/src/Content/Images/Icons/logo-radarr.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
BIN
frontend/src/Content/Images/Icons/logo-readarr.png
Normal file
BIN
frontend/src/Content/Images/Icons/logo-readarr.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
BIN
frontend/src/Content/Images/Icons/logo-sonarr.png
Normal file
BIN
frontend/src/Content/Images/Icons/logo-sonarr.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
@@ -272,7 +272,7 @@ class InteractiveImportModalContent extends Component {
|
||||
isPopulated && !!items.length && !isFetching && !isFetching &&
|
||||
<Table
|
||||
columns={columns}
|
||||
horizontalScroll={false}
|
||||
horizontalScroll={true}
|
||||
selectAll={true}
|
||||
allSelected={allSelected}
|
||||
allUnselected={allUnselected}
|
||||
|
||||
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { updateInteractiveImportItems } from 'Store/Actions/interactiveImportActions';
|
||||
import { reprocessInteractiveImportItems, updateInteractiveImportItems } from 'Store/Actions/interactiveImportActions';
|
||||
import { fetchLanguages } from 'Store/Actions/settingsActions';
|
||||
import SelectLanguageModalContent from './SelectLanguageModalContent';
|
||||
|
||||
@@ -33,6 +33,7 @@ function createMapStateToProps() {
|
||||
|
||||
const mapDispatchToProps = {
|
||||
dispatchFetchLanguages: fetchLanguages,
|
||||
dispatchReprocessInteractiveImportItems: reprocessInteractiveImportItems,
|
||||
dispatchUpdateInteractiveImportItems: updateInteractiveImportItems
|
||||
};
|
||||
|
||||
@@ -51,6 +52,12 @@ class SelectLanguageModalContentConnector extends Component {
|
||||
// Listeners
|
||||
|
||||
onLanguageSelect = ({ languageIds }) => {
|
||||
const {
|
||||
ids,
|
||||
dispatchUpdateInteractiveImportItems,
|
||||
dispatchReprocessInteractiveImportItems
|
||||
} = this.props;
|
||||
|
||||
const languages = [];
|
||||
|
||||
languageIds.forEach((languageId) => {
|
||||
@@ -62,11 +69,13 @@ class SelectLanguageModalContentConnector extends Component {
|
||||
}
|
||||
});
|
||||
|
||||
this.props.dispatchUpdateInteractiveImportItems({
|
||||
ids: this.props.ids,
|
||||
dispatchUpdateInteractiveImportItems({
|
||||
ids,
|
||||
languages
|
||||
});
|
||||
|
||||
dispatchReprocessInteractiveImportItems({ ids });
|
||||
|
||||
this.props.onModalClose(true);
|
||||
}
|
||||
|
||||
@@ -91,6 +100,7 @@ SelectLanguageModalContentConnector.propTypes = {
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
dispatchFetchLanguages: PropTypes.func.isRequired,
|
||||
dispatchUpdateInteractiveImportItems: PropTypes.func.isRequired,
|
||||
dispatchReprocessInteractiveImportItems: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { updateInteractiveImportItems } from 'Store/Actions/interactiveImportActions';
|
||||
import { reprocessInteractiveImportItems, updateInteractiveImportItems } from 'Store/Actions/interactiveImportActions';
|
||||
import { fetchQualityProfileSchema } from 'Store/Actions/settingsActions';
|
||||
import getQualities from 'Utilities/Quality/getQualities';
|
||||
import SelectQualityModalContent from './SelectQualityModalContent';
|
||||
@@ -31,6 +31,7 @@ function createMapStateToProps() {
|
||||
|
||||
const mapDispatchToProps = {
|
||||
dispatchFetchQualityProfileSchema: fetchQualityProfileSchema,
|
||||
dispatchReprocessInteractiveImportItems: reprocessInteractiveImportItems,
|
||||
dispatchUpdateInteractiveImportItems: updateInteractiveImportItems
|
||||
};
|
||||
|
||||
@@ -49,6 +50,12 @@ class SelectQualityModalContentConnector extends Component {
|
||||
// Listeners
|
||||
|
||||
onQualitySelect = ({ qualityId, proper, real }) => {
|
||||
const {
|
||||
ids,
|
||||
dispatchUpdateInteractiveImportItems,
|
||||
dispatchReprocessInteractiveImportItems
|
||||
} = this.props;
|
||||
|
||||
const quality = _.find(this.props.items,
|
||||
(item) => item.id === qualityId);
|
||||
|
||||
@@ -57,14 +64,16 @@ class SelectQualityModalContentConnector extends Component {
|
||||
real: real ? 1 : 0
|
||||
};
|
||||
|
||||
this.props.dispatchUpdateInteractiveImportItems({
|
||||
ids: this.props.ids,
|
||||
dispatchUpdateInteractiveImportItems({
|
||||
ids,
|
||||
quality: {
|
||||
quality,
|
||||
revision
|
||||
}
|
||||
});
|
||||
|
||||
dispatchReprocessInteractiveImportItems({ ids });
|
||||
|
||||
this.props.onModalClose(true);
|
||||
}
|
||||
|
||||
@@ -88,6 +97,7 @@ SelectQualityModalContentConnector.propTypes = {
|
||||
error: PropTypes.object,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
dispatchFetchQualityProfileSchema: PropTypes.func.isRequired,
|
||||
dispatchReprocessInteractiveImportItems: PropTypes.func.isRequired,
|
||||
dispatchUpdateInteractiveImportItems: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
@@ -54,22 +54,18 @@ class DeleteMovieModalContent extends Component {
|
||||
const {
|
||||
title,
|
||||
path,
|
||||
statistics,
|
||||
hasFile,
|
||||
sizeOnDisk,
|
||||
onModalClose
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
movieFileCount,
|
||||
sizeOnDisk
|
||||
} = statistics;
|
||||
|
||||
const deleteFiles = this.state.deleteFiles;
|
||||
const addImportExclusion = this.state.addImportExclusion;
|
||||
|
||||
let deleteFilesLabel = translate('DeleteFilesLabel', [movieFileCount]);
|
||||
let deleteFilesLabel = hasFile ? translate('DeleteFileLabel', [1]) : translate('DeleteFilesLabel', [0]);
|
||||
let deleteFilesHelpText = translate('DeleteFilesHelpText');
|
||||
|
||||
if (movieFileCount === 0) {
|
||||
if (!hasFile) {
|
||||
deleteFilesLabel = translate('DeleteMovieFolderLabel');
|
||||
deleteFilesHelpText = translate('DeleteMovieFolderHelpText');
|
||||
}
|
||||
@@ -92,6 +88,21 @@ class DeleteMovieModalContent extends Component {
|
||||
{path}
|
||||
</div>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>
|
||||
{translate('AddListExclusion')}
|
||||
</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="addImportExclusion"
|
||||
value={addImportExclusion}
|
||||
helpText={translate('AddImportExclusionHelpText')}
|
||||
kind={kinds.DANGER}
|
||||
onChange={this.onAddImportExclusionChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{deleteFilesLabel}</FormLabel>
|
||||
|
||||
@@ -113,29 +124,14 @@ class DeleteMovieModalContent extends Component {
|
||||
</div>
|
||||
|
||||
{
|
||||
!!movieFileCount &&
|
||||
!!hasFile &&
|
||||
<div>
|
||||
{movieFileCount} {translate('MovieFilesTotaling')} {formatBytes(sizeOnDisk)}
|
||||
{hasFile} {translate('MovieFilesTotaling')} {formatBytes(sizeOnDisk)}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>
|
||||
{translate('AddListExclusion')}
|
||||
</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="addImportExclusion"
|
||||
value={addImportExclusion}
|
||||
helpText={translate('AddImportExclusionHelpText')}
|
||||
kind={kinds.DANGER}
|
||||
onChange={this.onAddImportExclusionChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
@@ -158,15 +154,10 @@ class DeleteMovieModalContent extends Component {
|
||||
DeleteMovieModalContent.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
path: PropTypes.string.isRequired,
|
||||
statistics: PropTypes.object.isRequired,
|
||||
hasFile: PropTypes.bool.isRequired,
|
||||
sizeOnDisk: PropTypes.string.isRequired,
|
||||
onDeletePress: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
DeleteMovieModalContent.defaultProps = {
|
||||
statistics: {
|
||||
movieFileCount: 0
|
||||
}
|
||||
};
|
||||
|
||||
export default DeleteMovieModalContent;
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
|
||||
import MovieCreditPosters from '../MovieCreditPosters';
|
||||
import MovieCastPoster from './MovieCastPoster';
|
||||
|
||||
@@ -26,19 +24,8 @@ function createMapStateToProps() {
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
fetchRootFolders
|
||||
};
|
||||
|
||||
class MovieCastPostersConnector extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
componentDidMount() {
|
||||
this.props.fetchRootFolders();
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
@@ -53,8 +40,4 @@ class MovieCastPostersConnector extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
MovieCastPostersConnector.propTypes = {
|
||||
fetchRootFolders: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(MovieCastPostersConnector);
|
||||
export default connect(createMapStateToProps)(MovieCastPostersConnector);
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
|
||||
import MovieCreditPosters from '../MovieCreditPosters';
|
||||
import MovieCrewPoster from './MovieCrewPoster';
|
||||
|
||||
@@ -26,19 +24,8 @@ function createMapStateToProps() {
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
fetchRootFolders
|
||||
};
|
||||
|
||||
class MovieCrewPostersConnector extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
componentDidMount() {
|
||||
this.props.fetchRootFolders();
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
@@ -53,8 +40,4 @@ class MovieCrewPostersConnector extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
MovieCrewPostersConnector.propTypes = {
|
||||
fetchRootFolders: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(MovieCrewPostersConnector);
|
||||
export default connect(createMapStateToProps)(MovieCrewPostersConnector);
|
||||
|
||||
@@ -286,7 +286,7 @@ class MovieDetails extends Component {
|
||||
onMonitorTogglePress,
|
||||
onRefreshPress,
|
||||
onSearchPress,
|
||||
queueDetails,
|
||||
queueItems,
|
||||
movieRuntimeFormat
|
||||
} = this.props;
|
||||
|
||||
@@ -318,7 +318,6 @@ class MovieDetails extends Component {
|
||||
<PageToolbarButton
|
||||
label={translate('SearchMovie')}
|
||||
iconName={icons.SEARCH}
|
||||
isDisabled={!monitored}
|
||||
isSpinning={isSearching}
|
||||
title={undefined}
|
||||
onPress={onSearchPress}
|
||||
@@ -523,7 +522,7 @@ class MovieDetails extends Component {
|
||||
hasMovieFiles={hasMovieFiles}
|
||||
monitored={monitored}
|
||||
isAvailable={isAvailable}
|
||||
queueDetails={queueDetails}
|
||||
queueItem={(queueItems.length > 0) ? queueItems[0] : null}
|
||||
/>
|
||||
</span>
|
||||
</InfoLabel>
|
||||
@@ -794,7 +793,7 @@ MovieDetails.propTypes = {
|
||||
onRefreshPress: PropTypes.func.isRequired,
|
||||
onSearchPress: PropTypes.func.isRequired,
|
||||
onGoToMovie: PropTypes.func.isRequired,
|
||||
queueDetails: PropTypes.object,
|
||||
queueItems: PropTypes.arrayOf(PropTypes.object),
|
||||
movieRuntimeFormat: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
|
||||
@@ -89,10 +89,10 @@ function createMapStateToProps() {
|
||||
createAllMoviesSelector(),
|
||||
createCommandsSelector(),
|
||||
createDimensionsSelector(),
|
||||
(state) => state.queue.details,
|
||||
(state) => state.queue.details.items,
|
||||
(state) => state.app.isSidebarVisible,
|
||||
(state) => state.settings.ui.item.movieRuntimeFormat,
|
||||
(titleSlug, movieFiles, movieCredits, extraFiles, allMovies, commands, dimensions, queueDetails, isSidebarVisible, movieRuntimeFormat) => {
|
||||
(titleSlug, movieFiles, movieCredits, extraFiles, allMovies, commands, dimensions, queueItems, isSidebarVisible, movieRuntimeFormat) => {
|
||||
const sortedMovies = _.orderBy(allMovies, 'sortTitle');
|
||||
const movieIndex = _.findIndex(sortedMovies, { titleSlug });
|
||||
const movie = sortedMovies[movieIndex];
|
||||
@@ -165,7 +165,7 @@ function createMapStateToProps() {
|
||||
nextMovie,
|
||||
isSmallScreen: dimensions.isSmallScreen,
|
||||
isSidebarVisible,
|
||||
queueDetails,
|
||||
queueItems,
|
||||
movieRuntimeFormat
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import NotFound from 'Components/NotFound';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
|
||||
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import MovieDetailsConnector from './MovieDetailsConnector';
|
||||
@@ -46,7 +47,8 @@ function createMapStateToProps() {
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
push
|
||||
push,
|
||||
fetchRootFolders
|
||||
};
|
||||
|
||||
class MovieDetailsPageConnector extends Component {
|
||||
@@ -54,6 +56,10 @@ class MovieDetailsPageConnector extends Component {
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
componentDidMount() {
|
||||
this.props.fetchRootFolders();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (!this.props.titleSlug) {
|
||||
this.props.push(`${window.Radarr.urlBase}/`);
|
||||
@@ -112,7 +118,8 @@ MovieDetailsPageConnector.propTypes = {
|
||||
isPopulated: PropTypes.bool.isRequired,
|
||||
error: PropTypes.object,
|
||||
match: PropTypes.shape({ params: PropTypes.shape({ titleSlug: PropTypes.string.isRequired }).isRequired }).isRequired,
|
||||
push: PropTypes.func.isRequired
|
||||
push: PropTypes.func.isRequired,
|
||||
fetchRootFolders: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(MovieDetailsPageConnector);
|
||||
|
||||
@@ -1,24 +1,29 @@
|
||||
.missing {
|
||||
padding-left: 2px;
|
||||
border-left: 4px solid $dangerColor;
|
||||
}
|
||||
|
||||
.downloaded {
|
||||
padding-left: 2px;
|
||||
border-left: 4px solid $successColor;
|
||||
}
|
||||
|
||||
.notAvailable {
|
||||
padding-left: 2px;
|
||||
border-left: 4px solid $primaryColor;
|
||||
}
|
||||
|
||||
.unmonitored {
|
||||
padding-left: 2px;
|
||||
border-left: 4px solid $warningColor;
|
||||
}
|
||||
|
||||
.queue {
|
||||
padding-left: 2px;
|
||||
border-left: 4px solid $queueColor;
|
||||
}
|
||||
|
||||
.continuing {
|
||||
padding-left: 2px;
|
||||
border-left: 4px solid $primaryColor;
|
||||
}
|
||||
|
||||
.availNotMonitored {
|
||||
padding-left: 2px;
|
||||
border-left: 4px solid $darkGray;
|
||||
}
|
||||
|
||||
.ended {
|
||||
padding-left: 2px;
|
||||
border-left: 4px solid $successColor;
|
||||
}
|
||||
|
||||
.missingMonitored {
|
||||
padding-left: 2px;
|
||||
border-left: 4px solid $dangerColor;
|
||||
}
|
||||
|
||||
.missingUnmonitored {
|
||||
padding-left: 2px;
|
||||
border-left: 4px solid $warningColor;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Label from 'Components/Label';
|
||||
import { kinds, sizes } from 'Helpers/Props';
|
||||
import getQueueStatusText from 'Utilities/Movie/getQueueStatusText';
|
||||
import firstCharToUpper from 'Utilities/String/firstCharToUpper';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './MovieStatusLabel.css';
|
||||
|
||||
function getMovieStatus(hasFile, isMonitored, isAvailable, queueDetails = false) {
|
||||
function getMovieStatus(hasFile, isMonitored, isAvailable, queueItem = false) {
|
||||
|
||||
if (queueDetails.items[0]) {
|
||||
const queueStatus = queueDetails.items[0].status;
|
||||
const queueState = queueDetails.items[0].trackedDownloadStatus;
|
||||
if (queueItem) {
|
||||
const queueStatus = queueItem.status;
|
||||
const queueState = queueItem.trackedDownloadStatus;
|
||||
const queueStatusText = getQueueStatusText(queueStatus, queueState);
|
||||
|
||||
if (queueStatusText) {
|
||||
@@ -17,19 +19,23 @@ function getMovieStatus(hasFile, isMonitored, isAvailable, queueDetails = false)
|
||||
}
|
||||
}
|
||||
|
||||
if (hasFile) {
|
||||
return 'downloaded';
|
||||
if (hasFile && !isMonitored) {
|
||||
return 'availNotMonitored';
|
||||
}
|
||||
|
||||
if (!isMonitored) {
|
||||
return 'unmonitored';
|
||||
if (hasFile) {
|
||||
return 'ended';
|
||||
}
|
||||
|
||||
if (isAvailable && !isMonitored && !hasFile) {
|
||||
return 'missingUnmonitored';
|
||||
}
|
||||
|
||||
if (isAvailable && !hasFile) {
|
||||
return 'missing';
|
||||
return 'missingMonitored';
|
||||
}
|
||||
|
||||
return 'notAvailable';
|
||||
return 'continuing';
|
||||
}
|
||||
|
||||
function MovieStatusLabel(props) {
|
||||
@@ -37,16 +43,61 @@ function MovieStatusLabel(props) {
|
||||
hasMovieFiles,
|
||||
monitored,
|
||||
isAvailable,
|
||||
queueDetails
|
||||
queueItem,
|
||||
useLabel,
|
||||
colorImpairedMode
|
||||
} = props;
|
||||
|
||||
const status = getMovieStatus(hasMovieFiles, monitored, isAvailable, queueDetails);
|
||||
let status = getMovieStatus(hasMovieFiles, monitored, isAvailable, queueItem);
|
||||
let statusClass = status;
|
||||
|
||||
if (queueDetails.items.length) {
|
||||
if (status === 'availNotMonitored' || status === 'ended') {
|
||||
status = 'downloaded';
|
||||
}
|
||||
if (status === 'missingMonitored' || status === 'missingUnmonitored') {
|
||||
status = 'missing';
|
||||
}
|
||||
if (status === 'continuing') {
|
||||
status = 'notAvailable';
|
||||
}
|
||||
|
||||
if (queueItem) {
|
||||
statusClass = 'queue';
|
||||
}
|
||||
|
||||
if (useLabel) {
|
||||
let kind = kinds.SUCCESS;
|
||||
|
||||
switch (statusClass) {
|
||||
case 'queue':
|
||||
kind = kinds.QUEUE;
|
||||
break;
|
||||
case 'missingMonitored':
|
||||
kind = kinds.DANGER;
|
||||
break;
|
||||
case 'continuing':
|
||||
kind = kinds.INFO;
|
||||
break;
|
||||
case 'availNotMonitored':
|
||||
kind = kinds.DEFAULT;
|
||||
break;
|
||||
case 'missingUnmonitored':
|
||||
kind = kinds.WARNING;
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
return (
|
||||
<Label
|
||||
kind={kind}
|
||||
size={sizes.LARGE}
|
||||
colorImpairedMode={colorImpairedMode}
|
||||
>
|
||||
{translate(firstCharToUpper(status))}
|
||||
</Label>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<span
|
||||
className={styles[statusClass]}
|
||||
@@ -60,7 +111,9 @@ MovieStatusLabel.propTypes = {
|
||||
hasMovieFiles: PropTypes.bool.isRequired,
|
||||
monitored: PropTypes.bool.isRequired,
|
||||
isAvailable: PropTypes.bool.isRequired,
|
||||
queueDetails: PropTypes.object
|
||||
queueItem: PropTypes.object,
|
||||
useLabel: PropTypes.bool,
|
||||
colorImpairedMode: PropTypes.bool
|
||||
};
|
||||
|
||||
MovieStatusLabel.defaultProps = {
|
||||
|
||||
@@ -13,6 +13,7 @@ import MovieDetailsLinks from 'Movie/Details/MovieDetailsLinks';
|
||||
import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
|
||||
import MovieIndexProgressBar from 'Movie/Index/ProgressBar/MovieIndexProgressBar';
|
||||
import MoviePoster from 'Movie/MoviePoster';
|
||||
import getRelativeDate from 'Utilities/Date/getRelativeDate';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import MovieIndexPosterInfo from './MovieIndexPosterInfo';
|
||||
import styles from './MovieIndexPoster.css';
|
||||
@@ -101,6 +102,11 @@ class MovieIndexPoster extends Component {
|
||||
showSearchAction,
|
||||
showRelativeDates,
|
||||
shortDateFormat,
|
||||
showReleaseDate,
|
||||
showCinemaRelease,
|
||||
inCinemas,
|
||||
physicalRelease,
|
||||
digitalRelease,
|
||||
timeFormat,
|
||||
isRefreshingMovie,
|
||||
isSearchingMovie,
|
||||
@@ -127,6 +133,19 @@ class MovieIndexPoster extends Component {
|
||||
height: `${posterHeight}px`
|
||||
};
|
||||
|
||||
let releaseDate = '';
|
||||
let releaseDateType = '';
|
||||
if (physicalRelease && digitalRelease) {
|
||||
releaseDate = (physicalRelease < digitalRelease) ? physicalRelease : digitalRelease;
|
||||
releaseDateType = (physicalRelease < digitalRelease) ? 'Released' : 'Digital';
|
||||
} else if (physicalRelease && !digitalRelease) {
|
||||
releaseDate = physicalRelease;
|
||||
releaseDateType = 'Released';
|
||||
} else if (digitalRelease && !physicalRelease) {
|
||||
releaseDate = digitalRelease;
|
||||
releaseDateType = 'Digital';
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.content}>
|
||||
<div className={styles.posterContainer}>
|
||||
@@ -253,12 +272,67 @@ class MovieIndexPoster extends Component {
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
showCinemaRelease && inCinemas &&
|
||||
<div className={styles.title}>
|
||||
<Icon
|
||||
name={icons.IN_CINEMAS}
|
||||
/> {getRelativeDate(
|
||||
inCinemas,
|
||||
shortDateFormat,
|
||||
showRelativeDates,
|
||||
{
|
||||
timeFormat,
|
||||
timeForToday: false
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
showReleaseDate && releaseDateType === 'Released' &&
|
||||
<div className={styles.title}>
|
||||
<Icon
|
||||
name={icons.DISC}
|
||||
/> {getRelativeDate(
|
||||
releaseDate,
|
||||
shortDateFormat,
|
||||
showRelativeDates,
|
||||
{
|
||||
timeFormat,
|
||||
timeForToday: false
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
showReleaseDate && releaseDateType === 'Digital' &&
|
||||
<div className={styles.title}>
|
||||
<Icon
|
||||
name={icons.MOVIE_FILE}
|
||||
/> {getRelativeDate(
|
||||
releaseDate,
|
||||
shortDateFormat,
|
||||
showRelativeDates,
|
||||
{
|
||||
timeFormat,
|
||||
timeForToday: false
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
|
||||
<MovieIndexPosterInfo
|
||||
qualityProfile={qualityProfile}
|
||||
showQualityProfile={showQualityProfile}
|
||||
showReleaseDate={showReleaseDate}
|
||||
showRelativeDates={showRelativeDates}
|
||||
shortDateFormat={shortDateFormat}
|
||||
timeFormat={timeFormat}
|
||||
inCinemas={inCinemas}
|
||||
physicalRelease={physicalRelease}
|
||||
digitalRelease={digitalRelease}
|
||||
{...otherProps}
|
||||
/>
|
||||
|
||||
@@ -298,6 +372,11 @@ MovieIndexPoster.propTypes = {
|
||||
showSearchAction: PropTypes.bool.isRequired,
|
||||
showRelativeDates: PropTypes.bool.isRequired,
|
||||
shortDateFormat: PropTypes.string.isRequired,
|
||||
showCinemaRelease: PropTypes.bool.isRequired,
|
||||
showReleaseDate: PropTypes.bool.isRequired,
|
||||
inCinemas: PropTypes.string,
|
||||
physicalRelease: PropTypes.string,
|
||||
digitalRelease: PropTypes.string,
|
||||
timeFormat: PropTypes.string.isRequired,
|
||||
isRefreshingMovie: PropTypes.bool.isRequired,
|
||||
isSearchingMovie: PropTypes.bool.isRequired,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Icon from 'Components/Icon';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import getRelativeDate from 'Utilities/Date/getRelativeDate';
|
||||
import formatBytes from 'Utilities/Number/formatBytes';
|
||||
import translate from 'Utilities/String/translate';
|
||||
@@ -10,6 +12,7 @@ function MovieIndexPosterInfo(props) {
|
||||
studio,
|
||||
qualityProfile,
|
||||
showQualityProfile,
|
||||
showReleaseDate,
|
||||
added,
|
||||
inCinemas,
|
||||
digitalRelease,
|
||||
@@ -57,7 +60,7 @@ function MovieIndexPosterInfo(props) {
|
||||
);
|
||||
}
|
||||
|
||||
if (sortKey === 'inCinemas' && inCinemas) {
|
||||
if (sortKey === 'inCinemas' && inCinemas && !showReleaseDate) {
|
||||
const inCinemasDate = getRelativeDate(
|
||||
inCinemas,
|
||||
shortDateFormat,
|
||||
@@ -70,12 +73,14 @@ function MovieIndexPosterInfo(props) {
|
||||
|
||||
return (
|
||||
<div className={styles.info}>
|
||||
{translate('InCinemas')}: {inCinemasDate}
|
||||
<Icon
|
||||
name={icons.IN_CINEMAS}
|
||||
/> {inCinemasDate}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (sortKey === 'digitalRelease' && digitalRelease) {
|
||||
if (sortKey === 'digitalRelease' && digitalRelease && !showReleaseDate) {
|
||||
const digitalReleaseDate = getRelativeDate(
|
||||
digitalRelease,
|
||||
shortDateFormat,
|
||||
@@ -88,12 +93,14 @@ function MovieIndexPosterInfo(props) {
|
||||
|
||||
return (
|
||||
<div className={styles.info}>
|
||||
{translate('Digital')}: {digitalReleaseDate}
|
||||
<Icon
|
||||
name={icons.MOVIE_FILE}
|
||||
/> {digitalReleaseDate}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (sortKey === 'physicalRelease' && physicalRelease) {
|
||||
if (sortKey === 'physicalRelease' && physicalRelease && !showReleaseDate) {
|
||||
const physicalReleaseDate = getRelativeDate(
|
||||
physicalRelease,
|
||||
shortDateFormat,
|
||||
@@ -106,7 +113,9 @@ function MovieIndexPosterInfo(props) {
|
||||
|
||||
return (
|
||||
<div className={styles.info}>
|
||||
{translate('Released')}: {physicalReleaseDate}
|
||||
<Icon
|
||||
name={icons.DISC}
|
||||
/> {physicalReleaseDate}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -150,6 +159,7 @@ MovieIndexPosterInfo.propTypes = {
|
||||
path: PropTypes.string.isRequired,
|
||||
sizeOnDisk: PropTypes.number,
|
||||
sortKey: PropTypes.string.isRequired,
|
||||
showReleaseDate: PropTypes.bool.isRequired,
|
||||
showRelativeDates: PropTypes.bool.isRequired,
|
||||
shortDateFormat: PropTypes.string.isRequired,
|
||||
timeFormat: PropTypes.string.isRequired
|
||||
|
||||
@@ -38,7 +38,8 @@ function calculateRowHeight(posterHeight, sortKey, isSmallScreen, posterOptions)
|
||||
detailedProgressBar,
|
||||
showTitle,
|
||||
showMonitored,
|
||||
showQualityProfile
|
||||
showQualityProfile,
|
||||
showReleaseDate
|
||||
} = posterOptions;
|
||||
|
||||
const nextAiringHeight = 19;
|
||||
@@ -62,6 +63,10 @@ function calculateRowHeight(posterHeight, sortKey, isSmallScreen, posterOptions)
|
||||
heights.push(19);
|
||||
}
|
||||
|
||||
if (showReleaseDate) {
|
||||
heights.push(19);
|
||||
}
|
||||
|
||||
switch (sortKey) {
|
||||
case 'studio':
|
||||
case 'added':
|
||||
@@ -206,7 +211,9 @@ class MovieIndexPosters extends Component {
|
||||
detailedProgressBar,
|
||||
showTitle,
|
||||
showMonitored,
|
||||
showQualityProfile
|
||||
showQualityProfile,
|
||||
showCinemaRelease,
|
||||
showReleaseDate
|
||||
} = posterOptions;
|
||||
|
||||
const movieIdx = rowIndex * columnCount + columnIndex;
|
||||
@@ -235,6 +242,8 @@ class MovieIndexPosters extends Component {
|
||||
showTitle={showTitle}
|
||||
showMonitored={showMonitored}
|
||||
showQualityProfile={showQualityProfile}
|
||||
showReleaseDate={showReleaseDate}
|
||||
showCinemaRelease={showCinemaRelease}
|
||||
showRelativeDates={showRelativeDates}
|
||||
shortDateFormat={shortDateFormat}
|
||||
timeFormat={timeFormat}
|
||||
|
||||
@@ -33,6 +33,8 @@ class MovieIndexPosterOptionsModalContent extends Component {
|
||||
showTitle: props.showTitle,
|
||||
showMonitored: props.showMonitored,
|
||||
showQualityProfile: props.showQualityProfile,
|
||||
showCinemaRelease: props.showCinemaRelease,
|
||||
showReleaseDate: props.showReleaseDate,
|
||||
showSearchAction: props.showSearchAction
|
||||
};
|
||||
}
|
||||
@@ -44,6 +46,8 @@ class MovieIndexPosterOptionsModalContent extends Component {
|
||||
showTitle,
|
||||
showMonitored,
|
||||
showQualityProfile,
|
||||
showCinemaRelease,
|
||||
showReleaseDate,
|
||||
showSearchAction
|
||||
} = this.props;
|
||||
|
||||
@@ -69,6 +73,14 @@ class MovieIndexPosterOptionsModalContent extends Component {
|
||||
state.showQualityProfile = showQualityProfile;
|
||||
}
|
||||
|
||||
if (showCinemaRelease !== prevProps.showCinemaRelease) {
|
||||
state.showCinemaRelease = showCinemaRelease;
|
||||
}
|
||||
|
||||
if (showReleaseDate !== prevProps.showReleaseDate) {
|
||||
state.showReleaseDate = showReleaseDate;
|
||||
}
|
||||
|
||||
if (showSearchAction !== prevProps.showSearchAction) {
|
||||
state.showSearchAction = showSearchAction;
|
||||
}
|
||||
@@ -103,6 +115,8 @@ class MovieIndexPosterOptionsModalContent extends Component {
|
||||
showTitle,
|
||||
showMonitored,
|
||||
showQualityProfile,
|
||||
showCinemaRelease,
|
||||
showReleaseDate,
|
||||
showSearchAction
|
||||
} = this.state;
|
||||
|
||||
@@ -174,6 +188,30 @@ class MovieIndexPosterOptionsModalContent extends Component {
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('ShowCinemaRelease')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="showCinemaRelease"
|
||||
value={showCinemaRelease}
|
||||
helpText={translate('showCinemaReleaseHelpText')}
|
||||
onChange={this.onChangePosterOption}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('ShowReleaseDate')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="showReleaseDate"
|
||||
value={showReleaseDate}
|
||||
helpText={translate('ShowReleaseDateHelpText')}
|
||||
onChange={this.onChangePosterOption}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('ShowSearch')}</FormLabel>
|
||||
|
||||
@@ -206,6 +244,8 @@ MovieIndexPosterOptionsModalContent.propTypes = {
|
||||
showMonitored: PropTypes.bool.isRequired,
|
||||
showQualityProfile: PropTypes.bool.isRequired,
|
||||
detailedProgressBar: PropTypes.bool.isRequired,
|
||||
showCinemaRelease: PropTypes.bool.isRequired,
|
||||
showReleaseDate: PropTypes.bool.isRequired,
|
||||
showSearchAction: PropTypes.bool.isRequired,
|
||||
onChangePosterOption: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
|
||||
@@ -165,11 +165,13 @@ class MovieFileEditorRow extends Component {
|
||||
|
||||
<TableRowCell className={styles.actions}>
|
||||
<IconButton
|
||||
title={translate('EditMovieFile')}
|
||||
name={icons.EDIT}
|
||||
onPress={this.onFileEditPress}
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
title={translate('Details')}
|
||||
name={icons.MEDIA_INFO}
|
||||
onPress={this.onFileDetailsPress}
|
||||
/>
|
||||
|
||||
@@ -21,6 +21,7 @@ function EditImportListModalContent(props) {
|
||||
advancedSettings,
|
||||
isFetching,
|
||||
error,
|
||||
rootFolderError,
|
||||
isSaving,
|
||||
isTesting,
|
||||
saveError,
|
||||
@@ -62,14 +63,14 @@ function EditImportListModalContent(props) {
|
||||
}
|
||||
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
!isFetching && (!!error || !!rootFolderError) &&
|
||||
<div>
|
||||
{translate('UnableToAddANewListPleaseTryAgain')}
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
!isFetching && !error &&
|
||||
!isFetching && !error && !rootFolderError &&
|
||||
<Form
|
||||
{...otherProps}
|
||||
>
|
||||
@@ -163,6 +164,7 @@ function EditImportListModalContent(props) {
|
||||
type={inputTypes.ROOT_FOLDER_SELECT}
|
||||
name="rootFolderPath"
|
||||
{...rootFolderPath}
|
||||
includeMissingValue={true}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
@@ -239,6 +241,7 @@ EditImportListModalContent.propTypes = {
|
||||
advancedSettings: PropTypes.bool.isRequired,
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
error: PropTypes.object,
|
||||
rootFolderError: PropTypes.object,
|
||||
isSaving: PropTypes.bool.isRequired,
|
||||
isTesting: PropTypes.bool.isRequired,
|
||||
saveError: PropTypes.object,
|
||||
|
||||
@@ -9,11 +9,21 @@ import EditImportListModalContent from './EditImportListModalContent';
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.settings.advancedSettings,
|
||||
(state) => state.rootFolders,
|
||||
createProviderSettingsSelector('importLists'),
|
||||
(advancedSettings, importList) => {
|
||||
(advancedSettings, rootFolders, importList) => {
|
||||
const {
|
||||
isFetching,
|
||||
isPopulated,
|
||||
...otherProps
|
||||
} = importList;
|
||||
|
||||
return {
|
||||
advancedSettings,
|
||||
...importList
|
||||
isFetching: rootFolders.isFetching || isFetching,
|
||||
isPopulated: rootFolders.isPopulated && isPopulated,
|
||||
rootFolderError: rootFolders.error,
|
||||
...otherProps
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
@@ -139,7 +139,7 @@ class NamingModal extends Component {
|
||||
const mediaInfoTokens = [
|
||||
{ token: '{MediaInfo Simple}', example: 'x264 DTS' },
|
||||
{ token: '{MediaInfo Full}', example: 'x264 DTS [EN+DE]' },
|
||||
{ token: '{MediaInfo VideoCodec}', example: 'x264' },
|
||||
|
||||
{ token: '{MediaInfo AudioCodec}', example: 'DTS' },
|
||||
{ token: '{MediaInfo AudioChannels}', example: '5.1' },
|
||||
{ token: '{MediaInfo AudioLanguages}', example: '[EN+DE]' },
|
||||
|
||||
@@ -59,13 +59,17 @@ class Notification extends Component {
|
||||
onDownload,
|
||||
onUpgrade,
|
||||
onRename,
|
||||
onDelete,
|
||||
onMovieDelete,
|
||||
onMovieFileDelete,
|
||||
onMovieFileDeleteForUpgrade,
|
||||
onHealthIssue,
|
||||
supportsOnGrab,
|
||||
supportsOnDownload,
|
||||
supportsOnUpgrade,
|
||||
supportsOnRename,
|
||||
supportsOnDelete,
|
||||
supportsOnMovieDelete,
|
||||
supportsOnMovieFileDelete,
|
||||
supportsOnMovieFileDeleteForUpgrade,
|
||||
supportsOnHealthIssue
|
||||
} = this.props;
|
||||
|
||||
@@ -80,55 +84,78 @@ class Notification extends Component {
|
||||
</div>
|
||||
|
||||
{
|
||||
supportsOnGrab && onGrab &&
|
||||
supportsOnGrab && onGrab ?
|
||||
<Label kind={kinds.SUCCESS}>
|
||||
{translate('OnGrab')}
|
||||
</Label>
|
||||
</Label> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
supportsOnDelete && onDelete &&
|
||||
<Label kind={kinds.SUCCESS}>
|
||||
{translate('OnDelete')}
|
||||
</Label>
|
||||
}
|
||||
|
||||
{
|
||||
supportsOnDownload && onDownload &&
|
||||
supportsOnDownload && onDownload ?
|
||||
<Label kind={kinds.SUCCESS}>
|
||||
{translate('OnImport')}
|
||||
</Label>
|
||||
</Label> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
supportsOnUpgrade && onDownload && onUpgrade &&
|
||||
supportsOnUpgrade && onDownload && onUpgrade ?
|
||||
<Label kind={kinds.SUCCESS}>
|
||||
{translate('OnUpgrade')}
|
||||
</Label>
|
||||
</Label> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
supportsOnRename && onRename &&
|
||||
supportsOnRename && onRename ?
|
||||
<Label kind={kinds.SUCCESS}>
|
||||
{translate('OnRename')}
|
||||
</Label>
|
||||
</Label> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
supportsOnHealthIssue && onHealthIssue &&
|
||||
supportsOnHealthIssue && onHealthIssue ?
|
||||
<Label kind={kinds.SUCCESS}>
|
||||
{translate('OnHealthIssue')}
|
||||
</Label>
|
||||
</Label> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
!onGrab && !onDownload && !onRename && !onHealthIssue && !onDelete &&
|
||||
supportsOnMovieDelete && onMovieDelete ?
|
||||
<Label kind={kinds.SUCCESS}>
|
||||
{translate('OnMovieDelete')}
|
||||
</Label> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
supportsOnMovieFileDelete && onMovieFileDelete ?
|
||||
<Label kind={kinds.SUCCESS}>
|
||||
{translate('OnMovieFileDelete')}
|
||||
</Label> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
supportsOnMovieFileDeleteForUpgrade && onMovieFileDelete && onMovieFileDeleteForUpgrade ?
|
||||
<Label kind={kinds.SUCCESS}>
|
||||
{translate('OnMovieFileDeleteForUpgrade')}
|
||||
</Label> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
!onGrab && !onDownload && !onRename && !onHealthIssue && !onMovieDelete && !onMovieFileDelete ?
|
||||
<Label
|
||||
kind={kinds.DISABLED}
|
||||
outline={true}
|
||||
>
|
||||
{translate('Disabled')}
|
||||
</Label>
|
||||
</Label> :
|
||||
null
|
||||
}
|
||||
|
||||
<EditNotificationModalConnector
|
||||
@@ -159,11 +186,15 @@ Notification.propTypes = {
|
||||
onDownload: PropTypes.bool.isRequired,
|
||||
onUpgrade: PropTypes.bool.isRequired,
|
||||
onRename: PropTypes.bool.isRequired,
|
||||
onDelete: PropTypes.bool.isRequired,
|
||||
onMovieDelete: PropTypes.bool.isRequired,
|
||||
onMovieFileDelete: PropTypes.bool.isRequired,
|
||||
onMovieFileDeleteForUpgrade: PropTypes.bool.isRequired,
|
||||
onHealthIssue: PropTypes.bool.isRequired,
|
||||
supportsOnGrab: PropTypes.bool.isRequired,
|
||||
supportsOnDownload: PropTypes.bool.isRequired,
|
||||
supportsOnDelete: PropTypes.bool.isRequired,
|
||||
supportsOnMovieDelete: PropTypes.bool.isRequired,
|
||||
supportsOnMovieFileDelete: PropTypes.bool.isRequired,
|
||||
supportsOnMovieFileDeleteForUpgrade: PropTypes.bool.isRequired,
|
||||
supportsOnUpgrade: PropTypes.bool.isRequired,
|
||||
supportsOnRename: PropTypes.bool.isRequired,
|
||||
supportsOnHealthIssue: PropTypes.bool.isRequired,
|
||||
|
||||
@@ -19,13 +19,17 @@ function NotificationEventItems(props) {
|
||||
onDownload,
|
||||
onUpgrade,
|
||||
onRename,
|
||||
onDelete,
|
||||
onMovieDelete,
|
||||
onMovieFileDelete,
|
||||
onMovieFileDeleteForUpgrade,
|
||||
onHealthIssue,
|
||||
supportsOnGrab,
|
||||
supportsOnDownload,
|
||||
supportsOnUpgrade,
|
||||
supportsOnRename,
|
||||
supportsOnDelete,
|
||||
supportsOnMovieDelete,
|
||||
supportsOnMovieFileDelete,
|
||||
supportsOnMovieFileDeleteForUpgrade,
|
||||
supportsOnHealthIssue,
|
||||
includeHealthWarnings
|
||||
} = item;
|
||||
@@ -89,14 +93,39 @@ function NotificationEventItems(props) {
|
||||
<div>
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="onDelete"
|
||||
helpText={translate('OnDeleteHelpText')}
|
||||
isDisabled={!supportsOnDelete.value}
|
||||
{...onDelete}
|
||||
name="onMovieDelete"
|
||||
helpText={translate('OnMovieDeleteHelpText')}
|
||||
isDisabled={!supportsOnMovieDelete.value}
|
||||
{...onMovieDelete}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="onMovieFileDelete"
|
||||
helpText={translate('OnMovieFileDeleteHelpText')}
|
||||
isDisabled={!supportsOnMovieFileDelete.value}
|
||||
{...onMovieFileDelete}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{
|
||||
onMovieFileDelete.value &&
|
||||
<div>
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="onMovieFileDeleteForUpgrade"
|
||||
helpText={translate('OnMovieFileDeleteForUpgradeHelpText')}
|
||||
isDisabled={!supportsOnMovieFileDeleteForUpgrade.value}
|
||||
{...onMovieFileDeleteForUpgrade}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div>
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
.horizontalScroll {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.delayProfiles {
|
||||
user-select: none;
|
||||
}
|
||||
@@ -25,3 +29,10 @@
|
||||
width: $dragHandleWidth;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $breakpointSmall) {
|
||||
.horizontalScroll {
|
||||
overflow-y: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@ import Icon from 'Components/Icon';
|
||||
import Link from 'Components/Link/Link';
|
||||
import Measure from 'Components/Measure';
|
||||
import PageSectionContent from 'Components/Page/PageSectionContent';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import Scroller from 'Components/Scroller/Scroller';
|
||||
import { icons, scrollDirections } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import DelayProfile from './DelayProfile';
|
||||
import DelayProfileDragPreview from './DelayProfileDragPreview';
|
||||
@@ -72,48 +73,59 @@ class DelayProfiles extends Component {
|
||||
errorMessage={translate('UnableToLoadDelayProfiles')}
|
||||
{...otherProps}
|
||||
>
|
||||
<div className={styles.delayProfilesHeader}>
|
||||
<div className={styles.column}>Protocol</div>
|
||||
<div className={styles.column}>Usenet Delay</div>
|
||||
<div className={styles.column}>Torrent Delay</div>
|
||||
<div className={styles.tags}>Tags</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.delayProfiles}>
|
||||
{
|
||||
items.map((item, index) => {
|
||||
return (
|
||||
<DelayProfileDragSource
|
||||
key={item.id}
|
||||
tagList={tagList}
|
||||
{...item}
|
||||
{...otherProps}
|
||||
index={index}
|
||||
isDragging={isDragging}
|
||||
isDraggingUp={isDraggingUp}
|
||||
isDraggingDown={isDraggingDown}
|
||||
onConfirmDeleteDelayProfile={onConfirmDeleteDelayProfile}
|
||||
/>
|
||||
);
|
||||
})
|
||||
<Scroller
|
||||
className={styles.horizontalScroll}
|
||||
scrollDirection={
|
||||
scrollDirections.HORIZONTAL
|
||||
}
|
||||
autoFocus={false}
|
||||
>
|
||||
<div>
|
||||
<div className={styles.delayProfilesHeader}>
|
||||
<div className={styles.column}>Protocol</div>
|
||||
<div className={styles.column}>Usenet Delay</div>
|
||||
<div className={styles.column}>Torrent Delay</div>
|
||||
<div className={styles.tags}>Tags</div>
|
||||
</div>
|
||||
|
||||
<DelayProfileDragPreview
|
||||
width={width}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.delayProfiles}>
|
||||
{
|
||||
items.map((item, index) => {
|
||||
return (
|
||||
<DelayProfileDragSource
|
||||
key={item.id}
|
||||
tagList={tagList}
|
||||
{...item}
|
||||
{...otherProps}
|
||||
index={index}
|
||||
isDragging={isDragging}
|
||||
isDraggingUp={isDraggingUp}
|
||||
isDraggingDown={isDraggingDown}
|
||||
onConfirmDeleteDelayProfile={onConfirmDeleteDelayProfile}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
defaultProfile &&
|
||||
<div>
|
||||
<DelayProfile
|
||||
tagList={tagList}
|
||||
isDragging={false}
|
||||
onConfirmDeleteDelayProfile={onConfirmDeleteDelayProfile}
|
||||
{...defaultProfile}
|
||||
<DelayProfileDragPreview
|
||||
width={width}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
defaultProfile ?
|
||||
<div>
|
||||
<DelayProfile
|
||||
tagList={tagList}
|
||||
isDragging={false}
|
||||
onConfirmDeleteDelayProfile={onConfirmDeleteDelayProfile}
|
||||
{...defaultProfile}
|
||||
/>
|
||||
</div> :
|
||||
null
|
||||
}
|
||||
</div>
|
||||
</Scroller>
|
||||
|
||||
<div className={styles.addDelayProfile}>
|
||||
<Link
|
||||
|
||||
@@ -14,6 +14,7 @@ import styles from './QualityDefinition.css';
|
||||
|
||||
const MIN = 0;
|
||||
const MAX = 400;
|
||||
const MIN_DISTANCE = 1;
|
||||
|
||||
const slider = {
|
||||
min: MIN,
|
||||
@@ -176,7 +177,7 @@ class QualityDefinition extends Component {
|
||||
min={slider.min}
|
||||
max={slider.max}
|
||||
step={slider.step}
|
||||
minDistance={3}
|
||||
minDistance={MIN_DISTANCE * 3}
|
||||
value={[sliderMinSize, sliderPreferredSize, sliderMaxSize]}
|
||||
withTracks={true}
|
||||
allowCross={false}
|
||||
@@ -250,7 +251,7 @@ class QualityDefinition extends Component {
|
||||
name={`${id}.min`}
|
||||
value={minSize || MIN}
|
||||
min={MIN}
|
||||
max={preferredSize ? preferredSize - 5 : MAX - 5}
|
||||
max={preferredSize ? preferredSize - MIN_DISTANCE : MAX - MIN_DISTANCE}
|
||||
step={0.1}
|
||||
isFloat={true}
|
||||
onChange={this.onMinSizeChange}
|
||||
@@ -263,9 +264,9 @@ class QualityDefinition extends Component {
|
||||
<NumberInput
|
||||
className={styles.sizeInput}
|
||||
name={`${id}.min`}
|
||||
value={preferredSize || MAX - 5}
|
||||
value={preferredSize || MAX - MIN_DISTANCE}
|
||||
min={MIN}
|
||||
max={maxSize ? maxSize - 5 : MAX - 5}
|
||||
max={maxSize ? maxSize - MIN_DISTANCE : MAX - MIN_DISTANCE}
|
||||
step={0.1}
|
||||
isFloat={true}
|
||||
onChange={this.onPreferredSizeChange}
|
||||
@@ -277,9 +278,9 @@ class QualityDefinition extends Component {
|
||||
|
||||
<NumberInput
|
||||
className={styles.sizeInput}
|
||||
name={`${id}.min`}
|
||||
name={`${id}.max`}
|
||||
value={maxSize || MAX}
|
||||
min={minSize + 5}
|
||||
min={minSize + MIN_DISTANCE}
|
||||
max={MAX}
|
||||
step={0.1}
|
||||
isFloat={true}
|
||||
|
||||
@@ -38,8 +38,6 @@ class QualityDefinitionsConnector extends Component {
|
||||
// Lifecycle
|
||||
|
||||
componentDidMount() {
|
||||
this.props.dispatchFetchQualityDefinitions();
|
||||
|
||||
const {
|
||||
dispatchFetchQualityDefinitions,
|
||||
dispatchSaveQualityDefinitions,
|
||||
|
||||
@@ -106,6 +106,9 @@ export default {
|
||||
selectedSchema.onDownload = selectedSchema.supportsOnDownload;
|
||||
selectedSchema.onUpgrade = selectedSchema.supportsOnUpgrade;
|
||||
selectedSchema.onRename = selectedSchema.supportsOnRename;
|
||||
selectedSchema.onMovieDelete = selectedSchema.supportsOnMovieDelete;
|
||||
selectedSchema.onMovieFileDelete = selectedSchema.supportsOnMovieFileDelete;
|
||||
selectedSchema.onMovieFileDeleteForUpgrade = selectedSchema.supportsOnMovieFileDeleteForUpgrade;
|
||||
|
||||
return selectedSchema;
|
||||
});
|
||||
|
||||
@@ -88,6 +88,8 @@ export const actionHandlers = handleThunks({
|
||||
abortCurrentRequest = abortRequest;
|
||||
|
||||
request.done((data) => {
|
||||
data = data.map((movie) => ({ ...movie, internalId: movie.id, id: movie.tmdbId }));
|
||||
|
||||
dispatch(batchActions([
|
||||
update({ section, data }),
|
||||
|
||||
@@ -116,6 +118,7 @@ export const actionHandlers = handleThunks({
|
||||
const tmdbId = payload.tmdbId;
|
||||
const items = getState().addMovie.items;
|
||||
const newMovie = getNewMovie(_.cloneDeep(_.find(items, { tmdbId })), payload);
|
||||
newMovie.id = 0;
|
||||
|
||||
const promise = createAjaxRequest({
|
||||
url: '/movie',
|
||||
@@ -125,8 +128,12 @@ export const actionHandlers = handleThunks({
|
||||
}).request;
|
||||
|
||||
promise.done((data) => {
|
||||
const updatedItem = _.cloneDeep(data);
|
||||
updatedItem.id = updatedItem.tmdbId;
|
||||
|
||||
dispatch(batchActions([
|
||||
updateItem({ section: 'movies', ...data }),
|
||||
updateItem({ section: 'addMovie', ...updatedItem }),
|
||||
|
||||
set({
|
||||
section,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import _ from 'lodash';
|
||||
import { createAction } from 'redux-actions';
|
||||
import { createThunk, handleThunks } from 'Store/thunks';
|
||||
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
||||
import getSectionState from 'Utilities/State/getSectionState';
|
||||
import updateSectionState from 'Utilities/State/updateSectionState';
|
||||
import createHandleActions from './Creators/createHandleActions';
|
||||
@@ -22,6 +24,8 @@ function getDimensions(width, height) {
|
||||
|
||||
export const section = 'app';
|
||||
const messagesSection = 'app.messages';
|
||||
let abortPingServer = null;
|
||||
let pingTimeout = null;
|
||||
|
||||
//
|
||||
// State
|
||||
@@ -50,6 +54,8 @@ export const SET_VERSION = 'app/setVersion';
|
||||
export const SET_APP_VALUE = 'app/setAppValue';
|
||||
export const SET_IS_SIDEBAR_VISIBLE = 'app/setIsSidebarVisible';
|
||||
|
||||
export const PING_SERVER = 'app/pingServer';
|
||||
|
||||
//
|
||||
// Action Creators
|
||||
|
||||
@@ -59,6 +65,70 @@ export const setIsSidebarVisible = createAction(SET_IS_SIDEBAR_VISIBLE);
|
||||
export const setAppValue = createAction(SET_APP_VALUE);
|
||||
export const showMessage = createAction(SHOW_MESSAGE);
|
||||
export const hideMessage = createAction(HIDE_MESSAGE);
|
||||
export const pingServer = createThunk(PING_SERVER);
|
||||
|
||||
//
|
||||
// Helpers
|
||||
|
||||
function pingServerAfterTimeout(getState, dispatch) {
|
||||
if (abortPingServer) {
|
||||
abortPingServer();
|
||||
abortPingServer = null;
|
||||
}
|
||||
|
||||
if (pingTimeout) {
|
||||
clearTimeout(pingTimeout);
|
||||
pingTimeout = null;
|
||||
}
|
||||
|
||||
pingTimeout = setTimeout(() => {
|
||||
if (!getState().isRestarting && getState().isConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ajaxOptions = {
|
||||
url: '/system/status',
|
||||
method: 'GET',
|
||||
contentType: 'application/json'
|
||||
};
|
||||
|
||||
const { request, abortRequest } = createAjaxRequest(ajaxOptions);
|
||||
|
||||
abortPingServer = abortRequest;
|
||||
|
||||
request.done(() => {
|
||||
abortPingServer = null;
|
||||
pingTimeout = null;
|
||||
|
||||
dispatch(setAppValue({
|
||||
isRestarting: false
|
||||
}));
|
||||
});
|
||||
|
||||
request.fail((xhr) => {
|
||||
abortPingServer = null;
|
||||
pingTimeout = null;
|
||||
|
||||
// Unauthorized, but back online
|
||||
if (xhr.status === 401) {
|
||||
dispatch(setAppValue({
|
||||
isRestarting: false
|
||||
}));
|
||||
} else {
|
||||
pingServerAfterTimeout(getState, dispatch);
|
||||
}
|
||||
});
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
//
|
||||
// Action Handlers
|
||||
|
||||
export const actionHandlers = handleThunks({
|
||||
[PING_SERVER]: function(getState, payload, dispatch) {
|
||||
pingServerAfterTimeout(getState, dispatch);
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
// Reducers
|
||||
@@ -117,6 +187,9 @@ export const reducers = createHandleActions({
|
||||
};
|
||||
|
||||
if (state.version !== version) {
|
||||
if (!state.prevVersion) {
|
||||
newState.prevVersion = state.version;
|
||||
}
|
||||
newState.isUpdated = true;
|
||||
}
|
||||
|
||||
@@ -132,4 +205,3 @@ export const reducers = createHandleActions({
|
||||
}
|
||||
|
||||
}, defaultState, section);
|
||||
|
||||
|
||||
@@ -232,11 +232,9 @@ export const actionHandlers = handleThunks({
|
||||
}));
|
||||
|
||||
const promise = createAjaxRequest({
|
||||
url: '/history/failed',
|
||||
url: `/history/failed/${id}`,
|
||||
method: 'POST',
|
||||
data: {
|
||||
id
|
||||
}
|
||||
dataType: 'json'
|
||||
}).request;
|
||||
|
||||
promise.done(() => {
|
||||
|
||||
@@ -149,6 +149,8 @@ export const actionHandlers = handleThunks({
|
||||
id,
|
||||
path: item.path,
|
||||
movieId: item.movie.id,
|
||||
quality: item.quality,
|
||||
languages: item.languages,
|
||||
downloadId: item.downloadId
|
||||
};
|
||||
});
|
||||
|
||||
@@ -77,11 +77,8 @@ export const actionHandlers = handleThunks({
|
||||
} = payload;
|
||||
|
||||
const promise = createAjaxRequest({
|
||||
url: '/history/failed',
|
||||
method: 'POST',
|
||||
data: {
|
||||
id: historyId
|
||||
}
|
||||
url: `/history/failed/${historyId}`,
|
||||
method: 'POST'
|
||||
}).request;
|
||||
|
||||
promise.done(() => {
|
||||
|
||||
@@ -37,6 +37,8 @@ export const defaultState = {
|
||||
showTitle: false,
|
||||
showMonitored: true,
|
||||
showQualityProfile: true,
|
||||
showCinemaRelease: false,
|
||||
showReleaseDate: false,
|
||||
showSearchAction: false
|
||||
},
|
||||
|
||||
|
||||
@@ -154,7 +154,8 @@ export const defaultState = {
|
||||
{
|
||||
name: 'size',
|
||||
label: translate('Size'),
|
||||
type: filterBuilderTypes.NUMBER
|
||||
type: filterBuilderTypes.NUMBER,
|
||||
valueType: filterBuilderValueTypes.BYTES
|
||||
},
|
||||
{
|
||||
name: 'seeders',
|
||||
|
||||
@@ -5,6 +5,7 @@ import { createThunk, handleThunks } from 'Store/thunks';
|
||||
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
||||
import serverSideCollectionHandlers from 'Utilities/serverSideCollectionHandlers';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import { pingServer } from './appActions';
|
||||
import { set } from './baseActions';
|
||||
import createFetchHandler from './Creators/createFetchHandler';
|
||||
import createHandleActions from './Creators/createHandleActions';
|
||||
@@ -352,6 +353,7 @@ export const actionHandlers = handleThunks({
|
||||
|
||||
promise.done(() => {
|
||||
dispatch(setAppValue({ isRestarting: true }));
|
||||
dispatch(pingServer());
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@@ -28,27 +28,32 @@ function mergeColumns(path, initialState, persistedState, computedState) {
|
||||
|
||||
const columns = [];
|
||||
|
||||
initialColumns.forEach((initialColumn) => {
|
||||
const persistedColumnIndex = _.findIndex(persistedColumns, { name: initialColumn.name });
|
||||
const column = Object.assign({}, initialColumn);
|
||||
const persistedColumn = persistedColumnIndex > -1 ? persistedColumns[persistedColumnIndex] : undefined;
|
||||
// Add persisted columns in the same order they're currently in
|
||||
// as long as they haven't been removed.
|
||||
|
||||
if (persistedColumn) {
|
||||
column.isVisible = persistedColumn.isVisible;
|
||||
persistedColumns.forEach((persistedColumn) => {
|
||||
const column = initialColumns.find((i) => i.name === persistedColumn.name);
|
||||
|
||||
if (column) {
|
||||
columns.push({
|
||||
...column,
|
||||
isVisible: persistedColumn.isVisible
|
||||
});
|
||||
}
|
||||
|
||||
// If there is a persisted column, it's index doesn't exceed the column list
|
||||
// and it's modifiable, insert it in the proper position.
|
||||
|
||||
if (persistedColumn && columns.length - 1 > persistedColumnIndex && persistedColumn.isModifiable !== false) {
|
||||
columns.splice(persistedColumnIndex, 0, column);
|
||||
} else {
|
||||
columns.push(column);
|
||||
}
|
||||
|
||||
// Set the columns in the persisted state
|
||||
_.set(computedState, path, columns);
|
||||
});
|
||||
|
||||
// Add any columns added to the app in the initial position.
|
||||
initialColumns.forEach((initialColumn, index) => {
|
||||
const persistedColumnIndex = persistedColumns.findIndex((i) => i.name === initialColumn.name);
|
||||
const column = Object.assign({}, initialColumn);
|
||||
|
||||
if (persistedColumnIndex === -1) {
|
||||
columns.splice(index, 0, column);
|
||||
}
|
||||
});
|
||||
|
||||
// Set the columns in the persisted state
|
||||
_.set(computedState, path, columns);
|
||||
}
|
||||
|
||||
function slicer(paths_) {
|
||||
|
||||
56
frontend/src/System/Status/Donations/Donations.js
Normal file
56
frontend/src/System/Status/Donations/Donations.js
Normal file
@@ -0,0 +1,56 @@
|
||||
import React, { Component } from 'react';
|
||||
import FieldSet from 'Components/FieldSet';
|
||||
import Link from 'Components/Link/Link';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from '../styles.css';
|
||||
|
||||
class Donations extends Component {
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
return (
|
||||
<FieldSet legend={translate('Donations')}>
|
||||
<div className={styles.logoContainer} title="Radarr">
|
||||
<Link to="https://opencollective.com/radarr">
|
||||
<img
|
||||
className={styles.logo}
|
||||
src={`${window.Radarr.urlBase}/Content/Images/Icons/logo-radarr.png`}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
<div className={styles.logoContainer} title="Lidarr">
|
||||
<Link to="https://opencollective.com/lidarr">
|
||||
<img
|
||||
className={styles.logo}
|
||||
src={`${window.Radarr.urlBase}/Content/Images/Icons/logo-lidarr.png`}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
<div className={styles.logoContainer} title="Readarr">
|
||||
<Link to="https://opencollective.com/readarr">
|
||||
<img
|
||||
className={styles.logo}
|
||||
src={`${window.Radarr.urlBase}/Content/Images/Icons/logo-readarr.png`}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
<div className={styles.logoContainer} title="Sonarr">
|
||||
<Link to="https://opencollective.com/sonarr">
|
||||
<img
|
||||
className={styles.logo}
|
||||
src={`${window.Radarr.urlBase}/Content/Images/Icons/logo-sonarr.png`}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
</FieldSet>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Donations.propTypes = {
|
||||
|
||||
};
|
||||
|
||||
export default Donations;
|
||||
@@ -22,25 +22,25 @@ class MoreInfo extends Component {
|
||||
<Link to="https://radarr.video/">radarr.video</Link>
|
||||
</DescriptionListItemDescription>
|
||||
|
||||
<DescriptionListItemTitle>
|
||||
{translate('Discord')}
|
||||
</DescriptionListItemTitle>
|
||||
<DescriptionListItemDescription>
|
||||
<Link to="https://discord.gg/r5wJPt9">discord.gg/r5wJPt9</Link>
|
||||
</DescriptionListItemDescription>
|
||||
|
||||
<DescriptionListItemTitle>
|
||||
{translate('Wiki')}
|
||||
</DescriptionListItemTitle>
|
||||
<DescriptionListItemDescription>
|
||||
<Link to="https://wiki.servarr.com/Radarr">wiki.servarr.com/Radarr</Link>
|
||||
<Link to="https://wiki.servarr.com/Radarr">{translate('Wiki')}</Link>
|
||||
</DescriptionListItemDescription>
|
||||
|
||||
<DescriptionListItemTitle>
|
||||
{translate('Donations')}
|
||||
{translate('Reddit')}
|
||||
</DescriptionListItemTitle>
|
||||
<DescriptionListItemDescription>
|
||||
<Link to="https://opencollective.com/radarr">opencollective.com/radarr</Link>
|
||||
<Link to="https://www.reddit.com/r/Radarr/">/r/Radarr</Link>
|
||||
</DescriptionListItemDescription>
|
||||
|
||||
<DescriptionListItemTitle>
|
||||
{translate('Discord')}
|
||||
</DescriptionListItemTitle>
|
||||
<DescriptionListItemDescription>
|
||||
<Link to="https://radarr.video/discord">radarr.video/discord</Link>
|
||||
</DescriptionListItemDescription>
|
||||
|
||||
<DescriptionListItemTitle>
|
||||
|
||||
@@ -4,6 +4,7 @@ import PageContentBody from 'Components/Page/PageContentBody';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import AboutConnector from './About/AboutConnector';
|
||||
import DiskSpaceConnector from './DiskSpace/DiskSpaceConnector';
|
||||
import Donations from './Donations/Donations';
|
||||
import HealthConnector from './Health/HealthConnector';
|
||||
import MoreInfo from './MoreInfo/MoreInfo';
|
||||
|
||||
@@ -20,6 +21,7 @@ class Status extends Component {
|
||||
<DiskSpaceConnector />
|
||||
<AboutConnector />
|
||||
<MoreInfo />
|
||||
<Donations />
|
||||
</PageContentBody>
|
||||
</PageContent>
|
||||
);
|
||||
|
||||
17
frontend/src/System/Status/styles.css
Normal file
17
frontend/src/System/Status/styles.css
Normal file
@@ -0,0 +1,17 @@
|
||||
.logo {
|
||||
margin: auto;
|
||||
padding: 9px;
|
||||
}
|
||||
|
||||
.logoContainer {
|
||||
display: inline-block;
|
||||
margin: 1em;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
outline: none;
|
||||
border: solid 1px #e6e6e6;
|
||||
border-radius: 0.5em;
|
||||
background: #f8f8ff;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
import { icons, kinds } from 'Helpers/Props';
|
||||
import formatDate from 'Utilities/Date/formatDate';
|
||||
import formatDateTime from 'Utilities/Date/formatDateTime';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import UpdateChanges from './UpdateChanges';
|
||||
import styles from './Updates.css';
|
||||
@@ -32,6 +33,8 @@ class Updates extends Component {
|
||||
isDocker,
|
||||
updateMechanismMessage,
|
||||
shortDateFormat,
|
||||
longDateFormat,
|
||||
timeFormat,
|
||||
onInstallLatestPress
|
||||
} = this.props;
|
||||
|
||||
@@ -138,7 +141,12 @@ class Updates extends Component {
|
||||
<div className={styles.info}>
|
||||
<div className={styles.version}>{update.version}</div>
|
||||
<div className={styles.space}>—</div>
|
||||
<div className={styles.date}>{formatDate(update.releaseDate, shortDateFormat)}</div>
|
||||
<div
|
||||
className={styles.date}
|
||||
title={formatDateTime(update.releaseDate, longDateFormat, timeFormat)}
|
||||
>
|
||||
{formatDate(update.releaseDate, shortDateFormat)}
|
||||
</div>
|
||||
|
||||
{
|
||||
update.branch === 'master' ?
|
||||
@@ -155,11 +163,24 @@ class Updates extends Component {
|
||||
<Label
|
||||
className={styles.label}
|
||||
kind={kinds.SUCCESS}
|
||||
title={formatDateTime(update.installedOn, longDateFormat, timeFormat)}
|
||||
>
|
||||
{translate('CurrentlyInstalled')}
|
||||
</Label> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
update.version !== currentVersion && update.installedOn ?
|
||||
<Label
|
||||
className={styles.label}
|
||||
kind={kinds.INVERSE}
|
||||
title={formatDateTime(update.installedOn, longDateFormat, timeFormat)}
|
||||
>
|
||||
Previously Installed
|
||||
</Label> :
|
||||
null
|
||||
}
|
||||
</div>
|
||||
|
||||
{
|
||||
@@ -222,6 +243,8 @@ Updates.propTypes = {
|
||||
updateMechanism: PropTypes.string,
|
||||
updateMechanismMessage: PropTypes.string,
|
||||
shortDateFormat: PropTypes.string.isRequired,
|
||||
longDateFormat: PropTypes.string.isRequired,
|
||||
timeFormat: PropTypes.string.isRequired,
|
||||
onInstallLatestPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
|
||||
@@ -48,7 +48,9 @@ function createMapStateToProps() {
|
||||
isDocker: systemStatus.isDocker,
|
||||
updateMechanism: generalSettings.item.updateMechanism,
|
||||
updateMechanismMessage: status.packageUpdateMechanismMessage,
|
||||
shortDateFormat: uiSettings.shortDateFormat
|
||||
shortDateFormat: uiSettings.shortDateFormat,
|
||||
longDateFormat: uiSettings.longDateFormat,
|
||||
timeFormat: uiSettings.timeFormat
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
@@ -13,7 +13,7 @@ function getProgressBarKind(status, monitored, hasFile, isAvailable, queue = fal
|
||||
return kinds.DEFAULT;
|
||||
}
|
||||
|
||||
if (isAvailable) {
|
||||
if (isAvailable && monitored) {
|
||||
return kinds.DANGER;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,15 @@ function addApiKey(ajaxOptions) {
|
||||
ajaxOptions.headers['X-Api-Key'] = window.Radarr.apiKey;
|
||||
}
|
||||
|
||||
function addContentType(ajaxOptions) {
|
||||
if (
|
||||
ajaxOptions.contentType == null &&
|
||||
ajaxOptions.dataType === 'json' &&
|
||||
(ajaxOptions.method === 'PUT' || ajaxOptions.method === 'POST')) {
|
||||
ajaxOptions.contentType = 'application/json';
|
||||
}
|
||||
}
|
||||
|
||||
export default function createAjaxRequest(originalAjaxOptions) {
|
||||
const requestXHR = new window.XMLHttpRequest();
|
||||
let aborted = false;
|
||||
@@ -46,6 +55,7 @@ export default function createAjaxRequest(originalAjaxOptions) {
|
||||
moveBodyToQuery(ajaxOptions);
|
||||
addRootUrl(ajaxOptions);
|
||||
addApiKey(ajaxOptions);
|
||||
addContentType(ajaxOptions);
|
||||
}
|
||||
|
||||
const request = $.ajax({
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
sizes="16x16"
|
||||
href="/Content/Images/Icons/favicon-16x16.png"
|
||||
/>
|
||||
<link rel="manifest" href="/Content/Images/Icons/manifest.json" />
|
||||
<link rel="manifest" href="/Content/Images/Icons/manifest.json" crossorigin="use-credentials" />
|
||||
<link
|
||||
rel="mask-icon"
|
||||
href="/Content/Images/Icons/safari-pinned-tab.svg"
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
sizes="16x16"
|
||||
href="/Content/Images/Icons/favicon-16x16.png"
|
||||
/>
|
||||
<link rel="manifest" href="/Content/Images/Icons/manifest.json" />
|
||||
<link rel="manifest" href="/Content/Images/Icons/manifest.json" crossorigin="use-credentials" />
|
||||
<link
|
||||
rel="mask-icon"
|
||||
href="/Content/Images/Icons/safari-pinned-tab.svg"
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
"@fortawesome/free-regular-svg-icons": "5.15.0",
|
||||
"@fortawesome/free-solid-svg-icons": "5.15.0",
|
||||
"@fortawesome/react-fontawesome": "0.1.11",
|
||||
"@microsoft/signalr": "3.1.10",
|
||||
"@microsoft/signalr": "5.0.5",
|
||||
"@sentry/browser": "5.29.2",
|
||||
"@sentry/integrations": "5.29.2",
|
||||
"ansi-colors": "4.1.1",
|
||||
|
||||
@@ -62,9 +62,9 @@ Name: "{userstartup}\{#AppName}"; Filename: "{app}\Radarr.exe"; WorkingDir: "{ap
|
||||
Name: "{app}"; Type: filesandordirs
|
||||
|
||||
[Run]
|
||||
Filename: "{app}\Radarr.Console.exe"; StatusMsg: "Removing previous Windows Service"; Parameters: "/u"; Flags: runhidden waituntilterminated;
|
||||
Filename: "{app}\Radarr.Console.exe"; Description: "Enable Access from Other Devices"; StatusMsg: "Enabling Remote access"; Parameters: "/registerurl"; Flags: postinstall runascurrentuser runhidden waituntilterminated; Tasks: startupShortcut none;
|
||||
Filename: "{app}\Radarr.Console.exe"; StatusMsg: "Installing Windows Service"; Parameters: "/i"; Flags: runhidden waituntilterminated; Tasks: windowsService
|
||||
Filename: "{app}\Radarr.Console.exe"; StatusMsg: "Removing previous Windows Service"; Parameters: "/u /exitimmediately"; Flags: runhidden waituntilterminated;
|
||||
Filename: "{app}\Radarr.Console.exe"; Description: "Enable Access from Other Devices"; StatusMsg: "Enabling Remote access"; Parameters: "/registerurl /exitimmediately"; Flags: postinstall runascurrentuser runhidden waituntilterminated; Tasks: startupShortcut none;
|
||||
Filename: "{app}\Radarr.Console.exe"; StatusMsg: "Installing Windows Service"; Parameters: "/i /exitimmediately"; Flags: runhidden waituntilterminated; Tasks: windowsService
|
||||
Filename: "{app}\Radarr.exe"; Description: "Open Radarr Web UI"; Flags: postinstall skipifsilent nowait; Tasks: windowsService;
|
||||
Filename: "{app}\Radarr.exe"; Description: "Start Radarr"; Flags: postinstall skipifsilent nowait; Tasks: startupShortcut none;
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||
<RuntimeIdentifiers>win-x64;win-x86;osx-x64;linux-x64;linux-musl-x64;linux-arm;linux-arm64;linux-musl-arm64</RuntimeIdentifiers>
|
||||
<ExcludedRuntimeFrameworkPairs>win-x64:net462;win-x86:net462;osx-x64:net462;linux-arm:net462;linux-arm64:net462;linux-musl-x64:net462;linux-musl-arm64:net462</ExcludedRuntimeFrameworkPairs>
|
||||
<ExcludedRuntimeFrameworkPairs>win-x64:net472;win-x86:net472;osx-x64:net472;linux-arm:net472;linux-arm64:net472;linux-musl-x64:net472;linux-musl-arm64:net472</ExcludedRuntimeFrameworkPairs>
|
||||
|
||||
<RadarrRootDir>$(MSBuildThisFileDirectory)..\</RadarrRootDir>
|
||||
|
||||
@@ -24,6 +24,10 @@
|
||||
<RadarrProject Condition="$(MSBuildProjectName.StartsWith('Radarr'))">true</RadarrProject>
|
||||
<RadarrProject Condition="$(MSBuildProjectName.StartsWith('ServiceInstall'))">true</RadarrProject>
|
||||
<RadarrProject Condition="$(MSBuildProjectName.StartsWith('ServiceUninstall'))">true</RadarrProject>
|
||||
|
||||
<!-- A test project gets the test sdk packages automatically added -->
|
||||
<TestProject>false</TestProject>
|
||||
<TestProject Condition="$(MSBuildProjectName.EndsWith('.Test'))">true</TestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
@@ -39,6 +43,7 @@
|
||||
<OutputPath Condition="'$(RadarrOutputType)'=='Update'">$(RadarrRootDir)_output\Radarr.Update\</OutputPath>
|
||||
|
||||
<!-- Paths relative to project file for better readability -->
|
||||
<EnableBaseIntermediateOutputPathMismatchWarning>false</EnableBaseIntermediateOutputPathMismatchWarning>
|
||||
<BaseIntermediateOutputPath>$([MSBuild]::MakeRelative('$(MSBuildProjectDirectory)', '$(BaseIntermediateOutputPath)'))</BaseIntermediateOutputPath>
|
||||
<IntermediateOutputPath>$([MSBuild]::MakeRelative('$(MSBuildProjectDirectory)', '$(IntermediateOutputPath)'))</IntermediateOutputPath>
|
||||
<OutputPath>$([MSBuild]::MakeRelative('$(MSBuildProjectDirectory)', '$(OutputPath)'))</OutputPath>
|
||||
@@ -62,6 +67,7 @@
|
||||
<!-- Should be replaced by CI -->
|
||||
<AssemblyVersion>10.0.0.*</AssemblyVersion>
|
||||
<AssemblyConfiguration>$(Configuration)-dev</AssemblyConfiguration>
|
||||
<NeutralLanguage>en</NeutralLanguage>
|
||||
|
||||
<GenerateAssemblyFileVersionAttribute>false</GenerateAssemblyFileVersionAttribute>
|
||||
<GenerateAssemblyInformationalVersionAttribute>false</GenerateAssemblyInformationalVersionAttribute>
|
||||
@@ -82,6 +88,18 @@
|
||||
<RootNamespace Condition="'$(RadarrProject)'=='true'">$(MSBuildProjectName.Replace('Radarr','NzbDrone'))</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Standard testing packages -->
|
||||
<ItemGroup Condition="'$(TestProject)'=='true'">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
|
||||
<PackageReference Include="NUnit" Version="3.13.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||
<PackageReference Include="NunitXml.TestLogger" Version="2.1.62" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TestProject)'=='true' and '$(TargetFramework)'=='net5.0'">
|
||||
<PackageReference Include="coverlet.collector" Version="1.2.1" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Allow building net framework using mono -->
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0">
|
||||
@@ -90,13 +108,14 @@
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(RadarrProject)'=='true' and '$(EnableAnalyzers)'=='false'">
|
||||
<!-- FXCop Built into Net5 SDK now as NETAnalyzers, Enabled by default on net5 projects -->
|
||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Set up stylecop -->
|
||||
<ItemGroup Condition="'$(RadarrProject)'=='true' and '$(EnableAnalyzers)'!='false'">
|
||||
<!-- StyleCop analysis -->
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.3.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project>
|
||||
<!-- below net4.7.1 the new portable pdb format has no line numbers, pdb to mdb probably doesn't like it either -->
|
||||
<PropertyGroup Condition="'$(TargetFramework)' == 'net462'">
|
||||
<PropertyGroup Condition="'$(TargetFramework)' == 'net472'">
|
||||
<DebugType>full</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<clear />
|
||||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
|
||||
<add key="MyFeed" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/SQLite/nuget/v3/index.json" />
|
||||
<add key="FluentMigrator" value="https://www.myget.org/F/fluent-migrator/api/v3/index.json" />
|
||||
<add key="dotnet-bsd-crossbuild" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/dotnet-bsd-crossbuild/nuget/v3/index.json" />
|
||||
<add key="Mono.Posix.NETStandard" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/Mono.Posix.NETStandard/nuget/v3/index.json" />
|
||||
<add key="SQLite" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/SQLite/nuget/v3/index.json" />
|
||||
<add key="FluentMigrator" value="https://pkgs.dev.azure.com/fluentmigrator/fluentmigrator/_packaging/fluentmigrator/nuget/v3/index.json" />
|
||||
</packageSources>
|
||||
</configuration>
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net462;netcoreapp3.1</TargetFrameworks>
|
||||
<TargetFrameworks>net5.0;net472</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
|
||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.16.1" />
|
||||
<PackageReference Include="NunitXml.TestLogger" Version="2.1.41" />
|
||||
<PackageReference Include="NBuilder" Version="6.1.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1'">
|
||||
<PackageReference Include="coverlet.collector" Version="1.2.1" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NzbDrone.Core\Radarr.Core.csproj" />
|
||||
<ProjectReference Include="..\NzbDrone.Test.Common\Radarr.Test.Common.csproj" />
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Ical.Net;
|
||||
using Ical.Net.CalendarComponents;
|
||||
using Ical.Net.DataTypes;
|
||||
using Ical.Net.Serialization;
|
||||
using Nancy;
|
||||
using Nancy.Responses;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Movies;
|
||||
using NzbDrone.Core.Tags;
|
||||
|
||||
namespace NzbDrone.Api.Calendar
|
||||
{
|
||||
public class CalendarFeedModule : NzbDroneFeedModule
|
||||
{
|
||||
private readonly IMovieService _movieService;
|
||||
private readonly ITagService _tagService;
|
||||
|
||||
public CalendarFeedModule(IMovieService movieService, ITagService tagService)
|
||||
: base("calendar")
|
||||
{
|
||||
_movieService = movieService;
|
||||
_tagService = tagService;
|
||||
|
||||
Get("/NzbDrone.ics", options => GetCalendarFeed());
|
||||
Get("/Sonarr.ics", options => GetCalendarFeed());
|
||||
Get("/Radarr.ics", options => GetCalendarFeed());
|
||||
}
|
||||
|
||||
private object GetCalendarFeed()
|
||||
{
|
||||
var pastDays = 7;
|
||||
var futureDays = 28;
|
||||
var start = DateTime.Today.AddDays(-pastDays);
|
||||
var end = DateTime.Today.AddDays(futureDays);
|
||||
var unmonitored = false;
|
||||
|
||||
//var premiersOnly = false;
|
||||
var tags = new List<int>();
|
||||
|
||||
// TODO: Remove start/end parameters in v3, they don't work well for iCal
|
||||
var queryStart = Request.Query.Start;
|
||||
var queryEnd = Request.Query.End;
|
||||
var queryPastDays = Request.Query.PastDays;
|
||||
var queryFutureDays = Request.Query.FutureDays;
|
||||
var queryUnmonitored = Request.Query.Unmonitored;
|
||||
|
||||
// var queryPremiersOnly = Request.Query.PremiersOnly;
|
||||
var queryTags = Request.Query.Tags;
|
||||
|
||||
if (queryStart.HasValue)
|
||||
{
|
||||
start = DateTime.Parse(queryStart.Value);
|
||||
}
|
||||
|
||||
if (queryEnd.HasValue)
|
||||
{
|
||||
end = DateTime.Parse(queryEnd.Value);
|
||||
}
|
||||
|
||||
if (queryPastDays.HasValue)
|
||||
{
|
||||
pastDays = int.Parse(queryPastDays.Value);
|
||||
start = DateTime.Today.AddDays(-pastDays);
|
||||
}
|
||||
|
||||
if (queryFutureDays.HasValue)
|
||||
{
|
||||
futureDays = int.Parse(queryFutureDays.Value);
|
||||
end = DateTime.Today.AddDays(futureDays);
|
||||
}
|
||||
|
||||
if (queryUnmonitored.HasValue)
|
||||
{
|
||||
unmonitored = bool.Parse(queryUnmonitored.Value);
|
||||
}
|
||||
|
||||
//if (queryPremiersOnly.HasValue)
|
||||
//{
|
||||
// premiersOnly = bool.Parse(queryPremiersOnly.Value);
|
||||
//}
|
||||
if (queryTags.HasValue)
|
||||
{
|
||||
var tagInput = (string)queryTags.Value.ToString();
|
||||
tags.AddRange(tagInput.Split(',').Select(_tagService.GetTag).Select(t => t.Id));
|
||||
}
|
||||
|
||||
var movies = _movieService.GetMoviesBetweenDates(start, end, unmonitored);
|
||||
var calendar = new Ical.Net.Calendar
|
||||
{
|
||||
ProductId = "-//radarr.video//Radarr//EN"
|
||||
};
|
||||
|
||||
var calendarName = "Radarr Movies Calendar";
|
||||
calendar.AddProperty(new CalendarProperty("NAME", calendarName));
|
||||
calendar.AddProperty(new CalendarProperty("X-WR-CALNAME", calendarName));
|
||||
|
||||
foreach (var movie in movies.OrderBy(v => v.Added))
|
||||
{
|
||||
if (tags.Any() && tags.None(movie.Tags.Contains))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
CreateEvent(calendar, movie, true);
|
||||
CreateEvent(calendar, movie, false);
|
||||
}
|
||||
|
||||
var serializer = (IStringSerializer)new SerializerFactory().Build(calendar.GetType(), new SerializationContext());
|
||||
var icalendar = serializer.SerializeToString(calendar);
|
||||
|
||||
return new TextResponse(icalendar, "text/calendar");
|
||||
}
|
||||
|
||||
private void CreateEvent(Ical.Net.Calendar calendar, Movie movie, bool cinemasRelease)
|
||||
{
|
||||
var date = cinemasRelease ? movie.InCinemas : movie.PhysicalRelease;
|
||||
if (!date.HasValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var occurrence = calendar.Create<CalendarEvent>();
|
||||
occurrence.Uid = "NzbDrone_movie_" + movie.Id + (cinemasRelease ? "_cinemas" : "_physical");
|
||||
occurrence.Status = movie.Status == MovieStatusType.Announced ? EventStatus.Tentative : EventStatus.Confirmed;
|
||||
|
||||
occurrence.Start = new CalDateTime(date.Value);
|
||||
occurrence.End = occurrence.Start;
|
||||
occurrence.IsAllDay = true;
|
||||
|
||||
occurrence.Description = movie.Overview;
|
||||
occurrence.Categories = new List<string>() { movie.Studio };
|
||||
|
||||
var physicalText = "(Physical Release)";
|
||||
occurrence.Summary = $"{movie.Title} " + (cinemasRelease ? "(Theatrical Release)" : physicalText);
|
||||
}
|
||||
}
|
||||
}
|
||||
38
src/NzbDrone.Api/Calendar/LegacyCalendarFeedModule.cs
Normal file
38
src/NzbDrone.Api/Calendar/LegacyCalendarFeedModule.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System.Text;
|
||||
using Nancy;
|
||||
using Nancy.Responses;
|
||||
|
||||
namespace NzbDrone.Api.Calendar
|
||||
{
|
||||
public class LegacyCalendarFeedModule : NzbDroneFeedModule
|
||||
{
|
||||
public LegacyCalendarFeedModule()
|
||||
: base("calendar")
|
||||
{
|
||||
Get("/NzbDrone.ics", options => GetCalendarFeed());
|
||||
Get("/Sonarr.ics", options => GetCalendarFeed());
|
||||
Get("/Radarr.ics", options => GetCalendarFeed());
|
||||
}
|
||||
|
||||
private object GetCalendarFeed()
|
||||
{
|
||||
string queryString = ConvertQueryParams(Request.Query);
|
||||
var url = string.Format("/feed/v3/calendar/Radarr.ics?{0}", queryString);
|
||||
return Response.AsRedirect(url, RedirectResponse.RedirectType.Permanent);
|
||||
}
|
||||
|
||||
private string ConvertQueryParams(DynamicDictionary query)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
foreach (var key in query)
|
||||
{
|
||||
var value = query[key];
|
||||
|
||||
sb.AppendFormat("&{0}={1}", key, value);
|
||||
}
|
||||
|
||||
return sb.ToString().Trim('&');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
using Radarr.Http.REST;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace NzbDrone.Api.DownloadClient
|
||||
{
|
||||
public class DownloadClientResource : ProviderResource
|
||||
public class DownloadClientResource : ProviderResource<DownloadClientResource>
|
||||
{
|
||||
public bool Enable { get; set; }
|
||||
public DownloadProtocol Protocol { get; set; }
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace NzbDrone.Api.Indexers
|
||||
{
|
||||
public class IndexerResource : ProviderResource
|
||||
public class IndexerResource : ProviderResource<IndexerResource>
|
||||
{
|
||||
public bool EnableRss { get; set; }
|
||||
public bool EnableSearch { get; set; }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Languages;
|
||||
@@ -27,13 +27,9 @@ namespace NzbDrone.Api.Indexers
|
||||
public string ReleaseHash { get; set; }
|
||||
public string Edition { get; set; }
|
||||
public string Title { get; set; }
|
||||
public bool FullSeason { get; set; }
|
||||
public int SeasonNumber { get; set; }
|
||||
public List<Language> Languages { get; set; }
|
||||
public int Year { get; set; }
|
||||
public string MovieTitle { get; set; }
|
||||
public int[] EpisodeNumbers { get; set; }
|
||||
public int[] AbsoluteEpisodeNumbers { get; set; }
|
||||
public bool Approved { get; set; }
|
||||
public bool TemporarilyRejected { get; set; }
|
||||
public bool Rejected { get; set; }
|
||||
@@ -111,8 +107,6 @@ namespace NzbDrone.Api.Indexers
|
||||
Approved = model.Approved,
|
||||
TemporarilyRejected = model.TemporarilyRejected,
|
||||
Rejected = model.Rejected,
|
||||
TvdbId = releaseInfo.TvdbId,
|
||||
TvRageId = releaseInfo.TvRageId,
|
||||
Rejections = model.Rejections.Select(r => r.Reason).ToList(),
|
||||
PublishDate = releaseInfo.PublishDate,
|
||||
CommentUrl = releaseInfo.CommentUrl,
|
||||
@@ -163,8 +157,6 @@ namespace NzbDrone.Api.Indexers
|
||||
model.IndexerId = resource.IndexerId;
|
||||
model.Indexer = resource.Indexer;
|
||||
model.DownloadProtocol = resource.DownloadProtocol;
|
||||
model.TvdbId = resource.TvdbId;
|
||||
model.TvRageId = resource.TvRageId;
|
||||
model.PublishDate = resource.PublishDate;
|
||||
|
||||
return model;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user