Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
83 changes: 83 additions & 0 deletions demo/async_action/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import random
from dataclasses import field

from async_action_component import AsyncAction, async_action_component

import mesop as me


@me.stateclass
class State:
boxes: dict[str, list[bool | int | str]] = field(
default_factory=lambda: {
"box1": [False, 0, "red"],
"box2": [False, 0, "orange"],
"box3": [False, 0, "yellow"],
}
)
action: str
duration: int


def on_load(e: me.LoadEvent):
me.set_theme_mode("system")


@me.page(
on_load=on_load,
path="/async_action",
security_policy=me.SecurityPolicy(
allowed_iframe_parents=["https://mesop-dev.github.io"],
allowed_connect_srcs=["https://cdn.jsdelivr.net"],
allowed_script_srcs=["https://cdn.jsdelivr.net"],
dangerously_disable_trusted_types=True,
),
)
def page():
state = me.state(State)
action = (
AsyncAction(value=state.action, duration_seconds=state.duration)
if state.action
else None
)
async_action_component(
action=action, on_started=on_started, on_finished=on_finished
)
with me.box(
style=me.Style(
display="flex", flex_direction="column", margin=me.Margin.all(15)
)
):
for key, meta in state.boxes.items():
with me.box(style=me.Style(padding=me.Padding.all(15))):
me.button("Show " + key, type="flat", key=key, on_click=on_click)
if meta[0]:
with me.box(
style=me.Style(
background=str(meta[2]),
width=100,
height=100,
margin=me.Margin(top=15),
padding=me.Padding.all(15),
)
):
me.text(f"{meta[0]} {meta[1]}")


def on_click(e: me.ClickEvent):
state = me.state(State)
state.action = e.key
state.duration = random.randint(2, 10)
state.boxes[e.key][0] = True
state.boxes[e.key][1] = state.duration


def on_started(e: me.WebEvent):
state = me.state(State)
state.action = ""


def on_finished(e: me.WebEvent):
state = me.state(State)
state.action = ""
state.boxes[e.value["action"]][0] = False
47 changes: 47 additions & 0 deletions demo/async_action/async_action_component.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {
LitElement,
html,
} from 'https://cdn.jsdelivr.net/gh/lit/dist@3/core/lit-core.min.js';

class AsyncAction extends LitElement {
static properties = {
startedEvent: {type: String},
finishedEvent: {type: String},
// Format: {action: String, duration_seconds: Number}
action: {type: Object},
isRunning: {type: Boolean},
};

render() {
return html`<div></div>`;
}

firstUpdated() {
if (this.action) {
this.runTimeout(this.action);
}
}

updated(changedProperties) {
if (changedProperties.has('action') && this.action) {
this.runTimeout(this.action);
}
}

runTimeout(action) {
this.dispatchEvent(
new MesopEvent(this.startedEvent, {
action: action,
}),
);
setTimeout(() => {
this.dispatchEvent(
new MesopEvent(this.finishedEvent, {
action: action.value,
}),
);
}, action.duration_seconds * 1000);
}
}

customElements.define('async-action-component', AsyncAction);
41 changes: 41 additions & 0 deletions demo/async_action/async_action_component.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from dataclasses import asdict, dataclass
from typing import Any, Callable

import mesop as me


@dataclass
class AsyncAction:
value: str
duration_seconds: int


@me.web_component(path="./async_action_component.js")
def async_action_component(
*,
on_started: Callable[[me.WebEvent], Any],
on_finished: Callable[[me.WebEvent], Any],
action: AsyncAction | None = None,
key: str | None = None,
):
"""Creates an invisibe component that will delay state changes asynchronously.

Right now this implementation is limited since we basically just pass the key around.
But ideally we also pass in some kind of value to update when the time out expires.

The main benefit of this component is for cases, such as status messages that may
appear and disappear after some duration. The primary example here is the example
snackbar widget, which right now blocks the UI when using the sleep yield approach.

The other benefit of this component is that it works generically (rather than say
implementing a custom snackbar widget as a web component).
"""
return me.insert_web_component(
name="async-action-component",
key=key,
events={
"startedEvent": on_started,
"finishedEvent": on_finished,
},
properties={"action": asdict(action) if action else ""},
)
203 changes: 203 additions & 0 deletions demo/charts/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
from chartjs_component import chartjs_component

import mesop as me

_SUBTITLE_DATE = "Jan - May 2025"

_RED = "rgb(255, 99, 132)"
_BLUE = "rgb(54, 162, 235)"
_YELLOW = "rgb(255, 205, 86)"
_GREEN = "rgb(75, 192, 192)"
_GREY = "rgb(201, 203, 207)"

_DEFAULT_OPTIONS = {
"responsive": True,
"aspectRatio": 1,
"maintainAspectRatio": True,
"scales": {
"y": {
"beginAtZero": True,
}
},
}

_PIE_OPTIONS = {
"options": {
"plugins": {
"legend": {
"display": False,
},
},
"responsive": True,
"aspectRatio": 1,
"maintainAspectRatio": True,
},
}
_DOUGHNUT_OPTIONS = _PIE_OPTIONS
_POLAR_AREA_OPTIONS = (
{
"responsive": True,
"aspectRatio": 1,
"maintainAspectRatio": True,
"scales": {
"r": {
"beginAtZero": True,
}
},
},
)

config_line = {
"type": "line",
"data": {
"labels": ["January", "February", "March", "April", "May"],
"datasets": [
{
"label": "Pies",
"data": [65, 59, 80, 81, 56],
"backgroundColor": _RED,
"borderColor": _RED,
"fill": False,
},
{
"label": "Donuts",
"data": [102, 122, 115, 98, 128],
"backgroundColor": _GREEN,
"borderColor": _GREEN,
"fill": False,
},
],
},
"options": _DEFAULT_OPTIONS,
}

config_bar = {
"type": "bar",
"data": {
"labels": ["January", "February", "March", "April", "May"],
"datasets": [
{
"label": "Pies",
"data": [65, 59, 80, 81, 56],
"backgroundColor": _RED,
},
{
"label": "Donuts",
"data": [102, 122, 115, 98, 128],
"backgroundColor": _GREEN,
},
],
},
"options": _DEFAULT_OPTIONS,
}

config_area = {
"type": "line",
"data": {
"labels": ["January", "February", "March", "April", "May"],
"datasets": [
{
"label": "Pies",
"data": [65, 59, 80, 81, 56],
"backgroundColor": _RED,
"borderColor": _RED,
"fill": True,
},
{
"label": "Donuts",
"data": [102, 122, 115, 98, 128],
"backgroundColor": _GREEN,
"borderColor": _GREEN,
"fill": True,
},
],
},
"options": _DEFAULT_OPTIONS,
}

config_doughnut = {
"type": "doughnut",
"data": {
"labels": ["Apple fritter", "Glazed", "Bear claw", "Cruller"],
"datasets": [
{
"data": [300, 50, 100, 42],
"backgroundColor": [
_RED,
_BLUE,
_YELLOW,
_GREEN,
],
"hoverOffset": 4,
}
],
},
"options": _DOUGHNUT_OPTIONS,
}

config_pie = {
"type": "pie",
"data": {
"labels": ["Lemon Meringue", "Pumpkin", "Apple", "Banana Cream"],
"datasets": [
{
"data": [300, 50, 100, 42],
"backgroundColor": [
_RED,
_BLUE,
_YELLOW,
_GREEN,
],
"hoverOffset": 4,
}
],
},
"options": _PIE_OPTIONS,
}


def on_load(e: me.LoadEvent):
me.set_theme_mode("system")


@me.page(
on_load=on_load,
path="/charts",
security_policy=me.SecurityPolicy(
allowed_iframe_parents=["https://mesop-dev.github.io"],
allowed_script_srcs=[
"https://cdn.jsdelivr.net",
"https://cdn.jsdelivr.net/npm/chart.js",
],
),
)
def page():
with me.box(
style=me.Style(
margin=me.Margin.all(15), display="flex", gap=15, flex_wrap="wrap"
)
):
with me.card():
me.card_header(title="Bar Chart", subtitle=_SUBTITLE_DATE)
with me.card_content():
chartjs_component(config=config_bar)

with me.card():
me.card_header(title="Line Chart", subtitle=_SUBTITLE_DATE)
with me.card_content():
chartjs_component(config=config_line)

with me.card():
me.card_header(title="Area Chart", subtitle=_SUBTITLE_DATE)
with me.card_content():
chartjs_component(config=config_area)

with me.card():
me.card_header(title="Donut Chart", subtitle=_SUBTITLE_DATE)
with me.card_content():
chartjs_component(config=config_doughnut)

with me.card():
me.card_header(title="Pie Chart", subtitle=_SUBTITLE_DATE)
with me.card_content():
chartjs_component(config=config_pie)
Loading
Loading