Files
Toju/toju-app/src/app/domains/screen-share/feature/screen-share-workspace/screen-share-stream-tile.component.html
2026-03-29 23:55:24 +02:00

221 lines
8.2 KiB
HTML

<div
#tileRoot
class="group relative flex h-full min-h-0 flex-col overflow-hidden bg-black/85 transition duration-200"
tabindex="0"
role="button"
[attr.aria-label]="mini() ? 'Focus ' + displayName() + ' stream' : 'Open ' + displayName() + ' stream in widescreen mode'"
[attr.title]="canToggleFullscreen() ? (isFullscreen() ? 'Double-click to exit fullscreen' : 'Double-click for fullscreen') : null"
[ngClass]="{
'ring-2 ring-primary/70': focused() && !immersive() && !mini() && !isFullscreen(),
'min-h-[24rem] rounded-[1.75rem] border border-white/10 shadow-2xl': featured() && !compact() && !immersive() && !mini() && !isFullscreen(),
'rounded-[1.75rem] border border-white/10 shadow-2xl': !featured() && !compact() && !immersive() && !mini() && !isFullscreen(),
'rounded-2xl border border-white/10 shadow-2xl': compact() && !immersive() && !mini() && !isFullscreen(),
'rounded-2xl border border-white/10 shadow-xl': mini() && !isFullscreen(),
'shadow-none': immersive() || isFullscreen()
}"
(click)="requestFocus()"
(dblclick)="onTileDoubleClick($event)"
(mousemove)="onTilePointerMove()"
(keydown.enter)="requestFocus()"
(keydown.space)="requestFocus(); $event.preventDefault()"
>
<video
#streamVideo
autoplay
playsinline
class="absolute inset-0 h-full w-full bg-black object-contain"
></video>
<div class="pointer-events-none absolute inset-0 bg-gradient-to-b from-black/70 via-black/10 to-black/80"></div>
@if (isFullscreen()) {
<div
class="pointer-events-none absolute inset-x-3 top-3 z-20 transition-all duration-300 sm:inset-x-4 sm:top-4"
[class.opacity-0]="!showFullscreenHeader()"
[class.translate-y-[-12px]]="!showFullscreenHeader()"
>
<div
class="pointer-events-auto flex items-center gap-3 rounded-2xl border border-white/10 bg-black/45 px-4 py-3 backdrop-blur-lg"
[class.pointer-events-none]="!showFullscreenHeader()"
>
<div class="flex min-w-0 flex-1 items-center gap-3">
<app-user-avatar
[name]="displayName()"
[avatarUrl]="item().user.avatarUrl"
size="xs"
/>
<div class="min-w-0 flex-1">
<div class="flex flex-wrap items-center gap-2">
<p class="truncate text-sm font-semibold text-white sm:text-base">{{ displayName() }}</p>
<span class="rounded-full bg-primary/10 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-[0.18em] text-primary"> Live </span>
</div>
<p class="mt-1 text-xs text-white/60">
{{ item().isLocal ? 'Local preview in fullscreen' : 'Fullscreen stream view' }}
</p>
</div>
</div>
@if (!item().isLocal) {
<button
type="button"
class="inline-flex h-9 w-9 items-center justify-center rounded-full border border-white/10 bg-black/45 text-white/75 transition hover:bg-black/60 hover:text-white"
[title]="muted() ? 'Unmute stream audio' : 'Mute stream audio'"
(click)="toggleMuted(); $event.stopPropagation()"
>
<ng-icon
[name]="muted() ? 'lucideVolumeX' : 'lucideVolume2'"
class="h-4 w-4"
/>
</button>
}
<button
type="button"
class="inline-flex h-9 w-9 items-center justify-center rounded-full border border-white/10 bg-black/45 text-white/75 transition hover:bg-black/60 hover:text-white"
title="Exit fullscreen"
(click)="exitFullscreen($event)"
>
<ng-icon
name="lucideMinimize"
class="h-4 w-4"
/>
</button>
</div>
</div>
}
@if (mini()) {
<div class="absolute inset-x-0 bottom-0 p-2">
<div class="rounded-xl border border-white/10 bg-black/55 px-2.5 py-2 backdrop-blur-md">
<div class="flex items-center gap-2">
<app-user-avatar
[name]="displayName()"
[avatarUrl]="item().user.avatarUrl"
size="xs"
/>
<div class="min-w-0 flex-1">
<p class="truncate text-xs font-semibold text-white">{{ displayName() }}</p>
<p class="text-[10px] uppercase tracking-[0.16em] text-white/60">Live stream</p>
</div>
</div>
</div>
</div>
} @else if (!immersive()) {
<div
class="absolute left-4 top-4 flex items-center gap-3 bg-black/50 backdrop-blur-md"
[ngClass]="compact() ? 'max-w-[calc(100%-5rem)] rounded-xl px-2.5 py-2' : 'max-w-[calc(100%-8rem)] rounded-full px-3 py-2'"
>
<app-user-avatar
[name]="displayName()"
[avatarUrl]="item().user.avatarUrl"
size="xs"
/>
<div class="min-w-0">
<p
class="truncate font-semibold text-white"
[class.text-xs]="compact()"
[class.text-sm]="!compact()"
>
{{ displayName() }}
</p>
<p
class="flex items-center gap-1 uppercase text-white/65"
[class.text-[10px]]="compact()"
[class.text-[11px]]="!compact()"
[class.tracking-[0.18em]]="compact()"
[class.tracking-[0.24em]]="!compact()"
>
<ng-icon
name="lucideMonitor"
class="h-3 w-3"
/>
Live
</p>
</div>
</div>
<div class="absolute right-4 top-4 flex items-center gap-2 opacity-100 transition md:opacity-0 md:group-hover:opacity-100">
<button
type="button"
class="inline-flex items-center justify-center rounded-full border border-white/15 bg-black/45 text-white/90 backdrop-blur-md transition hover:bg-black/65"
[class.h-8]="compact()"
[class.w-8]="compact()"
[class.h-10]="!compact()"
[class.w-10]="!compact()"
[title]="focused() ? 'Viewing in widescreen' : 'View in widescreen'"
(click)="requestFocus(); $event.stopPropagation()"
>
<ng-icon
name="lucideMaximize"
[class.h-3.5]="compact()"
[class.w-3.5]="compact()"
[class.h-4]="!compact()"
[class.w-4]="!compact()"
/>
</button>
@if (!item().isLocal) {
<button
type="button"
class="inline-flex items-center justify-center rounded-full border border-white/15 bg-black/45 text-white/90 backdrop-blur-md transition hover:bg-black/65"
[class.h-8]="compact()"
[class.w-8]="compact()"
[class.h-10]="!compact()"
[class.w-10]="!compact()"
[title]="muted() ? 'Unmute stream audio' : 'Mute stream audio'"
(click)="toggleMuted(); $event.stopPropagation()"
>
<ng-icon
[name]="muted() ? 'lucideVolumeX' : 'lucideVolume2'"
[class.h-3.5]="compact()"
[class.w-3.5]="compact()"
[class.h-4]="!compact()"
[class.w-4]="!compact()"
/>
</button>
}
</div>
<div class="absolute inset-x-0 bottom-0 p-4">
@if (item().isLocal) {
@if (!compact()) {
<div class="rounded-2xl bg-black/50 px-4 py-3 text-xs text-white/75 backdrop-blur-md">
Your preview stays muted locally to avoid audio feedback.
</div>
}
} @else {
@if (compact()) {
<div class="rounded-xl bg-black/50 px-3 py-2 text-[11px] text-white/80 backdrop-blur-md">
{{ muted() ? 'Muted' : volume() + '% audio' }}
</div>
} @else {
<div class="rounded-2xl bg-black/50 px-4 py-3 backdrop-blur-md">
<div class="mb-2 flex items-center justify-between text-xs text-white/80">
<span class="flex items-center gap-2 font-medium">
<ng-icon
[name]="muted() ? 'lucideVolumeX' : 'lucideVolume2'"
class="h-3.5 w-3.5"
/>
Stream audio
</span>
<span>{{ muted() ? 'Muted' : volume() + '%' }}</span>
</div>
<input
type="range"
min="0"
max="100"
[value]="volume()"
class="w-full accent-primary"
(click)="$event.stopPropagation()"
(input)="updateVolume($event)"
/>
</div>
}
}
</div>
}
</div>