瀏覽代碼

Added editors for actions

Kenric Nugteren 8 月之前
父節點
當前提交
8668abd359

+ 21 - 6
prs.shared/Events/Event.cs

@@ -160,11 +160,12 @@ public static class EventUtils
     private static Dictionary<string, Type>? _actionTypes;
 
     private static Dictionary<Type, List<Type>>? _eventTriggerTypes;
+    private static Dictionary<Type, List<Type>>? _eventActionTypes;
 
-    [MemberNotNullWhen(true, nameof(_eventTypes), nameof(_triggerTypes), nameof(_actionTypes), nameof(_eventTriggerTypes))]
+    [MemberNotNullWhen(true, nameof(_eventTypes), nameof(_triggerTypes), nameof(_actionTypes), nameof(_eventTriggerTypes), nameof(_eventActionTypes))]
     private static bool _loadedTypes { get; set; }
 
-    [MemberNotNull(nameof(_eventTypes), nameof(_triggerTypes), nameof(_actionTypes), nameof(_eventTriggerTypes))]
+    [MemberNotNull(nameof(_eventTypes), nameof(_triggerTypes), nameof(_actionTypes), nameof(_eventTriggerTypes), nameof(_eventActionTypes))]
     private static void LoadTypes()
     {
         if (_loadedTypes) return;
@@ -174,6 +175,7 @@ public static class EventUtils
         _triggerTypes = new();
         _actionTypes = new();
         _eventTriggerTypes = new();
+        _eventActionTypes = new();
         foreach(var type in CoreUtils.TypeList(x => true))
         {
             if (type.HasInterface(typeof(IEvent<>)))
@@ -183,22 +185,26 @@ public static class EventUtils
                     _eventTypes[type.Name] = type;
                 }
             }
-            else if (type.GetInterfaceDefinition(typeof(IEventTrigger<,>)) is Type eventInterface)
+            else if (type.GetInterfaceDefinition(typeof(IEventTrigger<,>)) is Type eventTriggerInterface)
             {
                 if (type.GetConstructor([]) is not null)
                 {
                     _triggerTypes[type.Name] = type;
 
-                    var eventType = eventInterface.GenericTypeArguments[0];
+                    var eventType = eventTriggerInterface.GenericTypeArguments[0];
                     eventType = eventType.IsGenericType ? eventType.GetGenericTypeDefinition() : eventType;
                     _eventTriggerTypes.GetValueOrAdd(eventType).Add(type);
                 }
             }
-            else if (type.HasInterface(typeof(IEventAction<>)))
+            else if (type.GetInterfaceDefinition(typeof(IEventAction<>)) is Type eventActionInterface)
             {
                 if (type.GetConstructor([]) is not null)
                 {
-                    _triggerTypes[type.Name] = type;
+                    _actionTypes[type.Name] = type;
+
+                    var eventType = eventActionInterface.GenericTypeArguments[0];
+                    eventType = eventType.IsGenericType ? eventType.GetGenericTypeDefinition() : eventType;
+                    _eventActionTypes.GetValueOrAdd(eventType).Add(type);
                 }
             }
         }
@@ -211,6 +217,13 @@ public static class EventUtils
         return _eventTriggerTypes.GetValueOrDefault(eventType) ?? Enumerable.Empty<Type>();
     }
 
+    public static IEnumerable<Type> GetEventActionTypes(Type eventType)
+    {
+        LoadTypes();
+        eventType = eventType.IsGenericType ? eventType.GetGenericTypeDefinition() : eventType;
+        return _eventActionTypes.GetValueOrDefault(eventType) ?? Enumerable.Empty<Type>();
+    }
+
     public static Type GetEventType(string type)
     {
         LoadTypes();
@@ -479,6 +492,8 @@ public interface IEventAction : ISerializeBinary
     IEnumerable<string> ReferencedVariables();
 
     object? Execute(IEventDataModel dataModel);
+
+    string GetDescription();
 }
 
 public interface IEventAction<TEvent> : IEventAction

+ 21 - 11
prs.shared/Events/SaveEvent.cs

@@ -52,7 +52,7 @@ public class SaveEventDataModelDefinition<T>(SaveEvent<T> ev) : IEventDataModelD
     {
         if(variables is null)
         {
-            variables = DatabaseSchema.AllProperties(Event.Entity).Select(x => new StandardEventVariable(x.Name, x.PropertyType)).ToArray();
+            variables = DatabaseSchema.AllProperties(Event.Entity).Select(x => new StandardEventVariable($"{typeof(T).Name}.{x.Name}", x.PropertyType)).ToArray();
             variables.SortBy(x => x.Name);
         }
         return variables;
@@ -62,14 +62,23 @@ public class SaveEventDataModelDefinition<T>(SaveEvent<T> ev) : IEventDataModelD
     {
         if(variables is null)
         {
-            var prop = DatabaseSchema.Property(Event.Entity, name);
-            if(prop is null)
+            var prefix = $"{typeof(T)}.";
+            if (name.StartsWith(prefix))
             {
-                return null;
+                name = name[prefix.Length..];
+                var prop = DatabaseSchema.Property(Event.Entity, name);
+                if(prop is null)
+                {
+                    return null;
+                }
+                else
+                {
+                    return new StandardEventVariable(prop.Name, prop.PropertyType);
+                }
             }
             else
             {
-                return new StandardEventVariable(prop.Name, prop.PropertyType);
+                return null;
             }
         }
         else
@@ -201,8 +210,7 @@ public class ScriptSaveEventTrigger<T> : IEventTrigger<SaveEvent<T>, SaveEventDa
 
     public string DefaultScript()
     {
-        return @"
-using Comal.Classes;
+        return @"using PRS.Shared.Events;
 
 public class Module
 {
@@ -273,8 +281,7 @@ public class ScriptSaveEventAction<T> : IEventAction<SaveEvent<T>>
 
     public string DefaultScript()
     {
-        return @"
-using Comal.Classes;
+        return @"using PRS.Shared.Events;
 
 public class Module
 {
@@ -288,7 +295,6 @@ public class Module
 }";
     }
 
-
     public object? Execute(IEventDataModel dataModel)
     {
         if (Script is null) return null;
@@ -332,10 +338,12 @@ public class Module
             Script = script;
         }
     }
+
+    public string GetDescription() => "Custom Script";
 }
 
 [Caption("Create Entity")]
-public class CreateEntityAction<T> : IEventAction<SaveEvent<T>>
+public class CreateEntitySaveEventAction<T> : IEventAction<SaveEvent<T>>
     where T : Entity
 {
     public Type? EntityType { get; set; }
@@ -396,6 +404,8 @@ public class CreateEntityAction<T> : IEventAction<SaveEvent<T>>
             Initializers.Add(init);
         }
     }
+
+    public string GetDescription() => $"Create New {EntityType?.Name ?? "Entity"}";
 }
 
 public class PropertyInitializer

+ 94 - 0
prs.shared/Grids/EventEditor/Action Editors/ActionEditors.cs

@@ -0,0 +1,94 @@
+using InABox.Core;
+using InABox.DynamicGrid;
+using InABox.WPF;
+using Microsoft.CodeAnalysis.VisualBasic.Syntax;
+using PRS.Shared.Events;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+
+namespace PRS.Shared.Grids.EventEditor;
+
+public interface IEventActionEditor
+{
+    bool Edit(IEventAction trigger, IEventDataModelDefinition dataModelDefinition);
+}
+
+public interface IEventActionEditor<TAction> : IEventActionEditor
+    where TAction : IEventAction
+{
+    bool Edit(TAction trigger, IEventDataModelDefinition dataModelDefinition);
+
+    bool IEventActionEditor.Edit(IEventAction trigger, IEventDataModelDefinition dataModelDefinition) => Edit((TAction)trigger, dataModelDefinition);
+}
+
+public static class EventActionEditors
+{
+    private static Dictionary<Type, Type>? _actionEditors;
+    private static Dictionary<Type, Type> GetActionEditors()
+    {
+        if(_actionEditors is null)
+        {
+            _actionEditors = new Dictionary<Type, Type>();
+            foreach(var type in CoreUtils.TypeList(x => true))
+            {
+                if (type.GetInterfaceDefinition(typeof(IEventActionEditor<>)) is Type editorInterface)
+                {
+                    var actionType = editorInterface.GenericTypeArguments[0];
+                    actionType = actionType.IsGenericType ? actionType.GetGenericTypeDefinition() : actionType;
+                    _actionEditors[actionType] = type;
+                }
+            }
+        }
+        return _actionEditors;
+    }
+
+    public static Type? GetActionEditorType(Type actionType)
+    {
+        var genericActionType = actionType.IsGenericType ? actionType.GetGenericTypeDefinition() : actionType;
+        if(GetActionEditors().GetValueOrDefault(genericActionType) is Type editorType)
+        {
+            return editorType.IsGenericType ? editorType.MakeGenericType(actionType.GenericTypeArguments) : editorType;
+        }
+        else
+        {
+            return null;
+        }
+    }
+
+    public static bool EditAction<TEvent, TDataModel>(IEventAction<TEvent> action, IEventDataModelDefinition dataModelDefinition)
+        where TEvent : IEvent<TDataModel>
+        where TDataModel : IEventDataModel
+    {
+        var editorType = GetActionEditorType(action.GetType());
+        if (editorType is null) return false;
+
+        var editor = (Activator.CreateInstance(editorType) as IEventActionEditor)!;
+        return editor.Edit(action, dataModelDefinition);
+    }
+}
+
+public class ScriptSaveEventActionEditor<T> : IEventActionEditor<ScriptSaveEventAction<T>>
+    where T : Entity
+{
+    public bool Edit(ScriptSaveEventAction<T> action, IEventDataModelDefinition dataModelDefinition)
+    {
+        var window = new ScriptEditorWindow(action.Script ?? action.DefaultScript(), scriptTitle: "Edit Custom Script Action");
+        if(window.ShowDialog() == true)
+        {
+            action.Script = window.Script;
+            return true;
+        }
+        else
+        {
+            return false;
+        }
+    }
+}

+ 125 - 0
prs.shared/Grids/EventEditor/Action Editors/CreateEntityActionEditor.cs

@@ -0,0 +1,125 @@
+using InABox.Core;
+using InABox.DynamicGrid;
+using InABox.WPF;
+using PRS.Shared.Events;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace PRS.Shared.Grids.EventEditor;
+
+public class CreateEntitySaveEventActionEditor<T> : IEventActionEditor<CreateEntitySaveEventAction<T>>, INotifyPropertyChanged
+    where T : Entity
+{
+    private CreateEntitySaveEventAction<T> Action = null!;
+    private PropertyInitializerGrid Grid = null!;
+
+    public Type? SelectedType
+    {
+        get => Action.EntityType;
+        set
+        {
+            Action.EntityType = value;
+            Grid.EntityType = value;
+            OnPropertyChanged();
+
+            DoChanged();
+        }
+    }
+
+    private bool _changed;
+    public bool Changed
+    {
+        get => _changed;
+        set
+        {
+            _changed = value;
+            OnPropertyChanged();
+        }
+    }
+
+    private void DoChanged()
+    {
+        Changed = true;
+    }
+
+    public bool Edit(CreateEntitySaveEventAction<T> action, IEventDataModelDefinition dataModelDefinition)
+    {
+        Action = action;
+
+        var grid = new Grid();
+
+        grid.AddColumn(GridUnitType.Auto);
+        grid.AddColumn(GridUnitType.Auto);
+        grid.AddColumn(GridUnitType.Star);
+
+        grid.AddRow(GridUnitType.Auto);
+        grid.AddRow(GridUnitType.Star);
+
+        grid.AddChild(new Label
+        {
+            Content = "Entity Type:",
+            VerticalAlignment = VerticalAlignment.Center
+        }, 0, 0);
+
+        var box = new ComboBox
+        {
+            Margin = new(5, 0, 0, 0),
+            Padding = new(5),
+            MinWidth = 100,
+            DisplayMemberPath = "Name"
+        };
+
+        var entities = CoreUtils.Entities.Where(x => !x.IsGenericType && x.IsSubclassOf(typeof(Entity))).ToArray();
+        entities.SortBy(x => x.Name);
+        box.ItemsSource = entities;
+        box.Bind(ComboBox.SelectedValueProperty, this, x => x.SelectedType);
+
+        Grid = new()
+        {
+            EntityType = SelectedType,
+            DataModelDefinition = dataModelDefinition,
+            Margin = new(0, 5, 0, 0)
+        };
+        Grid.Items = action.Initializers.Select(x => new PropertyInitializerEditItem { PropertyInitializer = x }).ToList();
+        Grid.Refresh(true, true);
+        Grid.OnChanged += Grid_OnChanged;
+
+        grid.AddChild(box, 0, 1);
+        grid.AddChild(Grid, 1, 0, colSpan: 3);
+
+        var dlg = new DynamicContentDialog(grid)
+        {
+            Title = "Edit Create Entity Action",
+            WindowStartupLocation = WindowStartupLocation.CenterScreen
+        };
+        dlg.Bind(DynamicContentDialog.CanSaveProperty, this, x => x.Changed);
+        if(dlg.ShowDialog() == true)
+        {
+            action.Initializers = Grid.Items.Select(x => x.PropertyInitializer).ToList();
+            return true;
+        }
+        else
+        {
+            return false;
+        }
+    }
+
+    private void Grid_OnChanged(object? sender, EventArgs e)
+    {
+        DoChanged();
+    }
+
+    public event PropertyChangedEventHandler? PropertyChanged;
+
+    private void OnPropertyChanged([CallerMemberName] string propertyName = "")
+    {
+        PropertyChanged?.Invoke(this, new(propertyName));
+    }
+}

+ 96 - 0
prs.shared/Grids/EventEditor/Action Editors/EventActionGrid.cs

@@ -0,0 +1,96 @@
+using InABox.Core;
+using InABox.DynamicGrid;
+using InABox.WPF;
+using PRS.Shared.Events;
+using PRS.Shared.Grids.EventEditor;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Controls;
+using System.Windows.Media;
+
+namespace PRS.Shared;
+
+public class EventActionContainer<TEvent, TDataModel> : BaseObject
+    where TEvent : IEvent<TDataModel>
+{
+    public IEventAction<TEvent> Action { get; set; }
+
+    public string ActionType => Action.GetType().GetCaption();
+
+    public string Description => Action.GetDescription();
+}
+
+public class EventActionGrid<TEvent, TDataModel> : DynamicItemsListGrid<EventActionContainer<TEvent, TDataModel>>
+    where TEvent : IEvent<TDataModel>
+    where TDataModel : IEventDataModel
+{
+    public IEnumerable<IEventAction<TEvent>> EventActions => Items.Select(x => x.Action);
+
+    private readonly IEventDataModelDefinition DataModelDefinition;
+
+    public EventActionGrid(IEnumerable<IEventAction<TEvent>> items, IEventDataModelDefinition dataModelDefinition)
+    {
+        Items.AddRange(items.Select(x => new EventActionContainer<TEvent, TDataModel> { Action = x }));
+        DataModelDefinition = dataModelDefinition;
+    }
+
+    protected override void DoReconfigure(DynamicGridOptions options)
+    {
+        base.DoReconfigure(options);
+        options.AddRows = true;
+        options.EditRows = true;
+        options.DeleteRows = true;
+    }
+
+    public override DynamicGridColumns GenerateColumns()
+    {
+        var cols = new DynamicGridColumns<EventActionContainer<TEvent, TDataModel>>();
+        cols.Add(x => x.Description);
+        return cols;
+    }
+
+    protected override void DoAdd(bool openEditorOnDirectEdit = false)
+    {
+        var types = EventUtils.GetEventActionTypes(typeof(TEvent));
+
+        var menu = new ContextMenu();
+        foreach(var type in types)
+        {
+            menu.AddItem(type.GetCaption(), null, type, MenuAdd_Click);
+        }
+        menu.IsOpen = true;
+    }
+
+    protected override void DoEdit()
+    {
+        var row = SelectedRows.First();
+        var item = LoadItem(row);
+        if (EventActionEditors.EditAction<TEvent, TDataModel>(item.Action, DataModelDefinition))
+        {
+            UpdateRow(row, item);
+            DoChanged();
+        }
+    }
+
+    private void MenuAdd_Click(Type type)
+    {
+        if (type.IsGenericType)
+        {
+            type = type.MakeGenericType(typeof(TEvent).GenericTypeArguments);
+        }
+        var action = (Activator.CreateInstance(type) as IEventAction<TEvent>)!;
+        if(!EventActionEditors.EditAction<TEvent, TDataModel>(action, DataModelDefinition))
+        {
+            return;
+        }
+
+        Items.Add(new() { Action = action });
+
+        Refresh(false, true);
+        DoChanged();
+    }
+}
+

+ 230 - 0
prs.shared/Grids/EventEditor/Action Editors/PropertyInitializerGrid.cs

@@ -0,0 +1,230 @@
+using com.sun.tools.corba.se.idl.toJavaPortable;
+using InABox.Core;
+using InABox.DynamicGrid;
+using InABox.Wpf;
+using InABox.WPF;
+using PRS.Shared.Events;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+
+namespace PRS.Shared.Grids.EventEditor;
+
+public class PropertyInitializerEditItem : BaseObject
+{
+    public PropertyInitializer PropertyInitializer { get; set; }
+}
+
+public class PropertyInitializerGrid : DynamicItemsListGrid<PropertyInitializerEditItem>
+{
+    private string[] ColumnNames = null!;
+
+    private Type? _entityType;
+    public Type? EntityType
+    {
+        get => _entityType;
+        set
+        {
+            if(value != _entityType)
+            {
+                Items.Clear();
+                _entityType = value;
+
+                if(value is null)
+                {
+                    ColumnNames = [];
+                }
+                else
+                {
+                    var properties = DatabaseSchema.Properties(value)
+                        .Where(x => x.IsSerializable)
+                        .Select(x => x.Name)
+                        .ToArray();
+                    Array.Sort(properties);
+                    ColumnNames = properties;
+                }
+                Reconfigure();
+                Refresh(true, true);
+            }
+        }
+    }
+
+    public IEventDataModelDefinition DataModelDefinition { get; set; } = null!;
+
+    private BitmapImage _delete = InABox.Wpf.Resources.delete.AsBitmapImage();
+
+    protected override void Init()
+    {
+        base.Init();
+
+        ActionColumns.Add(new DynamicTemplateColumn(row =>
+        {
+            var item = LoadItem(row);
+            var button = new Button
+            {
+                Content = item.PropertyInitializer.Property.Name,
+
+                VerticalAlignment = System.Windows.VerticalAlignment.Stretch,
+                VerticalContentAlignment = System.Windows.VerticalAlignment.Center,
+                HorizontalContentAlignment = System.Windows.HorizontalAlignment.Left,
+                MinWidth = 100,
+
+                Padding = new System.Windows.Thickness(5, 0, 5, 0),
+                Background = new LinearGradientBrush(new GradientStopCollection()
+                {
+                    new(Color.FromRgb(0xF0, 0xF0, 0xF0), 0.0),
+                    new(Color.FromRgb(0xE5, 0xE5, 0xE5), 1.0),
+                })
+                {
+                    StartPoint = new(0, 0),
+                    EndPoint = new(0, 1),
+                },
+                BorderBrush = Color.FromRgb(0xAC, 0xAC, 0xAC).ToBrush(),
+                BorderThickness = new(0),
+                Tag = row
+            };
+            button.Click += PropertyNode_Click;
+            return button;
+        })
+        {
+            HeaderText = "Property"
+        });
+        ActionColumns.Add(new DynamicTemplateColumn(row =>
+        {
+            var item = LoadItem(row);
+
+            var panel = new DockPanel();
+
+            var textBox = new TextBox
+            {
+                Background = Colors.LightYellow.ToBrush(),
+                IsEnabled = false,
+                BorderThickness = new(0),
+                VerticalContentAlignment = VerticalAlignment.Center,
+                HorizontalContentAlignment = HorizontalAlignment.Left,
+                Padding = new(2, 0, 0, 0),
+                Text = item.PropertyInitializer.Value
+            };
+
+            var button = new Button
+            {
+                Content = "Edit",
+                Padding = new(5.0),
+                MinWidth = 30,
+                Tag = row
+            };
+            button.BorderBrush = Colors.Silver.ToBrush();
+            button.BorderThickness = new Thickness(0.75, 0.0, 0.0, 0.0);
+            button.Click += Value_Click;
+
+            DockPanel.SetDock(button, Dock.Right);
+            panel.Children.Add(button);
+            DockPanel.SetDock(textBox, Dock.Left);
+            panel.Children.Add(textBox);
+
+            return panel;
+        })
+        {
+            HeaderText = "Value"
+        });
+        ActionColumns.Add(new DynamicImageColumn(_delete, DeleteButton_Click));
+    }
+
+    private bool DeleteButton_Click(CoreRow? row)
+    {
+        if (row is null) return false;
+
+        if (CanDeleteItems([row]))
+            if (MessageWindow.ShowYesNo("Are you sure you wish to delete the selected records?", "Confirm Delete"))
+            {
+                DeleteItems([row]);
+                SelectedRows = Array.Empty<CoreRow>();
+                DoChanged();
+                return true;
+            }
+        return false;
+    }
+
+    #region UI Component
+
+    private class UIComponent : DynamicGridGridUIComponent<PropertyInitializerEditItem>
+    {
+        protected override Brush? GetCellSelectionBackgroundBrush()
+        {
+            return null;
+        }
+    }
+
+    protected override IDynamicGridUIComponent<PropertyInitializerEditItem> CreateUIComponent()
+    {
+        return new UIComponent { Parent = this };
+    }
+
+    #endregion
+
+    protected override void DoReconfigure(DynamicGridOptions options)
+    {
+        base.DoReconfigure(options);
+
+        options.Clear();
+        options.ReadOnly = EntityType is null;
+        options.AddRows = true;
+    }
+
+    protected override void DoAdd(bool openEditorOnDirectEdit = false)
+    {
+        if (EntityType is null) return;
+
+        if(DynamicGridColumnNameSelectorGrid.SelectColumnName(EntityType, ColumnNames, out var value))
+        {
+            var property = DatabaseSchema.PropertyStrict(EntityType, value);
+            CreateItems(() =>
+            {
+                return [new()
+                {
+                    PropertyInitializer = new(property, "")
+                }];
+            });
+        }
+    }
+
+    private void Value_Click(object sender, RoutedEventArgs e)
+    {
+        if (sender is not FrameworkElement el || el.Tag is not CoreRow row) return;
+        if (EntityType is null) return;
+
+        var item = LoadItem(row);
+
+        var window = new ExpressionEditorWindow(DataModelDefinition.GetVariables().Select(x => x.Name).ToArray())
+        {
+            Expression = item.PropertyInitializer.Value
+        };
+        if(window.ShowDialog() == true && item.PropertyInitializer.Value != window.Expression)
+        {
+            item.PropertyInitializer.Value = window.Expression;
+            UpdateRow(row, item);
+            DoChanged();
+        }
+    }
+
+    private void PropertyNode_Click(object sender, RoutedEventArgs e)
+    {
+        if (sender is not FrameworkElement el || el.Tag is not CoreRow row) return;
+        if (EntityType is null) return;
+
+        var item = LoadItem(row);
+
+        if(DynamicGridColumnNameSelectorGrid.SelectColumnName(EntityType, ColumnNames, out var value) && value != item.PropertyInitializer.Property.Name)
+        {
+            item.PropertyInitializer.Property = DatabaseSchema.PropertyStrict(EntityType, value);
+            UpdateRow(row, item);
+            DoChanged();
+        }
+    }
+}

+ 42 - 4
prs.shared/Grids/EventEditor/EventEditor.xaml.cs

@@ -1,6 +1,7 @@
 using Comal.Classes;
 using InABox.Core;
 using InABox.DynamicGrid;
+using InABox.WPF;
 using PRS.Shared.Events;
 using System;
 using System.Collections.Generic;
@@ -34,6 +35,7 @@ public partial class EventEditor : UserControl, INotifyPropertyChanged
             OnPropertyChanged();
 
             HasType = value.HasValue && EventTypeHasEntityType(value.Value);
+            DoChanged();
 
             UpdateEventData();
         }
@@ -57,28 +59,54 @@ public partial class EventEditor : UserControl, INotifyPropertyChanged
     private Type? EventDataType;
     private Type? DataModelType;
 
-    private IEventData? _data;
+    private IEventEditorControl? _eventEditorControl;
+
+    private bool _changed;
+    public bool Changed
+    {
+        get => _changed;
+        set
+        {
+            _changed = value;
+            OnPropertyChanged();
+        }
+    }
+
     public IEventData? Data
     {
-        get => _data;
+        get => _eventEditorControl?.Data;
         set
         {
-            _data = value;
             OnPropertyChanged();
 
-            if(value is not null && EventDataType is not null && DataModelType is not null)
+            if(value is not null && EventType.HasValue)
             {
+                if(EventDataType is null || DataModelType is null)
+                {
+                    var inter = value.GetType().GetSuperclassDefinition(typeof(EventData<,>))!;
+                    EventDataType = inter.GenericTypeArguments[0];
+                    DataModelType = inter.GenericTypeArguments[1];
+
+                    if (EventTypeHasEntityType(EventType.Value))
+                    {
+                        EntityType = EventDataType.GenericTypeArguments[0];
+                    }
+                }
                 var control = Activator.CreateInstance(typeof(EventEditorControl<,>).MakeGenericType(EventDataType, DataModelType), value);
+                _eventEditorControl = (control as IEventEditorControl)!;
+                _eventEditorControl.OnChanged += DoChanged;
                 Content.Content = control;
                 Content.Visibility = Visibility.Visible;
                 Placeholder.Visibility = Visibility.Collapsed;
             }
             else
             {
+                _eventEditorControl = null;
                 Content.Content = null;
                 Content.Visibility = Visibility.Collapsed;
                 Placeholder.Visibility = Visibility.Visible;
             }
+            DoChanged();
         }
     }
 
@@ -94,6 +122,11 @@ public partial class EventEditor : UserControl, INotifyPropertyChanged
         EntityTypeBox.ItemsSource = entities;
     }
 
+    private void DoChanged()
+    {
+        Changed = true;
+    }
+
     private bool EventTypeHasEntityType(EventType eventType)
     {
         switch (eventType)
@@ -139,14 +172,19 @@ public partial class EventEditor : UserControl, INotifyPropertyChanged
         editor.EventType = evType;
         editor.Data = eventData;
 
+        editor.Changed = false;
+
         var dialog = new DynamicContentDialog(editor)
         {
             SizeToContent = SizeToContent.Height,
             Title = "Edit Event"
         };
+        dialog.Bind(DynamicContentDialog.CanSaveProperty, editor, x => x.Changed);
 
         if(dialog.ShowDialog() == true)
         {
+            eventData = editor.Data;
+            evType = editor.EventType ?? default;
             return true;
         }
         else

+ 44 - 14
prs.shared/Grids/EventEditor/EventEditorControl.cs

@@ -11,11 +11,38 @@ using System.Windows.Media;
 
 namespace PRS.Shared;
 
-public class EventEditorControl<TEvent, TDataModel> : Grid
+public interface IEventEditorControl
+{
+    event Action? OnChanged;
+
+    IEventData Data { get; }
+}
+
+public class EventEditorControl<TEvent, TDataModel> : Grid, IEventEditorControl
     where TEvent : IEvent<TDataModel>
     where TDataModel : IEventDataModel
 {
-    public EventData<TEvent, TDataModel> Data { get; set; }
+    private EventData<TEvent, TDataModel> _data;
+    public EventData<TEvent, TDataModel> Data
+    {
+        get
+        {
+            _data.Triggers = TriggerGrid.EventTriggers.ToList();
+            _data.Actions = ActionGrid.EventActions.ToList();
+            return _data;
+        }
+        set
+        {
+            _data = value;
+        }
+    }
+
+    IEventData IEventEditorControl.Data => Data;
+
+    private EventTriggerGrid<TEvent, TDataModel> TriggerGrid;
+    private EventActionGrid<TEvent, TDataModel> ActionGrid;
+
+    public event Action? OnChanged;
 
     public EventEditorControl(EventData<TEvent, TDataModel> evData)
     {
@@ -57,19 +84,13 @@ public class EventEditorControl<TEvent, TDataModel> : Grid
             Margin = new(3, 0, 3, 0)
         });
 
-        var triggersGrid = new EventTriggerGrid<TEvent, TDataModel>(Data.Triggers)
+        TriggerGrid = new EventTriggerGrid<TEvent, TDataModel>(evData.Triggers)
         {
             Margin = new(0, 5, 0, 0)
         };
-        var actionsControl = new ContentControl
+        ActionGrid = new EventActionGrid<TEvent, TDataModel>(evData.Actions, evData.Event.DataModelDefinition())
         {
-            Margin = new(0, 5, 0, 0),
-            Content = new Border
-            {
-                BorderBrush = Colors.Gray.ToBrush(),
-                BorderThickness = new(0.75),
-                Background = Colors.White.ToBrush()
-            }
+            Margin = new(0, 5, 0, 0)
         };
 
         this.AddRow(GridUnitType.Auto);
@@ -81,9 +102,18 @@ public class EventEditorControl<TEvent, TDataModel> : Grid
         this.AddChild(triggerLabelBorder, 0, 0);
         this.AddChild(separator, 0, 1, rowSpan: 2);
         this.AddChild(actionsLabelBorder, 0, 2);
-        this.AddChild(triggersGrid, 1, 0);
-        this.AddChild(actionsControl, 1, 2);
+        this.AddChild(TriggerGrid, 1, 0);
+        this.AddChild(ActionGrid, 1, 2);
 
-        triggersGrid.Refresh(true, true);
+        TriggerGrid.Refresh(true, true);
+        ActionGrid.Refresh(true, true);
+
+        TriggerGrid.OnChanged += Grid_OnChanged;
+        ActionGrid.OnChanged += Grid_OnChanged;
+    }
+
+    private void Grid_OnChanged(object? sender, EventArgs e)
+    {
+        OnChanged?.Invoke();
     }
 }

+ 12 - 8
prs.shared/Grids/EventEditor/EventTriggerGrid.cs

@@ -64,8 +64,13 @@ public class EventTriggerGrid<TEvent, TDataModel> : DynamicItemsListGrid<EventTr
 
     protected override void DoEdit()
     {
-        var item = LoadItem(SelectedRows.First());
-        EditTrigger(item.Trigger);
+        var row = SelectedRows.First();
+        var item = LoadItem(row);
+        if (EventTriggerEditors.EditTrigger(item.Trigger))
+        {
+            UpdateRow(row, item);
+            DoChanged();
+        }
     }
 
     private void MenuAdd_Click(Type type)
@@ -75,16 +80,15 @@ public class EventTriggerGrid<TEvent, TDataModel> : DynamicItemsListGrid<EventTr
             type = type.MakeGenericType(typeof(TEvent).GenericTypeArguments);
         }
         var trigger = (Activator.CreateInstance(type) as IEventTrigger<TEvent, TDataModel>)!;
-        EditTrigger(trigger);
+        if (!EventTriggerEditors.EditTrigger(trigger))
+        {
+            return;
+        }
 
         Items.Add(new() { Trigger = trigger });
 
         Refresh(false, true);
-    }
-
-    private void EditTrigger(IEventTrigger<TEvent, TDataModel> trigger)
-    {
-        EventTriggerEditors.EditTrigger(trigger);
+        DoChanged();
     }
 }
 

+ 19 - 80
prs.shared/Grids/EventEditor/TriggerEditors.cs

@@ -72,7 +72,7 @@ public static class EventTriggerEditors
     }
 }
 
-public class PropertyChangedSaveEventTriggerEditor<T> : Grid, IEventTriggerEditor<PropertyChangedSaveEventTrigger<T>>
+public class PropertyChangedSaveEventTriggerEditor<T> : IEventTriggerEditor<PropertyChangedSaveEventTrigger<T>>
     where T : Entity
 {
     public bool Edit(PropertyChangedSaveEventTrigger<T> trigger)
@@ -93,82 +93,21 @@ public class PropertyChangedSaveEventTriggerEditor<T> : Grid, IEventTriggerEdito
         }
     }
 }
-// public class PropertyChangedSaveEventTriggerEditor<T> : Grid, IEventTriggerEditor<PropertyChangedSaveEventTrigger<T>>
-//     where T : Entity
-// {
-//     public PropertyChangedSaveEventTrigger<T> EventTrigger { get; set; }
-// 
-//     private readonly string[] ColumnNames;
-// 
-//     private Button PropertyButton;
-// 
-//     public PropertyChangedSaveEventTriggerEditor(PropertyChangedSaveEventTrigger<T> eventTrigger)
-//     {
-//         EventTrigger = eventTrigger;
-// 
-//         this.AddColumn(GridUnitType.Auto);
-//         this.AddColumn(GridUnitType.Auto);
-//         this.AddColumn(GridUnitType.Auto);
-//         this.AddColumn(GridUnitType.Star);
-//         this.AddColumn(GridUnitType.Auto);
-//         this.AddColumn(GridUnitType.Star);
-//         this.AddRow(GridUnitType.Auto);
-// 
-//         this.AddChild(new Label
-//         {
-//             Content = "Property:",
-//             VerticalAlignment = VerticalAlignment.Center,
-//         }, 0, 0);
-// 
-//         PropertyButton = new Button
-//         {
-//             Content = eventTrigger.TriggerProperty?.Name ?? "",
-//             Padding = new(5),
-//             Margin = new(5, 0, 0, 0),
-//             HorizontalContentAlignment = HorizontalAlignment.Left,
-//             Background = new LinearGradientBrush(new GradientStopCollection()
-//             {
-//                 new(Color.FromRgb(0xF0, 0xF0, 0xF0), 0.0),
-//                 new(Color.FromRgb(0xE5, 0xE5, 0xE5), 1.0),
-//             })
-//             {
-//                 StartPoint = new(0, 0),
-//                 EndPoint = new(0, 1),
-//             },
-//             BorderBrush = Color.FromRgb(0xAC, 0xAC, 0xAC).ToBrush(),
-//             MinWidth = 100
-//         };
-//         PropertyButton.Click += PropertyButton_Click;
-// 
-//         var properties = DatabaseSchema.Properties(typeof(T))
-//             .Where(x => x.IsSerializable)
-//             .Select(x => x.Name)
-//             .ToList();
-//         properties.Sort();
-//         ColumnNames = properties.ToArray();
-//         this.AddChild(PropertyButton, 0, 1);
-// 
-//         this.AddChild(new Label
-//         {
-//             Content = "Old Value:",
-//             Margin = new(5, 0, 0, 0),
-//             VerticalAlignment = VerticalAlignment.Center,
-//         }, 0, 2);
-// 
-//         this.AddChild(new Label
-//         {
-//             Content = "New Value:",
-//             Margin = new(5, 0, 0, 0),
-//             VerticalAlignment = VerticalAlignment.Center,
-//         }, 0, 4);
-//     }
-// 
-//     private void PropertyButton_Click(object sender, RoutedEventArgs e)
-//     {
-//         if(DynamicGridColumnNameSelectorGrid.SelectColumnName(typeof(T), ColumnNames, out var value))
-//         {
-//             EventTrigger.TriggerProperty = DatabaseSchema.Property(typeof(T), value);
-//             PropertyButton.Content = value;
-//         }
-//     }
-// }
+
+public class ScriptSaveEventTriggerEditor<T> : IEventTriggerEditor<ScriptSaveEventTrigger<T>>
+    where T : Entity
+{
+    public bool Edit(ScriptSaveEventTrigger<T> trigger)
+    {
+        var window = new ScriptEditorWindow(trigger.Script ?? trigger.DefaultScript(), scriptTitle: "Edit Custom Script trigger");
+        if(window.ShowDialog() == true)
+        {
+            trigger.Script = window.Script;
+            return true;
+        }
+        else
+        {
+            return false;
+        }
+    }
+}