| import gradio as gr | |
| from app import demo as app | |
| import os | |
| _docs = {'FolderExplorer': {'description': 'Creates a folder explorer component that allows users to browse and select a directory\non the machine hosting the Gradio app. The output of this component is the absolute\npath to the selected directory, which can then be used as input to another function.', 'members': {'__init__': {'value': {'type': 'str | Path | Callable | None', 'default': 'None', 'description': 'The directory path to show as "selected" when the component is first loaded. If a callable is provided, it will be called when the app loads to set the initial value.'}, 'root_dir': {'type': 'str | Path', 'default': '"."', 'description': 'Path to the root directory from which to browse folders. If not provided, defaults to the current working directory. Raises ValueError if the directory does not exist or is not a directory.'}, 'label': {'type': 'str | I18nData | None', 'default': 'None', 'description': 'The label for this component. Appears above the component and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to.'}, 'every': {'type': 'Timer | float | None', 'default': 'None', 'description': 'Continously calls `value` to recalculate it if `value` is a function (has no effect otherwise). Can provide a Timer whose tick resets `value`, or a float that provides the regular interval for the reset Timer.'}, 'inputs': {'type': 'Component | Sequence[Component] | set[Component] | None', 'default': 'None', 'description': 'Components that are used as inputs to calculate `value` if `value` is a function (has no effect otherwise). `value` is recalculated any time the inputs change.'}, 'show_label': {'type': 'bool | None', 'default': 'None', 'description': 'If True, will display the label.'}, 'container': {'type': 'bool', 'default': 'True', 'description': 'If True, will place the component in a container, providing some extra padding around the border.'}, 'scale': {'type': 'int | None', 'default': 'None', 'description': 'Relative size compared to adjacent Components. For example if Components A and B are in a Row, and A has scale=2, and B has scale=1, A will be twice as wide as B. Should be an integer.'}, 'min_width': {'type': 'int', 'default': '160', 'description': 'Minimum pixel width, will wrap if not sufficient screen space to satisfy this value.'}, 'height': {'type': 'int | str | None', 'default': 'None', 'description': 'The height of the component, specified in pixels if a number is passed, or in CSS units if a string is passed.'}, 'max_height': {'type': 'int | str | None', 'default': '500', 'description': 'The maximum height of the component.'}, 'min_height': {'type': 'int | str | None', 'default': 'None', 'description': 'The minimum height of the component.'}, 'interactive': {'type': 'bool | None', 'default': 'None', 'description': 'If True, will allow users to select a folder; if False, will only display the folder structure.'}, 'visible': {'type': 'bool | Literal["hidden"]', 'default': 'True', 'description': 'If False, component will be hidden. If "hidden", component will be visually hidden but still exist in the DOM.'}, 'elem_id': {'type': 'str | None', 'default': 'None', 'description': 'An optional string that is assigned as the id of this component in the HTML DOM.'}, 'elem_classes': {'type': 'list[str] | str | None', 'default': 'None', 'description': 'An optional list of strings that are assigned as the classes of this component in the HTML DOM.'}, 'render': {'type': 'bool', 'default': 'True', 'description': 'If False, component will not be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.'}, 'key': {'type': 'int | str | tuple[int | str, ...] | None', 'default': 'None', 'description': 'In a gr.render, Components with the same key across re-renders are treated as the same component.'}, 'preserved_by_key': {'type': 'list[str] | str | None', 'default': '"value"', 'description': "A list of parameters from this component's constructor. If a component is re-rendered with the same key, these parameters will be preserved in the UI."}}, 'postprocess': {'value': {'type': 'str | pathlib.Path | None', 'description': 'Expects a `str` or `Path` object representing the directory to be selected in the UI.'}}, 'preprocess': {'return': {'type': 'str | None', 'description': 'The absolute path of the selected folder as a `str`. Returns the root directory if no folder is selected.'}, 'value': None}}, 'events': {'change': {'type': None, 'default': None, 'description': ''}}}, '__meta__': {'additional_interfaces': {}, 'user_fn_refs': {'FolderExplorer': []}}} | |
| abs_path = os.path.join(os.path.dirname(__file__), "css.css") | |
| with gr.Blocks( | |
| css=abs_path, | |
| theme=gr.themes.Ocean( | |
| font_mono=[ | |
| gr.themes.GoogleFont("Inconsolata"), | |
| "monospace", | |
| ], | |
| ), | |
| ) as demo: | |
| gr.Markdown( | |
| """ | |
| # `gradio_folderexplorer` | |
| <div style="display: flex; gap: 7px;"> | |
| <img alt="Static Badge" src="https://img.shields.io/badge/version%20-%200.0.1%20-%20orange"> | |
| </div> | |
| A FolderExplorer for Gradio UI | |
| """, elem_classes=["md-custom"], header_links=True) | |
| app.render() | |
| gr.Markdown( | |
| """ | |
| ## Installation | |
| ```bash | |
| pip install gradio_folderexplorer | |
| ``` | |
| ## Usage | |
| ```python | |
| import gradio as gr | |
| from gradio_folderexplorer import FolderExplorer | |
| from gradio_folderexplorer.helpers import load_media_from_folder | |
| from PIL import Image | |
| import os | |
| # --- Configuration Constants --- | |
| # Define the root directory for the FolderExplorer to start in. | |
| # All browsable folders will be relative to this path. | |
| ROOT_DIR_PATH = "./examples" | |
| # --- UI Layout and Logic --- | |
| # Create the user interface using Gradio Blocks. | |
| with gr.Blocks(theme=gr.themes.Ocean()) as demo: | |
| # A single row is used to create a side-by-side layout. | |
| gr.Markdown("# FolderExplorer Component Demo") | |
| with gr.Row(equal_height=True): | |
| # The first column contains the custom folder explorer component. | |
| with gr.Column(scale=1, min_width=300): | |
| folder_explorer = FolderExplorer( | |
| label="Select a Folder", | |
| root_dir=ROOT_DIR_PATH, | |
| # Set the initial selected value to the root directory itself. | |
| # This is used by the demo.load() event. | |
| value=ROOT_DIR_PATH | |
| ) | |
| # The second column contains the gallery to display the media. | |
| with gr.Column(scale=3): | |
| gallery = gr.Gallery( | |
| label="Selected Images", | |
| columns=6, | |
| height="auto", | |
| ) | |
| # --- Event Handling --- | |
| # 1. Event for user interaction: | |
| # When the user selects a new folder in the FolderExplorer, the .change() event | |
| # is triggered. The `load_media_from_folder` helper is called with the new | |
| # folder path, and its output populates the gallery. | |
| folder_explorer.change( | |
| fn=load_media_from_folder, | |
| inputs=folder_explorer, | |
| outputs=gallery | |
| ) | |
| # 2. Event for initial page load: | |
| # This event runs once when the app starts. It takes the initial `value` of the | |
| # folder_explorer ('ROOT_DIR_PATH'), passes it to the helper function, | |
| # and populates the gallery with the media from the root directory. | |
| demo.load( | |
| fn=load_media_from_folder, | |
| inputs=folder_explorer, | |
| outputs=gallery | |
| ) | |
| # --- Application Launch --- | |
| if __name__ == "__main__": | |
| demo.launch() | |
| ``` | |
| """, elem_classes=["md-custom"], header_links=True) | |
| gr.Markdown(""" | |
| ## `FolderExplorer` | |
| ### Initialization | |
| """, elem_classes=["md-custom"], header_links=True) | |
| gr.ParamViewer(value=_docs["FolderExplorer"]["members"]["__init__"], linkify=[]) | |
| gr.Markdown("### Events") | |
| gr.ParamViewer(value=_docs["FolderExplorer"]["events"], linkify=['Event']) | |
| gr.Markdown(""" | |
| ### User function | |
| The impact on the users predict function varies depending on whether the component is used as an input or output for an event (or both). | |
| - When used as an Input, the component only impacts the input signature of the user function. | |
| - When used as an output, the component only impacts the return signature of the user function. | |
| The code snippet below is accurate in cases where the component is used as both an input and an output. | |
| - **As input:** Is passed, the absolute path of the selected folder as a `str`. Returns the root directory if no folder is selected. | |
| - **As output:** Should return, expects a `str` or `Path` object representing the directory to be selected in the UI. | |
| ```python | |
| def predict( | |
| value: str | None | |
| ) -> str | pathlib.Path | None: | |
| return value | |
| ``` | |
| """, elem_classes=["md-custom", "FolderExplorer-user-fn"], header_links=True) | |
| demo.load(None, js=r"""function() { | |
| const refs = {}; | |
| const user_fn_refs = { | |
| FolderExplorer: [], }; | |
| requestAnimationFrame(() => { | |
| Object.entries(user_fn_refs).forEach(([key, refs]) => { | |
| if (refs.length > 0) { | |
| const el = document.querySelector(`.${key}-user-fn`); | |
| if (!el) return; | |
| refs.forEach(ref => { | |
| el.innerHTML = el.innerHTML.replace( | |
| new RegExp("\\b"+ref+"\\b", "g"), | |
| `<a href="#h-${ref.toLowerCase()}">${ref}</a>` | |
| ); | |
| }) | |
| } | |
| }) | |
| Object.entries(refs).forEach(([key, refs]) => { | |
| if (refs.length > 0) { | |
| const el = document.querySelector(`.${key}`); | |
| if (!el) return; | |
| refs.forEach(ref => { | |
| el.innerHTML = el.innerHTML.replace( | |
| new RegExp("\\b"+ref+"\\b", "g"), | |
| `<a href="#h-${ref.toLowerCase()}">${ref}</a>` | |
| ); | |
| }) | |
| } | |
| }) | |
| }) | |
| } | |
| """) | |
| demo.launch() | |