Imrpove chat with gifs, videos, music player, redesigns and improved filesharing errors

This commit is contained in:
2026-03-06 04:47:07 +01:00
parent 2d84fbd91a
commit fe2347b54e
65 changed files with 3593 additions and 1030 deletions

View File

@@ -0,0 +1,65 @@
<div
class="fixed inset-0 z-40 bg-black/30"
(click)="cancelled.emit(undefined)"
(keydown.enter)="cancelled.emit(undefined)"
(keydown.space)="cancelled.emit(undefined)"
role="button"
tabindex="0"
aria-label="Close dialog"
></div>
<div
class="fixed left-1/2 top-1/2 z-50 w-[360px] max-w-[calc(100vw-2rem)] -translate-x-1/2 -translate-y-1/2 rounded-lg border border-border bg-card shadow-lg"
>
<div class="space-y-3 p-4">
<h4 class="font-semibold text-foreground">Leave Server?</h4>
<div class="space-y-3 text-sm text-muted-foreground">
<p>
Leaving will remove
<span class="font-medium text-foreground">{{ room().name }}</span>
from your My Servers list.
</p>
@if (isOwner()) {
<div class="space-y-2 rounded-md border border-border/80 bg-secondary/20 p-3">
<p class="text-foreground">You are the current owner of this server.</p>
<p>You can optionally promote another member before leaving. If you skip this step, the server will continue without an owner.</p>
@if (ownerCandidates().length > 0) {
<label class="block space-y-1">
<span class="text-xs font-medium uppercase tracking-wide text-muted-foreground"> New owner </span>
<select
class="w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-primary"
[ngModel]="selectedOwnerKey()"
(ngModelChange)="selectedOwnerKey.set($event || '')"
>
<option value="">Skip owner transfer</option>
@for (member of ownerCandidates(); track roomMemberKey(member)) {
<option [value]="roomMemberKey(member)">{{ member.displayName }} - {{ roleLabel(member) }}</option>
}
</select>
</label>
} @else {
<p>No other known members are available to promote right now.</p>
}
</div>
}
</div>
</div>
<div class="flex gap-2 border-t border-border p-3">
<button
(click)="cancelled.emit(undefined)"
type="button"
class="flex-1 rounded-lg bg-secondary px-3 py-2 text-sm text-foreground transition-colors hover:bg-secondary/80"
>
Cancel
</button>
<button
(click)="confirmLeave()"
type="button"
class="flex-1 rounded-lg bg-destructive px-3 py-2 text-sm text-destructive-foreground transition-colors hover:bg-destructive/90"
>
Leave Server
</button>
</div>
</div>

View File

@@ -0,0 +1,91 @@
import {
Component,
HostListener,
computed,
effect,
input,
output,
signal
} from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import {
Room,
RoomMember,
User
} from '../../../core/models';
export interface LeaveServerDialogResult {
nextOwnerKey?: string;
}
@Component({
selector: 'app-leave-server-dialog',
standalone: true,
imports: [CommonModule, FormsModule],
templateUrl: './leave-server-dialog.component.html'
})
export class LeaveServerDialogComponent {
room = input.required<Room>();
currentUser = input<User | null>(null);
confirmed = output<LeaveServerDialogResult>();
cancelled = output<undefined>();
selectedOwnerKey = signal('');
isOwner = computed(() => {
const room = this.room();
const user = this.currentUser();
if (!room || !user)
return false;
return room.hostId === user.id || room.hostId === user.oderId;
});
ownerCandidates = computed(() => {
const room = this.room();
const user = this.currentUser();
const userIds = new Set([user?.id, user?.oderId].filter((value): value is string => !!value));
return (room.members ?? []).filter((member) => !userIds.has(member.id) && !userIds.has(member.oderId || ''));
});
constructor() {
effect(() => {
this.room();
this.currentUser();
this.selectedOwnerKey.set('');
});
}
@HostListener('document:keydown.escape')
onEscape(): void {
this.cancelled.emit(undefined);
}
confirmLeave(): void {
this.confirmed.emit(
this.selectedOwnerKey()
? { nextOwnerKey: this.selectedOwnerKey() }
: {}
);
}
roomMemberKey(member: RoomMember): string {
return member.oderId || member.id;
}
roleLabel(member: RoomMember): string {
switch (member.role) {
case 'host':
return 'Owner';
case 'admin':
return 'Admin';
case 'moderator':
return 'Moderator';
default:
return 'Member';
}
}
}