Merge remote-tracking branch 'origin/develop' into SA-106-wishlists-cart-pages

This commit is contained in:
AndriiSyrotenko 2023-11-20 12:57:15 +00:00
commit 4f984d4136
23 changed files with 1358 additions and 585 deletions

View File

@ -0,0 +1,9 @@
namespace ShoppingAssistantWebClient.Web.Models.Enums;
public enum SearchEventType
{
Wishlist = 0,
Message = 1,
Suggestion = 2,
Product = 3
}

View File

@ -0,0 +1,6 @@
namespace ShoppingAssistantWebClient.Web.Models.Input;
public class MessageCreateDto
{
public required string Text { get; set; }
}

View File

@ -0,0 +1,18 @@
namespace ShoppingAssistantWebClient.Web.Models
{
public class Messages
{
public string Id { get; set; }
public string Text { get; set; }
public string Role { get; set; }
public string CreatedById { get; set; }
}
}

View File

@ -1,22 +1,22 @@
namespace ShoppingAssistantWebClient.Web.Models; namespace ShoppingAssistantWebClient.Web.Models;
public class Product public class Product
{ {
public required string Id {get; set;} public required string Id {get; set;}
public required string Url {get; set;} public required string Url {get; set;}
public required string Name {get; set;} public required string Name {get; set;}
public required string Description {get; set;} public required string Description {get; set;}
public required double Rating {get; set;} public required double Rating {get; set;}
public required double Price { get; set; } public required double Price { get; set; }
public required string[] ImagesUrls {get; set;} public required string[] ImagesUrls {get; set;}
public required bool WasOpened {get; set;} public required bool WasOpened {get; set;}
public required string WishlistId {get; set;} public required string WishlistId {get; set;}
} }

View File

@ -0,0 +1,10 @@
using ShoppingAssistantWebClient.Web.Models.Enums;
namespace ShoppingAssistantWebClient.Web.Models.ProductSearch;
public class ServerSentEvent
{
public SearchEventType Event { get; set; }
public string Data { get; set; }
}

View File

@ -3,6 +3,9 @@ using GraphQL;
using Newtonsoft.Json; using Newtonsoft.Json;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using ShoppingAssistantWebClient.Web.Models.GlobalInstances; using ShoppingAssistantWebClient.Web.Models.GlobalInstances;
using ShoppingAssistantWebClient.Web.Models.ProductSearch;
using System.Text;
using ShoppingAssistantWebClient.Web.Models.Enums;
namespace ShoppingAssistantWebClient.Web.Network; namespace ShoppingAssistantWebClient.Web.Network;
@ -93,6 +96,55 @@ public class ApiClient
return model; return model;
} }
public async IAsyncEnumerable<ServerSentEvent> GetServerSentEventStreamed(string url, Object obj, CancellationToken cancellationToken)
{
await SetAuthenticationAsync();
var count = 0; //
var requestUrl = $"{_httpClient.BaseAddress}{url}";
var response = await _httpClient.PostAsJsonAsync(requestUrl, obj);
using var responseStream = await response.Content.ReadAsStreamAsync();
using var reader = new StreamReader(responseStream, Encoding.UTF8);
SearchEventType eventType = SearchEventType.Message;
while (!cancellationToken.IsCancellationRequested)
{
var jsonChunk = await reader.ReadLineAsync(cancellationToken);
count += 1; //
if (count >=5 ){ //
break; //
}; //
if (jsonChunk == null) continue;
if (jsonChunk.StartsWith("event: "))
{
count = 0; //
var type = jsonChunk.Substring("event: ".Length);
switch(type)
{
case "Message":
eventType = SearchEventType.Message;
break;
case "Suggestion":
eventType = SearchEventType.Suggestion;
break;
case "Product":
eventType = SearchEventType.Product;
break;
case "Wishlist":
eventType = SearchEventType.Wishlist;
break;
}
}
if (jsonChunk.StartsWith("data: "))
{
yield return new ServerSentEvent
{
Event = eventType,
Data = jsonChunk.Substring("data: ".Length),
};
}
}
}
private MultipartFormDataContent MapIFormCollectionToForm(IFormCollection formCollection) private MultipartFormDataContent MapIFormCollectionToForm(IFormCollection formCollection)
{ {
var formDataContent = new MultipartFormDataContent(); var formDataContent = new MultipartFormDataContent();

View File

@ -1,133 +1,139 @@
@page "/chat/1" @page "/chat/{chatId}"
<div class="container-fluid"> @inject IHttpClientFactory ClientFactory
@inject IJSRuntime JSRuntime
<div class="left_frame" id="leftFrame">
<PageTitle>@name</PageTitle>
<div class="logo"> <div class="right_frame" id="rightFrame">
<img src="/images/logo.png" alt="Logo site">
<span class="logo_name">CARTAID</span> <div id="button_open" class="open_menu">
</div> <a class="button_open_menu">
<span></span>
<div class="menu"> <span></span>
<span></span>
<div class="elements_wishlisht"> </a>
</div>
<div class="close_menu">
<span >Wishlists</span>
<a class="button_close_menu" > <div class="new_chat">
<span></span>
<span></span> <div class="title_one_frame">@name</div>
<span></span>
</a> <div class="chat_message" @ref="chatMessageRef">
</div>
<ul class="chat_box">
<div class="add_chat">
<a href="/"> @if (!isLoading && Messages != null)
<span >Add chat +</span> {
</a>
</div> @foreach (var item in Messages)
{
<div class="wishlist_names">
<section> if (item.Role != "User")
<span id ="wishlist_id_1" class="wishlist_name">Gift for Jessica</span> {
</section>
<li class=" chat_incoming">
<section> <p>@item.Text</p>
<span id ="wishlist_id_2" class="wishlist_name">Secret Santa</span> </li>
</section>
}
<section> else
<span id ="wishlist_id_3" class="wishlist_name">Phone charger</span> {
</section>
<li class="chat_outgoing">
<section> <p>@item.Text</p>
<span class="wishlist_name">Phone</span> </li>
</section>
}
<section> }
<span class="wishlist_name">Printer</span>
</section> }
<section> </ul>
<span class="wishlist_name">Books</span>
</section> </div>
</div>
</div> <div class="possible_options">
<div class="line"></div>
<div class="elements"> @if (Suggestion.Count != 0)
<div class="info_user"> {
<img src="/images/avatar.jpg" alt="Avatar user">
<span class="user_name">Username</span> <div class="tite_options">Several possible options</div>
</div>
<div class="options">
</div>
</div> @foreach (var item in Suggestion)
{
</div> <div @onclick="() => ClickOption(item)" class="topic_options">
@item
<div class="right_frame" id="rightFrame"> </div>
}
<div class="open_menu">
<a class="button_open_menu" >
<span></span>
<span></span> </div>
<span></span> }
</a>
</div> </div>
<div class="chat_frame"> <div class="chat_input">
<input @bind="inputValue" @onkeydown="Enter" class="input_messages" type="text" id="chatInput"
<div class="new_chat"> placeholder="Describe what you are looking for....">
<img @onclick="AddNewMessage" class="button_sende" src="/images/send.svg" alt="Send message">
<div class="title_one_frame">Gift for Jessica</div> </div>
<ul class="chat_box"> </div>
<li class="chat_outgoing"> </div>
<p>Give me product recommendation. Ask me questions if you need more directions. I am looking for: hub for my macbook to connect external monitors</p>
</li>
<li class=" chat_incoming"> <script>
<p>Sure! I can help you with that. I will ask you some leading questions. This is the first:
<br> window.scrollToBottom = function (element) {
How many external monitors do you want to connect to your MacBook?</p> if (element) {
</li> element.scrollTop = 9999;
<li class="chat_outgoing">
<p>7</p> }
</li> };
<li class=" chat_incoming">
<p>Thank you. Here is the next question: document.getElementById('button_open').addEventListener('click', changetyle);
<br>
What type of external monitors do you have? (e.g., HDMI, DisplayPort, VGA)</p> </script>
</li> @code {
</ul> [Parameter] public string chatId { get; set; }
protected override async Task OnParametersSetAsync()
{
await LoadMessages();
<div class="chat_input"> }
<input type="text" id="chatInput" placeholder="Describe what you are looking for...."> public void Enter(KeyboardEventArgs e)
{
<a class="button_sende"> if (e.Code == "Enter" || e.Code == "NumpadEnter")
<img src="/images/send.png" alt="Send message"> {
</a> AddNewMessage();
}
}
</div>
private ElementReference chatMessageRef;
</div> protected override async Task OnAfterRenderAsync(bool firstRender)
</div> {
await JSRuntime.InvokeVoidAsync("scrollToBottom", chatMessageRef);
</div> }
</div>
private void ClickOption(string item)
{
inputValue = item;
}
}

View File

@ -1,14 +1,169 @@
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using ShoppingAssistantWebClient.Web.Models;
namespace ShoppingAssistantWebClient.Web.Pages; using GraphQL;
using Newtonsoft.Json;
public partial class Chat : ComponentBase using ShoppingAssistantWebClient.Web.Network;
{ using ShoppingAssistantWebClient.Web.Models.Input;
[Inject] using ShoppingAssistantWebClient.Web.Models.Enums;
public ILogger<Chat> Logger { get; set; } using System.Text.RegularExpressions;
namespace ShoppingAssistantWebClient.Web.Pages;
protected override async Task OnInitializedAsync()
{ public partial class Chat : ComponentBase
// Get data from Back-end {
}
} [Inject]
private ApiClient _apiClient { get; set; }
[Inject]
private NavigationManager Navigation { get; set; }
public List<Messages> Messages { get; set; }
public List<String> Suggestion { get; set; } = new List<String>();
public Messages Message { get; set; }
private CancellationTokenSource cancelTokenSource;
private MessageCreateDto messageCreateDto;
public bool isLoading = true;
private string inputValue = "";
private string name = "";
protected override async Task OnInitializedAsync()
{
await LoadMessages();
}
private async Task LoadMessages()
{
try{
string wishlistId = chatId;
var request = new GraphQLRequest
{
Query = @"query PersonalWishlist( $wishlistId: String!) {
personalWishlist(wishlistId: $wishlistId) {
name
}
}",
Variables = new
{
wishlistId,
}
};
var response = await _apiClient.QueryAsync(request);
var responseData = response.Data;
name = responseData.personalWishlist.name;
isLoading = true;
int pageNumber = 1;
request = new GraphQLRequest
{
Query = @"query MessagesPageFromPersonalWishlist($wishlistId: String!, $pageNumber: Int!, $pageSize: Int!) {
messagesPageFromPersonalWishlist( wishlistId: $wishlistId, pageNumber: $pageNumber, pageSize: $pageSize)
{
items {
id
text
role
createdById
}
}
}",
Variables = new
{
wishlistId,
pageNumber,
pageSize = 200
}
};
response = await _apiClient.QueryAsync(request);
responseData = response.Data;
var jsonCategoriesResponse = JsonConvert.SerializeObject(responseData.messagesPageFromPersonalWishlist.items);
this.Messages = JsonConvert.DeserializeObject<List<Messages>>(jsonCategoriesResponse);
Messages.Reverse();
isLoading = false;
}catch(Exception ex){
Console.WriteLine($"Error : {ex.Message}");
}
}
private async Task AddNewMessage()
{
try{
messageCreateDto = new MessageCreateDto { Text = inputValue };;
Message = new Messages();
Message.Text = inputValue;
Message.Role = "User";
Message.Id = "";
Message.CreatedById = "";
inputValue = "";
Suggestion = new List<String>();
Messages.Add(Message);
StateHasChanged();
cancelTokenSource = new CancellationTokenSource();
var cancellationToken = cancelTokenSource.Token;
var serverSentEvent = _apiClient.GetServerSentEventStreamed($"ProductsSearch/search/{chatId}", messageCreateDto, cancellationToken);
bool first = true;
await foreach (var sseEvent in serverSentEvent.WithCancellation(cancellationToken))
{
Console.WriteLine($"Received SSE Event: {sseEvent.Event}, Data: {sseEvent.Data}");
if(sseEvent.Event == SearchEventType.Message){
string input = sseEvent.Data;
Regex regex = new Regex("\"(.*?)\"");
Match match = regex.Match(input);
string result = match.Groups[1].Value;
Message = new Messages();
Message.Text = result;
Message.Role = "bot";
Message.Id = "";
Message.CreatedById = "";
if (first)
{
Messages.Add(Message);
first = false;
}
else
{
var lengt = Messages.Count();
Messages[lengt-1].Text += Message.Text;
}
StateHasChanged();
}else if(sseEvent.Event == SearchEventType.Product){
var url = $"/chat/{chatId}/product";
Navigation.NavigateTo(url);
}else if(sseEvent.Event == SearchEventType.Suggestion){
Suggestion.Add(sseEvent.Data);
}
}
}catch(Exception ex){
Console.WriteLine($"Error : {ex.Message}");
}
}
}

View File

@ -1,330 +1,139 @@
html { .right_frame {
font-family: 'Nunito'; position: relative;
padding-top: 20px; border: 0.09em solid;
padding-bottom: 20px; border-color: #0052CC;
} border-radius: 0.6em;
.menu { height: 100%;
position: relative; width: 100%;
width: 100%; }
height: 100%;
border: 1.5px solid;
border-color: #0052CC;
border-radius: 10px;
padding-top: 16px;
padding-bottom: 16px;
}
.logo { .button_open_menu {
display: flex; z-index: 2;
align-items: center; width: 1.43em;
padding-bottom: 15PX; height: 1.23em;
} position: absolute;
top: 1.56em;
.logo_name { left: 1.56em;
padding-top: 10px; cursor: pointer;
padding-left: 3px; visibility: hidden;
font-size: 17px; }
justify-content: center;
/* Горизонтальное центрирование */ .button_open_menu span {
align-items: center; width: 20px;
letter-spacing: 0.5px; height: 1.5px;
font-weight: 600; position: absolute;
top: 50%;
} left: 50%;
transform: translate(-50%, -50%);
.logo img { background-color: #4E4E4E;
float: left; }
width: 52px;
height: 52px; .button_open_menu span:nth-of-type(2) {
} top: calc(50% - 5px);
}
.left_frame {
padding-left: 20px; .button_open_menu span:nth-of-type(3) {
position: absolute; top: calc(50% + 5px);
height: calc(100% - 105px); }
left: 0;
width: 20%; .title_one_frame {
transition: 1s; padding-top: 1.25em;
} color: #0052CC;
font-size: 1.0625em;
text-align: center;
.wishlist_name { }
font-size: 15px;
margin-top: 7px; .chat_input {
margin-bottom: 7px; background-color: #EAEAEA;
color: black; position: absolute;
cursor: pointer; display: flex;
} align-items: center;
bottom: 2em;
.elements { margin-left: 25%;
padding-left: 12px; width: 50%;
padding-right: 12px; border-radius: 0.6em;
}
}
.elements_wishlisht{ .possible_options {
overflow-y: scroll; position: absolute;
padding-left: 12px; bottom: 5.5em;
padding-right: 12px; margin-left: 25%;
height: 90%; width: 50%;
border-radius: 0.6em;
} }
.elements_wishlisht::-webkit-scrollbar { .tite_options{
visibility: hidden; font-size: 0.9em;
} color: #ADADAD;
margin-bottom: 0.5em;
.close_menu { }
position: relative; .options{
background-color: #EAEAEA; justify-content: space-between;
border-radius: 10px; align-items: center;
color: #4E4E4E; font-size: 1em;
font-size: 16px; }
width: 90%; .topic_options
height: 40px; {
padding: 7px 9px; display: inline-block;
} padding: 0.5em;
border: 0.09em solid;
.add_chat {
display: flex;
align-items: center;
justify-content: center;
margin-top: 15px;
margin-bottom: 5px;
background-color: #EAEAEA;
border-radius: 10px;
font-size: 16px;
width: 100px;
height: 40px;
cursor: pointer;
}
.add_chat a{
border-radius: 10px;
padding: 7px 9px;
text-decoration: none;
color: #4E4E4E;
}
.info_user {
background-color: white;
position:absolute;
bottom: 0;
padding-bottom: 10px;
padding-top: 10px;
}
.user_name {
font-size: 16px;
padding-left: 7px;
}
.info_user img {
height: 40px;
width: 40px;
border-radius: 50%;
}
.line {
position: absolute;
bottom: 60px;
width: 100%;
border-bottom: 1px solid #4b7bc4;
}
.button_close_menu{
width: 23px;
height: 23px;
position: absolute;
right: 20px;
cursor: pointer;
}
.button_close_menu span {
width: 20px;
height: 1.5px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #4E4E4E;
}
.button_close_menu span:nth-of-type(2) {
top: calc(50% - 5px);
}
.button_close_menu span:nth-of-type(3) {
top: calc(50% + 5px);
}
.right_frame {
position: absolute;
border: 1.5px solid;
border-color: #0052CC;
border-radius: 10px;
left: 20px;
height: calc(100% - 40px);
width: calc(100% - 40px);
transition: 1s;
}
.button_open_menu{
z-index: 2;
width: 23px;
height: 23px;
position: absolute;
top: 25px;
left: 25px;
cursor: pointer;
}
.button_open_menu span {
width: 20px;
height: 1.5px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #4E4E4E;
}
.button_open_menu span:nth-of-type(2) {
top: calc(50% - 5px);
}
.button_open_menu span:nth-of-type(3) {
top: calc(50% + 5px);
}
.title_one_frame{
padding-top: 20px ;
color: #0052CC;
font-size: 17px;
text-align: center;
}
.title_two_frame{
padding-top: 60px ;
padding-bottom: 40px ;
color: black;
font-size: 40px;
text-align: center;
font-weight: 600;
}
.title_three_frame{
padding-top: 60px ;
padding-bottom: 40px ;
color: #202124;
font-size: 20px;
text-align: center;
font-weight: 400;
}
.topic {
font-size: 18px;
text-align: center;
line-height: 35px;
color: #009FFF;
width: 250px;
margin: 0 auto;
}
.topic div{
margin-top: 20px;
border: 1.5px solid;
border-color: #009FFF; border-color: #009FFF;
border-radius: 10px; border-radius: 0.6em;
justify-content: center; margin: 0em 0.6em;
align-items: center; flex: 1;
cursor: pointer;
}
.topic div a{
color: #009FFF;
text-decoration:none;
}
.switch{
height: 45px;
width: 350px;
margin: 0 auto;
border: 1.5px solid;
border-color: #EAEAEA;
border-radius: 10px;
text-align: center; text-align: center;
position: relative;
}
.switch_product{
position: absolute;
background-color: #0052CC;
border-radius: 10px;
margin: 5px;
width: calc(50% - 10px);
height: calc(100% - 10px);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer; cursor: pointer;
transition: 0.8s; }
color: white;
} .input_messages {
width: 100%;
.switch_gift{ height: 2.5em;
right: 0; font-size: 1.0625em;
position: absolute; background-color: #EAEAEA;
border-radius: 10px; color: #4E4E4E;
margin: 5px; border-radius: 0.6em;
width: calc(50% - 10px); border: none;
height: calc(100% - 10px); padding: 0.625em 1.25em;
display: flex; outline: none;
align-items: center; }
justify-content: center;
cursor: pointer; .button_sende {
transition: 0.8s; float: right;
color: #202124; cursor: pointer;
} line-height: 2.5em;
margin-right: 0.8em;
.chat_input{ width: 1.5em;
position: absolute; height: 1.4em;
margin-left: 25%; }
margin-bottom: 20px;
width: 50%; .new_chat {
border-radius: 10px; padding-top: 0.5em;
bottom: 0; position: relative;
height: 100%;
} width: 100%;
.chat_input input{ }
font-size: 17px;
width: 100%; .chat_message {
position: relative; position: relative;
color: #4E4E4E; overflow-y: auto;
background-color: #EAEAEA; height: calc(100% - 8em);
border-radius: 10px; width: 100%;
border: none; }
padding: 10px 20px; .chat_message::-webkit-scrollbar {
outline:none; border-radius: 20px;
} width: 0.2em;
.button_sende{ }
margin-top: 14px;
margin-right: 20px; .chat_message::-webkit-scrollbar-thumb {
cursor: pointer; background-color: #0052CC;
height: calc(100% - 28px); /* Колір позиції покажчика */
width: 2.8%; border-radius: 10px;
position: absolute; /* Закруглення країв позиції покажчика */
right: 0; width: 0.2em;
} }
.button_sende img{
height: 100%;
width: 100%;
position: absolute;
}
.new_chat{
position: absolute;
overflow-y: scroll;
height: 100%;
width: 100%;
}
.new_chat::-webkit-scrollbar {
visibility: hidden;
}
.chat_box{ .chat_box{
border-radius: 10px; border-radius: 10px;
position: absolute; position: absolute;

View File

@ -1,9 +1,136 @@
@page "/" @page "/"
<PageTitle>New Chat</PageTitle>
<PageTitle>CartaId</PageTitle> <div class="right_frame" id="rightFrame">
<h1>Hello, world!</h1> <div id="button_open" class="open_menu">
<a class="button_open_menu">
<span></span>
<span></span>
<span></span>
</a>
</div>
Welcome to your new app. @if(isLoading == false){
<SurveyPrompt Title="How is Blazor working for you?" /> <div class="new_chat">
<div class="chat_message">
<div class="title_one_frame">New chat</div>
<div class="title_two_frame">What you're looking for</div>
<div class="switch">
<div @onclick="Сhoose_product"class="switch_product" id="choose_product">
Product
</div>
<div @onclick="Сhoose_gift" class="switch_gift" id="choose_gift">
Gift
</div>
</div>
<div class="title_three_frame">What you're looking for, we will help you solve your problem and find it
</div>
<div class="topic">
<div class="topic_one">
<a class="button_topic_one">
Date
</a>
</div>
<div class="topic_two">
<a class="button_topic_two">
🎃 Halloween gift
</a>
</div>
<div class="topic_three">
<a class="button_topic_three">
🎁 Birthday gift
</a>
</div>
</div>
</div>
<div class="chat_input">
<input @bind="inputValue" class="input_messages" type="text" id="chatInput"
placeholder="Describe what you are looking for....">
<img @onclick="CreateNewChat" class="button_sende" src="/images/send.svg" alt="Send message">
</div>
</div>
}else{
<div class="new_chat">
<img class="loading" src="/images/loading.svg" alt="Loading chat">
</div>
}
</div>
<script>
var choose_gift = document.getElementById("choose_gift");
var choose_product = document.getElementById("choose_product");
var switchGi = document.querySelector(".switch_gift");
var switchProd = document.querySelector(".switch_product");
var choose = "Product";
function switchGift() {
choose_gift.style.backgroundColor = "#0052CC";
choose_product.style.backgroundColor = "transparent";
switchGi.style.color = "white";
switchProd.style.color = "#202124";
choose = "Gift";
}
function switchProduct() {
choose_product.style.backgroundColor = "#0052CC";
choose_gift.style.backgroundColor = "transparent";
switchProd.style.color = "white";
switchGi.style.color = "#202124";
choose = "Product";
}
function myJavaScriptFunction(wishlistId) {
UpdateMenu(wishlistId);
}
document.getElementById('choose_gift').addEventListener('click', switchGift);
document.getElementById('choose_product').addEventListener('click', switchProduct);
document.getElementById('button_open').addEventListener('click', changetyle);
</script>
@code{
private string selectedChoice = "Product";
private void Сhoose_product() {
selectedChoice = "Product";
}
private void Сhoose_gift() {
selectedChoice = "Gift";
}
private async Task UpdateSideMenu(string wishlistId)
{
await JSRuntime.InvokeVoidAsync("myJavaScriptFunction", wishlistId);
}
}

View File

@ -0,0 +1,120 @@
using Microsoft.AspNetCore.Components;
using ShoppingAssistantWebClient.Web.Models;
using ShoppingAssistantWebClient.Web.Models.ProductSearch;
using ShoppingAssistantWebClient.Web.Models.Input;
using GraphQL;
using Newtonsoft.Json;
using ShoppingAssistantWebClient.Web.Network;
using System;
using Microsoft.JSInterop;
namespace ShoppingAssistantWebClient.Web.Pages
{
public partial class Index : ComponentBase
{
[Inject]
private ApiClient _apiClient { get; set; }
[Inject]
private NavigationManager Navigation { get; set; }
[Inject]
protected IJSRuntime JSRuntime { get; set; }
private MessageCreateDto messageCreateDto;
private CancellationTokenSource cancelTokenSource;
private string inputValue = "";
public bool isLoading;
private async Task CreateNewChat() {
try
{
if (string.IsNullOrWhiteSpace(inputValue))
{
return;
}
isLoading = true;
StateHasChanged();
messageCreateDto = new MessageCreateDto { Text = inputValue };
var type = selectedChoice;
var firstMessageText = $"What are you looking for?";
var request = new GraphQLRequest
{
Query = @"
mutation StartPersonalWishlist($type: String!, $firstMessageText: String!) {
startPersonalWishlist(dto: { type: $type, firstMessageText: $firstMessageText }) {
id
}
}",
Variables = new
{
type,
firstMessageText
}
};
var response = await _apiClient.QueryAsync(request);
var responseData = response.Data;
var chatId = responseData?.startPersonalWishlist?.id;
string wishlistId1 = chatId;
var text = inputValue;
cancelTokenSource = new CancellationTokenSource();
var cancellationToken = cancelTokenSource.Token;
var serverSentEvent = _apiClient.GetServerSentEventStreamed($"ProductsSearch/search/{chatId}", messageCreateDto, cancellationToken);
await foreach (var sseEvent in serverSentEvent.WithCancellation(cancellationToken))
{
// Handle each ServerSentEvent as needed
Console.WriteLine($"Received SSE Event: {sseEvent.Event}, Data: {sseEvent.Data}");
}
string wishlistId = chatId;
request = new GraphQLRequest
{
Query = @"mutation GenerateNameForPersonalWishlist($wishlistId: String!) {
generateNameForPersonalWishlist(wishlistId: $wishlistId) {
id
name
}
}",
Variables = new
{
wishlistId
}
};
response = await _apiClient.QueryAsync(request);
isLoading = false;
StateHasChanged();
await UpdateSideMenu(wishlistId1);
var url = $"/chat/{chatId}";
Navigation.NavigateTo(url);
}
catch (Exception ex)
{
// Handle exceptions appropriately
Console.WriteLine($"Error in CreateNewChat: {ex.Message}");
}
finally
{
isLoading = false;
cancelTokenSource?.Dispose();
}
}
}
}

View File

@ -0,0 +1,196 @@
.right_frame {
position: relative;
border: 0.09em solid;
border-color: #0052CC;
border-radius: 0.6em;
height: 100%;
width: 100%;
}
.button_open_menu {
z-index: 2;
width: 1.43em;
height: 1.23em;
position: absolute;
top: 1.56em;
left: 1.56em;
cursor: pointer;
visibility: hidden;
}
.loading {
margin: 0 auto;
margin-top: 25%;
height: 10em;
width: 10em;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}
.button_open_menu span {
width: 20px;
height: 1.5px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #4E4E4E;
}
.button_open_menu span:nth-of-type(2) {
top: calc(50% - 5px);
}
.button_open_menu span:nth-of-type(3) {
top: calc(50% + 5px);
}
.title_one_frame {
padding-top: 1.25em;
color: #0052CC;
font-size: 1.0625em;
text-align: center;
}
.title_two_frame {
padding-top: 2.25em;
padding-bottom: 1.5em;
font-size: 2.5em;
text-align: center;
font-weight: 600;
}
.title_three_frame {
padding-top: 3.75em;
padding-bottom: 2.5em;
color: #202124;
font-size: 1.25em;
text-align: center;
font-weight: 400;
}
.topic {
font-size: 1.125em;
text-align: center;
line-height: 2.1875em;
color: #009FFF;
width: 15.625em;
margin: 0 auto;
}
.topic div {
margin-top: 1.25em;
border: 0.09em solid;
border-color: #009FFF;
border-radius: 0.6em;
justify-content: center;
align-items: center;
cursor: pointer;
}
.topic div a {
color: #009FFF;
text-decoration: none;
}
.switch {
height: 2.8125em;
width: 21.875em;
margin: 0 auto;
border: 0.09em solid;
border-color: #EAEAEA;
border-radius: 0.6em;
text-align: center;
position: relative;
}
.switch_product {
position: absolute;
background-color: #0052CC;
border-radius: 0.6em;
margin: 0.3125em;
width: calc(50% - 0.625em);
height: calc(100% - 0.625em);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: 0.8s;
color: white;
}
.switch_gift {
right: 0;
position: absolute;
border-radius: 0.6em;
margin: 0.3125em;
width: calc(50% - 0.625em);
height: calc(100% - 0.625em);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: 0.8s;
color: #202124;
}
.chat_message {
position: relative;
overflow-y: auto;
height: calc(100% - 5.5em);
width: 100%;
}
.chat_input {
background-color: #EAEAEA;
position: absolute;
display: flex;
align-items: center;
bottom: 2em;
margin-left: 25%;
width: 50%;
border-radius: 0.6em;
}
.input_messages {
width: 100%;
height: 2.5em;
font-size: 1.0625em;
background-color: #EAEAEA;
color: #4E4E4E;
border-radius: 0.6em;
border: none;
padding: 0.625em 1.25em;
outline: none;
}
.button_sende {
float: right;
cursor: pointer;
line-height: 2.5em;
margin-right: 0.8em;
width: 1.8em;
height: 1.7em;
}
.new_chat {
padding-top: 0.5em;
position: relative;
height: 100%;
width: 100%;
}
.chat_message::-webkit-scrollbar {
border-radius: 20px;
width: 0.2em;
}
.chat_message::-webkit-scrollbar-thumb {
background-color: #0052CC;
/* Колір позиції покажчика */
border-radius: 10px;
/* Закруглення країв позиції покажчика */
width: 0.2em;
}

View File

@ -1,5 +1,7 @@
using GraphQL.Client.Http;
using ShoppingAssistantWebClient.Web.Configurations; using ShoppingAssistantWebClient.Web.Configurations;
using ShoppingAssistantWebClient.Web.Data; using ShoppingAssistantWebClient.Web.Data;
using ShoppingAssistantWebClient.Web.Network;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);

View File

@ -1,6 +1,6 @@
@inherits LayoutComponentBase @inherits LayoutComponentBase
@using ShoppingAssistantWebClient.Web.Pages @using ShoppingAssistantWebClient.Web.Pages
<PageTitle>ShoppingAssistantWebClient.Web</PageTitle> <PageTitle>CARTAID</PageTitle>
<head> <head>
<link rel="stylesheet" href="css/MainLayout.css" /> <link rel="stylesheet" href="css/MainLayout.css" />
</head> </head>

View File

@ -0,0 +1,24 @@
.page {
position: relative;
width: 100%;
height: 100vh;
padding: 1.25em;
}
.sidebar-menu {
position: absolute;
width: 20em;
top: 1.25em;
bottom: 1.25em;
margin-right: 1.5em;
transition: 1s;
}
.right-frame {
position: absolute;
right: 1.25em;
left: 23.25em;
top: 1.25em;
bottom: 1.25em;
transition: 1s;
}

View File

@ -1,7 +1,11 @@
@using Models.GlobalInstances @using Models.GlobalInstances
@using System.Linq
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject NavigationManager Navigation @inject NavigationManager Navigation
<div id="leftframe" class="left_frame"> @inject IJSRuntime JSRuntime;
<div id="leftframe" class="left_frame">
<div class="logo"> <div class="logo">
<img src="/images/logo.svg" alt="Logo site"> <img src="/images/logo.svg" alt="Logo site">
@ -16,7 +20,8 @@
<span></span> <span></span>
</a> </a>
<div class="elements_wishlisht">
<div class="elements_wishlisht" id="elements_wishlisht" >
<div class="add_chat" @onclick="RedirectToNewChat" > <div class="add_chat" @onclick="RedirectToNewChat" >
<div> <div>
@ -27,22 +32,19 @@
<div class="wishlist_names"> <div class="wishlist_names">
@if(Wishlists!=null){ @if(!isLoading){
@foreach (var item in Wishlists) @foreach (var item in Wishlists)
{ {
<section class="cont_wishlist">
<div @onclick="() => RedirectToPage(item.Id)" class="wishlist_name" >@item.Name</div> <section class="cont_wishlist @(selectedWishlistId == item.Id ? "selected_wishlist" : "")">
<img @onclick="() => DeleteWishlist(item.Id)" class="button_delete_chat" src="/images/icon_delete.svg" alt="Delete wishlist"> <div class="wishlist_name" @onclick="() => RedirectToPage(item.Id)">@item.Name</div>
<img @onclick="() => RedirectToCard(item.Id)" class="button_open_card" src="/images/icon_open_card.svg" alt="Card open"> <img class="button_delete_chat" @onclick="() => DeleteWishlist(item.Id)" src="/images/icon_delete.svg" alt="Delete wishlist">
<img class="button_open_card" @onclick="() => RedirectToCard(item.Id)" src="/images/icon_open_card.svg" alt="Card open">
</section> </section>
} }
}else{
<div>loading ...</div>
} }
</div> </div>
</div> </div>
@ -51,15 +53,14 @@
<div class="elements"> <div class="elements">
<div class="info_user"> <div class="info_user">
<img src="/images/avatar.jpg" alt="Avatar user"> <img src="/images/avatar.jpg" alt="Avatar user">
<!-- Change to name -->
<span class="user_name">@GlobalUser.Id</span> <span class="user_name">@GlobalUser.Id</span>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
@ -85,24 +86,99 @@
} }
document.getElementById('button_close').addEventListener('click', changetyle); document.getElementById('button_close').addEventListener('click', changetyle);
/*
window.getScrollTop = function (element) {
return element.scrollTop;
};
window.getOffsetHeight = function (element) {
return element.offsetHeight;
};
window.getScrollHeight = function (element) {
return element.scrollHeight;
};
window.setScrollTop = function (element, scrollTop) {
element.scrollTop = scrollTop;
};
*/
</script>
<script>
function UpdateMenu(wishlistId)
{
DotNet.invokeMethodAsync('ShoppingAssistantWebClient.Web', 'Update', wishlistId);
}
</script> </script>
@code { @code {
private void RedirectToPage(string itemId) { [Parameter] public string chatId { get; set; }
var url = $"/chat/{itemId}";
Navigation.NavigateTo(url);
} private string selectedWishlistId;
private void RedirectToNewChat() {
var url = $"/";
Navigation.NavigateTo(url); private static NavMenu _app;
}
private void RedirectToCard(string itemId) { public NavMenu ()
var url = $"/chat/{itemId}/cart"; {
Navigation.NavigateTo(url); _app = this;
}
private void DeleteWishlist(string itemId) {
} }
[JSInvokable]
public static void Update(string wishlistId)
{
_app.LoadMenus(1,200);
_app.selectedWishlistId=wishlistId;
}
private void RedirectToPage(string itemId) {
_app.selectedWishlistId = itemId;
var url = $"/chat/{itemId}";
Navigation.NavigateTo(url);
}
private void RedirectToNewChat() {
var url = $"/";
Navigation.NavigateTo(url);
}
private void RedirectToCard(string itemId) {
var url = $"/chat/{itemId}/cart";
Navigation.NavigateTo(url);
}
private async void DeleteWishlist(string itemId) {
await DeleteWish(itemId);
}
/*
private int savedScrollTop = 0;
private ElementReference wishlishtRef;
private async Task OnScroll()
{
var scrollTop = await JSRuntime.InvokeAsync<int>("getScrollTop", wishlishtRef);
var offsetHeight = await JSRuntime.InvokeAsync<int>("getOffsetHeight", wishlishtRef);
var scrollHeight = await JSRuntime.InvokeAsync<int>("getScrollHeight", wishlishtRef);
if (scrollTop + offsetHeight > scrollHeight - 100)
{
savedScrollTop = scrollTop;
currentPage++;
await LoadMenus(currentPage, pageSize);
await InvokeAsync(() => JSRuntime.InvokeVoidAsync("setScrollTop", wishlishtRef, savedScrollTop));
}
}
*/
} }

View File

@ -2,7 +2,6 @@ using Microsoft.AspNetCore.Components;
using ShoppingAssistantWebClient.Web.Models; using ShoppingAssistantWebClient.Web.Models;
using GraphQL; using GraphQL;
using Newtonsoft.Json; using Newtonsoft.Json;
using ShoppingAssistantWebClient.Web.Models;
using ShoppingAssistantWebClient.Web.Network; using ShoppingAssistantWebClient.Web.Network;
namespace ShoppingAssistantWebClient.Web.Shared namespace ShoppingAssistantWebClient.Web.Shared
@ -10,7 +9,13 @@ namespace ShoppingAssistantWebClient.Web.Shared
public partial class NavMenu : ComponentBase public partial class NavMenu : ComponentBase
{ {
[Inject]
private ApiClient _apiClient { get; set; }
public List<Wishlist> Wishlists { get; set; } public List<Wishlist> Wishlists { get; set; }
public bool isLoading = true;
public int pageSize { get; set; }
public int currentPage { get; set; }
private readonly ApiClient _apiClient; private readonly ApiClient _apiClient;
@ -24,27 +29,77 @@ namespace ShoppingAssistantWebClient.Web.Shared
} }
public async Task OnGetAsync() public async Task OnGetAsync()
{ {
var request = new GraphQLRequest pageSize = 200;
{ currentPage = 1;
Query = @"query PersonalWishlistsPage { Wishlists = new List<Wishlist>();
personalWishlistsPage(pageNumber: $pageNumber, pageSize: $pageSize) { await LoadMenus(currentPage, pageSize);
items {
}
public async Task LoadMenus(int pageNumber, int pageSize )
{
try{
isLoading = true;
var request = new GraphQLRequest
{
Query = @"query PersonalWishlistsPage( $pageNumber: Int!, $pageSize: Int!) {
personalWishlistsPage(pageNumber: $pageNumber, pageSize: $pageSize) {
items {
id
name
}
}
}",
Variables = new
{
pageNumber,
pageSize,
}
};
var response = await _apiClient.QueryAsync(request);
var responseData = response.Data;
var jsonCategoriesResponse = JsonConvert.SerializeObject(responseData.personalWishlistsPage.items);
this.Wishlists = JsonConvert.DeserializeObject<List<Wishlist>>(jsonCategoriesResponse);
Wishlists.Reverse();
isLoading = false;
StateHasChanged();
}catch(Exception ex){
Console.WriteLine($"Error : {ex.Message}");
}
}
protected async Task DeleteWish(string wishlistId)
{
try{
var request = new GraphQLRequest
{
Query = @"mutation DeletePersonalWishlist($wishlistId: String!) {
deletePersonalWishlist(wishlistId: $wishlistId) {
id id
name
} }
} }
} ",
",
Variables = new Variables = new
{ {
pageNumber = 1, wishlistId
pageSize = 10, }
} };
};
var response = await _apiClient.QueryAsync(request); var response = await _apiClient.QueryAsync(request);
var jsonCategoriesResponse = JsonConvert.SerializeObject(response.Data.personalWishlistsPage.items); await LoadMenus(currentPage, pageSize);
this.Wishlists = JsonConvert.DeserializeObject<List<Wishlist>>(jsonCategoriesResponse); var url = $"/";
Navigation.NavigateTo(url);
}catch(Exception ex){
Console.WriteLine($"Error : {ex.Message}");
}
} }
} }
}
}

View File

@ -1,11 +1,10 @@
.left_frame { .left_frame {
position: relative; position: relative;
height: 100%; height: 100%;
width: 100%; width: 100%;
transition: 1s; transition: 1s;
transform: translateX(0%);
} }
.logo { .logo {
height: 5em; height: 5em;
position: relative; position: relative;
@ -13,33 +12,42 @@
align-items: center; align-items: center;
padding-bottom: 1.5%; padding-bottom: 1.5%;
} }
.menu { .menu {
position: absolute; position: absolute;
width: 100%; width: 100%;
top: 5em; top: 5em;
bottom: 0; bottom: 0;
border: 1.5px solid; border: 1.5px solid;
border-color: #0052CC; border-color: #0052CC;
border-radius: 0.6em; border-radius: 0.6em;
padding-top: 1.5%; padding-top: 1.5%;
} }
.elements_wishlisht { .elements_wishlisht {
position: absolute; position: absolute;
overflow-y: auto;
top: 2.43em; top: 2.43em;
width: 100%; width: 100%;
bottom: 4em; bottom: 3.8em;
padding-left: 1.25em; padding-left: 1.25em;
padding-right: 1.25em; padding-right: 1.25em;
padding-bottom: 3.8em;
} }
.wishlist_names {
overflow-y: auto;
position: relative;
width: 100%;
height:100%;
}
.info_user { .info_user {
position: absolute; position: absolute;
display: flex; display: flex;
align-items: center; align-items: center;
border-radius: 0 0 0.6em 0.6em; border-radius: 0 0 0.6em 0.6em;
background-color: rgb(255, 255, 255); background-color: rgb(255, 255, 255);
height: 4em; height: 3.6em;
left: 0; left: 0;
bottom: 0; bottom: 0;
width: 100%; width: 100%;
@ -47,6 +55,7 @@
padding-right: 1.25em; padding-right: 1.25em;
border-top: 1px solid #0165FF; border-top: 1px solid #0165FF;
} }
.logo_name { .logo_name {
padding-top: 0.5em; padding-top: 0.5em;
padding-left: 0.3em; padding-left: 0.3em;
@ -57,20 +66,25 @@
font-weight: 600; font-weight: 600;
overflow: hidden; overflow: hidden;
} }
.logo img { .logo img {
float: left; float: left;
height: 3.25em; height: 3.25em;
width: 3.25em; width: 3.25em;
} }
.wishlist_name { .wishlist_name {
padding-left: 0.7em; padding-left: 0.7em;
padding-right: 5em;
width: 10.5em; width: 10.5em;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
position: absolute; position: absolute;
height: 2.5em; height: 2.5em;
line-height: 2.5em; line-height: 2.5em;
cursor: pointer;
} }
.cont_wishlist { .cont_wishlist {
margin-top: 0.4375em; margin-top: 0.4375em;
margin-bottom: 0.4375em; margin-bottom: 0.4375em;
@ -78,52 +92,109 @@
border-radius: 0.6em; border-radius: 0.6em;
font-size: 1.1em; font-size: 1.1em;
width: 100%; width: 100%;
height:2.5em; height: 2.5em;
cursor: pointer;
} }
.cont_wishlist:hover { .cont_wishlist:hover {
background-color: #e6e6e6; background-color: #e6e6e6;
transition: 0.2s; transition: 0.2s;
} }
.cont_wishlist:hover .button_delete_chat{
.cont_wishlist:hover .button_delete_chat {
visibility: visible; visibility: visible;
} }
.cont_wishlist:hover .button_open_card{
.cont_wishlist:hover .button_open_card {
visibility: visible; visibility: visible;
} }
.selected_wishlist {
margin-top: 0.4375em;
margin-bottom: 0.4375em;
color: black;
border-radius: 0.6em;
font-size: 1.1em;
width: 100%;
height: 2.5em;
background-color: #e6e6e6;
transition: 0.2s;
}
.sel_del {
visibility: visible;
cursor: pointer;
margin-top: 0.55em;
margin-right: 1em;
float: right;
position: relative;
}
.selected_card{
cursor: pointer;
position: relative;
z-index: 999;
margin-top: 0.55em;
margin-right: 1em;
float: right;
visibility: visible;
height: 1.4em;
width: 1.5em;
}
.button_delete_chat { .button_delete_chat {
margin-top: 0.65em; cursor: pointer;
margin-top: 0.55em;
margin-right: 1em; margin-right: 1em;
float: right; float: right;
position: relative; position: relative;
visibility: hidden; visibility: hidden;
height: 1.2em; height: 1.4em;
width: 1.2em; width: 1.4em;
} }
.button_open_card { .button_open_card {
margin-top: 0.65em; cursor: pointer;
position: relative;
z-index: 999;
margin-top: 0.55em;
margin-right: 1em; margin-right: 1em;
float: right; float: right;
visibility: hidden; visibility: hidden;
height: 1.2em; height: 1.4em;
width: 1.3em; width: 1.5em;
} }
.elements_wishlisht::-webkit-scrollbar {
background-color: #009FFF; .wishlist_names::-webkit-scrollbar {
border-radius: 20px; border-radius: 20px;
width: 0.2em; width: 0.2em;
} }
.wishlist_names::-webkit-scrollbar-thumb {
background-color: #0052CC;
/* Колір позиції покажчика */
border-radius: 10px;
/* Закруглення країв позиції покажчика */
width: 0.2em;
}
.add_chat { .add_chat {
margin-bottom: 0.3125em; margin-bottom: 0.3125em;
background-color: #0165FF; background-color: #0165FF;
border-radius: 0.6em; border-radius: 0.6em;
font-size: 1.2em; font-size: 1.2em;
width: 100%; width: 100%;
height:2.5em; height: 2.5em;
cursor: pointer; cursor: pointer;
line-height: 2.5em; line-height: 2.5em;
} }
.add_chat a {
.add_chat div {
justify-content: center; justify-content: center;
align-items: center; align-items: center;
border-radius: 0.6em; border-radius: 0.6em;
@ -131,11 +202,12 @@
text-decoration: none; text-decoration: none;
color: #FFFFFF; color: #FFFFFF;
} }
.plus { .plus {
position: absolute; position: absolute;
right: 1em; right: 1em;
top: -0.1em; top: -0.1em;
font-size: 1.9em; font-size: 1.9em;
} }
.user_name { .user_name {
@ -144,12 +216,14 @@
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
.info_user img { .info_user img {
float: left; float: left;
border-radius: 50%; border-radius: 50%;
width: 2.3em; width: 2.3em;
height: 2.3em; height: 2.3em;
} }
.button_close_menu { .button_close_menu {
position: relative; position: relative;
width: 1.43em; width: 1.43em;
@ -159,6 +233,7 @@
margin-top: 0.5em; margin-top: 0.5em;
margin-right: 1.30em; margin-right: 1.30em;
} }
.button_close_menu span { .button_close_menu span {
width: 20px; width: 20px;
height: 1.5px; height: 1.5px;
@ -168,9 +243,11 @@
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
background-color: #4E4E4E; background-color: #4E4E4E;
} }
.button_close_menu span:nth-of-type(2) { .button_close_menu span:nth-of-type(2) {
top: calc(50% - 5px); top: calc(50% - 5px);
} }
.button_close_menu span:nth-of-type(3) { .button_close_menu span:nth-of-type(3) {
top: calc(50% + 5px); top: calc(50% + 5px);
} }

View File

@ -1,16 +0,0 @@
<div class="alert alert-secondary mt-4">
<span class="oi oi-pencil me-2" aria-hidden="true"></span>
<strong>@Title</strong>
<span class="text-nowrap">
Please take our
<a target="_blank" class="font-weight-bold link-dark" href="https://go.microsoft.com/fwlink/?linkid=2186158">brief survey</a>
</span>
and tell us what you think.
</div>
@code {
// Demonstrates how a parent component can supply parameters
[Parameter]
public string? Title { get; set; }
}

View File

@ -1,3 +1,5 @@
{ {
"ApiUrl": "https://shopping-assistant-api-dev.azurewebsites.net/" "ApiUrl": "https://shopping-assistant-api-dev.azurewebsites.net/"
} }

View File

@ -0,0 +1,42 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="602.000000pt" height="597.000000pt" viewBox="0 0 602.000000 597.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,597.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M2819 5887 c-104 -30 -199 -113 -247 -215 -22 -48 -27 -71 -27 -142
0 -105 28 -173 105 -254 74 -79 150 -111 260 -111 72 0 94 4 145 28 79 37 156
114 192 191 24 52 28 74 28 146 0 71 -4 94 -27 142 -77 166 -267 261 -429 215z"/>
<path d="M4083 5579 c-203 -64 -333 -300 -279 -509 40 -155 147 -257 308 -295
252 -59 480 80 521 318 28 163 -56 334 -213 435 -110 71 -221 88 -337 51z"/>
<path d="M1493 5496 c-116 -38 -189 -113 -225 -229 -24 -80 -21 -174 7 -245
21 -51 88 -112 164 -149 81 -38 211 -44 296 -13 81 30 165 114 190 191 56 164
-20 351 -172 425 -67 33 -191 43 -260 20z"/>
<path d="M5061 4675 c-366 -103 -489 -487 -241 -758 86 -94 189 -148 315 -166
202 -29 380 72 473 266 36 76 37 81 37 188 0 131 -20 191 -94 288 -115 150
-326 228 -490 182z"/>
<path d="M532 4508 c-51 -17 -124 -89 -157 -155 -26 -50 -30 -69 -30 -138 0
-71 4 -89 33 -147 37 -75 73 -110 151 -146 46 -21 63 -24 130 -20 90 6 152 32
212 90 62 60 83 116 84 218 0 73 -4 92 -27 136 -32 61 -94 119 -157 147 -54
24 -187 32 -239 15z"/>
<path d="M5440 3389 c-145 -25 -278 -125 -344 -261 -94 -190 -59 -406 89 -554
194 -195 490 -191 683 10 66 68 105 136 127 223 83 319 -222 639 -555 582z"/>
<path d="M218 3187 c-132 -37 -217 -162 -206 -301 13 -163 137 -267 303 -254
103 9 187 64 229 151 55 112 41 216 -40 306 -81 90 -185 126 -286 98z"/>
<path d="M5085 2120 c-164 -34 -301 -145 -383 -311 -103 -208 -72 -411 85
-568 101 -101 244 -161 383 -161 228 0 444 155 516 370 27 82 25 225 -4 312
-87 253 -347 409 -597 358z"/>
<path d="M539 1877 c-72 -20 -129 -75 -166 -157 -19 -43 -22 -168 -5 -227 15
-50 82 -122 143 -155 58 -30 186 -33 239 -4 63 33 115 83 145 137 26 47 30 64
30 134 0 63 -5 89 -23 122 -30 57 -86 111 -139 135 -56 25 -164 32 -224 15z"/>
<path d="M1509 870 c-20 -11 -49 -34 -64 -52 -36 -42 -85 -146 -85 -178 0 -78
56 -161 134 -203 63 -33 166 -31 228 5 98 58 144 191 100 290 -25 58 -87 127
-129 145 -47 19 -141 16 -184 -7z"/>
<path d="M2822 537 c-49 -15 -127 -90 -147 -139 -56 -139 19 -302 159 -343 62
-19 90 -19 152 0 140 41 214 197 161 337 -22 57 -100 132 -153 147 -48 13
-128 13 -172 -2z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 413 B

View File

@ -0,0 +1,3 @@
<svg width="14" height="12" viewBox="0 0 14 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 11.0192V0L14 5.50962L0 11.0192ZM1.47368 8.95313L10.2053 5.50962L1.47368 2.06611V4.47656L5.89474 5.50962L1.47368 6.54267V8.95313Z" fill="#4E4E4E" fill-opacity="0.4"/>
</svg>

After

Width:  |  Height:  |  Size: 281 B