Browse Source

feat: add `theme` support in `config()`

wangweimin 3 years ago
parent
commit
389ca81cf4

+ 44 - 1
pywebio/html/css/app.css

@@ -306,4 +306,47 @@ details[open]>summary {
 .webio-scrollable.scrollable-border{
     border: 1px solid rgba(0,0,0,.125);
     box-shadow: inset 0 0 2px 0 rgba(0,0,0,.1);
-}
+}
+
+/* dark theme */
+.webio-theme-dark #input-container {
+    background: #0d1117 !important;
+}
+
+.webio-theme-dark #input-container.fixed {
+    box-shadow: 0px 0px 8px 5px rgb(0 0 0) !important;
+}
+
+.webio-theme-dark .footer {
+    background-color: #0d1117 !important;
+    border-top: 1px solid #30363d !important;
+}
+
+.webio-theme-dark .webio-tabs {
+    border: 1px solid #343a40 !important;
+}
+
+.webio-theme-dark .webio-tabs > .webio-tabs-content {
+    border-top: 1px solid #343a40 !important;
+}
+
+.webio-theme-dark .webio-tabs > label:hover {
+    background-color: #000 !important;
+}
+.webio-theme-dark .webio-tabs > input[type=radio]:checked + label {
+    border-bottom: 2px solid #0040a1 !important;
+}
+
+.webio-theme-dark .scrollable-border{
+    border: 1px solid #343a40 !important;
+}
+.webio-theme-dark details{
+    border: 1px solid #343a40 !important;
+}
+.webio-theme-dark details>summary{
+    background-color: #191d21;
+}
+.webio-theme-dark details[open]>summary{
+    border-bottom: 1px solid #343a40 !important;
+}
+/* dark theme end */

File diff suppressed because it is too large
+ 0 - 0
pywebio/html/css/bootstrap.min.css


File diff suppressed because it is too large
+ 5 - 0
pywebio/html/css/bs-theme/dark.min.css


File diff suppressed because it is too large
+ 11 - 0
pywebio/html/css/bs-theme/lux.min.css


File diff suppressed because it is too large
+ 11 - 0
pywebio/html/css/bs-theme/sketchy.min.css


File diff suppressed because it is too large
+ 0 - 0
pywebio/html/css/codemirror.min.css


+ 3 - 3
pywebio/platform/tpl/index.html

@@ -10,8 +10,8 @@
     <link rel="stylesheet" href="{{ base_url }}css/markdown.min.css">
     <link rel="stylesheet" href="{{ base_url }}css/codemirror.min.css">
     <link rel="stylesheet" href="{{ base_url }}css/toastify.min.css">
-    {% if bootstrap_css %}
-    <link rel="stylesheet" href="{{ bootstrap_css }}">
+    {% if theme %}
+    <link rel="stylesheet" href="{{ base_url }}css/bs-theme/{{ theme }}.min.css">
     {% else %}
     <link rel="stylesheet" href="{{ base_url }}css/bootstrap.min.css">
     {% end %}
@@ -24,7 +24,7 @@
     {% end %}
 </head>
 <body>
-<div class="pywebio">
+<div class="pywebio webio-theme-{{ theme }}">
     <div class="container no-fix-height" id="output-container">
         <div class="markdown-body" id="markdown-body">
             <div class="text-center" id="pywebio-loading" style="display: none; position: fixed; top: 40%; left: 0;right: 0;">

+ 30 - 25
pywebio/platform/utils.py

@@ -8,11 +8,12 @@ from functools import partial
 from os import path, environ
 
 from tornado import template
+from functools import lru_cache
 
 from ..__version__ import __version__ as version
 from ..exceptions import PyWebIOWarning
 from ..utils import isgeneratorfunction, iscoroutinefunction, get_function_name, get_function_doc, \
-    get_function_attr
+    get_function_attr, STATIC_PATH
 
 """
 The maximum size in bytes of a http request body or a websocket message, after which the request or websocket is aborted
@@ -23,10 +24,8 @@ MAX_PAYLOAD_SIZE = 0
 
 DEFAULT_CDN = "https://cdn.jsdelivr.net/gh/wang0618/PyWebIO-assets@v{version}/"
 
-BOOTSTRAP_VERSION = '4.4.1'
-
 _global_config = {'title': 'PyWebIO Application'}
-config_keys = ['title', 'description', 'js_file', 'js_code', 'css_style', 'css_file']
+config_keys = ['title', 'description', 'js_file', 'js_code', 'css_style', 'css_file', 'theme']
 AppMeta = namedtuple('App', config_keys)
 
 _here_dir = path.dirname(path.abspath(__file__))
@@ -44,35 +43,30 @@ def render_page(app, protocol, cdn):
     assert protocol in ('ws', 'http')
     meta = parse_app_metadata(app)
     if cdn is True:
-        cdn = DEFAULT_CDN.format(version=version)
+        base_url = DEFAULT_CDN.format(version=version)
     elif not cdn:
-        cdn = ''
+        base_url = ''
     else:  # user custom cdn
-        cdn = cdn.rstrip('/') + '/'
+        base_url = cdn.rstrip('/') + '/'
 
-    bootstrap_css = bootstrap_css_url()
+    theme = environ.get('PYWEBIO_THEME', meta.theme)
+    check_theme(theme)
 
     return _index_page_tpl.generate(title=meta.title, description=meta.description, protocol=protocol,
-                                    script=True, content='', base_url=cdn, bootstrap_css=bootstrap_css,
+                                    script=True, content='', base_url=base_url,
                                     js_file=meta.js_file or [], js_code=meta.js_code, css_style=meta.css_style,
-                                    css_file=meta.css_file or [])
-
+                                    css_file=meta.css_file or [], theme=theme)
 
-def bootstrap_css_url():
-    """Get bootstrap theme css url from environment variable PYWEBIO_THEME
-
-    PYWEBIO_THEME can be one of bootswatch themes, or a custom css url. 
-    """
-    theme_name = environ.get('PYWEBIO_THEME')
-    bootswatch_themes = {'flatly', 'yeti', 'cerulean', 'pulse', 'journal', 'cosmo', 'sandstone', 'simplex', 'minty',
-                         'slate', 'superhero', 'lumen', 'spacelab', 'materia', 'litera', 'sketchy', 'cyborg', 'solar',
-                         'lux', 'united', 'darkly'}
 
-    if theme_name in bootswatch_themes:
-        return 'https://cdn.jsdelivr.net/npm/bootswatch@{version}/dist/{theme}/bootstrap.min.css'.format(
-            version=BOOTSTRAP_VERSION, theme=theme_name)
+@lru_cache(maxsize=64)
+def check_theme(theme):
+    """check theme file existence"""
+    if not theme:
+        return
 
-    return theme_name  # it's a url
+    theme_file = path.join(STATIC_PATH, 'css', 'bs-theme', theme + '.min.css')
+    if not path.isfile(theme_file):
+        raise RuntimeError("Can't find css file for theme `%s`" % theme)
 
 
 def cdn_validation(cdn, level='warn', stacklevel=3):
@@ -325,11 +319,19 @@ def seo(title, description=None, app=None):
     return config(title=title, description=description)
 
 
-def config(*, title=None, description=None, js_code=None, js_file=[], css_style=None, css_file=[]):
+def config(*, title=None, description=None, theme=None, js_code=None, js_file=[], css_style=None, css_file=[]):
     """PyWebIO application configuration
 
     :param str title: Application title
     :param str description: Application description
+    :param str theme: Application theme. Available themes are: ``dark``, ``sketchy``, ``lux``.
+        You can also use environment variable ``PYWEBIO_THEME`` to specify the theme (with high priority).
+
+        .. collapse:: Open Source Credits
+
+            The dark theme is modified from ForEvolve's `bootstrap-dark <https://github.com/ForEvolve/bootstrap-dark>`_.
+            The rest of the themes are from `bootswatch <https://bootswatch.com/4/>`_.
+
     :param str js_code: The javascript code that you want to inject to page.
     :param str/list js_file: The javascript files that inject to page, can be a URL in str or a list of it.
     :param str css_style: The CSS style that you want to inject to page.
@@ -365,6 +367,9 @@ def config(*, title=None, description=None, js_code=None, js_file=[], css_style=
             pass
 
     .. versionadded:: 1.4
+
+    .. versionchanged:: 1.5
+       add ``theme`` parameter
     """
     if isinstance(js_file, str):
         js_file = [js_file]

+ 3 - 0
setup.py

@@ -51,6 +51,9 @@ setup(
             "html/css/toastify.min.css",
             "html/css/app.css",
             "html/css/codemirror.min.css",
+            "html/css/bs-theme/dark.min.css",
+            "html/css/bs-theme/lux.min.css",
+            "html/css/bs-theme/sketchy.min.css",
             "html/js/FileSaver.min.js",
             "html/js/prism.min.js",
             "html/js/purify.min.js",

+ 1 - 1
webiojs/src/models/input/checkbox_radio.ts

@@ -4,8 +4,8 @@ import {deep_copy} from "../../utils"
 const options_tpl = `
 {{#options}}
 <div class="form-check {{#inline}}form-check-inline{{/inline}}">
-    <input type="{{type}}" id="{{id_name_prefix}}-{{idx}}" name="{{name}}" {{#selected}}checked{{/selected}} {{#disabled}}disabled{{/disabled}} class="form-check-input">
     <label class="form-check-label" for="{{id_name_prefix}}-{{idx}}">
+        <input type="{{type}}" id="{{id_name_prefix}}-{{idx}}" name="{{name}}" {{#selected}}checked{{/selected}} {{#disabled}}disabled{{/disabled}} class="form-check-input">
         {{label}}
     </label>
 </div>

Some files were not shown because too many files changed in this diff