Rename sort -> type

This commit is contained in:
Thomas Camlong
2023-12-02 16:15:39 +01:00
parent adaa4a5b64
commit 6a8ee0338e
26 changed files with 101 additions and 182 deletions

View File

@@ -37,7 +37,7 @@ export const SearchCustomization = ({ allMediaIntegrations }: IntegrationCustomi
data={allMediaIntegrations.map((x) => ({
value: x.id,
label: x.name,
sort: x.sort,
type: x.type,
}))}
/>
</Stack>
@@ -45,16 +45,16 @@ export const SearchCustomization = ({ allMediaIntegrations }: IntegrationCustomi
};
interface ItemProps extends React.ComponentPropsWithoutRef<'div'> {
sort: IntegrationType;
type: IntegrationType;
label: string;
}
const IntegrationSelectItem = forwardRef<HTMLDivElement, ItemProps>(
({ label, sort, ...others }: ItemProps, ref) => {
({ label, type, ...others }: ItemProps, ref) => {
return (
<div ref={ref} {...others}>
<Group noWrap>
<Avatar size={20} src={integrationTypes[sort].iconUrl} />
<Avatar size={20} src={integrationTypes[type].iconUrl} />
<Text>{label}</Text>
</Group>
@@ -89,7 +89,7 @@ const IntegrationSelectValue =
})}
>
<Box mr={10}>
<Avatar size="xs" src={integrationTypes[current!.sort].iconUrl} />
<Avatar size="xs" src={integrationTypes[current!.type].iconUrl} />
</Box>
<Box sx={{ lineHeight: 1, fontSize: rem(12) }}>{label}</Box>
<CloseButton

View File

@@ -38,8 +38,8 @@ export const ChangeWidgetPositionModal = ({
closeModal(id);
};
const widthData = useWidthData(innerProps.widget.sort);
const heightData = useHeightData(innerProps.widget.sort);
const widthData = useWidthData(innerProps.widget.type);
const heightData = useHeightData(innerProps.widget.type);
return (
<CommonChangePositionModal

View File

@@ -16,18 +16,18 @@ interface WidgetsMenuProps {
}
export const WidgetsMenu = ({ widget }: WidgetsMenuProps) => {
const { t } = useTranslation(`modules/${widget.sort}`);
const { t } = useTranslation(`modules/${widget.type}`);
const wrapperColumnCount = useWrapperColumnCount();
const resizeGridItem = useResizeGridItem();
const { removeItem } = useItemActions();
if (!widget || !wrapperColumnCount) return null;
// Then get the widget definition
const widgetDefinitionObject = WidgetsDefinitions[widget.sort as keyof typeof WidgetsDefinitions];
const widgetDefinitionObject = WidgetsDefinitions[widget.type as keyof typeof WidgetsDefinitions];
const handleDeleteClick = () => {
openRemoveItemModal({
name: widget.sort,
name: widget.type,
onConfirm() {
removeItem({
itemId: widget.id,
@@ -55,7 +55,7 @@ export const WidgetsMenu = ({ widget }: WidgetsMenuProps) => {
title: <Title order={4}>{t('descriptor.settings.title')}</Title>,
innerProps: {
widgetId: widget.id,
widgetType: widget.sort,
widgetType: widget.type,
options: widget.options,
widgetOptions: widgetDefinitionObject.options,
},

View File

@@ -1,7 +1,7 @@
import { useCallback } from 'react';
import { v4 } from 'uuid';
import { z } from 'zod';
import { widgetCreationSchema, widgetSortSchema } from '~/validations/widget';
import { widgetCreationSchema, widgetTypeSchema } from '~/validations/widget';
import { IWidgetDefinition } from '~/widgets/widgets';
import { useUpdateBoard } from '../../board-actions';
@@ -13,7 +13,7 @@ type UpdateWidgetOptions = {
};
type CreateWidget = {
sort: z.infer<typeof widgetSortSchema>;
type: z.infer<typeof widgetTypeSchema>;
definition: IWidgetDefinition;
};
@@ -47,7 +47,7 @@ export const useWidgetActions = () => {
);
const createWidget = useCallback(
({ sort, definition }: CreateWidget) => {
({ type, definition }: CreateWidget) => {
updateBoard((prev) => {
if (!prev) return prev;
@@ -58,7 +58,7 @@ export const useWidgetActions = () => {
const widget = {
id: v4(),
kind: 'widget',
sort,
type,
options: Object.entries(definition.options).reduce(
(prev, [k, v]) => {
const newPrev = prev;

View File

@@ -40,7 +40,7 @@ export function SectionContent({ items, refs }: SectionContentProps) {
</GridstackItemWrapper>
))}
{widgets.map((widget) => {
const definition = Widgets[widget.sort];
const definition = Widgets[widget.type];
if (!definition) return null;
return (

View File

@@ -26,7 +26,7 @@ export const AvailableIntegrationElements = ({
<Grid>
{objectEntries(widgets).map(([k, v]) => (
<WidgetElementType key={k} sort={k} image={v.icon} widget={v} modalId={modalId} />
<WidgetElementType key={k} type={k} image={v.icon} widget={v} modalId={modalId} />
))}
</Grid>
</>

View File

@@ -2,14 +2,14 @@ import { useModals } from '@mantine/modals';
import { showNotification } from '@mantine/notifications';
import { Icon, IconChecks } from '@tabler/icons-react';
import { useTranslation } from 'next-i18next';
import { WidgetSort } from '~/server/db/items';
import { WidgetType } from '~/server/db/items';
import { IWidgetDefinition } from '~/widgets/widgets';
import { useWidgetActions } from '../../Items/Widget/widget-actions';
import { GenericAvailableElementType } from '../Shared/GenericElementType';
interface WidgetElementTypeProps {
sort: WidgetSort;
type: WidgetType;
image: string | Icon;
disabled?: boolean;
widget: IWidgetDefinition;
@@ -17,19 +17,19 @@ interface WidgetElementTypeProps {
}
export const WidgetElementType = ({
sort,
type,
image,
disabled,
widget,
modalId,
}: WidgetElementTypeProps) => {
const { closeModal } = useModals();
const { t } = useTranslation(`modules/${sort}`);
const { t } = useTranslation(`modules/${type}`);
const { createWidget } = useWidgetActions();
const handleAddition = async () => {
createWidget({
sort,
type,
definition: widget,
});
closeModal(modalId);

View File

@@ -49,7 +49,7 @@ export const Search = ({ isMobile, autoFocus }: SearchProps) => {
)
.filter(
(engine) =>
engine.sort !== 'movie' || board?.mediaIntegrations.some((x) => x.sort === engine.value)
engine.sort !== 'movie' || board?.mediaIntegrations.some((x) => x.type === engine.value)
)
.map((engine) => ({
...engine,

View File

@@ -246,7 +246,7 @@ export function MediaDisplay({ media }: { media: IMedia }) {
{media.type === 'overseer' && !media.overseerrResult?.mediaInfo?.mediaAddedAt && (
<>
<RequestModal
base={media.overseerrResult as Result}
base={media.overseerrResult}
opened={opened}
setOpened={setOpened}
/>

View File

@@ -44,7 +44,7 @@ export const applyCreateWidgetChanges = (
const widgetId = randomUUID();
changes.widgets.create.push({
id: widgetId,
sort: widget.sort,
type: widget.type,
itemId: widget.id,
});

View File

@@ -6,12 +6,12 @@ export type MapWidget = Awaited<ReturnType<typeof getWidgetsForSectionsAsync>>[n
export const mapWidget = (widgetItem: MapWidget) => {
const { sectionId, itemId, id, ...commonLayoutItem } = widgetItem.item.layouts.at(0)!;
const common = { ...commonLayoutItem, id: itemId };
const { id: _id, itemId: _itemId, sort, item, options, integrations, ...widget } = widgetItem;
const { id: _id, itemId: _itemId, type, item, options, integrations, ...widget } = widgetItem;
return {
...common,
...widget,
kind: 'widget' as const,
sort,
type,
options: mapWidgetOptions(options),
integrations: integrations.map((x) => ({
...x.integration,

View File

@@ -43,10 +43,10 @@ export const calendarRouter = createTRPCRouter({
]);
const promises = widget.integrations.map(async (integration) => {
const endpoint = integrationTypeEndpointMap.get(integration.sort);
const endpoint = integrationTypeEndpointMap.get(integration.type);
if (!endpoint) {
return {
sort: integration.sort,
type: integration.type,
items: [],
success: false,
};
@@ -62,7 +62,7 @@ export const calendarRouter = createTRPCRouter({
const end = new Date(input.year, input.month, 0); // Last day of month
const apiKey = integration.secrets.find((x) => x.key === 'apiKey')?.value;
if (!apiKey) return { sort: integration.sort, items: [], success: false };
if (!apiKey) return { type: integration.type, items: [], success: false };
const url = new URL(`${origin}${endpoint}`);
url.searchParams.set('apiKey', apiKey);
url.searchParams.set('end', end.toISOString());
@@ -74,13 +74,13 @@ export const calendarRouter = createTRPCRouter({
return axios
.get(url.toString())
.then((x) => ({ sort: integration.sort, items: x.data as unknown[], success: true }))
.then((x) => ({ type: integration.type, items: x.data as unknown[], success: true }))
.catch((err) => {
Consola.error(
`failed to process request for integration '${integration.sort}' (${integration.name}): ${err}`
`failed to process request for integration '${integration.type}' (${integration.name}): ${err}`
);
return {
sort: integration.sort,
type: integration.type,
items: [],
success: false,
};
@@ -95,10 +95,10 @@ export const calendarRouter = createTRPCRouter({
}
return {
tvShows: medias.filter((m) => m.sort === 'sonarr').flatMap((m) => m.items),
movies: medias.filter((m) => m.sort === 'radarr').flatMap((m) => m.items),
books: medias.filter((m) => m.sort === 'readarr').flatMap((m) => m.items),
musics: medias.filter((m) => m.sort === 'lidarr').flatMap((m) => m.items),
tvShows: medias.filter((m) => m.type === 'sonarr').flatMap((m) => m.items),
movies: medias.filter((m) => m.type === 'radarr').flatMap((m) => m.items),
books: medias.filter((m) => m.type === 'readarr').flatMap((m) => m.items),
musics: medias.filter((m) => m.type === 'lidarr').flatMap((m) => m.items),
totalCount: medias.reduce((p, c) => p + c.items.length, 0),
};
}),

View File

@@ -127,26 +127,6 @@ export const configRouter = createTRPCRouter({
newConfig = {
...newConfig,
widgets: [
...newConfig.widgets.map((x) => {
if (x.type !== 'rss') {
return x;
}
const rssWidget = x as IRssWidget;
return {
...rssWidget,
properties: {
...rssWidget.properties,
rssFeedUrl:
typeof rssWidget.properties.rssFeedUrl === 'string'
? [rssWidget.properties.rssFeedUrl]
: rssWidget.properties.rssFeedUrl,
},
} as IRssWidget;
}),
],
};
// Save the body in the /data/config folder with the slug as filename

View File

@@ -25,7 +25,7 @@ export const downloadRouter = createTRPCRouter({
z.object({
boardId: z.string(),
widgetId: z.string(),
sort: z.enum(['torrents-status', 'dlspeed']),
type: z.enum(['torrents-status', 'dlspeed']),
})
)
.query(async ({ input, ctx }) => {
@@ -33,7 +33,7 @@ export const downloadRouter = createTRPCRouter({
input.boardId,
input.widgetId,
ctx.session?.user,
input.sort
input.type
);
if (!widget) {
@@ -107,7 +107,7 @@ const GetDataFromClient = async (
.reduce((acc, torrent) => acc + torrent, 0),
});
switch (integration.sort) {
switch (integration.type) {
case 'deluge': {
return reduceTorrent(
await new Deluge({

View File

@@ -7,7 +7,7 @@ import { adminProcedure, createTRPCRouter } from '../trpc';
export const integrationRouter = createTRPCRouter({
allMedia: adminProcedure.query(async () => {
return await db.query.integrations.findMany({
where: inArray(integrations.sort, ['jellyseerr', 'overseerr']),
where: inArray(integrations.type, ['jellyseerr', 'overseerr']),
});
}),
});

View File

@@ -32,7 +32,7 @@ export const overseerrRouter = createTRPCRouter({
.where(
and(
eq(boardIntegrations.boardId, input.boardId),
eq(integrations.sort, input.integration)
eq(integrations.type, input.integration)
)
)
.get();

View File

@@ -13,7 +13,7 @@ export interface UsenetClient {
}
export function createUsenetClient(integration: WidgetIntegration) {
if (integration.sort === 'nzbGet') return new NzbgetUsenetClient(integration);
if (integration.type === 'nzbGet') return new NzbgetUsenetClient(integration);
return new SabnzbdUsenetClient(integration);
}

View File

@@ -1,61 +1,37 @@
import { IconKey, IconPassword, IconUser, TablerIconsProps } from '@tabler/icons-react';
import widgets from '~/widgets';
import { objectEntries, objectKeys } from '../../tools/object';
import { objectKeys } from '../../tools/object';
type IntegrationTypeDefinition = {
export type IntegrationTypeDefinition = {
secrets: IntegrationSecretKey[];
iconUrl: string;
label: string;
groups: IntegrationGroup[];
testEndpoint?: string;
};
type IntegrationGroup =
| 'mediaServer'
| 'mediaRequest'
| 'mediaApp'
| 'usenet'
| 'torrent'
| 'download'
| 'dns';
type IntegrationSecretDefinition = {
visibility: IntegrationSecretVisibility;
icon: (props: TablerIconsProps) => JSX.Element;
};
export const colorSchemes = ['environment', 'light', 'dark'] as const;
export const firstDaysOfWeek = ['monday', 'saturday', 'sunday'] as const;
export const integrationSecretVisibility = ['private', 'public'] as const;
export const integrationSecrets = {
apiKey: {
visibility: 'private',
icon: IconKey,
},
username: {
visibility: 'public',
icon: IconUser,
},
password: {
visibility: 'private',
icon: IconPassword,
},
} satisfies Record<string, IntegrationSecretDefinition>;
export const widgetSorts = objectKeys(widgets);
export const widgetOptionTypes = [
'string',
'number',
'boolean',
'object',
'array',
'null',
] as const;
export const widgetTypes = objectKeys(widgets);
export const widgetOptionTypes = ['string', 'number', 'boolean', 'object', 'array', 'null'] as const;
export const boardBackgroundImageAttachmentTypes = ['fixed', 'scroll'] as const;
export const boardBackgroundImageRepeatTypes = [
'repeat',
'repeat-x',
'repeat-y',
'no-repeat',
] as const;
export const boardBackgroundImageRepeatTypes = ['repeat', 'repeat-x', 'repeat-y', 'no-repeat'] as const;
export const boardBackgroundImageSizeTypes = ['cover', 'contain'] as const;
export const appNamePositions = ['right', 'left', 'top', 'bottom'] as const;
export const appNameStyles = ['normal', 'hide', 'hover'] as const;
@@ -66,107 +42,95 @@ export const statusCodeTypes = [
'clientError',
'serverError',
] as const;
export const sectionTypes = ['sidebar', 'empty', 'category', 'hidden'] as const;
export const layoutKinds = ['mobile', 'desktop'] as const;
const sectionTypes = ['sidebar', 'empty', 'category', 'hidden'] as const;
const layoutKinds = ['mobile', 'desktop'] as const;
export const integrationTypes = {
readarr: {
secrets: ['apiKey'],
iconUrl: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/readarr.png',
label: 'Readarr',
groups: ['mediaApp'],
testEndpoint: '/api',
},
radarr: {
secrets: ['apiKey'],
iconUrl: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/radarr.png',
label: 'Radarr',
groups: ['mediaApp'],
testEndpoint: '/api',
},
sonarr: {
secrets: ['apiKey'],
iconUrl: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/sonarr.png',
label: 'Sonarr',
groups: ['mediaApp'],
testEndpoint: '/api',
},
lidarr: {
secrets: ['apiKey'],
iconUrl: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/lidarr.png',
label: 'Lidarr',
groups: ['mediaApp'],
testEndpoint: '/api',
},
sabnzbd: {
secrets: [],
secrets: ['username', 'password'],
iconUrl: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/sabnzbd.png',
label: 'SABnzbd',
groups: ['usenet', 'download'],
},
jellyseerr: {
secrets: ['apiKey'],
iconUrl: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/jellyseerr.png',
label: 'Jellyseerr',
groups: ['mediaRequest'],
},
overseerr: {
secrets: ['apiKey'],
iconUrl: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/overseerr.png',
label: 'Overseerr',
groups: ['mediaRequest'],
},
deluge: {
secrets: ['password'],
iconUrl: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/deluge.png',
label: 'Deluge',
groups: ['torrent'],
},
qBittorrent: {
secrets: ['username', 'password'],
iconUrl: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/qbittorrent.png',
label: 'qBittorrent',
groups: ['torrent'],
},
transmission: {
secrets: ['username', 'password'],
iconUrl: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/transmission.png',
label: 'Transmission',
groups: ['torrent'],
},
plex: {
secrets: ['apiKey'],
iconUrl: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/plex.png',
label: 'Plex',
groups: ['mediaServer'],
},
jellyfin: {
secrets: ['username', 'password'],
iconUrl: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/jellyfin.png',
label: 'Jellyfin',
groups: ['mediaServer'],
},
nzbGet: {
secrets: ['username', 'password'],
iconUrl: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/nzbget.png',
label: 'NZBGet',
groups: ['usenet', 'download'],
},
pihole: {
secrets: ['apiKey'],
iconUrl: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/pi-hole.png',
label: 'PiHole',
groups: ['dns'],
},
adGuardHome: {
secrets: ['username', 'password'],
iconUrl: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/adguard-home.png',
label: 'AdGuard Home',
groups: ['dns'],
},
} satisfies Record<string, IntegrationTypeDefinition>;
export type ColorScheme = (typeof colorSchemes)[number];
export type FirstDayOfWeek = (typeof firstDaysOfWeek)[number];
export type IntegrationType = keyof typeof integrationTypes;
export type IntegrationSecretVisibility = (typeof integrationSecretVisibility)[number];
export type IntegrationSecretKey = keyof typeof integrationSecrets;
export type WidgetSort = (typeof widgetSorts)[number];
export type WidgetType = (typeof widgetTypes)[number];
export type WidgetOptionType = (typeof widgetOptionTypes)[number];
export type BoardBackgroundImageAttachmentType =
(typeof boardBackgroundImageAttachmentTypes)[number];
@@ -177,17 +141,3 @@ export type AppNameStyle = (typeof appNameStyles)[number];
export type StatusCodeType = (typeof statusCodeTypes)[number];
export type SectionType = (typeof sectionTypes)[number];
export type LayoutKind = (typeof layoutKinds)[number];
type InferIntegrationTypeFromGroup<TGroup extends IntegrationGroup> = {
[key in keyof typeof integrationTypes]: (typeof integrationTypes)[key] extends {
groups: TGroup[];
}
? key
: never;
}[keyof typeof integrationTypes];
export const integrationGroup = <TGroup extends IntegrationGroup>(group: TGroup) => {
return objectEntries(integrationTypes)
.filter(([, { groups }]) => groups.some((g) => group === g))
.map(([key]) => key) as InferIntegrationTypeFromGroup<TGroup>[];
};

View File

@@ -15,7 +15,7 @@ export async function getIntegrations<TIntegrations extends IntegrationType>(
export const getIntegrationsForWidget = async <TIntegrations extends IntegrationType>(
boardId: string,
sorts: TIntegrations[],
types: TIntegrations[],
user: User | null | undefined,
widgetId: 'ignore' | (string & {})
@@ -24,7 +24,7 @@ export const getIntegrationsForWidget = async <TIntegrations extends Integration
where: and(
eq(items.boardId, boardId),
user ? undefined : eq(boards.allowGuests, true),
sorts.length >= 1 ? inArray(integrations.sort, sorts) : undefined,
types.length >= 1 ? inArray(integrations.type, types) : undefined,
widgetId !== 'ignore' ? eq(items.id, widgetId) : undefined
),
with: {
@@ -46,7 +46,7 @@ export const getIntegrationsForWidget = async <TIntegrations extends Integration
});
return widgetItems
.flatMap((x) => x.widget?.integrations ?? [])
.map((x) => ({ ...x.integration, sort: x.integration.sort as TIntegrations }));
.map((x) => ({ ...x.integration, type: x.integration.type as TIntegrations }));
};
export async function getIntegrationAsync(integrationId: string) {

View File

@@ -8,18 +8,18 @@ import { InferWidgetOptions } from '~/widgets/widgets';
import { db } from '..';
import { boards, items, widgets } from '../schema';
export const getWidgetAsync = async <TSort extends keyof typeof widgetDefinitions>(
export const getWidgetAsync = async <TType extends keyof typeof widgetDefinitions>(
boardId: string,
id: string,
user: User | null | undefined,
sort: TSort
type: TType
) => {
const widgetItem = await db.query.items.findFirst({
where: and(
eq(items.boardId, boardId),
eq(items.id, id),
user ? undefined : eq(boards.allowGuests, true),
eq(widgets.sort, sort)
eq(widgets.type, type)
),
with: {
widget: {
@@ -44,21 +44,21 @@ export const getWidgetAsync = async <TSort extends keyof typeof widgetDefinition
},
});
if (!widgetItem) {
if (!widgetItem || !widgetItem.widget) {
return null;
}
const mappedOptions = mapWidgetOptions(
widgetItem.widget!.options.sort((a, b) => a.path.localeCompare(b.path))
widgetItem.widget.options.sort((a, b) => a.path.localeCompare(b.path))
);
objectEntries(widgetDefinitions[sort].options).forEach(([key, definition]) => {
objectEntries(widgetDefinitions[type].options).forEach(([key, definition]) => {
mappedOptions[key] = mappedOptions[key] ?? definition.defaultValue;
});
return {
id: widgetItem.id,
sort: widgetItem.widget!.sort,
options: mappedOptions as InferWidgetOptions<(typeof widgetDefinitions)[TSort]>,
type: widgetItem.widget.type,
options: mappedOptions as InferWidgetOptions<(typeof widgetDefinitions)[TType]>,
integrations: widgetItem.widget!.integrations.map((i) => ({
...i.integration,
})),

View File

@@ -1,5 +1,5 @@
import type { MantineColor } from '@mantine/core';
import { type InferSelectModel, relations } from 'drizzle-orm';
import { relations, type InferSelectModel } from 'drizzle-orm';
import { index, int, integer, primaryKey, sqliteTable, text } from 'drizzle-orm/sqlite-core';
import { type AdapterAccount } from 'next-auth/adapters';
@@ -12,12 +12,11 @@ import type {
ColorScheme,
FirstDayOfWeek,
IntegrationSecretKey,
IntegrationSecretVisibility,
IntegrationType,
LayoutKind,
SectionType,
WidgetOptionType,
WidgetSort,
WidgetType,
} from './items';
export const users = sqliteTable('user', {
@@ -156,7 +155,7 @@ export const boards = sqliteTable('board', {
export const integrations = sqliteTable('integration', {
id: text('id').notNull().primaryKey(),
sort: text('sort').$type<IntegrationType>().notNull(),
type: text('type').$type<IntegrationType>().notNull(),
name: text('name').notNull(),
url: text('url').notNull(),
});
@@ -166,7 +165,6 @@ export const integrationSecrets = sqliteTable(
{
key: text('key').$type<IntegrationSecretKey>().notNull(),
value: text('value'),
visibility: text('visibility').$type<IntegrationSecretVisibility>().notNull(),
integrationId: text('integration_id')
.notNull()
.references(() => integrations.id, { onDelete: 'cascade' }),
@@ -189,7 +187,7 @@ export const boardIntegrations = sqliteTable(
export const widgets = sqliteTable('widget', {
id: text('id').notNull().primaryKey(),
sort: text('sort').$type<WidgetSort>().notNull(),
type: text('sort').$type<WidgetType>().notNull(),
itemId: text('item_id')
.notNull()
.references(() => items.id, { onDelete: 'cascade' }),

View File

@@ -1,15 +1,14 @@
import { z } from 'zod';
import { widgetSorts } from '~/server/db/items';
import { widgetTypes } from '~/server/db/items';
import { appSchema } from './app';
import { commonItemSchema } from './item';
export const widgetSortSchema = z.enum([widgetSorts[0], ...widgetSorts.slice(1)]);
export const widgetTypeSchema = z.enum([widgetTypes[0], ...widgetTypes.slice(1)]);
export const widgetCreationSchema = z.object({
id: z.string(),
kind: z.literal('widget'),
sort: widgetSortSchema,
type: widgetTypeSchema,
options: z.record(z.string(), z.unknown()),
});

View File

@@ -14,7 +14,7 @@ interface WidgetWrapperProps {
// If a property has no value, set it to the default value
const useWidgetWithDefaultOptionValues = <T extends WidgetItem>(widget: T): T => {
const definition = Widgets[widget.sort];
const definition = Widgets[widget.type];
const newProps: Record<string, unknown> = {};

View File

@@ -24,9 +24,9 @@ import {
NormalizedDownloadQueueResponse,
TorrentTotalDownload,
} from '~/types/api/downloads/queue/NormalizedDownloadQueueResponse';
import { api } from '~/utils/api';
import definition, { ITorrentNetworkTraffic } from './TorrentNetworkTrafficTile';
import { useGetDownloadClientsQueue } from './useGetNetworkSpeed';
interface TorrentNetworkTrafficTileProps {
widget: ITorrentNetworkTraffic;
@@ -42,11 +42,16 @@ export default function TorrentNetworkTrafficTile({ widget }: TorrentNetworkTraf
const [clientDataHistory, setClientDataHistory] = useListState<NormalizedDownloadQueueResponse>();
const { data, dataUpdatedAt } = useGetDownloadClientsQueue({
boardId,
widgetId: widget.id,
sort: 'dlspeed',
});
const { data, dataUpdatedAt } = api.download.get.useQuery(
{
boardId,
widgetId: widget.id,
type: definition.id,
},
{
refetchInterval: 3000,
}
);
useEffect(() => {
if (data) {
@@ -169,7 +174,7 @@ export default function TorrentNetworkTrafficTile({ widget }: TorrentNetworkTraf
return (
<Group key={`download-client-tooltip-${index}`}>
<AppAvatar iconUrl={integrationTypes[current.sort].iconUrl} />
<AppAvatar iconUrl={integrationTypes[current.type].iconUrl} />
<Stack spacing={0}>
<Text size="sm">{current.name}</Text>
@@ -257,7 +262,7 @@ export default function TorrentNetworkTrafficTile({ widget }: TorrentNetworkTraf
withArrow
withinPortal
>
<AppAvatar iconUrl={integrationTypes[current.sort].iconUrl} />
<AppAvatar iconUrl={integrationTypes[current.type].iconUrl} />
</Tooltip>
);
})}

View File

@@ -1,18 +0,0 @@
import { RouterInputs, api } from '~/utils/api';
export const useGetDownloadClientsQueue = ({
boardId,
widgetId,
sort,
}: RouterInputs['download']['get']) => {
return api.download.get.useQuery(
{
boardId,
widgetId,
sort,
},
{
refetchInterval: 3000,
}
);
};

View File

@@ -22,8 +22,8 @@ import { useCardStyles } from '~/components/layout/Common/useCardStyles';
import { MIN_WIDTH_MOBILE } from '~/constants/constants';
import { NormalizedDownloadQueueResponse } from '~/types/api/downloads/queue/NormalizedDownloadQueueResponse';
import { AppIntegrationType } from '~/types/app';
import { api } from '~/utils/api';
import { useGetDownloadClientsQueue } from '../download-speed/useGetNetworkSpeed';
import { defineWidget } from '../helper';
import { InferWidget } from '../widgets';
import { BitTorrentQueueItem } from './TorrentQueueItem';
@@ -99,11 +99,16 @@ function TorrentTile({ widget }: TorrentTileProps) {
isError: boolean;
isInitialLoading: boolean;
dataUpdatedAt: number;
} = useGetDownloadClientsQueue({
boardId,
widgetId: widget.id,
sort: 'torrents-status',
});
} = api.download.get.useQuery(
{
boardId,
widgetId: widget.id,
type: definition.id,
},
{
refetchInterval: 3000,
}
);
if (isError) {
return (