feat(desktop): port v0.11.4-v0.15.2 from Tauri to Electron + Discord-parity audio (v0.16.0)
Chronological port of every Tauri release commit (v0.11.4 -> v0.15.2)
into the Electron rebuild, plus Discord-style audio handling that goes
beyond the Tauri original.
Highlights:
- All 17 Tauri release commits ported (audio fixes, custom notification
sound, Discord-style chat UX, profile banner, changelog page,
Discord-parity call UX, screen-share echo + re-watch UX, audio-loop
fix).
- Native napi-rs audio-loopback addon with WASAPI process-loopback:
* EXCLUDE_TARGET_PROCESS_TREE for full-screen shares -> peers
never hear themselves echoed back through the capture.
* INCLUDE_TARGET_PROCESS_TREE for window shares -> only the
picked window's audio is captured, not the whole OS mixer
(Discord parity).
* HWND -> PID resolution via Win32 GetWindowThreadProcessId.
- Discord-style screen-source picker (thumbnail grid, screens vs
apps tabs, live-refreshing thumbnails).
- Hash routing fix for packaged builds (file:// can't resolve
BrowserRouter paths).
- Tauri sources removed (apps/desktop/src-tauri).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,15 +4,13 @@ import { NavLink } from 'react-router-dom';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import { useConversationsContext } from '../context/ConversationsContext';
|
||||
import { useFriendshipsContext } from '../context/FriendshipsContext';
|
||||
import { useTheme } from '../context/ThemeContext';
|
||||
import {
|
||||
ChatBubbleIcon,
|
||||
GearIcon,
|
||||
LogoMark,
|
||||
MoonIcon,
|
||||
ShieldIcon,
|
||||
SignOutIcon,
|
||||
SunIcon,
|
||||
SparklesIcon,
|
||||
UsersIcon,
|
||||
} from './icons';
|
||||
|
||||
@@ -41,29 +39,27 @@ export function Sidebar() {
|
||||
const { signOut, profile } = useAuth();
|
||||
const { incomingCount } = useFriendshipsContext();
|
||||
const { totalUnread } = useConversationsContext();
|
||||
const { theme, toggle } = useTheme();
|
||||
|
||||
const navItems: NavItem[] = profile?.isAdmin ? [...PRIMARY_NAV, ADMIN_NAV] : PRIMARY_NAV;
|
||||
|
||||
return (
|
||||
<aside
|
||||
aria-label="Primary navigation"
|
||||
className="flex h-screen w-[72px] shrink-0 flex-col items-center border-r border-line bg-surface-2/80 py-4 backdrop-blur-xl"
|
||||
className="discord-rail flex h-screen w-[72px] shrink-0 flex-col items-center border-r border-line bg-surface-2 py-3"
|
||||
>
|
||||
<div className="flex h-10 w-10 items-center justify-center" title="Netralax">
|
||||
<LogoMark className="h-8 w-8" />
|
||||
<div
|
||||
className="flex h-12 w-12 items-center justify-center rounded-2xl bg-accent text-accent-fg shadow-[0_10px_26px_rgba(88,101,242,0.32)]"
|
||||
title="Netralax"
|
||||
>
|
||||
<LogoMark className="h-10 w-10" />
|
||||
</div>
|
||||
|
||||
<div className="my-3 h-px w-8 bg-line" aria-hidden="true" />
|
||||
<div className="my-3 h-0.5 w-8 rounded-full bg-line" aria-hidden="true" />
|
||||
|
||||
<nav className="flex flex-col items-center gap-2">
|
||||
<nav className="flex flex-col items-center gap-2.5">
|
||||
{navItems.map((item) => {
|
||||
const badgeCount =
|
||||
item.badge === 'friends'
|
||||
? incomingCount
|
||||
: item.badge === 'chats'
|
||||
? totalUnread
|
||||
: 0;
|
||||
item.badge === 'friends' ? incomingCount : item.badge === 'chats' ? totalUnread : 0;
|
||||
return (
|
||||
<RailNavLink
|
||||
key={item.to}
|
||||
@@ -78,17 +74,13 @@ export function Sidebar() {
|
||||
|
||||
<div className="flex-1" />
|
||||
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<div className="flex flex-col items-center gap-2.5">
|
||||
<RailNavLink
|
||||
to="/settings"
|
||||
label={t('app:nav.settings')}
|
||||
icon={GearIcon}
|
||||
/>
|
||||
<RailIconButton
|
||||
label={theme === 'dark' ? t('app:theme.light', { defaultValue: 'Light mode' }) : t('app:theme.dark', { defaultValue: 'Dark mode' })}
|
||||
onClick={toggle}
|
||||
icon={theme === 'dark' ? SunIcon : MoonIcon}
|
||||
to="/changelog"
|
||||
label={t('app:nav.changelog', { defaultValue: 'Was ist neu' })}
|
||||
icon={SparklesIcon}
|
||||
/>
|
||||
<RailNavLink to="/settings" label={t('app:nav.settings')} icon={GearIcon} />
|
||||
<RailIconButton
|
||||
label={t('app:sidebar.sign_out')}
|
||||
onClick={() => void signOut()}
|
||||
@@ -119,8 +111,8 @@ function RailNavLink({ to, label, icon: Icon, badge = 0 }: RailNavLinkProps) {
|
||||
'group relative flex h-11 w-11 cursor-pointer items-center justify-center rounded-xl transition',
|
||||
'focus:outline-none focus-visible:ring-2 focus-visible:ring-accent/50',
|
||||
isActive
|
||||
? 'bg-accent/15 text-accent'
|
||||
: 'text-fg-muted hover:bg-surface-3/60 hover:text-fg',
|
||||
? 'bg-accent text-accent-fg shadow-[0_8px_22px_rgba(88,101,242,0.34)]'
|
||||
: 'text-fg-muted hover:rounded-2xl hover:bg-surface-3/80 hover:text-fg dark:hover:bg-[#313338]',
|
||||
].join(' ')
|
||||
}
|
||||
>
|
||||
@@ -129,7 +121,7 @@ function RailNavLink({ to, label, icon: Icon, badge = 0 }: RailNavLinkProps) {
|
||||
{isActive && (
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="absolute -left-4 h-6 w-1 rounded-r-full bg-accent"
|
||||
className="absolute -left-[15px] h-7 w-1 rounded-r-full bg-fg"
|
||||
/>
|
||||
)}
|
||||
<Icon style={{ width: '20px', height: '20px' }} />
|
||||
@@ -150,8 +142,8 @@ interface RailIconButtonProps {
|
||||
function RailIconButton({ label, onClick, icon: Icon, tone = 'default' }: RailIconButtonProps) {
|
||||
const toneClass =
|
||||
tone === 'danger'
|
||||
? 'text-fg-muted hover:bg-rose-500/10 hover:text-rose-400 focus-visible:ring-rose-400/40'
|
||||
: 'text-fg-muted hover:bg-surface-3/60 hover:text-fg focus-visible:ring-accent/50';
|
||||
? 'text-fg-muted hover:rounded-2xl hover:bg-rose-500/10 hover:text-rose-400 focus-visible:ring-rose-400/40'
|
||||
: 'text-fg-muted hover:rounded-2xl hover:bg-surface-3/80 hover:text-fg focus-visible:ring-accent/50 dark:hover:bg-[#313338]';
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
@@ -159,7 +151,7 @@ function RailIconButton({ label, onClick, icon: Icon, tone = 'default' }: RailIc
|
||||
title={label}
|
||||
onClick={onClick}
|
||||
className={
|
||||
'flex h-11 w-11 cursor-pointer items-center justify-center rounded-xl transition focus:outline-none focus-visible:ring-2 ' +
|
||||
'flex h-11 w-11 cursor-pointer items-center justify-center rounded-xl transition-all duration-150 focus:outline-none focus-visible:ring-2 ' +
|
||||
toneClass
|
||||
}
|
||||
>
|
||||
@@ -173,7 +165,7 @@ function RailBadge({ count }: { count: number }) {
|
||||
return (
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="absolute -right-1 -top-1 inline-flex min-w-[18px] items-center justify-center rounded-full bg-rose-500 px-1 text-[10px] font-bold leading-tight text-white ring-2 ring-surface-2"
|
||||
className="absolute -right-1 -top-1 inline-flex min-w-[18px] items-center justify-center rounded-full bg-rose-500 px-1 text-[10px] font-bold leading-tight text-white ring-2 ring-surface-2 dark:ring-[#1e1f22]"
|
||||
>
|
||||
{display}
|
||||
</span>
|
||||
|
||||
Reference in New Issue
Block a user