mirror of
https://github.com/Readarr/Readarr.git
synced 2026-03-21 16:54:15 -04:00
Compare commits
547 Commits
sonarr-pul
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b79d3000d | ||
|
|
0f3e716044 | ||
|
|
f53c4dc017 | ||
|
|
f48eac7e36 | ||
|
|
7cc02f95af | ||
|
|
1f92bf6679 | ||
|
|
a8d4aa6770 | ||
|
|
7661b5bc87 | ||
|
|
200ef600cd | ||
|
|
ad6228983b | ||
|
|
582ec9f7ce | ||
|
|
525e855038 | ||
|
|
7a629ed044 | ||
|
|
7f501322dd | ||
|
|
18bca0b228 | ||
|
|
9ddac60b47 | ||
|
|
bd8bc0b35b | ||
|
|
ae623f4481 | ||
|
|
e67d133bb6 | ||
|
|
772ea95ce4 | ||
|
|
5459a7bb7e | ||
|
|
614f98f9b4 | ||
|
|
55763dae43 | ||
|
|
a362dab503 | ||
|
|
dba9fbf254 | ||
|
|
59a7605385 | ||
|
|
f819e582cf | ||
|
|
0972d41bf8 | ||
|
|
05d2335bfe | ||
|
|
5b4f54a959 | ||
|
|
bb5ad605fd | ||
|
|
52c3a95e63 | ||
|
|
52c5460537 | ||
|
|
280cec3d0e | ||
|
|
f10c2c01d8 | ||
|
|
2b6a328dac | ||
|
|
4078525f67 | ||
|
|
214e4270ac | ||
|
|
5396dd3e8e | ||
|
|
d5d4996c40 | ||
|
|
8b2223a9c4 | ||
|
|
2a3e7f8dae | ||
|
|
6406ed6289 | ||
|
|
d5b0831b0f | ||
|
|
8d72d5dbab | ||
|
|
74d1ab84e2 | ||
|
|
7341d20c51 | ||
|
|
5abf4f2992 | ||
|
|
cc90050c77 | ||
|
|
7ba8f8baee | ||
|
|
70aec175ef | ||
|
|
080dd301f3 | ||
|
|
bab45481db | ||
|
|
3e1e03e0ce | ||
|
|
bb599d6dc9 | ||
|
|
c94685842f | ||
|
|
5966f6da51 | ||
|
|
229e0dfe5d | ||
|
|
5173daa265 | ||
|
|
c2f770f242 | ||
|
|
0b7ce67635 | ||
|
|
bc74456944 | ||
|
|
fa460567a7 | ||
|
|
7dfceb307b | ||
|
|
305ad235a5 | ||
|
|
74c20e41bf | ||
|
|
347289b173 | ||
|
|
0ef3d2a5cc | ||
|
|
e5519d60c9 | ||
|
|
3a85b3a060 | ||
|
|
c1cdf44322 | ||
|
|
f861e54139 | ||
|
|
279e1029e0 | ||
|
|
b9ed39175b | ||
|
|
faba3ada95 | ||
|
|
e8647aee05 | ||
|
|
eaf5ce52bc | ||
|
|
73ab2760e4 | ||
|
|
3bb036e8c6 | ||
|
|
6e05456d6a | ||
|
|
8563a42822 | ||
|
|
841d38f4a5 | ||
|
|
9326d88eb6 | ||
|
|
015da61004 | ||
|
|
d02ea4b121 | ||
|
|
7bc9d700f9 | ||
|
|
661d72ef9b | ||
|
|
258a8d1c95 | ||
|
|
d4459b9475 | ||
|
|
a550c6554f | ||
|
|
c1b26eec8d | ||
|
|
ffe5ede55d | ||
|
|
9005860899 | ||
|
|
c67f67109e | ||
|
|
51b9744e25 | ||
|
|
334d824633 | ||
|
|
ae01387ca9 | ||
|
|
4eb13e0938 | ||
|
|
6dbb826f2f | ||
|
|
52dfa57dd7 | ||
|
|
f354b3bc47 | ||
|
|
2d9e6788e6 | ||
|
|
0d121fe9c0 | ||
|
|
892c34fe35 | ||
|
|
24f6007594 | ||
|
|
5028ed4027 | ||
|
|
05f303436b | ||
|
|
5635de96a8 | ||
|
|
ce59f32023 | ||
|
|
6d675a5207 | ||
|
|
b093b23900 | ||
|
|
884ac2cb6f | ||
|
|
295a6c4255 | ||
|
|
74a59d5790 | ||
|
|
ae23e5f187 | ||
|
|
ba2add0d54 | ||
|
|
b6ebeb31c8 | ||
|
|
b8bd645560 | ||
|
|
e0d904fa69 | ||
|
|
cb532caca4 | ||
|
|
e1af8ad37f | ||
|
|
c4f30da648 | ||
|
|
b83a760873 | ||
|
|
22ab50f76d | ||
|
|
66758ca006 | ||
|
|
e7d7bc79f4 | ||
|
|
cfccb4f9c3 | ||
|
|
9312f17041 | ||
|
|
8192c22910 | ||
|
|
0b1d6b677a | ||
|
|
d666df0189 | ||
|
|
10d8f345c1 | ||
|
|
fb720b8714 | ||
|
|
e8131b5791 | ||
|
|
4f793f6b93 | ||
|
|
4215c21c94 | ||
|
|
6913789adc | ||
|
|
09e0c40792 | ||
|
|
baff805551 | ||
|
|
c885fe43cd | ||
|
|
464a777722 | ||
|
|
89e5999c85 | ||
|
|
b6fa332550 | ||
|
|
05f262dc0a | ||
|
|
699b765ee9 | ||
|
|
84beba2383 | ||
|
|
62eceb9148 | ||
|
|
f46070d4b0 | ||
|
|
73979c416a | ||
|
|
348e8f9c27 | ||
|
|
38bdb5a75d | ||
|
|
5e4c51e2f7 | ||
|
|
99a65246a9 | ||
|
|
598ce9a9d2 | ||
|
|
42d6b9e703 | ||
|
|
8f595838aa | ||
|
|
3d9d7d3582 | ||
|
|
77cf28bd78 | ||
|
|
2fb1b8af20 | ||
|
|
af1f389f8e | ||
|
|
b5334da253 | ||
|
|
68b3904382 | ||
|
|
c8b09b9e29 | ||
|
|
d910fc42ab | ||
|
|
a6db8bfe0e | ||
|
|
2033d7e411 | ||
|
|
4a04e54ceb | ||
|
|
d57a9ab9b0 | ||
|
|
d333204194 | ||
|
|
c3676f8d33 | ||
|
|
932356be61 | ||
|
|
5b1b2a2d67 | ||
|
|
c362e8c467 | ||
|
|
67c00a8cc7 | ||
|
|
27a086dfff | ||
|
|
8ee0df9c65 | ||
|
|
da30b55902 | ||
|
|
c7226fc85f | ||
|
|
84f22dbadc | ||
|
|
06a53ef9ca | ||
|
|
b5ef0cda1e | ||
|
|
1b1290efac | ||
|
|
dcbc3ea3f8 | ||
|
|
9a7b2cb818 | ||
|
|
f9cba39f0a | ||
|
|
6b6ff4fe76 | ||
|
|
05d0fe2da6 | ||
|
|
7aab2b49e2 | ||
|
|
8887df92ed | ||
|
|
9ee651d6c0 | ||
|
|
5544e169a6 | ||
|
|
11d83165e5 | ||
|
|
9e6d1c581c | ||
|
|
66e20a0aec | ||
|
|
e639b36283 | ||
|
|
c9f4fb141f | ||
|
|
29a43fc2fd | ||
|
|
f9454b5b5a | ||
|
|
9aa6d47349 | ||
|
|
e09946d946 | ||
|
|
c9c5429120 | ||
|
|
ed7bd6c66d | ||
|
|
c88fe7cae8 | ||
|
|
68642579d0 | ||
|
|
f061d70d38 | ||
|
|
fd4a609f51 | ||
|
|
9957f734a5 | ||
|
|
695b8b2ae1 | ||
|
|
420824b279 | ||
|
|
badc2567c3 | ||
|
|
c8c81927d9 | ||
|
|
f9df843789 | ||
|
|
3cd39d4ee8 | ||
|
|
8a39ef4c56 | ||
|
|
ba1195fc1b | ||
|
|
7656142db4 | ||
|
|
74c3b45ef8 | ||
|
|
f7368d3d09 | ||
|
|
5d8e2300f2 | ||
|
|
1fb54c0da5 | ||
|
|
5a9a6e593b | ||
|
|
2d5fc655c0 | ||
|
|
cfcc9a5856 | ||
|
|
8c9555f82e | ||
|
|
ee20ba1811 | ||
|
|
4cf1215cfa | ||
|
|
a8ab099177 | ||
|
|
50af8a12d4 | ||
|
|
510c39c5d8 | ||
|
|
dd4a0121f2 | ||
|
|
4fb62c072a | ||
|
|
2b100d0f72 | ||
|
|
abfdc44f92 | ||
|
|
6e76f9966a | ||
|
|
2b6ceab9d4 | ||
|
|
b636729960 | ||
|
|
8af8366575 | ||
|
|
1d31e9b9d9 | ||
|
|
37a9f670dd | ||
|
|
11eda3b11b | ||
|
|
04682c9d91 | ||
|
|
50fdc449ac | ||
|
|
b8c295727a | ||
|
|
93ee466780 | ||
|
|
77f1e8f8c9 | ||
|
|
1aa746bea1 | ||
|
|
490041d77c | ||
|
|
5dc5592c17 | ||
|
|
8fb1aff68a | ||
|
|
a397a19034 | ||
|
|
d0df761422 | ||
|
|
4781675c1a | ||
|
|
0361262bb4 | ||
|
|
3407cc9a7a | ||
|
|
4829916f0a | ||
|
|
c505eafd30 | ||
|
|
07f218f294 | ||
|
|
42751b598b | ||
|
|
5e7e0eb50b | ||
|
|
d6c631457c | ||
|
|
12ee76d222 | ||
|
|
3ea80038d3 | ||
|
|
55404cdf24 | ||
|
|
83a9cd4f3e | ||
|
|
3572d7330d | ||
|
|
a9b652a280 | ||
|
|
8efb2eb71a | ||
|
|
17094f1998 | ||
|
|
ddf5dc25a1 | ||
|
|
fa2614954b | ||
|
|
2e2894b3d3 | ||
|
|
59ff407e76 | ||
|
|
bbd7b9f92e | ||
|
|
c77d820763 | ||
|
|
3327ed0f49 | ||
|
|
44009e980b | ||
|
|
02fd733223 | ||
|
|
2fa9576d05 | ||
|
|
c7ee278ee4 | ||
|
|
d72c27ceed | ||
|
|
7a20fe2288 | ||
|
|
042b62a2a5 | ||
|
|
88141e9d63 | ||
|
|
7fa1114edf | ||
|
|
d4262532e2 | ||
|
|
a21f83aae1 | ||
|
|
d659e86a7d | ||
|
|
0b924005ec | ||
|
|
ba2fad5d9c | ||
|
|
58416cee67 | ||
|
|
38124313c7 | ||
|
|
3fc9f6c0a4 | ||
|
|
79ce5abd53 | ||
|
|
7f01d597cb | ||
|
|
31f35df71d | ||
|
|
faeb78801c | ||
|
|
bd5695f2dd | ||
|
|
5375cbe1c2 | ||
|
|
d0b797ea61 | ||
|
|
e76f160695 | ||
|
|
ef71fc1b41 | ||
|
|
14f14e5da4 | ||
|
|
bd265e47fa | ||
|
|
333d344c0b | ||
|
|
db6712f030 | ||
|
|
1065a6283c | ||
|
|
1b40c5c7ce | ||
|
|
a8de87300e | ||
|
|
f260078ac8 | ||
|
|
5a6486be21 | ||
|
|
2e9de3cb86 | ||
|
|
a259684916 | ||
|
|
5704adfbc5 | ||
|
|
6cfaab07ba | ||
|
|
b36085a3cc | ||
|
|
0afa0977b0 | ||
|
|
4a174e559f | ||
|
|
0fb8ab2280 | ||
|
|
261b0f398b | ||
|
|
d1fea384a7 | ||
|
|
9542ea0d2e | ||
|
|
e1d697c561 | ||
|
|
22ed847849 | ||
|
|
2faef704b4 | ||
|
|
a566c3e21f | ||
|
|
cc0d2a84ae | ||
|
|
1c3d2ce4e5 | ||
|
|
57f614f4cd | ||
|
|
9d2efe0944 | ||
|
|
e032be48e0 | ||
|
|
cd66de1992 | ||
|
|
3066dd92d7 | ||
|
|
467a87baec | ||
|
|
80fb077c94 | ||
|
|
07433d69ca | ||
|
|
3b3ebe463c | ||
|
|
03392ca635 | ||
|
|
d23ce9ecc2 | ||
|
|
e968fcaff6 | ||
|
|
31da559f89 | ||
|
|
a093290792 | ||
|
|
9e3dfc510d | ||
|
|
9d27c172ac | ||
|
|
518dbe53eb | ||
|
|
f9ba00c9e7 | ||
|
|
4aec7a0ea7 | ||
|
|
fc4cf8e81e | ||
|
|
143de3b220 | ||
|
|
e1a07d01b2 | ||
|
|
27e498bb14 | ||
|
|
b9f1882a57 | ||
|
|
2392573c39 | ||
|
|
2351efd013 | ||
|
|
526429bde4 | ||
|
|
abd44b59bc | ||
|
|
9942457ffc | ||
|
|
073342ef39 | ||
|
|
b455708f2e | ||
|
|
622b02c478 | ||
|
|
8effba383d | ||
|
|
2749479283 | ||
|
|
4cbafa76d8 | ||
|
|
73782cc233 | ||
|
|
de396fe9be | ||
|
|
71cb9e1dd7 | ||
|
|
ee5ed57fcc | ||
|
|
d20a049a5a | ||
|
|
a9f77ace37 | ||
|
|
0341a2ec26 | ||
|
|
d6796bbe1a | ||
|
|
9066f8558c | ||
|
|
c4e37528ee | ||
|
|
5937c952af | ||
|
|
0f4bd3c472 | ||
|
|
cf415e61de | ||
|
|
9865e92cea | ||
|
|
1cf956a9d9 | ||
|
|
8989c55c8c | ||
|
|
dc83e0127e | ||
|
|
34eb312426 | ||
|
|
9d5cdebdb2 | ||
|
|
a0ab224acd | ||
|
|
05aa35a54d | ||
|
|
ca7f8775f5 | ||
|
|
2a01e9b445 | ||
|
|
7d30c7d1ea | ||
|
|
50be87e5a4 | ||
|
|
0572d1ac80 | ||
|
|
d2240514d7 | ||
|
|
ad47dc032d | ||
|
|
6c6df7d7d9 | ||
|
|
2121204064 | ||
|
|
61004ea33f | ||
|
|
54c1c7862e | ||
|
|
43dfdc8bf5 | ||
|
|
0d1ae0ca4e | ||
|
|
9902889a30 | ||
|
|
04d7061030 | ||
|
|
fd201912a9 | ||
|
|
c412701a3d | ||
|
|
7451a66365 | ||
|
|
a6431fdb0b | ||
|
|
060b133f6d | ||
|
|
5ed13b942b | ||
|
|
89f3d8167b | ||
|
|
77b027374f | ||
|
|
650490abb2 | ||
|
|
7d2e215d61 | ||
|
|
65ff890c74 | ||
|
|
50c0b0dbaa | ||
|
|
d5f36d0144 | ||
|
|
fab7558bd4 | ||
|
|
3dc86b3a01 | ||
|
|
24ad6134e3 | ||
|
|
033f8c40af | ||
|
|
4c73a619eb | ||
|
|
3ca798e983 | ||
|
|
d9827fd6a6 | ||
|
|
f4f03a853f | ||
|
|
4f4e4bf2ca | ||
|
|
413a70a312 | ||
|
|
a8f2b91010 | ||
|
|
68a4ee6000 | ||
|
|
5196ce311b | ||
|
|
ae92b22727 | ||
|
|
0bccffef01 | ||
|
|
bca899b9c0 | ||
|
|
2bb576a94b | ||
|
|
bb49949853 | ||
|
|
a093061b29 | ||
|
|
df876707c4 | ||
|
|
2af33143ba | ||
|
|
c3c5a47776 | ||
|
|
a21abe0838 | ||
|
|
a32f5f6639 | ||
|
|
4cd45ecc21 | ||
|
|
2c8e0b1ca4 | ||
|
|
bd25c9e3e0 | ||
|
|
ee64b8788b | ||
|
|
7aeada2089 | ||
|
|
e188c9aac0 | ||
|
|
a3ae2359f5 | ||
|
|
5b92905dd4 | ||
|
|
fc402743aa | ||
|
|
b9d53ed732 | ||
|
|
d248747635 | ||
|
|
d70224c811 | ||
|
|
acdf8c8aa8 | ||
|
|
3ed41554ce | ||
|
|
ce808c6d7b | ||
|
|
63b1b56a4f | ||
|
|
a5647bedc8 | ||
|
|
fe659bb79d | ||
|
|
9918535509 | ||
|
|
f9a6db40b8 | ||
|
|
6273d69ed6 | ||
|
|
7012380e95 | ||
|
|
b001ecd698 | ||
|
|
e28becdda4 | ||
|
|
eae06695e8 | ||
|
|
54a9af2ced | ||
|
|
c9b55266fc | ||
|
|
05b64406a4 | ||
|
|
1f37c5387b | ||
|
|
4a6c7042fe | ||
|
|
d7305b9753 | ||
|
|
bd56643eaa | ||
|
|
44e6de2e23 | ||
|
|
b209d047fa | ||
|
|
fd5ab27df6 | ||
|
|
4a89befd79 | ||
|
|
1a30293c33 | ||
|
|
f5c2a6bf51 | ||
|
|
f3d90fdaf1 | ||
|
|
04c5671a0a | ||
|
|
22cc88c5e7 | ||
|
|
ca0c95a2d2 | ||
|
|
419f790d66 | ||
|
|
9fe08429bc | ||
|
|
71f4a88ab3 | ||
|
|
30b283eda3 | ||
|
|
e23d0bbfa1 | ||
|
|
765a2aa01b | ||
|
|
64895c3210 | ||
|
|
03ab84a814 | ||
|
|
b77e5b14e1 | ||
|
|
75efbd45e1 | ||
|
|
00cac507ad | ||
|
|
c4850505b0 | ||
|
|
75213c86a1 | ||
|
|
b8c3a42643 | ||
|
|
8acb034aa6 | ||
|
|
889d32552b | ||
|
|
adc5f4db97 | ||
|
|
9d08050f96 | ||
|
|
f8cffbb4cf | ||
|
|
14aeb66142 | ||
|
|
37e8e11e31 | ||
|
|
bdb2f14936 | ||
|
|
a97af657be | ||
|
|
301127e6dc | ||
|
|
1f95bcae4e | ||
|
|
29118cda45 | ||
|
|
09beaa939d | ||
|
|
2107624f1c | ||
|
|
c1c2076e5c | ||
|
|
c31a797bd8 | ||
|
|
ebb2b4eca3 | ||
|
|
3ec5d9b9fe | ||
|
|
1ad84a7c44 | ||
|
|
9d67c18254 | ||
|
|
2e39c7340c | ||
|
|
f75add984f | ||
|
|
f0f6c3eb35 | ||
|
|
d94f866aeb | ||
|
|
618f07d138 | ||
|
|
3db33c988a | ||
|
|
28e38b7f17 | ||
|
|
ca403e6f31 | ||
|
|
51351dee1d | ||
|
|
2081f2e321 | ||
|
|
c10a32534c | ||
|
|
0e415c6ce3 | ||
|
|
a8eb674071 | ||
|
|
7f8a1cf849 | ||
|
|
a3c0d10240 | ||
|
|
3ddeaaefe2 | ||
|
|
6d99de4fe0 | ||
|
|
8b36a5ce92 | ||
|
|
1202a43466 | ||
|
|
82bc2d1aa4 | ||
|
|
ed9af393b7 | ||
|
|
0799cfc885 | ||
|
|
331d0c9a9c | ||
|
|
03c93c9c84 | ||
|
|
60f6ed030b | ||
|
|
cc70d61735 | ||
|
|
a7b965100d | ||
|
|
8901118aef | ||
|
|
99c17d7698 | ||
|
|
b84e83b082 | ||
|
|
4249f5324a | ||
|
|
9e1630e9a4 | ||
|
|
68b2773913 | ||
|
|
ad446b358e | ||
|
|
423a8ecbe1 | ||
|
|
29a12aa3b0 |
13
.devcontainer/Readarr.code-workspace
Normal file
13
.devcontainer/Readarr.code-workspace
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// This file is used to open the backend and frontend in the same workspace, which is necessary as
|
||||||
|
// the frontend has vscode settings that are distinct from the backend
|
||||||
|
{
|
||||||
|
"folders": [
|
||||||
|
{
|
||||||
|
"path": ".."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../frontend"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {}
|
||||||
|
}
|
||||||
19
.devcontainer/devcontainer.json
Normal file
19
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
||||||
|
// README at: https://github.com/devcontainers/templates/tree/main/src/dotnet
|
||||||
|
{
|
||||||
|
"name": "Readarr",
|
||||||
|
"image": "mcr.microsoft.com/devcontainers/dotnet:1-6.0",
|
||||||
|
"features": {
|
||||||
|
"ghcr.io/devcontainers/features/node:1": {
|
||||||
|
"nodeGypDependencies": true,
|
||||||
|
"version": "16",
|
||||||
|
"nvmVersion": "latest"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"forwardPorts": [8787],
|
||||||
|
"customizations": {
|
||||||
|
"vscode": {
|
||||||
|
"extensions": ["esbenp.prettier-vscode"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
.gitattributes
vendored
3
.gitattributes
vendored
@@ -3,8 +3,7 @@
|
|||||||
|
|
||||||
# Explicitly set bash scripts to have unix endings
|
# Explicitly set bash scripts to have unix endings
|
||||||
*.sh text eol=lf
|
*.sh text eol=lf
|
||||||
distribution/debian/* text eol=lf
|
distribution/osx/Readarr text eol=lf
|
||||||
macOS/Readarr text eol=lf
|
|
||||||
|
|
||||||
# Custom for Visual Studio
|
# Custom for Visual Studio
|
||||||
*.cs diff=csharp
|
*.cs diff=csharp
|
||||||
|
|||||||
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -1,5 +1,5 @@
|
|||||||
name: Bug Report
|
name: Bug Report
|
||||||
description: 'Report a new bug, if you are not 100% certain this is a bug please go to our Reddit or Discord first'
|
description: 'Report a new bug, if you are not 100% certain this is a bug please go to our Discord first'
|
||||||
labels: ['Type: Bug', 'Status: Needs Triage']
|
labels: ['Type: Bug', 'Status: Needs Triage']
|
||||||
body:
|
body:
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
|
|||||||
3
.github/ISSUE_TEMPLATE/config.yml
vendored
3
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -3,6 +3,3 @@ contact_links:
|
|||||||
- name: Support via Discord
|
- name: Support via Discord
|
||||||
url: https://readarr.com/discord
|
url: https://readarr.com/discord
|
||||||
about: Chat with users and devs on support and setup related topics.
|
about: Chat with users and devs on support and setup related topics.
|
||||||
- name: Support via Reddit
|
|
||||||
url: https://reddit.com/r/Readarr
|
|
||||||
about: Discuss and search thru support topics.
|
|
||||||
|
|||||||
12
.github/dependabot.yml
vendored
Normal file
12
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# To get started with Dependabot version updates, you'll need to specify which
|
||||||
|
# package ecosystems to update and where the package manifests are located.
|
||||||
|
# Please see the documentation for more information:
|
||||||
|
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||||
|
# https://containers.dev/guide/dependabot
|
||||||
|
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "devcontainers"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: weekly
|
||||||
16
.github/label-actions.yml
vendored
Normal file
16
.github/label-actions.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Configuration for Label Actions - https://github.com/dessant/label-actions
|
||||||
|
|
||||||
|
'Type: Support':
|
||||||
|
comment: >
|
||||||
|
:wave: @{issue-author}, we use the issue tracker exclusively
|
||||||
|
for bug reports and feature requests. However, this issue appears
|
||||||
|
to be a support request. Please hop over onto our [Discord](https://readarr.com/discord).
|
||||||
|
close: true
|
||||||
|
close-reason: 'not planned'
|
||||||
|
|
||||||
|
'Status: Logs Needed':
|
||||||
|
comment: >
|
||||||
|
:wave: @{issue-author}, In order to help you further we'll need to see logs.
|
||||||
|
You'll need to enable trace logging and replicate the problem that you encountered.
|
||||||
|
Guidance on how to enable trace logging can be found in
|
||||||
|
our [troubleshooting guide](https://wiki.servarr.com/readarr/troubleshooting#logging-and-log-files).
|
||||||
17
.github/workflows/label-actions.yml
vendored
Normal file
17
.github/workflows/label-actions.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
name: 'Label Actions'
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [labeled, unlabeled]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
issues: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
action:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: dessant/label-actions@v3
|
||||||
|
with:
|
||||||
|
process-only: 'issues'
|
||||||
32
.github/workflows/support.yml
vendored
32
.github/workflows/support.yml
vendored
@@ -1,32 +0,0 @@
|
|||||||
name: 'Support requests'
|
|
||||||
|
|
||||||
on:
|
|
||||||
issues:
|
|
||||||
types: [labeled, unlabeled, reopened]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
support:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: dessant/support-requests@v3
|
|
||||||
with:
|
|
||||||
github-token: ${{ github.token }}
|
|
||||||
support-label: 'Type: Support'
|
|
||||||
issue-comment: >
|
|
||||||
:wave: @{issue-author}, we use the issue tracker exclusively
|
|
||||||
for bug reports and feature requests. However, this issue appears
|
|
||||||
to be a support request. Please hop over onto our [Discord](https://readarr.com/discord)
|
|
||||||
or [Subreddit](https://reddit.com/r/readarr)
|
|
||||||
close-issue: true
|
|
||||||
lock-issue: false
|
|
||||||
- uses: dessant/support-requests@v3
|
|
||||||
with:
|
|
||||||
github-token: ${{ github.token }}
|
|
||||||
support-label: 'Status: Logs Needed'
|
|
||||||
issue-comment: >
|
|
||||||
:wave: @{issue-author}, In order to help you further we'll need to see logs.
|
|
||||||
You'll need to enable trace logging and replicate the problem that you encountered.
|
|
||||||
Guidance on how to enable trace logging can be found in
|
|
||||||
our [troubleshooting guide](https://wiki.servarr.com/readarr/troubleshooting#logging-and-log-files).
|
|
||||||
close-issue: false
|
|
||||||
lock-issue: false
|
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -120,11 +120,13 @@ _artifacts
|
|||||||
_rawPackage/
|
_rawPackage/
|
||||||
_dotTrace*
|
_dotTrace*
|
||||||
_tests/
|
_tests/
|
||||||
|
_temp*
|
||||||
*.Result.xml
|
*.Result.xml
|
||||||
coverage*.xml
|
coverage*.xml
|
||||||
coverage*.json
|
coverage*.json
|
||||||
setup/Output/
|
setup/Output/
|
||||||
*.~is
|
*.~is
|
||||||
|
.mono
|
||||||
|
|
||||||
# .NET Core
|
# .NET Core
|
||||||
project.lock.json
|
project.lock.json
|
||||||
|
|||||||
7
.vscode/extensions.json
vendored
Normal file
7
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"esbenp.prettier-vscode",
|
||||||
|
"ms-dotnettools.csdevkit",
|
||||||
|
"ms-vscode-remote.remote-containers"
|
||||||
|
]
|
||||||
|
}
|
||||||
26
.vscode/launch.json
vendored
Normal file
26
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
// Use IntelliSense to find out which attributes exist for C# debugging
|
||||||
|
// Use hover for the description of the existing attributes
|
||||||
|
// For further information visit https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md
|
||||||
|
"name": "Run Readarr",
|
||||||
|
"type": "coreclr",
|
||||||
|
"request": "launch",
|
||||||
|
"preLaunchTask": "build dotnet",
|
||||||
|
// If you have changed target frameworks, make sure to update the program path.
|
||||||
|
"program": "${workspaceFolder}/_output/net6.0/Readarr",
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"stopAtEntry": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": ".NET Core Attach",
|
||||||
|
"type": "coreclr",
|
||||||
|
"request": "attach"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
44
.vscode/tasks.json
vendored
Normal file
44
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "build dotnet",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "process",
|
||||||
|
"args": [
|
||||||
|
"msbuild",
|
||||||
|
"-restore",
|
||||||
|
"${workspaceFolder}/src/Readarr.sln",
|
||||||
|
"-p:GenerateFullPaths=true",
|
||||||
|
"-p:Configuration=Debug",
|
||||||
|
"-p:Platform=Posix",
|
||||||
|
"-consoleloggerparameters:NoSummary;ForceNoAlign"
|
||||||
|
],
|
||||||
|
"problemMatcher": "$msCompile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "publish",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "process",
|
||||||
|
"args": [
|
||||||
|
"publish",
|
||||||
|
"${workspaceFolder}/src/Readarr.sln",
|
||||||
|
"-property:GenerateFullPaths=true",
|
||||||
|
"-consoleloggerparameters:NoSummary;ForceNoAlign"
|
||||||
|
],
|
||||||
|
"problemMatcher": "$msCompile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "watch",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "process",
|
||||||
|
"args": [
|
||||||
|
"watch",
|
||||||
|
"run",
|
||||||
|
"--project",
|
||||||
|
"${workspaceFolder}/src/Readarr.sln"
|
||||||
|
],
|
||||||
|
"problemMatcher": "$msCompile"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 3.7 KiB |
21
README.md
21
README.md
@@ -1,3 +1,23 @@
|
|||||||
|
# Announcement: Retirement of Readarr
|
||||||
|
|
||||||
|
We would like to announce that the [Readarr project](<https://github.com/Readarr/Readarr>) has been retired. This difficult decision was made due to a combination of factors: the project's metadata has become unusable, we no longer have the time to remake or repair it, and the community effort to transition to using Open Library as the source has stalled without much progress.
|
||||||
|
|
||||||
|
Third-party metadata mirrors exist, but as we're not involved with them at all, we cannot provide support for them. Use of them is entirely at your own risk. The most popular mirror appears to be [rreading-glasses](<https://github.com/blampe/rreading-glasses>).
|
||||||
|
|
||||||
|
Without anyone to take over Readarr development, we expect it to wither away, so we still encourage you to seek alternatives to Readarr.
|
||||||
|
|
||||||
|
## Key Points:
|
||||||
|
- **Effective Immediately**: The retirement takes effect immediately. Please stay tuned for any possible further communications.
|
||||||
|
- **Support Window**: We will provide support during a brief transition period to help with troubleshooting non metadata related issues.
|
||||||
|
- **Alternative Solutions**: Users are encouraged to explore and adopt any other possible solutions as alternatives to Readarr.
|
||||||
|
- **Opportunities for Revival**: We are open to someone taking over and revitalizing the project. If you are interested, please get in touch.
|
||||||
|
- **Gratitude**: We extend our deepest gratitude to all the contributors and community members who supported Readarr over the years.
|
||||||
|
|
||||||
|
Thank you for being part of the Readarr journey. For any inquiries or assistance during this transition, please contact our team.
|
||||||
|
|
||||||
|
Sincerely,
|
||||||
|
The Servarr Team
|
||||||
|
|
||||||
# Readarr
|
# Readarr
|
||||||
|
|
||||||
[](https://dev.azure.com/Readarr/Readarr/_build/latest?definitionId=1&branchName=develop)
|
[](https://dev.azure.com/Readarr/Readarr/_build/latest?definitionId=1&branchName=develop)
|
||||||
@@ -30,7 +50,6 @@ Note that only one type of a given book is supported. If you want both an audiob
|
|||||||
|
|
||||||
[](https://wiki.servarr.com/readarr)
|
[](https://wiki.servarr.com/readarr)
|
||||||
[](https://readarr.com/discord)
|
[](https://readarr.com/discord)
|
||||||
[](https://www.reddit.com/r/readarr)
|
|
||||||
|
|
||||||
Note: GitHub Issues are for Bugs and Feature Requests Only
|
Note: GitHub Issues are for Bugs and Feature Requests Only
|
||||||
|
|
||||||
|
|||||||
@@ -9,24 +9,28 @@ variables:
|
|||||||
testsFolder: './_tests'
|
testsFolder: './_tests'
|
||||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||||
majorVersion: '0.3.2'
|
majorVersion: '0.4.19'
|
||||||
minorVersion: $[counter('minorVersion', 1)]
|
minorVersion: $[counter('minorVersion', 1)]
|
||||||
readarrVersion: '$(majorVersion).$(minorVersion)'
|
readarrVersion: '$(majorVersion).$(minorVersion)'
|
||||||
buildName: '$(Build.SourceBranchName).$(readarrVersion)'
|
buildName: '$(Build.SourceBranchName).$(readarrVersion)'
|
||||||
sentryOrg: 'servarr'
|
sentryOrg: 'servarr'
|
||||||
sentryUrl: 'https://sentry.servarr.com'
|
sentryUrl: 'https://sentry.servarr.com'
|
||||||
dotnetVersion: '6.0.408'
|
dotnetVersion: '6.0.427'
|
||||||
nodeVersion: '16.X'
|
nodeVersion: '20.X'
|
||||||
innoVersion: '6.2.0'
|
innoVersion: '6.2.0'
|
||||||
windowsImage: 'windows-2022'
|
windowsImage: 'windows-2022'
|
||||||
linuxImage: 'ubuntu-20.04'
|
linuxImage: 'ubuntu-22.04'
|
||||||
macImage: 'macOS-11'
|
macImage: 'macOS-13'
|
||||||
|
|
||||||
trigger:
|
trigger:
|
||||||
branches:
|
branches:
|
||||||
include:
|
include:
|
||||||
- develop
|
- develop
|
||||||
- master
|
- master
|
||||||
|
paths:
|
||||||
|
exclude:
|
||||||
|
- .github
|
||||||
|
- src/Readarr.Api.*/openapi.json
|
||||||
|
|
||||||
pr:
|
pr:
|
||||||
branches:
|
branches:
|
||||||
@@ -34,82 +38,37 @@ pr:
|
|||||||
- develop
|
- develop
|
||||||
paths:
|
paths:
|
||||||
exclude:
|
exclude:
|
||||||
|
- .github
|
||||||
- src/NzbDrone.Core/Localization/Core
|
- src/NzbDrone.Core/Localization/Core
|
||||||
|
- src/Readarr.Api.*/openapi.json
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
|
- stage: Setup
|
||||||
- stage: Build_Backend_Windows
|
displayName: Setup
|
||||||
displayName: Build Backend
|
|
||||||
dependsOn: []
|
|
||||||
jobs:
|
jobs:
|
||||||
- job: Backend
|
- job:
|
||||||
strategy:
|
displayName: Build Variables
|
||||||
matrix:
|
|
||||||
Windows:
|
|
||||||
osName: 'Windows'
|
|
||||||
imageName: ${{ variables.windowsImage }}
|
|
||||||
enableAnalysis: 'false'
|
|
||||||
|
|
||||||
pool:
|
pool:
|
||||||
vmImage: $(imageName)
|
vmImage: ${{ variables.linuxImage }}
|
||||||
variables:
|
|
||||||
# Disable stylecop here - linting errors get caught by the analyze task
|
|
||||||
EnableAnalyzers: $(enableAnalysis)
|
|
||||||
steps:
|
steps:
|
||||||
# Set the build name properly. The 'name' property won't recursively expand so hack here:
|
# Set the build name properly. The 'name' property won't recursively expand so hack here:
|
||||||
- bash: echo "##vso[build.updatebuildnumber]$READARRVERSION"
|
- bash: echo "##vso[build.updatebuildnumber]$READARRVERSION"
|
||||||
displayName: Set Build Name
|
displayName: Set Build Name
|
||||||
- checkout: self
|
|
||||||
submodules: true
|
|
||||||
fetchDepth: 1
|
|
||||||
- task: UseDotNet@2
|
|
||||||
displayName: 'Install .net core'
|
|
||||||
inputs:
|
|
||||||
version: $(dotnetVersion)
|
|
||||||
- bash: |
|
- bash: |
|
||||||
SDK_PATH="${AGENT_TOOLSDIRECTORY}/dotnet/sdk/${DOTNETVERSION}"
|
if [[ $BUILD_REASON == "PullRequest" ]]; then
|
||||||
BUNDLEDVERSIONS="${SDK_PATH}/Microsoft.NETCoreSdk.BundledVersions.props"
|
git diff origin/develop...HEAD --name-only | grep -E "^(src/|azure-pipelines.yml)"
|
||||||
|
echo $? > not_backend_update
|
||||||
if ! grep -q freebsd-x64 $BUNDLEDVERSIONS; then
|
else
|
||||||
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64;linux-x86/' $BUNDLEDVERSIONS
|
echo 0 > not_backend_update
|
||||||
fi
|
fi
|
||||||
displayName: Extra Platform Support
|
cat not_backend_update
|
||||||
- task: Cache@2
|
displayName: Check for Backend File Changes
|
||||||
inputs:
|
- publish: not_backend_update
|
||||||
key: 'nuget | "$(Agent.OS)" | $(Build.SourcesDirectory)/src/Directory.Packages.props'
|
artifact: not_backend_update
|
||||||
path: $(nugetCacheFolder)
|
displayName: Publish update type
|
||||||
displayName: Cache NuGet packages
|
- stage: Build_Backend
|
||||||
- bash: ./build.sh --backend --enable-bsd
|
displayName: Build Backend
|
||||||
displayName: Build Readarr Backend
|
dependsOn: Setup
|
||||||
env:
|
|
||||||
NUGET_PACKAGES: $(nugetCacheFolder)
|
|
||||||
- powershell: Get-ChildItem _output\net6.0*,_output\*.Update\* -Recurse | Where { $_.Fullname -notlike "*\publish\*" -and $_.attributes -notlike "*directory*" } | Remove-Item
|
|
||||||
displayName: Clean up intermediate output
|
|
||||||
- publish: $(outputFolder)
|
|
||||||
artifact: '$(osName)Backend'
|
|
||||||
displayName: Publish Backend
|
|
||||||
- publish: '$(testsFolder)/net6.0/win-x64/publish'
|
|
||||||
artifact: win-x64-tests
|
|
||||||
displayName: Publish win-x64 Test Package
|
|
||||||
- publish: '$(testsFolder)/net6.0/linux-x64/publish'
|
|
||||||
artifact: linux-x64-tests
|
|
||||||
displayName: Publish linux-x64 Test Package
|
|
||||||
- publish: '$(testsFolder)/net6.0/linux-x86/publish'
|
|
||||||
artifact: linux-x86-tests
|
|
||||||
displayName: Publish linux-x86 Test Package
|
|
||||||
- publish: '$(testsFolder)/net6.0/linux-musl-x64/publish'
|
|
||||||
artifact: linux-musl-x64-tests
|
|
||||||
displayName: Publish linux-musl-x64 Test Package
|
|
||||||
- publish: '$(testsFolder)/net6.0/freebsd-x64/publish'
|
|
||||||
artifact: freebsd-x64-tests
|
|
||||||
displayName: Publish freebsd-x64 Test Package
|
|
||||||
- publish: '$(testsFolder)/net6.0/osx-x64/publish'
|
|
||||||
artifact: osx-x64-tests
|
|
||||||
displayName: Publish osx-x64 Test Package
|
|
||||||
|
|
||||||
- stage: Build_Backend_Other
|
|
||||||
displayName: Build Backend (Other OS)
|
|
||||||
dependsOn: []
|
|
||||||
jobs:
|
jobs:
|
||||||
- job: Backend
|
- job: Backend
|
||||||
strategy:
|
strategy:
|
||||||
@@ -122,6 +81,10 @@ stages:
|
|||||||
osName: 'Mac'
|
osName: 'Mac'
|
||||||
imageName: ${{ variables.macImage }}
|
imageName: ${{ variables.macImage }}
|
||||||
enableAnalysis: 'false'
|
enableAnalysis: 'false'
|
||||||
|
Windows:
|
||||||
|
osName: 'Windows'
|
||||||
|
imageName: ${{ variables.windowsImage }}
|
||||||
|
enableAnalysis: 'false'
|
||||||
|
|
||||||
pool:
|
pool:
|
||||||
vmImage: $(imageName)
|
vmImage: $(imageName)
|
||||||
@@ -137,22 +100,17 @@ stages:
|
|||||||
inputs:
|
inputs:
|
||||||
version: $(dotnetVersion)
|
version: $(dotnetVersion)
|
||||||
- bash: |
|
- bash: |
|
||||||
SDK_PATH="${AGENT_TOOLSDIRECTORY}/dotnet/sdk/${DOTNETVERSION}"
|
BUNDLEDVERSIONS=${AGENT_TOOLSDIRECTORY}/dotnet/sdk/${DOTNETVERSION}/Microsoft.NETCoreSdk.BundledVersions.props
|
||||||
BUNDLEDVERSIONS="${SDK_PATH}/Microsoft.NETCoreSdk.BundledVersions.props"
|
echo $BUNDLEDVERSIONS
|
||||||
|
if grep -q freebsd-x64 $BUNDLEDVERSIONS; then
|
||||||
if ! grep -q freebsd-x64 $BUNDLEDVERSIONS; then
|
echo "Extra platforms already enabled"
|
||||||
|
else
|
||||||
|
echo "Enabling extra platform support"
|
||||||
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64;linux-x86/' $BUNDLEDVERSIONS
|
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64;linux-x86/' $BUNDLEDVERSIONS
|
||||||
fi
|
fi
|
||||||
displayName: Extra Platform Support
|
displayName: Enable Extra Platform Support
|
||||||
- task: Cache@2
|
|
||||||
inputs:
|
|
||||||
key: 'nuget | "$(Agent.OS)" | $(Build.SourcesDirectory)/src/Directory.Packages.props'
|
|
||||||
path: $(nugetCacheFolder)
|
|
||||||
displayName: Cache NuGet packages
|
|
||||||
- bash: ./build.sh --backend --enable-extra-platforms
|
- bash: ./build.sh --backend --enable-extra-platforms
|
||||||
displayName: Build Readarr Backend
|
displayName: Build Readarr Backend
|
||||||
env:
|
|
||||||
NUGET_PACKAGES: $(nugetCacheFolder)
|
|
||||||
- bash: |
|
- bash: |
|
||||||
find ${OUTPUTFOLDER} -type f ! -path "*/publish/*" -exec rm -rf {} \;
|
find ${OUTPUTFOLDER} -type f ! -path "*/publish/*" -exec rm -rf {} \;
|
||||||
find ${OUTPUTFOLDER} -depth -empty -type d -exec rm -r "{}" \;
|
find ${OUTPUTFOLDER} -depth -empty -type d -exec rm -r "{}" \;
|
||||||
@@ -160,10 +118,38 @@ stages:
|
|||||||
find ${TESTSFOLDER} -depth -empty -type d -exec rm -r "{}" \;
|
find ${TESTSFOLDER} -depth -empty -type d -exec rm -r "{}" \;
|
||||||
displayName: Clean up intermediate output
|
displayName: Clean up intermediate output
|
||||||
condition: and(succeeded(), ne(variables['osName'], 'Windows'))
|
condition: and(succeeded(), ne(variables['osName'], 'Windows'))
|
||||||
|
- publish: $(outputFolder)
|
||||||
|
artifact: '$(osName)Backend'
|
||||||
|
displayName: Publish Backend
|
||||||
|
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||||
|
- publish: '$(testsFolder)/net6.0/win-x64/publish'
|
||||||
|
artifact: win-x64-tests
|
||||||
|
displayName: Publish win-x64 Test Package
|
||||||
|
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||||
|
- publish: '$(testsFolder)/net6.0/linux-x64/publish'
|
||||||
|
artifact: linux-x64-tests
|
||||||
|
displayName: Publish linux-x64 Test Package
|
||||||
|
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||||
|
- publish: '$(testsFolder)/net6.0/linux-x86/publish'
|
||||||
|
artifact: linux-x86-tests
|
||||||
|
displayName: Publish linux-x86 Test Package
|
||||||
|
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||||
|
- publish: '$(testsFolder)/net6.0/linux-musl-x64/publish'
|
||||||
|
artifact: linux-musl-x64-tests
|
||||||
|
displayName: Publish linux-musl-x64 Test Package
|
||||||
|
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||||
|
- publish: '$(testsFolder)/net6.0/freebsd-x64/publish'
|
||||||
|
artifact: freebsd-x64-tests
|
||||||
|
displayName: Publish freebsd-x64 Test Package
|
||||||
|
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||||
|
- publish: '$(testsFolder)/net6.0/osx-x64/publish'
|
||||||
|
artifact: osx-x64-tests
|
||||||
|
displayName: Publish osx-x64 Test Package
|
||||||
|
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||||
|
|
||||||
- stage: Build_Frontend
|
- stage: Build_Frontend
|
||||||
displayName: Frontend
|
displayName: Frontend
|
||||||
dependsOn: []
|
dependsOn: Setup
|
||||||
jobs:
|
jobs:
|
||||||
- job: Build
|
- job: Build
|
||||||
strategy:
|
strategy:
|
||||||
@@ -180,10 +166,10 @@ stages:
|
|||||||
pool:
|
pool:
|
||||||
vmImage: $(imageName)
|
vmImage: $(imageName)
|
||||||
steps:
|
steps:
|
||||||
- task: NodeTool@0
|
- task: UseNode@1
|
||||||
displayName: Set Node.js version
|
displayName: Set Node.js version
|
||||||
inputs:
|
inputs:
|
||||||
versionSpec: $(nodeVersion)
|
version: $(nodeVersion)
|
||||||
- checkout: self
|
- checkout: self
|
||||||
submodules: true
|
submodules: true
|
||||||
fetchDepth: 1
|
fetchDepth: 1
|
||||||
@@ -192,7 +178,6 @@ stages:
|
|||||||
key: 'yarn | "$(osName)" | yarn.lock'
|
key: 'yarn | "$(osName)" | yarn.lock'
|
||||||
restoreKeys: |
|
restoreKeys: |
|
||||||
yarn | "$(osName)"
|
yarn | "$(osName)"
|
||||||
yarn
|
|
||||||
path: $(yarnCacheFolder)
|
path: $(yarnCacheFolder)
|
||||||
displayName: Cache Yarn packages
|
displayName: Cache Yarn packages
|
||||||
- bash: ./build.sh --frontend
|
- bash: ./build.sh --frontend
|
||||||
@@ -204,10 +189,10 @@ stages:
|
|||||||
artifact: '$(osName)Frontend'
|
artifact: '$(osName)Frontend'
|
||||||
displayName: Publish Frontend
|
displayName: Publish Frontend
|
||||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||||
|
|
||||||
- stage: Installer
|
- stage: Installer
|
||||||
dependsOn:
|
dependsOn:
|
||||||
- Build_Backend_Windows
|
- Build_Backend
|
||||||
- Build_Frontend
|
- Build_Frontend
|
||||||
jobs:
|
jobs:
|
||||||
- job: Windows_Installer
|
- job: Windows_Installer
|
||||||
@@ -231,8 +216,8 @@ stages:
|
|||||||
displayName: Fetch Frontend
|
displayName: Fetch Frontend
|
||||||
- bash: |
|
- bash: |
|
||||||
./build.sh --packages --installer
|
./build.sh --packages --installer
|
||||||
cp setup/output/Readarr.*win-x64.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Readarr.${BUILDNAME}.windows-core-x64-installer.exe
|
cp distribution/windows/setup/output/Readarr.*win-x64.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Readarr.${BUILDNAME}.windows-core-x64-installer.exe
|
||||||
cp setup/output/Readarr.*win-x86.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Readarr.${BUILDNAME}.windows-core-x86-installer.exe
|
cp distribution/windows/setup/output/Readarr.*win-x86.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Readarr.${BUILDNAME}.windows-core-x86-installer.exe
|
||||||
displayName: Create Installers
|
displayName: Create Installers
|
||||||
- publish: $(Build.ArtifactStagingDirectory)
|
- publish: $(Build.ArtifactStagingDirectory)
|
||||||
artifact: 'WindowsInstaller'
|
artifact: 'WindowsInstaller'
|
||||||
@@ -240,7 +225,7 @@ stages:
|
|||||||
|
|
||||||
- stage: Packages
|
- stage: Packages
|
||||||
dependsOn:
|
dependsOn:
|
||||||
- Build_Backend_Windows
|
- Build_Backend
|
||||||
- Build_Frontend
|
- Build_Frontend
|
||||||
jobs:
|
jobs:
|
||||||
- job: Other_Packages
|
- job: Other_Packages
|
||||||
@@ -406,14 +391,29 @@ stages:
|
|||||||
SENTRY_AUTH_TOKEN: $(sentryAuthTokenServarr)
|
SENTRY_AUTH_TOKEN: $(sentryAuthTokenServarr)
|
||||||
SENTRY_ORG: $(sentryOrg)
|
SENTRY_ORG: $(sentryOrg)
|
||||||
SENTRY_URL: $(sentryUrl)
|
SENTRY_URL: $(sentryUrl)
|
||||||
|
|
||||||
- stage: Unit_Test
|
- stage: Unit_Test
|
||||||
displayName: Unit Tests
|
displayName: Unit Tests
|
||||||
dependsOn: Build_Backend_Windows
|
dependsOn: Build_Backend
|
||||||
condition: succeeded()
|
|
||||||
jobs:
|
jobs:
|
||||||
|
- job: Prepare
|
||||||
|
pool:
|
||||||
|
vmImage: ${{ variables.linuxImage }}
|
||||||
|
steps:
|
||||||
|
- checkout: none
|
||||||
|
- task: DownloadPipelineArtifact@2
|
||||||
|
inputs:
|
||||||
|
buildType: 'current'
|
||||||
|
artifactName: 'not_backend_update'
|
||||||
|
targetPath: '.'
|
||||||
|
- bash: echo "##vso[task.setvariable variable=backendNotUpdated;isOutput=true]$(cat not_backend_update)"
|
||||||
|
name: setVar
|
||||||
|
|
||||||
- job: Unit
|
- job: Unit
|
||||||
displayName: Unit Native
|
displayName: Unit Native
|
||||||
|
dependsOn: Prepare
|
||||||
|
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||||
workspace:
|
workspace:
|
||||||
clean: all
|
clean: all
|
||||||
|
|
||||||
@@ -479,6 +479,8 @@ stages:
|
|||||||
|
|
||||||
- job: Unit_Docker
|
- job: Unit_Docker
|
||||||
displayName: Unit Docker
|
displayName: Unit Docker
|
||||||
|
dependsOn: Prepare
|
||||||
|
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
alpine:
|
alpine:
|
||||||
@@ -492,11 +494,11 @@ stages:
|
|||||||
|
|
||||||
pool:
|
pool:
|
||||||
vmImage: ${{ variables.linuxImage }}
|
vmImage: ${{ variables.linuxImage }}
|
||||||
|
|
||||||
container: $[ variables['containerImage'] ]
|
container: $[ variables['containerImage'] ]
|
||||||
|
|
||||||
timeoutInMinutes: 10
|
timeoutInMinutes: 10
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- task: UseDotNet@2
|
- task: UseDotNet@2
|
||||||
displayName: 'Install .NET'
|
displayName: 'Install .NET'
|
||||||
@@ -530,12 +532,14 @@ stages:
|
|||||||
testResultsFiles: '**/TestResult.xml'
|
testResultsFiles: '**/TestResult.xml'
|
||||||
testRunTitle: '$(testName) Unit Tests'
|
testRunTitle: '$(testName) Unit Tests'
|
||||||
failTaskOnFailedTests: true
|
failTaskOnFailedTests: true
|
||||||
|
|
||||||
- job: Unit_LinuxCore_Postgres
|
- job: Unit_LinuxCore_Postgres14
|
||||||
displayName: Unit Native LinuxCore with Postgres Database
|
displayName: Unit Native LinuxCore with Postgres14 Database
|
||||||
|
dependsOn: Prepare
|
||||||
|
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||||
variables:
|
variables:
|
||||||
pattern: 'Readarr.*.linux-core-x64.tar.gz'
|
pattern: 'Readarr.*.linux-core-x64.tar.gz'
|
||||||
artifactName: LinuxCoreTests
|
artifactName: linux-x64-tests
|
||||||
Readarr__Postgres__Host: 'localhost'
|
Readarr__Postgres__Host: 'localhost'
|
||||||
Readarr__Postgres__Port: '5432'
|
Readarr__Postgres__Port: '5432'
|
||||||
Readarr__Postgres__User: 'readarr'
|
Readarr__Postgres__User: 'readarr'
|
||||||
@@ -545,7 +549,7 @@ stages:
|
|||||||
vmImage: ${{ variables.linuxImage }}
|
vmImage: ${{ variables.linuxImage }}
|
||||||
|
|
||||||
timeoutInMinutes: 10
|
timeoutInMinutes: 10
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- task: UseDotNet@2
|
- task: UseDotNet@2
|
||||||
displayName: 'Install .net core'
|
displayName: 'Install .net core'
|
||||||
@@ -556,7 +560,7 @@ stages:
|
|||||||
displayName: Download Test Artifact
|
displayName: Download Test Artifact
|
||||||
inputs:
|
inputs:
|
||||||
buildType: 'current'
|
buildType: 'current'
|
||||||
artifactName: 'linux-x64-Tests'
|
artifactName: $(artifactName)
|
||||||
targetPath: $(testsFolder)
|
targetPath: $(testsFolder)
|
||||||
- bash: find ${TESTSFOLDER} -name "Readarr.Test.Dummy" -exec chmod a+x {} \;
|
- bash: find ${TESTSFOLDER} -name "Readarr.Test.Dummy" -exec chmod a+x {} \;
|
||||||
displayName: Make Test Dummy Executable
|
displayName: Make Test Dummy Executable
|
||||||
@@ -579,15 +583,84 @@ stages:
|
|||||||
inputs:
|
inputs:
|
||||||
testResultsFormat: 'NUnit'
|
testResultsFormat: 'NUnit'
|
||||||
testResultsFiles: '**/TestResult.xml'
|
testResultsFiles: '**/TestResult.xml'
|
||||||
testRunTitle: 'LinuxCore Postgres Unit Tests'
|
testRunTitle: 'LinuxCore Postgres14 Unit Tests'
|
||||||
|
failTaskOnFailedTests: true
|
||||||
|
|
||||||
|
- job: Unit_LinuxCore_Postgres15
|
||||||
|
displayName: Unit Native LinuxCore with Postgres15 Database
|
||||||
|
dependsOn: Prepare
|
||||||
|
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||||
|
variables:
|
||||||
|
pattern: 'Readarr.*.linux-core-x64.tar.gz'
|
||||||
|
artifactName: linux-x64-tests
|
||||||
|
Readarr__Postgres__Host: 'localhost'
|
||||||
|
Readarr__Postgres__Port: '5432'
|
||||||
|
Readarr__Postgres__User: 'readarr'
|
||||||
|
Readarr__Postgres__Password: 'readarr'
|
||||||
|
|
||||||
|
pool:
|
||||||
|
vmImage: ${{ variables.linuxImage }}
|
||||||
|
|
||||||
|
timeoutInMinutes: 10
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- task: UseDotNet@2
|
||||||
|
displayName: 'Install .net core'
|
||||||
|
inputs:
|
||||||
|
version: $(dotnetVersion)
|
||||||
|
- checkout: none
|
||||||
|
- task: DownloadPipelineArtifact@2
|
||||||
|
displayName: Download Test Artifact
|
||||||
|
inputs:
|
||||||
|
buildType: 'current'
|
||||||
|
artifactName: $(artifactName)
|
||||||
|
targetPath: $(testsFolder)
|
||||||
|
- bash: find ${TESTSFOLDER} -name "Readarr.Test.Dummy" -exec chmod a+x {} \;
|
||||||
|
displayName: Make Test Dummy Executable
|
||||||
|
condition: and(succeeded(), ne(variables['osName'], 'Windows'))
|
||||||
|
- bash: |
|
||||||
|
docker run -d --name=postgres15 \
|
||||||
|
-e POSTGRES_PASSWORD=readarr \
|
||||||
|
-e POSTGRES_USER=readarr \
|
||||||
|
-p 5432:5432/tcp \
|
||||||
|
-v /usr/share/zoneinfo/America/Chicago:/etc/localtime:ro \
|
||||||
|
postgres:15
|
||||||
|
displayName: Start postgres
|
||||||
|
- bash: |
|
||||||
|
chmod a+x ${TESTSFOLDER}/test.sh
|
||||||
|
ls -lR ${TESTSFOLDER}
|
||||||
|
${TESTSFOLDER}/test.sh Linux Unit Test
|
||||||
|
displayName: Run Tests
|
||||||
|
- task: PublishTestResults@2
|
||||||
|
displayName: Publish Test Results
|
||||||
|
inputs:
|
||||||
|
testResultsFormat: 'NUnit'
|
||||||
|
testResultsFiles: '**/TestResult.xml'
|
||||||
|
testRunTitle: 'LinuxCore Postgres15 Unit Tests'
|
||||||
failTaskOnFailedTests: true
|
failTaskOnFailedTests: true
|
||||||
|
|
||||||
- stage: Integration
|
- stage: Integration
|
||||||
displayName: Integration
|
displayName: Integration
|
||||||
dependsOn: Packages
|
dependsOn: Packages
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
- job: Prepare
|
||||||
|
pool:
|
||||||
|
vmImage: ${{ variables.linuxImage }}
|
||||||
|
steps:
|
||||||
|
- checkout: none
|
||||||
|
- task: DownloadPipelineArtifact@2
|
||||||
|
inputs:
|
||||||
|
buildType: 'current'
|
||||||
|
artifactName: 'not_backend_update'
|
||||||
|
targetPath: '.'
|
||||||
|
- bash: echo "##vso[task.setvariable variable=backendNotUpdated;isOutput=true]$(cat not_backend_update)"
|
||||||
|
name: setVar
|
||||||
|
|
||||||
- job: Integration_Native
|
- job: Integration_Native
|
||||||
displayName: Integration Native
|
displayName: Integration Native
|
||||||
|
dependsOn: Prepare
|
||||||
|
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
MacCore:
|
MacCore:
|
||||||
@@ -608,7 +681,7 @@ stages:
|
|||||||
|
|
||||||
pool:
|
pool:
|
||||||
vmImage: $(imageName)
|
vmImage: $(imageName)
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- task: UseDotNet@2
|
- task: UseDotNet@2
|
||||||
displayName: 'Install .net core'
|
displayName: 'Install .net core'
|
||||||
@@ -630,7 +703,7 @@ stages:
|
|||||||
targetPath: $(Build.ArtifactStagingDirectory)
|
targetPath: $(Build.ArtifactStagingDirectory)
|
||||||
- task: ExtractFiles@1
|
- task: ExtractFiles@1
|
||||||
inputs:
|
inputs:
|
||||||
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
|
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
|
||||||
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
|
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
|
||||||
displayName: Extract Package
|
displayName: Extract Package
|
||||||
- bash: |
|
- bash: |
|
||||||
@@ -649,8 +722,10 @@ stages:
|
|||||||
failTaskOnFailedTests: true
|
failTaskOnFailedTests: true
|
||||||
displayName: Publish Test Results
|
displayName: Publish Test Results
|
||||||
|
|
||||||
- job: Integration_LinuxCore_Postgres
|
- job: Integration_LinuxCore_Postgres14
|
||||||
displayName: Integration Native LinuxCore with Postgres Database
|
displayName: Integration Native LinuxCore with Postgres14 Database
|
||||||
|
dependsOn: Prepare
|
||||||
|
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||||
variables:
|
variables:
|
||||||
pattern: 'Readarr.*.linux-core-x64.tar.gz'
|
pattern: 'Readarr.*.linux-core-x64.tar.gz'
|
||||||
Readarr__Postgres__Host: 'localhost'
|
Readarr__Postgres__Host: 'localhost'
|
||||||
@@ -682,7 +757,7 @@ stages:
|
|||||||
targetPath: $(Build.ArtifactStagingDirectory)
|
targetPath: $(Build.ArtifactStagingDirectory)
|
||||||
- task: ExtractFiles@1
|
- task: ExtractFiles@1
|
||||||
inputs:
|
inputs:
|
||||||
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
|
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
|
||||||
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
|
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
|
||||||
displayName: Extract Package
|
displayName: Extract Package
|
||||||
- bash: |
|
- bash: |
|
||||||
@@ -705,12 +780,77 @@ stages:
|
|||||||
inputs:
|
inputs:
|
||||||
testResultsFormat: 'NUnit'
|
testResultsFormat: 'NUnit'
|
||||||
testResultsFiles: '**/TestResult.xml'
|
testResultsFiles: '**/TestResult.xml'
|
||||||
testRunTitle: 'Integration LinuxCore Postgres Database Integration Tests'
|
testRunTitle: 'Integration LinuxCore Postgres14 Database Integration Tests'
|
||||||
|
failTaskOnFailedTests: true
|
||||||
|
displayName: Publish Test Results
|
||||||
|
|
||||||
|
|
||||||
|
- job: Integration_LinuxCore_Postgres15
|
||||||
|
displayName: Integration Native LinuxCore with Postgres Database
|
||||||
|
dependsOn: Prepare
|
||||||
|
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||||
|
variables:
|
||||||
|
pattern: 'Readarr.*.linux-core-x64.tar.gz'
|
||||||
|
Readarr__Postgres__Host: 'localhost'
|
||||||
|
Readarr__Postgres__Port: '5432'
|
||||||
|
Readarr__Postgres__User: 'readarr'
|
||||||
|
Readarr__Postgres__Password: 'readarr'
|
||||||
|
|
||||||
|
pool:
|
||||||
|
vmImage: ${{ variables.linuxImage }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- task: UseDotNet@2
|
||||||
|
displayName: 'Install .net core'
|
||||||
|
inputs:
|
||||||
|
version: $(dotnetVersion)
|
||||||
|
- checkout: none
|
||||||
|
- task: DownloadPipelineArtifact@2
|
||||||
|
displayName: Download Test Artifact
|
||||||
|
inputs:
|
||||||
|
buildType: 'current'
|
||||||
|
artifactName: 'linux-x64-tests'
|
||||||
|
targetPath: $(testsFolder)
|
||||||
|
- task: DownloadPipelineArtifact@2
|
||||||
|
displayName: Download Build Artifact
|
||||||
|
inputs:
|
||||||
|
buildType: 'current'
|
||||||
|
artifactName: Packages
|
||||||
|
itemPattern: '**/$(pattern)'
|
||||||
|
targetPath: $(Build.ArtifactStagingDirectory)
|
||||||
|
- task: ExtractFiles@1
|
||||||
|
inputs:
|
||||||
|
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
|
||||||
|
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
|
||||||
|
displayName: Extract Package
|
||||||
|
- bash: |
|
||||||
|
mkdir -p ./bin/
|
||||||
|
cp -r -v ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin/Readarr/. ./bin/
|
||||||
|
displayName: Move Package Contents
|
||||||
|
- bash: |
|
||||||
|
docker run -d --name=postgres15 \
|
||||||
|
-e POSTGRES_PASSWORD=readarr \
|
||||||
|
-e POSTGRES_USER=readarr \
|
||||||
|
-p 5432:5432/tcp \
|
||||||
|
-v /usr/share/zoneinfo/America/Chicago:/etc/localtime:ro \
|
||||||
|
postgres:15
|
||||||
|
displayName: Start postgres
|
||||||
|
- bash: |
|
||||||
|
chmod a+x ${TESTSFOLDER}/test.sh
|
||||||
|
${TESTSFOLDER}/test.sh Linux Integration Test
|
||||||
|
displayName: Run Integration Tests
|
||||||
|
- task: PublishTestResults@2
|
||||||
|
inputs:
|
||||||
|
testResultsFormat: 'NUnit'
|
||||||
|
testResultsFiles: '**/TestResult.xml'
|
||||||
|
testRunTitle: 'Integration LinuxCore Postgres15 Database Integration Tests'
|
||||||
failTaskOnFailedTests: true
|
failTaskOnFailedTests: true
|
||||||
displayName: Publish Test Results
|
displayName: Publish Test Results
|
||||||
|
|
||||||
- job: Integration_FreeBSD
|
- job: Integration_FreeBSD
|
||||||
displayName: Integration Native FreeBSD
|
displayName: Integration Native FreeBSD
|
||||||
|
dependsOn: Prepare
|
||||||
|
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||||
workspace:
|
workspace:
|
||||||
clean: all
|
clean: all
|
||||||
variables:
|
variables:
|
||||||
@@ -755,6 +895,8 @@ stages:
|
|||||||
|
|
||||||
- job: Integration_Docker
|
- job: Integration_Docker
|
||||||
displayName: Integration Docker
|
displayName: Integration Docker
|
||||||
|
dependsOn: Prepare
|
||||||
|
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
alpine:
|
alpine:
|
||||||
@@ -773,7 +915,7 @@ stages:
|
|||||||
container: $[ variables['containerImage'] ]
|
container: $[ variables['containerImage'] ]
|
||||||
|
|
||||||
timeoutInMinutes: 15
|
timeoutInMinutes: 15
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- task: UseDotNet@2
|
- task: UseDotNet@2
|
||||||
displayName: 'Install .NET'
|
displayName: 'Install .NET'
|
||||||
@@ -801,7 +943,7 @@ stages:
|
|||||||
targetPath: $(Build.ArtifactStagingDirectory)
|
targetPath: $(Build.ArtifactStagingDirectory)
|
||||||
- task: ExtractFiles@1
|
- task: ExtractFiles@1
|
||||||
inputs:
|
inputs:
|
||||||
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
|
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
|
||||||
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
|
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
|
||||||
displayName: Extract Package
|
displayName: Extract Package
|
||||||
- bash: |
|
- bash: |
|
||||||
@@ -823,7 +965,7 @@ stages:
|
|||||||
- stage: Automation
|
- stage: Automation
|
||||||
displayName: Automation
|
displayName: Automation
|
||||||
dependsOn: Packages
|
dependsOn: Packages
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
- job: Automation
|
- job: Automation
|
||||||
strategy:
|
strategy:
|
||||||
@@ -833,20 +975,23 @@ stages:
|
|||||||
artifactName: 'linux-x64'
|
artifactName: 'linux-x64'
|
||||||
imageName: ${{ variables.linuxImage }}
|
imageName: ${{ variables.linuxImage }}
|
||||||
pattern: 'Readarr.*.linux-core-x64.tar.gz'
|
pattern: 'Readarr.*.linux-core-x64.tar.gz'
|
||||||
|
failBuild: true
|
||||||
Mac:
|
Mac:
|
||||||
osName: 'Mac'
|
osName: 'Mac'
|
||||||
artifactName: 'osx-x64'
|
artifactName: 'osx-x64'
|
||||||
imageName: ${{ variables.macImage }}
|
imageName: ${{ variables.macImage }}
|
||||||
pattern: 'Readarr.*.osx-core-x64.tar.gz'
|
pattern: 'Readarr.*.osx-core-x64.tar.gz'
|
||||||
|
failBuild: true
|
||||||
Windows:
|
Windows:
|
||||||
osName: 'Windows'
|
osName: 'Windows'
|
||||||
artifactName: 'win-x64'
|
artifactName: 'win-x64'
|
||||||
imageName: ${{ variables.windowsImage }}
|
imageName: ${{ variables.windowsImage }}
|
||||||
pattern: 'Readarr.*.windows-core-x64.zip'
|
pattern: 'Readarr.*.windows-core-x64.zip'
|
||||||
|
failBuild: true
|
||||||
|
|
||||||
pool:
|
pool:
|
||||||
vmImage: $(imageName)
|
vmImage: $(imageName)
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- task: UseDotNet@2
|
- task: UseDotNet@2
|
||||||
displayName: 'Install .net core'
|
displayName: 'Install .net core'
|
||||||
@@ -868,7 +1013,7 @@ stages:
|
|||||||
targetPath: $(Build.ArtifactStagingDirectory)
|
targetPath: $(Build.ArtifactStagingDirectory)
|
||||||
- task: ExtractFiles@1
|
- task: ExtractFiles@1
|
||||||
inputs:
|
inputs:
|
||||||
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
|
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
|
||||||
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
|
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
|
||||||
displayName: Extract Package
|
displayName: Extract Package
|
||||||
- bash: |
|
- bash: |
|
||||||
@@ -888,20 +1033,35 @@ stages:
|
|||||||
TargetFolder: '$(Build.ArtifactStagingDirectory)/screenshots'
|
TargetFolder: '$(Build.ArtifactStagingDirectory)/screenshots'
|
||||||
- publish: $(Build.ArtifactStagingDirectory)/screenshots
|
- publish: $(Build.ArtifactStagingDirectory)/screenshots
|
||||||
artifact: '$(osName)AutomationScreenshots'
|
artifact: '$(osName)AutomationScreenshots'
|
||||||
condition: and(succeeded(), eq(variables['System.JobAttempt'], '1'))
|
|
||||||
displayName: Publish Screenshot Bundle
|
displayName: Publish Screenshot Bundle
|
||||||
|
condition: and(succeeded(), eq(variables['System.JobAttempt'], '1'))
|
||||||
- task: PublishTestResults@2
|
- task: PublishTestResults@2
|
||||||
inputs:
|
inputs:
|
||||||
testResultsFormat: 'NUnit'
|
testResultsFormat: 'NUnit'
|
||||||
testResultsFiles: '**/TestResult.xml'
|
testResultsFiles: '**/TestResult.xml'
|
||||||
testRunTitle: '$(osName) Automation Tests'
|
testRunTitle: '$(osName) Automation Tests'
|
||||||
failTaskOnFailedTests: true
|
failTaskOnFailedTests: $(failBuild)
|
||||||
displayName: Publish Test Results
|
displayName: Publish Test Results
|
||||||
|
|
||||||
- stage: Analyze
|
- stage: Analyze
|
||||||
dependsOn: []
|
dependsOn:
|
||||||
|
- Setup
|
||||||
displayName: Analyze
|
displayName: Analyze
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
- job: Prepare
|
||||||
|
pool:
|
||||||
|
vmImage: ${{ variables.linuxImage }}
|
||||||
|
steps:
|
||||||
|
- checkout: none
|
||||||
|
- task: DownloadPipelineArtifact@2
|
||||||
|
inputs:
|
||||||
|
buildType: 'current'
|
||||||
|
artifactName: 'not_backend_update'
|
||||||
|
targetPath: '.'
|
||||||
|
- bash: echo "##vso[task.setvariable variable=backendNotUpdated;isOutput=true]$(cat not_backend_update)"
|
||||||
|
name: setVar
|
||||||
|
|
||||||
- job: Lint_Frontend
|
- job: Lint_Frontend
|
||||||
displayName: Lint Frontend
|
displayName: Lint Frontend
|
||||||
strategy:
|
strategy:
|
||||||
@@ -915,10 +1075,10 @@ stages:
|
|||||||
pool:
|
pool:
|
||||||
vmImage: $(imageName)
|
vmImage: $(imageName)
|
||||||
steps:
|
steps:
|
||||||
- task: NodeTool@0
|
- task: UseNode@1
|
||||||
displayName: Set Node.js version
|
displayName: Set Node.js version
|
||||||
inputs:
|
inputs:
|
||||||
versionSpec: $(nodeVersion)
|
version: $(nodeVersion)
|
||||||
- checkout: self
|
- checkout: self
|
||||||
submodules: true
|
submodules: true
|
||||||
fetchDepth: 1
|
fetchDepth: 1
|
||||||
@@ -927,7 +1087,6 @@ stages:
|
|||||||
key: 'yarn | "$(osName)" | yarn.lock'
|
key: 'yarn | "$(osName)" | yarn.lock'
|
||||||
restoreKeys: |
|
restoreKeys: |
|
||||||
yarn | "$(osName)"
|
yarn | "$(osName)"
|
||||||
yarn
|
|
||||||
path: $(yarnCacheFolder)
|
path: $(yarnCacheFolder)
|
||||||
displayName: Cache Yarn packages
|
displayName: Cache Yarn packages
|
||||||
- bash: ./build.sh --lint
|
- bash: ./build.sh --lint
|
||||||
@@ -943,24 +1102,29 @@ stages:
|
|||||||
vmImage: ${{ variables.windowsImage }}
|
vmImage: ${{ variables.windowsImage }}
|
||||||
steps:
|
steps:
|
||||||
- checkout: self # Need history for Sonar analysis
|
- checkout: self # Need history for Sonar analysis
|
||||||
- task: SonarCloudPrepare@1
|
- task: SonarCloudPrepare@3
|
||||||
env:
|
env:
|
||||||
SONAR_SCANNER_OPTS: ''
|
SONAR_SCANNER_OPTS: ''
|
||||||
inputs:
|
inputs:
|
||||||
SonarCloud: 'SonarCloud'
|
SonarCloud: 'SonarCloud'
|
||||||
organization: 'readarr'
|
organization: 'readarr'
|
||||||
scannerMode: 'CLI'
|
scannerMode: 'cli'
|
||||||
configMode: 'manual'
|
configMode: 'manual'
|
||||||
cliProjectKey: 'readarrui'
|
cliProjectKey: 'readarrui'
|
||||||
cliProjectName: 'ReadarrUI'
|
cliProjectName: 'ReadarrUI'
|
||||||
cliProjectVersion: '$(readarrVersion)'
|
cliProjectVersion: '$(readarrVersion)'
|
||||||
cliSources: './frontend'
|
cliSources: './frontend'
|
||||||
- task: SonarCloudAnalyze@1
|
- task: SonarCloudAnalyze@3
|
||||||
|
|
||||||
- job: Api_Docs
|
- job: Api_Docs
|
||||||
displayName: API Docs
|
displayName: API Docs
|
||||||
|
dependsOn: Prepare
|
||||||
condition: |
|
condition: |
|
||||||
and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/develop'))
|
and
|
||||||
|
(
|
||||||
|
and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/develop')),
|
||||||
|
and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||||
|
)
|
||||||
|
|
||||||
pool:
|
pool:
|
||||||
vmImage: ${{ variables.windowsImage }}
|
vmImage: ${{ variables.windowsImage }}
|
||||||
@@ -973,7 +1137,7 @@ stages:
|
|||||||
- checkout: self
|
- checkout: self
|
||||||
submodules: true
|
submodules: true
|
||||||
persistCredentials: true
|
persistCredentials: true
|
||||||
fetchDepth: 1
|
fetchDepth: 1
|
||||||
- bash: ./docs.sh Windows
|
- bash: ./docs.sh Windows
|
||||||
displayName: Create openapi.json
|
displayName: Create openapi.json
|
||||||
- bash: |
|
- bash: |
|
||||||
@@ -981,10 +1145,9 @@ stages:
|
|||||||
git config --global user.name "Servarr"
|
git config --global user.name "Servarr"
|
||||||
git checkout -b api-docs
|
git checkout -b api-docs
|
||||||
git add .
|
git add .
|
||||||
git status
|
if git status | grep -q modified
|
||||||
if git status | grep modified
|
|
||||||
then
|
then
|
||||||
git commit -am 'Automated API Docs update [skip ci]'
|
git commit -am 'Automated API Docs update'
|
||||||
git push -f --set-upstream origin api-docs
|
git push -f --set-upstream origin api-docs
|
||||||
curl -X POST -H "Authorization: token ${GITHUBTOKEN}" -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/readarr/readarr/pulls -d '{"head":"api-docs","base":"develop","title":"Update API docs"}'
|
curl -X POST -H "Authorization: token ${GITHUBTOKEN}" -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/readarr/readarr/pulls -d '{"head":"api-docs","base":"develop","title":"Update API docs"}'
|
||||||
else
|
else
|
||||||
@@ -1008,68 +1171,53 @@ stages:
|
|||||||
|
|
||||||
- job: Analyze_Backend
|
- job: Analyze_Backend
|
||||||
displayName: Backend
|
displayName: Backend
|
||||||
|
dependsOn: Prepare
|
||||||
|
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||||
|
|
||||||
variables:
|
variables:
|
||||||
disable.coverage.autogenerate: 'true'
|
disable.coverage.autogenerate: 'true'
|
||||||
|
EnableAnalyzers: 'false'
|
||||||
|
|
||||||
pool:
|
pool:
|
||||||
vmImage: ${{ variables.linuxImage }}
|
vmImage: ${{ variables.windowsImage }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- task: UseDotNet@2
|
- task: UseDotNet@2
|
||||||
displayName: 'Install .net core 2.1'
|
displayName: 'Install .net core'
|
||||||
inputs:
|
|
||||||
version: 2.1.815
|
|
||||||
- task: UseDotNet@2
|
|
||||||
displayName: 'Install .net core 3.1'
|
|
||||||
inputs:
|
|
||||||
version: 3.1.413
|
|
||||||
- task: UseDotNet@2
|
|
||||||
displayName: 'Install .net core 5.0'
|
|
||||||
inputs:
|
inputs:
|
||||||
version: $(dotnetVersion)
|
version: $(dotnetVersion)
|
||||||
- checkout: self # Need history for Sonar analysis
|
- checkout: self # Need history for Sonar analysis
|
||||||
submodules: true
|
submodules: true
|
||||||
- task: Cache@2
|
- powershell: Set-Service SCardSvr -StartupType Manual
|
||||||
inputs:
|
displayName: Enable Windows Test Service
|
||||||
key: 'nuget | "$(Agent.OS)" | $(Build.SourcesDirectory)/src/Directory.Packages.props'
|
- task: SonarCloudPrepare@3
|
||||||
path: $(nugetCacheFolder)
|
|
||||||
displayName: Cache NuGet packages
|
|
||||||
|
|
||||||
- task: SonarCloudPrepare@1
|
|
||||||
condition: eq(variables['System.PullRequest.IsFork'], 'False')
|
condition: eq(variables['System.PullRequest.IsFork'], 'False')
|
||||||
inputs:
|
inputs:
|
||||||
SonarCloud: 'SonarCloud'
|
SonarCloud: 'SonarCloud'
|
||||||
organization: 'readarr'
|
organization: 'readarr'
|
||||||
scannerMode: 'MSBuild'
|
scannerMode: 'dotnet'
|
||||||
projectKey: 'Readarr_Readarr'
|
projectKey: 'Readarr_Readarr'
|
||||||
projectName: 'Readarr'
|
projectName: 'Readarr'
|
||||||
projectVersion: '$(readarrVersion)'
|
projectVersion: '$(readarrVersion)'
|
||||||
extraProperties: |
|
extraProperties: |
|
||||||
sonar.exclusions=**/obj/**,**/*.dll,**/NzbDrone.Core.Test/Files/**/*,./frontend/**,./src/Libraries/**
|
sonar.exclusions=**/obj/**,**/*.dll,**/NzbDrone.Core.Test/Files/**/*,./frontend/**,**/ExternalModules/**,./src/Libraries/**
|
||||||
sonar.coverage.exclusions=**/Readarr.Api.V1/**/*
|
sonar.coverage.exclusions=**/Readarr.Api.V1/**/*
|
||||||
sonar.cs.opencover.reportsPaths=$(Build.SourcesDirectory)/CoverageResults/**/coverage.opencover.xml
|
sonar.cs.opencover.reportsPaths=$(Build.SourcesDirectory)/CoverageResults/**/coverage.opencover.xml
|
||||||
sonar.cs.nunit.reportsPaths=$(Build.SourcesDirectory)/TestResult.xml
|
sonar.cs.nunit.reportsPaths=$(Build.SourcesDirectory)/TestResult.xml
|
||||||
- bash: |
|
- bash: |
|
||||||
./build.sh --backend -f net6.0 -r linux-x64
|
./build.sh --backend -f net6.0 -r win-x64
|
||||||
TEST_DIR=_tests/net6.0/linux-x64/publish/ ./test.sh Linux Unit Coverage
|
TEST_DIR=_tests/net6.0/win-x64/publish/ ./test.sh Windows Unit Coverage
|
||||||
displayName: Coverage Unit Tests
|
displayName: Coverage Unit Tests
|
||||||
env:
|
- task: SonarCloudAnalyze@3
|
||||||
NUGET_PACKAGES: $(nugetCacheFolder)
|
|
||||||
- task: SonarCloudAnalyze@1
|
|
||||||
condition: eq(variables['System.PullRequest.IsFork'], 'False')
|
condition: eq(variables['System.PullRequest.IsFork'], 'False')
|
||||||
displayName: Publish SonarCloud Results
|
displayName: Publish SonarCloud Results
|
||||||
- task: reportgenerator@4
|
- task: reportgenerator@5.3.11
|
||||||
displayName: Generate Coverage Report
|
displayName: Generate Coverage Report
|
||||||
inputs:
|
inputs:
|
||||||
reports: '$(Build.SourcesDirectory)/CoverageResults/**/coverage.opencover.xml'
|
reports: '$(Build.SourcesDirectory)/CoverageResults/**/coverage.opencover.xml'
|
||||||
targetdir: '$(Build.SourcesDirectory)/CoverageResults/combined'
|
targetdir: '$(Build.SourcesDirectory)/CoverageResults/combined'
|
||||||
reporttypes: 'HtmlInline_AzurePipelines;Cobertura;Badges'
|
reporttypes: 'HtmlInline_AzurePipelines;Cobertura;Badges'
|
||||||
- task: PublishCodeCoverageResults@1
|
publishCodeCoverageResults: true
|
||||||
displayName: Publish Coverage Report
|
|
||||||
inputs:
|
|
||||||
codeCoverageTool: 'cobertura'
|
|
||||||
summaryFileLocation: './CoverageResults/combined/Cobertura.xml'
|
|
||||||
reportDirectory: './CoverageResults/combined/'
|
|
||||||
|
|
||||||
- stage: Report_Out
|
- stage: Report_Out
|
||||||
dependsOn:
|
dependsOn:
|
||||||
@@ -1077,7 +1225,6 @@ stages:
|
|||||||
- Unit_Test
|
- Unit_Test
|
||||||
- Integration
|
- Integration
|
||||||
- Automation
|
- Automation
|
||||||
- Build_Backend_Other
|
|
||||||
condition: eq(variables['system.pullrequest.isfork'], false)
|
condition: eq(variables['system.pullrequest.isfork'], false)
|
||||||
displayName: Build Status Report
|
displayName: Build Status Report
|
||||||
jobs:
|
jobs:
|
||||||
@@ -1101,3 +1248,4 @@ stages:
|
|||||||
DISCORDCHANNELID: $(discordChannelId)
|
DISCORDCHANNELID: $(discordChannelId)
|
||||||
DISCORDWEBHOOKKEY: $(discordWebhookKey)
|
DISCORDWEBHOOKKEY: $(discordWebhookKey)
|
||||||
DISCORDTHREADID: $(discordThreadId)
|
DISCORDTHREADID: $(discordThreadId)
|
||||||
|
|
||||||
|
|||||||
6
build.sh
6
build.sh
@@ -23,7 +23,7 @@ UpdateVersionNumber()
|
|||||||
echo "Updating Version Info"
|
echo "Updating Version Info"
|
||||||
sed -i'' -e "s/<AssemblyVersion>[0-9.*]\+<\/AssemblyVersion>/<AssemblyVersion>$READARRVERSION<\/AssemblyVersion>/g" src/Directory.Build.props
|
sed -i'' -e "s/<AssemblyVersion>[0-9.*]\+<\/AssemblyVersion>/<AssemblyVersion>$READARRVERSION<\/AssemblyVersion>/g" src/Directory.Build.props
|
||||||
sed -i'' -e "s/<AssemblyConfiguration>[\$()A-Za-z-]\+<\/AssemblyConfiguration>/<AssemblyConfiguration>${BUILD_SOURCEBRANCHNAME}<\/AssemblyConfiguration>/g" src/Directory.Build.props
|
sed -i'' -e "s/<AssemblyConfiguration>[\$()A-Za-z-]\+<\/AssemblyConfiguration>/<AssemblyConfiguration>${BUILD_SOURCEBRANCHNAME}<\/AssemblyConfiguration>/g" src/Directory.Build.props
|
||||||
sed -i'' -e "s/<string>10.0.0.0<\/string>/<string>$READARRVERSION<\/string>/g" macOS/Readarr.app/Contents/Info.plist
|
sed -i'' -e "s/<string>10.0.0.0<\/string>/<string>$READARRVERSION<\/string>/g" distribution/osx/Readarr.app/Contents/Info.plist
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,7 +183,7 @@ PackageMacOSApp()
|
|||||||
|
|
||||||
rm -rf $folder
|
rm -rf $folder
|
||||||
mkdir -p $folder
|
mkdir -p $folder
|
||||||
cp -r macOS/Readarr.app $folder
|
cp -r distribution/osx/Readarr.app $folder
|
||||||
mkdir -p $folder/Readarr.app/Contents/MacOS
|
mkdir -p $folder/Readarr.app/Contents/MacOS
|
||||||
|
|
||||||
echo "Copying Binaries"
|
echo "Copying Binaries"
|
||||||
@@ -245,7 +245,7 @@ BuildInstaller()
|
|||||||
local framework="$1"
|
local framework="$1"
|
||||||
local runtime="$2"
|
local runtime="$2"
|
||||||
|
|
||||||
./_inno/ISCC.exe setup/readarr.iss "//DFramework=$framework" "//DRuntime=$runtime"
|
./_inno/ISCC.exe distribution/windows/setup/readarr.iss "//DFramework=$framework" "//DRuntime=$runtime"
|
||||||
}
|
}
|
||||||
|
|
||||||
InstallInno()
|
InstallInno()
|
||||||
|
|||||||
BIN
distribution/osx/Readarr.app/Contents/Resources/readarr.icns
Normal file
BIN
distribution/osx/Readarr.app/Contents/Resources/readarr.icns
Normal file
Binary file not shown.
@@ -44,16 +44,16 @@ Name: "english"; MessagesFile: "compiler:Default.isl"
|
|||||||
|
|
||||||
[Tasks]
|
[Tasks]
|
||||||
Name: "desktopIcon"; Description: "{cm:CreateDesktopIcon}"
|
Name: "desktopIcon"; Description: "{cm:CreateDesktopIcon}"
|
||||||
Name: "windowsService"; Description: "Install Windows Service (Starts when the computer starts as the LocalService user, you will need to change the user to access network shares)"; GroupDescription: "Start automatically"; Flags: exclusive unchecked
|
Name: "windowsService"; Description: "Install Windows Service (Starts when the computer starts as the LocalService user, you will need to change the user to access network shares)"; GroupDescription: "Start automatically"; Flags: exclusive
|
||||||
Name: "startupShortcut"; Description: "Create shortcut in Startup folder (Starts when you log into Windows)"; GroupDescription: "Start automatically"; Flags: exclusive
|
Name: "startupShortcut"; Description: "Create shortcut in Startup folder (Starts when you log into Windows)"; GroupDescription: "Start automatically"; Flags: exclusive unchecked
|
||||||
Name: "none"; Description: "Do not start automatically"; GroupDescription: "Start automatically"; Flags: exclusive unchecked
|
Name: "none"; Description: "Do not start automatically"; GroupDescription: "Start automatically"; Flags: exclusive unchecked
|
||||||
|
|
||||||
[Dirs]
|
[Dirs]
|
||||||
Name: "{app}"; Permissions: users-modify
|
Name: "{app}"; Permissions: users-modify
|
||||||
|
|
||||||
[Files]
|
[Files]
|
||||||
Source: "..\_artifacts\{#Runtime}\{#Framework}\Readarr\Readarr.exe"; DestDir: "{app}\bin"; Flags: ignoreversion
|
Source: "..\..\..\_artifacts\{#Runtime}\{#Framework}\Readarr\Readarr.exe"; DestDir: "{app}\bin"; Flags: ignoreversion
|
||||||
Source: "..\_artifacts\{#Runtime}\{#Framework}\Readarr\*"; Excludes: "Readarr.Update"; DestDir: "{app}\bin"; Flags: ignoreversion recursesubdirs createallsubdirs
|
Source: "..\..\..\_artifacts\{#Runtime}\{#Framework}\Readarr\*"; Excludes: "Readarr.Update"; DestDir: "{app}\bin"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||||
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
||||||
|
|
||||||
[Icons]
|
[Icons]
|
||||||
@@ -72,12 +72,13 @@ Filename: "{app}\bin\Readarr.exe"; Description: "Open Readarr Web UI"; Flags: po
|
|||||||
Filename: "{app}\bin\Readarr.exe"; Description: "Start Readarr"; Flags: postinstall skipifsilent nowait; Tasks: startupShortcut none;
|
Filename: "{app}\bin\Readarr.exe"; Description: "Start Readarr"; Flags: postinstall skipifsilent nowait; Tasks: startupShortcut none;
|
||||||
|
|
||||||
[UninstallRun]
|
[UninstallRun]
|
||||||
Filename: "{app}\bin\Readarr.Console.exe"; Parameters: "/u"; Flags: waituntilterminated skipifdoesntexist
|
Filename: "{app}\bin\readarr.console.exe"; Parameters: "/u"; Flags: waituntilterminated skipifdoesntexist
|
||||||
|
|
||||||
[Code]
|
[Code]
|
||||||
function PrepareToInstall(var NeedsRestart: Boolean): String;
|
function PrepareToInstall(var NeedsRestart: Boolean): String;
|
||||||
var
|
var
|
||||||
ResultCode: Integer;
|
ResultCode: Integer;
|
||||||
begin
|
begin
|
||||||
Exec(ExpandConstant('{commonappdata}\Readarr\bin\Readarr.Console.exe'), '/u', '', 0, ewWaitUntilTerminated, ResultCode)
|
Exec('net', 'stop readarr', '', 0, ewWaitUntilTerminated, ResultCode)
|
||||||
|
Exec('sc', 'delete readarr', '', 0, ewWaitUntilTerminated, ResultCode)
|
||||||
end;
|
end;
|
||||||
14
docs.sh
14
docs.sh
@@ -1,3 +1,7 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
FRAMEWORK="net6.0"
|
||||||
PLATFORM=$1
|
PLATFORM=$1
|
||||||
|
|
||||||
if [ "$PLATFORM" = "Windows" ]; then
|
if [ "$PLATFORM" = "Windows" ]; then
|
||||||
@@ -21,15 +25,21 @@ slnFile=src/Readarr.sln
|
|||||||
|
|
||||||
platform=Posix
|
platform=Posix
|
||||||
|
|
||||||
|
if [ "$PLATFORM" = "Windows" ]; then
|
||||||
|
application=Readarr.Console.dll
|
||||||
|
else
|
||||||
|
application=Readarr.dll
|
||||||
|
fi
|
||||||
|
|
||||||
dotnet clean $slnFile -c Debug
|
dotnet clean $slnFile -c Debug
|
||||||
dotnet clean $slnFile -c Release
|
dotnet clean $slnFile -c Release
|
||||||
|
|
||||||
dotnet msbuild -restore $slnFile -p:Configuration=Debug -p:Platform=$platform -p:RuntimeIdentifiers=$RUNTIME -t:PublishAllRids
|
dotnet msbuild -restore $slnFile -p:Configuration=Debug -p:Platform=$platform -p:RuntimeIdentifiers=$RUNTIME -t:PublishAllRids
|
||||||
|
|
||||||
dotnet new tool-manifest
|
dotnet new tool-manifest
|
||||||
dotnet tool install --version 6.5.0 Swashbuckle.AspNetCore.Cli
|
dotnet tool install --version 6.6.2 Swashbuckle.AspNetCore.Cli
|
||||||
|
|
||||||
dotnet tool run swagger tofile --output ./src/Readarr.Api.V1/openapi.json "$outputFolder/net6.0/$RUNTIME/Readarr.console.dll" v1 &
|
dotnet tool run swagger tofile --output ./src/Readarr.Api.V1/openapi.json "$outputFolder/$FRAMEWORK/$RUNTIME/$application" v1 &
|
||||||
|
|
||||||
sleep 45
|
sleep 45
|
||||||
|
|
||||||
|
|||||||
2
frontend/.vscode/settings.json
vendored
2
frontend/.vscode/settings.json
vendored
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
"editor.formatOnSave": false,
|
"editor.formatOnSave": false,
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll": true
|
"source.fixAll": "explicit"
|
||||||
},
|
},
|
||||||
|
|
||||||
"typescript.preferences.quoteStyle": "single",
|
"typescript.preferences.quoteStyle": "single",
|
||||||
|
|||||||
@@ -2,16 +2,18 @@ const loose = true;
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
plugins: [
|
plugins: [
|
||||||
|
'@babel/plugin-transform-logical-assignment-operators',
|
||||||
|
|
||||||
// Stage 1
|
// Stage 1
|
||||||
'@babel/plugin-proposal-export-default-from',
|
'@babel/plugin-proposal-export-default-from',
|
||||||
['@babel/plugin-proposal-optional-chaining', { loose }],
|
['@babel/plugin-transform-optional-chaining', { loose }],
|
||||||
['@babel/plugin-proposal-nullish-coalescing-operator', { loose }],
|
['@babel/plugin-transform-nullish-coalescing-operator', { loose }],
|
||||||
|
|
||||||
// Stage 2
|
// Stage 2
|
||||||
'@babel/plugin-proposal-export-namespace-from',
|
'@babel/plugin-transform-export-namespace-from',
|
||||||
|
|
||||||
// Stage 3
|
// Stage 3
|
||||||
['@babel/plugin-proposal-class-properties', { loose }],
|
['@babel/plugin-transform-class-properties', { loose }],
|
||||||
'@babel/plugin-syntax-dynamic-import'
|
'@babel/plugin-syntax-dynamic-import'
|
||||||
],
|
],
|
||||||
env: {
|
env: {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ module.exports = (env) => {
|
|||||||
const config = {
|
const config = {
|
||||||
mode: isProduction ? 'production' : 'development',
|
mode: isProduction ? 'production' : 'development',
|
||||||
devtool: isProduction ? 'source-map' : 'eval-source-map',
|
devtool: isProduction ? 'source-map' : 'eval-source-map',
|
||||||
|
target: 'web',
|
||||||
|
|
||||||
stats: {
|
stats: {
|
||||||
children: false
|
children: false
|
||||||
@@ -67,7 +68,7 @@ module.exports = (env) => {
|
|||||||
output: {
|
output: {
|
||||||
path: distFolder,
|
path: distFolder,
|
||||||
publicPath: '/',
|
publicPath: '/',
|
||||||
filename: '[name]-[contenthash].js',
|
filename: isProduction ? '[name]-[contenthash].js' : '[name].js',
|
||||||
sourceMapFilename: '[file].map'
|
sourceMapFilename: '[file].map'
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -92,7 +93,7 @@ module.exports = (env) => {
|
|||||||
|
|
||||||
new MiniCssExtractPlugin({
|
new MiniCssExtractPlugin({
|
||||||
filename: 'Content/styles.css',
|
filename: 'Content/styles.css',
|
||||||
chunkFilename: 'Content/[id]-[chunkhash].css'
|
chunkFilename: isProduction ? 'Content/[id]-[chunkhash].css' : 'Content/[id].css'
|
||||||
}),
|
}),
|
||||||
|
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
@@ -181,7 +182,7 @@ module.exports = (env) => {
|
|||||||
loose: true,
|
loose: true,
|
||||||
debug: false,
|
debug: false,
|
||||||
useBuiltIns: 'entry',
|
useBuiltIns: 'entry',
|
||||||
corejs: 3
|
corejs: '3.39'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
@@ -202,7 +203,7 @@ module.exports = (env) => {
|
|||||||
options: {
|
options: {
|
||||||
importLoaders: 1,
|
importLoaders: 1,
|
||||||
modules: {
|
modules: {
|
||||||
localIdentName: '[name]/[local]/[hash:base64:5]'
|
localIdentName: isProduction ? '[name]/[local]/[hash:base64:5]' : '[name]/[local]'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -165,7 +165,8 @@ function HistoryDetails(props) {
|
|||||||
|
|
||||||
if (eventType === 'downloadFailed') {
|
if (eventType === 'downloadFailed') {
|
||||||
const {
|
const {
|
||||||
message
|
message,
|
||||||
|
indexer
|
||||||
} = data;
|
} = data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -177,11 +178,21 @@ function HistoryDetails(props) {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{
|
{
|
||||||
!!message &&
|
indexer ?
|
||||||
|
<DescriptionListItem
|
||||||
|
title={translate('Indexer')}
|
||||||
|
data={indexer}
|
||||||
|
/> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
message ?
|
||||||
<DescriptionListItem
|
<DescriptionListItem
|
||||||
title={translate('Message')}
|
title={translate('Message')}
|
||||||
data={message}
|
data={message}
|
||||||
/>
|
/> :
|
||||||
|
null
|
||||||
}
|
}
|
||||||
</DescriptionList>
|
</DescriptionList>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -218,10 +218,12 @@ class HistoryRow extends Component {
|
|||||||
key={name}
|
key={name}
|
||||||
className={styles.details}
|
className={styles.details}
|
||||||
>
|
>
|
||||||
<IconButton
|
<div className={styles.actionContents}>
|
||||||
name={icons.INFO}
|
<IconButton
|
||||||
onPress={this.onDetailsPress}
|
name={icons.INFO}
|
||||||
/>
|
onPress={this.onDetailsPress}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import selectAll from 'Utilities/Table/selectAll';
|
|||||||
import toggleSelected from 'Utilities/Table/toggleSelected';
|
import toggleSelected from 'Utilities/Table/toggleSelected';
|
||||||
import QueueOptionsConnector from './QueueOptionsConnector';
|
import QueueOptionsConnector from './QueueOptionsConnector';
|
||||||
import QueueRowConnector from './QueueRowConnector';
|
import QueueRowConnector from './QueueRowConnector';
|
||||||
import RemoveQueueItemsModal from './RemoveQueueItemsModal';
|
import RemoveQueueItemModal from './RemoveQueueItemModal';
|
||||||
|
|
||||||
class Queue extends Component {
|
class Queue extends Component {
|
||||||
|
|
||||||
@@ -289,9 +289,16 @@ class Queue extends Component {
|
|||||||
}
|
}
|
||||||
</PageContentBody>
|
</PageContentBody>
|
||||||
|
|
||||||
<RemoveQueueItemsModal
|
<RemoveQueueItemModal
|
||||||
isOpen={isConfirmRemoveModalOpen}
|
isOpen={isConfirmRemoveModalOpen}
|
||||||
selectedCount={selectedCount}
|
selectedCount={selectedCount}
|
||||||
|
canChangeCategory={isConfirmRemoveModalOpen && (
|
||||||
|
selectedIds.every((id) => {
|
||||||
|
const item = items.find((i) => i.id === id);
|
||||||
|
|
||||||
|
return !!(item && item.downloadClientHasPostImportCategory);
|
||||||
|
})
|
||||||
|
)}
|
||||||
canIgnore={isConfirmRemoveModalOpen && (
|
canIgnore={isConfirmRemoveModalOpen && (
|
||||||
selectedIds.every((id) => {
|
selectedIds.every((id) => {
|
||||||
const item = items.find((i) => i.id === id);
|
const item = items.find((i) => i.id === id);
|
||||||
@@ -299,7 +306,7 @@ class Queue extends Component {
|
|||||||
return !!(item && item.authorId && item.bookId);
|
return !!(item && item.authorId && item.bookId);
|
||||||
})
|
})
|
||||||
)}
|
)}
|
||||||
allPending={isConfirmRemoveModalOpen && (
|
pending={isConfirmRemoveModalOpen && (
|
||||||
selectedIds.every((id) => {
|
selectedIds.every((id) => {
|
||||||
const item = items.find((i) => i.id === id);
|
const item = items.find((i) => i.id === id);
|
||||||
|
|
||||||
@@ -338,4 +345,8 @@ Queue.propTypes = {
|
|||||||
onRemoveSelectedPress: PropTypes.func.isRequired
|
onRemoveSelectedPress: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Queue.defaultProps = {
|
||||||
|
count: 0
|
||||||
|
};
|
||||||
|
|
||||||
export default Queue;
|
export default Queue;
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ class QueueRow extends Component {
|
|||||||
indexer,
|
indexer,
|
||||||
outputPath,
|
outputPath,
|
||||||
downloadClient,
|
downloadClient,
|
||||||
|
downloadClientHasPostImportCategory,
|
||||||
downloadForced,
|
downloadForced,
|
||||||
estimatedCompletionTime,
|
estimatedCompletionTime,
|
||||||
timeleft,
|
timeleft,
|
||||||
@@ -389,6 +390,7 @@ class QueueRow extends Component {
|
|||||||
<RemoveQueueItemModal
|
<RemoveQueueItemModal
|
||||||
isOpen={isRemoveQueueItemModalOpen}
|
isOpen={isRemoveQueueItemModalOpen}
|
||||||
sourceTitle={title}
|
sourceTitle={title}
|
||||||
|
canChangeCategory={!!downloadClientHasPostImportCategory}
|
||||||
canIgnore={!!author}
|
canIgnore={!!author}
|
||||||
isPending={isPending}
|
isPending={isPending}
|
||||||
onRemovePress={this.onRemoveQueueItemModalConfirmed}
|
onRemovePress={this.onRemoveQueueItemModalConfirmed}
|
||||||
@@ -418,6 +420,7 @@ QueueRow.propTypes = {
|
|||||||
indexer: PropTypes.string,
|
indexer: PropTypes.string,
|
||||||
outputPath: PropTypes.string,
|
outputPath: PropTypes.string,
|
||||||
downloadClient: PropTypes.string,
|
downloadClient: PropTypes.string,
|
||||||
|
downloadClientHasPostImportCategory: PropTypes.bool,
|
||||||
downloadForced: PropTypes.bool.isRequired,
|
downloadForced: PropTypes.bool.isRequired,
|
||||||
estimatedCompletionTime: PropTypes.string,
|
estimatedCompletionTime: PropTypes.string,
|
||||||
timeleft: PropTypes.string,
|
timeleft: PropTypes.string,
|
||||||
|
|||||||
@@ -1,177 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import FormGroup from 'Components/Form/FormGroup';
|
|
||||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
|
||||||
import FormLabel from 'Components/Form/FormLabel';
|
|
||||||
import Button from 'Components/Link/Button';
|
|
||||||
import Modal from 'Components/Modal/Modal';
|
|
||||||
import ModalBody from 'Components/Modal/ModalBody';
|
|
||||||
import ModalContent from 'Components/Modal/ModalContent';
|
|
||||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
|
||||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
|
||||||
import { inputTypes, kinds, sizes } from 'Helpers/Props';
|
|
||||||
import translate from 'Utilities/String/translate';
|
|
||||||
|
|
||||||
class RemoveQueueItemModal extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Lifecycle
|
|
||||||
|
|
||||||
constructor(props, context) {
|
|
||||||
super(props, context);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
remove: true,
|
|
||||||
blocklist: false,
|
|
||||||
skipRedownload: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Control
|
|
||||||
|
|
||||||
resetState = function() {
|
|
||||||
this.setState({
|
|
||||||
remove: true,
|
|
||||||
blocklist: false,
|
|
||||||
skipRedownload: false
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Listeners
|
|
||||||
|
|
||||||
onRemoveChange = ({ value }) => {
|
|
||||||
this.setState({ remove: value });
|
|
||||||
};
|
|
||||||
|
|
||||||
onBlocklistChange = ({ value }) => {
|
|
||||||
this.setState({ blocklist: value });
|
|
||||||
};
|
|
||||||
|
|
||||||
onSkipRedownloadChange = ({ value }) => {
|
|
||||||
this.setState({ skipRedownload: value });
|
|
||||||
};
|
|
||||||
|
|
||||||
onRemoveConfirmed = () => {
|
|
||||||
const state = this.state;
|
|
||||||
|
|
||||||
this.resetState();
|
|
||||||
this.props.onRemovePress(state);
|
|
||||||
};
|
|
||||||
|
|
||||||
onModalClose = () => {
|
|
||||||
this.resetState();
|
|
||||||
this.props.onModalClose();
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
isOpen,
|
|
||||||
sourceTitle,
|
|
||||||
canIgnore,
|
|
||||||
isPending
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const { remove, blocklist, skipRedownload } = this.state;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
isOpen={isOpen}
|
|
||||||
size={sizes.MEDIUM}
|
|
||||||
onModalClose={this.onModalClose}
|
|
||||||
>
|
|
||||||
<ModalContent
|
|
||||||
onModalClose={this.onModalClose}
|
|
||||||
>
|
|
||||||
<ModalHeader>
|
|
||||||
Remove - {sourceTitle}
|
|
||||||
</ModalHeader>
|
|
||||||
|
|
||||||
<ModalBody>
|
|
||||||
<div>
|
|
||||||
Are you sure you want to remove '{sourceTitle}' from the queue?
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{
|
|
||||||
isPending ?
|
|
||||||
null :
|
|
||||||
<FormGroup>
|
|
||||||
<FormLabel>
|
|
||||||
{translate('RemoveFromDownloadClient')}
|
|
||||||
</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
|
||||||
type={inputTypes.CHECK}
|
|
||||||
name="remove"
|
|
||||||
value={remove}
|
|
||||||
helpTextWarning={translate('RemoveFromDownloadClientHelpTextWarning')}
|
|
||||||
isDisabled={!canIgnore}
|
|
||||||
onChange={this.onRemoveChange}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
}
|
|
||||||
|
|
||||||
<FormGroup>
|
|
||||||
<FormLabel>
|
|
||||||
{translate('BlocklistRelease')}
|
|
||||||
</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
|
||||||
type={inputTypes.CHECK}
|
|
||||||
name="blocklist"
|
|
||||||
value={blocklist}
|
|
||||||
helpText={translate('BlocklistReleaseHelpText')}
|
|
||||||
onChange={this.onBlocklistChange}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
{
|
|
||||||
blocklist &&
|
|
||||||
<FormGroup>
|
|
||||||
<FormLabel>
|
|
||||||
{translate('SkipRedownload')}
|
|
||||||
</FormLabel>
|
|
||||||
<FormInputGroup
|
|
||||||
type={inputTypes.CHECK}
|
|
||||||
name="skipRedownload"
|
|
||||||
value={skipRedownload}
|
|
||||||
helpText={translate('SkipRedownloadHelpText')}
|
|
||||||
onChange={this.onSkipRedownloadChange}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
}
|
|
||||||
|
|
||||||
</ModalBody>
|
|
||||||
|
|
||||||
<ModalFooter>
|
|
||||||
<Button onPress={this.onModalClose}>
|
|
||||||
Close
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
kind={kinds.DANGER}
|
|
||||||
onPress={this.onRemoveConfirmed}
|
|
||||||
>
|
|
||||||
Remove
|
|
||||||
</Button>
|
|
||||||
</ModalFooter>
|
|
||||||
</ModalContent>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RemoveQueueItemModal.propTypes = {
|
|
||||||
isOpen: PropTypes.bool.isRequired,
|
|
||||||
sourceTitle: PropTypes.string.isRequired,
|
|
||||||
canIgnore: PropTypes.bool.isRequired,
|
|
||||||
isPending: PropTypes.bool.isRequired,
|
|
||||||
onRemovePress: PropTypes.func.isRequired,
|
|
||||||
onModalClose: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default RemoveQueueItemModal;
|
|
||||||
230
frontend/src/Activity/Queue/RemoveQueueItemModal.tsx
Normal file
230
frontend/src/Activity/Queue/RemoveQueueItemModal.tsx
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
import React, { useCallback, useMemo, useState } from 'react';
|
||||||
|
import FormGroup from 'Components/Form/FormGroup';
|
||||||
|
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||||
|
import FormLabel from 'Components/Form/FormLabel';
|
||||||
|
import Button from 'Components/Link/Button';
|
||||||
|
import Modal from 'Components/Modal/Modal';
|
||||||
|
import ModalBody from 'Components/Modal/ModalBody';
|
||||||
|
import ModalContent from 'Components/Modal/ModalContent';
|
||||||
|
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||||
|
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||||
|
import { inputTypes, kinds, sizes } from 'Helpers/Props';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
import styles from './RemoveQueueItemModal.css';
|
||||||
|
|
||||||
|
interface RemovePressProps {
|
||||||
|
remove: boolean;
|
||||||
|
changeCategory: boolean;
|
||||||
|
blocklist: boolean;
|
||||||
|
skipRedownload: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RemoveQueueItemModalProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
sourceTitle: string;
|
||||||
|
canChangeCategory: boolean;
|
||||||
|
canIgnore: boolean;
|
||||||
|
isPending: boolean;
|
||||||
|
selectedCount?: number;
|
||||||
|
onRemovePress(props: RemovePressProps): void;
|
||||||
|
onModalClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
type RemovalMethod = 'removeFromClient' | 'changeCategory' | 'ignore';
|
||||||
|
type BlocklistMethod =
|
||||||
|
| 'doNotBlocklist'
|
||||||
|
| 'blocklistAndSearch'
|
||||||
|
| 'blocklistOnly';
|
||||||
|
|
||||||
|
function RemoveQueueItemModal(props: RemoveQueueItemModalProps) {
|
||||||
|
const {
|
||||||
|
isOpen,
|
||||||
|
sourceTitle,
|
||||||
|
canIgnore,
|
||||||
|
canChangeCategory,
|
||||||
|
isPending,
|
||||||
|
selectedCount,
|
||||||
|
onRemovePress,
|
||||||
|
onModalClose,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const multipleSelected = selectedCount && selectedCount > 1;
|
||||||
|
|
||||||
|
const [removalMethod, setRemovalMethod] =
|
||||||
|
useState<RemovalMethod>('removeFromClient');
|
||||||
|
const [blocklistMethod, setBlocklistMethod] =
|
||||||
|
useState<BlocklistMethod>('doNotBlocklist');
|
||||||
|
|
||||||
|
const { title, message } = useMemo(() => {
|
||||||
|
if (!selectedCount) {
|
||||||
|
return {
|
||||||
|
title: translate('RemoveQueueItem', { sourceTitle }),
|
||||||
|
message: translate('RemoveQueueItemConfirmation', { sourceTitle }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedCount === 1) {
|
||||||
|
return {
|
||||||
|
title: translate('RemoveSelectedItem'),
|
||||||
|
message: translate('RemoveSelectedItemQueueMessageText'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: translate('RemoveSelectedItems'),
|
||||||
|
message: translate('RemoveSelectedItemsQueueMessageText', {
|
||||||
|
selectedCount,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}, [sourceTitle, selectedCount]);
|
||||||
|
|
||||||
|
const removalMethodOptions = useMemo(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: 'removeFromClient',
|
||||||
|
value: translate('RemoveFromDownloadClient'),
|
||||||
|
hint: multipleSelected
|
||||||
|
? translate('RemoveMultipleFromDownloadClientHint')
|
||||||
|
: translate('RemoveFromDownloadClientHint'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'changeCategory',
|
||||||
|
value: translate('ChangeCategory'),
|
||||||
|
isDisabled: !canChangeCategory,
|
||||||
|
hint: multipleSelected
|
||||||
|
? translate('ChangeCategoryMultipleHint')
|
||||||
|
: translate('ChangeCategoryHint'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'ignore',
|
||||||
|
value: multipleSelected
|
||||||
|
? translate('IgnoreDownloads')
|
||||||
|
: translate('IgnoreDownload'),
|
||||||
|
isDisabled: !canIgnore,
|
||||||
|
hint: multipleSelected
|
||||||
|
? translate('IgnoreDownloadsHint')
|
||||||
|
: translate('IgnoreDownloadHint'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}, [canChangeCategory, canIgnore, multipleSelected]);
|
||||||
|
|
||||||
|
const blocklistMethodOptions = useMemo(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: 'doNotBlocklist',
|
||||||
|
value: translate('DoNotBlocklist'),
|
||||||
|
hint: translate('DoNotBlocklistHint'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'blocklistAndSearch',
|
||||||
|
value: translate('BlocklistAndSearch'),
|
||||||
|
hint: multipleSelected
|
||||||
|
? translate('BlocklistAndSearchMultipleHint')
|
||||||
|
: translate('BlocklistAndSearchHint'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'blocklistOnly',
|
||||||
|
value: translate('BlocklistOnly'),
|
||||||
|
hint: multipleSelected
|
||||||
|
? translate('BlocklistMultipleOnlyHint')
|
||||||
|
: translate('BlocklistOnlyHint'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}, [multipleSelected]);
|
||||||
|
|
||||||
|
const handleRemovalMethodChange = useCallback(
|
||||||
|
({ value }: { value: RemovalMethod }) => {
|
||||||
|
setRemovalMethod(value);
|
||||||
|
},
|
||||||
|
[setRemovalMethod]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleBlocklistMethodChange = useCallback(
|
||||||
|
({ value }: { value: BlocklistMethod }) => {
|
||||||
|
setBlocklistMethod(value);
|
||||||
|
},
|
||||||
|
[setBlocklistMethod]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleConfirmRemove = useCallback(() => {
|
||||||
|
onRemovePress({
|
||||||
|
remove: removalMethod === 'removeFromClient',
|
||||||
|
changeCategory: removalMethod === 'changeCategory',
|
||||||
|
blocklist: blocklistMethod !== 'doNotBlocklist',
|
||||||
|
skipRedownload: blocklistMethod === 'blocklistOnly',
|
||||||
|
});
|
||||||
|
|
||||||
|
setRemovalMethod('removeFromClient');
|
||||||
|
setBlocklistMethod('doNotBlocklist');
|
||||||
|
}, [
|
||||||
|
removalMethod,
|
||||||
|
blocklistMethod,
|
||||||
|
setRemovalMethod,
|
||||||
|
setBlocklistMethod,
|
||||||
|
onRemovePress,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const handleModalClose = useCallback(() => {
|
||||||
|
setRemovalMethod('removeFromClient');
|
||||||
|
setBlocklistMethod('doNotBlocklist');
|
||||||
|
|
||||||
|
onModalClose();
|
||||||
|
}, [setRemovalMethod, setBlocklistMethod, onModalClose]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal isOpen={isOpen} size={sizes.MEDIUM} onModalClose={handleModalClose}>
|
||||||
|
<ModalContent onModalClose={handleModalClose}>
|
||||||
|
<ModalHeader>{title}</ModalHeader>
|
||||||
|
|
||||||
|
<ModalBody>
|
||||||
|
<div className={styles.message}>{message}</div>
|
||||||
|
|
||||||
|
{isPending ? null : (
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>{translate('RemoveQueueItemRemovalMethod')}</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.SELECT}
|
||||||
|
name="removalMethod"
|
||||||
|
value={removalMethod}
|
||||||
|
values={removalMethodOptions}
|
||||||
|
isDisabled={!canChangeCategory && !canIgnore}
|
||||||
|
helpTextWarning={translate(
|
||||||
|
'RemoveQueueItemRemovalMethodHelpTextWarning'
|
||||||
|
)}
|
||||||
|
onChange={handleRemovalMethodChange}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>
|
||||||
|
{multipleSelected
|
||||||
|
? translate('BlocklistReleases')
|
||||||
|
: translate('BlocklistRelease')}
|
||||||
|
</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.SELECT}
|
||||||
|
name="blocklistMethod"
|
||||||
|
value={blocklistMethod}
|
||||||
|
values={blocklistMethodOptions}
|
||||||
|
helpText={translate('BlocklistReleaseHelpText')}
|
||||||
|
onChange={handleBlocklistMethodChange}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
</ModalBody>
|
||||||
|
|
||||||
|
<ModalFooter>
|
||||||
|
<Button onPress={handleModalClose}>{translate('Close')}</Button>
|
||||||
|
|
||||||
|
<Button kind={kinds.DANGER} onPress={handleConfirmRemove}>
|
||||||
|
{translate('Remove')}
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RemoveQueueItemModal;
|
||||||
@@ -1,178 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import FormGroup from 'Components/Form/FormGroup';
|
|
||||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
|
||||||
import FormLabel from 'Components/Form/FormLabel';
|
|
||||||
import Button from 'Components/Link/Button';
|
|
||||||
import Modal from 'Components/Modal/Modal';
|
|
||||||
import ModalBody from 'Components/Modal/ModalBody';
|
|
||||||
import ModalContent from 'Components/Modal/ModalContent';
|
|
||||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
|
||||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
|
||||||
import { inputTypes, kinds, sizes } from 'Helpers/Props';
|
|
||||||
import translate from 'Utilities/String/translate';
|
|
||||||
import styles from './RemoveQueueItemsModal.css';
|
|
||||||
|
|
||||||
class RemoveQueueItemsModal extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Lifecycle
|
|
||||||
|
|
||||||
constructor(props, context) {
|
|
||||||
super(props, context);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
remove: true,
|
|
||||||
blocklist: false,
|
|
||||||
skipRedownload: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Control
|
|
||||||
|
|
||||||
resetState = function() {
|
|
||||||
this.setState({
|
|
||||||
remove: true,
|
|
||||||
blocklist: false,
|
|
||||||
skipRedownload: false
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Listeners
|
|
||||||
|
|
||||||
onRemoveChange = ({ value }) => {
|
|
||||||
this.setState({ remove: value });
|
|
||||||
};
|
|
||||||
|
|
||||||
onBlocklistChange = ({ value }) => {
|
|
||||||
this.setState({ blocklist: value });
|
|
||||||
};
|
|
||||||
|
|
||||||
onSkipRedownloadChange = ({ value }) => {
|
|
||||||
this.setState({ skipRedownload: value });
|
|
||||||
};
|
|
||||||
|
|
||||||
onRemoveConfirmed = () => {
|
|
||||||
const state = this.state;
|
|
||||||
|
|
||||||
this.resetState();
|
|
||||||
this.props.onRemovePress(state);
|
|
||||||
};
|
|
||||||
|
|
||||||
onModalClose = () => {
|
|
||||||
this.resetState();
|
|
||||||
this.props.onModalClose();
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
isOpen,
|
|
||||||
selectedCount,
|
|
||||||
canIgnore,
|
|
||||||
allPending
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const { remove, blocklist, skipRedownload } = this.state;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
isOpen={isOpen}
|
|
||||||
size={sizes.MEDIUM}
|
|
||||||
onModalClose={this.onModalClose}
|
|
||||||
>
|
|
||||||
<ModalContent
|
|
||||||
onModalClose={this.onModalClose}
|
|
||||||
>
|
|
||||||
<ModalHeader>
|
|
||||||
{selectedCount > 1 ? translate('RemoveSelectedItems') : translate('RemoveSelectedItem')}
|
|
||||||
</ModalHeader>
|
|
||||||
|
|
||||||
<ModalBody>
|
|
||||||
<div className={styles.message}>
|
|
||||||
{selectedCount > 1 ? translate('RemoveSelectedItemsQueueMessageText', selectedCount) : translate('RemoveSelectedItemQueueMessageText')}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{
|
|
||||||
allPending ?
|
|
||||||
null :
|
|
||||||
<FormGroup>
|
|
||||||
<FormLabel>
|
|
||||||
{translate('RemoveFromDownloadClient')}
|
|
||||||
</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
|
||||||
type={inputTypes.CHECK}
|
|
||||||
name="remove"
|
|
||||||
value={remove}
|
|
||||||
helpTextWarning={translate('RemoveHelpTextWarning')}
|
|
||||||
isDisabled={!canIgnore}
|
|
||||||
onChange={this.onRemoveChange}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
}
|
|
||||||
|
|
||||||
<FormGroup>
|
|
||||||
<FormLabel>
|
|
||||||
{selectedCount > 1 ? translate('BlocklistReleases') : translate('BlocklistRelease')}
|
|
||||||
</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
|
||||||
type={inputTypes.CHECK}
|
|
||||||
name="blocklist"
|
|
||||||
value={blocklist}
|
|
||||||
helpText={translate('BlocklistReleaseHelpText')}
|
|
||||||
onChange={this.onBlocklistChange}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
{
|
|
||||||
blocklist &&
|
|
||||||
<FormGroup>
|
|
||||||
<FormLabel>
|
|
||||||
{translate('SkipRedownload')}
|
|
||||||
</FormLabel>
|
|
||||||
<FormInputGroup
|
|
||||||
type={inputTypes.CHECK}
|
|
||||||
name="skipRedownload"
|
|
||||||
value={skipRedownload}
|
|
||||||
helpText={translate('SkipRedownloadHelpText')}
|
|
||||||
onChange={this.onSkipRedownloadChange}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
}
|
|
||||||
|
|
||||||
</ModalBody>
|
|
||||||
|
|
||||||
<ModalFooter>
|
|
||||||
<Button onPress={this.onModalClose}>
|
|
||||||
{translate('Close')}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
kind={kinds.DANGER}
|
|
||||||
onPress={this.onRemoveConfirmed}
|
|
||||||
>
|
|
||||||
{translate('Remove')}
|
|
||||||
</Button>
|
|
||||||
</ModalFooter>
|
|
||||||
</ModalContent>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RemoveQueueItemsModal.propTypes = {
|
|
||||||
isOpen: PropTypes.bool.isRequired,
|
|
||||||
selectedCount: PropTypes.number.isRequired,
|
|
||||||
canIgnore: PropTypes.bool.isRequired,
|
|
||||||
allPending: PropTypes.bool.isRequired,
|
|
||||||
onRemovePress: PropTypes.func.isRequired,
|
|
||||||
onModalClose: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default RemoveQueueItemsModal;
|
|
||||||
@@ -32,7 +32,7 @@ import LogsTableConnector from 'System/Events/LogsTableConnector';
|
|||||||
import Logs from 'System/Logs/Logs';
|
import Logs from 'System/Logs/Logs';
|
||||||
import Status from 'System/Status/Status';
|
import Status from 'System/Status/Status';
|
||||||
import Tasks from 'System/Tasks/Tasks';
|
import Tasks from 'System/Tasks/Tasks';
|
||||||
import UpdatesConnector from 'System/Updates/UpdatesConnector';
|
import Updates from 'System/Updates/Updates';
|
||||||
import UnmappedFilesTableConnector from 'UnmappedFiles/UnmappedFilesTableConnector';
|
import UnmappedFilesTableConnector from 'UnmappedFiles/UnmappedFilesTableConnector';
|
||||||
import getPathWithUrlBase from 'Utilities/getPathWithUrlBase';
|
import getPathWithUrlBase from 'Utilities/getPathWithUrlBase';
|
||||||
import CutoffUnmetConnector from 'Wanted/CutoffUnmet/CutoffUnmetConnector';
|
import CutoffUnmetConnector from 'Wanted/CutoffUnmet/CutoffUnmetConnector';
|
||||||
@@ -247,7 +247,7 @@ function AppRoutes(props) {
|
|||||||
|
|
||||||
<Route
|
<Route
|
||||||
path="/system/updates"
|
path="/system/updates"
|
||||||
component={UpdatesConnector}
|
component={Updates}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
.version {
|
.version {
|
||||||
margin: 0 3px;
|
margin: 0 3px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
font-family: var(--defaultFontFamily);
|
||||||
}
|
}
|
||||||
|
|
||||||
.maintenance {
|
.maintenance {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Button from 'Components/Link/Button';
|
import Button from 'Components/Link/Button';
|
||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
|
import InlineMarkdown from 'Components/Markdown/InlineMarkdown';
|
||||||
import ModalBody from 'Components/Modal/ModalBody';
|
import ModalBody from 'Components/Modal/ModalBody';
|
||||||
import ModalContent from 'Components/Modal/ModalContent';
|
import ModalContent from 'Components/Modal/ModalContent';
|
||||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||||
@@ -64,12 +65,12 @@ function AppUpdatedModalContent(props) {
|
|||||||
return (
|
return (
|
||||||
<ModalContent onModalClose={onModalClose}>
|
<ModalContent onModalClose={onModalClose}>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
Readarr Updated
|
{translate('AppUpdated', { appName: 'Readarr' })}
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
|
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<div>
|
<div>
|
||||||
Version <span className={styles.version}>{version}</span> of Readarr has been installed, in order to get the latest changes you'll need to reload Readarr.
|
<InlineMarkdown data={translate('AppUpdatedVersion', { appName: 'Readarr', version })} blockClassName={styles.version} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -77,16 +78,14 @@ function AppUpdatedModalContent(props) {
|
|||||||
<div>
|
<div>
|
||||||
{
|
{
|
||||||
!update.changes &&
|
!update.changes &&
|
||||||
<div className={styles.maintenance}>
|
<div className={styles.maintenance}>{translate('MaintenanceRelease')}</div>
|
||||||
{translate('MaintenanceRelease')}
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
!!update.changes &&
|
!!update.changes &&
|
||||||
<div>
|
<div>
|
||||||
<div className={styles.changes}>
|
<div className={styles.changes}>
|
||||||
What's new?
|
{translate('WhatsNew')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<UpdateChanges
|
<UpdateChanges
|
||||||
@@ -113,14 +112,14 @@ function AppUpdatedModalContent(props) {
|
|||||||
<Button
|
<Button
|
||||||
onPress={onSeeChangesPress}
|
onPress={onSeeChangesPress}
|
||||||
>
|
>
|
||||||
Recent Changes
|
{translate('RecentChanges')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
kind={kinds.PRIMARY}
|
kind={kinds.PRIMARY}
|
||||||
onPress={onModalClose}
|
onPress={onModalClose}
|
||||||
>
|
>
|
||||||
Reload
|
{translate('Reload')}
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import ModalContent from 'Components/Modal/ModalContent';
|
|||||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||||
import { kinds } from 'Helpers/Props';
|
import { kinds } from 'Helpers/Props';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
import styles from './ConnectionLostModal.css';
|
import styles from './ConnectionLostModal.css';
|
||||||
|
|
||||||
function ConnectionLostModal(props) {
|
function ConnectionLostModal(props) {
|
||||||
@@ -22,16 +23,16 @@ function ConnectionLostModal(props) {
|
|||||||
>
|
>
|
||||||
<ModalContent onModalClose={onModalClose}>
|
<ModalContent onModalClose={onModalClose}>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
Connection Lost
|
{translate('ConnectionLost')}
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
|
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<div>
|
<div>
|
||||||
Readarr has lost its connection to the backend and will need to be reloaded to restore functionality.
|
{translate('ConnectionLostToBackend', { appName: 'Readarr' })}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.automatic}>
|
<div className={styles.automatic}>
|
||||||
Readarr will try to connect automatically, or you can click reload below.
|
{translate('ConnectionLostReconnect', { appName: 'Readarr' })}
|
||||||
</div>
|
</div>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
@@ -39,7 +40,7 @@ function ConnectionLostModal(props) {
|
|||||||
kind={kinds.PRIMARY}
|
kind={kinds.PRIMARY}
|
||||||
onPress={onModalClose}
|
onPress={onModalClose}
|
||||||
>
|
>
|
||||||
Reload
|
{translate('Reload')}
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
|
import AuthorsAppState from './AuthorsAppState';
|
||||||
|
import CommandAppState from './CommandAppState';
|
||||||
import SettingsAppState from './SettingsAppState';
|
import SettingsAppState from './SettingsAppState';
|
||||||
|
import SystemAppState from './SystemAppState';
|
||||||
import TagsAppState from './TagsAppState';
|
import TagsAppState from './TagsAppState';
|
||||||
|
|
||||||
interface FilterBuilderPropOption {
|
interface FilterBuilderPropOption {
|
||||||
@@ -33,8 +36,24 @@ export interface CustomFilter {
|
|||||||
filers: PropertyFilter[];
|
filers: PropertyFilter[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AppSectionState {
|
||||||
|
isConnected: boolean;
|
||||||
|
isReconnecting: boolean;
|
||||||
|
version: string;
|
||||||
|
prevVersion?: string;
|
||||||
|
dimensions: {
|
||||||
|
isSmallScreen: boolean;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface AppState {
|
interface AppState {
|
||||||
|
app: AppSectionState;
|
||||||
|
authors: AuthorsAppState;
|
||||||
|
commands: CommandAppState;
|
||||||
settings: SettingsAppState;
|
settings: SettingsAppState;
|
||||||
|
system: SystemAppState;
|
||||||
tags: TagsAppState;
|
tags: TagsAppState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
18
frontend/src/App/State/AuthorsAppState.ts
Normal file
18
frontend/src/App/State/AuthorsAppState.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import AppSectionState, {
|
||||||
|
AppSectionDeleteState,
|
||||||
|
AppSectionSaveState,
|
||||||
|
} from 'App/State/AppSectionState';
|
||||||
|
import Author from 'Author/Author';
|
||||||
|
|
||||||
|
interface AuthorsAppState
|
||||||
|
extends AppSectionState<Author>,
|
||||||
|
AppSectionDeleteState,
|
||||||
|
AppSectionSaveState {
|
||||||
|
itemMap: Record<number, number>;
|
||||||
|
|
||||||
|
deleteOptions: {
|
||||||
|
addImportListExclusion: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AuthorsAppState;
|
||||||
6
frontend/src/App/State/CommandAppState.ts
Normal file
6
frontend/src/App/State/CommandAppState.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import AppSectionState from 'App/State/AppSectionState';
|
||||||
|
import Command from 'Commands/Command';
|
||||||
|
|
||||||
|
export type CommandAppState = AppSectionState<Command>;
|
||||||
|
|
||||||
|
export default CommandAppState;
|
||||||
@@ -1,18 +1,23 @@
|
|||||||
import AppSectionState, {
|
import AppSectionState, {
|
||||||
AppSectionDeleteState,
|
AppSectionDeleteState,
|
||||||
|
AppSectionItemState,
|
||||||
AppSectionSaveState,
|
AppSectionSaveState,
|
||||||
} from 'App/State/AppSectionState';
|
} from 'App/State/AppSectionState';
|
||||||
import DownloadClient from 'typings/DownloadClient';
|
import DownloadClient from 'typings/DownloadClient';
|
||||||
import ImportList from 'typings/ImportList';
|
import ImportList from 'typings/ImportList';
|
||||||
import Indexer from 'typings/Indexer';
|
import Indexer from 'typings/Indexer';
|
||||||
|
import IndexerFlag from 'typings/IndexerFlag';
|
||||||
import Notification from 'typings/Notification';
|
import Notification from 'typings/Notification';
|
||||||
import { UiSettings } from 'typings/UiSettings';
|
import General from 'typings/Settings/General';
|
||||||
|
import UiSettings from 'typings/Settings/UiSettings';
|
||||||
|
|
||||||
export interface DownloadClientAppState
|
export interface DownloadClientAppState
|
||||||
extends AppSectionState<DownloadClient>,
|
extends AppSectionState<DownloadClient>,
|
||||||
AppSectionDeleteState,
|
AppSectionDeleteState,
|
||||||
AppSectionSaveState {}
|
AppSectionSaveState {}
|
||||||
|
|
||||||
|
export type GeneralAppState = AppSectionItemState<General>;
|
||||||
|
|
||||||
export interface ImportListAppState
|
export interface ImportListAppState
|
||||||
extends AppSectionState<ImportList>,
|
extends AppSectionState<ImportList>,
|
||||||
AppSectionDeleteState,
|
AppSectionDeleteState,
|
||||||
@@ -27,14 +32,17 @@ export interface NotificationAppState
|
|||||||
extends AppSectionState<Notification>,
|
extends AppSectionState<Notification>,
|
||||||
AppSectionDeleteState {}
|
AppSectionDeleteState {}
|
||||||
|
|
||||||
|
export type IndexerFlagSettingsAppState = AppSectionState<IndexerFlag>;
|
||||||
export type UiSettingsAppState = AppSectionState<UiSettings>;
|
export type UiSettingsAppState = AppSectionState<UiSettings>;
|
||||||
|
|
||||||
interface SettingsAppState {
|
interface SettingsAppState {
|
||||||
downloadClients: DownloadClientAppState;
|
downloadClients: DownloadClientAppState;
|
||||||
|
general: GeneralAppState;
|
||||||
importLists: ImportListAppState;
|
importLists: ImportListAppState;
|
||||||
|
indexerFlags: IndexerFlagSettingsAppState;
|
||||||
indexers: IndexerAppState;
|
indexers: IndexerAppState;
|
||||||
notifications: NotificationAppState;
|
notifications: NotificationAppState;
|
||||||
uiSettings: UiSettingsAppState;
|
ui: UiSettingsAppState;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SettingsAppState;
|
export default SettingsAppState;
|
||||||
|
|||||||
13
frontend/src/App/State/SystemAppState.ts
Normal file
13
frontend/src/App/State/SystemAppState.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import SystemStatus from 'typings/SystemStatus';
|
||||||
|
import Update from 'typings/Update';
|
||||||
|
import AppSectionState, { AppSectionItemState } from './AppSectionState';
|
||||||
|
|
||||||
|
export type SystemStatusAppState = AppSectionItemState<SystemStatus>;
|
||||||
|
export type UpdateAppState = AppSectionState<Update>;
|
||||||
|
|
||||||
|
interface SystemAppState {
|
||||||
|
updates: UpdateAppState;
|
||||||
|
status: SystemStatusAppState;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SystemAppState;
|
||||||
21
frontend/src/Author/Author.ts
Normal file
21
frontend/src/Author/Author.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import ModelBase from 'App/ModelBase';
|
||||||
|
|
||||||
|
export type AuthorStatus = 'continuing' | 'ended';
|
||||||
|
|
||||||
|
interface Author extends ModelBase {
|
||||||
|
added: string;
|
||||||
|
genres: string[];
|
||||||
|
monitored: boolean;
|
||||||
|
overview: string;
|
||||||
|
path: string;
|
||||||
|
qualityProfileId: number;
|
||||||
|
metadataProfileId: number;
|
||||||
|
rootFolderPath: string;
|
||||||
|
sortName: string;
|
||||||
|
status: AuthorStatus;
|
||||||
|
tags: number[];
|
||||||
|
authorName: string;
|
||||||
|
isSaving?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Author;
|
||||||
@@ -7,13 +7,10 @@ function findImage(images, coverType) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getUrl(image, coverType, size) {
|
function getUrl(image, coverType, size) {
|
||||||
if (image) {
|
const imageUrl = image?.url;
|
||||||
// Remove protocol
|
|
||||||
let url = image.url;
|
|
||||||
|
|
||||||
url = url.replace(`${coverType}.jpg`, `${coverType}-${size}.jpg`);
|
if (imageUrl) {
|
||||||
|
return imageUrl.replace(`${coverType}.jpg`, `${coverType}-${size}.jpg`);
|
||||||
return url;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import PropTypes from 'prop-types';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Link from 'Components/Link/Link';
|
import Link from 'Components/Link/Link';
|
||||||
|
|
||||||
function AuthorNameLink({ titleSlug, authorName }) {
|
function AuthorNameLink({ titleSlug, authorName, ...otherProps }) {
|
||||||
const link = `/author/${titleSlug}`;
|
const link = `/author/${titleSlug}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link to={link}>
|
<Link to={link} {...otherProps}>
|
||||||
{authorName}
|
{authorName}
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
|||||||
21
frontend/src/Author/AuthorStatus.ts
Normal file
21
frontend/src/Author/AuthorStatus.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { AuthorStatus } from 'Author/Author';
|
||||||
|
import { icons } from 'Helpers/Props';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
|
||||||
|
export function getAuthorStatusDetails(status: AuthorStatus) {
|
||||||
|
let statusDetails = {
|
||||||
|
icon: icons.AUTHOR_CONTINUING,
|
||||||
|
title: translate('StatusEndedContinuing'),
|
||||||
|
message: translate('ContinuingMoreBooksAreExpected'),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (status === 'ended') {
|
||||||
|
statusDetails = {
|
||||||
|
icon: icons.AUTHOR_ENDED,
|
||||||
|
title: translate('StatusEndedEnded'),
|
||||||
|
message: translate('ContinuingNoAdditionalBooksAreExpected'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return statusDetails;
|
||||||
|
}
|
||||||
@@ -44,6 +44,10 @@
|
|||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.filterIcon {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
.authorNavigationButtons {
|
.authorNavigationButtons {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ interface CssExports {
|
|||||||
'authorUpButton': string;
|
'authorUpButton': string;
|
||||||
'contentContainer': string;
|
'contentContainer': string;
|
||||||
'errorMessage': string;
|
'errorMessage': string;
|
||||||
|
'filterIcon': string;
|
||||||
'innerContentBody': string;
|
'innerContentBody': string;
|
||||||
'metadataMessage': string;
|
'metadataMessage': string;
|
||||||
'selectedTab': string;
|
'selectedTab': string;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import AuthorHistoryTable from 'Author/History/AuthorHistoryTable';
|
|||||||
import MonitoringOptionsModal from 'Author/MonitoringOptions/MonitoringOptionsModal';
|
import MonitoringOptionsModal from 'Author/MonitoringOptions/MonitoringOptionsModal';
|
||||||
import BookEditorFooter from 'Book/Editor/BookEditorFooter';
|
import BookEditorFooter from 'Book/Editor/BookEditorFooter';
|
||||||
import BookFileEditorTable from 'BookFile/Editor/BookFileEditorTable';
|
import BookFileEditorTable from 'BookFile/Editor/BookFileEditorTable';
|
||||||
|
import Alert from 'Components/Alert';
|
||||||
import IconButton from 'Components/Link/IconButton';
|
import IconButton from 'Components/Link/IconButton';
|
||||||
import Link from 'Components/Link/Link';
|
import Link from 'Components/Link/Link';
|
||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
@@ -17,7 +18,7 @@ import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
|||||||
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
||||||
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
|
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
|
||||||
import SwipeHeaderConnector from 'Components/Swipe/SwipeHeaderConnector';
|
import SwipeHeaderConnector from 'Components/Swipe/SwipeHeaderConnector';
|
||||||
import { align, icons } from 'Helpers/Props';
|
import { align, icons, kinds } from 'Helpers/Props';
|
||||||
import InteractiveSearchFilterMenuConnector from 'InteractiveSearch/InteractiveSearchFilterMenuConnector';
|
import InteractiveSearchFilterMenuConnector from 'InteractiveSearch/InteractiveSearchFilterMenuConnector';
|
||||||
import InteractiveSearchTable from 'InteractiveSearch/InteractiveSearchTable';
|
import InteractiveSearchTable from 'InteractiveSearch/InteractiveSearchTable';
|
||||||
import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnector';
|
import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnector';
|
||||||
@@ -239,9 +240,14 @@ class AuthorDetails extends Component {
|
|||||||
saveError,
|
saveError,
|
||||||
isDeleting,
|
isDeleting,
|
||||||
deleteError,
|
deleteError,
|
||||||
statistics
|
statistics = {}
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
bookFileCount = 0,
|
||||||
|
totalBookCount = 0
|
||||||
|
} = statistics;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isOrganizeModalOpen,
|
isOrganizeModalOpen,
|
||||||
isRetagModalOpen,
|
isRetagModalOpen,
|
||||||
@@ -407,22 +413,25 @@ class AuthorDetails extends Component {
|
|||||||
|
|
||||||
<div className={styles.contentContainer}>
|
<div className={styles.contentContainer}>
|
||||||
{
|
{
|
||||||
!isPopulated && !booksError && !bookFilesError &&
|
!isPopulated && !booksError && !bookFilesError ?
|
||||||
<LoadingIndicator />
|
<LoadingIndicator /> :
|
||||||
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
!isFetching && booksError &&
|
!isFetching && booksError ?
|
||||||
<div>
|
<Alert kind={kinds.DANGER}>
|
||||||
{translate('LoadingBooksFailed')}
|
{translate('LoadingBooksFailed')}
|
||||||
</div>
|
</Alert> :
|
||||||
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
!isFetching && bookFilesError &&
|
!isFetching && bookFilesError ?
|
||||||
<div>
|
<Alert kind={kinds.DANGER}>
|
||||||
{translate('LoadingBookFilesFailed')}
|
{translate('LoadingBookFilesFailed')}
|
||||||
</div>
|
</Alert> :
|
||||||
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -435,7 +444,7 @@ class AuthorDetails extends Component {
|
|||||||
className={styles.tab}
|
className={styles.tab}
|
||||||
selectedClassName={styles.selectedTab}
|
selectedClassName={styles.selectedTab}
|
||||||
>
|
>
|
||||||
{translate('BooksTotal', [statistics.totalBookCount])}
|
{translate('BooksTotal', [totalBookCount])}
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
||||||
<Tab
|
<Tab
|
||||||
@@ -463,7 +472,7 @@ class AuthorDetails extends Component {
|
|||||||
className={styles.tab}
|
className={styles.tab}
|
||||||
selectedClassName={styles.selectedTab}
|
selectedClassName={styles.selectedTab}
|
||||||
>
|
>
|
||||||
{translate('FilesTotal', [statistics.bookFileCount])}
|
{translate('FilesTotal', [bookFileCount])}
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -155,7 +155,6 @@ function createMapStateToProps() {
|
|||||||
const isRefreshing = isAuthorRefreshing || allAuthorRefreshing;
|
const isRefreshing = isAuthorRefreshing || allAuthorRefreshing;
|
||||||
const isSearching = isCommandExecuting(findCommand(commands, { name: commandNames.AUTHOR_SEARCH, authorId: author.id }));
|
const isSearching = isCommandExecuting(findCommand(commands, { name: commandNames.AUTHOR_SEARCH, authorId: author.id }));
|
||||||
const isRenamingFiles = isCommandExecuting(findCommand(commands, { name: commandNames.RENAME_FILES, authorId: author.id }));
|
const isRenamingFiles = isCommandExecuting(findCommand(commands, { name: commandNames.RENAME_FILES, authorId: author.id }));
|
||||||
|
|
||||||
const isRenamingAuthorCommand = findCommand(commands, { name: commandNames.RENAME_AUTHOR });
|
const isRenamingAuthorCommand = findCommand(commands, { name: commandNames.RENAME_AUTHOR });
|
||||||
const isRenamingAuthor = (
|
const isRenamingAuthor = (
|
||||||
isCommandExecuting(isRenamingAuthorCommand) &&
|
isCommandExecuting(isRenamingAuthorCommand) &&
|
||||||
|
|||||||
@@ -136,8 +136,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
|
font-weight: 300;
|
||||||
font-size: 30px;
|
font-size: 30px;
|
||||||
line-height: 50px;
|
line-height: 30px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import TextTruncate from 'react-text-truncate';
|
import TextTruncate from 'react-text-truncate';
|
||||||
import AuthorPoster from 'Author/AuthorPoster';
|
import AuthorPoster from 'Author/AuthorPoster';
|
||||||
|
import { getAuthorStatusDetails } from 'Author/AuthorStatus';
|
||||||
import HeartRating from 'Components/HeartRating';
|
import HeartRating from 'Components/HeartRating';
|
||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
import Label from 'Components/Label';
|
import Label from 'Components/Label';
|
||||||
@@ -11,7 +12,7 @@ import MonitorToggleButton from 'Components/MonitorToggleButton';
|
|||||||
import Popover from 'Components/Tooltip/Popover';
|
import Popover from 'Components/Tooltip/Popover';
|
||||||
import Tooltip from 'Components/Tooltip/Tooltip';
|
import Tooltip from 'Components/Tooltip/Tooltip';
|
||||||
import { icons, kinds, sizes, tooltipPositions } from 'Helpers/Props';
|
import { icons, kinds, sizes, tooltipPositions } from 'Helpers/Props';
|
||||||
import QualityProfileNameConnector from 'Settings/Profiles/Quality/QualityProfileNameConnector';
|
import QualityProfileName from 'Settings/Profiles/Quality/QualityProfileName';
|
||||||
import fonts from 'Styles/Variables/fonts';
|
import fonts from 'Styles/Variables/fonts';
|
||||||
import formatBytes from 'Utilities/Number/formatBytes';
|
import formatBytes from 'Utilities/Number/formatBytes';
|
||||||
import stripHtml from 'Utilities/String/stripHtml';
|
import stripHtml from 'Utilities/String/stripHtml';
|
||||||
@@ -25,12 +26,7 @@ const defaultFontSize = parseInt(fonts.defaultFontSize);
|
|||||||
const lineHeight = parseFloat(fonts.lineHeight);
|
const lineHeight = parseFloat(fonts.lineHeight);
|
||||||
|
|
||||||
function getFanartUrl(images) {
|
function getFanartUrl(images) {
|
||||||
const fanartImage = images.find((x) => x.coverType === 'fanart');
|
return images.find((x) => x.coverType === 'fanart')?.url;
|
||||||
|
|
||||||
if (fanartImage) {
|
|
||||||
// Remove protocol
|
|
||||||
return fanartImage.url.replace(/^https?:/, '');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class AuthorDetailsHeader extends Component {
|
class AuthorDetailsHeader extends Component {
|
||||||
@@ -92,11 +88,11 @@ class AuthorDetailsHeader extends Component {
|
|||||||
titleWidth
|
titleWidth
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
|
const statusDetails = getAuthorStatusDetails(status);
|
||||||
|
|
||||||
const fanartUrl = getFanartUrl(images);
|
const fanartUrl = getFanartUrl(images);
|
||||||
const marqueeWidth = titleWidth - (isSmallScreen ? 85 : 160);
|
const marqueeWidth = titleWidth - (isSmallScreen ? 85 : 160);
|
||||||
|
|
||||||
const continuing = status === 'continuing';
|
|
||||||
|
|
||||||
let bookFilesCountMessage = translate('BookFilesCountMessage');
|
let bookFilesCountMessage = translate('BookFilesCountMessage');
|
||||||
|
|
||||||
if (bookFileCount === 1) {
|
if (bookFileCount === 1) {
|
||||||
@@ -218,7 +214,7 @@ class AuthorDetailsHeader extends Component {
|
|||||||
|
|
||||||
<span className={styles.qualityProfileName}>
|
<span className={styles.qualityProfileName}>
|
||||||
{
|
{
|
||||||
<QualityProfileNameConnector
|
<QualityProfileName
|
||||||
qualityProfileId={qualityProfileId}
|
qualityProfileId={qualityProfileId}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
@@ -241,16 +237,16 @@ class AuthorDetailsHeader extends Component {
|
|||||||
|
|
||||||
<Label
|
<Label
|
||||||
className={styles.detailsLabel}
|
className={styles.detailsLabel}
|
||||||
title={continuing ? translate('ContinuingMoreBooksAreExpected') : translate('ContinuingNoAdditionalBooksAreExpected')}
|
title={statusDetails.message}
|
||||||
size={sizes.LARGE}
|
size={sizes.LARGE}
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
name={continuing ? icons.AUTHOR_CONTINUING : icons.AUTHOR_ENDED}
|
name={statusDetails.icon}
|
||||||
size={17}
|
size={17}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<span className={styles.qualityProfileName}>
|
<span className={styles.qualityProfileName}>
|
||||||
{continuing ? 'Continuing' : 'Deceased'}
|
{statusDetails.title}
|
||||||
</span>
|
</span>
|
||||||
</Label>
|
</Label>
|
||||||
|
|
||||||
|
|||||||
@@ -27,3 +27,9 @@
|
|||||||
|
|
||||||
width: 80px;
|
width: 80px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.indexerFlags {
|
||||||
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
|||||||
1
frontend/src/Author/Details/BookRow.css.d.ts
vendored
1
frontend/src/Author/Details/BookRow.css.d.ts
vendored
@@ -1,6 +1,7 @@
|
|||||||
// This file is automatically generated.
|
// This file is automatically generated.
|
||||||
// Please do not change this file!
|
// Please do not change this file!
|
||||||
interface CssExports {
|
interface CssExports {
|
||||||
|
'indexerFlags': string;
|
||||||
'monitored': string;
|
'monitored': string;
|
||||||
'pageCount': string;
|
'pageCount': string;
|
||||||
'position': string;
|
'position': string;
|
||||||
|
|||||||
@@ -2,12 +2,17 @@ import PropTypes from 'prop-types';
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import BookSearchCellConnector from 'Book/BookSearchCellConnector';
|
import BookSearchCellConnector from 'Book/BookSearchCellConnector';
|
||||||
import BookTitleLink from 'Book/BookTitleLink';
|
import BookTitleLink from 'Book/BookTitleLink';
|
||||||
|
import IndexerFlags from 'Book/IndexerFlags';
|
||||||
|
import Icon from 'Components/Icon';
|
||||||
import MonitorToggleButton from 'Components/MonitorToggleButton';
|
import MonitorToggleButton from 'Components/MonitorToggleButton';
|
||||||
import StarRating from 'Components/StarRating';
|
import StarRating from 'Components/StarRating';
|
||||||
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
||||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||||
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
|
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
|
||||||
import TableRow from 'Components/Table/TableRow';
|
import TableRow from 'Components/Table/TableRow';
|
||||||
|
import Popover from 'Components/Tooltip/Popover';
|
||||||
|
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
import BookStatus from './BookStatus';
|
import BookStatus from './BookStatus';
|
||||||
import styles from './BookRow.css';
|
import styles from './BookRow.css';
|
||||||
|
|
||||||
@@ -59,6 +64,7 @@ class BookRow extends Component {
|
|||||||
releaseDate,
|
releaseDate,
|
||||||
title,
|
title,
|
||||||
seriesTitle,
|
seriesTitle,
|
||||||
|
authorName,
|
||||||
position,
|
position,
|
||||||
pageCount,
|
pageCount,
|
||||||
ratings,
|
ratings,
|
||||||
@@ -66,6 +72,7 @@ class BookRow extends Component {
|
|||||||
authorMonitored,
|
authorMonitored,
|
||||||
titleSlug,
|
titleSlug,
|
||||||
bookFiles,
|
bookFiles,
|
||||||
|
indexerFlags,
|
||||||
isEditorActive,
|
isEditorActive,
|
||||||
isSelected,
|
isSelected,
|
||||||
onSelectedChange,
|
onSelectedChange,
|
||||||
@@ -189,6 +196,24 @@ class BookRow extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (name === 'indexerFlags') {
|
||||||
|
return (
|
||||||
|
<TableRowCell
|
||||||
|
key={name}
|
||||||
|
className={styles.indexerFlags}
|
||||||
|
>
|
||||||
|
{indexerFlags ? (
|
||||||
|
<Popover
|
||||||
|
anchor={<Icon name={icons.FLAG} kind={kinds.PRIMARY} />}
|
||||||
|
title={translate('IndexerFlags')}
|
||||||
|
body={<IndexerFlags indexerFlags={indexerFlags} />}
|
||||||
|
position={tooltipPositions.LEFT}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</TableRowCell>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (name === 'status') {
|
if (name === 'status') {
|
||||||
return (
|
return (
|
||||||
<TableRowCell
|
<TableRowCell
|
||||||
@@ -211,6 +236,7 @@ class BookRow extends Component {
|
|||||||
bookId={id}
|
bookId={id}
|
||||||
authorId={authorId}
|
authorId={authorId}
|
||||||
bookTitle={title}
|
bookTitle={title}
|
||||||
|
authorName={authorName}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -229,9 +255,11 @@ BookRow.propTypes = {
|
|||||||
releaseDate: PropTypes.string,
|
releaseDate: PropTypes.string,
|
||||||
title: PropTypes.string.isRequired,
|
title: PropTypes.string.isRequired,
|
||||||
seriesTitle: PropTypes.string.isRequired,
|
seriesTitle: PropTypes.string.isRequired,
|
||||||
|
authorName: PropTypes.string.isRequired,
|
||||||
position: PropTypes.string,
|
position: PropTypes.string,
|
||||||
pageCount: PropTypes.number,
|
pageCount: PropTypes.number,
|
||||||
ratings: PropTypes.object.isRequired,
|
ratings: PropTypes.object.isRequired,
|
||||||
|
indexerFlags: PropTypes.number.isRequired,
|
||||||
titleSlug: PropTypes.string.isRequired,
|
titleSlug: PropTypes.string.isRequired,
|
||||||
isSaving: PropTypes.bool,
|
isSaving: PropTypes.bool,
|
||||||
authorMonitored: PropTypes.bool.isRequired,
|
authorMonitored: PropTypes.bool.isRequired,
|
||||||
@@ -243,4 +271,8 @@ BookRow.propTypes = {
|
|||||||
onMonitorBookPress: PropTypes.func.isRequired
|
onMonitorBookPress: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
BookRow.defaultProps = {
|
||||||
|
indexerFlags: 0
|
||||||
|
};
|
||||||
|
|
||||||
export default BookRow;
|
export default BookRow;
|
||||||
|
|||||||
@@ -7,21 +7,18 @@ import BookRow from './BookRow';
|
|||||||
const selectBookFiles = createSelector(
|
const selectBookFiles = createSelector(
|
||||||
(state) => state.bookFiles,
|
(state) => state.bookFiles,
|
||||||
(bookFiles) => {
|
(bookFiles) => {
|
||||||
const {
|
const { items } = bookFiles;
|
||||||
items
|
|
||||||
} = bookFiles;
|
|
||||||
|
|
||||||
const bookFileDict = items.reduce((acc, file) => {
|
return items.reduce((acc, file) => {
|
||||||
const bookId = file.bookId;
|
const bookId = file.bookId;
|
||||||
if (!acc.hasOwnProperty(bookId)) {
|
if (!acc.hasOwnProperty(bookId)) {
|
||||||
acc[bookId] = [];
|
acc[bookId] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
acc[bookId].push(file);
|
acc[bookId].push(file);
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
return bookFileDict;
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -31,9 +28,14 @@ function createMapStateToProps() {
|
|||||||
selectBookFiles,
|
selectBookFiles,
|
||||||
(state, { id }) => id,
|
(state, { id }) => id,
|
||||||
(author = {}, bookFiles, bookId) => {
|
(author = {}, bookFiles, bookId) => {
|
||||||
|
const files = bookFiles[bookId] ?? [];
|
||||||
|
const bookFile = files[0];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
authorMonitored: author.monitored,
|
authorMonitored: author.monitored,
|
||||||
bookFiles: bookFiles[bookId] ?? []
|
authorName: author.authorName,
|
||||||
|
bookFiles: files,
|
||||||
|
indexerFlags: bookFile ? bookFile.indexerFlags : 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ class AuthorEditorFooter extends Component {
|
|||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const monitoredOptions = [
|
const monitoredOptions = [
|
||||||
{ key: NO_CHANGE, value: translate('NoChange'), disabled: true },
|
{ key: NO_CHANGE, value: translate('NoChange'), isDisabled: true },
|
||||||
{ key: 'monitored', value: translate('Monitored') },
|
{ key: 'monitored', value: translate('Monitored') },
|
||||||
{ key: 'unmonitored', value: translate('Unmonitored') }
|
{ key: 'unmonitored', value: translate('Unmonitored') }
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Modal from 'Components/Modal/Modal';
|
import Modal from 'Components/Modal/Modal';
|
||||||
|
import { sizes } from 'Helpers/Props';
|
||||||
import AuthorHistoryContentConnector from './AuthorHistoryContentConnector';
|
import AuthorHistoryContentConnector from './AuthorHistoryContentConnector';
|
||||||
import AuthorHistoryModalContent from './AuthorHistoryModalContent';
|
import AuthorHistoryModalContent from './AuthorHistoryModalContent';
|
||||||
|
|
||||||
@@ -14,6 +15,7 @@ function AuthorHistoryModal(props) {
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
|
size={sizes.EXTRA_LARGE}
|
||||||
onModalClose={onModalClose}
|
onModalClose={onModalClose}
|
||||||
>
|
>
|
||||||
<AuthorHistoryContentConnector
|
<AuthorHistoryContentConnector
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import ModalBody from 'Components/Modal/ModalBody';
|
|||||||
import ModalContent from 'Components/Modal/ModalContent';
|
import ModalContent from 'Components/Modal/ModalContent';
|
||||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
import AuthorHistoryTableContent from './AuthorHistoryTableContent';
|
import AuthorHistoryTableContent from './AuthorHistoryTableContent';
|
||||||
|
|
||||||
class AuthorHistoryModalContent extends Component {
|
class AuthorHistoryModalContent extends Component {
|
||||||
@@ -20,7 +21,7 @@ class AuthorHistoryModalContent extends Component {
|
|||||||
return (
|
return (
|
||||||
<ModalContent onModalClose={onModalClose}>
|
<ModalContent onModalClose={onModalClose}>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
History
|
{translate('History')}
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
|
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
@@ -31,7 +32,7 @@ class AuthorHistoryModalContent extends Component {
|
|||||||
|
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button onPress={onModalClose}>
|
<Button onPress={onModalClose}>
|
||||||
Close
|
{translate('Close')}
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.details,
|
|
||||||
.actions {
|
.actions {
|
||||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
// Please do not change this file!
|
// Please do not change this file!
|
||||||
interface CssExports {
|
interface CssExports {
|
||||||
'actions': string;
|
'actions': string;
|
||||||
'details': string;
|
|
||||||
'sourceTitle': string;
|
'sourceTitle': string;
|
||||||
}
|
}
|
||||||
export const cssExports: CssExports;
|
export const cssExports: CssExports;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import HistoryDetailsConnector from 'Activity/History/Details/HistoryDetailsConnector';
|
import HistoryDetailsConnector from 'Activity/History/Details/HistoryDetailsConnector';
|
||||||
import HistoryEventTypeCell from 'Activity/History/HistoryEventTypeCell';
|
import HistoryEventTypeCell from 'Activity/History/HistoryEventTypeCell';
|
||||||
|
import BookFormats from 'Book/BookFormats';
|
||||||
import BookQuality from 'Book/BookQuality';
|
import BookQuality from 'Book/BookQuality';
|
||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
import IconButton from 'Components/Link/IconButton';
|
import IconButton from 'Components/Link/IconButton';
|
||||||
@@ -11,6 +12,7 @@ import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
|||||||
import TableRow from 'Components/Table/TableRow';
|
import TableRow from 'Components/Table/TableRow';
|
||||||
import Popover from 'Components/Tooltip/Popover';
|
import Popover from 'Components/Tooltip/Popover';
|
||||||
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
|
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
|
||||||
|
import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import styles from './AuthorHistoryRow.css';
|
import styles from './AuthorHistoryRow.css';
|
||||||
|
|
||||||
@@ -75,6 +77,8 @@ class AuthorHistoryRow extends Component {
|
|||||||
sourceTitle,
|
sourceTitle,
|
||||||
quality,
|
quality,
|
||||||
qualityCutoffNotMet,
|
qualityCutoffNotMet,
|
||||||
|
customFormats,
|
||||||
|
customFormatScore,
|
||||||
date,
|
date,
|
||||||
data,
|
data,
|
||||||
book
|
book
|
||||||
@@ -106,11 +110,19 @@ class AuthorHistoryRow extends Component {
|
|||||||
/>
|
/>
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
|
|
||||||
|
<TableRowCell>
|
||||||
|
<BookFormats formats={customFormats} />
|
||||||
|
</TableRowCell>
|
||||||
|
|
||||||
|
<TableRowCell>
|
||||||
|
{formatCustomFormatScore(customFormatScore, customFormats.length)}
|
||||||
|
</TableRowCell>
|
||||||
|
|
||||||
<RelativeDateCellConnector
|
<RelativeDateCellConnector
|
||||||
date={date}
|
date={date}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TableRowCell className={styles.details}>
|
<TableRowCell className={styles.actions}>
|
||||||
<Popover
|
<Popover
|
||||||
anchor={
|
anchor={
|
||||||
<Icon
|
<Icon
|
||||||
@@ -127,14 +139,13 @@ class AuthorHistoryRow extends Component {
|
|||||||
}
|
}
|
||||||
position={tooltipPositions.LEFT}
|
position={tooltipPositions.LEFT}
|
||||||
/>
|
/>
|
||||||
</TableRowCell>
|
|
||||||
|
|
||||||
<TableRowCell className={styles.actions}>
|
|
||||||
{
|
{
|
||||||
eventType === 'grabbed' &&
|
eventType === 'grabbed' &&
|
||||||
<IconButton
|
<IconButton
|
||||||
title={translate('MarkAsFailed')}
|
title={translate('MarkAsFailed')}
|
||||||
name={icons.REMOVE}
|
name={icons.REMOVE}
|
||||||
|
size={14}
|
||||||
onPress={this.onMarkAsFailedPress}
|
onPress={this.onMarkAsFailedPress}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
@@ -160,6 +171,8 @@ AuthorHistoryRow.propTypes = {
|
|||||||
sourceTitle: PropTypes.string.isRequired,
|
sourceTitle: PropTypes.string.isRequired,
|
||||||
quality: PropTypes.object.isRequired,
|
quality: PropTypes.object.isRequired,
|
||||||
qualityCutoffNotMet: PropTypes.bool.isRequired,
|
qualityCutoffNotMet: PropTypes.bool.isRequired,
|
||||||
|
customFormats: PropTypes.arrayOf(PropTypes.object),
|
||||||
|
customFormatScore: PropTypes.number.isRequired,
|
||||||
date: PropTypes.string.isRequired,
|
date: PropTypes.string.isRequired,
|
||||||
data: PropTypes.object.isRequired,
|
data: PropTypes.object.isRequired,
|
||||||
fullAuthor: PropTypes.bool.isRequired,
|
fullAuthor: PropTypes.bool.isRequired,
|
||||||
|
|||||||
9
frontend/src/Author/History/AuthorHistoryTable.css
Normal file
9
frontend/src/Author/History/AuthorHistoryTable.css
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
.container {
|
||||||
|
border: 1px solid var(--borderColor);
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: var(--inputBackgroundColor);
|
||||||
|
|
||||||
|
&:last-of-type {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
7
frontend/src/Author/History/AuthorHistoryTable.css.d.ts
vendored
Normal file
7
frontend/src/Author/History/AuthorHistoryTable.css.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// This file is automatically generated.
|
||||||
|
// Please do not change this file!
|
||||||
|
interface CssExports {
|
||||||
|
'container': string;
|
||||||
|
}
|
||||||
|
export const cssExports: CssExports;
|
||||||
|
export default cssExports;
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import AuthorHistoryContentConnector from 'Author/History/AuthorHistoryContentConnector';
|
import AuthorHistoryContentConnector from 'Author/History/AuthorHistoryContentConnector';
|
||||||
import AuthorHistoryTableContent from 'Author/History/AuthorHistoryTableContent';
|
import AuthorHistoryTableContent from 'Author/History/AuthorHistoryTableContent';
|
||||||
|
import styles from './AuthorHistoryTable.css';
|
||||||
|
|
||||||
function AuthorHistoryTable(props) {
|
function AuthorHistoryTable(props) {
|
||||||
const {
|
const {
|
||||||
@@ -8,10 +9,12 @@ function AuthorHistoryTable(props) {
|
|||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthorHistoryContentConnector
|
<div className={styles.container}>
|
||||||
component={AuthorHistoryTableContent}
|
<AuthorHistoryContentConnector
|
||||||
{...otherProps}
|
component={AuthorHistoryTableContent}
|
||||||
/>
|
{...otherProps}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
.blankpad {
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
padding-left: 2em;
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// This file is automatically generated.
|
// This file is automatically generated.
|
||||||
// Please do not change this file!
|
// Please do not change this file!
|
||||||
interface CssExports {
|
interface CssExports {
|
||||||
'input': string;
|
'blankpad': string;
|
||||||
}
|
}
|
||||||
export const cssExports: CssExports;
|
export const cssExports: CssExports;
|
||||||
export default cssExports;
|
export default cssExports;
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import Alert from 'Components/Alert';
|
import Alert from 'Components/Alert';
|
||||||
|
import Icon from 'Components/Icon';
|
||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
import Table from 'Components/Table/Table';
|
import Table from 'Components/Table/Table';
|
||||||
import TableBody from 'Components/Table/TableBody';
|
import TableBody from 'Components/Table/TableBody';
|
||||||
import { kinds } from 'Helpers/Props';
|
import { icons, kinds } from 'Helpers/Props';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import AuthorHistoryRowConnector from './AuthorHistoryRowConnector';
|
import AuthorHistoryRowConnector from './AuthorHistoryRowConnector';
|
||||||
|
import styles from './AuthorHistoryTableContent.css';
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
@@ -15,32 +17,41 @@ const columns = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'book',
|
name: 'book',
|
||||||
label: 'Book',
|
label: () => translate('Book'),
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'sourceTitle',
|
name: 'sourceTitle',
|
||||||
label: 'Source Title',
|
label: () => translate( 'SourceTitle'),
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'quality',
|
name: 'quality',
|
||||||
label: 'Quality',
|
label: () => translate('Quality'),
|
||||||
|
isVisible: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'customFormats',
|
||||||
|
label: () => translate('CustomFormats'),
|
||||||
|
isSortable: false,
|
||||||
|
isVisible: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'customFormatScore',
|
||||||
|
label: React.createElement(Icon, {
|
||||||
|
name: icons.SCORE,
|
||||||
|
title: () => translate('CustomFormatScore')
|
||||||
|
}),
|
||||||
|
isSortable: true,
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'date',
|
name: 'date',
|
||||||
label: 'Date',
|
label: () => translate('Date'),
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'details',
|
|
||||||
label: 'Details',
|
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'actions',
|
name: 'actions',
|
||||||
label: 'Actions',
|
|
||||||
isVisible: true
|
isVisible: true
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
@@ -64,7 +75,7 @@ class AuthorHistoryTableContent extends Component {
|
|||||||
const hasItems = !!items.length;
|
const hasItems = !!items.length;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div>
|
||||||
{
|
{
|
||||||
isFetching &&
|
isFetching &&
|
||||||
<LoadingIndicator />
|
<LoadingIndicator />
|
||||||
@@ -79,7 +90,7 @@ class AuthorHistoryTableContent extends Component {
|
|||||||
|
|
||||||
{
|
{
|
||||||
isPopulated && !hasItems && !error &&
|
isPopulated && !hasItems && !error &&
|
||||||
<div>
|
<div className={styles.blankpad}>
|
||||||
{translate('NoHistory')}
|
{translate('NoHistory')}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -103,7 +114,7 @@ class AuthorHistoryTableContent extends Component {
|
|||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
}
|
}
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import AuthorIndex from './AuthorIndex';
|
|||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
createAuthorClientSideCollectionItemsSelector('authorIndex'),
|
createAuthorClientSideCollectionItemsSelector('authorIndex'),
|
||||||
createCommandExecutingSelector(commandNames.REFRESH_AUTHOR),
|
createCommandExecutingSelector(commandNames.BULK_REFRESH_AUTHOR),
|
||||||
createCommandExecutingSelector(commandNames.RSS_SYNC),
|
createCommandExecutingSelector(commandNames.RSS_SYNC),
|
||||||
createCommandExecutingSelector(commandNames.RENAME_AUTHOR),
|
createCommandExecutingSelector(commandNames.RENAME_AUTHOR),
|
||||||
createCommandExecutingSelector(commandNames.RETAG_AUTHOR),
|
createCommandExecutingSelector(commandNames.RETAG_AUTHOR),
|
||||||
@@ -24,17 +24,17 @@ function createMapStateToProps() {
|
|||||||
(
|
(
|
||||||
author,
|
author,
|
||||||
isRefreshingAuthor,
|
isRefreshingAuthor,
|
||||||
|
isRssSyncExecuting,
|
||||||
isOrganizingAuthor,
|
isOrganizingAuthor,
|
||||||
isRetaggingAuthor,
|
isRetaggingAuthor,
|
||||||
isRssSyncExecuting,
|
|
||||||
dimensionsState
|
dimensionsState
|
||||||
) => {
|
) => {
|
||||||
return {
|
return {
|
||||||
...author,
|
...author,
|
||||||
isRefreshingAuthor,
|
isRefreshingAuthor,
|
||||||
|
isRssSyncExecuting,
|
||||||
isOrganizingAuthor,
|
isOrganizingAuthor,
|
||||||
isRetaggingAuthor,
|
isRetaggingAuthor,
|
||||||
isRssSyncExecuting,
|
|
||||||
isSmallScreen: dimensionsState.isSmallScreen
|
isSmallScreen: dimensionsState.isSmallScreen
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ class AuthorIndexOverview extends Component {
|
|||||||
status,
|
status,
|
||||||
titleSlug,
|
titleSlug,
|
||||||
nextAiring,
|
nextAiring,
|
||||||
statistics,
|
statistics = {},
|
||||||
images,
|
images,
|
||||||
posterWidth,
|
posterWidth,
|
||||||
posterHeight,
|
posterHeight,
|
||||||
@@ -113,10 +113,11 @@ class AuthorIndexOverview extends Component {
|
|||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
bookCount,
|
bookCount = 0,
|
||||||
sizeOnDisk,
|
availableBookCount = 0,
|
||||||
bookFileCount,
|
bookFileCount = 0,
|
||||||
totalBookCount
|
totalBookCount = 0,
|
||||||
|
sizeOnDisk = 0
|
||||||
} = statistics;
|
} = statistics;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -179,6 +180,7 @@ class AuthorIndexOverview extends Component {
|
|||||||
monitored={monitored}
|
monitored={monitored}
|
||||||
status={status}
|
status={status}
|
||||||
bookCount={bookCount}
|
bookCount={bookCount}
|
||||||
|
availableBookCount={availableBookCount}
|
||||||
bookFileCount={bookFileCount}
|
bookFileCount={bookFileCount}
|
||||||
totalBookCount={totalBookCount}
|
totalBookCount={totalBookCount}
|
||||||
posterWidth={posterWidth}
|
posterWidth={posterWidth}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import dimensions from 'Styles/Variables/dimensions';
|
|||||||
import formatDateTime from 'Utilities/Date/formatDateTime';
|
import formatDateTime from 'Utilities/Date/formatDateTime';
|
||||||
import getRelativeDate from 'Utilities/Date/getRelativeDate';
|
import getRelativeDate from 'Utilities/Date/getRelativeDate';
|
||||||
import formatBytes from 'Utilities/Number/formatBytes';
|
import formatBytes from 'Utilities/Number/formatBytes';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
import AuthorIndexOverviewInfoRow from './AuthorIndexOverviewInfoRow';
|
import AuthorIndexOverviewInfoRow from './AuthorIndexOverviewInfoRow';
|
||||||
import styles from './AuthorIndexOverviewInfo.css';
|
import styles from './AuthorIndexOverviewInfo.css';
|
||||||
|
|
||||||
@@ -76,9 +77,9 @@ function getInfoRowProps(row, props) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name === 'qualityProfileId') {
|
if (name === 'qualityProfileId' && !!props.qualityProfile?.name) {
|
||||||
return {
|
return {
|
||||||
title: 'Quality Profile',
|
title: translate('QualityProfile'),
|
||||||
iconName: icons.PROFILE,
|
iconName: icons.PROFILE,
|
||||||
label: props.qualityProfile.name
|
label: props.qualityProfile.name
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ class AuthorIndexPoster extends Component {
|
|||||||
titleSlug,
|
titleSlug,
|
||||||
status,
|
status,
|
||||||
nextAiring,
|
nextAiring,
|
||||||
statistics,
|
statistics = {},
|
||||||
images,
|
images,
|
||||||
posterWidth,
|
posterWidth,
|
||||||
posterHeight,
|
posterHeight,
|
||||||
@@ -110,10 +110,11 @@ class AuthorIndexPoster extends Component {
|
|||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
bookCount,
|
bookCount = 0,
|
||||||
sizeOnDisk,
|
availableBookCount = 0,
|
||||||
bookFileCount,
|
bookFileCount = 0,
|
||||||
totalBookCount
|
totalBookCount = 0,
|
||||||
|
sizeOnDisk = 0
|
||||||
} = statistics;
|
} = statistics;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -213,6 +214,7 @@ class AuthorIndexPoster extends Component {
|
|||||||
monitored={monitored}
|
monitored={monitored}
|
||||||
status={status}
|
status={status}
|
||||||
bookCount={bookCount}
|
bookCount={bookCount}
|
||||||
|
availableBookCount={availableBookCount}
|
||||||
bookFileCount={bookFileCount}
|
bookFileCount={bookFileCount}
|
||||||
totalBookCount={totalBookCount}
|
totalBookCount={totalBookCount}
|
||||||
posterWidth={posterWidth}
|
posterWidth={posterWidth}
|
||||||
@@ -233,12 +235,12 @@ class AuthorIndexPoster extends Component {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{showQualityProfile && !!qualityProfile?.name ? (
|
||||||
showQualityProfile &&
|
<div className={styles.title} title={translate('QualityProfile')}>
|
||||||
<div className={styles.title}>
|
{qualityProfile.name}
|
||||||
{qualityProfile.name}
|
</div>
|
||||||
</div>
|
) : null}
|
||||||
}
|
|
||||||
{
|
{
|
||||||
nextAiring &&
|
nextAiring &&
|
||||||
<div className={styles.nextAiring}>
|
<div className={styles.nextAiring}>
|
||||||
|
|||||||
@@ -11,14 +11,15 @@ function AuthorIndexProgressBar(props) {
|
|||||||
monitored,
|
monitored,
|
||||||
status,
|
status,
|
||||||
bookCount,
|
bookCount,
|
||||||
|
availableBookCount,
|
||||||
bookFileCount,
|
bookFileCount,
|
||||||
totalBookCount,
|
totalBookCount,
|
||||||
posterWidth,
|
posterWidth,
|
||||||
detailedProgressBar
|
detailedProgressBar
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const progress = bookCount ? bookCount / totalBookCount * 100 : 100;
|
const progress = bookCount ? (availableBookCount / bookCount) * 100 : 100;
|
||||||
const text = `${bookCount} / ${totalBookCount}`;
|
const text = `${availableBookCount} / ${bookCount}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
@@ -29,7 +30,7 @@ function AuthorIndexProgressBar(props) {
|
|||||||
size={detailedProgressBar ? sizes.MEDIUM : sizes.SMALL}
|
size={detailedProgressBar ? sizes.MEDIUM : sizes.SMALL}
|
||||||
showText={detailedProgressBar}
|
showText={detailedProgressBar}
|
||||||
text={text}
|
text={text}
|
||||||
title={translate('BookFileCountBookCountTotalTotalBookCountInterp', [bookFileCount, bookCount, totalBookCount])}
|
title={translate('AuthorProgressBarText', { bookCount, availableBookCount, bookFileCount, totalBookCount })}
|
||||||
width={posterWidth}
|
width={posterWidth}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -39,6 +40,7 @@ AuthorIndexProgressBar.propTypes = {
|
|||||||
monitored: PropTypes.bool.isRequired,
|
monitored: PropTypes.bool.isRequired,
|
||||||
status: PropTypes.string.isRequired,
|
status: PropTypes.string.isRequired,
|
||||||
bookCount: PropTypes.number.isRequired,
|
bookCount: PropTypes.number.isRequired,
|
||||||
|
availableBookCount: PropTypes.number.isRequired,
|
||||||
bookFileCount: PropTypes.number.isRequired,
|
bookFileCount: PropTypes.number.isRequired,
|
||||||
totalBookCount: PropTypes.number.isRequired,
|
totalBookCount: PropTypes.number.isRequired,
|
||||||
posterWidth: PropTypes.number.isRequired,
|
posterWidth: PropTypes.number.isRequired,
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ class AuthorIndexRow extends Component {
|
|||||||
nextBook,
|
nextBook,
|
||||||
lastBook,
|
lastBook,
|
||||||
added,
|
added,
|
||||||
statistics,
|
statistics = {},
|
||||||
genres,
|
genres,
|
||||||
ratings,
|
ratings,
|
||||||
path,
|
path,
|
||||||
@@ -110,10 +110,11 @@ class AuthorIndexRow extends Component {
|
|||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
bookCount,
|
bookCount = 0,
|
||||||
bookFileCount,
|
availableBookCount = 0,
|
||||||
totalBookCount,
|
bookFileCount = 0,
|
||||||
sizeOnDisk
|
totalBookCount = 0,
|
||||||
|
sizeOnDisk = 0
|
||||||
} = statistics;
|
} = statistics;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -208,7 +209,7 @@ class AuthorIndexRow extends Component {
|
|||||||
key={name}
|
key={name}
|
||||||
className={styles[name]}
|
className={styles[name]}
|
||||||
>
|
>
|
||||||
{qualityProfile.name}
|
{qualityProfile?.name ?? ''}
|
||||||
</VirtualTableRowCell>
|
</VirtualTableRowCell>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -219,7 +220,7 @@ class AuthorIndexRow extends Component {
|
|||||||
key={name}
|
key={name}
|
||||||
className={styles[name]}
|
className={styles[name]}
|
||||||
>
|
>
|
||||||
{metadataProfile.name}
|
{metadataProfile?.name ?? ''}
|
||||||
</VirtualTableRowCell>
|
</VirtualTableRowCell>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -286,7 +287,7 @@ class AuthorIndexRow extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (name === 'bookProgress') {
|
if (name === 'bookProgress') {
|
||||||
const progress = bookCount ? bookFileCount / bookCount * 100 : 100;
|
const progress = bookCount ? (availableBookCount / bookCount) * 100 : 100;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VirtualTableRowCell
|
<VirtualTableRowCell
|
||||||
@@ -297,8 +298,8 @@ class AuthorIndexRow extends Component {
|
|||||||
progress={progress}
|
progress={progress}
|
||||||
kind={getProgressBarKind(status, monitored, progress)}
|
kind={getProgressBarKind(status, monitored, progress)}
|
||||||
showText={true}
|
showText={true}
|
||||||
text={`${bookCount} / ${totalBookCount}`}
|
text={`${availableBookCount} / ${bookCount}`}
|
||||||
title={translate('BookFileCountBookCountTotalTotalBookCountInterp', [bookFileCount, bookCount, totalBookCount])}
|
title={translate('AuthorProgressBarText', { bookCount, availableBookCount, bookFileCount, totalBookCount })}
|
||||||
width={125}
|
width={125}
|
||||||
/>
|
/>
|
||||||
</VirtualTableRowCell>
|
</VirtualTableRowCell>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { getAuthorStatusDetails } from 'Author/AuthorStatus';
|
||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
import VirtualTableRowCell from 'Components/Table/Cells/TableRowCell';
|
import VirtualTableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||||
import { icons } from 'Helpers/Props';
|
import { icons } from 'Helpers/Props';
|
||||||
@@ -15,6 +16,8 @@ function AuthorStatusCell(props) {
|
|||||||
...otherProps
|
...otherProps
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
const statusDetails = getAuthorStatusDetails(status);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Component
|
<Component
|
||||||
className={className}
|
className={className}
|
||||||
@@ -28,8 +31,8 @@ function AuthorStatusCell(props) {
|
|||||||
|
|
||||||
<Icon
|
<Icon
|
||||||
className={styles.statusIcon}
|
className={styles.statusIcon}
|
||||||
name={status === 'ended' ? icons.AUTHOR_ENDED : icons.AUTHOR_CONTINUING}
|
name={statusDetails.icon}
|
||||||
title={status === 'ended' ? translate('StatusEndedDeceased') : translate('StatusEndedContinuing')}
|
title={`${statusDetails.title}: ${statusDetails.message}`}
|
||||||
/>
|
/>
|
||||||
</Component>
|
</Component>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Button from 'Components/Link/Button';
|
import Button from 'Components/Link/Button';
|
||||||
import { kinds } from 'Helpers/Props';
|
import { kinds } from 'Helpers/Props';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
import styles from './NoAuthor.css';
|
import styles from './NoAuthor.css';
|
||||||
|
|
||||||
function NoAuthor(props) {
|
function NoAuthor(props) {
|
||||||
@@ -31,7 +32,7 @@ function NoAuthor(props) {
|
|||||||
to="/settings/mediamanagement"
|
to="/settings/mediamanagement"
|
||||||
kind={kinds.PRIMARY}
|
kind={kinds.PRIMARY}
|
||||||
>
|
>
|
||||||
Add Root Folder
|
{translate('AddRootFolder')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -40,7 +41,7 @@ function NoAuthor(props) {
|
|||||||
to="/add/search"
|
to="/add/search"
|
||||||
kind={kinds.PRIMARY}
|
kind={kinds.PRIMARY}
|
||||||
>
|
>
|
||||||
Add New Author
|
{translate('AddNewAuthor')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import React from 'react';
|
|||||||
import Label from 'Components/Label';
|
import Label from 'Components/Label';
|
||||||
import { kinds } from 'Helpers/Props';
|
import { kinds } from 'Helpers/Props';
|
||||||
import formatBytes from 'Utilities/Number/formatBytes';
|
import formatBytes from 'Utilities/Number/formatBytes';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
|
||||||
function getTooltip(title, quality, size, isMonitored, isCutoffNotMet) {
|
function getTooltip(title, quality, size, isMonitored, isCutoffNotMet) {
|
||||||
const revision = quality.revision;
|
const revision = quality.revision;
|
||||||
@@ -28,6 +29,36 @@ function getTooltip(title, quality, size, isMonitored, isCutoffNotMet) {
|
|||||||
return title;
|
return title;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function revisionLabel(className, quality, showRevision) {
|
||||||
|
if (!showRevision) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (quality.revision.isRepack) {
|
||||||
|
return (
|
||||||
|
<Label
|
||||||
|
className={className}
|
||||||
|
kind={kinds.PRIMARY}
|
||||||
|
title={translate('Repack')}
|
||||||
|
>
|
||||||
|
R
|
||||||
|
</Label>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (quality.revision.version && quality.revision.version > 1) {
|
||||||
|
return (
|
||||||
|
<Label
|
||||||
|
className={className}
|
||||||
|
kind={kinds.PRIMARY}
|
||||||
|
title={translate('Proper')}
|
||||||
|
>
|
||||||
|
P
|
||||||
|
</Label>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function BookQuality(props) {
|
function BookQuality(props) {
|
||||||
const {
|
const {
|
||||||
className,
|
className,
|
||||||
@@ -35,7 +66,8 @@ function BookQuality(props) {
|
|||||||
quality,
|
quality,
|
||||||
size,
|
size,
|
||||||
isMonitored,
|
isMonitored,
|
||||||
isCutoffNotMet
|
isCutoffNotMet,
|
||||||
|
showRevision
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
let kind = kinds.DEFAULT;
|
let kind = kinds.DEFAULT;
|
||||||
@@ -50,13 +82,15 @@ function BookQuality(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Label
|
<span>
|
||||||
className={className}
|
<Label
|
||||||
kind={kind}
|
className={className}
|
||||||
title={getTooltip(title, quality, size, isMonitored, isCutoffNotMet)}
|
kind={kind}
|
||||||
>
|
title={getTooltip(title, quality, size, isMonitored, isCutoffNotMet)}
|
||||||
{quality.quality.name}
|
>
|
||||||
</Label>
|
{quality.quality.name}
|
||||||
|
</Label>{revisionLabel(className, quality, showRevision)}
|
||||||
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,12 +100,14 @@ BookQuality.propTypes = {
|
|||||||
quality: PropTypes.object.isRequired,
|
quality: PropTypes.object.isRequired,
|
||||||
size: PropTypes.number,
|
size: PropTypes.number,
|
||||||
isMonitored: PropTypes.bool,
|
isMonitored: PropTypes.bool,
|
||||||
isCutoffNotMet: PropTypes.bool
|
isCutoffNotMet: PropTypes.bool,
|
||||||
|
showRevision: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
BookQuality.defaultProps = {
|
BookQuality.defaultProps = {
|
||||||
title: '',
|
title: '',
|
||||||
isMonitored: true
|
isMonitored: true,
|
||||||
|
showRevision: false
|
||||||
};
|
};
|
||||||
|
|
||||||
export default BookQuality;
|
export default BookQuality;
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ class BookSearchCell extends Component {
|
|||||||
const {
|
const {
|
||||||
bookId,
|
bookId,
|
||||||
bookTitle,
|
bookTitle,
|
||||||
|
authorName,
|
||||||
isSearching,
|
isSearching,
|
||||||
onSearchPress,
|
onSearchPress,
|
||||||
...otherProps
|
...otherProps
|
||||||
@@ -60,6 +61,7 @@ class BookSearchCell extends Component {
|
|||||||
isOpen={this.state.isDetailsModalOpen}
|
isOpen={this.state.isDetailsModalOpen}
|
||||||
bookId={bookId}
|
bookId={bookId}
|
||||||
bookTitle={bookTitle}
|
bookTitle={bookTitle}
|
||||||
|
authorName={authorName}
|
||||||
onModalClose={this.onDetailsModalClose}
|
onModalClose={this.onDetailsModalClose}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
/>
|
/>
|
||||||
@@ -73,6 +75,7 @@ BookSearchCell.propTypes = {
|
|||||||
bookId: PropTypes.number.isRequired,
|
bookId: PropTypes.number.isRequired,
|
||||||
authorId: PropTypes.number.isRequired,
|
authorId: PropTypes.number.isRequired,
|
||||||
bookTitle: PropTypes.string.isRequired,
|
bookTitle: PropTypes.string.isRequired,
|
||||||
|
authorName: PropTypes.string.isRequired,
|
||||||
isSearching: PropTypes.bool.isRequired,
|
isSearching: PropTypes.bool.isRequired,
|
||||||
onSearchPress: PropTypes.func.isRequired
|
onSearchPress: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -99,9 +99,14 @@ class BookDetails extends Component {
|
|||||||
nextBook,
|
nextBook,
|
||||||
isSearching,
|
isSearching,
|
||||||
onRefreshPress,
|
onRefreshPress,
|
||||||
onSearchPress
|
onSearchPress,
|
||||||
|
statistics = {}
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
bookFileCount = 0
|
||||||
|
} = statistics;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isOrganizeModalOpen,
|
isOrganizeModalOpen,
|
||||||
isRetagModalOpen,
|
isRetagModalOpen,
|
||||||
@@ -238,21 +243,21 @@ class BookDetails extends Component {
|
|||||||
className={styles.tab}
|
className={styles.tab}
|
||||||
selectedClassName={styles.selectedTab}
|
selectedClassName={styles.selectedTab}
|
||||||
>
|
>
|
||||||
History
|
{translate('History')}
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
||||||
<Tab
|
<Tab
|
||||||
className={styles.tab}
|
className={styles.tab}
|
||||||
selectedClassName={styles.selectedTab}
|
selectedClassName={styles.selectedTab}
|
||||||
>
|
>
|
||||||
Search
|
{translate('Search')}
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
||||||
<Tab
|
<Tab
|
||||||
className={styles.tab}
|
className={styles.tab}
|
||||||
selectedClassName={styles.selectedTab}
|
selectedClassName={styles.selectedTab}
|
||||||
>
|
>
|
||||||
Files
|
{translate('FilesTotal', [bookFileCount])}
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -335,6 +340,7 @@ BookDetails.propTypes = {
|
|||||||
ratings: PropTypes.object.isRequired,
|
ratings: PropTypes.object.isRequired,
|
||||||
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
links: PropTypes.arrayOf(PropTypes.object).isRequired,
|
links: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
statistics: PropTypes.object.isRequired,
|
||||||
monitored: PropTypes.bool.isRequired,
|
monitored: PropTypes.bool.isRequired,
|
||||||
shortDateFormat: PropTypes.string.isRequired,
|
shortDateFormat: PropTypes.string.isRequired,
|
||||||
isSaving: PropTypes.bool.isRequired,
|
isSaving: PropTypes.bool.isRequired,
|
||||||
|
|||||||
@@ -69,16 +69,21 @@ function createMapStateToProps() {
|
|||||||
|
|
||||||
const previousBook = sortedBooks[bookIndex - 1] || _.last(sortedBooks);
|
const previousBook = sortedBooks[bookIndex - 1] || _.last(sortedBooks);
|
||||||
const nextBook = sortedBooks[bookIndex + 1] || _.first(sortedBooks);
|
const nextBook = sortedBooks[bookIndex + 1] || _.first(sortedBooks);
|
||||||
|
const isRefreshingCommand = findCommand(commands, { name: commandNames.REFRESH_BOOK });
|
||||||
|
const isRefreshing = (
|
||||||
|
isCommandExecuting(isRefreshingCommand) &&
|
||||||
|
isRefreshingCommand.body.bookId === book.id
|
||||||
|
);
|
||||||
const isSearchingCommand = findCommand(commands, { name: commandNames.BOOK_SEARCH });
|
const isSearchingCommand = findCommand(commands, { name: commandNames.BOOK_SEARCH });
|
||||||
const isSearching = (
|
const isSearching = (
|
||||||
isCommandExecuting(isSearchingCommand) &&
|
isCommandExecuting(isSearchingCommand) &&
|
||||||
isSearchingCommand.body.bookIds.indexOf(book.id) > -1
|
isSearchingCommand.body.bookIds.indexOf(book.id) > -1
|
||||||
);
|
);
|
||||||
|
const isRenamingFiles = isCommandExecuting(findCommand(commands, { name: commandNames.RENAME_FILES, authorId: author.id }));
|
||||||
const isRefreshingCommand = findCommand(commands, { name: commandNames.REFRESH_BOOK });
|
const isRenamingAuthorCommand = findCommand(commands, { name: commandNames.RENAME_AUTHOR });
|
||||||
const isRefreshing = (
|
const isRenamingAuthor = (
|
||||||
isCommandExecuting(isRefreshingCommand) &&
|
isCommandExecuting(isRenamingAuthorCommand) &&
|
||||||
isRefreshingCommand.body.bookId === book.id
|
isRenamingAuthorCommand.body.authorIds.indexOf(author.id) > -1
|
||||||
);
|
);
|
||||||
|
|
||||||
const isFetching = isBookFilesFetching || editions.isFetching;
|
const isFetching = isBookFilesFetching || editions.isFetching;
|
||||||
@@ -90,6 +95,8 @@ function createMapStateToProps() {
|
|||||||
author,
|
author,
|
||||||
isRefreshing,
|
isRefreshing,
|
||||||
isSearching,
|
isSearching,
|
||||||
|
isRenamingFiles,
|
||||||
|
isRenamingAuthor,
|
||||||
isFetching,
|
isFetching,
|
||||||
isPopulated,
|
isPopulated,
|
||||||
bookFilesError,
|
bookFilesError,
|
||||||
@@ -125,9 +132,27 @@ class BookDetailsConnector extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
if (prevProps.id !== this.props.id ||
|
const {
|
||||||
|
id,
|
||||||
|
anyReleaseOk,
|
||||||
|
isRenamingFiles,
|
||||||
|
isRenamingAuthor
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
if (
|
||||||
|
(prevProps.isRenamingFiles && !isRenamingFiles) ||
|
||||||
|
(prevProps.isRenamingAuthor && !isRenamingAuthor) ||
|
||||||
!_.isEqual(getMonitoredEditions(prevProps), getMonitoredEditions(this.props)) ||
|
!_.isEqual(getMonitoredEditions(prevProps), getMonitoredEditions(this.props)) ||
|
||||||
(prevProps.anyReleaseOk === false && this.props.anyReleaseOk === true)) {
|
(prevProps.anyReleaseOk === false && anyReleaseOk === true)
|
||||||
|
) {
|
||||||
|
this.unpopulate();
|
||||||
|
this.populate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the id has changed we need to clear the book
|
||||||
|
// files and fetch from the server.
|
||||||
|
|
||||||
|
if (prevProps.id !== id) {
|
||||||
this.unpopulate();
|
this.unpopulate();
|
||||||
this.populate();
|
this.populate();
|
||||||
}
|
}
|
||||||
@@ -197,6 +222,8 @@ class BookDetailsConnector extends Component {
|
|||||||
BookDetailsConnector.propTypes = {
|
BookDetailsConnector.propTypes = {
|
||||||
id: PropTypes.number,
|
id: PropTypes.number,
|
||||||
anyReleaseOk: PropTypes.bool,
|
anyReleaseOk: PropTypes.bool,
|
||||||
|
isRenamingFiles: PropTypes.bool.isRequired,
|
||||||
|
isRenamingAuthor: PropTypes.bool.isRequired,
|
||||||
isBookFetching: PropTypes.bool,
|
isBookFetching: PropTypes.bool,
|
||||||
isBookPopulated: PropTypes.bool,
|
isBookPopulated: PropTypes.bool,
|
||||||
titleSlug: PropTypes.string.isRequired,
|
titleSlug: PropTypes.string.isRequired,
|
||||||
|
|||||||
@@ -84,9 +84,15 @@
|
|||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.authorLink {
|
||||||
|
composes: link from '~Components/Link/Link.css';
|
||||||
|
|
||||||
|
margin-right: 15px;
|
||||||
|
color: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
.duration {
|
.duration {
|
||||||
margin-right: 15px;
|
margin-right: 15px;
|
||||||
margin-left: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.detailsLabel {
|
.detailsLabel {
|
||||||
@@ -117,8 +123,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
|
font-weight: 300;
|
||||||
font-size: 30px;
|
font-size: 30px;
|
||||||
line-height: 50px;
|
line-height: 30px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// Please do not change this file!
|
// Please do not change this file!
|
||||||
interface CssExports {
|
interface CssExports {
|
||||||
'alternateTitlesIconContainer': string;
|
'alternateTitlesIconContainer': string;
|
||||||
|
'authorLink': string;
|
||||||
'backdrop': string;
|
'backdrop': string;
|
||||||
'backdropOverlay': string;
|
'backdropOverlay': string;
|
||||||
'cover': string;
|
'cover': string;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import moment from 'moment';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import TextTruncate from 'react-text-truncate';
|
import TextTruncate from 'react-text-truncate';
|
||||||
|
import AuthorNameLink from 'Author/AuthorNameLink';
|
||||||
import BookCover from 'Book/BookCover';
|
import BookCover from 'Book/BookCover';
|
||||||
import HeartRating from 'Components/HeartRating';
|
import HeartRating from 'Components/HeartRating';
|
||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
@@ -21,12 +22,7 @@ const defaultFontSize = parseInt(fonts.defaultFontSize);
|
|||||||
const lineHeight = parseFloat(fonts.lineHeight);
|
const lineHeight = parseFloat(fonts.lineHeight);
|
||||||
|
|
||||||
function getFanartUrl(images) {
|
function getFanartUrl(images) {
|
||||||
const fanartImage = images.find((x) => x.coverType === 'fanart');
|
return images.find((x) => x.coverType === 'fanart')?.url;
|
||||||
|
|
||||||
if (fanartImage) {
|
|
||||||
// Remove protocol
|
|
||||||
return fanartImage.url.replace(/^https?:/, '');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class BookDetailsHeader extends Component {
|
class BookDetailsHeader extends Component {
|
||||||
@@ -118,7 +114,7 @@ class BookDetailsHeader extends Component {
|
|||||||
className={styles.monitorToggleButton}
|
className={styles.monitorToggleButton}
|
||||||
monitored={monitored}
|
monitored={monitored}
|
||||||
isSaving={isSaving}
|
isSaving={isSaving}
|
||||||
size={isSmallScreen ? 30: 40}
|
size={isSmallScreen ? 30 : 40}
|
||||||
onPress={onMonitorTogglePress}
|
onPress={onMonitorTogglePress}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -136,7 +132,12 @@ class BookDetailsHeader extends Component {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
{author.authorName}
|
<AuthorNameLink
|
||||||
|
className={styles.authorLink}
|
||||||
|
titleSlug={author.titleSlug}
|
||||||
|
authorName={author.authorName}
|
||||||
|
/>
|
||||||
|
|
||||||
{
|
{
|
||||||
!!pageCount &&
|
!!pageCount &&
|
||||||
<span className={styles.duration}>
|
<span className={styles.duration}>
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ class BookEditorFooter extends Component {
|
|||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const monitoredOptions = [
|
const monitoredOptions = [
|
||||||
{ key: NO_CHANGE, value: translate('NoChange'), disabled: true },
|
{ key: NO_CHANGE, value: translate('NoChange'), isDisabled: true },
|
||||||
{ key: 'monitored', value: translate('Monitored') },
|
{ key: 'monitored', value: translate('Monitored') },
|
||||||
{ key: 'unmonitored', value: translate('Unmonitored') }
|
{ key: 'unmonitored', value: translate('Unmonitored') }
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ import BookIndex from './BookIndex';
|
|||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
createBookClientSideCollectionItemsSelector('bookIndex'),
|
createBookClientSideCollectionItemsSelector('bookIndex'),
|
||||||
createCommandExecutingSelector(commandNames.REFRESH_AUTHOR),
|
createCommandExecutingSelector(commandNames.BULK_REFRESH_AUTHOR),
|
||||||
createCommandExecutingSelector(commandNames.REFRESH_BOOK),
|
createCommandExecutingSelector(commandNames.BULK_REFRESH_BOOK),
|
||||||
createCommandExecutingSelector(commandNames.RSS_SYNC),
|
createCommandExecutingSelector(commandNames.RSS_SYNC),
|
||||||
createCommandExecutingSelector(commandNames.CUTOFF_UNMET_BOOK_SEARCH),
|
createCommandExecutingSelector(commandNames.CUTOFF_UNMET_BOOK_SEARCH),
|
||||||
createCommandExecutingSelector(commandNames.MISSING_BOOK_SEARCH),
|
createCommandExecutingSelector(commandNames.MISSING_BOOK_SEARCH),
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import dimensions from 'Styles/Variables/dimensions';
|
|||||||
import formatDateTime from 'Utilities/Date/formatDateTime';
|
import formatDateTime from 'Utilities/Date/formatDateTime';
|
||||||
import getRelativeDate from 'Utilities/Date/getRelativeDate';
|
import getRelativeDate from 'Utilities/Date/getRelativeDate';
|
||||||
import formatBytes from 'Utilities/Number/formatBytes';
|
import formatBytes from 'Utilities/Number/formatBytes';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
import BookIndexOverviewInfoRow from './BookIndexOverviewInfoRow';
|
import BookIndexOverviewInfoRow from './BookIndexOverviewInfoRow';
|
||||||
import styles from './BookIndexOverviewInfo.css';
|
import styles from './BookIndexOverviewInfo.css';
|
||||||
|
|
||||||
@@ -71,9 +72,9 @@ function getInfoRowProps(row, props) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name === 'qualityProfileId') {
|
if (name === 'qualityProfileId' && !!props.qualityProfile?.name) {
|
||||||
return {
|
return {
|
||||||
title: 'Quality Profile',
|
title: translate('QualityProfile'),
|
||||||
iconName: icons.PROFILE,
|
iconName: icons.PROFILE,
|
||||||
label: props.qualityProfile.name
|
label: props.qualityProfile.name
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -250,12 +250,12 @@ class BookIndexPoster extends Component {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{showQualityProfile && !!qualityProfile?.name ? (
|
||||||
showQualityProfile &&
|
<div className={styles.title} title={translate('QualityProfile')}>
|
||||||
<div className={styles.title}>
|
{qualityProfile.name}
|
||||||
{qualityProfile.name}
|
</div>
|
||||||
</div>
|
) : null}
|
||||||
}
|
|
||||||
{
|
{
|
||||||
nextAiring &&
|
nextAiring &&
|
||||||
<div className={styles.nextAiring}>
|
<div className={styles.nextAiring}>
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ function BookIndexProgressBar(props) {
|
|||||||
detailedProgressBar
|
detailedProgressBar
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const progress = bookCount ? bookFileCount / totalBookCount * 100 : 0;
|
const progress = bookFileCount && bookCount ? (totalBookCount / bookCount) * 100 : 0;
|
||||||
const text = `${bookFileCount} / ${bookCount}`;
|
const text = `${bookFileCount ? bookCount : 0} / ${totalBookCount}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
@@ -28,7 +28,11 @@ function BookIndexProgressBar(props) {
|
|||||||
size={detailedProgressBar ? sizes.MEDIUM : sizes.SMALL}
|
size={detailedProgressBar ? sizes.MEDIUM : sizes.SMALL}
|
||||||
showText={detailedProgressBar}
|
showText={detailedProgressBar}
|
||||||
text={text}
|
text={text}
|
||||||
title={translate('BookFileCountBookCountTotalTotalBookCountInterp', [bookFileCount, bookCount, totalBookCount])}
|
title={translate('BookProgressBarText', {
|
||||||
|
bookCount: bookFileCount ? bookCount : 0,
|
||||||
|
bookFileCount,
|
||||||
|
totalBookCount
|
||||||
|
})}
|
||||||
width={posterWidth}
|
width={posterWidth}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ class BookIndexRow extends Component {
|
|||||||
key={name}
|
key={name}
|
||||||
className={styles[name]}
|
className={styles[name]}
|
||||||
>
|
>
|
||||||
{qualityProfile.name}
|
{qualityProfile?.name ?? ''}
|
||||||
</VirtualTableRowCell>
|
</VirtualTableRowCell>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -229,7 +229,6 @@ class BookIndexRow extends Component {
|
|||||||
className={styles[name]}
|
className={styles[name]}
|
||||||
>
|
>
|
||||||
{bookFileCount}
|
{bookFileCount}
|
||||||
|
|
||||||
</VirtualTableRowCell>
|
</VirtualTableRowCell>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
26
frontend/src/Book/IndexerFlags.tsx
Normal file
26
frontend/src/Book/IndexerFlags.tsx
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import createIndexerFlagsSelector from 'Store/Selectors/createIndexerFlagsSelector';
|
||||||
|
|
||||||
|
interface IndexerFlagsProps {
|
||||||
|
indexerFlags: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function IndexerFlags({ indexerFlags = 0 }: IndexerFlagsProps) {
|
||||||
|
const allIndexerFlags = useSelector(createIndexerFlagsSelector);
|
||||||
|
|
||||||
|
const flags = allIndexerFlags.items.filter(
|
||||||
|
// eslint-disable-next-line no-bitwise
|
||||||
|
(item) => (indexerFlags & item.id) === item.id
|
||||||
|
);
|
||||||
|
|
||||||
|
return flags.length ? (
|
||||||
|
<ul>
|
||||||
|
{flags.map((flag, index) => {
|
||||||
|
return <li key={index}>{flag.name}</li>;
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default IndexerFlags;
|
||||||
@@ -9,19 +9,21 @@ function BookInteractiveSearchModal(props) {
|
|||||||
isOpen,
|
isOpen,
|
||||||
bookId,
|
bookId,
|
||||||
bookTitle,
|
bookTitle,
|
||||||
|
authorName,
|
||||||
onModalClose
|
onModalClose
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
size={sizes.EXTRA_LARGE}
|
size={sizes.EXTRA_EXTRA_LARGE}
|
||||||
closeOnBackgroundClick={false}
|
closeOnBackgroundClick={false}
|
||||||
onModalClose={onModalClose}
|
onModalClose={onModalClose}
|
||||||
>
|
>
|
||||||
<BookInteractiveSearchModalContent
|
<BookInteractiveSearchModalContent
|
||||||
bookId={bookId}
|
bookId={bookId}
|
||||||
bookTitle={bookTitle}
|
bookTitle={bookTitle}
|
||||||
|
authorName={authorName}
|
||||||
onModalClose={onModalClose}
|
onModalClose={onModalClose}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
@@ -32,6 +34,7 @@ BookInteractiveSearchModal.propTypes = {
|
|||||||
isOpen: PropTypes.bool.isRequired,
|
isOpen: PropTypes.bool.isRequired,
|
||||||
bookId: PropTypes.number.isRequired,
|
bookId: PropTypes.number.isRequired,
|
||||||
bookTitle: PropTypes.string.isRequired,
|
bookTitle: PropTypes.string.isRequired,
|
||||||
|
authorName: PropTypes.string.isRequired,
|
||||||
onModalClose: PropTypes.func.isRequired
|
onModalClose: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -7,18 +7,23 @@ import ModalFooter from 'Components/Modal/ModalFooter';
|
|||||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||||
import { scrollDirections } from 'Helpers/Props';
|
import { scrollDirections } from 'Helpers/Props';
|
||||||
import InteractiveSearchConnector from 'InteractiveSearch/InteractiveSearchConnector';
|
import InteractiveSearchConnector from 'InteractiveSearch/InteractiveSearchConnector';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
|
||||||
function BookInteractiveSearchModalContent(props) {
|
function BookInteractiveSearchModalContent(props) {
|
||||||
const {
|
const {
|
||||||
bookId,
|
bookId,
|
||||||
bookTitle,
|
bookTitle,
|
||||||
|
authorName,
|
||||||
onModalClose
|
onModalClose
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalContent onModalClose={onModalClose}>
|
<ModalContent onModalClose={onModalClose}>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
Interactive Search {bookId != null && `- ${bookTitle}`}
|
{bookId === null ?
|
||||||
|
translate('InteractiveSearchModalHeader') :
|
||||||
|
translate('InteractiveSearchModalHeaderBookAuthor', { bookTitle, authorName })
|
||||||
|
}
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
|
|
||||||
<ModalBody scrollDirection={scrollDirections.BOTH}>
|
<ModalBody scrollDirection={scrollDirections.BOTH}>
|
||||||
@@ -32,7 +37,7 @@ function BookInteractiveSearchModalContent(props) {
|
|||||||
|
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button onPress={onModalClose}>
|
<Button onPress={onModalClose}>
|
||||||
Close
|
{translate('Close')}
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
@@ -42,6 +47,7 @@ function BookInteractiveSearchModalContent(props) {
|
|||||||
BookInteractiveSearchModalContent.propTypes = {
|
BookInteractiveSearchModalContent.propTypes = {
|
||||||
bookId: PropTypes.number.isRequired,
|
bookId: PropTypes.number.isRequired,
|
||||||
bookTitle: PropTypes.string.isRequired,
|
bookTitle: PropTypes.string.isRequired,
|
||||||
|
authorName: PropTypes.string.isRequired,
|
||||||
onModalClose: PropTypes.func.isRequired
|
onModalClose: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
9
frontend/src/BookFile/Editor/BookFileEditorTable.css
Normal file
9
frontend/src/BookFile/Editor/BookFileEditorTable.css
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
.container {
|
||||||
|
border: 1px solid var(--borderColor);
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: var(--inputBackgroundColor);
|
||||||
|
|
||||||
|
&:last-of-type {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
7
frontend/src/BookFile/Editor/BookFileEditorTable.css.d.ts
vendored
Normal file
7
frontend/src/BookFile/Editor/BookFileEditorTable.css.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// This file is automatically generated.
|
||||||
|
// Please do not change this file!
|
||||||
|
interface CssExports {
|
||||||
|
'container': string;
|
||||||
|
}
|
||||||
|
export const cssExports: CssExports;
|
||||||
|
export default cssExports;
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import BookFileEditorTableContentConnector from './BookFileEditorTableContentConnector';
|
import BookFileEditorTableContentConnector from './BookFileEditorTableContentConnector';
|
||||||
|
import styles from './BookFileEditorTable.css';
|
||||||
|
|
||||||
function BookFileEditorTable(props) {
|
function BookFileEditorTable(props) {
|
||||||
const {
|
const {
|
||||||
@@ -7,9 +8,11 @@ function BookFileEditorTable(props) {
|
|||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BookFileEditorTableContentConnector
|
<div className={styles.container}>
|
||||||
{...otherProps}
|
<BookFileEditorTableContentConnector
|
||||||
/>
|
{...otherProps}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.filesTable {
|
.filesTable {
|
||||||
margin-bottom: 20px;
|
margin: 10px;
|
||||||
padding-top: 15px;
|
padding-top: 5px;
|
||||||
border: 1px solid var(--borderColor);
|
border: 1px solid var(--borderColor);
|
||||||
border-top: 1px solid var(--borderColor);
|
border-top: 1px solid var(--borderColor);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
@@ -13,9 +13,15 @@
|
|||||||
|
|
||||||
.actions {
|
.actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-right: auto;
|
margin: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selectInput {
|
.selectInput {
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.blankpad {
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
padding-left: 2em;
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// Please do not change this file!
|
// Please do not change this file!
|
||||||
interface CssExports {
|
interface CssExports {
|
||||||
'actions': string;
|
'actions': string;
|
||||||
|
'blankpad': string;
|
||||||
'filesTable': string;
|
'filesTable': string;
|
||||||
'selectInput': string;
|
'selectInput': string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import Alert from 'Components/Alert';
|
||||||
import SelectInput from 'Components/Form/SelectInput';
|
import SelectInput from 'Components/Form/SelectInput';
|
||||||
import SpinnerButton from 'Components/Link/SpinnerButton';
|
import SpinnerButton from 'Components/Link/SpinnerButton';
|
||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
@@ -115,12 +116,12 @@ class BookFileEditorTableContent extends Component {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, [{ key: 'selectQuality', value: 'Select Quality', disabled: true }]);
|
}, [{ key: 'selectQuality', value: translate('SelectQuality'), isDisabled: true }]);
|
||||||
|
|
||||||
const hasSelectedFiles = this.getSelectedIds().length > 0;
|
const hasSelectedFiles = this.getSelectedIds().length > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div>
|
||||||
{
|
{
|
||||||
isFetching && !isPopulated ?
|
isFetching && !isPopulated ?
|
||||||
<LoadingIndicator /> :
|
<LoadingIndicator /> :
|
||||||
@@ -129,13 +130,13 @@ class BookFileEditorTableContent extends Component {
|
|||||||
|
|
||||||
{
|
{
|
||||||
!isFetching && error ?
|
!isFetching && error ?
|
||||||
<div>{error}</div> :
|
<Alert kind={kinds.DANGER}>{error}</Alert> :
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
isPopulated && !items.length ?
|
isPopulated && !items.length ?
|
||||||
<div>
|
<div className={styles.blankpad}>
|
||||||
No book files to manage.
|
No book files to manage.
|
||||||
</div> :
|
</div> :
|
||||||
null
|
null
|
||||||
@@ -173,26 +174,30 @@ class BookFileEditorTableContent extends Component {
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
<div className={styles.actions}>
|
{
|
||||||
<SpinnerButton
|
isPopulated && items.length ? (
|
||||||
kind={kinds.DANGER}
|
<div className={styles.actions}>
|
||||||
isSpinning={isDeleting}
|
<SpinnerButton
|
||||||
isDisabled={!hasSelectedFiles}
|
kind={kinds.DANGER}
|
||||||
onPress={this.onDeletePress}
|
isSpinning={isDeleting}
|
||||||
>
|
isDisabled={!hasSelectedFiles}
|
||||||
Delete
|
onPress={this.onDeletePress}
|
||||||
</SpinnerButton>
|
>
|
||||||
|
{translate('Delete')}
|
||||||
|
</SpinnerButton>
|
||||||
|
|
||||||
<div className={styles.selectInput}>
|
<div className={styles.selectInput}>
|
||||||
<SelectInput
|
<SelectInput
|
||||||
name="quality"
|
name="quality"
|
||||||
value="selectQuality"
|
value="selectQuality"
|
||||||
values={qualityOptions}
|
values={qualityOptions}
|
||||||
isDisabled={!hasSelectedFiles}
|
isDisabled={!hasSelectedFiles}
|
||||||
onChange={this.onQualityChange}
|
onChange={this.onQualityChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
isOpen={isConfirmDeleteModalOpen}
|
isOpen={isConfirmDeleteModalOpen}
|
||||||
@@ -203,7 +208,7 @@ class BookFileEditorTableContent extends Component {
|
|||||||
onConfirm={this.onConfirmDelete}
|
onConfirm={this.onConfirmDelete}
|
||||||
onCancel={this.onConfirmDeleteModalClose}
|
onCancel={this.onConfirmDeleteModalClose}
|
||||||
/>
|
/>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,14 +27,15 @@ class BookshelfBook extends Component {
|
|||||||
title,
|
title,
|
||||||
disambiguation,
|
disambiguation,
|
||||||
monitored,
|
monitored,
|
||||||
statistics,
|
statistics = {},
|
||||||
isSaving
|
isSaving
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
bookFileCount,
|
bookCount = 0,
|
||||||
totalBookCount,
|
bookFileCount = 0,
|
||||||
percentOfBooks
|
totalBookCount = 0,
|
||||||
|
percentOfBooks = 0
|
||||||
} = statistics;
|
} = statistics;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -59,10 +60,14 @@ class BookshelfBook extends Component {
|
|||||||
percentOfBooks < 100 && monitored && styles.missingWanted,
|
percentOfBooks < 100 && monitored && styles.missingWanted,
|
||||||
percentOfBooks === 100 && styles.allBooks
|
percentOfBooks === 100 && styles.allBooks
|
||||||
)}
|
)}
|
||||||
title={translate('BookFileCounttotalBookCountBooksDownloadedInterp', [bookFileCount, totalBookCount])}
|
title={translate('BookProgressBarText', {
|
||||||
|
bookCount: bookFileCount ? bookCount : 0,
|
||||||
|
bookFileCount,
|
||||||
|
totalBookCount
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
totalBookCount === 0 ? '0/0' : `${bookFileCount}/${totalBookCount}`
|
totalBookCount === 0 ? '0/0' : `${bookFileCount ? bookCount : 0}/${totalBookCount}`
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ class BookshelfFooter extends Component {
|
|||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const monitoredOptions = [
|
const monitoredOptions = [
|
||||||
{ key: NO_CHANGE, value: translate('NoChange'), disabled: true },
|
{ key: NO_CHANGE, value: translate('NoChange'), isDisabled: true },
|
||||||
{ key: 'monitored', value: translate('Monitored') },
|
{ key: 'monitored', value: translate('Monitored') },
|
||||||
{ key: 'unmonitored', value: translate('Unmonitored') }
|
{ key: 'unmonitored', value: translate('Unmonitored') }
|
||||||
];
|
];
|
||||||
@@ -143,7 +143,7 @@ class BookshelfFooter extends Component {
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div className={styles.label}>
|
<div className={styles.label}>
|
||||||
{selectedCount} Author(s) Selected
|
{translate('CountAuthorsSelected', { selectedCount })}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SpinnerButton
|
<SpinnerButton
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user