3.0 Beta 2

This commit is contained in:
2026-04-10 21:57:29 +02:00
parent 67918644e0
commit a01b5347d6
65 changed files with 1619 additions and 175 deletions

View File

@@ -1,3 +1,13 @@
<ng-template [(tuiDialog)]="changeLogOpen" [tuiDialogOptions]="{closable: false, dismissible: false, label: 'chat.changeLogDialog.label'|translate}">
<h3>{{"version"|translate}}</h3>
<ul>
<li>{{"chat.changeLogDialog.changeLog.1"|translate}}</li>
<li>{{"chat.changeLogDialog.changeLog.2"|translate}}</li>
</ul>
<button tuiButton iconStart="@tui.check" (click)="localStorage.setItem('changeLogLastRead', environment.version); changeLogOpen.set(false)">{{"ok"|translate}}</button>
</ng-template>
@if (serviceManager.currentSession() == null) {
<main style="width: 100%; height: 100svh; display: flex; justify-content: center; align-items: center;">
<tui-loader size="xl"/>
@@ -10,15 +20,15 @@
<button>
<tui-icon icon="@tui.message-circle"/>
</button>
<button>
<button disabled style="pointer-events: none; opacity: 0.5">
<tui-icon icon="@tui.network"/>
</button>
<button>
<button disabled style="pointer-events: none; opacity: 0.5">
<tui-icon icon="@tui.image"/>
</button>
</tui-segmented>
<button id="bottom_btn" tuiButton appearance="flat">
<button id="bottom_btn" tuiButton appearance="flat" disabled>
<tui-icon icon="@tui.cog"/>
</button>
</aside>

View File

@@ -7,6 +7,7 @@
#chatnav {
display: grid;
grid-template-columns: 70px minmax(0, 1fr);
height: 100svh;
aside {
padding: 15px;
@@ -25,6 +26,12 @@
button {
height: 50px;
tui-icon {
&::before {
color: var(--tui-background-accent-1);
}
}
}
}
@@ -41,11 +48,13 @@
border-radius: 20px 0 0 20px;
margin: 10px 0 10px 10px;
padding: 15px;
overflow-y: scroll;
}
}
#content {
width: 100%;
height: 100svh;
padding: 10px 10px 10px 0;
#content_tint {

View File

@@ -1,13 +1,15 @@
import {Component, inject, OnInit} from '@angular/core';
import {Component, inject, OnInit, signal} from '@angular/core';
import {RouterOutlet} from '@angular/router';
import {TuiSegmented} from '@taiga-ui/kit';
import {TuiAppearance, TuiButton, TuiGroup, TuiIcon, TuiLoader} from '@taiga-ui/core';
import {TuiAppearance, TuiButton, TuiDialog, TuiGroup, TuiIcon, TuiLoader} from '@taiga-ui/core';
import {SessionManager} from '@chatenium/chatenium-sdk/services/sessionManager';
import {LoadStatus, ServiceManager} from '../service-manager';
import {IndexedDB} from '../storage/indexed-db';
import {DmList} from './dm-list/dm-list';
import {JsonPipe} from '@angular/common';
import {WebSocketHandler} from '@chatenium/chatenium-sdk/core/webSocketHandler';
import {TranslatePipe} from '@ngx-translate/core';
import {environment} from '../../environments/environment';
@Component({
selector: 'app-chat',
@@ -20,7 +22,9 @@ import {WebSocketHandler} from '@chatenium/chatenium-sdk/core/webSocketHandler';
DmList,
JsonPipe,
TuiAppearance,
TuiGroup
TuiGroup,
TuiDialog,
TranslatePipe
],
templateUrl: './chat.html',
styleUrl: './chat.scss',
@@ -29,13 +33,24 @@ export class Chat implements OnInit {
serviceManager = inject(ServiceManager)
indexedDb = inject(IndexedDB)
changeLogOpen = signal(false)
async ngOnInit() {
this.indexedDb.openDatabase().then(async () => {
const session = await this.serviceManager.sessionManager.loadPreferredSession()
this.serviceManager.currentSession.set(session)
await WebSocketHandler.getInstance().connect(session.userData.userid, session.token)
})
setTimeout(() => {
const latestRead = localStorage.getItem("changeLogLastRead")
if (latestRead != environment.version) {
this.changeLogOpen.set(true)
}
}, 50)
}
protected readonly LoadStatus = LoadStatus;
protected readonly localStorage = localStorage;
protected readonly environment = environment;
}

View File

@@ -13,13 +13,13 @@
</div>
<div class="items-right">
<button tuiButton appearance="flat">
<button tuiButton appearance="flat" disabled>
<tui-icon icon="@tui.phone"/>
</button>
</div>
</navbar>
<messages [messages]="store.messages()"/>
<messages [messageBoxViewModel]="store.messageBox" [messages]="store.messages()" id="scrollContainer" (onDelete)="deleteMessage($event)"/>
<message-box [viewModel]="store.messageBox"/>
}

View File

@@ -40,8 +40,31 @@ export class Dm implements OnInit {
}
async sendMessage(message: string, files: FileDataWithPreview[] | null) {
if (!files && message.trim() == "") return
const session = this.serviceManager.currentSession();
if (session != null) {
const editedMessage = this.store.messageBox.editingMessage()
if (editedMessage) {
const storedMsg = this.store.messages().find(m => m.msgid == editedMessage.messageId)
const originalMessage: Message = JSON.parse(JSON.stringify(storedMsg))
if (storedMsg) {
storedMsg.message = message
}
try {
await this.store.service.editMessage(editedMessage.messageId, message)
} catch (e) {
if (storedMsg) {
storedMsg.message = originalMessage.message
}
}
this.store.messageBox.editingMessage.set(null)
this.store.messageBox.message.set("")
return
}
let attachments: Attachment[] = []
files?.forEach(file => {
const extraMetaData: Record<string, string> = {}
@@ -74,12 +97,40 @@ export class Dm implements OnInit {
forwardedFromName: ""
}])
this.scrollToBottom("smooth")
await this.store.service.sendMessage("", message, null, null, files, <FileUploadProgressListener>{
fileProgressUpdate: (tempMsgId, fileId, allChunks, chunksDone) => {
this.uploadProgressUpdate(tempMsgId, fileId, allChunks, chunksDone)
}
})
}
this.store.messageBox.message.set("")
}
async deleteMessage(messageId: string) {
const i = this.store.messages().findIndex(m => m.msgid == messageId)
if (i != -1) {
const originalMessage: Message = JSON.parse(JSON.stringify(this.store.messages()[i]))
this.store.messages().splice(i, 1)
try {
await this.store.service.deleteMessages([messageId])
} catch (e) {
this.store.messages().splice(i, 0, originalMessage)
}
}
}
scrollToBottom(anim: 'instant' | 'smooth'): void {
setTimeout(() => {
const scrollContainer = <HTMLDivElement>document.querySelector("#scrollContainer")
scrollContainer.scroll({
top: scrollContainer.scrollHeight,
left: 0,
behavior: anim
});
}, 0)
}
uploadProgressUpdate(tempMsgId: string, fileId: string, allChunks: number, chunksDone: number) {
@@ -95,6 +146,7 @@ export class Dm implements OnInit {
switch (action) {
case "newMessage":
targetStore.messages.update(messages => [...messages, data]);
this.scrollToBottom("smooth")
break;
}
}
@@ -135,6 +187,7 @@ export class Dm implements OnInit {
const currentStore = this.serviceManager.dmServices()[chatid];
const history = await currentStore.service.get();
currentStore.messages.set(history);
this.scrollToBottom("instant")
await currentStore.service.joinWebSocketRoom();
}

View File

@@ -12,4 +12,5 @@ export class MessageBoxViewModel {
message = signal<string>("")
files = signal<FileDataWithPreview[]>([])
dialogOpen = signal<boolean>(false)
editingMessage = signal<{messageId: string, message: string} | null >(null)
}

View File

@@ -5,7 +5,7 @@
</div>
<span>{{ "chat.elements.messageBox.uplDrag.upload"|translate }}</span>
</div>
<div class="method">
<div class="method" style="opacity: 0.5">
<div class="icon-holder">
<tui-icon icon="@tui.cloud-sync"/>
</div>
@@ -14,13 +14,25 @@
</div>
<ng-template [(tuiDialog)]="viewModel().dialogOpen" [tuiDialogOptions]="{label: 'chat.elements.messageBox.fileUploadDialog.label'|translate}">
<masonry [maxColSize]="2" style="overflow-y: scroll; height: 500px">
@if (filterPictureVideo(viewModel().files()).length != 0) {
<masonry [maxColSize]="2" style="overflow-y: scroll; height: 200px">
@for (file of viewModel().files(); track file) {
@if (file.type == "image") {
<img [src]="file.blob" style="width: 100%; height: 100%; object-fit: fill;"/>
}
}
</masonry>
}
<div tuiGroup orientation="vertical" style="width: 100%">
@for (file of viewModel().files(); track file) {
@if (file.type == "image") {
<img [src]="file.blob" style="width: 100%; height: 100%; object-fit: fill;"/>
@if (file.type != "image") {
<div style="background: var(--tui-background-neutral-1); height: 50px; display: flex; align-items: center; padding: 0 10px">
<span>{{ file.name }}</span>
</div>
}
}
</masonry>
</div>
<div style="margin-top: 10px; display: flex; gap: 10px; width: 100%">
<tui-textfield style="width: 100%">
@@ -37,25 +49,45 @@
</div>
</ng-template>
<div id="message-box" [style]="'border-radius:'+messageBoxRadius+'px;'">
<div class="items-left">
<button tuiButton appearance="flat" (click)="uplInput.click()">
<tui-icon icon="@tui.file-up"/>
<input #uplInput type="file" (change)="handleFileInput($event)" multiple hidden/>
</button>
<main>
<div id="message-box-extension" tuiAppearance="floating" [class.shown]="viewModel().editingMessage()">
<div id="content">
@if (viewModel().editingMessage()) {
<tui-icon icon="@tui.pencil"></tui-icon>
<span>{{"chat.elements.messageBox.editMessageLabel"|translate}}</span>
@if (viewModel().editingMessage()!.message == "") {
<tui-icon icon="@tui.paperclip"></tui-icon>
<span>{{"chat.elements.messageBox.attachments"|translate}}</span>
} @else {
{{viewModel().editingMessage()!.message}}
}
}
</div>
<button tuiButton appearance="flat">
<tui-icon icon="@tui.cloud-sync"/>
<button id="close" tuiButton appearance="flat" (click)="viewModel().editingMessage.set(null); viewModel().message.set('');">
<tui-icon icon="@tui.x"/>
</button>
</div>
<div class="items-middle">
<textarea [style]="'height:'+textareaHeight+'px;'" #message (input)="onTextAreaInput(message)" [(ngModel)]="viewModel().message"></textarea>
<span class="placeholder"
[class.hidden]="message.value != ''">{{ "chat.elements.messageBox.placeholder"|translate }}</span>
<div id="message-box" [style]="'border-radius:'+messageBoxRadius+'px;'">
<div class="items-left">
<button tuiButton appearance="flat" (click)="uplInput.click()">
<tui-icon icon="@tui.file-up"/>
<input #uplInput type="file" (change)="handleFileInput($event)" multiple hidden/>
</button>
<button tuiButton appearance="flat" disabled>
<tui-icon icon="@tui.cloud-sync"/>
</button>
</div>
<div class="items-middle">
<textarea (keydown.enter)="handleEnterKeydown($event); viewModel().onMessageSend(message.value, null)" [style]="'height:'+textareaHeight+'px;'" #message (input)="onTextAreaInput(message)" [(ngModel)]="viewModel().message"></textarea>
<span class="placeholder"
[class.hidden]="message.value != ''">{{ "chat.elements.messageBox.placeholder"|translate }}</span>
</div>
<div class="items-right">
<button [disabled]="message.value.trim() == ''" tuiButton appearance="flat" (click)="viewModel().onMessageSend(message.value, null)">
<tui-icon icon="@tui.send"/>
</button>
</div>
</div>
<div class="items-right">
<button tuiButton appearance="flat" (click)="viewModel().onMessageSend(message.value, null)">
<tui-icon icon="@tui.send"/>
</button>
</div>
</div>
</main>

View File

@@ -48,72 +48,115 @@
}
}
#message-box {
transition: all 0.2s ease-in-out;
width: 60%;
background: var(--tui-background-base-alt);
min-height: 75px;
max-height: 200px;
border: 2px solid var(--tui-border-normal);
display: grid;
grid-template-columns: 85px 1fr 100px;
main {
width: 100%;
display: flex;
align-items: center;
padding: 0 10px;
flex-direction: column;
gap: 5px;
.items-left, .items-middle, .items-right {
display: flex;
align-items: center;
}
#message-box-extension {
transition: all 0.2s ease-in-out;
width: 50%;
border-radius: 20px;
background: var(--tui-background-base-alt);
border: 2px solid var(--tui-border-normal);
transform: translateY(50px);
display: grid;
grid-template-columns: 1fr 40px;
justify-content: center;
height: 0;
#content {
padding-left: 10px;
display: flex;
gap: 5px;
align-items: center;
}
tui-icon {
font-size: 13px;
}
.items-left, .items-right {
button {
height: 30px;
height: 26px;
width: 30px;
}
tui-icon {
font-size: 20px;
}
&.shown {
transform: translateY(0);
height: 30px;
}
}
.items-middle {
::-webkit-scrollbar-thumb {
background: transparent;
border-radius: 10px;
#message-box {
z-index: 5;
transition: all 0.2s ease-in-out;
width: 60%;
background: var(--tui-background-base-alt);
min-height: 75px;
max-height: 200px;
border: 2px solid var(--tui-border-normal);
display: grid;
grid-template-columns: 85px 1fr 100px;
align-items: center;
padding: 0 10px;
.items-left, .items-middle, .items-right {
display: flex;
align-items: center;
}
position: relative;
.items-left, .items-right {
button {
height: 30px;
width: 30px;
textarea {
width: 100%;
background: transparent;
border: none;
outline: none;
resize: none;
color: var(--tui-text-01);
font-size: 16px;
z-index: 1;
}
.placeholder {
position: absolute;
font-size: 16px;
height: 25px;
top: 2px;
left: 2px;
color: gray;
transition: all 0.2s ease-in-out;
&.hidden {
margin-left: 10px;
opacity: 0;
tui-icon {
font-size: 20px;
}
}
}
}
.items-right {
display: flex;
justify-content: end;
.items-middle {
::-webkit-scrollbar-thumb {
background: transparent;
border-radius: 10px;
}
position: relative;
textarea {
width: 100%;
background: transparent;
border: none;
outline: none;
resize: none;
color: var(--tui-text-01);
font-size: 16px;
z-index: 1;
}
.placeholder {
position: absolute;
font-size: 16px;
height: 25px;
top: 2px;
left: 2px;
color: gray;
transition: all 0.2s ease-in-out;
&.hidden {
margin-left: 10px;
opacity: 0;
}
}
}
.items-right {
display: flex;
justify-content: end;
}
}
}
}

View File

@@ -103,6 +103,15 @@ export class MessageBox {
this.viewModel().dialogOpen.set(false)
}
handleEnterKeydown(e: any) {
e.preventDefault()
return false
}
filterPictureVideo(files: FileDataWithPreview[]) {
return files.filter(f => f.type == "image")
}
async processFiles(fileList: FileList) {
for (let i = 0; i < fileList.length; i++) {
const file = fileList[i];

View File

@@ -8,25 +8,59 @@
[class.chained_end]="isMessageEndOfChain(i)"
>
<div class="above">
<span>{{message.sent_at.T * 1000 | date: 'HH:mm'}}</span>
<span>{{ message.sent_at.T * 1000 | date: 'HH:mm' }}</span>
</div>
<div class="bubble">
<span class="message-text">{{message.message}}</span>
<div class="bubble" tuiDropdownContext [tuiDropdown]="messageContextMenu">
<span class="message-text">{{ message.message }}</span>
<masonry style="max-height: 300px">
@for (file of message.files; track file) {
@for (file of filterExpressedMedia(message.files); track file) {
@if (file.type == "image") {
<img [src]="file.path" style="width: 100%; height: 100%; max-height: 300px; object-fit: cover; border-radius: 25px"/>
<img [src]="file.path"
style="width: 100%; height: 100%; max-height: 300px; object-fit: cover; border-radius: 25px"/>
} @else if (file.type == "video") {
@if (file.extraMetaData && Object.keys(file.extraMetaData).length > 0) {
<video-player maxHeight="250px" maxWidth="250px" [src]="file.path" [thumbnailOverwrite]="file.extraMetaData['thumbnailMetaData']"></video-player>
<video-player maxHeight="300px" maxWidth="300px" [src]="file.path"
[thumbnailOverwrite]="file.extraMetaData['thumbnailMetaData']"></video-player>
} @else {
<video-player maxHeight="250px" maxWidth="250px" [src]="file.path"></video-player>
<video-player maxHeight="300px" maxWidth="300px" [src]="file.path"></video-player>
}
}
}
</masonry>
<div tuiGroup orientation="vertical" style="width: 100%">
@for (file of filterNonExpressedMedia(message.files); track file) {
<div style="width: 100%; height: 50px; display: flex; align-items: center; padding: 0 10px; background: var(--tui-background-accent-1); gap: 5px">
<tui-icon icon="@tui.file"/>
<span>{{ file.fileName }}</span>
</div>
}
</div>
</div>
<div class="below"></div>
<ng-template #messageContextMenu>
<tui-data-list>
<button
tuiOption
type="button"
iconEnd="@tui.pencil"
(click)="messageBoxViewModel().editingMessage.set({message: message.message, messageId: message.msgid}); messageBoxViewModel().message.set(message.message);"
>
{{ "chat.elements.messages.contextMenu.edit" | translate }}
</button>
<button
style="color: var(--tui-text-negative)"
tuiOption
type="button"
iconEnd="@tui.trash"
(click)="onDelete.emit(message.msgid)"
>
{{ "chat.elements.messages.contextMenu.delete" | translate }}
</button>
</tui-data-list>
</ng-template>
</div>
}
}

View File

@@ -56,6 +56,7 @@
max-width: 50%;
min-width: 250px;
min-height: 40px;
max-height: 400px;
display: flex;
flex-direction: column;
justify-content: center;

View File

@@ -1,17 +1,38 @@
import {Component, inject, input} from '@angular/core';
import {Component, EventEmitter, inject, input, Output} from '@angular/core';
import {Message} from '@chatenium/chatenium-sdk/domain/dmService.schema';
import {Message as NetworkMessage} from '@chatenium/chatenium-sdk/domain/textChannelService.schema'
import {ServiceManager} from '../../../service-manager';
import {DatePipe} from '@angular/common';
import {Masonry} from '../masonry/masonry';
import {VideoPlayer} from '../video-player/video-player';
import {
TuiButton,
TuiDataListComponent,
TuiDropdown,
TuiDropdownContext,
TuiGroup,
TuiIcon,
TuiOption
} from '@taiga-ui/core';
import {TranslatePipe} from '@ngx-translate/core';
import {MessageBoxViewModel} from '../message-box/message-box-viewmodel';
import {FileDataWithPreview} from '../message-box/message-box';
import {Attachment} from '@chatenium/chatenium-sdk/domain/common.schema';
@Component({
selector: 'messages',
imports: [
DatePipe,
Masonry,
VideoPlayer
VideoPlayer,
TuiDropdownContext,
TuiDropdown,
TuiButton,
TuiOption,
TranslatePipe,
TuiDataListComponent,
TuiGroup,
TuiIcon
],
templateUrl: './messages.html',
styleUrl: './messages.scss',
@@ -19,7 +40,10 @@ import {VideoPlayer} from '../video-player/video-player';
export class Messages {
serviceManager = inject(ServiceManager)
messages = input<Message[] | NetworkMessage[]>([])
messages = input.required<Message[] | NetworkMessage[]>()
messageBoxViewModel = input.required<MessageBoxViewModel>()
@Output() onDelete = new EventEmitter<string>()
/**
* Helps code readability by specifying what type of messages are being processed.
@@ -111,5 +135,13 @@ export class Messages {
return i + 1 === this.messages().length;
}
filterExpressedMedia(files: Attachment[]) {
return files.filter(f => f.type == "image" || f.type == "video")
}
filterNonExpressedMedia(files: Attachment[]) {
return files.filter(f => f.type != "image" && f.type != "video")
}
protected readonly Object = Object;
}

View File

@@ -1 +1 @@
<img [src]="src()" [style]="'width:'+width()+';height:'+height()+'; border-radius: '+radius()+'px;'" class="">
<img [src]="src()" [style]="'width:'+width()+';height:'+height()+'; border-radius: '+radius()+'px; object-fit:'+objectFit()" class="">

View File

@@ -15,4 +15,5 @@ export class Oimg {
width = input("")
src = input("")
radius = input(15)
objectFit = input("cover")
}

View File

@@ -1,7 +1,7 @@
@if (playerActive) {
<div id="player" [style]="'max-width:'+maxWidth+';max-height:'+maxHeight" (mouseover)="controlShowed = true"
(mouseleave)="controlShowed = false" #player>
<video (mouseout)="showVolRange = false" [class.upScale]="videoFullscreen"
<video (mouseout)="showVolRange = false" (mouseover)="showVolRange = false" [class.upScale]="videoFullscreen"
[style]="'max-width:'+maxWidth+';height:'+maxHeight+';border-radius: 15px'" (pause)="videoPlaying = false"
(play)="videoPlaying = true" #video (timeupdate)="watched = videoHMSFormat(video.currentTime)"
(loadedmetadata)="videoPlayer = video; video.style.display = 'block'; videoLoaded = true"
@@ -15,28 +15,21 @@
<div id="controlsHolder">
<div id="controls">
<button tuiButton appearance="icon"
(click)="videoPlaying ? video.pause() : video.play();">
@if (videoPlaying) {
<tui-icon icon="@tui.pause"></tui-icon>
} @else {
<tui-icon icon="@tui.play"></tui-icon>
}
</button>
<span>{{ watched }}</span>
<progress class="timeProgress" (click)="jump($event)" [max]="video.duration" [value]="video.currentTime"
tuiProgressBar size="xs"></progress>
<div>
<!-- @if (showVolRange) { -->
<!-- } -->
<div class="volumeSetter" (mouseover)="showVolRange = true">
<div id="buttons">
<button tuiButton appearance="icon"
(mouseover)="showVolRange = false"
(click)="videoPlaying ? video.pause() : video.play();" id="left">
@if (videoPlaying) {
<tui-icon icon="@tui.pause"></tui-icon>
} @else {
<tui-icon icon="@tui.play"></tui-icon>
}
</button>
<div id="right">
<button
type="button"
tuiButton appearance="icon"
tuiDropdown
tuiDropdownAppearance="neutral"
tuiDropdownAuto
tuiDropdownDirection="top"
tuiDropdownLimitWidth="fixed"
tuiIconButton
@@ -50,26 +43,30 @@
} @else {
<tui-icon icon="@tui.volume"></tui-icon>
}
<input
*tuiDropdown
tuiSlider
type="range"
value="100"
(input)="setVolume(volume)"
[(ngModel)]="volume"
/>
</button>
<input
tuiSlider
type="range"
value="100"
(input)="setVolume(volume)"
[(ngModel)]="volume"
/>
<button tuiButton appearance="icon"
(mouseover)="showVolRange = false"
(click)="videoFullscreen ? exitFullScreen() : player.requestFullscreen(); videoFullscreen = !videoFullscreen">
@if (videoFullscreen) {
<tui-icon icon="@tui.minimize"></tui-icon>
} @else {
<tui-icon icon="@tui.maximize"></tui-icon>
}
</button>
</div>
</div>
<button tuiButton appearance="icon"
(click)="videoFullscreen ? exitFullScreen() : player.requestFullscreen(); videoFullscreen = !videoFullscreen">
@if (videoFullscreen) {
<tui-icon icon="@tui.minimize"></tui-icon>
} @else {
<tui-icon icon="@tui.maximize"></tui-icon>
}
</button>
<div id="info">
<span>{{ watched }}</span>
<progress class="timeProgress" (click)="jump($event)" [max]="video.duration" [value]="video.currentTime"
tuiProgressBar size="xs"></progress>
</div>
</div>
</div>
</div>
@@ -77,9 +74,10 @@
} @else {
<div class="player_preview">
@if (thumbnailOverwrite) {
<img [style]="'max-width:'+maxWidth+';max-height:'+maxHeight+';border-radius: 15px'" [src]="thumbnailOverwrite" />
<img [style]="'max-width:'+maxWidth+';max-height:'+maxHeight+';border-radius: 15px'" [src]="thumbnailOverwrite"/>
} @else {
<oimg [height]="maxHeight" [width]="maxWidth" style="border-radius: 15px" [src]="src+'_thumbnail.png'"></oimg>
<oimg [height]="maxHeight" [width]="maxWidth" style="border-radius: 15px; object-fit: cover"
[src]="src+'_thumbnail.png'"></oimg>
}
<button tuiButton (click)="playVideo()">
<tui-icon icon="@tui.play"></tui-icon>

View File

@@ -36,33 +36,49 @@
#controls {
width: 95%;
height: 50px;
height: 60px;
background: var(--tui-background-base-alt);
border: 2px solid var(--tui-border-normal);
padding: 10px;
border-radius: 20px;
gap: 5px;
display: grid;
grid-template-columns: 50px 50px 1fr 50px 50px;
align-items: center;
display: flex;
flex-direction: column;
button {
height: 25px;
width: 50px;
}
.time {
font-size: 15px;
display: flex;
flex-direction: column;
gap: 0;
}
.volumeSetter {
display: flex;
gap: 5px;
#info {
display: grid;
grid-template-columns: 50px 1fr;
align-items: center;
}
#buttons {
display: grid;
grid-template-columns: 50px 1fr;
align-items: center;
#right {
display: flex;
align-items: center;
justify-content: end;
}
button {
height: 25px;
width: 50px;
}
.time {
font-size: 15px;
display: flex;
flex-direction: column;
gap: 0;
}
.volumeSetter {
display: flex;
gap: 5px;
align-items: center;
}
}
}
}
}
@@ -92,6 +108,7 @@ tui-dropdown[data-appearance] {
box-shadow: none;
border: none;
backdrop-filter: blur(1rem);
position: fixed;
[tuiSlider] {
position: absolute;

View File

@@ -1,7 +1,7 @@
import {Component, Input, ViewEncapsulation} from '@angular/core';
import {
TuiButton,
TuiDropdownDirective,
TuiDropdown,
TuiDropdownOpen,
TuiDropdownOptionsDirective,
TuiIcon,
@@ -21,7 +21,7 @@ import {TuiInputDateRange, TuiProgressBar} from '@taiga-ui/kit';
TuiSlider,
TuiProgressBar,
TuiDropdownOptionsDirective,
TuiDropdownDirective,
TuiDropdown,
TuiDropdownOpen,
TuiInputDateRange
],