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.",
|
"ClipboardTextCopyFailed_Message": "Failed to copy {0} to clipboard.",
|
||||||
"ComposerToPlaceholder": "click enter to input addresses",
|
"ComposerToPlaceholder": "click enter to input addresses",
|
||||||
"ComposerAttachmentsDropZone_Message": "Drop your files here",
|
"ComposerAttachmentsDropZone_Message": "Drop your files here",
|
||||||
|
"ComposerImagesDropZone_Message": "Drop your images here",
|
||||||
"ComposerAttachmentsDragDropAttach_Message": "Attach",
|
"ComposerAttachmentsDragDropAttach_Message": "Attach",
|
||||||
"CustomThemeBuilder_AccentColorDescription": "Set custom accent color if you wish. Not selecting a color will use your Windows accent color.",
|
"CustomThemeBuilder_AccentColorDescription": "Set custom accent color if you wish. Not selecting a color will use your Windows accent color.",
|
||||||
"CustomThemeBuilder_AccentColorTitle": "Accent color",
|
"CustomThemeBuilder_AccentColorTitle": "Accent color",
|
||||||
|
|||||||
539
Wino.Core.Domain/Translator.Designer.cs
generated
539
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;
|
private bool isDraggingOverComposerGrid;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private bool isDraggingOverDropZone;
|
private bool isDraggingOverFilesDropZone;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool isDraggingOverImagesDropZone;
|
||||||
|
|
||||||
public ObservableCollection<MailAttachmentViewModel> IncludedAttachments { get; set; } = new ObservableCollection<MailAttachmentViewModel>();
|
public ObservableCollection<MailAttachmentViewModel> IncludedAttachments { get; set; } = new ObservableCollection<MailAttachmentViewModel>();
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ imageInput.addEventListener('change', () => {
|
|||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = function (event) {
|
reader.onload = function (event) {
|
||||||
const base64Image = event.target.result;
|
const base64Image = event.target.result;
|
||||||
editor.selection.insertHTML(`<img src="${base64Image}" alt="Embedded Image">`);
|
insertImages([base64Image]);
|
||||||
};
|
};
|
||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file);
|
||||||
}
|
}
|
||||||
@@ -96,3 +96,9 @@ function toggleToolbar(enable) {
|
|||||||
toolbar.style.display = 'none';
|
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}">
|
Visibility="{x:Bind ViewModel.IsDraggingOverComposerGrid, Mode=OneWay}">
|
||||||
<Grid Background="{ThemeResource AcrylicInAppFillColorDefaultBrush}" CornerRadius="9">
|
<Grid Background="{ThemeResource AcrylicInAppFillColorDefaultBrush}" CornerRadius="9">
|
||||||
<Rectangle
|
<Rectangle
|
||||||
x:Name="DropZoneBorder"
|
x:Name="FilesDropZoneBorder"
|
||||||
Fill="Transparent"
|
Fill="Transparent"
|
||||||
Opacity="0.5"
|
Opacity="0.5"
|
||||||
RadiusX="9"
|
RadiusX="9"
|
||||||
@@ -604,7 +604,7 @@
|
|||||||
StrokeDashArray="3,4"
|
StrokeDashArray="3,4"
|
||||||
StrokeThickness="2" />
|
StrokeThickness="2" />
|
||||||
<TextBlock
|
<TextBlock
|
||||||
x:Name="DropZoneText"
|
x:Name="FilesDropZoneText"
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
FontSize="20"
|
FontSize="20"
|
||||||
@@ -613,21 +613,6 @@
|
|||||||
Text="{x:Bind domain:Translator.ComposerAttachmentsDropZone_Message}" />
|
Text="{x:Bind domain:Translator.ComposerAttachmentsDropZone_Message}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</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>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
<Border
|
<Border
|
||||||
@@ -635,13 +620,67 @@
|
|||||||
Background="{ThemeResource WinoContentZoneBackgroud}"
|
Background="{ThemeResource WinoContentZoneBackgroud}"
|
||||||
BorderBrush="{StaticResource CardStrokeColorDefaultBrush}"
|
BorderBrush="{StaticResource CardStrokeColorDefaultBrush}"
|
||||||
BorderThickness="1"
|
BorderThickness="1"
|
||||||
CornerRadius="10">
|
CornerRadius="7">
|
||||||
<Grid>
|
<Grid Margin="1" CornerRadius="7">
|
||||||
<Grid Background="White" Visibility="{x:Bind IsComposerDarkMode, Converter={StaticResource ReverseBooleanToVisibilityConverter}, Mode=OneWay}" />
|
<Grid Background="White" Visibility="{x:Bind IsComposerDarkMode, Converter={StaticResource ReverseBooleanToVisibilityConverter}, Mode=OneWay}" />
|
||||||
|
|
||||||
<muxc:WebView2 x:Name="Chromium" />
|
<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>
|
</Grid>
|
||||||
</Border>
|
</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>
|
</Grid>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ using CommunityToolkit.Mvvm.Messaging;
|
|||||||
using CommunityToolkit.WinUI.Controls;
|
using CommunityToolkit.WinUI.Controls;
|
||||||
using EmailValidation;
|
using EmailValidation;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Toolkit.Uwp.Helpers;
|
||||||
using Microsoft.UI.Xaml.Controls;
|
using Microsoft.UI.Xaml.Controls;
|
||||||
using Microsoft.Web.WebView2.Core;
|
using Microsoft.Web.WebView2.Core;
|
||||||
using MimeKit;
|
using MimeKit;
|
||||||
@@ -122,7 +123,7 @@ namespace Wino.Views
|
|||||||
|
|
||||||
private void OnFileDropGridDragOver(object sender, DragEventArgs e)
|
private void OnFileDropGridDragOver(object sender, DragEventArgs e)
|
||||||
{
|
{
|
||||||
ViewModel.IsDraggingOverDropZone = true;
|
ViewModel.IsDraggingOverFilesDropZone = true;
|
||||||
|
|
||||||
e.AcceptedOperation = DataPackageOperation.Copy;
|
e.AcceptedOperation = DataPackageOperation.Copy;
|
||||||
e.DragUIOverride.Caption = Translator.ComposerAttachmentsDragDropAttach_Message;
|
e.DragUIOverride.Caption = Translator.ComposerAttachmentsDragDropAttach_Message;
|
||||||
@@ -133,10 +134,12 @@ namespace Wino.Views
|
|||||||
|
|
||||||
private void OnFileDropGridDragLeave(object sender, DragEventArgs e)
|
private void OnFileDropGridDragLeave(object sender, DragEventArgs e)
|
||||||
{
|
{
|
||||||
ViewModel.IsDraggingOverDropZone = false;
|
ViewModel.IsDraggingOverFilesDropZone = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void OnFileDropGridFileDropped(object sender, DragEventArgs e)
|
private async void OnFileDropGridFileDropped(object sender, DragEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if (e.DataView.Contains(StandardDataFormats.StorageItems))
|
if (e.DataView.Contains(StandardDataFormats.StorageItems))
|
||||||
{
|
{
|
||||||
@@ -145,9 +148,80 @@ namespace Wino.Views
|
|||||||
|
|
||||||
await AttachFiles(files);
|
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.IsDraggingOverComposerGrid = false;
|
||||||
ViewModel.IsDraggingOverDropZone = false;
|
ViewModel.IsDraggingOverFilesDropZone = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void OnImageDropGridDragEnter(object sender, DragEventArgs e)
|
||||||
|
{
|
||||||
|
bool isValid = false;
|
||||||
|
if (e.DataView.Contains(StandardDataFormats.StorageItems))
|
||||||
|
{
|
||||||
|
// 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>();
|
||||||
|
|
||||||
|
foreach (var file in files)
|
||||||
|
{
|
||||||
|
if (ValidateImageFile(file))
|
||||||
|
{
|
||||||
|
isValid = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
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)
|
private async void BoldButtonClicked(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
await InvokeScriptSafeAsync("editor.execCommand('bold')");
|
await InvokeScriptSafeAsync("editor.execCommand('bold')");
|
||||||
@@ -254,7 +336,7 @@ namespace Wino.Views
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return await Chromium.ExecuteScriptAsync(function);
|
return await Chromium?.ExecuteScriptAsync(function);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -284,17 +366,6 @@ namespace Wino.Views
|
|||||||
await FocusEditorAsync();
|
await FocusEditorAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string> TryGetSelectedTextAsync()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return await Chromium.ExecuteScriptAsync("getSelectedText();");
|
|
||||||
}
|
|
||||||
catch (Exception) { }
|
|
||||||
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task UpdateEditorThemeAsync()
|
public async Task UpdateEditorThemeAsync()
|
||||||
{
|
{
|
||||||
await DOMLoadedTask.Task;
|
await DOMLoadedTask.Task;
|
||||||
|
|||||||
@@ -371,7 +371,7 @@
|
|||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Background="{ThemeResource WinoContentZoneBackgroud}"
|
Background="{ThemeResource WinoContentZoneBackgroud}"
|
||||||
BorderBrush="{StaticResource CardStrokeColorDefaultBrush}"
|
BorderBrush="{StaticResource CardStrokeColorDefaultBrush}"
|
||||||
BorderThickness="0"
|
BorderThickness="1"
|
||||||
CornerRadius="7">
|
CornerRadius="7">
|
||||||
<Grid Margin="1" CornerRadius="7">
|
<Grid Margin="1" CornerRadius="7">
|
||||||
<Grid Background="White" Visibility="{x:Bind IsDarkEditor, Converter={StaticResource ReverseBooleanToVisibilityConverter}, Mode=OneWay}" />
|
<Grid Background="White" Visibility="{x:Bind IsDarkEditor, Converter={StaticResource ReverseBooleanToVisibilityConverter}, Mode=OneWay}" />
|
||||||
|
|||||||
Reference in New Issue
Block a user