UtilityDashboard.xaml.cs 21 KB

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