Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide ability to set design system independently from template #4326

Merged
merged 25 commits into from
Feb 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ jobs:
# it as one of the sources.
PYCTDEV_SELF_CHANNEL: "pyviz/label/dev"
steps:
- uses: pyviz-dev/holoviz_tasks/install@v0.1a9
- uses: pyviz-dev/holoviz_tasks/install@v0.1a12
with:
name: unit_test_suite
python-version: ${{ matrix.python-version }}
Expand Down Expand Up @@ -126,13 +126,14 @@ jobs:
# it as one of the sources.
PYCTDEV_SELF_CHANNEL: "pyviz/label/dev"
steps:
- uses: pyviz-dev/holoviz_tasks/install@v0.1a9
- uses: pyviz-dev/holoviz_tasks/install@v0.1a12
with:
name: ui_test_suite
python-version: 3.9
channels: pyviz/label/dev,bokeh/label/dev,conda-forge,nodefaults
envs: "-o recommended -o tests -o build"
cache: true
nodejs: true
playwright: true
conda-mamba: mamba
id: install
Expand Down
85 changes: 49 additions & 36 deletions panel/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@
from .io.resources import RESOURCE_URLS
from .reactive import ReactiveHTML
from .template.base import BasicTemplate
from .template.theme import Theme
from .theme import Design, Theme

BASE_DIR = pathlib.Path(__file__).parent
BUNDLE_DIR = pathlib.Path(__file__).parent / 'dist' / 'bundled'

#---------------------------------------------------------------------
Expand All @@ -31,6 +32,12 @@
def write_bundled_files(name, files, explicit_dir=None, ext=None):
model_name = name.split('.')[-1].lower()
for bundle_file in files:
if not bundle_file.startswith('http'):
dest_path = BUNDLE_DIR / name.lower() / bundle_file
dest_path.parent.mkdir(parents=True, exist_ok=True)
shutil.copyfile(BASE_DIR / 'theme' / bundle_file, dest_path)
continue

bundle_file = bundle_file.split('?')[0]
try:
response = requests.get(bundle_file)
Expand Down Expand Up @@ -137,6 +144,17 @@ def write_bundled_zip(name, resource):
f.write(fdata.decode('utf-8'))
zip_obj.close()

def write_component_resources(name, component):
write_bundled_files(name, list(component._resources.get('css', {}).values()), BUNDLE_DIR, 'css')
write_bundled_files(name, list(component._resources.get('js', {}).values()), BUNDLE_DIR, 'js')
js_modules = []
for tar_name, js_module in component._resources.get('js_modules', {}).items():
if tar_name not in component._resources.get('tarball', {}):
js_modules.append(js_module)
write_bundled_files(name, js_modules, 'js', ext='mjs')
for tarball in component._resources.get('tarball', {}).values():
write_bundled_tarball(tarball)

def bundle_resource_urls(verbose=False, external=True):
# Collect shared resources
for name, resource in RESOURCE_URLS.items():
Expand All @@ -152,17 +170,10 @@ def bundle_templates(verbose=False, external=True):
for name, template in param.concrete_descendents(BasicTemplate).items():
if verbose:
print(f'Bundling {name} resources')

# Bundle Template._resources
if template._resources.get('bundle', True) and external:
write_bundled_files(name, list(template._resources.get('css', {}).values()), BUNDLE_DIR, 'css')
write_bundled_files(name, list(template._resources.get('js', {}).values()), BUNDLE_DIR, 'js')
js_modules = []
for tar_name, js_module in template._resources.get('js_modules', {}).items():
if tar_name not in template._resources.get('tarball', {}):
js_modules.append(js_module)
write_bundled_files(name, js_modules, 'js', ext='mjs')
for tarball in template._resources.get('tarball', {}).values():
write_bundled_tarball(tarball)
write_component_resources(name, template)

# Bundle CSS files in template dir
template_dir = pathlib.Path(inspect.getfile(template)).parent
Expand Down Expand Up @@ -198,33 +209,8 @@ def bundle_templates(verbose=False, external=True):
tmpl_dest_dir = BUNDLE_DIR / tmpl_name
shutil.copyfile(js, tmpl_dest_dir / os.path.basename(js))

# Bundle template stylesheets
for scls, modifiers in template._modifiers.items():
cls_modifiers = template._modifiers.get(scls, {})
if 'stylesheets' not in cls_modifiers:
continue
# Find the Template class the options were first defined on
def_cls = [
super_cls for super_cls in template.__mro__[::-1]
if getattr(super_cls, '_modifiers', {}).get(scls) is cls_modifiers
][0]
def_path = pathlib.Path(inspect.getmodule(def_cls).__file__).parent
for sts in cls_modifiers['stylesheets']:

if not isinstance(sts, str) or not sts.endswith('.css') or sts.startswith('http') or sts.startswith('/'):
continue
bundled_path = BUNDLE_DIR / def_cls.__name__.lower() / sts
shutil.copyfile(def_path / sts, bundled_path)


def bundle_themes(verbose=False, external=True):
# Bundle base themes
dest_dir = BUNDLE_DIR / 'theme'
theme_dir = pathlib.Path(inspect.getfile(Theme)).parent
dest_dir.mkdir(parents=True, exist_ok=True)
for css in glob.glob(str(theme_dir / '*.css')):
shutil.copyfile(css, dest_dir / os.path.basename(css))

# Bundle Theme classes
for name, theme in param.concrete_descendents(Theme).items():
if verbose:
Expand All @@ -234,10 +220,37 @@ def bundle_themes(verbose=False, external=True):
theme_bundle_dir.mkdir(parents=True, exist_ok=True)
shutil.copyfile(theme.base_css, theme_bundle_dir / os.path.basename(theme.base_css))
if theme.css:
tmplt_bundle_dir = BUNDLE_DIR / theme._template.__name__.lower()
tmplt_bundle_dir = BUNDLE_DIR / theme.param.css.owner.__name__.lower()
tmplt_bundle_dir.mkdir(parents=True, exist_ok=True)
shutil.copyfile(theme.css, tmplt_bundle_dir / os.path.basename(theme.css))

# Bundle design stylesheets
for name, design in param.concrete_descendents(Design).items():
if verbose:
print(f'Bundling {name} design resources')

# Bundle Design._resources
if design._resources.get('bundle', True) and external:
write_component_resources(name, design)

for scls, modifiers in design._modifiers.items():
cls_modifiers = design._modifiers.get(scls, {})
if 'stylesheets' not in cls_modifiers:
continue

# Find the Design class the options were first defined on
def_cls = [
super_cls for super_cls in design.__mro__[::-1]
if getattr(super_cls, '_modifiers', {}).get(scls) is cls_modifiers
][0]
def_path = pathlib.Path(inspect.getmodule(def_cls).__file__).parent
for sts in cls_modifiers['stylesheets']:
if not isinstance(sts, str) or not sts.endswith('.css') or sts.startswith('http') or sts.startswith('/'):
continue
bundled_path = BUNDLE_DIR / def_cls.__name__.lower() / sts
bundled_path.parent.mkdir(parents=True, exist_ok=True)
shutil.copyfile(def_path / sts, bundled_path)

def bundle_models(verbose=False, external=True):
for imp in panel_extension._imports.values():
if imp.startswith('panel.models'):
Expand Down
43 changes: 38 additions & 5 deletions panel/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"""
import ast
import copy
import importlib
import inspect
import os
import sys
Expand All @@ -21,6 +22,7 @@

from .io.logging import panel_log_handler
from .io.state import state
from .theme import Design

__version__ = str(param.version.Version(
fpath=__file__, archive_commit="$Format:%h$", reponame="panel"))
Expand Down Expand Up @@ -115,6 +117,9 @@ class _config(_base_config):
defer_load = param.Boolean(default=False, doc="""
Whether to defer load of rendered functions.""")

design = param.ClassSelector(class_=Design, is_instance=False, doc="""
The design system to use to style components.""")

exception_handler = param.Callable(default=None, doc="""
General exception handler for events.""")

Expand Down Expand Up @@ -160,9 +165,6 @@ class _config(_base_config):
template = param.ObjectSelector(default=None, doc="""
The default template to render served applications into.""")

theme = param.ObjectSelector(default='default', objects=['default', 'dark'], doc="""
The theme to apply to the selected global template.""")

throttled = param.Boolean(default=False, doc="""
If sliders and inputs should be throttled until release of mouse.""")

Expand Down Expand Up @@ -245,6 +247,9 @@ class _config(_base_config):
Whether to inline JS and CSS resources. If disabled, resources
are loaded from CDN if one is available.""")

_theme = param.ObjectSelector(default=None, objects=['default', 'dark'], allow_None=True, doc="""
The theme to apply to components.""")

# Global parameters that are shared across all sessions
_globals = [
'admin_plugins', 'autoreload', 'comms', 'cookie_secret',
Expand Down Expand Up @@ -359,7 +364,7 @@ def __getattribute__(self, attr):
curdoc and attr not in session_config[curdoc]):
new_obj = copy.copy(super().__getattribute__(attr))
setattr(self, attr, new_obj)
if attr in global_params:
if attr in global_params or attr == 'theme':
return super().__getattribute__(attr)
elif curdoc and curdoc in session_config and attr in session_config[curdoc]:
return session_config[curdoc][attr]
Expand Down Expand Up @@ -477,6 +482,20 @@ def oauth_extra_params(self):
else:
return self._oauth_extra_params

@property
def theme(self):
from .io.state import state
curdoc = state.curdoc
if curdoc and 'theme' in self._session_config.get(curdoc, {}):
return self._session_config[curdoc]['theme']
elif self._theme_:
return self._theme_
elif isinstance(state.session_args, dict) and state.session_args:
theme = state.session_args.get('theme', [b'default'])[0].decode('utf-8')
if theme in self.param._theme.objects:
return theme
return 'default'


if hasattr(_config.param, 'objects'):
_params = _config.param.objects()
Expand Down Expand Up @@ -575,7 +594,21 @@ def __call__(self, *args, **params):
'will be skipped.' % arg)

for k, v in params.items():
if k in ['raw_css', 'css_files']:
if k == 'design' and isinstance(v, str):
try:
importlib.import_module(f'panel.theme.{self._design}')
except Exception:
pass
designs = {
p.lower(): t for p, t in param.concrete_descendents(Design).items()
}
if v not in designs:
raise ValueError(
f'Design {v!r} was not recognized, available design '
f'systems include: {list(designs)}.'
)
setattr(config, k, designs[v])
elif k in ('css_files', 'raw_css'):
if not isinstance(v, list):
raise ValueError('%s should be supplied as a list, '
'not as a %s type.' %
Expand Down
4 changes: 2 additions & 2 deletions panel/dist/css/booleanstatus.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
:host(.dot) div {
height: 100%;
width: 100%;
border: 1px solid #000 !important;
border: 1px solid var(--jp-inverse-layout-color0, black) !important;
background-color: #fff;
border-radius: 50%;
display: inline-block;
Expand All @@ -10,7 +10,7 @@
:host(.dot-filled) div {
height: 100%;
width: 100%;
border: 1px solid #000 !important;
border: 1px solid var(--jp-inverse-layout-color0, black) !important;
border-radius: 50%;
display: inline-block;
}
Expand Down
26 changes: 16 additions & 10 deletions panel/dist/css/debugger.css
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
.bk.debugger-card {
.debugger-card {
border: 1px solid rgba(0,0,0,1);
color: rgba(255,255,255,1);
background-color: rgba(0,0,0,1);
border-radius: 0rem;
}
.bk.debugger-card-header {

.debugger-card-header {
align-items: center;
text-align: left;
background-color: rgba(0, 0, 0, 1)!important;
Expand All @@ -14,12 +15,14 @@
justify-content: start;
width: 100%;
}
.bk.debugger-card-button {

.debugger-card-button {
background-color: transparent;
color: rgba(255, 255, 255, 1);
margin-left: 0.5em;
}
.bk.debugger-card-title {

.debugger-card-title {
align-items: center;
text-align: left;
color: rgba(255, 255, 255, 1);
Expand All @@ -28,7 +31,7 @@
}

/* Special debugger buttons for clearing and saving */
.bk button.special_btn {
button.special_btn {
width: 25px;
height: 25px;
background-color: black;
Expand All @@ -37,7 +40,7 @@
}


.bk button.special_btn .tooltiptext {
button.special_btn .tooltiptext {
visibility: hidden;
width: 100px;
background-color: darkgray;
Expand All @@ -55,11 +58,14 @@
display: block;
}

.bk button.special_btn:hover .tooltiptext {
button.special_btn:hover .tooltiptext {
visibility: visible;
}

button.clear_btn:hover .shown {
display: none;
}


.bk button.clear_btn:hover .shown { display: none;}
.bk button.clear_btn:hover:before { content: "☑"; }
button.clear_btn:hover:before {
content: "☑";
}
Loading