Explorar o código

feat: add `toast()` to make notification

wangweimin %!s(int64=4) %!d(string=hai) anos
pai
achega
4ecc7c394f

+ 13 - 0
docs/spec.rst

@@ -230,6 +230,19 @@ popup
 * closable: 是否可由用户关闭弹窗. 默认情况下,用户可以通过点击弹窗右上角的关闭按钮来关闭弹窗,
 * closable: 是否可由用户关闭弹窗. 默认情况下,用户可以通过点击弹窗右上角的关闭按钮来关闭弹窗,
   设置为 ``false`` 时弹窗仅能通过 ``popup_close`` command 关闭, ``implicit_close`` 参数被忽略.
   设置为 ``false`` 时弹窗仅能通过 ``popup_close`` command 关闭, ``implicit_close`` 参数被忽略.
 
 
+toast
+^^^^^^^^^^^^^^^
+显示通知消息
+
+命令 spec 字段:
+
+* content: 通知内容
+* duration: 通知显示持续的时间,单位为毫秒
+* position: 通知消息显示的位置,可以为 `'left'` / `'center'` / `'right'`
+* color: 通知消息的背景颜色,格式为合法的css颜色值
+* callback_id: 点击通知消息时的回调函数callback_id, 没有回调时为 null
+
+
 close_popup
 close_popup
 ^^^^^^^^^^^^^^^
 ^^^^^^^^^^^^^^^
 关闭正在显示的弹窗
 关闭正在显示的弹窗

+ 15 - 0
pywebio/html/css/toastify.min.css

@@ -0,0 +1,15 @@
+/**
+ * Minified by jsDelivr using clean-css v4.2.3.
+ * Original file: /npm/toastify-js@1.9.3/src/toastify.css
+ *
+ * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
+ */
+/*!
+ * Toastify js 1.9.3
+ * https://github.com/apvarun/toastify-js
+ * @license MIT licensed
+ *
+ * Copyright (C) 2018 Varun A P
+ */
+.toastify{padding:12px 20px;color:#fff;display:inline-block;box-shadow:0 3px 6px -1px rgba(0,0,0,.12),0 10px 36px -4px rgba(77,96,232,.3);background:-webkit-linear-gradient(315deg,#73a5ff,#5477f5);background:linear-gradient(135deg,#73a5ff,#5477f5);position:fixed;opacity:0;transition:all .4s cubic-bezier(.215,.61,.355,1);border-radius:2px;cursor:pointer;text-decoration:none;max-width:calc(50% - 20px);z-index:2147483647}.toastify.on{opacity:1}.toast-close{opacity:.4;padding:0 5px}.toastify-right{right:15px}.toastify-left{left:15px}.toastify-top{top:-150px}.toastify-bottom{bottom:-150px}.toastify-rounded{border-radius:25px}.toastify-avatar{width:1.5em;height:1.5em;margin:-7px 5px;border-radius:2px}.toastify-center{margin-left:auto;margin-right:auto;left:0;right:0;max-width:fit-content;max-width:-moz-fit-content}@media only screen and (max-width:360px){.toastify-left,.toastify-right{margin-left:auto;margin-right:auto;left:0;right:0;max-width:fit-content}}
+/*# sourceMappingURL=/sm/40f738e33ed5dbe7907b48c3be4b63e977eab6cb49c8df4f76f3edc3f1f2fb0d.map */

+ 2 - 0
pywebio/html/index.html

@@ -9,6 +9,7 @@
     <link rel="stylesheet" href="css/bootstrap.min.css">
     <link rel="stylesheet" href="css/bootstrap.min.css">
     <link rel="stylesheet" href="css/codemirror.min.css">
     <link rel="stylesheet" href="css/codemirror.min.css">
     <link rel="stylesheet" href="codemirror/base16-light.min.css">
     <link rel="stylesheet" href="codemirror/base16-light.min.css">
+    <link rel="stylesheet" href="css/toastify.min.css">
     <link rel="stylesheet" href="css/app.css">
     <link rel="stylesheet" href="css/app.css">
 </head>
 </head>
 <body>
 <body>
@@ -48,6 +49,7 @@
 <script src="js/jquery.min.js"></script>
 <script src="js/jquery.min.js"></script>
 <script src="js/popper.min.js"></script>  <!-- tooltip engine -->
 <script src="js/popper.min.js"></script>  <!-- tooltip engine -->
 <script src="js/bootstrap.min.js"></script>
 <script src="js/bootstrap.min.js"></script>
+<script src="js/toastify.min.js"></script> <!-- toast -->
 <script src="js/bs-custom-file-input.min.js"></script> <!-- bootstrap custom file input-->
 <script src="js/bs-custom-file-input.min.js"></script> <!-- bootstrap custom file input-->
 
 
 <script src="js/pywebio.min.js"></script>
 <script src="js/pywebio.min.js"></script>

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 13 - 0
pywebio/html/js/toastify.min.js


+ 33 - 1
pywebio/output.py

@@ -39,6 +39,7 @@ r"""输出内容到用户浏览器
 
 
 其他交互
 其他交互
 --------------
 --------------
+.. autofunction:: toast
 .. autofunction:: popup
 .. autofunction:: popup
 
 
 
 
@@ -79,7 +80,7 @@ __all__ = ['Position', 'set_title', 'set_output_fixed_height', 'set_auto_scroll_
            'put_table', 'table_cell_buttons', 'put_buttons', 'put_image', 'put_file', 'PopupSize', 'popup',
            'put_table', 'table_cell_buttons', 'put_buttons', 'put_image', 'put_file', 'PopupSize', 'popup',
            'close_popup', 'put_widget', 'put_collapse', 'put_link', 'put_scrollable', 'style', 'put_column',
            'close_popup', 'put_widget', 'put_collapse', 'put_link', 'put_scrollable', 'style', 'put_column',
            'put_row', 'put_grid', 'column', 'row', 'grid', 'span', 'put_processbar', 'set_processbar', 'put_loading',
            'put_row', 'put_grid', 'column', 'row', 'grid', 'span', 'put_processbar', 'set_processbar', 'put_loading',
-           'output']
+           'output', 'toast']
 
 
 
 
 # popup尺寸
 # popup尺寸
@@ -1091,6 +1092,37 @@ def close_popup():
     send_msg(cmd='close_popup')
     send_msg(cmd='close_popup')
 
 
 
 
+def toast(content, duration=2, position='center', color='info', onclick=None):
+    """显示一条通知消息
+
+    :param str content: 通知内容
+    :param float duration: 通知显示持续的时间,单位为秒。 `0` 表示不自动关闭(此时消息旁会显示一个关闭图标,用户可以手动关闭消息)
+    :param str position: 通知消息显示的位置,可以为 `'left'` / `'center'` / `'right'`
+    :param str color: 通知消息的背景颜色,可以为 `'info'` / `'error'` / `'warn'` / `'success'` 或以 `'#'` 开始的十六进制颜色值
+    :param callable onclick: 点击通知消息时的回调函数,回调函数不接受任何参数。
+
+    Example::
+
+        def show_msg():
+            put_text("Some messages...")
+
+        toast('New messages', position='right', color='#2188ff', duration=0, onclick=show_msg)
+
+    """
+
+    colors = {
+        'info': '#1565c0',
+        'error': '#e53935',
+        'warn': '#ef6c00',
+        'success': '#2e7d32'
+    }
+    color = colors.get(color, color)
+    callback_id = output_register_callback(lambda _: onclick()) if onclick is not None else None
+
+    send_msg(cmd='toast', spec=dict(content=content, duration=int(duration*1000), position=position,
+                                    color=color, callback_id=callback_id))
+
+
 clear_scope = clear
 clear_scope = clear
 
 
 
 

+ 9 - 0
test/13.misc.py

@@ -80,6 +80,15 @@ def target():
     except Exception:
     except Exception:
         pass
         pass
 
 
+    toast('Awesome PyWebIO!!', duration=0)
+
+    def show_msg():
+        put_text("Toast clicked")
+
+    toast('You have new messages', duration=0, onclick=show_msg)
+
+    run_js("$('.toastify').eq(0).click()")
+
     yield hold()
     yield hold()
 
 
 
 

+ 37 - 0
webiojs/src/handlers/toast.ts

@@ -0,0 +1,37 @@
+import {Command} from "../session";
+import {CommandHandler} from "./base";
+import {state} from "../state";
+
+export class ToastHandler implements CommandHandler {
+    accept_command: string[] = ['toast'];
+
+    constructor() {
+    }
+
+    handle_message(msg: Command) {
+        let spec = msg.spec;
+        let toast = Toastify({
+            text: spec.content,
+            duration: spec.duration === 0 ? -1 : spec.duration,  // -1 for permanent toast
+            close: spec.duration === 0,//To show the close icon or not
+            gravity: "top", // `top` or `bottom`
+            position: spec.position, // `left`, `center` or `right`
+            backgroundColor: spec.color,
+            stopOnFocus: true, // Prevents dismissing of toast on hover
+            onClick: function () {
+                if (!spec.callback_id)
+                    return;
+
+                if (state.CurrentSession === null)
+                    return console.error("Error: WebIOController is not instantiated");
+                state.CurrentSession.send_message({
+                    event: "callback",
+                    task_id: spec.callback_id,
+                    data: null
+                });
+                toast.hideToast();
+            }
+        });
+        toast.showToast();
+    }
+}

+ 3 - 1
webiojs/src/main.ts

@@ -8,6 +8,7 @@ import {PopupHandler} from "./handlers/popup";
 import {openApp} from "./utils";
 import {openApp} from "./utils";
 import {ScriptHandler} from "./handlers/script";
 import {ScriptHandler} from "./handlers/script";
 import {DownloadHandler} from "./handlers/download";
 import {DownloadHandler} from "./handlers/download";
+import {ToastHandler} from "./handlers/toast";
 
 
 // 获取后端API地址
 // 获取后端API地址
 function get_backend_addr() {
 function get_backend_addr() {
@@ -30,8 +31,9 @@ function set_up_session(webio_session: Session, output_container_elem: JQuery, i
     let close_ctrl = new CloseHandler(webio_session);
     let close_ctrl = new CloseHandler(webio_session);
     let script_ctrl = new ScriptHandler(webio_session);
     let script_ctrl = new ScriptHandler(webio_session);
     let download_ctrl = new DownloadHandler();
     let download_ctrl = new DownloadHandler();
+    let toast_ctrl = new ToastHandler();
 
 
-    let dispatcher = new CommandDispatcher(output_ctrl, input_ctrl, popup_ctrl, close_ctrl, script_ctrl, download_ctrl);
+    let dispatcher = new CommandDispatcher(output_ctrl, input_ctrl, popup_ctrl, close_ctrl, script_ctrl, download_ctrl, toast_ctrl);
 
 
     webio_session.on_server_message((msg: Command) => {
     webio_session.on_server_message((msg: Command) => {
         try {
         try {

+ 2 - 1
webiojs/src/vendor.d.ts

@@ -3,4 +3,5 @@ declare let Mditor: any;
 declare let Mustache: any;
 declare let Mustache: any;
 declare let saveAs: any;
 declare let saveAs: any;
 declare let CodeMirror: any;
 declare let CodeMirror: any;
-declare let bsCustomFileInput: any;
+declare let bsCustomFileInput: any;
+declare let Toastify:any;

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio