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:
@@ -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",
|
||||
|
||||
541
Wino.Core.Domain/Translator.Designer.cs
generated
541
Wino.Core.Domain/Translator.Designer.cs
generated
File diff suppressed because it is too large
Load Diff
@@ -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>();
|
||||
|
||||
|
||||
@@ -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">`);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}" />
|
||||
|
||||
Reference in New Issue
Block a user