feat: ZeroSend v2.5.1 - Revamped 3-Step Live Folder Auto-Sync, Dynamic Credits & UI Polish
This commit is contained in:
Generated
+1
-1
@@ -6372,7 +6372,7 @@ checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerosend"
|
name = "zerosend"
|
||||||
version = "2.5.0"
|
version = "2.5.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arboard",
|
"arboard",
|
||||||
"axum",
|
"axum",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "zerosend"
|
name = "zerosend"
|
||||||
version = "2.5.0"
|
version = "2.5.1"
|
||||||
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"]
|
||||||
|
|||||||
+1
-1
@@ -298,7 +298,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||||||
.with_inner_size([config.window_width, config.window_height])
|
.with_inner_size([config.window_width, config.window_height])
|
||||||
.with_min_inner_size([780.0, 500.0])
|
.with_min_inner_size([780.0, 500.0])
|
||||||
.with_visible(!start_minimized)
|
.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()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2330,7 +2330,7 @@ async fn handle_web_ui(State(state): State<Arc<ServerState>>) -> Html<String> {
|
|||||||
<div class="toast" id="toast"></div>
|
<div class="toast" id="toast"></div>
|
||||||
|
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
ZeroSend v2.4.1 • Ultra-fast P2P File & Text Sharing • 🛡️ SHA-256 Verified
|
ZeroSend v{version} • Ultra-fast P2P File & Text Sharing • 🛡️ SHA-256 Verified
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -2766,6 +2766,7 @@ async fn handle_web_ui(State(state): State<Arc<ServerState>>) -> Html<String> {
|
|||||||
</html>"###,
|
</html>"###,
|
||||||
device_name = state.my_info.name,
|
device_name = state.my_info.name,
|
||||||
net_type = state.my_info.net_type,
|
net_type = state.my_info.net_type,
|
||||||
|
version = env!("CARGO_PKG_VERSION"),
|
||||||
);
|
);
|
||||||
|
|
||||||
Html(html)
|
Html(html)
|
||||||
|
|||||||
+207
-141
@@ -2023,62 +2023,6 @@ impl ZeroSendApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn render_my_shares(&mut self, ui: &mut Ui, theme: ThemeColors) {
|
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() {
|
if self.shared_folders.is_empty() {
|
||||||
Frame::none()
|
Frame::none()
|
||||||
.fill(theme.bg_card)
|
.fill(theme.bg_card)
|
||||||
@@ -2089,7 +2033,7 @@ impl ZeroSendApp {
|
|||||||
ui.vertical_centered(|ui| {
|
ui.vertical_centered(|ui| {
|
||||||
ui.label(RichText::new("📁").size(36.0));
|
ui.label(RichText::new("📁").size(36.0));
|
||||||
ui.label(
|
ui.label(
|
||||||
RichText::new("You haven't shared any local folders yet.")
|
RichText::new("У вас пока нет локальных общих папок.")
|
||||||
.size(15.0)
|
.size(15.0)
|
||||||
.strong()
|
.strong()
|
||||||
.color(theme.text_primary),
|
.color(theme.text_primary),
|
||||||
@@ -2097,15 +2041,15 @@ impl ZeroSendApp {
|
|||||||
ui.add_space(4.0);
|
ui.add_space(4.0);
|
||||||
ui.label(
|
ui.label(
|
||||||
RichText::new(
|
RichText::new(
|
||||||
"Share movies, music, or code folders so your friends on ZeroTier/LAN can stream and download them.",
|
"Откройте доступ к папкам с фильмами, музыкой или кодом, чтобы ваши друзья в ZeroTier/LAN могли смотреть и скачивать их.",
|
||||||
)
|
)
|
||||||
.size(12.0)
|
.size(12.0)
|
||||||
.color(theme.text_muted),
|
.color(theme.text_muted),
|
||||||
);
|
);
|
||||||
ui.add_space(12.0);
|
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))
|
.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 ui.add(choose_btn).clicked() {
|
||||||
if let Some(folder) = FileDialog::new().pick_folder() {
|
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));
|
ui.label(RichText::new(&share.name).strong().size(14.0).color(theme.text_primary));
|
||||||
|
|
||||||
let (ro_label, ro_color) = if share.read_only {
|
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 {
|
} else {
|
||||||
("Read & Write (Uploads Allowed)", Color32::from_rgb(34, 197, 94))
|
("Чтение и запись (Загрузка разрешена)", Color32::from_rgb(34, 197, 94))
|
||||||
};
|
};
|
||||||
|
|
||||||
Frame::none()
|
Frame::none()
|
||||||
@@ -2169,7 +2113,7 @@ impl ZeroSendApp {
|
|||||||
.inner_margin(Margin::symmetric(6.0, 2.0))
|
.inner_margin(Margin::symmetric(6.0, 2.0))
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
ui.label(
|
ui.label(
|
||||||
RichText::new("🔒 PIN Protected")
|
RichText::new("🔒 PIN-защита")
|
||||||
.size(10.5)
|
.size(10.5)
|
||||||
.color(Color32::from_rgb(245, 158, 11)),
|
.color(Color32::from_rgb(245, 158, 11)),
|
||||||
);
|
);
|
||||||
@@ -2186,15 +2130,15 @@ impl ZeroSendApp {
|
|||||||
});
|
});
|
||||||
|
|
||||||
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
|
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);
|
remove_index = Some(idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ui.button("📂 Open Folder").clicked() {
|
if ui.button("📂 Открыть папку").clicked() {
|
||||||
let _ = std::process::Command::new("explorer").arg(&share.path).spawn();
|
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;
|
config_changed = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -2215,80 +2159,184 @@ impl ZeroSendApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn render_auto_sync_subtab(&mut self, ui: &mut Ui, theme: ThemeColors) {
|
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()
|
Frame::none()
|
||||||
.fill(theme.bg_card)
|
.fill(theme.bg_card)
|
||||||
.rounding(Rounding::same(8.0))
|
.rounding(Rounding::same(8.0))
|
||||||
.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| {
|
||||||
ui.horizontal(|ui| {
|
ui.label(RichText::new("➕ Настройка новой авто-синхронизации (3 простых шага)").strong().size(13.5).color(theme.text_accent));
|
||||||
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.add_space(8.0);
|
ui.add_space(8.0);
|
||||||
ui.separator();
|
ui.separator();
|
||||||
ui.add_space(8.0);
|
ui.add_space(8.0);
|
||||||
|
|
||||||
// Add New Sync Pairing form
|
// --- ШАГ 1: Выбор папки друга ---
|
||||||
ui.label(RichText::new("➕ Create New Sync Pairing").strong().color(theme.text_secondary));
|
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.add_space(4.0);
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label("Name:");
|
let browse_btn = Button::new(RichText::new("📁 Выбрать папку на диске...").strong().color(Color32::WHITE))
|
||||||
ui.add(egui::TextEdit::singleline(&mut self.new_sync_name).hint_text("My Project Sync").desired_width(130.0));
|
.fill(Color32::from_rgb(59, 130, 246))
|
||||||
|
.rounding(Rounding::same(4.0));
|
||||||
|
|
||||||
ui.label("Local Path:");
|
if ui.add(browse_btn).clicked() {
|
||||||
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 let Some(folder) = FileDialog::new().pick_folder() {
|
if let Some(folder) = FileDialog::new().pick_folder() {
|
||||||
self.new_sync_local_path = folder.to_string_lossy().into_owned();
|
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.add_space(4.0);
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label("Friend (Peer ID):");
|
ui.label("Имя пары:");
|
||||||
ui.add(egui::TextEdit::singleline(&mut self.new_sync_peer_id).hint_text("Select or paste peer ID...").desired_width(180.0));
|
ui.add(egui::TextEdit::singleline(&mut self.new_sync_name).hint_text("Мой проект").desired_width(180.0));
|
||||||
|
|
||||||
ui.label("Remote Folder ID:");
|
let is_ready = !self.new_sync_local_path.trim().is_empty()
|
||||||
ui.add(egui::TextEdit::singleline(&mut self.new_sync_folder_id).hint_text("Shared folder ID...").desired_width(160.0));
|
&& !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))
|
let start_btn = Button::new(RichText::new("🚀 Запустить авто-синхронизацию").strong().color(Color32::WHITE))
|
||||||
.fill(Color32::from_rgb(34, 197, 94))
|
.fill(if is_ready { Color32::from_rgb(34, 197, 94) } else { Color32::from_rgb(100, 116, 139) })
|
||||||
.rounding(Rounding::same(4.0));
|
.rounding(Rounding::same(4.0))
|
||||||
|
.min_size(Vec2::new(200.0, 30.0));
|
||||||
|
|
||||||
if ui.add(add_pair_btn).clicked() {
|
if ui.add_enabled(is_ready, start_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() {
|
||||||
let name = if self.new_sync_name.trim().is_empty() {
|
"Folder Sync".to_string()
|
||||||
"Folder Sync".to_string()
|
} else {
|
||||||
} else {
|
self.new_sync_name.trim().to_string()
|
||||||
self.new_sync_name.trim().to_string()
|
};
|
||||||
};
|
|
||||||
|
|
||||||
let pair = crate::transfer::FolderSyncPairConfig {
|
let peer_name = self.peers.iter()
|
||||||
id: uuid::Uuid::new_v4().to_string(),
|
.find(|p| p.peer_id == self.new_sync_peer_id)
|
||||||
name,
|
.map(|p| self.get_peer_display_name(p).0)
|
||||||
local_path: PathBuf::from(self.new_sync_local_path.trim()),
|
.unwrap_or_else(|| "Friend".to_string());
|
||||||
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,
|
|
||||||
};
|
|
||||||
|
|
||||||
self.folder_sync_pairs.push(pair);
|
let pair = crate::transfer::FolderSyncPairConfig {
|
||||||
self.config.folder_sync_pairs = self.folder_sync_pairs.clone();
|
id: uuid::Uuid::new_v4().to_string(),
|
||||||
self.persist_config();
|
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.folder_sync_pairs.push(pair);
|
||||||
self.new_sync_name.clear();
|
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);
|
ui.add_space(10.0);
|
||||||
|
|
||||||
// List of Active Sync Pairs
|
// 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() {
|
if self.folder_sync_pairs.is_empty() {
|
||||||
Frame::none()
|
Frame::none()
|
||||||
.fill(theme.bg_card)
|
.fill(theme.bg_card)
|
||||||
@@ -2304,8 +2355,8 @@ impl ZeroSendApp {
|
|||||||
.inner_margin(Margin::same(24.0))
|
.inner_margin(Margin::same(24.0))
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
ui.vertical_centered(|ui| {
|
ui.vertical_centered(|ui| {
|
||||||
ui.label(RichText::new("🔄 No active sync pairings configured.").color(theme.text_secondary));
|
ui.label(RichText::new("🔄 Нет активных настроенных пар синхронизации.").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("Выберите общую папку друга в мастере выше и укажите локальную папку для старта.").size(11.5).color(theme.text_muted));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -2320,35 +2371,35 @@ impl ZeroSendApp {
|
|||||||
.inner_margin(Margin::same(12.0))
|
.inner_margin(Margin::same(12.0))
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label(RichText::new("🔄").size(20.0));
|
ui.label(RichText::new("🔄").size(22.0));
|
||||||
ui.vertical(|ui| {
|
ui.vertical(|ui| {
|
||||||
ui.horizontal(|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 {
|
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 {
|
} 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(
|
ui.horizontal(|ui| {
|
||||||
RichText::new(format!("Local: {:?} ➔ Remote Folder ID: {}", pair.local_path, pair.remote_folder_id))
|
ui.label(RichText::new("💻 Локально:").strong().size(11.0).color(theme.text_secondary));
|
||||||
.monospace()
|
ui.label(RichText::new(format!("{:?}", pair.local_path)).monospace().size(11.0).color(theme.text_muted));
|
||||||
.size(11.0)
|
ui.label(RichText::new("➔ 🌐 Удаленно (Друг):").strong().size(11.0).color(theme.text_secondary));
|
||||||
.color(theme.text_muted),
|
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| {
|
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);
|
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;
|
sync_changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ui.button("📂 Open Local").clicked() {
|
if ui.button("📂 Открыть локально").clicked() {
|
||||||
let _ = std::process::Command::new("explorer").arg(&pair.local_path).spawn();
|
let _ = std::process::Command::new("explorer").arg(&pair.local_path).spawn();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -2377,12 +2428,12 @@ impl ZeroSendApp {
|
|||||||
.stroke(theme.stroke_border)
|
.stroke(theme.stroke_border)
|
||||||
.inner_margin(Margin::same(10.0))
|
.inner_margin(Margin::same(10.0))
|
||||||
.show(ui, |ui| {
|
.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);
|
ui.add_space(4.0);
|
||||||
|
|
||||||
ScrollArea::vertical().max_height(140.0).show(ui, |ui| {
|
ScrollArea::vertical().max_height(140.0).show(ui, |ui| {
|
||||||
if self.sync_log_messages.is_empty() {
|
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 {
|
} else {
|
||||||
for log in self.sync_log_messages.iter().rev() {
|
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)));
|
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| {
|
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
|
||||||
// Grep search button
|
// 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.show_grep_modal = true;
|
||||||
self.grep_query.clear();
|
self.grep_query.clear();
|
||||||
self.grep_results.clear();
|
self.grep_results.clear();
|
||||||
self.grep_status_msg = None;
|
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
|
// Search filter box
|
||||||
ui.add(
|
ui.add(
|
||||||
egui::TextEdit::singleline(&mut self.share_search_query)
|
egui::TextEdit::singleline(&mut self.share_search_query)
|
||||||
.hint_text("🔍 Filter files...")
|
.hint_text("🔍 Фильтр файлов...")
|
||||||
.desired_width(140.0),
|
.desired_width(130.0),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Upload button if folder is Read-Write
|
// Upload button if folder is Read-Write
|
||||||
@@ -3564,7 +3631,7 @@ impl ZeroSendApp {
|
|||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label(RichText::new("⚡ ZeroSend").size(16.0).strong().color(theme.text_accent));
|
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.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("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.add_space(6.0);
|
||||||
ui.label(RichText::new("⚙ Technology Stack:").strong().color(theme.text_primary));
|
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(" • 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(" • 🔄 Two-Way Live Folder Synchronization Engine (Continuous Peer Mirroring)").color(theme.text_secondary));
|
||||||
ui.label(RichText::new(" • Full Portable Exe-Folder Configuration & Data Storage").color(theme.text_secondary));
|
ui.label(RichText::new(" • 🖧 Windows Explorer Network Drive (WebDAV RFC 4918)").color(theme.text_secondary));
|
||||||
ui.label(RichText::new(" • Embedded Lightweight Vim Code & Text Viewer").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(" • SHA-256 On-The-Fly Cryptographic File Integrity Verification").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(" • Mobile Web PWA with Thumbnails & Voice Notes Recorder").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(" • 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(" • 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));
|
ui.label(RichText::new(" • UDP Broadcast ZeroTier Mesh Discovery Engine").color(theme.text_secondary));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user