feat(desktop): переход в комнату по уведомлению, выход без потери правок, Ctrl/Cmd+K
Три пункта спецификации клиента (docs/client-tauri.md §5), которые оставались открытыми в обёртке: - **Клик по уведомлению → переход в канал.** У `tauri-plugin-notification` на desktop нет колбэка действия, поэтому реализовано лучшее доступное поведение: уведомление, показанное при неактивном окне, запоминается, и когда приложение активируется в течение 10 секунд (окно получило фокус или macOS прислала `RunEvent::Reopen`), окно поднимается и открывается комната из уведомления — `glchat://guild/<id>/channel/<id>`, для личной беседы `glchat://dm/<id>`. Идентификаторы проверяются (только цифры), чтобы из текста уведомления нельзя было собрать произвольный deep link. Ограничение: ОС не сообщает, что активация вызвана именно кликом, поэтому переход сработает и при обычном возврате в приложение в эти 10 секунд — это описано в desktop/README.md; - **Выход при несохранённых изменениях.** Клиент сообщает о правках командой `desktop_set_dirty`; закрытие окна и пункт «Выход» в трее перехватываются (`window::request_close_confirmation`), окно best-effort покачивается (`Window::set_position`, на Wayland молча пропускается), а решение принимает пользователь в диалоге клиента «Сохранить / Выйти без сохранения»; закрывает приложение команда `desktop_close_window`. Раньше выход из трея терял правки молча; - **Ctrl/Cmd+K.** Комбинация регистрируется глобально (`shortcuts.rs`) и при неактивном окне поднимает окно и просит клиент открыть палитру команд (`WebMessage::OpenPalette`); повторы комбинаций с настраиваемыми шорткатами отсекаются по разобранной комбинации, иначе вторая регистрация падала бы. Заодно: `updates.rs` получил тест, который разбирает манифест релиза типом самого `tauri-plugin-updater` (`RemoteRelease`) — он ловит расхождение формата (например, дату не в RFC 3339), из-за которого обновление молча не приходило бы. Проверки: `make desktop-check` (fmt, clippy, 10 тестов).
This commit is contained in:
@@ -12,6 +12,10 @@ use crate::bridge::{self, WebMessage};
|
||||
use crate::settings::Settings;
|
||||
use crate::state::AppState;
|
||||
|
||||
/// Палитра команд (docs/client-tauri.md §5): комбинация не настраивается —
|
||||
/// это системный жест «быстрый переход», одинаковый во всех сборках.
|
||||
const PALETTE_SHORTCUT: &str = "CommandOrControl+K";
|
||||
|
||||
/// Обработчик нажатий: плагин вызывает его для любой зарегистрированной комбинации.
|
||||
pub fn on_shortcut<R: Runtime>(
|
||||
app: &AppHandle<R>,
|
||||
@@ -36,6 +40,12 @@ pub fn on_shortcut<R: Runtime>(
|
||||
}
|
||||
if parse(&settings.deafen_shortcut).as_ref() == Some(shortcut) {
|
||||
bridge::send(app, &WebMessage::ToggleDeafen);
|
||||
return;
|
||||
}
|
||||
// Быстрый переход: окно поднимаем и просим клиент открыть палитру команд.
|
||||
if parse(PALETTE_SHORTCUT).as_ref() == Some(shortcut) {
|
||||
crate::window::show_main(app);
|
||||
bridge::send(app, &WebMessage::OpenPalette);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,17 +56,33 @@ pub fn apply<R: Runtime>(app: &AppHandle<R>, settings: &Settings) -> Result<(),
|
||||
.unregister_all()
|
||||
.map_err(|error| format!("не удалось снять старые шорткаты: {error}"))?;
|
||||
|
||||
for spec in [
|
||||
&settings.ptt_shortcut,
|
||||
&settings.mute_shortcut,
|
||||
&settings.deafen_shortcut,
|
||||
] {
|
||||
if spec.trim().is_empty() {
|
||||
// Палитра команд регистрируется всегда и последней: если пользователь занял
|
||||
// её комбинацию своим действием, второй раз её регистрировать нельзя —
|
||||
// сработает то действие, чья проверка в `on_shortcut` стоит раньше.
|
||||
let wanted = [
|
||||
settings.ptt_shortcut.as_str(),
|
||||
settings.mute_shortcut.as_str(),
|
||||
settings.deafen_shortcut.as_str(),
|
||||
PALETTE_SHORTCUT,
|
||||
];
|
||||
// Повторы отсекаются по разобранной комбинации: «Cmd+K» и «Command+K» —
|
||||
// одна и та же клавиша, и вторая регистрация вернула бы ошибку.
|
||||
let mut registered: Vec<Shortcut> = Vec::new();
|
||||
for spec in wanted {
|
||||
let trimmed = spec.trim();
|
||||
if trimmed.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let Some(parsed) = parse(trimmed) else {
|
||||
continue;
|
||||
};
|
||||
if registered.contains(&parsed) {
|
||||
continue;
|
||||
}
|
||||
shortcuts
|
||||
.register(spec.as_str())
|
||||
.map_err(|error| format!("комбинация «{spec}» не зарегистрирована: {error}"))?;
|
||||
.register(trimmed)
|
||||
.map_err(|error| format!("комбинация «{trimmed}» не зарегистрирована: {error}"))?;
|
||||
registered.push(parsed);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user