Browse Source

增加输出区回调功能

wangweimin 5 năm trước cách đây
mục cha
commit
1a8151c77c

+ 11 - 1
doc/spec.md

@@ -80,7 +80,12 @@ destroy_form:
 
 
 output:
 output:
     type: text
     type: text
-    content: {}
+    content: ''
+    ----
+    type: buttons
+    callback_id:  
+    buttons:[ {value:, label:, },...]
+    
 
 
 output_ctl:
 output_ctl:
     title
     title
@@ -102,6 +107,11 @@ input_event
     name:
     name:
     value:
     value:
 
 
+event: callback
+coro_id: callback_id
+data: value
+    
+
 checkbox_radio 不产生blur事件
 checkbox_radio 不产生blur事件
 
 
 from_submit:
 from_submit:

+ 30 - 4
wsrepl/html/js/form.js

@@ -91,19 +91,45 @@
 
 
     function OutputController(ws_client, container_elem) {
     function OutputController(ws_client, container_elem) {
         this.ws_client = ws_client;
         this.ws_client = ws_client;
-        this.container_elem = container_elem;
+        this.container_elem = $(container_elem);
         this.md_parser = new Mditor.Parser();
         this.md_parser = new Mditor.Parser();
 
 
         this.handle_message = function (msg) {
         this.handle_message = function (msg) {
-            if (msg.command === 'output')
-                this.container_elem[0].innerHTML += this.md_parser.parse(msg.spec.content);
-            else if (msg.command === 'output_ctl')
+            if (msg.command === 'output') {
+                if (msg.spec.type === 'text')
+                    this.container_elem.append(this.md_parser.parse(msg.spec.content));  // 直接更改innerHtml会导致事件绑定失效
+                else if (msg.spec.type === 'buttons')
+                    this.handle_buttons(msg);
+                else
+                    console.warn('Unknown output type:%s', msg.spec.type);
+            } else if (msg.command === 'output_ctl')
                 $('#title').text(msg.spec.title);  // todo 不规范
                 $('#title').text(msg.spec.title);  // todo 不规范
         }
         }
     }
     }
 
 
     OutputController.prototype.accept_command = ['output', 'output_ctl'];
     OutputController.prototype.accept_command = ['output', 'output_ctl'];
 
 
+    OutputController.prototype.handle_buttons = function (msg) {
+        const btns_tpl = `<div class="form-group">{{#buttons}}
+                             <button value="{{value}}" class="btn btn-primary">{{label}}</button> 
+                          {{/buttons}}</div>`;
+        var html = Mustache.render(btns_tpl, msg.spec);
+        var element = $(html);
+        this.container_elem.append(element);
+        // this.container_elem[0].innerHTML += element;
+        var that = this;
+        element.on('click', 'button', function (e) {
+            var val = $(this).val();
+            that.ws_client.send(JSON.stringify({
+                event: "callback",
+                coro_id: msg.spec.callback_id,
+                data: val
+            }));
+            return;
+        })
+    };
+
+
     const ShowDuration = 200; // ms
     const ShowDuration = 200; // ms
 
 
     FormsController.prototype.accept_command = ['input', 'input_group', 'update_input', 'destroy_form'];
     FormsController.prototype.accept_command = ['input', 'input_group', 'update_input', 'destroy_form'];

+ 3 - 0
wsrepl/interact.py

@@ -188,10 +188,13 @@ def file_upload(label, accept=None, name='data', placeholder='Choose file', help
     def read_file(data):  # data: {'filename':, 'dataurl'}
     def read_file(data):  # data: {'filename':, 'dataurl'}
         header, encoded = data['dataurl'].split(",", 1)
         header, encoded = data['dataurl'].split(",", 1)
         data['content'] = b64decode(encoded)
         data['content'] = b64decode(encoded)
+        del data['dataurl']
         return data
         return data
 
 
     return single_input(item_spec, valid_func, read_file)
     return single_input(item_spec, valid_func, read_file)
 
 
+def confirm():
+    pass
 
 
 def input_group(label, inputs, valid_func=None):
 def input_group(label, inputs, valid_func=None):
     """
     """

+ 46 - 4
wsrepl/output.py

@@ -2,8 +2,10 @@ import json
 import logging
 import logging
 from collections.abc import Mapping
 from collections.abc import Mapping
 
 
-from .framework import Global
-from .input_ctrl import send_msg, single_input, input_control
+from .framework import Global, Task
+from .input_ctrl import send_msg, single_input, input_control, next_event
+import asyncio
+import inspect
 
 
 
 
 def set_title(title):
 def set_title(title):
@@ -11,6 +13,8 @@ def set_title(title):
 
 
 
 
 def text_print(text, *, ws=None):
 def text_print(text, *, ws=None):
+    if text is None:
+        text = ''
     msg = dict(command="output", spec=dict(content=text, type='text'))
     msg = dict(command="output", spec=dict(content=text, type='text'))
     (ws or Global.active_ws).write_message(json.dumps(msg))
     (ws or Global.active_ws).write_message(json.dumps(msg))
 
 
@@ -46,5 +50,43 @@ def put_table(tdata):
     text_print('\n'.join(res))
     text_print('\n'.join(res))
 
 
 
 
-def buttons(buttons, onclick=None):
-    pass
+def buttons(buttons, onclick_coro, save=None):
+    """
+    :param buttons: button列表, button可用形式:
+        {value:, label:, }
+        (value, label,)
+        value 单值,label等于value
+    :param onclick_coro: CallBack(data, save) todo 允许onclick_coro非coro
+    :param save:
+    :return:
+    """
+
+    btns = []
+    for btn in buttons:
+        if isinstance(btn, Mapping):
+            assert 'value' in btn and 'label' in btn, 'actions item must have value and label key'
+        elif isinstance(btn, list):
+            assert len(btn) == 2, 'actions item format error'
+            btn = dict(zip(('value', 'label'), btn))
+        else:
+            btn = dict(value=btn, label=btn)
+        btns.append(btn)
+
+    async def callback_coro():
+        while True:
+            event = await next_event()
+            assert event['event'] == 'callback'
+            if asyncio.iscoroutinefunction(onclick_coro):
+                await onclick_coro(event['data'], save)
+            elif inspect.isgeneratorfunction(onclick_coro):
+                await asyncio.coroutine(onclick_coro)(save, event['data'])
+            else:
+                onclick_coro(event['data'], save)
+
+    print('Global.active_ws', Global.active_ws)
+    callback = Task(callback_coro(), Global.active_ws)
+    callback.coro.send(None)  # 激活,Non't callback.step() ,导致嵌套调用step  todo 与inactive_coro_instances整合
+    # callback_id = callback.coro_id
+    Global.active_ws.coros[callback.coro_id] = callback
+
+    send_msg('output', dict(type='buttons', callback_id=callback.coro_id, buttons=btns))

+ 3 - 3
wsrepl/platform/tornado.py

@@ -29,8 +29,8 @@ def ws_handler(coro_func):
             self.set_nodelay(True)
             self.set_nodelay(True)
             ############
             ############
             self.coros = {}  # coro_id -> coro
             self.coros = {}  # coro_id -> coro
-            self.callbacks = OrderedDict()  # UI元素时的回调, key -> callback, mark_id
-            self.mark2id = {}  # mark_name -> mark_id
+            # self.callbacks = OrderedDict()  # UI元素时的回调, callback_id -> (coro, save)
+            # self.mark2id = {}  # mark_name -> mark_id
 
 
             self._closed = False
             self._closed = False
             self.inactive_coro_instances = []  # 待激活的协程实例列表
             self.inactive_coro_instances = []  # 待激活的协程实例列表
@@ -65,7 +65,7 @@ def ws_handler(coro_func):
             data = json.loads(message)
             data = json.loads(message)
             coro_id = data['coro_id']
             coro_id = data['coro_id']
             coro = self.coros.get(coro_id)
             coro = self.coros.get(coro_id)
-            if not coro_id:
+            if not coro:
                 gen_log.error('coro not found, coro_id:%s', coro_id)
                 gen_log.error('coro not found, coro_id:%s', coro_id)
                 return
                 return