//! Central icon registry — every glyph in the app comes from here. //! //! All icons are Material Symbols (filled, 24x24) fetched from //! (set `material-symbols`) via the API at //! . Each variant's doc comment names the //! source glyph, so swapping any icon is a one-file change: replace the //! body (raw SVG inner markup of a 24x24 grid, filled with //! `currentColor`) with the new one from the same API. use api_types::FileKind; #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum IconName { /// material-symbols:folder Folder, /// material-symbols:folder-open FolderOpen, /// material-symbols:folder-off FolderOff, /// material-symbols:description File, /// material-symbols:image Image, /// material-symbols:video-file Video, /// material-symbols:audio-file Audio, /// material-symbols:article Text, /// material-symbols:code Code, /// material-symbols:folder-zip Archive, /// material-symbols:picture-as-pdf Pdf, /// material-symbols:grid-view Grid, /// material-symbols:view-list List, /// material-symbols:refresh Refresh, /// material-symbols:upload-file Upload, /// material-symbols:drive-folder-upload UploadFolder, /// material-symbols:create-new-folder NewFolder, /// material-symbols:note-add NewFile, /// material-symbols:download Download, /// material-symbols:open-in-full Open, /// material-symbols:drive-file-rename Rename, /// material-symbols:drive-file-move Move, /// material-symbols:content-copy Copy, /// material-symbols:link Share, /// material-symbols:info Info, /// material-symbols:delete Delete, /// material-symbols:open-in-new OpenInNew, /// material-symbols:logout Logout, /// material-symbols:menu Menu, /// material-symbols:close Close, /// material-symbols:settings Settings, /// material-symbols:search Search, /// material-symbols:stop Stop, /// material-symbols:person User, /// material-symbols:admin-panel-settings Admin, /// material-symbols:add Add, /// material-symbols:brightness-auto ThemeAuto, /// material-symbols:light-mode ThemeLight, /// material-symbols:dark-mode ThemeDark, /// material-symbols:more-vert MoreVert, /// material-symbols:chevron-right ChevronRight, /// material-symbols:arrow-upward SortAsc, /// material-symbols:arrow-downward SortDesc, /// material-symbols:pause Pause, /// material-symbols:play-arrow Play, /// material-symbols:expand-more ExpandMore, /// material-symbols:expand-less ExpandLess, /// material-symbols:calendar-month Calendar, /// material-symbols:contacts Contacts, /// material-symbols:meeting-room Room, /// material-symbols:devices Devices, } impl IconName { /// Raw SVG body for the 24x24 grid, rendered inside /// ``. pub const fn body(self) -> &'static str { match self { Self::Folder => { r#""# } Self::FolderOpen => { r#""# } Self::FolderOff => { r#""# } Self::File => { r#""# } Self::Image => { r#""# } Self::Video => { r#""# } Self::Audio => { r#""# } Self::Text => { r#""# } Self::Code => { r#""# } Self::Archive => { r#""# } Self::Pdf => { r#""# } Self::Grid => { r#""# } Self::List => { r#""# } Self::Refresh => { r#""# } Self::Upload => { r#""# } Self::UploadFolder => { r#""# } Self::NewFolder => { r#""# } Self::NewFile => { r#""# } Self::Download => { r#""# } Self::Open => { r#""# } Self::Rename => { r#""# } Self::Move => { r#""# } Self::Copy => { r#""# } Self::Share => { r#""# } Self::Info => { r#""# } Self::Delete => { r#""# } Self::OpenInNew => { r#""# } Self::Logout => { r#""# } Self::Menu => { r#""# } Self::Close => { r#""# } Self::Settings => { r#""# } Self::Search => { r#""# } Self::Stop => r#""#, Self::User => { r#""# } Self::Admin => { r#""# } Self::Add => r#""#, Self::ThemeAuto => { r#""# } Self::ThemeLight => { r#""# } Self::ThemeDark => { r#""# } Self::MoreVert => { r#""# } Self::ChevronRight => { r#""# } Self::Pause => r#""#, Self::Play => r#""#, Self::ExpandMore => { r#""# } Self::ExpandLess => { r#""# } Self::SortAsc => { r#""# } Self::SortDesc => { r#""# } Self::Calendar => { r#""# } Self::Contacts => { r#""# } Self::Room => { r#""# } Self::Devices => { r#""# } } } } /// File types that get the "code" icon instead of the plain-text one. Both are /// [`FileKind::Text`] to the server — code-vs-prose is a naming question, not /// a content one, so it stays here. const CODE_EXTS: &[&str] = &[ "rs", "js", "mjs", "cjs", "ts", "tsx", "jsx", "py", "rb", "go", "c", "cc", "cpp", "h", "hpp", "java", "kt", "cs", "php", "swift", "lua", "sh", "bash", "fish", "zsh", "ps1", "json", "toml", "yaml", "yml", "html", "htm", "css", "scss", "sql", "xml", "vue", "svelte", ]; /// Pick the icon for an entry from its server-sniffed [`FileKind`]. /// /// `name` is used only to split [`FileKind::Text`] into prose and code. pub fn icon_for(kind: FileKind, name: &str) -> IconName { match kind { FileKind::Dir => IconName::Folder, FileKind::Image => IconName::Image, FileKind::Video => IconName::Video, FileKind::Audio => IconName::Audio, FileKind::Pdf => IconName::Pdf, FileKind::Archive => IconName::Archive, FileKind::Text => { let ext = name.rsplit('.').next().unwrap_or("").to_lowercase(); if CODE_EXTS.contains(&ext.as_str()) { IconName::Code } else { IconName::Text } } FileKind::Binary => IconName::File, } } impl IconName { /// Folder-shaped glyphs are tinted with the accent color in the browser. pub const fn is_folder(self) -> bool { matches!(self, Self::Folder | Self::FolderOpen) } }