UtilityDashboard.xaml.cs 22 KB

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