mirror of
https://github.com/ajnart/homarr.git
synced 2026-02-02 12:49:20 +01:00
Merge branch 'dev' into add-umami
This commit is contained in:
@@ -492,7 +492,7 @@
|
||||
"pageTitle": "Homarr ⭐️",
|
||||
"logoImageUrl": "/imgs/logo/logo.png",
|
||||
"faviconUrl": "/imgs/favicon/favicon-squared.png",
|
||||
"backgroundImageUrl": "https://images.unsplash.com/32/Mc8kW4x9Q3aRR3RkP5Im_IMG_4417.jpg?ixid=M3wxMjA3fDB8MXxzZWFyY2h8MTV8fGJhY2tncm91bmQlMjBpbWFnZXxlbnwwfHx8fDE2OTE0NDQ5NjF8MA&ixlib=rb-4.0.3",
|
||||
"backgroundImageUrl": "",
|
||||
"customCss": "",
|
||||
"colors": {
|
||||
"primary": "red",
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
"useSonarrv4": {
|
||||
"label": "Use Sonarr v4 API"
|
||||
},
|
||||
"useRadarrv5": {
|
||||
"label": "Use Radarr v5 API"
|
||||
},
|
||||
"radarrReleaseType": {
|
||||
"label": "Radarr release type",
|
||||
"data":{
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
import { Group, Select, Stack, Text } from '@mantine/core';
|
||||
import { showNotification } from '@mantine/notifications';
|
||||
import { getCookie, setCookie } from 'cookies-next';
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useRouter } from 'next/router';
|
||||
import { forwardRef, useState } from 'react';
|
||||
import { api } from '~/utils/api';
|
||||
|
||||
import { COOKIE_LOCALE_KEY } from '../../../../../data/constants';
|
||||
import { Language, getLanguageByCode } from '~/tools/language';
|
||||
|
||||
export default function LanguageSelect() {
|
||||
const { data: sessionData } = useSession();
|
||||
const { t, i18n } = useTranslation('settings/general/internationalization');
|
||||
const { changeLanguage } = i18n;
|
||||
const configLocale = getCookie(COOKIE_LOCALE_KEY);
|
||||
const { locale, locales, pathname, query, asPath, push } = useRouter();
|
||||
const [selectedLanguage, setSelectedLanguage] = useState<string>(
|
||||
sessionData?.user.language ?? (configLocale as string) ?? locale ?? 'en'
|
||||
);
|
||||
const { mutateAsync } = api.user.changeLanguage.useMutation();
|
||||
|
||||
const data = locales
|
||||
? locales.map((localeItem) => ({
|
||||
value: localeItem,
|
||||
label: getLanguageByCode(localeItem).originalName,
|
||||
icon: getLanguageByCode(localeItem).emoji,
|
||||
language: getLanguageByCode(localeItem),
|
||||
}))
|
||||
: [];
|
||||
|
||||
const onChangeSelect = (value: string) => {
|
||||
setSelectedLanguage(value);
|
||||
|
||||
const newLanguage = getLanguageByCode(value);
|
||||
changeLanguage(value)
|
||||
.then(async () => {
|
||||
setCookie(COOKIE_LOCALE_KEY, value, {
|
||||
maxAge: 60 * 60 * 24 * 30,
|
||||
sameSite: 'strict',
|
||||
});
|
||||
|
||||
if (sessionData?.user && new Date(sessionData.expires) > new Date()) {
|
||||
await mutateAsync({
|
||||
language: value,
|
||||
});
|
||||
}
|
||||
|
||||
push(
|
||||
{
|
||||
pathname,
|
||||
query,
|
||||
},
|
||||
asPath,
|
||||
{ locale: value }
|
||||
);
|
||||
|
||||
showNotification({
|
||||
title: 'Language changed',
|
||||
message: `You changed the language to '${newLanguage.originalName}'`,
|
||||
color: 'green',
|
||||
autoClose: 5000,
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
showNotification({
|
||||
title: 'Failed to change language',
|
||||
message: `Failed to change to '${newLanguage.originalName}', Error:'${err}`,
|
||||
color: 'red',
|
||||
autoClose: 5000,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Select
|
||||
icon={<Text>{getLanguageByCode(selectedLanguage).emoji}</Text>}
|
||||
label={t('label')}
|
||||
data={data}
|
||||
itemComponent={SelectItem}
|
||||
nothingFound="Nothing found"
|
||||
onChange={onChangeSelect}
|
||||
value={selectedLanguage}
|
||||
defaultValue={locale}
|
||||
searchable
|
||||
filter={(value, item) => {
|
||||
const selectItems = item as unknown as { value: string; language: Language };
|
||||
return (
|
||||
selectItems.language.originalName
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.includes(value.toLowerCase().trim()) ||
|
||||
selectItems.language.translatedName
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.includes(value.toLowerCase().trim())
|
||||
);
|
||||
}}
|
||||
styles={{
|
||||
icon: {
|
||||
width: 42,
|
||||
},
|
||||
input: {
|
||||
paddingLeft: '45px !important',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
interface ItemProps extends React.ComponentPropsWithoutRef<'div'> {
|
||||
image: string;
|
||||
language: Language;
|
||||
}
|
||||
|
||||
const SelectItem = forwardRef<HTMLDivElement, ItemProps>(
|
||||
({ language, image, ...others }: ItemProps, ref) => (
|
||||
<div ref={ref} {...others}>
|
||||
<Group noWrap>
|
||||
<Text>{language.emoji}</Text>
|
||||
|
||||
<div>
|
||||
<Text size="sm">
|
||||
{language.originalName} ({language.translatedName})
|
||||
</Text>
|
||||
</div>
|
||||
</Group>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
@@ -219,7 +219,7 @@ const BackgroundImage = () => {
|
||||
return (
|
||||
<Global
|
||||
styles={{
|
||||
body: {
|
||||
'.mantine-AppShell-root': {
|
||||
minHeight: '100vh',
|
||||
backgroundImage: `url('${config?.settings.customization.backgroundImageUrl}')`,
|
||||
backgroundPosition: 'center center',
|
||||
|
||||
@@ -134,7 +134,12 @@ export function LidarrMediaDisplay(props: any) {
|
||||
|
||||
export function RadarrMediaDisplay(props: any) {
|
||||
const { media }: { media: any } = props;
|
||||
const { config } = useConfigContext();
|
||||
const calendar = config?.widgets.find((w) => w.type === 'calendar');
|
||||
const useRadarrv5 = calendar?.properties.useRadarrv5 ?? false;
|
||||
|
||||
// Find a poster CoverType
|
||||
const poster = media.images.find((image: any) => image.coverType === 'poster');
|
||||
return (
|
||||
<MediaDisplay
|
||||
media={{
|
||||
@@ -142,7 +147,7 @@ export function RadarrMediaDisplay(props: any) {
|
||||
title: media.title ?? media.originalTitle,
|
||||
overview: media.overview ?? '',
|
||||
genres: media.genres ?? [],
|
||||
poster: media.images.find((image: any) => image.coverType === 'poster')?.url,
|
||||
poster: useRadarrv5 ? poster.remoteUrl : poster.url,
|
||||
voteAverage: media.ratings.tmdb.value.toString(),
|
||||
imdbId: media.imdbId,
|
||||
type: 'movie',
|
||||
|
||||
@@ -21,6 +21,7 @@ import { ConfigProvider } from '~/config/provider';
|
||||
import { env } from '~/env.js';
|
||||
import { ColorSchemeProvider } from '~/hooks/use-colorscheme';
|
||||
import { modals } from '~/modals';
|
||||
import { usePackageAttributesStore } from '~/tools/client/zustands/usePackageAttributesStore';
|
||||
import { ColorTheme } from '~/tools/color';
|
||||
import { getLanguageByCode } from '~/tools/language';
|
||||
import {
|
||||
@@ -92,6 +93,11 @@ function App(
|
||||
};
|
||||
}, [props.pageProps]);
|
||||
|
||||
const { setInitialPackageAttributes } = usePackageAttributesStore();
|
||||
useEffect(() => {
|
||||
setInitialPackageAttributes(props.pageProps.packageAttributes);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<CommonHead />
|
||||
|
||||
@@ -130,6 +130,7 @@ export default function CustomizationPage({
|
||||
color: 'green',
|
||||
icon: <IconCheck />,
|
||||
});
|
||||
form.resetDirty();
|
||||
},
|
||||
onError() {
|
||||
updateNotification({
|
||||
@@ -193,12 +194,12 @@ export default function CustomizationPage({
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (!form.isValid()) {
|
||||
form.validate();
|
||||
return;
|
||||
}
|
||||
|
||||
handleSubmit(form.values);
|
||||
}}
|
||||
disabled={!form.isValid()}
|
||||
loading={isLoading}
|
||||
color="green"
|
||||
>
|
||||
|
||||
@@ -11,9 +11,11 @@ import {
|
||||
} from '@mantine/core';
|
||||
import { createFormContext } from '@mantine/form';
|
||||
import { IconArrowLeft } from '@tabler/icons-react';
|
||||
import { changeLanguage } from 'i18next';
|
||||
import { GetServerSideProps } from 'next';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import Head from 'next/head';
|
||||
import { useRouter } from 'next/router';
|
||||
import { forwardRef } from 'react';
|
||||
import { z } from 'zod';
|
||||
import { AccessibilitySettings } from '~/components/User/Preferences/AccessibilitySettings';
|
||||
@@ -76,9 +78,10 @@ const SettingsComponent = ({
|
||||
country: language.country,
|
||||
}));
|
||||
|
||||
const { t } = useTranslation(['user/preferences', 'common']);
|
||||
const { t, i18n } = useTranslation(['user/preferences', 'common']);
|
||||
|
||||
const { i18nZodResolver } = useI18nZodResolver();
|
||||
const { pathname, query, asPath, push } = useRouter();
|
||||
|
||||
const form = useForm({
|
||||
initialValues: {
|
||||
@@ -105,7 +108,22 @@ const SettingsComponent = ({
|
||||
});
|
||||
|
||||
const handleSubmit = (values: z.infer<typeof updateSettingsValidationSchema>) => {
|
||||
mutate(values);
|
||||
mutate(values, {
|
||||
onSuccess: () => {
|
||||
if (values.language !== settings.language) {
|
||||
i18n.changeLanguage(values.language).then(() => {
|
||||
push(
|
||||
{
|
||||
pathname,
|
||||
query,
|
||||
},
|
||||
asPath,
|
||||
{ locale: values.language }
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -157,7 +157,16 @@ export const userRouter = createTRPCRouter({
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
await db
|
||||
.update(userSettings)
|
||||
.set(input)
|
||||
.set({
|
||||
autoFocusSearch: input.autoFocusSearch,
|
||||
defaultBoard: input.defaultBoard,
|
||||
disablePingPulse: input.disablePingPulse,
|
||||
firstDayOfWeek: input.firstDayOfWeek,
|
||||
language: input.language,
|
||||
openSearchInNewTab: input.openSearchInNewTab,
|
||||
replacePingWithIcons: input.replaceDotsWithIcons,
|
||||
searchTemplate: input.searchTemplate,
|
||||
})
|
||||
.where(eq(userSettings.userId, ctx.session?.user?.id));
|
||||
}),
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ export const createBoardSchemaValidation = z.object({
|
||||
|
||||
export const boardCustomizationSchema = z.object({
|
||||
access: z.object({
|
||||
allowGuests: z.boolean()
|
||||
allowGuests: z.boolean(),
|
||||
}),
|
||||
layout: z.object({
|
||||
leftSidebarEnabled: z.boolean(),
|
||||
@@ -20,7 +20,7 @@ export const boardCustomizationSchema = z.object({
|
||||
lg: z.number().min(5).max(20),
|
||||
}),
|
||||
pageMetadata: z.object({
|
||||
pageTitle: z.string().min(1),
|
||||
pageTitle: z.string(),
|
||||
metaTitle: z.string(),
|
||||
logoSrc: z.string(),
|
||||
faviconSrc: z.string(),
|
||||
|
||||
@@ -30,6 +30,10 @@ const definition = defineWidget({
|
||||
type: 'switch',
|
||||
defaultValue: false,
|
||||
},
|
||||
useRadarrv5: {
|
||||
type: 'switch',
|
||||
defaultValue: false,
|
||||
},
|
||||
radarrReleaseType: {
|
||||
type: 'select',
|
||||
defaultValue: 'inCinemas',
|
||||
|
||||
Reference in New Issue
Block a user