Add syntax highlighting, OSD, search, perf optimizations, and UX improvements
- troika-three-text MSDF rendering for resolution-independent code text - highlight.js syntax highlighting with Catppuccin Mocha colors - Lazy text pool: max 25 concurrent code meshes, created on demand - LOD throttled to every 3 frames, OSD every 10 - 45° tiled watermark (repo/path/filename) behind code - OSD: breadcrumb, file stats, zoom level - Search: / or Ctrl+F to find and fly to files - Keybindings: WASD pan, Q/E rotate, Z/C zoom, Space overview, ? help modal - Mouse wheel zoom vs trackpad pan detection via event frequency - Zip GBK filename encoding fallback for Chinese filenames - Docker volume persistence for SQLite cache
This commit is contained in:
1
server/Cargo.lock
generated
1
server/Cargo.lock
generated
@@ -900,6 +900,7 @@ name = "repo-vis-server"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"encoding_rs",
|
||||
"hex",
|
||||
"rusqlite",
|
||||
"serde",
|
||||
|
||||
@@ -14,6 +14,7 @@ walkdir = "2"
|
||||
sha2 = "0.10"
|
||||
hex = "0.4"
|
||||
zip = "2"
|
||||
encoding_rs = "0.8"
|
||||
tempfile = "3"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = "0.3"
|
||||
|
||||
@@ -183,9 +183,40 @@ async fn scan_zip(
|
||||
let mut archive =
|
||||
zip::ZipArchive::new(cursor).map_err(|e| (StatusCode::BAD_REQUEST, format!("Invalid zip: {e}")))?;
|
||||
|
||||
archive
|
||||
.extract(tmp.path())
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("Extract failed: {e}")))?;
|
||||
// Extract manually to handle non-UTF-8 filenames (GBK from Windows zips)
|
||||
for i in 0..archive.len() {
|
||||
let mut file = archive
|
||||
.by_index_raw(i)
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("Zip entry error: {e}")))?;
|
||||
|
||||
let raw_name = file.name_raw();
|
||||
// Try UTF-8 first, then GBK fallback
|
||||
let name = match std::str::from_utf8(raw_name) {
|
||||
Ok(s) => s.to_string(),
|
||||
Err(_) => {
|
||||
let (decoded, _, _) = encoding_rs::GBK.decode(raw_name);
|
||||
decoded.into_owned()
|
||||
}
|
||||
};
|
||||
|
||||
// Sanitize: skip entries that try to escape
|
||||
if name.contains("..") {
|
||||
continue;
|
||||
}
|
||||
|
||||
let out_path = tmp.path().join(&name);
|
||||
if file.is_dir() {
|
||||
std::fs::create_dir_all(&out_path).ok();
|
||||
} else {
|
||||
if let Some(parent) = out_path.parent() {
|
||||
std::fs::create_dir_all(parent).ok();
|
||||
}
|
||||
let mut outfile = std::fs::File::create(&out_path)
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("Create file: {e}")))?;
|
||||
std::io::copy(&mut file, &mut outfile)
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("Write file: {e}")))?;
|
||||
}
|
||||
}
|
||||
|
||||
let entries: Vec<_> = std::fs::read_dir(tmp.path())
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
|
||||
|
||||
Reference in New Issue
Block a user