import json
from pywebio.output import *
from pywebio.input import *
from pywebio.session import *
from pywebio.pin import *
from pywebio.utils import random_str
from pywebio.output import Output
import io
from functools import partial
import subprocess
from pywebio.output import OutputPosition
__all__ = ['confirm', 'popup_input', 'redirect_stdout', 'run_shell', 'put_logbox', 'logbox_append']
[docs]def confirm(title, content=None, *, timeout=None):
"""Show a confirmation modal.
:param str title: Model title.
:param list/put_xxx() content: Model content.
:param None/float timeout: Seconds for operation time out.
:return: Return `True` when the "CONFIRM" button is clicked,
return `False` when the "CANCEL" button is clicked,
return `None` when a timeout is given and the operation times out.
"""
if content is None:
content = []
if not isinstance(content, list):
content = [content]
action_name = random_str(10)
content.append(put_actions(action_name, buttons=[
{'label': 'CONFIRM', 'value': True},
{'label': 'CANCEL', 'value': False, 'color': 'danger'},
]))
popup(title=title, content=content, closable=False)
result = pin_wait_change(action_name, timeout=timeout)
if result:
result = result['value']
close_popup()
return result
[docs]def redirect_stdout(output_func=partial(put_text, inline=True)):
"""Context manager for temporarily redirecting stdout to pywebio.
::
with redirect_stdout():
print("Hello world.")
"""
from contextlib import redirect_stdout
class WebIO(io.IOBase):
def write(self, content):
output_func(content)
return redirect_stdout(WebIO())
[docs]def run_shell(cmd, output_func=partial(put_text, inline=True)):
"""Run command in shell and output the result to pywebio"""
process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
while True:
out = process.stdout.readline()
if out:
output_func(out.decode('utf8'))
if not out and process.poll() is not None:
break
[docs]def put_logbox(name, height=None, scope=None, position=OutputPosition.BOTTOM) -> Output:
"""Output a logbox widget
:param str name:
:param int height: the height of the widget in pixel
"""
html = '<pre><code id="webio-logbox-%s"></code></pre>' % name
return put_html(html, scope=scope, position=position).style('height:%spx' % height if height else '')
[docs]def logbox_append(name, text):
"""Append text to a logbox widget"""
run_js('$("#webio-logbox-%s").append(text)' % name, text=str(text))
def put_datatable(tdata, header=None, scope=None, position=OutputPosition.BOTTOM, **extra_props) -> Output:
"""Output datatable
The ``tdata`` and ``header`` arguments have the same meaning as for `pywebio.output.put_table()` except that
the table cell can't be ``put_xxx()`` call
:param extra_props: extra properties passed to the underlying
`react data table component <https://github.com/jbetancur/react-data-table-component>`_ .
See: https://react-data-table-component.netlify.app/?path=/docs/api-props--page
"""
if not tdata:
return put_text('Empty datatable')
# Change ``dict`` row table to list row table
if isinstance(tdata[0], dict):
if isinstance(header[0], (list, tuple)):
header_ = [h[0] for h in header]
order = [h[-1] for h in header]
else:
header_ = order = header
tdata = [
[row.get(k, '') for k in order]
for row in tdata
]
header = header_
else:
tdata = [list(i) for i in tdata] # copy data
if not header:
header, tdata = tdata[0], tdata[1:]
dom_id = random_str(10)
html = """
<div id="react-app-%s">⌛Loading datatable️</div>
<script type="module">
let header=%s, data=%s, extra_props=%s;
import React from "https://esm.sh/react";
import ReactDOM from "https://esm.sh/react-dom";
import DataTable from "https://esm.sh/react-data-table-component"
data = data.map((value) => {
let row = {};
for (let idx in header)
row[header[idx]] = value[idx];
return row;
})
let columns = header.map((value) => ({
"name": value,
"selector": (row) => row[value],
"sortable": true,
"reorder": true
}))
ReactDOM.render(React.createElement(DataTable, {
"columns": columns,
"data": data,
"pagination": true,
...extra_props
}), document.getElementById("react-app-%s"));
</script>
""" % (dom_id, json.dumps(header), json.dumps(tdata), json.dumps(extra_props), dom_id)
return put_html(html, scope=scope, position=position)