Groundwork for redrawing the icons on their pixel grids. A name is now
<icon><px> (server14, server16, server24) and must match the size token
where it renders: 14 at --icon-size-sm, 16 at -md, 24 at -xl.
tdesignLoading is the one exception, since it spins. Every name not yet
redrawn aliases its TDesign glyph, so nothing changes on screen except
the manager-open overlay icon, which moves from lg to xl ("a status
mark on a card").
- pixel-icons.ts holds the redrawn glyphs, so far add14 (the + from
3f7145d, renamed from pixelAdd).
- TS code that picks icons takes Icon14 / Icon16 (menu items, file
glyphs, the copy arrow, toasts, transfer states), so build:ui
rejects a wrong name.
- icons.spec.ts checks every <ng-icon> in the skins: it has a size
token, and each literal name, both sides of a [name] ternary
included, is registered and drawn for that token's grid.
- TDesign's file1 is now fileText, since file1 plus a suffix would
read file114.
Co-Authored-By: Claude <noreply@anthropic.com>
103 lines
3.9 KiB
TypeScript
103 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';
|
|
import type { Icon16 } from './icons';
|
|
|
|
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: Icon16; class: string; extensions: string[] }> = {
|
|
image: {
|
|
glyph: 'image16',
|
|
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: 'music16',
|
|
class: 'shrink-0 text-icon-media',
|
|
extensions: 'mp3 wav flac aac ogg oga m4a opus wma aiff aif mid midi'.split(' '),
|
|
},
|
|
video: {
|
|
glyph: 'video16',
|
|
class: 'shrink-0 text-icon-media',
|
|
extensions: 'mp4 mkv mov avi webm m4v mpg mpeg wmv flv 3gp'.split(' '),
|
|
},
|
|
archive: {
|
|
glyph: 'fileZip16',
|
|
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: 'code16',
|
|
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: 'fileText16',
|
|
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): Icon16 {
|
|
if (entry.name === '..') return 'arrowUp16';
|
|
if (entry.type === 'dir') return 'folder16';
|
|
if (entry.type === 'symlink') return 'link16';
|
|
const category = categoryOf(entry.name);
|
|
return category ? CATEGORIES[category].glyph : 'file16';
|
|
}
|
|
|
|
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;
|
|
}
|