popup.ts 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. import {Command, Session} from "../session";
  2. import {randomid} from "../utils";
  3. import {getWidgetElement} from "../models/output"
  4. import {CommandHandler} from "./base";
  5. export class PopupHandler implements CommandHandler {
  6. session: Session;
  7. accept_command = ['popup', 'close_popup'];
  8. private body = $('body');
  9. constructor(session: Session) {
  10. this.session = session;
  11. }
  12. static current_elem: JQuery<HTMLElement> = null; // 当前正在处于显示中的弹窗元素,表示页面的期望状态
  13. handle_message(msg: Command) {
  14. if (PopupHandler.current_elem) {
  15. // @ts-ignore
  16. PopupHandler.current_elem.modal('hide');
  17. PopupHandler.current_elem = null;
  18. }
  19. if (msg.command == 'popup') {
  20. // 显示弹窗前,先关闭其他弹窗
  21. // @ts-ignore
  22. $('.modal').modal('hide');
  23. let elem = PopupHandler.get_element(msg.spec);
  24. this.body.append(elem);
  25. // 弹窗关闭后就立即销毁
  26. elem.on('hidden.bs.modal', function (e) {
  27. elem.remove();
  28. });
  29. elem.on('shown.bs.modal', function (e) {
  30. // 弹窗显示后,有新弹窗出现或当前弹窗被关闭,则立即关闭当前弹窗
  31. if (elem != PopupHandler.current_elem || !PopupHandler.current_elem) {
  32. // @ts-ignore
  33. elem.modal('hide');
  34. }
  35. });
  36. // @ts-ignore
  37. elem.modal('show');
  38. PopupHandler.current_elem = elem;
  39. } else if (msg.command == 'close_popup') {
  40. // @ts-ignore
  41. $('.modal').modal('hide');
  42. PopupHandler.current_elem = null;
  43. }
  44. }
  45. static get_element(spec: { title: string, content: any[], closable: boolean, implicit_close: boolean, size: string }) {
  46. // https://v4.bootcss.com/docs/components/modal/#options
  47. const tpl = `<div class="modal fade" {{^implicit_close}}data-backdrop="static"{{/implicit_close}} aria-labelledby="model-id-{{ mid }}" tabindex="-1" role="dialog" aria-hidden="true">
  48. <div class="modal-dialog modal-dialog-scrollable {{#large}}modal-lg{{/large}} {{#small}}modal-sm{{/small}}" role="document">
  49. <div class="modal-content">
  50. <div class="modal-header">
  51. <h5 class="modal-title" id="model-id-{{ mid }}">{{ title }}</h5>
  52. {{#closable}}
  53. <button type="button" class="close" data-dismiss="modal" aria-label="Close">
  54. <span aria-hidden="true">&times;</span>
  55. </button>
  56. {{/closable}}
  57. </div>
  58. <div class="modal-body markdown-body">
  59. {{& content }}
  60. </div>
  61. <!--
  62. <div class="modal-footer">
  63. <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
  64. <button type="button" class="btn btn-primary">Submit</button>
  65. </div>
  66. -->
  67. </div>
  68. </div>
  69. </div>`;
  70. let mid = randomid(10);
  71. let body_html = '';
  72. for (let output_item of spec.content) {
  73. if (typeof output_item === 'object') {
  74. try {
  75. let nodes = getWidgetElement(output_item);
  76. for (let node of nodes)
  77. body_html += node.outerHTML || '';
  78. } catch (e) {
  79. console.error('Get widget html error,', e, output_item);
  80. }
  81. } else {
  82. body_html += output_item;
  83. }
  84. }
  85. if (!spec.closable)
  86. spec.implicit_close = false;
  87. let html = Mustache.render(tpl, {
  88. ...spec, // 字段: content, title, size, implicit_close, closable
  89. large: spec.size == 'large',
  90. small: spec.size == 'small',
  91. mid: mid,
  92. content: body_html,
  93. });
  94. return $(html as string);
  95. }
  96. }