feat: rework downloads widget with mantine-datatable
- Replace mantine-react-table with mantine-datatable for a cleaner, lighter table - Add right-click context menu with pause/resume/delete/copy actions - Add row expansion for inline detail view (replaces modal) - Implement responsive scaling based on widget width - Add footer filter panel with client and status filtering - Show status badges with color-coded indicators - Display speed indicators with icons in column cells - Add global ratio, total speeds, and item count in footer - Sort always enabled (removed confusing enableRowSorting toggle) - Reorder default columns to prioritize useful information - Update docs and translations
This commit is contained in:
7 files changed
+856
-911
No files matched your search
@@ -74,11 +74,10 @@ const optionMapping: OptionMapping = {
|
||||
categoryFilter: (oldOptions) => ("labelFilter" in oldOptions ? oldOptions.labelFilter : undefined),
|
||||
filterIsWhitelist: (oldOptions) =>
|
||||
"labelFilterIsWhitelist" in oldOptions ? oldOptions.labelFilterIsWhitelist : undefined,
|
||||
enableRowSorting: (oldOptions) => ("rowSorting" in oldOptions ? oldOptions.rowSorting : undefined),
|
||||
showCompletedTorrent: (oldOptions) =>
|
||||
"displayCompletedTorrents" in oldOptions ? oldOptions.displayCompletedTorrents : undefined,
|
||||
columns: () => ["integration", "name", "progress", "time", "actions"],
|
||||
defaultSort: () => "type",
|
||||
columns: () => ["integration", "name", "progress", "time", "state"],
|
||||
defaultSort: () => "progress",
|
||||
descendingDefaultSort: () => false,
|
||||
showCompletedUsenet: () => true,
|
||||
showCompletedHttp: () => true,
|
||||
|
||||
@@ -3174,7 +3174,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"globalRatio": "Global Ratio"
|
||||
"globalRatio": "Global Ratio",
|
||||
"contextMenu": {
|
||||
"copyName": "Copy name",
|
||||
"copyId": "Copy ID",
|
||||
"clearFilters": "Clear filters"
|
||||
},
|
||||
"footer": {
|
||||
"filter": "Filter downloads"
|
||||
}
|
||||
},
|
||||
"mediaReleases": {
|
||||
"name": "Media releases",
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -8,28 +8,37 @@ import { createWidgetDefinition } from "../definition";
|
||||
import { optionsBuilder } from "../options";
|
||||
|
||||
const columnsList = [
|
||||
"id",
|
||||
"actions",
|
||||
"added",
|
||||
"category",
|
||||
"downSpeed",
|
||||
"index",
|
||||
"integration",
|
||||
"name",
|
||||
"progress",
|
||||
"size",
|
||||
"downSpeed",
|
||||
"upSpeed",
|
||||
"time",
|
||||
"state",
|
||||
"added",
|
||||
"ratio",
|
||||
"received",
|
||||
"sent",
|
||||
"size",
|
||||
"state",
|
||||
"time",
|
||||
"category",
|
||||
"integration",
|
||||
"index",
|
||||
"type",
|
||||
"upSpeed",
|
||||
] as const satisfies (keyof ExtendedDownloadClientItem)[];
|
||||
const sortingExclusion = ["actions", "id", "state"] as const satisfies readonly (typeof columnsList)[number][];
|
||||
const columnsSort = columnsList.filter((column) =>
|
||||
sortingExclusion.every((exclusion) => exclusion !== column),
|
||||
) as Exclude<typeof columnsList, (typeof sortingExclusion)[number]>;
|
||||
|
||||
const sortColumns = [
|
||||
"name",
|
||||
"progress",
|
||||
"size",
|
||||
"downSpeed",
|
||||
"upSpeed",
|
||||
"time",
|
||||
"added",
|
||||
"ratio",
|
||||
"received",
|
||||
"sent",
|
||||
"index",
|
||||
"type",
|
||||
] as const satisfies readonly (typeof columnsList)[number][];
|
||||
|
||||
export const { definition, componentLoader } = createWidgetDefinition("downloads", {
|
||||
icon: IconDownload,
|
||||
@@ -38,19 +47,16 @@ export const { definition, componentLoader } = createWidgetDefinition("downloads
|
||||
return optionsBuilder.from(
|
||||
(factory) => ({
|
||||
columns: factory.multiSelect({
|
||||
defaultValue: ["integration", "name", "progress", "time", "actions"],
|
||||
defaultValue: ["name", "progress", "downSpeed", "time", "state"],
|
||||
options: columnsList.map((value) => ({
|
||||
value,
|
||||
label: (t) => t(`widget.downloads.items.${value}.columnTitle`),
|
||||
})),
|
||||
searchable: true,
|
||||
}),
|
||||
enableRowSorting: factory.switch({
|
||||
defaultValue: false,
|
||||
}),
|
||||
defaultSort: factory.select({
|
||||
defaultValue: "type",
|
||||
options: columnsSort.map((value) => ({
|
||||
defaultValue: "progress",
|
||||
options: sortColumns.map((value) => ({
|
||||
value,
|
||||
label: (t) => t(`widget.downloads.items.${value}.columnTitle`),
|
||||
})),
|
||||
@@ -68,7 +74,6 @@ export const { definition, componentLoader } = createWidgetDefinition("downloads
|
||||
defaultValue: true,
|
||||
}),
|
||||
activeTorrentThreshold: factory.number({
|
||||
//in KiB/s
|
||||
validate: z.number().min(0),
|
||||
defaultValue: 0,
|
||||
step: 1,
|
||||
@@ -90,12 +95,6 @@ export const { definition, componentLoader } = createWidgetDefinition("downloads
|
||||
}),
|
||||
}),
|
||||
{
|
||||
defaultSort: {
|
||||
shouldHide: (options) => !options.enableRowSorting,
|
||||
},
|
||||
descendingDefaultSort: {
|
||||
shouldHide: (options) => !options.enableRowSorting,
|
||||
},
|
||||
showCompletedUsenet: {
|
||||
shouldHide: (_, integrationKinds) =>
|
||||
!getIntegrationKindsByCategory("usenet").some((kinds) => integrationKinds.includes(kinds)),
|
||||
|
||||
@@ -1,12 +1,52 @@
|
||||
/* Make the MantineReactTable transparent so the board card's opacity-controlled
|
||||
background shows through. MRT defaults to var(--mantine-color-body) which is opaque. */
|
||||
.downloads-widget-table,
|
||||
.downloads-widget-table .mantine-Table-table,
|
||||
.downloads-widget-table .mantine-Table-thead,
|
||||
.downloads-widget-table .mantine-Table-tbody,
|
||||
.downloads-widget-table .mantine-Table-tfoot,
|
||||
.downloads-widget-table .mantine-Table-tr,
|
||||
.downloads-widget-table th,
|
||||
.downloads-widget-table td {
|
||||
/* Make the DataTable transparent so the board card's opacity-controlled
|
||||
background shows through. The mantine-datatable defaults to
|
||||
var(--mantine-color-body) which is opaque. */
|
||||
.downloads-table,
|
||||
.downloads-table .mantine-datatable-table,
|
||||
.downloads-table .mantine-datatable-table thead,
|
||||
.downloads-table .mantine-datatable-table tbody,
|
||||
.downloads-table .mantine-datatable-table tfoot,
|
||||
.downloads-table th,
|
||||
.downloads-table td {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.downloads-table .mantine-datatable-table tr {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.downloads-table .mantine-datatable-table tbody tr:hover {
|
||||
background-color: color-mix(in srgb, var(--mantine-color-default-hover) 40%, transparent) !important;
|
||||
}
|
||||
|
||||
.downloads-table th {
|
||||
white-space: nowrap;
|
||||
background-color: color-mix(in srgb, var(--mantine-color-body) 60%, transparent) !important;
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
.downloads-table td {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.downloads-table td .mantine-Text-root,
|
||||
.downloads-table td .mantine-Group-root {
|
||||
white-space: nowrap;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.downloads-table .mantine-datatable-header-cell-sortable-icon {
|
||||
opacity: 0;
|
||||
transition: opacity 150ms;
|
||||
}
|
||||
|
||||
.downloads-table th:hover .mantine-datatable-header-cell-sortable-icon {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Row expansion styling */
|
||||
.downloads-table .mantine-datatable-row-expansion-cell {
|
||||
background-color: color-mix(in srgb, var(--mantine-color-body) 40%, transparent) !important;
|
||||
}
|
||||
Reference in new issue
Block a user