From 5d2e9fd05b084202befdcf19e1a5ad0aeda7dc6f Mon Sep 17 00:00:00 2001 From: qu1ck Date: Fri, 15 Sep 2023 20:57:43 -0700 Subject: [PATCH] Add custom file icons for some common types Issue #81 --- src/components/fileicon.tsx | 92 +++++++++++++++++++++++++ src/components/tables/common.tsx | 4 +- src/components/tables/filetreetable.tsx | 3 +- 3 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 src/components/fileicon.tsx diff --git a/src/components/fileicon.tsx b/src/components/fileicon.tsx new file mode 100644 index 0000000..99407d2 --- /dev/null +++ b/src/components/fileicon.tsx @@ -0,0 +1,92 @@ +/** + * TrguiNG - next gen remote GUI for transmission torrent daemon + * Copyright (C) 2023 qu1ck (mail at qu1ck.org) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import * as Icon from "react-bootstrap-icons"; +import React from "react"; +import { useMantineTheme, type DefaultMantineColor } from "@mantine/core"; + +interface FileType { + icon: React.FunctionComponent, + color: DefaultMantineColor, + extensions: Readonly, +} + +const fileTypes: Readonly = [ + // Video + { + icon: Icon.Film, + extensions: [ + "3gp", "avi", "flv", "m4v", "mkv", "mov", "mp4", "mpeg", + "mpg", "rm", "swf", "vob", "webm", "wmv"], + color: "grape", + }, + // Audio + { + icon: Icon.MusicNoteBeamed, + extensions: ["aac", "aif", "cda", "flac", "m4a", "mid", "midi", "mp3", "mpa", "ogg", "wav", "wma", "wpl"], + color: "cyan", + }, + // Image + { + icon: Icon.FileEarmarkImage, + extensions: ["ai", "bmp", "gif", "ico", "jpg", "jpeg", "png", "ps", "psd", "svg", "tif", "tiff", "webp"], + color: "yellow", + }, + // Archive + { + icon: Icon.FileZip, + extensions: [ + "7z", "arc", "arj", "bz", "bz2", "cbz", "cbr", "deb", "gz", + "pkg", "rar", "rpm", "tar", "z", "zip"], + color: "lime", + }, + // Binary + { + icon: Icon.FileEarmarkBinary, + extensions: ["exe", "bin", "dmg", "dll", "apk", "jar", "msi", "sys", "cab"], + color: "red", + }, + // Text/doc + { + icon: Icon.FileEarmarkText, + extensions: ["doc", "docx", "rtf", "txt", "md", "adoc", "ass", "epub", "mobi", "fb2"], + color: "gray", + }, + // Disc image + { + icon: Icon.Disc, + extensions: ["iso", "vcd", "toast", "mdf", "nrg", "img"], + color: "cyan", + }, +] as const; + +const extensions = fileTypes.reduce>((v, c) => { + for (const ext of c.extensions) v[ext] = c; + return v; +}, {}); + +export function FileIcon({ name, selected }: { name: string, selected: boolean }) { + const theme = useMantineTheme(); + + const ext = name.substring(name.lastIndexOf(".") + 1).toLowerCase(); + const FileIcon = Object.hasOwn(extensions, ext) ? extensions[ext].icon : Icon.FileEarmark; + const color = Object.hasOwn(extensions, ext) ? extensions[ext].color : "gray"; + const shade = (theme.colorScheme === "dark" || selected) ? 3 : 9; + + return ; +} diff --git a/src/components/tables/common.tsx b/src/components/tables/common.tsx index 1586f01..daf8534 100644 --- a/src/components/tables/common.tsx +++ b/src/components/tables/common.tsx @@ -227,6 +227,7 @@ function InnerRow(props: { columnSizing: ColumnSizingState, columnVisibility: VisibilityState, columnOrder: ColumnOrderState, + selected: boolean, }) { return <> {props.row.getVisibleCells().map(cell => { @@ -245,7 +246,8 @@ const MemoizedInnerRow = memo(InnerRow, (prev, next) => { prev.expanded === next.expanded && prev.columnSizing === next.columnSizing && prev.columnVisibility === next.columnVisibility && - prev.columnOrder === next.columnOrder + prev.columnOrder === next.columnOrder && + prev.selected === next.selected ); }) as typeof InnerRow; diff --git a/src/components/tables/filetreetable.tsx b/src/components/tables/filetreetable.tsx index 90c59e0..40e4187 100644 --- a/src/components/tables/filetreetable.tsx +++ b/src/components/tables/filetreetable.tsx @@ -35,6 +35,7 @@ import { ContextMenu, useContextMenu } from "components/contextmenu"; import { useHotkeysContext } from "hotkeys"; import debounce from "lodash-es/debounce"; import { useServerRpcVersion } from "rpc/torrent"; +import { FileIcon } from "components/fileicon"; const { TAURI, invoke } = await import(/* webpackChunkName: "taurishim" */"taurishim"); type FileDirEntryKey = keyof FileDirEntry; @@ -117,7 +118,7 @@ function NameField(props: TableFieldProps) { ? props.row.getIsExpanded() ? : - : + : }