🚧 Work in progress for Mantine v5

This commit is contained in:
ajnart
2022-07-26 00:51:55 +02:00
parent 7fcdb17d84
commit d4d9e5cfcb
25 changed files with 423 additions and 389 deletions

View File

@@ -8,8 +8,8 @@ import {
LoadingOverlay, LoadingOverlay,
Modal, Modal,
MultiSelect, MultiSelect,
ScrollArea,
Select, Select,
Stack,
Switch, Switch,
Tabs, Tabs,
TextInput, TextInput,
@@ -18,7 +18,7 @@ import {
} from '@mantine/core'; } from '@mantine/core';
import { useForm } from '@mantine/form'; import { useForm } from '@mantine/form';
import { useDebouncedValue } from '@mantine/hooks'; import { useDebouncedValue } from '@mantine/hooks';
import { IconApps as Apps } from '@tabler/icons'; import { IconApps } from '@tabler/icons';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { useConfig } from '../../tools/state'; import { useConfig } from '../../tools/state';
@@ -38,6 +38,7 @@ export function AddItemShelfButton(props: any) {
> >
<AddAppShelfItemForm setOpened={setOpened} /> <AddAppShelfItemForm setOpened={setOpened} />
</Modal> </Modal>
<Tooltip label="Add a service">
<ActionIcon <ActionIcon
variant="default" variant="default"
radius="md" radius="md"
@@ -46,10 +47,9 @@ export function AddItemShelfButton(props: any) {
style={props.style} style={props.style}
onClick={() => setOpened(true)} onClick={() => setOpened(true)}
> >
<Tooltip label="Add a service"> <IconApps />
<Apps />
</Tooltip>
</ActionIcon> </ActionIcon>
</Tooltip>
</> </>
); );
} }
@@ -192,10 +192,13 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } &
form.reset(); form.reset();
})} })}
> >
<Tabs grow> <Tabs defaultValue="Options">
<Tabs.Tab label="Options"> <Tabs.List grow>
<ScrollArea style={{ height: 500 }} scrollbarSize={4}> <Tabs.Tab value="Options">Options</Tabs.Tab>
<Group direction="column" grow> <Tabs.Tab value="Advanced Options">Advanced options</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value="Options">
<Stack>
<TextInput <TextInput
required required
label="Service name" label="Service name"
@@ -332,11 +335,10 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } &
/> />
</> </>
)} )}
</Group> </Stack>
</ScrollArea> </Tabs.Panel>
</Tabs.Tab> <Tabs.Panel value="Advanced Options">
<Tabs.Tab label="Advanced Options"> <Stack>
<Group direction="column" grow>
<MultiSelect <MultiSelect
required required
label="HTTP Status Codes" label="HTTP Status Codes"
@@ -354,8 +356,8 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } &
defaultChecked={form.values.newTab} defaultChecked={form.values.newTab}
{...form.getInputProps('newTab')} {...form.getInputProps('newTab')}
/> />
</Group> </Stack>
</Tabs.Tab> </Tabs.Panel>
</Tabs> </Tabs>
<Group grow position="center" mt="xl"> <Group grow position="center" mt="xl">
<Button type="submit">{props.message ?? 'Add service'}</Button> <Button type="submit">{props.message ?? 'Add service'}</Button>

View File

@@ -1,5 +1,5 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Accordion, createStyles, Grid, Group, Paper, useMantineColorScheme } from '@mantine/core'; import { Accordion, createStyles, Grid, Group, Paper, Stack, useMantineColorScheme } from '@mantine/core';
import { import {
closestCenter, closestCenter,
DndContext, DndContext,
@@ -156,7 +156,7 @@ const AppShelf = (props: any) => {
// Create an item with 0: true, 1: true, 2: true... For each category // Create an item with 0: true, 1: true, 2: true... For each category
return ( return (
// Return one item for each category // Return one item for each category
<Group grow direction="column"> <Stack>
<Accordion <Accordion
disableIconRotation disableIconRotation
classNames={classes} classNames={classes}
@@ -195,14 +195,14 @@ const AppShelf = (props: any) => {
</Accordion.Item> </Accordion.Item>
) : null} ) : null}
</Accordion> </Accordion>
</Group> </Stack>
); );
} }
return ( return (
<Group grow direction="column"> <Stack>
{item()} {item()}
<ModuleWrapper mt="xl" module={DownloadsModule} /> <ModuleWrapper mt="xl" module={DownloadsModule} />
</Group> </Stack>
); );
}; };

View File

@@ -1,7 +1,7 @@
import { Menu, Modal, Text, useMantineTheme } from '@mantine/core'; import { ActionIcon, Menu, Modal, Text, useMantineTheme } from '@mantine/core';
import { showNotification } from '@mantine/notifications'; import { showNotification } from '@mantine/notifications';
import { useState } from 'react'; import { useState } from 'react';
import { IconCheck as Check, IconEdit as Edit, IconTrash as Trash } from '@tabler/icons'; import { IconCheck as Check, IconEdit as Edit, IconMenu, IconTrash as Trash } from '@tabler/icons';
import { useConfig } from '../../tools/state'; import { useConfig } from '../../tools/state';
import { serviceItem } from '../../tools/types'; import { serviceItem } from '../../tools/types';
import { AddAppShelfItemForm } from './AddAppShelfItem'; import { AddAppShelfItemForm } from './AddAppShelfItem';
@@ -23,16 +23,26 @@ export default function AppShelfMenu(props: any) {
<AddAppShelfItemForm setOpened={setOpened} {...service} message="Save service" /> <AddAppShelfItemForm setOpened={setOpened} {...service} message="Save service" />
</Modal> </Modal>
<Menu <Menu
position="right" withinPortal
radius="md" width={150}
shadow="xl" shadow="xl"
withArrow
closeOnItemClick={false}
radius="md"
position="right"
styles={{ styles={{
body: { dropdown: {
// Add shadow and elevation to the body // Add shadow and elevation to the body
boxShadow: '0 0 14px 14px rgba(0, 0, 0, 0.05)', boxShadow: '0 0 14px 14px rgba(0, 0, 0, 0.05)',
}, },
}} }}
> >
<Menu.Target>
<ActionIcon style={{}}>
<IconMenu />
</ActionIcon>
</Menu.Target>
<Menu.Dropdown>
<Menu.Label>Settings</Menu.Label> <Menu.Label>Settings</Menu.Label>
<Menu.Item <Menu.Item
color="primary" color="primary"
@@ -66,6 +76,7 @@ export default function AppShelfMenu(props: any) {
> >
Delete Delete
</Menu.Item> </Menu.Item>
</Menu.Dropdown>
</Menu> </Menu>
</> </>
); );

View File

@@ -5,25 +5,27 @@ import { useConfig } from '../../tools/state';
export default function ConfigChanger() { export default function ConfigChanger() {
const { config, loadConfig, setConfig, getConfigs } = useConfig(); const { config, loadConfig, setConfig, getConfigs } = useConfig();
const [configList, setConfigList] = useState([] as string[]); const [configList, setConfigList] = useState<string[]>([]);
const [value, setValue] = useState(config.name);
useEffect(() => { useEffect(() => {
getConfigs().then((configs) => setConfigList(configs)); getConfigs().then((configs) => setConfigList(configs));
// setConfig(initialConfig);
}, [config]); }, [config]);
// If configlist is empty, return a loading indicator // If configlist is empty, return a loading indicator
if (configList.length === 0) { if (configList.length === 0) {
return ( return (
<Center>
<Tooltip label={"Loading your configs. This doesn't load in vercel."}> <Tooltip label={"Loading your configs. This doesn't load in vercel."}>
<Center>
<Loader /> <Loader />
</Tooltip>
</Center> </Center>
</Tooltip>
); );
} }
// return <Select data={[{ value: '1', label: '1' },]} onChange={(e) => console.log(e)} value="1" />;
return ( return (
<Select <Select
defaultValue={config.name}
label="Config loader" label="Config loader"
value={value}
defaultValue={config.name}
onChange={(e) => { onChange={(e) => {
loadConfig(e ?? 'default'); loadConfig(e ?? 'default');
setCookie('config-name', e ?? 'default', { setCookie('config-name', e ?? 'default', {

View File

@@ -6,11 +6,11 @@ import {
IconCheck as Check, IconCheck as Check,
TablerIcon, TablerIcon,
} from '@tabler/icons'; } from '@tabler/icons';
import { DropzoneStatus, FullScreenDropzone } from '@mantine/dropzone';
import { showNotification } from '@mantine/notifications'; import { showNotification } from '@mantine/notifications';
import { useRef } from 'react'; import { useRef } from 'react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { setCookie } from 'cookies-next'; import { setCookie } from 'cookies-next';
import { Dropzone } from '@mantine/dropzone';
import { useConfig } from '../../tools/state'; import { useConfig } from '../../tools/state';
import { Config } from '../../tools/types'; import { Config } from '../../tools/types';
import { migrateToIdConfig } from '../../tools/migrate'; import { migrateToIdConfig } from '../../tools/migrate';
@@ -62,7 +62,7 @@ export default function LoadConfigComponent(props: any) {
const openRef = useRef<() => void>(); const openRef = useRef<() => void>();
return ( return (
<FullScreenDropzone <Dropzone.FullScreen
onDrop={(files) => { onDrop={(files) => {
files[0].text().then((e) => { files[0].text().then((e) => {
try { try {
@@ -100,7 +100,7 @@ export default function LoadConfigComponent(props: any) {
}} }}
accept={['application/json']} accept={['application/json']}
> >
{(status) => dropzoneChildren(status, theme)} {(status: any) => dropzoneChildren(status, theme)}
</FullScreenDropzone> </Dropzone.FullScreen>
); );
} }

View File

@@ -1,4 +1,4 @@
import { TextInput, Group, Button } from '@mantine/core'; import { TextInput, Group, Button, Stack } from '@mantine/core';
import { useForm } from '@mantine/form'; import { useForm } from '@mantine/form';
import { useConfig } from '../../tools/state'; import { useConfig } from '../../tools/state';
import { ColorSelector } from './ColorSelector'; import { ColorSelector } from './ColorSelector';
@@ -37,9 +37,9 @@ export default function TitleChanger() {
}; };
return ( return (
<Group direction="column" grow mb="lg"> <Stack mb="lg">
<form onSubmit={form.onSubmit((values) => saveChanges(values))}> <form onSubmit={form.onSubmit((values) => saveChanges(values))}>
<Group grow direction="column"> <Stack>
<TextInput label="Page title" placeholder="Homarr 🦞" {...form.getInputProps('title')} /> <TextInput label="Page title" placeholder="Homarr 🦞" {...form.getInputProps('title')} />
<TextInput label="Logo" placeholder="/img/logo.png" {...form.getInputProps('logo')} /> <TextInput label="Logo" placeholder="/img/logo.png" {...form.getInputProps('logo')} />
<TextInput <TextInput
@@ -53,13 +53,13 @@ export default function TitleChanger() {
{...form.getInputProps('background')} {...form.getInputProps('background')}
/> />
<Button type="submit">Save</Button> <Button type="submit">Save</Button>
</Group> </Stack>
</form> </form>
<ColorSelector type="primary" /> <ColorSelector type="primary" />
<ColorSelector type="secondary" /> <ColorSelector type="secondary" />
<ShadeSelector /> <ShadeSelector />
<OpacitySelector /> <OpacitySelector />
<AppCardWidthSelector /> <AppCardWidthSelector />
</Group> </Stack>
); );
} }

View File

@@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { Group, Text, Slider } from '@mantine/core'; import { Group, Text, Slider, Stack } from '@mantine/core';
import { useConfig } from '../../tools/state'; import { useConfig } from '../../tools/state';
export function AppCardWidthSelector() { export function AppCardWidthSelector() {
@@ -16,7 +16,7 @@ export function AppCardWidthSelector() {
}; };
return ( return (
<Group direction="column" spacing="xs" grow> <Stack spacing="xs">
<Text>App Width</Text> <Text>App Width</Text>
<Slider <Slider
label={null} label={null}
@@ -27,6 +27,6 @@ export function AppCardWidthSelector() {
styles={{ markLabel: { fontSize: 'xx-small' } }} styles={{ markLabel: { fontSize: 'xx-small' } }}
onChange={(value) => setappCardWidth(value)} onChange={(value) => setappCardWidth(value)}
/> />
</Group> </Stack>
); );
} }

View File

@@ -1,5 +1,5 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { ColorSwatch, Group, Popover, Text, useMantineTheme } from '@mantine/core'; import { ColorSwatch, Grid, Group, Popover, Text, useMantineTheme } from '@mantine/core';
import { useConfig } from '../../tools/state'; import { useConfig } from '../../tools/state';
import { useColorTheme } from '../../tools/color'; import { useColorTheme } from '../../tools/color';
@@ -44,6 +44,7 @@ export function ColorSelector({ type }: ColorControlProps) {
}; };
const swatches = colors.map(({ color, swatch }) => ( const swatches = colors.map(({ color, swatch }) => (
<Grid.Col span={2}>
<ColorSwatch <ColorSwatch
component="button" component="button"
type="button" type="button"
@@ -51,44 +52,36 @@ export function ColorSelector({ type }: ColorControlProps) {
key={color} key={color}
color={swatch} color={swatch}
size={22} size={22}
style={{ color: theme.white, cursor: 'pointer' }} style={{ cursor: 'pointer' }}
/> />
</Grid.Col>
)); ));
return ( return (
<Group direction="row" spacing={3}> <Group>
<Popover <Popover
width={250}
withinPortal
opened={opened} opened={opened}
onClose={() => setOpened(false)} onClose={() => setOpened(false)}
transitionDuration={0} position="left"
target={ withArrow
>
<Popover.Target>
<ColorSwatch <ColorSwatch
component="button" component="button"
type="button" type="button"
color={theme.colors[configColor][6]} color={theme.colors[configColor][6]}
onClick={() => setOpened((o) => !o)} onClick={() => setOpened((o) => !o)}
size={22} size={22}
style={{ display: 'block', cursor: 'pointer' }} style={{ cursor: 'pointer' }}
/> />
} </Popover.Target>
styles={{ <Popover.Dropdown>
root: { <Grid gutter="lg" columns={14}>
marginRight: theme.spacing.xs, {swatches}
}, </Grid>
body: { </Popover.Dropdown>
width: 152,
backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[8] : theme.white,
},
arrow: {
backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[8] : theme.white,
},
}}
position="bottom"
placement="end"
withArrow
arrowSize={3}
>
<Group spacing="xs">{swatches}</Group>
</Popover> </Popover>
<Text>{type[0].toUpperCase() + type.slice(1)} color</Text> <Text>{type[0].toUpperCase() + type.slice(1)} color</Text>
</Group> </Group>

View File

@@ -1,4 +1,4 @@
import { Group, Text, SegmentedControl, TextInput } from '@mantine/core'; import { Group, Text, SegmentedControl, TextInput, Stack } from '@mantine/core';
import { useState } from 'react'; import { useState } from 'react';
import { useConfig } from '../../tools/state'; import { useConfig } from '../../tools/state';
import { ColorSchemeSwitch } from '../ColorSchemeToggle/ColorSchemeSwitch'; import { ColorSchemeSwitch } from '../ColorSchemeToggle/ColorSchemeSwitch';
@@ -24,8 +24,8 @@ export default function CommonSettings(args: any) {
); );
return ( return (
<Group direction="column" grow mb="lg"> <Stack mb="lg">
<Group grow direction="column" spacing={0}> <Stack spacing={0}>
<Text>Search engine</Text> <Text>Search engine</Text>
<Tip> <Tip>
Use the prefixes <b>!yt</b> and <b>!t</b> in front of your query to search on YouTube or Use the prefixes <b>!yt</b> and <b>!t</b> in front of your query to search on YouTube or
@@ -74,13 +74,13 @@ export default function CommonSettings(args: any) {
/> />
</> </>
)} )}
</Group> </Stack>
<ColorSchemeSwitch /> <ColorSchemeSwitch />
<WidgetsPositionSwitch /> <WidgetsPositionSwitch />
<ModuleEnabler /> <ModuleEnabler />
<ConfigChanger /> <ConfigChanger />
<SaveConfigComponent /> <SaveConfigComponent />
<Tip>Upload your config file by dragging and dropping it onto the page!</Tip> <Tip>Upload your config file by dragging and dropping it onto the page!</Tip>
</Group> </Stack>
); );
} }

View File

@@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { Group, Text, Slider } from '@mantine/core'; import { Group, Text, Slider, Stack } from '@mantine/core';
import { useConfig } from '../../tools/state'; import { useConfig } from '../../tools/state';
export function OpacitySelector() { export function OpacitySelector() {
@@ -29,7 +29,7 @@ export function OpacitySelector() {
}; };
return ( return (
<Group direction="column" spacing="xs" grow> <Stack spacing="xs">
<Text>App Opacity</Text> <Text>App Opacity</Text>
<Slider <Slider
defaultValue={config.settings.appOpacity || 100} defaultValue={config.settings.appOpacity || 100}
@@ -39,6 +39,6 @@ export function OpacitySelector() {
styles={{ markLabel: { fontSize: 'xx-small' } }} styles={{ markLabel: { fontSize: 'xx-small' } }}
onChange={(value) => setConfigOpacity(value)} onChange={(value) => setConfigOpacity(value)}
/> />
</Group> </Stack>
); );
} }

View File

@@ -8,17 +8,21 @@ import Credits from './Credits';
function SettingsMenu(props: any) { function SettingsMenu(props: any) {
return ( return (
<Tabs grow> <Tabs defaultValue="Common">
<Tabs.Tab data-autofocus label="Common"> <Tabs.List grow>
<Tabs.Tab value="Common">Common</Tabs.Tab>
<Tabs.Tab value="Customizations">Customizations</Tabs.Tab>
</Tabs.List>
<Tabs.Panel data-autofocus value="Common">
<ScrollArea style={{ height: '78vh' }} offsetScrollbars> <ScrollArea style={{ height: '78vh' }} offsetScrollbars>
<CommonSettings /> <CommonSettings />
</ScrollArea> </ScrollArea>
</Tabs.Tab> </Tabs.Panel>
<Tabs.Tab label="Customizations"> <Tabs.Panel value="Customizations">
<ScrollArea style={{ height: '78vh' }} offsetScrollbars> <ScrollArea style={{ height: '78vh' }} offsetScrollbars>
<AdvancedSettings /> <AdvancedSettings />
</ScrollArea> </ScrollArea>
</Tabs.Tab> </Tabs.Panel>
</Tabs> </Tabs>
); );
} }

View File

@@ -1,5 +1,14 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { ColorSwatch, Group, Popover, Text, useMantineTheme, MantineTheme } from '@mantine/core'; import {
ColorSwatch,
Group,
Popover,
Text,
useMantineTheme,
MantineTheme,
Stack,
Grid,
} from '@mantine/core';
import { useConfig } from '../../tools/state'; import { useConfig } from '../../tools/state';
import { useColorTheme } from '../../tools/color'; import { useColorTheme } from '../../tools/color';
@@ -31,6 +40,7 @@ export function ShadeSelector() {
}; };
const primarySwatches = primaryShades.map(({ swatch, shade }) => ( const primarySwatches = primaryShades.map(({ swatch, shade }) => (
<Grid.Col span={1}>
<ColorSwatch <ColorSwatch
component="button" component="button"
type="button" type="button"
@@ -38,11 +48,13 @@ export function ShadeSelector() {
key={Number(shade)} key={Number(shade)}
color={swatch} color={swatch}
size={22} size={22}
style={{ color: theme.white, cursor: 'pointer' }} style={{ cursor: 'pointer' }}
/> />
</Grid.Col>
)); ));
const secondarySwatches = secondaryShades.map(({ swatch, shade }) => ( const secondarySwatches = secondaryShades.map(({ swatch, shade }) => (
<Grid.Col span={1}>
<ColorSwatch <ColorSwatch
component="button" component="button"
type="button" type="button"
@@ -50,17 +62,22 @@ export function ShadeSelector() {
key={Number(shade)} key={Number(shade)}
color={swatch} color={swatch}
size={22} size={22}
style={{ color: theme.white, cursor: 'pointer' }} style={{ cursor: 'pointer' }}
/> />
</Grid.Col>
)); ));
return ( return (
<Group direction="row" spacing={3}> <Group>
<Popover <Popover
width={350}
withinPortal
opened={opened} opened={opened}
onClose={() => setOpened(false)} onClose={() => setOpened(false)}
transitionDuration={0} position="left"
target={ withArrow
>
<Popover.Target>
<ColorSwatch <ColorSwatch
component="button" component="button"
type="button" type="button"
@@ -69,27 +86,15 @@ export function ShadeSelector() {
size={22} size={22}
style={{ display: 'block', cursor: 'pointer' }} style={{ display: 'block', cursor: 'pointer' }}
/> />
} </Popover.Target>
styles={{ <Popover.Dropdown>
root: { <Stack spacing="xs">
marginRight: theme.spacing.xs, <Grid gutter="lg" columns={10}>
}, {primarySwatches}
body: { {secondarySwatches}
backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[8] : theme.white, </Grid>
}, </Stack>
arrow: { </Popover.Dropdown>
backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[8] : theme.white,
},
}}
position="bottom"
placement="end"
withArrow
arrowSize={3}
>
<Group direction="column" spacing="xs">
<Group spacing="xs">{primarySwatches}</Group>
<Group spacing="xs">{secondarySwatches}</Group>
</Group>
</Popover> </Popover>
<Text>Shade</Text> <Text>Shade</Text>
</Group> </Group>

View File

@@ -1,5 +1,4 @@
import { Box, createStyles, Group, Header as Head } from '@mantine/core'; import { Box, createStyles, Group, Header as Head } from '@mantine/core';
import { useBooleanToggle } from '@mantine/hooks';
import { AddItemShelfButton } from '../AppShelf/AddAppShelfItem'; import { AddItemShelfButton } from '../AppShelf/AddAppShelfItem';
import DockerMenuButton from '../../modules/docker/DockerModule'; import DockerMenuButton from '../../modules/docker/DockerModule';
@@ -23,9 +22,7 @@ const useStyles = createStyles((theme) => ({
})); }));
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);
return ( return (
<Head height="auto"> <Head height="auto">

View File

@@ -18,6 +18,7 @@ export default function Layout({ children, style }: any) {
return ( return (
<AppShell <AppShell
fixed={false}
header={<Header />} header={<Header />}
navbar={widgetPosition ? <Navbar /> : undefined} navbar={widgetPosition ? <Navbar /> : undefined}
aside={widgetPosition ? undefined : <Aside />} aside={widgetPosition ? undefined : <Aside />}

View File

@@ -1,16 +1,16 @@
import { Group } from '@mantine/core'; import { Group, Stack } from '@mantine/core';
import { CalendarModule, DateModule, TotalDownloadsModule, WeatherModule } from '../../modules'; import { CalendarModule, DateModule, TotalDownloadsModule, WeatherModule } from '../../modules';
import { DashdotModule } from '../../modules/dashdot'; import { DashdotModule } from '../../modules/dashdot';
import { ModuleWrapper } from '../../modules/moduleWrapper'; import { ModuleWrapper } from '../../modules/moduleWrapper';
export default function Widgets(props: any) { export default function Widgets(props: any) {
return ( return (
<Group my="sm" grow direction="column" style={{ width: 300 }}> <Stack my="sm" style={{ width: 300 }}>
<ModuleWrapper module={CalendarModule} /> <ModuleWrapper module={CalendarModule} />
<ModuleWrapper module={TotalDownloadsModule} /> <ModuleWrapper module={TotalDownloadsModule} />
<ModuleWrapper module={WeatherModule} /> <ModuleWrapper module={WeatherModule} />
<ModuleWrapper module={DateModule} /> <ModuleWrapper module={DateModule} />
<ModuleWrapper module={DashdotModule} /> <ModuleWrapper module={DashdotModule} />
</Group> </Stack>
); );
} }

View File

@@ -1,4 +1,4 @@
import { Group, Text, Title } from '@mantine/core'; import { Group, Stack, Text, Title } from '@mantine/core';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { IconClock as Clock } from '@tabler/icons'; import { IconClock as Clock } from '@tabler/icons';
@@ -34,7 +34,7 @@ export default function DateComponent(props: any) {
}, []); }, []);
return ( return (
<Group p="sm" spacing="xs" direction="column"> <Group p="sm" spacing="xs">
<Title>{dayjs(date).format(formatString)}</Title> <Title>{dayjs(date).format(formatString)}</Title>
<Text size="xl">{dayjs(date).format('dddd, MMMM D')}</Text> <Text size="xl">{dayjs(date).format('dddd, MMMM D')}</Text>
</Group> </Group>

View File

@@ -1,5 +1,4 @@
import { Button, Group, Modal, Title } from '@mantine/core'; import { Button, Group, Modal, Title } from '@mantine/core';
import { useBooleanToggle } from '@mantine/hooks';
import { showNotification, updateNotification } from '@mantine/notifications'; import { showNotification, updateNotification } from '@mantine/notifications';
import { import {
IconCheck, IconCheck,
@@ -14,6 +13,7 @@ import axios from 'axios';
import Dockerode from 'dockerode'; import Dockerode from 'dockerode';
import { tryMatchService } from '../../tools/addToHomarr'; import { tryMatchService } from '../../tools/addToHomarr';
import { AddAppShelfItemForm } from '../../components/AppShelf/AddAppShelfItem'; import { AddAppShelfItemForm } from '../../components/AppShelf/AddAppShelfItem';
import { useState } from 'react';
function sendDockerCommand( function sendDockerCommand(
action: string, action: string,
@@ -60,7 +60,7 @@ export interface ContainerActionBarProps {
} }
export default function ContainerActionBar({ selected, reload }: ContainerActionBarProps) { export default function ContainerActionBar({ selected, reload }: ContainerActionBarProps) {
const [opened, setOpened] = useBooleanToggle(false); const [opened, setOpened] = useState<boolean>(false);
return ( return (
<Group> <Group>
<Modal <Modal

View File

@@ -20,7 +20,6 @@ export default function DockerMenuButton(props: any) {
const [opened, setOpened] = useState(false); const [opened, setOpened] = useState(false);
const [containers, setContainers] = useState<Docker.ContainerInfo[]>([]); const [containers, setContainers] = useState<Docker.ContainerInfo[]>([]);
const [selection, setSelection] = useState<Docker.ContainerInfo[]>([]); const [selection, setSelection] = useState<Docker.ContainerInfo[]>([]);
const [visible, setVisible] = useState(false);
const { config } = useConfig(); const { config } = useConfig();
const moduleEnabled = config.modules?.[DockerModule.title]?.enabled ?? false; const moduleEnabled = config.modules?.[DockerModule.title]?.enabled ?? false;
@@ -32,14 +31,12 @@ export default function DockerMenuButton(props: any) {
if (!moduleEnabled) { if (!moduleEnabled) {
return; return;
} }
setVisible(true);
setTimeout(() => { setTimeout(() => {
axios axios
.get('/api/docker/containers') .get('/api/docker/containers')
.then((res) => { .then((res) => {
setContainers(res.data); setContainers(res.data);
setSelection([]); setSelection([]);
setVisible(false);
}) })
.catch(() => .catch(() =>
// Send an Error notification // Send an Error notification
@@ -61,12 +58,14 @@ export default function DockerMenuButton(props: any) {
if (containers.length < 1) return null; if (containers.length < 1) return null;
return ( return (
<> <>
<Drawer opened={opened} onClose={() => setOpened(false)} padding="xl" size="full"> <Drawer
<ContainerActionBar selected={selection} reload={reload} /> opened={opened}
<div style={{ position: 'relative' }}> onClose={() => setOpened(false)}
<LoadingOverlay transitionDuration={500} visible={visible} /> padding="xl"
size="full"
title={<ContainerActionBar selected={selection} reload={reload} />}
>
<DockerTable containers={containers} selection={selection} setSelection={setSelection} /> <DockerTable containers={containers} selection={selection} setSelection={setSelection} />
</div>
</Drawer> </Drawer>
<Group position="center"> <Group position="center">
<ActionIcon <ActionIcon

View File

@@ -101,7 +101,6 @@ export default function DockerTable({
onChange={handleSearchChange} onChange={handleSearchChange}
/> />
<Table captionSide="bottom" highlightOnHover sx={{ minWidth: 800 }} verticalSpacing="sm"> <Table captionSide="bottom" highlightOnHover sx={{ minWidth: 800 }} verticalSpacing="sm">
<caption>your docker containers</caption>
<thead> <thead>
<tr> <tr>
<th style={{ width: 40 }}> <th style={{ width: 40 }}>

View File

@@ -9,6 +9,7 @@ import {
ScrollArea, ScrollArea,
Center, Center,
Image, Image,
Stack,
} from '@mantine/core'; } from '@mantine/core';
import { IconDownload as Download } from '@tabler/icons'; import { IconDownload as Download } from '@tabler/icons';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
@@ -187,13 +188,8 @@ export default function DownloadComponent() {
); );
}); });
const easteregg = (
<Center style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<Image fit="cover" height={300} src="https://danjohnvelasco.github.io/images/empty.png" />
</Center>
);
return ( return (
<Group noWrap grow direction="column" mt="xl"> <Stack mt="xl">
<ScrollArea sx={{ height: 300 }}> <ScrollArea sx={{ height: 300 }}>
{rows.length > 0 ? ( {rows.length > 0 ? (
<Table highlightOnHover> <Table highlightOnHover>
@@ -201,9 +197,15 @@ export default function DownloadComponent() {
<tbody>{rows}</tbody> <tbody>{rows}</tbody>
</Table> </Table>
) : ( ) : (
easteregg <Center style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<Image
fit="cover"
height={300}
src="https://danjohnvelasco.github.io/images/empty.png"
/>
</Center>
)} )}
</ScrollArea> </ScrollArea>
</Group> </Stack>
); );
} }

View File

@@ -1,4 +1,4 @@
import { Text, Title, Group, useMantineTheme, Box, Card, ColorSwatch } from '@mantine/core'; import { Text, Title, Group, useMantineTheme, Box, Card, ColorSwatch, Stack } from '@mantine/core';
import { IconDownload as Download } from '@tabler/icons'; import { IconDownload as Download } from '@tabler/icons';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import axios from 'axios'; import axios from 'axios';
@@ -78,13 +78,13 @@ export default function TotalDownloadsComponent() {
if (downloadServices.length === 0) { if (downloadServices.length === 0) {
return ( return (
<Group direction="column"> <Stack>
<Title order={4}>No supported download clients found!</Title> <Title order={4}>No supported download clients found!</Title>
<Group noWrap> <Group noWrap>
<Text>Add a download service to view your current downloads...</Text> <Text>Add a download service to view your current downloads...</Text>
<AddItemShelfButton /> <AddItemShelfButton />
</Group> </Group>
</Group> </Stack>
); );
} }
@@ -101,9 +101,9 @@ export default function TotalDownloadsComponent() {
})) as Datum[]; })) as Datum[];
return ( return (
<Group noWrap direction="column" grow> <Stack>
<Title order={4}>Current download speed</Title> <Title order={4}>Current download speed</Title>
<Group direction="column"> <Stack>
<Group> <Group>
<ColorSwatch size={12} color={theme.colors.green[5]} /> <ColorSwatch size={12} color={theme.colors.green[5]} />
<Text>Download: {humanFileSize(totalDownloadSpeed)}/s</Text> <Text>Download: {humanFileSize(totalDownloadSpeed)}/s</Text>
@@ -112,7 +112,7 @@ export default function TotalDownloadsComponent() {
<ColorSwatch size={12} color={theme.colors.blue[5]} /> <ColorSwatch size={12} color={theme.colors.blue[5]} />
<Text>Upload: {humanFileSize(totalUploadSpeed)}/s</Text> <Text>Upload: {humanFileSize(totalUploadSpeed)}/s</Text>
</Group> </Group>
</Group> </Stack>
<Box <Box
style={{ style={{
height: 200, height: 200,
@@ -133,7 +133,7 @@ export default function TotalDownloadsComponent() {
<Card p="sm" radius="md" withBorder> <Card p="sm" radius="md" withBorder>
<Text size="md">{roundedSeconds} seconds ago</Text> <Text size="md">{roundedSeconds} seconds ago</Text>
<Card.Section p="sm"> <Card.Section p="sm">
<Group direction="column"> <Stack>
<Group> <Group>
<ColorSwatch size={10} color={theme.colors.green[5]} /> <ColorSwatch size={10} color={theme.colors.green[5]} />
<Text size="md">Download: {humanFileSize(Download)}</Text> <Text size="md">Download: {humanFileSize(Download)}</Text>
@@ -142,7 +142,7 @@ export default function TotalDownloadsComponent() {
<ColorSwatch size={10} color={theme.colors.blue[5]} /> <ColorSwatch size={10} color={theme.colors.blue[5]} />
<Text size="md">Upload: {humanFileSize(Upload)}</Text> <Text size="md">Upload: {humanFileSize(Upload)}</Text>
</Group> </Group>
</Group> </Stack>
</Card.Section> </Card.Section>
</Card> </Card>
); );
@@ -181,6 +181,6 @@ export default function TotalDownloadsComponent() {
]} ]}
/> />
</Box> </Box>
</Group> </Stack>
); );
} }

View File

@@ -1,4 +1,6 @@
import { import {
ActionIcon,
Box,
Button, Button,
Card, Card,
Group, Group,
@@ -8,6 +10,9 @@ import {
TextInput, TextInput,
useMantineColorScheme, useMantineColorScheme,
} from '@mantine/core'; } from '@mantine/core';
import { useHover } from '@mantine/hooks';
import { IconAdjustments } from '@tabler/icons';
import { motion } from 'framer-motion';
import { useConfig } from '../tools/state'; import { useConfig } from '../tools/state';
import { IModule } from './ModuleTypes'; import { IModule } from './ModuleTypes';
@@ -142,6 +147,8 @@ export function ModuleWrapper(props: any) {
const enabledModules = config.modules ?? {}; const enabledModules = config.modules ?? {};
// Remove 'Module' from enabled modules titles // Remove 'Module' from enabled modules titles
const isShown = enabledModules[module.title]?.enabled ?? false; const isShown = enabledModules[module.title]?.enabled ?? false;
//TODO: fix the hover problem
const { hovered, ref } = useHover();
if (!isShown) { if (!isShown) {
return null; return null;
@@ -150,6 +157,8 @@ export function ModuleWrapper(props: any) {
return ( return (
<Card <Card
{...props} {...props}
key={module.title}
ref={ref}
hidden={!isShown} hidden={!isShown}
withBorder withBorder
radius="lg" radius="lg"
@@ -161,47 +170,61 @@ export function ModuleWrapper(props: any) {
${(config.settings.appOpacity || 100) / 100}`, ${(config.settings.appOpacity || 100) / 100}`,
}} }}
> >
<ModuleMenu <Group position="apart">
module={module} <ModuleMenu module={module} hovered={hovered} />
styles={{
root: {
position: 'absolute',
top: 12,
right: 12,
},
}}
/>
<module.component /> <module.component />
</Group>
</Card> </Card>
); );
} }
export function ModuleMenu(props: any) { export function ModuleMenu(props: any) {
const { module, styles } = props; const { module, styles, hovered } = props;
const items: JSX.Element[] = getItems(module); const items: JSX.Element[] = getItems(module);
return ( return (
<> <>
{module.options && ( {module.options && (
<Menu <Menu
size="lg" withinPortal
width="lg"
shadow="xl" shadow="xl"
withArrow
closeOnItemClick={false} closeOnItemClick={false}
radius="md" radius="md"
position="left" position="left"
styles={{ styles={{
root: { dropdown: {
...props?.styles?.root,
},
body: {
// Add shadow and elevation to the body // Add shadow and elevation to the body
boxShadow: '0 0 14px 14px rgba(0, 0, 0, 0.05)', boxShadow: '0 0 14px 14px rgba(0, 0, 0, 0.05)',
}, },
}} }}
> >
<Menu.Target>
<Box
style={{
position: 'absolute',
top: 12,
right: 12,
}}
>
<motion.div
animate={{
//TODO: fix the hover problem
opacity: hovered ? 1 : 1,
}}
>
<ActionIcon>
<IconAdjustments />
</ActionIcon>
</motion.div>
</Box>
</Menu.Target>
<Menu.Dropdown>
<Menu.Label>Settings</Menu.Label> <Menu.Label>Settings</Menu.Label>
{items.map((item) => ( {items.map((item) => (
<Menu.Item key={item.key}>{item}</Menu.Item> <Menu.Item key={item.key}>{item}</Menu.Item>
))} ))}
</Menu.Dropdown>
</Menu> </Menu>
)} )}
</> </>

View File

@@ -1,5 +1,6 @@
import { Kbd, createStyles, Autocomplete } from '@mantine/core'; import { Kbd, createStyles, Autocomplete } from '@mantine/core';
import { useDebouncedValue, useForm, useHotkeys } from '@mantine/hooks'; import { useDebouncedValue, useHotkeys } from '@mantine/hooks';
import { useForm } from '@mantine/form';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { import {
IconSearch as Search, IconSearch as Search,

View File

@@ -8,7 +8,6 @@ import { NotificationsProvider } from '@mantine/notifications';
import { useHotkeys } from '@mantine/hooks'; import { useHotkeys } from '@mantine/hooks';
import { ConfigProvider } from '../tools/state'; import { ConfigProvider } from '../tools/state';
import { theme } from '../tools/theme'; import { theme } from '../tools/theme';
import { styles } from '../tools/styles';
import { ColorTheme } from '../tools/color'; import { ColorTheme } from '../tools/color';
export default function App(this: any, props: AppProps & { colorScheme: ColorScheme }) { export default function App(this: any, props: AppProps & { colorScheme: ColorScheme }) {
@@ -49,9 +48,6 @@ export default function App(this: any, props: AppProps & { colorScheme: ColorSch
primaryShade, primaryShade,
colorScheme, colorScheme,
}} }}
styles={{
...styles,
}}
withGlobalStyles withGlobalStyles
withNormalizeCSS withNormalizeCSS
> >

View File

@@ -1,21 +1,20 @@
import Document, { DocumentContext } from 'next/document'; import { createGetInitialProps } from '@mantine/next';
import { ServerStyles, createStylesServer } from '@mantine/next'; import Document, { Head, Html, Main, NextScript } from 'next/document';
const stylesServer = createStylesServer(); const getInitialProps = createGetInitialProps();
export default class _Document extends Document { export default class _Document extends Document {
static async getInitialProps(ctx: DocumentContext) { static getInitialProps = getInitialProps;
const initialProps = await Document.getInitialProps(ctx);
// Add your app specific logic here
return { render() {
...initialProps, return (
styles: ( <Html>
<> <Head />
{initialProps.styles} <body>
<ServerStyles html={initialProps.html} server={stylesServer} /> <Main />
</> <NextScript />
), </body>
}; </Html>
);
} }
} }