Fixed voice work supported language search. Include supported languages and original circle in the search result item. Several UI style updates.
Some checks failed
ci / build-test (push) Failing after 2m52s
ci / publish-image (push) Has been skipped

This commit is contained in:
2026-03-17 00:07:02 -04:00
parent c8403e0e21
commit 22d5a261c5
14 changed files with 352 additions and 45 deletions

View File

@@ -28,8 +28,10 @@ public record VoiceWorkSearchResult
public byte SubtitleLanguage { get; init; }
public bool? IsValid { get; init; }
public required VoiceWorkCircleItem Circle { get; set; }
public VoiceWorkCircleItem? OriginalCircle { get; set; }
public VoiceWorkTagItem[] Tags { get; set; } = [];
public VoiceWorkCreatorItem[] Creators { get; set; } = [];
public string[] SupportedLanguages { get; set; } = [];
}
public class VoiceWorkCircleItem

View File

@@ -3,6 +3,7 @@ using JSMR.Application.Enums;
using JSMR.Application.VoiceWorks.Queries.Search;
using JSMR.Domain.Entities;
using JSMR.Domain.Enums;
using JSMR.Domain.ValueObjects;
using JSMR.Infrastructure.Common.Queries;
using Microsoft.EntityFrameworkCore;
using System.Linq.Expressions;
@@ -60,9 +61,10 @@ public class VoiceWorkSearchProvider(AppDbContext context, IVoiceWorkFullTextSea
break;
}
if (criteria.SupportedLanguages.Length > 0)
filteredQuery = filteredQuery.Where(x => criteria.SupportedLanguages.AsEnumerable().Contains((Language)x.VoiceWork.SubtitleLanguage));
//if (criteria.SupportedLanguages.Length > 0)
// filteredQuery = filteredQuery.Where(x => criteria.SupportedLanguages.AsEnumerable().Contains((Language)x.VoiceWork.SubtitleLanguage));
filteredQuery = ApplySupportedLanguageFilter(filteredQuery, criteria);
filteredQuery = ApplyCircleStatusFilter(filteredQuery, criteria);
filteredQuery = ApplyTagStatusFilter(filteredQuery, criteria);
filteredQuery = ApplyCreatorStatusFilter(filteredQuery, criteria);
@@ -118,6 +120,26 @@ public class VoiceWorkSearchProvider(AppDbContext context, IVoiceWorkFullTextSea
return query.Where(x => voiceWorkIds.Contains(x.VoiceWork.VoiceWorkId));
}
private IQueryable<VoiceWorkQuery> ApplySupportedLanguageFilter(IQueryable<VoiceWorkQuery> query, VoiceWorkSearchCriteria criteria)
{
if (criteria.SupportedLanguages.Length == 0)
return query;
List<string> languageCodes = [];
foreach (Language language in criteria.SupportedLanguages)
{
if (SupportedLanguage.TryFromLanguage(language, out SupportedLanguage? supportedLanguage))
languageCodes.Add(supportedLanguage.Code);
}
if (languageCodes.Count == 0)
return query;
return query.Where(q => context.VoiceWorkSupportedLanguages.Any(sl => sl.VoiceWorkId == q.VoiceWork.VoiceWorkId && languageCodes.Contains(sl.Language)));
}
private IQueryable<VoiceWorkQuery> ApplyCircleStatusFilter(IQueryable<VoiceWorkQuery> query, VoiceWorkSearchCriteria criteria)
{
if (criteria.CircleStatus is null)
@@ -432,6 +454,7 @@ public class VoiceWorkSearchProvider(AppDbContext context, IVoiceWorkFullTextSea
PlannedReleaseDate = voiceWork.PlannedReleaseDate,
Downloads = voiceWork.Downloads,
WishlistCount = voiceWork.WishlistCount,
Rating = (AgeRating)voiceWork.Rating,
Status = voiceWork.Status,
SubtitleLanguage = voiceWork.SubtitleLanguage,
HasTrial = voiceWork.HasTrial,
@@ -449,16 +472,24 @@ public class VoiceWorkSearchProvider(AppDbContext context, IVoiceWorkFullTextSea
int[] voiceWorkIds = [.. items.Select(i => i.VoiceWorkId)];
Dictionary<int, VoiceWorkTagItem[]> tagsByVw = await GetTagsAsync(voiceWorkIds, cancellationToken);
Dictionary<int, VoiceWorkCreatorItem[]> creatorsByVw = await GetCreatorsAsync(voiceWorkIds, cancellationToken);
var tagsByVoiceWork = await GetTagsAsync(voiceWorkIds, cancellationToken);
var creatorsByVoiceWork = await GetCreatorsAsync(voiceWorkIds, cancellationToken);
var supportedLanguagesByVoiceWork = await GetSupportedLanguagesAsync(voiceWorkIds, cancellationToken);
var originalCirclesByVoiceWork = await GetOriginalCircles(voiceWorkIds, cancellationToken);
foreach (VoiceWorkSearchResult item in items)
{
if (tagsByVw.TryGetValue(item.VoiceWorkId, out VoiceWorkTagItem[]? tags))
if (tagsByVoiceWork.TryGetValue(item.VoiceWorkId, out VoiceWorkTagItem[]? tags))
item.Tags = tags;
if (creatorsByVw.TryGetValue(item.VoiceWorkId, out VoiceWorkCreatorItem[]? creators))
if (creatorsByVoiceWork.TryGetValue(item.VoiceWorkId, out VoiceWorkCreatorItem[]? creators))
item.Creators = creators;
if (supportedLanguagesByVoiceWork.TryGetValue(item.VoiceWorkId, out string[]? supportedLanguages))
item.SupportedLanguages = supportedLanguages;
if (originalCirclesByVoiceWork.TryGetValue(item.VoiceWorkId, out VoiceWorkCircleItem? voiceWorkCircleItem))
item.OriginalCircle = voiceWorkCircleItem;
}
}
@@ -499,4 +530,51 @@ public class VoiceWorkSearchProvider(AppDbContext context, IVoiceWorkFullTextSea
g => g.Select(r => new VoiceWorkCreatorItem { CreatorId = r.CreatorId, Name = r.Name, IsFavorite = r.Favorite, IsBlacklisted = r.Blacklisted }).ToArray()
);
}
private async Task<Dictionary<int, string[]>> GetSupportedLanguagesAsync(int[] voiceWorkIds, CancellationToken cancellationToken)
{
var supportedLanguageRows = await (
from voiceWorkSupportedLanguage in context.VoiceWorkSupportedLanguages.AsNoTracking()
where voiceWorkIds.Contains(voiceWorkSupportedLanguage.VoiceWorkId)
select new { voiceWorkSupportedLanguage.VoiceWorkId, voiceWorkSupportedLanguage.Language }
).ToListAsync(cancellationToken);
return supportedLanguageRows
.GroupBy(r => r.VoiceWorkId)
.ToDictionary(
g => g.Key,
g => g.Select(r => r.Language).ToArray()
);
}
private async Task<Dictionary<int, VoiceWorkCircleItem>> GetOriginalCircles(int[] voiceWorkIds, CancellationToken cancellationToken)
{
var originalCircleRows = await (
from voiceWork in context.VoiceWorks.AsNoTracking()
join orignalVoiceWork in context.VoiceWorks.AsNoTracking() on voiceWork.OriginalProductId equals orignalVoiceWork.ProductId
join originalCircle in context.Circles.AsNoTracking() on orignalVoiceWork.CircleId equals originalCircle.CircleId
where voiceWorkIds.Contains(voiceWork.VoiceWorkId)
select new
{
voiceWork.VoiceWorkId,
originalCircle.Name,
originalCircle.MakerId,
originalCircle.Favorite,
originalCircle.Blacklisted
}
).ToListAsync(cancellationToken);
return originalCircleRows
.GroupBy(r => r.VoiceWorkId)
.ToDictionary(
g => g.Key,
g => g.Select(r => new VoiceWorkCircleItem()
{
Name = r.Name,
MakerId = r.MakerId,
IsFavorite = r.Favorite,
IsBlacklisted = r.Blacklisted
}).First()
);
}
}

View File

@@ -3,12 +3,12 @@
@if (string.IsNullOrWhiteSpace(Url))
{
<div class="@GetClasses()" @onclick="@OnClickAsync">
@if (Graphic != null)
@if (Graphic != null && Graphic != Enums.Graphic.None)
{
<Icon Graphic="@Graphic.Value"
Varient="@(IconVarient ?? Enums.IconVarient.None)"
Size="@(IconSize ?? Enums.SizeVarient.Small)"
Color="@Color">
UseCurrentColor>
</Icon>
}
<span>@ChildContent</span>
@@ -23,7 +23,7 @@ else
Graphic="@Graphic.Value"
Varient="@(IconVarient ?? Enums.IconVarient.None)"
Size="@(IconSize ?? Enums.SizeVarient.Small)"
Color="@Color">
UseCurrentColor>
</Icon>
}
<span>@ChildContent</span>
@@ -94,6 +94,11 @@ else
break;
}
if (Varient == ElementVarient.Filled && Tone == ToneVarient.Tint)
{
classNames.Add($"varient-tint");
}
if (Click.HasDelegate || string.IsNullOrWhiteSpace(Url) == false)
{
classNames.Add("is-clickable");

View File

@@ -1,6 +1,6 @@
@using JSMR.UI.Blazor.Enums
<div class="@GetIconClasses()"></div>
<div class="@GetIconClasses()" style="@GetStyle()"></div>
@code {
[Parameter]
@@ -15,20 +15,33 @@
[Parameter]
public ColorVarient Color { get; set; }
[Parameter]
public bool UseCurrentColor { get; set; }
private string GetIconClasses()
{
string graphic = Varient == IconVarient.None
? Graphic.ToString().ToLower()
: $"{Graphic.ToString().ToLower()}-{Varient.ToString().ToLower()}";
List<string> classNames =
[
$"j-icon",
$"j-icon-{graphic}",
$"size-{Size.ToString().ToLower()}",
$"background-color-{Color.ToString().ToLower()}"
//$"background-color-{Color.ToString().ToLower()}"
];
if (!UseCurrentColor)
{
classNames.Add($"background-color-{Color.ToString().ToLower()}");
}
return string.Join(" ", classNames);
}
private string? GetStyle()
{
return UseCurrentColor ? "background-color: currentColor;" : null;
}
}

View File

@@ -15,34 +15,66 @@
<div class="j-product-title">
<a href="@Product.ProductUrl" target="_blank">@Product.ProductName</a>
</div>
<div class="j-product-contributors">
<span class="j-circle">
<MudChip T="string"
<BitStack Horizontal="true" Gap="0.5em" AutoHeight Wrap>
@* <MudChip T="string"
Href=@($"https://www.dlsite.com/maniax/circle/profile/=/maker_id/{Product.MakerId}.html")
Target="_blank"
Variant="MudBlazor.Variant.Filled"
Icon="@Icons.Material.Outlined.Circle">@Product.Maker</MudChip>
@* <CircleChip Circle="@Product.Circle"></CircleChip> *@
@foreach (var creator in Product.Creators)
{
<MudChip T="string"
Icon="@Icons.Material.Outlined.Circle">@Product.Maker</MudChip> *@
@* <CircleChip Circle="@Product.Circle"></CircleChip> *@
<Chip Graphic="Graphic.Circle" Varient="ElementVarient.Outlined" Color="ColorVarient.Secondary" Url=@($"https://www.dlsite.com/maniax/circle/profile/=/maker_id/{Product.MakerId}.html")>@Product.Maker</Chip>
@if (Product.OriginalCircle is not null)
{
<Chip Graphic="Graphic.Circle" Varient="ElementVarient.Outlined" Color="ColorVarient.Secondary" Url=@($"https://www.dlsite.com/maniax/circle/profile/=/maker_id/{Product.OriginalCircle.MakerId}.html")>@Product.OriginalCircle.Name</Chip>
}
@foreach (var creator in Product.Creators)
{
@* <MudChip T="string"
Href=@($"https://www.dlsite.com/maniax/fsr/=/keyword_creater/{creator.Name}")
Target="_blank"
Variant="MudBlazor.Variant.Filled"
Icon="@Icons.Material.Filled.Person">@creator.Name</MudChip>
@* <CreatorChip Creator="@creator"></CreatorChip> *@
}
</span>
</div>
Icon="@Icons.Material.Filled.Person">@creator.Name</MudChip> *@
@* <CreatorChip Creator="@creator"></CreatorChip> *@
<Chip Graphic="Graphic.Person" Varient="ElementVarient.Outlined" IconVarient="IconVarient.Fill" Color="ColorVarient.Secondary" Url=@($"https://www.dlsite.com/maniax/fsr/=/keyword_creater/{creator.Name}")>@creator.Name</Chip>
}
</BitStack>
<BitStack Horizontal="true" Gap="0.5em" AutoHeight Wrap>
@foreach (string supportedLanguage in Product.SupportedLanguages)
{
<Chip Graphic="Graphic.Globe" Varient="ElementVarient.Outlined" Color="ColorVarient.Secondary">@GetSupportedLanguageDescription(supportedLanguage)</Chip>
}
@if (Product.OriginalProductId is not null)
{
<Chip Graphic="Graphic.Translate" Varient="ElementVarient.Outlined" Color="ColorVarient.Secondary">Translation</Chip>
}
@if (Product.Rating == AgeRating.AllAges)
{
<Chip Graphic="Graphic.Age" Color="ColorVarient.Green" Varient="ElementVarient.Outlined">All Ages</Chip>
}
else if (Product.Rating == AgeRating.R15)
{
<Chip Graphic="Graphic.Age" Color="ColorVarient.Blue" Varient="ElementVarient.Outlined">R-15</Chip>
}
@if (Product.HasTrial || Product.HasChobit)
{
<Chip Graphic="Graphic.Download" Color="ColorVarient.Yellow" Varient="ElementVarient.Outlined">Trial</Chip>
}
@if (Product.Favorite)
{
<Chip Graphic="Graphic.Star" Color="ColorVarient.Teal" Varient="ElementVarient.Outlined">Favorite</Chip>
}
</BitStack>
<div class="j-product-description">@Product.Description</div>
<div class="j-tags">
@foreach (var tag in Product.Tags)
{
@* <div class="j-tag">@tag.Name</div> *@
@* <div class="j-tag">@tag.Name</div> *@
<ProductTag Tag="tag"></ProductTag>
}
</div>
@* <div class="j-tags">
@* <div class="j-tags">
@foreach (var tag in Product.Tags)
{
<TagChip Tag="tag"></TagChip>
@@ -149,4 +181,17 @@
return "jp";
}
}
private string GetSupportedLanguageDescription(string code)
{
switch (code)
{
case "JPN": return "Japanese";
case "ENG": return "English";
case "CHI_HANS": return "Chinese (Simplified)";
case "CHI_HANT": return "Chinese (Traditional)";
case "KO_KR": return "Korean";
default: return code;
}
}
}

View File

@@ -1,11 +1,13 @@
@using JSMR.Application.Tags.Queries.Search.Contracts
@using JSMR.Application.VoiceWorks.Queries.Search
@using JSMR.UI.Blazor.Enums
@using JSMR.UI.Blazor.Filters
@using JSMR.UI.Blazor.Services
@using Microsoft.AspNetCore.WebUtilities
<a class="@Classes" @onclick="@OnClick"><Icon Graphic="Enums.Graphic.Tag" Color="Enums.ColorVarient.Primary"></Icon>@Tag.Name</a>
@* <a class="@Classes" @onclick="@OnClick"><Icon Graphic="Enums.Graphic.Tag" Color="Enums.ColorVarient.Primary"></Icon>@Tag.Name</a> *@
@* <MudChip T="string" Icon="@Icons.Material.Outlined.Tag" @onclick="@OnClick" Variant="@MudBlazor.Variant.Filled" Color="@MudBlazor.Color.Surface">@Tag.Name</MudChip> *@
<Chip Graphic="Graphic.Tag" Varient="ElementVarient.Outlined" Color="@GetColorVarient()" Tone="ToneVarient.None" Click="@OnClick">@Tag.Name</Chip>
@code {
[Inject]
@@ -33,6 +35,21 @@
return string.Join(" ", classNames);
}
private ColorVarient GetColorVarient()
{
if (Tag.IsFavorite)
{
return ColorVarient.Mint;
}
if (Tag.IsFavorite)
{
return ColorVarient.Orange;
}
return ColorVarient.Primary;
}
private void OnClick()
{
VoiceWorkFilterState state = new()

View File

@@ -19,5 +19,7 @@ public enum Graphic
Sort,
Grid,
Age,
Calendar
Calendar,
Download,
Microphone
}

View File

@@ -3,32 +3,42 @@ using JSMR.Application.Creators.Queries.Search;
using JSMR.Application.Tags.Queries.Search;
using JSMR.Application.VoiceWorks.Queries.Search;
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace JSMR.UI.Blazor.Services;
public class VoiceWorksClient(HttpClient http)
{
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web)
{
Converters =
{
new JsonStringEnumConverter()
}
};
public async Task<SearchVoiceWorksResponse?> SearchAsync(SearchVoiceWorksRequest request, CancellationToken ct = default)
{
using var resp = await http.PostAsJsonAsync("/api/voiceworks/search", request, ct);
return await resp.Content.ReadFromJsonAsync<SearchVoiceWorksResponse>(cancellationToken: ct);
return await resp.Content.ReadFromJsonAsync<SearchVoiceWorksResponse>(JsonOptions, cancellationToken: ct);
}
public async Task<SearchCirclesResponse?> SearchAsync(SearchCirclesRequest request, CancellationToken ct = default)
{
using var resp = await http.PostAsJsonAsync("/api/circles/search", request, ct);
return await resp.Content.ReadFromJsonAsync<SearchCirclesResponse>(cancellationToken: ct);
return await resp.Content.ReadFromJsonAsync<SearchCirclesResponse>(JsonOptions, cancellationToken: ct);
}
public async Task<SearchCreatorsResponse?> SearchAsync(SearchCreatorsRequest request, CancellationToken ct = default)
{
using var resp = await http.PostAsJsonAsync("/api/creators/search", request, ct);
return await resp.Content.ReadFromJsonAsync<SearchCreatorsResponse>(cancellationToken: ct);
return await resp.Content.ReadFromJsonAsync<SearchCreatorsResponse>(JsonOptions, cancellationToken: ct);
}
public async Task<SearchTagsResponse?> SearchAsync(SearchTagsRequest request, CancellationToken ct = default)
{
using var resp = await http.PostAsJsonAsync("/api/tags/search", request, ct);
return await resp.Content.ReadFromJsonAsync<SearchTagsResponse>(cancellationToken: ct);
return await resp.Content.ReadFromJsonAsync<SearchTagsResponse>(JsonOptions, cancellationToken: ct);
}
}

View File

@@ -592,6 +592,7 @@ code {
.j-tags {
display: flex;
gap: .75rem .5rem;
gap: .5rem .5rem;
flex-wrap: wrap;
/*gap: .5rem 1rem;*/
}
@@ -647,41 +648,70 @@ code {
.j-chip {
display: inline-flex;
align-items: center;
gap: .4rem;
gap: .5em;
font-size: 1rem;
font-weight: 500;
font-size: .8rem;
font-weight: 400;
/*--chip-rgb: var(--secondary-rgb, 148 163 184);
--chip-tint-alpha: 0.12;*/
--chip-rgb: var(--secondary-rgb, 148 163 184);
--chip-bg-rgb: transparent;
--chip-fg-rgb: var(--chip-rgb);
--chip-border-rgb: transparent;
--chip-tint-alpha: 0.12;
color: rgb(var(--chip-fg-rgb));
background: rgb(var(--chip-bg-rgb));
border: 1px solid rgb(var(--chip-border-rgb));
padding: .5em 1em;
border-radius: 1rem;
border: 1px solid transparent;
}
.j-chip.is-clickable {
cursor: pointer;
user-select: none;
--chip-hover-alpha: 0.2;
transition: .2s linear;
transition: background .2s linear, color .2s linear, border-color .2s linear, filter .2s linear;
}
.j-chip.is-clickable:hover {
background: rgb(var(--chip-rgb) / var(--chip-hover-alpha));
filter: brightness(1.2);
}
.j-chip.varient-filled {
padding: .4rem .8rem;
border-radius: 1rem;
--chip-bg-rgb: var(--chip-rgb);
--chip-border-rgb: var(--chip-rgb);
}
/*.j-chip.varient-outlined {
--chip-bg-rgb: transparent;
--chip-fg-rgb: var(--chip-rgb);
--chip-border-rgb: var(--chip-rgb);
}*/
.j-chip.varient-outlined {
border-width: 1px;
border-style: solid;
padding: .4rem .8rem;
border-radius: 1rem;
border-color: rgb(var(--chip-rgb));
background: rgba(0,0,0,.35);
border: 1px solid rgb(var(--chip-rgb) / 0.65);
color: rgb(var(--chip-rgb));
background: transparent;
}
/* .j-chip.varient-tint {
--chip-bg-rgb: var(--chip-rgb) / var(--chip-tint-alpha);
--chip-fg-rgb: var(--chip-rgb);
--chip-border-rgb: transparent;
}*/
.j-chip.varient-tint {
--chip-bg-alpha: 0.14;
--chip-border-alpha: 0.32;
color: rgb(var(--chip-rgb));
background: rgb(var(--chip-rgb) / var(--chip-bg-alpha));
/*border: 1px solid rgb(var(--chip-rgb) / var(--chip-border-alpha));*/
}
/* Old ? */
.j-chip.tone-tint {
background: rgb(var(--chip-rgb) / var(--chip-tint-alpha));
}
@@ -698,6 +728,74 @@ code {
--chip-rgb: var(--rgb-teal);
}
/* New */
.j-chip.color-primary {
--chip-rgb: var(--rgb-primary);
}
.j-chip.varient-filled.color-primary {
--chip-fg-rgb: var(--rgb-on-primary, 0 0 0);
}
.j-chip.color-secondary {
--chip-rgb: var(--rgb-secondary);
}
.j-chip.varient-filled.color-secondary {
--chip-fg-rgb: var(--rgb-on-secondary, 0 0 0);
}
.j-chip.color-mint {
--chip-rgb: var(--rgb-mint);
}
.j-chip.varient-filled.color-mint {
--chip-fg-rgb: var(--rgb-on-mint, 0 0 0);
}
.j-chip.color-green {
--chip-rgb: var(--rgb-green);
}
.j-chip.varient-filled.color-green {
--chip-fg-rgb: var(--rgb-on-green, 255 255 255);
}
.j-chip.color-yellow {
color: rgb(var(--chip-fg-rgb));
--chip-rgb: var(--rgb-yellow);
}
.j-chip.varient-filled.color-yellow {
--chip-fg-rgb: var(--rgb-on-yellow, 255 255 255);
}
.j-chip.color-pink {
color: rgb(var(--chip-fg-rgb));
--chip-rgb: var(--rgb-pink);
}
.j-chip.varient-filled.color-pink {
--chip-fg-rgb: var(--rgb-on-pink, 255 255 255);
}
.j-chip.color-teal {
--chip-rgb: var(--rgb-teal);
}
.j-chip.varient-filled.color-teal {
--chip-fg-rgb: var(--rgb-on-teal, 255 255 255);
}
.j-chip.color-black {
color: inherit;
--chip-rgb: 39 39 39;
}
.j-chip.varient-filled.color-black {
--chip-fg-rgb: var(--primary-text-color);
}
/* Icons */
.j-icon {
mask-repeat: no-repeat;
@@ -825,6 +923,22 @@ code {
mask-image: url("../svg/age-rating.svg");
}
.j-icon-download {
mask-image: url("../svg/cloud-download.svg");
}
.j-icon-download-fill {
mask-image: url("../svg/cloud-download-fill.svg");
}
.j-icon-microphone {
mask-image: url("../svg/microphone.svg");
}
.j-icon-microphone-fill {
mask-image: url("../svg/microphone-fill.svg");
}
.j-icon-2 {
background-repeat: no-repeat;
background-position: center;

View File

@@ -40,9 +40,15 @@
--input-focus-border-color: #64b5f6;
--input-focus-box-shadow: 0 0 0 1px #93cbf9;
/* RGB Tokens */
--rgb-primary: 180 200 214;
--rgb-secondary: 200 220 234;
--rgb-mint: 167 243 208;
--rgb-green: 175 224 125;
--rgb-teal: 110 236 255;
--rgb-yellow: 255 224 115;
--rgb-on-yellow: 0 0 0;
--rgb-pink: 224 104 148;
--rgb-on-pink: 255 255 255;
/* Colors */
--color-primary: rgb(180,200, 214);
--color-secondary: rgb(200,220,234);

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-cloud-download-fill" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M8 0a5.53 5.53 0 0 0-3.594 1.342c-.766.66-1.321 1.52-1.464 2.383C1.266 4.095 0 5.555 0 7.318 0 9.366 1.708 11 3.781 11H7.5V5.5a.5.5 0 0 1 1 0V11h4.188C14.502 11 16 9.57 16 7.773c0-1.636-1.242-2.969-2.834-3.194C12.923 1.999 10.69 0 8 0m-.354 15.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 14.293V11h-1v3.293l-2.146-2.147a.5.5 0 0 0-.708.708z"/>
</svg>

After

Width:  |  Height:  |  Size: 525 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-cloud-download" viewBox="0 0 16 16">
<path d="M4.406 1.342A5.53 5.53 0 0 1 8 0c2.69 0 4.923 2 5.166 4.579C14.758 4.804 16 6.137 16 7.773 16 9.569 14.502 11 12.687 11H10a.5.5 0 0 1 0-1h2.688C13.979 10 15 8.988 15 7.773c0-1.216-1.02-2.228-2.313-2.228h-.5v-.5C12.188 2.825 10.328 1 8 1a4.53 4.53 0 0 0-2.941 1.1c-.757.652-1.153 1.438-1.153 2.055v.448l-.445.049C2.064 4.805 1 5.952 1 7.318 1 8.785 2.23 10 3.781 10H6a.5.5 0 0 1 0 1H3.781C1.708 11 0 9.366 0 7.318c0-1.763 1.266-3.223 2.942-3.593.143-.863.698-1.723 1.464-2.383"/>
<path d="M7.646 15.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 14.293V5.5a.5.5 0 0 0-1 0v8.793l-2.146-2.147a.5.5 0 0 0-.708.708z"/>
</svg>

After

Width:  |  Height:  |  Size: 768 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-mic-fill" viewBox="0 0 16 16">
<path d="M5 3a3 3 0 0 1 6 0v5a3 3 0 0 1-6 0z"/>
<path d="M3.5 6.5A.5.5 0 0 1 4 7v1a4 4 0 0 0 8 0V7a.5.5 0 0 1 1 0v1a5 5 0 0 1-4.5 4.975V15h3a.5.5 0 0 1 0 1h-7a.5.5 0 0 1 0-1h3v-2.025A5 5 0 0 1 3 8V7a.5.5 0 0 1 .5-.5"/>
</svg>

After

Width:  |  Height:  |  Size: 354 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-mic" viewBox="0 0 16 16">
<path d="M3.5 6.5A.5.5 0 0 1 4 7v1a4 4 0 0 0 8 0V7a.5.5 0 0 1 1 0v1a5 5 0 0 1-4.5 4.975V15h3a.5.5 0 0 1 0 1h-7a.5.5 0 0 1 0-1h3v-2.025A5 5 0 0 1 3 8V7a.5.5 0 0 1 .5-.5"/>
<path d="M10 8a2 2 0 1 1-4 0V3a2 2 0 1 1 4 0zM8 0a3 3 0 0 0-3 3v5a3 3 0 0 0 6 0V3a3 3 0 0 0-3-3"/>
</svg>

After

Width:  |  Height:  |  Size: 400 B