Merge pull request #109 from Moonlight-Panel/NewSupportChatFeatures
Added multi line feature and file upload in the support chat
This commit is contained in:
8
Moonlight/App/Database/Entities/SupportChatSnippets.cs
Normal file
8
Moonlight/App/Database/Entities/SupportChatSnippets.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Moonlight.App.Database.Entities;
|
||||
|
||||
public class SupportChatSnippets
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; } = "";
|
||||
public string Description { get; set; } = "";
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Moonlight.App.Helpers;
|
||||
using Moonlight.App.Models.Misc;
|
||||
using Moonlight.App.Services;
|
||||
using Moonlight.App.Services.LogServices;
|
||||
|
||||
namespace Moonlight.App.Http.Controllers.Api.Moonlight;
|
||||
@@ -11,10 +12,13 @@ namespace Moonlight.App.Http.Controllers.Api.Moonlight;
|
||||
public class ResourcesController : Controller
|
||||
{
|
||||
private readonly SecurityLogService SecurityLogService;
|
||||
private readonly BucketService BucketService;
|
||||
|
||||
public ResourcesController(SecurityLogService securityLogService)
|
||||
public ResourcesController(SecurityLogService securityLogService,
|
||||
BucketService bucketService)
|
||||
{
|
||||
SecurityLogService = securityLogService;
|
||||
BucketService = bucketService;
|
||||
}
|
||||
|
||||
[HttpGet("images/{name}")]
|
||||
@@ -26,6 +30,7 @@ public class ResourcesController : Controller
|
||||
{
|
||||
x.Add<string>(name);
|
||||
});
|
||||
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
@@ -38,4 +43,33 @@ public class ResourcesController : Controller
|
||||
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
[HttpGet("bucket/{bucket}/{name}")]
|
||||
public async Task<ActionResult> GetBucket([FromRoute] string bucket, [FromRoute] string name)
|
||||
{
|
||||
if (name.Contains(".."))
|
||||
{
|
||||
await SecurityLogService.Log(SecurityLogType.PathTransversal, x =>
|
||||
{
|
||||
x.Add<string>(name);
|
||||
});
|
||||
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var fs = await BucketService.GetFile(bucket, name);
|
||||
|
||||
return File(fs, MimeTypes.GetMimeType(name), name);
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return Problem();
|
||||
}
|
||||
}
|
||||
}
|
||||
68
Moonlight/App/Services/BucketService.cs
Normal file
68
Moonlight/App/Services/BucketService.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using Logging.Net;
|
||||
using Moonlight.App.Helpers;
|
||||
|
||||
namespace Moonlight.App.Services;
|
||||
|
||||
public class BucketService
|
||||
{
|
||||
private string BucketPath;
|
||||
|
||||
public BucketService()
|
||||
{
|
||||
BucketPath = PathBuilder.Dir("storage", "uploads");
|
||||
}
|
||||
|
||||
public Task<string[]> GetBuckets()
|
||||
{
|
||||
var buckets = Directory.GetDirectories(BucketPath)
|
||||
.Select(x =>
|
||||
x.Replace(BucketPath, "").TrimEnd('/')
|
||||
)
|
||||
.ToArray();
|
||||
|
||||
return Task.FromResult(buckets);
|
||||
}
|
||||
|
||||
private Task EnsureBucket(string name)
|
||||
{
|
||||
Directory.CreateDirectory(BucketPath + name);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task<string> StoreFile(string bucket, Stream dataStream, string? name = null)
|
||||
{
|
||||
await EnsureBucket(bucket);
|
||||
|
||||
var extension = "";
|
||||
|
||||
if (name != null)
|
||||
extension = Path.GetExtension(name);
|
||||
|
||||
var fileName = Path.GetRandomFileName() + extension; //TODO: Add check for existing file
|
||||
var filePath = BucketPath + PathBuilder.File(bucket, fileName);
|
||||
|
||||
var fileStream = File.Create(filePath);
|
||||
|
||||
await dataStream.CopyToAsync(fileStream);
|
||||
await fileStream.FlushAsync();
|
||||
|
||||
fileStream.Close();
|
||||
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public Task<Stream> GetFile(string bucket, string file)
|
||||
{
|
||||
var filePath = BucketPath + PathBuilder.File(bucket, file);
|
||||
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
var stream = File.Open(filePath, FileMode.Open);
|
||||
|
||||
return Task.FromResult<Stream>(stream);
|
||||
}
|
||||
else
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
}
|
||||
@@ -20,4 +20,9 @@ public class ResourceService
|
||||
{
|
||||
return $"{AppUrl}/api/moonlight/avatar/{user.Id}";
|
||||
}
|
||||
|
||||
public string BucketItem(string bucket, string name)
|
||||
{
|
||||
return $"{AppUrl}/api/moonlight/resources/bucket/{bucket}/{name}";
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,16 @@
|
||||
using Moonlight.App.Database.Entities;
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
using Moonlight.App.Database.Entities;
|
||||
using Moonlight.App.Events;
|
||||
using Moonlight.App.Services.Sessions;
|
||||
|
||||
namespace Moonlight.App.Services.SupportChat;
|
||||
|
||||
public class SupportChatAdminService
|
||||
public class SupportChatAdminService : IDisposable
|
||||
{
|
||||
private readonly EventSystem Event;
|
||||
private readonly IdentityService IdentityService;
|
||||
private readonly SupportChatServerService ServerService;
|
||||
private readonly BucketService BucketService;
|
||||
|
||||
public Func<SupportChatMessage, Task>? OnMessage { get; set; }
|
||||
public Func<string[], Task>? OnTypingChanged { get; set; }
|
||||
@@ -20,11 +22,13 @@ public class SupportChatAdminService
|
||||
public SupportChatAdminService(
|
||||
EventSystem eventSystem,
|
||||
SupportChatServerService serverService,
|
||||
IdentityService identityService)
|
||||
IdentityService identityService,
|
||||
BucketService bucketService)
|
||||
{
|
||||
Event = eventSystem;
|
||||
ServerService = serverService;
|
||||
IdentityService = identityService;
|
||||
BucketService = bucketService;
|
||||
}
|
||||
|
||||
public async Task Start(User recipient)
|
||||
@@ -60,11 +64,21 @@ public class SupportChatAdminService
|
||||
return await ServerService.GetMessages(Recipient);
|
||||
}
|
||||
|
||||
public async Task<SupportChatMessage> SendMessage(string content)
|
||||
public async Task<SupportChatMessage> SendMessage(string content, IBrowserFile? browserFile = null)
|
||||
{
|
||||
if (User != null)
|
||||
{
|
||||
return await ServerService.SendMessage(Recipient, content, User);
|
||||
string? attachment = null;
|
||||
|
||||
if (browserFile != null)
|
||||
{
|
||||
attachment = await BucketService.StoreFile(
|
||||
"supportChat",
|
||||
browserFile.OpenReadStream(1024 * 1024 * 5),
|
||||
browserFile.Name);
|
||||
}
|
||||
|
||||
return await ServerService.SendMessage(Recipient, content, User, attachment);
|
||||
}
|
||||
|
||||
return null!;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Logging.Net;
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
using Moonlight.App.Database.Entities;
|
||||
using Moonlight.App.Events;
|
||||
using Moonlight.App.Services.Sessions;
|
||||
@@ -8,6 +9,7 @@ namespace Moonlight.App.Services.SupportChat;
|
||||
public class SupportChatClientService : IDisposable
|
||||
{
|
||||
private readonly EventSystem Event;
|
||||
private readonly BucketService BucketService;
|
||||
private readonly IdentityService IdentityService;
|
||||
private readonly SupportChatServerService ServerService;
|
||||
|
||||
@@ -20,11 +22,13 @@ public class SupportChatClientService : IDisposable
|
||||
public SupportChatClientService(
|
||||
EventSystem eventSystem,
|
||||
SupportChatServerService serverService,
|
||||
IdentityService identityService)
|
||||
IdentityService identityService,
|
||||
BucketService bucketService)
|
||||
{
|
||||
Event = eventSystem;
|
||||
ServerService = serverService;
|
||||
IdentityService = identityService;
|
||||
BucketService = bucketService;
|
||||
}
|
||||
|
||||
public async Task Start()
|
||||
@@ -59,11 +63,21 @@ public class SupportChatClientService : IDisposable
|
||||
return await ServerService.GetMessages(User);
|
||||
}
|
||||
|
||||
public async Task<SupportChatMessage> SendMessage(string content)
|
||||
public async Task<SupportChatMessage> SendMessage(string content, IBrowserFile? browserFile = null)
|
||||
{
|
||||
if (User != null)
|
||||
{
|
||||
return await ServerService.SendMessage(User, content, User);
|
||||
string? attachment = null;
|
||||
|
||||
if (browserFile != null)
|
||||
{
|
||||
attachment = await BucketService.StoreFile(
|
||||
"supportChat",
|
||||
browserFile.OpenReadStream(1024 * 1024 * 5),
|
||||
browserFile.Name);
|
||||
}
|
||||
|
||||
return await ServerService.SendMessage(User, content, User, attachment);
|
||||
}
|
||||
|
||||
return null!;
|
||||
|
||||
@@ -108,6 +108,7 @@ namespace Moonlight
|
||||
builder.Services.AddScoped<FileDownloadService>();
|
||||
builder.Services.AddScoped<ForgeService>();
|
||||
builder.Services.AddScoped<FabricService>();
|
||||
builder.Services.AddSingleton<BucketService>();
|
||||
|
||||
builder.Services.AddScoped<GoogleOAuth2Service>();
|
||||
builder.Services.AddScoped<DiscordOAuth2Service>();
|
||||
|
||||
61
Moonlight/Shared/Components/Forms/SmartFileSelect.razor
Normal file
61
Moonlight/Shared/Components/Forms/SmartFileSelect.razor
Normal file
@@ -0,0 +1,61 @@
|
||||
@using Moonlight.App.Helpers.Files
|
||||
@using Moonlight.App.Services
|
||||
@using Moonlight.App.Services.Interop
|
||||
@using Logging.Net
|
||||
|
||||
@inject ToastService ToastService
|
||||
@inject SmartTranslateService SmartTranslateService
|
||||
|
||||
<InputFile OnChange="OnFileChanged" type="file" id="fileUpload" hidden=""/>
|
||||
<label for="fileUpload" class="btn btn-primary me-3">
|
||||
@if (SelectedFile != null)
|
||||
{
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" value="@(SelectedFile.Name)">
|
||||
<button class="btn btn-danger" type="button" @onclick="RemoveSelection">
|
||||
<i class="bx bx-md bx-x"></i>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="svg-icon svg-icon-2">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path opacity="0.3" d="M10 4H21C21.6 4 22 4.4 22 5V7H10V4Z" fill="currentColor"></path>
|
||||
<path d="M10.4 3.60001L12 6H21C21.6 6 22 6.4 22 7V19C22 19.6 21.6 20 21 20H3C2.4 20 2 19.6 2 19V4C2 3.4 2.4 3 3 3H9.20001C9.70001 3 10.2 3.20001 10.4 3.60001ZM16 11.6L12.7 8.29999C12.3 7.89999 11.7 7.89999 11.3 8.29999L8 11.6H11V17C11 17.6 11.4 18 12 18C12.6 18 13 17.6 13 17V11.6H16Z" fill="currentColor"></path>
|
||||
<path opacity="0.3" d="M11 11.6V17C11 17.6 11.4 18 12 18C12.6 18 13 17.6 13 17V11.6H11Z" fill="currentColor"></path>
|
||||
</svg>
|
||||
</span>
|
||||
}
|
||||
</label>
|
||||
|
||||
@code
|
||||
{
|
||||
public IBrowserFile? SelectedFile { get; set; }
|
||||
|
||||
private async Task OnFileChanged(InputFileChangeEventArgs arg)
|
||||
{
|
||||
if (arg.FileCount > 0)
|
||||
{
|
||||
if (arg.File.Size < 1024 * 1024 * 5)
|
||||
{
|
||||
SelectedFile = arg.File;
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
return;
|
||||
}
|
||||
|
||||
await ToastService.Error(SmartTranslateService.Translate("The uploaded file should not be bigger than 5MB"));
|
||||
}
|
||||
|
||||
SelectedFile = null;
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
public async Task RemoveSelection()
|
||||
{
|
||||
SelectedFile = null;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,15 @@
|
||||
@using Moonlight.App.Repositories
|
||||
@using Moonlight.App.Services
|
||||
@using Moonlight.App.Services.SupportChat
|
||||
@using System.Text.RegularExpressions
|
||||
|
||||
@inject SupportChatAdminService AdminService
|
||||
@inject UserRepository UserRepository
|
||||
@inject SmartTranslateService SmartTranslateService
|
||||
@inject ResourceService ResourceService
|
||||
|
||||
@implements IDisposable
|
||||
|
||||
<OnlyAdmin>
|
||||
<LazyLoader Load="Load">
|
||||
@if (User == null)
|
||||
@@ -43,7 +46,9 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<span><TL>System</TL></span>
|
||||
<span>
|
||||
<TL>System</TL>
|
||||
</span>
|
||||
}
|
||||
</a>
|
||||
</div>
|
||||
@@ -59,7 +64,26 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
@(message.Content)
|
||||
foreach (var line in message.Content.Split("\n"))
|
||||
{
|
||||
@(line)<br/>
|
||||
}
|
||||
|
||||
if (message.Attachment != "")
|
||||
{
|
||||
<div class="mt-3">
|
||||
@if (Regex.IsMatch(message.Attachment, @"\.(jpg|jpeg|png|gif|bmp)$"))
|
||||
{
|
||||
<img src="@(ResourceService.BucketItem("supportChat", message.Attachment))" class="img-fluid" alt="Attachment"/>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a class="btn btn-secondary" href="@(ResourceService.BucketItem("supportChat", message.Attachment))">
|
||||
<i class="me-2 bx bx-download"></i> @(message.Attachment)
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -82,7 +106,26 @@
|
||||
</div>
|
||||
|
||||
<div class="p-5 rounded bg-light-info text-dark fw-semibold mw-lg-400px text-start">
|
||||
@(message.Content)
|
||||
@foreach (var line in message.Content.Split("\n"))
|
||||
{
|
||||
@(line)<br/>
|
||||
}
|
||||
|
||||
@if (message.Attachment != "")
|
||||
{
|
||||
<div class="mt-3">
|
||||
@if (Regex.IsMatch(message.Attachment, @"\.(jpg|jpeg|png|gif|bmp)$"))
|
||||
{
|
||||
<img src="@(ResourceService.BucketItem("supportChat", message.Attachment))" class="img-fluid" alt="Attachment"/>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a class="btn btn-secondary" href="@(ResourceService.BucketItem("supportChat", message.Attachment))">
|
||||
<i class="me-2 bx bx-download"></i> @(message.Attachment)
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -118,11 +161,9 @@
|
||||
<div class="d-flex flex-stack">
|
||||
<table class="w-100">
|
||||
<tr>
|
||||
<!--<td class="align-top">
|
||||
<button class="btn btn-sm btn-icon btn-active-light-primary me-1" type="button">
|
||||
<i class="bx bx-upload fs-3"></i>
|
||||
</button>
|
||||
</td>-->
|
||||
<td class="align-top">
|
||||
<SmartFileSelect @ref="SmartFileSelect"></SmartFileSelect>
|
||||
</td>
|
||||
<td class="w-100">
|
||||
<textarea @bind="Content" @oninput="OnTyping" class="form-control mb-3 form-control-flush" rows="1" placeholder="Type a message">
|
||||
</textarea>
|
||||
@@ -186,6 +227,8 @@
|
||||
private string Content = "";
|
||||
private DateTime LastTypingTimestamp = DateTime.UtcNow.AddMinutes(-10);
|
||||
|
||||
private SmartFileSelect SmartFileSelect;
|
||||
|
||||
private async Task Load(LazyLoader arg)
|
||||
{
|
||||
User = UserRepository
|
||||
@@ -224,12 +267,17 @@
|
||||
|
||||
private async Task Send()
|
||||
{
|
||||
if (SmartFileSelect.SelectedFile != null && string.IsNullOrEmpty(Content))
|
||||
Content = "File upload";
|
||||
|
||||
if (string.IsNullOrEmpty(Content))
|
||||
return;
|
||||
|
||||
var message = await AdminService.SendMessage(Content);
|
||||
var message = await AdminService.SendMessage(Content, SmartFileSelect.SelectedFile);
|
||||
Content = "";
|
||||
|
||||
await SmartFileSelect.RemoveSelection();
|
||||
|
||||
Messages.Insert(0, message);
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
@@ -249,4 +297,9 @@
|
||||
await AdminService.SendTyping();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
AdminService?.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -4,18 +4,21 @@
|
||||
@using Moonlight.App.Helpers
|
||||
@using Moonlight.App.Services.SupportChat
|
||||
@using Logging.Net
|
||||
@using System.Text.RegularExpressions
|
||||
|
||||
@inject ResourceService ResourceService
|
||||
@inject SupportChatClientService ClientService
|
||||
@inject SmartTranslateService SmartTranslateService
|
||||
|
||||
@implements IDisposable
|
||||
|
||||
<LazyLoader Load="Load">
|
||||
<div class="row">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<LazyLoader Load="LoadMessages">
|
||||
<div class="scroll-y me-n5 pe-5" style="max-height: 55vh; display: flex; flex-direction: column-reverse;">
|
||||
@foreach (var message in Messages)
|
||||
@foreach (var message in Messages.ToArray())
|
||||
{
|
||||
if (message.Sender == null || message.Sender.Id != User.Id)
|
||||
{
|
||||
@@ -33,7 +36,9 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<span><TL>System</TL></span>
|
||||
<span>
|
||||
<TL>System</TL>
|
||||
</span>
|
||||
}
|
||||
</a>
|
||||
<span class="text-muted fs-7 mb-1">@(Formatter.FormatAgoFromDateTime(message.CreatedAt, SmartTranslateService))</span>
|
||||
@@ -47,7 +52,26 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
@(message.Content)
|
||||
foreach (var line in message.Content.Split("\n"))
|
||||
{
|
||||
@(line)<br/>
|
||||
}
|
||||
|
||||
if (message.Attachment != "")
|
||||
{
|
||||
<div class="mt-3">
|
||||
@if (Regex.IsMatch(message.Attachment, @"\.(jpg|jpeg|png|gif|bmp)$"))
|
||||
{
|
||||
<img src="@(ResourceService.BucketItem("supportChat", message.Attachment))" class="img-fluid" alt="Attachment"/>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a class="btn btn-secondary" href="@(ResourceService.BucketItem("supportChat", message.Attachment))">
|
||||
<i class="me-2 bx bx-download"></i> @(message.Attachment)
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -70,7 +94,26 @@
|
||||
</div>
|
||||
|
||||
<div class="p-5 rounded bg-light-primary text-dark fw-semibold mw-lg-400px text-end">
|
||||
@(message.Content)
|
||||
@foreach (var line in message.Content.Split("\n"))
|
||||
{
|
||||
@(line)<br/>
|
||||
}
|
||||
|
||||
@if (message.Attachment != "")
|
||||
{
|
||||
<div class="mt-3">
|
||||
@if (Regex.IsMatch(message.Attachment, @"\.(jpg|jpeg|png|gif|bmp)$"))
|
||||
{
|
||||
<img src="@(ResourceService.BucketItem("supportChat", message.Attachment))" class="img-fluid" alt="Attachment"/>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a class="btn btn-secondary" href="@(ResourceService.BucketItem("supportChat", message.Attachment))">
|
||||
<i class="me-2 bx bx-download"></i> @(message.Attachment)
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -84,7 +127,9 @@
|
||||
</div>
|
||||
<div class="ms-3">
|
||||
<a class="fs-5 fw-bold text-gray-900 text-hover-primary me-1">
|
||||
<span><TL>System</TL></span>
|
||||
<span>
|
||||
<TL>System</TL>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -108,11 +153,15 @@
|
||||
</div>
|
||||
@if (Typing.Length > 1)
|
||||
{
|
||||
<span>@(Typing.Aggregate((current, next) => current + ", " + next)) <TL>are typing</TL></span>
|
||||
<span>
|
||||
@(Typing.Aggregate((current, next) => current + ", " + next)) <TL>are typing</TL>
|
||||
</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>@(Typing.First()) <TL>is typing</TL></span>
|
||||
<span>
|
||||
@(Typing.First()) <TL>is typing</TL>
|
||||
</span>
|
||||
}
|
||||
</span>
|
||||
}
|
||||
@@ -120,11 +169,9 @@
|
||||
<div class="d-flex flex-stack">
|
||||
<table class="w-100">
|
||||
<tr>
|
||||
<!--<td class="align-top">
|
||||
<button class="btn btn-sm btn-icon btn-active-light-primary me-1" type="button">
|
||||
<i class="bx bx-upload fs-3"></i>
|
||||
</button>
|
||||
</td>-->
|
||||
<td class="align-top">
|
||||
<SmartFileSelect @ref="SmartFileSelect"></SmartFileSelect>
|
||||
</td>
|
||||
<td class="w-100">
|
||||
<textarea @bind="Content" @oninput="OnTyping" class="form-control mb-3 form-control-flush" rows="1" placeholder="Type a message">
|
||||
</textarea>
|
||||
@@ -155,6 +202,8 @@
|
||||
private string Content = "";
|
||||
private DateTime LastTypingTimestamp = DateTime.UtcNow.AddMinutes(-10);
|
||||
|
||||
private SmartFileSelect SmartFileSelect;
|
||||
|
||||
private async Task Load(LazyLoader lazyLoader)
|
||||
{
|
||||
await lazyLoader.SetText("Starting chat client");
|
||||
@@ -188,12 +237,14 @@
|
||||
|
||||
private async Task Send()
|
||||
{
|
||||
if(string.IsNullOrEmpty(Content))
|
||||
return;
|
||||
if (SmartFileSelect.SelectedFile != null && string.IsNullOrEmpty(Content))
|
||||
Content = "File upload";
|
||||
|
||||
var message = await ClientService.SendMessage(Content);
|
||||
var message = await ClientService.SendMessage(Content, SmartFileSelect.SelectedFile);
|
||||
Content = "";
|
||||
|
||||
await SmartFileSelect.RemoveSelection();
|
||||
|
||||
Messages.Insert(0, message);
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
@@ -207,4 +258,13 @@
|
||||
await ClientService.SendTyping();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
ClientService?.Dispose();
|
||||
}
|
||||
|
||||
private void OnFileChange(InputFileChangeEventArgs obj)
|
||||
{
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user