浏览代码

Merge commit '8c165df470d1d384fe2e5a64f4bf5688d0e6cd07' into frank

Frank van den Bos 1 年之前
父节点
当前提交
adb21823b1

+ 8 - 2
InABox.Client.RPC/Transports/Socket/RPCClientSocketTransport.cs

@@ -177,7 +177,7 @@ namespace InABox.Rpc
             }
             catch (Exception e)
             {
-                Console.WriteLine(e);
+                Logger.Send(LogType.Error, ClientFactory.UserID, $"Error in CreateSocket(): {e.Message}");
                 throw;
             }
             
@@ -246,6 +246,12 @@ namespace InABox.Rpc
                     else
                     {
                         childCts.Cancel();
+
+                        if(_socket != null)
+                        {
+                            throw new Exception("Socket already exists!");
+                        }
+
                         _socket = result.Result;
                         _connected = true;
 
@@ -267,7 +273,7 @@ namespace InABox.Rpc
             }
         }
 
-        public override bool IsConnected() => _connected;//_socket?.State == WebSocketState.Open;
+        public override bool IsConnected() => _socket?.State == WebSocketState.Open;
         public override bool IsSecure() => _socket?.Security.Certificates.Count > 0;
 
         public override String? ServerName() => _host;

+ 7 - 2
InABox.Core/Configuration/GlobalConfiguration.cs

@@ -75,11 +75,16 @@ namespace InABox.Configuration
             return result;
         }
 
-        public override Dictionary<string, T> LoadAll()
+        public override Dictionary<string, T> LoadAll(IEnumerable<string>? sections = null)
         {
             var result = new Dictionary<string, T>();
+            var filter = new Filter<GlobalSettings>(x => x.Section).IsEqualTo(typeof(T).Name);
+            if(sections != null)
+            {
+                filter.And(x => x.Key).InList(sections.AsArray());
+            }
             var data = new Client<GlobalSettings>().Query(
-                new Filter<GlobalSettings>(x => x.Section).IsEqualTo(typeof(T).Name),
+                filter,
                 new Columns<GlobalSettings>(x => x.Key, x => x.Contents),
                 new SortOrder<GlobalSettings>(x => x.Key)
             );

+ 2 - 2
InABox.Core/Configuration/IConfiguration.cs

@@ -9,7 +9,7 @@ namespace InABox.Configuration
         public string[] Sections();
 
         T Load(bool useCache = true);
-        Dictionary<string, T> LoadAll();
+        Dictionary<string, T> LoadAll(IEnumerable<string>? sections = null);
 
         void Save(T obj);
         /// <summary>
@@ -36,7 +36,7 @@ namespace InABox.Configuration
 
         public abstract string[] Sections();
 
-        public abstract Dictionary<string, T> LoadAll();
+        public abstract Dictionary<string, T> LoadAll(IEnumerable<string>? sections = null);
 
         public abstract T Load(bool useCache = true);
 

+ 5 - 2
InABox.Core/Configuration/LocalConfiguration.cs

@@ -96,9 +96,12 @@ namespace InABox.Configuration
             return result;
         }
 
-        public override Dictionary<string, T> LoadAll()
+        public override Dictionary<string, T> LoadAll(IEnumerable<string>? sections = null)
         {
-            return LoadCurrentConfig(EnsureFileName());
+            var config = LoadCurrentConfig(EnsureFileName());
+            return sections != null
+                ? sections.ToDictionary(x => x, x => config.GetValueOrDefault(x))
+                : config;
         }
 
         public override void Save(T obj)

+ 8 - 2
InABox.Core/Configuration/UserConfiguration.cs

@@ -77,11 +77,17 @@ namespace InABox.Configuration
             return result;
         }
 
-        public override Dictionary<string, T> LoadAll()
+        public override Dictionary<string, T> LoadAll(IEnumerable<string>? sections = null)
         {
             var result = new Dictionary<string, T>();
+            var filter = new Filter<UserSettings>(x => x.Section).IsEqualTo(typeof(T).Name)
+                .And(x => x.UserID).IsEqualTo(ClientFactory.UserID);
+            if (sections != null)
+            {
+                filter.And(x => x.Key).InList(sections.AsArray());
+            }
             var data = new Client<UserSettings>().Query(
-                new Filter<UserSettings>(x => x.Section).IsEqualTo(typeof(T).Name).And(x => x.UserID).IsEqualTo(ClientFactory.UserID),
+                filter,
                 new Columns<UserSettings>(x => x.Key, x => x.Contents),
                 new SortOrder<UserSettings>(x => x.Key)
             );

+ 11 - 0
InABox.Core/CoreUtils.cs

@@ -2787,6 +2787,17 @@ namespace InABox.Core
             return (trueResult, falseResult);
         }
 
+        public static TValue GetValueOrAdd<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key)
+            where TValue : new()
+        {
+            if(!dictionary.TryGetValue(key, out var value))
+            {
+                value = new TValue();
+                dictionary.Add(key, value);
+            }
+            return value;
+        }
+
         #endregion
 
     }

+ 14 - 7
inabox.wpf/DynamicGrid/BaseDynamicGrid.cs

@@ -83,10 +83,16 @@ namespace InABox.DynamicGrid
 
         public event IDynamicGrid.ReconfigureEvent? OnReconfigure;
         
+        private bool _hasLoadedOptions = false;
+        
         public BaseDynamicGrid()
         {
             Options = new FluentList<DynamicGridOption>();
-            Options.OnChanged += OptionsChanged;
+            Options.OnChanged += (sender, args) =>
+            {
+                _hasLoadedOptions = true;
+                OptionsChanged();
+            };
             
             RowStyleSelector = GetRowStyleSelector();
             RowStyleSelector.GetStyle += (row, style) => GetRowStyle(row, style);
@@ -94,17 +100,13 @@ namespace InABox.DynamicGrid
             HiddenColumns = new List<Expression<Func<T, object?>>>();
         }
 
-        private void DefaultOptions_OnChanged(object sender, EventArgs args)
-        {
-            Reconfigure();
-        }
-
         /// <summary>
         /// Initialise things like custom buttons; called once during construction.
         /// </summary>
         protected abstract void Init();
 
         protected abstract void DoReconfigure(FluentList<DynamicGridOption> options);
+
         /// <summary>
         /// Configure custom buttons and options.
         /// </summary>
@@ -114,6 +116,11 @@ namespace InABox.DynamicGrid
             DoReconfigure(options);
             OnReconfigure?.Invoke(options);
             options.EndUpdate();
+            if (!_hasLoadedOptions)
+            {
+                _hasLoadedOptions = true;
+                OptionsChanged();
+            }
         }
         public void Reconfigure()
         {
@@ -206,7 +213,7 @@ namespace InABox.DynamicGrid
             OnPrintData?.Invoke(sender);
         }
 
-        protected abstract void OptionsChanged(object sender, EventArgs args);
+        protected abstract void OptionsChanged();
         
         public IEnumerable<TType> ExtractValues<TType>(Expression<Func<T, TType>> column, Selection selection)
         {

+ 2 - 2
inabox.wpf/DynamicGrid/DynamicDataGrid.cs

@@ -148,9 +148,9 @@ namespace InABox.DynamicGrid
 
         public string? ColumnsTag { get; set; }
 
-        protected override void OptionsChanged(object sender, EventArgs args)
+        protected override void OptionsChanged()
         {
-            base.OptionsChanged(sender, args);
+            base.OptionsChanged();
             if (MergeBtn != null)
                 MergeBtn.Visibility = Visibility.Collapsed;
             ShowFilterList = HasOption(DynamicGridOption.FilterRows);

+ 3 - 2
inabox.wpf/DynamicGrid/DynamicGrid.cs

@@ -669,7 +669,7 @@ namespace InABox.DynamicGrid
             set => DataGrid.HeaderRowHeight = value;
         }
 
-        protected override void OptionsChanged(object sender, EventArgs args)
+        protected override void OptionsChanged()
         {
             var reloadColumns = false;
 
@@ -1398,11 +1398,12 @@ namespace InABox.DynamicGrid
         {
             if (!IsEnabled)
                 return;
-            
+
             var dac = ColumnList[e.RowColumnIndex.ColumnIndex] as DynamicActionColumn;
             if (dac != null)
             {
                 var bRefresh = false;
+                if(e.ChangedButton == MouseButton.Left)
                 {
                     foreach (var row in SelectedRows)
                         if (dac.Action?.Invoke(row) == true)

+ 306 - 0
inabox.wpf/DynamicGrid/DynamicManyToManyCrossTab.cs

@@ -0,0 +1,306 @@
+using InABox.Clients;
+using InABox.Core;
+using InABox.WPF;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Controls;
+using System.Windows.Media.Imaging;
+
+namespace InABox.DynamicGrid
+{
+    public abstract class DynamicManyToManyCrossTab<TManyToMany, TRow, TColumn> : DynamicGrid<TRow>
+        where TManyToMany : Entity, IRemotable, IPersistent, new()
+        where TRow : Entity, IRemotable, IPersistent, new()
+        where TColumn : Entity, IRemotable, IPersistent, new()
+    {
+        private static readonly BitmapImage tick = Wpf.Resources.tick.AsBitmapImage();
+
+        /// <summary>
+        /// Property on <typeparamref name="TManyToMany"/> which is an <see cref="EntityLink{T}"/> for <typeparamref name="TRow"/>.
+        /// </summary>
+        private readonly PropertyInfo rowProperty;
+        /// <summary>
+        /// Property on <typeparamref name="TManyToMany"/> which is an <see cref="EntityLink{T}"/> for <typeparamref name="TColumn"/>.
+        /// </summary>
+        private readonly PropertyInfo columnProperty;
+
+        private CoreTable? ColumnData;
+        /// <summary>
+        /// {ColumnID : { RowID }}
+        /// </summary>
+        private Dictionary<Guid, Dictionary<Guid, TManyToMany>>? ManyToManyData;
+
+        public DynamicManyToManyCrossTab()
+        {
+            rowProperty = CoreUtils.GetManyToManyThisProperty(typeof(TManyToMany), typeof(TRow));
+            columnProperty = CoreUtils.GetManyToManyThisProperty(typeof(TManyToMany), typeof(TColumn));
+
+            HeaderHeight = 125;
+        }
+
+        protected override void Init()
+        {
+        }
+
+        protected override void DoReconfigure(FluentList<DynamicGridOption> options)
+        {
+            options.Clear();
+        }
+
+        /// <summary>
+        /// Load the required columns for <typeparamref name="TRow"/>.
+        /// </summary>
+        protected abstract DynamicGridColumns LoadRowColumns();
+
+        protected abstract Columns<TColumn>? LoadColumnColumns();
+
+        protected abstract SortOrder<TColumn>? LoadColumnSort();
+
+        protected abstract string FormatColumnHeader(CoreRow row);
+
+        protected virtual Filter<TRow>? RowFilter() => null;
+
+        protected virtual Filter<TColumn>? ColumnFilter() => null;
+
+        protected override DynamicGridColumns LoadColumns()
+        {
+            var columns = LoadRowColumns();
+
+            var client = Client.Create(typeof(TColumn));
+
+            var columnColumns = new Columns<TColumn>(x => x.ID);
+            if(LoadColumnColumns() is Columns<TColumn> extra)
+            {
+                foreach(var col in extra.GetColumns())
+                {
+                    columnColumns.Add(col);
+                }
+            }
+
+            ColumnData = Client.Query(ColumnFilter(), columnColumns, LoadColumnSort());
+            ActionColumns.Clear();
+            foreach(var columnRow in ColumnData.Rows)
+            {
+                var colID = columnRow.Get<TColumn, Guid>(x => x.ID);
+                ActionColumns.Add(new DynamicImageColumn(
+                    (row) =>
+                    {
+                        if (row is null || ManyToManyData is null) return null;
+                        if(ManyToManyData.TryGetValue(colID, out var rowSet))
+                        {
+                            var rowID = row.Get<TRow, Guid>(x => x.ID);
+                            if (rowSet.ContainsKey(rowID))
+                            {
+                                return tick;
+                            }
+                        }
+                        return null;
+                    },
+                    (row) =>
+                    {
+                        if (row is null) return false;
+                        var rowID = row.Get<TRow, Guid>(x => x.ID);
+                        return CellClick(colID, rowID);
+                    }
+                )
+                {
+                    HeaderText = FormatColumnHeader(columnRow),
+                    ContextMenu = (rows) =>
+                    {
+                        var row = rows?.FirstOrDefault();
+                        if (row is null) return null;
+                        var rowID = row.Get<TRow, Guid>(x => x.ID);
+                        return CellMenu(colID, rowID);
+                    }
+                });
+            }
+
+            return columns;
+        }
+
+        private ContextMenu? CellMenu(Guid colID, Guid rowID)
+        {
+            if (ManyToManyData is null) return null;
+
+            var menu = new ContextMenu();
+
+            if (ManyToManyData.TryGetValue(colID, out var rowSet)
+                && rowSet.TryGetValue(rowID, out var obj))
+            {
+                if (Security.CanEdit<TManyToMany>() && CanEditCell(obj))
+                {
+                    menu.AddItem("Edit Item", Wpf.Resources.pencil, obj, (obj) =>
+                    {
+                        if (EditCell(obj))
+                        {
+                            Refresh(false, true);
+                        }
+                    });
+                }
+                if (Security.CanDelete<TManyToMany>())
+                {
+                    menu.AddItem("Delete Item", Wpf.Resources.delete, obj, DeleteCell);
+                }
+            }
+            else
+            {
+                if (Security.CanEdit<TManyToMany>())
+                {
+                    menu.AddItem("Create Item", Wpf.Resources.add, (colID, rowID), CreateCell);
+                }
+            }
+
+            if(menu.Items.Count > 0)
+            {
+                return menu;
+            }
+            else
+            {
+                return null;
+            }
+        }
+
+        private void CreateCell((Guid colID, Guid rowID) obj)
+        {
+            var manyToMany = CreateManyToMany(obj.rowID, obj.colID);
+            if (SaveManyToMany(manyToMany))
+            {
+                Refresh(false, true);
+            }
+        }
+
+        private void DeleteCell(TManyToMany obj)
+        {
+            if (DeleteManyToMany(obj))
+            {
+                Refresh(false, true);
+            }
+        }
+
+        protected virtual bool CanEditCell(TManyToMany obj) => false;
+
+        /// <summary>
+        /// Code to edit a <typeparamref name="TManyToMany"/> cell; note that this <b>must not</b> allow for changing either
+        /// the <typeparamref name="TColumn"/> or the <typeparamref name="TRow"/>, otherwise we would easily get duplicate <typeparamref name="TManyToMany"/>s.
+        /// </summary>
+        /// <remarks>
+        /// This method should also save the object.
+        /// </remarks>
+        /// <param name="obj"></param>
+        protected virtual bool EditCell(TManyToMany obj)
+        {
+            return false;
+        }
+
+        private bool CellClick(Guid columnID, Guid rowID)
+        {
+            if (ManyToManyData is null) return false;
+            if (ManyToManyData.TryGetValue(columnID, out var rowSet)
+                && rowSet.TryGetValue(rowID, out var obj))
+            {
+                if (Security.CanDelete<TManyToMany>())
+                {
+                    return DeleteManyToMany(obj);
+                }
+                else
+                {
+                    return false;
+                }
+            }
+            else
+            {
+                if (Security.CanEdit<TManyToMany>())
+                {
+                    obj = CreateManyToMany(rowID, columnID);
+                    return SaveManyToMany(obj);
+                }
+                else
+                {
+                    return false;
+                }
+            }
+        }
+
+        protected virtual TManyToMany CreateManyToMany(Guid rowID, Guid columnID)
+        {
+            var item = new TManyToMany();
+            (rowProperty.GetValue(item) as IEntityLink)!.ID = rowID;
+            (columnProperty.GetValue(item) as IEntityLink)!.ID = columnID;
+            return item;
+        }
+
+        protected virtual bool SaveManyToMany(TManyToMany obj)
+        {
+            Client.Save(obj, "Edited by user");
+            return true;
+        }
+
+        protected virtual bool DeleteManyToMany(TManyToMany obj)
+        {
+            Client.Delete(obj, "Deleted by user");
+            return true;
+        }
+
+        protected override void Reload(Filters<TRow> criteria, Columns<TRow> columns, ref SortOrder<TRow>? sort, Action<CoreTable?, Exception?> action)
+        {
+            var filter = criteria.Add(RowFilter()).Combine();
+
+            var manyToManyColumns = new Columns<TManyToMany>(x => x.ID);
+            manyToManyColumns.Add(rowProperty.Name + ".ID").Add(columnProperty.Name + ".ID");
+
+            Client.QueryMultiple(
+                (results, e) =>
+                {
+                    if(e is not null)
+                    {
+                        action(null, e);
+                    }
+                    else
+                    {
+                        var manyToManyTable = results!.Get<TManyToMany>();
+
+                        ManyToManyData = new Dictionary<Guid, Dictionary<Guid, TManyToMany>>();
+                        foreach(var row in manyToManyTable.Rows)
+                        {
+                            var obj = row.ToObject<TManyToMany>();
+                            var rowID = (rowProperty.GetValue(obj) as IEntityLink)!.ID;
+                            var colID = (columnProperty.GetValue(obj) as IEntityLink)!.ID;
+                            var rowSet = ManyToManyData.GetValueOrAdd(colID);
+                            rowSet[rowID] = obj;
+                        }
+
+                        action(results!.Get<TRow>(), null);
+                    }
+                },
+                new KeyedQueryDef<TRow>(filter, columns, sort),
+                new KeyedQueryDef<TManyToMany>(
+                    new Filter<TManyToMany>(rowProperty.Name + ".ID").InQuery(filter, x => x.ID)
+                        .And(columnProperty.Name + ".ID").InQuery(ColumnFilter(), x => x.ID),
+                    manyToManyColumns));
+        }
+
+        public override void SaveItem(TRow item)
+        {
+            // Never should get called.
+        }
+
+        protected override void DeleteItems(params CoreRow[] rows)
+        {
+            // Never should get called.
+        }
+
+        protected override TRow LoadItem(CoreRow row)
+        {
+            var id = row.Get<TRow, Guid>(x => x.ID);
+            return Client.Query(
+                new Filter<TRow>(x => x.ID).IsEqualTo(id),
+                DynamicGridUtils.LoadEditorColumns(DataColumns()))
+                .ToObjects<TRow>()
+                .FirstOrDefault() ?? throw new Exception($"No {typeof(TRow).Name} with ID {id}");
+        }
+    }
+}