Refactoring the html editor toolbar.

This commit is contained in:
Burak Kaan Köse
2026-03-07 23:33:25 +01:00
parent ebc35c3de8
commit 1da34080d1
19 changed files with 754 additions and 280 deletions
+91 -3
View File
@@ -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="&#xE8D2;" />
<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="&#xE8D2;" />
<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="&#xE7C3;" />
<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="&#xE8C8;" />
<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="&#xE8C8;" />
<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="&#xE14C;" />
</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="&#xEA53;" />
</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);
+96 -3
View File
@@ -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');
}
+1 -33
View File
@@ -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";
+11
View File
@@ -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"
+28
View File
@@ -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);
}
+49
View File
@@ -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;
+14 -55
View File
@@ -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 -->