Skip to content

Commit

Permalink
feat: add default sorting and fix Icon sizing warnings
Browse files Browse the repository at this point in the history
  • Loading branch information
SeDemal committed Aug 2, 2024
1 parent 134728a commit 3ed7adc
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 90 deletions.
12 changes: 9 additions & 3 deletions packages/translation/src/lang/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1040,6 +1040,15 @@ export default {
columns: {
label: "Columns to show",
},
enableRowSorting: {
label: "Enable items sorting",
},
defaultSort: {
label: "Column used for sorting by default",
},
descendingDefaultSort: {
label: "Invert sorting",
},
showCompletedUsenet: {
label: "Show usenet entries marked as completed",
},
Expand All @@ -1058,9 +1067,6 @@ export default {
applyFilterToRatio: {
label: "Use filter to calculate Ratio",
},
enableRowSorting: {
label: "Enable items sorting",
},
},
errors: {
noColumns: "Select Columns in Items",
Expand Down
141 changes: 78 additions & 63 deletions packages/widgets/src/downloads/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,13 @@ import { useScopedI18n } from "@homarr/translation/client";
import type { WidgetComponentProps } from "../definition";

//TODO:
// - Data Subscription permission issues <- Need help
// - Rename getData function and following
// - Update data on integration added <- update useSubscription integrationIds not working?
// - Make modals sizes relative (Based on whole screen)
// - table tbody hide under thead and keep transparency <- Need help
// - Add integrations to shouldHide options <- Potential help needed
// - default sorting option <- but I don't wannaaaaa....
// - Add integrations to shouldHide options <- Need help
// - Move columns ratio table to css vars
// - tests maybe?
// - Unexpected value xxxxx parsing width/height attribute <- Need help (Actually impacts all widgets using cq and var sizes...), Not critical

//Ratio table for relative width between columns
const columnsRatios: Record<keyof ExtendedDownloadClientItem, number> = {
Expand Down Expand Up @@ -93,25 +93,10 @@ export default function DownloadClientsWidget({
//Translations
const t = useScopedI18n("widget.downloads");
const tCommon = useScopedI18n("common");
const noIntegrationError = useScopedI18n("integration.permission")("use");
//Item modal state and selection
const [clickedIndex, setClickedIndex] = useState<number>(0);
const [opened, { open, close }] = useDisclosure(false);

if (integrationIds.length === 0)
return (
<Center h="100%">
<Text fz="7.5cqw">{noIntegrationError}</Text>
</Center>
);

if (options.columns.length === 0)
return (
<Center h="100%">
<Text fz="7.5cqw">{t("errors.noColumns")}</Text>
</Center>
);

//Get API mutation functions
const { mutate: mutateResumeItem } = clientApi.widget.downloads.resumeItem.useMutation();
const { mutate: mutatePauseItem } = clientApi.widget.downloads.pauseItem.useMutation();
Expand Down Expand Up @@ -175,7 +160,8 @@ export default function DownloadClientsWidget({
},
};
}),
),
)
.sort(({ type: typeA }, { type: typeB }) => typeA.length - typeB.length),
[currentItems, integrationIds, options],
);

Expand Down Expand Up @@ -316,9 +302,9 @@ export default function DownloadClientsWidget({
size="calc(var(--ratioWidth)*0.75)"
>
{isPaused ? (
<IconPlayerPlay size="calc(var(--ratioWidth)*0.5)" />
<IconPlayerPlay style={{ height: "calc(var(--ratioWidth)*0.5)", width: "calc(var(--ratioWidth)*0.5)" }} />
) : (
<IconPlayerPause size="calc(var(--ratioWidth)*0.5)" />
<IconPlayerPause style={{ height: "calc(var(--ratioWidth)*0.5)", width: "calc(var(--ratioWidth)*0.5)" }} />
)}
</ActionIcon>
</Tooltip>
Expand Down Expand Up @@ -350,7 +336,7 @@ export default function DownloadClientsWidget({
</Modal>
<Tooltip label={t("actions.item.delete.title")}>
<ActionIcon color="red" radius={999} onClick={open} size="calc(var(--ratioWidth)*0.75)">
<IconTrash size="calc(var(--ratioWidth)*0.5)" />
<IconTrash style={{ height: "calc(var(--ratioWidth)*0.5)", width: "calc(var(--ratioWidth)*0.5)" }} />
</ActionIcon>
</Tooltip>
</Group>
Expand All @@ -373,7 +359,7 @@ export default function DownloadClientsWidget({
return (
category !== undefined && (
<Tooltip label={category}>
<IconInfoCircle />
<IconInfoCircle style={{ height: "calc(var(--ratioWidth)*2/3)", width: "calc(var(--ratioWidth)*2/3)" }} />
</Tooltip>
)
);
Expand All @@ -388,13 +374,13 @@ export default function DownloadClientsWidget({
},
},
{
...columnsDefBase({ key: "id", showHeader: false }),
...columnsDefBase({ key: "id", showHeader: false, align: "center" }),
enableSorting: false,
Cell: ({ cell }) => {
const id = cell.getValue<ExtendedDownloadClientItem["id"]>();
return (
<Tooltip label={id}>
<IconCirclesRelation />
<IconCirclesRelation style={{ height: "calc(var(--ratioWidth)*2/3)", width: "calc(var(--ratioWidth)*2/3)" }} />
</Tooltip>
);
},
Expand Down Expand Up @@ -493,7 +479,7 @@ export default function DownloadClientsWidget({
Cell: ({ cell }) => {
const time = cell.getValue<ExtendedDownloadClientItem["time"]>();
return time === 0 ? (
<IconInfinity size="calc(var(--ratioWidth)*2/3)" />
<IconInfinity style={{ height: "calc(var(--ratioWidth)*2/3)", width: "calc(var(--ratioWidth)*2/3)" }} />
) : (
<Text>{dayjs().add(time).fromNow()}</Text>
);
Expand Down Expand Up @@ -553,6 +539,10 @@ export default function DownloadClientsWidget({
setOptions({ newOptions: { columns: columnOrder } });
},
initialState: {
sorting:
options.defaultSort != undefined
? [{ id: options.defaultSort, desc: options.descendingDefaultSort }]
: undefined,
columnVisibility: {
actions: false,
added: false,
Expand Down Expand Up @@ -591,6 +581,21 @@ export default function DownloadClientsWidget({
{ up: 0, down: 0 },
);

if (integrationIds.length === 0)
return (
<Center h="100%">
<Text fz="7.5cqw">{tCommon("errors.noIntegration")}</Text>
</Center>
);

if (options.columns.length === 0)
return (
<Center h="100%">
<Text fz="7.5cqw">{t("errors.noColumns")}</Text>
</Center>
);
//InfoModal and ClientControls hook might trigger here.

//The actual widget
return (
<Stack gap={0} h="100%" display="flex" style={baseStyle}>
Expand Down Expand Up @@ -621,42 +626,50 @@ interface ItemInfoModalProps {
}

const ItemInfoModal = ({ items, currentIndex, opened, onClose }: ItemInfoModalProps) => {
const item = useMemo<ExtendedDownloadClientItem | undefined>(() => items[currentIndex], [items, currentIndex, opened]);
const item = useMemo<ExtendedDownloadClientItem | undefined>(
() => items[currentIndex],
[items, currentIndex, opened],
);
const t = useScopedI18n("widget.downloads.states");
return (
<Modal opened={opened} onClose={onClose} centered title={item?.id ?? "ERROR"} size="auto">
{item === undefined ? <Center>{"No item found"}</Center> :
<Stack align="center">
<Title>{item.name}</Title>
<Group>
<Avatar src={getIconUrl(item.integration.kind)} />
<Text>{`${item.integration.name} (${item.integration.kind})`}</Text>
</Group>
<NormalizedLine itemKey="index" values={item.index} />
<NormalizedLine itemKey="type" values={item.type} />
<NormalizedLine itemKey="state" values={t(item.state)} />
<NormalizedLine
itemKey="upSpeed"
values={item.upSpeed === undefined ? undefined : humanFileSize(item.upSpeed)?.toString().concat("/s")}
/>
<NormalizedLine
itemKey="downSpeed"
values={item.downSpeed === undefined ? undefined : humanFileSize(item.downSpeed)?.toString().concat("/s")}
/>
<NormalizedLine itemKey="sent" values={item.sent === undefined ? undefined : humanFileSize(item.sent)} />
<NormalizedLine itemKey="received" values={humanFileSize(item.received)} />
<NormalizedLine itemKey="size" values={humanFileSize(item.size)} />
<NormalizedLine
itemKey="progress"
values={new Intl.NumberFormat("en", { style: "percent", notation: "compact", unitDisplay: "narrow" }).format(
item.progress,
)}
/>
<NormalizedLine itemKey="ratio" values={item.ratio} />
<NormalizedLine itemKey="added" values={item.added === undefined ? "unknown" : dayjs(item.added).format()} />
<NormalizedLine itemKey="time" values={item.time !== 0 ? dayjs().add(item.time).format() : "∞"} />
<NormalizedLine itemKey="category" values={item.category} />
</Stack>}
{item === undefined ? (
<Center>{"No item found"}</Center>
) : (
<Stack align="center">
<Title>{item.name}</Title>
<Group>
<Avatar src={getIconUrl(item.integration.kind)} />
<Text>{`${item.integration.name} (${item.integration.kind})`}</Text>
</Group>
<NormalizedLine itemKey="index" values={item.index} />
<NormalizedLine itemKey="type" values={item.type} />
<NormalizedLine itemKey="state" values={t(item.state)} />
<NormalizedLine
itemKey="upSpeed"
values={item.upSpeed === undefined ? undefined : humanFileSize(item.upSpeed)?.toString().concat("/s")}
/>
<NormalizedLine
itemKey="downSpeed"
values={item.downSpeed === undefined ? undefined : humanFileSize(item.downSpeed)?.toString().concat("/s")}
/>
<NormalizedLine itemKey="sent" values={item.sent === undefined ? undefined : humanFileSize(item.sent)} />
<NormalizedLine itemKey="received" values={humanFileSize(item.received)} />
<NormalizedLine itemKey="size" values={humanFileSize(item.size)} />
<NormalizedLine
itemKey="progress"
values={new Intl.NumberFormat("en", {
style: "percent",
notation: "compact",
unitDisplay: "narrow",
}).format(item.progress)}
/>
<NormalizedLine itemKey="ratio" values={item.ratio} />
<NormalizedLine itemKey="added" values={item.added === undefined ? "unknown" : dayjs(item.added).format()} />
<NormalizedLine itemKey="time" values={item.time !== 0 ? dayjs().add(item.time).format() : "∞"} />
<NormalizedLine itemKey="category" values={item.category} />
</Stack>
)}
</Modal>
);
};
Expand Down Expand Up @@ -708,7 +721,9 @@ const ClientsControl = ({ clients, style }: ClientsControlProps) => {
clients.forEach((client) =>
client.paused ? pausedIntegrations.push(client.integration.id) : activeIntegrations.push(client.integration.id),
);
const totalSpeed = humanFileSize(clients.reduce((count, { rates: { down } }) => count + down, 0))?.toString().concat("/s");
const totalSpeed = humanFileSize(clients.reduce((count, { rates: { down } }) => count + down, 0))
?.toString()
.concat("/s");
const { mutate: mutateResumeQueue } = clientApi.widget.downloads.resume.useMutation();
const { mutate: mutatePauseQueue } = clientApi.widget.downloads.pause.useMutation();
const [opened, { open, close }] = useDisclosure(false);
Expand All @@ -728,7 +743,7 @@ const ClientsControl = ({ clients, style }: ClientsControlProps) => {
variant="light"
onClick={() => mutateResumeQueue({ integrationIds: pausedIntegrations })}
>
<IconPlayerPlay size="calc(var(--ratioWidth)*0.75)" />
<IconPlayerPlay style={{ height: "calc(var(--ratioWidth)*0.75)", width: "calc(var(--ratioWidth)*0.75)" }} />
</ActionIcon>
</Tooltip>
<Modal opened={opened} onClose={close} title={t("clients.modalTitle")} centered size="auto">
Expand Down Expand Up @@ -807,7 +822,7 @@ const ClientsControl = ({ clients, style }: ClientsControlProps) => {
variant="light"
onClick={() => mutatePauseQueue({ integrationIds: activeIntegrations })}
>
<IconPlayerPause size="calc(var(--ratioWidth)*0.75)" />
<IconPlayerPause style={{ height: "calc(var(--ratioWidth)*0.75)", width: "calc(var(--ratioWidth)*0.75)" }} />
</ActionIcon>
</Tooltip>
</Group>
Expand Down
62 changes: 38 additions & 24 deletions packages/widgets/src/downloads/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,46 @@ import { createWidgetDefinition } from "../definition";
import { optionsBuilder } from "../options";
import type { ExtendedDownloadClientItem } from "@homarr/integrations";

const columnsList = [
"id",
"actions",
"added",
"category",
"downSpeed",
"index",
"integration",
"name",
"progress",
"ratio",
"received",
"sent",
"size",
"state",
"time",
"type",
"upSpeed"
] as const satisfies (keyof ExtendedDownloadClientItem)[]
const columnsSort = columnsList.filter((v) => !["actions","id","state"].includes(v));

export const { definition, componentLoader, serverDataLoader } = createWidgetDefinition("downloads", {
icon: IconDownload,
options: optionsBuilder.from(
(factory) => ({
columns: factory.multiSelect({
defaultValue: ["integration", "name", "progress", "time", "actions"],
options: (
[
"id",
"actions",
"added",
"category",
"downSpeed",
"index",
"integration",
"name",
"progress",
"ratio",
"received",
"sent",
"size",
"state",
"time",
"type",
"upSpeed",
] as const satisfies (keyof ExtendedDownloadClientItem)[]
).map((value) => ({ value, label: (t) => t(`widget.downloads.items.${value}.columnTitle`) })),
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) => ({ value, label: (t) => t(`widget.downloads.items.${value}.columnTitle`) })),
}),
descendingDefaultSort: factory.switch({
defaultValue: false,
}),
showCompletedUsenet: factory.switch({
defaultValue: true,
}),
Expand All @@ -57,11 +68,14 @@ export const { definition, componentLoader, serverDataLoader } = createWidgetDef
applyFilterToRatio: factory.switch({
defaultValue: true,
}),
enableRowSorting: factory.switch({
defaultValue: false,
}),
}),
{
defaultSort: {
shouldHide: (options) => !options.enableRowSorting,
},
descendingDefaultSort: {
shouldHide: (options) => !options.enableRowSorting,
},
showCompletedUsenet: {
shouldHide: () => false, //Get from presence of usenet client in integration list
},
Expand Down

0 comments on commit 3ed7adc

Please sign in to comment.