Forráskód Böngészése

Merge remote-tracking branch 'origin/kenric' into frank

frankvandenbos 3 hónapja
szülő
commit
3d3a406549

+ 65 - 0
InABox.Avalonia/Components/ListViewButton/ListViewButton.cs

@@ -0,0 +1,65 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
+using Avalonia.Markup.Xaml;
+using Avalonia.Media;
+using System.Windows.Input;
+
+namespace InABox.Avalonia.Components;
+
+public partial class ListViewButton : TemplatedControl
+{
+    public static readonly StyledProperty<IImage?> ImageProperty =
+        AvaloniaProperty.Register<ListViewButton, IImage?>(nameof(Image), null);
+
+    public static readonly StyledProperty<string> TitleProperty =
+        AvaloniaProperty.Register<ListViewButton, string>(nameof(Title), "");
+
+    public static readonly StyledProperty<string> DescriptionProperty =
+        AvaloniaProperty.Register<ListViewButton, string>(nameof(Description), "");
+
+    public static readonly StyledProperty<string> AlertProperty =
+        AvaloniaProperty.Register<ListViewButton, string>(nameof(Alert), "");
+
+    public IImage? Image
+    {
+        get => GetValue(ImageProperty);
+        set => SetValue(ImageProperty, value);
+    }
+
+    public string Title
+    {
+        get => GetValue(TitleProperty);
+        set => SetValue(TitleProperty, value);
+    }
+
+    public string Description
+    {
+        get => GetValue(DescriptionProperty);
+        set => SetValue(DescriptionProperty, value);
+    }
+
+    public string Alert
+    {
+        get => GetValue(AlertProperty);
+        set => SetValue(AlertProperty, value);
+    }
+
+    public static readonly StyledProperty<object?> CommandParameterProperty =
+        AvaloniaProperty.Register<ListViewButton, object?>(nameof(CommandParameter), null);
+
+    public object? CommandParameter
+    {
+        get => GetValue(CommandParameterProperty);
+        set => SetValue(CommandParameterProperty, value);
+    }
+
+    public static readonly StyledProperty<ICommand?> CommandProperty =
+        AvaloniaProperty.Register<ListViewButton, ICommand?>(nameof(Command), null);
+   
+    public ICommand? Command
+    {
+        get => GetValue(CommandProperty);
+        set => SetValue(CommandProperty, value);
+    }
+}

+ 0 - 4
InABox.Avalonia/InABox.Avalonia.csproj

@@ -64,10 +64,6 @@
       </Compile>
     </ItemGroup>
 
-    <ItemGroup>
-      <Folder Include="Components\Modules\" />
-    </ItemGroup>
-
     <ItemGroup>
       <UpToDateCheckInput Remove="Dialogs\TextBoxDialog\TextBoxDialogView.axaml" />
     </ItemGroup>

+ 2 - 0
InABox.Avalonia/Navigation/Navigation.cs

@@ -12,6 +12,8 @@ public interface IViewModelBase
     Task Activate();
     Task Deactivate();
     bool BackButtonVisible { get; set; }
+    
+    bool ProgressVisible { get; set; }
     AvaloniaMenuItemCollection PrimaryMenu { get; set; }
     AvaloniaMenuItemCollection SecondaryMenu { get; set; }
 }

+ 91 - 0
InABox.Avalonia/Theme/Classes/ListViewButton.axaml

@@ -0,0 +1,91 @@
+<Styles xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+		xmlns:components="using:InABox.Avalonia.Components"
+		xmlns:converters="using:InABox.Avalonia.Converters">
+	<Style Selector="components|ListViewButton">
+		<Style.Resources>
+			<converters:DoubleToCornerRadiusConverter
+				x:Key="SphericalBorder"
+				Ratio="0.5" />
+
+			<converters:DoubleToThicknessConverter
+				x:Key="MarginDoubler"
+				Ratio="2.0" />
+		</Style.Resources>
+		<Setter Property="Template">
+			<ControlTemplate>
+				<Button Height="80"
+						Padding="0"
+						Background="{TemplateBinding Background}"
+						Foreground="{TemplateBinding Foreground}"
+						BorderBrush="{StaticResource PrsTileBorder}"
+						HorizontalContentAlignment="Stretch"
+						VerticalContentAlignment="Stretch"
+						Command="{TemplateBinding Command}"
+						CommandParameter="{TemplateBinding CommandParameter}">
+					<Grid>
+
+						<Grid.RowDefinitions>
+							<RowDefinition Height="*" />
+							<RowDefinition Height="1.2*" />
+						</Grid.RowDefinitions>
+
+						<Grid.ColumnDefinitions>
+							<ColumnDefinition Width="70" />
+							<ColumnDefinition Width="*" />
+						</Grid.ColumnDefinitions>
+
+						<Image
+							Classes="Large"
+							Grid.Row="0"
+							Grid.Column="0"
+							Grid.RowSpan="2"
+							Source="{TemplateBinding Image}"
+							HorizontalAlignment="Center"
+							VerticalAlignment="Center" />
+
+						<Label
+							Grid.Row="0"
+							Grid.Column="1"
+							VerticalAlignment="Stretch"
+							VerticalContentAlignment="Center"
+							FontSize="{StaticResource PrsFontSizeLarge}"
+							FontWeight="{StaticResource PrsFontWeightBold}"
+							Content="{TemplateBinding Title}" />
+
+						<TextBlock
+							Grid.Row="1"
+							Grid.Column="1"
+							FontSize="{StaticResource PrsFontSizeNormal}"
+							FontStyle="{StaticResource PrsFontStylItalic}"
+							TextWrapping="WrapWithOverflow"
+							VerticalAlignment="Stretch"
+							Text="{TemplateBinding Description}" />
+
+						<Border
+							Background="{StaticResource PrsTileBackground}"
+							BorderBrush="{StaticResource PrsTileBorder}"
+							Grid.Row="0"
+							Grid.RowSpan="2"
+							Grid.Column="1"
+							VerticalAlignment="Top"
+							HorizontalAlignment="Right"
+							IsVisible="{TemplateBinding Alert, Converter={StaticResource StringToBooleanConverter}}"
+							MinWidth="{Binding $self.Bounds.Height}"
+							Margin="{Binding $self, Converter={StaticResource MarginDoubler}, ConverterParameter={StaticResource PrsControlSpacing}}"
+							CornerRadius="{Binding $self.Bounds.Height, Converter={StaticResource SphericalBorder}}">
+
+							<Label
+								Background="Transparent"
+								Foreground="{StaticResource PrsTileForeground}"
+								HorizontalContentAlignment="Center"
+								Content="{TemplateBinding Alert}" />
+						</Border>
+
+					</Grid>
+				</Button>
+			</ControlTemplate>
+		</Setter>
+	</Style>
+
+</Styles>

+ 12 - 0
InABox.Avalonia/Theme/Styles.axaml

@@ -0,0 +1,12 @@
+<Styles xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+	<StyleInclude Source="/Theme/Classes/ListViewButton.axaml"/>
+
+	<StyleInclude Source="/Theme/Classes/Border.axaml" />
+	<StyleInclude Source="/Theme/Classes/Button.axaml" />
+	<StyleInclude Source="/Theme/Classes/Image.axaml" />
+	<StyleInclude Source="/Theme/Classes/Label.axaml" />
+	<StyleInclude Source="/Theme/Classes/TextBox.axaml" />
+	<StyleInclude Source="/Theme/Classes/Separator.axaml" />
+	<StyleInclude Source="/Theme/Classes/ListBox.axaml" />
+</Styles>

+ 3 - 3
InABox.Core/CoreTable/CoreRow.cs

@@ -294,10 +294,10 @@ namespace InABox.Core
 
         private int GetColumn(string columnname)
         {
-            if (_columnindexes.ContainsKey(columnname))
-                return _columnindexes[columnname];
+            if (_columnindexes.TryGetValue(columnname, out var index))
+                return index;
 
-            var index = Table.GetColumnIndex(columnname);
+            index = Table.GetColumnIndex(columnname);
             _columnindexes[columnname] = index;
             return index;
         }

+ 4 - 16
InABox.Core/CoreTable/CoreTable.cs

@@ -231,21 +231,9 @@ namespace InABox.Core
         {
             var result = new Dictionary<TKey, TValue>();
             var sorted = sort == null ? Rows : Rows.OrderBy(x => x.Get(sort)).ToList();
-            foreach (var row in Rows)
-            {
-                var okey = row.Get(key);
-                var oval = row.Get(value);
-                try
-                {
-                    result[okey] = oval;
-                }
-                catch (Exception e)
-                {
-                    Logger.Send(LogType.Error,"", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
-                }
-                
-            }
-
+            foreach (var row in sorted)
+                // This threw an exception once.
+                result[row.Get(key)] = row.Get(value);
             return result;
         }
 
@@ -254,7 +242,7 @@ namespace InABox.Core
         {
             var result = new Dictionary<TKey, string>();
             var sorted = sort == null ? Rows : Rows.OrderBy(x => x.Get(sort)).ToList();
-            foreach (var row in Rows)
+            foreach (var row in sorted)
             {
                 var display = new List<object>();
                 foreach (var value in values)

+ 1 - 0
InABox.Core/Expression/DateFunctions.cs

@@ -12,6 +12,7 @@ namespace InABox.Core
         {
             CoreExpression.RegisterFunction("Now", "Returns the current date and time.", GROUP, new string[] {}, (p, vars, c) => DateTime.Now);
             CoreExpression.RegisterFunction("Today", "Returns the current date.", GROUP, new string[] {}, (p, vars, c) => DateTime.Today);
+            CoreExpression.RegisterFunction("DateTime_MinValue", "Returns the minimum value for a date and time.", GROUP, new string[] {}, (p, vars, c) => DateTime.MinValue);
         }
     }
 }

+ 3 - 3
inabox.database.sqlite/SQLiteProvider.cs

@@ -1742,9 +1742,9 @@ public class SQLiteProvider : IProvider
         { Operator.IsGreaterThanOrEqualTo, "{0} >= {1}" },
         { Operator.IsLessThan, "{0} < {1}" },
         { Operator.IsLessThanOrEqualTo, "{0} <= {1}" },
-        { Operator.BeginsWith, "{0} LIKE {1} || '%'" },
-        { Operator.Contains, "{0} LIKE '%' || {1} || '%'" },
-        { Operator.EndsWith, "{0} LIKE '%' || {1}" },
+        { Operator.BeginsWith, "IFNULL({0},'') LIKE {1} || '%'" },
+        { Operator.Contains, "IFNULL({0},'') LIKE '%' || {1} || '%'" },
+        { Operator.EndsWith, "IFNULL({0},'') LIKE '%' || {1}" },
         { Operator.InList, "{0} IN ({1})" },
         { Operator.NotInList, "{0} NOT IN ({1})" },
         { Operator.InQuery, "{0} IN ({1})" },

+ 5 - 0
inabox.wpf/Dashboard/Editor/DynamicDashboardDataEditor.xaml.cs

@@ -67,6 +67,11 @@ public partial class DynamicDashboardDataEditor : UserControl, INotifyPropertyCh
 
     private void Button_Click(object sender, RoutedEventArgs e)
     {
+        if (!QueryEditor.Validate())
+        {
+            return;
+        }
+
         var item = QueryGrid.CreateItem();
         if (QueryGrid.EditItems([item]))
         {

+ 1 - 1
inabox.wpf/DynamicGrid/DynamicEditorForm/DynamicEditorGrid.xaml.cs

@@ -481,7 +481,7 @@ public partial class DynamicEditorGrid : UserControl, IDynamicEditorHost
             var columnnames = changededitors != null ? changededitors.Keys.ToArray() : EditorList.Select(x => x.ColumnName).ToArray();
             foreach (var columnname in columnnames)
             {
-                if (!TryFindEditor(columnname, out var editor))
+                if (!EditorGrid.TryFindEditor(columnname, out var editor))
                     continue;
 
                 var bLoaded = editor.Loaded;

+ 20 - 1
inabox.wpf/DynamicGrid/DynamicEditorForm/EmbeddedDynamicEditorForm.xaml.cs

@@ -29,10 +29,12 @@ namespace InABox.DynamicGrid
         public event EventHandler OnChanged;
 
         private bool bChanged = false;
+        private bool _validated = false;
 
         public void DoChanged()
         {
             bChanged = true;
+            _validated = false;
             //OKButton.IsEnabled = true;
             //CancelButton.IsEnabled = true;
             OnChanged?.Invoke(this, EventArgs.Empty);
@@ -341,7 +343,7 @@ namespace InABox.DynamicGrid
             deb.Click();
         }
 
-        private void OKButton_Click(object sender, RoutedEventArgs e)
+        public bool Validate()
         {
             var errors = OnValidateData?.Invoke(this, Items);
 
@@ -351,6 +353,17 @@ namespace InABox.DynamicGrid
                     string.Format("The following errors have been found with your data!\nPlease correct them and try again.\n\n- {0}",
                         string.Join("\n- ", errors)),
                     "Validation Error");
+                return false;
+            }
+
+            _validated = true;
+            return true;
+        }
+
+        private void OKButton_Click(object sender, RoutedEventArgs e)
+        {
+            if (!Validate())
+            {
                 return;
             }
             OnOK?.Invoke();
@@ -381,7 +394,13 @@ namespace InABox.DynamicGrid
 
         public void SaveItem(CancelEventArgs e)
         {
+            if(!_validated && !Validate())
+            {
+                e.Cancel = true;
+                return;
+            }
             OnSaveItem?.Invoke(this, e);
+            _validated = false;
         }
 
         public bool TryFindEditor(string columnname, [NotNullWhen(true)] out IDynamicEditorControl? editor)

+ 1 - 0
inabox.wpf/DynamicGrid/DynamicGridFilterButtonComponent.cs

@@ -215,6 +215,7 @@ public abstract class DynamicGridFilterComponent<T>
                 {
                     globalFilters.Clear();
                     globalFilters.AddRange(filters.Where(x => !x.Private));
+
                     userFilters.Clear();
                     userFilters.AddRange(filters.Where(x => x.Private));
 

+ 41 - 0
inabox.wpf/DynamicGrid/Editors/TagsEditor/TagsEditorControl.cs

@@ -1,4 +1,5 @@
 using InABox.Core;
+using InABox.Wpf;
 using InABox.WPF;
 using Syncfusion.Data.Extensions;
 using System;
@@ -72,6 +73,35 @@ public class TagsEditorControl : DynamicEditorControl<string, TagsEditor>
             TagList = new ListBox();
             TagList.Width = Editor.ActualWidth;
             TagList.Height = 100;
+            TagList.HorizontalContentAlignment = HorizontalAlignment.Stretch;
+            TagList.ItemTemplate = TemplateGenerator.CreateDataTemplate(() =>
+            {
+                var panel = new DockPanel();
+
+                var item = new TextBlock();
+                item.Bind<string, string>(TextBlock.TextProperty, x => x);
+                DockPanel.SetDock(item, Dock.Left);
+
+                var btn = new Button();
+                btn.Content = "X";
+                btn.Width = 15;
+                btn.Height = 15;
+                btn.Padding = new();
+                btn.FontSize = 10;
+                btn.VerticalAlignment = VerticalAlignment.Center;
+                btn.VerticalContentAlignment = VerticalAlignment.Center;
+                btn.Background = Colors.Transparent.ToBrush();
+                btn.BorderBrush = Colors.Transparent.ToBrush();
+                btn.Bind<string, string>(Button.TagProperty, x => x);
+                btn.Bind(Button.VisibilityProperty, panel, x => x.IsMouseOver, new WPF.BooleanToVisibilityConverter(Visibility.Visible, Visibility.Collapsed));
+                DockPanel.SetDock(btn, Dock.Right);
+                btn.Click += DeleteTag_Click;
+
+                panel.Children.Add(btn);
+                panel.Children.Add(item);
+
+                return panel;
+            });
             TagList.ItemsSource = RecentTags;
             TagList.MouseUp += TagList_MouseUp;
 
@@ -82,6 +112,17 @@ public class TagsEditorControl : DynamicEditorControl<string, TagsEditor>
         }
     }
 
+    private void DeleteTag_Click(object sender, RoutedEventArgs e)
+    {
+        if (sender is not Button btn || btn.Tag is not string tag) return;
+
+        if (RecentTags.Remove(tag))
+        {
+            OnRecentTagsChanged?.Invoke(RecentTags);
+            UpdateTagList();
+        }
+    }
+
     private void TagList_MouseUp(object sender, MouseButtonEventArgs e)
     {
         AddSelectedTag();

+ 20 - 0
inabox.wpf/DynamicGrid/Grids/DynamicGridFilterGrid.cs

@@ -21,6 +21,21 @@ namespace InABox.DynamicGrid
             {
                 _filters = value;
                 Items = value;
+
+                var globalCount = _filters.Count(x => !x.Private);
+                var privateI = 0;
+                var globalI = 0;
+                foreach(var filter in _filters)
+                {
+                    if (filter.Private)
+                    {
+                        filter.Sequence = globalCount + privateI++;
+                    }
+                    else
+                    {
+                        filter.Sequence = globalI++;
+                    }
+                }
             }
         }
 
@@ -45,6 +60,11 @@ namespace InABox.DynamicGrid
             options.MultiSelect = true;
         }
 
+        public override void SaveItem(CoreFilterDefinition item)
+        {
+            base.SaveItem(item);
+        }
+
         private void DynamicGridFilterGrid_OnCustomiseEditor(IDynamicEditorForm sender, CoreFilterDefinition[]? items, DynamicGridColumn column, BaseEditor editor)
         {
             if(column.ColumnName == nameof(CoreFilterDefinition.Filter) && editor is FilterEditor filterEditor)