📱 Make the design way more responsive for mobile

This commit is contained in:
ajnart
2022-06-06 15:20:46 +02:00
parent bbb912479b
commit 00928ae709
9 changed files with 177 additions and 35 deletions

View File

@@ -1,4 +1,5 @@
import { Aside as MantineAside, Group } from '@mantine/core'; import { Aside as MantineAside, createStyles, Group } from '@mantine/core';
import { useMediaQuery } from '@mantine/hooks';
import { import {
WeatherModule, WeatherModule,
DateModule, DateModule,
@@ -8,12 +9,29 @@ import {
} from '../modules'; } from '../modules';
import { ModuleWrapper } from '../modules/moduleWrapper'; import { ModuleWrapper } from '../modules/moduleWrapper';
const useStyles = createStyles((theme) => ({
hide: {
[theme.fn.smallerThan('xs')]: {
display: 'none',
},
},
burger: {
[theme.fn.largerThan('sm')]: {
display: 'none',
},
},
}));
export default function Aside(props: any) { export default function Aside(props: any) {
const { classes, cx } = useStyles();
const matches = useMediaQuery('(min-width: 800px)');
return ( return (
<MantineAside <MantineAside
pr="md" pr="md"
hiddenBreakpoint="md" hiddenBreakpoint="sm"
hidden hidden
className={cx(classes.hide)}
style={{ style={{
border: 'none', border: 'none',
}} }}
@@ -21,6 +39,7 @@ export default function Aside(props: any) {
base: 'auto', base: 'auto',
}} }}
> >
{matches && (
<Group my="sm" grow direction="column" style={{ width: 300 }}> <Group my="sm" grow direction="column" style={{ width: 300 }}>
<ModuleWrapper module={CalendarModule} /> <ModuleWrapper module={CalendarModule} />
<ModuleWrapper module={TotalDownloadsModule} /> <ModuleWrapper module={TotalDownloadsModule} />
@@ -28,6 +47,7 @@ export default function Aside(props: any) {
<ModuleWrapper module={DateModule} /> <ModuleWrapper module={DateModule} />
<ModuleWrapper module={SystemModule} /> <ModuleWrapper module={SystemModule} />
</Group> </Group>
)}
</MantineAside> </MantineAside>
); );
} }

View File

@@ -1,9 +1,29 @@
import React from 'react'; import React from 'react';
import { createStyles, Header as Head, Group, Box } from '@mantine/core'; import {
createStyles,
Header as Head,
Group,
Box,
Burger,
Drawer,
Title,
ScrollArea,
ActionIcon,
Transition,
} from '@mantine/core';
import { useBooleanToggle } from '@mantine/hooks';
import { Logo } from './Logo'; import { Logo } from './Logo';
import SearchBar from '../modules/search/SearchModule'; import SearchBar from '../modules/search/SearchModule';
import { AddItemShelfButton } from '../AppShelf/AddAppShelfItem'; import { AddItemShelfButton } from '../AppShelf/AddAppShelfItem';
import { SettingsMenuButton } from '../Settings/SettingsMenu'; import { SettingsMenuButton } from '../Settings/SettingsMenu';
import { ModuleWrapper } from '../modules/moduleWrapper';
import {
CalendarModule,
TotalDownloadsModule,
WeatherModule,
DateModule,
SystemModule,
} from '../modules';
const HEADER_HEIGHT = 60; const HEADER_HEIGHT = 60;
@@ -13,10 +33,18 @@ const useStyles = createStyles((theme) => ({
display: 'none', display: 'none',
}, },
}, },
burger: {
[theme.fn.largerThan('sm')]: {
display: 'none',
},
},
})); }));
export function Header(props: any) { export function Header(props: any) {
const [opened, toggleOpened] = useBooleanToggle(false);
const { classes, cx } = useStyles(); const { classes, cx } = useStyles();
const [hidden, toggleHidden] = useBooleanToggle(true);
const drawerModule = CalendarModule;
return ( return (
<Head height="auto"> <Head height="auto">
@@ -28,6 +56,48 @@ export function Header(props: any) {
<SearchBar /> <SearchBar />
<SettingsMenuButton /> <SettingsMenuButton />
<AddItemShelfButton /> <AddItemShelfButton />
<ActionIcon className={classes.burger} variant="default" radius="md" size="xl">
<Burger
opened={!hidden}
onClick={(_) => {
toggleHidden();
toggleOpened();
}}
/>
</ActionIcon>
<Drawer
size="auto"
padding="xl"
position="right"
hidden={hidden}
title={<Title order={3}>Modules</Title>}
opened
onClose={() => {
toggleHidden();
}}
>
<Transition
mounted={opened}
transition="pop-top-right"
duration={300}
timingFunction="ease"
onExit={() => toggleOpened()}
>
{(styles) => (
<div style={styles}>
<ScrollArea style={{ height: '90vh' }}>
<Group my="sm" grow direction="column" style={{ width: 300 }}>
<ModuleWrapper module={drawerModule} />
<ModuleWrapper module={TotalDownloadsModule} />
<ModuleWrapper module={WeatherModule} />
<ModuleWrapper module={DateModule} />
<ModuleWrapper module={SystemModule} />
</Group>
</ScrollArea>
</div>
)}
</Transition>
</Drawer>
</Group> </Group>
</Group> </Group>
</Head> </Head>

View File

@@ -1,16 +1,37 @@
import { AppShell, createStyles } from '@mantine/core'; import { AppShell, createStyles, Group } from '@mantine/core';
import { Header } from './Header'; import { Header } from './Header';
import { Footer } from './Footer'; import { Footer } from './Footer';
import Aside from './Aside'; import Aside from './Aside';
import { ModuleWrapper } from '../modules/moduleWrapper';
import {
CalendarModule,
TotalDownloadsModule,
WeatherModule,
DateModule,
SystemModule,
} from '../modules';
const useStyles = createStyles((theme) => ({ const useStyles = createStyles((theme) => ({
main: {}, main: {},
})); }));
export default function Layout({ children, style }: any) { export default function Layout({ children, style }: any) {
const drawerContent = (
<Group my="sm" grow direction="column" style={{ width: 300 }}>
<ModuleWrapper module={CalendarModule} />
<ModuleWrapper module={TotalDownloadsModule} />
<ModuleWrapper module={WeatherModule} />
<ModuleWrapper module={DateModule} />
<ModuleWrapper module={SystemModule} />
</Group>
);
const { classes, cx } = useStyles(); const { classes, cx } = useStyles();
return ( return (
<AppShell aside={<Aside />} header={<Header />} footer={<Footer links={[]} />}> <AppShell
aside={<Aside />}
header={<Header data={drawerContent} />}
footer={<Footer links={[]} />}
>
<main <main
className={cx(classes.main)} className={cx(classes.main)}
style={{ style={{

View File

@@ -167,7 +167,7 @@ function DayComponent(props: any) {
/> />
)} )}
<Popover <Popover
position="left" position="bottom"
radius="lg" radius="lg"
shadow="xl" shadow="xl"
transition="pop" transition="pop"
@@ -176,7 +176,7 @@ function DayComponent(props: any) {
boxShadow: '0 0 14px 14px rgba(0, 0, 0, 0.1), 0 14px 11px rgba(0, 0, 0, 0.1)', boxShadow: '0 0 14px 14px rgba(0, 0, 0, 0.1), 0 14px 11px rgba(0, 0, 0, 0.1)',
}, },
}} }}
width={700} width="auto"
onClose={() => setOpened(false)} onClose={() => setOpened(false)}
opened={opened} opened={opened}
target={day} target={day}
@@ -197,12 +197,18 @@ function DayComponent(props: any) {
{index < radarrFiltered.length - 1 && <Divider variant="dashed" my="xl" />} {index < radarrFiltered.length - 1 && <Divider variant="dashed" my="xl" />}
</React.Fragment> </React.Fragment>
))} ))}
{sonarrFiltered.length > 0 && lidarrFiltered.length > 0 && (
<Divider variant="dashed" my="xl" />
)}
{lidarrFiltered.map((media: any, index: number) => ( {lidarrFiltered.map((media: any, index: number) => (
<React.Fragment key={index}> <React.Fragment key={index}>
<LidarrMediaDisplay media={media} /> <LidarrMediaDisplay media={media} />
{index < lidarrFiltered.length - 1 && <Divider variant="dashed" my="xl" />} {index < lidarrFiltered.length - 1 && <Divider variant="dashed" my="xl" />}
</React.Fragment> </React.Fragment>
))} ))}
{lidarrFiltered.length > 0 && readarrFiltered.length > 0 && (
<Divider variant="dashed" my="xl" />
)}
{readarrFiltered.map((media: any, index: number) => ( {readarrFiltered.map((media: any, index: number) => (
<React.Fragment key={index}> <React.Fragment key={index}>
<ReadarrMediaDisplay media={media} /> <ReadarrMediaDisplay media={media} />

View File

@@ -1,4 +1,14 @@
import { Image, Group, Title, Badge, Text, ActionIcon, Anchor, ScrollArea } from '@mantine/core'; import {
Image,
Group,
Title,
Badge,
Text,
ActionIcon,
Anchor,
ScrollArea,
createStyles,
} from '@mantine/core';
import { IconLink as Link } from '@tabler/icons'; import { IconLink as Link } from '@tabler/icons';
import { useConfig } from '../../../tools/state'; import { useConfig } from '../../../tools/state';
import { serviceItem } from '../../../tools/types'; import { serviceItem } from '../../../tools/types';
@@ -14,13 +24,31 @@ export interface IMedia {
episodeNumber?: number; episodeNumber?: number;
} }
const useStyles = createStyles((theme) => ({
poster: {
[theme.fn.smallerThan('sm')]: {
maxWidth: '20%',
},
[theme.fn.largerThan('sm')]: {
maxWidth: 300,
},
},
overview: {
[theme.fn.largerThan('sm')]: {
width: 400,
},
},
}));
export function MediaDisplay(props: { media: IMedia }) { export function MediaDisplay(props: { media: IMedia }) {
const { media }: { media: IMedia } = props; const { media }: { media: IMedia } = props;
const { classes, cx } = useStyles();
return ( return (
<Group position="apart"> <Group position="apart">
<Text> <Text>
{media.poster && ( {media.poster && (
<Image <Image
className={classes.poster}
style={{ style={{
float: 'right', float: 'right',
}} }}
@@ -28,12 +56,10 @@ export function MediaDisplay(props: { media: IMedia }) {
fit="cover" fit="cover"
src={media.poster} src={media.poster}
alt={media.title} alt={media.title}
width={250}
height={400}
/> />
)} )}
<Group direction="column"> <Group direction="column">
<Group noWrap mr="sm" style={{ minWidth: 400 }}> <Group noWrap mr="sm" className={classes.overview}>
<Title order={3}>{media.title}</Title> <Title order={3}>{media.title}</Title>
{media.imdbId && ( {media.imdbId && (
<Anchor href={`https://www.imdb.com/title/${media.imdbId}`} target="_blank"> <Anchor href={`https://www.imdb.com/title/${media.imdbId}`} target="_blank">
@@ -65,7 +91,7 @@ export function MediaDisplay(props: { media: IMedia }) {
)} )}
</Group> </Group>
<Group direction="column" position="apart"> <Group direction="column" position="apart">
<ScrollArea style={{ height: 250 }}>{media.overview}</ScrollArea> <ScrollArea style={{ maxHeight: 250, maxWidth: 700 }}>{media.overview}</ScrollArea>
<Group align="center" position="center" spacing="xs"> <Group align="center" position="center" spacing="xs">
{media.genres.map((genre: string, i: number) => ( {media.genres.map((genre: string, i: number) => (
<Badge size="sm" key={i}> <Badge size="sm" key={i}>

View File

@@ -4,6 +4,7 @@ import { useEffect, useState } from 'react';
import { IconClock as Clock } from '@tabler/icons'; import { IconClock as Clock } from '@tabler/icons';
import { useConfig } from '../../../tools/state'; import { useConfig } from '../../../tools/state';
import { IModule } from '../modules'; import { IModule } from '../modules';
import { useSetSafeInterval } from '../../../tools/hooks/useSetSafeInterval';
export const DateModule: IModule = { export const DateModule: IModule = {
title: 'Date', title: 'Date',
@@ -20,13 +21,14 @@ export const DateModule: IModule = {
export default function DateComponent(props: any) { export default function DateComponent(props: any) {
const [date, setDate] = useState(new Date()); const [date, setDate] = useState(new Date());
const setSafeInterval = useSetSafeInterval();
const { config } = useConfig(); const { config } = useConfig();
const isFullTime = config?.modules?.[DateModule.title]?.options?.full?.value ?? false; const isFullTime = config?.modules?.[DateModule.title]?.options?.full?.value ?? false;
const formatString = isFullTime ? 'HH:mm' : 'h:mm A'; const formatString = isFullTime ? 'HH:mm' : 'h:mm A';
// Change date on minute change // Change date on minute change
// Note: Using 10 000ms instead of 1000ms to chill a little :) // Note: Using 10 000ms instead of 1000ms to chill a little :)
useEffect(() => { useEffect(() => {
setInterval(() => { setSafeInterval(() => {
setDate(new Date()); setDate(new Date());
}, 1000 * 60); }, 1000 * 60);
}, []); }, []);

View File

@@ -6,6 +6,7 @@ import { NormalizedTorrent } from '@ctrl/shared-torrent';
import { IModule } from '../modules'; import { IModule } from '../modules';
import { useConfig } from '../../../tools/state'; import { useConfig } from '../../../tools/state';
import { AddItemShelfButton } from '../../AppShelf/AddAppShelfItem'; import { AddItemShelfButton } from '../../AppShelf/AddAppShelfItem';
import { useSetSafeInterval } from '../../../tools/hooks/useSetSafeInterval';
export const DownloadsModule: IModule = { export const DownloadsModule: IModule = {
title: 'Torrent', title: 'Torrent',
@@ -31,10 +32,10 @@ export default function DownloadComponent() {
const [delugeTorrents, setDelugeTorrents] = useState<NormalizedTorrent[]>([]); const [delugeTorrents, setDelugeTorrents] = useState<NormalizedTorrent[]>([]);
const [qBittorrentTorrents, setqBittorrentTorrents] = useState<NormalizedTorrent[]>([]); const [qBittorrentTorrents, setqBittorrentTorrents] = useState<NormalizedTorrent[]>([]);
const setSafeInterval = useSetSafeInterval();
useEffect(() => { useEffect(() => {
if (qBittorrentService) { if (qBittorrentService) {
setInterval(() => { setSafeInterval(() => {
axios axios
.post('/api/modules/downloads?dlclient=qbit', { ...qBittorrentService }) .post('/api/modules/downloads?dlclient=qbit', { ...qBittorrentService })
.then((res) => { .then((res) => {
@@ -43,7 +44,7 @@ export default function DownloadComponent() {
}, 3000); }, 3000);
} }
if (delugeService) { if (delugeService) {
setInterval(() => { setSafeInterval(() => {
axios.post('/api/modules/downloads?dlclient=deluge', { ...delugeService }).then((res) => { axios.post('/api/modules/downloads?dlclient=deluge', { ...delugeService }).then((res) => {
setDelugeTorrents(res.data.torrents); setDelugeTorrents(res.data.torrents);
}); });

View File

@@ -9,6 +9,7 @@ import { useListState } from '@mantine/hooks';
import { AddItemShelfButton } from '../../AppShelf/AddAppShelfItem'; import { AddItemShelfButton } from '../../AppShelf/AddAppShelfItem';
import { useConfig } from '../../../tools/state'; import { useConfig } from '../../../tools/state';
import { IModule } from '../modules'; import { IModule } from '../modules';
import { useSetSafeInterval } from '../../../tools/hooks/useSetSafeInterval';
/** /**
* Format bytes as human-readable text. * Format bytes as human-readable text.
@@ -74,9 +75,9 @@ export default function TotalDownloadsComponent() {
const totalDownloadSpeed = torrents.reduce((acc, torrent) => acc + torrent.downloadSpeed, 0); const totalDownloadSpeed = torrents.reduce((acc, torrent) => acc + torrent.downloadSpeed, 0);
const totalUploadSpeed = torrents.reduce((acc, torrent) => acc + torrent.uploadSpeed, 0); const totalUploadSpeed = torrents.reduce((acc, torrent) => acc + torrent.uploadSpeed, 0);
const setSafeInterval = useSetSafeInterval();
useEffect(() => { useEffect(() => {
const interval = setInterval(() => { setSafeInterval(() => {
// Get the current download speed of qBittorrent. // Get the current download speed of qBittorrent.
if (qBittorrentService) { if (qBittorrentService) {
axios axios

View File

@@ -1,16 +1,11 @@
import { import { Center, Group, RingProgress, Title, useMantineTheme } from '@mantine/core';
Center,
Group,
RingProgress,
Title,
useMantineTheme,
} from '@mantine/core';
import { IconCpu } from '@tabler/icons'; import { IconCpu } from '@tabler/icons';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import axios from 'axios'; import axios from 'axios';
import si from 'systeminformation'; import si from 'systeminformation';
import { useListState } from '@mantine/hooks'; import { useListState } from '@mantine/hooks';
import { IModule } from '../modules'; import { IModule } from '../modules';
import { useSetSafeInterval } from '../../../tools/hooks/useSetSafeInterval';
export const SystemModule: IModule = { export const SystemModule: IModule = {
title: 'System info', title: 'System info',
@@ -28,13 +23,13 @@ interface ApiResponse {
export default function SystemInfo(args: any) { export default function SystemInfo(args: any) {
const [data, setData] = useState<ApiResponse>(); const [data, setData] = useState<ApiResponse>();
const setSafeInterval = useSetSafeInterval();
// Refresh data every second // Refresh data every second
useEffect(() => { useEffect(() => {
setInterval(() => { setSafeInterval(() => {
axios.get('/api/modules/systeminfo').then((res) => setData(res.data)); axios.get('/api/modules/systeminfo').then((res) => setData(res.data));
}, 1000); }, 1000);
}, [args]); }, []);
// Update data every time data changes // Update data every time data changes
const [cpuLoadHistory, cpuLoadHistoryHandlers] = const [cpuLoadHistory, cpuLoadHistoryHandlers] =