interact.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. import json
  2. import logging
  3. from collections.abc import Mapping
  4. from base64 import b64decode
  5. from .framework import Global
  6. from .input_ctrl import send_msg, single_input, input_control
  7. logger = logging.getLogger(__name__)
  8. TEXT = 'text'
  9. NUMBER = "number"
  10. PASSWORD = "password"
  11. CHECKBOX = 'checkbox'
  12. RADIO = 'radio'
  13. SELECT = 'select'
  14. TEXTAREA = 'textarea'
  15. def _parse_args(kwargs):
  16. """处理传给各类input函数的原始参数,
  17. :return:(spec参数,valid_func)
  18. """
  19. # 对为None的参数忽律处理
  20. kwargs = {k: v for k, v in kwargs.items() if v is not None}
  21. kwargs.update(kwargs.get('other_html_attrs', {}))
  22. kwargs.pop('other_html_attrs', None)
  23. valid_func = kwargs.pop('valid_func', lambda _: None)
  24. return kwargs, valid_func
  25. def input(label, type=TEXT, *, valid_func=None, name='data', value=None, placeholder=None, required=None,
  26. readonly=None, disabled=None, help_text=None, **other_html_attrs):
  27. """可以通过datalist提供候选输入"""
  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, help_text=None, **other_html_attrs):
  39. """提供codemirror参数产生代码输入样式"""
  40. item_spec, valid_func = _parse_args(locals())
  41. item_spec['type'] = TEXTAREA
  42. return single_input(item_spec, valid_func, lambda d: d)
  43. def _parse_select_options(options):
  44. # option 可用形式:
  45. # {value:, label:, [selected:,] [disabled:]}
  46. # (value, label, [selected,] [disabled])
  47. # value 单值,label等于value
  48. opts_res = []
  49. for opt in options:
  50. if isinstance(opt, Mapping):
  51. assert 'value' in opt and 'label' in opt, 'options item must have value and label key'
  52. elif isinstance(opt, list):
  53. assert len(opt) > 1 and len(opt) <= 4, 'options item format error'
  54. opt = dict(zip(('value', 'label', 'selected', 'disabled'), opt))
  55. else:
  56. opt = dict(value=opt, label=opt)
  57. opts_res.append(opt)
  58. return opts_res
  59. def select(label, options, *, multiple=None, valid_func=None, name='data', value=None,
  60. placeholder=None, required=None, readonly=None, disabled=None, help_text=None,
  61. **other_html_attrs):
  62. """
  63. 参数值为None表示不指定,使用默认值
  64. :param label:
  65. :param options: option 列表
  66. option 可用形式:
  67. {value:, label:, [selected:,] [disabled:]}
  68. (value, label, [selected,] [disabled])
  69. value 单值,label等于value
  70. :param type: SELECT or CHECKBOX or RADIO
  71. :param multiple: 是否可以多选,只能用于type=SELECT
  72. :param valid_func:
  73. :param name:
  74. :param value:
  75. :param placeholder:
  76. :param required:
  77. :param readonly:
  78. :param disabled:
  79. :param inline: 是否将选项显示在一行上,只能用于 type=CHECKBOX, RADIO
  80. :param other_html_attrs:
  81. :await return: options中选中的项的value的列表
  82. """
  83. item_spec, valid_func = _parse_args(locals())
  84. item_spec['options'] = _parse_select_options(options)
  85. item_spec['type'] = SELECT
  86. return single_input(item_spec, valid_func, lambda d: d)
  87. def checkbox(label, options, *, inline=None, valid_func=None, name='data', value=None,
  88. placeholder=None, required=None, readonly=None, disabled=None, help_text=None,
  89. **other_html_attrs):
  90. """"""
  91. item_spec, valid_func = _parse_args(locals())
  92. item_spec['options'] = _parse_select_options(options)
  93. item_spec['type'] = CHECKBOX
  94. return single_input(item_spec, valid_func, lambda d: d)
  95. def radio(label, options, *, inline=None, valid_func=None, name='data', value=None,
  96. placeholder=None, required=None, readonly=None, disabled=None, help_text=None,
  97. **other_html_attrs):
  98. """"""
  99. item_spec, valid_func = _parse_args(locals())
  100. item_spec['options'] = _parse_select_options(options)
  101. item_spec['type'] = RADIO
  102. return single_input(item_spec, valid_func, lambda d: d)
  103. def _parse_action_buttons(buttons):
  104. """
  105. :param label:
  106. :param actions: action 列表
  107. action 可用形式:
  108. {value:, label:, [disabled:]}
  109. (value, label, [disabled])
  110. value 单值,label等于value
  111. :return:
  112. """
  113. act_res = []
  114. for act in buttons:
  115. if isinstance(act, Mapping):
  116. assert 'value' in act and 'label' in act, 'actions item must have value and label key'
  117. elif isinstance(act, list):
  118. assert len(act) in (2, 3), 'actions item format error'
  119. act = dict(zip(('value', 'label', 'disabled'), act))
  120. else:
  121. act = dict(value=act, label=act)
  122. act_res.append(act)
  123. return act_res
  124. def actions(label, buttons, name='data', help_text=None):
  125. """
  126. 选择一个动作。UI为多个按钮,点击后会将整个表单提交
  127. :param label:
  128. :param actions: action 列表
  129. action 可用形式:
  130. {value:, label:, [disabled:]}
  131. (value, label, [disabled])
  132. value 单值,label等于value
  133. 实现方式:
  134. 多个type=submit的input组成
  135. [
  136. <button data-name value>label</button>,
  137. ...
  138. ]
  139. """
  140. item_spec, valid_func = _parse_args(locals())
  141. item_spec['type'] = 'actions'
  142. item_spec['buttons'] = _parse_action_buttons(buttons)
  143. return single_input(item_spec, valid_func, lambda d: d)
  144. def file_upload(label, accept=None, name='data', placeholder='Choose file', help_text=None, **other_html_attrs):
  145. """
  146. :param label:
  147. :param accept: 表明服务器端可接受的文件类型;该属性的值必须为一个逗号分割的列表,包含了多个唯一的内容类型声明:
  148. 以 STOP 字符 (U+002E) 开始的文件扩展名。(例如:".jpg,.png,.doc")
  149. 一个有效的 MIME 类型,但没有扩展名
  150. audio/* 表示音频文件
  151. video/* 表示视频文件
  152. image/* 表示图片文件
  153. :param placeholder:
  154. :param help_text:
  155. :param other_html_attrs:
  156. :await return: {'filename': 文件名, 'content':文件二进制数据 bytes object}
  157. """
  158. item_spec, valid_func = _parse_args(locals())
  159. item_spec['type'] = 'file'
  160. def read_file(data): # data: {'filename':, 'dataurl'}
  161. header, encoded = data['dataurl'].split(",", 1)
  162. data['content'] = b64decode(encoded)
  163. return data
  164. return single_input(item_spec, valid_func, read_file)
  165. def confirm():
  166. pass
  167. def input_group(label, inputs, valid_func=None):
  168. """
  169. :param label:
  170. :param inputs: list of single_input coro
  171. :param valid_func: callback(data) -> (name, error_msg)
  172. :return:
  173. """
  174. spec_inputs = []
  175. preprocess_funcs = {}
  176. item_valid_funcs = {}
  177. for single_input_cr in inputs:
  178. input_kwargs = dict(single_input_cr.cr_frame.f_locals) # 拷贝一份,不可以对locals进行修改
  179. single_input_cr.close()
  180. input_name = input_kwargs['item_spec']['name']
  181. preprocess_funcs[input_name] = input_kwargs['preprocess_func']
  182. item_valid_funcs[input_name] = input_kwargs['valid_func']
  183. spec_inputs.append(input_kwargs['item_spec'])
  184. # def add_autofocus(spec_inputs):
  185. if all('auto_focus' not in i for i in spec_inputs): # 每一个输入项都没有设置autofocus参数
  186. for i in spec_inputs:
  187. text_inputs = {TEXT, NUMBER, PASSWORD, SELECT} # todo update
  188. if i.get('type') in text_inputs:
  189. i['auto_focus'] = True
  190. break
  191. spec = dict(label=label, inputs=spec_inputs)
  192. return input_control(spec, preprocess_funcs=preprocess_funcs, item_valid_funcs=item_valid_funcs,
  193. form_valid_funcs=valid_func)