Bläddra i källkod

feat: `put_scrollable()` support auto scroll bottom & change `max_height` to `height`

wangweimin 4 år sedan
förälder
incheckning
678a09d86d
2 ändrade filer med 41 tillägg och 9 borttagningar
  1. 1 4
      demos/chat_room.py
  2. 40 5
      pywebio/output.py

+ 1 - 4
demos/chat_room.py

@@ -31,7 +31,6 @@ async def refresh_msg(my_name, msg_box):
         for m in chat_msgs[last_idx:]:
             if m[0] != my_name:  # 仅刷新其他人的新信息
                 msg_box.append(put_markdown('`%s`: %s' % m))
-                run_js('$("#pywebio-scope-msg-container>div").stop().animate({ scrollTop: $("#pywebio-scope-msg-container>div").prop("scrollHeight")}, 1000)')  # hack: to scroll bottom
 
         # 清理聊天记录
         if len(chat_msgs) > MAX_MESSAGES_CNT:
@@ -51,8 +50,7 @@ async def main():
     "本应用使用不到80行代码实现,源代码[链接](https://github.com/wang0618/PyWebIO/blob/dev/demos/chat_room.py)", lstrip=True)
 
     msg_box = output()
-    with use_scope('msg-container'):
-        style(put_scrollable(msg_box, max_height=300), 'height:300px')
+    put_scrollable(msg_box, height=300, keep_bottom=True)
     nickname = await input("请输入你的昵称", required=True, validate=lambda n: '昵称已被使用' if n in online_users or n == '📢' else None)
 
     online_users.add(nickname)
@@ -76,7 +74,6 @@ async def main():
         if data['cmd'] == '多行输入':
             data['msg'] = '\n' + await textarea('消息内容', help_text='消息内容支持Markdown语法')
         msg_box.append(put_markdown('`%s`: %s' % (nickname, data['msg']), sanitize=True))
-        run_js('$("#pywebio-scope-msg-container>div").stop().animate({ scrollTop: $("#pywebio-scope-msg-container>div").prop("scrollHeight")}, 1000)')  # hack: to scroll bottom
         chat_msgs.append((nickname, data['msg']))
 
     refresh_task.close()

+ 40 - 5
pywebio/output.py

@@ -885,16 +885,22 @@ def put_collapse(title, content, open=False, scope=Scope.Current, position=Outpu
 
 
 @safely_destruct_output_when_exp('content')
-def put_scrollable(content, max_height=400, horizon_scroll=False, border=True, scope=Scope.Current,
-                   position=OutputPosition.BOTTOM) -> Output:
+def put_scrollable(content, height=400, keep_bottom=False, horizon_scroll=False, border=True,
+                   scope=Scope.Current, position=OutputPosition.BOTTOM, **kwargs) -> Output:
     """固定高度内容输出区域,内容超出则显示滚动条
 
     :type content: list/str/put_xxx()
     :param content: 内容可以为字符串或 ``put_xxx`` 类输出函数的返回值,或者由它们组成的列表。
-    :param int max_height: 区域的最大高度(像素),内容超出次高度则使用滚动条
+    :param int/tuple height: 区域的高度(像素),内容超出此高度则使用滚动条。
+       可以传入 ``(min_height, max_height)`` 来表示高度的范围,比如 ``(100, 200)`` 表示区域高度最小100像素、最高200像素。
+    :param bool keep_bottom: 是否在内容发生变化时自动滚动到底部,默认为 ``False``
     :param bool horizon_scroll: 是否显示水平滚动条
     :param bool border: 是否显示边框
     :param int scope, position: 与 `put_text` 函数的同名参数含义一致
+
+    .. versionchanged:: 1.1
+       添加 ``height`` 参数,移除 ``max_height`` 参数;
+       添加 ``auto_scroll_bottom`` 参数
     """
     if not isinstance(content, (list, tuple, OutputList)):
         content = [content]
@@ -902,7 +908,21 @@ def put_scrollable(content, max_height=400, horizon_scroll=False, border=True, s
     for item in content:
         assert isinstance(item, (str, Output)), "put_scrollable() content must be list of str/put_xxx()"
 
-    tpl = """<div style="max-height: {{max_height}}px;
+    if 'max_height' in kwargs:
+        import warnings
+        warnings.warn("`max_height` parameter is deprecated in `put_scrollable()`, use `height` instead.",
+                      DeprecationWarning, stacklevel=3)
+        height = kwargs['max_height']  # Backward compatible
+
+    try:
+        min_height, max_height = height
+    except Exception:
+        min_height, max_height = height, height
+
+    dom_id = 'pywebio-%s' % random_str(10)
+
+    tpl = """<div id="{{dom_id}}" {{#keep_bottom}}tabindex="0"{{/keep_bottom}}
+        style="min-height: {{min_height}}px; max-height: {{max_height}}px;
             overflow-y: scroll;
             {{#horizon_scroll}}overflow-x: scroll;{{/horizon_scroll}}
             {{#border}} 
@@ -916,8 +936,23 @@ def put_scrollable(content, max_height=400, horizon_scroll=False, border=True, s
             {{& pywebio_output_parse}}
         {{/contents}}
     </div>"""
+    if keep_bottom:
+        tpl += """
+        <script>
+            (function(){
+                let div = document.getElementById(%r), stop=false;
+                $(div).on('focusin', function(e){ stop=true }).on('focusout', function(e){ stop=false });;
+                new MutationObserver(function (mutations, observe) {
+                    if(!stop) $(div).stop().animate({ scrollTop: $(div).prop("scrollHeight")}, 200);
+                }).observe(div, { childList: true, subtree:true });
+            })();
+        </script>
+        """ % dom_id
+
     return put_widget(template=tpl,
-                      data=dict(contents=content, max_height=max_height, horizon_scroll=horizon_scroll, border=border),
+                      data=dict(dom_id=dom_id, contents=content, min_height=min_height,
+                                max_height=max_height, keep_bottom=keep_bottom,
+                                horizon_scroll=horizon_scroll, border=border),
                       scope=scope, position=position)