From 0e9f650e69ea8597cc90a872348005e62219c9a9 Mon Sep 17 00:00:00 2001 From: Joseph Burkhart <61951318+josephburkhart@users.noreply.github.com> Date: Thu, 30 Jan 2025 11:40:38 -0500 Subject: [PATCH 1/5] A couple of small improvements to the custom vue component example (#4229) Hi there, this is my first PR for this project and I'm somewhat new to collaborative Open Source development, so please be patient. ### Description This PR modifies the Custom Vue Component [example](https://github.com/zauberzeug/nicegui/tree/main/examples/custom_vue_component) to make it a bit more helpful for developers who need to create custom components but haven't used Vue before. ### Motivation In the example linked above, the Vue component is defined in `counter.js`. This way of bundling a component in a vanilla JavaScript file is obviously supported by Vue, since every built-in NiceGUI component is defined this way, but the official Vue documentation (and basically every other resource on the web) assumes that developers are writing `.vue` files, which are supported by additional tooling. I think the example should structure its Vue code in a way that makes it easier for new developers to understand how it works in relation to existing Vue documentation. ### Changes In the new modified example, the python class definition now uses a new file, `counter.vue`, which re-implements `counter.js` in the more customary Vue Single-File Component format. The call to `ui.run()` has been modified to ensure browser reload on changes to the `.js` and `.vue` files, which is important for iterative development on the Vue side. ### Testing These changes do not add any new functionality that needs to be covered by tests. I have run the modified example and ensured that it does not error. ### Additional Notes I am curious if the developers would be interested in adding a second, more advanced custom vue component example. I am in the process of creating a pannable and zoomable image component, which I would be happy to wrap up in a nice example and provide in a separate PR. --------- Co-authored-by: Falko Schindler --- examples/custom_vue_component/README.md | 21 ++++++++++++ examples/custom_vue_component/counter.js | 13 ++++---- examples/custom_vue_component/main.py | 17 +++++----- examples/custom_vue_component/on_off.py | 14 ++++++++ examples/custom_vue_component/on_off.vue | 41 ++++++++++++++++++++++++ 5 files changed, 91 insertions(+), 15 deletions(-) create mode 100644 examples/custom_vue_component/README.md create mode 100644 examples/custom_vue_component/on_off.py create mode 100644 examples/custom_vue_component/on_off.vue diff --git a/examples/custom_vue_component/README.md b/examples/custom_vue_component/README.md new file mode 100644 index 000000000..820a26157 --- /dev/null +++ b/examples/custom_vue_component/README.md @@ -0,0 +1,21 @@ +# Custom Vue Component + +This example demonstrates how to create and use custom Vue components in NiceGUI. +One component is implemented using JavaScript, the other using Vue's Single-File Component (SFC) syntax. + +## Counter Component (counter.js) + +The `Counter` component is a simple counter that increments a value. +On change, it emits an event with the new value. +A reset method allows to reset the counter to 0. + +The JavaScript code in `counter.js` defines the front-end logic of the component using JavaScript. + +## OnOff Component (on_off.vue) + +The `OnOff` component is a simple toggle that switches between on and off. +On change, it emits an event with the new value. +A reset method is also provided to reset the toggle to off. + +The Single-File Component in `on_off.vue` defines the front-end logic of the component using Vue 2. +In contrast to the JavaScript code in `counter.js`, it splits the template, script, and style into separate sections. diff --git a/examples/custom_vue_component/counter.js b/examples/custom_vue_component/counter.js index 16b40fd16..f005a9920 100644 --- a/examples/custom_vue_component/counter.js +++ b/examples/custom_vue_component/counter.js @@ -1,9 +1,13 @@ // NOTE: Make sure to reload the browser with cache disabled after making changes to this file. export default { template: ` - `, + + `, + props: { + title: String, + }, data() { return { value: 0, @@ -18,7 +22,4 @@ export default { this.value = 0; }, }, - props: { - title: String, - }, }; diff --git a/examples/custom_vue_component/main.py b/examples/custom_vue_component/main.py index bd373eecf..f6b104629 100755 --- a/examples/custom_vue_component/main.py +++ b/examples/custom_vue_component/main.py @@ -1,17 +1,16 @@ #!/usr/bin/env python3 from counter import Counter +from on_off import OnOff from nicegui import ui -ui.markdown(''' -#### Try the new click counter! +with ui.row(align_items='center'): + counter = Counter('Count', on_change=lambda e: ui.notify(f'The value changed to {e.args}.')) + ui.button('Reset', on_click=counter.reset).props('outline') -Click to increment its value. -''') -with ui.card(): - counter = Counter('Clicks', on_change=lambda e: ui.notify(f'The value changed to {e.args}.')) +with ui.row(align_items='center'): + on_off = OnOff('State', on_change=lambda e: ui.notify(f'The value changed to {e.args}.')) + ui.button('Reset', on_click=on_off.reset).props('outline') -ui.button('Reset', on_click=counter.reset).props('small outline') - -ui.run() +ui.run(uvicorn_reload_includes='*.py,*.js,*.vue') diff --git a/examples/custom_vue_component/on_off.py b/examples/custom_vue_component/on_off.py new file mode 100644 index 000000000..90328b8a8 --- /dev/null +++ b/examples/custom_vue_component/on_off.py @@ -0,0 +1,14 @@ +from typing import Callable, Optional + +from nicegui.element import Element + + +class OnOff(Element, component='on_off.vue'): + + def __init__(self, title: str, *, on_change: Optional[Callable] = None) -> None: + super().__init__() + self._props['title'] = title + self.on('change', on_change) + + def reset(self) -> None: + self.run_method('reset') diff --git a/examples/custom_vue_component/on_off.vue b/examples/custom_vue_component/on_off.vue new file mode 100644 index 000000000..f5d91b1d5 --- /dev/null +++ b/examples/custom_vue_component/on_off.vue @@ -0,0 +1,41 @@ + + + + + From 2f386ddd26b8e05d2e81a24fdf600e8114cd7f08 Mon Sep 17 00:00:00 2001 From: tfitzsim Date: Fri, 31 Jan 2025 02:44:04 -0500 Subject: [PATCH 2/5] Enable usage of leaflet plugins (#2767) (#4289) This change enables the usage of leaflet plugins by accepting an additional argument to leaflet, which is as list of additional resources (.js and .css) to load after leaflet but before the map is created. A small example is included to demonstrate and test the functionality using the leaflet-rotatedmarker plugin. --- Feature request: #2767 --------- Co-authored-by: Trevor Fitzsimmons Co-authored-by: Falko Schindler --- nicegui/elements/leaflet.js | 2 ++ nicegui/elements/leaflet.py | 3 +++ .../documentation/content/leaflet_documentation.py | 14 ++++++++++++++ 3 files changed, 19 insertions(+) diff --git a/nicegui/elements/leaflet.js b/nicegui/elements/leaflet.js index 1d554143f..cb1f3cb16 100644 --- a/nicegui/elements/leaflet.js +++ b/nicegui/elements/leaflet.js @@ -10,12 +10,14 @@ export default { draw_control: Object, resource_path: String, hide_drawn_items: Boolean, + additional_resources: Array, }, async mounted() { await this.$nextTick(); // NOTE: wait for window.path_prefix to be set await Promise.all([ loadResource(window.path_prefix + `${this.resource_path}/leaflet/leaflet.css`), loadResource(window.path_prefix + `${this.resource_path}/leaflet/leaflet.js`), + ...this.additional_resources.map((resource) => loadResource(resource)), ]); if (this.draw_control) { await Promise.all([ diff --git a/nicegui/elements/leaflet.py b/nicegui/elements/leaflet.py index c779d2041..31a09fd28 100644 --- a/nicegui/elements/leaflet.py +++ b/nicegui/elements/leaflet.py @@ -27,6 +27,7 @@ def __init__(self, options: Dict = {}, # noqa: B006 draw_control: Union[bool, Dict] = False, hide_drawn_items: bool = False, + additional_resources: List[str] | None = None, ) -> None: """Leaflet map @@ -37,6 +38,7 @@ def __init__(self, :param draw_control: whether to show the draw toolbar (default: False) :param options: additional options passed to the Leaflet map (default: {}) :param hide_drawn_items: whether to hide drawn items on the map (default: False, *added in version 2.0.0*) + :param additional_resources: additional resources like CSS or JS files to load (default: None) """ super().__init__() self.add_resource(Path(__file__).parent / 'lib' / 'leaflet') @@ -51,6 +53,7 @@ def __init__(self, self._props['options'] = {**options} self._props['draw_control'] = draw_control self._props['hide_drawn_items'] = hide_drawn_items + self._props['additional_resources'] = additional_resources or [] self.on('init', self._handle_init) self.on('map-moveend', self._handle_moveend) diff --git a/website/documentation/content/leaflet_documentation.py b/website/documentation/content/leaflet_documentation.py index d41b8e1f5..94b01c273 100644 --- a/website/documentation/content/leaflet_documentation.py +++ b/website/documentation/content/leaflet_documentation.py @@ -196,4 +196,18 @@ async def page(): m.run_map_method('fitBounds', [[bounds['_southWest'], bounds['_northEast']]]) ui.timer(0, page, once=True) # HIDE + +@doc.demo('Leaflet Plugins', ''' + You can add plugins to the map by passing the URLs of JS and CSS files to the `additional_resources` parameter. + This demo shows how to add the [Leaflet.RotatedMarker](https://github.com/bbecquet/Leaflet.RotatedMarker) plugin. + It allows you to rotate markers by a given `rotationAngle`. +''') +def leaflet_plugins() -> None: + m = ui.leaflet((51.51, -0.09), additional_resources=[ + 'https://unpkg.com/leaflet-rotatedmarker@0.2.0/leaflet.rotatedMarker.js', + ]) + m.marker(latlng=(51.51, -0.091), options={'rotationAngle': -30}) + m.marker(latlng=(51.51, -0.090), options={'rotationAngle': 30}) + + doc.reference(ui.leaflet) From 2aa1355d56bf8d3a7bce19d51ec5269314120538 Mon Sep 17 00:00:00 2001 From: Falko Schindler Date: Fri, 31 Jan 2025 09:09:10 +0100 Subject: [PATCH 3/5] fix type annotation --- nicegui/elements/leaflet.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nicegui/elements/leaflet.py b/nicegui/elements/leaflet.py index 31a09fd28..5cf41e56a 100644 --- a/nicegui/elements/leaflet.py +++ b/nicegui/elements/leaflet.py @@ -1,6 +1,6 @@ import asyncio from pathlib import Path -from typing import Any, Dict, List, Tuple, Union, cast +from typing import Any, Dict, List, Optional, Tuple, Union, cast from typing_extensions import Self @@ -27,7 +27,7 @@ def __init__(self, options: Dict = {}, # noqa: B006 draw_control: Union[bool, Dict] = False, hide_drawn_items: bool = False, - additional_resources: List[str] | None = None, + additional_resources: Optional[List[str]] = None, ) -> None: """Leaflet map From 7a540bf3dded6f1e58d32bf21a97d9a5c013afc1 Mon Sep 17 00:00:00 2001 From: Falko Schindler Date: Sat, 1 Feb 2025 04:33:23 +0100 Subject: [PATCH 4/5] Filter keyboard events by type to ignore autocompletion (#4291) This PR solves #4290 by ignoring keyboard events that are not of type `KeyboardEvent`. It can be tested with the following snippet by picking an email from the dropdown list: ```py ui.keyboard(ui.notify, ignore=[]) ui.input('email').props('autocomplete=email') ``` Without this PR, the autocompletion triggers a keyboard event, causing an exception due to missing attributes. With this PR there is simply no keyboard event in this case. --- nicegui/elements/keyboard.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/nicegui/elements/keyboard.js b/nicegui/elements/keyboard.js index ee9257e5e..f86c1bff4 100644 --- a/nicegui/elements/keyboard.js +++ b/nicegui/elements/keyboard.js @@ -2,10 +2,15 @@ export default { mounted() { for (const event of this.events) { document.addEventListener(event, (evt) => { + // https://github.com/zauberzeug/nicegui/issues/4290 + if (!(evt instanceof KeyboardEvent)) return; + // https://stackoverflow.com/a/36469636/3419103 const focus = document.activeElement; if (focus && this.ignore.includes(focus.tagName.toLowerCase())) return; + if (evt.repeat && !this.repeating) return; + this.$emit("key", { action: event, altKey: evt.altKey, From b373df1503ca0c9ae6244612a81eb4dda6ed6b80 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Feb 2025 10:11:03 +0100 Subject: [PATCH 5/5] Bump certifi from 2024.12.14 to 2025.1.31 (#4294) Bumps [certifi](https://github.com/certifi/python-certifi) from 2024.12.14 to 2025.1.31.
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=certifi&package-manager=pip&previous-version=2024.12.14&new-version=2025.1.31)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 95875eecb..6ea44956c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -281,13 +281,13 @@ files = [ [[package]] name = "certifi" -version = "2024.12.14" +version = "2025.1.31" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"}, - {file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"}, + {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, + {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, ] [[package]]