Stage 1: extract the skin-agnostic UI layer into app/core/
First step of splitting the UI into two skin trees. All 12 services plus platform.ts, format.ts, file-icons.ts, icons.ts and app.config.ts move to ui/src/app/core/ — the layer both skins will share. This is a pure move: git records all 17 as renames, and no file content changed beyond import paths. The premise holds because `git diff main...feat/7css-skin` touches none of these files — the service layer was already skin-agnostic, so there is nothing to untangle, only to relocate. Components now reach it through a new `@core/*` tsconfig alias rather than a relative path. That is deliberate and load-bearing for the next stage: skin components are about to move under app/skins/<skin>/ at varying depths, and an alias keeps every one of these 67 imports correct wherever its file ends up. `@shared/*` (the bun-side src/shared) already set the precedent. Intra-core imports needed no edits at all — every ./sibling reference among the moved files points at another moved file. Verified: tsc clean, `bun run build:ui` clean (AOT resolves every import and template symbol), and the running window's opaque interior is pixel-identical to a pre-refactor capture. Note on that comparison: the Aero frame and title bar do NOT compare equal between launches, and that is not a regression — .window.glass::before runs at opacity .6, so the chrome composites over whatever desktop is behind the window and shifts by a uniform ~8% brightness. The check therefore diffs the opaque interior (inset 30px sides, 80px top), where anything this refactor could break would show. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
// File-list glyphs + per-category colors ("Multi" icon mode). 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 PRIMARY = 'shrink-0 text-primary';
|
||||
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: 'mynaImage',
|
||||
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: 'mynaMusic',
|
||||
class: 'shrink-0 text-icon-media',
|
||||
extensions: 'mp3 wav flac aac ogg oga m4a opus wma aiff aif mid midi'.split(' '),
|
||||
},
|
||||
video: {
|
||||
glyph: 'mynaVideo',
|
||||
class: 'shrink-0 text-icon-media',
|
||||
extensions: 'mp4 mkv mov avi webm m4v mpg mpeg wmv flv 3gp'.split(' '),
|
||||
},
|
||||
archive: {
|
||||
glyph: 'mynaArchive',
|
||||
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: 'mynaCode',
|
||||
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: 'mynaFileText',
|
||||
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, multi: boolean): string {
|
||||
if (entry.name === '..') return 'mynaArrowUp';
|
||||
if (entry.type === 'dir') return 'mynaFolder';
|
||||
if (!multi) return 'mynaFile';
|
||||
if (entry.type === 'symlink') return 'mynaLink';
|
||||
const category = categoryOf(entry.name);
|
||||
return category ? CATEGORIES[category].glyph : 'mynaFile';
|
||||
}
|
||||
|
||||
export function fileIconClass(entry: FileEntry, multi: boolean): string {
|
||||
// plain mode: exactly the pre-color look ('..' has type 'dir' -> primary)
|
||||
if (!multi) return entry.type === 'dir' ? PRIMARY : MUTED;
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user