Web editor refactoring and some calendar occurrence summary stuff.
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Windows.UI;
|
||||
|
||||
namespace Wino.Mail.Controls;
|
||||
|
||||
public interface IEditorCommandTarget
|
||||
{
|
||||
EditorState CurrentState { get; }
|
||||
EditorCapabilities Capabilities { get; }
|
||||
event EventHandler<EditorState>? StateChanged;
|
||||
event EventHandler<EditorCapabilities>? CapabilitiesChanged;
|
||||
Task ExecuteCommandAsync(EditorCommand command);
|
||||
}
|
||||
|
||||
public interface IEditorCommandControl
|
||||
{
|
||||
IEditorCommandTarget? CommandTarget { get; set; }
|
||||
void AttachCommandTarget(IEditorCommandTarget? target);
|
||||
void DetachCommandTarget();
|
||||
}
|
||||
|
||||
public enum EditorCommandKind
|
||||
{
|
||||
ToggleBold,
|
||||
ToggleItalic,
|
||||
ToggleUnderline,
|
||||
ToggleStrikethrough,
|
||||
ToggleOrderedList,
|
||||
ToggleUnorderedList,
|
||||
Indent,
|
||||
Outdent,
|
||||
SetAlignment,
|
||||
SetFontFamily,
|
||||
SetFontSize,
|
||||
SetParagraphStyle,
|
||||
SetTextColor,
|
||||
SetHighlightColor,
|
||||
SetLineHeight,
|
||||
InsertImage,
|
||||
InsertLink,
|
||||
RemoveLink,
|
||||
InsertEmoji,
|
||||
InsertTable,
|
||||
ToggleBuiltInToolbar,
|
||||
ToggleTheme,
|
||||
ToggleSpellCheck
|
||||
}
|
||||
|
||||
public enum EditorTextAlignment
|
||||
{
|
||||
Left,
|
||||
Center,
|
||||
Right,
|
||||
Justify
|
||||
}
|
||||
|
||||
public sealed record class EditorCommand(EditorCommandKind Kind, object? Value = null)
|
||||
{
|
||||
public static EditorCommand ToggleBold() => new(EditorCommandKind.ToggleBold);
|
||||
public static EditorCommand ToggleItalic() => new(EditorCommandKind.ToggleItalic);
|
||||
public static EditorCommand ToggleUnderline() => new(EditorCommandKind.ToggleUnderline);
|
||||
public static EditorCommand ToggleStrikethrough() => new(EditorCommandKind.ToggleStrikethrough);
|
||||
public static EditorCommand ToggleOrderedList() => new(EditorCommandKind.ToggleOrderedList);
|
||||
public static EditorCommand ToggleUnorderedList() => new(EditorCommandKind.ToggleUnorderedList);
|
||||
public static EditorCommand Indent() => new(EditorCommandKind.Indent);
|
||||
public static EditorCommand Outdent() => new(EditorCommandKind.Outdent);
|
||||
public static EditorCommand SetAlignment(EditorTextAlignment alignment) => new(EditorCommandKind.SetAlignment, alignment);
|
||||
public static EditorCommand SetFontFamily(string fontFamily) => new(EditorCommandKind.SetFontFamily, fontFamily);
|
||||
public static EditorCommand SetFontSize(int fontSize) => new(EditorCommandKind.SetFontSize, fontSize);
|
||||
public static EditorCommand SetParagraphStyle(string tagName) => new(EditorCommandKind.SetParagraphStyle, tagName);
|
||||
public static EditorCommand SetTextColor(string color) => new(EditorCommandKind.SetTextColor, color);
|
||||
public static EditorCommand SetHighlightColor(string color) => new(EditorCommandKind.SetHighlightColor, color);
|
||||
public static EditorCommand SetLineHeight(string lineHeight) => new(EditorCommandKind.SetLineHeight, lineHeight);
|
||||
public static EditorCommand InsertImage() => new(EditorCommandKind.InsertImage);
|
||||
public static EditorCommand InsertEmoji() => new(EditorCommandKind.InsertEmoji);
|
||||
public static EditorCommand InsertLink(EditorLinkCommandArgs args) => new(EditorCommandKind.InsertLink, args);
|
||||
public static EditorCommand RemoveLink() => new(EditorCommandKind.RemoveLink);
|
||||
public static EditorCommand InsertTable(EditorTableCommandArgs args) => new(EditorCommandKind.InsertTable, args);
|
||||
public static EditorCommand ToggleBuiltInToolbar(bool isVisible) => new(EditorCommandKind.ToggleBuiltInToolbar, isVisible);
|
||||
public static EditorCommand ToggleTheme(bool isDarkMode) => new(EditorCommandKind.ToggleTheme, isDarkMode);
|
||||
public static EditorCommand ToggleSpellCheck(bool isEnabled) => new(EditorCommandKind.ToggleSpellCheck, isEnabled);
|
||||
}
|
||||
|
||||
public sealed record class EditorLinkCommandArgs(
|
||||
[property: JsonPropertyName("url")] string Url,
|
||||
[property: JsonPropertyName("text")] string? Text = null,
|
||||
[property: JsonPropertyName("openInNewWindow")] bool OpenInNewWindow = true);
|
||||
|
||||
public sealed record class EditorTableCommandArgs(
|
||||
[property: JsonPropertyName("rows")] int Rows,
|
||||
[property: JsonPropertyName("columns")] int Columns);
|
||||
|
||||
public sealed record class EditorColorOption(string Name, string Value)
|
||||
{
|
||||
public SolidColorBrush Brush => new(ParseColor(Value));
|
||||
|
||||
private static Color ParseColor(string? value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return Colors.Transparent;
|
||||
}
|
||||
|
||||
var hex = value.Trim().TrimStart('#');
|
||||
if (hex.Length == 6)
|
||||
{
|
||||
hex = $"FF{hex}";
|
||||
}
|
||||
|
||||
if (hex.Length != 8 || !uint.TryParse(hex, System.Globalization.NumberStyles.HexNumber, null, out var argb))
|
||||
{
|
||||
return Colors.Transparent;
|
||||
}
|
||||
|
||||
return Color.FromArgb(
|
||||
(byte)((argb >> 24) & 0xFF),
|
||||
(byte)((argb >> 16) & 0xFF),
|
||||
(byte)((argb >> 8) & 0xFF),
|
||||
(byte)(argb & 0xFF));
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record class EditorParagraphStyleOption(string Name, string Tag);
|
||||
|
||||
public sealed record class EditorCapabilities
|
||||
{
|
||||
public IReadOnlyList<string> Fonts { get; init; } = Array.Empty<string>();
|
||||
public IReadOnlyList<int> FontSizes { get; init; } = Array.Empty<int>();
|
||||
public IReadOnlyList<EditorColorOption> TextColors { get; init; } = Array.Empty<EditorColorOption>();
|
||||
public IReadOnlyList<EditorColorOption> HighlightColors { get; init; } = Array.Empty<EditorColorOption>();
|
||||
public IReadOnlyList<EditorParagraphStyleOption> ParagraphStyles { get; init; } = Array.Empty<EditorParagraphStyleOption>();
|
||||
public IReadOnlyList<string> LineHeights { get; init; } = Array.Empty<string>();
|
||||
public IReadOnlyList<EditorTextAlignment> Alignments { get; init; } = Array.Empty<EditorTextAlignment>();
|
||||
}
|
||||
|
||||
public sealed record class EditorState
|
||||
{
|
||||
public bool IsBold { get; init; }
|
||||
public bool IsItalic { get; init; }
|
||||
public bool IsUnderline { get; init; }
|
||||
public bool IsStrikethrough { get; init; }
|
||||
public bool IsOrderedList { get; init; }
|
||||
public bool IsUnorderedList { get; init; }
|
||||
public bool CanIndent { get; init; } = true;
|
||||
public bool CanOutdent { get; init; }
|
||||
public bool HasSelection { get; init; }
|
||||
public bool IsDarkMode { get; init; }
|
||||
public bool IsBuiltInToolbarVisible { get; init; }
|
||||
public bool IsSpellCheckEnabled { get; init; } = true;
|
||||
public EditorTextAlignment Alignment { get; init; } = EditorTextAlignment.Left;
|
||||
public string? FontFamily { get; init; }
|
||||
public int? FontSize { get; init; }
|
||||
public string? ParagraphStyle { get; init; }
|
||||
public string? TextColor { get; init; }
|
||||
public string? HighlightColor { get; init; }
|
||||
public string? LineHeight { get; init; }
|
||||
public string? LinkUrl { get; init; }
|
||||
public string? SelectedText { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
<UserControl
|
||||
x:Class="Wino.Mail.Controls.EditorTabbedCommandBarControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:Wino.Mail.WinUI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mail="using:Wino.Mail.Controls"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:toolkit="using:CommunityToolkit.WinUI.Controls"
|
||||
x:Name="Root"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<UserControl.Resources>
|
||||
<Style x:Key="CompactComboBoxStyle" TargetType="ComboBox">
|
||||
<Setter Property="MinWidth" Value="88" />
|
||||
<Setter Property="MaxWidth" Value="136" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="CompactPickerContainerStyle" TargetType="AppBarElementContainer">
|
||||
<Setter Property="MinWidth" Value="0" />
|
||||
<Setter Property="Margin" Value="0" />
|
||||
</Style>
|
||||
|
||||
<DataTemplate x:Key="ColorOptionTemplate" x:DataType="mail:EditorColorOption">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<Grid Width="14" Height="14">
|
||||
<Rectangle
|
||||
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>
|
||||
<toolkit:TabbedCommandBar.Resources>
|
||||
<SolidColorBrush x:Key="TabContentContentBorderBackground" Color="Transparent" />
|
||||
<SolidColorBrush x:Key="TabContentContentBorderBorderBrush" Color="Transparent" />
|
||||
<Thickness x:Key="TabContentBorderBorderThickness">0</Thickness>
|
||||
</toolkit:TabbedCommandBar.Resources>
|
||||
|
||||
<toolkit:TabbedCommandBar.PaneCustomContent>
|
||||
<ContentPresenter Content="{x:Bind PaneCustomContent, Mode=OneWay}" />
|
||||
</toolkit:TabbedCommandBar.PaneCustomContent>
|
||||
|
||||
<toolkit:TabbedCommandBar.MenuItems>
|
||||
<toolkit:TabbedCommandBarItem DefaultLabelPosition="Collapsed" Header="Format">
|
||||
<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.Icon>
|
||||
<PathIcon Data="{StaticResource ItalicPathIcon}" />
|
||||
</AppBarToggleButton.Icon>
|
||||
</AppBarToggleButton>
|
||||
|
||||
<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.Icon>
|
||||
<PathIcon Data="{StaticResource StrikePathIcon}" />
|
||||
</AppBarToggleButton.Icon>
|
||||
</AppBarToggleButton>
|
||||
|
||||
<AppBarSeparator />
|
||||
|
||||
<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.Icon>
|
||||
<PathIcon Data="{StaticResource OrderedListPathIcon}" />
|
||||
</AppBarToggleButton.Icon>
|
||||
</AppBarToggleButton>
|
||||
|
||||
<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.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>
|
||||
</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>
|
||||
</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>
|
||||
</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>
|
||||
</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>
|
||||
</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>
|
||||
</AppBarElementContainer>
|
||||
</toolkit:TabbedCommandBarItem>
|
||||
|
||||
<toolkit:TabbedCommandBarItem DefaultLabelPosition="Collapsed" Header="Insert">
|
||||
<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.Icon>
|
||||
<PathIcon Data="{StaticResource EmojiPathIcon}" />
|
||||
</AppBarButton.Icon>
|
||||
</AppBarButton>
|
||||
|
||||
<AppBarButton x:Name="LinkButton" Click="LinkButton_Click" Label="Link" ToolTipService.ToolTip="Insert or edit link">
|
||||
<AppBarButton.Icon>
|
||||
<PathIcon Data="{StaticResource AddLinkPathIcon}" />
|
||||
</AppBarButton.Icon>
|
||||
</AppBarButton>
|
||||
|
||||
<AppBarButton
|
||||
x:Name="RemoveLinkButton"
|
||||
Click="RemoveLinkButton_Click"
|
||||
Label="Remove link"
|
||||
ToolTipService.ToolTip="Remove link"
|
||||
Visibility="Collapsed">
|
||||
<AppBarButton.Icon>
|
||||
<SymbolIcon Symbol="Remove" />
|
||||
</AppBarButton.Icon>
|
||||
</AppBarButton>
|
||||
|
||||
<AppBarButton x:Name="TableButton" Click="TableButton_Click" Label="Table" ToolTipService.ToolTip="Insert table">
|
||||
<AppBarButton.Icon>
|
||||
<FontIcon Glyph="" />
|
||||
</AppBarButton.Icon>
|
||||
</AppBarButton>
|
||||
|
||||
<AppBarElementContainer ToolTipService.ToolTip="Insert actions">
|
||||
<ContentPresenter Content="{x:Bind InsertCustomContent, Mode=OneWay}" />
|
||||
</AppBarElementContainer>
|
||||
</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.Icon>
|
||||
<PathIcon Data="{StaticResource WebviewToolBarPathIcon}" />
|
||||
</AppBarToggleButton.Icon>
|
||||
</AppBarToggleButton>
|
||||
|
||||
<AppBarToggleButton x:Name="SpellCheckButton" Click="SpellCheckButton_Click" Label="Spell check" ToolTipService.ToolTip="Toggle spell check">
|
||||
<AppBarToggleButton.Icon>
|
||||
<FontIcon Glyph="" />
|
||||
</AppBarToggleButton.Icon>
|
||||
</AppBarToggleButton>
|
||||
|
||||
<AppBarElementContainer ToolTipService.ToolTip="Composer options">
|
||||
<ContentPresenter Content="{x:Bind OptionsCustomContent, Mode=OneWay}" />
|
||||
</AppBarElementContainer>
|
||||
</toolkit:TabbedCommandBarItem>
|
||||
</toolkit:TabbedCommandBar.MenuItems>
|
||||
</toolkit:TabbedCommandBar>
|
||||
</UserControl>
|
||||
|
||||
|
||||
@@ -0,0 +1,418 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
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));
|
||||
|
||||
public static readonly DependencyProperty PaneCustomContentProperty = DependencyProperty.Register(
|
||||
nameof(PaneCustomContent),
|
||||
typeof(object),
|
||||
typeof(EditorTabbedCommandBarControl),
|
||||
new PropertyMetadata(null));
|
||||
|
||||
public static readonly DependencyProperty InsertCustomContentProperty = DependencyProperty.Register(
|
||||
nameof(InsertCustomContent),
|
||||
typeof(object),
|
||||
typeof(EditorTabbedCommandBarControl),
|
||||
new PropertyMetadata(null));
|
||||
|
||||
public static readonly DependencyProperty OptionsCustomContentProperty = DependencyProperty.Register(
|
||||
nameof(OptionsCustomContent),
|
||||
typeof(object),
|
||||
typeof(EditorTabbedCommandBarControl),
|
||||
new PropertyMetadata(null));
|
||||
|
||||
private bool _isApplyingState;
|
||||
private IEditorCommandTarget? _subscribedTarget;
|
||||
|
||||
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 EditorTabbedCommandBarControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
Loaded += OnLoaded;
|
||||
Unloaded += OnUnloaded;
|
||||
}
|
||||
|
||||
public void AttachCommandTarget(IEditorCommandTarget? target)
|
||||
{
|
||||
if (_subscribedTarget == target)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_subscribedTarget != null)
|
||||
{
|
||||
_subscribedTarget.StateChanged -= CommandTarget_StateChanged;
|
||||
_subscribedTarget.CapabilitiesChanged -= CommandTarget_CapabilitiesChanged;
|
||||
}
|
||||
|
||||
_subscribedTarget = target;
|
||||
|
||||
if (_subscribedTarget != null)
|
||||
{
|
||||
_subscribedTarget.StateChanged += CommandTarget_StateChanged;
|
||||
_subscribedTarget.CapabilitiesChanged += CommandTarget_CapabilitiesChanged;
|
||||
ApplyCapabilities(_subscribedTarget.Capabilities);
|
||||
ApplyState(_subscribedTarget.CurrentState);
|
||||
}
|
||||
}
|
||||
|
||||
public void DetachCommandTarget()
|
||||
{
|
||||
if (_subscribedTarget == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_subscribedTarget.StateChanged -= CommandTarget_StateChanged;
|
||||
_subscribedTarget.CapabilitiesChanged -= CommandTarget_CapabilitiesChanged;
|
||||
_subscribedTarget = null;
|
||||
}
|
||||
|
||||
private static void OnCommandTargetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var control = (EditorTabbedCommandBarControl)d;
|
||||
control.AttachCommandTarget((IEditorCommandTarget?)e.NewValue);
|
||||
}
|
||||
|
||||
private void OnLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
AttachCommandTarget(CommandTarget);
|
||||
}
|
||||
|
||||
private void OnUnloaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DetachCommandTarget();
|
||||
}
|
||||
|
||||
private void CommandTarget_StateChanged(object? sender, EditorState e)
|
||||
{
|
||||
ApplyState(e);
|
||||
}
|
||||
|
||||
private void CommandTarget_CapabilitiesChanged(object? sender, EditorCapabilities e)
|
||||
{
|
||||
ApplyCapabilities(e);
|
||||
}
|
||||
|
||||
private void ApplyCapabilities(EditorCapabilities capabilities)
|
||||
{
|
||||
FontFamilyComboBox.ItemsSource = capabilities.Fonts;
|
||||
FontSizeComboBox.ItemsSource = capabilities.FontSizes;
|
||||
AlignmentComboBox.ItemsSource = capabilities.Alignments;
|
||||
ParagraphStyleComboBox.ItemsSource = capabilities.ParagraphStyles;
|
||||
TextColorComboBox.ItemsSource = capabilities.TextColors;
|
||||
HighlightColorComboBox.ItemsSource = capabilities.HighlightColors;
|
||||
LineHeightComboBox.ItemsSource = capabilities.LineHeights;
|
||||
}
|
||||
|
||||
private void ApplyState(EditorState state)
|
||||
{
|
||||
_isApplyingState = true;
|
||||
|
||||
BoldButton.IsChecked = state.IsBold;
|
||||
ItalicButton.IsChecked = state.IsItalic;
|
||||
UnderlineButton.IsChecked = state.IsUnderline;
|
||||
StrikeButton.IsChecked = state.IsStrikethrough;
|
||||
BulletListButton.IsChecked = state.IsUnorderedList;
|
||||
OrderedListButton.IsChecked = state.IsOrderedList;
|
||||
IndentButton.IsEnabled = state.CanIndent;
|
||||
OutdentButton.IsEnabled = state.CanOutdent;
|
||||
RemoveLinkButton.IsEnabled = !string.IsNullOrWhiteSpace(state.LinkUrl);
|
||||
RemoveLinkButton.Visibility = RemoveLinkButton.IsEnabled ? Visibility.Visible : Visibility.Collapsed;
|
||||
BuiltInToolbarButton.IsChecked = state.IsBuiltInToolbarVisible;
|
||||
SpellCheckButton.IsChecked = state.IsSpellCheckEnabled;
|
||||
|
||||
AlignmentComboBox.SelectedItem = state.Alignment;
|
||||
FontFamilyComboBox.SelectedItem = MatchStringItem(FontFamilyComboBox.ItemsSource, state.FontFamily);
|
||||
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);
|
||||
|
||||
_isApplyingState = false;
|
||||
}
|
||||
|
||||
private static object? MatchStringItem(object? itemsSource, string? value)
|
||||
{
|
||||
if (itemsSource is IEnumerable<string> strings)
|
||||
{
|
||||
return strings.FirstOrDefault(item => string.Equals(item, value, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static object? MatchValueItem<T>(object? itemsSource, T? value) where T : struct
|
||||
{
|
||||
if (!value.HasValue || itemsSource is not IEnumerable<T> values)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (var item in values)
|
||||
{
|
||||
if (EqualityComparer<T>.Default.Equals(item, value.Value))
|
||||
{
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private object? MatchParagraphItem(string? tag)
|
||||
{
|
||||
if (ParagraphStyleComboBox.ItemsSource is not IEnumerable<EditorParagraphStyleOption> styles)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return styles.FirstOrDefault(item => string.Equals(item.Tag, tag, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
private static object? MatchColorItem(object? itemsSource, string? value)
|
||||
{
|
||||
if (itemsSource is not IEnumerable<EditorColorOption> colors)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return colors.FirstOrDefault(item => string.Equals(item.Value, value ?? string.Empty, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
private async Task ExecuteAsync(EditorCommand command)
|
||||
{
|
||||
if (_isApplyingState || CommandTarget == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await CommandTarget.ExecuteCommandAsync(command);
|
||||
}
|
||||
|
||||
private async void BoldButton_Click(object sender, RoutedEventArgs e) => await ExecuteAsync(EditorCommand.ToggleBold());
|
||||
private async void ItalicButton_Click(object sender, RoutedEventArgs e) => await ExecuteAsync(EditorCommand.ToggleItalic());
|
||||
private async void UnderlineButton_Click(object sender, RoutedEventArgs e) => await ExecuteAsync(EditorCommand.ToggleUnderline());
|
||||
private async void StrikeButton_Click(object sender, RoutedEventArgs e) => await ExecuteAsync(EditorCommand.ToggleStrikethrough());
|
||||
private async void BulletListButton_Click(object sender, RoutedEventArgs e) => await ExecuteAsync(EditorCommand.ToggleUnorderedList());
|
||||
private async void OrderedListButton_Click(object sender, RoutedEventArgs e) => await ExecuteAsync(EditorCommand.ToggleOrderedList());
|
||||
private async void IndentButton_Click(object sender, RoutedEventArgs e) => await ExecuteAsync(EditorCommand.Indent());
|
||||
private async void OutdentButton_Click(object sender, RoutedEventArgs e) => await ExecuteAsync(EditorCommand.Outdent());
|
||||
private async void ImageButton_Click(object sender, RoutedEventArgs e) => await ExecuteAsync(EditorCommand.InsertImage());
|
||||
private async void EmojiButton_Click(object sender, RoutedEventArgs e) => await ExecuteAsync(EditorCommand.InsertEmoji());
|
||||
private async void RemoveLinkButton_Click(object sender, RoutedEventArgs e) => await ExecuteAsync(EditorCommand.RemoveLink());
|
||||
|
||||
private async void AlignmentComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (_isApplyingState || AlignmentComboBox.SelectedItem is not EditorTextAlignment alignment)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await ExecuteAsync(EditorCommand.SetAlignment(alignment));
|
||||
}
|
||||
|
||||
private async void FontFamilyComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (_isApplyingState || FontFamilyComboBox.SelectedItem is not string fontFamily || string.IsNullOrWhiteSpace(fontFamily))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await ExecuteAsync(EditorCommand.SetFontFamily(fontFamily));
|
||||
}
|
||||
|
||||
private async void FontSizeComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (_isApplyingState || FontSizeComboBox.SelectedItem is not int fontSize)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await ExecuteAsync(EditorCommand.SetFontSize(fontSize));
|
||||
}
|
||||
|
||||
private async void ParagraphStyleComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (_isApplyingState || ParagraphStyleComboBox.SelectedItem is not EditorParagraphStyleOption paragraphStyle)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await ExecuteAsync(EditorCommand.SetParagraphStyle(paragraphStyle.Tag));
|
||||
}
|
||||
|
||||
private async void TextColorComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (_isApplyingState || TextColorComboBox.SelectedItem is not EditorColorOption color)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await ExecuteAsync(EditorCommand.SetTextColor(color.Value));
|
||||
}
|
||||
|
||||
private async void HighlightColorComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (_isApplyingState || HighlightColorComboBox.SelectedItem is not EditorColorOption color)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await ExecuteAsync(EditorCommand.SetHighlightColor(color.Value));
|
||||
}
|
||||
|
||||
private async void LineHeightComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (_isApplyingState || LineHeightComboBox.SelectedItem is not string lineHeight || string.IsNullOrWhiteSpace(lineHeight))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await ExecuteAsync(EditorCommand.SetLineHeight(lineHeight));
|
||||
}
|
||||
|
||||
private async void BuiltInToolbarButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await ExecuteAsync(EditorCommand.ToggleBuiltInToolbar(BuiltInToolbarButton.IsChecked == true));
|
||||
}
|
||||
|
||||
private async void SpellCheckButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await ExecuteAsync(EditorCommand.ToggleSpellCheck(SpellCheckButton.IsChecked == true));
|
||||
}
|
||||
|
||||
private async void LinkButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (CommandTarget == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var currentState = CommandTarget.CurrentState;
|
||||
var urlTextBox = new TextBox
|
||||
{
|
||||
Header = "URL",
|
||||
Text = currentState.LinkUrl ?? string.Empty,
|
||||
PlaceholderText = "https://example.com"
|
||||
};
|
||||
var textTextBox = new TextBox
|
||||
{
|
||||
Header = "Text",
|
||||
Text = currentState.SelectedText ?? string.Empty,
|
||||
PlaceholderText = "Link text"
|
||||
};
|
||||
var openInNewWindow = new CheckBox
|
||||
{
|
||||
Content = "Open in new window",
|
||||
IsChecked = true
|
||||
};
|
||||
|
||||
var dialog = new ContentDialog
|
||||
{
|
||||
XamlRoot = XamlRoot,
|
||||
Title = "Insert link",
|
||||
PrimaryButtonText = "Apply",
|
||||
CloseButtonText = "Cancel",
|
||||
DefaultButton = ContentDialogButton.Primary,
|
||||
Content = new StackPanel
|
||||
{
|
||||
Spacing = 12,
|
||||
Children =
|
||||
{
|
||||
urlTextBox,
|
||||
textTextBox,
|
||||
openInNewWindow
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (await dialog.ShowAsync() == ContentDialogResult.Primary && !string.IsNullOrWhiteSpace(urlTextBox.Text))
|
||||
{
|
||||
await ExecuteAsync(EditorCommand.InsertLink(new EditorLinkCommandArgs(urlTextBox.Text.Trim(), textTextBox.Text.Trim(), openInNewWindow.IsChecked == true)));
|
||||
}
|
||||
}
|
||||
|
||||
private async void TableButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var rowsBox = new NumberBox
|
||||
{
|
||||
Header = "Rows",
|
||||
Minimum = 1,
|
||||
Maximum = 10,
|
||||
SmallChange = 1,
|
||||
Value = 2
|
||||
};
|
||||
var columnsBox = new NumberBox
|
||||
{
|
||||
Header = "Columns",
|
||||
Minimum = 1,
|
||||
Maximum = 10,
|
||||
SmallChange = 1,
|
||||
Value = 2
|
||||
};
|
||||
|
||||
var dialog = new ContentDialog
|
||||
{
|
||||
XamlRoot = XamlRoot,
|
||||
Title = "Insert table",
|
||||
PrimaryButtonText = "Insert",
|
||||
CloseButtonText = "Cancel",
|
||||
DefaultButton = ContentDialogButton.Primary,
|
||||
Content = new StackPanel
|
||||
{
|
||||
Spacing = 12,
|
||||
Children =
|
||||
{
|
||||
rowsBox,
|
||||
columnsBox
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (await dialog.ShowAsync() == ContentDialogResult.Primary)
|
||||
{
|
||||
await ExecuteAsync(EditorCommand.InsertTable(new EditorTableCommandArgs((int)Math.Max(1, rowsBox.Value), (int)Math.Max(1, columnsBox.Value))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user