UtilityDashboard.xaml.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Linq;
  5. using System.Reflection;
  6. using System.Windows;
  7. using System.Windows.Controls;
  8. using System.Windows.Media;
  9. using Comal.Classes;
  10. using InABox.Configuration;
  11. using InABox.Core;
  12. using InABox.DynamicGrid;
  13. using InABox.WPF;
  14. using InABox.Wpf;
  15. using InABox.WPF.Themes;
  16. using Microsoft.Xaml.Behaviors.Core;
  17. using Syncfusion.Windows.Tools.Controls;
  18. namespace PRSDesktop
  19. {
  20. public class DashboardFavourite : BaseObject
  21. {
  22. [TextBoxEditor]
  23. [EditorSequence(1)]
  24. public string Name { get; set; }
  25. [CheckBoxEditor]
  26. [EditorSequence(2)]
  27. public bool IsGlobal { get; set; }
  28. [NullEditor]
  29. public string Layout { get; set; }
  30. }
  31. // public class DynamicItemsListGrid<T> : DynamicGrid<T>
  32. // where T : BaseObject, new()
  33. // {
  34. // public List<T> Items { get; set; }
  35. //
  36. // public DynamicItemsListGrid() : this(new()) { }
  37. //
  38. // public DynamicItemsListGrid(List<T> items) : base()
  39. // {
  40. // Items = items;
  41. // }
  42. //
  43. // public override void DeleteItems(params CoreRow[] rows)
  44. // {
  45. // foreach (var row in rows.OrderByDescending(x => x.Index))
  46. // {
  47. // Items.RemoveAt(row.Index);
  48. // }
  49. // }
  50. //
  51. // public override T LoadItem(CoreRow row)
  52. // {
  53. // return Items[row.Index];
  54. // }
  55. //
  56. // protected override void Reload(Filters<T> criteria, Columns<T> columns, ref SortOrder<T>? sort, Action<CoreTable?, Exception?> action)
  57. // {
  58. // var result = new CoreTable();
  59. // result.LoadColumns(typeof(T));
  60. // result.LoadRows(Items);
  61. // action.Invoke(result, null);
  62. // }
  63. //
  64. // public override void SaveItem(T item)
  65. // {
  66. // if (!Items.Contains(item))
  67. // {
  68. // Items.Add(item);
  69. // }
  70. // }
  71. // }
  72. public class GlobalUtilityDashboardSettings : IGlobalConfigurationSettings
  73. {
  74. public List<DashboardFavourite> Favourites { get; set; }
  75. public GlobalUtilityDashboardSettings()
  76. {
  77. Favourites = new();
  78. }
  79. }
  80. public class UtilityDashboardSettings : IUserConfigurationSettings
  81. {
  82. public UtilityDashboardSettings()
  83. {
  84. Dashboards = new Dictionary<string, string>();
  85. Favourites = new();
  86. }
  87. public Dictionary<string, string> Dashboards { get; set; }
  88. public List<DashboardFavourite> Favourites { get; set; }
  89. public int Selected { get; set; }
  90. public bool AutoHide { get; set; }
  91. }
  92. /// <summary>
  93. /// Interaction logic for UtilityDashboard.xaml
  94. /// </summary>
  95. public partial class UtilityDashboard : UserControl, IBasePanel
  96. {
  97. private readonly Dictionary<string, DynamicFormDesignGrid> _dashboards = new();
  98. private readonly Dictionary<DynamicFormDesignGrid, List<ICorePanel>> _panels = new();
  99. private class WidgetDashboardElement
  100. {
  101. public Type DashboardElement { get; set; }
  102. public Type Widget { get; set; }
  103. public Type Group { get; set; }
  104. public Type Properties { get; set; }
  105. public string GroupCaption { get; set; }
  106. public string WidgetCaption { get; set; }
  107. public Type[] SecurityTokens { get; set; }
  108. public WidgetDashboardElement(Type dashboardElement, Type widget, Type group, Type properties, Type[] securityTokens)
  109. {
  110. DashboardElement = dashboardElement;
  111. Widget = widget;
  112. Group = group;
  113. Properties = properties;
  114. SecurityTokens = securityTokens;
  115. GroupCaption = UtilityDashboard.GetCaption(group);
  116. WidgetCaption = UtilityDashboard.GetCaption(widget);
  117. }
  118. }
  119. private static List<WidgetDashboardElement>? _dashboardElements;
  120. private string? CurrentDashboardName => DashboardsTab.SelectedTab?.Header?.ToString();
  121. private DynamicFormDesignGrid? CurrentDashboard => CurrentDashboardName != null ? _dashboards.GetValueOrDefault(CurrentDashboardName) : null;
  122. private UtilityDashboardSettings _settings = new();
  123. public UtilityDashboard()
  124. {
  125. InitializeComponent();
  126. }
  127. public void CreateToolbarButtons(IPanelHost host)
  128. {
  129. }
  130. private void SaveSettings()
  131. {
  132. new UserConfiguration<UtilityDashboardSettings>().Save(_settings);
  133. }
  134. #region Panel Functions & Properties
  135. public event DataModelUpdateEvent? OnUpdateDataModel;
  136. public bool IsReady { get; set; }
  137. public string SectionName => "Utility Dashboard";
  138. public DataModel DataModel(Selection selection)
  139. {
  140. return new EmptyDataModel();
  141. }
  142. public void Setup()
  143. {
  144. _settings = new UserConfiguration<UtilityDashboardSettings>().Load();
  145. if (_settings.Dashboards.Count == 0) _settings.Dashboards["New Dashboard"] = CreateForm("").SaveLayout();
  146. foreach (var key in _settings.Dashboards.Keys)
  147. CreateTab(key);
  148. if (_settings.Selected >= -1 && _settings.Selected < DashboardsTab.Items.Count)
  149. DashboardsTab.SelectedIndex = _settings.Selected;
  150. //DashboardsTab.FullScreenMode = _settings.AutoHide ? FullScreenMode.ControlMode : FullScreenMode.None;
  151. }
  152. public Dictionary<string, object[]> Selected()
  153. {
  154. return new Dictionary<string, object[]>();
  155. }
  156. public void Heartbeat(TimeSpan time)
  157. {
  158. }
  159. public void Refresh()
  160. {
  161. if(CurrentDashboardName is string name)
  162. {
  163. RefreshDashboard(name);
  164. }
  165. }
  166. public void Shutdown(CancelEventArgs? cancel)
  167. {
  168. foreach (var (name, grid) in _dashboards)
  169. {
  170. ShutdownDashboard(name, grid);
  171. }
  172. _panels.Clear();
  173. }
  174. #endregion
  175. #region Favourites
  176. private IEnumerable<DashboardFavourite> GetFavourites()
  177. {
  178. foreach(var favourite in _settings.Favourites)
  179. {
  180. yield return favourite;
  181. }
  182. var global = new GlobalConfiguration<GlobalUtilityDashboardSettings>().Load(false).Favourites;
  183. foreach (var favourite in global)
  184. {
  185. yield return favourite;
  186. }
  187. }
  188. private void ManageFavourites_Click()
  189. {
  190. var favourites = GetFavourites().ToList();
  191. var grid = new DynamicItemsListGrid<DashboardFavourite>() { Items = favourites };
  192. grid.Reconfigure(options =>
  193. {
  194. options.AddRange(DynamicGridOption.DeleteRows, DynamicGridOption.EditRows, DynamicGridOption.MultiSelect);
  195. });
  196. grid.OnCustomiseEditor += FavouritesGrid_OnCustomiseEditor;
  197. DynamicGridUtils.CreateGridWindow("Manage Favourites", grid).ShowDialog();
  198. _settings.Favourites = favourites.Where(x => !x.IsGlobal).ToList();
  199. SaveSettings();
  200. if (Security.IsAllowed<CanSetGlobalDashboardFavourites>())
  201. {
  202. var config = new GlobalConfiguration<GlobalUtilityDashboardSettings>();
  203. var global = config.Load();
  204. global.Favourites = favourites.Where(x => x.IsGlobal).ToList();
  205. config.Save(global);
  206. }
  207. }
  208. private void FavouritesGrid_OnCustomiseEditor(IDynamicEditorForm sender, DashboardFavourite[]? items, DynamicGridColumn column, BaseEditor editor)
  209. {
  210. if(column.ColumnName == "IsGlobal")
  211. {
  212. editor.Editable = Security.IsAllowed<CanSetGlobalDashboardFavourites>() ? Editable.Enabled : Editable.Disabled;
  213. }
  214. }
  215. private void SaveAsFavourite_Click(string dashboardName)
  216. {
  217. _settings.Favourites.Add(new DashboardFavourite
  218. {
  219. Name = dashboardName,
  220. Layout = _dashboards.GetValueOrDefault(dashboardName)?.Form.SaveLayout() ?? _settings.Dashboards[dashboardName]
  221. });
  222. SaveSettings();
  223. }
  224. private void LoadFavourite_Click(DashboardFavourite favourite)
  225. {
  226. var name = CreateNewTabName(favourite.Name);
  227. _settings.Dashboards[name] = favourite.Layout;
  228. SaveSettings();
  229. var tab = CreateTab(name);
  230. DashboardsTab.SelectedItem = tab;
  231. }
  232. #endregion
  233. #region Tabs
  234. private void Tab_OnContextMenuOpening(object sender, DynamicTabItemContextMenuEventArgs args)
  235. {
  236. var name = (DashboardsTab.SelectedItem as DynamicTabItem)?.Header?.ToString();
  237. if (string.IsNullOrEmpty(name))
  238. return;
  239. DynamicFormDesignGrid grid = _dashboards[name];
  240. var menu = args.Menu;
  241. menu.AddSeparatorIfNeeded();
  242. var isDesigning = grid.Mode != FormMode.Preview;
  243. menu.Items.Add(new MenuItem()
  244. {
  245. Header = isDesigning ? "Close Design Mode" : "Design Mode",
  246. Command = new ActionCommand(() =>
  247. {
  248. if (grid.Mode == FormMode.Designing)
  249. {
  250. grid.Mode = FormMode.Preview;
  251. SaveCurrentDashboard();
  252. DashboardsTab.ChangedCommand.Execute(null);
  253. }
  254. else
  255. {
  256. ShutdownDashboard();
  257. grid.Mode = FormMode.Designing;
  258. }
  259. }),
  260. Icon = new Image() { Source = (isDesigning ? PRSDesktop.Resources.delete : PRSDesktop.Resources.pencil).AsBitmapImage(24, 24) }
  261. });
  262. var index = 0;
  263. var favourites = GetFavourites().ToList();
  264. if (favourites.Any())
  265. {
  266. foreach (var favourite in favourites)
  267. {
  268. menu.AddItem(favourite.Name, null, favourite, LoadFavourite_Click, index: index++);
  269. }
  270. menu.AddSeparatorIfNeeded(index: index++);
  271. menu.AddItem("Manage Favourites", null, ManageFavourites_Click, index: index++);
  272. }
  273. menu.AddItem("Save as Favourite", null, name, SaveAsFavourite_Click, index: index++);
  274. menu.AddSeparator(index: index++);
  275. }
  276. private void Tab_OnCloseTab(object sender, DynamicTabControlEventArgs args)
  277. {
  278. var name = args.TabItem.Header?.ToString();
  279. if (name is null)
  280. return;
  281. _dashboards.Remove(name);
  282. _settings.Dashboards.Remove(name);
  283. if (!_settings.Dashboards.Any())
  284. {
  285. var tab = new DynamicTabItem();
  286. InitializeNewDashboardTab(tab);
  287. DashboardsTab.Items.Add(tab);
  288. }
  289. else
  290. {
  291. DashboardsTab.ChangedCommand.Execute(null);
  292. }
  293. }
  294. private void Tab_OnTabRenamed(object sender, DynamicTabItemRenamedEventArgs args)
  295. {
  296. var oldSettings = _settings.Dashboards[args.OldName];
  297. _settings.Dashboards.Remove(args.OldName);
  298. args.NewName = CreateNewTabName(args.NewName);
  299. if (_dashboards.TryGetValue(args.OldName, out var dashboard))
  300. {
  301. _dashboards.Remove(args.OldName);
  302. _dashboards[args.NewName] = dashboard;
  303. _settings.Dashboards[args.NewName] = dashboard.Form.SaveLayout();
  304. }
  305. else
  306. {
  307. _settings.Dashboards[args.NewName] = oldSettings;
  308. }
  309. }
  310. /// <summary>
  311. /// Setup events on a new tab.
  312. /// </summary>
  313. /// <param name="tab"></param>
  314. private void InitializeTab(DynamicTabItem tab)
  315. {
  316. tab.CanClose = true;
  317. tab.OnCloseTab += Tab_OnCloseTab;
  318. tab.CanRename = true;
  319. tab.OnTabRenamed += Tab_OnTabRenamed;
  320. tab.OnContextMenuOpening += Tab_OnContextMenuOpening;
  321. }
  322. private string CreateNewTabName(string name)
  323. {
  324. var newName = name;
  325. int i = 1;
  326. while (TabNameExists(newName))
  327. {
  328. newName = $"{name} ({i})";
  329. ++i;
  330. }
  331. return newName;
  332. }
  333. private bool TabNameExists(string name)
  334. {
  335. return _settings.Dashboards.ContainsKey(name);
  336. }
  337. /// <summary>
  338. /// Creates a new tab with a given header and adds it to <see cref="DashboardsTab"/>.
  339. /// </summary>
  340. /// <param name="header"></param>
  341. /// <returns></returns>
  342. private DynamicTabItem CreateTab(string header)
  343. {
  344. var tab = new DynamicTabItem() { Header = header };
  345. InitializeTab(tab);
  346. DashboardsTab.Items.Add(tab);
  347. return tab;
  348. }
  349. /// <summary>
  350. /// Creates a new dashboard for a tab, and then initializes the tab.
  351. /// </summary>
  352. /// <param name="tab"></param>
  353. private void InitializeNewDashboardTab(DynamicTabItem tab)
  354. {
  355. var name = CreateNewTabName("New Dashboard");
  356. _settings.Dashboards[name] = CreateForm("").SaveLayout();
  357. DashboardsTab.ChangedCommand.Execute(null);
  358. SaveSettings();
  359. tab.Header = name;
  360. InitializeTab(tab);
  361. }
  362. private void DashboardsTab_OnOnCreateTab(object sender, DynamicTabControlEventArgs args)
  363. {
  364. InitializeNewDashboardTab(args.TabItem);
  365. }
  366. private void DashboardsTab_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
  367. {
  368. if (e.OriginalSource != DashboardsTab)
  369. return;
  370. if (e.AddedItems.Count == 0)
  371. return;
  372. ShutdownDashboard();
  373. if (e.AddedItems[0] is DynamicTabItem tab)
  374. {
  375. var name = (tab.Header as string)!;
  376. if (tab!.Content == null)
  377. tab.Content = CreateDashboard(name, _settings.Dashboards[name]);
  378. else
  379. {
  380. RefreshDashboard(name);
  381. }
  382. if (IsReady)
  383. {
  384. _settings.Selected = tab.TabIndex;
  385. DashboardsTab.ChangedCommand.Execute(null);
  386. }
  387. }
  388. }
  389. private void DashboardsTab_OnOnTabsChanged(object sender, EventArgs args)
  390. {
  391. SaveSettings();
  392. }
  393. #endregion
  394. #region Dashboard & Design
  395. private void SaveDashboard(string name, DynamicFormDesignGrid grid)
  396. {
  397. _settings.Dashboards[name] = grid.Form.SaveLayout();
  398. if (IsReady)
  399. SaveSettings();
  400. }
  401. private void SaveCurrentDashboard()
  402. {
  403. var name = CurrentDashboardName;
  404. if (name == null) return;
  405. var grid = CurrentDashboard;
  406. if (grid == null) return;
  407. SaveDashboard(name, grid);
  408. }
  409. private void ShutdownDashboard(string name, DynamicFormDesignGrid grid)
  410. {
  411. SaveDashboard(name, grid);
  412. var cancel = new CancelEventArgs();
  413. foreach (var panel in _panels[grid])
  414. {
  415. panel.Shutdown(cancel);
  416. if (cancel.Cancel)
  417. {
  418. return;
  419. }
  420. }
  421. _panels[grid].Clear();
  422. }
  423. private void ShutdownDashboard()
  424. {
  425. var name = CurrentDashboardName;
  426. if (name == null) return;
  427. var grid = CurrentDashboard;
  428. if (grid == null) return;
  429. ShutdownDashboard(name, grid);
  430. }
  431. private void RefreshDashboard(string name)
  432. {
  433. if (!_dashboards.ContainsKey(name))
  434. return;
  435. var grid = _dashboards[name];
  436. if (_panels.ContainsKey(grid))
  437. {
  438. foreach (var panel in _panels[grid])
  439. panel.Refresh();
  440. }
  441. }
  442. private FrameworkElement CreateElement<TWidget, TGroup, TProperties>(DynamicFormDesignGrid grid, DFLayoutElement<TProperties> element)
  443. where TWidget : FrameworkElement, IDashboardWidget<TGroup, TProperties>, new()
  444. where TGroup : DashboardWidgetGroup
  445. where TProperties : IConfigurationSettings, IDashboardProperties, new()
  446. {
  447. if (!_panels.ContainsKey(grid))
  448. _panels[grid] = new List<ICorePanel>();
  449. var dashboardName = GetDashboardElements()
  450. .Where(x => x.DashboardElement == element.GetType())
  451. .FirstOrDefault()?.WidgetCaption ?? "Unknown Dashboard";
  452. var container = DashboardContainer.Create<TWidget, TGroup, TProperties>(element, dashboardName);
  453. _panels[grid].Add(container.Panel);
  454. return container;
  455. }
  456. private FrameworkElement OnCreateElement(object sender, DynamicFormCreateElementArgs e)
  457. {
  458. var widgetType = GetVisibleDashboardElements().Where(x => x.DashboardElement == e.Element.GetType()).FirstOrDefault();
  459. if(widgetType == null)
  460. {
  461. var border = new Border
  462. {
  463. BorderBrush = new SolidColorBrush(Colors.Gray),
  464. BorderThickness = new Thickness(0.0),
  465. Margin = new Thickness(0.0),
  466. Background = ThemeManager.WorkspaceBackgroundBrush //new SolidColorBrush(Colors.Silver);
  467. };
  468. return border;
  469. }
  470. var method = typeof(UtilityDashboard)
  471. .GetMethod(nameof(CreateElement), BindingFlags.Instance | BindingFlags.NonPublic)!
  472. .MakeGenericMethod(widgetType.Widget, widgetType.Group, widgetType.Properties);
  473. return (method.Invoke(this, new object[] { sender, e.Element }) as FrameworkElement)!;
  474. }
  475. private static string GetCaption(Type groupType)
  476. {
  477. var caption = groupType.GetCustomAttribute<Caption>();
  478. if(caption != null)
  479. {
  480. return caption.Text;
  481. }
  482. return CoreUtils.Neatify(groupType.Name);
  483. }
  484. private static List<WidgetDashboardElement> GetDashboardElements()
  485. {
  486. if (_dashboardElements == null)
  487. {
  488. _dashboardElements = new();
  489. var types = CoreUtils.TypeList(
  490. new [] { Assembly.GetExecutingAssembly() },
  491. x => x.IsClass
  492. && !x.IsAbstract
  493. && !x.IsGenericType
  494. && x.GetInterfaces().Contains(typeof(IDashboardElement)));
  495. foreach (var type in types)
  496. {
  497. var dashboardElementDef = type.GetSuperclassDefinition(typeof(DashboardElement<,,>));
  498. if (dashboardElementDef != null)
  499. {
  500. var dashboard = dashboardElementDef.GenericTypeArguments[0];
  501. var group = dashboardElementDef.GenericTypeArguments[1];
  502. var properties = dashboardElementDef.GenericTypeArguments[2];
  503. var requires = dashboard.GetInterfaces(typeof(IRequiresSecurity<>)).Select(x => x.GenericTypeArguments[0]);
  504. _dashboardElements.Add(new(type, dashboard, group, properties, requires.ToArray()));
  505. }
  506. }
  507. }
  508. return _dashboardElements;
  509. }
  510. private static IEnumerable<WidgetDashboardElement> GetVisibleDashboardElements()
  511. {
  512. return GetDashboardElements().Where(x =>
  513. {
  514. foreach (var require in x.SecurityTokens)
  515. {
  516. if (!Security.IsAllowed(require))
  517. return false;
  518. }
  519. return true;
  520. });
  521. }
  522. private Border CreateDashboard(string name, string layout)
  523. {
  524. var form = CreateForm(layout);
  525. var grid = new DynamicFormDesignGrid();
  526. foreach(var widget in GetVisibleDashboardElements())
  527. {
  528. grid.AddElement(widget.DashboardElement, widget.WidgetCaption, widget.GroupCaption, true);
  529. }
  530. grid.ShowBorders = false;
  531. grid.OnCreateElement += OnCreateElement;
  532. grid.OnAfterDesign += OnAfterDesign;
  533. grid.OnAfterRender += OnAfterRender;
  534. grid.Mode = FormMode.Preview;
  535. var border = new Border
  536. {
  537. BorderBrush = new SolidColorBrush(Colors.Silver),
  538. BorderThickness = new Thickness(0.75),
  539. Child = grid // scroll;
  540. };
  541. _dashboards[name] = grid;
  542. _panels[grid] = new List<ICorePanel>();
  543. grid.Form = form;
  544. grid.Initialize();
  545. return border;
  546. }
  547. private void OnAfterRender(DynamicFormDesignGrid sender)
  548. {
  549. if (!sender.IsDesigning)
  550. {
  551. if (_panels.TryGetValue(sender, out var panels))
  552. {
  553. foreach (var panel in panels)
  554. panel.Refresh();
  555. }
  556. }
  557. }
  558. private static DFLayout CreateForm(string layout)
  559. {
  560. var form = new DFLayout();
  561. if (string.IsNullOrWhiteSpace(layout))
  562. {
  563. form.ColumnWidths.Add("*");
  564. form.ColumnWidths.Add("*");
  565. form.ColumnWidths.Add("*");
  566. form.RowHeights.Add("*");
  567. form.RowHeights.Add("*");
  568. form.RowHeights.Add("*");
  569. }
  570. else
  571. {
  572. form.LoadLayout(layout);
  573. }
  574. return form;
  575. }
  576. private void OnAfterDesign(object sender)
  577. {
  578. SaveCurrentDashboard();
  579. if(CurrentDashboardName is string name) // Null-check
  580. {
  581. RefreshDashboard(name);
  582. }
  583. }
  584. #endregion
  585. }
  586. }