diff --git a/Dockerfile b/Dockerfile index 9d2244dd1..af28768ba 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,6 @@ -FROM node:16-alpine +FROM ghcr.io/linuxserver/baseimage-alpine:3.16 WORKDIR /app -RUN apk add tzdata - ENV NEXT_TELEMETRY_DISABLED 1 ENV NODE_ENV production diff --git a/README.md b/README.md index 4bc288f5b..9699df517 100644 --- a/README.md +++ b/README.md @@ -64,9 +64,9 @@ If you have any questions about Homarr or want to share information with us, ple ## ✨ Features - Integrates with services you use. -- Search the web direcetly from your homepage. +- Search the web directly from your homepage. - Real-time status indicator for every service. -- Automatically finds icons while you type the name of a serivce. +- Automatically finds icons while you type the name of a service. - Widgets that can display all types of information. - Easy deployment with Docker. - Very light-weight and fast. diff --git a/next.config.js b/next.config.js index 59a7bd7a8..b91c26880 100644 --- a/next.config.js +++ b/next.config.js @@ -5,6 +5,9 @@ const withBundleAnalyzer = require('@next/bundle-analyzer')({ }); module.exports = withBundleAnalyzer({ + images: { + domains: ['cdn.jsdelivr.net'], + }, reactStrictMode: false, experimental: { outputStandalone: true, diff --git a/package.json b/package.json index b097dea78..434488067 100644 --- a/package.json +++ b/package.json @@ -30,31 +30,36 @@ "@dnd-kit/core": "^6.0.5", "@dnd-kit/sortable": "^7.0.1", "@dnd-kit/utilities": "^3.2.0", - "@mantine/core": "^4.2.12", - "@mantine/dates": "^4.2.12", - "@mantine/dropzone": "^4.2.12", - "@mantine/form": "^4.2.12", - "@mantine/hooks": "^4.2.12", - "@mantine/modals": "^5.0.3", - "@mantine/next": "^4.2.12", - "@mantine/notifications": "^4.2.12", - "@mantine/prism": "^4.2.12", + "@emotion/react": "^11.10.0", + "@emotion/server": "^11.10.0", + "@mantine/carousel": "^5.0.0", + "@mantine/core": "^5.0.2", + "@mantine/dates": "^5.0.2", + "@mantine/dropzone": "^5.0.2", + "@mantine/form": "^5.0.2", + "@mantine/hooks": "^5.0.2", + "@mantine/next": "^5.0.2", + "@mantine/notifications": "^5.0.2", + "@mantine/prism": "^5.0.0", "@nivo/core": "^0.79.0", "@nivo/line": "^0.79.1", "@tabler/icons": "^1.78.0", + "add": "^2.0.6", "axios": "^0.27.2", "consola": "^2.15.3", "cookies-next": "^2.1.1", "dayjs": "^1.11.4", "dockerode": "^3.3.2", + "embla-carousel-react": "^7.0.0-rc05", "framer-motion": "^6.5.1", "js-file-download": "^0.4.12", "next": "12.1.6", "prism-react-renderer": "^1.3.5", - "react": "^17.0.1", - "react-dom": "^17.0.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", "systeminformation": "^5.12.1", - "uuid": "^8.3.2" + "uuid": "^8.3.2", + "yarn": "^1.22.19" }, "devDependencies": { "@next/bundle-analyzer": "^12.1.4", diff --git a/src/components/AppShelf/AddAppShelfItem.tsx b/src/components/AppShelf/AddAppShelfItem.tsx index 54af1cebe..27777f08d 100644 --- a/src/components/AppShelf/AddAppShelfItem.tsx +++ b/src/components/AppShelf/AddAppShelfItem.tsx @@ -8,8 +8,9 @@ import { LoadingOverlay, Modal, MultiSelect, - ScrollArea, + PasswordInput, Select, + Stack, Switch, Tabs, TextInput, @@ -17,7 +18,8 @@ import { Tooltip, } from '@mantine/core'; import { useForm } from '@mantine/form'; -import { IconApps as Apps } from '@tabler/icons'; +import { useDebouncedValue } from '@mantine/hooks'; +import { IconApps } from '@tabler/icons'; import { useEffect, useState } from 'react'; import { v4 as uuidv4 } from 'uuid'; import { useDebouncedValue } from '@mantine/hooks'; @@ -38,18 +40,18 @@ export function AddItemShelfButton(props: any) { > - setOpened(true)} - > - - - - + + setOpened(true)} + > + + + ); } @@ -85,25 +87,26 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } & const [isLoading, setLoading] = useState(false); // Extract all the categories from the services in config - const categoryList = config.services.reduce((acc, cur) => { + const InitialCategories = config.services.reduce((acc, cur) => { if (cur.category && !acc.includes(cur.category)) { acc.push(cur.category); } return acc; }, [] as string[]); + const [categories, setCategories] = useState(InitialCategories); const form = useForm({ initialValues: { id: props.id ?? uuidv4(), type: props.type ?? 'Other', - category: props.category ?? undefined, + category: props.category ?? null, name: props.name ?? '', icon: props.icon ?? DEFAULT_ICON, url: props.url ?? '', - apiKey: props.apiKey ?? (undefined as unknown as string), - username: props.username ?? (undefined as unknown as string), - password: props.password ?? (undefined as unknown as string), - openedUrl: props.openedUrl ?? (undefined as unknown as string), + apiKey: props.apiKey ?? undefined, + username: props.username ?? undefined, + password: props.password ?? undefined, + openedUrl: props.openedUrl ?? undefined, status: props.status ?? ['200'], newTab: props.newTab ?? true, }, @@ -162,21 +165,21 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } &
{ - if (JSON.stringify(form.values.status) === JSON.stringify(['200'])) { - form.values.status = undefined; - } - if (form.values.newTab === true) { - form.values.newTab = undefined; + const newForm = { ...form.values }; + if (newForm.newTab === true) newForm.newTab = undefined; + if (newForm.category === null) newForm.category = undefined; + if (newForm.status.length === 1 && newForm.status[0] === '200') { + delete newForm.status; } // If service already exists, update it. - if (config.services && config.services.find((s) => s.id === form.values.id)) { + if (config.services && config.services.find((s) => s.id === newForm.id)) { setConfig({ ...config, // replace the found item by matching ID services: config.services.map((s) => { - if (s.id === form.values.id) { + if (s.id === newForm.id) { return { - ...form.values, + ...newForm, }; } return s; @@ -185,159 +188,160 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } & } else { setConfig({ ...config, - services: [...config.services, form.values], + services: [...config.services, newForm], }); } setOpened(false); form.reset(); })} > - - - - - - - - - - { - e.preventDefault(); - }} - getCreateLabel={(query) => `+ Create "${query}"`} - onCreate={(query) => {}} - {...form.getInputProps('category')} - /> - - {(form.values.type === 'Sonarr' || - form.values.type === 'Radarr' || - form.values.type === 'Lidarr' || - form.values.type === 'Overseerr' || - form.values.type === 'Readarr') && ( - <> - { - form.setFieldValue('apiKey', event.currentTarget.value); - }} - error={form.errors.apiKey && 'Invalid API key'} - /> - - Get your API key{' '} - - here. - - - - )} - {form.values.type === 'qBittorrent' && ( - <> - { - form.setFieldValue('username', event.currentTarget.value); - }} - error={form.errors.username && 'Invalid username'} - /> - { - form.setFieldValue('password', event.currentTarget.value); - }} - error={form.errors.password && 'Invalid password'} - /> - - )} - {form.values.type === 'Deluge' && ( - <> - { - form.setFieldValue('password', event.currentTarget.value); - }} - error={form.errors.password && 'Invalid password'} - /> - - )} - {form.values.type === 'Transmission' && ( - <> - { - form.setFieldValue('username', event.currentTarget.value); - }} - error={form.errors.username && 'Invalid username'} - /> - { - form.setFieldValue('password', event.currentTarget.value); - }} - error={form.errors.password && 'Invalid password'} - /> - - )} - - - - - + + + Options + Advanced options + + + + + + + + { + const item = { value: query, label: query }; + setCategories([...InitialCategories, query]); + return item; + }} + getCreateLabel={(query) => `+ Create "${query}"`} + {...form.getInputProps('category')} + /> + + {(form.values.type === 'Sonarr' || + form.values.type === 'Radarr' || + form.values.type === 'Lidarr' || + form.values.type === 'Readarr') && ( + <> + { + form.setFieldValue('apiKey', event.currentTarget.value); + }} + error={form.errors.apiKey && 'Invalid API key'} + /> + + Get your API key{' '} + + here. + + + + )} + {form.values.type === 'qBittorrent' && ( + <> + { + form.setFieldValue('username', event.currentTarget.value); + }} + error={form.errors.username && 'Invalid username'} + /> + { + form.setFieldValue('password', event.currentTarget.value); + }} + error={form.errors.password && 'Invalid password'} + /> + + )} + {form.values.type === 'Deluge' && ( + <> + { + form.setFieldValue('password', event.currentTarget.value); + }} + error={form.errors.password && 'Invalid password'} + /> + + )} + {form.values.type === 'Transmission' && ( + <> + { + form.setFieldValue('username', event.currentTarget.value); + }} + error={form.errors.username && 'Invalid username'} + /> + { + form.setFieldValue('password', event.currentTarget.value); + }} + error={form.errors.password && 'Invalid password'} + /> + + )} + + + + void } & defaultChecked={form.values.newTab} {...form.getInputProps('newTab')} /> - - + + diff --git a/src/components/AppShelf/AppShelf.tsx b/src/components/AppShelf/AppShelf.tsx index 2d461663f..b557ba9af 100644 --- a/src/components/AppShelf/AppShelf.tsx +++ b/src/components/AppShelf/AppShelf.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { Accordion, createStyles, Grid, Group, Paper, useMantineColorScheme } from '@mantine/core'; +import { Accordion, Grid, Paper, Stack, useMantineColorScheme } from '@mantine/core'; import { closestCenter, DndContext, @@ -18,44 +18,22 @@ import { ModuleMenu, ModuleWrapper } from '../../modules/moduleWrapper'; import { DownloadsModule } from '../../modules'; import DownloadComponent from '../../modules/downloads/DownloadsModule'; -const useStyles = createStyles((theme, _params) => ({ - item: { - overflow: 'hidden', - borderLeft: '3px solid transparent', - borderRight: '3px solid transparent', - borderBottom: '3px solid transparent', - borderRadius: '20px', - borderColor: theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[1], - marginTop: theme.spacing.md, - }, - - control: { - backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[1], - borderRadius: theme.spacing.md, - - '&:hover': { - backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[1], - }, - }, - - content: { - margin: theme.spacing.md, - }, - - label: { - overflow: 'visible', - }, -})); - const AppShelf = (props: any) => { - const { classes, cx } = useStyles(props); - const [toggledCategories, settoggledCategories] = useLocalStorage({ + const { config, setConfig } = useConfig(); + // Extract all the categories from the services in config + const categoryList = config.services.reduce((acc, cur) => { + if (cur.category && !acc.includes(cur.category)) { + acc.push(cur.category); + } + return acc; + }, [] as string[]); + + const [toggledCategories, setToggledCategories] = useLocalStorage({ key: 'app-shelf-toggled', - // This is a bit of a hack to get the 5 first categories to be toggled on by default - defaultValue: { 0: true, 1: true, 2: true, 3: true, 4: true } as Record, + // This is a bit of a hack to toggle the categories on the first load, return a string[] of the categories + defaultValue: categoryList, }); const [activeId, setActiveId] = useState(null); - const { config, setConfig } = useConfig(); const { colorScheme } = useMantineColorScheme(); const sensors = useSensors( @@ -93,15 +71,8 @@ const AppShelf = (props: any) => { setActiveId(null); } - // Extract all the categories from the services in config - const categoryList = config.services.reduce((acc, cur) => { - if (cur.category && !acc.includes(cur.category)) { - acc.push(cur.category); - } - return acc; - }, [] as string[]); - const item = (filter?: string) => { + const getItems = (filter?: string) => { // If filter is not set, return all the services without a category or a null category let filtered = config.services; if (!filter) { @@ -155,54 +126,62 @@ const AppShelf = (props: any) => { const downloadEnabled = config.modules?.[DownloadsModule.title]?.enabled ?? false; // Create an item with 0: true, 1: true, 2: true... For each category return ( - // Return one item for each category - + // TODO: Style accordion so that the bar is transparent to the user settings + settoggledCategories(idx)} + value={toggledCategories} + onChange={(state) => { + setToggledCategories(state); + }} > {categoryList.map((category, idx) => ( - - {item(category)} + + {category} + {getItems(category)} ))} {/* Return the item for all services without category */} {noCategory && noCategory.length > 0 ? ( - - {item()} + + Other + {getItems()} ) : null} {downloadEnabled ? ( - - + Your downloads + + - - - + }} + > + + + + ) : null} - + ); } return ( - - {item()} + + {getItems()} - + ); }; diff --git a/src/components/AppShelf/AppShelfItem.tsx b/src/components/AppShelf/AppShelfItem.tsx index e611166c3..6c35923e8 100644 --- a/src/components/AppShelf/AppShelfItem.tsx +++ b/src/components/AppShelf/AppShelfItem.tsx @@ -3,7 +3,6 @@ import { Card, Anchor, AspectRatio, - Image, Center, createStyles, useMantineColorScheme, @@ -12,6 +11,7 @@ import { motion } from 'framer-motion'; import { useState } from 'react'; import { useSortable } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; +import Image from 'next/image'; import { serviceItem } from '../../tools/types'; import PingComponent from '../../modules/ping/PingModule'; import AppShelfMenu from './AppShelfMenu'; @@ -121,11 +121,13 @@ export function AppShelfItem(props: any) { }} > { if (service.openedUrl) { window.open(service.openedUrl, service.newTab === false ? '_top' : '_blank'); diff --git a/src/components/AppShelf/AppShelfMenu.tsx b/src/components/AppShelf/AppShelfMenu.tsx index 78f9b10ad..ced81000d 100644 --- a/src/components/AppShelf/AppShelfMenu.tsx +++ b/src/components/AppShelf/AppShelfMenu.tsx @@ -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 { 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 { serviceItem } from '../../tools/types'; import { AddAppShelfItemForm } from './AddAppShelfItem'; @@ -23,49 +23,59 @@ export default function AppShelfMenu(props: any) { - Settings - } - // TODO: #2 Add the ability to edit the service. - onClick={() => setOpened(true)} - > - Edit - - Danger zone - { - setConfig({ - ...config, - services: config.services.filter((s) => s.id !== service.id), - }); - showNotification({ - autoClose: 5000, - title: ( - - Service {service.name} removed successfully! - - ), - color: 'green', - icon: , - message: undefined, - }); - }} - icon={} - > - Delete - + + + + + + + Settings + } + // TODO: #2 Add the ability to edit the service. + onClick={() => setOpened(true)} + > + Edit + + Danger zone + { + setConfig({ + ...config, + services: config.services.filter((s) => s.id !== service.id), + }); + showNotification({ + autoClose: 5000, + title: ( + + Service {service.name} removed successfully! + + ), + color: 'green', + icon: , + message: undefined, + }); + }} + icon={} + > + Delete + + ); diff --git a/src/components/Config/ConfigChanger.tsx b/src/components/Config/ConfigChanger.tsx index 14e8cf5e5..b2c5a3898 100644 --- a/src/components/Config/ConfigChanger.tsx +++ b/src/components/Config/ConfigChanger.tsx @@ -5,25 +5,27 @@ import { useConfig } from '../../tools/state'; export default function ConfigChanger() { const { config, loadConfig, setConfig, getConfigs } = useConfig(); - const [configList, setConfigList] = useState([] as string[]); + const [configList, setConfigList] = useState([]); + const [value, setValue] = useState(config.name); useEffect(() => { getConfigs().then((configs) => setConfigList(configs)); - // setConfig(initialConfig); }, [config]); // If configlist is empty, return a loading indicator if (configList.length === 0) { return ( -
- + +
- -
+
+ ); } + // return { loadConfig(e ?? 'default'); setCookie('config-name', e ?? 'default', { diff --git a/src/components/Config/LoadConfig.tsx b/src/components/Config/LoadConfig.tsx index 6935c1f72..75d6972fa 100644 --- a/src/components/Config/LoadConfig.tsx +++ b/src/components/Config/LoadConfig.tsx @@ -1,68 +1,18 @@ -import { Group, Text, useMantineTheme, MantineTheme } from '@mantine/core'; -import { - IconUpload as Upload, - IconPhoto as Photo, - IconX as X, - IconCheck as Check, - TablerIcon, -} from '@tabler/icons'; -import { DropzoneStatus, FullScreenDropzone } from '@mantine/dropzone'; +import { Group, Text, useMantineTheme } from '@mantine/core'; +import { IconX as X, IconCheck as Check, IconX, IconPhoto, IconUpload } from '@tabler/icons'; import { showNotification } from '@mantine/notifications'; -import { useRef } from 'react'; -import { useRouter } from 'next/router'; import { setCookie } from 'cookies-next'; +import { Dropzone } from '@mantine/dropzone'; import { useConfig } from '../../tools/state'; import { Config } from '../../tools/types'; import { migrateToIdConfig } from '../../tools/migrate'; -function getIconColor(status: DropzoneStatus, theme: MantineTheme) { - return status.accepted - ? theme.colors[theme.primaryColor][theme.colorScheme === 'dark' ? 4 : 6] - : status.rejected - ? theme.colors.red[theme.colorScheme === 'dark' ? 4 : 6] - : theme.colorScheme === 'dark' - ? theme.colors.dark[0] - : theme.colors.gray[7]; -} - -function ImageUploadIcon({ - status, - ...props -}: React.ComponentProps & { status: DropzoneStatus }) { - if (status.accepted) { - return ; - } - - if (status.rejected) { - return ; - } - - return ; -} - -export const dropzoneChildren = (status: DropzoneStatus, theme: MantineTheme) => ( - - - -
- - Drag images here or click to select files - - - Attach as many files as you like, each file should not exceed 5mb - -
-
-); - export default function LoadConfigComponent(props: any) { const { setConfig } = useConfig(); const theme = useMantineTheme(); - const router = useRouter(); - const openRef = useRef<() => void>(); return ( - { files[0].text().then((e) => { try { @@ -100,7 +50,31 @@ export default function LoadConfigComponent(props: any) { }} accept={['application/json']} > - {(status) => dropzoneChildren(status, theme)} - + + + + + Drag files here to upload a config. Support for JSON only. + + + + + + This file format is not supported. Please only upload JSON. + + + + + + + ); } diff --git a/src/components/Settings/AdvancedSettings.tsx b/src/components/Settings/AdvancedSettings.tsx index 4c7d6a50e..536675c78 100644 --- a/src/components/Settings/AdvancedSettings.tsx +++ b/src/components/Settings/AdvancedSettings.tsx @@ -1,4 +1,4 @@ -import { TextInput, Group, Button } from '@mantine/core'; +import { TextInput, Button, Stack } from '@mantine/core'; import { useForm } from '@mantine/form'; import { useConfig } from '../../tools/state'; import { ColorSelector } from './ColorSelector'; @@ -37,9 +37,9 @@ export default function TitleChanger() { }; return ( - + saveChanges(values))}> - + - + - + ); } diff --git a/src/components/Settings/AppCardWidthSelector.tsx b/src/components/Settings/AppCardWidthSelector.tsx index 945778e67..26b453cd3 100644 --- a/src/components/Settings/AppCardWidthSelector.tsx +++ b/src/components/Settings/AppCardWidthSelector.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Group, Text, Slider } from '@mantine/core'; +import { Text, Slider, Stack } from '@mantine/core'; import { useConfig } from '../../tools/state'; export function AppCardWidthSelector() { @@ -16,7 +16,7 @@ export function AppCardWidthSelector() { }; return ( - + App Width setappCardWidth(value)} /> - + ); } diff --git a/src/components/Settings/ColorSelector.tsx b/src/components/Settings/ColorSelector.tsx index e7f175b3d..68a762989 100644 --- a/src/components/Settings/ColorSelector.tsx +++ b/src/components/Settings/ColorSelector.tsx @@ -1,5 +1,5 @@ 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 { useColorTheme } from '../../tools/color'; @@ -44,51 +44,43 @@ export function ColorSelector({ type }: ColorControlProps) { }; const swatches = colors.map(({ color, swatch }) => ( - setConfigColor(color)} - key={color} - color={swatch} - size={22} - style={{ color: theme.white, cursor: 'pointer' }} - /> + + setConfigColor(color)} + color={swatch} + size={22} + style={{ cursor: 'pointer' }} + /> + )); return ( - + setOpened(false)} - transitionDuration={0} - target={ + position="left" + withArrow + > + setOpened((o) => !o)} size={22} - style={{ display: 'block', cursor: 'pointer' }} + style={{ cursor: 'pointer' }} /> - } - styles={{ - root: { - marginRight: theme.spacing.xs, - }, - body: { - 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} - > - {swatches} + + + + {swatches} + + {type[0].toUpperCase() + type.slice(1)} color diff --git a/src/components/Settings/CommonSettings.tsx b/src/components/Settings/CommonSettings.tsx index 4d55eee18..739f65869 100644 --- a/src/components/Settings/CommonSettings.tsx +++ b/src/components/Settings/CommonSettings.tsx @@ -1,4 +1,4 @@ -import { Group, Text, SegmentedControl, TextInput } from '@mantine/core'; +import { Text, SegmentedControl, TextInput, Stack } from '@mantine/core'; import { useState } from 'react'; import { useConfig } from '../../tools/state'; import { ColorSchemeSwitch } from '../ColorSchemeToggle/ColorSchemeSwitch'; @@ -24,8 +24,8 @@ export default function CommonSettings(args: any) { ); return ( - - + + Search engine Use the prefixes !yt and !t in front of your query to search on YouTube or @@ -74,13 +74,13 @@ export default function CommonSettings(args: any) { /> )} - + Upload your config file by dragging and dropping it onto the page! - + ); } diff --git a/src/components/Settings/Credits.tsx b/src/components/Settings/Credits.tsx index 1d6271479..73778b0d5 100644 --- a/src/components/Settings/Credits.tsx +++ b/src/components/Settings/Credits.tsx @@ -4,7 +4,7 @@ import { CURRENT_VERSION } from '../../../data/constants'; export default function Credits(props: any) { return ( - + component="a" href="https://github.com/ajnart/homarr" size="lg"> diff --git a/src/components/Settings/ModuleEnabler.tsx b/src/components/Settings/ModuleEnabler.tsx index 4e11e6065..61cfbac07 100644 --- a/src/components/Settings/ModuleEnabler.tsx +++ b/src/components/Settings/ModuleEnabler.tsx @@ -1,4 +1,4 @@ -import { Checkbox, Group, SimpleGrid, Title } from '@mantine/core'; +import { Checkbox, SimpleGrid, Stack, Title } from '@mantine/core'; import * as Modules from '../../modules'; import { useConfig } from '../../tools/state'; @@ -6,13 +6,13 @@ export default function ModuleEnabler(props: any) { const { config, setConfig } = useConfig(); const modules = Object.values(Modules).map((module) => module); return ( - + Module enabler - + {modules.map((module) => ( { @@ -30,6 +30,6 @@ export default function ModuleEnabler(props: any) { /> ))} - + ); } diff --git a/src/components/Settings/OpacitySelector.tsx b/src/components/Settings/OpacitySelector.tsx index f94225cd8..9e6ca1594 100644 --- a/src/components/Settings/OpacitySelector.tsx +++ b/src/components/Settings/OpacitySelector.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Group, Text, Slider } from '@mantine/core'; +import { Text, Slider, Stack } from '@mantine/core'; import { useConfig } from '../../tools/state'; export function OpacitySelector() { @@ -29,7 +29,7 @@ export function OpacitySelector() { }; return ( - + App Opacity setConfigOpacity(value)} /> - + ); } diff --git a/src/components/Settings/SettingsMenu.tsx b/src/components/Settings/SettingsMenu.tsx index fcd6d1b91..3d66ad73c 100644 --- a/src/components/Settings/SettingsMenu.tsx +++ b/src/components/Settings/SettingsMenu.tsx @@ -8,17 +8,21 @@ import Credits from './Credits'; function SettingsMenu(props: any) { return ( - - + + + Common + Customizations + + - - + + - + ); } @@ -40,18 +44,18 @@ export function SettingsMenuButton(props: any) { - setOpened(true)} - > - + + setOpened(true)} + > - - + + ); } diff --git a/src/components/Settings/ShadeSelector.tsx b/src/components/Settings/ShadeSelector.tsx index ebd55e84d..addbf0906 100644 --- a/src/components/Settings/ShadeSelector.tsx +++ b/src/components/Settings/ShadeSelector.tsx @@ -1,5 +1,14 @@ 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 { useColorTheme } from '../../tools/color'; @@ -31,36 +40,42 @@ export function ShadeSelector() { }; const primarySwatches = primaryShades.map(({ swatch, shade }) => ( - setConfigShade(shade)} - key={Number(shade)} - color={swatch} - size={22} - style={{ color: theme.white, cursor: 'pointer' }} - /> + + setConfigShade(shade)} + color={swatch} + size={22} + style={{ cursor: 'pointer' }} + /> + )); const secondarySwatches = secondaryShades.map(({ swatch, shade }) => ( - setConfigShade(shade)} - key={Number(shade)} - color={swatch} - size={22} - style={{ color: theme.white, cursor: 'pointer' }} - /> + + setConfigShade(shade)} + color={swatch} + size={22} + style={{ cursor: 'pointer' }} + /> + )); return ( - + setOpened(false)} - transitionDuration={0} - target={ + position="left" + withArrow + > + - } - styles={{ - root: { - marginRight: theme.spacing.xs, - }, - body: { - 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} - > - - {primarySwatches} - {secondarySwatches} - + + + + + {primarySwatches} + {secondarySwatches} + + + Shade diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx index be3120a8d..7d675f5a8 100644 --- a/src/components/layout/Header.tsx +++ b/src/components/layout/Header.tsx @@ -1,5 +1,4 @@ import { Box, createStyles, Group, Header as Head } from '@mantine/core'; -import { useBooleanToggle } from '@mantine/hooks'; import { AddItemShelfButton } from '../AppShelf/AddAppShelfItem'; import DockerMenuButton from '../../modules/docker/DockerModule'; @@ -23,9 +22,7 @@ const useStyles = createStyles((theme) => ({ })); export function Header(props: any) { - const [opened, toggleOpened] = useBooleanToggle(false); const { classes, cx } = useStyles(); - const [hidden, toggleHidden] = useBooleanToggle(true); return ( diff --git a/src/components/layout/Layout.tsx b/src/components/layout/Layout.tsx index 0df7c4374..d91a3122d 100644 --- a/src/components/layout/Layout.tsx +++ b/src/components/layout/Layout.tsx @@ -18,6 +18,7 @@ export default function Layout({ children, style }: any) { return ( } navbar={widgetPosition ? : undefined} aside={widgetPosition ? undefined :