mirror of
https://github.com/ajnart/homarr.git
synced 2025-11-11 16:05:47 +01:00
✨ Add categories!
This commit is contained in:
@@ -82,7 +82,6 @@ function MatchPort(name: string, form: any) {
|
|||||||
];
|
];
|
||||||
// Match name with portmap key
|
// Match name with portmap key
|
||||||
const port = portmap.find((p) => p.name === name);
|
const port = portmap.find((p) => p.name === name);
|
||||||
console.log('port', port);
|
|
||||||
if (port) {
|
if (port) {
|
||||||
form.setFieldValue('url', `http://localhost:${port.value}`);
|
form.setFieldValue('url', `http://localhost:${port.value}`);
|
||||||
}
|
}
|
||||||
@@ -93,10 +92,19 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } &
|
|||||||
const { config, setConfig } = useConfig();
|
const { config, setConfig } = useConfig();
|
||||||
const [isLoading, setLoading] = useState(false);
|
const [isLoading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
// 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 form = useForm({
|
const form = useForm({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
id: props.id ?? uuidv4(),
|
id: props.id ?? uuidv4(),
|
||||||
type: props.type ?? 'Other',
|
type: props.type ?? 'Other',
|
||||||
|
category: props.category ?? undefined,
|
||||||
name: props.name ?? '',
|
name: props.name ?? '',
|
||||||
icon: props.icon ?? '/favicon.svg',
|
icon: props.icon ?? '/favicon.svg',
|
||||||
url: props.url ?? '',
|
url: props.url ?? '',
|
||||||
@@ -126,6 +134,15 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } &
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Try to set const hostname to new URL(form.values.url).hostname)
|
||||||
|
// If it fails, set it to the form.values.url
|
||||||
|
let hostname = form.values.url;
|
||||||
|
try {
|
||||||
|
hostname = new URL(form.values.url).origin;
|
||||||
|
} catch (e) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Center>
|
<Center>
|
||||||
@@ -200,6 +217,21 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } &
|
|||||||
data={ServiceTypeList}
|
data={ServiceTypeList}
|
||||||
{...form.getInputProps('type')}
|
{...form.getInputProps('type')}
|
||||||
/>
|
/>
|
||||||
|
<Select
|
||||||
|
label="Category"
|
||||||
|
data={categoryList}
|
||||||
|
placeholder="Select a category or create a new one"
|
||||||
|
nothingFound="Nothing found"
|
||||||
|
searchable
|
||||||
|
clearable
|
||||||
|
creatable
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
}}
|
||||||
|
getCreateLabel={(query) => `+ Create "${query}"`}
|
||||||
|
onCreate={(query) => {}}
|
||||||
|
{...form.getInputProps('category')}
|
||||||
|
/>
|
||||||
<LoadingOverlay visible={isLoading} />
|
<LoadingOverlay visible={isLoading} />
|
||||||
{(form.values.type === 'Sonarr' ||
|
{(form.values.type === 'Sonarr' ||
|
||||||
form.values.type === 'Radarr' ||
|
form.values.type === 'Radarr' ||
|
||||||
@@ -229,7 +261,7 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } &
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
weight="bold"
|
weight="bold"
|
||||||
style={{ fontStyle: 'inherit', fontSize: 'inherit' }}
|
style={{ fontStyle: 'inherit', fontSize: 'inherit' }}
|
||||||
href={`${form.values.url}/settings/general`}
|
href={`${hostname}/settings/general`}
|
||||||
>
|
>
|
||||||
here
|
here
|
||||||
</Anchor>
|
</Anchor>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Grid } from '@mantine/core';
|
import { Grid, Group, Title } from '@mantine/core';
|
||||||
import {
|
import {
|
||||||
closestCenter,
|
closestCenter,
|
||||||
DndContext,
|
DndContext,
|
||||||
@@ -45,6 +45,23 @@ const AppShelf = (props: any) => {
|
|||||||
|
|
||||||
setActiveId(null);
|
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) => {
|
||||||
|
// If filter is not set, return all the services without a category or a null category
|
||||||
|
let filtered = config.services;
|
||||||
|
if (!filter) {
|
||||||
|
filtered = config.services.filter((e) => !e.category || e.category === null);
|
||||||
|
}
|
||||||
|
if (filter) {
|
||||||
|
filtered = config.services.filter((e) => e.category === filter);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DndContext
|
<DndContext
|
||||||
@@ -55,7 +72,7 @@ const AppShelf = (props: any) => {
|
|||||||
>
|
>
|
||||||
<SortableContext items={config.services}>
|
<SortableContext items={config.services}>
|
||||||
<Grid gutter="xl" align="center">
|
<Grid gutter="xl" align="center">
|
||||||
{config.services.map((service) => (
|
{filtered.map((service) => (
|
||||||
<Grid.Col key={service.id} span={6} xl={2} xs={4} sm={3} md={3}>
|
<Grid.Col key={service.id} span={6} xl={2} xs={4} sm={3} md={3}>
|
||||||
<SortableAppShelfItem service={service} key={service.id} id={service.id} />
|
<SortableAppShelfItem service={service} key={service.id} id={service.id} />
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
@@ -76,4 +93,33 @@ const AppShelf = (props: any) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (categoryList.length > 0) {
|
||||||
|
const noCategory = config.services.filter(
|
||||||
|
(e) => e.category === undefined || e.category === null
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
// Return one item for each category
|
||||||
|
<Group grow direction="column">
|
||||||
|
{categoryList.map((category) => (
|
||||||
|
<>
|
||||||
|
<Title order={3} key={category}>
|
||||||
|
{category}
|
||||||
|
</Title>
|
||||||
|
{item(category)}
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
{/* Return the item for all services without category */}
|
||||||
|
{noCategory && noCategory.length > 0 ? (
|
||||||
|
<>
|
||||||
|
<Title order={3}>Other</Title>
|
||||||
|
{item()}
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return item();
|
||||||
|
};
|
||||||
|
|
||||||
export default AppShelf;
|
export default AppShelf;
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export default function AppShelfMenu(props: any) {
|
|||||||
setOpened={setOpened}
|
setOpened={setOpened}
|
||||||
name={service.name}
|
name={service.name}
|
||||||
id={service.id}
|
id={service.id}
|
||||||
|
category={service.category}
|
||||||
type={service.type}
|
type={service.type}
|
||||||
url={service.url}
|
url={service.url}
|
||||||
icon={service.icon}
|
icon={service.icon}
|
||||||
@@ -47,7 +48,7 @@ export default function AppShelfMenu(props: any) {
|
|||||||
<Menu.Label>Settings</Menu.Label>
|
<Menu.Label>Settings</Menu.Label>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
color="primary"
|
color="primary"
|
||||||
icon={<Edit size={14} />}
|
icon={<Edit />}
|
||||||
// TODO: #2 Add the ability to edit the service.
|
// TODO: #2 Add the ability to edit the service.
|
||||||
onClick={() => setOpened(true)}
|
onClick={() => setOpened(true)}
|
||||||
>
|
>
|
||||||
@@ -73,7 +74,7 @@ export default function AppShelfMenu(props: any) {
|
|||||||
message: undefined,
|
message: undefined,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
icon={<Trash size={14} />}
|
icon={<Trash />}
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ export interface serviceItem {
|
|||||||
type: string;
|
type: string;
|
||||||
url: string;
|
url: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
|
category?: string;
|
||||||
apiKey?: string;
|
apiKey?: string;
|
||||||
password?: string;
|
password?: string;
|
||||||
username?: string;
|
username?: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user