Added drag and drop for images

Fixed dropzones visual states
Corrected border radius
Fixed null reference exception when event dispatched when chromium still not initialized
This commit is contained in:
Aleh Khantsevich
2024-07-07 01:42:12 +02:00
parent 8cde976358
commit 5923d72803
7 changed files with 167 additions and 576 deletions

View File

@@ -52,6 +52,7 @@
"ClipboardTextCopyFailed_Message": "Failed to copy {0} to clipboard.",
"ComposerToPlaceholder": "click enter to input addresses",
"ComposerAttachmentsDropZone_Message": "Drop your files here",
"ComposerImagesDropZone_Message": "Drop your images here",
"ComposerAttachmentsDragDropAttach_Message": "Attach",
"CustomThemeBuilder_AccentColorDescription": "Set custom accent color if you wish. Not selecting a color will use your Windows accent color.",
"CustomThemeBuilder_AccentColorTitle": "Accent color",

File diff suppressed because it is too large Load Diff

View File

@@ -72,7 +72,10 @@ namespace Wino.Mail.ViewModels
private bool isDraggingOverComposerGrid;
[ObservableProperty]
private bool isDraggingOverDropZone;
private bool isDraggingOverFilesDropZone;
[ObservableProperty]
private bool isDraggingOverImagesDropZone;
public ObservableCollection<MailAttachmentViewModel> IncludedAttachments { get; set; } = new ObservableCollection<MailAttachmentViewModel>();

View File

@@ -24,7 +24,7 @@ imageInput.addEventListener('change', () => {
const reader = new FileReader();
reader.onload = function (event) {
const base64Image = event.target.result;
editor.selection.insertHTML(`<img src="${base64Image}" alt="Embedded Image">`);
insertImages([base64Image]);
};
reader.readAsDataURL(file);
}
@@ -96,3 +96,9 @@ function toggleToolbar(enable) {
toolbar.style.display = 'none';
}
}
function insertImages(images) {
images.forEach(image => {
editor.selection.insertHTML(`<img src="${image}" alt="Embedded Image">`);
});
};

View File

@@ -595,7 +595,7 @@
Visibility="{x:Bind ViewModel.IsDraggingOverComposerGrid, Mode=OneWay}">
<Grid Background="{ThemeResource AcrylicInAppFillColorDefaultBrush}" CornerRadius="9">
<Rectangle
x:Name="DropZoneBorder"
x:Name="FilesDropZoneBorder"
Fill="Transparent"
Opacity="0.5"
RadiusX="9"
@@ -604,7 +604,7 @@
StrokeDashArray="3,4"
StrokeThickness="2" />
<TextBlock
x:Name="DropZoneText"
x:Name="FilesDropZoneText"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="20"
@@ -613,21 +613,6 @@
Text="{x:Bind domain:Translator.ComposerAttachmentsDropZone_Message}" />
</Grid>
</Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="DropZoneState">
<VisualState x:Name="Hovered">
<VisualState.StateTriggers>
<StateTrigger IsActive="{x:Bind ViewModel.IsDraggingOverDropZone, Mode=OneWay}" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="DropZoneText.Opacity" Value="1" />
<Setter Target="DropZoneBorder.Opacity" Value="1" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="NotHovered" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</Border>
<Border
@@ -635,13 +620,67 @@
Background="{ThemeResource WinoContentZoneBackgroud}"
BorderBrush="{StaticResource CardStrokeColorDefaultBrush}"
BorderThickness="1"
CornerRadius="10">
<Grid>
CornerRadius="7">
<Grid Margin="1" CornerRadius="7">
<Grid Background="White" Visibility="{x:Bind IsComposerDarkMode, Converter={StaticResource ReverseBooleanToVisibilityConverter}, Mode=OneWay}" />
<muxc:WebView2 x:Name="Chromium" />
<!-- Dropzone for images -->
<Grid
AllowDrop="True"
DragEnter="OnImageDropGridDragEnter"
DragLeave="OnImageDropGridDragLeave"
Drop="OnImageDropGridImageDropped"
Visibility="{x:Bind ViewModel.IsDraggingOverComposerGrid, Mode=OneWay}">
<Grid Background="{ThemeResource AcrylicInAppFillColorDefaultBrush}" CornerRadius="9">
<Rectangle
x:Name="ImagesDropZoneBorder"
Fill="Transparent"
Opacity="0.5"
RadiusX="9"
RadiusY="9"
Stroke="{ThemeResource TextFillColorPrimaryBrush}"
StrokeDashArray="3,4"
StrokeThickness="2" />
<TextBlock
x:Name="ImagesDropZoneText"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="20"
FontWeight="SemiBold"
Opacity="0.5"
Text="{x:Bind domain:Translator.ComposerImagesDropZone_Message}" />
</Grid>
</Grid>
</Grid>
</Border>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="FilesDropZoneState">
<VisualState x:Name="FilesDropZoneHovered">
<VisualState.StateTriggers>
<StateTrigger IsActive="{x:Bind ViewModel.IsDraggingOverFilesDropZone, Mode=OneWay}" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="FilesDropZoneText.Opacity" Value="1" />
<Setter Target="FilesDropZoneBorder.Opacity" Value="1" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="FilesDropZoneNotHovered" />
</VisualStateGroup>
<VisualStateGroup x:Name="ImagesDropZoneState">
<VisualState x:Name="ImagesDropZoneHovered">
<VisualState.StateTriggers>
<StateTrigger IsActive="{x:Bind ViewModel.IsDraggingOverImagesDropZone, Mode=OneWay}" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="ImagesDropZoneText.Opacity" Value="1" />
<Setter Target="ImagesDropZoneBorder.Opacity" Value="1" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="ImagesDropZoneNotHovered" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>

View File

@@ -9,6 +9,7 @@ using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.WinUI.Controls;
using EmailValidation;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Toolkit.Uwp.Helpers;
using Microsoft.UI.Xaml.Controls;
using Microsoft.Web.WebView2.Core;
using MimeKit;
@@ -122,7 +123,7 @@ namespace Wino.Views
private void OnFileDropGridDragOver(object sender, DragEventArgs e)
{
ViewModel.IsDraggingOverDropZone = true;
ViewModel.IsDraggingOverFilesDropZone = true;
e.AcceptedOperation = DataPackageOperation.Copy;
e.DragUIOverride.Caption = Translator.ComposerAttachmentsDragDropAttach_Message;
@@ -133,21 +134,94 @@ namespace Wino.Views
private void OnFileDropGridDragLeave(object sender, DragEventArgs e)
{
ViewModel.IsDraggingOverDropZone = false;
ViewModel.IsDraggingOverFilesDropZone = false;
}
private async void OnFileDropGridFileDropped(object sender, DragEventArgs e)
{
try
{
if (e.DataView.Contains(StandardDataFormats.StorageItems))
{
var storageItems = await e.DataView.GetStorageItemsAsync();
var files = storageItems.OfType<StorageFile>();
await AttachFiles(files);
}
}
// State should be reset even when an exception occurs, otherwise the UI will be stuck in a dragging state.
finally
{
ViewModel.IsDraggingOverComposerGrid = false;
ViewModel.IsDraggingOverFilesDropZone = false;
}
}
private void OnImageDropGridDragEnter(object sender, DragEventArgs e)
{
bool isValid = false;
if (e.DataView.Contains(StandardDataFormats.StorageItems))
{
var storageItems = await e.DataView.GetStorageItemsAsync();
var files = storageItems.OfType<StorageFile>();
// We can't use async/await here because DragUIOverride becomes inaccessible.
// https://github.com/microsoft/microsoft-ui-xaml/issues/9296
var files = e.DataView.GetStorageItemsAsync().GetAwaiter().GetResult().OfType<StorageFile>();
await AttachFiles(files);
foreach (var file in files)
{
if (ValidateImageFile(file))
{
isValid = true;
}
}
}
ViewModel.IsDraggingOverComposerGrid = false;
ViewModel.IsDraggingOverDropZone = false;
e.AcceptedOperation = isValid ? DataPackageOperation.Copy : DataPackageOperation.None;
if (isValid)
{
ViewModel.IsDraggingOverImagesDropZone = true;
e.DragUIOverride.Caption = Translator.ComposerAttachmentsDragDropAttach_Message;
e.DragUIOverride.IsCaptionVisible = true;
e.DragUIOverride.IsGlyphVisible = true;
e.DragUIOverride.IsContentVisible = true;
}
}
private void OnImageDropGridDragLeave(object sender, DragEventArgs e)
{
ViewModel.IsDraggingOverImagesDropZone = false;
}
private async void OnImageDropGridImageDropped(object sender, DragEventArgs e)
{
try
{
if (e.DataView.Contains(StandardDataFormats.StorageItems))
{
var storageItems = await e.DataView.GetStorageItemsAsync();
var files = storageItems.OfType<StorageFile>();
var imageDataURLs = new List<string>();
foreach (var file in files)
{
if (ValidateImageFile(file))
imageDataURLs.Add(await GetDataURL(file));
}
await InvokeScriptSafeAsync($"insertImages({JsonConvert.SerializeObject(imageDataURLs)});");
}
}
// State should be reset even when an exception occurs, otherwise the UI will be stuck in a dragging state.
finally
{
ViewModel.IsDraggingOverComposerGrid = false;
ViewModel.IsDraggingOverImagesDropZone = false;
}
static async Task<string> GetDataURL(StorageFile file)
{
return $"data:image/{file.FileType.Replace(".", "")};base64,{Convert.ToBase64String(await file.ReadBytesAsync())}";
}
}
private async Task AttachFiles(IEnumerable<StorageFile> files)
@@ -166,6 +240,14 @@ namespace Wino.Views
}
}
private bool ValidateImageFile(StorageFile file)
{
string[] allowedTypes = new string[] { ".jpg", ".jpeg", ".png" };
var fileType = file.FileType.ToLower();
return allowedTypes.Contains(fileType);
}
private async void BoldButtonClicked(object sender, RoutedEventArgs e)
{
await InvokeScriptSafeAsync("editor.execCommand('bold')");
@@ -254,7 +336,7 @@ namespace Wino.Views
{
try
{
return await Chromium.ExecuteScriptAsync(function);
return await Chromium?.ExecuteScriptAsync(function);
}
catch (Exception ex)
{
@@ -284,17 +366,6 @@ namespace Wino.Views
await FocusEditorAsync();
}
private async Task<string> TryGetSelectedTextAsync()
{
try
{
return await Chromium.ExecuteScriptAsync("getSelectedText();");
}
catch (Exception) { }
return string.Empty;
}
public async Task UpdateEditorThemeAsync()
{
await DOMLoadedTask.Task;

View File

@@ -371,7 +371,7 @@
Grid.Row="1"
Background="{ThemeResource WinoContentZoneBackgroud}"
BorderBrush="{StaticResource CardStrokeColorDefaultBrush}"
BorderThickness="0"
BorderThickness="1"
CornerRadius="7">
<Grid Margin="1" CornerRadius="7">
<Grid Background="White" Visibility="{x:Bind IsDarkEditor, Converter={StaticResource ReverseBooleanToVisibilityConverter}, Mode=OneWay}" />