Browse Source

支持file输入

wangweimin 5 years ago
parent
commit
24ded46935
3 changed files with 109 additions and 9 deletions
  1. 7 0
      wsrepl/html/index.html
  2. 73 6
      wsrepl/html/js/form.js
  3. 29 3
      wsrepl/interact.py

+ 7 - 0
wsrepl/html/index.html

@@ -83,7 +83,14 @@
 <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.4.1/dist/js/bootstrap.min.js"
         integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6"
         crossorigin="anonymous"></script>
+<script src="https://cdn.jsdelivr.net/npm/bs-custom-file-input/dist/bs-custom-file-input.js"></script>
+
 <script>
+    $(document).ready(function () {
+        // https://www.npmjs.com/package/bs-custom-file-input
+        bsCustomFileInput.init()
+    });
+
     CodeMirror.modeURL = "https://cdn.bootcss.com/codemirror/2.36.0/%N.js";
 
 

+ 73 - 6
wsrepl/html/js/form.js

@@ -229,7 +229,7 @@
         this.create_element();
     }
 
-    FormController.prototype.input_controllers = [CommonInputController, CheckboxRadioController, ButtonsController, TextareaInputController];
+    FormController.prototype.input_controllers = [FileInputController, CommonInputController, CheckboxRadioController, ButtonsController, TextareaInputController];
 
     FormController.prototype.create_element = function () {
         var tpl = `
@@ -360,7 +360,7 @@
         this.create_element();
     }
 
-    CommonInputController.prototype.accept_input_types = ["text", "password", "number", "color", "date", "range", "time", "select"];
+    CommonInputController.prototype.accept_input_types = ["text", "password", "number", "color", "date", "range", "time", "select", "file"];
     /*
     *
     * type=
@@ -394,8 +394,8 @@
         var spec = deep_copy(this.spec);
         const id_name = spec.name + '-' + Math.floor(Math.random() * Math.floor(9999));
         spec['id_name'] = id_name;
-        if(spec.datalist)
-            spec['list'] = id_name+'-list';
+        if (spec.datalist)
+            spec['list'] = id_name + '-list';
 
         var html;
         if (spec.type === 'select')
@@ -417,7 +417,7 @@
             'valid_feedback': '',
             'help_text': '',
             'options': '',
-            'datalist':''
+            'datalist': ''
         };
         for (var key in this.spec) {
             if (key in ignore_keys) continue;
@@ -462,7 +462,7 @@
         // input_elem.on('blur', this.send_value_listener);
 
         // 将额外的html参数加到input标签上
-        const ignore_keys = make_set(['value','type', 'label', 'invalid_feedback', 'valid_feedback', 'help_text', 'rows', 'codemirror']);
+        const ignore_keys = make_set(['value', 'type', 'label', 'invalid_feedback', 'valid_feedback', 'help_text', 'rows', 'codemirror']);
         for (var key in this.spec) {
             if (key in ignore_keys) continue;
             input_elem.attr(key, this.spec[key]);
@@ -624,6 +624,73 @@
         return this.last_checked_value;
     };
 
+    function FileInputController(ws_client, coro_id, spec) {
+        FormItemController.apply(this, arguments);
+        this.data_url_value = null;
+        this.create_element();
+    }
+
+    FileInputController.prototype.accept_input_types = ["file"];
+
+    const file_input_tpl = `
+<div class="form-group">
+    <label for="customFile">{{label}}</label>
+    <div class="custom-file">
+        <input type="file" class="custom-file-input" id="{{name}}" aria-describedby="{{name}}_help">
+        <label class="custom-file-label" for="{{name}}">{{placeholder}}</label>
+    </div>
+    <div class="invalid-feedback">{{invalid_feedback}}</div>  <!-- input 添加 is-invalid 类 -->
+    <div class="valid-feedback">{{valid_feedback}}</div> <!-- input 添加 is-valid 类 -->
+    <small id="{{name}}_help"  class="form-text text-muted">{{help_text}}</small>
+</div>`;
+
+    FileInputController.prototype.create_element = function () {
+        var spec = deep_copy(this.spec);
+        const id_name = spec.name + '-' + Math.floor(Math.random() * Math.floor(9999));
+        spec['id_name'] = id_name;
+
+        const html = Mustache.render(file_input_tpl, spec);
+        this.element = $(html);
+        var input_elem = this.element.find('input[type="file"]');
+
+        const ignore_keys = {
+            'label': '',
+            'invalid_feedback': '',
+            'valid_feedback': '',
+            'help_text': '',
+            'placeholder': ''
+        };
+        for (var key in this.spec) {
+            if (key in ignore_keys) continue;
+            input_elem.attr(key, this.spec[key]);
+        }
+
+        // 文件选中后先不通知后端
+        var that = this;
+        input_elem.on('change', function () {
+            var file = input_elem[0].files[0];
+            var fr = new FileReader();
+            fr.onload = function () {
+                that.data_url_value = {
+                    'filename': file.name, 'dataurl': fr.result
+                };
+                console.log(that.data_url_value);
+            };
+            fr.readAsDataURL(file);
+        });
+        //  todo 通过回调的方式调用init
+        setTimeout(bsCustomFileInput.init, ShowDuration + 100);
+    };
+
+    FileInputController.prototype.update_input = function (spec) {
+        var attributes = spec.attributes;
+        this.update_input_helper(-1, attributes);
+    };
+
+    FileInputController.prototype.get_value = function () {
+        return this.data_url_value;
+    };
+
 
     function WSREPLController(ws_client, output_container_elem, input_container_elem) {
         this.output_ctrl = new OutputController(ws_client, output_container_elem);

+ 29 - 3
wsrepl/interact.py

@@ -1,7 +1,7 @@
 import json
 import logging
 from collections.abc import Mapping
-
+from base64 import b64decode
 from .framework import Global
 from .input_ctrl import send_msg, single_input, input_control
 
@@ -51,7 +51,7 @@ def input(label, type=TEXT, *, valid_func=None, name='data', value=None, placeho
 
 
 def textarea(label, rows=6, *, code=None, valid_func=None, name='data', value=None, placeholder=None, required=None,
-             maxlength=None, minlength=None, readonly=None, disabled=None,help_text=None, **other_html_attrs):
+             maxlength=None, minlength=None, readonly=None, disabled=None, help_text=None, **other_html_attrs):
     """提供codemirror参数产生代码输入样式"""
     item_spec, valid_func = _parse_args(locals())
     item_spec['type'] = TEXTAREA
@@ -79,7 +79,8 @@ def _parse_select_options(options):
 
 
 def select(label, options, type=SELECT, *, multiple=None, valid_func=None, name='data', value=None,
-           placeholder=None, required=None, readonly=None, disabled=None, inline=None, help_text=None, **other_html_attrs):
+           placeholder=None, required=None, readonly=None, disabled=None, inline=None, help_text=None,
+           **other_html_attrs):
     """
     参数值为None表示不指定,使用默认值
 
@@ -167,6 +168,31 @@ def actions(label, buttons, name='data', help_text=None):
     return single_input(item_spec, valid_func, lambda d: d)
 
 
+def file_upload(label, accept=None, name='data', placeholder='Choose file', help_text=None, **other_html_attrs):
+    """
+    :param label:
+    :param accept: 表明服务器端可接受的文件类型;该属性的值必须为一个逗号分割的列表,包含了多个唯一的内容类型声明:
+        以 STOP 字符 (U+002E) 开始的文件扩展名。(例如:".jpg,.png,.doc")
+        一个有效的 MIME 类型,但没有扩展名
+        audio/* 表示音频文件
+        video/* 表示视频文件
+        image/* 表示图片文件
+    :param placeholder:
+    :param help_text:
+    :param other_html_attrs:
+    :return:
+    """
+    item_spec, valid_func = _parse_args(locals())
+    item_spec['type'] = 'file'
+
+    def read_file(data):  # data: {'filename':, 'dataurl'}
+        header, encoded = data['dataurl'].split(",", 1)
+        data['content'] = b64decode(encoded)
+        return data
+
+    return single_input(item_spec, valid_func, read_file)
+
+
 def input_group(label, inputs, valid_func=None):
     """
     :param label: