Skip to content

Commit 7b141f6

Browse files
authored
January radarr upstream final (#15)
1 parent 304b2b1 commit 7b141f6

File tree

19 files changed

+361
-41
lines changed

19 files changed

+361
-41
lines changed

.editorconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ dotnet_diagnostic.IDE0018.severity = error
5555

5656
# Stylecop Rules
5757
dotnet_diagnostic.SA0001.severity = none
58+
dotnet_diagnostic.SA1000.severity = none
5859
dotnet_diagnostic.SA1025.severity = none
5960
dotnet_diagnostic.SA1101.severity = none
6061
dotnet_diagnostic.SA1116.severity = none

CONTRIBUTING.md

Lines changed: 178 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,186 @@
1+
12
# How to Contribute
23

34
We're always looking for people to help make Whisparr even better, there are a number of ways to contribute.
45

5-
This file has been moved to the wiki for the latest details please see the [contributing wiki page](https://wiki.servarr.com/whisparr/contributing).
6+
# Documentation
7+
8+
Setup guides, [FAQ](Whisparr/Whisparr-Eros/wiki/FAQ), the more information we have on the [wiki](https://wiki.servarr.com/whisparr) the better.
9+
10+
# Development
11+
12+
Whisparr is written in C# (backend) and JS (frontend). The backend is built on the .NET10 framework, while the frontend utilizes Reactjs.
13+
14+
## Tools required
15+
16+
- Visual Studio 2022 or higher is recommended (<https://www.visualstudio.com/vs/>). The community version is free and works (<https://www.visualstudio.com/downloads/>).
17+
18+
> VS 2022 V17.0 or higher is recommended as it includes the .NET10 SDK
19+
{.is-info}
20+
21+
- HTML/Javascript editor of choice (VS Code/Sublime Text/Webstorm/Atom/etc)
22+
- [Git](https://git-scm.com/downloads)
23+
- The [Node.js](https://nodejs.org/) runtime is required. The following versions are supported:
24+
- **20** (any minor or patch version within this)
25+
{.grid-list}
26+
27+
> The Application will **NOT** run on older versions such as `18.x`, `16.x` or any version below 20.0! Due to a dependency issue, it will also not run on `21.x` and is untested on other verisons.
28+
{.is-warning}
29+
30+
- [Yarn](https://yarnpkg.com/getting-started/install) is required to build the frontend
31+
- Yarn is included with **Node 20**+ by default. Enable it with `corepack enable`
32+
- For other Node versions, install it with `npm i -g corepack`
33+
34+
## Getting started
35+
36+
1. Fork Whisparr
37+
1. Clone the repository into your development machine. [*info*](https://docs.github.com/en/get-started/quickstart/fork-a-repo)
38+
39+
> Be sure to run lint `yarn lint --fix` on your code for any front end changes before committing.
40+
For css changes `yarn stylelint-windows --fix` {.is-info}
41+
42+
### Building the frontend
43+
44+
- Navigate to the cloned directory
45+
- Install the required Node Packages
46+
47+
```bash
48+
yarn install
49+
```
50+
51+
- Start webpack to monitor your development environment for any changes that need post processing using:
52+
53+
```bash
54+
yarn start
55+
```
56+
57+
### Building the Backend
58+
59+
The backend solution is most easily built and ran in Visual Studio [Code] or Rider, however if the only priority is working on the frontend UI it can be built easily from command line as well when the correct SDK is installed.
60+
61+
#### Visual Studio
62+
63+
> Ensure startup project is set to `Whisparr.Console` and framework to `net6.0`
64+
{.is-info}
65+
66+
1. First `Build` the solution in Visual Studio, this will ensure all projects are correctly built and dependencies restored
67+
1. Next `Debug/Run` the project in Visual Studio to start Whisparr
68+
1. Open <http://localhost:7878>
69+
70+
#### Command line
71+
72+
1. Clean solution
73+
74+
```shell
75+
dotnet clean src/Whisparr.sln -c Debug
76+
```
77+
78+
1. Restore and Build debug configuration for the correct platform (Posix or Windows)
79+
80+
```shell
81+
dotnet msbuild -restore src/Whisparr.sln -p:Configuration=Debug -p:Platform=Posix -t:PublishAllRids
82+
```
83+
84+
1. Run the produced executable from `/_output`
85+
86+
## Contributing Code
87+
88+
- If you're adding a new, already requested feature, please comment on [GitHub Issues](https://github.com/Whisparr/Whisparr/issues) so work is not duplicated (If you want to add something not already on there, please talk to us first)
89+
- Rebase from Whisparr's `eros-develop` branch, do not merge
90+
- Make meaningful commits, or squash them
91+
- Feel free to make a pull request before work is complete, this will let us see where its at and make comments/suggest improvements
92+
- Reach out to us on the discord if you have any questions
93+
- Add tests (unit/integration)
94+
- Commit with \*nix line endings for consistency (We checkout Windows and commit \*nix)
95+
- One feature/bug fix per pull request to keep things clean and easy to understand
96+
- Use 4 spaces instead of tabs, this is the default for VS 2022 and WebStorm
97+
98+
## Pull Requesting
99+
100+
- Only make pull requests to `eros-develop`, never `eros`, if you make a PR to `eros` we will comment on it and close it
101+
- You're probably going to get some comments or questions from us, they will be to ensure consistency and maintainability
102+
- We'll try to respond to pull requests as soon as possible, if its been a day or two, please reach out to us, we may have missed it
103+
- Each PR should come from its own [feature branch](http://martinfowler.com/bliki/FeatureBranch.html) not develop in your fork, it should have a meaningful branch name (what is being added/fixed)
104+
- `new-feature` (Good)
105+
- `fix-bug` (Good)
106+
- `patch` (Bad)
107+
- `develop` (Bad)
108+
- Commits should be written as `New:` or `Fix:` for changes that would not be considered a `maintenance release`. You may use `Chore:` for anything that would be considered maintenance.
109+
110+
## Unit Testing
111+
112+
Whisparr utilizes `nunit` for its unit, integration, and automation test suite.
113+
114+
### Running Tests
115+
116+
Tests can be run easily from within VS using the included nunit3testadapter nuget package or from the command line using the included bash script `test.sh`.
117+
118+
From VS simply navigate to Test Explorer and run or debug the tests you'd like to examine.
119+
120+
Tests can be run all at once or one at a time in VS.
121+
122+
From command line the `test.sh` script accepts 3 parameters
123+
124+
```bash
125+
test.sh <PLATFORM> <TYPE> <COVERAGE>
126+
```
127+
128+
### Writing Tests
129+
130+
While not always fun, we encourage writing unit tests for any backend code changes. This will ensure the change is functioning as you intended and that future changes dont break the expected behavior.
131+
132+
> We currently require 80% coverage on new code when submitting a PR
133+
{.is-info}
134+
135+
If you have any questions about any of this, please let us know.
136+
137+
# Translation
138+
139+
Whisparr uses a self hosted open access [Weblate](https://translate.servarr.com) instance to manage its json translation files. These files are stored in the repo at `src/NzbDrone.Core/Localization`
140+
141+
## Contributing to an Existing Translation
142+
143+
Weblate handles synchronization and translation of strings for all languages other than English. Editing of translated strings and translating existing strings for supported languages should be performed there for the Whisparr project.
144+
145+
The English translation, `en.json`, serves as the source for all other translations and is managed on GitHub repo.
146+
147+
## Adding a Language
148+
149+
Adding translations to Whisparr requires two steps
150+
151+
- Adding the Language to weblate
152+
- Adding the Language to Whisparr codebase
153+
154+
## Adding Translation Strings in Code
155+
156+
The English translation, `src/NzbDrone.Core/Localization/en.json`, serves as the source for all other translations and is managed on GitHub repo. When adding a new string to either the UI or backend a key must also be added to `en.json` along with the default value in English. This key may then be consumed as follows:
157+
158+
> PRs for translation of log messages will not be accepted
159+
{.is-warning}
160+
161+
### Backend Strings
162+
163+
Backend strings may be added utilizing the Localization Service `GetLocalizedString` method
164+
165+
```dotnet
166+
private readonly ILocalizationService _localizationService;
167+
168+
public IndexerCheck(ILocalizationService localizationService)
169+
{
170+
_localizationService = localizationService;
171+
}
172+
173+
var translated = _localizationService.GetLocalizedString("IndexerHealthCheckNoIndexers")
174+
```
6175
7-
## Documentation
176+
### Frontend Strings
8177
9-
Setup guides, [FAQ](https://wiki.servarr.com/whisparr/faq), the more information we have on the [wiki](https://wiki.servarr.com/whisparr) the better.
178+
New strings can be added to the frontend by importing the translate function and using a key specified from `en.json`
10179
11-
## Development
180+
```js
181+
import translate from 'Utilities/String/translate';
12182
13-
See the [Wiki Page](https://wiki.servarr.com/whisparr/contributing)
183+
<div>
184+
{translate('UnableToAddANewIndexerPleaseTryAgain')}
185+
</div>
186+
```

frontend/src/Components/Modal/Modal.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
.modal {
2020
position: relative;
2121
display: flex;
22+
max-width: 90%;
2223
max-height: 90%;
2324
border-radius: 6px;
2425
opacity: 1;
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { useEffect, useState } from 'react';
2+
import { useSelector } from 'react-redux';
3+
import { createSelector } from 'reselect';
4+
import AppState from 'App/State/AppState';
5+
import themes from 'Styles/Themes';
6+
7+
function createThemeSelector() {
8+
return createSelector(
9+
(state: AppState) => state.settings.ui.item.theme || window.Whisparr.theme,
10+
(theme) => theme
11+
);
12+
}
13+
14+
const useTheme = () => {
15+
const selectedTheme = useSelector(createThemeSelector());
16+
const [resolvedTheme, setResolvedTheme] = useState(selectedTheme);
17+
18+
useEffect(() => {
19+
if (selectedTheme !== 'auto') {
20+
setResolvedTheme(selectedTheme);
21+
return;
22+
}
23+
24+
const applySystemTheme = () => {
25+
setResolvedTheme(
26+
window.matchMedia('(prefers-color-scheme: dark)').matches
27+
? 'dark'
28+
: 'light'
29+
);
30+
};
31+
32+
applySystemTheme();
33+
34+
window
35+
.matchMedia('(prefers-color-scheme: dark)')
36+
.addEventListener('change', applySystemTheme);
37+
38+
return () => {
39+
window
40+
.matchMedia('(prefers-color-scheme: dark)')
41+
.removeEventListener('change', applySystemTheme);
42+
};
43+
}, [selectedTheme]);
44+
45+
return resolvedTheme;
46+
};
47+
48+
export default useTheme;
49+
50+
export const useThemeColor = (color: string) => {
51+
const theme = useTheme();
52+
const themeVariables = themes[theme];
53+
54+
// @ts-expect-error - themeVariables is a string indexable type
55+
return themeVariables[color];
56+
};

frontend/src/System/Updates/UpdateChanges.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ function UpdateChanges(props: UpdateChangesProps) {
2020
{uniqueChanges.map((change, index) => {
2121
// Linkify issue numbers
2222
let transformed = change.replace(
23-
/#\d{3,5}\b/g,
23+
/#\d{2,5}\b/g,
2424
(match) =>
25-
`[${match}](https://github.com/Whisparr/Whisparr/issues/${match.substring(
25+
`[${match}](https://github.com/Whisparr/Whisparr-Eros/issues/${match.substring(
2626
1
2727
)})`
2828
);
@@ -32,14 +32,15 @@ function UpdateChanges(props: UpdateChangesProps) {
3232
(_match, p1, username) =>
3333
`${p1}[@${username}](https://github.com/${username})`
3434
);
35-
// Transform GitHub PR URLs to PR#123 links
35+
// Linkify commit short hashes in parentheses (e.g., (abc1234))
3636
transformed = transformed.replace(
37-
/https:\/\/github\.com\/([\w-]+)\/([\w-]+)\/pull\/(\d+)/g,
38-
(url, _owner, _repo, pr) => `[#${pr}](${url})`
37+
/\(([a-f0-9]{7,40})\)/gi,
38+
(_match, hash) =>
39+
`([${hash}](https://github.com/Whisparr/Whisparr-Eros/commit/${hash}))`
3940
);
4041
// Linkify plain URLs not already inside markdown links
4142
transformed = transformed.replace(
42-
/(?<!\]\()https?:\/\/[^\s)]+/g,
43+
/(?<!\]\()https?:\/\/[\w\-._~:/?#[\]@!$&'()*+,;=%]+/g,
4344
(url) => `[${url}](${url})`
4445
);
4546
return (

frontend/src/Utilities/Object/selectUniqueIds.js

Lines changed: 0 additions & 15 deletions
This file was deleted.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import KeysMatching from 'typings/Helpers/KeysMatching';
2+
3+
function selectUniqueIds<T, K>(items: T[], idProp: KeysMatching<T, K>) {
4+
return items.reduce((acc: K[], item) => {
5+
if (item[idProp] && acc.indexOf(item[idProp] as K) === -1) {
6+
acc.push(item[idProp] as K);
7+
}
8+
9+
return acc;
10+
}, []);
11+
}
12+
13+
export default selectUniqueIds;

frontend/src/Wanted/CutoffUnmet/CutoffUnmet.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,19 @@ import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptions
1818
import TablePager from 'Components/Table/TablePager';
1919
import usePaging from 'Components/Table/usePaging';
2020
import useCurrentPage from 'Helpers/Hooks/useCurrentPage';
21+
import usePrevious from 'Helpers/Hooks/usePrevious';
2122
import useSelectState from 'Helpers/Hooks/useSelectState';
2223
import { align, icons, kinds } from 'Helpers/Props';
24+
import Movie from 'Movie/Movie';
2325
import { executeCommand } from 'Store/Actions/commandActions';
26+
import {
27+
clearMovieFiles,
28+
fetchMovieFiles,
29+
} from 'Store/Actions/movieFileActions';
30+
import {
31+
clearQueueDetails,
32+
fetchQueueDetails,
33+
} from 'Store/Actions/queueActions';
2434
import {
2535
batchToggleCutoffUnmetMovies,
2636
clearCutoffUnmet,
@@ -35,6 +45,8 @@ import { CheckInputChanged } from 'typings/inputs';
3545
import { SelectStateInputProps } from 'typings/props';
3646
import { TableOptionsChangePayload } from 'typings/Table';
3747
import getFilterValue from 'Utilities/Filter/getFilterValue';
48+
import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
49+
import selectUniqueIds from 'Utilities/Object/selectUniqueIds';
3850
import {
3951
registerPagePopulator,
4052
unregisterPagePopulator,
@@ -108,6 +120,8 @@ function CutoffUnmet() {
108120
const isSearchingForMovies =
109121
isSearchingForAllMovies || isSearchingForSelectedMovies;
110122

123+
const previousItems = usePrevious(items);
124+
111125
const handleSelectAllChange = useCallback(
112126
({ value }: CheckInputChanged) => {
113127
setSelectState({ type: value ? 'selectAll' : 'unselectAll', items });
@@ -204,6 +218,8 @@ function CutoffUnmet() {
204218

205219
return () => {
206220
dispatch(clearCutoffUnmet());
221+
dispatch(clearQueueDetails());
222+
dispatch(clearMovieFiles());
207223
};
208224
}, [requestCurrentPage, dispatch]);
209225

@@ -223,6 +239,21 @@ function CutoffUnmet() {
223239
};
224240
}, [dispatch]);
225241

242+
useEffect(() => {
243+
if (!previousItems || hasDifferentItems(items, previousItems)) {
244+
const movieIds = selectUniqueIds<Movie, number>(items, 'id');
245+
const movieFileIds = selectUniqueIds<Movie, number>(items, 'movieFileId');
246+
247+
if (movieIds.length) {
248+
dispatch(fetchQueueDetails({ movieIds }));
249+
}
250+
251+
if (movieFileIds.length) {
252+
dispatch(fetchMovieFiles({ movieFileIds }));
253+
}
254+
}
255+
}, [items, previousItems, dispatch]);
256+
226257
return (
227258
<PageContent title={translate('CutoffUnmet')}>
228259
<PageToolbar>

0 commit comments

Comments
 (0)