Files
Toju/toju-app/src/app/features/direct-call/private-call.component.html

241 lines
9.2 KiB
HTML

@if (isMobile()) {
<swiper-container
class="block h-full min-h-0 w-full"
direction="vertical"
slides-per-view="1"
space-between="0"
initial-slide="1"
threshold="10"
resistance-ratio="0"
(swiperslidechange)="onMobileCallSlideChange($event)"
>
<swiper-slide class="block h-full w-full" />
<swiper-slide class="block h-full w-full">
<ng-container *ngTemplateOutlet="privateCallSurface" />
</swiper-slide>
</swiper-container>
} @else {
<ng-container *ngTemplateOutlet="privateCallSurface" />
}
<ng-template #privateCallSurface>
<section
class="grid h-full min-h-0 bg-background lg:grid-cols-[minmax(0,1fr)_var(--private-call-chat-width)]"
[style.--private-call-chat-width]="chatWidthPx() + 'px'"
>
<main class="flex min-h-0 min-w-0 flex-col overflow-hidden bg-[radial-gradient(circle_at_top,rgba(16,185,129,0.10),transparent_34rem)]">
<header class="flex min-h-16 shrink-0 items-center justify-between gap-3 border-b border-border/70 bg-background/80 px-3 backdrop-blur sm:px-5">
<div class="flex min-w-0 items-center gap-3">
<div class="grid h-10 w-10 shrink-0 place-items-center rounded-2xl bg-emerald-500/10 text-emerald-500">
<ng-icon
name="lucidePhone"
class="h-5 w-5"
/>
</div>
<div class="min-w-0">
<h1 class="truncate text-base font-semibold text-foreground">Private Call</h1>
<p class="truncate text-xs text-muted-foreground">
@if (session()) {
{{ participantUsers().length }} participants
} @else {
Call not found
}
</p>
</div>
</div>
@if (session()) {
<div class="flex items-center gap-2">
@if (isMobile()) {
<button
type="button"
class="grid h-10 w-10 place-items-center rounded-full bg-secondary text-foreground transition-colors hover:bg-secondary/80"
(click)="minimizeCall()"
aria-label="Minimize call"
title="Minimize call"
>
<ng-icon
name="lucideX"
class="h-5 w-5"
/>
</button>
}
<select
class="hidden h-9 max-w-44 rounded-md border border-border bg-secondary px-2 text-sm text-foreground sm:block"
[ngModel]="inviteUserId()"
(ngModelChange)="inviteUserId.set($event)"
aria-label="Add user to call"
>
<option value="">Add user</option>
@for (user of inviteCandidates(); track userKey(user)) {
<option [value]="userKey(user)">{{ user.displayName }}</option>
}
</select>
<button
type="button"
class="hidden h-9 w-9 place-items-center rounded-md bg-secondary text-foreground transition-colors hover:bg-secondary/80 disabled:opacity-50 sm:grid"
[disabled]="!inviteUserId()"
(click)="inviteSelectedUser()"
aria-label="Add user"
title="Add user"
>
<ng-icon
name="lucideUserPlus"
class="h-4 w-4"
/>
</button>
</div>
}
</header>
@if (session()) {
<div class="flex min-h-0 flex-1 flex-col overflow-hidden px-3 py-3 sm:px-5 sm:py-4">
<div class="relative min-h-0 flex-1 overflow-hidden">
@if (activeShares().length > 0) {
@if (focusedShare()) {
@if (hasMultipleShares()) {
<div class="absolute right-3 top-3 z-10 sm:right-4 sm:top-4">
<button
type="button"
data-testid="private-call-show-all-streams"
class="inline-flex h-10 items-center gap-2 rounded-full border border-white/10 bg-black/45 px-3 text-xs font-medium text-white/80 backdrop-blur transition hover:bg-black/65 hover:text-white"
title="Show all streams"
(click)="showAllStreams()"
>
<ng-icon
name="lucideUsers"
class="h-3.5 w-3.5"
/>
All streams
</button>
</div>
}
<app-voice-workspace-stream-tile
[item]="focusedShare()!"
[featured]="true"
[focused]="true"
data-testid="private-call-focused-stream"
[immersive]="true"
(focusRequested)="focusShare($event)"
/>
} @else if (hasMultipleShares()) {
<div
class="grid h-full min-h-0 auto-rows-[minmax(12rem,1fr)] grid-cols-1 gap-3 p-3 sm:grid-cols-2 sm:gap-4 sm:p-4"
[ngClass]="{ '2xl:grid-cols-3': activeShares().length > 2 }"
data-testid="private-call-stream-grid"
>
@for (share of activeShares(); track share.id) {
<div class="min-h-0 overflow-hidden rounded-2xl bg-black">
<app-voice-workspace-stream-tile
[item]="share"
[focused]="false"
(focusRequested)="focusShare($event)"
/>
</div>
}
</div>
}
} @else {
<div class="flex h-full min-h-0 items-center justify-center p-1 sm:p-5">
<div
class="grid w-full max-w-7xl grid-cols-[repeat(auto-fit,minmax(min(11rem,100%),1fr))] items-stretch justify-center gap-3 sm:grid-cols-[repeat(auto-fit,minmax(min(16rem,100%),1fr))] sm:gap-5 lg:gap-7"
>
@for (user of participantUsers(); track trackUserKey($index, user)) {
<app-private-call-participant-card
[user]="user"
[connected]="isParticipantConnected(user)"
[speaking]="isSpeaking(user)"
[issueLabel]="participantIssueLabel(user)"
/>
}
</div>
</div>
}
</div>
@if (activeShares().length > 0) {
<div class="shrink-0 pt-4">
<div class="flex w-full items-stretch gap-3 overflow-x-auto pb-1">
@for (user of participantUsers(); track trackUserKey($index, user)) {
<app-private-call-participant-card
[user]="user"
[connected]="isParticipantConnected(user)"
[speaking]="isSpeaking(user)"
[issueLabel]="participantIssueLabel(user)"
[compact]="true"
/>
}
@if (hasMultipleShares()) {
@for (share of focusedShare() ? thumbnailShares() : activeShares(); track share.id) {
<article
class="flex min-h-[8.75rem] w-[11rem] shrink-0 flex-col overflow-hidden rounded-2xl border border-border/80 bg-black shadow-sm sm:w-[12.5rem]"
>
<div class="min-h-0 flex-1">
<app-voice-workspace-stream-tile
[item]="share"
[mini]="true"
[focused]="false"
(focusRequested)="focusShare($event)"
/>
</div>
<div class="shrink-0 bg-black/80 px-3 py-2 text-xs font-semibold text-white/75">
{{ streamLabel(share) }}
</div>
</article>
}
}
</div>
</div>
}
<div class="shrink-0 pt-3">
<app-private-call-controls
class="mx-auto block w-full max-w-5xl"
[connected]="isConnected()"
[muted]="isMuted()"
[cameraEnabled]="isCameraEnabled()"
[screenSharing]="isScreenSharing()"
(joinRequested)="join()"
(muteToggled)="toggleMute()"
(cameraToggled)="toggleCamera()"
(screenShareToggled)="toggleScreenShare()"
(leaveRequested)="leave()"
/>
</div>
</div>
} @else {
<div class="flex flex-1 items-center justify-center px-6 text-sm text-muted-foreground">No active call for this route.</div>
}
</main>
<aside class="relative hidden min-h-0 border-l border-border bg-card lg:block">
<div
class="group absolute inset-y-0 left-0 z-10 w-3 -translate-x-1/2 cursor-col-resize bg-transparent"
role="separator"
aria-orientation="vertical"
title="Resize chat"
data-testid="private-call-chat-resizer"
(mousedown)="startChatResize($event)"
>
<div class="mx-auto h-full w-px bg-border transition group-hover:bg-primary"></div>
</div>
<app-dm-chat
[conversationId]="session()?.conversationId ?? null"
[showCallButton]="false"
/>
</aside>
</section>
</ng-template>
@if (showScreenShareQualityDialog()) {
<app-screen-share-quality-dialog
[selectedQuality]="screenShareQuality()"
[includeSystemAudio]="includeSystemAudio()"
(cancelled)="onScreenShareQualityCancelled()"
(confirmed)="onScreenShareQualityConfirmed($event)"
/>
}