mirror of
https://github.com/morpheus65535/bazarr.git
synced 2025-04-24 22:57:13 -04: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.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.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.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_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.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),
|
Validator('general.movie_default_enabled', must_exist=True, default=False, is_type_of=bool),
|
||||||
|
@ -468,6 +469,7 @@ array_keys = ['excluded_tags',
|
||||||
'enabled_integrations',
|
'enabled_integrations',
|
||||||
'path_mappings',
|
'path_mappings',
|
||||||
'path_mappings_movie',
|
'path_mappings_movie',
|
||||||
|
'remove_profile_tags',
|
||||||
'language_equals',
|
'language_equals',
|
||||||
'blacklisted_languages',
|
'blacklisted_languages',
|
||||||
'blacklisted_providers']
|
'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)
|
tag_profile = get_matching_profile(tags, language_profiles)
|
||||||
if tag_profile:
|
if tag_profile:
|
||||||
parsed_movie['profileId'] = 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
|
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)
|
tag_profile = get_matching_profile(tags, language_profiles)
|
||||||
if tag_profile:
|
if tag_profile:
|
||||||
parsed_series['profileId'] = 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
|
return parsed_series
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -15,12 +15,11 @@
|
||||||
"@typescript-eslint/no-unused-vars": "warn"
|
"@typescript-eslint/no-unused-vars": "warn"
|
||||||
},
|
},
|
||||||
"extends": [
|
"extends": [
|
||||||
"react-app",
|
|
||||||
"plugin:react-hooks/recommended",
|
|
||||||
"eslint:recommended",
|
"eslint:recommended",
|
||||||
|
"plugin:react-hooks/recommended",
|
||||||
"plugin:@typescript-eslint/recommended"
|
"plugin:@typescript-eslint/recommended"
|
||||||
],
|
],
|
||||||
"plugins": ["testing-library", "simple-import-sort"],
|
"plugins": ["testing-library", "simple-import-sort", "react-refresh"],
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
"files": [
|
"files": [
|
||||||
|
@ -63,6 +62,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"sourceType": "module",
|
"sourceType": "module",
|
||||||
"ecmaVersion": "latest"
|
"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/node": "^20.12.6",
|
||||||
"@types/react": "^18.3.4",
|
"@types/react": "^18.3.4",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@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",
|
"@vite-pwa/assets-generator": "^0.2.4",
|
||||||
"@vitejs/plugin-react": "^4.2.1",
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
"@vitest/coverage-v8": "^1.4.0",
|
"@vitest/coverage-v8": "^1.4.0",
|
||||||
"@vitest/ui": "^1.2.2",
|
"@vitest/ui": "^1.2.2",
|
||||||
"clsx": "^2.1.0",
|
"clsx": "^2.1.0",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-config-react-app": "^7.0.1",
|
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
|
"eslint-plugin-react-refresh": "^0.4.7",
|
||||||
"eslint-plugin-simple-import-sort": "^12.1.0",
|
"eslint-plugin-simple-import-sort": "^12.1.0",
|
||||||
"eslint-plugin-testing-library": "^6.2.0",
|
"eslint-plugin-testing-library": "^6.2.0",
|
||||||
"husky": "^9.0.11",
|
"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) {
|
export function useEpisodesBySeriesId(id: number) {
|
||||||
const client = useQueryClient();
|
const client = useQueryClient();
|
||||||
|
|
||||||
|
@ -87,10 +70,11 @@ export function useEpisodeAddBlacklist() {
|
||||||
},
|
},
|
||||||
|
|
||||||
onSuccess: (_, { seriesId }) => {
|
onSuccess: (_, { seriesId }) => {
|
||||||
client.invalidateQueries({
|
void client.invalidateQueries({
|
||||||
queryKey: [QueryKeys.Series, QueryKeys.Episodes, QueryKeys.Blacklist],
|
queryKey: [QueryKeys.Series, QueryKeys.Episodes, QueryKeys.Blacklist],
|
||||||
});
|
});
|
||||||
client.invalidateQueries({
|
|
||||||
|
void client.invalidateQueries({
|
||||||
queryKey: [QueryKeys.Series, seriesId],
|
queryKey: [QueryKeys.Series, seriesId],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -105,8 +89,8 @@ export function useEpisodeDeleteBlacklist() {
|
||||||
mutationFn: (param: { all?: boolean; form?: FormType.DeleteBlacklist }) =>
|
mutationFn: (param: { all?: boolean; form?: FormType.DeleteBlacklist }) =>
|
||||||
api.episodes.deleteBlacklist(param.all, param.form),
|
api.episodes.deleteBlacklist(param.all, param.form),
|
||||||
|
|
||||||
onSuccess: (_) => {
|
onSuccess: () => {
|
||||||
client.invalidateQueries({
|
void client.invalidateQueries({
|
||||||
queryKey: [QueryKeys.Series, QueryKeys.Episodes, QueryKeys.Blacklist],
|
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) {
|
export function useMovieById(id: number) {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: [QueryKeys.Movies, id],
|
queryKey: [QueryKeys.Movies, id],
|
||||||
|
@ -74,12 +57,13 @@ export function useMovieModification() {
|
||||||
|
|
||||||
onSuccess: (_, form) => {
|
onSuccess: (_, form) => {
|
||||||
form.id.forEach((v) => {
|
form.id.forEach((v) => {
|
||||||
client.invalidateQueries({
|
void client.invalidateQueries({
|
||||||
queryKey: [QueryKeys.Movies, v],
|
queryKey: [QueryKeys.Movies, v],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: query less
|
// TODO: query less
|
||||||
client.invalidateQueries({
|
void client.invalidateQueries({
|
||||||
queryKey: [QueryKeys.Movies],
|
queryKey: [QueryKeys.Movies],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -93,7 +77,7 @@ export function useMovieAction() {
|
||||||
mutationFn: (form: FormType.MoviesAction) => api.movies.action(form),
|
mutationFn: (form: FormType.MoviesAction) => api.movies.action(form),
|
||||||
|
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
client.invalidateQueries({
|
void client.invalidateQueries({
|
||||||
queryKey: [QueryKeys.Movies],
|
queryKey: [QueryKeys.Movies],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -125,10 +109,11 @@ export function useMovieAddBlacklist() {
|
||||||
},
|
},
|
||||||
|
|
||||||
onSuccess: (_, { id }) => {
|
onSuccess: (_, { id }) => {
|
||||||
client.invalidateQueries({
|
void client.invalidateQueries({
|
||||||
queryKey: [QueryKeys.Movies, QueryKeys.Blacklist],
|
queryKey: [QueryKeys.Movies, QueryKeys.Blacklist],
|
||||||
});
|
});
|
||||||
client.invalidateQueries({
|
|
||||||
|
void client.invalidateQueries({
|
||||||
queryKey: [QueryKeys.Movies, id],
|
queryKey: [QueryKeys.Movies, id],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -143,8 +128,8 @@ export function useMovieDeleteBlacklist() {
|
||||||
mutationFn: (param: { all?: boolean; form?: FormType.DeleteBlacklist }) =>
|
mutationFn: (param: { all?: boolean; form?: FormType.DeleteBlacklist }) =>
|
||||||
api.movies.deleteBlacklist(param.all, param.form),
|
api.movies.deleteBlacklist(param.all, param.form),
|
||||||
|
|
||||||
onSuccess: (_, param) => {
|
onSuccess: () => {
|
||||||
client.invalidateQueries({
|
void client.invalidateQueries({
|
||||||
queryKey: [QueryKeys.Movies, QueryKeys.Blacklist],
|
queryKey: [QueryKeys.Movies, QueryKeys.Blacklist],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -54,22 +54,27 @@ export function useSettingsMutation() {
|
||||||
mutationFn: (data: LooseObject) => api.system.updateSettings(data),
|
mutationFn: (data: LooseObject) => api.system.updateSettings(data),
|
||||||
|
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
client.invalidateQueries({
|
void client.invalidateQueries({
|
||||||
queryKey: [QueryKeys.System],
|
queryKey: [QueryKeys.System],
|
||||||
});
|
});
|
||||||
client.invalidateQueries({
|
|
||||||
|
void client.invalidateQueries({
|
||||||
queryKey: [QueryKeys.Series],
|
queryKey: [QueryKeys.Series],
|
||||||
});
|
});
|
||||||
client.invalidateQueries({
|
|
||||||
|
void client.invalidateQueries({
|
||||||
queryKey: [QueryKeys.Episodes],
|
queryKey: [QueryKeys.Episodes],
|
||||||
});
|
});
|
||||||
client.invalidateQueries({
|
|
||||||
|
void client.invalidateQueries({
|
||||||
queryKey: [QueryKeys.Movies],
|
queryKey: [QueryKeys.Movies],
|
||||||
});
|
});
|
||||||
client.invalidateQueries({
|
|
||||||
|
void client.invalidateQueries({
|
||||||
queryKey: [QueryKeys.Wanted],
|
queryKey: [QueryKeys.Wanted],
|
||||||
});
|
});
|
||||||
client.invalidateQueries({
|
|
||||||
|
void client.invalidateQueries({
|
||||||
queryKey: [QueryKeys.Badges],
|
queryKey: [QueryKeys.Badges],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -101,7 +106,7 @@ export function useDeleteLogs() {
|
||||||
mutationFn: () => api.system.deleteLogs(),
|
mutationFn: () => api.system.deleteLogs(),
|
||||||
|
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
client.invalidateQueries({
|
void client.invalidateQueries({
|
||||||
queryKey: [QueryKeys.System, QueryKeys.Logs],
|
queryKey: [QueryKeys.System, QueryKeys.Logs],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -128,11 +133,12 @@ export function useSystemAnnouncementsAddDismiss() {
|
||||||
return api.system.addAnnouncementsDismiss(hash);
|
return api.system.addAnnouncementsDismiss(hash);
|
||||||
},
|
},
|
||||||
|
|
||||||
onSuccess: (_, { hash }) => {
|
onSuccess: () => {
|
||||||
client.invalidateQueries({
|
void client.invalidateQueries({
|
||||||
queryKey: [QueryKeys.System, QueryKeys.Announcements],
|
queryKey: [QueryKeys.System, QueryKeys.Announcements],
|
||||||
});
|
});
|
||||||
client.invalidateQueries({
|
|
||||||
|
void client.invalidateQueries({
|
||||||
queryKey: [QueryKeys.System, QueryKeys.Badges],
|
queryKey: [QueryKeys.System, QueryKeys.Badges],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -156,10 +162,11 @@ export function useRunTask() {
|
||||||
mutationFn: (id: string) => api.system.runTask(id),
|
mutationFn: (id: string) => api.system.runTask(id),
|
||||||
|
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
client.invalidateQueries({
|
void client.invalidateQueries({
|
||||||
queryKey: [QueryKeys.System, QueryKeys.Tasks],
|
queryKey: [QueryKeys.System, QueryKeys.Tasks],
|
||||||
});
|
});
|
||||||
client.invalidateQueries({
|
|
||||||
|
void client.invalidateQueries({
|
||||||
queryKey: [QueryKeys.System, QueryKeys.Backups],
|
queryKey: [QueryKeys.System, QueryKeys.Backups],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -180,7 +187,7 @@ export function useCreateBackups() {
|
||||||
mutationFn: () => api.system.createBackups(),
|
mutationFn: () => api.system.createBackups(),
|
||||||
|
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
client.invalidateQueries({
|
void client.invalidateQueries({
|
||||||
queryKey: [QueryKeys.System, QueryKeys.Backups],
|
queryKey: [QueryKeys.System, QueryKeys.Backups],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -194,7 +201,7 @@ export function useRestoreBackups() {
|
||||||
mutationFn: (filename: string) => api.system.restoreBackups(filename),
|
mutationFn: (filename: string) => api.system.restoreBackups(filename),
|
||||||
|
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
client.invalidateQueries({
|
void client.invalidateQueries({
|
||||||
queryKey: [QueryKeys.System, QueryKeys.Backups],
|
queryKey: [QueryKeys.System, QueryKeys.Backups],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -208,7 +215,7 @@ export function useDeleteBackups() {
|
||||||
mutationFn: (filename: string) => api.system.deleteBackups(filename),
|
mutationFn: (filename: string) => api.system.deleteBackups(filename),
|
||||||
|
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
client.invalidateQueries({
|
void client.invalidateQueries({
|
||||||
queryKey: [QueryKeys.System, QueryKeys.Backups],
|
queryKey: [QueryKeys.System, QueryKeys.Backups],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -16,7 +16,6 @@ type MutateActionProps<DATA, VAR> = Omit<
|
||||||
|
|
||||||
function MutateAction<DATA, VAR>({
|
function MutateAction<DATA, VAR>({
|
||||||
mutation,
|
mutation,
|
||||||
noReset,
|
|
||||||
onSuccess,
|
onSuccess,
|
||||||
onError,
|
onError,
|
||||||
args,
|
args,
|
||||||
|
|
|
@ -15,7 +15,6 @@ type MutateButtonProps<DATA, VAR> = Omit<
|
||||||
|
|
||||||
function MutateButton<DATA, VAR>({
|
function MutateButton<DATA, VAR>({
|
||||||
mutation,
|
mutation,
|
||||||
noReset,
|
|
||||||
onSuccess,
|
onSuccess,
|
||||||
onError,
|
onError,
|
||||||
args,
|
args,
|
||||||
|
|
|
@ -12,7 +12,7 @@ interface QueryOverlayProps {
|
||||||
const QueryOverlay: FunctionComponent<QueryOverlayProps> = ({
|
const QueryOverlay: FunctionComponent<QueryOverlayProps> = ({
|
||||||
children,
|
children,
|
||||||
global = false,
|
global = false,
|
||||||
result: { isLoading, isError, error },
|
result: { isLoading },
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<LoadingProvider value={isLoading}>
|
<LoadingProvider value={isLoading}>
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {
|
||||||
Select,
|
Select,
|
||||||
SelectProps,
|
SelectProps,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { isNull, isUndefined, noop } from "lodash";
|
import { isNull, isUndefined } from "lodash";
|
||||||
import { LOG } from "@/utilities/console";
|
import { LOG } from "@/utilities/console";
|
||||||
|
|
||||||
export type SelectorOption<T> = Override<
|
export type SelectorOption<T> = Override<
|
||||||
|
@ -49,10 +49,7 @@ export type GroupedSelectorProps<T> = Override<
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function GroupedSelector<T>({
|
export function GroupedSelector<T>({
|
||||||
value,
|
|
||||||
options,
|
options,
|
||||||
getkey = DefaultKeyBuilder,
|
|
||||||
onOptionSubmit = noop,
|
|
||||||
...select
|
...select
|
||||||
}: GroupedSelectorProps<T>) {
|
}: GroupedSelectorProps<T>) {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -5,11 +5,8 @@ import { ModalSettings } from "@mantine/modals/lib/context";
|
||||||
import { ModalComponent, ModalIdContext } from "./WithModal";
|
import { ModalComponent, ModalIdContext } from "./WithModal";
|
||||||
|
|
||||||
export function useModals() {
|
export function useModals() {
|
||||||
const {
|
const { openContextModal: openMantineContextModal, ...rest } =
|
||||||
openContextModal: openMantineContextModal,
|
useMantineModals();
|
||||||
closeContextModal: closeContextModalRaw,
|
|
||||||
...rest
|
|
||||||
} = useMantineModals();
|
|
||||||
|
|
||||||
const openContextModal = useCallback(
|
const openContextModal = useCallback(
|
||||||
<ARGS extends {}>(
|
<ARGS extends {}>(
|
||||||
|
@ -26,7 +23,7 @@ export function useModals() {
|
||||||
[openMantineContextModal],
|
[openMantineContextModal],
|
||||||
);
|
);
|
||||||
|
|
||||||
const closeContextModal = useCallback(
|
const closeContext = useCallback(
|
||||||
(modal: ModalComponent) => {
|
(modal: ModalComponent) => {
|
||||||
rest.closeModal(modal.modalKey);
|
rest.closeModal(modal.modalKey);
|
||||||
},
|
},
|
||||||
|
@ -43,7 +40,7 @@ export function useModals() {
|
||||||
|
|
||||||
// TODO: Performance
|
// TODO: Performance
|
||||||
return useMemo(
|
return useMemo(
|
||||||
() => ({ openContextModal, closeContextModal, closeSelf, ...rest }),
|
() => ({ openContextModal, closeContext, closeSelf, ...rest }),
|
||||||
[closeContextModal, closeSelf, openContextModal, rest],
|
[closeContext, closeSelf, openContextModal, rest],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,13 +40,17 @@ export function createDefaultReducer(): SocketIO.Reducer[] {
|
||||||
update: (ids) => {
|
update: (ids) => {
|
||||||
LOG("info", "Invalidating series", ids);
|
LOG("info", "Invalidating series", ids);
|
||||||
ids.forEach((id) => {
|
ids.forEach((id) => {
|
||||||
queryClient.invalidateQueries({ queryKey: [QueryKeys.Series, id] });
|
void queryClient.invalidateQueries({
|
||||||
|
queryKey: [QueryKeys.Series, id],
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
delete: (ids) => {
|
delete: (ids) => {
|
||||||
LOG("info", "Invalidating series", ids);
|
LOG("info", "Invalidating series", ids);
|
||||||
ids.forEach((id) => {
|
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) => {
|
update: (ids) => {
|
||||||
LOG("info", "Invalidating movies", ids);
|
LOG("info", "Invalidating movies", ids);
|
||||||
ids.forEach((id) => {
|
ids.forEach((id) => {
|
||||||
queryClient.invalidateQueries({ queryKey: [QueryKeys.Movies, id] });
|
void queryClient.invalidateQueries({
|
||||||
|
queryKey: [QueryKeys.Movies, id],
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
delete: (ids) => {
|
delete: (ids) => {
|
||||||
LOG("info", "Invalidating movies", ids);
|
LOG("info", "Invalidating movies", ids);
|
||||||
ids.forEach((id) => {
|
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,
|
id,
|
||||||
]);
|
]);
|
||||||
if (episode !== undefined) {
|
if (episode !== undefined) {
|
||||||
queryClient.invalidateQueries({
|
void queryClient.invalidateQueries({
|
||||||
queryKey: [QueryKeys.Series, episode.sonarrSeriesId],
|
queryKey: [QueryKeys.Series, episode.sonarrSeriesId],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -92,7 +100,7 @@ export function createDefaultReducer(): SocketIO.Reducer[] {
|
||||||
id,
|
id,
|
||||||
]);
|
]);
|
||||||
if (episode !== undefined) {
|
if (episode !== undefined) {
|
||||||
queryClient.invalidateQueries({
|
void queryClient.invalidateQueries({
|
||||||
queryKey: [QueryKeys.Series, episode.sonarrSeriesId],
|
queryKey: [QueryKeys.Series, episode.sonarrSeriesId],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -101,28 +109,28 @@ export function createDefaultReducer(): SocketIO.Reducer[] {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "episode-wanted",
|
key: "episode-wanted",
|
||||||
update: (ids) => {
|
update: () => {
|
||||||
// Find a better way to update wanted
|
// Find a better way to update wanted
|
||||||
queryClient.invalidateQueries({
|
void queryClient.invalidateQueries({
|
||||||
queryKey: [QueryKeys.Episodes, QueryKeys.Wanted],
|
queryKey: [QueryKeys.Episodes, QueryKeys.Wanted],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
delete: () => {
|
delete: () => {
|
||||||
queryClient.invalidateQueries({
|
void queryClient.invalidateQueries({
|
||||||
queryKey: [QueryKeys.Episodes, QueryKeys.Wanted],
|
queryKey: [QueryKeys.Episodes, QueryKeys.Wanted],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "movie-wanted",
|
key: "movie-wanted",
|
||||||
update: (ids) => {
|
update: () => {
|
||||||
// Find a better way to update wanted
|
// Find a better way to update wanted
|
||||||
queryClient.invalidateQueries({
|
void queryClient.invalidateQueries({
|
||||||
queryKey: [QueryKeys.Movies, QueryKeys.Wanted],
|
queryKey: [QueryKeys.Movies, QueryKeys.Wanted],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
delete: () => {
|
delete: () => {
|
||||||
queryClient.invalidateQueries({
|
void queryClient.invalidateQueries({
|
||||||
queryKey: [QueryKeys.Movies, QueryKeys.Wanted],
|
queryKey: [QueryKeys.Movies, QueryKeys.Wanted],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -130,13 +138,13 @@ export function createDefaultReducer(): SocketIO.Reducer[] {
|
||||||
{
|
{
|
||||||
key: "settings",
|
key: "settings",
|
||||||
any: () => {
|
any: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: [QueryKeys.System] });
|
void queryClient.invalidateQueries({ queryKey: [QueryKeys.System] });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "languages",
|
key: "languages",
|
||||||
any: () => {
|
any: () => {
|
||||||
queryClient.invalidateQueries({
|
void queryClient.invalidateQueries({
|
||||||
queryKey: [QueryKeys.System, QueryKeys.Languages],
|
queryKey: [QueryKeys.System, QueryKeys.Languages],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -144,7 +152,7 @@ export function createDefaultReducer(): SocketIO.Reducer[] {
|
||||||
{
|
{
|
||||||
key: "badges",
|
key: "badges",
|
||||||
any: () => {
|
any: () => {
|
||||||
queryClient.invalidateQueries({
|
void queryClient.invalidateQueries({
|
||||||
queryKey: [QueryKeys.System, QueryKeys.Badges],
|
queryKey: [QueryKeys.System, QueryKeys.Badges],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -152,7 +160,7 @@ export function createDefaultReducer(): SocketIO.Reducer[] {
|
||||||
{
|
{
|
||||||
key: "movie-history",
|
key: "movie-history",
|
||||||
any: () => {
|
any: () => {
|
||||||
queryClient.invalidateQueries({
|
void queryClient.invalidateQueries({
|
||||||
queryKey: [QueryKeys.Movies, QueryKeys.History],
|
queryKey: [QueryKeys.Movies, QueryKeys.History],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -160,7 +168,7 @@ export function createDefaultReducer(): SocketIO.Reducer[] {
|
||||||
{
|
{
|
||||||
key: "movie-blacklist",
|
key: "movie-blacklist",
|
||||||
any: () => {
|
any: () => {
|
||||||
queryClient.invalidateQueries({
|
void queryClient.invalidateQueries({
|
||||||
queryKey: [QueryKeys.Movies, QueryKeys.Blacklist],
|
queryKey: [QueryKeys.Movies, QueryKeys.Blacklist],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -168,7 +176,7 @@ export function createDefaultReducer(): SocketIO.Reducer[] {
|
||||||
{
|
{
|
||||||
key: "episode-history",
|
key: "episode-history",
|
||||||
any: () => {
|
any: () => {
|
||||||
queryClient.invalidateQueries({
|
void queryClient.invalidateQueries({
|
||||||
queryKey: [QueryKeys.Episodes, QueryKeys.History],
|
queryKey: [QueryKeys.Episodes, QueryKeys.History],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -176,7 +184,7 @@ export function createDefaultReducer(): SocketIO.Reducer[] {
|
||||||
{
|
{
|
||||||
key: "episode-blacklist",
|
key: "episode-blacklist",
|
||||||
any: () => {
|
any: () => {
|
||||||
queryClient.invalidateQueries({
|
void queryClient.invalidateQueries({
|
||||||
queryKey: [QueryKeys.Episodes, QueryKeys.Blacklist],
|
queryKey: [QueryKeys.Episodes, QueryKeys.Blacklist],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -184,7 +192,7 @@ export function createDefaultReducer(): SocketIO.Reducer[] {
|
||||||
{
|
{
|
||||||
key: "reset-episode-wanted",
|
key: "reset-episode-wanted",
|
||||||
any: () => {
|
any: () => {
|
||||||
queryClient.invalidateQueries({
|
void queryClient.invalidateQueries({
|
||||||
queryKey: [QueryKeys.Episodes, QueryKeys.Wanted],
|
queryKey: [QueryKeys.Episodes, QueryKeys.Wanted],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -192,7 +200,7 @@ export function createDefaultReducer(): SocketIO.Reducer[] {
|
||||||
{
|
{
|
||||||
key: "reset-movie-wanted",
|
key: "reset-movie-wanted",
|
||||||
any: () => {
|
any: () => {
|
||||||
queryClient.invalidateQueries({
|
void queryClient.invalidateQueries({
|
||||||
queryKey: [QueryKeys.Movies, QueryKeys.Wanted],
|
queryKey: [QueryKeys.Movies, QueryKeys.Wanted],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -200,7 +208,7 @@ export function createDefaultReducer(): SocketIO.Reducer[] {
|
||||||
{
|
{
|
||||||
key: "task",
|
key: "task",
|
||||||
any: () => {
|
any: () => {
|
||||||
queryClient.invalidateQueries({
|
void queryClient.invalidateQueries({
|
||||||
queryKey: [QueryKeys.System, QueryKeys.Tasks],
|
queryKey: [QueryKeys.System, QueryKeys.Tasks],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import { FunctionComponent } from "react";
|
import { FunctionComponent } from "react";
|
||||||
|
import { Text as MantineText } from "@mantine/core";
|
||||||
import { useLanguageProfiles, useLanguages } from "@/apis/hooks";
|
import { useLanguageProfiles, useLanguages } from "@/apis/hooks";
|
||||||
import {
|
import {
|
||||||
Check,
|
Check,
|
||||||
|
Chips,
|
||||||
CollapseBox,
|
CollapseBox,
|
||||||
Layout,
|
Layout,
|
||||||
Message,
|
Message,
|
||||||
|
@ -121,12 +123,21 @@ const SettingsLanguagesView: FunctionComponent = () => {
|
||||||
Sonarr (or a Movie from Radarr) to find a matching Bazarr language
|
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
|
profile tag. It will use as the language profile the FIRST tag from
|
||||||
Sonarr/Radarr that matches the tag of a Bazarr language profile
|
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
|
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
|
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 process for the affected shows. However, if a show's only
|
||||||
matching tag is removed from Sonarr/Radarr, Bazarr will NOT remove the
|
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>
|
</Message>
|
||||||
<Check
|
<Check
|
||||||
label="Series"
|
label="Series"
|
||||||
|
@ -136,6 +147,19 @@ const SettingsLanguagesView: FunctionComponent = () => {
|
||||||
label="Movies"
|
label="Movies"
|
||||||
settingKey="settings-general-movie_tag_enabled"
|
settingKey="settings-general-movie_tag_enabled"
|
||||||
></Check>
|
></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>
|
||||||
<Section header="Default Settings">
|
<Section header="Default Settings">
|
||||||
<Check
|
<Check
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { FunctionComponent } from "react";
|
import React, { FunctionComponent } from "react";
|
||||||
import { Code, Space, Table } from "@mantine/core";
|
import { Code, Space, Table, Text as MantineText } from "@mantine/core";
|
||||||
import {
|
import {
|
||||||
Check,
|
Check,
|
||||||
CollapseBox,
|
CollapseBox,
|
||||||
|
@ -115,14 +115,16 @@ const commandOptions: CommandOption[] = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const commandOptionElements: JSX.Element[] = commandOptions.map((op, idx) => (
|
const commandOptionElements: React.JSX.Element[] = commandOptions.map(
|
||||||
<tr key={idx}>
|
(op, idx) => (
|
||||||
<td>
|
<tr key={idx}>
|
||||||
<Code>{op.option}</Code>
|
<td>
|
||||||
</td>
|
<Code>{op.option}</Code>
|
||||||
<td>{op.description}</td>
|
</td>
|
||||||
</tr>
|
<td>{op.description}</td>
|
||||||
));
|
</tr>
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
const SettingsSubtitlesView: FunctionComponent = () => {
|
const SettingsSubtitlesView: FunctionComponent = () => {
|
||||||
return (
|
return (
|
||||||
|
@ -436,8 +438,11 @@ const SettingsSubtitlesView: FunctionComponent = () => {
|
||||||
<Slider settingKey="settings-subsync-subsync_threshold"></Slider>
|
<Slider settingKey="settings-subsync-subsync_threshold"></Slider>
|
||||||
<Space />
|
<Space />
|
||||||
<Message>
|
<Message>
|
||||||
Only series subtitles with scores <b>below</b> this value will be
|
Only series subtitles with scores{" "}
|
||||||
automatically synchronized.
|
<MantineText fw={700} span>
|
||||||
|
below
|
||||||
|
</MantineText>{" "}
|
||||||
|
this value will be automatically synchronized.
|
||||||
</Message>
|
</Message>
|
||||||
</CollapseBox>
|
</CollapseBox>
|
||||||
<Check
|
<Check
|
||||||
|
@ -451,8 +456,11 @@ const SettingsSubtitlesView: FunctionComponent = () => {
|
||||||
<Slider settingKey="settings-subsync-subsync_movie_threshold"></Slider>
|
<Slider settingKey="settings-subsync-subsync_movie_threshold"></Slider>
|
||||||
<Space />
|
<Space />
|
||||||
<Message>
|
<Message>
|
||||||
Only movie subtitles with scores <b>below</b> this value will be
|
Only movie subtitles with scores{" "}
|
||||||
automatically synchronized.
|
<MantineText fw={700} span>
|
||||||
|
below
|
||||||
|
</MantineText>{" "}
|
||||||
|
this value will be automatically synchronized.
|
||||||
</Message>
|
</Message>
|
||||||
</CollapseBox>
|
</CollapseBox>
|
||||||
</CollapseBox>
|
</CollapseBox>
|
||||||
|
@ -478,8 +486,11 @@ const SettingsSubtitlesView: FunctionComponent = () => {
|
||||||
<Slider settingKey="settings-general-postprocessing_threshold"></Slider>
|
<Slider settingKey="settings-general-postprocessing_threshold"></Slider>
|
||||||
<Space />
|
<Space />
|
||||||
<Message>
|
<Message>
|
||||||
Only series subtitles with scores <b>below</b> this value will be
|
Only series subtitles with scores{" "}
|
||||||
automatically post-processed.
|
<MantineText fw={700} span>
|
||||||
|
below
|
||||||
|
</MantineText>{" "}
|
||||||
|
this value will be automatically post-processed.
|
||||||
</Message>
|
</Message>
|
||||||
</CollapseBox>
|
</CollapseBox>
|
||||||
<Check
|
<Check
|
||||||
|
@ -493,8 +504,11 @@ const SettingsSubtitlesView: FunctionComponent = () => {
|
||||||
<Slider settingKey="settings-general-postprocessing_threshold_movie"></Slider>
|
<Slider settingKey="settings-general-postprocessing_threshold_movie"></Slider>
|
||||||
<Space />
|
<Space />
|
||||||
<Message>
|
<Message>
|
||||||
Only movie subtitles with scores <b>below</b> this value will be
|
Only movie subtitles with scores{" "}
|
||||||
automatically post-processed.
|
<MantineText fw={700} span>
|
||||||
|
below
|
||||||
|
</MantineText>{" "}
|
||||||
|
this value will be automatically post-processed.
|
||||||
</Message>
|
</Message>
|
||||||
</CollapseBox>
|
</CollapseBox>
|
||||||
<Text
|
<Text
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { FunctionComponent, PropsWithChildren, ReactElement } from "react";
|
||||||
import { useForm } from "@mantine/form";
|
import { useForm } from "@mantine/form";
|
||||||
import { describe, it } from "vitest";
|
import { describe, it } from "vitest";
|
||||||
import { FormContext, FormValues } from "@/pages/Settings/utilities/FormValues";
|
import { FormContext, FormValues } from "@/pages/Settings/utilities/FormValues";
|
||||||
import { render, RenderOptions, screen } from "@/tests";
|
import { render, screen } from "@/tests";
|
||||||
import { Number, Text } from "./forms";
|
import { Number, Text } from "./forms";
|
||||||
|
|
||||||
const FormSupport: FunctionComponent<PropsWithChildren> = ({ children }) => {
|
const FormSupport: FunctionComponent<PropsWithChildren> = ({ children }) => {
|
||||||
|
@ -15,10 +15,8 @@ const FormSupport: FunctionComponent<PropsWithChildren> = ({ children }) => {
|
||||||
return <FormContext.Provider value={form}>{children}</FormContext.Provider>;
|
return <FormContext.Provider value={form}>{children}</FormContext.Provider>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const formRender = (
|
const formRender = (ui: ReactElement) =>
|
||||||
ui: ReactElement,
|
render(<FormSupport>{ui}</FormSupport>);
|
||||||
options?: Omit<RenderOptions, "wrapper">,
|
|
||||||
) => render(<FormSupport>{ui}</FormSupport>);
|
|
||||||
|
|
||||||
describe("Settings form", () => {
|
describe("Settings form", () => {
|
||||||
describe("number component", () => {
|
describe("number component", () => {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { FunctionComponent, ReactNode, ReactText } from "react";
|
import { FunctionComponent, ReactNode } from "react";
|
||||||
import {
|
import {
|
||||||
Input,
|
Input,
|
||||||
NumberInput,
|
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) => {
|
export const Text: FunctionComponent<TextProps> = (props) => {
|
||||||
const { value, update, rest } = useBaseInput(props);
|
const { value, update, rest } = useBaseInput(props);
|
||||||
|
@ -86,11 +86,7 @@ export interface CheckProps extends BaseInput<boolean> {
|
||||||
inline?: boolean;
|
inline?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Check: FunctionComponent<CheckProps> = ({
|
export const Check: FunctionComponent<CheckProps> = ({ label, ...props }) => {
|
||||||
label,
|
|
||||||
inline,
|
|
||||||
...props
|
|
||||||
}) => {
|
|
||||||
const { value, update, rest } = useBaseInput(props);
|
const { value, update, rest } = useBaseInput(props);
|
||||||
|
|
||||||
return (
|
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_cmd?: string;
|
||||||
postprocessing_threshold: number;
|
postprocessing_threshold: number;
|
||||||
postprocessing_threshold_movie: number;
|
postprocessing_threshold_movie: number;
|
||||||
|
remove_profile_tags: string[];
|
||||||
single_language: boolean;
|
single_language: boolean;
|
||||||
subfolder: string;
|
subfolder: string;
|
||||||
subfolder_custom?: string;
|
subfolder_custom?: string;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue