mirror of
https://github.com/ajnart/homarr.git
synced 2025-11-14 17:26:26 +01:00
🚧 Add search engine to user preferences
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
import { Stack, Switch } from '@mantine/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useFormContext } from '~/pages/user/preferences';
|
||||
import { useUserPreferencesFormContext } from '~/pages/user/preferences';
|
||||
|
||||
export const AccessibilitySettings = () => {
|
||||
const { t } = useTranslation('user/preferences');
|
||||
|
||||
const form = useFormContext();
|
||||
const form = useUserPreferencesFormContext();
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
import { Alert, Paper, SegmentedControl, Space, Stack, TextInput, Title } from '@mantine/core';
|
||||
import { IconInfoCircle } from '@tabler/icons-react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { ChangeEventHandler, useState } from 'react';
|
||||
|
||||
import { useConfigContext } from '../../../../config/provider';
|
||||
import { useConfigStore } from '../../../../config/store';
|
||||
import {
|
||||
CommonSearchEngineCommonSettingsType,
|
||||
SearchEngineCommonSettingsType,
|
||||
} from '../../../../types/settings';
|
||||
import { SearchNewTabSwitch } from './SearchNewTabSwitch';
|
||||
|
||||
interface Props {
|
||||
searchEngine: SearchEngineCommonSettingsType;
|
||||
}
|
||||
|
||||
export const SearchEngineSelector = ({ searchEngine }: Props) => {
|
||||
const { t } = useTranslation(['settings/general/search-engine']);
|
||||
const { updateSearchEngineConfig } = useUpdateSearchEngineConfig();
|
||||
|
||||
const [engine, setEngine] = useState(searchEngine.type);
|
||||
const [searchUrl, setSearchUrl] = useState(
|
||||
searchEngine.type === 'custom' ? searchEngine.properties.template : searchUrls.google
|
||||
);
|
||||
|
||||
const onEngineChange = (value: EngineType) => {
|
||||
setEngine(value);
|
||||
updateSearchEngineConfig(value, searchUrl);
|
||||
};
|
||||
|
||||
const onSearchUrlChange: ChangeEventHandler<HTMLInputElement> = (ev) => {
|
||||
const url = ev.currentTarget.value;
|
||||
setSearchUrl(url);
|
||||
updateSearchEngineConfig(engine, url);
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack spacing={0} mt="xs">
|
||||
<Title order={5} mb="xs">
|
||||
{t('title')}
|
||||
</Title>
|
||||
|
||||
<SegmentedControl
|
||||
fullWidth
|
||||
mb="sm"
|
||||
title={t('title') ?? undefined}
|
||||
value={engine}
|
||||
onChange={onEngineChange}
|
||||
data={searchEngineOptions}
|
||||
/>
|
||||
<Paper p="md" py="sm" mb="md" withBorder>
|
||||
<Title order={6} mb={0}>
|
||||
{t('configurationName')}
|
||||
</Title>
|
||||
|
||||
<SearchNewTabSwitch defaultValue={searchEngine.properties.openInNewTab} />
|
||||
|
||||
{engine === 'custom' && (
|
||||
<>
|
||||
<Space mb="md" />
|
||||
<TextInput
|
||||
label={t('customEngine.label')}
|
||||
name={t('configurationName') ?? undefined}
|
||||
description={t('tips.placeholderTip')}
|
||||
placeholder={t('customEngine.placeholder') ?? undefined}
|
||||
value={searchUrl}
|
||||
onChange={onSearchUrlChange}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Paper>
|
||||
<Alert icon={<IconInfoCircle />} color="blue">
|
||||
{t('tips.generalTip')}
|
||||
</Alert>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
const searchEngineOptions: { label: string; value: EngineType }[] = [
|
||||
{ label: 'Google', value: 'google' },
|
||||
{ label: 'DuckDuckGo', value: 'duckDuckGo' },
|
||||
{ label: 'Bing', value: 'bing' },
|
||||
{ label: 'Custom', value: 'custom' },
|
||||
];
|
||||
|
||||
export const searchUrls: { [key in CommonSearchEngineCommonSettingsType['type']]: string } = {
|
||||
google: 'https://google.com/search?q=',
|
||||
duckDuckGo: 'https://duckduckgo.com/?q=',
|
||||
bing: 'https://bing.com/search?q=',
|
||||
};
|
||||
|
||||
type EngineType = SearchEngineCommonSettingsType['type'];
|
||||
|
||||
const useUpdateSearchEngineConfig = () => {
|
||||
const { name: configName } = useConfigContext();
|
||||
const updateConfig = useConfigStore((x) => x.updateConfig);
|
||||
|
||||
if (!configName) {
|
||||
return {
|
||||
updateSearchEngineConfig: () => {},
|
||||
};
|
||||
}
|
||||
|
||||
const updateSearchEngineConfig = (engine: EngineType, searchUrl: string) => {
|
||||
updateConfig(configName, (prev) => ({
|
||||
...prev,
|
||||
settings: {
|
||||
...prev.settings,
|
||||
common: {
|
||||
...prev.settings.common,
|
||||
searchEngine:
|
||||
engine === 'custom'
|
||||
? {
|
||||
type: engine,
|
||||
properties: {
|
||||
...prev.settings.common.searchEngine.properties,
|
||||
template: searchUrl,
|
||||
},
|
||||
}
|
||||
: {
|
||||
type: engine,
|
||||
properties: {
|
||||
openInNewTab: prev.settings.common.searchEngine.properties.openInNewTab,
|
||||
enabled: prev.settings.common.searchEngine.properties.enabled,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
return {
|
||||
updateSearchEngineConfig,
|
||||
};
|
||||
};
|
||||
@@ -1,45 +0,0 @@
|
||||
import { Switch } from '@mantine/core';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { useConfigContext } from '../../../../config/provider';
|
||||
import { useConfigStore } from '../../../../config/store';
|
||||
import { SearchEngineCommonSettingsType } from '../../../../types/settings';
|
||||
|
||||
interface SearchNewTabSwitchProps {
|
||||
defaultValue: boolean | undefined;
|
||||
}
|
||||
|
||||
export function SearchNewTabSwitch({ defaultValue }: SearchNewTabSwitchProps) {
|
||||
const { t } = useTranslation('settings/general/search-engine');
|
||||
const { name: configName } = useConfigContext();
|
||||
const updateConfig = useConfigStore((x) => x.updateConfig);
|
||||
|
||||
const [openInNewTab, setOpenInNewTab] = useState<boolean>(defaultValue ?? true);
|
||||
|
||||
if (!configName) return null;
|
||||
|
||||
const toggleOpenInNewTab = () => {
|
||||
setOpenInNewTab(!openInNewTab);
|
||||
updateConfig(configName, (prev) => ({
|
||||
...prev,
|
||||
settings: {
|
||||
...prev.settings,
|
||||
common: {
|
||||
...prev.settings.common,
|
||||
searchEngine: {
|
||||
...prev.settings.common.searchEngine,
|
||||
properties: {
|
||||
...prev.settings.common.searchEngine.properties,
|
||||
openInNewTab: !openInNewTab,
|
||||
},
|
||||
} as SearchEngineCommonSettingsType,
|
||||
},
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<Switch checked={openInNewTab} onChange={toggleOpenInNewTab} label={t('searchNewTab.label')} />
|
||||
);
|
||||
}
|
||||
60
src/components/User/Preferences/SearchEngineSelector.tsx
Normal file
60
src/components/User/Preferences/SearchEngineSelector.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import { Paper, SegmentedControl, Stack, Switch, TextInput } from '@mantine/core';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useMemo } from 'react';
|
||||
import { useUserPreferencesFormContext } from '~/pages/user/preferences';
|
||||
|
||||
const searchEngineOptions = [
|
||||
{ label: 'Google', value: 'https://google.com/search?q=%s' },
|
||||
{ label: 'DuckDuckGo', value: 'https://duckduckgo.com/?q=%s' },
|
||||
{ label: 'Bing', value: 'https://bing.com/search?q=%s' },
|
||||
{ value: 'custom' },
|
||||
] as const;
|
||||
|
||||
const useSegmentData = () => {
|
||||
const { t } = useTranslation('user/preferences');
|
||||
return searchEngineOptions.map((option) => ({
|
||||
label: option.value === 'custom' ? t('searchEngine.custom') : option.label,
|
||||
value: option.value,
|
||||
}));
|
||||
};
|
||||
|
||||
export const SearchEngineSelector = () => {
|
||||
const { t } = useTranslation('user/preferences');
|
||||
const form = useUserPreferencesFormContext();
|
||||
const segmentData = useSegmentData();
|
||||
const segmentValue = useMemo(
|
||||
() =>
|
||||
searchEngineOptions.find((x) => x.value === form.values.searchTemplate)?.value ?? 'custom',
|
||||
[form.values.searchTemplate]
|
||||
);
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<SegmentedControl
|
||||
fullWidth
|
||||
data={segmentData}
|
||||
value={segmentValue}
|
||||
onChange={(v: typeof segmentValue) => {
|
||||
v === 'custom'
|
||||
? form.setFieldValue('searchTemplate', '')
|
||||
: form.setFieldValue('searchTemplate', v);
|
||||
}}
|
||||
/>
|
||||
<Paper p="md" py="sm" mb="md" withBorder>
|
||||
<Stack spacing="sm">
|
||||
<Switch
|
||||
label={t('searchEngine.newTab.label')}
|
||||
{...form.getInputProps('openSearchInNewTab', { type: 'checkbox' })}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
label={t('searchEngine.template.label')}
|
||||
description={t('searchEngine.template.description')}
|
||||
inputWrapperOrder={['label', 'input', 'description', 'error']}
|
||||
{...form.getInputProps('searchTemplate')}
|
||||
/>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Autocomplete, Group, Kbd, Modal, Text, Tooltip, useMantineTheme } from '@mantine/core';
|
||||
import { useDisclosure, useHotkeys, useMediaQuery } from '@mantine/hooks';
|
||||
import { Autocomplete, Group, Text, useMantineTheme } from '@mantine/core';
|
||||
import { useDisclosure, useHotkeys } from '@mantine/hooks';
|
||||
import {
|
||||
IconBrandYoutube,
|
||||
IconDownload,
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
} from '@tabler/icons-react';
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { ReactNode, forwardRef, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { ReactNode, forwardRef, useMemo, useRef, useState } from 'react';
|
||||
import { useConfigContext } from '~/config/provider';
|
||||
import { api } from '~/utils/api';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user