output.ts 5.6 KB

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