Files
miniSCP/ui/src/app/core/file-icons.ts
T
jgamboaandClaude 99bfd3abc2 Switch the UI icons from mynaui to TDesign
Swap @ng-icons/mynaui for @ng-icons/tdesign-icons (34.0.0, the release
matching @ng-icons/core), drawn at TDesign's native stroke. Each former
mynaui name maps to one TDesign name everywhere it was used; the unused
ArrowLeftRight is dropped. The options button's ellipsis goes from 1.5rem
to 0.8rem: TDesign's dots span 18 of 24 units against mynaui's 8, so this
keeps today's footprint.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-09-23 17:00:43 +02:00

102 lines
3.9 KiB
TypeScript

// File-list glyphs + per-category colors. Category colors
// are fixed tokens (never follow the accent) so a folder is always folder-blue.
// Only the icon glyph is colored; row text stays neutral. Docs and unknown
// files stay muted on purpose — sparing color is what keeps it scannable.
import type { FileEntry } from '@shared/types';
type Category = 'image' | 'audio' | 'video' | 'archive' | 'code' | 'doc';
// Class strings are literals so the Tailwind scanner picks them up.
const MUTED = 'shrink-0 text-muted-foreground';
const FOLDER = 'shrink-0 text-icon-folder';
const LINK = 'shrink-0 text-icon-link';
const CATEGORIES: Record<Category, { glyph: string; class: string; extensions: string[] }> = {
image: {
glyph: 'tdesignImage',
class: 'shrink-0 text-icon-image',
extensions: 'png jpg jpeg gif webp avif svg ico icns bmp tiff tif heic heif raw'.split(' '),
},
audio: {
glyph: 'tdesignMusic',
class: 'shrink-0 text-icon-media',
extensions: 'mp3 wav flac aac ogg oga m4a opus wma aiff aif mid midi'.split(' '),
},
video: {
glyph: 'tdesignVideo',
class: 'shrink-0 text-icon-media',
extensions: 'mp4 mkv mov avi webm m4v mpg mpeg wmv flv 3gp'.split(' '),
},
archive: {
glyph: 'tdesignFileZip',
class: 'shrink-0 text-icon-archive',
extensions: 'zip tar gz tgz bz2 xz zst 7z rar dmg iso jar deb rpm pkg apk'.split(' '),
},
code: {
glyph: 'tdesignCode',
class: 'shrink-0 text-icon-code',
// ".ts" is TypeScript here, not an MPEG transport stream (deliberate)
extensions: (
'js mjs cjs ts tsx jsx json jsonc yaml yml toml xml html htm css scss sass less ' +
'py rb go rs java kt kts swift c h cpp hpp cc m mm cs php sh bash zsh fish ps1 bat ' +
'sql lua pl r vue svelte astro ini conf cfg env plist gradle cmake nix proto graphql ' +
'dart ex exs zig lock makefile dockerfile zshrc bashrc bash_profile profile ' +
'gitignore gitconfig gitattributes editorconfig npmrc nvmrc'
).split(' '),
},
doc: {
glyph: 'tdesignFile1',
class: MUTED, // docs keep the neutral color; only the glyph changes
extensions: 'md markdown txt rtf pdf doc docx xls xlsx ppt pptx csv tsv log odt ods odp epub'.split(' '),
},
};
const EXT_CATEGORY = new Map<string, Category>();
for (const category of Object.keys(CATEGORIES) as Category[]) {
for (const ext of CATEGORIES[category].extensions) EXT_CATEGORY.set(ext, category);
}
/** "a.tar.gz" -> "gz"; ".zshrc" -> "zshrc"; "Makefile" (no dot) -> "makefile". */
function categoryOf(name: string): Category | null {
const dot = name.lastIndexOf('.');
const key = (dot >= 0 ? name.slice(dot + 1) : name).toLowerCase();
return EXT_CATEGORY.get(key) ?? null;
}
const KIND_KEYS: Record<Category, string> = {
image: 'image',
audio: 'audio',
video: 'video',
archive: 'archive',
code: 'code',
doc: 'document',
};
/**
* i18n key suffix for the optional Kind column (category-based, like the
* icons); '' for the synthetic '..' row. The caller resolves `kinds.<suffix>`.
*/
export function fileKindKey(entry: FileEntry): string {
if (entry.name === '..') return '';
if (entry.type === 'dir') return 'folder';
if (entry.type === 'symlink') return 'symlink';
const category = categoryOf(entry.name);
return category ? KIND_KEYS[category] : 'file';
}
export function fileGlyph(entry: FileEntry): string {
if (entry.name === '..') return 'tdesignArrowUp';
if (entry.type === 'dir') return 'tdesignFolder';
if (entry.type === 'symlink') return 'tdesignLink';
const category = categoryOf(entry.name);
return category ? CATEGORIES[category].glyph : 'tdesignFile';
}
export function fileIconClass(entry: FileEntry): string {
if (entry.name === '..') return MUTED;
if (entry.type === 'dir') return FOLDER;
if (entry.type === 'symlink') return LINK;
const category = categoryOf(entry.name);
return category ? CATEGORIES[category].class : MUTED;
}