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

Possible to add ui.update_input_file?? #1583

Open
devdan852 opened this issue Jul 29, 2024 · 4 comments
Open

Possible to add ui.update_input_file?? #1583

devdan852 opened this issue Jul 29, 2024 · 4 comments
Labels
enhancement New feature or request
Milestone

Comments

@devdan852
Copy link

Is it possible to add a method that allows the user to update the path of the input file that was originally set with using ui.input_file? Or possibly allow the user to remove the uploaded file through such a method?

@schloerke
Copy link
Collaborator

TIL you can programmatically set the file value (assuming you know the full contents) 🤯

I thought it was a security risk. Maybe it was in the older DOM days.

Related: https://dev.to/code_rabbi/programmatically-setting-file-inputs-in-javascript-2p7i


POC of working example: shinylive link

Screen.Recording.2024-07-30.at.3.18.23.PM.mov

@schloerke schloerke added enhancement New feature or request and removed needs-triage labels Jul 30, 2024
@schloerke schloerke added this to the v1.2.0 milestone Jul 30, 2024
@schloerke
Copy link
Collaborator

Adding the milestone so that the issue receives more eyes

@wch
Copy link
Collaborator

wch commented Sep 12, 2024

Working with @schloerke, we came up with this version, where the file content is set purely on the server side (so that it doesn't need to be sent to the client browser). This is a proof of concept -- we would want to make changes to the Shiny package code to make this easier.

Shinylive link

import os

from shiny import App, reactive, render, ui

app_ui = ui.page_fluid(
    ui.h2("File Upload and Code Display"),
    ui.input_file("file", "Choose a file to upload"),
    ui.input_action_button("create_upload", "Create and Upload Hello World"),
    ui.output_ui("file_contents"),
    ui.tags.script(
        """
    // Custom message handler for receiving the filename
    Shiny.addCustomMessageHandler("setFileName", function(filename) {
        const fileInputImpl = document.querySelector('#file');
        const fileInputPlaceholder = $("div.input-group:has(> label > span > input#file) > input").get(0)

        const myFile = new File([''], filename, {
            type: 'text/shiny_serverside',
            lastModified: new Date(),
        });

        const dataTransfer = new DataTransfer();
        dataTransfer.items.add(myFile);

        fileInputImpl.files = dataTransfer.files;
        fileInputImpl.dispatchEvent(new Event('change', { bubbles: true }));
    });
    """,
        type="text/javascript",
    ),
)

example_file = {
    "name": "myFile.txt",
    "type": "text/plain",
    "content": "Hello, world!",
}


def server(input, output, session):
    @render.ui
    def file_contents():
        file = input.file()
        if file is not None and len(file) > 0:
            # =====================================================
            # Inject the content on disk.
            # This would be inside of the Shiny package code.
            # =====================================================
            if file[0]["type"] == "text/shiny_serverside":
                with open(file[0]["datapath"], "w") as f:
                    f.write(example_file["content"])
                # Find the file size and assign it here
                file[0]["size"] = os.path.getsize(file[0]["datapath"])
                file[0]["type"] = example_file["type"]
            # =====================================================

            print(file[0])

            content = file[0]["datapath"]
            with open(content, "r") as f:
                file_content = f.read()
            return ui.div(
                ui.h3(f"Contents of {file[0]['name']}:"), ui.pre(file_content)
            )
        return ui.p("No file uploaded yet.")

    @reactive.Effect
    @reactive.event(input.create_upload)
    async def _():
        await session.send_custom_message("setFileName", example_file["name"])


app = App(app_ui, server)

@schloerke
Copy link
Collaborator

This should also be able to reset file with a "" file value

This should also be able use the session file upload manager. Essentially we are uploading the contents already on the server and can use that for the per session cache

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

No branches or pull requests

3 participants