interact.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. import json
  2. import logging
  3. from collections.abc import Mapping
  4. from .framework import Global
  5. from .input_ctrl import send_msg, single_input, input_control
  6. logger = logging.getLogger(__name__)
  7. def run_async(coro):
  8. Global.active_ws.inactive_coro_instances.append(coro)
  9. TEXT = 'text'
  10. NUMBER = "number"
  11. PASSWORD = "password"
  12. CHECKBOX = 'checkbox'
  13. RADIO = 'radio'
  14. SELECT = 'select'
  15. TEXTAREA = 'textarea'
  16. def _parse_args(kwargs):
  17. """处理传给各类input函数的原始参数,
  18. :return:(spec参数,valid_func)
  19. """
  20. # 对为None的参数忽律处理
  21. kwargs = {k: v for k, v in kwargs.items() if v is not None}
  22. kwargs.update(kwargs.get('other_html_attrs', {}))
  23. kwargs.pop('other_html_attrs', None)
  24. valid_func = kwargs.pop('valid_func', lambda _: None)
  25. return kwargs, valid_func
  26. def input(label, type=TEXT, *, valid_func=None, name='data', value=None, placeholder=None, required=None,
  27. readonly=None, disabled=None, **other_html_attrs):
  28. item_spec, valid_func = _parse_args(locals())
  29. # 参数检查
  30. allowed_type = {TEXT, NUMBER, PASSWORD, TEXTAREA}
  31. assert type in allowed_type, 'Input type not allowed.'
  32. def preprocess_func(d):
  33. if type == NUMBER:
  34. return int(d)
  35. return d
  36. return single_input(item_spec, valid_func, preprocess_func)
  37. def textarea(label, rows=6, *, code=None, valid_func=None, name='data', value=None, placeholder=None, required=None,
  38. maxlength=None, minlength=None, readonly=None, disabled=None, **other_html_attrs):
  39. item_spec, valid_func = _parse_args(locals())
  40. item_spec['type'] = TEXTAREA
  41. return single_input(item_spec, valid_func, lambda d: d)
  42. def _parse_select_options(options):
  43. # option 可用形式:
  44. # {value:, label:, [selected:,] [disabled:]}
  45. # (value, label, [selected,] [disabled])
  46. # value 单值,label等于value
  47. opts_res = []
  48. for opt in options:
  49. if isinstance(opt, Mapping):
  50. assert 'value' in opt and 'label' in opt, 'options item must have value and label key'
  51. elif isinstance(opt, list):
  52. assert len(opt) > 1 and len(opt) <= 4, 'options item format error'
  53. opt = dict(zip(('value', 'label', 'selected', 'disabled'), opt))
  54. else:
  55. opt = dict(value=opt, label=opt)
  56. opts_res.append(opt)
  57. return opts_res
  58. def select(label, options, type=SELECT, *, multiple=None, valid_func=None, name='data', value=None,
  59. placeholder=None, required=None, readonly=None, disabled=None, inline=None, **other_html_attrs):
  60. """
  61. 参数值为None表示不指定,使用默认值
  62. :param label:
  63. :param options: option 列表
  64. option 可用形式:
  65. {value:, label:, [selected:,] [disabled:]}
  66. (value, label, [selected,] [disabled])
  67. value 单值,label等于value
  68. :param type:
  69. :param multiple:
  70. :param valid_func:
  71. :param name:
  72. :param value:
  73. :param placeholder:
  74. :param required:
  75. :param readonly:
  76. :param disabled:
  77. :param inline:
  78. :param other_html_attrs:
  79. :return:
  80. """
  81. item_spec, valid_func = _parse_args(locals())
  82. item_spec['options'] = _parse_select_options(options)
  83. allowed_type = {CHECKBOX, RADIO, SELECT}
  84. assert type in allowed_type, 'Input type not allowed.'
  85. if inline is not None and type not in {CHECKBOX, RADIO}:
  86. del item_spec['inline']
  87. logger.warning('inline 只能用于 CHECKBOX, RADIO type, now type:%s', type)
  88. if multiple is not None and type != SELECT:
  89. del item_spec['multiple']
  90. logger.warning('multiple 参数只能用于SELECT type, now type:%s', type)
  91. return single_input(item_spec, valid_func, lambda d: d)
  92. def _parse_action_buttons(buttons):
  93. """
  94. :param label:
  95. :param actions: action 列表
  96. action 可用形式:
  97. {value:, label:, [disabled:]}
  98. (value, label, [disabled])
  99. value 单值,label等于value
  100. :return:
  101. """
  102. act_res = []
  103. for act in buttons:
  104. if isinstance(act, Mapping):
  105. assert 'value' in act and 'label' in act, 'actions item must have value and label key'
  106. elif isinstance(act, list):
  107. assert len(act) in (2, 3), 'actions item format error'
  108. act = dict(zip(('value', 'label', 'disabled'), act))
  109. else:
  110. act = dict(value=act, label=act)
  111. act_res.append(act)
  112. return act_res
  113. def actions(label, buttons, name='data'):
  114. """
  115. 选择一个动作。UI为多个按钮,点击后会将整个表单提交
  116. :param label:
  117. :param actions: action 列表
  118. action 可用形式:
  119. {value:, label:, [disabled:]}
  120. (value, label, [disabled])
  121. value 单值,label等于value
  122. 实现方式:
  123. 多个type=submit的input组成
  124. [
  125. <button data-name value>label</button>,
  126. ...
  127. ]
  128. """
  129. item_spec, valid_func = _parse_args(locals())
  130. item_spec['type'] = 'actions'
  131. item_spec['buttons'] = _parse_action_buttons(buttons)
  132. return single_input(item_spec, valid_func, lambda d: d)
  133. def set_title(title):
  134. send_msg('output_ctl', dict(title=title))
  135. def text_print(text, *, ws=None):
  136. msg = dict(command="output", spec=dict(content=text, type='text'))
  137. (ws or Global.active_ws).write_message(json.dumps(msg))
  138. def json_print(obj):
  139. text = "```\n%s\n```" % json.dumps(obj, indent=4, ensure_ascii=False)
  140. text_print(text)
  141. put_markdown = text_print
  142. def input_group(label, inputs, valid_func=None):
  143. """
  144. :param label:
  145. :param inputs: list of single_input coro
  146. :param valid_func: callback(data) -> (name, error_msg)
  147. :return:
  148. """
  149. spec_inputs = []
  150. preprocess_funcs = {}
  151. item_valid_funcs = {}
  152. for single_input_cr in inputs:
  153. input_kwargs = dict(single_input_cr.cr_frame.f_locals) # 拷贝一份,不可以对locals进行修改
  154. single_input_cr.close()
  155. input_name = input_kwargs['item_spec']['name']
  156. preprocess_funcs[input_name] = input_kwargs['preprocess_func']
  157. item_valid_funcs[input_name] = input_kwargs['valid_func']
  158. spec_inputs.append(input_kwargs['item_spec'])
  159. # def add_autofocus(spec_inputs):
  160. if all('autofocus' not in i for i in spec_inputs): # 每一个输入项都没有设置autofocus参数
  161. for i in spec_inputs:
  162. text_inputs = {TEXT, NUMBER, PASSWORD, SELECT} # todo update
  163. if i.get('type') in text_inputs:
  164. i['autofocus'] = True
  165. break
  166. spec = dict(label=label, inputs=spec_inputs)
  167. return input_control(spec, preprocess_funcs=preprocess_funcs, item_valid_funcs=item_valid_funcs,
  168. form_valid_funcs=valid_func)