icon.rs
⎇
Raw
1use api_types::FileKind;
2use leptos::prelude::*;
3
4#[derive(Clone, Copy, PartialEq, Debug)]
5// Several variants are used from later milestones (context menu icons).
6#[allow(dead_code)]
7pub enum IconName {
8 Folder,
9 File,
10 Image,
11 Video,
12 Audio,
13 Text,
14 Code,
15 Archive,
16 Pdf,
17 Grid,
18 List,
19 Refresh,
20 Upload,
21 FolderPlus,
22 Download,
23 Pencil,
24 Move,
25 Copy,
26 Share,
27 Info,
28 Trash,
29}
30
31/// Extensions that get the "code" icon instead of the plain-text one. Both are
32/// [`FileKind::Text`] to the server — code-vs-prose is a naming question, not a
33/// content one, so it stays here.
34const CODE_EXTS: &[&str] = &[
35 "rs", "js", "mjs", "cjs", "ts", "tsx", "jsx", "py", "rb", "go", "c", "cc", "cpp", "h", "hpp",
36 "java", "kt", "cs", "php", "swift", "lua", "sh", "bash", "fish", "zsh", "ps1", "json", "toml",
37 "yaml", "yml", "html", "htm", "css", "scss", "sql", "xml", "vue", "svelte",
38];
39
40/// Pick the icon for an entry from its server-sniffed [`FileKind`].
41///
42/// `name` is used only to split [`FileKind::Text`] into prose and code.
43pub fn icon_for(kind: FileKind, name: &str) -> IconName {
44 match kind {
45 FileKind::Dir => IconName::Folder,
46 FileKind::Image => IconName::Image,
47 FileKind::Video => IconName::Video,
48 FileKind::Audio => IconName::Audio,
49 FileKind::Pdf => IconName::Pdf,
50 FileKind::Archive => IconName::Archive,
51 FileKind::Text => {
52 let ext = name.rsplit('.').next().unwrap_or("").to_lowercase();
53 if CODE_EXTS.contains(&ext.as_str()) {
54 IconName::Code
55 } else {
56 IconName::Text
57 }
58 }
59 FileKind::Binary => IconName::File,
60 }
61}
62
63fn icon_d(name: IconName) -> &'static str {
64 match name {
65 IconName::Folder => {
66 "M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"
67 }
68 IconName::File => "M6 2h8l5 5v13a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2z M13 2v6h6",
69 IconName::Image => {
70 "M3 5h18v14H3z M10 10a1.5 1.5 0 1 1-3 0a1.5 1.5 0 0 1 3 0z M21 15l-5-5-9 9"
71 }
72 IconName::Video => "M3 5h18v14H3z M10 9l5 3-5 3z",
73 IconName::Audio => {
74 "M9 18V6l10-2v10 M9 18a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0z M19 15a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0z"
75 }
76 IconName::Text => "M5 3h14v18H5z M9 8h6 M9 12h6 M9 16h4",
77 IconName::Code => "M8 8l-4 4 4 4 M16 8l4 4-4 4",
78 IconName::Archive => "M4 4h16v16H4z M12 4v2 M12 8v2 M12 12v2 M9 16h6v4H9z",
79 IconName::Pdf => {
80 "M6 2h8l5 5v13a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2z M13 2v6h6 M8 13h8 M8 17h8"
81 }
82 IconName::Grid => "M4 4h7v7H4z M13 4h7v7h-7z M4 13h7v7H4z M13 13h7v7h-7z",
83 IconName::List => "M4 6h2 M9 6h11 M4 12h2 M9 12h11 M4 18h2 M9 18h11",
84 IconName::Refresh => "M21 12a9 9 0 1 1-3-6.7 M21 3v6h-6",
85 IconName::Upload => "M12 16V4 M7 9l5-5 5 5 M4 20h16",
86 IconName::FolderPlus => {
87 "M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z M12 11v6 M9 14h6"
88 }
89 IconName::Download => "M12 4v12 M7 11l5 5 5-5 M4 20h16",
90 IconName::Pencil => "M4 20l1-4L16 5l3 3L8 19z M13 8l3 3",
91 IconName::Move => "M20 7H6 M9 4L6 7l3 3 M4 17h14 M15 14l3 3-3 3",
92 IconName::Copy => "M8 8h12v12H8z M4 16V4h12",
93 IconName::Share => "M4 13v6a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1v-6 M12 3v11 M8 7l4-4 4 4",
94 IconName::Info => "M12 3a9 9 0 1 0 .01 0z M12 11v5 M12 8h.01",
95 IconName::Trash => "M4 7h16 M9 7V4h6v3 M6 7l1 13h10l1-13 M10 11v6 M14 11v6",
96 }
97}
98
99fn icon_class(name: IconName) -> &'static str {
100 match name {
101 IconName::Folder | IconName::FolderPlus => "ic-folder",
102 _ => "ic-muted",
103 }
104}
105
106#[component]
107pub fn Icon(name: IconName, class: Option<String>) -> impl IntoView {
108 let mut cls = String::from("icon ");
109 cls.push_str(icon_class(name));
110 if let Some(extra) = class {
111 cls.push(' ');
112 cls.push_str(&extra);
113 }
114 view! {
115 <svg
116 class=cls
117 viewBox="0 0 24 24"
118 fill="none"
119 stroke="currentColor"
120 stroke_width="1.8"
121 stroke_linecap="round"
122 stroke_linejoin="round"
123 aria-hidden="true"
124 >
125 <path d=icon_d(name)/>
126 </svg>
127 }
128}
129