add pyweb.ui

This commit is contained in:
Fabio Pliger
2024-01-30 17:31:49 -06:00
parent bdce19714a
commit 25e1b0647e
5 changed files with 603 additions and 0 deletions

View File

@@ -0,0 +1 @@
from . import elements, shoelace

View File

@@ -0,0 +1,303 @@
import string
from textwrap import dedent
from pyscript import document, when, window
from pyweb import pydom
class ElementBase(pydom.Element):
tag = 'div'
def __init__(self, style = None, **kwargs):
super().__init__(document.createElement(self.tag))
# set all the style properties provided in input
if style:
for key, value in style.items():
self.style[key] = value
# IMPORTANT!!! This is used to auto-harvest all input arguments and set them as properties
kwargs['self'] = self
self._init_properties(**kwargs)
@staticmethod
def _init_properties(**kwargs):
self = kwargs.pop('self')
# Look at all the properties of the class and see if they were provided in kwargs
for attr_name, attr in self.__class__.__dict__.items():
# For each one, actually check if it is a property of the class and set it
if isinstance(attr, JSProperty) and attr_name in kwargs:
setattr(self, attr_name, kwargs[attr_name])
# def __add__(self, other):
# if isinstance(other, list):
# other = div(*other)
# return WidgetCollection(*self.widgets, other, separator=self.separator)
class TextElementBase(ElementBase):
def __init__(self, content=None, style=None, **kwargs):
super().__init__(style=style, **kwargs)
if isinstance(content, pydom.Element):
self.append(content)
elif isinstance(content, list):
for item in content:
self.append(item)
elif content is None:
pass
else:
self._js.innerHTML = content
class h1(TextElementBase):
tag = "h1"
class h2(TextElementBase):
tag = "h2"
class h3(TextElementBase):
tag = "h3"
class button(TextElementBase):
tag = 'button'
# JS Properties
autofocus = js_property('autofocus')
disabled = js_property('disabled')
name = js_property('name')
type = js_property('type')
value = js_property('value')
class link(TextElementBase):
tag = 'a'
href = js_property('href')
def __init__(self, content, href, style = None, **kwargs):
super().__init__(content, href=href, style=style, **kwargs)
class a(link):
pass
class p(TextElementBase):
tag = 'p'
class code(TextElementBase):
tag = 'code'
class pre(TextElementBase):
tag = 'pre'
class strong(TextElementBase):
tag = 'strong'
class small(TextElementBase):
tag = 'small'
class br(ElementBase):
tag = 'br'
class div(TextElementBase):
tag = 'div'
class img(ElementBase):
tag = 'img'
src = js_property('src')
# TODO: This should probably go on the ElementBase class since it's a global attribtute
# https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/slot
slot = js_property('slot')
def __init__(self, src, alt="", style = None, **kwargs):
super().__init__(src=src, alt=alt, style=style, **kwargs)
class Grid(ElementBase):
tag = 'div'
def __init__(self, layout="", gap=None, **kwargs):
super().__init__(**kwargs)
self.style['display'] = 'grid'
self.style['grid-template-columns'] = layout
# TODO: This should be a property
if not gap is None:
self.style['gap'] = gap
class input(ElementBase):
tag = 'input'
# JS Properties
autofocus = js_property('autofocus')
alt = js_property('alt')
autocapitalize = js_property('autocapitalize')
autocomplete = js_property('autocomplete')
checked = js_property('checked')
disabled = js_property('disabled')
name = js_property('name')
type = js_property('type')
value = js_property('value')
placeholder = js_property('placeholder')
# TODO: This is by anymeans complete!! We need to add more attributes
def __init__(self, style = None, **kwargs):
super().__init__(style=style, **kwargs)
# class Input(pydom.Element):
# tag = "sl-input"
# label = LabelProperty()
# placeholder = js_property('placeholder')
# pill = js_property('pill')
# def __init__(self, label=None, value=None, type='text', placeholder=None, help_text=None,
# size=None, filled=False, pill=False, disabled=False, readonly=False, autofocus=False,
# autocomplete=None, autocorrect=None, autocapitalize=None, spellcheck=None, min=None, max=None,
# step=None, name=None, required=False, pattern=None, minlength=None, maxlength=None,
# ):
# super().__init__()
# # Now lets map all the properties to the js object
# self.label = label
# self.placeholder = placeholder
# self.pill = pill
# # self.value = value
# # self.type = type
# # self.placeholder = placeholder
# # self.help_text = help_text
# # self.size = size
# # self.filled = filled
# # self.pill = pill
# # self.disabled = disabled
# # self.readonly = readonly
# # self.autofocus = autofocus
# # self.autocomplete = autocomplete
# # self.autocorrect = autocorrect
# # self.autocapitalize = autocapitalize
# # self.spellcheck = spellcheck
# # self.min = min
# # self.max = max
# # self.step = step
# # self.name = name
# # self.required = required
# # self.pattern = pattern
# # self.minlength = minlength
# # self.maxlength = maxlength
# @property
# def value(self):
# return self._js.value
# @value.setter
# def value(self, value):
# self._js.value = value
# @property
# def type(self):
# return self._js.type
# @type.setter
# def type(self, value):
# self._js.type = value
# @property
# def placeholder(self):
# return self._js.placeholder
# @placeholder.setter
# def placeholder(self, value):
# self._js.placeholder = value
# @property
# def help_text(self):
# return self._js.helpText
# @help_text.setter
# def help_text(self, value):
# self._js.helpText = value
# @property
# def size(self):
# return self._js.size
# @size.setter
# def size(self, value):
# self._js.size = value
# @property
# def filled(self):
# return self._js.filled
# @filled.setter
# def filled(self, value):
# self._js.filled = value
# @property
# def pill(self):
# return self._js.pill
# @pill.setter
# def pill(self, value):
# self._js.pill = value
# @property
# def disabled(self):
# return self._js.disabled
# @disabled.setter
# def disabled(self, value):
# self._js.disabled = value
# @property
# def readonly(self):
# return self._js.readonly
# @readonly.setter
# def readonly(self, value):
# self._js.readonly = value
# class Badge(Widget):
# template = '<sl-badge variant="{variant}" pill>{content}</sl-badge>'
# def __init__(self, content, variant='neutral'):
# self.content = content
# self.variant = variant
# class Card(ComplexWidget):
# template = dedent("""
# <sl-card class="card-overview">
# {img}
# {header}
# {content}
# {footer}
# </sl-card>
# """)
# templates = {
# 'content': '{content}',
# 'footer': dedent("""
# <div slot="footer">
# {footer}
# </div>
# """),
# 'header': dedent("""<div slot="header">
# {header}
# </div>"""
# ),
# 'img': dedent("""
# <img crossorigin='anonymous' slot="image" src="{img}" alt="..."
# />"""),
# }
# def __init__(self, content=None, header=None, footer=None, img=None, img_alt=''):
# self.content = to_widget(content)
# self.header = to_widget(header)
# self.footer = to_widget(footer)
# self.img_alt = img_alt
# if img:
# self.img = to_widget(img)
# else:
# self.img = to_widget('')

View File

@@ -0,0 +1,293 @@
import string
from textwrap import dedent
from pyscript import document, when, window
from pyweb import pydom
import element as el
from element import JSProperty, js_property
class ShoeBase(pydom.Element):
tag = 'div'
def __init__(self, style = None, **kwargs):
super().__init__(document.createElement(self.tag))
# set all the style properties provided in input
if style:
for key, value in style.items():
self.style[key] = value
# IMPORTANT!!! This is used to auto-harvest all input arguments and set them as properties
kwargs['self'] = self
self._init_properties(**kwargs)
@staticmethod
def _init_properties(**kwargs):
self = kwargs.pop('self')
# Look at all the properties of the class and see if they were provided in kwargs
# print(f"Looking for element properties for {self.__class__}...")
for attr_name, attr in self.__class__.__dict__.items():
# print("Checking", attr_name, isinstance(attr, ShoelaceProperty), attr_name in kwargs)
# For each one, actually check if it is a property of the class and set it
if isinstance(attr, JSProperty) and attr_name in kwargs:
setattr(self, attr_name, kwargs[attr_name])
class TextShoeBase(ShoeBase):
def __init__(self, content, style=None, **kwargs):
if not style and getattr(self, 'default_style', None):
style = self.default_style
super().__init__(style=style, **kwargs)
if isinstance(content, pydom.Element):
self.append(content)
else:
self._js.innerHTML = content
class Button(ShoeBase):
tag = 'sl-button'
variant = js_property('variant')
size = js_property('size')
outline = js_property('outline')
pill = js_property('pill')
circle = js_property('circle')
def __init__(self, content, variant='primary', size=None, outline=False, pill=False, circle=False, **kwargs):
super().__init__(**kwargs)
self._js.textContent = content
# IMPORTANT!!! This is use to auto-harvest all input arguments and set them as properties
self._init_properties(**locals())
class Alert(TextShoeBase):
"""Alerts are used to display important messages inline or as toast notifications.
Example: Alert("This is a standard alert. You can customize its content and even the icon.")"""
tag = 'sl-alert'
open = js_property('open')
variant = js_property('variant')
def __init__(self, content, variant=None, open=True, **kwargs):
# TODO: Should content be appended so we can support html Elements as well?
super().__init__(content, variant=variant, open=open, **kwargs)
class Select(ShoeBase):
tag = 'sl-select'
label = js_property('label')
helpText = js_property('helpText')
placeholder = js_property('placeholder')
pill = js_property('pill')
value = js_property('value')
def __init__(self, label=None, options=None, placeholder=None, help_text=None, value=None, style=None, **kwargs):
super().__init__(label=label, placeholder=placeholder, help_text=help_text, value=value, style=style, **kwargs)
html_ = '\n'.join([f'<sl-option value="{option}">{option}</sl-option>' for option in options])
self.html = html_
print("options", options)
print("HTML", html_)
print("HTML", self.html)
# for option in options:
# self.append(el.option(option))
class Button(TextShoeBase):
"""Buttons represent actions that are available to the user."""
tag = 'sl-button'
variant = js_property('variant')
size = js_property('size')
outline = js_property('outline')
pill = js_property('pill')
circle = js_property('circle')
def __init__(self, content, variant='primary', size=None, outline=False, pill=False, circle=False, **kwargs):
super().__init__(content, variant=variant, size=size, outline=outline, pill=pill, circle=circle, **kwargs)
class Details(TextShoeBase):
"""Details are used as a disclosure widget from which users can retrieve additional information."""
tag = 'sl-details'
open = js_property('open')
summary = js_property('summary')
disabled = js_property('disabled')
update_complete = js_property('updateComplete')
def __init__(self, content, summary, open=None, disabled=None, style=None, **kwargs):
super().__init__(content, summary=summary, open=open, disabled=disabled, style=style, **kwargs)
class Dialog(TextShoeBase):
tag = 'sl-dialog'
label = js_property('label')
noheader = js_property('noheader')
open = js_property('open')
# TODO: We should map the `modal` property as well but it's a bit of special...
def __init__(self, content, label=None, open=None, disabled=None, style=None, **kwargs):
super().__init__(content, label=label, open=open, disabled=disabled, style=style, **kwargs)
class Divider(ShoeBase):
tag = 'sl-divider'
vertical = js_property('vertical')
def __init__(self, vertical=None, **kwargs):
super().__init__(vertical=vertical, **kwargs)
self._init_properties(**locals())
class BaseMixin(pydom.Element):
@property
def label(self):
return self._js.label
@label.setter
def label(self, value):
self._js.label = value
# class LabelProperty:
# def __get__(self, obj, objtype=None):
# return obj._js.label
# def __set__(self, obj, value):
# obj._js.label = value
class PlaceholderProperty:
def __get__(self, obj, objtype=None):
return obj._js.placeholder
def __set__(self, obj, value):
obj._js.placeholder = value
class Input(ShoeBase):
tag = "sl-input"
label = js_property('label')
placeholder = js_property('placeholder')
pill = js_property('pill')
help_text = js_property('helpText')
value = js_property('value')
def __init__(self, label=None, value=None, type='text', placeholder=None, help_text=None,
size=None, filled=False, pill=False, disabled=False, readonly=False, autofocus=False,
autocomplete=None, autocorrect=None, autocapitalize=None, spellcheck=None, min=None, max=None,
step=None, name=None, required=False, pattern=None, minlength=None, maxlength=None,
style=None, **kwargs):
super().__init__(style=style, label=label, value=value, type=type, placeholder=placeholder, help_text=help_text,
size=size, filled=filled, pill=pill, disabled=disabled, readonly=readonly, autofocus=autofocus,
autocomplete=autocomplete, autocorrect=autocorrect, autocapitalize=autocapitalize,
spellcheck=spellcheck, min=min, max=max,
step=step, name=name, required=required, pattern=pattern, minlength=minlength, maxlength=maxlength,
**kwargs)
class Badge(TextShoeBase):
tag = 'sl-badge'
variant = js_property('variant')
pill = js_property('pill')
pulse = js_property('pulse')
class Rating(ShoeBase):
tag = 'sl-rating'
label = js_property('label')
value = js_property('value')
max = js_property('max')
# TODO: Properties missing...
class TextArea(ShoeBase):
tag = 'sl-textarea'
label = js_property('label')
helpText = js_property('helpText')
# TODO: Properties missing...
class Card(TextShoeBase):
tag = 'sl-card'
def __init__(self, content=None, image=None, img_alt=None, header=None, footer=None, style=None, **kwargs):
main_div = el.div()
if image:
if not isinstance(image, el.img):
image = el.img(image, alt=img_alt)
image.slot = "image"
if content:
if isinstance(content, pydom.Element):
main_div.append(content)
else:
main_div.append(el.div(content))
main_div.append(content)
super().__init__(content, style=style, **kwargs)
self._js.insertBefore(image._js, self._js.firstChild)
if header:
header = el.div(header, slot="header")
self._js.insertBefore(header._js, self._js.firstChild)
if footer:
self.append(el.div(footer, slot="footer"))
self.add_class('card-overview')
# ************* EXAMPLES SECTION *************
LOREM_IPSUM = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."
details_code = """
LOREM_IPSUM = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."
Details(LOREM_IPSUM, summary="Try me")
"""
example_dialog_close_btn = Button("Close")
example_dialog = Dialog(el.div([el.p(LOREM_IPSUM), example_dialog_close_btn]), label="Try me")
example_dialog_btn = Button("Open Dialog")
def toggle_dialog():
example_dialog.open = not (example_dialog.open)
when('click', example_dialog_btn)(toggle_dialog)
when('click', example_dialog_close_btn)(toggle_dialog)
pydom.body.append(example_dialog)
examples = {
'Alert': {
"instance": Alert("This is a standard alert. You can customize its content and even the icon."),
"code": el.code("Alert('This is a standard alert. You can customize its content and even the icon.'"),
},
'Button': {
"instance": Button("Try me"),
"code": el.code('Button("Try me")'),
},
'Card': {
"instance": Card(el.p("This is a cool card!"), image="https://pyscript.net/assets/images/pyscript-sticker-black.svg",
footer=el.div([Button("More Info"), Rating()])),
"code": el.code('''
Card(el.p("This is a cool card!"), image="https://pyscript.net/assets/images/pyscript-sticker-black.svg", footer=el.div([Button("More Info"), Rating()]))
'''),
},
'Details': {
"instance": Details(LOREM_IPSUM, summary="Try me"),
"code": el.code('Details(LOREM_IPSUM, summary="Try me")'),
},
'Dialog': {
"instance": example_dialog_btn,
"code": el.code('Dialog(div([p(LOREM_IPSUM), Button("Close")]), summary="Try me")'),
},
'Divider': {
"instance": Divider(),
"code": el.code('Divider()'),
},
'Rating': {
"instance": Rating(),
"code": el.code('Rating()'),
},
}