mirror of
https://github.com/ajnart/homarr.git
synced 2025-11-17 02:31:18 +01:00
@@ -57,7 +57,12 @@ export default function ConfigChanger() {
|
||||
size="lg"
|
||||
radius="md"
|
||||
>
|
||||
<Notification loading title={t('configSelect.loadingNew')} radius="md" disallowClose>
|
||||
<Notification
|
||||
loading
|
||||
title={t('configSelect.loadingNew')}
|
||||
radius="md"
|
||||
withCloseButton={false}
|
||||
>
|
||||
{t('configSelect.pleaseWait')}
|
||||
</Notification>
|
||||
</Dialog>
|
||||
|
||||
@@ -29,7 +29,7 @@ export const GenericAvailableElementType = ({
|
||||
: image;
|
||||
|
||||
return (
|
||||
<Grid.Col span={3}>
|
||||
<Grid.Col span="auto">
|
||||
<Card style={{ height: '100%' }}>
|
||||
<Stack justify="space-between" style={{ height: '100%' }}>
|
||||
<Stack spacing="xs">
|
||||
|
||||
@@ -19,7 +19,7 @@ export const AvailableStaticTypes = ({ onClickBack }: AvailableStaticTypesProps)
|
||||
they don't integrate with any apps and their content never changes.
|
||||
</Text>
|
||||
|
||||
<Grid>
|
||||
<Grid grow>
|
||||
<GenericAvailableElementType
|
||||
name="Static Text"
|
||||
description="Display a fixed string on your dashboard"
|
||||
|
||||
@@ -93,12 +93,10 @@ export const AppTile = ({ className, app }: AppTileProps) => {
|
||||
|
||||
const useStyles = createStyles((theme, _params, getRef) => ({
|
||||
image: {
|
||||
ref: getRef('image'),
|
||||
maxHeight: '90%',
|
||||
maxWidth: '90%',
|
||||
},
|
||||
appName: {
|
||||
ref: getRef('appName'),
|
||||
wordBreak: 'break-word',
|
||||
},
|
||||
button: {
|
||||
|
||||
@@ -18,12 +18,11 @@ interface GridstackStoreType {
|
||||
|
||||
export const useNamedWrapperColumnCount = (): 'small' | 'medium' | 'large' | null => {
|
||||
const mainAreaWidth = useGridstackStore((x) => x.mainAreaWidth);
|
||||
const { sm, xl } = useMantineTheme().breakpoints;
|
||||
if (!mainAreaWidth) return null;
|
||||
|
||||
if (mainAreaWidth >= xl) return 'large';
|
||||
if (mainAreaWidth >= 1400) return 'large';
|
||||
|
||||
if (mainAreaWidth >= sm) return 'medium';
|
||||
if (mainAreaWidth >= 800) return 'medium';
|
||||
|
||||
return 'small';
|
||||
};
|
||||
|
||||
@@ -18,7 +18,7 @@ export default function CommonSettings() {
|
||||
);
|
||||
}
|
||||
return (
|
||||
<ScrollArea style={{ height: height - 100 }} offsetScrollbars>
|
||||
<ScrollArea style={{ height: height - 100 }} scrollbarSize={5}>
|
||||
<Stack>
|
||||
<SearchEngineSelector searchEngine={config.settings.common.searchEngine} />
|
||||
<Space />
|
||||
|
||||
@@ -60,6 +60,7 @@ export const SearchEngineSelector = ({ searchEngine }: Props) => {
|
||||
<Space mb="md" />
|
||||
<TextInput
|
||||
label={t('customEngine.label')}
|
||||
name={t('configurationName')}
|
||||
description={t('tips.placeholderTip')}
|
||||
placeholder={t('customEngine.placeholder')}
|
||||
value={searchUrl}
|
||||
|
||||
@@ -8,7 +8,7 @@ export default function CustomizationSettings() {
|
||||
const { t } = useTranslation('settings/customization/general');
|
||||
|
||||
return (
|
||||
<ScrollArea style={{ height: height - 100 }} offsetScrollbars>
|
||||
<ScrollArea style={{ height: height - 100 }} scrollbarSize={5}>
|
||||
<Stack mt="xs" mb="md" spacing="xs">
|
||||
<Text color="dimmed">{t('text')}</Text>
|
||||
<CustomizationSettingsAccordeon />
|
||||
|
||||
@@ -41,7 +41,7 @@ export function SettingsDrawer({
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
size="xl"
|
||||
size="lg"
|
||||
padding="lg"
|
||||
position="right"
|
||||
title={<Title order={5}>{t('title')}</Title>}
|
||||
|
||||
@@ -181,7 +181,6 @@ export function Search() {
|
||||
shadow="md"
|
||||
radius="md"
|
||||
zIndex={100}
|
||||
transition="pop-top-right"
|
||||
>
|
||||
<Popover.Target>
|
||||
<Autocomplete
|
||||
@@ -297,7 +296,7 @@ export function Search() {
|
||||
setSearchEngine(item);
|
||||
showNotification({
|
||||
radius: 'lg',
|
||||
disallowClose: true,
|
||||
withCloseButton: false,
|
||||
id: 'spotlight',
|
||||
autoClose: 1000,
|
||||
icon: <ActionIcon size="sm">{item.icon}</ActionIcon>,
|
||||
|
||||
@@ -16,8 +16,8 @@ const ConfigContext = createContext<ConfigContextType>({
|
||||
name: 'unknown',
|
||||
config: undefined,
|
||||
configVersion: undefined,
|
||||
increaseVersion: () => console.error('Provider not set'),
|
||||
setConfigName: () => console.error('Provider not set'),
|
||||
increaseVersion: () => {},
|
||||
setConfigName: () => {},
|
||||
});
|
||||
|
||||
export const ConfigProvider = ({ children }: { children: ReactNode }) => {
|
||||
|
||||
1
src/constants/constants.ts
Normal file
1
src/constants/constants.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const MIN_WIDTH_MOBILE = 500;
|
||||
@@ -1,8 +1,9 @@
|
||||
import { MantineSize, useMantineTheme } from '@mantine/core';
|
||||
import { useMediaQuery } from '@mantine/hooks';
|
||||
import { MIN_WIDTH_MOBILE } from '../constants/constants';
|
||||
|
||||
export const useScreenLargerThan = (size: MantineSize | number) => {
|
||||
const { breakpoints } = useMantineTheme();
|
||||
const pixelCount = typeof size === 'string' ? breakpoints[size] : size;
|
||||
return useMediaQuery(`(min-width: ${pixelCount}px)`);
|
||||
return useMediaQuery(`(min-width: ${MIN_WIDTH_MOBILE})`);
|
||||
};
|
||||
|
||||
@@ -35,7 +35,7 @@ function sendDockerCommand(
|
||||
title: `${t(`actions.${action}.start`)} ${containerName}`,
|
||||
message: undefined,
|
||||
autoClose: false,
|
||||
disallowClose: true,
|
||||
withCloseButton: false,
|
||||
});
|
||||
axios
|
||||
.get(`/api/docker/container/${containerId}?action=${action}`)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Badge, BadgeVariant, MantineSize } from '@mantine/core';
|
||||
import { Badge, BadgeProps, MantineSize } from '@mantine/core';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import Dockerode from 'dockerode';
|
||||
|
||||
@@ -14,7 +14,7 @@ export default function ContainerState(props: ContainerStateProps) {
|
||||
const options: {
|
||||
size: MantineSize;
|
||||
radius: MantineSize;
|
||||
variant: BadgeVariant;
|
||||
variant: BadgeProps['variant'];
|
||||
} = {
|
||||
size: 'md',
|
||||
radius: 'md',
|
||||
|
||||
@@ -66,10 +66,13 @@ export default function DockerMenuButton(props: any) {
|
||||
onClose={() => setOpened(false)}
|
||||
padding="xl"
|
||||
position="right"
|
||||
size="full"
|
||||
size="100%"
|
||||
title={<ContainerActionBar selected={selection} reload={reload} />}
|
||||
transitionProps={{
|
||||
transition: 'pop',
|
||||
}}
|
||||
styles={{
|
||||
drawer: {
|
||||
content: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
},
|
||||
|
||||
@@ -14,6 +14,7 @@ import { IconSearch } from '@tabler/icons';
|
||||
import Dockerode from 'dockerode';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { MIN_WIDTH_MOBILE } from '../../constants/constants';
|
||||
import ContainerState from './ContainerState';
|
||||
|
||||
const useStyles = createStyles((theme) => ({
|
||||
@@ -34,7 +35,6 @@ export default function DockerTable({
|
||||
containers: Dockerode.ContainerInfo[];
|
||||
selection: Dockerode.ContainerInfo[];
|
||||
}) {
|
||||
const MIN_WIDTH_MOBILE = useMantineTheme().breakpoints.xs;
|
||||
const [usedContainers, setContainers] = useState<Dockerode.ContainerInfo[]>(containers);
|
||||
const { classes, cx } = useStyles();
|
||||
const [search, setSearch] = useState('');
|
||||
|
||||
@@ -221,7 +221,7 @@ function askForMedia(type: MediaType, id: number, name: string, seasons?: number
|
||||
color: 'orange',
|
||||
loading: true,
|
||||
autoClose: false,
|
||||
disallowClose: true,
|
||||
withCloseButton: false,
|
||||
icon: <IconAlertCircle />,
|
||||
});
|
||||
axios
|
||||
|
||||
@@ -56,7 +56,7 @@ const useStyles = createStyles((theme) => ({
|
||||
maxWidth: 540,
|
||||
margin: 'auto',
|
||||
marginTop: theme.spacing.xl,
|
||||
marginBottom: theme.spacing.xl * 1.5,
|
||||
marginBottom: `calc(${theme.spacing.xl} * 1.5)`,
|
||||
},
|
||||
}));
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ import { ColorScheme, ColorSchemeProvider, MantineProvider, MantineTheme } from
|
||||
import { useColorScheme, useHotkeys, useLocalStorage } from '@mantine/hooks';
|
||||
import { ModalsProvider } from '@mantine/modals';
|
||||
import Consola from 'consola';
|
||||
import { NotificationsProvider } from '@mantine/notifications';
|
||||
import { QueryClientProvider } from '@tanstack/react-query';
|
||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
||||
import { getCookie } from 'cookies-next';
|
||||
@@ -11,6 +10,7 @@ import { appWithTranslation } from 'next-i18next';
|
||||
import { AppProps } from 'next/app';
|
||||
import Head from 'next/head';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Notifications } from '@mantine/notifications';
|
||||
import 'video.js/dist/video-js.css';
|
||||
import { ChangeAppPositionModal } from '../components/Dashboard/Modals/ChangePosition/ChangeAppPositionModal';
|
||||
import { ChangeWidgetPositionModal } from '../components/Dashboard/Modals/ChangePosition/ChangeWidgetPositionModal';
|
||||
@@ -112,21 +112,20 @@ function App(
|
||||
withNormalizeCSS
|
||||
>
|
||||
<ConfigProvider>
|
||||
<NotificationsProvider limit={4} position="bottom-left">
|
||||
<ModalsProvider
|
||||
modals={{
|
||||
editApp: EditAppModal,
|
||||
selectElement: SelectElementModal,
|
||||
integrationOptions: WidgetsEditModal,
|
||||
integrationRemove: WidgetsRemoveModal,
|
||||
categoryEditModal: CategoryEditModal,
|
||||
changeAppPositionModal: ChangeAppPositionModal,
|
||||
changeIntegrationPositionModal: ChangeWidgetPositionModal,
|
||||
}}
|
||||
>
|
||||
<Component {...pageProps} />
|
||||
</ModalsProvider>
|
||||
</NotificationsProvider>
|
||||
<Notifications limit={4} position="bottom-left" />
|
||||
<ModalsProvider
|
||||
modals={{
|
||||
editApp: EditAppModal,
|
||||
selectElement: SelectElementModal,
|
||||
integrationOptions: WidgetsEditModal,
|
||||
integrationRemove: WidgetsRemoveModal,
|
||||
categoryEditModal: CategoryEditModal,
|
||||
changeAppPositionModal: ChangeAppPositionModal,
|
||||
changeIntegrationPositionModal: ChangeWidgetPositionModal,
|
||||
}}
|
||||
>
|
||||
<Component {...pageProps} />
|
||||
</ModalsProvider>
|
||||
</ConfigProvider>
|
||||
</MantineProvider>
|
||||
</ColorTheme.Provider>
|
||||
|
||||
@@ -62,7 +62,7 @@ export default function AuthenticationTitle() {
|
||||
title: t('notifications.checking.title'),
|
||||
message: t('notifications.checking.message'),
|
||||
autoClose: false,
|
||||
disallowClose: true,
|
||||
withCloseButton: false,
|
||||
});
|
||||
axios
|
||||
.post('/api/configs/tryPassword', {
|
||||
|
||||
@@ -56,7 +56,7 @@ const useStyles = createStyles((theme) => ({
|
||||
fontWeight: 900,
|
||||
fontSize: 110,
|
||||
lineHeight: 1,
|
||||
marginBottom: theme.spacing.xl * 1.5,
|
||||
marginBottom: `calc(${theme.spacing.xl} * 1.5)`,
|
||||
|
||||
[theme.fn.smallerThan('sm')]: {
|
||||
fontSize: 60,
|
||||
@@ -90,7 +90,7 @@ const useStyles = createStyles((theme) => ({
|
||||
maxWidth: 700,
|
||||
margin: 'auto',
|
||||
marginTop: theme.spacing.xl,
|
||||
marginBottom: theme.spacing.xl * 1.5,
|
||||
marginBottom: `calc(${theme.spacing.xl} * 1.5)`,
|
||||
},
|
||||
}));
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Box, Indicator, IndicatorProps, Popover } from '@mantine/core';
|
||||
import { Box, Indicator, IndicatorProps, Popover, useMantineTheme } from '@mantine/core';
|
||||
import { useDisclosure } from '@mantine/hooks';
|
||||
import { isToday } from '../../tools/isToday';
|
||||
import { MediaList } from './MediaList';
|
||||
import { MediasType } from './type';
|
||||
|
||||
@@ -22,12 +23,24 @@ export const CalendarDay = ({ date, medias }: CalendarDayProps) => {
|
||||
withinPortal
|
||||
radius="lg"
|
||||
shadow="sm"
|
||||
transition="pop"
|
||||
transitionProps={{
|
||||
transition: 'pop',
|
||||
}}
|
||||
onClose={close}
|
||||
opened={opened}
|
||||
>
|
||||
<Popover.Target>
|
||||
<Box onClick={open}>
|
||||
<Box
|
||||
onClick={open}
|
||||
sx={(theme) => ({
|
||||
margin: 5,
|
||||
backgroundColor: isToday(date)
|
||||
? theme.colorScheme === 'dark'
|
||||
? theme.colors.dark[5]
|
||||
: theme.colors.gray[0]
|
||||
: undefined,
|
||||
})}
|
||||
>
|
||||
<DayIndicator color="red" position="bottom-start" medias={medias.books}>
|
||||
<DayIndicator color="yellow" position="top-start" medias={medias.movies}>
|
||||
<DayIndicator color="blue" position="top-end" medias={medias.tvShows}>
|
||||
|
||||
@@ -72,39 +72,15 @@ function CalendarTile({ widget }: CalendarTileProps) {
|
||||
return (
|
||||
<Group grow style={{ height: '100%' }}>
|
||||
<Calendar
|
||||
m={0}
|
||||
p={0}
|
||||
month={month}
|
||||
// Should be offset 5px to the left
|
||||
style={{ position: 'relative', top: -15 }}
|
||||
onMonthChange={setMonth}
|
||||
defaultDate={new Date()}
|
||||
onPreviousMonth={setMonth}
|
||||
onNextMonth={setMonth}
|
||||
size="xs"
|
||||
locale={i18n?.resolvedLanguage ?? 'en'}
|
||||
fullWidth
|
||||
onChange={() => {}}
|
||||
firstDayOfWeek={widget.properties.sundayStart ? 'sunday' : 'monday'}
|
||||
dayStyle={(date) => ({
|
||||
margin: -1,
|
||||
backgroundColor: isToday(date)
|
||||
? colorScheme === 'dark'
|
||||
? colors.dark[5]
|
||||
: colors.gray[0]
|
||||
: undefined,
|
||||
})}
|
||||
firstDayOfWeek={widget.properties.sundayStart ? 0 : 1}
|
||||
hideWeekdays
|
||||
styles={{
|
||||
weekdayCell: {
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
},
|
||||
calendarHeader: {
|
||||
position: 'relative',
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
},
|
||||
}}
|
||||
allowLevelChange={false}
|
||||
dayClassName={(_, modifiers) => cx({ [classes.weekend]: modifiers.weekend })}
|
||||
date={month}
|
||||
hasNextLevel={false}
|
||||
renderDay={(date) => (
|
||||
<CalendarDay date={date} medias={getReleasedMediasForDate(medias, date, widget)} />
|
||||
)}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createStyles, Title, useMantineTheme } from '@mantine/core';
|
||||
import { createStyles, Title, useMantineTheme, getStylesRef } from '@mantine/core';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { DashDotCompactNetwork, DashDotInfo } from './DashDotCompactNetwork';
|
||||
import { DashDotCompactStorage } from './DashDotCompactStorage';
|
||||
@@ -77,7 +77,7 @@ const useIframeSrc = (
|
||||
);
|
||||
};
|
||||
|
||||
export const useStyles = createStyles((theme, _params, getRef) => ({
|
||||
export const useStyles = createStyles((theme, _params) => ({
|
||||
iframe: {
|
||||
flex: '1 0 auto',
|
||||
maxWidth: '100%',
|
||||
@@ -87,7 +87,7 @@ export const useStyles = createStyles((theme, _params, getRef) => ({
|
||||
colorScheme: 'light', // fixes white borders around iframe
|
||||
},
|
||||
graphTitle: {
|
||||
ref: getRef('graphTitle'),
|
||||
ref: getStylesRef('graphTitle'),
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
@@ -99,7 +99,7 @@ export const useStyles = createStyles((theme, _params, getRef) => ({
|
||||
},
|
||||
graphContainer: {
|
||||
position: 'relative',
|
||||
[`&:hover .${getRef('graphTitle')}`]: {
|
||||
[`&:hover .${getStylesRef('graphTitle')}`]: {
|
||||
opacity: 0.5,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -232,7 +232,7 @@ const fetchDashDotInfo = async (configName: string | undefined) => {
|
||||
|
||||
export const useDashDotTileStyles = createStyles((theme) => ({
|
||||
graphsContainer: {
|
||||
marginRight: theme.spacing.sm * -1, // fix because margin collapses weirdly
|
||||
marginRight: `calc(${theme.spacing.sm} * -1)`,
|
||||
},
|
||||
}));
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ interface TorrentQueueItemProps {
|
||||
|
||||
export const BitTorrrentQueueItem = ({ torrent, app }: TorrentQueueItemProps) => {
|
||||
const [popoverOpened, { open: openPopover, close: closePopover }] = useDisclosure(false);
|
||||
const MIN_WIDTH_MOBILE = useMantineTheme().breakpoints.xs;
|
||||
const theme = useMantineTheme();
|
||||
const { width } = useElementSize();
|
||||
const { t } = useTranslation('modules/torrents-status');
|
||||
|
||||
@@ -75,17 +75,17 @@ export const BitTorrrentQueueItem = ({ torrent, app }: TorrentQueueItemProps) =>
|
||||
<td>
|
||||
<Text size="xs">{humanFileSize(size, false)}</Text>
|
||||
</td>
|
||||
{width > MIN_WIDTH_MOBILE && (
|
||||
{theme.fn.largerThan('xs') && (
|
||||
<td>
|
||||
<Text size="xs">{downloadSpeed > 0 ? `${downloadSpeed.toFixed(1)} Mb/s` : '-'}</Text>
|
||||
</td>
|
||||
)}
|
||||
{width > MIN_WIDTH_MOBILE && (
|
||||
{theme.fn.largerThan('xs') && (
|
||||
<td>
|
||||
<Text size="xs">{uploadSpeed > 0 ? `${uploadSpeed.toFixed(1)} Mb/s` : '-'}</Text>
|
||||
</td>
|
||||
)}
|
||||
{width > MIN_WIDTH_MOBILE && (
|
||||
{theme.fn.largerThan('xs') && (
|
||||
<td>
|
||||
<Text size="xs">{torrent.eta <= 0 ? '∞' : calculateETA(torrent.eta)}</Text>
|
||||
</td>
|
||||
|
||||
@@ -17,6 +17,7 @@ import dayjs from 'dayjs';
|
||||
import duration from 'dayjs/plugin/duration';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { MIN_WIDTH_MOBILE } from '../../constants/constants';
|
||||
import { useGetDownloadClientsQueue } from '../../hooks/widgets/download-speed/useGetNetworkSpeed';
|
||||
import { NormalizedDownloadQueueResponse } from '../../types/api/downloads/queue/NormalizedDownloadQueueResponse';
|
||||
import { AppIntegrationType } from '../../types/app';
|
||||
@@ -59,7 +60,6 @@ interface TorrentTileProps {
|
||||
|
||||
function TorrentTile({ widget }: TorrentTileProps) {
|
||||
const { t } = useTranslation('modules/torrents-status');
|
||||
const MIN_WIDTH_MOBILE = useMantineTheme().breakpoints.xs;
|
||||
const { width } = useElementSize();
|
||||
|
||||
const {
|
||||
|
||||
@@ -28,6 +28,7 @@ import { defineWidget } from '../helper';
|
||||
import { IWidget } from '../widgets';
|
||||
import { UsenetHistoryList } from './UsenetHistoryList';
|
||||
import { UsenetQueueList } from './UsenetQueueList';
|
||||
import { MIN_WIDTH_MOBILE } from '../../constants/constants';
|
||||
|
||||
dayjs.extend(duration);
|
||||
|
||||
@@ -59,7 +60,6 @@ function UseNetTile({ widget }: UseNetTileProps) {
|
||||
config?.apps.filter((x) => x.integration && downloadAppTypes.includes(x.integration.type)) ??
|
||||
[];
|
||||
const { ref, width, height } = useElementSize();
|
||||
const MIN_WIDTH_MOBILE = useMantineTheme().breakpoints.xs;
|
||||
|
||||
const [selectedAppId, setSelectedApp] = useState<string | null>(downloadApps[0]?.id);
|
||||
const { data } = useGetUsenetInfo({ appId: selectedAppId! });
|
||||
|
||||
@@ -128,7 +128,7 @@ export const UsenetHistoryList: FunctionComponent<UsenetHistoryListProps> = ({ a
|
||||
position="center"
|
||||
mt="md"
|
||||
total={totalPages}
|
||||
page={page}
|
||||
value={page}
|
||||
onChange={setPage}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -35,7 +35,7 @@ const PAGE_SIZE = 13;
|
||||
export const UsenetQueueList: FunctionComponent<UsenetQueueListProps> = ({ appId }) => {
|
||||
const theme = useMantineTheme();
|
||||
const { t } = useTranslation('modules/usenet');
|
||||
const progressbarBreakpoint = theme.breakpoints.xs;
|
||||
const progressbarBreakpoint = parseInt(theme.breakpoints.xs, 10);
|
||||
const progressBreakpoint = 400;
|
||||
const sizeBreakpoint = 300;
|
||||
const { ref, width } = useElementSize();
|
||||
@@ -177,7 +177,7 @@ export const UsenetQueueList: FunctionComponent<UsenetQueueListProps> = ({ appId
|
||||
size="sm"
|
||||
position="center"
|
||||
total={totalPages}
|
||||
page={page}
|
||||
value={page}
|
||||
onChange={setPage}
|
||||
/>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user