PostUtils.cs 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886
  1. using Comal.Classes;
  2. using InABox.Clients;
  3. using InABox.Core;
  4. using InABox.Core.Postable;
  5. using InABox.DynamicGrid;
  6. using InABox.Wpf;
  7. using InABox.WPF;
  8. using Microsoft.CodeAnalysis.CSharp.Syntax;
  9. using Syncfusion.UI.Xaml.Diagram;
  10. using System;
  11. using System.Collections.Generic;
  12. using System.Diagnostics.CodeAnalysis;
  13. using System.Drawing;
  14. using System.Globalization;
  15. using System.Linq;
  16. using System.Text;
  17. using System.Threading.Tasks;
  18. using System.Windows;
  19. using System.Windows.Controls;
  20. using System.Windows.Media.Imaging;
  21. using System.Windows.Threading;
  22. namespace PRSDesktop;
  23. public class PullResultGrid<T> : DynamicItemsListGrid<T>
  24. where T : BaseObject, IPostable, new()
  25. {
  26. private static readonly BitmapImage tick = InABox.Wpf.Resources.tick.AsBitmapImage();
  27. private static readonly BitmapImage link = PRSDesktop.Resources.link.AsBitmapImage();
  28. private static readonly BitmapImage refresh = PRSDesktop.Resources.refresh.AsBitmapImage();
  29. private class ResultItem(PullResultItem<T> item, bool selected)
  30. {
  31. public PullResultItem<T> Item { get; set; } = item;
  32. public bool Selected { get; set; } = selected;
  33. }
  34. public bool CanSave => _items.Any(x => x.Selected);
  35. private List<ResultItem> _items;
  36. public IEnumerable<PullResultItem<T>> Selected => _items.Where(x => x.Selected).Select(x => x.Item);
  37. protected DynamicGridCustomColumnsComponent<T> ColumnsComponent;
  38. public PullResultGrid(IPullResult<T> result)
  39. {
  40. _items = result.PulledEntities.Where(x => x.Item.PostedStatus != PostedStatus.PostFailed).Select(x => new ResultItem(x, false)).ToList();
  41. Items = _items.Select(x => x.Item.Item).ToList();
  42. ColumnsComponent = new DynamicGridCustomColumnsComponent<T>(this, typeof(T).Name);
  43. }
  44. protected override DynamicGridColumns LoadColumns()
  45. {
  46. return ColumnsComponent.LoadColumns();
  47. }
  48. protected override void SaveColumns(DynamicGridColumns columns)
  49. {
  50. ColumnsComponent.SaveColumns(columns);
  51. }
  52. protected override void LoadColumnsMenu(ContextMenu menu)
  53. {
  54. ColumnsComponent.LoadColumnsMenu(menu);
  55. }
  56. private ResultItem? GetItem(CoreRow? row)
  57. {
  58. return row is not null ? _items[_recordmap[row].Index] : null;
  59. }
  60. protected override void Init()
  61. {
  62. base.Init();
  63. ActionColumns.Add(new DynamicImageColumn(Selected_Image, Selected_Click) { Position = DynamicActionColumnPosition.Start });
  64. ActionColumns.Add(new DynamicImageColumn(Action_Image)
  65. {
  66. Position = DynamicActionColumnPosition.Start,
  67. ToolTip = Action_ToolTip
  68. });
  69. }
  70. private FrameworkElement? Action_ToolTip(DynamicActionColumn column, CoreRow? row)
  71. {
  72. var item = GetItem(row);
  73. if(item is null)
  74. {
  75. return column.TextToolTip("Item Import Action");
  76. }
  77. else if(item.Item.Type == PullResultType.Linked)
  78. {
  79. return column.TextToolTip("Existing PRS item linked to external item.");
  80. }
  81. else if(item.Item.Type == PullResultType.Updated)
  82. {
  83. return column.TextToolTip("Existing PRS item updated to external item.");
  84. }
  85. else if(item.Item.Type == PullResultType.New)
  86. {
  87. return column.TextToolTip("New item imported.");
  88. }
  89. else
  90. {
  91. return null;
  92. }
  93. }
  94. private BitmapImage? Action_Image(CoreRow? row)
  95. {
  96. var item = GetItem(row);
  97. if(item is null)
  98. {
  99. return null;
  100. }
  101. else if(item.Item.Type == PullResultType.Linked)
  102. {
  103. return link;
  104. }
  105. else if(item.Item.Type == PullResultType.Updated)
  106. {
  107. return refresh;
  108. }
  109. else
  110. {
  111. return null;
  112. }
  113. }
  114. private BitmapImage? Selected_Image(CoreRow? row)
  115. {
  116. var item = GetItem(row);
  117. return (item is null || item.Selected)
  118. ? tick
  119. : null;
  120. }
  121. private bool Selected_Click(CoreRow? row)
  122. {
  123. var item = GetItem(row);
  124. if(item is not null)
  125. {
  126. item.Selected = !item.Selected;
  127. DoChanged();
  128. InvalidateRow(row!);
  129. return false;
  130. }
  131. else
  132. {
  133. var menu = new ContextMenu();
  134. menu.AddItem("Select All", null, () =>
  135. {
  136. foreach (var item in _items)
  137. {
  138. item.Selected = true;
  139. }
  140. DoChanged();
  141. InvalidateGrid();
  142. });
  143. menu.AddItem("Deselect All", null, () =>
  144. {
  145. foreach (var item in _items)
  146. {
  147. item.Selected = false;
  148. }
  149. DoChanged();
  150. InvalidateGrid();
  151. });
  152. menu.IsOpen = true;
  153. return false;
  154. }
  155. }
  156. protected override void DoReconfigure(DynamicGridOptions options)
  157. {
  158. base.DoReconfigure(options);
  159. options.Clear();
  160. options.SelectColumns = true;
  161. options.FilterRows = true;
  162. }
  163. }
  164. internal class PosterProgressDispatcher : IPosterDispatcher
  165. {
  166. private string Message;
  167. private TaskCompletionSource _yield = new();
  168. private TaskCompletionSource? _continue = new();
  169. private Task? _queue;
  170. private string? _setMessage;
  171. public PosterProgressDispatcher(string? message = null)
  172. {
  173. Message = message ?? "Loading Data";
  174. }
  175. public Task ExecuteAsync(Action action)
  176. {
  177. var taskCompletionSource = new TaskCompletionSource();
  178. _queue = new Task(() =>
  179. {
  180. try
  181. {
  182. action();
  183. taskCompletionSource.SetResult();
  184. }
  185. catch(Exception e)
  186. {
  187. taskCompletionSource.SetException(e);
  188. }
  189. });
  190. _continue = new();
  191. _yield.TrySetResult();
  192. return Task.WhenAll(_continue.Task, taskCompletionSource.Task);
  193. }
  194. public Task<T> ExecuteAsync<T>(Func<T> func)
  195. {
  196. var taskCompletionSource = new TaskCompletionSource<T>();
  197. _queue = new Task(() =>
  198. {
  199. try
  200. {
  201. var result = func();
  202. taskCompletionSource.SetResult(result);
  203. }
  204. catch (Exception e)
  205. {
  206. taskCompletionSource.SetException(e);
  207. }
  208. });
  209. _continue = new();
  210. _yield.TrySetResult();
  211. return _continue.Task.ContinueWith(task => taskCompletionSource.Task.Result);
  212. }
  213. public void Report(string report)
  214. {
  215. Message = report;
  216. _setMessage = report;
  217. _continue = new();
  218. _yield.TrySetResult();
  219. _continue.Task.Wait();
  220. }
  221. public void WaitTask(Task task)
  222. {
  223. _yield = new();
  224. task.ContinueWith(t => _yield.TrySetResult());
  225. while (true)
  226. {
  227. Progress.ShowModal(Message, progress =>
  228. {
  229. while (true)
  230. {
  231. _yield.Task.Wait();
  232. if(_setMessage is not null)
  233. {
  234. progress.Report(_setMessage);
  235. _yield = new();
  236. _setMessage = null;
  237. _continue?.SetResult();
  238. _continue = null;
  239. }
  240. else
  241. {
  242. break;
  243. }
  244. }
  245. });
  246. if(_queue is not null)
  247. {
  248. _yield = new();
  249. _queue.RunSynchronously();
  250. _queue = null;
  251. _continue?.SetResult();
  252. _continue = null;
  253. }
  254. else
  255. {
  256. break;
  257. }
  258. }
  259. }
  260. }
  261. public static class PostUtils
  262. {
  263. private static readonly Inflector.Inflector inflector = new(new CultureInfo("en"));
  264. public static async Task PostEntitiesAsync<T>(IDataModel<T> model, Action refresh, Action? configurePost = null)
  265. where T : Entity, IPostable, IRemotable, IPersistent, new()
  266. {
  267. bool retry;
  268. do
  269. {
  270. retry = false;
  271. try
  272. {
  273. var dispatcher = new PosterProgressDispatcher();
  274. var task = Task.Run(() =>
  275. {
  276. return PosterUtils.Process(model,
  277. engine =>
  278. {
  279. engine.Dispatcher = dispatcher;
  280. },
  281. engine =>
  282. {
  283. });
  284. });
  285. dispatcher.WaitTask(task);
  286. var result = await task;
  287. if (result is null)
  288. {
  289. MessageWindow.ShowMessage($"Processing failed", "Processing failed");
  290. refresh();
  291. }
  292. else
  293. {
  294. var failedMessages = new List<string>();
  295. var successCount = 0;
  296. foreach (var entity in result.PostedEntities)
  297. {
  298. if (entity.PostedStatus == PostedStatus.PostFailed)
  299. {
  300. failedMessages.Add(entity.PostedNote);
  301. }
  302. else
  303. {
  304. successCount++;
  305. }
  306. }
  307. if (successCount == 0)
  308. {
  309. MessageWindow.ShowMessage($"Processing failed:\n - {string.Join("\n - ", failedMessages)}", "Processing failed.");
  310. }
  311. else if (failedMessages.Count == 0)
  312. {
  313. MessageWindow.ShowMessage($"Processing successful; {successCount} items processed", "Processing successful.");
  314. }
  315. else
  316. {
  317. MessageWindow.ShowMessage($"{successCount} items succeeded, but {failedMessages.Count} failed:\n - {string.Join("\n - ", failedMessages)}", "Partial success");
  318. }
  319. refresh();
  320. }
  321. }
  322. catch (EmptyPostException)
  323. {
  324. MessageWindow.ShowMessage($"Please select at least one {typeof(T).Name}.", "Select items");
  325. }
  326. catch (PostFailedMessageException e)
  327. {
  328. MessageWindow.ShowMessage(e.Message, "Post failed");
  329. }
  330. catch (RepostedException)
  331. {
  332. MessageWindow.ShowMessage("At least one of the items you selected has already been processed. Processing cancelled.", "Already processed");
  333. }
  334. catch (PostCancelledException)
  335. {
  336. MessageWindow.ShowMessage("Processing cancelled.", "Cancelled");
  337. }
  338. catch (MissingSettingException e)
  339. {
  340. if (configurePost is not null && Security.CanConfigurePost<T>())
  341. {
  342. if (MessageWindow.ShowYesNo($"'{e.Setting}' has not been set-up for {inflector.Pluralize(typeof(T).Name)}. Would you like to configure this now?",
  343. "Configure Processing?"))
  344. {
  345. bool success = false;
  346. if (e.SettingsType.IsAssignableTo(typeof(IGlobalPosterSettings)))
  347. {
  348. success = PostableSettingsGrid.ConfigureGlobalPosterSettings(e.SettingsType);
  349. }
  350. else
  351. {
  352. success = PostableSettingsGrid.ConfigurePosterSettings<T>(e.SettingsType);
  353. }
  354. if (success && MessageWindow.ShowYesNo("Settings updated; Would you like to retry the post?", "Retry?"))
  355. {
  356. retry = true;
  357. }
  358. else
  359. {
  360. MessageWindow.ShowMessage("Processing cancelled.", "Cancelled");
  361. }
  362. }
  363. else
  364. {
  365. MessageWindow.ShowMessage("Processing cancelled.", "Cancelled");
  366. }
  367. }
  368. else
  369. {
  370. MessageWindow.ShowMessage($"'{e.Setting}' has not been set-up for {inflector.Pluralize(typeof(T).Name)}", "Unconfigured");
  371. }
  372. }
  373. catch (MissingSettingsException)
  374. {
  375. if (configurePost is not null && Security.CanConfigurePost<T>())
  376. {
  377. if (MessageWindow.ShowYesNo($"Processing has not been configured for {inflector.Pluralize(typeof(T).Name)}. Would you like to configure this now?",
  378. "Configure Processing?"))
  379. {
  380. configurePost();
  381. }
  382. else
  383. {
  384. MessageWindow.ShowMessage("Processing cancelled.", "Cancelled");
  385. }
  386. }
  387. else
  388. {
  389. MessageWindow.ShowMessage($"Processing has not been configured for {inflector.Pluralize(typeof(T).Name)}!", "Unconfigured");
  390. }
  391. }
  392. catch (Exception e)
  393. {
  394. MessageWindow.ShowError("Processing failed.", e);
  395. refresh();
  396. }
  397. } while (retry);
  398. }
  399. public static void PostEntities<T>(IDataModel<T> model, Action refresh, Action? configurePost = null)
  400. where T : Entity, IPostable, IRemotable, IPersistent, new()
  401. {
  402. PostEntitiesAsync(model, refresh, configurePost).LogIfFail();
  403. }
  404. public static bool ShowPullResultGrid<T>(IPullResult<T> result, [NotNullWhen(true)] out List<PullResultItem<T>>? items)
  405. where T : Entity, IPostable, IRemotable, IPersistent, new()
  406. {
  407. var resultGrid = new PullResultGrid<T>(result);
  408. var window = new DynamicContentDialog(resultGrid)
  409. {
  410. Title = "Select items to import:",
  411. CanSave = false
  412. };
  413. resultGrid.OnChanged += (o, e) => window.CanSave = resultGrid.CanSave;
  414. resultGrid.Refresh(true, true);
  415. if(window.ShowDialog() == true)
  416. {
  417. items = resultGrid.Selected.ToList();
  418. result.SavePull(items);
  419. return true;
  420. }
  421. else
  422. {
  423. items = null;
  424. return false;
  425. }
  426. }
  427. public static async Task PullEntitiesAsync<T>(Action refresh, Action? configurePost = null)
  428. where T : Entity, IPostable, IRemotable, IPersistent, new()
  429. {
  430. bool retry;
  431. do
  432. {
  433. retry = false;
  434. try
  435. {
  436. var dispatcher = new PosterProgressDispatcher("Importing Data");
  437. var task = Task.Run(() =>
  438. {
  439. return PosterUtils.Pull<T>(
  440. engine =>
  441. {
  442. engine.Dispatcher = dispatcher;
  443. },
  444. engine =>
  445. {
  446. });
  447. });
  448. dispatcher.WaitTask(task);
  449. var result = await task;
  450. if (result is null)
  451. {
  452. MessageWindow.ShowMessage($"Import failed", "Import failed");
  453. refresh();
  454. }
  455. else
  456. {
  457. List<PullResultItem<T>>? items;
  458. if (!result.PulledEntities.Any(x => x.Item.PostedStatus != PostedStatus.PostFailed))
  459. {
  460. items = result.PulledEntities.ToList();
  461. }
  462. else
  463. {
  464. ShowPullResultGrid(result, out items);
  465. }
  466. if (items is null)
  467. {
  468. MessageWindow.ShowMessage("Import cancelled.", "Cancelled");
  469. }
  470. else
  471. {
  472. var failedMessages = new List<string>();
  473. var successCount = 0;
  474. var importCount = 0;
  475. var updateCount = 0;
  476. var linkCount = 0;
  477. foreach (var item in items)
  478. {
  479. if (item.Item.PostedStatus == PostedStatus.PostFailed)
  480. {
  481. failedMessages.Add(item.Item.PostedNote);
  482. }
  483. else
  484. {
  485. successCount++;
  486. switch (item.Type)
  487. {
  488. case PullResultType.New:
  489. importCount++;
  490. break;
  491. case PullResultType.Linked:
  492. linkCount++;
  493. break;
  494. case PullResultType.Updated:
  495. default:
  496. updateCount++;
  497. break;
  498. }
  499. }
  500. }
  501. if (failedMessages.Count > 0 && successCount == 0)
  502. {
  503. MessageWindow.ShowMessage($"Import failed:\n - {string.Join("\n - ", failedMessages)}", "Import failed.");
  504. }
  505. else if (failedMessages.Count == 0)
  506. {
  507. if (successCount == 0)
  508. {
  509. MessageWindow.ShowMessage($"Nothing imported.", "Import successful.");
  510. }
  511. else
  512. {
  513. MessageWindow.ShowMessage($"Import successful; {importCount} items imported, {linkCount} items linked.", "Import successful.");
  514. }
  515. }
  516. else
  517. {
  518. MessageWindow.ShowMessage($"{successCount} items succeeded, but {failedMessages.Count} failed:\n - {string.Join("\n - ", failedMessages)}", "Partial success");
  519. }
  520. refresh();
  521. }
  522. }
  523. }
  524. catch (PullFailedMessageException e)
  525. {
  526. MessageWindow.ShowMessage(e.Message, "Import failed");
  527. }
  528. catch (PullCancelledException)
  529. {
  530. MessageWindow.ShowMessage("Import cancelled.", "Cancelled");
  531. }
  532. catch (MissingSettingException e)
  533. {
  534. if (configurePost is not null && Security.CanConfigurePost<T>())
  535. {
  536. if (MessageWindow.ShowYesNo($"'{e.Setting}' has not been set-up for {inflector.Pluralize(typeof(T).Name)}. Would you like to configure this now?",
  537. "Configure Import?"))
  538. {
  539. bool success = false;
  540. if (e.SettingsType.IsAssignableTo(typeof(IGlobalPosterSettings)))
  541. {
  542. success = PostableSettingsGrid.ConfigureGlobalPosterSettings(e.SettingsType);
  543. }
  544. else
  545. {
  546. success = PostableSettingsGrid.ConfigurePosterSettings<T>(e.SettingsType);
  547. }
  548. if (success && MessageWindow.ShowYesNo("Settings updated; Would you like to retry the import?", "Retry?"))
  549. {
  550. retry = true;
  551. }
  552. else
  553. {
  554. MessageWindow.ShowMessage("Import cancelled.", "Cancelled");
  555. }
  556. }
  557. else
  558. {
  559. MessageWindow.ShowMessage("Import cancelled.", "Cancelled");
  560. }
  561. }
  562. else
  563. {
  564. MessageWindow.ShowMessage($"'{e.Setting}' has not been set-up for {inflector.Pluralize(typeof(T).Name)}", "Unconfigured");
  565. }
  566. }
  567. catch (MissingSettingsException)
  568. {
  569. if (configurePost is not null && Security.CanConfigurePost<T>())
  570. {
  571. if (MessageWindow.ShowYesNo($"Importing has not been configured for {inflector.Pluralize(typeof(T).Name)}. Would you like to configure this now?",
  572. "Configure Import?"))
  573. {
  574. configurePost();
  575. }
  576. else
  577. {
  578. MessageWindow.ShowMessage("Import cancelled.", "Cancelled");
  579. }
  580. }
  581. else
  582. {
  583. MessageWindow.ShowMessage($"Importing has not been configured for {inflector.Pluralize(typeof(T).Name)}!", "Unconfigured");
  584. }
  585. }
  586. catch (Exception e)
  587. {
  588. MessageWindow.ShowError("Import failed.", e);
  589. refresh();
  590. }
  591. } while (retry);
  592. }
  593. public static void PullEntities<T>(Action refresh, Action? configurePost = null)
  594. where T : Entity, IPostable, IRemotable, IPersistent, new()
  595. {
  596. PullEntitiesAsync<T>(refresh, configurePost).LogIfFail();
  597. }
  598. public static void CreateToolbarButtons<T>(IPanelHost host, Func<IDataModel<T>> model, Action refresh, Action? configurePost = null)
  599. where T : Entity, IPostable, IRemotable, IPersistent, new()
  600. {
  601. if (!Security.CanPost<T>()) return;
  602. var postSettings = PosterUtils.LoadPostableSettings<T>();
  603. Type? poster = null;
  604. if (!postSettings.PosterType.IsNullOrWhiteSpace())
  605. {
  606. var posterEngine = PosterUtils.GetEngine(typeof(T));
  607. poster = PosterUtils.GetPoster(postSettings.PosterType);
  608. Bitmap? image = null;
  609. if (postSettings.Thumbnail.ID != Guid.Empty)
  610. {
  611. var icon = new Client<Document>()
  612. .Load(Filter<Document>.Where(x => x.ID).IsEqualTo(postSettings.Thumbnail.ID)).FirstOrDefault();
  613. if (icon?.Data?.Any() == true)
  614. image = new ImageConverter().ConvertFrom(icon.Data) as Bitmap;
  615. }
  616. host.CreatePanelAction(new PanelAction
  617. {
  618. Caption = postSettings.ButtonName.NotWhiteSpaceOr($"Process {inflector.Pluralize(typeof(T).Name)}"),
  619. Image = image ?? PRSDesktop.Resources.edit,
  620. OnExecute = action =>
  621. {
  622. PostEntities(
  623. model(),
  624. refresh,
  625. configurePost);
  626. }
  627. });
  628. if(posterEngine.Get(out var posterEngineType, out var _) && posterEngineType.HasInterface(typeof(IPullerEngine<>)) && postSettings.ShowPullButton)
  629. {
  630. host.CreatePanelAction(new PanelAction($"Import {inflector.Pluralize(typeof(T).Name)}", image ?? PRSDesktop.Resources.doc_xls, action =>
  631. {
  632. PullEntities<T>(refresh);
  633. }));
  634. }
  635. if (postSettings.ShowClearButton)
  636. {
  637. host.CreatePanelAction(new PanelAction
  638. {
  639. Caption = "Clear Posted Flag",
  640. Image = image ?? PRSDesktop.Resources.refresh,
  641. OnExecute = action =>
  642. {
  643. var dataModel = model();
  644. foreach(var (key, table) in dataModel.ModelTables)
  645. {
  646. table.IsDefault = false;
  647. }
  648. dataModel.SetColumns<T>(Columns.Required<T>().Add(x => x.PostedStatus).Add(x => x.PostedReference).Add(x => x.PostedNote).Add(x => x.Posted));
  649. dataModel.SetIsDefault<T>(true);
  650. dataModel.LoadModel();
  651. var items = dataModel.GetTable<T>().ToArray<T>();
  652. foreach(var item in items)
  653. {
  654. item.PostedStatus = PostedStatus.NeverPosted;
  655. item.PostedReference = "";
  656. item.PostedNote = "";
  657. item.Posted = DateTime.MinValue;
  658. }
  659. Client.Save(items, "Cleared posted flag");
  660. refresh();
  661. }
  662. });
  663. }
  664. }
  665. if (configurePost is not null)
  666. {
  667. host.CreateSetupAction(new PanelAction
  668. {
  669. Caption = $"Configure {CoreUtils.Neatify(typeof(T).Name)} Processing",
  670. OnExecute = action =>
  671. {
  672. configurePost();
  673. }
  674. });
  675. var configures = poster?.GetInterfaces(typeof(IConfigureMapsPoster<,>)).ToArray() ?? [];
  676. if(configures.Length > 0)
  677. {
  678. host.CreateSetupAction(new PanelAction
  679. {
  680. Caption = $"Configure Post Maps...",
  681. OnPopulate = action =>
  682. {
  683. return configures.ToArray(x =>
  684. {
  685. var tConfigure = x.GenericTypeArguments[0];
  686. return new PanelActionEntry<Type>(tConfigure.Name, null, x.GenericTypeArguments[1], ConfigureMaps);
  687. });
  688. }
  689. });
  690. }
  691. }
  692. }
  693. private class ConfigureItem(object? value, string display, string reference)
  694. {
  695. public object? Value { get; } = value;
  696. public string Display { get; } = display;
  697. public string Reference { get; } = reference;
  698. }
  699. private static void ConfigureMaps(PanelActionEntry<Type> entry) // entry.Data is a definition of IPosterMapConfigurer
  700. {
  701. try
  702. {
  703. var configurerType = entry.Data!;
  704. var configurer = (Activator.CreateInstance(configurerType) as IPosterMapConfigurer)!;
  705. var dispatcher = new PosterProgressDispatcher("Loading Maps");
  706. var task = Task.Run(() => configurer.GetMaps(dispatcher));
  707. dispatcher.WaitTask(task);
  708. var allItems =
  709. CoreUtils.One(new ConfigureItem(null, "(Blank)", ""))
  710. .Concat(task.Result.OfType<object?>().Select(x => new ConfigureItem(x, configurer.MapDescriptor(x), configurer.Reference(x))))
  711. .ToArray();
  712. var tConfigure = configurerType.GetInterfaceDefinition(typeof(IPosterMapConfigurer<>))!.GenericTypeArguments[0];
  713. var grid = (DynamicGridUtils.CreateDynamicGrid(typeof(DynamicItemsListGrid<>), tConfigure) as IDynamicItemsListGrid)!;
  714. var window = new DynamicContentDialog((grid as FrameworkElement)!)
  715. {
  716. Title = $"Configure {tConfigure.Name} maps"
  717. };
  718. grid.AddHiddenColumn(CoreUtils.GetFullPropertyName<IPostableFragment, string>(x => x.PostedReference));
  719. grid.Reconfigure(options =>
  720. {
  721. options.Clear();
  722. });
  723. grid.ActionColumns.Add(new DynamicTemplateColumn(row =>
  724. {
  725. var item = (grid.LoadItem(row) as IPostableFragment);
  726. var value = item!.PostedReference;
  727. var comboBox = new ComboBox
  728. {
  729. ItemsSource = allItems,
  730. SelectedValuePath = "Reference",
  731. DisplayMemberPath = "Display",
  732. VerticalContentAlignment = VerticalAlignment.Center
  733. };
  734. comboBox.SelectedValue = value;
  735. comboBox.SelectionChanged += (o, e) =>
  736. {
  737. window.CanSave = true;
  738. item.PostedReference = (comboBox.SelectedValue as string)!;
  739. };
  740. return comboBox;
  741. })
  742. {
  743. HeaderText = "Maps To..."
  744. });
  745. grid.Refresh(true, false);
  746. var items = Client.Create(tConfigure)
  747. .Query(
  748. Filter.Create(tConfigure).All(),
  749. grid.DataColumns())
  750. .ToObjects(tConfigure);
  751. foreach(var item in items)
  752. {
  753. grid.Items.Add(item);
  754. }
  755. grid.Refresh(false, true);
  756. if(window.ShowDialog() == true)
  757. {
  758. Client.Create(tConfigure).Save(grid.Items.OfType<Entity>().Where(x => x.IsChanged()), "Updated poster mapping");
  759. }
  760. }
  761. catch(Exception e)
  762. {
  763. MessageWindow.ShowError("Configuration failed", e);
  764. }
  765. }
  766. public static void ConfigurePost<T>()
  767. where T : Entity, IPostable, IRemotable, IPersistent, new()
  768. {
  769. var postSettings = PosterUtils.LoadPostableSettings<T>();
  770. var grid = (DynamicGridUtils.CreateDynamicGrid(typeof(DynamicGrid<>), typeof(PostableSettings)) as DynamicGrid<PostableSettings>)!;
  771. if (grid.EditItems(new PostableSettings[] { postSettings }))
  772. {
  773. PosterUtils.SavePostableSettings<T>(postSettings);
  774. }
  775. }
  776. public static void CreateToolbarButtons<T>(IPanelHost host, Func<IDataModel<T>> model, Action refresh, bool allowConfig)
  777. where T : Entity, IPostable, IRemotable, IPersistent, new()
  778. {
  779. CreateToolbarButtons(host, model, refresh, allowConfig ? ConfigurePost<T> : null);
  780. }
  781. #region PostColumn
  782. private static readonly BitmapImage? post = PRSDesktop.Resources.post.AsBitmapImage();
  783. private static readonly BitmapImage? tick = PRSDesktop.Resources.tick.AsBitmapImage();
  784. private static readonly BitmapImage? warning = PRSDesktop.Resources.warning.AsBitmapImage();
  785. private static readonly BitmapImage? refresh = PRSDesktop.Resources.refresh.AsBitmapImage();
  786. public static void AddPostColumn<T>(DynamicGrid<T> grid)
  787. where T : Entity, IPostable, IRemotable, IPersistent, new()
  788. {
  789. grid.HiddenColumns.Add(x => x.PostedStatus);
  790. grid.HiddenColumns.Add(x => x.PostedNote);
  791. grid.ActionColumns.Add(new DynamicImageColumn(
  792. row =>
  793. {
  794. if (row is null)
  795. return post;
  796. return row.Get<T, PostedStatus>(x => x.PostedStatus) switch
  797. {
  798. PostedStatus.PostFailed => warning,
  799. PostedStatus.Posted => tick,
  800. PostedStatus.RequiresRepost => refresh,
  801. PostedStatus.NeverPosted or _ => null,
  802. };
  803. },
  804. null)
  805. {
  806. ToolTip = (column, row) =>
  807. {
  808. if (row is null)
  809. {
  810. return column.TextToolTip($"{CoreUtils.Neatify(typeof(T).Name)} Processed Status");
  811. }
  812. return column.TextToolTip(row.Get<T, PostedStatus>(x => x.PostedStatus) switch
  813. {
  814. PostedStatus.PostFailed => "Post failed: " + row.Get<T, string>(x => x.PostedNote),
  815. PostedStatus.RequiresRepost => "Repost required: " + row.Get<T, string>(x => x.PostedNote),
  816. PostedStatus.Posted => "Processed",
  817. PostedStatus.NeverPosted or _ => "Not posted yet",
  818. });
  819. }
  820. });
  821. }
  822. #endregion
  823. }