From dc72fe47e1e5fe7f51c7e4c9b4481bea68fc2f39 Mon Sep 17 00:00:00 2001 From: RarDog Date: Thu, 27 Aug 2026 12:59:46 +0300 Subject: [PATCH] feat: ZeroSend v2.5.1 - Revamped 3-Step Live Folder Auto-Sync, Dynamic Credits & UI Polish --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/main.rs | 2 +- src/transfer/server.rs | 3 +- src/ui/gui.rs | 348 ++++++++++++++++++++++++----------------- 5 files changed, 212 insertions(+), 145 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6f0c49a..45e0431 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6372,7 +6372,7 @@ checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" [[package]] name = "zerosend" -version = "2.5.0" +version = "2.5.1" dependencies = [ "arboard", "axum", diff --git a/Cargo.toml b/Cargo.toml index fc729ce..ecd5dcf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "zerosend" -version = "2.5.0" +version = "2.5.1" edition = "2021" description = "Fast, peer-to-peer file transfer designed for ZeroTier, Tailscale, Radmin VPN and LAN" authors = ["RarDog"] diff --git a/src/main.rs b/src/main.rs index 9a6af86..c069685 100644 --- a/src/main.rs +++ b/src/main.rs @@ -298,7 +298,7 @@ async fn main() -> Result<(), Box> { .with_inner_size([config.window_width, config.window_height]) .with_min_inner_size([780.0, 500.0]) .with_visible(!start_minimized) - .with_title("ZeroSend v2.5.0 - P2P Sharing for ZeroTier & VPN"), + .with_title(format!("ZeroSend v{} - P2P Sharing for ZeroTier & VPN", env!("CARGO_PKG_VERSION"))), ..Default::default() }; diff --git a/src/transfer/server.rs b/src/transfer/server.rs index 8eda670..0924585 100644 --- a/src/transfer/server.rs +++ b/src/transfer/server.rs @@ -2330,7 +2330,7 @@ async fn handle_web_ui(State(state): State>) -> Html {
@@ -2766,6 +2766,7 @@ async fn handle_web_ui(State(state): State>) -> Html { "###, device_name = state.my_info.name, net_type = state.my_info.net_type, + version = env!("CARGO_PKG_VERSION"), ); Html(html) diff --git a/src/ui/gui.rs b/src/ui/gui.rs index 2186ed4..028e03c 100644 --- a/src/ui/gui.rs +++ b/src/ui/gui.rs @@ -2023,62 +2023,6 @@ impl ZeroSendApp { } fn render_my_shares(&mut self, ui: &mut Ui, theme: ThemeColors) { - // WebDAV Windows Explorer Network Drive Card - Frame::none() - .fill(theme.bg_card) - .rounding(Rounding::same(8.0)) - .stroke(theme.stroke_border) - .inner_margin(Margin::same(12.0)) - .show(ui, |ui| { - ui.horizontal(|ui| { - ui.label(RichText::new("🌐 WebDAV Windows Drive Mount").strong().color(theme.text_primary)); - ui.label( - RichText::new(format!("(Local WebDAV: http://127.0.0.1:{}/webdav)", self.config.webdav_port)) - .size(11.0) - .color(theme.text_muted), - ); - - ui.with_layout(Layout::right_to_left(Align::Center), |ui| { - if self.webdav_mounted { - if ui.button(RichText::new("✕ Unmount Z: Drive").color(Color32::from_rgb(239, 68, 68))).clicked() { - let _ = crate::transfer::unmount_webdav_drive(self.webdav_drive_letter); - self.webdav_mounted = false; - self.webdav_status_msg = Some("Unmounted drive".to_string()); - } - ui.label(RichText::new(format!("● Mounted as Drive {}:", self.webdav_drive_letter)).color(Color32::from_rgb(34, 197, 94)).strong()); - } else { - let mount_btn = Button::new( - RichText::new("🔗 Mount as Network Drive Z: in Windows Explorer") - .strong() - .color(Color32::WHITE), - ) - .fill(Color32::from_rgb(37, 99, 235)) - .rounding(Rounding::same(6.0)); - - if ui.add(mount_btn).clicked() { - match crate::transfer::mount_webdav_drive(self.webdav_drive_letter, self.config.webdav_port) { - Ok(msg) => { - self.webdav_mounted = true; - self.webdav_status_msg = Some(msg); - let _ = std::process::Command::new("explorer").arg(format!("{}:\\", self.webdav_drive_letter)).spawn(); - } - Err(e) => { - self.webdav_status_msg = Some(format!("Error: {}", e)); - } - } - } - } - }); - }); - - if let Some(msg) = &self.webdav_status_msg { - ui.add_space(4.0); - ui.label(RichText::new(msg).size(11.0).color(theme.text_muted)); - } - }); - - ui.add_space(8.0); - if self.shared_folders.is_empty() { Frame::none() .fill(theme.bg_card) @@ -2089,7 +2033,7 @@ impl ZeroSendApp { ui.vertical_centered(|ui| { ui.label(RichText::new("📁").size(36.0)); ui.label( - RichText::new("You haven't shared any local folders yet.") + RichText::new("У вас пока нет локальных общих папок.") .size(15.0) .strong() .color(theme.text_primary), @@ -2097,15 +2041,15 @@ impl ZeroSendApp { ui.add_space(4.0); ui.label( RichText::new( - "Share movies, music, or code folders so your friends on ZeroTier/LAN can stream and download them.", + "Откройте доступ к папкам с фильмами, музыкой или кодом, чтобы ваши друзья в ZeroTier/LAN могли смотреть и скачивать их.", ) .size(12.0) .color(theme.text_muted), ); ui.add_space(12.0); - let choose_btn = Button::new(RichText::new("➕ Choose Folder to Share").strong().color(Color32::WHITE)) + let choose_btn = Button::new(RichText::new("➕ Выбрать папку для общего доступа").strong().color(Color32::WHITE)) .fill(Color32::from_rgb(34, 197, 94)) - .min_size(Vec2::new(180.0, 34.0)); + .min_size(Vec2::new(220.0, 36.0)); if ui.add(choose_btn).clicked() { if let Some(folder) = FileDialog::new().pick_folder() { @@ -2147,9 +2091,9 @@ impl ZeroSendApp { ui.label(RichText::new(&share.name).strong().size(14.0).color(theme.text_primary)); let (ro_label, ro_color) = if share.read_only { - ("Read-Only (Streaming/Download)", Color32::from_rgb(59, 130, 246)) + ("Только чтение (Стриминг/Скачивание)", Color32::from_rgb(59, 130, 246)) } else { - ("Read & Write (Uploads Allowed)", Color32::from_rgb(34, 197, 94)) + ("Чтение и запись (Загрузка разрешена)", Color32::from_rgb(34, 197, 94)) }; Frame::none() @@ -2169,7 +2113,7 @@ impl ZeroSendApp { .inner_margin(Margin::symmetric(6.0, 2.0)) .show(ui, |ui| { ui.label( - RichText::new("🔒 PIN Protected") + RichText::new("🔒 PIN-защита") .size(10.5) .color(Color32::from_rgb(245, 158, 11)), ); @@ -2186,15 +2130,15 @@ impl ZeroSendApp { }); ui.with_layout(Layout::right_to_left(Align::Center), |ui| { - if ui.button(RichText::new("🗑 Remove").color(Color32::from_rgb(239, 68, 68))).clicked() { + if ui.button(RichText::new("🗑 Удалить").color(Color32::from_rgb(239, 68, 68))).clicked() { remove_index = Some(idx); } - if ui.button("📂 Open Folder").clicked() { + if ui.button("📂 Открыть папку").clicked() { let _ = std::process::Command::new("explorer").arg(&share.path).spawn(); } - if ui.checkbox(&mut share.read_only, "Read-Only").changed() { + if ui.checkbox(&mut share.read_only, "Только чтение").changed() { config_changed = true; } }); @@ -2215,80 +2159,184 @@ impl ZeroSendApp { } fn render_auto_sync_subtab(&mut self, ui: &mut Ui, theme: ThemeColors) { + // Explanatory Banner + Frame::none() + .fill(if theme.is_dark { Color32::from_rgb(15, 23, 42) } else { Color32::from_rgb(238, 242, 255) }) + .rounding(Rounding::same(8.0)) + .stroke(Stroke::new(1.0_f32, Color32::from_rgb(99, 102, 241))) + .inner_margin(Margin::symmetric(14.0, 10.0)) + .show(ui, |ui| { + ui.horizontal(|ui| { + ui.label(RichText::new("🔄").size(22.0)); + ui.vertical(|ui| { + ui.label( + RichText::new("Двусторонняя авто-синхронизация (Two-Way Live Sync)") + .strong() + .size(14.0) + .color(theme.text_primary), + ); + ui.label( + RichText::new( + "Автоматически зеркалирует локальную папку на вашем ПК с общей папкой друга в фоновом режиме каждые 8 секунд без лишних действий.", + ) + .size(11.5) + .color(theme.text_secondary), + ); + }); + }); + }); + + ui.add_space(10.0); + + // Collect all online friends with shared folders + let mut online_shares: Vec<(String, String, String, String)> = Vec::new(); + for peer in &self.peers { + if peer.is_online && !peer.shared_folders.is_empty() { + let (pname, _) = self.get_peer_display_name(peer); + for folder in &peer.shared_folders { + online_shares.push((peer.peer_id.clone(), pname.clone(), folder.id.clone(), folder.name.clone())); + } + } + } + + // STEP-BY-STEP PAIRING WIZARD Frame::none() .fill(theme.bg_card) .rounding(Rounding::same(8.0)) .stroke(theme.stroke_border) .inner_margin(Margin::same(14.0)) .show(ui, |ui| { - ui.horizontal(|ui| { - ui.label(RichText::new("🔄 Two-Way Live Folder Synchronization").strong().size(14.0).color(theme.text_primary)); - ui.label( - RichText::new("(Automatic real-time background sync with friend's shared folder)") - .size(11.0) - .color(theme.text_muted), - ); - }); - + ui.label(RichText::new("➕ Настройка новой авто-синхронизации (3 простых шага)").strong().size(13.5).color(theme.text_accent)); ui.add_space(8.0); ui.separator(); ui.add_space(8.0); - // Add New Sync Pairing form - ui.label(RichText::new("➕ Create New Sync Pairing").strong().color(theme.text_secondary)); + // --- ШАГ 1: Выбор папки друга --- + ui.label(RichText::new("Шаг 1: Выберите общую папку друга из сети").strong().color(theme.text_primary)); + ui.add_space(4.0); + + if online_shares.is_empty() { + Frame::none() + .fill(theme.bg_card_secondary) + .rounding(Rounding::same(6.0)) + .inner_margin(Margin::same(8.0)) + .show(ui, |ui| { + ui.label( + RichText::new("ℹ️ В сети пока не обнаружено общих папок у друзей. Подключитесь к ZeroTier/LAN, чтобы друзья появились онлайн.") + .size(11.5) + .color(theme.text_muted), + ); + }); + } else { + ui.horizontal_wrapped(|ui| { + for (pid, pname, fid, fname) in &online_shares { + let is_chosen = self.new_sync_peer_id == *pid && self.new_sync_folder_id == *fid; + let btn_text = format!("📁 {} (от {})", fname, pname); + let share_btn = Button::new( + RichText::new(btn_text) + .size(12.0) + .strong() + .color(if is_chosen { Color32::WHITE } else { theme.text_primary }), + ) + .fill(if is_chosen { + Color32::from_rgb(37, 99, 235) + } else { + theme.bg_card_secondary + }) + .stroke(if is_chosen { + Stroke::new(1.5_f32, Color32::from_rgb(96, 165, 250)) + } else { + Stroke::NONE + }) + .rounding(Rounding::same(6.0)) + .min_size(Vec2::new(180.0, 32.0)); + + if ui.add(share_btn).clicked() { + self.new_sync_peer_id = pid.clone(); + self.new_sync_folder_id = fid.clone(); + if self.new_sync_name.trim().is_empty() { + self.new_sync_name = format!("Синхронизация {} ({})", fname, pname); + } + } + } + }); + } + + ui.add_space(10.0); + + // --- ШАГ 2: Выбор локальной папки на диске --- + ui.label(RichText::new("Шаг 2: Выберите локальную папку на вашем компьютере").strong().color(theme.text_primary)); ui.add_space(4.0); ui.horizontal(|ui| { - ui.label("Name:"); - ui.add(egui::TextEdit::singleline(&mut self.new_sync_name).hint_text("My Project Sync").desired_width(130.0)); + let browse_btn = Button::new(RichText::new("📁 Выбрать папку на диске...").strong().color(Color32::WHITE)) + .fill(Color32::from_rgb(59, 130, 246)) + .rounding(Rounding::same(4.0)); - ui.label("Local Path:"); - ui.add(egui::TextEdit::singleline(&mut self.new_sync_local_path).hint_text("C:\\Projects\\...").desired_width(180.0)); - if ui.button("📁 Browse").clicked() { + if ui.add(browse_btn).clicked() { if let Some(folder) = FileDialog::new().pick_folder() { self.new_sync_local_path = folder.to_string_lossy().into_owned(); } } + + if self.new_sync_local_path.is_empty() { + ui.label(RichText::new("Папка не выбрана").color(Color32::from_rgb(239, 68, 68)).size(11.5)); + } else { + ui.label(RichText::new(&self.new_sync_local_path).strong().color(Color32::from_rgb(34, 197, 94)).monospace().size(11.5)); + } }); + ui.add_space(10.0); + + // --- ШАГ 3: Запуск синхронизации --- + ui.label(RichText::new("Шаг 3: Назовите и запустите синхронизацию").strong().color(theme.text_primary)); ui.add_space(4.0); + ui.horizontal(|ui| { - ui.label("Friend (Peer ID):"); - ui.add(egui::TextEdit::singleline(&mut self.new_sync_peer_id).hint_text("Select or paste peer ID...").desired_width(180.0)); + ui.label("Имя пары:"); + ui.add(egui::TextEdit::singleline(&mut self.new_sync_name).hint_text("Мой проект").desired_width(180.0)); - ui.label("Remote Folder ID:"); - ui.add(egui::TextEdit::singleline(&mut self.new_sync_folder_id).hint_text("Shared folder ID...").desired_width(160.0)); + let is_ready = !self.new_sync_local_path.trim().is_empty() + && !self.new_sync_peer_id.trim().is_empty() + && !self.new_sync_folder_id.trim().is_empty(); - let add_pair_btn = Button::new(RichText::new("➕ Add Pairing").strong().color(Color32::WHITE)) - .fill(Color32::from_rgb(34, 197, 94)) - .rounding(Rounding::same(4.0)); + let start_btn = Button::new(RichText::new("🚀 Запустить авто-синхронизацию").strong().color(Color32::WHITE)) + .fill(if is_ready { Color32::from_rgb(34, 197, 94) } else { Color32::from_rgb(100, 116, 139) }) + .rounding(Rounding::same(4.0)) + .min_size(Vec2::new(200.0, 30.0)); - if ui.add(add_pair_btn).clicked() { - if !self.new_sync_local_path.trim().is_empty() && !self.new_sync_peer_id.trim().is_empty() { - let name = if self.new_sync_name.trim().is_empty() { - "Folder Sync".to_string() - } else { - self.new_sync_name.trim().to_string() - }; + if ui.add_enabled(is_ready, start_btn).clicked() { + let name = if self.new_sync_name.trim().is_empty() { + "Folder Sync".to_string() + } else { + self.new_sync_name.trim().to_string() + }; - let pair = crate::transfer::FolderSyncPairConfig { - id: uuid::Uuid::new_v4().to_string(), - name, - local_path: PathBuf::from(self.new_sync_local_path.trim()), - remote_peer_id: self.new_sync_peer_id.trim().to_string(), - remote_peer_name: "Friend".to_string(), - remote_folder_id: self.new_sync_folder_id.trim().to_string(), - is_active: true, - last_sync: None, - }; + let peer_name = self.peers.iter() + .find(|p| p.peer_id == self.new_sync_peer_id) + .map(|p| self.get_peer_display_name(p).0) + .unwrap_or_else(|| "Friend".to_string()); - self.folder_sync_pairs.push(pair); - self.config.folder_sync_pairs = self.folder_sync_pairs.clone(); - self.persist_config(); + let pair = crate::transfer::FolderSyncPairConfig { + id: uuid::Uuid::new_v4().to_string(), + name, + local_path: PathBuf::from(self.new_sync_local_path.trim()), + remote_peer_id: self.new_sync_peer_id.trim().to_string(), + remote_peer_name: peer_name, + remote_folder_id: self.new_sync_folder_id.trim().to_string(), + is_active: true, + last_sync: None, + }; - self.new_sync_local_path.clear(); - self.new_sync_name.clear(); - } + self.folder_sync_pairs.push(pair); + self.config.folder_sync_pairs = self.folder_sync_pairs.clone(); + self.persist_config(); + + self.logs.push(format!("🔄 Создана новая авто-синхронизация: {}", self.new_sync_name)); + self.new_sync_local_path.clear(); + self.new_sync_name.clear(); + self.new_sync_peer_id.clear(); + self.new_sync_folder_id.clear(); } }); }); @@ -2296,6 +2344,9 @@ impl ZeroSendApp { ui.add_space(10.0); // List of Active Sync Pairs + ui.label(RichText::new(format!("Активные пары синхронизации ({})", self.folder_sync_pairs.len())).strong().size(13.0).color(theme.text_primary)); + ui.add_space(4.0); + if self.folder_sync_pairs.is_empty() { Frame::none() .fill(theme.bg_card) @@ -2304,8 +2355,8 @@ impl ZeroSendApp { .inner_margin(Margin::same(24.0)) .show(ui, |ui| { ui.vertical_centered(|ui| { - ui.label(RichText::new("🔄 No active sync pairings configured.").color(theme.text_secondary)); - ui.label(RichText::new("Configure a pairing above to automatically synchronize folders with friends.").size(11.5).color(theme.text_muted)); + ui.label(RichText::new("🔄 Нет активных настроенных пар синхронизации.").color(theme.text_secondary)); + ui.label(RichText::new("Выберите общую папку друга в мастере выше и укажите локальную папку для старта.").size(11.5).color(theme.text_muted)); }); }); } else { @@ -2320,35 +2371,35 @@ impl ZeroSendApp { .inner_margin(Margin::same(12.0)) .show(ui, |ui| { ui.horizontal(|ui| { - ui.label(RichText::new("🔄").size(20.0)); + ui.label(RichText::new("🔄").size(22.0)); ui.vertical(|ui| { ui.horizontal(|ui| { - ui.label(RichText::new(&pair.name).strong().color(theme.text_primary)); + ui.label(RichText::new(&pair.name).strong().size(13.5).color(theme.text_primary)); if pair.is_active { - ui.label(RichText::new("● Active (Auto-Syncing)").color(Color32::from_rgb(34, 197, 94)).size(11.0)); + ui.label(RichText::new("● Авто-синхронизация активна (каждые 8с)").color(Color32::from_rgb(34, 197, 94)).size(11.0).strong()); } else { - ui.label(RichText::new("⏸ Paused").color(theme.text_muted).size(11.0)); + ui.label(RichText::new("⏸ На паузе").color(theme.text_muted).size(11.0)); } }); - ui.label( - RichText::new(format!("Local: {:?} ➔ Remote Folder ID: {}", pair.local_path, pair.remote_folder_id)) - .monospace() - .size(11.0) - .color(theme.text_muted), - ); + ui.horizontal(|ui| { + ui.label(RichText::new("💻 Локально:").strong().size(11.0).color(theme.text_secondary)); + ui.label(RichText::new(format!("{:?}", pair.local_path)).monospace().size(11.0).color(theme.text_muted)); + ui.label(RichText::new("➔ 🌐 Удаленно (Друг):").strong().size(11.0).color(theme.text_secondary)); + ui.label(RichText::new(&pair.remote_peer_name).size(11.0).color(theme.text_accent)); + }); }); ui.with_layout(Layout::right_to_left(Align::Center), |ui| { - if ui.button(RichText::new("🗑 Delete").color(Color32::from_rgb(239, 68, 68))).clicked() { + if ui.button(RichText::new("🗑 Удалить").color(Color32::from_rgb(239, 68, 68))).clicked() { remove_sync_idx = Some(idx); } - if ui.checkbox(&mut pair.is_active, "Enabled").changed() { + if ui.checkbox(&mut pair.is_active, "Включено").changed() { sync_changed = true; } - if ui.button("📂 Open Local").clicked() { + if ui.button("📂 Открыть локально").clicked() { let _ = std::process::Command::new("explorer").arg(&pair.local_path).spawn(); } }); @@ -2377,12 +2428,12 @@ impl ZeroSendApp { .stroke(theme.stroke_border) .inner_margin(Margin::same(10.0)) .show(ui, |ui| { - ui.label(RichText::new("📜 Live Sync Activity Log").strong().size(12.0).color(Color32::from_rgb(148, 163, 184))); + ui.label(RichText::new("📜 Журнал работы авто-синхронизации (Live Activity Log)").strong().size(12.0).color(Color32::from_rgb(148, 163, 184))); ui.add_space(4.0); ScrollArea::vertical().max_height(140.0).show(ui, |ui| { if self.sync_log_messages.is_empty() { - ui.label(RichText::new("No sync activity yet. Files will be mirrored automatically.").size(11.0).color(Color32::from_rgb(100, 116, 139))); + ui.label(RichText::new("Синхронизация ожидает изменений. Новые и измененные файлы будут синхронизироваться автоматически.").size(11.0).color(Color32::from_rgb(100, 116, 139))); } else { for log in self.sync_log_messages.iter().rev() { ui.label(RichText::new(log).monospace().size(11.0).color(Color32::from_rgb(203, 213, 225))); @@ -2532,18 +2583,34 @@ impl ZeroSendApp { ui.with_layout(Layout::right_to_left(Align::Center), |ui| { // Grep search button - if ui.button(RichText::new("🔎 Search Text/Code").color(Color32::from_rgb(56, 189, 248))).clicked() { + if ui.button(RichText::new("🔎 Поиск текста/кода").color(Color32::from_rgb(56, 189, 248))).clicked() { self.show_grep_modal = true; self.grep_query.clear(); self.grep_results.clear(); self.grep_status_msg = None; } + // Quick Auto-Sync pairing shortcut + let folder_id_clone = folder_id.clone(); + let peer_id_clone = peer.peer_id.clone(); + let peer_name_clone = peer_name.clone(); + let f_name = peer.shared_folders.iter().find(|f| f.id == folder_id_clone).map(|f| f.name.clone()).unwrap_or_else(|| "Shared".to_string()); + + if ui.button(RichText::new("🔄 Авто-Синхронизация").color(Color32::from_rgb(168, 85, 247))).clicked() { + self.new_sync_peer_id = peer_id_clone; + self.new_sync_folder_id = folder_id_clone; + self.new_sync_name = format!("Синхронизация {} ({})", f_name, peer_name_clone); + self.selected_share_subtab = SharedSubTab::AutoSync; + if let Some(folder) = FileDialog::new().pick_folder() { + self.new_sync_local_path = folder.to_string_lossy().into_owned(); + } + } + // Search filter box ui.add( egui::TextEdit::singleline(&mut self.share_search_query) - .hint_text("🔍 Filter files...") - .desired_width(140.0), + .hint_text("🔍 Фильтр файлов...") + .desired_width(130.0), ); // Upload button if folder is Read-Write @@ -3564,7 +3631,7 @@ impl ZeroSendApp { .show(ui, |ui| { ui.horizontal(|ui| { ui.label(RichText::new("⚡ ZeroSend").size(16.0).strong().color(theme.text_accent)); - ui.label(RichText::new("v2.4.1").size(13.0).color(Color32::from_rgb(34, 197, 94)).strong()); + ui.label(RichText::new(format!("v{}", env!("CARGO_PKG_VERSION"))).size(13.0).color(Color32::from_rgb(34, 197, 94)).strong()); }); ui.add_space(4.0); ui.label(RichText::new("Fast, peer-to-peer file & text sharing optimized for ZeroTier, Tailscale, Radmin VPN & LAN.").color(theme.text_secondary)); @@ -3577,16 +3644,15 @@ impl ZeroSendApp { ui.add_space(6.0); ui.label(RichText::new("⚙ Technology Stack:").strong().color(theme.text_primary)); ui.label(RichText::new(" • Rust 1.98, Tokio Async Runtime, Axum HTTP Streaming").color(theme.text_secondary)); - ui.label(RichText::new(" • HTTP 206 Partial Content Media Streaming & Real-time Seeking").color(theme.text_secondary)); - ui.label(RichText::new(" • Full Portable Exe-Folder Configuration & Data Storage").color(theme.text_secondary)); - ui.label(RichText::new(" • Embedded Lightweight Vim Code & Text Viewer").color(theme.text_secondary)); - ui.label(RichText::new(" • SHA-256 On-The-Fly Cryptographic File Integrity Verification").color(theme.text_secondary)); - ui.label(RichText::new(" • Mobile Web PWA with Thumbnails & Voice Notes Recorder").color(theme.text_secondary)); + ui.label(RichText::new(" • 🔄 Two-Way Live Folder Synchronization Engine (Continuous Peer Mirroring)").color(theme.text_secondary)); + ui.label(RichText::new(" • 🖧 Windows Explorer Network Drive (WebDAV RFC 4918)").color(theme.text_secondary)); + ui.label(RichText::new(" • 🎬 HTTP 206 Partial Content Streaming & Watch Party Session Sync").color(theme.text_secondary)); + ui.label(RichText::new(" • ✏️ Full Modal Vim Editor with Normal/Insert Modes, :w Save & Markdown Preview").color(theme.text_secondary)); + ui.label(RichText::new(" • 🔎 Remote Full-Text Grep Code & Text Search Engine").color(theme.text_secondary)); + ui.label(RichText::new(" • 📦 Single-Folder Portable Exe Configuration & Data Storage").color(theme.text_secondary)); + ui.label(RichText::new(" • 🛡️ SHA-256 On-The-Fly Cryptographic File Integrity Verification").color(theme.text_secondary)); ui.label(RichText::new(" • 4-Stream Parallel Chunk Worker Engine (Multi-stream Range transfers)").color(theme.text_secondary)); ui.label(RichText::new(" • Resumable .zerosend_part File Checkpointing Engine").color(theme.text_secondary)); - ui.label(RichText::new(" • Token-Bucket Bandwidth Rate Limiter & Priority Discovery").color(theme.text_secondary)); - ui.label(RichText::new(" • Egui & Eframe Desktop GUI Engine with Dynamic Dark/Light Themes").color(theme.text_secondary)); - ui.label(RichText::new(" • Zip Fast Deflate Streaming Compression Engine").color(theme.text_secondary)); ui.label(RichText::new(" • UDP Broadcast ZeroTier Mesh Discovery Engine").color(theme.text_secondary)); }); });