diff --git a/app/src/assets/style/style.css b/app/src/assets/style/style.css index 582e3c9..68ecb4f 100644 --- a/app/src/assets/style/style.css +++ b/app/src/assets/style/style.css @@ -49,14 +49,15 @@ body { color: var(--text-primary); line-height: 1.6; transition: background-color 0.3s ease, color 0.3s ease; + overflow: hidden; } .container { margin: 4vh; padding: 0; - min-height: 100vh; display: flex; flex-direction: column; + height: 92vh; } .header { @@ -131,9 +132,23 @@ body { } .theme-toggle { + width: 1.75rem; + height: 1.75rem; + padding: 0; + border: none; background: var(--text-secondary); -webkit-mask: var(--icon) center/60% no-repeat; mask: var(--icon) center/60% no-repeat; + -webkit-mask-size: 60%; + mask-size: 60%; + background-repeat: no-repeat; + background-position: center; + border-radius: 6px; + cursor: pointer; +} + +.theme-toggle:active { + transform: scale(0.98); } .btn:hover { @@ -147,15 +162,16 @@ body { } .content { - padding: 1.5rem; + padding: 0 1.5rem; display: flex; justify-content: space-between; - align-items: center; flex-wrap: wrap; gap: 1rem; border-bottom: 1px solid var(--border-color); background-color: var(--bg-secondary); transition: background-color 0.3s ease, border-color 0.3s ease; + overflow: scroll; + height: 100%; } .meta { @@ -221,6 +237,13 @@ body { overflow-x: auto; } +.file-row a, +.btn, +.theme-toggle { + outline: none; + -webkit-tap-highlight-color: transparent; +} + .file-list { display: flex; flex-direction: column; @@ -229,14 +252,11 @@ body { background: transparent; border-radius: 0.25rem; overflow: hidden; - /* if you want a border around the whole list: */ - /* border: 1px solid var(--border-color); */ } -/* Each row uses grid to emulate columns */ .file-row { display: grid; - grid-template-columns: 2.5rem 1fr 10.5rem 14rem; /* icon | name | size | modified */ + grid-template-columns: 2.5rem 1fr 10.5rem 14rem; align-items: center; gap: 0.75rem; padding: 0.75rem; @@ -246,12 +266,22 @@ body { background: transparent; } -/* last row - optional: remove border */ +.file-row > div:first-child { + display: flex; + align-items: center; + justify-content: center; + align-self: center; +} + +.file-row .name { + align-self: center; + display: block; +} + .file-list .file-row:last-child { border-bottom: none; } -/* icon cell (keeps the look from your table td.icon) */ .file-row > div:first-child { width: 2.5rem; height: 2rem; @@ -263,20 +293,26 @@ body { /* name cell */ .file-row .name { + display: flex; + align-items: center; /* vertically center the link text with the icon */ + min-width: 0; /* allow ellipsis to work inside flex items */ font-weight: 500; color: var(--text-primary); - overflow: hidden; + overflow: hidden; /* keep long names from breaking layout */ text-overflow: ellipsis; white-space: nowrap; } /* name link */ .file-row .name a { - color: inherit; - text-decoration: none; - display: inline-block; + display: block; /* block-level so height:100% works reliably */ width: 100%; - overflow: hidden; + height: 100%; /* fill the .name (including padding row gives) */ + line-height: 1.2; /* control baseline — tweak if you want tighter/looser text */ + text-decoration: none; + color: inherit; + padding: 0; /* avoid extra internal vertical padding */ + overflow: hidden; /* ensure ellipsis works on the link text */ text-overflow: ellipsis; white-space: nowrap; } @@ -302,8 +338,7 @@ body { } /* hover / focus */ -.file-row:hover, -.file-row:focus-within { +.file-row:hover { background-color: var(--file-hover); } @@ -311,11 +346,6 @@ body { transform: translateY(0.5px); } -.file-row a:focus { - outline: 2px solid var(--accent); - outline-offset: 2px; -} - .file-list.compact .file-row { padding: 0.5rem; grid-template-columns: 2rem 1fr 8.5rem 12rem; @@ -347,13 +377,15 @@ body { grid-template-columns: 2.5rem 1fr; grid-template-rows: auto auto; gap: 0.25rem 0.5rem; - align-items: start; + align-items: center; /* center items rather than start */ padding: 0.6rem; } .file-row > div:first-child { grid-row: 1 / span 2; - align-self: start; + align-self: center; /* was start, now center */ + width: 2.5rem; + height: 2.5rem; /* slightly bigger on mobile for touch */ } .file-row .name { @@ -361,6 +393,14 @@ body { grid-row: 1; white-space: normal; overflow: visible; + align-self: center; + } + + @media (max-width: 480px) { + .file-row .name a { + padding-left: 4px; + padding-right: 4px; + } } .file-row .size { @@ -376,6 +416,15 @@ body { } } +@media (max-width: 480px) { + .theme-toggle { + width: 1.5rem; + height: 1.5rem; + -webkit-mask-size: 56%; + mask-size: 56%; + } +} + .view-list .file-list { display: block; } diff --git a/app/src/template/dashboard.html b/app/src/template/dashboard.html index 8e6af6f..f4e8859 100644 --- a/app/src/template/dashboard.html +++ b/app/src/template/dashboard.html @@ -13,7 +13,7 @@
-
+
@{directory}
@@ -27,8 +27,8 @@
-
-
+
+
@{files}
diff --git a/app/src/util/structs.v b/app/src/util/structs.v index 5491bb7..2fae691 100644 --- a/app/src/util/structs.v +++ b/app/src/util/structs.v @@ -3,6 +3,7 @@ module util import os import time +import encoding.base64 pub struct Database { pub: @@ -32,10 +33,51 @@ pub: is_dir bool size i64 modified i64 + icon string } pub struct Utility {} +pub fn Utility.get_icon(ext string, is_dir bool) string { + icons := { + 'dir': '' //'icon-dir' + 'pdf': '' // 'icon-pdf', + 'txt': '' + 'md': '' + 'json': '' + 'xml': '' + 'jpg': '' + 'jpeg': '' + 'png': '' + 'gif': '' + 'svg': '' + 'zip': '' + 'rar': '' + '7z': '' + 'mp3': '' + 'mp4': '' + 'avi': '' + 'mov': '' + 'java': '' + 'py': '' + 'js': '' + 'ts': '' + 'v': '' + 'html': '' + 'css': '' + 'exe': '' + 'unknown': '' + } + + return Utility.svg_to_data_uri(if is_dir { icons['dir'] } else { icons[ext] or { + icons['unknown']} }) +} + +pub fn Utility.svg_to_data_uri(svg string) string { + return svg + // return 'data:image/svg+xml;base64,${base64.encode(svg.bytes())}' +} + pub fn Utility.format_size(bytes i64) string { if bytes == 0 { return '-' @@ -70,6 +112,7 @@ pub fn Utility.list_files(dir_path string, relative string) ![]FileEntry { full_path := os.join_path(dir_path, file) is_dir := os.is_dir(full_path) file_info := os.stat(full_path)! + file_ext := full_path.split('.').last() entries << FileEntry{ name: if is_dir { '${file}/' } else { file } @@ -78,6 +121,7 @@ pub fn Utility.list_files(dir_path string, relative string) ![]FileEntry { is_dir: is_dir size: if !is_dir { file_info.size } else { 0 } modified: file_info.mtime + icon: Utility.get_icon(file_ext, is_dir) } } @@ -139,7 +183,7 @@ pub fn HtmlBuilder.generate_file_list(entries []FileEntry, current_path string) return '

This folder is empty

', '0 directories0 files0B total' } - mut html := '' + mut html := '
' mut dir_count := 0 mut file_count := 0 @@ -149,21 +193,25 @@ pub fn HtmlBuilder.generate_file_list(entries []FileEntry, current_path string) if entry.is_dir { dir_count++ file_class := 'directory' - html += '
' - html += '' - html += '' - html += '' + html += '
' + html += '
${entry.icon}
' + html += '' + html += '
-
' + html += '
${Utility.format_date(entry.modified)}
' + html += '
' } else { file_count++ total_size += entry.size - html += '' - html += '' - html += '' - html += '' + html += '
' + html += '
${entry.icon}
' + html += '' + html += '
${Utility.format_size(entry.size)}
' + html += '
${Utility.format_date(entry.modified)}
' + html += '
' } } - html += '
NameSizeModified
icon${entry.name}-${Utility.format_date(entry.modified)}
icon${entry.name}${Utility.format_size(entry.size)}${Utility.format_date(entry.modified)}
' + html += '
' return html, '${dir_count} directories${file_count} files${Utility.format_size(total_size)} total' }