mirror of
https://github.com/morpheus65535/bazarr
synced 2024-12-21 23:32:31 +00:00
Merge remote-tracking branch 'origin/development' into development
This commit is contained in:
commit
8e8311186d
20 changed files with 593 additions and 1510 deletions
|
@ -96,6 +96,7 @@ validators = [
|
|||
Validator('general.path_mappings_movie', must_exist=True, default=[], is_type_of=list),
|
||||
Validator('general.serie_tag_enabled', must_exist=True, default=False, is_type_of=bool),
|
||||
Validator('general.movie_tag_enabled', must_exist=True, default=False, is_type_of=bool),
|
||||
Validator('general.remove_profile_tags', must_exist=True, default=[], is_type_of=list, condition=validate_tags),
|
||||
Validator('general.serie_default_enabled', must_exist=True, default=False, is_type_of=bool),
|
||||
Validator('general.serie_default_profile', must_exist=True, default='', is_type_of=(int, str)),
|
||||
Validator('general.movie_default_enabled', must_exist=True, default=False, is_type_of=bool),
|
||||
|
@ -468,6 +469,7 @@ array_keys = ['excluded_tags',
|
|||
'enabled_integrations',
|
||||
'path_mappings',
|
||||
'path_mappings_movie',
|
||||
'remove_profile_tags',
|
||||
'language_equals',
|
||||
'blacklisted_languages',
|
||||
'blacklisted_providers']
|
||||
|
|
|
@ -152,6 +152,10 @@ def movieParser(movie, action, tags_dict, language_profiles, movie_default_profi
|
|||
tag_profile = get_matching_profile(tags, language_profiles)
|
||||
if tag_profile:
|
||||
parsed_movie['profileId'] = tag_profile
|
||||
remove_profile_tags_list = settings.general.remove_profile_tags
|
||||
if len(remove_profile_tags_list) > 0:
|
||||
if set(tags) & set(remove_profile_tags_list):
|
||||
parsed_movie['profileId'] = None
|
||||
|
||||
return parsed_movie
|
||||
|
||||
|
|
|
@ -78,7 +78,11 @@ def seriesParser(show, action, tags_dict, language_profiles, serie_default_profi
|
|||
tag_profile = get_matching_profile(tags, language_profiles)
|
||||
if tag_profile:
|
||||
parsed_series['profileId'] = tag_profile
|
||||
|
||||
remove_profile_tags_list = settings.general.remove_profile_tags
|
||||
if len(remove_profile_tags_list) > 0:
|
||||
if set(tags) & set(remove_profile_tags_list):
|
||||
parsed_series['profileId'] = None
|
||||
|
||||
return parsed_series
|
||||
|
||||
|
||||
|
|
|
@ -15,12 +15,11 @@
|
|||
"@typescript-eslint/no-unused-vars": "warn"
|
||||
},
|
||||
"extends": [
|
||||
"react-app",
|
||||
"plugin:react-hooks/recommended",
|
||||
"eslint:recommended",
|
||||
"plugin:react-hooks/recommended",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
],
|
||||
"plugins": ["testing-library", "simple-import-sort"],
|
||||
"plugins": ["testing-library", "simple-import-sort", "react-refresh"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": [
|
||||
|
@ -63,6 +62,7 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"sourceType": "module",
|
||||
"ecmaVersion": "latest"
|
||||
|
|
1814
frontend/package-lock.json
generated
1814
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -44,14 +44,16 @@
|
|||
"@types/node": "^20.12.6",
|
||||
"@types/react": "^18.3.4",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.16.0",
|
||||
"@typescript-eslint/parser": "^7.16.0",
|
||||
"@vite-pwa/assets-generator": "^0.2.4",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"@vitest/coverage-v8": "^1.4.0",
|
||||
"@vitest/ui": "^1.2.2",
|
||||
"clsx": "^2.1.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-react-app": "^7.0.1",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.7",
|
||||
"eslint-plugin-simple-import-sort": "^12.1.0",
|
||||
"eslint-plugin-testing-library": "^6.2.0",
|
||||
"husky": "^9.0.11",
|
||||
|
|
|
@ -25,23 +25,6 @@ const cacheEpisodes = (client: QueryClient, episodes: Item.Episode[]) => {
|
|||
});
|
||||
};
|
||||
|
||||
export function useEpisodesByIds(ids: number[]) {
|
||||
const client = useQueryClient();
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: [QueryKeys.Series, QueryKeys.Episodes, ids],
|
||||
queryFn: () => api.episodes.byEpisodeId(ids),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (query.isSuccess && query.data) {
|
||||
cacheEpisodes(client, query.data);
|
||||
}
|
||||
}, [query.isSuccess, query.data, client]);
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
export function useEpisodesBySeriesId(id: number) {
|
||||
const client = useQueryClient();
|
||||
|
||||
|
@ -87,10 +70,11 @@ export function useEpisodeAddBlacklist() {
|
|||
},
|
||||
|
||||
onSuccess: (_, { seriesId }) => {
|
||||
client.invalidateQueries({
|
||||
void client.invalidateQueries({
|
||||
queryKey: [QueryKeys.Series, QueryKeys.Episodes, QueryKeys.Blacklist],
|
||||
});
|
||||
client.invalidateQueries({
|
||||
|
||||
void client.invalidateQueries({
|
||||
queryKey: [QueryKeys.Series, seriesId],
|
||||
});
|
||||
},
|
||||
|
@ -105,8 +89,8 @@ export function useEpisodeDeleteBlacklist() {
|
|||
mutationFn: (param: { all?: boolean; form?: FormType.DeleteBlacklist }) =>
|
||||
api.episodes.deleteBlacklist(param.all, param.form),
|
||||
|
||||
onSuccess: (_) => {
|
||||
client.invalidateQueries({
|
||||
onSuccess: () => {
|
||||
void client.invalidateQueries({
|
||||
queryKey: [QueryKeys.Series, QueryKeys.Episodes, QueryKeys.Blacklist],
|
||||
});
|
||||
},
|
||||
|
|
|
@ -15,23 +15,6 @@ const cacheMovies = (client: QueryClient, movies: Item.Movie[]) => {
|
|||
});
|
||||
};
|
||||
|
||||
export function useMoviesByIds(ids: number[]) {
|
||||
const client = useQueryClient();
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: [QueryKeys.Movies, ...ids],
|
||||
queryFn: () => api.movies.movies(ids),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (query.isSuccess && query.data) {
|
||||
cacheMovies(client, query.data);
|
||||
}
|
||||
}, [query.isSuccess, query.data, client]);
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
export function useMovieById(id: number) {
|
||||
return useQuery({
|
||||
queryKey: [QueryKeys.Movies, id],
|
||||
|
@ -74,12 +57,13 @@ export function useMovieModification() {
|
|||
|
||||
onSuccess: (_, form) => {
|
||||
form.id.forEach((v) => {
|
||||
client.invalidateQueries({
|
||||
void client.invalidateQueries({
|
||||
queryKey: [QueryKeys.Movies, v],
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: query less
|
||||
client.invalidateQueries({
|
||||
void client.invalidateQueries({
|
||||
queryKey: [QueryKeys.Movies],
|
||||
});
|
||||
},
|
||||
|
@ -93,7 +77,7 @@ export function useMovieAction() {
|
|||
mutationFn: (form: FormType.MoviesAction) => api.movies.action(form),
|
||||
|
||||
onSuccess: () => {
|
||||
client.invalidateQueries({
|
||||
void client.invalidateQueries({
|
||||
queryKey: [QueryKeys.Movies],
|
||||
});
|
||||
},
|
||||
|
@ -125,10 +109,11 @@ export function useMovieAddBlacklist() {
|
|||
},
|
||||
|
||||
onSuccess: (_, { id }) => {
|
||||
client.invalidateQueries({
|
||||
void client.invalidateQueries({
|
||||
queryKey: [QueryKeys.Movies, QueryKeys.Blacklist],
|
||||
});
|
||||
client.invalidateQueries({
|
||||
|
||||
void client.invalidateQueries({
|
||||
queryKey: [QueryKeys.Movies, id],
|
||||
});
|
||||
},
|
||||
|
@ -143,8 +128,8 @@ export function useMovieDeleteBlacklist() {
|
|||
mutationFn: (param: { all?: boolean; form?: FormType.DeleteBlacklist }) =>
|
||||
api.movies.deleteBlacklist(param.all, param.form),
|
||||
|
||||
onSuccess: (_, param) => {
|
||||
client.invalidateQueries({
|
||||
onSuccess: () => {
|
||||
void client.invalidateQueries({
|
||||
queryKey: [QueryKeys.Movies, QueryKeys.Blacklist],
|
||||
});
|
||||
},
|
||||
|
|
|
@ -54,22 +54,27 @@ export function useSettingsMutation() {
|
|||
mutationFn: (data: LooseObject) => api.system.updateSettings(data),
|
||||
|
||||
onSuccess: () => {
|
||||
client.invalidateQueries({
|
||||
void client.invalidateQueries({
|
||||
queryKey: [QueryKeys.System],
|
||||
});
|
||||
client.invalidateQueries({
|
||||
|
||||
void client.invalidateQueries({
|
||||
queryKey: [QueryKeys.Series],
|
||||
});
|
||||
client.invalidateQueries({
|
||||
|
||||
void client.invalidateQueries({
|
||||
queryKey: [QueryKeys.Episodes],
|
||||
});
|
||||
client.invalidateQueries({
|
||||
|
||||
void client.invalidateQueries({
|
||||
queryKey: [QueryKeys.Movies],
|
||||
});
|
||||
client.invalidateQueries({
|
||||
|
||||
void client.invalidateQueries({
|
||||
queryKey: [QueryKeys.Wanted],
|
||||
});
|
||||
client.invalidateQueries({
|
||||
|
||||
void client.invalidateQueries({
|
||||
queryKey: [QueryKeys.Badges],
|
||||
});
|
||||
},
|
||||
|
@ -101,7 +106,7 @@ export function useDeleteLogs() {
|
|||
mutationFn: () => api.system.deleteLogs(),
|
||||
|
||||
onSuccess: () => {
|
||||
client.invalidateQueries({
|
||||
void client.invalidateQueries({
|
||||
queryKey: [QueryKeys.System, QueryKeys.Logs],
|
||||
});
|
||||
},
|
||||
|
@ -128,11 +133,12 @@ export function useSystemAnnouncementsAddDismiss() {
|
|||
return api.system.addAnnouncementsDismiss(hash);
|
||||
},
|
||||
|
||||
onSuccess: (_, { hash }) => {
|
||||
client.invalidateQueries({
|
||||
onSuccess: () => {
|
||||
void client.invalidateQueries({
|
||||
queryKey: [QueryKeys.System, QueryKeys.Announcements],
|
||||
});
|
||||
client.invalidateQueries({
|
||||
|
||||
void client.invalidateQueries({
|
||||
queryKey: [QueryKeys.System, QueryKeys.Badges],
|
||||
});
|
||||
},
|
||||
|
@ -156,10 +162,11 @@ export function useRunTask() {
|
|||
mutationFn: (id: string) => api.system.runTask(id),
|
||||
|
||||
onSuccess: () => {
|
||||
client.invalidateQueries({
|
||||
void client.invalidateQueries({
|
||||
queryKey: [QueryKeys.System, QueryKeys.Tasks],
|
||||
});
|
||||
client.invalidateQueries({
|
||||
|
||||
void client.invalidateQueries({
|
||||
queryKey: [QueryKeys.System, QueryKeys.Backups],
|
||||
});
|
||||
},
|
||||
|
@ -180,7 +187,7 @@ export function useCreateBackups() {
|
|||
mutationFn: () => api.system.createBackups(),
|
||||
|
||||
onSuccess: () => {
|
||||
client.invalidateQueries({
|
||||
void client.invalidateQueries({
|
||||
queryKey: [QueryKeys.System, QueryKeys.Backups],
|
||||
});
|
||||
},
|
||||
|
@ -194,7 +201,7 @@ export function useRestoreBackups() {
|
|||
mutationFn: (filename: string) => api.system.restoreBackups(filename),
|
||||
|
||||
onSuccess: () => {
|
||||
client.invalidateQueries({
|
||||
void client.invalidateQueries({
|
||||
queryKey: [QueryKeys.System, QueryKeys.Backups],
|
||||
});
|
||||
},
|
||||
|
@ -208,7 +215,7 @@ export function useDeleteBackups() {
|
|||
mutationFn: (filename: string) => api.system.deleteBackups(filename),
|
||||
|
||||
onSuccess: () => {
|
||||
client.invalidateQueries({
|
||||
void client.invalidateQueries({
|
||||
queryKey: [QueryKeys.System, QueryKeys.Backups],
|
||||
});
|
||||
},
|
||||
|
|
|
@ -16,7 +16,6 @@ type MutateActionProps<DATA, VAR> = Omit<
|
|||
|
||||
function MutateAction<DATA, VAR>({
|
||||
mutation,
|
||||
noReset,
|
||||
onSuccess,
|
||||
onError,
|
||||
args,
|
||||
|
|
|
@ -15,7 +15,6 @@ type MutateButtonProps<DATA, VAR> = Omit<
|
|||
|
||||
function MutateButton<DATA, VAR>({
|
||||
mutation,
|
||||
noReset,
|
||||
onSuccess,
|
||||
onError,
|
||||
args,
|
||||
|
|
|
@ -12,7 +12,7 @@ interface QueryOverlayProps {
|
|||
const QueryOverlay: FunctionComponent<QueryOverlayProps> = ({
|
||||
children,
|
||||
global = false,
|
||||
result: { isLoading, isError, error },
|
||||
result: { isLoading },
|
||||
}) => {
|
||||
return (
|
||||
<LoadingProvider value={isLoading}>
|
||||
|
|
|
@ -7,7 +7,7 @@ import {
|
|||
Select,
|
||||
SelectProps,
|
||||
} from "@mantine/core";
|
||||
import { isNull, isUndefined, noop } from "lodash";
|
||||
import { isNull, isUndefined } from "lodash";
|
||||
import { LOG } from "@/utilities/console";
|
||||
|
||||
export type SelectorOption<T> = Override<
|
||||
|
@ -49,10 +49,7 @@ export type GroupedSelectorProps<T> = Override<
|
|||
>;
|
||||
|
||||
export function GroupedSelector<T>({
|
||||
value,
|
||||
options,
|
||||
getkey = DefaultKeyBuilder,
|
||||
onOptionSubmit = noop,
|
||||
...select
|
||||
}: GroupedSelectorProps<T>) {
|
||||
return (
|
||||
|
|
|
@ -5,11 +5,8 @@ import { ModalSettings } from "@mantine/modals/lib/context";
|
|||
import { ModalComponent, ModalIdContext } from "./WithModal";
|
||||
|
||||
export function useModals() {
|
||||
const {
|
||||
openContextModal: openMantineContextModal,
|
||||
closeContextModal: closeContextModalRaw,
|
||||
...rest
|
||||
} = useMantineModals();
|
||||
const { openContextModal: openMantineContextModal, ...rest } =
|
||||
useMantineModals();
|
||||
|
||||
const openContextModal = useCallback(
|
||||
<ARGS extends {}>(
|
||||
|
@ -26,7 +23,7 @@ export function useModals() {
|
|||
[openMantineContextModal],
|
||||
);
|
||||
|
||||
const closeContextModal = useCallback(
|
||||
const closeContext = useCallback(
|
||||
(modal: ModalComponent) => {
|
||||
rest.closeModal(modal.modalKey);
|
||||
},
|
||||
|
@ -43,7 +40,7 @@ export function useModals() {
|
|||
|
||||
// TODO: Performance
|
||||
return useMemo(
|
||||
() => ({ openContextModal, closeContextModal, closeSelf, ...rest }),
|
||||
[closeContextModal, closeSelf, openContextModal, rest],
|
||||
() => ({ openContextModal, closeContext, closeSelf, ...rest }),
|
||||
[closeContext, closeSelf, openContextModal, rest],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -40,13 +40,17 @@ export function createDefaultReducer(): SocketIO.Reducer[] {
|
|||
update: (ids) => {
|
||||
LOG("info", "Invalidating series", ids);
|
||||
ids.forEach((id) => {
|
||||
queryClient.invalidateQueries({ queryKey: [QueryKeys.Series, id] });
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: [QueryKeys.Series, id],
|
||||
});
|
||||
});
|
||||
},
|
||||
delete: (ids) => {
|
||||
LOG("info", "Invalidating series", ids);
|
||||
ids.forEach((id) => {
|
||||
queryClient.invalidateQueries({ queryKey: [QueryKeys.Series, id] });
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: [QueryKeys.Series, id],
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
|
@ -55,13 +59,17 @@ export function createDefaultReducer(): SocketIO.Reducer[] {
|
|||
update: (ids) => {
|
||||
LOG("info", "Invalidating movies", ids);
|
||||
ids.forEach((id) => {
|
||||
queryClient.invalidateQueries({ queryKey: [QueryKeys.Movies, id] });
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: [QueryKeys.Movies, id],
|
||||
});
|
||||
});
|
||||
},
|
||||
delete: (ids) => {
|
||||
LOG("info", "Invalidating movies", ids);
|
||||
ids.forEach((id) => {
|
||||
queryClient.invalidateQueries({ queryKey: [QueryKeys.Movies, id] });
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: [QueryKeys.Movies, id],
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
|
@ -78,7 +86,7 @@ export function createDefaultReducer(): SocketIO.Reducer[] {
|
|||
id,
|
||||
]);
|
||||
if (episode !== undefined) {
|
||||
queryClient.invalidateQueries({
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: [QueryKeys.Series, episode.sonarrSeriesId],
|
||||
});
|
||||
}
|
||||
|
@ -92,7 +100,7 @@ export function createDefaultReducer(): SocketIO.Reducer[] {
|
|||
id,
|
||||
]);
|
||||
if (episode !== undefined) {
|
||||
queryClient.invalidateQueries({
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: [QueryKeys.Series, episode.sonarrSeriesId],
|
||||
});
|
||||
}
|
||||
|
@ -101,28 +109,28 @@ export function createDefaultReducer(): SocketIO.Reducer[] {
|
|||
},
|
||||
{
|
||||
key: "episode-wanted",
|
||||
update: (ids) => {
|
||||
update: () => {
|
||||
// Find a better way to update wanted
|
||||
queryClient.invalidateQueries({
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: [QueryKeys.Episodes, QueryKeys.Wanted],
|
||||
});
|
||||
},
|
||||
delete: () => {
|
||||
queryClient.invalidateQueries({
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: [QueryKeys.Episodes, QueryKeys.Wanted],
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "movie-wanted",
|
||||
update: (ids) => {
|
||||
update: () => {
|
||||
// Find a better way to update wanted
|
||||
queryClient.invalidateQueries({
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: [QueryKeys.Movies, QueryKeys.Wanted],
|
||||
});
|
||||
},
|
||||
delete: () => {
|
||||
queryClient.invalidateQueries({
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: [QueryKeys.Movies, QueryKeys.Wanted],
|
||||
});
|
||||
},
|
||||
|
@ -130,13 +138,13 @@ export function createDefaultReducer(): SocketIO.Reducer[] {
|
|||
{
|
||||
key: "settings",
|
||||
any: () => {
|
||||
queryClient.invalidateQueries({ queryKey: [QueryKeys.System] });
|
||||
void queryClient.invalidateQueries({ queryKey: [QueryKeys.System] });
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "languages",
|
||||
any: () => {
|
||||
queryClient.invalidateQueries({
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: [QueryKeys.System, QueryKeys.Languages],
|
||||
});
|
||||
},
|
||||
|
@ -144,7 +152,7 @@ export function createDefaultReducer(): SocketIO.Reducer[] {
|
|||
{
|
||||
key: "badges",
|
||||
any: () => {
|
||||
queryClient.invalidateQueries({
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: [QueryKeys.System, QueryKeys.Badges],
|
||||
});
|
||||
},
|
||||
|
@ -152,7 +160,7 @@ export function createDefaultReducer(): SocketIO.Reducer[] {
|
|||
{
|
||||
key: "movie-history",
|
||||
any: () => {
|
||||
queryClient.invalidateQueries({
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: [QueryKeys.Movies, QueryKeys.History],
|
||||
});
|
||||
},
|
||||
|
@ -160,7 +168,7 @@ export function createDefaultReducer(): SocketIO.Reducer[] {
|
|||
{
|
||||
key: "movie-blacklist",
|
||||
any: () => {
|
||||
queryClient.invalidateQueries({
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: [QueryKeys.Movies, QueryKeys.Blacklist],
|
||||
});
|
||||
},
|
||||
|
@ -168,7 +176,7 @@ export function createDefaultReducer(): SocketIO.Reducer[] {
|
|||
{
|
||||
key: "episode-history",
|
||||
any: () => {
|
||||
queryClient.invalidateQueries({
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: [QueryKeys.Episodes, QueryKeys.History],
|
||||
});
|
||||
},
|
||||
|
@ -176,7 +184,7 @@ export function createDefaultReducer(): SocketIO.Reducer[] {
|
|||
{
|
||||
key: "episode-blacklist",
|
||||
any: () => {
|
||||
queryClient.invalidateQueries({
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: [QueryKeys.Episodes, QueryKeys.Blacklist],
|
||||
});
|
||||
},
|
||||
|
@ -184,7 +192,7 @@ export function createDefaultReducer(): SocketIO.Reducer[] {
|
|||
{
|
||||
key: "reset-episode-wanted",
|
||||
any: () => {
|
||||
queryClient.invalidateQueries({
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: [QueryKeys.Episodes, QueryKeys.Wanted],
|
||||
});
|
||||
},
|
||||
|
@ -192,7 +200,7 @@ export function createDefaultReducer(): SocketIO.Reducer[] {
|
|||
{
|
||||
key: "reset-movie-wanted",
|
||||
any: () => {
|
||||
queryClient.invalidateQueries({
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: [QueryKeys.Movies, QueryKeys.Wanted],
|
||||
});
|
||||
},
|
||||
|
@ -200,7 +208,7 @@ export function createDefaultReducer(): SocketIO.Reducer[] {
|
|||
{
|
||||
key: "task",
|
||||
any: () => {
|
||||
queryClient.invalidateQueries({
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: [QueryKeys.System, QueryKeys.Tasks],
|
||||
});
|
||||
},
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import { FunctionComponent } from "react";
|
||||
import { Text as MantineText } from "@mantine/core";
|
||||
import { useLanguageProfiles, useLanguages } from "@/apis/hooks";
|
||||
import {
|
||||
Check,
|
||||
Chips,
|
||||
CollapseBox,
|
||||
Layout,
|
||||
Message,
|
||||
|
@ -121,12 +123,21 @@ const SettingsLanguagesView: FunctionComponent = () => {
|
|||
Sonarr (or a Movie from Radarr) to find a matching Bazarr language
|
||||
profile tag. It will use as the language profile the FIRST tag from
|
||||
Sonarr/Radarr that matches the tag of a Bazarr language profile
|
||||
EXACTLY. If mutiple tags match, there is no guarantee as to which one
|
||||
EXACTLY. If multiple tags match, there is no guarantee as to which one
|
||||
will be used, so choose your tag names carefully. Also, if you update
|
||||
the tag names in Sonarr/Radarr, Bazarr will detect this and repeat the
|
||||
matching process for the affected shows. However, if a show's only
|
||||
matching tag is removed from Sonarr/Radarr, Bazarr will NOT remove the
|
||||
show's existing language profile, but keep it, as is.
|
||||
show's existing language profile for that reason. But if you wish to
|
||||
have language profiles removed automatically by tag value, simply
|
||||
enter a list of one or more tags in the{" "}
|
||||
<MantineText fw={700} span>
|
||||
Remove Profile Tags
|
||||
</MantineText>{" "}
|
||||
entry list below. If your video tag matches one of the tags in that
|
||||
list, then Bazarr will remove the language profile for that video. If
|
||||
there is a conflict between profile selection and profile removal,
|
||||
then profile removal wins out and is performed.
|
||||
</Message>
|
||||
<Check
|
||||
label="Series"
|
||||
|
@ -136,6 +147,19 @@ const SettingsLanguagesView: FunctionComponent = () => {
|
|||
label="Movies"
|
||||
settingKey="settings-general-movie_tag_enabled"
|
||||
></Check>
|
||||
<Chips
|
||||
label="Remove Profile Tags"
|
||||
settingKey="settings-general-remove_profile_tags"
|
||||
sanitizeFn={(values: string[] | null) =>
|
||||
values?.map((item) =>
|
||||
item.replace(/[^a-z0-9_-]/gi, "").toLowerCase(),
|
||||
)
|
||||
}
|
||||
></Chips>
|
||||
<Message>
|
||||
Enter tag values that will trigger a language profile removal. Leave
|
||||
empty if you don't want Bazarr to remove language profiles.
|
||||
</Message>
|
||||
</Section>
|
||||
<Section header="Default Settings">
|
||||
<Check
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { FunctionComponent } from "react";
|
||||
import { Code, Space, Table } from "@mantine/core";
|
||||
import React, { FunctionComponent } from "react";
|
||||
import { Code, Space, Table, Text as MantineText } from "@mantine/core";
|
||||
import {
|
||||
Check,
|
||||
CollapseBox,
|
||||
|
@ -115,14 +115,16 @@ const commandOptions: CommandOption[] = [
|
|||
},
|
||||
];
|
||||
|
||||
const commandOptionElements: JSX.Element[] = commandOptions.map((op, idx) => (
|
||||
<tr key={idx}>
|
||||
<td>
|
||||
<Code>{op.option}</Code>
|
||||
</td>
|
||||
<td>{op.description}</td>
|
||||
</tr>
|
||||
));
|
||||
const commandOptionElements: React.JSX.Element[] = commandOptions.map(
|
||||
(op, idx) => (
|
||||
<tr key={idx}>
|
||||
<td>
|
||||
<Code>{op.option}</Code>
|
||||
</td>
|
||||
<td>{op.description}</td>
|
||||
</tr>
|
||||
),
|
||||
);
|
||||
|
||||
const SettingsSubtitlesView: FunctionComponent = () => {
|
||||
return (
|
||||
|
@ -436,8 +438,11 @@ const SettingsSubtitlesView: FunctionComponent = () => {
|
|||
<Slider settingKey="settings-subsync-subsync_threshold"></Slider>
|
||||
<Space />
|
||||
<Message>
|
||||
Only series subtitles with scores <b>below</b> this value will be
|
||||
automatically synchronized.
|
||||
Only series subtitles with scores{" "}
|
||||
<MantineText fw={700} span>
|
||||
below
|
||||
</MantineText>{" "}
|
||||
this value will be automatically synchronized.
|
||||
</Message>
|
||||
</CollapseBox>
|
||||
<Check
|
||||
|
@ -451,8 +456,11 @@ const SettingsSubtitlesView: FunctionComponent = () => {
|
|||
<Slider settingKey="settings-subsync-subsync_movie_threshold"></Slider>
|
||||
<Space />
|
||||
<Message>
|
||||
Only movie subtitles with scores <b>below</b> this value will be
|
||||
automatically synchronized.
|
||||
Only movie subtitles with scores{" "}
|
||||
<MantineText fw={700} span>
|
||||
below
|
||||
</MantineText>{" "}
|
||||
this value will be automatically synchronized.
|
||||
</Message>
|
||||
</CollapseBox>
|
||||
</CollapseBox>
|
||||
|
@ -478,8 +486,11 @@ const SettingsSubtitlesView: FunctionComponent = () => {
|
|||
<Slider settingKey="settings-general-postprocessing_threshold"></Slider>
|
||||
<Space />
|
||||
<Message>
|
||||
Only series subtitles with scores <b>below</b> this value will be
|
||||
automatically post-processed.
|
||||
Only series subtitles with scores{" "}
|
||||
<MantineText fw={700} span>
|
||||
below
|
||||
</MantineText>{" "}
|
||||
this value will be automatically post-processed.
|
||||
</Message>
|
||||
</CollapseBox>
|
||||
<Check
|
||||
|
@ -493,8 +504,11 @@ const SettingsSubtitlesView: FunctionComponent = () => {
|
|||
<Slider settingKey="settings-general-postprocessing_threshold_movie"></Slider>
|
||||
<Space />
|
||||
<Message>
|
||||
Only movie subtitles with scores <b>below</b> this value will be
|
||||
automatically post-processed.
|
||||
Only movie subtitles with scores{" "}
|
||||
<MantineText fw={700} span>
|
||||
below
|
||||
</MantineText>{" "}
|
||||
this value will be automatically post-processed.
|
||||
</Message>
|
||||
</CollapseBox>
|
||||
<Text
|
||||
|
|
|
@ -2,7 +2,7 @@ import { FunctionComponent, PropsWithChildren, ReactElement } from "react";
|
|||
import { useForm } from "@mantine/form";
|
||||
import { describe, it } from "vitest";
|
||||
import { FormContext, FormValues } from "@/pages/Settings/utilities/FormValues";
|
||||
import { render, RenderOptions, screen } from "@/tests";
|
||||
import { render, screen } from "@/tests";
|
||||
import { Number, Text } from "./forms";
|
||||
|
||||
const FormSupport: FunctionComponent<PropsWithChildren> = ({ children }) => {
|
||||
|
@ -15,10 +15,8 @@ const FormSupport: FunctionComponent<PropsWithChildren> = ({ children }) => {
|
|||
return <FormContext.Provider value={form}>{children}</FormContext.Provider>;
|
||||
};
|
||||
|
||||
const formRender = (
|
||||
ui: ReactElement,
|
||||
options?: Omit<RenderOptions, "wrapper">,
|
||||
) => render(<FormSupport>{ui}</FormSupport>);
|
||||
const formRender = (ui: ReactElement) =>
|
||||
render(<FormSupport>{ui}</FormSupport>);
|
||||
|
||||
describe("Settings form", () => {
|
||||
describe("number component", () => {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { FunctionComponent, ReactNode, ReactText } from "react";
|
||||
import { FunctionComponent, ReactNode } from "react";
|
||||
import {
|
||||
Input,
|
||||
NumberInput,
|
||||
|
@ -49,7 +49,7 @@ export const Number: FunctionComponent<NumberProps> = (props) => {
|
|||
);
|
||||
};
|
||||
|
||||
export type TextProps = BaseInput<ReactText> & TextInputProps;
|
||||
export type TextProps = BaseInput<string | number> & TextInputProps;
|
||||
|
||||
export const Text: FunctionComponent<TextProps> = (props) => {
|
||||
const { value, update, rest } = useBaseInput(props);
|
||||
|
@ -86,11 +86,7 @@ export interface CheckProps extends BaseInput<boolean> {
|
|||
inline?: boolean;
|
||||
}
|
||||
|
||||
export const Check: FunctionComponent<CheckProps> = ({
|
||||
label,
|
||||
inline,
|
||||
...props
|
||||
}) => {
|
||||
export const Check: FunctionComponent<CheckProps> = ({ label, ...props }) => {
|
||||
const { value, update, rest } = useBaseInput(props);
|
||||
|
||||
return (
|
||||
|
|
1
frontend/src/types/settings.d.ts
vendored
1
frontend/src/types/settings.d.ts
vendored
|
@ -62,6 +62,7 @@ declare namespace Settings {
|
|||
postprocessing_cmd?: string;
|
||||
postprocessing_threshold: number;
|
||||
postprocessing_threshold_movie: number;
|
||||
remove_profile_tags: string[];
|
||||
single_language: boolean;
|
||||
subfolder: string;
|
||||
subfolder_custom?: string;
|
||||
|
|
Loading…
Reference in a new issue