Refactoring the html editor toolbar.
This commit is contained in:
@@ -28,6 +28,15 @@ dotnet restore Wino.Mail.WinUI/Wino.Mail.WinUI.csproj --configfile nuget.config
|
||||
|
||||
**Platforms:** x86, x64, ARM64
|
||||
|
||||
## Efficient Workflow
|
||||
|
||||
- Start with targeted symbol or file search before reading full files
|
||||
- Prefer one focused task per thread; use a new thread for unrelated follow-up work
|
||||
- Keep verification narrow: build only the affected project, not the full solution, unless cross-project changes require it
|
||||
- After the first restore, prefer `--no-restore` builds unless package or project references changed
|
||||
- Summarize long build logs and inspect only the files named in diagnostics instead of loading large logs into context
|
||||
- When the prompt already names likely files, types, or symbols, start there instead of re-mapping the repository
|
||||
|
||||
## Architecture
|
||||
|
||||
### Solution Structure
|
||||
@@ -130,6 +139,7 @@ private string searchQuery = string.Empty;
|
||||
- String interpolation over string.Format
|
||||
- Wrap async operations in try-catch
|
||||
- Log errors via IWinoLogger
|
||||
- For dependency properties in WinUI code, always prefer `[GeneratedDependencyProperty]` from CommunityToolkit over manual `DependencyProperty.Register(...)` declarations.
|
||||
- In ViewModels, update all UI-bound properties/collections via `ExecuteUIThread(...)` (especially after awaited calls and any use of `ConfigureAwait(false)`).
|
||||
- In `EventDetailsPageViewModel.LoadAttendeesAsync`, never mutate `CurrentEvent.Attendees` outside `ExecuteUIThread(...)`.
|
||||
|
||||
|
||||
@@ -242,14 +242,21 @@ public partial class CalendarEventComposePageViewModel : CalendarBaseViewModel
|
||||
{
|
||||
foreach (var file in pickedFiles)
|
||||
{
|
||||
if (Attachments.Any(existing => existing.FilePath.Equals(file.FullFilePath, StringComparison.OrdinalIgnoreCase)))
|
||||
continue;
|
||||
|
||||
Attachments.Add(new CalendarComposeAttachmentViewModel(file.FileName, file.FullFilePath, file.FileExtension, file.Size));
|
||||
TryAddAttachment(file.FileName, file.FullFilePath, file.FileExtension, file.Size);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public bool TryAddAttachment(string filePath, long size)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
return false;
|
||||
|
||||
var fileName = Path.GetFileName(filePath);
|
||||
var fileExtension = Path.GetExtension(filePath);
|
||||
return TryAddAttachment(fileName, filePath, fileExtension, size);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void RemoveAttachment(CalendarComposeAttachmentViewModel attachment)
|
||||
{
|
||||
@@ -640,6 +647,18 @@ public partial class CalendarEventComposePageViewModel : CalendarBaseViewModel
|
||||
OnPropertyChanged(nameof(HasAttachments));
|
||||
}
|
||||
|
||||
private bool TryAddAttachment(string fileName, string filePath, string fileExtension, long size)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filePath) ||
|
||||
Attachments.Any(existing => existing.FilePath.Equals(filePath, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Attachments.Add(new CalendarComposeAttachmentViewModel(fileName, filePath, fileExtension, size));
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public partial class CalendarComposeFrequencyOption : ObservableObject
|
||||
|
||||
@@ -2,11 +2,13 @@ using Wino.Core.Domain.Entities.Shared;
|
||||
|
||||
namespace Wino.Calendar.ViewModels.Data;
|
||||
|
||||
public class CalendarComposeAttendeeViewModel
|
||||
public class CalendarComposeAttendeeViewModel : IContactDisplayItem
|
||||
{
|
||||
public string DisplayName { get; }
|
||||
public string Email { get; }
|
||||
public AccountContact ResolvedContact { get; }
|
||||
public string Address => Email;
|
||||
public AccountContact PreviewContact => ResolvedContact;
|
||||
public bool HasDistinctDisplayName => !string.IsNullOrWhiteSpace(DisplayName) && !DisplayName.Equals(Email, System.StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
public CalendarComposeAttendeeViewModel(string displayName, string email, AccountContact resolvedContact = null)
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Wino.Core.Domain.Entities.Shared;
|
||||
|
||||
// TODO: This can easily evolve to Contact store, just like People app in Windows 10/11.
|
||||
// Do it.
|
||||
public class AccountContact : IEquatable<AccountContact>
|
||||
public class AccountContact : IEquatable<AccountContact>, IContactDisplayItem
|
||||
{
|
||||
/// <summary>
|
||||
/// E-mail address of the contact.
|
||||
@@ -43,6 +43,9 @@ public class AccountContact : IEquatable<AccountContact>
|
||||
/// </summary>
|
||||
public bool IsOverridden { get; set; } = false;
|
||||
|
||||
public string DisplayName => string.IsNullOrWhiteSpace(Name) ? Address : Name;
|
||||
AccountContact IContactDisplayItem.PreviewContact => this;
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return Equals(obj as AccountContact);
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Wino.Core.Domain.Entities.Shared;
|
||||
|
||||
public interface IContactDisplayItem
|
||||
{
|
||||
string DisplayName { get; }
|
||||
string Address { get; }
|
||||
AccountContact PreviewContact { get; }
|
||||
}
|
||||
@@ -149,7 +149,6 @@
|
||||
"CalendarEventCompose_PickCalendarTitle": "Pick a calendar",
|
||||
"CalendarEventCompose_Recurring": "Recurring",
|
||||
"CalendarEventCompose_RecurringSummary": "Occurs every {0} {1}{2} {3} effective {4}{5}",
|
||||
"CalendarEventCompose_RecurringSummarySmart": "Occurs {0}{1} {2} starting {3}{4}",
|
||||
"CalendarEventCompose_RepeatEvery": "Repeat every",
|
||||
"CalendarEventCompose_SelectCalendar": "Select calendar",
|
||||
"CalendarEventCompose_SingleOccurrenceSummary": "Occurs on {0} {1}",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
@@ -97,16 +98,33 @@ public sealed record class EditorTableCommandArgs(
|
||||
|
||||
public sealed record class EditorColorOption(string Name, string Value)
|
||||
{
|
||||
public SolidColorBrush Brush => new(ParseColor(Value));
|
||||
public SolidColorBrush Brush => new(ParseColorValue(Value));
|
||||
|
||||
private static Color ParseColor(string? value)
|
||||
public static Color ParseColorValue(string? value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return Colors.Transparent;
|
||||
}
|
||||
|
||||
var hex = value.Trim().TrimStart('#');
|
||||
var normalizedValue = value.Trim();
|
||||
|
||||
if (string.Equals(normalizedValue, "transparent", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return Colors.Transparent;
|
||||
}
|
||||
|
||||
if (TryParseRgbColor(normalizedValue, out var rgbColor))
|
||||
{
|
||||
return rgbColor;
|
||||
}
|
||||
|
||||
if (TryParseNamedColor(normalizedValue, out var namedColor))
|
||||
{
|
||||
return namedColor;
|
||||
}
|
||||
|
||||
var hex = normalizedValue.TrimStart('#');
|
||||
if (hex.Length == 6)
|
||||
{
|
||||
hex = $"FF{hex}";
|
||||
@@ -123,6 +141,76 @@ public sealed record class EditorColorOption(string Name, string Value)
|
||||
(byte)((argb >> 8) & 0xFF),
|
||||
(byte)(argb & 0xFF));
|
||||
}
|
||||
|
||||
private static bool TryParseRgbColor(string value, out Color color)
|
||||
{
|
||||
color = Colors.Transparent;
|
||||
|
||||
var isRgba = value.StartsWith("rgba(", StringComparison.OrdinalIgnoreCase);
|
||||
var isRgb = value.StartsWith("rgb(", StringComparison.OrdinalIgnoreCase);
|
||||
if (!isRgb && !isRgba)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var startIndex = value.IndexOf('(');
|
||||
var endIndex = value.LastIndexOf(')');
|
||||
if (startIndex < 0 || endIndex <= startIndex)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var segments = value[(startIndex + 1)..endIndex]
|
||||
.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if ((isRgb && segments.Length != 3) || (isRgba && segments.Length != 4))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!byte.TryParse(segments[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out var red) ||
|
||||
!byte.TryParse(segments[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var green) ||
|
||||
!byte.TryParse(segments[2], NumberStyles.Integer, CultureInfo.InvariantCulture, out var blue))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
byte alpha = 255;
|
||||
if (isRgba)
|
||||
{
|
||||
if (!double.TryParse(segments[3], NumberStyles.Float, CultureInfo.InvariantCulture, out var alphaValue))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
alpha = alphaValue <= 1d
|
||||
? (byte)Math.Clamp(Math.Round(alphaValue * 255d), 0d, 255d)
|
||||
: (byte)Math.Clamp(Math.Round(alphaValue), 0d, 255d);
|
||||
}
|
||||
|
||||
color = Color.FromArgb(alpha, red, green, blue);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryParseNamedColor(string value, out Color color)
|
||||
{
|
||||
color = value.ToLowerInvariant() switch
|
||||
{
|
||||
"black" => Colors.Black,
|
||||
"white" => Colors.White,
|
||||
"gray" or "grey" => Colors.Gray,
|
||||
"red" => Colors.Red,
|
||||
"orange" => Colors.Orange,
|
||||
"yellow" => Colors.Yellow,
|
||||
"green" => Colors.Green,
|
||||
"blue" => Colors.Blue,
|
||||
"purple" => Colors.Purple,
|
||||
"pink" => Colors.Pink,
|
||||
_ => Colors.Transparent
|
||||
};
|
||||
|
||||
return !color.Equals(Colors.Transparent) || string.Equals(value, "transparent", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record class EditorParagraphStyleOption(string Name, string Tag);
|
||||
|
||||
@@ -11,29 +11,47 @@
|
||||
mc:Ignorable="d">
|
||||
|
||||
<UserControl.Resources>
|
||||
<Style x:Key="CompactComboBoxStyle" TargetType="ComboBox">
|
||||
<Setter Property="MinWidth" Value="88" />
|
||||
<Setter Property="MaxWidth" Value="136" />
|
||||
<Style
|
||||
x:Key="CompactComboBoxStyle"
|
||||
BasedOn="{StaticResource DefaultComboBoxStyle}"
|
||||
TargetType="ComboBox">
|
||||
<Setter Property="MinWidth" Value="72" />
|
||||
<Setter Property="MaxWidth" Value="132" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="Transparent" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Padding" Value="4,0,0,0" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="CompactPickerContainerStyle" TargetType="AppBarElementContainer">
|
||||
<Setter Property="MinWidth" Value="0" />
|
||||
<Setter Property="Margin" Value="0" />
|
||||
<Setter Property="Margin" Value="0,8,0,0" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="CompactPickerBorderStyle" TargetType="Border">
|
||||
<!--<Setter Property="Background" Value="{ThemeResource ControlFillColorSecondaryBrush}" />-->
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource ControlStrokeColorDefaultBrush}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="CornerRadius" Value="{StaticResource ControlCornerRadius}" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="MinHeight" Value="36" />
|
||||
<Setter Property="Margin" Value="4,0" />
|
||||
</Style>
|
||||
|
||||
<DataTemplate x:Key="ColorOptionTemplate" x:DataType="mail:EditorColorOption">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<Grid Width="14" Height="14">
|
||||
<Rectangle
|
||||
Fill="{x:Bind Brush}"
|
||||
RadiusX="3"
|
||||
RadiusY="3"
|
||||
Fill="{x:Bind Brush}"
|
||||
Stroke="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
StrokeThickness="1" />
|
||||
</Grid>
|
||||
<TextBlock VerticalAlignment="Center" Text="{x:Bind Name}" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
|
||||
</UserControl.Resources>
|
||||
|
||||
<toolkit:TabbedCommandBar>
|
||||
@@ -41,6 +59,12 @@
|
||||
<SolidColorBrush x:Key="TabContentContentBorderBackground" Color="Transparent" />
|
||||
<SolidColorBrush x:Key="TabContentContentBorderBorderBrush" Color="Transparent" />
|
||||
<Thickness x:Key="TabContentBorderBorderThickness">0</Thickness>
|
||||
<Style BasedOn="{StaticResource DefaultAppBarToggleButtonStyle}" TargetType="AppBarToggleButton">
|
||||
<Setter Property="Width" Value="50" />
|
||||
</Style>
|
||||
<Style BasedOn="{StaticResource DefaultAppBarButtonStyle}" TargetType="AppBarButton">
|
||||
<Setter Property="Width" Value="50" />
|
||||
</Style>
|
||||
</toolkit:TabbedCommandBar.Resources>
|
||||
|
||||
<toolkit:TabbedCommandBar.PaneCustomContent>
|
||||
@@ -49,25 +73,41 @@
|
||||
|
||||
<toolkit:TabbedCommandBar.MenuItems>
|
||||
<toolkit:TabbedCommandBarItem DefaultLabelPosition="Collapsed" Header="Format">
|
||||
<AppBarToggleButton x:Name="BoldButton" Click="BoldButton_Click" Label="Bold" ToolTipService.ToolTip="Bold (Ctrl+B)">
|
||||
<AppBarToggleButton
|
||||
x:Name="BoldButton"
|
||||
Click="BoldButton_Click"
|
||||
Label="Bold"
|
||||
ToolTipService.ToolTip="Bold (Ctrl+B)">
|
||||
<AppBarToggleButton.Icon>
|
||||
<PathIcon Data="{StaticResource BoldPathIcon}" />
|
||||
</AppBarToggleButton.Icon>
|
||||
</AppBarToggleButton>
|
||||
|
||||
<AppBarToggleButton x:Name="ItalicButton" Click="ItalicButton_Click" Label="Italic" ToolTipService.ToolTip="Italic (Ctrl+I)">
|
||||
<AppBarToggleButton
|
||||
x:Name="ItalicButton"
|
||||
Click="ItalicButton_Click"
|
||||
Label="Italic"
|
||||
ToolTipService.ToolTip="Italic (Ctrl+I)">
|
||||
<AppBarToggleButton.Icon>
|
||||
<PathIcon Data="{StaticResource ItalicPathIcon}" />
|
||||
</AppBarToggleButton.Icon>
|
||||
</AppBarToggleButton>
|
||||
|
||||
<AppBarToggleButton x:Name="UnderlineButton" Click="UnderlineButton_Click" Label="Underline" ToolTipService.ToolTip="Underline (Ctrl+U)">
|
||||
<AppBarToggleButton
|
||||
x:Name="UnderlineButton"
|
||||
Click="UnderlineButton_Click"
|
||||
Label="Underline"
|
||||
ToolTipService.ToolTip="Underline (Ctrl+U)">
|
||||
<AppBarToggleButton.Icon>
|
||||
<PathIcon Data="{StaticResource UnderlinePathIcon}" />
|
||||
</AppBarToggleButton.Icon>
|
||||
</AppBarToggleButton>
|
||||
|
||||
<AppBarToggleButton x:Name="StrikeButton" Click="StrikeButton_Click" Label="Strikethrough" ToolTipService.ToolTip="Strikethrough">
|
||||
<AppBarToggleButton
|
||||
x:Name="StrikeButton"
|
||||
Click="StrikeButton_Click"
|
||||
Label="Strikethrough"
|
||||
ToolTipService.ToolTip="Strikethrough">
|
||||
<AppBarToggleButton.Icon>
|
||||
<PathIcon Data="{StaticResource StrikePathIcon}" />
|
||||
</AppBarToggleButton.Icon>
|
||||
@@ -75,139 +115,254 @@
|
||||
|
||||
<AppBarSeparator />
|
||||
|
||||
<AppBarToggleButton x:Name="BulletListButton" Click="BulletListButton_Click" Label="Bullets" ToolTipService.ToolTip="Bulleted list">
|
||||
<AppBarToggleButton
|
||||
x:Name="BulletListButton"
|
||||
Click="BulletListButton_Click"
|
||||
Label="Bullets"
|
||||
ToolTipService.ToolTip="Bulleted list">
|
||||
<AppBarToggleButton.Icon>
|
||||
<PathIcon Data="{StaticResource BulletedListPathIcon}" />
|
||||
</AppBarToggleButton.Icon>
|
||||
</AppBarToggleButton>
|
||||
|
||||
<AppBarToggleButton x:Name="OrderedListButton" Click="OrderedListButton_Click" Label="Numbered list" ToolTipService.ToolTip="Numbered list">
|
||||
<AppBarToggleButton
|
||||
x:Name="OrderedListButton"
|
||||
Click="OrderedListButton_Click"
|
||||
Label="Numbered list"
|
||||
ToolTipService.ToolTip="Numbered list">
|
||||
<AppBarToggleButton.Icon>
|
||||
<PathIcon Data="{StaticResource OrderedListPathIcon}" />
|
||||
</AppBarToggleButton.Icon>
|
||||
</AppBarToggleButton>
|
||||
|
||||
<AppBarButton x:Name="OutdentButton" Click="OutdentButton_Click" Label="Outdent" ToolTipService.ToolTip="Outdent">
|
||||
<AppBarButton
|
||||
x:Name="OutdentButton"
|
||||
Click="OutdentButton_Click"
|
||||
Label="Outdent"
|
||||
ToolTipService.ToolTip="Outdent">
|
||||
<AppBarButton.Icon>
|
||||
<PathIcon Data="{StaticResource DecreaseIndentPathIcon}" />
|
||||
</AppBarButton.Icon>
|
||||
</AppBarButton>
|
||||
|
||||
<AppBarButton x:Name="IndentButton" Click="IndentButton_Click" Label="Indent" ToolTipService.ToolTip="Indent">
|
||||
<AppBarButton
|
||||
x:Name="IndentButton"
|
||||
Click="IndentButton_Click"
|
||||
Label="Indent"
|
||||
ToolTipService.ToolTip="Indent">
|
||||
<AppBarButton.Icon>
|
||||
<PathIcon Data="{StaticResource IncreaseIndentPathIcon}" />
|
||||
</AppBarButton.Icon>
|
||||
</AppBarButton>
|
||||
|
||||
<AppBarElementContainer Style="{StaticResource CompactPickerContainerStyle}" ToolTipService.ToolTip="Text alignment">
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<PathIcon
|
||||
Width="14"
|
||||
Height="14"
|
||||
VerticalAlignment="Center"
|
||||
Data="{StaticResource AlignLeftPathIcon}" />
|
||||
<ComboBox
|
||||
x:Name="AlignmentComboBox"
|
||||
Style="{StaticResource CompactComboBoxStyle}"
|
||||
MinWidth="108"
|
||||
SelectionChanged="AlignmentComboBox_SelectionChanged" />
|
||||
</StackPanel>
|
||||
<Border Style="{StaticResource CompactPickerBorderStyle}">
|
||||
<Grid ColumnSpacing="8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="32" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<PathIcon
|
||||
Width="14"
|
||||
Height="14"
|
||||
Margin="10,0,0,0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Data="{StaticResource AlignLeftPathIcon}"
|
||||
Foreground="{ThemeResource TextFillColorPrimaryBrush}" />
|
||||
<ComboBox
|
||||
x:Name="AlignmentComboBox"
|
||||
Grid.Column="1"
|
||||
MinWidth="94"
|
||||
SelectionChanged="AlignmentComboBox_SelectionChanged"
|
||||
Style="{StaticResource CompactComboBoxStyle}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</AppBarElementContainer>
|
||||
|
||||
<AppBarElementContainer Style="{StaticResource CompactPickerContainerStyle}" ToolTipService.ToolTip="Font family">
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<FontIcon VerticalAlignment="Center" FontSize="14" Glyph="" />
|
||||
<ComboBox
|
||||
x:Name="FontFamilyComboBox"
|
||||
Style="{StaticResource CompactComboBoxStyle}"
|
||||
MinWidth="120"
|
||||
PlaceholderText="Font"
|
||||
SelectionChanged="FontFamilyComboBox_SelectionChanged" />
|
||||
</StackPanel>
|
||||
</AppBarElementContainer>
|
||||
|
||||
<AppBarElementContainer Style="{StaticResource CompactPickerContainerStyle}" ToolTipService.ToolTip="Font size">
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<TextBlock VerticalAlignment="Center" FontWeight="SemiBold" Text="12" />
|
||||
<ComboBox
|
||||
x:Name="FontSizeComboBox"
|
||||
Style="{StaticResource CompactComboBoxStyle}"
|
||||
MinWidth="80"
|
||||
PlaceholderText="Size"
|
||||
SelectionChanged="FontSizeComboBox_SelectionChanged" />
|
||||
</StackPanel>
|
||||
<Border Style="{StaticResource CompactPickerBorderStyle}">
|
||||
<Grid ColumnSpacing="8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="32" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<FontIcon
|
||||
Margin="10,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="14"
|
||||
Glyph="" />
|
||||
<ComboBox
|
||||
x:Name="FontFamilyComboBox"
|
||||
Grid.Column="1"
|
||||
MinWidth="110"
|
||||
PlaceholderText="Font"
|
||||
SelectionChanged="FontFamilyComboBox_SelectionChanged"
|
||||
Style="{StaticResource CompactComboBoxStyle}" />
|
||||
<ComboBox
|
||||
x:Name="FontSizeComboBox"
|
||||
Grid.Column="2"
|
||||
MinWidth="72"
|
||||
HorizontalContentAlignment="Center"
|
||||
PlaceholderText="Size"
|
||||
SelectionChanged="FontSizeComboBox_SelectionChanged"
|
||||
Style="{StaticResource CompactComboBoxStyle}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</AppBarElementContainer>
|
||||
|
||||
<AppBarElementContainer Style="{StaticResource CompactPickerContainerStyle}" ToolTipService.ToolTip="Paragraph style">
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<PathIcon
|
||||
Width="14"
|
||||
Height="14"
|
||||
VerticalAlignment="Center"
|
||||
Data="{StaticResource ParagraphPathIcon}" />
|
||||
<ComboBox
|
||||
x:Name="ParagraphStyleComboBox"
|
||||
Style="{StaticResource CompactComboBoxStyle}"
|
||||
MinWidth="110"
|
||||
DisplayMemberPath="Name"
|
||||
PlaceholderText="Paragraph"
|
||||
SelectionChanged="ParagraphStyleComboBox_SelectionChanged" />
|
||||
</StackPanel>
|
||||
<Border Style="{StaticResource CompactPickerBorderStyle}">
|
||||
<Grid ColumnSpacing="8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="32" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<PathIcon
|
||||
Width="14"
|
||||
Height="14"
|
||||
Margin="10,0,0,0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Data="{StaticResource ParagraphPathIcon}"
|
||||
Foreground="{ThemeResource TextFillColorPrimaryBrush}" />
|
||||
<ComboBox
|
||||
x:Name="ParagraphStyleComboBox"
|
||||
Grid.Column="1"
|
||||
MinWidth="104"
|
||||
DisplayMemberPath="Name"
|
||||
PlaceholderText="Paragraph"
|
||||
SelectionChanged="ParagraphStyleComboBox_SelectionChanged"
|
||||
Style="{StaticResource CompactComboBoxStyle}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</AppBarElementContainer>
|
||||
|
||||
<AppBarElementContainer Style="{StaticResource CompactPickerContainerStyle}" ToolTipService.ToolTip="Text color">
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<TextBlock VerticalAlignment="Center" FontWeight="SemiBold" Text="A" />
|
||||
<ComboBox
|
||||
x:Name="TextColorComboBox"
|
||||
Style="{StaticResource CompactComboBoxStyle}"
|
||||
MinWidth="116"
|
||||
ItemTemplate="{StaticResource ColorOptionTemplate}"
|
||||
PlaceholderText="Text"
|
||||
SelectionChanged="TextColorComboBox_SelectionChanged" />
|
||||
</StackPanel>
|
||||
<Border Style="{StaticResource CompactPickerBorderStyle}">
|
||||
<Grid ColumnSpacing="8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="32" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid
|
||||
Width="18"
|
||||
Height="18"
|
||||
Margin="8,0,0,0"
|
||||
VerticalAlignment="Center">
|
||||
<Rectangle
|
||||
Fill="{x:Bind SelectedTextColorBrush, Mode=OneWay}"
|
||||
RadiusX="4"
|
||||
RadiusY="4"
|
||||
Stroke="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
StrokeThickness="1" />
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="11"
|
||||
FontWeight="SemiBold"
|
||||
Text="A" />
|
||||
</Grid>
|
||||
<ComboBox
|
||||
x:Name="TextColorComboBox"
|
||||
Grid.Column="1"
|
||||
MinWidth="104"
|
||||
ItemTemplate="{StaticResource ColorOptionTemplate}"
|
||||
PlaceholderText="Text"
|
||||
SelectionChanged="TextColorComboBox_SelectionChanged"
|
||||
Style="{StaticResource CompactComboBoxStyle}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</AppBarElementContainer>
|
||||
|
||||
<AppBarElementContainer Style="{StaticResource CompactPickerContainerStyle}" ToolTipService.ToolTip="Highlight color">
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<FontIcon VerticalAlignment="Center" FontSize="14" Glyph="" />
|
||||
<ComboBox
|
||||
x:Name="HighlightColorComboBox"
|
||||
Style="{StaticResource CompactComboBoxStyle}"
|
||||
MinWidth="122"
|
||||
ItemTemplate="{StaticResource ColorOptionTemplate}"
|
||||
PlaceholderText="Highlight"
|
||||
SelectionChanged="HighlightColorComboBox_SelectionChanged" />
|
||||
</StackPanel>
|
||||
<Border Style="{StaticResource CompactPickerBorderStyle}">
|
||||
<Grid ColumnSpacing="8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="32" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid
|
||||
Width="18"
|
||||
Height="18"
|
||||
Margin="8,0,0,0"
|
||||
VerticalAlignment="Center">
|
||||
<Rectangle
|
||||
Fill="{x:Bind SelectedHighlightColorBrush, Mode=OneWay}"
|
||||
RadiusX="4"
|
||||
RadiusY="4"
|
||||
Stroke="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
StrokeThickness="1" />
|
||||
<Rectangle
|
||||
Width="10"
|
||||
Height="2"
|
||||
Margin="0,0,0,3"
|
||||
VerticalAlignment="Bottom"
|
||||
Fill="{ThemeResource TextFillColorPrimaryBrush}" />
|
||||
</Grid>
|
||||
<ComboBox
|
||||
x:Name="HighlightColorComboBox"
|
||||
Grid.Column="1"
|
||||
MinWidth="108"
|
||||
ItemTemplate="{StaticResource ColorOptionTemplate}"
|
||||
PlaceholderText="Highlight"
|
||||
SelectionChanged="HighlightColorComboBox_SelectionChanged"
|
||||
Style="{StaticResource CompactComboBoxStyle}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</AppBarElementContainer>
|
||||
|
||||
<AppBarElementContainer Style="{StaticResource CompactPickerContainerStyle}" ToolTipService.ToolTip="Line height">
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<FontIcon VerticalAlignment="Center" FontSize="14" Glyph="" />
|
||||
<ComboBox
|
||||
x:Name="LineHeightComboBox"
|
||||
Style="{StaticResource CompactComboBoxStyle}"
|
||||
MinWidth="86"
|
||||
PlaceholderText="Line"
|
||||
SelectionChanged="LineHeightComboBox_SelectionChanged" />
|
||||
</StackPanel>
|
||||
<Border Style="{StaticResource CompactPickerBorderStyle}">
|
||||
<Grid ColumnSpacing="8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="32" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<FontIcon
|
||||
Margin="9,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="14"
|
||||
Glyph="" />
|
||||
<ComboBox
|
||||
x:Name="LineHeightComboBox"
|
||||
Grid.Column="1"
|
||||
MinWidth="72"
|
||||
PlaceholderText="Line"
|
||||
SelectionChanged="LineHeightComboBox_SelectionChanged"
|
||||
Style="{StaticResource CompactComboBoxStyle}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</AppBarElementContainer>
|
||||
</toolkit:TabbedCommandBarItem>
|
||||
|
||||
<toolkit:TabbedCommandBarItem DefaultLabelPosition="Collapsed" Header="Insert">
|
||||
<AppBarButton x:Name="ImageButton" Click="ImageButton_Click" Label="Image" ToolTipService.ToolTip="Insert image">
|
||||
<AppBarButton
|
||||
x:Name="ImageButton"
|
||||
Click="ImageButton_Click"
|
||||
Label="Image"
|
||||
ToolTipService.ToolTip="Insert image">
|
||||
<AppBarButton.Icon>
|
||||
<PathIcon Data="{StaticResource AddPhotoPathIcon}" />
|
||||
</AppBarButton.Icon>
|
||||
</AppBarButton>
|
||||
|
||||
<AppBarButton x:Name="EmojiButton" Click="EmojiButton_Click" Label="Emoji" ToolTipService.ToolTip="Insert emoji">
|
||||
<AppBarButton
|
||||
x:Name="EmojiButton"
|
||||
Click="EmojiButton_Click"
|
||||
Label="Emoji"
|
||||
ToolTipService.ToolTip="Insert emoji">
|
||||
<AppBarButton.Icon>
|
||||
<PathIcon Data="{StaticResource EmojiPathIcon}" />
|
||||
</AppBarButton.Icon>
|
||||
</AppBarButton>
|
||||
|
||||
<AppBarButton x:Name="LinkButton" Click="LinkButton_Click" Label="Link" ToolTipService.ToolTip="Insert or edit link">
|
||||
<AppBarButton
|
||||
x:Name="LinkButton"
|
||||
Click="LinkButton_Click"
|
||||
Label="Link"
|
||||
ToolTipService.ToolTip="Insert or edit link">
|
||||
<AppBarButton.Icon>
|
||||
<PathIcon Data="{StaticResource AddLinkPathIcon}" />
|
||||
</AppBarButton.Icon>
|
||||
@@ -224,7 +379,11 @@
|
||||
</AppBarButton.Icon>
|
||||
</AppBarButton>
|
||||
|
||||
<AppBarButton x:Name="TableButton" Click="TableButton_Click" Label="Table" ToolTipService.ToolTip="Insert table">
|
||||
<AppBarButton
|
||||
x:Name="TableButton"
|
||||
Click="TableButton_Click"
|
||||
Label="Table"
|
||||
ToolTipService.ToolTip="Insert table">
|
||||
<AppBarButton.Icon>
|
||||
<FontIcon Glyph="" />
|
||||
</AppBarButton.Icon>
|
||||
@@ -236,13 +395,21 @@
|
||||
</toolkit:TabbedCommandBarItem>
|
||||
|
||||
<toolkit:TabbedCommandBarItem DefaultLabelPosition="Collapsed" Header="Options">
|
||||
<AppBarToggleButton x:Name="BuiltInToolbarButton" Click="BuiltInToolbarButton_Click" Label="Web toolbar" ToolTipService.ToolTip="Toggle built-in web toolbar">
|
||||
<AppBarToggleButton
|
||||
x:Name="BuiltInToolbarButton"
|
||||
Click="BuiltInToolbarButton_Click"
|
||||
Label="Web toolbar"
|
||||
ToolTipService.ToolTip="Toggle built-in web toolbar">
|
||||
<AppBarToggleButton.Icon>
|
||||
<PathIcon Data="{StaticResource WebviewToolBarPathIcon}" />
|
||||
</AppBarToggleButton.Icon>
|
||||
</AppBarToggleButton>
|
||||
|
||||
<AppBarToggleButton x:Name="SpellCheckButton" Click="SpellCheckButton_Click" Label="Spell check" ToolTipService.ToolTip="Toggle spell check">
|
||||
<AppBarToggleButton
|
||||
x:Name="SpellCheckButton"
|
||||
Click="SpellCheckButton_Click"
|
||||
Label="Spell check"
|
||||
ToolTipService.ToolTip="Toggle spell check">
|
||||
<AppBarToggleButton.Icon>
|
||||
<FontIcon Glyph="" />
|
||||
</AppBarToggleButton.Icon>
|
||||
|
||||
@@ -2,63 +2,42 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.WinUI;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Windows.UI;
|
||||
|
||||
namespace Wino.Mail.Controls;
|
||||
|
||||
public sealed partial class EditorTabbedCommandBarControl : UserControl, IEditorCommandControl
|
||||
{
|
||||
public static readonly DependencyProperty CommandTargetProperty = DependencyProperty.Register(
|
||||
nameof(CommandTarget),
|
||||
typeof(IEditorCommandTarget),
|
||||
typeof(EditorTabbedCommandBarControl),
|
||||
new PropertyMetadata(null, OnCommandTargetChanged));
|
||||
[GeneratedDependencyProperty]
|
||||
public partial IEditorCommandTarget? CommandTarget { get; set; }
|
||||
|
||||
public static readonly DependencyProperty PaneCustomContentProperty = DependencyProperty.Register(
|
||||
nameof(PaneCustomContent),
|
||||
typeof(object),
|
||||
typeof(EditorTabbedCommandBarControl),
|
||||
new PropertyMetadata(null));
|
||||
[GeneratedDependencyProperty]
|
||||
public partial object? PaneCustomContent { get; set; }
|
||||
|
||||
public static readonly DependencyProperty InsertCustomContentProperty = DependencyProperty.Register(
|
||||
nameof(InsertCustomContent),
|
||||
typeof(object),
|
||||
typeof(EditorTabbedCommandBarControl),
|
||||
new PropertyMetadata(null));
|
||||
[GeneratedDependencyProperty]
|
||||
public partial object? InsertCustomContent { get; set; }
|
||||
|
||||
public static readonly DependencyProperty OptionsCustomContentProperty = DependencyProperty.Register(
|
||||
nameof(OptionsCustomContent),
|
||||
typeof(object),
|
||||
typeof(EditorTabbedCommandBarControl),
|
||||
new PropertyMetadata(null));
|
||||
[GeneratedDependencyProperty]
|
||||
public partial object? OptionsCustomContent { get; set; }
|
||||
|
||||
[GeneratedDependencyProperty]
|
||||
public partial EditorColorOption? SelectedTextColorOption { get; set; }
|
||||
|
||||
[GeneratedDependencyProperty]
|
||||
public partial EditorColorOption? SelectedHighlightColorOption { get; set; }
|
||||
|
||||
private bool _isApplyingState;
|
||||
private IEditorCommandTarget? _subscribedTarget;
|
||||
private static readonly SolidColorBrush TransparentBrush = new(EditorColorOption.ParseColorValue(null));
|
||||
private IReadOnlyList<EditorColorOption> _textColorOptions = Array.Empty<EditorColorOption>();
|
||||
private IReadOnlyList<EditorColorOption> _highlightColorOptions = Array.Empty<EditorColorOption>();
|
||||
|
||||
public IEditorCommandTarget? CommandTarget
|
||||
{
|
||||
get => (IEditorCommandTarget?)GetValue(CommandTargetProperty);
|
||||
set => SetValue(CommandTargetProperty, value);
|
||||
}
|
||||
|
||||
public object? PaneCustomContent
|
||||
{
|
||||
get => GetValue(PaneCustomContentProperty);
|
||||
set => SetValue(PaneCustomContentProperty, value);
|
||||
}
|
||||
|
||||
public object? InsertCustomContent
|
||||
{
|
||||
get => GetValue(InsertCustomContentProperty);
|
||||
set => SetValue(InsertCustomContentProperty, value);
|
||||
}
|
||||
|
||||
public object? OptionsCustomContent
|
||||
{
|
||||
get => GetValue(OptionsCustomContentProperty);
|
||||
set => SetValue(OptionsCustomContentProperty, value);
|
||||
}
|
||||
public Brush SelectedTextColorBrush => SelectedTextColorOption?.Brush ?? TransparentBrush;
|
||||
public Brush SelectedHighlightColorBrush => SelectedHighlightColorOption?.Brush ?? TransparentBrush;
|
||||
|
||||
public EditorTabbedCommandBarControl()
|
||||
{
|
||||
@@ -104,12 +83,15 @@ public sealed partial class EditorTabbedCommandBarControl : UserControl, IEditor
|
||||
_subscribedTarget = null;
|
||||
}
|
||||
|
||||
private static void OnCommandTargetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
partial void OnCommandTargetChanged(IEditorCommandTarget? newValue)
|
||||
{
|
||||
var control = (EditorTabbedCommandBarControl)d;
|
||||
control.AttachCommandTarget((IEditorCommandTarget?)e.NewValue);
|
||||
AttachCommandTarget(newValue);
|
||||
}
|
||||
|
||||
partial void OnSelectedTextColorOptionChanged(EditorColorOption? newValue) => Bindings.Update();
|
||||
|
||||
partial void OnSelectedHighlightColorOptionChanged(EditorColorOption? newValue) => Bindings.Update();
|
||||
|
||||
private void OnLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
AttachCommandTarget(CommandTarget);
|
||||
@@ -136,8 +118,10 @@ public sealed partial class EditorTabbedCommandBarControl : UserControl, IEditor
|
||||
FontSizeComboBox.ItemsSource = capabilities.FontSizes;
|
||||
AlignmentComboBox.ItemsSource = capabilities.Alignments;
|
||||
ParagraphStyleComboBox.ItemsSource = capabilities.ParagraphStyles;
|
||||
TextColorComboBox.ItemsSource = capabilities.TextColors;
|
||||
HighlightColorComboBox.ItemsSource = capabilities.HighlightColors;
|
||||
_textColorOptions = capabilities.TextColors;
|
||||
_highlightColorOptions = capabilities.HighlightColors;
|
||||
TextColorComboBox.ItemsSource = _textColorOptions;
|
||||
HighlightColorComboBox.ItemsSource = _highlightColorOptions;
|
||||
LineHeightComboBox.ItemsSource = capabilities.LineHeights;
|
||||
}
|
||||
|
||||
@@ -163,8 +147,10 @@ public sealed partial class EditorTabbedCommandBarControl : UserControl, IEditor
|
||||
FontSizeComboBox.SelectedItem = MatchValueItem<int>(FontSizeComboBox.ItemsSource, state.FontSize);
|
||||
LineHeightComboBox.SelectedItem = MatchStringItem(LineHeightComboBox.ItemsSource, state.LineHeight);
|
||||
ParagraphStyleComboBox.SelectedItem = MatchParagraphItem(state.ParagraphStyle);
|
||||
TextColorComboBox.SelectedItem = MatchColorItem(TextColorComboBox.ItemsSource, state.TextColor);
|
||||
HighlightColorComboBox.SelectedItem = MatchColorItem(HighlightColorComboBox.ItemsSource, state.HighlightColor);
|
||||
SelectedTextColorOption = ResolveColorOption(_textColorOptions, state.TextColor);
|
||||
SelectedHighlightColorOption = ResolveColorOption(_highlightColorOptions, state.HighlightColor);
|
||||
TextColorComboBox.SelectedItem = SelectedTextColorOption;
|
||||
HighlightColorComboBox.SelectedItem = SelectedHighlightColorOption;
|
||||
|
||||
_isApplyingState = false;
|
||||
}
|
||||
@@ -207,14 +193,54 @@ public sealed partial class EditorTabbedCommandBarControl : UserControl, IEditor
|
||||
return styles.FirstOrDefault(item => string.Equals(item.Tag, tag, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
private static object? MatchColorItem(object? itemsSource, string? value)
|
||||
private static EditorColorOption? MatchColorItem(IEnumerable<EditorColorOption> colors, string? value)
|
||||
{
|
||||
if (itemsSource is not IEnumerable<EditorColorOption> colors)
|
||||
var normalizedValue = value ?? string.Empty;
|
||||
var matchedByValue = colors.FirstOrDefault(item => string.Equals(item.Value, normalizedValue, StringComparison.OrdinalIgnoreCase));
|
||||
if (matchedByValue != null)
|
||||
{
|
||||
return null;
|
||||
return matchedByValue;
|
||||
}
|
||||
|
||||
return colors.FirstOrDefault(item => string.Equals(item.Value, value ?? string.Empty, StringComparison.OrdinalIgnoreCase));
|
||||
var targetColor = EditorColorOption.ParseColorValue(value);
|
||||
return colors.FirstOrDefault(item => item.Brush.Color.Equals(targetColor));
|
||||
}
|
||||
|
||||
private static EditorColorOption? ResolveColorOption(IEnumerable<EditorColorOption> colors, string? value)
|
||||
{
|
||||
var colorOptions = colors.ToList();
|
||||
var matchedColor = MatchColorItem(colors, value);
|
||||
if (matchedColor != null)
|
||||
{
|
||||
return matchedColor;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return colorOptions.FirstOrDefault(item => string.IsNullOrWhiteSpace(item.Value));
|
||||
}
|
||||
|
||||
var targetColor = EditorColorOption.ParseColorValue(value);
|
||||
var selectableColors = colorOptions
|
||||
.Where(item => !string.IsNullOrWhiteSpace(item.Value))
|
||||
.ToList();
|
||||
|
||||
if (selectableColors.Count == 0)
|
||||
{
|
||||
return colorOptions.FirstOrDefault();
|
||||
}
|
||||
|
||||
return selectableColors
|
||||
.OrderBy(item => GetColorDistance(item.Brush.Color, targetColor))
|
||||
.First();
|
||||
}
|
||||
|
||||
private static int GetColorDistance(Color left, Color right)
|
||||
{
|
||||
var redDiff = left.R - right.R;
|
||||
var greenDiff = left.G - right.G;
|
||||
var blueDiff = left.B - right.B;
|
||||
return (redDiff * redDiff) + (greenDiff * greenDiff) + (blueDiff * blueDiff);
|
||||
}
|
||||
|
||||
private async Task ExecuteAsync(EditorCommand command)
|
||||
@@ -281,22 +307,26 @@ public sealed partial class EditorTabbedCommandBarControl : UserControl, IEditor
|
||||
|
||||
private async void TextColorComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (_isApplyingState || TextColorComboBox.SelectedItem is not EditorColorOption color)
|
||||
SelectedTextColorOption = TextColorComboBox.SelectedItem as EditorColorOption;
|
||||
|
||||
if (_isApplyingState || SelectedTextColorOption == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await ExecuteAsync(EditorCommand.SetTextColor(color.Value));
|
||||
await ExecuteAsync(EditorCommand.SetTextColor(SelectedTextColorOption.Value));
|
||||
}
|
||||
|
||||
private async void HighlightColorComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (_isApplyingState || HighlightColorComboBox.SelectedItem is not EditorColorOption color)
|
||||
SelectedHighlightColorOption = HighlightColorComboBox.SelectedItem as EditorColorOption;
|
||||
|
||||
if (_isApplyingState || SelectedHighlightColorOption == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await ExecuteAsync(EditorCommand.SetHighlightColor(color.Value));
|
||||
await ExecuteAsync(EditorCommand.SetHighlightColor(SelectedHighlightColorOption.Value));
|
||||
}
|
||||
|
||||
private async void LineHeightComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
@@ -412,6 +442,7 @@ public sealed partial class EditorTabbedCommandBarControl : UserControl, IEditor
|
||||
await ExecuteAsync(EditorCommand.InsertTable(new EditorTableCommandArgs((int)Math.Max(1, rowsBox.Value), (int)Math.Max(1, columnsBox.Value))));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
@@ -392,7 +393,9 @@ public sealed partial class WebViewEditorControl : Control, IDisposable, IEditor
|
||||
JsonSerializer.Serialize(_preferencesService.ComposerFont, BasicTypesJsonContext.Default.String),
|
||||
JsonSerializer.Serialize(_preferencesService.ComposerFontSize, BasicTypesJsonContext.Default.Int32),
|
||||
JsonSerializer.Serialize(_preferencesService.ReaderFont, BasicTypesJsonContext.Default.String),
|
||||
JsonSerializer.Serialize(_preferencesService.ReaderFontSize, BasicTypesJsonContext.Default.Int32));
|
||||
JsonSerializer.Serialize(_preferencesService.ReaderFontSize, BasicTypesJsonContext.Default.Int32),
|
||||
JsonSerializer.Serialize(DefaultTextColors.Select(option => option.Value).Where(value => !string.IsNullOrWhiteSpace(value)).ToList(), BasicTypesJsonContext.Default.ListString),
|
||||
JsonSerializer.Serialize(DefaultHighlightColors.Select(option => option.Value).Where(value => !string.IsNullOrWhiteSpace(value)).ToList(), BasicTypesJsonContext.Default.ListString));
|
||||
|
||||
UpdateCapabilities(BuildCapabilities(fonts));
|
||||
_editorReadyTask.TrySetResult(true);
|
||||
|
||||
@@ -27,13 +27,18 @@ let stateSyncQueued = false;
|
||||
let imageInputBound = false;
|
||||
let inlineFontsPluginRegistered = false;
|
||||
let lastKnownRange = null;
|
||||
let availableTextColors = [];
|
||||
let availableHighlightColors = [];
|
||||
|
||||
function initializeJodit(fonts, defaultComposerFont, defaultComposerFontSize, defaultReaderFont, defaultReaderFontSize) {
|
||||
function initializeJodit(fonts, defaultComposerFont, defaultComposerFontSize, defaultReaderFont, defaultReaderFontSize, textColors, highlightColors) {
|
||||
if (editor) {
|
||||
scheduleStateSync();
|
||||
return true;
|
||||
}
|
||||
|
||||
availableTextColors = Array.isArray(textColors) ? textColors.map(normalizeColor).filter(Boolean) : [];
|
||||
availableHighlightColors = Array.isArray(highlightColors) ? highlightColors.map(normalizeColor).filter(Boolean) : [];
|
||||
|
||||
const fontsWithFallbackObject = fonts.reduce((acc, font) => {
|
||||
acc[`'${font}',Arial,sans-serif`] = font;
|
||||
return acc;
|
||||
@@ -445,8 +450,8 @@ function buildEditorState() {
|
||||
fontFamily: normalizeFontFamily(style.fontFamily),
|
||||
fontSize: fontSize,
|
||||
paragraphStyle: normalizeParagraphTag(blockElement),
|
||||
textColor: normalizeColor(style.color),
|
||||
highlightColor: normalizeColor(style.backgroundColor),
|
||||
textColor: snapColorToPalette(resolveEditorColorValue(selectionElement, 'color', style.color), availableTextColors),
|
||||
highlightColor: snapColorToPalette(resolveEditorColorValue(selectionElement, 'backgroundColor', style.backgroundColor), availableHighlightColors),
|
||||
lineHeight: normalizeLineHeight(blockStyle.lineHeight, fontSize),
|
||||
linkUrl: linkElement ? linkElement.getAttribute('href') || '' : '',
|
||||
selectedText: selection && isSelectionInsideEditor() ? selection.toString() : ''
|
||||
@@ -634,6 +639,94 @@ function normalizeColor(value) {
|
||||
return `#${toHex(red)}${toHex(green)}${toHex(blue)}`;
|
||||
}
|
||||
|
||||
function snapColorToPalette(value, palette) {
|
||||
const normalizedColor = normalizeColor(value);
|
||||
if (!normalizedColor) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!Array.isArray(palette) || palette.length === 0) {
|
||||
return normalizedColor;
|
||||
}
|
||||
|
||||
if (palette.includes(normalizedColor)) {
|
||||
return normalizedColor;
|
||||
}
|
||||
|
||||
const targetRgb = hexToRgb(normalizedColor);
|
||||
if (!targetRgb) {
|
||||
return normalizedColor;
|
||||
}
|
||||
|
||||
let nearestColor = palette[0];
|
||||
let nearestDistance = Number.MAX_SAFE_INTEGER;
|
||||
|
||||
palette.forEach(candidate => {
|
||||
const candidateRgb = hexToRgb(candidate);
|
||||
if (!candidateRgb) {
|
||||
return;
|
||||
}
|
||||
|
||||
const distance = getColorDistance(targetRgb, candidateRgb);
|
||||
if (distance < nearestDistance) {
|
||||
nearestColor = candidate;
|
||||
nearestDistance = distance;
|
||||
}
|
||||
});
|
||||
|
||||
return nearestColor;
|
||||
}
|
||||
|
||||
function hexToRgb(value) {
|
||||
const normalized = normalizeColor(value);
|
||||
if (!normalized || !normalized.startsWith('#') || normalized.length !== 7) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
red: parseInt(normalized.slice(1, 3), 16),
|
||||
green: parseInt(normalized.slice(3, 5), 16),
|
||||
blue: parseInt(normalized.slice(5, 7), 16)
|
||||
};
|
||||
}
|
||||
|
||||
function getColorDistance(left, right) {
|
||||
const redDiff = left.red - right.red;
|
||||
const greenDiff = left.green - right.green;
|
||||
const blueDiff = left.blue - right.blue;
|
||||
return (redDiff * redDiff) + (greenDiff * greenDiff) + (blueDiff * blueDiff);
|
||||
}
|
||||
|
||||
function resolveEditorColorValue(selectionElement, propertyName, computedValue) {
|
||||
if (!editor || !editor.editor) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const darkReaderAttributeName = propertyName === 'backgroundColor'
|
||||
? 'data-darkreader-inline-bgcolor'
|
||||
: 'data-darkreader-inline-color';
|
||||
|
||||
let currentElement = selectionElement;
|
||||
while (currentElement) {
|
||||
if (currentElement.style && currentElement.style[propertyName]) {
|
||||
return currentElement.style[propertyName];
|
||||
}
|
||||
|
||||
const darkReaderValue = currentElement.getAttribute && currentElement.getAttribute(darkReaderAttributeName);
|
||||
if (darkReaderValue) {
|
||||
return darkReaderValue;
|
||||
}
|
||||
|
||||
if (currentElement === editor.editor) {
|
||||
break;
|
||||
}
|
||||
|
||||
currentElement = currentElement.parentElement;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function toHex(value) {
|
||||
return Number(value).toString(16).padStart(2, '0');
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.WinUI;
|
||||
using Microsoft.UI.Xaml;
|
||||
@@ -18,7 +17,6 @@ using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Folders;
|
||||
using Wino.Core.Domain.Models.MailItem;
|
||||
using Wino.Core.Domain.Models.Navigation;
|
||||
using Wino.Extensions;
|
||||
using Wino.Mail.ViewModels.Data;
|
||||
using Wino.Mail.WinUI;
|
||||
using Wino.Mail.WinUI.Controls;
|
||||
@@ -35,8 +33,7 @@ namespace Wino.Views;
|
||||
public sealed partial class MailAppShell : MailAppShellAbstract,
|
||||
IRecipient<AccountMenuItemExtended>,
|
||||
IRecipient<NavigateMailFolderEvent>,
|
||||
IRecipient<CreateNewMailWithMultipleAccountsRequested>,
|
||||
IRecipient<InfoBarMessageRequested>
|
||||
IRecipient<CreateNewMailWithMultipleAccountsRequested>
|
||||
{
|
||||
public Frame GetShellFrame() => InnerShellFrame;
|
||||
|
||||
@@ -306,33 +303,6 @@ public sealed partial class MailAppShell : MailAppShellAbstract,
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// InfoBar message is requested.
|
||||
/// </summary>
|
||||
public async void Receive(InfoBarMessageRequested message)
|
||||
{
|
||||
await DispatcherQueue.EnqueueAsync(async () =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(message.ActionButtonTitle) || message.Action == null)
|
||||
{
|
||||
ShellInfoBar.ActionButton = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
ShellInfoBar.ActionButton = new Button()
|
||||
{
|
||||
Content = message.ActionButtonTitle,
|
||||
Command = new RelayCommand(message.Action)
|
||||
};
|
||||
}
|
||||
|
||||
ShellInfoBar.Message = message.Message;
|
||||
ShellInfoBar.Title = message.Title;
|
||||
ShellInfoBar.Severity = message.Severity.AsMUXCInfoBarSeverity();
|
||||
ShellInfoBar.IsOpen = true;
|
||||
});
|
||||
}
|
||||
|
||||
private void NavigationViewDisplayModeChanged(NavigationView sender, NavigationViewDisplayModeChangedEventArgs args)
|
||||
{
|
||||
if (args.DisplayMode == NavigationViewDisplayMode.Minimal)
|
||||
@@ -349,7 +319,6 @@ public sealed partial class MailAppShell : MailAppShellAbstract,
|
||||
{
|
||||
base.RegisterRecipients();
|
||||
|
||||
WeakReferenceMessenger.Default.Register<InfoBarMessageRequested>(this);
|
||||
WeakReferenceMessenger.Default.Register<AccountMenuItemExtended>(this);
|
||||
WeakReferenceMessenger.Default.Register<CreateNewMailWithMultipleAccountsRequested>(this);
|
||||
WeakReferenceMessenger.Default.Register<NavigateMailFolderEvent>(this);
|
||||
@@ -359,7 +328,6 @@ public sealed partial class MailAppShell : MailAppShellAbstract,
|
||||
{
|
||||
base.UnregisterRecipients();
|
||||
|
||||
WeakReferenceMessenger.Default.Unregister<InfoBarMessageRequested>(this);
|
||||
WeakReferenceMessenger.Default.Unregister<AccountMenuItemExtended>(this);
|
||||
WeakReferenceMessenger.Default.Unregister<CreateNewMailWithMultipleAccountsRequested>(this);
|
||||
WeakReferenceMessenger.Default.Unregister<NavigateMailFolderEvent>(this);
|
||||
|
||||
@@ -33,6 +33,7 @@ public class MailAuthenticatorConfiguration : IAuthenticatorConfig
|
||||
"https://www.googleapis.com/auth/calendar",
|
||||
"https://www.googleapis.com/auth/calendar.events",
|
||||
"https://www.googleapis.com/auth/calendar.settings.readonly",
|
||||
"https://www.googleapis.com/auth/drive.file",
|
||||
];
|
||||
|
||||
public string GmailTokenStoreIdentifier => "WinoMailGmailTokenStore";
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:coreControls="using:Wino.Mail.WinUI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:domain="using:Wino.Core.Domain"
|
||||
xmlns:local="using:Wino.Mail.WinUI"
|
||||
@@ -115,6 +116,16 @@
|
||||
CacheSize="2"
|
||||
Navigated="MainFrameNavigated" />
|
||||
|
||||
<coreControls:WinoInfoBar
|
||||
x:Name="ShellInfoBar"
|
||||
Grid.RowSpan="2"
|
||||
MaxWidth="700"
|
||||
Margin="0,60,25,0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
IsClosable="False"
|
||||
IsOpen="False" />
|
||||
|
||||
<notifyicon:TaskbarIcon
|
||||
x:Name="SystemTrayIcon"
|
||||
ContextMenuMode="PopupMenu"
|
||||
|
||||
@@ -14,6 +14,7 @@ using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Synchronization;
|
||||
using Wino.Extensions;
|
||||
using Wino.Mail.WinUI.Activation;
|
||||
using Wino.Mail.WinUI.Interfaces;
|
||||
using Wino.Messaging.Client.Shell;
|
||||
@@ -25,6 +26,7 @@ namespace Wino.Mail.WinUI;
|
||||
|
||||
public sealed partial class ShellWindow : WindowEx, IWinoShellWindow,
|
||||
IRecipient<ApplicationThemeChanged>,
|
||||
IRecipient<InfoBarMessageRequested>,
|
||||
IRecipient<TitleBarShellContentUpdated>,
|
||||
IRecipient<SynchronizationActionsAdded>,
|
||||
IRecipient<SynchronizationActionsCompleted>
|
||||
@@ -186,6 +188,30 @@ public sealed partial class ShellWindow : WindowEx, IWinoShellWindow,
|
||||
UpdateTitleBarColors(message.IsUnderlyingThemeDark);
|
||||
}
|
||||
|
||||
public void Receive(InfoBarMessageRequested message)
|
||||
{
|
||||
DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(message.ActionButtonTitle) || message.Action == null)
|
||||
{
|
||||
ShellInfoBar.ActionButton = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
ShellInfoBar.ActionButton = new Button()
|
||||
{
|
||||
Content = message.ActionButtonTitle,
|
||||
Command = new RelayCommand(message.Action)
|
||||
};
|
||||
}
|
||||
|
||||
ShellInfoBar.Message = message.Message;
|
||||
ShellInfoBar.Title = message.Title;
|
||||
ShellInfoBar.Severity = message.Severity.AsMUXCInfoBarSeverity();
|
||||
ShellInfoBar.IsOpen = true;
|
||||
});
|
||||
}
|
||||
|
||||
public void Receive(SynchronizationActionsAdded message)
|
||||
{
|
||||
DispatcherQueue.TryEnqueue(() =>
|
||||
@@ -316,6 +342,7 @@ public sealed partial class ShellWindow : WindowEx, IWinoShellWindow,
|
||||
{
|
||||
WeakReferenceMessenger.Default.Register<TitleBarShellContentUpdated>(this);
|
||||
WeakReferenceMessenger.Default.Register<ApplicationThemeChanged>(this);
|
||||
WeakReferenceMessenger.Default.Register<InfoBarMessageRequested>(this);
|
||||
WeakReferenceMessenger.Default.Register<SynchronizationActionsAdded>(this);
|
||||
WeakReferenceMessenger.Default.Register<SynchronizationActionsCompleted>(this);
|
||||
}
|
||||
@@ -324,6 +351,7 @@ public sealed partial class ShellWindow : WindowEx, IWinoShellWindow,
|
||||
{
|
||||
WeakReferenceMessenger.Default.Unregister<TitleBarShellContentUpdated>(this);
|
||||
WeakReferenceMessenger.Default.Unregister<ApplicationThemeChanged>(this);
|
||||
WeakReferenceMessenger.Default.Unregister<InfoBarMessageRequested>(this);
|
||||
WeakReferenceMessenger.Default.Unregister<SynchronizationActionsAdded>(this);
|
||||
WeakReferenceMessenger.Default.Unregister<SynchronizationActionsCompleted>(this);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:animatedvisuals="using:Microsoft.UI.Xaml.Controls.AnimatedVisuals"
|
||||
xmlns:controls="using:Wino.Controls"
|
||||
xmlns:coreControls="using:Wino.Mail.WinUI.Controls"
|
||||
xmlns:coreSelectors="using:Wino.Mail.WinUI.Selectors"
|
||||
xmlns:coreViewModelData="using:Wino.Core.ViewModels.Data"
|
||||
@@ -13,6 +14,7 @@
|
||||
xmlns:menu="using:Wino.Core.Domain.MenuItems"
|
||||
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
|
||||
xmlns:personalization="using:Wino.Mail.WinUI.Models.Personalization"
|
||||
xmlns:shared="using:Wino.Core.Domain.Entities.Shared"
|
||||
xmlns:viewModelData="using:Wino.Mail.ViewModels.Data"
|
||||
xmlns:winuiControls="using:CommunityToolkit.WinUI.Controls">
|
||||
|
||||
@@ -219,6 +221,53 @@
|
||||
<Setter Property="FontWeight" Value="SemiBold" />
|
||||
</Style>
|
||||
|
||||
<DataTemplate x:Key="ContactTokenTemplate" x:DataType="shared:IContactDisplayItem">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ToolTipService.ToolTip>
|
||||
<ToolTip Content="{x:Bind Address}" />
|
||||
</ToolTipService.ToolTip>
|
||||
|
||||
<Viewbox Width="24">
|
||||
<controls:ImagePreviewControl
|
||||
Address="{x:Bind Address}"
|
||||
DisplayNameOverride="{x:Bind DisplayName}"
|
||||
PreviewContact="{x:Bind PreviewContact}" />
|
||||
</Viewbox>
|
||||
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
Margin="6,0,8,0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Bind DisplayName}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="ContactSuggestionTemplate" x:DataType="shared:IContactDisplayItem">
|
||||
<Grid Margin="0,12" ColumnSpacing="6">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<controls:ImagePreviewControl
|
||||
Width="40"
|
||||
Height="40"
|
||||
Address="{x:Bind Address}"
|
||||
DisplayNameOverride="{x:Bind DisplayName}"
|
||||
PreviewContact="{x:Bind PreviewContact}" />
|
||||
|
||||
<TextBlock Grid.Column="1">
|
||||
<Run FontWeight="SemiBold" Text="{x:Bind DisplayName}" />
|
||||
<LineBreak />
|
||||
<Run Text="{x:Bind Address}" />
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
|
||||
<!--#endregion-->
|
||||
|
||||
</ResourceDictionary>
|
||||
|
||||
@@ -29,16 +29,6 @@
|
||||
<Setter Property="CornerRadius" Value="4" />
|
||||
</Style>
|
||||
|
||||
<DataTemplate x:Key="AttendeeSuggestionTemplate" x:DataType="shared:AccountContact">
|
||||
<StackPanel Padding="8,4" Orientation="Vertical">
|
||||
<TextBlock FontWeight="SemiBold" Text="{x:Bind Name}" />
|
||||
<TextBlock FontSize="12" Text="{x:Bind Address}" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="AttendeeTokenTemplate" x:DataType="data:CalendarComposeAttendeeViewModel">
|
||||
<TextBlock Text="{x:Bind DisplayName}" />
|
||||
</DataTemplate>
|
||||
</Page.Resources>
|
||||
|
||||
<Grid>
|
||||
@@ -60,9 +50,7 @@
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Left: Calendar, Show As, Reminder -->
|
||||
<StackPanel
|
||||
Orientation="Horizontal"
|
||||
Spacing="12">
|
||||
<StackPanel Orientation="Horizontal" Spacing="12">
|
||||
|
||||
<!-- Calendar -->
|
||||
<StackPanel
|
||||
@@ -410,10 +398,10 @@
|
||||
ItemsSource="{x:Bind ViewModel.Attendees, Mode=OneWay}"
|
||||
LostFocus="AddressBoxLostFocus"
|
||||
PlaceholderText="{x:Bind domain:Translator.CalendarEventDetails_InviteSomeone}"
|
||||
SuggestedItemTemplate="{StaticResource AttendeeSuggestionTemplate}"
|
||||
SuggestedItemTemplate="{StaticResource ContactSuggestionTemplate}"
|
||||
TokenDelimiter=";"
|
||||
TokenItemAdding="TokenItemAdding"
|
||||
TokenItemTemplate="{StaticResource AttendeeTokenTemplate}" />
|
||||
TokenItemTemplate="{StaticResource ContactTokenTemplate}" />
|
||||
|
||||
<ListView
|
||||
Margin="-8,0,-8,-8"
|
||||
@@ -475,10 +463,15 @@
|
||||
|
||||
<!-- Attachments Pane -->
|
||||
<Border
|
||||
x:Name="AttachmentsPane"
|
||||
Margin="0,8,0,0"
|
||||
AllowDrop="True"
|
||||
Padding="16"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
CornerRadius="{StaticResource ControlCornerRadius}"
|
||||
DragLeave="AttachmentsPane_DragLeave"
|
||||
DragOver="AttachmentsPane_DragOver"
|
||||
Drop="AttachmentsPane_Drop"
|
||||
Visibility="{x:Bind AttachmentsToggle.IsChecked, Mode=OneWay}">
|
||||
<StackPanel Spacing="8">
|
||||
<Button
|
||||
@@ -487,21 +480,21 @@
|
||||
Style="{StaticResource TransparentActionButtonStyle}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<coreControls:WinoFontIcon FontSize="14" Icon="AttachmentNew" />
|
||||
<TextBlock Text="{x:Bind domain:Translator.CalendarEventCompose_AddAttachment}" />
|
||||
<TextBlock FontSize="18" FontWeight="SemiBold" Text="+" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<ListView
|
||||
<GridView
|
||||
Margin="-8,0,-8,-8"
|
||||
ItemsSource="{x:Bind ViewModel.Attachments, Mode=OneWay}"
|
||||
SelectionMode="None">
|
||||
<ListView.ItemContainerStyle>
|
||||
<Style TargetType="ListViewItem">
|
||||
<!--<ListView.ItemContainerStyle>
|
||||
<Style BasedOn="{StaticResource DefaultListViewItemStyle}" TargetType="ListViewItem">
|
||||
<Setter Property="Padding" Value="8,4" />
|
||||
<Setter Property="MinHeight" Value="0" />
|
||||
</Style>
|
||||
</ListView.ItemContainerStyle>
|
||||
<ListView.ItemTemplate>
|
||||
</ListView.ItemContainerStyle>-->
|
||||
<GridView.ItemTemplate>
|
||||
<DataTemplate x:DataType="data:CalendarComposeAttachmentViewModel">
|
||||
<Grid Height="44" ColumnSpacing="8">
|
||||
<Grid.ColumnDefinitions>
|
||||
@@ -536,12 +529,15 @@
|
||||
Click="RemoveAttachmentClicked"
|
||||
Style="{StaticResource TransparentActionButtonStyle}"
|
||||
Tag="{x:Bind}">
|
||||
<coreControls:WinoFontIcon FontSize="12" Icon="Delete" />
|
||||
<coreControls:WinoFontIcon
|
||||
FontSize="20"
|
||||
Foreground="{ThemeResource DeleteBrush}"
|
||||
Icon="Delete" />
|
||||
</Button>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</GridView.ItemTemplate>
|
||||
</GridView>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
@@ -560,7 +556,7 @@
|
||||
<mailControls:EditorTabbedCommandBarControl CommandTarget="{x:Bind NotesEditor}" />
|
||||
<mailControls:WebViewEditorControl
|
||||
x:Name="NotesEditor"
|
||||
MinHeight="600"
|
||||
MinHeight="500"
|
||||
IsEditorDarkMode="{x:Bind ViewModel.IsDarkWebviewRenderer, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
@@ -9,7 +9,10 @@ using EmailValidation;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using Windows.ApplicationModel.DataTransfer;
|
||||
using Windows.Foundation;
|
||||
using Windows.Storage;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Messaging.Client.Shell;
|
||||
using Wino.Calendar.ViewModels.Data;
|
||||
using Wino.Mail.WinUI.Views.Abstract;
|
||||
@@ -145,6 +148,42 @@ public sealed partial class CalendarEventComposePage : CalendarEventComposePageA
|
||||
}
|
||||
}
|
||||
|
||||
private void AttachmentsPane_DragOver(object sender, DragEventArgs e)
|
||||
{
|
||||
e.AcceptedOperation = e.DataView.Contains(StandardDataFormats.StorageItems)
|
||||
? DataPackageOperation.Copy
|
||||
: DataPackageOperation.None;
|
||||
|
||||
if (e.AcceptedOperation == DataPackageOperation.Copy)
|
||||
{
|
||||
e.DragUIOverride.Caption = Translator.ComposerAttachmentsDragDropAttach_Message;
|
||||
e.DragUIOverride.IsCaptionVisible = true;
|
||||
e.DragUIOverride.IsGlyphVisible = true;
|
||||
e.DragUIOverride.IsContentVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void AttachmentsPane_DragLeave(object sender, DragEventArgs e)
|
||||
{
|
||||
}
|
||||
|
||||
private async void AttachmentsPane_Drop(object sender, DragEventArgs e)
|
||||
{
|
||||
if (!e.DataView.Contains(StandardDataFormats.StorageItems))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var storageItems = await e.DataView.GetStorageItemsAsync();
|
||||
var files = storageItems.OfType<StorageFile>();
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
var basicProperties = await file.GetBasicPropertiesAsync();
|
||||
await ViewModel.ExecuteUIThread(() => ViewModel.TryAddAttachment(file.Path, (long)basicProperties.Size));
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(ApplicationThemeChanged message)
|
||||
{
|
||||
ViewModel.IsDarkWebviewRenderer = message.IsUnderlyingThemeDark;
|
||||
|
||||
@@ -25,47 +25,6 @@
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Page.Resources>
|
||||
<DataTemplate x:Key="TokenBoxTemplate" x:DataType="entities:AccountContact">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ToolTipService.ToolTip>
|
||||
<ToolTip Content="{x:Bind Address}" />
|
||||
</ToolTipService.ToolTip>
|
||||
|
||||
<!-- TODO: Display contact info. -->
|
||||
<!--<Grid.ContextFlyout>
|
||||
<MenuFlyout Placement="RightEdgeAlignedBottom">
|
||||
<MenuFlyoutItem Text="{x:Bind domain:Translator.ViewContactDetails}" />
|
||||
</MenuFlyout>
|
||||
</Grid.ContextFlyout>-->
|
||||
|
||||
<Viewbox Width="24">
|
||||
<controls:ImagePreviewControl PreviewContact="{x:Bind}" />
|
||||
</Viewbox>
|
||||
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
Margin="6,0,8,0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Bind Name}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="SuggestionBoxTemplate" x:DataType="entities:AccountContact">
|
||||
<Grid Margin="0,12" ColumnSpacing="6">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<controls:ImagePreviewControl PreviewContact="{x:Bind}" />
|
||||
<TextBlock Grid.Column="1">
|
||||
<Run FontWeight="SemiBold" Text="{x:Bind Name}" /><LineBreak /><Run Text="{x:Bind Address}" />
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
|
||||
<!-- Attachment Template -->
|
||||
<!-- Margin -8 0 is used to remove the padding from the ListViewItem -->
|
||||
<DataTemplate x:Key="ComposerFileAttachmentTemplate" x:DataType="data:MailAttachmentViewModel">
|
||||
@@ -186,13 +145,7 @@
|
||||
Visibility="{x:Bind ViewModel.IsDraftBusy, Mode=OneWay}">
|
||||
<ProgressRing IsActive="True" />
|
||||
</AppBarButton>
|
||||
|
||||
<AppBarButton Command="{x:Bind ViewModel.DiscardCommand}" Label="{x:Bind domain:Translator.Buttons_Discard}">
|
||||
<AppBarButton.Icon>
|
||||
<coreControls:WinoFontIcon Icon="Delete" />
|
||||
</AppBarButton.Icon>
|
||||
</AppBarButton>
|
||||
<AppBarToggleButton
|
||||
<AppBarToggleButton
|
||||
x:Name="EditorThemeToggleButton"
|
||||
IsChecked="{x:Bind WebViewEditor.IsEditorDarkMode, Mode=TwoWay}"
|
||||
Label=""
|
||||
@@ -201,6 +154,12 @@
|
||||
<coreControls:WinoFontIcon Icon="DarkEditor" />
|
||||
</AppBarToggleButton.Icon>
|
||||
</AppBarToggleButton>
|
||||
<AppBarButton Command="{x:Bind ViewModel.DiscardCommand}" Label="{x:Bind domain:Translator.Buttons_Discard}">
|
||||
<AppBarButton.Icon>
|
||||
<coreControls:WinoFontIcon Icon="Delete" />
|
||||
</AppBarButton.Icon>
|
||||
</AppBarButton>
|
||||
|
||||
<AppBarButton
|
||||
Command="{x:Bind ViewModel.SendCommand}"
|
||||
Label="{x:Bind domain:Translator.Buttons_Send}"
|
||||
@@ -226,7 +185,7 @@
|
||||
<AppBarButton
|
||||
x:Name="FilesButton"
|
||||
Command="{x:Bind ViewModel.AttachFilesCommand}"
|
||||
Label="{x:Bind domain:Translator.Files}">
|
||||
LabelPosition="Collapsed">
|
||||
<AppBarButton.Icon>
|
||||
<PathIcon Data="{StaticResource AttachPathIcon}" />
|
||||
</AppBarButton.Icon>
|
||||
@@ -388,11 +347,11 @@
|
||||
ItemsSource="{x:Bind ViewModel.ToItems, Mode=OneTime}"
|
||||
LostFocus="AddressBoxLostFocus"
|
||||
PlaceholderText="{x:Bind domain:Translator.ComposerToPlaceholder}"
|
||||
SuggestedItemTemplate="{StaticResource SuggestionBoxTemplate}"
|
||||
SuggestedItemTemplate="{StaticResource ContactSuggestionTemplate}"
|
||||
Tag="ToBox"
|
||||
TokenDelimiter=";"
|
||||
TokenItemAdding="TokenItemAdding"
|
||||
TokenItemTemplate="{StaticResource TokenBoxTemplate}" />
|
||||
TokenItemTemplate="{StaticResource ContactTokenTemplate}" />
|
||||
|
||||
<Button
|
||||
x:Name="CCBCCShowButton"
|
||||
@@ -430,11 +389,11 @@
|
||||
ItemsSource="{x:Bind ViewModel.CCItems, Mode=OneTime}"
|
||||
LostFocus="AddressBoxLostFocus"
|
||||
PlaceholderText="{x:Bind domain:Translator.ComposerToPlaceholder}"
|
||||
SuggestedItemTemplate="{StaticResource SuggestionBoxTemplate}"
|
||||
SuggestedItemTemplate="{StaticResource ContactSuggestionTemplate}"
|
||||
Tag="CCBox"
|
||||
TokenDelimiter=";"
|
||||
TokenItemAdding="TokenItemAdding"
|
||||
TokenItemTemplate="{StaticResource TokenBoxTemplate}"
|
||||
TokenItemTemplate="{StaticResource ContactTokenTemplate}"
|
||||
Visibility="{x:Bind ViewModel.IsCCBCCVisible, Mode=OneWay}" />
|
||||
|
||||
<TextBlock
|
||||
@@ -453,11 +412,11 @@
|
||||
ItemsSource="{x:Bind ViewModel.BCCItems, Mode=OneTime}"
|
||||
LostFocus="AddressBoxLostFocus"
|
||||
PlaceholderText="{x:Bind domain:Translator.ComposerToPlaceholder}"
|
||||
SuggestedItemTemplate="{StaticResource SuggestionBoxTemplate}"
|
||||
SuggestedItemTemplate="{StaticResource ContactSuggestionTemplate}"
|
||||
Tag="BCCBox"
|
||||
TokenDelimiter=";"
|
||||
TokenItemAdding="TokenItemAdding"
|
||||
TokenItemTemplate="{StaticResource TokenBoxTemplate}"
|
||||
TokenItemTemplate="{StaticResource ContactTokenTemplate}"
|
||||
Visibility="{x:Bind ViewModel.IsCCBCCVisible, Mode=OneWay}" />
|
||||
|
||||
<!-- Subject -->
|
||||
|
||||
Reference in New Issue
Block a user