Skip to content

Commit 6abc306

Browse files
committed
split chat into components, mark chat as active if id is in route, focus input automatically
1 parent a23f586 commit 6abc306

File tree

5 files changed

+157
-102
lines changed

5 files changed

+157
-102
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<Virtualize Items="ActiveChat?.Messages" Context="message" OverscanCount="8" ItemSize="80">
2+
3+
@if (IsMessageFromSelf(message))
4+
{
5+
<MudListItem Class="message-from-self">
6+
7+
<MudTooltip Placement="Placement.Top" ShowOnClick ShowOnHover="false" Arrow Text="@message.SentUtc.DisplayExactTime()">
8+
<MudTooltip ShowOnHover Arrow Text="@message.SentUtc.DisplayTimePassed()">
9+
<MudChip Text="@message.Text"
10+
Class="chat-chip px-2 py-1" />
11+
</MudTooltip>
12+
</MudTooltip>
13+
</MudListItem>
14+
}
15+
else
16+
{
17+
<MudListItem>
18+
<MudTooltip Placement="Placement.Top" ShowOnClick ShowOnHover="false" Arrow Text="@message.SentUtc.DisplayExactTime()">
19+
<MudTooltip ShowOnHover Arrow Text="@message.SentUtc.DisplayTimePassed()">
20+
<MudChip Class="pl-0 pb-0 chat-chip">
21+
<MudAvatar Size="Size.Medium" Class="mr-2">
22+
<MudImage Src="@GetRecipientsProfilePictureUrl(message)" loading="lazy" Alt="Avatar" />
23+
</MudAvatar>
24+
@message.Text
25+
</MudChip>
26+
</MudTooltip>
27+
</MudTooltip>
28+
</MudListItem>
29+
}
30+
</Virtualize>
31+
32+
@code
33+
{
34+
[Parameter]
35+
public required UserProfile CurrentUser { get; set; } = null!;
36+
[Parameter]
37+
public required ChatDto? ActiveChat { get; set; }
38+
39+
private bool IsMessageFromSelf(ChatMessageDto message) => message.SenderId == CurrentUser.Id;
40+
41+
private string GetRecipientsProfilePictureUrl(ChatMessageDto message)
42+
=> ActiveChat?
43+
.Recipients
44+
.FirstOrDefault(recipient => recipient.Id == message.SenderId)?.ProfilePictureUrl
45+
?? ProfileConstants.Default_Profile_Picture;
46+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<MudItem Class="chat-selector-window" md="3" lg="3" xl="3" xxl="3">
2+
<MudList Clickable>
3+
<MudListSubheader>
4+
<UserAutoComplete SelectedUserChanged="StartNewChat" />
5+
</MudListSubheader>
6+
@foreach (var chat in Chats)
7+
{
8+
<MudListItem OnClick="@(() => SelectChat(chat))" Dense="true" Class="chat-selector">
9+
10+
<MudAvatar Size="Size.Large" Class="mr-3">
11+
<MudImage Src="@chat.GetChatImage(CurrentUser.Id)" loading="lazy" Alt="Avatar" />
12+
</MudAvatar>
13+
<MudText>
14+
@if (chat.HasUnreadMessages)
15+
{
16+
<b>@chat.GetDisplayName(CurrentUser.Id) (@chat.UnreadMessageCount)</b>
17+
}
18+
else
19+
{
20+
@chat.GetDisplayName(CurrentUser.Id)
21+
}
22+
</MudText>
23+
24+
</MudListItem>
25+
<MudDivider DividerType="DividerType.FullWidth" />
26+
27+
}
28+
</MudList>
29+
</MudItem>
30+
31+
@code{
32+
[Parameter]
33+
public required UserProfile CurrentUser { get; set; } = null!;
34+
[Parameter]
35+
public required List<ChatDto> Chats { get; set; } = null!;
36+
37+
[Parameter]
38+
public required EventCallback<ChatDto> SelectChatCallback { get; set; }
39+
[Parameter]
40+
public required EventCallback<IEnumerable<UserSlim>> StartNewChatCallback { get; set; }
41+
42+
43+
private async Task StartNewChat(IEnumerable<UserSlim> users)
44+
{
45+
await StartNewChatCallback.InvokeAsync(users);
46+
}
47+
48+
private async Task SelectChat(ChatDto chat)
49+
{
50+
await SelectChatCallback.InvokeAsync(chat);
51+
}
52+
53+
}

src/web/Client/Features/Chat/LargeChatComponent.razor

Lines changed: 42 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -13,115 +13,59 @@
1313

1414
<MudLoading @bind-Loading="_isLoading" Darken Overlap>
1515
<MudGrid Style="height: 600px">
16-
<MudItem Class="chat-selector-window" md="3" lg="3" xl="3" xxl="3">
17-
<MudList Clickable>
18-
<MudListSubheader>
19-
<UserAutoComplete SelectedUserChanged="StartNewChat" />
20-
</MudListSubheader>
21-
@foreach (var chat in _chats)
22-
{
23-
<MudListItem OnClick="@(() => SelectChat(chat))" Dense="true" Class="chat-selector">
24-
25-
<MudAvatar Size="Size.Large" Class="mr-3">
26-
<MudImage Src="@chat.GetChatImage(_currentUser.Id)" loading="lazy" Alt="Avatar" />
27-
</MudAvatar>
28-
<MudText>
29-
@if (chat.HasUnreadMessages)
30-
{
31-
<b>@chat.GetDisplayName(_currentUser.Id) (@chat.UnreadMessageCount)</b>
32-
}
33-
else
34-
{
35-
@chat.GetDisplayName(_currentUser.Id)
36-
}
37-
</MudText>
38-
39-
</MudListItem>
40-
<MudDivider DividerType="DividerType.FullWidth" />
41-
42-
}
43-
</MudList>
44-
</MudItem>
16+
17+
<ChatSelector CurrentUser="_currentUser" Chats="_chats" SelectChatCallback="SelectChat" StartNewChatCallback="StartNewChat"/>
4518

4619
<MudItem md="1" lg="1" xl="1" xxl="1">
47-
<MudDivider Vertical DividerType="DividerType.FullWidth" />
20+
<MudDivider Vertical DividerType="DividerType.FullWidth"/>
4821
</MudItem>
4922

50-
<MudItem id="chat-message-window" Class="chat-message-window" md="8" lg="8" xl="8" xxl="8">
23+
<MudItem Class="chat-message-window" md="8" lg="8" xl="8" xxl="8">
5124
@if (_activeChat != null)
5225
{
5326
<MudList Class="chat-message-list" DisablePadding Dense>
54-
@* ReSharper disable once UnusedParameter.Local *@
55-
<Virtualize Items="_activeChat.Messages" Context="message" OverscanCount="8" ItemSize="80">
56-
@if (IsMessageFromSelf(message))
57-
{
58-
<MudListItem Class="message-from-self">
59-
60-
<MudTooltip Placement="Placement.Top" ShowOnClick ShowOnHover="false" Arrow Text="@message.SentUtc.DisplayExactTime()">
61-
<MudTooltip ShowOnHover Arrow Text="@message.SentUtc.DisplayTimePassed()">
62-
<MudChip Text="@message.Text"
63-
Class="chat-chip px-2 py-1" />
64-
</MudTooltip>
65-
</MudTooltip>
66-
</MudListItem>
67-
}
68-
else
69-
{
70-
<MudListItem>
71-
<MudTooltip Placement="Placement.Top" ShowOnClick ShowOnHover="false" Arrow Text="@message.SentUtc.DisplayExactTime()">
72-
<MudTooltip ShowOnHover Arrow Text="@message.SentUtc.DisplayTimePassed()">
73-
<MudChip Class="pl-0 pb-0 chat-chip">
74-
<MudAvatar Size="Size.Medium" Class="mr-2">
75-
<MudImage Src="@GetRecipientsProfilePictureUrl(message)" loading="lazy" Alt="Avatar" />
76-
</MudAvatar>
77-
@message.Text
78-
</MudChip>
79-
</MudTooltip>
80-
</MudTooltip>
81-
</MudListItem>
82-
}
83-
84-
</Virtualize>
27+
28+
<ChatMessageList CurrentUser="_currentUser" ActiveChat="_activeChat" />
29+
8530

8631
<MudAnimate @ref="_messageSuccessfullySentAnimation"
8732
AnimationType="AnimationType.Fade"
8833
AnimationTiming="AnimationTiming.EaseIn"
8934
Delay="500"
90-
Selector=".message-sent-successfully-container" />
35+
Selector=".message-sent-successfully-container"/>
36+
37+
@if (LastMessageWasSentSuccessfullyByCurrentUser)
38+
{
39+
<div title="Din besked er blevet sendt" class="message-sent-successfully-container">
40+
<MudIcon Icon="@Icons.Material.Outlined.CheckCircleOutline"
41+
Class="message-sent-successfully"/>
42+
</div>
43+
}
9144

92-
@if (LastMessageWasSentSuccessfullyByCurrentUser)
93-
{
94-
<div title="Din besked er blevet sendt" class="message-sent-successfully-container">
95-
<MudIcon Icon="@Icons.Material.Outlined.CheckCircleOutline"
96-
Class="message-sent-successfully" />
97-
</div>
98-
}
99-
<div class="flex-spacer"></div>
45+
<div class="flex-spacer"></div>
10046

10147
<MudTextField @bind-Value="_userText"
102-
FullWidth
103-
Immediate
104-
AutoFocus
48+
id="chat-message-input"
49+
FullWidth
50+
Immediate
51+
AutoFocus
10552
Adornment="Adornment.End"
10653
TextUpdateSuppression="false"
10754
OnKeyDown="SendMessageOnEnter"
10855
AdornmentIcon="@Icons.Material.Filled.Send"
10956
AdornmentColor="@(string.IsNullOrEmpty(_userText) ? Color.Default : Color.Primary)"
110-
OnAdornmentClick="SendMessage" />
57+
OnAdornmentClick="SendMessage"/>
11158
</MudList>
11259
}
113-
11460
</MudItem>
11561
</MudGrid>
116-
11762
</MudLoading>
63+
11864
@code {
119-
// TODO: Select currently displayed chat using this, when we update to dotnet 8
120-
[SupplyParameterFromQuery]
65+
[Parameter]
12166
public Guid? ChatId { get; set; }
12267

12368
private UserProfile? _currentUser;
124-
private UserSlim _currentUserSlim = null!;
12569
private List<ChatDto> _chats = new();
12670
private ChatDto? _activeChat;
12771

@@ -163,9 +107,11 @@
163107
return;
164108
}
165109

166-
_currentUserSlim = _currentUser.ToUserSlim();
167-
168110
_chats = await ChatService.GetChats(_currentUser.Id);
111+
if (ChatId is not null)
112+
{
113+
_activeChat = _chats.FirstOrDefault(chat => chat.Id == ChatId);
114+
}
169115

170116
ChatSignalRClient.OnChatStarted(chat =>
171117
{
@@ -227,8 +173,6 @@
227173
_activeChat.Messages = await ChatService.GetChatMessages(_activeChat.Id);
228174

229175
StateHasChanged();
230-
231-
await ScrollToBottom();
232176
}
233177

234178
private async Task SelectChat(ChatDto chat)
@@ -237,9 +181,11 @@
237181
if (_isActiveChatPublished)
238182
{
239183
await LoadMessages();
240-
// We call this twice to ensure we're at the very bottom. Silly but easy
184+
241185
await ScrollToBottom();
242186

187+
await FocusMessageInput();
188+
243189
if (chat.HasUnreadMessages)
244190
{
245191
await ChatService.MarkMessagesAsRead(_activeChat.Id);
@@ -248,6 +194,8 @@
248194
}
249195
}
250196

197+
private async Task FocusMessageInput() => await JsRuntime.InvokeVoidAsync("scrollFunctions.focusMessageInput");
198+
251199
private async Task ScrollToBottom() => await JsRuntime.InvokeVoidAsync("scrollFunctions.scrollToTheBottomOfChat");
252200

253201
private void BackToList() => _activeChat = null;
@@ -264,7 +212,7 @@
264212
ChatId = _activeChat.Id,
265213
Id = NewId.NextGuid(),
266214
SentUtc = DateTime.UtcNow,
267-
SenderId = _currentUserSlim.Id,
215+
SenderId = _currentUser!.Id,
268216
Text = _userText
269217
};
270218

@@ -292,12 +240,10 @@
292240
}
293241
}
294242

295-
private bool IsMessageFromSelf(ChatMessageDto message) => message.SenderId == _currentUser!.Id;
296-
297243
private async Task StartNewChat(IEnumerable<UserSlim> users)
298244
{
299245
var userList = users.ToList();
300-
if (userList.Count() == 1)
246+
if (userList.Count == 1)
301247
{
302248
var userIdsToFind = new[] { userList.First().Id, _currentUser!.Id };
303249
var existingChat = _chats.FirstOrDefault(chat => chat.Recipients.Count == 2 &&
@@ -311,12 +257,12 @@
311257
}
312258

313259
var newChat = new ChatDto
314-
{
315-
Id = NewId.NextGuid(),
316-
Recipients = new List<UserSlim> { _currentUserSlim },
317-
LastMessageSentUtc = DateTime.UtcNow,
318-
StartedUtc = DateTime.UtcNow
319-
};
260+
{
261+
Id = NewId.NextGuid(),
262+
Recipients = new List<UserSlim> { _currentUser!.ToUserSlim() },
263+
LastMessageSentUtc = DateTime.UtcNow,
264+
StartedUtc = DateTime.UtcNow
265+
};
320266

321267
foreach (var user in userList.Where(u => u.Id != _currentUser!.Id))
322268
{
@@ -331,12 +277,7 @@
331277
_chats.Insert(0, newChat);
332278

333279
_isActiveChatPublished = false;
280+
334281
await SelectChat(newChat);
335282
}
336-
337-
private string GetRecipientsProfilePictureUrl(ChatMessageDto message)
338-
=> _activeChat?
339-
.Recipients
340-
.FirstOrDefault(recipient => recipient.Id == message.SenderId)?.ProfilePictureUrl
341-
?? ProfileConstants.Default_Profile_Picture;
342283
}

src/web/Client/Pages/Chat/ChatPage.razor

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
@page "/chat"
2+
@page "/chat/{chatId:guid}"
3+
24
@attribute [Authorize]
35

46
<MudContainer MaxWidth="MaxWidth.Large">
@@ -9,8 +11,14 @@
911
</MudHidden>
1012

1113
<MudHidden Invert Breakpoint="Breakpoint.MdAndUp">
12-
<LargeChatComponent />
14+
<LargeChatComponent ChatId="ChatId"/>
1315
</MudHidden>
1416

1517
</MudPaper>
1618
</MudContainer>
19+
20+
@code
21+
{
22+
[Parameter]
23+
public Guid? ChatId { get; set; }
24+
}

src/web/Client/wwwroot/js/scroll.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,12 @@ window.scrollFunctions = {
1515
if (!chatContainer) return;
1616

1717
chatContainer.scrollTop = chatContainer.scrollHeight;
18+
},
19+
focusMessageInput: function () {
20+
const chatMessageInput = document.querySelector('#chat-message-input');
21+
22+
if (!chatMessageInput) return;
23+
24+
chatMessageInput.focus();
1825
}
1926
};

0 commit comments

Comments
 (0)