Release v2.5.2: fix transfers speed metric overflow, auto-cleanup old binary on update, full Russian localization
This commit is contained in:
Generated
+1
-1
@@ -6372,7 +6372,7 @@ checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerosend"
|
name = "zerosend"
|
||||||
version = "2.5.1"
|
version = "2.5.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arboard",
|
"arboard",
|
||||||
"axum",
|
"axum",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "zerosend"
|
name = "zerosend"
|
||||||
version = "2.5.1"
|
version = "2.5.2"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "Fast, peer-to-peer file transfer designed for ZeroTier, Tailscale, Radmin VPN and LAN"
|
description = "Fast, peer-to-peer file transfer designed for ZeroTier, Tailscale, Radmin VPN and LAN"
|
||||||
authors = ["RarDog"]
|
authors = ["RarDog"]
|
||||||
|
|||||||
@@ -32,6 +32,14 @@ fn get_default_device_name() -> String {
|
|||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
// Clean up any old binaries or temp files from previous self-updates
|
||||||
|
if let Ok(current_exe) = std::env::current_exe() {
|
||||||
|
if let Some(exe_dir) = current_exe.parent() {
|
||||||
|
let _ = std::fs::remove_file(exe_dir.join("zerosend.exe.old"));
|
||||||
|
let _ = std::fs::remove_file(exe_dir.join("zerosend_update.exe"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let config = AppConfig::load();
|
let config = AppConfig::load();
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
|
|
||||||
|
|||||||
+153
-167
@@ -3187,45 +3187,63 @@ impl ZeroSendApp {
|
|||||||
ui.separator();
|
ui.separator();
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
|
|
||||||
// 4 Metric Pill Cards
|
// 4 Metric Pill Cards with equal columns and perfect rounding
|
||||||
ui.horizontal(|ui| {
|
ui.columns(4, |cols| {
|
||||||
let card_w = (ui.available_width() - 30.0) / 4.0;
|
|
||||||
|
|
||||||
// Metric 1: Total
|
// Metric 1: Total
|
||||||
Frame::none().fill(theme.bg_card).rounding(Rounding::same(6.0)).stroke(theme.stroke_border).inner_margin(Margin::symmetric(10.0, 6.0)).show(ui, |ui| {
|
Frame::none()
|
||||||
ui.set_width(card_w);
|
.fill(theme.bg_card)
|
||||||
ui.vertical(|ui| {
|
.rounding(Rounding::same(6.0))
|
||||||
ui.label(RichText::new("Всего передач").size(10.5).color(theme.text_muted));
|
.stroke(theme.stroke_border)
|
||||||
ui.label(RichText::new(format!("{}", total_count)).strong().size(15.0).color(theme.text_primary));
|
.inner_margin(Margin::symmetric(10.0, 6.0))
|
||||||
|
.show(&mut cols[0], |ui| {
|
||||||
|
ui.set_width(ui.available_width());
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
ui.label(RichText::new("Всего передач").size(10.5).color(theme.text_muted));
|
||||||
|
ui.label(RichText::new(format!("{}", total_count)).strong().size(15.0).color(theme.text_primary));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
// Metric 2: Active
|
// Metric 2: Active
|
||||||
Frame::none().fill(theme.bg_card).rounding(Rounding::same(6.0)).stroke(theme.stroke_border).inner_margin(Margin::symmetric(10.0, 6.0)).show(ui, |ui| {
|
Frame::none()
|
||||||
ui.set_width(card_w);
|
.fill(theme.bg_card)
|
||||||
ui.vertical(|ui| {
|
.rounding(Rounding::same(6.0))
|
||||||
ui.label(RichText::new("В процессе").size(10.5).color(theme.text_muted));
|
.stroke(theme.stroke_border)
|
||||||
ui.label(RichText::new(format!("{}", active_count)).strong().size(15.0).color(Color32::from_rgb(56, 189, 248)));
|
.inner_margin(Margin::symmetric(10.0, 6.0))
|
||||||
|
.show(&mut cols[1], |ui| {
|
||||||
|
ui.set_width(ui.available_width());
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
ui.label(RichText::new("В процессе").size(10.5).color(theme.text_muted));
|
||||||
|
ui.label(RichText::new(format!("{}", active_count)).strong().size(15.0).color(Color32::from_rgb(56, 189, 248)));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
// Metric 3: Completed
|
// Metric 3: Completed
|
||||||
Frame::none().fill(theme.bg_card).rounding(Rounding::same(6.0)).stroke(theme.stroke_border).inner_margin(Margin::symmetric(10.0, 6.0)).show(ui, |ui| {
|
Frame::none()
|
||||||
ui.set_width(card_w);
|
.fill(theme.bg_card)
|
||||||
ui.vertical(|ui| {
|
.rounding(Rounding::same(6.0))
|
||||||
ui.label(RichText::new("Завершено").size(10.5).color(theme.text_muted));
|
.stroke(theme.stroke_border)
|
||||||
ui.label(RichText::new(format!("{}", completed_count)).strong().size(15.0).color(Color32::from_rgb(34, 197, 94)));
|
.inner_margin(Margin::symmetric(10.0, 6.0))
|
||||||
|
.show(&mut cols[2], |ui| {
|
||||||
|
ui.set_width(ui.available_width());
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
ui.label(RichText::new("Завершено").size(10.5).color(theme.text_muted));
|
||||||
|
ui.label(RichText::new(format!("{}", completed_count)).strong().size(15.0).color(Color32::from_rgb(34, 197, 94)));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
// Metric 4: Speed
|
// Metric 4: Speed
|
||||||
Frame::none().fill(theme.bg_card).rounding(Rounding::same(6.0)).stroke(theme.stroke_border).inner_margin(Margin::symmetric(10.0, 6.0)).show(ui, |ui| {
|
Frame::none()
|
||||||
ui.set_width(card_w);
|
.fill(theme.bg_card)
|
||||||
ui.vertical(|ui| {
|
.rounding(Rounding::same(6.0))
|
||||||
ui.label(RichText::new("Общая скорость").size(10.5).color(theme.text_muted));
|
.stroke(theme.stroke_border)
|
||||||
ui.label(RichText::new(format!("{:.1} MB/s", total_speed_mb)).strong().size(15.0).color(if total_speed_mb > 0.0 { Color32::from_rgb(250, 204, 21) } else { theme.text_secondary }));
|
.inner_margin(Margin::symmetric(10.0, 6.0))
|
||||||
|
.show(&mut cols[3], |ui| {
|
||||||
|
ui.set_width(ui.available_width());
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
ui.label(RichText::new("Общая скорость").size(10.5).color(theme.text_muted));
|
||||||
|
ui.label(RichText::new(format!("{:.1} MB/s", total_speed_mb)).strong().size(15.0).color(if total_speed_mb > 0.0 { Color32::from_rgb(250, 204, 21) } else { theme.text_secondary }));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -3885,7 +3903,7 @@ impl ZeroSendApp {
|
|||||||
ui.add_space(10.0);
|
ui.add_space(10.0);
|
||||||
|
|
||||||
// Collapsible Credits & About Card
|
// Collapsible Credits & About Card
|
||||||
egui::CollapsingHeader::new(RichText::new("👥 About & Credits").strong().size(14.0).color(theme.text_primary))
|
egui::CollapsingHeader::new(RichText::new("👥 О программе и Разработчики").strong().size(14.0).color(theme.text_primary))
|
||||||
.default_open(false)
|
.default_open(false)
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
Frame::none()
|
Frame::none()
|
||||||
@@ -3899,33 +3917,33 @@ impl ZeroSendApp {
|
|||||||
ui.label(RichText::new(format!("v{}", env!("CARGO_PKG_VERSION"))).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.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));
|
ui.label(RichText::new("Быстрый децентрализованный обмен файлами, медиа и текстом, оптимизированный для сетей ZeroTier, Tailscale, Radmin VPN и локального Wi-Fi/LAN.").color(theme.text_secondary));
|
||||||
ui.add_space(8.0);
|
ui.add_space(8.0);
|
||||||
|
|
||||||
ui.label(RichText::new("🛠 Developed by:").strong().color(theme.text_primary));
|
ui.label(RichText::new("🛠 Создатели и разработка:").strong().color(theme.text_primary));
|
||||||
ui.label(RichText::new(" • RarDog (Idea, Requirements & Direction)").color(theme.text_secondary));
|
ui.label(RichText::new(" • RarDog (Идея, архитектура, требования и руководство проектом)").color(theme.text_secondary));
|
||||||
ui.label(RichText::new(" • Antigravity (Google DeepMind AI Coding Assistant)").color(theme.text_secondary));
|
ui.label(RichText::new(" • Antigravity (Google DeepMind AI Coding Assistant — парное программирование и реализация)").color(theme.text_secondary));
|
||||||
|
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
ui.label(RichText::new("⚙ Technology Stack:").strong().color(theme.text_primary));
|
ui.label(RichText::new("⚙ Стек технологий и возможности:").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(" • Rust 1.98, асинхронный рантайм Tokio, потоковый HTTP сервер Axum").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(" • 🔄 Двусторонняя автосинхронизация папок в реальном времени (Auto-Sync Mirroring)").color(theme.text_secondary));
|
||||||
ui.label(RichText::new(" • 🖧 Windows Explorer Network Drive (WebDAV RFC 4918)").color(theme.text_secondary));
|
ui.label(RichText::new(" • 🖧 Монтирование сетевого диска в Проводник Windows (WebDAV RFC 4918, диск Z:\\)").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(" • 🎬 Потоковое воспроизведение видео/аудио без скачивания (HTTP 206) и совместный просмотр Watch Party").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(" • ✏️ Встроенный Vim-редактор: режимы Normal/Insert, сохранение :w по сети, предпросмотр Markdown").color(theme.text_secondary));
|
||||||
ui.label(RichText::new(" • 🔎 Remote Full-Text Grep Code & Text Search Engine").color(theme.text_secondary));
|
ui.label(RichText::new(" • 🔎 Удаленный полнотекстовый поиск Grep по коду и файлам друзей с переходом по строкам").color(theme.text_secondary));
|
||||||
ui.label(RichText::new(" • 📦 Single-Folder Portable Exe Configuration & Data Storage").color(theme.text_secondary));
|
ui.label(RichText::new(" • 📦 Портативный режим (Portable): конфиг и загрузки хранятся рядом с exe в одной папке").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(" • 🛡️ Криптографическая проверка целостности SHA-256 на лету").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(" • ⚡ 4-поточный параллельный чанк-движок быстрой передачи").color(theme.text_secondary));
|
||||||
ui.label(RichText::new(" • Resumable .zerosend_part File Checkpointing Engine").color(theme.text_secondary));
|
ui.label(RichText::new(" • 🔄 Докачка и контрольные точки (.zerosend_part) при обрыве связи").color(theme.text_secondary));
|
||||||
ui.label(RichText::new(" • UDP Broadcast ZeroTier Mesh Discovery Engine").color(theme.text_secondary));
|
ui.label(RichText::new(" • 🌐 Автоматическое UDP-обнаружение узлов в сетях ZeroTier / LAN").color(theme.text_secondary));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.add_space(8.0);
|
ui.add_space(8.0);
|
||||||
|
|
||||||
// Collapsible Version History / Changelog Card
|
// Collapsible Version History / Changelog Card
|
||||||
egui::CollapsingHeader::new(RichText::new("📜 Version History (Changelog)").strong().size(14.0).color(theme.text_primary))
|
egui::CollapsingHeader::new(RichText::new("📜 История версий (Список изменений)").strong().size(14.0).color(theme.text_primary))
|
||||||
.default_open(false)
|
.default_open(false)
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
Frame::none()
|
Frame::none()
|
||||||
@@ -3934,10 +3952,21 @@ impl ZeroSendApp {
|
|||||||
.stroke(theme.stroke_border)
|
.stroke(theme.stroke_border)
|
||||||
.inner_margin(Margin::same(14.0))
|
.inner_margin(Margin::same(14.0))
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
|
// v2.5.2
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label(RichText::new("v2.5.2").strong().color(Color32::from_rgb(34, 197, 94)));
|
||||||
|
ui.label(RichText::new("(Текущая версия)").size(11.0).color(Color32::GRAY));
|
||||||
|
});
|
||||||
|
ui.label(" • 🎨 Исправление макета передач: Идеальное скругление и выравнивание карточек скорости и статистики дашборда.");
|
||||||
|
ui.label(" • 🧹 Чистое автообновление: Автоматическое удаление старых версий (.old и временных файлов) при установке обновлений.");
|
||||||
|
ui.label(" • 🇷🇺 Полная русская локализация: Переведены Credits, стек технологий, модальные окна, Grep, редактор Vim и история версий.");
|
||||||
|
ui.add_space(8.0);
|
||||||
|
ui.separator();
|
||||||
|
ui.add_space(8.0);
|
||||||
|
|
||||||
// v2.5.1
|
// v2.5.1
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label(RichText::new("v2.5.1").strong().color(Color32::from_rgb(34, 197, 94)));
|
ui.label(RichText::new("v2.5.1").strong().color(Color32::from_rgb(56, 189, 248)));
|
||||||
ui.label(RichText::new("(Текущая версия)").size(11.0).color(Color32::GRAY));
|
|
||||||
});
|
});
|
||||||
ui.label(" • 🎨 Полная модернизация интерфейса: Единый стиль карточек, дашборд статистики передач, пошаговые мастера отправки и автосинхронизации, баннеры подсказок.");
|
ui.label(" • 🎨 Полная модернизация интерфейса: Единый стиль карточек, дашборд статистики передач, пошаговые мастера отправки и автосинхронизации, баннеры подсказок.");
|
||||||
ui.label(" • 🔄 Улучшенный мастер Auto-Sync: Наглядный 3-шаговый визард выбора папок и онлайн-друзей с интуитивным управлением синхронизацией.");
|
ui.label(" • 🔄 Улучшенный мастер Auto-Sync: Наглядный 3-шаговый визард выбора папок и онлайн-друзей с интуитивным управлением синхронизацией.");
|
||||||
@@ -3950,12 +3979,12 @@ impl ZeroSendApp {
|
|||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label(RichText::new("v2.5.0").strong().color(Color32::from_rgb(56, 189, 248)));
|
ui.label(RichText::new("v2.5.0").strong().color(Color32::from_rgb(56, 189, 248)));
|
||||||
});
|
});
|
||||||
ui.label(" • 🔄 Live Folder Two-Way Auto-Sync: Real-time background sync engine that keeps local directories and remote shared folders mirrored without user interaction.");
|
ui.label(" • 🔄 Двусторонняя автосинхронизация папок: Фоновый движок синхронизации локальных и удаленных каталогов без участия пользователя.");
|
||||||
ui.label(" • 🖧 Windows Explorer Network Drive (WebDAV): 1-click mounting of all shared folders directly into Windows Explorer as drive Z:\\.");
|
ui.label(" • 🖧 Сетевой диск Windows (WebDAV): Подключение общих папок напрямую в Проводник Windows как диск Z:\\ в 1 клик.");
|
||||||
ui.label(" • 🎵 In-App Audio Player Dock & 🎬 Watch Party: Sticky mini-player dock and interactive video watch rooms with real-time playback sync.");
|
ui.label(" • 🎵 Аудиоплеер и 🎬 Watch Party: Док мини-плеера и совместные комнаты просмотра видео с синхронизацией воспроизведения.");
|
||||||
ui.label(" • ✏️ Full Vim Editor with Write-Back: Normal + Insert Mode, buffer editing, Markdown preview, and :w saving back across the network.");
|
ui.label(" • ✏️ Полноценный Vim-редактор с сохранением по сети: Режимы Normal/Insert, правка буфера, предпросмотр Markdown и запись (:w).");
|
||||||
ui.label(" • 🔎 Remote Full-Text Grep: Search code and text files across friends' machines with instant line navigation.");
|
ui.label(" • 🔎 Удаленный Grep-поиск: Поиск кода и текста на компьютерах друзей с мгновенным переходом к строке.");
|
||||||
ui.label(" • ⬇️ Multi-Select Batch Downloads & Drag-and-Drop: Multi-file checkboxes and direct drag & drop upload into remote shares.");
|
ui.label(" • ⬇️ Пакетное скачивание и Drag & Drop: Выбор файлов чекбоксами и прямое перетаскивание в сетевые папки.");
|
||||||
ui.add_space(8.0);
|
ui.add_space(8.0);
|
||||||
ui.separator();
|
ui.separator();
|
||||||
ui.add_space(8.0);
|
ui.add_space(8.0);
|
||||||
@@ -3964,11 +3993,11 @@ impl ZeroSendApp {
|
|||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label(RichText::new("v2.4.1").strong().color(Color32::from_rgb(56, 189, 248)));
|
ui.label(RichText::new("v2.4.1").strong().color(Color32::from_rgb(56, 189, 248)));
|
||||||
});
|
});
|
||||||
ui.label(" • 📁 Shared Folders Tab: Dedicated Shared Folders management with Read-Only / Read-Write permissions and PIN security.");
|
ui.label(" • 📁 Вкладка Общие папки: Управление сетевыми папками с правами «Только чтение» / «Чтение и запись» и защитой PIN.");
|
||||||
ui.label(" • 🎬 HTTP 206 Streaming: Instant real-time video/audio streaming with seeking without downloading entire files.");
|
ui.label(" • 🎬 HTTP 206 Streaming: Мгновенное воспроизведение видео/аудио с перемоткой без предварительного скачивания.");
|
||||||
ui.label(" • ⚡ Built-in Vim Code Viewer: Terminal-style code viewer with full Vim keybindings, search (/), and line jump.");
|
ui.label(" • ⚡ Встроенный просмотрщик Vim: Терминальный просмотр кода с горячими клавишами Vim, поиском (/) и прыжком по строкам.");
|
||||||
ui.label(" • 📦 Single-Folder Portable Mode: All settings (config.json) and downloads are created & saved right in the exe folder.");
|
ui.label(" • 📦 Единая папка (Portable): Все настройки (config.json) и загрузки создаются и хранятся рядом с exe.");
|
||||||
ui.label(" • 🔄 Auto-Sync: Real-time discovery beacon broadcasting of shared folders when connecting to ZeroTier/VPN.");
|
ui.label(" • 🔄 Auto-Sync маяки: Оповещение об общих папках при подключении к ZeroTier/VPN.");
|
||||||
ui.add_space(8.0);
|
ui.add_space(8.0);
|
||||||
ui.separator();
|
ui.separator();
|
||||||
ui.add_space(8.0);
|
ui.add_space(8.0);
|
||||||
@@ -3977,11 +4006,11 @@ impl ZeroSendApp {
|
|||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label(RichText::new("v2.4.0").strong().color(Color32::from_rgb(56, 189, 248)));
|
ui.label(RichText::new("v2.4.0").strong().color(Color32::from_rgb(56, 189, 248)));
|
||||||
});
|
});
|
||||||
ui.label(" • 🛡️ SHA-256 Checksum on the Fly: File integrity calculation & verification during streaming with green verification badges.");
|
ui.label(" • 🛡️ Контрольная сумма SHA-256 на лету: Проверка целостности передаваемых файлов с зелеными бейджами гарантии.");
|
||||||
ui.label(" • 📸 Mobile Media Previews: Thumbnail gallery previews for photos with instant remove buttons before uploading.");
|
ui.label(" • 📸 Превью фото на мобильных: Галерея миниатюр перед отправкой с возможностью удаления лишних снимков.");
|
||||||
ui.label(" • 🎙️ Mobile Voice Notes: Built-in voice recorder with live audio wave pulse animation and one-tap delivery.");
|
ui.label(" • 🎙️ Голосовые сообщения на мобильных: Встроенный диктофон с пульсирующей анимацией волны.");
|
||||||
ui.label(" • 📲 PWA Support: Web App Manifest & Add to Home Screen standalone install capabilities for mobile browsers.");
|
ui.label(" • 📲 Поддержка PWA: Установка веб-приложения на главный экран смартфона прямо из браузера.");
|
||||||
ui.label(" • 🎨 Auto Theme: Dark/Light theme mode with Windows Registry theme sync & contrast fixes.");
|
ui.label(" • 🎨 Авто-тема: Темная/светлая тема с синхронизацией с реестром Windows.");
|
||||||
ui.add_space(8.0);
|
ui.add_space(8.0);
|
||||||
ui.separator();
|
ui.separator();
|
||||||
ui.add_space(8.0);
|
ui.add_space(8.0);
|
||||||
@@ -3990,13 +4019,12 @@ impl ZeroSendApp {
|
|||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label(RichText::new("v2.3.2").strong().color(Color32::from_rgb(56, 189, 248)));
|
ui.label(RichText::new("v2.3.2").strong().color(Color32::from_rgb(56, 189, 248)));
|
||||||
});
|
});
|
||||||
ui.label(" • 🚀 Start with Windows: Option to run ZeroSend on Windows startup minimized to tray.");
|
ui.label(" • 🚀 Автозапуск с Windows: Опция запуска ZeroSend при старте системы в свёрнутом виде в трей.");
|
||||||
ui.label(" • 🎯 Drag & Drop Quick Send: Drag files directly onto any friend's card in Nearby tab.");
|
ui.label(" • 🎯 Быстрая отправка Drag & Drop: Перетаскивание файлов прямо на карточку любого друга во вкладке Устройства.");
|
||||||
ui.label(" • Dual Web Links with dedicated QR codes (ZeroTier/VPN mesh + Local Wi-Fi router).");
|
ui.label(" • Двойные ссылки и QR-коды (ZeroTier/VPN + Домашний Wi-Fi).");
|
||||||
ui.label(" • ZeroTier Web Confirmation popup on PC (Auto-accept on Local LAN).");
|
ui.label(" • Всплывающее окно подтверждения передачи через ZeroTier.");
|
||||||
ui.label(" • Auto-switch to Transfers view on both PC and mobile when transfers begin.");
|
ui.label(" • Сглаживание показаний скорости алгоритмом EMA (без скачков).");
|
||||||
ui.label(" • Exponential Moving Average (EMA) smoothed speed calculations (no spikes).");
|
ui.label(" • 4-поточная параллельная передача файлов.");
|
||||||
ui.label(" • 4-Stream Parallel Chunk Transfers with Adaptive Chunk Sizing.");
|
|
||||||
ui.add_space(8.0);
|
ui.add_space(8.0);
|
||||||
ui.separator();
|
ui.separator();
|
||||||
ui.add_space(8.0);
|
ui.add_space(8.0);
|
||||||
@@ -4005,83 +4033,41 @@ impl ZeroSendApp {
|
|||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label(RichText::new("v2.3.1").strong().color(Color32::from_rgb(34, 197, 94)));
|
ui.label(RichText::new("v2.3.1").strong().color(Color32::from_rgb(34, 197, 94)));
|
||||||
});
|
});
|
||||||
ui.label(" • Redesigned Mobile-First Web Dropzone (camera/photo quick shortcuts, mobile UX).");
|
ui.label(" • Модернизированная мобильная Web Dropzone с кнопками камеры и фото.");
|
||||||
ui.label(" • Dual Web Links with dedicated QR codes (ZeroTier/VPN mesh + Local Wi-Fi router).");
|
ui.label(" • Раздельные QR-коды для ZeroTier и локальной сети Wi-Fi.");
|
||||||
ui.label(" • ZeroTier Web Confirmation popup on PC (Auto-accept on Local LAN).");
|
ui.label(" • Автоматическое переключение на вкладку Передач при старте отправки.");
|
||||||
ui.label(" • Auto-switch to Transfers view on both PC and mobile when transfers begin.");
|
ui.label(" • Отображение статуса сжатия папок в ZIP в реальном времени.");
|
||||||
ui.label(" • Exponential Moving Average (EMA) smoothed speed calculations (no spikes).");
|
|
||||||
ui.label(" • Live folder compression status (shows 'Compressing to ZIP' in Transfers).");
|
|
||||||
ui.label(" • Smart Dropzone with colored category badges (Video, Audio, Docs, Binaries).");
|
|
||||||
ui.label(" • 4-Stream Parallel Chunk Transfers with Adaptive Chunk Sizing.");
|
|
||||||
ui.add_space(8.0);
|
ui.add_space(8.0);
|
||||||
ui.separator();
|
ui.separator();
|
||||||
ui.add_space(8.0);
|
ui.add_space(8.0);
|
||||||
|
|
||||||
// v2.0.0
|
// v2.0.0
|
||||||
ui.label(RichText::new("v2.0.0").strong().color(Color32::from_rgb(56, 189, 248)));
|
ui.label(RichText::new("v2.0.0").strong().color(Color32::from_rgb(56, 189, 248)));
|
||||||
ui.label(" • Redesigned modern Glassmorphism Web & Mobile dropzone dashboard.");
|
ui.label(" • Современный стеклянный интерфейс (Glassmorphism) для Web Dropzone.");
|
||||||
ui.label(" • Web Quick Text sender & mobile-responsive drag-and-drop.");
|
ui.label(" • Быстрая передача текста из браузера и адаптивный Drag & Drop.");
|
||||||
ui.label(" • Fixed global Ctrl+V screenshot pasting with retry mechanism.");
|
ui.label(" • Глобальная вставка скриншотов из буфера (Ctrl+V).");
|
||||||
ui.label(" • Real-time upload streaming progress & speed meters for sender.");
|
ui.label(" • Реальная скорость и прогресс на стороне отправителя.");
|
||||||
ui.label(" • Permanent purge for 'Clear finished' transfers.");
|
ui.label(" • Постоянные UUID устройств и защита от дубликатов.");
|
||||||
ui.label(" • Peer deduplication & persistent device UUIDs.");
|
|
||||||
ui.add_space(8.0);
|
|
||||||
ui.separator();
|
|
||||||
ui.add_space(8.0);
|
|
||||||
|
|
||||||
// v1.2.1
|
|
||||||
ui.label(RichText::new("v1.2.1").strong().color(Color32::from_rgb(56, 189, 248)));
|
|
||||||
ui.label(" • Real-time stream upload progress & live speed on sender side.");
|
|
||||||
ui.label(" • Working 'Clear finished' button in Transfers tab.");
|
|
||||||
ui.label(" • Persistent peer UUIDs & automatic peer deduplication (no duplicate ghosts).");
|
|
||||||
ui.label(" • Official Windows PE Resource & Manifest embedding (zero false positives).");
|
|
||||||
ui.add_space(8.0);
|
ui.add_space(8.0);
|
||||||
ui.separator();
|
ui.separator();
|
||||||
ui.add_space(8.0);
|
ui.add_space(8.0);
|
||||||
|
|
||||||
// v1.2.0
|
// v1.2.0
|
||||||
ui.label(RichText::new("v1.2.0").strong().color(Color32::from_rgb(56, 189, 248)));
|
ui.label(RichText::new("v1.2.0").strong().color(Color32::from_rgb(56, 189, 248)));
|
||||||
ui.label(" • Crystal-clear transfer visualization, file icons & live speed meters.");
|
ui.label(" • Наглядная визуализация передач, иконки типов файлов и спидометры.");
|
||||||
ui.label(" • Fixed incoming transfer confirmation popup & robust message delivery.");
|
ui.label(" • Фоновая работа в системном трее Windows и мгновенный выход.");
|
||||||
ui.label(" • Fixed System Tray background listener and instant exit.");
|
ui.label(" • Сжатие папок в ZIP на лету и автораспаковка в папке загрузок.");
|
||||||
ui.label(" • On-the-fly fast ZIP compression for directories (10x faster).");
|
|
||||||
ui.label(" • Automatic ZIP extraction in receiver download directory.");
|
|
||||||
ui.add_space(8.0);
|
|
||||||
ui.separator();
|
|
||||||
ui.add_space(8.0);
|
|
||||||
|
|
||||||
// v1.1.0
|
|
||||||
ui.label(RichText::new("v1.1.0").strong().color(Color32::from_rgb(56, 189, 248)));
|
|
||||||
ui.label(" • System Tray integration with background daemon and tray menu.");
|
|
||||||
ui.label(" • Window size & layout auto-save (memory).");
|
|
||||||
ui.label(" • Collapsible About, Credits & Version History section.");
|
|
||||||
ui.label(" • Polish and performance improvements.");
|
|
||||||
ui.add_space(8.0);
|
ui.add_space(8.0);
|
||||||
ui.separator();
|
ui.separator();
|
||||||
ui.add_space(8.0);
|
ui.add_space(8.0);
|
||||||
|
|
||||||
// v1.0.0
|
// v1.0.0
|
||||||
ui.label(RichText::new("v1.0.0").strong().color(Color32::GRAY));
|
ui.label(RichText::new("v1.0.0").strong().color(Color32::GRAY));
|
||||||
ui.label(" • Native Desktop GUI (no terminal console window).");
|
ui.label(" • Нативный графический интерфейс (GUI) без консоли.");
|
||||||
ui.label(" • Multi-recipient parallel file & text broadcasting.");
|
ui.label(" • Параллельная рассылка файлов и текста нескольким получателям.");
|
||||||
ui.label(" • Smart Clipboard with content auto-detection (IP, URL, Code, JSON).");
|
ui.label(" • Генератор QR-кодов для отправки с телефонов.");
|
||||||
ui.label(" • QR Code generator for mobile file dropzone.");
|
ui.label(" • Защита 4-значным PIN-кодом входящих передач.");
|
||||||
ui.label(" • 4-digit PIN security protection for incoming transfers.");
|
ui.label(" • Кнопки «Открыть файл» и «Показать в папке».");
|
||||||
ui.label(" • Global Drag-and-Drop files directly into the window.");
|
|
||||||
ui.label(" • Ctrl+V screenshot/clipboard image pasting.");
|
|
||||||
ui.label(" • 'Open file' and 'Show in folder' buttons in Transfers.");
|
|
||||||
ui.label(" • Custom peer nicknames (aliases) saved in config.");
|
|
||||||
ui.label(" • Windows Explorer context menu ('Send with ZeroSend').");
|
|
||||||
ui.add_space(8.0);
|
|
||||||
ui.separator();
|
|
||||||
ui.add_space(8.0);
|
|
||||||
|
|
||||||
// v0.1.0
|
|
||||||
ui.label(RichText::new("v0.1.0").strong().color(Color32::GRAY));
|
|
||||||
ui.label(" • Initial ZeroTier & VPN network interface scanner.");
|
|
||||||
ui.label(" • UDP broadcast peer discovery engine.");
|
|
||||||
ui.label(" • High-speed HTTP streaming transfer engine.");
|
|
||||||
ui.label(" • Terminal UI (TUI) and headless CLI daemon.");
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -4092,12 +4078,12 @@ impl ZeroSendApp {
|
|||||||
fn render_modals(&mut self, ctx: &egui::Context, theme: ThemeColors) {
|
fn render_modals(&mut self, ctx: &egui::Context, theme: ThemeColors) {
|
||||||
// Direct IP Modal
|
// Direct IP Modal
|
||||||
if self.show_direct_ip_modal {
|
if self.show_direct_ip_modal {
|
||||||
egui::Window::new("🔍 Direct Connect (IP)")
|
egui::Window::new("🔍 Прямое подключение (IP)")
|
||||||
.collapsible(false)
|
.collapsible(false)
|
||||||
.resizable(false)
|
.resizable(false)
|
||||||
.anchor(egui::Align2::CENTER_CENTER, Vec2::ZERO)
|
.anchor(egui::Align2::CENTER_CENTER, Vec2::ZERO)
|
||||||
.show(ctx, |ui| {
|
.show(ctx, |ui| {
|
||||||
ui.label(RichText::new("Enter ZeroTier / Tailscale / Radmin IP of peer:").color(theme.text_primary));
|
ui.label(RichText::new("Введите IP-адрес узла в сети ZeroTier / Tailscale / Radmin:").color(theme.text_primary));
|
||||||
ui.add_space(4.0);
|
ui.add_space(4.0);
|
||||||
ui.text_edit_singleline(&mut self.direct_ip_input);
|
ui.text_edit_singleline(&mut self.direct_ip_input);
|
||||||
|
|
||||||
@@ -4108,19 +4094,19 @@ impl ZeroSendApp {
|
|||||||
|
|
||||||
ui.add_space(12.0);
|
ui.add_space(12.0);
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
if ui.add(Button::new(RichText::new("Connect & Ping").strong().color(Color32::WHITE)).fill(Color32::from_rgb(37, 99, 235))).clicked() {
|
if ui.add(Button::new(RichText::new("Подключить и проверить").strong().color(Color32::WHITE)).fill(Color32::from_rgb(37, 99, 235))).clicked() {
|
||||||
let ip = self.direct_ip_input.trim().to_string();
|
let ip = self.direct_ip_input.trim().to_string();
|
||||||
if !ip.is_empty() {
|
if !ip.is_empty() {
|
||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
let peers_store = self.discovery_peers.clone();
|
let peers_store = self.discovery_peers.clone();
|
||||||
let log_tx = self.log_tx.clone();
|
let log_tx = self.log_tx.clone();
|
||||||
|
|
||||||
self.direct_ip_status = "Connecting...".to_string();
|
self.direct_ip_status = "Подключение...".to_string();
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
match client.ping_peer(&ip, 53318).await {
|
match client.ping_peer(&ip, 53318).await {
|
||||||
Ok(beacon) => {
|
Ok(beacon) => {
|
||||||
let _ = log_tx.send(format!("✨ Added direct node: {} ({})", beacon.name, beacon.ip));
|
let _ = log_tx.send(format!("✨ Добавлен узел: {} ({})", beacon.name, beacon.ip));
|
||||||
let mut peers = peers_store.write().await;
|
let mut peers = peers_store.write().await;
|
||||||
peers.insert(
|
peers.insert(
|
||||||
beacon.peer_id.clone(),
|
beacon.peer_id.clone(),
|
||||||
@@ -4140,7 +4126,7 @@ impl ZeroSendApp {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let _ = log_tx.send(format!("❌ Direct connect error: {}", e));
|
let _ = log_tx.send(format!("❌ Ошибка прямого подключения: {}", e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -4148,7 +4134,7 @@ impl ZeroSendApp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ui.button("Cancel").clicked() {
|
if ui.button("Отмена").clicked() {
|
||||||
self.show_direct_ip_modal = false;
|
self.show_direct_ip_modal = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -4157,18 +4143,18 @@ impl ZeroSendApp {
|
|||||||
|
|
||||||
// Rename Peer Modal
|
// Rename Peer Modal
|
||||||
if let Some((peer_id, peer_name, mut current_alias)) = self.renaming_peer.clone() {
|
if let Some((peer_id, peer_name, mut current_alias)) = self.renaming_peer.clone() {
|
||||||
egui::Window::new("✏️ Rename Friend / Set Alias")
|
egui::Window::new("✏️ Переименовать друга / Псевдоним")
|
||||||
.collapsible(false)
|
.collapsible(false)
|
||||||
.resizable(false)
|
.resizable(false)
|
||||||
.anchor(egui::Align2::CENTER_CENTER, Vec2::ZERO)
|
.anchor(egui::Align2::CENTER_CENTER, Vec2::ZERO)
|
||||||
.show(ctx, |ui| {
|
.show(ctx, |ui| {
|
||||||
ui.label(RichText::new(format!("Set custom nickname for {}", peer_name)).color(theme.text_primary));
|
ui.label(RichText::new(format!("Укажите удобное имя (псевдоним) для {}", peer_name)).color(theme.text_primary));
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
ui.text_edit_singleline(&mut current_alias);
|
ui.text_edit_singleline(&mut current_alias);
|
||||||
ui.add_space(10.0);
|
ui.add_space(10.0);
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
if ui.add(Button::new(RichText::new("Save Alias").strong().color(Color32::WHITE)).fill(Color32::from_rgb(37, 99, 235))).clicked() {
|
if ui.add(Button::new(RichText::new("Сохранить псевдоним").strong().color(Color32::WHITE)).fill(Color32::from_rgb(37, 99, 235))).clicked() {
|
||||||
if current_alias.trim().is_empty() {
|
if current_alias.trim().is_empty() {
|
||||||
self.config.peer_aliases.remove(&peer_id);
|
self.config.peer_aliases.remove(&peer_id);
|
||||||
} else {
|
} else {
|
||||||
@@ -4178,7 +4164,7 @@ impl ZeroSendApp {
|
|||||||
self.renaming_peer = None;
|
self.renaming_peer = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ui.button("Cancel").clicked() {
|
if ui.button("Отмена").clicked() {
|
||||||
self.renaming_peer = None;
|
self.renaming_peer = None;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -4188,14 +4174,14 @@ impl ZeroSendApp {
|
|||||||
// Received Quick Text Modal
|
// Received Quick Text Modal
|
||||||
if let Some(msg) = self.received_text_modal.clone() {
|
if let Some(msg) = self.received_text_modal.clone() {
|
||||||
let (r, g, b) = msg.content_type.badge_color_rgb();
|
let (r, g, b) = msg.content_type.badge_color_rgb();
|
||||||
egui::Window::new("📋 Received Quick Text")
|
egui::Window::new("📋 Входящий быстрый текст")
|
||||||
.collapsible(false)
|
.collapsible(false)
|
||||||
.resizable(true)
|
.resizable(true)
|
||||||
.min_width(400.0)
|
.min_width(400.0)
|
||||||
.anchor(egui::Align2::CENTER_CENTER, Vec2::ZERO)
|
.anchor(egui::Align2::CENTER_CENTER, Vec2::ZERO)
|
||||||
.show(ctx, |ui| {
|
.show(ctx, |ui| {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label(RichText::new(format!("From: {}", msg.sender_name)).strong().size(14.0).color(theme.text_primary));
|
ui.label(RichText::new(format!("От: {}", msg.sender_name)).strong().size(14.0).color(theme.text_primary));
|
||||||
ui.label(
|
ui.label(
|
||||||
RichText::new(format!("[{}]", msg.content_type.label()))
|
RichText::new(format!("[{}]", msg.content_type.label()))
|
||||||
.strong()
|
.strong()
|
||||||
@@ -4223,18 +4209,18 @@ impl ZeroSendApp {
|
|||||||
ui.add_space(12.0);
|
ui.add_space(12.0);
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
let content_copy = msg.content.clone();
|
let content_copy = msg.content.clone();
|
||||||
if ui.add(Button::new(RichText::new("📋 Copy to Clipboard").strong().color(Color32::WHITE)).fill(Color32::from_rgb(37, 99, 235))).clicked() {
|
if ui.add(Button::new(RichText::new("📋 Скопировать в буфер").strong().color(Color32::WHITE)).fill(Color32::from_rgb(37, 99, 235))).clicked() {
|
||||||
ui.output_mut(|o| o.copied_text = content_copy.clone());
|
ui.output_mut(|o| o.copied_text = content_copy.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
if msg.content_type == ContentType::WebUrl {
|
if msg.content_type == ContentType::WebUrl {
|
||||||
if ui.button("🌐 Open Link").clicked() {
|
if ui.button("🌐 Открыть ссылку").clicked() {
|
||||||
ui.ctx().open_url(egui::OpenUrl::same_tab(&content_copy));
|
ui.ctx().open_url(egui::OpenUrl::same_tab(&content_copy));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
|
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
|
||||||
if ui.button("Close").clicked() {
|
if ui.button("Закрыть").clicked() {
|
||||||
self.received_text_modal = None;
|
self.received_text_modal = None;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -4244,7 +4230,7 @@ impl ZeroSendApp {
|
|||||||
|
|
||||||
// PROMINENT INCOMING FILE TRANSFER CONFIRMATION MODAL
|
// PROMINENT INCOMING FILE TRANSFER CONFIRMATION MODAL
|
||||||
if let Some(req) = self.pending_request.clone() {
|
if let Some(req) = self.pending_request.clone() {
|
||||||
egui::Window::new("📥 INCOMING FILE TRANSFER REQUEST")
|
egui::Window::new("📥 ВХОДЯЩИЙ ЗАПРОС НА ПЕРЕДАЧУ ФАЙЛОВ")
|
||||||
.collapsible(false)
|
.collapsible(false)
|
||||||
.resizable(false)
|
.resizable(false)
|
||||||
.min_width(450.0)
|
.min_width(450.0)
|
||||||
@@ -4255,14 +4241,14 @@ impl ZeroSendApp {
|
|||||||
ui.label(RichText::new("📥").size(32.0));
|
ui.label(RichText::new("📥").size(32.0));
|
||||||
ui.vertical(|ui| {
|
ui.vertical(|ui| {
|
||||||
ui.label(
|
ui.label(
|
||||||
RichText::new(format!("Incoming files from: {}", req.sender_name))
|
RichText::new(format!("Входящие файлы от: {}", req.sender_name))
|
||||||
.size(17.0)
|
.size(16.0)
|
||||||
.strong()
|
.strong()
|
||||||
.color(theme.text_accent),
|
.color(theme.text_accent),
|
||||||
);
|
);
|
||||||
ui.label(
|
ui.label(
|
||||||
RichText::new(format!(
|
RichText::new(format!(
|
||||||
"Total: {} file(s) • Total Size: {}",
|
"Всего: {} файл(ов) • Общий размер: {}",
|
||||||
req.files.len(),
|
req.files.len(),
|
||||||
human_bytes(req.total_size as f64)
|
human_bytes(req.total_size as f64)
|
||||||
))
|
))
|
||||||
@@ -4273,7 +4259,7 @@ impl ZeroSendApp {
|
|||||||
});
|
});
|
||||||
|
|
||||||
ui.add_space(10.0);
|
ui.add_space(10.0);
|
||||||
ui.label(RichText::new("Items to be received:").strong().size(13.0).color(theme.text_primary));
|
ui.label(RichText::new("Список передаваемых файлов:").strong().size(13.0).color(theme.text_primary));
|
||||||
ui.add_space(4.0);
|
ui.add_space(4.0);
|
||||||
|
|
||||||
Frame::none()
|
Frame::none()
|
||||||
@@ -4293,21 +4279,21 @@ impl ZeroSendApp {
|
|||||||
ui.separator();
|
ui.separator();
|
||||||
}
|
}
|
||||||
if req.files.len() > 8 {
|
if req.files.len() > 8 {
|
||||||
ui.label(RichText::new(format!(" ... and {} more files", req.files.len() - 8)).color(theme.text_muted));
|
ui.label(RichText::new(format!(" ... и ещё {} файлов", req.files.len() - 8)).color(theme.text_muted));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.add_space(8.0);
|
ui.add_space(8.0);
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label(RichText::new("Save destination:").size(11.0).color(theme.text_muted));
|
ui.label(RichText::new("Папка сохранения:").size(11.0).color(theme.text_muted));
|
||||||
ui.label(RichText::new(format!("{:?}", self.download_dir)).size(11.0).color(theme.text_secondary));
|
ui.label(RichText::new(format!("{:?}", self.download_dir)).size(11.0).color(theme.text_secondary));
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.add_space(16.0);
|
ui.add_space(16.0);
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
let accept_btn = Button::new(
|
let accept_btn = Button::new(
|
||||||
RichText::new("✅ ACCEPT & DOWNLOAD")
|
RichText::new("✅ ПРИНЯТЬ И СКАЧАТЬ")
|
||||||
.size(14.0)
|
.size(14.0)
|
||||||
.strong()
|
.strong()
|
||||||
.color(Color32::WHITE),
|
.color(Color32::WHITE),
|
||||||
@@ -4348,7 +4334,7 @@ impl ZeroSendApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let reject_btn = Button::new(
|
let reject_btn = Button::new(
|
||||||
RichText::new("❌ DECLINE")
|
RichText::new("❌ ОТКЛОНИТЬ")
|
||||||
.size(14.0)
|
.size(14.0)
|
||||||
.strong()
|
.strong()
|
||||||
.color(Color32::WHITE),
|
.color(Color32::WHITE),
|
||||||
@@ -4375,7 +4361,7 @@ impl ZeroSendApp {
|
|||||||
let mut is_open = self.show_grep_modal;
|
let mut is_open = self.show_grep_modal;
|
||||||
let mut trigger_open_file = None;
|
let mut trigger_open_file = None;
|
||||||
|
|
||||||
egui::Window::new("🔎 Search Text / Code in Remote Share")
|
egui::Window::new("🔎 Поиск текста и кода в общей папке (Grep)")
|
||||||
.open(&mut is_open)
|
.open(&mut is_open)
|
||||||
.collapsible(false)
|
.collapsible(false)
|
||||||
.resizable(true)
|
.resizable(true)
|
||||||
@@ -4383,10 +4369,10 @@ impl ZeroSendApp {
|
|||||||
.anchor(egui::Align2::CENTER_CENTER, Vec2::ZERO)
|
.anchor(egui::Align2::CENTER_CENTER, Vec2::ZERO)
|
||||||
.show(ctx, |ui| {
|
.show(ctx, |ui| {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label(RichText::new("Query:").strong().color(theme.text_primary));
|
ui.label(RichText::new("Запрос:").strong().color(theme.text_primary));
|
||||||
let edit = ui.add(
|
let edit = ui.add(
|
||||||
egui::TextEdit::singleline(&mut self.grep_query)
|
egui::TextEdit::singleline(&mut self.grep_query)
|
||||||
.hint_text("e.g. fn main, TODO, password...")
|
.hint_text("например: fn main, TODO, пароль...")
|
||||||
.desired_width(260.0),
|
.desired_width(260.0),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -4396,7 +4382,7 @@ impl ZeroSendApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let search_btn = Button::new(
|
let search_btn = Button::new(
|
||||||
RichText::new(if self.is_grepping { "⏳ Searching..." } else { "🔍 Search" })
|
RichText::new(if self.is_grepping { "⏳ Поиск..." } else { "🔍 Искать" })
|
||||||
.strong()
|
.strong()
|
||||||
.color(Color32::WHITE),
|
.color(Color32::WHITE),
|
||||||
)
|
)
|
||||||
@@ -4411,7 +4397,7 @@ impl ZeroSendApp {
|
|||||||
if let (Some(peer_id), Some(folder_id)) = (&self.selected_peer_id_for_share, &self.selected_folder_id_for_share) {
|
if let (Some(peer_id), Some(folder_id)) = (&self.selected_peer_id_for_share, &self.selected_folder_id_for_share) {
|
||||||
if let Some(peer) = self.peers.iter().find(|p| &p.peer_id == peer_id).cloned() {
|
if let Some(peer) = self.peers.iter().find(|p| &p.peer_id == peer_id).cloned() {
|
||||||
self.is_grepping = true;
|
self.is_grepping = true;
|
||||||
self.grep_status_msg = Some("Searching across remote files...".to_string());
|
self.grep_status_msg = Some("Поиск по удаленным файлам...".to_string());
|
||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
let peer_url = peer.transfer_url();
|
let peer_url = peer.transfer_url();
|
||||||
let f_id = folder_id.clone();
|
let f_id = folder_id.clone();
|
||||||
@@ -4423,10 +4409,10 @@ impl ZeroSendApp {
|
|||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
match client.grep_shared_folder(&peer_url, &f_id, &q, pin.as_deref(), Some(&sub), false).await {
|
match client.grep_shared_folder(&peer_url, &f_id, &q, pin.as_deref(), Some(&sub), false).await {
|
||||||
Ok(resp) => {
|
Ok(resp) => {
|
||||||
let _ = log_tx.send(format!("🔎 Grep found {} match(es) in {} searched files", resp.matches.len(), resp.total_files_searched));
|
let _ = log_tx.send(format!("🔎 Grep: найдено {} совпадений в {} проверенных файлах", resp.matches.len(), resp.total_files_searched));
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let _ = log_tx.send(format!("❌ Grep failed: {}", e));
|
let _ = log_tx.send(format!("❌ Ошибка Grep: {}", e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -4443,12 +4429,12 @@ impl ZeroSendApp {
|
|||||||
if self.is_grepping {
|
if self.is_grepping {
|
||||||
ui.vertical_centered(|ui| {
|
ui.vertical_centered(|ui| {
|
||||||
ui.spinner();
|
ui.spinner();
|
||||||
ui.label("Searching remote code & text files...");
|
ui.label("Поиск по удаленным файлам кода и текста...");
|
||||||
});
|
});
|
||||||
} else if self.grep_results.is_empty() {
|
} else if self.grep_results.is_empty() {
|
||||||
ui.label(RichText::new("Enter a search term above and press Search to find text or code lines.").size(12.0).color(theme.text_muted));
|
ui.label(RichText::new("Введите поисковый запрос выше и нажмите «Искать» для поиска строк в коде.").size(12.0).color(theme.text_muted));
|
||||||
} else {
|
} else {
|
||||||
ui.label(RichText::new(format!("Found {} matches:", self.grep_results.len())).strong().color(theme.text_accent));
|
ui.label(RichText::new(format!("Найдено совпадений: {}", self.grep_results.len())).strong().color(theme.text_accent));
|
||||||
ui.add_space(4.0);
|
ui.add_space(4.0);
|
||||||
|
|
||||||
ScrollArea::vertical().max_height(280.0).show(ui, |ui| {
|
ScrollArea::vertical().max_height(280.0).show(ui, |ui| {
|
||||||
@@ -4467,7 +4453,7 @@ impl ZeroSendApp {
|
|||||||
ui.label(RichText::new(&m.line_content).monospace().color(theme.text_primary));
|
ui.label(RichText::new(&m.line_content).monospace().color(theme.text_primary));
|
||||||
|
|
||||||
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
|
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
|
||||||
if ui.button("Open in Vim").clicked() {
|
if ui.button("Открыть в Vim").clicked() {
|
||||||
trigger_open_file = Some((m.file_path.clone(), m.line_number));
|
trigger_open_file = Some((m.file_path.clone(), m.line_number));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -4509,7 +4495,7 @@ impl ZeroSendApp {
|
|||||||
|
|
||||||
pub fn trigger_update_check(&mut self) {
|
pub fn trigger_update_check(&mut self) {
|
||||||
self.is_checking_update = true;
|
self.is_checking_update = true;
|
||||||
self.update_check_status_msg = Some("Connecting to Gitea and checking for updates...".to_string());
|
self.update_check_status_msg = Some("Подключение к Gitea и проверка обновлений...".to_string());
|
||||||
let tx = self.update_tx.clone();
|
let tx = self.update_tx.clone();
|
||||||
let api_url = self.config.gitea_api_url.clone();
|
let api_url = self.config.gitea_api_url.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
|
|||||||
+21
-21
@@ -100,9 +100,9 @@ impl VimViewerModal {
|
|||||||
self.is_command_mode = false;
|
self.is_command_mode = false;
|
||||||
self.is_dirty = false;
|
self.is_dirty = false;
|
||||||
self.status_msg = Some(format!(
|
self.status_msg = Some(format!(
|
||||||
"Loaded {} lines • {}",
|
"Загружено {} строк • {}",
|
||||||
self.lines.len(),
|
self.lines.len(),
|
||||||
if self.is_writable { "Writable [i: edit, :w: save]" } else { "Read-Only" }
|
if self.is_writable { "Доступна запись [i: правка, :w: сохранить]" } else { "Только чтение" }
|
||||||
));
|
));
|
||||||
self.last_g_press = None;
|
self.last_g_press = None;
|
||||||
}
|
}
|
||||||
@@ -117,7 +117,7 @@ impl VimViewerModal {
|
|||||||
|
|
||||||
pub fn trigger_save(&mut self) {
|
pub fn trigger_save(&mut self) {
|
||||||
if !self.is_writable {
|
if !self.is_writable {
|
||||||
self.status_msg = Some("❌ Cannot save: folder is Read-Only".to_string());
|
self.status_msg = Some("❌ Невозможно сохранить: папка только для чтения".to_string());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,10 +129,10 @@ impl VimViewerModal {
|
|||||||
self.buffer_content.clone(),
|
self.buffer_content.clone(),
|
||||||
self.pin.clone(),
|
self.pin.clone(),
|
||||||
));
|
));
|
||||||
self.status_msg = Some("⏳ Saving changes to remote peer...".to_string());
|
self.status_msg = Some("⏳ Сохранение изменений на удаленном узле...".to_string());
|
||||||
self.is_dirty = false;
|
self.is_dirty = false;
|
||||||
} else {
|
} else {
|
||||||
self.status_msg = Some("❌ No remote endpoint associated with this file".to_string());
|
self.status_msg = Some("❌ Нет удаленного подключения для этого файла".to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -361,14 +361,14 @@ impl VimViewerModal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if self.is_writable {
|
if self.is_writable {
|
||||||
ui.label(RichText::new("• [Writable]").color(Color32::from_rgb(34, 197, 94)).size(11.0));
|
ui.label(RichText::new("• [Доступна запись]").color(Color32::from_rgb(34, 197, 94)).size(11.0));
|
||||||
} else {
|
} else {
|
||||||
ui.label(RichText::new("• [Read-Only]").color(Color32::from_rgb(148, 163, 184)).size(11.0));
|
ui.label(RichText::new("• [Только чтение]").color(Color32::from_rgb(148, 163, 184)).size(11.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||||
if ui
|
if ui
|
||||||
.button(RichText::new("✕ Close (:q)").color(Color32::from_rgb(203, 213, 225)))
|
.button(RichText::new("✕ Закрыть (:q)").color(Color32::from_rgb(203, 213, 225)))
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
{
|
||||||
self.close();
|
self.close();
|
||||||
@@ -376,7 +376,7 @@ impl VimViewerModal {
|
|||||||
|
|
||||||
if self.is_writable {
|
if self.is_writable {
|
||||||
let save_btn = egui::Button::new(
|
let save_btn = egui::Button::new(
|
||||||
RichText::new("💾 Save (:w)")
|
RichText::new("💾 Сохранить (:w)")
|
||||||
.strong()
|
.strong()
|
||||||
.color(Color32::WHITE),
|
.color(Color32::WHITE),
|
||||||
)
|
)
|
||||||
@@ -387,7 +387,7 @@ impl VimViewerModal {
|
|||||||
self.trigger_save();
|
self.trigger_save();
|
||||||
}
|
}
|
||||||
|
|
||||||
let edit_label = if self.is_insert_mode { "👁 View Mode (Esc)" } else { "✏️ Edit (i)" };
|
let edit_label = if self.is_insert_mode { "👁 Просмотр (Esc)" } else { "✏️ Редактировать (i)" };
|
||||||
if ui.button(RichText::new(edit_label).color(Color32::from_rgb(226, 232, 240))).clicked() {
|
if ui.button(RichText::new(edit_label).color(Color32::from_rgb(226, 232, 240))).clicked() {
|
||||||
self.is_insert_mode = !self.is_insert_mode;
|
self.is_insert_mode = !self.is_insert_mode;
|
||||||
if !self.is_insert_mode {
|
if !self.is_insert_mode {
|
||||||
@@ -398,7 +398,7 @@ impl VimViewerModal {
|
|||||||
|
|
||||||
if self.file_name.to_lowercase().ends_with(".md") {
|
if self.file_name.to_lowercase().ends_with(".md") {
|
||||||
if ui
|
if ui
|
||||||
.selectable_label(self.is_markdown_view, "👁 Markdown Preview")
|
.selectable_label(self.is_markdown_view, "👁 Предпросмотр Markdown")
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
{
|
||||||
self.is_markdown_view = !self.is_markdown_view;
|
self.is_markdown_view = !self.is_markdown_view;
|
||||||
@@ -406,7 +406,7 @@ impl VimViewerModal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ui.label(
|
ui.label(
|
||||||
RichText::new(format!("{} lines", self.lines.len()))
|
RichText::new(format!("{} строк", self.lines.len()))
|
||||||
.size(11.0)
|
.size(11.0)
|
||||||
.color(Color32::from_rgb(148, 163, 184)),
|
.color(Color32::from_rgb(148, 163, 184)),
|
||||||
);
|
);
|
||||||
@@ -539,13 +539,13 @@ impl VimViewerModal {
|
|||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
// Mode Pill
|
// Mode Pill
|
||||||
let (mode_text, mode_bg, mode_fg) = if self.is_insert_mode {
|
let (mode_text, mode_bg, mode_fg) = if self.is_insert_mode {
|
||||||
("INSERT", Color32::from_rgb(34, 197, 94), Color32::BLACK)
|
("ВСТАВКА", Color32::from_rgb(34, 197, 94), Color32::BLACK)
|
||||||
} else if self.is_searching {
|
} else if self.is_searching {
|
||||||
("SEARCH", Color32::from_rgb(217, 119, 6), Color32::WHITE)
|
("ПОИСК", Color32::from_rgb(217, 119, 6), Color32::WHITE)
|
||||||
} else if self.is_command_mode {
|
} else if self.is_command_mode {
|
||||||
("COMMAND", Color32::from_rgb(147, 51, 234), Color32::WHITE)
|
("КОМАНДА", Color32::from_rgb(147, 51, 234), Color32::WHITE)
|
||||||
} else {
|
} else {
|
||||||
("NORMAL", Color32::from_rgb(37, 99, 235), Color32::WHITE)
|
("ОБЫЧНЫЙ", Color32::from_rgb(37, 99, 235), Color32::WHITE)
|
||||||
};
|
};
|
||||||
|
|
||||||
Frame::none()
|
Frame::none()
|
||||||
@@ -559,16 +559,16 @@ impl VimViewerModal {
|
|||||||
if self.is_searching {
|
if self.is_searching {
|
||||||
ui.label(RichText::new("/").monospace().color(Color32::from_rgb(245, 158, 11)));
|
ui.label(RichText::new("/").monospace().color(Color32::from_rgb(245, 158, 11)));
|
||||||
let edit = TextEdit::singleline(&mut self.search_query)
|
let edit = TextEdit::singleline(&mut self.search_query)
|
||||||
.hint_text("pattern (press Enter)...")
|
.hint_text("поиск (нажмите Enter)...")
|
||||||
.font(egui::TextStyle::Monospace)
|
.font(egui::TextStyle::Monospace)
|
||||||
.desired_width(200.0);
|
.desired_width(200.0);
|
||||||
ui.add(edit).request_focus();
|
ui.add(edit).request_focus();
|
||||||
} else if self.is_command_mode {
|
} else if self.is_command_mode {
|
||||||
ui.label(RichText::new(":").monospace().color(Color32::from_rgb(168, 85, 247)));
|
ui.label(RichText::new(":").monospace().color(Color32::from_rgb(168, 85, 247)));
|
||||||
let edit = TextEdit::singleline(&mut self.command_input)
|
let edit = TextEdit::singleline(&mut self.command_input)
|
||||||
.hint_text("w (save) / q (quit) / line...")
|
.hint_text("w (сохранить) / q (выход) / номер...")
|
||||||
.font(egui::TextStyle::Monospace)
|
.font(egui::TextStyle::Monospace)
|
||||||
.desired_width(200.0);
|
.desired_width(220.0);
|
||||||
ui.add(edit).request_focus();
|
ui.add(edit).request_focus();
|
||||||
} else {
|
} else {
|
||||||
if let Some(msg) = &self.status_msg {
|
if let Some(msg) = &self.status_msg {
|
||||||
@@ -578,7 +578,7 @@ impl VimViewerModal {
|
|||||||
|
|
||||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||||
ui.label(
|
ui.label(
|
||||||
RichText::new("i: edit • Esc: normal • :w: save • :q: exit")
|
RichText::new("i: правка • Esc: обычный • :w: сохранить • :q: выход")
|
||||||
.size(10.0)
|
.size(10.0)
|
||||||
.color(Color32::from_rgb(100, 116, 139)),
|
.color(Color32::from_rgb(100, 116, 139)),
|
||||||
);
|
);
|
||||||
@@ -586,7 +586,7 @@ impl VimViewerModal {
|
|||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
ui.label(
|
ui.label(
|
||||||
RichText::new(format!("Ln {}, Col 1", self.cursor_line))
|
RichText::new(format!("Стр {}, Кол 1", self.cursor_line))
|
||||||
.size(11.0)
|
.size(11.0)
|
||||||
.monospace()
|
.monospace()
|
||||||
.color(Color32::from_rgb(148, 163, 184)),
|
.color(Color32::from_rgb(148, 163, 184)),
|
||||||
|
|||||||
+15
-5
@@ -335,17 +335,27 @@ pub async fn perform_update_download(
|
|||||||
info!("Update successfully installed to {:?}", current_exe);
|
info!("Update successfully installed to {:?}", current_exe);
|
||||||
let _ = progress_tx.send(UpdateProgress::Completed);
|
let _ = progress_tx.send(UpdateProgress::Completed);
|
||||||
|
|
||||||
|
// Spawn background PowerShell cleanup script to delete old binary once this process terminates
|
||||||
|
let cleanup_script = format!(
|
||||||
|
"Start-Sleep -Seconds 1; Remove-Item -Force -ErrorAction SilentlyContinue '{}'",
|
||||||
|
old_backup_path.to_string_lossy()
|
||||||
|
);
|
||||||
|
let _ = std::process::Command::new("powershell")
|
||||||
|
.args(["-NoProfile", "-WindowStyle", "Hidden", "-Command", &cleanup_script])
|
||||||
|
.spawn();
|
||||||
|
|
||||||
// Spawn updated application and exit
|
// Spawn updated application and exit
|
||||||
let _ = std::process::Command::new(¤t_exe).spawn();
|
let _ = std::process::Command::new(¤t_exe).spawn();
|
||||||
std::thread::sleep(Duration::from_millis(500));
|
std::thread::sleep(Duration::from_millis(400));
|
||||||
std::process::exit(0);
|
std::process::exit(0);
|
||||||
} else {
|
} else {
|
||||||
// Fallback: Use PowerShell deferred replacement
|
// Fallback: Use PowerShell deferred replacement and cleanup
|
||||||
info!("Direct rename locked; triggering background PowerShell replacement...");
|
info!("Direct rename locked; triggering background PowerShell replacement...");
|
||||||
let script = format!(
|
let script = format!(
|
||||||
"Start-Sleep -Milliseconds 600; Move-Item -Force '{temp}' '{target}'; Start-Process '{target}'",
|
"Start-Sleep -Milliseconds 600; Move-Item -Force '{temp}' '{target}'; Start-Sleep -Seconds 1; Remove-Item -Force -ErrorAction SilentlyContinue '{old}'; Start-Process '{target}'",
|
||||||
temp = temp_download_path.to_string_lossy(),
|
temp = temp_download_path.to_string_lossy(),
|
||||||
target = current_exe.to_string_lossy()
|
target = current_exe.to_string_lossy(),
|
||||||
|
old = old_backup_path.to_string_lossy()
|
||||||
);
|
);
|
||||||
|
|
||||||
let _ = std::process::Command::new("powershell")
|
let _ = std::process::Command::new("powershell")
|
||||||
@@ -353,7 +363,7 @@ pub async fn perform_update_download(
|
|||||||
.spawn();
|
.spawn();
|
||||||
|
|
||||||
let _ = progress_tx.send(UpdateProgress::Completed);
|
let _ = progress_tx.send(UpdateProgress::Completed);
|
||||||
std::thread::sleep(Duration::from_millis(500));
|
std::thread::sleep(Duration::from_millis(400));
|
||||||
std::process::exit(0);
|
std::process::exit(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user