feat(desktop): обёртка Tauri 2 вокруг веб-клиента
Первый инкремент Фазы 6 (docs/client-tauri.md): окно с тем же веб-клиентом (по умолчанию https://gl.mhspx.su, адрес настраивается), трей с меню и индикатором непрочитанных, нативные уведомления, глобальный push-to-talk и переключение микрофона/deafen, автозапуск, single-instance, сохранение геометрии окна, deep links glchat://…, автообновление по подписанному манифесту. Связь страницы с обёрткой — через платформенный адаптер (web/src/lib/platform.ts) и минимальный типизированный набор команд; в веб-клиенте появились системные уведомления об упоминаниях и личных сообщениях и переключатель в настройках. В web/src/stores/gateway.ts вместе с вызовом уведомления лежат правки по дефектам realtime (READY и сессии) из параллельной волны: файл коммитится целиком по договорённости с координатором.
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { unreadCount, useMessagesStore } from '@/stores/messages';
|
||||
import { useVoiceConnectionStore } from '@/stores/voiceConnection';
|
||||
import { platform, routeFromDeepLink } from '@/lib/platform';
|
||||
import { router } from '@/router';
|
||||
|
||||
/**
|
||||
* Мост desktop-обёртки (docs/client-tauri.md §2).
|
||||
*
|
||||
* Обёртка присылает события платформы (глобальный push-to-talk, mute/deafen,
|
||||
* deep link, готовность обновления), а клиент отдаёт ей счётчик непрочитанных.
|
||||
* В браузере компонент безвреден: реализация платформы там пустая.
|
||||
*/
|
||||
export function DesktopBridge() {
|
||||
// События обёртки: глобальные шорткаты, deep links, обновления.
|
||||
useEffect(
|
||||
() =>
|
||||
platform.onMessage((message) => {
|
||||
const voice = useVoiceConnectionStore.getState();
|
||||
switch (message.type) {
|
||||
case 'ptt':
|
||||
// Удержание клавиши: микрофон включается, пока она нажата.
|
||||
voice.setPushToTalkActive(message.active);
|
||||
break;
|
||||
case 'toggleMic':
|
||||
void voice.setMicEnabled(!voice.micEnabled);
|
||||
break;
|
||||
case 'toggleDeafen':
|
||||
void voice.setDeafened(!voice.deafened);
|
||||
break;
|
||||
case 'deepLink': {
|
||||
const route = routeFromDeepLink(message.url);
|
||||
if (route === null) {
|
||||
console.warn('[desktop] неизвестная ссылка', message.url);
|
||||
break;
|
||||
}
|
||||
void router.navigate(route);
|
||||
break;
|
||||
}
|
||||
case 'updateAvailable':
|
||||
platform.notify({
|
||||
title: 'Доступно обновление glchat',
|
||||
body: `Версия ${message.version}. Установить можно из настроек приложения.`,
|
||||
});
|
||||
break;
|
||||
case 'settings':
|
||||
// Настройки обёртки клиенту не нужны: он читает только свои.
|
||||
break;
|
||||
}
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
// Обёртка отдаёт накопленные deep links после этого сигнала.
|
||||
useEffect(() => {
|
||||
platform.ready();
|
||||
}, []);
|
||||
|
||||
// Счётчик непрочитанных для значка приложения и иконки в трее.
|
||||
useEffect(() => {
|
||||
let last = -1;
|
||||
const sync = (): void => {
|
||||
const channels = useMessagesStore.getState().channels;
|
||||
let total = 0;
|
||||
for (const record of Object.values(channels)) {
|
||||
total += unreadCount(record);
|
||||
}
|
||||
if (total !== last) {
|
||||
last = total;
|
||||
platform.setBadge(total);
|
||||
}
|
||||
};
|
||||
sync();
|
||||
return useMessagesStore.subscribe(sync);
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
}
|
||||
Reference in New Issue
Block a user