diff --git a/packages/api/src/router/widgets/downloads.ts b/packages/api/src/router/widgets/downloads.ts index 5387da62f..4a3b0dab5 100644 --- a/packages/api/src/router/widgets/downloads.ts +++ b/packages/api/src/router/widgets/downloads.ts @@ -19,10 +19,11 @@ const createDownloadClientIntegrationMiddleware = (action: IntegrationAction) => export const downloadsRouter = createTRPCRouter({ getJobsAndStatuses: publicProcedure .concat(createDownloadClientIntegrationMiddleware("query")) - .query(async ({ ctx }) => { + .input(z.object({ limitPerIntegration: z.number().default(50) })) + .query(async ({ ctx, input }) => { return await Promise.all( ctx.integrations.map(async (integration) => { - const innerHandler = downloadClientRequestHandler.handler(integration, {}); + const innerHandler = downloadClientRequestHandler.handler(integration, { limit: input.limitPerIntegration }); const { data, timestamp } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); @@ -40,7 +41,8 @@ export const downloadsRouter = createTRPCRouter({ }), subscribeToJobsAndStatuses: publicProcedure .concat(createDownloadClientIntegrationMiddleware("query")) - .subscription(({ ctx }) => { + .input(z.object({ limitPerIntegration: z.number().default(50) })) + .subscription(({ ctx, input }) => { return observable<{ integration: Modify }>; data: DownloadClientJobsAndStatus; @@ -48,7 +50,9 @@ export const downloadsRouter = createTRPCRouter({ const unsubscribes: (() => void)[] = []; for (const integrationWithSecrets of ctx.integrations) { const { decryptedSecrets: _, ...integration } = integrationWithSecrets; - const innerHandler = downloadClientRequestHandler.handler(integrationWithSecrets, {}); + const innerHandler = downloadClientRequestHandler.handler(integrationWithSecrets, { + limit: input.limitPerIntegration, + }); const unsubscribe = innerHandler.subscribe((data) => { emit.next({ integration, diff --git a/packages/cron-jobs/src/jobs/integrations/downloads.ts b/packages/cron-jobs/src/jobs/integrations/downloads.ts index e7d010d65..54eb476dc 100644 --- a/packages/cron-jobs/src/jobs/integrations/downloads.ts +++ b/packages/cron-jobs/src/jobs/integrations/downloads.ts @@ -8,7 +8,9 @@ export const downloadsJob = createCronJob("downloads", EVERY_5_SECONDS).withCall createRequestIntegrationJobHandler(downloadClientRequestHandler.handler, { widgetKinds: ["downloads"], getInput: { - downloads: () => ({}), + downloads: (options) => ({ + limit: options.limitPerIntegration, + }), }, }), ); diff --git a/packages/integrations/src/download-client/aria2/aria2-integration.ts b/packages/integrations/src/download-client/aria2/aria2-integration.ts index 84458fda5..07869dcef 100644 --- a/packages/integrations/src/download-client/aria2/aria2-integration.ts +++ b/packages/integrations/src/download-client/aria2/aria2-integration.ts @@ -12,7 +12,7 @@ import type { DownloadClientItem } from "../../interfaces/downloads/download-cli import type { Aria2Download, Aria2GetClient } from "./aria2-types"; export class Aria2Integration extends DownloadClientIntegration { - public async getClientJobsAndStatusAsync(): Promise { + public async getClientJobsAndStatusAsync(input: { limit: number }): Promise { const client = this.getClient(); const keys: (keyof Aria2Download)[] = [ "bittorrent", @@ -27,12 +27,12 @@ export class Aria2Integration extends DownloadClientIntegration { ]; const [activeDownloads, waitingDownloads, stoppedDownloads, globalStats] = await Promise.all([ client.tellActive(), - client.tellWaiting(0, 1000, keys), - client.tellStopped(0, 1000, keys), + client.tellWaiting(0, input.limit, keys), + client.tellStopped(0, input.limit, keys), client.getGlobalStat(), ]); - const downloads = [...activeDownloads, ...waitingDownloads, ...stoppedDownloads]; + const downloads = [...activeDownloads, ...waitingDownloads, ...stoppedDownloads].slice(0, input.limit); const allPaused = downloads.every((download) => download.status === "paused"); return { diff --git a/packages/integrations/src/download-client/deluge/deluge-integration.ts b/packages/integrations/src/download-client/deluge/deluge-integration.ts index e5669446a..1e054108b 100644 --- a/packages/integrations/src/download-client/deluge/deluge-integration.ts +++ b/packages/integrations/src/download-client/deluge/deluge-integration.ts @@ -29,9 +29,10 @@ export class DelugeIntegration extends DownloadClientIntegration { }; } - public async getClientJobsAndStatusAsync(): Promise { + public async getClientJobsAndStatusAsync(input: { limit: number }): Promise { const type = "torrent"; const client = await this.getClientAsync(); + // Currently there is no way to limit the number of returned torrents const { stats: { download_rate, upload_rate }, torrents: rawTorrents, @@ -49,27 +50,29 @@ export class DelugeIntegration extends DownloadClientIntegration { }, types: [type], }; - const items = torrents.map((torrent): DownloadClientItem => { - const state = DelugeIntegration.getTorrentState(torrent.state); - return { - type, - id: torrent.id, - index: torrent.queue, - name: torrent.name, - size: torrent.total_wanted, - sent: torrent.total_uploaded, - downSpeed: torrent.progress !== 100 ? torrent.download_payload_rate : undefined, - upSpeed: torrent.upload_payload_rate, - time: - torrent.progress === 100 - ? Math.min((torrent.completed_time - dayjs().unix()) * 1000, -1) - : Math.max(torrent.eta * 1000, 0), - added: torrent.time_added * 1000, - state, - progress: torrent.progress / 100, - category: torrent.label, - }; - }); + const items = torrents + .map((torrent): DownloadClientItem => { + const state = DelugeIntegration.getTorrentState(torrent.state); + return { + type, + id: torrent.id, + index: torrent.queue, + name: torrent.name, + size: torrent.total_wanted, + sent: torrent.total_uploaded, + downSpeed: torrent.progress !== 100 ? torrent.download_payload_rate : undefined, + upSpeed: torrent.upload_payload_rate, + time: + torrent.progress === 100 + ? Math.min((torrent.completed_time - dayjs().unix()) * 1000, -1) + : Math.max(torrent.eta * 1000, 0), + added: torrent.time_added * 1000, + state, + progress: torrent.progress / 100, + category: torrent.label, + }; + }) + .slice(0, input.limit); return { status, items }; } diff --git a/packages/integrations/src/download-client/nzbget/nzbget-integration.ts b/packages/integrations/src/download-client/nzbget/nzbget-integration.ts index 65905cdd8..31f0c6a09 100644 --- a/packages/integrations/src/download-client/nzbget/nzbget-integration.ts +++ b/packages/integrations/src/download-client/nzbget/nzbget-integration.ts @@ -20,7 +20,7 @@ export class NzbGetIntegration extends DownloadClientIntegration { }; } - public async getClientJobsAndStatusAsync(): Promise { + public async getClientJobsAndStatusAsync(input: { limit: number }): Promise { const type = "usenet"; const queue = await this.nzbGetApiCallAsync("listgroups"); const history = await this.nzbGetApiCallAsync("history"); @@ -65,7 +65,8 @@ export class NzbGetIntegration extends DownloadClientIntegration { category: file.Category, }; }), - ); + ) + .slice(0, input.limit); return { status, items }; } diff --git a/packages/integrations/src/download-client/qbittorrent/qbittorrent-integration.ts b/packages/integrations/src/download-client/qbittorrent/qbittorrent-integration.ts index 2c649d256..5eeaa2695 100644 --- a/packages/integrations/src/download-client/qbittorrent/qbittorrent-integration.ts +++ b/packages/integrations/src/download-client/qbittorrent/qbittorrent-integration.ts @@ -26,10 +26,10 @@ export class QBitTorrentIntegration extends DownloadClientIntegration { }; } - public async getClientJobsAndStatusAsync(): Promise { + public async getClientJobsAndStatusAsync(input: { limit: number }): Promise { const type = "torrent"; const client = await this.getClientAsync(); - const torrents = await client.listTorrents(); + const torrents = await client.listTorrents({ limit: input.limit }); const rates = torrents.reduce( ({ down, up }, { dlspeed, upspeed }) => ({ down: down + dlspeed, up: up + upspeed }), { down: 0, up: 0 }, diff --git a/packages/integrations/src/download-client/sabnzbd/sabnzbd-integration.ts b/packages/integrations/src/download-client/sabnzbd/sabnzbd-integration.ts index 505e4cd51..986b5bd4c 100644 --- a/packages/integrations/src/download-client/sabnzbd/sabnzbd-integration.ts +++ b/packages/integrations/src/download-client/sabnzbd/sabnzbd-integration.ts @@ -22,10 +22,14 @@ export class SabnzbdIntegration extends DownloadClientIntegration { return { success: true }; } - public async getClientJobsAndStatusAsync(): Promise { + public async getClientJobsAndStatusAsync(input: { limit: number }): Promise { const type = "usenet"; - const { queue } = await queueSchema.parseAsync(await this.sabNzbApiCallAsync("queue")); - const { history } = await historySchema.parseAsync(await this.sabNzbApiCallAsync("history")); + const { queue } = await queueSchema.parseAsync( + await this.sabNzbApiCallAsync("queue", { limit: input.limit.toString() }), + ); + const { history } = await historySchema.parseAsync( + await this.sabNzbApiCallAsync("history", { limit: input.limit.toString() }), + ); const status: DownloadClientStatus = { paused: queue.paused, rates: { down: Math.floor(Number(queue.kbpersec) * 1024) }, //Actually rounded kiBps () @@ -73,7 +77,8 @@ export class SabnzbdIntegration extends DownloadClientIntegration { category: slot.category, }; }), - ); + ) + .slice(0, input.limit); return { status, items }; } diff --git a/packages/integrations/src/download-client/transmission/transmission-integration.ts b/packages/integrations/src/download-client/transmission/transmission-integration.ts index 350ce0552..29615ed56 100644 --- a/packages/integrations/src/download-client/transmission/transmission-integration.ts +++ b/packages/integrations/src/download-client/transmission/transmission-integration.ts @@ -23,9 +23,10 @@ export class TransmissionIntegration extends DownloadClientIntegration { }; } - public async getClientJobsAndStatusAsync(): Promise { + public async getClientJobsAndStatusAsync(input: { limit: number }): Promise { const type = "torrent"; const client = await this.getClientAsync(); + // Currently there is no way to limit the number of returned torrents const { torrents } = (await client.listTorrents()).arguments; const rates = torrents.reduce( ({ down, up }, { rateDownload, rateUpload }) => ({ down: down + rateDownload, up: up + rateUpload }), @@ -34,27 +35,29 @@ export class TransmissionIntegration extends DownloadClientIntegration { const paused = torrents.find(({ status }) => TransmissionIntegration.getTorrentState(status) !== "paused") === undefined; const status: DownloadClientStatus = { paused, rates, types: [type] }; - const items = torrents.map((torrent): DownloadClientItem => { - const state = TransmissionIntegration.getTorrentState(torrent.status); - return { - type, - id: torrent.hashString, - index: torrent.queuePosition, - name: torrent.name, - size: torrent.totalSize, - sent: torrent.uploadedEver, - downSpeed: torrent.percentDone !== 1 ? torrent.rateDownload : undefined, - upSpeed: torrent.rateUpload, - time: - torrent.percentDone === 1 - ? Math.min(torrent.doneDate * 1000 - dayjs().valueOf(), -1) - : Math.max(torrent.eta * 1000, 0), - added: torrent.addedDate * 1000, - state, - progress: torrent.percentDone, - category: torrent.labels, - }; - }); + const items = torrents + .map((torrent): DownloadClientItem => { + const state = TransmissionIntegration.getTorrentState(torrent.status); + return { + type, + id: torrent.hashString, + index: torrent.queuePosition, + name: torrent.name, + size: torrent.totalSize, + sent: torrent.uploadedEver, + downSpeed: torrent.percentDone !== 1 ? torrent.rateDownload : undefined, + upSpeed: torrent.rateUpload, + time: + torrent.percentDone === 1 + ? Math.min(torrent.doneDate * 1000 - dayjs().valueOf(), -1) + : Math.max(torrent.eta * 1000, 0), + added: torrent.addedDate * 1000, + state, + progress: torrent.percentDone, + category: torrent.labels, + }; + }) + .slice(0, input.limit); return { status, items }; } diff --git a/packages/integrations/src/interfaces/downloads/download-client-integration.ts b/packages/integrations/src/interfaces/downloads/download-client-integration.ts index b14b4b6e6..97b4844ab 100644 --- a/packages/integrations/src/interfaces/downloads/download-client-integration.ts +++ b/packages/integrations/src/interfaces/downloads/download-client-integration.ts @@ -4,7 +4,7 @@ import type { DownloadClientItem } from "./download-client-items"; export abstract class DownloadClientIntegration extends Integration { /** Get download client's status and list of all of it's items */ - public abstract getClientJobsAndStatusAsync(): Promise; + public abstract getClientJobsAndStatusAsync(input: { limit: number }): Promise; /** Pauses the client or all of it's items */ public abstract pauseQueueAsync(): Promise; /** Pause a single item using it's ID */ diff --git a/packages/integrations/test/aria2.spec.ts b/packages/integrations/test/aria2.spec.ts index 5ea0d8ed4..840a266c9 100644 --- a/packages/integrations/test/aria2.spec.ts +++ b/packages/integrations/test/aria2.spec.ts @@ -46,7 +46,7 @@ describe("Aria2 integration", () => { // Acts const actAsync = async () => await aria2Integration.pauseQueueAsync(); - const getAsync = async () => await aria2Integration.getClientJobsAndStatusAsync(); + const getAsync = async () => await aria2Integration.getClientJobsAndStatusAsync({ limit: 99 }); // Assert await expect(actAsync()).resolves.not.toThrow(); @@ -62,7 +62,7 @@ describe("Aria2 integration", () => { const aria2Integration = createAria2Intergration(startedContainer, API_KEY); // Act - const getAsync = async () => await aria2Integration.getClientJobsAndStatusAsync(); + const getAsync = async () => await aria2Integration.getClientJobsAndStatusAsync({ limit: 99 }); // Assert await expect(getAsync()).resolves.not.toThrow(); @@ -81,7 +81,7 @@ describe("Aria2 integration", () => { await aria2AddItemAsync(startedContainer, API_KEY, aria2Integration); // Act - const getAsync = async () => await aria2Integration.getClientJobsAndStatusAsync(); + const getAsync = async () => await aria2Integration.getClientJobsAndStatusAsync({ limit: 99 }); // Assert await expect(getAsync()).resolves.not.toThrow(); @@ -104,7 +104,7 @@ describe("Aria2 integration", () => { await expect(actAsync()).resolves.not.toThrow(); // NzbGet is slow and we wait for a second before querying the items. Test was flaky without this. await new Promise((resolve) => setTimeout(resolve, 1000)); - const result = await aria2Integration.getClientJobsAndStatusAsync(); + const result = await aria2Integration.getClientJobsAndStatusAsync({ limit: 99 }); expect(result.items).toHaveLength(0); // Cleanup @@ -153,7 +153,7 @@ const aria2AddItemAsync = async (container: StartedTestContainer, apiKey: string const { items: [item], - } = await integration.getClientJobsAndStatusAsync(); + } = await integration.getClientJobsAndStatusAsync({ limit: 99 }); if (!item) { throw new Error("No item found"); diff --git a/packages/integrations/test/nzbget.spec.ts b/packages/integrations/test/nzbget.spec.ts index 938ac927e..f8a82ddad 100644 --- a/packages/integrations/test/nzbget.spec.ts +++ b/packages/integrations/test/nzbget.spec.ts @@ -69,7 +69,7 @@ describe("Nzbget integration", () => { // Acts const actAsync = async () => await nzbGetIntegration.pauseQueueAsync(); - const getAsync = async () => await nzbGetIntegration.getClientJobsAndStatusAsync(); + const getAsync = async () => await nzbGetIntegration.getClientJobsAndStatusAsync({ limit: 99 }); // Assert await expect(actAsync()).resolves.not.toThrow(); @@ -87,7 +87,7 @@ describe("Nzbget integration", () => { // Acts const actAsync = async () => await nzbGetIntegration.resumeQueueAsync(); - const getAsync = async () => await nzbGetIntegration.getClientJobsAndStatusAsync(); + const getAsync = async () => await nzbGetIntegration.getClientJobsAndStatusAsync({ limit: 99 }); // Assert await expect(actAsync()).resolves.not.toThrow(); @@ -105,7 +105,7 @@ describe("Nzbget integration", () => { const nzbGetIntegration = createNzbGetIntegration(startedContainer, username, password); // Act - const getAsync = async () => await nzbGetIntegration.getClientJobsAndStatusAsync(); + const getAsync = async () => await nzbGetIntegration.getClientJobsAndStatusAsync({ limit: 99 }); // Assert await expect(getAsync()).resolves.not.toThrow(); @@ -124,7 +124,7 @@ describe("Nzbget integration", () => { await nzbGetAddItemAsync(startedContainer, username, password, nzbGetIntegration); // Act - const getAsync = async () => await nzbGetIntegration.getClientJobsAndStatusAsync(); + const getAsync = async () => await nzbGetIntegration.getClientJobsAndStatusAsync({ limit: 99 }); // Assert await expect(getAsync()).resolves.not.toThrow(); @@ -147,7 +147,7 @@ describe("Nzbget integration", () => { await expect(actAsync()).resolves.not.toThrow(); // NzbGet is slow and we wait for a second before querying the items. Test was flaky without this. await new Promise((resolve) => setTimeout(resolve, 1000)); - const result = await nzbGetIntegration.getClientJobsAndStatusAsync(); + const result = await nzbGetIntegration.getClientJobsAndStatusAsync({ limit: 99 }); expect(result.items).toHaveLength(0); // Cleanup @@ -209,7 +209,7 @@ const nzbGetAddItemAsync = async ( const { items: [item], - } = await integration.getClientJobsAndStatusAsync(); + } = await integration.getClientJobsAndStatusAsync({ limit: 99 }); if (!item) { throw new Error("No item found"); diff --git a/packages/integrations/test/sabnzbd.spec.ts b/packages/integrations/test/sabnzbd.spec.ts index cb2201120..f6d36a69f 100644 --- a/packages/integrations/test/sabnzbd.spec.ts +++ b/packages/integrations/test/sabnzbd.spec.ts @@ -67,7 +67,7 @@ describe("Sabnzbd integration", () => { // Acts const actAsync = async () => await sabnzbdIntegration.pauseQueueAsync(); - const getAsync = async () => await sabnzbdIntegration.getClientJobsAndStatusAsync(); + const getAsync = async () => await sabnzbdIntegration.getClientJobsAndStatusAsync({ limit: 99 }); // Assert await expect(actAsync()).resolves.not.toThrow(); @@ -85,7 +85,7 @@ describe("Sabnzbd integration", () => { // Acts const actAsync = async () => await sabnzbdIntegration.resumeQueueAsync(); - const getAsync = async () => await sabnzbdIntegration.getClientJobsAndStatusAsync(); + const getAsync = async () => await sabnzbdIntegration.getClientJobsAndStatusAsync({ limit: 99 }); // Assert await expect(actAsync()).resolves.not.toThrow(); @@ -103,7 +103,7 @@ describe("Sabnzbd integration", () => { const sabnzbdIntegration = createSabnzbdIntegration(startedContainer, DEFAULT_API_KEY); // Act - const getAsync = async () => await sabnzbdIntegration.getClientJobsAndStatusAsync(); + const getAsync = async () => await sabnzbdIntegration.getClientJobsAndStatusAsync({ limit: 99 }); // Assert await expect(getAsync()).resolves.not.toThrow(); @@ -122,7 +122,7 @@ describe("Sabnzbd integration", () => { await sabNzbdAddItemAsync(startedContainer, DEFAULT_API_KEY, sabnzbdIntegration); // Act - const getAsync = async () => await sabnzbdIntegration.getClientJobsAndStatusAsync(); + const getAsync = async () => await sabnzbdIntegration.getClientJobsAndStatusAsync({ limit: 99 }); // Assert await expect(getAsync()).resolves.not.toThrow(); @@ -140,7 +140,7 @@ describe("Sabnzbd integration", () => { // Act const actAsync = async () => await sabnzbdIntegration.pauseItemAsync(item); - const getAsync = async () => await sabnzbdIntegration.getClientJobsAndStatusAsync(); + const getAsync = async () => await sabnzbdIntegration.getClientJobsAndStatusAsync({ limit: 99 }); // Assert await expect(getAsync()).resolves.toMatchObject({ items: [{ ...item, state: "downloading" }] }); @@ -160,7 +160,7 @@ describe("Sabnzbd integration", () => { // Act const actAsync = async () => await sabnzbdIntegration.resumeItemAsync(item); - const getAsync = async () => await sabnzbdIntegration.getClientJobsAndStatusAsync(); + const getAsync = async () => await sabnzbdIntegration.getClientJobsAndStatusAsync({ limit: 99 }); // Assert await expect(getAsync()).resolves.toMatchObject({ items: [{ ...item, state: "paused" }] }); @@ -180,7 +180,7 @@ describe("Sabnzbd integration", () => { // Act - fromDisk already doesn't work for sabnzbd, so only test deletion itself. const actAsync = async () => await sabnzbdIntegration.deleteItemAsync({ ...item, progress: 0 } as DownloadClientItem, false); - const getAsync = async () => await sabnzbdIntegration.getClientJobsAndStatusAsync(); + const getAsync = async () => await sabnzbdIntegration.getClientJobsAndStatusAsync({ limit: 99 }); // Assert await expect(actAsync()).resolves.not.toThrow(); @@ -242,7 +242,7 @@ const sabNzbdAddItemAsync = async ( for (let i = 0; i < 5; i++) { const { items: [item], - } = await integration.getClientJobsAndStatusAsync(); + } = await integration.getClientJobsAndStatusAsync({ limit: 99 }); if (item) return item; } // Throws if it can't find the item diff --git a/packages/old-import/src/widgets/options.ts b/packages/old-import/src/widgets/options.ts index d8c9678af..793a4caa7 100644 --- a/packages/old-import/src/widgets/options.ts +++ b/packages/old-import/src/widgets/options.ts @@ -77,6 +77,7 @@ const optionMapping: OptionMapping = { descendingDefaultSort: () => false, showCompletedUsenet: () => true, showCompletedHttp: () => true, + limitPerIntegration: () => undefined, }, weather: { forecastDayCount: (oldOptions) => oldOptions.forecastDays, diff --git a/packages/request-handler/src/downloads.ts b/packages/request-handler/src/downloads.ts index 567b88324..06d4e9c64 100644 --- a/packages/request-handler/src/downloads.ts +++ b/packages/request-handler/src/downloads.ts @@ -9,11 +9,11 @@ import { createCachedIntegrationRequestHandler } from "./lib/cached-integration- export const downloadClientRequestHandler = createCachedIntegrationRequestHandler< DownloadClientJobsAndStatus, IntegrationKindByCategory<"downloadClient">, - Record + { limit: number } >({ - async requestAsync(integration, _input) { + async requestAsync(integration, input) { const integrationInstance = await createIntegrationAsync(integration); - return await integrationInstance.getClientJobsAndStatusAsync(); + return await integrationInstance.getClientJobsAndStatusAsync(input); }, cacheDuration: dayjs.duration(5, "seconds"), queryKey: "downloadClientJobStatus", diff --git a/packages/translation/src/lang/en.json b/packages/translation/src/lang/en.json index e03c79aa3..1df588680 100644 --- a/packages/translation/src/lang/en.json +++ b/packages/translation/src/lang/en.json @@ -1953,6 +1953,10 @@ }, "applyFilterToRatio": { "label": "Use filter to calculate Ratio" + }, + "limitPerIntegration": { + "label": "Limit items per integration", + "description": "This will limit the number of items shown per integration, not globally" } }, "errors": { diff --git a/packages/widgets/src/downloads/component.tsx b/packages/widgets/src/downloads/component.tsx index d8139641e..b13b7e144 100644 --- a/packages/widgets/src/downloads/component.tsx +++ b/packages/widgets/src/downloads/component.tsx @@ -87,6 +87,7 @@ export default function DownloadClientsWidget({ const [currentItems] = clientApi.widget.downloads.getJobsAndStatuses.useSuspenseQuery( { integrationIds, + limitPerIntegration: options.limitPerIntegration, }, { refetchOnMount: false, @@ -126,6 +127,7 @@ export default function DownloadClientsWidget({ clientApi.widget.downloads.subscribeToJobsAndStatuses.useSubscription( { integrationIds, + limitPerIntegration: options.limitPerIntegration, }, { onData: (data) => { diff --git a/packages/widgets/src/downloads/index.ts b/packages/widgets/src/downloads/index.ts index 5323c20df..edd4506e0 100644 --- a/packages/widgets/src/downloads/index.ts +++ b/packages/widgets/src/downloads/index.ts @@ -82,6 +82,11 @@ export const { definition, componentLoader } = createWidgetDefinition("downloads applyFilterToRatio: factory.switch({ defaultValue: true, }), + limitPerIntegration: factory.number({ + defaultValue: 50, + validate: z.number().min(1), + withDescription: true, + }), }), { defaultSort: { diff --git a/packages/widgets/src/options.ts b/packages/widgets/src/options.ts index 8ea413c50..194759396 100644 --- a/packages/widgets/src/options.ts +++ b/packages/widgets/src/options.ts @@ -43,7 +43,7 @@ interface SelectInput searchable?: boolean; } -interface NumberInput extends CommonInput { +interface NumberInput extends CommonInput { validate: z.ZodNumber; step?: number; } @@ -87,7 +87,7 @@ const optionsFactory = { }), number: (input: NumberInput) => ({ type: "number" as const, - defaultValue: input.defaultValue ?? ("" as const), + defaultValue: input.defaultValue ?? 0, step: input.step, withDescription: input.withDescription ?? false, validate: input.validate, diff --git a/packages/widgets/src/rssFeed/component.tsx b/packages/widgets/src/rssFeed/component.tsx index 672c264ec..7b3b0a5e9 100644 --- a/packages/widgets/src/rssFeed/component.tsx +++ b/packages/widgets/src/rssFeed/component.tsx @@ -55,7 +55,7 @@ export default function RssFeed({ options }: WidgetComponentProps<"rssFeed">) { dir={languageDir} c="dimmed" size="sm" - lineClamp={options.textLinesClamp as number} + lineClamp={options.textLinesClamp} dangerouslySetInnerHTML={{ __html: feedEntry.description }} /> )}