mirror of
https://github.com/Radarr/Radarr.git
synced 2026-03-05 13:21:25 -05:00
Compare commits
471 Commits
v3.0.2.436
...
v3.2.1.507
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bdc7733faf | ||
|
|
7f4be53db0 | ||
|
|
27d998d6f2 | ||
|
|
5eb593f453 | ||
|
|
4652db0583 | ||
|
|
35d43480bf | ||
|
|
3e7c136a7f | ||
|
|
05f9f6b413 | ||
|
|
67f6eb544a | ||
|
|
3c11e934a8 | ||
|
|
627a39b8fc | ||
|
|
95c7b96dff | ||
|
|
dadd59fc3a | ||
|
|
e67d3d3666 | ||
|
|
f4718243ed | ||
|
|
fcec787eb6 | ||
|
|
5fe8f65d64 | ||
|
|
c2a21cd238 | ||
|
|
a31ca4e80b | ||
|
|
db14ac4605 | ||
|
|
5f229b78be | ||
|
|
543f2e7ddc | ||
|
|
d6b7ab6260 | ||
|
|
d7ab9292fb | ||
|
|
4300d8d8c6 | ||
|
|
446b2ffff9 | ||
|
|
695720b552 | ||
|
|
c47934c5ca | ||
|
|
9938737cd7 | ||
|
|
58326f05e0 | ||
|
|
04ad5ec9c0 | ||
|
|
2c008384dd | ||
|
|
a9b605c872 | ||
|
|
cd9b469823 | ||
|
|
df4bfa501c | ||
|
|
194a1e5154 | ||
|
|
e53b2bb83c | ||
|
|
10772c09ef | ||
|
|
f9ed15409a | ||
|
|
f75ab93458 | ||
|
|
7755a8bd3b | ||
|
|
017c7998be | ||
|
|
f40ddfef10 | ||
|
|
80049909eb | ||
|
|
fc22264f89 | ||
|
|
aba2e10b5c | ||
|
|
4b6874d551 | ||
|
|
58934a30ce | ||
|
|
33a960f325 | ||
|
|
8560ff43fe | ||
|
|
c88a47b275 | ||
|
|
67fe9101d9 | ||
|
|
af99c78352 | ||
|
|
df3253f55c | ||
|
|
b9abc1be11 | ||
|
|
5c0ee04271 | ||
|
|
5e2cd3798b | ||
|
|
83041b1d37 | ||
|
|
9f6c48191b | ||
|
|
5696fa2efe | ||
|
|
d38311b717 | ||
|
|
aa522066ee | ||
|
|
d2ba70c4d7 | ||
|
|
ca2e62492d | ||
|
|
02bcb4d865 | ||
|
|
36962f176f | ||
|
|
0a2afe692f | ||
|
|
5140ee8f2e | ||
|
|
e47ceae0c5 | ||
|
|
906f8c1049 | ||
|
|
27e871656e | ||
|
|
1ff147c541 | ||
|
|
7028bfb082 | ||
|
|
efdb9c20d4 | ||
|
|
9bf872c9fa | ||
|
|
feaeda9186 | ||
|
|
e9cf9d05f5 | ||
|
|
a1211f54de | ||
|
|
c7b5e6204c | ||
|
|
788404ee27 | ||
|
|
8ef4429b9e | ||
|
|
7c5fc1e4b0 | ||
|
|
c66f7abea5 | ||
|
|
ce6f52552a | ||
|
|
7807e2e13a | ||
|
|
d5aa73fe8f | ||
|
|
cb5bf86d8e | ||
|
|
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. -->
|
||||
|
||||
2
.github/config.yml
vendored
2
.github/config.yml
vendored
@@ -1,2 +0,0 @@
|
||||
todo:
|
||||
keyword: "TODO"
|
||||
1
.github/reaction.yml
vendored
1
.github/reaction.yml
vendored
@@ -1 +0,0 @@
|
||||
|
||||
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
|
||||
|
||||
13
.github/support.yml
vendored
13
.github/support.yml
vendored
@@ -1,13 +0,0 @@
|
||||
# Configuration for support-requests - https://github.com/dessant/support-requests
|
||||
|
||||
# Label used to mark issues as support requests
|
||||
supportLabel: support
|
||||
# Comment to post on issues marked as support requests. Add a link
|
||||
# to a support page, or set to `false` to disable
|
||||
supportComment: >
|
||||
We use the issue tracker exclusively for bug reports and feature requests.
|
||||
However, this issue appears to be a support request. Please hop over onto our [Discord](https://discord.gg/r5wJPt9) or [Subreddit](https://reddit.com/r/radarr)
|
||||
# Whether to close issues marked as support requests
|
||||
close: true
|
||||
# Whether to lock issues marked as support requests
|
||||
lock: false
|
||||
21
.github/workflows/support.yml
vendored
Normal file
21
.github/workflows/support.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
name: 'Support requests'
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [labeled, unlabeled, reopened]
|
||||
|
||||
jobs:
|
||||
support:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/support-requests@v2
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
support-label: 'Type: Support'
|
||||
issue-comment: >
|
||||
:wave: @{issue-author}, we use the issue tracker exclusively
|
||||
for bug reports and feature requests. However, this issue appears
|
||||
to be a support request. Please hop over onto our [Discord](https://radarr.video/discord)
|
||||
or [Subreddit](https://reddit.com/r/radarr)
|
||||
close-issue: true
|
||||
lock-issue: false
|
||||
@@ -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.2.1'
|
||||
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:
|
||||
@@ -23,7 +23,12 @@ trigger:
|
||||
- master
|
||||
|
||||
pr:
|
||||
- develop
|
||||
branches:
|
||||
include:
|
||||
- develop
|
||||
paths:
|
||||
exclude:
|
||||
- src/NzbDrone.Core/Localization/Core
|
||||
|
||||
stages:
|
||||
- stage: Setup
|
||||
@@ -59,18 +64,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 +87,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 +111,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 +158,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 +207,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 +242,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 +254,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 +276,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 +284,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 +292,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 +300,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 +308,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 +316,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 +324,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 +386,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 +422,7 @@ stages:
|
||||
displayName: 'Install .net core'
|
||||
inputs:
|
||||
version: $(dotnetVersion)
|
||||
condition: ne(variables['poolName'], 'FreeBSD')
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download Test Artifact
|
||||
inputs:
|
||||
@@ -393,7 +435,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 +473,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 +550,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 +613,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: true
|
||||
displayName: Publish Test Results
|
||||
|
||||
- job: Integration_Docker
|
||||
displayName: Integration Docker
|
||||
dependsOn: Prepare
|
||||
@@ -580,23 +668,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 +743,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 +845,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 +891,7 @@ stages:
|
||||
|
||||
variables:
|
||||
disable.coverage.autogenerate: 'true'
|
||||
EnableAnalyzers: 'false'
|
||||
|
||||
pool:
|
||||
vmImage: windows-2019
|
||||
@@ -831,8 +920,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')
|
||||
|
||||
80
build.sh
80
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'
|
||||
@@ -75,11 +87,11 @@ YarnInstall()
|
||||
ProgressEnd 'yarn install'
|
||||
}
|
||||
|
||||
RunGulp()
|
||||
RunWebpack()
|
||||
{
|
||||
ProgressStart 'Running gulp'
|
||||
yarn run build --production
|
||||
ProgressEnd 'Running gulp'
|
||||
ProgressStart 'Running webpack'
|
||||
yarn run build --env production
|
||||
ProgressEnd 'Running webpack'
|
||||
}
|
||||
|
||||
PackageFiles()
|
||||
@@ -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
|
||||
@@ -324,7 +350,7 @@ fi
|
||||
if [ "$FRONTEND" = "YES" ];
|
||||
then
|
||||
YarnInstall
|
||||
RunGulp
|
||||
RunWebpack
|
||||
fi
|
||||
|
||||
if [ "$LINT" = "YES" ];
|
||||
@@ -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
|
||||
|
||||
@@ -6,8 +6,10 @@ const dirs = fs
|
||||
.map((dirent) => dirent.name)
|
||||
.join('|');
|
||||
|
||||
const frontendFolder = __dirname;
|
||||
|
||||
module.exports = {
|
||||
parser: 'babel-eslint',
|
||||
parser: '@babel/eslint-parser',
|
||||
|
||||
env: {
|
||||
browser: true,
|
||||
@@ -25,6 +27,9 @@ module.exports = {
|
||||
parserOptions: {
|
||||
ecmaVersion: 6,
|
||||
sourceType: 'module',
|
||||
babelOptions: {
|
||||
configFile: `${frontendFolder}/babel.config.js`
|
||||
},
|
||||
ecmaFeatures: {
|
||||
modules: true,
|
||||
impliedStrict: true
|
||||
@@ -271,7 +276,7 @@ module.exports = {
|
||||
|
||||
// ImportSort
|
||||
|
||||
'simple-import-sort/sort': 'error',
|
||||
'simple-import-sort/imports': 'error',
|
||||
'import/newline-after-import': 'error',
|
||||
|
||||
// React
|
||||
@@ -309,7 +314,7 @@ module.exports = {
|
||||
{
|
||||
files: ['*.js'],
|
||||
rules: {
|
||||
'simple-import-sort/sort': [
|
||||
'simple-import-sort/imports': [
|
||||
'error',
|
||||
{
|
||||
groups: [
|
||||
|
||||
269
frontend/build/webpack.config.js
Normal file
269
frontend/build/webpack.config.js
Normal file
@@ -0,0 +1,269 @@
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const CopyPlugin = require('copy-webpack-plugin');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const LiveReloadPlugin = require('webpack-livereload-plugin');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const TerserPlugin = require('terser-webpack-plugin');
|
||||
|
||||
module.exports = (env) => {
|
||||
const uiFolder = 'UI';
|
||||
const frontendFolder = path.join(__dirname, '..');
|
||||
const srcFolder = path.join(frontendFolder, 'src');
|
||||
const isProduction = !!env.production;
|
||||
const isProfiling = isProduction && !!env.profile;
|
||||
const inlineWebWorkers = 'no-fallback';
|
||||
|
||||
const distFolder = path.resolve(frontendFolder, '..', '_output', uiFolder);
|
||||
|
||||
console.log('Source Folder:', srcFolder);
|
||||
console.log('Output Folder:', distFolder);
|
||||
console.log('isProduction:', isProduction);
|
||||
console.log('isProfiling:', isProfiling);
|
||||
|
||||
const config = {
|
||||
mode: isProduction ? 'production' : 'development',
|
||||
devtool: 'source-map',
|
||||
|
||||
stats: {
|
||||
children: false
|
||||
},
|
||||
|
||||
watchOptions: {
|
||||
ignored: /node_modules/
|
||||
},
|
||||
|
||||
entry: {
|
||||
index: 'index.js'
|
||||
},
|
||||
|
||||
resolve: {
|
||||
modules: [
|
||||
srcFolder,
|
||||
path.join(srcFolder, 'Shims'),
|
||||
'node_modules'
|
||||
],
|
||||
alias: {
|
||||
jquery: 'jquery/src/jquery'
|
||||
},
|
||||
fallback: {
|
||||
buffer: false,
|
||||
http: false,
|
||||
https: false,
|
||||
url: false,
|
||||
util: false,
|
||||
net: false
|
||||
}
|
||||
},
|
||||
|
||||
output: {
|
||||
path: distFolder,
|
||||
publicPath: '/',
|
||||
filename: '[name].js',
|
||||
sourceMapFilename: '[file].map'
|
||||
},
|
||||
|
||||
optimization: {
|
||||
moduleIds: 'deterministic',
|
||||
chunkIds: 'named',
|
||||
splitChunks: {
|
||||
chunks: 'initial',
|
||||
name: 'vendors'
|
||||
}
|
||||
},
|
||||
|
||||
performance: {
|
||||
hints: false
|
||||
},
|
||||
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
__DEV__: !isProduction,
|
||||
'process.env.NODE_ENV': isProduction ? JSON.stringify('production') : JSON.stringify('development')
|
||||
}),
|
||||
|
||||
new MiniCssExtractPlugin({
|
||||
filename: 'Content/styles.css'
|
||||
}),
|
||||
|
||||
new HtmlWebpackPlugin({
|
||||
template: 'frontend/src/index.html',
|
||||
filename: 'index.html',
|
||||
publicPath: '/'
|
||||
}),
|
||||
|
||||
new CopyPlugin({
|
||||
patterns: [
|
||||
// HTML
|
||||
{
|
||||
from: 'frontend/src/*.html',
|
||||
to: path.join(distFolder, '[name][ext]'),
|
||||
globOptions: {
|
||||
ignore: ['**/index.html']
|
||||
}
|
||||
},
|
||||
|
||||
// Fonts
|
||||
{
|
||||
from: 'frontend/src/Content/Fonts/*.*',
|
||||
to: path.join(distFolder, 'Content/Fonts', '[name][ext]')
|
||||
},
|
||||
|
||||
// Icon Images
|
||||
{
|
||||
from: 'frontend/src/Content/Images/Icons/*.*',
|
||||
to: path.join(distFolder, 'Content/Images/Icons', '[name][ext]')
|
||||
},
|
||||
|
||||
// Images
|
||||
{
|
||||
from: 'frontend/src/Content/Images/*.*',
|
||||
to: path.join(distFolder, 'Content/Images', '[name][ext]')
|
||||
},
|
||||
|
||||
// Robots
|
||||
{
|
||||
from: 'frontend/src/Content/robots.txt',
|
||||
to: path.join(distFolder, 'Content', '[name][ext]')
|
||||
}
|
||||
]
|
||||
}),
|
||||
|
||||
new LiveReloadPlugin()
|
||||
],
|
||||
|
||||
resolveLoader: {
|
||||
modules: [
|
||||
'node_modules',
|
||||
'frontend/build/webpack/'
|
||||
]
|
||||
},
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.worker\.js$/,
|
||||
use: {
|
||||
loader: 'worker-loader',
|
||||
options: {
|
||||
filename: '[name].js',
|
||||
inline: inlineWebWorkers
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.js?$/,
|
||||
exclude: /(node_modules|JsLibraries)/,
|
||||
use: [
|
||||
{
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
configFile: `${frontendFolder}/babel.config.js`,
|
||||
envName: isProduction ? 'production' : 'development',
|
||||
presets: [
|
||||
[
|
||||
'@babel/preset-env',
|
||||
{
|
||||
modules: false,
|
||||
loose: true,
|
||||
debug: false,
|
||||
useBuiltIns: 'entry',
|
||||
corejs: 3
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// CSS Modules
|
||||
{
|
||||
test: /\.css$/,
|
||||
exclude: /(node_modules|globals.css)/,
|
||||
use: [
|
||||
{ loader: MiniCssExtractPlugin.loader },
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
importLoaders: 1,
|
||||
modules: {
|
||||
localIdentName: '[name]/[local]/[hash:base64:5]'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
loader: 'postcss-loader',
|
||||
options: {
|
||||
postcssOptions: {
|
||||
config: 'frontend/postcss.config.js'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// Global styles
|
||||
{
|
||||
test: /\.css$/,
|
||||
include: /(node_modules|globals.css)/,
|
||||
use: [
|
||||
'style-loader',
|
||||
{
|
||||
loader: 'css-loader'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// Fonts
|
||||
{
|
||||
test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 10240,
|
||||
mimetype: 'application/font-woff',
|
||||
emitFile: false,
|
||||
name: 'Content/Fonts/[name].[ext]'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
test: /\.(ttf|eot|eot?#iefix|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
emitFile: false,
|
||||
name: 'Content/Fonts/[name].[ext]'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
if (isProfiling) {
|
||||
config.resolve.alias['react-dom$'] = 'react-dom/profiling';
|
||||
config.resolve.alias['scheduler/tracing'] = 'scheduler/tracing-profiling';
|
||||
|
||||
config.optimization.minimizer = [
|
||||
new TerserPlugin({
|
||||
cache: true,
|
||||
parallel: true,
|
||||
sourceMap: true, // Must be set to true if using source-maps in production
|
||||
terserOptions: {
|
||||
mangle: false,
|
||||
keep_classnames: true,
|
||||
keep_fnames: true
|
||||
}
|
||||
})
|
||||
];
|
||||
}
|
||||
|
||||
return config;
|
||||
};
|
||||
@@ -1,18 +0,0 @@
|
||||
const gulp = require('gulp');
|
||||
|
||||
require('./clean');
|
||||
require('./copy');
|
||||
require('./webpack');
|
||||
|
||||
gulp.task('build',
|
||||
gulp.series('clean',
|
||||
gulp.parallel(
|
||||
'webpack',
|
||||
'copyHtml',
|
||||
'copyFonts',
|
||||
'copyImages',
|
||||
'copyRobots'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
const gulp = require('gulp');
|
||||
const del = require('del');
|
||||
|
||||
const paths = require('./helpers/paths');
|
||||
|
||||
gulp.task('clean', () => {
|
||||
return del([paths.dest.root]);
|
||||
});
|
||||
@@ -1,42 +0,0 @@
|
||||
const path = require('path');
|
||||
const gulp = require('gulp');
|
||||
const print = require('gulp-print').default;
|
||||
const cache = require('gulp-cached');
|
||||
const livereload = require('gulp-livereload');
|
||||
const paths = require('./helpers/paths.js');
|
||||
|
||||
gulp.task('copyHtml', () => {
|
||||
return gulp.src(paths.src.html, { base: paths.src.root })
|
||||
.pipe(cache('copyHtml'))
|
||||
.pipe(print())
|
||||
.pipe(gulp.dest(paths.dest.root))
|
||||
.pipe(livereload());
|
||||
});
|
||||
|
||||
gulp.task('copyFonts', () => {
|
||||
return gulp.src(
|
||||
path.join(paths.src.fonts, '**', '*.*'), { base: paths.src.root }
|
||||
)
|
||||
.pipe(cache('copyFonts'))
|
||||
.pipe(print())
|
||||
.pipe(gulp.dest(paths.dest.root))
|
||||
.pipe(livereload());
|
||||
});
|
||||
|
||||
gulp.task('copyImages', () => {
|
||||
return gulp.src(
|
||||
path.join(paths.src.images, '**', '*.*'), { base: paths.src.root }
|
||||
)
|
||||
.pipe(cache('copyImages'))
|
||||
.pipe(print())
|
||||
.pipe(gulp.dest(paths.dest.root))
|
||||
.pipe(livereload());
|
||||
});
|
||||
|
||||
gulp.task('copyRobots', () => {
|
||||
return gulp.src(paths.src.robots, { base: paths.src.root })
|
||||
.pipe(cache('copyRobots'))
|
||||
.pipe(print())
|
||||
.pipe(gulp.dest(paths.dest.root))
|
||||
.pipe(livereload());
|
||||
});
|
||||
@@ -1,5 +0,0 @@
|
||||
require('./build.js');
|
||||
require('./clean.js');
|
||||
require('./copy.js');
|
||||
require('./watch.js');
|
||||
require('./webpack.js');
|
||||
@@ -1,6 +0,0 @@
|
||||
const colors = require('ansi-colors');
|
||||
|
||||
module.exports = function errorHandler(error) {
|
||||
console.log(colors.red(`Error (${error.plugin}): ${error.message}`));
|
||||
this.emit('end');
|
||||
};
|
||||
@@ -1,24 +0,0 @@
|
||||
const root = './frontend/src';
|
||||
|
||||
const paths = {
|
||||
src: {
|
||||
root,
|
||||
html: `${root}/*.html`,
|
||||
scripts: `${root}/**/*.js`,
|
||||
content: `${root}/Content/`,
|
||||
fonts: `${root}/Content/Fonts/`,
|
||||
images: `${root}/Content/Images/`,
|
||||
robots: `${root}/Content/robots.txt`,
|
||||
exclude: {
|
||||
libs: `!${root}/JsLibraries/**`
|
||||
}
|
||||
},
|
||||
dest: {
|
||||
root: './_output/UI/',
|
||||
content: './_output/UI/Content/',
|
||||
fonts: './_output/UI/Content/Fonts/',
|
||||
images: './_output/UI/Content/Images/'
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = paths;
|
||||
@@ -1,19 +0,0 @@
|
||||
const gulp = require('gulp');
|
||||
const livereload = require('gulp-livereload');
|
||||
const gulpWatch = require('gulp-watch');
|
||||
const paths = require('./helpers/paths.js');
|
||||
|
||||
require('./copy.js');
|
||||
require('./webpack.js');
|
||||
|
||||
function watch() {
|
||||
livereload.listen({ start: true });
|
||||
|
||||
gulp.task('webpackWatch')();
|
||||
gulpWatch(paths.src.html, gulp.series('copyHtml'));
|
||||
gulpWatch(`${paths.src.fonts}**/*.*`, gulp.series('copyFonts'));
|
||||
gulpWatch(`${paths.src.images}**/*.*`, gulp.series('copyImages'));
|
||||
gulpWatch(paths.src.robots, gulp.series('copyRobots'));
|
||||
}
|
||||
|
||||
gulp.task('watch', gulp.series('build', watch));
|
||||
@@ -1,271 +0,0 @@
|
||||
const gulp = require('gulp');
|
||||
const webpackStream = require('webpack-stream');
|
||||
const livereload = require('gulp-livereload');
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const errorHandler = require('./helpers/errorHandler');
|
||||
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const HtmlWebpackPluginHtmlTags = require('html-webpack-plugin/lib/html-tags');
|
||||
const TerserPlugin = require('terser-webpack-plugin');
|
||||
|
||||
const uiFolder = 'UI';
|
||||
const frontendFolder = path.join(__dirname, '..');
|
||||
const srcFolder = path.join(frontendFolder, 'src');
|
||||
const isProduction = process.argv.indexOf('--production') > -1;
|
||||
const isProfiling = isProduction && process.argv.indexOf('--profile') > -1;
|
||||
const inlineWebWorkers = 'no-fallback';
|
||||
|
||||
const distFolder = path.resolve(frontendFolder, '..', '_output', uiFolder);
|
||||
|
||||
console.log('Source Folder:', srcFolder);
|
||||
console.log('Output Folder:', distFolder);
|
||||
console.log('isProduction:', isProduction);
|
||||
console.log('isProfiling:', isProfiling);
|
||||
|
||||
const cssVarsFiles = [
|
||||
'../src/Styles/Variables/colors',
|
||||
'../src/Styles/Variables/dimensions',
|
||||
'../src/Styles/Variables/fonts',
|
||||
'../src/Styles/Variables/animations',
|
||||
'../src/Styles/Variables/zIndexes'
|
||||
].map(require.resolve);
|
||||
|
||||
// Override the way HtmlWebpackPlugin injects the scripts
|
||||
// TODO: Find a better way to get these paths without
|
||||
HtmlWebpackPlugin.prototype.injectAssetsIntoHtml = function(html, assets, assetTags) {
|
||||
const head = assetTags.headTags.map((v) => {
|
||||
const href = v.attributes.href
|
||||
.replace('\\', '/')
|
||||
.replace('%5C', '/');
|
||||
|
||||
v.attributes = { rel: 'stylesheet', type: 'text/css', href: `/${href}` };
|
||||
return HtmlWebpackPluginHtmlTags.htmlTagObjectToString(v, this.options.xhtml);
|
||||
});
|
||||
const body = assetTags.bodyTags.map((v) => {
|
||||
v.attributes = { src: `/${v.attributes.src}` };
|
||||
return HtmlWebpackPluginHtmlTags.htmlTagObjectToString(v, this.options.xhtml);
|
||||
});
|
||||
|
||||
return html
|
||||
.replace('<!-- webpack bundles head -->', head.join('\r\n '))
|
||||
.replace('<!-- webpack bundles body -->', body.join('\r\n '));
|
||||
};
|
||||
|
||||
const plugins = [
|
||||
new OptimizeCssAssetsPlugin({}),
|
||||
|
||||
new webpack.DefinePlugin({
|
||||
__DEV__: !isProduction,
|
||||
'process.env.NODE_ENV': isProduction ? JSON.stringify('production') : JSON.stringify('development')
|
||||
}),
|
||||
|
||||
new MiniCssExtractPlugin({
|
||||
filename: path.join('Content', 'styles.css')
|
||||
}),
|
||||
|
||||
new HtmlWebpackPlugin({
|
||||
template: 'frontend/src/index.html',
|
||||
filename: 'index.html'
|
||||
})
|
||||
];
|
||||
|
||||
const config = {
|
||||
mode: isProduction ? 'production' : 'development',
|
||||
devtool: '#source-map',
|
||||
|
||||
stats: {
|
||||
children: false
|
||||
},
|
||||
|
||||
watchOptions: {
|
||||
ignored: /node_modules/
|
||||
},
|
||||
|
||||
entry: {
|
||||
index: 'index.js'
|
||||
},
|
||||
|
||||
resolve: {
|
||||
modules: [
|
||||
srcFolder,
|
||||
path.join(srcFolder, 'Shims'),
|
||||
'node_modules'
|
||||
],
|
||||
alias: {
|
||||
jquery: 'jquery/src/jquery'
|
||||
}
|
||||
},
|
||||
|
||||
output: {
|
||||
path: distFolder,
|
||||
filename: '[name].js',
|
||||
sourceMapFilename: '[file].map'
|
||||
},
|
||||
|
||||
optimization: {
|
||||
chunkIds: 'named',
|
||||
splitChunks: {
|
||||
chunks: 'initial'
|
||||
}
|
||||
},
|
||||
|
||||
performance: {
|
||||
hints: false
|
||||
},
|
||||
|
||||
plugins,
|
||||
|
||||
resolveLoader: {
|
||||
modules: [
|
||||
'node_modules',
|
||||
'frontend/gulp/webpack/'
|
||||
]
|
||||
},
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.worker\.js$/,
|
||||
use: {
|
||||
loader: 'worker-loader',
|
||||
options: {
|
||||
filename: '[name].js',
|
||||
inline: inlineWebWorkers
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.js?$/,
|
||||
exclude: /(node_modules|JsLibraries)/,
|
||||
use: [
|
||||
{
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
configFile: `${frontendFolder}/babel.config.js`,
|
||||
envName: isProduction ? 'production' : 'development',
|
||||
presets: [
|
||||
[
|
||||
'@babel/preset-env',
|
||||
{
|
||||
modules: false,
|
||||
loose: true,
|
||||
debug: false,
|
||||
useBuiltIns: 'entry',
|
||||
corejs: 3
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// CSS Modules
|
||||
{
|
||||
test: /\.css$/,
|
||||
exclude: /(node_modules|globals.css)/,
|
||||
use: [
|
||||
{ loader: MiniCssExtractPlugin.loader },
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
importLoaders: 1,
|
||||
modules: {
|
||||
localIdentName: '[name]/[local]/[hash:base64:5]'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
loader: 'postcss-loader',
|
||||
options: {
|
||||
ident: 'postcss',
|
||||
config: {
|
||||
ctx: {
|
||||
cssVarsFiles
|
||||
},
|
||||
path: 'frontend/postcss.config.js'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// Global styles
|
||||
{
|
||||
test: /\.css$/,
|
||||
include: /(node_modules|globals.css)/,
|
||||
use: [
|
||||
'style-loader',
|
||||
{
|
||||
loader: 'css-loader'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// Fonts
|
||||
{
|
||||
test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 10240,
|
||||
mimetype: 'application/font-woff',
|
||||
emitFile: false,
|
||||
name: 'Content/Fonts/[name].[ext]'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
test: /\.(ttf|eot|eot?#iefix|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
emitFile: false,
|
||||
name: 'Content/Fonts/[name].[ext]'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
if (isProfiling) {
|
||||
config.resolve.alias['react-dom$'] = 'react-dom/profiling';
|
||||
config.resolve.alias['scheduler/tracing'] = 'scheduler/tracing-profiling';
|
||||
|
||||
config.optimization.minimizer = [
|
||||
new TerserPlugin({
|
||||
cache: true,
|
||||
parallel: true,
|
||||
sourceMap: true, // Must be set to true if using source-maps in production
|
||||
terserOptions: {
|
||||
mangle: false,
|
||||
keep_classnames: true,
|
||||
keep_fnames: true
|
||||
}
|
||||
})
|
||||
];
|
||||
}
|
||||
|
||||
gulp.task('webpack', () => {
|
||||
return webpackStream(config)
|
||||
.pipe(gulp.dest('_output/UI'));
|
||||
});
|
||||
|
||||
gulp.task('webpackWatch', () => {
|
||||
config.watch = true;
|
||||
|
||||
return webpackStream(config, webpack)
|
||||
.on('error', errorHandler)
|
||||
.pipe(gulp.dest('_output/UI'))
|
||||
.on('error', errorHandler)
|
||||
.pipe(livereload())
|
||||
.on('error', errorHandler);
|
||||
});
|
||||
@@ -1,23 +1,32 @@
|
||||
const reload = require('require-nocache')(module);
|
||||
|
||||
module.exports = (ctx, configPath, options) => {
|
||||
const config = {
|
||||
plugins: {
|
||||
'postcss-mixins': {
|
||||
mixinsDir: [
|
||||
'frontend/src/Styles/Mixins'
|
||||
]
|
||||
},
|
||||
'postcss-simple-vars': {
|
||||
variables: () =>
|
||||
ctx.options.cssVarsFiles.reduce((acc, vars) => {
|
||||
return Object.assign(acc, reload(vars));
|
||||
}, {})
|
||||
},
|
||||
'postcss-color-function': {},
|
||||
'postcss-nested': {}
|
||||
}
|
||||
};
|
||||
const cssVarsFiles = [
|
||||
'./src/Styles/Variables/colors',
|
||||
'./src/Styles/Variables/dimensions',
|
||||
'./src/Styles/Variables/fonts',
|
||||
'./src/Styles/Variables/animations',
|
||||
'./src/Styles/Variables/zIndexes'
|
||||
].map(require.resolve);
|
||||
|
||||
return config;
|
||||
};
|
||||
const mixinsFiles = [
|
||||
'frontend/src/Styles/Mixins/cover.css',
|
||||
'frontend/src/Styles/Mixins/linkOverlay.css',
|
||||
'frontend/src/Styles/Mixins/scroller.css',
|
||||
'frontend/src/Styles/Mixins/truncate.css'
|
||||
];
|
||||
|
||||
module.exports = {
|
||||
plugins: [
|
||||
['postcss-mixins', {
|
||||
mixinsFiles
|
||||
}],
|
||||
['postcss-simple-vars', {
|
||||
variables: () =>
|
||||
cssVarsFiles.reduce((acc, vars) => {
|
||||
return Object.assign(acc, reload(vars));
|
||||
}, {})
|
||||
}],
|
||||
'postcss-color-function',
|
||||
'postcss-nested'
|
||||
]
|
||||
};
|
||||
@@ -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,6 +43,7 @@ class QueueConnector extends Component {
|
||||
const {
|
||||
useCurrentPage,
|
||||
fetchQueue,
|
||||
fetchQueueStatus,
|
||||
gotoQueueFirstPage
|
||||
} = this.props;
|
||||
|
||||
@@ -53,6 +54,8 @@ class QueueConnector extends Component {
|
||||
} else {
|
||||
gotoQueueFirstPage();
|
||||
}
|
||||
|
||||
fetchQueueStatus();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
@@ -152,6 +155,7 @@ QueueConnector.propTypes = {
|
||||
useCurrentPage: PropTypes.bool.isRequired,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
fetchQueue: PropTypes.func.isRequired,
|
||||
fetchQueueStatus: PropTypes.func.isRequired,
|
||||
gotoQueueFirstPage: PropTypes.func.isRequired,
|
||||
gotoQueuePreviousPage: PropTypes.func.isRequired,
|
||||
gotoQueueNextPage: PropTypes.func.isRequired,
|
||||
|
||||
@@ -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,6 +88,28 @@
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.posterContainer {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.certification {
|
||||
margin-left: 2px;
|
||||
padding: 0 5px;
|
||||
border: 1px solid;
|
||||
border-radius: 5px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.runtime {
|
||||
margin-left: 8px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.statusContainer {
|
||||
margin-right: 22px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $breakpointMedium) {
|
||||
.titleRow {
|
||||
justify-content: space-between;
|
||||
|
||||
@@ -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 className={styles.runtime}>
|
||||
{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&';
|
||||
|
||||
@@ -16,7 +16,7 @@ function createTagListSelector() {
|
||||
(selectedFilterBuilderProp.type === filterBuilderTypes.NUMBER ||
|
||||
selectedFilterBuilderProp.type === filterBuilderTypes.STRING) &&
|
||||
filterType !== filterTypes.EQUAL &&
|
||||
filterType !== filterBuilderTypes.NOT_EQUAL ||
|
||||
filterType !== filterTypes.NOT_EQUAL ||
|
||||
!selectedFilterBuilderProp.optionsSelector
|
||||
) {
|
||||
return [];
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -479,6 +479,7 @@ class EnhancedSelectInput extends Component {
|
||||
<OptionComponent
|
||||
key={v.key}
|
||||
id={v.key}
|
||||
dividerAfter={v.dividerAfter}
|
||||
depth={depth}
|
||||
isSelected={isSelectedItem(index, this.props)}
|
||||
isDisabled={parentSelected}
|
||||
@@ -518,6 +519,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;
|
||||
@@ -527,6 +540,7 @@ class EnhancedSelectInput extends Component {
|
||||
<OptionComponent
|
||||
key={v.key}
|
||||
id={v.key}
|
||||
dividerAfter={v.dividerAfter}
|
||||
depth={depth}
|
||||
isSelected={isSelectedItem(index, this.props)}
|
||||
isMultiSelect={isMultiSelect}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -13,6 +13,7 @@ import EnhancedSelectInputConnector from './EnhancedSelectInputConnector';
|
||||
import FormInputHelpText from './FormInputHelpText';
|
||||
import IndexerFlagsSelectInputConnector from './IndexerFlagsSelectInputConnector';
|
||||
import KeyValueListInput from './KeyValueListInput';
|
||||
import LanguageSelectInputConnector from './LanguageSelectInputConnector';
|
||||
import MovieMonitoredSelectInput from './MovieMonitoredSelectInput';
|
||||
import NumberInput from './NumberInput';
|
||||
import OAuthInputConnector from './OAuthInputConnector';
|
||||
@@ -72,6 +73,9 @@ function getComponent(type) {
|
||||
case inputTypes.INDEXER_FLAGS_SELECT:
|
||||
return IndexerFlagsSelectInputConnector;
|
||||
|
||||
case inputTypes.LANGUAGE_SELECT:
|
||||
return LanguageSelectInputConnector;
|
||||
|
||||
case inputTypes.SELECT:
|
||||
return EnhancedSelectInput;
|
||||
|
||||
|
||||
@@ -21,3 +21,8 @@
|
||||
color: $darkGray;
|
||||
font-size: $smallFontSize;
|
||||
}
|
||||
|
||||
.divider {
|
||||
border: none;
|
||||
border-bottom: 1px solid $lightGray;
|
||||
}
|
||||
|
||||
@@ -12,37 +12,46 @@ function HintedSelectInputOption(props) {
|
||||
depth,
|
||||
isSelected,
|
||||
isDisabled,
|
||||
dividerAfter,
|
||||
isMultiSelect,
|
||||
isMobile,
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<EnhancedSelectInputOption
|
||||
id={id}
|
||||
depth={depth}
|
||||
isSelected={isSelected}
|
||||
isDisabled={isDisabled}
|
||||
isHidden={isDisabled}
|
||||
isMultiSelect={isMultiSelect}
|
||||
isMobile={isMobile}
|
||||
{...otherProps}
|
||||
>
|
||||
<div className={classNames(
|
||||
styles.optionText,
|
||||
isMobile && styles.isMobile
|
||||
)}
|
||||
<div>
|
||||
<EnhancedSelectInputOption
|
||||
id={id}
|
||||
depth={depth}
|
||||
isSelected={isSelected}
|
||||
isDisabled={isDisabled}
|
||||
isHidden={isDisabled}
|
||||
isMultiSelect={isMultiSelect}
|
||||
isMobile={isMobile}
|
||||
{...otherProps}
|
||||
>
|
||||
<div>{value}</div>
|
||||
<div className={classNames(
|
||||
styles.optionText,
|
||||
isMobile && styles.isMobile
|
||||
)}
|
||||
>
|
||||
<div>{value}</div>
|
||||
|
||||
{
|
||||
hint != null &&
|
||||
<div className={styles.hintText}>
|
||||
{hint}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</EnhancedSelectInputOption>
|
||||
{
|
||||
hint != null &&
|
||||
<div className={styles.hintText}>
|
||||
{hint}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</EnhancedSelectInputOption>
|
||||
|
||||
{
|
||||
dividerAfter ?
|
||||
<div className={styles.divider} /> :
|
||||
null
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -50,15 +59,18 @@ HintedSelectInputOption.propTypes = {
|
||||
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
||||
value: PropTypes.string.isRequired,
|
||||
hint: PropTypes.node,
|
||||
name: PropTypes.string,
|
||||
depth: PropTypes.number,
|
||||
isSelected: PropTypes.bool.isRequired,
|
||||
isDisabled: PropTypes.bool.isRequired,
|
||||
dividerAfter: PropTypes.bool.isRequired,
|
||||
isMultiSelect: PropTypes.bool.isRequired,
|
||||
isMobile: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
HintedSelectInputOption.defaultProps = {
|
||||
isDisabled: false,
|
||||
dividerAfter: false,
|
||||
isHidden: false,
|
||||
isMultiSelect: false
|
||||
};
|
||||
|
||||
52
frontend/src/Components/Form/LanguageSelectInputConnector.js
Normal file
52
frontend/src/Components/Form/LanguageSelectInputConnector.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import EnhancedSelectInput from './EnhancedSelectInput';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state, { values }) => values,
|
||||
( languages ) => {
|
||||
|
||||
const minId = languages.reduce((min, v) => (v.key < 1 ? v.key : min), languages[0].key);
|
||||
|
||||
const values = languages.map(({ key, value }) => {
|
||||
return {
|
||||
key,
|
||||
value,
|
||||
dividerAfter: minId < 1 ? key === minId : false
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
values
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
class LanguageSelectInputConnector extends Component {
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
|
||||
return (
|
||||
<EnhancedSelectInput
|
||||
{...this.props}
|
||||
onChange={this.props.onChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
LanguageSelectInputConnector.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
value: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.number), PropTypes.number]).isRequired,
|
||||
values: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
onChange: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps)(LanguageSelectInputConnector);
|
||||
@@ -49,6 +49,7 @@ function getSelectValues(selectOptions) {
|
||||
result.push({
|
||||
key: option.value,
|
||||
value: option.name,
|
||||
dividerAfter: option.dividerAfter,
|
||||
hint: option.hint
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -38,11 +38,12 @@ class Link extends Component {
|
||||
const linkProps = { target };
|
||||
let el = component;
|
||||
|
||||
if (to) {
|
||||
if (to && typeof to === 'string') {
|
||||
if ((/\w+?:\/\//).test(to)) {
|
||||
el = 'a';
|
||||
linkProps.href = to;
|
||||
linkProps.target = target || '_blank';
|
||||
linkProps.rel = 'noreferrer';
|
||||
} else if (noRouter) {
|
||||
el = 'a';
|
||||
linkProps.href = to;
|
||||
@@ -52,6 +53,18 @@ class Link extends Component {
|
||||
linkProps.to = `${window.Radarr.urlBase}/${to.replace(/^\//, '')}`;
|
||||
linkProps.target = target;
|
||||
}
|
||||
} else if (to && typeof to === 'object') {
|
||||
el = RouterLink;
|
||||
linkProps.target = target;
|
||||
if (to.pathname.startsWith(`${window.Radarr.urlBase}/`)) {
|
||||
linkProps.to = to;
|
||||
} else {
|
||||
const pathname = `${window.Radarr.urlBase}/${to.pathname.replace(/^\//, '')}`;
|
||||
linkProps.to = {
|
||||
...to,
|
||||
pathname
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (el === 'button' || el === 'input') {
|
||||
@@ -82,7 +95,7 @@ class Link extends Component {
|
||||
Link.propTypes = {
|
||||
className: PropTypes.string,
|
||||
component: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
|
||||
to: PropTypes.string,
|
||||
to: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||
target: PropTypes.string,
|
||||
isDisabled: PropTypes.bool,
|
||||
noRouter: PropTypes.bool,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -53,7 +53,13 @@ class PageHeader extends Component {
|
||||
return (
|
||||
<div className={styles.header}>
|
||||
<div className={styles.logoContainer}>
|
||||
<Link to={'/'}>
|
||||
<Link
|
||||
className={styles.logoLink}
|
||||
to={{
|
||||
pathname: '/',
|
||||
state: { restoreScrollPosition: true }
|
||||
}}
|
||||
>
|
||||
<img
|
||||
className={isSmallScreen ? styles.logo : styles.logoFull}
|
||||
src={isSmallScreen ? `${window.Radarr.urlBase}/Content/Images/logo.png` : `${window.Radarr.urlBase}/Content/Images/logo-full.png`}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { DndProvider } from 'react-dnd';
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
import { DndProvider } from 'react-dnd-multi-backend';
|
||||
import HTML5toTouch from 'react-dnd-multi-backend/dist/esm/HTML5toTouch';
|
||||
import Form from 'Components/Form/Form';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
@@ -128,7 +128,7 @@ class TableOptionsModal extends Component {
|
||||
const isDraggingDown = isDragging && dropIndex > dragIndex;
|
||||
|
||||
return (
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<DndProvider options={HTML5toTouch}>
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onModalClose={onModalClose}
|
||||
|
||||
@@ -39,7 +39,8 @@ class VirtualTable extends Component {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
width: 0
|
||||
width: 0,
|
||||
scrollRestored: false
|
||||
};
|
||||
|
||||
this._grid = null;
|
||||
@@ -48,11 +49,13 @@ class VirtualTable extends Component {
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
const {
|
||||
items,
|
||||
scrollIndex
|
||||
scrollIndex,
|
||||
scrollTop
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
width
|
||||
width,
|
||||
scrollRestored
|
||||
} = this.state;
|
||||
|
||||
if (this._grid &&
|
||||
@@ -68,6 +71,11 @@ class VirtualTable extends Component {
|
||||
columnIndex: 0
|
||||
});
|
||||
}
|
||||
|
||||
if (this._grid && scrollTop !== undefined && scrollTop !== 0 && !scrollRestored) {
|
||||
this.setState({ scrollRestored: true });
|
||||
this._grid.scrollToPosition({ scrollTop });
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
@@ -96,6 +104,7 @@ class VirtualTable extends Component {
|
||||
items,
|
||||
scroller,
|
||||
focusScroller,
|
||||
scrollTop: ignored,
|
||||
header,
|
||||
headerHeight,
|
||||
rowRenderer,
|
||||
@@ -180,6 +189,7 @@ VirtualTable.propTypes = {
|
||||
className: PropTypes.string.isRequired,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
scrollIndex: PropTypes.number,
|
||||
scrollTop: PropTypes.number,
|
||||
scroller: PropTypes.instanceOf(Element).isRequired,
|
||||
focusScroller: PropTypes.bool.isRequired,
|
||||
header: PropTypes.node.isRequired,
|
||||
|
||||
@@ -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 = () => {
|
||||
|
||||
@@ -8,7 +8,7 @@ function withScrollPosition(WrappedComponent, scrollPositionKey) {
|
||||
history
|
||||
} = props;
|
||||
|
||||
const scrollTop = history.action === 'POP' ?
|
||||
const scrollTop = history.action === 'POP' || (history.location.state && history.location.state.restoreScrollPosition) ?
|
||||
scrollPositions[scrollPositionKey] :
|
||||
0;
|
||||
|
||||
|
||||
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 |
@@ -184,6 +184,7 @@ export const PAGE_LAST = fasFastForward;
|
||||
export const PARENT = fasLevelUpAlt;
|
||||
export const PAUSED = fasPause;
|
||||
export const PENDING = farClock;
|
||||
export const PLAY = fasPlay;
|
||||
export const PROFILE = fasUser;
|
||||
export const POSTER = fasTh;
|
||||
export const QUEUED = fasCloud;
|
||||
|
||||
@@ -3,6 +3,7 @@ export const AVAILABILITY_SELECT = 'availabilitySelect';
|
||||
export const CAPTCHA = 'captcha';
|
||||
export const CHECK = 'check';
|
||||
export const DEVICE = 'device';
|
||||
export const KEY_VALUE_LIST = 'keyValueList';
|
||||
export const MOVIE_MONITORED_SELECT = 'movieMonitoredSelect';
|
||||
export const NUMBER = 'number';
|
||||
export const OAUTH = 'oauth';
|
||||
@@ -11,6 +12,7 @@ export const PATH = 'path';
|
||||
export const QUALITY_PROFILE_SELECT = 'qualityProfileSelect';
|
||||
export const ROOT_FOLDER_SELECT = 'rootFolderSelect';
|
||||
export const INDEXER_FLAGS_SELECT = 'indexerFlagsSelect';
|
||||
export const LANGUAGE_SELECT = 'languageSelect';
|
||||
export const SELECT = 'select';
|
||||
export const DYNAMIC_SELECT = 'dynamicSelect';
|
||||
export const TAG = 'tag';
|
||||
@@ -26,6 +28,7 @@ export const all = [
|
||||
CAPTCHA,
|
||||
CHECK,
|
||||
DEVICE,
|
||||
KEY_VALUE_LIST,
|
||||
MOVIE_MONITORED_SELECT,
|
||||
NUMBER,
|
||||
OAUTH,
|
||||
@@ -34,6 +37,7 @@ export const all = [
|
||||
QUALITY_PROFILE_SELECT,
|
||||
ROOT_FOLDER_SELECT,
|
||||
INDEXER_FLAGS_SELECT,
|
||||
LANGUAGE_SELECT,
|
||||
SELECT,
|
||||
DYNAMIC_SELECT,
|
||||
TAG,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export const DANGER = 'danger';
|
||||
export const DEFAULT = 'default';
|
||||
export const DELETE = 'delete';
|
||||
export const DISABLED = 'disabled';
|
||||
export const INFO = 'info';
|
||||
export const INVERSE = 'inverse';
|
||||
@@ -13,6 +14,7 @@ export const QUEUE = 'queue';
|
||||
export const all = [
|
||||
DANGER,
|
||||
DEFAULT,
|
||||
DELETE,
|
||||
DISABLED,
|
||||
INFO,
|
||||
INVERSE,
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -22,6 +22,20 @@ const columns = [
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'releaseWeight',
|
||||
label: React.createElement(Icon, { name: icons.DOWNLOAD }),
|
||||
isSortable: true,
|
||||
fixedSortDirection: sortDirections.ASCENDING,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'rejections',
|
||||
label: React.createElement(Icon, { name: icons.DANGER }),
|
||||
isSortable: true,
|
||||
fixedSortDirection: sortDirections.ASCENDING,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
label: translate('Title'),
|
||||
@@ -85,20 +99,6 @@ const columns = [
|
||||
label: React.createElement(Icon, { name: icons.FLAG }),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'rejections',
|
||||
label: React.createElement(Icon, { name: icons.DANGER }),
|
||||
isSortable: true,
|
||||
fixedSortDirection: sortDirections.ASCENDING,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'releaseWeight',
|
||||
label: React.createElement(Icon, { name: icons.DOWNLOAD }),
|
||||
isSortable: true,
|
||||
fixedSortDirection: sortDirections.ASCENDING,
|
||||
isVisible: true
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -145,6 +145,46 @@ class InteractiveSearchRow extends Component {
|
||||
{formatAge(age, ageHours, ageMinutes)}
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell className={styles.download}>
|
||||
<SpinnerIconButton
|
||||
name={getDownloadIcon(isGrabbing, isGrabbed, grabError)}
|
||||
kind={grabError ? kinds.DANGER : kinds.DEFAULT}
|
||||
title={getDownloadTooltip(isGrabbing, isGrabbed, grabError)}
|
||||
isDisabled={isGrabbed}
|
||||
isSpinning={isGrabbing}
|
||||
onPress={downloadAllowed ? this.onGrabPress : this.onConfirmGrabPress}
|
||||
/>
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell className={styles.rejected}>
|
||||
{
|
||||
!!rejections.length &&
|
||||
<Popover
|
||||
anchor={
|
||||
<Icon
|
||||
name={icons.DANGER}
|
||||
kind={kinds.DANGER}
|
||||
/>
|
||||
}
|
||||
title={translate('ReleaseRejected')}
|
||||
body={
|
||||
<ul>
|
||||
{
|
||||
rejections.map((rejection, index) => {
|
||||
return (
|
||||
<li key={index}>
|
||||
{rejection}
|
||||
</li>
|
||||
);
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
position={tooltipPositions.BOTTOM}
|
||||
/>
|
||||
}
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell className={styles.title}>
|
||||
<Link
|
||||
to={infoUrl}
|
||||
@@ -252,51 +292,11 @@ class InteractiveSearchRow extends Component {
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
position={tooltipPositions.LEFT}
|
||||
position={tooltipPositions.BOTTOM}
|
||||
/>
|
||||
}
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell className={styles.rejected}>
|
||||
{
|
||||
!!rejections.length &&
|
||||
<Popover
|
||||
anchor={
|
||||
<Icon
|
||||
name={icons.DANGER}
|
||||
kind={kinds.DANGER}
|
||||
/>
|
||||
}
|
||||
title={translate('ReleaseRejected')}
|
||||
body={
|
||||
<ul>
|
||||
{
|
||||
rejections.map((rejection, index) => {
|
||||
return (
|
||||
<li key={index}>
|
||||
{rejection}
|
||||
</li>
|
||||
);
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
position={tooltipPositions.LEFT}
|
||||
/>
|
||||
}
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell className={styles.download}>
|
||||
<SpinnerIconButton
|
||||
name={getDownloadIcon(isGrabbing, isGrabbed, grabError)}
|
||||
kind={grabError ? kinds.DANGER : kinds.DEFAULT}
|
||||
title={getDownloadTooltip(isGrabbing, isGrabbed, grabError)}
|
||||
isDisabled={isGrabbed}
|
||||
isSpinning={isGrabbing}
|
||||
onPress={downloadAllowed ? this.onGrabPress : this.onConfirmGrabPress}
|
||||
/>
|
||||
</TableRowCell>
|
||||
|
||||
<ConfirmModal
|
||||
isOpen={this.state.isConfirmGrabModalOpen}
|
||||
kind={kinds.WARNING}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
@@ -41,6 +41,19 @@ function MovieDetailsLinks(props) {
|
||||
</Label>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
className={styles.link}
|
||||
to={`https://letterboxd.com/tmdb/${tmdbId}`}
|
||||
>
|
||||
<Label
|
||||
className={styles.linkLabel}
|
||||
kind={kinds.INFO}
|
||||
size={sizes.LARGE}
|
||||
>
|
||||
{translate('Letterboxd')}
|
||||
</Label>
|
||||
</Link>
|
||||
|
||||
{
|
||||
!!imdbId &&
|
||||
<Link
|
||||
@@ -61,7 +74,7 @@ function MovieDetailsLinks(props) {
|
||||
!!imdbId &&
|
||||
<Link
|
||||
className={styles.link}
|
||||
to={` https://moviechat.org/${imdbId}/`}
|
||||
to={`https://moviechat.org/${imdbId}/`}
|
||||
>
|
||||
<Label
|
||||
className={styles.linkLabel}
|
||||
@@ -77,7 +90,7 @@ function MovieDetailsLinks(props) {
|
||||
!!youTubeTrailerId &&
|
||||
<Link
|
||||
className={styles.link}
|
||||
to={` https://www.youtube.com/watch?v=${youTubeTrailerId}/`}
|
||||
to={`https://www.youtube.com/watch?v=${youTubeTrailerId}/`}
|
||||
>
|
||||
<Label
|
||||
className={styles.linkLabel}
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -60,7 +60,8 @@ class MovieIndexOverviews extends Component {
|
||||
columnCount: 1,
|
||||
posterWidth: 162,
|
||||
posterHeight: 238,
|
||||
rowHeight: calculateRowHeight(238, null, props.isSmallScreen, {})
|
||||
rowHeight: calculateRowHeight(238, null, props.isSmallScreen, {}),
|
||||
scrollRestored: false
|
||||
};
|
||||
|
||||
this._grid = null;
|
||||
@@ -72,13 +73,15 @@ class MovieIndexOverviews extends Component {
|
||||
sortKey,
|
||||
overviewOptions,
|
||||
jumpToCharacter,
|
||||
scrollTop,
|
||||
isMovieEditorActive,
|
||||
isSmallScreen
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
width,
|
||||
rowHeight
|
||||
rowHeight,
|
||||
scrollRestored
|
||||
} = this.state;
|
||||
|
||||
if (prevProps.sortKey !== sortKey ||
|
||||
@@ -97,6 +100,11 @@ class MovieIndexOverviews extends Component {
|
||||
this._grid.recomputeGridSize();
|
||||
}
|
||||
|
||||
if (this._grid && scrollTop !== 0 && !scrollRestored) {
|
||||
this.setState({ scrollRestored: true });
|
||||
this._grid.scrollToPosition({ scrollTop });
|
||||
}
|
||||
|
||||
if (jumpToCharacter != null && jumpToCharacter !== prevProps.jumpToCharacter) {
|
||||
const index = getIndexOfFirstCharacter(items, jumpToCharacter);
|
||||
|
||||
@@ -262,6 +270,7 @@ MovieIndexOverviews.propTypes = {
|
||||
sortKey: PropTypes.string,
|
||||
overviewOptions: PropTypes.object.isRequired,
|
||||
jumpToCharacter: PropTypes.string,
|
||||
scrollTop: PropTypes.number.isRequired,
|
||||
scroller: PropTypes.instanceOf(Element).isRequired,
|
||||
showRelativeDates: PropTypes.bool.isRequired,
|
||||
shortDateFormat: PropTypes.string.isRequired,
|
||||
|
||||
@@ -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':
|
||||
@@ -99,7 +104,8 @@ class MovieIndexPosters extends Component {
|
||||
columnCount: 1,
|
||||
posterWidth: 162,
|
||||
posterHeight: 238,
|
||||
rowHeight: calculateRowHeight(238, null, props.isSmallScreen, {})
|
||||
rowHeight: calculateRowHeight(238, null, props.isSmallScreen, {}),
|
||||
scrollRestored: false
|
||||
};
|
||||
|
||||
this._isInitialized = false;
|
||||
@@ -114,14 +120,16 @@ class MovieIndexPosters extends Component {
|
||||
posterOptions,
|
||||
jumpToCharacter,
|
||||
isSmallScreen,
|
||||
isMovieEditorActive
|
||||
isMovieEditorActive,
|
||||
scrollTop
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
width,
|
||||
columnWidth,
|
||||
columnCount,
|
||||
rowHeight
|
||||
rowHeight,
|
||||
scrollRestored
|
||||
} = this.state;
|
||||
|
||||
if (prevProps.sortKey !== sortKey ||
|
||||
@@ -140,6 +148,11 @@ class MovieIndexPosters extends Component {
|
||||
this._grid.recomputeGridSize();
|
||||
}
|
||||
|
||||
if (this._grid && scrollTop !== 0 && !scrollRestored) {
|
||||
this.setState({ scrollRestored: true });
|
||||
this._grid.scrollToPosition({ scrollTop });
|
||||
}
|
||||
|
||||
if (jumpToCharacter != null && jumpToCharacter !== prevProps.jumpToCharacter) {
|
||||
const index = getIndexOfFirstCharacter(items, jumpToCharacter);
|
||||
|
||||
@@ -152,6 +165,10 @@ class MovieIndexPosters extends Component {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (this._grid && scrollTop !== 0) {
|
||||
this._grid.scrollToPosition({ scrollTop });
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
@@ -206,7 +223,9 @@ class MovieIndexPosters extends Component {
|
||||
detailedProgressBar,
|
||||
showTitle,
|
||||
showMonitored,
|
||||
showQualityProfile
|
||||
showQualityProfile,
|
||||
showCinemaRelease,
|
||||
showReleaseDate
|
||||
} = posterOptions;
|
||||
|
||||
const movieIdx = rowIndex * columnCount + columnIndex;
|
||||
@@ -235,6 +254,8 @@ class MovieIndexPosters extends Component {
|
||||
showTitle={showTitle}
|
||||
showMonitored={showMonitored}
|
||||
showQualityProfile={showQualityProfile}
|
||||
showReleaseDate={showReleaseDate}
|
||||
showCinemaRelease={showCinemaRelease}
|
||||
showRelativeDates={showRelativeDates}
|
||||
shortDateFormat={shortDateFormat}
|
||||
timeFormat={timeFormat}
|
||||
@@ -323,6 +344,7 @@ MovieIndexPosters.propTypes = {
|
||||
sortKey: PropTypes.string,
|
||||
posterOptions: PropTypes.object.isRequired,
|
||||
jumpToCharacter: PropTypes.string,
|
||||
scrollTop: PropTypes.number.isRequired,
|
||||
scroller: PropTypes.instanceOf(Element).isRequired,
|
||||
showRelativeDates: PropTypes.bool.isRequired,
|
||||
shortDateFormat: PropTypes.string.isRequired,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -87,6 +87,7 @@ class MovieIndexTable extends Component {
|
||||
isSmallScreen,
|
||||
onSortPress,
|
||||
scroller,
|
||||
scrollTop,
|
||||
allSelected,
|
||||
allUnselected,
|
||||
onSelectAllChange,
|
||||
@@ -100,6 +101,7 @@ class MovieIndexTable extends Component {
|
||||
items={items}
|
||||
scrollIndex={this.state.scrollIndex}
|
||||
isSmallScreen={isSmallScreen}
|
||||
scrollTop={scrollTop}
|
||||
scroller={scroller}
|
||||
rowHeight={38}
|
||||
overscanRowCount={2}
|
||||
@@ -130,6 +132,7 @@ MovieIndexTable.propTypes = {
|
||||
sortDirection: PropTypes.oneOf(sortDirections.all),
|
||||
jumpToCharacter: PropTypes.string,
|
||||
isSmallScreen: PropTypes.bool.isRequired,
|
||||
scrollTop: PropTypes.number,
|
||||
scroller: PropTypes.instanceOf(Element).isRequired,
|
||||
onSortPress: PropTypes.func.isRequired,
|
||||
allSelected: PropTypes.bool.isRequired,
|
||||
|
||||
@@ -155,7 +155,7 @@ class FileEditModalContent extends Component {
|
||||
<FormLabel>{translate('Languages')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.SELECT}
|
||||
type={inputTypes.LANGUAGE_SELECT}
|
||||
name="languageIds"
|
||||
value={languageIds}
|
||||
values={languageOptions}
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import Form from 'Components/Form/Form';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
@@ -21,6 +22,7 @@ function EditImportListModalContent(props) {
|
||||
advancedSettings,
|
||||
isFetching,
|
||||
error,
|
||||
rootFolderError,
|
||||
isSaving,
|
||||
isTesting,
|
||||
saveError,
|
||||
@@ -46,7 +48,8 @@ function EditImportListModalContent(props) {
|
||||
rootFolderPath,
|
||||
searchOnAdd,
|
||||
tags,
|
||||
fields
|
||||
fields,
|
||||
message
|
||||
} = item;
|
||||
|
||||
return (
|
||||
@@ -62,17 +65,26 @@ function EditImportListModalContent(props) {
|
||||
}
|
||||
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
!isFetching && (!!error || !!rootFolderError) &&
|
||||
<div>
|
||||
{translate('UnableToAddANewListPleaseTryAgain')}
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
!isFetching && !error &&
|
||||
!isFetching && !error && !rootFolderError &&
|
||||
<Form
|
||||
{...otherProps}
|
||||
>
|
||||
{
|
||||
!!message &&
|
||||
<Alert
|
||||
className={styles.message}
|
||||
kind={message.value.type}
|
||||
>
|
||||
{message.value.message}
|
||||
</Alert>
|
||||
}
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('Name')}</FormLabel>
|
||||
|
||||
@@ -163,6 +175,7 @@ function EditImportListModalContent(props) {
|
||||
type={inputTypes.ROOT_FOLDER_SELECT}
|
||||
name="rootFolderPath"
|
||||
{...rootFolderPath}
|
||||
includeMissingValue={true}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
@@ -239,6 +252,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%;
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user