mirror of
https://github.com/Radarr/Radarr.git
synced 2026-04-18 21:35:51 -04:00
Compare commits
502 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 66aeb19d88 | |||
| 455aca85bd | |||
| a29d794cbe | |||
| bb3123772f | |||
| 46a20e1dcd | |||
| 993144b67a | |||
| 1f209848dc | |||
| 3dafe44fed | |||
| 767e75ca45 | |||
| 1d4db26f17 | |||
| 757cb9a956 | |||
| 5dc3726023 | |||
| 5b2f30227b | |||
| ed94eee859 | |||
| 6eb271eee4 | |||
| 9fe205727c | |||
| 4627093616 | |||
| a006984d5e | |||
| 7d9183ef12 | |||
| d35c6683e9 | |||
| ac26bcddd9 | |||
| 15bafce8cc | |||
| 2167da87ce | |||
| 926d37a572 | |||
| 42c9e4e3e5 | |||
| 89b609a221 | |||
| dfc9f74116 | |||
| 189603c756 | |||
| a78693a2f7 | |||
| cea0c5033a | |||
| 1e3a42bf42 | |||
| 030744ab7b | |||
| 17fda02d8c | |||
| aabf6b9ff8 | |||
| 7b2fd5140b | |||
| b6b10d7c6f | |||
| 16fcf0b56b | |||
| c222a1a434 | |||
| c6e91e028b | |||
| fcf5984944 | |||
| fdfe8ca656 | |||
| 150a5c1fc6 | |||
| 9ea0957351 | |||
| 8befa436cc | |||
| 53fdb6f07f | |||
| 0697dbff96 | |||
| 34924859aa | |||
| 9c86598b54 | |||
| 0fe2262162 | |||
| 47353aea75 | |||
| af43cb2aca | |||
| bc838b74c7 | |||
| cbcf3d1058 | |||
| c72e64f081 | |||
| e09607edb0 | |||
| d91578aee3 | |||
| affedd7f9d | |||
| c3665e9fea | |||
| 364d8bd7c5 | |||
| 7142d1f224 | |||
| 86777e021b | |||
| 9d2dacea97 | |||
| d98c86c3d9 | |||
| df681d82be | |||
| daf81c5b26 | |||
| 78f929c60b | |||
| 87d59d12a4 | |||
| ce031124c7 | |||
| d4ce08a044 | |||
| 871e78b314 | |||
| eeee682f6c | |||
| 9c594c3e53 | |||
| 0b1b19a165 | |||
| f1ff7b3b61 | |||
| 165c588557 | |||
| 327e18bc7a | |||
| f61f2c89dc | |||
| 2327b72558 | |||
| 66ddd08684 | |||
| 4d2143e9b2 | |||
| 7906ea2a0c | |||
| 9d1956794e | |||
| 4956ff7914 | |||
| f22a589cb8 | |||
| 04185d6839 | |||
| fb25e5d577 | |||
| 6845eaa9b2 | |||
| c1e65874bc | |||
| 226a5da0c9 | |||
| 685a24e476 | |||
| cae4faae61 | |||
| 5dac6badf2 | |||
| 5948f56482 | |||
| 98ddd0386b | |||
| 2947b244e4 | |||
| 72552b8084 | |||
| 09642444d7 | |||
| d1080b825c | |||
| 001421de10 | |||
| bab9b8b36a | |||
| 0fb738aa2e | |||
| 4963920a46 | |||
| f0d10fe1cd | |||
| 386b33b624 | |||
| 98201508f2 | |||
| 9723c569a1 | |||
| 0584d7676c | |||
| 09c42530ec | |||
| 0697d694e0 | |||
| e085f6af8a | |||
| 7feda1c446 | |||
| e1f83c205d | |||
| db00edd266 | |||
| d699f61f5d | |||
| dc1b478f2c | |||
| 0ca665c903 | |||
| 111c6a743f | |||
| d3517532a4 | |||
| 5790ebc558 | |||
| c11f72c098 | |||
| 3617bef54b | |||
| a5fb01f1e6 | |||
| fa6acb7497 | |||
| 904259df92 | |||
| 65c316bd6d | |||
| 3b46a08606 | |||
| 6ad49373d4 | |||
| 2a1f57c085 | |||
| 9d9065fbcd | |||
| 694940452c | |||
| f5d6a79998 | |||
| 4cc98a10a0 | |||
| 1751bd1a58 | |||
| c0caf65b69 | |||
| cd889872de | |||
| 6366e335bc | |||
| 41f10d098e | |||
| b2c1698097 | |||
| ed20487f30 | |||
| d1235adfc4 | |||
| 561993e30c | |||
| 14f8f89634 | |||
| 874482dbce | |||
| bae374c0c8 | |||
| 4f5ad899bb | |||
| adcd00d9fd | |||
| d70d351ea2 | |||
| ef90ac7041 | |||
| aa8e886dab | |||
| c7ee2c9166 | |||
| 5330815e1b | |||
| 296ec6c565 | |||
| bf89995984 | |||
| 44d7c54077 | |||
| d37fac5343 | |||
| ae8245c3c5 | |||
| 850bfdcf82 | |||
| 7d4865dea3 | |||
| f9ef7e3578 | |||
| fb25422922 | |||
| ac3d4bee35 | |||
| bb60510515 | |||
| 5c9e11d7a0 | |||
| 6c01e8c91f | |||
| 488a7d183e | |||
| 7fcb0d6e45 | |||
| bd19c89f6e | |||
| 15e5ad5f84 | |||
| 1732e23945 | |||
| 5a7a9db7ed | |||
| 182cda47b0 | |||
| 294d95fae4 | |||
| 0e3f871e0e | |||
| b0f5f02edc | |||
| 2afe6af5a6 | |||
| e2eaf91aa7 | |||
| 0e1c2c3c50 | |||
| 69cf2e89a6 | |||
| 9830230589 | |||
| b1f0b2c216 | |||
| 7c6858ecfb | |||
| ee32d42c94 | |||
| 3390df4085 | |||
| 01bc5f6fc8 | |||
| 2d867a6cb6 | |||
| 411f8866ec | |||
| 5316382113 | |||
| 8fe81b428a | |||
| 43a2a2d335 | |||
| 5c8b58c30d | |||
| 131a223bb9 | |||
| dfaab639bf | |||
| c7be63d48f | |||
| 2958faf4a8 | |||
| 4280df8b61 | |||
| 1f91be6407 | |||
| eb43a3c2d0 | |||
| 20c7e84676 | |||
| 691a8955fe | |||
| 53a9c849cb | |||
| 856a55a9c9 | |||
| 43cd536746 | |||
| 4a205d8041 | |||
| 2525ac2d1a | |||
| e5ceb20a83 | |||
| 0f6b11f55d | |||
| 500bc3a571 | |||
| e6567d0365 | |||
| dbca393772 | |||
| 9662495fa2 | |||
| 76f0c54b3c | |||
| d7ff92115c | |||
| 54a49d6878 | |||
| a8362511f9 | |||
| b9f2b3e06f | |||
| d995bc5a7e | |||
| 8886162bba | |||
| eb9eb4ec64 | |||
| f910a8fde7 | |||
| f6904608a7 | |||
| 0c79548fc4 | |||
| 362e664ce6 | |||
| c2cbfb274a | |||
| 9b3770a018 | |||
| 9db6289693 | |||
| 8a63f6ae37 | |||
| 069b18e5e3 | |||
| f05333db51 | |||
| f50e8f631e | |||
| b9886cd11c | |||
| 9f3eecb2a9 | |||
| 52c24a4333 | |||
| 9bc31b46fa | |||
| f4d8e113c1 | |||
| 1e1a4240d1 | |||
| 8fb53df4af | |||
| f6dd600d2b | |||
| 40a15d59e0 | |||
| c7baa66de2 | |||
| 2be70f5001 | |||
| da857701f6 | |||
| 828d7eb1f3 | |||
| b3a056edf9 | |||
| 98437c3cac | |||
| c5616c5ba1 | |||
| 61979bff7a | |||
| 90d0d8bec8 | |||
| 2d814ecd20 | |||
| 6542119402 | |||
| b9185574f3 | |||
| 99e0d42b71 | |||
| d01fa5e6a4 | |||
| 2ce9d099e1 | |||
| 12829580e5 | |||
| dadd796737 | |||
| a3f508b8d4 | |||
| 1ab3df03a3 | |||
| 5558e10711 | |||
| 573405bae7 | |||
| 43d77308f9 | |||
| b3c3f7ddae | |||
| dd5bc41eda | |||
| c8ab4f8c68 | |||
| a4ddae0ccc | |||
| d730161800 | |||
| 66c1af0555 | |||
| dca00db317 | |||
| 812e5ac5a3 | |||
| d01bae92bf | |||
| 1a6bf51741 | |||
| f3e7843150 | |||
| 886b9b1c05 | |||
| d8891ee4ea | |||
| 192dd9c137 | |||
| b549fddf95 | |||
| c1f538ed97 | |||
| e72f8097fb | |||
| 3eec088306 | |||
| ad097dd1a2 | |||
| b4b38a5318 | |||
| b0717a0803 | |||
| 4d1d08d345 | |||
| e689817508 | |||
| 3b191caf16 | |||
| cc6ca0b067 | |||
| 57cb63fb18 | |||
| 20f709d22a | |||
| 5d8775ac96 | |||
| 4890972e16 | |||
| 40dc808f61 | |||
| 97077e09d2 | |||
| 9ba7027d00 | |||
| 9903e70925 | |||
| 3a6f3666f5 | |||
| 915c66be50 | |||
| 70b22e483a | |||
| cad1191da5 | |||
| 43910af127 | |||
| f01c477b81 | |||
| 0054318658 | |||
| 03a3f4522a | |||
| 3d3562dcda | |||
| 7a079c5e0c | |||
| 4d70798f2f | |||
| d55864f869 | |||
| 3c41c84fb0 | |||
| eae9a6d6e0 | |||
| 867f8f5835 | |||
| 0c81387cfb | |||
| c5fb5200de | |||
| cc306fcd36 | |||
| 2bb7984961 | |||
| 21e605452a | |||
| 476f5b5bfd | |||
| b6920cfe82 | |||
| e89b98d0f6 | |||
| 1db690ad39 | |||
| d5c524719b | |||
| ced6586860 | |||
| 8b3019821a | |||
| 16ed68d5de | |||
| 098a893083 | |||
| 548e3400b5 | |||
| 5c31e3f1a2 | |||
| 7404793dcf | |||
| d8af17ce3d | |||
| 44c912f02d | |||
| b104368e23 | |||
| aa0104b6bc | |||
| 69fcd8ec94 | |||
| a59928c66a | |||
| 1cb7ae11a2 | |||
| ca519047dd | |||
| f15a6abde0 | |||
| 2aacebc938 | |||
| 120e9b673e | |||
| 0a77a13fa8 | |||
| 383f9647c3 | |||
| 7f7c672b93 | |||
| 2690ad8fe1 | |||
| 801204b6de | |||
| cb9514abaf | |||
| fd22cb44f6 | |||
| 2d68716376 | |||
| b97e76c8b8 | |||
| bfad4a8cd1 | |||
| 61f05710f5 | |||
| a8ecefd91f | |||
| e3468daba0 | |||
| f2a7d0d520 | |||
| 43257f0726 | |||
| 6c2bf860fe | |||
| 3a1d848e59 | |||
| f6590e71d2 | |||
| 586dd737fd | |||
| fa84dda38c | |||
| 4a233ce915 | |||
| ffdd9a1708 | |||
| f890aadffa | |||
| a8695959f1 | |||
| 696bb845a5 | |||
| 301a6904c0 | |||
| 8b4621db61 | |||
| 822b597f26 | |||
| 737a0176d4 | |||
| 5dc541c69e | |||
| a9627771e6 | |||
| 5facab0744 | |||
| 63dde3eb89 | |||
| 144f564076 | |||
| a6205c1ad4 | |||
| 64f27bca4f | |||
| 8a84975954 | |||
| e923b2fc6c | |||
| a4136150d0 | |||
| 1f1f3cdaa2 | |||
| bd85936a62 | |||
| 93b2395228 | |||
| 14cccd3a23 | |||
| 006c9289de | |||
| 79cd2b2346 | |||
| f80272a659 | |||
| a158e008e9 | |||
| c798987379 | |||
| 097b46c49f | |||
| ddeb3a7840 | |||
| 50018d0325 | |||
| a37fc0dc1f | |||
| 39ad315e73 | |||
| 0559996566 | |||
| e7d4429fe4 | |||
| 6c494e9a92 | |||
| 62faa1aad8 | |||
| 907950e309 | |||
| 1caced614e | |||
| 5824ba963b | |||
| 7f2d5d8d10 | |||
| 81bffe243a | |||
| 2d6fde282a | |||
| 3125b038d5 | |||
| 89e25a6241 | |||
| 4db6688fe0 | |||
| 4ac1aeaf06 | |||
| e2ae743ee1 | |||
| 9ad316a6f5 | |||
| b643d2e23d | |||
| 6a03eddda9 | |||
| 1576bf1f17 | |||
| 6325b70e27 | |||
| 24206ad0a3 | |||
| 2fc7cbff89 | |||
| 55ef505d74 | |||
| cabdad6306 | |||
| 8d4b2dd21b | |||
| ad04031c99 | |||
| e9a5f87e45 | |||
| bc6ac0cd4b | |||
| c2328e4b79 | |||
| 36119facf0 | |||
| a1fa1ddf5d | |||
| ba770dce73 | |||
| d58f0806f6 | |||
| a95f8fa873 | |||
| d1a9cf98cc | |||
| be29fc6adb | |||
| c1085e965b | |||
| dc5c997e9b | |||
| 040acbf65a | |||
| d089d036e6 | |||
| 46732c7d73 | |||
| 8fd3254745 | |||
| 9b21408a03 | |||
| bd53092f0c | |||
| 80b1aa9a2c | |||
| df863a08a1 | |||
| 8a0c318540 | |||
| 4f9d067361 | |||
| 6739310ba8 | |||
| a45b91abe8 | |||
| 1ad2dc54b3 | |||
| 7f0b708cb9 | |||
| c8cdf03077 | |||
| c8afe578f3 | |||
| 9bb394f420 | |||
| 4f512c5cdf | |||
| 8ae84222d1 | |||
| 7ec0fd1cea | |||
| ad8629fac9 | |||
| 32393eabb7 | |||
| 6f42dd671f | |||
| 966963b53d | |||
| 43a74ab32a | |||
| 897e4b4d0a | |||
| 0dd639ff76 | |||
| 64da17a9ab | |||
| dbaabf90a5 | |||
| 1aabc4bc5c | |||
| cce4334310 | |||
| 8d54b4268b | |||
| 2be9b306a3 | |||
| 7f5cd9a765 | |||
| d26ca9a7a5 | |||
| 933796e214 | |||
| 121a347fc9 | |||
| ed060d552f | |||
| 51485259ae | |||
| 76a3ca37ed | |||
| e5e7292f11 | |||
| 397ee20160 | |||
| 2914c3c88f | |||
| 750cb94692 | |||
| 1ffbae9d16 | |||
| a515c5c7a5 | |||
| c58f31d690 | |||
| 2041797587 | |||
| b1d8763593 | |||
| ff7f95a0ab | |||
| ccd561eb24 | |||
| 3e353a6710 | |||
| 544e942fe4 | |||
| 8bb3764708 | |||
| 356013a84e | |||
| 75b2414e29 | |||
| 4084ce5c2f | |||
| ca2f12338e | |||
| 347fdc1572 | |||
| 6c04d0701c | |||
| 3f4e5e55a3 | |||
| 7310481e6a | |||
| e59a1a64f9 | |||
| 5c1ebc72b3 | |||
| a96971beff | |||
| b34f4fde1b | |||
| a9b2635cb0 | |||
| 1ed8bd7350 | |||
| f0891e9df7 | |||
| 5834afe30b | |||
| c015a3ebf0 | |||
| 8fd1f121f4 | |||
| b18cc49a83 | |||
| 69b969cfc8 | |||
| bc3e3714b9 |
+7
-8
@@ -46,7 +46,6 @@ csharp_style_var_elsewhere = true:suggestion
|
|||||||
|
|
||||||
# Stylecop Rules
|
# Stylecop Rules
|
||||||
dotnet_diagnostic.SA0001.severity = none
|
dotnet_diagnostic.SA0001.severity = none
|
||||||
dotnet_diagnostic.SA1005.severity = none
|
|
||||||
dotnet_diagnostic.SA1025.severity = none
|
dotnet_diagnostic.SA1025.severity = none
|
||||||
dotnet_diagnostic.SA1101.severity = none
|
dotnet_diagnostic.SA1101.severity = none
|
||||||
dotnet_diagnostic.SA1116.severity = none
|
dotnet_diagnostic.SA1116.severity = none
|
||||||
@@ -69,6 +68,7 @@ dotnet_diagnostic.SA1406.severity = suggestion
|
|||||||
dotnet_diagnostic.SA1410.severity = suggestion
|
dotnet_diagnostic.SA1410.severity = suggestion
|
||||||
dotnet_diagnostic.SA1411.severity = suggestion
|
dotnet_diagnostic.SA1411.severity = suggestion
|
||||||
dotnet_diagnostic.SA1413.severity = none
|
dotnet_diagnostic.SA1413.severity = none
|
||||||
|
dotnet_diagnostic.SA1512.severity = none
|
||||||
dotnet_diagnostic.SA1516.severity = none
|
dotnet_diagnostic.SA1516.severity = none
|
||||||
dotnet_diagnostic.SA1600.severity = none
|
dotnet_diagnostic.SA1600.severity = none
|
||||||
dotnet_diagnostic.SA1601.severity = none
|
dotnet_diagnostic.SA1601.severity = none
|
||||||
@@ -167,6 +167,7 @@ dotnet_diagnostic.CA1309.severity = suggestion
|
|||||||
dotnet_diagnostic.CA1310.severity = suggestion
|
dotnet_diagnostic.CA1310.severity = suggestion
|
||||||
dotnet_diagnostic.CA1401.severity = suggestion
|
dotnet_diagnostic.CA1401.severity = suggestion
|
||||||
dotnet_diagnostic.CA1416.severity = suggestion
|
dotnet_diagnostic.CA1416.severity = suggestion
|
||||||
|
dotnet_diagnostic.CA1419.severity = suggestion
|
||||||
dotnet_diagnostic.CA1507.severity = suggestion
|
dotnet_diagnostic.CA1507.severity = suggestion
|
||||||
dotnet_diagnostic.CA1508.severity = suggestion
|
dotnet_diagnostic.CA1508.severity = suggestion
|
||||||
dotnet_diagnostic.CA1707.severity = suggestion
|
dotnet_diagnostic.CA1707.severity = suggestion
|
||||||
@@ -182,9 +183,6 @@ dotnet_diagnostic.CA1720.severity = suggestion
|
|||||||
dotnet_diagnostic.CA1721.severity = suggestion
|
dotnet_diagnostic.CA1721.severity = suggestion
|
||||||
dotnet_diagnostic.CA1724.severity = suggestion
|
dotnet_diagnostic.CA1724.severity = suggestion
|
||||||
dotnet_diagnostic.CA1725.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.CA1806.severity = suggestion
|
||||||
dotnet_diagnostic.CA1810.severity = suggestion
|
dotnet_diagnostic.CA1810.severity = suggestion
|
||||||
dotnet_diagnostic.CA1812.severity = suggestion
|
dotnet_diagnostic.CA1812.severity = suggestion
|
||||||
@@ -196,13 +194,11 @@ dotnet_diagnostic.CA1819.severity = suggestion
|
|||||||
dotnet_diagnostic.CA1822.severity = suggestion
|
dotnet_diagnostic.CA1822.severity = suggestion
|
||||||
dotnet_diagnostic.CA1823.severity = suggestion
|
dotnet_diagnostic.CA1823.severity = suggestion
|
||||||
dotnet_diagnostic.CA1824.severity = suggestion
|
dotnet_diagnostic.CA1824.severity = suggestion
|
||||||
|
dotnet_diagnostic.CA1848.severity = suggestion
|
||||||
dotnet_diagnostic.CA2000.severity = suggestion
|
dotnet_diagnostic.CA2000.severity = suggestion
|
||||||
dotnet_diagnostic.CA2002.severity = suggestion
|
dotnet_diagnostic.CA2002.severity = suggestion
|
||||||
dotnet_diagnostic.CA2007.severity = suggestion
|
dotnet_diagnostic.CA2007.severity = suggestion
|
||||||
dotnet_diagnostic.CA2008.severity = suggestion
|
dotnet_diagnostic.CA2008.severity = suggestion
|
||||||
dotnet_diagnostic.CA2009.severity = suggestion
|
|
||||||
dotnet_diagnostic.CA2010.severity = suggestion
|
|
||||||
dotnet_diagnostic.CA2011.severity = suggestion
|
|
||||||
dotnet_diagnostic.CA2012.severity = suggestion
|
dotnet_diagnostic.CA2012.severity = suggestion
|
||||||
dotnet_diagnostic.CA2013.severity = suggestion
|
dotnet_diagnostic.CA2013.severity = suggestion
|
||||||
dotnet_diagnostic.CA2100.severity = suggestion
|
dotnet_diagnostic.CA2100.severity = suggestion
|
||||||
@@ -233,6 +229,9 @@ dotnet_diagnostic.CA2243.severity = suggestion
|
|||||||
dotnet_diagnostic.CA2244.severity = suggestion
|
dotnet_diagnostic.CA2244.severity = suggestion
|
||||||
dotnet_diagnostic.CA2245.severity = suggestion
|
dotnet_diagnostic.CA2245.severity = suggestion
|
||||||
dotnet_diagnostic.CA2246.severity = suggestion
|
dotnet_diagnostic.CA2246.severity = suggestion
|
||||||
|
dotnet_diagnostic.CA2249.severity = suggestion
|
||||||
|
dotnet_diagnostic.CA2251.severity = suggestion
|
||||||
|
dotnet_diagnostic.CA2254.severity = suggestion
|
||||||
dotnet_diagnostic.CA3061.severity = suggestion
|
dotnet_diagnostic.CA3061.severity = suggestion
|
||||||
dotnet_diagnostic.CA3075.severity = suggestion
|
dotnet_diagnostic.CA3075.severity = suggestion
|
||||||
dotnet_diagnostic.CA3076.severity = suggestion
|
dotnet_diagnostic.CA3076.severity = suggestion
|
||||||
@@ -260,7 +259,7 @@ dotnet_diagnostic.CA5392.severity = suggestion
|
|||||||
dotnet_diagnostic.CA5394.severity = suggestion
|
dotnet_diagnostic.CA5394.severity = suggestion
|
||||||
dotnet_diagnostic.CA5397.severity = suggestion
|
dotnet_diagnostic.CA5397.severity = suggestion
|
||||||
|
|
||||||
|
dotnet_diagnostic.SYSLIB0006.severity = none
|
||||||
|
|
||||||
[*.{js,html,js,hbs,less,css}]
|
[*.{js,html,js,hbs,less,css}]
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
|
|||||||
-10
@@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"paths": [
|
|
||||||
"frontend/src/**/*.js",
|
|
||||||
"src/NzbDrone.Core/Localization/Core/*.json"
|
|
||||||
],
|
|
||||||
"ignored": [
|
|
||||||
"**/node_modules/**/*"
|
|
||||||
],
|
|
||||||
"port": 5004
|
|
||||||
}
|
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
# These are supported funding model platforms
|
# These are supported funding model platforms
|
||||||
|
|
||||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
github: radarr
|
||||||
patreon: # Replace with a single Patreon username
|
patreon: # Replace with a single Patreon username
|
||||||
open_collective: radarr
|
open_collective: radarr
|
||||||
ko_fi: # Replace with a single Ko-fi username
|
ko_fi: # Replace with a single Ko-fi username
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ body:
|
|||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
attributes:
|
attributes:
|
||||||
label: Is there an existing issue for this?
|
label: Is there an existing issue for this?
|
||||||
description: Please search to see if an issue already exists for the bug you encountered.
|
description: Please search to see if an open or closed issue already exists for the bug you encountered. If a bug exists and is closed note that it may only be fixed in an unstable branch.
|
||||||
options:
|
options:
|
||||||
- label: I have searched the existing issues
|
- label: I have searched the existing open and closed issues
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
@@ -42,12 +42,14 @@ body:
|
|||||||
- **Docker Install**: Yes
|
- **Docker Install**: Yes
|
||||||
- **Using Reverse Proxy**: No
|
- **Using Reverse Proxy**: No
|
||||||
- **Browser**: Firefox 90 (If UI related)
|
- **Browser**: Firefox 90 (If UI related)
|
||||||
|
- **Database**: Sqlite 3.36.0
|
||||||
value: |
|
value: |
|
||||||
- OS:
|
- OS:
|
||||||
- Radarr:
|
- Radarr:
|
||||||
- Docker Install:
|
- Docker Install:
|
||||||
- Using Reverse Proxy:
|
- Using Reverse Proxy:
|
||||||
- Browser:
|
- Browser:
|
||||||
|
- Database:
|
||||||
render: markdown
|
render: markdown
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ body:
|
|||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
attributes:
|
attributes:
|
||||||
label: Is there an existing issue for this?
|
label: Is there an existing issue for this?
|
||||||
description: Please search to see if an issue already exists for the feature you are requesting.
|
description: Please search to see if an open or closed issue already exists for the feature you are requesting. If a request exists and is closed note that it may only be fixed in an unstable branch.
|
||||||
options:
|
options:
|
||||||
- label: I have searched the existing issues
|
- label: I have searched the existing open and closed issues
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
name: Sync issue to Azure DevOps work item
|
|
||||||
|
|
||||||
on:
|
|
||||||
issues:
|
|
||||||
types:
|
|
||||||
[opened, edited, deleted, closed, reopened, labeled, unlabeled, assigned]
|
|
||||||
|
|
||||||
concurrency: azuresync-${{ github.event.issue.number }}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
alert:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: danhellem/github-actions-issue-to-work-item@master
|
|
||||||
if: "${{ contains(github.event.issue.labels.*.name, 'Type: Bug') == true }}"
|
|
||||||
env:
|
|
||||||
ado_token: "${{ secrets.ADO_PERSONAL_ACCESS_TOKEN }}"
|
|
||||||
github_token: "${{ github.token }}"
|
|
||||||
ado_organization: "Servarr"
|
|
||||||
ado_project: "Servarr"
|
|
||||||
ado_area_path: "Servarr\\Radarr"
|
|
||||||
ado_wit: "Bug"
|
|
||||||
ado_new_state: "New"
|
|
||||||
ado_active_state: "Active"
|
|
||||||
ado_close_state: "Closed"
|
|
||||||
ado_bypassrules: true
|
|
||||||
log_level: 100
|
|
||||||
- uses: danhellem/github-actions-issue-to-work-item@master
|
|
||||||
if: "${{ contains(github.event.issue.labels.*.name, 'Type: Bug') == false }}"
|
|
||||||
env:
|
|
||||||
ado_token: "${{ secrets.ADO_PERSONAL_ACCESS_TOKEN }}"
|
|
||||||
github_token: "${{ github.token }}"
|
|
||||||
ado_organization: "Servarr"
|
|
||||||
ado_project: "Servarr"
|
|
||||||
ado_area_path: "Servarr\\Radarr"
|
|
||||||
ado_wit: "User Story"
|
|
||||||
ado_new_state: "New"
|
|
||||||
ado_active_state: "Active"
|
|
||||||
ado_close_state: "Closed"
|
|
||||||
ado_bypassrules: true
|
|
||||||
log_level: 100
|
|
||||||
@@ -5,8 +5,13 @@ on:
|
|||||||
schedule:
|
schedule:
|
||||||
- cron: '0 0 * * *'
|
- cron: '0 0 * * *'
|
||||||
|
|
||||||
|
permissions: {}
|
||||||
jobs:
|
jobs:
|
||||||
lock:
|
lock:
|
||||||
|
permissions:
|
||||||
|
issues: write # to lock issues (dessant/lock-threads)
|
||||||
|
pull-requests: write # to lock PRs (dessant/lock-threads)
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: dessant/lock-threads@v2
|
- uses: dessant/lock-threads@v2
|
||||||
|
|||||||
@@ -4,8 +4,12 @@ on:
|
|||||||
issues:
|
issues:
|
||||||
types: [labeled, unlabeled, reopened]
|
types: [labeled, unlabeled, reopened]
|
||||||
|
|
||||||
|
permissions: {}
|
||||||
jobs:
|
jobs:
|
||||||
support:
|
support:
|
||||||
|
permissions:
|
||||||
|
issues: write # to modify issues
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: dessant/support-requests@v2
|
- uses: dessant/support-requests@v2
|
||||||
|
|||||||
+5
-20
@@ -166,27 +166,12 @@ packages.config.md5sum
|
|||||||
|
|
||||||
# Common IntelliJ Platform excludes
|
# Common IntelliJ Platform excludes
|
||||||
|
|
||||||
# User specific
|
# Ignore Rider projects completely for now
|
||||||
**/.idea/**/workspace.xml
|
.idea/
|
||||||
**/.idea/**/tasks.xml
|
|
||||||
**/.idea/shelf/*
|
|
||||||
**/.idea/dictionaries
|
|
||||||
**/.idea/.idea.Radarr.Posix
|
|
||||||
**/.idea/.idea.Radarr.Windows
|
|
||||||
|
|
||||||
# Sensitive or high-churn files
|
|
||||||
**/.idea/**/dataSources/
|
|
||||||
**/.idea/**/dataSources.ids
|
|
||||||
**/.idea/**/dataSources.xml
|
|
||||||
**/.idea/**/dataSources.local.xml
|
|
||||||
**/.idea/**/sqlDataSources.xml
|
|
||||||
**/.idea/**/dynamic.xml
|
|
||||||
|
|
||||||
# Rider
|
|
||||||
# Rider auto-generates .iml files, and contentModel.xml
|
|
||||||
**/.idea/**/*.iml
|
|
||||||
**/.idea/**/contentModel.xml
|
|
||||||
**/.idea/**/modules.xml
|
|
||||||
# ignore node_modules symlink
|
# ignore node_modules symlink
|
||||||
node_modules
|
node_modules
|
||||||
node_modules.nosync
|
node_modules.nosync
|
||||||
|
|
||||||
|
# API doc generation
|
||||||
|
.config/
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
We as members, contributors, and leaders pledge to make participation in our
|
||||||
|
community a harassment-free experience for everyone, regardless of age, body
|
||||||
|
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||||
|
identity and expression, level of experience, education, socio-economic status,
|
||||||
|
nationality, personal appearance, race, caste, color, religion, or sexual
|
||||||
|
identity and orientation.
|
||||||
|
|
||||||
|
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||||
|
diverse, inclusive, and healthy community.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to a positive environment for our
|
||||||
|
community include:
|
||||||
|
|
||||||
|
* Demonstrating empathy and kindness toward other people
|
||||||
|
* Being respectful of differing opinions, viewpoints, and experiences
|
||||||
|
* Giving and gracefully accepting constructive feedback
|
||||||
|
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||||
|
and learning from the experience
|
||||||
|
* Focusing on what is best not just for us as individuals, but for the overall
|
||||||
|
community
|
||||||
|
|
||||||
|
Examples of unacceptable behavior include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery, and sexual attention or advances of
|
||||||
|
any kind
|
||||||
|
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or email address,
|
||||||
|
without their explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
|
professional setting
|
||||||
|
|
||||||
|
## Enforcement Responsibilities
|
||||||
|
|
||||||
|
Community leaders are responsible for clarifying and enforcing our standards of
|
||||||
|
acceptable behavior and will take appropriate and fair corrective action in
|
||||||
|
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||||
|
or harmful.
|
||||||
|
|
||||||
|
Community leaders have the right and responsibility to remove, edit, or reject
|
||||||
|
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||||
|
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||||
|
decisions when appropriate.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies within all community spaces, and also applies when
|
||||||
|
an individual is officially representing the community in public spaces.
|
||||||
|
Examples of representing our community include using an official e-mail address,
|
||||||
|
posting via an official social media account, or acting as an appointed
|
||||||
|
representative at an online or offline event.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported to the community leaders responsible for enforcement at
|
||||||
|
<development@radarr.video>.
|
||||||
|
All complaints will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
|
All community leaders are obligated to respect the privacy and security of the
|
||||||
|
reporter of any incident.
|
||||||
|
|
||||||
|
## Enforcement Guidelines
|
||||||
|
|
||||||
|
Community leaders will follow these Community Impact Guidelines in determining
|
||||||
|
the consequences for any action they deem in violation of this Code of Conduct:
|
||||||
|
|
||||||
|
### 1. Correction
|
||||||
|
|
||||||
|
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||||
|
unprofessional or unwelcome in the community.
|
||||||
|
|
||||||
|
**Consequence**: A private, written warning from community leaders, providing
|
||||||
|
clarity around the nature of the violation and an explanation of why the
|
||||||
|
behavior was inappropriate. A public apology may be requested.
|
||||||
|
|
||||||
|
### 2. Warning
|
||||||
|
|
||||||
|
**Community Impact**: A violation through a single incident or series of
|
||||||
|
actions.
|
||||||
|
|
||||||
|
**Consequence**: A warning with consequences for continued behavior. No
|
||||||
|
interaction with the people involved, including unsolicited interaction with
|
||||||
|
those enforcing the Code of Conduct, for a specified period of time. This
|
||||||
|
includes avoiding interactions in community spaces as well as external channels
|
||||||
|
like social media. Violating these terms may lead to a temporary or permanent
|
||||||
|
ban.
|
||||||
|
|
||||||
|
### 3. Temporary Ban
|
||||||
|
|
||||||
|
**Community Impact**: A serious violation of community standards, including
|
||||||
|
sustained inappropriate behavior.
|
||||||
|
|
||||||
|
**Consequence**: A temporary ban from any sort of interaction or public
|
||||||
|
communication with the community for a specified period of time. No public or
|
||||||
|
private interaction with the people involved, including unsolicited interaction
|
||||||
|
with those enforcing the Code of Conduct, is allowed during this period.
|
||||||
|
Violating these terms may lead to a permanent ban.
|
||||||
|
|
||||||
|
### 4. Permanent Ban
|
||||||
|
|
||||||
|
**Community Impact**: Demonstrating a pattern of violation of community
|
||||||
|
standards, including sustained inappropriate behavior, harassment of an
|
||||||
|
individual, or aggression toward or disparagement of classes of individuals.
|
||||||
|
|
||||||
|
**Consequence**: A permanent ban from any sort of public interaction within the
|
||||||
|
community.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||||
|
version 2.1, available at
|
||||||
|
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
||||||
|
|
||||||
|
Community Impact Guidelines were inspired by
|
||||||
|
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||||
|
|
||||||
|
For answers to common questions about this code of conduct, see the FAQ at
|
||||||
|
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
||||||
|
[https://www.contributor-covenant.org/translations][translations].
|
||||||
|
|
||||||
|
[homepage]: https://www.contributor-covenant.org
|
||||||
|
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
||||||
|
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||||
|
[FAQ]: https://www.contributor-covenant.org/faq
|
||||||
|
[translations]: https://www.contributor-covenant.org/translations
|
||||||
@@ -76,6 +76,15 @@ Thank you to [<img src="/Logo/jetbrains.svg" alt="JetBrains" width="32"> JetBrai
|
|||||||
* [<img src="/Logo/rider.svg" alt="Rider" width="32"> Rider](http://www.jetbrains.com/rider/)
|
* [<img src="/Logo/rider.svg" alt="Rider" width="32"> Rider](http://www.jetbrains.com/rider/)
|
||||||
* [<img src="/Logo/dottrace.svg" alt="dotTrace" width="32"> dotTrace](http://www.jetbrains.com/dottrace/)
|
* [<img src="/Logo/dottrace.svg" alt="dotTrace" width="32"> dotTrace](http://www.jetbrains.com/dottrace/)
|
||||||
|
|
||||||
|
## DigitalOcean
|
||||||
|
|
||||||
|
This project is also supported by DigitalOcean
|
||||||
|
<p>
|
||||||
|
<a href="https://www.digitalocean.com/">
|
||||||
|
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" width="201px">
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
### License
|
### License
|
||||||
|
|
||||||
* [GNU GPL v3](http://www.gnu.org/licenses/gpl.html)
|
* [GNU GPL v3](http://www.gnu.org/licenses/gpl.html)
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
Please report (suspected) security vulnerabilities on Discord (preferred) to
|
||||||
|
any of the Servarr Dev role holders (red names) or via email: development@servarr.com. You will receive a response from
|
||||||
|
us within 72 hours. If the issue is confirmed, we will release a patch as soon
|
||||||
|
as possible depending on complexity/severity.
|
||||||
+296
-83
@@ -7,21 +7,27 @@ variables:
|
|||||||
outputFolder: './_output'
|
outputFolder: './_output'
|
||||||
artifactsFolder: './_artifacts'
|
artifactsFolder: './_artifacts'
|
||||||
testsFolder: './_tests'
|
testsFolder: './_tests'
|
||||||
majorVersion: '4.0.5'
|
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||||
|
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||||
|
majorVersion: '5.0.0'
|
||||||
minorVersion: $[counter('minorVersion', 2000)]
|
minorVersion: $[counter('minorVersion', 2000)]
|
||||||
radarrVersion: '$(majorVersion).$(minorVersion)'
|
radarrVersion: '$(majorVersion).$(minorVersion)'
|
||||||
buildName: '$(Build.SourceBranchName).$(radarrVersion)'
|
buildName: '$(Build.SourceBranchName).$(radarrVersion)'
|
||||||
sentryOrg: 'servarr'
|
sentryOrg: 'servarr'
|
||||||
sentryUrl: 'https://sentry.servarr.com'
|
sentryUrl: 'https://sentry.servarr.com'
|
||||||
dotnetVersion: '6.0.101'
|
dotnetVersion: '6.0.400'
|
||||||
|
nodeVersion: '16.X'
|
||||||
innoVersion: '6.2.0'
|
innoVersion: '6.2.0'
|
||||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
windowsImage: 'windows-2022'
|
||||||
|
linuxImage: 'ubuntu-20.04'
|
||||||
|
macImage: 'macOS-11'
|
||||||
|
|
||||||
trigger:
|
trigger:
|
||||||
branches:
|
branches:
|
||||||
include:
|
include:
|
||||||
- develop
|
- develop
|
||||||
- master
|
- master
|
||||||
|
- zeus
|
||||||
|
|
||||||
pr:
|
pr:
|
||||||
branches:
|
branches:
|
||||||
@@ -30,6 +36,7 @@ pr:
|
|||||||
paths:
|
paths:
|
||||||
exclude:
|
exclude:
|
||||||
- src/NzbDrone.Core/Localization/Core
|
- src/NzbDrone.Core/Localization/Core
|
||||||
|
- src/Radarr.Api.*/openapi.json
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- stage: Setup
|
- stage: Setup
|
||||||
@@ -38,7 +45,7 @@ stages:
|
|||||||
- job:
|
- job:
|
||||||
displayName: Build Variables
|
displayName: Build Variables
|
||||||
pool:
|
pool:
|
||||||
vmImage: 'ubuntu-18.04'
|
vmImage: ${{ variables.linuxImage }}
|
||||||
steps:
|
steps:
|
||||||
# Set the build name properly. The 'name' property won't recursively expand so hack here:
|
# Set the build name properly. The 'name' property won't recursively expand so hack here:
|
||||||
- bash: echo "##vso[build.updatebuildnumber]$RADARRVERSION"
|
- bash: echo "##vso[build.updatebuildnumber]$RADARRVERSION"
|
||||||
@@ -64,15 +71,15 @@ stages:
|
|||||||
matrix:
|
matrix:
|
||||||
Linux:
|
Linux:
|
||||||
osName: 'Linux'
|
osName: 'Linux'
|
||||||
imageName: 'ubuntu-18.04'
|
imageName: ${{ variables.linuxImage }}
|
||||||
enableAnalysis: 'true'
|
enableAnalysis: 'true'
|
||||||
Mac:
|
Mac:
|
||||||
osName: 'Mac'
|
osName: 'Mac'
|
||||||
imageName: 'macos-10.15'
|
imageName: ${{ variables.macImage }}
|
||||||
enableAnalysis: 'false'
|
enableAnalysis: 'false'
|
||||||
Windows:
|
Windows:
|
||||||
osName: 'Windows'
|
osName: 'Windows'
|
||||||
imageName: 'windows-2019'
|
imageName: ${{ variables.windowsImage }}
|
||||||
enableAnalysis: 'false'
|
enableAnalysis: 'false'
|
||||||
|
|
||||||
pool:
|
pool:
|
||||||
@@ -91,15 +98,14 @@ stages:
|
|||||||
- bash: |
|
- bash: |
|
||||||
BUNDLEDVERSIONS=${AGENT_TOOLSDIRECTORY}/dotnet/sdk/${DOTNETVERSION}/Microsoft.NETCoreSdk.BundledVersions.props
|
BUNDLEDVERSIONS=${AGENT_TOOLSDIRECTORY}/dotnet/sdk/${DOTNETVERSION}/Microsoft.NETCoreSdk.BundledVersions.props
|
||||||
echo $BUNDLEDVERSIONS
|
echo $BUNDLEDVERSIONS
|
||||||
grep osx-x64 $BUNDLEDVERSIONS
|
|
||||||
if grep -q freebsd-x64 $BUNDLEDVERSIONS; then
|
if grep -q freebsd-x64 $BUNDLEDVERSIONS; then
|
||||||
echo "BSD already enabled"
|
echo "Extra platforms already enabled"
|
||||||
else
|
else
|
||||||
echo "Enabling BSD support"
|
echo "Enabling extra platform support"
|
||||||
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64/' $BUNDLEDVERSIONS
|
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64;linux-x86/' $BUNDLEDVERSIONS
|
||||||
fi
|
fi
|
||||||
displayName: Enable FreeBSD Support
|
displayName: Enable Extra Platform Support
|
||||||
- bash: ./build.sh --backend --enable-bsd
|
- bash: ./build.sh --backend --enable-extra-platforms
|
||||||
displayName: Build Radarr Backend
|
displayName: Build Radarr Backend
|
||||||
- bash: |
|
- bash: |
|
||||||
find ${OUTPUTFOLDER} -type f ! -path "*/publish/*" -exec rm -rf {} \;
|
find ${OUTPUTFOLDER} -type f ! -path "*/publish/*" -exec rm -rf {} \;
|
||||||
@@ -113,24 +119,28 @@ stages:
|
|||||||
displayName: Publish Backend
|
displayName: Publish Backend
|
||||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||||
- publish: '$(testsFolder)/net6.0/win-x64/publish'
|
- publish: '$(testsFolder)/net6.0/win-x64/publish'
|
||||||
artifact: WindowsCoreTests
|
artifact: win-x64-tests
|
||||||
displayName: Publish Windows Test Package
|
displayName: Publish win-x64 Test Package
|
||||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||||
- publish: '$(testsFolder)/net6.0/linux-x64/publish'
|
- publish: '$(testsFolder)/net6.0/linux-x64/publish'
|
||||||
artifact: LinuxCoreTests
|
artifact: linux-x64-tests
|
||||||
displayName: Publish Linux Test Package
|
displayName: Publish linux-x64 Test Package
|
||||||
|
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||||
|
- publish: '$(testsFolder)/net6.0/linux-x86/publish'
|
||||||
|
artifact: linux-x86-tests
|
||||||
|
displayName: Publish linux-x86 Test Package
|
||||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||||
- publish: '$(testsFolder)/net6.0/linux-musl-x64/publish'
|
- publish: '$(testsFolder)/net6.0/linux-musl-x64/publish'
|
||||||
artifact: LinuxMuslCoreTests
|
artifact: linux-musl-x64-tests
|
||||||
displayName: Publish Linux Musl Test Package
|
displayName: Publish linux-musl-x64 Test Package
|
||||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||||
- publish: '$(testsFolder)/net6.0/freebsd-x64/publish'
|
- publish: '$(testsFolder)/net6.0/freebsd-x64/publish'
|
||||||
artifact: FreebsdCoreTests
|
artifact: freebsd-x64-tests
|
||||||
displayName: Publish FreeBSD Test Package
|
displayName: Publish freebsd-x64 Test Package
|
||||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||||
- publish: '$(testsFolder)/net6.0/osx-x64/publish'
|
- publish: '$(testsFolder)/net6.0/osx-x64/publish'
|
||||||
artifact: MacCoreTests
|
artifact: osx-x64-tests
|
||||||
displayName: Publish MacOS Test Package
|
displayName: Publish osx-x64 Test Package
|
||||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||||
|
|
||||||
- stage: Build_Frontend
|
- stage: Build_Frontend
|
||||||
@@ -142,20 +152,20 @@ stages:
|
|||||||
matrix:
|
matrix:
|
||||||
Linux:
|
Linux:
|
||||||
osName: 'Linux'
|
osName: 'Linux'
|
||||||
imageName: 'ubuntu-18.04'
|
imageName: ${{ variables.linuxImage }}
|
||||||
Mac:
|
Mac:
|
||||||
osName: 'Mac'
|
osName: 'Mac'
|
||||||
imageName: 'macos-10.15'
|
imageName: ${{ variables.macImage }}
|
||||||
Windows:
|
Windows:
|
||||||
osName: 'Windows'
|
osName: 'Windows'
|
||||||
imageName: 'windows-2019'
|
imageName: ${{ variables.windowsImage }}
|
||||||
pool:
|
pool:
|
||||||
vmImage: $(imageName)
|
vmImage: $(imageName)
|
||||||
steps:
|
steps:
|
||||||
- task: NodeTool@0
|
- task: NodeTool@0
|
||||||
displayName: Set Node.js version
|
displayName: Set Node.js version
|
||||||
inputs:
|
inputs:
|
||||||
versionSpec: '12.x'
|
versionSpec: $(nodeVersion)
|
||||||
- checkout: self
|
- checkout: self
|
||||||
submodules: true
|
submodules: true
|
||||||
fetchDepth: 1
|
fetchDepth: 1
|
||||||
@@ -164,7 +174,6 @@ stages:
|
|||||||
key: 'yarn | "$(osName)" | yarn.lock'
|
key: 'yarn | "$(osName)" | yarn.lock'
|
||||||
restoreKeys: |
|
restoreKeys: |
|
||||||
yarn | "$(osName)"
|
yarn | "$(osName)"
|
||||||
yarn
|
|
||||||
path: $(yarnCacheFolder)
|
path: $(yarnCacheFolder)
|
||||||
displayName: Cache Yarn packages
|
displayName: Cache Yarn packages
|
||||||
- bash: ./build.sh --frontend
|
- bash: ./build.sh --frontend
|
||||||
@@ -185,7 +194,7 @@ stages:
|
|||||||
- job: Windows_Installer
|
- job: Windows_Installer
|
||||||
displayName: Create Installer
|
displayName: Create Installer
|
||||||
pool:
|
pool:
|
||||||
vmImage: 'windows-2019'
|
vmImage: ${{ variables.windowsImage }}
|
||||||
steps:
|
steps:
|
||||||
- checkout: self
|
- checkout: self
|
||||||
fetchDepth: 1
|
fetchDepth: 1
|
||||||
@@ -218,7 +227,7 @@ stages:
|
|||||||
- job: Other_Packages
|
- job: Other_Packages
|
||||||
displayName: Create Standard Packages
|
displayName: Create Standard Packages
|
||||||
pool:
|
pool:
|
||||||
vmImage: 'ubuntu-18.04'
|
vmImage: ${{ variables.linuxImage }}
|
||||||
steps:
|
steps:
|
||||||
- checkout: self
|
- checkout: self
|
||||||
fetchDepth: 1
|
fetchDepth: 1
|
||||||
@@ -234,7 +243,7 @@ stages:
|
|||||||
artifactName: WindowsFrontend
|
artifactName: WindowsFrontend
|
||||||
targetPath: _output
|
targetPath: _output
|
||||||
displayName: Fetch Frontend
|
displayName: Fetch Frontend
|
||||||
- bash: ./build.sh --packages --enable-bsd
|
- bash: ./build.sh --packages --enable-extra-platforms
|
||||||
displayName: Create Packages
|
displayName: Create Packages
|
||||||
- bash: |
|
- bash: |
|
||||||
find . -name "ffprobe" -exec chmod a+x {} \;
|
find . -name "ffprobe" -exec chmod a+x {} \;
|
||||||
@@ -242,28 +251,28 @@ stages:
|
|||||||
find . -name "Radarr.Update" -exec chmod a+x {} \;
|
find . -name "Radarr.Update" -exec chmod a+x {} \;
|
||||||
displayName: Set executable bits
|
displayName: Set executable bits
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create Windows Core zip
|
displayName: Create win-x64 zip
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).windows-core-x64.zip'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).windows-core-x64.zip'
|
||||||
archiveType: 'zip'
|
archiveType: 'zip'
|
||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/win-x64/net6.0
|
rootFolderOrFile: $(artifactsFolder)/win-x64/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create Windows x86 Core zip
|
displayName: Create win-x86 zip
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).windows-core-x86.zip'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).windows-core-x86.zip'
|
||||||
archiveType: 'zip'
|
archiveType: 'zip'
|
||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/win-x86/net6.0
|
rootFolderOrFile: $(artifactsFolder)/win-x86/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create MacOS x64 Core app
|
displayName: Create osx-x64 app
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).osx-app-core-x64.zip'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).osx-app-core-x64.zip'
|
||||||
archiveType: 'zip'
|
archiveType: 'zip'
|
||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/osx-x64-app/net6.0
|
rootFolderOrFile: $(artifactsFolder)/osx-x64-app/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create MacOS x64 Core tar
|
displayName: Create osx-x64 tar
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).osx-core-x64.tar.gz'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).osx-core-x64.tar.gz'
|
||||||
archiveType: 'tar'
|
archiveType: 'tar'
|
||||||
@@ -271,14 +280,14 @@ stages:
|
|||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/osx-x64/net6.0
|
rootFolderOrFile: $(artifactsFolder)/osx-x64/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create MacOS arm64 Core app
|
displayName: Create osx-arm64 app
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).osx-app-core-arm64.zip'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).osx-app-core-arm64.zip'
|
||||||
archiveType: 'zip'
|
archiveType: 'zip'
|
||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/osx-arm64-app/net6.0
|
rootFolderOrFile: $(artifactsFolder)/osx-arm64-app/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create MacOS arm64 Core tar
|
displayName: Create osx-arm64 tar
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).osx-core-arm64.tar.gz'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).osx-core-arm64.tar.gz'
|
||||||
archiveType: 'tar'
|
archiveType: 'tar'
|
||||||
@@ -286,7 +295,7 @@ stages:
|
|||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/osx-arm64/net6.0
|
rootFolderOrFile: $(artifactsFolder)/osx-arm64/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create Linux Core tar
|
displayName: Create linux-x64 tar
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).linux-core-x64.tar.gz'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).linux-core-x64.tar.gz'
|
||||||
archiveType: 'tar'
|
archiveType: 'tar'
|
||||||
@@ -294,7 +303,7 @@ stages:
|
|||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/linux-x64/net6.0
|
rootFolderOrFile: $(artifactsFolder)/linux-x64/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create Linux Musl Core tar
|
displayName: Create linux-musl-x64 tar
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).linux-musl-core-x64.tar.gz'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).linux-musl-core-x64.tar.gz'
|
||||||
archiveType: 'tar'
|
archiveType: 'tar'
|
||||||
@@ -302,7 +311,15 @@ stages:
|
|||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/linux-musl-x64/net6.0
|
rootFolderOrFile: $(artifactsFolder)/linux-musl-x64/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create ARM32 Linux Core tar
|
displayName: Create linux-x86 tar
|
||||||
|
inputs:
|
||||||
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).linux-core-x86.tar.gz'
|
||||||
|
archiveType: 'tar'
|
||||||
|
tarCompression: 'gz'
|
||||||
|
includeRootFolder: false
|
||||||
|
rootFolderOrFile: $(artifactsFolder)/linux-x86/net6.0
|
||||||
|
- task: ArchiveFiles@2
|
||||||
|
displayName: Create linux-arm tar
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).linux-core-arm.tar.gz'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).linux-core-arm.tar.gz'
|
||||||
archiveType: 'tar'
|
archiveType: 'tar'
|
||||||
@@ -310,7 +327,7 @@ stages:
|
|||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/linux-arm/net6.0
|
rootFolderOrFile: $(artifactsFolder)/linux-arm/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create ARM32 Linux Musl Core tar
|
displayName: Create linux-musl-arm tar
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).linux-musl-core-arm.tar.gz'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).linux-musl-core-arm.tar.gz'
|
||||||
archiveType: 'tar'
|
archiveType: 'tar'
|
||||||
@@ -318,7 +335,7 @@ stages:
|
|||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/linux-musl-arm/net6.0
|
rootFolderOrFile: $(artifactsFolder)/linux-musl-arm/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create ARM64 Linux Core tar
|
displayName: Create linux-arm64 tar
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).linux-core-arm64.tar.gz'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).linux-core-arm64.tar.gz'
|
||||||
archiveType: 'tar'
|
archiveType: 'tar'
|
||||||
@@ -326,7 +343,7 @@ stages:
|
|||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/linux-arm64/net6.0
|
rootFolderOrFile: $(artifactsFolder)/linux-arm64/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create ARM64 Linux Musl Core tar
|
displayName: Create linux-musl-arm64 tar
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).linux-musl-core-arm64.tar.gz'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).linux-musl-core-arm64.tar.gz'
|
||||||
archiveType: 'tar'
|
archiveType: 'tar'
|
||||||
@@ -334,7 +351,7 @@ stages:
|
|||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/linux-musl-arm64/net6.0
|
rootFolderOrFile: $(artifactsFolder)/linux-musl-arm64/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create FreeBSD Core Core tar
|
displayName: Create freebsd-x64 tar
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).freebsd-core-x64.tar.gz'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).freebsd-core-x64.tar.gz'
|
||||||
archiveType: 'tar'
|
archiveType: 'tar'
|
||||||
@@ -379,7 +396,7 @@ stages:
|
|||||||
jobs:
|
jobs:
|
||||||
- job: Prepare
|
- job: Prepare
|
||||||
pool:
|
pool:
|
||||||
vmImage: 'ubuntu-18.04'
|
vmImage: ${{ variables.linuxImage }}
|
||||||
steps:
|
steps:
|
||||||
- checkout: none
|
- checkout: none
|
||||||
- task: DownloadPipelineArtifact@2
|
- task: DownloadPipelineArtifact@2
|
||||||
@@ -401,22 +418,22 @@ stages:
|
|||||||
matrix:
|
matrix:
|
||||||
MacCore:
|
MacCore:
|
||||||
osName: 'Mac'
|
osName: 'Mac'
|
||||||
testName: 'MacCore'
|
testName: 'osx-x64'
|
||||||
poolName: 'Azure Pipelines'
|
poolName: 'Azure Pipelines'
|
||||||
imageName: 'macos-10.15'
|
imageName: ${{ variables.macImage }}
|
||||||
WindowsCore:
|
WindowsCore:
|
||||||
osName: 'Windows'
|
osName: 'Windows'
|
||||||
testName: 'WindowsCore'
|
testName: 'win-x64'
|
||||||
poolName: 'Azure Pipelines'
|
poolName: 'Azure Pipelines'
|
||||||
imageName: 'windows-2019'
|
imageName: ${{ variables.windowsImage }}
|
||||||
LinuxCore:
|
LinuxCore:
|
||||||
osName: 'Linux'
|
osName: 'Linux'
|
||||||
testName: 'LinuxCore'
|
testName: 'linux-x64'
|
||||||
poolName: 'Azure Pipelines'
|
poolName: 'Azure Pipelines'
|
||||||
imageName: 'ubuntu-18.04'
|
imageName: ${{ variables.linuxImage }}
|
||||||
FreebsdCore:
|
FreebsdCore:
|
||||||
osName: 'Linux'
|
osName: 'Linux'
|
||||||
testName: 'FreebsdCore'
|
testName: 'freebsd-x64'
|
||||||
poolName: 'FreeBSD'
|
poolName: 'FreeBSD'
|
||||||
imageName:
|
imageName:
|
||||||
|
|
||||||
@@ -435,7 +452,7 @@ stages:
|
|||||||
displayName: Download Test Artifact
|
displayName: Download Test Artifact
|
||||||
inputs:
|
inputs:
|
||||||
buildType: 'current'
|
buildType: 'current'
|
||||||
artifactName: '$(testName)Tests'
|
artifactName: '$(testName)-tests'
|
||||||
targetPath: $(testsFolder)
|
targetPath: $(testsFolder)
|
||||||
- powershell: Set-Service SCardSvr -StartupType Manual
|
- powershell: Set-Service SCardSvr -StartupType Manual
|
||||||
displayName: Enable Windows Test Service
|
displayName: Enable Windows Test Service
|
||||||
@@ -469,11 +486,15 @@ stages:
|
|||||||
matrix:
|
matrix:
|
||||||
alpine:
|
alpine:
|
||||||
testName: 'Musl Net Core'
|
testName: 'Musl Net Core'
|
||||||
artifactName: LinuxMuslCoreTests
|
artifactName: linux-musl-x64-tests
|
||||||
containerImage: ghcr.io/servarr/testimages:alpine
|
containerImage: ghcr.io/servarr/testimages:alpine
|
||||||
|
linux-x86:
|
||||||
|
testName: 'linux-x86'
|
||||||
|
artifactName: linux-x86-tests
|
||||||
|
containerImage: ghcr.io/servarr/testimages:linux-x86
|
||||||
|
|
||||||
pool:
|
pool:
|
||||||
vmImage: 'ubuntu-18.04'
|
vmImage: ${{ variables.linuxImage }}
|
||||||
|
|
||||||
container: $[ variables['containerImage'] ]
|
container: $[ variables['containerImage'] ]
|
||||||
|
|
||||||
@@ -481,9 +502,15 @@ stages:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- task: UseDotNet@2
|
- task: UseDotNet@2
|
||||||
displayName: 'Install .net core'
|
displayName: 'Install .NET'
|
||||||
inputs:
|
inputs:
|
||||||
version: $(dotnetVersion)
|
version: $(dotnetVersion)
|
||||||
|
condition: and(succeeded(), ne(variables['testName'], 'linux-x86'))
|
||||||
|
- bash: |
|
||||||
|
SDKURL=$(curl -s https://api.github.com/repos/Servarr/dotnet-linux-x86/releases | jq -rc '.[].assets[].browser_download_url' | grep sdk-${DOTNETVERSION}.*gz$)
|
||||||
|
curl -fsSL $SDKURL | tar xzf - -C /opt/dotnet
|
||||||
|
displayName: 'Install .NET'
|
||||||
|
condition: and(succeeded(), eq(variables['testName'], 'linux-x86'))
|
||||||
- checkout: none
|
- checkout: none
|
||||||
- task: DownloadPipelineArtifact@2
|
- task: DownloadPipelineArtifact@2
|
||||||
displayName: Download Test Artifact
|
displayName: Download Test Artifact
|
||||||
@@ -509,6 +536,62 @@ stages:
|
|||||||
testResultsFiles: '**/TestResult.xml'
|
testResultsFiles: '**/TestResult.xml'
|
||||||
testRunTitle: '$(testName) Unit Tests'
|
testRunTitle: '$(testName) Unit Tests'
|
||||||
failTaskOnFailedTests: true
|
failTaskOnFailedTests: true
|
||||||
|
|
||||||
|
- job: Unit_LinuxCore_Postgres
|
||||||
|
displayName: Unit Native LinuxCore with Postgres Database
|
||||||
|
dependsOn: Prepare
|
||||||
|
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||||
|
variables:
|
||||||
|
pattern: 'Radarr.*.linux-core-x64.tar.gz'
|
||||||
|
artifactName: linux-x64-tests
|
||||||
|
Radarr__Postgres__Host: 'localhost'
|
||||||
|
Radarr__Postgres__Port: '5432'
|
||||||
|
Radarr__Postgres__User: 'radarr'
|
||||||
|
Radarr__Postgres__Password: 'radarr'
|
||||||
|
|
||||||
|
pool:
|
||||||
|
vmImage: ${{ variables.linuxImage }}
|
||||||
|
|
||||||
|
timeoutInMinutes: 10
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- task: UseDotNet@2
|
||||||
|
displayName: 'Install .net core'
|
||||||
|
inputs:
|
||||||
|
version: $(dotnetVersion)
|
||||||
|
- checkout: none
|
||||||
|
- task: DownloadPipelineArtifact@2
|
||||||
|
displayName: Download Test Artifact
|
||||||
|
inputs:
|
||||||
|
buildType: 'current'
|
||||||
|
artifactName: $(artifactName)
|
||||||
|
targetPath: $(testsFolder)
|
||||||
|
- bash: |
|
||||||
|
chmod a+x _tests/ffprobe
|
||||||
|
displayName: Make ffprobe Executable
|
||||||
|
- bash: find ${TESTSFOLDER} -name "Radarr.Test.Dummy" -exec chmod a+x {} \;
|
||||||
|
displayName: Make Test Dummy Executable
|
||||||
|
condition: and(succeeded(), ne(variables['osName'], 'Windows'))
|
||||||
|
- bash: |
|
||||||
|
docker run -d --name=postgres14 \
|
||||||
|
-e POSTGRES_PASSWORD=radarr \
|
||||||
|
-e POSTGRES_USER=radarr \
|
||||||
|
-p 5432:5432/tcp \
|
||||||
|
-v /usr/share/zoneinfo/America/Chicago:/etc/localtime:ro \
|
||||||
|
postgres:14
|
||||||
|
displayName: Start postgres
|
||||||
|
- bash: |
|
||||||
|
chmod a+x ${TESTSFOLDER}/test.sh
|
||||||
|
ls -lR ${TESTSFOLDER}
|
||||||
|
${TESTSFOLDER}/test.sh Linux Unit Test
|
||||||
|
displayName: Run Tests
|
||||||
|
- task: PublishTestResults@2
|
||||||
|
displayName: Publish Test Results
|
||||||
|
inputs:
|
||||||
|
testResultsFormat: 'NUnit'
|
||||||
|
testResultsFiles: '**/TestResult.xml'
|
||||||
|
testRunTitle: 'LinuxCore Postgres Unit Tests'
|
||||||
|
failTaskOnFailedTests: true
|
||||||
|
|
||||||
- stage: Integration
|
- stage: Integration
|
||||||
displayName: Integration
|
displayName: Integration
|
||||||
@@ -517,7 +600,7 @@ stages:
|
|||||||
jobs:
|
jobs:
|
||||||
- job: Prepare
|
- job: Prepare
|
||||||
pool:
|
pool:
|
||||||
vmImage: 'ubuntu-18.04'
|
vmImage: ${{ variables.linuxImage }}
|
||||||
steps:
|
steps:
|
||||||
- checkout: none
|
- checkout: none
|
||||||
- task: DownloadPipelineArtifact@2
|
- task: DownloadPipelineArtifact@2
|
||||||
@@ -536,18 +619,18 @@ stages:
|
|||||||
matrix:
|
matrix:
|
||||||
MacCore:
|
MacCore:
|
||||||
osName: 'Mac'
|
osName: 'Mac'
|
||||||
testName: 'MacCore'
|
testName: 'osx-x64'
|
||||||
imageName: 'macos-10.15'
|
imageName: ${{ variables.macImage }}
|
||||||
pattern: 'Radarr.*.osx-core-x64.tar.gz'
|
pattern: 'Radarr.*.osx-core-x64.tar.gz'
|
||||||
WindowsCore:
|
WindowsCore:
|
||||||
osName: 'Windows'
|
osName: 'Windows'
|
||||||
testName: 'WindowsCore'
|
testName: 'win-x64'
|
||||||
imageName: 'windows-2019'
|
imageName: ${{ variables.windowsImage }}
|
||||||
pattern: 'Radarr.*.windows-core-x64.zip'
|
pattern: 'Radarr.*.windows-core-x64.zip'
|
||||||
LinuxCore:
|
LinuxCore:
|
||||||
osName: 'Linux'
|
osName: 'Linux'
|
||||||
testName: 'LinuxCore'
|
testName: 'linux-x64'
|
||||||
imageName: 'ubuntu-18.04'
|
imageName: ${{ variables.linuxImage }}
|
||||||
pattern: 'Radarr.*.linux-core-x64.tar.gz'
|
pattern: 'Radarr.*.linux-core-x64.tar.gz'
|
||||||
|
|
||||||
pool:
|
pool:
|
||||||
@@ -563,7 +646,7 @@ stages:
|
|||||||
displayName: Download Test Artifact
|
displayName: Download Test Artifact
|
||||||
inputs:
|
inputs:
|
||||||
buildType: 'current'
|
buildType: 'current'
|
||||||
artifactName: '$(testName)Tests'
|
artifactName: '$(testName)-tests'
|
||||||
targetPath: $(testsFolder)
|
targetPath: $(testsFolder)
|
||||||
- task: DownloadPipelineArtifact@2
|
- task: DownloadPipelineArtifact@2
|
||||||
displayName: Download Build Artifact
|
displayName: Download Build Artifact
|
||||||
@@ -593,6 +676,68 @@ stages:
|
|||||||
failTaskOnFailedTests: true
|
failTaskOnFailedTests: true
|
||||||
displayName: Publish Test Results
|
displayName: Publish Test Results
|
||||||
|
|
||||||
|
- job: Integration_LinuxCore_Postgres
|
||||||
|
displayName: Integration Native LinuxCore with Postgres Database
|
||||||
|
dependsOn: Prepare
|
||||||
|
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||||
|
variables:
|
||||||
|
pattern: 'Radarr.*.linux-core-x64.tar.gz'
|
||||||
|
Radarr__Postgres__Host: 'localhost'
|
||||||
|
Radarr__Postgres__Port: '5432'
|
||||||
|
Radarr__Postgres__User: 'radarr'
|
||||||
|
Radarr__Postgres__Password: 'radarr'
|
||||||
|
|
||||||
|
pool:
|
||||||
|
vmImage: ${{ variables.linuxImage }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- task: UseDotNet@2
|
||||||
|
displayName: 'Install .net core'
|
||||||
|
inputs:
|
||||||
|
version: $(dotnetVersion)
|
||||||
|
- checkout: none
|
||||||
|
- task: DownloadPipelineArtifact@2
|
||||||
|
displayName: Download Test Artifact
|
||||||
|
inputs:
|
||||||
|
buildType: 'current'
|
||||||
|
artifactName: 'linux-x64-tests'
|
||||||
|
targetPath: $(testsFolder)
|
||||||
|
- task: DownloadPipelineArtifact@2
|
||||||
|
displayName: Download Build Artifact
|
||||||
|
inputs:
|
||||||
|
buildType: 'current'
|
||||||
|
artifactName: Packages
|
||||||
|
itemPattern: '**/$(pattern)'
|
||||||
|
targetPath: $(Build.ArtifactStagingDirectory)
|
||||||
|
- task: ExtractFiles@1
|
||||||
|
inputs:
|
||||||
|
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
|
||||||
|
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
|
||||||
|
displayName: Extract Package
|
||||||
|
- bash: |
|
||||||
|
mkdir -p ./bin/
|
||||||
|
cp -r -v ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin/Radarr/. ./bin/
|
||||||
|
displayName: Move Package Contents
|
||||||
|
- bash: |
|
||||||
|
docker run -d --name=postgres14 \
|
||||||
|
-e POSTGRES_PASSWORD=radarr \
|
||||||
|
-e POSTGRES_USER=radarr \
|
||||||
|
-p 5432:5432/tcp \
|
||||||
|
-v /usr/share/zoneinfo/America/Chicago:/etc/localtime:ro \
|
||||||
|
postgres:14
|
||||||
|
displayName: Start postgres
|
||||||
|
- 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: 'Integration LinuxCore Postgres Database Integration Tests'
|
||||||
|
failTaskOnFailedTests: true
|
||||||
|
displayName: Publish Test Results
|
||||||
|
|
||||||
- job: Integration_FreeBSD
|
- job: Integration_FreeBSD
|
||||||
displayName: Integration Native FreeBSD
|
displayName: Integration Native FreeBSD
|
||||||
dependsOn: Prepare
|
dependsOn: Prepare
|
||||||
@@ -610,14 +755,14 @@ stages:
|
|||||||
displayName: Download Test Artifact
|
displayName: Download Test Artifact
|
||||||
inputs:
|
inputs:
|
||||||
buildType: 'current'
|
buildType: 'current'
|
||||||
artifactName: 'FreebsdCoreTests'
|
artifactName: 'freebsd-x64-tests'
|
||||||
targetPath: $(testsFolder)
|
targetPath: $(testsFolder)
|
||||||
- task: DownloadPipelineArtifact@2
|
- task: DownloadPipelineArtifact@2
|
||||||
displayName: Download Build Artifact
|
displayName: Download Build Artifact
|
||||||
inputs:
|
inputs:
|
||||||
buildType: 'current'
|
buildType: 'current'
|
||||||
artifactName: Packages
|
artifactName: Packages
|
||||||
itemPattern: '/$(pattern)'
|
itemPattern: '**/$(pattern)'
|
||||||
targetPath: $(Build.ArtifactStagingDirectory)
|
targetPath: $(Build.ArtifactStagingDirectory)
|
||||||
- bash: |
|
- bash: |
|
||||||
mkdir -p ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin
|
mkdir -p ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin
|
||||||
@@ -646,12 +791,17 @@ stages:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
alpine:
|
alpine:
|
||||||
testName: 'Musl Net Core'
|
testName: 'linux-musl-x64'
|
||||||
artifactName: LinuxMuslCoreTests
|
artifactName: linux-musl-x64-tests
|
||||||
containerImage: ghcr.io/servarr/testimages:alpine
|
containerImage: ghcr.io/servarr/testimages:alpine
|
||||||
pattern: 'Radarr.*.linux-musl-core-x64.tar.gz'
|
pattern: 'Radarr.*.linux-musl-core-x64.tar.gz'
|
||||||
|
linux-x86:
|
||||||
|
testName: 'linux-x86'
|
||||||
|
artifactName: linux-x86-tests
|
||||||
|
containerImage: ghcr.io/servarr/testimages:linux-x86
|
||||||
|
pattern: 'Radarr.*.linux-core-x86.tar.gz'
|
||||||
pool:
|
pool:
|
||||||
vmImage: 'ubuntu-18.04'
|
vmImage: ${{ variables.linuxImage }}
|
||||||
|
|
||||||
container: $[ variables['containerImage'] ]
|
container: $[ variables['containerImage'] ]
|
||||||
|
|
||||||
@@ -659,9 +809,15 @@ stages:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- task: UseDotNet@2
|
- task: UseDotNet@2
|
||||||
displayName: 'Install .net core'
|
displayName: 'Install .NET'
|
||||||
inputs:
|
inputs:
|
||||||
version: $(dotnetVersion)
|
version: $(dotnetVersion)
|
||||||
|
condition: and(succeeded(), ne(variables['testName'], 'linux-x86'))
|
||||||
|
- bash: |
|
||||||
|
SDKURL=$(curl -s https://api.github.com/repos/Servarr/dotnet-linux-x86/releases | jq -rc '.[].assets[].browser_download_url' | grep sdk-${DOTNETVERSION}.*gz$)
|
||||||
|
curl -fsSL $SDKURL | tar xzf - -C /opt/dotnet
|
||||||
|
displayName: 'Install .NET'
|
||||||
|
condition: and(succeeded(), eq(variables['testName'], 'linux-x86'))
|
||||||
- checkout: none
|
- checkout: none
|
||||||
- task: DownloadPipelineArtifact@2
|
- task: DownloadPipelineArtifact@2
|
||||||
displayName: Download Test Artifact
|
displayName: Download Test Artifact
|
||||||
@@ -707,17 +863,20 @@ stages:
|
|||||||
matrix:
|
matrix:
|
||||||
Linux:
|
Linux:
|
||||||
osName: 'Linux'
|
osName: 'Linux'
|
||||||
imageName: 'ubuntu-18.04'
|
artifactName: 'linux-x64'
|
||||||
|
imageName: ${{ variables.linuxImage }}
|
||||||
pattern: 'Radarr.*.linux-core-x64.tar.gz'
|
pattern: 'Radarr.*.linux-core-x64.tar.gz'
|
||||||
failBuild: true
|
failBuild: true
|
||||||
Mac:
|
Mac:
|
||||||
osName: 'Mac'
|
osName: 'Mac'
|
||||||
imageName: 'macos-10.15'
|
artifactName: 'osx-x64'
|
||||||
|
imageName: ${{ variables.macImage }}
|
||||||
pattern: 'Radarr.*.osx-core-x64.tar.gz'
|
pattern: 'Radarr.*.osx-core-x64.tar.gz'
|
||||||
failBuild: true
|
failBuild: true
|
||||||
Windows:
|
Windows:
|
||||||
osName: 'Windows'
|
osName: 'Windows'
|
||||||
imageName: 'windows-2019'
|
artifactName: 'win-x64'
|
||||||
|
imageName: ${{ variables.windowsImage }}
|
||||||
pattern: 'Radarr.*.windows-core-x64.zip'
|
pattern: 'Radarr.*.windows-core-x64.zip'
|
||||||
failBuild: true
|
failBuild: true
|
||||||
|
|
||||||
@@ -734,7 +893,7 @@ stages:
|
|||||||
displayName: Download Test Artifact
|
displayName: Download Test Artifact
|
||||||
inputs:
|
inputs:
|
||||||
buildType: 'current'
|
buildType: 'current'
|
||||||
artifactName: '$(osName)CoreTests'
|
artifactName: '$(artifactName)-tests'
|
||||||
targetPath: $(testsFolder)
|
targetPath: $(testsFolder)
|
||||||
- task: DownloadPipelineArtifact@2
|
- task: DownloadPipelineArtifact@2
|
||||||
displayName: Download Build Artifact
|
displayName: Download Build Artifact
|
||||||
@@ -783,7 +942,7 @@ stages:
|
|||||||
jobs:
|
jobs:
|
||||||
- job: Prepare
|
- job: Prepare
|
||||||
pool:
|
pool:
|
||||||
vmImage: 'ubuntu-18.04'
|
vmImage: ${{ variables.linuxImage }}
|
||||||
steps:
|
steps:
|
||||||
- checkout: none
|
- checkout: none
|
||||||
- task: DownloadPipelineArtifact@2
|
- task: DownloadPipelineArtifact@2
|
||||||
@@ -800,17 +959,17 @@ stages:
|
|||||||
matrix:
|
matrix:
|
||||||
Linux:
|
Linux:
|
||||||
osName: 'Linux'
|
osName: 'Linux'
|
||||||
imageName: 'ubuntu-18.04'
|
imageName: ${{ variables.linuxImage }}
|
||||||
Windows:
|
Windows:
|
||||||
osName: 'Windows'
|
osName: 'Windows'
|
||||||
imageName: 'windows-2019'
|
imageName: ${{ variables.windowsImage }}
|
||||||
pool:
|
pool:
|
||||||
vmImage: $(imageName)
|
vmImage: $(imageName)
|
||||||
steps:
|
steps:
|
||||||
- task: NodeTool@0
|
- task: NodeTool@0
|
||||||
displayName: Set Node.js version
|
displayName: Set Node.js version
|
||||||
inputs:
|
inputs:
|
||||||
versionSpec: '12.x'
|
versionSpec: $(nodeVersion)
|
||||||
- checkout: self
|
- checkout: self
|
||||||
submodules: true
|
submodules: true
|
||||||
fetchDepth: 1
|
fetchDepth: 1
|
||||||
@@ -819,7 +978,6 @@ stages:
|
|||||||
key: 'yarn | "$(osName)" | yarn.lock'
|
key: 'yarn | "$(osName)" | yarn.lock'
|
||||||
restoreKeys: |
|
restoreKeys: |
|
||||||
yarn | "$(osName)"
|
yarn | "$(osName)"
|
||||||
yarn
|
|
||||||
path: $(yarnCacheFolder)
|
path: $(yarnCacheFolder)
|
||||||
displayName: Cache Yarn packages
|
displayName: Cache Yarn packages
|
||||||
- bash: ./build.sh --lint
|
- bash: ./build.sh --lint
|
||||||
@@ -832,7 +990,7 @@ stages:
|
|||||||
displayName: Frontend
|
displayName: Frontend
|
||||||
condition: eq(variables['System.PullRequest.IsFork'], 'False')
|
condition: eq(variables['System.PullRequest.IsFork'], 'False')
|
||||||
pool:
|
pool:
|
||||||
vmImage: windows-2019
|
vmImage: ${{ variables.windowsImage }}
|
||||||
steps:
|
steps:
|
||||||
- checkout: self # Need history for Sonar analysis
|
- checkout: self # Need history for Sonar analysis
|
||||||
- task: SonarCloudPrepare@1
|
- task: SonarCloudPrepare@1
|
||||||
@@ -848,6 +1006,60 @@ stages:
|
|||||||
cliProjectVersion: '$(radarrVersion)'
|
cliProjectVersion: '$(radarrVersion)'
|
||||||
cliSources: './frontend'
|
cliSources: './frontend'
|
||||||
- task: SonarCloudAnalyze@1
|
- task: SonarCloudAnalyze@1
|
||||||
|
|
||||||
|
- job: Api_Docs
|
||||||
|
displayName: API Docs
|
||||||
|
dependsOn: Prepare
|
||||||
|
condition: |
|
||||||
|
and
|
||||||
|
(
|
||||||
|
and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/develop')),
|
||||||
|
and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||||
|
)
|
||||||
|
|
||||||
|
pool:
|
||||||
|
vmImage: ${{ variables.windowsImage }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- task: UseDotNet@2
|
||||||
|
displayName: 'Install .net core'
|
||||||
|
inputs:
|
||||||
|
version: $(dotnetVersion)
|
||||||
|
- checkout: self
|
||||||
|
submodules: true
|
||||||
|
persistCredentials: true
|
||||||
|
fetchDepth: 1
|
||||||
|
- bash: ./docs.sh Windows
|
||||||
|
displayName: Create openapi.json
|
||||||
|
- bash: |
|
||||||
|
git config --global user.email "development@lidarr.audio"
|
||||||
|
git config --global user.name "Servarr"
|
||||||
|
git checkout -b api-docs
|
||||||
|
git add .
|
||||||
|
git status
|
||||||
|
if git status | grep modified
|
||||||
|
then
|
||||||
|
git commit -am 'Automated API Docs update'
|
||||||
|
git push -f --set-upstream origin api-docs
|
||||||
|
curl -X POST -H "Authorization: token ${GITHUBTOKEN}" -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/radarr/radarr/pulls -d '{"head":"api-docs","base":"develop","title":"Update API docs"}'
|
||||||
|
else
|
||||||
|
echo "No changes since last run"
|
||||||
|
fi
|
||||||
|
displayName: Commit API Doc Change
|
||||||
|
continueOnError: true
|
||||||
|
env:
|
||||||
|
GITHUBTOKEN: $(githubToken)
|
||||||
|
- task: CopyFiles@2
|
||||||
|
displayName: 'Copy openapi.json to: $(Build.ArtifactStagingDirectory)'
|
||||||
|
inputs:
|
||||||
|
SourceFolder: '$(Build.SourcesDirectory)'
|
||||||
|
Contents: |
|
||||||
|
**/*openapi.json
|
||||||
|
TargetFolder: '$(Build.ArtifactStagingDirectory)/api_docs'
|
||||||
|
- publish: $(Build.ArtifactStagingDirectory)/api_docs
|
||||||
|
artifact: 'APIDocs'
|
||||||
|
displayName: Publish API Docs Bundle
|
||||||
|
condition: and(succeeded(), eq(variables['System.JobAttempt'], '1'))
|
||||||
|
|
||||||
- job: Analyze_Backend
|
- job: Analyze_Backend
|
||||||
displayName: Backend
|
displayName: Backend
|
||||||
@@ -859,7 +1071,7 @@ stages:
|
|||||||
EnableAnalyzers: 'false'
|
EnableAnalyzers: 'false'
|
||||||
|
|
||||||
pool:
|
pool:
|
||||||
vmImage: windows-2019
|
vmImage: ${{ variables.windowsImage }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- task: UseDotNet@2
|
- task: UseDotNet@2
|
||||||
@@ -881,7 +1093,7 @@ stages:
|
|||||||
projectVersion: '$(radarrVersion)'
|
projectVersion: '$(radarrVersion)'
|
||||||
extraProperties: |
|
extraProperties: |
|
||||||
sonar.exclusions=**/obj/**,**/*.dll,**/NzbDrone.Core.Test/Files/**/*,./frontend/**,**/ExternalModules/**,./src/Libraries/**
|
sonar.exclusions=**/obj/**,**/*.dll,**/NzbDrone.Core.Test/Files/**/*,./frontend/**,**/ExternalModules/**,./src/Libraries/**
|
||||||
sonar.coverage.exclusions=**/Radarr.Api.V3/**/*
|
sonar.coverage.exclusions=**/Radarr.Api.V*/**/*
|
||||||
sonar.cs.opencover.reportsPaths=$(Build.SourcesDirectory)/CoverageResults/**/coverage.opencover.xml
|
sonar.cs.opencover.reportsPaths=$(Build.SourcesDirectory)/CoverageResults/**/coverage.opencover.xml
|
||||||
sonar.cs.nunit.reportsPaths=$(Build.SourcesDirectory)/TestResult.xml
|
sonar.cs.nunit.reportsPaths=$(Build.SourcesDirectory)/TestResult.xml
|
||||||
- bash: |
|
- bash: |
|
||||||
@@ -916,7 +1128,7 @@ stages:
|
|||||||
- job:
|
- job:
|
||||||
displayName: Discord Notification
|
displayName: Discord Notification
|
||||||
pool:
|
pool:
|
||||||
vmImage: 'ubuntu-18.04'
|
vmImage: ${{ variables.linuxImage }}
|
||||||
steps:
|
steps:
|
||||||
- task: DownloadPipelineArtifact@2
|
- task: DownloadPipelineArtifact@2
|
||||||
continueOnError: true
|
continueOnError: true
|
||||||
@@ -932,4 +1144,5 @@ stages:
|
|||||||
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
|
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
|
||||||
DISCORDCHANNELID: $(discordChannelId)
|
DISCORDCHANNELID: $(discordChannelId)
|
||||||
DISCORDWEBHOOKKEY: $(discordWebhookKey)
|
DISCORDWEBHOOKKEY: $(discordWebhookKey)
|
||||||
|
DISCORDTHREADID: $(discordThreadId)
|
||||||
|
|
||||||
|
|||||||
@@ -25,14 +25,22 @@ UpdateVersionNumber()
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
EnableBsdSupport()
|
EnableExtraPlatformsInSDK()
|
||||||
{
|
{
|
||||||
#todo enable sdk with
|
SDK_PATH=$(dotnet --list-sdks | grep -P '6\.\d\.\d+' | head -1 | sed 's/\(6\.[0-9]*\.[0-9]*\).*\[\(.*\)\]/\2\/\1/g')
|
||||||
#SDK_PATH=$(dotnet --list-sdks | grep -P '5\.\d\.\d+' | head -1 | sed 's/\(5\.[0-9]*\.[0-9]*\).*\[\(.*\)\]/\2\/\1/g')
|
BUNDLEDVERSIONS="${SDK_PATH}/Microsoft.NETCoreSdk.BundledVersions.props"
|
||||||
# BUNDLED_VERSIONS="${SDK_PATH}/Microsoft.NETCoreSdk.BundledVersions.props"
|
if grep -q freebsd-x64 $BUNDLEDVERSIONS; then
|
||||||
|
echo "Extra platforms already enabled"
|
||||||
|
else
|
||||||
|
echo "Enabling extra platform support"
|
||||||
|
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64;linux-x86/' $BUNDLEDVERSIONS
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
EnableExtraPlatforms()
|
||||||
|
{
|
||||||
if grep -qv freebsd-x64 src/Directory.Build.props; then
|
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^<RuntimeIdentifiers>\(.*\)</RuntimeIdentifiers>^<RuntimeIdentifiers>\1;freebsd-x64;linux-x86</RuntimeIdentifiers>^g" src/Directory.Build.props
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,7 +300,8 @@ if [ $# -eq 0 ]; then
|
|||||||
PACKAGES=YES
|
PACKAGES=YES
|
||||||
INSTALLER=NO
|
INSTALLER=NO
|
||||||
LINT=YES
|
LINT=YES
|
||||||
ENABLE_BSD=NO
|
ENABLE_EXTRA_PLATFORMS=NO
|
||||||
|
ENABLE_EXTRA_PLATFORMS_IN_SDK=NO
|
||||||
fi
|
fi
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]
|
while [[ $# -gt 0 ]]
|
||||||
@@ -304,8 +313,12 @@ case $key in
|
|||||||
BACKEND=YES
|
BACKEND=YES
|
||||||
shift # past argument
|
shift # past argument
|
||||||
;;
|
;;
|
||||||
--enable-bsd)
|
--enable-bsd|--enable-extra-platforms)
|
||||||
ENABLE_BSD=YES
|
ENABLE_EXTRA_PLATFORMS=YES
|
||||||
|
shift # past argument
|
||||||
|
;;
|
||||||
|
--enable-extra-platforms-in-sdk)
|
||||||
|
ENABLE_EXTRA_PLATFORMS_IN_SDK=YES
|
||||||
shift # past argument
|
shift # past argument
|
||||||
;;
|
;;
|
||||||
-r|--runtime)
|
-r|--runtime)
|
||||||
@@ -349,12 +362,17 @@ esac
|
|||||||
done
|
done
|
||||||
set -- "${POSITIONAL[@]}" # restore positional parameters
|
set -- "${POSITIONAL[@]}" # restore positional parameters
|
||||||
|
|
||||||
|
if [ "$ENABLE_EXTRA_PLATFORMS_IN_SDK" = "YES" ];
|
||||||
|
then
|
||||||
|
EnableExtraPlatformsInSDK
|
||||||
|
fi
|
||||||
|
|
||||||
if [ "$BACKEND" = "YES" ];
|
if [ "$BACKEND" = "YES" ];
|
||||||
then
|
then
|
||||||
UpdateVersionNumber
|
UpdateVersionNumber
|
||||||
if [ "$ENABLE_BSD" = "YES" ];
|
if [ "$ENABLE_EXTRA_PLATFORMS" = "YES" ];
|
||||||
then
|
then
|
||||||
EnableBsdSupport
|
EnableExtraPlatforms
|
||||||
fi
|
fi
|
||||||
Build
|
Build
|
||||||
if [[ -z "$RID" || -z "$FRAMEWORK" ]];
|
if [[ -z "$RID" || -z "$FRAMEWORK" ]];
|
||||||
@@ -364,9 +382,10 @@ then
|
|||||||
PackageTests "net6.0" "linux-x64"
|
PackageTests "net6.0" "linux-x64"
|
||||||
PackageTests "net6.0" "linux-musl-x64"
|
PackageTests "net6.0" "linux-musl-x64"
|
||||||
PackageTests "net6.0" "osx-x64"
|
PackageTests "net6.0" "osx-x64"
|
||||||
if [ "$ENABLE_BSD" = "YES" ];
|
if [ "$ENABLE_EXTRA_PLATFORMS" = "YES" ];
|
||||||
then
|
then
|
||||||
PackageTests "net6.0" "freebsd-x64"
|
PackageTests "net6.0" "freebsd-x64"
|
||||||
|
PackageTests "net6.0" "linux-x86"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
PackageTests "$FRAMEWORK" "$RID"
|
PackageTests "$FRAMEWORK" "$RID"
|
||||||
@@ -405,9 +424,10 @@ then
|
|||||||
Package "net6.0" "linux-musl-arm"
|
Package "net6.0" "linux-musl-arm"
|
||||||
Package "net6.0" "osx-x64"
|
Package "net6.0" "osx-x64"
|
||||||
Package "net6.0" "osx-arm64"
|
Package "net6.0" "osx-arm64"
|
||||||
if [ "$ENABLE_BSD" = "YES" ];
|
if [ "$ENABLE_EXTRA_PLATFORMS" = "YES" ];
|
||||||
then
|
then
|
||||||
Package "net6.0" "freebsd-x64"
|
Package "net6.0" "freebsd-x64"
|
||||||
|
Package "net6.0" "linux-x86"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
Package "$FRAMEWORK" "$RID"
|
Package "$FRAMEWORK" "$RID"
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
PLATFORM=$1
|
||||||
|
|
||||||
|
if [ "$PLATFORM" = "Windows" ]; then
|
||||||
|
RUNTIME="win-x64"
|
||||||
|
elif [ "$PLATFORM" = "Linux" ]; then
|
||||||
|
WHERE="linux-x64"
|
||||||
|
elif [ "$PLATFORM" = "Mac" ]; then
|
||||||
|
WHERE="osx-x64"
|
||||||
|
else
|
||||||
|
echo "Platform must be provided as first arguement: Windows, Linux or Mac"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
outputFolder='_output'
|
||||||
|
testPackageFolder='_tests'
|
||||||
|
|
||||||
|
rm -rf $outputFolder
|
||||||
|
rm -rf $testPackageFolder
|
||||||
|
|
||||||
|
slnFile=src/Radarr.sln
|
||||||
|
|
||||||
|
platform=Posix
|
||||||
|
|
||||||
|
dotnet clean $slnFile -c Debug
|
||||||
|
dotnet clean $slnFile -c Release
|
||||||
|
|
||||||
|
dotnet msbuild -restore $slnFile -p:Configuration=Debug -p:Platform=$platform -p:RuntimeIdentifiers=$RUNTIME -t:PublishAllRids
|
||||||
|
|
||||||
|
dotnet new tool-manifest
|
||||||
|
dotnet tool install --version 6.3.0 Swashbuckle.AspNetCore.Cli
|
||||||
|
|
||||||
|
dotnet tool run swagger tofile --output ./src/Radarr.Api.V4/openapi.json "$outputFolder/net6.0/$RUNTIME/radarr.console.dll" v4 &
|
||||||
|
|
||||||
|
sleep 45
|
||||||
|
|
||||||
|
kill %1
|
||||||
|
|
||||||
|
exit 0
|
||||||
@@ -39,6 +39,7 @@ module.exports = {
|
|||||||
plugins: [
|
plugins: [
|
||||||
'filenames',
|
'filenames',
|
||||||
'react',
|
'react',
|
||||||
|
'react-hooks',
|
||||||
'simple-import-sort',
|
'simple-import-sort',
|
||||||
'import'
|
'import'
|
||||||
],
|
],
|
||||||
@@ -308,7 +309,9 @@ module.exports = {
|
|||||||
'react/react-in-jsx-scope': 2,
|
'react/react-in-jsx-scope': 2,
|
||||||
'react/self-closing-comp': 2,
|
'react/self-closing-comp': 2,
|
||||||
'react/sort-comp': 2,
|
'react/sort-comp': 2,
|
||||||
'react/jsx-wrap-multilines': 2
|
'react/jsx-wrap-multilines': 2,
|
||||||
|
'react-hooks/rules-of-hooks': 'error',
|
||||||
|
'react-hooks/exhaustive-deps': 'error'
|
||||||
},
|
},
|
||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -223,7 +223,6 @@ module.exports = (env) => {
|
|||||||
{
|
{
|
||||||
loader: 'url-loader',
|
loader: 'url-loader',
|
||||||
options: {
|
options: {
|
||||||
limit: 10240,
|
|
||||||
mimetype: 'application/font-woff',
|
mimetype: 'application/font-woff',
|
||||||
emitFile: false,
|
emitFile: false,
|
||||||
name: 'Content/Fonts/[name].[ext]'
|
name: 'Content/Fonts/[name].[ext]'
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// eslint-disable-next-line filenames/match-exported
|
||||||
const loaderUtils = require('loader-utils');
|
const loaderUtils = require('loader-utils');
|
||||||
|
|
||||||
module.exports = function cssVariablesLoader(source) {
|
module.exports = function cssVariablesLoader(source) {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
const reload = require('require-nocache')(module);
|
const reload = require('require-nocache')(module);
|
||||||
|
|
||||||
const cssVarsFiles = [
|
const cssVarsFiles = [
|
||||||
'./src/Styles/Variables/colors',
|
|
||||||
'./src/Styles/Variables/dimensions',
|
'./src/Styles/Variables/dimensions',
|
||||||
'./src/Styles/Variables/fonts',
|
'./src/Styles/Variables/fonts',
|
||||||
'./src/Styles/Variables/animations',
|
'./src/Styles/Variables/animations',
|
||||||
@@ -29,4 +28,4 @@ module.exports = {
|
|||||||
'postcss-color-function',
|
'postcss-color-function',
|
||||||
'postcss-nested'
|
'postcss-nested'
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -61,33 +61,33 @@ class Blocklist extends Component {
|
|||||||
|
|
||||||
getSelectedIds = () => {
|
getSelectedIds = () => {
|
||||||
return getSelectedIds(this.state.selectedState);
|
return getSelectedIds(this.state.selectedState);
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Listeners
|
// Listeners
|
||||||
|
|
||||||
onSelectAllChange = ({ value }) => {
|
onSelectAllChange = ({ value }) => {
|
||||||
this.setState(selectAll(this.state.selectedState, value));
|
this.setState(selectAll(this.state.selectedState, value));
|
||||||
}
|
};
|
||||||
|
|
||||||
onSelectedChange = ({ id, value, shiftKey = false }) => {
|
onSelectedChange = ({ id, value, shiftKey = false }) => {
|
||||||
this.setState((state) => {
|
this.setState((state) => {
|
||||||
return toggleSelected(state, this.props.items, id, value, shiftKey);
|
return toggleSelected(state, this.props.items, id, value, shiftKey);
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
onRemoveSelectedPress = () => {
|
onRemoveSelectedPress = () => {
|
||||||
this.setState({ isConfirmRemoveModalOpen: true });
|
this.setState({ isConfirmRemoveModalOpen: true });
|
||||||
}
|
};
|
||||||
|
|
||||||
onRemoveSelectedConfirmed = () => {
|
onRemoveSelectedConfirmed = () => {
|
||||||
this.props.onRemoveSelected(this.getSelectedIds());
|
this.props.onRemoveSelected(this.getSelectedIds());
|
||||||
this.setState({ isConfirmRemoveModalOpen: false });
|
this.setState({ isConfirmRemoveModalOpen: false });
|
||||||
}
|
};
|
||||||
|
|
||||||
onConfirmRemoveModalClose = () => {
|
onConfirmRemoveModalClose = () => {
|
||||||
this.setState({ isConfirmRemoveModalOpen: false });
|
this.setState({ isConfirmRemoveModalOpen: false });
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|||||||
@@ -65,37 +65,37 @@ class BlocklistConnector extends Component {
|
|||||||
|
|
||||||
repopulate = () => {
|
repopulate = () => {
|
||||||
this.props.fetchBlocklist();
|
this.props.fetchBlocklist();
|
||||||
}
|
};
|
||||||
//
|
//
|
||||||
// Listeners
|
// Listeners
|
||||||
|
|
||||||
onFirstPagePress = () => {
|
onFirstPagePress = () => {
|
||||||
this.props.gotoBlocklistFirstPage();
|
this.props.gotoBlocklistFirstPage();
|
||||||
}
|
};
|
||||||
|
|
||||||
onPreviousPagePress = () => {
|
onPreviousPagePress = () => {
|
||||||
this.props.gotoBlocklistPreviousPage();
|
this.props.gotoBlocklistPreviousPage();
|
||||||
}
|
};
|
||||||
|
|
||||||
onNextPagePress = () => {
|
onNextPagePress = () => {
|
||||||
this.props.gotoBlocklistNextPage();
|
this.props.gotoBlocklistNextPage();
|
||||||
}
|
};
|
||||||
|
|
||||||
onLastPagePress = () => {
|
onLastPagePress = () => {
|
||||||
this.props.gotoBlocklistLastPage();
|
this.props.gotoBlocklistLastPage();
|
||||||
}
|
};
|
||||||
|
|
||||||
onPageSelect = (page) => {
|
onPageSelect = (page) => {
|
||||||
this.props.gotoBlocklistPage({ page });
|
this.props.gotoBlocklistPage({ page });
|
||||||
}
|
};
|
||||||
|
|
||||||
onRemoveSelected = (ids) => {
|
onRemoveSelected = (ids) => {
|
||||||
this.props.removeBlocklistItems({ ids });
|
this.props.removeBlocklistItems({ ids });
|
||||||
}
|
};
|
||||||
|
|
||||||
onSortPress = (sortKey) => {
|
onSortPress = (sortKey) => {
|
||||||
this.props.setBlocklistSort({ sortKey });
|
this.props.setBlocklistSort({ sortKey });
|
||||||
}
|
};
|
||||||
|
|
||||||
onTableOptionChange = (payload) => {
|
onTableOptionChange = (payload) => {
|
||||||
this.props.setBlocklistTableOption(payload);
|
this.props.setBlocklistTableOption(payload);
|
||||||
@@ -103,11 +103,11 @@ class BlocklistConnector extends Component {
|
|||||||
if (payload.pageSize) {
|
if (payload.pageSize) {
|
||||||
this.props.gotoBlocklistFirstPage();
|
this.props.gotoBlocklistFirstPage();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
onClearBlocklistPress = () => {
|
onClearBlocklistPress = () => {
|
||||||
this.props.executeCommand({ name: commandNames.CLEAR_BLOCKLIST });
|
this.props.executeCommand({ name: commandNames.CLEAR_BLOCKLIST });
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|||||||
@@ -32,11 +32,11 @@ class BlocklistRow extends Component {
|
|||||||
|
|
||||||
onDetailsPress = () => {
|
onDetailsPress = () => {
|
||||||
this.setState({ isDetailsModalOpen: true });
|
this.setState({ isDetailsModalOpen: true });
|
||||||
}
|
};
|
||||||
|
|
||||||
onDetailsModalClose = () => {
|
onDetailsModalClose = () => {
|
||||||
this.setState({ isDetailsModalOpen: false });
|
this.setState({ isDetailsModalOpen: false });
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|||||||
@@ -57,38 +57,38 @@ class HistoryConnector extends Component {
|
|||||||
|
|
||||||
repopulate = () => {
|
repopulate = () => {
|
||||||
this.props.fetchHistory();
|
this.props.fetchHistory();
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Listeners
|
// Listeners
|
||||||
|
|
||||||
onFirstPagePress = () => {
|
onFirstPagePress = () => {
|
||||||
this.props.gotoHistoryFirstPage();
|
this.props.gotoHistoryFirstPage();
|
||||||
}
|
};
|
||||||
|
|
||||||
onPreviousPagePress = () => {
|
onPreviousPagePress = () => {
|
||||||
this.props.gotoHistoryPreviousPage();
|
this.props.gotoHistoryPreviousPage();
|
||||||
}
|
};
|
||||||
|
|
||||||
onNextPagePress = () => {
|
onNextPagePress = () => {
|
||||||
this.props.gotoHistoryNextPage();
|
this.props.gotoHistoryNextPage();
|
||||||
}
|
};
|
||||||
|
|
||||||
onLastPagePress = () => {
|
onLastPagePress = () => {
|
||||||
this.props.gotoHistoryLastPage();
|
this.props.gotoHistoryLastPage();
|
||||||
}
|
};
|
||||||
|
|
||||||
onPageSelect = (page) => {
|
onPageSelect = (page) => {
|
||||||
this.props.gotoHistoryPage({ page });
|
this.props.gotoHistoryPage({ page });
|
||||||
}
|
};
|
||||||
|
|
||||||
onSortPress = (sortKey) => {
|
onSortPress = (sortKey) => {
|
||||||
this.props.setHistorySort({ sortKey });
|
this.props.setHistorySort({ sortKey });
|
||||||
}
|
};
|
||||||
|
|
||||||
onFilterSelect = (selectedFilterKey) => {
|
onFilterSelect = (selectedFilterKey) => {
|
||||||
this.props.setHistoryFilter({ selectedFilterKey });
|
this.props.setHistoryFilter({ selectedFilterKey });
|
||||||
}
|
};
|
||||||
|
|
||||||
onTableOptionChange = (payload) => {
|
onTableOptionChange = (payload) => {
|
||||||
this.props.setHistoryTableOption(payload);
|
this.props.setHistoryTableOption(payload);
|
||||||
@@ -96,7 +96,7 @@ class HistoryConnector extends Component {
|
|||||||
if (payload.pageSize) {
|
if (payload.pageSize) {
|
||||||
this.props.gotoHistoryFirstPage();
|
this.props.gotoHistoryFirstPage();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|||||||
@@ -10,6 +10,12 @@
|
|||||||
width: 80px;
|
width: 80px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.customFormatScore {
|
||||||
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
|
width: 55px;
|
||||||
|
}
|
||||||
|
|
||||||
.releaseGroup {
|
.releaseGroup {
|
||||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import MovieFormats from 'Movie/MovieFormats';
|
|||||||
import MovieLanguage from 'Movie/MovieLanguage';
|
import MovieLanguage from 'Movie/MovieLanguage';
|
||||||
import MovieQuality from 'Movie/MovieQuality';
|
import MovieQuality from 'Movie/MovieQuality';
|
||||||
import MovieTitleLink from 'Movie/MovieTitleLink';
|
import MovieTitleLink from 'Movie/MovieTitleLink';
|
||||||
|
import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
|
||||||
import HistoryDetailsModal from './Details/HistoryDetailsModal';
|
import HistoryDetailsModal from './Details/HistoryDetailsModal';
|
||||||
import HistoryEventTypeCell from './HistoryEventTypeCell';
|
import HistoryEventTypeCell from './HistoryEventTypeCell';
|
||||||
import styles from './HistoryRow.css';
|
import styles from './HistoryRow.css';
|
||||||
@@ -41,11 +42,11 @@ class HistoryRow extends Component {
|
|||||||
|
|
||||||
onDetailsPress = () => {
|
onDetailsPress = () => {
|
||||||
this.setState({ isDetailsModalOpen: true });
|
this.setState({ isDetailsModalOpen: true });
|
||||||
}
|
};
|
||||||
|
|
||||||
onDetailsModalClose = () => {
|
onDetailsModalClose = () => {
|
||||||
this.setState({ isDetailsModalOpen: false });
|
this.setState({ isDetailsModalOpen: false });
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
@@ -55,6 +56,7 @@ class HistoryRow extends Component {
|
|||||||
movie,
|
movie,
|
||||||
quality,
|
quality,
|
||||||
customFormats,
|
customFormats,
|
||||||
|
customFormatScore,
|
||||||
languages,
|
languages,
|
||||||
qualityCutoffNotMet,
|
qualityCutoffNotMet,
|
||||||
eventType,
|
eventType,
|
||||||
@@ -168,6 +170,17 @@ class HistoryRow extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (name === 'customFormatScore') {
|
||||||
|
return (
|
||||||
|
<TableRowCell
|
||||||
|
key={name}
|
||||||
|
className={styles.customFormatScore}
|
||||||
|
>
|
||||||
|
{formatCustomFormatScore(customFormatScore)}
|
||||||
|
</TableRowCell>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (name === 'releaseGroup') {
|
if (name === 'releaseGroup') {
|
||||||
return (
|
return (
|
||||||
<TableRowCell
|
<TableRowCell
|
||||||
@@ -179,6 +192,16 @@ class HistoryRow extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (name === 'sourceTitle') {
|
||||||
|
return (
|
||||||
|
<TableRowCell
|
||||||
|
key={name}
|
||||||
|
>
|
||||||
|
{sourceTitle}
|
||||||
|
</TableRowCell>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (name === 'details') {
|
if (name === 'details') {
|
||||||
return (
|
return (
|
||||||
<TableRowCell
|
<TableRowCell
|
||||||
@@ -219,8 +242,9 @@ HistoryRow.propTypes = {
|
|||||||
movie: PropTypes.object.isRequired,
|
movie: PropTypes.object.isRequired,
|
||||||
languages: PropTypes.arrayOf(PropTypes.object).isRequired,
|
languages: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
quality: PropTypes.object.isRequired,
|
quality: PropTypes.object.isRequired,
|
||||||
|
customFormats: PropTypes.arrayOf(PropTypes.object),
|
||||||
|
customFormatScore: PropTypes.number.isRequired,
|
||||||
qualityCutoffNotMet: PropTypes.bool.isRequired,
|
qualityCutoffNotMet: PropTypes.bool.isRequired,
|
||||||
customFormats: PropTypes.arrayOf(PropTypes.object).isRequired,
|
|
||||||
eventType: PropTypes.string.isRequired,
|
eventType: PropTypes.string.isRequired,
|
||||||
sourceTitle: PropTypes.string.isRequired,
|
sourceTitle: PropTypes.string.isRequired,
|
||||||
date: PropTypes.string.isRequired,
|
date: PropTypes.string.isRequired,
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ class HistoryRowConnector extends Component {
|
|||||||
|
|
||||||
onMarkAsFailedPress = () => {
|
onMarkAsFailedPress = () => {
|
||||||
this.props.markAsFailed({ id: this.props.id });
|
this.props.markAsFailed({ id: this.props.id });
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
.torrent {
|
.torrent {
|
||||||
composes: label from '~Components/Label.css';
|
composes: label from '~Components/Label.css';
|
||||||
|
|
||||||
border-color: $torrentColor;
|
border-color: var(--torrentColor);
|
||||||
background-color: $torrentColor;
|
background-color: var(--torrentColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
.usenet {
|
.usenet {
|
||||||
composes: label from '~Components/Label.css';
|
composes: label from '~Components/Label.css';
|
||||||
|
|
||||||
border-color: $usenetColor;
|
border-color: var(--usenetColor);
|
||||||
background-color: $usenetColor;
|
background-color: var(--usenetColor);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,13 +75,23 @@ class Queue extends Component {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const nextState = {};
|
||||||
|
|
||||||
|
if (prevProps.items !== items) {
|
||||||
|
nextState.items = items;
|
||||||
|
}
|
||||||
|
|
||||||
const selectedIds = this.getSelectedIds();
|
const selectedIds = this.getSelectedIds();
|
||||||
const isPendingSelected = _.some(this.props.items, (item) => {
|
const isPendingSelected = _.some(this.props.items, (item) => {
|
||||||
return selectedIds.indexOf(item.id) > -1 && item.status === 'delay';
|
return selectedIds.indexOf(item.id) > -1 && item.status === 'delay';
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isPendingSelected !== this.state.isPendingSelected) {
|
if (isPendingSelected !== this.state.isPendingSelected) {
|
||||||
this.setState({ isPendingSelected });
|
nextState.isPendingSelected = isPendingSelected;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_.isEmpty(nextState)) {
|
||||||
|
this.setState(nextState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,45 +100,45 @@ class Queue extends Component {
|
|||||||
|
|
||||||
getSelectedIds = () => {
|
getSelectedIds = () => {
|
||||||
return getSelectedIds(this.state.selectedState);
|
return getSelectedIds(this.state.selectedState);
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Listeners
|
// Listeners
|
||||||
|
|
||||||
onQueueRowModalOpenOrClose = (isOpen) => {
|
onQueueRowModalOpenOrClose = (isOpen) => {
|
||||||
this._shouldBlockRefresh = isOpen;
|
this._shouldBlockRefresh = isOpen;
|
||||||
}
|
};
|
||||||
|
|
||||||
onSelectAllChange = ({ value }) => {
|
onSelectAllChange = ({ value }) => {
|
||||||
this.setState(selectAll(this.state.selectedState, value));
|
this.setState(selectAll(this.state.selectedState, value));
|
||||||
}
|
};
|
||||||
|
|
||||||
onSelectedChange = ({ id, value, shiftKey = false }) => {
|
onSelectedChange = ({ id, value, shiftKey = false }) => {
|
||||||
this.setState((state) => {
|
this.setState((state) => {
|
||||||
return toggleSelected(state, this.props.items, id, value, shiftKey);
|
return toggleSelected(state, this.props.items, id, value, shiftKey);
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
onGrabSelectedPress = () => {
|
onGrabSelectedPress = () => {
|
||||||
this.props.onGrabSelectedPress(this.getSelectedIds());
|
this.props.onGrabSelectedPress(this.getSelectedIds());
|
||||||
}
|
};
|
||||||
|
|
||||||
onRemoveSelectedPress = () => {
|
onRemoveSelectedPress = () => {
|
||||||
this.setState({ isConfirmRemoveModalOpen: true }, () => {
|
this.setState({ isConfirmRemoveModalOpen: true }, () => {
|
||||||
this._shouldBlockRefresh = true;
|
this._shouldBlockRefresh = true;
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
onRemoveSelectedConfirmed = (payload) => {
|
onRemoveSelectedConfirmed = (payload) => {
|
||||||
this._shouldBlockRefresh = false;
|
this._shouldBlockRefresh = false;
|
||||||
this.props.onRemoveSelectedPress({ ids: this.getSelectedIds(), ...payload });
|
this.props.onRemoveSelectedPress({ ids: this.getSelectedIds(), ...payload });
|
||||||
this.setState({ isConfirmRemoveModalOpen: false });
|
this.setState({ isConfirmRemoveModalOpen: false });
|
||||||
}
|
};
|
||||||
|
|
||||||
onConfirmRemoveModalClose = () => {
|
onConfirmRemoveModalClose = () => {
|
||||||
this._shouldBlockRefresh = false;
|
this._shouldBlockRefresh = false;
|
||||||
this.setState({ isConfirmRemoveModalOpen: false });
|
this.setState({ isConfirmRemoveModalOpen: false });
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
@@ -214,26 +224,29 @@ class Queue extends Component {
|
|||||||
|
|
||||||
<PageContentBody>
|
<PageContentBody>
|
||||||
{
|
{
|
||||||
isRefreshing && !isAllPopulated &&
|
isRefreshing && !isAllPopulated ?
|
||||||
<LoadingIndicator />
|
<LoadingIndicator /> :
|
||||||
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
!isRefreshing && hasError &&
|
!isRefreshing && hasError ?
|
||||||
<div>
|
<div>
|
||||||
{translate('FailedToLoadQueue')}
|
{translate('FailedToLoadQueue')}
|
||||||
</div>
|
</div> :
|
||||||
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
isAllPopulated && !hasError && !items.length &&
|
isAllPopulated && !hasError && !items.length ?
|
||||||
<div>
|
<div>
|
||||||
{translate('QueueIsEmpty')}
|
{translate('QueueIsEmpty')}
|
||||||
</div>
|
</div> :
|
||||||
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
isAllPopulated && !hasError && !!items.length &&
|
isAllPopulated && !hasError && !!items.length ?
|
||||||
<div>
|
<div>
|
||||||
<Table
|
<Table
|
||||||
columns={columns}
|
columns={columns}
|
||||||
@@ -268,7 +281,8 @@ class Queue extends Component {
|
|||||||
isFetching={isRefreshing}
|
isFetching={isRefreshing}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div> :
|
||||||
|
null
|
||||||
}
|
}
|
||||||
</PageContentBody>
|
</PageContentBody>
|
||||||
|
|
||||||
|
|||||||
@@ -77,34 +77,34 @@ class QueueConnector extends Component {
|
|||||||
|
|
||||||
repopulate = () => {
|
repopulate = () => {
|
||||||
this.props.fetchQueue();
|
this.props.fetchQueue();
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Listeners
|
// Listeners
|
||||||
|
|
||||||
onFirstPagePress = () => {
|
onFirstPagePress = () => {
|
||||||
this.props.gotoQueueFirstPage();
|
this.props.gotoQueueFirstPage();
|
||||||
}
|
};
|
||||||
|
|
||||||
onPreviousPagePress = () => {
|
onPreviousPagePress = () => {
|
||||||
this.props.gotoQueuePreviousPage();
|
this.props.gotoQueuePreviousPage();
|
||||||
}
|
};
|
||||||
|
|
||||||
onNextPagePress = () => {
|
onNextPagePress = () => {
|
||||||
this.props.gotoQueueNextPage();
|
this.props.gotoQueueNextPage();
|
||||||
}
|
};
|
||||||
|
|
||||||
onLastPagePress = () => {
|
onLastPagePress = () => {
|
||||||
this.props.gotoQueueLastPage();
|
this.props.gotoQueueLastPage();
|
||||||
}
|
};
|
||||||
|
|
||||||
onPageSelect = (page) => {
|
onPageSelect = (page) => {
|
||||||
this.props.gotoQueuePage({ page });
|
this.props.gotoQueuePage({ page });
|
||||||
}
|
};
|
||||||
|
|
||||||
onSortPress = (sortKey) => {
|
onSortPress = (sortKey) => {
|
||||||
this.props.setQueueSort({ sortKey });
|
this.props.setQueueSort({ sortKey });
|
||||||
}
|
};
|
||||||
|
|
||||||
onTableOptionChange = (payload) => {
|
onTableOptionChange = (payload) => {
|
||||||
this.props.setQueueTableOption(payload);
|
this.props.setQueueTableOption(payload);
|
||||||
@@ -112,21 +112,21 @@ class QueueConnector extends Component {
|
|||||||
if (payload.pageSize) {
|
if (payload.pageSize) {
|
||||||
this.props.gotoQueueFirstPage();
|
this.props.gotoQueueFirstPage();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
onRefreshPress = () => {
|
onRefreshPress = () => {
|
||||||
this.props.executeCommand({
|
this.props.executeCommand({
|
||||||
name: commandNames.REFRESH_MONITORED_DOWNLOADS
|
name: commandNames.REFRESH_MONITORED_DOWNLOADS
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
onGrabSelectedPress = (ids) => {
|
onGrabSelectedPress = (ids) => {
|
||||||
this.props.grabQueueItems({ ids });
|
this.props.grabQueueItems({ ids });
|
||||||
}
|
};
|
||||||
|
|
||||||
onRemoveSelectedPress = (payload) => {
|
onRemoveSelectedPress = (payload) => {
|
||||||
this.props.removeQueueItems(payload);
|
this.props.removeQueueItems(payload);
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ class QueueOptions extends Component {
|
|||||||
[name]: value
|
[name]: value
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class QueueRow extends Component {
|
|||||||
|
|
||||||
onRemoveQueueItemPress = () => {
|
onRemoveQueueItemPress = () => {
|
||||||
this.setState({ isRemoveQueueItemModalOpen: true });
|
this.setState({ isRemoveQueueItemModalOpen: true });
|
||||||
}
|
};
|
||||||
|
|
||||||
onRemoveQueueItemModalConfirmed = (blocklist) => {
|
onRemoveQueueItemModalConfirmed = (blocklist) => {
|
||||||
const {
|
const {
|
||||||
@@ -52,25 +52,25 @@ class QueueRow extends Component {
|
|||||||
onRemoveQueueItemPress(blocklist);
|
onRemoveQueueItemPress(blocklist);
|
||||||
|
|
||||||
this.setState({ isRemoveQueueItemModalOpen: false });
|
this.setState({ isRemoveQueueItemModalOpen: false });
|
||||||
}
|
};
|
||||||
|
|
||||||
onRemoveQueueItemModalClose = () => {
|
onRemoveQueueItemModalClose = () => {
|
||||||
this.props.onQueueRowModalOpenOrClose(false);
|
this.props.onQueueRowModalOpenOrClose(false);
|
||||||
|
|
||||||
this.setState({ isRemoveQueueItemModalOpen: false });
|
this.setState({ isRemoveQueueItemModalOpen: false });
|
||||||
}
|
};
|
||||||
|
|
||||||
onInteractiveImportPress = () => {
|
onInteractiveImportPress = () => {
|
||||||
this.props.onQueueRowModalOpenOrClose(true);
|
this.props.onQueueRowModalOpenOrClose(true);
|
||||||
|
|
||||||
this.setState({ isInteractiveImportModalOpen: true });
|
this.setState({ isInteractiveImportModalOpen: true });
|
||||||
}
|
};
|
||||||
|
|
||||||
onInteractiveImportModalClose = () => {
|
onInteractiveImportModalClose = () => {
|
||||||
this.props.onQueueRowModalOpenOrClose(false);
|
this.props.onQueueRowModalOpenOrClose(false);
|
||||||
|
|
||||||
this.setState({ isInteractiveImportModalOpen: false });
|
this.setState({ isInteractiveImportModalOpen: false });
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
@@ -128,6 +128,7 @@ class QueueRow extends Component {
|
|||||||
|
|
||||||
{
|
{
|
||||||
columns.map((column) => {
|
columns.map((column) => {
|
||||||
|
|
||||||
const {
|
const {
|
||||||
name,
|
name,
|
||||||
isVisible
|
isVisible
|
||||||
@@ -234,6 +235,16 @@ class QueueRow extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (name === 'year') {
|
||||||
|
return (
|
||||||
|
<TableRowCell key={name}>
|
||||||
|
{
|
||||||
|
movie ? movie.year : ''
|
||||||
|
}
|
||||||
|
</TableRowCell>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (name === 'title') {
|
if (name === 'title') {
|
||||||
return (
|
return (
|
||||||
<TableRowCell key={name}>
|
<TableRowCell key={name}>
|
||||||
@@ -362,6 +373,7 @@ QueueRow.propTypes = {
|
|||||||
estimatedCompletionTime: PropTypes.string,
|
estimatedCompletionTime: PropTypes.string,
|
||||||
timeleft: PropTypes.string,
|
timeleft: PropTypes.string,
|
||||||
size: PropTypes.number,
|
size: PropTypes.number,
|
||||||
|
year: PropTypes.number,
|
||||||
sizeleft: PropTypes.number,
|
sizeleft: PropTypes.number,
|
||||||
showRelativeDates: PropTypes.bool.isRequired,
|
showRelativeDates: PropTypes.bool.isRequired,
|
||||||
shortDateFormat: PropTypes.string.isRequired,
|
shortDateFormat: PropTypes.string.isRequired,
|
||||||
|
|||||||
@@ -37,11 +37,11 @@ class QueueRowConnector extends Component {
|
|||||||
|
|
||||||
onGrabPress = () => {
|
onGrabPress = () => {
|
||||||
this.props.grabQueueItem({ id: this.props.id });
|
this.props.grabQueueItem({ id: this.props.id });
|
||||||
}
|
};
|
||||||
|
|
||||||
onRemoveQueueItemPress = (payload) => {
|
onRemoveQueueItemPress = (payload) => {
|
||||||
this.props.removeQueueItem({ id: this.props.id, ...payload });
|
this.props.removeQueueItem({ id: this.props.id, ...payload });
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|||||||
@@ -34,30 +34,30 @@ class RemoveQueueItemModal extends Component {
|
|||||||
remove: true,
|
remove: true,
|
||||||
blocklist: false
|
blocklist: false
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Listeners
|
// Listeners
|
||||||
|
|
||||||
onRemoveChange = ({ value }) => {
|
onRemoveChange = ({ value }) => {
|
||||||
this.setState({ remove: value });
|
this.setState({ remove: value });
|
||||||
}
|
};
|
||||||
|
|
||||||
onBlocklistChange = ({ value }) => {
|
onBlocklistChange = ({ value }) => {
|
||||||
this.setState({ blocklist: value });
|
this.setState({ blocklist: value });
|
||||||
}
|
};
|
||||||
|
|
||||||
onRemoveConfirmed = () => {
|
onRemoveConfirmed = () => {
|
||||||
const state = this.state;
|
const state = this.state;
|
||||||
|
|
||||||
this.resetState();
|
this.resetState();
|
||||||
this.props.onRemovePress(state);
|
this.props.onRemovePress(state);
|
||||||
}
|
};
|
||||||
|
|
||||||
onModalClose = () => {
|
onModalClose = () => {
|
||||||
this.resetState();
|
this.resetState();
|
||||||
this.props.onModalClose();
|
this.props.onModalClose();
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|||||||
@@ -30,35 +30,35 @@ class RemoveQueueItemsModal extends Component {
|
|||||||
//
|
//
|
||||||
// Control
|
// Control
|
||||||
|
|
||||||
resetState = function() {
|
resetState = function() {
|
||||||
this.setState({
|
this.setState({
|
||||||
remove: true,
|
remove: true,
|
||||||
blocklist: false
|
blocklist: false
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Listeners
|
// Listeners
|
||||||
|
|
||||||
onRemoveChange = ({ value }) => {
|
onRemoveChange = ({ value }) => {
|
||||||
this.setState({ remove: value });
|
this.setState({ remove: value });
|
||||||
}
|
};
|
||||||
|
|
||||||
onBlocklistChange = ({ value }) => {
|
onBlocklistChange = ({ value }) => {
|
||||||
this.setState({ blocklist: value });
|
this.setState({ blocklist: value });
|
||||||
}
|
};
|
||||||
|
|
||||||
onRemoveConfirmed = () => {
|
onRemoveConfirmed = () => {
|
||||||
const state = this.state;
|
const state = this.state;
|
||||||
|
|
||||||
this.resetState();
|
this.resetState();
|
||||||
this.props.onRemovePress(state);
|
this.props.onRemovePress(state);
|
||||||
}
|
};
|
||||||
|
|
||||||
onModalClose = () => {
|
onModalClose = () => {
|
||||||
this.resetState();
|
this.resetState();
|
||||||
this.props.onModalClose();
|
this.props.onModalClose();
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|||||||
@@ -6,12 +6,12 @@
|
|||||||
.searchIconContainer {
|
.searchIconContainer {
|
||||||
width: 58px;
|
width: 58px;
|
||||||
height: 46px;
|
height: 46px;
|
||||||
border: 1px solid $inputBorderColor;
|
border: 1px solid var(--inputBorderColor);
|
||||||
border-right: none;
|
border-right: none;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
border-top-right-radius: 0;
|
border-top-right-radius: 0;
|
||||||
border-bottom-right-radius: 0;
|
border-bottom-right-radius: 0;
|
||||||
background-color: #edf1f2;
|
background-color: var(--searchIconContainerBackgroundColor);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
line-height: 46px;
|
line-height: 46px;
|
||||||
}
|
}
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.clearLookupButton {
|
.clearLookupButton {
|
||||||
border: 1px solid $inputBorderColor;
|
border: 1px solid var(--inputBorderColor);
|
||||||
border-left: none;
|
border-left: none;
|
||||||
border-top-right-radius: 4px;
|
border-top-right-radius: 4px;
|
||||||
border-bottom-right-radius: 4px;
|
border-bottom-right-radius: 4px;
|
||||||
|
|||||||
@@ -67,12 +67,12 @@ class AddNewMovie extends Component {
|
|||||||
this.props.onClearMovieLookup();
|
this.props.onClearMovieLookup();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
onClearMovieLookupPress = () => {
|
onClearMovieLookupPress = () => {
|
||||||
this.setState({ term: '' });
|
this.setState({ term: '' });
|
||||||
this.props.onClearMovieLookup();
|
this.props.onClearMovieLookup();
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|||||||
@@ -79,11 +79,11 @@ class AddNewMovieConnector extends Component {
|
|||||||
this.props.lookupMovie({ term });
|
this.props.lookupMovie({ term });
|
||||||
}, 300);
|
}, 300);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
onClearMovieLookup = () => {
|
onClearMovieLookup = () => {
|
||||||
this.props.clearAddMovie();
|
this.props.clearAddMovie();
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
.year {
|
.year {
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
color: $disabledColor;
|
color: var(--disabledColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
.poster {
|
.poster {
|
||||||
|
|||||||
@@ -20,13 +20,9 @@ class AddNewMovieModalContent extends Component {
|
|||||||
//
|
//
|
||||||
// Listeners
|
// Listeners
|
||||||
|
|
||||||
onQualityProfileIdChange = ({ value }) => {
|
|
||||||
this.props.onInputChange({ name: 'qualityProfileId', value: parseInt(value) });
|
|
||||||
}
|
|
||||||
|
|
||||||
onAddMoviePress = () => {
|
onAddMoviePress = () => {
|
||||||
this.props.onAddMoviePress();
|
this.props.onAddMoviePress();
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
@@ -40,7 +36,7 @@ class AddNewMovieModalContent extends Component {
|
|||||||
isAdding,
|
isAdding,
|
||||||
rootFolderPath,
|
rootFolderPath,
|
||||||
monitor,
|
monitor,
|
||||||
qualityProfileId,
|
qualityProfileIds,
|
||||||
minimumAvailability,
|
minimumAvailability,
|
||||||
searchForMovie,
|
searchForMovie,
|
||||||
folder,
|
folder,
|
||||||
@@ -130,9 +126,9 @@ class AddNewMovieModalContent extends Component {
|
|||||||
|
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.QUALITY_PROFILE_SELECT}
|
type={inputTypes.QUALITY_PROFILE_SELECT}
|
||||||
name="qualityProfileId"
|
name="qualityProfileIds"
|
||||||
onChange={this.onQualityProfileIdChange}
|
onChange={onInputChange}
|
||||||
{...qualityProfileId}
|
{...qualityProfileIds}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
@@ -189,7 +185,7 @@ AddNewMovieModalContent.propTypes = {
|
|||||||
addError: PropTypes.object,
|
addError: PropTypes.object,
|
||||||
rootFolderPath: PropTypes.object,
|
rootFolderPath: PropTypes.object,
|
||||||
monitor: PropTypes.object.isRequired,
|
monitor: PropTypes.object.isRequired,
|
||||||
qualityProfileId: PropTypes.object,
|
qualityProfileIds: PropTypes.arrayOf(PropTypes.object),
|
||||||
minimumAvailability: PropTypes.object.isRequired,
|
minimumAvailability: PropTypes.object.isRequired,
|
||||||
searchForMovie: PropTypes.object.isRequired,
|
searchForMovie: PropTypes.object.isRequired,
|
||||||
folder: PropTypes.string.isRequired,
|
folder: PropTypes.string.isRequired,
|
||||||
|
|||||||
@@ -51,14 +51,14 @@ class AddNewMovieModalContentConnector extends Component {
|
|||||||
|
|
||||||
onInputChange = ({ name, value }) => {
|
onInputChange = ({ name, value }) => {
|
||||||
this.props.setAddMovieDefault({ [name]: value });
|
this.props.setAddMovieDefault({ [name]: value });
|
||||||
}
|
};
|
||||||
|
|
||||||
onAddMoviePress = () => {
|
onAddMoviePress = () => {
|
||||||
const {
|
const {
|
||||||
tmdbId,
|
tmdbId,
|
||||||
rootFolderPath,
|
rootFolderPath,
|
||||||
monitor,
|
monitor,
|
||||||
qualityProfileId,
|
qualityProfileIds,
|
||||||
minimumAvailability,
|
minimumAvailability,
|
||||||
searchForMovie,
|
searchForMovie,
|
||||||
tags
|
tags
|
||||||
@@ -68,12 +68,12 @@ class AddNewMovieModalContentConnector extends Component {
|
|||||||
tmdbId,
|
tmdbId,
|
||||||
rootFolderPath: rootFolderPath.value,
|
rootFolderPath: rootFolderPath.value,
|
||||||
monitor: monitor.value,
|
monitor: monitor.value,
|
||||||
qualityProfileId: qualityProfileId.value,
|
qualityProfileIds: qualityProfileIds.value,
|
||||||
minimumAvailability: minimumAvailability.value,
|
minimumAvailability: minimumAvailability.value,
|
||||||
searchForMovie: searchForMovie.value,
|
searchForMovie: searchForMovie.value,
|
||||||
tags: tags.value
|
tags: tags.value
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
@@ -93,7 +93,7 @@ AddNewMovieModalContentConnector.propTypes = {
|
|||||||
tmdbId: PropTypes.number.isRequired,
|
tmdbId: PropTypes.number.isRequired,
|
||||||
rootFolderPath: PropTypes.object,
|
rootFolderPath: PropTypes.object,
|
||||||
monitor: PropTypes.object.isRequired,
|
monitor: PropTypes.object.isRequired,
|
||||||
qualityProfileId: PropTypes.object,
|
qualityProfileIds: PropTypes.arrayOf(PropTypes.object),
|
||||||
minimumAvailability: PropTypes.object.isRequired,
|
minimumAvailability: PropTypes.object.isRequired,
|
||||||
searchForMovie: PropTypes.object.isRequired,
|
searchForMovie: PropTypes.object.isRequired,
|
||||||
tags: PropTypes.object.isRequired,
|
tags: PropTypes.object.isRequired,
|
||||||
|
|||||||
@@ -9,13 +9,15 @@
|
|||||||
.underlay {
|
.underlay {
|
||||||
@add-mixin cover;
|
@add-mixin cover;
|
||||||
|
|
||||||
background-color: $white;
|
background-color: var(--addMovieBackgroundColor);
|
||||||
transition: background 500ms;
|
transition: background 500ms;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: #eaf2ff;
|
background-color: var(--pageBackground);
|
||||||
|
box-shadow: 0 0 12px var(--black);
|
||||||
color: inherit;
|
color: inherit;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
transition: all 200ms ease-in;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,7 +33,7 @@
|
|||||||
display: block;
|
display: block;
|
||||||
margin-right: 20px;
|
margin-right: 20px;
|
||||||
height: 250px;
|
height: 250px;
|
||||||
background-color: $defaultColor;
|
background-color: var(--defaultColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
@@ -56,7 +58,7 @@
|
|||||||
|
|
||||||
.year {
|
.year {
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
color: $disabledColor;
|
color: var(--disabledColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icons {
|
.icons {
|
||||||
@@ -75,7 +77,7 @@
|
|||||||
|
|
||||||
.exclusionIcon {
|
.exclusionIcon {
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
color: $dangerColor;
|
color: var(--dangerColor);
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import HeartRating from 'Components/HeartRating';
|
|
||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
import Label from 'Components/Label';
|
import Label from 'Components/Label';
|
||||||
import Link from 'Components/Link/Link';
|
import Link from 'Components/Link/Link';
|
||||||
|
import TmdbRating from 'Components/TmdbRating';
|
||||||
import Tooltip from 'Components/Tooltip/Tooltip';
|
import Tooltip from 'Components/Tooltip/Tooltip';
|
||||||
import { icons, kinds, sizes, tooltipPositions } from 'Helpers/Props';
|
import { icons, kinds, sizes, tooltipPositions } from 'Helpers/Props';
|
||||||
import MovieDetailsLinks from 'Movie/Details/MovieDetailsLinks';
|
import MovieDetailsLinks from 'Movie/Details/MovieDetailsLinks';
|
||||||
@@ -39,15 +39,15 @@ class AddNewMovieSearchResult extends Component {
|
|||||||
|
|
||||||
onPress = () => {
|
onPress = () => {
|
||||||
this.setState({ isNewAddMovieModalOpen: true });
|
this.setState({ isNewAddMovieModalOpen: true });
|
||||||
}
|
};
|
||||||
|
|
||||||
onAddMovieModalClose = () => {
|
onAddMovieModalClose = () => {
|
||||||
this.setState({ isNewAddMovieModalOpen: false });
|
this.setState({ isNewAddMovieModalOpen: false });
|
||||||
}
|
};
|
||||||
|
|
||||||
onExternalLinkPress = (event) => {
|
onExternalLinkPress = (event) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
@@ -72,15 +72,19 @@ class AddNewMovieSearchResult extends Component {
|
|||||||
colorImpairedMode,
|
colorImpairedMode,
|
||||||
id,
|
id,
|
||||||
monitored,
|
monitored,
|
||||||
hasFile,
|
|
||||||
isAvailable,
|
isAvailable,
|
||||||
queueStatus,
|
queueStatus,
|
||||||
queueState,
|
queueState,
|
||||||
runtime,
|
runtime,
|
||||||
movieRuntimeFormat,
|
movieRuntimeFormat,
|
||||||
certification
|
certification,
|
||||||
|
statistics
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
movieFileCount
|
||||||
|
} = statistics;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isNewAddMovieModalOpen
|
isNewAddMovieModalOpen
|
||||||
} = this.state;
|
} = this.state;
|
||||||
@@ -120,7 +124,7 @@ class AddNewMovieSearchResult extends Component {
|
|||||||
isExistingMovie &&
|
isExistingMovie &&
|
||||||
<MovieIndexProgressBar
|
<MovieIndexProgressBar
|
||||||
monitored={monitored}
|
monitored={monitored}
|
||||||
hasFile={hasFile}
|
hasFile={movieFileCount > 0}
|
||||||
status={status}
|
status={status}
|
||||||
posterWidth={posterWidth}
|
posterWidth={posterWidth}
|
||||||
detailedProgressBar={true}
|
detailedProgressBar={true}
|
||||||
@@ -190,7 +194,7 @@ class AddNewMovieSearchResult extends Component {
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Label size={sizes.LARGE}>
|
<Label size={sizes.LARGE}>
|
||||||
<HeartRating
|
<TmdbRating
|
||||||
ratings={ratings}
|
ratings={ratings}
|
||||||
iconSize={13}
|
iconSize={13}
|
||||||
/>
|
/>
|
||||||
@@ -233,7 +237,7 @@ class AddNewMovieSearchResult extends Component {
|
|||||||
{
|
{
|
||||||
isExistingMovie && isSmallScreen &&
|
isExistingMovie && isSmallScreen &&
|
||||||
<MovieStatusLabel
|
<MovieStatusLabel
|
||||||
hasMovieFiles={hasFile}
|
hasMovieFiles={movieFileCount > 0}
|
||||||
monitored={monitored}
|
monitored={monitored}
|
||||||
isAvailable={isAvailable}
|
isAvailable={isAvailable}
|
||||||
id={id}
|
id={id}
|
||||||
@@ -290,7 +294,14 @@ AddNewMovieSearchResult.propTypes = {
|
|||||||
queueState: PropTypes.string,
|
queueState: PropTypes.string,
|
||||||
runtime: PropTypes.number.isRequired,
|
runtime: PropTypes.number.isRequired,
|
||||||
movieRuntimeFormat: PropTypes.string.isRequired,
|
movieRuntimeFormat: PropTypes.string.isRequired,
|
||||||
certification: PropTypes.string
|
certification: PropTypes.string,
|
||||||
|
statistics: PropTypes.object
|
||||||
|
};
|
||||||
|
|
||||||
|
AddNewMovieSearchResult.defaultProps = {
|
||||||
|
statistics: {
|
||||||
|
movieFileCount: 0
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AddNewMovieSearchResult;
|
export default AddNewMovieSearchResult;
|
||||||
|
|||||||
@@ -32,25 +32,25 @@ class ImportMovie extends Component {
|
|||||||
|
|
||||||
setScrollerRef = (ref) => {
|
setScrollerRef = (ref) => {
|
||||||
this.setState({ scroller: ref });
|
this.setState({ scroller: ref });
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Listeners
|
// Listeners
|
||||||
|
|
||||||
getSelectedIds = () => {
|
getSelectedIds = () => {
|
||||||
return getSelectedIds(this.state.selectedState, { parseIds: false });
|
return getSelectedIds(this.state.selectedState, { parseIds: false });
|
||||||
}
|
};
|
||||||
|
|
||||||
onSelectAllChange = ({ value }) => {
|
onSelectAllChange = ({ value }) => {
|
||||||
// Only select non-dupes
|
// Only select non-dupes
|
||||||
this.setState(selectAll(this.state.selectedState, value));
|
this.setState(selectAll(this.state.selectedState, value));
|
||||||
}
|
};
|
||||||
|
|
||||||
onSelectedChange = ({ id, value, shiftKey = false }) => {
|
onSelectedChange = ({ id, value, shiftKey = false }) => {
|
||||||
this.setState((state) => {
|
this.setState((state) => {
|
||||||
return toggleSelected(state, this.props.items, id, value, shiftKey);
|
return toggleSelected(state, this.props.items, id, value, shiftKey);
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
onRemoveSelectedStateItem = (id) => {
|
onRemoveSelectedStateItem = (id) => {
|
||||||
this.setState((state) => {
|
this.setState((state) => {
|
||||||
@@ -62,15 +62,15 @@ class ImportMovie extends Component {
|
|||||||
selectedState
|
selectedState
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
onInputChange = ({ name, value }) => {
|
onInputChange = ({ name, value }) => {
|
||||||
this.props.onInputChange(this.getSelectedIds(), name, value);
|
this.props.onInputChange(this.getSelectedIds(), name, value);
|
||||||
}
|
};
|
||||||
|
|
||||||
onImportPress = () => {
|
onImportPress = () => {
|
||||||
this.props.onImportPress(this.getSelectedIds());
|
this.props.onImportPress(this.getSelectedIds());
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|||||||
@@ -112,11 +112,11 @@ class ImportMovieConnector extends Component {
|
|||||||
[name]: value
|
[name]: value
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
onImportPress = (ids) => {
|
onImportPress = (ids) => {
|
||||||
this.props.dispatchImportMovie({ ids });
|
this.props.dispatchImportMovie({ ids });
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|||||||
@@ -25,13 +25,13 @@ class ImportMovieFooter extends Component {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
defaultMonitor,
|
defaultMonitor,
|
||||||
defaultQualityProfileId,
|
defaultQualityProfileIds,
|
||||||
defaultMinimumAvailability
|
defaultMinimumAvailability
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
monitor: defaultMonitor,
|
monitor: defaultMonitor,
|
||||||
qualityProfileId: defaultQualityProfileId,
|
qualityProfileIds: defaultQualityProfileIds,
|
||||||
minimumAvailability: defaultMinimumAvailability
|
minimumAvailability: defaultMinimumAvailability
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -39,16 +39,16 @@ class ImportMovieFooter extends Component {
|
|||||||
componentDidUpdate(prevProps, prevState) {
|
componentDidUpdate(prevProps, prevState) {
|
||||||
const {
|
const {
|
||||||
defaultMonitor,
|
defaultMonitor,
|
||||||
defaultQualityProfileId,
|
defaultQualityProfileIds,
|
||||||
defaultMinimumAvailability,
|
defaultMinimumAvailability,
|
||||||
isMonitorMixed,
|
isMonitorMixed,
|
||||||
isQualityProfileIdMixed,
|
isQualityProfileIdsMixed,
|
||||||
isMinimumAvailabilityMixed
|
isMinimumAvailabilityMixed
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
monitor,
|
monitor,
|
||||||
qualityProfileId,
|
qualityProfileIds,
|
||||||
minimumAvailability
|
minimumAvailability
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
@@ -60,10 +60,10 @@ class ImportMovieFooter extends Component {
|
|||||||
newState.monitor = defaultMonitor;
|
newState.monitor = defaultMonitor;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isQualityProfileIdMixed && qualityProfileId !== MIXED) {
|
if (isQualityProfileIdsMixed && qualityProfileIds !== MIXED) {
|
||||||
newState.qualityProfileId = MIXED;
|
newState.qualityProfileIds = MIXED;
|
||||||
} else if (!isQualityProfileIdMixed && qualityProfileId !== defaultQualityProfileId) {
|
} else if (!isQualityProfileIdsMixed && qualityProfileIds !== defaultQualityProfileIds) {
|
||||||
newState.qualityProfileId = defaultQualityProfileId;
|
newState.qualityProfileIds = defaultQualityProfileIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isMinimumAvailabilityMixed && minimumAvailability !== MIXED) {
|
if (isMinimumAvailabilityMixed && minimumAvailability !== MIXED) {
|
||||||
@@ -83,7 +83,7 @@ class ImportMovieFooter extends Component {
|
|||||||
onInputChange = ({ name, value }) => {
|
onInputChange = ({ name, value }) => {
|
||||||
this.setState({ [name]: value });
|
this.setState({ [name]: value });
|
||||||
this.props.onInputChange({ name, value });
|
this.props.onInputChange({ name, value });
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
@@ -94,7 +94,7 @@ class ImportMovieFooter extends Component {
|
|||||||
isImporting,
|
isImporting,
|
||||||
isLookingUpMovie,
|
isLookingUpMovie,
|
||||||
isMonitorMixed,
|
isMonitorMixed,
|
||||||
isQualityProfileIdMixed,
|
isQualityProfileIdsMixed,
|
||||||
isMinimumAvailabilityMixed,
|
isMinimumAvailabilityMixed,
|
||||||
hasUnsearchedItems,
|
hasUnsearchedItems,
|
||||||
importError,
|
importError,
|
||||||
@@ -105,7 +105,7 @@ class ImportMovieFooter extends Component {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
monitor,
|
monitor,
|
||||||
qualityProfileId,
|
qualityProfileIds,
|
||||||
minimumAvailability
|
minimumAvailability
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
@@ -148,10 +148,10 @@ class ImportMovieFooter extends Component {
|
|||||||
|
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.QUALITY_PROFILE_SELECT}
|
type={inputTypes.QUALITY_PROFILE_SELECT}
|
||||||
name="qualityProfileId"
|
name="qualityProfileIds"
|
||||||
value={qualityProfileId}
|
value={qualityProfileIds}
|
||||||
isDisabled={!selectedCount}
|
isDisabled={!selectedCount}
|
||||||
includeMixed={isQualityProfileIdMixed}
|
includeMixed={isQualityProfileIdsMixed}
|
||||||
onChange={this.onInputChange}
|
onChange={this.onInputChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -225,13 +225,19 @@ class ImportMovieFooter extends Component {
|
|||||||
body={
|
body={
|
||||||
<ul>
|
<ul>
|
||||||
{
|
{
|
||||||
importError.responseJSON.map((error, index) => {
|
Array.isArray(importError.responseJSON) ?
|
||||||
return (
|
importError.responseJSON.map((error, index) => {
|
||||||
<li key={index}>
|
return (
|
||||||
{error.errorMessage}
|
<li key={index}>
|
||||||
</li>
|
{error.errorMessage}
|
||||||
);
|
</li>
|
||||||
})
|
);
|
||||||
|
}) :
|
||||||
|
<li>
|
||||||
|
{
|
||||||
|
JSON.stringify(importError.responseJSON)
|
||||||
|
}
|
||||||
|
</li>
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
}
|
}
|
||||||
@@ -251,10 +257,10 @@ ImportMovieFooter.propTypes = {
|
|||||||
isImporting: PropTypes.bool.isRequired,
|
isImporting: PropTypes.bool.isRequired,
|
||||||
isLookingUpMovie: PropTypes.bool.isRequired,
|
isLookingUpMovie: PropTypes.bool.isRequired,
|
||||||
defaultMonitor: PropTypes.string.isRequired,
|
defaultMonitor: PropTypes.string.isRequired,
|
||||||
defaultQualityProfileId: PropTypes.number,
|
defaultQualityProfileIds: PropTypes.arrayOf(PropTypes.number),
|
||||||
defaultMinimumAvailability: PropTypes.string,
|
defaultMinimumAvailability: PropTypes.string,
|
||||||
isMonitorMixed: PropTypes.bool.isRequired,
|
isMonitorMixed: PropTypes.bool.isRequired,
|
||||||
isQualityProfileIdMixed: PropTypes.bool.isRequired,
|
isQualityProfileIdsMixed: PropTypes.bool.isRequired,
|
||||||
isMinimumAvailabilityMixed: PropTypes.bool.isRequired,
|
isMinimumAvailabilityMixed: PropTypes.bool.isRequired,
|
||||||
hasUnsearchedItems: PropTypes.bool.isRequired,
|
hasUnsearchedItems: PropTypes.bool.isRequired,
|
||||||
importError: PropTypes.object,
|
importError: PropTypes.object,
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ function createMapStateToProps() {
|
|||||||
(addMovie, importMovie, selectedIds) => {
|
(addMovie, importMovie, selectedIds) => {
|
||||||
const {
|
const {
|
||||||
monitor: defaultMonitor,
|
monitor: defaultMonitor,
|
||||||
qualityProfileId: defaultQualityProfileId,
|
qualityProfileIds: defaultQualityProfileIds,
|
||||||
minimumAvailability: defaultMinimumAvailability
|
minimumAvailability: defaultMinimumAvailability
|
||||||
} = addMovie.defaults;
|
} = addMovie.defaults;
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ function createMapStateToProps() {
|
|||||||
} = importMovie;
|
} = importMovie;
|
||||||
|
|
||||||
const isMonitorMixed = isMixed(items, selectedIds, defaultMonitor, 'monitor');
|
const isMonitorMixed = isMixed(items, selectedIds, defaultMonitor, 'monitor');
|
||||||
const isQualityProfileIdMixed = isMixed(items, selectedIds, defaultQualityProfileId, 'qualityProfileId');
|
const isQualityProfileIdsMixed = isMixed(items, selectedIds, defaultQualityProfileIds, 'qualityProfileIds');
|
||||||
const isMinimumAvailabilityMixed = isMixed(items, selectedIds, defaultMinimumAvailability, 'minimumAvailability');
|
const isMinimumAvailabilityMixed = isMixed(items, selectedIds, defaultMinimumAvailability, 'minimumAvailability');
|
||||||
const hasUnsearchedItems = !isLookingUpMovie && items.some((item) => !item.isPopulated);
|
const hasUnsearchedItems = !isLookingUpMovie && items.some((item) => !item.isPopulated);
|
||||||
|
|
||||||
@@ -39,10 +39,10 @@ function createMapStateToProps() {
|
|||||||
isLookingUpMovie,
|
isLookingUpMovie,
|
||||||
isImporting,
|
isImporting,
|
||||||
defaultMonitor,
|
defaultMonitor,
|
||||||
defaultQualityProfileId,
|
defaultQualityProfileIds,
|
||||||
defaultMinimumAvailability,
|
defaultMinimumAvailability,
|
||||||
isMonitorMixed,
|
isMonitorMixed,
|
||||||
isQualityProfileIdMixed,
|
isQualityProfileIdsMixed,
|
||||||
isMinimumAvailabilityMixed,
|
isMinimumAvailabilityMixed,
|
||||||
importError,
|
importError,
|
||||||
hasUnsearchedItems
|
hasUnsearchedItems
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ function ImportMovieRow(props) {
|
|||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
monitor,
|
monitor,
|
||||||
qualityProfileId,
|
qualityProfileIds,
|
||||||
minimumAvailability,
|
minimumAvailability,
|
||||||
selectedMovie,
|
selectedMovie,
|
||||||
isExistingMovie,
|
isExistingMovie,
|
||||||
@@ -62,8 +62,8 @@ function ImportMovieRow(props) {
|
|||||||
<VirtualTableRowCell className={styles.qualityProfile}>
|
<VirtualTableRowCell className={styles.qualityProfile}>
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.QUALITY_PROFILE_SELECT}
|
type={inputTypes.QUALITY_PROFILE_SELECT}
|
||||||
name="qualityProfileId"
|
name="qualityProfileIds"
|
||||||
value={qualityProfileId}
|
value={qualityProfileIds}
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
/>
|
/>
|
||||||
</VirtualTableRowCell>
|
</VirtualTableRowCell>
|
||||||
@@ -74,7 +74,7 @@ function ImportMovieRow(props) {
|
|||||||
ImportMovieRow.propTypes = {
|
ImportMovieRow.propTypes = {
|
||||||
id: PropTypes.string.isRequired,
|
id: PropTypes.string.isRequired,
|
||||||
monitor: PropTypes.string.isRequired,
|
monitor: PropTypes.string.isRequired,
|
||||||
qualityProfileId: PropTypes.number.isRequired,
|
qualityProfileIds: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||||
minimumAvailability: PropTypes.string.isRequired,
|
minimumAvailability: PropTypes.string.isRequired,
|
||||||
selectedMovie: PropTypes.object,
|
selectedMovie: PropTypes.object,
|
||||||
isExistingMovie: PropTypes.bool.isRequired,
|
isExistingMovie: PropTypes.bool.isRequired,
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ class ImportMovieRowConnector extends Component {
|
|||||||
id: this.props.id,
|
id: this.props.id,
|
||||||
[name]: value
|
[name]: value
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ class ImportMovieTable extends Component {
|
|||||||
const {
|
const {
|
||||||
unmappedFolders,
|
unmappedFolders,
|
||||||
defaultMonitor,
|
defaultMonitor,
|
||||||
defaultQualityProfileId,
|
defaultQualityProfileIds,
|
||||||
defaultMinimumAvailability,
|
defaultMinimumAvailability,
|
||||||
onMovieLookup,
|
onMovieLookup,
|
||||||
onSetImportMovieValue
|
onSetImportMovieValue
|
||||||
@@ -23,7 +23,7 @@ class ImportMovieTable extends Component {
|
|||||||
|
|
||||||
const values = {
|
const values = {
|
||||||
monitor: defaultMonitor,
|
monitor: defaultMonitor,
|
||||||
qualityProfileId: defaultQualityProfileId,
|
qualityProfileIds: defaultQualityProfileIds,
|
||||||
minimumAvailability: defaultMinimumAvailability
|
minimumAvailability: defaultMinimumAvailability
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -121,7 +121,7 @@ class ImportMovieTable extends Component {
|
|||||||
/>
|
/>
|
||||||
</VirtualTableRow>
|
</VirtualTableRow>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
@@ -167,7 +167,7 @@ ImportMovieTable.propTypes = {
|
|||||||
items: PropTypes.arrayOf(PropTypes.object),
|
items: PropTypes.arrayOf(PropTypes.object),
|
||||||
unmappedFolders: PropTypes.arrayOf(PropTypes.object),
|
unmappedFolders: PropTypes.arrayOf(PropTypes.object),
|
||||||
defaultMonitor: PropTypes.string.isRequired,
|
defaultMonitor: PropTypes.string.isRequired,
|
||||||
defaultQualityProfileId: PropTypes.number,
|
defaultQualityProfileIds: PropTypes.arrayOf(PropTypes.number),
|
||||||
defaultMinimumAvailability: PropTypes.string,
|
defaultMinimumAvailability: PropTypes.string,
|
||||||
allSelected: PropTypes.bool.isRequired,
|
allSelected: PropTypes.bool.isRequired,
|
||||||
allUnselected: PropTypes.bool.isRequired,
|
allUnselected: PropTypes.bool.isRequired,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ function createMapStateToProps() {
|
|||||||
(addMovie, importMovie, dimensions, allMovies) => {
|
(addMovie, importMovie, dimensions, allMovies) => {
|
||||||
return {
|
return {
|
||||||
defaultMonitor: addMovie.defaults.monitor,
|
defaultMonitor: addMovie.defaults.monitor,
|
||||||
defaultQualityProfileId: addMovie.defaults.qualityProfileId,
|
defaultQualityProfileIds: addMovie.defaults.qualityProfileIds,
|
||||||
defaultMinimumAvailability: addMovie.defaults.minimumAvailability,
|
defaultMinimumAvailability: addMovie.defaults.minimumAvailability,
|
||||||
items: importMovie.items,
|
items: importMovie.items,
|
||||||
isSmallScreen: dimensions.isSmallScreen,
|
isSmallScreen: dimensions.isSmallScreen,
|
||||||
|
|||||||
@@ -1,8 +1,25 @@
|
|||||||
.movie {
|
.container {
|
||||||
|
display: flex;
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: $menuItemHoverBackgroundColor;
|
background-color: var(--menuItemHoverBackgroundColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.movie {
|
||||||
|
flex: 1 0 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tmdbLink {
|
||||||
|
composes: link from '~Components/Link/Link.css';
|
||||||
|
|
||||||
|
margin-left: auto;
|
||||||
|
color: var(--textColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tmdbLinkIcon {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import Icon from 'Components/Icon';
|
||||||
import Link from 'Components/Link/Link';
|
import Link from 'Components/Link/Link';
|
||||||
|
import { icons } from 'Helpers/Props';
|
||||||
import ImportMovieTitle from './ImportMovieTitle';
|
import ImportMovieTitle from './ImportMovieTitle';
|
||||||
import styles from './ImportMovieSearchResult.css';
|
import styles from './ImportMovieSearchResult.css';
|
||||||
|
|
||||||
@@ -11,13 +13,14 @@ class ImportMovieSearchResult extends Component {
|
|||||||
|
|
||||||
onPress = () => {
|
onPress = () => {
|
||||||
this.props.onPress(this.props.tmdbId);
|
this.props.onPress(this.props.tmdbId);
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
|
tmdbId,
|
||||||
title,
|
title,
|
||||||
year,
|
year,
|
||||||
studio,
|
studio,
|
||||||
@@ -25,17 +28,30 @@ class ImportMovieSearchResult extends Component {
|
|||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<div className={styles.container}>
|
||||||
className={styles.movie}
|
<Link
|
||||||
onPress={this.onPress}
|
className={styles.movie}
|
||||||
>
|
onPress={this.onPress}
|
||||||
<ImportMovieTitle
|
>
|
||||||
title={title}
|
<ImportMovieTitle
|
||||||
year={year}
|
title={title}
|
||||||
network={studio}
|
year={year}
|
||||||
isExistingMovie={isExistingMovie}
|
network={studio}
|
||||||
/>
|
isExistingMovie={isExistingMovie}
|
||||||
</Link>
|
/>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Link
|
||||||
|
className={styles.tmdbLink}
|
||||||
|
to={`https://www.themoviedb.org/movie/${tmdbId}`}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
className={styles.tmdbLinkIcon}
|
||||||
|
name={icons.EXTERNAL_LINK}
|
||||||
|
size={16}
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,10 +7,10 @@
|
|||||||
padding: 6px 16px;
|
padding: 6px 16px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 35px;
|
height: 35px;
|
||||||
border: 1px solid $inputBorderColor;
|
border: 1px solid var(--inputBorderColor);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background-color: $white;
|
background-color: var(--inputBackgroundColor);
|
||||||
box-shadow: inset 0 1px 1px $inputBoxShadowColor;
|
box-shadow: inset 0 1px 1px var(--inputBoxShadowColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading {
|
.loading {
|
||||||
@@ -38,9 +38,9 @@
|
|||||||
|
|
||||||
.content {
|
.content {
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
border: 1px solid $inputBorderColor;
|
border: 1px solid var(--inputBorderColor);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background-color: $white;
|
background-color: var(--inputBackgroundColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
.searchContainer {
|
.searchContainer {
|
||||||
@@ -49,12 +49,12 @@
|
|||||||
|
|
||||||
.searchIconContainer {
|
.searchIconContainer {
|
||||||
width: 58px;
|
width: 58px;
|
||||||
border: 1px solid $inputBorderColor;
|
border: 1px solid var(--inputBorderColor);
|
||||||
border-right: none;
|
border-right: none;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
border-top-right-radius: 0;
|
border-top-right-radius: 0;
|
||||||
border-bottom-right-radius: 0;
|
border-bottom-right-radius: 0;
|
||||||
background-color: #edf1f2;
|
background-color: var(--searchIconContainerBackgroundColor);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
line-height: 33px;
|
line-height: 33px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import FormInputButton from 'Components/Form/FormInputButton';
|
|||||||
import TextInput from 'Components/Form/TextInput';
|
import TextInput from 'Components/Form/TextInput';
|
||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
import Link from 'Components/Link/Link';
|
import Link from 'Components/Link/Link';
|
||||||
|
import SpinnerButton from 'Components/Link/SpinnerButton';
|
||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
import Portal from 'Components/Portal';
|
import Portal from 'Components/Portal';
|
||||||
import { icons, kinds } from 'Helpers/Props';
|
import { icons, kinds } from 'Helpers/Props';
|
||||||
@@ -69,7 +70,7 @@ class ImportMovieSelectMovie extends Component {
|
|||||||
this.setState({ isOpen: false });
|
this.setState({ isOpen: false });
|
||||||
this._removeListener();
|
this._removeListener();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
onPress = () => {
|
onPress = () => {
|
||||||
if (this.state.isOpen) {
|
if (this.state.isOpen) {
|
||||||
@@ -79,7 +80,7 @@ class ImportMovieSelectMovie extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.setState({ isOpen: !this.state.isOpen });
|
this.setState({ isOpen: !this.state.isOpen });
|
||||||
}
|
};
|
||||||
|
|
||||||
onSearchInputChange = ({ value }) => {
|
onSearchInputChange = ({ value }) => {
|
||||||
if (this._movieLookupTimeout) {
|
if (this._movieLookupTimeout) {
|
||||||
@@ -91,17 +92,17 @@ class ImportMovieSelectMovie extends Component {
|
|||||||
this.props.onSearchInputChange(value);
|
this.props.onSearchInputChange(value);
|
||||||
}, 200);
|
}, 200);
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
onRefreshPress = () => {
|
onRefreshPress = () => {
|
||||||
this.props.onSearchInputChange(this.state.term);
|
this.props.onSearchInputChange(this.state.term);
|
||||||
}
|
};
|
||||||
|
|
||||||
onMovieSelect = (tmdbId) => {
|
onMovieSelect = (tmdbId) => {
|
||||||
this.setState({ isOpen: false });
|
this.setState({ isOpen: false });
|
||||||
|
|
||||||
this.props.onMovieSelect(tmdbId);
|
this.props.onMovieSelect(tmdbId);
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
@@ -242,7 +243,7 @@ class ImportMovieSelectMovie extends Component {
|
|||||||
<FormInputButton
|
<FormInputButton
|
||||||
kind={kinds.DEFAULT}
|
kind={kinds.DEFAULT}
|
||||||
spinnerIcon={icons.REFRESH}
|
spinnerIcon={icons.REFRESH}
|
||||||
canSpin={true}
|
ButtonComponent={SpinnerButton}
|
||||||
isSpinning={isFetching}
|
isSpinning={isFetching}
|
||||||
onPress={this.onRefreshPress}
|
onPress={this.onRefreshPress}
|
||||||
>
|
>
|
||||||
|
|||||||
+2
-2
@@ -36,7 +36,7 @@ class ImportMovieSelectMovieConnector extends Component {
|
|||||||
term,
|
term,
|
||||||
topOfQueue: true
|
topOfQueue: true
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
onMovieSelect = (tmdbId) => {
|
onMovieSelect = (tmdbId) => {
|
||||||
const {
|
const {
|
||||||
@@ -48,7 +48,7 @@ class ImportMovieSelectMovieConnector extends Component {
|
|||||||
id,
|
id,
|
||||||
selectedMovie: _.find(items, { tmdbId })
|
selectedMovie: _.find(items, { tmdbId })
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
.year {
|
.year {
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
color: $disabledColor;
|
color: var(--disabledColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
.existing {
|
.existing {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class ImportMovieRootFolderRowConnector extends Component {
|
|||||||
|
|
||||||
onDeletePress = () => {
|
onDeletePress = () => {
|
||||||
this.props.deleteRootFolder({ id: this.props.id });
|
this.props.deleteRootFolder({ id: this.props.id });
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|||||||
@@ -55,15 +55,15 @@ class ImportMovieSelectFolder extends Component {
|
|||||||
|
|
||||||
onAddNewRootFolderPress = () => {
|
onAddNewRootFolderPress = () => {
|
||||||
this.setState({ isAddNewRootFolderModalOpen: true });
|
this.setState({ isAddNewRootFolderModalOpen: true });
|
||||||
}
|
};
|
||||||
|
|
||||||
onNewRootFolderSelect = ({ value }) => {
|
onNewRootFolderSelect = ({ value }) => {
|
||||||
this.props.onNewRootFolderSelect(value);
|
this.props.onNewRootFolderSelect(value);
|
||||||
}
|
};
|
||||||
|
|
||||||
onAddRootFolderModalClose = () => {
|
onAddRootFolderModalClose = () => {
|
||||||
this.setState({ isAddNewRootFolderModalOpen: false });
|
this.setState({ isAddNewRootFolderModalOpen: false });
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
@@ -152,13 +152,19 @@ class ImportMovieSelectFolder extends Component {
|
|||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
{
|
{
|
||||||
saveError.responseJSON.map((e, index) => {
|
Array.isArray(saveError.responseJSON) ?
|
||||||
return (
|
saveError.responseJSON.map((e, index) => {
|
||||||
<li key={index}>
|
return (
|
||||||
{e.errorMessage}
|
<li key={index}>
|
||||||
</li>
|
{e.errorMessage}
|
||||||
);
|
</li>
|
||||||
})
|
);
|
||||||
|
}) :
|
||||||
|
<li>
|
||||||
|
{
|
||||||
|
JSON.stringify(saveError.responseJSON)
|
||||||
|
}
|
||||||
|
</li>
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
</Alert> :
|
</Alert> :
|
||||||
|
|||||||
@@ -58,11 +58,11 @@ class ImportMovieSelectFolderConnector extends Component {
|
|||||||
|
|
||||||
onNewRootFolderSelect = (path) => {
|
onNewRootFolderSelect = (path) => {
|
||||||
this.props.addRootFolder({ path });
|
this.props.addRootFolder({ path });
|
||||||
}
|
};
|
||||||
|
|
||||||
onDeleteRootFolderPress = (id) => {
|
onDeleteRootFolderPress = (id) => {
|
||||||
this.props.deleteRootFolder({ id });
|
this.props.deleteRootFolder({ id });
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|||||||
@@ -4,16 +4,19 @@ import React from 'react';
|
|||||||
import DocumentTitle from 'react-document-title';
|
import DocumentTitle from 'react-document-title';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import PageConnector from 'Components/Page/PageConnector';
|
import PageConnector from 'Components/Page/PageConnector';
|
||||||
|
import ApplyTheme from './ApplyTheme';
|
||||||
import AppRoutes from './AppRoutes';
|
import AppRoutes from './AppRoutes';
|
||||||
|
|
||||||
function App({ store, history }) {
|
function App({ store, history }) {
|
||||||
return (
|
return (
|
||||||
<DocumentTitle title="Radarr">
|
<DocumentTitle title={window.Radarr.instanceName}>
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<ConnectedRouter history={history}>
|
<ConnectedRouter history={history}>
|
||||||
<PageConnector>
|
<ApplyTheme>
|
||||||
<AppRoutes app={App} />
|
<PageConnector>
|
||||||
</PageConnector>
|
<AppRoutes app={App} />
|
||||||
|
</PageConnector>
|
||||||
|
</ApplyTheme>
|
||||||
</ConnectedRouter>
|
</ConnectedRouter>
|
||||||
</Provider>
|
</Provider>
|
||||||
</DocumentTitle>
|
</DocumentTitle>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import QueueConnector from 'Activity/Queue/QueueConnector';
|
|||||||
import AddNewMovieConnector from 'AddMovie/AddNewMovie/AddNewMovieConnector';
|
import AddNewMovieConnector from 'AddMovie/AddNewMovie/AddNewMovieConnector';
|
||||||
import ImportMovies from 'AddMovie/ImportMovie/ImportMovies';
|
import ImportMovies from 'AddMovie/ImportMovie/ImportMovies';
|
||||||
import CalendarPageConnector from 'Calendar/CalendarPageConnector';
|
import CalendarPageConnector from 'Calendar/CalendarPageConnector';
|
||||||
|
import CollectionConnector from 'Collection/CollectionConnector';
|
||||||
import NotFound from 'Components/NotFound';
|
import NotFound from 'Components/NotFound';
|
||||||
import Switch from 'Components/Router/Switch';
|
import Switch from 'Components/Router/Switch';
|
||||||
import DiscoverMovieConnector from 'DiscoverMovie/DiscoverMovieConnector';
|
import DiscoverMovieConnector from 'DiscoverMovie/DiscoverMovieConnector';
|
||||||
@@ -21,7 +22,7 @@ import MediaManagementConnector from 'Settings/MediaManagement/MediaManagementCo
|
|||||||
import MetadataSettings from 'Settings/Metadata/MetadataSettings';
|
import MetadataSettings from 'Settings/Metadata/MetadataSettings';
|
||||||
import NotificationSettings from 'Settings/Notifications/NotificationSettings';
|
import NotificationSettings from 'Settings/Notifications/NotificationSettings';
|
||||||
import Profiles from 'Settings/Profiles/Profiles';
|
import Profiles from 'Settings/Profiles/Profiles';
|
||||||
import Quality from 'Settings/Quality/Quality';
|
import QualityConnector from 'Settings/Quality/QualityConnector';
|
||||||
import Settings from 'Settings/Settings';
|
import Settings from 'Settings/Settings';
|
||||||
import TagSettings from 'Settings/Tags/TagSettings';
|
import TagSettings from 'Settings/Tags/TagSettings';
|
||||||
import UISettingsConnector from 'Settings/UI/UISettingsConnector';
|
import UISettingsConnector from 'Settings/UI/UISettingsConnector';
|
||||||
@@ -72,6 +73,11 @@ function AppRoutes(props) {
|
|||||||
component={AddNewMovieConnector}
|
component={AddNewMovieConnector}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path="/collections"
|
||||||
|
component={CollectionConnector}
|
||||||
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path="/add/import"
|
path="/add/import"
|
||||||
component={ImportMovies}
|
component={ImportMovies}
|
||||||
@@ -137,7 +143,7 @@ function AppRoutes(props) {
|
|||||||
|
|
||||||
<Route
|
<Route
|
||||||
path="/settings/quality"
|
path="/settings/quality"
|
||||||
component={Quality}
|
component={QualityConnector}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Fragment, useCallback, useEffect } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import themes from 'Styles/Themes';
|
||||||
|
|
||||||
|
function createMapStateToProps() {
|
||||||
|
return createSelector(
|
||||||
|
(state) => state.settings.ui.item.theme || window.Radarr.theme,
|
||||||
|
(
|
||||||
|
theme
|
||||||
|
) => {
|
||||||
|
return {
|
||||||
|
theme
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ApplyTheme({ theme, children }) {
|
||||||
|
// Update the CSS Variables
|
||||||
|
const updateCSSVariables = useCallback(() => {
|
||||||
|
const arrayOfVariableKeys = Object.keys(themes[theme]);
|
||||||
|
const arrayOfVariableValues = Object.values(themes[theme]);
|
||||||
|
|
||||||
|
// Loop through each array key and set the CSS Variables
|
||||||
|
arrayOfVariableKeys.forEach((cssVariableKey, index) => {
|
||||||
|
// Based on our snippet from MDN
|
||||||
|
document.documentElement.style.setProperty(
|
||||||
|
`--${cssVariableKey}`,
|
||||||
|
arrayOfVariableValues[index]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}, [theme]);
|
||||||
|
|
||||||
|
// On Component Mount and Component Update
|
||||||
|
useEffect(() => {
|
||||||
|
updateCSSVariables(theme);
|
||||||
|
}, [updateCSSVariables, theme]);
|
||||||
|
|
||||||
|
return <Fragment>{children}</Fragment>;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplyTheme.propTypes = {
|
||||||
|
theme: PropTypes.string.isRequired,
|
||||||
|
children: PropTypes.object.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(createMapStateToProps)(ApplyTheme);
|
||||||
@@ -2,11 +2,11 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
border-bottom: 1px solid $borderColor;
|
border-bottom: 1px solid var(--borderColor);
|
||||||
font-size: $defaultFontSize;
|
font-size: $defaultFontSize;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: $tableRowHoverBackgroundColor;
|
background-color: var(--tableRowHoverBackgroundColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,11 +27,11 @@ class AgendaEvent extends Component {
|
|||||||
|
|
||||||
onPress = () => {
|
onPress = () => {
|
||||||
this.setState({ isDetailsModalOpen: true });
|
this.setState({ isDetailsModalOpen: true });
|
||||||
}
|
};
|
||||||
|
|
||||||
onDetailsModalClose = () => {
|
onDetailsModalClose = () => {
|
||||||
this.setState({ isDetailsModalOpen: false });
|
this.setState({ isDetailsModalOpen: false });
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|||||||
@@ -119,43 +119,43 @@ class CalendarConnector extends Component {
|
|||||||
|
|
||||||
this.props.fetchQueueDetails({ time, view });
|
this.props.fetchQueueDetails({ time, view });
|
||||||
this.props.fetchCalendar({ time, view });
|
this.props.fetchCalendar({ time, view });
|
||||||
}
|
};
|
||||||
|
|
||||||
scheduleUpdate = () => {
|
scheduleUpdate = () => {
|
||||||
this.clearUpdateTimeout();
|
this.clearUpdateTimeout();
|
||||||
|
|
||||||
this.updateTimeoutId = setTimeout(this.updateCalendar, UPDATE_DELAY);
|
this.updateTimeoutId = setTimeout(this.updateCalendar, UPDATE_DELAY);
|
||||||
}
|
};
|
||||||
|
|
||||||
clearUpdateTimeout = () => {
|
clearUpdateTimeout = () => {
|
||||||
if (this.updateTimeoutId) {
|
if (this.updateTimeoutId) {
|
||||||
clearTimeout(this.updateTimeoutId);
|
clearTimeout(this.updateTimeoutId);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
updateCalendar = () => {
|
updateCalendar = () => {
|
||||||
this.props.gotoCalendarToday();
|
this.props.gotoCalendarToday();
|
||||||
this.scheduleUpdate();
|
this.scheduleUpdate();
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Listeners
|
// Listeners
|
||||||
|
|
||||||
onCalendarViewChange = (view) => {
|
onCalendarViewChange = (view) => {
|
||||||
this.props.setCalendarView({ view });
|
this.props.setCalendarView({ view });
|
||||||
}
|
};
|
||||||
|
|
||||||
onTodayPress = () => {
|
onTodayPress = () => {
|
||||||
this.props.gotoCalendarToday();
|
this.props.gotoCalendarToday();
|
||||||
}
|
};
|
||||||
|
|
||||||
onPreviousPress = () => {
|
onPreviousPress = () => {
|
||||||
this.props.gotoCalendarPreviousRange();
|
this.props.gotoCalendarPreviousRange();
|
||||||
}
|
};
|
||||||
|
|
||||||
onNextPress = () => {
|
onNextPress = () => {
|
||||||
this.props.gotoCalendarNextRange();
|
this.props.gotoCalendarNextRange();
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|||||||
@@ -44,23 +44,23 @@ class CalendarPage extends Component {
|
|||||||
const days = Math.max(3, Math.min(7, Math.floor(width / MINIMUM_DAY_WIDTH)));
|
const days = Math.max(3, Math.min(7, Math.floor(width / MINIMUM_DAY_WIDTH)));
|
||||||
|
|
||||||
this.props.onDaysCountChange(days);
|
this.props.onDaysCountChange(days);
|
||||||
}
|
};
|
||||||
|
|
||||||
onGetCalendarLinkPress = () => {
|
onGetCalendarLinkPress = () => {
|
||||||
this.setState({ isCalendarLinkModalOpen: true });
|
this.setState({ isCalendarLinkModalOpen: true });
|
||||||
}
|
};
|
||||||
|
|
||||||
onGetCalendarLinkModalClose = () => {
|
onGetCalendarLinkModalClose = () => {
|
||||||
this.setState({ isCalendarLinkModalOpen: false });
|
this.setState({ isCalendarLinkModalOpen: false });
|
||||||
}
|
};
|
||||||
|
|
||||||
onOptionsPress = () => {
|
onOptionsPress = () => {
|
||||||
this.setState({ isOptionsModalOpen: true });
|
this.setState({ isOptionsModalOpen: true });
|
||||||
}
|
};
|
||||||
|
|
||||||
onOptionsModalClose = () => {
|
onOptionsModalClose = () => {
|
||||||
this.setState({ isOptionsModalOpen: false });
|
this.setState({ isOptionsModalOpen: false });
|
||||||
}
|
};
|
||||||
|
|
||||||
onSearchMissingPress = () => {
|
onSearchMissingPress = () => {
|
||||||
const {
|
const {
|
||||||
@@ -69,7 +69,7 @@ class CalendarPage extends Component {
|
|||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
onSearchMissingPress(missingMovieIds);
|
onSearchMissingPress(missingMovieIds);
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ function createMissingMovieIdsSelector() {
|
|||||||
const inCinemas = movie.inCinemas;
|
const inCinemas = movie.inCinemas;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!movie.hasFile &&
|
(!movie.statistics || movie.statistics.movieFileCount === 0) &&
|
||||||
moment(inCinemas).isAfter(start) &&
|
moment(inCinemas).isAfter(start) &&
|
||||||
moment(inCinemas).isBefore(end) &&
|
moment(inCinemas).isBefore(end) &&
|
||||||
isBefore(movie.inCinemas) &&
|
isBefore(movie.inCinemas) &&
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
flex: 1 0 14.28%;
|
flex: 1 0 14.28%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
min-height: 70px;
|
min-height: 70px;
|
||||||
border-bottom: 1px solid $calendarBorderColor;
|
border-bottom: 1px solid var(--calendarBorderColor);
|
||||||
border-left: 1px solid $calendarBorderColor;
|
border-left: 1px solid var(--calendarBorderColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
.isSingleDay {
|
.isSingleDay {
|
||||||
@@ -12,14 +12,14 @@
|
|||||||
|
|
||||||
.dayOfMonth {
|
.dayOfMonth {
|
||||||
padding-right: 5px;
|
padding-right: 5px;
|
||||||
border-bottom: 1px solid $calendarBorderColor;
|
border-bottom: 1px solid var(--calendarBorderColor);
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.isToday {
|
.isToday {
|
||||||
background-color: $calendarTodayBackgroundColor;
|
background-color: var(--calendarTodayBackgroundColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
.isDifferentMonth {
|
.isDifferentMonth {
|
||||||
color: $disabledColor;
|
color: var(--disabledColor);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.days {
|
.days {
|
||||||
display: flex;
|
display: flex;
|
||||||
border-right: 1px solid $calendarBorderColor;
|
border-right: 1px solid var(--calendarBorderColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
.day,
|
.day,
|
||||||
|
|||||||
@@ -60,20 +60,20 @@ class CalendarDays extends Component {
|
|||||||
this.setState({ todaysDate: todaysDate.toISOString() });
|
this.setState({ todaysDate: todaysDate.toISOString() });
|
||||||
|
|
||||||
this.updateTimeoutId = setTimeout(this.scheduleUpdate, diff);
|
this.updateTimeoutId = setTimeout(this.scheduleUpdate, diff);
|
||||||
}
|
};
|
||||||
|
|
||||||
clearUpdateTimeout = () => {
|
clearUpdateTimeout = () => {
|
||||||
if (this.updateTimeoutId) {
|
if (this.updateTimeoutId) {
|
||||||
clearTimeout(this.updateTimeoutId);
|
clearTimeout(this.updateTimeoutId);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Listeners
|
// Listeners
|
||||||
|
|
||||||
onEventModalOpenToggle = (isEventModalOpen) => {
|
onEventModalOpenToggle = (isEventModalOpen) => {
|
||||||
this.setState({ isEventModalOpen });
|
this.setState({ isEventModalOpen });
|
||||||
}
|
};
|
||||||
|
|
||||||
onTouchStart = (event) => {
|
onTouchStart = (event) => {
|
||||||
const touches = event.touches;
|
const touches = event.touches;
|
||||||
@@ -92,7 +92,7 @@ class CalendarDays extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this._touchStart = touchStart;
|
this._touchStart = touchStart;
|
||||||
}
|
};
|
||||||
|
|
||||||
onTouchEnd = (event) => {
|
onTouchEnd = (event) => {
|
||||||
const touches = event.changedTouches;
|
const touches = event.changedTouches;
|
||||||
@@ -109,17 +109,17 @@ class CalendarDays extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this._touchStart = null;
|
this._touchStart = null;
|
||||||
}
|
};
|
||||||
|
|
||||||
onTouchCancel = (event) => {
|
onTouchCancel = (event) => {
|
||||||
this._touchStart = null;
|
this._touchStart = null;
|
||||||
}
|
};
|
||||||
|
|
||||||
onTouchMove = (event) => {
|
onTouchMove = (event) => {
|
||||||
if (!this._touchStart) {
|
if (!this._touchStart) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.dayOfWeek {
|
.dayOfWeek {
|
||||||
flex: 1 0 14.28%;
|
flex: 1 0 14.28%;
|
||||||
background-color: #e4eaec;
|
background-color: var(--calendarBackgroudColor);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -9,5 +9,5 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.isToday {
|
.isToday {
|
||||||
background-color: $calendarTodayBackgroundColor;
|
background-color: var(--calendarTodayBackgroundColor);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,13 +47,13 @@ class DaysOfWeek extends Component {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.updateTimeoutId = setTimeout(this.scheduleUpdate, diff);
|
this.updateTimeoutId = setTimeout(this.scheduleUpdate, diff);
|
||||||
}
|
};
|
||||||
|
|
||||||
clearUpdateTimeout = () => {
|
clearUpdateTimeout = () => {
|
||||||
if (this.updateTimeoutId) {
|
if (this.updateTimeoutId) {
|
||||||
clearTimeout(this.updateTimeoutId);
|
clearTimeout(this.updateTimeoutId);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
|
$fullColorGradient: rgba(244, 245, 246, 0.2);
|
||||||
|
|
||||||
.event {
|
.event {
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
margin: 4px 2px;
|
margin: 4px 2px;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
border-bottom: 1px solid $borderColor;
|
border-bottom: 1px solid var(--calendarBorderColor);
|
||||||
border-left: 4px solid $borderColor;
|
border-left: 4px solid var(--calendarBorderColor);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
|
||||||
&:global(.colorImpaired) {
|
&:global(.colorImpaired) {
|
||||||
@@ -15,10 +17,10 @@
|
|||||||
composes: link from '~Components/Link/Link.css';
|
composes: link from '~Components/Link/Link.css';
|
||||||
|
|
||||||
display: block;
|
display: block;
|
||||||
color: $defaultColor;
|
color: var(--defaultColor);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $defaultColor;
|
color: var(--defaultColor);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -29,7 +31,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.movieInfo {
|
.movieInfo {
|
||||||
color: $calendarTextDim;
|
color: var(--calendarTextDim);
|
||||||
}
|
}
|
||||||
|
|
||||||
.movieTitle,
|
.movieTitle,
|
||||||
@@ -40,7 +42,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.movieTitle {
|
.movieTitle {
|
||||||
color: #3a3f51;
|
color: var(--calendarTextDimAlternate);
|
||||||
font-size: $defaultFontSize;
|
font-size: $defaultFontSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,37 +55,85 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
.downloaded {
|
.downloaded {
|
||||||
border-left-color: $successColor !important;
|
border-left-color: var(--successColor) !important;
|
||||||
|
|
||||||
|
&:global(.fullColor) {
|
||||||
|
background-color: rgba(39, 194, 76, 0.4) !important;
|
||||||
|
}
|
||||||
|
|
||||||
&:global(.colorImpaired) {
|
&:global(.colorImpaired) {
|
||||||
border-left-color: color($successColor, saturation(+15%)) !important;
|
border-left-color: color(var(--successColor), saturation(+15%)) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.queue {
|
.queue {
|
||||||
border-left-color: $purple !important;
|
border-left-color: var(--purple) !important;
|
||||||
|
|
||||||
|
&:global(.fullColor) {
|
||||||
|
background-color: rgba(122, 67, 182, 0.4) !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.unmonitored {
|
.unmonitored {
|
||||||
border-left-color: $gray !important;
|
border-left-color: var(--gray) !important;
|
||||||
|
|
||||||
|
&:global(.fullColor) {
|
||||||
|
background-color: rgba(173, 173, 173, 0.5) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:global(.colorImpaired) {
|
||||||
|
background: repeating-linear-gradient(45deg, var(--colorImpairedGradientDark), var(--colorImpairedGradientDark) 5px, var(--colorImpairedGradient) 5px, var(--colorImpairedGradient) 10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:global(.fullColor.colorImpaired) {
|
||||||
|
background: repeating-linear-gradient(45deg, $fullColorGradient, $fullColorGradient 5px, transparent 5px, transparent 10px);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.missingUnmonitored {
|
.missingUnmonitored {
|
||||||
border-left-color: $warningColor !important;
|
border-left-color: var(--warningColor) !important;
|
||||||
|
|
||||||
|
&:global(.fullColor) {
|
||||||
|
background-color: rgba(255, 165, 0, 0.6) !important;
|
||||||
|
}
|
||||||
|
|
||||||
&:global(.colorImpaired) {
|
&:global(.colorImpaired) {
|
||||||
background: repeating-linear-gradient(45deg, $colorImpairedGradientDark, $colorImpairedGradientDark 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
|
background: repeating-linear-gradient(45deg, var(--colorImpairedGradientDark), var(--colorImpairedGradientDark) 5px, var(--colorImpairedGradient) 5px, var(--colorImpairedGradient) 10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:global(.fullColor.colorImpaired) {
|
||||||
|
background: repeating-linear-gradient(45deg, $fullColorGradient, $fullColorGradient 5px, transparent 5px, transparent 10px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.missingMonitored {
|
.missingMonitored {
|
||||||
border-left-color: $dangerColor !important;
|
border-left-color: var(--dangerColor) !important;
|
||||||
|
|
||||||
|
&:global(.fullColor) {
|
||||||
|
background-color: rgba(240, 80, 80, 0.6) !important;
|
||||||
|
}
|
||||||
|
|
||||||
&:global(.colorImpaired) {
|
&:global(.colorImpaired) {
|
||||||
background: repeating-linear-gradient(90deg, $colorImpairedGradientDark, $colorImpairedGradientDark 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
|
background: repeating-linear-gradient(90deg, var(--colorImpairedGradientDark), var(--colorImpairedGradientDark) 5px, var(--colorImpairedGradient) 5px, var(--colorImpairedGradient) 10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:global(.fullColor.colorImpaired) {
|
||||||
|
background: repeating-linear-gradient(90deg, $fullColorGradient, $fullColorGradient 5px, transparent 5px, transparent 10px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.continuing {
|
.unaired {
|
||||||
border-left-color: $primaryColor !important;
|
border-left-color: var(--primaryColor) !important;
|
||||||
|
|
||||||
|
&:global(.fullColor) {
|
||||||
|
background-color: rgba(93, 156, 236, 0.4) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:global(.colorImpaired) {
|
||||||
|
background: repeating-linear-gradient(90deg, var(--colorImpairedGradientDark), var(--colorImpairedGradientDark) 5px, var(--colorImpairedGradient) 5px, var(--colorImpairedGradient) 10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:global(.fullColor.colorImpaired) {
|
||||||
|
background: repeating-linear-gradient(90deg, $fullColorGradient, $fullColorGradient 5px, transparent 5px, transparent 10px);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ class CalendarEvent extends Component {
|
|||||||
queueItem,
|
queueItem,
|
||||||
showMovieInformation,
|
showMovieInformation,
|
||||||
showCutoffUnmetIcon,
|
showCutoffUnmetIcon,
|
||||||
|
fullColorEvents,
|
||||||
colorImpairedMode,
|
colorImpairedMode,
|
||||||
date
|
date
|
||||||
} = this.props;
|
} = this.props;
|
||||||
@@ -62,7 +63,8 @@ class CalendarEvent extends Component {
|
|||||||
styles.event,
|
styles.event,
|
||||||
styles.link,
|
styles.link,
|
||||||
styles[statusStyle],
|
styles[statusStyle],
|
||||||
colorImpairedMode && 'colorImpaired'
|
colorImpairedMode && 'colorImpaired',
|
||||||
|
fullColorEvents && 'fullColor'
|
||||||
)}
|
)}
|
||||||
// component="div"
|
// component="div"
|
||||||
to={link}
|
to={link}
|
||||||
@@ -97,7 +99,7 @@ class CalendarEvent extends Component {
|
|||||||
<Icon
|
<Icon
|
||||||
className={styles.statusIcon}
|
className={styles.statusIcon}
|
||||||
name={icons.MOVIE_FILE}
|
name={icons.MOVIE_FILE}
|
||||||
kind={kinds.WARNING}
|
kind={fullColorEvents ? kinds.DEFAULT : kinds.WARNING}
|
||||||
title={translate('QualityCutoffHasNotBeenMet')}
|
title={translate('QualityCutoffHasNotBeenMet')}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
@@ -142,11 +144,12 @@ CalendarEvent.propTypes = {
|
|||||||
digitalRelease: PropTypes.string,
|
digitalRelease: PropTypes.string,
|
||||||
monitored: PropTypes.bool.isRequired,
|
monitored: PropTypes.bool.isRequired,
|
||||||
certification: PropTypes.string,
|
certification: PropTypes.string,
|
||||||
hasFile: PropTypes.bool.isRequired,
|
hasFile: PropTypes.bool,
|
||||||
grabbed: PropTypes.bool,
|
grabbed: PropTypes.bool,
|
||||||
queueItem: PropTypes.object,
|
queueItem: PropTypes.object,
|
||||||
showMovieInformation: PropTypes.bool.isRequired,
|
showMovieInformation: PropTypes.bool.isRequired,
|
||||||
showCutoffUnmetIcon: PropTypes.bool.isRequired,
|
showCutoffUnmetIcon: PropTypes.bool.isRequired,
|
||||||
|
fullColorEvents: PropTypes.bool.isRequired,
|
||||||
timeFormat: PropTypes.string.isRequired,
|
timeFormat: PropTypes.string.isRequired,
|
||||||
colorImpairedMode: PropTypes.bool.isRequired,
|
colorImpairedMode: PropTypes.bool.isRequired,
|
||||||
date: PropTypes.string.isRequired
|
date: PropTypes.string.isRequired
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import PropTypes from 'prop-types';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import QueueDetails from 'Activity/Queue/QueueDetails';
|
import QueueDetails from 'Activity/Queue/QueueDetails';
|
||||||
import CircularProgressBar from 'Components/CircularProgressBar';
|
import CircularProgressBar from 'Components/CircularProgressBar';
|
||||||
import colors from 'Styles/Variables/colors';
|
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
|
|
||||||
function CalendarEventQueueDetails(props) {
|
function CalendarEventQueueDetails(props) {
|
||||||
@@ -35,7 +34,7 @@ function CalendarEventQueueDetails(props) {
|
|||||||
progress={progress}
|
progress={progress}
|
||||||
size={20}
|
size={20}
|
||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
strokeColor={colors.purple}
|
strokeColor={'#7a43b6'}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ class CalendarHeader extends Component {
|
|||||||
this.setState({ view }, () => {
|
this.setState({ view }, () => {
|
||||||
this.props.onViewChange(view);
|
this.props.onViewChange(view);
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|||||||
@@ -41,19 +41,19 @@ class CalendarHeaderConnector extends Component {
|
|||||||
|
|
||||||
onViewChange = (view) => {
|
onViewChange = (view) => {
|
||||||
this.props.setCalendarView({ view });
|
this.props.setCalendarView({ view });
|
||||||
}
|
};
|
||||||
|
|
||||||
onTodayPress = () => {
|
onTodayPress = () => {
|
||||||
this.props.gotoCalendarToday();
|
this.props.gotoCalendarToday();
|
||||||
}
|
};
|
||||||
|
|
||||||
onPreviousPress = () => {
|
onPreviousPress = () => {
|
||||||
this.props.gotoCalendarPreviousRange();
|
this.props.gotoCalendarPreviousRange();
|
||||||
}
|
};
|
||||||
|
|
||||||
onNextPress = () => {
|
onNextPress = () => {
|
||||||
this.props.gotoCalendarNextRange();
|
this.props.gotoCalendarNextRange();
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class CalendarHeaderViewButton extends Component {
|
|||||||
|
|
||||||
onPress = () => {
|
onPress = () => {
|
||||||
this.props.onPress(this.props.view);
|
this.props.onPress(this.props.view);
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import styles from './Legend.css';
|
|||||||
function Legend(props) {
|
function Legend(props) {
|
||||||
const {
|
const {
|
||||||
showCutoffUnmetIcon,
|
showCutoffUnmetIcon,
|
||||||
|
fullColorEvents,
|
||||||
colorImpairedMode
|
colorImpairedMode
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
@@ -19,7 +20,7 @@ function Legend(props) {
|
|||||||
<LegendIconItem
|
<LegendIconItem
|
||||||
name={translate('CutoffUnmet')}
|
name={translate('CutoffUnmet')}
|
||||||
icon={icons.MOVIE_FILE}
|
icon={icons.MOVIE_FILE}
|
||||||
kind={kinds.WARNING}
|
kind={fullColorEvents ? kinds.DEFAULT : kinds.WARNING}
|
||||||
tooltip={translate('QualityOrLangCutoffHasNotBeenMet')}
|
tooltip={translate('QualityOrLangCutoffHasNotBeenMet')}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -31,12 +32,14 @@ function Legend(props) {
|
|||||||
<LegendItem
|
<LegendItem
|
||||||
style='ended'
|
style='ended'
|
||||||
name={translate('DownloadedAndMonitored')}
|
name={translate('DownloadedAndMonitored')}
|
||||||
|
fullColorEvents={fullColorEvents}
|
||||||
colorImpairedMode={colorImpairedMode}
|
colorImpairedMode={colorImpairedMode}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<LegendItem
|
<LegendItem
|
||||||
style='availNotMonitored'
|
style='availNotMonitored'
|
||||||
name={translate('DownloadedButNotMonitored')}
|
name={translate('DownloadedButNotMonitored')}
|
||||||
|
fullColorEvents={fullColorEvents}
|
||||||
colorImpairedMode={colorImpairedMode}
|
colorImpairedMode={colorImpairedMode}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -45,12 +48,14 @@ function Legend(props) {
|
|||||||
<LegendItem
|
<LegendItem
|
||||||
style='missingMonitored'
|
style='missingMonitored'
|
||||||
name={translate('MissingMonitoredAndConsideredAvailable')}
|
name={translate('MissingMonitoredAndConsideredAvailable')}
|
||||||
|
fullColorEvents={fullColorEvents}
|
||||||
colorImpairedMode={colorImpairedMode}
|
colorImpairedMode={colorImpairedMode}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<LegendItem
|
<LegendItem
|
||||||
style='missingUnmonitored'
|
style='missingUnmonitored'
|
||||||
name={translate('MissingNotMonitored')}
|
name={translate('MissingNotMonitored')}
|
||||||
|
fullColorEvents={fullColorEvents}
|
||||||
colorImpairedMode={colorImpairedMode}
|
colorImpairedMode={colorImpairedMode}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -59,12 +64,14 @@ function Legend(props) {
|
|||||||
<LegendItem
|
<LegendItem
|
||||||
style='queue'
|
style='queue'
|
||||||
name={translate('Queued')}
|
name={translate('Queued')}
|
||||||
|
fullColorEvents={fullColorEvents}
|
||||||
colorImpairedMode={colorImpairedMode}
|
colorImpairedMode={colorImpairedMode}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<LegendItem
|
<LegendItem
|
||||||
style='continuing'
|
style='continuing'
|
||||||
name={translate('Unreleased')}
|
name={translate('Unreleased')}
|
||||||
|
fullColorEvents={fullColorEvents}
|
||||||
colorImpairedMode={colorImpairedMode}
|
colorImpairedMode={colorImpairedMode}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -79,7 +86,9 @@ function Legend(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Legend.propTypes = {
|
Legend.propTypes = {
|
||||||
|
view: PropTypes.string.isRequired,
|
||||||
showCutoffUnmetIcon: PropTypes.bool.isRequired,
|
showCutoffUnmetIcon: PropTypes.bool.isRequired,
|
||||||
|
fullColorEvents: PropTypes.bool.isRequired,
|
||||||
colorImpairedMode: PropTypes.bool.isRequired
|
colorImpairedMode: PropTypes.bool.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -6,10 +6,12 @@ import Legend from './Legend';
|
|||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state) => state.calendar.options,
|
(state) => state.calendar.options,
|
||||||
|
(state) => state.calendar.view,
|
||||||
createUISettingsSelector(),
|
createUISettingsSelector(),
|
||||||
(calendarOptions, uiSettings) => {
|
(calendarOptions, view, uiSettings) => {
|
||||||
return {
|
return {
|
||||||
...calendarOptions,
|
...calendarOptions,
|
||||||
|
view,
|
||||||
colorImpairedMode: uiSettings.enableColorImpairedMode
|
colorImpairedMode: uiSettings.enableColorImpairedMode
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ function LegendIconItem(props) {
|
|||||||
name,
|
name,
|
||||||
icon,
|
icon,
|
||||||
kind,
|
kind,
|
||||||
|
darken,
|
||||||
tooltip
|
tooltip
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
@@ -19,6 +20,7 @@ function LegendIconItem(props) {
|
|||||||
<Icon
|
<Icon
|
||||||
className={styles.icon}
|
className={styles.icon}
|
||||||
name={icon}
|
name={icon}
|
||||||
|
darken={darken}
|
||||||
kind={kind}
|
kind={kind}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -31,7 +33,12 @@ LegendIconItem.propTypes = {
|
|||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
icon: PropTypes.object.isRequired,
|
icon: PropTypes.object.isRequired,
|
||||||
kind: PropTypes.string.isRequired,
|
kind: PropTypes.string.isRequired,
|
||||||
|
darken: PropTypes.bool.isRequired,
|
||||||
tooltip: PropTypes.string.isRequired
|
tooltip: PropTypes.string.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
LegendIconItem.defaultProps = {
|
||||||
|
darken: false
|
||||||
|
};
|
||||||
|
|
||||||
export default LegendIconItem;
|
export default LegendIconItem;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
$fullColorGradient: rgba(244, 245, 246, 0.2);
|
||||||
|
|
||||||
.legendItemContainer {
|
.legendItemContainer {
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
width: 220px;
|
width: 220px;
|
||||||
@@ -20,53 +22,55 @@
|
|||||||
.queue {
|
.queue {
|
||||||
composes: legendItemColor;
|
composes: legendItemColor;
|
||||||
|
|
||||||
background-color: $queueColor;
|
background-color: var(--queueColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
.continuing {
|
.continuing {
|
||||||
composes: legendItemColor;
|
composes: legendItemColor;
|
||||||
|
|
||||||
background-color: $primaryColor;
|
background-color: var(--primaryColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
.availNotMonitored {
|
.availNotMonitored {
|
||||||
composes: legendItemColor;
|
composes: legendItemColor;
|
||||||
|
|
||||||
background-color: $darkGray;
|
background-color: var(--darkGray);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ended {
|
.ended {
|
||||||
composes: legendItemColor;
|
composes: legendItemColor;
|
||||||
|
|
||||||
background-color: $successColor;
|
background-color: var(--successColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
.missingMonitored {
|
.missingMonitored {
|
||||||
composes: legendItemColor;
|
composes: legendItemColor;
|
||||||
|
|
||||||
background-color: $dangerColor;
|
background-color: var(--dangerColor);
|
||||||
|
|
||||||
&:global(.colorImpaired) {
|
&: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);
|
background: repeating-linear-gradient(90deg, color(var(--dangerColor) shade(5%)), color(var(--dangerColor) shade(5%)) 5px, color(var(--dangerColor) shade(15%)) 5px, color(var(--dangerColor) shade(15%)) 10px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.missingUnmonitored {
|
.missingUnmonitored {
|
||||||
composes: legendItemColor;
|
composes: legendItemColor;
|
||||||
|
|
||||||
background-color: $warningColor;
|
background-color: var(--warningColor);
|
||||||
|
|
||||||
&:global(.colorImpaired) {
|
&:global(.colorImpaired) {
|
||||||
background: repeating-linear-gradient(45deg, $warningColor, $warningColor 5px, color($warningColor tint(15%)) 5px, color($warningColor tint(15%)) 10px);
|
background: repeating-linear-gradient(45deg, var(--warningColor), var(--warningColor) 5px, color(var(--warningColor) tint(15%)) 5px, color(var(--warningColor) tint(15%)) 10px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.missingMonitoredColorImpaired {
|
.missingMonitoredColorImpaired {
|
||||||
background: repeating-linear-gradient(90deg, $colorImpairedGradientDark, $colorImpairedGradientDark 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
|
background: repeating-linear-gradient(90deg, var(--colorImpairedGradientDark), var(--colorImpairedGradientDark) 5px, var(--colorImpairedGradient) 5px, var(--colorImpairedGradient) 10px);
|
||||||
|
color: var(--white);
|
||||||
}
|
}
|
||||||
|
|
||||||
.missingUnmonitoredColorImpaired {
|
.missingUnmonitoredColorImpaired {
|
||||||
background: repeating-linear-gradient(45deg, $colorImpairedGradientDark, $colorImpairedGradientDark 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
|
background: repeating-linear-gradient(45deg, var(--colorImpairedGradientDark), var(--colorImpairedGradientDark) 5px, var(--colorImpairedGradient) 5px, var(--colorImpairedGradient) 10px);
|
||||||
|
color: var(--white);
|
||||||
}
|
}
|
||||||
|
|
||||||
.legendItemText {
|
.legendItemText {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ function LegendItem(props) {
|
|||||||
const {
|
const {
|
||||||
name,
|
name,
|
||||||
style,
|
style,
|
||||||
|
fullColorEvents,
|
||||||
colorImpairedMode
|
colorImpairedMode
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
@@ -16,7 +17,8 @@ function LegendItem(props) {
|
|||||||
className={classNames(
|
className={classNames(
|
||||||
styles.legendItem,
|
styles.legendItem,
|
||||||
styles[style],
|
styles[style],
|
||||||
colorImpairedMode && 'colorImpaired'
|
colorImpairedMode && 'colorImpaired',
|
||||||
|
fullColorEvents && 'fullColor'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<div className={classNames(styles.legendItemText, colorImpairedMode && styles[`${style}ColorImpaired`])}>
|
<div className={classNames(styles.legendItemText, colorImpairedMode && styles[`${style}ColorImpaired`])}>
|
||||||
@@ -29,6 +31,7 @@ function LegendItem(props) {
|
|||||||
LegendItem.propTypes = {
|
LegendItem.propTypes = {
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
style: PropTypes.string.isRequired,
|
style: PropTypes.string.isRequired,
|
||||||
|
fullColorEvents: PropTypes.bool.isRequired,
|
||||||
colorImpairedMode: PropTypes.bool.isRequired
|
colorImpairedMode: PropTypes.bool.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -26,14 +26,16 @@ class CalendarOptionsModalContent extends Component {
|
|||||||
firstDayOfWeek,
|
firstDayOfWeek,
|
||||||
calendarWeekColumnHeader,
|
calendarWeekColumnHeader,
|
||||||
timeFormat,
|
timeFormat,
|
||||||
enableColorImpairedMode
|
enableColorImpairedMode,
|
||||||
|
fullColorEvents
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
firstDayOfWeek,
|
firstDayOfWeek,
|
||||||
calendarWeekColumnHeader,
|
calendarWeekColumnHeader,
|
||||||
timeFormat,
|
timeFormat,
|
||||||
enableColorImpairedMode
|
enableColorImpairedMode,
|
||||||
|
fullColorEvents
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +71,7 @@ class CalendarOptionsModalContent extends Component {
|
|||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
dispatchSetCalendarOption({ [name]: value });
|
dispatchSetCalendarOption({ [name]: value });
|
||||||
}
|
};
|
||||||
|
|
||||||
onGlobalInputChange = ({ name, value }) => {
|
onGlobalInputChange = ({ name, value }) => {
|
||||||
const {
|
const {
|
||||||
@@ -81,11 +83,11 @@ class CalendarOptionsModalContent extends Component {
|
|||||||
this.setState(setting, () => {
|
this.setState(setting, () => {
|
||||||
dispatchSaveUISettings(setting);
|
dispatchSaveUISettings(setting);
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
onLinkFocus = (event) => {
|
onLinkFocus = (event) => {
|
||||||
event.target.select();
|
event.target.select();
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
@@ -94,6 +96,7 @@ class CalendarOptionsModalContent extends Component {
|
|||||||
const {
|
const {
|
||||||
showMovieInformation,
|
showMovieInformation,
|
||||||
showCutoffUnmetIcon,
|
showCutoffUnmetIcon,
|
||||||
|
fullColorEvents,
|
||||||
onModalClose
|
onModalClose
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
@@ -136,6 +139,18 @@ class CalendarOptionsModalContent extends Component {
|
|||||||
onChange={this.onOptionInputChange}
|
onChange={this.onOptionInputChange}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>{translate('FullColorEvents')}</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.CHECK}
|
||||||
|
name="fullColorEvents"
|
||||||
|
value={fullColorEvents}
|
||||||
|
helpText={translate('FullColorEventsHelpText')}
|
||||||
|
onChange={this.onOptionInputChange}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
</Form>
|
</Form>
|
||||||
</FieldSet>
|
</FieldSet>
|
||||||
|
|
||||||
@@ -162,7 +177,7 @@ class CalendarOptionsModalContent extends Component {
|
|||||||
values={weekColumnOptions}
|
values={weekColumnOptions}
|
||||||
value={calendarWeekColumnHeader}
|
value={calendarWeekColumnHeader}
|
||||||
onChange={this.onGlobalInputChange}
|
onChange={this.onGlobalInputChange}
|
||||||
helpText={translate('HelpText')}
|
helpText={translate('SettingsWeekColumnHeaderHelpText')}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
@@ -176,7 +191,9 @@ class CalendarOptionsModalContent extends Component {
|
|||||||
value={timeFormat}
|
value={timeFormat}
|
||||||
onChange={this.onGlobalInputChange}
|
onChange={this.onGlobalInputChange}
|
||||||
/>
|
/>
|
||||||
</FormGroup><FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup>
|
||||||
<FormLabel>{translate('EnableColorImpairedMode')}</FormLabel>
|
<FormLabel>{translate('EnableColorImpairedMode')}</FormLabel>
|
||||||
|
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
@@ -187,7 +204,6 @@ class CalendarOptionsModalContent extends Component {
|
|||||||
onChange={this.onGlobalInputChange}
|
onChange={this.onGlobalInputChange}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
</Form>
|
</Form>
|
||||||
</FieldSet>
|
</FieldSet>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
@@ -209,6 +225,7 @@ CalendarOptionsModalContent.propTypes = {
|
|||||||
calendarWeekColumnHeader: PropTypes.string.isRequired,
|
calendarWeekColumnHeader: PropTypes.string.isRequired,
|
||||||
timeFormat: PropTypes.string.isRequired,
|
timeFormat: PropTypes.string.isRequired,
|
||||||
enableColorImpairedMode: PropTypes.bool.isRequired,
|
enableColorImpairedMode: PropTypes.bool.isRequired,
|
||||||
|
fullColorEvents: PropTypes.bool.isRequired,
|
||||||
dispatchSetCalendarOption: PropTypes.func.isRequired,
|
dispatchSetCalendarOption: PropTypes.func.isRequired,
|
||||||
dispatchSaveUISettings: PropTypes.func.isRequired,
|
dispatchSaveUISettings: PropTypes.func.isRequired,
|
||||||
onModalClose: PropTypes.func.isRequired
|
onModalClose: PropTypes.func.isRequired
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ function getUrls(state) {
|
|||||||
tags
|
tags
|
||||||
} = state;
|
} = state;
|
||||||
|
|
||||||
let icalUrl = `${window.location.host}${window.Radarr.urlBase}/feed/v3/calendar/Radarr.ics?`;
|
let icalUrl = `${window.location.host}${window.Radarr.urlBase}/feed/v4/calendar/Radarr.ics?`;
|
||||||
|
|
||||||
if (unmonitored) {
|
if (unmonitored) {
|
||||||
icalUrl += 'unmonitored=true&';
|
icalUrl += 'unmonitored=true&';
|
||||||
@@ -84,11 +84,11 @@ class CalendarLinkModalContent extends Component {
|
|||||||
[name]: value,
|
[name]: value,
|
||||||
...urls
|
...urls
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
onLinkFocus = (event) => {
|
onLinkFocus = (event) => {
|
||||||
event.target.select();
|
event.target.select();
|
||||||
}
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import Modal from 'Components/Modal/Modal';
|
||||||
|
import AddNewCollectionMovieModalContentConnector from './AddNewCollectionMovieModalContentConnector';
|
||||||
|
|
||||||
|
function AddNewCollectionMovieModal(props) {
|
||||||
|
const {
|
||||||
|
isOpen,
|
||||||
|
onModalClose,
|
||||||
|
...otherProps
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen={isOpen}
|
||||||
|
onModalClose={onModalClose}
|
||||||
|
>
|
||||||
|
<AddNewCollectionMovieModalContentConnector
|
||||||
|
{...otherProps}
|
||||||
|
onModalClose={onModalClose}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
AddNewCollectionMovieModal.propTypes = {
|
||||||
|
isOpen: PropTypes.bool.isRequired,
|
||||||
|
onModalClose: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddNewCollectionMovieModal;
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.year {
|
||||||
|
margin-left: 5px;
|
||||||
|
color: var(--disabledColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
.poster {
|
||||||
|
flex: 0 0 170px;
|
||||||
|
margin-right: 20px;
|
||||||
|
height: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overview {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.labelIcon {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchForMissingMovieLabelContainer {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchForMissingMovieLabel {
|
||||||
|
margin-right: 8px;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchForMissingMovieContainer {
|
||||||
|
composes: container from '~Components/Form/CheckInput.css';
|
||||||
|
|
||||||
|
flex: 0 1 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchForMissingMovieInput {
|
||||||
|
composes: input from '~Components/Form/CheckInput.css';
|
||||||
|
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modalFooter {
|
||||||
|
composes: modalFooter from '~Components/Modal/ModalFooter.css';
|
||||||
|
}
|
||||||
|
|
||||||
|
.addButton {
|
||||||
|
@add-mixin truncate;
|
||||||
|
composes: button from '~Components/Link/SpinnerButton.css';
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: $breakpointSmall) {
|
||||||
|
.modalFooter {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.addButton {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,204 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import CheckInput from 'Components/Form/CheckInput';
|
||||||
|
import Form from 'Components/Form/Form';
|
||||||
|
import FormGroup from 'Components/Form/FormGroup';
|
||||||
|
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||||
|
import FormLabel from 'Components/Form/FormLabel';
|
||||||
|
import SpinnerButton from 'Components/Link/SpinnerButton';
|
||||||
|
import ModalBody from 'Components/Modal/ModalBody';
|
||||||
|
import ModalContent from 'Components/Modal/ModalContent';
|
||||||
|
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||||
|
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||||
|
import { inputTypes, kinds } from 'Helpers/Props';
|
||||||
|
import MoviePoster from 'Movie/MoviePoster';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
import styles from './AddNewCollectionMovieModalContent.css';
|
||||||
|
|
||||||
|
class AddNewCollectionMovieModalContent extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Listeners
|
||||||
|
|
||||||
|
onQualityProfileIdChange = ({ value }) => {
|
||||||
|
this.props.onInputChange({ name: 'qualityProfileId', value: parseInt(value) });
|
||||||
|
};
|
||||||
|
|
||||||
|
onAddMoviePress = () => {
|
||||||
|
this.props.onAddMoviePress();
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
title,
|
||||||
|
year,
|
||||||
|
overview,
|
||||||
|
images,
|
||||||
|
isAdding,
|
||||||
|
folder,
|
||||||
|
tags,
|
||||||
|
isSmallScreen,
|
||||||
|
isWindows,
|
||||||
|
onModalClose,
|
||||||
|
onInputChange,
|
||||||
|
rootFolderPath,
|
||||||
|
monitor,
|
||||||
|
qualityProfileIds,
|
||||||
|
minimumAvailability,
|
||||||
|
searchForMovie
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalContent onModalClose={onModalClose}>
|
||||||
|
<ModalHeader>
|
||||||
|
{title}
|
||||||
|
|
||||||
|
{
|
||||||
|
!title.contains(year) && !!year &&
|
||||||
|
<span className={styles.year}>({year})</span>
|
||||||
|
}
|
||||||
|
</ModalHeader>
|
||||||
|
|
||||||
|
<ModalBody>
|
||||||
|
<div className={styles.container}>
|
||||||
|
{
|
||||||
|
!isSmallScreen &&
|
||||||
|
<div className={styles.poster}>
|
||||||
|
<MoviePoster
|
||||||
|
className={styles.poster}
|
||||||
|
images={images}
|
||||||
|
size={250}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div className={styles.info}>
|
||||||
|
<div className={styles.overview}>
|
||||||
|
{overview}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Form>
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>{translate('RootFolder')}</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.ROOT_FOLDER_SELECT}
|
||||||
|
name="rootFolderPath"
|
||||||
|
valueOptions={{
|
||||||
|
movieFolder: folder,
|
||||||
|
isWindows
|
||||||
|
}}
|
||||||
|
selectedValueOptions={{
|
||||||
|
movieFolder: folder,
|
||||||
|
isWindows
|
||||||
|
}}
|
||||||
|
helpText={translate('SubfolderWillBeCreatedAutomaticallyInterp', [folder])}
|
||||||
|
onChange={onInputChange}
|
||||||
|
{...rootFolderPath}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>
|
||||||
|
{translate('Monitor')}
|
||||||
|
</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.MOVIE_MONITORED_SELECT}
|
||||||
|
name="monitor"
|
||||||
|
onChange={onInputChange}
|
||||||
|
{...monitor}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>{translate('MinimumAvailability')}</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.AVAILABILITY_SELECT}
|
||||||
|
name="minimumAvailability"
|
||||||
|
onChange={onInputChange}
|
||||||
|
{...minimumAvailability}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>{translate('QualityProfiles')}</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.QUALITY_PROFILE_SELECT}
|
||||||
|
name="qualityProfileIds"
|
||||||
|
onChange={this.onQualityProfileIdChange}
|
||||||
|
{...qualityProfileIds}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>{translate('Tags')}</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.TAG}
|
||||||
|
name="tags"
|
||||||
|
onChange={onInputChange}
|
||||||
|
{...tags}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ModalBody>
|
||||||
|
|
||||||
|
<ModalFooter className={styles.modalFooter}>
|
||||||
|
<label className={styles.searchForMissingMovieLabelContainer}>
|
||||||
|
<span className={styles.searchForMissingMovieLabel}>
|
||||||
|
{translate('StartSearchForMissingMovie')}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<CheckInput
|
||||||
|
containerClassName={styles.searchForMissingMovieContainer}
|
||||||
|
className={styles.searchForMissingMovieInput}
|
||||||
|
name="searchForMovie"
|
||||||
|
onChange={onInputChange}
|
||||||
|
{...searchForMovie}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<SpinnerButton
|
||||||
|
className={styles.addButton}
|
||||||
|
kind={kinds.SUCCESS}
|
||||||
|
isSpinning={isAdding}
|
||||||
|
onPress={this.onAddMoviePress}
|
||||||
|
>
|
||||||
|
{translate('AddMovie')}
|
||||||
|
</SpinnerButton>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AddNewCollectionMovieModalContent.propTypes = {
|
||||||
|
title: PropTypes.string.isRequired,
|
||||||
|
year: PropTypes.number.isRequired,
|
||||||
|
overview: PropTypes.string,
|
||||||
|
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
isAdding: PropTypes.bool.isRequired,
|
||||||
|
addError: PropTypes.object,
|
||||||
|
rootFolderPath: PropTypes.object,
|
||||||
|
monitor: PropTypes.object.isRequired,
|
||||||
|
qualityProfileIds: PropTypes.object,
|
||||||
|
minimumAvailability: PropTypes.object.isRequired,
|
||||||
|
searchForMovie: PropTypes.object.isRequired,
|
||||||
|
folder: PropTypes.string.isRequired,
|
||||||
|
tags: PropTypes.object.isRequired,
|
||||||
|
isSmallScreen: PropTypes.bool.isRequired,
|
||||||
|
isWindows: PropTypes.bool.isRequired,
|
||||||
|
onModalClose: PropTypes.func.isRequired,
|
||||||
|
onInputChange: PropTypes.func.isRequired,
|
||||||
|
onAddMoviePress: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddNewCollectionMovieModalContent;
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import { addMovie, setMovieCollectionValue } from 'Store/Actions/movieCollectionActions';
|
||||||
|
import createCollectionSelector from 'Store/Selectors/createCollectionSelector';
|
||||||
|
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||||
|
import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector';
|
||||||
|
import selectSettings from 'Store/Selectors/selectSettings';
|
||||||
|
import AddNewMovieModalContent from './AddNewCollectionMovieModalContent';
|
||||||
|
|
||||||
|
function createMapStateToProps() {
|
||||||
|
return createSelector(
|
||||||
|
(state) => state.movieCollections,
|
||||||
|
createCollectionSelector(),
|
||||||
|
createDimensionsSelector(),
|
||||||
|
createSystemStatusSelector(),
|
||||||
|
(discoverMovieState, collection, dimensions, systemStatus) => {
|
||||||
|
const {
|
||||||
|
isAdding,
|
||||||
|
addError,
|
||||||
|
pendingChanges
|
||||||
|
} = discoverMovieState;
|
||||||
|
|
||||||
|
const collectionDefaults = {
|
||||||
|
rootFolderPath: collection.rootFolderPath,
|
||||||
|
monitor: 'movieOnly',
|
||||||
|
qualityProfileIds: collection.qualityProfileIds,
|
||||||
|
minimumAvailability: collection.minimumAvailability,
|
||||||
|
searchForMovie: collection.searchOnAdd,
|
||||||
|
tags: []
|
||||||
|
};
|
||||||
|
|
||||||
|
const {
|
||||||
|
settings,
|
||||||
|
validationErrors,
|
||||||
|
validationWarnings
|
||||||
|
} = selectSettings(collectionDefaults, pendingChanges, addError);
|
||||||
|
|
||||||
|
return {
|
||||||
|
isAdding,
|
||||||
|
addError,
|
||||||
|
isSmallScreen: dimensions.isSmallScreen,
|
||||||
|
validationErrors,
|
||||||
|
validationWarnings,
|
||||||
|
isWindows: systemStatus.isWindows,
|
||||||
|
...settings
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
addMovie,
|
||||||
|
setMovieCollectionValue
|
||||||
|
};
|
||||||
|
|
||||||
|
class AddNewCollectionMovieModalContentConnector extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Listeners
|
||||||
|
|
||||||
|
onInputChange = ({ name, value }) => {
|
||||||
|
this.props.setMovieCollectionValue({ name, value });
|
||||||
|
};
|
||||||
|
|
||||||
|
onAddMoviePress = () => {
|
||||||
|
const {
|
||||||
|
tmdbId,
|
||||||
|
title,
|
||||||
|
rootFolderPath,
|
||||||
|
monitor,
|
||||||
|
qualityProfileIds,
|
||||||
|
minimumAvailability,
|
||||||
|
searchForMovie,
|
||||||
|
tags
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
this.props.addMovie({
|
||||||
|
tmdbId,
|
||||||
|
title,
|
||||||
|
rootFolderPath: rootFolderPath.value,
|
||||||
|
monitor: monitor.value,
|
||||||
|
qualityProfileIds: qualityProfileIds.value,
|
||||||
|
minimumAvailability: minimumAvailability.value,
|
||||||
|
searchForMovie: searchForMovie.value,
|
||||||
|
tags: tags.value
|
||||||
|
});
|
||||||
|
|
||||||
|
this.props.onModalClose(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<AddNewMovieModalContent
|
||||||
|
{...this.props}
|
||||||
|
onInputChange={this.onInputChange}
|
||||||
|
onAddMoviePress={this.onAddMoviePress}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AddNewCollectionMovieModalContentConnector.propTypes = {
|
||||||
|
tmdbId: PropTypes.number.isRequired,
|
||||||
|
title: PropTypes.string.isRequired,
|
||||||
|
rootFolderPath: PropTypes.object,
|
||||||
|
monitor: PropTypes.object.isRequired,
|
||||||
|
qualityProfileIds: PropTypes.object,
|
||||||
|
minimumAvailability: PropTypes.object.isRequired,
|
||||||
|
searchForMovie: PropTypes.object.isRequired,
|
||||||
|
tags: PropTypes.object.isRequired,
|
||||||
|
onModalClose: PropTypes.func.isRequired,
|
||||||
|
addMovie: PropTypes.func.isRequired,
|
||||||
|
setMovieCollectionValue: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(createMapStateToProps, mapDispatchToProps)(AddNewCollectionMovieModalContentConnector);
|
||||||
@@ -0,0 +1,403 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
|
import PageContent from 'Components/Page/PageContent';
|
||||||
|
import PageContentBody from 'Components/Page/PageContentBody';
|
||||||
|
import PageJumpBar from 'Components/Page/PageJumpBar';
|
||||||
|
import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
|
||||||
|
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||||
|
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
||||||
|
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
|
||||||
|
import { align, icons, sortDirections } from 'Helpers/Props';
|
||||||
|
import styles from 'Movie/Index/MovieIndex.css';
|
||||||
|
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
import getSelectedIds from 'Utilities/Table/getSelectedIds';
|
||||||
|
import selectAll from 'Utilities/Table/selectAll';
|
||||||
|
import toggleSelected from 'Utilities/Table/toggleSelected';
|
||||||
|
import CollectionFooter from './CollectionFooter';
|
||||||
|
import CollectionFilterMenu from './Menus/CollectionFilterMenu';
|
||||||
|
import CollectionSortMenu from './Menus/CollectionSortMenu';
|
||||||
|
import NoCollection from './NoCollection';
|
||||||
|
import CollectionOverviewsConnector from './Overview/CollectionOverviewsConnector';
|
||||||
|
import CollectionOverviewOptionsModal from './Overview/Options/CollectionOverviewOptionsModal';
|
||||||
|
|
||||||
|
function getViewComponent(view) {
|
||||||
|
return CollectionOverviewsConnector;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Collection extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Lifecycle
|
||||||
|
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
scroller: null,
|
||||||
|
jumpBarItems: { order: [] },
|
||||||
|
jumpToCharacter: null,
|
||||||
|
isPosterOptionsModalOpen: false,
|
||||||
|
isOverviewOptionsModalOpen: false,
|
||||||
|
isConfirmSearchModalOpen: false,
|
||||||
|
searchType: null,
|
||||||
|
allSelected: false,
|
||||||
|
allUnselected: false,
|
||||||
|
lastToggled: null,
|
||||||
|
selectedState: {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.setJumpBarItems();
|
||||||
|
this.setSelectedState();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
const {
|
||||||
|
items,
|
||||||
|
sortKey,
|
||||||
|
sortDirection
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
if (sortKey !== prevProps.sortKey ||
|
||||||
|
sortDirection !== prevProps.sortDirection ||
|
||||||
|
hasDifferentItemsOrOrder(prevProps.items, items)
|
||||||
|
) {
|
||||||
|
this.setJumpBarItems();
|
||||||
|
this.setSelectedState();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state.jumpToCharacter != null) {
|
||||||
|
this.setState({ jumpToCharacter: null });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Control
|
||||||
|
|
||||||
|
setScrollerRef = (ref) => {
|
||||||
|
this.setState({ scroller: ref });
|
||||||
|
};
|
||||||
|
|
||||||
|
getSelectedIds = () => {
|
||||||
|
if (this.state.allUnselected) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return getSelectedIds(this.state.selectedState);
|
||||||
|
};
|
||||||
|
|
||||||
|
setSelectedState() {
|
||||||
|
const {
|
||||||
|
items
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
selectedState
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
|
const newSelectedState = {};
|
||||||
|
|
||||||
|
items.forEach((collection) => {
|
||||||
|
const isItemSelected = selectedState[collection.id];
|
||||||
|
|
||||||
|
if (isItemSelected) {
|
||||||
|
newSelectedState[collection.id] = isItemSelected;
|
||||||
|
} else {
|
||||||
|
newSelectedState[collection.id] = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedCount = getSelectedIds(newSelectedState).length;
|
||||||
|
const newStateCount = Object.keys(newSelectedState).length;
|
||||||
|
let isAllSelected = false;
|
||||||
|
let isAllUnselected = false;
|
||||||
|
|
||||||
|
if (selectedCount === 0) {
|
||||||
|
isAllUnselected = true;
|
||||||
|
} else if (selectedCount === newStateCount) {
|
||||||
|
isAllSelected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ selectedState: newSelectedState, allSelected: isAllSelected, allUnselected: isAllUnselected });
|
||||||
|
}
|
||||||
|
|
||||||
|
setJumpBarItems() {
|
||||||
|
const {
|
||||||
|
items,
|
||||||
|
sortKey,
|
||||||
|
sortDirection
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
// Reset if not sorting by sortTitle
|
||||||
|
if (sortKey !== 'sortTitle') {
|
||||||
|
this.setState({ jumpBarItems: { order: [] } });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const characters = _.reduce(items, (acc, item) => {
|
||||||
|
let char = item.sortTitle.charAt(0);
|
||||||
|
|
||||||
|
if (!isNaN(char)) {
|
||||||
|
char = '#';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (char in acc) {
|
||||||
|
acc[char] = acc[char] + 1;
|
||||||
|
} else {
|
||||||
|
acc[char] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const order = Object.keys(characters).sort();
|
||||||
|
|
||||||
|
// Reverse if sorting descending
|
||||||
|
if (sortDirection === sortDirections.DESCENDING) {
|
||||||
|
order.reverse();
|
||||||
|
}
|
||||||
|
|
||||||
|
const jumpBarItems = {
|
||||||
|
characters,
|
||||||
|
order
|
||||||
|
};
|
||||||
|
|
||||||
|
this.setState({ jumpBarItems });
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Listeners
|
||||||
|
|
||||||
|
onOverviewOptionsPress = () => {
|
||||||
|
this.setState({ isOverviewOptionsModalOpen: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
onOverviewOptionsModalClose = () => {
|
||||||
|
this.setState({ isOverviewOptionsModalOpen: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
onJumpBarItemPress = (jumpToCharacter) => {
|
||||||
|
this.setState({ jumpToCharacter });
|
||||||
|
};
|
||||||
|
|
||||||
|
onSelectAllChange = ({ value }) => {
|
||||||
|
this.setState(selectAll(this.state.selectedState, value));
|
||||||
|
};
|
||||||
|
|
||||||
|
onSelectAllPress = () => {
|
||||||
|
this.onSelectAllChange({ value: !this.state.allSelected });
|
||||||
|
};
|
||||||
|
|
||||||
|
onRefreshMovieCollectionsPress = () => {
|
||||||
|
this.props.onRefreshMovieCollectionsPress();
|
||||||
|
};
|
||||||
|
|
||||||
|
onSelectedChange = ({ id, value, shiftKey = false }) => {
|
||||||
|
this.setState((state) => {
|
||||||
|
return toggleSelected(state, this.props.items, id, value, shiftKey, 'id');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onUpdateSelectedPress = (changes) => {
|
||||||
|
this.props.onUpdateSelectedPress({
|
||||||
|
collectionIds: this.getSelectedIds(),
|
||||||
|
...changes
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
isFetching,
|
||||||
|
isPopulated,
|
||||||
|
error,
|
||||||
|
totalItems,
|
||||||
|
items,
|
||||||
|
selectedFilterKey,
|
||||||
|
filters,
|
||||||
|
customFilters,
|
||||||
|
sortKey,
|
||||||
|
sortDirection,
|
||||||
|
view,
|
||||||
|
onSortSelect,
|
||||||
|
onFilterSelect,
|
||||||
|
onScroll,
|
||||||
|
isRefreshingCollections,
|
||||||
|
isSaving,
|
||||||
|
isAdding,
|
||||||
|
...otherProps
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
scroller,
|
||||||
|
jumpBarItems,
|
||||||
|
jumpToCharacter,
|
||||||
|
isOverviewOptionsModalOpen,
|
||||||
|
selectedState,
|
||||||
|
allSelected,
|
||||||
|
allUnselected
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
|
const selectedMovieIds = this.getSelectedIds();
|
||||||
|
|
||||||
|
const ViewComponent = getViewComponent(view);
|
||||||
|
const isLoaded = !!(!error && isPopulated && items.length && scroller);
|
||||||
|
const hasNoCollection = !totalItems;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageContent>
|
||||||
|
<PageToolbar>
|
||||||
|
<PageToolbarSection>
|
||||||
|
<PageToolbarButton
|
||||||
|
label={translate('RefreshCollections')}
|
||||||
|
iconName={icons.REFRESH}
|
||||||
|
isSpinning={isRefreshingCollections}
|
||||||
|
isDisabled={hasNoCollection}
|
||||||
|
onPress={this.onRefreshMovieCollectionsPress}
|
||||||
|
/>
|
||||||
|
<PageToolbarButton
|
||||||
|
label={allSelected ? translate('UnselectAll') : translate('SelectAll')}
|
||||||
|
iconName={icons.CHECK_SQUARE}
|
||||||
|
isDisabled={hasNoCollection}
|
||||||
|
onPress={this.onSelectAllPress}
|
||||||
|
/>
|
||||||
|
</PageToolbarSection>
|
||||||
|
|
||||||
|
<PageToolbarSection
|
||||||
|
alignContent={align.RIGHT}
|
||||||
|
collapseButtons={false}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
view === 'overview' ?
|
||||||
|
<PageToolbarButton
|
||||||
|
label={translate('Options')}
|
||||||
|
iconName={icons.OVERVIEW}
|
||||||
|
onPress={this.onOverviewOptionsPress}
|
||||||
|
/> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
(view === 'posters' || view === 'overview') &&
|
||||||
|
<PageToolbarSeparator />
|
||||||
|
}
|
||||||
|
|
||||||
|
<CollectionSortMenu
|
||||||
|
sortKey={sortKey}
|
||||||
|
sortDirection={sortDirection}
|
||||||
|
isDisabled={hasNoCollection}
|
||||||
|
onSortSelect={onSortSelect}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<CollectionFilterMenu
|
||||||
|
selectedFilterKey={selectedFilterKey}
|
||||||
|
filters={filters}
|
||||||
|
customFilters={customFilters}
|
||||||
|
isDisabled={hasNoCollection}
|
||||||
|
onFilterSelect={onFilterSelect}
|
||||||
|
/>
|
||||||
|
</PageToolbarSection>
|
||||||
|
</PageToolbar>
|
||||||
|
|
||||||
|
<div className={styles.pageContentBodyWrapper}>
|
||||||
|
<PageContentBody
|
||||||
|
registerScroller={this.setScrollerRef}
|
||||||
|
className={styles.contentBody}
|
||||||
|
innerClassName={styles[`${view}InnerContentBody`]}
|
||||||
|
onScroll={onScroll}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
isFetching && !isPopulated &&
|
||||||
|
<LoadingIndicator />
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
!isFetching && !!error &&
|
||||||
|
<div>
|
||||||
|
{translate('UnableToLoadCollections')}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
isLoaded &&
|
||||||
|
<div className={styles.contentBodyContainer}>
|
||||||
|
<ViewComponent
|
||||||
|
scroller={scroller}
|
||||||
|
items={items}
|
||||||
|
filters={filters}
|
||||||
|
sortKey={sortKey}
|
||||||
|
sortDirection={sortDirection}
|
||||||
|
jumpToCharacter={jumpToCharacter}
|
||||||
|
allSelected={allSelected}
|
||||||
|
allUnselected={allUnselected}
|
||||||
|
onSelectedChange={this.onSelectedChange}
|
||||||
|
onSelectAllChange={this.onSelectAllChange}
|
||||||
|
selectedState={selectedState}
|
||||||
|
{...otherProps}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
!error && isPopulated && !items.length &&
|
||||||
|
<NoCollection totalItems={totalItems} />
|
||||||
|
}
|
||||||
|
</PageContentBody>
|
||||||
|
|
||||||
|
{
|
||||||
|
isLoaded && !!jumpBarItems.order.length &&
|
||||||
|
<PageJumpBar
|
||||||
|
items={jumpBarItems}
|
||||||
|
onItemPress={this.onJumpBarItemPress}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{
|
||||||
|
isLoaded &&
|
||||||
|
<CollectionFooter
|
||||||
|
selectedIds={selectedMovieIds}
|
||||||
|
isSaving={isSaving}
|
||||||
|
isAdding={isAdding}
|
||||||
|
onUpdateSelectedPress={this.onUpdateSelectedPress}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
|
<CollectionOverviewOptionsModal
|
||||||
|
isOpen={isOverviewOptionsModalOpen}
|
||||||
|
onModalClose={this.onOverviewOptionsModalClose}
|
||||||
|
/>
|
||||||
|
</PageContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Collection.propTypes = {
|
||||||
|
isFetching: PropTypes.bool.isRequired,
|
||||||
|
isPopulated: PropTypes.bool.isRequired,
|
||||||
|
isSaving: PropTypes.bool.isRequired,
|
||||||
|
isAdding: PropTypes.bool.isRequired,
|
||||||
|
error: PropTypes.object,
|
||||||
|
totalItems: PropTypes.number.isRequired,
|
||||||
|
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
||||||
|
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
sortKey: PropTypes.string,
|
||||||
|
sortDirection: PropTypes.oneOf(sortDirections.all),
|
||||||
|
view: PropTypes.string.isRequired,
|
||||||
|
isRefreshingCollections: PropTypes.bool.isRequired,
|
||||||
|
isSmallScreen: PropTypes.bool.isRequired,
|
||||||
|
onSortSelect: PropTypes.func.isRequired,
|
||||||
|
onFilterSelect: PropTypes.func.isRequired,
|
||||||
|
onScroll: PropTypes.func.isRequired,
|
||||||
|
onUpdateSelectedPress: PropTypes.func.isRequired,
|
||||||
|
onRefreshMovieCollectionsPress: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Collection;
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import * as commandNames from 'Commands/commandNames';
|
||||||
|
import withScrollPosition from 'Components/withScrollPosition';
|
||||||
|
import { executeCommand } from 'Store/Actions/commandActions';
|
||||||
|
import { saveMovieCollections, setMovieCollectionsFilter, setMovieCollectionsSort } from 'Store/Actions/movieCollectionActions';
|
||||||
|
import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
|
||||||
|
import scrollPositions from 'Store/scrollPositions';
|
||||||
|
import createCollectionClientSideCollectionItemsSelector from 'Store/Selectors/createCollectionClientSideCollectionItemsSelector';
|
||||||
|
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
|
||||||
|
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||||
|
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
|
||||||
|
import Collection from './Collection';
|
||||||
|
|
||||||
|
function createMapStateToProps() {
|
||||||
|
return createSelector(
|
||||||
|
createCollectionClientSideCollectionItemsSelector('movieCollections'),
|
||||||
|
createCommandExecutingSelector(commandNames.REFRESH_COLLECTIONS),
|
||||||
|
createDimensionsSelector(),
|
||||||
|
(
|
||||||
|
collections,
|
||||||
|
isRefreshingCollections,
|
||||||
|
dimensionsState
|
||||||
|
) => {
|
||||||
|
return {
|
||||||
|
...collections,
|
||||||
|
isRefreshingCollections,
|
||||||
|
isSmallScreen: dimensionsState.isSmallScreen
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMapDispatchToProps(dispatch, props) {
|
||||||
|
return {
|
||||||
|
dispatchFetchRootFolders() {
|
||||||
|
dispatch(fetchRootFolders());
|
||||||
|
},
|
||||||
|
onUpdateSelectedPress(payload) {
|
||||||
|
dispatch(saveMovieCollections(payload));
|
||||||
|
},
|
||||||
|
onSortSelect(sortKey) {
|
||||||
|
dispatch(setMovieCollectionsSort({ sortKey }));
|
||||||
|
},
|
||||||
|
onFilterSelect(selectedFilterKey) {
|
||||||
|
dispatch(setMovieCollectionsFilter({ selectedFilterKey }));
|
||||||
|
},
|
||||||
|
onRefreshMovieCollectionsPress() {
|
||||||
|
dispatch(executeCommand({
|
||||||
|
name: commandNames.REFRESH_COLLECTIONS
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class CollectionConnector extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Lifecycle
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
registerPagePopulator(this.repopulate);
|
||||||
|
this.props.dispatchFetchRootFolders();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
unregisterPagePopulator(this.repopulate);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Listeners
|
||||||
|
|
||||||
|
onScroll = ({ scrollTop }) => {
|
||||||
|
scrollPositions.movieCollections = scrollTop;
|
||||||
|
};
|
||||||
|
|
||||||
|
onUpdateSelectedPress = (payload) => {
|
||||||
|
this.props.onUpdateSelectedPress(payload);
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Collection
|
||||||
|
{...this.props}
|
||||||
|
onViewSelect={this.onViewSelect}
|
||||||
|
onScroll={this.onScroll}
|
||||||
|
onUpdateSelectedPress={this.onUpdateSelectedPress}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CollectionConnector.propTypes = {
|
||||||
|
isSmallScreen: PropTypes.bool.isRequired,
|
||||||
|
view: PropTypes.string.isRequired,
|
||||||
|
onUpdateSelectedPress: PropTypes.func.isRequired,
|
||||||
|
dispatchFetchRootFolders: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withScrollPosition(
|
||||||
|
connect(createMapStateToProps, createMapDispatchToProps)(CollectionConnector),
|
||||||
|
'movieCollections'
|
||||||
|
);
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import FilterModal from 'Components/Filter/FilterModal';
|
||||||
|
import { setMovieCollectionsFilter } from 'Store/Actions/movieCollectionActions';
|
||||||
|
|
||||||
|
function createMapStateToProps() {
|
||||||
|
return createSelector(
|
||||||
|
(state) => state.movieCollections.items,
|
||||||
|
(state) => state.movieCollections.filterBuilderProps,
|
||||||
|
(sectionItems, filterBuilderProps) => {
|
||||||
|
return {
|
||||||
|
sectionItems,
|
||||||
|
filterBuilderProps,
|
||||||
|
customFilterType: 'movieCollections'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
dispatchSetFilter: setMovieCollectionsFilter
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(createMapStateToProps, mapDispatchToProps)(FilterModal);
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
.inputContainer {
|
||||||
|
margin-right: 20px;
|
||||||
|
min-width: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonContainer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonContainerContent {
|
||||||
|
flex-grow: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.addSelectedButton {
|
||||||
|
composes: button from '~Components/Link/SpinnerButton.css';
|
||||||
|
|
||||||
|
margin-right: 10px;
|
||||||
|
height: 35px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.excludeSelectedButton {
|
||||||
|
composes: button from '~Components/Link/SpinnerButton.css';
|
||||||
|
|
||||||
|
margin-left: 25px;
|
||||||
|
height: 35px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: $breakpointSmall) {
|
||||||
|
.inputContainer {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonContainer {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonContainerContent {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selectedMovieLabel {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,236 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import AvailabilitySelectInput from 'Components/Form/AvailabilitySelectInput';
|
||||||
|
import QualityProfileSelectInputConnector from 'Components/Form/QualityProfileSelectInputConnector';
|
||||||
|
import RootFolderSelectInputConnector from 'Components/Form/RootFolderSelectInputConnector';
|
||||||
|
import SelectInput from 'Components/Form/SelectInput';
|
||||||
|
import SpinnerButton from 'Components/Link/SpinnerButton';
|
||||||
|
import PageContentFooter from 'Components/Page/PageContentFooter';
|
||||||
|
import { kinds } from 'Helpers/Props';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
import CollectionFooterLabel from './CollectionFooterLabel';
|
||||||
|
import styles from './CollectionFooter.css';
|
||||||
|
|
||||||
|
const NO_CHANGE = 'noChange';
|
||||||
|
|
||||||
|
class CollectionFooter extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Lifecycle
|
||||||
|
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
monitor: NO_CHANGE,
|
||||||
|
monitored: NO_CHANGE,
|
||||||
|
qualityProfileId: NO_CHANGE,
|
||||||
|
minimumAvailability: NO_CHANGE,
|
||||||
|
rootFolderPath: NO_CHANGE,
|
||||||
|
destinationRootFolder: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
const {
|
||||||
|
isSaving,
|
||||||
|
saveError
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const newState = {};
|
||||||
|
if (prevProps.isSaving && !isSaving && !saveError) {
|
||||||
|
this.setState({
|
||||||
|
monitored: NO_CHANGE,
|
||||||
|
monitor: NO_CHANGE,
|
||||||
|
qualityProfileId: NO_CHANGE,
|
||||||
|
rootFolderPath: NO_CHANGE,
|
||||||
|
minimumAvailability: NO_CHANGE
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_.isEmpty(newState)) {
|
||||||
|
this.setState(newState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Listeners
|
||||||
|
|
||||||
|
onInputChange = ({ name, value }) => {
|
||||||
|
this.setState({ [name]: value });
|
||||||
|
};
|
||||||
|
|
||||||
|
onUpdateSelectedPress = () => {
|
||||||
|
const {
|
||||||
|
monitor,
|
||||||
|
monitored,
|
||||||
|
qualityProfileId,
|
||||||
|
minimumAvailability,
|
||||||
|
rootFolderPath
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
|
const changes = {};
|
||||||
|
|
||||||
|
if (monitored !== NO_CHANGE) {
|
||||||
|
changes.monitored = monitored === 'monitored';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (monitor !== NO_CHANGE) {
|
||||||
|
changes.monitor = monitor;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (qualityProfileId !== NO_CHANGE) {
|
||||||
|
changes.qualityProfileId = qualityProfileId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minimumAvailability !== NO_CHANGE) {
|
||||||
|
changes.minimumAvailability = minimumAvailability;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rootFolderPath !== NO_CHANGE) {
|
||||||
|
changes.rootFolderPath = rootFolderPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.onUpdateSelectedPress(changes);
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
selectedIds,
|
||||||
|
isSaving
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
monitored,
|
||||||
|
monitor,
|
||||||
|
qualityProfileId,
|
||||||
|
minimumAvailability,
|
||||||
|
rootFolderPath
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
|
const monitoredOptions = [
|
||||||
|
{ key: NO_CHANGE, value: translate('NoChange'), disabled: true },
|
||||||
|
{ key: 'monitored', value: translate('Monitored') },
|
||||||
|
{ key: 'unmonitored', value: translate('Unmonitored') }
|
||||||
|
];
|
||||||
|
|
||||||
|
const selectedCount = selectedIds.length;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageContentFooter>
|
||||||
|
<div className={styles.inputContainer}>
|
||||||
|
<CollectionFooterLabel
|
||||||
|
label={translate('MonitorCollection')}
|
||||||
|
isSaving={isSaving}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SelectInput
|
||||||
|
name="monitored"
|
||||||
|
value={monitored}
|
||||||
|
values={monitoredOptions}
|
||||||
|
isDisabled={!selectedCount}
|
||||||
|
onChange={this.onInputChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.inputContainer}>
|
||||||
|
<CollectionFooterLabel
|
||||||
|
label={translate('MonitorMovies')}
|
||||||
|
isSaving={isSaving}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SelectInput
|
||||||
|
name="monitor"
|
||||||
|
value={monitor}
|
||||||
|
values={monitoredOptions}
|
||||||
|
isDisabled={!selectedCount}
|
||||||
|
onChange={this.onInputChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.inputContainer}>
|
||||||
|
<CollectionFooterLabel
|
||||||
|
label={translate('QualityProfile')}
|
||||||
|
isSaving={isSaving && qualityProfileId !== NO_CHANGE}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<QualityProfileSelectInputConnector
|
||||||
|
name="qualityProfileId"
|
||||||
|
value={qualityProfileId}
|
||||||
|
includeNoChange={true}
|
||||||
|
isDisabled={!selectedCount}
|
||||||
|
onChange={this.onInputChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.inputContainer}>
|
||||||
|
<CollectionFooterLabel
|
||||||
|
label={translate('MinimumAvailability')}
|
||||||
|
isSaving={isSaving && minimumAvailability !== NO_CHANGE}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<AvailabilitySelectInput
|
||||||
|
name="minimumAvailability"
|
||||||
|
value={minimumAvailability}
|
||||||
|
includeNoChange={true}
|
||||||
|
isDisabled={!selectedCount}
|
||||||
|
onChange={this.onInputChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.inputContainer}>
|
||||||
|
<CollectionFooterLabel
|
||||||
|
label={translate('RootFolder')}
|
||||||
|
isSaving={isSaving && rootFolderPath !== NO_CHANGE}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<RootFolderSelectInputConnector
|
||||||
|
name="rootFolderPath"
|
||||||
|
value={rootFolderPath}
|
||||||
|
includeNoChange={true}
|
||||||
|
isDisabled={!selectedCount}
|
||||||
|
selectedValueOptions={{ includeFreeSpace: false }}
|
||||||
|
onChange={this.onInputChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.buttonContainer}>
|
||||||
|
<div className={styles.buttonContainerContent}>
|
||||||
|
<CollectionFooterLabel
|
||||||
|
label={translate('CollectionsSelectedInterp', [selectedCount])}
|
||||||
|
isSaving={false}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className={styles.buttons}>
|
||||||
|
<div>
|
||||||
|
<SpinnerButton
|
||||||
|
className={styles.addSelectedButton}
|
||||||
|
kind={kinds.PRIMARY}
|
||||||
|
isSpinning={isSaving}
|
||||||
|
isDisabled={!selectedCount || isSaving}
|
||||||
|
onPress={this.onUpdateSelectedPress}
|
||||||
|
>
|
||||||
|
{translate('UpdateSelected')}
|
||||||
|
</SpinnerButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PageContentFooter>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CollectionFooter.propTypes = {
|
||||||
|
selectedIds: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||||
|
isAdding: PropTypes.bool.isRequired,
|
||||||
|
isSaving: PropTypes.bool.isRequired,
|
||||||
|
saveError: PropTypes.object,
|
||||||
|
onUpdateSelectedPress: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CollectionFooter;
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
.label {
|
||||||
|
margin-bottom: 3px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.savingIcon {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import SpinnerIcon from 'Components/SpinnerIcon';
|
||||||
|
import { icons } from 'Helpers/Props';
|
||||||
|
import styles from './CollectionFooterLabel.css';
|
||||||
|
|
||||||
|
function CollectionFooterLabel(props) {
|
||||||
|
const {
|
||||||
|
className,
|
||||||
|
label,
|
||||||
|
isSaving
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={className}>
|
||||||
|
{label}
|
||||||
|
|
||||||
|
{
|
||||||
|
isSaving &&
|
||||||
|
<SpinnerIcon
|
||||||
|
className={styles.savingIcon}
|
||||||
|
name={icons.SPINNER}
|
||||||
|
isSpinning={true}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
CollectionFooterLabel.propTypes = {
|
||||||
|
className: PropTypes.string.isRequired,
|
||||||
|
label: PropTypes.string.isRequired,
|
||||||
|
isSaving: PropTypes.bool.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
CollectionFooterLabel.defaultProps = {
|
||||||
|
className: styles.label
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CollectionFooterLabel;
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import createAllMoviesSelector from 'Store/Selectors/createAllMoviesSelector';
|
||||||
|
import createCollectionSelector from 'Store/Selectors/createCollectionSelector';
|
||||||
|
|
||||||
|
function createMapStateToProps() {
|
||||||
|
return createSelector(
|
||||||
|
createCollectionSelector(),
|
||||||
|
createAllMoviesSelector(),
|
||||||
|
(
|
||||||
|
collection,
|
||||||
|
allMovies
|
||||||
|
) => {
|
||||||
|
// If a movie is deleted this selector may fire before the parent
|
||||||
|
// selecors, which will result in an undefined movie, if that happens
|
||||||
|
// we want to return early here and again in the render function to avoid
|
||||||
|
// trying to show a movie that has no information available.
|
||||||
|
|
||||||
|
if (!collection) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
let allGenres = [];
|
||||||
|
let libraryMovies = 0;
|
||||||
|
|
||||||
|
collection.movies.forEach((movie) => {
|
||||||
|
allGenres = allGenres.concat(movie.genres);
|
||||||
|
|
||||||
|
if (allMovies.find((libraryMovie) => libraryMovie.tmdbId === movie.tmdbId)) {
|
||||||
|
libraryMovies++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
...collection,
|
||||||
|
genres: Array.from(new Set(allGenres)).slice(0, 3),
|
||||||
|
missingMovies: collection.movies.length - libraryMovies
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class CollectionItemConnector extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
component: ItemComponent,
|
||||||
|
...otherProps
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
if (!id) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ItemComponent
|
||||||
|
{...otherProps}
|
||||||
|
id={id}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CollectionItemConnector.propTypes = {
|
||||||
|
id: PropTypes.number,
|
||||||
|
component: PropTypes.elementType.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(createMapStateToProps)(CollectionItemConnector);
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import Modal from 'Components/Modal/Modal';
|
||||||
|
import EditCollectionModalContentConnector from './EditCollectionModalContentConnector';
|
||||||
|
|
||||||
|
function EditCollectionModal({ isOpen, onModalClose, ...otherProps }) {
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen={isOpen}
|
||||||
|
onModalClose={onModalClose}
|
||||||
|
>
|
||||||
|
<EditCollectionModalContentConnector
|
||||||
|
{...otherProps}
|
||||||
|
onModalClose={onModalClose}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
EditCollectionModal.propTypes = {
|
||||||
|
isOpen: PropTypes.bool.isRequired,
|
||||||
|
onModalClose: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditCollectionModal;
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { clearPendingChanges } from 'Store/Actions/baseActions';
|
||||||
|
import EditCollectionModal from './EditCollectionModal';
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
clearPendingChanges
|
||||||
|
};
|
||||||
|
|
||||||
|
class EditCollectionModalConnector extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Listeners
|
||||||
|
|
||||||
|
onModalClose = () => {
|
||||||
|
this.props.clearPendingChanges({ section: 'movieCollections' });
|
||||||
|
this.props.onModalClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<EditCollectionModal
|
||||||
|
{...this.props}
|
||||||
|
onModalClose={this.onModalClose}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EditCollectionModalConnector.propTypes = {
|
||||||
|
onModalClose: PropTypes.func.isRequired,
|
||||||
|
clearPendingChanges: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(undefined, mapDispatchToProps)(EditCollectionModalConnector);
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poster {
|
||||||
|
flex: 0 0 170px;
|
||||||
|
margin-right: 20px;
|
||||||
|
height: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overview {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user