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

Add Clipboard component for handling global on_paste event #3513

Merged
merged 3 commits into from
Jun 26, 2024

Conversation

masenf
Copy link
Collaborator

@masenf masenf commented Jun 17, 2024

Supersede #3078 to fix #3055

Creates a new rx.clipboard component with on_paste handler that will be called when pasting data into the page. If it has children components, then only those components will register the paste handler, otherwise, it gets registered at the top-level document (you can also pass targets prop as a list of ids).

The handler will be called with the data arg as list of tuples of (mime_type, data). If the data is text, it will be a regular string. If the data is associated with a different type, then it will be returned as a base64 encoded data uri. There may be multiple entries in the data list for example if the data is available in several formats (text/plain and text/html) or if several files have been pasted from the filesystem.

Sample code

import reflex as rx


class State(rx.State):
    image_uri: str = ""
    targets: list[str] = ["t4", "t3"]

    def on_paste_generic(self, data, source):
        for type, content in data:
            if type.startswith("image/"):
                self.image_uri = content
            yield rx.toast.info(f"Pasted {type} in {source}", description=content[:64])

    def on_paste(self, data):
        yield from self.on_paste_generic(data, "document")

    def on_paste_input(self, data):
        yield from self.on_paste_generic(data, "input")

    def on_paste_textarea(self, data):
        yield from self.on_paste_generic(data, "textarea")

    def handle_checked_change(self, target, checked):
        if checked:
            self.targets.append(target)
        else:
            try:
                self.targets.remove(target)
            except ValueError:
                pass


def togglable_input(id: str):
    return rx.hstack(
        rx.input(id=id),
        rx.checkbox(
            checked=State.targets.contains(id),
            on_change=State.handle_checked_change(id)
        ),
    )


def index() -> rx.Component:
    return rx.vstack(
        rx.clipboard(on_paste=State.on_paste),
        rx.clipboard(on_paste=State.on_paste_input.stop_propagation, targets=State.targets),
        rx.cond(State.image_uri, rx.image(src=State.image_uri), rx.text("No image")),
        rx.clipboard(rx.text_area(), on_paste=State.on_paste_textarea.prevent_default),
        rx.clipboard(rx.text_area(), on_paste=State.on_paste_textarea.stop_propagation),
        togglable_input("t1"),
        togglable_input("t2"),
        togglable_input("t3"),
        rx.text("Selected inputs: ", State.targets.to_string()),
    )


app = rx.App()
app.add_page(index)

@masenf masenf mentioned this pull request Jun 17, 2024
10 tasks
@masenf masenf merged commit 956bc0a into main Jun 26, 2024
47 checks passed
@masenf masenf deleted the masenf/global-paste-handler branch June 26, 2024 16:22
@dentroai
Copy link

When using the new paste handler, how do I need to write the event handler so that if a file is pasted we only handle the file but do not paste in the file path as text into the input field / text area, but when we paste in pure text we display the text in the input field / text area?

When I use prevent_default the file path isn't displayed when I paste a file, but then I also can't paste in normal text.
If I don't use prevent_default, the file path is always pasted in along

@masenf
Copy link
Collaborator Author

masenf commented Jul 11, 2024

@dentro-innovation so there's not currently a way to accomplish this, but the logic in the paste handler could be update to achieve what sounds like a pretty common workflow. Can you file a follow up bug for this?

In the meantime, you might be able to get fancy with a controlled input, i haven't checked if the on_paste gets called before on_change though.

@dentroai
Copy link

Not sure if I'm doing something wrong, but it seems to me as if I'm only able to paste in a single file.
When I select multiple files on my laptop, copy them and then paste them into the reflex app, only the first file is pasted.

When inspecting my event handler in debug mode, it seems like data is always an array of length 1, even though I pasted e.g. 3 files

async def on_paste(self, data):
        for type, content in data:

Is this something you are aware of?
Expected behavior would be that I can paste in as many files as I want.

cutoffthetop referenced this pull request in robert-koch-institut/mex-editor Jul 16, 2024
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [reflex](https://reflex.dev)
([source](https://github.com/reflex-dev/reflex)) |
project.dependencies | patch | `==0.5.5` -> `==0.5.6` |

---

### Release Notes

<details>
<summary>reflex-dev/reflex (reflex)</summary>

###
[`v0.5.6`](https://github.com/reflex-dev/reflex/releases/tag/v0.5.6)

[Compare
Source](https://github.com/reflex-dev/reflex/compare/v0.5.5...v0.5.6)

#### New Features

##### `rx.clipboard`

Handle global and component-scoped `on_paste` with multi-type data

- Add Clipboard component for handling global on_paste event by
[@&#8203;masenf](https://github.com/masenf) in
[https://github.com/reflex-dev/reflex/pull/3513](https://github.com/reflex-dev/reflex/pull/3513)

##### `rx.breakpoints` - Set breakpoints by name

- introduce customizable breakpoints by
[@&#8203;adhami3310](https://github.com/adhami3310) in
[https://github.com/reflex-dev/reflex/pull/3568](https://github.com/reflex-dev/reflex/pull/3568)
- add responsive props to radix components by
[@&#8203;adhami3310](https://github.com/adhami3310) in
[https://github.com/reflex-dev/reflex/pull/3583](https://github.com/reflex-dev/reflex/pull/3583)

##### Computed Backend Vars

- add computed backend vars by
[@&#8203;benedikt-bartscher](https://github.com/benedikt-bartscher) in
[https://github.com/reflex-dev/reflex/pull/3573](https://github.com/reflex-dev/reflex/pull/3573)
- improve backend var determination by
[@&#8203;benedikt-bartscher](https://github.com/benedikt-bartscher) in
[https://github.com/reflex-dev/reflex/pull/3587](https://github.com/reflex-dev/reflex/pull/3587)

#### Improvements

- \[REF-3056]Config knob for redis StateManager expiration times by
[@&#8203;ElijahAhianyo](https://github.com/ElijahAhianyo) in
[https://github.com/reflex-dev/reflex/pull/3523](https://github.com/reflex-dev/reflex/pull/3523)
- \[Perf] Ensure rx.match gets memoized to avoid excessive re-rendering
by [@&#8203;masenf](https://github.com/masenf) in
[https://github.com/reflex-dev/reflex/pull/3552](https://github.com/reflex-dev/reflex/pull/3552)
- bare sqlalchemy session + tests by
[@&#8203;benedikt-bartscher](https://github.com/benedikt-bartscher) in
[https://github.com/reflex-dev/reflex/pull/3522](https://github.com/reflex-dev/reflex/pull/3522)
- Remove chakra from codeblock by
[@&#8203;abulvenz](https://github.com/abulvenz) in
[https://github.com/reflex-dev/reflex/pull/3570](https://github.com/reflex-dev/reflex/pull/3570)
- add compilation timestamp in log by
[@&#8203;Lendemor](https://github.com/Lendemor) in
[https://github.com/reflex-dev/reflex/pull/3563](https://github.com/reflex-dev/reflex/pull/3563)
- \[REF-3148] add props for tabs by
[@&#8203;Lendemor](https://github.com/Lendemor) in
[https://github.com/reflex-dev/reflex/pull/3560](https://github.com/reflex-dev/reflex/pull/3560)
- show the value causing problem in deprecation warning by
[@&#8203;Lendemor](https://github.com/Lendemor) in
[https://github.com/reflex-dev/reflex/pull/3558](https://github.com/reflex-dev/reflex/pull/3558)
-   Better support for Github Codespaces
- Add a link to backend in connection error by
[@&#8203;masenf](https://github.com/masenf) in
[https://github.com/reflex-dev/reflex/pull/3044](https://github.com/reflex-dev/reflex/pull/3044)
- Better hot reload times on Windows with python 3.12 and uvicorn > 0.20
- \[REF-3164] Hack to fix Windows hot reload + Uvicorn upgrade by
[@&#8203;masenf](https://github.com/masenf) in
[https://github.com/reflex-dev/reflex/pull/3584](https://github.com/reflex-dev/reflex/pull/3584)
-   `rx.theme` appearance is reset in dev mode
- \[REF-2588]Clear color mode local storage for dev mode by
[@&#8203;ElijahAhianyo](https://github.com/ElijahAhianyo) in
[https://github.com/reflex-dev/reflex/pull/3548](https://github.com/reflex-dev/reflex/pull/3548)

#### Bug Fixes

- Fix radix radio cards component by
[@&#8203;emmakodes](https://github.com/emmakodes) in
[https://github.com/reflex-dev/reflex/pull/3545](https://github.com/reflex-dev/reflex/pull/3545)
- fix small typing issue by
[@&#8203;Lendemor](https://github.com/Lendemor) in
[https://github.com/reflex-dev/reflex/pull/3562](https://github.com/reflex-dev/reflex/pull/3562)
- \[REF-3185]\[REF-3180]Dont escape backticks in JS string interpolation
by [@&#8203;ElijahAhianyo](https://github.com/ElijahAhianyo) in
[https://github.com/reflex-dev/reflex/pull/3566](https://github.com/reflex-dev/reflex/pull/3566)
- classvars should not be backend vars by
[@&#8203;benedikt-bartscher](https://github.com/benedikt-bartscher) in
[https://github.com/reflex-dev/reflex/pull/3578](https://github.com/reflex-dev/reflex/pull/3578)
-   ComponentState and State mixins now work with backend vars
- copy backend vars from mixins by
[@&#8203;benedikt-bartscher](https://github.com/benedikt-bartscher) in
[https://github.com/reflex-dev/reflex/pull/3580](https://github.com/reflex-dev/reflex/pull/3580)
- fix var dependency over properties by
[@&#8203;benedikt-bartscher](https://github.com/benedikt-bartscher) in
[https://github.com/reflex-dev/reflex/pull/3588](https://github.com/reflex-dev/reflex/pull/3588)
- \[REF-3157] Avoid SQLModel metaclass conflict
([#&#8203;3610](https://github.com/reflex-dev/reflex/issues/3610))
- \[REF-3220] Fix rx.cancel_upload EventSpec
([#&#8203;3608](https://github.com/reflex-dev/reflex/issues/3608))
- do not get_config in global scope
([#&#8203;3597](https://github.com/reflex-dev/reflex/issues/3597))

#### Other Changes

- Validate ComputedVar dependencies, add tests by
[@&#8203;benedikt-bartscher](https://github.com/benedikt-bartscher) in
[https://github.com/reflex-dev/reflex/pull/3527](https://github.com/reflex-dev/reflex/pull/3527)
- do not validate non-cached var deps by
[@&#8203;benedikt-bartscher](https://github.com/benedikt-bartscher) in
[https://github.com/reflex-dev/reflex/pull/3576](https://github.com/reflex-dev/reflex/pull/3576)
- Radio group fix deprecation warning for creating vars with strings by
[@&#8203;ElijahAhianyo](https://github.com/ElijahAhianyo) in
[https://github.com/reflex-dev/reflex/pull/3567](https://github.com/reflex-dev/reflex/pull/3567)
- update dependencies pinning by
[@&#8203;Lendemor](https://github.com/Lendemor) in
[https://github.com/reflex-dev/reflex/pull/3556](https://github.com/reflex-dev/reflex/pull/3556)
- fix formatting by
[@&#8203;benedikt-bartscher](https://github.com/benedikt-bartscher) in
[https://github.com/reflex-dev/reflex/pull/3574](https://github.com/reflex-dev/reflex/pull/3574)
- order type annotations in pyi_generator by
[@&#8203;benedikt-bartscher](https://github.com/benedikt-bartscher) in
[https://github.com/reflex-dev/reflex/pull/3585](https://github.com/reflex-dev/reflex/pull/3585)
- Deprecate cached var by
[@&#8203;benedikt-bartscher](https://github.com/benedikt-bartscher) in
[https://github.com/reflex-dev/reflex/pull/3582](https://github.com/reflex-dev/reflex/pull/3582)
- Change Strategy Prop to Literal by
[@&#8203;Alek99](https://github.com/Alek99) in
[https://github.com/reflex-dev/reflex/pull/3575](https://github.com/reflex-dev/reflex/pull/3575)
- pyi_generator cleanup: ruff, remove fully qualified reflex. names by
[@&#8203;masenf](https://github.com/masenf) in
[https://github.com/reflex-dev/reflex/pull/3591](https://github.com/reflex-dev/reflex/pull/3591)
- cleanup unused check by
[@&#8203;benedikt-bartscher](https://github.com/benedikt-bartscher) in
[https://github.com/reflex-dev/reflex/pull/3590](https://github.com/reflex-dev/reflex/pull/3590)
- split lifespan and middleware logic in separate mixin files by
[@&#8203;Lendemor](https://github.com/Lendemor) in
[https://github.com/reflex-dev/reflex/pull/3557](https://github.com/reflex-dev/reflex/pull/3557)
- Update links to /docs/library/dynamic-rendering/foreach/
([#&#8203;3609](https://github.com/reflex-dev/reflex/issues/3609))

#### New Contributors

##### Welcome Khaleel to the Core Team

- [@&#8203;adhami3310](https://github.com/adhami3310) made their first
contribution in
[https://github.com/reflex-dev/reflex/pull/3568](https://github.com/reflex-dev/reflex/pull/3568)

**Full Changelog**:
reflex-dev/reflex@v0.5.5...v0.5.6

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR has been generated by [Renovate
Bot](https://github.com/renovatebot/renovate).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy40MzEuNyIsInVwZGF0ZWRJblZlciI6IjM3LjQzMS43IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
@dentroai
Copy link

Also, could it be that it isn't possible to paste very big files?
I got a screenshot that's 2.8 MB.
I can upload it using the "normal" file upload functionality, but when I try to upload this big image, the on_paste handler isn't even triggered.

I do not use max_size of the file_upload component.

Here the screenshot for reference, inside a zip archive to make sure it doesn't get compressed when uploaded to github:

big_image.zip

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Global on_paste event listener
3 participants