mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 22:01:09 +02:00
✨ feat(matrix): implement WhatsApp-style mobile navigation
Mobile users now get a separate room list and chat view: - /chat shows full-screen room list on mobile - /chat/[roomId] shows full-screen chat with back button - Desktop retains side-by-side layout unchanged - Last room restore only happens on desktop
This commit is contained in:
parent
f4c2663122
commit
435d06a756
4 changed files with 362 additions and 172 deletions
|
|
@ -1,6 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { matrixStore } from '$lib/matrix';
|
||||
import {
|
||||
ArrowLeft,
|
||||
List,
|
||||
Phone,
|
||||
VideoCamera,
|
||||
|
|
@ -18,9 +19,19 @@
|
|||
onSearchClick?: () => void;
|
||||
onVoiceCall?: () => void;
|
||||
onVideoCall?: () => void;
|
||||
showBackButton?: boolean;
|
||||
onBackClick?: () => void;
|
||||
}
|
||||
|
||||
let { onMenuClick, onInfoClick, onSearchClick, onVoiceCall, onVideoCall }: Props = $props();
|
||||
let {
|
||||
onMenuClick,
|
||||
onInfoClick,
|
||||
onSearchClick,
|
||||
onVoiceCall,
|
||||
onVideoCall,
|
||||
showBackButton = false,
|
||||
onBackClick,
|
||||
}: Props = $props();
|
||||
|
||||
// Check if calls are possible (DMs only for now)
|
||||
let canCall = $derived(matrixStore.currentSimpleRoom?.isDirect ?? false);
|
||||
|
|
@ -68,13 +79,23 @@
|
|||
<header
|
||||
class="flex items-center gap-3 border-b border-black/10 dark:border-white/10 bg-white/50 dark:bg-white/5 backdrop-blur-sm px-4 py-3"
|
||||
>
|
||||
<!-- Mobile menu button -->
|
||||
<button
|
||||
class="p-2 rounded-lg hover:bg-black/5 dark:hover:bg-white/10 transition-colors lg:hidden"
|
||||
onclick={onMenuClick}
|
||||
>
|
||||
<List class="h-5 w-5" />
|
||||
</button>
|
||||
<!-- Mobile back button or menu button -->
|
||||
{#if showBackButton}
|
||||
<button
|
||||
class="p-2 rounded-lg hover:bg-black/5 dark:hover:bg-white/10 transition-colors"
|
||||
onclick={onBackClick}
|
||||
aria-label="Zurück"
|
||||
>
|
||||
<ArrowLeft class="h-5 w-5" />
|
||||
</button>
|
||||
{:else}
|
||||
<button
|
||||
class="p-2 rounded-lg hover:bg-black/5 dark:hover:bg-white/10 transition-colors lg:hidden"
|
||||
onclick={onMenuClick}
|
||||
>
|
||||
<List class="h-5 w-5" />
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<!-- Room avatar with online indicator -->
|
||||
<div class="relative flex-shrink-0">
|
||||
|
|
|
|||
|
|
@ -262,26 +262,8 @@ class MatrixStore {
|
|||
this._rooms = this._client!.getRooms();
|
||||
console.log(`Matrix sync prepared, ${this._rooms.length} rooms loaded`);
|
||||
|
||||
// Restore last selected room
|
||||
if (browser && !this._currentRoomId) {
|
||||
const lastRoomId = localStorage.getItem(LAST_ROOM_KEY);
|
||||
console.log(
|
||||
`[Matrix] Restore check: lastRoomId=${lastRoomId}, rooms=${this._rooms.length}`
|
||||
);
|
||||
if (lastRoomId) {
|
||||
// Check if room exists in loaded rooms
|
||||
const roomExists = this._rooms.some((r) => r.roomId === lastRoomId);
|
||||
console.log(`[Matrix] Room exists: ${roomExists}`);
|
||||
if (roomExists) {
|
||||
console.log(`[Matrix] Restoring room: ${lastRoomId}`);
|
||||
this.selectRoom(lastRoomId);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log(
|
||||
`[Matrix] Skip restore: browser=${browser}, currentRoomId=${this._currentRoomId}`
|
||||
);
|
||||
}
|
||||
// Note: Last room restore is now handled in the /chat page component
|
||||
// to support different behavior on mobile vs desktop
|
||||
}
|
||||
|
||||
if (state === 'ERROR') {
|
||||
|
|
@ -482,7 +464,6 @@ class MatrixStore {
|
|||
* Select a room to view
|
||||
*/
|
||||
selectRoom(roomId: string) {
|
||||
console.log(`[Matrix] selectRoom called: ${roomId}`);
|
||||
this._currentRoomId = roomId;
|
||||
const room = this._client?.getRoom(roomId);
|
||||
|
||||
|
|
@ -498,10 +479,8 @@ class MatrixStore {
|
|||
// Save last room to localStorage
|
||||
if (browser) {
|
||||
localStorage.setItem(LAST_ROOM_KEY, roomId);
|
||||
console.log(`[Matrix] Saved to localStorage: ${roomId}`);
|
||||
}
|
||||
} else {
|
||||
console.log(`[Matrix] Room not found by getRoom: ${roomId}`);
|
||||
this._timeline = [];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,15 +6,15 @@
|
|||
import SearchDialog from '$lib/components/chat/SearchDialog.svelte';
|
||||
import ForwardMessageDialog from '$lib/components/chat/ForwardMessageDialog.svelte';
|
||||
import { CallView, IncomingCallDialog } from '$lib/components/call';
|
||||
import { ChatCircle, Plus, Gear, List } from '@manacore/shared-icons';
|
||||
import { ChatCircle, Plus, Gear } from '@manacore/shared-icons';
|
||||
import { browser } from '$app/environment';
|
||||
import { goto } from '$app/navigation';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
// Call state
|
||||
let activeCall = $derived(matrixStore.activeCall);
|
||||
let incomingCall = $derived(matrixStore.incomingCall);
|
||||
|
||||
// Start with sidebar closed on mobile
|
||||
let sidebarOpen = $state(browser ? window.innerWidth >= 1024 : true);
|
||||
let showCreateRoom = $state(false);
|
||||
let showRoomSettings = $state(false);
|
||||
let showSearch = $state(false);
|
||||
|
|
@ -25,17 +25,13 @@
|
|||
let editMessage = $state<SimpleMessage | null>(null);
|
||||
let forwardMessage = $state<SimpleMessage | null>(null);
|
||||
|
||||
// Check if mobile
|
||||
// Check if mobile/desktop
|
||||
let isMobile = $state(browser ? window.innerWidth < 1024 : false);
|
||||
|
||||
// Update on resize
|
||||
if (browser) {
|
||||
window.addEventListener('resize', () => {
|
||||
isMobile = window.innerWidth < 1024;
|
||||
// Auto-close sidebar on resize to mobile
|
||||
if (isMobile && sidebarOpen) {
|
||||
sidebarOpen = false;
|
||||
}
|
||||
});
|
||||
|
||||
// Keyboard shortcuts
|
||||
|
|
@ -53,14 +49,39 @@
|
|||
});
|
||||
}
|
||||
|
||||
function toggleSidebar() {
|
||||
sidebarOpen = !sidebarOpen;
|
||||
}
|
||||
// Handle last room restore - only on desktop
|
||||
onMount(() => {
|
||||
if (browser && !isMobile && matrixStore.isReady) {
|
||||
const lastRoomId = localStorage.getItem('matrix_last_room');
|
||||
if (lastRoomId) {
|
||||
const roomExists = matrixStore.rooms.some((r) => r.id === lastRoomId);
|
||||
if (roomExists) {
|
||||
matrixStore.selectRoom(lastRoomId);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function selectRoomAndCloseSidebar(roomId: string) {
|
||||
matrixStore.selectRoom(roomId);
|
||||
// Also watch for sync state changes to restore room on desktop
|
||||
$effect(() => {
|
||||
if (browser && !isMobile && matrixStore.isReady && !matrixStore.currentRoomId) {
|
||||
const lastRoomId = localStorage.getItem('matrix_last_room');
|
||||
if (lastRoomId) {
|
||||
const roomExists = matrixStore.rooms.some((r) => r.id === lastRoomId);
|
||||
if (roomExists) {
|
||||
matrixStore.selectRoom(lastRoomId);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function handleSelectRoom(roomId: string) {
|
||||
if (isMobile) {
|
||||
sidebarOpen = false;
|
||||
// On mobile, navigate to the room page
|
||||
goto('/chat/' + encodeURIComponent(roomId));
|
||||
} else {
|
||||
// On desktop, just select the room (stay on same page)
|
||||
matrixStore.selectRoom(roomId);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -80,7 +101,11 @@
|
|||
}
|
||||
|
||||
function handleRoomCreated(roomId: string) {
|
||||
matrixStore.selectRoom(roomId);
|
||||
if (isMobile) {
|
||||
goto('/chat/' + encodeURIComponent(roomId));
|
||||
} else {
|
||||
matrixStore.selectRoom(roomId);
|
||||
}
|
||||
}
|
||||
|
||||
// Call handlers
|
||||
|
|
@ -109,147 +134,162 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<div class="chat-layout flex h-full min-h-0 overflow-hidden bg-background relative">
|
||||
<!-- Mobile Sidebar Backdrop -->
|
||||
{#if sidebarOpen && isMobile}
|
||||
<button
|
||||
class="fixed inset-0 z-40 bg-black/50 backdrop-blur-sm lg:hidden"
|
||||
onclick={() => (sidebarOpen = false)}
|
||||
aria-label="Sidebar schließen"
|
||||
></button>
|
||||
{/if}
|
||||
|
||||
<!-- Sidebar -->
|
||||
<aside
|
||||
class="flex flex-col border-r border-black/10 dark:border-white/10 bg-white/95 dark:bg-zinc-900/95 backdrop-blur-xl transition-all duration-300 ease-in-out
|
||||
fixed lg:relative inset-y-0 left-0 z-50 lg:z-auto
|
||||
w-[85vw] max-w-[320px] lg:w-80 lg:max-w-none
|
||||
{sidebarOpen ? 'translate-x-0' : '-translate-x-full lg:translate-x-0'}"
|
||||
>
|
||||
{#if isMobile}
|
||||
<!-- Mobile: Full-screen room list -->
|
||||
<div class="flex flex-col h-full bg-background">
|
||||
<!-- User Info / Status Bar -->
|
||||
<div class="border-b border-black/10 dark:border-white/10 px-4 py-3">
|
||||
<div
|
||||
class="border-b border-black/10 dark:border-white/10 px-4 py-3 bg-white/95 dark:bg-zinc-900/95 backdrop-blur-xl"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<p class="truncate text-sm font-medium">{matrixStore.userId}</p>
|
||||
<div>
|
||||
<h1 class="text-xl font-bold text-foreground">Manalink</h1>
|
||||
<p class="flex items-center gap-1.5 text-xs text-muted-foreground mt-0.5">
|
||||
<span class="h-2 w-2 rounded-full bg-green-500"></span>
|
||||
{matrixStore.syncState === 'SYNCING' ? 'Verbunden' : matrixStore.syncState}
|
||||
{#if matrixStore.totalUnreadCount > 0}
|
||||
<span
|
||||
class="ml-2 px-1.5 py-0.5 rounded-full bg-gradient-to-r from-blue-500 to-indigo-600 text-white text-xs font-medium"
|
||||
>
|
||||
{matrixStore.totalUnreadCount} ungelesen
|
||||
</span>
|
||||
{/if}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<a
|
||||
href="/settings"
|
||||
class="p-1.5 rounded-lg hover:bg-black/5 dark:hover:bg-white/10 transition-colors"
|
||||
class="p-2 rounded-lg hover:bg-black/5 dark:hover:bg-white/10 transition-colors"
|
||||
title="Einstellungen"
|
||||
>
|
||||
<Gear class="h-4 w-4" />
|
||||
<Gear class="h-5 w-5" />
|
||||
</a>
|
||||
<button
|
||||
class="p-1.5 rounded-lg hover:bg-black/5 dark:hover:bg-white/10 transition-colors"
|
||||
class="p-2 rounded-lg hover:bg-black/5 dark:hover:bg-white/10 transition-colors"
|
||||
title="Neuer Chat"
|
||||
onclick={() => (showCreateRoom = true)}
|
||||
>
|
||||
<Plus class="h-4 w-4" />
|
||||
<Plus class="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="flex items-center gap-1.5 text-xs text-muted-foreground mt-0.5">
|
||||
<span class="h-2 w-2 rounded-full bg-green-500"></span>
|
||||
{matrixStore.syncState === 'SYNCING' ? 'Verbunden' : matrixStore.syncState}
|
||||
{#if matrixStore.totalUnreadCount > 0}
|
||||
<span
|
||||
class="ml-auto px-1.5 py-0.5 rounded-full bg-gradient-to-r from-blue-500 to-indigo-600 text-white text-xs font-medium"
|
||||
>
|
||||
{matrixStore.totalUnreadCount}
|
||||
</span>
|
||||
{/if}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Room List -->
|
||||
<div class="flex-1 min-h-0 overflow-hidden">
|
||||
<RoomList
|
||||
onCreateRoom={() => (showCreateRoom = true)}
|
||||
onSelectRoom={selectRoomAndCloseSidebar}
|
||||
/>
|
||||
<RoomList onCreateRoom={() => (showCreateRoom = true)} onSelectRoom={handleSelectRoom} />
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Chat Area -->
|
||||
<main class="flex flex-1 min-h-0 flex-col overflow-hidden bg-background">
|
||||
{#if matrixStore.currentRoom}
|
||||
<!-- Room Header -->
|
||||
<RoomHeader
|
||||
onMenuClick={toggleSidebar}
|
||||
onInfoClick={() => (showRoomSettings = true)}
|
||||
onSearchClick={() => (showSearch = true)}
|
||||
onVoiceCall={handleVoiceCall}
|
||||
onVideoCall={handleVideoCall}
|
||||
/>
|
||||
|
||||
<!-- Timeline -->
|
||||
<Timeline onReply={handleReply} onEdit={handleEdit} onForward={handleForward} />
|
||||
|
||||
<!-- Message Input -->
|
||||
<MessageInput
|
||||
{replyTo}
|
||||
{editMessage}
|
||||
onCancelReply={() => (replyTo = null)}
|
||||
onCancelEdit={() => (editMessage = null)}
|
||||
/>
|
||||
{:else}
|
||||
<!-- No Room Selected -->
|
||||
<div
|
||||
class="flex flex-1 flex-col items-center justify-center gap-4 p-8 pb-24 text-muted-foreground"
|
||||
>
|
||||
<div class="p-4 rounded-2xl bg-gradient-to-br from-violet-500/20 to-purple-600/20">
|
||||
<ChatCircle class="h-12 w-12 text-violet-500" />
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<h2 class="text-xl font-semibold text-foreground">Willkommen bei Manalink</h2>
|
||||
<p class="mt-2 max-w-sm">
|
||||
Wähle eine Unterhaltung aus der Seitenleiste oder starte einen neuen Chat
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="mt-4 px-6 py-3 rounded-xl bg-gradient-to-r from-blue-500 to-indigo-600 text-white font-medium shadow-lg hover:shadow-xl hover:-translate-y-0.5 transition-all duration-200 flex items-center gap-2"
|
||||
onclick={() => (showCreateRoom = true)}
|
||||
>
|
||||
<Plus class="h-4 w-4" />
|
||||
Neuer Chat
|
||||
</button>
|
||||
|
||||
<!-- Stats -->
|
||||
<div class="mt-8 flex gap-8 text-center">
|
||||
<div class="glass-card rounded-xl px-6 py-4">
|
||||
<p class="text-3xl font-bold text-foreground">{matrixStore.rooms.length}</p>
|
||||
<p class="text-sm text-muted-foreground">Räume</p>
|
||||
</div>
|
||||
<div class="glass-card rounded-xl px-6 py-4">
|
||||
<p class="text-3xl font-bold text-foreground">{matrixStore.totalUnreadCount}</p>
|
||||
<p class="text-sm text-muted-foreground">Ungelesen</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</main>
|
||||
|
||||
<!-- Room Settings Panel -->
|
||||
<RoomSettingsPanel open={showRoomSettings} onClose={() => (showRoomSettings = false)} />
|
||||
|
||||
<!-- FAB to open sidebar (mobile/tablet only, when sidebar is closed) -->
|
||||
{#if !sidebarOpen}
|
||||
<button
|
||||
onclick={toggleSidebar}
|
||||
class="fixed bottom-24 left-4 z-[100] lg:hidden flex items-center justify-center w-14 h-14 rounded-full bg-gradient-to-r from-violet-500 to-purple-600 text-white shadow-lg hover:shadow-xl hover:scale-105 active:scale-95 transition-all duration-200"
|
||||
aria-label="Chats anzeigen"
|
||||
</div>
|
||||
{:else}
|
||||
<!-- Desktop: Side-by-side layout -->
|
||||
<div class="chat-layout flex h-full min-h-0 overflow-hidden bg-background">
|
||||
<!-- Sidebar -->
|
||||
<aside
|
||||
class="flex flex-col border-r border-black/10 dark:border-white/10 bg-white/95 dark:bg-zinc-900/95 backdrop-blur-xl w-80"
|
||||
>
|
||||
<List class="h-6 w-6" weight="bold" />
|
||||
{#if matrixStore.totalUnreadCount > 0}
|
||||
<span
|
||||
class="absolute -top-1 -right-1 min-w-[22px] h-[22px] px-1.5 flex items-center justify-center rounded-full bg-red-500 text-white text-xs font-bold shadow-md"
|
||||
<!-- User Info / Status Bar -->
|
||||
<div class="border-b border-black/10 dark:border-white/10 px-4 py-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<p class="truncate text-sm font-medium">{matrixStore.userId}</p>
|
||||
<div class="flex items-center gap-1">
|
||||
<a
|
||||
href="/settings"
|
||||
class="p-1.5 rounded-lg hover:bg-black/5 dark:hover:bg-white/10 transition-colors"
|
||||
title="Einstellungen"
|
||||
>
|
||||
<Gear class="h-4 w-4" />
|
||||
</a>
|
||||
<button
|
||||
class="p-1.5 rounded-lg hover:bg-black/5 dark:hover:bg-white/10 transition-colors"
|
||||
title="Neuer Chat"
|
||||
onclick={() => (showCreateRoom = true)}
|
||||
>
|
||||
<Plus class="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="flex items-center gap-1.5 text-xs text-muted-foreground mt-0.5">
|
||||
<span class="h-2 w-2 rounded-full bg-green-500"></span>
|
||||
{matrixStore.syncState === 'SYNCING' ? 'Verbunden' : matrixStore.syncState}
|
||||
{#if matrixStore.totalUnreadCount > 0}
|
||||
<span
|
||||
class="ml-auto px-1.5 py-0.5 rounded-full bg-gradient-to-r from-blue-500 to-indigo-600 text-white text-xs font-medium"
|
||||
>
|
||||
{matrixStore.totalUnreadCount}
|
||||
</span>
|
||||
{/if}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Room List -->
|
||||
<div class="flex-1 min-h-0 overflow-hidden">
|
||||
<RoomList onCreateRoom={() => (showCreateRoom = true)} onSelectRoom={handleSelectRoom} />
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Chat Area -->
|
||||
<main class="flex flex-1 min-h-0 flex-col overflow-hidden bg-background">
|
||||
{#if matrixStore.currentRoom}
|
||||
<!-- Room Header -->
|
||||
<RoomHeader
|
||||
onInfoClick={() => (showRoomSettings = true)}
|
||||
onSearchClick={() => (showSearch = true)}
|
||||
onVoiceCall={handleVoiceCall}
|
||||
onVideoCall={handleVideoCall}
|
||||
/>
|
||||
|
||||
<!-- Timeline -->
|
||||
<Timeline onReply={handleReply} onEdit={handleEdit} onForward={handleForward} />
|
||||
|
||||
<!-- Message Input -->
|
||||
<MessageInput
|
||||
{replyTo}
|
||||
{editMessage}
|
||||
onCancelReply={() => (replyTo = null)}
|
||||
onCancelEdit={() => (editMessage = null)}
|
||||
/>
|
||||
{:else}
|
||||
<!-- No Room Selected -->
|
||||
<div
|
||||
class="flex flex-1 flex-col items-center justify-center gap-4 p-8 pb-24 text-muted-foreground"
|
||||
>
|
||||
{matrixStore.totalUnreadCount > 99 ? '99+' : matrixStore.totalUnreadCount}
|
||||
</span>
|
||||
<div class="p-4 rounded-2xl bg-gradient-to-br from-violet-500/20 to-purple-600/20">
|
||||
<ChatCircle class="h-12 w-12 text-violet-500" />
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<h2 class="text-xl font-semibold text-foreground">Willkommen bei Manalink</h2>
|
||||
<p class="mt-2 max-w-sm">
|
||||
Wähle eine Unterhaltung aus der Seitenleiste oder starte einen neuen Chat
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="mt-4 px-6 py-3 rounded-xl bg-gradient-to-r from-blue-500 to-indigo-600 text-white font-medium shadow-lg hover:shadow-xl hover:-translate-y-0.5 transition-all duration-200 flex items-center gap-2"
|
||||
onclick={() => (showCreateRoom = true)}
|
||||
>
|
||||
<Plus class="h-4 w-4" />
|
||||
Neuer Chat
|
||||
</button>
|
||||
|
||||
<!-- Stats -->
|
||||
<div class="mt-8 flex gap-8 text-center">
|
||||
<div class="glass-card rounded-xl px-6 py-4">
|
||||
<p class="text-3xl font-bold text-foreground">{matrixStore.rooms.length}</p>
|
||||
<p class="text-sm text-muted-foreground">Räume</p>
|
||||
</div>
|
||||
<div class="glass-card rounded-xl px-6 py-4">
|
||||
<p class="text-3xl font-bold text-foreground">{matrixStore.totalUnreadCount}</p>
|
||||
<p class="text-sm text-muted-foreground">Ungelesen</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Room Settings Panel -->
|
||||
<RoomSettingsPanel open={showRoomSettings} onClose={() => (showRoomSettings = false)} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Create Room Dialog -->
|
||||
<CreateRoomDialog
|
||||
|
|
|
|||
|
|
@ -1,26 +1,176 @@
|
|||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { matrixStore } from '$lib/matrix';
|
||||
import { matrixStore, type SimpleMessage } from '$lib/matrix';
|
||||
import { RoomHeader, Timeline, MessageInput } from '$lib/components/chat';
|
||||
import RoomSettingsPanel from '$lib/components/chat/RoomSettingsPanel.svelte';
|
||||
import SearchDialog from '$lib/components/chat/SearchDialog.svelte';
|
||||
import ForwardMessageDialog from '$lib/components/chat/ForwardMessageDialog.svelte';
|
||||
import { CallView, IncomingCallDialog } from '$lib/components/call';
|
||||
import { goto } from '$app/navigation';
|
||||
import { browser } from '$app/environment';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
// Get roomId from URL and select that room
|
||||
// Call state
|
||||
let activeCall = $derived(matrixStore.activeCall);
|
||||
let incomingCall = $derived(matrixStore.incomingCall);
|
||||
|
||||
let showRoomSettings = $state(false);
|
||||
let showSearch = $state(false);
|
||||
let showForward = $state(false);
|
||||
|
||||
// Reply/Edit/Forward state
|
||||
let replyTo = $state<SimpleMessage | null>(null);
|
||||
let editMessage = $state<SimpleMessage | null>(null);
|
||||
let forwardMessage = $state<SimpleMessage | null>(null);
|
||||
|
||||
// Check if mobile
|
||||
let isMobile = $state(browser ? window.innerWidth < 1024 : false);
|
||||
|
||||
// Update on resize
|
||||
if (browser) {
|
||||
window.addEventListener('resize', () => {
|
||||
const wasMobile = isMobile;
|
||||
isMobile = window.innerWidth < 1024;
|
||||
|
||||
// If we switched from mobile to desktop, redirect to /chat
|
||||
if (wasMobile && !isMobile) {
|
||||
goto('/chat');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// On mount: Select the room and handle desktop redirect
|
||||
onMount(() => {
|
||||
const roomId = $page.params.roomId;
|
||||
if (roomId) {
|
||||
// URL decode the room ID (Matrix room IDs contain special chars)
|
||||
const decodedRoomId = decodeURIComponent(roomId);
|
||||
|
||||
// On desktop, redirect to /chat and select the room there
|
||||
if (!isMobile) {
|
||||
matrixStore.selectRoom(decodedRoomId);
|
||||
goto('/chat');
|
||||
return;
|
||||
}
|
||||
|
||||
// On mobile, select the room and stay on this page
|
||||
matrixStore.selectRoom(decodedRoomId);
|
||||
}
|
||||
});
|
||||
|
||||
// Redirect to main chat page - the room selection is handled there
|
||||
// Handle URL changes (e.g., navigating between rooms)
|
||||
$effect(() => {
|
||||
goto('/chat');
|
||||
const roomId = $page.params.roomId;
|
||||
if (roomId && isMobile) {
|
||||
const decodedRoomId = decodeURIComponent(roomId);
|
||||
if (matrixStore.currentRoomId !== decodedRoomId) {
|
||||
matrixStore.selectRoom(decodedRoomId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function handleBack() {
|
||||
goto('/chat');
|
||||
}
|
||||
|
||||
function handleReply(message: SimpleMessage) {
|
||||
editMessage = null;
|
||||
replyTo = message;
|
||||
}
|
||||
|
||||
function handleEdit(message: SimpleMessage) {
|
||||
replyTo = null;
|
||||
editMessage = message;
|
||||
}
|
||||
|
||||
function handleForward(message: SimpleMessage) {
|
||||
forwardMessage = message;
|
||||
showForward = true;
|
||||
}
|
||||
|
||||
// Call handlers
|
||||
async function handleVoiceCall() {
|
||||
if (matrixStore.currentRoom) {
|
||||
await matrixStore.placeVoiceCall(matrixStore.currentRoom.roomId);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleVideoCall() {
|
||||
if (matrixStore.currentRoom) {
|
||||
await matrixStore.placeVideoCall(matrixStore.currentRoom.roomId);
|
||||
}
|
||||
}
|
||||
|
||||
function handleCallHangup() {
|
||||
// Call ended - UI will update automatically
|
||||
}
|
||||
|
||||
function handleCallAnswer() {
|
||||
// Call answered - UI will update automatically
|
||||
}
|
||||
|
||||
function handleCallReject() {
|
||||
// Call rejected - UI will update automatically
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- This page just handles deep-linking to specific rooms -->
|
||||
<div class="flex h-screen items-center justify-center">
|
||||
<p class="text-base-content/60">Loading room...</p>
|
||||
<!-- Full-screen chat view for mobile -->
|
||||
<div class="flex flex-col h-full bg-background">
|
||||
{#if matrixStore.currentRoom}
|
||||
<!-- Room Header with back button -->
|
||||
<RoomHeader
|
||||
showBackButton={true}
|
||||
onBackClick={handleBack}
|
||||
onInfoClick={() => (showRoomSettings = true)}
|
||||
onSearchClick={() => (showSearch = true)}
|
||||
onVoiceCall={handleVoiceCall}
|
||||
onVideoCall={handleVideoCall}
|
||||
/>
|
||||
|
||||
<!-- Timeline -->
|
||||
<Timeline onReply={handleReply} onEdit={handleEdit} onForward={handleForward} />
|
||||
|
||||
<!-- Message Input -->
|
||||
<MessageInput
|
||||
{replyTo}
|
||||
{editMessage}
|
||||
onCancelReply={() => (replyTo = null)}
|
||||
onCancelEdit={() => (editMessage = null)}
|
||||
/>
|
||||
{:else}
|
||||
<!-- Loading state -->
|
||||
<div class="flex flex-1 items-center justify-center">
|
||||
<div class="text-center">
|
||||
<div
|
||||
class="animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full mx-auto mb-4"
|
||||
></div>
|
||||
<p class="text-muted-foreground">Lade Raum...</p>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Room Settings Panel -->
|
||||
<RoomSettingsPanel open={showRoomSettings} onClose={() => (showRoomSettings = false)} />
|
||||
|
||||
<!-- Search Dialog -->
|
||||
<SearchDialog open={showSearch} onClose={() => (showSearch = false)} />
|
||||
|
||||
<!-- Active Call View -->
|
||||
{#if activeCall}
|
||||
<CallView call={activeCall} onHangup={handleCallHangup} />
|
||||
{/if}
|
||||
|
||||
<!-- Incoming Call Dialog -->
|
||||
{#if incomingCall && !activeCall}
|
||||
<IncomingCallDialog call={incomingCall} onAnswer={handleCallAnswer} onReject={handleCallReject} />
|
||||
{/if}
|
||||
|
||||
<!-- Forward Message Dialog -->
|
||||
<ForwardMessageDialog
|
||||
open={showForward}
|
||||
message={forwardMessage}
|
||||
onClose={() => {
|
||||
showForward = false;
|
||||
forwardMessage = null;
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue