Skip to content

Commit

Permalink
Minor tidy ups, added comments, renamed 'via_function' to 'with_funct…
Browse files Browse the repository at this point in the history
…ion' for reading age reasons.
  • Loading branch information
ntoll committed Mar 2, 2024
1 parent e8effa9 commit 471550d
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 51 deletions.
34 changes: 14 additions & 20 deletions examples/farmyard/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,11 @@ def make_oink(message):


def make_geese(number_of_honks):
return [
invent.ui.TextBox(text="🪿")

for _ in range(number_of_honks)
]
return [invent.ui.TextBox(text="🪿") for _ in range(number_of_honks)]


def make_pigs(number_of_oinks):
return [
invent.ui.TextBox(text="🐖")

for _ in range(number_of_oinks)
]
return [invent.ui.TextBox(text="🐖") for _ in range(number_of_oinks)]


# Channels #############################################################################
Expand Down Expand Up @@ -92,7 +84,9 @@ def make_pigs(number_of_oinks):
),
invent.ui.TextBox(
name="number_of_honks",
text=invent.ui.from_datastore("number_of_honks"),
text=invent.ui.from_datastore(
"number_of_honks"
),
position="MIDDLE-CENTER",
),
],
Expand All @@ -101,8 +95,8 @@ def make_pigs(number_of_oinks):
id="geese",
position="CENTER",
content=invent.ui.from_datastore(
"number_of_honks", via_function=make_geese
)
"number_of_honks", with_function=make_geese
),
),
invent.ui.Button(
name="to_code",
Expand Down Expand Up @@ -140,7 +134,9 @@ def make_pigs(number_of_oinks):
),
invent.ui.TextBox(
name="number_of_oinks",
text=invent.ui.from_datastore("number_of_oinks"),
text=invent.ui.from_datastore(
"number_of_oinks"
),
position="MIDDLE-CENTER",
),
],
Expand All @@ -149,8 +145,8 @@ def make_pigs(number_of_oinks):
id="pigs",
position="CENTER",
content=invent.ui.from_datastore(
"number_of_oinks", via_function=make_pigs
)
"number_of_oinks", with_function=make_pigs
),
),
invent.ui.Button(
name="to_code",
Expand Down Expand Up @@ -189,10 +185,8 @@ def make_pigs(number_of_oinks):
),
]
),
invent.ui.Code(
code=exporter.as_python_code(app)
)
]
invent.ui.Code(code=exporter.as_python_code(app)),
],
)
)

Expand Down
80 changes: 57 additions & 23 deletions src/invent/ui/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,19 +112,19 @@ class from_datastore: # NOQA
orthodox capitalised camel case, for aesthetic reasons. ;-)
"""

def __init__(self, key, via_function=None):
def __init__(self, key, with_function=None):
"""
The key identifies the value in the datastore.
"""
self.key = key
self.via_function = via_function
self.with_function = with_function

def __repr__(self):
"""Create the expression for a property that gets its value from the datastore."""

expression = f"from_datastore('{self.key}'"
if self.via_function:
expression += f", via_function={self.via_function.__name__}"
if self.with_function:
expression += f", with_function={self.with_function.__name__}"
expression += ")"

return expression
Expand Down Expand Up @@ -182,7 +182,8 @@ def __set__(self, obj, value):
"""

if isinstance(value, from_datastore):
via_function = value.via_function
via_function = value.with_function

def reactor(message): # pragma: no cover
"""
Set the value in the widget and call the optional
Expand All @@ -201,9 +202,6 @@ def reactor(message): # pragma: no cover
# Attach the "from_datastore" instance to the object.
setattr(obj, self.private_name + "_from_datastore", value)

from pyscript import window
window.console.log(f'Setting: {self.private_name + "_from_datastore"}')

# Subscribe to store events for the specified key.
invent.subscribe(
reactor, to_channel="store-data", when_subject=value.key
Expand Down Expand Up @@ -301,7 +299,7 @@ def coerce(self, value):
result = int(value)
return result
except ValueError:
pass # Handle below
pass # Handle below
raise ValueError(_("Not a valid number: ") + value)

def validate(self, value):
Expand Down Expand Up @@ -402,7 +400,6 @@ def coerce(self, value):
"""
# Don't coerce None because None may be a valid value.
return str(value) if value is not None else None
return str(value)

def validate(self, value):
"""
Expand Down Expand Up @@ -465,7 +462,6 @@ def validate(self, value):
"""
Ensure the property's value is a boolean value (True / False).
"""

return super().validate(self.coerce(value))


Expand Down Expand Up @@ -508,10 +504,13 @@ class Component:
A base class for all user interface components (Widget, Container).
Ensures they all have optional names and ids. If they're not given, will
auto-generate them for the user.
auto-generate them for the user. The position of the component determines
how it will be drawn in its parent.
"""

# Used for quick component look-up.
_components_by_id = {}
# Used for generating unique component names.
_component_counter = 0

id = TextProperty("The id of the widget instance in the DOM.")
Expand Down Expand Up @@ -545,26 +544,50 @@ def update(self, **kwargs):
a property on this class, set the property's value to the associated
value in the dict.
"""
# Set values from the **kwargs
for k, v in kwargs.items():
if hasattr(self, k):
setattr(self, k, v)

# Set values from the property's own default_values.
for property_name, property_obj in type(self).properties().items():
if property_name not in kwargs:
if property_obj.default_value is not None:
setattr(self, property_name, property_obj.default_value)

def on_id_changed(self):
"""
Automatically called to update the id of the HTML element associated
with the component.
"""
self.element.id = self.id

def on_name_changed(self):
"""
Automatically called to update the name attribute of the HTML element
associated with the component.
"""
if self.name:
self.element.setAttribute("name", self.name)
else:
self.element.removeAttribute("name")

def on_position_changed(self):
"""
Automatically called to update the position information relating to
the HTML element associated with the component.
"""
if self.element.parentElement:
self.set_position(self.element.parentElement)

@classmethod
def _generate_name(cls):
"""
Create a human friendly name for the component.
E.g.
"Button 1"
"""
cls._component_counter += 1
return f"{cls.__name__} {cls._component_counter}"

Expand All @@ -577,10 +600,18 @@ def get_component_by_id(cls, component_id):
return Component._components_by_id.get(component_id)

def render(self):
"""
In base classes, return the HTML element used to display the
component.
"""
raise NotImplementedError() # pragma: no cover

@classmethod
def preview(cls):
"""
In base classes, return the outerHTML to display in the menu of
available components.
"""
raise NotImplementedError() # pragma: no cover

@classmethod
Expand Down Expand Up @@ -648,7 +679,7 @@ def blueprint(cls):

def as_dict(self):
"""
Return a dict representation of the state of this component's
Return a dict representation of the state of this instance's
properties and message blueprints.
"""
properties = {
Expand Down Expand Up @@ -693,7 +724,7 @@ def parse_position(self):
if not (horizontal_position or vertical_position):
# Bail out if we don't have a valid position state.
raise ValueError(f"'{self.position}' is not a valid position.")
return (vertical_position, horizontal_position)
return vertical_position, horizontal_position

def set_position(self, container):
"""
Expand All @@ -702,8 +733,10 @@ def set_position(self, container):
the element into the expected position in the container.
"""

# Reset...
def reset():
"""
Reset the style state for the component and its parent container.
"""
self.element.style.removeProperty("width")
self.element.style.removeProperty("height")
container.style.removeProperty("align-self")
Expand Down Expand Up @@ -749,26 +782,27 @@ def reset():

class Widget(Component):
"""
A widget is a UI component drawn onto the interface in some way.
All widgets have these things:
* A unique human friendly name that's meaningful in the context of the
application (if none is given, one is automatically generated).
* A unique id (if none is given, one is automatically generated).
* An indication of the widget's preferred position (default: top left).
* An indication of the widget's preferred position.
* A render function that takes the widget's container and renders
itself as an HTML element into the container.
* An optional channel name to which it broadcasts its messages (defaults to
the id).
* An optional indication of the channel[s] to which it broadcasts
messages (defaults to the id).
* A publish method that takes the name of a message blueprint, and
associated kwargs, and publishes it to the channel[s] set for the
widget.
"""

channel = TextProperty(
"A comma separated list of channels to which the widget broadcasts."
)

def on_position_changed(self):
if self.element.parentElement:
self.set_position(self.element.parentElement)

def publish(self, blueprint, **kwargs):
"""
Given the name of one of the class's MessageBlueprints, publish
Expand Down
18 changes: 10 additions & 8 deletions src/invent/ui/exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
"""


from invent.ui import Container


Expand Down Expand Up @@ -97,13 +96,13 @@ def make_pigs(value_from_datastore):


def as_python_code(app):
""" Generate the *textual* Python code for the app."""
"""Generate the *textual* Python code for the app."""

return MAIN_PY_TEMPLATE.format(
imports=IMPORTS,
datastore=DATASTORE,
code=CODE,
app=_pretty_repr_app(app)
app=_pretty_repr_app(app),
)


Expand All @@ -126,8 +125,7 @@ def _pretty_repr_app(app):
"""Generate a pretty repr of the App's UI."""

return APP_TEMPLATE.format(
name=app.name,
pages=_pretty_repr_pages(app.content)
name=app.name, pages=_pretty_repr_pages(app.content)
)


Expand All @@ -136,7 +134,7 @@ def _pretty_repr_pages(pages):

lines = []
for page in pages:
_pretty_repr_component(page, lines=lines, indent=" "*8)
_pretty_repr_component(page, lines=lines, indent=" " * 8)

return "\n".join(lines)

Expand Down Expand Up @@ -167,7 +165,9 @@ def _pretty_repr_component(component, lines, indent=""):
if is_container and property_name == "content":
continue

from_datastore = getattr(component, f"_{property_name}_from_datastore", None)
from_datastore = getattr(
component, f"_{property_name}_from_datastore", None
)
if from_datastore:
property_value = from_datastore

Expand All @@ -186,7 +186,9 @@ def _pretty_repr_component(component, lines, indent=""):
else:
lines.append(f"{indent}content=[")
for child in component.content:
_pretty_repr_component(child, lines=lines, indent=indent + " ")
_pretty_repr_component(
child, lines=lines, indent=indent + " "
)
lines.append(f"{indent}],")

# The last line of the component's constructor e.g.")" :) ##########################
Expand Down

0 comments on commit 471550d

Please sign in to comment.