3.0 Beta 2
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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"/>
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
max-width: 50%;
|
||||
min-width: 250px;
|
||||
min-height: 40px;
|
||||
max-height: 400px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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="">
|
||||
|
||||
@@ -15,4 +15,5 @@ export class Oimg {
|
||||
width = input("")
|
||||
src = input("")
|
||||
radius = input(15)
|
||||
objectFit = input("cover")
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user