2022-05-12 21:38:21 +02:00
|
|
|
import React, { useState } from 'react';
|
2022-08-02 00:19:39 +02:00
|
|
|
import { Accordion, Grid, Paper, Stack, useMantineColorScheme } from '@mantine/core';
|
2022-05-23 11:20:08 +02:00
|
|
|
import {
|
|
|
|
|
closestCenter,
|
|
|
|
|
DndContext,
|
|
|
|
|
DragOverlay,
|
|
|
|
|
MouseSensor,
|
2022-06-06 17:39:18 +02:00
|
|
|
TouchSensor,
|
2022-05-23 11:20:08 +02:00
|
|
|
useSensor,
|
|
|
|
|
useSensors,
|
|
|
|
|
} from '@dnd-kit/core';
|
2022-06-12 06:36:33 +02:00
|
|
|
import { arrayMove, SortableContext } from '@dnd-kit/sortable';
|
2022-06-06 12:18:51 +02:00
|
|
|
import { useLocalStorage } from '@mantine/hooks';
|
2022-08-22 09:50:54 +02:00
|
|
|
import { useTranslation } from 'next-i18next';
|
2022-05-02 15:09:39 +02:00
|
|
|
import { useConfig } from '../../tools/state';
|
2022-04-25 00:11:32 +02:00
|
|
|
|
2022-05-23 11:20:08 +02:00
|
|
|
import { SortableAppShelfItem, AppShelfItem } from './AppShelfItem';
|
2022-07-23 22:22:55 +02:00
|
|
|
import { ModuleMenu, ModuleWrapper } from '../../modules/moduleWrapper';
|
2022-08-25 18:10:23 +02:00
|
|
|
import { NzbModule, TorrentsModule } from '../../modules';
|
|
|
|
|
import TorrentsComponent from '../../modules/torrents/TorrentsModule';
|
2022-05-17 01:43:40 +02:00
|
|
|
|
2022-05-16 15:55:22 +02:00
|
|
|
const AppShelf = (props: any) => {
|
2022-08-02 00:19:39 +02:00
|
|
|
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({
|
2022-06-06 12:18:51 +02:00
|
|
|
key: 'app-shelf-toggled',
|
2022-08-02 00:19:39 +02:00
|
|
|
// This is a bit of a hack to toggle the categories on the first load, return a string[] of the categories
|
|
|
|
|
defaultValue: categoryList,
|
2022-06-06 12:18:51 +02:00
|
|
|
});
|
2022-05-23 11:20:08 +02:00
|
|
|
const [activeId, setActiveId] = useState(null);
|
2022-06-11 18:37:13 +02:00
|
|
|
const { colorScheme } = useMantineColorScheme();
|
2022-06-06 18:16:24 +02:00
|
|
|
|
2022-08-22 09:50:54 +02:00
|
|
|
const { t } = useTranslation('layout/app-shelf');
|
|
|
|
|
|
2022-05-23 11:20:08 +02:00
|
|
|
const sensors = useSensors(
|
2022-06-14 22:45:31 +02:00
|
|
|
useSensor(TouchSensor, {
|
|
|
|
|
activationConstraint: {
|
|
|
|
|
delay: 500,
|
|
|
|
|
tolerance: 5,
|
|
|
|
|
},
|
|
|
|
|
}),
|
2022-05-23 11:20:08 +02:00
|
|
|
useSensor(MouseSensor, {
|
|
|
|
|
// Require the mouse to move by 10 pixels before activating
|
|
|
|
|
activationConstraint: {
|
2022-06-14 22:45:31 +02:00
|
|
|
delay: 500,
|
2022-05-23 11:20:08 +02:00
|
|
|
tolerance: 5,
|
|
|
|
|
},
|
|
|
|
|
})
|
2022-05-04 07:12:22 +02:00
|
|
|
);
|
|
|
|
|
|
2022-05-23 11:20:08 +02:00
|
|
|
function handleDragStart(event: any) {
|
|
|
|
|
const { active } = event;
|
|
|
|
|
|
|
|
|
|
setActiveId(active.id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleDragEnd(event: any) {
|
|
|
|
|
const { active, over } = event;
|
|
|
|
|
|
|
|
|
|
if (active.id !== over.id) {
|
|
|
|
|
const newConfig = { ...config };
|
|
|
|
|
const activeIndex = newConfig.services.findIndex((e) => e.id === active.id);
|
|
|
|
|
const overIndex = newConfig.services.findIndex((e) => e.id === over.id);
|
|
|
|
|
newConfig.services = arrayMove(newConfig.services, activeIndex, overIndex);
|
|
|
|
|
setConfig(newConfig);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setActiveId(null);
|
|
|
|
|
}
|
2022-05-29 10:45:49 +02:00
|
|
|
|
2022-08-02 00:19:39 +02:00
|
|
|
const getItems = (filter?: string) => {
|
2022-05-29 10:45:49 +02:00
|
|
|
// 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);
|
|
|
|
|
}
|
2022-05-23 11:20:08 +02:00
|
|
|
|
2022-05-29 10:45:49 +02:00
|
|
|
return (
|
|
|
|
|
<DndContext
|
|
|
|
|
sensors={sensors}
|
|
|
|
|
collisionDetection={closestCenter}
|
|
|
|
|
onDragStart={handleDragStart}
|
|
|
|
|
onDragEnd={handleDragEnd}
|
2022-05-23 11:20:08 +02:00
|
|
|
>
|
2022-05-29 10:45:49 +02:00
|
|
|
<SortableContext items={config.services}>
|
|
|
|
|
<Grid gutter="xl" align="center">
|
|
|
|
|
{filtered.map((service) => (
|
2022-06-14 17:45:22 +00:00
|
|
|
<Grid.Col
|
|
|
|
|
key={service.id}
|
|
|
|
|
span={6}
|
|
|
|
|
xl={config.settings.appCardWidth || 2}
|
|
|
|
|
xs={4}
|
|
|
|
|
sm={3}
|
|
|
|
|
md={3}
|
|
|
|
|
>
|
2022-05-29 10:45:49 +02:00
|
|
|
<SortableAppShelfItem service={service} key={service.id} id={service.id} />
|
|
|
|
|
</Grid.Col>
|
|
|
|
|
))}
|
|
|
|
|
</Grid>
|
|
|
|
|
</SortableContext>
|
|
|
|
|
<DragOverlay
|
|
|
|
|
style={{
|
|
|
|
|
// Add a shadow to the drag overlay
|
|
|
|
|
boxShadow: '0 0 10px rgba(0, 0, 0, 0.5)',
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{activeId ? (
|
|
|
|
|
<AppShelfItem service={config.services.find((e) => e.id === activeId)} id={activeId} />
|
|
|
|
|
) : null}
|
|
|
|
|
</DragOverlay>
|
|
|
|
|
</DndContext>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (categoryList.length > 0) {
|
|
|
|
|
const noCategory = config.services.filter(
|
|
|
|
|
(e) => e.category === undefined || e.category === null
|
|
|
|
|
);
|
2022-08-25 11:07:25 +02:00
|
|
|
const downloadEnabled = config.modules?.[DownloadsModule.id]?.enabled ?? false;
|
2022-06-06 12:18:51 +02:00
|
|
|
// Create an item with 0: true, 1: true, 2: true... For each category
|
2022-05-29 10:45:49 +02:00
|
|
|
return (
|
2022-08-02 00:19:39 +02:00
|
|
|
// TODO: Style accordion so that the bar is transparent to the user settings
|
2022-07-26 00:51:55 +02:00
|
|
|
<Stack>
|
2022-06-06 12:18:51 +02:00
|
|
|
<Accordion
|
2022-08-02 00:19:39 +02:00
|
|
|
variant="separated"
|
|
|
|
|
radius="lg"
|
2022-06-06 12:18:51 +02:00
|
|
|
order={2}
|
|
|
|
|
multiple
|
2022-08-02 00:19:39 +02:00
|
|
|
value={toggledCategories}
|
|
|
|
|
onChange={(state) => {
|
2022-08-11 10:08:18 +02:00
|
|
|
setToggledCategories([...state]);
|
2022-08-02 00:19:39 +02:00
|
|
|
}}
|
2022-06-06 12:18:51 +02:00
|
|
|
>
|
2022-06-06 18:16:24 +02:00
|
|
|
{categoryList.map((category, idx) => (
|
2022-08-02 00:19:39 +02:00
|
|
|
<Accordion.Item key={category} value={idx.toString()}>
|
|
|
|
|
<Accordion.Control>{category}</Accordion.Control>
|
|
|
|
|
<Accordion.Panel>{getItems(category)}</Accordion.Panel>
|
2022-06-06 23:56:33 +02:00
|
|
|
</Accordion.Item>
|
2022-06-06 12:18:51 +02:00
|
|
|
))}
|
|
|
|
|
{/* Return the item for all services without category */}
|
|
|
|
|
{noCategory && noCategory.length > 0 ? (
|
2022-08-02 00:19:39 +02:00
|
|
|
<Accordion.Item key="Other" value="Other">
|
2022-08-25 18:10:23 +02:00
|
|
|
<Accordion.Control>Other</Accordion.Control>
|
2022-08-02 00:19:39 +02:00
|
|
|
<Accordion.Panel>{getItems()}</Accordion.Panel>
|
2022-06-06 23:56:33 +02:00
|
|
|
</Accordion.Item>
|
2022-06-06 12:18:51 +02:00
|
|
|
) : null}
|
2022-06-28 10:34:25 +02:00
|
|
|
{downloadEnabled ? (
|
2022-08-02 00:19:39 +02:00
|
|
|
<Accordion.Item key="Downloads" value="Your downloads">
|
2022-08-22 09:50:54 +02:00
|
|
|
<Accordion.Control>{t('accordions.downloads.text')}</Accordion.Control>
|
2022-08-02 00:19:39 +02:00
|
|
|
<Accordion.Panel>
|
|
|
|
|
<Paper
|
|
|
|
|
p="lg"
|
|
|
|
|
radius="lg"
|
|
|
|
|
style={{
|
|
|
|
|
background: `rgba(${colorScheme === 'dark' ? '37, 38, 43,' : '255, 255, 255,'} \
|
2022-06-11 18:37:13 +02:00
|
|
|
${(config.settings.appOpacity || 100) / 100}`,
|
2022-08-02 00:19:39 +02:00
|
|
|
borderColor: `rgba(${
|
|
|
|
|
colorScheme === 'dark' ? '37, 38, 43,' : '233, 236, 239,'
|
|
|
|
|
} \
|
2022-06-11 18:37:13 +02:00
|
|
|
${(config.settings.appOpacity || 100) / 100}`,
|
2022-08-02 00:19:39 +02:00
|
|
|
}}
|
|
|
|
|
>
|
2022-08-25 18:10:23 +02:00
|
|
|
<ModuleMenu module={TorrentsModule} />
|
|
|
|
|
<TorrentsComponent />
|
2022-08-02 00:19:39 +02:00
|
|
|
</Paper>
|
|
|
|
|
</Accordion.Panel>
|
2022-07-21 11:43:43 +02:00
|
|
|
</Accordion.Item>
|
2022-06-28 10:34:25 +02:00
|
|
|
) : null}
|
2022-06-06 12:18:51 +02:00
|
|
|
</Accordion>
|
2022-07-26 00:51:55 +02:00
|
|
|
</Stack>
|
2022-05-29 10:45:49 +02:00
|
|
|
);
|
|
|
|
|
}
|
2022-05-29 15:32:39 +02:00
|
|
|
return (
|
2022-07-26 00:51:55 +02:00
|
|
|
<Stack>
|
2022-08-02 00:19:39 +02:00
|
|
|
{getItems()}
|
2022-08-25 18:10:23 +02:00
|
|
|
<ModuleWrapper mt="xl" module={TorrentsModule} />
|
|
|
|
|
<ModuleWrapper mt="xl" module={NzbModule} />
|
2022-07-26 00:51:55 +02:00
|
|
|
</Stack>
|
2022-05-29 15:32:39 +02:00
|
|
|
);
|
2022-05-23 11:20:08 +02:00
|
|
|
};
|
2022-04-25 00:11:32 +02:00
|
|
|
|
2022-04-27 03:11:35 +02:00
|
|
|
export default AppShelf;
|