output.ts 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. import {state} from '../state'
  2. import {b64toBlob} from "../utils";
  3. /*
  4. * 当前限制
  5. * 若外层为layout类的Widget,则内层Widget在get_element中绑定的事件将会失效
  6. * */
  7. export interface Widget {
  8. handle_type: string;
  9. get_element(spec: any): JQuery;
  10. }
  11. let Text = {
  12. handle_type: 'text',
  13. get_element: function (spec: any): JQuery {
  14. let elem = spec.inline ? $('<span></span>') : $('<p></p>');
  15. spec.content = spec.content.replace(/ /g, '\u00A0');
  16. // make '\n' to <br/>
  17. let lines = (spec.content || '').split('\n');
  18. for (let idx = 0; idx < lines.length - 1; idx++)
  19. elem.append(document.createTextNode(lines[idx])).append('<br/>');
  20. elem.append(document.createTextNode(lines[lines.length - 1]));
  21. return elem;
  22. }
  23. };
  24. let _md_parser = new Mditor.Parser();
  25. let Markdown = {
  26. handle_type: 'markdown',
  27. get_element: function (spec: any) {
  28. return $(_md_parser.parse(spec.content));
  29. }
  30. };
  31. // 将html字符串解析成jQuery对象
  32. function parseHtml(html_str: string) {
  33. let nodes = $.parseHTML(html_str, null, true);
  34. let elem;
  35. if (nodes.length != 1)
  36. elem = $(document.createElement('div')).append(nodes);
  37. else
  38. elem = $(nodes[0]);
  39. return elem;
  40. }
  41. let Html = {
  42. handle_type: 'html',
  43. get_element: function (spec: any) {
  44. return parseHtml(spec.content);
  45. }
  46. };
  47. let Buttons = {
  48. handle_type: 'buttons',
  49. get_element: function (spec: any) {
  50. const btns_tpl = `<div>{{#buttons}}
  51. <button value="{{value}}" onclick="WebIO.DisplayAreaButtonOnClick(this, '{{callback_id}}')" class="btn {{btn_class}}{{#small}} btn-sm{{/small}}">{{label}}</button>
  52. {{/buttons}}</div>`;
  53. spec.btn_class = spec.link ? "btn-link" : "btn-primary";
  54. let html = Mustache.render(btns_tpl, spec);
  55. return $(html);
  56. }
  57. };
  58. // 显示区按钮点击回调函数
  59. export function DisplayAreaButtonOnClick(this_ele: HTMLElement, callback_id: string) {
  60. if (state.CurrentSession === null)
  61. return console.error("can't invoke DisplayAreaButtonOnClick when WebIOController is not instantiated");
  62. let val = $(this_ele).val();
  63. state.CurrentSession.send_message({
  64. event: "callback",
  65. task_id: callback_id,
  66. data: val
  67. });
  68. }
  69. // 已废弃。为了向下兼容而保留
  70. let File = {
  71. handle_type: 'file',
  72. get_element: function (spec: any) {
  73. const html = `<div><button type="button" class="btn btn-link">${spec.name}</button></div>`;
  74. let element = $(html);
  75. let blob = b64toBlob(spec.content);
  76. element.on('click', 'button', function (e) {
  77. saveAs(blob, spec.name, {}, false);
  78. });
  79. return element;
  80. }
  81. };
  82. let Table = {
  83. handle_type: 'table',
  84. get_element: function (spec: { data: string[][], span: { [i: string]: { col: number, row: number } } }) {
  85. const table_tpl = `
  86. <table>
  87. <tr>
  88. {{#header}}
  89. <th{{#col}} colspan="{{col}}"{{/col}}{{#row}} rowspan="{{row}}"{{/row}}>{{& data}}</th>
  90. {{/header}}
  91. </tr>
  92. {{#tdata}}
  93. <tr>
  94. {{# . }}
  95. <td{{#col}} colspan="{{col}}"{{/col}}{{#row}} rowspan="{{row}}"{{/row}}>{{& data}}</td>
  96. {{/ . }}
  97. </tr>
  98. {{/tdata}}
  99. </table>`;
  100. interface itemType {
  101. data: string,
  102. col?: number,
  103. row?: number
  104. }
  105. // 将spec转化成模版引擎的输入
  106. let table_data: itemType[][] = [];
  107. for (let row_id in spec.data) {
  108. table_data.push([]);
  109. let row = spec.data[row_id];
  110. for (let col_id in row) {
  111. let data = spec.data[row_id][col_id];
  112. // 处理复合类型单元格,即单元格不是简单的html,而是一个output命令的spec
  113. if (typeof data === 'object') {
  114. data = outputSpecToHtml(data);
  115. }
  116. table_data[row_id].push({
  117. data: data,
  118. ...(spec.span[row_id + ',' + col_id] || {})
  119. });
  120. }
  121. }
  122. let header: itemType[], data: itemType[][];
  123. [header, ...data] = table_data;
  124. let html = Mustache.render(table_tpl, {header: header, tdata: data});
  125. return $(html);
  126. }
  127. };
  128. let CustomWidget = {
  129. handle_type: 'custom_widget',
  130. get_element: function (spec: { template: string, data: { [i: string]: any } }) {
  131. spec.data['pywebio_output_parse'] = function () {
  132. if (this.type)
  133. return outputSpecToHtml(this);
  134. else
  135. return outputSpecToHtml({type: 'text', content: this, inline: true});
  136. };
  137. let html = Mustache.render(spec.template, spec.data);
  138. return parseHtml(html);
  139. }
  140. };
  141. let all_widgets: Widget[] = [Text, Markdown, Html, Buttons, File, Table, CustomWidget];
  142. let type2widget: { [i: string]: Widget } = {};
  143. for (let w of all_widgets)
  144. type2widget[w.handle_type] = w;
  145. export function getWidgetElement(spec: any) {
  146. if (!(spec.type in type2widget))
  147. throw Error("Unknown type in getWidgetElement() :" + spec.type);
  148. let elem = type2widget[spec.type].get_element(spec);
  149. if (spec.style) {
  150. let old_style = elem.attr('style') || '';
  151. elem.attr({"style": old_style + spec.style});
  152. }
  153. return elem;
  154. }
  155. // 将output指令的spec字段解析成html字符串
  156. export function outputSpecToHtml(spec: any) {
  157. let html = '';
  158. try {
  159. let nodes = getWidgetElement(spec);
  160. for (let node of nodes)
  161. html += node.outerHTML || '';
  162. } catch (e) {
  163. console.error('Get sub widget html error,', e, spec);
  164. }
  165. return html;
  166. }