Jelajahi Sumber

Added ScheduledEvent

Kenric Nugteren 7 bulan lalu
induk
melakukan
fc579e9a6c

+ 2 - 1
prs.classes/Entities/Events/Event.cs

@@ -12,7 +12,8 @@ namespace Comal.Classes
 {
     public enum EventType
     {
-        AfterSave
+        AfterSave,
+        Scheduled
     }
 
     public class Event : Entity, IRemotable, IPersistent, ILicense<CoreLicense>

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

@@ -75,20 +75,3 @@ public static class EventActionEditors
     }
 }
 
-public class ScriptSaveEventActionEditor<T> : IEventActionEditor<ScriptSaveEventAction<T>>
-    where T : Entity, new()
-{
-    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;
-        }
-    }
-}

+ 28 - 0
prs.shared/Grids/EventEditor/Action Editors/SaveEventActionEditors.cs

@@ -0,0 +1,28 @@
+using InABox.Core;
+using InABox.DynamicGrid;
+using PRS.Shared.Events;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PRS.Shared.Grids.EventEditor;
+
+public class ScriptSaveEventActionEditor<T> : IEventActionEditor<ScriptSaveEventAction<T>>
+    where T : Entity, new()
+{
+    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;
+        }
+    }
+}

+ 27 - 0
prs.shared/Grids/EventEditor/Action Editors/ScheduledEventActionEditors.cs

@@ -0,0 +1,27 @@
+using InABox.Core;
+using InABox.DynamicGrid;
+using PRS.Shared.Events;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PRS.Shared.Grids.EventEditor;
+
+public class ScriptScheduledEventActionEditor : IEventActionEditor<ScriptScheduledEventAction>
+{
+    public bool Edit(ScriptScheduledEventAction 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;
+        }
+    }
+}

+ 8 - 1
prs.shared/Grids/EventEditor/EventEditor.xaml

@@ -7,7 +7,9 @@
         xmlns:WPF="clr-namespace:InABox.WPF;assembly=InABox.Wpf"
         mc:Ignorable="d" x:Name="Editor">
     <UserControl.Resources>
-        <WPF:BooleanToVisibilityConverter x:Key="boolToVisibility"/>
+        <WPF:BooleanToVisibilityConverter x:Key="boolToVisibility"
+                                          TrueValue="Visible"
+                                          FalseValue="Collapsed"/>
     </UserControl.Resources>
     <Grid Margin="0" DataContext="{Binding ElementName=Editor}"
           MinHeight="400">
@@ -30,6 +32,11 @@
                       MinWidth="100"
                       DisplayMemberPath="Name"
                       SelectedValue="{Binding EntityType}"/>
+
+            <Button x:Name="EditButton" DockPanel.Dock="Left" Padding="5" Margin="5,0,0,0"
+                    Content="Edit Properties"
+                    Visibility="{Binding HasProperties,Converter={StaticResource boolToVisibility}}"
+                    Click="EditButton_Click"/>
         </DockPanel>
         <ContentControl x:Name="Content" Grid.Row="1" Visibility="Collapsed"/>
         <Grid x:Name="Placeholder" Grid.Row="1">

+ 57 - 3
prs.shared/Grids/EventEditor/EventEditor.xaml.cs

@@ -1,4 +1,5 @@
-using Comal.Classes;
+using AutoProperties;
+using Comal.Classes;
 using InABox.Core;
 using InABox.DynamicGrid;
 using InABox.WPF;
@@ -7,6 +8,7 @@ using System;
 using System.Collections.Generic;
 using System.ComponentModel;
 using System.Linq;
+using System.Reflection;
 using System.Runtime.CompilerServices;
 using System.Text;
 using System.Threading.Tasks;
@@ -54,7 +56,27 @@ public partial class EventEditor : UserControl, INotifyPropertyChanged
         }
     }
 
-    public bool HasType { get; set; }
+    private bool _hasProperties;
+    public bool HasProperties
+    {
+        get => _hasProperties;
+        set
+        {
+            _hasProperties = value;
+            OnPropertyChanged();
+        }
+    }
+
+    private bool _hasType;
+    public bool HasType
+    {
+        get => _hasType;
+        set
+        {
+            _hasType = value;
+            OnPropertyChanged();
+        }
+    }
 
     private Type? EventDataType;
     private Type? DataModelType;
@@ -143,6 +165,7 @@ public partial class EventEditor : UserControl, INotifyPropertyChanged
         if (EventType is null || (EventTypeHasEntityType(EventType.Value) && EntityType is null))
         {
             Data = null;
+            HasProperties = false;
             return;
         }
 
@@ -154,23 +177,54 @@ public partial class EventEditor : UserControl, INotifyPropertyChanged
                 eventType = typeof(SaveEvent<>).MakeGenericType(EntityType!);
                 dataModelType = typeof(SaveEventDataModel<>).MakeGenericType(EntityType!);
                 break;
+            case Comal.Classes.EventType.Scheduled:
+                eventType = typeof(ScheduledEvent);
+                dataModelType = typeof(ScheduledEventDataModel);
+                break;
             default:
                 Data = null;
+                HasProperties = false;
                 return;
         }
 
+        HasProperties = eventType.HasInterface(typeof(IPropertiesEvent<>));
+
         var ev = Activator.CreateInstance(eventType);
         EventDataType = eventType;
         DataModelType = dataModelType;
         Data = (Activator.CreateInstance(typeof(EventData<,>).MakeGenericType(eventType, dataModelType), ev) as IEventData)!;
     }
 
+    private void EditButton_Click(object sender, RoutedEventArgs e)
+    {
+        if (EventDataType is null || EventDataType.GetInterfaceDefinition(typeof(IPropertiesEvent<>)) is not Type propertiesInterface) return;
+
+        var method = GetType().GetMethod(nameof(EditProperties), BindingFlags.Instance | BindingFlags.NonPublic)!
+            .MakeGenericMethod(propertiesInterface.GenericTypeArguments[0]);
+        method.Invoke(this, []);
+    }
+
+    private void EditProperties<TProperties>() where TProperties : BaseObject, new()
+    {
+        if (Data?.Event is not IPropertiesEvent<TProperties> propertiesEvent) return;
+
+        var props = propertiesEvent.GetProperties();
+        if (DynamicGridUtils.EditObject(props))
+        {
+            propertiesEvent.SetProperties(props);
+            DoChanged();
+        }
+    }
+
     public static bool Run(ref EventType evType, ref IEventData? eventData)
     {
         var editor = new EventEditor();
 
         editor.EventType = evType;
-        editor.Data = eventData;
+        if(eventData is not null)
+        {
+            editor.Data = eventData;
+        }
 
         editor.Changed = false;
 

+ 68 - 0
prs.shared/Grids/EventEditor/Trigger Editors/SaveEventTriggerEditors.cs

@@ -0,0 +1,68 @@
+using InABox.Core;
+using InABox.DynamicGrid;
+using PRS.Shared.Events;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PRS.Shared.Grids.EventEditor;
+
+public class PropertyChangedSaveEventTriggerEditor<T> : IEventTriggerEditor<PropertyChangedSaveEventTrigger<T>>
+    where T : Entity, new()
+{
+    public bool Edit(PropertyChangedSaveEventTrigger<T> trigger)
+    {
+        var properties = DatabaseSchema.Properties(typeof(T))
+            .Where(x => x.IsSerializable)
+            .Select(x => x.Name)
+            .ToList();
+        properties.Sort();
+        if(DynamicGridColumnNameSelectorGrid.SelectColumnName(typeof(T), properties, out var value))
+        {
+            trigger.TriggerProperty = DatabaseSchema.Property(typeof(T), value);
+            return true;
+        }
+        else
+        {
+            return false;
+        }
+    }
+}
+
+public class ScriptSaveEventTriggerEditor<T> : IEventTriggerEditor<ScriptSaveEventTrigger<T>>
+    where T : Entity, new()
+{
+    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;
+        }
+    }
+}
+
+public class FilterSaveEventTriggerEditor<T> : IEventTriggerEditor<FilterSaveEventTrigger<T>>
+    where T : Entity, new()
+{
+    public bool Edit(FilterSaveEventTrigger<T> trigger)
+    {
+        var filter = trigger.Filter;
+        if(FilterEditorWindow.Execute(ref filter))
+        {
+            trigger.Filter = filter;
+            return true;
+        }
+        else
+        {
+            return false;
+        }
+    }
+}

+ 27 - 0
prs.shared/Grids/EventEditor/Trigger Editors/ScheduledEventTriggerEditors.cs

@@ -0,0 +1,27 @@
+using InABox.Core;
+using InABox.DynamicGrid;
+using PRS.Shared.Events;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PRS.Shared.Grids.EventEditor;
+
+public class ScriptScheduledEventTriggerEditor : IEventTriggerEditor<ScriptScheduledEventTrigger>
+{
+    public bool Edit(ScriptScheduledEventTrigger 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;
+        }
+    }
+}

+ 0 - 57
prs.shared/Grids/EventEditor/TriggerEditors.cs → prs.shared/Grids/EventEditor/Trigger Editors/TriggerEditors.cs

@@ -72,60 +72,3 @@ public static class EventTriggerEditors
     }
 }
 
-public class PropertyChangedSaveEventTriggerEditor<T> : IEventTriggerEditor<PropertyChangedSaveEventTrigger<T>>
-    where T : Entity, new()
-{
-    public bool Edit(PropertyChangedSaveEventTrigger<T> trigger)
-    {
-        var properties = DatabaseSchema.Properties(typeof(T))
-            .Where(x => x.IsSerializable)
-            .Select(x => x.Name)
-            .ToList();
-        properties.Sort();
-        if(DynamicGridColumnNameSelectorGrid.SelectColumnName(typeof(T), properties, out var value))
-        {
-            trigger.TriggerProperty = DatabaseSchema.Property(typeof(T), value);
-            return true;
-        }
-        else
-        {
-            return false;
-        }
-    }
-}
-
-public class ScriptSaveEventTriggerEditor<T> : IEventTriggerEditor<ScriptSaveEventTrigger<T>>
-    where T : Entity, new()
-{
-    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;
-        }
-    }
-}
-
-public class FilterSaveEventTriggerEditor<T> : IEventTriggerEditor<FilterSaveEventTrigger<T>>
-    where T : Entity, new()
-{
-    public bool Edit(FilterSaveEventTrigger<T> trigger)
-    {
-        var filter = trigger.Filter;
-        if(FilterEditorWindow.Execute(ref filter))
-        {
-            trigger.Filter = filter;
-            return true;
-        }
-        else
-        {
-            return false;
-        }
-    }
-}

+ 4 - 3
prs.shared/Grids/EventGrid.cs

@@ -230,7 +230,7 @@ public class EventGrid : DynamicDataGrid<Event>
         {
             var ev = items.First();
 
-            var editButton = new EditorButton(ev, "Edit Event", 100, EditEvent_Click, false);
+            var editButton = new EditorButton(ev, "Edit Event", 100, (e, i) => EditEvent_Click(form, e, i), false);
             enumEditor.Buttons = [editButton];
         }
         else if(NotificationExpressionColumn.IsEqualTo(column.ColumnName) && editor is ExpressionEditor exprEditor)
@@ -244,7 +244,7 @@ public class EventGrid : DynamicDataGrid<Event>
         return EventData?.Event.DataModelDefinition().GetVariables().Select(x => x.Name) ?? [];
     }
 
-    private void EditEvent_Click(object editor, object? item)
+    private void EditEvent_Click(IDynamicEditorForm form, object editor, object? item)
     {
         if (item is not Event ev) return;
 
@@ -255,6 +255,7 @@ public class EventGrid : DynamicDataGrid<Event>
         {
             EventData = data;
             ev.EventType = type;
+            form.SetEditorValue(nameof(Event.EventType), type);
             if(data is not null)
             {
                 ev.Data = EventUtils.Serialize(data);
@@ -278,7 +279,7 @@ public class EventGrid : DynamicDataGrid<Event>
     {
         if (!Security.IsAllowed<CanManageEvents>())
         {
-            criteria.Add(new Filter<Event>(x => x.Enabled).IsEqualTo(true));
+            criteria.Add(new Filter<Event>(x => x.Visible).IsEqualTo(true));
         }
         base.Reload(criteria, columns, ref sort, token, (data, error) =>
         {

+ 18 - 0
prs.stores/Events/Event.cs

@@ -512,6 +512,24 @@ public interface IEvent
     }
 }
 
+/// <summary>
+/// Indicates that this event type has properties that can be edited; <typeparamref name="TProperties"/> is used to edit these properties
+/// with a dynamic editor.
+/// </summary>
+public interface IPropertiesEvent<TProperties>
+    where TProperties : BaseObject, new()
+{
+    /// <summary>
+    /// Create the properties object for editing.
+    /// </summary>
+    TProperties GetProperties();
+
+    /// <summary>
+    /// Given an edited properties object, save them again.
+    /// </summary>
+    void SetProperties(TProperties properties);
+}
+
 public interface IEvent<TDataModel> : IEvent
 {
     void Init(IStore store, IEventData data, TDataModel model);

+ 2 - 4
prs.stores/Events/SaveEvent.cs

@@ -71,13 +71,11 @@ public class SaveEventDataModelDefinition<T>(SaveEvent<T> ev) : IEventDataModelD
 {
     private IEventVariable[]? variables;
 
-    public SaveEvent<T> Event { get; set; } = ev;
-
     public IEnumerable<IEventVariable> GetVariables()
     {
         if(variables is null)
         {
-            variables = DatabaseSchema.AllProperties(Event.Entity).Select(x => new StandardEventVariable($"{typeof(T).Name}.{x.Name}", x.PropertyType)).ToArray();
+            variables = DatabaseSchema.AllProperties(ev.Entity).Select(x => new StandardEventVariable($"{typeof(T).Name}.{x.Name}", x.PropertyType)).ToArray();
             variables.SortBy(x => x.Name);
         }
         return variables;
@@ -91,7 +89,7 @@ public class SaveEventDataModelDefinition<T>(SaveEvent<T> ev) : IEventDataModelD
             if (name.StartsWith(prefix))
             {
                 name = name[prefix.Length..];
-                var prop = DatabaseSchema.Property(Event.Entity, name);
+                var prop = DatabaseSchema.Property(ev.Entity, name);
                 if(prop is null)
                 {
                     return null;

+ 216 - 0
prs.stores/Events/ScheduledEvent.cs

@@ -0,0 +1,216 @@
+using Comal.Classes;
+using InABox.Core;
+using InABox.Database;
+using InABox.Scripting;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PRS.Shared.Events;
+
+public class ScheduledEventProperties : BaseObject
+{
+    [EditorSequence(1)]
+    public int Frequency { get; set; } = 1;
+
+    [EditorSequence(2)]
+    public SchedulePeriod Period { get; set; } = SchedulePeriod.Hour;
+}
+
+public class ScheduledEvent : IEvent<ScheduledEventDataModel>, IPropertiesEvent<ScheduledEventProperties>
+{
+    public int Frequency { get; set; } = 1;
+
+    public SchedulePeriod Period { get; set; } = SchedulePeriod.Hour;
+
+    public DateTime LastExecution { get; set; }
+
+    public IEventDataModelDefinition DataModelDefinition()
+    {
+        return new ScheduledEventDataModelDefinition();
+    }
+
+    public Notification GenerateNotification(ScheduledEventDataModel model)
+    {
+        var notification = new Notification();
+        notification.Title = $"Schedule has run.";
+        notification.Description = $"Schedule has run.";
+        return notification;
+    }
+
+    public void Init(IStore store, IEventData data, ScheduledEventDataModel model)
+    {
+    }
+
+    public ScheduledEventProperties GetProperties()
+    {
+        return new()
+        {
+            Frequency = Frequency,
+            Period = Period,
+        };
+    }
+
+    public void SetProperties(ScheduledEventProperties properties)
+    {
+        Frequency = properties.Frequency;
+        Period = properties.Period;
+    }
+}
+
+public class ScheduledEventDataModelDefinition : IEventDataModelDefinition
+{
+    public IEventVariable? GetVariable(string name)
+    {
+        return null;
+    }
+
+    public IEnumerable<IEventVariable> GetVariables()
+    {
+        return [];
+    }
+}
+
+public class ScheduledEventDataModel : IEventDataModel
+{
+    public bool TryGetVariable(string name, out object? value)
+    {
+        value = null;
+        return false;
+    }
+}
+
+#region Triggers
+
+[Caption("Custom Script")]
+public class ScriptScheduledEventTrigger : IEventTrigger<ScheduledEvent, ScheduledEventDataModel>
+{
+    public string Description => "Custom Script";
+
+    private ScriptDocument? _scriptDocument;
+    private ScriptDocument? ScriptDocument
+    {
+        get
+        {
+            if(_scriptDocument is null && Script is not null)
+            {
+                _scriptDocument = new(Script);
+                _scriptDocument.Compile();
+            }
+            return _scriptDocument;
+        }
+    }
+
+    private string? _script;
+    public string? Script
+    {
+        get => _script;
+        set
+        {
+            if(_script != value)
+            {
+                _script = value;
+                _scriptDocument = null;
+            }
+        }
+    }
+
+    public IEnumerable<string> ReferencedVariables => [];
+
+    public string DefaultScript()
+    {
+        return @"using PRS.Shared.Events;
+
+public class Module
+{
+    public bool Check(ScheduledEventDataModel model)
+    {
+        // Return true if the requirements are met for this event trigger.
+        return true;
+    }
+}";
+    }
+
+    public bool Check(ScheduledEventDataModel dataModel)
+    {
+        if (ScriptDocument is null) return false;
+
+        return ScriptDocument.Execute(methodname: "Check", parameters: [dataModel]);
+    }
+}
+
+#endregion
+
+#region Actions
+
+[Caption("Custom Script")]
+public class ScriptScheduledEventAction : IEventAction<ScheduledEvent>
+{
+    private ScriptDocument? _scriptDocument;
+    private ScriptDocument? ScriptDocument
+    {
+        get
+        {
+            if(_scriptDocument is null && Script is not null)
+            {
+                _scriptDocument = new(Script);
+                _scriptDocument.SetValue("Result", null);
+                _scriptDocument.Compile();
+            }
+            return _scriptDocument;
+        }
+    }
+
+    private string? _script;
+    public string? Script
+    {
+        get => _script;
+        set
+        {
+            if(_script != value)
+            {
+                _script = value;
+                _scriptDocument = null;
+            }
+        }
+    }
+
+    public IEnumerable<string> ReferencedVariables => [];
+
+    public string Description => "Custom Script";
+
+    public string DefaultScript()
+    {
+        return @"using PRS.Shared.Events;
+
+public class Module
+{
+    public object? Result { get; set; }
+
+    public bool Execute(ScheduledEventDataModel model)
+    {
+        // Do anything you want, and then save return-value to 'Result', or leave it as 'null' if no return value is needed.
+        return true;
+    }
+}";
+    }
+
+    public object? Execute(IEventDataModel dataModel)
+    {
+        if (ScriptDocument is null) return null;
+
+        var model = dataModel.RootModel<ScheduledEventDataModel>();
+        if(ScriptDocument.Execute(methodname: "Execute", parameters: [model]))
+        {
+            return ScriptDocument.GetValue("Result");
+        }
+        else
+        {
+            return null;
+        }
+    }
+}
+
+#endregion