Prechádzať zdrojové kódy

Added DynamicManyToMany cross tab.

Kenric Nugteren 1 rok pred
rodič
commit
3f9eb92adc

+ 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
 
     }

+ 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.pencil, obj, DeleteCell);
+                }
+            }
+            else
+            {
+                if (Security.CanEdit<TManyToMany>())
+                {
+                    menu.AddItem("Create Item", Wpf.Resources.pencil, (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}");
+        }
+    }
+}