feat: Theme studio v2
This commit is contained in:
@@ -1,5 +1,11 @@
|
||||
<section class="chat-layout relative h-full bg-background">
|
||||
<header class="flex h-14 shrink-0 items-center gap-3 border-b border-border px-4">
|
||||
<section
|
||||
appThemeNode="dmChatSurface"
|
||||
class="chat-layout relative h-full bg-background"
|
||||
>
|
||||
<header
|
||||
appThemeNode="dmChatHeader"
|
||||
class="flex h-14 shrink-0 items-center gap-3 border-b border-border px-4"
|
||||
>
|
||||
<app-user-avatar
|
||||
[name]="peerName()"
|
||||
[avatarUrl]="peerUser()?.avatarUrl"
|
||||
@@ -14,7 +20,10 @@
|
||||
</header>
|
||||
|
||||
@if (conversation()) {
|
||||
<div class="absolute inset-x-0 bottom-0 top-14">
|
||||
<div
|
||||
appThemeNode="dmMessageRegion"
|
||||
class="absolute inset-x-0 bottom-0 top-14"
|
||||
>
|
||||
<app-chat-message-list
|
||||
[allMessages]="chatMessages()"
|
||||
[channelMessages]="chatMessages()"
|
||||
@@ -45,7 +54,10 @@
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="chat-bottom-bar absolute bottom-0 left-0 right-2 z-10 bg-background/85 backdrop-blur-md">
|
||||
<div
|
||||
appThemeNode="chatComposerBar"
|
||||
class="chat-bottom-bar absolute bottom-0 left-0 right-2 z-10 bg-background/85 backdrop-blur-md"
|
||||
>
|
||||
<app-chat-message-composer
|
||||
[replyTo]="replyTo()"
|
||||
[showKlipyGifPicker]="showGifPicker()"
|
||||
@@ -72,6 +84,7 @@
|
||||
|
||||
<div class="pointer-events-none fixed inset-0 z-[90]">
|
||||
<div
|
||||
appThemeNode="chatGifPickerSurface"
|
||||
class="pointer-events-auto absolute w-[calc(100vw-2rem)] max-w-5xl sm:w-[34rem] md:w-[42rem] xl:w-[52rem]"
|
||||
[style.bottom.px]="composerBottomPadding() + 8"
|
||||
[style.right.px]="gifPickerAnchorRight()"
|
||||
|
||||
@@ -16,6 +16,7 @@ import { map } from 'rxjs';
|
||||
import { ElectronBridgeService } from '../../../../core/platform/electron/electron-bridge.service';
|
||||
import { UserAvatarComponent } from '../../../../shared';
|
||||
import { Attachment, AttachmentFacade } from '../../../attachment';
|
||||
import { ThemeNodeDirective } from '../../../theme';
|
||||
import { DirectMessageService } from '../../application/services/direct-message.service';
|
||||
import { selectAllUsers, selectCurrentUser } from '../../../../store/users/users.selectors';
|
||||
import {
|
||||
@@ -56,6 +57,7 @@ interface DmStatusLabel {
|
||||
ChatMessageListComponent,
|
||||
ChatMessageOverlaysComponent,
|
||||
KlipyGifPickerComponent,
|
||||
ThemeNodeDirective,
|
||||
UserAvatarComponent
|
||||
],
|
||||
templateUrl: './dm-chat.component.html',
|
||||
@@ -106,21 +108,23 @@ export class DmChatComponent {
|
||||
const knownUser = knownUsers.find((user) => user.id === participantId || user.oderId === participantId);
|
||||
const participant = conversation.participantProfiles[participantId];
|
||||
|
||||
return knownUser ?? {
|
||||
id: participantId,
|
||||
oderId: participantId,
|
||||
username: participant?.username || participant?.displayName || participantId,
|
||||
displayName: participant?.displayName || participant?.username || participantId,
|
||||
description: participant?.description,
|
||||
profileUpdatedAt: participant?.profileUpdatedAt,
|
||||
avatarUrl: participant?.avatarUrl,
|
||||
avatarHash: participant?.avatarHash,
|
||||
avatarMime: participant?.avatarMime,
|
||||
avatarUpdatedAt: participant?.avatarUpdatedAt,
|
||||
status: 'disconnected',
|
||||
role: 'member',
|
||||
joinedAt: 0
|
||||
};
|
||||
return (
|
||||
knownUser ?? {
|
||||
id: participantId,
|
||||
oderId: participantId,
|
||||
username: participant?.username || participant?.displayName || participantId,
|
||||
displayName: participant?.displayName || participant?.username || participantId,
|
||||
description: participant?.description,
|
||||
profileUpdatedAt: participant?.profileUpdatedAt,
|
||||
avatarUrl: participant?.avatarUrl,
|
||||
avatarHash: participant?.avatarHash,
|
||||
avatarMime: participant?.avatarMime,
|
||||
avatarUpdatedAt: participant?.avatarUpdatedAt,
|
||||
status: 'disconnected',
|
||||
role: 'member',
|
||||
joinedAt: 0
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
readonly messageStatuses = computed<DmStatusLabel[]>(() => {
|
||||
@@ -218,14 +222,13 @@ export class DmChatComponent {
|
||||
|
||||
const content = event.content.trim() || event.pendingFiles.map((file) => file.name).join('\n');
|
||||
|
||||
void this.directMessages.sendMessage(conversation.id, content, this.replyTo()?.id)
|
||||
.then((message) => {
|
||||
this.replyTo.set(null);
|
||||
void this.directMessages.sendMessage(conversation.id, content, this.replyTo()?.id).then((message) => {
|
||||
this.replyTo.set(null);
|
||||
|
||||
if (event.pendingFiles.length > 0) {
|
||||
this.attachments.publishAttachments(message.id, event.pendingFiles, this.currentUserId() || undefined);
|
||||
}
|
||||
});
|
||||
if (event.pendingFiles.length > 0) {
|
||||
this.attachments.publishAttachments(message.id, event.pendingFiles, this.currentUserId() || undefined);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setReplyTo(message: ChatMessageReplyEvent): void {
|
||||
@@ -388,8 +391,7 @@ export class DmChatComponent {
|
||||
continue;
|
||||
}
|
||||
|
||||
const urls = this.linkMetadata.extractUrls(message.content)
|
||||
.filter((url) => !hasDedicatedChatEmbed(url));
|
||||
const urls = this.linkMetadata.extractUrls(message.content).filter((url) => !hasDedicatedChatEmbed(url));
|
||||
|
||||
if (urls.length === 0) {
|
||||
continue;
|
||||
|
||||
@@ -8,7 +8,10 @@
|
||||
[ngStyle]="listPanelStyles()"
|
||||
>
|
||||
<section class="flex h-full w-full min-w-0 flex-col">
|
||||
<header class="flex h-14 shrink-0 items-center gap-2 border-b border-border px-3">
|
||||
<header
|
||||
appThemeNode="dmConversationsHeader"
|
||||
class="flex h-14 shrink-0 items-center gap-2 border-b border-border px-3"
|
||||
>
|
||||
<div class="grid h-8 w-8 place-items-center rounded-lg bg-secondary text-muted-foreground">
|
||||
<ng-icon
|
||||
name="lucideMessageCircle"
|
||||
@@ -21,13 +24,17 @@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="min-h-0 flex-1 overflow-y-auto p-2">
|
||||
<div
|
||||
appThemeNode="dmConversationList"
|
||||
class="min-h-0 flex-1 overflow-y-auto p-2"
|
||||
>
|
||||
@if (directMessages.conversations().length === 0) {
|
||||
<div class="flex h-full items-center justify-center px-4 text-center text-sm text-muted-foreground">No direct messages yet.</div>
|
||||
} @else {
|
||||
<div class="space-y-1">
|
||||
@for (conversation of directMessages.conversations(); track conversation.id) {
|
||||
<div
|
||||
appThemeNode="dmConversationItem"
|
||||
class="group flex w-full items-center gap-2 rounded-md px-2 py-2 text-left transition-colors hover:bg-secondary/60"
|
||||
[class.bg-primary/10]="isSelectedConversation(conversation)"
|
||||
[class.text-foreground]="isSelectedConversation(conversation)"
|
||||
@@ -72,7 +79,10 @@
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="border-t border-border px-2 py-3">
|
||||
<div
|
||||
appThemeNode="dmVoiceControlsArea"
|
||||
class="border-t border-border px-2 py-3"
|
||||
>
|
||||
<app-voice-controls />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -50,7 +50,7 @@ export class DmWorkspaceComponent implements OnDestroy {
|
||||
readonly routeConversationId = toSignal(this.route.paramMap.pipe(map((params) => params.get('conversationId'))), {
|
||||
initialValue: this.route.snapshot.paramMap.get('conversationId')
|
||||
});
|
||||
readonly layoutStyles = computed(() => this.theme.getLayoutContainerStyles('roomLayout'));
|
||||
readonly layoutStyles = computed(() => this.theme.getLayoutContainerStyles('dmLayout'));
|
||||
readonly listPanelStyles = computed(() => this.theme.getLayoutItemStyles('dmConversationsPanel'));
|
||||
readonly chatPanelStyles = computed(() => this.theme.getLayoutItemStyles('dmChatPanel'));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user