mirror of
https://github.com/Sonarr/Sonarr.git
synced 2026-03-05 13:20:20 -05:00
Compare commits
554 Commits
phantom-mo
...
phantom-x6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
473b146ae2 | ||
|
|
bdc0bb4441 | ||
|
|
96c5a37ca8 | ||
|
|
ce0b7e1077 | ||
|
|
29cde083f9 | ||
|
|
8faebc01ee | ||
|
|
2ea154b863 | ||
|
|
d02dfc9ff1 | ||
|
|
7bcfa3d7b2 | ||
|
|
1c1f9cddff | ||
|
|
8308d65375 | ||
|
|
d64d59ff27 | ||
|
|
8da6f7d7f4 | ||
|
|
b86cfd49ef | ||
|
|
d421ff9736 | ||
|
|
92c61701f2 | ||
|
|
d45d9e356c | ||
|
|
098f9a2675 | ||
|
|
0347dab82e | ||
|
|
9aa89a0df9 | ||
|
|
556bd11725 | ||
|
|
07f5c21a07 | ||
|
|
93b20960b8 | ||
|
|
3cbdd6bfd3 | ||
|
|
c3c38880e6 | ||
|
|
415bbf5b3b | ||
|
|
186cb02748 | ||
|
|
4aaccb909f | ||
|
|
2daf7dd01a | ||
|
|
ab9ed73e55 | ||
|
|
a4a33fe167 | ||
|
|
e6fbd10031 | ||
|
|
9868d96fec | ||
|
|
0d1c2ac40c | ||
|
|
a6d0dddaf7 | ||
|
|
06d57e8f32 | ||
|
|
70a40edc5d | ||
|
|
95d64208d0 | ||
|
|
e28b2e8328 | ||
|
|
4123745a6b | ||
|
|
70bb4d71e6 | ||
|
|
dd314e1741 | ||
|
|
3cbb489ac6 | ||
|
|
101df4cbf1 | ||
|
|
42263a0ec0 | ||
|
|
d402f7514e | ||
|
|
84e6674e23 | ||
|
|
afcfaace19 | ||
|
|
5a3bd8cfe5 | ||
|
|
f0c90a4744 | ||
|
|
3baed292e1 | ||
|
|
d41a2cad73 | ||
|
|
ffccc3be38 | ||
|
|
ef1e8d7ef3 | ||
|
|
7af891216d | ||
|
|
aa80500b35 | ||
|
|
b72fbe06f7 | ||
|
|
41a63a5418 | ||
|
|
e8ce7898c1 | ||
|
|
687a45c564 | ||
|
|
3ac3dd3ca5 | ||
|
|
f2efebf7d9 | ||
|
|
7b68ce49d5 | ||
|
|
8a2a41fab0 | ||
|
|
ceaaec5378 | ||
|
|
e49a3e7206 | ||
|
|
dc7986dbad | ||
|
|
2dfba130f5 | ||
|
|
155c7c409b | ||
|
|
aacb8970f8 | ||
|
|
be66a0520d | ||
|
|
3fa3c45794 | ||
|
|
0f6da1873e | ||
|
|
1564208e83 | ||
|
|
e724e8db60 | ||
|
|
5a092a83cd | ||
|
|
ffefe5e8aa | ||
|
|
631fdd8a26 | ||
|
|
53d7ef4014 | ||
|
|
5c3ac79043 | ||
|
|
90fb1646e0 | ||
|
|
54604e45e0 | ||
|
|
9ed0f9eee8 | ||
|
|
b764c44318 | ||
|
|
adbd519061 | ||
|
|
b0415299ca | ||
|
|
e96d05149c | ||
|
|
354ddcfee5 | ||
|
|
6d232778e2 | ||
|
|
95ee7daf21 | ||
|
|
2238ac5d17 | ||
|
|
c209c1c034 | ||
|
|
b1eec16333 | ||
|
|
e126c45fb3 | ||
|
|
c89ff93be4 | ||
|
|
c82c27a5c5 | ||
|
|
b3e84f407a | ||
|
|
72902c8984 | ||
|
|
2c47c5eb99 | ||
|
|
398129f3e1 | ||
|
|
d74ab12d9e | ||
|
|
679c0599dd | ||
|
|
4d04ad5632 | ||
|
|
3fdc50b354 | ||
|
|
7eeff32185 | ||
|
|
d40f2cb852 | ||
|
|
f9dc2fb6d5 | ||
|
|
de31dfb11e | ||
|
|
ef6a648189 | ||
|
|
09953e2af8 | ||
|
|
be240119e8 | ||
|
|
2b7893c834 | ||
|
|
896e824ca1 | ||
|
|
7a94725808 | ||
|
|
a66fb76e9a | ||
|
|
b453d48fee | ||
|
|
a7f2c07998 | ||
|
|
3cff878f74 | ||
|
|
665d536481 | ||
|
|
ec6d407fbb | ||
|
|
72bc7ed6d4 | ||
|
|
ac407ca2c0 | ||
|
|
6af5f2b528 | ||
|
|
8fd4a98fbe | ||
|
|
07d553fae3 | ||
|
|
34e0eea173 | ||
|
|
73e6db9a12 | ||
|
|
0df464ac03 | ||
|
|
78ee6afbae | ||
|
|
31be74e6d3 | ||
|
|
767a09894a | ||
|
|
0d410d107d | ||
|
|
7829b18b3c | ||
|
|
b2267a55ce | ||
|
|
eca016fe61 | ||
|
|
81723f7fa9 | ||
|
|
44c91fb90c | ||
|
|
7cb5bd9c95 | ||
|
|
8196f6b9db | ||
|
|
d72b16531b | ||
|
|
f333196efe | ||
|
|
6ea047dcb4 | ||
|
|
ea65867b23 | ||
|
|
c41200e762 | ||
|
|
4c70afbb53 | ||
|
|
0c1ce66053 | ||
|
|
fa8b8cebf9 | ||
|
|
72fa89ba76 | ||
|
|
dc7b4cebf2 | ||
|
|
3c1dd94915 | ||
|
|
3decbbac3a | ||
|
|
27f43569f5 | ||
|
|
bd9bded73b | ||
|
|
0ce81e1ab6 | ||
|
|
2926201694 | ||
|
|
059be2c853 | ||
|
|
dd09f31abb | ||
|
|
1da20da3ff | ||
|
|
341773830b | ||
|
|
c65452bb01 | ||
|
|
34d81356a3 | ||
|
|
c9b84a5202 | ||
|
|
5394cc2dc9 | ||
|
|
63141f339f | ||
|
|
079a0b56c3 | ||
|
|
66721affe7 | ||
|
|
d8ab23e9ba | ||
|
|
11b0a5c9cc | ||
|
|
d273a72cb3 | ||
|
|
08641a6694 | ||
|
|
9ac5c0d886 | ||
|
|
8a57d33223 | ||
|
|
1945b53e43 | ||
|
|
161e1820a8 | ||
|
|
22b5ac8622 | ||
|
|
f75a70e42b | ||
|
|
c9076606be | ||
|
|
853d22b947 | ||
|
|
82d6719d91 | ||
|
|
3f02c150e3 | ||
|
|
d7d46a93a7 | ||
|
|
dfd51635a6 | ||
|
|
c04366d505 | ||
|
|
fd89e88d40 | ||
|
|
894de923b9 | ||
|
|
c47e7cd91d | ||
|
|
e359347a3b | ||
|
|
d320017e3c | ||
|
|
e6c34f4311 | ||
|
|
9b0c945086 | ||
|
|
bc85f5de1d | ||
|
|
4df219161c | ||
|
|
06f157e634 | ||
|
|
54addbdd28 | ||
|
|
0e721917e7 | ||
|
|
4dc7089f89 | ||
|
|
30b5a35db2 | ||
|
|
dc8f81b536 | ||
|
|
a018770a18 | ||
|
|
5e4f7c5d8e | ||
|
|
1d9d665ed0 | ||
|
|
68477c09a7 | ||
|
|
574b9086d4 | ||
|
|
d74c323c66 | ||
|
|
8e85a1b84e | ||
|
|
6ce1cb4325 | ||
|
|
fbbd85d8b2 | ||
|
|
e611dc43c5 | ||
|
|
c86309cfc0 | ||
|
|
ec74e9bce0 | ||
|
|
f371e8a523 | ||
|
|
57a059eecb | ||
|
|
2cb149c647 | ||
|
|
ee5371b582 | ||
|
|
70e4dbe3bd | ||
|
|
18ead9a64f | ||
|
|
d2764cee2a | ||
|
|
082c098420 | ||
|
|
46a42e2901 | ||
|
|
c21cacd309 | ||
|
|
81ac359f71 | ||
|
|
f5b91c90bc | ||
|
|
0a92a3012e | ||
|
|
ff8fc237e2 | ||
|
|
b99d943b4d | ||
|
|
3199fe08e8 | ||
|
|
7503ce62af | ||
|
|
df8ca250aa | ||
|
|
c71b4bde86 | ||
|
|
3f67802e3d | ||
|
|
093ed23140 | ||
|
|
0f8dee7011 | ||
|
|
0cb557b716 | ||
|
|
8137a776b6 | ||
|
|
1af3e0bd93 | ||
|
|
d3662f2302 | ||
|
|
5fe34cb593 | ||
|
|
af5166e95d | ||
|
|
13907d6711 | ||
|
|
c4c9f0e368 | ||
|
|
394932b57f | ||
|
|
7dff9bc696 | ||
|
|
4713eaffdb | ||
|
|
3a7992b1c8 | ||
|
|
5275aa72fb | ||
|
|
9e45b9e808 | ||
|
|
88dfa14046 | ||
|
|
dd4216d432 | ||
|
|
628ab85de4 | ||
|
|
39ea2dd32f | ||
|
|
1d77c40d0e | ||
|
|
a3cbb4158c | ||
|
|
52aa84e9f9 | ||
|
|
f08fc7493d | ||
|
|
a80b1bbcb3 | ||
|
|
7ce8bac3ea | ||
|
|
5c258797ec | ||
|
|
0dccc7e91e | ||
|
|
9e68653949 | ||
|
|
83c09b4540 | ||
|
|
5cf2672469 | ||
|
|
50144721d7 | ||
|
|
0fe7da80ab | ||
|
|
068eb33bf6 | ||
|
|
98b1a7681b | ||
|
|
488967c6ef | ||
|
|
a5aab95ecf | ||
|
|
345de3654a | ||
|
|
6ea7e785e3 | ||
|
|
0c2331f638 | ||
|
|
5fe1ce1eff | ||
|
|
6a6d6f9e0d | ||
|
|
30a512c880 | ||
|
|
2b4519a4ad | ||
|
|
3e25d41c0f | ||
|
|
d8baa93289 | ||
|
|
d8c2640959 | ||
|
|
2ae4337d0d | ||
|
|
0416060643 | ||
|
|
2b1fd77ad7 | ||
|
|
43567a3119 | ||
|
|
d463c2fbc5 | ||
|
|
940cba5f90 | ||
|
|
7321075631 | ||
|
|
a06cbc44cd | ||
|
|
088b4af795 | ||
|
|
fc0b1e8941 | ||
|
|
9ad3b12403 | ||
|
|
88ecec2f9a | ||
|
|
4ea5e9ce9b | ||
|
|
9b617af713 | ||
|
|
e70d92f670 | ||
|
|
1b3acb52f1 | ||
|
|
052ddc11b7 | ||
|
|
4e3a5a8823 | ||
|
|
05e17b70b5 | ||
|
|
949d764638 | ||
|
|
5c2cb4de80 | ||
|
|
f68c5cb4f7 | ||
|
|
06c1f376bc | ||
|
|
7c7a6a4514 | ||
|
|
5293349785 | ||
|
|
2ee0ae1f9e | ||
|
|
8143237d25 | ||
|
|
a426068273 | ||
|
|
599b19102e | ||
|
|
e7a979d6b4 | ||
|
|
7991ed0154 | ||
|
|
a9a3e50179 | ||
|
|
7642fe046b | ||
|
|
ccc2c39d43 | ||
|
|
26228e546e | ||
|
|
6036bc17c5 | ||
|
|
ca4b03f48a | ||
|
|
b0f59ad988 | ||
|
|
c9bdf43a0d | ||
|
|
dadab50f3b | ||
|
|
0b49eba77a | ||
|
|
004b7391c6 | ||
|
|
45c221a3b2 | ||
|
|
364f074be1 | ||
|
|
e82eded1e9 | ||
|
|
b298f84f51 | ||
|
|
26ff28aae6 | ||
|
|
588eb6f691 | ||
|
|
c10448af0b | ||
|
|
0eb7973ab0 | ||
|
|
150a87f2ea | ||
|
|
2505a19a88 | ||
|
|
beea02cea9 | ||
|
|
600b5cfa8e | ||
|
|
8055b5e5da | ||
|
|
4933a75d15 | ||
|
|
478e13b0fd | ||
|
|
9c26da70da | ||
|
|
872a8d983b | ||
|
|
47d3fe1de5 | ||
|
|
9421af2c3f | ||
|
|
90626e353f | ||
|
|
b3e019e7a0 | ||
|
|
84a0a0743b | ||
|
|
377bd6e2b7 | ||
|
|
dc42c6a1df | ||
|
|
0ca70a3197 | ||
|
|
889f92268e | ||
|
|
24fba7a36d | ||
|
|
c90672a5ab | ||
|
|
81c8fc0381 | ||
|
|
7922a3856e | ||
|
|
9aea452829 | ||
|
|
e797b759b7 | ||
|
|
b60d5f837e | ||
|
|
877235c8a2 | ||
|
|
558bbeaa34 | ||
|
|
0911abcfc0 | ||
|
|
04850331ce | ||
|
|
b58336f4c8 | ||
|
|
4665b4fb37 | ||
|
|
49197a2ae0 | ||
|
|
c718db21da | ||
|
|
35f28bdc71 | ||
|
|
bf86b09f4e | ||
|
|
cdde8cfdbe | ||
|
|
cd26b8f728 | ||
|
|
8abfc7609e | ||
|
|
7d06e5d684 | ||
|
|
439870a546 | ||
|
|
82b35f095e | ||
|
|
b40d7d89a1 | ||
|
|
8f3dbbc356 | ||
|
|
f748891b4b | ||
|
|
e325a5c27e | ||
|
|
7d131fbb3d | ||
|
|
2b63110323 | ||
|
|
a63ca0f0e9 | ||
|
|
5b2d0a2ade | ||
|
|
1a984df11a | ||
|
|
fdc7a19628 | ||
|
|
8087996c8e | ||
|
|
e5f264a510 | ||
|
|
0a2eb74c26 | ||
|
|
d5bb3bd799 | ||
|
|
cfa9d88be6 | ||
|
|
ac4617ae51 | ||
|
|
f6bcadfeec | ||
|
|
5272078ea3 | ||
|
|
b6257400ec | ||
|
|
c3a6e01040 | ||
|
|
b63cbbdaaa | ||
|
|
81d3f35034 | ||
|
|
1fc2866032 | ||
|
|
eb2e7b9c79 | ||
|
|
cab900f656 | ||
|
|
e2b91e5dc4 | ||
|
|
00a66e9030 | ||
|
|
775c8780a6 | ||
|
|
50a968ace8 | ||
|
|
db41104d9b | ||
|
|
95bb73c5ac | ||
|
|
7ed2776476 | ||
|
|
341dfb934d | ||
|
|
ecebe73c33 | ||
|
|
1b0c6b919f | ||
|
|
06b876cf8b | ||
|
|
de0d0a3526 | ||
|
|
7f99ac0efa | ||
|
|
d2980c58ec | ||
|
|
d244ed6c64 | ||
|
|
a30d9a1af2 | ||
|
|
7acd6a4d3c | ||
|
|
a6fdcb7493 | ||
|
|
0cc2437a9d | ||
|
|
8102cb63ae | ||
|
|
5062d74041 | ||
|
|
feebb349d5 | ||
|
|
1036813b97 | ||
|
|
6b405700ec | ||
|
|
29b4a83d93 | ||
|
|
7b159c1e63 | ||
|
|
02c64ad3a5 | ||
|
|
13c625d7c0 | ||
|
|
9a3f49bf9c | ||
|
|
6698ca400c | ||
|
|
6bb649bac5 | ||
|
|
b72b74b6c6 | ||
|
|
e7bfea8c69 | ||
|
|
de7e805718 | ||
|
|
7b5e8646eb | ||
|
|
966e147a20 | ||
|
|
269a5bd914 | ||
|
|
4f1f56f653 | ||
|
|
6b517d7ffd | ||
|
|
4eb9a1cfa5 | ||
|
|
db3aeb9ab5 | ||
|
|
984b7bbeea | ||
|
|
582beed977 | ||
|
|
7fa8bd5613 | ||
|
|
4474172c40 | ||
|
|
1dde397e2b | ||
|
|
7ea3b6ca15 | ||
|
|
e52fcf843c | ||
|
|
08ba273089 | ||
|
|
956e7b564c | ||
|
|
08990dd58a | ||
|
|
faa2d632e5 | ||
|
|
1b939ebf4b | ||
|
|
aa46216117 | ||
|
|
ca32434535 | ||
|
|
ebf4f17f17 | ||
|
|
22778091f9 | ||
|
|
4d389ae5ce | ||
|
|
39e3120058 | ||
|
|
6b15d7e260 | ||
|
|
277025775d | ||
|
|
dbd649bfb5 | ||
|
|
df4ddba1ab | ||
|
|
21985d0814 | ||
|
|
f7031dcb7f | ||
|
|
0219e62979 | ||
|
|
e66725047a | ||
|
|
84fa99a126 | ||
|
|
78b3c9552b | ||
|
|
1222aeaab6 | ||
|
|
cbbfc5b58c | ||
|
|
11164ab838 | ||
|
|
8339f7fdb3 | ||
|
|
220cd84ef5 | ||
|
|
e5fa446159 | ||
|
|
70c320e98b | ||
|
|
8e486da928 | ||
|
|
7ae906863d | ||
|
|
fb4483fdcf | ||
|
|
e32e6e0bec | ||
|
|
05e7b90aab | ||
|
|
ee59f91ba2 | ||
|
|
ece3241041 | ||
|
|
fcb1bcb91b | ||
|
|
728c0e8272 | ||
|
|
9b212d11f0 | ||
|
|
223209e1eb | ||
|
|
5b741a10db | ||
|
|
1606ea19a8 | ||
|
|
e5632019db | ||
|
|
ff994d594a | ||
|
|
0214ced8f0 | ||
|
|
813e5e1db8 | ||
|
|
69627911b3 | ||
|
|
29f905b942 | ||
|
|
551fa7fe10 | ||
|
|
2ce3cd4c2a | ||
|
|
8ea24a6b09 | ||
|
|
00ca91399a | ||
|
|
cf327077e9 | ||
|
|
f215ba9bac | ||
|
|
c3c6b3d166 | ||
|
|
b319cb9525 | ||
|
|
95cd327022 | ||
|
|
807cfebf76 | ||
|
|
a29b1259b5 | ||
|
|
102b8afd66 | ||
|
|
561fdef815 | ||
|
|
47db1db861 | ||
|
|
de3d7e925a | ||
|
|
b04c286efc | ||
|
|
b8b82189f7 | ||
|
|
08b65a954d | ||
|
|
c6c998dc9f | ||
|
|
b3ff91608e | ||
|
|
1d862db7c9 | ||
|
|
070cbeebbe | ||
|
|
2c95f07cb2 | ||
|
|
4a2277b424 | ||
|
|
a1f02916d4 | ||
|
|
900dfd92d0 | ||
|
|
d6997b0588 | ||
|
|
86c74b3ee0 | ||
|
|
b1a8c70d20 | ||
|
|
9107d1678c | ||
|
|
095234babc | ||
|
|
8aecec507e | ||
|
|
70fb1551af | ||
|
|
fb67e123f4 | ||
|
|
ae2efbc116 | ||
|
|
4bc0ffa74d | ||
|
|
6fed932a61 | ||
|
|
939ebcf897 | ||
|
|
1239fa874d | ||
|
|
779ab39f50 | ||
|
|
00283e3d6e | ||
|
|
2b4429f8b7 | ||
|
|
2446c4185a | ||
|
|
04900e5f90 | ||
|
|
8abdb8bf51 | ||
|
|
e217068dbd | ||
|
|
e3a9f753d2 | ||
|
|
fc2a586453 | ||
|
|
979ea449bd | ||
|
|
ba5e2cfc45 | ||
|
|
3b565d8bb1 | ||
|
|
21a92b62fd | ||
|
|
7e33261ccc | ||
|
|
6a489a0b8f | ||
|
|
44e9c77568 | ||
|
|
77816aebac | ||
|
|
9dd967f2aa | ||
|
|
ef7a08879f | ||
|
|
647e444a07 | ||
|
|
c417239652 | ||
|
|
e10c92878d | ||
|
|
fc376bfe3f | ||
|
|
36fe4eaa49 | ||
|
|
a3baab9671 | ||
|
|
edd6c0bd4c | ||
|
|
ce59db528b |
@@ -9,7 +9,7 @@ insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.{js,html,js,hbs,less}]
|
||||
[*.{js,html,js,hbs,less,css}]
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
24
.gitattributes
vendored
24
.gitattributes
vendored
@@ -1,22 +1,12 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
*text eol=lf
|
||||
* text=auto
|
||||
|
||||
# Explicitly set bash scripts to have unix endings
|
||||
# when checked out on windows
|
||||
*.sh text eol=lf
|
||||
distribution/debian/* text eol=lf
|
||||
macOS/Sonarr text eol=lf
|
||||
|
||||
# Custom for Visual Studio
|
||||
*.cs diff=csharp
|
||||
*.sln merge=union
|
||||
*.csproj merge=union
|
||||
*.vbproj merge=union
|
||||
*.fsproj merge=union
|
||||
*.dbproj merge=union
|
||||
|
||||
# Standard to msysgit
|
||||
*.doc diff=astextplain
|
||||
*.DOC diff=astextplain
|
||||
*.docx diff=astextplain
|
||||
*.DOCX diff=astextplain
|
||||
*.dot diff=astextplain
|
||||
*.DOT diff=astextplain
|
||||
*.pdf diff=astextplain
|
||||
*.PDF diff=astextplain
|
||||
*.rtf diff=astextplain
|
||||
*.RTF diff=astextplain
|
||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -45,6 +45,10 @@ _dotCover*
|
||||
# DevExpress CodeRush
|
||||
src/.cr/
|
||||
|
||||
# Emacs
|
||||
*~
|
||||
\#*\#
|
||||
|
||||
# NCrunch
|
||||
*.ncrunch*
|
||||
.*crunch*.local.xml
|
||||
@@ -115,7 +119,9 @@ node_modules/
|
||||
_output*
|
||||
_rawPackage/
|
||||
_dotTrace*
|
||||
_tests/
|
||||
_tests*
|
||||
_publish*
|
||||
_temp*
|
||||
*.Result.xml
|
||||
setup/Output/
|
||||
*.~is
|
||||
@@ -133,6 +139,5 @@ output/*
|
||||
.DS_Store
|
||||
|
||||
_start
|
||||
_temp_*/**/*
|
||||
|
||||
src/.idea/
|
||||
|
||||
4
.gitmodules
vendored
4
.gitmodules
vendored
@@ -1,4 +0,0 @@
|
||||
[submodule "src/ExternalModules/CurlSharp"]
|
||||
path = src/ExternalModules/CurlSharp
|
||||
url = https://github.com/Sonarr/CurlSharp.git
|
||||
branch = master
|
||||
3
.yarnrc
3
.yarnrc
@@ -1 +1,2 @@
|
||||
save-prefix ""
|
||||
save-exact true
|
||||
registry "https://registry.yarnpkg.com"
|
||||
|
||||
@@ -7,21 +7,7 @@ Setup guides, FAQ, the more information we have on the wiki the better.
|
||||
|
||||
## Development ##
|
||||
|
||||
### Tools required ###
|
||||
- Visual Studio 2015
|
||||
- HTML/Javascript editor of choice (Sublime Text/Webstorm/Atom/etc)
|
||||
- npm (node package manager)
|
||||
- git
|
||||
|
||||
### Getting started ###
|
||||
|
||||
1. Fork Sonarr
|
||||
2. Clone (develop branch) *you may need pull in submodules separately if you client doesn't clone them automatically (CurlSharp)*
|
||||
3. Run `npm install`
|
||||
4. Run `npm start` - Used to compile the UI components and copy them.
|
||||
Leave this window open.
|
||||
If you have gulp globally installed you can use `gulp watch` instead
|
||||
5. Compile in Visual Studio
|
||||
See the readme for information on setting up your development environment.
|
||||
|
||||
### Contributing Code ###
|
||||
- If you're adding a new, already requested feature, please comment on [Github Issues](https://github.com/Sonarr/Sonarr/issues "Github Issues") so work is not duplicated (If you want to add something not already on there, please talk to us first)
|
||||
|
||||
28
README.md
28
README.md
@@ -20,33 +20,39 @@ Sonarr is a PVR for Usenet and BitTorrent users. It can monitor multiple RSS fee
|
||||
|
||||
### Requirements
|
||||
|
||||
* Visual Studio 2015 (https://www.visualstudio.com/vs/)
|
||||
* [Visual Studio 2017](https://www.visualstudio.com/vs/)
|
||||
* [Git](https://git-scm.com/downloads)
|
||||
* [NodeJS](https://nodejs.org/en/download/)
|
||||
* [Yarn](https://yarnpkg.com/)
|
||||
|
||||
### Setup
|
||||
|
||||
* Make sure all the required software mentioned above are installed.
|
||||
* Clone the repository into your development machine. [*info*](https://help.github.com/articles/working-with-repositories)
|
||||
* Make sure all the required software mentioned above are installed
|
||||
* Clone the repository into your development machine. [*info*](https://help.github.com/en/articles/working-with-forks)
|
||||
* Grab the submodules `git submodule init && git submodule update`
|
||||
* Install the required Node Packages `npm install`
|
||||
* Start gulp to monitor your dev environment for any changes that need post processing using `npm start` command.
|
||||
* Install the required Node Packages `yarn`
|
||||
|
||||
*Please note gulp must be running at all times while you are working with Sonarr client source files.*
|
||||
### Backend Development
|
||||
|
||||
### Development
|
||||
|
||||
* Open `NzbDrone.sln` in Visual Studio
|
||||
* Run `yarn build` to build the UI
|
||||
* Open `Sonarr.sln` in Visual Studio
|
||||
* Make sure `NzbDrone.Console` is set as the startup project
|
||||
* Build `NzbDrone.Windows` and `NzbDrone.Mono` projects
|
||||
* Build Solution
|
||||
|
||||
### UI Development
|
||||
|
||||
* Run `yarn watch` to build UI and rebuild automatically when changes are detected
|
||||
* Run Sonarr.Console.exe (or debug in Visual Studio)
|
||||
|
||||
### License
|
||||
|
||||
|
||||
* [GNU GPL v3](http://www.gnu.org/licenses/gpl.html)
|
||||
* Copyright 2010-2017
|
||||
* Copyright 2010-2019
|
||||
|
||||
### Sponsors
|
||||
|
||||
* [JetBrains](http://www.jetbrains.com/) for providing us with free licenses to their great tools
|
||||
* [ReSharper](http://www.jetbrains.com/resharper/)
|
||||
* [WebStorm](http://www.jetbrains.com/webstorm/)
|
||||
* [TeamCity](http://www.jetbrains.com/teamcity/)
|
||||
|
||||
186
build.sh
186
build.sh
@@ -1,15 +1,18 @@
|
||||
#! /bin/bash
|
||||
msBuildVersion='15.0'
|
||||
outputFolder='./_output'
|
||||
outputFolderWindows='./_output_windows'
|
||||
outputFolderLinux='./_output_linux'
|
||||
outputFolderMacOS='./_output_macos'
|
||||
outputFolderMacOSApp='./_output_macos_app'
|
||||
testPackageFolder='./_tests/'
|
||||
testSearchPattern='*.Test/bin/x86/Release'
|
||||
testPackageFolder='./_tests'
|
||||
testPackageFolderWindows='./_tests_windows'
|
||||
testPackageFolderLinux='./_tests_linux'
|
||||
sourceFolder='./src'
|
||||
slnFile=$sourceFolder/Sonarr.sln
|
||||
updateFolder=$outputFolder/Sonarr.Update
|
||||
updateFolderMono=$outputFolderLinux/Sonarr.Update
|
||||
updateSubFolder=Sonarr.Update
|
||||
|
||||
sqlitePackageDir="$HOME/.nuget/packages/system.data.sqlite.core.lidarr/1.0.111-5"
|
||||
|
||||
nuget='tools/nuget/nuget.exe';
|
||||
vswhere='tools/vswhere/vswhere.exe';
|
||||
@@ -47,7 +50,8 @@ UpdateVersionNumber()
|
||||
verBuild=`echo "${BUILD_NUMBER}" | cut -d. -f4`
|
||||
BUILD_NUMBER=$verMajorMinorRevision.$verBuild
|
||||
echo "##teamcity[buildNumber '$BUILD_NUMBER']"
|
||||
sed -i "s/^[[]assembly: Assembly\(File\|Informational\)\?Version[(]\"[0-9.*]\+\"[)]/[assembly: Assembly\1Version(\"$BUILD_NUMBER\")/g" ./src/NzbDrone*/Properties/AssemblyInfo.cs ./src/Sonarr*/Properties/AssemblyInfo.cs ./src/ServiceHelpers/*/Properties/AssemblyInfo.cs ./src/Common/CommonVersionInfo.cs
|
||||
sed -i "s/<AssemblyVersion>[0-9.*]\+<\/AssemblyVersion>/<AssemblyVersion>$BUILD_NUMBER<\/AssemblyVersion>/g" ./src/Directory.Build.props
|
||||
sed -i "s/<AssemblyConfiguration>[\$()A-Za-z-]\+<\/AssemblyConfiguration>/<AssemblyConfiguration>${BRANCH:-dev}<\/AssemblyConfiguration>/g" ./src/Directory.Build.props
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -86,16 +90,17 @@ CleanFolder()
|
||||
|
||||
BuildWithMSBuild()
|
||||
{
|
||||
installationPath=`$vswhere -latest -products \* -requires Microsoft.Component.MSBuild -property installationPath`
|
||||
installationPath=${installationPath/C:\\/\/c\/}
|
||||
installationPath=${installationPath//\\/\/}
|
||||
msBuild="$installationPath/MSBuild/$msBuildVersion/Bin"
|
||||
echo $msBuild
|
||||
msBuildPath=`$vswhere -latest -products \* -requires Microsoft.Component.MSBuild -find MSBuild\\\\\*\*\\\\Bin\\\\MSBuild.exe`
|
||||
msBuildPath=${msBuildPath/C:\\/\/c\/}
|
||||
msBuildPath=${msBuildPath//\\/\/}
|
||||
msBuildDir=$(dirname "$msBuildPath")
|
||||
|
||||
export PATH=$msBuild:$PATH
|
||||
CheckExitCode MSBuild.exe $slnFile //p:Configuration=Release //p:Platform=x86 //t:Clean //m
|
||||
echo $msBuildDir
|
||||
|
||||
export PATH=$msBuildDir:$PATH
|
||||
CheckExitCode MSBuild.exe $slnFile //p:Configuration=Release //p:Platform=x64 //t:Clean //m
|
||||
$nuget restore $slnFile
|
||||
CheckExitCode MSBuild.exe $slnFile //p:Configuration=Release //p:Platform=x86 //t:Build //m //p:AllowedReferenceRelatedFileExtensions=.pdb
|
||||
CheckExitCode MSBuild.exe $slnFile //p:Configuration=Release //p:Platform=x64 //t:Build //m //p:AllowedReferenceRelatedFileExtensions=.pdb
|
||||
}
|
||||
|
||||
BuildWithXbuild()
|
||||
@@ -103,13 +108,13 @@ BuildWithXbuild()
|
||||
export MONO_IOMAP=case
|
||||
CheckExitCode xbuild /t:Clean $slnFile
|
||||
mono $nuget restore $slnFile
|
||||
CheckExitCode xbuild /p:Configuration=Release /p:Platform=x86 /t:Build /p:AllowedReferenceRelatedFileExtensions=.pdb $slnFile
|
||||
CheckExitCode xbuild /p:Configuration=Release /p:Platform=x64 /t:Build /p:AllowedReferenceRelatedFileExtensions=.pdb $slnFile
|
||||
}
|
||||
|
||||
LintUI()
|
||||
{
|
||||
ProgressStart 'ESLint'
|
||||
CheckExitCode yarn eslint
|
||||
CheckExitCode yarn lint
|
||||
ProgressEnd 'ESLint'
|
||||
|
||||
ProgressStart 'Stylelint'
|
||||
@@ -122,6 +127,7 @@ Build()
|
||||
ProgressStart 'Build'
|
||||
|
||||
rm -rf $outputFolder
|
||||
rm -rf $testPackageFolder
|
||||
|
||||
if [ $runtime = "dotnet" ] ; then
|
||||
BuildWithMSBuild
|
||||
@@ -131,9 +137,6 @@ Build()
|
||||
|
||||
CleanFolder $outputFolder false
|
||||
|
||||
echo "Removing Mono.Posix.dll"
|
||||
rm $outputFolder/Mono.Posix.dll
|
||||
|
||||
ProgressEnd 'Build'
|
||||
}
|
||||
|
||||
@@ -167,6 +170,36 @@ CreateMdbs()
|
||||
fi
|
||||
}
|
||||
|
||||
PatchMono()
|
||||
{
|
||||
local path=$1
|
||||
|
||||
# Copy over the netstandard.dll facade since mono has no separate package for it and includes it in mono-devel
|
||||
for assembly in netstandard System.Runtime
|
||||
do
|
||||
echo "Copy Mono-specific facade $assembly.dll"
|
||||
cp $sourceFolder/Libraries/Mono/$assembly.dll $path/$assembly.dll
|
||||
done
|
||||
|
||||
# Copy more stable version of Vectors for mono <5.12
|
||||
if [ -e $path/System.Numerics.Vectors.dll ]; then
|
||||
packageDir="$HOME/.nuget/packages/system.numerics.vectors/4.5.0"
|
||||
|
||||
if [ ! -d "$HOME/.nuget/packages/system.numerics.vectors/4.5.0" ]; then
|
||||
# May reside in the NuGetFallback folder, which is harder to find
|
||||
# Download somewhere to get the real cache populated
|
||||
if [ $runtime = "dotnet" ] ; then
|
||||
$nuget install System.Numerics.Vectors -Version 4.5.0 -Output ./_temp/System.Numerics.Vectors
|
||||
else
|
||||
mono $nuget install System.Numerics.Vectors -Version 4.5.0 -Output ./_temp/System.Numerics.Vectors
|
||||
fi
|
||||
rm -rf ./_temp/System.Numerics.Vectors
|
||||
fi
|
||||
# Copy the netstandard2.0 version rather than net46
|
||||
cp "$packageDir/lib/netstandard2.0/System.Numerics.Vectors.dll" $path/
|
||||
fi
|
||||
}
|
||||
|
||||
PackageMono()
|
||||
{
|
||||
ProgressStart 'Creating Mono Package'
|
||||
@@ -190,16 +223,11 @@ PackageMono()
|
||||
rm -f $outputFolderLinux/sqlite3.*
|
||||
rm -f $outputFolderLinux/MediaInfo.*
|
||||
|
||||
PatchMono $outputFolderLinux
|
||||
|
||||
echo "Adding Sonarr.Core.dll.config (for dllmap)"
|
||||
cp $sourceFolder/NzbDrone.Core/Sonarr.Core.dll.config $outputFolderLinux
|
||||
|
||||
echo "Adding CurlSharp.dll.config (for dllmap)"
|
||||
cp $sourceFolder/NzbDrone.Common/CurlSharp.dll.config $outputFolderLinux
|
||||
|
||||
echo "Adding unix System.Runtime.InteropServices.RuntimeInformation.dll (for SharpRaven)"
|
||||
cp $sourceFolder/packages/System.Runtime.InteropServices.RuntimeInformation.4.3.0/runtimes/unix/lib/netstandard1.1/System.Runtime.InteropServices.RuntimeInformation.dll $outputFolderLinux
|
||||
cp $sourceFolder/packages/System.Runtime.InteropServices.RuntimeInformation.4.3.0/runtimes/unix/lib/netstandard1.1/System.Runtime.InteropServices.RuntimeInformation.dll $outputFolderLinux/Sonarr.Update
|
||||
|
||||
echo "Renaming Sonarr.Console.exe to Sonarr.exe"
|
||||
rm $outputFolderLinux/Sonarr.exe*
|
||||
for file in $outputFolderLinux/Sonarr.Console.exe*; do
|
||||
@@ -210,7 +238,7 @@ PackageMono()
|
||||
rm $outputFolderLinux/Sonarr.Windows.*
|
||||
|
||||
echo "Adding Sonarr.Mono to UpdatePackage"
|
||||
cp $outputFolderLinux/Sonarr.Mono.* $updateFolderMono
|
||||
cp $outputFolderLinux/Sonarr.Mono.* $outputFolderLinux/$updateSubFolder/
|
||||
|
||||
ProgressEnd 'Creating Mono Package'
|
||||
}
|
||||
@@ -229,11 +257,11 @@ PackageMacOS()
|
||||
echo "Copying Binaries"
|
||||
cp -r $outputFolderLinux/* $outputFolderMacOS
|
||||
|
||||
echo "Adding sqlite dylibs"
|
||||
cp $sourceFolder/Libraries/Sqlite/*.dylib $outputFolderMacOS
|
||||
echo "Adding sqlite dylib"
|
||||
cp "$sqlitePackageDir/runtimes/osx-x64/native/net46"/* $outputFolderMacOS
|
||||
|
||||
echo "Adding MediaInfo dylib"
|
||||
cp $sourceFolder/Libraries/MediaInfo/*.dylib $outputFolderMacOS
|
||||
cp $sourceFolder/Libraries/MediaInfo/x64/*.dylib $outputFolderMacOS
|
||||
|
||||
ProgressEnd 'Creating MacOS Package'
|
||||
}
|
||||
@@ -254,11 +282,11 @@ PackageMacOSApp()
|
||||
echo "Copying Binaries"
|
||||
cp -r $outputFolderLinux/* $outputFolderMacOSApp/Sonarr.app/Contents/MacOS
|
||||
|
||||
echo "Adding sqlite dylibs"
|
||||
cp $sourceFolder/Libraries/Sqlite/*.dylib $outputFolderMacOSApp/Sonarr.app/Contents/MacOS
|
||||
echo "Adding sqlite dylib"
|
||||
cp "$sqlitePackageDir/runtimes/osx-x64/native/net46"/* $outputFolderMacOS
|
||||
|
||||
echo "Adding MediaInfo dylib"
|
||||
cp $sourceFolder/Libraries/MediaInfo/*.dylib $outputFolderMacOSApp/Sonarr.app/Contents/MacOS
|
||||
cp $sourceFolder/Libraries/MediaInfo/x64/*.dylib $outputFolderMacOS
|
||||
|
||||
echo "Removing Update Folder"
|
||||
rm -r $outputFolderMacOSApp/Sonarr.app/Contents/MacOS/Sonarr.Update
|
||||
@@ -266,54 +294,88 @@ PackageMacOSApp()
|
||||
ProgressEnd 'Creating macOS App Package'
|
||||
}
|
||||
|
||||
PackageTests()
|
||||
PackageTestsMono()
|
||||
{
|
||||
ProgressStart 'Creating Test Package'
|
||||
ProgressStart 'Creating Mono Test Package'
|
||||
|
||||
rm -rf $testPackageFolder
|
||||
mkdir $testPackageFolder
|
||||
rm -rf $testPackageFolderLinux
|
||||
|
||||
find $sourceFolder -path $testSearchPattern -exec cp -r -u -T "{}" $testPackageFolder \;
|
||||
echo "Copying Binaries"
|
||||
cp -r $testPackageFolder $testPackageFolderLinux
|
||||
|
||||
if [ $runtime = "dotnet" ] ; then
|
||||
$nuget install NUnit.ConsoleRunner -Version 3.2.0 -Output $testPackageFolder
|
||||
$nuget install NUnit.ConsoleRunner -Version 3.10.0 -Output $testPackageFolderLinux
|
||||
else
|
||||
mono $nuget install NUnit.ConsoleRunner -Version 3.2.0 -Output $testPackageFolder
|
||||
mono $nuget install NUnit.ConsoleRunner -Version 3.10.0 -Output $testPackageFolderLinux
|
||||
fi
|
||||
|
||||
cp $outputFolder/*.dll $testPackageFolder
|
||||
cp ./test.sh $testPackageFolder
|
||||
echo "Creating MDBs"
|
||||
CreateMdbs $testPackageFolderLinux
|
||||
|
||||
echo "Creating MDBs for tests"
|
||||
CreateMdbs $testPackageFolder
|
||||
echo "Removing PDBs"
|
||||
find $testPackageFolderLinux -name "*.pdb" -exec rm "{}" \;
|
||||
|
||||
rm -f $testPackageFolder/*.log.config
|
||||
|
||||
CleanFolder $testPackageFolder true
|
||||
PatchMono $testPackageFolderLinux
|
||||
|
||||
echo "Adding Sonarr.Core.dll.config (for dllmap)"
|
||||
cp $sourceFolder/NzbDrone.Core/Sonarr.Core.dll.config $testPackageFolder
|
||||
cp $sourceFolder/NzbDrone.Core/Sonarr.Core.dll.config $testPackageFolderLinux
|
||||
|
||||
echo "Adding CurlSharp.dll.config (for dllmap)"
|
||||
cp $sourceFolder/NzbDrone.Common/CurlSharp.dll.config $testPackageFolder
|
||||
cp ./test.sh $testPackageFolderLinux/
|
||||
dos2unix $testPackageFolderLinux/test.sh
|
||||
|
||||
echo "Copying CurlSharp libraries"
|
||||
cp $sourceFolder/ExternalModules/CurlSharp/libs/i386/* $testPackageFolder
|
||||
echo "Removing Sonarr.Windows"
|
||||
rm $testPackageFolderLinux/Sonarr.Windows.*
|
||||
|
||||
ProgressEnd 'Creating Test Package'
|
||||
rm -f $testPackageFolderLinux/*.log.config
|
||||
|
||||
CleanFolder $testPackageFolderLinux true
|
||||
|
||||
ProgressEnd 'Creating Linux Test Package'
|
||||
}
|
||||
|
||||
CleanupWindowsPackage()
|
||||
PackageTestsWindows()
|
||||
{
|
||||
ProgressStart 'Cleaning Windows Package'
|
||||
ProgressStart 'Creating Windows Test Package'
|
||||
|
||||
rm -rf $testPackageFolderWindows
|
||||
|
||||
echo "Copying Binaries"
|
||||
cp -r $testPackageFolder $testPackageFolderWindows
|
||||
|
||||
if [ $runtime = "dotnet" ] ; then
|
||||
$nuget install NUnit.ConsoleRunner -Version 3.10.0 -Output $testPackageFolderWindows
|
||||
else
|
||||
mono $nuget install NUnit.ConsoleRunner -Version 3.10.0 -Output $testPackageFolderWindows
|
||||
fi
|
||||
|
||||
cp ./test.sh $testPackageFolderWindows
|
||||
|
||||
echo "Removing Sonarr.Mono"
|
||||
rm -f $outputFolder/Sonarr.Mono.*
|
||||
rm -f $testPackageFolderWindows/Sonarr.Mono.*
|
||||
|
||||
rm -f $testPackageFolderWindows/*.log.config
|
||||
|
||||
CleanFolder $testPackageFolderWindows true
|
||||
|
||||
ProgressEnd 'Creating Windows Test Package'
|
||||
}
|
||||
|
||||
PackageWindows()
|
||||
{
|
||||
ProgressStart 'Creating Windows Package'
|
||||
|
||||
rm -rf $outputFolderWindows
|
||||
|
||||
echo "Copying Binaries"
|
||||
cp -r $outputFolder $outputFolderWindows
|
||||
|
||||
echo "Removing Sonarr.Mono"
|
||||
rm -f $outputFolderWindows/Sonarr.Mono.*
|
||||
|
||||
echo "Adding Sonarr.Windows to UpdatePackage"
|
||||
cp $outputFolder/Sonarr.Windows.* $updateFolder
|
||||
cp $outputFolderWindows/Sonarr.Windows.* $outputFolderWindows/$updateSubFolder/
|
||||
|
||||
ProgressEnd 'Cleaning Windows Package'
|
||||
ProgressEnd 'Creating Windows Package'
|
||||
}
|
||||
|
||||
PublishArtifacts()
|
||||
@@ -321,10 +383,11 @@ PublishArtifacts()
|
||||
ProgressStart 'Publishing Artifacts'
|
||||
|
||||
# Tests
|
||||
echo "##teamcity[publishArtifacts '_tests/** => tests.zip']"
|
||||
echo "##teamcity[publishArtifacts '$testPackageFolderWindows/** => tests.windows.zip']"
|
||||
echo "##teamcity[publishArtifacts '$testPackageFolderLinux/** => tests.linux.zip']"
|
||||
|
||||
# Releases
|
||||
echo "##teamcity[publishArtifacts '$outputFolder/** => Sonarr.$BRANCH.$BUILD_NUMBER.windows.zip!Sonarr']"
|
||||
echo "##teamcity[publishArtifacts '$outputFolderWindows/** => Sonarr.$BRANCH.$BUILD_NUMBER.windows.zip!Sonarr']"
|
||||
echo "##teamcity[publishArtifacts '$outputFolderLinux/** => Sonarr.$BRANCH.$BUILD_NUMBER.linux.tar.gz!Sonarr']"
|
||||
echo "##teamcity[publishArtifacts '$outputFolderMacOS/** => Sonarr.$BRANCH.$BUILD_NUMBER.macos.tar.gz!Sonarr']"
|
||||
echo "##teamcity[publishArtifacts '$outputFolderMacOSApp/** => Sonarr.$BRANCH.$BUILD_NUMBER.macos.zip']"
|
||||
@@ -354,6 +417,7 @@ RunGulp
|
||||
PackageMono
|
||||
PackageMacOS
|
||||
PackageMacOSApp
|
||||
PackageTests
|
||||
CleanupWindowsPackage
|
||||
PackageTestsMono
|
||||
PackageTestsWindows
|
||||
PackageWindows
|
||||
PublishArtifacts
|
||||
|
||||
7
distribution/build-package.sh
Normal file
7
distribution/build-package.sh
Normal file
@@ -0,0 +1,7 @@
|
||||
# Note, this script is only used for local dev tests, this is not the script used for building the official sonarr package
|
||||
|
||||
mkdir -p /${PWD}/../_output_debian
|
||||
|
||||
docker build -f docker-build/Dockerfile -t sonarr-packager ./docker-build
|
||||
|
||||
docker run --rm -v /${PWD}/../_output_linux:/data/sonarr_bin:ro -v /${PWD}:/data/build -v /${PWD}/../_output_debian:/data/output sonarr-packager
|
||||
@@ -19,7 +19,11 @@ sed -i '/#BEGIN BUILTIN UPDATER/,/#END BUILTIN UPDATER/d' debian/preinst debian/
|
||||
echo "# Do Not Edit\nPackageVersion=$BuildVersion\nReleaseVersion=$BuildVersion\nUpdateMethod=$PackageUpdater\nBranch=$BuildBranch" > package_info
|
||||
|
||||
echo Running debuild for $BuildVersion
|
||||
debuild -b
|
||||
if [ -z "${TEST_OUTPUT}" ]; then
|
||||
debuild -b
|
||||
else
|
||||
debuild -us -uc -b
|
||||
fi
|
||||
|
||||
# Restore debian directory to the original files
|
||||
rm -rf ./debian
|
||||
@@ -32,16 +36,28 @@ sed -i '/#BEGIN BUILTIN UPDATER/d; /#END BUILTIN UPDATER/d' debian/preinst debia
|
||||
echo "# Do Not Edit\nPackageVersion=$BootstrapVersion\nReleaseVersion=$BuildVersion\nUpdateMethod=$BootstrapUpdater\nBranch=$BuildBranch" > package_info
|
||||
|
||||
echo Running debuild for $BootstrapVersion
|
||||
debuild -b
|
||||
if [ -z "${TEST_OUTPUT}" ]; then
|
||||
debuild -b
|
||||
else
|
||||
debuild -us -uc -b
|
||||
fi
|
||||
|
||||
echo Moving stuff around
|
||||
mv ../sonarr_*.deb ./
|
||||
mv ../sonarr_*.changes ./
|
||||
rm ../sonarr_*.build
|
||||
|
||||
echo Signing Package
|
||||
dpkg-sig -k 884589CE --sign builder "sonarr_${BuildVersion}_all.deb"
|
||||
dpkg-sig -k 884589CE --sign builder "sonarr_${BootstrapVersion}_all.deb"
|
||||
if [ -z "${TEST_OUTPUT}" ]; then
|
||||
echo Signing Package
|
||||
dpkg-sig -k 884589CE --sign builder "sonarr_${BuildVersion}_all.deb"
|
||||
dpkg-sig -k 884589CE --sign builder "sonarr_${BootstrapVersion}_all.deb"
|
||||
|
||||
echo running alien
|
||||
alien -r -v ./*.deb
|
||||
echo running alien
|
||||
alien -r -v ./*.deb
|
||||
else
|
||||
echo "Exporting packages to ${TEST_OUTPUT}"
|
||||
dpkg -e "sonarr_${BuildVersion}_all.deb" ${TEST_OUTPUT}/sonarr-build
|
||||
dpkg -e "sonarr_${BootstrapVersion}_all.deb" ${TEST_OUTPUT}/sonarr-release
|
||||
|
||||
cp *.deb ${TEST_OUTPUT}/
|
||||
fi
|
||||
|
||||
@@ -7,15 +7,16 @@ Vcs-Git: git@github.com:Sonarr/Sonarr.git
|
||||
Vcs-Browser: https://github.com/Sonarr/Sonarr
|
||||
Build-Depends: debhelper (>= 9),
|
||||
dh-systemd (>= 1.5),
|
||||
mono-devel (>= 4.6),
|
||||
libmono-cil-dev (>= 4.6),
|
||||
cli-common-dev (>= 0.5.7)
|
||||
mono-devel (>= 5.18),
|
||||
libmono-cil-dev (>= 5.18),
|
||||
cli-common-dev (>= 0.9+xamarin5)
|
||||
|
||||
Package: sonarr
|
||||
Architecture: all
|
||||
Provides: nzbdrone
|
||||
Conflicts: nzbdrone
|
||||
Replaces: nzbdrone
|
||||
Depends: adduser, libsqlite3-0 (>= 3.7), libmediainfo0v5 (>= 0.7.52), mono-runtime (>= 5.4), ${cli:Depends}, ${misc:Depends}
|
||||
Recommends: sqlite3 (>= 3.7), mediainfo (>= 0.7.52), ${cli:Recommends}
|
||||
Depends: adduser, libsqlite3-0 (>= 3.7), libmediainfo0v5 (>= 0.7.52) | libmediainfo0 (>= 0.7.52), mono-runtime (>= 5.18), ca-certificates-mono, libmono-system-net-http4.0-cil (>= 4.0.0~alpha1), ${cli:Depends}, ${misc:Depends}
|
||||
Recommends: libmediainfo0v5 (>= 18.03) | libmediainfo0 (>= 18.03)
|
||||
Suggests: sqlite3 (>= 3.7), mediainfo (>= 0.7.52)
|
||||
Description: Internet PVR
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
sonarr_bin/* /usr/lib/sonarr/bin
|
||||
package_info /usr/lib/sonarr
|
||||
sonarr_bin/* usr/lib/sonarr/bin
|
||||
package_info usr/lib/sonarr
|
||||
|
||||
@@ -104,4 +104,4 @@ fi
|
||||
|
||||
#DEBHELPER#
|
||||
|
||||
exit 0
|
||||
exit 0
|
||||
|
||||
@@ -22,10 +22,25 @@ if [ $1 = "install" ]; then
|
||||
fi
|
||||
|
||||
if [ "$psNzbDroneUnit" != "-" ] && [ -d /run/systemd/system ]; then
|
||||
# The user used a systemd auto-startup for NzbDrone, we can deal with that.
|
||||
echo "NzbDrone systemd startup detected at $psNzbDroneUnit, stopping and disabling..."
|
||||
deb-systemd-invoke stop $psNzbDroneUnit >/dev/null
|
||||
deb-systemd-invoke mask $psNzbDroneUnit >/dev/null
|
||||
if [ "$psNzbDroneUnit" = "sonarr.service" ]; then
|
||||
# Conflicts with our new sonarr.service so we have to remove it
|
||||
echo "NzbDrone systemd startup detected at $psNzbDroneUnit, stopping and removing..."
|
||||
deb-systemd-invoke stop $psNzbDroneUnit >/dev/null
|
||||
if [ -f "/etc/systemd/system/$psNzbDroneUnit" ]; then
|
||||
rm /etc/systemd/system/$psNzbDroneUnit
|
||||
fi
|
||||
if [ -f "/usr/lib/systemd/system/$psNzbDroneUnit" ]; then
|
||||
rm /usr/lib/systemd/system/$psNzbDroneUnit
|
||||
fi
|
||||
deb-systemd-helper purge $psNzbDroneUnit >/dev/null
|
||||
deb-systemd-helper unmask $psNzbDroneUnit >/dev/null
|
||||
systemctl --system daemon-reload >/dev/null || true
|
||||
else
|
||||
# Just disable it, so the user can revisit the settings later
|
||||
echo "NzbDrone systemd startup detected at $psNzbDroneUnit, stopping and disabling..."
|
||||
deb-systemd-invoke stop $psNzbDroneUnit >/dev/null
|
||||
deb-systemd-invoke mask $psNzbDroneUnit >/dev/null
|
||||
fi
|
||||
else
|
||||
# We don't support auto migration for other startup methods, so bail.
|
||||
# This leaves the sonarr package in an incomplete state.
|
||||
|
||||
@@ -1,17 +1,9 @@
|
||||
#!/usr/bin/make -f
|
||||
# -*- makefile -*-
|
||||
# Sample debian/rules that uses debhelper.
|
||||
# This file was originally written by Joey Hess and Craig Small.
|
||||
# As a special exception, when this file is copied by dh-make into a
|
||||
# dh-make output file, you may use that output file without restriction.
|
||||
# This special exception was added by Craig Small in version 0.37 of dh-make.
|
||||
|
||||
# Uncomment this to turn on verbose mode.
|
||||
#export DH_VERBOSE=1
|
||||
|
||||
# Note: System.Native is a dependency of System.Runtime.InteropServices.RuntimeInformation used by SharpRaven,
|
||||
# but SharpRaven doesn't use any functions that need System.Native
|
||||
EXCLUDE_MODULEREFS = crypt32 httpapi System.Native
|
||||
EXCLUDE_MODULEREFS = crypt32 httpapi __Internal
|
||||
|
||||
%:
|
||||
dh $@ --with=systemd --with=cli
|
||||
@@ -20,7 +12,7 @@ EXCLUDE_MODULEREFS = crypt32 httpapi System.Native
|
||||
override_dh_installinit:
|
||||
true
|
||||
|
||||
# Sonarr like debug symbols for logging
|
||||
# Sonarr likes debug symbols for logging
|
||||
override_dh_clistrip:
|
||||
|
||||
override_dh_makeclilibs:
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
recommends libcurl3
|
||||
ignores msbuild
|
||||
ignores msbuild
|
||||
ignores libmediainfo0v5
|
||||
|
||||
23
distribution/docker-build/Dockerfile
Normal file
23
distribution/docker-build/Dockerfile
Normal file
@@ -0,0 +1,23 @@
|
||||
FROM ubuntu:xenial AS builder
|
||||
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
ENV MONO_VERSION 5.18
|
||||
|
||||
RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF && \
|
||||
echo "deb http://download.mono-project.com/repo/debian stable-xenial/snapshots/$MONO_VERSION main" > /etc/apt/sources.list.d/mono-official-stable.list && \
|
||||
apt-get update && apt-get install -y \
|
||||
devscripts build-essential tofrodos \
|
||||
dh-make dh-systemd \
|
||||
cli-common-dev \
|
||||
mono-complete \
|
||||
sqlite3 libcurl3 mediainfo
|
||||
|
||||
RUN apt-cache policy mono-complete
|
||||
RUN apt-cache policy cli-common-dev
|
||||
|
||||
COPY debian-start.sh /debian-start.sh
|
||||
RUN fromdos /debian-start.sh
|
||||
|
||||
WORKDIR /data
|
||||
VOLUME [ "/data/sonarr_bin", "/data/build", "/data/output" ]
|
||||
CMD /debian-start.sh
|
||||
18
distribution/docker-build/debian-start.sh
Normal file
18
distribution/docker-build/debian-start.sh
Normal file
@@ -0,0 +1,18 @@
|
||||
echo "Debian Build Dev bootstrap..."
|
||||
|
||||
export TEST_OUTPUT=/data/output
|
||||
|
||||
mkdir ${TEST_OUTPUT}
|
||||
|
||||
mkdir /data/temp
|
||||
|
||||
cp -rf /data/build/debian.sh /data/temp
|
||||
cp -rf /data/build/debian /data/temp
|
||||
cp -rf /data/sonarr_bin /data/temp/sonarr_bin
|
||||
|
||||
cd /data/temp
|
||||
|
||||
ls -al .
|
||||
|
||||
fromdos debian.sh
|
||||
sh debian.sh
|
||||
22
docker/tests/mono/complete/Dockerfile
Normal file
22
docker/tests/mono/complete/Dockerfile
Normal file
@@ -0,0 +1,22 @@
|
||||
FROM ubuntu:xenial
|
||||
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
ARG MONO_VERSION=5.20
|
||||
ARG MONO_URL=stable-xenial/snapshots/$MONO_VERSION
|
||||
|
||||
RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF && \
|
||||
echo "deb http://download.mono-project.com/repo/debian $MONO_URL main" > /etc/apt/sources.list.d/mono-official-stable.list && \
|
||||
apt-get update && apt-get install -y \
|
||||
tofrodos tzdata \
|
||||
mono-complete \
|
||||
sqlite3 mediainfo \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY startup.sh /startup.sh
|
||||
RUN fromdos /startup.sh
|
||||
|
||||
WORKDIR /data/
|
||||
VOLUME ["/data/_tests_linux", "/data/_output_linux", "/data/_tests_results"]
|
||||
|
||||
CMD bash /startup.sh
|
||||
|
||||
29
docker/tests/mono/sonarr/Dockerfile
Normal file
29
docker/tests/mono/sonarr/Dockerfile
Normal file
@@ -0,0 +1,29 @@
|
||||
FROM ubuntu:xenial
|
||||
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
ARG MONO_VERSION=5.20
|
||||
ARG MONO_URL=stable-xenial/snapshots/$MONO_VERSION
|
||||
|
||||
RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF && \
|
||||
echo "deb http://download.mono-project.com/repo/debian $MONO_URL main" > /etc/apt/sources.list.d/mono-official-stable.list && \
|
||||
apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 2009837CBFFD68F45BC180471F4F90DE2A9B4BF8 && \
|
||||
echo "deb http://apt.sonarr.tv/ubuntu xenial main" > /etc/apt/sources.list.d/sonarr.list && \
|
||||
apt-get update && apt-get install -y \
|
||||
tofrodos tzdata \
|
||||
sonarr \
|
||||
sqlite3 mediainfo \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
libmono-system-runtime4.0-cil \
|
||||
libmono-system-net-http4.0-cil \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY startup.sh /startup.sh
|
||||
RUN fromdos /startup.sh
|
||||
|
||||
WORKDIR /data/
|
||||
VOLUME ["/data/_tests_linux", "/data/_output_linux", "/data/_tests_results"]
|
||||
|
||||
CMD bash /startup.sh
|
||||
|
||||
15
docker/tests/mono/startup.sh
Normal file
15
docker/tests/mono/startup.sh
Normal file
@@ -0,0 +1,15 @@
|
||||
echo "Preparing Test..."
|
||||
mkdir -p /data/test
|
||||
cp -r /data/_tests_linux/* /data/test/
|
||||
cp -r /data/_output_linux /data/test/bin
|
||||
|
||||
cd /data/test
|
||||
|
||||
runTest()
|
||||
{
|
||||
bash test.sh Linux $1
|
||||
cp TestResult.xml /data/_tests_results/TestResult_$1.xml
|
||||
}
|
||||
|
||||
runTest Integration
|
||||
runTest Unit
|
||||
121
docker/tests/run-all.sh
Normal file
121
docker/tests/run-all.sh
Normal file
@@ -0,0 +1,121 @@
|
||||
|
||||
opt_parallel=
|
||||
opt_version=
|
||||
opt_mode=both
|
||||
while getopts 'pv:m:r?h' c
|
||||
do
|
||||
case $c in
|
||||
p) opt_parallel=1 ;;
|
||||
v) opt_version=$OPTARG ;;
|
||||
m) opt_mode=$OPTARG ;;
|
||||
r) opt_report=1 ;;
|
||||
?|h) printf "Usage: %s [-p] [-v mono-ver] [-m sonarr|complete]\n" $0
|
||||
printf " -p run parallel\n"
|
||||
printf " -v run specified mono version\n"
|
||||
printf " -m run only mono-'complete' or 'sonarr' package variants\n"
|
||||
printf " -r only report\n"
|
||||
exit 2
|
||||
esac
|
||||
done
|
||||
# NOTE:
|
||||
# each container has a 1gb tmpfs mounted since it greatly speeds up the normally intensive db operations
|
||||
# make sure that the docker host has enough memory to handle about ~300 MB per container, so 2-3 GB total
|
||||
# excess goes to the swap and will slow down the entire system
|
||||
|
||||
MONO_VERSIONS=""
|
||||
|
||||
# Future versions
|
||||
MONO_VERSIONS="$MONO_VERSIONS 6.8=preview-xenial"
|
||||
|
||||
# Semi-Supported versions
|
||||
MONO_VERSIONS="$MONO_VERSIONS 6.6 6.4 6.0"
|
||||
|
||||
# Supported versions
|
||||
MONO_VERSIONS="$MONO_VERSIONS 5.20 5.18"
|
||||
|
||||
# Legacy unsupported versions (but appear to work)
|
||||
MONO_VERSIONS="$MONO_VERSIONS 5.16 5.14 5.12"
|
||||
|
||||
# Legacy unsupported versions
|
||||
MONO_VERSIONS="$MONO_VERSIONS 5.10 5.8 5.4 5.0"
|
||||
#MONO_VERSIONS="$MONO_VERSIONS 4.8=stable-wheezy/snapshots/4.8"
|
||||
|
||||
if [ "$opt_version" != "" ]; then
|
||||
MONO_VERSIONS="$opt_version"
|
||||
fi
|
||||
|
||||
mkdir -p ${PWD}/../../_tests_results
|
||||
|
||||
prepOne() {
|
||||
local MONO_VERSION_PAIR=$1
|
||||
|
||||
MONO_VERSION_SPLIT=(${MONO_VERSION_PAIR//=/ })
|
||||
MONO_VERSION=${MONO_VERSION_SPLIT[0]}
|
||||
MONO_URL=${MONO_VERSION_SPLIT[1]:-"stable-xenial/snapshots/$MONO_VERSION"}
|
||||
|
||||
echo "Building Test Docker for mono $MONO_VERSION"
|
||||
|
||||
if [ "$opt_mode" != "sonarr" ]; then
|
||||
docker build -t sonarr-test-$MONO_VERSION --build-arg MONO_VERSION=$MONO_VERSION --build-arg MONO_URL=$MONO_URL --file mono/complete/Dockerfile mono
|
||||
fi
|
||||
|
||||
if [ "$opt_mode" != "complete" ] && [ "$MONO_VERSION" != "5.0" ]; then
|
||||
docker build -t sonarr-test-$MONO_VERSION-sonarr --build-arg MONO_VERSION=$MONO_VERSION --build-arg MONO_URL=$MONO_URL --file mono/sonarr/Dockerfile mono
|
||||
fi
|
||||
}
|
||||
|
||||
runOne() {
|
||||
local MONO_VERSION_PAIR=$1
|
||||
|
||||
MONO_VERSION_SPLIT=(${MONO_VERSION_PAIR//=/ })
|
||||
MONO_VERSION=${MONO_VERSION_SPLIT[0]}
|
||||
|
||||
echo "Running Test Docker for mono $MONO_VERSION"
|
||||
|
||||
if [ "$opt_mode" != "sonarr" ]; then
|
||||
dockerArgs="--rm"
|
||||
dockerArgs="$dockerArgs -v /${PWD}/../../_tests_linux:/data/_tests_linux:ro"
|
||||
dockerArgs="$dockerArgs -v /${PWD}/../../_output_linux:/data/_output_linux:ro"
|
||||
dockerArgs="$dockerArgs -v /${PWD}/../../_tests_results/mono-$MONO_VERSION:/data/_tests_results"
|
||||
dockerArgs="$dockerArgs --mount type=tmpfs,destination=//data/test,tmpfs-size=1g"
|
||||
docker run $dockerArgs sonarr-test-$MONO_VERSION
|
||||
fi
|
||||
|
||||
if [ "$opt_mode" != "complete" ] && [ "$MONO_VERSION" != "5.0" ]; then
|
||||
dockerArgs="--rm"
|
||||
dockerArgs="$dockerArgs -v /${PWD}/../../_tests_linux:/data/_tests_linux:ro"
|
||||
dockerArgs="$dockerArgs -v /${PWD}/../../_output_linux:/data/_output_linux:ro"
|
||||
dockerArgs="$dockerArgs -v /${PWD}/../../_tests_results/mono-$MONO_VERSION-sonarr:/data/_tests_results"
|
||||
dockerArgs="$dockerArgs --mount type=tmpfs,destination=//data/test,tmpfs-size=1g"
|
||||
docker run $dockerArgs sonarr-test-$MONO_VERSION-sonarr
|
||||
fi
|
||||
|
||||
echo "Finished Test Docker for mono $MONO_VERSION"
|
||||
}
|
||||
|
||||
if [ "$opt_report" != "1" ]; then
|
||||
|
||||
if [ "$opt_parallel" == "1" ]; then
|
||||
for MONO_VERSION_PAIR in $MONO_VERSIONS; do
|
||||
prepOne "$MONO_VERSION_PAIR"
|
||||
done
|
||||
fi
|
||||
|
||||
for MONO_VERSION_PAIR in $MONO_VERSIONS; do
|
||||
if [ "$opt_parallel" == "1" ]; then
|
||||
runOne "$MONO_VERSION_PAIR" &
|
||||
else
|
||||
prepOne "$MONO_VERSION_PAIR"
|
||||
runOne "$MONO_VERSION_PAIR"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$opt_parallel" == "1" ]; then
|
||||
echo "Waiting for all runs to finish"
|
||||
wait
|
||||
echo "Finished all runs"
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
grep "<test-run" ../../_tests_results/**/*.xml | sed -r 's/.*?mono-([0-9.]+(-s)?).*?_([IU]).*?\.xml.*?failed="([0-9]*)".*/\1\t\3:\tfailed \4/g' | sort -V -t.
|
||||
@@ -28,6 +28,12 @@
|
||||
"react"
|
||||
],
|
||||
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "detect"
|
||||
}
|
||||
},
|
||||
|
||||
"rules": {
|
||||
"filenames/match-exported": ["error"],
|
||||
|
||||
@@ -209,7 +215,6 @@
|
||||
"lines-around-comment": ["error", { "beforeBlockComment": true, "afterBlockComment": false }],
|
||||
"max-depth": ["error", {"maximum": 5}],
|
||||
"max-nested-callbacks": ["error", 4],
|
||||
"max-params": ["error", 6],
|
||||
"max-statements": "off",
|
||||
"max-statements-per-line": ["error", { "max": 1 }],
|
||||
"new-cap": ["error", {"capIsNewExceptions": ["$.Deferred", "DragDropContext", "DragLayer", "DragSource", "DropTarget"]}],
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"ignoreAtRules": [
|
||||
"/^add\\-mixin$/",
|
||||
"/^define\\-mixin$/"
|
||||
]
|
||||
]
|
||||
}
|
||||
],
|
||||
"at-rule-no-vendor-prefix": true,
|
||||
|
||||
35
frontend/babel.config.js
Normal file
35
frontend/babel.config.js
Normal file
@@ -0,0 +1,35 @@
|
||||
const loose = true;
|
||||
|
||||
module.exports = {
|
||||
plugins: [
|
||||
// Stage 1
|
||||
'@babel/plugin-proposal-export-default-from',
|
||||
['@babel/plugin-proposal-optional-chaining', { loose }],
|
||||
['@babel/plugin-proposal-nullish-coalescing-operator', { loose }],
|
||||
|
||||
// Stage 2
|
||||
'@babel/plugin-proposal-export-namespace-from',
|
||||
|
||||
// Stage 3
|
||||
['@babel/plugin-proposal-class-properties', { loose }],
|
||||
'@babel/plugin-syntax-dynamic-import'
|
||||
],
|
||||
env: {
|
||||
development: {
|
||||
presets: [
|
||||
['@babel/preset-react', { development: true }]
|
||||
],
|
||||
plugins: [
|
||||
'babel-plugin-inline-classnames'
|
||||
]
|
||||
},
|
||||
production: {
|
||||
presets: [
|
||||
'@babel/preset-react'
|
||||
],
|
||||
plugins: [
|
||||
'babel-plugin-transform-react-remove-prop-types'
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,15 +1,18 @@
|
||||
const gulp = require('gulp');
|
||||
const runSequence = require('run-sequence');
|
||||
|
||||
require('./clean');
|
||||
require('./copy');
|
||||
require('./webpack');
|
||||
|
||||
gulp.task('build',
|
||||
gulp.series('clean',
|
||||
gulp.parallel(
|
||||
'webpack',
|
||||
'copyHtml',
|
||||
'copyFonts',
|
||||
'copyImages',
|
||||
'copyJs'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
gulp.task('build', () => {
|
||||
return runSequence('clean', [
|
||||
'webpack',
|
||||
'copyHtml',
|
||||
'copyFonts',
|
||||
'copyImages',
|
||||
'copyJs'
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
var path = require('path');
|
||||
var gulp = require('gulp');
|
||||
var print = require('gulp-print').default;
|
||||
var cache = require('gulp-cached');
|
||||
var livereload = require('gulp-livereload');
|
||||
var paths = require('./helpers/paths.js');
|
||||
const path = require('path');
|
||||
const gulp = require('gulp');
|
||||
const print = require('gulp-print').default;
|
||||
const cache = require('gulp-cached');
|
||||
const livereload = require('gulp-livereload');
|
||||
const paths = require('./helpers/paths.js');
|
||||
|
||||
gulp.task('copyJs', () => {
|
||||
return gulp.src(
|
||||
[
|
||||
path.join(paths.src.root, 'polyfills.js')
|
||||
])
|
||||
], { base: paths.src.root })
|
||||
.pipe(cache('copyJs'))
|
||||
.pipe(print())
|
||||
.pipe(gulp.dest(paths.dest.root))
|
||||
@@ -17,7 +17,7 @@ gulp.task('copyJs', () => {
|
||||
});
|
||||
|
||||
gulp.task('copyHtml', () => {
|
||||
return gulp.src(paths.src.html)
|
||||
return gulp.src(paths.src.html, { base: paths.src.root })
|
||||
.pipe(cache('copyHtml'))
|
||||
.pipe(print())
|
||||
.pipe(gulp.dest(paths.dest.root))
|
||||
@@ -26,20 +26,20 @@ gulp.task('copyHtml', () => {
|
||||
|
||||
gulp.task('copyFonts', () => {
|
||||
return gulp.src(
|
||||
path.join(paths.src.fonts, '**', '*.*')
|
||||
path.join(paths.src.fonts, '**', '*.*'), { base: paths.src.root }
|
||||
)
|
||||
.pipe(cache('copyFonts'))
|
||||
.pipe(print())
|
||||
.pipe(gulp.dest(paths.dest.fonts))
|
||||
.pipe(gulp.dest(paths.dest.root))
|
||||
.pipe(livereload());
|
||||
});
|
||||
|
||||
gulp.task('copyImages', () => {
|
||||
return gulp.src(
|
||||
path.join(paths.src.images, '**', '*.*')
|
||||
path.join(paths.src.images, '**', '*.*'), { base: paths.src.root }
|
||||
)
|
||||
.pipe(cache('copyImages'))
|
||||
.pipe(print())
|
||||
.pipe(gulp.dest(paths.dest.images))
|
||||
.pipe(gulp.dest(paths.dest.root))
|
||||
.pipe(livereload());
|
||||
});
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
require('./build.js');
|
||||
require('./clean.js');
|
||||
require('./copy.js');
|
||||
require('./imageMin.js');
|
||||
require('./start.js');
|
||||
require('./stripBom.js');
|
||||
require('./watch.js');
|
||||
require('./webpack.js');
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const gulpUtil = require('gulp-util');
|
||||
const colors = require('ansi-colors');
|
||||
|
||||
module.exports = function errorHandler(error) {
|
||||
gulpUtil.log(gulpUtil.colors.red(`Error (${error.plugin}): ${error.message}`));
|
||||
console.log(colors.red(`Error (${error.plugin}): ${error.message}`));
|
||||
this.emit('end');
|
||||
};
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
const path = require('path');
|
||||
const rootPath = path.resolve(__dirname + '/../../src/');
|
||||
module.exports = function(source) {
|
||||
if (this.cacheable) {
|
||||
this.cacheable();
|
||||
}
|
||||
|
||||
const resourcePath = this.resourcePath.replace(rootPath, '');
|
||||
const wrappedSource =`
|
||||
<!-- begin ${resourcePath} -->
|
||||
${source}
|
||||
<!-- end ${resourcePath} -->`;
|
||||
|
||||
return wrappedSource;
|
||||
};
|
||||
@@ -1,15 +1,15 @@
|
||||
const root = './frontend/src/';
|
||||
const root = './frontend/src';
|
||||
|
||||
const paths = {
|
||||
src: {
|
||||
root,
|
||||
html: root + '*.html',
|
||||
scripts: root + '**/*.js',
|
||||
content: root + 'Content/',
|
||||
fonts: root + 'Content/Fonts/',
|
||||
images: root + 'Content/Images/',
|
||||
html: `${root}/*.html`,
|
||||
scripts: `${root}/**/*.js`,
|
||||
content: `${root}/Content/`,
|
||||
fonts: `${root}/Content/Fonts/`,
|
||||
images: `${root}/Content/Images/`,
|
||||
exclude: {
|
||||
libs: `!${root}JsLibraries/**`
|
||||
libs: `!${root}/JsLibraries/**`
|
||||
}
|
||||
},
|
||||
dest: {
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
var gulp = require('gulp');
|
||||
var print = require('gulp-print').default;
|
||||
var paths = require('./helpers/paths.js');
|
||||
|
||||
gulp.task('imageMin', () => {
|
||||
var imagemin = require('gulp-imagemin');
|
||||
return gulp.src(paths.src.images)
|
||||
.pipe(imagemin({
|
||||
progressive: false,
|
||||
optimizationLevel: 4,
|
||||
svgoPlugins: [{ removeViewBox: false }]
|
||||
}))
|
||||
.pipe(print())
|
||||
.pipe(gulp.dest(paths.src.content + 'Images/'));
|
||||
});
|
||||
@@ -1,104 +0,0 @@
|
||||
// will download and run sonarr (server) in a non-windows enviroment
|
||||
// you can use this if you don't care about the server code and just want to work
|
||||
// with the web code.
|
||||
|
||||
var http = require('http');
|
||||
var gulp = require('gulp');
|
||||
var fs = require('fs');
|
||||
var targz = require('tar.gz');
|
||||
var del = require('del');
|
||||
var spawn = require('child_process').spawn;
|
||||
|
||||
function download(url, dest, cb) {
|
||||
console.log('Downloading ' + url + ' to ' + dest);
|
||||
var file = fs.createWriteStream(dest);
|
||||
http.get(url, function(response) {
|
||||
response.pipe(file);
|
||||
file.on('finish', function() {
|
||||
console.log('Download completed');
|
||||
file.close(cb);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getLatest(cb) {
|
||||
var branch = 'develop';
|
||||
process.argv.forEach(function(val) {
|
||||
var branchMatch = /branch=([\S]*)/.exec(val);
|
||||
if (branchMatch && branchMatch.length > 1) {
|
||||
branch = branchMatch[1];
|
||||
}
|
||||
});
|
||||
|
||||
var url = 'http://services.sonarr.tv/v1/update/' + branch + '?os=osx';
|
||||
|
||||
console.log('Checking for latest version:', url);
|
||||
|
||||
http.get(url, function(res) {
|
||||
var data = '';
|
||||
|
||||
res.on('data', function(chunk) {
|
||||
data += chunk;
|
||||
});
|
||||
|
||||
res.on('end', function() {
|
||||
var updatePackage = JSON.parse(data).updatePackage;
|
||||
console.log('Latest version available: ' + updatePackage.version + ' Release Date: ' + updatePackage.releaseDate);
|
||||
cb(updatePackage);
|
||||
});
|
||||
}).on('error', function(e) {
|
||||
console.log('problem with request: ' + e.message);
|
||||
});
|
||||
}
|
||||
|
||||
function extract(source, dest, cb) {
|
||||
console.log('extracting download page to ' + dest);
|
||||
new targz().extract(source, dest, function(err) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
}
|
||||
console.log('Update package extracted.');
|
||||
cb();
|
||||
});
|
||||
}
|
||||
|
||||
gulp.task('getSonarr', function() {
|
||||
try {
|
||||
fs.mkdirSync('./_start/');
|
||||
} catch (e) {
|
||||
if (e.code !== 'EEXIST') {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
getLatest(function(updatePackage) {
|
||||
var packagePath = './_start/' + updatePackage.filename;
|
||||
var dirName = './_start/' + updatePackage.version;
|
||||
download(updatePackage.url, packagePath, function() {
|
||||
extract(packagePath, dirName, function() {
|
||||
// clean old binaries
|
||||
console.log('Cleaning old binaries');
|
||||
del.sync(['./_output/*', '!./_output/UI/']);
|
||||
console.log('copying binaries to target');
|
||||
gulp.src(dirName + '/NzbDrone/*.*')
|
||||
.pipe(gulp.dest('./_output/'));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task('startSonarr', function() {
|
||||
var ls = spawn('mono', ['--debug', './_output/NzbDrone.exe']);
|
||||
|
||||
ls.stdout.on('data', function(data) {
|
||||
process.stdout.write(data);
|
||||
});
|
||||
|
||||
ls.stderr.on('data', function(data) {
|
||||
process.stdout.write(data);
|
||||
});
|
||||
|
||||
ls.on('close', function(code) {
|
||||
console.log('child process exited with code ' + code);
|
||||
});
|
||||
});
|
||||
@@ -1,13 +0,0 @@
|
||||
const gulp = require('gulp');
|
||||
const paths = require('./helpers/paths.js');
|
||||
const stripbom = require('gulp-stripbom');
|
||||
|
||||
function stripBom(dest) {
|
||||
gulp.src([paths.src.scripts, paths.src.exclude.libs])
|
||||
.pipe(stripbom({ showLog: false }))
|
||||
.pipe(gulp.dest(dest));
|
||||
}
|
||||
|
||||
gulp.task('stripBom', () => {
|
||||
stripBom(paths.src.root);
|
||||
});
|
||||
@@ -1,27 +1,18 @@
|
||||
const gulp = require('gulp');
|
||||
const livereload = require('gulp-livereload');
|
||||
const watch = require('gulp-watch');
|
||||
const gulpWatch = require('gulp-watch');
|
||||
const paths = require('./helpers/paths.js');
|
||||
|
||||
require('./copy.js');
|
||||
require('./webpack.js');
|
||||
|
||||
function watchTask(glob, task) {
|
||||
const options = {
|
||||
name: `watch: ${task}`,
|
||||
verbose: true
|
||||
};
|
||||
return watch(glob, options, () => {
|
||||
gulp.start(task);
|
||||
});
|
||||
}
|
||||
|
||||
gulp.task('watch', ['copyHtml', 'copyFonts', 'copyImages', 'copyJs'], () => {
|
||||
function watch() {
|
||||
livereload.listen({ start: true });
|
||||
|
||||
gulp.start('webpackWatch');
|
||||
gulp.task('webpackWatch')();
|
||||
gulpWatch(paths.src.html, gulp.series('copyHtml'));
|
||||
gulpWatch(`${paths.src.fonts}**/*.*`, gulp.series('copyFonts'));
|
||||
gulpWatch(`${paths.src.images}**/*.*`, gulp.series('copyImages'));
|
||||
}
|
||||
|
||||
watchTask(paths.src.html, 'copyHtml');
|
||||
watchTask(`${paths.src.fonts}**/*.*`, 'copyFonts');
|
||||
watchTask(`${paths.src.images}**/*.*`, 'copyImages');
|
||||
});
|
||||
gulp.task('watch', gulp.series('build', watch));
|
||||
|
||||
@@ -4,57 +4,62 @@ const livereload = require('gulp-livereload');
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const errorHandler = require('./helpers/errorHandler');
|
||||
const ExtractTextPlugin = require('extract-text-webpack-plugin');
|
||||
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
|
||||
const uiFolder = 'UI';
|
||||
const root = path.join(__dirname, '..', 'src');
|
||||
const frontendFolder = path.join(__dirname, '..');
|
||||
const srcFolder = path.join(frontendFolder, 'src');
|
||||
const isProduction = process.argv.indexOf('--production') > -1;
|
||||
|
||||
console.log('ROOT:', root);
|
||||
const distFolder = path.resolve(frontendFolder, '..', '_output', uiFolder);
|
||||
|
||||
console.log('Source Folder:', srcFolder);
|
||||
console.log('Output Folder:', distFolder);
|
||||
console.log('isProduction:', isProduction);
|
||||
|
||||
const cssVarsFiles = [
|
||||
'../src/Styles/Variables/colors',
|
||||
'../src/Styles/Variables/dimensions',
|
||||
'../src/Styles/Variables/fonts',
|
||||
'../src/Styles/Variables/animations'
|
||||
'../src/Styles/Variables/animations',
|
||||
'../src/Styles/Variables/zIndexes'
|
||||
].map(require.resolve);
|
||||
|
||||
const extractCSSPlugin = new ExtractTextPlugin({
|
||||
filename: path.join('_output', uiFolder, 'Content', 'styles.css'),
|
||||
allChunks: true,
|
||||
disable: false,
|
||||
ignoreOrder: true
|
||||
});
|
||||
// Override the way HtmlWebpackPlugin injects the scripts
|
||||
HtmlWebpackPlugin.prototype.injectAssetsIntoHtml = function(html, assets, assetTags) {
|
||||
const head = assetTags.head.map((v) => {
|
||||
v.attributes = { rel: 'stylesheet', type: 'text/css', href: `/${v.attributes.href.replace('\\', '/')}` };
|
||||
return this.createHtmlTag(v);
|
||||
});
|
||||
const body = assetTags.body.map((v) => {
|
||||
v.attributes = { src: `/${v.attributes.src}` };
|
||||
return this.createHtmlTag(v);
|
||||
});
|
||||
|
||||
return html
|
||||
.replace('<!-- webpack bundles head -->', head.join('\r\n '))
|
||||
.replace('<!-- webpack bundles body -->', body.join('\r\n '));
|
||||
};
|
||||
|
||||
const plugins = [
|
||||
extractCSSPlugin,
|
||||
|
||||
new webpack.optimize.CommonsChunkPlugin({
|
||||
name: 'vendor'
|
||||
}),
|
||||
|
||||
new webpack.DefinePlugin({
|
||||
__DEV__: !isProduction,
|
||||
'process.env.NODE_ENV': isProduction ? JSON.stringify('production') : JSON.stringify('development')
|
||||
}),
|
||||
|
||||
new MiniCssExtractPlugin({
|
||||
filename: path.join('Content', 'styles.css')
|
||||
}),
|
||||
|
||||
new HtmlWebpackPlugin({
|
||||
template: 'frontend/src/index.html',
|
||||
filename: 'index.html'
|
||||
})
|
||||
];
|
||||
|
||||
if (isProduction) {
|
||||
plugins.push(new UglifyJSPlugin({
|
||||
sourceMap: true,
|
||||
uglifyOptions: {
|
||||
mangle: false,
|
||||
output: {
|
||||
comments: false,
|
||||
beautify: true
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
const config = {
|
||||
mode: isProduction ? 'production' : 'development',
|
||||
devtool: '#source-map',
|
||||
|
||||
stats: {
|
||||
@@ -66,15 +71,13 @@ const config = {
|
||||
},
|
||||
|
||||
entry: {
|
||||
preload: 'preload.js',
|
||||
vendor: 'vendor.js',
|
||||
index: 'index.js'
|
||||
},
|
||||
|
||||
resolve: {
|
||||
modules: [
|
||||
root,
|
||||
path.join(root, 'Shims'),
|
||||
srcFolder,
|
||||
path.join(srcFolder, 'Shims'),
|
||||
'node_modules'
|
||||
],
|
||||
alias: {
|
||||
@@ -83,10 +86,22 @@ const config = {
|
||||
},
|
||||
|
||||
output: {
|
||||
filename: path.join('_output', uiFolder, '[name].js'),
|
||||
path: distFolder,
|
||||
filename: '[name].js',
|
||||
sourceMapFilename: '[file].map'
|
||||
},
|
||||
|
||||
optimization: {
|
||||
chunkIds: 'named',
|
||||
splitChunks: {
|
||||
chunks: 'initial'
|
||||
}
|
||||
},
|
||||
|
||||
performance: {
|
||||
hints: false
|
||||
},
|
||||
|
||||
plugins,
|
||||
|
||||
resolveLoader: {
|
||||
@@ -101,53 +116,57 @@ const config = {
|
||||
{
|
||||
test: /\.js?$/,
|
||||
exclude: /(node_modules|JsLibraries)/,
|
||||
loader: 'babel-loader',
|
||||
query: {
|
||||
plugins: ['transform-class-properties'],
|
||||
presets: ['es2015', 'decorators-legacy', 'react', 'stage-2'],
|
||||
env: {
|
||||
development: {
|
||||
plugins: ['transform-react-jsx-source']
|
||||
use: [
|
||||
{
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
configFile: `${frontendFolder}/babel.config.js`,
|
||||
envName: isProduction ? 'production' : 'development',
|
||||
presets: [
|
||||
[
|
||||
'@babel/preset-env',
|
||||
{
|
||||
modules: false,
|
||||
loose: true,
|
||||
debug: false,
|
||||
useBuiltIns: 'entry',
|
||||
corejs: 3
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// CSS Modules
|
||||
{
|
||||
test: /\.css$/,
|
||||
exclude: /(node_modules|globals.css)/,
|
||||
use: extractCSSPlugin.extract({
|
||||
fallback: 'style-loader',
|
||||
use: [
|
||||
{
|
||||
loader: 'css-variables-loader',
|
||||
options: {
|
||||
cssVarsFiles
|
||||
}
|
||||
},
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
modules: true,
|
||||
importLoaders: 1,
|
||||
localIdentName: '[name]-[local]-[hash:base64:5]',
|
||||
sourceMap: true
|
||||
}
|
||||
},
|
||||
{
|
||||
loader: 'postcss-loader',
|
||||
options: {
|
||||
config: {
|
||||
ctx: {
|
||||
cssVarsFiles
|
||||
},
|
||||
path: 'frontend/postcss.config.js'
|
||||
}
|
||||
use: [
|
||||
{ loader: MiniCssExtractPlugin.loader },
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
importLoaders: 1,
|
||||
modules: {
|
||||
localIdentName: '[name]/[local]/[hash:base64:5]'
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
},
|
||||
{
|
||||
loader: 'postcss-loader',
|
||||
options: {
|
||||
ident: 'postcss',
|
||||
config: {
|
||||
ctx: {
|
||||
cssVarsFiles
|
||||
},
|
||||
path: 'frontend/postcss.config.js'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// Global styles
|
||||
@@ -195,17 +214,16 @@ const config = {
|
||||
};
|
||||
|
||||
gulp.task('webpack', () => {
|
||||
return gulp.src('index.js')
|
||||
.pipe(webpackStream(config))
|
||||
.pipe(gulp.dest(''));
|
||||
return webpackStream(config)
|
||||
.pipe(gulp.dest('_output/UI'));
|
||||
});
|
||||
|
||||
gulp.task('webpackWatch', () => {
|
||||
config.watch = true;
|
||||
return gulp.src('')
|
||||
.pipe(webpackStream(config))
|
||||
|
||||
return webpackStream(config)
|
||||
.on('error', errorHandler)
|
||||
.pipe(gulp.dest(''))
|
||||
.pipe(gulp.dest('_output/UI'))
|
||||
.on('error', errorHandler)
|
||||
.pipe(livereload())
|
||||
.on('error', errorHandler);
|
||||
|
||||
@@ -14,18 +14,8 @@ module.exports = (ctx, configPath, options) => {
|
||||
return Object.assign(acc, reload(vars));
|
||||
}, {})
|
||||
},
|
||||
'postcss-nested': {},
|
||||
autoprefixer: {
|
||||
browsers: [
|
||||
'Chrome >= 30',
|
||||
'Firefox >= 30',
|
||||
'Safari >= 6',
|
||||
'Edge >= 12',
|
||||
'Explorer >= 11',
|
||||
'iOS >= 7',
|
||||
'Android >= 4.4'
|
||||
]
|
||||
}
|
||||
'postcss-color-function': {},
|
||||
'postcss-nested': {}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import { align, icons } from 'Helpers/Props';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
||||
import TablePager from 'Components/Table/TablePager';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBodyConnector from 'Components/Page/PageContentBodyConnector';
|
||||
@@ -41,6 +42,18 @@ class Blacklist extends Component {
|
||||
onPress={onClearBlacklistPress}
|
||||
/>
|
||||
</PageToolbarSection>
|
||||
|
||||
<PageToolbarSection alignContent={align.RIGHT}>
|
||||
<TableOptionsModalWrapper
|
||||
{...otherProps}
|
||||
columns={columns}
|
||||
>
|
||||
<PageToolbarButton
|
||||
label="Options"
|
||||
iconName={icons.TABLE}
|
||||
/>
|
||||
</TableOptionsModalWrapper>
|
||||
</PageToolbarSection>
|
||||
</PageToolbar>
|
||||
|
||||
<PageContentBodyConnector>
|
||||
|
||||
@@ -105,6 +105,14 @@ class BlacklistConnector extends Component {
|
||||
this.props.executeCommand({ name: commandNames.CLEAR_BLACKLIST });
|
||||
}
|
||||
|
||||
onTableOptionChange = (payload) => {
|
||||
this.props.setBlacklistTableOption(payload);
|
||||
|
||||
if (payload.pageSize) {
|
||||
this.props.gotoBlacklistFirstPage();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
@@ -126,6 +134,7 @@ class BlacklistConnector extends Component {
|
||||
}
|
||||
|
||||
BlacklistConnector.propTypes = {
|
||||
useCurrentPage: PropTypes.bool.isRequired,
|
||||
isClearingBlacklistExecuting: PropTypes.bool.isRequired,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
fetchBlacklist: PropTypes.func.isRequired,
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
.language,
|
||||
.quality {
|
||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.indexer {
|
||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 70px;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.description {
|
||||
composes: title from 'Components/DescriptionList/DescriptionListItemDescription.css';
|
||||
composes: description from '~Components/DescriptionList/DescriptionListItemDescription.css';
|
||||
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
@@ -231,6 +231,16 @@ function HistoryDetails(props) {
|
||||
</DescriptionList>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title="Name"
|
||||
data={sourceTitle}
|
||||
/>
|
||||
</DescriptionList>
|
||||
);
|
||||
}
|
||||
|
||||
HistoryDetails.propTypes = {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.markAsFailedButton {
|
||||
composes: button from 'Components/Link/Button.css';
|
||||
composes: button from '~Components/Link/Button.css';
|
||||
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
||||
import TablePager from 'Components/Table/TablePager';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBodyConnector from 'Components/Page/PageContentBodyConnector';
|
||||
@@ -75,6 +76,16 @@ class History extends Component {
|
||||
</PageToolbarSection>
|
||||
|
||||
<PageToolbarSection alignContent={align.RIGHT}>
|
||||
<TableOptionsModalWrapper
|
||||
{...otherProps}
|
||||
columns={columns}
|
||||
>
|
||||
<PageToolbarButton
|
||||
label="Options"
|
||||
iconName={icons.TABLE}
|
||||
/>
|
||||
</TableOptionsModalWrapper>
|
||||
|
||||
<FilterMenu
|
||||
alignMenu={align.RIGHT}
|
||||
selectedFilterKey={selectedFilterKey}
|
||||
|
||||
@@ -8,6 +8,7 @@ import selectUniqueIds from 'Utilities/Object/selectUniqueIds';
|
||||
import withCurrentPage from 'Components/withCurrentPage';
|
||||
import * as historyActions from 'Store/Actions/historyActions';
|
||||
import { fetchEpisodes, clearEpisodes } from 'Store/Actions/episodeActions';
|
||||
import { clearEpisodeFiles } from 'Store/Actions/episodeFileActions';
|
||||
import History from './History';
|
||||
|
||||
function createMapStateToProps() {
|
||||
@@ -28,7 +29,8 @@ function createMapStateToProps() {
|
||||
const mapDispatchToProps = {
|
||||
...historyActions,
|
||||
fetchEpisodes,
|
||||
clearEpisodes
|
||||
clearEpisodes,
|
||||
clearEpisodeFiles
|
||||
};
|
||||
|
||||
class HistoryConnector extends Component {
|
||||
@@ -68,6 +70,7 @@ class HistoryConnector extends Component {
|
||||
unregisterPagePopulator(this.repopulate);
|
||||
this.props.clearHistory();
|
||||
this.props.clearEpisodes();
|
||||
this.props.clearEpisodeFiles();
|
||||
}
|
||||
|
||||
//
|
||||
@@ -137,6 +140,7 @@ class HistoryConnector extends Component {
|
||||
}
|
||||
|
||||
HistoryConnector.propTypes = {
|
||||
useCurrentPage: PropTypes.bool.isRequired,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
fetchHistory: PropTypes.func.isRequired,
|
||||
gotoHistoryFirstPage: PropTypes.func.isRequired,
|
||||
@@ -149,7 +153,8 @@ HistoryConnector.propTypes = {
|
||||
setHistoryTableOption: PropTypes.func.isRequired,
|
||||
clearHistory: PropTypes.func.isRequired,
|
||||
fetchEpisodes: PropTypes.func.isRequired,
|
||||
clearEpisodes: PropTypes.func.isRequired
|
||||
clearEpisodes: PropTypes.func.isRequired,
|
||||
clearEpisodeFiles: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default withCurrentPage(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.cell {
|
||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 35px;
|
||||
text-align: center;
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
.downloadClient {
|
||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.indexer {
|
||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.releaseGroup {
|
||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 110px;
|
||||
}
|
||||
|
||||
.details {
|
||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
.torrent {
|
||||
composes: label from 'Components/Label.css';
|
||||
composes: label from '~Components/Label.css';
|
||||
|
||||
border-color: $torrentColor;
|
||||
background-color: $torrentColor;
|
||||
}
|
||||
|
||||
.usenet {
|
||||
composes: label from 'Components/Label.css';
|
||||
composes: label from '~Components/Label.css';
|
||||
|
||||
border-color: $usenetColor;
|
||||
background-color: $usenetColor;
|
||||
|
||||
@@ -3,9 +3,10 @@ import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
|
||||
import getSelectedIds from 'Utilities/Table/getSelectedIds';
|
||||
import removeOldSelectedState from 'Utilities/Table/removeOldSelectedState';
|
||||
import selectAll from 'Utilities/Table/selectAll';
|
||||
import toggleSelected from 'Utilities/Table/toggleSelected';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import { align, icons } from 'Helpers/Props';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
@@ -16,6 +17,7 @@ import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
|
||||
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
||||
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
|
||||
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
||||
import RemoveQueueItemsModal from './RemoveQueueItemsModal';
|
||||
import QueueOptionsConnector from './QueueOptionsConnector';
|
||||
import QueueRowConnector from './QueueRowConnector';
|
||||
@@ -43,22 +45,27 @@ class Queue extends Component {
|
||||
// before episodes start fetching or when episodes start fetching.
|
||||
|
||||
if (
|
||||
(
|
||||
this.props.isFetching &&
|
||||
nextProps.isPopulated &&
|
||||
hasDifferentItems(this.props.items, nextProps.items)
|
||||
) ||
|
||||
(!this.props.isEpisodesFetching && nextProps.isEpisodesFetching)
|
||||
this.props.isFetching &&
|
||||
nextProps.isPopulated &&
|
||||
hasDifferentItems(this.props.items, nextProps.items) &&
|
||||
nextProps.items.some((e) => e.episodeId)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.props.isEpisodesFetching && nextProps.isEpisodesFetching) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (hasDifferentItems(prevProps.items, this.props.items)) {
|
||||
this.setState({ selectedState: {} });
|
||||
this.setState((state) => {
|
||||
return removeOldSelectedState(state, prevProps.items);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -139,7 +146,7 @@ class Queue extends Component {
|
||||
} = this.state;
|
||||
|
||||
const isRefreshing = isFetching || isEpisodesFetching || isCheckForFinishedDownloadExecuting;
|
||||
const isAllPopulated = isPopulated && (isEpisodesPopulated || !items.length);
|
||||
const isAllPopulated = isPopulated && (isEpisodesPopulated || !items.length || items.every((e) => !e.episodeId));
|
||||
const hasError = error || episodesError;
|
||||
const selectedCount = this.getSelectedIds().length;
|
||||
const disableSelectedActions = selectedCount === 0;
|
||||
@@ -173,6 +180,21 @@ class Queue extends Component {
|
||||
onPress={this.onRemoveSelectedPress}
|
||||
/>
|
||||
</PageToolbarSection>
|
||||
|
||||
<PageToolbarSection
|
||||
alignContent={align.RIGHT}
|
||||
>
|
||||
<TableOptionsModalWrapper
|
||||
columns={columns}
|
||||
{...otherProps}
|
||||
optionsComponent={QueueOptionsConnector}
|
||||
>
|
||||
<PageToolbarButton
|
||||
label="Options"
|
||||
iconName={icons.TABLE}
|
||||
/>
|
||||
</TableOptionsModalWrapper>
|
||||
</PageToolbarSection>
|
||||
</PageToolbar>
|
||||
|
||||
<PageContentBodyConnector>
|
||||
|
||||
@@ -164,6 +164,8 @@ class QueueConnector extends Component {
|
||||
}
|
||||
|
||||
QueueConnector.propTypes = {
|
||||
includeUnknownSeriesItems: PropTypes.bool.isRequired,
|
||||
useCurrentPage: PropTypes.bool.isRequired,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
fetchQueue: PropTypes.func.isRequired,
|
||||
gotoQueueFirstPage: PropTypes.func.isRequired,
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
.quality {
|
||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.protocol {
|
||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.progress {
|
||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 70px;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
|
||||
import ProtocolLabel from 'Activity/Queue/ProtocolLabel';
|
||||
import EpisodeTitleLink from 'Episode/EpisodeTitleLink';
|
||||
import EpisodeLanguage from 'Episode/EpisodeLanguage';
|
||||
import EpisodeQuality from 'Episode/EpisodeQuality';
|
||||
import SeasonEpisodeNumber from 'Episode/SeasonEpisodeNumber';
|
||||
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
|
||||
@@ -71,9 +72,11 @@ class QueueRow extends Component {
|
||||
errorMessage,
|
||||
series,
|
||||
episode,
|
||||
language,
|
||||
quality,
|
||||
protocol,
|
||||
indexer,
|
||||
outputPath,
|
||||
downloadClient,
|
||||
estimatedCompletionTime,
|
||||
timeleft,
|
||||
@@ -204,6 +207,16 @@ class QueueRow extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'language') {
|
||||
return (
|
||||
<TableRowCell key={name}>
|
||||
<EpisodeLanguage
|
||||
language={language}
|
||||
/>
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'quality') {
|
||||
return (
|
||||
<TableRowCell key={name}>
|
||||
@@ -240,6 +253,22 @@ class QueueRow extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'title') {
|
||||
return (
|
||||
<TableRowCell key={name}>
|
||||
{title}
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'outputPath') {
|
||||
return (
|
||||
<TableRowCell key={name}>
|
||||
{outputPath}
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'estimatedCompletionTime') {
|
||||
return (
|
||||
<TimeleftCell
|
||||
@@ -340,9 +369,11 @@ QueueRow.propTypes = {
|
||||
errorMessage: PropTypes.string,
|
||||
series: PropTypes.object,
|
||||
episode: PropTypes.object,
|
||||
language: PropTypes.object.isRequired,
|
||||
quality: PropTypes.object.isRequired,
|
||||
protocol: PropTypes.string.isRequired,
|
||||
indexer: PropTypes.string,
|
||||
outputPath: PropTypes.string,
|
||||
downloadClient: PropTypes.string,
|
||||
estimatedCompletionTime: PropTypes.string,
|
||||
timeleft: PropTypes.string,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.status {
|
||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
@@ -116,6 +116,7 @@ function QueueStatusCell(props) {
|
||||
title={title}
|
||||
body={hasWarning || hasError ? getDetailedPopoverBody(statusMessages) : sourceTitle}
|
||||
position={tooltipPositions.RIGHT}
|
||||
canFlip={false}
|
||||
/>
|
||||
</TableRowCell>
|
||||
);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
.message {
|
||||
.messageRemove {
|
||||
margin-bottom: 30px;
|
||||
color: $dangerColor;
|
||||
}
|
||||
|
||||
@@ -69,10 +69,14 @@ class RemoveQueueItemModal extends Component {
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<div className={styles.message}>
|
||||
<div>
|
||||
Are you sure you want to remove '{sourceTitle}' from the queue?
|
||||
</div>
|
||||
|
||||
<div className={styles.messageRemove}>
|
||||
Removing will remove the download and the file(s) from the download client.
|
||||
</div>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>Blacklist Release</FormLabel>
|
||||
<FormInputGroup
|
||||
|
||||
@@ -12,8 +12,12 @@ function createMapStateToProps() {
|
||||
(state) => state.queue.options.includeUnknownSeriesItems,
|
||||
(app, status, includeUnknownSeriesItems) => {
|
||||
const {
|
||||
errors,
|
||||
warnings,
|
||||
unknownErrors,
|
||||
unknownWarnings,
|
||||
count,
|
||||
unknownCount
|
||||
totalCount
|
||||
} = status.item;
|
||||
|
||||
return {
|
||||
@@ -21,7 +25,9 @@ function createMapStateToProps() {
|
||||
isReconnecting: app.isReconnecting,
|
||||
isPopulated: status.isPopulated,
|
||||
...status.item,
|
||||
count: includeUnknownSeriesItems ? count : count - unknownCount
|
||||
count: includeUnknownSeriesItems ? totalCount : count,
|
||||
errors: includeUnknownSeriesItems ? errors || unknownErrors : errors,
|
||||
warnings: includeUnknownSeriesItems ? warnings || unknownWarnings : warnings
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.timeleft {
|
||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
}
|
||||
|
||||
.searchInput {
|
||||
composes: input from 'Components/Form/TextInput.css';
|
||||
composes: input from '~Components/Form/TextInput.css';
|
||||
|
||||
height: 46px;
|
||||
border-radius: 0;
|
||||
@@ -35,14 +35,20 @@
|
||||
.message {
|
||||
margin-top: 30px;
|
||||
text-align: center;
|
||||
font-weight: 300;
|
||||
font-size: $largeFontSize;
|
||||
}
|
||||
|
||||
.helpText {
|
||||
margin-bottom: 10px;
|
||||
font-weight: 300;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.noSeriesText {
|
||||
margin-top: 80px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.noResults {
|
||||
margin-bottom: 10px;
|
||||
font-weight: 300;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
||||
import { icons, kinds } from 'Helpers/Props';
|
||||
import Button from 'Components/Link/Button';
|
||||
import Link from 'Components/Link/Link';
|
||||
import Icon from 'Components/Icon';
|
||||
@@ -78,7 +79,8 @@ class AddNewSeries extends Component {
|
||||
render() {
|
||||
const {
|
||||
error,
|
||||
items
|
||||
items,
|
||||
hasExistingSeries
|
||||
} = this.props;
|
||||
|
||||
const term = this.state.term;
|
||||
@@ -100,6 +102,7 @@ class AddNewSeries extends Component {
|
||||
name="seriesLookup"
|
||||
value={term}
|
||||
placeholder="eg. Breaking Bad, tvdb:####"
|
||||
autoFocus={true}
|
||||
onChange={this.onSearchInputChange}
|
||||
/>
|
||||
|
||||
@@ -120,8 +123,13 @@ class AddNewSeries extends Component {
|
||||
}
|
||||
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
<div>Failed to load search results, please try again.</div>
|
||||
!isFetching && !!error ?
|
||||
<div className={styles.message}>
|
||||
<div className={styles.helpText}>
|
||||
Failed to load search results, please try again.
|
||||
</div>
|
||||
<div>{getErrorMessage(error)}</div>
|
||||
</div> : null
|
||||
}
|
||||
|
||||
{
|
||||
@@ -154,13 +162,34 @@ class AddNewSeries extends Component {
|
||||
}
|
||||
|
||||
{
|
||||
!term &&
|
||||
term ?
|
||||
null :
|
||||
<div className={styles.message}>
|
||||
<div className={styles.helpText}>It's easy to add a new series, just start typing the name the series you want to add.</div>
|
||||
<div className={styles.helpText}>
|
||||
It's easy to add a new series, just start typing the name the series you want to add.
|
||||
</div>
|
||||
<div>You can also search using TVDB ID of a show. eg. tvdb:71663</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
!term && !hasExistingSeries ?
|
||||
<div className={styles.message}>
|
||||
<div className={styles.noSeriesText}>
|
||||
You haven't added any series yet, do you want to import some or all of your series first?
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
to="/add/import"
|
||||
kind={kinds.PRIMARY}
|
||||
>
|
||||
Import Existing Series
|
||||
</Button>
|
||||
</div>
|
||||
</div> :
|
||||
null
|
||||
}
|
||||
|
||||
<div />
|
||||
</PageContentBodyConnector>
|
||||
</PageContent>
|
||||
@@ -175,6 +204,7 @@ AddNewSeries.propTypes = {
|
||||
isAdding: PropTypes.bool.isRequired,
|
||||
addError: PropTypes.object,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
hasExistingSeries: PropTypes.bool.isRequired,
|
||||
onSeriesLookupChange: PropTypes.func.isRequired,
|
||||
onClearSeriesLookup: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
@@ -10,13 +10,15 @@ import AddNewSeries from './AddNewSeries';
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.addSeries,
|
||||
(state) => state.routing.location,
|
||||
(addSeries, location) => {
|
||||
(state) => state.series.items.length,
|
||||
(state) => state.router.location,
|
||||
(addSeries, existingSeriesCount, location) => {
|
||||
const { params } = parseUrl(location.search);
|
||||
|
||||
return {
|
||||
...addSeries,
|
||||
term: params.term,
|
||||
...addSeries
|
||||
hasExistingSeries: existingSeriesCount > 0
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
@@ -36,28 +36,28 @@
|
||||
}
|
||||
|
||||
.searchForMissingEpisodesContainer {
|
||||
composes: container from 'Components/Form/CheckInput.css';
|
||||
composes: container from '~Components/Form/CheckInput.css';
|
||||
|
||||
flex: 0 1 0;
|
||||
}
|
||||
|
||||
.searchForMissingEpisodesInput {
|
||||
composes: input from 'Components/Form/CheckInput.css';
|
||||
composes: input from '~Components/Form/CheckInput.css';
|
||||
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.modalFooter {
|
||||
composes: modalFooter from 'Components/Modal/ModalFooter.css';
|
||||
composes: modalFooter from '~Components/Modal/ModalFooter.css';
|
||||
}
|
||||
|
||||
.addButton {
|
||||
@add-mixin truncate;
|
||||
composes: button from 'Components/Link/SpinnerButton.css';
|
||||
composes: button from '~Components/Link/SpinnerButton.css';
|
||||
}
|
||||
|
||||
.hideLanguageProfile {
|
||||
composes: group from 'Components/Form/FormGroup.css';
|
||||
composes: group from '~Components/Form/FormGroup.css';
|
||||
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -66,11 +66,14 @@ class AddNewSeriesModalContent extends Component {
|
||||
languageProfileId,
|
||||
seriesType,
|
||||
seasonFolder,
|
||||
folder,
|
||||
tags,
|
||||
showLanguageProfile,
|
||||
isSmallScreen,
|
||||
isWindows,
|
||||
onModalClose,
|
||||
onInputChange
|
||||
onInputChange,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
@@ -87,7 +90,8 @@ class AddNewSeriesModalContent extends Component {
|
||||
<ModalBody>
|
||||
<div className={styles.container}>
|
||||
{
|
||||
!isSmallScreen &&
|
||||
isSmallScreen ?
|
||||
null :
|
||||
<div className={styles.poster}>
|
||||
<SeriesPoster
|
||||
className={styles.poster}
|
||||
@@ -98,17 +102,30 @@ class AddNewSeriesModalContent extends Component {
|
||||
}
|
||||
|
||||
<div className={styles.info}>
|
||||
<div className={styles.overview}>
|
||||
{overview}
|
||||
</div>
|
||||
{
|
||||
overview ?
|
||||
<div className={styles.overview}>
|
||||
{overview}
|
||||
</div> :
|
||||
null
|
||||
}
|
||||
|
||||
<Form>
|
||||
<Form {...otherProps}>
|
||||
<FormGroup>
|
||||
<FormLabel>Root Folder</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.ROOT_FOLDER_SELECT}
|
||||
name="rootFolderPath"
|
||||
valueOptions={{
|
||||
seriesFolder: folder,
|
||||
isWindows
|
||||
}}
|
||||
selectedValueOptions={{
|
||||
seriesFolder: folder,
|
||||
isWindows
|
||||
}}
|
||||
helpText={`'${folder}' subfolder will be created automatically`}
|
||||
onChange={onInputChange}
|
||||
{...rootFolderPath}
|
||||
/>
|
||||
@@ -254,9 +271,11 @@ AddNewSeriesModalContent.propTypes = {
|
||||
languageProfileId: PropTypes.object,
|
||||
seriesType: PropTypes.object.isRequired,
|
||||
seasonFolder: PropTypes.object.isRequired,
|
||||
folder: PropTypes.string.isRequired,
|
||||
tags: PropTypes.object.isRequired,
|
||||
showLanguageProfile: PropTypes.bool.isRequired,
|
||||
isSmallScreen: PropTypes.bool.isRequired,
|
||||
isWindows: PropTypes.bool.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired,
|
||||
onInputChange: PropTypes.func.isRequired,
|
||||
onAddSeriesPress: PropTypes.func.isRequired
|
||||
|
||||
@@ -4,6 +4,7 @@ import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { setAddSeriesDefault, addSeries } from 'Store/Actions/addSeriesActions';
|
||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||
import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector';
|
||||
import selectSettings from 'Store/Selectors/selectSettings';
|
||||
import AddNewSeriesModalContent from './AddNewSeriesModalContent';
|
||||
|
||||
@@ -12,7 +13,8 @@ function createMapStateToProps() {
|
||||
(state) => state.addSeries,
|
||||
(state) => state.settings.languageProfiles,
|
||||
createDimensionsSelector(),
|
||||
(addSeriesState, languageProfiles, dimensions) => {
|
||||
createSystemStatusSelector(),
|
||||
(addSeriesState, languageProfiles, dimensions, systemStatus) => {
|
||||
const {
|
||||
isAdding,
|
||||
addError,
|
||||
@@ -32,6 +34,7 @@ function createMapStateToProps() {
|
||||
isSmallScreen: dimensions.isSmallScreen,
|
||||
validationErrors,
|
||||
validationWarnings,
|
||||
isWindows: systemStatus.isWindows,
|
||||
...settings
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
.searchResult {
|
||||
display: flex;
|
||||
position: relative;
|
||||
margin: 20px 0;
|
||||
padding: 20px;
|
||||
width: 100%;
|
||||
background-color: $white;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.underlay {
|
||||
@add-mixin cover;
|
||||
|
||||
background-color: $white;
|
||||
transition: background 500ms;
|
||||
|
||||
&:hover {
|
||||
@@ -14,13 +19,25 @@
|
||||
}
|
||||
}
|
||||
|
||||
.overlay {
|
||||
@add-mixin linkOverlay;
|
||||
|
||||
position: relative;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.poster {
|
||||
flex: 0 0 170px;
|
||||
margin-right: 20px;
|
||||
height: 250px;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 0 1 100%;
|
||||
}
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
font-weight: 300;
|
||||
font-size: 36px;
|
||||
}
|
||||
@@ -30,6 +47,18 @@
|
||||
color: $disabledColor;
|
||||
}
|
||||
|
||||
.tvdbLink {
|
||||
composes: link from '~Components/Link/Link.css';
|
||||
|
||||
margin-top: -4px;
|
||||
margin-left: auto;
|
||||
color: $textColor;
|
||||
}
|
||||
|
||||
.tvdbLinkIcon {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.alreadyExistsIcon {
|
||||
margin-left: 10px;
|
||||
color: #37bc9b;
|
||||
|
||||
@@ -24,7 +24,7 @@ class AddNewSeriesSearchResult extends Component {
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (!prevProps.isExistingSeries && this.props.isExistingSeries) {
|
||||
this.onAddSerisModalClose();
|
||||
this.onAddSeriesModalClose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,10 +35,14 @@ class AddNewSeriesSearchResult extends Component {
|
||||
this.setState({ isNewAddSeriesModalOpen: true });
|
||||
}
|
||||
|
||||
onAddSerisModalClose = () => {
|
||||
onAddSeriesModalClose = () => {
|
||||
this.setState({ isNewAddSeriesModalOpen: false });
|
||||
}
|
||||
|
||||
onTVDBLinkPress = (event) => {
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
@@ -53,6 +57,7 @@ class AddNewSeriesSearchResult extends Component {
|
||||
overview,
|
||||
statistics,
|
||||
ratings,
|
||||
folder,
|
||||
images,
|
||||
isExistingSeries,
|
||||
isSmallScreen
|
||||
@@ -72,38 +77,58 @@ class AddNewSeriesSearchResult extends Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.searchResult}>
|
||||
<Link
|
||||
className={styles.searchResult}
|
||||
className={styles.underlay}
|
||||
{...linkProps}
|
||||
>
|
||||
/>
|
||||
|
||||
<div className={styles.overlay}>
|
||||
{
|
||||
!isSmallScreen &&
|
||||
<SeriesPoster
|
||||
className={styles.poster}
|
||||
images={images}
|
||||
size={250}
|
||||
/>
|
||||
isSmallScreen ?
|
||||
null :
|
||||
<SeriesPoster
|
||||
className={styles.poster}
|
||||
images={images}
|
||||
size={250}
|
||||
overflow={true}
|
||||
/>
|
||||
}
|
||||
|
||||
<div>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.title}>
|
||||
{title}
|
||||
|
||||
{
|
||||
!title.contains(year) && !!year &&
|
||||
<span className={styles.year}>({year})</span>
|
||||
!title.contains(year) && year ?
|
||||
<span className={styles.year}>
|
||||
({year})
|
||||
</span> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
isExistingSeries &&
|
||||
<Icon
|
||||
className={styles.alreadyExistsIcon}
|
||||
name={icons.CHECK_CIRCLE}
|
||||
size={36}
|
||||
title="Already in your library"
|
||||
/>
|
||||
isExistingSeries ?
|
||||
<Icon
|
||||
className={styles.alreadyExistsIcon}
|
||||
name={icons.CHECK_CIRCLE}
|
||||
size={36}
|
||||
title="Already in your library"
|
||||
/> :
|
||||
null
|
||||
}
|
||||
|
||||
<Link
|
||||
className={styles.tvdbLink}
|
||||
to={`http://www.thetvdb.com/?tab=series&id=${tvdbId}`}
|
||||
onPress={this.onTVDBLinkPress}
|
||||
>
|
||||
<Icon
|
||||
className={styles.tvdbLinkIcon}
|
||||
name={icons.EXTERNAL_LINK}
|
||||
size={28}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@@ -115,27 +140,30 @@ class AddNewSeriesSearchResult extends Component {
|
||||
</Label>
|
||||
|
||||
{
|
||||
!!network &&
|
||||
<Label size={sizes.LARGE}>
|
||||
{network}
|
||||
</Label>
|
||||
network ?
|
||||
<Label size={sizes.LARGE}>
|
||||
{network}
|
||||
</Label> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
!!seasonCount &&
|
||||
<Label size={sizes.LARGE}>
|
||||
{seasons}
|
||||
</Label>
|
||||
seasonCount ?
|
||||
<Label size={sizes.LARGE}>
|
||||
{seasons}
|
||||
</Label> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
status === 'ended' &&
|
||||
<Label
|
||||
kind={kinds.DANGER}
|
||||
size={sizes.LARGE}
|
||||
>
|
||||
status === 'ended' ?
|
||||
<Label
|
||||
kind={kinds.DANGER}
|
||||
size={sizes.LARGE}
|
||||
>
|
||||
Ended
|
||||
</Label>
|
||||
</Label> :
|
||||
null
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -143,7 +171,7 @@ class AddNewSeriesSearchResult extends Component {
|
||||
{overview}
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<AddNewSeriesModal
|
||||
isOpen={isNewAddSeriesModalOpen && !isExistingSeries}
|
||||
@@ -151,8 +179,9 @@ class AddNewSeriesSearchResult extends Component {
|
||||
title={title}
|
||||
year={year}
|
||||
overview={overview}
|
||||
folder={folder}
|
||||
images={images}
|
||||
onModalClose={this.onAddSerisModalClose}
|
||||
onModalClose={this.onAddSeriesModalClose}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -169,6 +198,7 @@ AddNewSeriesSearchResult.propTypes = {
|
||||
overview: PropTypes.string,
|
||||
statistics: PropTypes.object.isRequired,
|
||||
ratings: PropTypes.object.isRequired,
|
||||
folder: PropTypes.string.isRequired,
|
||||
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
isExistingSeries: PropTypes.bool.isRequired,
|
||||
isSmallScreen: PropTypes.bool.isRequired
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
}
|
||||
|
||||
.importButton {
|
||||
composes: button from 'Components/Link/SpinnerButton.css';
|
||||
composes: button from '~Components/Link/SpinnerButton.css';
|
||||
|
||||
height: 35px;
|
||||
}
|
||||
@@ -26,7 +26,7 @@
|
||||
}
|
||||
|
||||
.loading {
|
||||
composes: loading from 'Components/Loading/LoadingIndicator.css';
|
||||
composes: loading from '~Components/Loading/LoadingIndicator.css';
|
||||
|
||||
margin: 0 10px 0 12px;
|
||||
text-align: left;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
.folder {
|
||||
composes: headerCell from 'Components/Table/VirtualTableHeaderCell.css';
|
||||
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
|
||||
|
||||
flex: 1 0 200px;
|
||||
}
|
||||
|
||||
.monitor {
|
||||
composes: headerCell from 'Components/Table/VirtualTableHeaderCell.css';
|
||||
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
|
||||
|
||||
flex: 0 1 200px;
|
||||
min-width: 185px;
|
||||
@@ -13,28 +13,28 @@
|
||||
|
||||
.qualityProfile,
|
||||
.languageProfile {
|
||||
composes: headerCell from 'Components/Table/VirtualTableHeaderCell.css';
|
||||
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
|
||||
|
||||
flex: 0 1 250px;
|
||||
min-width: 170px;
|
||||
}
|
||||
|
||||
.seriesType {
|
||||
composes: headerCell from 'Components/Table/VirtualTableHeaderCell.css';
|
||||
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
|
||||
|
||||
flex: 0 1 200px;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.seasonFolder {
|
||||
composes: headerCell from 'Components/Table/VirtualTableHeaderCell.css';
|
||||
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
|
||||
|
||||
flex: 0 1 150px;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.series {
|
||||
composes: headerCell from 'Components/Table/VirtualTableHeaderCell.css';
|
||||
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
|
||||
|
||||
flex: 0 1 400px;
|
||||
min-width: 300px;
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
.selectInput {
|
||||
composes: input from 'Components/Form/CheckInput.css';
|
||||
composes: input from '~Components/Form/CheckInput.css';
|
||||
}
|
||||
|
||||
.folder {
|
||||
composes: cell from 'Components/Table/Cells/VirtualTableRowCell.css';
|
||||
composes: cell from '~Components/Table/Cells/VirtualTableRowCell.css';
|
||||
|
||||
flex: 1 0 200px;
|
||||
line-height: 36px;
|
||||
}
|
||||
|
||||
.monitor {
|
||||
composes: cell from 'Components/Table/Cells/VirtualTableRowCell.css';
|
||||
composes: cell from '~Components/Table/Cells/VirtualTableRowCell.css';
|
||||
|
||||
flex: 0 1 200px;
|
||||
min-width: 185px;
|
||||
@@ -18,35 +18,35 @@
|
||||
|
||||
.qualityProfile,
|
||||
.languageProfile {
|
||||
composes: cell from 'Components/Table/Cells/VirtualTableRowCell.css';
|
||||
composes: cell from '~Components/Table/Cells/VirtualTableRowCell.css';
|
||||
|
||||
flex: 0 1 250px;
|
||||
min-width: 170px;
|
||||
}
|
||||
|
||||
.seriesType {
|
||||
composes: cell from 'Components/Table/Cells/VirtualTableRowCell.css';
|
||||
composes: cell from '~Components/Table/Cells/VirtualTableRowCell.css';
|
||||
|
||||
flex: 0 1 200px;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.seasonFolder {
|
||||
composes: cell from 'Components/Table/Cells/VirtualTableRowCell.css';
|
||||
composes: cell from '~Components/Table/Cells/VirtualTableRowCell.css';
|
||||
|
||||
flex: 0 1 150px;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.series {
|
||||
composes: cell from 'Components/Table/Cells/VirtualTableRowCell.css';
|
||||
composes: cell from '~Components/Table/Cells/VirtualTableRowCell.css';
|
||||
|
||||
flex: 0 1 400px;
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
.hideLanguageProfile {
|
||||
composes: cell from 'Components/Table/Cells/VirtualTableRowCell.css';
|
||||
composes: cell from '~Components/Table/Cells/VirtualTableRowCell.css';
|
||||
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
.input {
|
||||
composes: input from 'Components/Form/CheckInput.css';
|
||||
composes: input from '~Components/Form/CheckInput.css';
|
||||
}
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
.tether {
|
||||
z-index: 2000;
|
||||
}
|
||||
|
||||
.button {
|
||||
composes: link from 'Components/Link/Link.css';
|
||||
composes: link from '~Components/Link/Link.css';
|
||||
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 6px 16px;
|
||||
@@ -36,9 +31,10 @@
|
||||
}
|
||||
|
||||
.contentContainer {
|
||||
z-index: $popperZIndex;
|
||||
margin-top: 4px;
|
||||
padding: 0 8px;
|
||||
width: 400px;
|
||||
/* 400px container witdh with 8px padding on each side */
|
||||
width: 384px;
|
||||
}
|
||||
|
||||
.content {
|
||||
@@ -65,7 +61,7 @@
|
||||
}
|
||||
|
||||
.searchInput {
|
||||
composes: input from 'Components/Form/TextInput.css';
|
||||
composes: input from '~Components/Form/TextInput.css';
|
||||
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import TetherComponent from 'react-tether';
|
||||
import { Manager, Popper, Reference } from 'react-popper';
|
||||
import getUniqueElememtId from 'Utilities/getUniqueElementId';
|
||||
import { icons, kinds } from 'Helpers/Props';
|
||||
import Icon from 'Components/Icon';
|
||||
import Portal from 'Components/Portal';
|
||||
import FormInputButton from 'Components/Form/FormInputButton';
|
||||
import Link from 'Components/Link/Link';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
@@ -12,19 +13,6 @@ import ImportSeriesSearchResultConnector from './ImportSeriesSearchResultConnect
|
||||
import ImportSeriesTitle from './ImportSeriesTitle';
|
||||
import styles from './ImportSeriesSelectSeries.css';
|
||||
|
||||
const tetherOptions = {
|
||||
skipMoveElement: true,
|
||||
constraints: [
|
||||
{
|
||||
to: 'window',
|
||||
attachment: 'together',
|
||||
pin: true
|
||||
}
|
||||
],
|
||||
attachment: 'top center',
|
||||
targetAttachment: 'bottom center'
|
||||
};
|
||||
|
||||
class ImportSeriesSelectSeries extends Component {
|
||||
|
||||
//
|
||||
@@ -34,6 +22,9 @@ class ImportSeriesSelectSeries extends Component {
|
||||
super(props, context);
|
||||
|
||||
this._seriesLookupTimeout = null;
|
||||
this._scheduleUpdate = null;
|
||||
this._buttonId = getUniqueElememtId();
|
||||
this._contentId = getUniqueElememtId();
|
||||
|
||||
this.state = {
|
||||
term: props.id,
|
||||
@@ -41,17 +32,15 @@ class ImportSeriesSelectSeries extends Component {
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if (this._scheduleUpdate) {
|
||||
this._scheduleUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Control
|
||||
|
||||
_setButtonRef = (ref) => {
|
||||
this._buttonRef = ref;
|
||||
}
|
||||
|
||||
_setContentRef = (ref) => {
|
||||
this._contentRef = ref;
|
||||
}
|
||||
|
||||
_addListener() {
|
||||
window.addEventListener('click', this.onWindowClick);
|
||||
}
|
||||
@@ -64,14 +53,18 @@ class ImportSeriesSelectSeries extends Component {
|
||||
// Listeners
|
||||
|
||||
onWindowClick = (event) => {
|
||||
const button = ReactDOM.findDOMNode(this._buttonRef);
|
||||
const content = ReactDOM.findDOMNode(this._contentRef);
|
||||
const button = document.getElementById(this._buttonId);
|
||||
const content = document.getElementById(this._contentId);
|
||||
|
||||
if (!button) {
|
||||
if (!button || !content) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!button.contains(event.target) && content && !content.contains(event.target) && this.state.isOpen) {
|
||||
if (
|
||||
!button.contains(event.target) &&
|
||||
!content.contains(event.target) &&
|
||||
this.state.isOpen
|
||||
) {
|
||||
this.setState({ isOpen: false });
|
||||
this._removeListener();
|
||||
}
|
||||
@@ -129,129 +122,158 @@ class ImportSeriesSelectSeries extends Component {
|
||||
error.responseJSON.message;
|
||||
|
||||
return (
|
||||
<TetherComponent
|
||||
classes={{
|
||||
element: styles.tether
|
||||
}}
|
||||
{...tetherOptions}
|
||||
>
|
||||
<Link
|
||||
ref={this._setButtonRef}
|
||||
className={styles.button}
|
||||
component="div"
|
||||
onPress={this.onPress}
|
||||
>
|
||||
{
|
||||
isLookingUpSeries && isQueued && !isPopulated &&
|
||||
<LoadingIndicator
|
||||
className={styles.loading}
|
||||
size={20}
|
||||
/>
|
||||
}
|
||||
<Manager>
|
||||
<Reference>
|
||||
{({ ref }) => (
|
||||
<div
|
||||
ref={ref}
|
||||
id={this._buttonId}
|
||||
>
|
||||
<Link
|
||||
ref={ref}
|
||||
className={styles.button}
|
||||
component="div"
|
||||
onPress={this.onPress}
|
||||
>
|
||||
{
|
||||
isLookingUpSeries && isQueued && !isPopulated ?
|
||||
<LoadingIndicator
|
||||
className={styles.loading}
|
||||
size={20}
|
||||
/> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
isPopulated && selectedSeries && isExistingSeries &&
|
||||
<Icon
|
||||
className={styles.warningIcon}
|
||||
name={icons.WARNING}
|
||||
kind={kinds.WARNING}
|
||||
/>
|
||||
}
|
||||
{
|
||||
isPopulated && selectedSeries && isExistingSeries ?
|
||||
<Icon
|
||||
className={styles.warningIcon}
|
||||
name={icons.WARNING}
|
||||
kind={kinds.WARNING}
|
||||
/> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
isPopulated && selectedSeries &&
|
||||
<ImportSeriesTitle
|
||||
title={selectedSeries.title}
|
||||
year={selectedSeries.year}
|
||||
network={selectedSeries.network}
|
||||
isExistingSeries={isExistingSeries}
|
||||
/>
|
||||
}
|
||||
{
|
||||
isPopulated && selectedSeries ?
|
||||
<ImportSeriesTitle
|
||||
title={selectedSeries.title}
|
||||
year={selectedSeries.year}
|
||||
network={selectedSeries.network}
|
||||
isExistingSeries={isExistingSeries}
|
||||
/> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
isPopulated && !selectedSeries &&
|
||||
<div className={styles.noMatches}>
|
||||
<Icon
|
||||
className={styles.warningIcon}
|
||||
name={icons.WARNING}
|
||||
kind={kinds.WARNING}
|
||||
/>
|
||||
{
|
||||
isPopulated && !selectedSeries ?
|
||||
<div className={styles.noMatches}>
|
||||
<Icon
|
||||
className={styles.warningIcon}
|
||||
name={icons.WARNING}
|
||||
kind={kinds.WARNING}
|
||||
/>
|
||||
|
||||
No match found!
|
||||
</div>
|
||||
}
|
||||
</div> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
<div>
|
||||
<Icon
|
||||
className={styles.warningIcon}
|
||||
title={errorMessage}
|
||||
name={icons.WARNING}
|
||||
kind={kinds.WARNING}
|
||||
/>
|
||||
{
|
||||
!isFetching && !!error ?
|
||||
<div>
|
||||
<Icon
|
||||
className={styles.warningIcon}
|
||||
title={errorMessage}
|
||||
name={icons.WARNING}
|
||||
kind={kinds.WARNING}
|
||||
/>
|
||||
|
||||
Search failed, please try again later.
|
||||
</div>
|
||||
}
|
||||
</div> :
|
||||
null
|
||||
}
|
||||
|
||||
<div className={styles.dropdownArrowContainer}>
|
||||
<Icon
|
||||
name={icons.CARET_DOWN}
|
||||
/>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
{
|
||||
this.state.isOpen &&
|
||||
<div
|
||||
ref={this._setContentRef}
|
||||
className={styles.contentContainer}
|
||||
>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.searchContainer}>
|
||||
<div className={styles.searchIconContainer}>
|
||||
<Icon name={icons.SEARCH} />
|
||||
</div>
|
||||
|
||||
<TextInput
|
||||
className={styles.searchInput}
|
||||
name={`${name}_textInput`}
|
||||
value={this.state.term}
|
||||
onChange={this.onSearchInputChange}
|
||||
<div className={styles.dropdownArrowContainer}>
|
||||
<Icon
|
||||
name={icons.CARET_DOWN}
|
||||
/>
|
||||
|
||||
<FormInputButton
|
||||
kind={kinds.DEFAULT}
|
||||
spinnerIcon={icons.REFRESH}
|
||||
canSpin={true}
|
||||
isSpinning={isFetching}
|
||||
onPress={this.onRefreshPress}
|
||||
>
|
||||
<Icon name={icons.REFRESH} />
|
||||
</FormInputButton>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</Reference>
|
||||
|
||||
<div className={styles.results}>
|
||||
<Portal>
|
||||
<Popper
|
||||
placement="bottom"
|
||||
modifiers={{
|
||||
preventOverflow: {
|
||||
boundariesElement: 'viewport'
|
||||
}
|
||||
}}
|
||||
>
|
||||
{({ ref, style, scheduleUpdate }) => {
|
||||
this._scheduleUpdate = scheduleUpdate;
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
id={this._contentId}
|
||||
className={styles.contentContainer}
|
||||
style={style}
|
||||
>
|
||||
{
|
||||
items.map((item) => {
|
||||
return (
|
||||
<ImportSeriesSearchResultConnector
|
||||
key={item.tvdbId}
|
||||
tvdbId={item.tvdbId}
|
||||
title={item.title}
|
||||
year={item.year}
|
||||
network={item.network}
|
||||
onPress={this.onSeriesSelect}
|
||||
/>
|
||||
);
|
||||
})
|
||||
this.state.isOpen ?
|
||||
<div className={styles.content}>
|
||||
<div className={styles.searchContainer}>
|
||||
<div className={styles.searchIconContainer}>
|
||||
<Icon name={icons.SEARCH} />
|
||||
</div>
|
||||
|
||||
<TextInput
|
||||
className={styles.searchInput}
|
||||
name={`${name}_textInput`}
|
||||
value={this.state.term}
|
||||
onChange={this.onSearchInputChange}
|
||||
/>
|
||||
|
||||
<FormInputButton
|
||||
kind={kinds.DEFAULT}
|
||||
spinnerIcon={icons.REFRESH}
|
||||
canSpin={true}
|
||||
isSpinning={isFetching}
|
||||
onPress={this.onRefreshPress}
|
||||
>
|
||||
<Icon name={icons.REFRESH} />
|
||||
</FormInputButton>
|
||||
</div>
|
||||
|
||||
<div className={styles.results}>
|
||||
{
|
||||
items.map((item) => {
|
||||
return (
|
||||
<ImportSeriesSearchResultConnector
|
||||
key={item.tvdbId}
|
||||
tvdbId={item.tvdbId}
|
||||
title={item.title}
|
||||
year={item.year}
|
||||
network={item.network}
|
||||
onPress={this.onSeriesSelect}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div> :
|
||||
null
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</TetherComponent>
|
||||
);
|
||||
}}
|
||||
</Popper>
|
||||
</Portal>
|
||||
</Manager>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
.link {
|
||||
composes: link from 'Components/Link/Link.css';
|
||||
|
||||
display: block;
|
||||
}
|
||||
|
||||
.freeSpace,
|
||||
.unmappedFolders {
|
||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 45px;
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { deleteRootFolder } from 'Store/Actions/rootFolderActions';
|
||||
import ImportSeriesRootFolderRow from './ImportSeriesRootFolderRow';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
() => {
|
||||
return {
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
deleteRootFolder
|
||||
};
|
||||
|
||||
class ImportSeriesRootFolderRowConnector extends Component {
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onDeletePress = () => {
|
||||
this.props.deleteRootFolder({ id: this.props.id });
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
return (
|
||||
<ImportSeriesRootFolderRow
|
||||
{...this.props}
|
||||
onDeletePress={this.onDeletePress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ImportSeriesRootFolderRowConnector.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
deleteRootFolder: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(ImportSeriesRootFolderRowConnector);
|
||||
@@ -2,39 +2,15 @@ import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { icons, kinds, sizes } from 'Helpers/Props';
|
||||
import Button from 'Components/Link/Button';
|
||||
import FieldSet from 'Components/FieldSet';
|
||||
import Icon from 'Components/Icon';
|
||||
import FieldSet from 'Components/FieldSet';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import FileBrowserModal from 'Components/FileBrowser/FileBrowserModal';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBodyConnector from 'Components/Page/PageContentBodyConnector';
|
||||
import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import ImportSeriesRootFolderRowConnector from './ImportSeriesRootFolderRowConnector';
|
||||
import RootFolders from 'RootFolder/RootFolders';
|
||||
import styles from './ImportSeriesSelectFolder.css';
|
||||
|
||||
const rootFolderColumns = [
|
||||
{
|
||||
name: 'path',
|
||||
label: 'Path',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'freeSpace',
|
||||
label: 'Free Space',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'unmappedFolders',
|
||||
label: 'Unmapped Folders',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'actions',
|
||||
isVisible: true
|
||||
}
|
||||
];
|
||||
|
||||
class ImportSeriesSelectFolder extends Component {
|
||||
|
||||
//
|
||||
@@ -99,10 +75,10 @@ class ImportSeriesSelectFolder extends Component {
|
||||
Some tips to ensure the import goes smoothly:
|
||||
<ul>
|
||||
<li className={styles.tip}>
|
||||
Make sure your files include the quality in the name. eg. <span className={styles.code}>episode.s02e15.bluray.mkv</span>
|
||||
Make sure that your files include the quality in their filenames. eg. <span className={styles.code}>episode.s02e15.bluray.mkv</span>
|
||||
</li>
|
||||
<li className={styles.tip}>
|
||||
Point Sonarr to the folder containing all of your tv shows not a specific one. eg. <span className={styles.code}>"{isWindows ? 'C:\\tv shows' : '/tv shows'}"</span> and not <span className={styles.code}>"{isWindows ? 'C:\\tv shows\\the simpsons' : '/tv shows/the simpsons'}"</span>
|
||||
Point Sonarr to the folder containing all of your tv shows, not a specific one. eg. <span className={styles.code}>"{isWindows ? 'C:\\tv shows' : '/tv shows'}"</span> and not <span className={styles.code}>"{isWindows ? 'C:\\tv shows\\the simpsons' : '/tv shows/the simpsons'}"</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -110,26 +86,13 @@ class ImportSeriesSelectFolder extends Component {
|
||||
{
|
||||
items.length > 0 ?
|
||||
<div className={styles.recentFolders}>
|
||||
<FieldSet legend="Recent Folders">
|
||||
<Table
|
||||
columns={rootFolderColumns}
|
||||
>
|
||||
<TableBody>
|
||||
{
|
||||
items.map((rootFolder) => {
|
||||
return (
|
||||
<ImportSeriesRootFolderRowConnector
|
||||
key={rootFolder.id}
|
||||
id={rootFolder.id}
|
||||
path={rootFolder.path}
|
||||
freeSpace={rootFolder.freeSpace}
|
||||
unmappedFolders={rootFolder.unmappedFolders}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<FieldSet legend="Root Folders">
|
||||
<RootFolders
|
||||
isFetching={isFetching}
|
||||
isPopulated={isPopulated}
|
||||
error={error}
|
||||
items={items}
|
||||
/>
|
||||
</FieldSet>
|
||||
|
||||
<Button
|
||||
@@ -181,8 +144,7 @@ ImportSeriesSelectFolder.propTypes = {
|
||||
isPopulated: PropTypes.bool.isRequired,
|
||||
error: PropTypes.object,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
onNewRootFolderSelect: PropTypes.func.isRequired,
|
||||
onDeleteRootFolderPress: PropTypes.func.isRequired
|
||||
onNewRootFolderSelect: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default ImportSeriesSelectFolder;
|
||||
|
||||
@@ -3,9 +3,9 @@ import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { push } from 'react-router-redux';
|
||||
import { push } from 'connected-react-router';
|
||||
import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector';
|
||||
import { fetchRootFolders, addRootFolder, deleteRootFolder } from 'Store/Actions/rootFolderActions';
|
||||
import { fetchRootFolders, addRootFolder } from 'Store/Actions/rootFolderActions';
|
||||
import ImportSeriesSelectFolder from './ImportSeriesSelectFolder';
|
||||
|
||||
function createMapStateToProps() {
|
||||
@@ -24,7 +24,6 @@ function createMapStateToProps() {
|
||||
const mapDispatchToProps = {
|
||||
fetchRootFolders,
|
||||
addRootFolder,
|
||||
deleteRootFolder,
|
||||
push
|
||||
};
|
||||
|
||||
@@ -60,10 +59,6 @@ class ImportSeriesSelectFolderConnector extends Component {
|
||||
this.props.addRootFolder({ path });
|
||||
}
|
||||
|
||||
onDeleteRootFolderPress = (id) => {
|
||||
this.props.deleteRootFolder({ id });
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
@@ -72,7 +67,6 @@ class ImportSeriesSelectFolderConnector extends Component {
|
||||
<ImportSeriesSelectFolder
|
||||
{...this.props}
|
||||
onNewRootFolderSelect={this.onNewRootFolderSelect}
|
||||
onDeleteRootFolderPress={this.onDeleteRootFolderPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -84,7 +78,6 @@ ImportSeriesSelectFolderConnector.propTypes = {
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
fetchRootFolders: PropTypes.func.isRequired,
|
||||
addRootFolder: PropTypes.func.isRequired,
|
||||
deleteRootFolder: PropTypes.func.isRequired,
|
||||
push: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import DocumentTitle from 'react-document-title';
|
||||
import { Provider } from 'react-redux';
|
||||
import { ConnectedRouter } from 'react-router-redux';
|
||||
import { ConnectedRouter } from 'connected-react-router';
|
||||
import PageConnector from 'Components/Page/PageConnector';
|
||||
import AppRoutes from './AppRoutes';
|
||||
|
||||
|
||||
6
frontend/src/App/ColorImpairedContext.js
Normal file
6
frontend/src/App/ColorImpairedContext.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import React from 'react';
|
||||
|
||||
const ColorImpairedContext = React.createContext(false);
|
||||
export const ColorImpairedConsumer = ColorImpairedContext.Consumer;
|
||||
|
||||
export default ColorImpairedContext;
|
||||
@@ -63,27 +63,27 @@
|
||||
*/
|
||||
|
||||
.downloaded {
|
||||
composes: downloaded from 'Calendar/Events/CalendarEvent.css';
|
||||
composes: downloaded from '~Calendar/Events/CalendarEvent.css';
|
||||
}
|
||||
|
||||
.downloading {
|
||||
composes: downloading from 'Calendar/Events/CalendarEvent.css';
|
||||
composes: downloading from '~Calendar/Events/CalendarEvent.css';
|
||||
}
|
||||
|
||||
.unmonitored {
|
||||
composes: unmonitored from 'Calendar/Events/CalendarEvent.css';
|
||||
composes: unmonitored from '~Calendar/Events/CalendarEvent.css';
|
||||
}
|
||||
|
||||
.onAir {
|
||||
composes: onAir from 'Calendar/Events/CalendarEvent.css';
|
||||
composes: onAir from '~Calendar/Events/CalendarEvent.css';
|
||||
}
|
||||
|
||||
.missing {
|
||||
composes: missing from 'Calendar/Events/CalendarEvent.css';
|
||||
composes: missing from '~Calendar/Events/CalendarEvent.css';
|
||||
}
|
||||
|
||||
.premiere {
|
||||
composes: premiere from 'Calendar/Events/CalendarEvent.css';
|
||||
composes: premiere from '~Calendar/Events/CalendarEvent.css';
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $breakpointSmall) {
|
||||
|
||||
@@ -175,6 +175,7 @@ class CalendarConnector extends Component {
|
||||
}
|
||||
|
||||
CalendarConnector.propTypes = {
|
||||
useCurrentPage: PropTypes.bool.isRequired,
|
||||
time: PropTypes.string,
|
||||
view: PropTypes.string.isRequired,
|
||||
firstDayOfWeek: PropTypes.number.isRequired,
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
.calendarPageBody {
|
||||
composes: contentBody from 'Components/Page/PageContentBody.css';
|
||||
composes: contentBody from '~Components/Page/PageContentBody.css';
|
||||
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.calendarInnerPageBody {
|
||||
composes: innerContentBody from 'Components/Page/PageContentBody.css';
|
||||
composes: innerContentBody from '~Components/Page/PageContentBody.css';
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -25,7 +25,7 @@ function createMissingEpisodeIdsSelector() {
|
||||
moment(airDateUtc).isAfter(start) &&
|
||||
moment(airDateUtc).isBefore(end) &&
|
||||
isBefore(episode.airDateUtc) &&
|
||||
!queueDetails.some((details) => details.episode.id === episode.id)
|
||||
!queueDetails.some((details) => !!details.episode && details.episode.id === episode.id)
|
||||
) {
|
||||
acc.push(episode.id);
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
flex: 1 0 14.28%;
|
||||
overflow: hidden;
|
||||
min-height: 70px;
|
||||
border-bottom: 1px solid $borderColor;
|
||||
border-left: 1px solid $borderColor;
|
||||
border-bottom: 1px solid $calendarBorderColor;
|
||||
border-left: 1px solid $calendarBorderColor;
|
||||
}
|
||||
|
||||
.isSingleDay {
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
.dayOfMonth {
|
||||
padding-right: 5px;
|
||||
border-bottom: 1px solid $borderColor;
|
||||
border-bottom: 1px solid $calendarBorderColor;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.days {
|
||||
display: flex;
|
||||
border-right: 1px solid $borderColor;
|
||||
border-right: 1px solid $calendarBorderColor;
|
||||
}
|
||||
|
||||
.day,
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
border-bottom: 1px solid $borderColor;
|
||||
border-left: 4px solid $borderColor;
|
||||
font-size: 12px;
|
||||
|
||||
&:global(.colorImpaired) {
|
||||
border-left-width: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.info,
|
||||
@@ -12,10 +16,13 @@
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.episodeInfo {
|
||||
color: $calendarTextDim;
|
||||
}
|
||||
|
||||
.seriesTitle,
|
||||
.episodeTitle {
|
||||
@add-mixin truncate;
|
||||
|
||||
flex: 1 0 1px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
@@ -33,12 +40,20 @@
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
.airTime {
|
||||
color: $calendarTextDim;
|
||||
}
|
||||
|
||||
/*
|
||||
* Status
|
||||
*/
|
||||
|
||||
.downloaded {
|
||||
border-left-color: $successColor !important;
|
||||
|
||||
&:global(.colorImpaired) {
|
||||
border-left-color: color($successColor, saturation(+15%)) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.downloading {
|
||||
@@ -49,7 +64,7 @@
|
||||
border-left-color: $gray !important;
|
||||
|
||||
&:global(.colorImpaired) {
|
||||
background: repeating-linear-gradient(45deg, transparent, transparent 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
|
||||
background: repeating-linear-gradient(45deg, $colorImpairedGradientDark, $colorImpairedGradientDark 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +72,7 @@
|
||||
border-left-color: $warningColor !important;
|
||||
|
||||
&:global(.colorImpaired) {
|
||||
background: repeating-linear-gradient(90deg, transparent, transparent 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
|
||||
background: repeating-linear-gradient(90deg, $colorImpairedGradientDark, $colorImpairedGradientDark 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +80,8 @@
|
||||
border-left-color: $dangerColor !important;
|
||||
|
||||
&:global(.colorImpaired) {
|
||||
background: repeating-linear-gradient(90deg, transparent, transparent 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
|
||||
border-left-color: color($dangerColor saturation(+15%)) !important;
|
||||
background: repeating-linear-gradient(90deg, $colorImpairedGradientDark, $colorImpairedGradientDark 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,6 +89,6 @@
|
||||
border-left-color: $primaryColor !important;
|
||||
|
||||
&:global(.colorImpaired) {
|
||||
background: repeating-linear-gradient(90deg, transparent, transparent 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
|
||||
background: repeating-linear-gradient(90deg, $colorImpairedGradientDark, $colorImpairedGradientDark 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,7 +200,7 @@ class CalendarEvent extends Component {
|
||||
</div>
|
||||
}
|
||||
|
||||
<div>
|
||||
<div className={styles.airTime}>
|
||||
{formatTime(airDateUtc, timeFormat)} - {formatTime(endTime.toISOString(), timeFormat, { includeMinuteZero: true })}
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
.seriesTitle {
|
||||
@add-mixin truncate;
|
||||
|
||||
flex: 1 0 1px;
|
||||
margin-right: 10px;
|
||||
color: #3a3f51;
|
||||
@@ -23,10 +22,12 @@
|
||||
|
||||
.airTime {
|
||||
flex: 1 0 1px;
|
||||
color: $calendarTextDim;
|
||||
}
|
||||
|
||||
.episodeInfo {
|
||||
margin-left: 10px;
|
||||
color: $calendarTextDim;
|
||||
}
|
||||
|
||||
.absoluteEpisodeNumber {
|
||||
@@ -58,25 +59,29 @@
|
||||
*/
|
||||
|
||||
.downloaded {
|
||||
composes: downloaded from 'Calendar/Events/CalendarEvent.css';
|
||||
composes: downloaded from '~Calendar/Events/CalendarEvent.css';
|
||||
}
|
||||
|
||||
.downloading {
|
||||
composes: downloading from 'Calendar/Events/CalendarEvent.css';
|
||||
composes: downloading from '~Calendar/Events/CalendarEvent.css';
|
||||
}
|
||||
|
||||
.unmonitored {
|
||||
composes: unmonitored from 'Calendar/Events/CalendarEvent.css';
|
||||
composes: unmonitored from '~Calendar/Events/CalendarEvent.css';
|
||||
}
|
||||
|
||||
.onAir {
|
||||
composes: onAir from 'Calendar/Events/CalendarEvent.css';
|
||||
composes: onAir from '~Calendar/Events/CalendarEvent.css';
|
||||
}
|
||||
|
||||
.missing {
|
||||
composes: missing from 'Calendar/Events/CalendarEvent.css';
|
||||
composes: missing from '~Calendar/Events/CalendarEvent.css';
|
||||
}
|
||||
|
||||
.premiere {
|
||||
composes: premiere from 'Calendar/Events/CalendarEvent.css';
|
||||
composes: premiere from '~Calendar/Events/CalendarEvent.css';
|
||||
}
|
||||
|
||||
.unaired {
|
||||
composes: unaired from '~Calendar/Events/CalendarEvent.css';
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ function createIsDownloadingSelector() {
|
||||
(state) => state.queue.details,
|
||||
(episodeIds, details) => {
|
||||
return details.items.some((item) => {
|
||||
return episodeIds.includes(item.episode.id);
|
||||
return item.episode && episodeIds.includes(item.episode.id);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
}
|
||||
|
||||
.todayButton {
|
||||
composes: button from 'Components/Link/Button.css';
|
||||
composes: button from '~Components/Link/Button.css';
|
||||
|
||||
margin-left: 5px;
|
||||
}
|
||||
@@ -30,13 +30,13 @@
|
||||
}
|
||||
|
||||
.viewMenu {
|
||||
composes: menu from 'Components/Menu/Menu.css';
|
||||
composes: menu from '~Components/Menu/Menu.css';
|
||||
|
||||
line-height: 31px;
|
||||
}
|
||||
|
||||
.loading {
|
||||
composes: loading from 'Components/Loading/LoadingIndicator.css';
|
||||
composes: loading from '~Components/Loading/LoadingIndicator.css';
|
||||
|
||||
margin-top: 5px;
|
||||
margin-right: 10px;
|
||||
|
||||
@@ -83,6 +83,7 @@ class CalendarHeader extends Component {
|
||||
end,
|
||||
longDateFormat,
|
||||
isSmallScreen,
|
||||
collapseViewButtons,
|
||||
onTodayPress,
|
||||
onPreviousPress,
|
||||
onNextPress
|
||||
@@ -145,7 +146,7 @@ class CalendarHeader extends Component {
|
||||
}
|
||||
|
||||
{
|
||||
isSmallScreen ?
|
||||
collapseViewButtons ?
|
||||
<Menu
|
||||
className={styles.viewMenu}
|
||||
alignMenu={align.RIGHT}
|
||||
@@ -158,6 +159,18 @@ class CalendarHeader extends Component {
|
||||
</MenuButton>
|
||||
|
||||
<MenuContent>
|
||||
{
|
||||
isSmallScreen ?
|
||||
null :
|
||||
<ViewMenuItem
|
||||
name={calendarViews.MONTH}
|
||||
selectedView={view}
|
||||
onPress={this.onViewChange}
|
||||
>
|
||||
Month
|
||||
</ViewMenuItem>
|
||||
}
|
||||
|
||||
<ViewMenuItem
|
||||
name={calendarViews.WEEK}
|
||||
selectedView={view}
|
||||
@@ -243,6 +256,7 @@ CalendarHeader.propTypes = {
|
||||
end: PropTypes.string.isRequired,
|
||||
view: PropTypes.oneOf(calendarViews.all).isRequired,
|
||||
isSmallScreen: PropTypes.bool.isRequired,
|
||||
collapseViewButtons: PropTypes.bool.isRequired,
|
||||
longDateFormat: PropTypes.string.isRequired,
|
||||
onViewChange: PropTypes.func.isRequired,
|
||||
onTodayPress: PropTypes.func.isRequired,
|
||||
|
||||
@@ -23,6 +23,7 @@ function createMapStateToProps() {
|
||||
]);
|
||||
|
||||
result.isSmallScreen = dimensions.isSmallScreen;
|
||||
result.collapseViewButtons = dimensions.isLargeScreen;
|
||||
result.longDateFormat = uiSettings.longDateFormat;
|
||||
|
||||
return result;
|
||||
|
||||
@@ -63,6 +63,21 @@ function Legend(props) {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<LegendItem
|
||||
status="onAir"
|
||||
name="On Air"
|
||||
tooltip="Episode is currently airing"
|
||||
colorImpairedMode={colorImpairedMode}
|
||||
/>
|
||||
|
||||
<LegendItem
|
||||
status="missing"
|
||||
tooltip="Episode has aired and is missing from disk"
|
||||
colorImpairedMode={colorImpairedMode}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<LegendItem
|
||||
status="downloading"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Icon from 'Components/Icon';
|
||||
import styles from './legendIconItem.css';
|
||||
import styles from './LegendIconItem.css';
|
||||
|
||||
function LegendIconItem(props) {
|
||||
const {
|
||||
|
||||
@@ -13,29 +13,29 @@
|
||||
*/
|
||||
|
||||
.downloaded {
|
||||
composes: downloaded from 'Calendar/Events/CalendarEvent.css';
|
||||
composes: downloaded from '~Calendar/Events/CalendarEvent.css';
|
||||
}
|
||||
|
||||
.downloading {
|
||||
composes: downloading from 'Calendar/Events/CalendarEvent.css';
|
||||
composes: downloading from '~Calendar/Events/CalendarEvent.css';
|
||||
}
|
||||
|
||||
.unmonitored {
|
||||
composes: unmonitored from 'Calendar/Events/CalendarEvent.css';
|
||||
composes: unmonitored from '~Calendar/Events/CalendarEvent.css';
|
||||
}
|
||||
|
||||
.onAir {
|
||||
composes: onAir from 'Calendar/Events/CalendarEvent.css';
|
||||
composes: onAir from '~Calendar/Events/CalendarEvent.css';
|
||||
}
|
||||
|
||||
.missing {
|
||||
composes: missing from 'Calendar/Events/CalendarEvent.css';
|
||||
composes: missing from '~Calendar/Events/CalendarEvent.css';
|
||||
}
|
||||
|
||||
.premiere {
|
||||
composes: premiere from 'Calendar/Events/CalendarEvent.css';
|
||||
composes: premiere from '~Calendar/Events/CalendarEvent.css';
|
||||
}
|
||||
|
||||
.unaired {
|
||||
composes: unaired from 'Calendar/Events/CalendarEvent.css';
|
||||
composes: unaired from '~Calendar/Events/CalendarEvent.css';
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ class ErrorBoundary extends Component {
|
||||
|
||||
ErrorBoundary.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
errorComponent: PropTypes.func.isRequired
|
||||
errorComponent: PropTypes.elementType.isRequired
|
||||
};
|
||||
|
||||
export default ErrorBoundary;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.modal {
|
||||
composes: modal from 'Components/Modal/Modal.css';
|
||||
composes: modal from '~Components/Modal/Modal.css';
|
||||
|
||||
height: 600px;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
.modalBody {
|
||||
composes: modalBody from 'Components/Modal/ModalBody.css';
|
||||
composes: modalBody from '~Components/Modal/ModalBody.css';
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.mappedDrivesWarning {
|
||||
composes: alert from 'Components/Alert.css';
|
||||
composes: alert from '~Components/Alert.css';
|
||||
|
||||
margin: 0;
|
||||
margin-bottom: 20px;
|
||||
@@ -18,7 +18,7 @@
|
||||
}
|
||||
|
||||
.pathInput {
|
||||
composes: pathInputWrapper from 'Components/Form/PathInput.css';
|
||||
composes: inputWrapper from '~Components/Form/PathInput.css';
|
||||
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
@@ -144,6 +144,7 @@ class FileBrowserModalContent extends Component {
|
||||
<Scroller
|
||||
ref={this.setScrollerRef}
|
||||
className={styles.scroller}
|
||||
scrollDirection={scrollDirections.BOTH}
|
||||
>
|
||||
{
|
||||
!!error &&
|
||||
@@ -152,7 +153,10 @@ class FileBrowserModalContent extends Component {
|
||||
|
||||
{
|
||||
isPopulated && !error &&
|
||||
<Table columns={columns}>
|
||||
<Table
|
||||
horizontalScroll={false}
|
||||
columns={columns}
|
||||
>
|
||||
<TableBody>
|
||||
{
|
||||
emptyParent &&
|
||||
|
||||
@@ -42,8 +42,8 @@ function createMapStateToProps() {
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
fetchPaths,
|
||||
clearPaths
|
||||
dispatchFetchPaths: fetchPaths,
|
||||
dispatchClearPaths: clearPaths
|
||||
};
|
||||
|
||||
class FileBrowserModalContentConnector extends Component {
|
||||
@@ -51,9 +51,16 @@ class FileBrowserModalContentConnector extends Component {
|
||||
// Lifecycle
|
||||
|
||||
componentDidMount() {
|
||||
this.props.fetchPaths({
|
||||
path: this.props.value,
|
||||
allowFoldersWithoutTrailingSlashes: true
|
||||
const {
|
||||
value,
|
||||
includeFiles,
|
||||
dispatchFetchPaths
|
||||
} = this.props;
|
||||
|
||||
dispatchFetchPaths({
|
||||
path: value,
|
||||
allowFoldersWithoutTrailingSlashes: true,
|
||||
includeFiles
|
||||
});
|
||||
}
|
||||
|
||||
@@ -61,18 +68,24 @@ class FileBrowserModalContentConnector extends Component {
|
||||
// Listeners
|
||||
|
||||
onFetchPaths = (path) => {
|
||||
this.props.fetchPaths({
|
||||
const {
|
||||
includeFiles,
|
||||
dispatchFetchPaths
|
||||
} = this.props;
|
||||
|
||||
dispatchFetchPaths({
|
||||
path,
|
||||
allowFoldersWithoutTrailingSlashes: true
|
||||
allowFoldersWithoutTrailingSlashes: true,
|
||||
includeFiles
|
||||
});
|
||||
}
|
||||
|
||||
onClearPaths = () => {
|
||||
// this.props.clearPaths();
|
||||
// this.props.dispatchClearPaths();
|
||||
}
|
||||
|
||||
onModalClose = () => {
|
||||
this.props.clearPaths();
|
||||
this.props.dispatchClearPaths();
|
||||
this.props.onModalClose();
|
||||
}
|
||||
|
||||
@@ -93,9 +106,14 @@ class FileBrowserModalContentConnector extends Component {
|
||||
|
||||
FileBrowserModalContentConnector.propTypes = {
|
||||
value: PropTypes.string,
|
||||
fetchPaths: PropTypes.func.isRequired,
|
||||
clearPaths: PropTypes.func.isRequired,
|
||||
includeFiles: PropTypes.bool.isRequired,
|
||||
dispatchFetchPaths: PropTypes.func.isRequired,
|
||||
dispatchClearPaths: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
FileBrowserModalContentConnector.defaultProps = {
|
||||
includeFiles: false
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(FileBrowserModalContentConnector);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user